betterwithage Claude Opus 4.7 commited on
Commit
dcf704e
·
verified ·
1 Parent(s): a09503b

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

Browse files

Reusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy main.
Files: 778 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>

Files changed (2) hide show
  1. pages/console.html +65 -0
  2. static/a11oy_landing.js +248 -159
pages/console.html CHANGED
@@ -2510,6 +2510,71 @@ function _bootGo(){go(VIEWS[start]?start:'command');}
2510
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',_bootGo);else setTimeout(_bootGo,0);
2511
  </script>
2512
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2513
  <script>
2514
  /* ============================================================================
2515
  a11oy build-wave — LEADER-GRADE out-of-this-world tabs + live/animated helpers
 
2510
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',_bootGo);else setTimeout(_bootGo,0);
2511
  </script>
2512
 
2513
+ <script>
2514
+ /* ============================================================================
2515
+ STATUS-EVERYWHERE (v2 console polish) — Vercel-style live status in the tab title
2516
+ + favicon, driven by the REAL org posture endpoint /api/a11oy/v1/lambda/org.
2517
+ The tab title shows the live org Λ and whether it clears the 0.90 floor; the
2518
+ favicon is repainted to match (proof-teal pass / gold below-floor / gray when the
2519
+ source is UNREACHABLE). HONEST by construction: on any fetch/parse failure the
2520
+ title reads "Λ N/A" and the dot goes gray — never a fabricated score, never a
2521
+ faked green. Self-contained, no new deps, 0 runtime CDN; one 45s poll, GPU-light
2522
+ (a 32px canvas favicon). KANCHAY palette only: void / proof / lattice / gold. */
2523
+ (function szlStatusEverywhere(){
2524
+ var BASE='a11oy console';
2525
+ var FLOOR=0.90;
2526
+ var C={void:'#080c14',proof:'#3af4c8',gold:'#d7b96b',gray:'#5a6b7a',lattice:'#5b8dee'};
2527
+ function iconLink(){
2528
+ var l=document.querySelector('link[rel~="icon"]');
2529
+ if(!l){ l=document.createElement('link'); l.rel='icon'; document.head.appendChild(l); }
2530
+ return l;
2531
+ }
2532
+ /* paint a 32px favicon: void ground + a status disc + a Λ glyph in the disc colour */
2533
+ function paintFavicon(color){
2534
+ try{
2535
+ var s=32, cv=document.createElement('canvas'); cv.width=s; cv.height=s;
2536
+ var x=cv.getContext('2d'); if(!x) return;
2537
+ x.fillStyle=C.void; x.fillRect(0,0,s,s);
2538
+ x.beginPath(); x.arc(s/2,s/2,s/2-2,0,Math.PI*2);
2539
+ x.lineWidth=2.5; x.strokeStyle=color; x.stroke();
2540
+ x.fillStyle=color; x.font='bold 19px Georgia,serif';
2541
+ x.textAlign='center'; x.textBaseline='middle';
2542
+ x.fillText('Λ', s/2, s/2+1);
2543
+ iconLink().href=cv.toDataURL('image/png');
2544
+ }catch(e){/* canvas unavailable — leave the static favicon, never crash */}
2545
+ }
2546
+ function apply(state){
2547
+ /* state: {lambda:Number|null, pass:Boolean|null, unreachable:Boolean} */
2548
+ var color, prefix;
2549
+ if(state.unreachable || state.lambda==null){
2550
+ color=C.gray; prefix='Λ N/A';
2551
+ } else {
2552
+ var clears=(state.pass===true) || (state.pass==null && state.lambda>=FLOOR);
2553
+ color=clears?C.proof:C.gold;
2554
+ prefix='Λ '+state.lambda.toFixed(3)+' '+(clears?'✓':'⚠');
2555
+ }
2556
+ document.title=prefix+' · '+BASE;
2557
+ paintFavicon(color);
2558
+ }
2559
+ function poll(){
2560
+ /* AbortController bounds the probe so a hung backend never wedges the poller. */
2561
+ var ctl=(typeof AbortController!=='undefined')?new AbortController():null;
2562
+ var to=ctl?setTimeout(function(){try{ctl.abort();}catch(e){}},8000):null;
2563
+ fetch('/api/a11oy/v1/lambda/org',{headers:{accept:'application/json'},signal:ctl?ctl.signal:undefined})
2564
+ .then(function(r){ if(!r.ok) throw new Error('HTTP '+r.status); return r.json(); })
2565
+ .then(function(d){
2566
+ if(to)clearTimeout(to);
2567
+ var lam=(d&&typeof d.lambda_org==='number')?d.lambda_org:null;
2568
+ var pass=(d&&typeof d.pass==='boolean')?d.pass:null;
2569
+ apply({lambda:lam,pass:pass,unreachable:lam==null});
2570
+ })
2571
+ .catch(function(){ if(to)clearTimeout(to); apply({lambda:null,pass:null,unreachable:true}); });
2572
+ }
2573
+ function start(){ apply({lambda:null,pass:null,unreachable:true}); poll(); setInterval(poll,45000); }
2574
+ if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start);else setTimeout(start,0);
2575
+ })();
2576
+ </script>
2577
+
2578
  <script>
2579
  /* ============================================================================
2580
  a11oy build-wave — LEADER-GRADE out-of-this-world tabs + live/animated helpers
static/a11oy_landing.js CHANGED
@@ -1,34 +1,41 @@
1
  /* ============================================================================
2
- * a11oy_landing.js — holographic hero for the a11oy front door.
3
  *
4
- * THE MOTIF: a "governed inference field" a holographic glass governance core
5
- * (fresnel icosahedron) wrapped by an instanced-particle shell whose points are
6
- * gated inference tokens streaming inward and being sealed at the core. The core
7
- * pulses on the advisory Λ trust-ceiling; particles that "pass the gate" brighten
8
- * to holo-teal, the rest dim a literal picture of deny-by-default governance.
 
 
 
 
 
 
 
 
 
 
 
9
  *
10
- * SOVEREIGN: Three.js r160 (MIT) + nothing else, vendored in-image under
11
- * /hero/vendor3d (importmap in a11oy_landing.html). 0 runtime CDN. Bloom is faked
12
- * with additive fresnel + a layered glow sprite (no postprocessing dependency).
13
  *
14
- * HONESTY DOCTRINE v11: Λ = Conjecture 1 (advisory, NOT a theorem). Nothing here
15
- * fabricates a number the scene is a motif; all live figures are fetched and
16
- * labelled by a11oy_landing.html. locked-proven kernel = EXACTLY 8.
17
  *
18
- * PERF: capped DPR, instanced points (single draw call), 60fps target. Honors
19
- * prefers-reduced-motion (renders one static frame) and downshifts particle count
20
- * on small / low-DPR devices.
21
  *
22
- * VISUAL FIX (2026-06-30): Tamed the canvas from blinding-cyan flood to a
23
- * restrained, elegant dark field. Key changes:
24
- * - Core glow opacity: clamped to 0.40 max (was 0.92) whispers, doesn't shout
25
- * - Halo sprite: opacity 0.10 (was 0.55), scale 7×7 (was 9×9)
26
- * - Particle alpha: gate-pass 0.45 (was 0.95), denied 0.18 (was 0.40)
27
- * - Canvas renderer alpha: transparent over deep near-black page bg (#05070d)
28
- * - Colors desaturated: teal at 30% brightness, dim at near-black for particles
29
- * - Net effect: holographic mesh whispers on deep dark, never floods
30
  *
31
- * Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
 
32
  * ========================================================================== */
33
  import * as THREE from "three";
34
 
@@ -37,10 +44,36 @@ const REDUCED = window.matchMedia &&
37
  const MOBILE = Math.min(window.innerWidth, window.innerHeight) < 720 ||
38
  /Mobi|Android/i.test(navigator.userAgent);
39
 
40
- // Desaturated, restrained palette hairlines on near-black, not bright floods
41
- const TEAL = new THREE.Color(0x1a6b63); // deep teal — was 0x39d8c8 (too bright)
42
- const DEEP = new THREE.Color(0x2a3578); // muted violet was 0x6a7bff (too bright)
43
- const DIM = new THREE.Color(0x0a0f1c); // near-black for denied particles
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  export function mountHero(canvas) {
46
  let renderer;
@@ -55,34 +88,29 @@ export function mountHero(canvas) {
55
 
56
  const DPR = Math.min(window.devicePixelRatio || 1, MOBILE ? 1.5 : 2);
57
  renderer.setPixelRatio(DPR);
58
- renderer.setClearColor(0x000000, 0); // fully transparent — page bg shows through
59
 
60
  const scene = new THREE.Scene();
61
  const camera = new THREE.PerspectiveCamera(46, 1, 0.1, 100);
62
- camera.position.set(0, 0, 7.2);
63
 
64
  const root = new THREE.Group();
65
  scene.add(root);
66
 
67
- // ---- Governance core: fresnel-shaded glass icosahedron --------------------
68
- const coreGeo = new THREE.IcosahedronGeometry(1.55, MOBILE ? 2 : 4);
69
  const coreMat = new THREE.ShaderMaterial({
70
- transparent: true,
71
- blending: THREE.AdditiveBlending,
72
- depthWrite: false,
73
  uniforms: {
74
  uTime: { value: 0 },
75
- uLambda: { value: 0.97 }, // advisory Λ ceiling (Conjecture 1)
76
- uTeal: { value: new THREE.Vector3(TEAL.r, TEAL.g, TEAL.b) },
77
- uDeep: { value: new THREE.Vector3(DEEP.r, DEEP.g, DEEP.b) },
78
  },
79
  vertexShader: /* glsl */`
80
- varying vec3 vN; varying vec3 vView;
81
- uniform float uTime;
82
  void main(){
83
  vN = normalize(normalMatrix * normal);
84
  vec3 p = position;
85
- // gentle breathing displacement along the normal
86
  float w = sin(uTime*0.8 + position.y*2.0)*0.03
87
  + sin(uTime*1.3 + position.x*3.0)*0.02;
88
  p += normal * w;
@@ -93,108 +121,131 @@ export function mountHero(canvas) {
93
  fragmentShader: /* glsl */`
94
  precision highp float;
95
  varying vec3 vN; varying vec3 vView;
96
- uniform float uTime; uniform float uLambda;
97
- uniform vec3 uTeal; uniform vec3 uDeep;
98
  void main(){
99
  float fres = pow(1.0 - max(dot(normalize(vN), normalize(vView)), 0.0), 2.4);
100
- // Λ-pulse: the shell breathes gently — restrained, not flooding
101
- float pulse = 0.5 + 0.5*sin(uTime*1.6);
102
- // TAMED: glow multiplier capped — elegant whisper, not a bright flood
103
- float glow = fres * (0.28 + 0.18*pulse) * uLambda;
104
- vec3 col = mix(uDeep, uTeal, fres);
105
- // max alpha 0.40 — ensures the dark page bg always dominates
106
- gl_FragColor = vec4(col * glow, clamp(glow,0.0,0.40));
107
  }`,
108
  });
109
- const core = new THREE.Mesh(coreGeo, coreMat);
110
  root.add(core);
111
 
112
- // wire lattice over the core (deny-by-default cage)very dim hairlines
113
  const cage = new THREE.LineSegments(
114
- new THREE.EdgesGeometry(new THREE.IcosahedronGeometry(1.62, 1)),
115
- new THREE.LineBasicMaterial({ color: 0x1a4a46, transparent: true, opacity: 0.14 })
116
  );
117
  root.add(cage);
118
 
119
- // ---- Inference field: instanced particle shell ---------------------------
120
- const COUNT = REDUCED ? 1400 : (MOBILE ? 2600 : 7000);
121
- const pGeo = new THREE.BufferGeometry();
122
- const pos = new Float32Array(COUNT * 3);
123
- const seed = new Float32Array(COUNT); // per-particle phase
124
- const radius = new Float32Array(COUNT); // home radius
125
- const gate = new Float32Array(COUNT); // 1 = passed gate, 0 = denied
126
- for (let i = 0; i < COUNT; i++) {
127
- // even-ish sphere distribution (golden spiral)
128
- const t = i / COUNT;
129
- const phi = Math.acos(1 - 2 * t);
130
- const theta = Math.PI * (1 + Math.sqrt(5)) * i;
131
- const r = 2.4 + Math.random() * 2.6;
132
- pos[i*3+0] = Math.sin(phi) * Math.cos(theta) * r;
133
- pos[i*3+1] = Math.cos(phi) * r;
134
- pos[i*3+2] = Math.sin(phi) * Math.sin(theta) * r;
135
- seed[i] = Math.random() * Math.PI * 2;
136
- radius[i] = r;
137
- gate[i] = Math.random() < 0.62 ? 1.0 : 0.0; // ~Λ share pass the gate
138
  }
139
- pGeo.setAttribute("position", new THREE.BufferAttribute(pos, 3));
140
- pGeo.setAttribute("aSeed", new THREE.BufferAttribute(seed, 1));
141
- pGeo.setAttribute("aRadius", new THREE.BufferAttribute(radius, 1));
142
- pGeo.setAttribute("aGate", new THREE.BufferAttribute(gate, 1));
 
 
 
143
 
144
- const pMat = new THREE.ShaderMaterial({
145
- transparent: true,
146
- blending: THREE.AdditiveBlending,
147
- depthWrite: false,
148
- uniforms: {
149
- uTime: { value: 0 },
150
- uDpr: { value: DPR },
151
- uTeal: { value: new THREE.Vector3(TEAL.r, TEAL.g, TEAL.b) },
152
- uDim: { value: new THREE.Vector3(DIM.r, DIM.g, DIM.b) },
153
- },
154
- vertexShader: /* glsl */`
155
- attribute float aSeed; attribute float aRadius; attribute float aGate;
156
- uniform float uTime; uniform float uDpr;
157
- varying float vGate; varying float vTw;
158
- void main(){
159
- vGate = aGate;
160
- // tokens drift inward then reset — a stream toward the governed core
161
- float flow = fract(uTime*0.06 + aSeed*0.16);
162
- float r = mix(aRadius, 1.75, flow*flow);
163
- vec3 p = normalize(position) * r;
164
- // subtle orbital sway
165
- p.x += sin(uTime*0.5 + aSeed)*0.05;
166
- p.y += cos(uTime*0.4 + aSeed*1.3)*0.05;
167
- vTw = 0.6 + 0.4*sin(uTime*3.0 + aSeed*6.0);
168
- vec4 mv = modelViewMatrix * vec4(p,1.0);
169
- float size = (aGate>0.5 ? 5.5 : 2.6) * uDpr;
170
- gl_PointSize = size * (300.0 / -mv.z);
171
- gl_Position = projectionMatrix * mv;
172
- }`,
173
- fragmentShader: /* glsl */`
174
- precision highp float;
175
- varying float vGate; varying float vTw;
176
- uniform vec3 uTeal; uniform vec3 uDim;
177
- void main(){
178
- vec2 uv = gl_PointCoord - 0.5;
179
- float d = length(uv);
180
- if (d > 0.5) discard;
181
- float a = smoothstep(0.5, 0.0, d);
182
- vec3 col = mix(uDim, uTeal, vGate) * (vGate>0.5 ? vTw : 0.5);
183
- // TAMED: max alpha 0.45 gate-pass, 0.18 denied — subtle field on dark bg
184
- gl_FragColor = vec4(col, a * (vGate>0.5 ? 0.45 : 0.18));
185
- }`,
186
  });
187
- const points = new THREE.Points(pGeo, pMat);
188
- root.add(points);
 
 
 
189
 
190
- // ---- soft additive halo sprite (fake bloom) — very restrained -----------
191
- // TAMED: opacity 0.10 (was 0.55), scale 7×7 (was 9×9) — barely perceptible glow
192
- const halo = new THREE.Sprite(new THREE.SpriteMaterial({
193
- map: makeHaloTexture(), transparent: true, blending: THREE.AdditiveBlending,
194
- depthWrite: false, opacity: 0.10, color: 0x1a6b63,
195
- }));
196
- halo.scale.set(7, 7, 1);
197
- scene.add(halo);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  // ---- resize ---------------------------------------------------------------
200
  function resize() {
@@ -216,29 +267,82 @@ export function mountHero(canvas) {
216
  }, { passive: true });
217
  }
218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  // ---- render loop ----------------------------------------------------------
220
  const clock = new THREE.Clock();
221
  let raf = 0, running = true;
222
 
223
- function frame() {
224
- if (!running) return;
225
- const t = clock.getElapsedTime();
226
  coreMat.uniforms.uTime.value = t;
227
- pMat.uniforms.uTime.value = t;
228
  px += (tx - px) * 0.04; py += (ty - py) * 0.04;
229
- root.rotation.y = t * 0.12 + px * 0.6;
230
- root.rotation.x = py * 0.4;
231
- cage.rotation.y = -t * 0.05;
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  renderer.render(scene, camera);
 
 
 
 
 
233
  raf = requestAnimationFrame(frame);
234
  }
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  if (REDUCED) {
237
- coreMat.uniforms.uTime.value = 1.2;
238
- pMat.uniforms.uTime.value = 1.2;
239
- renderer.render(scene, camera);
240
  } else {
241
- // pause when the hero scrolls out of view (battery + perf)
242
  const io = new IntersectionObserver((ents) => {
243
  for (const en of ents) {
244
  if (en.isIntersecting && !running) { running = true; clock.start(); frame(); }
@@ -247,25 +351,10 @@ export function mountHero(canvas) {
247
  }, { threshold: 0.01 });
248
  io.observe(canvas);
249
  frame();
 
 
 
250
  }
251
 
252
- return { ok: true, renderer, setLambda(v){ coreMat.uniforms.uLambda.value = v; } };
253
- }
254
-
255
- // radial-gradient halo texture, built in-canvas (no asset, no CDN)
256
- // TAMED: core stop 0.18 (was 0.55) — barely-visible glow ring on dark bg
257
- function makeHaloTexture() {
258
- const s = 256;
259
- const c = document.createElement("canvas");
260
- c.width = c.height = s;
261
- const g = c.getContext("2d");
262
- const grad = g.createRadialGradient(s/2, s/2, 0, s/2, s/2, s/2);
263
- grad.addColorStop(0.0, "rgba(26,107,99,0.18)"); // was rgba(57,216,200,0.55)
264
- grad.addColorStop(0.25, "rgba(26,107,99,0.06)"); // was rgba(57,216,200,0.18)
265
- grad.addColorStop(1.0, "rgba(26,107,99,0.0)");
266
- g.fillStyle = grad;
267
- g.fillRect(0, 0, s, s);
268
- const tex = new THREE.CanvasTexture(c);
269
- tex.needsUpdate = true;
270
- return tex;
271
  }
 
1
  /* ============================================================================
2
+ * a11oy_landing.js — the living-proof hero for the a11oy front door (v2).
3
  *
4
+ * THE MOTIF (NVIDIA technical-sublime + True Anomaly operational COP, restrained
5
+ * like Anthropic): a holographic PROOF LATTICE / signed-receipt constellation.
6
+ * It encodes REAL state, not decorative noise:
7
+ * NODES = the recent receipts from GET /api/a11oy/v1/ledger. Each instanced
8
+ * node is one real receipt {seq, action, receipt_id}; the newest
9
+ * (chain head) sits innermost and brightest.
10
+ * • EDGES = the SHA3-256 hash-chain links between consecutive receipts —
11
+ * the append-only ledger drawn as a lattice.
12
+ * • PULSE = a token traveling the chain (genesis → head). When the live lake
13
+ * (GET /api/lake/v1/health.total_receipts) actually GROWS between
14
+ * polls, a brighter pulse fires to the head — a genuinely fresh
15
+ * signed receipt. No growth → only the ambient chain traversal of
16
+ * the receipts that already exist. We never fake a "new signature".
17
+ * • GLOW = the governance core's intensity is tied to the advisory Λ from
18
+ * GET /api/a11oy/v1/lambda/org. Λ is Conjecture 1 (advisory bound),
19
+ * so the glow is a mood, never a pass/fail oracle.
20
  *
21
+ * HONEST DEGRADE: if the ledger is unreachable we do NOT invent receipts or a
22
+ * fake flow. The scene falls back to a calm, still lattice (dim wireframe core +
23
+ * sparse static dust), reports dataState:"unreachable", and emits no pulses.
24
  *
25
+ * SOVEREIGN: Three.js r160 (MIT) only, vendored in-image under /hero/vendor3d
26
+ * (importmap in a11oy_landing.html). 0 runtime CDN. Bloom is faked with additive
27
+ * fresnel + a layered halo sprite (no postprocessing dependency = GPU-light).
28
  *
29
+ * KANCHAY palette: --void #080c14, --proof #3af4c8, --lattice #5b8dee. Calm and
30
+ * deep-near-black; the lattice whispers, it never floods. Purple is banned.
 
31
  *
32
+ * PERF: instanced nodes (one draw call), capped DPR, <13ms/frame target. Honors
33
+ * prefers-reduced-motion (one static frame, no polling) and pauses when the hero
34
+ * scrolls out of view (IntersectionObserver). Live figures are also fetched and
35
+ * labelled by a11oy_landing.html this module owns only the canvas.
 
 
 
 
36
  *
37
+ * Doctrine v11: Λ = Conjecture 1 (advisory, NOT a theorem). locked-proven = 8.
38
+ * Nothing here fabricates a number; every visual quantity is real or honestly off.
39
  * ========================================================================== */
40
  import * as THREE from "three";
41
 
 
44
  const MOBILE = Math.min(window.innerWidth, window.innerHeight) < 720 ||
45
  /Mobi|Android/i.test(navigator.userAgent);
46
 
47
+ // KANCHAY palette, held at restrained brightness so the deep void dominates.
48
+ const VOID = 0x080c14;
49
+ const PROOF = new THREE.Color(0x3af4c8); // --proof : freshest receipt / head
50
+ const LATTICE = new THREE.Color(0x5b8dee); // --lattice: older receipts / chain
51
+ const DUSK = new THREE.Color(0x101a2e); // near-void: ambient dust, denied glow
52
+
53
+ const LEDGER_URL = "/api/a11oy/v1/ledger";
54
+ const LAMBDA_URL = "/api/a11oy/v1/lambda/org";
55
+ const LAKE_URL = "/api/lake/v1/health";
56
+
57
+ // Λ (Conjecture 1) → glow factor. Below the 0.80 advisory band the core is dim;
58
+ // near 1.0 it is at full restrained glow. Never implies a "pass".
59
+ function lambdaGlow(L) {
60
+ if (typeof L !== "number" || !isFinite(L)) return 0.45; // neutral, no claim
61
+ return Math.max(0.25, Math.min(1.0, (L - 0.80) / 0.20));
62
+ }
63
+
64
+ async function getJSON(url, ms = 3500) {
65
+ const ctl = new AbortController();
66
+ const to = setTimeout(() => ctl.abort(), ms);
67
+ try {
68
+ const r = await fetch(url, { cache: "no-store", signal: ctl.signal });
69
+ if (!r.ok) return null;
70
+ return await r.json();
71
+ } catch (_e) {
72
+ return null;
73
+ } finally {
74
+ clearTimeout(to);
75
+ }
76
+ }
77
 
78
  export function mountHero(canvas) {
79
  let renderer;
 
88
 
89
  const DPR = Math.min(window.devicePixelRatio || 1, MOBILE ? 1.5 : 2);
90
  renderer.setPixelRatio(DPR);
91
+ renderer.setClearColor(VOID, 0); // transparent — the page's deep void shows through
92
 
93
  const scene = new THREE.Scene();
94
  const camera = new THREE.PerspectiveCamera(46, 1, 0.1, 100);
95
+ camera.position.set(0, 0, 8.4);
96
 
97
  const root = new THREE.Group();
98
  scene.add(root);
99
 
100
+ // ---- Governance core: fresnel glass icosahedron, glow tied to Λ ------------
 
101
  const coreMat = new THREE.ShaderMaterial({
102
+ transparent: true, blending: THREE.AdditiveBlending, depthWrite: false,
 
 
103
  uniforms: {
104
  uTime: { value: 0 },
105
+ uGlow: { value: 0.45 }, // set from real Λ once fetched
106
+ uProof: { value: new THREE.Vector3(PROOF.r, PROOF.g, PROOF.b) },
107
+ uLattice: { value: new THREE.Vector3(LATTICE.r, LATTICE.g, LATTICE.b) },
108
  },
109
  vertexShader: /* glsl */`
110
+ varying vec3 vN; varying vec3 vView; uniform float uTime;
 
111
  void main(){
112
  vN = normalize(normalMatrix * normal);
113
  vec3 p = position;
 
114
  float w = sin(uTime*0.8 + position.y*2.0)*0.03
115
  + sin(uTime*1.3 + position.x*3.0)*0.02;
116
  p += normal * w;
 
121
  fragmentShader: /* glsl */`
122
  precision highp float;
123
  varying vec3 vN; varying vec3 vView;
124
+ uniform float uTime; uniform float uGlow; uniform vec3 uProof; uniform vec3 uLattice;
 
125
  void main(){
126
  float fres = pow(1.0 - max(dot(normalize(vN), normalize(vView)), 0.0), 2.4);
127
+ float breathe = 0.5 + 0.5*sin(uTime*1.4);
128
+ float glow = fres * (0.22 + 0.16*breathe) * uGlow;
129
+ vec3 col = mix(uLattice, uProof, fres);
130
+ gl_FragColor = vec4(col * glow, clamp(glow, 0.0, 0.42));
 
 
 
131
  }`,
132
  });
133
+ const core = new THREE.Mesh(new THREE.IcosahedronGeometry(1.35, MOBILE ? 2 : 4), coreMat);
134
  root.add(core);
135
 
136
+ // deny-by-default cage — dim hairlines over the core
137
  const cage = new THREE.LineSegments(
138
+ new THREE.EdgesGeometry(new THREE.IcosahedronGeometry(1.42, 1)),
139
+ new THREE.LineBasicMaterial({ color: 0x274063, transparent: true, opacity: 0.16 })
140
  );
141
  root.add(cage);
142
 
143
+ // ---- Ambient dust (pure decoration, clearly NOT data) ----------------------
144
+ // Sparse, very dim, static-ish depth field. Never labelled as receipts.
145
+ const dustN = REDUCED ? 350 : (MOBILE ? 600 : 1100);
146
+ const dpos = new Float32Array(dustN * 3);
147
+ for (let i = 0; i < dustN; i++) {
148
+ const t = i / dustN, phi = Math.acos(1 - 2 * t);
149
+ const theta = Math.PI * (1 + Math.sqrt(5)) * i, r = 4.5 + Math.random() * 4.0;
150
+ dpos[i*3+0] = Math.sin(phi) * Math.cos(theta) * r;
151
+ dpos[i*3+1] = Math.cos(phi) * r;
152
+ dpos[i*3+2] = Math.sin(phi) * Math.sin(theta) * r;
 
 
 
 
 
 
 
 
 
153
  }
154
+ const dustGeo = new THREE.BufferGeometry();
155
+ dustGeo.setAttribute("position", new THREE.BufferAttribute(dpos, 3));
156
+ const dust = new THREE.Points(dustGeo, new THREE.PointsMaterial({
157
+ color: DUSK, size: 0.6 * DPR, sizeAttenuation: false,
158
+ transparent: true, opacity: 0.22, depthWrite: false, blending: THREE.AdditiveBlending,
159
+ }));
160
+ root.add(dust);
161
 
162
+ // ---- Receipt constellation (instanced nodes + chain edges + pulse) ---------
163
+ // Built only from REAL ledger data. Empty until/unless the ledger is reachable.
164
+ const MAX_NODES = 24;
165
+ const nodeGeo = new THREE.SphereGeometry(0.06, 10, 10);
166
+ const nodeMat = new THREE.MeshBasicMaterial({
167
+ transparent: true, opacity: 0.92, blending: THREE.AdditiveBlending, depthWrite: false,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  });
169
+ const nodes = new THREE.InstancedMesh(nodeGeo, nodeMat, MAX_NODES);
170
+ nodes.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
171
+ nodes.count = 0; // nothing claimed until real receipts arrive
172
+ nodes.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(MAX_NODES * 3), 3);
173
+ root.add(nodes);
174
 
175
+ const edgeMat = new THREE.LineBasicMaterial({
176
+ color: 0x5b8dee, transparent: true, opacity: 0.0, // raised once edges exist
177
+ blending: THREE.AdditiveBlending, depthWrite: false,
178
+ });
179
+ const edges = new THREE.Line(new THREE.BufferGeometry(), edgeMat);
180
+ root.add(edges);
181
+
182
+ const pulse = new THREE.Mesh(
183
+ new THREE.SphereGeometry(0.11, 12, 12),
184
+ new THREE.MeshBasicMaterial({ color: PROOF, transparent: true, opacity: 0.0,
185
+ blending: THREE.AdditiveBlending, depthWrite: false })
186
+ );
187
+ root.add(pulse);
188
+
189
+ const _m = new THREE.Matrix4();
190
+ const _v = new THREE.Vector3();
191
+ let chainCurve = null; // CatmullRom through node positions (genesis→head)
192
+ let nodePositions = []; // Vector3[]
193
+ let pulseT = 0; // 0..1 along the chain
194
+ let pulseBoost = 0; // brief extra brightness on a genuinely fresh receipt
195
+ let receiptCount = 0; // honest count actually rendered
196
+
197
+ // Lay out N receipts on a gentle helix around the core: genesis outer/low,
198
+ // head inner/high (append-only growth climbs toward the governed core).
199
+ function layout(n) {
200
+ nodePositions = [];
201
+ for (let i = 0; i < n; i++) {
202
+ const f = n > 1 ? i / (n - 1) : 0; // 0 genesis → 1 head
203
+ const ang = f * Math.PI * 4.0; // two turns of the helix
204
+ const rad = 3.4 - f * 1.4; // spirals inward to the core
205
+ const y = (f - 0.5) * 3.6; // climbs upward
206
+ nodePositions.push(new THREE.Vector3(Math.cos(ang) * rad, y, Math.sin(ang) * rad));
207
+ }
208
+ chainCurve = nodePositions.length > 1
209
+ ? new THREE.CatmullRomCurve3(nodePositions, false, "catmullrom", 0.4) : null;
210
+ }
211
+
212
+ // Apply REAL receipts to the constellation. `list` = [{seq,action,receipt_id}].
213
+ function applyReceipts(list) {
214
+ const n = Math.min(MAX_NODES, list.length);
215
+ receiptCount = n;
216
+ layout(n);
217
+ for (let i = 0; i < n; i++) {
218
+ const f = n > 1 ? i / (n - 1) : 1;
219
+ _m.makeScale(0.7 + f * 0.9, 0.7 + f * 0.9, 0.7 + f * 0.9); // head a touch larger
220
+ _m.setPosition(nodePositions[i]);
221
+ nodes.setMatrixAt(i, _m);
222
+ const c = LATTICE.clone().lerp(PROOF, f); // older=lattice blue, head=proof teal
223
+ nodes.setColorAt(i, c);
224
+ }
225
+ nodes.count = n;
226
+ nodes.instanceMatrix.needsUpdate = true;
227
+ if (nodes.instanceColor) nodes.instanceColor.needsUpdate = true;
228
+
229
+ if (n > 1) {
230
+ const pts = chainCurve.getPoints(Math.max(32, n * 4));
231
+ edges.geometry.dispose();
232
+ edges.geometry = new THREE.BufferGeometry().setFromPoints(pts);
233
+ edgeMat.opacity = 0.20;
234
+ pulse.material.opacity = 0.0; // animated in the frame loop
235
+ } else {
236
+ edgeMat.opacity = 0.0;
237
+ }
238
+ }
239
+
240
+ // Calm, still, honestly-empty state — no receipts invented, no flow faked.
241
+ function showUnreachable() {
242
+ receiptCount = 0;
243
+ nodes.count = 0;
244
+ edgeMat.opacity = 0.0;
245
+ pulse.material.opacity = 0.0;
246
+ chainCurve = null;
247
+ cage.material.opacity = 0.22; // the lattice cage carries the quiet, honest mood
248
+ }
249
 
250
  // ---- resize ---------------------------------------------------------------
251
  function resize() {
 
267
  }, { passive: true });
268
  }
269
 
270
+ const hero = {
271
+ ok: true, renderer, dataState: "loading", receiptCount: 0, lambda: null,
272
+ setLambda(v) {
273
+ hero.lambda = (typeof v === "number") ? v : null;
274
+ coreMat.uniforms.uGlow.value = lambdaGlow(v);
275
+ },
276
+ setReceipts(list) {
277
+ if (Array.isArray(list) && list.length) {
278
+ applyReceipts(list);
279
+ hero.dataState = "live"; hero.receiptCount = receiptCount;
280
+ } else {
281
+ showUnreachable(); hero.dataState = "unreachable"; hero.receiptCount = 0;
282
+ }
283
+ },
284
+ fireFreshReceipt() { pulseT = 0; pulseBoost = 1.0; }, // genuine new signed receipt
285
+ };
286
+
287
  // ---- render loop ----------------------------------------------------------
288
  const clock = new THREE.Clock();
289
  let raf = 0, running = true;
290
 
291
+ function render(t) {
 
 
292
  coreMat.uniforms.uTime.value = t;
 
293
  px += (tx - px) * 0.04; py += (ty - py) * 0.04;
294
+ root.rotation.y = t * 0.10 + px * 0.6;
295
+ root.rotation.x = py * 0.35;
296
+ cage.rotation.y = -t * 0.04;
297
+ dust.rotation.y = t * 0.015;
298
+
299
+ // signed-receipt pulse traverses the real chain (only when we have one)
300
+ if (chainCurve) {
301
+ pulseT += 0.0016 + 0.004 * pulseBoost; // fresh receipts travel faster + brighter
302
+ if (pulseT >= 1) { pulseT = 0; pulseBoost = 0; }
303
+ chainCurve.getPointAt(pulseT, _v);
304
+ pulse.position.copy(_v);
305
+ const base = 0.45 + 0.25 * Math.sin(t * 3.0);
306
+ pulse.material.opacity = Math.min(1.0, base + pulseBoost * 0.5);
307
+ const s = 1.0 + pulseBoost * 0.8;
308
+ pulse.scale.setScalar(s);
309
+ }
310
  renderer.render(scene, camera);
311
+ }
312
+
313
+ function frame() {
314
+ if (!running) return;
315
+ render(clock.getElapsedTime());
316
  raf = requestAnimationFrame(frame);
317
  }
318
 
319
+ // ---- live data: fetch real receipts + Λ; poll lake for genuine growth ------
320
+ async function boot() {
321
+ const [ledger, lam] = await Promise.all([getJSON(LEDGER_URL), getJSON(LAMBDA_URL)]);
322
+ if (lam && typeof lam.lambda_org === "number") hero.setLambda(lam.lambda_org);
323
+ if (ledger && Array.isArray(ledger.receipts) && ledger.receipts.length) {
324
+ hero.setReceipts(ledger.receipts);
325
+ } else {
326
+ hero.setReceipts(null); // honest still state — no fabricated receipts
327
+ }
328
+ }
329
+
330
+ let lastLakeTotal = null;
331
+ async function pollLake() {
332
+ const h = await getJSON(LAKE_URL);
333
+ const total = h && typeof h.total_receipts === "number" ? h.total_receipts : null;
334
+ if (total !== null && lastLakeTotal !== null && total > lastLakeTotal) {
335
+ hero.fireFreshReceipt(); // a real new receipt landed in the lake
336
+ }
337
+ if (total !== null) lastLakeTotal = total;
338
+ }
339
+
340
  if (REDUCED) {
341
+ // Static, dignified single frame. Still fetch real data (no animation/polling).
342
+ boot().then(() => render(1.2));
343
+ render(1.2);
344
  } else {
345
+ boot();
346
  const io = new IntersectionObserver((ents) => {
347
  for (const en of ents) {
348
  if (en.isIntersecting && !running) { running = true; clock.start(); frame(); }
 
351
  }, { threshold: 0.01 });
352
  io.observe(canvas);
353
  frame();
354
+ // poll the lake every 15s for genuine receipt growth (honest fresh pulse)
355
+ const poll = setInterval(() => { if (running) pollLake(); }, 15000);
356
+ hero._stopPoll = () => clearInterval(poll);
357
  }
358
 
359
+ return hero;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  }