Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>a11oy — Mission Surfaces · SZL Holdings</title> | |
| <meta name="description" content="Five working a11oy mission surfaces: AI Oversight, Deploy Posture, Mission Health, Trajectory Picture, and Edge Run. Each runs live in-image and produces a real, signed, re-verifiable result."/> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"/> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/> | |
| <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/> | |
| <style> | |
| /* a11oy house style — dark-constellation + gold/teal ribbon (matches /superpowers) */ | |
| :root{ | |
| --ground:#0a0a0a; --panel:#0c0c0c; --panel2:#070707; | |
| --gold:#c9b787; --gold-bright:#d6c69a; | |
| --teal:#5fb3a3; --teal-soft:rgba(95,179,163,0.10); | |
| --cream:#f5f5f5; --paragraph:#9a9a9a; --muted:#888; --dim:#555; | |
| --gold-line:rgba(201,183,135,0.15); --gold-soft:rgba(201,183,135,0.04); | |
| --teal-line:rgba(95,179,163,0.22); | |
| --live:#5a8a6e; --err:#b06a5a; | |
| --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,monospace; | |
| --display:'Space Grotesk',Georgia,serif; | |
| } | |
| *{box-sizing:border-box;} | |
| html,body{margin:0;padding:0;background:var(--ground);color:var(--cream); | |
| font-family:var(--display);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;} | |
| .mono{font-family:var(--mono);} | |
| a{color:inherit;} | |
| :focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:3px;} | |
| .ribbon{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:1.25rem; | |
| flex-wrap:wrap;padding:0.5rem 1.25rem;font-family:var(--mono);font-size:10px; | |
| letter-spacing:0.12em;text-transform:uppercase;color:var(--gold); | |
| background:rgba(10,10,10,0.85);backdrop-filter:blur(10px); | |
| border-bottom:1px solid var(--gold-line);} | |
| .ribbon .sep{color:var(--dim);} | |
| .ribbon .teal{color:var(--teal);} | |
| .ribbon .live{display:inline-flex;align-items:center;gap:0.4rem;color:var(--cream);} | |
| .ribbon a{margin-left:auto;color:var(--muted);text-decoration:none;} | |
| .ribbon a:hover{color:var(--gold);} | |
| .live-dot{width:6px;height:6px;border-radius:50%;background:var(--live); | |
| box-shadow:0 0 6px var(--live);animation:pulseDot 2.2s ease-in-out infinite;} | |
| @keyframes pulseDot{0%,100%{opacity:1;}50%{opacity:.35;}} | |
| .hero{position:relative;overflow:hidden;padding:3.5rem 1.5rem 2.5rem;text-align:center;} | |
| .grid-bg{position:absolute;inset:0;pointer-events:none; | |
| background-image: | |
| linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px), | |
| linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px); | |
| background-size:56px 56px; | |
| -webkit-mask-image:radial-gradient(ellipse at center,#000 30%,transparent 75%); | |
| mask-image:radial-gradient(ellipse at center,#000 30%,transparent 75%);} | |
| .hero-inner{position:relative;z-index:2;max-width:64rem;margin:0 auto;} | |
| .eyebrow{display:inline-flex;align-items:center;gap:.5rem;padding:.3rem .85rem; | |
| border-radius:999px;border:1px solid var(--gold-line);background:var(--gold-soft);margin-bottom:1.75rem;} | |
| .eyebrow-dot{width:6px;height:6px;border-radius:50%;background:var(--gold); | |
| animation:pulseDot 2.2s ease-in-out infinite;} | |
| .eyebrow-text{font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);} | |
| .headline{font-size:clamp(2.4rem,5.5vw,4.4rem);font-weight:300;line-height:1.03; | |
| letter-spacing:-.035em;margin:0 0 1.25rem;color:var(--cream);} | |
| .headline .accent{background:linear-gradient(120deg,var(--cream) 20%,var(--gold) 90%); | |
| -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;} | |
| .support{font-size:16px;line-height:1.65;color:var(--paragraph);max-width:44rem;margin:0 auto 2rem;} | |
| .runall{display:inline-flex;align-items:center;gap:.5rem;padding:.7rem 1.5rem;font-size:12.5px; | |
| font-weight:500;font-family:var(--mono);border-radius:8px;border:1px solid var(--gold); | |
| background:var(--gold);color:var(--ground);cursor:pointer;letter-spacing:.06em;margin-top:.5rem;} | |
| .runall:hover{background:var(--gold-bright);} | |
| .runall:disabled{opacity:.5;cursor:not-allowed;} | |
| .stats{display:flex;flex-wrap:wrap;justify-content:center;gap:.75rem;max-width:60rem;margin:2rem auto 0;} | |
| .stat{flex:1 1 150px;min-width:140px;border:1px solid var(--gold-line);border-radius:8px; | |
| background:var(--panel);padding:.9rem 1rem;text-align:left;} | |
| .stat .k{font-family:var(--mono);font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);} | |
| .stat .v{font-size:1.35rem;font-weight:500;color:var(--gold);margin-top:.25rem;} | |
| .stat .v.teal{color:var(--teal);} | |
| .grid{max-width:64rem;margin:2.75rem auto 4rem;padding:0 1.25rem; | |
| display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:1.25rem;} | |
| .card{border:1px solid var(--gold-line);border-radius:12px;background:var(--panel); | |
| padding:1.4rem 1.4rem 1.6rem;display:flex;flex-direction:column;} | |
| .card-head{display:flex;align-items:baseline;gap:.6rem;margin-bottom:.5rem;flex-wrap:wrap;} | |
| .card-num{font-family:var(--mono);font-size:11px;color:var(--teal);border:1px solid var(--teal-line); | |
| border-radius:5px;padding:.1rem .4rem;background:var(--teal-soft);} | |
| .card-organ{font-family:var(--mono);font-size:10px;color:var(--gold);border:1px solid var(--gold-line); | |
| border-radius:5px;padding:.1rem .4rem;background:var(--gold-soft);text-transform:uppercase;letter-spacing:.08em;} | |
| .card-title{font-size:1.1rem;font-weight:500;color:var(--cream);letter-spacing:-.01em;} | |
| .card-sub{font-size:12.5px;color:var(--paragraph);line-height:1.6;margin:0 0 1rem;} | |
| .card-endpoint{font-family:var(--mono);font-size:10px;color:var(--muted);margin-bottom:1rem;word-break:break-all;} | |
| .card-endpoint b{color:var(--gold);font-weight:500;} | |
| .card-controls{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1rem;} | |
| .r-btn{display:inline-flex;align-items:center;gap:.4rem;padding:.5rem 1rem;font-size:11.5px; | |
| font-weight:500;font-family:var(--mono);border-radius:6px;border:1px solid var(--gold-line); | |
| background:transparent;color:var(--gold);cursor:pointer;letter-spacing:.04em; | |
| transition:background .18s ease,border-color .18s ease;} | |
| .r-btn:hover{background:rgba(201,183,135,.08);border-color:rgba(201,183,135,.35);} | |
| .r-btn:active{opacity:.75;} | |
| .r-btn:disabled{opacity:.4;cursor:not-allowed;} | |
| .r-btn-primary{background:var(--gold);color:var(--ground);border-color:var(--gold);} | |
| .r-btn-primary:hover{background:var(--gold-bright);border-color:var(--gold-bright);} | |
| .verdict{display:inline-flex;align-items:center;gap:.4rem;font-family:var(--mono);font-size:11px; | |
| padding:.2rem .6rem;border-radius:5px;margin-bottom:.6rem;visibility:hidden;} | |
| .verdict.show{visibility:visible;} | |
| .verdict.ok{color:var(--live);border:1px solid rgba(90,138,110,.4);background:rgba(90,138,110,.08);} | |
| .verdict.warn{color:var(--gold);border:1px solid var(--gold-line);background:var(--gold-soft);} | |
| .verdict.err{color:var(--err);border:1px solid rgba(176,106,90,.4);background:rgba(176,106,90,.08);} | |
| .receipt{font-family:var(--mono);font-size:10px;color:var(--teal);margin:-.2rem 0 .6rem;visibility:hidden;} | |
| .receipt.show{visibility:visible;} | |
| .out{position:relative;border:1px solid var(--gold-line);border-radius:8px;background:var(--panel2); | |
| overflow:hidden;margin-top:auto;} | |
| .out-label{display:flex;align-items:center;justify-content:space-between;padding:.45rem .85rem; | |
| border-bottom:1px solid var(--gold-line);font-family:var(--mono);font-size:9px; | |
| letter-spacing:.1em;text-transform:uppercase;color:var(--muted);} | |
| .out-status{display:inline-flex;align-items:center;gap:.4rem;} | |
| .status-dot{width:6px;height:6px;border-radius:50%;background:var(--dim);transition:background .3s;} | |
| .status-dot.ok{background:var(--live);box-shadow:0 0 6px var(--live);} | |
| .status-dot.err{background:var(--err);box-shadow:0 0 6px var(--err);} | |
| .status-dot.loading{background:var(--gold);animation:pulseDot 1s ease-in-out infinite;} | |
| .out-pre{margin:0;padding:.85rem;font-family:var(--mono);font-size:10.5px;line-height:1.6; | |
| color:var(--paragraph);white-space:pre-wrap;word-break:break-word;max-height:300px;overflow-y:auto; | |
| scrollbar-width:thin;scrollbar-color:var(--dim) transparent;} | |
| .out-pre:empty::before{content:"— launch this demo to call the live organ —";color:var(--dim);font-style:italic;} | |
| .footer{border-top:1px solid var(--gold-line);padding:2rem 1.5rem;text-align:center;} | |
| .footer .note{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);} | |
| .honest{max-width:64rem;margin:0 auto 2rem;padding:0 1.25rem;} | |
| .honest-box{border:1px solid var(--teal-line);border-radius:8px;background:var(--teal-soft); | |
| padding:.9rem 1.1rem;font-size:12px;color:var(--paragraph);line-height:1.6;} | |
| .honest-box b{color:var(--teal);} | |
| @media (prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms ;animation-iteration-count:1 ;}} | |
| </style> | |
| <style> | |
| .tabs-bar{position:sticky;top:0;z-index:40;display:flex;gap:.35rem;flex-wrap:wrap;justify-content:center; | |
| padding:.6rem 1rem;background:rgba(10,10,10,.92);backdrop-filter:blur(10px);border-bottom:1px solid var(--gold-line);} | |
| .tab-btn{font-family:var(--mono);font-size:11px;letter-spacing:.04em;padding:.5rem .9rem;border-radius:7px; | |
| border:1px solid var(--gold-line);background:transparent;color:var(--paragraph);cursor:pointer;transition:all .18s ease;} | |
| .tab-btn:hover{color:var(--gold);border-color:rgba(201,183,135,.35);} | |
| .tab-btn.active{background:var(--gold);color:var(--ground);border-color:var(--gold);font-weight:600;} | |
| .tab-btn .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--dim);margin-right:.4rem;vertical-align:middle;} | |
| .tab-btn .dot.ok{background:var(--live);box-shadow:0 0 5px var(--live);} | |
| .tab-btn .dot.err{background:var(--err);} | |
| .tab-btn .dot.loading{background:var(--gold);animation:pulseDot 1s ease-in-out infinite;} | |
| .panel-wrap{max-width:60rem;margin:1.75rem auto 3rem;padding:0 1.25rem;} | |
| .panel{display:none;} | |
| .panel.active{display:block;animation:fade .3s ease;} | |
| @keyframes fade{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}} | |
| .p-head{margin-bottom:1rem;} | |
| .p-title{font-size:1.6rem;font-weight:400;letter-spacing:-.02em;margin:0 0 .35rem;color:var(--cream);} | |
| .p-blurb{font-size:13.5px;line-height:1.6;color:var(--paragraph);max-width:42rem;margin:0;} | |
| .seg{display:inline-flex;gap:.3rem;margin:.75rem 0 1rem;flex-wrap:wrap;} | |
| .seg .r-btn.sel{background:var(--gold);color:var(--ground);border-color:var(--gold);} | |
| textarea.ta{width:100%;min-height:120px;background:var(--panel2);border:1px solid var(--gold-line);border-radius:8px; | |
| color:var(--cream);font-family:var(--mono);font-size:11px;padding:.7rem;resize:vertical;line-height:1.5;} | |
| .tl{list-style:none;margin:.6rem 0 0;padding:0;} | |
| .tl li{display:flex;gap:.6rem;align-items:flex-start;padding:.45rem 0;border-bottom:1px solid var(--gold-soft);font-size:12px;} | |
| .tl .ic{flex:0 0 auto;width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center; | |
| font-family:var(--mono);font-size:10px;margin-top:1px;} | |
| .tl .ic.ok{background:rgba(90,138,110,.15);color:var(--live);border:1px solid rgba(90,138,110,.4);} | |
| .tl .ic.no{background:rgba(176,106,90,.15);color:var(--err);border:1px solid rgba(176,106,90,.4);} | |
| .tl .step{color:var(--cream);font-weight:500;} | |
| .tl .det{color:var(--muted);} | |
| .crossed{border:1px solid rgba(176,106,90,.4);background:rgba(176,106,90,.07);border-radius:8px;padding:.7rem .9rem;margin:.7rem 0;} | |
| .crossed h4{margin:0 0 .4rem;font-size:11px;font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--err);} | |
| .crossed .row{font-size:12px;color:var(--paragraph);padding:.25rem 0;} | |
| .crossed .row b{color:var(--cream);} | |
| table.t{width:100%;border-collapse:collapse;margin-top:.6rem;font-size:11.5px;} | |
| table.t th{text-align:left;font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase; | |
| color:var(--muted);padding:.4rem .5rem;border-bottom:1px solid var(--gold-line);} | |
| table.t td{padding:.4rem .5rem;border-bottom:1px solid var(--gold-soft);color:var(--paragraph);} | |
| .pill{font-family:var(--mono);font-size:10px;padding:.12rem .45rem;border-radius:4px;} | |
| .pill.go,.pill.nominal,.pill.allow{color:var(--live);background:rgba(90,138,110,.12);border:1px solid rgba(90,138,110,.35);} | |
| .pill.review,.pill.insufficient{color:var(--gold);background:var(--gold-soft);border:1px solid var(--gold-line);} | |
| .pill.nogo,.pill.anomalous,.pill.block{color:var(--err);background:rgba(176,106,90,.1);border:1px solid rgba(176,106,90,.35);} | |
| .kpis{display:flex;gap:.6rem;flex-wrap:wrap;margin:.4rem 0 .2rem;} | |
| .kpi{flex:1 1 100px;min-width:90px;border:1px solid var(--gold-line);border-radius:8px;background:var(--panel);padding:.7rem .8rem;} | |
| .kpi .k{font-family:var(--mono);font-size:8.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);} | |
| .kpi .v{font-size:1.5rem;font-weight:500;color:var(--gold);} | |
| .kpi .v.live{color:var(--live);} .kpi .v.err{color:var(--err);} .kpi .v.teal{color:var(--teal);} | |
| #trajCanvas{width:100%;height:340px;background:radial-gradient(ellipse at center,#0d0d12,#060606); | |
| border:1px solid var(--gold-line);border-radius:10px;display:block;margin-top:.6rem;cursor:grab;} | |
| #trajCanvas:active{cursor:grabbing;} | |
| .leg{display:flex;gap:1rem;flex-wrap:wrap;font-family:var(--mono);font-size:10px;color:var(--muted);margin-top:.5rem;} | |
| .leg span b{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:.35rem;vertical-align:middle;} | |
| .field{display:flex;flex-direction:column;gap:.3rem;margin-bottom:.6rem;} | |
| .field label{font-family:var(--mono);font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);} | |
| .sub{font-family:var(--mono);font-size:10px;color:var(--teal);margin:.3rem 0 0;} | |
| .gap{height:.6rem;} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="ribbon"> | |
| <span class="live"><span class="live-dot"></span>a11oy</span> | |
| <span class="sep">·</span><span>Mission Surfaces</span> | |
| <span class="sep">·</span><span class="teal">in-image · signed · re-verifiable</span> | |
| <a href="/console">console →</a> | |
| </div> | |
| <section class="hero"> | |
| <div class="grid-bg"></div> | |
| <div class="hero-inner"> | |
| <span class="eyebrow"><span class="eyebrow-dot"></span><span class="eyebrow-text">Five working surfaces · live on this Space</span></span> | |
| <h1 class="headline">One platform.<br/><span class="accent">Five mission answers.</span></h1> | |
| <p class="support">Each surface below runs live inside this container and returns a real result — | |
| a governed decision with a signed, tamper-evident record you can re-verify on screen, a deployment | |
| package read live from the registry, a readiness dashboard, an instant trajectory picture, and the | |
| whole governed decision running on the device itself.</p> | |
| <button class="runall" id="runAll">▶ Run all five</button> | |
| <div class="stats"> | |
| <div class="stat"><div class="k">Surfaces live</div><div class="v" id="stLive">0 / 5</div></div> | |
| <div class="stat"><div class="k">Records signed</div><div class="v teal" id="stSigned">0</div></div> | |
| <div class="stat"><div class="k">Re-verified</div><div class="v" id="stVerified">—</div></div> | |
| <div class="stat"><div class="k">Registry</div><div class="v teal" id="stReg">—</div></div> | |
| </div> | |
| </div> | |
| </section> | |
| <div class="tabs-bar" id="tabsBar"> | |
| <button class="tab-btn active" data-tab="oversight"><span class="dot" id="dot-oversight"></span>AI Oversight</button> | |
| <button class="tab-btn" data-tab="deploy"><span class="dot" id="dot-deploy"></span>Deploy Posture</button> | |
| <button class="tab-btn" data-tab="health"><span class="dot" id="dot-health"></span>Mission Health</button> | |
| <button class="tab-btn" data-tab="trajectory"><span class="dot" id="dot-trajectory"></span>Trajectory Picture</button> | |
| <button class="tab-btn" data-tab="edge"><span class="dot" id="dot-edge"></span>Edge Run</button> | |
| </div> | |
| <div class="panel-wrap"> | |
| <!-- 1. AI OVERSIGHT --> | |
| <section class="panel active" id="panel-oversight"> | |
| <div class="p-head"> | |
| <h2 class="p-title">AI Oversight</h2> | |
| <p class="p-blurb">Watch an autonomous system make a decision and catch the exact moment it crosses an | |
| authorized limit. Every run is sealed into a signed, tamper-evident record — press <b>Re-verify</b> to | |
| confirm it, then <b>Tamper test</b> to prove a single changed byte is detected.</p> | |
| </div> | |
| <div class="seg" id="ovrSeg"> | |
| <button class="r-btn sel" data-sc="within">Within limits</button> | |
| <button class="r-btn" data-sc="crossing">Crosses the line</button> | |
| <button class="r-btn" data-sc="drift">Borderline</button> | |
| </div> | |
| <div class="card-controls"> | |
| <button class="r-btn r-btn-primary" id="ovrRun">▶ Run governed decision</button> | |
| <button class="r-btn" id="ovrVerify" disabled>✓ Re-verify record</button> | |
| <button class="r-btn" id="ovrTamper" disabled>⚠ Tamper test</button> | |
| </div> | |
| <div id="ovrVerdict" class="verdict"></div> | |
| <div id="ovrCrossed"></div> | |
| <ul class="tl" id="ovrTimeline"></ul> | |
| <div class="out"> | |
| <div class="out-label"><span>Signed record</span> | |
| <span class="out-status"><span class="status-dot" id="ovrDot"></span><span id="ovrStatus">idle</span></span></div> | |
| <pre class="out-pre" id="ovrOut"></pre> | |
| </div> | |
| <p class="sub" id="ovrSub"></p> | |
| </section> | |
| <!-- 2. DEPLOY POSTURE --> | |
| <section class="panel" id="panel-deploy"> | |
| <div class="p-head"> | |
| <h2 class="p-title">Deploy Posture</h2> | |
| <p class="p-blurb">The signed, ready-to-ship deployment package — the same artifact deploys from cloud to | |
| a disconnected edge node with one command. Existence, digest and signature are read <b>live from the public | |
| registry</b> on each click, not asserted from memory.</p> | |
| </div> | |
| <div class="card-controls"> | |
| <button class="r-btn r-btn-primary" id="depRun">▶ Check deploy posture (live)</button> | |
| </div> | |
| <div id="depVerdict" class="verdict"></div> | |
| <div id="depBody"></div> | |
| <div class="out"> | |
| <div class="out-label"><span>Registry response</span> | |
| <span class="out-status"><span class="status-dot" id="depDot"></span><span id="depStatus">idle</span></span></div> | |
| <pre class="out-pre" id="depOut"></pre> | |
| </div> | |
| </section> | |
| <!-- 3. MISSION HEALTH --> | |
| <section class="panel" id="panel-health"> | |
| <div class="p-head"> | |
| <h2 class="p-title">Mission Health</h2> | |
| <p class="p-blurb">A live readiness dashboard that tells you who is ready to deploy and who needs review. | |
| Each verdict is computed in-image from concrete readiness criteria; <b>missing data forces a review</b> — | |
| the system never assumes a member is ready.</p> | |
| </div> | |
| <div class="card-controls"> | |
| <button class="r-btn r-btn-primary" id="hlRun">▶ Generate readiness dashboard</button> | |
| </div> | |
| <div class="kpis" id="hlKpis"></div> | |
| <div id="hlBody"></div> | |
| <div class="out"> | |
| <div class="out-label"><span>Computed in-image</span> | |
| <span class="out-status"><span class="status-dot" id="hlDot"></span><span id="hlStatus">idle</span></span></div> | |
| <pre class="out-pre" id="hlOut"></pre> | |
| </div> | |
| </section> | |
| <!-- 4. TRAJECTORY PICTURE --> | |
| <section class="panel" id="panel-trajectory"> | |
| <div class="p-head"> | |
| <h2 class="p-title">Trajectory Picture</h2> | |
| <p class="p-blurb">Paste any trajectory or orbit data — a JSON array or plain rows — and see it placed | |
| in an operational 3D picture instantly. The system auto-detects the fields, assesses each track against real | |
| orbital-regime envelopes, and flags anomalies. <b>No bespoke per-source integration.</b></p> | |
| </div> | |
| <div class="field"> | |
| <label>Paste tracks (JSON array, or rows: <span class="mono">id alt_km velocity_kms inclination_deg</span>) — or leave blank for a sample</label> | |
| <textarea class="ta" id="trajIn" placeholder='[{"id":"ISS","alt_km":420,"velocity_kms":7.66,"inclination_deg":51.6}] | |
| or | |
| SAT-A 550 7.59 53.0 | |
| SAT-B 35786 3.07 0.0'></textarea> | |
| </div> | |
| <div class="card-controls"> | |
| <button class="r-btn r-btn-primary" id="trajRun">▶ Build trajectory picture</button> | |
| <button class="r-btn" id="trajSample">Load sample</button> | |
| </div> | |
| <div id="trajVerdict" class="verdict"></div> | |
| <canvas id="trajCanvas"></canvas> | |
| <div class="leg"> | |
| <span><b style="background:#5fb3a3"></b>LEO</span> | |
| <span><b style="background:#c9b787"></b>MEO</span> | |
| <span><b style="background:#7aa0d0"></b>GEO</span> | |
| <span><b style="background:#b06a5a"></b>anomalous</span> | |
| <span class="mono">drag to rotate</span> | |
| </div> | |
| <div id="trajBody"></div> | |
| <div class="out"> | |
| <div class="out-label"><span>Assessment</span> | |
| <span class="out-status"><span class="status-dot" id="trajDot"></span><span id="trajStatus">idle</span></span></div> | |
| <pre class="out-pre" id="trajOut"></pre> | |
| </div> | |
| </section> | |
| <!-- 5. EDGE RUN --> | |
| <section class="panel" id="panel-edge"> | |
| <div class="p-head"> | |
| <h2 class="p-title">Edge Run</h2> | |
| <p class="p-blurb">The whole governed decision running on the device itself, with no connection required. The | |
| run executes <b>fully in this container</b> with no outbound call, the on-device model router picks a tier, | |
| and the result is signed and re-verifiable — the same container is the package that deploys to a | |
| disconnected node.</p> | |
| </div> | |
| <div class="card-controls"> | |
| <button class="r-btn r-btn-primary" id="edgeRun">▶ Run on the edge (disconnected)</button> | |
| <button class="r-btn" id="edgeVerify" disabled>✓ Re-verify record</button> | |
| </div> | |
| <div id="edgeVerdict" class="verdict"></div> | |
| <div id="edgeRouter"></div> | |
| <ul class="tl" id="edgeTimeline"></ul> | |
| <div class="out"> | |
| <div class="out-label"><span>In-image governed run</span> | |
| <span class="out-status"><span class="status-dot" id="edgeDot"></span><span id="edgeStatus">idle</span></span></div> | |
| <pre class="out-pre" id="edgeOut"></pre> | |
| </div> | |
| <p class="sub" id="edgeSub"></p> | |
| </section> | |
| </div> | |
| <div class="honest"> | |
| <div class="honest-box" id="honestBox"> | |
| <b>What is real here.</b> Every surface above runs live in this container. Governed decisions are sealed into | |
| real DSSE envelopes signed by an in-image ECDSA-P256 key (verifiable against | |
| <a href="/cosign.pub" style="color:var(--teal)">/cosign.pub</a>); change one byte and re-verify returns false. | |
| The advisory trust score (Λ) is a research conjecture, not a pass/fail oracle. Deploy posture is read live | |
| from the public registry. Attestation is SLSA Build L2 on the organ images (honest L1 + L2) — not the bundle, | |
| not L3, not an ATO. Readiness and trajectory use clearly-labelled sample inputs over real logic; plug a real feed | |
| into the same shape and they are live. | |
| </div> | |
| </div> | |
| <footer class="footer"><span class="note">a11oy · SZL Holdings · in-image · signed · re-verifiable</span></footer> | |
| <script> | |
| (function(){ | |
| "use strict"; | |
| // API base: the /api/a11oy/v1 form is what the Node proxy forwards; the stripped | |
| // /v1 form is reachable directly. Try /api first, fall back to /v1. | |
| var BASES = ["/api/a11oy/v1", "/v1"]; | |
| function api(path, opts){ | |
| var i = 0; | |
| function attempt(){ | |
| return fetch(BASES[i] + path, opts).then(function(r){ | |
| if(!r.ok && r.status === 404 && i < BASES.length-1){ i++; return attempt(); } | |
| return r.json().then(function(j){ return {ok:r.ok, status:r.status, json:j}; }); | |
| }).catch(function(e){ | |
| if(i < BASES.length-1){ i++; return attempt(); } | |
| throw e; | |
| }); | |
| } | |
| return attempt(); | |
| } | |
| function getJSON(p){ return api(p, {method:"GET"}); } | |
| function postJSON(p, body){ | |
| return api(p, {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(body||{})}); | |
| } | |
| var $ = function(id){ return document.getElementById(id); }; | |
| function setDot(id, state){ var d=$(id); if(d){ d.className = "dot" + (state?(" "+state):""); } } | |
| function setStatus(dotId, statusId, state, text){ | |
| var d=$(dotId); if(d) d.className="status-dot"+(state?(" "+state):""); | |
| var s=$(statusId); if(s) s.textContent=text||state||""; | |
| } | |
| function show(el, cls, html){ el.className = el.className.split(" ")[0] + " " + cls + " show"; el.innerHTML = html; } | |
| function pretty(o){ return JSON.stringify(o, null, 2); } | |
| // ---------- tab switching ---------- | |
| var tabBtns = document.querySelectorAll(".tab-btn"); | |
| tabBtns.forEach(function(b){ | |
| b.addEventListener("click", function(){ | |
| tabBtns.forEach(function(x){ x.classList.remove("active"); }); | |
| b.classList.add("active"); | |
| document.querySelectorAll(".panel").forEach(function(p){ p.classList.remove("active"); }); | |
| $("panel-"+b.dataset.tab).classList.add("active"); | |
| if(b.dataset.tab === "trajectory") drawTraj(); | |
| }); | |
| }); | |
| // ---------- shared run-state for the hero stats ---------- | |
| var state = {live:{}, signed:0, verified:null}; | |
| function refreshStats(){ | |
| var n = Object.keys(state.live).filter(function(k){return state.live[k];}).length; | |
| $("stLive").textContent = n + " / 5"; | |
| $("stSigned").textContent = state.signed; | |
| if(state.verified !== null){ $("stVerified").textContent = state.verified ? "PASS" : "FAIL"; $("stVerified").className = "v " + (state.verified?"teal":""); } | |
| } | |
| function markLive(tab, ok){ state.live[tab]= ok; setDot("dot-"+tab, ok?"ok":"err"); refreshStats(); } | |
| // ---------- helper: render a governed-run timeline ---------- | |
| function renderTimeline(ulId, timeline){ | |
| var ul = $(ulId); ul.innerHTML = ""; | |
| (timeline||[]).forEach(function(t){ | |
| var li = document.createElement("li"); | |
| li.innerHTML = '<span class="ic '+(t.ok?"ok":"no")+'">'+(t.ok?"✓":"✕")+'</span>'+ | |
| '<span><span class="step">'+t.step+'</span> — <span class="det">'+t.detail+'</span></span>'; | |
| ul.appendChild(li); | |
| }); | |
| } | |
| function renderCrossed(elId, crossed){ | |
| var el = $(elId); | |
| if(!crossed || !crossed.length){ el.innerHTML=""; return; } | |
| var rows = crossed.map(function(c){ | |
| return '<div class="row"><b>'+c.name+'</b> — observed: '+c.observed+'; limit: '+c.limit+'</div>'; | |
| }).join(""); | |
| el.innerHTML = '<div class="crossed"><h4>Lines crossed (caught at the limit-check step)</h4>'+rows+'</div>'; | |
| } | |
| // ========== 1. AI OVERSIGHT ========== | |
| var ovrScenario = "within", lastRun = null; | |
| $("ovrSeg").querySelectorAll(".r-btn").forEach(function(b){ | |
| b.addEventListener("click", function(){ | |
| $("ovrSeg").querySelectorAll(".r-btn").forEach(function(x){x.classList.remove("sel");}); | |
| b.classList.add("sel"); ovrScenario = b.dataset.sc; | |
| }); | |
| }); | |
| function ovrRun(){ | |
| setStatus("ovrDot","ovrStatus","loading","running…"); | |
| $("ovrVerify").disabled=true; $("ovrTamper").disabled=true; | |
| return postJSON("/oversight/run", {scenario: ovrScenario}).then(function(r){ | |
| var d = r.json; lastRun = d; | |
| renderTimeline("ovrTimeline", d.timeline); | |
| renderCrossed("ovrCrossed", d.crossed_lines); | |
| var v = $("ovrVerdict"); | |
| if(d.decision === "ALLOW"){ show(v,"ok", "ALLOW — "+d.headline); } | |
| else { show(v,"err", "BLOCK — "+d.headline); } | |
| $("ovrOut").textContent = pretty(d.signed_record); | |
| setStatus("ovrDot","ovrStatus","ok","sealed + signed"); | |
| $("ovrSub").textContent = d.honesty || ""; | |
| $("ovrVerify").disabled=false; $("ovrTamper").disabled=false; | |
| if(d.signed_record && d.signed_record.signed) state.signed++; | |
| markLive("oversight", true); refreshStats(); | |
| return d; | |
| }).catch(function(e){ setStatus("ovrDot","ovrStatus","err","error"); $("ovrOut").textContent=String(e); markLive("oversight",false); }); | |
| } | |
| function ovrVerify(tamper){ | |
| if(!lastRun) return; | |
| var run = JSON.parse(JSON.stringify(lastRun)); | |
| if(tamper){ | |
| // flip a byte in a receipt body to prove detection | |
| if(run.receipt_chain && run.receipt_chain.length>2){ run.receipt_chain[2].body.allow = !run.receipt_chain[2].body.allow; } | |
| } | |
| setStatus("ovrDot","ovrStatus","loading","verifying…"); | |
| return postJSON("/oversight/verify", {run: run}).then(function(r){ | |
| var v = r.json; var vd = $("ovrVerdict"); | |
| if(tamper){ | |
| show(vd, "err", "TAMPER TEST — verified:false (chain break at receipt #"+v.chain_break_at_seq+"). A single changed byte is caught."); | |
| setStatus("ovrDot","ovrStatus","err","tamper detected"); | |
| state.verified = false; | |
| } else { | |
| show(vd, "ok", "RE-VERIFIED — verified:"+v.verified+" · chain intact:"+v.chain_intact+" · signature valid:"+v.signature_valid); | |
| setStatus("ovrDot","ovrStatus","ok","verified PASS"); | |
| state.verified = v.verified; | |
| } | |
| $("ovrOut").textContent = pretty(v); refreshStats(); | |
| }).catch(function(e){ setStatus("ovrDot","ovrStatus","err","error"); $("ovrOut").textContent=String(e); }); | |
| } | |
| $("ovrRun").addEventListener("click", function(){ ovrRun(); }); | |
| $("ovrVerify").addEventListener("click", function(){ ovrVerify(false); }); | |
| $("ovrTamper").addEventListener("click", function(){ ovrVerify(true); }); | |
| // ========== 2. DEPLOY POSTURE ========== | |
| function depRun(){ | |
| setStatus("depDot","depStatus","loading","reading registry…"); | |
| return getJSON("/deploy/posture").then(function(r){ | |
| var d = r.json; | |
| var pub = (d.bundles||[]).filter(function(b){return b.published;}).length; | |
| var rows = (d.bundles||[]).map(function(b){ | |
| return '<tr><td><b>'+b.name+'</b><br><span class="mono" style="color:var(--muted);font-size:10px">'+b.ref+'</span></td>'+ | |
| '<td><span class="pill '+(b.published?"go":"nogo")+'">'+(b.published?"HTTP "+b.registry_status:"unreachable")+'</span></td>'+ | |
| '<td class="mono" style="font-size:9.5px;color:var(--teal)">'+((b.digest||"").slice(0,23))+'…</td>'+ | |
| '<td>'+(b.cosign_signature_tag?'<span class="pill go">signed</span>':'—')+'</td></tr>'; | |
| }).join(""); | |
| $("depBody").innerHTML = | |
| '<table class="t"><thead><tr><th>Bundle</th><th>Registry</th><th>Digest</th><th>Cosign</th></tr></thead><tbody>'+rows+'</tbody></table>'+ | |
| '<div class="gap"></div>'+ | |
| '<div class="kpis">'+ | |
| '<div class="kpi"><div class="k">One-command deploy</div><div class="v teal mono" style="font-size:11px;line-height:1.5">'+d.deploy_one_command+'</div></div>'+ | |
| '</div>'+ | |
| '<div class="gap"></div>'+ | |
| '<table class="t"><tbody>'+ | |
| '<tr><td>Package CR</td><td>'+d.package_cr.apiVersion+' · '+d.package_cr.kind+'</td></tr>'+ | |
| '<tr><td>SBOM</td><td>'+d.sbom.formats.join(" + ")+'</td></tr>'+ | |
| '<tr><td>Image attestation</td><td>'+d.attestation.image_level+'</td></tr>'+ | |
| '<tr><td>Bundle provenance</td><td>'+d.attestation.bundle_level+'</td></tr>'+ | |
| '<tr><td>Not claimed</td><td class="mono" style="font-size:10px">'+d.attestation.not_claimed.join(" · ")+'</td></tr>'+ | |
| '<tr><td>UDS Core compat</td><td>'+d.uds_core_compat+'</td></tr>'+ | |
| '</tbody></table>'; | |
| show($("depVerdict"), pub>=1?"ok":"err", pub+" / "+(d.bundles||[]).length+" bundles published & signed on the registry"); | |
| $("depOut").textContent = pretty(d.bundles); | |
| setStatus("depDot","depStatus","ok","live from registry"); | |
| $("stReg").textContent = pub>=1 ? "200 OK" : "down"; | |
| markLive("deploy", pub>=1); | |
| }).catch(function(e){ setStatus("depDot","depStatus","err","error"); $("depOut").textContent=String(e); markLive("deploy",false); }); | |
| } | |
| $("depRun").addEventListener("click", function(){ depRun(); }); | |
| // ========== 3. MISSION HEALTH ========== | |
| function hlRun(){ | |
| setStatus("hlDot","hlStatus","loading","computing…"); | |
| return getJSON("/mission/health").then(function(r){ | |
| var d = r.json, s = d.summary; | |
| $("hlKpis").innerHTML = | |
| '<div class="kpi"><div class="k">Ready (GO)</div><div class="v live">'+s.go+'</div></div>'+ | |
| '<div class="kpi"><div class="k">Review</div><div class="v">'+s.review+'</div></div>'+ | |
| '<div class="kpi"><div class="k">Not ready</div><div class="v err">'+s.no_go+'</div></div>'+ | |
| '<div class="kpi"><div class="k">% Ready</div><div class="v teal">'+s.percent_ready+'%</div></div>'; | |
| var rows = (d.members||[]).map(function(m){ | |
| var pc = m.verdict==="GO"?"go":(m.verdict==="REVIEW"?"review":"nogo"); | |
| var gaps = (m.gaps||[]).map(function(g){return g.criterion+": "+g.result;}).join("; ") || "all current"; | |
| return '<tr><td class="mono">'+m.id+'</td><td>'+m.unit+'</td><td>'+m.role+'</td>'+ | |
| '<td><span class="pill '+pc+'">'+m.verdict+'</span></td><td style="font-size:10.5px">'+gaps+'</td></tr>'; | |
| }).join(""); | |
| $("hlBody").innerHTML = '<table class="t"><thead><tr><th>ID</th><th>Unit</th><th>Role</th><th>Verdict</th><th>Gaps</th></tr></thead><tbody>'+rows+'</tbody></table>'; | |
| $("hlOut").textContent = pretty(d.summary)+"\n\nby unit: "+pretty(d.by_unit); | |
| setStatus("hlDot","hlStatus","ok","computed in-image"); | |
| markLive("health", true); | |
| }).catch(function(e){ setStatus("hlDot","hlStatus","err","error"); $("hlOut").textContent=String(e); markLive("health",false); }); | |
| } | |
| $("hlRun").addEventListener("click", function(){ hlRun(); }); | |
| // ========== 4. TRAJECTORY PICTURE ========== | |
| var trajTracks = [], rot = {x: -0.5, y: 0.6}, dragging=false, lastXY=null; | |
| function trajRun(){ | |
| setStatus("trajDot","trajStatus","loading","ingesting…"); | |
| var raw = $("trajIn").value.trim(); | |
| var body = raw ? {data: raw} : {}; | |
| return postJSON("/trajectory/ingest", body).then(function(r){ | |
| var d = r.json; trajTracks = d.tracks || []; | |
| var rows = trajTracks.map(function(t){ | |
| var a = t.assessment || {}; var pc = a.verdict==="NOMINAL"?"nominal":(a.verdict==="ANOMALOUS"?"anomalous":"insufficient"); | |
| return '<tr><td class="mono">'+t.id+'</td><td>'+(t.alt_km!=null?t.alt_km+" km":"—")+'</td>'+ | |
| '<td>'+(t.velocity_kms!=null?t.velocity_kms+" km/s":"—")+'</td><td>'+(a.regime||"—")+'</td>'+ | |
| '<td><span class="pill '+pc+'">'+(a.verdict||"—")+'</span></td>'+ | |
| '<td style="font-size:10px">'+((a.flags||[]).join("; ")||"—")+'</td></tr>'; | |
| }).join(""); | |
| $("trajBody").innerHTML = '<table class="t"><thead><tr><th>Track</th><th>Alt</th><th>Velocity</th><th>Regime</th><th>Verdict</th><th>Flags</th></tr></thead><tbody>'+rows+'</tbody></table>'; | |
| show($("trajVerdict"), (d.summary.ANOMALOUS>0?"warn":"ok"), d.headline); | |
| $("trajOut").textContent = pretty(d.summary); | |
| setStatus("trajDot","trajStatus","ok","assessed"); | |
| markLive("trajectory", true); | |
| drawTraj(); | |
| }).catch(function(e){ setStatus("trajDot","trajStatus","err","error"); $("trajOut").textContent=String(e); markLive("trajectory",false); }); | |
| } | |
| // sovereign 3D: orthographic projection on canvas, no external libs | |
| function drawTraj(){ | |
| var cv = $("trajCanvas"); if(!cv) return; | |
| var dpr = window.devicePixelRatio||1; | |
| var w = cv.clientWidth, h = cv.clientHeight; | |
| cv.width = w*dpr; cv.height = h*dpr; | |
| var ctx = cv.getContext("2d"); ctx.scale(dpr,dpr); | |
| ctx.clearRect(0,0,w,h); | |
| var cx=w/2, cy=h/2, Rearth=38; | |
| function proj(x,y,z){ | |
| var cyaw=Math.cos(rot.y), syaw=Math.sin(rot.y), cp=Math.cos(rot.x), sp=Math.sin(rot.x); | |
| var x1=x*cyaw - z*syaw, z1=x*syaw + z*cyaw; | |
| var y1=y*cp - z1*sp, z2=y*sp + z1*cp; | |
| var scale = 320/(320+z2); | |
| return [cx + x1*scale, cy - y1*scale, z2]; | |
| } | |
| // earth sphere | |
| var ep = proj(0,0,0); | |
| var g = ctx.createRadialGradient(ep[0]-10, ep[1]-10, 4, ep[0], ep[1], Rearth); | |
| g.addColorStop(0,"#2a4a6a"); g.addColorStop(1,"#0a1828"); | |
| ctx.beginPath(); ctx.arc(ep[0],ep[1],Rearth,0,2*Math.PI); ctx.fillStyle=g; ctx.fill(); | |
| ctx.strokeStyle="rgba(95,179,163,0.3)"; ctx.stroke(); | |
| // regime rings (radii scaled by log of altitude) | |
| function ringR(alt){ return Rearth + 26*Math.log10(1 + alt/300); } | |
| [{a:1000,c:"rgba(95,179,163,0.25)"},{a:20000,c:"rgba(201,183,135,0.22)"},{a:35786,c:"rgba(122,160,208,0.25)"}].forEach(function(rg){ | |
| var rr=ringR(rg.a); ctx.beginPath(); | |
| for(var t=0;t<=64;t++){ var th=t/64*2*Math.PI; var p=proj(rr*Math.cos(th),0,rr*Math.sin(th)); if(t===0)ctx.moveTo(p[0],p[1]);else ctx.lineTo(p[0],p[1]); } | |
| ctx.strokeStyle=rg.c; ctx.lineWidth=1; ctx.stroke(); | |
| }); | |
| // tracks: place each on its regime ring by inclination + a hash of id for spread | |
| (trajTracks||[]).forEach(function(t,i){ | |
| if(t.alt_km==null) return; | |
| var rr = ringR(t.alt_km); | |
| var inc = (t.inclination_deg!=null? t.inclination_deg: 30) * Math.PI/180; | |
| var ang = (i/Math.max(1,trajTracks.length))*2*Math.PI + 0.3; | |
| var x = rr*Math.cos(ang), z = rr*Math.sin(ang), y = rr*Math.sin(inc)*0.4*Math.sin(ang*1.3); | |
| var p = proj(x,y,z); | |
| var a = t.assessment||{}; | |
| var col = a.verdict==="ANOMALOUS" ? "#b06a5a" : | |
| (a.regime==="LEO"?"#5fb3a3": a.regime==="MEO"?"#c9b787": a.regime==="GEO"?"#7aa0d0":"#9a9a9a"); | |
| ctx.beginPath(); ctx.arc(p[0],p[1],4.5,0,2*Math.PI); ctx.fillStyle=col; ctx.fill(); | |
| ctx.shadowColor=col; ctx.shadowBlur=8; ctx.fill(); ctx.shadowBlur=0; | |
| ctx.fillStyle="rgba(245,245,245,0.85)"; ctx.font="9px 'JetBrains Mono',monospace"; | |
| ctx.fillText(t.id, p[0]+7, p[1]+3); | |
| }); | |
| } | |
| (function bindDrag(){ | |
| var cv = $("trajCanvas"); | |
| cv.addEventListener("mousedown", function(e){ dragging=true; lastXY=[e.clientX,e.clientY]; }); | |
| window.addEventListener("mouseup", function(){ dragging=false; }); | |
| window.addEventListener("mousemove", function(e){ | |
| if(!dragging||!lastXY) return; | |
| rot.y += (e.clientX-lastXY[0])*0.01; rot.x += (e.clientY-lastXY[1])*0.01; | |
| lastXY=[e.clientX,e.clientY]; drawTraj(); | |
| }); | |
| window.addEventListener("resize", function(){ drawTraj(); }); | |
| })(); | |
| $("trajRun").addEventListener("click", function(){ trajRun(); }); | |
| $("trajSample").addEventListener("click", function(){ | |
| getJSON("/trajectory/sample").then(function(r){ $("trajIn").value = JSON.stringify(r.json.sample, null, 2); }); | |
| }); | |
| // ========== 5. EDGE RUN ========== | |
| var edgeLast = null; | |
| function edgeRun(){ | |
| setStatus("edgeDot","edgeStatus","loading","running on edge…"); | |
| $("edgeVerify").disabled=true; | |
| return postJSON("/edge/run", {connected:false}).then(function(r){ | |
| var d = r.json; edgeLast = d; | |
| renderTimeline("edgeTimeline", d.timeline); | |
| var ed = d.edge || {}; | |
| var mr = ed.model_router || {}; | |
| $("edgeRouter").innerHTML = | |
| '<div class="kpis">'+ | |
| '<div class="kpi"><div class="k">Ran where</div><div class="v teal" style="font-size:12px;line-height:1.4">in-image · no uplink</div></div>'+ | |
| '<div class="kpi"><div class="k">Model tier</div><div class="v" style="font-size:13px">'+(mr.picked||"—")+'</div></div>'+ | |
| '<div class="kpi"><div class="k">Decision</div><div class="v '+(d.decision==="ALLOW"?"live":"err")+'" style="font-size:14px">'+d.decision+'</div></div>'+ | |
| '</div>'+ | |
| '<table class="t"><tbody>'+ | |
| '<tr><td>Router policy</td><td>'+(mr.policy||"")+'</td></tr>'+ | |
| '<tr><td>Deploy command</td><td class="mono" style="font-size:10px;color:var(--teal)">'+(ed.deploy_command||"")+'</td></tr>'+ | |
| '</tbody></table>'; | |
| show($("edgeVerdict"), d.decision==="ALLOW"?"ok":"err", | |
| (d.decision==="ALLOW"?"ALLOW":"BLOCK")+" — governed run completed fully in-image, no outbound call"); | |
| $("edgeOut").textContent = pretty({decision:d.decision, edge:ed, signed:!!(d.signed_record&&d.signed_record.signed)}); | |
| $("edgeSub").textContent = ed.honesty || ""; | |
| setStatus("edgeDot","edgeStatus","ok","in-image · signed"); | |
| $("edgeVerify").disabled=false; | |
| if(d.signed_record && d.signed_record.signed) state.signed++; | |
| markLive("edge", true); refreshStats(); | |
| }).catch(function(e){ setStatus("edgeDot","edgeStatus","err","error"); $("edgeOut").textContent=String(e); markLive("edge",false); }); | |
| } | |
| function edgeVerify(){ | |
| if(!edgeLast) return; | |
| setStatus("edgeDot","edgeStatus","loading","verifying…"); | |
| return postJSON("/oversight/verify", {run: edgeLast}).then(function(r){ | |
| var v = r.json; | |
| show($("edgeVerdict"), v.verified?"ok":"err", "RE-VERIFIED — verified:"+v.verified+" · signature valid:"+v.signature_valid); | |
| $("edgeOut").textContent = pretty(v); | |
| setStatus("edgeDot","edgeStatus", v.verified?"ok":"err", v.verified?"verified PASS":"FAIL"); | |
| }); | |
| } | |
| $("edgeRun").addEventListener("click", function(){ edgeRun(); }); | |
| $("edgeVerify").addEventListener("click", function(){ edgeVerify(); }); | |
| // ========== RUN ALL ========== | |
| $("runAll").addEventListener("click", function(){ | |
| var btn = $("runAll"); btn.disabled = true; btn.textContent = "running all five…"; | |
| ovrScenario = "crossing"; // show the dramatic line-crossing catch by default in run-all | |
| Promise.resolve() | |
| .then(ovrRun).then(function(){ return ovrVerify(false); }) | |
| .then(depRun).then(hlRun).then(function(){ $("trajIn").value=""; return trajRun(); }).then(edgeRun) | |
| .then(function(){ btn.disabled=false; btn.textContent="✓ all five ran — click any tab"; }) | |
| .catch(function(){ btn.disabled=false; btn.textContent="▶ Run all five"; }); | |
| }); | |
| // mark surfaces present on load (index probe) + size canvas | |
| getJSON("/warhacker-tabs/index").then(function(r){ | |
| if(r.json && r.json.ok){ /* surfaces present */ } | |
| }).catch(function(){}); | |
| drawTraj(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |