The hand may stand at the board's left (preference)

A player asked for cards down the side instead of along the bottom.
With the new pref on (wide screens only), the hand claims a sticky,
scrolling column at the left while the hint strips and action buttons
stay beneath the board — the table-edge dissolves into the grid so its
children place themselves. The strip moves inside the .game grid to
make that possible; the default bottom layout spans the full row as
before, and phones keep their one-column flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
Eric Wagoner
2026-08-29 23:28:00 -04:00
co-authored by Claude Fable 5
parent b538a0a2ae
commit d334b267a2
2 changed files with 39 additions and 3 deletions
+34 -2
View File
@@ -1497,6 +1497,11 @@
onchange={(e) => setPref("instantReplay", e.currentTarget.checked)} /> onchange={(e) => setPref("instantReplay", e.currentTarget.checked)} />
<span>Instant replay: notable chronicle lines offer a 👁 that relives the turn</span> <span>Instant replay: notable chronicle lines offer a 👁 that relives the turn</span>
</label> </label>
<label class="pref-row pref-check">
<input type="checkbox" checked={prefs.handLeft}
onchange={(e) => setPref("handLeft", e.currentTarget.checked)} />
<span>Hand at the left: your cards stand in a column beside the board (wide screens)</span>
</label>
<label class="pref-row pref-check"> <label class="pref-row pref-check">
<input type="checkbox" checked={prefs.cautions} <input type="checkbox" checked={prefs.cautions}
onchange={(e) => setPref("cautions", e.currentTarget.checked)} /> onchange={(e) => setPref("cautions", e.currentTarget.checked)} />
@@ -1986,7 +1991,7 @@
</div> </div>
</section> </section>
{:else if view} {:else if view}
<div class="game"> <div class="game" class:hand-left={prefs.handLeft}>
<section class="board-zone"> <section class="board-zone">
<div class="table-stack" class:fpv-primary={prefs.liveFp && !!view.you && !net.spectating}> <div class="table-stack" class:fpv-primary={prefs.liveFp && !!view.you && !net.spectating}>
{#if prefs.liveFp && view.you && !net.spectating} {#if prefs.liveFp && view.you && !net.spectating}
@@ -2249,7 +2254,6 @@
</form> </form>
{/if} {/if}
</aside> </aside>
</div>
<div class="table-edge"> <div class="table-edge">
{#if ambushVia} {#if ambushVia}
@@ -2558,6 +2562,7 @@
{/each} {/each}
</div> </div>
</div> </div>
</div>
{/if} {/if}
</div> </div>
{/if} {/if}
@@ -3036,6 +3041,32 @@
min-height: 0; min-height: 0;
} }
.board-zone { display: flex; align-items: flex-start; justify-content: center; min-width: 0; } .board-zone { display: flex; align-items: flex-start; justify-content: center; min-width: 0; }
/* Hand at the left: the cards stand in their own column beside the
* board while the hint strips and action buttons stay beneath it —
* the strip dissolves into the grid so its children place themselves.
* Phones keep the bottom hand; the one-column flow re-orders anyway. */
@media (min-width: 901px) {
.game.hand-left {
grid-template-columns: minmax(9.6rem, 10.4rem) minmax(340px, 1fr) minmax(250px, 330px);
}
.game.hand-left .table-edge { display: contents; }
.game.hand-left .table-edge > * { grid-column: 2; min-width: 0; }
.game.hand-left .table-edge > .hand {
grid-column: 1;
grid-row: 1 / span 6;
flex-direction: column;
align-items: center;
align-self: start;
position: sticky;
top: 0.5rem;
max-height: calc(100dvh - 5rem);
overflow-y: auto;
overflow-x: visible;
padding: 0.3rem 0.3rem 0.9rem;
}
.game.hand-left .board-zone { grid-column: 2; grid-row: 1; }
.game.hand-left .paper-rail { grid-column: 3; grid-row: 1; }
}
/* The first-person pane wears exactly the board's width, right above /* The first-person pane wears exactly the board's width, right above
* it: the stack shrink-wraps to the board, and width:0 + min-width * it: the stack shrink-wraps to the board, and width:0 + min-width
* keeps the pane out of the intrinsic sizing so the BOARD alone * keeps the pane out of the intrinsic sizing so the BOARD alone
@@ -3220,6 +3251,7 @@
.moment-eye:hover { opacity: 1; } .moment-eye:hover { opacity: 1; }
.table-edge { .table-edge {
grid-column: 1 / -1;
margin-top: 0.9rem; margin-top: 0.9rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
+5 -1
View File
@@ -22,6 +22,9 @@ export interface Prefs {
instantReplay: boolean; instantReplay: boolean;
/** The live first-person pane above the board during play. */ /** The live first-person pane above the board during play. */
liveFp: boolean; liveFp: boolean;
/** The hand of cards stands in a column at the board's left instead of
* lying along the bottom (wide screens only). */
handLeft: boolean;
} }
const KEY = "wizwar-prefs"; const KEY = "wizwar-prefs";
@@ -29,7 +32,7 @@ const KEY = "wizwar-prefs";
function load(): Prefs { function load(): Prefs {
const fallback: Prefs = { const fallback: Prefs = {
art: "photo", autoGrab: false, flourishes: true, wizardName: "", color: null, art: "photo", autoGrab: false, flourishes: true, wizardName: "", color: null,
cautions: true, botTier: "adept", instantReplay: true, liveFp: false, cautions: true, botTier: "adept", instantReplay: true, liveFp: false, handLeft: false,
}; };
try { try {
const raw = localStorage.getItem(KEY); const raw = localStorage.getItem(KEY);
@@ -45,6 +48,7 @@ function load(): Prefs {
botTier: p.botTier === "apprentice" || p.botTier === "archmage" ? p.botTier : "adept", botTier: p.botTier === "apprentice" || p.botTier === "archmage" ? p.botTier : "adept",
instantReplay: p.instantReplay !== false, instantReplay: p.instantReplay !== false,
liveFp: p.liveFp === true, liveFp: p.liveFp === true,
handLeft: p.handLeft === true,
}; };
} catch { } catch {
return fallback; return fallback;