betterwithage commited on
Commit
453b484
·
verified ·
1 Parent(s): c4e0d28

feat(landing): deploy holographic Series-A front door

Browse files

Holographic governed-inference hero (vendored Three.js r160 MIT), live ledger counter + WebCrypto verify + mesh viz; spa_root prefers a11oy_landing.html. Signed-off-by: Stephen Lutar <stephenlutar2@gmail.com>

Files changed (2) hide show
  1. a11oy_landing.html +536 -0
  2. static/a11oy_landing.js +254 -0
a11oy_landing.html ADDED
@@ -0,0 +1,536 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <!--
3
+ a11oy — front door (holographic landing). Served at the Space root "/" by serve.py.
4
+ SOVEREIGN: Three.js r160 (MIT) vendored in-image at /hero/vendor3d. 0 runtime CDN.
5
+ HONESTY DOCTRINE v11 LOCKED — every figure on this page is fetched live and labelled
6
+ honestly (LIVE / DOWN / empty-state). Nothing is fabricated. Λ = Conjecture 1 (advisory,
7
+ NOT a theorem). locked-proven kernel = EXACTLY 8. Receipts are signed on write, never on GET.
8
+ © Stephen P. Lutar Jr. (ORCID 0009-0001-0110-4173) · Apache-2.0
9
+ -->
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="utf-8" />
13
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
14
+ <title>a11oy — governed inference, signed and shown</title>
15
+ <meta name="description" content="a11oy governs every AI turn before it runs and seals it as a signed receipt you can verify offline. They hide the governor; we sign and show it." />
16
+ <link rel="icon" href="/social-preview.svg" />
17
+ <style>
18
+ :root{
19
+ --bg:#070b16; --deep:#0a0f1e; --surface:#10161f; --border:#22304a;
20
+ --teal:#39d8c8; --teal-soft:#39d8c833; --violet:#6a7bff; --gold:#cda64a;
21
+ --ink:#eaf1f7; --sub:#9fb2c9; --ghost:#64768f;
22
+ --ok:#39d8c8; --warn:#cda64a; --down:#e06a6a;
23
+ --r:16px; --maxw:1180px;
24
+ --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
25
+ --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
26
+ }
27
+ *{box-sizing:border-box}
28
+ html,body{margin:0;padding:0}
29
+ body{
30
+ background:
31
+ radial-gradient(1200px 700px at 70% -10%, #14213a 0%, transparent 60%),
32
+ radial-gradient(900px 600px at 10% 10%, #0d1830 0%, transparent 55%),
33
+ var(--bg);
34
+ color:var(--ink); font-family:var(--sans); line-height:1.55;
35
+ -webkit-font-smoothing:antialiased; overflow-x:hidden;
36
+ }
37
+ a{color:inherit;text-decoration:none}
38
+ .wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px}
39
+ .mono{font-family:var(--mono)}
40
+
41
+ /* ---- nav ---- */
42
+ header.nav{position:sticky;top:0;z-index:40;
43
+ backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
44
+ background:linear-gradient(180deg,#070b16cc,#070b1688);
45
+ border-bottom:1px solid var(--border)}
46
+ .nav .wrap{display:flex;align-items:center;gap:18px;height:64px}
47
+ .brand{display:flex;align-items:center;gap:11px;font-weight:700;letter-spacing:.2px}
48
+ .brand svg{display:block}
49
+ .brand .sub{color:var(--ghost);font-weight:500;font-size:12px;letter-spacing:2px;text-transform:uppercase}
50
+ .nav nav{margin-left:auto;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
51
+ .nav nav a{padding:8px 13px;border-radius:10px;color:var(--sub);font-size:14px;font-weight:500;transition:.15s}
52
+ .nav nav a:hover{color:var(--ink);background:#ffffff0e}
53
+ .btn{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;border-radius:11px;
54
+ font-weight:600;font-size:14px;border:1px solid transparent;cursor:pointer;transition:.15s;white-space:nowrap}
55
+ .btn-primary{background:linear-gradient(135deg,var(--teal),#2bb6a8);color:#04130f;
56
+ box-shadow:0 6px 24px -8px var(--teal)}
57
+ .btn-primary:hover{transform:translateY(-1px);box-shadow:0 10px 30px -8px var(--teal)}
58
+ .btn-ghost{border-color:var(--border);color:var(--ink);background:#ffffff08}
59
+ .btn-ghost:hover{border-color:var(--teal);color:var(--teal)}
60
+
61
+ /* ---- hero ---- */
62
+ .hero{position:relative;min-height:92vh;display:flex;align-items:center;overflow:hidden}
63
+ #hero-canvas{position:absolute;inset:0;width:100%;height:100%;z-index:0;display:block}
64
+ .hero-fallback{position:absolute;inset:0;z-index:0;display:none;
65
+ background:radial-gradient(600px 600px at 65% 45%, #39d8c81f, transparent 70%)}
66
+ .hero .wrap{position:relative;z-index:2;padding-top:48px;padding-bottom:48px}
67
+ .hero-inner{max-width:680px}
68
+ .eyebrow{display:inline-flex;align-items:center;gap:9px;padding:6px 13px;border-radius:999px;
69
+ border:1px solid var(--teal-soft);background:#39d8c812;color:var(--teal);
70
+ font-size:12.5px;font-weight:600;letter-spacing:.4px;margin-bottom:22px}
71
+ .eyebrow .dot{width:7px;height:7px;border-radius:50%;background:var(--teal);
72
+ box-shadow:0 0 0 0 var(--teal);animation:pulse 2.4s infinite}
73
+ @keyframes pulse{0%{box-shadow:0 0 0 0 #39d8c8aa}70%{box-shadow:0 0 0 9px #39d8c800}100%{box-shadow:0 0 0 0 #39d8c800}}
74
+ h1.title{font-size:clamp(2.3rem,5.6vw,4rem);line-height:1.04;margin:0 0 18px;
75
+ font-weight:760;letter-spacing:-1.4px}
76
+ h1.title .grad{background:linear-gradient(120deg,var(--teal),var(--violet));
77
+ -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
78
+ .lede{font-size:clamp(1.05rem,2vw,1.3rem);color:var(--sub);max-width:600px;margin:0 0 28px}
79
+ .lede b{color:var(--ink);font-weight:600}
80
+ .cta-row{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:30px}
81
+ .hero-stats{display:flex;gap:14px;flex-wrap:wrap}
82
+ .stat{padding:13px 17px;border-radius:13px;border:1px solid var(--border);
83
+ background:linear-gradient(180deg,#ffffff0c,#ffffff04);min-width:150px;backdrop-filter:blur(6px)}
84
+ .stat .k{font-size:1.55rem;font-weight:720;font-family:var(--mono);line-height:1.1}
85
+ .stat .l{font-size:11.5px;color:var(--ghost);text-transform:uppercase;letter-spacing:1.1px;margin-top:3px}
86
+ .stat .k.teal{color:var(--teal)}
87
+
88
+ /* ---- generic section ---- */
89
+ section.band{padding:72px 0;position:relative}
90
+ .band h2{font-size:clamp(1.6rem,3.2vw,2.3rem);font-weight:720;letter-spacing:-.6px;margin:0 0 12px}
91
+ .band .kick{color:var(--teal);font-weight:600;font-size:13px;letter-spacing:1.6px;text-transform:uppercase;margin:0 0 10px}
92
+ .band p.intro{color:var(--sub);max-width:680px;font-size:1.05rem;margin:0 0 36px}
93
+
94
+ .grid{display:grid;gap:18px}
95
+ .g3{grid-template-columns:repeat(3,1fr)}
96
+ .g2{grid-template-columns:repeat(2,1fr)}
97
+ @media(max-width:900px){.g3,.g2{grid-template-columns:1fr}}
98
+
99
+ .card{padding:24px;border-radius:var(--r);border:1px solid var(--border);
100
+ background:linear-gradient(180deg,#ffffff0b,#ffffff05);position:relative;overflow:hidden}
101
+ .card::before{content:"";position:absolute;inset:0;border-radius:var(--r);padding:1px;
102
+ background:linear-gradient(135deg,#39d8c855,transparent 40%);
103
+ -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
104
+ -webkit-mask-composite:xor;mask-composite:exclude;opacity:.6;pointer-events:none}
105
+ .card h3{margin:0 0 8px;font-size:1.12rem;font-weight:680}
106
+ .card p{margin:0;color:var(--sub);font-size:.96rem}
107
+ .card .ic{width:38px;height:38px;border-radius:10px;display:grid;place-items:center;
108
+ background:#39d8c815;border:1px solid var(--teal-soft);color:var(--teal);margin-bottom:14px;font-size:18px}
109
+
110
+ /* ---- labels / chips ---- */
111
+ .chip{display:inline-flex;align-items:center;gap:7px;padding:5px 11px;border-radius:999px;
112
+ font-size:12px;font-weight:600;font-family:var(--mono);border:1px solid var(--border)}
113
+ .chip .dot{width:8px;height:8px;border-radius:50%}
114
+ .chip.live{color:var(--ok);border-color:#39d8c855;background:#39d8c810}
115
+ .chip.live .dot{background:var(--ok);box-shadow:0 0 8px var(--ok)}
116
+ .chip.down{color:var(--down);border-color:#e06a6a55;background:#e06a6a10}
117
+ .chip.down .dot{background:var(--down)}
118
+ .chip.wait{color:var(--ghost)}
119
+ .chip.wait .dot{background:var(--ghost)}
120
+ .chip.gold{color:var(--gold);border-color:#cda64a55;background:#cda64a10}
121
+ .chip.gold .dot{background:var(--gold)}
122
+
123
+ /* ---- verify panel ---- */
124
+ .verify{display:grid;grid-template-columns:1.05fr .95fr;gap:22px;align-items:start}
125
+ @media(max-width:900px){.verify{grid-template-columns:1fr}}
126
+ .term{border-radius:14px;border:1px solid var(--border);background:#060a13;overflow:hidden;font-family:var(--mono)}
127
+ .term .bar{display:flex;align-items:center;gap:7px;padding:10px 14px;border-bottom:1px solid var(--border);background:#0b1120}
128
+ .term .bar i{width:11px;height:11px;border-radius:50%;display:block}
129
+ .term .bar i:nth-child(1){background:#e06a6a}.term .bar i:nth-child(2){background:#cda64a}.term .bar i:nth-child(3){background:#39d8c8}
130
+ .term .bar .t{margin-left:8px;color:var(--ghost);font-size:12px}
131
+ .term .body{padding:16px;font-size:12.5px;line-height:1.7;max-height:330px;overflow:auto;white-space:pre-wrap;word-break:break-word}
132
+ .term .body .ln{color:var(--sub)}
133
+ .term .body .ok{color:var(--ok)}
134
+ .term .body .er{color:var(--down)}
135
+ .term .body .mut{color:var(--ghost)}
136
+ .term .body .hl{color:var(--teal)}
137
+ .verdict{margin-top:14px;padding:14px 16px;border-radius:12px;border:1px solid var(--border);font-weight:600;display:none}
138
+ .verdict.show{display:block}
139
+ .verdict.pass{border-color:#39d8c8;background:#39d8c810;color:var(--ok)}
140
+ .verdict.fail{border-color:#e06a6a;background:#e06a6a10;color:var(--down)}
141
+
142
+ /* ---- mesh ---- */
143
+ .mesh-list{display:flex;flex-direction:column;gap:12px}
144
+ .node{display:flex;align-items:center;gap:14px;padding:16px 18px;border-radius:13px;
145
+ border:1px solid var(--border);background:linear-gradient(180deg,#ffffff0a,#ffffff04)}
146
+ .node .nm{font-weight:640}
147
+ .node .meta{color:var(--ghost);font-size:12.5px;font-family:var(--mono);margin-top:2px}
148
+ .node .right{margin-left:auto;text-align:right}
149
+
150
+ /* ---- footer ---- */
151
+ footer{border-top:1px solid var(--border);padding:46px 0 60px;color:var(--ghost);font-size:13.5px}
152
+ footer .cols{display:flex;gap:40px;flex-wrap:wrap;justify-content:space-between}
153
+ footer a{color:var(--sub)}footer a:hover{color:var(--teal)}
154
+ footer .lk{display:flex;flex-direction:column;gap:8px}
155
+ .disc{margin-top:32px;padding-top:22px;border-top:1px solid var(--border);max-width:760px;line-height:1.6}
156
+
157
+ .muted{color:var(--ghost)}
158
+ .skel{color:var(--ghost);font-family:var(--mono)}
159
+ </style>
160
+ </head>
161
+ <body>
162
+
163
+ <!-- ============================ NAV ============================ -->
164
+ <header class="nav">
165
+ <div class="wrap">
166
+ <a class="brand" href="/" aria-label="a11oy home">
167
+ <svg width="30" height="30" viewBox="0 0 32 32" fill="none" aria-hidden="true">
168
+ <defs><linearGradient id="lg" x1="0" y1="0" x2="1" y2="1">
169
+ <stop offset="0" stop-color="#39d8c8"/><stop offset="1" stop-color="#6a7bff"/></linearGradient></defs>
170
+ <circle cx="16" cy="16" r="13" stroke="url(#lg)" stroke-width="2"/>
171
+ <circle cx="16" cy="16" r="5" fill="url(#lg)"/>
172
+ <path d="M16 3v8M16 21v8M3 16h8M21 16h8" stroke="url(#lg)" stroke-width="1.6" opacity=".7"/>
173
+ </svg>
174
+ <span>a11oy<span class="sub" style="margin-left:9px">SZL Holdings</span></span>
175
+ </a>
176
+ <nav>
177
+ <a href="/console">Console</a>
178
+ <a href="/verify">Verify</a>
179
+ <a href="/assurance">Assurance</a>
180
+ <a href="/willay">WILLAY</a>
181
+ <a href="/governance">Governance</a>
182
+ <a class="btn btn-primary" href="/console">Open Console →</a>
183
+ </nav>
184
+ </div>
185
+ </header>
186
+
187
+ <!-- ============================ HERO ============================ -->
188
+ <section class="hero">
189
+ <canvas id="hero-canvas"></canvas>
190
+ <div class="hero-fallback" id="hero-fallback"></div>
191
+ <div class="wrap">
192
+ <div class="hero-inner">
193
+ <span class="eyebrow"><span class="dot"></span> Governed inference · signed receipts · verify offline</span>
194
+ <h1 class="title">Every AI turn,<br><span class="grad">governed and signed.</span></h1>
195
+ <p class="lede">a11oy gates every model call against a constitution <b>before it runs</b>,
196
+ routes it on a sovereign mesh, and seals the decision as a signed receipt you can verify
197
+ yourself. <b>They hide the governor. We sign and show it.</b></p>
198
+ <div class="cta-row">
199
+ <a class="btn btn-primary" href="/console">Open the Console →</a>
200
+ <a class="btn btn-ghost" href="#verify">Verify a receipt offline</a>
201
+ </div>
202
+ <div class="hero-stats">
203
+ <div class="stat">
204
+ <div class="k teal" id="hs-receipts">—</div>
205
+ <div class="l">Signed receipts <span id="hs-receipts-chip"></span></div>
206
+ </div>
207
+ <div class="stat">
208
+ <div class="k" id="hs-lambda">0.97</div>
209
+ <div class="l">Λ trust ceiling · advisory</div>
210
+ </div>
211
+ <div class="stat">
212
+ <div class="k" id="hs-proven">8</div>
213
+ <div class="l">Locked-proven formulas</div>
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </section>
219
+
220
+ <!-- ====================== HOW IT WORKS ====================== -->
221
+ <section class="band wrap" id="how">
222
+ <p class="kick">The governed loop</p>
223
+ <h2>Gate before it runs. Sign what it did.</h2>
224
+ <p class="intro">Most AI stacks bolt safety on after the answer. a11oy puts a deny-by-default
225
+ governor in front of every action and a signed, hash-chained receipt behind it — so a buyer,
226
+ an auditor, or a regulator can replay the decision instead of trusting a screenshot.</p>
227
+ <div class="grid g3">
228
+ <div class="card"><div class="ic">⛓</div><h3>Gate</h3>
229
+ <p>Each turn clears the constitution, the doctrine gate, and the policy guards before a
230
+ single token is generated. No clearance, no run.</p></div>
231
+ <div class="card"><div class="ic">⚖</div><h3>Route</h3>
232
+ <p>Smart Routing picks the smallest sovereign engine that clears the sensitivity class and
233
+ license tier — self-hosted first, never leaking sensitive context.</p></div>
234
+ <div class="card"><div class="ic">🔏</div><h3>Sign</h3>
235
+ <p>The decision is sealed as a DSSE receipt, ECDSA&#8209;P256 signed and hash-chained
236
+ (SHA3&#8209;256). Signed on write, never on a read.</p></div>
237
+ </div>
238
+ </section>
239
+
240
+ <!-- ====================== VERIFY OFFLINE ====================== -->
241
+ <section class="band" id="verify" style="background:linear-gradient(180deg,#0a0f1e00,#0a0f1e88)">
242
+ <div class="wrap">
243
+ <p class="kick">The trust demo</p>
244
+ <h2>Verify a live signature in your own browser.</h2>
245
+ <p class="intro">This makes a real governed call, fetches a11oy's public key, and checks the
246
+ ECDSA&#8209;P256 signature over the DSSE payload using your browser's WebCrypto — no server
247
+ round-trip for the verification, nothing to take on faith. Tamper with one byte and it fails.</p>
248
+ <div class="verify">
249
+ <div>
250
+ <div class="term">
251
+ <div class="bar"><i></i><i></i><i></i><span class="t">webcrypto · verify offline</span></div>
252
+ <div class="body" id="vlog"><span class="mut">// click “Run verification” to make a governed call and check its signature locally.</span></div>
253
+ </div>
254
+ <div class="verdict" id="vverdict"></div>
255
+ <div style="margin-top:14px;display:flex;gap:12px;flex-wrap:wrap">
256
+ <button class="btn btn-primary" id="vrun">Run verification</button>
257
+ <a class="btn btn-ghost" href="/verify">Open the full Verify surface →</a>
258
+ </div>
259
+ </div>
260
+ <div class="grid" style="gap:14px">
261
+ <div class="card"><h3>What gets checked</h3>
262
+ <p style="margin-bottom:10px">1 · <span class="mono">POST /api/a11oy/v1/govern/infer</span> — a real governed turn.<br>
263
+ 2 · <span class="mono">GET /cosign.pub</span> — the in-image public key (PEM).<br>
264
+ 3 · Rebuild the DSSE PAE, recompute SHA&#8209;256, run <span class="mono">crypto.subtle.verify</span>.</p>
265
+ <p class="muted">DER→raw r‖s conversion happens in-browser; the private key never leaves the box.</p>
266
+ </div>
267
+ <div class="card"><h3>Why it matters</h3>
268
+ <p>A signature anyone can check, against a key they fetched themselves, is the difference
269
+ between “trust us” and proof. The same logic backs every receipt in the ledger.</p>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </section>
275
+
276
+ <!-- ====================== SOVEREIGN MESH ====================== -->
277
+ <section class="band wrap" id="mesh">
278
+ <p class="kick">Sovereign compute mesh</p>
279
+ <h2>Real nodes. Honest status. No fake green lights.</h2>
280
+ <p class="intro">Inference runs on a governed mesh of sovereign engines. The board below is live
281
+ from <span class="mono">/api/a11oy/v1/govern/health</span>. A node that is offline is shown
282
+ offline — we never paint a down engine green.</p>
283
+ <div class="mesh-list" id="mesh-list">
284
+ <div class="node"><div><div class="nm skel">Loading mesh…</div>
285
+ <div class="meta">querying /api/a11oy/v1/govern/health</div></div>
286
+ <div class="right"><span class="chip wait"><span class="dot"></span>…</span></div></div>
287
+ </div>
288
+ </section>
289
+
290
+ <!-- ====================== ASSURANCE / NARRATIVE ====================== -->
291
+ <section class="band" style="background:linear-gradient(180deg,#0a0f1e00,#0d18308c)">
292
+ <div class="wrap">
293
+ <p class="kick">Built for buyers and auditors</p>
294
+ <h2>An assurance overlay for AI you can defend in a review.</h2>
295
+ <div class="grid g3" style="margin-top:30px">
296
+ <div class="card"><div class="ic">📐</div><h3>Honest by construction</h3>
297
+ <p>Capabilities are labelled LIVE / MEASURED / SAMPLE / MODELED / ROADMAP. The trust
298
+ ceiling Λ is an advisory bound (Conjecture 1) — never asserted as a theorem, never 100%.</p></div>
299
+ <div class="card"><div class="ic">🧾</div><h3>Replayable evidence</h3>
300
+ <p>Every governed decision carries a signed receipt and a hash-chain link. An auditor
301
+ replays the chain to a byte-identical root instead of trusting a dashboard.</p></div>
302
+ <div class="card"><div class="ic">🛡</div><h3>Deny by default</h3>
303
+ <p>New action paths must clear governance before they execute. A truthful BLOCKED is the
304
+ designed outcome — better than a fabricated pass.</p></div>
305
+ </div>
306
+ <div style="margin-top:34px;display:flex;gap:12px;flex-wrap:wrap">
307
+ <a class="btn btn-primary" href="/assurance">See the assurance surface →</a>
308
+ <a class="btn btn-ghost" href="/willay">Explore WILLAY</a>
309
+ <a class="btn btn-ghost" href="/governance">Governance model</a>
310
+ </div>
311
+ </div>
312
+ </section>
313
+
314
+ <!-- ============================ FOOTER ============================ -->
315
+ <footer>
316
+ <div class="wrap">
317
+ <div class="cols">
318
+ <div style="max-width:320px">
319
+ <a class="brand" href="/" style="margin-bottom:12px">
320
+ <svg width="26" height="26" viewBox="0 0 32 32" fill="none" aria-hidden="true">
321
+ <circle cx="16" cy="16" r="13" stroke="url(#lg)" stroke-width="2"/>
322
+ <circle cx="16" cy="16" r="5" fill="url(#lg)"/></svg>
323
+ <span>a11oy</span></a>
324
+ <p class="muted" style="margin:6px 0 0">The governed agentic substrate of SZL Holdings.
325
+ Governed inference, signed receipts, sovereign compute.</p>
326
+ </div>
327
+ <div class="lk"><strong style="color:var(--ink)">Product</strong>
328
+ <a href="/console">Console</a><a href="/verify">Verify</a>
329
+ <a href="/assurance">Assurance</a><a href="/willay">WILLAY</a></div>
330
+ <div class="lk"><strong style="color:var(--ink)">Platform</strong>
331
+ <a href="/governance">Governance</a><a href="/frontier">Frontier</a>
332
+ <a href="/orbital">Orbital</a><a href="/api/a11oy/v1/honest">Honest status (JSON)</a></div>
333
+ </div>
334
+ <p class="disc">Honesty doctrine v11. Λ is Conjecture 1 — an advisory trust ceiling, not a
335
+ theorem, never 100%. The locked-proven formula kernel is exactly 8 (of ~35 wired, ~185 in the
336
+ corpus). Receipts are signed on state changes, never on reads. Joules are MEASURED only with a
337
+ live exporter delta; otherwise shown as SAMPLE. © Stephen P. Lutar Jr. · Apache-2.0.</p>
338
+ </div>
339
+ </footer>
340
+
341
+ <!-- ===================== SOVEREIGN IMPORTMAP (vendored, NO CDN) ===================== -->
342
+ <script type="importmap">
343
+ { "imports": {
344
+ "three": "/hero/vendor3d/three.module.min.js",
345
+ "three/addons/": "/hero/vendor3d/"
346
+ } }
347
+ </script>
348
+
349
+ <!-- ===================== HERO BOOT (Three.js holographic field) ===================== -->
350
+ <script type="module">
351
+ import { mountHero } from "/landing/app.js";
352
+ const canvas = document.getElementById("hero-canvas");
353
+ try {
354
+ const h = mountHero(canvas);
355
+ if (!h || !h.ok) {
356
+ canvas.style.display = "none";
357
+ document.getElementById("hero-fallback").style.display = "block";
358
+ } else {
359
+ window.__a11oyHero = h;
360
+ }
361
+ } catch (e) {
362
+ canvas.style.display = "none";
363
+ document.getElementById("hero-fallback").style.display = "block";
364
+ }
365
+ </script>
366
+
367
+ <!-- ===================== LIVE DATA + WEBCRYPTO VERIFY (no deps) ===================== -->
368
+ <script>
369
+ (function(){
370
+ "use strict";
371
+ const $ = (id) => document.getElementById(id);
372
+ const esc = (s) => String(s).replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
373
+
374
+ /* ---------- 1) live receipt counter + chain head ---------- */
375
+ (async function loadLake(){
376
+ try{
377
+ const r = await fetch("/api/lake/v1/health", {cache:"no-store"});
378
+ const d = await r.json();
379
+ const total = (typeof d.total_receipts === "number") ? d.total_receipts : null;
380
+ $("hs-receipts").textContent = (total === null) ? "—" : total.toLocaleString();
381
+ const alg = d.chain_alg || "sha3_256";
382
+ $("hs-receipts-chip").innerHTML =
383
+ '<span class="chip live" style="margin-left:6px"><span class="dot"></span>' + esc(alg) + '</span>';
384
+ }catch(e){
385
+ $("hs-receipts").textContent = "—";
386
+ $("hs-receipts-chip").innerHTML =
387
+ '<span class="chip down" style="margin-left:6px"><span class="dot"></span>offline</span>';
388
+ }
389
+ })();
390
+
391
+ /* ---------- 3) sovereign mesh viz ---------- */
392
+ (async function loadMesh(){
393
+ const host = $("mesh-list");
394
+ try{
395
+ const r = await fetch("/api/a11oy/v1/govern/health", {cache:"no-store"});
396
+ const d = await r.json();
397
+ const mesh = Array.isArray(d.mesh) ? d.mesh : [];
398
+ const rows = [];
399
+ mesh.forEach(n => {
400
+ const live = !!n.live;
401
+ const cls = live ? "live" : "down";
402
+ const lbl = live ? "LIVE" : "DOWN";
403
+ const tier = n.tier ? (" · " + esc(n.tier)) : "";
404
+ rows.push(
405
+ '<div class="node"><div>'+
406
+ '<div class="nm">'+esc(n.name || "engine")+'</div>'+
407
+ '<div class="meta">'+esc(n.model || "—")+tier+(n.effort?(" · "+esc(n.effort)):"")+'</div>'+
408
+ '</div><div class="right">'+
409
+ '<span class="chip '+cls+'"><span class="dot"></span>'+lbl+'</span>'+
410
+ '</div></div>'
411
+ );
412
+ });
413
+ // GLM engine: honest-DOWN when not on metal
414
+ if (d.glm && d.glm.on_metal_live === false){
415
+ rows.push(
416
+ '<div class="node"><div>'+
417
+ '<div class="nm">GLM engine</div>'+
418
+ '<div class="meta">'+esc(d.glm.model_tag || "glm")+' · not in mesh</div>'+
419
+ '</div><div class="right">'+
420
+ '<span class="chip down"><span class="dot"></span>DOWN</span>'+
421
+ '</div></div>'
422
+ );
423
+ }
424
+ const liveN = (typeof d.engines_live === "number") ? d.engines_live : mesh.filter(n=>n.live).length;
425
+ const totN = (typeof d.engines_total === "number") ? d.engines_total : mesh.length;
426
+ rows.push(
427
+ '<div class="node" style="border-style:dashed"><div>'+
428
+ '<div class="nm muted">Mesh summary</div>'+
429
+ '<div class="meta">'+liveN+' / '+totN+' engines live · governance '+(d.governance?'on':'off')+'</div>'+
430
+ '</div><div class="right"><span class="chip '+(liveN>0?'live':'down')+'"><span class="dot"></span>'+
431
+ (liveN>0?'LIVE':'NONE')+'</span></div></div>'
432
+ );
433
+ host.innerHTML = rows.join("") ||
434
+ '<div class="node"><div class="nm muted">No engines reported</div></div>';
435
+ }catch(e){
436
+ host.innerHTML =
437
+ '<div class="node"><div><div class="nm">Mesh health unreachable</div>'+
438
+ '<div class="meta">/api/a11oy/v1/govern/health did not respond</div></div>'+
439
+ '<div class="right"><span class="chip down"><span class="dot"></span>OFFLINE</span></div></div>';
440
+ }
441
+ })();
442
+
443
+ /* ---------- 2) WebCrypto ECDSA-P256 DSSE verify (Tier-1 logic) ---------- */
444
+ function b64Decode(s){const bin=atob(s);const b=new Uint8Array(bin.length);for(let i=0;i<bin.length;i++)b[i]=bin.charCodeAt(i);return b;}
445
+ function derToRaw(der){
446
+ let i=0; if(der[i++]!==0x30) throw new Error("not DER SEQUENCE");
447
+ let sl=der[i++]; if(sl&0x80){let nb=sl&0x7f;sl=0;for(let b=0;b<nb;b++)sl=(sl<<8)|der[i++];}
448
+ function readInt(){if(der[i++]!==0x02)throw new Error("expected INTEGER");let len=der[i++];
449
+ if(len&0x80){let nb=len&0x7f;len=0;for(let b=0;b<nb;b++)len=(len<<8)|der[i++];}
450
+ const v=der.slice(i,i+len);i+=len;return v;}
451
+ const r=readInt(),s=readInt();
452
+ function pad32(a){let j=0;while(j<a.length-1&&a[j]===0)j++;const st=a.slice(j);
453
+ const o=new Uint8Array(32);const off=32-st.length;if(off<0)throw new Error("int too long");o.set(st,off);return o;}
454
+ const raw=new Uint8Array(64);raw.set(pad32(r),0);raw.set(pad32(s),32);return raw;
455
+ }
456
+ function buildPAE(type,body){const enc=new TextEncoder();const t=enc.encode(type);
457
+ const pre=enc.encode("DSSEv1 "+t.length+" ");const mid=enc.encode(" "+body.length+" ");
458
+ const buf=new Uint8Array(pre.length+t.length+mid.length+body.length);let o=0;
459
+ buf.set(pre,o);o+=pre.length;buf.set(t,o);o+=t.length;buf.set(mid,o);o+=mid.length;buf.set(body,o);return buf;}
460
+ async function sha256hex(buf){const h=await crypto.subtle.digest("SHA-256",buf);
461
+ return Array.from(new Uint8Array(h)).map(b=>b.toString(16).padStart(2,"0")).join("");}
462
+ async function importPub(pem){const b64=pem.replace(/-----BEGIN PUBLIC KEY-----/,"").replace(/-----END PUBLIC KEY-----/,"").replace(/\s+/g,"");
463
+ return crypto.subtle.importKey("spki", b64Decode(b64).buffer, {name:"ECDSA",namedCurve:"P-256"}, false, ["verify"]);}
464
+
465
+ let busy=false;
466
+ function log(html){const b=$("vlog");b.innerHTML+= "\n"+html;b.scrollTop=b.scrollHeight;}
467
+ function reset(){$("vlog").innerHTML='<span class="mut">// running…</span>';
468
+ const v=$("vverdict");v.className="verdict";v.textContent="";}
469
+
470
+ async function runVerify(){
471
+ if(busy) return; busy=true;
472
+ const btn=$("vrun"); btn.disabled=true; btn.textContent="Verifying…";
473
+ reset();
474
+ try{
475
+ log('<span class="ln">→ POST /api/a11oy/v1/govern/infer</span>');
476
+ const resp=await fetch("/api/a11oy/v1/govern/infer",{method:"POST",
477
+ headers:{"Content-Type":"application/json"},
478
+ body:JSON.stringify({prompt:"hello",vertical:"general",declared:"PUBLIC"})});
479
+ const data=await resp.json();
480
+ const dsse=data.dsse||{}, receipt=data.receipt||{};
481
+ log('<span class="ok"> decision: '+esc(data.decision||"?")+'</span>'+
482
+ ' <span class="mut">Λ='+esc((data.governance&&data.governance.lambda)||"?")+' (Conjecture 1, advisory)</span>');
483
+
484
+ log('<span class="ln">→ GET /cosign.pub</span>');
485
+ const pem=await (await fetch("/cosign.pub")).text();
486
+ const pub=await importPub(pem);
487
+ log('<span class="mut"> public key imported (ECDSA P-256, SPKI)</span>');
488
+
489
+ if(!dsse.signed){
490
+ log('<span class="er"> envelope is UNSIGNED in this runtime — cannot verify.</span>');
491
+ verdict(false,"Envelope unsigned in this runtime. Receipt chain still valid; signature unavailable.");
492
+ return;
493
+ }
494
+ const payloadType=dsse.payloadType||"application/vnd.szl.khipu+json";
495
+ const payload=b64Decode(dsse.payload||"");
496
+ log('<span class="mut"> payload SHA-256: '+(await sha256hex(payload.buffer)).slice(0,32)+'…</span>');
497
+
498
+ const pae=buildPAE(payloadType,payload);
499
+ const paeSha=await sha256hex(pae.buffer);
500
+ log('<span class="ln">→ rebuild DSSE PAE · SHA-256 '+paeSha.slice(0,24)+'…</span>');
501
+
502
+ const sigs=dsse.signatures||[];
503
+ if(!sigs.length){verdict(false,"No signatures in envelope.");return;}
504
+ let sig=b64Decode(sigs[0].sig||""), raw=sig;
505
+ if(sig[0]===0x30){ raw=derToRaw(sig); log('<span class="mut"> DER→raw r‖s converted in-browser</span>'); }
506
+ log('<span class="ln">→ crypto.subtle.verify(ECDSA, SHA-256)</span>');
507
+ let ok=await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},pub,raw.buffer,pae.buffer);
508
+ if(!ok && sig[0]===0x30){ ok=await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},pub,sig.buffer,pae.buffer); }
509
+
510
+ if(ok){
511
+ log('<span class="ok">✓ signature VALID — verified locally in your browser.</span>');
512
+ const kid=(sigs[0].keyid||"").slice(0,16);
513
+ verdict(true,"VERIFIED — WebCrypto ECDSA-P256 signature valid over the DSSE PAE."+
514
+ (kid?(" keyid "+esc(kid)+"…"):"")+" Nothing trusted; everything checked.");
515
+ }else{
516
+ log('<span class="er">✗ verification returned false.</span>');
517
+ verdict(false,"Signature did NOT verify against the fetched key.");
518
+ }
519
+ }catch(e){
520
+ log('<span class="er">! '+esc(e.message||e)+'</span>');
521
+ verdict(false,"Verification could not complete: "+esc(e.message||e));
522
+ }finally{
523
+ busy=false; btn.disabled=false; btn.textContent="Run again";
524
+ }
525
+ }
526
+ function verdict(pass,msg){const v=$("vverdict");
527
+ v.className="verdict show "+(pass?"pass":"fail");
528
+ v.textContent=(pass?"✓ ":"✗ ")+msg;}
529
+
530
+ document.addEventListener("DOMContentLoaded",()=>{
531
+ const b=$("vrun"); if(b) b.addEventListener("click",runVerify);
532
+ });
533
+ })();
534
+ </script>
535
+ </body>
536
+ </html>
static/a11oy_landing.js ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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. Signed-off-by: Stephen P. Lutar Jr.
21
+ * ========================================================================== */
22
+ import * as THREE from "three";
23
+
24
+ const REDUCED = window.matchMedia &&
25
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
26
+ const MOBILE = Math.min(window.innerWidth, window.innerHeight) < 720 ||
27
+ /Mobi|Android/i.test(navigator.userAgent);
28
+
29
+ const TEAL = new THREE.Color(0x39d8c8);
30
+ const DEEP = new THREE.Color(0x6a7bff);
31
+ const DIM = new THREE.Color(0x1b2740);
32
+
33
+ export function mountHero(canvas) {
34
+ let renderer;
35
+ try {
36
+ renderer = new THREE.WebGLRenderer({
37
+ canvas, antialias: !MOBILE, alpha: true, powerPreference: "high-performance",
38
+ });
39
+ } catch (e) {
40
+ return { ok: false, reason: String(e) };
41
+ }
42
+ if (!renderer.getContext()) return { ok: false, reason: "no-webgl" };
43
+
44
+ const DPR = Math.min(window.devicePixelRatio || 1, MOBILE ? 1.5 : 2);
45
+ renderer.setPixelRatio(DPR);
46
+ renderer.setClearColor(0x000000, 0);
47
+
48
+ const scene = new THREE.Scene();
49
+ const camera = new THREE.PerspectiveCamera(46, 1, 0.1, 100);
50
+ camera.position.set(0, 0, 7.2);
51
+
52
+ const root = new THREE.Group();
53
+ scene.add(root);
54
+
55
+ // ---- Governance core: fresnel-shaded glass icosahedron --------------------
56
+ const coreGeo = new THREE.IcosahedronGeometry(1.55, MOBILE ? 2 : 4);
57
+ const coreMat = new THREE.ShaderMaterial({
58
+ transparent: true,
59
+ blending: THREE.AdditiveBlending,
60
+ depthWrite: false,
61
+ uniforms: {
62
+ uTime: { value: 0 },
63
+ uLambda: { value: 0.97 }, // advisory Λ ceiling (Conjecture 1)
64
+ uTeal: { value: new THREE.Vector3(TEAL.r, TEAL.g, TEAL.b) },
65
+ uDeep: { value: new THREE.Vector3(DEEP.r, DEEP.g, DEEP.b) },
66
+ },
67
+ vertexShader: /* glsl */`
68
+ varying vec3 vN; varying vec3 vView;
69
+ uniform float uTime;
70
+ void main(){
71
+ vN = normalize(normalMatrix * normal);
72
+ vec3 p = position;
73
+ // gentle breathing displacement along the normal
74
+ float w = sin(uTime*0.8 + position.y*2.0)*0.03
75
+ + sin(uTime*1.3 + position.x*3.0)*0.02;
76
+ p += normal * w;
77
+ vec4 mv = modelViewMatrix * vec4(p,1.0);
78
+ vView = normalize(-mv.xyz);
79
+ gl_Position = projectionMatrix * mv;
80
+ }`,
81
+ fragmentShader: /* glsl */`
82
+ precision highp float;
83
+ varying vec3 vN; varying vec3 vView;
84
+ uniform float uTime; uniform float uLambda;
85
+ uniform vec3 uTeal; uniform vec3 uDeep;
86
+ void main(){
87
+ float fres = pow(1.0 - max(dot(normalize(vN), normalize(vView)), 0.0), 2.4);
88
+ // Λ-pulse: the shell brightens toward the advisory ceiling, never to 1.0
89
+ float pulse = 0.5 + 0.5*sin(uTime*1.6);
90
+ float glow = fres * (0.55 + 0.45*pulse) * uLambda;
91
+ vec3 col = mix(uDeep, uTeal, fres);
92
+ gl_FragColor = vec4(col * glow, clamp(glow,0.0,0.92));
93
+ }`,
94
+ });
95
+ const core = new THREE.Mesh(coreGeo, coreMat);
96
+ root.add(core);
97
+
98
+ // wire lattice over the core (deny-by-default cage)
99
+ const cage = new THREE.LineSegments(
100
+ new THREE.EdgesGeometry(new THREE.IcosahedronGeometry(1.62, 1)),
101
+ new THREE.LineBasicMaterial({ color: 0x39d8c8, transparent: true, opacity: 0.18 })
102
+ );
103
+ root.add(cage);
104
+
105
+ // ---- Inference field: instanced particle shell ---------------------------
106
+ const COUNT = REDUCED ? 1400 : (MOBILE ? 2600 : 7000);
107
+ const pGeo = new THREE.BufferGeometry();
108
+ const pos = new Float32Array(COUNT * 3);
109
+ const seed = new Float32Array(COUNT); // per-particle phase
110
+ const radius = new Float32Array(COUNT); // home radius
111
+ const gate = new Float32Array(COUNT); // 1 = passed gate, 0 = denied
112
+ for (let i = 0; i < COUNT; i++) {
113
+ // even-ish sphere distribution (golden spiral)
114
+ const t = i / COUNT;
115
+ const phi = Math.acos(1 - 2 * t);
116
+ const theta = Math.PI * (1 + Math.sqrt(5)) * i;
117
+ const r = 2.4 + Math.random() * 2.6;
118
+ pos[i*3+0] = Math.sin(phi) * Math.cos(theta) * r;
119
+ pos[i*3+1] = Math.cos(phi) * r;
120
+ pos[i*3+2] = Math.sin(phi) * Math.sin(theta) * r;
121
+ seed[i] = Math.random() * Math.PI * 2;
122
+ radius[i] = r;
123
+ gate[i] = Math.random() < 0.62 ? 1.0 : 0.0; // ~Λ share pass the gate
124
+ }
125
+ pGeo.setAttribute("position", new THREE.BufferAttribute(pos, 3));
126
+ pGeo.setAttribute("aSeed", new THREE.BufferAttribute(seed, 1));
127
+ pGeo.setAttribute("aRadius", new THREE.BufferAttribute(radius, 1));
128
+ pGeo.setAttribute("aGate", new THREE.BufferAttribute(gate, 1));
129
+
130
+ const pMat = new THREE.ShaderMaterial({
131
+ transparent: true,
132
+ blending: THREE.AdditiveBlending,
133
+ depthWrite: false,
134
+ uniforms: {
135
+ uTime: { value: 0 },
136
+ uDpr: { value: DPR },
137
+ uTeal: { value: new THREE.Vector3(TEAL.r, TEAL.g, TEAL.b) },
138
+ uDim: { value: new THREE.Vector3(DIM.r, DIM.g, DIM.b) },
139
+ },
140
+ vertexShader: /* glsl */`
141
+ attribute float aSeed; attribute float aRadius; attribute float aGate;
142
+ uniform float uTime; uniform float uDpr;
143
+ varying float vGate; varying float vTw;
144
+ void main(){
145
+ vGate = aGate;
146
+ // tokens drift inward then reset — a stream toward the governed core
147
+ float flow = fract(uTime*0.06 + aSeed*0.16);
148
+ float r = mix(aRadius, 1.75, flow*flow);
149
+ vec3 p = normalize(position) * r;
150
+ // subtle orbital sway
151
+ p.x += sin(uTime*0.5 + aSeed)*0.05;
152
+ p.y += cos(uTime*0.4 + aSeed*1.3)*0.05;
153
+ vTw = 0.6 + 0.4*sin(uTime*3.0 + aSeed*6.0);
154
+ vec4 mv = modelViewMatrix * vec4(p,1.0);
155
+ float size = (aGate>0.5 ? 5.5 : 2.6) * uDpr;
156
+ gl_PointSize = size * (300.0 / -mv.z);
157
+ gl_Position = projectionMatrix * mv;
158
+ }`,
159
+ fragmentShader: /* glsl */`
160
+ precision highp float;
161
+ varying float vGate; varying float vTw;
162
+ uniform vec3 uTeal; uniform vec3 uDim;
163
+ void main(){
164
+ vec2 uv = gl_PointCoord - 0.5;
165
+ float d = length(uv);
166
+ if (d > 0.5) discard;
167
+ float a = smoothstep(0.5, 0.0, d);
168
+ vec3 col = mix(uDim, uTeal, vGate) * (vGate>0.5 ? vTw : 0.5);
169
+ gl_FragColor = vec4(col, a * (vGate>0.5 ? 0.95 : 0.4));
170
+ }`,
171
+ });
172
+ const points = new THREE.Points(pGeo, pMat);
173
+ root.add(points);
174
+
175
+ // ---- soft additive halo sprite (fake bloom, no postprocessing) -----------
176
+ const halo = new THREE.Sprite(new THREE.SpriteMaterial({
177
+ map: makeHaloTexture(), transparent: true, blending: THREE.AdditiveBlending,
178
+ depthWrite: false, opacity: 0.55, color: 0x39d8c8,
179
+ }));
180
+ halo.scale.set(9, 9, 1);
181
+ scene.add(halo);
182
+
183
+ // ---- resize ---------------------------------------------------------------
184
+ function resize() {
185
+ const r = canvas.getBoundingClientRect();
186
+ const w = Math.max(1, r.width), h = Math.max(1, r.height);
187
+ renderer.setSize(w, h, false);
188
+ camera.aspect = w / h;
189
+ camera.updateProjectionMatrix();
190
+ }
191
+ resize();
192
+ window.addEventListener("resize", resize, { passive: true });
193
+
194
+ // ---- pointer parallax -----------------------------------------------------
195
+ let px = 0, py = 0, tx = 0, ty = 0;
196
+ if (!REDUCED) {
197
+ window.addEventListener("pointermove", (e) => {
198
+ tx = (e.clientX / window.innerWidth - 0.5);
199
+ ty = (e.clientY / window.innerHeight - 0.5);
200
+ }, { passive: true });
201
+ }
202
+
203
+ // ---- render loop ----------------------------------------------------------
204
+ const clock = new THREE.Clock();
205
+ let raf = 0, running = true;
206
+
207
+ function frame() {
208
+ if (!running) return;
209
+ const t = clock.getElapsedTime();
210
+ coreMat.uniforms.uTime.value = t;
211
+ pMat.uniforms.uTime.value = t;
212
+ px += (tx - px) * 0.04; py += (ty - py) * 0.04;
213
+ root.rotation.y = t * 0.12 + px * 0.6;
214
+ root.rotation.x = py * 0.4;
215
+ cage.rotation.y = -t * 0.05;
216
+ renderer.render(scene, camera);
217
+ raf = requestAnimationFrame(frame);
218
+ }
219
+
220
+ if (REDUCED) {
221
+ coreMat.uniforms.uTime.value = 1.2;
222
+ pMat.uniforms.uTime.value = 1.2;
223
+ renderer.render(scene, camera);
224
+ } else {
225
+ // pause when the hero scrolls out of view (battery + perf)
226
+ const io = new IntersectionObserver((ents) => {
227
+ for (const en of ents) {
228
+ if (en.isIntersecting && !running) { running = true; clock.start(); frame(); }
229
+ else if (!en.isIntersecting && running) { running = false; cancelAnimationFrame(raf); }
230
+ }
231
+ }, { threshold: 0.01 });
232
+ io.observe(canvas);
233
+ frame();
234
+ }
235
+
236
+ return { ok: true, renderer, setLambda(v){ coreMat.uniforms.uLambda.value = v; } };
237
+ }
238
+
239
+ // radial-gradient halo texture, built in-canvas (no asset, no CDN)
240
+ function makeHaloTexture() {
241
+ const s = 256;
242
+ const c = document.createElement("canvas");
243
+ c.width = c.height = s;
244
+ const g = c.getContext("2d");
245
+ const grad = g.createRadialGradient(s/2, s/2, 0, s/2, s/2, s/2);
246
+ grad.addColorStop(0.0, "rgba(57,216,200,0.55)");
247
+ grad.addColorStop(0.25, "rgba(57,216,200,0.18)");
248
+ grad.addColorStop(1.0, "rgba(57,216,200,0.0)");
249
+ g.fillStyle = grad;
250
+ g.fillRect(0, 0, s, s);
251
+ const tex = new THREE.CanvasTexture(c);
252
+ tex.needsUpdate = true;
253
+ return tex;
254
+ }