// ============================================================
// 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 */}
>
);
}
// ============================================================
// 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 */}
{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 (
);
}
// ============================================================
// 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
{/* 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 */}
{/* Module nodes around perimeter */}
{MODULES.map((m, i) => )}
{/* Center: POS first, then dashboard */}
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);