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)}
  1. @@ -97,7 +99,7 @@ nothing, which is worth hearing too. --> {#if elimination.suggestions.length > 0}
    -

    {t.suggestionsTitle}

    +

    {t.suggestionsTitle}:

      {#each elimination.suggestions as word}
    • {word}
    • @@ -115,14 +117,17 @@

      {t.newRecord}

      {/if} +
      {#if onrematch} {/if} +
      - -
    {/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; }