/* Just Sudoku — shared page chrome (colors, lang-switch, footer) matches the rest of the
   itkama.de site (see ../index.html, ../privacy/index.html): same --bg/--fg/--muted/--accent/
   --line variables and dark-mode values. The board itself needs a few extra roles the marketing
   pages don't (selection/peer/conflict highlighting) — those get their own --game-* variables
   below, taken from the Android app's ui/theme/Theme.kt fallback palette for exact parity with
   the app's board colors.

   --game-numpad-inactive-bg/-fg (the number pad's default, not-held-not-exhausted look) is its
   own pair rather than reusing --game-secondary-container/--game-surface-variant (the board's
   same-value/peer highlight colors): the two concerns happen to share values in this default
   palette, but a custom palette (see [data-theme="purple"] below) may reasonably want the number
   pad's resting color to differ from either board highlight, so they're decoupled from the start. */
:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #5a5a5a;
  --accent: #3060c0;
  --line: #dddddd;

  --game-primary: #3D5AFE;
  --game-on-primary: #ffffff;
  --game-primary-container: #DCE1FF;
  --game-on-primary-container: #00105C;
  --game-primary-fixed-dim: #5C6BC0;
  /* Violet rather than the teal this used to be: this fills both the same-value cells and the
     number pad, so a teal against the blue primary put a green block next to a blue board and read
     as two unrelated themes. A violet is analogous to the blue and still clearly separable from the
     blue selected-cell tone by hue. */
  --game-secondary-container: #D9C6FA;
  --game-error: #BA1A1A;
  --game-error-container: #FFDAD6;
  --game-surface-variant: #E1E2EC;
  --game-outline-variant: #C4C6D0;
  --game-numpad-inactive-bg: #D9C6FA;
  --game-numpad-inactive-fg: #21005E;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --fg: #f0f0f0;
    --muted: #a8a8a8;
    --accent: #8ab4ff;
    --line: #333333;

    --game-primary: #8C9EFF;
    --game-on-primary: #001A6B;
    --game-primary-container: #1A2F66;
    --game-on-primary-container: #DCE1FF;
    /* Violet, matching the light scheme above and for the same reason. */
    --game-secondary-container: #341C5F;
    --game-error: #FFB4AB;
    --game-error-container: #93000A;
    --game-surface-variant: #2A3350;
    --game-outline-variant: #3A3A3C;
    --game-numpad-inactive-bg: #341C5F;
    --game-numpad-inactive-fg: #E6DCFF;
  }
}

/* Every palette below is picked via the palette button and follows the system light/dark setting,
   matching the Android app — which has had a light and a dark variant per theme all along, while
   these were dark-only here. The light values are the app's own (ui/theme/Palettes.kt) rather than
   newly derived, so the two surfaces show the same colours for the same theme name.

   :root[data-theme="…"] outranks the bare :root blocks above on specificity alone (an attribute
   selector on :root beats a plain :root), so these win regardless of source order. Within each
   theme the light values come first and a prefers-color-scheme override follows — equal
   specificity, so the later one has to be the dark override, not the other way round. */
:root[data-theme="purple"] {
  color-scheme: light;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #4a4459;
  --accent: #613cdd;
  --line: #c4bddb;

  --game-primary: #613cdd;
  --game-on-primary: #ffffff;
  --game-primary-container: #aa92fc;
  --game-on-primary-container: #21005e;
  --game-primary-fixed-dim: #613cdd;
  --game-secondary-container: #cabbfb;
  --game-error: #ba1a1a;
  --game-error-container: #ffdad6;
  --game-surface-variant: #e3dcf9;
  --game-outline-variant: #c4bddb;
  --game-numpad-inactive-bg: #d6cbfb;
  --game-numpad-inactive-fg: #3d1f9e;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="purple"] {
    color-scheme: dark;
    --bg: #000000;
    --fg: #ffffff;
    --muted: #9c8bb5;
    --accent: #aa90fe;
    --line: #3a2a56;

    --game-primary: #aa90fe;
    --game-on-primary: #1a0f2e;
    --game-primary-container: #aa90fe;
    --game-on-primary-container: #1a0f2e;
    --game-primary-fixed-dim: #aa90fe;
    --game-secondary-container: #5b2c94;
    --game-error: #ffb4ab;
    --game-error-container: #93000a;
    --game-surface-variant: #31184f;
    /* Same hue as the selected-cell color, but at reduced opacity — full strength against a black
       background made even a 1px line read as loud, not actually thicker. */
    --game-outline-variant: rgba(170, 144, 254, 0.35);
    --game-numpad-inactive-bg: #31184f;
    --game-numpad-inactive-fg: #aa90fe;
  }
}

/* Pink, Orange and Blue below each follow the system light/dark setting, unlike Purple/Berry/
   Turquoise further down, which are fixed dark looks. Each is derived from a single base colour
   (#f7138f, #f76300, #386aa7) so the whole palette reads as one hue.

   Two rules came out of measuring contrast rather than eyeballing it, and they explain why the
   text colours are not simply the base:
   * Text-bearing roles sit well past the 4.5:1 minimum. Using the base itself put an entered digit
     on a same-value cell at ~2.4:1, because base and highlight fill are the same hue and end up too
     close in lightness. So dark mode lightens the base for text, light mode deepens it.
   * The selected cell keeps the vivid base as its fill, with whichever of dark or white text
     actually reads on it — dark for pink and orange, white for the mid-tone blue.
   Grid lines are the brightened tone at low opacity in dark mode (composites over whatever cell
   tint is underneath) and an opaque tinted grey in light mode, where a translucent accent would
   land on the light container tones and vanish inside a highlighted cell. Conflict/error stays the
   shared red in every theme, so "wrong" never turns into a theme colour. */
:root[data-theme="pink"] {
  color-scheme: light;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #6b4358;
  --accent: #ca0771;
  --line: #d9bfcd;

  --game-primary: #ca0771;
  --game-on-primary: #ffffff;
  --game-primary-container: #f88bc7;
  --game-on-primary-container: #3d0524;
  --game-primary-fixed-dim: #ca0771;
  --game-secondary-container: #fac2e0;
  --game-error: #ba1a1a;
  --game-error-container: #ffdad6;
  --game-surface-variant: #f9e1ee;
  --game-outline-variant: #d9bfcd;
  --game-numpad-inactive-bg: #fad1e7;
  --game-numpad-inactive-fg: #ca0771;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="pink"] {
    color-scheme: dark;
    --bg: #000000;
    --fg: #ffffff;
    --muted: #bb8ca5;
    --accent: #f957af;
    --line: #432334;

    --game-primary: #f957af;
    --game-on-primary: #2a0419;
    --game-primary-container: #f7138f;
    --game-on-primary-container: #2a0419;
    --game-primary-fixed-dim: #f957af;
    --game-secondary-container: #600b39;
    --game-error: #ffb4ab;
    --game-error-container: #93000a;
    --game-surface-variant: #350821;
    --game-outline-variant: rgba(249, 87, 175, 0.3);
    --game-numpad-inactive-bg: #350821;
    --game-numpad-inactive-fg: #f957af;
  }
}

:root[data-theme="orange"] {
  color-scheme: light;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #6c5241;
  --accent: #ad4600;
  --line: #dac9be;

  /* Deeper than the #f76300 base on purpose: white text on the base itself reaches only 3.1:1, so
     the base cannot carry button-sized text on a light background. */
  --game-primary: #ad4600;
  --game-on-primary: #ffffff;
  --game-primary-container: #fcb688;
  --game-on-primary-container: #3f1b03;
  --game-primary-fixed-dim: #ad4600;
  --game-secondary-container: #fcd8c0;
  --game-error: #ba1a1a;
  --game-error-container: #ffdad6;
  --game-surface-variant: #faebe1;
  --game-outline-variant: #dac9be;
  --game-numpad-inactive-bg: #fbe1d0;
  --game-numpad-inactive-fg: #ad4600;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="orange"] {
    color-scheme: dark;
    --bg: #000000;
    --fg: #ffffff;
    --muted: #bc9e8a;
    --accent: #ff6600;
    --line: #443022;

    --game-primary: #ff6600;
    --game-on-primary: #2c1302;
    --game-primary-container: #f76300;
    --game-on-primary-container: #2c1302;
    --game-primary-fixed-dim: #ff6600;
    --game-secondary-container: #632d08;
    --game-error: #ffb4ab;
    --game-error-container: #93000a;
    --game-surface-variant: #371a06;
    --game-outline-variant: rgba(255, 102, 0, 0.3);
    --game-numpad-inactive-bg: #371a06;
    --game-numpad-inactive-fg: #ff6600;
  }
}

:root[data-theme="blue"] {
  color-scheme: light;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #4c5661;
  --accent: #3768a4;
  --line: #c5cbd3;

  --game-primary: #3768a4;
  --game-on-primary: #ffffff;
  --game-primary-container: #a5bfdf;
  --game-on-primary-container: #122030;
  --game-primary-fixed-dim: #3768a4;
  --game-secondary-container: #cfdced;
  --game-error: #ba1a1a;
  --game-error-container: #ffdad6;
  --game-surface-variant: #e7edf3;
  --game-outline-variant: #c5cbd3;
  --game-numpad-inactive-bg: #dbe4f0;
  --game-numpad-inactive-fg: #3768a4;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="blue"] {
    color-scheme: dark;
    --bg: #000000;
    --fg: #ffffff;
    --muted: #97a2b0;
    --accent: #6e99ce;
    --line: #2b323b;

    /* The base #386aa7 is too dark to carry text on black (3.8:1), so text roles use a lighter
       tone; the base stays as the selected cell's fill. That cell's digit is white here rather than
       dark — the only theme where that is so — because dark on the base reaches only 3.3:1. */
    --game-primary: #6e99ce;
    --game-on-primary: #0d1621;
    --game-primary-container: #386aa7;
    --game-on-primary-container: #ffffff;
    --game-primary-fixed-dim: #6e99ce;
    --game-secondary-container: #1f334c;
    --game-error: #ffb4ab;
    --game-error-container: #93000a;
    --game-surface-variant: #121d2b;
    --game-outline-variant: rgba(110, 153, 206, 0.3);
    --game-numpad-inactive-bg: #121d2b;
    --game-numpad-inactive-fg: #6e99ce;
  }
}

/* Same given brand colors and dark (black) background as Pink above, but the same-value/peer/
   number-pad dark backgrounds are shades of *primary* here instead of the given accent — every
   dark surface stays a shade of the same berry-pink rather than mixing in Pink's more
   purple-leaning accent undertone. --accent itself is unchanged (still used for the
   language-switch highlight), just not the source for these three derived shades. Different
   enough from Pink in practice to keep as its own separate theme rather than a variant. */
:root[data-theme="berry"] {
  color-scheme: light;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #6b5560;
  --accent: #c2185b;
  --line: #d6b3c0;

  --game-primary: #c2185b;
  --game-on-primary: #ffffff;
  /* Deeper than Pink's selected cell: Berry's own same-value tone below is a light pink too, and
     the two have to stay clearly apart from each other. */
  --game-primary-container: #f584aa;
  --game-on-primary-container: #3e001d;
  --game-primary-fixed-dim: #c2185b;
  --game-secondary-container: #f7bacf;
  --game-error: #ba1a1a;
  --game-error-container: #ffdad6;
  --game-surface-variant: #f5e0e7;
  --game-outline-variant: #d6b3c0;
  --game-numpad-inactive-bg: #f6cbd9;
  --game-numpad-inactive-fg: #a5124c;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="berry"] {
    color-scheme: dark;
    --bg: #000000;
    --fg: #ffffff;
    --muted: #b8899e;
    --accent: #e040fb;
    --line: #4a1f30;

    --game-primary: #e91e63;
    --game-on-primary: #ffffff;
    --game-primary-container: #f8bbd0;
    --game-on-primary-container: #212121;
    --game-primary-fixed-dim: #f8bbd0;
    --game-secondary-container: #8c1245;
    --game-error: #ffb4ab;
    --game-error-container: #93000a;
    --game-surface-variant: #3a0f22;
    --game-outline-variant: rgba(233, 30, 99, 0.3);
    --game-numpad-inactive-bg: #3a0f22;
    --game-numpad-inactive-fg: #f8bbd0;
  }
}

/* Built from a single given base color (#52c4b7) with everything else derived from it, following
   Purple's structure: the base itself carries every "bright" role (held digit, selected cell,
   entered-digit text, number-pad text, accent), and the two board highlights plus the divider and
   secondary-text tones are the same hue at progressively lower lightness. Kept the derived
   lightness steps far enough apart that same-value (#1c5f58) can't be confused with the
   peer row/column tint (#10322f) — the mistake that once made a highlighted row read as one solid
   block in the Android app. Dark text on the base (not white): the base is light enough that
   white on it lands around 2:1 contrast, while this dark teal reaches ~7:1. Grid lines are the
   translucent base again, so they composite over whatever cell tint is underneath and stay
   visible on all of them. Conflict/error red is the shared dark-theme red as in Purple/Berry —
   deliberately not derived from the base, so "wrong" stays universally recognizable. */
:root[data-theme="turquoise"] {
  color-scheme: light;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #3f4947;
  --accent: #217d72;
  --line: #abc9c5;

  /* Deeper than the #52c4b7 base: white text on the base itself lands around 2:1, so the base
     cannot carry button-sized text on a light background. */
  --game-primary: #217d72;
  --game-on-primary: #ffffff;
  --game-primary-container: #90dfd6;
  --game-on-primary-container: #00201c;
  --game-primary-fixed-dim: #217d72;
  --game-secondary-container: #c2ebe6;
  --game-error: #ba1a1a;
  --game-error-container: #ffdad6;
  --game-surface-variant: #e0f0ee;
  --game-outline-variant: #abc9c5;
  --game-numpad-inactive-bg: #cfede9;
  --game-numpad-inactive-fg: #1a6a61;
}

@media (prefers-color-scheme: dark) {
  :root[data-theme="turquoise"] {
    color-scheme: dark;
    --bg: #000000;
    --fg: #ffffff;
    --muted: #8bb1ae;
    --accent: #52c4b7;
    --line: #234340;

    --game-primary: #52c4b7;
    --game-on-primary: #0d2b28;
    --game-primary-container: #52c4b7;
    --game-on-primary-container: #0d2b28;
    --game-primary-fixed-dim: #52c4b7;
    --game-secondary-container: #1c5f58;
    --game-error: #ffb4ab;
    --game-error-container: #93000a;
    --game-surface-variant: #10322f;
    --game-outline-variant: rgba(82, 196, 183, 0.3);
    --game-numpad-inactive-bg: #10322f;
    --game-numpad-inactive-fg: #52c4b7;
  }
}

* { box-sizing: border-box; }

/* .screen and .dialog-overlay below each set their own `display`, which — at equal CSS
   specificity — would otherwise silently override the `hidden` attribute's native
   `display: none` and leave a "hidden" screen/dialog visible on top of everything else. */
[hidden] { display: none !important; }

html, body {
  height: 100%; /* fallback for browsers without dvh support (still fine, just the old bug) */
  height: 100dvh;
  /* 100% (and old-style 100vh) means the *largest possible* viewport on mobile Safari/Chrome —
     with the address bar collapsed — not the currently-visible one. With the address bar shown
     (the common case right after loading), that made the page a bit taller than what's actually
     visible, pushing the number pad's second row below the fold. 100dvh ("dynamic viewport
     height") tracks the real, current viewport instead. */

  /* Kills the iOS/Android rubber-band bounce past the edges of the page. The layout below is
     built to exactly fill the viewport (flex column, footer/lang-switch at their natural size,
     #app taking the rest) so there's normally nothing to scroll in the first place — this just
     also removes the elastic overscroll glow some browsers still show even then. */
  overscroll-behavior-y: none;
  /* The app never intentionally scrolls sideways — a stray sub-pixel rounding overflow anywhere
     (e.g. splitting the number pad's row width across 5 buttons) is enough for mobile browsers to
     allow a slight horizontal pan/scroll otherwise, which throws off centering just enough to
     notice without any content actually being cut off. This forecloses that regardless of cause. */
  overflow-x: hidden;
}

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--fg);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

button {
  font-family: inherit;
  border: none;
  cursor: pointer;
  background: none;
  color: inherit;
}
button:disabled { opacity: 0.4; cursor: default; }

/* Icon buttons (header actions + toolbar) hold a single inline SVG each — same stroke style
   throughout (line icons, no emoji/glyph mix) so they read as one consistent set. */
.game-header button svg, .toolbar button svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: block;
}

/* body is a flex column filling the exact viewport height: lang-switch and the footer keep
   their natural size, #app takes whatever's left — nothing left over to scroll. */
.lang-switch {
  flex: 0 0 auto;
  text-align: right;
  font-size: 0.9rem;
  padding: 1rem 1rem 0;
}
.lang-switch button {
  background: none;
  border: none;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  padding: 0.2rem 0.4rem;
}
.lang-switch button:hover,
.lang-switch button[aria-current="true"] { color: var(--accent); }

.site-footer {
  flex: 0 0 auto;
  margin: 0.5rem auto 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: 0.9rem;
  color: var(--muted);
  max-width: 480px;
  width: 100%;
}
.site-footer a { color: var(--muted); }

#app {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.screen {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 16px;
  /* Safety valve only: the layout is sized to fit without scrolling, but on a genuinely too-short
     viewport this lets content still reach rather than getting silently clipped. */
  overflow-y: auto;
}

/* --- Difficulty screen --- */
#screen-difficulty { justify-content: center; gap: 12px; }
#screen-difficulty h1 { margin-bottom: 24px; }
.difficulty-list { display: flex; flex-direction: column; gap: 12px; width: 100%; max-width: 280px; }
.difficulty-button {
  padding: 14px;
  border-radius: 8px;
  background: var(--game-primary);
  color: var(--game-on-primary);
  font-size: 1.05rem;
}

/* --- Game screen ---
   A CSS Grid with named areas so portrait and landscape can reflow the exact same 4 elements
   (header/board/toolbar/numpad) without touching the HTML or JS at all — just which areas go
   where, and how many columns/rows there are. */
#screen-game {
  display: grid;
  grid-template-areas: "header" "board" "toolbar" "numpad";
  /* minmax(0, 1fr), not plain 1fr: a 1fr track's implicit minimum is its content's min-content
     width, so the number pad's non-shrinkable buttons were widening the column past the viewport —
     measured at 435px of content inside a 430px viewport, which is the slight sideways scroll that
     turned up on Android. The overflow-x: hidden further up only hid that; this is the actual fix. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto auto;
  align-items: stretch;
  gap: 12px;
  max-width: 480px;
  margin: 0 auto;
  width: 100%;
}

.game-header {
  grid-area: header;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  font-size: 1.1rem;
}
.game-header .header-actions { display: flex; gap: 8px; }
.game-header button, .toolbar button { padding: 6px; border-radius: 8px; }

.board {
  grid-area: board;
  display: grid;
  grid-template-columns: repeat(9, 1fr);
  grid-template-rows: repeat(9, 1fr);
  width: 100%;
  aspect-ratio: 1;
  border: 2px solid var(--fg);
  outline: none;
  touch-action: none;
}

.cell {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  border-right: 1px solid var(--game-outline-variant);
  border-bottom: 1px solid var(--game-outline-variant);
  cursor: pointer;
  user-select: none;
  /* A muted, always-the-same-tone-in-both-themes accent for user-entered digits (distinct from
     a given clue's plain --fg text below), matching the app's `primaryFixedDim` role. */
  color: var(--game-primary-fixed-dim);
}
.cell.box-right { border-right: 3px solid var(--fg); }
.cell.box-bottom { border-bottom: 3px solid var(--fg); }

/* Background priority (mutually exclusive, applied in this order by app.js):
   selected > conflict > same-value > peer > plain. Text-color priority is separate and only
   these three rules affect it, later ones winning ties: selected, then given (must beat
   selected — you can select a given cell), then conflict-text (must beat everything). */
.cell.peer { background: var(--game-surface-variant); }
.cell.same-value { background: var(--game-secondary-container); }
.cell.selected { background: var(--game-primary-container); color: var(--game-on-primary-container); }
.cell.given { font-weight: 700; color: var(--fg); }
.cell.conflict { background: var(--game-error-container); }
.cell.conflict-text { color: var(--game-error); }

.pencil-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  width: 100%;
  height: 100%;
  padding: 2px;
}
.pencil-grid span {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.55rem;
  color: var(--fg);
}

.toolbar {
  grid-area: toolbar;
  display: flex;
  justify-content: space-around;
  width: 100%;
}
.toolbar button.active { background: var(--game-primary-container); color: var(--game-on-primary-container); }

/* Flexbox + wrap + centered justify-content (not CSS grid): this way, when the trailing row of
   digits has fewer than a full row's worth, it centers itself automatically instead of
   left-aligning with a lopsided gap — no JS layout math needed. 5 columns in portrait (bigger,
   easier-to-tap buttons); 3 in landscape, in its own dedicated right-hand column — mirroring the
   Android app's own portrait/landscape split. */
.number-pad {
  grid-area: numpad;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-content: center;
  gap: 4px;
  width: 100%;
}
.digit-button {
  flex: 0 0 auto;
  aspect-ratio: 1;
  border-radius: 8px;
  font-size: 1.1rem;
  background: var(--game-numpad-inactive-bg);
  color: var(--game-numpad-inactive-fg);
  touch-action: none;
}
.digit-button.exhausted { opacity: 0.4; }
.digit-button.held { background: var(--game-primary); color: var(--game-on-primary); opacity: 1; }

@media (orientation: portrait) {
  .digit-button { flex-basis: calc((100% - 4 * 4px) / 5); }
}

/* All landscape overrides live in this one block, deliberately placed after every base rule
   above rather than scattered next to what each one modifies: a media query does not raise a
   rule's priority over a later rule of equal selector specificity, so an earlier landscape-only
   `.toolbar { justify-content: center }` was silently overridden right back to the base
   `.toolbar { justify-content: space-around }` declared further down the file — the layout kept
   looking like the un-overridden default no matter what the media query said. Keeping every
   landscape rule below all the rules it touches makes that class of bug impossible here. */
@media (orientation: landscape) {
  /* Three columns: board left, the toolbar ("functions") in a narrow middle column, number pad
     right — each with its own dedicated space instead of the toolbar/number pad previously
     sharing one column (which is also what made board-height vs. right-column-height so hard to
     keep in sync). The header (title/timer/reset/share/close) spans the full width up top instead,
     since it's page-level chrome rather than an in-game action. --board-size/--digit-size are
     still computed by app.js (updateLandscapeLayout) from the real measured space, the same way
     the Android app's BoxWithConstraints does — the values below only matter as a same-frame
     fallback, before JS has run once. */
  #screen-game {
    grid-template-areas: "header header header" "board toolbar numpad";
    grid-template-columns: var(--board-size, min(60vmin, 420px)) 84px minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    max-width: 1000px;
  }
  #board {
    width: var(--board-size, min(60vmin, 420px));
    height: var(--board-size, min(60vmin, 420px));
    aspect-ratio: unset;
  }
  .toolbar {
    flex-direction: column;
    justify-content: center;
    gap: 12px;
  }
  .game-header button svg, .toolbar button svg { width: 26px; height: 26px; }
  /* Same JS-measured sizing as #board above and for the same reason: a width-only flex-basis
     doesn't know how much vertical room is actually available, so a 3-wide row could end up
     taller than the space it has to fit in. --digit-size is capped by both the available width
     and height (app.js), mirroring the app's own NumberPad.kt exactly. */
  .digit-button {
    flex-basis: var(--digit-size, calc((100% - 2 * 4px) / 3));
    width: var(--digit-size, auto);
    height: var(--digit-size, auto);
    aspect-ratio: unset;
  }
}

.dialog-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
.dialog {
  background: var(--bg);
  color: var(--fg);
  border-radius: 12px;
  padding: 24px;
  max-width: 320px;
  width: 100%;
  text-align: center;
  /* The theme picker is seven options tall. In landscape on a phone that overflows the
     viewport in both directions at once — the flex centering pushes the title off the top
     and the last button off the bottom, with no way to reach either. Resolves against the
     overlay's content box, so the 16px padding around it stays. */
  max-height: 100%;
  overflow-y: auto;
}
.dialog p { color: var(--muted); }
.dialog button {
  margin: 16px 4px 0;
  padding: 10px 20px;
  border-radius: 8px;
  background: var(--game-primary);
  color: var(--game-on-primary);
}
.dialog button.secondary {
  background: none;
  color: var(--muted);
  border: 1px solid var(--line);
}

/* Higher-specificity selector (.dialog button.theme-option, not just .theme-option) so this
   reliably wins over the plain ".dialog button" rule above regardless of source order — see the
   landscape-layout comment further up for why that's not something to leave to chance here. */
.dialog button.theme-option {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin: 8px 0 0;
  padding: 10px 14px;
  border-radius: 8px;
  background: none;
  border: 1px solid var(--line);
  color: var(--fg);
  text-align: left;
  font-size: 0.95rem;
}
.dialog button.theme-option.active { border-color: var(--game-primary); border-width: 2px; }
.theme-swatch {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid rgba(128, 128, 128, 0.4);
}

.update-banner {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: calc(100% - 32px);
  padding: 10px 10px 10px 16px;
  border-radius: 8px;
  background: var(--game-primary);
  color: var(--game-on-primary);
  font-size: 0.9rem;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
  z-index: 10;
}
.update-banner button {
  flex: 0 0 auto;
  padding: 6px 14px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.25);
  font-weight: 600;
}
