convert-next.js

This commit is contained in:
R-Bharathraj
2026-06-29 18:07:43 +05:30
parent 4760f8e063
commit 0140b0bdf8
1474 changed files with 16523 additions and 9272 deletions

View File

@@ -0,0 +1,344 @@
'use client';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import AnimatedDarkBg from './AnimatedDarkBg';
if (typeof window !== 'undefined') {
gsap.registerPlugin(ScrollTrigger);
}
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
interface Step {
step: string;
id: 'earn' | 'engage' | 'spend' | 'repeat';
title: string;
desc: string;
}
const STEPS: Step[] = [
{ step: '01', id: 'earn', title: 'Earn Online', desc: 'Daily login, 5,000-step walking challenges, and interactive games like Maze and Spin Wheel earn Loyaly Coins and unlock surprise coupons.' },
{ step: '02', id: 'engage', title: 'Unlock Deals', desc: 'Merchants create game-based discount campaigns — customers play once to reveal their exclusive reward on a specific product.' },
{ step: '03', id: 'spend', title: 'Discover & Visit', desc: 'Browse participating stores nearby, view active game campaigns and coin-earning offers, then walk in and shop in person.' },
{ step: '04', id: 'repeat', title: 'Redeem & Repeat', desc: 'Use Loyaly Coins and coupons at checkout to save money — then come back tomorrow to earn more and keep the loop going.' },
];
const IMPACTS = [
{ label: 'Conversion Rates', value: 42, suffix: '%', prefix: '+', decimals: 0 },
{ label: 'Repeat Customers', value: 3.8, suffix: 'x', prefix: '+', decimals: 1 },
{ label: 'Discount Optimization', value: 28, suffix: '%', prefix: '+', decimals: 0 },
{ label: 'Real-Time Decisions', value: 500, suffix: 'ms', prefix: '<', decimals: 0 },
];
function StepIcon({ id }: { id: Step['id'] }) {
const p = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const };
if (id === 'earn') return (
<svg viewBox="0 0 24 24" {...p} style={{ width: '100%', height: '100%' }}>
<circle cx="12" cy="12" r="9" />
<path d="M14.5 9a2.5 2.5 0 00-5 0c0 2.5 5 2.5 5 5a2.5 2.5 0 01-5 0" />
<path d="M12 6.5V5M12 19v-1.5" />
</svg>
);
if (id === 'engage') return (
<svg viewBox="0 0 24 24" {...p} style={{ width: '100%', height: '100%' }}>
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
);
if (id === 'spend') return (
<svg viewBox="0 0 24 24" {...p} style={{ width: '100%', height: '100%' }}>
<rect x="1.5" y="4" width="21" height="16" rx="2" />
<line x1="1.5" y1="9.5" x2="22.5" y2="9.5" />
</svg>
);
return (
<svg viewBox="0 0 24 24" {...p} style={{ width: '100%', height: '100%' }}>
<polyline points="17 1 21 5 17 9" />
<path d="M3 11V9a4 4 0 014-4h14" />
<polyline points="7 23 3 19 7 15" />
<path d="M21 13v2a4 4 0 01-4 4H3" />
</svg>
);
}
export default function TheLoop() {
const [active, setActive] = useState(0);
const [counts, setCounts] = useState<number[]>(IMPACTS.map(() => 0));
const [revealed, setRevealed] = useState(false);
const sectionRef = useRef<HTMLDivElement>(null);
const metricsRef = useRef<HTMLDivElement>(null);
// Sequential highlight cycle (client-only → no hydration mismatch)
useEffect(() => {
const t = setInterval(() => setActive(a => (a + 1) % STEPS.length), 1900);
return () => clearInterval(t);
}, []);
// Scroll-triggered reveals + count-up, all via GSAP ScrollTrigger
useIsomorphicLayoutEffect(() => {
if (typeof window === 'undefined') return;
const ctx = gsap.context(() => {
// Reveal the flow diagram cards on scroll-in
ScrollTrigger.create({
trigger: '.loop-diagram',
start: 'top 78%',
once: true,
onEnter: () => setRevealed(true),
});
// Subtle parallax drift on the diagram + metrics as the section scrolls
gsap.fromTo('.loop-diagram', { y: 40 }, {
y: -40, ease: 'none',
scrollTrigger: { trigger: '.loop-diagram', start: 'top bottom', end: 'bottom top', scrub: 1 },
});
// Count-up metrics when they scroll into view
ScrollTrigger.create({
trigger: metricsRef.current,
start: 'top 82%',
once: true,
onEnter: () => {
const start = performance.now();
const dur = 1800;
const tick = (now: number) => {
const t = Math.min((now - start) / dur, 1);
const ease = 1 - Math.pow(1 - t, 3);
setCounts(IMPACTS.map(m => m.value * ease));
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
},
});
// Metric cells stagger in
gsap.fromTo('.loop-metric', { opacity: 0, y: 26 }, {
opacity: 1, y: 0, duration: 0.7, stagger: 0.1, ease: 'power3.out',
scrollTrigger: { trigger: metricsRef.current, start: 'top 82%', toggleActions: 'play none none reset' },
});
ScrollTrigger.refresh();
}, sectionRef);
return () => ctx.revert();
}, []);
return (
<section
ref={sectionRef}
id="the-loop"
style={{
position: 'relative',
width: 'calc(100% - 40px)',
margin: '0 20px 20px',
borderRadius: '32px',
overflow: 'hidden',
zIndex: 2,
padding: 'clamp(80px, 10vh, 140px) clamp(28px, 5vw, 80px) clamp(72px, 8vh, 120px)',
}}
>
<AnimatedDarkBg variant="amber" />
{/* ── Header ── */}
<div style={{ position: 'relative', zIndex: 10, textAlign: 'center', maxWidth: 760, margin: '0 auto' }}>
<div style={{
display: 'inline-flex', alignItems: 'center', gap: 8, marginBottom: 24,
padding: '7px 18px', borderRadius: 999,
background: 'rgba(244,190,40,0.08)', border: '1px solid rgba(244,190,40,0.22)',
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#f4be28', display: 'block' }} />
<span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: '#f4be28', fontFamily: 'Manrope, sans-serif' }}>The Loop</span>
</div>
<h2 style={{
margin: '0 0 18px',
fontSize: 'clamp(2.6rem, 6vw, 5rem)',
fontWeight: 900, color: '#fff',
lineHeight: 0.92, letterSpacing: '-0.05em',
fontFamily: 'Sora, sans-serif',
}}>
A smarter<br />
<span style={{
background: 'linear-gradient(105deg,#f4be28 0%,#fff8e0 48%,#f4be28 62%,#ff9500 100%)',
backgroundSize: '200% auto', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
animation: 'loopShimmer 3s linear infinite',
}}>retail cycle.</span>
</h2>
<p style={{
margin: '0 auto', maxWidth: 480,
fontSize: 'clamp(0.95rem, 1.1vw, 1.05rem)',
color: 'rgba(255,255,255,0.42)', lineHeight: 1.8,
fontFamily: 'Manrope, sans-serif',
}}>
Earn coins online, unlock game-based deals, discover nearby stores, and redeem offline a self-reinforcing loop that keeps customers engaged and drives repeat footfall.
</p>
</div>
{/* ── Flow diagram ── */}
<div className="loop-diagram" style={{
position: 'relative', zIndex: 10,
maxWidth: 1180, margin: 'clamp(140px, 16vh, 200px) auto 0',
}}>
{/* Loop-back arc (desktop) */}
<svg className="loop-arc" viewBox="0 0 1000 120" preserveAspectRatio="none" aria-hidden style={{
position: 'absolute', top: -64, left: '11%', width: '78%', height: 70, overflow: 'visible',
}}>
<defs>
<linearGradient id="loopArcGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stopColor="rgba(244,190,40,0)" />
<stop offset="50%" stopColor="rgba(244,190,40,0.55)" />
<stop offset="100%" stopColor="rgba(244,190,40,0)" />
</linearGradient>
</defs>
<path d="M 985 118 C 985 20, 800 8, 500 8 C 200 8, 15 20, 15 118"
fill="none" stroke="url(#loopArcGrad)" strokeWidth="2"
strokeDasharray="7 9" strokeLinecap="round"
style={{ animation: revealed ? 'loopDash 1.4s linear infinite' : 'none' }} />
<path d="M 8 104 L 15 120 L 22 104" fill="none" stroke="rgba(244,190,40,0.6)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
{/* Center "continuous" label on the arc */}
<div className="loop-arc-label" style={{
position: 'absolute', top: -78, left: '50%', transform: 'translateX(-50%)',
fontSize: 10, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase',
color: 'rgba(244,190,40,0.7)', fontFamily: 'Manrope, sans-serif',
background: 'rgba(244,190,40,0.06)', border: '1px solid rgba(244,190,40,0.18)',
padding: '5px 14px', borderRadius: 999, whiteSpace: 'nowrap',
}}>
Continuous loop
</div>
{/* Steps row */}
<div className="loop-row" style={{ display: 'flex', alignItems: 'stretch', gap: 0 }}>
{STEPS.map((s, i) => {
const isActive = i === active;
return (
<React.Fragment key={s.id}>
<div
className="loop-card"
style={{
flex: 1,
position: 'relative',
padding: 'clamp(26px, 2.2vw, 38px) clamp(20px, 1.8vw, 28px)',
borderRadius: 26,
background: isActive ? 'rgba(244,190,40,0.07)' : 'rgba(255,255,255,0.025)',
border: `1px solid ${isActive ? 'rgba(244,190,40,0.45)' : 'rgba(255,255,255,0.07)'}`,
backdropFilter: 'blur(20px)',
WebkitBackdropFilter: 'blur(20px)',
boxShadow: isActive
? '0 28px 80px rgba(244,190,40,0.2), inset 0 1px 0 rgba(255,255,255,0.08)'
: 'inset 0 1px 0 rgba(255,255,255,0.04)',
transform: revealed ? (isActive ? 'translateY(-10px) scale(1.02)' : 'translateY(0) scale(1)') : 'translateY(48px) scale(0.98)',
opacity: revealed ? 1 : 0,
transition: `transform 0.7s cubic-bezier(0.22,1,0.36,1) ${i * 0.1}s, opacity 0.7s ease ${i * 0.1}s, background 0.5s ease, border-color 0.5s ease, box-shadow 0.5s ease`,
}}
>
{/* big ghost number */}
<span style={{
position: 'absolute', top: 8, right: 16,
fontSize: 'clamp(3rem, 5vw, 5rem)', fontWeight: 900, lineHeight: 1,
color: 'transparent', WebkitTextStroke: `1px rgba(244,190,40,${isActive ? 0.2 : 0.06})`,
fontFamily: 'Sora, sans-serif', letterSpacing: '-0.05em',
pointerEvents: 'none', userSelect: 'none', transition: 'all 0.5s ease',
}}>{s.step}</span>
{/* icon */}
<div style={{
width: 56, height: 56, borderRadius: 16, padding: 13, marginBottom: 22,
background: isActive ? 'rgba(244,190,40,0.16)' : 'rgba(244,190,40,0.08)',
border: `1px solid rgba(244,190,40,${isActive ? 0.4 : 0.2})`,
color: '#f4be28',
boxShadow: isActive ? '0 0 30px rgba(244,190,40,0.35)' : 'none',
transition: 'all 0.5s ease',
position: 'relative', zIndex: 2,
}}>
<StepIcon id={s.id} />
</div>
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'rgba(244,190,40,0.7)', fontFamily: 'Manrope, sans-serif', marginBottom: 8 }}>
Step {s.step}
</div>
<h3 style={{ margin: '0 0 10px', fontSize: 'clamp(1.15rem, 1.5vw, 1.5rem)', fontWeight: 800, color: '#fff', letterSpacing: '-0.02em', lineHeight: 1.1, fontFamily: 'Sora, sans-serif' }}>
{s.title}
</h3>
<p style={{ margin: 0, fontSize: '0.86rem', lineHeight: 1.65, color: 'rgba(255,255,255,0.42)', fontFamily: 'Manrope, sans-serif' }}>
{s.desc}
</p>
</div>
{/* connector arrow between cards */}
{i < STEPS.length - 1 && (
<div className="loop-connector" style={{
flexShrink: 0, alignSelf: 'center',
width: 'clamp(28px, 3vw, 56px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
position: 'relative',
}}>
<div style={{
position: 'absolute', left: 0, right: 0, height: 2, borderRadius: 2,
background: 'linear-gradient(90deg, rgba(244,190,40,0.1), rgba(244,190,40,0.45), rgba(244,190,40,0.1))',
backgroundSize: '200% 100%',
animation: revealed ? 'loopFlow 1.8s linear infinite' : 'none',
}} />
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#f4be28" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ position: 'relative', zIndex: 2, opacity: 0.8 }}>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</div>
)}
</React.Fragment>
);
})}
</div>
</div>
{/* ── Impact metrics ── */}
<div ref={metricsRef} style={{ position: 'relative', zIndex: 10, maxWidth: 1000, margin: 'clamp(64px, 8vh, 110px) auto 0' }}>
<div style={{ textAlign: 'center', marginBottom: 36 }}>
<span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)', fontFamily: 'Manrope, sans-serif' }}>
Measured Impact
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: 2, borderRadius: 24, overflow: 'hidden' }}>
{IMPACTS.map((m, i) => (
<div key={m.label} className="loop-metric" style={{
padding: 'clamp(28px, 3vw, 44px) 28px',
background: 'rgba(255,255,255,0.025)',
borderRight: i < IMPACTS.length - 1 ? '1px solid rgba(255,255,255,0.06)' : 'none',
textAlign: 'center',
}}>
<div style={{
fontSize: 'clamp(2.4rem, 4vw, 3.4rem)', fontWeight: 900,
color: '#f4be28', lineHeight: 1, letterSpacing: '-0.04em',
fontVariantNumeric: 'tabular-nums', fontFamily: 'Sora, sans-serif',
}}>
<span style={{ fontSize: '55%', verticalAlign: 'middle', marginRight: 1 }}>{m.prefix}</span>
{counts[i].toFixed(m.decimals)}
<span style={{ fontSize: '50%', verticalAlign: 'middle', marginLeft: 1 }}>{m.suffix}</span>
</div>
<div style={{ fontSize: 13, fontWeight: 600, color: 'rgba(255,255,255,0.5)', fontFamily: 'Manrope, sans-serif', marginTop: 12, lineHeight: 1.4 }}>
{m.label}
</div>
</div>
))}
</div>
</div>
<style>{`
@keyframes loopShimmer { 0%{background-position:200% center} 100%{background-position:-200% center} }
@keyframes loopDash { to { stroke-dashoffset: -32; } }
@keyframes loopFlow { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
@media (max-width: 880px) {
.loop-row { flex-direction: column; }
.loop-connector { width: 100% !important; height: 40px; transform: rotate(90deg); }
.loop-arc, .loop-arc-label { display: none !important; }
}
@media (prefers-reduced-motion: reduce) {
#the-loop [style*="animation"] { animation: none !important; }
}
`}</style>
</section>
);
}