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
235 lines
11 KiB
TypeScript
235 lines
11 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { m, Variants } from 'framer-motion';
|
|
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
|
|
|
export interface ServicePanelData {
|
|
id: string;
|
|
title: string;
|
|
icon: 'energy' | 'fiber' | 'hdd' | 'planning' | 'survey';
|
|
fullWidth?: boolean;
|
|
lists: string[][];
|
|
}
|
|
|
|
interface ServiceDetailGridProps {
|
|
badge?: string;
|
|
title?: string;
|
|
descriptionParagraphs?: string[];
|
|
problemStatement?: {
|
|
title: string;
|
|
text: string;
|
|
};
|
|
solutionStatement?: {
|
|
title: string;
|
|
text: string;
|
|
};
|
|
panels: ServicePanelData[];
|
|
}
|
|
|
|
const containerVariants: Variants = {
|
|
hidden: { opacity: 0 },
|
|
visible: {
|
|
opacity: 1,
|
|
transition: {
|
|
staggerChildren: 0.1,
|
|
},
|
|
},
|
|
};
|
|
|
|
const itemVariants: Variants = {
|
|
hidden: { opacity: 0, y: 20 },
|
|
visible: { opacity: 1, y: 0, transition: { duration: 1.0, ease: [0.16, 1, 0.3, 1] } },
|
|
};
|
|
|
|
const Icons = {
|
|
energy: (
|
|
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
|
|
<path d="M32 4L12 36H30L26 60L52 24H34L32 4Z" stroke="currentColor" strokeWidth="1" strokeLinejoin="miter"/>
|
|
<circle cx="32" cy="32" r="30" stroke="currentColor" strokeWidth="1" strokeDasharray="4 4"/>
|
|
<line x1="32" y1="2" x2="32" y2="62" stroke="currentColor" strokeWidth="0.5" strokeDasharray="2 2"/>
|
|
<line x1="2" y1="32" x2="62" y2="32" stroke="currentColor" strokeWidth="0.5" strokeDasharray="2 2"/>
|
|
</svg>
|
|
),
|
|
fiber: (
|
|
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
|
|
<circle cx="32" cy="32" r="8" stroke="currentColor" strokeWidth="1"/>
|
|
<circle cx="12" cy="16" r="4" stroke="currentColor" strokeWidth="1"/>
|
|
<circle cx="52" cy="16" r="4" stroke="currentColor" strokeWidth="1"/>
|
|
<circle cx="12" cy="48" r="4" stroke="currentColor" strokeWidth="1"/>
|
|
<circle cx="52" cy="48" r="4" stroke="currentColor" strokeWidth="1"/>
|
|
<path d="M15 19L27 28M49 19L37 28M15 45L27 36M49 45L37 36M32 40V60M32 4V24" stroke="currentColor" strokeWidth="1"/>
|
|
<rect x="2" y="2" width="60" height="60" stroke="currentColor" strokeWidth="0.5" strokeDasharray="2 4"/>
|
|
</svg>
|
|
),
|
|
hdd: (
|
|
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
|
|
<path d="M4 32H20L28 20H44L52 32H60" stroke="currentColor" strokeWidth="1" strokeLinejoin="miter"/>
|
|
<path d="M28 44H44L52 32" stroke="currentColor" strokeWidth="1" strokeLinejoin="miter" strokeDasharray="4 4"/>
|
|
<circle cx="12" cy="32" r="2" fill="currentColor"/>
|
|
<circle cx="36" cy="20" r="2" fill="currentColor"/>
|
|
<circle cx="56" cy="32" r="2" fill="currentColor"/>
|
|
<path d="M0 48H64" stroke="currentColor" strokeWidth="1"/>
|
|
<path d="M0 54H64M0 60H64" stroke="currentColor" strokeWidth="0.5" strokeDasharray="2 2"/>
|
|
</svg>
|
|
),
|
|
planning: (
|
|
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
|
|
<path d="M10 54L32 10L54 54" stroke="currentColor" strokeWidth="1"/>
|
|
<path d="M20 34H44" stroke="currentColor" strokeWidth="1"/>
|
|
<circle cx="32" cy="10" r="4" stroke="currentColor" strokeWidth="1"/>
|
|
<rect x="4" y="4" width="56" height="56" stroke="currentColor" strokeWidth="0.5" strokeDasharray="2 2"/>
|
|
<line x1="4" y1="20" x2="60" y2="20" stroke="currentColor" strokeWidth="0.5" strokeDasharray="1 3"/>
|
|
<line x1="4" y1="44" x2="60" y2="44" stroke="currentColor" strokeWidth="0.5" strokeDasharray="1 3"/>
|
|
</svg>
|
|
),
|
|
survey: (
|
|
<svg width="128" height="64" viewBox="0 0 128 64" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
|
|
<path d="M10 32H118" stroke="currentColor" strokeWidth="1"/>
|
|
<path d="M64 10V54" stroke="currentColor" strokeWidth="1"/>
|
|
<circle cx="64" cy="32" r="16" stroke="currentColor" strokeWidth="1"/>
|
|
<circle cx="64" cy="32" r="24" stroke="currentColor" strokeWidth="0.5" strokeDasharray="4 4"/>
|
|
<path d="M64 24L72 32L64 40L56 32L64 24Z" stroke="currentColor" strokeWidth="1"/>
|
|
<rect x="4" y="4" width="120" height="56" stroke="currentColor" strokeWidth="0.5" strokeDasharray="2 4"/>
|
|
<path d="M24 32V24M44 32V28M84 32V28M104 32V24" stroke="currentColor" strokeWidth="1"/>
|
|
</svg>
|
|
)
|
|
};
|
|
|
|
export function ServiceDetailGrid({
|
|
badge,
|
|
title,
|
|
descriptionParagraphs,
|
|
problemStatement,
|
|
solutionStatement,
|
|
panels
|
|
}: ServiceDetailGridProps) {
|
|
return (
|
|
<section className="bg-neutral-50 py-16 md:py-24 lg:py-32 border-b border-neutral-100 relative overflow-hidden">
|
|
|
|
<div className="container relative z-10 px-4 max-w-7xl mx-auto">
|
|
<div className="max-w-3xl mb-12 md:mb-20 text-left">
|
|
{badge && <h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-4">{badge}</h2>}
|
|
{title && (
|
|
<h3 className="font-heading text-2xl md:text-5xl font-extrabold text-neutral-dark mb-6">
|
|
{title}
|
|
</h3>
|
|
)}
|
|
<div className="w-20 h-2 bg-primary mb-8"></div>
|
|
|
|
<div className="space-y-6 text-text-secondary text-lg leading-relaxed">
|
|
{descriptionParagraphs?.map((p, i) => {
|
|
const parts = p.split('**');
|
|
if (parts.length > 1) {
|
|
return (
|
|
<p key={i}>
|
|
{parts.map((part, j) => j % 2 === 1 ? <strong key={j} className="text-neutral-dark font-bold">{part}</strong> : part)}
|
|
</p>
|
|
);
|
|
}
|
|
return <p key={i}>{p}</p>;
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{(problemStatement || solutionStatement) && (
|
|
<m.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, margin: "-50px" }}
|
|
transition={{ duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
|
|
className="max-w-4xl mx-auto mb-20 grid md:grid-cols-2 gap-0 bg-white rounded-2xl overflow-hidden shadow-lg border border-neutral-200 text-left"
|
|
>
|
|
{problemStatement && (
|
|
<div className="p-6 md:p-12 border-b md:border-b-0 md:border-r border-neutral-100 bg-red-50/50">
|
|
<div className="flex items-center mb-6 text-red-600">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-3">
|
|
<circle cx="12" cy="12" r="10"></circle>
|
|
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
</svg>
|
|
<h4 className="font-bold text-xl">{problemStatement.title}</h4>
|
|
</div>
|
|
<p className="text-text-secondary leading-relaxed">{problemStatement.text}</p>
|
|
</div>
|
|
)}
|
|
{solutionStatement && (
|
|
<div className="p-6 md:p-12 bg-primary/5">
|
|
<div className="flex items-center mb-6 text-primary">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-3">
|
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
|
|
<polyline points="22 4 12 14.01 9 11.01"></polyline>
|
|
</svg>
|
|
<h4 className="font-bold text-xl">{solutionStatement.title}</h4>
|
|
</div>
|
|
<p className="text-text-secondary leading-relaxed">{solutionStatement.text}</p>
|
|
</div>
|
|
)}
|
|
</m.div>
|
|
)}
|
|
|
|
<m.div
|
|
className="grid grid-cols-1 md:grid-cols-2 gap-8"
|
|
variants={containerVariants}
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
viewport={{ once: true, margin: "-50px" }}
|
|
>
|
|
{panels.map((panel, idx) => {
|
|
const isFullWidth = panel.fullWidth;
|
|
const number = String(idx + 1).padStart(2, '0');
|
|
|
|
return (
|
|
<m.div
|
|
key={panel.id}
|
|
variants={itemVariants}
|
|
className={`group relative overflow-hidden bg-white border border-neutral-200 rounded-3xl p-6 md:p-12 transition-all duration-500 hover:border-primary/40 hover:shadow-[0_20px_40px_-15px_rgba(0,0,0,0.05)] hover:-translate-y-1 select-none ${isFullWidth ? 'md:col-span-2' : ''}`}
|
|
>
|
|
{/* Premium Watermark Number */}
|
|
<div className="absolute -right-8 -bottom-12 text-[12rem] font-heading font-black text-neutral-50 z-0 pointer-events-none select-none transition-all duration-700 group-hover:scale-105 group-hover:-translate-y-4 group-hover:text-primary/[0.03]">
|
|
{number}
|
|
</div>
|
|
|
|
{/* On a white card, a very subtle primary/grey shine works best */}
|
|
<HoverShineOverlay shineColor="via-primary/5" />
|
|
|
|
<div className="relative z-10 flex flex-row justify-between items-start mb-12 gap-6">
|
|
<div className="pt-2 flex-1 min-w-0 pr-4">
|
|
<h4 className="text-lg md:text-2xl font-extrabold text-neutral-dark uppercase tracking-wider text-balance break-words" lang="de">{panel.title}</h4>
|
|
<div className="h-1 w-12 bg-primary mt-6 transition-all duration-700 group-hover:w-24 rounded-full"></div>
|
|
</div>
|
|
|
|
{/* Glossy Icon Container */}
|
|
<div className="relative shrink-0">
|
|
<div className="absolute inset-0 bg-primary/20 rounded-2xl blur-xl transition-all duration-500 group-hover:bg-primary/30 group-hover:blur-2xl" />
|
|
<div className={`relative flex items-center justify-center bg-white border border-primary/10 rounded-2xl shadow-sm text-primary transition-transform duration-700 group-hover:scale-110 group-hover:-rotate-3 ${isFullWidth ? 'w-32 h-20 p-4' : 'w-20 h-20 p-4'}`}>
|
|
{Icons[panel.icon]}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={`relative z-10 grid gap-y-2 gap-x-12 ${isFullWidth ? 'md:grid-cols-2' : 'grid-cols-1'}`}>
|
|
{panel.lists.map((list, listIdx) => (
|
|
<ul key={listIdx} className="space-y-1">
|
|
{list.map((item, itemIdx) => (
|
|
<li key={itemIdx} className="group/item flex items-start py-3 -mx-4 px-4 rounded-xl hover:bg-neutral-50/80 transition-colors">
|
|
<span className="text-primary mr-4 mt-1 opacity-80 group-hover/item:translate-x-1 transition-transform">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
|
|
<polyline points="9 18 15 12 9 6"></polyline>
|
|
</svg>
|
|
</span>
|
|
<span className="leading-snug text-text-secondary group-hover/item:text-neutral-900 transition-colors font-medium">{item}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
))}
|
|
</div>
|
|
</m.div>
|
|
);
|
|
})}
|
|
</m.div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|