[object Object]

← back to Ken

Ken: live-run status chip in dashboard header (motion graphics)

a327393ff82239882cc28079fab23e4d250840d5 · 2026-07-14 10:11:54 -0700 · Steve Abrams

- GET /api/live-run — day X/N, $ spent today vs daily cap, auto-off date, days left
- Layout.jsx status bar: animated LiveRunChip (pulsing glow, shimmer sweep, blinking
  live dot, flowing progress bar, rAF count-up on $ spent). Idle 'PAPER' pill when
  no run armed; full green LIVE chip when active.
- index.css: lrchip motion keyframes (lrGlow/lrShimmer/lrBlink/lrBarFlow)
- also added the chip + endpoint-consumer to the legacy inline dashboard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit a327393ff82239882cc28079fab23e4d250840d5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 14 10:11:54 2026 -0700

    Ken: live-run status chip in dashboard header (motion graphics)
    
    - GET /api/live-run — day X/N, $ spent today vs daily cap, auto-off date, days left
    - Layout.jsx status bar: animated LiveRunChip (pulsing glow, shimmer sweep, blinking
      live dot, flowing progress bar, rAF count-up on $ spent). Idle 'PAPER' pill when
      no run armed; full green LIVE chip when active.
    - index.css: lrchip motion keyframes (lrGlow/lrShimmer/lrBlink/lrBarFlow)
    - also added the chip + endpoint-consumer to the legacy inline dashboard
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 kalshi-dash/server.js      | 128 +++++++++++++++++++++++++++++++++++++++++++++
 kalshi-dash/src/Layout.jsx |  62 +++++++++++++++++++++-
 kalshi-dash/src/index.css  |  26 +++++++++
 3 files changed, 214 insertions(+), 2 deletions(-)

diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index 6370473..384067c 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -710,6 +710,40 @@ const routes = {
     }
   },
 
+  // Live-run status for the header chip (bounded real-money run).
+  'GET /api/live-run': async (req, res) => {
+    try {
+      const risk = await q('SELECT config FROM risk_state ORDER BY updated_at DESC LIMIT 1');
+      const safeMode = risk.rows[0]?.config?.safe_mode ?? true;
+      const lrRow = await kenQ("SELECT value FROM ken_config WHERE key = 'live_run'");
+      const lr = lrRow.rows[0]?.value || null;
+      if (!lr || !lr.active) {
+        return json(res, { active: false, safe_mode: safeMode });
+      }
+      const today = new Date().toISOString().split('T')[0];
+      const spentRow = await kenQ("SELECT COALESCE(SUM(cost_cents),0) t FROM ken_trades WHERE created_at::date = CURRENT_DATE AND action = 'buy'");
+      const spentToday = Number(spentRow.rows[0]?.t || 0);
+      // day index (1-based) within the run window
+      const msDay = 86400000;
+      const dayIdx = Math.min(lr.days, Math.max(1, Math.round((Date.parse(today) - Date.parse(lr.start_date)) / msDay) + 1));
+      const daysLeft = Math.max(0, Math.round((Date.parse(lr.end_date) - Date.parse(today)) / msDay));
+      json(res, {
+        active: true,
+        safe_mode: safeMode,
+        day: dayIdx,
+        days: lr.days,
+        days_left: daysLeft,
+        start_date: lr.start_date,
+        end_date: lr.end_date,
+        daily_budget_cents: lr.daily_budget_cents,
+        spent_today_cents: spentToday,
+        remaining_today_cents: Math.max(0, (lr.daily_budget_cents || 0) - spentToday),
+      });
+    } catch (err) {
+      json(res, { active: false, error: err.message }, 500);
+    }
+  },
+
   // ════════════════════════════════════════
   // KALSHI PROXY (all actions via POST)
   // ════════════════════════════════════════
@@ -2954,6 +2988,34 @@ body{background:#000;overflow:hidden;font-family:'Segoe UI',system-ui,sans-serif
 #c{position:fixed;top:0;left:0;z-index:0}
 #hud{position:fixed;top:0;left:0;right:0;z-index:10;pointer-events:none}
 .top-bar{display:flex;justify-content:center;align-items:center;padding:20px 40px;gap:30px}
+/* ── LIVE-RUN chip (motion graphics) ── */
+.lrchip{position:relative;display:inline-flex;align-items:center;gap:14px;margin:0 auto 20px;padding:11px 20px 11px 16px;border-radius:14px;
+  background:linear-gradient(120deg,rgba(16,185,129,.14),rgba(250,204,21,.10),rgba(16,185,129,.14));background-size:220% 100%;
+  border:1px solid rgba(16,185,129,.45);overflow:hidden;animation:lrGlow 2.4s ease-in-out infinite, lrSlide 6s linear infinite}
+.lrchip::after{content:"";position:absolute;top:0;left:-60%;width:45%;height:100%;
+  background:linear-gradient(100deg,transparent,rgba(255,255,255,.18),transparent);transform:skewX(-18deg);animation:lrShimmer 3.2s ease-in-out infinite}
+.lrchip.paper{background:rgba(148,163,184,.10);border-color:rgba(148,163,184,.28);animation:none}
+.lrchip.paper::after{display:none}
+.lr-live{display:flex;align-items:center;gap:7px;font-weight:800;font-size:12px;letter-spacing:1.5px;color:#34d399}
+.lr-dot{width:9px;height:9px;border-radius:50%;background:#34d399;box-shadow:0 0 0 0 rgba(52,211,153,.7);animation:lrBlink 1.3s ease-out infinite}
+.lrchip.paper .lr-live{color:#94a3b8}
+.lrchip.paper .lr-dot{background:#94a3b8;animation:none;box-shadow:none}
+.lr-day{font-size:12px;font-weight:700;color:var(--text);letter-spacing:.4px}
+.lr-day b{color:#facc15;font-size:15px}
+.lr-sep{width:1px;height:26px;background:rgba(255,255,255,.14)}
+.lr-spend{display:flex;flex-direction:column;gap:4px;min-width:150px}
+.lr-spend .lr-nums{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);letter-spacing:.3px}
+.lr-spend .lr-nums b{color:#34d399;font-variant-numeric:tabular-nums}
+.lr-bar{height:6px;border-radius:6px;background:rgba(255,255,255,.10);overflow:hidden}
+.lr-bar>i{display:block;height:100%;border-radius:6px;background:linear-gradient(90deg,#22c55e,#facc15);background-size:200% 100%;
+  transition:width .9s cubic-bezier(.22,1,.36,1);animation:lrBarFlow 2.5s linear infinite}
+.lr-off{font-size:11px;color:var(--muted);letter-spacing:.3px;text-align:right}
+.lr-off b{color:var(--text)}
+@keyframes lrGlow{0%,100%{box-shadow:0 0 0 rgba(16,185,129,0),0 0 18px rgba(16,185,129,.18)}50%{box-shadow:0 0 22px rgba(16,185,129,.45),0 0 34px rgba(250,204,21,.18)}}
+@keyframes lrSlide{0%{background-position:0% 0}100%{background-position:220% 0}}
+@keyframes lrShimmer{0%{left:-60%}55%,100%{left:130%}}
+@keyframes lrBlink{0%{box-shadow:0 0 0 0 rgba(52,211,153,.7)}70%{box-shadow:0 0 0 8px rgba(52,211,153,0)}100%{box-shadow:0 0 0 0 rgba(52,211,153,0)}}
+@keyframes lrBarFlow{0%{background-position:0 0}100%{background-position:200% 0}}
 .team-badge{display:flex;align-items:center;gap:12px;padding:10px 24px;border-radius:12px;pointer-events:auto;cursor:default}
 .team-badge.alpha{background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.4)}
 .team-badge.beta{background:rgba(244,63,94,.15);border:1px solid rgba(244,63,94,.4)}
@@ -4664,6 +4726,67 @@ function MarketDataTab() {
   </div>);
 }
 
+// rAF count-up tween for a number (smooth motion on value changes)
+function useCountUp(target, dur = 900) {
+  const [val, setVal] = useState(target);
+  const from = useRef(target);
+  useEffect(() => {
+    const start = performance.now(), a = from.current, b = target;
+    let raf;
+    const tick = (now) => {
+      const t = Math.min(1, (now - start) / dur), e = 1 - Math.pow(1 - t, 3);
+      setVal(a + (b - a) * e);
+      if (t < 1) raf = requestAnimationFrame(tick); else from.current = b;
+    };
+    raf = requestAnimationFrame(tick);
+    return () => cancelAnimationFrame(raf);
+  }, [target]);
+  return val;
+}
+
+function LiveRunChip() {
+  const [lr, setLr] = useState(null);
+  useEffect(() => {
+    const load = () => api('/api/live-run').then(setLr).catch(() => {});
+    load();
+    const iv = setInterval(load, 7000);
+    return () => clearInterval(iv);
+  }, []);
+  const spentTarget = lr && lr.active ? lr.spent_today_cents : 0;
+  const spent = useCountUp(spentTarget);
+  if (!lr) return null;
+
+  if (!lr.active) {
+    // Not armed — subtle paper-mode pill so the state is always visible.
+    return (
+      <div className="lrchip paper" title="Real-money trading is OFF (safe/paper). Market data + paper portfolios only.">
+        <span className="lr-live"><span className="lr-dot" /> PAPER MODE</span>
+        <span className="lr-day">no live run armed</span>
+      </div>
+    );
+  }
+
+  const cap = lr.daily_budget_cents || 0;
+  const pct = cap > 0 ? Math.min(100, (lr.spent_today_cents / cap) * 100) : 0;
+  const fmtOff = (() => {
+    const [y, m, dd] = lr.end_date.split('-').map(Number);
+    return new Date(y, m - 1, dd).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
+  })();
+  return (
+    <div className="lrchip" title={'Bounded live run — $'+(cap/100).toFixed(0)+'/day, auto-off after '+lr.end_date}>
+      <span className="lr-live"><span className="lr-dot" /> LIVE</span>
+      <span className="lr-day">DAY <b>{lr.day}</b> / {lr.days}</span>
+      <span className="lr-sep" />
+      <div className="lr-spend">
+        <div className="lr-nums"><span>today</span><span><b>{'$'+(spent/100).toFixed(2)}</b> / {'$'+(cap/100).toFixed(0)}</span></div>
+        <div className="lr-bar"><i style={{ width: pct + '%' }} /></div>
+      </div>
+      <span className="lr-sep" />
+      <div className="lr-off">auto-off<br/><b>{fmtOff}</b> · {lr.days_left}d left</div>
+    </div>
+  );
+}
+
 function App() {
   const [data, setData] = useState(null);
   const [expandedAgent, setExpandedAgent] = useState(null);
@@ -4713,6 +4836,10 @@ function App() {
         <div className="ceo-tagline">Trading Intelligence Corporation</div>
       </div>
 
+      <div style={{display:'flex',justifyContent:'center',marginBottom:8}}>
+        <LiveRunChip />
+      </div>
+
       <div style={{display:'flex',justifyContent:'center',gap:8,marginBottom:24,flexWrap:'wrap'}}>
         <button style={tabStyle('command')} onClick={()=>setTab('command')}>Command Center</button>
         <button style={tabStyle('market')} onClick={()=>setTab('market')}>Market Data</button>
@@ -4790,6 +4917,7 @@ const server = http.createServer(async (req, res) => {
     '/api/auth/google',
     '/api/auth/google-config',
     '/api/health',
+    '/api/live-run',
     '/api/portfauxlio',
     '/api/portfauxlio/charts',
     '/api/portfauxlio/swot',
diff --git a/kalshi-dash/src/Layout.jsx b/kalshi-dash/src/Layout.jsx
index 81c76f4..a9250dd 100644
--- a/kalshi-dash/src/Layout.jsx
+++ b/kalshi-dash/src/Layout.jsx
@@ -1,8 +1,60 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
 import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom';
 import { api, checkSession, logout } from './api';
 import TickerTape from './components/TickerTape';
 
+// rAF count-up tween so the $ spent value animates on change (motion graphics).
+function useCountUp(target, dur = 900) {
+  const [val, setVal] = useState(target);
+  const from = useRef(target);
+  useEffect(() => {
+    const start = performance.now(), a = from.current, b = target;
+    let raf;
+    const tick = (now) => {
+      const t = Math.min(1, (now - start) / dur), e = 1 - Math.pow(1 - t, 3);
+      setVal(a + (b - a) * e);
+      if (t < 1) raf = requestAnimationFrame(tick); else from.current = b;
+    };
+    raf = requestAnimationFrame(tick);
+    return () => cancelAnimationFrame(raf);
+  }, [target]);
+  return val;
+}
+
+// Header status-bar chip for the bounded real-money run.
+function LiveRunChip({ lr }) {
+  const spent = useCountUp(lr && lr.active ? lr.spent_today_cents : 0);
+  if (!lr) return null;
+
+  if (!lr.active) {
+    return (
+      <span className="lrchip idle" title="No bounded live run armed — safe/paper (market data + paper portfolios only).">
+        <span className="lr-dot" /> <span className="lr-live">PAPER</span>
+        <span className="lr-day" style={{ opacity: .8 }}>run idle</span>
+      </span>
+    );
+  }
+
+  const cap = lr.daily_budget_cents || 0;
+  const pct = cap > 0 ? Math.min(100, (lr.spent_today_cents / cap) * 100) : 0;
+  const off = (() => {
+    const [y, m, d] = String(lr.end_date).split('-').map(Number);
+    return new Date(y, m - 1, d).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
+  })();
+  return (
+    <span className="lrchip" title={'Bounded live run — $' + (cap / 100).toFixed(0) + '/day, auto-off after ' + lr.end_date}>
+      <span className="lr-dot" /> <span className="lr-live">LIVE</span>
+      <span className="lr-pipe">·</span>
+      <span className="lr-day">DAY <b>{lr.day}</b>/{lr.days}</span>
+      <span className="lr-pipe">·</span>
+      <span className="lr-amt"><b>{'$' + (spent / 100).toFixed(2)}</b>/{'$' + (cap / 100).toFixed(0)}</span>
+      <span className="lr-bar"><i style={{ width: pct + '%' }} /></span>
+      <span className="lr-pipe">·</span>
+      <span className="lr-off">off <b>{off}</b> · {lr.days_left}d</span>
+    </span>
+  );
+}
+
 const NAV = [
   { to: '/', label: 'Dashboard', d: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' },
   { to: '/markets', label: 'Markets', d: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6' },
@@ -44,6 +96,7 @@ export default function Layout() {
   const loc = useLocation();
   const nav = useNavigate();
   const [h, setH] = useState(null);
+  const [lr, setLr] = useState(null);
   const [col, setCol] = useState(false);
   const [authChecked, setAuthChecked] = useState(false);
 
@@ -51,7 +104,10 @@ export default function Layout() {
     checkSession().then(ok => {
       if (!ok) { nav('/login'); return; }
       setAuthChecked(true);
-      const load = () => api('/health').then(setH).catch(() => {});
+      const load = () => {
+        api('/health').then(setH).catch(() => {});
+        api('/live-run').then(setLr).catch(() => {});
+      };
       load();
       const i = setInterval(load, 15000);
       return () => clearInterval(i);
@@ -327,6 +383,8 @@ export default function Layout() {
               <span style={{ color: 'var(--orange)', fontWeight: 700 }}>SAFE MODE ON</span>
             </>
           )}
+          <span style={{ color: 'var(--border-light)' }}>|</span>
+          <LiveRunChip lr={lr} />
           <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 12 }}>
             <span style={{ color: 'var(--border-light)' }}>
               {new Date().toLocaleTimeString('en-US', { timeZone: 'America/Los_Angeles', hour: '2-digit', minute: '2-digit', second: '2-digit' })} PT
diff --git a/kalshi-dash/src/index.css b/kalshi-dash/src/index.css
index 0aea79d..0670379 100644
--- a/kalshi-dash/src/index.css
+++ b/kalshi-dash/src/index.css
@@ -911,3 +911,29 @@ tbody tr:nth-child(even):hover td {
     grid-template-columns: 1fr;
   }
 }
+
+/* ── LIVE-RUN chip (bounded real-money run) — motion graphics ── */
+.lrchip{position:relative;display:inline-flex;align-items:center;gap:10px;padding:4px 12px;border-radius:9px;overflow:hidden;
+  font-size:11px;letter-spacing:.04em;
+  background:linear-gradient(120deg,rgba(16,185,129,.16),rgba(250,204,21,.10),rgba(16,185,129,.16));background-size:220% 100%;
+  border:1px solid rgba(16,185,129,.5);animation:lrGlow 2.4s ease-in-out infinite,lrSlide 6s linear infinite}
+.lrchip::after{content:"";position:absolute;top:0;left:-60%;width:45%;height:100%;
+  background:linear-gradient(100deg,transparent,rgba(255,255,255,.20),transparent);transform:skewX(-18deg);animation:lrShimmer 3.2s ease-in-out infinite}
+.lrchip.idle{background:rgba(148,163,184,.12);border-color:rgba(148,163,184,.30);animation:none}
+.lrchip.idle::after{display:none}
+.lrchip .lr-dot{width:8px;height:8px;border-radius:50%;background:#34d399;box-shadow:0 0 0 0 rgba(52,211,153,.7);animation:lrBlink 1.3s ease-out infinite}
+.lrchip.idle .lr-dot{background:#94a3b8;animation:none;box-shadow:none}
+.lrchip .lr-live{font-weight:800;color:#34d399}
+.lrchip.idle .lr-live{color:#94a3b8}
+.lrchip .lr-day b{color:#facc15;font-weight:800}
+.lrchip .lr-pipe{color:rgba(255,255,255,.18)}
+.lrchip .lr-bar{width:70px;height:5px;border-radius:5px;background:rgba(255,255,255,.12);overflow:hidden;display:inline-block;vertical-align:middle}
+.lrchip .lr-bar>i{display:block;height:100%;border-radius:5px;background:linear-gradient(90deg,#22c55e,#facc15);background-size:200% 100%;
+  transition:width .9s cubic-bezier(.22,1,.36,1);animation:lrBarFlow 2.5s linear infinite}
+.lrchip .lr-amt b{color:#34d399;font-variant-numeric:tabular-nums}
+.lrchip .lr-off b{color:var(--text,#e5e7eb)}
+@keyframes lrGlow{0%,100%{box-shadow:0 0 0 rgba(16,185,129,0),0 0 14px rgba(16,185,129,.16)}50%{box-shadow:0 0 20px rgba(16,185,129,.42),0 0 30px rgba(250,204,21,.16)}}
+@keyframes lrSlide{0%{background-position:0 0}100%{background-position:220% 0}}
+@keyframes lrShimmer{0%{left:-60%}55%,100%{left:130%}}
+@keyframes lrBlink{0%{box-shadow:0 0 0 0 rgba(52,211,153,.7)}70%{box-shadow:0 0 0 7px rgba(52,211,153,0)}100%{box-shadow:0 0 0 0 rgba(52,211,153,0)}}
+@keyframes lrBarFlow{0%{background-position:0 0}100%{background-position:200% 0}}

← 1bdf579 Ken: bounded live-run controls — $/day spend cap + auto-off  ·  back to Ken  ·  Ken: fix Weather page 400 — server now handles 'get_data' ac 16c240e →