diff --git a/web/src/lib/components/AttributionFooter.svelte b/web/src/lib/components/AttributionFooter.svelte
index 86ea2f0..de77c5c 100644
--- a/web/src/lib/components/AttributionFooter.svelte
+++ b/web/src/lib/components/AttributionFooter.svelte
@@ -31,10 +31,10 @@
* the credit is an obligation, not a decoration.
*/
.credit {
- padding-block: var(--space-3);
+ padding-block: var(--space-2);
padding-left: max(16px, env(safe-area-inset-left));
padding-right: max(16px, env(safe-area-inset-right));
- padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
+ padding-bottom: max(var(--space-2), env(safe-area-inset-bottom));
text-align: center;
color: var(--text-muted);
font-size: var(--text-2);
diff --git a/web/src/lib/components/GameBoard.svelte b/web/src/lib/components/GameBoard.svelte
index 5ef631c..a4090c2 100644
--- a/web/src/lib/components/GameBoard.svelte
+++ b/web/src/lib/components/GameBoard.svelte
@@ -84,7 +84,12 @@
{#if modeLabel}{modeLabel}{/if}
-
+
+ {#if game.state.phase !== 'over'}
+
+ {/if}
{#if banner}{@render banner()}{/if}
@@ -176,7 +181,7 @@
display: flex;
flex-direction: column;
flex: 1;
- gap: 14px;
+ gap: var(--space-3);
min-height: 0;
padding-bottom: var(--space-2);
}
diff --git a/web/src/lib/components/GameOverPanel.svelte b/web/src/lib/components/GameOverPanel.svelte
index 7516ee2..ba68b65 100644
--- a/web/src/lib/components/GameOverPanel.svelte
+++ b/web/src/lib/components/GameOverPanel.svelte
@@ -62,9 +62,11 @@
{endReasonMessages[result.reason]}
{/if}
- {#if standings.length > 2}
+ {#if standings.length > 1}
+ on. The score sits beside the place rather than setting it.
+ Shown for two players as well now that it is the only table on
+ the screen: the board's own stops at the final whistle. -->
{#each standings as player (player.playerId)}
-
@@ -97,7 +99,7 @@
nothing, which is worth hearing too. -->
{#if elimination.suggestions.length > 0}
-
{t.suggestionsTitle}
+
{t.suggestionsTitle}:
{/if}
@@ -130,8 +135,8 @@
.panel {
display: flex;
flex-direction: column;
- gap: var(--space-3);
- padding: var(--space-5);
+ gap: var(--space-2);
+ padding: var(--space-4);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
@@ -142,7 +147,7 @@
h2 {
margin: 0;
color: var(--danger);
- font-size: var(--text-8);
+ font-size: var(--text-7);
}
h2.won {
@@ -168,7 +173,8 @@
display: flex;
align-items: baseline;
gap: 10px;
- padding: var(--space-2) var(--space-3);
+ padding: var(--space-1) var(--space-3);
+ font-size: var(--text-5);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface-alt);
@@ -201,27 +207,48 @@
font-variant-numeric: tabular-nums;
}
+ /* One line rather than two stacked tiles. The score is in the table above
+ this, so the tiles were 40px of repeating it — and 40px is the
+ difference between the lobby's buttons being on a 1080p screen and
+ under it. */
.stats {
display: flex;
+ flex-wrap: wrap;
justify-content: center;
- gap: var(--space-8);
+ gap: var(--space-2) var(--space-5);
margin: 0;
}
+ .stats div {
+ display: flex;
+ align-items: baseline;
+ gap: var(--space-2);
+ }
+
dt {
color: var(--text-muted);
- font-size: var(--text-3);
+ font-size: var(--text-4);
}
dd {
margin: 0;
- font-size: var(--text-8);
+ font-size: var(--text-5);
font-weight: 700;
font-variant-numeric: tabular-nums;
}
+ /* Label and words on one line, wrapping when they run out of it: a heading
+ of its own cost a whole row of a screen the lobby's buttons are also on. */
+ .suggestions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ justify-content: center;
+ gap: 6px var(--space-2);
+ }
+
.suggestions h3 {
- margin: 0 0 6px;
+ margin: 0;
color: var(--text-muted);
font-size: var(--text-3);
font-weight: 600;
@@ -242,7 +269,7 @@
}
.suggestions li {
- padding: 6px var(--space-3);
+ padding: var(--space-1) var(--space-3);
border: 1px solid var(--border);
border-radius: var(--radius-pill);
background: var(--surface-alt);
@@ -263,26 +290,19 @@
font-weight: 700;
}
- .export {
- min-height: 44px;
- padding: 10px var(--space-3);
- border: 1px solid var(--border-strong);
- border-radius: var(--radius-sm);
- background: transparent;
- color: var(--text-muted);
- font-size: var(--text-5);
- font-weight: 600;
- }
-
.actions {
display: flex;
+ flex-wrap: wrap;
gap: 8px;
}
+ /* Wrapping rather than shrinking: three of these across a phone would
+ break "Tải chuỗi từ" over two lines, so the row gives way instead. */
.actions button {
- flex: 1;
+ flex: 1 1 auto;
+ min-width: 120px;
min-height: 44px;
- padding: var(--space-3);
+ padding: var(--space-2) var(--space-3);
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
background: var(--surface);
@@ -294,4 +314,11 @@
background: var(--accent);
color: var(--accent-text);
}
+
+ /* Keeping the chain is worth offering and not worth pressing first. */
+ .actions .export {
+ background: transparent;
+ color: var(--text-muted);
+ font-size: var(--text-5);
+ }
diff --git a/web/src/lib/components/Lobby.svelte b/web/src/lib/components/Lobby.svelte
index 215ae69..fa66ed9 100644
--- a/web/src/lib/components/Lobby.svelte
+++ b/web/src/lib/components/Lobby.svelte
@@ -36,7 +36,12 @@
const s = $derived(game.state);
// The seats nobody is in yet, drawn so a room that is waiting on people
// looks like one rather than like a room that is simply small.
- const empties = $derived(Array.from({ length: game.freeSeats }, (_, i) => i));
+ // Not under a finished game: there the count line above says 3/4 already,
+ // and three empty rows between the result and the button that starts the
+ // next game is what pushed that button off a 1080p screen.
+ const empties = $derived(
+ compact ? [] : Array.from({ length: game.freeSeats }, (_, i) => i)
+ );
const shortHanded = $derived(s.roomPlayers.length < s.minPlayers);
const offline = $derived(connection.status !== Status.OPEN);
// The owner is who everybody else is waiting on, so the hint has to stop
@@ -77,7 +82,7 @@
$effect(() => () => clearTimeout(armTimer));
-
+
{#if !compact}
@@ -239,7 +244,7 @@
display: flex;
flex-direction: column;
align-items: stretch;
- gap: 14px;
+ gap: var(--space-3);
}
.top {
@@ -268,12 +273,21 @@
flex-wrap: wrap;
align-items: baseline;
gap: 10px;
- padding: var(--space-3) 14px;
+ /* A seat is a row to read, not a target to hit, so it keeps the
+ type and gives up the padding: four of them decide whether the
+ buttons under the list are on screen. */
+ padding: var(--space-2) 14px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface);
}
+ /* Tighter still under a finished game, where the list is competing with a
+ result panel for the same screen. */
+ .compact .seat {
+ padding: var(--space-1) 14px;
+ }
+
/* Readiness is a tint. Being the owner is a marker down the edge: a fact
about the seat, not a state it has entered. */
.seat.ready {
@@ -401,7 +415,10 @@
.actions button {
flex: 1;
- padding: 14px;
+ /* Still a full 44px: readying and starting are what a player came
+ here to press. */
+ min-height: 44px;
+ padding: 10px var(--space-3);
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
background: var(--surface);
@@ -427,8 +444,8 @@
.leave {
align-self: center;
- min-height: 44px;
- padding: 10px var(--space-4);
+ min-height: 36px;
+ padding: var(--space-1) var(--space-4);
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm);
background: transparent;
diff --git a/web/src/lib/components/ScoreBoard.svelte b/web/src/lib/components/ScoreBoard.svelte
index 67e1703..db70a5a 100644
--- a/web/src/lib/components/ScoreBoard.svelte
+++ b/web/src/lib/components/ScoreBoard.svelte
@@ -68,7 +68,7 @@
align-items: center;
flex: 1 1 0;
min-width: 72px;
- padding: 6px var(--space-2);
+ padding: var(--space-1) var(--space-2);
border: 1px solid transparent;
border-radius: var(--radius-sm);
}
@@ -115,7 +115,7 @@
}
.score {
- font-size: var(--text-8);
+ font-size: var(--text-7);
font-weight: 700;
font-variant-numeric: tabular-nums;
}
diff --git a/web/src/lib/components/ThemeToggle.svelte b/web/src/lib/components/ThemeToggle.svelte
index e004128..38642e3 100644
--- a/web/src/lib/components/ThemeToggle.svelte
+++ b/web/src/lib/components/ThemeToggle.svelte
@@ -21,8 +21,8 @@
display: inline-flex;
align-items: center;
justify-content: center;
- width: 44px;
- height: 44px;
+ width: 40px;
+ height: 40px;
border: 1px solid var(--border-strong);
border-radius: 50%;
background: var(--surface);
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index 383dabd..2aec6c6 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -61,7 +61,10 @@
align-items: center;
justify-content: space-between;
gap: 12px;
- padding-block: 14px;
+ /* The header is chrome: every pixel of it is one the board and the
+ lobby below do not get, and on a 1080p screen the post-game panel
+ was the one that had to scroll for them. */
+ padding-block: var(--space-2);
padding-left: max(16px, env(safe-area-inset-left));
padding-right: max(16px, env(safe-area-inset-right));
}
diff --git a/web/src/routes/online/+page.svelte b/web/src/routes/online/+page.svelte
index d8b955b..7fe9c24 100644
--- a/web/src/routes/online/+page.svelte
+++ b/web/src/routes/online/+page.svelte
@@ -470,7 +470,7 @@
*/
.pane.game {
flex: 1;
- gap: var(--space-4);
+ gap: var(--space-3);
min-height: 0;
}