a11oy / pages /energy-ops.html
betterwithage's picture
chore(sync): mirror front-door files to Space (hf-sync)
5dd0077 verified
Raw
History Blame
38.9 kB
<!DOCTYPE html>
<!--
SZL Energy — press-play "Today" operational console (WarHacker-grade, live).
Doctrine v11: every value carries a MEASURED / MODELED / SAMPLE / ESTIMATE
honesty chip. Revenue is NEVER shown MEASURED until a real charge clears.
0 runtime CDN, system fonts only. Degrades to NO-LIVE-DATA on 404/error —
never fabricates joules, tokens, or dollars.
Reads live endpoints (Dev1/2/3 contracts):
POST /api/a11oy/v1/energy/operator/start (press play)
POST /api/a11oy/v1/energy/operator/stop (stop)
GET /api/a11oy/v1/energy/operator/status (node status, running flag)
GET /api/a11oy/v1/energy/ledger (signed JouleCharge receipts)
GET /api/a11oy/v1/energy/projection?window=running (1-day MODELED headline)
GET /api/a11oy/v1/harvest/posture (grid price + negative window)
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SZL Energy — Today (press play)</title>
<style>
/* System fonts only — 0 CDN, 0 webfont. */
:root {
--bg:#070b12; --panel:#0d141f; --panel2:#111b29; --line:#1c2a3c;
--ink:#e8eef7; --dim:#8aa0bd; --gold:#d4a444; --green:#34d399;
--red:#f87171; --amber:#fbbf24; --blue:#60a5fa; --mag:#c084fc;
--mono: ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing:border-box; }
html,body { margin:0; background:var(--bg); color:var(--ink); font-family:var(--sans); }
body { padding:18px clamp(12px,3vw,34px) 60px; }
a { color:var(--blue); }
header.top { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-bottom:14px; }
.brand { display:flex; align-items:baseline; gap:12px; }
.brand h1 { font-size:clamp(18px,2.4vw,26px); margin:0; letter-spacing:.5px; }
.brand .sub { color:var(--dim); font-size:13px; }
.conn { font-family:var(--mono); font-size:12px; color:var(--dim); }
.conn b { color:var(--green); }
.conn.degraded b { color:var(--amber); }
/* honesty chip — 4 states (replicates szl3d_label) */
.chip {
display:inline-block; font-family:var(--mono); font-size:10px; font-weight:700;
letter-spacing:.6px; padding:2px 6px; border-radius:4px; vertical-align:middle;
border:1px solid currentColor; line-height:1.2; text-transform:uppercase;
}
.chip-measured { color:var(--green); }
.chip-modeled { color:var(--amber); }
.chip-sample { color:var(--blue); }
.chip-estimate { color:var(--mag); }
.chip-zero { color:var(--dim); }
.chip-degraded { color:var(--red); }
.grid { display:grid; gap:14px; grid-template-columns:repeat(12,1fr); }
.card { background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:16px 18px; }
.card h2 { margin:0 0 10px; font-size:12px; letter-spacing:1.4px; text-transform:uppercase; color:var(--gold); font-weight:700; }
.span12{grid-column:span 12} .span8{grid-column:span 8} .span6{grid-column:span 6}
.span5{grid-column:span 5} .span4{grid-column:span 4} .span7{grid-column:span 7}
@media (max-width:880px){ .span4,.span5,.span6,.span7,.span8{grid-column:span 12} }
/* PRESS PLAY */
.playwrap { display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
.playbtn {
appearance:none; border:none; cursor:pointer; font-family:var(--sans);
font-weight:800; font-size:22px; letter-spacing:1px; color:#06210f;
background:linear-gradient(180deg,#5fe6a8,#2bb877); border-radius:14px;
padding:20px 34px; box-shadow:0 0 0 1px #0a3b27, 0 8px 26px rgba(52,211,153,.28);
transition:transform .08s ease, filter .15s ease; min-width:230px;
}
.playbtn:hover{ filter:brightness(1.06); }
.playbtn:active{ transform:translateY(1px); }
.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); }
.playbtn[disabled]{ opacity:.55; cursor:wait; }
.playstate { font-family:var(--mono); font-size:13px; color:var(--dim); }
.dot { display:inline-block; width:10px; height:10px; border-radius:50%; margin-right:6px; vertical-align:middle; background:var(--dim); }
.dot.on { background:var(--green); box-shadow:0 0 8px var(--green); }
.dot.off{ background:var(--red); }
/* THE BIG NUMBER */
.bignum { font-family:var(--mono); font-weight:800; line-height:1; }
.bignum .val { font-size:clamp(34px,7vw,76px); color:var(--green); letter-spacing:-1px; }
.bignum .unit { font-size:18px; color:var(--dim); margin-left:8px; }
.bigrow { display:flex; align-items:flex-end; gap:14px; flex-wrap:wrap; }
.proj { font-family:var(--mono); font-size:clamp(15px,2.2vw,22px); color:var(--amber); }
.proj .pv { font-weight:800; }
.metrics { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
@media (max-width:560px){ .metrics{grid-template-columns:1fr} }
.metric { background:var(--panel2); border:1px solid var(--line); border-radius:10px; padding:12px 14px; }
.metric .k { font-size:11px; color:var(--dim); text-transform:uppercase; letter-spacing:1px; margin-bottom:6px; }
.metric .v { font-family:var(--mono); font-size:clamp(20px,3.2vw,30px); font-weight:800; }
.metric .lbl { margin-top:6px; }
.nodes { display:flex; gap:10px; flex-wrap:wrap; }
.node { background:var(--panel2); border:1px solid var(--line); border-radius:10px; padding:10px 14px; min-width:170px; }
.node .nm { font-family:var(--mono); font-weight:700; }
.node .st { font-size:12px; color:var(--dim); margin-top:4px; }
.node.computing{ border-color:#1d4d39; box-shadow:0 0 0 1px #143a2a inset; }
.node.degraded { border-color:#5a2222; }
.grid-strip { display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
.grid-price { font-family:var(--mono); font-size:clamp(22px,3.6vw,34px); font-weight:800; }
.neg-banner {
display:none; align-items:center; gap:8px; padding:8px 14px; border-radius:10px;
background:linear-gradient(90deg,#0e2a1d,#13402b); border:1px solid #1f6b48;
color:var(--green); font-weight:800; font-family:var(--mono); font-size:14px;
}
.neg-banner.show{ display:inline-flex; animation:pulse 1.6s ease-in-out infinite; }
@keyframes pulse{ 0%,100%{filter:brightness(1)} 50%{filter:brightness(1.35)} }
/* RECEIPT FEED */
.feed { max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:10px; }
table.feed-t { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12px; }
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); }
table.feed-t td { padding:7px 10px; border-bottom:1px solid #16202e; }
table.feed-t tr.new td { animation:flashin 1.2s ease; }
@keyframes flashin{ from{ background:#13402b; } to{ background:transparent; } }
.digest { color:var(--dim); }
.cents { color:var(--amber); font-weight:700; }
.empty { color:var(--dim); font-family:var(--mono); font-size:13px; padding:14px; }
.foot { margin-top:22px; color:var(--dim); font-size:11px; font-family:var(--mono); line-height:1.7; }
.foot b{ color:var(--ink); }
/* ===== LIVE GRAPHS (vanilla canvas, 0 CDN) ===== */
.chartwrap { position:relative; width:100%; height:210px; }
.chartwrap.sm { height:150px; }
.chartwrap canvas, .gaugewrap canvas { width:100%; height:100%; display:block; }
.chart-empty { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
color:var(--dim); font-family:var(--mono); font-size:12px; pointer-events:none; text-align:center; padding:0 12px; }
.chart-empty.hide { display:none; }
.chart-read { position:absolute; top:6px; right:12px; font-family:var(--mono); font-weight:800;
font-size:22px; color:var(--green); text-shadow:0 0 14px rgba(52,211,153,.35); }
.chart-read .cu { font-size:12px; color:var(--dim); margin-left:3px; font-weight:700; }
.chart-axis { display:flex; justify-content:space-between; align-items:center; margin-top:7px;
font-family:var(--mono); font-size:10px; color:var(--dim); letter-spacing:.4px; }
.chart-axis .mid { color:#6b809c; }
.gaugewrap { position:relative; width:100%; height:210px; }
.gauge-read { position:absolute; left:0; right:0; bottom:30px; text-align:center;
font-family:var(--mono); font-weight:800; font-size:30px; color:var(--green); }
.gauge-read .cu { font-size:13px; color:var(--dim); margin-left:4px; font-weight:700; }
.chart-sub { text-align:center; color:var(--dim); font-family:var(--mono); font-size:11px; margin-top:6px; }
</style>
</head>
<body>
<header class="top">
<div class="brand">
<h1>SZL ENERGY · <span style="color:var(--gold)">TODAY</span></h1>
<span class="sub">press-play operational console — every number labeled, nothing faked</span>
</div>
<div class="conn" id="conn">link <b id="conn-state"></b> · <span id="conn-ts"></span></div>
</header>
<div class="grid">
<!-- PRESS PLAY + THE BIG NUMBER -->
<section class="card span7">
<h2>Operator</h2>
<div class="playwrap">
<button class="playbtn" id="play" aria-pressed="false">PRESS PLAY</button>
<div class="playstate">
<div><span class="dot" id="run-dot"></span><span id="run-text">querying operator…</span></div>
<div id="run-detail" style="margin-top:6px;color:var(--dim)"></div>
</div>
</div>
</section>
<section class="card span5">
<h2>Big number · measured joules</h2>
<div class="bigrow">
<div class="bignum"><span class="val" id="big-joules"></span><span class="unit">J</span></div>
<span class="chip chip-measured" id="big-joules-chip">MEASURED</span>
</div>
<div class="proj" style="margin-top:12px">
1-day projection: <span class="pv" id="proj-joules"></span> J
<span class="chip chip-modeled">MODELED</span>
</div>
</section>
<!-- ===== LIVE GRAPHS (founder #1: measured joules climbing) ===== -->
<section class="card span8">
<h2>Live · measured joules climbing <span class="chip chip-measured">MEASURED</span></h2>
<div class="chartwrap">
<canvas id="chart-joules"></canvas>
<div class="chart-empty" id="chart-joules-empty">awaiting first live sample…</div>
<div class="chart-read"><span id="chart-joules-val"></span><span class="cu">J</span></div>
</div>
<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>
</section>
<section class="card span4">
<h2>GPU power now <span class="chip chip-measured">MEASURED</span></h2>
<div class="gaugewrap">
<canvas id="gauge-power"></canvas>
<div class="gauge-read"><span id="gauge-power-val"></span><span class="cu">W</span></div>
</div>
<div class="chart-sub" id="gauge-power-sub">computing node —</div>
</section>
<section class="card span6">
<h2>GPU power trend <span class="chip chip-measured">MEASURED</span></h2>
<div class="chartwrap sm">
<canvas id="chart-power"></canvas>
<div class="chart-empty" id="chart-power-empty">awaiting first live sample…</div>
<div class="chart-read"><span id="chart-power-val"></span><span class="cu">W</span></div>
</div>
</section>
<section class="card span6">
<h2>Throughput · tokens / sec <span class="chip chip-measured">MEASURED</span></h2>
<div class="chartwrap sm">
<canvas id="chart-tps"></canvas>
<div class="chart-empty" id="chart-tps-empty">measuring rate from live deltas…</div>
<div class="chart-read"><span id="chart-tps-val"></span><span class="cu">tok/s</span></div>
</div>
</section>
<!-- TODAY: compute done + earnings -->
<section class="card span7">
<h2>Today · compute done &amp; earnings</h2>
<div class="metrics">
<div class="metric">
<div class="k">Tokens generated</div>
<div class="v" id="m-tokens"></div>
<div class="lbl"><span class="chip chip-measured">MEASURED</span></div>
</div>
<div class="metric">
<div class="k">Jobs completed</div>
<div class="v" id="m-jobs"></div>
<div class="lbl"><span class="chip chip-measured">MEASURED</span></div>
</div>
<div class="metric">
<div class="k">Joules</div>
<div class="v" id="m-joules"></div>
<div class="lbl"><span class="chip chip-measured">MEASURED</span></div>
</div>
<div class="metric">
<div class="k">Earnings so-far (dry-run)</div>
<div class="v" id="m-earn" style="color:var(--amber)"></div>
<div class="lbl"><span class="chip chip-modeled" id="m-earn-chip">MODELED</span></div>
</div>
<div class="metric">
<div class="k">Projected · 1 day (resale)</div>
<div class="v" id="m-proj-earn" style="color:var(--amber)"></div>
<div class="lbl"><span class="chip chip-estimate">ESTIMATE</span></div>
</div>
<div class="metric">
<div class="k">Real cleared revenue</div>
<div class="v" id="m-real" style="color:var(--dim)">$0.00</div>
<div class="lbl"><span class="chip chip-zero" id="m-real-chip">ZERO</span></div>
</div>
</div>
<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>
</section>
<!-- NODE STATUS -->
<section class="card span5">
<h2>Nodes computing now</h2>
<div class="nodes" id="nodes"><div class="empty">querying operator status…</div></div>
</section>
<!-- GRID STRIP -->
<section class="card span12">
<h2>Grid</h2>
<div class="grid-strip">
<div>
<div style="font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:1px">Price now</div>
<div class="grid-price" id="grid-price"></div>
</div>
<div><span class="chip" id="grid-chip"></span></div>
<div class="neg-banner" id="neg-banner">⚡ THE GRID IS PAYING US TO COMPUTE</div>
<div style="margin-left:auto;font-family:var(--mono);font-size:12px;color:var(--dim)" id="grid-posture">posture: —</div>
</div>
</section>
<!-- RECEIPT FEED -->
<section class="card span12">
<h2>Live receipt feed · SZL.Energy.JouleCharge.v1 <span class="chip chip-modeled" id="feed-mode">DRY-RUN</span></h2>
<div class="feed">
<table class="feed-t">
<thead><tr><th>time</th><th>node</th><th>joules</th><th>would-charge</th><th>digest</th></tr></thead>
<tbody id="feed-body"><tr><td colspan="5" class="empty">no receipts yet — press play to mint the first JouleCharge</td></tr></tbody>
</table>
</div>
</section>
</div>
<div class="foot">
<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>
<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>
</div>
<script>
"use strict";
(function () {
var API = "/api/a11oy/v1";
// ---- honesty chip helper (4-state; replicates szl3d_label) -------------
var CHIP = { MEASURED:"chip-measured", MODELED:"chip-modeled", SAMPLE:"chip-sample",
ESTIMATE:"chip-estimate", ZERO:"chip-zero", DEGRADED:"chip-degraded" };
function setChip(el, label) {
if (!el) return;
var key = String(label || "DEGRADED").toUpperCase();
var cls = CHIP[key] || "chip-degraded";
el.className = "chip " + cls;
el.textContent = key;
}
// ---- live poll helper (reuses szl3d_live.poll contract if present) -----
// Falls back to a local implementation so the page is self-contained.
function getPoller() {
if (window.szl3d_live && typeof window.szl3d_live.poll === "function") {
return window.szl3d_live.poll;
}
return function (url, opts) {
opts = opts || {};
return fetch(url, { method: opts.method || "GET",
headers: opts.body ? {"content-type":"application/json"} : undefined,
body: opts.body })
.then(function (r) {
if (!r.ok) return { __error: true, status: r.status };
return r.json().catch(function () { return { __error: true, status: r.status }; });
})
.catch(function () { return { __error: true, status: 0 }; });
};
}
var poll = getPoller();
// ---- smooth counter (eases displayed value toward a target) ------------
function Counter(el, opts) {
opts = opts || {};
this.el = el; this.cur = 0; this.target = 0; this.decimals = opts.decimals || 0;
this.started = false;
}
Counter.prototype.set = function (v) {
if (typeof v !== "number" || !isFinite(v)) return;
this.target = v;
if (!this.started) { this.cur = v; this.started = true; this.render(); }
};
Counter.prototype.tick = function () {
if (!this.started) return;
var d = this.target - this.cur;
if (Math.abs(d) < Math.pow(10, -this.decimals) / 2) { this.cur = this.target; }
else { this.cur += d * 0.18; }
this.render();
};
Counter.prototype.render = function () {
this.el.textContent = fmt(this.cur, this.decimals);
};
function fmt(n, dec) {
if (typeof n !== "number" || !isFinite(n)) return "—";
return n.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
}
var bigJoules = new Counter(document.getElementById("big-joules"), { decimals: 3 });
var mTokens = new Counter(document.getElementById("m-tokens"), { decimals: 0 });
var mJobs = new Counter(document.getElementById("m-jobs"), { decimals: 0 });
var mJoules = new Counter(document.getElementById("m-joules"), { decimals: 1 });
setInterval(function () { bigJoules.tick(); mTokens.tick(); mJobs.tick(); mJoules.tick(); }, 33);
// ---- connection banner -------------------------------------------------
var anyLive = false;
function markLive(ok) {
anyLive = anyLive || ok;
var conn = document.getElementById("conn");
var st = document.getElementById("conn-state");
if (ok) { conn.classList.remove("degraded"); st.textContent = "LIVE"; }
else if (!anyLive) { conn.classList.add("degraded"); st.textContent = "NO-LIVE-DATA"; }
document.getElementById("conn-ts").textContent = new Date().toLocaleTimeString();
}
function isErr(d) { return !d || d.__error === true; }
// ===== OPERATOR: PLAY / STOP + status ==================================
var running = null; // unknown
var playBtn = document.getElementById("play");
var busy = false;
function renderRun(status) {
var dot = document.getElementById("run-dot");
var txt = document.getElementById("run-text");
var det = document.getElementById("run-detail");
if (isErr(status)) {
running = null; dot.className = "dot off";
txt.textContent = "operator DEGRADED — NO-LIVE-DATA";
det.innerHTML = '<span class="chip chip-degraded">DEGRADED</span> operator status unreachable';
playBtn.textContent = "PRESS PLAY"; playBtn.classList.remove("stop");
playBtn.setAttribute("aria-pressed", "false");
return;
}
running = (status.running === true) || (status.state === "running") || (status.status === "running");
dot.className = "dot " + (running ? "on" : "off");
if (running) {
txt.textContent = "RUNNING — computing non-stop";
playBtn.textContent = "STOP"; playBtn.classList.add("stop");
playBtn.setAttribute("aria-pressed", "true");
} else {
txt.textContent = "STOPPED — idle";
playBtn.textContent = "PRESS PLAY"; playBtn.classList.remove("stop");
playBtn.setAttribute("aria-pressed", "false");
}
var nodeNames = (status.node_status && typeof status.node_status === "object")
? Object.keys(status.node_status)
: (status.nodes_computing || []).concat(status.nodes_standby || [], status.nodes_degraded || []);
var n = nodeNames.length;
var computing = (status.nodes_computing || []).length;
var jobsField = status.jobs_running != null ? status.jobs_running
: (status.active_jobs != null ? status.active_jobs : null);
det.textContent = "nodes: " + n + " · computing: " + computing +
(jobsField != null ? (" · active jobs: " + jobsField) : "");
renderNodes(status);
}
playBtn.addEventListener("click", function () {
if (busy || running === null && false) {}
var action = running ? "stop" : "start";
busy = true; playBtn.disabled = true;
poll(API + "/energy/operator/" + action, { method: "POST", body: "{}" })
.then(function (d) {
busy = false; playBtn.disabled = false;
if (isErr(d)) {
document.getElementById("run-detail").innerHTML =
'<span class="chip chip-degraded">DEGRADED</span> ' + action + " failed (operator unreachable)";
return;
}
// optimistic; status poll will confirm
running = (action === "start");
refreshStatus();
});
});
function refreshStatus() {
return poll(API + "/energy/operator/status").then(function (d) {
markLive(!isErr(d)); renderRun(d);
});
}
function renderNodes(status) {
var box = document.getElementById("nodes");
if (!status || isErr(status)) {
box.innerHTML = '<div class="empty">no node telemetry — <span class="chip chip-degraded">DEGRADED</span></div>';
return;
}
// operator/status contract: node_status {name:state}, by_node {name:{jobs,joules,tokens}},
// plus nodes_computing / nodes_standby / nodes_degraded arrays.
var ns = (status.node_status && typeof status.node_status === "object") ? status.node_status : null;
var byNode = status.by_node || {};
var names = ns ? Object.keys(ns) : [];
if (!names.length) {
var set = {};
(status.nodes_computing || []).forEach(function (n) { set[n] = "computing"; });
(status.nodes_standby || []).forEach(function (n) { set[n] = "standby"; });
(status.nodes_degraded || []).forEach(function (n) { set[n] = "degraded"; });
ns = set; names = Object.keys(set);
}
if (!names.length) {
box.innerHTML = '<div class="empty">no node telemetry — <span class="chip chip-degraded">DEGRADED</span></div>';
return;
}
box.innerHTML = "";
names.forEach(function (name) {
var state = String(ns[name] || "idle").toLowerCase();
var computing = state === "computing";
var degraded = state === "degraded";
var div = document.createElement("div");
div.className = "node " + (computing ? "computing" : (degraded ? "degraded" : ""));
var chip = computing ? '<span class="chip chip-measured">COMPUTING</span>'
: degraded ? '<span class="chip chip-degraded">DEGRADED</span>'
: state === "standby" ? '<span class="chip chip-sample">STANDBY</span>'
: '<span class="chip chip-sample">IDLE</span>';
var bn = byNode[name] || {};
var detail = "";
if (bn.joules_measured != null) detail += " · " + fmt(num(bn.joules_measured), 1) + " J";
if (bn.tokens != null) detail += " · " + fmt(num(bn.tokens), 0) + " tok";
div.innerHTML = '<div class="nm">' + esc(name) + "</div>" +
'<div class="st">' + chip + detail + "</div>";
box.appendChild(div);
});
}
// ===== LEDGER: receipt feed + today totals =============================
var seen = {};
function refreshLedger() {
return poll(API + "/energy/ledger").then(function (d) {
markLive(!isErr(d));
var body = document.getElementById("feed-body");
if (isErr(d)) {
if (!Object.keys(seen).length)
body.innerHTML = '<tr><td colspan="5" class="empty"><span class="chip chip-degraded">DEGRADED</span> ledger unreachable — NO-LIVE-DATA</td></tr>';
return;
}
var receipts = d.receipts || d.entries || d.ledger || [];
var dryRunTotal = 0, anyCharged = false;
// dry-run mode chip
var mode = (String(d.stripe_mode || d.mode || "").toLowerCase() === "live") ? "LIVE" : "DRY-RUN";
var feedMode = document.getElementById("feed-mode");
feedMode.textContent = mode;
feedMode.className = "chip " + (mode === "LIVE" ? "chip-measured" : "chip-modeled");
// render newest first
var rows = receipts.slice().reverse();
if (rows.length && body.querySelector(".empty")) body.innerHTML = "";
rows.forEach(function (r) {
var dec = (r.receipt && r.receipt.decision) || r.decision || r;
var digest = (r.receipt && r.receipt.payload_digest) || r.entry_digest || r.payload_digest || r.digest || "";
var cents = pickCents(r, dec);
var charge = r.charge || {};
if (String(charge.status) === "charged") anyCharged = true;
else dryRunTotal += (typeof cents === "number" ? cents : 0);
if (digest && seen[digest]) return;
if (digest) seen[digest] = true;
var tr = document.createElement("tr");
tr.className = "new";
var when = dec.ts || r.ts || "";
tr.innerHTML =
"<td>" + esc(shortTime(when)) + "</td>" +
"<td>" + esc(dec.node || r.node || "—") + "</td>" +
"<td>" + fmt(num(dec.joules_measured || dec.joules), 3) + "</td>" +
'<td class="cents">' + (typeof cents === "number" ? (cents + "¢") : "—") +
' <span class="chip chip-modeled">' + (mode === "LIVE" && anyCharged ? "MODELED" : "DRY-RUN") + "</span></td>" +
'<td class="digest">' + esc(shortDigest(digest)) + "</td>";
body.insertBefore(tr, body.firstChild);
});
// today earnings = MODELED dry-run total
document.getElementById("m-earn").textContent = "$" + (dryRunTotal / 100).toFixed(2);
// real cleared revenue: only MEASURED if a real charge cleared
var realCents = num(d.cleared_revenue_cents || d.charged_cents || 0);
var mReal = document.getElementById("m-real");
mReal.textContent = "$" + (realCents / 100).toFixed(2);
setChip(document.getElementById("m-real-chip"), realCents > 0 ? "MEASURED" : "ZERO");
mReal.style.color = realCents > 0 ? "var(--green)" : "var(--dim)";
// chain integrity note (honest)
// (display omitted from UI chrome; integrity reflected by DEGRADED if absent)
});
}
function pickCents(r, dec) {
var c = r.charge || {};
if (typeof c.would_charge_cents === "number") return c.would_charge_cents;
if (typeof c.amount_cents === "number") return c.amount_cents;
if (dec && typeof dec.amount_cents === "number") return dec.amount_cents;
return null;
}
// ===== TODAY totals from operator status (MEASURED compute) ============
function refreshToday() {
return poll(API + "/energy/operator/status").then(function (d) {
if (isErr(d)) return;
var t = d;
var tokens = num(t.tokens_total != null ? t.tokens_total : (t.tokens_measured != null ? t.tokens_measured : t.tokens));
var jobs = num(t.jobs_done != null ? t.jobs_done : (t.jobs_completed != null ? t.jobs_completed : t.jobs));
var joules = num(t.joules_measured_total != null ? t.joules_measured_total : (t.joules_measured != null ? t.joules_measured : t.joules));
if (tokens != null) mTokens.set(tokens);
if (jobs != null) mJobs.set(jobs);
if (joules != null) { mJoules.set(joules); bigJoules.set(joules); }
pushEnergySamples(d);
});
}
// ===== PROJECTION: 1-day MODELED headline ==============================
function refreshProjection() {
return poll(API + "/energy/projection?window=running").then(function (d) {
markLive(!isErr(d));
var pj = document.getElementById("proj-joules");
var pe = document.getElementById("m-proj-earn");
if (isErr(d)) { pj.textContent = "—"; pe.textContent = "—"; return; }
var p = d.projection_1day_single_node || d.one_day || d.projection || d;
var cd = p.compute_done || {};
var joules = num((cd.joules && cd.joules.value != null) ? cd.joules.value
: (p.joules && p.joules.value != null ? p.joules.value
: (p.joules != null ? p.joules : (p.joules_projected != null ? p.joules_projected : p.joules_1d))));
// headline dollar = compute_resale (ESTIMATE: assumed ¢/kWh), per API honesty contract
var earn = p.earnings || {};
var resale = earn.compute_resale_usd || {};
var earnDollars = num(resale.value != null ? resale.value
: (earn.usd != null ? earn.usd : (earn.value != null ? earn.value : null)));
if (earnDollars == null) {
var ec = num(earn.amount_cents != null ? earn.amount_cents : (earn.earnings_cents != null ? earn.earnings_cents : earn.cents));
if (ec != null) earnDollars = ec / 100;
}
pj.textContent = joules != null ? fmt(joules, 0) : "—";
pe.textContent = earnDollars != null ? ("$" + earnDollars.toFixed(2)) : "—";
var formula = (resale.formula) || (cd.joules && cd.joules.formula) || p.basis;
if (formula) {
document.getElementById("earn-formula").textContent =
"ESTIMATE (assumed ¢/kWh): " + String(formula);
}
});
}
// ===== GRID: price now + negative-window indicator =====================
function refreshGrid() {
return poll(API + "/harvest/posture").then(function (d) {
markLive(!isErr(d));
var priceEl = document.getElementById("grid-price");
var chip = document.getElementById("grid-chip");
var banner = document.getElementById("neg-banner");
var postureEl = document.getElementById("grid-posture");
if (isErr(d) || d.ok === false) {
priceEl.textContent = "—"; setChip(chip, "DEGRADED");
banner.classList.remove("show"); postureEl.textContent = "posture: NO-LIVE-DATA";
return;
}
var price = extractPrice(d);
if (price == null) { priceEl.textContent = "—"; setChip(chip, "SAMPLE"); }
else {
priceEl.textContent = price.toFixed(2) + " EUR/MWh";
priceEl.style.color = price < 0 ? "var(--green)" : "var(--ink)";
// grid price reading off-box is SAMPLE per posture contract
setChip(chip, (d.measured_any ? "MEASURED" : "SAMPLE"));
banner.classList.toggle("show", price < 0);
}
postureEl.textContent = "posture: " + (d.posture || "—") +
(d.wasted_energy_available ? " · wasted-energy available" : "");
});
}
function extractPrice(d) {
if (typeof d.grid_price_eur_mwh === "number") return d.grid_price_eur_mwh;
if (typeof d.price_now_eur_mwh === "number") return d.price_now_eur_mwh;
if (typeof d.price_eur_mwh === "number") return d.price_eur_mwh;
var rs = d.readings || [];
for (var i = 0; i < rs.length; i++) {
var r = rs[i];
if (r && (r.feed === "price" || /price/i.test(r.feed || r.name || ""))) {
if (typeof r.value === "number") return r.value;
if (typeof r.price_eur_mwh === "number") return r.price_eur_mwh;
if (typeof r.marketprice === "number") return r.marketprice;
}
}
return null;
}
// ---- small utils -------------------------------------------------------
function num(v) { var n = typeof v === "number" ? v : parseFloat(v); return isFinite(n) ? n : null; }
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) {
return { "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;" }[c]; }); }
function shortDigest(d) { if (!d) return "—"; var h = String(d).replace(/^sha256:/, ""); return h.slice(0, 12) + "…"; }
function shortTime(t) { if (!t) return "—"; try { return new Date(t).toLocaleTimeString(); } catch (e) { return String(t); } }
// ===== LIVE CHARTS (vanilla canvas, DPR-aware, 0 CDN) =================
function LiveChart(id, opts) {
opts = opts || {};
this.c = document.getElementById(id);
this.ctx = this.c ? this.c.getContext("2d") : null;
this.empty = opts.empty ? document.getElementById(opts.empty) : null;
this.valEl = opts.val ? document.getElementById(opts.val) : null;
this.loEl = opts.lo ? document.getElementById(opts.lo) : null;
this.hiEl = opts.hi ? document.getElementById(opts.hi) : null;
this.pts = [];
this.max = opts.max || 180;
this.color = opts.color || "#34d399";
this.decimals = opts.decimals != null ? opts.decimals : 0;
this.minSpan = opts.minSpan || 0;
this.fixedMin = opts.fixedMin;
}
LiveChart.prototype.push = function (v) {
if (typeof v !== "number" || !isFinite(v)) return;
this.pts.push(v);
if (this.pts.length > this.max) this.pts.shift();
if (this.empty) this.empty.classList.add("hide");
if (this.valEl) this.valEl.textContent = fmt(v, this.decimals);
this.draw();
};
LiveChart.prototype.draw = function () {
var c = this.c, ctx = this.ctx;
if (!ctx) return;
var dpr = window.devicePixelRatio || 1;
var w = c.clientWidth || 600, h = c.clientHeight || 200;
if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
}
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, w, h);
var padL = 8, padR = 8, padT = 14, padB = 10;
var plotW = w - padL - padR, plotH = h - padT - padB;
ctx.strokeStyle = "rgba(255,255,255,0.05)"; ctx.lineWidth = 1;
for (var g = 0; g <= 4; g++) {
var gy = padT + plotH * g / 4;
ctx.beginPath(); ctx.moveTo(padL, gy); ctx.lineTo(w - padR, gy); ctx.stroke();
}
var pts = this.pts;
if (pts.length < 2) return;
var mn = Math.min.apply(null, pts), mx = Math.max.apply(null, pts);
if (this.fixedMin != null) mn = Math.min(mn, this.fixedMin);
if (mx - mn < this.minSpan) mx = mn + this.minSpan;
if (mx === mn) mx = mn + 1;
var n = pts.length;
function X(i) { return padL + plotW * (i / (n - 1)); }
function Y(v) { return padT + plotH * (1 - (v - mn) / (mx - mn)); }
var grad = ctx.createLinearGradient(0, padT, 0, padT + plotH);
grad.addColorStop(0, this.color + "55");
grad.addColorStop(1, this.color + "08");
ctx.beginPath();
ctx.moveTo(X(0), Y(pts[0]));
for (var i = 1; i < n; i++) ctx.lineTo(X(i), Y(pts[i]));
ctx.lineTo(X(n - 1), padT + plotH);
ctx.lineTo(X(0), padT + plotH);
ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
ctx.beginPath();
ctx.moveTo(X(0), Y(pts[0]));
for (var j = 1; j < n; j++) ctx.lineTo(X(j), Y(pts[j]));
ctx.strokeStyle = this.color; ctx.lineWidth = 2;
ctx.lineJoin = "round"; ctx.stroke();
ctx.beginPath();
ctx.arc(X(n - 1), Y(pts[n - 1]), 3, 0, Math.PI * 2);
ctx.fillStyle = this.color; ctx.fill();
if (this.loEl) this.loEl.textContent = fmt(mn, this.decimals);
if (this.hiEl) this.hiEl.textContent = fmt(mx, this.decimals);
};
function Gauge(id, opts) {
opts = opts || {};
this.c = document.getElementById(id);
this.ctx = this.c ? this.c.getContext("2d") : null;
this.valEl = opts.val ? document.getElementById(opts.val) : null;
this.max = opts.max || 120; this.cur = 0;
}
Gauge.prototype.set = function (v) {
if (typeof v !== "number" || !isFinite(v)) return;
this.cur = v; if (this.valEl) this.valEl.textContent = fmt(v, 1);
this.draw();
};
Gauge.prototype.draw = function () {
var c = this.c, ctx = this.ctx; if (!ctx) return;
var dpr = window.devicePixelRatio || 1;
var w = c.clientWidth || 240, h = c.clientHeight || 200;
if (c.width !== Math.round(w * dpr) || c.height !== Math.round(h * dpr)) {
c.width = Math.round(w * dpr); c.height = Math.round(h * dpr);
}
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, w, h);
var cx = w / 2, cy = h * 0.70, r = Math.min(w * 0.40, h * 0.56);
var a0 = Math.PI * 0.85, a1 = Math.PI * 2.15;
ctx.lineWidth = 14; ctx.lineCap = "round";
ctx.strokeStyle = "#16202e";
ctx.beginPath(); ctx.arc(cx, cy, r, a0, a1); ctx.stroke();
var frac = Math.max(0, Math.min(1, this.cur / this.max));
var grad = ctx.createLinearGradient(cx - r, 0, cx + r, 0);
grad.addColorStop(0, "#34d399"); grad.addColorStop(1, "#fbbf24");
ctx.strokeStyle = grad;
ctx.beginPath(); ctx.arc(cx, cy, r, a0, a0 + (a1 - a0) * frac); ctx.stroke();
};
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 });
var chPower = new LiveChart("chart-power", { empty:"chart-power-empty", val:"chart-power-val", color:"#60a5fa", decimals:1, fixedMin:0, minSpan:5 });
var chTps = new LiveChart("chart-tps", { empty:"chart-tps-empty", val:"chart-tps-val", color:"#c084fc", decimals:1, fixedMin:0, minSpan:1 });
var gPower = new Gauge("gauge-power", { val:"gauge-power-val", max:120 });
var tpsState = { lastTokens:null, lastTs:null };
function pushEnergySamples(d) {
if (isErr(d)) return;
var j = num(d.joules_measured_total != null ? d.joules_measured_total : d.joules_measured);
if (j != null) chJoules.push(j);
var pw = num(d.power_w_sample);
if (pw != null) { chPower.push(pw); gPower.set(pw); }
var nodeName = (d.nodes_computing && d.nodes_computing[0]) || "—";
var sub = document.getElementById("gauge-power-sub");
if (sub) sub.textContent = "computing node " + nodeName;
var tk = num(d.tokens_total != null ? d.tokens_total : d.tokens_measured);
var now = Date.now();
if (tk != null) {
if (tpsState.lastTokens != null && tpsState.lastTs != null && now > tpsState.lastTs) {
var rate = (tk - tpsState.lastTokens) / ((now - tpsState.lastTs) / 1000);
if (rate >= 0 && isFinite(rate)) chTps.push(rate);
}
tpsState.lastTokens = tk; tpsState.lastTs = now;
}
}
window.addEventListener("resize", function () {
chJoules.draw(); chPower.draw(); chTps.draw(); gPower.draw();
});
// ---- poll loop ---------------------------------------------------------
function tickFast() { refreshStatus(); refreshToday(); refreshLedger(); }
function tickSlow() { refreshProjection(); refreshGrid(); }
tickFast(); tickSlow();
setInterval(tickFast, 3000);
setInterval(tickSlow, 8000);
})();
</script>
</body>
</html>