// ============================================================ // Kost Hero Motion — 12s loop // One tap fans out into 6 modules, then resolves into a dashboard. // ============================================================ const { Stage, Sprite, useTime, interpolate, Easing } = window; // ---------- helpers ---------- const lerp = (a, b, t) => a + (b - a) * t; const ease = Easing.easeInOutCubic; const easeOut = Easing.easeOutCubic; const iv = (t, input, output, easing = ease) => interpolate(input, output, easing)(t); const clamp01 = (x) => Math.max(0, Math.min(1, x)); // ---------- tokens ---------- const CYAN = '#28B4DC'; const CYAN_BRIGHT = '#5FD1F4'; const CYAN_INK = '#0E7A9C'; const SUCCESS = '#2AA981'; const FONT_DISPLAY = '"SF Pro Display","Inter","Helvetica Neue",sans-serif'; const FONT_TEXT = '"SF Pro Text","Inter","Helvetica Neue",sans-serif'; const FONT_MONO = '"SF Mono","JetBrains Mono",ui-monospace,Menlo,monospace'; // ---------- timeline ---------- const DURATION = 12; const T = { introIn: [0.0, 0.8], // POS phase posIn: [0.6, 1.4], tapRipple: [1.4, 2.6], amountCount: [1.6, 2.4], approved: [2.4, 3.0], // Fan-out phase raysGrow: [3.0, 4.6], // Modules light up sequentially (each 0.45s wide, staggered 0.32s) modules: [ [4.4, 4.85], [4.72, 5.17], [5.04, 5.49], [5.36, 5.81], [5.68, 6.13], [6.0, 6.45], ], // Convergence — rays pull data back to center, dashboard materializes converge: [7.0, 8.4], dashIn: [7.8, 8.8], dashFill: [8.6, 10.4], eligiblePop: [10.0, 10.7], tagline: [9.6, 10.6], outro: [11.4, 12.0], }; // 6 modules — clockwise from top. // Positions are around the center (960, 540) at radius 380. const CENTER = { x: 960, y: 540 }; const RADIUS = 380; const MODULES = [ { id: 'pay', label: 'Payment Processing', sub: 'Tap · Insert · Swipe', chip: 'AUTH · 200ms', angle: -90 }, // top { id: 'rtr', label: 'Real-Time Settlement',sub: 'RTR · Interac · EFT', chip: 'Settled in 12s', angle: -30 }, // top-right { id: 'openbk', label: 'Open Banking', sub: 'OFX · CDR feeds', chip: '6 institutions linked', angle: 30 }, // bottom-right { id: 'cash', label: 'Cashflow Dashboard', sub: 'Live ledger · forecast', chip: '+$47.20 today', angle: 90 }, // bottom { id: 'fee', label: 'Fee Transparency', sub: 'Per-txn breakdown', chip: '$0.34 · 0.72%', angle: 150 }, // bottom-left { id: 'wc', label: 'Working Capital', sub: 'Underwritten on cashflow',chip: 'Eligible $25,000', angle: 210 }, // top-left ]; const polar = (cx, cy, r, deg) => { const rad = (deg * Math.PI) / 180; return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }; }; // ============================================================ // Background — hex grid + ambient glow // ============================================================ function Background({ t }) { const glowPulse = 0.05 + 0.05 * Math.sin(t * 0.7); return ( <> {/* radial glow that follows the action */}
{/* dot grid */} {/* faint concentric circles */} {[160, 260, 380, 500].map(r => ( ))} ); } // ============================================================ // POS Terminal at center (during phases 1–6) // ============================================================ function CenterPOS({ t, opacity, scale }) { const amountP = clamp01(iv(t, T.amountCount, [0, 1], easeOut)); const amount = lerp(0, 47.20, amountP); const showApproved = t >= T.approved[0]; // Tap ripple const inRipple = t > T.tapRipple[0] && t < T.tapRipple[1]; return (
{/* Terminal body */}
{/* Screen */}
Total
Lumiere Coffee · #2847
${amount.toFixed(2)}
{showApproved ? '✓ Approved' : 'Tap to pay'}
{/* Reader area */}
{/* NFC ripples */} {inRipple && [0, 0.5, 1].map((delay, i) => { const localT = t - T.tapRipple[0] - delay * 0.4; if (localT < 0) return null; const cycle = (localT % 1.2) / 1.2; return (
); })}
); } // ============================================================ // A single module card around the perimeter // ============================================================ function ModuleNode({ mod, idx, t }) { const pos = polar(CENTER.x, CENTER.y, RADIUS, mod.angle); const window_ = T.modules[idx]; // Activation: 0 before window, 1 once activated, stays 1 const active = t >= window_[0]; const litP = clamp01(iv(t, window_, [0, 1], easeOut)); // Pre-state: faint placeholder visible after raysGrow begins const preP = clamp01(iv(t, [T.raysGrow[0] + 0.4, T.raysGrow[0] + 1.4], [0, 1], ease)); const baseOpacity = lerp(0, 0.45, preP); const opacity = lerp(baseOpacity, 1, litP); // Outro: fade modules slightly during convergence to draw attention to center const fadeOut = clamp01(iv(t, [T.converge[0] + 0.4, T.converge[1]], [0, 0.55], ease)); const finalOpacity = opacity * (1 - fadeOut); // Final outro const outroFade = clamp01(iv(t, T.outro, [1, 0], ease)); const lit = litP > 0.1; return (
{/* Glow halo when lit */} {lit && (
)}
{/* Top row: index + label */}
{idx + 1}
{mod.label}
{mod.sub}
{/* Chip */}
{mod.chip}
); } // ============================================================ // Rays from center to each module + traveling packets // ============================================================ function Rays({ t }) { const growP = clamp01(iv(t, T.raysGrow, [0, 1], ease)); const fadeOut = clamp01(iv(t, [T.converge[0] + 0.4, T.converge[1]], [0, 0.7], ease)); const outroFade = clamp01(iv(t, T.outro, [1, 0], ease)); return ( {MODULES.map((mod, idx) => { const end = polar(CENTER.x, CENTER.y, RADIUS - 90, mod.angle); // Distance from center to end const dx = end.x - CENTER.x; const dy = end.y - CENTER.y; // Ray grows out: shrink end toward center based on growP const cur = { x: CENTER.x + dx * growP, y: CENTER.y + dy * growP, }; // Module activation packet — fires once, traveling outward const win = T.modules[idx]; const packetT = clamp01(iv(t, [win[0] - 0.4, win[0] + 0.1], [0, 1], easeOut)); const packetVisible = t > win[0] - 0.4 && t < win[0] + 0.2; const px = CENTER.x + dx * packetT; const py = CENTER.y + dy * packetT; // Convergence packet — pulls back to center const convT = clamp01(iv(t, [T.converge[0] + idx * 0.06, T.converge[0] + 0.5 + idx * 0.06], [0, 1], ease)); const convVisible = t > T.converge[0] + idx * 0.06 && t < T.converge[0] + 0.7 + idx * 0.06; const cpx = end.x - dx * convT; const cpy = end.y - dy * convT; const moduleLit = t >= win[0]; const lineOpacity = lerp(0.3, moduleLit ? 0.85 : 0.45, growP) * (1 - fadeOut) * outroFade; return ( {/* Faint ray (always while growing) */} {/* Outward packet on activation */} {packetVisible && ( <> )} {/* Convergence packet */} {convVisible && ( <> )} ); })} {/* Center node halo */} ); } // ============================================================ // Final dashboard — materializes after convergence // ============================================================ function Dashboard({ t }) { const opacity = clamp01(iv(t, T.dashIn, [0, 1], ease)); const scale = lerp(0.92, 1, clamp01(iv(t, T.dashIn, [0, 1], easeOut))); if (opacity <= 0.01) return null; // Animated values const fillP = clamp01(iv(t, T.dashFill, [0, 1], easeOut)); const sales = lerp(0, 4827.40, fillP); const balance = lerp(0, 482902.45, fillP); // Chart progress const chartP = clamp01(iv(t, [T.dashFill[0] + 0.3, T.dashFill[1] + 0.2], [0, 1], ease)); // Eligible pop const eligibleP = clamp01(iv(t, T.eligiblePop, [0, 1], easeOut)); const eligibleScale = lerp(0.7, 1, eligibleP); const outroFade = clamp01(iv(t, T.outro, [1, 0], ease)); return (
{/* Window chrome */}
app.kost.ca · Today Live
{/* Top metrics row */}
{/* Chart */}
Projected cashflow · 14 days
+$8,340 forecast
{/* baseline */}
{/* Settlement row */}
RTR settlement received
+$47.20 · ID #TX-2847 · 12s ago
Settled
{/* Eligible pill */}
$
Eligible for Working Capital
Up to $25,000 · underwritten on your live cashflow
Review
); } function Metric({ label, value, sub, trend }) { return (
{label}
{value}
{sub}
{trend}
); } // ============================================================ // Title card / tagline // ============================================================ function Title({ t }) { // Title only visible during intro phase const opacity = clamp01(iv(t, [0.0, 0.6, T.posIn[1] + 0.2, T.posIn[1] + 0.8], [0, 1, 1, 0], ease)); const y = lerp(12, 0, clamp01(iv(t, [0, 0.6], [0, 1], ease))); if (opacity <= 0.01) return null; return (
The financial operating system
One transaction. Six layers of value.
); } function Tagline({ t }) { const opacity = clamp01(iv(t, T.tagline, [0, 1], ease)) * clamp01(iv(t, T.outro, [1, 0], ease)); const y = lerp(8, 0, clamp01(iv(t, T.tagline, [0, 1], ease))); if (opacity <= 0.01) return null; return (
Not a terminal. An operating system.
kost.ca — built for Canadian SMEs
); } // ============================================================ // Phase indicator (subtle, bottom) // ============================================================ function PhaseLabel({ t }) { const labels = [ { range: [T.posIn[0], T.tapRipple[1]], text: 'Customer taps to pay' }, { range: [T.raysGrow[0], T.modules[0][0]], text: 'One transaction enters the platform' }, { range: [T.modules[0][0], T.modules[5][1]], text: 'Six modules activate in sequence' }, { range: [T.converge[0], T.dashFill[1]], text: 'All data resolves into one dashboard' }, ]; const cur = labels.find(l => t >= l.range[0] && t < l.range[1]); if (!cur) return null; const localT = t - cur.range[0]; const dur = cur.range[1] - cur.range[0]; const opacity = Math.min(1, localT / 0.4) * Math.min(1, (dur - localT) / 0.4); return (
{cur.text}
); } // ============================================================ // Brand mark + progress // ============================================================ function Chrome({ t }) { return ( <>
e.target.style.display='none'}/> KOST
); } // ============================================================ // Root scene // ============================================================ function Scene() { const t = useTime() % DURATION; // POS state const posOpacity = clamp01(iv(t, T.posIn, [0, 1], ease)) * (1 - clamp01(iv(t, [T.converge[0] - 0.2, T.dashIn[0]], [0, 1], ease))); const posScale = lerp(0.85, 1, clamp01(iv(t, T.posIn, [0, 1], easeOut))) * lerp(1, 0.6, clamp01(iv(t, [T.converge[0] - 0.2, T.dashIn[0]], [0, 1], ease))); return (
{/* Rays sit beneath nodes/dashboard so circles overlay */} <Rays t={t}/> {/* Module nodes around perimeter */} {MODULES.map((m, i) => <ModuleNode key={m.id} mod={m} idx={i} t={t}/>)} {/* Center: POS first, then dashboard */} <CenterPOS t={t} opacity={posOpacity} scale={posScale}/> <Dashboard t={t}/> <Tagline t={t}/> <PhaseLabel t={t}/> </div> ); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Stage width={1920} height={1080} fit="contain" duration={DURATION} loop> <Scene/> </Stage> );