More bug fixes. Show the timetable, and stop the arrival message contradicting itself. Fixed division point in labels. And highlighted discard as targets.
This commit is contained in:
+86
-11
@@ -97,6 +97,43 @@ export function yardHtml(rows: { type: string; loaded: number; empty: number }[]
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* THE TIMETABLE: twelve Stages of a Day, and which train is due out at each.
|
||||
*
|
||||
* Playing a train card rolls 1D12 for a slot, and the roll landed nowhere a player could see — the
|
||||
* card was gone and the answer to "so when does it run?" was one line of history. The Frame has
|
||||
* carried the twelve slots all along and only the standalone replay ever drew them.
|
||||
*
|
||||
* `justSet` is the slot filled by the roll being reported this frame, so it can be flashed. It marks
|
||||
* the moment rather than the state: the highlight is gone by the next render, which is what makes it
|
||||
* read as "that just happened" rather than "this is special".
|
||||
*/
|
||||
export function timetableHtml(f: Frame, justSet: number | null): string {
|
||||
const slots = f.timetable
|
||||
.map((t, k) => {
|
||||
const stage = k + 1;
|
||||
const cls = [
|
||||
t !== null ? 'due' : '',
|
||||
stage === f.stage ? 'now' : '',
|
||||
stage < f.stage ? 'past' : '',
|
||||
k === justSet ? 'fresh' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
const tip =
|
||||
t !== null
|
||||
? `Train ${t} departs at Stage ${stage}. A train's NUMBER is its seniority and direction — odd runs west, even runs east — and the Stage it leaves is set by the 1D12 roll made when its card was played, so Train 8 at Stage 6 is normal.`
|
||||
: `Stage ${stage} — no train due out.`;
|
||||
return (
|
||||
`<div class="tt-slot ${cls}" data-tip="${esc(tip)}" tabindex="0">` +
|
||||
`<span class="tt-s">${stage}</span>` +
|
||||
`<span class="tt-t">${t !== null ? `T${t}` : '·'}</span></div>`
|
||||
);
|
||||
})
|
||||
.join('');
|
||||
return `<div class="tt">${slots}</div>`;
|
||||
}
|
||||
|
||||
export function blockedHtml(f: Frame): string {
|
||||
return f.blocked.length === 0
|
||||
? '<li class="dim">nothing blocked</li>'
|
||||
@@ -147,18 +184,28 @@ export function facilitiesHtml(f: Frame): string {
|
||||
*/
|
||||
`<div class="caps">` +
|
||||
stat('laborers', x.laborers, x.base.laborers, 'Actions this facility can take each Stage — one Laborer moves one load one square.') +
|
||||
stat('out', String(x.greenCap), x.base.out, 'Green boxes: loads waiting to be worked onto a car.') +
|
||||
stat('in', String(x.redCap), x.base.in, 'Red boxes: loads that have come off an arriving car and are waiting to be cleared.') +
|
||||
(x.allowsOut ? stat('out', String(x.greenCap), x.base.out, 'Green boxes: loads waiting to be worked onto a car.') : '') +
|
||||
(x.allowsIn ? stat('in', String(x.redCap), x.base.in, 'Red boxes: loads cleared off an arriving car.') : '') +
|
||||
(x.modifiers.length
|
||||
? `<span class="mods" data-tip="Modifier cards standing beside this industry, each raising one of the numbers above.">+ ${esc(x.modifiers.join(', '))}</span>`
|
||||
: '') +
|
||||
`</div>` +
|
||||
`<div class="boxes"><span class="dim">green</span>${boxes(x.green, x.greenCap)}</div>` +
|
||||
`<div class="boxes"><span class="dim">MEN AT WORK</span>` +
|
||||
/**
|
||||
* IN THE ORDER THE LOAD TRAVELS. §9.3 loads Green → MEN → AT → WORK → the spotted car,
|
||||
* and unloads the other way: car → WORK → AT → MEN → red. Labelling the rows by colour
|
||||
* alone left a player to work out which direction their industry ran; naming the step
|
||||
* says it outright, and an industry that only receives has no green row to puzzle over.
|
||||
*/
|
||||
(x.allowsOut
|
||||
? `<div class="boxes"><span class="dim" data-tip="Loads waiting to be worked out onto a car. They travel green → MEN → AT → WORK, then onto a spotted empty car.">waiting to load</span>${boxes(x.green, x.greenCap)}</div>`
|
||||
: '') +
|
||||
`<div class="boxes"><span class="dim" data-tip="One physical sign, worked in whichever direction this industry runs. Only one load may sit on each of the three boxes.">MEN AT WORK</span>` +
|
||||
x.maw.map((m) => `<span class="box ${m ? 'm' : 'empty'}">${m ? esc(m) : '·'}</span>`).join('') +
|
||||
`</div>` +
|
||||
`<div class="boxes"><span class="dim">red</span>${boxes(x.red, x.redCap)}</div>` +
|
||||
`<div class="boxes"><span class="dim">siding</span>${boxes(x.track, x.trackCap)}</div>` +
|
||||
(x.allowsIn
|
||||
? `<div class="boxes"><span class="dim" data-tip="Loads that have come off an arriving car and been cleared. They travel car → WORK → AT → MEN, then into a red box — the opposite direction to loading.">cleared inbound</span>${boxes(x.red, x.redCap)}</div>`
|
||||
: '') +
|
||||
`<div class="boxes"><span class="dim" data-tip="Cars spotted on this industry's track. A load leaves the sign onto one of these, or an arriving load starts from one.">siding</span>${boxes(x.track, x.trackCap)}</div>` +
|
||||
`<div class="fstat ${x.jammed ? 'bad' : x.canFinish ? 'good' : 'idle'}" data-tip="${
|
||||
x.jammed
|
||||
? 'A load is sitting on MEN|AT|WORK with no spotted car to receive it. That locks the industry track, which blocks the very car that would clear it (§9.3).'
|
||||
@@ -196,14 +243,42 @@ h3{font-size:11px;text-transform:uppercase;letter-spacing:.07em;color:#8b94a3;ma
|
||||
.stock .ld{color:#8fd6a0}
|
||||
.stock .mt{color:#9fb6d8}
|
||||
.stock.none{opacity:.4}
|
||||
/* A pile or a yard chip that is currently a target for the thing you picked. Blue, matching the
|
||||
legal-square outline on the board: the same question is being asked, so it looks the same. */
|
||||
.handcard.target{border-color:#5aa9e6;background:#233246;cursor:pointer;
|
||||
box-shadow:0 0 0 2px rgba(90,169,230,.20)}
|
||||
.handcard.target:hover{background:#2b3d55}
|
||||
/* A PILE THAT IS WAITING TO BE PICKED, drawn like the board's ghost squares because it is the same
|
||||
question: you have chosen a card, now choose where it goes. It was a 2px outline on a small tile
|
||||
in a panel the player might not be looking at, and was reported as "a very tiny highlight". Now it
|
||||
grows, brightens, says DISCARD HERE outright, and pulses once so the eye finds it. */
|
||||
.handcard.target{border:2px dashed #5aa9e6;background:#233246;cursor:pointer;color:#e6e9ee;
|
||||
box-shadow:0 0 0 4px rgba(90,169,230,.22);transform:translateY(-2px);
|
||||
animation:pick 1.1s ease-out 2}
|
||||
.handcard.target:hover{background:#31485f;border-style:solid;box-shadow:0 0 0 6px rgba(90,169,230,.30)}
|
||||
.handcard.target::before{content:"DISCARD HERE";display:block;font-size:9px;letter-spacing:.09em;
|
||||
color:#8fc4ee;font-weight:700;margin-bottom:2px}
|
||||
@keyframes pick{0%{box-shadow:0 0 0 4px rgba(90,169,230,.22)}
|
||||
50%{box-shadow:0 0 0 9px rgba(90,169,230,.10)}
|
||||
100%{box-shadow:0 0 0 4px rgba(90,169,230,.22)}}
|
||||
@media(prefers-reduced-motion:reduce){.handcard.target{animation:none}}
|
||||
/* And the piles that are NOT targets step back while a discard is being aimed, so the three that
|
||||
are stand out from the Salvage Yard beside them. */
|
||||
.cardrow.aiming .handcard:not(.target){opacity:.4}
|
||||
.stock .ld.addable,.stock .mt.addable{cursor:pointer;border-radius:3px;padding:0 4px;
|
||||
outline:1px solid #5aa9e6;background:rgba(90,169,230,.16)}
|
||||
.stock .ld.addable:hover,.stock .mt.addable:hover{background:rgba(90,169,230,.34)}
|
||||
/* Twelve Stages across, so a Day is one glance. The current Stage is lit, Stages already gone are
|
||||
dimmed, and a slot the die has just filled flashes once. */
|
||||
.tt{display:flex;gap:3px;flex-wrap:wrap}
|
||||
.tt-slot{flex:1 1 0;min-width:26px;text-align:center;border:1px solid #2c333d;border-radius:4px;
|
||||
padding:2px 0;background:#1a1f26;font-size:10px;cursor:help;line-height:1.25}
|
||||
.tt-slot .tt-s{display:block;color:#5f6b7a;font-size:9px}
|
||||
.tt-slot .tt-t{display:block;color:#5f6b7a;font-variant-numeric:tabular-nums}
|
||||
.tt-slot.due{background:#222c38;border-color:#4d6fa8}
|
||||
.tt-slot.due .tt-t{color:#cfe0f5;font-weight:700}
|
||||
.tt-slot.past{opacity:.45}
|
||||
.tt-slot.now{border-color:#b98cf0;box-shadow:0 0 0 2px rgba(150,110,230,.20)}
|
||||
.tt-slot.now .tt-s{color:#b98cf0;font-weight:700}
|
||||
.tt-slot.fresh{animation:ttflash 1.5s ease-out 1}
|
||||
@keyframes ttflash{0%{background:#2f6b47;border-color:#8fd6a0;transform:scale(1.18)}
|
||||
100%{background:#222c38;border-color:#4d6fa8;transform:scale(1)}}
|
||||
@media(prefers-reduced-motion:reduce){.tt-slot.fresh{animation:none;outline:2px solid #8fd6a0}}
|
||||
ul.blocked{margin:0;padding-left:18px}
|
||||
.sev-waiting{color:#8b94a3}.sev-risk{color:#e0b060}.sev-stuck{color:#e58080}
|
||||
.fac{border-top:1px solid #2c333d;padding:7px 0}
|
||||
|
||||
Reference in New Issue
Block a user