Update website layout, styles, and story components

This commit is contained in:
R-Bharathraj
2026-07-21 17:48:28 +05:30
parent e41094a6fd
commit aca42a3213
7 changed files with 736 additions and 263 deletions

View File

@@ -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;
}
}

View File

@@ -13,8 +13,6 @@ import {
TheLoop,
} from "@/components/story/SectionsA";
import {
ForYou,
ForBusiness,
WhyNow,
HowItWorks,
OldVsNew,
@@ -30,15 +28,13 @@ export default function Home() {
<Header />
<Intro />
<TheWhy />
<Difference />
<TheProblem />
<TheShift />
<MeetLyts />
<OneDay />
<Feelings />
<Difference />
<TheLoop />
<ForYou />
<ForBusiness />
<WhyNow />
<HowItWorks />
<OldVsNew />

View File

@@ -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() {
</div>
</div>
{/* ── BIG CTA STICKER CARD ── */}
<div className="lu-cta-wrap" style={{ padding: '56px 48px 40px' }}>
{/* ── 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,
@@ -136,13 +238,13 @@ export default function Footer() {
}}>
{/* rotating star sticker */}
<span className="foot-cta-star" aria-hidden style={{ position: 'absolute', top: -20, right: 30, willChange: 'transform' }}>
<span style={{ display: 'inline-flex', background: '#FF2E88', border: `3px solid ${INK}`, borderRadius: '50%', padding: 10, boxShadow: `3px 3px 0 0 ${INK}` }}>
<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 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}` }}>
<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 }}>
@@ -154,7 +256,7 @@ export default function Footer() {
</h2>
</div>
<Link href="#for-business" className="lu-cta-btn" style={{
<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',
@@ -231,7 +333,7 @@ export default function Footer() {
}}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontWeight: 600 }}>
© {new Date().getFullYear()} LytsUp. Made with
<span style={{ color: '#FF2E88' }}></span> for Gen Z.
<Ico e="♥" size={14} /> for Gen Z.
</span>
<div style={{ display: 'flex', gap: 20 }}>
{[['Terms', '#'], ['Privacy', '#'], ['Cookies', '#']].map(([l, h]) => (

View File

@@ -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<HTMLDivElement>(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() {
</div>
<Link
href="#for-business"
href="#how-it-works"
onClick={() => setMobileOpen(false)}
className="font-sora"
style={{
@@ -219,22 +214,21 @@ export default function Header() {
{/* ——— Header: cream neobrutalist bar, loyaly layout ——— */}
<header
className={`header-bar${stuck ? ' is-stuck' : ''}`}
style={{
position: 'fixed',
top: isMobileViewport ? '16px' : 0,
left: '20px',
right: '20px',
width: 'auto',
zIndex: 99999999,
background: CREAM,
border: `3px solid ${INK}`,
borderTopLeftRadius: isMobileViewport ? '22px' : '0px',
borderTopRightRadius: isMobileViewport ? '22px' : '0px',
borderBottomLeftRadius: '22px',
borderBottomRightRadius: '22px',
boxShadow: scrolled ? `0 9px 0 0 ${INK}` : `0 6px 0 0 ${INK}`,
/* ── two-state sticky skin ──
top of page → transparent chrome · scrolled → solid cream sticker.
The border stays present but transparent so nothing shifts on flip.
Geometry (top/left/right/radius) lives in the media queries below. */
background: stuck ? CREAM : 'transparent',
border: `3px solid ${stuck ? INK : 'transparent'}`,
boxShadow: stuck ? `0 6px 0 0 ${INK}` : 'none',
transform: hidden ? 'translateY(-160%)' : 'translateY(0)',
transition: 'transform 0.35s cubic-bezier(0.4,0,0.2,1), box-shadow 0.25s ease',
transition:
'transform 0.35s cubic-bezier(0.4,0,0.2,1), background 0.28s ease, border-color 0.28s ease, box-shadow 0.28s ease',
}}
>
<div className="header-inner" style={{
@@ -249,14 +243,30 @@ export default function Header() {
}}>
{/* Logo */}
<div style={{ flexShrink: 0, flex: 1, minWidth: 120, display: 'flex', alignItems: 'center' }}>
<Link href="#intro" aria-label="LytsUp home" style={{ display: 'inline-flex', alignItems: 'center', textDecoration: 'none' }}>
{/* Light sticker chip behind the mark — the "lyts" wordmark is dark
navy, so it vanishes on the dark hero without a light backing. */}
<Link
href="#intro"
aria-label="LytsUp home"
className="header-logo-chip"
style={{
display: 'inline-flex',
alignItems: 'center',
textDecoration: 'none',
background: CREAM,
border: `3px solid ${INK}`,
borderRadius: 14,
padding: '8px 14px',
boxShadow: `3px 3px 0 0 ${INK}`,
}}
>
<Image
src="/images/lytsup_app.png"
alt="LytsUp"
width={2048}
height={554}
className="header-logo-img"
style={{ objectFit: 'contain', height: 40, width: 'auto' }}
style={{ objectFit: 'contain', height: 34, width: 'auto' }}
priority
/>
</Link>
@@ -323,7 +333,7 @@ export default function Header() {
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', flex: 1, justifyContent: 'flex-end' }}>
{/* Download Now — chunky gold sticker button */}
<Link
href="#for-business"
href="#how-it-works"
className="header-cta desktop-nav font-sora"
style={{
display: 'inline-flex',
@@ -368,11 +378,60 @@ export default function Header() {
@media (max-width: 1023px) {
.desktop-nav { display: none !important; }
.menu-trigger { display: flex !important; }
.header-inner { height: 62px !important; }
.header-logo-img { height: 28px !important; }
.header-inner { height: 58px !important; padding: 0 12px !important; }
.header-logo-img { height: 26px !important; }
}
/* ─────────────────────────────────────────────────────────────
FLOATING CARD MOBILE HEADER & STICKY GEOMETRY
───────────────────────────────────────────────────────────── */
.header-bar {
top: 20px;
left: 20px;
right: 20px;
border-radius: 22px;
}
/* Once it sticks on desktop, drop the top margin so it sits flush */
@media (min-width: 769px) {
.header-bar.is-stuck {
top: 0;
border-top: none !important;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
/* ── Mobile Floating Card Header (Media Query) ── */
@media (max-width: 768px) {
.header-bar {
top: 12px !important;
left: 12px !important;
right: 12px !important;
border-radius: 20px !important;
background: ${CREAM} !important;
border: 3px solid ${INK} !important;
box-shadow: 4px 4px 0 0 ${INK} !important;
}
.header-bar.is-stuck {
top: 10px !important;
border-top: 3px solid ${INK} !important;
border-top-left-radius: 20px !important;
border-top-right-radius: 20px !important;
box-shadow: 5px 5px 0 0 ${INK} !important;
}
.header-logo-chip {
padding: 5px 10px !important;
border-radius: 12px !important;
box-shadow: 2px 2px 0 0 ${INK} !important;
}
.header-logo-img {
height: 24px !important;
}
.menu-trigger {
width: 38px !important;
height: 38px !important;
border-radius: 10px !important;
}
@media (max-width: 600px) {
header { left: 10px !important; right: 10px !important; top: 10px !important; }
}
@media (min-width: 1024px) and (max-width: 1280px) {
.header-nav-row { gap: 7px !important; }

View File

@@ -0,0 +1,99 @@
"use client";
/* Site-wide emoji → animated icon replacement.
Every emoji used across the story sections maps to a solid black Lucide
glyph with no border and no background, plus a looping idle motion that
suits its meaning. Motion is pure CSS (see `globals.css`) so ~100 icons on
a page cost nothing, and it's disabled under prefers-reduced-motion.
Two entry points:
<Ico e="🚶" /> — drop an icon into JSX where an emoji used to sit
{withIcons(str)} — render a string that has emoji baked into it
(used for copy that lives inside const arrays) */
import type { ReactNode } from "react";
import {
Heart, Flame, Gift, Mail, Clapperboard, Users, Sparkles, Zap, X, Check,
CircleAlert, Tv, ShoppingCart, CreditCard, Smartphone, Gamepad2, Footprints,
Coffee, MapPin, Store, Sprout, Trophy, TreePalm, MessageCircle, Laptop,
Rocket, Recycle, Sunrise, Globe, Megaphone, Hand, Handshake, Folder, Repeat,
Brain, TrendingDown, Camera, FerrisWheel, Ticket, ShoppingBag, PartyPopper,
Tag, EyeOff, Rainbow, Wallet, Fingerprint, Star, Clock,
type LucideIcon,
} from "lucide-react";
/* motion kinds — matched to CSS keyframes in globals.css */
type Motion = "bob" | "swing" | "spin" | "pulse" | "rock";
const MAP: Record<string, [LucideIcon, Motion]> = {
// hearts / feelings
"♡": [Heart, "pulse"], "♥": [Heart, "pulse"], "💗": [Heart, "pulse"],
"💞": [Heart, "pulse"], "🤍": [Heart, "pulse"], "💚": [Heart, "pulse"],
"🫂": [Users, "rock"], "🤲": [Hand, "rock"],
// sparkle / energy
"✨": [Sparkles, "pulse"], "✦": [Sparkles, "pulse"], "⚡": [Zap, "pulse"],
"🔥": [Flame, "bob"], "🌈": [Rainbow, "pulse"], "🚀": [Rocket, "bob"],
// marks
"✗": [X, "pulse"], "✓": [Check, "pulse"], "❗": [CircleAlert, "pulse"],
// people / social
"👯": [Users, "rock"], "👥": [Users, "rock"], "🙋": [Hand, "swing"],
"🤝": [Handshake, "rock"], "💬": [MessageCircle, "bob"], "📢": [Megaphone, "swing"],
"🤳": [Camera, "pulse"], "🪞": [Fingerprint, "pulse"], "🧠": [Brain, "pulse"],
// movement / places
"🚶": [Footprints, "bob"], "👟": [Footprints, "bob"], "📍": [MapPin, "bob"],
"🏪": [Store, "bob"], "🌴": [TreePalm, "swing"], "🌱": [Sprout, "bob"],
"🌅": [Sunrise, "bob"], "🌐": [Globe, "spin"], "☕": [Coffee, "bob"],
// commerce
"🛒": [ShoppingCart, "bob"], "🛍️": [ShoppingBag, "swing"], "🛍": [ShoppingBag, "swing"],
"💳": [CreditCard, "bob"], "💰": [Wallet, "bob"], "🏷️": [Tag, "swing"], "🏷": [Tag, "swing"],
"🎁": [Gift, "bob"], "📉": [TrendingDown, "bob"],
// media / tech
"📺": [Tv, "pulse"], "🎬": [Clapperboard, "rock"], "📱": [Smartphone, "bob"],
"💻": [Laptop, "bob"], "🎮": [Gamepad2, "rock"], "💌": [Mail, "bob"],
"📁": [Folder, "bob"], "🔁": [Repeat, "spin"], "♻️": [Recycle, "spin"], "♻": [Recycle, "spin"],
// fun / rewards
"🏆": [Trophy, "pulse"], "🎉": [PartyPopper, "rock"], "🎡": [FerrisWheel, "spin"],
"🎟️": [Ticket, "swing"], "🎟": [Ticket, "swing"], "🫥": [EyeOff, "pulse"],
"⏰": [Clock, "rock"], "⏳": [Clock, "rock"], "⭐": [Star, "pulse"], "🌟": [Star, "pulse"],
};
/** Matches any emoji we know how to swap (longest keys first for ZWJ/VS16). */
const EMOJI_RE = new RegExp(
`(${Object.keys(MAP)
.sort((a, b) => b.length - a.length)
.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
.join("|")})`,
"gu",
);
export function Ico({
e,
size = 16,
className = "",
}: {
e: string;
size?: number;
className?: string;
}) {
const hit = MAP[e] ?? MAP[e.replace(//g, "")];
if (!hit) return null;
const [Glyph, motion] = hit;
return (
<span aria-hidden className={`ico ico-${motion} ${className}`}>
<Glyph size={size} strokeWidth={2.6} color="#0a0a0a" />
</span>
);
}
/** Swap every known emoji inside a plain string for an animated icon. */
export function withIcons(text: string, size = 16): ReactNode {
const parts = text.split(EMOJI_RE).filter((p) => p !== "");
return parts.map((p, i) =>
MAP[p] || MAP[p.replace(//g, "")] ? (
<Ico key={i} e={p} size={size} />
) : (
<span key={i}>{p}</span>
),
);
}

View File

@@ -1,6 +1,7 @@
"use client";
import { useRef } from "react";
import { motion } from "framer-motion";
import {
Section,
Pill,
@@ -20,6 +21,7 @@ import {
usePointerParallax,
MaskWords,
} from "./gsapkit";
import { Ico, withIcons } from "./Ico";
/* 01 — INTRO / HERO
GSAP: masked line-reveal on load · scroll-scrubbed depth parallax on the
@@ -150,7 +152,7 @@ export function Intro() {
<div className="mt-9 flex flex-wrap items-center gap-4">
<Magnetic className="hero-cta">
<a
href="#for-you"
href="#how-it-works"
className="nb nb-hover inline-flex items-center gap-2 rounded-full bg-[#FFC72C] px-7 py-3.5 font-sora text-base font-extrabold uppercase tracking-wide text-[#0a0a0a]"
>
get started
@@ -185,7 +187,7 @@ export function Intro() {
<div className="text-xs font-bold uppercase tracking-widest text-[#FF2E88]">psst</div>
make everyday
<br />
count. <span className="text-[#FF2E88]"></span>
count. <span className="text-[#FF2E88]"><Ico e="♡" /></span>
</div>
</div>
</div>
@@ -198,12 +200,12 @@ export function Intro() {
</div>
<div className="hero-object absolute left-10 top-1/2" data-depth="0.12" data-speed="1.0">
<span className="animate-float nb-sm inline-block rotate-3 rounded-xl bg-white px-4 py-2 font-sora text-lg font-extrabold text-[#0a0a0a]" style={{ animationDelay: "0.8s" }}>
🔥 7-day streak
<Ico e="🔥" /> 7-day streak
</span>
</div>
<div className="hero-object absolute bottom-8 right-16" data-depth="0.2" data-speed="1.7">
<span className="animate-float nb-sm inline-block -rotate-3 rounded-xl bg-[#C8FF37] px-4 py-2 font-sora text-lg font-extrabold text-[#0a0a0a]" style={{ animationDelay: "1.6s" }}>
🎁 reward unlocked
<Ico e="🎁" /> reward unlocked
</span>
</div>
@@ -235,7 +237,7 @@ export function TheWhy() {
return (
<Section id="the-why" variant="cream" size="punch" width="narrow">
<div className="flex flex-col gap-8">
<Pill bg="#0a0a0a" text="#FFD21E">💌 hey. quick story before we dive in</Pill>
<Pill bg="#0a0a0a" text="#FFD21E"><Ico e="💌" /> hey. quick story before we dive in</Pill>
<div className="grid gap-8 lg:grid-cols-[1.4fr_1fr]">
<div>
<Card bg="#0a0a0a" text="#fff" className="p-6">
@@ -257,20 +259,20 @@ export function TheWhy() {
<span></span>
<Chip bg={C.lime}>REDEEM</Chip>
<span></span>
<Chip bg="#0a0a0a" text="#fff"> POINTS EXPIRE</Chip>
<Chip bg="#0a0a0a" text="#fff"><Ico e="⏳" /> POINTS EXPIRE</Chip>
</div>
</div>
<div className="flex flex-col gap-4">
<Card bg="#fff" className="-rotate-1 p-5">
<div className="font-sora font-extrabold">🎬 DISCOVER ON SOCIAL MEDIA</div>
<div className="font-sora font-extrabold"><Ico e="🎬" /> DISCOVER ON SOCIAL MEDIA</div>
<div className="text-sm font-semibold text-black/70">brand relationships start before purchase</div>
</Card>
<Card bg={C.pink} text="#fff" className="rotate-1 p-5">
<div className="font-sora font-extrabold">👯 COMMUNITIES DECIDE</div>
<div className="font-sora font-extrabold"><Ico e="👯" /> COMMUNITIES DECIDE</div>
<div className="text-sm font-semibold text-white/80">influence &gt; paid advertising</div>
</Card>
<Card bg={C.lime} className="-rotate-1 p-5">
<div className="font-sora font-extrabold"> REWARD PARTICIPATION</div>
<div className="font-sora font-extrabold"><Ico e="✨" /> REWARD PARTICIPATION</div>
<div className="text-sm font-semibold text-black/70">recognition, not coupons</div>
</Card>
</div>
@@ -281,40 +283,60 @@ export function TheWhy() {
}
/* 03 — THE PROBLEM
GSAP: the three cards deal in like cards flipped onto a table — a 3D
rotationY/lift entrance scrubbed to scroll, with each number badge
popping as its card lands. */
GSAP: 3D Horizontal Flip Card Reveal */
export function TheProblem() {
useGSAP(() => {
syncLenisScrollTrigger();
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduce) return;
/* Play-once on enter: the cards deal in with a gentle 3D lift, then settle
perfectly flat and aligned (transforms cleared) so nothing lingers
skewed or overlaps the note below. */
/* Set 3D perspective container and initial 180-degree horizontal flip state */
gsap.set("#the-problem .prob-card-grid", { perspective: 1400 });
gsap.set("#the-problem .prob-card", {
transformStyle: "preserve-3d",
transformOrigin: "50% 50%",
rotationY: -180,
y: 45,
scale: 0.85,
autoAlpha: 0,
boxShadow: "0 30px 60px rgba(0,0,0,0.5)",
});
gsap.set("#the-problem .prob-num", {
scale: 0,
rotation: -30,
transformOrigin: "50% 50%",
});
const tl = gsap.timeline({
scrollTrigger: { trigger: "#the-problem", start: "top 72%", once: true },
defaults: { ease: "power3.out" },
onComplete: () => gsap.set("#the-problem .prob-card", { clearProps: "transform" }),
defaults: { ease: "back.out(1.5)" },
onComplete: () => gsap.set("#the-problem .prob-card", { clearProps: "transform,transformStyle" }),
});
tl.from("#the-problem .prob-card", {
y: 60,
rotationY: -18,
rotationX: 8,
opacity: 0,
transformOrigin: "50% 100%",
stagger: 0.14,
duration: 0.9,
/* Horizontal 3D flip animation sequence */
tl.to("#the-problem .prob-card", {
rotationY: 0,
y: 0,
scale: 1,
autoAlpha: 1,
boxShadow: "6px 6px 0 0 #0a0a0a",
duration: 0.85,
stagger: 0.2,
ease: "back.out(1.5)",
})
.from("#the-problem .prob-num", {
scale: 0,
rotation: -20,
transformOrigin: "50% 50%",
stagger: 0.14,
duration: 0.5,
.to(
"#the-problem .prob-num",
{
scale: 1,
rotation: 0,
duration: 0.45,
stagger: 0.2,
ease: "back.out(2.2)",
}, 0.2);
},
"-=0.7"
);
}, []);
const cards = [
@@ -324,24 +346,24 @@ export function TheProblem() {
];
return (
<Section id="the-problem" variant="night" size="full" width="wide">
<Pill> the problem</Pill>
<Pill><Ico e="⚡" /> the problem</Pill>
<Heading className="mt-6">
traditional loyalty is <span className="strike-pink">broken.</span>
<br />
<span className="text-[#FFD21E]">here&apos;s why.</span>
</Heading>
<div className="mt-10 grid gap-5 [perspective:1200px] md:grid-cols-3">
<div className="prob-card-grid mt-10 grid gap-5 [perspective:1400px] md:grid-cols-3">
{cards.map((c) => (
<div
key={c.n}
className="prob-card nb nb-hover flex flex-col rounded-2xl p-6"
className="prob-card nb nb-hover flex flex-col rounded-2xl p-6 cursor-pointer"
style={{ background: c.bg, color: c.tc }}
>
<div className="prob-num mb-2 w-fit rounded-md bg-[#0a0a0a] px-2 py-0.5 text-xs font-extrabold text-white">{c.n}</div>
<div className="font-sora text-2xl font-extrabold">{c.t}</div>
<ul className="mt-3 flex-1 space-y-1.5 text-sm font-bold">
{c.items.map((i) => (
<li key={i}> {i}</li>
<li key={i}><Ico e="✗" /> {i}</li>
))}
</ul>
<div className="mt-4 rounded-lg bg-[#0a0a0a] py-2 text-center text-sm font-extrabold text-[#FFD21E]">{c.foot}</div>
@@ -349,7 +371,7 @@ export function TheProblem() {
))}
</div>
<div className="mt-6">
<Chip bg={C.yellow}> customers create value before spending. then traditional loyalty assigns zero credit.</Chip>
<Chip bg={C.yellow}><Ico e="❗" /> customers create value before spending. then traditional loyalty assigns zero credit.</Chip>
</div>
</Section>
);
@@ -360,30 +382,30 @@ export function TheShift() {
return (
<Section id="the-shift" variant="plain" className="!px-0">
<div className="mx-auto max-w-6xl px-6 sm:px-10 lg:px-20">
<Pill bg="#0a0a0a" text="#FFD21E"> the shift</Pill>
<Pill bg="#0a0a0a" text="#FFD21E"><Ico e="⚡" /> the shift</Pill>
<Heading className="mt-6">
from <span className="strike-pink">loyalty</span> to <span className="text-[#FF2E88]">engagement.</span>
</Heading>
<div className="mt-10 grid gap-8 md:grid-cols-2">
<div>
<div className="font-sora text-2xl font-extrabold">📺 old world</div>
<div className="font-sora text-2xl font-extrabold"><Ico e="📺" /> old world</div>
<div className="text-sm font-semibold text-white/50">rewards spending only</div>
<div className="mt-5 space-y-3">
{[["🛒 OFFLINE COMMERCE", "still rewards only purchases"], ["📺 TV ADS · BANNERS", "one-way broadcast · no loop"], ["💳 SPEND-BASED POINTS", "transaction, no participation"]].map(([t, s]) => (
<Card key={t} bg="#fff" className="p-4">
<div className="font-sora font-extrabold">{t}</div>
<div className="font-sora font-extrabold"><span className="inline-flex items-center gap-2">{withIcons(t, 16)}</span></div>
<div className="text-sm font-semibold text-black/60">{s}</div>
</Card>
))}
</div>
</div>
<div>
<div className="font-sora text-2xl font-extrabold"> new world</div>
<div className="font-sora text-2xl font-extrabold"><Ico e="✨" /> new world</div>
<div className="text-sm font-semibold text-[#C8FF37]">rewards participation</div>
<div className="mt-5 space-y-3">
{[["📱 SOCIAL MEDIA", "rewards engagement + time", C.purple], ["🎮 GAMING", "rewards achievements + grind", C.pink], ["🎬 CREATOR PLATFORMS", "rewards contribution + community", C.orange]].map(([t, s, bg]) => (
<Card key={t as string} bg={bg as string} text="#fff" className="p-4">
<div className="font-sora font-extrabold">{t}</div>
<div className="font-sora font-extrabold"><span className="inline-flex items-center gap-2">{withIcons(t, 16)}</span></div>
<div className="text-sm font-semibold text-white/80">{s}</div>
</Card>
))}
@@ -391,13 +413,37 @@ export function TheShift() {
</div>
</div>
<div className="mt-8">
<Chip bg={C.yellow}>🔥 biggest miss in consumer tech right now: offline commerce still rewards only spending. that changes with LYTs.</Chip>
<Chip bg={C.yellow}><Ico e="🔥" /> biggest miss in consumer tech right now: offline commerce still rewards only spending. that changes with LYTs.</Chip>
</div>
</div>
</Section>
);
}
/* Solid "slab" icons — bare black glyphs sitting inline with the chip label.
Each one loops a motion matching its meaning (see MeetLyts' GSAP hook). */
const SLAB_PATHS: Record<string, string> = {
walk: "M13.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zM9.8 8.9 7 23h2.1l1.8-8 2.1 2v6h2v-7.5l-2.1-2 .6-3C14.8 12 16.8 13 19 13v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1L6 8.3V13h2V9.6l1.8-.7z",
visit: "M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z",
friend: "M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z",
shop: "M18 6h-2c0-2.21-1.79-4-4-4S8 3.79 8 6H6c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-6-2c1.1 0 2 .9 2 2h-4c0-1.1.9-2 2-2z",
event: "M12 .6l3.1 7.5 8.1.7-6.1 5.3 1.8 7.9L12 17.8 5.1 22l1.8-7.9L.8 8.8l8.1-.7z",
};
function SlabIcon({ name }: { name: keyof typeof SLAB_PATHS | string }) {
return (
<span
aria-hidden
data-icon={name}
className="slab-icon inline-flex shrink-0 items-center justify-center"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="#0a0a0a" style={{ willChange: "transform" }}>
<path d={SLAB_PATHS[name]} />
</svg>
</span>
);
}
/* 05 — MEET LYTs
GSAP: the wordmark "LYTs." lands letter-by-letter in 3D as the section
enters, then the value flow pops in beat by beat. The giant word also
@@ -433,12 +479,27 @@ export function MeetLyts() {
ease: "none",
scrollTrigger: { trigger: "#lyts", start: "top bottom", end: "bottom top", scrub: 0.6 },
});
/* ── living slab icons — each glyph loops a motion that matches its meaning ── */
const glyph = (name: string) => `#lyts .slab-icon[data-icon="${name}"] svg`;
// walk → steps up and down
gsap.to(glyph("walk"), { y: -2.5, duration: 0.42, yoyo: true, repeat: -1, ease: "sine.inOut" });
// visit → the pin drops and settles
gsap.to(glyph("visit"), { y: -3.5, duration: 0.75, yoyo: true, repeat: -1, ease: "power2.inOut" });
// friend → the pair rocks side to side
gsap.to(glyph("friend"), { rotation: 9, transformOrigin: "50% 80%", duration: 0.8, yoyo: true, repeat: -1, ease: "sine.inOut" });
// shop → the bag swings from its handle
gsap.to(glyph("shop"), { rotation: -11, transformOrigin: "50% 0%", duration: 0.9, yoyo: true, repeat: -1, ease: "sine.inOut" });
// event → the star spins and twinkles
gsap.to(glyph("event"), { rotation: 360, transformOrigin: "50% 50%", duration: 6, ease: "none", repeat: -1 });
gsap.to(glyph("event"), { scale: 0.78, duration: 0.7, yoyo: true, repeat: -1, ease: "sine.inOut" });
}, []);
return (
<Section id="lyts" variant="deep" size="normal" width="narrow">
<span className="lyts-pill inline-block">
<Pill bg={C.yellow} text="#0a0a0a"> introducing</Pill>
<Pill bg={C.yellow} text="#0a0a0a"><Ico e="✦" /> introducing</Pill>
</span>
<h2 className="lyts-word mt-6 flex font-sora text-[24vw] font-extrabold leading-none tracking-tight text-white [perspective:1000px] sm:text-9xl">
{chars.map((ch, i) => (
@@ -456,11 +517,11 @@ export function MeetLyts() {
</p>
</div>
<div className="mt-12 flex flex-wrap items-center gap-2">
<span className="lyts-chip inline-block"><Chip>walk 🚶</Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.yellow}>visit 📍</Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.lime}>friend 🤝</Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.orange} text="#fff">shop 🛍</Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.purple} text="#fff">event 🎉</Chip></span>
<span className="lyts-chip inline-block"><Chip>walk <SlabIcon name="walk" /></Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.yellow}>visit <SlabIcon name="visit" /></Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.lime}>friend <SlabIcon name="friend" /></Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.orange} text="#fff">shop <SlabIcon name="shop" /></Chip></span>
<span className="lyts-chip inline-block"><Chip bg={C.purple} text="#fff">event <SlabIcon name="event" /></Chip></span>
<span className="lyts-chip"></span>
<span className="lyts-chip inline-block"><Chip bg="#0a0a0a" text={C.lime}>your LYTs balance</Chip></span>
<span className="lyts-chip"></span>
@@ -481,41 +542,155 @@ export function OneDay() {
["🏪", "supported", "a local business.", C.orange],
["🌱", "showed up", "for life.", C.purple],
] as const;
useGSAP(() => {
syncLenisScrollTrigger();
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduce) return;
const lineBeam = document.querySelector("#one-day .line-beam");
const lineOrb = document.querySelector("#one-day .line-orb");
const iconBadges = gsap.utils.toArray<HTMLElement>("#one-day .icon-badge");
const iconTexts = gsap.utils.toArray<HTMLElement>("#one-day .icon-text");
const cardResult = document.querySelector("#one-day .card-result");
if (!lineBeam || !lineOrb) return;
/* Hide / dim elements initially so they reveal as line touches them */
gsap.set(lineBeam, { width: "0%", opacity: 0 });
gsap.set(lineOrb, { left: "10%", opacity: 0, scale: 0.5 });
iconBadges.forEach((badge) => {
gsap.set(badge, { opacity: 0.2, scale: 0.82, filter: "brightness(0.4) blur(1px)", boxShadow: "0 0 0px transparent" });
});
iconTexts.forEach((text) => {
gsap.set(text, { opacity: 0.15, y: 12 });
});
if (cardResult) {
gsap.set(cardResult, { opacity: 0, y: 30, scale: 0.9 });
}
/* Pinned ScrollTrigger timeline: pins section until line reaches last round icon */
const tl = gsap.timeline({
scrollTrigger: {
trigger: "#one-day",
start: "top top",
end: "+=130%",
pin: true,
scrub: 0.8,
anticipatePin: 1,
},
});
const glowColors = ["#C8FF37", "#FFD21E", "#FF2E88", "#FF6B2C", "#9D4EDD"];
const positions = ["10%", "30%", "50%", "70%", "90%"];
/* Initial orb reveal */
tl.to(lineOrb, { opacity: 1, scale: 1.3, duration: 0.2 });
/* Draw line & reveal each round icon when line touches it */
positions.forEach((pos, i) => {
const targetWidth = `${(i / 4) * 80}%`;
const label = `step-${i}`;
tl.to(lineOrb, { left: pos, ease: "none", duration: 0.45 }, label);
tl.to(lineBeam, { width: targetWidth, opacity: 1, ease: "none", duration: 0.45 }, label);
tl.to(
iconBadges[i],
{
opacity: 1,
scale: 1.15,
filter: "brightness(1.15) blur(0px)",
boxShadow: `0 0 35px ${glowColors[i]}, 6px 6px 0 0 #0a0a0a`,
duration: 0.25,
},
`${label}+=0.1`
);
tl.to(
iconTexts[i],
{
opacity: 1,
y: 0,
duration: 0.25,
},
`${label}+=0.1`
);
});
/* Final reward card reveal after line reaches last round icon */
if (cardResult) {
tl.to(cardResult, {
opacity: 1,
y: 0,
scale: 1,
duration: 0.4,
ease: "back.out(1.5)",
});
}
});
return (
<Section id="one-day" variant="deep" size="normal">
<Pill bg={C.lime} text="#0a0a0a">💚 one ordinary day</Pill>
<Section id="one-day" variant="deep" size="full" className="min-h-screen flex flex-col justify-center py-12">
<Pill bg={C.lime} text="#0a0a0a"><Ico e="💚" /> one ordinary day</Pill>
<Heading className="mt-6">
here is what one ordinary day with <span className="text-[#C8FF37]">LYTs</span> looks like.
</Heading>
<div className="mt-8 max-w-3xl space-y-3">
<div className="relative mt-14 max-w-5xl w-full">
{/* Connecting Track Line & Ultra-Bright Glow Beam */}
<div className="absolute top-6 sm:top-10 md:top-12 left-0 right-0 -translate-y-1/2 z-0 pointer-events-none">
{/* Base Dark Track */}
<div className="absolute left-[10%] right-[10%] top-1/2 -translate-y-1/2 h-3 bg-[#0a0a0a] rounded-full border border-white/20 shadow-inner" />
{/* Super-Bright Neon Beam */}
<div className="line-beam absolute top-1/2 -translate-y-1/2 left-[10%] h-3 rounded-full bg-gradient-to-r from-[#C8FF37] via-[#FF2E88] to-[#FFD21E] shadow-[0_0_30px_#C8FF37,0_0_15px_#FF2E88]" />
{/* High-Intensity Energy Orb */}
<div className="line-orb absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-7 h-7 rounded-full bg-[#C8FF37] border-3 border-[#0a0a0a] shadow-[0_0_30px_#C8FF37,0_0_15px_#ffffff] z-20 flex items-center justify-center">
<div className="w-2 h-2 rounded-full bg-[#0a0a0a]" />
</div>
</div>
{/* 5 Icons Row */}
<div className="relative z-10 grid grid-cols-5 gap-2 sm:gap-6 md:gap-8">
{rows.map(([e, a, b, c], i) => (
<Reveal key={a} delay={i * 0.08} y={18}>
<div className="nb flex items-center gap-3 rounded-xl px-5 py-3 font-bold text-[#0a0a0a]" style={{ background: c }}>
<span className="text-xl">{e}</span>
<div key={a} className="flex flex-col items-center text-center group">
<div
className="icon-badge nb nb-hover relative z-10 flex h-12 w-12 sm:h-20 sm:w-20 md:h-24 md:w-24 items-center justify-center rounded-full shrink-0 text-[#0a0a0a] transition-all duration-300"
style={{ background: c }}
>
<Ico e={e} size={28} />
</div>
<div className="icon-text mt-3.5 flex flex-col items-center text-center font-sora text-xs sm:text-sm md:text-base text-white leading-tight">
<span>{a}</span>
<span className="font-extrabold">{b}</span>
</div>
</Reveal>
</div>
))}
</div>
<Card bg="#fff" className="mt-6 w-fit p-5">
</div>
<Card bg="#fff" className="card-result mt-12 w-fit p-5">
<div className="font-sora text-xl font-extrabold">you earned LYTs</div>
<div className="mt-1 w-fit bg-[#FFD21E] px-2 font-sora text-xl font-extrabold">because your life has value. 🤍</div>
<div className="mt-1 w-fit bg-[#FFD21E] px-2 font-sora text-xl font-extrabold">because your life has value. <Ico e="🤍" /></div>
</Card>
</Section>
);
}
/* 07 — FEELINGS
GSAP: the headline reveals word-by-word out of masks, then the five cards
wipe up like curtains (clip-path) coming from blur into sharp focus. */
GSAP: card game dealer reveal from center stack one by one */
export function Feelings() {
const cards = [
["🏆", "Recognition", "be seen, be credited", C.pink, "#fff"],
["🌴", "Experiences", "real life, not receipts", C.yellow, "#0a0a0a"],
["🫂", "Belonging", "community = currency", C.lime, "#0a0a0a"],
["🪞", "Identity", "who you are matters", C.orange, "#fff"],
["💬", "Community", "tribes win", "#0a0a0a", C.lime],
["💬", "Community", "tribes win", C.purple, "#fff"],
] as const;
useGSAP(() => {
@@ -523,11 +698,31 @@ export function Feelings() {
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduce) return;
/* hide immediately (before paint) so nothing flashes before its trigger */
/* Hide headline & sum box */
gsap.set("#feelings .feel-w-in", { yPercent: 118, rotationZ: 4, autoAlpha: 0 });
gsap.set("#feelings .feel-card", { clipPath: "inset(100% 0% 0% 0%)", yPercent: 14, scale: 0.92, autoAlpha: 0, filter: "blur(12px)" });
gsap.set("#feelings .feel-sum", { y: 34, autoAlpha: 0 });
/* Set 3D perspective for card game dealer grid */
gsap.set("#feelings .feel-grid", { perspective: 1200 });
const cardEls = gsap.utils.toArray<HTMLElement>("#feelings .feel-card");
/* Stack all cards at the CENTER of the grid before dealer deals them out */
cardEls.forEach((card, i) => {
gsap.set(card, {
transformStyle: "preserve-3d",
transformOrigin: "50% 50%",
autoAlpha: 0,
xPercent: (2 - i) * 110, // Shifts all cards to center column (index 2)
y: -100,
scale: 0.5,
rotationY: -90,
rotationZ: (i - 2) * 8,
boxShadow: "0 30px 60px rgba(0,0,0,0.5)",
});
});
/* Headline reveal */
gsap.to("#feelings .feel-w-in", {
scrollTrigger: { trigger: "#feelings", start: "top 78%", once: true },
yPercent: 0,
@@ -538,17 +733,22 @@ export function Feelings() {
ease: "power4.out",
});
gsap.to("#feelings .feel-card", {
scrollTrigger: { trigger: "#feelings .feel-grid", start: "top 82%", once: true },
clipPath: "inset(0% 0% 0% 0%)",
yPercent: 0,
/* Dealer deals cards from center out to their positions one by one */
gsap.to(cardEls, {
scrollTrigger: { trigger: "#feelings .feel-grid", start: "top 75%", once: true },
xPercent: 0,
y: 0,
scale: 1,
rotationY: 0,
rotationZ: 0,
autoAlpha: 1,
filter: "blur(0px)",
stagger: 0.1,
duration: 0.95,
ease: "power3.out",
onComplete: () => gsap.set("#feelings .feel-card", { clearProps: "filter,clipPath,transform" }),
boxShadow: "6px 6px 0 0 #0a0a0a",
duration: 0.65,
stagger: 0.15,
ease: "back.out(1.5)",
onComplete: () => {
gsap.set(cardEls, { clearProps: "transform,transformStyle,perspective" });
},
});
gsap.to("#feelings .feel-sum", {
@@ -562,7 +762,7 @@ export function Feelings() {
return (
<Section id="feelings" variant="cream" size="epic" width="wide">
<Pill bg={C.purple} text="#fff">💗 why social currency</Pill>
<Pill bg={C.purple} text="#fff"><Ico e="💗" /> why social currency</Pill>
<h2 className="mt-6 font-sora text-4xl font-extrabold leading-[1.02] tracking-tight [perspective:800px] sm:text-5xl lg:text-6xl">
<MaskWords text="people don't want points. they want" wordClass="feel-w-in" />
<MaskWords text="feelings." wordClass="feel-w-in" inner="text-[#FF2E88]" />
@@ -571,12 +771,14 @@ export function Feelings() {
{cards.map(([e, t, s, bg, tc]) => (
<div
key={t}
className="feel-card nb nb-hover rounded-2xl p-5 [will-change:transform,filter]"
className="feel-card nb nb-hover flex flex-col justify-between rounded-2xl p-5 cursor-pointer [will-change:transform]"
style={{ background: bg, color: tc }}
>
<div className="text-2xl">{e}</div>
<div className="mt-6 font-sora text-lg font-extrabold">{t}</div>
<div className="text-xs font-bold opacity-80">{s}</div>
<div>
<div className="mb-1"><Ico e={e} size={28} /></div>
<div className="mt-6 font-sora text-lg font-extrabold leading-tight">{t}</div>
<div className="mt-1 text-xs font-bold opacity-80">{s}</div>
</div>
</div>
))}
</div>
@@ -619,29 +821,29 @@ export function Difference() {
return (
<Section id="difference" variant="night" size="full" width="wide" bgImage="/images/background/FD_bg.png">
<Pill> the fundamental difference</Pill>
<Pill><Ico e="⚡" /> the fundamental difference</Pill>
<h2 className="mt-6 font-sora text-4xl font-extrabold leading-[0.95] tracking-tight text-[#FFD21E] [perspective:800px] sm:text-5xl lg:text-6xl">
<span className="block"><MaskWords text="loyalty starts at spend." wordClass="diff-w-in" /></span>
<span className="block"><MaskWords text="LYTs starts at engage." wordClass="diff-w-in" /></span>
</h2>
<div className="mt-10 grid items-start gap-4 md:grid-cols-2">
<div className="diff-left">
<Pill bg={C.yellow} text="#0a0a0a">💻 OLD WORLD</Pill>
<Pill bg={C.yellow} text="#0a0a0a"><Ico e="💻" /> OLD WORLD</Pill>
<div className="mt-3 font-sora text-3xl font-extrabold">TRADITIONAL LOYALTY</div>
<div className="mt-4 space-y-2">
<div className="diff-step nb rounded-lg bg-white px-4 py-2 text-center font-extrabold text-[#0a0a0a]">PURCHASE </div>
<div className="diff-step nb rounded-lg px-4 py-2 text-center font-extrabold text-[#0a0a0a]" style={{ background: C.yellow }}>EARN POINTS </div>
<div className="diff-step nb rounded-lg px-4 py-2 text-center font-extrabold text-white" style={{ background: C.orange }}>REDEEM </div>
<div className="diff-step font-extrabold text-[#C8FF37]"> POINTS EXPIRE</div>
<div className="diff-step font-extrabold text-[#C8FF37]"><Ico e="⏳" /> POINTS EXPIRE</div>
</div>
<div className="mt-4"><Chip bg={C.lime}>🔥 not a better coupon. a whole new currency. 🌱</Chip></div>
<div className="mt-4"><Chip bg={C.lime}><Ico e="🔥" /> not a better coupon. a whole new currency. <Ico e="🌱" /></Chip></div>
</div>
<div className="diff-right nb-lg rounded-2xl bg-gradient-to-br from-[#FF2E88] to-[#A020F0] p-5">
<Pill bg={C.yellow} text="#0a0a0a"> LYTS WAY</Pill>
<Pill bg={C.yellow} text="#0a0a0a"><Ico e="✨" /> LYTS WAY</Pill>
<div className="mt-3 font-sora text-3xl font-extrabold text-white">LYTS SOCIAL CURRENCY</div>
<div className="mt-4 space-y-2">
{[["DISCOVER", C.lime, "#0a0a0a"], ["ENGAGE", C.yellow, "#0a0a0a"], ["EARN LYTS", C.orange, "#fff"], ["SPEND", C.pink, "#fff"], ["LIVE MORE", "#fff", "#0a0a0a"], ["💞 LYTIAN", "#0a0a0a", C.lime]].map(([t, bg, tc]) => (
<div key={t} className="diff-step nb rounded-lg px-4 py-2 text-center font-extrabold" style={{ background: bg, color: tc }}>{t} {t !== "💞 LYTIAN" && "↓"}</div>
<div key={t} className="diff-step nb rounded-lg px-4 py-2 text-center font-extrabold" style={{ background: bg, color: tc }}><span className="inline-flex items-center justify-center gap-1.5">{withIcons(t, 15)} {t !== "💞 LYTIAN" && "↓"}</span></div>
))}
</div>
</div>
@@ -695,7 +897,7 @@ export function TheLoop() {
return (
<Section id="the-loop" variant="night" size="normal">
<Pill bg={C.lime} text="#0a0a0a">🚀 your 6-step loop</Pill>
<Pill bg={C.lime} text="#0a0a0a"><Ico e="🚀" /> your 6-step loop</Pill>
<h2 className="mt-6 font-sora text-4xl font-extrabold leading-[1.02] tracking-tight [perspective:900px] sm:text-5xl lg:text-6xl">
<span className="block"><MaskWords text="six small moments." wordClass="loop-w-in" /></span>
<span className="block">
@@ -712,11 +914,11 @@ export function TheLoop() {
>
<div className="mb-2 w-fit rounded bg-[#0a0a0a] px-1.5 text-xs font-extrabold text-white">{n}</div>
<div className="font-sora text-lg font-extrabold leading-tight">{t}</div>
<div className="mt-2 text-xs font-bold opacity-80">{s}</div>
<div className="mt-2 text-xs font-bold opacity-80"><span className="inline-flex flex-wrap items-center gap-1">{withIcons(s, 13)}</span></div>
</div>
))}
</div>
<div className="mt-6"><Chip bg={C.purple} text="#fff"> every interaction creates value. every LYTIAN invites another.</Chip></div>
<div className="mt-6"><Chip bg={C.purple} text="#fff"><Ico e="♻️" /> every interaction creates value. every LYTIAN invites another.</Chip></div>
</Section>
);
}

View File

@@ -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 (
<Section id="for-you" variant="cream" size="full" width="wide">
<div className="grid gap-8 lg:grid-cols-[1.3fr_1fr] lg:items-start">
<div>
<Pill>🙋 for you</Pill>
<h2 className="mt-6 font-sora text-4xl sm:text-6xl md:text-7xl font-extrabold leading-[0.9] tracking-tight">
your life
<br />
is <span className="text-[#A020F0]">worth</span>
<br />
something.
</h2>
<p className="mt-4 font-bold">every step. every visit. every selfie. it&apos;s all worth it here. 💗</p>
</div>
<div className="space-y-4">
<Card bg="#0a0a0a" text={C.lime} className="p-5">
<div className="font-sora text-lg sm:text-xl font-extrabold">value begins before purchase. 🌱</div>
</Card>
<Card bg={C.orange} text="#fff" className="p-5">
<div className="font-sora text-xl sm:text-2xl font-extrabold">walk in. win more.</div>
<div className="mt-1 text-sm font-bold">
10k steps? <span className="rounded bg-[#FFD21E] px-1 text-[#0a0a0a]">earn.</span> coffee with a friend?{" "}
<span className="rounded bg-[#FFD21E] px-1 text-[#0a0a0a]">earn.</span> sharing a vibe?{" "}
<span className="rounded bg-[#FFD21E] px-1 text-[#0a0a0a]">earn.</span>
</div>
</Card>
</div>
</div>
<Group className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{cards.map(([e, t, s, bg, tc]) => (
<Card key={t} group bg={bg} text={tc} className="p-5">
<div className="text-2xl">{e}</div>
<div className="mt-4 font-sora text-lg font-extrabold leading-tight">{t}</div>
<div className="mt-1 text-xs font-bold opacity-85">{s}</div>
</Card>
))}
</Group>
</Section>
);
}
/* 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 (
<Section id="for-business" variant="night" size="full" width="wide">
<Pill bg={C.yellow} text="#0a0a0a">🤝 for businesses</Pill>
<Heading className="mt-6">
acquire earlier.
<br />
retain <span className="text-[#FFD21E]">forever.</span>
</Heading>
<p className="mt-4 font-bold text-[#C8FF37]">meet people before they buy. keep them warm after. 💞</p>
<Group className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{cards.map(([n, tag, t, s, bg, tc]) => (
<Card key={n} group bg={bg} text={tc} className="relative overflow-hidden p-5">
<span className="absolute right-3 top-2 font-sora text-4xl font-extrabold opacity-20">{n}</span>
<div className="w-fit rounded bg-[#0a0a0a] px-2 py-0.5 text-xs font-extrabold text-[#FFD21E]">{tag}</div>
<div className="mt-3 font-sora text-xl font-extrabold leading-tight">{t}</div>
<div className="mt-1 text-sm font-bold opacity-85">{s}</div>
</Card>
))}
</Group>
</Section>
);
}
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<HTMLElement>("#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 (
<Section id="why-now" variant="deep" size="normal" width="wide">
<Pill bg={C.lime} text="#0a0a0a"> why this matters</Pill>
<Pill bg={C.lime} text="#0a0a0a"><Ico e="⏰" /> why this matters</Pill>
<Heading className="mt-6">
5 currents
<br />
are converging <span className="text-[#FFD21E]">right now.</span>
</Heading>
<Group className="mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
<div className="game-card-grid mt-10 grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
{cards.map(([e, t, s, a, bg, tc]) => (
<Card key={t} group bg={bg} text={tc} className="flex flex-col p-5">
<div className="text-2xl">{e}</div>
<div
key={t}
className="game-card nb nb-hover flex flex-col justify-between p-5 rounded-2xl cursor-pointer transition-transform duration-200"
style={{ background: bg, color: tc }}
>
<div>
<div className="mb-1"><Ico e={e} size={28} /></div>
<div className="mt-4 font-sora text-lg font-extrabold leading-tight">{t}</div>
<div className="mt-1 text-xs font-bold opacity-80">{s}</div>
<div className="mt-4 text-2xl font-extrabold">{a}</div>
</Card>
</div>
<div className="mt-6 text-2xl font-extrabold">{a}</div>
</div>
))}
</Group>
</div>
</Section>
);
}
@@ -145,14 +135,14 @@ export function HowItWorks() {
const bottom = cards.slice(5);
return (
<Section id="how-it-works" variant="cream" size="epic" width="wide">
<Pill bg="#0a0a0a" text={C.lime}> how it works</Pill>
<Pill bg="#0a0a0a" text={C.lime}><Ico e="⚡" /> how it works</Pill>
<h2 className="mt-6 font-sora text-5xl font-extrabold leading-[0.95] tracking-tight sm:text-6xl">
every action earns points{" "}
<span className="nb-sm bg-[#C8FF37] px-2 text-[#0a0a0a]">LYTs.</span>
</h2>
<p className="mt-4 font-bold">
no app-opening rituals. no complicated loyalty rules.{" "}
<span className="bg-[#FFD21E] px-1">no waiting until you spend.</span> just live your life, engage the world, and watch your LYTs grow. 🌱
<span className="bg-[#FFD21E] px-1">no waiting until you spend.</span> just live your life, engage the world, and watch your LYTs grow. <Ico e="🌱" />
</p>
{/* Two counter-scrolling tracks. The opposing directions read as a
@@ -196,7 +186,7 @@ function ActionTile({
<span className="absolute right-2 top-2 rounded bg-[#0a0a0a] px-1.5 py-0.5 text-[10px] font-extrabold text-[#FFD21E]">
+LYTs
</span>
<div className="text-2xl">{emoji}</div>
<div className="mb-1"><Ico e={emoji} size={26} /></div>
<div className="mt-2 font-sora text-lg font-extrabold leading-tight">{title}</div>
<div className="mt-0.5 text-xs font-bold opacity-80">{sub}</div>
</div>
@@ -233,7 +223,7 @@ export function OldVsNew() {
return (
<Section id="old-vs-new" variant="deep" size="full" width="wide">
<Pill bg={C.yellow} text="#0a0a0a">💞 a new kind of loyalty</Pill>
<Pill bg={C.yellow} text="#0a0a0a"><Ico e="💞" /> a new kind of loyalty</Pill>
<p className="mt-4 font-bold text-[#C8FF37]">traditional loyalty said one thing.</p>
<h2 className="mt-1 font-sora text-4xl font-extrabold leading-[0.95] tracking-tight [perspective:800px] sm:text-5xl lg:text-6xl">
<MaskWords text="LYTs says" wordClass="ovn-w-in" inner="text-[#FFD21E]" />
@@ -241,25 +231,25 @@ export function OldVsNew() {
</h2>
<div className="mt-10 grid items-stretch gap-4 md:grid-cols-[1fr_auto_1fr]">
<div className="ovn-left nb nb-hover rounded-2xl bg-[#0a0a0a] p-6 text-white [will-change:transform]">
<Pill bg={C.yellow} text="#0a0a0a">💻 TRADITIONAL LOYALTY</Pill>
<Pill bg={C.yellow} text="#0a0a0a"><Ico e="💻" /> TRADITIONAL LOYALTY</Pill>
<div className="mt-4 font-sora text-3xl font-extrabold leading-tight text-[#FFD21E]">
<span className="ovn-rule block">the old rule</span>
<span className="ovn-rule block text-white">spend first.</span>
<span className="ovn-rule block text-white">earn later.</span>
</div>
<div className="ovn-rule mt-4 text-sm font-bold text-white/70">value is created only when you buy. participation = invisible. 🫥</div>
<div className="ovn-rule mt-4 text-sm font-bold text-white/70">value is created only when you buy. participation = invisible. <Ico e="🫥" /></div>
</div>
<div className="flex items-center justify-center">
<span className="ovn-vs nb flex h-16 w-16 items-center justify-center rounded-full bg-[#FFD21E] font-sora text-xl font-extrabold text-[#0a0a0a] [will-change:transform]">VS</span>
</div>
<div className="ovn-right nb-lg rounded-2xl bg-gradient-to-br from-[#FF2E88] to-[#A020F0] p-6 [will-change:transform]">
<Pill bg="#0a0a0a" text="#fff"> THE LYTS WAY</Pill>
<Pill bg="#0a0a0a" text="#fff"><Ico e="✨" /> THE LYTS WAY</Pill>
<div className="mt-4 font-sora text-3xl font-extrabold leading-tight text-white">
<span className="ovn-rule block">the new rule</span>
<span className="ovn-rule block">live first.</span>
<span className="ovn-rule block">earn everywhere.</span>
</div>
<div className="ovn-rule mt-4 text-sm font-bold text-white/85">value is created when you participate. everyday life is the unlock. 🎉</div>
<div className="ovn-rule mt-4 text-sm font-bold text-white/85">value is created when you participate. everyday life is the unlock. <Ico e="🎉" /></div>
</div>
</div>
<div className="mt-6">
@@ -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 <Ico e="✓" /> 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() {
<Section id="the-vision" variant="deep" size="normal">
<div className="grid gap-8 lg:grid-cols-[1.2fr_1fr] lg:items-center">
<div>
<Pill bg={C.yellow} text="#0a0a0a">🌈 the vision</Pill>
<Pill bg={C.yellow} text="#0a0a0a"><Ico e="🌈" /> the vision</Pill>
<h2 className="mt-6 font-sora text-4xl font-extrabold leading-[0.95] tracking-tight [perspective:800px] sm:text-6xl md:text-7xl">
<MaskWords text="world's largest" wordClass="vis-w-in" />
<span className="mr-[0.24em] inline-block overflow-hidden align-bottom pb-[0.08em]">
@@ -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 }}
>
<span className="vis-check flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[#C8FF37] text-sm text-[#0a0a0a]"></span>
<span className="vis-check flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[#C8FF37] text-sm text-[#0a0a0a]"><Ico e="✓" /></span>
{t}
</div>
))}
</div>
</div>
<div className="mt-8">
<Chip bg="#0a0a0a" text={C.lime}>every meaningful interaction has value. 🤝</Chip>
<Chip bg="#0a0a0a" text={C.lime}>every meaningful interaction has value. <Ico e="🤝" /></Chip>
</div>
</Section>
);
@@ -344,7 +334,7 @@ export function OurBelief() {
return (
<Section id="our-belief" variant="night" size="normal" width="wide">
<div className="mx-auto flex flex-col items-center text-center">
<Pill bg="#0a0a0a" text={C.yellow}>🌱 our belief</Pill>
<Pill bg="#0a0a0a" text={C.yellow}><Ico e="🌱" /> our belief</Pill>
</div>
{/* The thesis, lit word by word as it scrolls past. */}
@@ -388,7 +378,7 @@ export function ThePromise() {
return (
<Section id="the-promise" variant="cream" size="full" width="wide" align="center">
<div className="flex items-start justify-between">
<Pill bg="#0a0a0a" text={C.yellow}> the promise</Pill>
<Pill bg="#0a0a0a" text={C.yellow}><Ico e="⚡" /> the promise</Pill>
<Logo />
</div>
<h2 className="mt-8 font-sora text-6xl font-extrabold leading-[0.9] tracking-tight sm:text-7xl">
@@ -398,15 +388,15 @@ export function ThePromise() {
</h2>
<div className="mt-8 flex flex-wrap gap-3">
{chips.map(([t, bg, tc]) => (
<Chip key={t} bg={bg} text={tc}>{t}</Chip>
<Chip key={t} bg={bg} text={tc}>{withIcons(t, 15)}</Chip>
))}
</div>
<div className="mt-10 flex flex-col items-start justify-between gap-6 sm:flex-row sm:items-center">
<Chip bg={C.purple} text="#fff">
👉 Earn. Spend. Live More. <span className="ml-1 text-[#FFD21E]"> the rest is just coupons.</span>
<Ico e="👉" /> Earn. Spend. Live More. <span className="ml-1 text-[#FFD21E]"> the rest is just coupons.</span>
</Chip>
<span className="nb flex h-28 w-28 shrink-0 rotate-6 items-center justify-center rounded-full bg-[#C8FF37] text-center font-sora text-sm font-extrabold text-[#0a0a0a]">
join the LYTIANS 🤝
join the LYTIANS <Ico e="🤝" />
</span>
</div>
</Section>