* { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  /* ═══════════════════════════════════════════════════════════════════
     TWO PALETTES, ONE VOCABULARY

     The tier names below are permanent. What changes is their values.
     This block is the LIVE game — the green theme players see today.
     The block after it is the cut-paper redesign, which only appears
     when <html data-theme="paper"> is set. Launch = swapping which
     block is the default, and nothing else.
     ═══════════════════════════════════════════════════════════════════ */

  /* Tier 1 · Ground — legacy values, matching the shipped green theme */
  --sand:  #d4ecd4;
  --dune:  #f0c060;
  --kelp:  #7a9e5c;
  --reef:  #3498db;
  --coral: #e74c3c;
  --abyss: #162516;

  --abyss-sink: #191207;
  --abyss-deep: #0d1f0d;
  --abyss-lift: #1e3320;
  --abyss-edge: #2e4f2e;
  --sand-pale:  #ffffff;
  --sand-mute:  #7aaa7a;
  --stone-pale: #c8c0ae;
  --stone-deep: #6e6857;
  --dune-pale:  #f0c878;
  --dune-deep:  #c47f3a;
  --dune-dark:  #4a3b1e;
  --kelp-pale:  #a3d17e;
  --kelp-lift:  #4caf50;
  --kelp-deep:  #2e4a20;
  --reef-pale:  #7ec8f0;
  --reef-lift:  #3a8abf;
  --reef-deep:  #3f7fa8;
  --coral-pale: #ff8a7a;
  --coral-deep: #c0392b;

  /* Tier 2 · Light — night emission only, never a fill */
  --lit-biolume: #4ff0d0;
  --lit-moon:    #d8f6ff;
  --lit-living:  #52d97e;
  --lit-ember:   #ff6a3c;

  /* Tier 3 · Material — inside icons and illustration only */
  --mat-wood:   #8a6434;
  --mat-thread: #ece0c4;
  --mat-stone:  #4b6b78;
  --mat-stone-lift: #6b8b96;
  --mat-metal:  #c9d4d8;
  --mat-husk:   #6b4c26;
  --mat-carve:  #8a5a34;

  /* Tier 4 · Player — a castaway, their map dot, log row, card edge. Never chrome. */
  --pl-sailcloth: #e4d6b4;  --pl-sailcloth-d: #c9b990;
  --pl-reef:      #3f9aa0;  --pl-reef-d:      #2b7a80;
  --pl-clay:      #b5654a;  --pl-clay-d:      #944e38;
  --pl-kelp:      #4e8757;  --pl-kelp-d:      #3a6a42;
  --pl-sunset:    #e0785f;  --pl-sunset-d:    #c25a44;
  --pl-indigo:    #4a6f96;  --pl-indigo-d:    #365577;
  --pl-ochre:     #d3a04e;  --pl-ochre-d:     #b3813a;
  --pl-drift:     #9a9384;  --pl-drift-d:     #7d7668;
  /* The castaways themselves. Everything they wear is salvaged, so the
     palette is the eight colourways above plus a few neutrals that are not
     a colourway in their own right — you cannot be "Sand Wrap". */
  --pl-wrap-sand: #d8c49a;  --pl-band-sea:    #3f6b78;
  --pl-band-moss: #6a8f5e;  --pl-blush:       #e0785f;
  --pl-thread:    #ece0c4;  --pl-thread2:     #d6c8a4;
  --pl-rope:      #a8813f;  --pl-ink:         #0c2e3d;
  /* The tone a castaway is actually painted in. The figures use these two and
     nothing else, so .skin-a/.skin-b/.skin-c on any wrapper re-points them and
     the whole drawing follows. Defaults to skin-b, the middle tone. */
  --pl-skin:      #d9a97a;  --pl-skin-d:      #bf8c5f;
  --pl-skin-a:    #f2d9a0;  --pl-skin-a-d:    #dcc08a;
  --pl-skin-b:    #d9a97a;  --pl-skin-b-d:    #bf8c5f;
  --pl-skin-c:    #a87246;  --pl-skin-c-d:    #8a5a34;

  /* Tier 5 · Semantic */
  --good: var(--kelp-lift);
  --warn: var(--dune);
  --bad:  var(--coral);

  /* Legacy names — the 494 existing rules retint through these ten lines */
  --bg:     var(--abyss-deep);
  --card:   var(--abyss);
  --card2:  var(--abyss-lift);
  --border: var(--abyss-edge);
  --text:   var(--sand);
  --muted:  var(--sand-mute);
  --gold:   var(--dune);
  --green:  var(--kelp-lift);
  --red:    var(--coral);
  --blue:   var(--reef);
  --radius: 12px;
}

/* ═══════════════════════════════════════════════════════════════════════
   THE CUT-PAPER THEME — Deep Reef

   Not live. Reach it with ?theme=paper (it sticks), leave with ?theme=off.
   Same token names, new values, so every rule in this file follows without
   being touched. To launch: move these values into the block above.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] {
  --sand:  #f2d9a0;
  --dune:  #c9a35f;
  --kelp:  #1e6b4a;
  --reef:  #17888f;
  --coral: #e85d6e;
  --abyss: #0c2e3d;

  --abyss-sink: #04121a;
  --abyss-deep: #071a24;
  --abyss-lift: #123b4d;
  --abyss-edge: #1c4f60;
  --sand-pale:  #f7e6bd;
  --sand-mute:  #a49b80;
  --stone-pale: #c8c0ae;
  --stone-deep: #6e6857;
  --dune-pale:  #e0c48a;
  --dune-deep:  #a8813f;
  --dune-dark:  #6b4c26;
  --kelp-pale:  #7fd4a0;
  --kelp-lift:  #2e9463;
  --kelp-deep:  #155338;
  --reef-pale:  #8fd4ef;
  --reef-lift:  #3aa0d8;
  --reef-deep:  #0c6b73;
  --coral-pale: #ff9a8a;
  --coral-deep: #c94456;

  /* The four island maps are inline SVG in index.html. Their colours are tokens
     whose fallback is the original value, so the live theme resolves to exactly
     what it always did and only this block repaints them. Retint, not redraw —
     the flat cut-paper rebuild is still to come. */
  --map-abyss:   #0a2a38;
  --map-deep:    #10495a;
  --map-sea:     #17727e;
  --map-shelf:   #17888f;
  --map-shallow: #3fb3bd;
  --map-foam:    #8fd4ef;
  --map-canopy:  #155338;
  --map-forest:  #1e6b4a;
  --map-scrub:   #2e9463;
  --map-frond:   #7fd4a0;
  --map-soil:    #4a3620;
  --map-bark:    #6b4c26;
  --map-rock:    #8a6434;
  --map-dune:    #c9a35f;
  --map-sand:    #f2d9a0;
  --map-pale:    #e0c48a;
  --map-bone:    #f7e6bd;
  --map-sun:     #fff3cc;
  --map-coral:   #e85d6e;
  /* fire, named for the role. --map-rock already stands in for nine
     different live values, which is how the volcano's lava came out
     the colour of beach sand. */
  --map-fire:      #e08a3c;
  --map-fire-core: #ffe9b8;
  /* lava is its own role, not the campfire's colour. A campfire is
     orange-yellow; molten rock is red with a hotter core. One token
     for both would mean 'make the lava redder' also reddens the fire. */
  --map-lava:      #cf3617;
  --map-lava-core: #ff8f3a;
  /* the kraken. Deep red-purple, so it is plainly not coral — the reef
     heads next door are bright pink and this must not read as more of
     them. Named for the role, like the lava and the fire. */
  --map-kraken:      #7d3a5c;
  --map-kraken-deep: #4e2039;
  /* the logo palm's own two lift tones, from bushier-palm-aligned-rope */
  --map-trunk-lit:  #d6b070;
  --map-trunk-deep: #a8813f;
  /* weathered stone, so the ruins stop reading as the same brown as the cave */
  --map-stone:      #9aa39b;
  --map-stone-deep: #6b7570;

  /* The night palette. Same names with an -n prefix, so the chart can be
     repainted by reassigning tokens rather than by filtering pixels —
     which is the only approach that works in WebKit, and is also the
     artifact's own: same paths, second stylesheet, fills swapped. */
  --n-shelf:       #0b2028;
  --n-sea:         #0a1c24;
  --n-deep:        #071620;
  --n-shallow:     #124049;
  --n-abyss:       #040d13;
  --n-sand:        #13202a;
  --n-dune:        #101b23;
  --n-pale:        #16232c;
  --n-bone:        #182631;
  --n-soil:        #0a1017;
  --n-bark:        #0e1820;
  --n-rock:        #121d26;
  --n-canopy:      #0a2419;
  --n-forest:      #0d2a1e;
  --n-scrub:       #103425;
  --n-frond:       #133a2a;
  --n-frond-lit:   #17452f;
  --n-trunk-lit:   #16222a;
  --n-trunk-deep:  #101a21;
  --n-stone:       #1d2a31;
  --n-stone-deep:  #131e25;

  --map-frond-lit: #46b06a;

  /* Tier 3 · Material — the tokens the action glyphs are cut from. Declared
     here as well as in the live block: a token in one palette and not the
     other is how a screen ends up half one theme and half the other. Same
     values for now — Deep Reef's own materials are still to be tuned. */
  --mat-wood:   #8a6434;
  --mat-thread: #ece0c4;
  --mat-stone:  #4b6b78;
  --mat-stone-lift: #6b8b96;
  --mat-metal:  #c9d4d8;
  --mat-husk:   #6b4c26;
  --mat-carve:  #8a5a34;

  --text: var(--sand-pale);
}

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 16px;
  line-height: 1.5;
}

/* ── Screens ── */
.screen { display: none; }
.screen.active { display: block; }

/* ── Lobby ── */
#screen-lobby, #screen-waiting, #screen-gameover {
  min-height: 100vh;
  min-height: -webkit-fill-available;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
#screen-lobby.active,
#screen-waiting.active,
#screen-gameover.active {
  display: flex;
}

/* Lobby stacks the card above the credit line */
#screen-lobby.active { flex-direction: column; }


.credit {
  color: var(--muted);
  font-size: 0.75rem;
  letter-spacing: 0.03em;
  margin-top: 18px;
  opacity: 0.6;
  text-align: center;
}

.lobby-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px 24px;
  max-width: 400px;
  width: 100%;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.game-logo { font-size: 64px; line-height: 1; }

h1 { font-size: 2rem; color: var(--gold); }
h2 { font-size: 1.5rem; color: var(--gold); }

.subtitle { color: var(--muted); font-size: 0.95rem; }
.small { color: var(--muted); font-size: 0.85rem; }

.lobby-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-align: left;
}
.lobby-section label {
  font-size: 0.85rem;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 1rem;
  padding: 10px 14px;
  width: 100%;
  outline: none;
  transition: border-color 0.2s;
}
input:focus { border-color: var(--green); }

.divider { color: var(--muted); font-size: 0.85rem; }

/* ── Player count picker (lobby) ── */
.player-count-picker {
  display: flex;
  gap: 8px;
}
.count-btn {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  flex: 1;
  font-size: 1.2rem;
  font-weight: 700;
  padding: 12px 0;
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}
.count-btn.selected {
  background: rgba(76, 175, 80, 0.15);
  border-color: var(--green);
  color: var(--green);
}

.room-code-display {
  background: var(--bg);
  border: 2px solid var(--gold);
  border-radius: var(--radius);
  color: var(--gold);
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  padding: 14px;
  font-family: monospace;
}

.go-history { display: flex; flex-direction: column; gap: 8px; }
.go-best { color: var(--gold); font-weight: 700; font-size: 1rem; }
.go-past-label { color: var(--muted); font-size: 0.8rem; }
.go-past-list { display: flex; flex-wrap: wrap; gap: 6px; }
.go-past-chip {
  background: var(--card2);
  border: 1px solid var(--border);
  border-radius: 14px;
  color: var(--text);
  font-size: 0.82rem;
  font-weight: 600;
  padding: 4px 10px;
}
.go-past-chip.win { border-color: var(--green); color: var(--green); }

.error-msg { color: var(--red); font-size: 0.9rem; min-height: 1.2em; }

/* ── Buttons ── */
.btn {
  border: none;
  border-radius: 10px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  padding: 12px 20px;
  transition: opacity 0.15s, transform 0.1s;
  width: 100%;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }

.btn-primary  { background: var(--green); color: var(--sand-pale); }
.btn-secondary { background: var(--card2); color: var(--text); border: 1px solid var(--border); }
.btn-action   { background: var(--card2); color: var(--text); border: 1px solid var(--border); width: auto; flex: 1; min-width: 0; }
.btn-craft    { border-color: var(--gold); color: var(--gold); }
.btn-fish     { border-color: var(--blue); color: var(--reef-pale); }
.btn-dig      { border-color: var(--dune-deep); color: var(--dune-pale); }
.btn-hunt     { border-color: var(--kelp); color: var(--kelp-pale); }
.btn-fell     { border-color: var(--dune-deep); color: var(--dune-pale); }
.btn-carve    { border-color: var(--dune); color: var(--dune-pale); }
.btn-sail     { border-color: var(--blue); color: var(--reef-pale); }
.btn-dive     { border-color: var(--reef-lift); color: var(--reef-pale); }
.btn-tinker   { border-color: var(--dune); color: var(--dune-pale); }
.btn-radio    { border-color: var(--kelp); color: var(--kelp-pale); }
.btn-garden   { border-color: var(--kelp-lift); color: var(--kelp-pale); }
.btn-join     { background: var(--abyss-lift); color: var(--reef-pale); border: 2px solid var(--reef-lift); border-radius: 10px; cursor: pointer; font-size: 1rem; font-weight: 600; padding: 12px 20px; width: 100%; transition: opacity 0.15s; }
.btn-join:hover { opacity: 0.85; }

/* ═══════════════════════════════════════════════════════════════════════
   PAPER THEME · STONE BUTTONS

   Only active under [data-theme="paper"], so the live game keeps its
   rounded rectangles until launch. A button is a flat sea-worn pebble:
   wide rather than round so a long label doesn't distort it, flecked so
   the surface reads at any size, and it sinks when pressed.

   Everything here is Tier 1. The twelve per-action colour rules are
   deliberately overridden rather than deleted — the live theme still
   uses them, and the classes stay so game.js can find each button.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] .btn {
  --lo:  var(--sand-mute);
  --hi:  var(--stone-pale);
  --lo2: var(--stone-deep);
  --label: var(--abyss);
  --lift: 5px;

  background: none;
  border: 0;
  border-radius: 0;
  color: var(--label);
  padding: 0;
  min-height: 0;
  transform: translateY(0);
  /* The lift is a box-shadow on the pebble (::before), NOT a drop-shadow
     filter on the button. A filter cannot be composited: transitioning it
     repaints the whole layer every frame, and on an iPad — where iOS fires
     :hover on tap before :active — every press flashed and the six stones on
     screen each held a filter layer. Oliver found it. box-shadow follows the
     ::before's organic border-radius, so the shape is identical and free. */
  transition: transform 90ms cubic-bezier(.2, .8, .3, 1);
  /* the pebble itself is the ::before, so the label sits on top of it */
  position: relative;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 52px;
  padding: 13px 22px;
  line-height: 1.2;
}
:root[data-theme="paper"] .btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* wide and flat — 68/32 vertically is what stops a long label bulging */
  border-radius: 44% 56% 47% 53% / 68% 32% 68% 32%;
  box-shadow: 0 var(--lift) 0 var(--lo2),
              0 calc(var(--lift) + 3px) 0 rgba(4, 18, 26, .38);
  background-image:
    radial-gradient(circle at 22% 34%, rgba(12, 46, 61, .20) 0 1.6px, transparent 1.7px),
    radial-gradient(circle at 63% 22%, rgba(12, 46, 61, .16) 0 1.2px, transparent 1.3px),
    radial-gradient(circle at 78% 63%, rgba(12, 46, 61, .20) 0 1.8px, transparent 1.9px),
    radial-gradient(circle at 41% 74%, rgba(12, 46, 61, .14) 0 1.3px, transparent 1.4px),
    radial-gradient(circle at 86% 38%, rgba(247, 230, 189, .50) 0 1.4px, transparent 1.5px),
    radial-gradient(circle at 33% 55%, rgba(247, 230, 189, .44) 0 1.1px, transparent 1.2px),
    linear-gradient(155deg, var(--hi) 0%, var(--lo) 56%, var(--lo2) 100%);
}
@media (hover: hover) and (pointer: fine) {
  :root[data-theme="paper"] .btn:hover:not(:disabled) {
    background: none;
    transform: translateY(-2px);
  }
  :root[data-theme="paper"] .btn:hover:not(:disabled)::before {
    box-shadow: 0 calc(var(--lift) + 2px) 0 var(--lo2),
                0 calc(var(--lift) + 6px) 0 rgba(4, 18, 26, .40);
  }
}
/* presses exactly its own lift, and the shadow goes with it */
:root[data-theme="paper"] .btn:active:not(:disabled) {
  transform: translateY(var(--lift));
}
:root[data-theme="paper"] .btn:active:not(:disabled)::before { box-shadow: none; }
/* flat on the sand, and greyed — three cues, none of them opacity alone */
:root[data-theme="paper"] .btn:disabled {
  opacity: 1;
  transform: translateY(var(--lift));
  --label: var(--sand-pale);
}
:root[data-theme="paper"] .action-buttons .btn-action.locked {
  /* The game marks unavailable actions with .locked, not :disabled — the live
     theme gives them opacity .55 and a dashed border. On a stone neither means
     anything, so locked gets the same treatment as disabled: flat on the sand,
     darkened, hatched. Without this the main "you can't do that yet" state
     would look identical to an available button. */
  opacity: 1;
  border: 0;
  transform: translateY(var(--lift));
  --label: var(--sand-pale);
}
:root[data-theme="paper"] .btn:disabled::before,
:root[data-theme="paper"] .action-buttons .btn-action.locked::before {
  box-shadow: none;         /* pressed flat into the sand */
  filter: brightness(.6);   /* 0.72 left the label at 4.25:1 — under AA */
  /* granite is grey already, so greying it out reads as nothing. The hatch is
     what carries "unavailable" once desaturation has stopped being a signal. */
  background-image:
    repeating-linear-gradient(45deg, transparent 0 5px, rgba(4, 18, 26, .16) 5px 10px),
    linear-gradient(155deg, var(--hi) 0%, var(--lo) 56%, var(--lo2) 100%);
}

:root[data-theme="paper"] .btn-primary { --hi: var(--kelp-pale); --lo: var(--kelp-lift);
  --lo2: var(--kelp-deep); --label: var(--abyss); background: none; }
:root[data-theme="paper"] .btn-treat   { --hi: var(--coral-pale); --lo: var(--coral);
  --lo2: var(--coral-deep); --label: var(--sand-pale); }

/* A pebble has a shape, so it can't be told to fill any width. Two problems
   followed from that. The live theme gives .btn-action flex:1, which stretched
   these into surfboards at ~440px. And because border-radius is a percentage,
   a wider stone curves proportionally wider — so a long label made a lozenge
   and a short one a pebble, and no two stones matched.

   A grid of equal columns fixes both: every stone is the same size, so every
   stone is the same shape. Labels wrap instead of stretching the rock. */
:root[data-theme="paper"] .action-buttons {
  /* Flex rather than grid: auto-fill leaves empty columns, so the row sat
     left-aligned with dead space on the right. A fixed basis with no grow
     keeps every stone identical on every row, and justify-content centres
     whatever is left over. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  /* stones sit almost touching, the way they'd lie on a beach */
  gap: 7px 6px;
}
:root[data-theme="paper"] .action-buttons .btn-action {
  /* no grow, so a row of two is never wider than a row of six */
  flex: 0 1 222px;
  width: auto;
  max-width: 222px;
  min-width: 132px;
  padding-inline: 12px;
  text-align: center;
  /* a two-line label must not make one stone taller than its neighbours */
  min-height: 68px;
  font-size: 1.02rem;
}
:root[data-theme="paper"] .btn-primary,
:root[data-theme="paper"] .btn-secondary { max-width: 340px; margin-inline: auto; }

/* ═══════════════════════════════════════════════════════════════════════
   PAPER THEME · ACTION SYMBOLS

   Symbols instead of words. The live theme keeps its emoji labels; here the
   glyph is replaced by a cut-paper silhouette and the word is hidden.

   These are CSS masks, not images: the SVG carries shape only and the colour
   comes from a token, so the icon retints with the theme and no hex ends up
   baked into a data URI. Two-tone icons would have cost that.

   The word stays in the DOM as .a-lbl for screen readers, and every button
   carries an aria-label, so nothing is lost to anyone who can't read a
   pictogram of an axe.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] .btn-action .a-lbl { display: none; }
:root[data-theme="paper"] .btn-action .a-ico {
  display: block;
  position: relative;
  width: 44px;
  height: 44px;
  font-size: 0;            /* hides the emoji glyph underneath */
  /* sheet one: the base shape */
  background-color: var(--ico-hi, var(--sand-pale));
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
  /* the hard offset shadow every other piece of paper in the game has */
  filter: drop-shadow(0 1.6px 0 rgba(4, 18, 26, .45));
}
/* sheet two: the darker facet, laid on top so you can count the sheets */
:root[data-theme="paper"] .btn-action .a-ico::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--ico-lo, var(--dune-deep));
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
}
/* on a pale stone the icon needs dark paper; on a locked one it flips */
:root[data-theme="paper"] .btn-action {
  --ico-hi: var(--abyss);
  --ico-lo: var(--abyss-sink);
}
/* ── The glyphs are cut from Tier 3, one material each ──────────────────
   Every glyph used to be --abyss over --abyss-sink: fifteen shapes in the
   same two near-blacks, on fifteen grey stones. Tier 3 was declared for
   exactly this and used nowhere, and dropping the twelve per-action button
   colours was argued for on the grounds that the icon would carry identity.
   It didn't. This is that debt.

   The base sheet is the lit face and the facet is the shadow, so a pair is
   normally one material in two tones. On craft, carve and hunt the facet
   falls on a genuinely different part, and those get two materials.
   Locked overrides all of this — an unavailable stone loses its colour. */
/* forage — wild greens */
:root[data-theme="paper"] .btn-action[data-icon="forage"] { --ico-hi: var(--kelp-lift); --ico-lo: var(--kelp-deep); }
/* garden — a shoot in a pot. The facet is the pot, so the plant stays green and the pot is earthenware: shape and colour both separate it from forage now */
:root[data-theme="paper"] .btn-action[data-icon="garden"] { --ico-hi: var(--kelp-lift); --ico-lo: var(--mat-wood); }
/* rest — night. The darkest glyph in the set, which is itself an identity */
:root[data-theme="paper"] .btn-action[data-icon="rest"] { --ico-hi: var(--abyss-lift); --ico-lo: var(--abyss-sink); }
/* craft — the facet IS the hammer head, so this reads as a real handle and a real head */
:root[data-theme="paper"] .btn-action[data-icon="craft"] { --ico-hi: var(--mat-wood); --ico-lo: var(--mat-stone); }
/* fell — same two materials as craft, and the shape has to carry the difference:
   the bit hangs below the eye and the beard is concave. A hammer does neither.
   A tree-and-axe version was tried and dropped: broccoli and a spoon */
:root[data-theme="paper"] .btn-action[data-icon="fell"] { --ico-hi: var(--mat-wood); --ico-lo: var(--mat-stone); }
/* dig — a stone head, dark, against tinker's bright steel */
:root[data-theme="paper"] .btn-action[data-icon="dig"] { --ico-hi: var(--mat-stone); --ico-lo: var(--abyss-lift); }
/* carve — the facet IS the blade — a pale edge over a wooden handle */
:root[data-theme="paper"] .btn-action[data-icon="carve"] { --ico-hi: var(--mat-carve); --ico-lo: var(--mat-metal); }
/* hunt — the facet IS the arrow, nocked on the string, so this one runs
   pale-over-dark on purpose: the only inverted pair in the set */
:root[data-theme="paper"] .btn-action[data-icon="hunt"] { --ico-hi: var(--mat-wood); --ico-lo: var(--mat-thread); }
/* tinker — bright steel */
:root[data-theme="paper"] .btn-action[data-icon="tinker"] { --ico-hi: var(--mat-metal); --ico-lo: var(--mat-stone); }
/* fish — open water */
:root[data-theme="paper"] .btn-action[data-icon="fish"] { --ico-hi: var(--reef-lift); --ico-lo: var(--reef-deep); }
/* dive — two round lenses in a frame with a hooked snorkel. The facet is the
   pair of lenses; the snorkel starts on the frame's straight edge so it joins
   without crossing. Ben's call after a single-lens mask read as a lunchbox */
:root[data-theme="paper"] .btn-action[data-icon="dive"] { --ico-hi: var(--reef-deep); --ico-lo: var(--abyss-deep); }
/* sail — canvas sails over a blue hull and one blue sail. Not coral: treat is
   the one glyph allowed to shout, and two reds would spend that twice */
:root[data-theme="paper"] .btn-action[data-icon="sail"] { --ico-hi: var(--mat-thread); --ico-lo: var(--reef-lift); }
/* radio — a wooden set with a pale speaker and two knobs; the facet is the fittings, not a shadow */
:root[data-theme="paper"] .btn-action[data-icon="radio"] { --ico-hi: var(--mat-wood); --ico-lo: var(--mat-metal); }
/* upgrade — the only action with no island object behind it, so it takes the one colour that means better rather than a material */
:root[data-theme="paper"] .btn-action[data-icon="upgrade"] { --ico-hi: var(--dune-deep); --ico-lo: var(--dune-dark); }
/* treat — the one glyph allowed to shout */
:root[data-theme="paper"] .btn-action[data-icon="treat"] { --ico-hi: var(--coral); --ico-lo: var(--coral-deep); }

:root[data-theme="paper"] .action-buttons .btn-action.locked,
:root[data-theme="paper"] .btn-action:disabled {
  --ico-hi: var(--sand-pale);
  --ico-lo: var(--sand-mute);
}
/* a symbol needs less room than a word, so the stones get rounder */
:root[data-theme="paper"] .action-buttons .btn-action {
  flex: 0 1 158px;
  max-width: 158px;
  min-width: 96px;
  min-height: 86px;
  padding: 12px;
}
:root[data-theme="paper"] .btn-action[data-icon="forage"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.05%200.08%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M20%2045h8V25h-8Z%20M24%2025C24%2012%2032%203%2046%201c2%2014-9%2024-22%2024Z%20M25%2031C23%2019%2014%2010%202%209c-1%2013%2010%2022%2023%2022Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.05%200.08%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M20%2045h8V25h-8Z%20M24%2025C24%2012%2032%203%2046%201c2%2014-9%2024-22%2024Z%20M25%2031C23%2019%2014%2010%202%209c-1%2013%2010%2022%2023%2022Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="forage"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.05%200.08%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M24%2025C24%2012%2032%203%2046%201c2%2014-9%2024-22%2024Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.05%200.08%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M24%2025C24%2012%2032%203%2046%201c2%2014-9%2024-22%2024Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="rest"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.91%200.45%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M30%202a21%2021%200%201%200%2016%2034A18%2018%200%200%201%2030%202Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.91%200.45%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M30%202a21%2021%200%201%200%2016%2034A18%2018%200%200%201%2030%202Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="rest"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.91%200.45%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M30%202a18%2018%200%200%200%2016%2034%2021%2021%200%200%201-16-34Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.91%200.45%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M30%202a18%2018%200%200%200%2016%2034%2021%2021%200%200%201-16-34Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="craft"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-1.13%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11.5%209.3L31.5%202%2035.4%206.8%2037.7%2013.2%2037.5%2018.4%2029.3%2021.4%2039.1%2042.7%2030%2046%2023.8%2023.4%2017.4%2025.7%2010.9%2021.9%208.9%2016.5Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-1.13%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11.5%209.3L31.5%202%2035.4%206.8%2037.7%2013.2%2037.5%2018.4%2029.3%2021.4%2039.1%2042.7%2030%2046%2023.8%2023.4%2017.4%2025.7%2010.9%2021.9%208.9%2016.5Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="craft"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-1.13%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11.5%209.3L31.5%202%2035.4%206.8%2037.7%2013.2%2037.5%2018.4%2017.4%2025.7%2010.9%2021.9%208.9%2016.5Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-1.13%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11.5%209.3L31.5%202%2035.4%206.8%2037.7%2013.2%2037.5%2018.4%2017.4%2025.7%2010.9%2021.9%208.9%2016.5Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="upgrade"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-2.18%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M23%203%2043%2023%2033%2023%2034%2037%2015%2037%2015%2024%205%2025Z%20M8%2041h32v6H8Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-2.18%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M23%203%2043%2023%2033%2023%2034%2037%2015%2037%2015%2024%205%2025Z%20M8%2041h32v6H8Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="upgrade"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-2.18%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M23%203%2043%2023%2033%2023%2034%2037%2024%2037%2024%205Z%20M24%2041h16v6H24Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.13%20-2.18%29%20scale%281.0473%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M23%203%2043%2023%2033%2023%2034%2037%2024%2037%2024%205Z%20M24%2041h16v6H24Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="fish"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.09%20-0.58%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M2%2024C10%2010%2022%206%2034%2014%20L47%205%20L41%2024%20L47%2043%20L34%2034C22%2042%2010%2038%202%2024Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.09%20-0.58%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M2%2024C10%2010%2022%206%2034%2014%20L47%205%20L41%2024%20L47%2043%20L34%2034C22%2042%2010%2038%202%2024Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="fish"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.09%20-0.58%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M34%2014%20L47%205%20L41%2024%20L47%2043%20L34%2034%20C36%2027%2036%2021%2034%2014Z%20M12%2016.5a3.5%203.5%200%201%200%200%207%203.5%203.5%200%201%200%200-7Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-1.09%20-0.58%29%20scale%281.0240%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M34%2014%20L47%205%20L41%2024%20L47%2043%20L34%2034%20C36%2027%2036%2021%2034%2014Z%20M12%2016.5a3.5%203.5%200%201%200%200%207%203.5%203.5%200%201%200%200-7Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="dig"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%280.47%20-2.23%29%20scale%280.9804%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M2%2021C13%203%2035%203%2046%2021C40%2014%2033%2015%2029%2017L29%2046%2019%2046%2019%2017C15%2015%208%2014%202%2021Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%280.47%20-2.23%29%20scale%280.9804%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M2%2021C13%203%2035%203%2046%2021C40%2014%2033%2015%2029%2017L29%2046%2019%2046%2019%2017C15%2015%208%2014%202%2021Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="dig"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%280.47%20-2.23%29%20scale%280.9804%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M24%208C32%208%2040%2012%2046%2021C40%2014%2033%2015%2029%2017L29%2046%2024%2046Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%280.47%20-2.23%29%20scale%280.9804%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.0%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M24%208C32%208%2040%2012%2046%2021C40%2014%2033%2015%2029%2017L29%2046%2024%2046Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="hunt"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M14%205C29%209%2034%2015%2032%2021L23%2021C24%2016%2019%2012%2014%2012Z%20M14%2045C29%2041%2034%2035%2032%2029L23%2029C24%2034%2019%2038%2014%2038Z%20M8%205h6v16H8Z%20M8%2029h6v16H8Z%20M0%2013L8%2021L34%2021L34%2015L48%2025L34%2035L34%2029L8%2029L0%2037Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M14%205C29%209%2034%2015%2032%2021L23%2021C24%2016%2019%2012%2014%2012Z%20M14%2045C29%2041%2034%2035%2032%2029L23%2029C24%2034%2019%2038%2014%2038Z%20M8%205h6v16H8Z%20M8%2029h6v16H8Z%20M0%2013L8%2021L34%2021L34%2015L48%2025L34%2035L34%2029L8%2029L0%2037Z%22%2F%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="hunt"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M0%2013L8%2021L34%2021L34%2015L48%2025L34%2035L34%2029L8%2029L0%2037Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M0%2013L8%2021L34%2021L34%2015L48%2025L34%2035L34%2029L8%2029L0%2037Z%22%2F%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="fell"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.23%20-4.51%29%20scale%281.0945%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M16.1%2022.6%20L21.7%2020.6%20L30.6%2045.0%20L24.9%2047.1Z%20M7.8%208.6%20L17.2%205.2%20C33.6%203.5%2043.3%2012.7%2041.8%2026.0%20C34.1%2022.5%2029.0%2020.1%2028.3%2018.2%20L13.2%2023.7Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.23%20-4.51%29%20scale%281.0945%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M16.1%2022.6%20L21.7%2020.6%20L30.6%2045.0%20L24.9%2047.1Z%20M7.8%208.6%20L17.2%205.2%20C33.6%203.5%2043.3%2012.7%2041.8%2026.0%20C34.1%2022.5%2029.0%2020.1%2028.3%2018.2%20L13.2%2023.7Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="fell"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.23%20-4.51%29%20scale%281.0945%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M7.8%208.6%20L17.2%205.2%20C33.6%203.5%2043.3%2012.7%2041.8%2026.0%20C34.1%2022.5%2029.0%2020.1%2028.3%2018.2%20L13.2%2023.7Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-3.23%20-4.51%29%20scale%281.0945%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M7.8%208.6%20L17.2%205.2%20C33.6%203.5%2043.3%2012.7%2041.8%2026.0%20C34.1%2022.5%2029.0%2020.1%2028.3%2018.2%20L13.2%2023.7Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="carve"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.39%20-1.43%29%20scale%281.0378%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.4%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M3%2039%2015%2027%2014%2026%2044%204C36%2022%2030%2030%2022%2034L21%2033%209%2045Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.39%20-1.43%29%20scale%281.0378%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.4%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M3%2039%2015%2027%2014%2026%2044%204C36%2022%2030%2030%2022%2034L21%2033%209%2045Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="carve"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.39%20-1.43%29%20scale%281.0378%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.4%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M18%2030%2044%204C36%2022%2030%2030%2022%2034L21%2033Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.39%20-1.43%29%20scale%281.0378%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.4%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M18%2030%2044%204C36%2022%2030%2030%2022%2034L21%2033Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="dive"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11%207h14a11%2011%200%200%201%2011%2011v12a11%2011%200%200%201-11%2011H11A11%2011%200%200%201%200%2030V18A11%2011%200%200%201%2011%207Z%20M41%201a6.5%206.5%200%200%201%206.5%206.5v22A10%2010%200%200%201%2037.5%2040H36V27h5.5a2%202%200%200%200%202-2V7.5a6.5%206.5%200%200%201%200%200Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11%207h14a11%2011%200%200%201%2011%2011v12a11%2011%200%200%201-11%2011H11A11%2011%200%200%201%200%2030V18A11%2011%200%200%201%2011%207Z%20M41%201a6.5%206.5%200%200%201%206.5%206.5v22A10%2010%200%200%201%2037.5%2040H36V27h5.5a2%202%200%200%200%202-2V7.5a6.5%206.5%200%200%201%200%200Z%22%2F%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="dive"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M10%2015.5a8.5%208.5%200%201%200%200%2017.0a8.5%208.5%200%201%200%200%20-17.0Z%20M27%2015.5a8.5%208.5%200%201%200%200%2017.0a8.5%208.5%200%201%200%200%20-17.0Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M10%2015.5a8.5%208.5%200%201%200%200%2017.0a8.5%208.5%200%201%200%200%20-17.0Z%20M27%2015.5a8.5%208.5%200%201%200%200%2017.0a8.5%208.5%200%201%200%200%20-17.0Z%22%2F%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="sail"] .a-ico {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M22%205v23H8Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M22%205v23H8Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E");
}
:root[data-theme="paper"] .btn-action[data-icon="sail"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="tinker"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%281.69%200.77%29%20scale%280.9681%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.6%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M11.9%206.9L20.2%2014.7%2025.9%2013%2028.8%202%2033%205.9C37.6%2014.8%2033%2020.2%2029.8%2023.1L35.7%2043.8%2028.2%2046%2022.3%2025.3C18%2024.5%2011.2%2022.3%2010.4%2012.4Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%281.69%200.77%29%20scale%280.9681%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.6%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M11.9%206.9L20.2%2014.7%2025.9%2013%2028.8%202%2033%205.9C37.6%2014.8%2033%2020.2%2029.8%2023.1L35.7%2043.8%2028.2%2046%2022.3%2025.3C18%2024.5%2011.2%2022.3%2010.4%2012.4Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="tinker"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%281.69%200.77%29%20scale%280.9681%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.6%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M11.9%206.9L20.2%2014.7%2025.9%2013%2028.8%202%2033%205.9C37.6%2014.8%2033%2020.2%2029.8%2023.1L22.3%2025.3C18%2024.5%2011.2%2022.3%2010.4%2012.4Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%281.69%200.77%29%20scale%280.9681%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20stroke%3D%22%23000%22%20stroke-width%3D%223.6%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%20d%3D%22M11.9%206.9L20.2%2014.7%2025.9%2013%2028.8%202%2033%205.9C37.6%2014.8%2033%2020.2%2029.8%2023.1L22.3%2025.3C18%2024.5%2011.2%2022.3%2010.4%2012.4Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="radio"] .a-ico {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M2%2018h40v28H2Z%20M31%2014%2046%200l4%204-13%2014Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M2%2018h40v28H2Z%20M31%2014%2046%200l4%204-13%2014Z%22%2F%3E%3C%2Fsvg%3E");
}
:root[data-theme="paper"] .btn-action[data-icon="radio"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M16%2023a9%209%200%201%200%200%2018%209%209%200%201%200%200-18Z%20M32%2023a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%20M32%2034a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M16%2023a9%209%200%201%200%200%2018%209%209%200%201%200%200-18Z%20M32%2023a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%20M32%2034a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%22%2F%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="garden"] .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.01%20-1.48%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M20%2032h7V26h-7Z%20M23%2026C23%2014%2031%205%2045%203c2%2014-9%2023-22%2023Z%20M22%2030C20%2019%2012%2011%201%2010c-1%2012%2010%2020%2021%2020Z%20M8%2032h32v5H8Z%20M11%2037h26l-3%209H14Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.01%20-1.48%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M20%2032h7V26h-7Z%20M23%2026C23%2014%2031%205%2045%203c2%2014-9%2023-22%2023Z%20M22%2030C20%2019%2012%2011%201%2010c-1%2012%2010%2020%2021%2020Z%20M8%2032h32v5H8Z%20M11%2037h26l-3%209H14Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="garden"] .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.01%20-1.48%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M8%2032h32v5H8Z%20M11%2037h26l-3%209H14Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cg%20transform%3D%22translate%28-0.01%20-1.48%29%20scale%281.0402%29%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M8%2032h32v5H8Z%20M11%2037h26l-3%209H14Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
:root[data-theme="paper"] .btn-action[data-icon="treat"] .a-ico {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M1%2012h46v32H1Z%20M17%203h14v9H17Z%20M20%2018h8v6h6v8h-6v6h-8v-6h-6v-8h6Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M1%2012h46v32H1Z%20M17%203h14v9H17Z%20M20%2018h8v6h6v8h-6v6h-8v-6h-6v-8h6Z%22%2F%3E%3C%2Fsvg%3E");
}
:root[data-theme="paper"] .btn-action[data-icon="treat"] .a-ico::after {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M24%2012h23v32H24Z%20M24%2018h4v6h6v8h-6v6h-4Z%22%2F%3E%3C%2Fsvg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M24%2012h23v32H24Z%20M24%2018h4v6h6v8h-6v6h-4Z%22%2F%3E%3C%2Fsvg%3E");
}

/* One material for every action — the icon carries identity, not the colour.
   These twelve rules exist in the live theme and are neutralised here. */
:root[data-theme="paper"] .btn-action,
:root[data-theme="paper"] .btn-secondary,
:root[data-theme="paper"] .btn-craft, :root[data-theme="paper"] .btn-fish,
:root[data-theme="paper"] .btn-dig,   :root[data-theme="paper"] .btn-hunt,
:root[data-theme="paper"] .btn-fell,  :root[data-theme="paper"] .btn-carve,
:root[data-theme="paper"] .btn-sail,  :root[data-theme="paper"] .btn-dive,
:root[data-theme="paper"] .btn-tinker,:root[data-theme="paper"] .btn-radio,
:root[data-theme="paper"] .btn-garden {
  background: none;
  border: 0;
  color: var(--label);
}

.btn-primary:hover:not(:disabled)  { opacity: 0.88; }
.btn-secondary:hover:not(:disabled) { background: var(--abyss-lift); }
.btn-action:hover:not(:disabled)   { background: var(--abyss-lift); }

/* ── Game Screen ── */
#screen-game.active { display: flex; flex-direction: column; min-height: 100vh; padding-bottom: 24px; }

header {
  background: var(--card);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 16px;
  position: sticky;
  top: 0;
  z-index: 10;
}
.header-left, .header-right { display: flex; gap: 8px; align-items: center; }

.day-badge {
  background: var(--green);
  border-radius: 20px;
  color: var(--sand-pale);
  font-size: 0.85rem;
  font-weight: 700;
  padding: 4px 12px;
}
.weather-badge {
  background: var(--card2);
  border: 1px solid var(--border);
  border-radius: 20px;
  font-size: 0.85rem;
  padding: 4px 10px;
}
.room-badge {
  background: var(--card2);
  border: 1px solid var(--gold);
  border-radius: 20px;
  color: var(--gold);
  cursor: pointer;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 4px 10px;
}
.room-badge:hover { opacity: 0.8; }
.score-badge {
  color: var(--gold);
  font-size: 0.9rem;
  font-weight: 600;
}
.btn-leave {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 20px;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.75rem;
  padding: 4px 10px;
}
.btn-leave:hover { border-color: var(--red); color: var(--red); }

/* ── Resources (pinned rails down the left/right edges) ── */
.resource-rail {
  position: fixed;
  top: 0;
  bottom: 0;
  width: 58px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 22px;
  padding-top: 82px;
  background: var(--card);
  z-index: 8;
  overflow-y: auto;
}
.rail-left  { left: 0;  border-right: 1px solid var(--border); }
.rail-right { right: 0; border-left: 1px solid var(--border); }

.res {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 1.7rem;
  line-height: 1;
  gap: 4px;
  color: var(--text);
}
.res span {
  font-weight: 700;
  font-size: 1.05rem;
}
/* The rail's cut-paper icons: five objects from design/forty-paper-icons.html
   and three meters from design/heart-battery-smile.html. Both themes are live
   at once, so the emoji stays and is swapped for the drawing only under paper.
   It keeps its own size — .res span above would otherwise shrink it. */
.res-emo { font-size: 1.7rem; font-weight: 400; }
.res-ico { display: none; }
:root[data-theme="paper"] .res-emo { display: none; }
:root[data-theme="paper"] .res-ico {
  /* the rail is 58px; 30px left 14px of dead air each side */
  display: block; width: 42px; height: 42px; flex: none;
}
/* only the meter shells wrap an svg; a bare `svg.res-ico` here would win over
   the 30px box above and stretch the still icons to the width of the rail. */
:root[data-theme="paper"] .res-meter > svg { width: 100%; height: 100%; display: block; }

/* ── Main column, inset to clear the fixed side rails ── */
.game-main {
  margin: 0 58px;
}

/* ── Map ── */
.map-section {
  padding: 16px;
  border-bottom: 1px solid var(--border);
}
.map-section h3 { font-size: 0.9rem; color: var(--muted); margin-bottom: 12px; }

.island-map-container {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 2;
  margin-inline: auto;
  border-radius: var(--radius);
  overflow: hidden;
}

.island-bg-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.weather-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  overflow: hidden;   /* contains the oversized rain/lightning layers below */
}

.island-map-container[data-weather="sunny"] .weather-overlay {
  background: radial-gradient(ellipse at 82% 8%, rgba(255,215,50,0.13) 0%, transparent 52%);
}

/* Rain and lightning loop forever while the weather holds, so they're built to run
   on the compositor: the falling streaks are a transform on an oversized layer and
   the flash is pure opacity. Animating background-position / box-shadow instead
   (as these once did) repaints the whole map every frame — fine on a laptop, but
   it's exactly what stutters and drains battery on an old tablet. */
.island-map-container[data-weather="rainy"] .weather-overlay {
  background: linear-gradient(rgba(70,110,180,0.10), rgba(70,110,180,0.10));
}

.island-map-container[data-weather="rainy"] .weather-overlay::after {
  content: "";
  position: absolute;
  /* Overscan so the travelling layer never exposes an edge mid-loop. */
  inset: -60px;
  background-image: repeating-linear-gradient(
    170deg,
    transparent, transparent 4px,
    rgba(140,180,255,0.20) 4px, rgba(140,180,255,0.20) 5px
  );
  background-size: 22px 22px;
  animation: rain-fall 0.35s linear infinite;
  will-change: transform;
}

/* Travels exactly one tile across and two down, so the loop is seamless. */
@keyframes rain-fall {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-22px, 44px, 0); }
}

/* A storm used to be a 48%-black sheet over the whole map and nothing else, so
   the island simply disappeared — worse at night, where the chart is already
   dark. Ben: "you can't see the map by day or night, it covers it up too much."
   A storm should read as WEATHER, not as the lights going out. So the wash
   drops to 20% and the storm gets what it never had: rain. Driving rain, at a
   steeper angle and roughly twice the speed of the rainy setting, which says
   "storm" far better than darkness does and leaves the island visible. */
.island-map-container[data-weather="stormy"] .weather-overlay {
  background: rgba(8, 4, 20, 0.20);
}

/* The rain gets its own layer so it can travel on a TRANSFORM, like the rainy
   setting does. The first cut put the streaks and the wash in one element and
   animated background-position, which repaints the whole map every frame — the
   exact thing the note above warns about, and test_ui_rules caught it. The
   wash stays on the overlay, the streaks move on ::before, lightning is
   ::after. */
.island-map-container[data-weather="stormy"] .weather-overlay::before {
  content: "";
  position: absolute;
  inset: -60px;                       /* overscan, so no edge shows mid-loop */
  background-image: repeating-linear-gradient(
    162deg,
    transparent, transparent 3px,
    rgba(150,185,255,0.16) 3px, rgba(150,185,255,0.16) 4.4px
  );
  background-size: 18px 18px;
  animation: storm-rain 0.22s linear infinite;
  will-change: transform;
}

/* One tile across and two down, so the loop is seamless. */
@keyframes storm-rain {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-18px, 36px, 0); }
}

.island-map-container[data-weather="stormy"] .weather-overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  /* was 0.55 to full opacity — a white-out on a map that is already dark */
  background: radial-gradient(ellipse at 50% 50%, transparent 34%, rgba(190,200,255,0.34) 100%);
  opacity: 0;
  animation: lightning-flash 4.5s ease-in-out infinite;
  will-change: opacity;
}

@keyframes lightning-flash {
  0%, 87%, 100% { opacity: 0; }
  89%           { opacity: .8; }
  91%           { opacity: 0; }
  93%           { opacity: 0.62; }
}

.map-location {
  background: rgba(20, 35, 20, 0.88);
  backdrop-filter: blur(3px);
  border: 2px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 58px;
  height: 62px;
  position: absolute;
  gap: 3px;
  font-size: 1.6rem;
  transform: translate(-50%, -50%);
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
  user-select: none;
  z-index: 3;
}
.map-location span {
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--muted);
}
.map-location:hover { transform: translate(-50%, -50%) scale(1.1); background: rgba(38,61,38,0.95); }
.map-location:active { transform: translate(-50%, -50%) scale(0.93); }
.map-location.current {
  border-color: var(--green);
  background: rgba(28, 60, 28, 0.94);
  box-shadow: 0 0 12px rgba(76,175,80,0.4);
}
.map-location.current span { color: var(--green); }

/* Palm Isle */
#loc-forest { top: 40%; left: 30%; }
#loc-ruins  { top: 28%; left: 55%; }
#loc-cave   { top: 53%; left: 74%; }
#loc-beach  { top: 82%; left: 50%; }
/* Volcano Isle */
#loc-slopes  { top: 52%; left: 35%; }
#loc-springs { top: 28%; left: 69%; }
#loc-jungle  { top: 68%; left: 69%; }
/* Coral Isle */
#loc-reef    { top: 36%; left: 27%; }
#loc-sandbar { top: 36%; left: 76%; }
#loc-wreck   { top: 70%; left: 57%; }

.map-location.hidden,
.island-bg-svg.hidden,
.player-token.hidden { display: none; }

/* A tile on an island you're not standing on — a paddle, not a walk */
.map-location.overseas {
  border-color: var(--reef-deep);
  border-style: dashed;
}
.map-location.overseas.unreachable {
  opacity: 0.5;
  border-color: var(--border);
}

/* ── Saved games in the lobby ── */
.resume-section { margin-bottom: 14px; text-align: left; }
.resume-section.hidden { display: none; }
.resume-section > label {
  display: block;
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 8px;
}
/* ── The Time machine, at the foot of the lobby ──────────────────────────
   Quiet on purpose. It is the last thing on the opening screen, below even
   the join box, so it never sits in the path of someone starting a game. */
.museum-section { margin-top: 22px; text-align: center; }
.museum-section.hidden { display: none; }
.museum-toggle {
  background: none;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 0.8rem;
  padding: 6px 10px;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.museum-toggle:hover { color: var(--text); }
.museum-list {
  display: grid;
  gap: 6px;
  margin-top: 8px;
  text-align: left;
}
.museum-list.hidden { display: none; }
.museum-item {
  display: grid;
  grid-template-columns: auto auto 1fr;
  gap: 4px 10px;
  align-items: baseline;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  text-decoration: none;
}
.museum-item:hover { border-color: var(--gold); }
.museum-head {
  color: var(--muted);
  font-size: 0.72rem;
  margin: 10px 2px 2px;
  line-height: 1.35;
}
.museum-item.is-recon { border-style: dashed; }
.museum-item.is-recon .mi-label { color: var(--muted); }

.mi-label { font-weight: 700; color: var(--gold); font-size: 0.86rem; }
.mi-date { color: var(--muted); font-size: 0.74rem; }
.mi-note {
  grid-column: 1 / -1;
  color: var(--muted);
  font-size: 0.74rem;
  line-height: 1.35;
}

.resume-item {
  background: var(--card2);
  border: 1px solid var(--green);
  border-radius: var(--radius);
  padding: 12px;
  margin-bottom: 8px;
  position: relative;   /* for the clear button in the corner */
}
/* Every saved game can be cleared, not just the finished ones — waiting out
   the 30-day expiry was the only way to tidy the list. Clearing forgets the
   code on this device; the room itself lives on until it expires, so this can
   never end a game someone else is still playing. */
.resume-clear {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  background: var(--card);
  border: 1.5px solid var(--border);
  border-radius: 50%;
  color: var(--text);
  cursor: pointer;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1;
  padding: 0;
}
.resume-clear:hover,
.resume-clear:focus-visible {
  background: var(--coral);
  border-color: var(--coral);
  color: var(--card);
}
.resume-clear-all { width: 100%; margin-top: 4px; margin-bottom: 10px; }
.resume-item.finished { border-color: var(--border); opacity: 0.75; }
.resume-code {
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--gold);
  font-size: 0.95rem;
}
.resume-meta { font-size: 0.82rem; color: var(--text); margin-top: 3px; }
.resume-meta.small { color: var(--muted); font-size: 0.74rem; }
.resume-item .btn { margin-top: 8px; width: 100%; }
.resume-item .resume-clear { margin-top: 0; width: 30px; }

/* ── Connection banner ── */
.conn-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  padding: 7px 12px;
  text-align: center;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--abyss);
  background: var(--dune-pale);
  box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
.conn-banner.hidden { display: none; }
.conn-banner.stuck { background: var(--red); color: var(--sand-pale); }
.conn-fix, .conn-x {
  font: inherit;
  cursor: pointer;
  border-radius: 999px;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  margin-left: 8px;
  padding: 1px 10px;
}
.conn-x { padding: 1px 8px; font-weight: 700; line-height: 1.2; }

/* ── Island chart switcher ── */
.island-chips {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
}
.island-chip {
  flex: 1;
  background: var(--card2);
  border: 1px solid var(--border);
  color: var(--muted);
  border-radius: 999px;
  padding: 6px 4px;
  font-size: 0.72rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.island-chip.viewing {
  border-color: var(--gold);
  color: var(--gold);
  background: rgba(80, 66, 20, 0.35);
}
.island-chip.you-are-here::after {
  content: " 🧍";
}
.island-chip.locked { opacity: 0.6; }
.island-chip.locked::before { content: "🔒 "; }
/* the icon only exists for the paper theme; the live theme keeps its words */
.chip-ico { display: none; }

.chart-note {
  color: var(--reef-pale);
  font-size: 0.78rem;
  margin-top: 8px;
}
.chart-note.hidden { display: none; }

.player-token {
  position: absolute;
  font-size: 2.4rem;
  z-index: 6;
  transform: translate(-50%, calc(-50% - 46px));
  transition: top 0.45s cubic-bezier(0.34, 1.56, 0.64, 1),
              left 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8));
  line-height: 1;
}

.location-tip {
  color: var(--muted);
  font-size: 0.8rem;
  margin-top: 10px;
  min-height: 1.2em;
}

/* ── Actions ── */
.actions-section {
  padding: 16px;
  border-bottom: 1px solid var(--border);
}
.actions-section h3 { font-size: 0.9rem; color: var(--muted); margin-bottom: 12px; }

.action-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.action-buttons .btn-action {
  flex: 1 1 28%;
  min-width: 88px;
}
.action-buttons .btn-action.hidden { display: none; }
.action-buttons .btn-action.locked {
  opacity: 0.55;
  border-style: dashed;
}
.action-loc {
  color: var(--text);
  font-size: 0.85rem;
  margin-left: 4px;
}
.action-hint {
  margin-top: 10px;
  font-size: 0.8rem;
  color: var(--muted);
  min-height: 1.2em;
}
.action-status {
  margin-top: 6px;
  font-size: 0.85rem;
  color: var(--gold);
  min-height: 1.3em;
}

/* ── Player Status ── */
.player-status {
  display: flex;
  flex-wrap: wrap;          /* 3–4 players wrap onto a second row on phones */
  gap: 10px;
  padding: 16px;
  border-bottom: 1px solid var(--border);
}
.player-card {
  background: var(--card2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  flex: 1 1 calc(50% - 5px);   /* two per row, so 4 players stay compact */
  font-size: 0.85rem;
  min-width: 0;
  padding: 10px 12px;
}
.player-card.ready { border-color: var(--green); }
.player-card.away { opacity: 0.55; }
.player-card .p-name { font-weight: 700; margin-bottom: 4px; }
/* Half-width cards are tight — keep the skill badges on one line under the name */
.player-card .p-skills { display: flex; gap: 4px; margin-bottom: 4px; }
.player-card .p-skills .fish-badge { margin-left: 0; padding: 1px 5px; }
.player-card .p-status { color: var(--muted); font-size: 0.8rem; }
.player-card.ready .p-status { color: var(--green); }
.player-card.hurt { border-color: var(--coral-deep); }

/* Injury badge — same shape as the skill badges, red so it reads at a glance */
.injury-badge {
  display: inline-block;
  background: rgba(192, 57, 43, 0.15);
  border: 1px solid var(--coral-deep);
  border-radius: 10px;
  color: var(--coral-pale);
  font-size: 0.68rem;
  padding: 1px 5px;
  white-space: nowrap;
}

/* The one turn's warning before a slow disaster lands — deliberately loud */
/* There is no global `.hidden` rule in this stylesheet — every element that toggles
   the class needs its own. Without this line the banner never goes away. */
.disaster-warning.hidden { display: none; }
.disaster-warning {
  background: rgba(192, 57, 43, 0.18);
  border: 1px solid var(--coral-deep);
  border-radius: var(--radius);
  color: var(--coral-pale);
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.35;
  margin: 0 0 12px;
  padding: 10px 12px;
  animation: warn-pulse 2s ease-in-out infinite;
}
@keyframes warn-pulse {
  0%, 100% { border-color: var(--coral-deep); }
  50%      { border-color: var(--coral-pale); }
}

.btn-treat { border-color: var(--coral-deep); color: var(--coral-pale); }
.modal-note { color: var(--muted); font-size: 0.85rem; margin: 0 0 12px; }
.bed-count  { color: var(--muted); }

.fish-badge {
  display: inline-block;
  background: var(--card);
  border: 1px solid var(--gold);
  border-radius: 10px;
  color: var(--gold);
  font-size: 0.68rem;
  font-weight: 700;
  padding: 1px 7px;
  margin-left: 4px;
  vertical-align: middle;
}

/* ── Inventory ── */
.inventory-section {
  padding: 16px;
  border-bottom: 1px solid var(--border);
}
.inventory-section h3 { font-size: 0.9rem; color: var(--muted); margin-bottom: 10px; }
.inventory-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.inv-item {
  background: var(--card2);
  border: 1px solid var(--gold);
  border-radius: 20px;
  color: var(--gold);
  font-size: 0.8rem;
  font-weight: 600;
  padding: 4px 12px;
}
.inv-empty { color: var(--muted); font-size: 0.85rem; }

/* ── Event Banner ── */
.event-banner {
  background: var(--dune-dark);
  border-left: 4px solid var(--gold);
  color: var(--gold);
  font-size: 0.9rem;
  margin: 0 16px;
  padding: 10px 14px;
  border-radius: 0 var(--radius) var(--radius) 0;
}
.event-banner.hidden { display: none; }

/* ── Log ── */
/* ── The activity log, over the map ──────────────────────────────────────
   It used to sit below everything, which made the page very tall on a phone.
   Now a button in the left rail lifts it over the chart.

   .island-map-container is overflow:hidden, so the list MUST scroll inside
   this panel — a body that grows past the panel is clipped, not scrolled,
   and takes the close button with it. Same trap as .modal-card. */
:root[data-theme="paper"] .toast-ico {
  width: 1.3em;
  height: 1.3em;
  vertical-align: -0.3em;
  margin-right: 6px;
}

:root[data-theme="paper"] .sail-ico {
  width: 64px;
  height: 64px;
  display: block;
  margin: 0 auto;
}

:root[data-theme="paper"] .hdr-ico {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.22em;
  margin-right: 3px;
  flex: none;
}

.log-overlay {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border-radius: var(--radius);
}
.log-overlay.hidden { display: none; }
.log-overlay-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  flex: none;
}
.log-overlay-head h3 { font-size: 0.9rem; color: var(--muted); }
.log-close {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 4px 8px;
}
.log-overlay .game-log {
  min-height: 0;          /* or flex refuses to shrink it and it overflows */
  overflow-y: auto;
  padding: 10px 12px;
}
.log-overlay .game-log:empty::after {
  content: "Nothing has happened yet.";
  color: var(--muted);
  font-size: 0.82rem;
}

/* The rail button. Built from .res so it lines up with the meters above it,
   and carries both sheets like they do — emoji live, drawing under paper. */
.rail-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  padding: 0;
}
.rail-btn-label { font-size: 0.68rem; font-weight: 700; color: var(--muted); }
.rail-btn[aria-expanded="true"] .rail-btn-label { color: var(--gold); }

.game-log {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.log-entry {
  background: var(--card2);
  border-radius: 8px;
  font-size: 0.82rem;
  padding: 8px 12px;
  color: var(--text);
}
.log-entry:first-child {
  border-left: 3px solid var(--green);
}

/* ── Craft Modal ── */
.modal-overlay {
  background: rgba(0,0,0,0.75);
  bottom: 0;
  left: 0;
  position: fixed;
  right: 0;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 100;
}
.modal-overlay.hidden { display: none; }

.modal-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  max-width: 420px;
  max-height: 85vh;
  width: 100%;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  overflow: hidden;
}

#craft-list, #upgrade-list, #tinker-list, #treat-list, #ask-body {
  overflow-y: auto;
  flex: 1;
  /* Without min-height:0 a flex item refuses to shrink below its content, so the
     list overflows the card instead of scrolling inside it — the intermittent
     "won't scroll until I re-open it" bug. */
  min-height: 0;
  -webkit-overflow-scrolling: touch;   /* momentum scrolling on iOS */
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-right: 4px;
}
.modal-card h2 { color: var(--gold); font-size: 1.2rem; }

/* Sail discovery reveal */
.sail-reveal { text-align: center; align-items: center; }
.sail-emoji {
  font-size: 4.5rem;
  line-height: 1;
  animation: sail-pop 0.5s ease;
}
@keyframes sail-pop {
  0%   { transform: scale(0.3); opacity: 0; }
  70%  { transform: scale(1.15); }
  100% { transform: scale(1); opacity: 1; }
}
.sail-desc { color: var(--muted, var(--sand-mute)); font-size: 0.95rem; margin: 0; }
.island-pill { border-color: var(--blue) !important; color: var(--reef-pale); letter-spacing: 1px; }
.sail-note {
  background: var(--sand-pale);
  color: var(--dune-dark);
  border: 1px solid var(--dune-pale);
  border-radius: 8px;
  padding: 16px 18px;
  width: 100%;
  font-family: "Bradley Hand", "Segoe Print", "Comic Sans MS", cursive;
  font-size: 1.05rem;
  line-height: 1.5;
  text-align: center;
  box-shadow: inset 0 0 24px rgba(120, 90, 40, 0.18);
  transform: rotate(-1.2deg);
}
.sail-note.hidden { display: none; }
.sail-reward {
  background: var(--card2);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  color: var(--sand-pale);
  font-weight: 600;
  padding: 14px 16px;
  width: 100%;
}

.craft-item {
  background: var(--card2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.craft-item.owned { opacity: 0.5; }
.craft-item .craft-name { font-weight: 700; font-size: 1rem; }
.craft-item .craft-effect { color: var(--muted); font-size: 0.82rem; }
.craft-item .craft-cost  { font-size: 0.82rem; color: var(--gold); }
.craft-item .btn { margin-top: 8px; width: 100%; }

/* ── Fishing minigames ── */
.cast-track {
  position: relative;
  height: 44px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.cast-zone-poor, .cast-zone-good, .cast-zone-perfect {
  position: absolute;
  top: 0;
  bottom: 0;
}
.cast-zone-poor    { background: rgba(230, 126, 34, 0.22); }
.cast-zone-good    { background: rgba(76, 175, 80, 0.35); }
.cast-zone-perfect { background: rgba(240, 192, 96, 0.60); }
.cast-marker {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 4px;
  background: var(--sand-pale);
  border-radius: 2px;
  transform: translateX(-50%);
  box-shadow: 0 0 8px rgba(255,255,255,0.8);
}

.cast-result {
  text-align: center;
  font-weight: 700;
  color: var(--gold);
  min-height: 1.4em;
}

.reel-bar-label {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 4px;
}
.reel-bar-track {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  height: 18px;
  overflow: hidden;
}
.reel-bar-fill { height: 100%; width: 0%; transition: width 0.15s linear; }
.reel-progress-fill { background: var(--green); }
.carve-progress-fill { background: var(--dune); }

/* ── Canoe carving minigame ── */
.carve-log {
  position: relative;
  height: 62px;
  margin: 10px 0;
  border-radius: 31px / 18px;
  background: linear-gradient(180deg, var(--dune-deep) 0%, var(--dune-deep) 55%, var(--dune-dark) 100%);
  border: 2px solid var(--dune-dark);
  overflow: hidden;
  box-shadow: inset 0 -6px 12px rgba(0,0,0,0.35);
}
.carve-grain {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    90deg,
    transparent, transparent 13px,
    rgba(90, 61, 30, 0.45) 13px, rgba(90, 61, 30, 0.45) 15px
  );
}
.carve-sweet {
  position: absolute;
  top: 6px;
  bottom: 6px;
  left: 42%;
  width: 16%;
  border-radius: 10px;
  background: rgba(255, 224, 150, 0.30);
  border: 1px dashed rgba(255, 224, 150, 0.65);
}
.carve-chisel {
  position: absolute;
  top: -4px;
  left: 50%;
  font-size: 1.7rem;
  transform: translateX(-50%);
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.7));
  line-height: 1;
}
#carve-done-btn { margin-top: 8px; }
.canoe-pill { border-color: var(--dune) !important; color: var(--dune-pale) !important; }
.reel-tension-fill { background: var(--blue); }
.reel-tension-fill.danger { background: var(--red); animation: pulse-tension-bg 0.6s infinite; }
@keyframes pulse-tension-bg {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

.btn-reel {
  font-size: 1.15rem;
  padding: 18px 20px;
  background: var(--abyss-lift);
  color: var(--reef-pale);
}

/* ── Digging minigame ── */
.dig-energy-label {
  text-align: center;
  font-weight: 700;
  color: var(--gold);
  font-size: 0.9rem;
}

.dig-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.dig-cell {
  aspect-ratio: 1;
  background: var(--card2);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--muted);
  font-size: 1.4rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s, transform 0.1s;
}
.dig-cell:not(:disabled):hover { background: var(--abyss-lift); }
.dig-cell:active:not(:disabled) { transform: scale(0.94); }
.dig-cell:disabled { opacity: 0.35; cursor: not-allowed; }

.dig-cell.revealed {
  flex-direction: column;
  gap: 2px;
  font-size: 1.3rem;
  cursor: default;
}
.dig-cell.revealed span {
  font-size: 0.55rem;
  font-weight: 600;
  color: var(--muted);
}
.dig-cell.dig-empty { opacity: 0.45; }
.dig-cell.dig-found {
  border-color: var(--gold);
  background: rgba(240, 192, 96, 0.15);
}
.dig-cell.dig-found span { color: var(--gold); }

.material-pill { border-color: var(--blue); color: var(--reef-pale); }

/* ── Hunting minigame ── */
.hunt-scene { display: none; }
.hunt-clearing {
  position: relative;
  height: 220px;
  background: linear-gradient(180deg, var(--kelp-deep) 0%, var(--abyss-deep) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.hunt-animal, .hunt-sight {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 2rem;
  line-height: 1;
  pointer-events: none;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.6));
}

.hunt-sight {
  font-size: 1.7rem;
  opacity: 0.85;
}

.btn-hunt-shoot {
  font-size: 1.15rem;
  padding: 18px 20px;
  background: var(--kelp-deep);
  color: var(--kelp-pale);
}

/* ── Low resource pulse ── */
@keyframes pulse-red {
  0%, 100% { color: var(--red); }
  50% { color: var(--coral-pale); }
}
.res.low span { animation: pulse-red 1.2s infinite; }

/* ── Resource change flash ── */
@keyframes flash-gain {
  0%   { color: var(--green); transform: scale(1.4); }
  70%  { color: var(--green); }
  100% { color: inherit; transform: scale(1); }
}
@keyframes flash-lose {
  0%   { color: var(--red); transform: scale(1.25); }
  70%  { color: var(--red); }
  100% { color: inherit; transform: scale(1); }
}
.res span.gained { animation: flash-gain 0.7s ease-out forwards; }
.res span.lost   { animation: flash-lose 0.7s ease-out forwards; }

/* ── Diving / spearfishing ── */
.dive-water {
  position: relative;
  height: 240px;
  background: linear-gradient(180deg, var(--abyss-lift) 0%, var(--abyss-deep) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.dive-fish {
  position: absolute;
  top: 0;
  left: 0;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  line-height: 1;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  will-change: transform;
}
.dive-fish.speared { opacity: 0.3; }
.dive-pearl { filter: drop-shadow(0 0 7px var(--sand-pale)); }

.dive-breath { display: flex; align-items: center; gap: 8px; }
.dive-breath-label { color: var(--muted, var(--sand-mute)); font-size: 0.8rem; white-space: nowrap; }
.dive-breath-track {
  flex: 1;
  height: 10px;
  background: var(--card2);
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
/* Driven by scaleX from JS — see openDive() for why it isn't width. */
.dive-breath-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--reef-deep), var(--reef-pale));
  transform-origin: left center;
}

.dive-tally { text-align: center; font-size: 0.9rem; margin: 0; }

/* ── Tinkering: circuit puzzle ── */
.circuit-loop {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px;
  padding: 12px;
  background: linear-gradient(180deg, var(--dune-dark) 0%, var(--abyss-sink) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.circuit-slot {
  width: 52px;
  height: 52px;
  font-size: 1.7rem;
  line-height: 1;
  border: 2px dashed var(--dune-deep);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.25);
  color: var(--dune-pale);
  cursor: pointer;
}
.circuit-slot.filled { border-style: solid; border-color: var(--dune); background: var(--dune-dark); }
.circuit-slot.bad { border-color: var(--red); background: var(--coral-deep); }
.circuit-link { color: var(--dune-deep); font-size: 1rem; }

.circuit-bulb {
  text-align: center;
  font-size: 2.6rem;
  line-height: 1;
  filter: grayscale(1) brightness(0.5);
  transition: filter 0.2s, transform 0.2s;
}
.circuit-bulb.lit {
  filter: none;
  transform: scale(1.15);
  animation: bulb-pop 0.5s ease;
}
@keyframes bulb-pop {
  0% { transform: scale(0.6); }
  60% { transform: scale(1.3); }
  100% { transform: scale(1.15); }
}

.circuit-parts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}
.circuit-part {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 58px;
  padding: 8px 6px;
  font-size: 1.6rem;
  line-height: 1;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card2);
  color: var(--text, var(--sand-pale));
  cursor: pointer;
}
.circuit-part span { font-size: 0.62rem; color: var(--muted, var(--sand-mute)); }
.circuit-part.selected { border-color: var(--dune); box-shadow: 0 0 0 2px rgba(216, 161, 58, 0.4); }

.circuit-msg { text-align: center; min-height: 1.2em; font-size: 0.9rem; margin: 0; }
.circuit-btns { display: flex; gap: 10px; }
.circuit-btns .btn { flex: 1; }

/* ── Gardening ── */
.garden-grid {
  display: grid;
  /* Beds are craftable now, so the grid has to hold anything from 4 to MAX_PLOTS.
     auto-fit keeps two per row on a phone and fills the width on anything bigger,
     instead of growing into one long column. */
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
  /* Same trap the craft lists hit: .modal-card is overflow:hidden, so a grid tall
     enough to pass 85vh gets clipped — taking the seed tray and the Done button
     with it — rather than scrolling. Four beds always fit, which is why this only
     showed up once beds became craftable. 0 1 auto so a small garden still sits at
     its natural height instead of stretching. */
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.garden-plot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 84px;
  padding: 10px 6px;
  font-size: 2rem;
  line-height: 1;
  border: 2px solid var(--dune-deep);
  border-radius: 10px;
  background: linear-gradient(180deg, var(--dune-dark) 0%, var(--abyss-sink) 100%);
  color: var(--sand-pale);
  cursor: pointer;
}
.garden-plot.empty { border-style: dashed; color: var(--dune-deep); font-size: 1.5rem; }
.garden-plot.watered { border-color: var(--reef-lift); }
.garden-plot.ripe { border-color: var(--gold); background: var(--dune-dark); }
.garden-plot span { font-size: 0.72rem; color: var(--sand-pale); }
.garden-plot small { font-size: 0.62rem; color: var(--muted, var(--sand-mute)); }

.garden-seeds {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 0.82rem;
  color: var(--muted, var(--sand-mute));
}
.seed-tile {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 8px;
  font-size: 1.4rem;
  line-height: 1;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card2);
  color: var(--text, var(--sand-pale));
  cursor: pointer;
}
.seed-tile span { font-size: 0.6rem; color: var(--muted, var(--sand-mute)); }
.seed-tile.selected { border-color: var(--kelp-pale); box-shadow: 0 0 0 2px rgba(142, 216, 142, 0.4); }
.garden-tally { text-align: center; min-height: 1.1em; font-size: 0.9rem; margin: 0; color: var(--gold); }
.garden-pill { border-color: var(--kelp-lift) !important; color: var(--kelp-pale); }
.kit-pill { border-color: var(--dune) !important; color: var(--dune-pale); }

/* ── Weather background shift ── */
body { transition: background-color 1.5s ease; }
body[data-weather="rainy"]  { background: var(--abyss-deep); }
body[data-weather="stormy"] { background: var(--abyss-sink); }

/* ── Desktop: the map must not push the actions off screen ──────────────
   The column is mobile-first, so on a wide window the map took the full
   1292px and stood 861px tall — taller than the viewport. Ben had to scroll
   down to the action stones and back up to the map every single turn.
   Capping by HEIGHT rather than width is what matters: the 3/2 ratio turns
   the leftover height into a width, so the map grows until the turn no
   longer fits on one screen and then stops. */
@media (min-width: 900px) {
  /* One width, used by both, so the chart and the isle row above it can never
     drift apart. They are siblings in the same section, so the 100% resolves
     against the same box for each. Ben: the chips were stretching the full
     window while the map had been narrowed, and the ellipses read as stretched. */
  .map-section { --isle-w: min(100%, calc(50vh * 3 / 2)); }
  .island-map-container,
  .island-chips { max-width: var(--isle-w); margin-inline: auto; }
}

/* ── Reduced motion ───────────────────────────────────────
   Honour the OS "reduce motion" setting — some players get motion sickness or
   find looping movement distracting, and it's a free win on weak hardware too.

   Animations aren't removed, they're collapsed to their END state, which is why
   nothing the game needs to communicate is lost: a low resource settles on solid
   red instead of pulsing, a found island appears without the pop, rain sits still
   instead of falling. The minigames move their markers from JavaScript, so cast,
   reel, hunt and dig all stay fully playable. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── One-shot disaster effects ──────────────────────────────────────────────
   The rules these follow, and the reason they're affordable at all:
     • transform and opacity ONLY, so the compositor does the work and nothing
       repaints — the same discipline the rain/lightning pass established.
     • Few large layers rather than many small ones. Each composited layer is a
       texture in GPU memory, and that's the real limit on an old iPad. Five
       one-shot layers for five seconds is a very different cost from one layer
       looping forever, which is why the rain had to be so much more frugal.
     • Removed from the DOM the moment they finish, so ordinary days cost nothing.
   `test_ui_rules.py` enforces the first rule on every @keyframes block. */
.disaster-fx {
  position: absolute;
  pointer-events: none;
  /* Above the player token, which is 6. A disaster happens TO the island and
     everyone standing on it: at 3 the castaway rode along on top of the wave
     instead of being swallowed by it, which read as the wave passing behind
     them. Nothing else inside the map goes higher — the modal overlay and the
     fx replay button are both outside it. */
  z-index: 7;
  will-change: transform, opacity;
}

/* Layer 1 — the sky going dark. Opacity only: as close to free as animation gets. */
.disaster-fx.disaster-dim {
  inset: 0;
  background: rgba(6, 4, 20, 0.7);
  opacity: 0;
  animation: disaster-dim 5.5s ease-in-out forwards;
}
@keyframes disaster-dim {
  0%, 100% { opacity: 0; }
  14%, 78% { opacity: 1; }
}

/* Layer 2 — spray and debris torn past on the wind. The same tiled-gradient trick
   as the rain: one oversized layer sliding exactly one tile, so the loop is
   seamless and it never exposes an edge. Two animations, both compositor-safe:
   the drift loops, the fade gives it a beginning and an end. */
.disaster-fx.cyclone-debris {
  inset: -70px;
  background-image: repeating-linear-gradient(
    100deg,
    transparent, transparent 7px,
    rgba(215, 235, 255, 0.22) 7px, rgba(215, 235, 255, 0.22) 8px
  );
  background-size: 44px 44px;
  opacity: 0;
  animation: cyclone-debris-drift 0.28s linear infinite,
             cyclone-debris-fade 5.5s ease-in-out forwards;
}
@keyframes cyclone-debris-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-44px, 8px, 0); }
}
@keyframes cyclone-debris-fade {
  0%, 100% { opacity: 0; }
  20%, 74% { opacity: 1; }
}

/* The 3D layers live inside this, and it exists for one specific reason: a
   perspective/rotateX element and a flat element are sorted against each other in 3D
   space, and where the tilted plane cuts through the flat one the browser draws the
   intersection — a dead-straight line across the map with the stacking order flipped
   either side of it. That's the horizontal seam this showed on iPad.
   translateZ(0) + flat + isolate render the tilted layers to their own flattened
   image first, so they composite as an ordinary 2D picture against everything else
   and can't intersect anything. Costs nothing: it's still just a composited layer. */
.disaster-fx.fx-stage {
  inset: 0;
  overflow: hidden;
  isolation: isolate;
  transform: translateZ(0);
  transform-style: flat;
}

/* Layer 3 — a wide outer wall of cloud, turning the other way and slower than the
   core. The counter-rotation is what stops it reading as one flat spinning disc. */
/* Everything about the paper cyclone lives behind the flag. Size, position and
   animation were all changed unscoped at first, which would have shipped a
   different cyclone to everyone still on the live theme — the redesign is meant
   to arrive all at once, not leak out a piece at a time. */
:root[data-theme="paper"] .disaster-fx.cyclone-outer {
  width: 138px;
  height: 182px;
  margin-top: -132px;
  margin-left: -69px;
  animation-name: cyclone-outer-sweep-paper;
}
:root[data-theme="paper"] .disaster-fx.cyclone {
  width: 112px;
  height: 164px;
  margin-top: -122px;
  margin-left: -56px;
  animation-name: cyclone-sweep-paper;
}

/* Cut paper, not gradients — see tools/cyclone.py. The outer wall is six thin
   arms; the core is four fat ones. Both are flat shapes with a hole punched in
   the middle, laid on the map like everything else. */
:root[data-theme="paper"] .disaster-fx.cyclone-outer {
  background: url("cyclone-outer.svg") center / contain no-repeat;
}
:root[data-theme="paper"] .disaster-fx.cyclone {
  background: url("cyclone.svg") center / contain no-repeat;
}
.disaster-fx.cyclone-outer {
  top: 50%;
  left: 0;
  /* Both layers hung off left:0, so their LEFT EDGES lined up and the wider
     wall stood BESIDE the core instead of behind it. Each is centred on the
     same axis now. Sizes are small on purpose: at 150px the funnel was taller
     than the island it was supposed to be crossing. */
  width: 250px;
  height: 250px;
  margin-top: -125px;
  border-radius: 50%;
  background: none;
  animation: cyclone-outer-sweep 5.5s ease-in-out forwards;
}
/* perspective() + rotateX() tilt the disc into the ground plane, so it reads as a
   wall of cloud standing on the island rather than a coin spinning on the screen.
   CSS 3D transforms cost exactly the same as 2D ones — same compositor, same GPU
   path, no repaint — so the depth is genuinely free. */
/* Seen from the side now, so no rotateX tilting it into the ground and no spin
   — a funnel does not read as turning by rotating the whole shape, it reads by
   leaning. The wall drifts slower and larger than the core, which gives the two
   layers a parallax as they cross. */
@keyframes cyclone-outer-sweep {
  0%   { transform: perspective(620px) rotateX(66deg) translate3d(-90%, 6%, 0) rotate(0deg) scale(0.5); opacity: 0; }
  18%  { opacity: 0.85; }
  72%  { opacity: 0.85; }
  100% { transform: perspective(620px) rotateX(66deg) translate3d(200%, -6%, 0) rotate(-560deg) scale(1.3); opacity: 0; }
}
/* The paper cyclone is a side-on funnel, so it neither tilts into the ground
   nor spins — it leans. Its own keyframes, because the live theme still has
   the disc and must keep sweeping it exactly as it always did. */
@keyframes cyclone-outer-sweep-paper {
  0%   { transform: translate3d(-110%, 4%, 0) scale(.62); opacity: 0; }
  18%  { opacity: .42; }
  72%  { opacity: .42; }
  100% { transform: translate3d(210%, -2%, 0) scale(1.24); opacity: 0; }
}

/* Layer 4 — the core vortex. conic-gradient makes a true spiral; the radial above
   it is the fallback older Safari falls back to when it drops the conic. */
.disaster-fx.cyclone {
  top: 50%;
  left: 0;
  width: 150px;
  height: 150px;
  margin-top: -75px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%,
    rgba(235, 245, 255, 0.9) 0%, rgba(175, 210, 248, 0.5) 34%,
    rgba(125, 170, 218, 0.24) 62%, rgba(125, 170, 218, 0) 76%);
  background: conic-gradient(from 0deg,
      rgba(220, 238, 255, 0)    0deg,
      rgba(220, 238, 255, 0.72) 50deg,
      rgba(155, 195, 242, 0.24) 125deg,
      rgba(220, 238, 255, 0)    195deg,
      rgba(190, 220, 252, 0.60) 285deg,
      rgba(220, 238, 255, 0)    360deg),
    radial-gradient(circle at 50% 50%,
      rgba(255, 255, 255, 0.85) 0%, rgba(255, 255, 255, 0) 24%);
  animation: cyclone-sweep 5.5s ease-in-out forwards;
}
/* Eight rotations across the map, growing hugely as it makes landfall mid-sweep. */
/* The core tilts less than the outer wall and straightens as it makes landfall,
   which is what sells it as a funnel leaning over the island rather than a flat
   spiral. Eight rotations across the sweep. */
@keyframes cyclone-sweep {
  0%   { transform: perspective(620px) rotateX(58deg) translate3d(-120%, 22%, 0) rotate(0deg) scale(0.4); opacity: 0; }
  16%  { opacity: 1; }
  50%  { transform: perspective(620px) rotateX(34deg) translate3d(90%, 0, 0) rotate(1440deg) scale(1.45); }
  76%  { opacity: 1; }
  100% { transform: perspective(620px) rotateX(52deg) translate3d(300%, -16%, 0) rotate(2880deg) scale(1.1); opacity: 0; }
}
@keyframes cyclone-sweep-paper {
  0%   { transform: translate3d(-130%, 6%, 0) scale(.5)  skewX(6deg);  opacity: 0; }
  16%  { opacity: 1; }
  50%  { transform: translate3d(80%, 0, 0)    scale(1.2) skewX(-5deg); }
  76%  { opacity: 1; }
  100% { transform: translate3d(300%, -4%, 0) scale(1)   skewX(5deg);  opacity: 0; }
}

/* Layer 5 — lightning inside the storm. Pure opacity. */
.disaster-fx.cyclone-flash {
  inset: 0;
  background: radial-gradient(ellipse at 55% 45%, rgba(225, 235, 255, 0.85) 0%, transparent 60%);
  opacity: 0;
  animation: cyclone-flash 5.5s linear forwards;
}
@keyframes cyclone-flash {
  0%, 26%, 30%, 44%, 48%, 61%, 65%, 100% { opacity: 0; }
  28%  { opacity: 0.95; }
  46%  { opacity: 0.7; }
  63%  { opacity: 0.85; }
}

/* The map itself flinches as the storm crosses. A transform on the container, so it
   stays on the compositor like everything else; kept small, and zero at both ends so
   it builds and settles rather than juddering for five seconds straight. */
.island-map-container.storm-shake { animation: storm-shake 5.5s ease-in-out forwards; }
@keyframes storm-shake {
  0%, 100%          { transform: translate3d(0, 0, 0); }
  22%               { transform: translate3d(-1px, 1px, 0); }
  30%               { transform: translate3d(2px, -1px, 0); }
  38%               { transform: translate3d(-3px, 2px, 0); }
  46%               { transform: translate3d(3px, 2px, 0); }
  54%               { transform: translate3d(-3px, -2px, 0); }
  62%               { transform: translate3d(3px, -1px, 0); }
  70%               { transform: translate3d(-2px, 1px, 0); }
  80%               { transform: translate3d(1px, -1px, 0); }
}

@media (prefers-reduced-motion: reduce) {
  /* The global reduce rule collapses animations to their end state, which for a
     sweeping cyclone means "already gone". Pin a still frame instead so the event is
     still seen — the layers are removed on a timer either way. */
  .disaster-fx.cyclone {
    opacity: 0.9;
    transform: perspective(620px) rotateX(40deg) translate3d(70%, 0, 0) rotate(200deg) scale(1.3);
  }
  .disaster-fx.cyclone-outer {
    opacity: 0.8;
    transform: perspective(620px) rotateX(66deg) translate3d(40%, 0, 0) rotate(-120deg) scale(1.1);
  }
  .disaster-fx.disaster-dim { opacity: 0.5; }
  .disaster-fx.cyclone-debris { opacity: 0.55; }
}

/* ── Volcanic eruption ──────────────────────────────────────────────────────
   Split deliberately across two mechanisms, because they don't cost the same:

     • The full-map work — sky darkening, falling ash, the red glow off the
       mountain — is done with overlay divs, exactly like the cyclone, so it lives
       on the compositor and never repaints.
     • The eruption itself is drawn *inside* the island SVG, so the mountain the
       player already knows is the thing that erupts. Worth knowing: SVG children
       can't be promoted to their own compositor layer, so animating them does
       repaint that region. It stays cheap here because the map is small (~350px)
       and the stages are a handful of slow opacity fades, not continuous motion.
       Keep it that way — heavy continuous animation belongs in an overlay div. */

.disaster-fx.eruption-dim {
  inset: 0;
  /* A vignette, not a flat wash. The eruption is drawn *inside* the island SVG while
     these layers sit on top of it, so a full-screen tint masks the very thing it's
     meant to be dramatising. This stays clear over the volcano (about 35%/32% of the
     map) and only darkens towards the edges. Static gradient, so it costs nothing. */
  background: radial-gradient(ellipse 52% 82% at 35% 24%,
    rgba(30, 16, 10, 0) 0%,
    rgba(30, 16, 10, 0.26) 50%,
    rgba(22, 11, 7, 0.72) 100%);
  opacity: 0;
  animation: eruption-dim 7s ease-in-out forwards;
}
@keyframes eruption-dim {
  0%       { opacity: 0; }
  34%      { opacity: 0.85; }
  56%      { opacity: 0.6; }   /* backs off while the plume is at full height */
  78%      { opacity: 0.9; }
  100%     { opacity: 0; }
}

/* Ash falling over everything — the rain trick, slowed right down and greyed out. */
.disaster-fx.eruption-ash {
  inset: -70px;
  background-image: repeating-linear-gradient(
    186deg,
    transparent, transparent 9px,
    rgba(180, 172, 162, 0.17) 9px, rgba(180, 172, 162, 0.17) 11px
  );
  background-size: 34px 34px;
  opacity: 0;
  animation: eruption-ash-fall 1.5s linear infinite,
             eruption-ash-fade 7s ease-in-out forwards;
}
@keyframes eruption-ash-fall {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-34px, 68px, 0); }
}
/* Ash arrives late on purpose. It used to be at full strength from 40%, right on top
   of the fountain and the climbing plume — so the stages played out behind a curtain.
   Ashfall is the aftermath anyway, so letting it follow the eruption is truer as well
   as clearer. */
@keyframes eruption-ash-fade {
  0%, 52%  { opacity: 0; }
  70%, 88% { opacity: 1; }
  100%     { opacity: 0; }
}

/* Firelight thrown off the mountain, pulsing as the fountain surges. */
.disaster-fx.eruption-glow {
  inset: 0;
  /* Firelight thrown *around* the mountain — a ring, so the crater, fountain and
     lava keep their own colour instead of being flooded orange. */
  background: radial-gradient(ellipse at 35% 32%,
    rgba(255, 130, 40, 0) 0%,
    rgba(255, 130, 40, 0) 16%,
    rgba(255, 126, 36, 0.42) 33%,
    rgba(255, 120, 30, 0) 66%);
  opacity: 0;
  animation: eruption-glow 7s ease-in-out forwards;
}
@keyframes eruption-glow {
  0%, 100%     { opacity: 0; }
  16%          { opacity: 0.35; }
  30%          { opacity: 1; }
  44%          { opacity: 0.6; }
  56%          { opacity: 0.95; }
  80%          { opacity: 0.4; }
}

/* The stages themselves, each on its own delay so the mountain wakes, bursts,
   throws a column of ash, and finally bleeds lava down its flanks. */
/* Both volcano charts, not one by id: the live theme's #map-volcano and
   the paper theme's #map-volcano-paper. Naming the id meant the paper
   chart never erupted — the class landed on a chart nobody was looking at. */
.island-bg-svg .vfx-eruption > *,
.island-bg-svg .vfx-glow { opacity: 0; }
.island-bg-svg.erupting .vfx-glow     { animation: vfx-glow 7s ease-in-out forwards; }
.island-bg-svg.erupting .vfx-fountain { animation: vfx-fountain 7s ease-out forwards; }
.island-bg-svg.erupting .vfx-plume    { animation: vfx-plume 7s ease-out forwards; }
.island-bg-svg.erupting .vfx-lava     { animation: vfx-lava 7s ease-in forwards; }

/* Stage 1 — the crater wakes and keeps burning. */
@keyframes vfx-glow {
  0%        { opacity: 0; }
  14%, 86%  { opacity: 1; }
  100%      { opacity: 0.35; }
}
/* Stage 2 — the lava fountain bursts out of the crater. */
@keyframes vfx-fountain {
  0%, 16%   { opacity: 0; transform: translateY(14px) scaleY(0.25); }
  30%       { opacity: 1; transform: translateY(0)    scaleY(1); }
  76%       { opacity: 1; transform: translateY(0)    scaleY(0.9); }
  100%      { opacity: 0; transform: translateY(6px)  scaleY(0.5); }
}
/* Stage 3 — the ash column climbs and keeps billowing outward. */
@keyframes vfx-plume {
  0%, 22%   { opacity: 0; transform: translateY(26px) scale(0.45); }
  52%       { opacity: 1; transform: translateY(0)    scale(1); }
  84%       { opacity: 0.9; transform: translateY(-6px) scale(1.12); }
  100%      { opacity: 0; transform: translateY(-14px) scale(1.2); }
}
/* Stage 4 — fresh lava runs down the flanks, and is the last thing to fade. */
@keyframes vfx-lava {
  0%, 40%   { opacity: 0; }
  70%, 90%  { opacity: 1; }
  100%      { opacity: 0.5; }
}
/* SVG transforms need an explicit box, or the origin is the whole viewBox and the
   fountain scales from the corner of the map instead of out of the crater. */
.island-bg-svg .vfx-fountain,
.island-bg-svg .vfx-plume {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

@media (prefers-reduced-motion: reduce) {
  /* Collapsed to the end state means "erupted and faded". Pin the peak instead. */
  .disaster-fx.eruption-dim  { opacity: 0.55; }
  .disaster-fx.eruption-glow { opacity: 0.6; }
  .disaster-fx.eruption-ash  { opacity: 0.35; }
  .island-bg-svg.erupting .vfx-eruption > * { opacity: 1; transform: none; }
}

/* ── Tsunami ────────────────────────────────────────────────────────────────
   Built around the draw-back, because that's the real tell: the sea leaves first,
   and the pause before it comes back is what makes the hit land. Slow, ominous,
   then violent — the contrast does more than raw speed would.
   The wave is tilted in 3D and therefore lives in an .fx-stage wrapper, or it would
   intersect the flat layers and draw that horizontal seam again. */

/* Stage 1 — the sea withdraws, uncovering wet sand and reef. */
.disaster-fx.tsunami-drawback {
  inset: 0;
  background: linear-gradient(90deg,
    rgba(120, 104, 76, 0) 0%, rgba(120, 104, 76, 0.4) 52%, rgba(126, 110, 80, 0.62) 100%);
  opacity: 0;
  animation: tsunami-drawback 12s ease-out forwards;
}
@keyframes tsunami-drawback {
  0%        { opacity: 0;   transform: translate3d(-16%, 0, 0); }
  13%       { opacity: 1;   transform: translate3d(0, 0, 0); }
  40%       { opacity: 1;   transform: translate3d(2%, 0, 0); }
  54%       { opacity: 0;   transform: translate3d(12%, 0, 0); }
  100%      { opacity: 0;   transform: translate3d(12%, 0, 0); }
}

/* ═══════════════════════════════════════════════════════════════════════
   TSUNAMI — a flip-book, not a slide
   Ben's idea: "more like the old fashioned cartoon flip books where the image
   changes in tiny stages... but the book is the movement from right to left
   rather than pages turning. That way we can keep the paper cut theme and make
   it look more crafty rather than trying to look like a high tech graphic wave
   which it doesn't anyway."

   Seven stills cut from flat paper, laid out in one strip seven frames wide
   (tools/wave.py). The window shows one at a time and the strip steps across
   it. Travel is drawn INTO the frames — the crest is 44 units further left in
   each — so the wave crosses the map by being redrawn, the way a flip-book
   moves a figure across a page.

   Percentages in `transform` are of the element, so translating the strip by
   -100% over steps(7) lands on each frame exactly. It is a transform, so it
   runs on the compositor: cheaper than the gradient wave it replaces, which
   animated three gradients and two pseudo-elements for twelve seconds.
   ═══════════════════════════════════════════════════════════════════════ */
.disaster-fx.tsunami-flip {
  inset: 0;
  overflow: hidden;
}
.disaster-fx.tsunami-flip::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 1200%;
  background-image: url("wave-strip.svg");
  background-size: 100% 100%;
  background-repeat: no-repeat;
  animation: wave-flip 4.8s steps(12) forwards;
  will-change: transform;
}
@keyframes wave-flip {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-100%, 0, 0); }
}

/* Stage 3 — everything under water, then draining away. */
.disaster-fx.tsunami-flood {
  inset: 0;
  background: linear-gradient(180deg,
    rgba(42, 112, 172, 0) 30%, rgba(42, 112, 172, 0.34) 58%, rgba(16, 58, 104, 0.55) 100%);
  opacity: 0;
  animation: tsunami-flood 12s ease-in-out forwards;
}
@keyframes tsunami-flood {
  0%, 52%   { opacity: 0; }
  70%, 88%  { opacity: 1; }
  100%      { opacity: 0; }
}

/* Stage 4 — foam and torn-up spray racing over the flood. */
.disaster-fx.tsunami-foam {
  /* Full bleed, not inset. Confining this to the lower half gave it a hard top edge
     straight across the page — a repeating gradient stops dead where its box stops.
     Held faint instead, so it reads as spray over everything without a seam. */
  inset: -70px;
  background-image: repeating-linear-gradient(
    96deg,
    transparent, transparent 10px,
    rgba(240, 252, 255, 0.2) 10px, rgba(240, 252, 255, 0.2) 13px
  );
  background-size: 52px 52px;
  opacity: 0;
  animation: tsunami-foam-race 0.4s linear infinite,
             tsunami-foam-fade 12s ease-in-out forwards;
}
@keyframes tsunami-foam-race {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-52px, 6px, 0); }
}
@keyframes tsunami-foam-fade {
  0%, 54%  { opacity: 0; }
  68%, 86% { opacity: 1; }
  100%     { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  /* Collapsed to the end state means "already drained". Pin the moment of
     impact — with a flip-book that is simply one page, held: frame 5 of 7,
     which is the break. -57.142% of the strip is 4/7 of the way across. */
  .disaster-fx.tsunami-flip::before {
    animation: none;
    transform: translate3d(-75.000%, 0, 0);
  }
  .disaster-fx.tsunami-flood { opacity: 0.85; }
  .disaster-fx.tsunami-foam  { opacity: 0.6; }
  .disaster-fx.tsunami-drawback { opacity: 0; }
}

/* ── Hail ───────────────────────────────────────────────────────────────────
   Big stones, and few of them. The first version tiled small dots densely across
   the whole screen, which is a picture of snow, not hail — hail is sparse, heavy
   and fast. Three layers, each with a *single* stone per tile and the tiles kept
   large, so only a handful of stones are ever on screen at once. Stones are big — the
   tiles were grown to match, or a dozen at this scale would blanket the map again.
   Each layer falls exactly one tile height per loop so it stays seamless, and the
   wind angle comes from rotating the layer rather than adding sideways travel —
   a rotated layer still loops perfectly on a pure vertical translate, whereas a
   diagonal one only lines up if the tile happens to divide both ways.
   Stones are drawn as vertical ellipses: a round dot looks like it's hanging, a
   stretched one reads as falling fast. */
.disaster-fx.hail-dim {
  inset: 0;
  /* Barely there. The heavy white wash was half of why it looked like a blizzard. */
  background: rgba(188, 206, 222, 0.13);
  opacity: 0;
  animation: hail-dim 4.2s ease-in-out forwards;
}
@keyframes hail-dim {
  0%, 100% { opacity: 0; }
  16%, 76% { opacity: 1; }
}

.disaster-fx.hail-a,
.disaster-fx.hail-b,
.disaster-fx.hail-c {
  /* Overscan has to clear a rotated layer's corners *and* a whole stone. */
  inset: -150px;
  opacity: 0;
  background-repeat: repeat;
}

/* Nearest: the big ones you actually notice. */
.disaster-fx.hail-a {
  background-image: radial-gradient(ellipse 45px 80px at 30% 22%,
    rgba(255,255,255,0.98) 0%, rgba(232,246,255,0.95) 46%,
    rgba(198,226,248,0.55) 70%, rgba(198,226,248,0) 100%);
  background-size: 290px 380px;
  animation: hail-a-fall 0.5s linear infinite, hail-fade 4.2s ease-in-out forwards;
}
.disaster-fx.hail-b {
  background-image: radial-gradient(ellipse 30px 55px at 68% 55%,
    rgba(255,255,255,0.92) 0%, rgba(226,242,254,0.82) 48%,
    rgba(210,232,250,0.45) 72%, rgba(210,232,250,0) 100%);
  background-size: 250px 330px;
  animation: hail-b-fall 0.6s linear infinite, hail-fade 4.2s ease-in-out forwards;
}
/* Furthest and slowest — just enough to give the squall some depth. */
.disaster-fx.hail-c {
  background-image: radial-gradient(ellipse 20px 35px at 45% 80%,
    rgba(248,252,255,0.82) 0%, rgba(226,242,254,0.68) 50%,
    rgba(224,240,254,0.36) 74%, rgba(224,240,254,0) 100%);
  background-size: 210px 280px;
  animation: hail-c-fall 0.75s linear infinite, hail-fade 4.2s ease-in-out forwards;
}

@keyframes hail-a-fall {
  from { transform: rotate(9deg) translate3d(0, 0, 0); }
  to   { transform: rotate(9deg) translate3d(0, 380px, 0); }
}
@keyframes hail-b-fall {
  from { transform: rotate(7deg) translate3d(0, 0, 0); }
  to   { transform: rotate(7deg) translate3d(0, 330px, 0); }
}
@keyframes hail-c-fall {
  from { transform: rotate(6deg) translate3d(0, 0, 0); }
  to   { transform: rotate(6deg) translate3d(0, 280px, 0); }
}
@keyframes hail-fade {
  0%, 100% { opacity: 0; }
  12%, 80% { opacity: 1; }
}

/* Rattly rather than heavy — quick and jittery, where storm-shake rolls. */
.island-map-container.hail-rattle { animation: hail-rattle 4.2s linear forwards; }
@keyframes hail-rattle {
  0%, 100%                          { transform: translate3d(0, 0, 0); }
  12%, 20%, 28%, 36%, 44%, 52%, 60% { transform: translate3d(-1px, 1px, 0); }
  16%, 24%, 32%, 40%, 48%, 56%, 64% { transform: translate3d(1px, -1px, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .disaster-fx.hail-dim { opacity: 1; }
  .disaster-fx.hail-a, .disaster-fx.hail-b, .disaster-fx.hail-c { opacity: 1; }
}

/* ── Bushfire ───────────────────────────────────────────────────────────────
   The slow one. A fire front doesn't arrive, it builds: glow first, then smoke
   thickening over everything, embers lifting off it. No shake — fire doesn't
   shake — so the movement is all drift and pulse. */
.disaster-fx.bushfire-dim {
  inset: 0;
  background: linear-gradient(180deg,
    rgba(60, 30, 12, 0.5) 0%, rgba(84, 38, 12, 0.34) 55%, rgba(120, 52, 14, 0.2) 100%);
  opacity: 0;
  animation: bushfire-dim 8s ease-in-out forwards;
}
@keyframes bushfire-dim {
  0%, 100% { opacity: 0; }
  22%, 80% { opacity: 1; }
}

/* The fire front itself, burning along the bottom of the map and flaring. */
.disaster-fx.bushfire-glow {
  inset: 0;
  background: linear-gradient(0deg,
    rgba(255, 138, 30, 0.75) 0%, rgba(255, 106, 20, 0.42) 16%,
    rgba(255, 90, 20, 0.14) 34%, rgba(255, 90, 20, 0) 52%);
  opacity: 0;
  animation: bushfire-glow 8s ease-in-out forwards;
}
@keyframes bushfire-glow {
  0%       { opacity: 0; }
  16%      { opacity: 0.5; }
  30%      { opacity: 1; }
  44%      { opacity: 0.62; }
  58%      { opacity: 1; }
  72%      { opacity: 0.7; }
  100%     { opacity: 0; }
}

/* Smoke rolling across, thick and slow. */
.disaster-fx.bushfire-smoke {
  inset: -80px;
  background-image: repeating-linear-gradient(
    72deg,
    rgba(96, 78, 64, 0) 0px, rgba(96, 78, 64, 0) 22px,
    rgba(104, 86, 70, 0.34) 30px, rgba(88, 70, 56, 0.34) 52px,
    rgba(96, 78, 64, 0) 62px, rgba(96, 78, 64, 0) 84px
  );
  opacity: 0;
  animation: bushfire-smoke-drift 5.5s linear infinite,
             bushfire-smoke-fade 8s ease-in-out forwards;
}
@keyframes bushfire-smoke-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-84px, -26px, 0); }
}
@keyframes bushfire-smoke-fade {
  0%, 100% { opacity: 0; }
  34%, 78% { opacity: 1; }
}

/* Embers lifting off the fire — the detail that makes it read as a bushfire. */
.disaster-fx.bushfire-embers {
  inset: -80px;
  background-image: radial-gradient(circle, rgba(255,186,86,0.95) 0 1.4px, rgba(255,186,86,0) 2.1px);
  background-size: 46px 62px;
  opacity: 0;
  animation: bushfire-embers-rise 2.6s linear infinite,
             bushfire-embers-fade 8s ease-in-out forwards;
}
@keyframes bushfire-embers-rise {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-14px, -62px, 0); }
}
@keyframes bushfire-embers-fade {
  0%, 100% { opacity: 0; }
  30%, 74% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .disaster-fx.bushfire-dim    { opacity: 1; }
  .disaster-fx.bushfire-glow   { opacity: 0.9; }
  .disaster-fx.bushfire-smoke  { opacity: 0.8; }
  .disaster-fx.bushfire-embers { opacity: 0.9; }
}

/* Only ever created when ?fx= is in the URL — see startFxPreview() in game.js. */
#fx-replay,
#hurt-toggle {
  position: fixed;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  background: var(--card2);
  border: 1px solid var(--gold);
  border-radius: 20px;
  color: var(--gold);
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 8px 16px;
}
/* Above #fx-replay, so ?night&hurt together do not sit one on the other. */
#hurt-toggle { bottom: 58px; }

/* Music toggle. Hidden until initMusic() confirms a track actually loaded, so a
   game with no soundtrack shows no control for it. */
.btn-mute {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 20px;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  padding: 4px 8px;
}
.btn-mute:hover { border-color: var(--gold); color: var(--gold); }
.btn-mute.hidden { display: none; }

/* ═══════════════════════════════════════════════════════════════════════
   NIGHT — the charts light up while the day resolves

   From "Deep Reef Nights" (design/deep-reef-nights.html). Four steps turn a
   silhouette into a lit object: the day shape tinted down but never black,
   a rim in whatever light is hitting it, a bloom on that rim, and the day's
   second layer as a dimmer interior line.

   Four lights, one meaning each:
     --lit-biolume  the sea is awake   surf and ripples
     --lit-moon     direction          upper rims only, always from above
     --lit-living   it grows           canopy and fronds
     --lit-ember    heat, yours or not fire and the mountain
   Cold light is the world; every warm point is yours or a threat.

   RULE 01 — a lower layer's rim is dimmer than the one above it. That is
   what keeps the cut-paper depth readable with the lights off, so land is
   dimmer than the zones on it, which are dimmer than their details.

   The rim is a zero-blur drop-shadow and the bloom a blurred one, chained.
   That keeps fills sharp, follows any shape without a second path, and
   applies per *group* — so the whole chart costs six filter chains, which
   is what RULE 03 ("two blur filters, total") is really protecting.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] .island-map-container [data-lit] {
  transition: filter .7s ease, opacity .7s ease;
}
/* ── NIGHT, rebuilt so it works in WebKit ────────────────────────────────
   WebKit does not apply a CSS `filter` to an SVG group. It parses the
   declaration — getComputedStyle reports it back verbatim — and then paints
   the group as though it were not there. Chromium applies it. So the first
   night worked on my machine and showed Ben a fully lit island: the sun
   toggled, because that was `opacity`, and nothing else changed at all.

   Both halves are rebuilt out of things that work in both engines, verified
   side by side:
     · the dark          — swap the --map- tokens, so every fill changes at
                           once. This is the artifact's own model: "same
                           paths, second stylesheet, fills swapped".
     · the glow          — SVG <filter> referenced by ATTRIBUTE, always on,
                           whose flood-color is a var that is transparent by
                           day and the light colour at night.
     · the rim           — CSS `stroke`, which WebKit does apply.
   No CSS `filter` on an SVG element anywhere. ─────────────────────────── */
:root[data-theme="paper"] .island-map-container.is-night {
  --map-shelf: var(--n-shelf);
  --map-sea: var(--n-sea);
  --map-deep: var(--n-deep);
  --map-shallow: var(--n-shallow);
  --map-abyss: var(--n-abyss);
  --map-sand: var(--n-sand);
  --map-dune: var(--n-dune);
  --map-pale: var(--n-pale);
  --map-bone: var(--n-bone);
  --map-soil: var(--n-soil);
  --map-bark: var(--n-bark);
  --map-rock: var(--n-rock);
  --map-canopy: var(--n-canopy);
  --map-forest: var(--n-forest);
  --map-scrub: var(--n-scrub);
  --map-frond: var(--n-frond);
  --map-frond-lit: var(--n-frond-lit);
  --map-trunk-lit: var(--n-trunk-lit);
  --map-trunk-deep: var(--n-trunk-deep);
  --map-stone: var(--n-stone);
  --map-stone-deep: var(--n-stone-deep);
  --map-kraken: var(--n-kraken);
  --map-kraken-deep: var(--n-kraken-deep);
  /* fire and foam do not dim: they are the light */
  --glow-land:   var(--lit-biolume);
  --glow-rock:   var(--lit-moon);
  --glow-living: var(--lit-living);
  --glow-ember:  var(--lit-ember);
}
/* STEP 02 — the rim traces the object's own outline */
:root[data-theme="paper"] .island-map-container.is-night [data-lit="land"] {
  stroke: var(--lit-biolume); stroke-width: .8; stroke-opacity: .8;
}
:root[data-theme="paper"] .island-map-container.is-night [data-lit="rock"] {
  stroke: var(--lit-moon); stroke-width: .55; stroke-opacity: .85;
}
:root[data-theme="paper"] .island-map-container.is-night [data-lit="living"] {
  stroke: var(--lit-living); stroke-width: .7; stroke-opacity: .9;
}
:root[data-theme="paper"] .island-map-container.is-night [data-lit="biolume"] {
  stroke: var(--lit-biolume); opacity: 1;
}
:root[data-theme="paper"] .island-map-container.is-night [data-lit="ember"] {
  stroke: var(--lit-ember); stroke-width: .7; stroke-opacity: .95;
}
/* The sun sets and the moon rises. Both are always in the markup and only one
   is ever visible, so neither sky is ever empty. */
:root[data-theme="paper"] .island-map-container [data-lit="moon"] { opacity: 0; }
:root[data-theme="paper"] .island-map-container.is-night [data-lit="moon"] { opacity: 1; }
/* The moon gets the same neon rim every other lit thing has. Dimming the body
   is what makes the border read — and it is the correct way round anyway:
   Tier 2 is emission, never a fill, so the light belongs on the edge. The
   `path` selector keeps this off the halo circle behind it. */
:root[data-theme="paper"] .island-map-container.is-night [data-lit="moon"] path {
  fill-opacity: .5;
  stroke: var(--lit-moon);
  stroke-width: 1.5;
  stroke-opacity: 1;
}
:root[data-theme="paper"] .island-map-container.is-night [data-lit="sun"] { opacity: 0; }
:root[data-theme="paper"] .island-map-container.is-night [data-lit="cloud"] { opacity: .07; }

/* ── The recut Palm Isle chart is paper-only ─────────────────────────────
   Two charts now exist for the same island: the original, which the live
   theme keeps exactly as it is, and the cut-paper recut. Both carry
   data-island="home", so renderMap shows both — which chart you actually
   see is decided here, by theme, and nowhere else. Without this the recut
   would draw over the old one for every player on the live site. */
.island-bg-svg.paper-chart { display: none; }
:root[data-theme="paper"] .island-bg-svg.paper-chart:not(.hidden) { display: block; }
:root[data-theme="paper"] #map-home,
:root[data-theme="paper"] #map-volcano,
:root[data-theme="paper"] #map-coral { display: none; }

/* ── The island IS the map ───────────────────────────────────────────────
   Ben, 1 Sep: "rather than have the mountain and then an icon in front of
   it, if the mountain/forest is distinguishable enough it doesn't need the
   icon as well."

   Right — the recut chart already draws the forest, the standing stones,
   the cave mouth and the beach. A card in front of each one says the same
   thing twice and hides the drawing it is describing. So under paper the
   tiles stop being cards and become what they always were underneath: a
   hit area over the zone.

   What is lost and where it comes back: the tile no longer names the place,
   but the action panel already does ("👉 Your Action 🏖️ Beach"), and the
   player token already says where you are. What is kept is a rim on the
   current zone, so a tap has somewhere to land and "here" reads at a glance.

   Live theme untouched — it keeps its cards, its emoji and its labels. */
:root[data-theme="paper"] .map-location {
  background: none;
  backdrop-filter: none;
  border: 2px solid transparent;
  border-radius: 14px;
  font-size: 0;
  width: 66px;
  height: 60px;
  gap: 0;
}
:root[data-theme="paper"] .map-location span,
:root[data-theme="paper"] .loc-ico { display: none; }

/* No rim either. A rounded rectangle over the zone is a card by another
   name, and it hung off the bottom of the island like a stray box. The
   player token already says where you are, and the action panel names the
   place — so the tile's only remaining job is to be tappable. Pressing it
   dips, which is the one piece of feedback a touch screen actually gets. */
:root[data-theme="paper"] .map-location.current,
:root[data-theme="paper"] .map-location:hover {
  background: none;
  border-color: transparent;
  box-shadow: none;   /* the live rule's green halo, which showed as a dark box */
}
:root[data-theme="paper"] .map-location:active {
  transform: translate(-50%, -50%) scale(0.92);
}

/* ── The drawing is the button ───────────────────────────────────────────
   Ben, 2 Sep: "it would be good if the whole of the icon could be tappable."
   So each zone on the recut chart is a <g data-goto> carrying an invisible
   hit shape sized for a thumb, and the HTML tiles step out of the way
   entirely — a transparent 66px box floating over the art was still
   intercepting taps meant for the island.

   Live theme untouched: it keeps its cards, and its tiles keep working. */
:root[data-theme="paper"] .map-location { display: none; }

.paper-chart .zone { cursor: pointer; }
.paper-chart .hit { fill: transparent; }
.paper-chart .zone:active { opacity: .82; }

/* ── The castaway on the map ─────────────────────────────────────────────
   Under paper the token is a figure, not an emoji, so it needs a box rather
   than a font size. It keeps the same anchor — feet on the tile — and the
   same slide between locations. */
:root[data-theme="paper"] .player-token {
  font-size: 0;
  /* 42x60 first. The castaways are the only characters in the game and they
     were smaller than the tent behind them; the offset grows with the height
     so their feet stay on the same spot. */
  width: 56px;
  height: 80px;
  transform: translate(-50%, calc(-50% - 40px));
  /* filter: none is deliberate and has to be SAID. A CSS filter clips its
     element to the filter region — about 10% past the box — and the thought
     bubble sits 22% above the castaway's head, so the shadow was slicing the
     top off every bubble. Deleting the line from this block was not enough:
     the base .player-token rule sets its own drop-shadow and simply kept
     applying. The shadow moves to each castaway's drawing instead, which is
     where it belongs anyway — one per figure, not one for the whole group. */
  filter: none;
}
:root[data-theme="paper"] .player-token svg {
  width: 100%; height: 100%; display: block;
  filter: drop-shadow(0 2px 3px rgba(4, 18, 26, .55));
}

/* The castaway at night. Ben: stand out more, but not as a full glow item —
   and he is right that a rim would be wrong, because Deep Reef Nights gives
   the neon rim to the ISLAND, and a castaway wearing one would read as another
   piece of scenery rather than as a person standing on it.
   So the light lands on the GROUND, not the figure: a pool of moonlight at
   their feet, the way a standing figure actually separates from dark ground.
   The paper stays paper. A touch more lift under the drop-shadow does the
   rest. --lit-moon is Tier 2, emission only, which is exactly this. */
:root[data-theme="paper"] .island-map-container.is-night .player-token::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -5px;
  width: 44px;
  height: 15px;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--lit-moon), transparent);
  opacity: .42;
  z-index: -1;
}
:root[data-theme="paper"] .island-map-container.is-night .player-token {
  filter: drop-shadow(0 2px 3px var(--abyss-sink))
          drop-shadow(0 0 2px var(--lit-moon));
}
#castaway-set { display: none; }

/* ═══════════════════════════════════════════════════════════════════════
   ISLE BUTTONS — a stone each, with an icon and no word
   Ben: like the opening screen's buttons. Those are the beach stones from
   design/buttons-made-of-island.html, so these reuse that recipe rather than
   inventing a second button material: same organic radius, same lift, same
   press. Smaller, because there are three of them side by side.

   The icons are miniatures of each island's own chart — see tools/isle_chips.py
   for why they had to be drawn rather than lifted from an artifact.
   ═══════════════════════════════════════════════════════════════════════ */
/* The stones are separated from the map by a rule and some air, not by a
   frame around the chart. Ben's call and the right one: a box round the map
   fought the island's own silhouette, and the game already draws exactly this
   line between its other sections (--border, the same blue as .map-section's
   own bottom rule), so the map gets no new furniture of its own. */
:root[data-theme="paper"] .island-chips {
  gap: 10px;
  margin-bottom: 18px;
  padding-bottom: 15px;
  border-bottom: 1px solid var(--border);
}

:root[data-theme="paper"] .island-chip {
  --lo:  var(--sand-mute);
  --hi:  var(--stone-pale);
  --lo2: var(--stone-deep);
  --lift: 4px;

  position: relative;
  z-index: 0;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 5px 0 7px;
  overflow: visible;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translateY(0);
  filter: drop-shadow(0 var(--lift) 0 var(--lo2))
          drop-shadow(0 calc(var(--lift) + 3px) 0 rgba(4, 18, 26, .38));
  transition: transform 90ms cubic-bezier(.2, .8, .3, 1),
              filter    90ms cubic-bezier(.2, .8, .3, 1);
}
:root[data-theme="paper"] .island-chip::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* the opening screen's pebble, a little rounder: three of these sit in a
     row and the wide 68/32 lozenge reads as a bar at this width */
  border-radius: 46% 54% 49% 51% / 58% 42% 58% 42%;
  background-image:
    radial-gradient(circle at 24% 32%, rgba(12, 46, 61, .18) 0 1.3px, transparent 1.4px),
    radial-gradient(circle at 71% 26%, rgba(12, 46, 61, .14) 0 1.0px, transparent 1.1px),
    radial-gradient(circle at 80% 66%, rgba(12, 46, 61, .18) 0 1.4px, transparent 1.5px),
    radial-gradient(circle at 84% 40%, rgba(247, 230, 189, .46) 0 1.2px, transparent 1.3px),
    linear-gradient(155deg, var(--hi) 0%, var(--lo) 56%, var(--lo2) 100%);
}
:root[data-theme="paper"] .island-chip .chip-word { display: none; }
:root[data-theme="paper"] .island-chip .chip-ico {
  display: block;
  width: 44px;
  height: 44px;
}
:root[data-theme="paper"] .island-chip:hover:not(.locked) {
  transform: translateY(-2px);
  filter: drop-shadow(0 calc(var(--lift) + 2px) 0 var(--lo2))
          drop-shadow(0 calc(var(--lift) + 6px) 0 rgba(4, 18, 26, .40));
}
:root[data-theme="paper"] .island-chip:active:not(.locked) {
  transform: translateY(var(--lift));
  filter: none;
}

/* The chart you are looking at. The live theme says so with a gold border;
   a stone has no border, so it says so by being the pale one. */
:root[data-theme="paper"] .island-chip.viewing {
  background: none;
  --hi:  var(--sand-pale);
  --lo:  var(--dune-pale);
  --lo2: var(--dune-deep);
}

/* Where you actually are. The live theme appends a 🧍 to the label and there
   is no label any more, so it becomes a mark on the stone.
   A round pale dot was the first try and it was a mistake: under a palm tree,
   a small round white thing on the ground is a fallen coconut, which is
   exactly how Ben read it. A marker must not look like an object. So it is a
   bar — an underline under the icon, in the castaway's own thread colour. */
:root[data-theme="paper"] .island-chip.you-are-here::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 4px;
  width: 16px;
  height: 3px;
  transform: translateX(-50%);
  border-radius: 2px;
  background: var(--pl-thread);
  box-shadow: 0 1px 0 rgba(4, 18, 26, .30);
}

/* Unreachable without a canoe. Same three cues the opening screen's disabled
   buttons use — flat on the sand, greyed, and no press — never opacity alone,
   and the ::before is the stone here so the live theme's 🔒 has to go. */
:root[data-theme="paper"] .island-chip.locked {
  transform: translateY(var(--lift));
  filter: none;
  opacity: 1;
  cursor: default;
}
:root[data-theme="paper"] .island-chip.locked::before {
  content: "";
  --hi:  var(--stone-pale);
  --lo:  var(--sand-mute);
  --lo2: var(--sand-mute);
}
:root[data-theme="paper"] .island-chip.locked .chip-ico { opacity: .38; }

/* The in-game ask dialog. The body scrolls like every other growable thing in
   a modal-card — a long question must never push the buttons out of the card
   and strand the player mid-turn. */
.ask-body {
  color: var(--text);
  font-size: 0.95rem;
  line-height: 1.5;
  margin: 6px 0 18px;
}
.ask-actions {
  display: flex;
  gap: 10px;
}
.ask-actions .btn { flex: 1; }

/* ═══════════════════════════════════════════════════════════════════════
   THE LOGO — design/rope-trunk.html
   Both themes are live at once, so the live theme keeps its 🏝️ and its <h1>
   and the logo only appears under paper. The rope inside each lockup is drawn
   by placeRopes() in game.js, measured off the rendered type.
   ═══════════════════════════════════════════════════════════════════════ */
.lg-slot { display: none; }
:root[data-theme="paper"] .lg-slot { display: block; }
:root[data-theme="paper"] .game-logo,
:root[data-theme="paper"] .lobby-card > h1 { display: none; }

:root[data-theme="paper"] #screen-lobby .lg-slot {
  width: 230px;
  margin: 0 auto 6px;
}
.lg-lockup, .lg-mark, .lg-palm { width: 100%; height: auto; display: block; }

/* The top bar. The logo is the first thing in the header and the badges run
   after it, which is the relaid-out bar Ben asked for.
   Sized by HEIGHT, not width: the mark is as tall as its palm, so a width
   that looked right made the header 78px deep. Height fixed, width auto, and
   the viewBox keeps the proportion. */
:root[data-theme="paper"] .lg-slot-bar {
  flex: none;
  margin-right: 10px;
  line-height: 0;
}
:root[data-theme="paper"] .lg-slot-bar .lg-mark { height: 40px; width: auto; }
:root[data-theme="paper"] .lg-slot-bar .lg-palm { height: 34px; width: auto; }
/* The bar's rope is paler than the lockup's: it sits on --card, which is
   near-black under paper, and --mat-carve disappears on it. */
:root[data-theme="paper"] .lg-slot-bar { --lg-rope-fill: var(--dune); }

/* On a narrow screen the wordmark stops being readable long before it stops
   fitting, so the bar swaps to the palm alone — the standalone mark the logo
   artifacts settled on. A separate drawing, not the full mark with its words
   hidden, which would leave the palm off to one side of an empty box. */
.lg-palm { display: none; }
@media (max-width: 560px) {
  :root[data-theme="paper"] .lg-slot-bar .lg-mark { display: none; }
  :root[data-theme="paper"] .lg-slot-bar .lg-palm { display: block; }
}

/* ── The mountain stirring ──────────────────────────────────────────────
   design/deep-reef-nights.html, "Four states, one shape": the volcano is the
   only object that changes on its own, and its stirring state is the game's
   best foreshadowing — a warning you can SEE rather than read. The server
   already sets pending_disaster the night before an eruption lands, so this
   costs no new state at all.

   Drawn into the chart always and revealed by an attribute, rather than
   injected by JS: the charts are generated files and nothing should be
   building SVG at runtime to add a puff of ash. */
.mtn-stir { opacity: 0; transition: opacity .8s ease-in; }
.island-map-container[data-mountain="stirring"] .mtn-stir { opacity: 1; }
/* The ash drifts. Slow, and transform-only so it stays on the compositor. */
.island-map-container[data-mountain="stirring"] .mtn-ash {
  opacity: .34;
  animation: mtn-ash-drift 9s ease-in-out infinite;
  will-change: transform;
}
@keyframes mtn-ash-drift {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(4px, -3px, 0); }
}

/* ═══════════════════════════════════════════════════════════════════════
   THE PARTY — who you are, and who we are waiting for
   Two of Ben's asks, and they belong together: you pick your castaway in the
   lobby, and the castaways on the map say who has chosen an action. The line
   that used to read "waiting for Dad..." is gone — same fact, no sentence.
   ═══════════════════════════════════════════════════════════════════════ */
.way-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* The three skin tones. Set on any wrapper around a castaway — a lobby swatch,
   a figure on the map — and the drawing inside follows. Named for the castaway
   rather than just the tone: Tier 4 is fenced to player identity, and the guard
   in test_ui_rules.py reads the selector to enforce it. Two players may share a
   tone; it is the outfit colourway that has to be unique, because that is what
   tells them apart. */
.castaway-skin-a { --pl-skin: var(--pl-skin-a); --pl-skin-d: var(--pl-skin-a-d); }
.castaway-skin-b { --pl-skin: var(--pl-skin-b); --pl-skin-d: var(--pl-skin-b-d); }
.castaway-skin-c { --pl-skin: var(--pl-skin-c); --pl-skin-d: var(--pl-skin-c-d); }

.sub-label {
  display: block;
  margin: 10px 0 6px;
  font-size: 0.78rem;
  color: var(--muted);
}

.way-swatch {
  width: 46px;
  height: 62px;
  padding: 2px;
  border: 2px solid transparent;
  border-radius: 10px;
  background: var(--card2);
  cursor: pointer;
  transition: transform .12s, border-color .12s, background .12s;
}
.way-swatch svg { width: 100%; height: 100%; display: block; }
.way-swatch:hover { transform: translateY(-2px); }
.way-swatch.taken {
  opacity: .3;
  cursor: not-allowed;
  filter: grayscale(1);
}
.way-swatch.taken:hover { transform: none; }
.way-swatch.picked {
  border-color: var(--sand-pale);
  background: var(--dune-deep);
}
.way-section.hidden, #way-section.hidden { display: none; }

/* The token is a row of castaways now, not one figure. It stays centred on the
   tile, and they fan either side of it — --fan is 0 for a lone player, ±0.5
   for two, ±1 and 0 for three, and so on. */
.castaway-one {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 100%;
  height: 100%;
  /* 74% first, which overlapped them into a huddle. 100% puts each castaway
     exactly one body-width from the next: standing together, not stacked. */
  transform: translateX(calc(-50% + var(--fan, 0) * 100%));
}
.castaway-one svg { width: 100%; height: 100%; display: block; }

/* Which one is you. A scuff in the sand at your own feet — with a crowd on the
   tile there was otherwise nothing saying which castaway to look at, and that
   is half of "my colour isn't changing". Tapping it opens the picker.

   A real element, not a pseudo: ::after is the thought bubble and ::before its
   dot, so a third mark on the same element meant your own castaway lost its
   bubble the moment it gained a scuff. */
.castaway-one.me {
  pointer-events: auto;
  cursor: pointer;
}
.me-mark {
  position: absolute;
  left: 50%;
  bottom: -5%;
  width: 58%;
  height: 9%;
  transform: translateX(-50%);
  border-radius: 50%;
  background: var(--sand-pale);
  opacity: .5;
  pointer-events: none;
}

/* Still deciding. A thought bubble, off the shoulder and clear of the hat —
   sat directly over the head first, where it read as a second hat rather than
   as thinking. The little dot underneath is what makes it a bubble and not a
   blob; both drift together so it reads as one object. */
.castaway-one.deciding::after,
.castaway-one.deciding::before {
  content: "";
  position: absolute;
  background: var(--sand-pale);
  box-shadow: 0 1.5px 0 rgba(4, 18, 26, .3);
  animation: deciding-bob 1.9s ease-in-out infinite;
  will-change: transform, opacity;
  /* ::before sits before the castaway's drawing in tree order, so without this
     the bubble's little tail dot paints UNDER the hat and vanishes. */
  z-index: 2;
}
.castaway-one.deciding::after {
  left: 62%;
  top: -22%;          /* clear of the hat brim, which it clipped at -12% */
  width: 42%;
  height: 33%;
  border-radius: 50%;
}
.castaway-one.deciding::before {
  left: 57%;
  top: 4%;
  width: 15%;
  height: 12%;
  border-radius: 50%;
  animation-delay: .12s;
}
@keyframes deciding-bob {
  0%, 100% { transform: translateY(0)    scale(.92); opacity: .5; }
  50%      { transform: translateY(-16%) scale(1);   opacity: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CAST — the first minigame in cut paper
   NO ARTIFACT COVERS THE MINIGAMES. design/DECISIONS.md says so outright, so
   this is invented rather than built from a drawing, and it is meant to set
   the vocabulary the other five inherit: Carve and Hunt are the same timing
   game in different clothes.

   Three things it decides for the rest:
     · a TRACK is a place, not a bar — here a stretch of water seen from above,
       with the zones as bands of depth: open sea, the shallows, the shoal.
     · the MARKER is an object you recognise — a fishing float, not a line.
     · the RESULT is drawn where it happened. The float lands and rings spread
       from it, so the picture says "there" before the words do.
     Tier 1 Ground throughout, never --map-*. Those belong to the island SVGs
     alone (CLAUDE.md), and a minigame is chrome — it was built with them first,
     which is a colour being in the palette and still wrong for where it sits.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] .cast-track {
  height: 74px;
  border: 0;
  border-radius: 12px;
  background: var(--reef-deep);
  box-shadow: inset 0 2px 0 var(--abyss-lift);
}
:root[data-theme="paper"] .cast-zone-poor    { background: var(--reef); }
:root[data-theme="paper"] .cast-zone-good    { background: var(--reef-lift); }
:root[data-theme="paper"] .cast-zone-perfect { background: var(--reef-pale); }
/* Each band is cut paper laid on the one beneath, so the edges are the join */
:root[data-theme="paper"] .cast-zone-poor,
:root[data-theme="paper"] .cast-zone-good,
:root[data-theme="paper"] .cast-zone-perfect {
  border-radius: 10px;
  box-shadow: 0 2px 0 rgba(4, 18, 26, .22);
}

/* Two still swells, so the water is water and not a swatch. Static on
   purpose: nothing should compete with the float for the eye. */
:root[data-theme="paper"] .cast-swell {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(12px 4px at 18% 26%, var(--reef-pale) 0 99%, transparent 100%),
    radial-gradient(14px 4px at 79% 72%, var(--reef-pale) 0 99%, transparent 100%);
  opacity: .35;
}

/* The float. Coral over bone, the way every fishing float has ever looked. */
:root[data-theme="paper"] .cast-marker {
  top: 50%;
  left: 50%;
  bottom: auto;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  background: linear-gradient(var(--coral) 0 50%, var(--sand-pale) 50% 100%);
  box-shadow: 0 2px 0 rgba(4, 18, 26, .38);
  will-change: transform;
}

/* Where it landed. Rings spread from the float itself, so the answer is drawn
   at the spot rather than written underneath. transform and opacity only. */
:root[data-theme="paper"] .cast-ripple {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 2px solid var(--reef-pale);
  opacity: 0;
  pointer-events: none;
}
:root[data-theme="paper"] .cast-marker.landed .cast-ripple {
  animation: cast-ring .9s ease-out 2;
}
@keyframes cast-ring {
  from { transform: scale(.7); opacity: .9; }
  to   { transform: scale(2.6); opacity: 0; }
}

/* ── CARVE, in cut paper ───────────────────────────────────────────────
   Cast's vocabulary, second outing: the track is a place (a log, seen from the
   side), the marker is an object (the Chisel from forty-paper-icons.html, not
   the 🔪 emoji — the action needs a chisel and the emoji is a kitchen knife),
   and the result is drawn where it happened (chips fly from the strike). */
:root[data-theme="paper"] .carve-log {
  height: 76px;
  border: 0;
  border-radius: 38px / 20px;
  background: var(--mat-wood);
  box-shadow: inset 0 -10px 0 var(--dune-dark), 0 3px 0 rgba(4, 18, 26, .3);
  overflow: visible;                 /* the chisel hangs above the log */
}
/* Grain: a few long cuts along the log, not a barcode across it. */
:root[data-theme="paper"] .carve-grain {
  border-radius: 38px / 20px;
  overflow: hidden;
  background: repeating-linear-gradient(
    90deg, transparent 0 26px, var(--dune-dark) 26px 28px);
  opacity: .35;
}
/* The soft wood. Pale paper laid into the log, no dashes and no border. */
:root[data-theme="paper"] .carve-sweet {
  top: 10px;
  bottom: 18px;
  border: 0;
  border-radius: 14px / 10px;
  background: var(--dune-pale);
  box-shadow: inset 0 -4px 0 var(--dune);
}
:root[data-theme="paper"] .carve-chisel {
  top: -34px;
  width: 44px;
  height: 44px;
  margin-left: -22px;
  font-size: 0;
  filter: none;                      /* clips the chips, exactly as it did the
                                        castaway's thought bubble */
  will-change: transform;
}
:root[data-theme="paper"] .carve-chisel svg {
  width: 100%; height: 100%; display: block;
  filter: drop-shadow(0 2px 0 rgba(4, 18, 26, .3));
}
/* Chips. Three scraps of paper thrown off the blade, transform and opacity. */
:root[data-theme="paper"] .carve-chips {
  position: absolute;
  left: 50%;
  bottom: -2px;
  width: 0; height: 0;
  pointer-events: none;
}
:root[data-theme="paper"] .carve-chips i {
  position: absolute;
  width: 8px; height: 6px;
  border-radius: 2px 4px 3px 5px;
  background: var(--dune-pale);
  opacity: 0;
}
:root[data-theme="paper"] .carve-chisel.struck .carve-chips i {
  animation: carve-chip .55s ease-out forwards;
}
:root[data-theme="paper"] .carve-chisel.struck .carve-chips i:nth-child(2) {
  animation-delay: .04s;
}
:root[data-theme="paper"] .carve-chisel.struck .carve-chips i:nth-child(3) {
  animation-delay: .08s;
}
@keyframes carve-chip {
  from { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }
  to   { transform: translate3d(-26px, -22px, 0) rotate(-140deg); opacity: 0; }
}
:root[data-theme="paper"] .carve-chisel.struck .carve-chips i:nth-child(2) {
  animation-name: carve-chip-b;
}
@keyframes carve-chip-b {
  from { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }
  to   { transform: translate3d(22px, -26px, 0) rotate(120deg); opacity: 0; }
}
:root[data-theme="paper"] .carve-chisel.struck .carve-chips i:nth-child(3) {
  animation-name: carve-chip-c;
}
@keyframes carve-chip-c {
  from { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 1; }
  to   { transform: translate3d(4px, -32px, 0) rotate(40deg); opacity: 0; }
}

/* ── HUNT, in cut paper ────────────────────────────────────────────────
   Third outing for Cast's vocabulary. The clearing is a place — undergrowth,
   with the leaf shapes Volcano Isle's jungle uses — the animal is an object,
   and the sight is a real sight rather than a 🎯.

   THE DEER IS INVENTED. No artifact draws an animal; forty-paper-icons has the
   Bow and the Arrows and stops. Said out loud, per CLAUDE.md, rather than
   passed off as a port.

   Hunt already moved on `transform` and had a comment saying why, so unlike
   Cast and Carve there was nothing to fix here — only to draw. */
:root[data-theme="paper"] .hunt-clearing {
  height: 210px;
  border: 0;
  border-radius: 14px;
  background: var(--kelp-deep);
  overflow: hidden;
  box-shadow: inset 0 3px 0 rgba(4, 18, 26, .28);
}
:root[data-theme="paper"] .hunt-scene {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
:root[data-theme="paper"] .hunt-animal,
:root[data-theme="paper"] .hunt-sight {
  font-size: 0;
  filter: none;
}
:root[data-theme="paper"] .hunt-animal {
  width: 76px;
  height: 64px;
  margin: -32px 0 0 -38px;
  top: 50%; left: 50%;
}
:root[data-theme="paper"] .hunt-sight {
  width: 54px;
  height: 54px;
  margin: -27px 0 0 -27px;
  top: 50%; left: 50%;
  opacity: 1;
}
/* Both of these are re-positioned by JS every frame at 60fps. The animal's
   svg carries a drop-shadow, and a FILTERED element that moves has to be
   re-rasterised on every one of those frames — which is why the hunt felt
   laggy on Oliver's iPad. Promoting the mover to its own layer means the
   filter is rasterised once and the compositor just slides the layer. */
:root[data-theme="paper"] .hunt-animal,
:root[data-theme="paper"] .hunt-sight {
  will-change: transform;
}
:root[data-theme="paper"] .hunt-animal svg {
  width: 100%; height: 100%; display: block;
  filter: drop-shadow(0 2px 0 rgba(4, 18, 26, .32));
}
:root[data-theme="paper"] .hunt-sight svg { width: 100%; height: 100%; display: block; }

/* ── DIG, DIVE and CIRCUIT ─────────────────────────────────────────────
   The last three, and the only ones that are not the same timing game. Cast's
   vocabulary still applies: a track is a place, the pieces are objects, the
   result is drawn where it happened.

   Tier 1 Ground and Tier 3 Material only — never --map-*, which belongs to the
   island charts (test_ui_rules enforces it now). */

/* DIG — ground you have not turned over yet. Each cell is a patch of sand with
   its own cut edge, not a rounded UI tile; a dug one is a hole with what you
   found sitting in it. */
:root[data-theme="paper"] .dig-grid {
  /* The lift shadow under each patch sits OUTSIDE the cell box, so at full
     width the bottom row and the right column pushed past the card. Room for
     the shadow, and a smaller gap to pay for it. */
  gap: 8px;
  padding: 2px 4px 6px;
}
:root[data-theme="paper"] .dig-cell {
  border: 0;
  border-radius: 44% 56% 47% 53% / 55% 45% 55% 45%;
  background: var(--dune);
  color: var(--dune-dark);
  box-shadow: 0 3px 0 var(--dune-deep), inset 0 3px 0 var(--dune-pale);
  font-size: 1.5rem;
}
:root[data-theme="paper"] .dig-cell:not(:disabled):hover {
  background: var(--dune-pale);
}
:root[data-theme="paper"] .dig-cell:active:not(:disabled) {
  transform: translateY(3px);
  box-shadow: 0 0 0 var(--dune-deep), inset 0 3px 0 var(--dune-pale);
}
/* Dug. The sand is gone and you are looking into the hole. */
:root[data-theme="paper"] .dig-cell.revealed {
  background: var(--dune-dark);
  color: var(--sand-pale);
  box-shadow: inset 0 5px 0 rgba(4, 18, 26, .45);
}
/* The find owns the cell. There was never a size rule here at all — an earlier
   edit silently matched nothing — so the drawings rendered at whatever the SVG
   felt like, which is why they read as specks. The name becomes a caption
   across the bottom rather than a third of the cell. */
:root[data-theme="paper"] .dig-art {
  /* A percentage HEIGHT here fights the cell's aspect-ratio — the cell sizes
     to its content, the content sizes to the cell, and the grid grew until it
     pushed the Collect button off the card. Width plus its own aspect-ratio
     leaves the cell square and the drawing inside the sand patch. */
  width: 82%;
  aspect-ratio: 1;
  height: auto;
  display: block;
}
:root[data-theme="paper"] .dig-cell.revealed {
  gap: 0;
  padding: 4px;
  position: relative;
  justify-content: center;
}
:root[data-theme="paper"] .dig-cell.revealed span {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 3px;
  font-size: .55rem;
  opacity: .8;
  margin: 0;
  text-align: center;
}
/* An empty hole has no drawing, so its word sits where the find would have. */
:root[data-theme="paper"] .dig-cell.dig-empty span {
  position: static;
  font-size: .68rem;
}
:root[data-theme="paper"] .dig-cell.dig-found {
  background: var(--dune-deep);
  box-shadow: inset 0 5px 0 rgba(4, 18, 26, .35), 0 0 0 3px var(--sand-pale);
}

/* DIVE — water in bands, the way the charts cut it, with a reef floor. The
   deeper you look the darker the paper, which is also the shape of the game:
   the good fish are down there and your breath is up here. */
:root[data-theme="paper"] .dive-water {
  border: 0;
  height: 260px;
  border-radius: 14px;
  background:
    linear-gradient(var(--reef) 0 22%, var(--reef-deep) 22% 52%,
                    var(--abyss-lift) 52% 80%, var(--abyss) 80% 100%);
  box-shadow: inset 0 3px 0 rgba(247, 230, 189, .18);
}
:root[data-theme="paper"] .dive-breath-track {
  border: 0;
  background: var(--abyss-deep);
  box-shadow: inset 0 2px 0 rgba(4, 18, 26, .5);
}
:root[data-theme="paper"] .dive-breath-fill {
  background: var(--reef-pale);
}
:root[data-theme="paper"] .dive-fish svg {
  width: 100%; height: 100%; display: block;
  filter: drop-shadow(0 2px 0 rgba(4, 18, 26, .35));
}
/* A speared fish keeps more of itself than 0.3 opacity left it with — on a dark
   ground that was nearly gone. */
:root[data-theme="paper"] .dive-fish.speared { opacity: .5; }

/* CIRCUIT — a workbench, and the loop laid out on it. The slots are cut holes
   waiting for a part rather than dashed boxes. */
:root[data-theme="paper"] .circuit-loop {
  border: 0;
  border-radius: 12px;
  background: var(--mat-wood);
  box-shadow: inset 0 -8px 0 var(--dune-dark), 0 3px 0 rgba(4, 18, 26, .3);
}
:root[data-theme="paper"] .circuit-slot {
  border: 0;
  border-radius: 12px;
  background: var(--dune-dark);
  box-shadow: inset 0 4px 0 rgba(4, 18, 26, .4);
}
:root[data-theme="paper"] .circuit-slot.filled {
  background: var(--dune-pale);
  box-shadow: inset 0 3px 0 var(--sand-pale), 0 2px 0 var(--dune-deep);
}
:root[data-theme="paper"] .circuit-slot.bad {
  background: var(--coral);
  box-shadow: inset 0 3px 0 var(--coral-pale), 0 2px 0 var(--coral-deep);
}
:root[data-theme="paper"] .circuit-link { color: var(--dune-pale); }

/* ── Emoji that were only ever decoration ─────────────────────────────────
   A heading that reads "🔨 Craft an Item" is saying it twice: the word already
   names the thing, and the emoji is a picture of the word rather than a picture
   of anything in the game. Same for the emoji sitting in front of a button
   label. Both go under paper.

   HIDDEN, not deleted. The live theme was never redesigned and its emoji are
   doing real work there — this is the same bargain the resource rail struck
   with .res-emo. */
:root[data-theme="paper"] .ui-emo { display: none; }

/* Where the redesign needed different WORDS, both are in the markup and the
   theme picks one. The live theme still has a bar with a bright zone and a log
   with a dashed box: telling those players to "drop the float on the pale
   water" describes a game they cannot see. Nothing about the redesign should
   reach anyone before the flag comes off. */
.paper-only { display: none; }
:root[data-theme="paper"] .paper-only { display: revert; }
:root[data-theme="paper"] .live-only { display: none; }

/* Where the emoji WAS carrying meaning, the drawing takes over rather than the
   label simply losing its mark. Energy is the artifact's own Energy icon. */
.lbl-ico { display: none; }
:root[data-theme="paper"] .lbl-ico {
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  vertical-align: -0.18em;
  margin-right: .3em;      /* the space the emoji used to carry */
}

/* ── Three things the pictures already say ────────────────────────────────
   Ben's list. Each of these is a sentence describing something the redesign
   now draws, so under paper they go. All three are PAPER ONLY — the live theme
   has none of those drawings and would just lose the information.

   1. The location tip ("Good for fishing and collecting rainwater…"). The chart
      draws the place: the reef has fish on it, the springs steam. Naming what a
      tile is good for is the map read out loud.
   2. The player-status box ("Dad (you) · Deciding…"). The castaways stand on
      the map in their own colours with a thought bubble over whoever has not
      chosen — the box is the same fact in words.
   3. The action hint under the buttons ("Here: Fish (needs a Net or Rod)…").
      An action you cannot take is already a locked stone, and every stone
      carries its own glyph.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] #location-tip,
:root[data-theme="paper"] #action-hint,
:root[data-theme="paper"] #player-status { display: none; }

/* The tinker parts, drawn. An empty slot shows nothing under paper — a cut hole
   waiting for a part says "put something here" better than a ➕ does. */
:root[data-theme="paper"] .circ-art {
  width: 78%;
  aspect-ratio: 1;
  height: auto;
  display: block;
}
:root[data-theme="paper"] .circuit-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0;
}
:root[data-theme="paper"] .circuit-part {
  font-size: 0;
  width: 66px;
}
:root[data-theme="paper"] .circuit-part .circ-art { width: 46px; }
:root[data-theme="paper"] .circuit-part span {
  font-size: .62rem;
  line-height: 1.15;
}

/* The bulb is the puzzle's answer, so it has to change when the loop closes.
   Dark grey glass until then; the whole thing warms when it lights. */
:root[data-theme="paper"] .circuit-bulb { font-size: 0; filter: none; }
:root[data-theme="paper"] .bulb-art { width: 54px; height: 54px; margin: 0 auto; display: block; }
:root[data-theme="paper"] .circuit-bulb.lit .bulb-glass    { fill: var(--dune-pale); }
:root[data-theme="paper"] .circuit-bulb.lit .bulb-lit-half { fill: var(--dune); }
:root[data-theme="paper"] .circuit-bulb.lit .bulb-filament { stroke: var(--coral); }

/* Icons inside a line of text — the costs in the craft menu, and anywhere else
   iconFor() lands. Sized to the text, not to a box. */
.ico-inline { display: none; }
:root[data-theme="paper"] .ico-inline {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.24em;
}
/* The thing being made, beside its name. */
:root[data-theme="paper"] .craft-art {
  width: 2.5em;
  height: 2.5em;
  vertical-align: -0.82em;
  margin-right: .45em;
  display: inline-block;
}

/* ── BUSHFIRE, in cut paper ───────────────────────────────────────────────
   The gradients go, the shapes stay behind the flag. Both tiles repeat at
   240px and are moved with `transform`, so the existing animations carry them
   without change — the fire front creeps sideways along the bottom, the smoke
   bank rolls up and across.

   Literal colour inside the SVGs, not var(): a file loaded through url() is an
   isolated document and cannot see the page's tokens. See tools/bushfire.py.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] .disaster-fx.bushfire-dim {
  /* Tier 1, not --map-soil: this is chrome laid over the chart, not part of
     the chart. test_ui_rules catches the difference.
     Its own keyframes, peaking at a quarter rather than at 1 — a flat brown
     sheet at full strength hid the island completely, which is the same fault
     the storm had before it was thinned out. */
  background: var(--dune-dark);
  opacity: 0;
  animation-name: bushfire-dim-paper;
}
@keyframes bushfire-dim-paper {
  0%, 100% { opacity: 0; }
  22%, 80% { opacity: .26; }
}
:root[data-theme="paper"] .disaster-fx.bushfire-smoke {
  animation-name: bushfire-smoke-drift, bushfire-smoke-fade-paper;
}
@keyframes bushfire-smoke-fade-paper {
  0%, 100% { opacity: 0; }
  34%, 78% { opacity: .62; }
}
/* The fire front: a strip along the bottom, not a glow over everything. */
:root[data-theme="paper"] .disaster-fx.bushfire-glow {
  inset: auto 0 0 0;
  height: 42%;
  overflow: hidden;
}
/* The front creeps on a TRANSFORM, not background-position — the first cut
   animated the position and test_ui_rules failed it, which is the same repaint
   the storm's rain had to be rescued from. The strip is one tile wider than the
   map so sliding it a whole tile leaves no gap. */
:root[data-theme="paper"] .disaster-fx.bushfire-glow::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  top: 0;
  width: calc(100% + 240px);
  background: url("bushfire-flames.svg") repeat-x bottom left / auto 100%;
  animation: bushfire-front 3.4s linear infinite;
  will-change: transform;
}
@keyframes bushfire-front {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-240px, 0, 0); }
}
:root[data-theme="paper"] .disaster-fx.bushfire-smoke {
  background-image: url("bushfire-smoke.svg");
  background-repeat: repeat;
  background-size: 240px 150px;
}
/* Embers: flecks of paper, not blurred dots. */
:root[data-theme="paper"] .disaster-fx.bushfire-embers {
  background-image:
    radial-gradient(circle at 50% 50%, var(--dune-pale) 0 1.6px, transparent 1.7px),
    radial-gradient(circle at 20% 70%, var(--coral) 0 1.2px, transparent 1.3px);
  background-size: 46px 62px, 71px 83px;
}

/* Crafted items: the drawing, then the name. */
:root[data-theme="paper"] .inv-art {
  width: 1.5em;
  height: 1.5em;
  vertical-align: -0.42em;
  margin-right: .28em;
  display: inline-block;
}

/* ── HAIL, in cut paper ───────────────────────────────────────────────────
   The motion above is untouched — it was already right, and the comment
   explaining the rotation trick was hard won. Only the stones change: soft
   radial gradients become cut lumps of ice with a lit face and an underside.
   Same tiles, same centres, so every keyframe still lines up.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="paper"] .disaster-fx.hail-a {
  background-image: url("hail-a.svg");
  background-size: 290px 380px;
}
:root[data-theme="paper"] .disaster-fx.hail-b {
  background-image: url("hail-b.svg");
  background-size: 250px 330px;
}
:root[data-theme="paper"] .disaster-fx.hail-c {
  background-image: url("hail-c.svg");
  background-size: 210px 280px;
}
/* The wash was tuned for blurred stones; flat ones read stronger, so it thins. */
:root[data-theme="paper"] .disaster-fx.hail-dim {
  background: var(--reef-pale);
  animation-name: hail-dim-paper;
}
@keyframes hail-dim-paper {
  0%, 100% { opacity: 0; }
  16%, 76% { opacity: .1; }
}

/* The bandage pulses so a hurt player is impossible to miss from across the
   room — which is the whole reason it replaced a badge in a box nobody looked
   at. transform and opacity only, so it stays on the compositor. */
.cw-stain {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: hurt-pulse 1.15s ease-in-out infinite;
  will-change: transform, opacity;
}
/* The drips run a beat behind the stain, so the two read as one wound
   seeping rather than as a badge blinking. */
.cw-drip {
  transform-box: fill-box;
  transform-origin: 50% 0%;
  animation: hurt-drip 1.15s ease-in-out infinite;
  will-change: transform, opacity;
}
@keyframes hurt-drip {
  0%, 100% { transform: scaleY(.82); opacity: .7; }
  62%      { transform: scaleY(1.1); opacity: 1; }
}
@keyframes hurt-pulse {
  0%, 100% { transform: scale(1);    opacity: .78; }
  50%      { transform: scale(1.5);  opacity: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════
   LAUNCH INTRO — the one-time "here's the new theme" reel, design/DECISIONS.md
   "Launch" section. Shown once per browser, before the lobby, to a player who
   hasn't opted into ?theme=paper yet. Everything inside is either a real
   element cloned from elsewhere on this page (castaways, the island charts,
   the Fell/Dig/Hunt/Sail/Radio buttons) or a small isolated copy of a real
   mechanism (night lighting, the cyclone) under its own `.li-` classes, so it
   can't collide with the ids/selectors the live game already uses on those
   same elements. See maybeShowLaunchIntro() in game.js.
   ═══════════════════════════════════════════════════════════════════════ */
#screen-launch-intro.active {
  display: flex; flex-direction: column; gap: 12px;
  align-items: center; justify-content: center; padding: 4vh 16px;
}
.li-skip {
  background: transparent; border: 1px solid var(--border); color: var(--muted);
  border-radius: 20px; padding: 6px 18px; font-size: .85rem; cursor: pointer;
}
.li-skip:hover, .li-skip:focus-visible { color: var(--text); border-color: var(--gold); }
.li-frame {
  /* Sized by whichever runs out first — the column's width, or the height
     left once the Skip button and the screen's padding are accounted for.
     max-width was 640px, which on a 1440px desktop left the launch playing
     in 44% of the window. Ben: "will be more impacting". The 3/2 ratio is
     what turns the height budget into a width. */
  width: min(100%, calc(78vh * 3 / 2), 1280px); aspect-ratio: 3 / 2;
  position: relative; overflow: hidden; border-radius: var(--radius);
  background: var(--abyss-deep);
}
.li-scene { position: absolute; inset: 0; opacity: 0; z-index: 1; transition: opacity .7s ease; }
.li-scene.active { opacity: 1; z-index: 2; }

/* the old look, then the paper redesign walks on and takes over */
.li-layer { position: absolute; inset: 0; }
.li-layer-old {
  clip-path: inset(0 0 0 0%); transition: clip-path 5.2s cubic-bezier(.65, 0, .35, 1); z-index: 3;
}
.li-scene-a.li-reveal .li-layer-old { clip-path: inset(0 0 0 101%); }
.li-layer-paper { z-index: 1; }

.li-island-map-container { position: absolute; inset: 0; }
.li-island-map-container .island-bg-svg { position: absolute; inset: 0; width: 100%; height: 100%; }

/* three real "stone" buttons (min-width 96px, basis 158px each per the real
   .action-buttons rule) need >= ~490px to stay on one line — flex-wrap breaks
   a line based on each item's basis, before shrink is ever considered, so a
   narrower box wraps them even though they'd fit fine once shrunk. */
.li-action-strip {
  position: absolute; left: 50%; bottom: 5%; transform: translateX(-50%);
  width: 94%; max-width: 520px;
}
/* the old layer's inert buttons have no .action-buttons to lay them out */
.li-layer-old .li-action-strip { display: flex; gap: 8px; max-width: 320px; }

/* the old-layer's buttons are deliberately NOT real .btn-action elements —
   those pick up the paper theme's base override the instant [data-theme]
   flips, which happens at the very start of the intro (see game.js). A
   plain inert button, styled from the always-on legacy token aliases, is
   what stays looking like "today" underneath the wipe. */
.li-obtn {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 1px;
  background: var(--card2); border: 1.5px solid var(--border); border-radius: 9px;
  padding: 7px 4px 5px; font-size: 1.1rem; line-height: 1;
}
.li-obtn span { font-size: .5rem; letter-spacing: .02em; color: var(--kelp-pale); text-transform: uppercase; }
.li-obtn.li-o-fell, .li-obtn.li-o-dig { border-color: var(--dune-pale); }
.li-obtn.li-o-hunt { border-color: var(--kelp); }

/* one demo castaway, walking the island and playing a turn — a first look at
   "tap the island to move, tap the glyph to act" now that labels are gone */
.li-token {
  position: absolute; width: 9%; aspect-ratio: 7 / 10; z-index: 6; opacity: 0;
  transform: translate(-50%, calc(-50% - 34%));
  transition: top .55s cubic-bezier(.34, 1.56, .64, 1), left .55s cubic-bezier(.34, 1.56, .64, 1), opacity .4s ease;
  pointer-events: none;
}
.li-token.li-show { opacity: 1; }
.li-token svg { width: 100%; height: 100%; display: block; filter: drop-shadow(0 2px 3px var(--abyss-sink)); }
.li-token.li-walking .r-legL, .li-token.li-walking .r-legR {
  transform-box: fill-box; transform-origin: 50% 4%; animation: li-gait .38s ease-in-out infinite alternate;
}
.li-token.li-walking .r-legR { animation-delay: .19s; }
@keyframes li-gait { from { transform: rotate(-7deg); } to { transform: rotate(7deg); } }

.li-walkers { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
.li-walker {
  position: absolute; bottom: 8%; width: 11%; left: -18%; opacity: 0;
  transition: transform 5.2s cubic-bezier(.4, .1, .4, 1), opacity .8s ease;
}
.li-walker svg { width: 100%; display: block; overflow: visible; }
.li-scene-a.li-reveal .li-walker { opacity: 1; transform: translateX(760%); }
.li-scene-a.li-settled .li-walker { opacity: 0; transition: opacity .6s ease; }
.li-walker.li-walking .r-legL, .li-walker.li-walking .r-legR {
  transform-box: fill-box; transform-origin: 50% 4%; animation: li-gait .42s ease-in-out infinite alternate;
}
.li-walker.li-walking .r-legR { animation-delay: .21s; }

.li-action-strip .btn-action.li-pulse { animation: li-btnscale .7s ease; }
@keyframes li-btnscale { 0% { transform: scale(1); } 35% { transform: scale(1.14); } 100% { transform: scale(1); } }
.li-burst { position: absolute; width: 6%; aspect-ratio: 1; z-index: 7; pointer-events: none; }
.li-burst i {
  position: absolute; left: 50%; top: 50%; width: 9%; height: 9%; border-radius: 50%;
  background: var(--sand-pale); transform: translate(-50%, -50%); opacity: 0; animation: li-burstpop .7s ease-out forwards;
}
@keyframes li-burstpop {
  0% { opacity: 1; transform: translate(-50%, -50%) translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) translate(var(--dx), var(--dy)) scale(.4); }
}

/* the real night mechanism (style.css's own "NIGHT — the charts light up"
   section), copied under `.li-` classes since --n-* / --map-* only resolve
   once [data-theme="paper"] is really set on the document, which is true for
   the whole intro (see maybeShowLaunchIntro()) — this just avoids the two
   places that pick ".island-map-container" by querySelector() ever matching
   this clone instead of the real one later in the same page session. */
.li-island-map-container.li-night {
  --map-shelf: var(--n-shelf); --map-sea: var(--n-sea); --map-deep: var(--n-deep);
  --map-shallow: var(--n-shallow); --map-abyss: var(--n-abyss); --map-sand: var(--n-sand);
  --map-dune: var(--n-dune); --map-pale: var(--n-pale); --map-bone: var(--n-bone);
  --map-soil: var(--n-soil); --map-bark: var(--n-bark); --map-rock: var(--n-rock);
  --map-canopy: var(--n-canopy); --map-forest: var(--n-forest); --map-scrub: var(--n-scrub);
  --map-frond: var(--n-frond); --map-frond-lit: var(--n-frond-lit);
  --map-trunk-lit: var(--n-trunk-lit); --map-trunk-deep: var(--n-trunk-deep);
  --map-stone: var(--n-stone); --map-stone-deep: var(--n-stone-deep);
  --glow-land: var(--lit-biolume); --glow-rock: var(--lit-moon);
  --glow-living: var(--lit-living); --glow-ember: var(--lit-ember);
}
.li-island-map-container [data-lit] { transition: filter .7s ease, opacity .7s ease; }
.li-island-map-container.li-night [data-lit="land"] { stroke: var(--lit-biolume); stroke-width: .8; stroke-opacity: .8; }
.li-island-map-container.li-night [data-lit="rock"] { stroke: var(--lit-moon); stroke-width: .55; stroke-opacity: .85; }
.li-island-map-container.li-night [data-lit="living"] { stroke: var(--lit-living); stroke-width: .7; stroke-opacity: .9; }
.li-island-map-container.li-night [data-lit="biolume"] { stroke: var(--lit-biolume); opacity: 1; }
.li-island-map-container.li-night [data-lit="ember"] { stroke: var(--lit-ember); stroke-width: .7; stroke-opacity: .95; }
.li-island-map-container [data-lit="moon"] { opacity: 0; }
.li-island-map-container.li-night [data-lit="moon"] { opacity: 1; }
.li-island-map-container.li-night [data-lit="moon"] path { fill-opacity: .5; stroke: var(--lit-moon); stroke-width: 1.5; stroke-opacity: 1; }
.li-island-map-container.li-night [data-lit="sun"] { opacity: 0; }
.li-island-map-container.li-night [data-lit="cloud"] { opacity: .07; }

/* the real cyclone (DISASTER_FX.cyclone above), copied under `.li-` classes
   for the same querySelector reason — this uses the same two art files. */
.li-fx { position: absolute; pointer-events: none; z-index: 7; will-change: transform, opacity; }
.li-fx-dim { inset: 0; background: var(--abyss-sink); opacity: 0; animation: li-dim 5.5s ease-in-out forwards; }
@keyframes li-dim { 0%, 100% { opacity: 0; } 14%, 78% { opacity: .7; } }
.li-fx-stage { inset: 0; overflow: hidden; isolation: isolate; transform: translateZ(0); transform-style: flat; }
.li-fx-outer {
  top: 50%; left: 0; width: 138px; height: 182px; margin-top: -132px; margin-left: -69px;
  background: url("cyclone-outer.svg") center / contain no-repeat; animation: li-cyclone-outer 5.5s ease-in-out forwards;
}
.li-fx-core {
  top: 50%; left: 0; width: 112px; height: 164px; margin-top: -122px; margin-left: -56px;
  background: url("cyclone.svg") center / contain no-repeat; animation: li-cyclone-core 5.5s ease-in-out forwards;
}
@keyframes li-cyclone-outer {
  0% { transform: translate3d(-110%, 4%, 0) scale(.62); opacity: 0; }
  18% { opacity: .42; } 72% { opacity: .42; }
  100% { transform: translate3d(210%, -2%, 0) scale(1.24); opacity: 0; }
}
@keyframes li-cyclone-core {
  0% { transform: translate3d(-130%, 6%, 0) scale(.5) skewX(6deg); opacity: 0; }
  16% { opacity: 1; }
  50% { transform: translate3d(80%, 0, 0) scale(1.2) skewX(-5deg); }
  76% { opacity: 1; }
  100% { transform: translate3d(300%, -4%, 0) scale(1) skewX(5deg); opacity: 0; }
}
.li-fx-flash { inset: 0; background: var(--sand-pale); opacity: 0; animation: li-flash 5.5s linear forwards; }
@keyframes li-flash {
  0%, 26%, 30%, 44%, 48%, 61%, 65%, 100% { opacity: 0; } 28% { opacity: .55; } 46% { opacity: .4; } 63% { opacity: .5; }
}
.li-island-map-container.li-shake { animation: li-shake 5.5s ease-in-out forwards; }
@keyframes li-shake {
  0%, 100% { transform: translate3d(0, 0, 0); } 22% { transform: translate3d(-1px, 1px, 0); }
  30% { transform: translate3d(2px, -1px, 0); } 38% { transform: translate3d(-3px, 2px, 0); }
  46% { transform: translate3d(3px, 2px, 0); } 54% { transform: translate3d(-3px, -2px, 0); }
  62% { transform: translate3d(3px, -1px, 0); } 70% { transform: translate3d(-2px, 1px, 0); } 80% { transform: translate3d(1px, -1px, 0); }
}

/* a brief, deliberately unlabelled flash of a few real locked drawings —
   Ben's call, overriding "don't spoil discoveries" for this one reel only
   (design/DECISIONS.md, Launch). */
.li-scene-mystery { background: var(--abyss-deep); overflow: hidden; }
.li-fog {
  position: absolute; inset: -10%;
  background: radial-gradient(ellipse 40% 30% at 30% 70%, var(--abyss-sink), transparent 70%),
              radial-gradient(ellipse 35% 25% at 75% 40%, var(--abyss-sink), transparent 70%);
  animation: li-drift 9s ease-in-out infinite alternate;
}
@keyframes li-drift { from { transform: translateX(-2%); } to { transform: translateX(2%); } }
.li-litem {
  position: absolute; width: 17%; aspect-ratio: 1; border-radius: 50%; background: var(--abyss-lift);
  border: 1px solid var(--abyss-edge); opacity: 0; transform: scale(.6); animation: li-lockflash 3.4s ease-in-out infinite;
}
.li-litem .a-ico, .li-litem .a-ico::after {
  content: ""; position: absolute; inset: 20%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center; -webkit-mask-size: contain; mask-size: contain;
}
.li-litem .a-ico { background: var(--hi); }
.li-litem .a-ico::after { background: var(--lo); }
.li-litem.li-sail {
  left: 20%; top: 28%; animation-delay: 0s; --hi: var(--mat-thread); --lo: var(--reef-lift);
}
.li-litem.li-sail .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M22%205v23H8Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M22%205v23H8Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E"); }
.li-litem.li-sail .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M26%201v27h16Z%20M0%2033h48c-4%209-13%2014-24%2014S4%2042%200%2033Z%22%2F%3E%3C%2Fsvg%3E"); }
.li-litem.li-radio {
  left: 42%; top: 52%; animation-delay: .85s; --hi: var(--mat-wood); --lo: var(--mat-metal);
}
.li-litem.li-radio .a-ico { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M2%2018h40v28H2Z%20M31%2014%2046%200l4%204-13%2014Z%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M2%2018h40v28H2Z%20M31%2014%2046%200l4%204-13%2014Z%22%2F%3E%3C%2Fsvg%3E"); }
.li-litem.li-radio .a-ico::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M16%2023a9%209%200%201%200%200%2018%209%209%200%201%200%200-18Z%20M32%2023a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%20M32%2034a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%22%2F%3E%3C%2Fsvg%3E"); mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2048%2048%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M16%2023a9%209%200%201%200%200%2018%209%209%200%201%200%200-18Z%20M32%2023a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%20M32%2034a4%204%200%201%200%200%208%204%204%200%201%200%200-8Z%22%2F%3E%3C%2Fsvg%3E"); }
.li-litem.li-crystal { left: 66%; top: 26%; animation-delay: 1.7s; }
.li-litem.li-crystal svg { position: absolute; inset: 18%; width: 64%; height: 64%; }
.li-lock {
  position: absolute; right: -4%; bottom: -4%; width: 38%; aspect-ratio: 1;
  background: var(--abyss-sink); border: 1.5px solid var(--dune); border-radius: 4px;
}
.li-lock::before {
  content: ""; position: absolute; left: 50%; top: -38%; width: 56%; height: 70%;
  border: 2px solid var(--dune); border-bottom: none; border-radius: 50% 50% 0 0; transform: translateX(-50%);
}
@keyframes li-lockflash {
  0%, 8% { opacity: 0; transform: scale(.6); } 12% { opacity: 1; transform: scale(1.08); }
  18% { opacity: 1; transform: scale(1); } 26%, 100% { opacity: 0; transform: scale(.85); }
}
.li-glint {
  position: absolute; width: 5px; height: 5px; border-radius: 50%; background: var(--sand-pale);
  animation: li-twinkle 2.2s ease-in-out infinite;
}
@keyframes li-twinkle {
  0%, 100% { opacity: .15; transform: scale(.7); } 50% { opacity: 1; transform: scale(1.2); }
}

.li-scene-cta {
  background: var(--abyss-sink); display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 16px; text-align: center; padding: 8%;
}
.li-cta-eyebrow { margin: 0; font-size: .65rem; letter-spacing: .16em; text-transform: uppercase; color: var(--reef-pale); }
.li-scene-cta h2 { margin: 0; color: var(--sand-pale); font-size: 1.35rem; line-height: 1.3; }
.li-cta-buttons { display: flex; gap: 10px; width: 100%; max-width: 200px; }
.li-cta-buttons button {
  flex: 1; border-radius: 9px; padding: 11px 8px; font-size: .85rem; font-weight: 700;
  border: 1px solid transparent; cursor: pointer;
}
.li-cta-yes { background: var(--dune); color: var(--abyss-sink); }
/* Captions — the reel's script, one line at a time. Across the top of the
   frame, because the bottom is where the demo turn's stones sit. */
.li-caption {
  position: absolute; left: 50%; top: 5%; z-index: 10; max-width: 88%;
  transform: translate(-50%, -4px); opacity: 0; pointer-events: none;
  padding: 7px 14px; border-radius: 18px; text-align: center; text-wrap: balance;
  font-size: clamp(.8rem, 2.4vw, 1.02rem); line-height: 1.3; font-weight: 600;
  color: var(--sand-pale);
  background: var(--abyss-sink);
  background: color-mix(in srgb, var(--abyss-sink) 78%, transparent);
  transition: opacity .45s ease, transform .45s ease;
}
.li-caption.li-show { opacity: 1; transform: translate(-50%, 0); }

/* ?script — the caption table beside the reel, for review only */
#screen-launch-intro.li-with-script.active { flex-direction: row; flex-wrap: wrap; gap: 24px; align-items: flex-start; }
#screen-launch-intro.li-with-script .li-frame { flex: 1 1 520px; }
.li-vo {
  flex: 1 1 320px; max-width: 460px; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px; color: var(--text);
}
.li-vo h3 { margin: 0 0 2px; font-size: 1rem; }
.li-vo-clock { color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums; margin-bottom: 10px; }
.li-vo-row {
  display: grid; grid-template-columns: 92px 1fr; gap: 10px; padding: 8px 6px;
  border-top: 1px solid var(--border); border-radius: 6px;
}
.li-vo-t { color: var(--muted); font-size: .76rem; font-variant-numeric: tabular-nums; }
.li-vo-t span { display: block; }
.li-vo-line { font-size: .95rem; line-height: 1.35; }
.li-vo-row.li-vo-now { background: var(--card2); }
.li-vo-row.li-vo-now .li-vo-line { color: var(--gold); }
.li-vo .btn { margin-top: 12px; }

@media (prefers-reduced-motion: reduce) {
  .li-token.li-walking .r-legL, .li-token.li-walking .r-legR,
  .li-walker.li-walking .r-legL, .li-walker.li-walking .r-legR,
  .li-fog { animation: none; }
}
