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:
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’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 —
<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(' · ') + '.';
- }());
+ 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');
}
});