YrXtals/web/index.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>