All checks were successful
Build & Push / build-and-push (push) Successful in 1m23s
- Reduced py-24 → py-20 on Services, Configurator, Process, Works, Philosophy - TrustBar: py-16 → py-12, gradient bridge adjusted - Services: pillar divider opacity increased (outline/20), header gap tightened - Configurator: added subtle dividers between service cards - SelectedWorks: darkened coming-soon text and border opacity - Philosophy: confirmed 2px primary border on pillars Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
195 lines
6.7 KiB
TypeScript
195 lines
6.7 KiB
TypeScript
'use client';
|
||
|
||
import { motion, type Variants } from 'framer-motion';
|
||
import { useTranslations } from 'next-intl';
|
||
import { Search, LayoutDashboard, PenTool, Rocket, type LucideIcon } from 'lucide-react';
|
||
import { cn } from '@/lib/utils';
|
||
import {
|
||
staggerContainerWide,
|
||
revealVariants,
|
||
viewportOnce,
|
||
} from '@/lib/animations';
|
||
import SectionHeader from '@/components/ui/SectionHeader';
|
||
|
||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||
|
||
interface Step {
|
||
numeral: string;
|
||
key: 'discovery' | 'strategy' | 'build' | 'launch';
|
||
Icon: LucideIcon;
|
||
}
|
||
|
||
// ─── Data ─────────────────────────────────────────────────────────────────────
|
||
|
||
const STEPS: Step[] = [
|
||
{ numeral: '01', key: 'discovery', Icon: Search },
|
||
{ numeral: '02', key: 'strategy', Icon: LayoutDashboard },
|
||
{ numeral: '03', key: 'build', Icon: PenTool },
|
||
{ numeral: '04', key: 'launch', Icon: Rocket },
|
||
];
|
||
|
||
// ─── Variants ─────────────────────────────────────────────────────────────────
|
||
|
||
// Numeral scales up from 80% while fading in
|
||
const numeralScaleVariants: Variants = {
|
||
hidden: { opacity: 0, scale: 0.8 },
|
||
visible: {
|
||
opacity: 1,
|
||
scale: 1,
|
||
transition: {
|
||
duration: 0.7,
|
||
ease: [0.16, 1, 0.3, 1],
|
||
},
|
||
},
|
||
};
|
||
|
||
// ─── Sub-components ───────────────────────────────────────────────────────────
|
||
|
||
function StepCard({ numeral, stepKey, Icon }: { numeral: string; stepKey: string; Icon: LucideIcon }) {
|
||
const t = useTranslations();
|
||
const title = t(`process.steps.${stepKey}.title`);
|
||
const description = t(`process.steps.${stepKey}.description`);
|
||
|
||
return (
|
||
<motion.div
|
||
variants={revealVariants}
|
||
className={cn(
|
||
'relative flex flex-col bg-surface-high rounded-xl pt-0 px-6 pb-6 overflow-hidden',
|
||
'shadow-subtle',
|
||
)}
|
||
>
|
||
{/* Top accent strip — gradient from primary-dark to primary */}
|
||
<div
|
||
className="absolute top-0 left-0 right-0 h-[3px] rounded-t-xl"
|
||
style={{ background: 'linear-gradient(90deg, #006494, #5BA4D9)' }}
|
||
aria-hidden="true"
|
||
/>
|
||
|
||
{/* Ghosted numeral — scales up on scroll */}
|
||
<motion.span
|
||
variants={numeralScaleVariants}
|
||
aria-hidden="true"
|
||
className="font-serif text-6xl font-light leading-none text-on-surface/[0.12] select-none -ml-0.5 mb-3 mt-6"
|
||
>
|
||
{numeral}
|
||
</motion.span>
|
||
|
||
{/* Icon container */}
|
||
<div className="mb-4">
|
||
<div className="w-12 h-12 rounded-xl bg-primary/8 flex items-center justify-center">
|
||
<Icon
|
||
size={22}
|
||
strokeWidth={1.5}
|
||
className="text-primary"
|
||
aria-hidden="true"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Title */}
|
||
<h3 className="font-semibold text-lg text-on-surface mb-2 leading-snug">
|
||
{title}
|
||
</h3>
|
||
|
||
{/* Description */}
|
||
<p className="text-sm text-outline leading-relaxed">
|
||
{description}
|
||
</p>
|
||
</motion.div>
|
||
);
|
||
}
|
||
|
||
// ─── Main Component ───────────────────────────────────────────────────────────
|
||
|
||
export default function Process() {
|
||
const t = useTranslations();
|
||
|
||
return (
|
||
<section id="process" className="bg-surface-low py-20">
|
||
<div className="container mx-auto px-6">
|
||
|
||
{/*
|
||
Desktop layout:
|
||
col 1 (lg:col-span-1) → SectionHeader, flush left
|
||
col 2–4 (lg:col-span-3) → 2×2 grid of step cards
|
||
|
||
Mobile layout:
|
||
header on top, steps in single column below
|
||
*/}
|
||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-12 lg:gap-10 items-start">
|
||
|
||
{/* ── Header column ── */}
|
||
<div className="lg:col-span-1 lg:sticky lg:top-32">
|
||
{/* Vertical accent bar left of heading */}
|
||
<div className="relative pl-4">
|
||
<div
|
||
className="absolute left-0 top-[3.25rem] w-0.5 h-10 rounded-full bg-primary"
|
||
aria-hidden="true"
|
||
/>
|
||
<SectionHeader
|
||
eyebrow={t('process.eyebrow')}
|
||
title={t('process.title')}
|
||
align="left"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── Steps column ── */}
|
||
<div className="lg:col-span-3">
|
||
<div className="relative">
|
||
{/* Dashed connector line — visible on sm+ grid layouts only */}
|
||
<div
|
||
className="hidden sm:block absolute inset-0 pointer-events-none"
|
||
aria-hidden="true"
|
||
>
|
||
<svg
|
||
className="w-full h-full"
|
||
preserveAspectRatio="none"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
>
|
||
{/* Horizontal dashes across the middle gap */}
|
||
<line
|
||
x1="50%" y1="50%"
|
||
x2="50%" y2="50%"
|
||
stroke="rgba(91,164,217,0.18)"
|
||
strokeWidth="1.5"
|
||
strokeDasharray="4 6"
|
||
/>
|
||
{/* Vertical dashes down the centre gap */}
|
||
<line
|
||
x1="0%" y1="50%"
|
||
x2="100%" y2="50%"
|
||
stroke="rgba(91,164,217,0.18)"
|
||
strokeWidth="1.5"
|
||
strokeDasharray="4 6"
|
||
/>
|
||
<line
|
||
x1="50%" y1="0%"
|
||
x2="50%" y2="100%"
|
||
stroke="rgba(91,164,217,0.18)"
|
||
strokeWidth="1.5"
|
||
strokeDasharray="4 6"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
|
||
<motion.div
|
||
variants={staggerContainerWide}
|
||
initial="hidden"
|
||
whileInView="visible"
|
||
viewport={viewportOnce}
|
||
className="grid grid-cols-1 sm:grid-cols-2 gap-5"
|
||
>
|
||
{STEPS.map((step) => (
|
||
<StepCard key={step.key} numeral={step.numeral} stepKey={step.key} Icon={step.Icon} />
|
||
))}
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|