diff --git a/src/pages/cannons.astro b/src/pages/cannons.astro index 418f3e8..50e19f6 100644 --- a/src/pages/cannons.astro +++ b/src/pages/cannons.astro @@ -48,14 +48,18 @@ const overTheWire = build ? mb(build.wireBytes) : null; { build ? ( -
- + {/* Rendered here rather than created in the script, so Astro's scoped + styles actually reach it -- see the note on .cn-frame below. With + no src it loads nothing, so the payload still waits for the click. */} +
) : (

@@ -78,15 +82,12 @@ const overTheWire = build ? mb(build.wireBytes) : null; // the frame nears the viewport, and this frame is above the fold, so the // whole payload would download for anyone who opened the page. Several MB // is a lot to spend on a visitor who came here to read the paragraph. - const button = document.querySelector('.cn-start'); + const wrap = document.querySelector('.cn-wrap'); + const button = wrap?.querySelector('.cn-start'); + const frame = wrap?.querySelector('.cn-frame'); button?.addEventListener('click', () => { - const frame = document.createElement('iframe'); - frame.className = 'cn-frame'; - frame.src = button.dataset.src; - frame.title = 'Cannons'; - // The client takes the keyboard for chat and aiming, and plays sound. - frame.allow = 'autoplay'; - button.replaceWith(frame); + frame.src = wrap.dataset.src; + wrap.classList.add('is-playing'); // The game only sees keystrokes once the frame has focus, and a visitor // who just clicked Play should not have to click a second time to type. frame.addEventListener('load', () => frame.focus(), { once: true }); @@ -98,7 +99,15 @@ const overTheWire = build ? mb(build.wireBytes) : null; margin: 1.5rem 0; } /* 770x540 is the applet's own size; the frame is not responsive because the - game's layout is fixed pixels and scaling it would blur the terrain. */ + game's layout is fixed pixels and scaling it would blur the terrain. + + ⚠️ These styles are SCOPED -- Astro compiles them to + `.cn-frame[data-astro-cid-…]` and stamps that attribute on elements in + this template only. An iframe built with document.createElement() in the + script below carries no such attribute, matches nothing, and renders at + the HTML default of 300x150. That is exactly what shipped on 2026-08-28. + Hence the iframe living in the template with the script only setting its + src. */ .cn-start, .cn-frame { display: block; @@ -129,8 +138,17 @@ const overTheWire = build ? mb(build.wireBytes) : null; font-size: 0.8rem; color: var(--muted); } + /* Not the `hidden` attribute: `display: block` above outranks the user + agent's `[hidden] { display: none }`, so the frame would show anyway. */ .cn-frame { background: #0b0d12; + display: none; + } + .cn-wrap.is-playing .cn-start { + display: none; + } + .cn-wrap.is-playing .cn-frame { + display: block; } .cn-note, .cn-missing {