241 lines
13 KiB
TypeScript
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>
|
|
);
|
|
}
|