kgames

KGames — free keyboard games for kids
git clone https://www.keyboard.games/code/kgames.git
Log | Files | Refs | README | LICENSE

commit 59e2f40d4ac6b2aa81ebb64d4bee442aae20fd37
parent d5b89decce3fee0676dd96522c7867f8eed946fc
Author: Kyle Barlow <kb@kylebarlow.com>
Date:   Mon, 17 Aug 2026 20:50:33 -0700

Only show story attribution when story active

Diffstat:
Mgames/work/game.js | 10++++++++++
Mgames/work/index.html | 45++++++++++++++++++++++-----------------------
Mtests/browser/runner.js | 34++++++++++++++++++++++++++++------
3 files changed, 60 insertions(+), 29 deletions(-)

diff --git a/games/work/game.js b/games/work/game.js @@ -207,6 +207,14 @@ class WorkScene extends Phaser.Scene { // ── Modes ───────────────────────────────────────────────────────────────── + // Parent-facing attribution below the canvas: visible only while a story is + // being played, and only for that story. + _showStoryCredit(source) { + if (typeof window.KGamesShowStoryCredit === 'function') { + window.KGamesShowStoryCredit(source || null); + } + } + enterMode(mode) { this._cancelTypewriter(); this._cancelPause(); @@ -218,6 +226,7 @@ class WorkScene extends Phaser.Scene { this.stopwatchText.setVisible(false); this.inputText.setText(''); this.mode = mode; + if (mode !== 'madlib') this._showStoryCredit(null); if (mode === 'idle') { this.nameState = null; this.madState = null; @@ -444,6 +453,7 @@ class WorkScene extends Phaser.Scene { startMadlib() { this.madTemplate = WorkLib.pickTemplate(); + this._showStoryCredit(this.madTemplate.source); this.madValues = {}; this.madIndex = 0; this.madState = 'asking'; diff --git a/games/work/index.html b/games/work/index.html @@ -49,9 +49,8 @@ line-height: 1.35; color: #8a8a97; text-align: center; - max-height: 4.6rem; - overflow-y: auto; } + #story-credits[hidden] { display: none; } #story-credits a { color: #667; text-decoration: underline; } #story-credits a:hover { color: #1D7CF2; } #story-credits strong { font-weight: 600; color: #778; } @@ -64,10 +63,8 @@ <button id="btn-sfx">Sound: ON</button> </div> <div id="game-container"></div> - <footer id="story-credits"> - <strong>For grown-ups:</strong> every fill-in-the-blank tale in Stories mode is adapted from a - public-domain children&rsquo;s classic. Tap a title to read the original on - <a href="https://www.gutenberg.org/" target="_blank" rel="noopener">Project Gutenberg</a>. + <footer id="story-credits" hidden> + <strong>For grown-ups:</strong> this story is adapted from the public domain original &mdash; <span id="story-credits-list"></span> </footer> <script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script> @@ -77,26 +74,28 @@ <script src="game.js"></script> <script src="../../js/shared/touch-controls.js"></script> <script> - // Build the parent credits list straight from the story data so the two - // can never drift apart. - (function () { - const el = document.getElementById('story-credits-list'); - if (!el || !window.WorkLib) return; - const seen = new Set(); - const parts = []; - WorkLib.MADLIB_TEMPLATES.forEach(function (t) { - const src = t.source; - if (!src || seen.has(src.title)) return; - seen.add(src.title); + // Credit for the story currently being played — shown only in Stories mode, + // and only for that one story. Called by game.js; `null` hides the footer. + window.KGamesShowStoryCredit = function (source) { + const el = document.getElementById('story-credits'); + const list = document.getElementById('story-credits-list'); + if (!el || !list) return; + if (!source) { + el.hidden = true; + list.textContent = ''; + } else { const a = document.createElement('a'); - a.href = src.url; + a.href = source.url; a.target = '_blank'; a.rel = 'noopener'; - a.textContent = src.title; - parts.push(a.outerHTML + ' by ' + src.author); - }); - el.innerHTML = '<br>' + parts.join(' &middot; ') + '.'; - }()); + a.textContent = source.title; + list.innerHTML = a.outerHTML + ' by ' + source.author; + el.hidden = false; + } + // The footer is a flex sibling of the canvas, so showing or hiding it + // changes the canvas box; Phaser's RESIZE mode needs a nudge. + if (window.__KG_GAME__ && window.__KG_GAME__.scale) window.__KG_GAME__.scale.refresh(); + }; </script> <script>KGames.initTouchControls({ layout: 'text' });</script> </body> diff --git a/tests/browser/runner.js b/tests/browser/runner.js @@ -186,13 +186,35 @@ async function main() { if (!canvas) throw new Error('No canvas element'); }); - await test('work: story credits link to public-domain sources', async (page, url) => { + await test('work: story credit is hidden until a story is playing', async (page, url) => { await page.goto(`${url}/games/work/`, { waitUntil: 'domcontentloaded' }); - await page.waitForTimeout(500); - const hrefs = await page.$$eval('#story-credits a', (as) => as.map(a => a.getAttribute('href'))); - if (!hrefs.length) throw new Error('No source links in #story-credits'); - if (!hrefs.every(h => /^https:\/\/www\.gutenberg\.org\//.test(h))) { - throw new Error('Credit links must point at Project Gutenberg: ' + hrefs.join(', ')); + await page.waitForTimeout(1200); + if (await page.isVisible('#story-credits')) { + throw new Error('Story credit should be hidden outside Stories mode'); + } + // Enter Stories mode and check the credit matches the story actually chosen. + const expected = await page.evaluate(() => { + const sc = window.__WORK_SCENE__; + sc.enterMode('madlib'); + return sc.madTemplate.source; + }); + await page.waitForTimeout(300); + if (!(await page.isVisible('#story-credits'))) { + throw new Error('Story credit should be visible in Stories mode'); + } + const shown = await page.$$eval('#story-credits a', (as) => + as.map(a => ({ href: a.getAttribute('href'), text: a.textContent }))); + if (shown.length !== 1) throw new Error(`Expected 1 credit link, got ${shown.length}`); + if (shown[0].href !== expected.url || shown[0].text !== expected.title) { + throw new Error(`Credit does not match active story: ${JSON.stringify(shown[0])}`); + } + const body = await page.textContent('#story-credits'); + if (!body.includes(expected.author)) throw new Error('Credit missing author'); + // Leaving Stories mode hides it again. + await page.evaluate(() => window.__WORK_SCENE__.enterMode('idle')); + await page.waitForTimeout(200); + if (await page.isVisible('#story-credits')) { + throw new Error('Story credit should hide again on leaving Stories mode'); } });