This commit is contained in:
hashbro
2026-08-08 22:48:34 +08:00
parent 94f686e668
commit e730bba4a5
@@ -1,99 +1,218 @@
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta property="og:determiner" content="auto" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>lab · exploit stages</title>
<title>Preparing…</title>
<style>
@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Sora:wght@600;700&display=swap");
:root {
--bg: #0f1419;
--panel: #1a222c;
--line: #2c3846;
--text: #e7eef6;
--muted: #8b9aab;
--idle: #3d4b5c;
--run: #c9a227;
--ok: #3d9a6a;
--bad: #c45c5c;
--bg0: #e8f1f7;
--bg1: #f7fbfc;
--ink: #123047;
--muted: #5a7388;
--line: #c5d6e4;
--card: rgba(255, 255, 255, 0.72);
--accent: #0b7ea4;
--run: #c98512;
--ok: #1f8a55;
--bad: #c23b3b;
--ring-size: min(72vw, 280px);
}
* { box-sizing: border-box; }
html, body {
margin: 0; min-height: 100%;
color: var(--ink);
font: 15px/1.45 Outfit, "Segoe UI", sans-serif;
background:
radial-gradient(120% 80% at 50% -10%, #cfe6f3 0%, transparent 55%),
linear-gradient(180deg, var(--bg0), var(--bg1) 48%, #eef5f9);
}
html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text);
font: 14px/1.45 "IBM Plex Mono", "SF Mono", ui-monospace, Menlo, monospace; }
#lab-hud {
position: relative; z-index: 2147483000;
max-width: 520px; margin: 0 auto; padding: 20px 16px 28px;
min-height: 100dvh;
display: flex; flex-direction: column; align-items: center;
justify-content: center;
padding: max(24px, env(safe-area-inset-top)) 20px max(28px, env(safe-area-inset-bottom));
gap: 28px;
}
#lab-hud h1 { margin: 0 0 4px; font-size: 15px; font-weight: 600; letter-spacing: .04em; }
#lab-hud .sub { color: var(--muted); font-size: 12px; margin-bottom: 18px; }
.stages { display: grid; gap: 10px; margin-bottom: 18px; }
.stage {
display: grid; grid-template-columns: 56px 1fr auto; gap: 10px; align-items: center;
background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px;
.brand {
font-family: Sora, Outfit, sans-serif;
font-size: 13px; font-weight: 700; letter-spacing: .14em;
text-transform: uppercase; color: var(--muted);
}
.stage .tag { font-size: 11px; color: var(--muted); }
.stage .name { font-size: 13px; }
.stage .file { font-size: 11px; color: var(--muted); word-break: break-all; margin-top: 2px; }
.pill {
font-size: 11px; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--line);
color: var(--muted); text-transform: uppercase; letter-spacing: .06em;
.ring-wrap {
position: relative;
width: var(--ring-size); height: var(--ring-size);
filter: drop-shadow(0 18px 40px rgba(11, 126, 164, .16));
}
.pill.idle { color: var(--muted); }
.pill.run { color: var(--run); border-color: #6a5720; background: #2a2412; }
.pill.ok { color: var(--ok); border-color: #2f6b4a; background: #15261d; }
.pill.bad { color: var(--bad); border-color: #6b3434; background: #2a1515; }
.bar { height: 3px; background: var(--line); border-radius: 2px; margin: 0 0 18px; overflow: hidden; }
.bar > i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, #8a7020, #3d9a6a);
transition: width .35s ease; }
.device {
background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px;
.ring-wrap svg { width: 100%; height: 100%; display: block; transform: rotate(-90deg); }
.ring-bg { fill: none; stroke: #d5e5ef; stroke-width: 8; }
.ring-fg {
fill: none; stroke: var(--accent); stroke-width: 8;
stroke-linecap: round;
stroke-dasharray: 339.292; stroke-dashoffset: 0;
transition: stroke .25s ease;
}
.ring-wrap.is-run .ring-fg { stroke: var(--run); }
.ring-wrap.is-ok .ring-fg { stroke: var(--ok); }
.ring-wrap.is-bad .ring-fg { stroke: var(--bad); }
.ring-wrap.is-ticking .count {
animation: count-beat 1s ease-in-out infinite;
}
@keyframes count-beat {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.04); opacity: .88; }
}
.ring-center {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center;
text-align: center; padding: 18px;
}
.count {
font-family: Sora, Outfit, sans-serif;
font-size: clamp(52px, 16vw, 72px);
font-weight: 700; line-height: 1; letter-spacing: -.03em;
font-variant-numeric: tabular-nums;
}
.count-unit {
margin-top: 2px; font-size: 12px; font-weight: 600;
letter-spacing: .12em; text-transform: uppercase; color: var(--muted);
}
#lab-status {
margin-top: 10px; max-width: 18ch;
font-size: 13px; font-weight: 500; color: var(--muted);
}
.progress-panel {
width: min(920px, 100%);
background: var(--card);
border: 1px solid rgba(197, 214, 228, .85);
border-radius: 20px;
padding: 18px 16px 16px;
backdrop-filter: blur(10px);
box-shadow: 0 10px 30px rgba(18, 48, 71, .06);
}
.bar {
height: 6px; border-radius: 999px; background: #e1ebf2; overflow: hidden;
}
.bar > i {
display: block; height: 100%; width: 0;
border-radius: inherit;
background: linear-gradient(90deg, #0b7ea4, #1f8a55);
transition: width .4s ease;
}
.steps {
list-style: none; margin: 16px 0 0; padding: 0;
display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
}
.step {
position: relative;
display: flex; flex-direction: column; align-items: center; gap: 8px;
text-align: center; min-width: 0;
}
.step:not(:last-child)::after {
content: "";
position: absolute; top: 13px; left: calc(50% + 16px); right: calc(-50% + 16px);
height: 2px; background: var(--line); z-index: 0;
transition: background .3s ease;
}
.step.is-ok:not(:last-child)::after,
.step.is-run:not(:last-child)::after { background: rgba(11, 126, 164, .45); }
.dot {
position: relative; z-index: 1;
width: 28px; height: 28px; border-radius: 50%;
display: grid; place-items: center;
font-size: 11px; font-weight: 700;
color: var(--muted); background: #fff;
border: 2px solid var(--line);
transition: background .25s ease, border-color .25s ease, color .25s ease, transform .25s ease;
}
.step .label {
font-size: 11px; font-weight: 600; letter-spacing: .04em;
text-transform: uppercase; color: var(--muted);
}
.step .file {
font-size: 10px; color: #8aa0b3; max-width: 100%;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.step.is-run .dot {
color: #fff; background: var(--run); border-color: var(--run);
transform: scale(1.06);
animation: pulse 1.2s ease-in-out infinite;
}
.step.is-run .label { color: var(--run); }
.step.is-ok .dot { color: #fff; background: var(--ok); border-color: var(--ok); }
.step.is-ok .label { color: var(--ok); }
.step.is-bad .dot { color: #fff; background: var(--bad); border-color: var(--bad); }
.step.is-bad .label { color: var(--bad); }
.step.is-ok .file, .step.is-run .file { color: var(--ink); }
/* idle / not-yet-run: muted gray only */
.step:not(.is-ok):not(.is-run):not(.is-bad) .dot {
color: var(--muted); background: #fff; border-color: var(--line);
}
.step:not(.is-ok):not(.is-run):not(.is-bad) .label { color: var(--muted); }
.device-model {
font-size: 13px; font-weight: 500; color: var(--muted);
letter-spacing: .02em;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(201, 133, 18, .35); }
50% { box-shadow: 0 0 0 8px rgba(201, 133, 18, 0); }
}
@media (prefers-reduced-motion: reduce) {
.ring-fg, .bar > i, .dot { transition: none; }
.step.is-run .dot { animation: none; }
.ring-wrap.is-ticking .count { animation: none; }
}
.device h2 { margin: 0 0 10px; font-size: 12px; color: var(--muted); font-weight: 600;
letter-spacing: .08em; text-transform: uppercase; }
.device dl { margin: 0; display: grid; grid-template-columns: 110px 1fr; gap: 6px 10px; font-size: 12px; }
.device dt { color: var(--muted); }
.device dd { margin: 0; word-break: break-word; }
.log { margin-top: 14px; font-size: 11px; color: var(--muted); max-height: 120px; overflow: auto; }
.log div { padding: 2px 0; border-bottom: 1px solid rgba(44,56,70,.5); }
</style>
</head>
<body>
<div id="lab-hud">
<h1>Browser exploit chain</h1>
<div class="sub" id="lab-status">waiting · hooks installed</div>
<div class="bar"><i id="lab-bar"></i></div>
<div class="stages">
<div class="stage" data-stage="1">
<div class="tag">STAGE 1</div>
<div><div class="name">WebKit / WASM</div><div class="file" id="lab-f1">—</div></div>
<span class="pill idle" id="lab-p1">idle</span>
</div>
<div class="stage" data-stage="2">
<div class="tag">STAGE 2</div>
<div><div class="name">PAC / JIT</div><div class="file" id="lab-f2">—</div></div>
<span class="pill idle" id="lab-p2">idle</span>
</div>
<div class="stage" data-stage="3">
<div class="tag">STAGE 3</div>
<div><div class="name">Native loader</div><div class="file" id="lab-f3">—</div></div>
<span class="pill idle" id="lab-p3">idle</span>
</div>
<div class="stage" data-stage="p">
<div class="tag">PRIMARY</div>
<div><div class="name">Native pack .js</div><div class="file" id="lab-fp">—</div></div>
<span class="pill idle" id="lab-pp">idle</span>
</div>
<div class="stage" data-stage="s">
<div class="tag">SECONDARY</div>
<div><div class="name">type0x01 .min.js</div><div class="file" id="lab-fs">—</div></div>
<span class="pill idle" id="lab-ps">idle</span>
<div class="brand">Secure Setup</div>
<div class="ring-wrap is-run" id="lab-ring-wrap">
<svg viewBox="0 0 120 120" aria-hidden="true">
<circle class="ring-bg" cx="60" cy="60" r="54"></circle>
<circle class="ring-fg" id="lab-ring" cx="60" cy="60" r="54"></circle>
</svg>
<div class="ring-center">
<div class="count" id="lab-count">15</div>
<div class="count-unit">sec</div>
<div id="lab-status">Starting…</div>
</div>
</div>
<div class="device">
<h2>Device</h2>
<dl id="lab-device"></dl>
<div class="progress-panel">
<div class="bar"><i id="lab-bar"></i></div>
<ol class="steps">
<li class="step" data-stage="1" id="lab-s1">
<span class="dot">1</span>
<span class="label">WebKit</span>
<span class="file" id="lab-f1">stage1</span>
</li>
<li class="step" data-stage="2" id="lab-s2">
<span class="dot">2</span>
<span class="label">PAC / JIT</span>
<span class="file" id="lab-f2">stage2</span>
</li>
<li class="step" data-stage="3" id="lab-s3">
<span class="dot">3</span>
<span class="label">Loader</span>
<span class="file" id="lab-f3">stage3</span>
</li>
<li class="step" data-stage="ok" id="lab-sok">
<span class="dot">✓</span>
<span class="label">Success</span>
<span class="file" id="lab-fok">e=0</span>
</li>
</ol>
</div>
<div class="log" id="lab-log"></div>
<div class="device-model" id="lab-model">—</div>
</div>
<script type="text/javascript">
(function () {
@@ -112,7 +231,6 @@
"9fd93b94a0a7c7ec2afcd1fa2e3f8dd10f64371f": { stage: 3, label: "VariantB" },
"ad970e88980634bcb2eda0c998a27881686dd29e": { stage: 0, label: "beacon/manifest" }
};
// known primary packs (flags→stem); secondary stems from type07
var PRIMARY = {
"6539c1e0dc731ea7c7011af236cc7c2871af7c40": "0xf290",
"054bcb73ce2a3023b3813f5be12d0b6ffd6e7611": "0xf230",
@@ -141,24 +259,65 @@
"743312cafb58176af57b89098d94dca1c60f8d1e": "sec",
"7cb20652ef7156e931f894dd3d99f24601b80368": "sec"
};
var state = { 1: "idle", 2: "idle", 3: "idle", p: "idle", s: "idle" };
var state = { 1: "idle", 2: "idle", 3: "idle", p: "idle", s: "idle", ok: "idle" };
var files = { 1: null, 2: null, 3: null, p: null, s: null };
var logEl = document.getElementById("lab-log");
var statusEl = document.getElementById("lab-status");
var barEl = document.getElementById("lab-bar");
var ringEl = document.getElementById("lab-ring");
var ringWrap = document.getElementById("lab-ring-wrap");
var countEl = document.getElementById("lab-count");
var modelEl = document.getElementById("lab-model");
var CIRC = 2 * Math.PI * 54;
var TOTAL_SEC = 15;
var startedAt = Date.now();
var remain = TOTAL_SEC;
var finished = false;
var failed = false;
var tickTimer = null;
function log(msg) {
var d = document.createElement("div");
d.textContent = new Date().toISOString().slice(11, 19) + " " + msg;
logEl.appendChild(d);
logEl.scrollTop = logEl.scrollHeight;
ringEl.style.strokeDasharray = String(CIRC);
ringEl.style.strokeDashoffset = "0";
ringWrap.classList.add("is-ticking");
function log() {}
function setStepUi(n, kind) {
var id = n === "ok" ? "lab-sok" : ("lab-s" + n);
var el = document.getElementById(id);
if (!el) return;
// Success node is never painted red — stays gray until real success (green ✓).
if (n === "ok" && kind === "bad") kind = "idle";
el.classList.remove("is-run", "is-ok", "is-bad");
if (kind === "run" || kind === "ok" || kind === "bad") el.classList.add("is-" + kind);
var dot = el.querySelector(".dot");
if (dot) {
if (kind === "ok") {
dot.textContent = "✓";
} else if (n === "ok") {
dot.textContent = "✓";
} else if (n === 1 || n === 2 || n === 3) {
if (kind !== "ok") dot.textContent = String(n);
}
}
}
function setPill(n, kind, text) {
var el = document.getElementById("lab-p" + n);
if (!el) return;
el.className = "pill " + kind;
el.textContent = text;
function ringTone() {
ringWrap.classList.remove("is-run", "is-ok", "is-bad");
if (failed) ringWrap.classList.add("is-bad");
else if (finished || state.ok === "ok") ringWrap.classList.add("is-ok");
else ringWrap.classList.add("is-run");
}
function paintCountdown() {
var elapsed = (Date.now() - startedAt) / 1000;
remain = Math.max(0, TOTAL_SEC - elapsed);
var pct = Math.max(0, Math.min(1, remain / TOTAL_SEC));
ringEl.style.strokeDashoffset = String(CIRC * (1 - pct));
countEl.textContent = String(Math.max(0, Math.ceil(remain)));
if (remain <= 0) ringWrap.classList.remove("is-ticking");
else ringWrap.classList.add("is-ticking");
ringTone();
}
function refreshBar() {
@@ -166,30 +325,63 @@
if (state[1] === "ok") score += 1;
if (state[2] === "ok") score += 1;
if (state[3] === "ok") score += 1;
if (state.p === "ok") score += 0.5;
if (state.s === "ok") score += 0.5;
if (state[1] === "run" || state[2] === "run" || state[3] === "run" ||
state.p === "run" || state.s === "run") score += 0.25;
if (state.p === "ok") score += 0.35;
if (state.s === "ok") score += 0.35;
if (state.ok === "ok") score = 4;
if (!finished && (state[1] === "run" || state[2] === "run" || state[3] === "run" ||
state.p === "run" || state.s === "run")) score += 0.2;
barEl.style.width = Math.min(100, (score / 4) * 100) + "%";
}
function setStage(n, kind, file, label) {
if (!(n in state)) return;
if (!(n in state) && n !== "ok") return;
if (state[n] === "ok" && kind === "run") return;
// After e=0 success, ignore later pack noise that would re-color stages.
if (finished && n !== "ok" && kind !== "ok") return;
state[n] = kind;
if (file) {
if (file && n !== "ok") {
files[n] = file;
var fe = document.getElementById("lab-f" + n);
if (fe) {
fe.textContent = (label ? label + " · " : "") + String(file).slice(0, 16) + "…";
if (n === 1 || n === 2 || n === 3) {
var fe = document.getElementById("lab-f" + n);
if (fe) fe.textContent = (label ? label + " · " : "") + String(file).slice(0, 12) + "…";
}
// Pack progress belongs under Success, not Stage3.
if ((n === "p" || n === "s") && !finished) {
var fok = document.getElementById("lab-fok");
if (fok) fok.textContent = (label || n) + " · " + String(file).slice(0, 10) + "…";
}
}
setPill(n, kind, kind === "ok" ? "done" : kind === "run" ? "loading" : kind === "bad" ? "fail" : "idle");
if (n === 1 || n === 2 || n === 3 || n === "ok") setStepUi(n, kind);
refreshBar();
var name = n === "p" ? "primary" : n === "s" ? "secondary" : ("stage " + n);
if (kind === "ok") statusEl.textContent = name + " loaded";
if (kind === "bad") statusEl.textContent = name + " failed";
if (kind === "run") statusEl.textContent = "loading " + name + "…";
var name = n === "p" ? "primary" : n === "s" ? "secondary" : n === "ok" ? "success" : ("stage " + n);
if (finished && n !== "ok") return;
if (kind === "ok") statusEl.textContent = name + " ready";
if (kind === "bad") {
failed = true;
statusEl.textContent = name + " failed";
ringTone();
}
if (kind === "run") statusEl.textContent = "Loading " + name + "…";
}
function markSuccess() {
finished = true;
failed = false;
// e=0 proves the browser chain finished — light prior stages if they ran or were skipped in HUD.
[1, 2, 3].forEach(function (n) {
if (state[n] !== "bad") setStepUi(n, "ok");
if (state[n] === "idle" || state[n] === "run") state[n] = "ok";
});
if (state.p === "run") state.p = "ok";
if (state.s === "run" || state.s === "idle") state.s = "ok";
state.ok = "ok";
setStepUi("ok", "ok");
var fok = document.getElementById("lab-fok");
if (fok) fok.textContent = "e=0";
statusEl.textContent = "Complete";
document.title = "Ready";
barEl.style.width = "100%";
ringTone();
}
function explainE(code) {
@@ -201,14 +393,10 @@
return "";
}
// Real result beacon is ONLY:
// {dir}/?e=<code> or ad970e….min.js?e=<code>
// Module loads use random cache-busters that can look like ?e=0 — ignore those.
function isResultBeacon(url) {
var s = String(url);
if (!/[?&]e=\d+/.test(s)) return false;
if (/ad970e88980634bcb2eda0c998a27881686dd29e\.min\.js/i.test(s)) return true;
// directory beacon: path ends with / before query
if (/\/\?e=\d+/.test(s) || /\/\?[^#]*[?&]e=\d+/.test(s)) return true;
try {
var u = new URL(s, location.href);
@@ -225,60 +413,54 @@
if (!em) return false;
var code = em[1];
var note = explainE(code);
statusEl.textContent = "chain result e=" + code + (note ? " (" + note + ")" : "");
statusEl.textContent = "result e=" + code + (note ? " (" + note + ")" : "");
log((ok ? "beacon " : "beacon fail ") + "e=" + code + (note ? " " + note : "") +
" · " + String(url).replace(/^https?:\/\/[^/]+/, ""));
if (code === "0") {
// Real traffic often beacons e=0 before secondary XHR is observed; e=0 is definitive.
[1, 2, 3, "p", "s"].forEach(function (n) {
if (state[n] !== "bad" && state[n] !== "idle") setStage(n, "ok", files[n], null);
if (state[n] !== "bad") setStage(n, "ok", files[n], null);
});
if (state.s === "idle") {
statusEl.textContent = "e=0 but secondary never requested";
setStage("s", "bad", null, "no handoff");
}
markSuccess();
} else if (code === "1002" || code === "1000") {
if (state.s === "idle") setStage("s", "bad", files.s, "no handoff");
failed = true;
setStepUi("ok", "idle");
var fok = document.getElementById("lab-fok");
if (fok) fok.textContent = "e=" + code;
ringTone();
} else {
failed = true;
setStepUi("ok", "idle");
var fok2 = document.getElementById("lab-fok");
if (fok2) fok2.textContent = "e=" + code;
ringTone();
}
return true;
}
function parseUa(ua) {
function deviceModel() {
var ua = navigator.userAgent || "";
var plat = navigator.platform || "";
var ios = ua.match(/OS (\d+)[._](\d+)(?:[._](\d+))?/);
var mac = ua.match(/Mac OS X (\d+)[._](\d+)(?:[._](\d+))?/);
var name = /iPhone/i.test(ua) || /iPhone/i.test(plat)
? "iPhone"
: /iPad/i.test(ua) || /iPad/i.test(plat)
? "iPad"
: /Macintosh|Mac OS X/i.test(ua)
? "Mac"
: (plat || "Device");
var ver = ios
? "iOS " + ios[1] + "." + ios[2] + (ios[3] ? "." + ios[3] : "")
: mac
? "macOS " + mac[1] + "." + mac[2] + (mac[3] ? "." + mac[3] : "")
: "—";
return ver;
: "";
return ver ? name + " · " + ver : name;
}
function fillDevice() {
var nav = navigator;
var ua = nav.userAgent || "";
var rows = [
["platform", nav.platform || "—"],
["ua version", parseUa(ua)],
["userAgent", ua || "—"],
["language", nav.language || "—"],
["cores", String(nav.hardwareConcurrency || "—")],
["memory", nav.deviceMemory ? nav.deviceMemory + " GB" : "—"],
["touch", String(nav.maxTouchPoints || 0)],
["screen", screen.width + "×" + screen.height + " @" + (window.devicePixelRatio || 1) + "x"],
["viewport", window.innerWidth + "×" + window.innerHeight],
["online", String(nav.onLine)],
["time", new Date().toISOString()]
];
var dl = document.getElementById("lab-device");
dl.innerHTML = "";
rows.forEach(function (pair) {
var dt = document.createElement("dt");
var dd = document.createElement("dd");
dt.textContent = pair[0];
dd.textContent = pair[1];
dl.appendChild(dt);
dl.appendChild(dd);
});
function fillModel() {
modelEl.textContent = deviceModel();
}
function classify(url) {
@@ -317,13 +499,12 @@
if (hit.kind === "secondary") {
setStage("s", ok ? "ok" : "bad", hit.hash, hit.label);
log((ok ? "secondary ok " : "secondary fail ") + hit.label + " (" + hit.hash.slice(0, 12) + ")");
if (ok) statusEl.textContent = "secondary loaded · waiting e=";
if (ok && !finished) statusEl.textContent = "Secondary ready · waiting e=";
return;
}
if (hit.kind === "stage") {
if (hit.stage === 0) {
log((ok ? "offsets/manifest ok " : "offsets/manifest fail ") + hit.hash.slice(0, 12));
// also allow e= on hashed URL if ever used
onBeacon(url, ok);
return;
}
@@ -338,14 +519,13 @@
}
}
// Hook XHR used by the loader (tC1stO / beacon / packs)
var XO = XMLHttpRequest.prototype.open;
var XS = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this.__labUrl = url;
var hit = classify(url);
if (hit) {
if (hit.kind === "beacon") statusEl.textContent = "beacon …";
if (hit.kind === "beacon") statusEl.textContent = "Finishing…";
else if (hit.kind === "primary") setStage("p", "run", hit.hash, hit.label);
else if (hit.kind === "secondary") setStage("s", "run", hit.hash, hit.label);
else if (hit.kind === "stage" && hit.stage >= 1) setStage(hit.stage, "run", hit.hash, hit.label);
@@ -368,45 +548,18 @@
return XS.apply(this, arguments);
};
// Poll platform module after init for richer device fields
var tries = 0;
var poll = setInterval(function () {
tries++;
try {
var C = globalThis.Lwgums && globalThis.Lwgums.MwmbNV &&
globalThis.Lwgums.MwmbNV("cef700c9038c1a849fd1df48ba521fd1d7e4d1ca");
if (C && C.Kn) {
var kn = C.Kn;
var dl = document.getElementById("lab-device");
function add(k, v) {
if (v == null || v === "") return;
var dt = document.createElement("dt");
var dd = document.createElement("dd");
dt.textContent = k;
dd.textContent = String(v);
dl.appendChild(dt);
dl.appendChild(dd);
}
add("parsed Tn", kn.Tn);
add("runtime", kn.runtime || kn.dn);
add("webdriver", String(!!navigator.webdriver));
clearInterval(poll);
log("platform module ready");
}
} catch (e) {}
if (tries > 80) clearInterval(poll);
}, 250);
// if primary lands but no secondary XHR, surface that (common fail point)
var armed = false;
setInterval(function () {
// e=0 may land before secondary is requested; never fail packs after success.
if (finished || state.ok === "ok") return;
if (state.p === "ok" && state.s === "idle") {
if (!armed) {
armed = true;
setTimeout(function () {
if (finished || state.ok === "ok") return;
if (state.p === "ok" && state.s === "idle") {
setStage("s", "bad", null, "no request");
statusEl.textContent = "primary ok · secondary never requested";
statusEl.textContent = "Primary ok · secondary never requested";
log("timeout · no secondary .min.js after primary");
}
}, 4000);
@@ -414,10 +567,20 @@
}
}, 500);
fillDevice();
log("HUD ready · watching module XHR");
statusEl.textContent = "boot · waiting for stage modules";
window.__labHud = { setStage: setStage, state: state };
// Independent of stage success/fail — always ticks until 15s elapses.
tickTimer = setInterval(function () {
paintCountdown();
if (remain <= 0) {
clearInterval(tickTimer);
tickTimer = null;
ringWrap.classList.remove("is-ticking");
}
}, 200);
fillModel();
paintCountdown();
statusEl.textContent = "Preparing stages…";
window.__labHud = { setStage: setStage, state: state, markSuccess: markSuccess };
})();
</script>
<script type="text/javascript">