Put the game frame in the template, so its styles reach it
The frame rendered at 300x150 -- the HTML default for an unstyled iframe --
instead of the applet's 770x540. Astro's <style> is scoped: it compiles
.cn-frame to `.cn-frame[data-astro-cid-kr4ipkmt]` and stamps that attribute on
elements in the template. The iframe was built with document.createElement()
in the script, carried no such attribute, and therefore matched none of its
own rules.
So the iframe lives in the template now and the click only sets its src. An
iframe with no src loads nothing, so the payload still waits for the click,
which was the only reason it was being created dynamically.
It is hidden with a class rather than the `hidden` attribute, because the
`display: block` in the sizing rule outranks the user agent's
`[hidden] { display: none }` and it would have shown regardless.
Verified by rendering the staged branch locally against a stub manifest --
the page only builds its Play button when an export is present, so the
previous change shipped without its markup ever being looked at.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S4wdbGSVzvUDHBWRjSVxfr
This commit is contained in:
+29
-11
@@ -48,14 +48,18 @@ const overTheWire = build ? mb(build.wireBytes) : null;
|
||||
|
||||
{
|
||||
build ? (
|
||||
<div class="cn-wrap">
|
||||
<button class="cn-start" type="button" data-src={cannonsFrameUrl}>
|
||||
<div class="cn-wrap" data-src={cannonsFrameUrl}>
|
||||
<button class="cn-start" type="button">
|
||||
<span class="cn-start-label">Play</span>
|
||||
<span class="cn-start-sub">
|
||||
downloads about {overTheWire} MB — the client, the server, and
|
||||
the game's own art, compressed
|
||||
</span>
|
||||
</button>
|
||||
{/* 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. */}
|
||||
<iframe class="cn-frame" title="Cannons" allow="autoplay"></iframe>
|
||||
</div>
|
||||
) : (
|
||||
<p class="cn-missing">
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user