Fifteen items from two playtest sessions. Three that read as drawing faults were engine bugs: cars could be added to a train that was not being made up (50 offers in 8 games), the make-up panel merged two trains and could couple a car to the wrong one, and an Office upgrade silently deleted what a Modifier had added. A fourth was a sentinel inside a coordinate's own value range — a Mainline placement travelling as row -1, which is an ordinary district row. Trains are now drawn the way they stand: west on the left, nose toward the way the engine faces, on both the district card and the Division chip. Undo steps back through the game by replaying the save without its last intent. The switching walk keeps its rejections, so the board can say why a square is not offered. Laborers and Porters are on the card, and the rule that a district only grows outwards is finally written down. Versions start here: third digit for fixes, second for a feature set, 1.0 for a release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GgtkX8JnvKa8y2tuJ8aQf4
241 lines
15 KiB
HTML
241 lines
15 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Station Master — play</title>
|
|
<style>
|
|
:root{--bg:#12151a;--fg:#e6e9ee;--dim:#8b94a3;--line:#2c333d;--panel:#1a1f26;--run:#3a4250;--now:#b98cf0}
|
|
*{box-sizing:border-box}
|
|
body{margin:0;background:var(--bg);color:var(--fg);
|
|
font:14px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
h1{font-size:17px;margin:0}
|
|
h2{font-size:12px;text-transform:uppercase;letter-spacing:.09em;color:var(--dim);
|
|
margin:0 0 6px;font-weight:600}
|
|
h2.big{font-size:15px;text-transform:none;letter-spacing:.01em;color:#cfe0f5;
|
|
margin:0 0 9px;padding-bottom:5px;border-bottom:1px solid #2c333d}
|
|
h3{font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:var(--dim);margin:9px 0 4px}
|
|
.dim{color:var(--dim)}
|
|
/* THE TOP TWO ROWS TRAVEL TOGETHER.
|
|
The title bar was sticky and the turn chart under it was not, so scrolling down took away the one
|
|
thing you most need while reading the board — which Day and Stage it is, which phase is running,
|
|
and who it is waiting on. They are one block now, and it sticks as one. */
|
|
.topbar{position:sticky;top:0;z-index:5}
|
|
header{background:var(--panel);border-bottom:1px solid var(--line);
|
|
padding:9px 14px;display:flex;gap:20px;align-items:baseline;flex-wrap:wrap}
|
|
header b{font-size:16px}
|
|
header button{background:#2a3038;color:inherit;border:1px solid #2c333d;border-radius:5px;padding:3px 9px;cursor:pointer;font:inherit;font-size:12px}
|
|
header button:hover{border-color:#4d6fa8}
|
|
/* Nothing to take back yet. It keeps its place — a button that disappears reads as a bug — but it
|
|
stops inviting the press. */
|
|
header button:disabled{opacity:.45;cursor:not-allowed;border-color:#2c333d}
|
|
header button:disabled:hover{border-color:#2c333d}
|
|
.build{margin-left:auto;font-size:11px;opacity:.7}
|
|
.home{color:inherit;text-decoration:none;border-bottom:1px dotted #5f6b7a}
|
|
.home:hover{color:#5aa9e6}
|
|
.pace{font-size:12px;padding:1px 7px;border-radius:10px}
|
|
.pace.good{background:rgba(40,140,60,.32)}
|
|
.pace.behind{background:rgba(190,120,40,.28)}
|
|
.yard.bare{outline:1px dashed #e0a060;outline-offset:3px;border-radius:4px;padding:3px}
|
|
main{display:grid;grid-template-columns:minmax(0,1fr) 400px;gap:14px;padding:14px;align-items:start}
|
|
@media(max-width:1100px){main{grid-template-columns:1fr}}
|
|
section{background:var(--panel);border:1px solid var(--line);border-radius:7px;
|
|
padding:10px 12px;margin-bottom:12px}
|
|
/* division strip */
|
|
#division{display:flex;gap:7px;overflow-x:auto;padding-bottom:4px}
|
|
.node{border:1px solid var(--line);border-radius:6px;padding:6px 9px;min-width:112px;flex:0 0 auto}
|
|
.node.office{border-color:#4d6fa8;background:rgba(60,100,170,.13)}
|
|
.node .nm{font-weight:600;font-size:12px}
|
|
.chip{display:inline-block;background:#2f6b3d;border-radius:4px;padding:1px 6px;margin:3px 3px 0 0;font-size:11px}
|
|
.consist{font-size:10px;color:var(--dim)}
|
|
.grade{font-size:10px;background:rgba(200,90,60,.35);border-radius:3px;padding:1px 4px;font-weight:400}
|
|
.mlmods{font-size:10px;background:rgba(200,150,60,.28);border-radius:3px;padding:1px 4px;margin-top:2px}
|
|
/* board */
|
|
#grid{display:grid;gap:5px;overflow-x:auto}
|
|
.cell{border:1px solid var(--line);border-radius:5px;padding:5px 6px;min-height:64px;background:#161b21}
|
|
.cell.run{background:var(--run)}
|
|
.cell.office{border-color:#4d6fa8}
|
|
.cell.modifier{background:#2b2333;border-style:dashed}
|
|
.cell.legal{outline:2px solid #5aa9e6;outline-offset:-2px;cursor:pointer;
|
|
box-shadow:0 0 0 3px rgba(90,169,230,.18) inset}
|
|
.cell.legal:hover{background:#233246}
|
|
.cell.ghost{border-style:dashed;background:rgba(90,169,230,.07);
|
|
display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center}
|
|
.cell.ghost .nm{font-weight:400;color:#5aa9e6;font-size:11px}
|
|
.cell .nm{font-size:12px;font-weight:600}
|
|
.cell .what{font-size:10px;line-height:1.25;margin-top:2px}
|
|
.cell{cursor:help}
|
|
.coord{font-size:10px}
|
|
.enh{font-size:10px;background:rgba(90,140,220,.30);border-radius:3px;padding:1px 4px;margin-top:2px}
|
|
.crew{font-size:11px;margin-top:3px;color:#8fd6a0}
|
|
.cars{font-size:10px;color:var(--dim)}
|
|
/* THE HAND IS THE ACTION SURFACE. Each card carries its own verbs, so the twelve-button discard
|
|
block and the separate "play a card" subject list are both gone. The buttons are small and quiet
|
|
until the card is picked — the card is the thing being read, they are what you do with it. */
|
|
.handcard .cardacts{display:flex;gap:4px;margin-top:5px}
|
|
button.cardact{margin:0;padding:1px 7px;font-size:10px;background:#2f3a48;border:1px solid #46536440;
|
|
border-radius:9px;color:#cfd6e0;text-align:center}
|
|
button.cardact:hover{background:#3d4c60;border-color:#5aa9e6}
|
|
button.cardact.on{background:#2b3444;border-color:#c8912f;color:#f2e6cf;font-weight:700}
|
|
button.cardact.discard{color:#d6b48a}
|
|
.handcard.picked{border-color:#c8912f;box-shadow:0 0 0 2px rgba(200,145,47,.18)}
|
|
#phasenote{position:fixed;left:50%;top:14px;transform:translateX(-50%) translateY(-14px);z-index:40;
|
|
background:#241d33;border:1px solid var(--now);color:#e7dcfb;border-radius:14px;padding:5px 16px;
|
|
font-size:13px;font-weight:700;opacity:0;pointer-events:none;transition:opacity .18s,transform .18s;
|
|
box-shadow:0 6px 22px rgba(0,0,0,.5)}
|
|
#phasenote.shown{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
@media(prefers-reduced-motion:reduce){#phasenote{transition:none}}
|
|
/* The die's answer, said where the player is looking. Green like a revenue gain — it is a good
|
|
thing that just happened — and it disappears with the next render, as a moment should. */
|
|
.scheduled{display:block;font-size:11.5px;margin:0 0 7px;padding:3px 9px;border-radius:5px;
|
|
background:rgba(40,140,60,.22);border:1px solid #2f6b47;color:#bfe8cd;font-weight:600}
|
|
.makeup-note{font-size:11px;margin:0 0 5px}
|
|
/* Moves left, beside the moves. Amber like the actions, because it is a budget being spent by the
|
|
buttons underneath it; struck through in red once it is gone. */
|
|
.moves{display:inline-block;font-size:11px;margin:0 0 7px;padding:1px 8px;border-radius:10px;
|
|
background:rgba(200,145,47,.16);border:1px solid #8a6d1f;color:#f2e6cf}
|
|
.moves.spent{background:rgba(190,50,50,.20);border-color:#8a4040;color:#e58080}
|
|
/* actions */
|
|
#actions{max-height:none}
|
|
.grp{margin-bottom:6px}
|
|
/* THESE ARE THE THINGS YOU CAN DO. An action carried the same grey border as every other panel
|
|
on the page, so the one region that is clickable did not look it. Amber border and a lit face,
|
|
used nowhere else, so "this is a move" is answered before the label is read. */
|
|
button{background:#2a3038;color:var(--fg);border:1px solid var(--line);border-radius:5px;
|
|
padding:5px 9px;margin:2px 3px 2px 0;cursor:pointer;font:inherit;font-size:12px;text-align:left}
|
|
button:hover{background:#39424e;border-color:#4d6fa8}
|
|
#actions button{background:#2b3444;border:2px solid #c8912f;box-shadow:0 0 0 1px rgba(200,145,47,.18);
|
|
color:#f2e6cf;font-weight:600}
|
|
#actions button:hover{background:#3a4a63;border-color:#f0b64a;box-shadow:0 0 0 3px rgba(240,182,74,.20)}
|
|
#actions button:focus-visible{outline:2px solid #f0b64a;outline-offset:2px}
|
|
button.act{display:inline-block}
|
|
/* The district collapses when nothing in it is changing. A summary line stays, because a panel
|
|
that vanishes entirely reads as broken rather than as folded away. */
|
|
button.ghost{background:#222831;border:1px solid #4a5361;color:#c6ccd6;font-size:11px;
|
|
padding:2px 9px;margin-left:10px;text-transform:none;letter-spacing:0;vertical-align:middle}
|
|
button.ghost:hover{border-color:#4d6fa8;color:var(--fg)}
|
|
#district.folded #grid{display:none}
|
|
#district.folded .districtrule{display:none}
|
|
/* Said once, quietly, beside the thing it governs — a rule a player needs on their first district
|
|
and never again, so it must not shout. */
|
|
.districtrule{font-size:11px;line-height:1.4;color:#8b94a3;margin:0 0 7px;padding:5px 8px;
|
|
border-left:2px solid #39424e;background:#171c23;border-radius:0 4px 4px 0}
|
|
.districtrule b{color:#cfd6e0}
|
|
#district.folded #districtsummary{display:block;padding:2px 0 1px;font-size:12px}
|
|
#districtsummary{display:none}
|
|
/* An action you cannot take yet keeps its place but drops its light — the amber means "press me",
|
|
so a disabled button must not wear it. */
|
|
#actions button.blocked,#actions button:disabled{background:#232830;border:1px dashed #4a5361;
|
|
color:#7d8794;box-shadow:none;cursor:not-allowed;font-weight:400}
|
|
#actions button.blocked:hover{background:#232830;border-color:#4a5361;box-shadow:none}
|
|
h3.actions-hd{font-size:13px;text-transform:none;letter-spacing:.01em;color:#cfe0f5;
|
|
margin:0 0 7px;font-weight:700}
|
|
.over{padding:9px;border-radius:5px;font-weight:700;margin-bottom:8px}
|
|
.over.win{background:rgba(40,140,60,.35)}
|
|
.over.loss{background:rgba(160,60,60,.3)}
|
|
/* cards, log, blocked */
|
|
.card.gone{opacity:.35;text-decoration:line-through}
|
|
.subj{display:block;width:100%;margin:2px 0}
|
|
.subj.open{border-color:#4d6fa8;background:#39424e}
|
|
.spots{margin:0 0 6px 12px;padding-left:8px;border-left:2px solid #4d6fa8}
|
|
.card{display:inline-block;background:#242c36;border:1px solid var(--line);border-radius:4px;
|
|
padding:2px 6px;margin:2px 2px 0 0;font-size:11px}
|
|
#log{max-height:230px;overflow:auto;font-size:12px}
|
|
.line{padding:1px 0}
|
|
.t-good{color:#8fd6a0}.t-bad{color:#e58080}.t-clock{color:#9fb6d8;font-weight:600}.t-quiet{color:var(--dim)}
|
|
/* A phase marker is a HEADING inside the log, and takes the same violet the turn chart uses for
|
|
"where you are" — the two are saying the same thing in different places. `act` is what the
|
|
player did, so it stays the brightest thing in the column. */
|
|
.t-phase{color:var(--now);font-weight:700;margin-top:4px;letter-spacing:.02em}
|
|
.t-act{color:#e6e9ee}
|
|
.t-start{color:#cfe0f5;font-weight:700;font-size:13px;border-bottom:1px solid var(--line);
|
|
padding-bottom:3px;margin-bottom:3px}
|
|
ul.blocked{list-style:none;margin:0;padding:0;font-size:12px}
|
|
ul.blocked li{padding:2px 0}
|
|
.sev-warn{color:#e0b060}.sev-stop{color:#e58080}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="topbar">
|
|
<header>
|
|
<b><a href="./index.html" class="home">Station Master</a></b>
|
|
<span>Revenue <b id="revenue">0</b></span>
|
|
<span id="objective" class="pace">—</span>
|
|
<span class="dim">seed <span id="seed">—</span></span>
|
|
<span class="dim">saved in this browser · add ?seed=1234 for a set deal</span>
|
|
<button id="sound" title="Whistle at the end of each Stage, the crossing bell at the end of each Day, and the conductor when a train is built. Currently synthesised, not recorded.">🔇 muted</button>
|
|
<button id="undo" title="Take the last action back. The save is the seed plus the moves made, so this replays the game without the last one — as far back as you like.">Undo</button>
|
|
<button id="savefile" title="Download this game as a save file you can replay or share">Save replay</button>
|
|
<button id="newgame" title="Forget the game saved in this browser and deal a fresh one. Undo steps back one action at a time; this throws the whole game away, so download the replay first if you want to keep it.">New game</button>
|
|
<a class="home" href="./replays.html" style="font-size:12px">replays</a>
|
|
<span class="dim build" title="what is actually deployed">__BUILD__</span>
|
|
</header>
|
|
|
|
<!-- The turn chart: where in the Day we are, and which of the five phases is running. Lifted out
|
|
of the title bar, where "phase: Mainline" said nothing about what came before or after it. -->
|
|
<!-- Filled by `turnChartHtml`, shared with both replay viewers so all three screens report where
|
|
you are in the Day identically. -->
|
|
<div id="turnchart"></div>
|
|
</div><!-- /topbar -->
|
|
<!-- Announces a phase CHANGE. The turn chart says where you are; this says that it moved, which is
|
|
what you miss when the automatic phases run between two clicks. -->
|
|
<div id="phasenote"></div>
|
|
|
|
<main>
|
|
<div>
|
|
<section><h2>The Division — west to east</h2><div id="division"></div></section>
|
|
<section id="district">
|
|
<h2>Your Office Area
|
|
<span class="dim" style="text-transform:none;letter-spacing:0">— hover any card for the full explanation</span>
|
|
<button id="districttoggle" class="ghost" title="Auto-hide keeps the district open during Local Operations and Cargo — the phases that change it — and folds it otherwise. Click to pin it open or hidden instead.">auto-hide: on</button>
|
|
</h2>
|
|
<div id="districtsummary" class="dim"></div>
|
|
<!-- THE RULE THAT SHAPES EVERY DISTRICT, said once where the district is.
|
|
It is enforced everywhere and was stated nowhere: the Limits signs are the only growth
|
|
point on the Running Track and they move outward as you build, and no card anywhere can
|
|
be inserted between two cards already down. -->
|
|
<div class="districtrule">A district only ever grows <b>outwards</b>. On the Running Track the
|
|
Limits signs are the growth point — lay a card on a sign and the sign moves one further out;
|
|
nothing may be built beyond it. Everywhere else a card needs an empty square that its rails
|
|
reach. <b>No card is ever inserted between two cards already down</b>, so nothing you have
|
|
built is pushed along by something laid later.</div>
|
|
<div id="grid"></div>
|
|
</section>
|
|
<section><h2>History</h2><div id="log"></div></section>
|
|
</div>
|
|
|
|
<div>
|
|
<section><h2 class="big">Your Move</h2><div id="actions"></div></section>
|
|
<section><h2 class="big">Cards Available</h2>
|
|
<h3>Cards in My Hand</h3><div id="hand" class="cardrow">—</div>
|
|
<h3>Department Decks & Salvage Yard — top card face up, with depth</h3><div id="depts" class="cardrow ref">—</div>
|
|
</section>
|
|
<section><h2 class="big">The Yards</h2>
|
|
<h3>Division Yard <span class="dim" id="divtot"></span></h3>
|
|
<div id="divyard" class="yard"></div>
|
|
<h3>Classification Yard <span class="dim" id="clstot"></span></h3>
|
|
<div id="clsyard" class="yard"></div>
|
|
<div class="dim" id="yardnote"></div>
|
|
</section>
|
|
<section><h2>Timetable — who is due out, and when</h2><div id="timetable"></div>
|
|
<!-- WHAT THE COLOURS MEAN. Asked directly in playtesting — "what do green and purple mean?"
|
|
Violet is "where you are" everywhere on this page, and the green is a flash rather than a
|
|
state, which is exactly why it needed saying. -->
|
|
<div class="tt-key">
|
|
<span class="k-due">Train due</span>
|
|
<span class="k-now">this Stage</span>
|
|
<span class="k-past">already gone</span>
|
|
<span class="k-fresh">just rolled</span>
|
|
</div>
|
|
<div class="dim" style="font-size:11px;margin-top:5px">A train's number is its seniority and direction — odd runs west, even runs east. The Stage it departs is set by a 1D12 roll when its card is played, and it then runs at that time every Day.</div>
|
|
</section>
|
|
<section><h2>Blocked — why nothing is moving</h2><ul class="blocked" id="blocked"></ul></section>
|
|
<section><h2>Facilities</h2><div id="facs"></div></section>
|
|
</div>
|
|
</main>
|
|
|
|
<script type="module" src="./web/main.js"></script>
|
|
</body>
|
|
</html>
|