polish: deep visual pass across all homepage sections
All checks were successful
Build & Push / build-and-push (push) Successful in 1m26s

Hero:
- Removed fake trust proof avatars
- Added decorative gradient separator line
- Pushed content down for better vertical rhythm
- More visible secondary CTA button

TrustBar:
- Icon circles with primary tint backdrops
- Gradient top accent bar on hover
- Gradient fade transition from hero section
- Increased card padding

Services:
- Subtle grid texture background
- Larger primary-colored feature dots
- Enhanced AI narrative with horizontal decorative rules
- Hover shadow elevation on pillars

Configurator:
- Step numbers in tinted circles with vertical rail
- ShieldCheck trust icon
- Radial gradient glow on wizard panel
- Sparkles icon on AI toggle
- Empty-state hint message
- Service card icons in circular primary backdrops

Process:
- Gradient top accent strips on cards
- Icon containers with primary tint
- Vertical accent bar on section heading
- Dashed SVG connector lines between cards

SelectedWorks:
- Distinct geometric compositions per card (Monaco, Nimara, Amador)
- Bottom gradient fade on placeholder images
- Dot separators between tags
- Animated background pulse on coming-soon cards
- Enhanced CTA hover animations

Philosophy:
- Enriched decorative panel (circle rings, diagonal lines, navy gradient)
- Primary-colored pillar numbers
- Left border accent on pull-quote card

CTABanner:
- Grid pattern + circle ring overlays
- Email link underline-on-hover animation

Footer:
- Full logo image replacing text wordmark
- Larger social icons
- Gradient divider line
- Larger nav logo (h-14 desktop, h-11 mobile)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 21:11:30 +01:00
parent a6882d517a
commit 7ca9f6f5e2
11 changed files with 644 additions and 212 deletions

View File

@@ -100,10 +100,10 @@ function ServiceCard({ option, selected, onToggle }: ServiceCardProps) {
{/* Icon */} {/* Icon */}
<div <div
className={cn( className={cn(
'flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center transition-colors duration-200', 'flex-shrink-0 w-10 h-10 rounded-full flex items-center justify-center transition-colors duration-200',
selected selected
? 'bg-primary/15 text-primary-dark' ? 'bg-primary/15 text-primary-dark'
: 'bg-surface-low text-outline group-hover:bg-primary/10 group-hover:text-primary', : 'bg-primary/8 text-primary group-hover:bg-primary/15 group-hover:text-primary-dark',
)} )}
> >
<Icon size={20} strokeWidth={1.5} /> <Icon size={20} strokeWidth={1.5} />
@@ -175,22 +175,23 @@ function AIToggle({ enabled, onToggle }: AIToggleProps) {
? 'bg-primary/5 shadow-card' ? 'bg-primary/5 shadow-card'
: 'bg-surface-high shadow-subtle hover:shadow-card', : 'bg-surface-high shadow-subtle hover:shadow-card',
)} )}
>
<div className="flex-1 min-w-0">
<span
className={cn(
'text-sm font-semibold transition-colors duration-200 flex items-center gap-1.5',
enabled ? 'text-primary-dark' : 'text-on-surface',
)}
> >
<Sparkles <Sparkles
size={16} size={14}
strokeWidth={1.5} strokeWidth={1.75}
className={cn( className={cn(
'flex-shrink-0 transition-colors duration-200', 'flex-shrink-0 transition-colors duration-200',
enabled ? 'text-primary' : 'text-outline', enabled ? 'text-primary' : 'text-outline',
)} )}
aria-hidden="true"
/> />
<div className="flex-1 min-w-0">
<span
className={cn(
'text-sm font-medium transition-colors duration-200',
enabled ? 'text-primary-dark' : 'text-on-surface',
)}
>
Enhance with AI Enhance with AI
</span> </span>
<p className="text-xs text-outline mt-0.5"> <p className="text-xs text-outline mt-0.5">
@@ -270,6 +271,20 @@ export default function StepServices({ formData, setFormData, onNext }: StepProp
onToggle={() => toggleService(option.id)} onToggle={() => toggleService(option.id)}
/> />
))} ))}
{/* Empty-state hint */}
<AnimatePresence>
{formData.services.length === 0 && (
<motion.p
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.2 }}
className="text-xs text-outline/60 text-center pt-1 pb-0.5 select-none"
>
Select at least one service to continue
</motion.p>
)}
</AnimatePresence>
</div> </div>
{/* AI Toggle */} {/* AI Toggle */}

View File

@@ -1,6 +1,7 @@
'use client' 'use client'
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
import Image from 'next/image'
import { Link } from '@/i18n/navigation' import { Link } from '@/i18n/navigation'
import CalButton from '@/components/ui/CalButton' import CalButton from '@/components/ui/CalButton'
@@ -111,12 +112,13 @@ export default function Footer() {
{/* Col 1 — Brand */} {/* Col 1 — Brand */}
<div className="sm:col-span-2 lg:col-span-1 flex flex-col gap-5"> <div className="sm:col-span-2 lg:col-span-1 flex flex-col gap-5">
<p <Image
className="font-serif text-3xl text-on-surface tracking-tight leading-none" src="/images/LogoLetsBe_Biz_celesBlue.png"
aria-label="LetsBe." alt="LetsBe."
> width={180}
LetsBe. height={60}
</p> className="h-12 w-auto object-contain"
/>
<p className="text-sm text-on-surface/55 leading-relaxed max-w-[260px]"> <p className="text-sm text-on-surface/55 leading-relaxed max-w-[260px]">
{t('tagline')} {t('tagline')}
</p> </p>
@@ -175,7 +177,7 @@ export default function Footer() {
className="inline-flex items-center justify-center w-9 h-9 rounded-full text-on-surface/40 hover:text-primary transition-all duration-200 hover:-translate-y-0.5" className="inline-flex items-center justify-center w-9 h-9 rounded-full text-on-surface/40 hover:text-primary transition-all duration-200 hover:-translate-y-0.5"
style={{ willChange: 'transform' }} style={{ willChange: 'transform' }}
> >
<Icon className="w-[18px] h-[18px]" /> <Icon className="w-5 h-5" />
</a> </a>
</li> </li>
))} ))}
@@ -192,10 +194,13 @@ export default function Footer() {
</div> </div>
</div> </div>
{/* Tonal divider */} {/* Gradient separator line */}
<div <div
className="mx-6 lg:mx-8 h-px" className="mx-6 lg:mx-8 h-px"
style={{ backgroundColor: 'rgba(25,28,29,0.07)' }} style={{
background:
'linear-gradient(90deg, transparent 0%, rgba(25,28,29,0.12) 25%, rgba(25,28,29,0.16) 50%, rgba(25,28,29,0.12) 75%, transparent 100%)',
}}
aria-hidden="true" aria-hidden="true"
/> />

View File

@@ -161,9 +161,9 @@ export default function Nav() {
<Image <Image
src="/images/letsbe-logo-short.png" src="/images/letsbe-logo-short.png"
alt="LetsBe." alt="LetsBe."
width={132} width={168}
height={44} height={56}
className="h-11 w-auto object-contain" className="h-14 w-auto object-contain"
priority priority
/> />
</Link> </Link>
@@ -256,9 +256,9 @@ export default function Nav() {
<Image <Image
src="/images/letsbe-logo-short.png" src="/images/letsbe-logo-short.png"
alt="LetsBe." alt="LetsBe."
width={100} width={132}
height={34} height={44}
className="h-8 w-auto object-contain" className="h-11 w-auto object-contain"
/> />
</Link> </Link>
<button <button

View File

@@ -109,6 +109,50 @@ function DecorativeBand() {
strokeDasharray="4 10" strokeDasharray="4 10"
/> />
</svg> </svg>
{/* Fine geometric line pattern — white at 3-4% opacity */}
<div
className="absolute inset-0"
style={{
backgroundImage:
'linear-gradient(rgba(255,255,255,0.035) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.035) 1px, transparent 1px)',
backgroundSize: '48px 48px',
}}
/>
{/* Diagonal accent lines crossing the grid */}
<div
className="absolute inset-0 opacity-[0.025]"
style={{
backgroundImage:
'repeating-linear-gradient(-45deg, #fff 0, #fff 1px, transparent 0, transparent 50%)',
backgroundSize: '40px 40px',
}}
/>
{/* Circle ring — large, right side */}
<div
className="absolute rounded-full"
style={{
width: '400px',
height: '400px',
top: '-100px',
right: '-80px',
border: '1px solid rgba(255,255,255,0.04)',
}}
/>
{/* Circle ring — smaller, left side */}
<div
className="absolute rounded-full"
style={{
width: '260px',
height: '260px',
bottom: '-60px',
left: '-40px',
border: '1px solid rgba(255,255,255,0.04)',
}}
/>
</div> </div>
); );
} }
@@ -198,6 +242,7 @@ export default function CTABanner() {
'transition-all duration-200', 'transition-all duration-200',
'hover:text-white hover:bg-white/8', 'hover:text-white hover:bg-white/8',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#006494]', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#006494]',
'group/email',
)} )}
> >
<Mail <Mail
@@ -206,7 +251,9 @@ export default function CTABanner() {
aria-hidden="true" aria-hidden="true"
strokeWidth={2} strokeWidth={2}
/> />
<span className="relative after:absolute after:bottom-0 after:left-0 after:h-px after:w-full after:bg-white/70 after:origin-left after:scale-x-0 after:transition-transform after:duration-200 group-hover/email:after:scale-x-100">
hello@letsbe.biz hello@letsbe.biz
</span>
</a> </a>
</motion.div> </motion.div>

View File

@@ -2,6 +2,7 @@
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { ShieldCheck } from 'lucide-react';
import { revealVariants, staggerContainer, viewportOnce } from '@/lib/animations'; import { revealVariants, staggerContainer, viewportOnce } from '@/lib/animations';
import WizardContainer from '@/components/configurator/WizardContainer'; import WizardContainer from '@/components/configurator/WizardContainer';
@@ -15,8 +16,8 @@ interface StepDotProps {
function StepDot({ index, label }: StepDotProps) { function StepDot({ index, label }: StepDotProps) {
return ( return (
<motion.div variants={revealVariants} className="flex items-center gap-3"> <motion.div variants={revealVariants} className="flex items-center gap-3">
<div className="w-5 h-5 rounded-full border border-outline-variant/60 bg-surface-high flex items-center justify-center flex-shrink-0"> <div className="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<span className="text-[10px] font-semibold text-outline">{index}</span> <span className="text-xs font-semibold text-primary-dark leading-none">{index}</span>
</div> </div>
<span className="text-sm text-outline">{label}</span> <span className="text-sm text-outline">{label}</span>
</motion.div> </motion.div>
@@ -81,21 +82,24 @@ export default function Configurator() {
<p className="text-xs font-semibold uppercase tracking-label text-outline/70"> <p className="text-xs font-semibold uppercase tracking-label text-outline/70">
How it works How it works
</p> </p>
<div className="flex flex-col gap-2.5"> {/* Vertical accent line + steps */}
<div className="flex gap-4">
<div className="flex-shrink-0 w-px bg-primary/20 ml-3 rounded-full" aria-hidden="true" />
<div className="flex flex-col gap-2.5 flex-1">
{steps.map((step, i) => ( {steps.map((step, i) => (
<StepDot key={i} index={i + 1} label={step} /> <StepDot key={i} index={i + 1} label={step} />
))} ))}
</div> </div>
</div>
</motion.div> </motion.div>
{/* Trust signal */} {/* Trust signal */}
<motion.div <motion.div
variants={revealVariants} variants={revealVariants}
className="pt-2 flex items-center gap-2.5" className="pt-1 flex items-center gap-2"
> >
<div className="h-px flex-1 bg-outline-variant/40" /> <ShieldCheck size={14} strokeWidth={1.75} className="text-primary flex-shrink-0" aria-hidden="true" />
<p className="text-xs text-outline">No commitment required</p> <p className="text-xs text-outline">No commitment required</p>
<div className="h-px flex-1 bg-outline-variant/40" />
</motion.div> </motion.div>
</motion.div> </motion.div>
</div> </div>
@@ -103,7 +107,15 @@ export default function Configurator() {
{/* ── Right: Wizard ───────────────────────────────────────────── */} {/* ── Right: Wizard ───────────────────────────────────────────── */}
<div className="lg:col-span-7"> <div className="lg:col-span-7">
<div className="rounded-2xl bg-surface-high shadow-subtle p-6 sm:p-8"> <div className="relative rounded-2xl bg-surface-high shadow-subtle p-6 sm:p-8 overflow-hidden">
{/* Radial gradient glow — top-left warmth */}
<div
className="pointer-events-none absolute -top-16 -left-16 w-72 h-72 rounded-full"
style={{
background: 'radial-gradient(circle, rgba(91,164,217,0.07) 0%, transparent 70%)',
}}
aria-hidden="true"
/>
<WizardContainer /> <WizardContainer />
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { viewportOnce } from '@/lib/animations';
import Button from '@/components/ui/Button'; import Button from '@/components/ui/Button';
import HeroGeometric from '@/components/icons/HeroGeometric'; import HeroGeometric from '@/components/icons/HeroGeometric';
@@ -100,40 +100,20 @@ const ctaVariant = {
}, },
}; };
// Trust strip slide in from left // Decorative separator line fade-in — after subtitle
const trustVariant = { const separatorVariant = {
hidden: { opacity: 0, x: -32 }, hidden: { opacity: 0, scaleX: 0 },
visible: { visible: {
opacity: 1, opacity: 1,
x: 0, scaleX: 1,
transition: { transition: {
duration: 0.55, duration: 0.6,
delay: 0.9, delay: 0.65,
ease: [0.16, 1, 0.3, 1] as [number, number, number, number], ease: [0.16, 1, 0.3, 1] as [number, number, number, number],
}, },
}, },
}; };
// Gradient stops for each avatar circle — subtle variations on the primary palette
const AVATAR_GRADIENTS = [
'linear-gradient(135deg, rgba(0,100,148,0.35), rgba(91,164,217,0.45))',
'linear-gradient(135deg, rgba(91,164,217,0.40), rgba(0,100,148,0.30))',
'linear-gradient(135deg, rgba(0,100,148,0.28), rgba(91,164,217,0.38))',
] as const;
function AvatarCircle({ index }: { index: number }) {
return (
<span
className={cn(
'inline-block w-8 h-8 rounded-full ring-2 ring-white',
index > 0 && '-ml-2',
)}
style={{ background: AVATAR_GRADIENTS[index] }}
aria-hidden="true"
/>
);
}
export default function Hero() { export default function Hero() {
const t = useTranslations('hero'); const t = useTranslations('hero');
@@ -187,7 +167,7 @@ export default function Hero() {
/> />
{/* ─── Content ──────────────────────────────────────────────────── */} {/* ─── Content ──────────────────────────────────────────────────── */}
<div className="relative z-10 w-full max-w-4xl mx-auto px-6 py-24 flex flex-col items-center text-center"> <div className="relative z-10 w-full max-w-4xl mx-auto px-6 pt-32 md:pt-40 pb-24 flex flex-col items-center text-center">
{/* Eyebrow */} {/* Eyebrow */}
<motion.span <motion.span
@@ -238,7 +218,7 @@ export default function Hero() {
{/* Subtitle */} {/* Subtitle */}
<motion.p <motion.p
className="text-lg text-outline leading-relaxed max-w-xl mb-10" className="text-lg text-outline leading-relaxed max-w-xl mb-8"
variants={subtitleVariant} variants={subtitleVariant}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
@@ -246,9 +226,22 @@ export default function Hero() {
{t('subtitle')} {t('subtitle')}
</motion.p> </motion.p>
{/* Decorative gradient separator */}
<motion.span
className="block w-20 h-px mb-10 origin-center"
style={{
background:
'linear-gradient(to right, rgba(0,100,148,0.3), rgba(91,164,217,0.3))',
}}
variants={separatorVariant}
initial="hidden"
animate="visible"
aria-hidden="true"
/>
{/* CTA row */} {/* CTA row */}
<motion.div <motion.div
className="flex flex-col sm:flex-row items-center gap-4 mb-12" className="flex flex-col sm:flex-row items-center gap-4"
variants={ctaVariant} variants={ctaVariant}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
@@ -256,28 +249,15 @@ export default function Hero() {
<Button variant="primary" size="lg" arrow href="#configure"> <Button variant="primary" size="lg" arrow href="#configure">
{t('cta')} {t('cta')}
</Button> </Button>
<Button variant="secondary" size="lg" href="#work"> <Button
variant="secondary"
size="lg"
href="#work"
className="bg-on-surface/5 hover:bg-on-surface/10"
>
{t('ctaSecondary')} {t('ctaSecondary')}
</Button> </Button>
</motion.div> </motion.div>
{/* Trust proof strip */}
<motion.div
className="flex items-center gap-3"
variants={trustVariant}
initial="hidden"
animate="visible"
>
{/* Overlapping avatar circles */}
<div className="flex items-center" aria-hidden="true">
<AvatarCircle index={0} />
<AvatarCircle index={1} />
<AvatarCircle index={2} />
</div>
<p className="text-sm text-outline">
{t('trust')}
</p>
</motion.div>
</div> </div>
{/* Bottom fade-out to next section */} {/* Bottom fade-out to next section */}

View File

@@ -108,7 +108,7 @@ function PhilosophyPillar({
{/* Content */} {/* Content */}
<div className="flex flex-col gap-1.5 py-4"> <div className="flex flex-col gap-1.5 py-4">
<p <p
className="label-md text-outline/60 mb-1" className="font-serif text-sm font-semibold text-primary mb-1"
aria-hidden="true" aria-hidden="true"
> >
{String(index + 1).padStart(2, '0')} {String(index + 1).padStart(2, '0')}
@@ -132,7 +132,7 @@ function AbstractGeometry() {
className="absolute inset-0 overflow-hidden rounded-xl" className="absolute inset-0 overflow-hidden rounded-xl"
aria-hidden="true" aria-hidden="true"
> >
{/* Primary large circle */} {/* Primary large circle — filled radial glow */}
<div <div
className="absolute rounded-full" className="absolute rounded-full"
style={{ style={{
@@ -145,6 +145,19 @@ function AbstractGeometry() {
}} }}
/> />
{/* Large circle ring — border only, ~40% opacity */}
<div
className="absolute rounded-full"
style={{
width: '72%',
height: '72%',
top: '-18%',
right: '-20%',
border: '1px solid rgba(91,164,217,0.20)',
opacity: 0.4,
}}
/>
{/* Secondary circle, bottom-left */} {/* Secondary circle, bottom-left */}
<div <div
className="absolute rounded-full" className="absolute rounded-full"
@@ -158,6 +171,19 @@ function AbstractGeometry() {
}} }}
/> />
{/* Second smaller ring — bottom-left */}
<div
className="absolute rounded-full"
style={{
width: '38%',
height: '38%',
bottom: '-4%',
left: '4%',
border: '1px solid rgba(0,100,148,0.18)',
opacity: 0.45,
}}
/>
{/* Diagonal grid */} {/* Diagonal grid */}
<div <div
className="absolute inset-0 opacity-[0.03]" className="absolute inset-0 opacity-[0.03]"
@@ -168,7 +194,22 @@ function AbstractGeometry() {
}} }}
/> />
{/* Accent rectangle top-left */} {/* Diagonal line crossing the composition */}
<div
className="absolute"
style={{
width: '130%',
height: '1px',
top: '42%',
left: '-15%',
background:
'linear-gradient(90deg, transparent 0%, rgba(91,164,217,0.18) 30%, rgba(0,100,148,0.22) 60%, transparent 100%)',
transform: 'rotate(-12deg)',
transformOrigin: 'left center',
}}
/>
{/* Accent rectangle top-left, rotated */}
<div <div
className="absolute rounded-md" className="absolute rounded-md"
style={{ style={{
@@ -181,6 +222,19 @@ function AbstractGeometry() {
}} }}
/> />
{/* Second rotated rectangle outline — center */}
<div
className="absolute rounded-sm"
style={{
width: '16%',
height: '22%',
top: '38%',
left: '28%',
border: '1px solid rgba(91,164,217,0.12)',
transform: 'rotate(14deg)',
}}
/>
{/* Floating dot cluster center-right */} {/* Floating dot cluster center-right */}
<div <div
className="absolute" className="absolute"
@@ -190,15 +244,29 @@ function AbstractGeometry() {
top: '35%', top: '35%',
right: '10%', right: '10%',
backgroundImage: backgroundImage:
'radial-gradient(circle, rgba(91,164,217,0.25) 1.5px, transparent 1.5px)', 'radial-gradient(circle, rgba(91,164,217,0.28) 1.5px, transparent 1.5px)',
backgroundSize: '10px 10px', backgroundSize: '10px 10px',
}} }}
/> />
{/* Thin arc line */} {/* Small dot cluster upper-left */}
<div
className="absolute"
style={{
width: '14%',
height: '16%',
top: '12%',
right: '32%',
backgroundImage:
'radial-gradient(circle, rgba(0,100,148,0.20) 1.5px, transparent 1.5px)',
backgroundSize: '8px 8px',
}}
/>
{/* Thin dashed arc */}
<svg <svg
className="absolute" className="absolute"
style={{ top: '20%', left: '30%', opacity: 0.08 }} style={{ top: '20%', left: '30%', opacity: 0.09 }}
width="140" width="140"
height="140" height="140"
viewBox="0 0 140 140" viewBox="0 0 140 140"
@@ -223,10 +291,20 @@ function AbstractGeometry() {
height: '6%', height: '6%',
bottom: '28%', bottom: '28%',
right: '28%', right: '28%',
background: 'rgba(91,164,217,0.20)', background: 'rgba(91,164,217,0.22)',
transform: 'rotate(12deg)', transform: 'rotate(12deg)',
}} }}
/> />
{/* Navy bottom overlay that fades upward — gives depth to pull-quote */}
<div
className="absolute inset-x-0 bottom-0 rounded-b-xl"
style={{
height: '45%',
background:
'linear-gradient(to top, rgba(28,43,58,0.55) 0%, rgba(28,43,58,0.18) 55%, transparent 100%)',
}}
/>
</div> </div>
); );
} }
@@ -319,6 +397,7 @@ export default function Philosophy() {
'bg-surface-high rounded-xl p-6 shadow-subtle', 'bg-surface-high rounded-xl p-6 shadow-subtle',
'max-w-[320px] w-[calc(100%-2.5rem)] lg:max-w-[340px]', 'max-w-[320px] w-[calc(100%-2.5rem)] lg:max-w-[340px]',
'z-10', 'z-10',
'border-l-[3px] border-primary',
)} )}
> >
{/* CornerBracket top-right decoration */} {/* CornerBracket top-right decoration */}

View File

@@ -54,28 +54,37 @@ function StepCard({ numeral, stepKey, Icon }: { numeral: string; stepKey: string
<motion.div <motion.div
variants={revealVariants} variants={revealVariants}
className={cn( className={cn(
'relative flex flex-col bg-surface-high rounded-xl p-6', 'relative flex flex-col bg-surface-high rounded-xl pt-0 px-6 pb-6 overflow-hidden',
'shadow-subtle', '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 */} {/* Ghosted numeral — scales up on scroll */}
<motion.span <motion.span
variants={numeralScaleVariants} variants={numeralScaleVariants}
aria-hidden="true" aria-hidden="true"
className="font-serif text-6xl font-light leading-none text-on-surface/[0.06] select-none -ml-0.5 mb-3" className="font-serif text-6xl font-light leading-none text-on-surface/[0.06] select-none -ml-0.5 mb-3 mt-6"
> >
{numeral} {numeral}
</motion.span> </motion.span>
{/* Icon */} {/* Icon container */}
<div className="mb-4"> <div className="mb-4">
<div className="w-12 h-12 rounded-xl bg-primary/8 flex items-center justify-center">
<Icon <Icon
size={24} size={22}
strokeWidth={1.5} strokeWidth={1.5}
className="text-primary" className="text-primary"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
</div>
{/* Title */} {/* Title */}
<h3 className="font-semibold text-lg text-on-surface mb-2 leading-snug"> <h3 className="font-semibold text-lg text-on-surface mb-2 leading-snug">
@@ -111,15 +120,59 @@ export default function Process() {
{/* ── Header column ── */} {/* ── Header column ── */}
<div className="lg:col-span-1 lg:sticky lg:top-32"> <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 <SectionHeader
eyebrow={t('process.eyebrow')} eyebrow={t('process.eyebrow')}
title={t('process.title')} title={t('process.title')}
align="left" align="left"
/> />
</div> </div>
</div>
{/* ── Steps column ── */} {/* ── Steps column ── */}
<div className="lg:col-span-3"> <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 <motion.div
variants={staggerContainerWide} variants={staggerContainerWide}
initial="hidden" initial="hidden"
@@ -132,6 +185,7 @@ export default function Process() {
))} ))}
</motion.div> </motion.div>
</div> </div>
</div>
</div> </div>
</div> </div>

View File

@@ -92,97 +92,278 @@ const comingSoonVariants = {
function GeometricPlaceholder({ function GeometricPlaceholder({
variant = 'featured', variant = 'featured',
cardVariant = 'default',
className, className,
}: { }: {
variant?: 'featured' | 'small'; variant?: 'featured' | 'small';
cardVariant?: 'default' | 'nimara' | 'amador';
className?: string; className?: string;
}) { }) {
const isFeatured = variant === 'featured'; const isFeatured = variant === 'featured';
// Different gradient bases per card so secondary cards look distinct
const gradientMap = {
default: 'from-primary-dark/90 to-primary/70',
nimara: 'from-navy/95 to-primary-dark/75',
amador: 'from-[#1a3a4a]/95 to-primary/60',
};
return ( return (
<div <div
className={cn( className={cn(
'relative overflow-hidden bg-gradient-to-br from-primary-dark/90 to-primary/70', 'relative overflow-hidden bg-gradient-to-br',
gradientMap[cardVariant],
className, className,
)} )}
aria-hidden="true" aria-hidden="true"
> >
{/* Abstract geometric shapes */}
<div className="absolute inset-0"> <div className="absolute inset-0">
{/* Large circle top-right */}
{isFeatured ? (
/* ── Featured (Monaco): blueprint feel ── */
<>
{/* Large filled circle top-right */}
<div <div
className="absolute rounded-full bg-white/[0.06]" className="absolute rounded-full bg-white/[0.06]"
style={{ width: '55%', height: '55%', top: '-15%', right: '-10%' }}
/>
{/* Large circle ring — blueprint layer */}
<div
className="absolute rounded-full"
style={{ style={{
width: isFeatured ? '55%' : '70%', width: '45%',
height: isFeatured ? '55%' : '70%', height: '45%',
top: '-15%', top: '10%',
right: '-10%', left: '5%',
border: '1px solid rgba(255,255,255,0.12)',
opacity: 0.4,
}} }}
/> />
{/* Medium circle bottom-left */} {/* Medium circle bottom-left */}
<div <div
className="absolute rounded-full bg-white/[0.08]" className="absolute rounded-full bg-white/[0.08]"
style={{ width: '40%', height: '40%', bottom: '-20%', left: '-5%' }}
/>
{/* Subtle grid overlay (blueprint feel) */}
<div
className="absolute inset-0 opacity-[0.035]"
style={{ style={{
width: isFeatured ? '40%' : '50%', backgroundImage:
height: isFeatured ? '40%' : '50%', 'linear-gradient(rgba(255,255,255,1) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,1) 1px, transparent 1px)',
bottom: '-20%', backgroundSize: '32px 32px',
left: '-5%',
}} }}
/> />
{/* Diagonal stripes overlay */} {/* Diagonal stripes very subtle */}
<div <div
className="absolute inset-0 opacity-[0.04]" className="absolute inset-0 opacity-[0.025]"
style={{ style={{
backgroundImage: backgroundImage:
'repeating-linear-gradient(-45deg, #fff 0, #fff 1px, transparent 0, transparent 50%)', 'repeating-linear-gradient(-45deg, #fff 0, #fff 1px, transparent 0, transparent 50%)',
backgroundSize: isFeatured ? '28px 28px' : '20px 20px', backgroundSize: '28px 28px',
}} }}
/> />
{/* Small accent rectangle */} {/* Small dot cluster */}
<div <div
className="absolute bg-white/[0.12] rounded-sm" className="absolute opacity-[0.12]"
style={{ style={{
width: isFeatured ? '18%' : '24%', width: '20%',
height: isFeatured ? '28%' : '36%', height: '20%',
top: '55%',
right: '18%',
backgroundImage: 'radial-gradient(circle, #fff 1.5px, transparent 1.5px)',
backgroundSize: '8px 8px',
}}
/>
{/* Blueprint horizontal line — wide, white/20 */}
<div
className="absolute bg-white/[0.22] rounded-full"
style={{ width: '60%', height: '1px', top: '50%', left: '5%' }}
/>
{/* Second thinner line below */}
<div
className="absolute bg-white/[0.10] rounded-full"
style={{ width: '40%', height: '1px', top: 'calc(50% + 10px)', left: '5%' }}
/>
{/* Accent rotated rectangle */}
<div
className="absolute bg-white/[0.10] rounded-sm"
style={{
width: '18%',
height: '28%',
bottom: '18%', bottom: '18%',
right: '15%', right: '15%',
transform: 'rotate(-6deg)', transform: 'rotate(-6deg)',
}} }}
/> />
{/* Thin horizontal line accent */} {/* Small diagonal accent line */}
<div <div
className="absolute bg-white/20 rounded-full" className="absolute bg-white/[0.15]"
style={{ style={{
width: isFeatured ? '30%' : '40%', width: '25%',
height: '1px', height: '1px',
top: '38%', top: '28%',
left: '10%', right: '8%',
transform: 'rotate(-30deg)',
transformOrigin: 'left center',
}} }}
/> />
{/* Grid-dot accent */} </>
) : cardVariant === 'nimara' ? (
/* ── Port Nimara: horizontal bands + arc ── */
<>
{/* Large arc ring top-left */}
<div <div
className="absolute opacity-[0.07]" className="absolute rounded-full"
style={{ style={{
width: isFeatured ? '25%' : '30%', width: '80%',
height: isFeatured ? '25%' : '30%', height: '80%',
top: '50%', top: '-30%',
right: '22%', left: '-20%',
backgroundImage: 'radial-gradient(circle, #fff 1px, transparent 1px)', border: '1px solid rgba(255,255,255,0.10)',
opacity: 0.5,
}}
/>
{/* Smaller ring center */}
<div
className="absolute rounded-full"
style={{
width: '45%',
height: '70%',
bottom: '-15%',
right: '-10%',
border: '1px solid rgba(91,164,217,0.25)',
}}
/>
{/* Horizontal rule band */}
<div
className="absolute bg-white/[0.06]"
style={{ height: '28%', bottom: 0, left: 0, right: 0 }}
/>
{/* Fine horizontal lines */}
<div
className="absolute bg-white/20 rounded-full"
style={{ width: '50%', height: '1px', top: '35%', right: '8%' }}
/>
<div
className="absolute bg-white/10 rounded-full"
style={{ width: '30%', height: '1px', top: '45%', right: '8%' }}
/>
{/* Dot cluster top-right */}
<div
className="absolute opacity-[0.14]"
style={{
width: '25%',
height: '30%',
top: '5%',
right: '5%',
backgroundImage: 'radial-gradient(circle, #fff 1.5px, transparent 1.5px)',
backgroundSize: '8px 8px', backgroundSize: '8px 8px',
}} }}
/> />
{/* Diagonal line */}
<div
className="absolute bg-white/[0.12]"
style={{
width: '40%',
height: '1px',
top: '60%',
left: '5%',
transform: 'rotate(-20deg)',
transformOrigin: 'left center',
}}
/>
</>
) : (
/* ── Port Amador: triangular/radial composition ── */
<>
{/* Central radial glow */}
<div
className="absolute"
style={{
width: '70%',
height: '70%',
top: '-10%',
right: '-15%',
background:
'radial-gradient(circle, rgba(91,164,217,0.18) 0%, transparent 70%)',
}}
/>
{/* Diamond/rotated square accent */}
<div
className="absolute"
style={{
width: '30%',
height: '50%',
bottom: '-10%',
left: '10%',
border: '1px solid rgba(255,255,255,0.12)',
transform: 'rotate(45deg)',
}}
/>
{/* Diagonal stripes (different angle) */}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage:
'repeating-linear-gradient(30deg, #fff 0, #fff 1px, transparent 0, transparent 50%)',
backgroundSize: '20px 20px',
}}
/>
{/* Top horizontal line */}
<div
className="absolute bg-white/20 rounded-full"
style={{ width: '40%', height: '1px', top: '22%', left: '8%' }}
/>
{/* Small filled rectangle */}
<div
className="absolute bg-white/[0.12] rounded-sm"
style={{
width: '20%',
height: '32%',
top: '15%',
right: '12%',
transform: 'rotate(8deg)',
}}
/>
{/* Dot accent bottom-right */}
<div
className="absolute opacity-[0.16]"
style={{
width: '22%',
height: '22%',
bottom: '8%',
right: '8%',
backgroundImage: 'radial-gradient(circle, #fff 1.5px, transparent 1.5px)',
backgroundSize: '7px 7px',
}}
/>
</>
)}
</div> </div>
{/* Bottom gradient fade */}
<div className="absolute inset-x-0 bottom-0 h-1/3 bg-gradient-to-t from-primary-dark/50 to-transparent" /> {/* Bottom gradient fade — card bg color bleed for text readability */}
<div
className="absolute inset-x-0 bottom-0 h-2/5"
style={{
background: isFeatured
? 'linear-gradient(to top, rgba(0,100,148,0.55) 0%, transparent 100%)'
: cardVariant === 'nimara'
? 'linear-gradient(to top, rgba(28,43,58,0.60) 0%, transparent 100%)'
: 'linear-gradient(to top, rgba(26,58,74,0.55) 0%, transparent 100%)',
}}
/>
</div> </div>
); );
} }
// ─── Tag Chip ───────────────────────────────────────────────────────────────── // ─── Tag Chip ─────────────────────────────────────────────────────────────────
function TagChip({ label }: { label: string }) { function TagChip({ label, showDot = false }: { label: string; showDot?: boolean }) {
return ( return (
<span className="inline-flex items-center bg-primary/10 text-primary-dark text-xs font-medium px-2.5 py-1 rounded-full leading-none"> <span className="inline-flex items-center gap-1.5 bg-primary/10 text-primary-dark text-[0.75rem] font-semibold px-3 py-1 rounded-full leading-none tracking-wide">
{showDot && (
<span className="w-1 h-1 rounded-full bg-primary/50 shrink-0" aria-hidden="true" />
)}
{label} {label}
</span> </span>
); );
@@ -203,6 +384,7 @@ function FeaturedCard({ project, readLabel }: { project: Project; readLabel: str
{/* Geometric image placeholder */} {/* Geometric image placeholder */}
<GeometricPlaceholder <GeometricPlaceholder
variant="featured" variant="featured"
cardVariant="default"
className="w-full aspect-[16/9] md:aspect-[2/1]" className="w-full aspect-[16/9] md:aspect-[2/1]"
/> />
@@ -210,8 +392,8 @@ function FeaturedCard({ project, readLabel }: { project: Project; readLabel: str
<div className="flex flex-col flex-1 p-7 gap-4"> <div className="flex flex-col flex-1 p-7 gap-4">
{/* Tags */} {/* Tags */}
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{project.tags.map((tag) => ( {project.tags.map((tag, i) => (
<TagChip key={tag} label={tag} /> <TagChip key={tag} label={tag} showDot={i > 0} />
))} ))}
</div> </div>
@@ -230,16 +412,15 @@ function FeaturedCard({ project, readLabel }: { project: Project; readLabel: str
href={`/work/${project.slug}`} href={`/work/${project.slug}`}
className={cn( className={cn(
'inline-flex items-center gap-2 text-sm font-medium text-primary-dark', 'inline-flex items-center gap-2 text-sm font-medium text-primary-dark',
'transition-gap duration-200 group/link', 'mt-1 group/link',
'mt-1',
)} )}
> >
<span className="underline underline-offset-4 decoration-primary/40 group-hover/link:decoration-primary-dark transition-colors duration-200"> <span className="relative after:absolute after:bottom-0 after:left-0 after:h-px after:w-full after:bg-primary-dark after:origin-left after:scale-x-100 after:transition-transform after:duration-200">
{readLabel} {readLabel}
</span> </span>
<ArrowRight <ArrowRight
size={14} size={15}
className="transition-transform duration-200 group-hover/link:translate-x-1" className="transition-transform duration-200 group-hover/link:translate-x-1.5"
/> />
</Link> </Link>
</div> </div>
@@ -249,7 +430,14 @@ function FeaturedCard({ project, readLabel }: { project: Project; readLabel: str
// ─── Small Card ─────────────────────────────────────────────────────────────── // ─── Small Card ───────────────────────────────────────────────────────────────
const SLUG_TO_VARIANT: Record<string, 'nimara' | 'amador'> = {
'port-nimara': 'nimara',
'port-amador': 'amador',
};
function SmallCard({ project, readLabel }: { project: Project; readLabel: string }) { function SmallCard({ project, readLabel }: { project: Project; readLabel: string }) {
const cardVariant = SLUG_TO_VARIANT[project.slug] ?? 'nimara';
return ( return (
<motion.article <motion.article
variants={smallCardVariants} variants={smallCardVariants}
@@ -271,6 +459,7 @@ function SmallCard({ project, readLabel }: { project: Project; readLabel: string
> >
<GeometricPlaceholder <GeometricPlaceholder
variant="small" variant="small"
cardVariant={cardVariant}
className="w-full aspect-[16/7]" className="w-full aspect-[16/7]"
/> />
</div> </div>
@@ -280,8 +469,8 @@ function SmallCard({ project, readLabel }: { project: Project; readLabel: string
<div className="flex flex-col flex-1 p-5 gap-3"> <div className="flex flex-col flex-1 p-5 gap-3">
{/* Tags */} {/* Tags */}
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
{project.tags.map((tag) => ( {project.tags.map((tag, i) => (
<TagChip key={tag} label={tag} /> <TagChip key={tag} label={tag} showDot={i > 0} />
))} ))}
</div> </div>
@@ -300,12 +489,12 @@ function SmallCard({ project, readLabel }: { project: Project; readLabel: string
href={`/work/${project.slug}`} href={`/work/${project.slug}`}
className="inline-flex items-center gap-1.5 text-xs font-medium text-primary-dark group/link" className="inline-flex items-center gap-1.5 text-xs font-medium text-primary-dark group/link"
> >
<span className="underline underline-offset-4 decoration-primary/40 group-hover/link:decoration-primary-dark transition-colors duration-200"> <span className="relative after:absolute after:bottom-0 after:left-0 after:h-px after:w-full after:bg-primary-dark after:origin-left after:scale-x-100 after:transition-transform after:duration-200">
{readLabel} {readLabel}
</span> </span>
<ArrowRight <ArrowRight
size={12} size={13}
className="transition-transform duration-200 group-hover/link:translate-x-0.5" className="transition-transform duration-200 group-hover/link:translate-x-1"
/> />
</Link> </Link>
</div> </div>
@@ -368,14 +557,21 @@ export default function SelectedWorks() {
0% { background-position: 0 0, 100% 0, 100% 100%, 0 100%; } 0% { background-position: 0 0, 100% 0, 100% 100%, 0 100%; }
100% { background-position: 100% 0, 100% 100%, 0 100%, 0 0; } 100% { background-position: 100% 0, 100% 100%, 0 100%, 0 0; }
} }
@keyframes coming-soon-bg-pulse {
0%, 100% { background-color: var(--color-surface-low); }
50% { background-color: var(--color-surface); }
}
@keyframes coming-soon-fade { @keyframes coming-soon-fade {
0%, 100% { opacity: 0; } 0%, 100% { opacity: 0; }
50% { opacity: 1; } 50% { opacity: 1; }
} }
.coming-soon-card {
animation: coming-soon-bg-pulse 4s ease-in-out infinite;
}
.coming-soon-pulse { .coming-soon-pulse {
background: radial-gradient( background: radial-gradient(
ellipse at center, ellipse at center,
rgba(91, 164, 217, 0.04) 0%, rgba(91, 164, 217, 0.06) 0%,
transparent 70% transparent 70%
); );
animation: coming-soon-fade 4s ease-in-out infinite; animation: coming-soon-fade 4s ease-in-out infinite;

View File

@@ -44,7 +44,16 @@ export default function ServicesOverview() {
const t = useTranslations(); const t = useTranslations();
return ( return (
<section id="services" className="bg-surface py-24"> <section
id="services"
className="relative bg-surface py-24"
style={{
backgroundImage: [
'repeating-linear-gradient(0deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
'repeating-linear-gradient(90deg, rgba(25,28,29,0.02) 0px, rgba(25,28,29,0.02) 1px, transparent 1px, transparent 48px)',
].join(', '),
}}
>
<div className="container mx-auto px-6"> <div className="container mx-auto px-6">
{/* Section header */} {/* Section header */}
@@ -75,8 +84,8 @@ export default function ServicesOverview() {
variants={revealVariants} variants={revealVariants}
className={cn( className={cn(
'relative flex flex-col p-8 bg-surface', 'relative flex flex-col p-8 bg-surface',
'transition-colors duration-200 ease-out', 'transition-all duration-200 ease-out',
'hover:bg-surface-high', 'hover:bg-surface-high hover:shadow-subtle',
)} )}
> >
{/* Ghosted numeral */} {/* Ghosted numeral */}
@@ -102,7 +111,7 @@ export default function ServicesOverview() {
> >
<span <span
aria-hidden="true" aria-hidden="true"
className="mt-[0.35em] shrink-0 w-1 h-1 rounded-full bg-primary/50" className="mt-[0.4em] shrink-0 w-1.5 h-1.5 rounded-full bg-primary"
/> />
{feature} {feature}
</li> </li>
@@ -120,18 +129,33 @@ export default function ServicesOverview() {
initial="hidden" initial="hidden"
whileInView="visible" whileInView="visible"
viewport={viewportOnce} viewport={viewportOnce}
className="mt-16 flex flex-col items-center gap-4" className="mt-16 flex items-center gap-6"
> >
{/* Decorative rule */} {/* Left horizontal rule */}
<motion.span <motion.span
variants={scaleVariants} variants={scaleVariants}
className="block w-px h-10 bg-primary/30" className="flex-1 h-px max-w-[120px] ml-auto"
style={{
background:
'linear-gradient(to left, rgba(0,100,148,0.25), transparent)',
}}
aria-hidden="true" aria-hidden="true"
/> />
<p className="font-serif italic text-xl text-primary-dark text-center max-w-xl leading-relaxed"> <p className="font-serif italic text-2xl text-primary-dark text-center max-w-xl leading-relaxed">
{t('services.aiNarrative')} {t('services.aiNarrative')}
</p> </p>
{/* Right horizontal rule */}
<motion.span
variants={scaleVariants}
className="flex-1 h-px max-w-[120px] mr-auto"
style={{
background:
'linear-gradient(to right, rgba(0,100,148,0.25), transparent)',
}}
aria-hidden="true"
/>
</motion.div> </motion.div>
</div> </div>

View File

@@ -49,17 +49,29 @@ function TrustCard({ item, index, t }: TrustCardProps) {
<motion.div <motion.div
variants={revealVariants} variants={revealVariants}
className={cn( className={cn(
'group flex flex-col items-start gap-4 p-6', 'group relative flex flex-col items-start gap-4 p-8',
'rounded-2xl bg-surface-high shadow-subtle', 'rounded-2xl bg-surface-high shadow-subtle',
'transition-shadow duration-300 hover:shadow-card', 'transition-shadow duration-300 hover:shadow-card',
'cursor-default', 'overflow-hidden cursor-default',
)} )}
> >
{/* Top accent gradient bar — fades in on hover */}
<span
className={cn(
'pointer-events-none absolute top-0 left-6 right-6 h-[3px] rounded-full',
'opacity-0 group-hover:opacity-100 transition-opacity duration-300',
)}
style={{
background: 'linear-gradient(to right, #006494, #5BA4D9)',
}}
aria-hidden="true"
/>
{/* Icon with scale-bounce on scroll reveal */} {/* Icon with scale-bounce on scroll reveal */}
<motion.div <motion.div
variants={iconBounceVariants} variants={iconBounceVariants}
className={cn( className={cn(
'flex items-center justify-center w-12 h-12 rounded-xl', 'flex items-center justify-center w-12 h-12 rounded-full',
'bg-primary/8', 'bg-primary/8',
'transition-transform duration-300 ease-out', 'transition-transform duration-300 ease-out',
'group-hover:-translate-y-1', 'group-hover:-translate-y-1',
@@ -67,7 +79,7 @@ function TrustCard({ item, index, t }: TrustCardProps) {
aria-hidden="true" aria-hidden="true"
> >
<Icon <Icon
size={28} size={24}
className="text-primary transition-colors duration-300" className="text-primary transition-colors duration-300"
strokeWidth={1.75} strokeWidth={1.75}
/> />
@@ -99,8 +111,16 @@ export default function TrustBar() {
return ( return (
<section <section
aria-label="Trust indicators" aria-label="Trust indicators"
className="bg-surface-low py-16" className="relative bg-surface-low py-16"
> >
{/* Gradient bridge — blends hero surface-high into this section */}
<div
className="absolute top-0 left-0 right-0 h-8 pointer-events-none"
style={{
background: 'linear-gradient(to bottom, #ffffff, transparent)',
}}
aria-hidden="true"
/>
<div className="max-w-6xl mx-auto px-6"> <div className="max-w-6xl mx-auto px-6">
{/* Stagger wrapper — triggers children revealVariants on scroll */} {/* Stagger wrapper — triggers children revealVariants on scroll */}
<motion.div <motion.div