a11oy / static /a11oy_landing.js
betterwithage's picture
deploy(hf): sync szl-holdings/a11oy@main derived COPY set
dcf704e verified
Raw
History Blame
15.3 kB
/* ============================================================================
* 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;
}