feat: implement solutions page, use cases, and update landing page sections including MagicBento, ScrollStack, and interactive components

This commit is contained in:
R-Bharathraj
2026-07-04 17:28:38 +05:30
parent abcefbb9c9
commit 727a6bcb62
52 changed files with 3061 additions and 835 deletions

View File

@@ -1,7 +1,13 @@
'use client';
import React, { useState } from 'react';
import React, { useEffect, useLayoutEffect, useRef } from 'react';
import Image from 'next/image';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
if (typeof window !== 'undefined') gsap.registerPlugin(ScrollTrigger);
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
const GroceryIcon = () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>;
const FashionIcon = () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M20.38 3.46L16 2a4 4 0 01-8 0L3.62 3.46a2 2 0 00-1.34 2.23l.58 3.57a1 1 0 00.99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 002-2V10h2.15a1 1 0 00.99-.84l.58-3.57a2 2 0 00-1.34-2.23z"/></svg>;
@@ -11,16 +17,57 @@ const ElectronicsIcon = () => <svg width="20" height="20" viewBox="0 0 24 24" fi
const BeautyIcon = () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>;
const brands = [
{ name: 'Groceries', Icon: GroceryIcon, stores: ['BigBasket', 'DMart', 'Reliance Fresh'], color: '#22c55e' },
{ name: 'Groceries', Icon: GroceryIcon, stores: ['DMart', 'Reliance Fresh', "Spencer's"], color: '#22c55e' },
{ name: 'Fashion', Icon: FashionIcon, stores: ['Zara', 'H&M', 'Lifestyle'], color: '#a855f7' },
{ name: 'Food & Cafe', Icon: CafeIcon, stores: ['Starbucks', 'KFC', 'McD'], color: '#f97316' },
{ name: 'Sports', Icon: SportsIcon, stores: ['Decathlon', 'Nike', 'Adidas'], color: '#3b82f6' },
{ name: 'Electronics', Icon: ElectronicsIcon, stores: ['Croma', 'Reliance Digital'], color: '#06b6d4' },
{ name: 'Beauty', Icon: BeautyIcon, stores: ['Nykaa', 'MAC', 'Forest Essentials'], color: '#ec4899' },
{ name: 'Beauty', Icon: BeautyIcon, stores: ['Health & Glow', 'MAC', 'Forest Essentials'], color: '#ec4899' },
];
export default function SpendAnywhereSection() {
const [active, setActive] = useState<number | null>(null);
const gridRef = useRef<HTMLDivElement>(null);
const cardRefs = useRef<(HTMLDivElement | null)[]>([]);
// Scroll-triggered blur/scale entrance for the cards
useIsomorphicLayoutEffect(() => {
if (typeof window === 'undefined') return;
const grid = gridRef.current;
if (!grid) return;
const ctx = gsap.context(() => {
gsap.fromTo('.spend-card',
{ opacity: 0, y: 44, scale: 0.92, filter: 'blur(8px)' },
{
opacity: 1, y: 0, scale: 1, filter: 'blur(0px)',
duration: 0.9, stagger: 0.09, ease: 'expo.out',
scrollTrigger: { trigger: grid, start: 'top 85%', toggleActions: 'play none none reset' },
}
);
}, grid);
return () => ctx.revert();
}, []);
// Cursor-tracked spotlight + tilt per card
const handleMove = (i: number) => (e: React.MouseEvent<HTMLDivElement>) => {
const el = cardRefs.current[i];
if (!el) return;
const rect = el.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
el.style.setProperty('--mx', `${(x / rect.width) * 100}%`);
el.style.setProperty('--my', `${(y / rect.height) * 100}%`);
const rotateX = ((y - rect.height / 2) / rect.height) * -6;
const rotateY = ((x - rect.width / 2) / rect.width) * 6;
gsap.to(el, { rotateX, rotateY, duration: 0.4, ease: 'power2.out', transformPerspective: 700 });
};
const handleLeave = (i: number) => () => {
const el = cardRefs.current[i];
if (!el) return;
gsap.to(el, { rotateX: 0, rotateY: 0, duration: 0.5, ease: 'power3.out' });
};
return (
<section className="fp-panel" style={{ background: '#080810' }}>
@@ -33,7 +80,7 @@ export default function SpendAnywhereSection() {
{/* Header row */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', flexWrap: 'wrap', gap: 24, marginBottom: 'clamp(40px, 6vh, 64px)' }}>
<div>
<div style={{ marginBottom: 22 }}><span className="fp-kicker fp-rise">Spend · 04</span></div>
<div style={{ marginBottom: 22 }}><span className="fp-kicker fp-rise">Spend<span className="fp-kicker-num">04</span></span></div>
<h2 style={{
fontSize: 'clamp(46px, 7vw, 110px)', fontWeight: 800, letterSpacing: '-0.06em', lineHeight: 0.9,
color: '#fff', margin: 0, fontFamily: 'Sora, sans-serif',
@@ -45,89 +92,83 @@ export default function SpendAnywhereSection() {
<p className="fp-rise" style={{
fontSize: 15, lineHeight: 1.75, color: 'rgba(255,255,255,0.45)', fontFamily: 'Sora, sans-serif', margin: 0, maxWidth: 320,
}}>
One wallet. Every store. Your Loyaly points are universal: no restrictions, no expiry, no fine print.
One balance. Every store. Your LYT is universal: no restrictions, no expiry, no fine print.
</p>
</div>
{/* Category grid */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 16 }}>
<div ref={gridRef} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 16, perspective: 900 }}>
{brands.map((brand, i) => {
const BrandIcon = brand.Icon;
return (
<div
key={i}
onMouseEnter={() => setActive(i)}
onMouseLeave={() => setActive(null)}
className="fp-rise"
ref={el => { cardRefs.current[i] = el; }}
onMouseMove={handleMove(i)}
onMouseLeave={handleLeave(i)}
className="spend-card"
style={{
position: 'relative',
padding: '24px 28px',
padding: '26px 28px',
borderRadius: 22,
background: active === i ? 'rgba(12, 12, 22, 0.65)' : 'rgba(10, 10, 18, 0.45)',
border: `1px solid ${active === i ? `${brand.color}45` : 'rgba(255,255,255,0.08)'}`,
backdropFilter: 'blur(20px) saturate(140%)',
WebkitBackdropFilter: 'blur(20px) saturate(140%)',
background: '#0d0d16',
border: '1px solid rgba(255,255,255,0.08)',
cursor: 'default',
transitionDelay: `${0.08 + i * 0.06}s`,
transform: active === i ? 'translateY(-6px)' : 'translateY(0)',
boxShadow: active === i
? `0 24px 50px -12px rgba(0,0,0,0.65), 0 0 25px -4px ${brand.color}25, inset 0 1px 0 rgba(255,255,255,0.1)`
: '0 8px 30px -10px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255,255,255,0.05)',
transition: 'all 0.4s cubic-bezier(0.25, 1, 0.22, 1)',
overflow: 'hidden',
transformStyle: 'preserve-3d',
['--card-color' as string]: brand.color,
}}
>
{/* Glowing Left Accent Bar */}
<span style={{
position: 'absolute', left: 0, top: '20%', bottom: '20%', width: 3,
borderRadius: '0 4px 4px 0',
background: brand.color,
opacity: active === i ? 1 : 0,
transform: active === i ? 'scaleY(1.2)' : 'scaleY(0.8)',
transition: 'all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)',
boxShadow: `0 0 12px ${brand.color}`,
{/* Cursor-tracked spotlight */}
<div className="spend-card-glow" style={{
position: 'absolute', inset: 0, pointerEvents: 'none', borderRadius: 'inherit',
background: `radial-gradient(260px circle at var(--mx,50%) var(--my,50%), ${brand.color}26, transparent 65%)`,
}} />
{/* Border glow ring */}
<div className="spend-card-border" style={{
position: 'absolute', inset: 0, borderRadius: 'inherit', pointerEvents: 'none',
padding: 1,
background: `radial-gradient(220px circle at var(--mx,50%) var(--my,50%), ${brand.color}90, transparent 70%)`,
WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
WebkitMaskComposite: 'xor',
maskComposite: 'exclude',
}} />
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 18 }}>
{/* Icon Container with subtle background shape */}
<div style={{
width: 42,
height: 42,
borderRadius: 13,
background: active === i ? `${brand.color}20` : 'rgba(255,255,255,0.04)',
border: `1px solid ${active === i ? `${brand.color}45` : 'rgba(255,255,255,0.09)'}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: active === i ? brand.color : 'rgba(255,255,255,0.45)',
transition: 'all 0.35s cubic-bezier(0.25, 1, 0.22, 1)',
}}>
<BrandIcon />
<div style={{ position: 'relative', zIndex: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
<div className="spend-card-icon" style={{
width: 42, height: 42, borderRadius: 13,
background: `${brand.color}18`, border: `1px solid ${brand.color}40`,
display: 'flex', alignItems: 'center', justifyContent: 'center', color: brand.color,
}}>
<BrandIcon />
</div>
<span style={{
fontSize: 15, fontWeight: 800, color: '#fff', fontFamily: 'Sora, sans-serif', letterSpacing: '-0.01em',
}}>{brand.name}</span>
</div>
<span style={{
fontSize: 15,
fontWeight: 800,
color: active === i ? '#fff' : 'rgba(255,255,255,0.75)',
fontFamily: 'Sora, sans-serif',
letterSpacing: '-0.01em',
transition: 'color 0.3s ease',
}}>{brand.name}</span>
{/* Explore arrow, slides in on hover */}
<span className="spend-card-arrow" style={{
width: 28, height: 28, borderRadius: '50%', flexShrink: 0,
display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${brand.color}40`, color: brand.color,
opacity: 0, transform: 'translateX(-6px)',
transition: 'opacity 0.3s ease, transform 0.3s cubic-bezier(0.34,1.56,0.64,1)',
}}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
</span>
</div>
<div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
<div style={{ position: 'relative', zIndex: 2, height: 1, background: 'rgba(255,255,255,0.07)', marginBottom: 16 }} />
<div style={{ position: 'relative', zIndex: 2, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{brand.stores.map((s, j) => (
<span key={j} style={{
fontSize: 10,
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.04em',
fontFamily: 'Sora, sans-serif',
color: active === i ? '#ffffff' : 'rgba(255,255,255,0.3)',
background: active === i ? `${brand.color}22` : 'rgba(255,255,255,0.04)',
border: `1px solid ${active === i ? `${brand.color}40` : 'rgba(255,255,255,0.06)'}`,
borderRadius: 100,
padding: '5px 12px',
transition: 'all 0.3s cubic-bezier(0.25, 1, 0.22, 1)',
fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.04em',
fontFamily: 'Sora, sans-serif', color: 'rgba(255,255,255,0.55)',
background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.07)',
borderRadius: 100, padding: '5px 12px',
}}>{s}</span>
))}
</div>
@@ -143,8 +184,8 @@ export default function SpendAnywhereSection() {
}}>
{[
{ val: '10,000+', lbl: 'Partner stores' },
{ val: 'Zero', lbl: 'Expiry on points' },
{ val: 'Universal', lbl: 'One wallet, all brands' },
{ val: 'Zero', lbl: 'Expiry on LYT' },
{ val: 'Universal', lbl: 'One balance, all brands' },
{ val: 'Instant', lbl: 'Redeem at checkout' },
].map((s, i) => (
<div key={i}>
@@ -154,6 +195,17 @@ export default function SpendAnywhereSection() {
))}
</div>
</div>
<style>{`
.spend-card { transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; }
.spend-card:hover {
border-color: rgba(255,255,255,0.16);
box-shadow: 0 24px 50px -16px rgba(0,0,0,0.6);
}
.spend-card:hover .spend-card-arrow { opacity: 1; transform: translateX(0); }
.spend-card-glow, .spend-card-border { opacity: 0; transition: opacity 0.35s ease; }
.spend-card:hover .spend-card-glow, .spend-card:hover .spend-card-border { opacity: 1; }
`}</style>
</section>
);
}