/* ============================================================================ * a11oy_landing.js — the living-proof hero for the a11oy front door (v2). * * THE MOTIF (NVIDIA technical-sublime + True Anomaly operational COP, restrained * like Anthropic): a holographic PROOF LATTICE / signed-receipt constellation. * It encodes REAL state, not decorative noise: * • NODES = the recent receipts from GET /api/a11oy/v1/ledger. Each instanced * node is one real receipt {seq, action, receipt_id}; the newest * (chain head) sits innermost and brightest. * • EDGES = the SHA3-256 hash-chain links between consecutive receipts — * the append-only ledger drawn as a lattice. * • PULSE = a token traveling the chain (genesis → head). When the live lake * (GET /api/lake/v1/health.total_receipts) actually GROWS between * polls, a brighter pulse fires to the head — a genuinely fresh * signed receipt. No growth → only the ambient chain traversal of * the receipts that already exist. We never fake a "new signature". * • GLOW = the governance core's intensity is tied to the advisory Λ from * GET /api/a11oy/v1/lambda/org. Λ is Conjecture 1 (advisory bound), * so the glow is a mood, never a pass/fail oracle. * * HONEST DEGRADE: if the ledger is unreachable we do NOT invent receipts or a * fake flow. The scene falls back to a calm, still lattice (dim wireframe core + * sparse static dust), reports dataState:"unreachable", and emits no pulses. * * SOVEREIGN: Three.js r160 (MIT) only, vendored in-image under /hero/vendor3d * (importmap in a11oy_landing.html). 0 runtime CDN. Bloom is faked with additive * fresnel + a layered halo sprite (no postprocessing dependency = GPU-light). * * KANCHAY palette: --void #080c14, --proof #3af4c8, --lattice #5b8dee. Calm and * deep-near-black; the lattice whispers, it never floods. Purple is banned. * * PERF: instanced nodes (one draw call), capped DPR, <13ms/frame target. Honors * prefers-reduced-motion (one static frame, no polling) and pauses when the hero * scrolls out of view (IntersectionObserver). Live figures are also fetched and * labelled by a11oy_landing.html — this module owns only the canvas. * * Doctrine v11: Λ = Conjecture 1 (advisory, NOT a theorem). locked-proven = 8. * Nothing here fabricates a number; every visual quantity is real or honestly off. * ========================================================================== */ import * as THREE from "three"; const REDUCED = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; const MOBILE = Math.min(window.innerWidth, window.innerHeight) < 720 || /Mobi|Android/i.test(navigator.userAgent); // KANCHAY palette, held at restrained brightness so the deep void dominates. const VOID = 0x080c14; const PROOF = new THREE.Color(0x3af4c8); // --proof : freshest receipt / head const LATTICE = new THREE.Color(0x5b8dee); // --lattice: older receipts / chain const DUSK = new THREE.Color(0x101a2e); // near-void: ambient dust, denied glow const LEDGER_URL = "/api/a11oy/v1/ledger"; const LAMBDA_URL = "/api/a11oy/v1/lambda/org"; const LAKE_URL = "/api/lake/v1/health"; // Λ (Conjecture 1) → glow factor. Below the 0.80 advisory band the core is dim; // near 1.0 it is at full restrained glow. Never implies a "pass". function lambdaGlow(L) { if (typeof L !== "number" || !isFinite(L)) return 0.45; // neutral, no claim return Math.max(0.25, Math.min(1.0, (L - 0.80) / 0.20)); } async function getJSON(url, ms = 3500) { const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), ms); try { const r = await fetch(url, { cache: "no-store", signal: ctl.signal }); if (!r.ok) return null; return await r.json(); } catch (_e) { return null; } finally { clearTimeout(to); } } export function mountHero(canvas) { let renderer; try { renderer = new THREE.WebGLRenderer({ canvas, antialias: !MOBILE, alpha: true, powerPreference: "high-performance", }); } catch (e) { return { ok: false, reason: String(e) }; } if (!renderer.getContext()) return { ok: false, reason: "no-webgl" }; const DPR = Math.min(window.devicePixelRatio || 1, MOBILE ? 1.5 : 2); renderer.setPixelRatio(DPR); renderer.setClearColor(VOID, 0); // transparent — the page's deep void shows through const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(46, 1, 0.1, 100); camera.position.set(0, 0, 8.4); const root = new THREE.Group(); scene.add(root); // ---- Governance core: fresnel glass icosahedron, glow tied to Λ ------------ const coreMat = new THREE.ShaderMaterial({ transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, uniforms: { uTime: { value: 0 }, uGlow: { value: 0.45 }, // set from real Λ once fetched uProof: { value: new THREE.Vector3(PROOF.r, PROOF.g, PROOF.b) }, uLattice: { value: new THREE.Vector3(LATTICE.r, LATTICE.g, LATTICE.b) }, }, vertexShader: /* glsl */` varying vec3 vN; varying vec3 vView; uniform float uTime; void main(){ vN = normalize(normalMatrix * normal); vec3 p = position; float w = sin(uTime*0.8 + position.y*2.0)*0.03 + sin(uTime*1.3 + position.x*3.0)*0.02; p += normal * w; vec4 mv = modelViewMatrix * vec4(p,1.0); vView = normalize(-mv.xyz); gl_Position = projectionMatrix * mv; }`, fragmentShader: /* glsl */` precision highp float; varying vec3 vN; varying vec3 vView; uniform float uTime; uniform float uGlow; uniform vec3 uProof; uniform vec3 uLattice; void main(){ float fres = pow(1.0 - max(dot(normalize(vN), normalize(vView)), 0.0), 2.4); float breathe = 0.5 + 0.5*sin(uTime*1.4); float glow = fres * (0.22 + 0.16*breathe) * uGlow; vec3 col = mix(uLattice, uProof, fres); gl_FragColor = vec4(col * glow, clamp(glow, 0.0, 0.42)); }`, }); const core = new THREE.Mesh(new THREE.IcosahedronGeometry(1.35, MOBILE ? 2 : 4), coreMat); root.add(core); // deny-by-default cage — dim hairlines over the core const cage = new THREE.LineSegments( new THREE.EdgesGeometry(new THREE.IcosahedronGeometry(1.42, 1)), new THREE.LineBasicMaterial({ color: 0x274063, transparent: true, opacity: 0.16 }) ); root.add(cage); // ---- Ambient dust (pure decoration, clearly NOT data) ---------------------- // Sparse, very dim, static-ish depth field. Never labelled as receipts. const dustN = REDUCED ? 350 : (MOBILE ? 600 : 1100); const dpos = new Float32Array(dustN * 3); for (let i = 0; i < dustN; i++) { const t = i / dustN, phi = Math.acos(1 - 2 * t); const theta = Math.PI * (1 + Math.sqrt(5)) * i, r = 4.5 + Math.random() * 4.0; dpos[i*3+0] = Math.sin(phi) * Math.cos(theta) * r; dpos[i*3+1] = Math.cos(phi) * r; dpos[i*3+2] = Math.sin(phi) * Math.sin(theta) * r; } const dustGeo = new THREE.BufferGeometry(); dustGeo.setAttribute("position", new THREE.BufferAttribute(dpos, 3)); const dust = new THREE.Points(dustGeo, new THREE.PointsMaterial({ color: DUSK, size: 0.6 * DPR, sizeAttenuation: false, transparent: true, opacity: 0.22, depthWrite: false, blending: THREE.AdditiveBlending, })); root.add(dust); // ---- Receipt constellation (instanced nodes + chain edges + pulse) --------- // Built only from REAL ledger data. Empty until/unless the ledger is reachable. const MAX_NODES = 24; const nodeGeo = new THREE.SphereGeometry(0.06, 10, 10); const nodeMat = new THREE.MeshBasicMaterial({ transparent: true, opacity: 0.92, blending: THREE.AdditiveBlending, depthWrite: false, }); const nodes = new THREE.InstancedMesh(nodeGeo, nodeMat, MAX_NODES); nodes.instanceMatrix.setUsage(THREE.DynamicDrawUsage); nodes.count = 0; // nothing claimed until real receipts arrive nodes.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(MAX_NODES * 3), 3); root.add(nodes); const edgeMat = new THREE.LineBasicMaterial({ color: 0x5b8dee, transparent: true, opacity: 0.0, // raised once edges exist blending: THREE.AdditiveBlending, depthWrite: false, }); const edges = new THREE.Line(new THREE.BufferGeometry(), edgeMat); root.add(edges); const pulse = new THREE.Mesh( new THREE.SphereGeometry(0.11, 12, 12), new THREE.MeshBasicMaterial({ color: PROOF, transparent: true, opacity: 0.0, blending: THREE.AdditiveBlending, depthWrite: false }) ); root.add(pulse); const _m = new THREE.Matrix4(); const _v = new THREE.Vector3(); let chainCurve = null; // CatmullRom through node positions (genesis→head) let nodePositions = []; // Vector3[] let pulseT = 0; // 0..1 along the chain let pulseBoost = 0; // brief extra brightness on a genuinely fresh receipt let receiptCount = 0; // honest count actually rendered // Lay out N receipts on a gentle helix around the core: genesis outer/low, // head inner/high (append-only growth climbs toward the governed core). function layout(n) { nodePositions = []; for (let i = 0; i < n; i++) { const f = n > 1 ? i / (n - 1) : 0; // 0 genesis → 1 head const ang = f * Math.PI * 4.0; // two turns of the helix const rad = 3.4 - f * 1.4; // spirals inward to the core const y = (f - 0.5) * 3.6; // climbs upward nodePositions.push(new THREE.Vector3(Math.cos(ang) * rad, y, Math.sin(ang) * rad)); } chainCurve = nodePositions.length > 1 ? new THREE.CatmullRomCurve3(nodePositions, false, "catmullrom", 0.4) : null; } // Apply REAL receipts to the constellation. `list` = [{seq,action,receipt_id}]. function applyReceipts(list) { const n = Math.min(MAX_NODES, list.length); receiptCount = n; layout(n); for (let i = 0; i < n; i++) { const f = n > 1 ? i / (n - 1) : 1; _m.makeScale(0.7 + f * 0.9, 0.7 + f * 0.9, 0.7 + f * 0.9); // head a touch larger _m.setPosition(nodePositions[i]); nodes.setMatrixAt(i, _m); const c = LATTICE.clone().lerp(PROOF, f); // older=lattice blue, head=proof teal nodes.setColorAt(i, c); } nodes.count = n; nodes.instanceMatrix.needsUpdate = true; if (nodes.instanceColor) nodes.instanceColor.needsUpdate = true; if (n > 1) { const pts = chainCurve.getPoints(Math.max(32, n * 4)); edges.geometry.dispose(); edges.geometry = new THREE.BufferGeometry().setFromPoints(pts); edgeMat.opacity = 0.20; pulse.material.opacity = 0.0; // animated in the frame loop } else { edgeMat.opacity = 0.0; } } // Calm, still, honestly-empty state — no receipts invented, no flow faked. function showUnreachable() { receiptCount = 0; nodes.count = 0; edgeMat.opacity = 0.0; pulse.material.opacity = 0.0; chainCurve = null; cage.material.opacity = 0.22; // the lattice cage carries the quiet, honest mood } // ---- resize --------------------------------------------------------------- function resize() { const r = canvas.getBoundingClientRect(); const w = Math.max(1, r.width), h = Math.max(1, r.height); renderer.setSize(w, h, false); camera.aspect = w / h; camera.updateProjectionMatrix(); } resize(); window.addEventListener("resize", resize, { passive: true }); // ---- pointer parallax ----------------------------------------------------- let px = 0, py = 0, tx = 0, ty = 0; if (!REDUCED) { window.addEventListener("pointermove", (e) => { tx = (e.clientX / window.innerWidth - 0.5); ty = (e.clientY / window.innerHeight - 0.5); }, { passive: true }); } const hero = { ok: true, renderer, dataState: "loading", receiptCount: 0, lambda: null, setLambda(v) { hero.lambda = (typeof v === "number") ? v : null; coreMat.uniforms.uGlow.value = lambdaGlow(v); }, setReceipts(list) { if (Array.isArray(list) && list.length) { applyReceipts(list); hero.dataState = "live"; hero.receiptCount = receiptCount; } else { showUnreachable(); hero.dataState = "unreachable"; hero.receiptCount = 0; } }, fireFreshReceipt() { pulseT = 0; pulseBoost = 1.0; }, // genuine new signed receipt }; // ---- render loop ---------------------------------------------------------- const clock = new THREE.Clock(); let raf = 0, running = true; function render(t) { coreMat.uniforms.uTime.value = t; px += (tx - px) * 0.04; py += (ty - py) * 0.04; root.rotation.y = t * 0.10 + px * 0.6; root.rotation.x = py * 0.35; cage.rotation.y = -t * 0.04; dust.rotation.y = t * 0.015; // signed-receipt pulse traverses the real chain (only when we have one) if (chainCurve) { pulseT += 0.0016 + 0.004 * pulseBoost; // fresh receipts travel faster + brighter if (pulseT >= 1) { pulseT = 0; pulseBoost = 0; } chainCurve.getPointAt(pulseT, _v); pulse.position.copy(_v); const base = 0.45 + 0.25 * Math.sin(t * 3.0); pulse.material.opacity = Math.min(1.0, base + pulseBoost * 0.5); const s = 1.0 + pulseBoost * 0.8; pulse.scale.setScalar(s); } renderer.render(scene, camera); } function frame() { if (!running) return; render(clock.getElapsedTime()); raf = requestAnimationFrame(frame); } // ---- live data: fetch real receipts + Λ; poll lake for genuine growth ------ async function boot() { const [ledger, lam] = await Promise.all([getJSON(LEDGER_URL), getJSON(LAMBDA_URL)]); if (lam && typeof lam.lambda_org === "number") hero.setLambda(lam.lambda_org); if (ledger && Array.isArray(ledger.receipts) && ledger.receipts.length) { hero.setReceipts(ledger.receipts); } else { hero.setReceipts(null); // honest still state — no fabricated receipts } } let lastLakeTotal = null; async function pollLake() { const h = await getJSON(LAKE_URL); const total = h && typeof h.total_receipts === "number" ? h.total_receipts : null; if (total !== null && lastLakeTotal !== null && total > lastLakeTotal) { hero.fireFreshReceipt(); // a real new receipt landed in the lake } if (total !== null) lastLakeTotal = total; } if (REDUCED) { // Static, dignified single frame. Still fetch real data (no animation/polling). boot().then(() => render(1.2)); render(1.2); } else { boot(); const io = new IntersectionObserver((ents) => { for (const en of ents) { if (en.isIntersecting && !running) { running = true; clock.start(); frame(); } else if (!en.isIntersecting && running) { running = false; cancelAnimationFrame(raf); } } }, { threshold: 0.01 }); io.observe(canvas); frame(); // poll the lake every 15s for genuine receipt growth (honest fresh pulse) const poll = setInterval(() => { if (running) pollLake(); }, 15000); hero._stopPoll = () => clearInterval(poll); } return hero; }