Files
TheLadder/src/ui/screens/packs.js
T
JesseMarkowitzandClaude Opus 5 7f20be83e8 Add The Frontier, a second setting, and a picker to choose between them
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
2026-09-12 21:03:50 -04:00

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;