Files
LetsBeBiz-Site/src/components/sections/Process.tsx
Matt 9e4afd215b
All checks were successful
Build & Push / build-and-push (push) Successful in 1m23s
polish: tighten spacing + visual refinements across all sections
- 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>
2026-03-25 21:55:23 +01:00

195 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 24 (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>
);
}