Files
lytsup-site/src/components/layout/Footer.tsx

397 lines
18 KiB
TypeScript

'use client';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { gsap, useGSAP, syncLenisScrollTrigger } from '../story/gsapkit';
import { Ico } from '../story/Ico';
const INK = '#0a0a0a';
const BLUE = '#2F6BFF';
const NAV = [
{
title: 'Earn',
color: '#C8FF37', // lime
links: [
{ label: 'Walk to Earn', href: '#how-it-works' },
{ label: 'Play & Win', href: '#how-it-works' },
{ label: 'Refer Friends', href: '#how-it-works' },
],
},
{
title: 'Spend',
color: '#FF2E88', // pink
links: [
{ label: 'The Difference', href: '#difference' },
{ label: 'How It Works', href: '#how-it-works' },
{ label: 'The Loop', href: '#the-loop' },
],
},
{
title: 'Live',
color: '#8B3DFF', // purple
links: [
{ label: 'The Vision', href: '#the-vision' },
{ label: 'The Why', href: '#the-why' },
{ label: 'Our Belief', href: '#our-belief' },
],
},
];
const SOCIALS = [
{
label: 'Instagram', href: 'https://instagram.com/lytsup', bg: '#FF2E88',
icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>,
},
{
label: 'X / Twitter', href: 'https://twitter.com/lytsup', bg: '#8B3DFF',
icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M13.98 10.62 20.54 3h-1.55l-5.7 6.62L8.74 3H3.5l6.88 10.01L3.5 21h1.55l6.01-6.99L15.87 21h5.24l-7.13-10.38zm-2.13 2.47-.7-1-5.54-7.93H8l4.47 6.4.7 1 5.82 8.32h-2.39l-4.74-6.79z"/></svg>,
},
{
label: 'YouTube', href: 'https://www.youtube.com/@lytsup', bg: '#FF6B35',
icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M21.8 8s-.2-1.38-.8-1.99c-.76-.8-1.61-.8-2-.85C16.2 5 12 5 12 5s-4.2 0-7 .17c-.39.05-1.24.05-2 .85C2.4 6.62 2.2 8 2.2 8S2 9.62 2 11.24v1.52c0 1.62.2 3.24.2 3.24s.2 1.38.8 1.99c.76.8 1.76.77 2.2.85C6.8 19 12 19.03 12 19.03s4.2-.01 7-.22c.39-.05 1.24-.05 2-.85.6-.61.8-1.99.8-1.99S22 14.38 22 12.76v-1.52C22 9.62 21.8 8 21.8 8zm-11.87 6.59V8.79l5.4 2.82-5.4 2.98z"/></svg>,
},
];
/* Kinetic ticker phrases + the giant wordmark letters */
const TICKER = ['Earn', 'Spend', 'Live More', 'Make Everyday Count', 'Join The Loop', 'Real Value'];
function Star({ size = 22, color = INK }: { size?: number; color?: string }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill={color} aria-hidden="true" style={{ flexShrink: 0 }}>
<path d="M12 0l2.5 8.2L22 6l-5.4 6.2L22 18l-7.5-2.2L12 24l-2.5-8.2L2 18l5.4-6.2L2 6l7.5 2.2z" />
</svg>
);
}
export default function Footer() {
const [showScroll, setShowScroll] = useState(false);
const rootRef = useRef<HTMLElement>(null);
useEffect(() => {
const onScroll = () => setShowScroll(window.scrollY > 400);
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
useGSAP(
() => {
syncLenisScrollTrigger();
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduce) {
/* Continuous continuous animations */
gsap.to('.foot-ticker-track', { xPercent: -50, duration: 22, ease: 'none', repeat: -1 });
gsap.to('.foot-cta-star-spin', { rotate: 360, duration: 9, ease: 'none', repeat: -1 });
/* ── 3D Sticker Fold-Out & Stamp Burst Reveal for Footer CTA Banner ── */
gsap.set('.lu-cta', {
transformStyle: 'preserve-3d',
transformOrigin: '50% 100%',
rotationX: 35,
rotationY: -10,
scale: 0.85,
y: 70,
autoAlpha: 0,
boxShadow: '0 40px 80px rgba(0,0,0,0.4)',
});
gsap.set('.foot-cta-star', { scale: 0, rotation: -180, autoAlpha: 0 });
gsap.set('.lu-cta-pill', { y: -25, autoAlpha: 0 });
gsap.set('.lu-cta-heading', { y: 30, rotationX: -40, autoAlpha: 0 });
gsap.set('.lu-cta-btn', { scale: 0.6, rotationZ: -12, autoAlpha: 0 });
gsap.set('.lu-columns > *', { y: 40, autoAlpha: 0 });
const tl = gsap.timeline({
scrollTrigger: {
trigger: '.lu-cta-wrap',
start: 'top 78%',
once: true,
},
defaults: { ease: 'power3.out' },
});
/* Phase 1: Card Unfolds from 3D Stack */
tl.to('.lu-cta', {
rotationX: 0,
rotationY: 0,
scale: 1,
y: 0,
autoAlpha: 1,
boxShadow: '8px 8px 0 0 #0a0a0a',
duration: 0.85,
ease: 'back.out(1.5)',
onComplete: () => {
gsap.set('.lu-cta', { clearProps: 'transform,transformStyle' });
},
})
/* Phase 2: Pink Star Pops Out from Top Corner */
.to(
'.foot-cta-star',
{
scale: 1,
rotation: 0,
autoAlpha: 1,
duration: 0.5,
ease: 'back.out(2)',
},
'-=0.4'
)
/* Phase 3: Purple Pill Tag Drops Down */
.to(
'.lu-cta-pill',
{
y: 0,
autoAlpha: 1,
duration: 0.4,
ease: 'back.out(1.8)',
},
'-=0.3'
)
/* Phase 4: Heading 3D Unrolls */
.to(
'.lu-cta-heading',
{
y: 0,
rotationX: 0,
autoAlpha: 1,
duration: 0.55,
ease: 'power4.out',
},
'-=0.3'
)
/* Phase 5: Yellow Download Button Stamps On */
.to(
'.lu-cta-btn',
{
scale: 1,
rotationZ: 0,
autoAlpha: 1,
duration: 0.5,
ease: 'back.out(2.2)',
},
'-=0.35'
)
/* Phase 6: Footer columns cascade in */
.to(
'.lu-columns > *',
{
y: 0,
autoAlpha: 1,
duration: 0.6,
stagger: 0.1,
ease: 'power3.out',
},
'-=0.2'
);
}
},
{ scope: rootRef },
);
return (
<>
<footer ref={rootRef} className="lu-footer font-sora" style={{
background: '#FFFDF5',
color: INK,
position: 'relative',
overflow: 'hidden',
margin: '0 var(--sec-gutter) var(--sec-gap)',
border: `4px solid ${INK}`,
borderRadius: 28,
boxShadow: '0 12px 0 0 rgba(0,0,0,0.35)',
}}>
{/* ── KINETIC TICKER STRIP ── */}
<div className="foot-ticker" style={{
overflow: 'hidden',
background: '#C8FF37',
borderBottom: `4px solid ${INK}`,
}}>
<div className="foot-ticker-track" style={{ display: 'flex', width: 'max-content', willChange: 'transform' }}>
{[0, 1].map((half) => (
<div key={half} aria-hidden={half === 1} style={{ display: 'flex', alignItems: 'center' }}>
{TICKER.map((t, i) => (
<span key={`${half}-${i}`} style={{ display: 'inline-flex', alignItems: 'center', gap: 22, padding: '13px 22px' }}>
<span style={{ fontSize: 'clamp(18px, 2.4vw, 30px)', fontWeight: 900, textTransform: 'uppercase', letterSpacing: '-0.01em', color: INK, whiteSpace: 'nowrap' }}>{t}</span>
<Star size={20} />
</span>
))}
</div>
))}
</div>
</div>
{/* ── BIG CTA STICKER CARD (3D Fold-Out Reveal) ── */}
<div className="lu-cta-wrap" style={{ padding: '56px 48px 40px', perspective: 1200 }}>
<div className="lu-cta" style={{
position: 'relative',
background: BLUE,
color: '#fff',
border: `4px solid ${INK}`,
borderRadius: 24,
boxShadow: `8px 8px 0 0 ${INK}`,
padding: '48px 48px',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
flexWrap: 'wrap', gap: 28,
overflow: 'hidden',
}}>
{/* rotating star sticker */}
<span className="foot-cta-star" aria-hidden style={{ position: 'absolute', top: -20, right: 30, willChange: 'transform' }}>
<span className="foot-cta-star-spin" style={{ display: 'inline-flex', background: '#FF2E88', border: `3px solid ${INK}`, borderRadius: '50%', padding: 10, boxShadow: `3px 3px 0 0 ${INK}` }}>
<Star size={22} color="#fff" />
</span>
</span>
<div style={{ maxWidth: 640 }}>
<span className="lu-cta-pill" style={{ display: 'inline-block', background: '#8B3DFF', color: '#fff', border: `3px solid ${INK}`, borderRadius: 10, padding: '5px 12px', fontSize: 11, fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.14em', marginBottom: 18, boxShadow: `3px 3px 0 0 ${INK}` }}>
Earn. Spend. Live More.
</span>
<h2 className="lu-cta-heading" style={{ fontSize: 'clamp(32px, 4.8vw, 62px)', fontWeight: 800, letterSpacing: '-0.03em', lineHeight: 0.96, margin: 0 }}>
Turn everyday moments<br />
into <span style={{ position: 'relative', display: 'inline-block', color: '#FF2E88' }}>
real value.
<span aria-hidden style={{ position: 'absolute', left: 0, right: 0, bottom: 4, height: 6, background: '#C8FF37', zIndex: -1, borderRadius: 4 }} />
</span>
</h2>
</div>
<Link href="#how-it-works" className="lu-cta-btn" style={{
display: 'inline-flex', alignItems: 'center', gap: 10,
padding: '18px 36px', background: '#FFC72C', color: INK,
fontSize: 17, fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.03em',
border: `4px solid ${INK}`, borderRadius: 16, textDecoration: 'none',
boxShadow: `5px 5px 0 0 ${INK}`, whiteSpace: 'nowrap',
transition: 'transform 0.12s ease, box-shadow 0.12s ease',
}}>
Download Now
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</Link>
</div>
</div>
{/* ── COLUMNS ── */}
<div className="lu-columns" style={{
display: 'grid',
gridTemplateColumns: '1.6fr 1fr 1fr 1fr',
gap: 40, padding: '24px 48px 36px',
alignItems: 'start',
}}>
{/* Brand */}
<div className="lu-brand" style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<Link href="#intro" aria-label="LytsUp home" style={{
display: 'inline-flex', marginBottom: 22, textDecoration: 'none',
}}>
<Image src="/images/lytsup_app.png" alt="LytsUp" width={2048} height={554} style={{ height: 52, width: 'auto', display: 'block' }} />
</Link>
<p style={{ fontSize: 15, lineHeight: 1.6, color: 'rgba(10,10,10,0.6)', margin: '0 0 24px', maxWidth: 300 }}>
Offline shopping. Online intelligence.
</p>
<div style={{ display: 'flex', gap: 12 }}>
{SOCIALS.map(({ label, href, icon, bg }) => (
<a key={label} href={href} target="_blank" rel="noopener noreferrer" aria-label={label}
className="lu-social"
style={{
width: 42, height: 42, borderRadius: 12,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: bg, color: '#fff', border: `3px solid ${INK}`,
boxShadow: `3px 3px 0 0 ${INK}`,
transition: 'transform 0.12s ease, box-shadow 0.12s ease',
}}
>{icon}</a>
))}
</div>
</div>
{/* Nav columns */}
{NAV.map(col => (
<nav key={col.title} className="lu-nav" aria-label={col.title}>
<h4 style={{ display: 'inline-block', margin: '0 0 18px', fontSize: 12, fontWeight: 900, letterSpacing: '0.08em', textTransform: 'uppercase', color: INK, background: col.color, border: `2px solid ${INK}`, borderRadius: 8, padding: '4px 10px', boxShadow: `2px 2px 0 0 ${INK}` }}>
{col.title}
</h4>
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
{col.links.map(({ label, href }) => (
<li key={label}>
<Link href={href} className="lu-link" style={{ textDecoration: 'none', fontSize: 15, color: 'rgba(10,10,10,0.62)', fontWeight: 600, transition: 'color 0.15s, transform 0.15s', display: 'inline-block' }}
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.color = INK; el.style.transform = 'translateX(4px)'; }}
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.color = 'rgba(10,10,10,0.62)'; el.style.transform = 'translateX(0)'; }}
>{label}</Link>
</li>
))}
</ul>
</nav>
))}
</div>
{/* ── BOTTOM BAR ── */}
<div className="lu-bottom" style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
flexWrap: 'wrap', gap: 14,
padding: '18px 48px 30px',
borderTop: '2px dashed rgba(10,10,10,0.2)',
fontSize: 13, color: 'rgba(10,10,10,0.55)',
}}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontWeight: 600 }}>
© {new Date().getFullYear()} LytsUp. Made with
<Ico e="♥" size={14} /> for Gen Z.
</span>
<div style={{ display: 'flex', gap: 20 }}>
{[['Terms', '#'], ['Privacy', '#'], ['Cookies', '#']].map(([l, h]) => (
<Link key={l} href={h} style={{ textDecoration: 'none', color: 'inherit', fontWeight: 600, transition: 'color 0.15s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = INK)}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = 'rgba(10,10,10,0.55)')}
>{l}</Link>
))}
</div>
</div>
<style>{`
.lu-cta-btn:hover, .lu-social:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 0 ${INK} !important; }
.lu-cta-btn:active { transform: translate(1px, 1px); box-shadow: 2px 2px 0 0 ${INK} !important; }
@media (max-width: 900px) {
.lu-footer { margin: 0 12px 12px; border-radius: 22px; }
.lu-cta-wrap { padding: 32px 20px 24px !important; }
.lu-cta { padding: 32px 26px !important; align-items: flex-start !important; }
.lu-cta-btn { width: 100%; justify-content: center; }
.lu-columns { grid-template-columns: 1fr 1fr !important; gap: 32px 24px !important; padding: 16px 24px 28px !important; }
.lu-brand { grid-column: 1 / -1; }
.lu-bottom { padding: 18px 24px 26px !important; flex-direction: column !important; align-items: flex-start !important; }
}
@media (max-width: 520px) {
.lu-columns { grid-template-columns: 1fr !important; }
.lu-cta-heading { font-size: clamp(26px, 8vw, 40px) !important; }
}
`}</style>
</footer>
{/* Scroll-to-top — neobrutalist */}
<style>{`
@keyframes luBounce { 0%,100% { transform: translateY(0);} 40% { transform: translateY(-4px);} 60% { transform: translateY(-2px);} }
.lu-stt:hover { transform: translate(-2px,-2px) !important; box-shadow: 6px 6px 0 0 ${INK} !important; }
.lu-stt:active { transform: translate(1px,1px) !important; box-shadow: 1px 1px 0 0 ${INK} !important; }
.lu-stt-arrow { animation: luBounce 1.5s ease-in-out infinite; }
`}</style>
<button
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
aria-label="Scroll to top"
className="lu-stt"
style={{
position: 'fixed', bottom: 28, right: 28, zIndex: 9999,
width: 60, height: 60, borderRadius: 16,
background: '#FFC72C', color: INK, border: `3px solid ${INK}`,
cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: `4px 4px 0 0 ${INK}`,
opacity: showScroll ? 1 : 0,
transform: showScroll ? 'translateY(0)' : 'translateY(16px)',
pointerEvents: showScroll ? 'auto' : 'none',
transition: 'opacity 0.25s ease, transform 0.15s ease, box-shadow 0.15s ease',
}}
>
<span className="lu-stt-arrow" style={{ display: 'flex' }}>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
</span>
</button>
</>
);
}