Spaces:
Running
Running
deploy(hf): sync szl-holdings/a11oy@main derived COPY set
Browse filesReusable Dockerfile-COPY-derived deploy from szl-holdings/a11oy main.
Files: 1039 Pruned: 0
Derived from Dockerfile COPY sources (NO hand-maintained allowlist).
Signed-off-by: SZL Holdings <noreply@szlholdings.ai>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- static/3d/holographic.html +15 -90
static/3d/holographic.html
CHANGED
|
@@ -35,7 +35,7 @@
|
|
| 35 |
surfaces stand out and the rest group behind one-tap dropdowns. Accents are proof-teal
|
| 36 |
(0x3af4c8) — NEVER purple (doctrine v11 palette). */
|
| 37 |
.tabs{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--line);
|
| 38 |
-
background:#070d15;flex:0 0 auto
|
| 39 |
.navrow{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
|
| 40 |
.navlabel{font:9.5px ui-monospace,monospace;color:var(--para);letter-spacing:.6px;
|
| 41 |
text-transform:uppercase;opacity:.7;margin-right:2px;flex:0 0 auto}
|
|
@@ -59,29 +59,15 @@
|
|
| 59 |
.catbtn[aria-expanded="true"] .caret{transform:rotate(90deg)}
|
| 60 |
.catbtn[data-hasactive="true"]{box-shadow:0 0 0 1px var(--proof) inset}
|
| 61 |
.catbtn .count{font-size:9px;color:var(--para);opacity:.7}
|
| 62 |
-
/* Drop panel:
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
.panel{display:none;position:absolute;left:10px;right:10px;top:100%;margin-top:5px;z-index:15;
|
| 66 |
-
gap:6px;flex-wrap:wrap;padding:10px;border:1px solid var(--line);border-radius:11px;
|
| 67 |
-
background:rgba(9,18,27,.97);box-shadow:0 18px 44px rgba(0,0,0,.6);
|
| 68 |
-
max-height:min(56vh,440px);overflow:auto;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
|
| 69 |
.panel[data-open="true"]{display:flex}
|
| 70 |
#stage-wrap{position:relative;flex:1 1 auto;min-height:0;width:100%}
|
| 71 |
#stage{position:absolute;inset:0}
|
| 72 |
#stage canvas{display:block;width:100%;height:100%;touch-action:none}
|
| 73 |
#fallback{position:absolute;inset:0;display:none;align-items:center;justify-content:center;
|
| 74 |
-
text-align:center;color:var(--para);padding:24px;font:13px
|
| 75 |
-
#fallback a{color:var(--teal)}
|
| 76 |
-
/* Boot hint: a calm "the holo is starting" state so the stage is never an unexplained black
|
| 77 |
-
void while three.js + the first surface load. Hidden the instant a canvas appears. */
|
| 78 |
-
#booting{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
|
| 79 |
-
justify-content:center;gap:12px;text-align:center;color:var(--teal);pointer-events:none;
|
| 80 |
-
font:12.5px ui-monospace,monospace;letter-spacing:.4px;transition:opacity .4s}
|
| 81 |
-
#booting[hidden]{display:none}
|
| 82 |
-
#booting .spin{width:26px;height:26px;border:2px solid #143240;border-top-color:var(--proof);
|
| 83 |
-
border-radius:50%;animation:szspin 1s linear infinite}
|
| 84 |
-
@keyframes szspin{to{transform:rotate(360deg)}}
|
| 85 |
footer{padding:6px 14px;border-top:1px solid var(--line);flex:0 0 auto;
|
| 86 |
background:#070d15;color:var(--para);font:10.5px ui-monospace,monospace;letter-spacing:.3px;z-index:6;
|
| 87 |
white-space:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
@@ -177,9 +163,8 @@
|
|
| 177 |
</nav>
|
| 178 |
<div id="stage-wrap">
|
| 179 |
<div id="stage"></div>
|
| 180 |
-
<div id="booting"><div class="spin"></div>booting the holographic estate…</div>
|
| 181 |
<div id="fallback">3D unavailable on this device — the holographic estate needs a WebGL2 or WebGPU canvas.<br>
|
| 182 |
-
All underlying data is still available as JSON at the per-surface endpoints
|
| 183 |
<!-- Landing overlay (Dev4): coherent honest entry over the live estate. -->
|
| 184 |
<div id="intro" role="dialog" aria-label="a11oy holographic estate — landing overview" hidden>
|
| 185 |
<div class="intro-eyebrow">SZL Holdings · Doctrine v11 · Λ = Conjecture 1</div>
|
|
@@ -221,38 +206,6 @@
|
|
| 221 |
<footer>Doctrine v11 · locked = 8 @ c7c0ba17 · Λ = Conjecture 1 · Khipu BFT = Conjecture 2 ·
|
| 222 |
shared szl3d toolkit (Dev0) · every value carries its honesty label · trust < 100%</footer>
|
| 223 |
|
| 224 |
-
<script>
|
| 225 |
-
/* Module-independent safety net + dead-man timer. Runs even if the ES module below never
|
| 226 |
-
loads/executes (old browser, blocked importmap, strict CSP). If after 10s there is still no
|
| 227 |
-
<canvas> in the stage and the honest #fallback isn't already shown, reveal an honest message
|
| 228 |
-
+ a 2D-console escape hatch — so the stage is never an unexplained black void. */
|
| 229 |
-
(function () {
|
| 230 |
-
window.__SZL3D_BOOTED__ = false;
|
| 231 |
-
var run = function () {
|
| 232 |
-
setTimeout(function () {
|
| 233 |
-
try {
|
| 234 |
-
var stage = document.getElementById("stage");
|
| 235 |
-
var hasCanvas = stage && stage.querySelector("canvas");
|
| 236 |
-
var boot = document.getElementById("booting");
|
| 237 |
-
var fb = document.getElementById("fallback");
|
| 238 |
-
var fbShown = fb && getComputedStyle(fb).display !== "none";
|
| 239 |
-
if (!window.__SZL3D_BOOTED__ && !hasCanvas && !fbShown && fb) {
|
| 240 |
-
if (boot) boot.setAttribute("hidden", "");
|
| 241 |
-
fb.innerHTML =
|
| 242 |
-
"The holographic estate couldn't start in this browser.<br>" +
|
| 243 |
-
"It needs WebGL2 — available in most current desktop and mobile browsers " +
|
| 244 |
-
"(try updating, or open it on a laptop). Every value is still available as JSON at " +
|
| 245 |
-
"the per-surface endpoints, and the <a href=\"/atlas\">2D console</a> works everywhere.";
|
| 246 |
-
fb.style.display = "flex";
|
| 247 |
-
}
|
| 248 |
-
} catch (e) {}
|
| 249 |
-
}, 10000);
|
| 250 |
-
};
|
| 251 |
-
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", run);
|
| 252 |
-
else run();
|
| 253 |
-
})();
|
| 254 |
-
</script>
|
| 255 |
-
|
| 256 |
<script type="module">
|
| 257 |
import szl3d from "/static/3d/szl3d/szl3d_boot.js";
|
| 258 |
import * as live from "/static/3d/szl3d/szl3d_live.js";
|
|
@@ -375,7 +328,10 @@ const SURFACES = [
|
|
| 375 |
{ id: "brainhealth", cat: "brain", title: "Brain Health · live \"can the brain be trusted for this query right now?\" rollup · reads each brain-honesty surface's OWN label VERBATIM (grounding, freshness, provenance, contradiction, uncertainty), degrades absent siblings to UNAVAILABLE → TRUSTWORTHY/DEGRADED/UNTRUSTWORTHY/INSUFFICIENT-SIGNAL, never TRUSTWORTHY if any available component abstains, unsigned SHA-256 receipt-on-write (MODELED)", mod: "/static/3d/surfaces/brainhealth.js" },
|
| 376 |
{ id: "brainwatch", cat: "brain", title: "Brain Watch · knowledge-graph honesty-posture drift monitor · MEASURED label-distribution/orphan/community/salience snapshot vs a caller-supplied PRIOR → STABLE/DRIFTING/DEGRADED/BASELINE-ONLY (no fabricated trend without a real prior), unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainwatch.js" },
|
| 377 |
{ id: "brainconsensus", cat: "brain", title: "Brain Consensus · honest corroboration of a brain grounding · measures how MANY distinct nodes support a query and how many distinct communities they span (cross-community agreement is stronger than one clique) → CORROBORATED/WEAK-CORROBORATION/SINGLE-SOURCE, single-source-risk flag when support collapses to one node/community, never CORROBORATED while that flag is set, MODELED corroboration honesty not a truth guarantee, unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainconsensus.js" },
|
| 378 |
-
{ id: "
|
|
|
|
|
|
|
|
|
|
| 379 |
];
|
| 380 |
|
| 381 |
const stageEl = document.getElementById("stage");
|
|
@@ -403,10 +359,7 @@ async function ensureStage() {
|
|
| 403 |
document.getElementById("backend").textContent = "renderer: " + stage.backend.toUpperCase();
|
| 404 |
_syncGpuToggle();
|
| 405 |
stage.start();
|
| 406 |
-
window.__SZL3D_BOOTED__ = true;
|
| 407 |
-
const _boot = document.getElementById("booting"); if (_boot) _boot.setAttribute("hidden", "");
|
| 408 |
} catch (e) {
|
| 409 |
-
const _boot = document.getElementById("booting"); if (_boot) _boot.setAttribute("hidden", "");
|
| 410 |
fallbackEl.style.display = "flex";
|
| 411 |
document.getElementById("backend").textContent = "renderer: UNAVAILABLE";
|
| 412 |
console.error("[holographic] stage boot failed:", e);
|
|
@@ -490,7 +443,6 @@ async function selectSurface(id) {
|
|
| 490 |
// rendered; any surface without a `cat` falls to "more" (safety net — never dropped).
|
| 491 |
const CATS = [
|
| 492 |
["map", "Map"],
|
| 493 |
-
["finance", "Markets & Finance"],
|
| 494 |
["brain", "Brain & Memory"],
|
| 495 |
["anatomy", "Anatomy & Living System"],
|
| 496 |
["reasoning", "Reasoning & Agents"],
|
|
@@ -511,35 +463,20 @@ const catPanels = document.getElementById("cat-panels");
|
|
| 511 |
const _panels = {}; // cat -> drop-panel element
|
| 512 |
const _catBtns = {}; // cat -> dropdown button
|
| 513 |
|
| 514 |
-
// Display label = the head of the honest title (everything before the first descriptor
|
| 515 |
-
// separator · — ( or " - "), capped at 30 chars. The FULL verbatim title stays as the hover
|
| 516 |
-
// tooltip (b.title), so no honesty is lost — this only stops paragraph-length titles from
|
| 517 |
-
// building a wall of text across the nav.
|
| 518 |
-
function shortLabel(s) {
|
| 519 |
-
if (s.short) return s.short;
|
| 520 |
-
let t = String(s.title || s.id);
|
| 521 |
-
const cut = t.search(/\s[\u00b7\u2014]\s|\s[\u00b7\u2014]|\s\(|\s[-\u2013]\s/);
|
| 522 |
-
if (cut > 0) t = t.slice(0, cut);
|
| 523 |
-
t = t.trim();
|
| 524 |
-
return t.length > 30 ? t.slice(0, 29).trim() + "\u2026" : t;
|
| 525 |
-
}
|
| 526 |
-
|
| 527 |
function _makeTab(s, feat) {
|
| 528 |
const b = document.createElement("button");
|
| 529 |
b.className = "tab" + (feat ? " tab--feat" : "");
|
| 530 |
b.dataset.id = s.id; b.dataset.cat = catOf(s);
|
| 531 |
b.setAttribute("role", "tab"); b.setAttribute("aria-selected", "false");
|
| 532 |
b.title = s.title;
|
| 533 |
-
const lbl = shortLabel(s);
|
| 534 |
if (feat) {
|
| 535 |
const star = document.createElement("span"); star.className = "star"; star.textContent = "★";
|
| 536 |
-
b.appendChild(star); b.appendChild(document.createTextNode(
|
| 537 |
} else {
|
| 538 |
-
b.textContent =
|
| 539 |
}
|
| 540 |
b.addEventListener("click", () => {
|
| 541 |
selectSurface(s.id);
|
| 542 |
-
openCategory(null, false); // close any open dropdown so the holo is revealed
|
| 543 |
// On the mobile horizontal strips, keep the tapped tab in view.
|
| 544 |
try { b.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" }); } catch (_) {}
|
| 545 |
});
|
|
@@ -555,14 +492,8 @@ function openCategory(cat, open) {
|
|
| 555 |
});
|
| 556 |
}
|
| 557 |
|
| 558 |
-
// (a) pinned Featured row —
|
| 559 |
-
|
| 560 |
-
// (incl. the remaining flag:true ones) is one tap away in its category dropdown.
|
| 561 |
-
const FEATURED_IDS = ["atlas", "markets", "brain", "anatomy_body", "governance", "evalarena", "ecosystem", "brainquery", "sovereign"];
|
| 562 |
-
FEATURED_IDS.forEach((id) => {
|
| 563 |
-
const s = SURFACES.find((x) => x.id === id);
|
| 564 |
-
if (s) featuredRow.appendChild(_makeTab(s, true));
|
| 565 |
-
});
|
| 566 |
|
| 567 |
// (b) category dropdown buttons + their drop panels.
|
| 568 |
CATS.forEach(([cat, label]) => {
|
|
@@ -585,11 +516,6 @@ CATS.forEach(([cat, label]) => {
|
|
| 585 |
_panels[cat] = panel; _catBtns[cat] = btn;
|
| 586 |
});
|
| 587 |
|
| 588 |
-
// Click the 3D stage (or press Esc) to dismiss any open category dropdown, so a floating panel
|
| 589 |
-
// never permanently covers the holo.
|
| 590 |
-
stageEl.addEventListener("pointerdown", () => openCategory(null, false));
|
| 591 |
-
document.addEventListener("keydown", (e) => { if (e.key === "Escape") openCategory(null, false); });
|
| 592 |
-
|
| 593 |
// Mark every tab button carrying `id` as selected (a flagship is in both the Featured row
|
| 594 |
// and its category panel), auto-open the active surface's category, and proof-teal-flag the
|
| 595 |
// owning dropdown button so the current location is always legible.
|
|
@@ -598,8 +524,7 @@ function _highlight(id) {
|
|
| 598 |
const cat = def ? catOf(def) : null;
|
| 599 |
tabsEl.querySelectorAll("[data-id]").forEach((t) =>
|
| 600 |
t.setAttribute("aria-selected", String(t.dataset.id === id)));
|
| 601 |
-
|
| 602 |
-
// panel would cover the holo on load / on every switch. The user opens dropdowns on demand.
|
| 603 |
Object.keys(_catBtns).forEach((c) =>
|
| 604 |
_catBtns[c].setAttribute("data-hasactive", String(c === cat)));
|
| 605 |
}
|
|
|
|
| 35 |
surfaces stand out and the rest group behind one-tap dropdowns. Accents are proof-teal
|
| 36 |
(0x3af4c8) — NEVER purple (doctrine v11 palette). */
|
| 37 |
.tabs{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--line);
|
| 38 |
+
background:#070d15;flex:0 0 auto}
|
| 39 |
.navrow{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
|
| 40 |
.navlabel{font:9.5px ui-monospace,monospace;color:var(--para);letter-spacing:.6px;
|
| 41 |
text-transform:uppercase;opacity:.7;margin-right:2px;flex:0 0 auto}
|
|
|
|
| 59 |
.catbtn[aria-expanded="true"] .caret{transform:rotate(90deg)}
|
| 60 |
.catbtn[data-hasactive="true"]{box-shadow:0 0 0 1px var(--proof) inset}
|
| 61 |
.catbtn .count{font-size:9px;color:var(--para);opacity:.7}
|
| 62 |
+
/* Drop panel: the tabs of the currently-open category (only one open at a time). */
|
| 63 |
+
.panel{display:none;gap:6px;flex-wrap:wrap;padding:8px;margin-top:2px;
|
| 64 |
+
border:1px solid var(--line);border-radius:9px;background:#08111a}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
.panel[data-open="true"]{display:flex}
|
| 66 |
#stage-wrap{position:relative;flex:1 1 auto;min-height:0;width:100%}
|
| 67 |
#stage{position:absolute;inset:0}
|
| 68 |
#stage canvas{display:block;width:100%;height:100%;touch-action:none}
|
| 69 |
#fallback{position:absolute;inset:0;display:none;align-items:center;justify-content:center;
|
| 70 |
+
text-align:center;color:var(--para);padding:24px;font:13px ui-monospace,monospace}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
footer{padding:6px 14px;border-top:1px solid var(--line);flex:0 0 auto;
|
| 72 |
background:#070d15;color:var(--para);font:10.5px ui-monospace,monospace;letter-spacing:.3px;z-index:6;
|
| 73 |
white-space:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
|
|
| 163 |
</nav>
|
| 164 |
<div id="stage-wrap">
|
| 165 |
<div id="stage"></div>
|
|
|
|
| 166 |
<div id="fallback">3D unavailable on this device — the holographic estate needs a WebGL2 or WebGPU canvas.<br>
|
| 167 |
+
All underlying data is still available as JSON at the per-surface endpoints.</div>
|
| 168 |
<!-- Landing overlay (Dev4): coherent honest entry over the live estate. -->
|
| 169 |
<div id="intro" role="dialog" aria-label="a11oy holographic estate — landing overview" hidden>
|
| 170 |
<div class="intro-eyebrow">SZL Holdings · Doctrine v11 · Λ = Conjecture 1</div>
|
|
|
|
| 206 |
<footer>Doctrine v11 · locked = 8 @ c7c0ba17 · Λ = Conjecture 1 · Khipu BFT = Conjecture 2 ·
|
| 207 |
shared szl3d toolkit (Dev0) · every value carries its honesty label · trust < 100%</footer>
|
| 208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
<script type="module">
|
| 210 |
import szl3d from "/static/3d/szl3d/szl3d_boot.js";
|
| 211 |
import * as live from "/static/3d/szl3d/szl3d_live.js";
|
|
|
|
| 328 |
{ id: "brainhealth", cat: "brain", title: "Brain Health · live \"can the brain be trusted for this query right now?\" rollup · reads each brain-honesty surface's OWN label VERBATIM (grounding, freshness, provenance, contradiction, uncertainty), degrades absent siblings to UNAVAILABLE → TRUSTWORTHY/DEGRADED/UNTRUSTWORTHY/INSUFFICIENT-SIGNAL, never TRUSTWORTHY if any available component abstains, unsigned SHA-256 receipt-on-write (MODELED)", mod: "/static/3d/surfaces/brainhealth.js" },
|
| 329 |
{ id: "brainwatch", cat: "brain", title: "Brain Watch · knowledge-graph honesty-posture drift monitor · MEASURED label-distribution/orphan/community/salience snapshot vs a caller-supplied PRIOR → STABLE/DRIFTING/DEGRADED/BASELINE-ONLY (no fabricated trend without a real prior), unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainwatch.js" },
|
| 330 |
{ id: "brainconsensus", cat: "brain", title: "Brain Consensus · honest corroboration of a brain grounding · measures how MANY distinct nodes support a query and how many distinct communities they span (cross-community agreement is stronger than one clique) → CORROBORATED/WEAK-CORROBORATION/SINGLE-SOURCE, single-source-risk flag when support collapses to one node/community, never CORROBORATED while that flag is set, MODELED corroboration honesty not a truth guarantee, unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainconsensus.js" },
|
| 331 |
+
{ id: "brainqueryaudit", cat: "brain", title: "Brain Query Audit · append-only hash-linked ledger of brain queries + the honest verdict each returned · POST appends {query, verdict, grounding_label} and mints an UNSIGNED SHA-256 receipt chained to the prior entry (tamper-evident); GET recomputes the whole chain → CHAIN-INTACT/CHAIN-BROKEN (never softened), ephemeral in-memory ledger labelled honestly, MODELED, receipt-on-write-not-on-read", mod: "/static/3d/surfaces/brainqueryaudit.js" },
|
| 332 |
+
{ id: "brainlineage", cat: "brain", title: "Brain Lineage · node-origin chain · how each knowledge-graph node ENTERED the graph, read VERBATIM from its OWN real origin fields (source/url → structural derivation → none) → TRACED/PARTIAL-LINEAGE/UNKNOWN-ORIGIN, a node with no source is UNKNOWN-ORIGIN never a fabricated source, aggregate never TRACED while any origin UNKNOWN, unsigned SHA-256 receipt-on-write (node-origin lineage, NOT per-answer provenance, NOT build/model attestation)", mod: "/static/3d/surfaces/brainlineage.js" },
|
| 333 |
+
{ id: "brainexplain", cat: "brain", title: "Brain Explain · transparent explanation of WHY the brain retrieved what it did · MODELED descriptive trace over the REAL retrieval subgraph (which query terms matched which seed nodes, per-node ppr-vs-salience rationale, communities traversed, each node's OWN label VERBATIM) → EXPLAINABLE/PARTIALLY-EXPLAINABLE/OPAQUE (never invents a rationale; honest OPAQUE beats a fake one), unsigned SHA-256 receipt-on-write", mod: "/static/3d/surfaces/brainexplain.js" },
|
| 334 |
+
{ id: "braingaps", cat: "brain", title: "Brain Gaps · an honest map of what the brain does NOT know · MEASURED thin (sparse) communities + weakly-connected island nodes (degree≤1) + weak-label share over the live graph, and per-query COVERED/THIN/GAP grounding → estate verdict WELL-COVERED/PATCHY/SPARSE (a GAP is never fabricated into coverage), unsigned SHA-256 receipt-on-write (MODELED)", mod: "/static/3d/surfaces/braingaps.js" },
|
| 335 |
];
|
| 336 |
|
| 337 |
const stageEl = document.getElementById("stage");
|
|
|
|
| 359 |
document.getElementById("backend").textContent = "renderer: " + stage.backend.toUpperCase();
|
| 360 |
_syncGpuToggle();
|
| 361 |
stage.start();
|
|
|
|
|
|
|
| 362 |
} catch (e) {
|
|
|
|
| 363 |
fallbackEl.style.display = "flex";
|
| 364 |
document.getElementById("backend").textContent = "renderer: UNAVAILABLE";
|
| 365 |
console.error("[holographic] stage boot failed:", e);
|
|
|
|
| 443 |
// rendered; any surface without a `cat` falls to "more" (safety net — never dropped).
|
| 444 |
const CATS = [
|
| 445 |
["map", "Map"],
|
|
|
|
| 446 |
["brain", "Brain & Memory"],
|
| 447 |
["anatomy", "Anatomy & Living System"],
|
| 448 |
["reasoning", "Reasoning & Agents"],
|
|
|
|
| 463 |
const _panels = {}; // cat -> drop-panel element
|
| 464 |
const _catBtns = {}; // cat -> dropdown button
|
| 465 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 466 |
function _makeTab(s, feat) {
|
| 467 |
const b = document.createElement("button");
|
| 468 |
b.className = "tab" + (feat ? " tab--feat" : "");
|
| 469 |
b.dataset.id = s.id; b.dataset.cat = catOf(s);
|
| 470 |
b.setAttribute("role", "tab"); b.setAttribute("aria-selected", "false");
|
| 471 |
b.title = s.title;
|
|
|
|
| 472 |
if (feat) {
|
| 473 |
const star = document.createElement("span"); star.className = "star"; star.textContent = "★";
|
| 474 |
+
b.appendChild(star); b.appendChild(document.createTextNode(s.title));
|
| 475 |
} else {
|
| 476 |
+
b.textContent = s.title;
|
| 477 |
}
|
| 478 |
b.addEventListener("click", () => {
|
| 479 |
selectSurface(s.id);
|
|
|
|
| 480 |
// On the mobile horizontal strips, keep the tapped tab in view.
|
| 481 |
try { b.scrollIntoView({ behavior: "smooth", inline: "center", block: "nearest" }); } catch (_) {}
|
| 482 |
});
|
|
|
|
| 492 |
});
|
| 493 |
}
|
| 494 |
|
| 495 |
+
// (a) pinned Featured row — flagships (incl. Atlas), always visible above the dropdowns.
|
| 496 |
+
SURFACES.filter((s) => s.flag).forEach((s) => featuredRow.appendChild(_makeTab(s, true)));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
|
| 498 |
// (b) category dropdown buttons + their drop panels.
|
| 499 |
CATS.forEach(([cat, label]) => {
|
|
|
|
| 516 |
_panels[cat] = panel; _catBtns[cat] = btn;
|
| 517 |
});
|
| 518 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 519 |
// Mark every tab button carrying `id` as selected (a flagship is in both the Featured row
|
| 520 |
// and its category panel), auto-open the active surface's category, and proof-teal-flag the
|
| 521 |
// owning dropdown button so the current location is always legible.
|
|
|
|
| 524 |
const cat = def ? catOf(def) : null;
|
| 525 |
tabsEl.querySelectorAll("[data-id]").forEach((t) =>
|
| 526 |
t.setAttribute("aria-selected", String(t.dataset.id === id)));
|
| 527 |
+
if (cat && _panels[cat]) openCategory(cat, true);
|
|
|
|
| 528 |
Object.keys(_catBtns).forEach((c) =>
|
| 529 |
_catBtns[c].setAttribute("data-hasactive", String(c === cat)));
|
| 530 |
}
|