'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 (
);
if (id === 'engage') return (
);
if (id === 'spend') return (
);
return (
);
}
export default function TheLoop() {
const [active, setActive] = useState(0);
const [counts, setCounts] = useState(IMPACTS.map(() => 0));
const [revealed, setRevealed] = useState(false);
const sectionRef = useRef(null);
const metricsRef = useRef(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 (
{/* ── Header ── */}
The Loop
A smarter retail cycle.
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.
{/* ── Flow diagram ── */}
{/* Loop-back arc (desktop) */}
{/* Center "continuous" label on the arc */}