All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🚀 Deploy (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m46s
Build & Deploy / 🏗️ Build (push) Successful in 3m38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m19s
Build & Deploy / 🔔 Notify (push) Successful in 5s
193 lines
8.7 KiB
TypeScript
193 lines
8.7 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { m } from 'framer-motion';
|
|
import { useState, useEffect } from 'react';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
|
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
|
import { Container } from '@/components/ui/Container';
|
|
import { LogoArcs } from '@/components/ui/LogoArcs';
|
|
|
|
interface CompetenceBentoGridProps {
|
|
badge?: string;
|
|
title?: string;
|
|
ctaLabel?: string;
|
|
ctaHref?: string;
|
|
items?: {
|
|
title?: string;
|
|
description?: string;
|
|
tag?: string;
|
|
href?: string;
|
|
image?: {
|
|
url?: string;
|
|
alt?: string;
|
|
} | any;
|
|
size?: 'large' | 'medium' | 'small' | 'accent';
|
|
}[];
|
|
data?: {
|
|
badge?: string;
|
|
title?: string;
|
|
ctaLabel?: string;
|
|
ctaHref?: string;
|
|
items?: {
|
|
title?: string;
|
|
description?: string;
|
|
tag?: string;
|
|
href?: string;
|
|
image?: {
|
|
url?: string;
|
|
alt?: string;
|
|
} | any;
|
|
size?: 'large' | 'medium' | 'small' | 'accent';
|
|
}[];
|
|
};
|
|
}
|
|
|
|
export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
|
|
const { data } = props;
|
|
const [isMounted, setIsMounted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
setIsMounted(true);
|
|
}, []);
|
|
|
|
const badge = props.badge || data?.badge || 'Leistungsspektrum';
|
|
const title = props.title || data?.title || 'Umfassende Lösungen für komplexe Netzwerke';
|
|
const ctaLabel = props.ctaLabel || data?.ctaLabel || 'Alle Kompetenzen ansehen';
|
|
const ctaHref = props.ctaHref || data?.ctaHref || '/kompetenzen';
|
|
const items = props.items || data?.items || [];
|
|
|
|
// Static fallback for SSR
|
|
if (!isMounted) {
|
|
return (
|
|
<section className="py-16 md:py-24 lg:py-32 bg-neutral text-neutral-dark overflow-hidden relative">
|
|
<Container>
|
|
<div className="flex flex-col md:flex-row justify-between items-end mb-12">
|
|
<div className="max-w-2xl">
|
|
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
|
|
<h3 className="font-heading text-3xl md:text-5xl font-extrabold text-neutral-dark mb-4">{title}</h3>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[minmax(220px,auto)]">
|
|
{/* Simplified static grid */}
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="py-12 md:py-24 bg-neutral text-neutral-dark relative overflow-hidden">
|
|
<Container className="relative z-10 px-0 md:px-12 lg:px-16">
|
|
<div className="flex flex-row justify-between items-end mb-12 px-4 md:px-0">
|
|
<div className="max-w-2xl text-left">
|
|
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
|
|
<h3 className="font-heading text-3xl md:text-5xl font-extrabold text-neutral-dark mb-4">{title}</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[minmax(220px,auto)] px-4 md:px-0">
|
|
{items.map((item, idx) => {
|
|
const isLarge = item.size === 'large';
|
|
const isMedium = item.size === 'medium';
|
|
const isAccent = item.size === 'accent';
|
|
const imgSrc = item.image?.url;
|
|
|
|
let gridClasses = "rounded-2xl overflow-hidden relative group select-none ";
|
|
if (isLarge) gridClasses += "md:col-span-2 md:row-span-2";
|
|
else if (isMedium) gridClasses += "md:col-span-2 lg:col-span-2";
|
|
else if (isAccent) gridClasses += "md:col-span-1 bg-primary text-white p-6 md:p-8 flex flex-col justify-center border-none shadow-md";
|
|
else gridClasses += "md:col-span-1";
|
|
|
|
if (isAccent) {
|
|
return (
|
|
<m.div
|
|
key={idx}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, margin: "-50px" }}
|
|
transition={{ delay: idx * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
|
|
className={`${gridClasses} ${item.href ? 'cursor-pointer hover:-translate-y-1 transition-transform duration-300' : ''}`}
|
|
>
|
|
{item.href && (
|
|
<Link href={item.href} className="absolute inset-0 z-30" aria-label={item.title} />
|
|
)}
|
|
<div className="absolute top-0 right-0 w-32 h-32 bg-primary/30 blur-3xl rounded-full -mr-10 -mt-10 group-hover:bg-primary/50 transition-colors duration-500" />
|
|
|
|
{/* Subtle Background Logo Arcs */}
|
|
<div className={`absolute -bottom-[30%] -right-[30%] w-[120%] h-[120%] z-0 opacity-10 group-hover:opacity-20 transition-opacity duration-1000 pointer-events-none mix-blend-overlay`}>
|
|
<LogoArcs className={`w-full h-full text-white animate-[spin_60s_linear_infinite]`} />
|
|
</div>
|
|
|
|
<HoverShineOverlay shineColor="via-white/30" />
|
|
<AnimatedGlossyBorder color="white" className="opacity-0 group-hover:opacity-100 transition-opacity duration-700" borderWidth={1} />
|
|
<h4 className="font-heading font-extrabold text-2xl md:text-3xl relative z-10 leading-snug">{item.title}</h4>
|
|
{item.description && <p className="text-white/80 text-base md:text-lg mt-3 relative z-10 font-medium">{item.description}</p>}
|
|
</m.div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<m.div
|
|
key={idx}
|
|
initial={{ opacity: 0, y: 20, scale: 0.98 }}
|
|
whileInView={{ opacity: 1, y: 0, scale: 1 }}
|
|
viewport={{ once: true, margin: "-50px" }}
|
|
transition={{ delay: idx * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
|
|
className={`${gridClasses} ${item.href ? 'cursor-pointer hover:-translate-y-1 transition-transform duration-300' : ''}`}
|
|
>
|
|
{item.href && (
|
|
<Link href={item.href} className="absolute inset-0 z-30" aria-label={item.title} />
|
|
)}
|
|
{!imgSrc && (
|
|
<div className="absolute inset-0 bg-neutral-dark z-0 transition-colors duration-500 group-hover:bg-primary-dark" />
|
|
)}
|
|
{imgSrc && (
|
|
<>
|
|
<div className="absolute inset-0 bg-white z-0">
|
|
<Image
|
|
src={imgSrc}
|
|
alt={item.title || ''}
|
|
fill
|
|
className="object-cover opacity-90 group-hover:scale-105 group-hover:opacity-100 transition-all duration-700 ease-in-out"
|
|
sizes={isLarge ? "(max-width: 768px) 100vw, 50vw" : "(max-width: 768px) 100vw, 25vw"}
|
|
/>
|
|
</div>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/60 to-black/30 opacity-100 transition-opacity duration-500 z-0 group-hover:from-black/80 group-hover:via-black/50 group-hover:to-black/20" />
|
|
</>
|
|
)}
|
|
|
|
<HoverShineOverlay />
|
|
<AnimatedGlossyBorder color="white" className="opacity-0 group-hover:opacity-100 transition-opacity duration-700" borderWidth={1} />
|
|
|
|
{/* Subtle Background Logo Arcs */}
|
|
<div className="absolute -top-[30%] -left-[30%] w-[120%] h-[120%] z-0 opacity-0 group-hover:opacity-[0.05] transition-opacity duration-1000 pointer-events-none mix-blend-overlay">
|
|
<LogoArcs className={`w-full h-full text-white animate-[spin_80s_linear_infinite]`} />
|
|
</div>
|
|
|
|
<div className="relative z-10 p-8 flex flex-col justify-end h-full text-white">
|
|
{item.tag && (
|
|
<span className="inline-block px-3 py-1 bg-primary/20 backdrop-blur-md rounded-full text-xs font-bold uppercase tracking-wider text-primary-light mb-3 w-fit border border-primary/30">
|
|
{item.tag}
|
|
</span>
|
|
)}
|
|
<h4 className="font-heading font-extrabold mb-2 text-2xl md:text-3xl">
|
|
{item.title}
|
|
</h4>
|
|
{item.description && (
|
|
<p className="text-white/90 text-sm md:text-base max-w-md">
|
|
{item.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</m.div>
|
|
);
|
|
})}
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|