133 lines
4.7 KiB
HTML
133 lines
4.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<title>YrXtls</title>
|
|
<style>
|
|
html, body {
|
|
margin: 0;
|
|
padding: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: transparent;
|
|
overflow: hidden;
|
|
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
color: #eee;
|
|
}
|
|
html { height: 100%; }
|
|
body { height: 100%; }
|
|
#stage {
|
|
display: block;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
#status {
|
|
position: fixed;
|
|
top: 12px;
|
|
left: 12px;
|
|
right: 12px;
|
|
font-size: 12px;
|
|
white-space: pre-wrap;
|
|
line-height: 1.5;
|
|
opacity: 0.85;
|
|
pointer-events: none;
|
|
}
|
|
#status.ok { color: #6f6; }
|
|
#status.warn { color: #fc6; }
|
|
#status.err { color: #f66; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="stage"></canvas>
|
|
<pre id="status">booting...</pre>
|
|
<script type="module">
|
|
// these two strings get filled in by scripts/web/build.sh; the unbuilt template ships them empty so editors don't choke on multi-MB lines.
|
|
const __JS_B64 = "__JS_BASE64__";
|
|
const __WASM_B64 = "__WASM_BASE64__";
|
|
|
|
const status = document.getElementById("status");
|
|
const log = (msg, cls = "") => {
|
|
status.textContent += "\n" + msg;
|
|
if (cls) status.className = cls;
|
|
console.log("[yrxtls]", msg);
|
|
};
|
|
|
|
function sizeCanvas() {
|
|
const canvas = document.getElementById("stage");
|
|
const dpr = window.devicePixelRatio || 1;
|
|
const w = window.innerWidth;
|
|
const h = window.innerHeight;
|
|
canvas.style.width = w + "px";
|
|
canvas.style.height = h + "px";
|
|
canvas.width = Math.max(1, Math.floor(w * dpr));
|
|
canvas.height = Math.max(1, Math.floor(h * dpr));
|
|
return [canvas.width, canvas.height];
|
|
}
|
|
|
|
function b64ToBytes(b64) {
|
|
const bin = atob(b64);
|
|
const bytes = new Uint8Array(bin.length);
|
|
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
return bytes;
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
status.textContent = "booting...";
|
|
log(`location: ${location.href}`);
|
|
log(`secure context: ${window.isSecureContext}`);
|
|
log(`navigator.gpu: ${typeof navigator.gpu}`);
|
|
|
|
if (!("gpu" in navigator) || !navigator.gpu) {
|
|
log("navigator.gpu missing -> browser has no WebGPU.", "err");
|
|
return;
|
|
}
|
|
|
|
const adapter = await navigator.gpu.requestAdapter();
|
|
if (!adapter) {
|
|
log("requestAdapter() returned null -> WebGPU rejected.", "err");
|
|
return;
|
|
}
|
|
log(`adapter: ${adapter.info?.vendor ?? "?"} / ${adapter.info?.architecture ?? "?"}`, "ok");
|
|
|
|
const [w, h] = sizeCanvas();
|
|
log(`canvas: ${w}x${h}`);
|
|
|
|
if (!__JS_B64 || !__WASM_B64) {
|
|
log("inlined wasm/js bundles are empty -> this is the unbuilt template, run scripts/web/build.sh.", "err");
|
|
return;
|
|
}
|
|
|
|
log("decoding inlined bundles...");
|
|
const jsBlob = new Blob([b64ToBytes(__JS_B64)], { type: "text/javascript" });
|
|
const jsUrl = URL.createObjectURL(jsBlob);
|
|
const mod = await import(jsUrl);
|
|
URL.revokeObjectURL(jsUrl);
|
|
|
|
await mod.default({ module_or_path: b64ToBytes(__WASM_B64) });
|
|
log("wasm loaded, starting...", "ok");
|
|
|
|
mod.start("#stage");
|
|
|
|
const propagateResize = () => {
|
|
const [w, h] = sizeCanvas();
|
|
mod.resize(w, h);
|
|
};
|
|
// ResizeObserver fires immediately for current size and on every layout change.
|
|
// critical for iframe embeds where the iframe element grows from its 300x150 default to its real size AFTER the inner script first runs.
|
|
const ro = new ResizeObserver(propagateResize);
|
|
ro.observe(document.documentElement);
|
|
window.addEventListener("resize", propagateResize);
|
|
log("running.", "ok");
|
|
} catch (e) {
|
|
log("FATAL: " + (e?.stack ?? e), "err");
|
|
}
|
|
}
|
|
|
|
main();
|
|
</script>
|
|
</body>
|
|
</html>
|