[object Object]

← back to AbramsEgo

scoreboard: at-a-glance game-state hero panel (self-funding, wins, canary record, fleet, sessions)

27e1f69867f6f34686d2ba64236ac34ecc61a11c · 2026-07-08 09:02:27 -0700 · Steve

Files touched

Diff

commit 27e1f69867f6f34686d2ba64236ac34ecc61a11c
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 8 09:02:27 2026 -0700

    scoreboard: at-a-glance game-state hero panel (self-funding, wins, canary record, fleet, sessions)
---
 public/index.html | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/public/index.html b/public/index.html
index 2bba8ce..f095d1d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -44,6 +44,26 @@
   .span12{grid-column:span 12}.span8{grid-column:span 8}.span7{grid-column:span 7}.span6{grid-column:span 6}
   .span5{grid-column:span 5}.span4{grid-column:span 4}.span3{grid-column:span 3}
   @media(max-width:1000px){.span8,.span7,.span6,.span5,.span4,.span3{grid-column:span 12}}
+  /* ---- SCOREBOARD (the at-a-glance game-state hero) ---- */
+  .scoreboard{background:linear-gradient(135deg,#0e1826,#0a1018 60%,#0d141d);
+    border:1px solid #263a52;box-shadow:0 0 0 1px #4d9fff10, inset 0 0 60px #4d9fff08}
+  .sb-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px}
+  .sb-status{font-size:13px;font-weight:700;letter-spacing:2px;text-transform:uppercase;
+    padding:5px 14px;border-radius:8px;border:1px solid var(--line)}
+  .sb-status.win{color:var(--good);border-color:#3fd18b55;background:#3fd18b12;text-shadow:0 0 14px #3fd18b55}
+  .sb-status.hold{color:var(--warn);border-color:#ffb45455;background:#ffb45412;text-shadow:0 0 14px #ffb45455}
+  .sb-status.down{color:var(--bad);border-color:#ff5d6c55;background:#ff5d6c12;text-shadow:0 0 14px #ff5d6c55}
+  .sb-clock{margin-left:auto;font-size:11px;color:var(--dim)}
+  .sb-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
+  .sb-tile{background:#0b1421;border:1px solid var(--line);border-radius:12px;padding:14px 16px;position:relative;overflow:hidden}
+  .sb-tile::after{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--dim);opacity:.6}
+  .sb-tile.good::after{background:var(--good)} .sb-tile.warn::after{background:var(--warn)}
+  .sb-tile.bad::after{background:var(--bad)} .sb-tile.gold::after{background:var(--gold)} .sb-tile.accent::after{background:var(--accent)}
+  .sb-tile .sk{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:1px}
+  .sb-tile .sv{font-size:38px;font-weight:800;line-height:1.05;margin-top:4px;font-variant-numeric:tabular-nums}
+  .sb-tile .su{font-size:15px;font-weight:600;color:var(--dim);margin-left:4px}
+  .sb-tile .ss{font-size:10.5px;color:var(--dim);margin-top:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+  .sb-tile.hero .sv{font-size:44px}
   .big{font-size:30px;font-weight:700}
   .good{color:var(--good)}.warn{color:var(--warn)}.bad{color:var(--bad)}.gold{color:var(--gold)}.dim{color:var(--dim)}
   .row{display:flex;justify-content:space-between;gap:10px;padding:4px 0;border-bottom:1px dashed #ffffff08}
@@ -113,6 +133,16 @@
 <main id="main">
   <div class="alert" id="alert"></div>
 
+  <!-- SCOREBOARD — at-a-glance game-state of the whole operation (all fields from /api/data) -->
+  <section class="card span12 scoreboard">
+    <div class="sb-head">
+      <h2 style="margin:0">🏟 Scoreboard</h2>
+      <span class="sb-status" id="sb-status">—</span>
+      <span class="sb-clock" id="sb-clock"></span>
+    </div>
+    <div class="sb-tiles" id="sb-tiles"></div>
+  </section>
+
   <!-- SELF-FUNDING P&L (the money panel) -->
   <section class="card span8">
     <h2>💰 Self-Funding P&amp;L <span class="muted" id="pnlwin">· today</span></h2>
@@ -381,6 +411,9 @@ async function load(){
   const ss=(d.sessions&&d.sessions.recent)||[];
   $('sessions').innerHTML=ss.map(x=>lrow(esc(x.title||x.cwd||'session'),`${x.msgCount!=null?x.msgCount+' msg':''}`,whenChip(x.lastTs,x.mtime,x.firstTs))).join('')||'<span class="muted">no sessions</span>';
 
+  // ---- scoreboard (the game-state hero; reuses everything already in d) ----
+  renderScoreboard(d);
+
   // ---- alert banner ----
   const alerts=[];
   if((s.diskUsedPct||0)>=90) alerts.push('disk '+s.diskUsedPct+'%');
@@ -560,6 +593,53 @@ function renderCrons(d){
   });
   feedGrid('crons', jobs);
 }
+// ---- SCOREBOARD ------------------------------------------------------------
+// The at-a-glance "game state" hero. Every number is pulled straight from the
+// live /api/data snapshot (P&L, wins, canaries, fleet, sessions) — nothing is
+// invented. Headline STATUS is a rolled-up verdict; tiles are the big scores.
+function renderScoreboard(d){
+  const p=(d.pnl&&d.pnl.today)||{}, w=d.wins||{};
+  const cans=(d.canaries&&d.canaries.canaries)||[];
+  const k=d.kamatera||{}, l=d.localFleet||{}, us=d.usageSummary||{};
+  // canary W-L record: wins = PASS/UP/HEALTHY/OK, losses = FAIL/DOWN/CRIT, warns split out
+  const rank=(v)=>{v=(v||'').toUpperCase(); return /FAIL|DOWN|CRIT/.test(v)?'L':/WARN|DEGRAD/.test(v)?'W?':/PASS|UP|HEALTH|OK/.test(v)?'W':'?';};
+  const rec={W:0,'W?':0,L:0,'?':0}; cans.forEach(c=>rec[rank(c.verdict||c.status)]++);
+  const canFail=rec.L, canWarn=rec['W?'], canTotal=cans.length;
+  const fleetUp=(k.up||0)+(l.up||0), fleetTotal=(k.total||0)+(l.total||0);
+  const fleetDown=fleetTotal-fleetUp;
+  const selfPct=p.selfFundingPct||0;
+  const net=p.net||0;
+
+  // headline STATUS — the "who's winning" call. DOWN if a canary is failing or a
+  // proc is down; HOLD if warns or not self-funding; WIN only when all-green.
+  let status='WIN', scls='win';
+  if(canFail>0 || fleetDown>0){ status='DEFENSE'; scls='down'; }
+  else if(canWarn>0 || selfPct<100){ status='HOLDING'; scls='hold'; }
+  $('sb-status').textContent=status; $('sb-status').className='sb-status '+scls;
+  $('sb-clock').innerHTML=whenChip(d.builtAt)+' · live';
+
+  // tiles — big legible scores. self-funding is the headline "score".
+  const fundCls=selfPct>=100?'good':selfPct>0?'warn':'bad';
+  const canCls=canFail>0?'bad':canWarn>0?'warn':'good';
+  const fleetCls=fleetDown>0?'bad':'good';
+  const tiles=[
+    {cls:'hero '+fundCls, k:'Self-funding', v:selfPct, u:'%',
+      s:`${usd(p.revenue)} rev − ${usd(p.cost)} cost = <b class="${net>=0?'good':'bad'}">${usd(net)}</b>`},
+    {cls:'gold', k:'Wins today', v:(w.today||0), u:'',
+      s:`${(w.count||0).toLocaleString()} all-time`},
+    {cls:canCls, k:'Canary record', v:`${rec.W}–${canFail}`, u:'',
+      s:`${canTotal} checks${canWarn?` · ${canWarn} warn`:''}`},
+    {cls:fleetCls, k:'Fleet online', v:fleetUp, u:'/'+fleetTotal,
+      s:`Mac2 ${l.up||0}/${l.total||0} · Kam ${k.up||0}/${k.total||0}${k.stale?' (stale)':''}`},
+    {cls:'accent', k:'Live sessions', v:(us.activeSessions||0), u:'',
+      s:`${(us.subagentRuns||0).toLocaleString()} sub-agent runs`},
+  ];
+  $('sb-tiles').innerHTML=tiles.map(t=>
+    `<div class="sb-tile ${t.cls}"><div class="sk">${esc(t.k)}</div>`+
+    `<div class="sv">${t.v}${t.u?`<span class="su">${esc(t.u)}</span>`:''}</div>`+
+    `<div class="ss">${t.s}</div></div>`).join('');
+}
+
 // canary board — each canary is a card with a created/scanned date+time; grid
 // gets sort + density + infinite scroll (verdict/staleness/name modes).
 function renderCanaries(cans){

← 1b0ece9 auto-save: 2026-07-08T08:34:49 (1 files) — data/local-fleet.  ·  back to AbramsEgo  ·  auto-save: 2026-07-08T09:04:55 (1 files) — data/local-fleet. d4825b6 →