Files
coruna-lab/channel-builder/source/templates/support/test.html
T
2026-10-07 05:19:52 +08:00

589 lines
22 KiB
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>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 {
--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);
}
#lab-hud {
position: relative; z-index: 2147483000;
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;
}
.brand {
font-family: Sora, Outfit, sans-serif;
font-size: 13px; font-weight: 700; letter-spacing: .14em;
text-transform: uppercase; color: var(--muted);
}
.ring-wrap {
position: relative;
width: var(--ring-size); height: var(--ring-size);
filter: drop-shadow(0 18px 40px rgba(11, 126, 164, .16));
}
.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; }
}
</style>
</head>
<body>
<div id="lab-hud">
<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="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="device-model" id="lab-model">—</div>
</div>
<script type="text/javascript">
(function () {
var STAGE_MAP = {
"98f0c8fb182309faa687aa849e92d0ac5f93af7d": { stage: 1, label: "jacurutu" },
"700491384cc59bd25c3aa4dd670c8660963bffe3": { stage: 1, label: "bluebird" },
"3c04ae31f9ba8f809b275be4b3fa93deb558902c": { stage: 1, label: "terrorbird" },
"1c5bd923f56ca7fcf2cfa695bc0d54b6a2c849bf": { stage: 1, label: "cassowary" },
"40a27e7916aa554e6d38d39beb6bb7ee095692ed": { stage: 1, label: "buffout" },
"9075c25766e57019db4c86fac179b03ebf1b56e5": { stage: 2, label: "breezy" },
"b099ff22b5c8e65654744fd307d81ad208009103": { stage: 2, label: "breezy15" },
"651774047bf8d72258a5f04785c9dabf5e793670": { stage: 2, label: "seedbell_pre" },
"291b914c574e1196039313595217367c44cca436": { stage: 2, label: "seedbell_16.6" },
"0f2be2a4e0ab7e60b6ce550692996d079a5769a0": { stage: 2, label: "seedbell_17" },
"0c297489d8c9d5470bfce17b0d99da3338b44a18": { stage: 3, label: "VariantA" },
"9fd93b94a0a7c7ec2afcd1fa2e3f8dd10f64371f": { stage: 3, label: "VariantB" },
"ad970e88980634bcb2eda0c998a27881686dd29e": { stage: 0, label: "beacon/manifest" }
};
var PRIMARY = {
"6539c1e0dc731ea7c7011af236cc7c2871af7c40": "0xf290",
"054bcb73ce2a3023b3813f5be12d0b6ffd6e7611": "0xf230",
"e406714e92671b5218496fcb6666734411cb2320": "0xf330",
"694c829e379e12085de6158b85f32509f54f4796": "0xf240",
"3b0133801a3f844e7ebafa0363f2423a50005b72": "0xf340",
"6f8a7a3bc74d9c65f5463a6a29d4e2c52feefcca": "0xf270",
"eb3e81b54e8763bfe505e7a18be8f5fd828a76f6": "0xf370",
"6bbb364c8a423374d42a2cbc45c0dee84e7dc710": "0xf280",
"99010a27e08b3312650c8d9f321958433e577a30": "0xf380",
"c9118a62558ed444a64c2dfe350c6c57fa277a3a": "0xf390",
"076de672aebfc78137aa863e51ff3d8980dcdd10": "0xf373",
"62415a3d105a8c40c41b19cf456e8474fe441359": "0xf383",
"a5847c3e2e439e2f7c4b1582932cf81a06100981": "0xf275",
"f7994d47ee03dfb33e0fc7df94c8a215ff8fe66a": "0xf375"
};
var SECONDARY = {
"65704c0722165a7bdedad3f3f61258b2f95470f6": "groupA",
"7f208248c748f97956fe4a7cf246c91235852e67": "groupB",
"039c68f0ca742a85e94516818385a9eca2e204d8": "sec",
"1d0df5a0a12a20aa8b0c8aeb660742268f311d19": "sec",
"242a0afb1d88b83e9a1a5b570fed6778def892fc": "sec",
"347367155da44f3efcc9053337913061079610b9": "sec",
"630c2b42300333d91588353d43afab9ec8325e09": "sec",
"6bac8b93b6f97ddd8a1f86fecfa6431b9ffeb9fb": "sec",
"743312cafb58176af57b89098d94dca1c60f8d1e": "sec",
"7cb20652ef7156e931f894dd3d99f24601b80368": "sec"
};
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 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;
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 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() {
var score = 0;
if (state[1] === "ok") score += 1;
if (state[2] === "ok") score += 1;
if (state[3] === "ok") score += 1;
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) && 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 && n !== "ok") {
files[n] = file;
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) + "…";
}
}
if (n === 1 || n === 2 || n === 3 || n === "ok") setStepUi(n, kind);
refreshBar();
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) {
if (code === "0") return "ok";
if (code === "1000") return "exception";
if (code === "1001") return "unsupported";
if (code === "1002") return "stage3/native fail";
if (code === "1003") return "gate fail";
return "";
}
function isResultBeacon(url) {
var s = String(url);
if (!/[?&]e=\d+/.test(s)) return false;
if (/ad970e88980634bcb2eda0c998a27881686dd29e\.min\.js/i.test(s)) return true;
if (/\/\?e=\d+/.test(s) || /\/\?[^#]*[?&]e=\d+/.test(s)) return true;
try {
var u = new URL(s, location.href);
var path = u.pathname || "";
if (/\/$/.test(path) && u.searchParams.has("e")) return true;
if (!/\.js$/i.test(path) && u.searchParams.has("e")) return true;
} catch (err) {}
return false;
}
function onBeacon(url, ok) {
if (!isResultBeacon(url)) return false;
var em = String(url).match(/[?&]e=(\d+)/);
if (!em) return false;
var code = em[1];
var note = explainE(code);
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") setStage(n, "ok", files[n], null);
});
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 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 ? name + " · " + ver : name;
}
function fillModel() {
modelEl.textContent = deviceModel();
}
function classify(url) {
if (!url) return null;
var s = String(url);
if (isResultBeacon(s)) return { kind: "beacon", url: s };
var min = s.match(/([0-9a-f]{40})\.min\.js/i);
if (min) {
var sh = min[1].toLowerCase();
if (SECONDARY[sh]) return { kind: "secondary", hash: sh, label: SECONDARY[sh] };
if (PRIMARY[sh]) return { kind: "primary", hash: sh, label: PRIMARY[sh] };
return { kind: "secondary", hash: sh, label: "min.js" };
}
var m = s.match(/([0-9a-f]{40})\.js/i);
if (!m) return null;
var hash = m[1].toLowerCase();
if (PRIMARY[hash]) return { kind: "primary", hash: hash, label: PRIMARY[hash] };
if (SECONDARY[hash]) return { kind: "secondary", hash: hash, label: SECONDARY[hash] };
var info = STAGE_MAP[hash];
if (info) return { kind: "stage", stage: info.stage, hash: hash, label: info.label };
return null;
}
function onModule(url, ok) {
var hit = classify(url);
if (!hit) return;
if (hit.kind === "beacon") {
onBeacon(url, ok);
return;
}
if (hit.kind === "primary") {
setStage("p", ok ? "ok" : "bad", hit.hash, hit.label);
log((ok ? "primary ok " : "primary fail ") + hit.label + " (" + hit.hash.slice(0, 12) + ")");
return;
}
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 && !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));
onBeacon(url, ok);
return;
}
setStage(hit.stage, ok ? "ok" : "bad", hit.hash, hit.label);
log((ok ? "loaded " : "failed ") + "stage" + hit.stage + " " + hit.label +
" (" + hit.hash.slice(0, 12) + ")");
if (ok && hit.stage === 2 && state[1] === "idle") setStage(1, "ok", files[1], null);
if (ok && hit.stage === 3) {
if (state[1] === "idle") setStage(1, "ok", files[1], null);
if (state[2] === "idle") setStage(2, "ok", files[2], null);
}
}
}
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 = "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);
}
return XO.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function () {
var xhr = this;
xhr.addEventListener("loadend", function () {
var ok = xhr.status === 200 || xhr.status === 0;
if (xhr.status === 0 && xhr.response != null) ok = true;
if (xhr.status >= 400) ok = false;
var u = xhr.__labUrl;
if (u && isResultBeacon(u)) {
onBeacon(u, true);
return;
}
onModule(u, ok && xhr.status !== 404);
});
return XS.apply(this, arguments);
};
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";
log("timeout · no secondary .min.js after primary");
}
}, 4000);
}
}
}, 500);
// 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 src="index.js"></script>
</body>
</html>