refactor: update UI layout, remove legacy sections, and add background image
This commit is contained in:
BIN
public/images/background/FD_bg.png
Normal file
BIN
public/images/background/FD_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@@ -29,6 +29,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Marquee — the track renders its children twice, so travelling exactly -50%
|
||||||
|
lands the copy where the original started and the loop is seamless.
|
||||||
|
Defined at top level, not inside @theme: Tailwind only emits keyframes from
|
||||||
|
@theme when a --animate-* token references them, and these are applied by
|
||||||
|
plain class instead. */
|
||||||
|
@keyframes marquee-left {
|
||||||
|
from { transform: translate3d(0, 0, 0); }
|
||||||
|
to { transform: translate3d(-50%, 0, 0); }
|
||||||
|
}
|
||||||
|
@keyframes marquee-right {
|
||||||
|
from { transform: translate3d(-50%, 0, 0); }
|
||||||
|
to { transform: translate3d(0, 0, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.marquee-track {
|
||||||
|
animation: marquee-left var(--marquee-duration, 30s) linear infinite;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.marquee-track[data-reverse="true"] {
|
||||||
|
animation-name: marquee-right;
|
||||||
|
}
|
||||||
|
/* Pausing on hover lets people actually read a card they're interested in. */
|
||||||
|
.marquee-viewport:hover .marquee-track {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.marquee-track { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-sora: 'Sora', sans-serif;
|
--font-sora: 'Sora', sans-serif;
|
||||||
--font-inter: 'Inter', sans-serif;
|
--font-inter: 'Inter', sans-serif;
|
||||||
|
|||||||
@@ -196,7 +196,10 @@ export default function Header() {
|
|||||||
<header
|
<header
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: isMobileViewport ? '14px' : '18px',
|
// Sections are inset 20px (m-5) from the viewport. Sit the bar that far
|
||||||
|
// down again plus a section-sized gap, so it floats *inside* the first
|
||||||
|
// section with the same breathing room sections give each other.
|
||||||
|
top: 'calc(20px + var(--sec-gap))',
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
width: isMobileViewport ? 'auto' : '70%',
|
width: isMobileViewport ? 'auto' : '70%',
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Marquee from "react-fast-marquee";
|
|
||||||
import { Coffee, ShoppingBag, Music, Film, Dumbbell, Ticket, Sparkles } from "lucide-react";
|
|
||||||
|
|
||||||
export default function BrandMarquee() {
|
|
||||||
const brands = [
|
|
||||||
{ name: "Starbucks", icon: <Coffee className="w-5 h-5 text-emerald-400" />, color: "text-emerald-400" },
|
|
||||||
{ name: "Nike", icon: <ShoppingBag className="w-5 h-5 text-white" />, color: "text-white" },
|
|
||||||
{ name: "Spotify", icon: <Music className="w-5 h-5 text-green-400" />, color: "text-green-400" },
|
|
||||||
{ name: "Netflix", icon: <Film className="w-5 h-5 text-red-500" />, color: "text-red-500" },
|
|
||||||
{ name: "Gymshark", icon: <Dumbbell className="w-5 h-5 text-amber-500" />, color: "text-amber-500" },
|
|
||||||
{ name: "Ticketmaster", icon: <Ticket className="w-5 h-5 text-sky-400" />, color: "text-sky-400" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="w-full bg-[#070707] border-y border-white/5 py-8 relative z-10 overflow-hidden">
|
|
||||||
<Marquee gradient={false} speed={40} className="font-sora text-sm font-extrabold tracking-wider">
|
|
||||||
{brands.concat(brands).map((brand, idx) => (
|
|
||||||
<div key={idx} className="flex items-center mx-12 space-x-3 text-white/70 hover:text-white transition-colors duration-200">
|
|
||||||
<span className="p-2 bg-white/5 rounded-xl border border-white/10 flex items-center justify-center">
|
|
||||||
{brand.icon}
|
|
||||||
</span>
|
|
||||||
<span className="text-base font-bold">{brand.name}</span>
|
|
||||||
<Sparkles className="w-3 h-3 text-primary animate-pulse" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Marquee>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { User, Flame, Coins, CheckCircle, Smartphone, ArrowRight, Share2, Compass, ChevronRight, Activity, TrendingUp } from "lucide-react";
|
|
||||||
|
|
||||||
export default function DashboardPreview() {
|
|
||||||
const { profile } = useUser();
|
|
||||||
|
|
||||||
// Dynamic calculations based on user's steps
|
|
||||||
const caloriesBurned = Math.round(profile.stepsToday * 0.04);
|
|
||||||
const distanceKm = (profile.stepsToday * 0.00076).toFixed(2);
|
|
||||||
const activeMinutes = Math.round(profile.stepsToday * 0.008);
|
|
||||||
|
|
||||||
const stepPercentage = Math.min(100, Math.round((profile.stepsToday / profile.stepGoal) * 100));
|
|
||||||
|
|
||||||
// Weekly steps dummy data
|
|
||||||
const weeklySteps = [
|
|
||||||
{ day: "Mon", steps: 8200 },
|
|
||||||
{ day: "Tue", steps: 9400 },
|
|
||||||
{ day: "Wed", steps: 6100 },
|
|
||||||
{ day: "Thu", steps: 7800 },
|
|
||||||
{ day: "Fri", steps: 11200 },
|
|
||||||
{ day: "Sat", steps: 9000 },
|
|
||||||
{ day: "Sun", steps: profile.stepsToday },
|
|
||||||
];
|
|
||||||
|
|
||||||
const maxWeeklySteps = Math.max(...weeklySteps.map((d) => d.steps));
|
|
||||||
|
|
||||||
// Transaction history dummy data
|
|
||||||
const transactions = [
|
|
||||||
{ id: 1, type: "Walk Reward", detail: "6,420 steps synced", amount: "+16 LYT", time: "2 hours ago", isEarn: true },
|
|
||||||
{ id: 2, type: "Redeemed Brand", detail: "Starbucks $10 Voucher", amount: "-150 LYT", time: "5 hours ago", isEarn: false },
|
|
||||||
{ id: 3, type: "Referral Bonus", detail: "Invited Marcus Lee", amount: "+50 LYT", time: "1 day ago", isEarn: true },
|
|
||||||
{ id: 4, type: "Game Catch", detail: "LYT Catcher Score: 45", amount: "+45 LYT", time: "1 day ago", isEarn: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="dashboard" className="py-24 bg-dark relative overflow-hidden">
|
|
||||||
{/* Background visual glows */}
|
|
||||||
<div className="absolute top-1/2 left-1/4 -translate-y-1/2 w-96 h-96 rounded-full bg-accent-purple/10 blur-[130px] pointer-events-none" />
|
|
||||||
<div className="absolute bottom-0 right-1/4 w-96 h-96 rounded-full bg-accent-pink/5 blur-[130px] pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
{/* Section Header */}
|
|
||||||
<div className="text-center max-w-3xl mx-auto mb-20 space-y-4">
|
|
||||||
<span className="text-accent-lime font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
Platform Preview
|
|
||||||
</span>
|
|
||||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
|
||||||
Manage Assets on <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-accent-pink text-glow-yellow">Your Dashboard</span>
|
|
||||||
</h2>
|
|
||||||
<p className="text-white/60 text-base sm:text-lg">
|
|
||||||
Track walking logs, check voucher redemption codes, and review points transaction history in your unified portal.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contents Grid */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
|
|
||||||
|
|
||||||
{/* Left Block: Weekly Progress Charts (col-span-3) */}
|
|
||||||
<div className="lg:col-span-3 space-y-6 hidden lg:block">
|
|
||||||
<div className="glassmorphism rounded-3xl p-6 space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm font-bold text-white font-sora flex items-center gap-1.5">
|
|
||||||
<Activity className="w-4 h-4 text-accent-lime" />
|
|
||||||
<span>Activity Logs</span>
|
|
||||||
</span>
|
|
||||||
<span className="text-[10px] text-white/40 font-bold uppercase">Weekly</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Steps Chart */}
|
|
||||||
<div className="h-44 flex items-end justify-between gap-2.5 pt-4">
|
|
||||||
{weeklySteps.map((d, idx) => {
|
|
||||||
const pct = Math.max(15, Math.round((d.steps / maxWeeklySteps) * 100));
|
|
||||||
const isToday = idx === weeklySteps.length - 1;
|
|
||||||
return (
|
|
||||||
<div key={idx} className="flex-1 flex flex-col items-center gap-2">
|
|
||||||
<div className="w-full relative group">
|
|
||||||
<div
|
|
||||||
style={{ height: `${pct}%` }}
|
|
||||||
className={`w-full rounded-t-lg transition-all duration-500 min-h-[15px] ${
|
|
||||||
isToday
|
|
||||||
? "bg-gradient-to-t from-accent-pink to-accent-purple shadow-[0_0_10px_rgba(255,46,136,0.2)]"
|
|
||||||
: "bg-white/10 hover:bg-white/20"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
{/* Tooltip */}
|
|
||||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 hidden group-hover:block bg-dark border border-white/10 px-2 py-0.5 rounded text-[9px] font-mono text-white z-10 whitespace-nowrap">
|
|
||||||
{d.steps.toLocaleString()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span className={`text-[10px] font-bold ${isToday ? "text-accent-pink" : "text-white/40"}`}>
|
|
||||||
{d.day}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border-t border-white/5 pt-4 text-xs text-white/50 leading-relaxed">
|
|
||||||
🎉 Your weekly walk step frequency has increased by <span className="text-accent-lime font-bold">12.4%</span> since last week. Keep it up!
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Center Block: Mobile iOS Phone Mockup (col-span-5) */}
|
|
||||||
<div className="lg:col-span-5 flex justify-center">
|
|
||||||
{/* Phone Shell */}
|
|
||||||
<div className="relative w-[320px] h-[640px] rounded-[48px] border-[8px] border-white/10 bg-[#090909] p-3.5 shadow-[0_25px_60px_-15px_rgba(0,0,0,0.9)] overflow-hidden flex flex-col justify-between">
|
|
||||||
|
|
||||||
{/* Dynamic Island Notch */}
|
|
||||||
<div className="absolute top-3.5 left-1/2 -translate-x-1/2 w-28 h-6 rounded-full bg-black z-30 flex items-center justify-center">
|
|
||||||
<div className="w-2.5 h-2.5 rounded-full bg-zinc-900 ml-auto mr-4" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Status Header */}
|
|
||||||
<div className="flex justify-between items-center text-[10px] text-white/70 font-semibold px-4 pt-1.5 z-20">
|
|
||||||
<span>9:41</span>
|
|
||||||
<div className="flex space-x-1.5">
|
|
||||||
<span>5G</span>
|
|
||||||
<span>100%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* App Internal Body (Scrollable mock container) */}
|
|
||||||
<div className="flex-grow overflow-y-auto px-1.5 py-4 space-y-5 scrollbar-none mt-2">
|
|
||||||
|
|
||||||
{/* User Header */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center space-x-2.5">
|
|
||||||
<div className="w-8 h-8 rounded-full bg-gradient-to-tr from-accent-pink to-accent-purple p-0.5 flex items-center justify-center">
|
|
||||||
<div className="w-full h-full rounded-full bg-dark flex items-center justify-center text-xs font-bold text-white">
|
|
||||||
AS
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-white font-bold text-xs block">{profile.name}</span>
|
|
||||||
<span className="text-[9px] text-white/40 block">Level {profile.level} Premium Member</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Streak tag */}
|
|
||||||
<div className="flex items-center space-x-1 px-2.5 py-1 rounded-full bg-white/5 border border-white/10 text-accent-orange font-bold text-[10px]">
|
|
||||||
<Flame className="w-3 h-3 fill-current" />
|
|
||||||
<span>{profile.streak} Days</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Circular Step Tracker */}
|
|
||||||
<div className="bg-white/3 border border-white/5 rounded-3xl p-5 flex flex-col items-center justify-center text-center relative overflow-hidden">
|
|
||||||
|
|
||||||
{/* Gauge */}
|
|
||||||
<div className="relative w-36 h-36 flex items-center justify-center">
|
|
||||||
|
|
||||||
{/* SVG circular track */}
|
|
||||||
<svg className="w-full h-full transform -rotate-90">
|
|
||||||
<circle
|
|
||||||
cx="72"
|
|
||||||
cy="72"
|
|
||||||
r="60"
|
|
||||||
className="stroke-white/5 fill-transparent"
|
|
||||||
strokeWidth="8"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx="72"
|
|
||||||
cy="72"
|
|
||||||
r="60"
|
|
||||||
className="stroke-primary fill-transparent"
|
|
||||||
strokeWidth="8"
|
|
||||||
strokeDasharray={377}
|
|
||||||
strokeDashoffset={377 - (377 * stepPercentage) / 100}
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
{/* Step values */}
|
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
|
||||||
<span className="text-2xl font-extrabold font-sora text-white">
|
|
||||||
{profile.stepsToday.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
<span className="text-[9px] text-white/40 font-bold uppercase tracking-wider">
|
|
||||||
Steps Today
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className="text-[10px] text-white/50 font-bold mt-2">
|
|
||||||
Goal: {profile.stepGoal.toLocaleString()} steps ({stepPercentage}%)
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Stats split */}
|
|
||||||
<div className="grid grid-cols-3 gap-2 w-full pt-4 mt-4 border-t border-white/5">
|
|
||||||
<div>
|
|
||||||
<span className="text-white font-extrabold text-xs block">{caloriesBurned}</span>
|
|
||||||
<span className="text-[8px] text-white/40 block">Kcal</span>
|
|
||||||
</div>
|
|
||||||
<div className="border-x border-white/5">
|
|
||||||
<span className="text-white font-extrabold text-xs block">{distanceKm}</span>
|
|
||||||
<span className="text-[8px] text-white/40 block">Km</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-white font-extrabold text-xs block">{activeMinutes}</span>
|
|
||||||
<span className="text-[8px] text-white/40 block">Mins</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Balance & Send Row */}
|
|
||||||
<div className="bg-gradient-to-tr from-accent-purple/20 to-accent-pink/10 border border-accent-purple/20 rounded-2xl p-4 flex items-center justify-between">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<span className="text-[9px] text-white/40 font-bold uppercase tracking-wider block">Wallet Balance</span>
|
|
||||||
<span className="text-lg font-extrabold font-sora text-primary flex items-center gap-1">
|
|
||||||
<Coins className="w-4.5 h-4.5" />
|
|
||||||
<span>{profile.lytBalance} LYTs</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button className="px-3.5 py-1.5 bg-primary text-dark font-extrabold text-[10px] tracking-wide rounded-xl">
|
|
||||||
Redeem
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Challenges Panel */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h4 className="text-[10px] font-bold text-white/40 uppercase tracking-widest px-1">Daily Missions</h4>
|
|
||||||
<div className="bg-white/3 border border-white/5 rounded-2xl p-3 space-y-2">
|
|
||||||
<div className="flex items-center justify-between p-2 hover:bg-white/2 rounded-lg text-xs">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<CheckCircle className="w-4 h-4 text-accent-lime" />
|
|
||||||
<span className="text-white/80">Log 5,000 steps today</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-accent-lime font-bold text-[10px]">+10 LYT</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-2 hover:bg-white/2 rounded-lg text-xs">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<CheckCircle className="w-4 h-4 text-accent-lime" />
|
|
||||||
<span className="text-white/80">Check in at Brew & Co</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-accent-lime font-bold text-[10px]">+5 LYT</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bottom Nav Bar */}
|
|
||||||
<div className="border-t border-white/5 pt-2 pb-1.5 flex justify-around items-center z-20">
|
|
||||||
<div className="flex flex-col items-center text-primary">
|
|
||||||
<Activity className="w-4.5 h-4.5" />
|
|
||||||
<span className="text-[8px] font-bold mt-0.5">Track</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center text-white/40 hover:text-white transition-colors">
|
|
||||||
<Compass className="w-4.5 h-4.5" />
|
|
||||||
<span className="text-[8px] font-bold mt-0.5">Explore</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center text-white/40 hover:text-white transition-colors">
|
|
||||||
<Coins className="w-4.5 h-4.5" />
|
|
||||||
<span className="text-[8px] font-bold mt-0.5">Wallet</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Block: Transaction Log Feed (col-span-4) */}
|
|
||||||
<div className="lg:col-span-4 space-y-6">
|
|
||||||
<div className="glassmorphism rounded-3xl p-6 space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm font-bold text-white font-sora flex items-center gap-1.5">
|
|
||||||
<TrendingUp className="w-4 h-4 text-accent-pink" />
|
|
||||||
<span>Wallet Transaction Ledger</span>
|
|
||||||
</span>
|
|
||||||
<span className="text-[10px] text-white/40 font-bold uppercase">Real-Time</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Transaction list */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
{transactions.map((tx) => (
|
|
||||||
<div key={tx.id} className="flex justify-between items-center p-3 rounded-xl bg-white/2 border border-white/5">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<span className="text-xs font-bold text-white block">{tx.type}</span>
|
|
||||||
<span className="text-[10px] text-white/40 block">{tx.detail}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-right">
|
|
||||||
<span className={`text-xs font-bold block ${tx.isEarn ? "text-accent-lime" : "text-red-400"}`}>
|
|
||||||
{tx.amount}
|
|
||||||
</span>
|
|
||||||
<span className="text-[8px] text-white/30">{tx.time}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-2">
|
|
||||||
<a
|
|
||||||
href="#spend"
|
|
||||||
className="w-full py-3 rounded-xl bg-white/5 hover:bg-white/10 text-white font-extrabold text-xs tracking-wider flex items-center justify-center gap-1 border border-white/10 hover:border-white/20 transition-all cursor-pointer"
|
|
||||||
>
|
|
||||||
<span>Redeem Store Vouchers</span>
|
|
||||||
<ChevronRight className="w-4 h-4" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { Footprints, ShoppingBag, MapPin, Share2, ClipboardCheck, Sparkles, Check, RefreshCw } from "lucide-react";
|
|
||||||
|
|
||||||
export default function EarningFeatures() {
|
|
||||||
const { profile, addLyts, incrementSteps } = useUser();
|
|
||||||
|
|
||||||
// Walk simulator state
|
|
||||||
const [simSteps, setSimSteps] = useState(1200);
|
|
||||||
const [walkClaimed, setWalkClaimed] = useState(false);
|
|
||||||
const walkReward = Math.floor(simSteps / 400); // 1 LYT per 400 steps for the simulator
|
|
||||||
|
|
||||||
// Shop state
|
|
||||||
const [selectedItems, setSelectedItems] = useState<string[]>(["coffee"]);
|
|
||||||
const [shopClaimed, setShopClaimed] = useState(false);
|
|
||||||
|
|
||||||
const shopItems = [
|
|
||||||
{ id: "coffee", name: "Starbucks Iced Latte", cost: "$5.80", reward: 8 },
|
|
||||||
{ id: "sneakers", name: "Nike Air Max Essentials", cost: "$120.00", reward: 150 },
|
|
||||||
{ id: "gym", name: "Gymshark Fitness Tee", cost: "$35.00", reward: 40 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const totalShopReward = shopItems
|
|
||||||
.filter((item) => selectedItems.includes(item.id))
|
|
||||||
.reduce((sum, item) => sum + item.reward, 0);
|
|
||||||
|
|
||||||
// Map state
|
|
||||||
const [visitedStores, setVisitedStores] = useState<string[]>([]);
|
|
||||||
const [mapClaimed, setMapClaimed] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const checkinLocations = [
|
|
||||||
{ id: "cafe", name: "Brew & Co Cafe", reward: 5, checked: false },
|
|
||||||
{ id: "apparel", name: "H&M flagship", reward: 12, checked: false },
|
|
||||||
{ id: "arena", name: "Downtown Arena", reward: 25, checked: false },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Referral state
|
|
||||||
const [referralCopied, setReferralCopied] = useState(false);
|
|
||||||
const referralCode = `lytsup.com/invite/${profile.name.toLowerCase().replace(/\s+/g, "")}-77`;
|
|
||||||
|
|
||||||
const handleSimulateWalk = () => {
|
|
||||||
incrementSteps(simSteps);
|
|
||||||
addLyts(walkReward);
|
|
||||||
setWalkClaimed(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setWalkClaimed(false);
|
|
||||||
setSimSteps(1200);
|
|
||||||
}, 4000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleShopClaim = () => {
|
|
||||||
if (totalShopReward > 0) {
|
|
||||||
addLyts(totalShopReward);
|
|
||||||
setShopClaimed(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setShopClaimed(false);
|
|
||||||
setSelectedItems([]);
|
|
||||||
}, 4000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckin = (id: string, name: string, reward: number) => {
|
|
||||||
if (visitedStores.includes(id)) return;
|
|
||||||
setVisitedStores((prev) => [...prev, id]);
|
|
||||||
addLyts(reward);
|
|
||||||
setMapClaimed(name);
|
|
||||||
setTimeout(() => setMapClaimed(null), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCopyReferral = () => {
|
|
||||||
navigator.clipboard.writeText(referralCode);
|
|
||||||
setReferralCopied(true);
|
|
||||||
addLyts(50); // Referral bonus!
|
|
||||||
setTimeout(() => setReferralCopied(false), 4000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="earn" className="py-24 bg-[#090909] relative overflow-hidden">
|
|
||||||
{/* Background glow lines */}
|
|
||||||
<div className="absolute top-1/2 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-white/5 to-transparent pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
{/* Section Header */}
|
|
||||||
<div className="text-center max-w-3xl mx-auto mb-20 space-y-4">
|
|
||||||
<span className="text-accent-pink font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
How it works
|
|
||||||
</span>
|
|
||||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
|
||||||
Earn LYTs for <span className="text-glow-yellow text-primary">Living Your Life</span>
|
|
||||||
</h2>
|
|
||||||
<p className="text-white/60 text-base sm:text-lg">
|
|
||||||
Interact with our live simulations below to see how easy it is to rack up social currency.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Feature Grid */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
||||||
|
|
||||||
{/* Card 1: Walk to Earn (Simulator) */}
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 flex flex-col justify-between group hover:border-primary/20 transition-all duration-300">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary">
|
|
||||||
<Footprints className="w-6 h-6 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-bold px-3 py-1 rounded-full bg-white/5 text-white/50 border border-white/5">
|
|
||||||
1 LYT / 400 Steps
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white">Walk-to-Earn</h3>
|
|
||||||
<p className="text-white/60 text-sm">
|
|
||||||
Keep your phone in your pocket, walk to class, run errands, or jog. Watch your step count convert to LYTs automatically.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step Simulator Widget */}
|
|
||||||
<div className="bg-white/3 border border-white/5 rounded-2xl p-6 space-y-4">
|
|
||||||
<div className="flex justify-between items-end">
|
|
||||||
<div>
|
|
||||||
<span className="text-white/40 text-[10px] font-bold tracking-wider uppercase block">Steps to Simulate</span>
|
|
||||||
<span className="text-2xl font-extrabold font-sora text-white">
|
|
||||||
+{simSteps.toLocaleString()} <span className="text-xs text-white/40">Steps</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<span className="text-white/40 text-[10px] font-bold tracking-wider uppercase block">Estimated Earn</span>
|
|
||||||
<span className="text-xl font-extrabold text-primary font-sora flex items-center justify-end gap-1">
|
|
||||||
+{walkReward} <span className="text-xs font-medium text-white/60">LYTs</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Range Slider */}
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="400"
|
|
||||||
max="12000"
|
|
||||||
step="400"
|
|
||||||
value={simSteps}
|
|
||||||
onChange={(e) => setSimSteps(parseInt(e.target.value))}
|
|
||||||
disabled={walkClaimed}
|
|
||||||
className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-primary"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex justify-between text-[10px] text-white/40 font-bold">
|
|
||||||
<span>400 Steps</span>
|
|
||||||
<span>6,000 Steps</span>
|
|
||||||
<span>12,000 Steps</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-8">
|
|
||||||
<button
|
|
||||||
onClick={handleSimulateWalk}
|
|
||||||
disabled={walkClaimed}
|
|
||||||
className={`w-full py-4 rounded-xl font-extrabold text-sm tracking-wider flex items-center justify-center gap-2 cursor-pointer transition-all ${
|
|
||||||
walkClaimed
|
|
||||||
? "bg-accent-lime text-dark shadow-[0_0_20px_rgba(200,255,55,0.3)]"
|
|
||||||
: "bg-white/5 hover:bg-primary hover:text-dark text-white border border-white/10 hover:border-primary"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{walkClaimed ? (
|
|
||||||
<>
|
|
||||||
<Check className="w-5 h-5" />
|
|
||||||
<span>Steps Synced! +{walkReward} LYTs Claimed</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<RefreshCw className="w-4 h-4 animate-spin-slow" />
|
|
||||||
<span>Simulate Walk & Sync Steps</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card 2: Shop & Earn (Receipt Simulator) */}
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 flex flex-col justify-between group hover:border-accent-pink/20 transition-all duration-300">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-pink/10 border border-accent-pink/20 flex items-center justify-center text-accent-pink">
|
|
||||||
<ShoppingBag className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-bold px-3 py-1 rounded-full bg-white/5 text-white/50 border border-white/5">
|
|
||||||
10% LYT Cashback
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white">Shop-to-Earn</h3>
|
|
||||||
<p className="text-white/60 text-sm">
|
|
||||||
Shop from partner merchants (food, clothing, fitness centers) and scan your digital receipt to claim instant LYT cashback.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Receipt Widget */}
|
|
||||||
<div className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-5 space-y-3.5 relative overflow-hidden">
|
|
||||||
<div className="absolute top-0 right-0 bg-accent-pink/10 text-accent-pink px-2.5 py-0.5 text-[9px] font-bold tracking-wider rounded-bl-lg uppercase">
|
|
||||||
Pending Receipt
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 className="text-[10px] font-bold text-white/40 uppercase tracking-widest">Select items purchased:</h4>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{shopItems.map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => {
|
|
||||||
if (shopClaimed) return;
|
|
||||||
setSelectedItems((prev) =>
|
|
||||||
prev.includes(item.id)
|
|
||||||
? prev.filter((id) => id !== item.id)
|
|
||||||
: [...prev, item.id]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className={`flex items-center justify-between p-3 rounded-xl border cursor-pointer transition-all ${
|
|
||||||
selectedItems.includes(item.id)
|
|
||||||
? "bg-accent-pink/5 border-accent-pink/30 text-white"
|
|
||||||
: "bg-white/3 border-white/5 text-white/60 hover:border-white/10"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div
|
|
||||||
className={`w-4 h-4 rounded flex items-center justify-center border transition-all ${
|
|
||||||
selectedItems.includes(item.id)
|
|
||||||
? "bg-accent-pink border-accent-pink"
|
|
||||||
: "border-white/20"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{selectedItems.includes(item.id) && <Check className="w-3 h-3 text-white" />}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs font-semibold">{item.name}</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<div className="text-xs font-extrabold text-white">{item.cost}</div>
|
|
||||||
<div className="text-[10px] text-accent-pink font-bold">+{item.reward} LYTs</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-8">
|
|
||||||
<button
|
|
||||||
onClick={handleShopClaim}
|
|
||||||
disabled={shopClaimed || totalShopReward === 0}
|
|
||||||
className={`w-full py-4 rounded-xl font-extrabold text-sm tracking-wider flex items-center justify-center gap-2 cursor-pointer transition-all ${
|
|
||||||
shopClaimed
|
|
||||||
? "bg-accent-lime text-dark shadow-[0_0_20px_rgba(200,255,55,0.3)]"
|
|
||||||
: totalShopReward === 0
|
|
||||||
? "bg-white/5 text-white/20 border border-white/5 cursor-not-allowed"
|
|
||||||
: "bg-white/5 hover:bg-accent-pink hover:text-white text-white border border-white/10 hover:border-accent-pink"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{shopClaimed ? (
|
|
||||||
<>
|
|
||||||
<Check className="w-5 h-5" />
|
|
||||||
<span>Receipt Scanned! +{totalShopReward} LYTs Claimed</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span>Claim Receipt Reward (+{totalShopReward} LYTs)</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card 3: Visit to Earn (Geofence Checkins) */}
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 flex flex-col justify-between group hover:border-accent-purple/20 transition-all duration-300">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-purple/10 border border-accent-purple/20 flex items-center justify-center text-accent-purple">
|
|
||||||
<MapPin className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-bold px-3 py-1 rounded-full bg-white/5 text-white/50 border border-white/5">
|
|
||||||
Location Check-Ins
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white">Visit-to-Earn</h3>
|
|
||||||
<p className="text-white/60 text-sm">
|
|
||||||
Explore your city! Check in at partner gyms, parks, retail shops, or cafes. Each location visit awards you instant points.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Map Simulator Widget */}
|
|
||||||
<div className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-5 space-y-4 relative overflow-hidden">
|
|
||||||
{mapClaimed && (
|
|
||||||
<div className="absolute inset-0 bg-dark/95 flex items-center justify-center z-10 p-4 text-center animate-in fade-in duration-200">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-accent-purple/10 border border-accent-purple/30 mx-auto flex items-center justify-center text-accent-purple">
|
|
||||||
<Sparkles className="w-5 h-5 animate-spin-slow" />
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-semibold text-white">Checked in at {mapClaimed}!</p>
|
|
||||||
<p className="text-xs text-accent-lime font-bold">LYTs Added directly to wallet</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h4 className="text-[10px] font-bold text-white/40 uppercase tracking-widest">Active nodes near you:</h4>
|
|
||||||
<div className="space-y-2.5">
|
|
||||||
{checkinLocations.map((loc) => {
|
|
||||||
const visited = visitedStores.includes(loc.id);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={loc.id}
|
|
||||||
className={`flex items-center justify-between p-3 rounded-xl border transition-all ${
|
|
||||||
visited
|
|
||||||
? "bg-white/2 border-white/5 opacity-50"
|
|
||||||
: "bg-white/3 border-white/5 hover:border-accent-purple/30"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<MapPin className={`w-4 h-4 ${visited ? "text-white/40" : "text-accent-purple animate-bounce"}`} />
|
|
||||||
<div className="text-xs font-semibold text-white">{loc.name}</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => handleCheckin(loc.id, loc.name, loc.reward)}
|
|
||||||
disabled={visited}
|
|
||||||
className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all cursor-pointer ${
|
|
||||||
visited
|
|
||||||
? "bg-white/5 text-white/40 cursor-not-allowed"
|
|
||||||
: "bg-accent-purple text-white hover:bg-accent-purple/90"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{visited ? "Visited" : `Check In (+${loc.reward} LYT)`}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-8 text-center text-xs text-white/40">
|
|
||||||
<span className="font-semibold text-accent-purple">Glow tips:</span> Allow location permissions in-app to auto check-in.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card 4: Refer to Earn (Social Loop) */}
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 flex flex-col justify-between group hover:border-accent-orange/20 transition-all duration-300">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-orange/10 border border-accent-orange/20 flex items-center justify-center text-accent-orange">
|
|
||||||
<Share2 className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-bold px-3 py-1 rounded-full bg-white/5 text-white/50 border border-white/5">
|
|
||||||
50 LYTs / Referral
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white">Refer-to-Earn</h3>
|
|
||||||
<p className="text-white/60 text-sm">
|
|
||||||
Everything is better with friends. Send your personal link to your friends. Get 50 LYTs the second they verify their profile!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Referral Widget */}
|
|
||||||
<div className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-5 space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="text-[10px] font-bold text-white/40 uppercase tracking-widest block mb-2">
|
|
||||||
Your Invite Link:
|
|
||||||
</label>
|
|
||||||
<div className="relative flex">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
readOnly
|
|
||||||
value={referralCode}
|
|
||||||
className="w-full bg-white/3 border border-white/10 rounded-xl px-4 py-3 text-xs text-white/80 font-mono focus:outline-none select-all pr-12"
|
|
||||||
/>
|
|
||||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-white/40">
|
|
||||||
<Share2 className="w-4 h-4" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs text-white/50 leading-relaxed bg-white/3 p-3 rounded-xl border border-white/5">
|
|
||||||
🔥 <span className="text-white font-semibold">Bonus:</span> Your friends also start with <span className="text-primary font-bold">50 LYTs</span> in their wallet when they sign up!
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-8">
|
|
||||||
<button
|
|
||||||
onClick={handleCopyReferral}
|
|
||||||
className={`w-full py-4 rounded-xl font-extrabold text-sm tracking-wider flex items-center justify-center gap-2 cursor-pointer transition-all ${
|
|
||||||
referralCopied
|
|
||||||
? "bg-accent-lime text-dark shadow-[0_0_20px_rgba(200,255,55,0.3)]"
|
|
||||||
: "bg-white/5 hover:bg-accent-orange hover:text-white text-white border border-white/10 hover:border-accent-orange"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{referralCopied ? (
|
|
||||||
<>
|
|
||||||
<ClipboardCheck className="w-5 h-5 animate-bounce" />
|
|
||||||
<span>Copied! +50 LYTs Added to wallet</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span>Copy Invite Link & Earn</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Zap, Clock, ShieldAlert, Award } from "lucide-react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
export default function FlashDrops() {
|
|
||||||
const [timeLeft, setTimeLeft] = useState(7920); // 2 hours 12 minutes in seconds
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
setTimeLeft((prev) => (prev > 0 ? prev - 1 : 7920));
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const formatTime = (secs: number) => {
|
|
||||||
const h = Math.floor(secs / 3600);
|
|
||||||
const m = Math.floor((secs % 3600) / 60);
|
|
||||||
const s = secs % 60;
|
|
||||||
return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const drops = [
|
|
||||||
{
|
|
||||||
brand: "Starbucks Concert",
|
|
||||||
detail: "VIP Pass Ticket Drop",
|
|
||||||
cost: "800 LYTs",
|
|
||||||
locked: true,
|
|
||||||
time: "2h 12m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
brand: "Nike Air Jordan",
|
|
||||||
detail: "$100 Gift Voucher Drop",
|
|
||||||
cost: "1200 LYTs",
|
|
||||||
locked: true,
|
|
||||||
time: "5h 40m",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-dark relative overflow-hidden">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 sm:p-12 border border-accent-lime/10 shadow-[0_0_50px_rgba(200,255,55,0.05)]">
|
|
||||||
<div className="text-center space-y-6 max-w-xl mx-auto mb-10">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-lime/10 border border-accent-lime/20 flex items-center justify-center text-accent-lime mx-auto">
|
|
||||||
<Clock className="w-6 h-6 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-accent-lime font-bold text-xs uppercase tracking-wider">Section 12 • Flash Drops</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Ultra-Rare <br />
|
|
||||||
<span className="text-glow-yellow text-primary">Limited Vouchers</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">
|
|
||||||
Check back periodically. High-value partner rewards drop at set intervals. When the timer hits zero, vouchers unlock for instant claims. First come, first served.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Time & Drops display */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-stretch">
|
|
||||||
|
|
||||||
{/* Timer card */}
|
|
||||||
<div className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-6 flex flex-col justify-center items-center text-center space-y-4">
|
|
||||||
<span className="text-white/40 text-[10px] font-bold tracking-widest uppercase block">Next Secret Drop Unlocks In:</span>
|
|
||||||
<span className="text-3xl sm:text-4xl font-mono font-black text-accent-lime tracking-widest">
|
|
||||||
{formatTime(timeLeft)}
|
|
||||||
</span>
|
|
||||||
<div className="flex items-center space-x-1.5 text-xs text-white/50 bg-white/5 border border-white/10 px-3 py-1 rounded-full">
|
|
||||||
<Zap className="w-3.5 h-3.5 text-accent-lime fill-current" />
|
|
||||||
<span>Notify me when drops open</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Locked drops listing */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
{drops.map((drop, idx) => (
|
|
||||||
<div key={idx} className="bg-white/3 border border-white/5 p-4 rounded-xl flex items-center justify-between opacity-80 hover:opacity-100 transition-opacity">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<span className="text-sm font-bold text-white block">{drop.brand}</span>
|
|
||||||
<span className="text-[10px] text-white/40 block">{drop.detail}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-right space-y-1">
|
|
||||||
<span className="text-xs font-mono font-bold text-white/50 bg-white/5 px-2 py-0.5 rounded border border-white/10 block">
|
|
||||||
Locked
|
|
||||||
</span>
|
|
||||||
<span className="text-[9px] text-accent-lime font-bold block">Cost: {drop.cost}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { ChevronDown, Send, Instagram, Twitter, MessageSquare, Heart, Sparkles } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
interface FaqItem {
|
|
||||||
question: string;
|
|
||||||
answer: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FooterFAQ() {
|
|
||||||
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [subscribed, setSubscribed] = useState(false);
|
|
||||||
|
|
||||||
const faqItems: FaqItem[] = [
|
|
||||||
{
|
|
||||||
question: "How do I sync my walk steps to earn LYTs?",
|
|
||||||
answer: "LytsUp requests standard health API permissions during app registration. It automatically reads your daily distance in the background and converts every 500 steps taken into 1 LYT coin. You don't need to keep the app open!",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: "Are there any fees or hidden charges?",
|
|
||||||
answer: "No, LytsUp is 100% free for users. Our partner brands sponsor the cashback rewards and vouchers to encourage physical activity, foot traffic, and community engagement.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: "How do I redeem my vouchers at partner stores?",
|
|
||||||
answer: "When you click 'Redeem' on a voucher card in the marketplace, LytsUp validates your balance, deducts the points, and generates an active checkout code. Copy the code and apply it during checkout on the partner brand's site.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: "Can I transfer LYT coins to my friends?",
|
|
||||||
answer: "Yes! The mobile app contains a peer-to-peer wallet system where you can send coins to friends to pool assets, complete squad challenges, or gift rewards.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleSubscribe = (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (email) {
|
|
||||||
setSubscribed(true);
|
|
||||||
setEmail("");
|
|
||||||
setTimeout(() => setSubscribed(false), 5000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleFaq = (idx: number) => {
|
|
||||||
setOpenFaq((prev) => (prev === idx ? null : idx));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="bg-[#050505] border-t border-white/5 relative overflow-hidden">
|
|
||||||
{/* Glow lights */}
|
|
||||||
<div className="absolute top-0 left-1/4 -translate-y-1/2 w-90 h-90 rounded-full bg-accent-purple/5 blur-[120px] pointer-events-none" />
|
|
||||||
<div className="absolute top-0 right-1/4 -translate-y-1/2 w-90 h-90 rounded-full bg-accent-pink/5 blur-[120px] pointer-events-none" />
|
|
||||||
|
|
||||||
{/* Part 1: FAQ Accordions */}
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-20 border-b border-white/5">
|
|
||||||
<div className="text-center max-w-2xl mx-auto mb-12 space-y-4">
|
|
||||||
<span className="text-accent-lime font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
Support Hub
|
|
||||||
</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Frequently Asked <span className="text-glow-yellow text-primary">Questions</span>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{faqItems.map((faq, idx) => {
|
|
||||||
const isOpen = openFaq === idx;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
className="bg-white/3 border border-white/5 rounded-2xl overflow-hidden transition-all duration-300"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => toggleFaq(idx)}
|
|
||||||
className="w-full p-5 text-left flex justify-between items-center font-semibold font-sora text-sm text-white hover:bg-white/5 transition-colors cursor-pointer"
|
|
||||||
>
|
|
||||||
<span>{faq.question}</span>
|
|
||||||
<ChevronDown
|
|
||||||
className={`w-4.5 h-4.5 text-white/50 transition-transform duration-300 shrink-0 ml-4 ${
|
|
||||||
isOpen ? "transform rotate-180 text-primary" : ""
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{isOpen && (
|
|
||||||
<div className="p-5 border-t border-white/5 text-xs text-white/60 leading-relaxed bg-[#070707] animate-in slide-in-from-top-2 duration-300">
|
|
||||||
{faq.answer}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Part 2: Footer layout */}
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-16 pb-8 relative z-10">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-12 mb-16">
|
|
||||||
|
|
||||||
{/* Brand Info */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Link href="/" className="flex items-center space-x-2.5">
|
|
||||||
<img src="/images/lytsup_app.png" alt="LytsUp Logo" className="h-8 w-auto object-contain rounded-lg" />
|
|
||||||
<span className="text-xl font-extrabold tracking-wider font-sora text-white">
|
|
||||||
Lyts<span className="text-primary text-glow-yellow">Up</span>
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
<p className="text-white/60 text-xs leading-relaxed max-w-xs">
|
|
||||||
The premier social currency app for Gen Z. Earn rewards for walking, playing, and living your best life.
|
|
||||||
</p>
|
|
||||||
<p className="text-primary font-semibold text-[10px] tracking-widest uppercase">
|
|
||||||
Earn. Spend. Live More.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Site Navigation */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h4 className="text-white font-bold text-xs tracking-wider uppercase font-sora">Navigate</h4>
|
|
||||||
<ul className="space-y-2.5 text-xs">
|
|
||||||
<li>
|
|
||||||
<a href="#earn" className="text-white/60 hover:text-primary transition-colors">Earn Points</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#spend" className="text-white/60 hover:text-primary transition-colors">Redeem Vouchers</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#play" className="text-white/60 hover:text-primary transition-colors">Mini-Game</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#leaderboard" className="text-white/60 hover:text-primary transition-colors">Leaderboard</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Corporate Links */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h4 className="text-white font-bold text-xs tracking-wider uppercase font-sora">Partnerships</h4>
|
|
||||||
<ul className="space-y-2.5 text-xs">
|
|
||||||
<li>
|
|
||||||
<Link href="#" className="text-white/60 hover:text-primary transition-colors">Become a Partner</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href="#" className="text-white/60 hover:text-primary transition-colors">Brands Portal</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href="#" className="text-white/60 hover:text-primary transition-colors">API Integration</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Newsletter subscription */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h4 className="text-white font-bold text-xs tracking-wider uppercase font-sora">Get Secret Drops</h4>
|
|
||||||
<p className="text-white/60 text-xs leading-relaxed">
|
|
||||||
Subscribe to get notified about ultra-rare brand voucher drops.
|
|
||||||
</p>
|
|
||||||
<form onSubmit={handleSubscribe} className="relative flex">
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
placeholder="your.email@gmail.com"
|
|
||||||
value={email}
|
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
|
||||||
required
|
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2 text-xs text-white focus:outline-none focus:border-primary/50 transition-colors pr-10"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="absolute right-1 top-1 bottom-1 bg-primary text-dark font-bold px-3 rounded-lg hover:bg-primary/95 transition-colors flex items-center justify-center cursor-pointer"
|
|
||||||
aria-label="Subscribe"
|
|
||||||
>
|
|
||||||
<Send className="w-3.5 h-3.5" />
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{subscribed && (
|
|
||||||
<p className="text-accent-lime text-[10px] font-semibold animate-pulse">
|
|
||||||
You're in! Check your inbox for secret codes.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr className="border-white/5 mb-8" />
|
|
||||||
|
|
||||||
{/* Socials & Copyright */}
|
|
||||||
<div className="flex flex-col sm:flex-row justify-between items-center space-y-4 sm:space-y-0">
|
|
||||||
<div className="flex items-center space-x-2 text-[10px] text-white/40">
|
|
||||||
<span>© {new Date().getFullYear()} LytsUp.com. All rights reserved.</span>
|
|
||||||
<span className="hidden sm:inline">•</span>
|
|
||||||
<span className="flex items-center">
|
|
||||||
Made with <Heart className="w-3 h-3 text-accent-pink fill-current mx-1" /> for Gen Z
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Social Icons */}
|
|
||||||
<div className="flex space-x-4">
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="w-7 h-7 rounded-full bg-white/5 flex items-center justify-center text-white/60 hover:bg-accent-pink hover:text-white transition-all scale-95 hover:scale-100"
|
|
||||||
aria-label="Instagram"
|
|
||||||
>
|
|
||||||
<Instagram className="w-3.5 h-3.5" />
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="w-7 h-7 rounded-full bg-white/5 flex items-center justify-center text-white/60 hover:bg-accent-purple hover:text-white transition-all scale-95 hover:scale-100"
|
|
||||||
aria-label="Twitter"
|
|
||||||
>
|
|
||||||
<Twitter className="w-3.5 h-3.5" />
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="w-7 h-7 rounded-full bg-white/5 flex items-center justify-center text-white/60 hover:bg-primary hover:text-dark transition-all scale-95 hover:scale-100"
|
|
||||||
aria-label="Discord"
|
|
||||||
>
|
|
||||||
<MessageSquare className="w-3.5 h-3.5" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import { ArrowRight, Sparkles, TrendingUp, Users, ShoppingBag } from "lucide-react";
|
|
||||||
import CountUp from "react-countup";
|
|
||||||
import Marquee from "react-fast-marquee";
|
|
||||||
|
|
||||||
// Dynamically import Three.js Hero3D to prevent SSR issues
|
|
||||||
const Hero3D = dynamic(() => import("../animations/Hero3D"), {
|
|
||||||
ssr: false,
|
|
||||||
loading: () => (
|
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
|
||||||
<div className="relative w-24 h-24">
|
|
||||||
<div className="absolute inset-0 rounded-full border-4 border-primary/20" />
|
|
||||||
<div className="absolute inset-0 rounded-full border-4 border-t-primary animate-spin" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function Hero() {
|
|
||||||
const highlights = [
|
|
||||||
"WALK & EARN",
|
|
||||||
"PLAY & WIN",
|
|
||||||
"SHOP & REDEEM",
|
|
||||||
"SHARE WITH FRIENDS",
|
|
||||||
"DRINK FREE COFFEE",
|
|
||||||
"LEVEL UP",
|
|
||||||
"GEN Z SOCIAL CURRENCY",
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="relative min-h-screen flex flex-col justify-between pt-24 overflow-hidden bg-dark">
|
|
||||||
{/* Background Gradients & Glows */}
|
|
||||||
<div className="absolute top-1/4 left-1/10 w-96 h-96 rounded-full bg-accent-pink/10 blur-[120px] pointer-events-none" />
|
|
||||||
<div className="absolute bottom-1/4 right-1/10 w-96 h-96 rounded-full bg-accent-purple/10 blur-[120px] pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full flex-grow flex items-center">
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center w-full py-12">
|
|
||||||
|
|
||||||
{/* Left Column: Copy & Actions */}
|
|
||||||
<div className="space-y-8 text-left z-10">
|
|
||||||
{/* Promo Tag */}
|
|
||||||
<div className="inline-flex items-center space-x-2 px-3 py-1.5 rounded-full bg-white/5 border border-white/10 text-xs font-semibold tracking-wider text-accent-lime uppercase animate-pulse">
|
|
||||||
<Sparkles className="w-3.5 h-3.5" />
|
|
||||||
<span>Next Gen Social Currency</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Headline */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h1 className="text-4xl sm:text-6xl font-extrabold tracking-tight font-sora leading-[1.1] text-white">
|
|
||||||
Earn. Spend. <br />
|
|
||||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary via-accent-pink to-accent-purple text-glow-pink">
|
|
||||||
Live More.
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
<p className="text-white/60 text-lg sm:text-xl font-medium max-w-lg leading-relaxed font-inter">
|
|
||||||
Turn your everyday activities into social capital. Walk, play games, and shop to earn <span className="text-primary font-bold">LYTs</span>. Spend them at the brands you love.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* CTAs */}
|
|
||||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4">
|
|
||||||
<a
|
|
||||||
href="#play"
|
|
||||||
className="group relative px-8 py-4 rounded-2xl bg-primary text-dark font-extrabold text-center text-base tracking-wide shadow-[0_0_30px_rgba(255,199,44,0.35)] hover:shadow-[0_0_40px_rgba(255,199,44,0.5)] transition-all transform hover:-translate-y-0.5 cursor-pointer flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<span>Earn LYTs Now</span>
|
|
||||||
<ArrowRight className="w-5 h-5 group-hover:translate-x-1.5 transition-transform" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="#spend"
|
|
||||||
className="px-8 py-4 rounded-2xl bg-white/5 hover:bg-white/10 border border-white/10 text-white font-extrabold text-center text-base tracking-wide transition-all transform hover:-translate-y-0.5 cursor-pointer flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<span>Spend Vouchers</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Live Counters / Stats */}
|
|
||||||
<div className="grid grid-cols-3 gap-6 pt-6 border-t border-white/5">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="text-2xl sm:text-3xl font-extrabold text-white flex items-center gap-1 font-sora">
|
|
||||||
<CountUp end={850} duration={3} />
|
|
||||||
<span>K+</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-white/40 text-xs font-semibold flex items-center gap-1.5">
|
|
||||||
<Users className="w-3.5 h-3.5 text-accent-pink" />
|
|
||||||
<span>Active Users</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="text-2xl sm:text-3xl font-extrabold text-white flex items-center gap-1 font-sora">
|
|
||||||
<CountUp end={120} duration={3} />
|
|
||||||
<span>+</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-white/40 text-xs font-semibold flex items-center gap-1.5">
|
|
||||||
<ShoppingBag className="w-3.5 h-3.5 text-accent-purple" />
|
|
||||||
<span>Brand Partners</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="text-2xl sm:text-3xl font-extrabold text-white flex items-center gap-1 font-sora">
|
|
||||||
<CountUp end={45} duration={3} />
|
|
||||||
<span>M+</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-white/40 text-xs font-semibold flex items-center gap-1.5">
|
|
||||||
<TrendingUp className="w-3.5 h-3.5 text-accent-lime" />
|
|
||||||
<span>Steps Tracked</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Column: Interactive 3D Canvas */}
|
|
||||||
<div className="relative h-[450px] sm:h-[600px] w-full flex items-center justify-center">
|
|
||||||
{/* Hologram Circle Rings behind 3D Object */}
|
|
||||||
<div className="absolute w-[300px] sm:w-[450px] h-[300px] sm:h-[450px] rounded-full border border-white/5 animate-pulse" />
|
|
||||||
<div className="absolute w-[200px] sm:w-[350px] h-[200px] sm:h-[350px] rounded-full border border-dashed border-primary/10 animate-spin [animation-duration:40s]" />
|
|
||||||
<div className="absolute w-[100px] sm:w-[250px] h-[100px] sm:h-[250px] rounded-full border border-accent-pink/10 animate-spin [animation-duration:20s] [animation-direction:reverse]" />
|
|
||||||
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<Hero3D />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Highlights Marquee Ticker */}
|
|
||||||
<div className="w-full bg-white/3 border-y border-white/5 py-4 relative z-10 overflow-hidden">
|
|
||||||
<Marquee gradient={false} speed={55} className="font-sora text-sm font-extrabold tracking-widest">
|
|
||||||
{highlights.map((text, idx) => (
|
|
||||||
<div key={idx} className="flex items-center mx-8 space-x-3 text-white">
|
|
||||||
<span className="h-2 w-2 rounded-full bg-primary" />
|
|
||||||
<span>{text}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Marquee>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { Zap, Coins, Gift } from "lucide-react";
|
|
||||||
|
|
||||||
export default function HowItWorks() {
|
|
||||||
const steps = [
|
|
||||||
{
|
|
||||||
num: "01",
|
|
||||||
title: "Walk, Play & Shop",
|
|
||||||
description: "Keep doing your daily activities. Walk to school, play browser games, check-in, or scan shopping receipts.",
|
|
||||||
icon: <Zap className="w-6 h-6 text-accent-pink" />,
|
|
||||||
bg: "rgba(255, 46, 136, 0.05)",
|
|
||||||
border: "border-accent-pink/15",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
num: "02",
|
|
||||||
title: "Earn LYT Coins",
|
|
||||||
description: "Earn LYTs directly into your wallet. Watch your points and account experience points (XP) scale as you walk.",
|
|
||||||
icon: <Coins className="w-6 h-6 text-primary" />,
|
|
||||||
bg: "rgba(255, 199, 44, 0.05)",
|
|
||||||
border: "border-primary/15",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
num: "03",
|
|
||||||
title: "Redeem Brand Vouchers",
|
|
||||||
description: "Spend your earned LYTs on our checkout catalog for gift cards, subscription tokens, and exclusive brand discounts.",
|
|
||||||
icon: <Gift className="w-6 h-6 text-accent-purple" />,
|
|
||||||
bg: "rgba(139, 61, 255, 0.05)",
|
|
||||||
border: "border-accent-purple/15",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-dark relative overflow-hidden">
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
{/* Section Header */}
|
|
||||||
<div className="text-center max-w-3xl mx-auto mb-16 space-y-4">
|
|
||||||
<span className="text-primary font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
Platform Workflow
|
|
||||||
</span>
|
|
||||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
|
||||||
The Social Currency <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-accent-pink">Lifecycle</span>
|
|
||||||
</h2>
|
|
||||||
<p className="text-white/60 text-base sm:text-lg">
|
|
||||||
A three-step gamified loop designed to reward you for living an active, social, and healthy lifestyle.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step Cards Grid */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
||||||
{steps.map((step, idx) => (
|
|
||||||
<motion.div
|
|
||||||
key={step.num}
|
|
||||||
initial={{ opacity: 0, y: 30 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ duration: 0.5, delay: idx * 0.15 }}
|
|
||||||
style={{ backgroundColor: step.bg }}
|
|
||||||
className={`glassmorphism rounded-3xl p-8 border ${step.border} relative flex flex-col justify-between group hover:scale-[1.02] transition-transform`}
|
|
||||||
>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center">
|
|
||||||
{step.icon}
|
|
||||||
</div>
|
|
||||||
<span className="text-3xl font-black font-sora text-white/10 group-hover:text-white/20 transition-colors">
|
|
||||||
{step.num}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-xl font-bold font-sora text-white">{step.title}</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">{step.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { Users, Flame, Trophy } from "lucide-react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
export default function Leaderboard() {
|
|
||||||
const { profile } = useUser();
|
|
||||||
|
|
||||||
const leaderboardUsers = [
|
|
||||||
{ rank: 1, name: "Zara Cole", level: 6, steps: 14200, points: 285, avatar: "Z" },
|
|
||||||
{ rank: 2, name: "Marcus Lee", level: 5, steps: 12100, points: 240, avatar: "M" },
|
|
||||||
{ rank: 3, name: "Chloe Vane", level: 4, steps: 9400, points: 188, avatar: "C" },
|
|
||||||
{ rank: 4, name: "Alex Stark (You)", level: profile.level, steps: profile.stepsToday, points: Math.round(profile.stepsToday / 15), avatar: "A", highlight: true },
|
|
||||||
{ rank: 5, name: "Devon Shaw", level: 3, steps: 5900, points: 118, avatar: "D" },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-dark relative overflow-hidden">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 sm:p-12 border border-accent-pink/10 space-y-8 shadow-[0_0_50px_rgba(255,46,136,0.02)]">
|
|
||||||
<div className="text-left space-y-4">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-pink/10 border border-accent-pink/20 flex items-center justify-center text-accent-pink">
|
|
||||||
<Trophy className="w-6 h-6 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-accent-pink font-bold text-xs uppercase tracking-wider">Section 14 • Leaderboard</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Daily Ranks & <br />
|
|
||||||
<span className="text-glow-pink text-accent-pink">Community Standings</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">
|
|
||||||
Check out who is logging the most activity today. Walk, play, refer, and climb to the top to unlock premium avatar banners and multiplier rewards.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Leaderboard panel list */}
|
|
||||||
<div className="bg-white/3 border border-white/5 rounded-2xl p-4 sm:p-6 space-y-4">
|
|
||||||
<div className="flex items-center justify-between text-[10px] text-white/40 font-bold uppercase tracking-wider px-3 border-b border-white/5 pb-3">
|
|
||||||
<span>User Roster</span>
|
|
||||||
<div className="flex space-x-12">
|
|
||||||
<span>Steps Logged</span>
|
|
||||||
<span>Est. LYTs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2.5">
|
|
||||||
{leaderboardUsers.map((user) => (
|
|
||||||
<div
|
|
||||||
key={user.rank}
|
|
||||||
className={`flex items-center justify-between p-3 rounded-xl transition-all ${
|
|
||||||
user.highlight
|
|
||||||
? "bg-accent-pink/10 border border-accent-pink/20 shadow-[0_0_15px_rgba(255,46,136,0.08)]"
|
|
||||||
: "bg-white/2 border border-transparent hover:border-white/5"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<span
|
|
||||||
className={`w-6 text-center font-sora font-extrabold text-sm ${
|
|
||||||
user.rank === 1
|
|
||||||
? "text-primary text-glow-yellow"
|
|
||||||
: user.rank === 2
|
|
||||||
? "text-slate-300"
|
|
||||||
: user.rank === 3
|
|
||||||
? "text-amber-600"
|
|
||||||
: "text-white/60"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
#{user.rank}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div className="relative">
|
|
||||||
<div className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center font-bold text-xs text-white">
|
|
||||||
{user.avatar}
|
|
||||||
</div>
|
|
||||||
<span className="absolute -bottom-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-[#181818] text-[8px] font-bold text-white/80 border border-white/10">
|
|
||||||
L{user.level}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<span className="text-xs font-bold text-white block">{user.name}</span>
|
|
||||||
{user.highlight && (
|
|
||||||
<span className="text-[9px] text-accent-pink font-semibold flex items-center gap-1 mt-0.5">
|
|
||||||
<Flame className="w-3 h-3 fill-current animate-pulse" />
|
|
||||||
<span>{profile.streak} days active streak</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-12 text-right">
|
|
||||||
<span className="text-xs font-semibold text-white/80 font-mono">
|
|
||||||
{user.steps.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span className="text-sm font-bold text-primary font-mono">
|
|
||||||
+{user.points}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { Users, Flame, Award, ArrowUpRight, Check, Trophy, Footprints, Landmark } from "lucide-react";
|
|
||||||
|
|
||||||
interface TeamChallenge {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
reward: number;
|
|
||||||
current: number;
|
|
||||||
goal: number;
|
|
||||||
joined: boolean;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LeaderboardSection() {
|
|
||||||
const { profile, addLyts } = useUser();
|
|
||||||
const [challenges, setChallenges] = useState<TeamChallenge[]>([
|
|
||||||
{
|
|
||||||
id: "ch-steps",
|
|
||||||
title: "Weekend Step Sprint",
|
|
||||||
description: "Collectively log 50,000 steps with your team this weekend.",
|
|
||||||
reward: 100,
|
|
||||||
current: 38400,
|
|
||||||
goal: 50000,
|
|
||||||
joined: false,
|
|
||||||
icon: <Footprints className="w-5 h-5 text-accent-lime" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ch-stores",
|
|
||||||
title: "Starbucks Double Drop",
|
|
||||||
description: "Check in at any two Starbucks locations within 7 days.",
|
|
||||||
reward: 40,
|
|
||||||
current: 1,
|
|
||||||
goal: 2,
|
|
||||||
joined: true,
|
|
||||||
icon: <Landmark className="w-5 h-5 text-emerald-400" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ch-ref",
|
|
||||||
title: "Referral Rush",
|
|
||||||
description: "Invite 3 friends to join LytsUp. Earn bonus points.",
|
|
||||||
reward: 150,
|
|
||||||
current: 2,
|
|
||||||
goal: 3,
|
|
||||||
joined: false,
|
|
||||||
icon: <Users className="w-5 h-5 text-accent-pink" />,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const leaderboardUsers = [
|
|
||||||
{ rank: 1, name: "Zara Cole", level: 6, steps: 14200, points: 285, avatar: "Z" },
|
|
||||||
{ rank: 2, name: "Marcus Lee", level: 5, steps: 12100, points: 240, avatar: "M" },
|
|
||||||
{ rank: 3, name: "Chloe Vane", level: 4, steps: 9400, points: 188, avatar: "C" },
|
|
||||||
{ rank: 4, name: "Alex Stark (You)", level: profile.level, steps: profile.stepsToday, points: Math.round(profile.stepsToday / 15), avatar: "A", highlight: true },
|
|
||||||
{ rank: 5, name: "Devon Shaw", level: 3, steps: 5900, points: 118, avatar: "D" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleJoinChallenge = (id: string, reward: number) => {
|
|
||||||
setChallenges((prev) =>
|
|
||||||
prev.map((ch) => {
|
|
||||||
if (ch.id === id) {
|
|
||||||
// If already joined, complete it to give points for the simulation
|
|
||||||
if (ch.joined) {
|
|
||||||
addLyts(reward);
|
|
||||||
return { ...ch, current: ch.goal, completed: true };
|
|
||||||
}
|
|
||||||
return { ...ch, joined: true };
|
|
||||||
}
|
|
||||||
return ch;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="leaderboard" className="py-24 bg-[#090909] relative overflow-hidden">
|
|
||||||
{/* Background ambient lighting */}
|
|
||||||
<div className="absolute top-1/3 left-0 w-80 h-80 rounded-full bg-accent-lime/5 blur-[100px] pointer-events-none" />
|
|
||||||
<div className="absolute bottom-1/3 right-0 w-80 h-80 rounded-full bg-accent-purple/5 blur-[100px] pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
{/* Section Header */}
|
|
||||||
<div className="text-center max-w-3xl mx-auto mb-20 space-y-4">
|
|
||||||
<span className="text-accent-pink font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
Community Hub
|
|
||||||
</span>
|
|
||||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
|
||||||
Play Together. <span className="text-glow-yellow text-primary">Earn Together.</span>
|
|
||||||
</h2>
|
|
||||||
<p className="text-white/60 text-base sm:text-lg">
|
|
||||||
Join squad goals to multiply your LYT earnings, or compete for the top spots on the daily leaderboard.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contents Grid */}
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-start">
|
|
||||||
|
|
||||||
{/* Left Column: Team Challenges */}
|
|
||||||
<div className="space-y-6">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white flex items-center gap-2">
|
|
||||||
<Trophy className="w-6 h-6 text-primary" />
|
|
||||||
<span>Active Squad Challenges</span>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{challenges.map((ch) => {
|
|
||||||
const percentage = Math.min(100, Math.round((ch.current / ch.goal) * 100));
|
|
||||||
const isCompleted = ch.current >= ch.goal;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={ch.id}
|
|
||||||
className={`glassmorphism rounded-3xl p-6 border transition-colors ${
|
|
||||||
ch.joined ? "border-primary/20 bg-primary/2" : "border-white/5"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between items-start gap-4 mb-4">
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center">
|
|
||||||
{ch.icon}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-base font-bold text-white font-sora">{ch.title}</h4>
|
|
||||||
<p className="text-white/60 text-xs mt-0.5 leading-relaxed">{ch.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-right">
|
|
||||||
<span className="text-xs text-white/40 font-bold uppercase tracking-wider block">Reward</span>
|
|
||||||
<span className="text-sm font-extrabold text-primary">+{ch.reward} LYTs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Progress Slider */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-xs font-semibold">
|
|
||||||
<span className="text-white/50">{ch.current.toLocaleString()} / {ch.goal.toLocaleString()}</span>
|
|
||||||
<span className="text-primary">{percentage}%</span>
|
|
||||||
</div>
|
|
||||||
<div className="w-full h-2 bg-white/5 rounded-full overflow-hidden">
|
|
||||||
<div
|
|
||||||
style={{ width: `${percentage}%` }}
|
|
||||||
className="h-full bg-gradient-to-r from-primary to-accent-lime rounded-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Button */}
|
|
||||||
<div className="mt-6 flex justify-end">
|
|
||||||
<button
|
|
||||||
onClick={() => handleJoinChallenge(ch.id, ch.reward)}
|
|
||||||
disabled={isCompleted}
|
|
||||||
className={`px-5 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer ${
|
|
||||||
isCompleted
|
|
||||||
? "bg-accent-lime/10 border border-accent-lime/20 text-accent-lime cursor-not-allowed"
|
|
||||||
: ch.joined
|
|
||||||
? "bg-primary text-dark hover:bg-primary/90"
|
|
||||||
: "bg-white/5 border border-white/10 text-white hover:bg-white/10"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{isCompleted ? (
|
|
||||||
<>
|
|
||||||
<Check className="w-3.5 h-3.5" />
|
|
||||||
<span>Claimed</span>
|
|
||||||
</>
|
|
||||||
) : ch.joined ? (
|
|
||||||
<>
|
|
||||||
<span>Complete & Claim</span>
|
|
||||||
<ArrowUpRight className="w-3.5 h-3.5" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span>Join Challenge</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Column: Leaderboard list */}
|
|
||||||
<div className="space-y-6">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white flex items-center gap-2">
|
|
||||||
<Users className="w-6 h-6 text-accent-pink" />
|
|
||||||
<span>Daily Leaderboard</span>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-6 space-y-4">
|
|
||||||
<div className="flex items-center justify-between text-xs text-white/40 font-bold uppercase tracking-wider px-3 border-b border-white/5 pb-3">
|
|
||||||
<span>User</span>
|
|
||||||
<div className="flex space-x-12">
|
|
||||||
<span>Steps</span>
|
|
||||||
<span>Est. LYTs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
{leaderboardUsers.map((user) => (
|
|
||||||
<div
|
|
||||||
key={user.rank}
|
|
||||||
className={`flex items-center justify-between p-3 rounded-2xl transition-all ${
|
|
||||||
user.highlight
|
|
||||||
? "bg-accent-pink/10 border border-accent-pink/20 shadow-[0_0_15px_rgba(255,46,136,0.1)]"
|
|
||||||
: "bg-white/3 border border-transparent hover:border-white/5"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
{/* Rank badge */}
|
|
||||||
<span
|
|
||||||
className={`w-6 text-center font-sora font-extrabold text-sm ${
|
|
||||||
user.rank === 1
|
|
||||||
? "text-primary text-glow-yellow"
|
|
||||||
: user.rank === 2
|
|
||||||
? "text-slate-300"
|
|
||||||
: user.rank === 3
|
|
||||||
? "text-amber-600"
|
|
||||||
: "text-white/60"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
#{user.rank}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Avatar */}
|
|
||||||
<div className="relative">
|
|
||||||
<div className="w-9 h-9 rounded-full bg-white/10 flex items-center justify-center font-bold text-sm text-white font-sora">
|
|
||||||
{user.avatar}
|
|
||||||
</div>
|
|
||||||
<span className="absolute -bottom-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-[#181818] text-[9px] font-bold text-white/80 border border-white/10">
|
|
||||||
L{user.level}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Profile Details */}
|
|
||||||
<div>
|
|
||||||
<span className="text-sm font-bold text-white block">{user.name}</span>
|
|
||||||
{user.highlight && (
|
|
||||||
<span className="text-[10px] text-accent-pink font-semibold flex items-center gap-1 mt-0.5">
|
|
||||||
<Flame className="w-3 h-3 fill-current animate-pulse" />
|
|
||||||
<span>{profile.streak} days active streak</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-12 text-right">
|
|
||||||
{/* Steps */}
|
|
||||||
<span className="text-xs font-semibold text-white/80 font-mono">
|
|
||||||
{user.steps.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Points */}
|
|
||||||
<span className="text-sm font-bold text-primary font-mono">
|
|
||||||
+{user.points}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import CoinCatcher from "../games/CoinCatcher";
|
|
||||||
|
|
||||||
export default function PlayToEarn() {
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
{/* Renders the fully functional canvas coin-catcher mini-game */}
|
|
||||||
<CoinCatcher />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { Share2, ClipboardCheck, Sparkles } from "lucide-react";
|
|
||||||
|
|
||||||
export default function ReferToEarn() {
|
|
||||||
const { profile, addLyts } = useUser();
|
|
||||||
const [referralCopied, setReferralCopied] = useState(false);
|
|
||||||
const referralCode = `lytsup.com/invite/${profile.name.toLowerCase().replace(/\s+/g, "")}-77`;
|
|
||||||
|
|
||||||
const handleCopyReferral = () => {
|
|
||||||
navigator.clipboard.writeText(referralCode);
|
|
||||||
setReferralCopied(true);
|
|
||||||
addLyts(50); // Give user 50 LYTs bonus for checking/testing referral copy!
|
|
||||||
setTimeout(() => setReferralCopied(false), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-dark relative overflow-hidden">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 sm:p-12 border border-accent-orange/10 shadow-[0_0_50px_rgba(255,107,53,0.05)]">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
|
|
||||||
|
|
||||||
{/* Referral Widget */}
|
|
||||||
<div className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-6 space-y-4 order-2 md:order-1">
|
|
||||||
<div>
|
|
||||||
<label className="text-[9px] font-bold text-white/40 uppercase tracking-widest block mb-2">
|
|
||||||
Your Referral Link:
|
|
||||||
</label>
|
|
||||||
<div className="relative flex">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
readOnly
|
|
||||||
value={referralCode}
|
|
||||||
className="w-full bg-white/3 border border-white/10 rounded-xl px-3 py-3 text-xs text-white/80 font-mono focus:outline-none select-all pr-10"
|
|
||||||
/>
|
|
||||||
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-white/40">
|
|
||||||
<Share2 className="w-4 h-4" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-xs text-white/50 leading-relaxed bg-white/3 p-3.5 rounded-xl border border-white/5">
|
|
||||||
🔥 <span className="text-white font-semibold">Invite Code Bonus:</span> Friends start with <span className="text-primary font-bold">50 LYTs</span> and you get <span className="text-primary font-bold">50 LYTs</span> immediately on activation!
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleCopyReferral}
|
|
||||||
className={`w-full py-3.5 rounded-xl font-extrabold text-xs tracking-wider flex items-center justify-center gap-2 cursor-pointer transition-all ${
|
|
||||||
referralCopied
|
|
||||||
? "bg-accent-lime text-dark shadow-[0_0_20px_rgba(200,255,55,0.3)]"
|
|
||||||
: "bg-white/5 hover:bg-accent-orange hover:text-white text-white border border-white/10 hover:border-accent-orange"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{referralCopied ? (
|
|
||||||
<>
|
|
||||||
<ClipboardCheck className="w-4.5 h-4.5 animate-bounce" />
|
|
||||||
<span>Invite Copied! +50 LYT</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span>Copy Invitation Link</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Information */}
|
|
||||||
<div className="space-y-6 text-left order-1 md:order-2 md:pl-6">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-orange/10 border border-accent-orange/20 flex items-center justify-center text-accent-orange">
|
|
||||||
<Share2 className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-accent-orange font-bold text-xs uppercase tracking-wider">Section 09 • Refer-to-Earn</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Squad Invites & <br />
|
|
||||||
<span className="text-glow-purple text-accent-orange">Growth Loops</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">
|
|
||||||
Earn bonuses by inviting your friends. Create shared earning pools, unlock group level multipliers, and dominate leaderboards together.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { ShoppingBag, Check } from "lucide-react";
|
|
||||||
|
|
||||||
export default function ShopToEarn() {
|
|
||||||
const { addLyts } = useUser();
|
|
||||||
const [selectedItems, setSelectedItems] = useState<string[]>(["coffee"]);
|
|
||||||
const [shopClaimed, setShopClaimed] = useState(false);
|
|
||||||
|
|
||||||
const shopItems = [
|
|
||||||
{ id: "coffee", name: "Starbucks Iced Caramel Macchiato", cost: "$6.20", reward: 12 },
|
|
||||||
{ id: "sneakers", name: "Nike Pegasus Running Shoes", cost: "$130.00", reward: 260 },
|
|
||||||
{ id: "tee", name: "Gymshark Seamless Tee", cost: "$38.00", reward: 76 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const totalReward = shopItems
|
|
||||||
.filter((item) => selectedItems.includes(item.id))
|
|
||||||
.reduce((sum, item) => sum + item.reward, 0);
|
|
||||||
|
|
||||||
const handleShopClaim = () => {
|
|
||||||
if (totalReward > 0) {
|
|
||||||
addLyts(totalReward);
|
|
||||||
setShopClaimed(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setShopClaimed(false);
|
|
||||||
setSelectedItems([]);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-dark relative overflow-hidden">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 sm:p-12 border border-accent-pink/10 shadow-[0_0_50px_rgba(255,46,136,0.05)]">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
|
|
||||||
|
|
||||||
{/* Simulator box */}
|
|
||||||
<div className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-6 space-y-4 order-2 md:order-1">
|
|
||||||
<h4 className="text-[10px] font-bold text-white/40 uppercase tracking-widest">Select items purchased:</h4>
|
|
||||||
<div className="space-y-2.5">
|
|
||||||
{shopItems.map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => {
|
|
||||||
if (shopClaimed) return;
|
|
||||||
setSelectedItems((prev) =>
|
|
||||||
prev.includes(item.id)
|
|
||||||
? prev.filter((id) => id !== item.id)
|
|
||||||
: [...prev, item.id]
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className={`flex items-center justify-between p-3.5 rounded-xl border cursor-pointer transition-all ${
|
|
||||||
selectedItems.includes(item.id)
|
|
||||||
? "bg-accent-pink/5 border-accent-pink/30 text-white"
|
|
||||||
: "bg-white/3 border-white/5 text-white/60 hover:border-white/10"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div
|
|
||||||
className={`w-4 h-4 rounded flex items-center justify-center border transition-all ${
|
|
||||||
selectedItems.includes(item.id)
|
|
||||||
? "bg-accent-pink border-accent-pink"
|
|
||||||
: "border-white/20"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{selectedItems.includes(item.id) && <Check className="w-3.5 h-3.5 text-white" />}
|
|
||||||
</div>
|
|
||||||
<span className="text-xs font-semibold">{item.name}</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<span className="text-xs font-extrabold text-white block">{item.cost}</span>
|
|
||||||
<span className="text-[9px] text-accent-pink font-bold">+{item.reward} LYTs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleShopClaim}
|
|
||||||
disabled={shopClaimed || totalReward === 0}
|
|
||||||
className={`w-full py-3 rounded-xl font-extrabold text-xs tracking-wider flex items-center justify-center gap-2 cursor-pointer transition-all ${
|
|
||||||
shopClaimed
|
|
||||||
? "bg-accent-lime text-dark shadow-[0_0_20px_rgba(200,255,55,0.3)]"
|
|
||||||
: totalReward === 0
|
|
||||||
? "bg-white/5 text-white/20 border border-white/5 cursor-not-allowed"
|
|
||||||
: "bg-white/5 hover:bg-accent-pink hover:text-white text-white border border-white/10 hover:border-accent-pink"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{shopClaimed ? (
|
|
||||||
<>
|
|
||||||
<Check className="w-4 h-4" />
|
|
||||||
<span>Cashback Claimed! +{totalReward} LYTs</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span>Claim Receipt Cashback (+{totalReward} LYTs)</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Information */}
|
|
||||||
<div className="space-y-6 text-left order-1 md:order-2 md:pl-6">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-pink/10 border border-accent-pink/20 flex items-center justify-center text-accent-pink">
|
|
||||||
<ShoppingBag className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-accent-pink font-bold text-xs uppercase tracking-wider">Section 06 • Shop-to-Earn</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Cashback & <br />
|
|
||||||
<span className="text-glow-pink text-accent-pink">Instant Rebates</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">
|
|
||||||
Earn high cashback rates when shopping with retail partners. Submit digital checks or verify loyalty IDs to earn LYT social tokens.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { Heart, MessageCircle, Share, Star } from "lucide-react";
|
|
||||||
|
|
||||||
export default function SocialWall() {
|
|
||||||
const posts = [
|
|
||||||
{
|
|
||||||
user: "@charlie_walks",
|
|
||||||
content: "Just cleared my daily step goal and claimed a $10 Starbucks gift card. Literally paid to walk to class! ☕🚶♂️",
|
|
||||||
likes: 242,
|
|
||||||
comments: 18,
|
|
||||||
brand: "Starbucks",
|
|
||||||
bgGlow: "rgba(16, 185, 129, 0.08)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
user: "@kicks_n_lyts",
|
|
||||||
content: "Redeemed my LYTs for a 20% discount code at Nike. Ripped these Air Max 90s for absolute dirt. Walk simulator goes crazy! 🔥👟",
|
|
||||||
likes: 512,
|
|
||||||
comments: 44,
|
|
||||||
brand: "Nike",
|
|
||||||
bgGlow: "rgba(255, 255, 255, 0.08)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
user: "@gamer_earn",
|
|
||||||
content: "Hit score 75 on the Coin Catcher mini-game, converted it directly and unlocked Spotify Premium. This app is essential. 🎧✨",
|
|
||||||
likes: 189,
|
|
||||||
comments: 12,
|
|
||||||
brand: "Spotify",
|
|
||||||
bgGlow: "rgba(34, 197, 94, 0.08)",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-[#090909] relative overflow-hidden">
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
{/* Header */}
|
|
||||||
<div className="text-center max-w-3xl mx-auto mb-20 space-y-4">
|
|
||||||
<span className="text-accent-purple font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
Gen Z Social Wall
|
|
||||||
</span>
|
|
||||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
|
||||||
Hear it from the <span className="text-transparent bg-clip-text bg-gradient-to-r from-accent-pink via-accent-purple to-accent-lime">Squad</span>
|
|
||||||
</h2>
|
|
||||||
<p className="text-white/60 text-base sm:text-lg">
|
|
||||||
See how real users are converting their daily distance and plays into social currency vouchers.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Social Cards Grid */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
||||||
{posts.map((post, idx) => (
|
|
||||||
<motion.div
|
|
||||||
key={idx}
|
|
||||||
initial={{ opacity: 0, y: 30 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ duration: 0.5, delay: idx * 0.15 }}
|
|
||||||
style={{ boxShadow: `0 10px 30px -15px ${post.bgGlow}` }}
|
|
||||||
className="glassmorphism rounded-3xl p-6 border border-white/5 flex flex-col justify-between hover:border-white/10 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex justify-between items-center border-b border-white/5 pb-3">
|
|
||||||
<span className="font-bold text-sm text-white">{post.user}</span>
|
|
||||||
<span className="text-[10px] font-bold text-accent-lime uppercase bg-white/5 px-2 py-0.5 rounded border border-white/10">
|
|
||||||
Redeemed {post.brand}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-white/80 text-sm leading-relaxed font-medium">
|
|
||||||
"{post.content}"
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Feed Actions Mock */}
|
|
||||||
<div className="flex items-center space-x-6 text-white/40 mt-6 pt-3 border-t border-white/5 text-xs font-semibold">
|
|
||||||
<button className="flex items-center space-x-1.5 hover:text-accent-pink transition-colors">
|
|
||||||
<Heart className="w-4 h-4" />
|
|
||||||
<span>{post.likes}</span>
|
|
||||||
</button>
|
|
||||||
<button className="flex items-center space-x-1.5 hover:text-accent-purple transition-colors">
|
|
||||||
<MessageCircle className="w-4 h-4" />
|
|
||||||
<span>{post.comments}</span>
|
|
||||||
</button>
|
|
||||||
<button className="flex items-center space-x-1.5 hover:text-primary transition-colors ml-auto">
|
|
||||||
<Share className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { Coffee, ShoppingBag, Music, Film, Dumbbell, Ticket, Check, Coins, Copy, ArrowRight, ShieldCheck } from "lucide-react";
|
|
||||||
|
|
||||||
interface BrandVoucher {
|
|
||||||
id: string;
|
|
||||||
brand: string;
|
|
||||||
discount: string;
|
|
||||||
cost: number;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
color: string;
|
|
||||||
bgGlow: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SpendSection() {
|
|
||||||
const { profile, claimVoucher, vouchers } = useUser();
|
|
||||||
const [redeemedCode, setRedeemedCode] = useState<{ brand: string; code: string } | null>(null);
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
||||||
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const availableVouchers: BrandVoucher[] = [
|
|
||||||
{
|
|
||||||
id: "v-sbux",
|
|
||||||
brand: "Starbucks",
|
|
||||||
discount: "$10 Gift Card",
|
|
||||||
cost: 150,
|
|
||||||
icon: <Coffee className="w-8 h-8 text-emerald-400" />,
|
|
||||||
color: "border-emerald-500/20 text-emerald-400 hover:border-emerald-500/50",
|
|
||||||
bgGlow: "rgba(16, 185, 129, 0.15)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "v-nike",
|
|
||||||
brand: "Nike",
|
|
||||||
discount: "20% Discount Code",
|
|
||||||
cost: 300,
|
|
||||||
icon: <ShoppingBag className="w-8 h-8 text-white" />,
|
|
||||||
color: "border-white/10 text-white hover:border-white/30",
|
|
||||||
bgGlow: "rgba(255, 255, 255, 0.15)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "v-spotify",
|
|
||||||
brand: "Spotify",
|
|
||||||
discount: "1 Month Premium",
|
|
||||||
cost: 200,
|
|
||||||
icon: <Music className="w-8 h-8 text-green-400" />,
|
|
||||||
color: "border-green-500/20 text-green-400 hover:border-green-500/50",
|
|
||||||
bgGlow: "rgba(34, 197, 94, 0.15)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "v-netflix",
|
|
||||||
brand: "Netflix",
|
|
||||||
discount: "1 Month Free UltraHD",
|
|
||||||
cost: 450,
|
|
||||||
icon: <Film className="w-8 h-8 text-red-500" />,
|
|
||||||
color: "border-red-500/20 text-red-500 hover:border-red-500/50",
|
|
||||||
bgGlow: "rgba(239, 68, 68, 0.15)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "v-gymshark",
|
|
||||||
brand: "Gymshark",
|
|
||||||
discount: "30% Storewide Voucher",
|
|
||||||
cost: 250,
|
|
||||||
icon: <Dumbbell className="w-8 h-8 text-amber-500" />,
|
|
||||||
color: "border-amber-500/20 text-amber-500 hover:border-amber-500/50",
|
|
||||||
bgGlow: "rgba(245, 158, 11, 0.15)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "v-tickets",
|
|
||||||
brand: "Ticketmaster",
|
|
||||||
discount: "$25 Concert Coupon",
|
|
||||||
cost: 400,
|
|
||||||
icon: <Ticket className="w-8 h-8 text-sky-400" />,
|
|
||||||
color: "border-sky-500/20 text-sky-400 hover:border-sky-500/50",
|
|
||||||
bgGlow: "rgba(56, 189, 248, 0.15)",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleRedeem = (voucher: BrandVoucher) => {
|
|
||||||
if (profile.lytBalance < voucher.cost) {
|
|
||||||
setErrorMessage(`Insufficient LYT balance. You need ${voucher.cost - profile.lytBalance} more LYTs for this voucher.`);
|
|
||||||
setTimeout(() => setErrorMessage(null), 5000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = claimVoucher(voucher.brand, voucher.discount, voucher.cost);
|
|
||||||
if (res.success && res.code) {
|
|
||||||
setRedeemedCode({ brand: voucher.brand, code: res.code });
|
|
||||||
// Clear popup after a while
|
|
||||||
setTimeout(() => setRedeemedCode(null), 10000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyToClipboard = (id: string, text: string) => {
|
|
||||||
navigator.clipboard.writeText(text);
|
|
||||||
setCopiedId(id);
|
|
||||||
setTimeout(() => setCopiedId(null), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="spend" className="py-24 bg-dark relative overflow-hidden">
|
|
||||||
{/* Visual background glows */}
|
|
||||||
<div className="absolute top-1/4 right-0 w-[500px] h-[500px] rounded-full bg-primary/5 blur-[150px] pointer-events-none" />
|
|
||||||
<div className="absolute bottom-1/4 left-0 w-[500px] h-[500px] rounded-full bg-accent-pink/5 blur-[150px] pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
{/* Section Header */}
|
|
||||||
<div className="text-center max-w-3xl mx-auto mb-16 space-y-4">
|
|
||||||
<span className="text-accent-purple font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
Spend Your LYTs
|
|
||||||
</span>
|
|
||||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
|
||||||
Claim Vouchers at <span className="text-transparent bg-clip-text bg-gradient-to-r from-accent-pink via-accent-purple to-accent-lime">Your Favorite Brands</span>
|
|
||||||
</h2>
|
|
||||||
<p className="text-white/60 text-base sm:text-lg">
|
|
||||||
Redeem your accumulated LYT coins for gift cards, subscriptions, and discounts. Click redeem to generate a checkout code.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Dynamic Warning Alert */}
|
|
||||||
{errorMessage && (
|
|
||||||
<div className="max-w-xl mx-auto mb-8 bg-red-950/40 border border-red-500/20 text-red-200 px-5 py-4 rounded-2xl flex items-center justify-between text-sm animate-bounce">
|
|
||||||
<span>{errorMessage}</span>
|
|
||||||
<a href="#play" className="text-primary font-bold underline hover:no-underline">Earn LYTs fast</a>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Success Modal Popup */}
|
|
||||||
{redeemedCode && (
|
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-dark/80 backdrop-blur-sm animate-in fade-in duration-200">
|
|
||||||
<div className="glassmorphism max-w-md w-full p-8 rounded-3xl border border-accent-lime/30 text-center space-y-6 shadow-[0_0_50px_rgba(200,255,55,0.15)]">
|
|
||||||
<div className="w-16 h-16 rounded-full bg-accent-lime/10 border border-accent-lime/30 mx-auto flex items-center justify-center text-accent-lime">
|
|
||||||
<ShieldCheck className="w-8 h-8 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white">Redemption Successful!</h3>
|
|
||||||
<p className="text-white/60 text-sm">
|
|
||||||
You successfully claimed your <span className="text-white font-bold">{redeemedCode.brand}</span> voucher code.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Code Shelf */}
|
|
||||||
<div className="bg-white/5 border border-white/10 rounded-2xl p-4 flex items-center justify-between">
|
|
||||||
<code className="text-xl font-mono font-bold tracking-wider text-accent-lime">{redeemedCode.code}</code>
|
|
||||||
<button
|
|
||||||
onClick={() => copyToClipboard("modal", redeemedCode.code)}
|
|
||||||
className="p-2.5 rounded-xl bg-white/5 hover:bg-white/10 text-white/80 hover:text-white transition-colors cursor-pointer"
|
|
||||||
>
|
|
||||||
{copiedId === "modal" ? <Check className="w-5 h-5 text-accent-lime" /> : <Copy className="w-5 h-5" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setRedeemedCode(null)}
|
|
||||||
className="w-full py-3 rounded-xl bg-primary text-dark font-extrabold text-sm tracking-wider cursor-pointer"
|
|
||||||
>
|
|
||||||
Awesome, Thanks!
|
|
||||||
</button>
|
|
||||||
<span className="text-[10px] text-white/40">Code is also saved to your Active Vouchers panel below.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Voucher Grid */}
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-20">
|
|
||||||
{availableVouchers.map((voucher) => {
|
|
||||||
const isAffordable = profile.lytBalance >= voucher.cost;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={voucher.id}
|
|
||||||
style={{
|
|
||||||
boxShadow: `0 10px 30px -15px ${voucher.bgGlow}`,
|
|
||||||
}}
|
|
||||||
className={`glassmorphism rounded-3xl p-6 flex flex-col justify-between border transition-all duration-300 transform hover:-translate-y-1.5 ${voucher.color}`}
|
|
||||||
>
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Brand Header */}
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<div className="p-3.5 bg-white/5 rounded-2xl border border-white/5 flex items-center justify-center">
|
|
||||||
{voucher.icon}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-1.5 px-3 py-1 rounded-full bg-white/5 border border-white/10 font-sora font-extrabold text-xs text-white">
|
|
||||||
<Coins className="w-3.5 h-3.5 text-primary" />
|
|
||||||
<span>{voucher.cost} LYTs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Info */}
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<h3 className="text-xl font-bold font-sora text-white">{voucher.brand}</h3>
|
|
||||||
<p className="text-white/60 text-sm font-semibold">{voucher.discount}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bottom action */}
|
|
||||||
<div className="mt-8">
|
|
||||||
<button
|
|
||||||
onClick={() => handleRedeem(voucher)}
|
|
||||||
className={`w-full py-3.5 rounded-xl font-extrabold text-xs tracking-wider uppercase transition-all flex items-center justify-center gap-1.5 cursor-pointer ${
|
|
||||||
isAffordable
|
|
||||||
? "bg-white/5 hover:bg-primary hover:text-dark text-white border border-white/10 hover:border-primary shadow-sm"
|
|
||||||
: "bg-white/2 text-white/20 border border-white/5 cursor-not-allowed"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span>Redeem Voucher</span>
|
|
||||||
<ArrowRight className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Claimed Vouchers Display */}
|
|
||||||
{vouchers.length > 0 && (
|
|
||||||
<div className="border-t border-white/5 pt-16">
|
|
||||||
<h3 className="text-2xl font-bold font-sora text-white mb-6 flex items-center gap-2">
|
|
||||||
<span>Your Claimed Vouchers</span>
|
|
||||||
<span className="text-xs px-2.5 py-1 rounded-full bg-accent-lime/10 border border-accent-lime/20 text-accent-lime">
|
|
||||||
{vouchers.length} Total
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
||||||
{vouchers.map((v) => (
|
|
||||||
<div
|
|
||||||
key={v.id}
|
|
||||||
className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-5 flex items-center justify-between gap-4 animate-in slide-in-from-bottom-2 duration-300"
|
|
||||||
>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<span className="text-white font-extrabold text-sm">{v.brand}</span>
|
|
||||||
<p className="text-white/60 text-xs">{v.discount}</p>
|
|
||||||
<span className="text-[10px] text-white/40 block">Claimed on {v.claimedAt}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-right space-y-1.5">
|
|
||||||
<span className="inline-block px-2.5 py-1 bg-white/5 border border-white/10 rounded-lg text-xs font-mono font-extrabold text-accent-lime">
|
|
||||||
{v.code}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => copyToClipboard(v.id, v.code)}
|
|
||||||
className="text-[10px] font-bold text-primary hover:underline flex items-center justify-end gap-1 ml-auto cursor-pointer"
|
|
||||||
>
|
|
||||||
{copiedId === v.id ? (
|
|
||||||
<span className="text-accent-lime">Copied!</span>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Copy className="w-3 h-3" />
|
|
||||||
<span>Copy Code</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import SpendSection from "./SpendSection";
|
|
||||||
|
|
||||||
export default function SpendShop() {
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
{/* Renders the fully functional vouchers shop store catalog */}
|
|
||||||
<SpendSection />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { Users, Footprints, Check, Zap, Landmark } from "lucide-react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
interface TeamChallenge {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
reward: number;
|
|
||||||
current: number;
|
|
||||||
goal: number;
|
|
||||||
joined: boolean;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SquadChallenges() {
|
|
||||||
const { addLyts } = useUser();
|
|
||||||
const [challenges, setChallenges] = useState<TeamChallenge[]>([
|
|
||||||
{
|
|
||||||
id: "ch-steps",
|
|
||||||
title: "Weekend Step Sprint",
|
|
||||||
description: "Collectively log 50,000 steps with your team this weekend.",
|
|
||||||
reward: 100,
|
|
||||||
current: 41200,
|
|
||||||
goal: 50000,
|
|
||||||
joined: false,
|
|
||||||
icon: <Footprints className="w-5 h-5 text-accent-lime" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ch-stores",
|
|
||||||
title: "Starbucks Double Drop",
|
|
||||||
description: "Check in at any two Starbucks locations within 7 days.",
|
|
||||||
reward: 40,
|
|
||||||
current: 1,
|
|
||||||
goal: 2,
|
|
||||||
joined: true,
|
|
||||||
icon: <Landmark className="w-5 h-5 text-emerald-400" />,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleJoinChallenge = (id: string, reward: number) => {
|
|
||||||
setChallenges((prev) =>
|
|
||||||
prev.map((ch) => {
|
|
||||||
if (ch.id === id) {
|
|
||||||
if (ch.joined) {
|
|
||||||
addLyts(reward);
|
|
||||||
return { ...ch, current: ch.goal };
|
|
||||||
}
|
|
||||||
return { ...ch, joined: true };
|
|
||||||
}
|
|
||||||
return ch;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-[#090909] relative overflow-hidden">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 sm:p-12 border border-primary/10 space-y-8">
|
|
||||||
<div className="text-left space-y-4">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary">
|
|
||||||
<Users className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-primary font-bold text-xs uppercase tracking-wider">Section 13 • Squad Goals</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Cooperative Team <br />
|
|
||||||
<span className="text-glow-yellow text-primary">Active Challenges</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">
|
|
||||||
Connect with friends or match with nearby community squads. Log steps, complete visits, and unlock bulk LYT currency bonuses together.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Challenges listing */}
|
|
||||||
<div className="space-y-4 pt-4">
|
|
||||||
{challenges.map((ch) => {
|
|
||||||
const percentage = Math.min(100, Math.round((ch.current / ch.goal) * 100));
|
|
||||||
const isCompleted = ch.current >= ch.goal;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={ch.id}
|
|
||||||
className={`glassmorphism rounded-2xl p-6 border transition-colors ${
|
|
||||||
ch.joined ? "border-primary/20 bg-primary/2" : "border-white/5"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex justify-between items-start gap-4 mb-4">
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center">
|
|
||||||
{ch.icon}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 className="text-sm font-bold text-white font-sora">{ch.title}</h4>
|
|
||||||
<p className="text-white/60 text-xs mt-0.5 leading-relaxed">{ch.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-right">
|
|
||||||
<span className="text-[9px] text-white/40 font-bold uppercase tracking-wider block">Reward</span>
|
|
||||||
<span className="text-xs font-extrabold text-primary">+{ch.reward} LYTs</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between text-xs font-semibold">
|
|
||||||
<span className="text-white/50">{ch.current.toLocaleString()} / {ch.goal.toLocaleString()}</span>
|
|
||||||
<span className="text-primary">{percentage}%</span>
|
|
||||||
</div>
|
|
||||||
<div className="w-full h-1.5 bg-white/5 rounded-full overflow-hidden">
|
|
||||||
<div
|
|
||||||
style={{ width: `${percentage}%` }}
|
|
||||||
className="h-full bg-gradient-to-r from-primary to-accent-lime rounded-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-4 flex justify-end">
|
|
||||||
<button
|
|
||||||
onClick={() => handleJoinChallenge(ch.id, ch.reward)}
|
|
||||||
disabled={isCompleted}
|
|
||||||
className={`px-4 py-2 rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer ${
|
|
||||||
isCompleted
|
|
||||||
? "bg-accent-lime/10 border border-accent-lime/20 text-accent-lime cursor-not-allowed"
|
|
||||||
: ch.joined
|
|
||||||
? "bg-primary text-dark hover:bg-primary/90"
|
|
||||||
: "bg-white/5 border border-white/10 text-white hover:bg-white/10"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{isCompleted ? (
|
|
||||||
<>
|
|
||||||
<Check className="w-3.5 h-3.5" />
|
|
||||||
<span>Claimed</span>
|
|
||||||
</>
|
|
||||||
) : ch.joined ? (
|
|
||||||
<span>Complete & Claim</span>
|
|
||||||
) : (
|
|
||||||
<span>Join Squad Goal</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
interface StorySectionProps {
|
|
||||||
id: string;
|
|
||||||
src: string;
|
|
||||||
alt: string;
|
|
||||||
priority?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function StorySection({ id, src, alt, priority }: StorySectionProps) {
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
id={id}
|
|
||||||
className="relative flex w-full items-center justify-center scroll-mt-0"
|
|
||||||
>
|
|
||||||
<div className="w-full">
|
|
||||||
<Image
|
|
||||||
src={src}
|
|
||||||
alt={alt}
|
|
||||||
width={3000}
|
|
||||||
height={1688}
|
|
||||||
priority={priority}
|
|
||||||
sizes="100vw"
|
|
||||||
className="h-auto w-full select-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { ShieldCheck, Award, Zap, Heart } from "lucide-react";
|
|
||||||
|
|
||||||
export default function ValueProp() {
|
|
||||||
const points = [
|
|
||||||
{
|
|
||||||
title: "Real Brands, Instant Codes",
|
|
||||||
desc: "No points conversions or delays. Claim digital voucher codes instantly at Starbucks, Nike, Netflix, Spotify.",
|
|
||||||
icon: <Award className="w-5 h-5 text-primary" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "100% Free & Transparent",
|
|
||||||
desc: "Zero fees, zero limits. Earn by living your active, healthy life. Your steps and plays are your only capital.",
|
|
||||||
icon: <ShieldCheck className="w-5 h-5 text-accent-lime" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Gen Z Social Ecosystem",
|
|
||||||
desc: "Form squad step goals, battle in real-time catcher leaderboards, and trade vouchers with friends in our app.",
|
|
||||||
icon: <Zap className="w-5 h-5 text-accent-pink" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Health & Well-being Loop",
|
|
||||||
desc: "Earn currency while prioritizing your physical health, mental focus, and social community connections.",
|
|
||||||
icon: <Heart className="w-5 h-5 text-accent-purple" />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-[#090909] relative overflow-hidden">
|
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
{/* Header */}
|
|
||||||
<div className="text-center max-w-3xl mx-auto mb-16 space-y-4">
|
|
||||||
<span className="text-accent-pink font-extrabold text-xs tracking-widest uppercase">
|
|
||||||
Platform Benefits
|
|
||||||
</span>
|
|
||||||
<h2 className="text-3xl sm:text-5xl font-extrabold font-sora text-white leading-tight">
|
|
||||||
Designed for the <span className="text-glow-yellow text-primary">Connected Generation</span>
|
|
||||||
</h2>
|
|
||||||
<p className="text-white/60 text-base sm:text-lg">
|
|
||||||
LytsUp shifts the loyalty points equation. Discover why thousands of users sync daily.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Grid */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
||||||
{points.map((item, idx) => (
|
|
||||||
<motion.div
|
|
||||||
key={idx}
|
|
||||||
initial={{ opacity: 0, x: idx % 2 === 0 ? -30 : 30 }}
|
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
className="glassmorphism rounded-3xl p-8 border border-white/5 flex gap-5 hover:border-white/10 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center shrink-0">
|
|
||||||
{item.icon}
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-lg font-bold font-sora text-white">{item.title}</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">{item.desc}</p>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { MapPin, Sparkles } from "lucide-react";
|
|
||||||
|
|
||||||
export default function VisitToEarn() {
|
|
||||||
const { addLyts } = useUser();
|
|
||||||
const [visitedStores, setVisitedStores] = useState<string[]>([]);
|
|
||||||
const [mapClaimed, setMapClaimed] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const checkinLocations = [
|
|
||||||
{ id: "cafe", name: "Brew & Co Cafe", reward: 8 },
|
|
||||||
{ id: "hm", name: "H&M旗舰店 Retail Store", reward: 15 },
|
|
||||||
{ id: "gym", name: "Core Fitness Gym", reward: 20 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleCheckin = (id: string, name: string, reward: number) => {
|
|
||||||
if (visitedStores.includes(id)) return;
|
|
||||||
setVisitedStores((prev) => [...prev, id]);
|
|
||||||
addLyts(reward);
|
|
||||||
setMapClaimed(name);
|
|
||||||
setTimeout(() => setMapClaimed(null), 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-[#090909] relative overflow-hidden">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 sm:p-12 border border-accent-purple/10 shadow-[0_0_50px_rgba(139,61,255,0.05)]">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
|
|
||||||
|
|
||||||
{/* Information */}
|
|
||||||
<div className="space-y-6 text-left">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-accent-purple/10 border border-accent-purple/20 flex items-center justify-center text-accent-purple">
|
|
||||||
<MapPin className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-accent-purple font-bold text-xs uppercase tracking-wider">Section 08 • Visit-to-Earn</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Location Check-Ins & <br />
|
|
||||||
<span className="text-glow-purple text-accent-purple">GPS Rewards</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">
|
|
||||||
Earn points simply by visiting local spaces. Confirm your location status via GPS check-ins at restaurants, parks, concerts, and malls.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Map Simulator */}
|
|
||||||
<div className="bg-[#0b0b0b] border border-white/5 rounded-2xl p-6 space-y-4 relative overflow-hidden min-h-[220px]">
|
|
||||||
{mapClaimed && (
|
|
||||||
<div className="absolute inset-0 bg-dark/95 flex items-center justify-center z-10 p-4 text-center animate-in fade-in duration-200">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Sparkles className="w-8 h-8 text-accent-purple mx-auto animate-bounce" />
|
|
||||||
<p className="text-sm font-semibold text-white">Checked in at {mapClaimed}!</p>
|
|
||||||
<p className="text-xs text-accent-lime font-bold">LYT reward added directly to wallet</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<h4 className="text-[10px] font-bold text-white/40 uppercase tracking-widest">Active nodes near you:</h4>
|
|
||||||
<div className="space-y-2.5">
|
|
||||||
{checkinLocations.map((loc) => {
|
|
||||||
const visited = visitedStores.includes(loc.id);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={loc.id}
|
|
||||||
className={`flex items-center justify-between p-3 rounded-xl border transition-all ${
|
|
||||||
visited
|
|
||||||
? "bg-white/2 border-white/5 opacity-55"
|
|
||||||
: "bg-white/3 border-white/5 hover:border-accent-purple/35"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<MapPin className={`w-4 h-4 ${visited ? "text-white/40" : "text-accent-purple animate-bounce"}`} />
|
|
||||||
<span className="text-xs font-semibold text-white">{loc.name}</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => handleCheckin(loc.id, loc.name, loc.reward)}
|
|
||||||
disabled={visited}
|
|
||||||
className={`px-3 py-1.5 rounded-lg text-[10px] font-bold transition-all cursor-pointer ${
|
|
||||||
visited
|
|
||||||
? "bg-white/5 text-white/40 cursor-not-allowed"
|
|
||||||
: "bg-accent-purple text-white hover:bg-accent-purple/90"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{visited ? "Visited" : `Check In (+${loc.reward} LYT)`}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useUser } from "@/lib/context";
|
|
||||||
import { Footprints, Check, RefreshCw } from "lucide-react";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
export default function WalkToEarn() {
|
|
||||||
const { profile, addLyts, incrementSteps } = useUser();
|
|
||||||
const [simSteps, setSimSteps] = useState(2500);
|
|
||||||
const [walkClaimed, setWalkClaimed] = useState(false);
|
|
||||||
const walkReward = Math.floor(simSteps / 500); // 1 LYT / 500 steps
|
|
||||||
|
|
||||||
const handleSimulateWalk = () => {
|
|
||||||
incrementSteps(simSteps);
|
|
||||||
addLyts(walkReward);
|
|
||||||
setWalkClaimed(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setWalkClaimed(false);
|
|
||||||
setSimSteps(2500);
|
|
||||||
}, 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-24 bg-[#090909] relative overflow-hidden">
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
|
||||||
|
|
||||||
<div className="glassmorphism rounded-3xl p-8 sm:p-12 border border-primary/10 shadow-[0_0_50px_rgba(255,199,44,0.05)]">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
|
|
||||||
|
|
||||||
{/* Left side: Information */}
|
|
||||||
<div className="space-y-6 text-left">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary">
|
|
||||||
<Footprints className="w-6 h-6 animate-pulse" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<span className="text-primary font-bold text-xs uppercase tracking-wider">Section 05 • Walk-to-Earn</span>
|
|
||||||
<h3 className="text-2xl sm:text-4xl font-extrabold font-sora text-white">
|
|
||||||
Step Sync & <br />
|
|
||||||
<span className="text-glow-yellow text-primary">Points Generation</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-white/60 text-sm leading-relaxed">
|
|
||||||
Go about your day. Next.js integration syncs steps in the background from your phone's Health API. Convert them to tradeable LYTs.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right side: Step Simulator Panel */}
|
|
||||||
<div className="bg-white/3 border border-white/5 rounded-2xl p-6 space-y-6">
|
|
||||||
<div className="flex justify-between items-end">
|
|
||||||
<div>
|
|
||||||
<span className="text-white/40 text-[10px] font-bold tracking-wider uppercase block">Simulate Distance</span>
|
|
||||||
<span className="text-2xl font-extrabold font-sora text-white">
|
|
||||||
+{simSteps.toLocaleString()} <span className="text-xs text-white/40 font-medium">Steps</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<span className="text-white/40 text-[10px] font-bold tracking-wider uppercase block">Rewards Wallet</span>
|
|
||||||
<span className="text-xl font-extrabold text-primary font-sora flex items-center justify-end gap-1">
|
|
||||||
+{walkReward} <span className="text-xs font-semibold text-white/60">LYTs</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Slider */}
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="500"
|
|
||||||
max="10000"
|
|
||||||
step="500"
|
|
||||||
value={simSteps}
|
|
||||||
onChange={(e) => setSimSteps(parseInt(e.target.value))}
|
|
||||||
disabled={walkClaimed}
|
|
||||||
className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-primary"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex justify-between text-[10px] text-white/40 font-bold">
|
|
||||||
<span>500 Steps</span>
|
|
||||||
<span>5,000 Steps</span>
|
|
||||||
<span>10,000 Steps</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleSimulateWalk}
|
|
||||||
disabled={walkClaimed}
|
|
||||||
className={`w-full py-3.5 rounded-xl font-extrabold text-sm tracking-wider flex items-center justify-center gap-2 cursor-pointer transition-all ${
|
|
||||||
walkClaimed
|
|
||||||
? "bg-accent-lime text-dark shadow-[0_0_20px_rgba(200,255,55,0.3)]"
|
|
||||||
: "bg-white/5 hover:bg-primary hover:text-dark text-white border border-white/10 hover:border-primary"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{walkClaimed ? (
|
|
||||||
<>
|
|
||||||
<Check className="w-4 h-4" />
|
|
||||||
<span>Reward Added! +{walkReward} LYTs</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<RefreshCw className="w-4 h-4 animate-spin-slow" />
|
|
||||||
<span>Sync Walk Distance</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,35 @@
|
|||||||
import { Section, Pill, Card, Chip, Heading, Group, Reveal } from "./ui";
|
import {
|
||||||
|
Section,
|
||||||
|
Pill,
|
||||||
|
Card,
|
||||||
|
Chip,
|
||||||
|
Heading,
|
||||||
|
Group,
|
||||||
|
Reveal,
|
||||||
|
SplitWords,
|
||||||
|
Magnetic,
|
||||||
|
Parallax,
|
||||||
|
} from "./ui";
|
||||||
import { C } from "./palette";
|
import { C } from "./palette";
|
||||||
|
|
||||||
/* 01 — INTRO / HERO */
|
/* 01 — INTRO / HERO */
|
||||||
export function Intro() {
|
export function Intro() {
|
||||||
return (
|
return (
|
||||||
<Section id="intro" variant="deep">
|
<Section id="intro" variant="deep" size="epic" width="wide" align="start">
|
||||||
{/* animated ambient glows — soft washes, heavily blurred so no hard edge shows */}
|
{/* animated ambient glows — soft washes, heavily blurred so no hard edge shows */}
|
||||||
<span aria-hidden className="animate-pulse-glow pointer-events-none absolute -left-40 -top-16 -z-0 h-[30rem] w-[30rem] rounded-full bg-[#FF2E88]/15 blur-[150px]" />
|
<span aria-hidden className="animate-pulse-glow pointer-events-none absolute -left-40 -top-16 -z-0 h-[30rem] w-[30rem] rounded-full bg-[#FF2E88]/15 blur-[150px]" />
|
||||||
<span aria-hidden className="animate-float pointer-events-none absolute -bottom-16 left-1/4 -z-0 h-[28rem] w-[28rem] rounded-full bg-[#8B3DFF]/20 blur-[150px]" />
|
<span aria-hidden className="animate-float pointer-events-none absolute -bottom-16 left-1/4 -z-0 h-[28rem] w-[28rem] rounded-full bg-[#8B3DFF]/20 blur-[150px]" />
|
||||||
<span aria-hidden className="animate-float pointer-events-none absolute -right-16 top-1/3 -z-0 h-[26rem] w-[26rem] rounded-full bg-[#C8FF37]/10 blur-[140px]" style={{ animationDelay: "1.5s" }} />
|
<span aria-hidden className="animate-float pointer-events-none absolute -right-16 top-1/3 -z-0 h-[26rem] w-[26rem] rounded-full bg-[#C8FF37]/10 blur-[140px]" style={{ animationDelay: "1.5s" }} />
|
||||||
|
|
||||||
{/* giant drifting watermark */}
|
{/* giant watermark — drifts against the scroll for depth */}
|
||||||
<span className="animate-float pointer-events-none absolute right-0 top-1/2 -z-0 -translate-y-1/2 select-none font-sora text-[28vw] font-extrabold leading-none text-white/[0.06]">
|
<Parallax
|
||||||
LYTS
|
speed={120}
|
||||||
</span>
|
className="pointer-events-none absolute right-0 top-1/2 -z-0 -translate-y-1/2 select-none"
|
||||||
|
>
|
||||||
|
<span className="font-sora text-[28vw] font-extrabold leading-none text-white/[0.06]">
|
||||||
|
LYTS
|
||||||
|
</span>
|
||||||
|
</Parallax>
|
||||||
|
|
||||||
<div className="relative z-10 pt-24 sm:pt-28 lg:pt-32">
|
<div className="relative z-10 pt-24 sm:pt-28 lg:pt-32">
|
||||||
{/* eyebrow badge */}
|
{/* eyebrow badge */}
|
||||||
@@ -40,34 +56,40 @@ export function Intro() {
|
|||||||
|
|
||||||
{/* headline — line-by-line reveal, shimmering last line */}
|
{/* headline — line-by-line reveal, shimmering last line */}
|
||||||
<h1 className="mt-8 font-sora text-6xl font-extrabold leading-[0.88] tracking-tight text-white sm:text-7xl lg:text-8xl">
|
<h1 className="mt-8 font-sora text-6xl font-extrabold leading-[0.88] tracking-tight text-white sm:text-7xl lg:text-8xl">
|
||||||
<Reveal delay={0.14}><span className="block">social</span></Reveal>
|
<span className="block">
|
||||||
<Reveal delay={0.22}><span className="block">currency</span></Reveal>
|
<SplitWords text="social" delay={0.14} />{" "}
|
||||||
<Reveal delay={0.3}>
|
</span>
|
||||||
<span className="block">
|
<span className="block">
|
||||||
for{" "}
|
<SplitWords text="currency" delay={0.24} />{" "}
|
||||||
<span className="animate-shimmer bg-gradient-to-r from-[#FFC72C] via-[#FF2E88] to-[#8B3DFF] bg-[length:200%_auto] bg-clip-text text-transparent">
|
</span>
|
||||||
real life.
|
<span className="block">
|
||||||
</span>
|
<SplitWords text="for" delay={0.34} />{" "}
|
||||||
|
<span className="animate-shimmer bg-gradient-to-r from-[#FFC72C] via-[#FF2E88] to-[#8B3DFF] bg-[length:200%_auto] bg-clip-text text-transparent">
|
||||||
|
<SplitWords text="real life." delay={0.42} />
|
||||||
</span>
|
</span>
|
||||||
</Reveal>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* CTA buttons */}
|
{/* CTA buttons */}
|
||||||
<Reveal delay={0.38}>
|
<Reveal delay={0.38}>
|
||||||
<div className="mt-9 flex flex-col items-start gap-4 sm:flex-row sm:items-center">
|
<div className="mt-9 flex flex-col items-start gap-4 sm:flex-row sm:items-center">
|
||||||
<a
|
<Magnetic>
|
||||||
href="#for-business"
|
<a
|
||||||
className="nb nb-hover inline-flex items-center gap-2 rounded-full bg-[#FFC72C] px-8 py-4 font-sora text-base font-extrabold uppercase tracking-wide text-[#0a0a0a] sm:text-lg"
|
href="#for-business"
|
||||||
>
|
className="nb nb-hover inline-flex items-center gap-2 rounded-full bg-[#FFC72C] px-8 py-4 font-sora text-base font-extrabold uppercase tracking-wide text-[#0a0a0a] sm:text-lg"
|
||||||
Get Started
|
>
|
||||||
<span aria-hidden>→</span>
|
Get Started
|
||||||
</a>
|
<span aria-hidden>→</span>
|
||||||
<a
|
</a>
|
||||||
href="#how-it-works"
|
</Magnetic>
|
||||||
className="nb nb-hover inline-flex items-center gap-2 rounded-full bg-white px-8 py-4 font-sora text-base font-extrabold uppercase tracking-wide text-[#0a0a0a] sm:text-lg"
|
<Magnetic>
|
||||||
>
|
<a
|
||||||
How it works
|
href="#how-it-works"
|
||||||
</a>
|
className="nb nb-hover inline-flex items-center gap-2 rounded-full bg-white px-8 py-4 font-sora text-base font-extrabold uppercase tracking-wide text-[#0a0a0a] sm:text-lg"
|
||||||
|
>
|
||||||
|
How it works
|
||||||
|
</a>
|
||||||
|
</Magnetic>
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
@@ -113,7 +135,7 @@ export function Intro() {
|
|||||||
/* 02 — THE WHY */
|
/* 02 — THE WHY */
|
||||||
export function TheWhy() {
|
export function TheWhy() {
|
||||||
return (
|
return (
|
||||||
<Section id="the-why" variant="cream">
|
<Section id="the-why" variant="cream" size="punch" width="narrow">
|
||||||
<div className="flex flex-col gap-8">
|
<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">💌 hey. quick story before we dive in</Pill>
|
||||||
<div className="grid gap-8 lg:grid-cols-[1.4fr_1fr]">
|
<div className="grid gap-8 lg:grid-cols-[1.4fr_1fr]">
|
||||||
@@ -168,7 +190,7 @@ export function TheProblem() {
|
|||||||
{ n: "03", t: "businesses stuck.", bg: C.lime, tc: "#0a0a0a", items: ["CAC is thru the roof", "Engagement nosedives", "Retention is melting", "Communities = missing", "Zero pre-purchase insight"], foot: "spend $$$ · earn $0." },
|
{ n: "03", t: "businesses stuck.", bg: C.lime, tc: "#0a0a0a", items: ["CAC is thru the roof", "Engagement nosedives", "Retention is melting", "Communities = missing", "Zero pre-purchase insight"], foot: "spend $$$ · earn $0." },
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Section id="the-problem" variant="night">
|
<Section id="the-problem" variant="night" size="full" width="wide">
|
||||||
<Pill>⚡ the problem</Pill>
|
<Pill>⚡ the problem</Pill>
|
||||||
<Heading className="mt-6">
|
<Heading className="mt-6">
|
||||||
traditional loyalty is <span className="strike-pink">broken.</span>
|
traditional loyalty is <span className="strike-pink">broken.</span>
|
||||||
@@ -242,7 +264,7 @@ export function TheShift() {
|
|||||||
/* 05 — MEET LYTs */
|
/* 05 — MEET LYTs */
|
||||||
export function MeetLyts() {
|
export function MeetLyts() {
|
||||||
return (
|
return (
|
||||||
<Section id="lyts" variant="deep">
|
<Section id="lyts" variant="deep" size="normal" width="narrow">
|
||||||
<Pill bg={C.yellow} text="#0a0a0a">✦ introducing</Pill>
|
<Pill bg={C.yellow} text="#0a0a0a">✦ introducing</Pill>
|
||||||
<h2 className="mt-6 font-sora text-8xl font-extrabold tracking-tight text-white">LYTs.</h2>
|
<h2 className="mt-6 font-sora text-8xl font-extrabold tracking-tight text-white">LYTs.</h2>
|
||||||
<Card bg="#fff" className="mt-6 max-w-3xl p-6">
|
<Card bg="#fff" className="mt-6 max-w-3xl p-6">
|
||||||
@@ -277,7 +299,7 @@ export function OneDay() {
|
|||||||
["🌱", "showed up", "for life.", C.purple],
|
["🌱", "showed up", "for life.", C.purple],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="one-day" variant="deep">
|
<Section id="one-day" variant="deep" size="normal">
|
||||||
<Pill bg={C.lime} text="#0a0a0a">💚 one ordinary day</Pill>
|
<Pill bg={C.lime} text="#0a0a0a">💚 one ordinary day</Pill>
|
||||||
<Heading className="mt-6">
|
<Heading className="mt-6">
|
||||||
here is what one ordinary day with <span className="text-[#C8FF37]">LYTs</span> looks like.
|
here is what one ordinary day with <span className="text-[#C8FF37]">LYTs</span> looks like.
|
||||||
@@ -311,7 +333,7 @@ export function Feelings() {
|
|||||||
["💬", "Community", "tribes win", "#0a0a0a", C.lime],
|
["💬", "Community", "tribes win", "#0a0a0a", C.lime],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="feelings" variant="cream">
|
<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">💗 why social currency</Pill>
|
||||||
<Heading className="mt-6">
|
<Heading className="mt-6">
|
||||||
people don't want points. they want <span className="text-[#FF2E88]">feelings.</span>
|
people don't want points. they want <span className="text-[#FF2E88]">feelings.</span>
|
||||||
@@ -339,7 +361,7 @@ export function Feelings() {
|
|||||||
/* 08 — THE DIFFERENCE */
|
/* 08 — THE DIFFERENCE */
|
||||||
export function Difference() {
|
export function Difference() {
|
||||||
return (
|
return (
|
||||||
<Section id="difference" variant="night">
|
<Section id="difference" variant="night" size="full" width="wide" bgImage="/images/background/FD_bg.png">
|
||||||
<Pill>⚡ the fundamental difference</Pill>
|
<Pill>⚡ the fundamental difference</Pill>
|
||||||
<Heading className="mt-6 text-[#FFD21E]">
|
<Heading className="mt-6 text-[#FFD21E]">
|
||||||
loyalty starts at spend.
|
loyalty starts at spend.
|
||||||
@@ -383,7 +405,7 @@ export function TheLoop() {
|
|||||||
["06", "BECOME A LYTIAN", "the community you always wanted. 💞", "#0a0a0a", C.lime],
|
["06", "BECOME A LYTIAN", "the community you always wanted. 💞", "#0a0a0a", C.lime],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="the-loop" variant="night">
|
<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">🚀 your 6-step loop</Pill>
|
||||||
<Heading className="mt-6">
|
<Heading className="mt-6">
|
||||||
six small moments.
|
six small moments.
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
import { Section, Pill, Card, Chip, Heading, Logo, Group, Reveal } from "./ui";
|
import {
|
||||||
|
Section,
|
||||||
|
Pill,
|
||||||
|
Card,
|
||||||
|
Chip,
|
||||||
|
Heading,
|
||||||
|
Logo,
|
||||||
|
Group,
|
||||||
|
Reveal,
|
||||||
|
Marquee,
|
||||||
|
ScrollLitText,
|
||||||
|
} from "./ui";
|
||||||
import { C } from "./palette";
|
import { C } from "./palette";
|
||||||
|
|
||||||
/* 10 — FOR YOU */
|
/* 10 — FOR YOU */
|
||||||
@@ -10,7 +21,7 @@ export function ForYou() {
|
|||||||
["📢", "real recognition", "rewarded for contribution and engagement, not just transactions.", C.purple, "#fff"],
|
["📢", "real recognition", "rewarded for contribution and engagement, not just transactions.", C.purple, "#fff"],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="for-you" variant="cream">
|
<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 className="grid gap-8 lg:grid-cols-[1.3fr_1fr] lg:items-start">
|
||||||
<div>
|
<div>
|
||||||
<Pill>🙋 for you</Pill>
|
<Pill>🙋 for you</Pill>
|
||||||
@@ -61,7 +72,7 @@ export function ForBusiness() {
|
|||||||
["6", "🧠 INSIGHT", "BETTER INTENT SIGNALS", "See them before the receipt.", "#fff", "#0a0a0a"],
|
["6", "🧠 INSIGHT", "BETTER INTENT SIGNALS", "See them before the receipt.", "#fff", "#0a0a0a"],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="for-business" variant="night">
|
<Section id="for-business" variant="night" size="full" width="wide">
|
||||||
<Pill bg={C.yellow} text="#0a0a0a">🤝 for businesses</Pill>
|
<Pill bg={C.yellow} text="#0a0a0a">🤝 for businesses</Pill>
|
||||||
<Heading className="mt-6">
|
<Heading className="mt-6">
|
||||||
acquire earlier.
|
acquire earlier.
|
||||||
@@ -93,7 +104,7 @@ export function WhyNow() {
|
|||||||
["📉", "OLD LOYALTY FADING", "No real bond anymore.", "↓↓", C.pink, "#fff"],
|
["📉", "OLD LOYALTY FADING", "No real bond anymore.", "↓↓", C.pink, "#fff"],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="why-now" variant="deep">
|
<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">⏰ why this matters</Pill>
|
||||||
<Heading className="mt-6">
|
<Heading className="mt-6">
|
||||||
5 currents
|
5 currents
|
||||||
@@ -128,8 +139,10 @@ export function HowItWorks() {
|
|||||||
["🎉", "Event", "show up, get rewarded for the moment", C.pink, "#fff"],
|
["🎉", "Event", "show up, get rewarded for the moment", C.pink, "#fff"],
|
||||||
["🏷️", "Brand", "campaigns + unlock exclusive LYTs", C.orange, "#fff"],
|
["🏷️", "Brand", "campaigns + unlock exclusive LYTs", C.orange, "#fff"],
|
||||||
] as const;
|
] as const;
|
||||||
|
const top = cards.slice(0, 5);
|
||||||
|
const bottom = cards.slice(5);
|
||||||
return (
|
return (
|
||||||
<Section id="how-it-works" variant="cream">
|
<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}>⚡ how it works</Pill>
|
||||||
<h2 className="mt-6 font-sora text-5xl font-extrabold leading-[0.95] tracking-tight sm:text-6xl">
|
<h2 className="mt-6 font-sora text-5xl font-extrabold leading-[0.95] tracking-tight sm:text-6xl">
|
||||||
every action earns points{" "}
|
every action earns points{" "}
|
||||||
@@ -139,24 +152,59 @@ export function HowItWorks() {
|
|||||||
no app-opening rituals. no complicated loyalty rules.{" "}
|
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. 🌱
|
||||||
</p>
|
</p>
|
||||||
<Group className="mt-8 grid gap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-5">
|
|
||||||
{cards.map(([e, t, s, bg, tc]) => (
|
{/* Two counter-scrolling tracks. The opposing directions read as a
|
||||||
<Card key={t} group bg={bg} text={tc} className="relative p-4">
|
machine that never stops running — which is the point of the copy. */}
|
||||||
<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="mt-14 -mx-6 space-y-4 sm:-mx-10 lg:-mx-20">
|
||||||
<div className="text-2xl">{e}</div>
|
<Marquee duration={34}>
|
||||||
<div className="mt-4 font-sora text-lg font-extrabold">{t}</div>
|
{top.map(([e, t, s, bg, tc], i) => (
|
||||||
<div className="text-xs font-bold opacity-80">{s}</div>
|
<ActionTile key={`t${i}`} emoji={e} title={t} sub={s} bg={bg} text={tc} />
|
||||||
</Card>
|
))}
|
||||||
))}
|
</Marquee>
|
||||||
</Group>
|
<Marquee duration={40} reverse>
|
||||||
|
{bottom.map(([e, t, s, bg, tc], i) => (
|
||||||
|
<ActionTile key={`b${i}`} emoji={e} title={t} sub={s} bg={bg} text={tc} />
|
||||||
|
))}
|
||||||
|
</Marquee>
|
||||||
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* One tile on the HowItWorks marquee tracks. Fixed width so the loop reads as
|
||||||
|
an even cadence rather than a ragged queue. */
|
||||||
|
function ActionTile({
|
||||||
|
emoji,
|
||||||
|
title,
|
||||||
|
sub,
|
||||||
|
bg,
|
||||||
|
text,
|
||||||
|
}: {
|
||||||
|
emoji: string;
|
||||||
|
title: string;
|
||||||
|
sub: string;
|
||||||
|
bg: string;
|
||||||
|
text: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="nb relative w-[248px] shrink-0 rounded-2xl p-5"
|
||||||
|
style={{ background: bg, color: text }}
|
||||||
|
>
|
||||||
|
<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="mt-4 font-sora text-lg font-extrabold">{title}</div>
|
||||||
|
<div className="text-xs font-bold opacity-80">{sub}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* 14 — OLD vs NEW */
|
/* 14 — OLD vs NEW */
|
||||||
export function OldVsNew() {
|
export function OldVsNew() {
|
||||||
return (
|
return (
|
||||||
<Section id="old-vs-new" variant="deep">
|
<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">💞 a new kind of loyalty</Pill>
|
||||||
<p className="mt-4 font-bold text-[#C8FF37]">traditional loyalty said one thing.</p>
|
<p className="mt-4 font-bold text-[#C8FF37]">traditional loyalty said one thing.</p>
|
||||||
<Heading className="mt-1">
|
<Heading className="mt-1">
|
||||||
@@ -200,7 +248,7 @@ export function TheVision() {
|
|||||||
["every person can live more", C.purple, "#fff"],
|
["every person can live more", C.purple, "#fff"],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="the-vision" variant="deep">
|
<Section id="the-vision" variant="deep" size="normal">
|
||||||
<div className="grid gap-8 lg:grid-cols-[1.2fr_1fr] lg:items-center">
|
<div className="grid gap-8 lg:grid-cols-[1.2fr_1fr] lg:items-center">
|
||||||
<div>
|
<div>
|
||||||
<Pill bg={C.yellow} text="#0a0a0a">🌈 the vision</Pill>
|
<Pill bg={C.yellow} text="#0a0a0a">🌈 the vision</Pill>
|
||||||
@@ -229,11 +277,20 @@ export function TheVision() {
|
|||||||
/* 16 — OUR BELIEF */
|
/* 16 — OUR BELIEF */
|
||||||
export function OurBelief() {
|
export function OurBelief() {
|
||||||
return (
|
return (
|
||||||
<Section id="our-belief" variant="night">
|
<Section id="our-belief" variant="night" size="normal" width="wide">
|
||||||
<div className="mx-auto flex flex-col items-center text-center">
|
<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}>🌱 our belief</Pill>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-14 space-y-8">
|
|
||||||
|
{/* The thesis, lit word by word as it scrolls past. */}
|
||||||
|
<ScrollLitText
|
||||||
|
className="mx-auto mt-12 max-w-4xl text-center font-sora text-2xl font-extrabold leading-snug tracking-tight sm:text-4xl"
|
||||||
|
text="you already create value every single day. nobody has ever paid you for it."
|
||||||
|
dim="rgba(255,255,255,0.14)"
|
||||||
|
lit={C.lime}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-16 space-y-8">
|
||||||
<div className="flex flex-wrap items-start gap-4">
|
<div className="flex flex-wrap items-start gap-4">
|
||||||
<span className="nb-sm flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-[#0a0a0a] font-sora text-2xl font-extrabold text-white">1</span>
|
<span className="nb-sm flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-[#0a0a0a] font-sora text-2xl font-extrabold text-white">1</span>
|
||||||
<h3 className="font-sora text-4xl font-extrabold leading-[1.05] tracking-tight sm:text-6xl">
|
<h3 className="font-sora text-4xl font-extrabold leading-[1.05] tracking-tight sm:text-6xl">
|
||||||
@@ -264,7 +321,7 @@ export function ThePromise() {
|
|||||||
["💞 LYTIAN.", "#0a0a0a", C.lime],
|
["💞 LYTIAN.", "#0a0a0a", C.lime],
|
||||||
] as const;
|
] as const;
|
||||||
return (
|
return (
|
||||||
<Section id="the-promise" variant="cream">
|
<Section id="the-promise" variant="cream" size="full" width="wide" align="center">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<Pill bg="#0a0a0a" text={C.yellow}>⚡ the promise</Pill>
|
<Pill bg="#0a0a0a" text={C.yellow}>⚡ the promise</Pill>
|
||||||
<Logo />
|
<Logo />
|
||||||
|
|||||||
@@ -1,14 +1,32 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useRef, useSyncExternalStore } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion, Variants } from "framer-motion";
|
import { motion, useSpring, Variants } from "framer-motion";
|
||||||
import { C } from "./palette";
|
import { C } from "./palette";
|
||||||
|
|
||||||
export { C };
|
export { C };
|
||||||
|
|
||||||
const viewport = { once: true, amount: 0.2 } as const;
|
const viewport = { once: true, amount: 0.2 } as const;
|
||||||
|
|
||||||
|
/* Respect the OS "reduce motion" setting — entrances collapse to a plain fade
|
||||||
|
so the page stays usable for anyone who gets motion sickness. */
|
||||||
|
const REDUCE_QUERY = "(prefers-reduced-motion: reduce)";
|
||||||
|
|
||||||
|
function subscribeReduced(onChange: () => void) {
|
||||||
|
const mql = window.matchMedia(REDUCE_QUERY);
|
||||||
|
mql.addEventListener("change", onChange);
|
||||||
|
return () => mql.removeEventListener("change", onChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReducedMotion() {
|
||||||
|
return useSyncExternalStore(
|
||||||
|
subscribeReduced,
|
||||||
|
() => window.matchMedia(REDUCE_QUERY).matches,
|
||||||
|
() => false, // server render: assume motion is fine, corrected on hydrate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* Shared smooth easing — a soft, premium ease-out curve reused across reveals. */
|
/* Shared smooth easing — a soft, premium ease-out curve reused across reveals. */
|
||||||
const smoothEase = [0.22, 1, 0.36, 1] as const;
|
const smoothEase = [0.22, 1, 0.36, 1] as const;
|
||||||
|
|
||||||
@@ -33,9 +51,49 @@ type SectionProps = {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
variant?: "cream" | "deep" | "night" | "plain";
|
variant?: "cream" | "deep" | "night" | "plain";
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** optional full-bleed background image layered over the variant colour */
|
||||||
|
bgImage?: string;
|
||||||
|
/** Vertical weight. Drives the page's pacing — not every beat deserves a
|
||||||
|
full screen. `punch` is a short interstitial, `epic` is an over-tall
|
||||||
|
statement moment. */
|
||||||
|
size?: "punch" | "normal" | "full" | "epic";
|
||||||
|
/** Content width. `bleed` lets a section run edge-to-edge instead of sitting
|
||||||
|
in the same 6xl column as everything else. */
|
||||||
|
width?: "narrow" | "default" | "wide" | "bleed";
|
||||||
|
/** Vertical anchoring of the content within the section. */
|
||||||
|
align?: "center" | "start" | "end";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Section({ id, children, variant = "deep", className = "" }: SectionProps) {
|
const SIZE = {
|
||||||
|
punch: "min-h-[42vh] py-14",
|
||||||
|
normal: "min-h-[70vh] py-16",
|
||||||
|
full: "min-h-[calc(100vh-40px)] py-20",
|
||||||
|
epic: "min-h-[125vh] py-28",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const WIDTH = {
|
||||||
|
narrow: "max-w-3xl",
|
||||||
|
default: "max-w-6xl",
|
||||||
|
wide: "max-w-[1600px]",
|
||||||
|
bleed: "max-w-none",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const ALIGN = {
|
||||||
|
center: "justify-center",
|
||||||
|
start: "justify-start",
|
||||||
|
end: "justify-end",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function Section({
|
||||||
|
id,
|
||||||
|
children,
|
||||||
|
variant = "deep",
|
||||||
|
className = "",
|
||||||
|
bgImage,
|
||||||
|
size = "full",
|
||||||
|
width = "default",
|
||||||
|
align = "center",
|
||||||
|
}: SectionProps) {
|
||||||
const bg =
|
const bg =
|
||||||
variant === "cream"
|
variant === "cream"
|
||||||
? "bg-cream text-[#0a0a0a]"
|
? "bg-cream text-[#0a0a0a]"
|
||||||
@@ -47,9 +105,29 @@ export function Section({ id, children, variant = "deep", className = "" }: Sect
|
|||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id={id}
|
id={id}
|
||||||
className={`relative m-5 flex min-h-[calc(100vh-40px)] flex-col justify-center overflow-hidden rounded-[20px] px-6 py-20 sm:px-10 lg:px-20 ${bg} ${className}`}
|
className={`relative m-5 flex flex-col overflow-hidden rounded-[20px] px-6 sm:px-10 lg:px-20 ${SIZE[size]} ${ALIGN[align]} ${bg} ${className}`}
|
||||||
>
|
>
|
||||||
<div className="mx-auto w-full max-w-6xl">{children}</div>
|
{bgImage && (
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat"
|
||||||
|
style={{ backgroundImage: `url(${bgImage})` }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Scrim — keeps copy legible over the busy artwork. Darkest through the
|
||||||
|
centre column where the text sits, lighter at the edges so the props
|
||||||
|
stay visible. */}
|
||||||
|
{bgImage && (
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute inset-0"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"linear-gradient(90deg, rgba(10,4,20,0.45) 0%, rgba(10,4,20,0.78) 30%, rgba(10,4,20,0.78) 70%, rgba(10,4,20,0.45) 100%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={`relative mx-auto w-full ${WIDTH[width]}`}>{children}</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -79,6 +157,266 @@ export function Reveal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------- useScrollPass -------
|
||||||
|
Calls back with 0..1 as an element travels from entering the bottom of the
|
||||||
|
viewport to leaving the top.
|
||||||
|
|
||||||
|
Deliberately NOT framer-motion's useScroll: this app drives scrolling with
|
||||||
|
Lenis, and useScroll's progress stays pinned at 0 the whole way through
|
||||||
|
(verified — a Parallax on it never left its initial offset). A rAF loop is
|
||||||
|
agnostic to how scroll is driven. Gated on an IntersectionObserver so the
|
||||||
|
loop only spins while the element is actually near the viewport. */
|
||||||
|
function useScrollPass(
|
||||||
|
ref: React.RefObject<HTMLElement | null>,
|
||||||
|
onProgress: (p: number) => void,
|
||||||
|
enabled = true,
|
||||||
|
) {
|
||||||
|
const cb = useRef(onProgress);
|
||||||
|
useEffect(() => {
|
||||||
|
cb.current = onProgress;
|
||||||
|
}, [onProgress]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = ref.current;
|
||||||
|
if (!el || !enabled) return;
|
||||||
|
let frame = 0;
|
||||||
|
let last = -1;
|
||||||
|
|
||||||
|
const paint = () => {
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const span = window.innerHeight + r.height;
|
||||||
|
const p = span > 0 ? (window.innerHeight - r.top) / span : 0;
|
||||||
|
const clamped = Math.min(1, Math.max(0, p));
|
||||||
|
if (clamped !== last) {
|
||||||
|
last = clamped;
|
||||||
|
cb.current(clamped);
|
||||||
|
}
|
||||||
|
frame = requestAnimationFrame(paint);
|
||||||
|
};
|
||||||
|
|
||||||
|
const io = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting && !frame) frame = requestAnimationFrame(paint);
|
||||||
|
else if (!entry.isIntersecting && frame) {
|
||||||
|
cancelAnimationFrame(frame);
|
||||||
|
frame = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ rootMargin: "200px 0px" },
|
||||||
|
);
|
||||||
|
io.observe(el);
|
||||||
|
return () => {
|
||||||
|
io.disconnect();
|
||||||
|
if (frame) cancelAnimationFrame(frame);
|
||||||
|
};
|
||||||
|
}, [ref, enabled]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------- Parallax (scroll-linked drift) -------
|
||||||
|
Unlike Reveal, this stays tied to scroll position the whole time the element
|
||||||
|
is on screen, so the page has depth rather than just fading things in.
|
||||||
|
`speed` is how far it travels, in px, across the full pass. */
|
||||||
|
export function Parallax({
|
||||||
|
children,
|
||||||
|
className = "",
|
||||||
|
speed = 80,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
speed?: number;
|
||||||
|
}) {
|
||||||
|
const outer = useRef<HTMLDivElement>(null);
|
||||||
|
const inner = useRef<HTMLDivElement>(null);
|
||||||
|
const reduced = useReducedMotion();
|
||||||
|
|
||||||
|
useScrollPass(
|
||||||
|
outer,
|
||||||
|
(p) => {
|
||||||
|
const el = inner.current;
|
||||||
|
if (el) el.style.transform = `translate3d(0, ${(0.5 - p) * 2 * speed}px, 0)`;
|
||||||
|
},
|
||||||
|
!reduced,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={outer} className={className}>
|
||||||
|
<div ref={inner} className="will-change-transform">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------- Marquee (continuous horizontal scroll) -------
|
||||||
|
A funky, always-moving band. Doubles its children so the loop is seamless. */
|
||||||
|
export function Marquee({
|
||||||
|
children,
|
||||||
|
className = "",
|
||||||
|
duration = 22,
|
||||||
|
reverse = false,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
duration?: number;
|
||||||
|
reverse?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={`marquee-viewport relative flex overflow-hidden ${className}`}>
|
||||||
|
<div
|
||||||
|
className="marquee-track flex w-max shrink-0 items-center gap-6 pr-6"
|
||||||
|
data-reverse={reverse ? "true" : undefined}
|
||||||
|
style={{ ["--marquee-duration" as string]: `${duration}s` }}
|
||||||
|
>
|
||||||
|
{/* Rendered twice — the second copy is what the loop scrolls into. */}
|
||||||
|
{children}
|
||||||
|
<span aria-hidden className="contents">
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------- SplitWords -------
|
||||||
|
Headline that assembles word by word, each one rising and unblurring from
|
||||||
|
its own mask. The mask is what sells it — words emerge from behind a hard
|
||||||
|
edge rather than just fading. */
|
||||||
|
export function SplitWords({
|
||||||
|
text,
|
||||||
|
className = "",
|
||||||
|
delay = 0,
|
||||||
|
stagger = 0.055,
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
className?: string;
|
||||||
|
delay?: number;
|
||||||
|
stagger?: number;
|
||||||
|
}) {
|
||||||
|
const reduced = useReducedMotion();
|
||||||
|
const words = text.split(" ");
|
||||||
|
return (
|
||||||
|
<span className={className}>
|
||||||
|
{words.map((w, i) => (
|
||||||
|
// Real space between words so the text stays one readable string for
|
||||||
|
// screen readers, copy-paste and crawlers — the mask is presentation.
|
||||||
|
<span key={`${w}-${i}`}>
|
||||||
|
<span className="inline-block overflow-hidden pb-[0.12em] align-bottom">
|
||||||
|
<motion.span
|
||||||
|
className="inline-block will-change-transform"
|
||||||
|
initial={reduced ? { opacity: 0 } : { y: "110%", opacity: 0, rotate: 4 }}
|
||||||
|
whileInView={reduced ? { opacity: 1 } : { y: "0%", opacity: 1, rotate: 0 }}
|
||||||
|
viewport={viewport}
|
||||||
|
transition={{
|
||||||
|
duration: 0.85,
|
||||||
|
ease: smoothEase,
|
||||||
|
delay: delay + i * stagger,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{w}
|
||||||
|
</motion.span>
|
||||||
|
</span>
|
||||||
|
{i < words.length - 1 ? " " : null}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------- ScrollLitText -------
|
||||||
|
Long statement where each word lights up as it passes the middle of the
|
||||||
|
viewport — the copy reads itself as you scroll. */
|
||||||
|
export function ScrollLitText({
|
||||||
|
text,
|
||||||
|
className = "",
|
||||||
|
dim = "rgba(255,255,255,0.16)",
|
||||||
|
lit = "#ffffff",
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
className?: string;
|
||||||
|
dim?: string;
|
||||||
|
lit?: string;
|
||||||
|
}) {
|
||||||
|
const ref = useRef<HTMLParagraphElement>(null);
|
||||||
|
const reduced = useReducedMotion();
|
||||||
|
const words = text.split(" ");
|
||||||
|
|
||||||
|
/* The pass is remapped so the line is fully lit by the time it reaches the
|
||||||
|
middle of the screen, rather than only at the very top. */
|
||||||
|
useScrollPass(
|
||||||
|
ref,
|
||||||
|
(p) => {
|
||||||
|
const el = ref.current;
|
||||||
|
if (!el) return;
|
||||||
|
const spans = el.querySelectorAll<HTMLElement>("[data-lit-word]");
|
||||||
|
const eased = Math.min(1, Math.max(0, (p - 0.15) / 0.45));
|
||||||
|
spans.forEach((s, i) => {
|
||||||
|
const t = Math.min(1, Math.max(0, eased * spans.length - i));
|
||||||
|
s.style.color = t > 0.5 ? lit : dim;
|
||||||
|
s.style.opacity = String(0.35 + t * 0.65);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
!reduced,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p ref={ref} className={className}>
|
||||||
|
{words.map((w, i) => (
|
||||||
|
<span key={`${w}-${i}`}>
|
||||||
|
<span
|
||||||
|
data-lit-word
|
||||||
|
className="inline-block transition-colors duration-200"
|
||||||
|
style={{ color: reduced ? lit : dim }}
|
||||||
|
>
|
||||||
|
{w}
|
||||||
|
</span>
|
||||||
|
{i < words.length - 1 ? " " : null}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------- Magnetic -------
|
||||||
|
Pulls toward the cursor on hover and springs back on leave. Pointer-driven,
|
||||||
|
so it's skipped on touch and under reduced-motion. */
|
||||||
|
export function Magnetic({
|
||||||
|
children,
|
||||||
|
className = "",
|
||||||
|
strength = 0.35,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
strength?: number;
|
||||||
|
}) {
|
||||||
|
const ref = useRef<HTMLSpanElement>(null);
|
||||||
|
const reduced = useReducedMotion();
|
||||||
|
const x = useSpring(0, { stiffness: 260, damping: 18 });
|
||||||
|
const y = useSpring(0, { stiffness: 260, damping: 18 });
|
||||||
|
|
||||||
|
const onMove = (e: React.PointerEvent) => {
|
||||||
|
if (reduced || e.pointerType !== "mouse" || !ref.current) return;
|
||||||
|
const r = ref.current.getBoundingClientRect();
|
||||||
|
x.set((e.clientX - (r.left + r.width / 2)) * strength);
|
||||||
|
y.set((e.clientY - (r.top + r.height / 2)) * strength);
|
||||||
|
};
|
||||||
|
const reset = () => {
|
||||||
|
x.set(0);
|
||||||
|
y.set(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.span
|
||||||
|
ref={ref}
|
||||||
|
className={`inline-block ${className}`}
|
||||||
|
style={{ x, y }}
|
||||||
|
onPointerMove={onMove}
|
||||||
|
onPointerLeave={reset}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------- Group (stagger children that use riseItem) ------- */
|
/* ------- Group (stagger children that use riseItem) ------- */
|
||||||
export function Group({
|
export function Group({
|
||||||
children,
|
children,
|
||||||
|
|||||||
Reference in New Issue
Block a user