Cleanup old PHP source files and update Next.js pages and components

This commit is contained in:
R-Bharathraj
2026-06-30 17:02:35 +05:30
parent 7175adb0db
commit 7cf5035c89
1223 changed files with 438 additions and 168007 deletions

View File

@@ -1,6 +1,6 @@
'use client';
import React, { useEffect, useLayoutEffect, useRef, useState, useCallback } from 'react';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { DEFAULT_EASE, DEFAULT_DURATION } from '@/utils/animation';
@@ -12,93 +12,204 @@ if (typeof window !== 'undefined') {
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
interface CardProps {
interface Stage {
num: string;
title: string;
description: string;
icon: React.ReactNode;
cardRef: React.RefObject<HTMLDivElement | null>;
orderClass: string;
}
function StageCard({ num, title, description, icon, cardRef, orderClass }: CardProps) {
const STAGES: Stage[] = [
{
num: '01',
title: 'Understand',
description: 'Track store footfall, customer behavior, and real-time engagement with AI — giving merchants the intelligence to run smarter campaigns and targeted offers that actually convert.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
),
},
{
num: '02',
title: 'Engage',
description: 'Customers earn Loyaly Coins through daily login, 5,000-step walking challenges, and interactive games — Maze, Spin Wheel, Scratch Card, and Match Master — keeping them engaged every day.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
),
},
{
num: '03',
title: 'Convert',
description: 'Merchants create game-based discount campaigns where customers play once to reveal their reward — turning product discovery into an exciting, high-conversion in-store experience.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
),
},
{
num: '04',
title: 'Retain',
description: 'Daily rewards, surprise coupons, and game-based offers keep customers returning to earn, play, and redeem — creating a self-reinforcing loyalty loop that compounds value with every visit.',
icon: (
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
),
},
];
function StageCard({
stage,
index,
active,
onHover,
}: {
stage: Stage;
index: number;
active: boolean;
onHover: (i: number | null) => void;
}) {
const cardRef = useRef<HTMLDivElement>(null);
const onMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const el = cardRef.current;
if (!el) return;
const r = el.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width - 0.5;
const py = (e.clientY - r.top) / r.height - 0.5;
el.style.setProperty('--rx', `${(-py * 6).toFixed(2)}deg`);
el.style.setProperty('--ry', `${(px * 8).toFixed(2)}deg`);
el.style.setProperty('--mx', `${((px + 0.5) * 100).toFixed(1)}%`);
el.style.setProperty('--my', `${((py + 0.5) * 100).toFixed(1)}%`);
};
const onMouseLeave = () => {
const el = cardRef.current;
if (!el) return;
el.style.setProperty('--rx', '0deg');
el.style.setProperty('--ry', '0deg');
onHover(null);
};
return (
<div
ref={cardRef}
className={`wwd-card relative group ${orderClass}`}
data-stage-card={index}
className={`wwd-card relative group ${active ? 'wwd-card-active' : ''}`}
onMouseMove={onMouseMove}
onMouseEnter={() => onHover(index)}
onMouseLeave={onMouseLeave}
style={{
background: 'rgba(255,255,255,0.025)',
backdropFilter: 'blur(24px) saturate(160%)',
WebkitBackdropFilter: 'blur(24px) saturate(160%)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '28px',
padding: 'clamp(30px, 3vw, 48px)',
transition: 'transform 0.5s cubic-bezier(0.22,1,0.36,1), box-shadow 0.5s ease, border-color 0.5s ease',
cursor: 'default',
overflow: 'hidden',
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.05)',
}}
onMouseEnter={e => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(-8px)';
(e.currentTarget as HTMLElement).style.boxShadow = '0 32px 80px rgba(244, 190, 40,0.12), 0 0 0 1px rgba(244, 190, 40,0.25), inset 0 1px 0 rgba(255,255,255,0.08)';
(e.currentTarget as HTMLElement).style.borderColor = 'rgba(244, 190, 40,0.3)';
}}
onMouseLeave={e => {
(e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
(e.currentTarget as HTMLElement).style.boxShadow = 'inset 0 1px 0 rgba(255,255,255,0.05)';
(e.currentTarget as HTMLElement).style.borderColor = 'rgba(255,255,255,0.08)';
position: 'relative',
transform: 'perspective(1100px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translateY(var(--cascade, 0px))',
transformStyle: 'preserve-3d',
transition: 'transform 0.45s cubic-bezier(0.22,1,0.36,1), box-shadow 0.45s ease, border-color 0.45s ease',
}}
>
{/* Giant editorial number watermark */}
<div style={{
position: 'absolute', top: '-24px', right: '8px',
fontSize: 'clamp(8rem, 11vw, 13rem)', fontWeight: 900, lineHeight: 1,
color: 'transparent',
WebkitTextStroke: '1px rgba(244, 190, 40,0.06)',
fontFamily: 'Sora, sans-serif',
letterSpacing: '-0.06em',
userSelect: 'none', pointerEvents: 'none',
}}>
{num}
<div
className="wwd-card-inner"
style={{
position: 'relative',
background: 'rgba(255,255,255,0.025)',
backdropFilter: 'blur(24px) saturate(160%)',
WebkitBackdropFilter: 'blur(24px) saturate(160%)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 26,
padding: 'clamp(28px, 2.4vw, 38px)',
overflow: 'hidden',
cursor: 'default',
height: '100%',
boxShadow: active
? '0 32px 70px -20px rgba(244,190,40,0.22), 0 0 0 1px rgba(244,190,40,0.3), inset 0 1px 0 rgba(255,255,255,0.08)'
: 'inset 0 1px 0 rgba(255,255,255,0.05)',
borderColor: active ? 'rgba(244,190,40,0.32)' : 'rgba(255,255,255,0.08)',
}}
>
{/* Cursor-following glow */}
<div
className="wwd-card-glow"
style={{
position: 'absolute', inset: 0, pointerEvents: 'none', opacity: active ? 1 : 0,
background: 'radial-gradient(280px circle at var(--mx,50%) var(--my,50%), rgba(244,190,40,0.12), transparent 70%)',
transition: 'opacity 0.4s ease',
}}
/>
{/* Giant editorial number, bleeding off the top edge */}
<div style={{
position: 'absolute', top: '-0.32em', right: '-0.06em',
fontSize: 'clamp(6rem, 8vw, 9rem)', fontWeight: 900, lineHeight: 1,
color: 'transparent',
WebkitTextStroke: active ? '1px rgba(244,190,40,0.18)' : '1px rgba(244,190,40,0.07)',
fontFamily: 'Sora, sans-serif',
letterSpacing: '-0.06em',
userSelect: 'none', pointerEvents: 'none',
transition: 'all 0.45s ease',
}}>
{stage.num}
</div>
{/* Step rail dot */}
<div style={{
display: 'flex', alignItems: 'center', gap: 10, marginBottom: 22,
position: 'relative', zIndex: 2,
}}>
<span style={{
fontSize: 11, fontWeight: 800, letterSpacing: '0.18em', textTransform: 'uppercase',
color: active ? '#f4be28' : 'rgba(255,255,255,0.32)',
fontFamily: 'Sora, sans-serif', transition: 'color 0.4s ease',
}}>
Stage {stage.num}
</span>
<span style={{ flex: 1, height: 1, background: active ? 'rgba(244,190,40,0.35)' : 'rgba(255,255,255,0.08)', transition: 'background 0.4s ease' }} />
</div>
{/* Icon */}
<div style={{
width: 54, height: 54,
borderRadius: 16,
background: active ? 'rgba(244,190,40,0.16)' : 'rgba(244,190,40,0.1)',
border: `1px solid rgba(244,190,40,${active ? 0.4 : 0.22})`,
color: '#f4be28',
display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: 28,
position: 'relative', zIndex: 2,
boxShadow: active ? '0 0 28px rgba(244,190,40,0.3), inset 0 1px 0 rgba(255,255,255,0.08)' : 'inset 0 1px 0 rgba(255,255,255,0.08)',
transform: active ? 'rotate(-6deg) scale(1.05)' : 'none',
transition: 'all 0.45s cubic-bezier(0.22,1,0.36,1)',
}}>
{stage.icon}
</div>
{/* Title */}
<h3 style={{
margin: '0 0 14px',
fontSize: 'clamp(1.4rem, 2vw, 1.85rem)',
fontWeight: 800, color: '#ffffff',
lineHeight: 1.1, letterSpacing: '-0.03em',
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{stage.title}
</h3>
{/* Description */}
<p style={{
margin: 0,
fontSize: '0.92rem',
color: 'rgba(255,255,255,0.45)',
lineHeight: 1.75,
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{stage.description}
</p>
</div>
{/* Icon */}
<div style={{
width: '56px', height: '56px',
borderRadius: '16px',
background: 'rgba(244, 190, 40,0.1)',
border: '1px solid rgba(244, 190, 40,0.22)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: '32px',
position: 'relative', zIndex: 2,
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.08)',
}}>
{icon}
</div>
{/* Title */}
<h3 style={{
margin: '0 0 16px',
fontSize: 'clamp(1.5rem, 2.2vw, 2rem)',
fontWeight: 800, color: '#ffffff',
lineHeight: 1.1, letterSpacing: '-0.03em',
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{title}
</h3>
{/* Description */}
<p style={{
margin: 0,
fontSize: 'clamp(0.9rem, 1vw, 1rem)',
color: 'rgba(255,255,255,0.45)',
lineHeight: 1.75, maxWidth: '360px',
fontFamily: 'Sora, sans-serif',
position: 'relative', zIndex: 2,
}}>
{description}
</p>
</div>
);
}
@@ -109,129 +220,43 @@ interface WhatWeDoProps {
export default function WhatWeDo({ isLoaded = false }: WhatWeDoProps) {
const containerRef = useRef<HTMLDivElement>(null);
const gridContainerRef = useRef<HTMLDivElement>(null);
const card1Ref = useRef<HTMLDivElement>(null);
const card2Ref = useRef<HTMLDivElement>(null);
const card3Ref = useRef<HTMLDivElement>(null);
const card4Ref = useRef<HTMLDivElement>(null);
const [active, setActive] = useState<number | null>(null);
const [points, setPoints] = useState({
p1: { x: 0, y: 0 },
p2: { x: 0, y: 0 },
p3: { x: 0, y: 0 },
p4: { x: 0, y: 0 }
});
const [hasMeasured, setHasMeasured] = useState(false);
const updatePoints = useCallback(() => {
if (!gridContainerRef.current || !card1Ref.current || !card2Ref.current || !card3Ref.current || !card4Ref.current) return;
const containerRect = gridContainerRef.current.getBoundingClientRect();
const r1 = card1Ref.current.getBoundingClientRect();
const r2 = card2Ref.current.getBoundingClientRect();
const r3 = card3Ref.current.getBoundingClientRect();
const r4 = card4Ref.current.getBoundingClientRect();
// Check that width is non-zero to verify the elements have rendered and have dimensions
if (r1.width === 0 || r2.width === 0 || r3.width === 0 || r4.width === 0) return;
setPoints({
p1: {
x: r1.left - containerRect.left + r1.width / 2,
y: r1.top - containerRect.top + r1.height / 2
},
p2: {
x: r2.left - containerRect.left + r2.width / 2,
y: r2.top - containerRect.top + r2.height / 2
},
p3: {
x: r3.left - containerRect.left + r3.width / 2,
y: r3.top - containerRect.top + r3.height / 2
},
p4: {
x: r4.left - containerRect.left + r4.width / 2,
y: r4.top - containerRect.top + r4.height / 2
}
});
setHasMeasured(true);
}, []);
useEffect(() => {
if (typeof window === 'undefined') return;
// Use ResizeObserver for highly responsive layout tracking
const observer = new ResizeObserver(() => {
updatePoints();
});
if (gridContainerRef.current) observer.observe(gridContainerRef.current);
if (card1Ref.current) observer.observe(card1Ref.current);
if (card2Ref.current) observer.observe(card2Ref.current);
if (card3Ref.current) observer.observe(card3Ref.current);
if (card4Ref.current) observer.observe(card4Ref.current);
// Initial calculation
updatePoints();
// Secondary delay to ensure clean alignment post-font/css loading
const timer = setTimeout(updatePoints, 200);
window.addEventListener('resize', updatePoints);
return () => {
observer.disconnect();
window.removeEventListener('resize', updatePoints);
clearTimeout(timer);
};
}, [updatePoints]);
// GSAP animations for stagger entrances on entering viewport
useIsomorphicLayoutEffect(() => {
if (!isLoaded) return;
if (typeof window === 'undefined') return;
const ctx = gsap.context(() => {
// Header Animation
gsap.fromTo(
'.wwd-header-el',
{ opacity: 0, y: 40 },
{
opacity: 1,
y: 0,
duration: DEFAULT_DURATION,
stagger: 0.15,
ease: DEFAULT_EASE,
scrollTrigger: {
trigger: '.wwd-header',
start: 'top 85%',
toggleActions: 'play none none reset'
}
opacity: 1, y: 0, duration: DEFAULT_DURATION, stagger: 0.15, ease: DEFAULT_EASE,
scrollTrigger: { trigger: '.wwd-header', start: 'top 85%', toggleActions: 'play none none reset' },
}
);
// Card Stagger Animation
gsap.fromTo(
'.wwd-card',
{ opacity: 0, y: 60 },
{ opacity: 0, y: 70, scale: 0.94 },
{
opacity: 1,
y: 0,
duration: DEFAULT_DURATION,
stagger: 0.15,
ease: DEFAULT_EASE,
onComplete: updatePoints,
scrollTrigger: {
trigger: '.wwd-grid-container',
start: 'top 80%',
toggleActions: 'play none none reset'
}
opacity: 1, y: 0, scale: 1, duration: 0.9, stagger: 0.12, ease: 'power3.out',
scrollTrigger: { trigger: '.wwd-grid-container', start: 'top 80%', toggleActions: 'play none none reset' },
}
);
gsap.fromTo(
'.wwd-rail-step',
{ opacity: 0, y: 14 },
{
opacity: 1, y: 0, duration: 0.5, stagger: 0.08, ease: 'power3.out',
scrollTrigger: { trigger: '.wwd-rail', start: 'top 85%', toggleActions: 'play none none reset' },
}
);
}, containerRef);
return () => ctx.revert();
}, [isLoaded, updatePoints]);
}, [isLoaded]);
return (
<section
@@ -241,136 +266,83 @@ export default function WhatWeDo({ isLoaded = false }: WhatWeDoProps) {
style={{
width: 'calc(100% - 40px)',
margin: '0 20px 20px',
borderRadius: '32px',
borderRadius: 32,
zIndex: 2,
}}
>
{/* Animated dark background (replaces video) */}
<AnimatedDarkBg variant="amber" />
{/* Header Container */}
<div className="wwd-header relative z-10 text-center max-w-4xl mx-auto mb-24 sm:mb-28 md:mb-32 lg:mb-36 flex flex-col items-center">
{/* Pill Badge */}
{/* Header */}
<div className="wwd-header relative z-10 text-center max-w-4xl mx-auto mb-16 sm:mb-20 flex flex-col items-center">
<div className="wwd-header-el inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-white/10 bg-white/5 text-[#f4be28] text-xs font-bold tracking-wider uppercase mb-5">
<span className="w-1.5 h-1.5 rounded-full bg-[#f4be28] animate-pulse" />
What We Do
</div>
{/* Main Heading */}
<h2 className="wwd-header-el text-white font-black mb-6" style={{ fontSize: 'clamp(2.8rem, 6vw, 5.5rem)', letterSpacing: '-0.05em', lineHeight: 0.9, fontFamily: 'Sora, sans-serif' }}>
Understand.<br />
<span className="text-[#f4be28]">Engage. Convert.</span><br />
Retain.
</h2>
{/* Subtitle */}
<p className="wwd-header-el text-gray-400 text-base sm:text-lg max-w-xl font-medium" style={{ fontFamily: 'Sora, sans-serif', lineHeight: 1.75 }}>
Four stages. One platform. A smarter retail cycle that turns every walk-in into a loyal customer.
</p>
</div>
{/* Grid and Paths Container */}
<div ref={gridContainerRef} className="relative z-10 wwd-grid-container max-w-6xl mx-auto">
{/* 2 x 2 Responsive Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 lg:gap-24 relative z-20 px-2 md:px-0">
{/* Card 1 — Understand */}
<StageCard
num="01"
title="Understand"
description="Track store footfall, customer behavior, and real-time engagement with AI — giving merchants the intelligence to run smarter campaigns and targeted offers that actually convert."
cardRef={card1Ref}
orderClass="order-1 md:order-1"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
}
/>
{/* Card 2 — Engage */}
<StageCard
num="02"
title="Engage"
description="Customers earn Loyaly Coins through daily login, 5,000-step walking challenges, and interactive games — Maze, Spin Wheel, Scratch Card, and Match Master — keeping them engaged every day."
cardRef={card2Ref}
orderClass="order-2 md:order-2"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
}
/>
{/* Card 4 — Retain (positioned bottom-left on desktop via order-3) */}
<StageCard
num="04"
title="Retain"
description="Daily rewards, surprise coupons, and game-based offers keep customers returning to earn, play, and redeem — creating a self-reinforcing loyalty loop that compounds value with every visit."
cardRef={card4Ref}
orderClass="order-4 md:order-3"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
}
/>
{/* Card 3 — Convert (positioned bottom-right on desktop via order-4) */}
<StageCard
num="03"
title="Convert"
description="Merchants create game-based discount campaigns where customers play once to reveal their reward — turning product discovery into an exciting, high-conversion in-store experience."
cardRef={card3Ref}
orderClass="order-3 md:order-4"
icon={
<svg className="w-6 h-6 text-[#f4be28]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.2">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
}
/>
</div>
{/* Center Area Ornament */}
{hasMeasured && (
<div
className="absolute z-30 hidden md:flex items-center justify-center"
{/* Step rail — desktop flow indicator synced to card hover, aligned to the card columns below */}
<div className="wwd-rail relative z-10 max-w-6xl mx-auto mb-6 hidden lg:grid lg:grid-cols-4 gap-7">
{STAGES.map((s, i) => (
<button
key={s.num}
type="button"
onMouseEnter={() => setActive(i)}
onMouseLeave={() => setActive(null)}
className="wwd-rail-step"
style={{
pointerEvents: 'none',
left: `${(points.p1.x + points.p2.x + points.p3.x + points.p4.x) / 4}px`,
top: `${(points.p1.y + points.p2.y + points.p3.y + points.p4.y) / 4}px`,
transform: 'translate(-50%, -50%)'
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
padding: '6px 14px', borderRadius: 100, border: '1px solid',
borderColor: active === i ? 'rgba(244,190,40,0.4)' : 'rgba(255,255,255,0.1)',
background: active === i ? 'rgba(244,190,40,0.1)' : 'rgba(255,255,255,0.03)',
color: active === i ? '#f4be28' : 'rgba(255,255,255,0.4)',
fontSize: 11, fontWeight: 800, letterSpacing: '0.1em', textTransform: 'uppercase',
fontFamily: 'Sora, sans-serif', cursor: 'default',
transition: 'all 0.3s ease',
}}
>
{/* Glowing container */}
<div className="relative w-16 h-16 rounded-full bg-[#0D0D0D] border border-[#f4be28]/35 flex items-center justify-center shadow-[0_0_35px_rgba(244, 190, 40,0.22)]">
{/* Pulsing outer aura */}
<div className="absolute inset-0 rounded-full border border-[#f4be28]/20 animate-ping opacity-60 pointer-events-none" />
{/* Rotating ↻ Icon */}
<svg
className="w-7 h-7 text-[#f4be28] animate-[spin_12s_linear_infinite]"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2.5"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</div>
</div>
)}
{s.num} · {s.title}
</button>
))}
</div>
{/* Cascading 4-up grid */}
<div className="relative z-10 wwd-grid-container max-w-6xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-7">
{STAGES.map((stage, i) => (
<div key={stage.num} className="wwd-cascade-item" style={{ ['--cascade-offset' as string]: `${(i % 2 === 1 ? 32 : 0)}px` }}>
<StageCard stage={stage} index={i} active={active === i} onHover={setActive} />
</div>
))}
</div>
</div>
<style>{`
@media (min-width: 1024px) {
.wwd-cascade-item:nth-child(2n) .wwd-card { --cascade: 36px; }
}
.wwd-card-inner::before {
content: '';
position: absolute; inset: 0; border-radius: inherit; padding: 1px;
background: linear-gradient(135deg, rgba(244,190,40,0.5), transparent 40%, transparent 60%, rgba(244,190,40,0.25));
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.4s ease;
pointer-events: none;
}
.wwd-card-active .wwd-card-inner::before { opacity: 1; }
`}</style>
</section>
);
}