betterwithage commited on
Commit
90b365b
·
verified ·
1 Parent(s): a2e6679

chore(sync): mirror front-door files to Space (hf-sync)

Browse files

Automated front-door sync from szl-holdings/a11oy main via hf-sync.
Added/updated: cathedral.html, cathedral_genius.html, console/docs.html, console/index.html, console/pricing.html, console/throne-room.html, console/throne-room.js, live_wires.html, pages/api-keys.html, pages/assurance.html, pages/audit.html, pages/ayni.html, pages/brain-dual.html, pages/brain-jack.html, pages/brain.html, pages/chaski.html, pages/codex-kernel.html, pages/company.html, pages/compliance.html, pages/console.html, pages/counter-uas.html, pages/cued-engagement.html, pages/docs.html, pages/energy-ops.html, pages/evidence.html, pages/fabric.html, pages/gap-report.html, pages/harvest.html, pages/hatun-mcp.html, pages/hub.html, pages/integrations.html, pages/landing.html, pages/mesh.html, pages/observability.html, pages/operator_organ.html, pages/pinn.html, pages/pnt.html, pages/pricing.html, pages/run-all.html, pages/sdk.html, pages/security.html, pages/status.html, pages/substrate.html, pages/superpowers.html, pages/throne-room.html, pages/throne-room.js, pages/uds.html, pages/upgrades.html, pages/verify.html, pages/wallpa.html, pages/warhacker.html, pages/wasi-rikuq.html, pages/wires.html, static/a11oy_cathedral.js, static/cathedral_app.js, static/shared/szl_codename_sanitizer.js, static/shared/szl_holo3d.js, static/shared/szl_label_engine.js, static/shared/szl_receipt_cosign.js, web/agent-loop.html, web/agentic-gpu.html, web/autoreview.html, web/console.html, web/console_index.html, web/constitution.html, web/defense-readiness.html, web/determinacy.html, web/dns.html, web/elite_console.html, web/energy-holographic.html, web/energy.html, web/estate-hologram.html, web/fleet-c2.html, web/formulas.html, web/governance.html, web/holo.html, web/hologram.html, web/immune.html, web/index.html, web/living-anatomy.html, web/materials.html, web/nemo.html, web/operator.html, web/quant.html, web/restraint-bench.html, web/restraint.html, web/sda.html, web/signature-is-not-proof.html, web/v4_fleet_panel.html
Deleted (gone from GitHub main): (none)

Keeps the served front-door (pages/*.html, console/*.html) identical
to GitHub main so an HF factory rebuild never drops a GitHub edit or
keeps serving a page that was deleted on GitHub.

Files changed (1) hide show
  1. pages/energy-ops.html +475 -305
pages/energy-ops.html CHANGED
@@ -1,72 +1,139 @@
1
  <!DOCTYPE html>
2
  <!--
3
- SZL Energy — press-play "Today" operational console (WarHacker-grade, live).
4
- Doctrine v11: every value carries a MEASURED / MODELED / SAMPLE / ESTIMATE
5
- honesty chip. Revenue is NEVER shown MEASURED until a real charge clears.
6
- 0 runtime CDN, system fonts only. Degrades to NO-LIVE-DATA on 404/error
7
- never fabricates joules, tokens, or dollars.
8
-
9
- Reads live endpoints (Dev1/2/3 contracts):
10
- POST /api/a11oy/v1/energy/operator/start (press play)
11
- POST /api/a11oy/v1/energy/operator/stop (stop)
 
 
 
 
 
 
 
12
  GET /api/a11oy/v1/energy/operator/status (node status, running flag)
13
  GET /api/a11oy/v1/energy/ledger (signed JouleCharge receipts)
14
  GET /api/a11oy/v1/energy/projection?window=running (1-day MODELED headline)
15
  GET /api/a11oy/v1/harvest/posture (grid price + negative window)
 
 
16
  -->
17
  <html lang="en">
18
  <head>
19
  <meta charset="utf-8" />
20
  <meta name="viewport" content="width=device-width, initial-scale=1" />
21
- <title>SZL Energy — Today (press play)</title>
 
22
  <style>
23
  /* System fonts only — 0 CDN, 0 webfont. */
24
  :root {
25
  --bg:#070b12; --panel:#0d141f; --panel2:#111b29; --line:#1c2a3c;
26
  --ink:#e8eef7; --dim:#8aa0bd; --gold:#d4a444; --green:#34d399;
27
  --red:#f87171; --amber:#fbbf24; --blue:#60a5fa; --mag:#c084fc;
 
28
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
29
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
30
  }
31
  * { box-sizing:border-box; }
32
  html,body { margin:0; background:var(--bg); color:var(--ink); font-family:var(--sans); }
33
  body { padding:18px clamp(12px,3vw,34px) 60px; }
34
- a { color:var(--blue); }
35
- header.top { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-bottom:14px; }
 
36
  .brand { display:flex; align-items:baseline; gap:12px; }
37
  .brand h1 { font-size:clamp(18px,2.4vw,26px); margin:0; letter-spacing:.5px; }
 
38
  .brand .sub { color:var(--dim); font-size:13px; }
 
 
 
39
  .conn { font-family:var(--mono); font-size:12px; color:var(--dim); }
40
- .conn b { color:var(--green); }
41
  .conn.degraded b { color:var(--amber); }
42
 
43
- /* honesty chip — 4 states (replicates szl3d_label) */
44
  .chip {
45
  display:inline-block; font-family:var(--mono); font-size:10px; font-weight:700;
46
  letter-spacing:.6px; padding:2px 6px; border-radius:4px; vertical-align:middle;
47
  border:1px solid currentColor; line-height:1.2; text-transform:uppercase;
48
  }
49
- .chip-measured { color:var(--green); }
50
- .chip-modeled { color:var(--amber); }
51
- .chip-sample { color:var(--blue); }
52
- .chip-estimate { color:var(--mag); }
53
- .chip-zero { color:var(--dim); }
54
- .chip-degraded { color:var(--red); }
 
 
 
 
 
 
 
 
55
 
56
  .grid { display:grid; gap:14px; grid-template-columns:repeat(12,1fr); }
57
- .card { background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:16px 18px; }
58
  .card h2 { margin:0 0 10px; font-size:12px; letter-spacing:1.4px; text-transform:uppercase; color:var(--gold); font-weight:700; }
59
  .span12{grid-column:span 12} .span8{grid-column:span 8} .span6{grid-column:span 6}
60
  .span5{grid-column:span 5} .span4{grid-column:span 4} .span7{grid-column:span 7}
61
  @media (max-width:880px){ .span4,.span5,.span6,.span7,.span8{grid-column:span 12} }
62
 
63
- /* PRESS PLAY */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  .playwrap { display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
65
  .playbtn {
66
  appearance:none; border:none; cursor:pointer; font-family:var(--sans);
67
  font-weight:800; font-size:22px; letter-spacing:1px; color:#06210f;
68
- background:linear-gradient(180deg,#5fe6a8,#2bb877); border-radius:14px;
69
- padding:20px 34px; box-shadow:0 0 0 1px #0a3b27, 0 8px 26px rgba(52,211,153,.28);
70
  transition:transform .08s ease, filter .15s ease; min-width:230px;
71
  }
72
  .playbtn:hover{ filter:brightness(1.06); }
@@ -74,11 +141,10 @@
74
  .playbtn.stop { background:linear-gradient(180deg,#ff8b8b,#e2554f); color:#2a0606; box-shadow:0 0 0 1px #5a1414, 0 8px 26px rgba(248,113,113,.28); }
75
  .playbtn[disabled]{ opacity:.55; cursor:wait; }
76
  .playstate { font-family:var(--mono); font-size:13px; color:var(--dim); }
77
- .dot { display:inline-block; width:10px; height:10px; border-radius:50%; margin-right:6px; vertical-align:middle; background:var(--dim); }
78
- .dot.on { background:var(--green); box-shadow:0 0 8px var(--green); }
79
- .dot.off{ background:var(--red); }
80
 
81
- /* THE BIG NUMBER */
82
  .bignum { font-family:var(--mono); font-weight:800; line-height:1; }
83
  .bignum .val { font-size:clamp(34px,7vw,76px); color:var(--green); letter-spacing:-1px; }
84
  .bignum .unit { font-size:18px; color:var(--dim); margin-left:8px; }
@@ -110,7 +176,6 @@
110
  .neg-banner.show{ display:inline-flex; animation:pulse 1.6s ease-in-out infinite; }
111
  @keyframes pulse{ 0%,100%{filter:brightness(1)} 50%{filter:brightness(1.35)} }
112
 
113
- /* RECEIPT FEED */
114
  .feed { max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:10px; }
115
  table.feed-t { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12px; }
116
  table.feed-t th { position:sticky; top:0; background:var(--panel2); color:var(--dim); text-align:left; padding:8px 10px; font-weight:700; letter-spacing:.5px; border-bottom:1px solid var(--line); }
@@ -120,48 +185,106 @@
120
  .digest { color:var(--dim); }
121
  .cents { color:var(--amber); font-weight:700; }
122
 
123
- .empty { color:var(--dim); font-family:var(--mono); font-size:13px; padding:14px; }
124
  .foot { margin-top:22px; color:var(--dim); font-size:11px; font-family:var(--mono); line-height:1.7; }
125
  .foot b{ color:var(--ink); }
126
 
127
- /* ===== LIVE GRAPHS (vanilla canvas, 0 CDN) ===== */
128
- .chartwrap { position:relative; width:100%; height:210px; }
129
- .chartwrap.sm { height:150px; }
130
- .chartwrap canvas, .gaugewrap canvas { width:100%; height:100%; display:block; }
131
- .chart-empty { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
132
- color:var(--dim); font-family:var(--mono); font-size:12px; pointer-events:none; text-align:center; padding:0 12px; }
133
- .chart-empty.hide { display:none; }
134
- .chart-read { position:absolute; top:6px; right:12px; font-family:var(--mono); font-weight:800;
135
- font-size:22px; color:var(--green); text-shadow:0 0 14px rgba(52,211,153,.35); }
136
- .chart-read .cu { font-size:12px; color:var(--dim); margin-left:3px; font-weight:700; }
137
- .chart-axis { display:flex; justify-content:space-between; align-items:center; margin-top:7px;
138
- font-family:var(--mono); font-size:10px; color:var(--dim); letter-spacing:.4px; }
139
- .chart-axis .mid { color:#6b809c; }
140
- .gaugewrap { position:relative; width:100%; height:210px; }
141
- .gauge-read { position:absolute; left:0; right:0; bottom:30px; text-align:center;
142
- font-family:var(--mono); font-weight:800; font-size:30px; color:var(--green); }
143
- .gauge-read .cu { font-size:13px; color:var(--dim); margin-left:4px; font-weight:700; }
144
- .chart-sub { text-align:center; color:var(--dim); font-family:var(--mono); font-size:11px; margin-top:6px; }
145
  </style>
146
  </head>
147
  <body>
148
  <header class="top">
149
  <div class="brand">
150
- <h1>SZL ENERGY · <span style="color:var(--gold)">TODAY</span></h1>
151
- <span class="sub">press-play operational console — every number labeled, nothing faked</span>
 
 
 
 
 
152
  </div>
153
- <div class="conn" id="conn">link <b id="conn-state">…</b> · <span id="conn-ts">—</span></div>
154
  </header>
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  <div class="grid">
157
 
158
- <!-- PRESS PLAY + THE BIG NUMBER -->
159
  <section class="card span7">
160
  <h2>Operator</h2>
161
  <div class="playwrap">
162
  <button class="playbtn" id="play" aria-pressed="false">PRESS PLAY</button>
163
  <div class="playstate">
164
- <div><span class="dot" id="run-dot"></span><span id="run-text">querying operator…</span></div>
165
  <div id="run-detail" style="margin-top:6px;color:var(--dim)">—</div>
166
  </div>
167
  </div>
@@ -179,45 +302,6 @@
179
  </div>
180
  </section>
181
 
182
- <!-- ===== LIVE GRAPHS (founder #1: measured joules climbing) ===== -->
183
- <section class="card span8">
184
- <h2>Live · measured joules climbing <span class="chip chip-measured">MEASURED</span></h2>
185
- <div class="chartwrap">
186
- <canvas id="chart-joules"></canvas>
187
- <div class="chart-empty" id="chart-joules-empty">awaiting first live sample…</div>
188
- <div class="chart-read"><span id="chart-joules-val">—</span><span class="cu">J</span></div>
189
- </div>
190
- <div class="chart-axis"><span id="chart-joules-lo">—</span><span class="mid">cumulative on-box NVML energy · live every 3s</span><span id="chart-joules-hi">—</span></div>
191
- </section>
192
-
193
- <section class="card span4">
194
- <h2>GPU power now <span class="chip chip-measured">MEASURED</span></h2>
195
- <div class="gaugewrap">
196
- <canvas id="gauge-power"></canvas>
197
- <div class="gauge-read"><span id="gauge-power-val">—</span><span class="cu">W</span></div>
198
- </div>
199
- <div class="chart-sub" id="gauge-power-sub">computing node —</div>
200
- </section>
201
-
202
- <section class="card span6">
203
- <h2>GPU power trend <span class="chip chip-measured">MEASURED</span></h2>
204
- <div class="chartwrap sm">
205
- <canvas id="chart-power"></canvas>
206
- <div class="chart-empty" id="chart-power-empty">awaiting first live sample…</div>
207
- <div class="chart-read"><span id="chart-power-val">—</span><span class="cu">W</span></div>
208
- </div>
209
- </section>
210
-
211
- <section class="card span6">
212
- <h2>Throughput · tokens / sec <span class="chip chip-measured">MEASURED</span></h2>
213
- <div class="chartwrap sm">
214
- <canvas id="chart-tps"></canvas>
215
- <div class="chart-empty" id="chart-tps-empty">measuring rate from live deltas…</div>
216
- <div class="chart-read"><span id="chart-tps-val">—</span><span class="cu">tok/s</span></div>
217
- </div>
218
- </section>
219
-
220
- <!-- TODAY: compute done + earnings -->
221
  <section class="card span7">
222
  <h2>Today · compute done &amp; earnings</h2>
223
  <div class="metrics">
@@ -252,16 +336,14 @@
252
  <div class="lbl"><span class="chip chip-zero" id="m-real-chip">ZERO</span></div>
253
  </div>
254
  </div>
255
- <div style="margin-top:10px;color:var(--dim);font-size:11px;font-family:var(--mono)" id="earn-formula">earnings = MODELED dry-run ledger total + projection (no charge has cleared)</div>
256
  </section>
257
 
258
- <!-- NODE STATUS -->
259
  <section class="card span5">
260
  <h2>Nodes computing now</h2>
261
  <div class="nodes" id="nodes"><div class="empty">querying operator status…</div></div>
262
  </section>
263
 
264
- <!-- GRID STRIP -->
265
  <section class="card span12">
266
  <h2>Grid</h2>
267
  <div class="grid-strip">
@@ -275,7 +357,6 @@
275
  </div>
276
  </section>
277
 
278
- <!-- RECEIPT FEED -->
279
  <section class="card span12">
280
  <h2>Live receipt feed · SZL.Energy.JouleCharge.v1 <span class="chip chip-modeled" id="feed-mode">DRY-RUN</span></h2>
281
  <div class="feed">
@@ -289,47 +370,306 @@
289
  </div>
290
 
291
  <div class="foot">
292
- <div><b>Honesty (Doctrine v11):</b> joules &amp; tokens are <span class="chip chip-measured">MEASURED</span> from the on-box NVML exporter. Dollars are <span class="chip chip-modeled">MODELED</span> / <span class="chip chip-estimate">ESTIMATE</span> projections — revenue is <span class="chip chip-zero">ZERO</span> until a real Stripe charge clears (then, and only then, <span class="chip chip-measured">MEASURED</span>).</div>
293
- <div>No live data → panels show <span class="chip chip-degraded">DEGRADED</span> / NO-LIVE-DATA. Nothing is ever fabricated. 0 runtime CDN · system fonts.</div>
294
  </div>
295
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  <script>
297
  "use strict";
 
 
 
298
  (function () {
299
- var API = "/api/a11oy/v1";
300
-
301
- // ---- honesty chip helper (4-state; replicates szl3d_label) -------------
302
- var CHIP = { MEASURED:"chip-measured", MODELED:"chip-modeled", SAMPLE:"chip-sample",
303
- ESTIMATE:"chip-estimate", ZERO:"chip-zero", DEGRADED:"chip-degraded" };
304
- function setChip(el, label) {
305
- if (!el) return;
306
- var key = String(label || "DEGRADED").toUpperCase();
307
- var cls = CHIP[key] || "chip-degraded";
308
- el.className = "chip " + cls;
309
- el.textContent = key;
310
  }
311
-
312
- // ---- live poll helper (reuses szl3d_live.poll contract if present) -----
313
- // Falls back to a local implementation so the page is self-contained.
314
- function getPoller() {
315
- if (window.szl3d_live && typeof window.szl3d_live.poll === "function") {
316
- return window.szl3d_live.poll;
 
 
 
 
 
317
  }
318
- return function (url, opts) {
319
- opts = opts || {};
320
- return fetch(url, { method: opts.method || "GET",
321
- headers: opts.body ? {"content-type":"application/json"} : undefined,
322
- body: opts.body })
323
- .then(function (r) {
324
- if (!r.ok) return { __error: true, status: r.status };
325
- return r.json().catch(function () { return { __error: true, status: r.status }; });
326
- })
327
- .catch(function () { return { __error: true, status: 0 }; });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  };
 
 
 
 
 
329
  }
330
- var poll = getPoller();
331
 
332
- // ---- smooth counter (eases displayed value toward a target) ------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  function Counter(el, opts) {
334
  opts = opts || {};
335
  this.el = el; this.cur = 0; this.target = 0; this.decimals = opts.decimals || 0;
@@ -338,44 +678,25 @@
338
  Counter.prototype.set = function (v) {
339
  if (typeof v !== "number" || !isFinite(v)) return;
340
  this.target = v;
341
- if (!this.started) { this.cur = v; this.started = true; this.render(); }
342
  };
343
  Counter.prototype.tick = function () {
344
  if (!this.started) return;
345
  var d = this.target - this.cur;
346
- if (Math.abs(d) < Math.pow(10, -this.decimals) / 2) { this.cur = this.target; }
347
  else { this.cur += d * 0.18; }
348
  this.render();
349
  };
350
- Counter.prototype.render = function () {
351
- this.el.textContent = fmt(this.cur, this.decimals);
352
- };
353
- function fmt(n, dec) {
354
- if (typeof n !== "number" || !isFinite(n)) return "—";
355
- return n.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
356
- }
357
 
358
  var bigJoules = new Counter(document.getElementById("big-joules"), { decimals: 3 });
359
  var mTokens = new Counter(document.getElementById("m-tokens"), { decimals: 0 });
360
  var mJobs = new Counter(document.getElementById("m-jobs"), { decimals: 0 });
361
  var mJoules = new Counter(document.getElementById("m-joules"), { decimals: 1 });
362
- setInterval(function () { bigJoules.tick(); mTokens.tick(); mJobs.tick(); mJoules.tick(); }, 33);
363
-
364
- // ---- connection banner -------------------------------------------------
365
- var anyLive = false;
366
- function markLive(ok) {
367
- anyLive = anyLive || ok;
368
- var conn = document.getElementById("conn");
369
- var st = document.getElementById("conn-state");
370
- if (ok) { conn.classList.remove("degraded"); st.textContent = "LIVE"; }
371
- else if (!anyLive) { conn.classList.add("degraded"); st.textContent = "NO-LIVE-DATA"; }
372
- document.getElementById("conn-ts").textContent = new Date().toLocaleTimeString();
373
- }
374
-
375
- function isErr(d) { return !d || d.__error === true; }
376
 
377
  // ===== OPERATOR: PLAY / STOP + status ==================================
378
- var running = null; // unknown
379
  var playBtn = document.getElementById("play");
380
  var busy = false;
381
 
@@ -384,7 +705,7 @@
384
  var txt = document.getElementById("run-text");
385
  var det = document.getElementById("run-detail");
386
  if (isErr(status)) {
387
- running = null; dot.className = "dot off";
388
  txt.textContent = "operator DEGRADED — NO-LIVE-DATA";
389
  det.innerHTML = '<span class="chip chip-degraded">DEGRADED</span> operator status unreachable';
390
  playBtn.textContent = "PRESS PLAY"; playBtn.classList.remove("stop");
@@ -392,7 +713,7 @@
392
  return;
393
  }
394
  running = (status.running === true) || (status.state === "running") || (status.status === "running");
395
- dot.className = "dot " + (running ? "on" : "off");
396
  if (running) {
397
  txt.textContent = "RUNNING — computing non-stop";
398
  playBtn.textContent = "STOP"; playBtn.classList.add("stop");
@@ -415,7 +736,7 @@
415
  }
416
 
417
  playBtn.addEventListener("click", function () {
418
- if (busy || running === null && false) {}
419
  var action = running ? "stop" : "start";
420
  busy = true; playBtn.disabled = true;
421
  poll(API + "/energy/operator/" + action, { method: "POST", body: "{}" })
@@ -426,7 +747,6 @@
426
  '<span class="chip chip-degraded">DEGRADED</span> ' + action + " failed (operator unreachable)";
427
  return;
428
  }
429
- // optimistic; status poll will confirm
430
  running = (action === "start");
431
  refreshStatus();
432
  });
@@ -444,8 +764,6 @@
444
  box.innerHTML = '<div class="empty">no node telemetry — <span class="chip chip-degraded">DEGRADED</span></div>';
445
  return;
446
  }
447
- // operator/status contract: node_status {name:state}, by_node {name:{jobs,joules,tokens}},
448
- // plus nodes_computing / nodes_standby / nodes_degraded arrays.
449
  var ns = (status.node_status && typeof status.node_status === "object") ? status.node_status : null;
450
  var byNode = status.by_node || {};
451
  var names = ns ? Object.keys(ns) : [];
@@ -494,13 +812,11 @@
494
  }
495
  var receipts = d.receipts || d.entries || d.ledger || [];
496
  var dryRunTotal = 0, anyCharged = false;
497
- // dry-run mode chip
498
  var mode = (String(d.stripe_mode || d.mode || "").toLowerCase() === "live") ? "LIVE" : "DRY-RUN";
499
  var feedMode = document.getElementById("feed-mode");
500
  feedMode.textContent = mode;
501
  feedMode.className = "chip " + (mode === "LIVE" ? "chip-measured" : "chip-modeled");
502
 
503
- // render newest first
504
  var rows = receipts.slice().reverse();
505
  if (rows.length && body.querySelector(".empty")) body.innerHTML = "";
506
  rows.forEach(function (r) {
@@ -533,9 +849,6 @@
533
  mReal.textContent = "$" + (realCents / 100).toFixed(2);
534
  setChip(document.getElementById("m-real-chip"), realCents > 0 ? "MEASURED" : "ZERO");
535
  mReal.style.color = realCents > 0 ? "var(--green)" : "var(--dim)";
536
-
537
- // chain integrity note (honest)
538
- // (display omitted from UI chrome; integrity reflected by DEGRADED if absent)
539
  });
540
  }
541
 
@@ -558,7 +871,6 @@
558
  if (tokens != null) mTokens.set(tokens);
559
  if (jobs != null) mJobs.set(jobs);
560
  if (joules != null) { mJoules.set(joules); bigJoules.set(joules); }
561
- pushEnergySamples(d);
562
  });
563
  }
564
 
@@ -574,7 +886,6 @@
574
  var joules = num((cd.joules && cd.joules.value != null) ? cd.joules.value
575
  : (p.joules && p.joules.value != null ? p.joules.value
576
  : (p.joules != null ? p.joules : (p.joules_projected != null ? p.joules_projected : p.joules_1d))));
577
- // headline dollar = compute_resale (ESTIMATE: assumed ¢/kWh), per API honesty contract
578
  var earn = p.earnings || {};
579
  var resale = earn.compute_resale_usd || {};
580
  var earnDollars = num(resale.value != null ? resale.value
@@ -611,7 +922,6 @@
611
  else {
612
  priceEl.textContent = price.toFixed(2) + " EUR/MWh";
613
  priceEl.style.color = price < 0 ? "var(--green)" : "var(--ink)";
614
- // grid price reading off-box is SAMPLE per posture contract
615
  setChip(chip, (d.measured_any ? "MEASURED" : "SAMPLE"));
616
  banner.classList.toggle("show", price < 0);
617
  }
@@ -636,146 +946,6 @@
636
  return null;
637
  }
638
 
639
- // ---- small utils -------------------------------------------------------
640
- function num(v) { var n = typeof v === "number" ? v : parseFloat(v); return isFinite(n) ? n : null; }
641
- function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) {
642
- return { "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;" }[c]; }); }
643
- function shortDigest(d) { if (!d) return "—"; var h = String(d).replace(/^sha256:/, ""); return h.slice(0, 12) + "…"; }
644
- function shortTime(t) { if (!t) return "—"; try { return new Date(t).toLocaleTimeString(); } catch (e) { return String(t); } }
645
-
646
- // ===== LIVE CHARTS (vanilla canvas, DPR-aware, 0 CDN) =================
647
- function LiveChart(id, opts) {
648
- opts = opts || {};
649
- this.c = document.getElementById(id);
650
- this.ctx = this.c ? this.c.getContext("2d") : null;
651
- this.empty = opts.empty ? document.getElementById(opts.empty) : null;
652
- this.valEl = opts.val ? document.getElementById(opts.val) : null;
653
- this.loEl = opts.lo ? document.getElementById(opts.lo) : null;
654
- this.hiEl = opts.hi ? document.getElementById(opts.hi) : null;
655
- this.pts = [];
656
- this.max = opts.max || 180;
657
- this.color = opts.color || "#34d399";
658
- this.decimals = opts.decimals != null ? opts.decimals : 0;
659
- this.minSpan = opts.minSpan || 0;
660
- this.fixedMin = opts.fixedMin;
661
- }
662
- LiveChart.prototype.push = function (v) {
663
- if (typeof v !== "number" || !isFinite(v)) return;
664
- this.pts.push(v);
665
- if (this.pts.length > this.max) this.pts.shift();
666
- if (this.empty) this.empty.classList.add("hide");
667
- if (this.valEl) this.valEl.textContent = fmt(v, this.decimals);
668
- this.draw();
669
- };
670
- LiveChart.prototype.draw = function () {
671
- var c = this.c, ctx = this.ctx;
672
- if (!ctx) return;
673
- var dpr = window.devicePixelRatio || 1;
674
- var w = c.clientWidth || 600, h = c.clientHeight || 200;
675
- if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
676
- c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
677
- }
678
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
679
- ctx.clearRect(0, 0, w, h);
680
- var padL = 8, padR = 8, padT = 14, padB = 10;
681
- var plotW = w - padL - padR, plotH = h - padT - padB;
682
- ctx.strokeStyle = "rgba(255,255,255,0.05)"; ctx.lineWidth = 1;
683
- for (var g = 0; g <= 4; g++) {
684
- var gy = padT + plotH * g / 4;
685
- ctx.beginPath(); ctx.moveTo(padL, gy); ctx.lineTo(w - padR, gy); ctx.stroke();
686
- }
687
- var pts = this.pts;
688
- if (pts.length < 2) return;
689
- var mn = Math.min.apply(null, pts), mx = Math.max.apply(null, pts);
690
- if (this.fixedMin != null) mn = Math.min(mn, this.fixedMin);
691
- if (mx - mn < this.minSpan) mx = mn + this.minSpan;
692
- if (mx === mn) mx = mn + 1;
693
- var n = pts.length;
694
- function X(i) { return padL + plotW * (i / (n - 1)); }
695
- function Y(v) { return padT + plotH * (1 - (v - mn) / (mx - mn)); }
696
- var grad = ctx.createLinearGradient(0, padT, 0, padT + plotH);
697
- grad.addColorStop(0, this.color + "55");
698
- grad.addColorStop(1, this.color + "08");
699
- ctx.beginPath();
700
- ctx.moveTo(X(0), Y(pts[0]));
701
- for (var i = 1; i < n; i++) ctx.lineTo(X(i), Y(pts[i]));
702
- ctx.lineTo(X(n - 1), padT + plotH);
703
- ctx.lineTo(X(0), padT + plotH);
704
- ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
705
- ctx.beginPath();
706
- ctx.moveTo(X(0), Y(pts[0]));
707
- for (var j = 1; j < n; j++) ctx.lineTo(X(j), Y(pts[j]));
708
- ctx.strokeStyle = this.color; ctx.lineWidth = 2;
709
- ctx.lineJoin = "round"; ctx.stroke();
710
- ctx.beginPath();
711
- ctx.arc(X(n - 1), Y(pts[n - 1]), 3, 0, Math.PI * 2);
712
- ctx.fillStyle = this.color; ctx.fill();
713
- if (this.loEl) this.loEl.textContent = fmt(mn, this.decimals);
714
- if (this.hiEl) this.hiEl.textContent = fmt(mx, this.decimals);
715
- };
716
-
717
- function Gauge(id, opts) {
718
- opts = opts || {};
719
- this.c = document.getElementById(id);
720
- this.ctx = this.c ? this.c.getContext("2d") : null;
721
- this.valEl = opts.val ? document.getElementById(opts.val) : null;
722
- this.max = opts.max || 120; this.cur = 0;
723
- }
724
- Gauge.prototype.set = function (v) {
725
- if (typeof v !== "number" || !isFinite(v)) return;
726
- this.cur = v; if (this.valEl) this.valEl.textContent = fmt(v, 1);
727
- this.draw();
728
- };
729
- Gauge.prototype.draw = function () {
730
- var c = this.c, ctx = this.ctx; if (!ctx) return;
731
- var dpr = window.devicePixelRatio || 1;
732
- var w = c.clientWidth || 240, h = c.clientHeight || 200;
733
- if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
734
- c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
735
- }
736
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
737
- ctx.clearRect(0, 0, w, h);
738
- var cx = w / 2, cy = h * 0.70, r = Math.min(w * 0.40, h * 0.56);
739
- var a0 = Math.PI * 0.85, a1 = Math.PI * 2.15;
740
- ctx.lineWidth = 14; ctx.lineCap = "round";
741
- ctx.strokeStyle = "#16202e";
742
- ctx.beginPath(); ctx.arc(cx, cy, r, a0, a1); ctx.stroke();
743
- var frac = Math.max(0, Math.min(1, this.cur / this.max));
744
- var grad = ctx.createLinearGradient(cx - r, 0, cx + r, 0);
745
- grad.addColorStop(0, "#34d399"); grad.addColorStop(1, "#fbbf24");
746
- ctx.strokeStyle = grad;
747
- ctx.beginPath(); ctx.arc(cx, cy, r, a0, a0 + (a1 - a0) * frac); ctx.stroke();
748
- };
749
-
750
- var chJoules = new LiveChart("chart-joules", { empty:"chart-joules-empty", val:"chart-joules-val", lo:"chart-joules-lo", hi:"chart-joules-hi", color:"#34d399", decimals:0, minSpan:1 });
751
- var chPower = new LiveChart("chart-power", { empty:"chart-power-empty", val:"chart-power-val", color:"#60a5fa", decimals:1, fixedMin:0, minSpan:5 });
752
- var chTps = new LiveChart("chart-tps", { empty:"chart-tps-empty", val:"chart-tps-val", color:"#c084fc", decimals:1, fixedMin:0, minSpan:1 });
753
- var gPower = new Gauge("gauge-power", { val:"gauge-power-val", max:120 });
754
- var tpsState = { lastTokens:null, lastTs:null };
755
-
756
- function pushEnergySamples(d) {
757
- if (isErr(d)) return;
758
- var j = num(d.joules_measured_total != null ? d.joules_measured_total : d.joules_measured);
759
- if (j != null) chJoules.push(j);
760
- var pw = num(d.power_w_sample);
761
- if (pw != null) { chPower.push(pw); gPower.set(pw); }
762
- var nodeName = (d.nodes_computing && d.nodes_computing[0]) || "—";
763
- var sub = document.getElementById("gauge-power-sub");
764
- if (sub) sub.textContent = "computing node " + nodeName;
765
- var tk = num(d.tokens_total != null ? d.tokens_total : d.tokens_measured);
766
- var now = Date.now();
767
- if (tk != null) {
768
- if (tpsState.lastTokens != null && tpsState.lastTs != null && now > tpsState.lastTs) {
769
- var rate = (tk - tpsState.lastTokens) / ((now - tpsState.lastTs) / 1000);
770
- if (rate >= 0 && isFinite(rate)) chTps.push(rate);
771
- }
772
- tpsState.lastTokens = tk; tpsState.lastTs = now;
773
- }
774
- }
775
- window.addEventListener("resize", function () {
776
- chJoules.draw(); chPower.draw(); chTps.draw(); gPower.draw();
777
- });
778
-
779
  // ---- poll loop ---------------------------------------------------------
780
  function tickFast() { refreshStatus(); refreshToday(); refreshLedger(); }
781
  function tickSlow() { refreshProjection(); refreshGrid(); }
 
1
  <!DOCTYPE html>
2
  <!--
3
+ SZL Energy — Live Energy Dashboard (/energy-ops), leader-grade & bound to REAL data.
4
+ Doctrine v11: every value carries an honest label chip
5
+ (LIVE / MEASURED / SAMPLE / ESTIMATE / MODELED / UNAVAILABLE / ZERO / DEGRADED).
6
+ Joules are MEASURED only from a reachable on-box NVML exporter never fabricated.
7
+ Revenue is NEVER shown MEASURED until a real charge clears. 0 runtime CDN,
8
+ system fonts only. Degrades honestly to NO-LIVE-DATA / UNAVAILABLE on 404/error.
9
+
10
+ Tri-row live surface (DCGM / Kepler / Scaphandre patterns, reimplemented):
11
+ (1) live power gauge + per-node cards GET /api/a11oy/v1/energy/live
12
+ (2) streaming watts line chart (uPlot) GET /api/a11oy/v1/energy/live (poll 2s)
13
+ (3) Bekenstein budget ledger table GET /api/a11oy/v1/energy/budget (F19/TH6)
14
+ mesh node health GET /api/a11oy/v1/govern/health
15
+
16
+ Press-play operator console (live operator control + signed receipt feed):
17
+ POST /api/a11oy/v1/energy/operator/start (press play)
18
+ POST /api/a11oy/v1/energy/operator/stop (stop)
19
  GET /api/a11oy/v1/energy/operator/status (node status, running flag)
20
  GET /api/a11oy/v1/energy/ledger (signed JouleCharge receipts)
21
  GET /api/a11oy/v1/energy/projection?window=running (1-day MODELED headline)
22
  GET /api/a11oy/v1/harvest/posture (grid price + negative window)
23
+
24
+ uPlot v1.6.32 (MIT, Leon Sorokin) vendored 0-CDN at /vendor/uPlot.* .
25
  -->
26
  <html lang="en">
27
  <head>
28
  <meta charset="utf-8" />
29
  <meta name="viewport" content="width=device-width, initial-scale=1" />
30
+ <title>SZL Energy — Live Energy Dashboard</title>
31
+ <link rel="stylesheet" href="/vendor/uPlot.min.css" />
32
  <style>
33
  /* System fonts only — 0 CDN, 0 webfont. */
34
  :root {
35
  --bg:#070b12; --panel:#0d141f; --panel2:#111b29; --line:#1c2a3c;
36
  --ink:#e8eef7; --dim:#8aa0bd; --gold:#d4a444; --green:#34d399;
37
  --red:#f87171; --amber:#fbbf24; --blue:#60a5fa; --mag:#c084fc;
38
+ --teal:#39d8c8; /* holo-teal — the energy surface signature */
39
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
40
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
41
  }
42
  * { box-sizing:border-box; }
43
  html,body { margin:0; background:var(--bg); color:var(--ink); font-family:var(--sans); }
44
  body { padding:18px clamp(12px,3vw,34px) 60px; }
45
+ a { color:var(--teal); }
46
+ header.top { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-bottom:14px;
47
+ border-bottom:1px solid var(--line); padding-bottom:14px; }
48
  .brand { display:flex; align-items:baseline; gap:12px; }
49
  .brand h1 { font-size:clamp(18px,2.4vw,26px); margin:0; letter-spacing:.5px; }
50
+ .brand .tl { color:var(--teal); text-shadow:0 0 18px rgba(57,216,200,.35); }
51
  .brand .sub { color:var(--dim); font-size:13px; }
52
+ .topright { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
53
+ .mesh-pill { font-family:var(--mono); font-size:12px; color:var(--dim); }
54
+ .mesh-pill b.up { color:var(--green); } .mesh-pill b.down { color:var(--red); }
55
  .conn { font-family:var(--mono); font-size:12px; color:var(--dim); }
56
+ .conn b { color:var(--teal); }
57
  .conn.degraded b { color:var(--amber); }
58
 
59
+ /* honesty chip — multi-state (replicates szl3d_label) */
60
  .chip {
61
  display:inline-block; font-family:var(--mono); font-size:10px; font-weight:700;
62
  letter-spacing:.6px; padding:2px 6px; border-radius:4px; vertical-align:middle;
63
  border:1px solid currentColor; line-height:1.2; text-transform:uppercase;
64
  }
65
+ .chip-live { color:var(--teal); }
66
+ .chip-measured { color:var(--green); }
67
+ .chip-modeled { color:var(--amber); }
68
+ .chip-sample { color:var(--blue); }
69
+ .chip-estimate { color:var(--mag); }
70
+ .chip-zero { color:var(--dim); }
71
+ .chip-degraded { color:var(--red); }
72
+ .chip-unavailable { color:var(--red); }
73
+
74
+ .secthead { display:flex; align-items:center; gap:10px; margin:22px 0 12px; }
75
+ .secthead .n { font-family:var(--mono); font-size:11px; color:var(--teal); border:1px solid var(--teal);
76
+ border-radius:5px; padding:2px 7px; opacity:.85; }
77
+ .secthead h2 { margin:0; font-size:13px; letter-spacing:1.6px; text-transform:uppercase; color:var(--ink); font-weight:700; }
78
+ .secthead .sub { color:var(--dim); font-size:12px; font-family:var(--mono); }
79
 
80
  .grid { display:grid; gap:14px; grid-template-columns:repeat(12,1fr); }
81
+ .card { background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:16px 18px; min-width:0; }
82
  .card h2 { margin:0 0 10px; font-size:12px; letter-spacing:1.4px; text-transform:uppercase; color:var(--gold); font-weight:700; }
83
  .span12{grid-column:span 12} .span8{grid-column:span 8} .span6{grid-column:span 6}
84
  .span5{grid-column:span 5} .span4{grid-column:span 4} .span7{grid-column:span 7}
85
  @media (max-width:880px){ .span4,.span5,.span6,.span7,.span8{grid-column:span 12} }
86
 
87
+ /* total-power gauge */
88
+ .gaugewrap { position:relative; width:100%; height:210px; }
89
+ .gaugewrap canvas { width:100%; height:100%; display:block; }
90
+ .gauge-read { position:absolute; left:0; right:0; bottom:30px; text-align:center;
91
+ font-family:var(--mono); font-weight:800; font-size:34px; color:var(--teal); text-shadow:0 0 16px rgba(57,216,200,.35); }
92
+ .gauge-read.off { color:var(--red); text-shadow:none; }
93
+ .gauge-read .cu { font-size:13px; color:var(--dim); margin-left:4px; font-weight:700; }
94
+ .gauge-sub { text-align:center; color:var(--dim); font-family:var(--mono); font-size:11px; margin-top:6px; }
95
+
96
+ /* per-node cards */
97
+ .nodegrid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:12px; }
98
+ .ncard { background:var(--panel2); border:1px solid var(--line); border-radius:10px; padding:13px 14px; position:relative; overflow:hidden; }
99
+ .ncard.live { border-color:#1f5e54; box-shadow:0 0 0 1px #14463f inset; }
100
+ .ncard.down { border-color:#5a2222; opacity:.86; }
101
+ .ncard .nm { font-family:var(--mono); font-weight:700; font-size:13px; display:flex; align-items:center; gap:7px; word-break:break-word; }
102
+ .ncard .watt { font-family:var(--mono); font-weight:800; font-size:30px; margin-top:8px; color:var(--teal); }
103
+ .ncard.down .watt { color:var(--dim); }
104
+ .ncard .watt .u { font-size:13px; color:var(--dim); margin-left:3px; font-weight:700; }
105
+ .ncard .meta { color:var(--dim); font-family:var(--mono); font-size:11px; margin-top:6px; line-height:1.6; }
106
+ .dot { display:inline-block; width:9px; height:9px; border-radius:50%; background:var(--dim); flex:0 0 auto; }
107
+ .dot.on { background:var(--teal); box-shadow:0 0 8px var(--teal); }
108
+ .dot.off{ background:var(--red); }
109
+
110
+ /* streaming uPlot chart */
111
+ #watts-chart { width:100%; }
112
+ .u-legend { color:var(--dim); font-family:var(--mono); font-size:11px; }
113
+ .chart-note { display:flex; align-items:center; gap:10px; margin-top:8px; font-family:var(--mono); font-size:11px; color:var(--dim); }
114
+ .offline-tag { display:none; align-items:center; gap:7px; color:var(--red); font-weight:700; }
115
+ .offline-tag.show { display:inline-flex; }
116
+ .chart-empty { color:var(--dim); font-family:var(--mono); font-size:12px; padding:18px 4px; }
117
+
118
+ /* budget ledger table */
119
+ .budgetbar { display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin-bottom:10px; font-family:var(--mono); font-size:12px; color:var(--dim); }
120
+ .budgetbar .k { color:var(--dim); } .budgetbar .v { color:var(--ink); font-weight:700; }
121
+ .gate-ok { color:var(--green); font-weight:700; } .gate-bad { color:var(--red); font-weight:700; }
122
+ .tbl-wrap { max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:10px; }
123
+ table.tbl { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12px; }
124
+ table.tbl th { position:sticky; top:0; background:var(--panel2); color:var(--dim); text-align:left; padding:8px 10px; font-weight:700; letter-spacing:.5px; border-bottom:1px solid var(--line); }
125
+ table.tbl td { padding:7px 10px; border-bottom:1px solid #16202e; }
126
+ table.tbl tr:hover td { background:#0f1a28; }
127
+ .within-y { color:var(--green); } .within-n { color:var(--red); }
128
+ .empty { color:var(--dim); font-family:var(--mono); font-size:13px; padding:16px; }
129
+
130
+ /* ===== PRESS-PLAY OPERATOR CONSOLE ===== */
131
  .playwrap { display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
132
  .playbtn {
133
  appearance:none; border:none; cursor:pointer; font-family:var(--sans);
134
  font-weight:800; font-size:22px; letter-spacing:1px; color:#06210f;
135
+ background:linear-gradient(180deg,#5fe6cf,#2bb8a4); border-radius:14px;
136
+ padding:20px 34px; box-shadow:0 0 0 1px #0a3b34, 0 8px 26px rgba(57,216,200,.28);
137
  transition:transform .08s ease, filter .15s ease; min-width:230px;
138
  }
139
  .playbtn:hover{ filter:brightness(1.06); }
 
141
  .playbtn.stop { background:linear-gradient(180deg,#ff8b8b,#e2554f); color:#2a0606; box-shadow:0 0 0 1px #5a1414, 0 8px 26px rgba(248,113,113,.28); }
142
  .playbtn[disabled]{ opacity:.55; cursor:wait; }
143
  .playstate { font-family:var(--mono); font-size:13px; color:var(--dim); }
144
+ .rdot { display:inline-block; width:10px; height:10px; border-radius:50%; margin-right:6px; vertical-align:middle; background:var(--dim); }
145
+ .rdot.on { background:var(--green); box-shadow:0 0 8px var(--green); }
146
+ .rdot.off{ background:var(--red); }
147
 
 
148
  .bignum { font-family:var(--mono); font-weight:800; line-height:1; }
149
  .bignum .val { font-size:clamp(34px,7vw,76px); color:var(--green); letter-spacing:-1px; }
150
  .bignum .unit { font-size:18px; color:var(--dim); margin-left:8px; }
 
176
  .neg-banner.show{ display:inline-flex; animation:pulse 1.6s ease-in-out infinite; }
177
  @keyframes pulse{ 0%,100%{filter:brightness(1)} 50%{filter:brightness(1.35)} }
178
 
 
179
  .feed { max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:10px; }
180
  table.feed-t { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12px; }
181
  table.feed-t th { position:sticky; top:0; background:var(--panel2); color:var(--dim); text-align:left; padding:8px 10px; font-weight:700; letter-spacing:.5px; border-bottom:1px solid var(--line); }
 
185
  .digest { color:var(--dim); }
186
  .cents { color:var(--amber); font-weight:700; }
187
 
 
188
  .foot { margin-top:22px; color:var(--dim); font-size:11px; font-family:var(--mono); line-height:1.7; }
189
  .foot b{ color:var(--ink); }
190
 
191
+ /* Respect reduced-motion: kill pulses, flashes, transitions. */
192
+ @media (prefers-reduced-motion: reduce) {
193
+ *, *::before, *::after { animation:none !important; transition:none !important; }
194
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  </style>
196
  </head>
197
  <body>
198
  <header class="top">
199
  <div class="brand">
200
+ <h1>SZL ENERGY · <span class="tl">LIVE ENERGY DASHBOARD</span></h1>
201
+ <span class="sub">real-time sovereign-mesh telemetry — every number labeled, nothing faked</span>
202
+ </div>
203
+ <div class="topright">
204
+ <div class="mesh-pill" id="mesh-pill">mesh <b id="mesh-up" class="up">…</b>/<span id="mesh-total">…</span> live</div>
205
+ <div class="conn" id="conn">link <b id="conn-state">…</b> · <span id="conn-ts">—</span></div>
206
+ <span class="chip chip-sample" id="global-chip">SAMPLE</span>
207
  </div>
 
208
  </header>
209
 
210
+ <!-- ============================== ROW 1 ============================== -->
211
+ <div class="secthead">
212
+ <span class="n">ROW 1</span>
213
+ <h2>Live Power</h2>
214
+ <span class="sub">on-box NVML watts &middot; per sovereign-mesh node</span>
215
+ </div>
216
+ <div class="grid">
217
+ <section class="card span4">
218
+ <h2>Total live power <span class="chip chip-live" id="total-chip">LIVE</span></h2>
219
+ <div class="gaugewrap">
220
+ <canvas id="gauge-total"></canvas>
221
+ <div class="gauge-read" id="gauge-total-read"><span id="gauge-total-val">—</span><span class="cu">W</span></div>
222
+ </div>
223
+ <div class="gauge-sub" id="gauge-total-sub">awaiting first live sample…</div>
224
+ </section>
225
+
226
+ <section class="card span8">
227
+ <h2>Nodes <span class="chip chip-live" id="nodes-chip">LIVE</span></h2>
228
+ <div class="nodegrid" id="live-nodes"><div class="empty">querying /energy/live …</div></div>
229
+ </section>
230
+ </div>
231
+
232
+ <!-- ============================== ROW 2 ============================== -->
233
+ <div class="secthead">
234
+ <span class="n">ROW 2</span>
235
+ <h2>Streaming Power</h2>
236
+ <span class="sub">total watts &middot; uPlot ring-buffer (~120 pts) &middot; poll every 2s</span>
237
+ </div>
238
+ <div class="grid">
239
+ <section class="card span12">
240
+ <h2>Watts over time <span class="chip chip-measured" id="stream-chip">MEASURED</span></h2>
241
+ <div id="watts-chart"></div>
242
+ <div class="chart-empty" id="watts-empty">awaiting first live sample…</div>
243
+ <div class="chart-note">
244
+ <span id="stream-note">cumulative on-box NVML power, live</span>
245
+ <span class="offline-tag" id="offline-tag">⚠ meter offline — joules NOT fabricated</span>
246
+ </div>
247
+ </section>
248
+ </div>
249
+
250
+ <!-- ============================== ROW 3 ============================== -->
251
+ <div class="secthead">
252
+ <span class="n">ROW 3</span>
253
+ <h2>Bekenstein Budget Ledger</h2>
254
+ <span class="sub">joules_est per task &middot; F19/TH6 proven-inequality gate</span>
255
+ </div>
256
+ <div class="grid">
257
+ <section class="card span12">
258
+ <h2>Energy + information budget <span class="chip chip-estimate" id="budget-chip">ESTIMATE</span></h2>
259
+ <div class="budgetbar">
260
+ <span><span class="k">tasks</span> <span class="v" id="b-tasks">—</span></span>
261
+ <span><span class="k">Σ joules_est</span> <span class="v" id="b-joules">—</span> J</span>
262
+ <span><span class="k">all_within_bound (F19/TH6)</span> <span id="b-gate">—</span></span>
263
+ <span class="k" id="b-gatedef">F19 Bekenstein: proven inequality, not assertion (locked-8)</span>
264
+ </div>
265
+ <div class="tbl-wrap">
266
+ <table class="tbl">
267
+ <thead><tr><th>#</th><th>time</th><th>joules_est</th><th>cumulative J</th><th>shannon bits</th><th>Bekenstein bound</th><th>within bound</th></tr></thead>
268
+ <tbody id="budget-body"><tr><td colspan="7" class="empty">querying /energy/budget …</td></tr></tbody>
269
+ </table>
270
+ </div>
271
+ </section>
272
+ </div>
273
+
274
+ <!-- ====================== PRESS-PLAY OPERATOR CONSOLE ====================== -->
275
+ <div class="secthead">
276
+ <span class="n">OPS</span>
277
+ <h2>Operator Console</h2>
278
+ <span class="sub">press-play control &middot; signed JouleCharge receipts &middot; nothing faked</span>
279
+ </div>
280
  <div class="grid">
281
 
 
282
  <section class="card span7">
283
  <h2>Operator</h2>
284
  <div class="playwrap">
285
  <button class="playbtn" id="play" aria-pressed="false">PRESS PLAY</button>
286
  <div class="playstate">
287
+ <div><span class="rdot" id="run-dot"></span><span id="run-text">querying operator…</span></div>
288
  <div id="run-detail" style="margin-top:6px;color:var(--dim)">—</div>
289
  </div>
290
  </div>
 
302
  </div>
303
  </section>
304
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  <section class="card span7">
306
  <h2>Today · compute done &amp; earnings</h2>
307
  <div class="metrics">
 
336
  <div class="lbl"><span class="chip chip-zero" id="m-real-chip">ZERO</span></div>
337
  </div>
338
  </div>
339
+ <div style="margin-top:10px;color:var(--dim);font-size:11px;font-family:var(--mono)" id="earn-formula">earnings = MODELED dry-run ledger total + projection never MEASURED until a real charge clears</div>
340
  </section>
341
 
 
342
  <section class="card span5">
343
  <h2>Nodes computing now</h2>
344
  <div class="nodes" id="nodes"><div class="empty">querying operator status…</div></div>
345
  </section>
346
 
 
347
  <section class="card span12">
348
  <h2>Grid</h2>
349
  <div class="grid-strip">
 
357
  </div>
358
  </section>
359
 
 
360
  <section class="card span12">
361
  <h2>Live receipt feed · SZL.Energy.JouleCharge.v1 <span class="chip chip-modeled" id="feed-mode">DRY-RUN</span></h2>
362
  <div class="feed">
 
370
  </div>
371
 
372
  <div class="foot">
373
+ <div><b>Honesty (Doctrine v11):</b> watts &amp; joules are <span class="chip chip-measured">MEASURED</span> from the on-box NVML exporter only when it is reachable; when the meter is offline the stream goes dashed and labels flip to <span class="chip chip-unavailable">UNAVAILABLE</span> joules are NOT fabricated. The Bekenstein budget series is <span class="chip chip-estimate">ESTIMATE</span> / <span class="chip chip-sample">SAMPLE</span> (no on-box meter behind the budget layer); F19 Bekenstein is a proven inequality (locked-8), Λ is Conjecture 1.</div>
374
+ <div>Dollars are <span class="chip chip-modeled">MODELED</span> / <span class="chip chip-estimate">ESTIMATE</span> — revenue is <span class="chip chip-zero">ZERO</span> until a real charge clears, and only then <span class="chip chip-measured">MEASURED</span>. No live data → panels show <span class="chip chip-degraded">DEGRADED</span> / NO-LIVE-DATA. 0 runtime CDN · system fonts · uPlot v1.6.32 (MIT) vendored locally.</div>
375
  </div>
376
 
377
+ <script src="/vendor/uPlot.iife.min.js"></script>
378
+ <script>
379
+ "use strict";
380
+ /* ===================================================================
381
+ Shared honesty + fetch helpers (single source of truth on the page).
382
+ =================================================================== */
383
+ var API = "/api/a11oy/v1";
384
+ var REDUCED = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
385
+
386
+ var CHIP = { LIVE:"chip-live", MEASURED:"chip-measured", MODELED:"chip-modeled",
387
+ SAMPLE:"chip-sample", ESTIMATE:"chip-estimate", ZERO:"chip-zero",
388
+ DEGRADED:"chip-degraded", UNAVAILABLE:"chip-unavailable" };
389
+ function setChip(el, label) {
390
+ if (!el) return;
391
+ var key = String(label || "DEGRADED").toUpperCase();
392
+ var cls = CHIP[key] || "chip-degraded";
393
+ el.className = "chip " + cls;
394
+ el.textContent = key;
395
+ }
396
+ function isErr(d) { return !d || d.__error === true; }
397
+ function num(v) { var n = typeof v === "number" ? v : parseFloat(v); return isFinite(n) ? n : null; }
398
+ function fmt(n, dec) {
399
+ if (typeof n !== "number" || !isFinite(n)) return "—";
400
+ return n.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
401
+ }
402
+ function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) {
403
+ return { "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;" }[c]; }); }
404
+ function shortDigest(d) { if (!d) return "—"; var h = String(d).replace(/^sha256:/, ""); return h.slice(0, 12) + "…"; }
405
+ function shortTime(t) { if (!t) return "—"; try { return new Date(t).toLocaleTimeString(); } catch (e) { return String(t); } }
406
+
407
+ function poll(url, opts) {
408
+ opts = opts || {};
409
+ return fetch(url, { method: opts.method || "GET",
410
+ headers: opts.body ? {"content-type":"application/json"} : undefined,
411
+ body: opts.body })
412
+ .then(function (r) {
413
+ if (!r.ok) return { __error: true, status: r.status };
414
+ return r.json().catch(function () { return { __error: true, status: r.status }; });
415
+ })
416
+ .catch(function () { return { __error: true, status: 0 }; });
417
+ }
418
+
419
+ /* connection banner (any live endpoint flips it green) */
420
+ var anyLive = false;
421
+ function markLive(ok) {
422
+ anyLive = anyLive || ok;
423
+ var conn = document.getElementById("conn");
424
+ var st = document.getElementById("conn-state");
425
+ if (ok) { conn.classList.remove("degraded"); st.textContent = "LIVE"; }
426
+ else if (!anyLive) { conn.classList.add("degraded"); st.textContent = "NO-LIVE-DATA"; }
427
+ document.getElementById("conn-ts").textContent = new Date().toLocaleTimeString();
428
+ }
429
+ </script>
430
+
431
  <script>
432
  "use strict";
433
+ /* ===================================================================
434
+ TRI-ROW LIVE DASHBOARD — bound to the REAL live energy endpoints.
435
+ =================================================================== */
436
  (function () {
437
+ // ---- canvas gauge (vanilla, DPR-aware, 0 CDN) ------------------------
438
+ function Gauge(id, opts) {
439
+ opts = opts || {};
440
+ this.c = document.getElementById(id);
441
+ this.ctx = this.c ? this.c.getContext("2d") : null;
442
+ this.max = opts.max || 600; this.cur = 0; this.live = true;
 
 
 
 
 
443
  }
444
+ Gauge.prototype.set = function (v, live) {
445
+ this.live = live !== false;
446
+ this.cur = (typeof v === "number" && isFinite(v)) ? v : 0;
447
+ this.draw();
448
+ };
449
+ Gauge.prototype.draw = function () {
450
+ var c = this.c, ctx = this.ctx; if (!ctx) return;
451
+ var dpr = window.devicePixelRatio || 1;
452
+ var w = c.clientWidth || 240, h = c.clientHeight || 210;
453
+ if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
454
+ c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
455
  }
456
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
457
+ ctx.clearRect(0, 0, w, h);
458
+ var cx = w / 2, cy = h * 0.70, r = Math.min(w * 0.40, h * 0.56);
459
+ var a0 = Math.PI * 0.85, a1 = Math.PI * 2.15;
460
+ ctx.lineWidth = 14; ctx.lineCap = "round";
461
+ ctx.strokeStyle = "#16202e";
462
+ ctx.beginPath(); ctx.arc(cx, cy, r, a0, a1); ctx.stroke();
463
+ if (!this.live) {
464
+ ctx.setLineDash([5, 7]); ctx.strokeStyle = "#5a2222";
465
+ ctx.beginPath(); ctx.arc(cx, cy, r, a0, a1); ctx.stroke();
466
+ ctx.setLineDash([]); return;
467
+ }
468
+ var frac = Math.max(0, Math.min(1, this.cur / this.max));
469
+ var grad = ctx.createLinearGradient(cx - r, 0, cx + r, 0);
470
+ grad.addColorStop(0, "#39d8c8"); grad.addColorStop(0.7, "#34d399"); grad.addColorStop(1, "#fbbf24");
471
+ ctx.strokeStyle = grad;
472
+ ctx.beginPath(); ctx.arc(cx, cy, r, a0, a0 + (a1 - a0) * frac); ctx.stroke();
473
+ };
474
+ var gTotal = new Gauge("gauge-total", { max: 600 });
475
+
476
+ // ---- uPlot streaming watts chart (ring buffer ~120 pts) --------------
477
+ var RING = 120;
478
+ var xs = [], ys = [];
479
+ var t0 = Date.now();
480
+ var chartEl = document.getElementById("watts-chart");
481
+ var emptyEl = document.getElementById("watts-empty");
482
+ var u = null, lastOffline = null;
483
+
484
+ function chartWidth() { return Math.max(320, chartEl.clientWidth || 800); }
485
+
486
+ function buildChart(offline) {
487
+ if (u) { u.destroy(); u = null; }
488
+ var opts = {
489
+ width: chartWidth(),
490
+ height: 260,
491
+ cursor: { y: false },
492
+ legend: { show: false },
493
+ scales: { x: { time: false }, y: { range: function (self, lo, hi) {
494
+ if (hi == null) return [0, 1]; return [0, Math.max(hi * 1.15, 1)]; } } },
495
+ axes: [
496
+ { stroke: "#8aa0bd", grid: { stroke: "#16202e" }, ticks: { stroke: "#16202e" },
497
+ font: "11px ui-monospace, monospace",
498
+ values: function (self, splits) { return splits.map(function (s) { return s + "s"; }); } },
499
+ { stroke: "#8aa0bd", grid: { stroke: "#16202e" }, ticks: { stroke: "#16202e" },
500
+ font: "11px ui-monospace, monospace",
501
+ values: function (self, splits) { return splits.map(function (s) { return s + " W"; }); } }
502
+ ],
503
+ series: [
504
+ {},
505
+ { label: "watts", stroke: offline ? "#f87171" : "#39d8c8", width: 2,
506
+ dash: offline ? [6, 5] : [],
507
+ fill: offline ? "rgba(248,113,113,0.05)" : "rgba(57,216,200,0.12)",
508
+ points: { show: false } }
509
+ ]
510
  };
511
+ u = new uPlot(opts, [xs, ys], chartEl);
512
+ }
513
+
514
+ function ensureChart(offline) {
515
+ if (u == null || lastOffline !== offline) { buildChart(offline); lastOffline = offline; }
516
  }
 
517
 
518
+ function pushSample(watts, offline) {
519
+ var t = Math.round((Date.now() - t0) / 1000);
520
+ xs.push(t);
521
+ ys.push(offline ? null : watts); // honest gap on offline — never a fabricated zero
522
+ if (xs.length > RING) { xs.shift(); ys.shift(); }
523
+ ensureChart(offline);
524
+ if (emptyEl && xs.length >= 1) emptyEl.style.display = "none";
525
+ u.setData([xs, ys]);
526
+ }
527
+
528
+ window.addEventListener("resize", function () { if (u) u.setSize({ width: chartWidth(), height: 260 }); });
529
+
530
+ // ---- ROW 1 + ROW 2: /energy/live -------------------------------------
531
+ function renderLive(d) {
532
+ markLive(!isErr(d));
533
+ var offline = isErr(d) || d.label === "UNAVAILABLE" ||
534
+ (d.meter_status && d.meter_status !== 200) || d.total_watts == null;
535
+
536
+ // global + per-section chips
537
+ setChip(document.getElementById("global-chip"), offline ? "UNAVAILABLE" : "LIVE");
538
+ setChip(document.getElementById("total-chip"), offline ? "UNAVAILABLE" : "LIVE");
539
+ setChip(document.getElementById("nodes-chip"), offline ? "UNAVAILABLE" : "LIVE");
540
+ setChip(document.getElementById("stream-chip"), offline ? "UNAVAILABLE" : (d.joules_label || "MEASURED"));
541
+
542
+ // total power gauge
543
+ var read = document.getElementById("gauge-total-read");
544
+ var valEl = document.getElementById("gauge-total-val");
545
+ var subEl = document.getElementById("gauge-total-sub");
546
+ var tw = offline ? null : num(d.total_watts);
547
+ if (tw == null) {
548
+ valEl.textContent = "—"; read.classList.add("off");
549
+ gTotal.set(0, false);
550
+ subEl.textContent = offline ? (d && d.note ? d.note : "meter offline — joules NOT fabricated") : "no live watts";
551
+ } else {
552
+ valEl.textContent = fmt(tw, 1); read.classList.remove("off");
553
+ gTotal.set(tw, true);
554
+ var tj = num(d.total_joules);
555
+ subEl.textContent = "Σ joules " + (tj != null ? fmt(tj, 1) + " J" : "—") + " · " + esc(d.exporter || "NVML");
556
+ }
557
+
558
+ // streaming chart
559
+ document.getElementById("offline-tag").classList.toggle("show", offline);
560
+ document.getElementById("stream-note").textContent = offline
561
+ ? "stream paused — gaps, not fabricated values"
562
+ : "cumulative on-box NVML power, live";
563
+ pushSample(tw, offline);
564
+
565
+ // per-node cards
566
+ renderNodeCards(d, offline);
567
+ }
568
+
569
+ function renderNodeCards(d, offline) {
570
+ var box = document.getElementById("live-nodes");
571
+ var nodes = (d && !isErr(d) && Array.isArray(d.nodes)) ? d.nodes : [];
572
+ if (!nodes.length) {
573
+ box.innerHTML = '<div class="empty"><span class="chip chip-unavailable">UNAVAILABLE</span> no node telemetry — meter offline, nothing fabricated</div>';
574
+ return;
575
+ }
576
+ box.innerHTML = "";
577
+ nodes.forEach(function (n) {
578
+ var live = n.live === true && !offline;
579
+ var watts = num(n.watts);
580
+ var div = document.createElement("div");
581
+ div.className = "ncard " + (live ? "live" : "down");
582
+ var lbl = (watts != null && live) ? "MEASURED" : "UNAVAILABLE";
583
+ var joules = num(n.joules);
584
+ var meta = "src " + esc(n.source || "—");
585
+ if (joules != null) meta += " · " + fmt(joules, 1) + " J";
586
+ div.innerHTML =
587
+ '<div class="nm"><span class="dot ' + (live ? "on" : "off") + '"></span>' + esc(n.name || "—") +
588
+ ' <span class="chip ' + (CHIP[lbl] || "chip-unavailable") + '">' + lbl + '</span></div>' +
589
+ '<div class="watt">' + (watts != null && live ? fmt(watts, 1) : "—") + '<span class="u">W</span></div>' +
590
+ '<div class="meta">' + meta + ' · ' + (live ? "live" : "down") + '</div>';
591
+ box.appendChild(div);
592
+ });
593
+ }
594
+
595
+ // ---- mesh health: /govern/health -------------------------------------
596
+ function renderMesh(d) {
597
+ var up = document.getElementById("mesh-up");
598
+ var tot = document.getElementById("mesh-total");
599
+ if (isErr(d)) { up.textContent = "0"; up.className = "down"; tot.textContent = "?"; return; }
600
+ var live = num(d.engines_live); var total = num(d.engines_total);
601
+ up.textContent = (live != null ? live : "?");
602
+ tot.textContent = (total != null ? total : "?");
603
+ up.className = (live != null && total != null && live >= total) ? "up" : (live ? "up" : "down");
604
+ }
605
+
606
+ // ---- ROW 3: /energy/budget (Bekenstein ledger) -----------------------
607
+ function renderBudget(d) {
608
+ markLive(!isErr(d));
609
+ var body = document.getElementById("budget-body");
610
+ var chip = document.getElementById("budget-chip");
611
+ if (isErr(d)) {
612
+ setChip(chip, "DEGRADED");
613
+ body.innerHTML = '<tr><td colspan="7" class="empty"><span class="chip chip-degraded">DEGRADED</span> budget unreachable — NO-LIVE-DATA</td></tr>';
614
+ document.getElementById("b-gate").innerHTML = '<span class="gate-bad">—</span>';
615
+ return;
616
+ }
617
+ // honest label from the budget layer (no on-box meter -> SAMPLE/ESTIMATE)
618
+ var lbl = String(d.joules_label || d.total_joules_est_label || "ESTIMATE").toUpperCase();
619
+ setChip(chip, (CHIP[lbl] ? lbl : "ESTIMATE"));
620
+
621
+ document.getElementById("b-tasks").textContent = (num(d.task_count) != null ? fmt(d.task_count, 0) : "0");
622
+ document.getElementById("b-joules").textContent = (num(d.total_joules_est) != null ? fmt(d.total_joules_est, 3) : "0");
623
+ if (d.gate) document.getElementById("b-gatedef").textContent = String(d.gate);
624
+ var gateEl = document.getElementById("b-gate");
625
+ if (d.all_within_bound === true) gateEl.innerHTML = '<span class="gate-ok">✓ all within bound</span>';
626
+ else if (d.all_within_bound === false) gateEl.innerHTML = '<span class="gate-bad">✗ breach</span>';
627
+ else gateEl.innerHTML = '<span class="within-y">—</span>';
628
+
629
+ var series = Array.isArray(d.series) ? d.series : [];
630
+ if (!series.length) {
631
+ // honest EMPTY state — never fabricate a row
632
+ body.innerHTML = '<tr><td colspan="7" class="empty">budget EMPTY — no tasks metered yet (gate VERIFIED on first task). Nothing fabricated.</td></tr>';
633
+ return;
634
+ }
635
+ var rows = series.slice(-50).reverse();
636
+ body.innerHTML = "";
637
+ var base = series.length;
638
+ rows.forEach(function (r, i) {
639
+ var within = r.within_bound === true;
640
+ var tr = document.createElement("tr");
641
+ tr.innerHTML =
642
+ "<td>" + (base - i) + "</td>" +
643
+ "<td>" + esc(shortTime(r.ts)) + "</td>" +
644
+ "<td>" + fmt(num(r.joules_est), 3) + "</td>" +
645
+ "<td>" + fmt(num(r.cumulative_joules_est), 3) + "</td>" +
646
+ "<td>" + (num(r.shannon_bits) != null ? fmt(r.shannon_bits, 2) : "—") + "</td>" +
647
+ "<td>" + (num(r.bekenstein_bound_bits) != null ? fmt(r.bekenstein_bound_bits, 0) : "—") + "</td>" +
648
+ '<td class="' + (within ? "within-y" : "within-n") + '">' + (within ? "✓ within" : "✗ breach") + "</td>";
649
+ body.appendChild(tr);
650
+ });
651
+ }
652
+
653
+ // ---- poll loops ------------------------------------------------------
654
+ function tickLive() { poll(API + "/energy/live").then(renderLive); }
655
+ function tickMesh() { poll(API + "/govern/health").then(renderMesh); }
656
+ function tickBudget() { poll(API + "/energy/budget").then(renderBudget); }
657
+
658
+ tickLive(); tickMesh(); tickBudget();
659
+ setInterval(tickLive, 2000); // streaming cadence (1-5s window)
660
+ setInterval(tickMesh, 5000);
661
+ setInterval(tickBudget, 5000);
662
+ })();
663
+ </script>
664
+
665
+ <script>
666
+ "use strict";
667
+ /* ===================================================================
668
+ PRESS-PLAY OPERATOR CONSOLE — live operator control + signed receipts.
669
+ Reuses the page-level helpers (setChip / isErr / poll / markLive).
670
+ =================================================================== */
671
+ (function () {
672
+ // ---- smooth counter (eases displayed value toward a target) ----------
673
  function Counter(el, opts) {
674
  opts = opts || {};
675
  this.el = el; this.cur = 0; this.target = 0; this.decimals = opts.decimals || 0;
 
678
  Counter.prototype.set = function (v) {
679
  if (typeof v !== "number" || !isFinite(v)) return;
680
  this.target = v;
681
+ if (!this.started || REDUCED) { this.cur = v; this.started = true; this.render(); }
682
  };
683
  Counter.prototype.tick = function () {
684
  if (!this.started) return;
685
  var d = this.target - this.cur;
686
+ if (REDUCED || Math.abs(d) < Math.pow(10, -this.decimals) / 2) { this.cur = this.target; }
687
  else { this.cur += d * 0.18; }
688
  this.render();
689
  };
690
+ Counter.prototype.render = function () { this.el.textContent = fmt(this.cur, this.decimals); };
 
 
 
 
 
 
691
 
692
  var bigJoules = new Counter(document.getElementById("big-joules"), { decimals: 3 });
693
  var mTokens = new Counter(document.getElementById("m-tokens"), { decimals: 0 });
694
  var mJobs = new Counter(document.getElementById("m-jobs"), { decimals: 0 });
695
  var mJoules = new Counter(document.getElementById("m-joules"), { decimals: 1 });
696
+ if (!REDUCED) setInterval(function () { bigJoules.tick(); mTokens.tick(); mJobs.tick(); mJoules.tick(); }, 33);
 
 
 
 
 
 
 
 
 
 
 
 
 
697
 
698
  // ===== OPERATOR: PLAY / STOP + status ==================================
699
+ var running = null;
700
  var playBtn = document.getElementById("play");
701
  var busy = false;
702
 
 
705
  var txt = document.getElementById("run-text");
706
  var det = document.getElementById("run-detail");
707
  if (isErr(status)) {
708
+ running = null; dot.className = "rdot off";
709
  txt.textContent = "operator DEGRADED — NO-LIVE-DATA";
710
  det.innerHTML = '<span class="chip chip-degraded">DEGRADED</span> operator status unreachable';
711
  playBtn.textContent = "PRESS PLAY"; playBtn.classList.remove("stop");
 
713
  return;
714
  }
715
  running = (status.running === true) || (status.state === "running") || (status.status === "running");
716
+ dot.className = "rdot " + (running ? "on" : "off");
717
  if (running) {
718
  txt.textContent = "RUNNING — computing non-stop";
719
  playBtn.textContent = "STOP"; playBtn.classList.add("stop");
 
736
  }
737
 
738
  playBtn.addEventListener("click", function () {
739
+ if (busy) return;
740
  var action = running ? "stop" : "start";
741
  busy = true; playBtn.disabled = true;
742
  poll(API + "/energy/operator/" + action, { method: "POST", body: "{}" })
 
747
  '<span class="chip chip-degraded">DEGRADED</span> ' + action + " failed (operator unreachable)";
748
  return;
749
  }
 
750
  running = (action === "start");
751
  refreshStatus();
752
  });
 
764
  box.innerHTML = '<div class="empty">no node telemetry — <span class="chip chip-degraded">DEGRADED</span></div>';
765
  return;
766
  }
 
 
767
  var ns = (status.node_status && typeof status.node_status === "object") ? status.node_status : null;
768
  var byNode = status.by_node || {};
769
  var names = ns ? Object.keys(ns) : [];
 
812
  }
813
  var receipts = d.receipts || d.entries || d.ledger || [];
814
  var dryRunTotal = 0, anyCharged = false;
 
815
  var mode = (String(d.stripe_mode || d.mode || "").toLowerCase() === "live") ? "LIVE" : "DRY-RUN";
816
  var feedMode = document.getElementById("feed-mode");
817
  feedMode.textContent = mode;
818
  feedMode.className = "chip " + (mode === "LIVE" ? "chip-measured" : "chip-modeled");
819
 
 
820
  var rows = receipts.slice().reverse();
821
  if (rows.length && body.querySelector(".empty")) body.innerHTML = "";
822
  rows.forEach(function (r) {
 
849
  mReal.textContent = "$" + (realCents / 100).toFixed(2);
850
  setChip(document.getElementById("m-real-chip"), realCents > 0 ? "MEASURED" : "ZERO");
851
  mReal.style.color = realCents > 0 ? "var(--green)" : "var(--dim)";
 
 
 
852
  });
853
  }
854
 
 
871
  if (tokens != null) mTokens.set(tokens);
872
  if (jobs != null) mJobs.set(jobs);
873
  if (joules != null) { mJoules.set(joules); bigJoules.set(joules); }
 
874
  });
875
  }
876
 
 
886
  var joules = num((cd.joules && cd.joules.value != null) ? cd.joules.value
887
  : (p.joules && p.joules.value != null ? p.joules.value
888
  : (p.joules != null ? p.joules : (p.joules_projected != null ? p.joules_projected : p.joules_1d))));
 
889
  var earn = p.earnings || {};
890
  var resale = earn.compute_resale_usd || {};
891
  var earnDollars = num(resale.value != null ? resale.value
 
922
  else {
923
  priceEl.textContent = price.toFixed(2) + " EUR/MWh";
924
  priceEl.style.color = price < 0 ? "var(--green)" : "var(--ink)";
 
925
  setChip(chip, (d.measured_any ? "MEASURED" : "SAMPLE"));
926
  banner.classList.toggle("show", price < 0);
927
  }
 
946
  return null;
947
  }
948
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
949
  // ---- poll loop ---------------------------------------------------------
950
  function tickFast() { refreshStatus(); refreshToday(); refreshLedger(); }
951
  function tickSlow() { refreshProjection(); refreshGrid(); }