:root {
  --bg: #ffffff;
  --fg: #1a1a1b;
  --border: #d3d6da;
  --border-strong: #878a8c;
  --key-bg: #d3d6da;
  --accent: #ffb703;
  --found: #4caf7d;
  --selected: #5a6fd8;
  /* Palette assigned round-robin to a puzzle's target words, so each solved word gets its own color. */
  --word-0: #e0663e;
  --word-1: #d1a92e;
  --word-2: #4caf7d;
  --word-3: #2f9e9e;
  --word-4: #3b82c4;
  --word-5: #6f6fd8;
  --word-6: #c94fb0;
  --word-7: #d1477a;
  --diff-easy: #4caf7d;
  --diff-moderate: #d1a92e;
  --diff-hard: #c9504a;

  /* Shared geometry/timing tokens, factored out wherever a value repeated 2+ times in this file —
     not applied to genuinely single-use values, which would just be indirection with no DRY payoff. */
  --radius-sm: 6px; /* buttons, menu rows, pills-that-aren't-pills */
  --radius-md: 8px; /* elevated surfaces: menu-panel, pause-overlay, modal-content */
  --radius-pill: 999px; /* badges, word chips */
  --duration-fast: 0.15s;

  /* Text color for content sitting on top of a saturated/colored surface — a found cell, a selected
     pill, a primary button — as opposed to --fg, which is body text on the plain --bg surface.
     --on-accent is specifically for content on --accent (amber/gold): that surface stays light-toned
     in both themes (see dark block below), so its readable text color doesn't need a dark-mode
     override the way --on-color conceptually could if a saturated color ever did. */
  --on-color: #fff;
  --on-accent: #1a1a1b;

  /* Elevated-surface (modal/popover) treatment — see "Modal look" further down for how these combine. */
  --shadow-sm: 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 24px 60px -12px rgba(0, 0, 0, 0.45), 0 8px 20px rgba(0, 0, 0, 0.3);
  --overlay-bg: rgba(0, 0, 0, 0.6);
  --surface-border: rgba(0, 0, 0, 0.08);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121325;
    --fg: #ffffff;
    --border: #3a3a4c;
    --border-strong: #565868;
    --key-bg: #2a2b40;
    --accent: #ffcc4d;
    --found: #3f8f6a;
    --selected: #7b8cf0;
    --word-0: #ff8b63;
    --word-1: #e6c34d;
    --word-2: #6fce9f;
    --word-3: #4fc4c4;
    --word-4: #6badea;
    --word-5: #9797f0;
    --word-6: #e070d0;
    --word-7: #ea6f9a;
    --diff-easy: #3f8f6a;
    --diff-moderate: #b59f3b;
    --diff-hard: #a8433d;
    /* Darker + more diffuse than the light-theme overlay: a plain 60%-black backdrop barely reads
       as "dimmed" against this theme's own already-dark page background, so it needs to go darker
       here to still visually separate the popup from the page behind it. */
    --overlay-bg: rgba(0, 0, 0, 0.72);
    --surface-border: rgba(255, 255, 255, 0.08);
  }
}

* { box-sizing: border-box; }

html {
  /* Mobile Safari/Chrome silently boost font-size on narrow text blocks ("text inflation") when
     the real device's viewport is narrower than its screen — a heuristic desktop dev-tools device
     emulation doesn't reproduce, which is why the footer only looked oversized on an actual phone.
     Disabling it makes every font-size render at the CSS value we actually wrote everywhere. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: "Helvetica Neue", Arial, sans-serif;
  background: var(--bg);
  color: var(--fg);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 40px;
  touch-action: manipulation;
}

header {
  width: 100%;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

.header-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.header-row-top {
  justify-content: space-between;
}

/* Two-row (mobile) layout only: level/found badges stay left, timer+pause push to the right edge
   of their row. Scoped to below the single-row breakpoint so it doesn't fight with h1's own
   flex-grow, which already does the equivalent push once everything's back on one line. */
@media (max-width: 639px) {
  .header-row-bottom #timer {
    margin-left: auto;
  }
}

/* Once the header has its full 640px available (its own max-width cap, so this is "as wide as
   this header ever gets" rather than an arbitrary breakpoint), everything actually fits on one
   line — collapse the two stacked rows back into a single row instead of leaving a half-empty
   second line. `display: contents` drops the two wrapper divs from the box tree so h1/badges/
   pause/timer/actions become direct flex children of <header> itself; `order` then places them
   left-to-right in the same sequence the single-row layout used to use. Below this width, the
   wrappers keep their own boxes (order has no effect there, since it only applies among elements
   that are siblings in the same flex formatting context) and the two-row stacked layout applies. */
@media (min-width: 640px) {
  header {
    flex-direction: row;
    align-items: center;
    flex-wrap: nowrap;
  }
  .header-row-top,
  .header-row-bottom {
    display: contents;
  }
  header h1 { order: 1; flex: 1 1 auto; }
  #level-badge { order: 2; }
  #found-badge { order: 3; }
  #timer { order: 4; }
  #pause-btn { order: 5; }
  .header-actions { order: 6; }
}

header h1 {
  font-size: clamp(1.1rem, 5vw, 1.6rem);
  letter-spacing: 0.03em;
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.found-badge,
.level-badge {
  display: none;
  vertical-align: middle;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  font-size: 0.6rem;
  letter-spacing: 0.04em;
  font-weight: bold;
  color: var(--on-color);
  white-space: nowrap;
}

.found-badge {
  display: inline-block;
  background: var(--selected);
}

.level-badge.visible {
  display: inline-block;
  text-transform: uppercase;
}

.level-badge.easy { background: var(--diff-easy); }
.level-badge.moderate { background: var(--diff-moderate); }
.level-badge.hard { background: var(--diff-hard); }

.timer {
  font-variant-numeric: tabular-nums;
  font-weight: bold;
  font-size: 0.9rem;
  color: var(--border-strong);
  flex-shrink: 0;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

@media (max-width: 420px) {
  .header-btn {
    padding: 0 8px;
    font-size: 0.7rem;
  }
  .icon-btn {
    width: 32px;
    height: 32px;
  }
}

.icon-btn,
.header-btn {
  background: none;
  border: 1px solid var(--border-strong);
  color: var(--fg);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-weight: bold;
  transition: background var(--duration-fast) ease, border-color var(--duration-fast) ease;
}

.icon-btn:hover,
.header-btn:hover {
  background: var(--key-bg);
}

.icon-btn:disabled,
.header-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.icon-btn:disabled:hover,
.header-btn:disabled:hover {
  background: none;
}

.icon-btn {
  width: 36px;
  height: 36px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
}

.header-btn {
  height: 36px;
  padding: 0 14px;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.menu-wrap {
  position: relative;
}

.menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 200px;
  max-height: 70vh;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 20;
}

.menu-panel.hidden {
  display: none;
}

.menu-item {
  background: none;
  border: none;
  color: var(--fg);
  text-align: left;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-weight: bold;
  font-size: 0.9rem;
}

.menu-item:hover {
  background: var(--key-bg);
}

.menu-separator {
  height: 1px;
  background: var(--border);
  margin: 2px 0;
}

.menu-label {
  font-size: 0.7rem;
  color: var(--border-strong);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0 2px;
}

.lang-options {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.lang-option {
  background: none;
  border: 1px solid transparent;
  color: var(--fg);
  text-align: left;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-weight: bold;
  font-size: 0.85rem;
}

.lang-option:hover {
  background: var(--key-bg);
}

.lang-option.selected {
  background: var(--found);
  border-color: var(--found);
  color: var(--on-color);
}

main {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 16px;
  width: 100%;
  max-width: 640px;
}

#board-column {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: 100%;
}

#board-wrap {
  position: relative;
  width: min(92vw, 520px);
  aspect-ratio: 1;
}

/* #board and #badge-layer overlay each other exactly (same grid geometry, same box) so badges can
   float outside their cell's edge without ever being covered by a neighboring cell: they live in a
   layer entirely above #board, not inside the cell they belong to. See AGENTS.md for why that
   matters — badges used to be per-cell children and got clipped by sibling cells' paint order. */
#board,
#badge-layer {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  grid-template-rows: repeat(var(--cols), 1fr);
  gap: 2px;
}

#badge-layer {
  pointer-events: none;
  z-index: 3;
}

#board-wrap.paused #board,
#board-wrap.paused #badge-layer {
  visibility: hidden;
}

.pause-overlay {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--bg);
  border-radius: var(--radius-md);
  border: 2px solid var(--border-strong);
}

.pause-overlay.hidden {
  display: none;
}

.pause-overlay .pause-icon {
  font-size: 2.2rem;
}

.pause-overlay p {
  margin: 0;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--border-strong);
}

.word-list.paused {
  filter: blur(8px);
  pointer-events: none;
  user-select: none;
}

.cell {
  position: relative;
  aspect-ratio: 1;
  border: none;
  border-radius: 3px;
  background: var(--key-bg);
  color: var(--fg);
  display: flex;
  align-items: center;
  justify-content: center;
  /* --cell-size is measured in JS from the actual rendered cell box (see updateCellFontSize()) —
     a viewport-width-based formula looked fine in landscape (vw is the board's long axis there)
     but was too small in portrait, where vw is the narrow axis and unrelated to actual cell size. */
  font-size: clamp(0.6rem, calc(var(--cell-size, 32px) * 0.5), 1.5rem);
  font-weight: bold;
  text-transform: uppercase;
  user-select: none;
  cursor: pointer;
  padding: 0;
  touch-action: none; /* dragging across cells to select a word must never trigger page scroll */
}

.cell.selected {
  background: var(--selected);
  color: var(--on-color);
}

.cell.found {
  color: var(--on-color);
}

.cell.bonus {
  background: var(--accent);
  color: var(--on-accent);
}

.cell.bonus-reveal {
  box-shadow: inset 0 0 0 3px var(--accent);
}

.cell.word-color-0 { background: var(--word-0); }
.cell.word-color-1 { background: var(--word-1); }
.cell.word-color-2 { background: var(--word-2); }
.cell.word-color-3 { background: var(--word-3); }
.cell.word-color-4 { background: var(--word-4); }
.cell.word-color-5 { background: var(--word-5); }
.cell.word-color-6 { background: var(--word-6); }
.cell.word-color-7 { background: var(--word-7); }

.badge {
  /* Placed in the same grid cell as the letter it marks (via JS grid-row/grid-column), then pulled
     to that cell's top-right corner and nudged outward with the transform — floating fully outside
     the cell's own box. Safe to overhang here because #badge-layer sits above the whole #board as
     one layer, so this can never be covered by a neighboring cell (see #badge-layer's comment). */
  justify-self: end;
  align-self: start;
  transform: translate(45%, -45%);
  min-width: 18px;
  height: 18px;
  padding: 0 3px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.6rem;
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px var(--bg);
}

#message {
  min-height: 1.5em;
  font-weight: bold;
  text-align: center;
}

#message:not(:empty) {
  color: var(--found);
}

/* Groups the topic heading with the word list so `main` still only ever sees two flex children
   (#board-column and this) — see the landscape override at the end of this file for why that
   matters. */
#word-list-column {
  width: 100%;
}

.topic-label {
  margin: 0 0 8px;
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--border-strong);
  text-align: center;
}

.word-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  width: 100%;
}

.word-chip {
  background: var(--key-bg);
  color: var(--fg);
  border-radius: var(--radius-pill);
  padding: 5px 12px;
  font-size: 0.8rem;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  text-align: center;
}

.word-chip.found {
  color: var(--on-color);
  text-decoration: line-through;
  opacity: 0.85;
}

.word-chip.bonus-chip-found {
  background: var(--accent);
  color: var(--on-accent);
}

.word-chip.word-color-0 { background: var(--word-0); }
.word-chip.word-color-1 { background: var(--word-1); }
.word-chip.word-color-2 { background: var(--word-2); }
.word-chip.word-color-3 { background: var(--word-3); }
.word-chip.word-color-4 { background: var(--word-4); }
.word-chip.word-color-5 { background: var(--word-5); }
.word-chip.word-color-6 { background: var(--word-6); }
.word-chip.word-color-7 { background: var(--word-7); }

/* Modal look: a darker, softly blurred backdrop (--overlay-bg — noticeably dimmer than the page's
   own contrast, so the popup reads as the one thing in focus) behind a borderless-looking card —
   --surface-border is a low-alpha 1px hairline rather than the old bold 2px var(--border-strong),
   with the card's edge instead mostly defined by --shadow-lg's soft, wide spread. backdrop-filter is
   progressive enhancement: unsupported browsers just get the plain --overlay-bg dim, no fallback
   needed since -webkit-/unprefixed both no-op safely when absent. */
.modal {
  position: fixed;
  inset: 0;
  background: var(--overlay-bg);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;
}

.modal.hidden {
  display: none;
}

.modal-content {
  position: relative;
  background: var(--bg);
  color: var(--fg);
  /* No top padding here — .modal-header supplies its own padding-top instead, so it can sit flush
     against this element's own top edge (the actual scrollport boundary, since overflow-y:auto's
     scrolling starts at the padding edge) and stick there. If this kept a top padding like the other
     three sides, that padding would sit *above* .modal-header and stay in the scrollport's static
     "always visible" region, but .modal-header's own sticky top:0 would then be offset from the
     wrong edge — the two would visually separate as the content scrolled underneath. */
  padding: 0 24px 24px;
  border-radius: var(--radius-md);
  border: 1px solid var(--surface-border);
  box-shadow: var(--shadow-lg);
  max-width: 420px;
  width: 90%;
  max-height: 85vh;
  overflow-y: auto;
  animation: modal-in var(--duration-fast) ease;
}

@keyframes modal-in {
  from { transform: scale(0.95); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* Keeps the title (and close button) visible while a modal's own content scrolls beneath it —
   position: sticky against .modal-content's scrollport, opaque --bg so scrolled text doesn't show
   through. Supplies the padding-top .modal-content itself no longer has (see there) so the header
   sits exactly where the old all-sides padding used to put the title, then the sticky positioning
   keeps it pinned there through a scroll instead of scrolling away with everything else. Unlike
   .category-select-start-btn's edge-to-edge sticky footer, this doesn't need negative side margins
   or its own border-radius — it isn't full-bleed, it's inset by .modal-content's own left/right
   padding like any other child, so it never reaches (and doesn't need to mask) the rounded corners. */
.modal-header {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg);
  padding-top: 24px;
}

.modal-content h2 {
  margin-top: 0;
  padding-right: 32px; /* room for .modal-close-btn pinned to the top-right corner */
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-bottom: 1px solid var(--border);
  padding-bottom: 12px;
}

.modal-content h3 {
  font-size: 0.9rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--border-strong);
  margin-bottom: 6px;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 16px;
}

.btn-primary,
.btn-secondary {
  padding: 10px 18px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.85rem;
}

.btn-primary {
  background: var(--found);
  color: var(--on-color);
}

.btn-secondary {
  background: var(--key-bg);
  color: var(--fg);
}

.menu-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 4px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-weight: bold;
  vertical-align: middle;
}

#close-help-btn,
#close-stats-btn,
#close-history-btn {
  margin-top: 12px;
}

/* Anchors to .modal-header (its parent, position: sticky — sticky establishes a containing block
   the same way relative does), not .modal-content directly, so it scrolls together with the sticky
   title instead of scrolling away with the rest of the modal's content. */
.modal-close-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 28px;
  height: 28px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--fg);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
}

.modal-close-btn:hover {
  background: var(--key-bg);
}

/* Consistent vertical rhythm for this modal: 16px between major sections (title / difficulty /
   hint / list), 4px between individual rows within a section — every margin/padding below is one
   of those two numbers (or a divisor of the row gap) rather than an ad hoc value. */
.select-modal-content {
  max-width: 480px;
  /* A block parent's own non-zero padding blocks vertical margin collapsing at that edge — with
     padding-bottom left at the base .modal-content 24px, .category-select-start-btn's negative
     margin-bottom (meant to pull it flush against the popup's bottom edge) would only cancel space
     *above* it, not escape this padding, leaving a visible gap below the button. Zeroing it here
     hands that space entirely to the button's own margin/padding instead. */
  padding-bottom: 0;
}

.select-modal-content h2 {
  font-size: 1.05rem;
  padding-bottom: 10px;
}

.select-modal-content .menu-label {
  display: block;
  margin: 16px 0 6px;
}

.select-level-options {
  flex-direction: row;
  gap: 6px;
  margin: 0 0 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.select-level-options .lang-option {
  flex: 1;
  text-align: center;
}

#category-select-hint {
  font-size: 0.7rem;
  color: var(--border-strong);
  letter-spacing: 0.03em;
  margin: 0 0 16px;
}

.category-select-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 0;
}

.select-row {
  width: 100%;
  font-size: 0.85rem;
}

.tree-row {
  display: flex;
  align-items: stretch;
  gap: 4px;
}

.tree-toggle {
  flex-shrink: 0;
  width: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--fg);
  cursor: pointer;
  font-size: 1.3rem;
  font-weight: bold;
  line-height: 1;
  border-radius: var(--radius-sm);
}

.tree-toggle:hover {
  background: var(--key-bg);
}

.tree-label-btn {
  flex: 1;
}

/* The category label only indicates "this category is expanded and contains the pending pick" —
   it's not itself a pick the way a subcategory/"All Mixed" row is, so it gets a muted tint (the same
   neutral --key-bg already used for hover states) rather than the bright --found green, which is
   reserved for .tree-sub-row.selected (a real subcategory row, or the "All Mixed" row — both
   .tree-sub-row) so there's exactly one bright-green highlight in the tree at a time: whichever row
   Start will actually use. Two things sharing the same bright green read as two equally "selected"
   picks, which isn't true — only the row matters. */
.tree-label-btn.selected {
  background: var(--key-bg);
  color: var(--fg);
}

.tree-sub-row.selected {
  background: var(--found);
  color: var(--on-color);
}

.tree-subcategories {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-left: 32px;
  margin-top: 2px;
}

.tree-subcategories.hidden {
  display: none;
}

.tree-sub-row {
  font-size: 0.8rem;
  color: var(--fg);
}

.tree-mixed-row {
  font-style: italic;
  border-top: 1px solid var(--border);
  border-radius: 0;
  margin-top: 4px;
  padding-top: 12px;
}

.category-select-list.hidden {
  display: none;
}

/* Sticks to the bottom of the popup's own scroll area (.modal-content is the scroll container, see
   .modal-content's overflow-y: auto) while the category tree scrolls beneath it. Negative side
   margins cancel .modal-content's own side padding so this bar spans edge-to-edge — that part of
   the escape-parent-padding trick works fine, since horizontal margins aren't subject to margin
   collapsing. The *bottom* edge instead relies on .select-modal-content zeroing its padding-bottom
   (see there) rather than a negative margin, since a block parent's own non-zero padding blocks
   vertical margin collapsing at that edge — a negative margin-bottom here would only have pulled
   the button closer to the content above it, not escaped the parent's padding below it.
   Deliberately its own color (--selected, the blue used for found-badge/selected cells), not
   .btn-primary's green — every segmented switch above it (difficulty, category mode) already uses
   that same green for its active pick, so a green Start button here would visually blend in as
   "just another selected option" instead of reading as the one action that actually commits them.
   Bottom corners use --radius-md (the popup's own radius), not --radius-sm — they sit flush against
   .modal-content's actual rounded corners, so mismatching the radius would show as a visible seam. */
.category-select-start-btn {
  position: sticky;
  bottom: 0;
  width: calc(100% + 48px);
  margin: 16px -24px 0;
  padding: 14px 24px;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: var(--selected);
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.2);
}

.stats-summary,
.stats-distribution {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 12px 0;
}

.stat-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.9rem;
  padding: 4px 0;
  border-bottom: 1px solid var(--border);
}

/* Each entry needs several fields (topic, level, date, time, words, bonus), unlike a plain
   label/value .stat-row — two lines per entry instead of one. */
.history-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 12px 0;
}

.history-empty {
  font-size: 0.9rem;
  color: var(--border-strong);
  margin: 12px 0;
}

.history-entry {
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.history-entry-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.history-topic {
  font-weight: bold;
  font-size: 0.9rem;
}

.history-entry-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  font-size: 0.8rem;
  color: var(--border-strong);
}

.history-bonus {
  color: var(--accent);
  font-weight: bold;
}

#site-footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 8px 16px;
  text-align: center;
  font-size: 0.8rem;
  color: var(--border-strong);
  background: var(--bg);
  border-top: 1px solid var(--border);
  z-index: 5;
}

@media (max-width: 380px) {
  #site-footer {
    font-size: 0.7rem;
  }
}

#site-footer a {
  color: var(--border-strong);
  font-weight: bold;
  text-decoration: none;
}

#site-footer a:hover {
  text-decoration: underline;
}

.footer-version {
  opacity: 0.6;
}

.footer-version::before {
  content: " \00b7 "; /* middot separator, matches the em-space rhythm of the rest of the line */
}

/* Wide-enough landscape: word list moves to a side column instead of stacking below the board.
   Gated on min-width too so short landscape phones (little vertical room, but not much width
   either) keep the portrait stacked layout rather than squeezing everything sideways. Kept at the
   very end of the file on purpose — #board-wrap/.word-list have unconditional base rules further
   up that set the same properties (width, flex-direction); equal-specificity CSS resolves ties by
   source order regardless of the media query, so putting this earlier let those base rules win and
   silently no-op the whole landscape layout. */
@media (orientation: landscape) and (min-width: 700px) {
  main {
    flex-direction: row;
    align-items: flex-start;
    justify-content: center;
    max-width: 960px;
    gap: 24px;
  }

  #board-column {
    width: auto;
    flex: 0 1 auto;
  }

  #board-wrap {
    width: min(70vh, 480px);
  }

  /* Sizing/scrolling lives on the wrapper now, not .word-list itself, so the topic-label heading
     stays pinned above the list instead of scrolling away with the chips. */
  #word-list-column {
    display: flex;
    flex-direction: column;
    width: 200px;
    flex-shrink: 0;
    max-height: min(75vh, 600px);
    gap: 8px;
  }

  .word-list {
    flex-direction: column;
    flex-wrap: nowrap; /* base rule sets wrap; column+wrap overflows into extra columns instead of scrolling */
    align-items: stretch;
    flex: 1 1 auto;
    min-height: 0; /* lets a flex child actually shrink below its content size so overflow-y can kick in */
    overflow-y: auto;
  }
}
