Files
loyalyweb/src/components/for-people/EarnRewardsSection.tsx

241 lines
13 KiB
TypeScript

'use client';
import React, { useEffect, useRef, useState } from 'react';
import Image from 'next/image';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { PhoneFrame, PhoneBottomNav, BellIcon, FlameIcon } from './PhoneMockup';
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
const feed = [
{ store: 'Daily Login', pts: +50, time: '2m ago', type: 'earn' },
{ store: 'Walking 5,000 steps', pts: +80, time: '1h ago', type: 'earn' },
{ store: 'Spin Wheel Game', pts: +120, time: '3h ago', type: 'earn' },
{ store: 'Selfie Booth', pts: +200, time: 'Yesterday', type: 'earn' },
];
export default function EarnRewardsSection() {
const [pts, setPts] = useState(0);
const panelRef = useRef<HTMLDivElement>(null);
const rafRef = useRef<number>(0);
const rowRefs = useRef<(HTMLDivElement | null)[]>([]);
const activeRowRef = useRef(0);
// Move the highlighted feed card as the section scrolls through the viewport
useEffect(() => {
if (typeof window === 'undefined') return;
const el = panelRef.current;
if (!el) return;
const applyRow = (idx: number) => {
if (idx === activeRowRef.current) return;
activeRowRef.current = idx;
rowRefs.current.forEach((row, i) => {
if (!row) return;
const active = i === idx;
row.style.background = active ? '#f0fdf4' : 'transparent';
row.style.borderColor = active ? '#16a34a' : 'transparent';
row.style.transform = active ? 'translateX(4px) scale(1.02)' : 'translateX(0) scale(1)';
row.style.boxShadow = active ? '0 10px 24px -12px rgba(22,163,74,0.35)' : 'none';
const icon = row.querySelector<HTMLElement>('.feed-icon');
if (icon) {
icon.style.background = active ? 'rgba(22,163,74,0.14)' : 'rgba(23,21,18,0.04)';
icon.style.borderColor = active ? '#16a34a' : 'rgba(23,21,18,0.08)';
icon.style.color = active ? '#16a34a' : '#9ca3af';
}
const points = row.querySelector<HTMLElement>('.feed-pts');
if (points) {
points.style.background = active ? 'rgba(22,163,74,0.14)' : 'rgba(22,163,74,0.08)';
}
});
};
const st = ScrollTrigger.create({
trigger: el,
start: 'top 65%',
end: 'bottom 60%',
onUpdate: (self) => {
applyRow(Math.min(feed.length - 1, Math.floor(self.progress * feed.length)));
},
});
return () => st.kill();
}, []);
useEffect(() => {
const el = panelRef.current;
if (!el) return;
const obs = new MutationObserver(() => {
if (el.hasAttribute('data-active')) {
const start = performance.now();
const tick = (now: number) => {
const t = Math.min((now - start) / 1800, 1);
setPts(Math.round((1 - Math.pow(1 - t, 3)) * 284));
if (t < 1) rafRef.current = requestAnimationFrame(tick);
};
rafRef.current = requestAnimationFrame(tick);
}
});
obs.observe(el, { attributes: true, attributeFilter: ['data-active'] });
return () => { obs.disconnect(); cancelAnimationFrame(rafRef.current); };
}, []);
return (
<section ref={panelRef} className="fp-panel fp-light" style={{ background: '#ffffff' }}>
<div className="fp-shell">
<div className="fp-split" style={{ gridTemplateColumns: '1.05fr 0.95fr' }}>
{/* Left — points + headline */}
<div>
{/* Live label */}
<div className="fp-rise" style={{
display: 'inline-flex', alignItems: 'center', gap: 8, marginBottom: 20,
background: '#f0fdf4', border: '1.5px solid #16a34a', borderRadius: 100, padding: '7px 16px',
}}>
<span style={{ width: 7, height: 7, borderRadius: '50%', background: '#16a34a', flexShrink: 0, animation: 'fpPulse 1.5s infinite' }} />
<span style={{ fontSize: 11, fontWeight: 700, color: '#16a34a', fontFamily: 'Sora, sans-serif', letterSpacing: '0.1em', textTransform: 'uppercase' }}>Live LYTs balance</span>
</div>
{/* Giant gold number */}
<div style={{
fontSize: 'clamp(80px, 12vw, 168px)', fontWeight: 900, fontFamily: 'Sora, sans-serif',
color: '#f4be28', letterSpacing: '-0.05em', lineHeight: 0.92,
textShadow: '0 4px 40px rgba(244,190,40,0.25)',
}}>
{pts.toLocaleString()}
</div>
{/* Redeemable line */}
<div className="fp-rise" style={{
display: 'inline-flex', alignItems: 'center', gap: 10, marginTop: 14, marginBottom: 44,
padding: '10px 18px', borderRadius: 12,
background: '#fff7e0', border: '1px solid #f4be28',
}}>
<span style={{ fontSize: 18, fontWeight: 900, color: '#bc7122', fontFamily: 'Sora, sans-serif' }}>{pts.toLocaleString()}</span>
<span style={{ fontSize: 13, color: '#4f463a', fontFamily: 'Sora, sans-serif' }}>redeemable value</span>
</div>
<div style={{ marginBottom: 16 }}><span className="fp-kicker fp-rise">Earn<span className="fp-kicker-num">04</span></span></div>
<h2 style={{
fontSize: 'clamp(28px, 3.4vw, 48px)', fontWeight: 800, lineHeight: 1.08,
letterSpacing: '-0.04em', color: '#171512', margin: '0 0 20px', fontFamily: 'Sora, sans-serif',
}}>
<span className="fp-mask"><span>Every step.</span></span>
<span className="fp-mask"><span style={{ color: '#f4be28' }}>Every game.</span></span>
<span className="fp-mask"><span>Every day.</span></span>
</h2>
<p className="fp-rise" style={{
fontSize: 15, lineHeight: 1.8, color: '#4f463a', fontFamily: 'Sora, sans-serif', maxWidth: 380, margin: 0,
}}>
Earn LYTs (Loyaly Tokens) through daily app login, completing 5,000-step walking challenges, playing Maze, Spin Wheel, Scratch Card, and Match Master games, or visiting Selfie Booths at partner malls. Every activity adds more LYTs to your balance.
</p>
</div>
{/* Right — phone mockup, matches the in-app Earn / Home experience */}
<div className="fp-rise" style={{ transitionDelay: '0.15s', display: 'flex', justifyContent: 'center' }}>
<PhoneFrame floatClassName="erm-phone-shell">
{/* Amber header */}
<div style={{ background: 'linear-gradient(180deg, #fcd34d 0%, #f4be28 100%)', padding: '34px 16px 14px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
<Image src="/images/loyalyai.png" alt="Loyaly.AI" width={100} height={26} style={{ objectFit: 'contain', height: 16, width: 'auto' }} />
<div style={{ width: 26, height: 26, borderRadius: '50%', background: 'rgba(255,255,255,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<BellIcon />
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<FlameIcon />
<span style={{ fontSize: 11.5, fontWeight: 600, color: '#171512', fontFamily: 'Sora, sans-serif' }}>6-day streak · keep it going!</span>
</div>
</div>
{/* Balance card */}
<div style={{ margin: '14px 14px 0', borderRadius: 18, padding: '16px 18px', background: 'linear-gradient(135deg, #f4be28 0%, #e09820 100%)', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: '-30%', right: '-8%', width: 90, height: 90, borderRadius: '50%', background: 'rgba(255,255,255,0.14)' }} />
<div style={{ fontSize: 10, color: 'rgba(23,21,18,0.65)', fontWeight: 700, fontFamily: 'Sora, sans-serif', letterSpacing: '0.1em', textTransform: 'uppercase' }}>Total LYTs</div>
<div style={{ fontSize: 30, color: '#171512', fontWeight: 900, fontFamily: 'Sora, sans-serif', letterSpacing: '-0.03em', marginTop: 2 }}>284</div>
<div style={{ fontSize: 10.5, color: 'rgba(23,21,18,0.6)', fontFamily: 'Sora, sans-serif', marginTop: 3 }}> 284 redeemable</div>
</div>
{/* Feed header */}
<div style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
margin: '16px 14px 6px',
}}>
<span style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#171512', fontFamily: 'Sora, sans-serif' }}>Activity feed</span>
<span style={{ fontSize: 9.5, color: '#16a34a', fontWeight: 700, fontFamily: 'Sora, sans-serif', display: 'flex', alignItems: 'center', gap: 4,
background: '#f0fdf4', border: '1px solid #16a34a', borderRadius: 100, padding: '3px 8px',
}}>
<span style={{ width: 4.5, height: 4.5, borderRadius: '50%', background: '#16a34a', display: 'block', animation: 'fpPulse 1.5s infinite' }} />
LIVE
</span>
</div>
{/* Feed rows */}
<div style={{ padding: '2px 10px', display: 'flex', flexDirection: 'column', gap: 3 }}>
{feed.map((tx, i) => (
<div key={i} ref={el => { rowRefs.current[i] = el; }} style={{
display: 'grid', gridTemplateColumns: '32px minmax(0,1fr) auto', alignItems: 'center', gap: 10,
padding: '8px 8px', borderRadius: 12,
background: i === 0 ? '#f0fdf4' : 'transparent',
border: `1px solid ${i === 0 ? '#16a34a' : 'transparent'}`,
transform: i === 0 ? 'translateX(4px) scale(1.02)' : 'translateX(0) scale(1)',
boxShadow: i === 0 ? '0 10px 24px -12px rgba(22,163,74,0.35)' : 'none',
transition: 'background 0.35s ease, border-color 0.35s ease, transform 0.35s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.35s ease',
}}>
<div className="feed-icon" style={{
width: 32, height: 32, borderRadius: 9, flexShrink: 0,
background: i === 0 ? 'rgba(22,163,74,0.14)' : 'rgba(23,21,18,0.04)',
border: `1.5px solid ${i === 0 ? '#16a34a' : 'rgba(23,21,18,0.08)'}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: i === 0 ? '#16a34a' : '#9ca3af',
transition: 'background 0.35s ease, border-color 0.35s ease, color 0.35s ease',
}}>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><path d="M12 19V5" /><path d="M5 12l7-7 7 7" /></svg>
</div>
<div style={{ minWidth: 0 }}>
<div style={{
fontSize: 11.5, fontWeight: 700, color: '#171512', fontFamily: 'Sora, sans-serif',
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
}}>{tx.store}</div>
<div style={{ fontSize: 9, color: '#9ca3af', fontFamily: 'Sora, sans-serif', marginTop: 1 }}>{tx.time}</div>
</div>
<span className="feed-pts" style={{
fontSize: 12, fontWeight: 900, fontFamily: 'Sora, sans-serif',
color: '#16a34a', letterSpacing: '-0.01em',
background: i === 0 ? 'rgba(22,163,74,0.14)' : 'rgba(22,163,74,0.08)',
padding: '3px 8px', borderRadius: 8, whiteSpace: 'nowrap',
transition: 'background 0.35s ease',
}}>
+{tx.pts}
</span>
</div>
))}
</div>
{/* Summary strip */}
<div style={{ margin: '10px 14px 12px', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
{[
{ label: 'Earned this month', val: '660 LYTs', color: '#16a34a', bg: '#f0fdf4', border: '#16a34a' },
{ label: 'Redeemable value', val: '₹660', color: '#bc7122', bg: '#fff7e0', border: '#f4be28' },
].map((s, i) => (
<div key={i} style={{ padding: '10px 12px', borderRadius: 12, background: s.bg, border: `1.5px solid ${s.border}` }}>
<div style={{ fontSize: 8.5, color: 'rgba(23,21,18,0.5)', fontFamily: 'Sora, sans-serif', marginBottom: 3 }}>{s.label}</div>
<div style={{ fontSize: 14, fontWeight: 900, color: s.color, fontFamily: 'Sora, sans-serif' }}>{s.val}</div>
</div>
))}
</div>
<PhoneBottomNav active="home" />
</PhoneFrame>
</div>
</div>
</div>
</section>
);
}