247 lines
14 KiB
HTML
247 lines
14 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)}
|
|
header{position:sticky;top:0;z-index:5;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}
|
|
.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)}
|
|
/* CARDS YOU HOLD vs CARDS YOU READ.
|
|
Both are drawn as cards, because they are cards. What separates them is the frame: a card in
|
|
Your Move is clickable and carries the action border; a reference card is flat, unlit and
|
|
explicitly not a target. Previously the hand was a list of bare names and the Department slots a
|
|
second list, so nothing looked like a card at all. */
|
|
.cardrow{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 2px}
|
|
/* THE YARDS. Rolling stock is finite and the Classification Yard only comes back when the Division
|
|
Yard is bare, so a player has to be able to watch the supply run down. */
|
|
.yard{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 6px}
|
|
.stock{background:#1c2129;border:1px solid #39424e;border-radius:4px;padding:2px 7px;font-size:11px}
|
|
.stock b{color:#e6e9ee}
|
|
.stock .ld{color:#8fd6a0}
|
|
.stock .mt{color:#9fb6d8}
|
|
.stock.none{opacity:.4}
|
|
.yard.bare{outline:1px dashed #e0a060;outline-offset:3px;border-radius:4px;padding:3px}
|
|
.handcard{background:#242c36;border:1px solid var(--line);border-radius:5px;
|
|
padding:5px 8px;font-size:11px;min-width:96px;position:relative}
|
|
.handcard:focus{outline:2px solid #4d6fa8;outline-offset:1px}
|
|
.cardrow.ref .handcard{background:#1c2129;border-style:dashed;border-color:#39424e;color:#b6bec9}
|
|
/* A pile shows two things: which card is face up on top, and how many are under it. The depth is a
|
|
count and not a hint — only the top card may ever be drawn, so everything below it is out of
|
|
reach, and choosing where to discard is choosing what to put there. */
|
|
.pilehd{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-bottom:2px;
|
|
font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:var(--dim)}
|
|
.pilehd .depth{font-variant-numeric:tabular-nums;background:#2a3038;border-radius:8px;
|
|
padding:0 6px;color:#cfe0f5}
|
|
/* NOT YET PLAYABLE, drawn rather than captioned. A "not yet" tag had to be read; a crosshatch is
|
|
seen. The card stays legible underneath — it is unavailable, not irrelevant. */
|
|
.handcard.unplayable{color:#7d8794;border-color:#39424e}
|
|
.handcard.unplayable::after{content:"";position:absolute;inset:0;border-radius:5px;pointer-events:none;
|
|
background:repeating-linear-gradient(45deg,transparent 0 5px,rgba(150,160,175,.20) 5px 6px)}
|
|
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)}
|
|
/* facilities */
|
|
.fac{border-top:1px solid var(--line);padding:7px 0}
|
|
.fac:first-child{border-top:0}
|
|
.boxes{display:flex;gap:3px;align-items:center;margin-top:3px;flex-wrap:wrap}
|
|
.box{display:inline-block;min-width:22px;text-align:center;border-radius:3px;padding:1px 4px;font-size:10px}
|
|
.box.empty{background:#242a32;color:#5a6472}
|
|
.box.f{background:#2f6b3d}
|
|
.box.m{background:#8a6d1f}
|
|
.fstat{margin-top:3px;font-size:10px;padding:1px 6px;border-radius:3px;display:inline-block}
|
|
.fstat.good{background:rgba(40,140,60,.28)}
|
|
.fstat.bad{background:rgba(190,50,50,.38);font-weight:700}
|
|
.fstat.idle{opacity:.6}
|
|
/* 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 #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}
|
|
/* turn chart */
|
|
#turnchart{display:flex;align-items:center;gap:16px;flex-wrap:wrap;
|
|
background:#161b22;border-bottom:1px solid var(--line);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:var(--dim)}
|
|
.tc-now b{color:var(--now);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:var(--dim)}
|
|
.tc-who b{color:var(--now);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 var(--line);border-radius:14px;
|
|
padding:3px 10px 3px 7px;font-size:11px;color:var(--dim);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}
|
|
/* 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 here, so it cannot be confused with any of them. */
|
|
.tc-phase.now{border-color:var(--now);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:var(--now)}
|
|
.tc-phase.now .icf{fill:var(--now)}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<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="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. There is no undo — 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. -->
|
|
<div id="turnchart">
|
|
<div class="tc-when"><b id="tc-day">Day —</b><span id="tc-stage">Stage —</span><span class="dim" id="tc-clock">—</span></div>
|
|
<div class="tc-now">phase <b id="tc-phase">—</b></div>
|
|
<div class="tc-who">waiting on <b id="tc-who">—</b></div>
|
|
<ol class="tc-phases" id="tc-phases"></ol>
|
|
</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>
|
|
<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>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>
|