  :root {
    --bg: #15171c;
    --panel: #1e2128;
    --panel2: #262a33;
    --line: #333845;
    --text: #e6e8ec;
    --muted: #9aa3b2;
    --accent: #ffd24a;
    --empty: #2a2d34;
    --item: #caa23a;
    /* The two glyph inks. Every cell background takes whichever contrasts (see inkFor).
       Pure black/white on purpose: any softer pair caps out below WCAG AA. */
    --ink-light: #fff;
    --ink-dark: #000;
  }
  * { box-sizing: border-box; }
  /* The UA stylesheet hides [hidden] with display:none, and *any* author `display`
     outranks it (author origin beats UA, whatever the specificity). #setRow is a
     .row, which is display:flex, so the set picker rendered as an empty box on the
     16 single-set stages instead of disappearing. !important settles it for every
     hidden element, so the attribute means what it says no matter what else styles
     the element. jsdom drops this file, so no DOM test can catch a regression here;
     the guard is the stylesheet assertion in tests/app.test.js. */
  [hidden] { display: none !important; }
  body {
    margin: 0;
    font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.4;
    /* Flex column so the footer can sit at the bottom of the viewport when the
       content is short (desktop), and simply at the end of the scroll otherwise
       (mobile). No position:fixed, so it never overlays content. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
  }
  header {
    padding: 16px 20px;
    border-bottom: 1px solid var(--line);
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
  }
  header .head-main { min-width: 0; }
  header h1 { margin: 0 0 2px; font-size: 18px; }
  header p { margin: 0; color: var(--muted); font-size: 13px; }
  /* Language picker: a <details> menu of real per-locale links (see index.html). */
  #langPicker { flex: 0 0 auto; position: relative; }
  #langPicker > summary {
    list-style: none;
    cursor: pointer;
    padding: 6px 10px;
    font-size: 13px;
    white-space: nowrap;
    color: var(--text);
    background: var(--panel2);
    border: 1px solid var(--line);
    border-radius: 6px;
  }
  #langPicker > summary::-webkit-details-marker { display: none; }  /* Safari's default triangle */
  #langPicker > summary::after { content: " ▾"; color: var(--muted); }
  #langPicker > summary:hover { border-color: var(--muted); }
  #langMenu {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 60;                    /* over the board, under nothing else */
    display: grid;
    gap: 2px;
    padding: 6px;
    min-width: 168px;
    max-height: 60vh;
    overflow-y: auto;               /* 16 locales overflow a short viewport */
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,.45);
  }
  #langMenu a {
    display: block;
    padding: 6px 10px;
    border-radius: 5px;
    font-size: 13px;
    white-space: nowrap;
    color: var(--text);
    text-decoration: none;
  }
  #langMenu a:hover { background: var(--panel2); }
  #langMenu a[aria-current="true"] { color: var(--accent); font-weight: 600; }
  .wrap {
    display: flex;
    gap: 20px;
    padding: 20px;
    margin: auto 0;            /* absorb free vertical space top+bottom so the cluster centers between header and footer */
    align-items: stretch;      /* equal-height columns so the controls divider runs the full height */
    justify-content: center;   /* center the controls+board cluster; gutters stay balanced on wide screens */
    flex-wrap: wrap;
  }
  /* Flat, borderless layout: controls and board are columns, not cards. */
  .panel { padding: 0; }
  .controls { width: 320px; flex: 0 0 auto; border-right: 1px solid var(--line); padding-right: 24px; }
  .controls h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 8px; }
  .controls section { margin-bottom: 18px; }
  label { font-size: 14px; color: var(--muted); }
  input[type=number] {
    background: var(--panel2);
    border: 1px solid var(--line);
    color: var(--text);
    border-radius: 6px;
    padding: 5px 7px;
    width: 64px;
    font-size: 14px;
  }
  select {
    background: var(--panel2);
    border: 1px solid var(--line);
    color: var(--text);
    border-radius: 6px;
    padding: 5px 7px;
    font-size: 14px;
  }
  button {
    background: var(--panel2);
    border: 1px solid var(--line);
    color: var(--text);
    border-radius: 6px;
    padding: 6px 10px;
    font-size: 13px;
    cursor: pointer;
  }
  button:hover { border-color: #4a5160; }
  button.primary { background: var(--accent); color: #1a1a1a; border-color: var(--accent); font-weight: 600; }
  button.danger { color: #ff8a8a; }
  .quick-add { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
  .quick-add button { font-size: 12px; padding: 4px 7px; }
  table.pieces { width: 100%; border-collapse: collapse; font-size: 13px; }
  table.pieces th { text-align: left; color: var(--muted); font-weight: 500; padding: 4px 6px; }
  table.pieces td { padding: 3px 6px; border-top: 1px solid var(--line); }
  table.pieces tr.done td { color: var(--muted); }
  .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  /* Panel width is set in JS (a bit wider than the board) so a wide screen has
     balanced gutters and the legend/hint get more text width; the board is
     centered inside it (margin:auto in JS). */
  .stage-area { flex: 0 1 auto; min-width: 0; }
  #status { font-size: 14px; color: var(--muted); margin: 0 0 10px; min-height: 18px; }
  #status .warn { color: #ff9b6b; }
  /* The no-treasures notice points at the setup panel, which is the left column on
     desktop and sits below the board on mobile (.stage-area { order: -1 }). One
     string, two arrows, and the breakpoint picks which one is showing. */
  #status .point-down { display: none; }
  #grid {
    display: grid;
    gap: 4px;
    user-select: none;
  }
  .cell {
    position: relative;
    aspect-ratio: 1 / 1;
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1em;            /* inherits grid font-size (set in sizeBoard) so % scales with the board */
    font-weight: 600;
    cursor: pointer;
    border: 2px solid transparent;
    background: #333;
    /* background-color, not the `background` shorthand: the buried hatch is a
       background-image, which cannot interpolate, so transitioning the shorthand
       would fight the colour fade. `color` rides along so the ink doesn't pop
       white-to-dark while the tile is still crossfading under it. */
    transition: background-color .12s, color .12s, border-color .12s;
  }
  /* Ink is picked for contrast against each background (same rule as inkFor): light
     on the dark empty tile (13.8:1), dark on the light gold (8.7:1). The ⛏ lies
     across both hatch stripes, so the darker stripe sets the floor there (5.3:1). */
  .cell.empty { background: var(--empty); color: var(--ink-light); cursor: pointer; }
  .cell.item  { background: var(--item); color: var(--ink-dark); }   /* dug-out treasure (solid) */
  .cell.item.buried {                                                /* located but not dug yet */
    background-color: var(--item);                                   /* lerps from the heat colour */
    background-image: repeating-linear-gradient(45deg, transparent 0 5px, #9b7b29 5px 10px);
    color: var(--ink-dark);
  }
  .cell .sub { position: absolute; bottom: 2px; right: 4px; font-size: .7em; font-weight: 500; opacity: .8; }
  .cell .star { position: absolute; top: 1px; left: 3px; font-size: .77em; color: #fff; text-shadow: 0 0 3px #000; }
  .cell.best { border-color: #fff; box-shadow: 0 0 0 2px #fff, 0 0 10px 2px rgba(255,255,255,.55); z-index: 1; }
  .cell.hl { border-color: #fff; }
  .cell.dim { opacity: .35; }
  /* Two stacked rows: the 0–100% colour scale, then the status keys. Each row
     wraps its own entries as whole units, so a label never breaks onto a second
     line tucked under its swatch. */
  .legend { margin-top: 14px; font-size: 12px; color: var(--muted); }
  .legend-scale, .legend-keys { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 8px; row-gap: 7px; }
  .legend-keys { margin-top: 8px; column-gap: 16px; }
  .legend-scale > span, .legend-keys > span { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
  .legend .bar { height: 12px; width: 200px; border-radius: 6px;
    background: linear-gradient(90deg,
      hsl(240 70% 45%), hsl(180 70% 42%), hsl(120 65% 42%), hsl(60 80% 48%), hsl(0 80% 52%)); }
  /* popover */
  #pop {
    position: fixed;
    z-index: 50;
    background: var(--panel2);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 6px;
    box-shadow: 0 8px 24px rgba(0,0,0,.5);
    display: none;
    min-width: 180px;
    /* A long candidate list (e.g. a 2x4 on a big board) can outgrow the viewport;
       cap it and scroll inside rather than overflowing off-screen. The mobile
       .sheet variant sets its own max-height and overrides this. */
    max-height: calc(100vh - 16px);
    overflow-y: auto;
  }
  #pop .ttl { font-size: 11px; color: var(--muted); padding: 3px 6px 6px; }
  #pop button { display: block; width: 100%; text-align: left; margin: 2px 0; }
  #pop button.opt:hover { background: #3a4150; }
  #pop button.opt.sel { background: #3a4150; outline: 2px solid var(--accent); outline-offset: -2px; }
  #pop button.primary { text-align: center; background: var(--accent); color: #1a1a1a; font-weight: 600; margin-top: 6px; }
  #pop button.place { display: flex; align-items: center; gap: 8px; }
  #pop button.place span:last-child { flex: 1; }
  .mini { display: inline-grid; gap: 1px; width: 46px; flex: 0 0 auto; }
  .mini span { aspect-ratio: 1 / 1; background: #2c2f37; border-radius: 1px; }
  .mini span.f { background: var(--item); }
  .mini span.f.dug { background: #7a5f16; }       /* the tile you actually dug */
  .hint { font-size: 13px; color: var(--muted); margin-top: 6px; }

  /* taps feel instant (no 300ms double-tap-zoom wait) */
  .cell, button, select { touch-action: manipulation; }

  /* ---------- Mobile ---------- */
  @media (max-width: 720px) {
    .wrap { padding: 12px; gap: 12px; margin: 0; }   /* content fills the phone; no vertical centering */
    /* Restore the bordered cards on mobile (desktop is flat columns + divider). */
    .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; }
    .controls { width: 100%; border-right: none; padding: 14px 16px; }
    /* Grid first so the bottom-sheet picker covers the setup, not the board. */
    .stage-area { min-width: 0; flex-basis: auto; width: 100%; order: -1; }
    /* ...which puts the setup panel below the board, so the notice points down. */
    #status .point-left { display: none; }
    #status .point-down { display: inline; }
    input[type=number], select { font-size: 16px; }   /* >=16px stops iOS zoom-on-focus */
    /* popover becomes a bottom sheet: thumb-reachable, big targets, no finger occlusion */
    #pop.sheet {
      left: 0; right: 0; bottom: 0; top: auto;
      width: 100%; min-width: 0;
      border-radius: 14px 14px 0 0;
      max-height: 78vh; overflow-y: auto;
      padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
    }
    #pop.sheet .ttl { font-size: 13px; }
    #pop.sheet button { padding: 11px 12px; font-size: 15px; }
    .mini { width: 54px; }
    /* The chooser is a near-full-width sheet here and a 3×3 at 15px would crowd it. */
    .set-shapes { --shape-cell: 12px; gap: 12px; }
  }
  .credits-dialog {
    background: var(--panel); color: var(--text);
    border: 1px solid var(--line); border-radius: 12px;
    padding: 18px 18px 16px; max-width: 360px; width: calc(100% - 32px);
    box-shadow: 0 12px 40px rgba(0,0,0,.5);
  }
  .credits-dialog::backdrop { background: rgba(0,0,0,.55); }
  /* Patch notice. Same shell as the credits dialog, wider because it is prose. */
  /* Set chooser. Each option draws the set's treasures as gold blocks at their real
     proportions, so a player can match them against the game screen by shape instead of
     parsing "1×2 (×2), 2×2 (×2), 2×4". The dimensions stay underneath as the exact answer. */
  .set-dialog { max-width: 460px; font-size: 13px; line-height: 1.6; }
  .set-dialog h2 { margin: 0 0 10px; font-size: 16px; color: var(--text); }
  .set-dialog p { margin: 0 0 12px; color: var(--muted); }
  .set-opt {
    display: block; width: 100%; text-align: left; margin: 0 0 8px;
    padding: 10px 12px; background: var(--panel2); border: 1px solid var(--line);
    border-radius: 10px; color: var(--text); cursor: pointer; font: inherit;
  }
  .set-opt:hover, .set-opt:focus-visible { background: #3a4150; border-color: var(--accent); }
  .set-opt.custom { text-align: center; color: var(--muted); }
  /* Each block sits in its own column directly above its label. The row is bottom-aligned,
     so every label lands on one line and the blocks grow upward off it: a 1×4 reads as
     taller than a 1×2 without the text drifting out from under it. --shape-cell is the only
     thing to tune, the blocks being grids of it. */
  .set-shapes { --shape-cell: 15px; display: flex; align-items: flex-end; flex-wrap: wrap; gap: 8px 18px; }
  .set-shapes .piece { display: flex; flex-direction: column; align-items: center; gap: 7px; }
  .set-shapes .shape { display: inline-grid; gap: 2px; }
  .set-shapes .shape span {
    width: var(--shape-cell); height: var(--shape-cell);
    background: var(--item); border-radius: 2px;
  }
  .set-shapes .plabel { color: var(--muted); font-size: 12px; line-height: 1; white-space: nowrap; }

  .notice-dialog { max-width: 460px; font-size: 13px; line-height: 1.6; }
  .notice-dialog h2 { margin: 0 0 10px; font-size: 16px; color: var(--text); }
  .notice-dialog p { margin: 0 0 12px; color: var(--muted); }
  .notice-dialog blockquote {
    margin: 0 0 12px; padding: 8px 12px;
    border-left: 3px solid var(--accent); border-radius: 0 6px 6px 0;
    background: rgba(255,255,255,.03); color: var(--text);
  }
  .notice-actions { display: flex; gap: 14px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }
  .notice-actions a { color: var(--accent); }
  /* A button that reads as a link (the footer's Translators button predates this). */
  .linkish {
    background: none; border: none; padding: 0; margin-top: 6px;
    font: inherit; font-size: 13px; color: var(--accent);
    cursor: pointer; text-decoration: underline;
  }
