/* Sections — all the page chunks except hero (which lives in app.jsx) */
const { useState, useEffect, useRef } = React;
/* ==== reveal-on-scroll wrapper ==== */
function Reveal({ children, delay = 0, as: Tag = 'div', className = '', ...rest }) {
const ref = useRef(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {if (e.isIntersecting) {setVisible(true);io.disconnect();}});
}, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' });
io.observe(el);
return () => io.disconnect();
}, []);
return (
{children}
);
}
/* ============ NAV ============ */
function Nav() {
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 24);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
);
}
/* ============ PROBLEM ============ */
function ProblemSection() {
const stats = [
{ num: '$23B', label: 'in unused US gift cards' },
{ num: '43%', label: 'of Americans hold at least one' },
{ num: '$244', label: 'average value per person' }];
return (
The problem
The largest dormant balance in your wallet.
{stats.map((s, i) =>
{s.num}
{s.label}
)}
Existing buyers take 8–30% . Most cards never get used. That value belongs to you.
);
}
/* ============ SOLUTION ============ */
function SolutionSection() {
return (
The solution
Every brand. One protocol.
Register cards from any major brand and earn $LOOPG. Keep your card. Get tokens. The protocol does the rest.
300+ brands supported · Priority verification for Tier 1 retailers
);
}
/* ============ HOW IT WORKS ============ */
function StepIcon({ kind }) {
// Abstract geometric icons — not literal gift cards
if (kind === 'register') return (
);
if (kind === 'earn') return (
);
return (
);
}
/* ===== HOW IT WORKS ANIMATED DEMO ===== */
function TypedText({ text, active, speed = 60 }) {
const [shown, setShown] = useState('');
useEffect(() => {
if (!active) {setShown('');return;}
setShown('');
let i = 0;
const t = setInterval(() => {
i++;
setShown(text.slice(0, i));
if (i >= text.length) clearInterval(t);
}, speed);
return () => clearInterval(t);
}, [text, active, speed]);
return {shown}| ;
}
function ScreenRegister({ active }) {
const [success, setSuccess] = useState(false);
useEffect(() => {
if (!active) {setSuccess(false);return;}
const t = setTimeout(() => setSuccess(true), 2200);
return () => clearTimeout(t);
}, [active]);
return (
Register card
Brand
Target
▾
Register your gift card →
Provisional allocation
8,742 $LOOPG
);
}
function ScreenEarn({ active }) {
const [bal, setBal] = useState(8742.00);
useEffect(() => {
if (!active) {setBal(8742.00);return;}
let raf;
const start = performance.now();
const tick = (now) => {
const t = (now - start) / 1000;
setBal(8742.00 + t * 0.85);
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [active]);
const whole = Math.floor(bal).toLocaleString();
const frac = (bal % 1).toFixed(2).slice(2);
return (
$LOOPG balance
{whole}
.{frac}
$LOOPG
+24.50 today
Verified
);
}
function ScreenKeep({ active }) {
return (
Your card
Spend it whenever — at face value.
BALANCE: $87.42 · STATUS: ACTIVE
);
}
function HowItWorksDemo({ activeStep }) {
return (
);
}
function HiwArrows({ activeStep }) {
// Three curved paths converging toward the top of the phone (x=600, y=150 in viewBox).
// Step centers: 200 (left), 600 (mid), 1000 (right).
const paths = [
'M 200,4 C 200,70 360,116 600,150',
'M 600,4 L 600,150',
'M 1000,4 C 1000,70 840,116 600,150'];
return (
{paths.map((d, i) => {
const isActive = activeStep === i;
return (
{/* Glow underlay (active only) */}
{isActive &&
}
{/* Main arrow line */}
{/* Animated signal dot */}
{isActive &&
}
);
})}
);
}
function HowItWorks() {
const steps = [
{ num: '01', icon: 'register', title: 'Register', copy: 'Submit card details. We log the value on-chain. Provisional allocation begins immediately.', meta: 'FACE VALUE · UNCHANGED · YOURS FOREVER' },
{ num: '02', icon: 'earn', title: 'Earn $LOOPG', copy: 'Get tokens proportional to what you registered. Verification tier sets the unlock pace.', meta: 'ON-CHAIN · INSTANT · NO CARD HANDOVER' },
{ num: '03', icon: 'keep', title: 'Keep your card', copy: 'It stays yours. Spend it whenever — at face value, on whatever you actually want.', meta: 'RAYDIUM · SOLANA SPL · 9 DECIMALS' }];
const screens = [ScreenRegister, ScreenEarn, ScreenKeep];
const [activeStep, setActiveStep] = useState(0);
const sectionRef = useRef(null);
useEffect(() => {
let inView = false;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {inView = e.isIntersecting;});
}, { threshold: 0.2 });
if (sectionRef.current) io.observe(sectionRef.current);
const t = setInterval(() => {
if (inView) setActiveStep((s) => (s + 1) % 3);
}, 4000);
return () => {clearInterval(t);io.disconnect();};
}, []);
return (
How it works
Three steps. No surrendered cards.
{steps.map((s, i) =>
setActiveStep(i)}
onMouseEnter={() => setActiveStep(i)}
style={{ cursor: 'pointer' }}>
{s.num}
{s.title}
{s.copy}
{s.meta}
)}
{screens.map((Screen, i) =>
)}
);
}
/* ============ FACTION WARS ============ */
const BRAND_LOGOS = {
'Target': 'assets/logos/target.png',
'Walmart': 'assets/logos/walmart.png',
'Home Depot': 'assets/logos/homedepot.png',
'Starbucks': 'assets/logos/starbucks.png',
'Apple': 'assets/logos/apple.png',
'DoorDash': 'assets/logos/doordash.png'
};
function BrandLogo({ name }) {
const src = BRAND_LOGOS[name];
if (!src) {
return (
{name[0]}
);
}
return (
);
}
function FactionWars() {
const initial = [
{ name: 'Target', val: 1247392 },
{ name: 'Walmart', val: 983104 },
{ name: 'Home Depot', val: 742856 },
{ name: 'Starbucks', val: 621003 },
{ name: 'Apple', val: 418772 },
{ name: 'DoorDash', val: 287440 }];
const max = initial[0].val;
const [values, setValues] = useState(initial.map((b) => ({ ...b, animated: 0 })));
const ref = useRef(null);
const startedRef = useRef(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting && !startedRef.current) {
startedRef.current = true;
// animate bars
requestAnimationFrame(() => {
setValues(initial.map((b) => ({ ...b, animated: b.val })));
});
}
});
}, { threshold: 0.2 });
io.observe(el);
return () => io.disconnect();
}, []);
// Live tick: random small bumps to make it feel live
useEffect(() => {
if (!startedRef.current) return;
const t = setInterval(() => {
setValues((prev) => prev.map((b) => ({
...b,
val: b.val + Math.floor(Math.random() * 80)
})));
}, 2200);
return () => clearInterval(t);
}, []);
return (
Faction wars
Brands ranked by trapped value.
Live · updated {new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}
{values.map((b, i) => {
const pct = Math.min(100, b.animated / max * 100);
return (
{String(i + 1).padStart(2, '0')}
${b.val.toLocaleString()}
);
})}
Help your brand win the Genesis Window. Priority Brands unlock fastest.
);
}
/* ============ TOKEN ============ */
function useInViewOnce(ref, threshold = 0.3) {
const [seen, setSeen] = useState(false);
useEffect(() => {
if (!ref.current || seen) return;
const io = new IntersectionObserver((ents) => {
ents.forEach((e) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } });
}, { threshold });
io.observe(ref.current);
return () => io.disconnect();
}, [seen]);
return seen;
}
function AnimatedNumber({ to, suffix = '', duration = 1800, start }) {
const [val, setVal] = useState(0);
useEffect(() => {
if (!start) { setVal(0); return; }
const t0 = performance.now();
let raf;
const ease = (t) => 1 - Math.pow(1 - t, 4);
const tick = (now) => {
const t = Math.min(1, (now - t0) / duration);
setVal(to * ease(t));
if (t < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [start, to, duration]);
// format: show with one decimal under 10, else integer with commas
const fmt = to >= 1000 ? Math.round(val).toLocaleString() :
to >= 10 ? Math.round(val).toString() :
val.toFixed(1);
return {fmt}{suffix} ;
}
function Donut() {
const segments = [
{ name: 'Claimant Pool', pct: 60, color: '#FF0080' },
{ name: 'Team', pct: 15, color: '#9333EA' },
{ name: 'Treasury', pct: 10, color: '#534BB1' },
{ name: 'Growth', pct: 10, color: '#AB9FF2' },
{ name: 'DEX Float', pct: 5, color: '#3B0764' },
];
const r = 86, c = 2 * Math.PI * r;
const ref = useRef(null);
const seen = useInViewOnce(ref, 0.35);
const [hover, setHover] = useState(-1);
// pre-compute starts in % so we can stagger animation per segment
const starts = [];
let acc = 0;
segments.forEach((s) => { starts.push(acc); acc += s.pct; });
return (
{segments.map((s, i) => {
const fullDash = (s.pct / 100) * c;
const startFrac = starts[i] / 100;
const segDelay = i * 220; // ms
const isHover = hover === i;
const isDim = hover !== -1 && !isHover;
return (
setHover(i)}
onMouseLeave={() => setHover(-1)}
style={{ cursor: 'pointer' }}>
{/* Glow under hover */}
{isHover && (
)}
);
})}
= 0 ? segments[hover].pct : 10} suffix={hover >= 0 ? '%' : 'B'} start={seen} duration={hover >= 0 ? 400 : 1800} />
{hover >= 0 ? segments[hover].name : '$LOOPG · Fixed'}
{segments.map((s, i) => (
setHover(i)}
onMouseLeave={() => setHover(-1)}>
{s.name}
))}
);
}
function TokenSection() {
return (
$LOOPG token
Utility, not speculation.
$LOOPG is the protocol's coordination layer. Holders get queue priority, governance votes,
referral rewards, and access to staking. Solana SPL. Fixed supply, no inflation.
Listing
Raydium · Genesis
See launch economics →
);
}
/* ============ LOOPCHAIN ============ */
function LoopChainSection() {
return (
Built on LoopChain
Settlement infrastructure for atomic value transfer.
LoopG runs on LoopChain — Solana settlement infrastructure delivering instant atomic value transfer at
96.8% lower cost than legacy systems.
Explore LoopChain →
);
}
/* ============ VERIFICATION ============ */
function VerifyIcon({ kind }) {
const s = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.4, strokeLinecap: 'round', strokeLinejoin: 'round' };
if (kind === 'shield') return (
);
if (kind === 'lock') return (
);
if (kind === 'clock') return (
);
// globe
return (
);
}
function VerificationSection() {
const tiers = [
{ tag: 'Tier 0', name: 'Registered', copy: 'Card details submitted. Provisional allocation. Counts in the global counter.', mark: 't0', meter: 33 },
{ tag: 'Tier 1', name: 'Soft Verified', copy: 'Video proof submitted. Sybil-resistance layer activated. Faster unlocks.', mark: 't1', meter: 66 },
{ tag: 'Tier 2', name: 'Hard Verified', copy: 'Protocol verifies card balance directly. Full unlock pace. Top of the queue.', mark: 't2', meter: 100 }];
return (
Verification protocol
Three tiers. Honest allocations.
{[
{ kind: 'shield', tone: 'green', t1: 'Sybil-resistant', t2: 'verification' },
{ kind: 'lock', tone: 'brand', t1: 'Privacy-first', t2: 'by design' },
{ kind: 'clock', tone: 'brand', t1: 'Fair queue', t2: 'execution' },
{ kind: 'globe', tone: 'brand', t1: 'Global counter.', t2: 'Transparent pace.' },
].map((p, i) => (
))}
{tiers.map((t, i) =>
{i}
{t.tag}
{t.name}
{t.copy}
)}
Brand tiers
Cards from Priority Brands (top retailers, large balances)
unlock first. Standard Queue cards unlock as protocol throughput allows. Both earn $LOOPG — pace differs.
);
}
/* ============ STAKE ============ */
function StakeQueueViz() {
return (
{/* Queue lanes */}
{[80, 140, 200, 260].map((y, i) =>
)}
{/* Standard claims (gray) drifting slowly */}
{[
{ y: 80, x: 60, w: 50 }, { y: 80, x: 130, w: 50 }, { y: 80, x: 200, w: 50 },
{ y: 200, x: 100, w: 50 }, { y: 200, x: 170, w: 50 }, { y: 200, x: 240, w: 50 }, { y: 200, x: 310, w: 50 },
{ y: 260, x: 80, w: 50 }, { y: 260, x: 150, w: 50 }, { y: 260, x: 220, w: 50 }, { y: 260, x: 290, w: 50 }].
map((c, i) =>
)}
{/* Staked claims jumping ahead with glow */}
{/* Genesis line */}
Genesis →
{/* Labels */}
Tier 2
Tier 1 + Stake
Tier 1
Tier 0
);
}
function StakeSection() {
return (
Coming soon · Phase 2
Stake-to-Accelerate.
Stake $LOOPG to push specific claims to the front of the verification queue.
Day-one utility for token holders. Position scales with stake size and duration.
);
}
/* ============ ROADMAP ============ */
function RoadmapSection() {
const phases = [
{
num: 'Phase 1', when: 'Live now', status: 'live', title: 'Registry & Genesis',
items: ['Portal & onboarding', 'Claims registry', 'Genesis Window allocations', '$LOOPG launch on Raydium']
},
{
num: 'Phase 2', when: 'Q3 2026', status: 'next', title: 'Verification scale',
items: ['Account abstraction (no seed phrase)', 'Video verification (Tier 1)', 'Stake-to-Accelerate', 'Auto-scrape balance verification']
},
{
num: 'Phase 3', when: 'Beyond', status: 'future', title: 'Network effects',
items: ['Mobile app', 'Processor partnerships', 'Deep LoopChain settlement integration', 'Cross-protocol RWA bridges']
}];
return (
Roadmap
Three phases. Shipping in public.
{phases.map((p, i) =>
{p.num} · {p.when}
{p.status === 'live' && }
{p.status === 'live' ? 'Live' : p.status === 'next' ? 'Next' : 'Future'}
{p.title}
{p.items.map((it) => {it} )}
)}
);
}
/* ============ FAQ ============ */
function FAQ() {
const items = [
{ q: 'Do I lose my card?', a: "No. You keep it. We just log its trapped value on-chain. The card never leaves your wallet — physical or digital." },
{ q: 'Do I get cash?', a: "No. You get $LOOPG tokens. This is a registry, not a redemption service. We're not a gift card buyer." },
{ q: 'When do tokens unlock?', a: "Per protocol schedule. Verification tier (0/1/2) and brand tier (Priority/Standard) determine pace. Higher tier = faster unlock." },
{ q: 'Is $LOOPG a security?', a: "No. It's a utility token providing queue priority, governance voting, fee abatement, and referral rewards. Holders use it to interact with the protocol." },
{ q: 'What if I spend my card before unlocks complete?', a: "Future unlocks pause. Past unlocks remain yours. If you spend down before full unlock, the remaining schedule simply ends." },
{ q: 'How do you verify balances?', a: "Three tiers: self-attestation (provisional), video proof (sybil-resistant), and protocol-side balance scrape (ground truth). Priority Brands run first — they have programmatic balance APIs." }];
const [open, setOpen] = useState(0);
return (
FAQ
Questions, answered honestly.
{items.map((it, i) => {
const isOpen = open === i;
return (
setOpen(isOpen ? -1 : i)}>
{it.q}
);
})}
);
}
/* ============ GENESIS CTA ============ */
function GenesisCTA() {
const [email, setEmail] = useState('');
const [submitted, setSubmitted] = useState(false);
const onSubmit = (e) => {e.preventDefault();if (email.includes('@')) setSubmitted(true);};
return (
Genesis Window
Genesis Window is open .
The first $5M registered earns a
2× allocation multiplier.
{submitted ?
Reserved · check your inbox
:
}
You'll be notified when registration opens. No spam.
);
}
/* ============ FOOTER ============ */
function SocialIcon({ kind }) {
if (kind === 'x') return (
);
if (kind === 'discord') return (
);
return (
);
}
function Footer() {
return (
);
}
Object.assign(window, {
Nav, ProblemSection, SolutionSection, HowItWorks, FactionWars,
TokenSection, LoopChainSection, VerificationSection,
StakeSection, RoadmapSection, FAQ, GenesisCTA, Footer, Reveal
});