/** * The turn chart — where in the Day we are, and which of the five phases is running. * * SELF-CONTAINED ON PURPOSE, exactly like the two board renderers in `board-svg.ts`. The playable * app and the site's replay viewer import it normally; the standalone replay is a single HTML file * with an inline script and cannot import anything, so it embeds this via `Function.toString()`. * That keeps ONE implementation across all three screens. * * It lived only in `main.ts` before, so a replay showed the Day and the phase as plain text while * the live game showed the chart — and the violet "you are here" highlight, which is the single * most-glanced-at thing on the page, appeared on one screen out of three. */ /** What a chart needs from a frame. A `Frame` satisfies this; so does a replay's rehydrated one. */ export type TurnChartFrame = { day: number; stage: number; clock: string; phase: string; phaseKey: string; actor: number | null; }; /** * The five phases of a Stage, in order, each with what actually happens in it. * * The first three are the rules text; Cargo and Supervisor Shift are written from what the engine * does, since the recovered sheet does not spell them out. */ export function turnChartHtml(f: TurnChartFrame, actorName: string | null): string { const PHASES: { key: string; label: string; tip: string; icon: string }[] = [ { key: 'localOps', label: 'Local Operations', tip: 'Switch with a train, Freight Agent operations, or draw a card. One of the three, once per Stage — this is the whole of your turn.', // a turnout: the choice of three roads icon: '', }, { key: 'newTrain', label: 'New Train', tip: 'Timetabled trains for this Stage are built. New timetabled trains are randomly placed on the timetable. Held trains are built. Extra trains are built.', // a locomotive being made up icon: '', }, { key: 'mainline', label: 'Mainline', tip: 'All trains holding at an Office or Division Point must attempt to move. This phase is automatic — you make no decisions in it, except a clearance ruling if one is called for.', // open road icon: '', }, { key: 'loadUnload', label: 'Cargo', tip: 'Your workers act, once each per Stage. A Laborer moves a load one square across MEN | AT | WORK, or starts one, or begins an unload. A Porter boards or detrains passengers, which earns Revenue in a single action.', // a loaded box on a flat icon: '', }, { key: 'shiftChange', label: 'Supervisor Shift', tip: "End of the Stage. Every Laborer and Porter is refreshed for the next one, and every third Stage — 3, 6, 9 and 12 — the Superintendent's hat passes to the next player.", // a pocket watch icon: '', }, ]; const esc = (t: string): string => String(t).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c] ?? c); const at = PHASES.findIndex((p) => p.key === f.phaseKey); const chips = PHASES.map((p, i) => { const state = p.key === f.phaseKey ? 'now' : i < at ? 'done' : ''; return ( `
  • ` + `` + `${esc(p.label)}
  • ` ); }).join(''); // An automatic phase is waiting on nobody, and saying so is more use than a blank. const who = actorName ?? 'nobody — the Division is running itself'; return ( `
    Day ${f.day}Stage ${f.stage} of 12` + `${esc(f.clock)}
    ` + `
    phase ${esc(f.phase)}
    ` + `
    waiting on ${esc(who)}
    ` + `
      ${chips}
    ` ); } /** * Styling, shared so the three screens cannot drift. * * VIOLET, not the amber used for actions. The palette already spends amber on "you can click this", * blue on "this is a legal square or a link", green on good and red on bad. The turn chart is none * of those — it reports WHERE YOU ARE, and a player should never read it as something to press. * Violet is the one hue not already carrying a meaning, so it cannot be confused with any of them. */ export const TURNCHART_CSS = ` #turnchart{display:flex;align-items:center;gap:16px;flex-wrap:wrap; background:#161b22;border-bottom:1px solid #2c333d;padding:7px 14px} .tc-when{display:flex;align-items:baseline;gap:10px} .tc-when b{font-size:15px;color:#cfe0f5} .tc-now{font-size:12px;color:#8b94a3} .tc-now b{color:#b98cf0;font-size:13px} /* WHO EVERYONE IS WAITING ON. Solitaire has one player, but the seat still has a name — the log says "Player Solitaire", so the chart should agree. In multiplayer this is the thing a table glances at most often, so it gets its own chip rather than hiding in the phase text. */ .tc-who{display:flex;align-items:center;gap:6px;font-size:12px;color:#8b94a3} .tc-who b{color:#b98cf0;background:rgba(150,110,230,.16);border:1px solid #8b6ad0; border-radius:11px;padding:1px 9px;font-size:12px} ol.tc-phases{display:flex;gap:6px;list-style:none;margin:0;padding:0;flex-wrap:wrap} .tc-phase{display:flex;align-items:center;gap:6px;border:1px solid #2c333d;border-radius:14px; padding:3px 10px 3px 7px;font-size:11px;color:#8b94a3;background:#1a1f26;cursor:help} .tc-phase svg{display:block;flex:0 0 auto} .tc-phase .ic{stroke:#5f6b7a;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round} .tc-phase .icf{fill:#5f6b7a;stroke:none} .tc-phase.done{opacity:.55} .tc-phase.now{border-color:#b98cf0;background:rgba(150,110,230,.15);color:#e7dcfb;font-weight:700; box-shadow:0 0 0 2px rgba(150,110,230,.18)} .tc-phase.now .ic{stroke:#b98cf0} .tc-phase.now .icf{fill:#b98cf0} `;