The ledger's box is trimmed after each turn to the header and as many of the newest whole rows as fit under its cap, and the header stacks above every body cell, so a long duel no longer shows the oldest visible row cut in half under the sticky header; the cap allows a few more rows on a tall screen; and the guide's picture of the hall is the hall as it is now

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
Eric WagonerandClaude Fable 5.1 committed 2026-10-29 11:10:19 -04:00
1 parent 4c224a8e22
commit 22979afd35
2 files changed
+48 -10

No files matched your search

+48 -10
View File
@@ -22,7 +22,7 @@
cells: Record<WizardId, Placed[]>; cells: Record<WizardId, Placed[]>;
} }
/** You first, then the others in table order; the gallery sees the table as laid. */ /** Column order: the viewer first, then the others in seat order; a spectator gets no column of their own. */
const columns = $derived<WizardId[]>(duel.spectating ? duel.others : [duel.me, ...duel.others]); const columns = $derived<WizardId[]>(duel.spectating ? duel.others : [duel.me, ...duel.others]);
function placed(who: WizardId, turn: number, phases: string[]): Placed[] { function placed(who: WizardId, turn: number, phases: string[]): Placed[] {
@@ -68,9 +68,42 @@
return !!h && h.who === who && h.hand === hand && index >= h.from && index <= h.to; return !!h && h.who === who && h.hand === hand && index >= h.from && index <= h.to;
} }
// The stylesheet caps the box at a share of the viewport, which is rarely a whole number of
// rows, so after each turn the cap is trimmed to the header plus as many of the newest rows
// as fit under it, and the box is scrolled to the newest. Older rows then sit wholly under
// the sticky header rather than showing a cut-off sliver. The second header row sticks at
// the measured height of the first, so the two meet exactly whatever the font or wrapping.
// Row heights change with the viewport and the web font, so this is redone on resize and
// once fonts are in.
function fitRows() {
if (!scroller) return;
scroller.style.maxHeight = '';
const names = scroller.querySelector('thead tr');
if (names) scroller.style.setProperty('--names-row', `${names.getBoundingClientRect().height}px`);
const cap = parseFloat(getComputedStyle(scroller).maxHeight);
if (Number.isFinite(cap)) {
const head = scroller.querySelector('thead')?.getBoundingClientRect().height ?? 0;
const chrome = scroller.offsetHeight - scroller.clientHeight;
let inner = head;
for (const row of [...scroller.querySelectorAll('tbody tr')].reverse()) {
const h = row.getBoundingClientRect().height;
if (inner > head && inner + h + chrome > cap) break;
inner += h;
}
scroller.style.maxHeight = `${Math.ceil(inner + chrome)}px`;
}
scroller.scrollTop = scroller.scrollHeight;
}
$effect(() => { $effect(() => {
void turns; void turns;
if (scroller) scroller.scrollTop = scroller.scrollHeight; fitRows();
});
$effect(() => {
window.addEventListener('resize', fitRows);
void document.fonts?.ready.then(fitRows);
return () => window.removeEventListener('resize', fitRows);
}); });
const crowded = $derived(duel.others.length >= 3); const crowded = $derived(duel.others.length >= 3);
@@ -221,7 +254,7 @@
} }
.scroller { .scroller {
max-height: clamp(170px, 30vh, 400px); max-height: clamp(170px, 38vh, 420px);
overflow: auto; overflow: auto;
border-top: 1px solid var(--rule-strong); border-top: 1px solid var(--rule-strong);
border-bottom: 1px solid var(--rule-strong); border-bottom: 1px solid var(--rule-strong);
@@ -259,16 +292,13 @@
left: 7.4rem; left: 7.4rem;
} }
thead th.pin-left,
thead th.pin-right {
z-index: 3;
}
thead th.gone { thead th.gone {
text-decoration: line-through; text-decoration: line-through;
color: var(--bone-dim); color: var(--bone-dim);
} }
/* The header must stack above every body cell, including the pinned ones (z-index 1 and 2 below),
or a row scrolled under it shows its turn number through the header while its letters are hidden. */
thead th { thead th {
position: sticky; position: sticky;
top: 0; top: 0;
@@ -277,7 +307,7 @@
text-align: center; text-align: center;
padding: 0.5rem 0 0.15rem; padding: 0.5rem 0 0.15rem;
font-size: 1.05rem; font-size: 1.05rem;
z-index: 1; z-index: 3;
} }
thead th.you, thead th.you,
@@ -287,7 +317,8 @@
} }
thead tr.hands th { thead tr.hands th {
top: 2.2rem; /* The measured height of the row above (set by fitRows), so the two sticky rows meet with no gap. */
top: var(--names-row, 2.2rem);
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 400; font-weight: 400;
color: var(--bone-dim); color: var(--bone-dim);
@@ -312,6 +343,13 @@
padding-top: 0.6rem; padding-top: 0.6rem;
} }
/* The header's own pinned cells sit above the rest of the header, which slides under them sideways. */
thead th.turn,
thead th.pin-left,
thead th.pin-right {
z-index: 4;
}
.rownote { .rownote {
display: block; display: block;
font-size: 0.6rem; font-size: 0.6rem;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 106 KiB