Spaces:
Running
chore(sync): mirror front-door files to Space (hf-sync)
Browse filesAutomated front-door sync from szl-holdings/a11oy main via hf-sync.
Added/updated: cathedral.html, console/docs.html, console/index.html, console/pricing.html, console/throne-room.html, console/throne-room.js, pages/api-keys.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/evidence.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/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/wallpa.html, pages/warhacker.html, pages/wasi-rikuq.html, pages/wires.html, static/a11oy_cathedral.js
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.
- pages/harvest.html +815 -0
|
@@ -0,0 +1,815 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<!-- SPDX-License-Identifier: Apache-2.0 © 2026 Lutar, Stephen P. — SZL Holdings · Doctrine v11+ -->
|
| 3 |
+
<!--
|
| 4 |
+
Allodial Harvest Console — web/harvest.html
|
| 5 |
+
Honest labels only:
|
| 6 |
+
• posture → real /api/a11oy/v1/harvest/posture
|
| 7 |
+
• sovereign banner → real /api/a11oy/v1/formula/sovereign
|
| 8 |
+
• joules: SAMPLE until on-box NVML provides MEASURED
|
| 9 |
+
• Λ = Conjecture 1 (advisory, not proven)
|
| 10 |
+
• NO free-energy claim — harvests WASTED grid energy
|
| 11 |
+
0 runtime CDN — all CSS/JS inline.
|
| 12 |
+
Auto-refreshes every 30 s. Degrades honestly to "—" / "feed down" on 404/error.
|
| 13 |
+
-->
|
| 14 |
+
<html lang="en">
|
| 15 |
+
<head>
|
| 16 |
+
<meta charset="utf-8"/>
|
| 17 |
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
| 18 |
+
<title>a11oy · Allodial Harvest Console</title>
|
| 19 |
+
<style>
|
| 20 |
+
/* ── design tokens ───────────────────────────────────────────── */
|
| 21 |
+
:root{
|
| 22 |
+
--bg:#0a0b10; --panel:#12141d; --panel2:#171a26; --ink:#e8eaf0;
|
| 23 |
+
--mut:#9aa0b4; --line:#252938; --mono:'SF Mono',ui-monospace,'JetBrains Mono',Menlo,Consolas,monospace;
|
| 24 |
+
|
| 25 |
+
/* posture colours */
|
| 26 |
+
--c-neg:#c94eff; /* negative-price – vivid purple */
|
| 27 |
+
--c-cur:#3fb950; /* curtailed-renew – green */
|
| 28 |
+
--c-chp:#20808d; /* cheap – teal */
|
| 29 |
+
--c-nor:#9aa0b4; /* normal – grey */
|
| 30 |
+
--c-exp:#d29922; /* expensive – amber */
|
| 31 |
+
|
| 32 |
+
/* semantic */
|
| 33 |
+
--ok:#3fb950; --warn:#d29922; --bad:#f85149;
|
| 34 |
+
--gold:#d9b35c;
|
| 35 |
+
|
| 36 |
+
/* badges */
|
| 37 |
+
--badge-sample:#2a1f00;
|
| 38 |
+
--badge-sample-ink:#d29922;
|
| 39 |
+
--badge-measured:#0e2b0e;
|
| 40 |
+
--badge-measured-ink:#3fb950;
|
| 41 |
+
--badge-sovereign:#0e1e2b;
|
| 42 |
+
--badge-sovereign-ink:#7cc4ff;
|
| 43 |
+
--badge-half:#1c1620;
|
| 44 |
+
--badge-half-ink:#b090d0;
|
| 45 |
+
--badge-advisory:#231a00;
|
| 46 |
+
--badge-advisory-ink:#d9b35c;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
/* ── reset & base ────────────────────────────────────────────── */
|
| 50 |
+
*{box-sizing:border-box;margin:0;padding:0}
|
| 51 |
+
html{scroll-behavior:smooth}
|
| 52 |
+
body{
|
| 53 |
+
background:var(--bg);color:var(--ink);
|
| 54 |
+
font:15px/1.6 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
|
| 55 |
+
min-height:100vh;
|
| 56 |
+
}
|
| 57 |
+
a{color:var(--gold);text-decoration:none}
|
| 58 |
+
a:hover{text-decoration:underline}
|
| 59 |
+
|
| 60 |
+
/* ── layout ──────────────────────────────────────────────────── */
|
| 61 |
+
.wrap{max-width:1080px;margin:0 auto;padding:0 20px 80px}
|
| 62 |
+
|
| 63 |
+
/* ── header ──────────────────────────────────────────────────── */
|
| 64 |
+
header{
|
| 65 |
+
border-bottom:1px solid var(--line);
|
| 66 |
+
padding:14px 20px;
|
| 67 |
+
display:flex;align-items:center;gap:14px;flex-wrap:wrap;
|
| 68 |
+
background:linear-gradient(180deg,#10121b,var(--bg));
|
| 69 |
+
position:sticky;top:0;z-index:100;backdrop-filter:blur(6px);
|
| 70 |
+
}
|
| 71 |
+
.brand{font-weight:700;letter-spacing:.5px;color:var(--gold);font-size:18px}
|
| 72 |
+
.brand span{font-weight:400;opacity:.6}
|
| 73 |
+
.hdr-right{margin-left:auto;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
| 74 |
+
.ts{font-size:12px;color:var(--mut);font-family:var(--mono)}
|
| 75 |
+
button.refresh{
|
| 76 |
+
background:transparent;border:1px solid var(--line);color:var(--mut);
|
| 77 |
+
border-radius:8px;padding:5px 12px;font-size:12.5px;cursor:pointer;
|
| 78 |
+
transition:border-color .15s,color .15s;
|
| 79 |
+
}
|
| 80 |
+
button.refresh:hover{border-color:var(--gold);color:var(--gold)}
|
| 81 |
+
.tick{font-size:11px;color:var(--mut);font-family:var(--mono)}
|
| 82 |
+
|
| 83 |
+
/* ── nav ─────────────────────────────────────────────────────── */
|
| 84 |
+
nav{
|
| 85 |
+
display:flex;flex-wrap:wrap;gap:8px;padding:10px 20px;
|
| 86 |
+
border-bottom:1px solid var(--line);background:var(--panel);
|
| 87 |
+
}
|
| 88 |
+
nav a{
|
| 89 |
+
font-size:12px;padding:4px 10px;border:1px solid var(--line);
|
| 90 |
+
border-radius:999px;color:var(--mut);transition:border-color .15s,color .15s;
|
| 91 |
+
}
|
| 92 |
+
nav a:hover{border-color:var(--gold);color:var(--gold);text-decoration:none}
|
| 93 |
+
nav a.here{background:var(--gold);color:#0a0b10;border-color:var(--gold);font-weight:600}
|
| 94 |
+
|
| 95 |
+
/* ── section spacing ─────────────────────────────────────────── */
|
| 96 |
+
section{margin-top:28px}
|
| 97 |
+
h2{font-size:16px;font-weight:700;color:var(--gold);margin-bottom:12px;letter-spacing:.3px;text-transform:uppercase}
|
| 98 |
+
|
| 99 |
+
/* ── badges ──────────────────────────────────────────────────── */
|
| 100 |
+
.badge{
|
| 101 |
+
display:inline-block;padding:2px 9px;border-radius:999px;
|
| 102 |
+
font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;
|
| 103 |
+
vertical-align:middle;
|
| 104 |
+
}
|
| 105 |
+
.badge-sample {background:var(--badge-sample);color:var(--badge-sample-ink)}
|
| 106 |
+
.badge-measured {background:var(--badge-measured);color:var(--badge-measured-ink)}
|
| 107 |
+
.badge-sov {background:var(--badge-sovereign);color:var(--badge-sovereign-ink)}
|
| 108 |
+
.badge-half {background:var(--badge-half);color:var(--badge-half-ink)}
|
| 109 |
+
.badge-advisory {background:var(--badge-advisory);color:var(--badge-advisory-ink)}
|
| 110 |
+
.badge-live {background:rgba(63,185,80,.15);color:var(--ok)}
|
| 111 |
+
.badge-down {background:rgba(248,81,73,.15);color:var(--bad)}
|
| 112 |
+
.badge-up {background:rgba(63,185,80,.15);color:var(--ok)}
|
| 113 |
+
|
| 114 |
+
/* ── status HERO ─────────────────────────────────────────────── */
|
| 115 |
+
.hero{
|
| 116 |
+
border-radius:16px;padding:28px 28px 24px;margin-top:24px;
|
| 117 |
+
border:1px solid var(--line);position:relative;overflow:hidden;
|
| 118 |
+
transition:border-color .4s,background .4s;
|
| 119 |
+
}
|
| 120 |
+
.hero::before{
|
| 121 |
+
content:'';position:absolute;inset:0;
|
| 122 |
+
border-radius:16px;pointer-events:none;opacity:.08;
|
| 123 |
+
}
|
| 124 |
+
.hero.p-neg{border-color:var(--c-neg);background:linear-gradient(135deg,#180a22,#0a0b10)}
|
| 125 |
+
.hero.p-neg::before{background:var(--c-neg)}
|
| 126 |
+
.hero.p-cur{border-color:var(--c-cur);background:linear-gradient(135deg,#0d1f0f,#0a0b10)}
|
| 127 |
+
.hero.p-cur::before{background:var(--c-cur)}
|
| 128 |
+
.hero.p-chp{border-color:var(--c-chp);background:linear-gradient(135deg,#091a1d,#0a0b10)}
|
| 129 |
+
.hero.p-chp::before{background:var(--c-chp)}
|
| 130 |
+
.hero.p-nor{border-color:var(--line);background:var(--panel)}
|
| 131 |
+
.hero.p-exp{border-color:var(--c-exp);background:linear-gradient(135deg,#1e1200,#0a0b10)}
|
| 132 |
+
.hero.p-exp::before{background:var(--c-exp)}
|
| 133 |
+
|
| 134 |
+
.hero-inner{display:flex;align-items:flex-start;gap:20px;flex-wrap:wrap}
|
| 135 |
+
.hero-status{
|
| 136 |
+
font-size:28px;font-weight:800;letter-spacing:1px;text-transform:uppercase;
|
| 137 |
+
line-height:1.1;
|
| 138 |
+
}
|
| 139 |
+
.p-neg .hero-status{color:var(--c-neg)}
|
| 140 |
+
.p-cur .hero-status{color:var(--c-cur)}
|
| 141 |
+
.p-chp .hero-status{color:var(--c-chp)}
|
| 142 |
+
.p-nor .hero-status{color:var(--c-nor)}
|
| 143 |
+
.p-exp .hero-status{color:var(--c-exp)}
|
| 144 |
+
|
| 145 |
+
.hero-right{flex:1;min-width:220px}
|
| 146 |
+
.hero-price{font-size:22px;font-weight:700;margin:4px 0;font-family:var(--mono)}
|
| 147 |
+
.hero-note{font-size:13.5px;color:var(--mut);margin-bottom:10px}
|
| 148 |
+
.drivers{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
|
| 149 |
+
.driver{
|
| 150 |
+
font-size:11.5px;background:var(--panel2);border:1px solid var(--line);
|
| 151 |
+
border-radius:6px;padding:3px 9px;color:var(--mut);
|
| 152 |
+
}
|
| 153 |
+
.hero-meta{display:flex;align-items:center;gap:10px;margin-top:16px;flex-wrap:wrap}
|
| 154 |
+
.soak-hard{
|
| 155 |
+
font-size:12px;font-weight:600;padding:3px 10px;border-radius:6px;
|
| 156 |
+
background:rgba(201,78,255,.12);color:var(--c-neg);border:1px solid rgba(201,78,255,.3);
|
| 157 |
+
}
|
| 158 |
+
.wasted{font-size:13px;color:var(--mut)}
|
| 159 |
+
.hero-loading{font-size:16px;color:var(--mut)}
|
| 160 |
+
|
| 161 |
+
/* ── sovereign banner ────────────────────────────────────────── */
|
| 162 |
+
.sov-banner{
|
| 163 |
+
border-radius:12px;padding:14px 20px;margin-top:8px;
|
| 164 |
+
display:flex;align-items:center;gap:14px;flex-wrap:wrap;
|
| 165 |
+
border:1px solid var(--line);transition:border-color .4s,background .4s;
|
| 166 |
+
}
|
| 167 |
+
.sov-banner.sov{
|
| 168 |
+
background:rgba(124,196,255,.05);border-color:rgba(124,196,255,.3);
|
| 169 |
+
}
|
| 170 |
+
.sov-banner.half{
|
| 171 |
+
background:rgba(176,144,208,.04);border-color:rgba(176,144,208,.3);
|
| 172 |
+
}
|
| 173 |
+
.sov-banner.loading{background:var(--panel)}
|
| 174 |
+
.sov-icon{font-size:22px;line-height:1}
|
| 175 |
+
.sov-text strong{font-size:15px;display:block}
|
| 176 |
+
.sov-text p{font-size:12.5px;color:var(--mut);margin-top:2px}
|
| 177 |
+
|
| 178 |
+
/* ── cards ────────────────────────────────────────────────────── */
|
| 179 |
+
.card{
|
| 180 |
+
background:var(--panel);border:1px solid var(--line);
|
| 181 |
+
border-radius:12px;padding:18px 20px;margin-bottom:14px;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
/* ── feeds panel ─────────────────────────────────────────────── */
|
| 185 |
+
.feeds-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
|
| 186 |
+
.feed-row{
|
| 187 |
+
background:var(--panel2);border:1px solid var(--line);border-radius:10px;
|
| 188 |
+
padding:12px 14px;display:flex;align-items:flex-start;gap:12px;
|
| 189 |
+
}
|
| 190 |
+
.feed-dot{
|
| 191 |
+
width:9px;height:9px;border-radius:50%;margin-top:5px;flex-shrink:0;
|
| 192 |
+
}
|
| 193 |
+
.feed-dot.live{background:var(--ok);box-shadow:0 0 6px var(--ok)}
|
| 194 |
+
.feed-dot.down{background:var(--bad)}
|
| 195 |
+
.feed-dot.loading{background:var(--mut);animation:pulse 1.2s infinite}
|
| 196 |
+
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
| 197 |
+
.feed-name{font-size:13px;font-weight:600}
|
| 198 |
+
.feed-val{font-family:var(--mono);font-size:14px;font-weight:700;margin:2px 0}
|
| 199 |
+
.feed-unit{font-size:11.5px;color:var(--mut)}
|
| 200 |
+
.feed-badges{display:flex;gap:5px;flex-wrap:wrap;margin-top:5px}
|
| 201 |
+
.feed-note{font-size:11.5px;color:var(--mut);margin-top:4px;line-height:1.4}
|
| 202 |
+
|
| 203 |
+
/* ── soak panel ──────────────────────────────────────────────── */
|
| 204 |
+
.soak-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;margin-bottom:14px}
|
| 205 |
+
.soak-kpi{
|
| 206 |
+
background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:14px;
|
| 207 |
+
}
|
| 208 |
+
.soak-kpi .k{font-size:11.5px;color:var(--mut);text-transform:uppercase;letter-spacing:.4px}
|
| 209 |
+
.soak-kpi .v{font-family:var(--mono);font-size:20px;font-weight:700;margin:4px 0;color:var(--ink)}
|
| 210 |
+
.soak-kpi .d{font-size:11.5px;color:var(--mut);line-height:1.4}
|
| 211 |
+
.soak-jobs{margin-top:12px}
|
| 212 |
+
.soak-jobs h3{font-size:13px;font-weight:600;color:var(--mut);margin-bottom:8px}
|
| 213 |
+
.job-list{display:flex;flex-wrap:wrap;gap:6px}
|
| 214 |
+
.job-chip{
|
| 215 |
+
background:var(--panel2);border:1px solid var(--line);border-radius:6px;
|
| 216 |
+
font-family:var(--mono);font-size:11.5px;padding:3px 9px;color:var(--ink);
|
| 217 |
+
}
|
| 218 |
+
.soak-reason{
|
| 219 |
+
margin-top:12px;font-size:12.5px;color:var(--mut);
|
| 220 |
+
border-top:1px solid var(--line);padding-top:10px;
|
| 221 |
+
}
|
| 222 |
+
.soak-admit{
|
| 223 |
+
display:inline-flex;align-items:center;gap:6px;
|
| 224 |
+
font-size:13px;font-weight:600;padding:4px 12px;border-radius:8px;
|
| 225 |
+
background:rgba(63,185,80,.1);color:var(--ok);border:1px solid rgba(63,185,80,.25);
|
| 226 |
+
margin-bottom:10px;
|
| 227 |
+
}
|
| 228 |
+
.soak-admit.deny{
|
| 229 |
+
background:rgba(248,81,73,.1);color:var(--bad);border-color:rgba(248,81,73,.25);
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
/* ── receipt panel ───────────────────────────────────────────── */
|
| 233 |
+
.receipt-table{width:100%;border-collapse:collapse;font-size:13.5px}
|
| 234 |
+
.receipt-table td,.receipt-table th{
|
| 235 |
+
padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top;
|
| 236 |
+
text-align:left;
|
| 237 |
+
}
|
| 238 |
+
.receipt-table th{
|
| 239 |
+
color:var(--mut);font-size:11.5px;font-weight:600;
|
| 240 |
+
text-transform:uppercase;letter-spacing:.4px;
|
| 241 |
+
}
|
| 242 |
+
.receipt-table td.lbl{color:var(--mut);width:44%}
|
| 243 |
+
.receipt-table td.val{font-family:var(--mono);font-weight:600}
|
| 244 |
+
.joules-row .val{font-size:16px}
|
| 245 |
+
.receipt-note{
|
| 246 |
+
margin-top:12px;padding:10px 14px;border-left:3px solid var(--gold);
|
| 247 |
+
background:rgba(217,179,92,.05);border-radius:0 8px 8px 0;
|
| 248 |
+
font-size:12.5px;color:var(--mut);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
/* ── world panel ─────────────────────────────────────────────── */
|
| 252 |
+
.world-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px}
|
| 253 |
+
.zone-card{
|
| 254 |
+
background:var(--panel2);border:1px solid var(--line);border-radius:10px;
|
| 255 |
+
padding:12px;text-align:center;transition:border-color .2s;
|
| 256 |
+
}
|
| 257 |
+
.zone-card.best{border-color:var(--ok);background:rgba(63,185,80,.05)}
|
| 258 |
+
.zone-name{font-size:12px;font-weight:600;color:var(--mut);text-transform:uppercase;letter-spacing:.5px}
|
| 259 |
+
.zone-share{font-family:var(--mono);font-size:20px;font-weight:700;margin:4px 0}
|
| 260 |
+
.zone-card.best .zone-share{color:var(--ok)}
|
| 261 |
+
.zone-best-tag{font-size:11px;color:var(--ok);margin-top:2px}
|
| 262 |
+
|
| 263 |
+
/* ── lambda advisory ─────────────────────────────────────────── */
|
| 264 |
+
.lambda-note{
|
| 265 |
+
font-size:12px;padding:10px 14px;border-left:3px solid var(--badge-advisory-ink);
|
| 266 |
+
background:rgba(217,179,92,.04);border-radius:0 8px 8px 0;color:var(--mut);
|
| 267 |
+
margin-top:14px;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
/* ── error / empty states ────────────────────────────────────── */
|
| 271 |
+
.feed-unavail{
|
| 272 |
+
font-size:13px;color:var(--mut);
|
| 273 |
+
padding:14px;text-align:center;
|
| 274 |
+
border:1px dashed var(--line);border-radius:10px;
|
| 275 |
+
}
|
| 276 |
+
.skeleton{background:var(--panel2);border-radius:6px;height:1em;animation:pulse 1.2s infinite}
|
| 277 |
+
|
| 278 |
+
/* ── footer ──────────────────────────────────────────────────── */
|
| 279 |
+
footer{
|
| 280 |
+
border-top:1px solid var(--line);color:var(--mut);
|
| 281 |
+
font-size:11.5px;padding:18px 20px;line-height:1.7;
|
| 282 |
+
margin-top:40px;
|
| 283 |
+
}
|
| 284 |
+
footer strong{color:var(--ink)}
|
| 285 |
+
|
| 286 |
+
/* ── responsive ──────────────────────────────────────────────── */
|
| 287 |
+
@media(max-width:600px){
|
| 288 |
+
.hero{padding:18px 16px 14px}
|
| 289 |
+
.hero-status{font-size:20px}
|
| 290 |
+
.hero-price{font-size:16px}
|
| 291 |
+
.sov-banner{padding:10px 14px}
|
| 292 |
+
.wrap{padding:0 12px 60px}
|
| 293 |
+
header{padding:10px 12px}
|
| 294 |
+
nav{padding:8px 12px}
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
/* ── reduced motion ──────────────────────────────────────────── */
|
| 298 |
+
@media(prefers-reduced-motion:reduce){
|
| 299 |
+
*{animation:none!important;transition:none!important}
|
| 300 |
+
}
|
| 301 |
+
</style>
|
| 302 |
+
</head>
|
| 303 |
+
<body>
|
| 304 |
+
|
| 305 |
+
<header>
|
| 306 |
+
<span class="brand">a<span>11</span>oy</span>
|
| 307 |
+
<span style="color:var(--mut);font-size:13px">· Allodial Harvest Console</span>
|
| 308 |
+
<div class="hdr-right">
|
| 309 |
+
<span class="ts" id="ts">—</span>
|
| 310 |
+
<button class="refresh" id="btn-refresh" onclick="loadAll(true)">↻ Refresh</button>
|
| 311 |
+
<span class="tick" id="tick" title="next auto-refresh">—</span>
|
| 312 |
+
</div>
|
| 313 |
+
</header>
|
| 314 |
+
|
| 315 |
+
<nav>
|
| 316 |
+
<a href="/">home</a>
|
| 317 |
+
<a href="/hub">hub</a>
|
| 318 |
+
<a href="/status">status</a>
|
| 319 |
+
<a href="/console">console</a>
|
| 320 |
+
<a href="/harvest" class="here">harvest</a>
|
| 321 |
+
<a href="/formulas">formulas</a>
|
| 322 |
+
</nav>
|
| 323 |
+
|
| 324 |
+
<div class="wrap">
|
| 325 |
+
|
| 326 |
+
<!-- ────────────────────── STATUS HERO ───────────────────────── -->
|
| 327 |
+
<section id="sec-posture">
|
| 328 |
+
<div class="hero p-nor" id="hero">
|
| 329 |
+
<div class="hero-inner">
|
| 330 |
+
<div>
|
| 331 |
+
<div class="hero-status" id="hero-label">loading…</div>
|
| 332 |
+
</div>
|
| 333 |
+
<div class="hero-right">
|
| 334 |
+
<div class="hero-price" id="hero-price">—</div>
|
| 335 |
+
<div class="hero-note" id="hero-note">fetching posture…</div>
|
| 336 |
+
<div class="drivers" id="drivers"></div>
|
| 337 |
+
<div class="hero-meta">
|
| 338 |
+
<span class="wasted" id="wasted-label">—</span>
|
| 339 |
+
<span id="soak-hard-badge"></span>
|
| 340 |
+
</div>
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
+
</section>
|
| 345 |
+
|
| 346 |
+
<!-- ────────────────────── SOVEREIGN BANNER ─────────────────── -->
|
| 347 |
+
<section id="sec-sovereign" style="margin-top:14px">
|
| 348 |
+
<div class="sov-banner loading" id="sov-banner">
|
| 349 |
+
<span class="sov-icon">⏳</span>
|
| 350 |
+
<div class="sov-text">
|
| 351 |
+
<strong id="sov-title">Checking sovereignty…</strong>
|
| 352 |
+
<p id="sov-desc">Reading /api/a11oy/v1/formula/sovereign</p>
|
| 353 |
+
</div>
|
| 354 |
+
</div>
|
| 355 |
+
</section>
|
| 356 |
+
|
| 357 |
+
<!-- ────────────────────── FEEDS PANEL ──────────────────────── -->
|
| 358 |
+
<section id="sec-feeds">
|
| 359 |
+
<h2>Energy Feeds</h2>
|
| 360 |
+
<div class="feeds-grid" id="feeds-grid">
|
| 361 |
+
<!-- populated by JS -->
|
| 362 |
+
<div class="feed-unavail">Loading feed data…</div>
|
| 363 |
+
</div>
|
| 364 |
+
</section>
|
| 365 |
+
|
| 366 |
+
<!-- ────────────────────── SOAK PANEL ───────────────────────── -->
|
| 367 |
+
<section id="sec-soak">
|
| 368 |
+
<h2>Formula-Bounded Soak Plan</h2>
|
| 369 |
+
<div class="card" id="soak-card">
|
| 370 |
+
<div class="feed-unavail">Loading soak plan…</div>
|
| 371 |
+
</div>
|
| 372 |
+
<div class="lambda-note">
|
| 373 |
+
<span class="badge badge-advisory">Λ = Conjecture 1</span>
|
| 374 |
+
The Ouroboros iteration bound is derived from Λ, which is currently
|
| 375 |
+
<strong>advisory (conjecture)</strong> — not a proven theorem. Plans using
|
| 376 |
+
this bound are clearly labelled as best-effort estimates.
|
| 377 |
+
</div>
|
| 378 |
+
</section>
|
| 379 |
+
|
| 380 |
+
<!-- ────────────────────── RECEIPT PANEL ────────────────────── -->
|
| 381 |
+
<section id="sec-receipt">
|
| 382 |
+
<h2>Latest Receipt</h2>
|
| 383 |
+
<div class="card" id="receipt-card">
|
| 384 |
+
<div class="feed-unavail">Loading receipt…</div>
|
| 385 |
+
</div>
|
| 386 |
+
</section>
|
| 387 |
+
|
| 388 |
+
<!-- ────────────────────── WORLD PANEL ──────────────────────── -->
|
| 389 |
+
<section id="sec-world">
|
| 390 |
+
<h2>Follow the Wind — Global Zone Shares</h2>
|
| 391 |
+
<div class="card">
|
| 392 |
+
<div id="world-best" style="margin-bottom:14px;font-size:13.5px;color:var(--mut)">Loading…</div>
|
| 393 |
+
<div class="world-grid" id="world-grid"></div>
|
| 394 |
+
</div>
|
| 395 |
+
</section>
|
| 396 |
+
|
| 397 |
+
</div><!-- .wrap -->
|
| 398 |
+
|
| 399 |
+
<footer>
|
| 400 |
+
<strong>Allodial sovereign compute — harvests wasted grid energy, proves bounded work.</strong><br>
|
| 401 |
+
No free-energy claim. The tagline is "harvest WASTED grid energy" — not "free energy".<br>
|
| 402 |
+
Joules are <strong>SAMPLE</strong> until metered by on-box NVML; they become
|
| 403 |
+
<strong>MEASURED</strong> only on a sovereign GPU node.<br>
|
| 404 |
+
Λ = Conjecture 1 (advisory). sovereign:true only when a local node serves.
|
| 405 |
+
<span class="badge badge-advisory" style="margin-left:4px">Free to use</span><br><br>
|
| 406 |
+
<span style="font-family:var(--mono)">Doctrine v11+ · SLSA L1 (honest) · Apache-2.0 · SZL Holdings · a11oy</span>
|
| 407 |
+
</footer>
|
| 408 |
+
|
| 409 |
+
<script>
|
| 410 |
+
/* ──────────────────────────────────────────────────────────────
|
| 411 |
+
Allodial Harvest Console — vanilla JS, 0 runtime CDN.
|
| 412 |
+
All data from REAL backend endpoints; degrades honestly.
|
| 413 |
+
────────────────────────────────────────────────────────────── */
|
| 414 |
+
"use strict";
|
| 415 |
+
|
| 416 |
+
const BASE = ""; /* same-origin; no hard-coded host */
|
| 417 |
+
const ENDPOINTS = {
|
| 418 |
+
posture : "/api/a11oy/v1/harvest/posture",
|
| 419 |
+
plan : "/api/a11oy/v1/harvest/plan?bytes=0",
|
| 420 |
+
receipt : "/api/a11oy/v1/harvest/receipt",
|
| 421 |
+
world : "/api/a11oy/v1/harvest/world",
|
| 422 |
+
sovereign: "/api/a11oy/v1/formula/sovereign",
|
| 423 |
+
healthz : "/api/a11oy/healthz",
|
| 424 |
+
};
|
| 425 |
+
|
| 426 |
+
/* ── helpers ──────────────────────────────────────────────────── */
|
| 427 |
+
function q(id){ return document.getElementById(id); }
|
| 428 |
+
|
| 429 |
+
function safeFetch(url, label){
|
| 430 |
+
return fetch(url, {cache:"no-store"})
|
| 431 |
+
.then(r => {
|
| 432 |
+
if(!r.ok) throw new Error(`${r.status} ${r.statusText}`);
|
| 433 |
+
return r.json();
|
| 434 |
+
})
|
| 435 |
+
.catch(e => {
|
| 436 |
+
console.warn(`[harvest] ${label} failed:`, e.message);
|
| 437 |
+
return null;
|
| 438 |
+
});
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
function fmtNum(v){
|
| 442 |
+
if(v == null || v === "") return "—";
|
| 443 |
+
const n = Number(v);
|
| 444 |
+
if(isNaN(n)) return String(v);
|
| 445 |
+
return n.toLocaleString("en-US", {maximumFractionDigits:4});
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
function esc(s){ /* XSS-safe text */
|
| 449 |
+
if(s == null) return "—";
|
| 450 |
+
return String(s)
|
| 451 |
+
.replace(/&/g,"&").replace(/</g,"<")
|
| 452 |
+
.replace(/>/g,">").replace(/"/g,""");
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
/* ── posture config ───────────────────────────────────────────── */
|
| 456 |
+
const POSTURE_CFG = {
|
| 457 |
+
"negative-price" : {cls:"p-neg", label:"HARVESTING NOW", color:"var(--c-neg)", note:"The grid is paying to offload power — maximum harvest window."},
|
| 458 |
+
"curtailed-renewable": {cls:"p-cur", label:"RENEWABLE SURPLUS", color:"var(--c-cur)", note:"Excess renewable generation curtailed — clean harvest opportunity."},
|
| 459 |
+
"cheap" : {cls:"p-chp", label:"CHEAP ENERGY", color:"var(--c-chp)", note:"Below-average grid price — cost-effective harvest window."},
|
| 460 |
+
"normal" : {cls:"p-nor", label:"NORMAL CONDITIONS", color:"var(--c-nor)", note:"Grid at normal price and mix — standard operating mode."},
|
| 461 |
+
"expensive" : {cls:"p-exp", label:"EXPENSIVE — STANDING BY", color:"var(--c-exp)", note:"Grid price elevated — harvest paused to avoid wasteful spend."},
|
| 462 |
+
};
|
| 463 |
+
|
| 464 |
+
function getPostureCfg(p){
|
| 465 |
+
return POSTURE_CFG[p] || {cls:"p-nor", label: esc(p)||"UNKNOWN", color:"var(--c-nor)", note:"Posture value not recognised."};
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
/* ── posture hero ─────────────────────────────────────────────── */
|
| 469 |
+
function renderPosture(d){
|
| 470 |
+
const hero = q("hero");
|
| 471 |
+
if(!d){
|
| 472 |
+
hero.className="hero p-nor";
|
| 473 |
+
q("hero-label").textContent="feed down";
|
| 474 |
+
q("hero-price").textContent="—";
|
| 475 |
+
q("hero-note").textContent="Posture feed unavailable — showing last known state.";
|
| 476 |
+
q("drivers").innerHTML="";
|
| 477 |
+
q("wasted-label").textContent="—";
|
| 478 |
+
q("soak-hard-badge").innerHTML="";
|
| 479 |
+
return;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
const cfg = getPostureCfg(d.posture);
|
| 483 |
+
hero.className = "hero " + cfg.cls;
|
| 484 |
+
q("hero-label").textContent = cfg.label;
|
| 485 |
+
|
| 486 |
+
/* price */
|
| 487 |
+
let priceHtml = "—";
|
| 488 |
+
if(d.price != null && d.price_unit){
|
| 489 |
+
priceHtml = `<span style="color:${cfg.color}">${fmtNum(d.price)}</span> <span style="font-size:14px;color:var(--mut)">${esc(d.price_unit)}</span>`;
|
| 490 |
+
} else if(d.price != null){
|
| 491 |
+
priceHtml = `<span style="color:${cfg.color}">${fmtNum(d.price)}</span>`;
|
| 492 |
+
}
|
| 493 |
+
q("hero-price").innerHTML = priceHtml;
|
| 494 |
+
q("hero-note").textContent = cfg.note;
|
| 495 |
+
|
| 496 |
+
/* drivers */
|
| 497 |
+
const drvs = Array.isArray(d.drivers) ? d.drivers : [];
|
| 498 |
+
q("drivers").innerHTML = drvs.length
|
| 499 |
+
? drvs.map(dr => `<span class="driver">${esc(typeof dr === "string" ? dr : JSON.stringify(dr))}</span>`).join("")
|
| 500 |
+
: '<span class="driver" style="color:var(--mut)">no drivers reported</span>';
|
| 501 |
+
|
| 502 |
+
/* wasted energy available */
|
| 503 |
+
if(d.wasted_energy_available != null){
|
| 504 |
+
q("wasted-label").textContent = `Wasted energy available: ${fmtNum(d.wasted_energy_available)} ${d.wasted_energy_unit||""}`;
|
| 505 |
+
} else {
|
| 506 |
+
q("wasted-label").textContent = "Wasted energy: not reported";
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
/* soak_hard flag */
|
| 510 |
+
if(d.soak_hard === true){
|
| 511 |
+
q("soak-hard-badge").innerHTML = '<span class="soak-hard">SOAK HARD</span>';
|
| 512 |
+
} else {
|
| 513 |
+
q("soak-hard-badge").innerHTML="";
|
| 514 |
+
}
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
/* ── sovereign banner ─────────────────────────────────────────── */
|
| 518 |
+
function renderSovereign(d){
|
| 519 |
+
const banner = q("sov-banner");
|
| 520 |
+
if(!d){
|
| 521 |
+
banner.className="sov-banner loading";
|
| 522 |
+
q("sov-icon").textContent="⚠";
|
| 523 |
+
q("sov-title").textContent="Sovereignty gate unreachable";
|
| 524 |
+
q("sov-desc").textContent="Cannot verify sovereign state — feed down. Status: HALF-STATE assumed.";
|
| 525 |
+
return;
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
/* Accept both {sovereign:true/false} and {gate:{sovereign:...}} shapes */
|
| 529 |
+
const isSov = d.sovereign === true
|
| 530 |
+
|| (d.gate && d.gate.sovereign === true)
|
| 531 |
+
|| (d.result && d.result.sovereign === true);
|
| 532 |
+
|
| 533 |
+
if(isSov){
|
| 534 |
+
banner.className="sov-banner sov";
|
| 535 |
+
q("sov-icon").textContent="⚡";
|
| 536 |
+
q("sov-title").innerHTML = '<span class="badge badge-sov">SOVEREIGN</span> — running on own metal';
|
| 537 |
+
q("sov-desc").textContent = d.node_id
|
| 538 |
+
? `Node: ${d.node_id}. Compute fully local — no external routing. Data stays on-prem.`
|
| 539 |
+
: "Compute fully local — no external routing. Data stays on-prem.";
|
| 540 |
+
} else {
|
| 541 |
+
banner.className="sov-banner half";
|
| 542 |
+
q("sov-icon").textContent="🔗";
|
| 543 |
+
q("sov-title").innerHTML = '<span class="badge badge-half">HALF-STATE</span> — routed externally';
|
| 544 |
+
q("sov-desc").textContent = "This request is handled via an external or managed node. Sovereignty not established. Joules will remain SAMPLE until a local sovereign GPU is active.";
|
| 545 |
+
}
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
/* ── feeds panel ──────────────────────────────────────────────── */
|
| 549 |
+
const KNOWN_FEEDS = [
|
| 550 |
+
{feed:"awattar", display:"aWATTar", desc:"Real-time day-ahead electricity price (DE/AT market)"},
|
| 551 |
+
{feed:"energy_charts", display:"Energy-Charts", desc:"Renewable share + grid frequency (DE)"},
|
| 552 |
+
{feed:"uk_carbon", display:"UK Carbon Intensity", desc:"Real-time UK grid carbon intensity (gCO₂/kWh)"},
|
| 553 |
+
{feed:"open_meteo", display:"Open-Meteo", desc:"Weather (wind/solar irradiance) for harvest forecast"},
|
| 554 |
+
{feed:"caiso", display:"CAISO", desc:"California ISO real-time system price (US West)"},
|
| 555 |
+
];
|
| 556 |
+
|
| 557 |
+
function renderFeeds(readings){
|
| 558 |
+
const grid = q("feeds-grid");
|
| 559 |
+
if(!readings || !Array.isArray(readings) || readings.length === 0){
|
| 560 |
+
grid.innerHTML = `<div class="feed-unavail">Posture feed down — no reading data available.</div>`;
|
| 561 |
+
return;
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
/* Build a lookup from actual readings */
|
| 565 |
+
const byFeed = {};
|
| 566 |
+
readings.forEach(r => { byFeed[(r.feed||"").toLowerCase()] = r; });
|
| 567 |
+
|
| 568 |
+
/* Merge known feeds + any extra returned */
|
| 569 |
+
const allFeedIds = new Set([
|
| 570 |
+
...KNOWN_FEEDS.map(f => f.feed.toLowerCase()),
|
| 571 |
+
...Object.keys(byFeed),
|
| 572 |
+
]);
|
| 573 |
+
|
| 574 |
+
const rows = [];
|
| 575 |
+
KNOWN_FEEDS.forEach(kf => {
|
| 576 |
+
const key = kf.feed.toLowerCase();
|
| 577 |
+
allFeedIds.delete(key);
|
| 578 |
+
rows.push({display: kf.display, desc: kf.desc, data: byFeed[key] || null});
|
| 579 |
+
});
|
| 580 |
+
/* extras not in known list */
|
| 581 |
+
allFeedIds.forEach(id => {
|
| 582 |
+
rows.push({display: id, desc:"", data: byFeed[id]||null});
|
| 583 |
+
});
|
| 584 |
+
|
| 585 |
+
grid.innerHTML = rows.map(row => {
|
| 586 |
+
const r = row.data;
|
| 587 |
+
const reachable = r ? r.reachable !== false : false;
|
| 588 |
+
const dotCls = !r ? "loading" : reachable ? "live" : "down";
|
| 589 |
+
const statusBadge = !r
|
| 590 |
+
? `<span class="badge" style="background:#1c2030;color:var(--mut)">no data</span>`
|
| 591 |
+
: reachable
|
| 592 |
+
? `<span class="badge badge-live">LIVE</span>`
|
| 593 |
+
: `<span class="badge badge-down">DOWN</span>`;
|
| 594 |
+
const measuredBadge = r && r.measured === true
|
| 595 |
+
? `<span class="badge badge-measured">MEASURED</span>`
|
| 596 |
+
: r ? `<span class="badge badge-sample">SAMPLE</span>` : "";
|
| 597 |
+
const val = r && r.value != null ? fmtNum(r.value) : "—";
|
| 598 |
+
const unit = r && r.unit ? esc(r.unit) : "";
|
| 599 |
+
const note = r && r.note ? `<div class="feed-note">${esc(r.note)}</div>` : (row.desc ? `<div class="feed-note">${esc(row.desc)}</div>` : "");
|
| 600 |
+
|
| 601 |
+
return `<div class="feed-row">
|
| 602 |
+
<span class="feed-dot ${dotCls}" title="${reachable ? "live" : "down"}"></span>
|
| 603 |
+
<div style="flex:1;min-width:0">
|
| 604 |
+
<div class="feed-name">${esc(row.display)}</div>
|
| 605 |
+
<div class="feed-val">${esc(val)} <span class="feed-unit">${unit}</span></div>
|
| 606 |
+
<div class="feed-badges">${statusBadge}${measuredBadge}</div>
|
| 607 |
+
${note}
|
| 608 |
+
</div>
|
| 609 |
+
</div>`;
|
| 610 |
+
}).join("");
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
/* ── soak panel ───────────────────────────────────────────────── */
|
| 614 |
+
function renderSoak(d){
|
| 615 |
+
const card = q("soak-card");
|
| 616 |
+
if(!d){
|
| 617 |
+
card.innerHTML = `<div class="feed-unavail">Soak plan feed unavailable — cannot determine formula-bounded plan. Will retry on next refresh.</div>`;
|
| 618 |
+
return;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
const admitted = d.admit === true || d.admit === "true";
|
| 622 |
+
const admitHtml = admitted
|
| 623 |
+
? `<span class="soak-admit">✓ ADMIT — wasted energy available, plan within bounds</span>`
|
| 624 |
+
: `<span class="soak-admit deny">✗ HOLD — soak not admitted (posture or bounds)</span>`;
|
| 625 |
+
|
| 626 |
+
const bekBits = d.bekenstein_cap_bits != null ? fmtNum(d.bekenstein_cap_bits) : "—";
|
| 627 |
+
const landauJ = d.landauer_floor_joules != null ? fmtNum(d.landauer_floor_joules) : "—";
|
| 628 |
+
const ouroboros = d.ouroboros_max_iters != null ? fmtNum(d.ouroboros_max_iters) : "—";
|
| 629 |
+
const soakVal = d.soak != null ? esc(String(d.soak)) : "—";
|
| 630 |
+
|
| 631 |
+
const jobs = Array.isArray(d.admitted_ids) ? d.admitted_ids : [];
|
| 632 |
+
const jobsHtml = jobs.length
|
| 633 |
+
? `<div class="soak-jobs"><h3>Admitted job IDs</h3><div class="job-list">${jobs.map(j=>`<span class="job-chip">${esc(String(j))}</span>`).join("")}</div></div>`
|
| 634 |
+
: `<div class="soak-jobs"><h3>Admitted job IDs</h3><div class="job-list"><span style="color:var(--mut);font-size:12.5px">none admitted</span></div></div>`;
|
| 635 |
+
|
| 636 |
+
const reasonHtml = d.reason
|
| 637 |
+
? `<div class="soak-reason">Reason: ${esc(d.reason)}</div>`
|
| 638 |
+
: "";
|
| 639 |
+
|
| 640 |
+
card.innerHTML = `
|
| 641 |
+
${admitHtml}
|
| 642 |
+
<div class="soak-grid">
|
| 643 |
+
<div class="soak-kpi">
|
| 644 |
+
<div class="k">Bekenstein Cap</div>
|
| 645 |
+
<div class="v">${bekBits}</div>
|
| 646 |
+
<div class="d">bits — max information throughput proven by the Bekenstein bound. We only run as much as the proven info budget allows.</div>
|
| 647 |
+
</div>
|
| 648 |
+
<div class="soak-kpi">
|
| 649 |
+
<div class="k">Landauer Floor</div>
|
| 650 |
+
<div class="v">${landauJ}</div>
|
| 651 |
+
<div class="d">joules — thermodynamic minimum energy per bit-erasure (kT ln2). Computations below this cost are physically impossible.</div>
|
| 652 |
+
</div>
|
| 653 |
+
<div class="soak-kpi">
|
| 654 |
+
<div class="k">Ouroboros Bound</div>
|
| 655 |
+
<div class="v">${ouroboros}</div>
|
| 656 |
+
<div class="d">max iterations — derived from Λ (Conjecture 1, advisory). Prevents unbounded self-modification loops. <span class="badge badge-advisory">Λ advisory</span></div>
|
| 657 |
+
</div>
|
| 658 |
+
<div class="soak-kpi">
|
| 659 |
+
<div class="k">Soak</div>
|
| 660 |
+
<div class="v">${soakVal}</div>
|
| 661 |
+
<div class="d">whether the system is soaking this cycle (consuming admitted wasted energy).</div>
|
| 662 |
+
</div>
|
| 663 |
+
</div>
|
| 664 |
+
${jobsHtml}
|
| 665 |
+
${reasonHtml}
|
| 666 |
+
`;
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
/* ── receipt panel ────────────────────────────────────────────── */
|
| 670 |
+
function renderReceipt(d){
|
| 671 |
+
const card = q("receipt-card");
|
| 672 |
+
if(!d){
|
| 673 |
+
card.innerHTML = `<div class="feed-unavail">Receipt feed unavailable — no receipt data. Will retry.</div>`;
|
| 674 |
+
return;
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
const joulLabel = (d.joules_label||"").toLowerCase();
|
| 678 |
+
const joulMeasured = joulLabel === "measured";
|
| 679 |
+
const joulBadge = joulMeasured
|
| 680 |
+
? `<span class="badge badge-measured">MEASURED</span>`
|
| 681 |
+
: `<span class="badge badge-sample">SAMPLE</span>`;
|
| 682 |
+
const joulsVal = d.joules_est != null ? fmtNum(d.joules_est) : "—";
|
| 683 |
+
|
| 684 |
+
const priceRow = d.price_measured != null
|
| 685 |
+
? `<tr><td class="lbl">Price (measured)</td><td class="val">${esc(String(d.price_measured))}</td></tr>`
|
| 686 |
+
: "";
|
| 687 |
+
|
| 688 |
+
const rows = [
|
| 689 |
+
["Energy source", d.energy_source],
|
| 690 |
+
["Posture", d.posture],
|
| 691 |
+
].map(([l,v]) => v != null
|
| 692 |
+
? `<tr><td class="lbl">${esc(l)}</td><td class="val">${esc(String(v))}</td></tr>`
|
| 693 |
+
: ""
|
| 694 |
+
).join("");
|
| 695 |
+
|
| 696 |
+
/* extra keys except the ones we've already shown */
|
| 697 |
+
const skip = new Set(["energy_source","posture","price_measured","joules_est","joules_label","status","fetchedAt","doctrine","citations"]);
|
| 698 |
+
const extras = Object.entries(d)
|
| 699 |
+
.filter(([k]) => !skip.has(k) && d[k] != null)
|
| 700 |
+
.map(([k,v]) => `<tr><td class="lbl">${esc(k)}</td><td class="val">${esc(typeof v==="object"?JSON.stringify(v):String(v))}</td></tr>`)
|
| 701 |
+
.join("");
|
| 702 |
+
|
| 703 |
+
card.innerHTML = `
|
| 704 |
+
<table class="receipt-table">
|
| 705 |
+
<thead><tr><th>Field</th><th>Value</th></tr></thead>
|
| 706 |
+
<tbody>
|
| 707 |
+
${rows}
|
| 708 |
+
${priceRow}
|
| 709 |
+
<tr class="joules-row">
|
| 710 |
+
<td class="lbl">Joules (estimated) ${joulBadge}</td>
|
| 711 |
+
<td class="val">${esc(joulsVal)}</td>
|
| 712 |
+
</tr>
|
| 713 |
+
${extras}
|
| 714 |
+
</tbody>
|
| 715 |
+
</table>
|
| 716 |
+
<div class="receipt-note">
|
| 717 |
+
Joules become <strong>MEASURED</strong> only when on-box NVML is active on a sovereign GPU node.
|
| 718 |
+
Until then all joule readings are <strong>SAMPLE</strong> — statistically estimated, not directly metered.
|
| 719 |
+
This is an honest label, not a placeholder: the system does real work, the accounting is just not yet metered.
|
| 720 |
+
</div>
|
| 721 |
+
`;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
/* ── world panel ──────────────────────────────────────────────── */
|
| 725 |
+
function renderWorld(d){
|
| 726 |
+
const best = q("world-best");
|
| 727 |
+
const grid = q("world-grid");
|
| 728 |
+
if(!d || !d.shares){
|
| 729 |
+
best.textContent = "World feed unavailable — cannot show zone renewable shares.";
|
| 730 |
+
grid.innerHTML = "";
|
| 731 |
+
return;
|
| 732 |
+
}
|
| 733 |
+
|
| 734 |
+
const bz = d.best_zone || "—";
|
| 735 |
+
const bs = d.best_share != null ? Math.round(d.best_share*100)+"%" : "—";
|
| 736 |
+
best.innerHTML = `Best zone: <strong style="color:var(--ok)">${esc(bz)}</strong>
|
| 737 |
+
<span style="font-family:var(--mono);color:var(--ok);font-weight:700"> ${esc(bs)}</span>
|
| 738 |
+
renewable share — follow the wind to this zone for greenest harvest.`;
|
| 739 |
+
|
| 740 |
+
const zones = Object.entries(d.shares)
|
| 741 |
+
.sort((a,b) => (b[1]||0)-(a[1]||0));
|
| 742 |
+
|
| 743 |
+
grid.innerHTML = zones.map(([zone, share]) => {
|
| 744 |
+
const pct = share != null ? Math.round(share*100)+"%" : "—";
|
| 745 |
+
const isBest = zone === d.best_zone;
|
| 746 |
+
return `<div class="zone-card${isBest?" best":""}">
|
| 747 |
+
<div class="zone-name">${esc(zone)}</div>
|
| 748 |
+
<div class="zone-share">${esc(pct)}</div>
|
| 749 |
+
${isBest ? `<div class="zone-best-tag">★ best</div>` : ""}
|
| 750 |
+
</div>`;
|
| 751 |
+
}).join("");
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
/* ── timestamp ────────────────────────────────────────────────── */
|
| 755 |
+
function updateTs(){
|
| 756 |
+
const now = new Date();
|
| 757 |
+
q("ts").textContent = now.toUTCString().replace("GMT","UTC");
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
/* ── countdown tick ───────────────────────────────────────────── */
|
| 761 |
+
let _nextRefresh = Date.now() + 30000;
|
| 762 |
+
function startTick(){
|
| 763 |
+
setInterval(()=>{
|
| 764 |
+
const s = Math.max(0, Math.round((_nextRefresh - Date.now())/1000));
|
| 765 |
+
q("tick").textContent = `↺ ${s}s`;
|
| 766 |
+
}, 1000);
|
| 767 |
+
}
|
| 768 |
+
|
| 769 |
+
/* ── main load ────────────────────────────────────────────────── */
|
| 770 |
+
async function loadAll(manual){
|
| 771 |
+
_nextRefresh = Date.now() + 30000;
|
| 772 |
+
updateTs();
|
| 773 |
+
q("btn-refresh").textContent = "loading…";
|
| 774 |
+
q("btn-refresh").disabled = true;
|
| 775 |
+
|
| 776 |
+
try {
|
| 777 |
+
/* parallel fetch all endpoints */
|
| 778 |
+
const [posture, plan, receipt, world, sovereign] = await Promise.all([
|
| 779 |
+
safeFetch(ENDPOINTS.posture, "posture"),
|
| 780 |
+
safeFetch(ENDPOINTS.plan, "plan"),
|
| 781 |
+
safeFetch(ENDPOINTS.receipt, "receipt"),
|
| 782 |
+
safeFetch(ENDPOINTS.world, "world"),
|
| 783 |
+
safeFetch(ENDPOINTS.sovereign, "sovereign"),
|
| 784 |
+
]);
|
| 785 |
+
|
| 786 |
+
renderPosture(posture);
|
| 787 |
+
renderFeeds(posture ? posture.readings : null);
|
| 788 |
+
renderSovereign(sovereign);
|
| 789 |
+
renderSoak(plan);
|
| 790 |
+
renderReceipt(receipt);
|
| 791 |
+
renderWorld(world);
|
| 792 |
+
updateTs();
|
| 793 |
+
} finally {
|
| 794 |
+
q("btn-refresh").textContent = "↻ Refresh";
|
| 795 |
+
q("btn-refresh").disabled = false;
|
| 796 |
+
}
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
/* ── auto-refresh every 30 s ──────────────────────────────────── */
|
| 800 |
+
function scheduleRefresh(){
|
| 801 |
+
setTimeout(async ()=>{
|
| 802 |
+
await loadAll(false);
|
| 803 |
+
scheduleRefresh();
|
| 804 |
+
}, 30000);
|
| 805 |
+
}
|
| 806 |
+
|
| 807 |
+
/* ── boot ─────────────────────────────────────────────────────── */
|
| 808 |
+
(async()=>{
|
| 809 |
+
startTick();
|
| 810 |
+
await loadAll(false);
|
| 811 |
+
scheduleRefresh();
|
| 812 |
+
})();
|
| 813 |
+
</script>
|
| 814 |
+
</body>
|
| 815 |
+
</html>
|