:root {
  --bg1: #0f172a;
  --bg2: #134e5a;
  --ink: #e2e8f0;
  --dim: #94a3b8;
  --line: rgba(255, 255, 255, .16);
  --accent: #38bdf8;
  --good: #22c55e;
  --pill: rgba(255, 255, 255, .12);
  --card: #ffffff;
  --cardInk: #14303a;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
/* R-C15: the page itself never scrolls, and nothing chains out of it. */
html, body {
  margin: 0; padding: 0; height: 100%;
  overflow: hidden; overscroll-behavior: none;
}
body {
  height: 100vh; height: 100dvh;
  display: flex; flex-direction: column;
  font-family: ui-rounded, "SF Pro Rounded", "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 50% 0%, var(--bg2), var(--bg1));
  color: var(--ink);
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  touch-action: manipulation;
}

#hdr {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px 6px;
  padding-top: calc(10px + env(safe-area-inset-top));
  padding-left: calc(14px + env(safe-area-inset-left));
  padding-right: calc(14px + env(safe-area-inset-right));
}
.pill {
  background: var(--pill); border-radius: 999px; padding: 7px 12px;
  font-size: 15px; font-weight: 700; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* R-C19: a number the player WATCHES CHANGE gets tabular figures. Measured on
   word-search: the clock pill swings between 51.3px and 66.3px as it counts,
   and the pill beside it slides 8px sideways — every second, and again at
   0:09 -> 0:10. Crossword found this, fixed its own clock, and wrote the
   reason into its own stylesheet, where the other four games could not read
   it. Fourth time a lesson has been recorded only where it was learned. */
#runInfo { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px;
           overflow: hidden; font-variant-numeric: tabular-nums; }
#hudTheme { flex: 0 1 auto; min-width: 0; }
.iconBtn {
  background: var(--pill); border: none; color: #fff; border-radius: 999px;
  flex: 0 0 auto; width: 46px; height: 46px; font-size: 20px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: transform .08s;
}
.iconBtn:active { transform: scale(.9); }
.iconBtn[hidden] { display: none; }
/* ---- the hint control (docs/offline-pwa.md R-C16) -------------------------
   ONE bulb, and the count as an OVERLAID badge. Absolutely positioned inside
   `.bulb`, so 10 -> 9 -> 8 can never change the button's width and shove
   whatever sits beside it — which is exactly what the inline count did on a
   phone. The number is always how many are LEFT, it is shown at 1 and at 0,
   and at 0 the button is disabled with the badge still on it, because a
   control that vanishes teaches a child nothing about why.
   The badge is dark with a pale ring at every game, on purpose: the games run
   from near-black to amber to a light theme, and a dark pill with a light halo
   is the one combination that reads on all of them. */
.hintBtn { position: relative; overflow: visible; }
.hintBtn .bulb { position: relative; display: inline-block; line-height: 1; }
.hintBtn .hintN {
  position: absolute; right: -9px; bottom: -6px;
  min-width: 16px; height: 16px; padding: 0 3px; border-radius: 999px;
  background: #0f172a; color: #f8fafc; box-shadow: 0 0 0 1.5px rgba(255, 255, 255, .6);
  font: 800 10px/16px inherit; letter-spacing: 0; pointer-events: none;
}
.hintBtn:disabled .hintN, .hintBtn[disabled] .hintN { background: #64748b; }
/* `wide` = the bulb is followed by a WORD. The badge hangs 9px past the
   bulb, so without this it sits on the first letter of the label. */
.hintBtn.wide .bulb { margin-right: 11px; }

/* Same call snake makes: below 480px the pills take a row of their own rather
   than being sliced by overflow:hidden, because four 46px targets and three
   pills do not both fit and a button may never give way. */
@media (max-width: 480px) {
  #hdr { flex-wrap: wrap; row-gap: 6px; }
  #runInfo { order: 2; flex: 1 1 100%; justify-content: center; overflow: visible; }
  #hdrHelp { margin-left: 0; }
  #hdrHome { margin-right: auto; }
}

/* R-C12: header and footer are pinned, so the frame between them names its
   scroll region — and it is the WORD LIST, never the page. */
#stage {
  position: relative; flex: 1; min-height: 0;
  display: flex; flex-direction: column; align-items: center;
  padding: 0 8px env(safe-area-inset-bottom);
  gap: 6px;
}
/* `width: 100%` is load-bearing, and its absence was a real bug.
   #stage is a COLUMN flex with align-items:center, so a child that does not
   claim the cross axis shrink-wraps its content — and this one's content is
   the canvas, whose size is computed FROM this box's width. The container was
   sized by the thing it was supposed to be sizing, so the grid froze at
   whatever the first layout pass produced and could never grow back: measured
   at 300px inside a 359px stage, throwing away 59px and filling 80% of the
   smaller dimension when it should fill nearly all of it. */
#board {
  flex: 1 1 auto; min-height: 0; width: 100%;
  display: flex; align-items: center; justify-content: center;
}
#cv { touch-action: none; border-radius: 14px; box-shadow: 0 10px 40px rgba(0, 0, 0, .45); }

/* R6b: the list is the other half of the puzzle, so it is always on screen.
   Capped at a third of the stage and scrollable inside itself, contained so a
   flick through a long list cannot drag the frame. */
#wordList {
  flex: 0 0 auto; width: 100%; max-height: 28%;
  overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 5px 7px; padding: 2px 2px 6px;
}
.wsw {
  font-size: 14px; font-weight: 800; letter-spacing: .4px;
  padding: 5px 10px; border-radius: 999px;
  background: rgba(255, 255, 255, .09); color: var(--ink);
  border: 1px solid var(--line);
}
/* A long list is a shorter board. #wordList and #board share the vertical axis,
   so twelve words wrapping to a third row costs the grid about 26px — measured
   live, the biggest mix dropped the grid to 86% of the smaller dimension while
   the default sits at 93%.
   Denser chips buy the row back rather than capping the list lower, which would
   only move the problem into a scrollbar the child then has to use. */
#wordList.dense { gap: 4px 5px; }
#wordList.dense .wsw { font-size: 12px; padding: 3px 8px; letter-spacing: .2px; }
/* v1.2: a Colossal puzzle lists 25 words, and past about fifteen no amount of
   shrinking keeps them all on screen at once. The list stays scrollable (it
   always was) and gets small enough that the FIRST rows are visible without
   scrolling, which is the half of R6b that actually matters — you need to see
   that there is a list and roughly how much of it is left, not all of it at
   once. Below 11px it stops being readable, so this is the last step down. */
#wordList.denser { gap: 3px 4px; max-height: 30%; }
#wordList.denser .wsw { font-size: 11px; padding: 2px 6px; letter-spacing: 0; }

.wsw.got {
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  opacity: .55;
  border-color: transparent;
}

/* Landscape: the list moves BESIDE the grid.
   A square grid is bounded by the smaller of the two dimensions, so on a wide
   short window the height is what limits it — and stacking the word list under
   the grid spends the scarce axis on the thing that does not need it. Beside
   it, the grid gets the full height and the list gets width there is plenty
   of.
   `min-aspect-ratio: 1/1` rather than `orientation: landscape`, because the
   question is "is this box wider than it is tall", which is also true of a
   short desktop window that no phone would call landscape. */
@media (min-aspect-ratio: 1/1) {
  #stage { flex-direction: row; align-items: stretch; gap: 8px; }
  #board { width: auto; flex: 1 1 auto; min-width: 0; }
  #wordList {
    width: auto; flex: 0 0 auto;
    min-width: 108px; max-width: 34%;
    max-height: none;
    flex-direction: column; flex-wrap: wrap;
    align-content: flex-start; justify-content: flex-start;
    padding: 6px 0;
    /* Column-wrap overflows SIDEWAYS, so `overflow-y` — correct while the list
       was a wrapping row — governed the wrong axis here and a 25-word list was
       simply clipped. */
    overflow: auto;
    /* And it needs the full height to wrap INTO, or it makes one 842px-wide
       row and calls that wrapping. Measured: 25 chips wanted 842px of width in
       an 282px panel until this line existed. */
    align-self: stretch;
  }
  /* A Colossal list is two or three columns wide. The grid is bounded by the
     HEIGHT in landscape — 280px of a 284px box — so the extra width costs it
     nothing, which is the whole reason the row layout exists.
     `max-height: none` has to be repeated at THIS specificity: the portrait
     `.denser` cap is (1,1,0) and the `#wordList` rule above is (1,0,0), so the
     more specific one wins no matter which comes later in the file. It capped
     the panel at 30% of 284px — 85px — and a column-wrap list with 85px to
     wrap into makes one 847px row and calls it wrapped. */
  #wordList.dense, #wordList.denser { max-width: 46%; max-height: none; }
  .wsw { font-size: 13px; padding: 4px 9px; }
}

.overlay {
  position: absolute; inset: 0;
  display: flex; align-items: flex-start; justify-content: center;
  overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  background: rgba(8, 20, 30, .62);
  backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px);
  z-index: 5; padding: 16px;
}
.overlay.hidden { display: none; }
.card {
  background: var(--card); color: var(--cardInk); border-radius: 24px;
  padding: 24px 22px 20px; width: min(360px, 94%);
  margin: auto; flex: 0 0 auto; text-align: center;
  box-shadow: 0 16px 50px rgba(0, 0, 0, .45);
  animation: pop .25s ease-out;
}
@keyframes pop { from { transform: scale(.85); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .card { animation: none; } }
.card .big { font-size: 52px; line-height: 1; }
.card h1 { margin: 6px 0 4px; font-size: 28px; font-weight: 800; }
.card .hint { margin: 4px 0 14px; font-size: 14px; color: #4f6b76; font-weight: 600; }
.card .lbl { margin: 14px 0 6px; font-size: 13px; color: #4f6b76; font-weight: 700; text-align: left; }
.blurb { margin: 6px 0 4px; font-size: 13px; line-height: 1.4; color: #4f6b76; font-weight: 600; min-height: 1em; }
.howBody { text-align: left; font-size: 15px; line-height: 1.5; }
.howBody p { margin: 0 0 9px; }

.seg { display: flex; gap: 6px; }
.seg.wrap { flex-wrap: wrap; }
.opt {
  flex: 1 1 auto; min-width: 0; min-height: 44px; padding: 8px 8px;
  border-radius: 12px; border: 2px solid #d6e3e8; background: #f1f7f9;
  color: var(--cardInk); font: 800 14px/1.2 inherit; font-family: inherit;
  cursor: pointer; transition: transform .08s;
}
.opt:active { transform: scale(.96); }
.opt[aria-checked="true"] { border-color: var(--good); background: #e6f8ec; }

.btn {
  display: block; width: 100%; border: none; border-radius: 16px; padding: 14px;
  margin-top: 10px; font: 800 18px/1 inherit; font-family: inherit;
  cursor: pointer; background: #eaf1f4; color: var(--cardInk);
  transition: transform .08s;
}
.btn:active { transform: scale(.96); }
.btn.primary {
  background: linear-gradient(180deg, #38bdf8, #0ea5e9); color: #06283a;
  box-shadow: 0 5px 0 #0369a1;
}
.btn.primary:active { box-shadow: 0 2px 0 #0369a1; transform: translateY(3px) scale(.98); }
.stats { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px 18px; font-size: 19px; margin: 8px 0 12px; }

#toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(58px + env(safe-area-inset-bottom));
  background: rgba(8, 20, 30, .92); color: #fff;
  padding: 9px 15px; border-radius: 999px; font-size: 14px; font-weight: 700;
  opacity: 0; pointer-events: none; transition: opacity .18s; z-index: 8;
  max-width: 88vw; text-align: center;
}
#toast.on { opacity: 1; }
.hidden { display: none !important; }

/* The Settings disclosure. Same shape as maze's: the tiers are on the surface
   and the switches are one tap away, so the menu is a choice of three for a
   parent and a workbench for a child who wants one. */
#custom { margin: 10px 0 4px; text-align: left; }
#custom > summary {
  cursor: pointer; list-style: none;
  font: 800 14px/1 inherit; color: #4f6b76;
  padding: 12px 4px; min-height: 44px;
  display: flex; align-items: center; gap: 6px;
}
#custom > summary::-webkit-details-marker { display: none; }
#custom > summary::before { content: "▸"; transition: transform .12s; }
#custom[open] > summary::before { transform: rotate(90deg); }
.field { display: flex; align-items: center; gap: 10px; margin: 8px 0; }
.field .lbl { flex: 0 0 52px; font-size: 13px; color: #4f6b76; font-weight: 700; }
.field .seg { flex: 1; min-width: 0; }
.field .opt { font-size: 13px; padding: 6px 4px; }
/* The secret-mode caption is not a word, so it must not look like one you
   could go and find. */
.wsw.secret {
  background: none; border-style: dashed; opacity: .8;
  font-weight: 700; letter-spacing: 0;
}
