// waiv-interactive.jsx — playable, scroll-synced rebuilds of HowItWorks, // ShareGrid (→ orbit), Onelynk (→ ripple + switcher) and ForBusiness (count-up). // These override the globals defined in waiv-home-sections.jsx (loaded earlier). (function () { const { useState, useRef, useEffect } = React; const clamp = (v, a, b) => Math.min(b, Math.max(a, v)); const ease = t => t * t * (3 - 2 * t); const mix = (a, b, t) => a + (b - a) * t; // fire `cb` once when the element scrolls into view function useInView(opts = { threshold: 0.3 }) { const ref = useRef(null); const [seen, setSeen] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } }, opts); io.observe(el); return () => io.disconnect(); }, []); return [ref, seen]; } /* ══════════════ HOW IT WORKS — interactive 3-step demo ══════════════ */ function HowItWorks() { const steps = [ { t: 'Tap your waiv to a phone', d: 'Hold the card near the top of any phone. The NFC chip wakes instantly — no app, no scan, no pairing.' }, { t: 'Your Onelynk opens', d: 'A full-screen profile loads in their browser in under a second — photo, links and a save button.' }, { t: 'They keep you for good', d: 'One tap drops your details into their contacts. Edit your profile anytime and every card updates.' }, ]; const [stage, setStage] = useState(0); const secRef = useRef(null); const stageElRef = useRef(null); const runRef = useRef(null); useEffect(() => { const timers = []; let armed = true; const run = () => { timers.forEach(clearTimeout); timers.length = 0; setStage(0); timers.push(setTimeout(() => setStage(1), 1900)); timers.push(setTimeout(() => setStage(2), 3900)); }; runRef.current = run; const check = () => { const el = stageElRef.current; if (!el) return; const r = el.getBoundingClientRect(); const vis = r.top < window.innerHeight * 0.82 && r.bottom > window.innerHeight * 0.18; if (vis && armed) { armed = false; run(); } else if (!vis && r.bottom < 0 || r.top > window.innerHeight) { armed = true; } }; window.addEventListener('scroll', check, { passive: true }); window.addEventListener('resize', check); const id = setTimeout(check, 300); return () => { window.removeEventListener('scroll', check); window.removeEventListener('resize', check); clearTimeout(id); timers.forEach(clearTimeout); }; }, []); const replay = () => runRef.current && runRef.current(); const screenCls = stage === 0 ? 's1' : stage === 1 ? 's2' : 's3 done'; return (
HOW IT WORKS

Three seconds from stranger to saved.

The whole exchange, start to finish — it plays as you scroll in. No app to download, for you or for them.

{steps.map((s, i) => { const done = stage > i, active = stage === i; const w = (active || done) ? 100 : 0; return (
{done ? : i + 1}

{s.t}

{s.d}

); })}
{steps.map((_, i) =>
{/* the tapping card (only beat 1) */}
tap

Maya Okafor

Product Designer · Studio North

Portfolio
Book a coffee
Instagram
Save contact
Saved to contacts in 3 seconds · no app
); } /* ══════════════ SHARE — interactive orbit ══════════════ */ function ShareGrid() { const nodes = [ { ico: 'user', label: 'Contacts', title: 'Your contact card', desc: 'Name, number, email and address drop straight into their phone in a single tap — no typing, no typos.', chips: ['vCard', 'Apple Wallet', 'Google Contacts'] }, { ico: 'share', label: 'Socials', title: 'Every handle, one tap', desc: 'Link all your profiles so people follow you on the platform they actually use — without spelling out usernames.', chips: ['Instagram', 'LinkedIn', 'X', 'TikTok', 'YouTube'] }, { ico: 'card', label: 'Payments', title: 'Get paid on the spot', desc: 'Surface your payment links the moment you meet, so splitting a bill or closing a sale takes seconds.', chips: ['Venmo', 'PayPal', 'Cash App', 'UPI'] }, { ico: 'grid', label: 'Portfolio', title: 'Show your best work', desc: 'Point to your site, store, menu or reel. Anything with a URL becomes a tappable block on your profile.', chips: ['Website', 'Behance', 'Dribbble', 'PDF'] }, { ico: 'calendar', label: 'Booking', title: 'Let them book you', desc: 'Drop your calendar link and turn a handshake into a meeting before either of you leaves the room.', chips: ['Calendly', 'Cal.com', 'Google'] }, { ico: 'wifi', label: 'Extras', title: 'And everything else', desc: 'Guest Wi-Fi, reviews, a tip jar, a waiver, a menu — mix in whatever the moment calls for.', chips: ['Guest Wi-Fi', 'Reviews', 'Menu', 'Docs'] }, ]; const [active, setActive] = useState(0); const R = 38; // radius as % of stage const pos = nodes.map((_, i) => { const ang = (-90 + i * (360 / nodes.length)) * Math.PI / 180; return { x: 50 + R * Math.cos(ang), y: 50 + R * Math.sin(ang) }; }); const a = nodes[active]; return (
ONE TAP, EVERYTHING

Share whatever matters most.

Your waiv is a hub, not a business card. Tap a bubble to see what it can hand off — then mix and match your own.

{pos.map((p, i) => ( ))} {pos.map((p, i) => (
setActive(i)} onClick={() => setActive(i)}>
{nodes[i].label}
))}
waiv
{a.label.toUpperCase()}

{a.title}

{a.desc}

{a.chips.map(c => {c})}
Hover the bubbles →
); } /* ══════════════ ONELYNK — ripple + feature switcher ══════════════ */ function Onelynk() { const feats = [ { id: 'edit', ico: 'edit', t: 'Edit once, update everywhere', p: 'Change a job, a number, a link — every card you ever handed out reflects it instantly.' }, { id: 'analytics', ico: 'chart', t: 'See who you’re connecting with', p: 'Live analytics on taps, views and saved contacts — know exactly which rooms are working.' }, { id: 'noapp', ico: 'bolt', t: 'No app for anyone', p: 'Your profile is just a web link. The person tapping needs nothing installed, ever.' }, ]; const [feat, setFeat] = useState('edit'); const [bursts, setBursts] = useState([]); const onPick = (id) => { setFeat(id); const key = Date.now(); setBursts(b => [...b, key]); setTimeout(() => setBursts(b => b.filter(k => k !== key)), 900); }; const bars = [40, 62, 48, 78, 95, 70, 88]; return (
); } /* ══════════════ FOR BUSINESS — count-up stats ══════════════ */ function CountUp({ to, decimals = 0, suffix = '', dur = 1400 }) { const [ref, seen] = useInView({ threshold: 0.5 }); const [val, setVal] = useState(0); useEffect(() => { if (!seen) return; let raf, start; const step = (now) => { if (!start) start = now; const p = ease(clamp((now - start) / dur, 0, 1)); setVal(to * p); if (p < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [seen]); return {val.toFixed(decimals)}{suffix}; } function ForBusiness() { const cards = [ { ico: 'users', t: 'Equip the whole team', p: 'Order matching cards for everyone and manage every profile from one dashboard.' }, { ico: 'building', t: 'On-brand, every tap', p: 'Lock logos, colours and layout so every profile looks unmistakably yours.' }, { ico: 'refresh', t: 'CRM in real time', p: 'Pipe captured leads straight into Salesforce, HubSpot or a webhook automatically.' }, ]; return (
FOR BUSINESS

Networking that scales with the team.

From two-person studios to thousand-seat sales floors — deploy, brand and measure every connection from one place.

{cards.map((c, i) => (

{c.t}

{c.p}

))}
more contacts kept
professionals on waiv
taps shared
); } Object.assign(window, { HowItWorks, ShareGrid, Onelynk, ForBusiness }); })();