Cold Fork is a freight yard: 29 events across two stages, a four-day cycle, and its own relationship and skill vocabulary. src/engine/ was not touched. Adding it needed three changes outside the engine: one save slot per pack, app.js holding the current pack, and turn.js no longer naming the mailroom (DECISIONS #34). build.js now handles imports wrapped across lines. A conformance suite runs over every registered pack. DECISIONS #35 records three calls on open questions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RxdJFbLq1rBKsUeticGV1g
65 lines
2.6 KiB
JavaScript
65 lines
2.6 KiB
JavaScript
// Choosing a setting.
|
|
//
|
|
// A setting is a content pack, and this screen is the whole of the interface's
|
|
// knowledge about there being more than one: it reads `name`, `blurb` and the
|
|
// stage titles off each pack and knows nothing else about any of them. Adding
|
|
// a third pack changes this file not at all.
|
|
//
|
|
// Each pack keeps its own save slot, so switching is not destructive and the
|
|
// screen says so — a run in progress is reported with the turn it is on.
|
|
|
|
import { h } from '../dom.js';
|
|
|
|
export function packsScreen(packs, { currentPackId, runs, canGoBack }, actions) {
|
|
return h('div', { class: 'screen screen--packs' },
|
|
h('header', { class: 'packs__head' },
|
|
h('p', { class: 'packs__eyebrow' }, 'The Ladder'),
|
|
h('h1', { class: 'packs__title' }, 'Choose a setting'),
|
|
h('p', { class: 'packs__subtitle' },
|
|
'The same game underneath. Each setting keeps its own run, so you can ' +
|
|
'move between them without losing either.'),
|
|
),
|
|
|
|
h('ul', { class: 'packs__list' },
|
|
packs.map((pack, index) => {
|
|
const run = runs[pack.id];
|
|
const current = pack.id === currentPackId;
|
|
return h('li', { class: `packs__item${current ? ' packs__item--current' : ''}` },
|
|
h('h2', { class: 'packs__name' }, pack.name),
|
|
pack.blurb && h('p', { class: 'packs__blurb' }, pack.blurb),
|
|
h('p', { class: 'packs__state' }, runLine(pack, run, current)),
|
|
h('button', {
|
|
class: current ? 'button' : 'button button--quiet',
|
|
// The setting the player is already in, or failing that the first
|
|
// one, is what Enter takes — the same keyboard contract as
|
|
// everywhere else.
|
|
dataset: (current || (!currentPackId && index === 0)) ? { autofocus: 'true' } : undefined,
|
|
onClick: () => actions.onChoosePack(pack.id),
|
|
}, buttonLabel(run, current)),
|
|
);
|
|
}),
|
|
),
|
|
|
|
canGoBack && h('footer', { class: 'controls' },
|
|
h('button', { class: 'button button--quiet', onClick: actions.onResume }, 'Back'),
|
|
),
|
|
);
|
|
}
|
|
|
|
function runLine(pack, run, current) {
|
|
const unit = pack.turnUnit ?? 'turn';
|
|
if (!run) return 'Not started.';
|
|
const title = pack.stages?.[run.stage]?.title ?? run.stage;
|
|
const where = `${capitalize(unit)} ${run.turn}, ${title}`;
|
|
return current ? `${where} — where you are now.` : `${where}, saved.`;
|
|
}
|
|
|
|
function buttonLabel(run, current) {
|
|
if (current) return 'Keep playing';
|
|
return run ? 'Go back to this run' : 'Start here';
|
|
}
|
|
|
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
|
|
export default packsScreen;
|