betterwithage Claude Opus 4.7 commited on
Commit
97bcac1
·
verified ·
1 Parent(s): cb237dd

deploy(hf): sync szl-holdings/a11oy@main derived COPY set

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy main.
Files: 1037 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).

Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

static/3d/holographic.html CHANGED
@@ -331,6 +331,7 @@ const SURFACES = [
331
  { id: "brainqueryaudit", cat: "brain", title: "Brain Query Audit · append-only hash-linked ledger of brain queries + the honest verdict each returned · POST appends {query, verdict, grounding_label} and mints an UNSIGNED SHA-256 receipt chained to the prior entry (tamper-evident); GET recomputes the whole chain → CHAIN-INTACT/CHAIN-BROKEN (never softened), ephemeral in-memory ledger labelled honestly, MODELED, receipt-on-write-not-on-read", mod: "/static/3d/surfaces/brainqueryaudit.js" },
332
  { id: "brainlineage", cat: "brain", title: "Brain Lineage · node-origin chain · how each knowledge-graph node ENTERED the graph, read VERBATIM from its OWN real origin fields (source/url → structural derivation → none) → TRACED/PARTIAL-LINEAGE/UNKNOWN-ORIGIN, a node with no source is UNKNOWN-ORIGIN never a fabricated source, aggregate never TRACED while any origin UNKNOWN, unsigned SHA-256 receipt-on-write (node-origin lineage, NOT per-answer provenance, NOT build/model attestation)", mod: "/static/3d/surfaces/brainlineage.js" },
333
  { id: "brainexplain", cat: "brain", title: "Brain Explain · transparent explanation of WHY the brain retrieved what it did · MODELED descriptive trace over the REAL retrieval subgraph (which query terms matched which seed nodes, per-node ppr-vs-salience rationale, communities traversed, each node's OWN label VERBATIM) → EXPLAINABLE/PARTIALLY-EXPLAINABLE/OPAQUE (never invents a rationale; honest OPAQUE beats a fake one), unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainexplain.js" },
 
334
  ];
335
 
336
  const stageEl = document.getElementById("stage");
 
331
  { id: "brainqueryaudit", cat: "brain", title: "Brain Query Audit · append-only hash-linked ledger of brain queries + the honest verdict each returned · POST appends {query, verdict, grounding_label} and mints an UNSIGNED SHA-256 receipt chained to the prior entry (tamper-evident); GET recomputes the whole chain → CHAIN-INTACT/CHAIN-BROKEN (never softened), ephemeral in-memory ledger labelled honestly, MODELED, receipt-on-write-not-on-read", mod: "/static/3d/surfaces/brainqueryaudit.js" },
332
  { id: "brainlineage", cat: "brain", title: "Brain Lineage · node-origin chain · how each knowledge-graph node ENTERED the graph, read VERBATIM from its OWN real origin fields (source/url → structural derivation → none) → TRACED/PARTIAL-LINEAGE/UNKNOWN-ORIGIN, a node with no source is UNKNOWN-ORIGIN never a fabricated source, aggregate never TRACED while any origin UNKNOWN, unsigned SHA-256 receipt-on-write (node-origin lineage, NOT per-answer provenance, NOT build/model attestation)", mod: "/static/3d/surfaces/brainlineage.js" },
333
  { id: "brainexplain", cat: "brain", title: "Brain Explain · transparent explanation of WHY the brain retrieved what it did · MODELED descriptive trace over the REAL retrieval subgraph (which query terms matched which seed nodes, per-node ppr-vs-salience rationale, communities traversed, each node's OWN label VERBATIM) → EXPLAINABLE/PARTIALLY-EXPLAINABLE/OPAQUE (never invents a rationale; honest OPAQUE beats a fake one), unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainexplain.js" },
334
+ { id: "braingaps", cat: "brain", title: "Brain Gaps · an honest map of what the brain does NOT know · MEASURED thin (sparse) communities + weakly-connected island nodes (degree≤1) + weak-label share over the live graph, and per-query COVERED/THIN/GAP grounding → estate verdict WELL-COVERED/PATCHY/SPARSE (a GAP is never fabricated into coverage), unsigned SHA-256 receipt-on-write (MODELED)", mod: "/static/3d/surfaces/braingaps.js" },
335
  ];
336
 
337
  const stageEl = document.getElementById("stage");
static/3d/surfaces/braingaps.js ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11
3
+ //
4
+ // surfaces/braingaps.js — BRAIN GAPS · a live map of what the brain does NOT know.
5
+ // The mirror image of the coverage surfaces: it renders where the live knowledge graph is
6
+ // THIN or EMPTY — sparse communities, weakly-connected island nodes, and the share of nodes
7
+ // with no real honesty label. Pure honesty/observability over the knowledge graph; it advances
8
+ // no detection / fusion / effector / targeting / cueing capability.
9
+ //
10
+ // A ring of GAP BARS, one per structural gap metric (island share, thin-community share,
11
+ // weak-label share). Each bar's height is that MEASURED fraction; a taller bar means MORE gap.
12
+ // A CORE orb at the centre carries the estate-wide verdict — WELL-COVERED / PATCHY / SPARSE.
13
+ // When a query is supplied the core instead reflects the per-topic verdict — COVERED / THIN /
14
+ // GAP — and a GAP is drawn plainly (violet), never dressed up as coverage.
15
+ //
16
+ // DATA: live snapshot from GET /api/a11oy/v1/brain/gaps (PURE READ, mints nothing):
17
+ // ok, label, estate_verdict, query,
18
+ // gaps{ label (MEASURED), estate_verdict, metrics{ node_count, island_count, isolated_count,
19
+ // island_share, thin_community_count, thin_community_share, community_count,
20
+ // weak_label_count, weak_label_share, label_distribution },
21
+ // topic{ label (MODELED), query, verdict, match_count, best_connected_degree } }.
22
+ //
23
+ // HONESTY LABEL: MODELED — this surface's own top label is MODELED (a derived gap map + verdict,
24
+ // not a measurement). The structural counts it renders are MEASURED and read VERBATIM; no
25
+ // coverage is fabricated — a topic the graph cannot ground is drawn as a GAP. No green
26
+ // "1.0 / VERIFIED" state. Trust ceiling 0.97, never 100%.
27
+ // COLOURS (approved palette only, no green): proof-teal 0x3af4c8 (WELL-COVERED / COVERED),
28
+ // lattice-blue 0x5b8dee (PATCHY / THIN / frame), violet-blue 0x8a6bff (SPARSE / GAP),
29
+ // grey 0x42505d (UNAVAILABLE / init). No amber, no crimson, no other purple.
30
+ // 0 RUNTIME CDN. Vendored three.js via page importmap (ctx.THREE).
31
+ // DOCTRINE v11: OBSERVES only — adds NOTHING to the locked-8 {F1,F4,F7,F11,F12,F18,F19,F22} @
32
+ // c7c0ba17; Λ stays Conjecture 1; introduces no theorem. Degrades grey on 404/error.
33
+
34
+ import { createShowcase } from "./_showcase.js";
35
+
36
+ const ID = "braingaps";
37
+ const TITLE = "Brain Gaps · an honest map of what the brain does NOT know (live)";
38
+
39
+ // same-origin, relative — no CDN, no cross-origin fetch. PURE-READ gap-map endpoint.
40
+ const EP = "/api/a11oy/v1/brain/gaps";
41
+
42
+ // verdict hues — approved palette only, no green
43
+ const C_OK = 0x3af4c8; // proof-teal — WELL-COVERED / COVERED
44
+ const C_MID = 0x5b8dee; // lattice-blue — PATCHY / THIN / frame
45
+ const C_GAP = 0x8a6bff; // violet-blue — SPARSE / GAP
46
+ const C_NEUTRAL = 0x42505d; // grey — UNAVAILABLE / init
47
+ const C_GRID = 0x1b3a44; // floor colour
48
+
49
+ // estate verdict -> core colour
50
+ function _estateColor(v) {
51
+ const s = String(v || "").toUpperCase();
52
+ if (s === "WELL-COVERED") return C_OK;
53
+ if (s === "PATCHY") return C_MID;
54
+ if (s === "SPARSE") return C_GAP;
55
+ return C_NEUTRAL; // init / unknown
56
+ }
57
+ // per-topic verdict -> core colour (when a query is supplied)
58
+ function _topicColor(v) {
59
+ const s = String(v || "").toUpperCase();
60
+ if (s === "COVERED") return C_OK;
61
+ if (s === "THIN") return C_MID;
62
+ if (s === "GAP") return C_GAP;
63
+ return C_NEUTRAL;
64
+ }
65
+ // a gap-fraction bar's colour: more gap = hotter (teal -> blue -> violet)
66
+ function _gapColor(frac) {
67
+ if (typeof frac !== "number" || !isFinite(frac)) return C_NEUTRAL;
68
+ if (frac >= 0.5) return C_GAP;
69
+ if (frac > 0.0) return C_MID;
70
+ return C_OK; // zero gap on this axis reads teal
71
+ }
72
+
73
+ let _stage = null, _THREE = null, _ctx = null, _group = null, _show = null;
74
+ let _frameReg = false, _polls = [], _el = {}, _badge = null, _plain = false;
75
+
76
+ let _core = null; // THREE.Mesh — the verdict core orb
77
+ let _bars = []; // Array<{ mesh, key, frac }>
78
+ let _spin = 0;
79
+
80
+ // The gap axes we render as bars (each a MEASURED fraction in [0,1]; more = more gap).
81
+ const _AXES = [
82
+ { key: "island_share", label: "island share (deg≤1)" },
83
+ { key: "thin_community_share", label: "thin-community share" },
84
+ { key: "weak_label_share", label: "weak-label share" },
85
+ ];
86
+
87
+ // live state (all read from JSON; nothing invented)
88
+ const S = {
89
+ label: null, // top honesty label VERBATIM (MODELED)
90
+ estate: null, // WELL-COVERED | PATCHY | SPARSE
91
+ query: null, // the topic asked, if any
92
+ topicVerd: null, // COVERED | THIN | GAP (only when query supplied)
93
+ topicMatch: null, // match_count for the topic
94
+ nodeCount: null,
95
+ islandCount: null,
96
+ isolated: null,
97
+ thinCount: null,
98
+ commCount: null,
99
+ weakCount: null,
100
+ fracs: {}, // key -> MEASURED fraction
101
+ trustCeil: null,
102
+ lambda: null,
103
+ locked: null,
104
+ state: "init",
105
+ };
106
+
107
+ // =============================================================================
108
+ export function mount(ctx) {
109
+ _ctx = ctx; _stage = ctx.stage; _THREE = ctx.THREE;
110
+ _group = new _THREE.Group();
111
+ _stage.scene.add(_group);
112
+ _stage.camera.position.set(0, 5.0, 17);
113
+ try { if (_stage.controls && _stage.controls.target) { _stage.controls.target.set(0, 2.0, 0); _stage.controls.update(); } } catch (_) {}
114
+ try { _stage.setBloom(true); } catch (_) {}
115
+
116
+ _buildFloor();
117
+ _buildCore();
118
+
119
+ if (!_frameReg) { _stage.onFrame(_onFrame); _frameReg = true; }
120
+
121
+ _badge = ctx.live.createBadge();
122
+ _polls.push(ctx.live.poll(EP, 8000, _onData, {
123
+ badge: _badge, onState: (m) => { S.state = m.state; _paintOverlay(); _paintCore(); },
124
+ }));
125
+
126
+ _buildOverlay();
127
+ return { id: ID, started: true };
128
+ }
129
+
130
+ // =============================================================================
131
+ function _buildFloor() {
132
+ const THREE = _THREE;
133
+ const grid = new THREE.GridHelper(40, 40, C_GRID, 0x0f2027);
134
+ grid.material.opacity = 0.16; grid.material.transparent = true; grid.position.y = -0.01;
135
+ _group.add(grid);
136
+ }
137
+
138
+ function _buildCore() {
139
+ const THREE = _THREE;
140
+ const g = new THREE.IcosahedronGeometry(1.5, 1);
141
+ _core = new THREE.Mesh(g, new THREE.MeshStandardMaterial({
142
+ color: C_NEUTRAL, emissive: C_NEUTRAL, emissiveIntensity: 0.35,
143
+ transparent: true, opacity: 0.9, flatShading: true,
144
+ }));
145
+ _core.position.set(0, 4.4, 0);
146
+ _group.add(_core);
147
+
148
+ // lattice ring beneath the core (the gap-map base rail)
149
+ const pts = [];
150
+ const R = 5.2;
151
+ for (let i = 0; i <= 64; i++) {
152
+ const a = (i / 64) * Math.PI * 2;
153
+ pts.push(new THREE.Vector3(Math.cos(a) * R, 0.02, Math.sin(a) * R));
154
+ }
155
+ const rg = new THREE.BufferGeometry().setFromPoints(pts);
156
+ const ring = new THREE.Line(rg, new THREE.LineBasicMaterial({
157
+ color: C_MID, transparent: true, opacity: 0.4,
158
+ }));
159
+ _group.add(ring);
160
+ }
161
+
162
+ // Build (or rebuild) one bar per gap axis, in a ring, height by the MEASURED fraction. Called
163
+ // on each live snapshot so the ring always mirrors the CURRENT feed (never a hard-coded set).
164
+ function _buildBars() {
165
+ const THREE = _THREE;
166
+ _disposeBars();
167
+
168
+ const n = _AXES.length;
169
+ const R = 4.6;
170
+ const geo = new THREE.BoxGeometry(0.9, 1.0, 0.9);
171
+
172
+ for (let i = 0; i < n; i++) {
173
+ const ax = _AXES[i];
174
+ const frac = (typeof S.fracs[ax.key] === "number" && isFinite(S.fracs[ax.key]))
175
+ ? Math.max(0, Math.min(1, S.fracs[ax.key])) : null;
176
+ // height: by fraction when present, else a short "stub" so an empty axis still reads.
177
+ const h = frac != null ? 0.5 + frac * 4.0 : 0.5;
178
+ const color = frac != null ? _gapColor(frac) : C_NEUTRAL;
179
+ const a = (i / n) * Math.PI * 2;
180
+ const x = Math.cos(a) * R, z = Math.sin(a) * R;
181
+
182
+ const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({
183
+ color, emissive: color, emissiveIntensity: 0.26,
184
+ transparent: true, opacity: frac != null ? 0.92 : 0.4,
185
+ }));
186
+ mesh.scale.y = h;
187
+ mesh.position.set(x, h / 2, z);
188
+ _group.add(mesh);
189
+
190
+ _bars.push({ mesh, key: ax.key, frac });
191
+ }
192
+ }
193
+
194
+ function _disposeBars() {
195
+ const rm = (o) => {
196
+ if (!o) return;
197
+ try {
198
+ if (o.geometry && o.geometry.dispose) o.geometry.dispose();
199
+ if (o.material) { const ms = Array.isArray(o.material) ? o.material : [o.material]; ms.forEach((m) => m.dispose && m.dispose()); }
200
+ if (_group) _group.remove(o);
201
+ } catch (_) {}
202
+ };
203
+ _bars.forEach((b) => rm(b.mesh));
204
+ _bars = [];
205
+ }
206
+
207
+ // =============================================================================
208
+ // live data handler — read VERBATIM, never upgrade, never fabricate coverage
209
+ // =============================================================================
210
+ function _onData(j) {
211
+ // Top honesty label VERBATIM; absent live data this surface is MODELED (a derived gap map).
212
+ S.label = (j.label || "MODELED").toUpperCase();
213
+ S.estate = j && j.estate_verdict ? String(j.estate_verdict).toUpperCase() : null;
214
+ S.query = j && j.query ? String(j.query) : "";
215
+
216
+ const g = (j && j.gaps) || {};
217
+ const m = (g && g.metrics) || {};
218
+ S.nodeCount = typeof m.node_count === "number" ? m.node_count : null;
219
+ S.islandCount = typeof m.island_count === "number" ? m.island_count : null;
220
+ S.isolated = typeof m.isolated_count === "number" ? m.isolated_count : null;
221
+ S.thinCount = typeof m.thin_community_count === "number" ? m.thin_community_count : null;
222
+ S.commCount = typeof m.community_count === "number" ? m.community_count : null;
223
+ S.weakCount = typeof m.weak_label_count === "number" ? m.weak_label_count : null;
224
+
225
+ S.fracs = {};
226
+ _AXES.forEach((ax) => {
227
+ const v = m[ax.key];
228
+ if (typeof v === "number" && isFinite(v)) S.fracs[ax.key] = v;
229
+ });
230
+
231
+ const tp = (g && g.topic) || null;
232
+ S.topicVerd = tp && tp.verdict ? String(tp.verdict).toUpperCase() : null;
233
+ S.topicMatch = tp && typeof tp.match_count === "number" ? tp.match_count : null;
234
+
235
+ const d = (g && g.doctrine) || (j && j.doctrine) || {};
236
+ S.trustCeil = typeof d.trust_ceiling === "number" ? d.trust_ceiling : null;
237
+ S.lambda = typeof d.lambda === "string" ? d.lambda : null;
238
+ S.locked = typeof d.locked_proven === "number" ? d.locked_proven : null;
239
+
240
+ _buildBars();
241
+ _paintCore();
242
+ _paintOverlay();
243
+ }
244
+
245
+ // =============================================================================
246
+ function _onFrame() {
247
+ const t = performance.now();
248
+ if (_group) _group.rotation.y = Math.sin(t * 0.00005) * 0.08;
249
+
250
+ const live = S.state === "live";
251
+ if (_core) {
252
+ _core.rotation.y += 0.004; _core.rotation.x += 0.0015;
253
+ const pulse = 0.35 + (live ? 0.25 : 0.08) * (0.5 + 0.5 * Math.sin(t * 0.003));
254
+ _core.material.emissiveIntensity = pulse;
255
+ }
256
+ if (_bars.length) {
257
+ _spin = (t * 0.0002) % 1;
258
+ const lead = Math.floor(_spin * _bars.length);
259
+ for (let i = 0; i < _bars.length; i++) {
260
+ const b = _bars[i];
261
+ const near = i === lead;
262
+ // a materially gappy bar (>=0.5) always glows hot; others follow the sweep when live.
263
+ const gappy = typeof b.frac === "number" && b.frac >= 0.5;
264
+ const base = gappy ? 0.6 : (b.frac != null && live ? 0.26 : 0.12);
265
+ b.mesh.material.emissiveIntensity = (near && live) ? Math.max(base, 0.85) : base;
266
+ }
267
+ }
268
+ }
269
+
270
+ // =============================================================================
271
+ function _paintCore() {
272
+ if (!_core) return;
273
+ // when a topic is asked, the core reflects the per-topic verdict; else the estate verdict.
274
+ const useTopic = !!(S.query && S.topicVerd);
275
+ const col = (S.state === "live")
276
+ ? (useTopic ? _topicColor(S.topicVerd) : _estateColor(S.estate))
277
+ : C_NEUTRAL;
278
+ _core.material.color.setHex(col);
279
+ _core.material.emissive.setHex(col);
280
+ }
281
+
282
+ // =============================================================================
283
+ // overlay (HUD)
284
+ // =============================================================================
285
+ function _buildOverlay() {
286
+ const ctx = _ctx;
287
+ _show = createShowcase(ctx, {
288
+ id: ID, title: TITLE, accent: "#5b8dee", badge: _badge,
289
+ chips: [{ label: "MODELED", text: "gap map", name: "lbl" },
290
+ { label: "—", text: "verdict", name: "vrd" }],
291
+ legend: ["MODELED"],
292
+ });
293
+ const host = _show.body;
294
+
295
+ const sub = document.createElement("div");
296
+ sub.style.cssText = "color:#9fb1bf;font-size:11px;line-height:1.55";
297
+ sub.innerHTML =
298
+ 'A live map of <b>what the brain does NOT know.</b> It reads the knowledge graph and reports, ' +
299
+ 'without flattery, where it is <b>thin or empty</b>: sparse communities, weakly-connected ' +
300
+ '<b>island</b> nodes (degree ≤ 1), and the share of nodes carrying <b>no real honesty ' +
301
+ 'label</b>. For a query it grades the topic <b>COVERED / THIN / GAP</b> — and a <b>GAP</b> ' +
302
+ 'is drawn plainly, <b>never</b> dressed up as coverage. Estate verdict: <b>WELL-COVERED / ' +
303
+ 'PATCHY / SPARSE</b> (a SPARSE graph is never softened). Structural counts are MEASURED and ' +
304
+ 'read verbatim; labels are never upgraded. Strictly knowledge-graph honesty. 0 runtime CDN.';
305
+ host.appendChild(sub);
306
+
307
+ const card = document.createElement("div");
308
+ card.style.cssText = "background:#0a1117;border:1px solid #1d2a36;border-radius:9px;padding:9px 10px;display:flex;flex-direction:column;gap:6px";
309
+
310
+ const grid = document.createElement("div");
311
+ grid.style.cssText = "display:grid;grid-template-columns:1fr;gap:4px";
312
+ function kpiRow(id, label) {
313
+ const r = document.createElement("div");
314
+ r.style.cssText = "display:flex;justify-content:space-between;gap:10px;font-size:11px";
315
+ const l = document.createElement("span"); l.style.cssText = "color:#9fb1bf"; l.textContent = label;
316
+ const v = document.createElement("b");
317
+ v.id = id;
318
+ v.style.cssText = "font-variant-numeric:tabular-nums;color:#eef3f6;text-align:right;max-width:62%;overflow-wrap:anywhere";
319
+ v.textContent = "—";
320
+ _el[id] = v;
321
+ r.appendChild(l); r.appendChild(v); return r;
322
+ }
323
+ grid.appendChild(kpiRow("bg-estate", "estate verdict"));
324
+ grid.appendChild(kpiRow("bg-topic", "topic verdict"));
325
+ grid.appendChild(kpiRow("bg-nodes", "nodes"));
326
+ grid.appendChild(kpiRow("bg-islands", "islands (deg≤1)"));
327
+ grid.appendChild(kpiRow("bg-iso", "isolated (deg=0)"));
328
+ grid.appendChild(kpiRow("bg-thin", "thin communities"));
329
+ grid.appendChild(kpiRow("bg-weak", "weak-label nodes"));
330
+ grid.appendChild(kpiRow("bg-locked", "locked proofs"));
331
+ grid.appendChild(kpiRow("bg-ceil", "trust ceiling"));
332
+ grid.appendChild(kpiRow("bg-lambda", "Λ"));
333
+ card.appendChild(grid);
334
+ host.appendChild(card);
335
+
336
+ const leg = document.createElement("div");
337
+ leg.style.cssText = "font-size:9.5px;color:#6b7a86;line-height:1.6";
338
+ leg.innerHTML =
339
+ '<span style="color:#3af4c8">■</span> WELL-COVERED / COVERED &nbsp; ' +
340
+ '<span style="color:#5b8dee">■</span> PATCHY / THIN &nbsp; ' +
341
+ '<span style="color:#8a6bff">■</span> SPARSE / GAP &nbsp; ' +
342
+ '<span style="color:#8494a1">■</span> UNAVAILABLE. ' +
343
+ 'MODELED · structural counts MEASURED, read verbatim · coverage is never fabricated (a GAP is a GAP).';
344
+ card.appendChild(leg);
345
+
346
+ const pl = document.createElement("button");
347
+ pl.textContent = "◑ what this means";
348
+ pl.title = "Toggle plain-language explanation for investors & consumers.";
349
+ pl.style.cssText = "font:11px ui-monospace,monospace;padding:5px 11px;border-radius:7px;border:1px solid #3af4c8;background:#08140f;color:#3af4c8;cursor:pointer;width:fit-content";
350
+ pl.addEventListener("click", () => {
351
+ _plain = !_plain;
352
+ pl.style.background = _plain ? "#0f2a20" : "#08140f";
353
+ _applyPlain();
354
+ });
355
+ host.appendChild(pl);
356
+
357
+ const pd = document.createElement("div");
358
+ pd.id = "bg-plain";
359
+ pd.style.cssText = "font-size:10.5px;color:#c9d6df;line-height:1.55;border:1px dashed #26333f;border-radius:7px;padding:7px 9px;display:none";
360
+ _el["plain"] = pd;
361
+ host.appendChild(pd);
362
+
363
+ _paintOverlay();
364
+ }
365
+
366
+ function _applyPlain() {
367
+ const pd = _el["plain"];
368
+ if (!pd) return;
369
+ pd.style.display = _plain ? "block" : "none";
370
+ if (!_plain) return;
371
+ pd.innerHTML =
372
+ "<b>What this means:</b> an honest read of the brain’s <b>blind spots</b>. Instead of showing " +
373
+ "off what it knows, it shows where it is <b>thin or empty</b> — little clusters of knowledge " +
374
+ "with almost nothing in them, facts that connect to nothing else, and nodes that never got a " +
375
+ "trustworthy label. Ask it about a topic and it will tell you plainly whether it actually has " +
376
+ "grounding (<b>COVERED</b>), only a shaky bit (<b>THIN</b>), or <b>nothing at all</b> " +
377
+ "(<b>GAP</b>). It will <b>never</b> pretend to cover a topic it can’t. Overall it reads " +
378
+ "<b>WELL-COVERED</b>, <b>PATCHY</b>, or <b>SPARSE</b>, and a sparse graph is never softened. " +
379
+ "No “verified / 1.0” state; confidence is capped at 0.97, never 100%.";
380
+ }
381
+
382
+ function _tok(s) {
383
+ if (s === "live") return null;
384
+ if (s === "missing") return "NO-LIVE-DATA";
385
+ if (s === "degraded") return "DEGRADED";
386
+ if (s === "error") return "OFFLINE";
387
+ return "…";
388
+ }
389
+ function _set(id, v) { if (_el[id]) _el[id].textContent = v; }
390
+ function _n(v) { return v == null ? "—" : String(v); }
391
+
392
+ function _paintOverlay() {
393
+ const t = _tok(S.state);
394
+ // the headline verdict: topic verdict when a query is asked, else estate verdict.
395
+ const useTopic = !!(S.query && S.topicVerd);
396
+ const headline = t || (useTopic ? S.topicVerd : (S.estate || "—"));
397
+ if (_show) {
398
+ _show.setChip("lbl", S.label || "MODELED", { text: "gap map" });
399
+ _show.setChip("vrd", headline, { text: useTopic ? "topic" : "estate" });
400
+ }
401
+ _set("bg-estate", t || (S.estate || "—"));
402
+ _set("bg-topic", t || (useTopic
403
+ ? (S.topicVerd + (S.topicMatch != null ? " · " + S.topicMatch + " match" : ""))
404
+ : "—"));
405
+ _set("bg-nodes", t || _n(S.nodeCount));
406
+ _set("bg-islands", t || _n(S.islandCount));
407
+ _set("bg-iso", t || _n(S.isolated));
408
+ _set("bg-thin", t || (S.thinCount != null && S.commCount != null
409
+ ? S.thinCount + " / " + S.commCount : _n(S.thinCount)));
410
+ _set("bg-weak", t || _n(S.weakCount));
411
+ _set("bg-locked", t || (S.locked != null ? String(S.locked) : "—"));
412
+ _set("bg-ceil", t || (S.trustCeil != null ? String(S.trustCeil) : "—"));
413
+ _set("bg-lambda", t || (S.lambda || "—"));
414
+ if (_plain) _applyPlain();
415
+ }
416
+
417
+ // =============================================================================
418
+ export function unmount() {
419
+ _polls.forEach((p) => { try { p.stop(); } catch (_) {} }); _polls = [];
420
+ try { if (_show) _show.destroy(); } catch (_) {}
421
+ try {
422
+ _disposeBars();
423
+ if (_group && _stage) {
424
+ _group.traverse((o) => {
425
+ if (o.geometry && o.geometry.dispose) o.geometry.dispose();
426
+ if (o.material) {
427
+ const ms = Array.isArray(o.material) ? o.material : [o.material];
428
+ ms.forEach((mm) => { if (mm.dispose) mm.dispose(); });
429
+ }
430
+ });
431
+ _stage.scene.remove(_group);
432
+ }
433
+ } catch (_) {}
434
+ _group = _show = null;
435
+ _core = null; _bars = [];
436
+ _el = {}; _badge = null; _plain = false; _frameReg = false; _spin = 0;
437
+ _stage = _THREE = _ctx = null;
438
+ S.label = S.estate = S.query = S.topicVerd = null;
439
+ S.topicMatch = S.nodeCount = S.islandCount = S.isolated = null;
440
+ S.thinCount = S.commCount = S.weakCount = null;
441
+ S.fracs = {};
442
+ S.trustCeil = S.lambda = S.locked = null; S.state = "init";
443
+ }
444
+
445
+ export default { id: ID, title: TITLE, endpoints: [EP], mount, unmount };