diff --git a/src/components/icons/HeroGeometric.tsx b/src/components/icons/HeroGeometric.tsx index 7b23a07..d7b753d 100644 --- a/src/components/icons/HeroGeometric.tsx +++ b/src/components/icons/HeroGeometric.tsx @@ -1,402 +1,205 @@ -import React from "react"; - interface HeroGeometricProps { - className?: string; + className?: string } export default function HeroGeometric({ className }: HeroGeometricProps) { + // Arc center — pushed further right and scaled down + const cx = 760 + const cy = 400 + const R = 180 // Main radius — compact, not dominating + return ( - ); + ) } diff --git a/src/components/sections/Hero.tsx b/src/components/sections/Hero.tsx index dd06870..a059601 100644 --- a/src/components/sections/Hero.tsx +++ b/src/components/sections/Hero.tsx @@ -2,18 +2,21 @@ import { motion } from 'framer-motion'; import { useTranslations } from 'next-intl'; -import { cn } from '@/lib/utils'; import Button from '@/components/ui/Button'; import HeroGeometric from '@/components/icons/HeroGeometric'; -// Slow drift animation for the SVG background layers +// ─── Animation variants ──────────────────────────────────────────────────── + +const EASE_OUT_EXPO = [0.16, 1, 0.3, 1] as [number, number, number, number]; + +// Background SVG drift — slow, almost imperceptible const bgDriftA = { animate: { - y: [0, -12, 0], - x: [0, 6, 0], + y: [0, -10, 0], + x: [0, 5, 0], transition: { - duration: 18, + duration: 22, ease: 'easeInOut' as const, repeat: Infinity, repeatType: 'loop' as const, @@ -23,11 +26,10 @@ const bgDriftA = { const bgDriftB = { animate: { - y: [0, 8, 0], - x: [0, -8, 0], - scale: [1, 1.015, 1], + y: [0, 7, 0], + x: [0, -6, 0], transition: { - duration: 24, + duration: 28, ease: 'easeInOut' as const, repeat: Infinity, repeatType: 'loop' as const, @@ -35,237 +37,392 @@ const bgDriftB = { }, }; -const bgRotate = { - animate: { - rotate: [0, 1.5, 0, -1.5, 0], - transition: { - duration: 30, - ease: 'easeInOut' as const, - repeat: Infinity, - repeatType: 'loop' as const, - }, - }, -}; - -// Word-level stagger container — fires on mount (not scroll) -const heroHeadlineContainer = { +// Headline word stagger container +const headlineContainer = { hidden: {}, visible: { transition: { - staggerChildren: 0.07, - delayChildren: 0.15, + staggerChildren: 0.065, + delayChildren: 0.2, }, }, }; -// Individual word reveal +// Per-word reveal — slide up + blur clear const wordReveal = { - hidden: { opacity: 0, y: 48, filter: 'blur(4px)' }, + hidden: { opacity: 0, y: 52, filter: 'blur(6px)' }, visible: { opacity: 1, y: 0, filter: 'blur(0px)', transition: { - duration: 0.7, - ease: [0.16, 1, 0.3, 1] as [number, number, number, number], + duration: 0.72, + ease: EASE_OUT_EXPO, }, }, }; -// Subtitle fade — delay after headline -const subtitleVariant = { - hidden: { opacity: 0, y: 20 }, +// Eyebrow label fade +const eyebrowVariant = { + hidden: { opacity: 0, y: 14 }, visible: { opacity: 1, y: 0, - transition: { - duration: 0.6, - delay: 0.5, - ease: [0.16, 1, 0.3, 1] as [number, number, number, number], - }, + transition: { duration: 0.5, delay: 0.08, ease: EASE_OUT_EXPO }, }, }; -// CTA scale-in -const ctaVariant = { - hidden: { opacity: 0, scale: 0.93 }, +// Subtitle fade-up — delayed after headline completes +const subtitleVariant = { + hidden: { opacity: 0, y: 18 }, visible: { opacity: 1, - scale: 1, - transition: { - duration: 0.45, - delay: 0.7, - ease: [0.16, 1, 0.3, 1] as [number, number, number, number], - }, + y: 0, + transition: { duration: 0.6, delay: 0.6, ease: EASE_OUT_EXPO }, }, }; -// Decorative separator line fade-in — after subtitle +// Separator expand from origin-left const separatorVariant = { hidden: { opacity: 0, scaleX: 0 }, visible: { opacity: 1, scaleX: 1, + transition: { duration: 0.55, delay: 0.72, ease: EASE_OUT_EXPO }, + }, +}; + +// CTA fade in +const ctaVariant = { + hidden: { opacity: 0, y: 12 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.5, delay: 0.82, ease: EASE_OUT_EXPO }, + }, +}; + +// Right column entrance — SVG composition fades in from right +const rightColumnVariant = { + hidden: { opacity: 0, x: 24 }, + visible: { + opacity: 1, + x: 0, + transition: { duration: 0.9, delay: 0.1, ease: EASE_OUT_EXPO }, + }, +}; + +// Slow full rotation for the large decorative ring +const slowRotate = { + animate: { + rotate: [0, 360], transition: { - duration: 0.6, - delay: 0.65, - ease: [0.16, 1, 0.3, 1] as [number, number, number, number], + duration: 30, + ease: 'linear' as const, + repeat: Infinity, }, }, }; +// ─── Component ──────────────────────────────────────────────────────────── + export default function Hero() { const t = useTranslations('hero'); - // Split the raw title by the {exclusively} placeholder so we can inject the styled - // Expected translation shape: "Built {exclusively} for ambitious brands." + // Split the raw title on the {exclusively} placeholder + // Expected translation: "Built {exclusively} for ambitious brands." const rawTitle: string = t.raw('title'); const parts = rawTitle.split('{exclusively}'); const before = parts[0] ?? ''; const after = parts[1] ?? ''; - // Split each segment into words for per-word animation - const beforeWords = before.trim() ? before.trim().split(' ') : []; - const afterWords = after.trim() ? after.trim().split(' ') : []; - const exclusivelyWord = 'exclusively'; - - // All words in order: before + [exclusively] + after + // Build flat word array with type tagging for accent word type WordItem = | { type: 'normal'; text: string } | { type: 'accent'; text: string }; + const beforeWords: WordItem[] = before.trim() + ? before.trim().split(' ').map((w) => ({ type: 'normal', text: w })) + : []; + const afterWords: WordItem[] = after.trim() + ? after.trim().split(' ').map((w) => ({ type: 'normal', text: w })) + : []; const allWords: WordItem[] = [ - ...beforeWords.map((w) => ({ type: 'normal' as const, text: w })), - { type: 'accent' as const, text: exclusivelyWord }, - ...afterWords.map((w) => ({ type: 'normal' as const, text: w })), + ...beforeWords, + { type: 'accent', text: 'exclusively' }, + ...afterWords, ]; return (
- {/* ─── Background: animated SVG layers ─────────────────────────── */} + {/* ─── Full-bleed SVG background — spans entire section ─────────── */} - - - + - {/* Subtle radial gradient vignette over the SVG */} + {/* ─── Radial glow — soft primary haze in right column ──────────── */}