From aca42a3213f4a03a49725a96e61d4ffe1f589929 Mon Sep 17 00:00:00 2001 From: R-Bharathraj Date: Tue, 21 Jul 2026 17:48:28 +0530 Subject: [PATCH] Update website layout, styles, and story components --- src/app/globals.css | 43 +++- src/app/page.tsx | 6 +- src/components/layout/Footer.tsx | 130 ++++++++-- src/components/layout/Header.tsx | 131 +++++++--- src/components/story/Ico.tsx | 99 ++++++++ src/components/story/SectionsA.tsx | 384 ++++++++++++++++++++++------- src/components/story/SectionsB.tsx | 206 ++++++++-------- 7 files changed, 736 insertions(+), 263 deletions(-) create mode 100644 src/components/story/Ico.tsx diff --git a/src/app/globals.css b/src/app/globals.css index 8a25e30..2e572bf 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -99,6 +99,35 @@ } } +/* ───────────────────────────────────────────────────────────── + Animated icons (emoji replacements) — solid black glyphs with + no border/background, each looping an idle motion. CSS-driven + so the ~120 icons on a page stay cheap. + ───────────────────────────────────────────────────────────── */ +.ico { + display: inline-flex; + align-items: center; + vertical-align: -0.18em; + flex-shrink: 0; +} +.ico svg { display: block; } + +@keyframes icoBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.5px); } } +@keyframes icoSwing { 0%, 100% { transform: rotate(-10deg); } 50% { transform: rotate(10deg); } } +@keyframes icoSpin { to { transform: rotate(360deg); } } +@keyframes icoPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(0.84); } } +@keyframes icoRock { 0%, 100% { transform: rotate(-8deg); } 50% { transform: rotate(8deg); } } + +.ico-bob svg { animation: icoBob 1.15s ease-in-out infinite; } +.ico-swing svg { animation: icoSwing 1.7s ease-in-out infinite; transform-origin: 50% 10%; } +.ico-spin svg { animation: icoSpin 6s linear infinite; transform-origin: 50% 50%; } +.ico-pulse svg { animation: icoPulse 1.45s ease-in-out infinite; transform-origin: 50% 50%; } +.ico-rock svg { animation: icoRock 1.55s ease-in-out infinite; transform-origin: 50% 85%; } + +@media (prefers-reduced-motion: reduce) { + .ico svg { animation: none !important; } +} + /* Custom Scrollbar — neobrutalist, on-theme (no track border) */ ::-webkit-scrollbar { width: 14px; @@ -275,18 +304,16 @@ h1, h2, h3, h4, h5, h6 { border-radius: 4px !important; } -/* Mobile view media queries for The Vision and For You sections */ +/* Mobile view media queries for The Vision section */ @media (max-width: 640px) { - #the-vision, - #for-you { + #the-vision { padding-left: 1.25rem !important; padding-right: 1.25rem !important; padding-top: 4rem !important; padding-bottom: 4rem !important; } - #the-vision h2, - #for-you h2 { + #the-vision h2 { font-size: 2.5rem !important; line-height: 1.05 !important; letter-spacing: -0.02em !important; @@ -295,8 +322,7 @@ h1, h2, h3, h4, h5, h6 { } /* Optimize card spacing and shadow sizes for mobile neobrutalism style */ - #the-vision .nb, - #for-you .nb { + #the-vision .nb { padding: 1rem !important; font-size: 0.95rem !important; box-shadow: 4px 4px 0 0 #0a0a0a !important; @@ -304,8 +330,7 @@ h1, h2, h3, h4, h5, h6 { } /* Decrease vertical spacing inside grid for better layout flow on mobile */ - #the-vision .grid, - #for-you .grid { + #the-vision .grid { gap: 1.25rem !important; } } diff --git a/src/app/page.tsx b/src/app/page.tsx index 0190045..27745b1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -13,8 +13,6 @@ import { TheLoop, } from "@/components/story/SectionsA"; import { - ForYou, - ForBusiness, WhyNow, HowItWorks, OldVsNew, @@ -30,15 +28,13 @@ export default function Home() {
+ - - - diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 3a5c2cd..443bd72 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -4,6 +4,7 @@ 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'; @@ -13,18 +14,18 @@ const NAV = [ title: 'Earn', color: '#C8FF37', // lime links: [ - { label: 'Walk to Earn', href: '#for-you' }, - { label: 'Play & Win', href: '#for-you' }, - { label: 'Refer Friends', href: '#for-you' }, + { label: 'Walk to Earn', href: '#how-it-works' }, + { label: 'Play & Win', href: '#how-it-works' }, + { label: 'Refer Friends', href: '#how-it-works' }, ], }, { title: 'Platform', color: '#FF2E88', // pink links: [ - { label: 'For You', href: '#for-you' }, - { label: 'For Business', href: '#for-business' }, + { label: 'Why Now', href: '#why-now' }, { label: 'How It Works', href: '#how-it-works' }, + { label: 'The Loop', href: '#the-loop' }, ], }, { @@ -33,7 +34,7 @@ const NAV = [ links: [ { label: 'The Vision', href: '#the-vision' }, { label: 'The Why', href: '#the-why' }, - { label: 'Contact Us', href: '#for-business' }, + { label: 'Our Belief', href: '#our-belief' }, ], }, ]; @@ -79,10 +80,111 @@ export default function Footer() { syncLenisScrollTrigger(); const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; - // Infinite marquee ticker (two identical halves → seamless). if (!reduce) { + /* Continuous continuous animations */ gsap.to('.foot-ticker-track', { xPercent: -50, duration: 22, ease: 'none', repeat: -1 }); - gsap.to('.foot-cta-star', { rotate: 360, duration: 9, 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 }, @@ -120,8 +222,8 @@ export default function Footer() { - {/* ── BIG CTA STICKER CARD ── */} -
+ {/* ── BIG CTA STICKER CARD (3D Fold-Out Reveal) ── */} +
{/* rotating star sticker */} - +
- + Earn. Spend. Live More.

@@ -154,7 +256,7 @@ export default function Footer() {

- © {new Date().getFullYear()} LytsUp. Made with - for Gen Z. + for Gen Z.
{[['Terms', '#'], ['Privacy', '#'], ['Cookies', '#']].map(([l, h]) => ( diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index f4e987b..cd751ff 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -5,32 +5,36 @@ import Link from 'next/link'; import Image from 'next/image'; const INK = '#0a0a0a'; -const CREAM = '#FFFDF5'; const GOLD = '#FFC72C'; +const CREAM = '#FFFDF5'; + +/* Scroll distance (px) before the bar solidifies into the sticky sticker. */ +const STICK_AFTER = 500; const NAV_LINKS = [ - { label: 'Earn', href: '#for-you', color: '#C8FF37' }, // lime + { label: 'Earn', href: '#how-it-works', color: '#C8FF37' }, // lime { label: 'Spend', href: '#difference', color: '#FF2E88' }, // pink { label: 'Live', href: '#the-vision', color: '#8B3DFF' }, // purple - { label: 'For Business', href: '#for-business', color: GOLD }, + { label: 'Why Now', href: '#why-now', color: GOLD }, ]; -const MOBILE_QUERY = '(max-width: 1023px)'; - export default function Header() { const [mobileOpen, setMobileOpen] = useState(false); - const [scrolled, setScrolled] = useState(false); const [hidden, setHidden] = useState(false); + const [stuck, setStuck] = useState(false); const [activeHash, setActiveHash] = useState(''); const mobileMenuRef = useRef(null); const lastScrollYRef = useRef(0); - // Deepen the header shadow once scrolled; hide on scroll-down, reveal on up. + /* Sticky behaviour (same model as the loyaly header): + · above STICK_AFTER → `stuck = false`, invisible chrome over the hero + · past STICK_AFTER → `stuck = true`, solidifies into the cream sticker bar + · scroll down → hide, scroll up → reveal */ useEffect(() => { lastScrollYRef.current = window.scrollY; const onScroll = () => { const y = window.scrollY; - setScrolled(y > 50); + setStuck(y > STICK_AFTER); const lastY = lastScrollYRef.current; if (y <= 80) setHidden(false); else if (y > lastY + 4) setHidden(true); @@ -82,15 +86,6 @@ export default function Header() { return () => { document.body.style.overflow = ''; }; }, [mobileOpen]); - const [isMobileViewport, setIsMobileViewport] = useState(false); - useEffect(() => { - const mql = window.matchMedia(MOBILE_QUERY); - setIsMobileViewport(mql.matches); - const update = () => setIsMobileViewport(mql.matches); - mql.addEventListener('change', update); - return () => mql.removeEventListener('change', update); - }, []); - const isActive = (href: string) => href === activeHash; return ( @@ -194,7 +189,7 @@ export default function Header() {
setMobileOpen(false)} className="font-sora" style={{ @@ -219,22 +214,21 @@ export default function Header() { {/* ——— Header: cream neobrutalist bar, loyaly layout ——— */}
-
♻️ every interaction creates value. every LYTIAN invites another.
+
every interaction creates value. every LYTIAN invites another.
); } diff --git a/src/components/story/SectionsB.tsx b/src/components/story/SectionsB.tsx index 0a83c50..474c1d4 100644 --- a/src/components/story/SectionsB.tsx +++ b/src/components/story/SectionsB.tsx @@ -13,88 +13,7 @@ import { } from "./ui"; import { C } from "./palette"; import { gsap, useGSAP, syncLenisScrollTrigger, MaskWords } from "./gsapkit"; - -/* 10 — FOR YOU */ -export function ForYou() { - const cards = [ - ["🚶", "earn before spending", "value starts before purchase. discovery, visits, sharing.", C.pink, "#fff"], - ["🌅", "everyday life = value", "walking. exploring. visiting. sharing. everything matters.", C.yellow, "#0a0a0a"], - ["🌐", "one connected currency", "one LYTs balance across experiences and brands. no silos.", C.lime, "#0a0a0a"], - ["📢", "real recognition", "rewarded for contribution and engagement, not just transactions.", C.purple, "#fff"], - ] as const; - return ( -
-
-
- 🙋 for you -

- your life -
- is worth -
- something. -

-

every step. every visit. every selfie. it's all worth it here. 💗

-
-
- -
value begins before purchase. 🌱
-
- -
walk in. win more.
-
- 10k steps? earn. coffee with a friend?{" "} - earn. sharing a vibe?{" "} - earn. -
-
-
-
- - {cards.map(([e, t, s, bg, tc]) => ( - -
{e}
-
{t}
-
{s}
-
- ))} -
-
- ); -} - -/* 11 — FOR BUSINESS */ -export function ForBusiness() { - const cards = [ - ["1", "🤝 ACQUIRE", "CUSTOMERS EARLIER", "Engage them BEFORE the transaction.", C.pink, "#fff"], - ["2", "👟 FOOTFALL", "INCREASE VISITS", "Reward the showing up.", C.lime, "#0a0a0a"], - ["3", "📁 CAC", "SLASH ACQUISITION COSTS", "Organic loops replace paid ads.", C.yellow, "#0a0a0a"], - ["4", "🔁 KEEP", "STICKIER CUSTOMERS", "Continuous engagement = love.", C.orange, "#fff"], - ["5", "👥 TRIBE", "BUILD ADVOCATES", "Customers turn into LYTIANS.", C.purple, "#fff"], - ["6", "🧠 INSIGHT", "BETTER INTENT SIGNALS", "See them before the receipt.", "#fff", "#0a0a0a"], - ] as const; - return ( -
- 🤝 for businesses - - acquire earlier. -
- retain forever. -
-

meet people before they buy. keep them warm after. 💞

- - {cards.map(([n, tag, t, s, bg, tc]) => ( - - {n} -
{tag}
-
{t}
-
{s}
-
- ))} -
-
- ); -} +import { Ico, withIcons } from "./Ico"; /* 12 — WHY NOW */ export function WhyNow() { @@ -105,24 +24,95 @@ export function WhyNow() { ["🎮", "GAMIFIED LIFE", "Systems make it real.", "↑", C.orange, "#fff"], ["📉", "OLD LOYALTY FADING", "No real bond anymore.", "↓↓", C.pink, "#fff"], ] as const; + + useGSAP(() => { + syncLenisScrollTrigger(); + const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + if (reduce) return; + + const cardsEl = gsap.utils.toArray("#why-now .game-card"); + if (!cardsEl.length) return; + + /* 3D perspective wrapper */ + gsap.set("#why-now .game-card-grid", { perspective: 1200 }); + + /* Set cards in a fanned-out HAND at bottom center */ + cardsEl.forEach((card, i) => { + gsap.set(card, { + transformStyle: "preserve-3d", + transformOrigin: "50% 120%", + autoAlpha: 0, + xPercent: (2 - i) * 110, // Gather cards at bottom center hand position + y: 140, // Lowered down in hand + scale: 0.6, + rotationZ: (i - 2) * 12, // Fan out angle in hand (-24deg to +24deg) + rotationY: -45, // Slightly tilted in hand + boxShadow: "0 25px 50px rgba(0,0,0,0.6)", + }); + }); + + /* Card deal animation timeline on ScrollTrigger */ + const tl = gsap.timeline({ + scrollTrigger: { + trigger: "#why-now", + start: "top 72%", + once: true, + }, + }); + + /* Step 1: Hand appears fanned open */ + tl.to(cardsEl, { + autoAlpha: 1, + duration: 0.3, + stagger: 0.05, + ease: "power2.out", + }); + + /* Step 2: Deal cards from hand onto grid one by one */ + tl.to( + cardsEl, + { + xPercent: 0, + y: 0, + scale: 1, + rotationZ: 0, + rotationY: 0, + boxShadow: "6px 6px 0 0 #0a0a0a", + duration: 0.65, + stagger: 0.16, + ease: "back.out(1.6)", + onComplete: () => { + gsap.set(cardsEl, { clearProps: "transform,transformStyle,perspective" }); + }, + }, + "-=0.1" + ); + }); + return (
- ⏰ why this matters + why this matters 5 currents
are converging right now.
- +
{cards.map(([e, t, s, a, bg, tc]) => ( - -
{e}
-
{t}
-
{s}
-
{a}
-
+
+
+
+
{t}
+
{s}
+
+
{a}
+
))} - +
); } @@ -145,14 +135,14 @@ export function HowItWorks() { const bottom = cards.slice(5); return (
- ⚡ how it works + how it works

every action earns points{" "} LYTs.

no app-opening rituals. no complicated loyalty rules.{" "} - no waiting until you spend. just live your life, engage the world, and watch your LYTs grow. 🌱 + no waiting until you spend. just live your life, engage the world, and watch your LYTs grow.

{/* Two counter-scrolling tracks. The opposing directions read as a @@ -196,7 +186,7 @@ function ActionTile({ +LYTs -
{emoji}
+
{title}
{sub}
@@ -233,7 +223,7 @@ export function OldVsNew() { return (
- 💞 a new kind of loyalty + a new kind of loyalty

traditional loyalty said one thing.

@@ -241,25 +231,25 @@ export function OldVsNew() {

- 💻 TRADITIONAL LOYALTY + TRADITIONAL LOYALTY
the old rule spend first. earn later.
-
value is created only when you buy. participation = invisible. 🫥
+
value is created only when you buy. participation = invisible.
VS
- ✨ THE LYTS WAY + THE LYTS WAY
the new rule live first. earn everywhere.
-
value is created when you participate. everyday life is the unlock. 🎉
+
value is created when you participate. everyday life is the unlock.
@@ -274,7 +264,7 @@ export function OldVsNew() { /* 15 — THE VISION GSAP: the headline reveals word by word with "social" punching in on an overshoot, while the value list stacks up — each row slides in from the - right and its ✓ ticks in with a springy pop, like a checklist completing. */ + right and its ticks in with a springy pop, like a checklist completing. */ export function TheVision() { const items = [ ["every discovery has value", "#fff", "#0a0a0a"], @@ -308,7 +298,7 @@ export function TheVision() {
- 🌈 the vision + the vision

@@ -326,14 +316,14 @@ export function TheVision() { className="vis-row nb flex items-center gap-3 rounded-xl px-4 py-3 font-extrabold [will-change:transform]" style={{ background: bg, color: tc }} > - + {t}

))}
- every meaningful interaction has value. 🤝 + every meaningful interaction has value.
); @@ -344,7 +334,7 @@ export function OurBelief() { return (
- 🌱 our belief + our belief
{/* The thesis, lit word by word as it scrolls past. */} @@ -388,7 +378,7 @@ export function ThePromise() { return (
- ⚡ the promise + the promise

@@ -398,15 +388,15 @@ export function ThePromise() {

{chips.map(([t, bg, tc]) => ( - {t} + {withIcons(t, 15)} ))}
- 👉 Earn. Spend. Live More. — the rest is just coupons. + Earn. Spend. Live More. — the rest is just coupons. - join the LYTIANS 🤝 + join the LYTIANS