/* ---- type ----------------------------------------------------------------
 * Poppins was the tell. It is the default geometric sans on half the internet,
 * it carries no craft association at all, and on a game about a Japanese
 * repair tradition it reads as a template someone dropped a theme onto.
 *
 * Shippori Mincho is a modern MINCHO — the Japanese serif class, drawn here
 * with the slightly inked, letterpress-on-paper weight that suits a studio
 * full of fired clay. It sets the display type only.
 *
 * Zen Kaku Gothic New carries everything small. A mincho at 10-13px on a dark
 * ground goes thin and grey, and this UI is full of 10.5px captions, so the
 * serif is kept off them deliberately rather than applied everywhere and
 * apologised for.
 *
 * Self-hosted, Latin subset only: the full Japanese cuts run to megabytes, and
 * this game ships no Japanese text. 85KB for five faces, no external request,
 * no font-blocking third party. font-display: swap so the game never waits.
 * ------------------------------------------------------------------------ */
@font-face {
  font-family: 'Shippori Mincho'; font-style: normal; font-weight: 500;
  font-display: swap; src: url('./fonts/shippori-mincho-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Shippori Mincho'; font-style: normal; font-weight: 700;
  font-display: swap; src: url('./fonts/shippori-mincho-700.woff2') format('woff2');
}
@font-face {
  font-family: 'Zen Kaku Gothic New'; font-style: normal; font-weight: 400;
  font-display: swap; src: url('./fonts/zenkaku-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Zen Kaku Gothic New'; font-style: normal; font-weight: 500;
  font-display: swap; src: url('./fonts/zenkaku-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Zen Kaku Gothic New'; font-style: normal; font-weight: 700;
  font-display: swap; src: url('./fonts/zenkaku-700.woff2') format('woff2');
}

/* ============================================================================
 * GOLDMEND — studio UI.
 * Palette is a dark warm studio so the gold reads as light, not as yellow.
 * Every surface token is declared WITH its on-colour; nothing picks a text
 * colour on its own.
 * ========================================================================== */
#gm-stage {
  --gm-bg:        #0b0806;   --gm-on-bg:      #f3e7d6;
  --gm-surface:   #1b1410;   --gm-on-surface: #eaddc9;
  --gm-raised:    #2a201a;   --gm-on-raised:  #f5ead9;
  --gm-gold:      #e8c069;   --gm-on-gold:    #2a1c06;
  --gm-clay:      #a9714a;   --gm-on-clay:    #1a0f08;
  --gm-warn:      #e2734a;   --gm-on-warn:    #1a0b05;
  --gm-cool:      #6f9c86;   --gm-ink:        #cbb79a;
  --gm-line:      rgba(233, 206, 158, 0.16);

  position: relative;
  width: 100%;
  aspect-ratio: 9 / 16;
  max-height: 78vh;
  min-height: 380px;
  overflow: hidden;
  border-radius: 18px;
  background: var(--gm-bg);
  color: var(--gm-on-bg);
  font-family: 'Zen Kaku Gothic New', 'Poppins', system-ui, -apple-system, sans-serif;
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
}
/* in the app the stage is full-bleed — no card, no rounding, no cap */
html.tz-app-mode #gm-stage {
  border-radius: 0;
  max-height: none;
  min-height: 0;
  height: 100%;
  aspect-ratio: auto;
}

#gmCanvas { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }

/* ---------- HUD ---------------------------------------------------------- */
#gmHud { transition: opacity .25s ease;
  position: absolute; top: 0; left: 0; right: 0; z-index: 4;
  display: flex; align-items: flex-start; justify-content: space-between; gap: 8px;
  pointer-events: none;
  padding: calc(10px + var(--tz-safe-top, 0px)) calc(12px + var(--tz-safe-right, 0px))
           0 calc(12px + var(--tz-safe-left, 0px));
}
.gm-pills { display: flex; gap: 6px; flex-wrap: wrap; }
.gm-pill {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--gm-surface); color: var(--gm-on-surface);
  border: 1px solid var(--gm-line); border-radius: 999px;
  /* 8px, not 5px. Something in the HUD stacking sits just under the gold pill
     and swallowed the bottom of its hit-slop element, so the slop alone could
     not be trusted for the 44pt floor. Taller pills fix it honestly and both
     pills grow together, so the row stays consistent. */
  padding: 8px 11px; font-size: 12px; font-weight: 600;
  white-space: nowrap;
}
.gm-pill b { color: var(--gm-on-raised); font-weight: 700; font-variant-numeric: tabular-nums; }

/* The gold pill as a shop door. Strips every button affordance until there is
   actually a store to open — a control that looks tappable and is not is its
   own bug, and the web build has no store at all.
   NOTE the !important: shared/css/tools.css puts `min-height: 44px !important`
   on every <button> under 768px. Honouring it here would make the HUD pill
   44px tall and push the brief down onto the shelf. So the visual height stays
   as it was and the TAP AREA is grown instead — 9px of extra padding takes the
   touch box past 44, and -9px of margin hands the space straight back to the
   layout, so nothing in the HUD moves. */
.gm-pill-btn {
  appearance: none; -webkit-appearance: none;
  font: inherit; text-align: left;
  pointer-events: auto;
  cursor: default;
  min-height: 0 !important;
  /* SAME padding as .gm-pill. A previous pass grew the padding to 14px and
     pulled it back with -9px margin, which fixes the LAYOUT but not the paint:
     the pill's own background and border still fill the padded box, so the
     gold pill rendered 45px tall beside a 27px coins pill. The hit area is
     extended by the invisible ::before below instead, which paints nothing. */
  padding: 8px 11px;   /* must track .gm-pill above, or the two pills differ */
  position: relative;
}
/* The 44pt touch box: a REAL element, not a ::before. Verified by clicking 7px
   below the painted pill — a pseudo-element there is not hit-tested inside a
   pointer-events:none HUD, so the earlier version looked correct in the box
   model and was not actually tappable. This one is. */
.gm-hit {
  position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: 44px; pointer-events: auto;
}
.gm-pill-plus { display: none; }
/* only once RevenueCat has actually answered */
.gm-pill-btn.buyable { cursor: pointer; border-color: rgba(232, 192, 105, .42); }
/* A round filled chip, not a bare "+" character. A plus sitting loose at the
   end of a label reads as punctuation; the same glyph inside a filled circle
   reads as a control, which is the whole point of putting it there. */
.gm-pill-btn.buyable .gm-pill-plus {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; margin-left: 2px; flex: none;
  border-radius: 999px;
  background: var(--gm-gold); color: var(--gm-on-gold);
  font-style: normal; font-weight: 700; font-size: 12px; line-height: 1;
  padding-bottom: 1px;   /* optical centring — "+" sits high in the em box */
}
.gm-pill-btn.buyable:active { filter: brightness(1.25); transform: scale(.97); }

/* A ONE-SHOT nudge, never a loop. The pill is the only route to the shop from
   inside a game, so it has to be findable once — but a control that pulses
   forever is a nag, and the benchmark data has that class of pressure absent
   from paywalls that actually convert. Two cycles, then it stops for good.
   Removed entirely under reduced motion; the pill is still there and still
   works, it just does not announce itself. */
@keyframes gm-pill-nudge {
  0%, 100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(232, 192, 105, 0); }
  40%      { transform: scale(1.06); box-shadow: 0 0 0 5px rgba(232, 192, 105, .22); }
}
.gm-pill-btn.nudge { animation: gm-pill-nudge 900ms ease-out 2; }
@media (prefers-reduced-motion: reduce) {
  .gm-pill-btn.nudge { animation: none; }
}
.gm-unit { color: var(--gm-ink); font-weight: 500; font-size: 11px; }
/* SVG icons inherit the pill's colour, sit on the metric centre (emoji do not),
   and render identically on every OS */
.gm-ic { width: 15px; height: 15px; display: block; flex: none; color: var(--gm-gold); }
.gm-wave, .gm-slash { transition: opacity .15s ease; }

/* the mute control is a pill too, but it must be tappable while the rest of
   the HUD stays pointer-transparent */
.gm-mute {
  pointer-events: auto; cursor: pointer;
  padding: 7px 9px; border: 1px solid var(--gm-line);
}
.gm-mute:active { filter: brightness(1.25); }

/* High-scores pill. Same shape and weight as the mute control on purpose: it is
   a peer, not a feature being advertised. It shares .gm-mute's need to be
   tappable inside an otherwise pointer-transparent HUD. */
.gm-board {
  pointer-events: auto; cursor: pointer;
  padding: 7px 9px; border: 1px solid var(--gm-line);
}
.gm-board:active { filter: brightness(1.25); }
.gm-board[hidden] { display: none; }

/* The kit's floating cup never renders — the placeholder that claims its id is
   hidden. Belt and braces in case a future kit version appends a second node
   rather than checking the id first. */
.tz-leaderboard-btn { display: none !important; }

/* The brief sits over the room, so it needs its own ground — on a tall phone
   the pot's rim reached this text and the two overlapped illegibly. A scrim
   plus a hard clamp keeps it readable without boxing it in. */
.gm-commission {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(50px + var(--tz-safe-top, 0px));
  z-index: 4; width: min(92%, 360px); text-align: center; pointer-events: none;
  /* Generous vertical padding is load-bearing, not decoration: it is the room
     the scrim needs to reach zero before the box ends. Tighten it and the hard
     edge comes straight back. */
  /* Measured 2026-08-30. The room plate is 2:3 and the stage is 9:16, so cover
     fit has ZERO vertical slack: the painted plank lands at exactly 0.238 x H
     on every device, 195px on a 420x820 phone. The shelf pieces are ~43px tall
     and grow UPWARD from it, so they occupy 152-195 and the brief has to end
     above 152 for the row to be clear. During a throw the brief also carries
     the risk meter and cannot; that phase keeps the overlap and is why the
     shelf row is dropped there. At reveal and sold the risk meter is hidden,
     and THEN this padding is what decides whether the row fits. Tightened from
     16/15 for exactly that: it buys the 16px that clears the pieces on the two
     screens where a player actually looks at their own collection. */
  padding: 8px 12px 7px;
  /* `closest-side`, NOT the default.
     A bare `radial-gradient(ellipse at center, ...)` sizes to `farthest-corner`.
     On a wide, short banner the corners sit far away horizontally, so the 100%
     stop falls well outside the box vertically — the fade never reaches zero
     before the TOP and BOTTOM edges and gets clipped flat. That straight cut
     across the artwork was the visible edge.
     `closest-side` puts the ellipse exactly on all four edges, so alpha hits 0
     at every edge midpoint and the scrim dissolves into the room instead of
     ending. A border-radius is then pointless — there is no longer an edge to
     round. */
  background: radial-gradient(ellipse closest-side at center,
    rgba(10,7,5,.84) 0%,
    rgba(10,7,5,.66) 42%,
    rgba(10,7,5,.34) 72%,
    rgba(10,7,5,.10) 89%,
    rgba(10,7,5,0) 100%);
}
#gmCommission {
  font-size: 13px; font-weight: 600; color: var(--gm-on-bg);
  line-height: 1.3;
  text-shadow: 0 1px 3px rgba(0,0,0,.9);
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
#gmPatronLine {
  font-size: 11.5px; color: var(--gm-ink); font-style: italic; margin-top: 3px;
  text-shadow: 0 1px 3px rgba(0,0,0,.9);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------- risk meter ---------------------------------------------------
 * Sits inside .gm-commission now, so it is laid out by the brief instead of
 * being pinned a fixed distance off the bottom. The old absolute position was
 * measured in pixels while the wheel is placed at a fraction of the height, so
 * the two collided the moment either changed — and a phone's safe-area inset
 * pushed the meter up into the wheel every time.
 * -------------------------------------------------------------------------- */
.gm-risk { margin-top: 7px; }
.gm-risk[hidden] { display: none; }
/* Two ticks at the 30% and 60% marks — the exact points where the label
   changes from low to some to high. Without them the track is a featureless
   line and the fill has nothing to be measured against, so a player cannot
   see how close they are to the next band. */
.gm-risk-track {
  height: 6px; border-radius: 999px; overflow: hidden;
  background:
    linear-gradient(90deg, transparent 0 calc(30% - 1px),
      rgba(233, 206, 158, .30) calc(30% - 1px) 30%,
      transparent 30% calc(60% - 1px),
      rgba(233, 206, 158, .30) calc(60% - 1px) 60%,
      transparent 60%),
    rgba(12, 8, 6, .72);
  border: 1px solid var(--gm-line);
}
/* `display: block` is the whole bug. This is an <i>, which is inline by
   default, and width/height do not apply to an inline box — so the element
   measured 0x0 and the risk meter has been an empty track since it shipped.
   The JS was correct the entire time: it set width to 34%, 90%, and swapped
   cool/warm/hot classes that nothing could ever paint. Its sibling
   `.gm-prog > i` (kiln and mend) already carries display:block, which is why
   those two bars always worked and this one never did. */
.gm-risk-fill {
  display: block;
  height: 100%; width: 0;
  transition: width .18s linear, background .3s;
}
.gm-risk-fill.cool { background: var(--gm-cool); }
.gm-risk-fill.warm { background: var(--gm-gold); }
.gm-risk-fill.hot  { background: var(--gm-warn); }
.gm-risk-row {
  display: flex; justify-content: space-between; gap: 8px; margin-top: 4px;
  font-size: 10.5px; color: var(--gm-ink);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
}
.gm-risk-row span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gm-risk-row b { color: var(--gm-on-bg); }

/* ---------- bottom panels ------------------------------------------------ */
.gm-bar {
  position: absolute; left: 0; right: 0;
  bottom: calc(14px + var(--tz-safe-bottom, 0px));
  z-index: 5;
  padding: 0 calc(14px + var(--tz-safe-left, 0px)) 0 calc(14px + var(--tz-safe-right, 0px));
  display: flex; flex-direction: column; align-items: center; gap: 9px;
}
.gm-bar[hidden] { display: none; }

.gm-btn {
  appearance: none; border: 0; cursor: pointer;
  width: min(100%, 320px);
  padding: 14px 18px; border-radius: 14px;
  font-family: inherit; font-size: 15px; font-weight: 700;
  background: var(--gm-gold); color: var(--gm-on-gold);
  transition: transform .12s ease, filter .12s ease;
}
.gm-btn:active { transform: scale(.975); filter: brightness(.94); }
.gm-btn.ghost {
  background: var(--gm-raised); color: var(--gm-on-raised);
  border: 1px solid var(--gm-line); font-weight: 600; font-size: 14px; padding: 11px 16px;
}
/* Reward actions get their own colour so an opt-in ad never looks like the
   primary path — same hierarchy Brimful uses for "+1 Jar". */
/* The reward button was the only text in the game below WCAG AA: #fff6ec on
   the old #d9762f→#b85a1e ramp measured 3.49:1 against the rendered pixels,
   where 15px non-bold needs 4.5. Darkening the ramp fixes it at every point of
   the gradient, not just on average — the LIGHTEST stop now clears at 4.51:1
   and the darkest sits at 6.91. Chosen over lightening the text because white
   on a warm mid-orange is exactly the pairing that fails, and over a dark ink
   because this button has to stay visibly distinct from the gold primary. */
.gm-btn.reward {
  background: linear-gradient(180deg, #b8551c, #8f3d12);
  color: #fff6ec;
  border: 0;
  box-shadow: 0 6px 18px rgba(170, 76, 22, .30);
}
.gm-btn[hidden] { display: none; }

/* primary button gets a lit top edge + drop — flat fills read as unfinished */
.gm-btn:not(.ghost):not(.reward) {
  background: linear-gradient(180deg, #f0cd7d, #d3a44b);
  box-shadow: 0 6px 18px rgba(180, 130, 40, .22), inset 0 1px 0 rgba(255, 248, 226, .55);
}

/* Throw bar: one row. The glaze chip is subordinate to the one primary CTA and
   sized to its own label, so firing keeps the width and the pot keeps the row
   the second full-width button used to take. */
.gm-bar-row {
  width: min(100%, 320px);
  display: flex; align-items: stretch; gap: 8px;
}
.gm-bar-row .gm-btn { width: auto; }
.gm-bar-row .gm-btn:not(.ghost) { flex: 1 1 auto; }   /* primary takes the rest */
.gm-glaze-chip {
  flex: 0 1 auto; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 11px 14px; font-size: 13px;
}
.gm-glaze-chip span {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.gm-hint { font-size: 12px; color: var(--gm-ink); text-align: center; line-height: 1.45; }

/* progress bars (kiln + mend) */
.gm-prog {
  width: min(100%, 320px); height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--gm-surface); border: 1px solid var(--gm-line);
}
.gm-prog > i { display: block; height: 100%; width: 0; background: var(--gm-gold); transition: width .2s linear; }
.gm-prog.mend > i { background: linear-gradient(90deg, #9a6b1f, #f6d585); }

/* ---------- cards (reveal / sold / menu) ---------------------------------
 * Only the MENU sits centred. The reveal and sold cards anchor to the bottom
 * so the mended pot stays visible behind them — that pot with gold running
 * through it is the whole point of the moment, and a centred modal covers
 * exactly the thing the player just made.
 * ----------------------------------------------------------------------- */
.gm-card {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 6; width: min(92%, 390px);
  background: var(--gm-surface); color: var(--gm-on-surface);
  border: 1px solid var(--gm-line); border-radius: 18px;
  padding: 24px 22px 20px; text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, .55);
}
.gm-card[hidden] { display: none; }
/* Display type only, and only at 17px and up. Below that a mincho loses its
   strokes on a dark ground; everything smaller stays in the sans. */
.gm-card h2, .gm-sel-head h2, #gmRevealTitle, #gmFailTitle, .gm-value {
  font-family: 'Shippori Mincho', Georgia, 'Times New Roman', serif;
  letter-spacing: .005em;
}
.gm-card h2 { margin: 0 0 8px; font-size: 19px; font-weight: 700; color: var(--gm-on-surface); }
.gm-card p  { margin: 0 0 16px; font-size: 13px; line-height: 1.5; color: var(--gm-ink); }
.gm-value   { font-size: 34px; font-weight: 800; color: var(--gm-gold); line-height: 1; margin-bottom: 4px; }
.gm-value small { font-size: 14px; font-weight: 600; color: var(--gm-ink); }
.gm-seams   { font-size: 12.5px; color: var(--gm-on-surface); margin-bottom: 16px; }
.gm-card .gm-btn { width: 100%; }
.gm-card .gm-btn + .gm-btn { margin-top: 8px; }

/* bottom-anchored variant — keeps the pot in frame */
.gm-card.gm-bottom {
  top: auto; bottom: calc(14px + var(--tz-safe-bottom, 0px));
  transform: translateX(-50%);
  width: min(92%, 360px);
  padding: 18px 18px 20px;
  background: rgba(27, 20, 16, .94);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.gm-card.gm-bottom h2 { font-size: 17px; margin-bottom: 5px; }
.gm-card.gm-bottom p { font-size: 12.5px; margin-bottom: 12px; }

/* ---------- menu: wordmark, stats, footer -------------------------------- */
.gm-menu { width: min(88%, 344px); padding-top: 26px; }
.gm-mark {
  display: flex; align-items: center; justify-content: center; gap: 1px;
  font-weight: 800; font-size: 30px; letter-spacing: .04em;
  color: var(--gm-on-surface); line-height: 1;
}
.gm-mark-o { width: 30px; height: 30px; color: var(--gm-on-surface); margin: 0 -1px; }
.gm-tagline {
  margin-top: 7px; font-size: 10.5px; font-weight: 600;
  letter-spacing: .22em; text-transform: uppercase; color: var(--gm-gold);
}
.gm-menu p { margin-top: 14px; }

/* 2x2, not a single row of four.
   This was `display:flex` with `flex:1`, which was right while there were
   three stats. The wheel shed added a fourth (forms) and nobody re-measured:
   the menu card is ~330px wide at EVERY viewport, so four tiles are 51px each,
   ~43px of content. "1/5" is 26px of glyphs but the browser is allowed to
   break after the slash, and it did -- glazes and forms both rendered on two
   lines at 360, 448 and 540px. Measured on the live build before this change.

   nowrap alone would have stopped the break and then let a long number spill
   instead: `best` is a coin total with no cap, and a five-digit best already
   exceeds 43px. Two columns give ~155px per tile, which no realistic value
   reaches, so the fix holds instead of moving the failure one digit along. */
.gm-stats {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px; margin: 0 0 18px;
}
.gm-stat {
  background: var(--gm-raised); border: 1px solid var(--gm-line);
  border-radius: 11px; padding: 9px 4px;
}
.gm-stat b {
  display: block; font-size: 17px; font-weight: 700; color: var(--gm-gold);
  font-variant-numeric: tabular-nums; line-height: 1.15;
  /* belt and braces: the slash is the break opportunity that started this */
  white-space: nowrap;
}
.gm-stat span {
  font-size: 9.5px; color: var(--gm-ink); letter-spacing: .06em;
  text-transform: uppercase; white-space: nowrap;   /* never wrap — uneven tiles read as broken */
}

.gm-foot { margin-top: 13px; font-size: 9.5px; color: var(--gm-ink); opacity: .75; letter-spacing: .06em; }

/* slow mode — the calm switch, deliberately plain and always reachable */
/* Two columns: the control, then everything it is about. Left-aligned because
   a two-line caption centred under a checkbox reads as unrelated body copy. */
.gm-slow {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 10px; row-gap: 3px;
  align-items: start;
  margin-top: 18px; padding: 10px 2px;
  text-align: left; color: var(--gm-ink); cursor: pointer;
  border-top: 1px solid var(--gm-line);
}
.gm-slow-name {
  grid-column: 2; font-size: 12.5px; font-weight: 600; line-height: 1.35;
  color: var(--gm-on-surface);
}
.gm-slow-cost {
  grid-column: 2; font-size: 11px; line-height: 1.4; color: var(--gm-ink);
  opacity: .82;
}
/* Custom checkbox — the native control renders as a stark white box on dark
   and is one of the loudest "unfinished" tells in a themed game. */
.gm-slow input {
  appearance: none; -webkit-appearance: none;
  grid-column: 1; grid-row: 1;
  width: 18px; height: 18px; flex: none; margin: 1px 0 0;
  border: 1.5px solid var(--gm-line); border-radius: 5px;
  background: var(--gm-raised); cursor: pointer;
  position: relative; transition: background .15s, border-color .15s;
}
.gm-slow input:checked { background: var(--gm-gold); border-color: var(--gm-gold); }
.gm-slow input:checked::after {
  content: ''; position: absolute; left: 5px; top: 1.5px;
  width: 4px; height: 9px;
  border: solid var(--gm-on-gold); border-width: 0 2px 2px 0;
  transform: rotate(42deg);
}
.gm-slow input:focus-visible { outline: 2px solid var(--gm-gold); outline-offset: 2px; }

/* ---------- small screens ------------------------------------------------ */
@media (max-width: 600px) {
  .gm-card { width: min(90%, 320px); padding: 20px 18px; }
  .gm-card h2 { font-size: 18px; }
  .gm-btn { padding: 13px 16px; font-size: 14.5px; }
  .gm-pill { font-size: 11.5px; padding: 4px 9px; }
}
/* Small phones: tighten the throw row rather than let it degrade. Truncating a
   glaze name to "Wood A…" hides the thing the chip exists to show, and wrapping
   the primary CTA onto two lines makes it read as a paragraph. */
@media (max-width: 360px) {
  .gm-bar-row { gap: 6px; }
  .gm-glaze-chip { padding: 11px 10px; font-size: 12px; gap: 5px; }
  .gm-bar-row .gm-btn:not(.ghost) { padding: 13px 10px; font-size: 14px; }
}
/* was: a second hand-tuned bottom offset for short screens. The meter is no
   longer bottom-anchored, so there is nothing left to hand-tune. */
#gm-stage.gm-short .gm-risk { margin-top: 5px; }
#gm-stage.gm-short .gm-card { padding: 16px 15px; }
#gm-stage.gm-short .gm-value { font-size: 28px; }

@media (prefers-reduced-motion: reduce) {
  .gm-btn, .gm-risk-fill, .gm-prog > i { transition: none; }
}

/* ---------- sold card: show the maths ------------------------------------
 * The core claim of the game is that a repaired pot beats a flawless one.
 * A single total hides that; the breakdown proves it every round.
 * ----------------------------------------------------------------------- */
.gm-sold-head { margin-bottom: 2px; }
.gm-sold-label { font-size: 11.5px; color: var(--gm-ink); letter-spacing: .02em; }
.gm-sold-label b { color: var(--gm-on-surface); font-weight: 600; }

.gm-breakdown {
  margin: 10px 0 10px; padding: 9px 12px;
  background: var(--gm-raised); border: 1px solid var(--gm-line); border-radius: 11px;
  display: flex; flex-direction: column; gap: 5px;
}
.gm-brow {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 12px; color: var(--gm-ink);
}
.gm-brow b { color: var(--gm-on-surface); font-weight: 700; font-variant-numeric: tabular-nums; }
.gm-brow-gold span, .gm-brow-gold b { color: var(--gm-gold); }
.gm-brow-gold b { text-shadow: 0 0 12px rgba(232, 192, 105, .45); }
.gm-brow[hidden] { display: none; }

/* ---------- commission select -------------------------------------------
 * The structure the game was missing. Chapters, locks, stars — the same
 * shape voltline uses, because it works: the player can see what they have
 * mastered and what is still ahead.
 * ----------------------------------------------------------------------- */
.gm-select { width: min(92%, 356px); max-height: 84%; display: flex; flex-direction: column; padding: 18px 16px 16px; }
.gm-sel-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.gm-sel-head h2 { margin: 0; font-size: 17px; }
.gm-sel-stars { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--gm-ink); }
.gm-sel-stars b { color: var(--gm-gold); font-weight: 700; }
.gm-ic-star { width: 13px; height: 13px; color: var(--gm-gold); }

.gm-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px;
  overflow-y: auto; padding-right: 2px; margin-bottom: 12px;
  -webkit-overflow-scrolling: touch;
}
/* A chapter heading that only names the chapter tells the player nothing they
   cannot already see, so it carries the price of the door or how much of the
   chapter is still on the table. */
.gm-chapter {
  grid-column: 1 / -1; margin: 10px 0 1px;
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .18em;
  text-transform: uppercase; color: var(--gm-gold); opacity: .85;
}
.gm-chapter:first-child { margin-top: 0; }
.gm-chapter.shut { color: var(--gm-ink); opacity: .7; }
.gm-ch-prog {
  display: inline-flex; align-items: center; gap: 3px; flex: none;
  letter-spacing: .04em; font-size: 9.5px; color: var(--gm-ink);
}
.gm-chapter:not(.shut) .gm-ch-prog { color: var(--gm-gold); }

/* ---------- the studio shop ---------------------------------------------
 * Each row states what it is and what it costs, in that order. The price comes
 * from the store already localised — never written into the markup.
 * ----------------------------------------------------------------------- */
.gm-shop { width: min(92%, 360px); max-height: 86%; display: flex; flex-direction: column; padding: 18px 16px 16px; }
.gm-shop-note { font-size: 11.5px; color: var(--gm-ink); margin: 0 0 10px; line-height: 1.45; }
.gm-shop-list { display: flex; flex-direction: column; gap: 8px; overflow-y: auto; margin-bottom: 12px; }
/* Every product here sells a VISUAL outcome — gold seams, metal seams, a shelf
   that keeps your work — and the row used to describe all three in prose. The
   art column is a live canvas drawn by the same renderer that draws the piece
   after the purchase, so what is promised cannot drift from what is delivered. */
.gm-shop-row {
  appearance: none; cursor: pointer; text-align: left; font-family: inherit;
  background: var(--gm-raised); color: var(--gm-on-raised);
  border: 1px solid var(--gm-line); border-radius: 12px;
  padding: 11px 12px; display: grid; gap: 3px 11px;
  grid-template-columns: 68px 1fr auto;
  grid-template-areas: 'art name price' 'art body body' 'art note note' 'art reason reason';
  align-items: start;
  transition: transform .12s ease, border-color .12s ease;
}
/* No frame. The row is already inside a bordered card inside a bordered panel
   inside the studio's painted frame — a fourth box around a 26px pot is one
   nesting level past anywhere the eye can rest, and it boxed the art in tight
   enough to clip the tallest piece. The pieces sit directly on the row now. */
.gm-shop-art { grid-area: art; align-self: center; justify-self: center; }
/* Under 360px the art and three text columns fight and the body wraps to a
   ribbon. Drop the art to its own full-width row rather than shrinking it to
   an unreadable smudge. */
@media (max-width: 359px) {
  .gm-shop-row {
    grid-template-columns: 1fr auto;
    grid-template-areas: 'art art' 'name price' 'body body' 'note note' 'reason reason';
  }
  .gm-shop-art { justify-self: center; margin-bottom: 4px; }
}
/* The reason the player was sent to THIS row. A real number, never a nag —
   the benchmark data has countdowns and fake scarcity virtually absent from
   paywalls that actually convert. */
.gm-shop-reason {
  grid-area: reason; margin-top: 4px; font-size: 11px; line-height: 1.4;
  font-weight: 600; color: var(--gm-gold);
}
.gm-shop-row:active:not(:disabled) { transform: scale(.985); }
.gm-shop-row.owned { cursor: default; border-color: rgba(232, 192, 105, .38); }
/* align with the title, not the middle of a six-line body */
.gm-shop-art { align-self: start; }
.gm-shop-name {
  grid-area: name; font-size: 12.5px; font-weight: 700; line-height: 1.3;
  align-self: center;
}
.gm-shop-price {
  grid-area: price; font-size: 12.5px; font-weight: 700; color: #2a1c07;
  white-space: nowrap; align-self: start;
  /* a pill, so the price reads as the control the row already is */
  background: linear-gradient(180deg, #f0cd7d, #d3a44b);
  border-radius: 999px; padding: 4px 9px;
  box-shadow: inset 0 1px 0 rgba(255, 248, 226, .5);
}
.gm-shop-row.owned .gm-shop-price {
  color: var(--gm-ink); background: none; box-shadow: none; padding: 4px 0;
}
.gm-shop-body {
  grid-area: body; font-size: 11.5px; color: var(--gm-on-raised);
  font-weight: 600; line-height: 1.4;
}
/* The honest small print, demoted but never dropped: "colour only, never
   power" is the sentence that keeps this from being pay-to-win, and the free
   shelf's real size belongs beside the thing that extends it. */
.gm-shop-note-sm {
  grid-area: note; margin-top: 3px;
  font-size: 10.5px; color: var(--gm-ink); line-height: 1.4; opacity: .92;
}

.gm-tile {
  appearance: none; cursor: pointer; text-align: left;
  background: var(--gm-raised); color: var(--gm-on-raised);
  border: 1px solid var(--gm-line); border-radius: 12px;
  padding: 9px 10px 8px; font-family: inherit;
  display: flex; flex-direction: column; gap: 3px;
  /* nothing may paint outside the tile, whatever the text turns out to be */
  overflow: hidden;
  transition: transform .12s ease, border-color .12s ease;
}
/* `!important` here is answering an `!important`, not shouting for no reason.
   shared/css/tools.css puts `min-height: 44px !important` on every <button>
   under 768px to hold the 44pt tap floor. That beats any selector, so the tile
   could not grow to fit a wrapped second line, and at 320px the reason text
   painted outside its own border and over the tile below it. Overriding it
   locally is the only lever, and 68px still clears the tap floor it protects.
   tools.css is shared by every tool and game — do not edit it for this. */
.gm-grid .gm-tile { min-height: 76px !important; }
.gm-tile:active:not(:disabled) { transform: scale(.97); }
.gm-tile.done { border-color: rgba(232, 192, 105, .38); }

/* There is always exactly one piece the player should tap next. Marking it is
   the difference between a grid to read and a game to continue. */
.gm-tile.next {
  border-color: var(--gm-gold);
  box-shadow: 0 0 0 1px rgba(232, 192, 105, .28), 0 4px 16px rgba(232, 192, 105, .14);
}
.gm-tile.next .gm-tile-name { color: var(--gm-gold); }

/* Locked tiles stay READABLE. At .42 opacity behind a padlock they were twelve
   identical walls; the name and the reason are what turn a wall into a goal. */
.gm-tile.locked { cursor: default; background: rgba(255, 255, 255, .015); }
.gm-tile.locked .gm-tile-n,
.gm-tile.locked .gm-tile-name { opacity: .55; }

.gm-tile-n {
  display: flex; align-items: center; gap: 5px;
  font-size: 10px; color: var(--gm-ink); font-weight: 700; letter-spacing: .06em;
}
.gm-tile-n em {
  font-style: normal; font-size: 8px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--gm-on-gold); background: var(--gm-gold);
  border-radius: 999px; padding: 1px 5px 1.5px;
}
.gm-tile-name {
  font-size: 12px; font-weight: 600; line-height: 1.25;
  overflow-wrap: anywhere;                 /* a long word breaks, never overhangs */
}
.gm-tile-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px; margin-top: auto; padding-top: 4px;
  min-width: 0;                            /* lets the children actually shrink */
}
.gm-tile-stars { display: flex; gap: 2px; flex: none; }
.gm-tile-brief {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  font-size: 9.5px; font-weight: 600; color: var(--gm-ink);
}
/* the glaze the patron asked for, as the colour it actually fires to */
.gm-swatch {
  width: 8px; height: 8px; border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, .45); box-shadow: 0 0 0 1px rgba(255, 255, 255, .10);
}
/* One line, always. This is a status label, not prose — wrapping it was what
   burst the tile, and at this size a second line reads as a mistake anyway. */
.gm-tile-why {
  font-size: 9.5px; font-weight: 600; color: var(--gm-ink);
  display: inline-flex; align-items: center; gap: 2px;
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gm-star { width: 11px; height: 11px; color: rgba(233, 206, 158, .22); }
.gm-star.on { color: var(--gm-gold); }
.gm-lock { width: 13px; height: 13px; color: var(--gm-ink); flex: none; opacity: .7; }

/* ---------- 9-slice panel art -------------------------------------------
 * The cards were flat CSS boxes — default web UI in a game. border-image
 * stretches the painted frame's edges while keeping its corners intact, so
 * one 512px asset dresses every card at any size.
 * ----------------------------------------------------------------------- */
/* NOTE: no `fill` on border-image-slice.
   With `fill`, WKWebView paints the image's middle region OVER the element's
   content — on an iPhone the frame and the tile separators drew but every
   label and heading vanished, while desktop Chromium was fine. The frame is
   the border; the interior is a normal background underneath the content. */
.gm-card {
  border-image-source: url('./art/panel.webp');
  border-image-slice: 86;
  border-image-width: 22px;
  border-image-repeat: stretch;
  border-style: solid;
  border-width: 22px;
  border-color: transparent;
  background-color: #241a13;
  background-clip: padding-box;
  padding: 16px 14px;
}
/* the art carries the frame, so drop the flat border+radius it replaces */
.gm-card { border-radius: 0; }
.gm-card.gm-bottom { background-color: rgba(36,26,19,.97); }
.gm-select { padding: 12px 10px 10px; }

/* ========================================================================
 * DIALOGS — full pass.
 * The panel art gave the cards a frame; this gives them typography, entrance,
 * hierarchy, and pulls the KIT's shared modals (toast, reward gate, review
 * prompt) into the same world. Kit CSS is shared by seven published games, so
 * every override here is scoped under #gm-stage / .gm-scope and never edits
 * games/kit/*.
 * ===================================================================== */

/* --- entrance: cards arrive, they do not blink into existence ----------- */
@keyframes gmCardIn {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 12px)) scale(.97); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes gmCardInBottom {
  from { opacity: 0; transform: translateX(-50%) translateY(14px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
.gm-card:not([hidden]) { animation: gmCardIn .26s cubic-bezier(.2,.8,.3,1) both; }
.gm-card.gm-bottom:not([hidden]) { animation: gmCardInBottom .24s cubic-bezier(.2,.8,.3,1) both; }

/* --- heading + gold rule ------------------------------------------------ */
.gm-card h2 {
  font-size: 18px; letter-spacing: .01em;
  text-shadow: 0 1px 0 rgba(0,0,0,.5);
}
/* a thin inlaid rule under the heading — the detail that makes a panel read
   as made rather than as a div with a border */
.gm-rule {
  height: 1px; margin: 9px auto 13px; width: 62%;
  background: linear-gradient(90deg,
    rgba(232,192,105,0) 0%, rgba(232,192,105,.55) 50%, rgba(232,192,105,0) 100%);
}
.gm-rule::after {
  content: ''; display: block; width: 5px; height: 5px; margin: -3px auto 0;
  transform: rotate(45deg);
  background: var(--gm-gold); opacity: .8;
}

/* --- toast: match the studio instead of the kit default ----------------- */
#gm-stage ~ .tz-toast, .tz-toast.gm-toast, #gm-stage .tz-toast {
  background: rgba(27, 20, 16, .96) !important;
  color: var(--gm-on-surface) !important;
  border: 1px solid rgba(232, 192, 105, .34) !important;
  border-radius: 12px !important;
  box-shadow: 0 10px 30px rgba(0,0,0,.55) !important;
  font-family: 'Poppins', system-ui, sans-serif !important;
  font-size: 12.5px !important;
  letter-spacing: .01em;
}

/* --- the kit modal's own tokens, in goldmend's palette -------------------
 * The --gm-* tokens are declared on #gm-stage. The kit reward gate mounts to
 * #game-root (tz-game-reward-gate.js: `getElementById('game-root') || body`),
 * which is OUTSIDE #gm-stage -- so inside the modal `var(--gm-gold)` resolves
 * to the empty string and every kit token fell through to the SITE's light
 * theme instead. Measured on the live modal before this block existed:
 *
 *   --tz-game-modal-heading  #12151A   (site heading, near-black)
 *   --tz-game-primary        #2e7d32   (site success green)
 *   --surface                #FFFFFF
 *
 * The panel is #241a13, so the title, the offer heading and the offer body all
 * rendered near-black on near-black -- about 1.1:1, against the 4.5:1 the rest
 * of this file holds itself to -- and the one button the player is meant to
 * press came out Android-success green in a studio painted in gold.
 *
 * Declaring the tokens on #game-root fixes every kit surface at once instead of
 * chasing selectors, and it stays inside this game: kit/tz-game-modal.css is
 * shared with arrow-escape-3d and voltline and is not touched.
 * Every colour here is paired with its on-colour, never picked alone.
 * --------------------------------------------------------------------- */
#game-root {
  --tz-game-modal-backdrop:     rgba(11, 8, 6, .72);
  --tz-game-modal-panel-bg:     #241a13;
  --tz-game-modal-panel-border: rgba(233, 206, 158, .16);
  --tz-game-modal-heading:      #f5ead9;   /* on #241a13 */
  --tz-game-modal-text:         #eaddc9;
  --tz-game-modal-text-muted:   #cbb79a;
  --tz-game-primary:            #d3a44b;   /* the studio's gold, not #2e7d32 */
  --tz-game-primary-on:         #2a1c06;   /* paired: dark ink on gold */
  --tz-game-btn-bg:             transparent;
  --tz-game-btn-color:          #f5ead9;
  --tz-game-btn-border:         rgba(233, 206, 158, .16);
  --tz-game-btn-primary-bg:     #d3a44b;
  --tz-game-btn-primary-color:  #2a1c06;
  --tz-game-btn-primary-border: #d3a44b;
}

/* --- kit reward gate + review prompt, dressed as studio panels ---------- */
#game-root .tz-game-modal {
  background: rgba(11, 8, 6, .72) !important;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
#game-root .tz-reward-gate,
#game-root .tz-game-modal > div {
  background: var(--gm-surface, #1b1410) !important;
  color: var(--gm-on-surface, #eaddc9) !important;
  border-image-source: url('./art/panel.webp');
  border-image-slice: 86;          /* no `fill` — see .gm-card note */
  border-image-width: 22px;
  border-image-repeat: stretch;
  border-style: solid;
  border-width: 22px;
  border-color: transparent;
  background-color: #241a13 !important;
  background-clip: padding-box;
  border-radius: 0 !important;
  box-shadow: 0 22px 54px rgba(0,0,0,.6) !important;
}
#game-root .tz-game-modal-btn {
  font-family: 'Poppins', system-ui, sans-serif !important;
  border-radius: 13px !important;
  font-weight: 700 !important;
  background: linear-gradient(180deg, #f0cd7d, #d3a44b) !important;
  color: #2a1c06 !important;
  border: 0 !important;
  box-shadow: 0 5px 14px rgba(180,130,40,.24), inset 0 1px 0 rgba(255,248,226,.5) !important;
}
/* the secondary/dismiss action must not compete with the primary one */
#game-root .tz-game-modal-btn.secondary,
#game-root .tz-game-modal-btn[data-role="cancel"] {
  background: var(--gm-raised, #2a201a) !important;
  color: var(--gm-on-raised, #f5ead9) !important;
  border: 1px solid rgba(233,206,158,.16) !important;
  box-shadow: none !important;
}
/* the ad offer block inside the reward gate */
#game-root .tz-ad-offer {
  background: rgba(0,0,0,.26) !important;
  border: 1px solid rgba(233,206,158,.14) !important;
  border-radius: 11px !important;
  color: var(--gm-ink, #cbb79a) !important;
}
/* The watch button is the primary action in this modal and was the ONLY kit
   button never restyled here, so it kept the green gradient. It is now the
   same gold as every other primary in the game. */
#game-root .tz-ad-offer__cta {
  font-family: 'Poppins', system-ui, sans-serif !important;
  background-image: linear-gradient(180deg, #f0cd7d, #d3a44b) !important;
  background-color: transparent !important;
  color: #2a1c06 !important;
  border-radius: 13px !important;
  box-shadow: 0 5px 14px rgba(180,130,40,.24), inset 0 1px 0 rgba(255,248,226,.5) !important;
}
#game-root .tz-ad-offer__cta:disabled {
  background-image: none !important;
  background-color: var(--gm-raised, #2a201a) !important;
  color: var(--gm-ink, #cbb79a) !important;
  box-shadow: none !important;
}
/* HIERARCHY WAS INVERTED. The rule above this one styles .tz-game-modal-btn
   gold, and the reward gate's dismiss button carries exactly that class -- so
   "Wait it out" was rendered as the primary while the actual offer sat in
   green. The quiet-secondary rule underneath was written for
   `[data-role="cancel"]` / `.secondary`, and the gate emits NEITHER: its markup
   is `class="tz-game-modal-btn" data-act="cancel"` (tz-game-reward-gate.js:73),
   so that selector had never matched anything.
   Scoped to .tz-reward-gate on purpose -- tz-game-dock.js emits the same class
   and must keep its own styling. */
#game-root .tz-reward-gate .tz-game-modal-btn[data-act="cancel"] {
  background-image: none !important;
  background-color: var(--gm-raised, #2a201a) !important;
  color: var(--gm-on-raised, #f5ead9) !important;
  border: 1px solid rgba(233,206,158,.16) !important;
  box-shadow: none !important;
}

/* --- fail card: read as a refusal, not as an error toast ---------------- */
#gmFailCard h2 { color: #e7a882; }
#gmFailCard .gm-rule { background: linear-gradient(90deg,
  rgba(226,115,74,0) 0%, rgba(226,115,74,.5) 50%, rgba(226,115,74,0) 100%); }
#gmFailCard .gm-rule::after { background: #e2734a; }

@media (prefers-reduced-motion: reduce) {
  .gm-card:not([hidden]), .gm-card.gm-bottom:not([hidden]) { animation: none; }
}

/* ---------- the guided first pot ----------------------------------------
 * Sits low, over the game, never over the clay: the player has to SEE the
 * thing being taught while the line about it is on screen. Pointer-transparent
 * except the Skip button, so the lesson can never eat a drag — the one input
 * the whole game runs on.
 * -------------------------------------------------------------------- */
.gm-tutor {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(96px + var(--tz-safe-bottom, 0px));
  z-index: 12; width: min(94%, 380px);
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  pointer-events: none;
  border-radius: 14px;
  border: 1px solid rgba(232,192,105,.28);
  background: linear-gradient(180deg, rgba(24,16,10,.94), rgba(14,10,7,.94));
  box-shadow: 0 10px 30px rgba(0,0,0,.55);
}
.gm-tutor[hidden] { display: none; }
.gm-tutor-line {
  margin: 0; flex: 1 1 auto;
  font-size: 13.5px; line-height: 1.35; font-weight: 600;
  color: var(--gm-on-bg);
}
.gm-tutor-skip {
  pointer-events: auto; cursor: pointer; flex: 0 0 auto;
  padding: 7px 11px; min-height: 36px;
  font-size: 12px; font-weight: 700;
  color: var(--gm-ink); background: transparent;
  border: 1px solid var(--gm-line); border-radius: 10px;
}
.gm-tutor-skip:active { filter: brightness(1.3); }
/* the acknowledgement steps wait for a tap, so say so */
.gm-tutor.tappable::after {
  content: 'tap to continue';
  position: absolute; left: 0; right: 0; bottom: -17px;
  text-align: center; font-size: 10.5px; letter-spacing: .04em;
  color: var(--gm-ink); opacity: .75;
}

/* One pulse, used for every element the lesson points at. Outline-only: a
   filled highlight over a live control hides the control being explained. */
@keyframes gmTutorPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232,192,105,.0); }
  50%      { box-shadow: 0 0 0 4px rgba(232,192,105,.45); }
}
.gm-tutor-pulse {
  animation: gmTutorPulse 1.5s ease-in-out infinite;
  border-radius: 12px;
}
@media (prefers-reduced-motion: reduce) {
  /* Motion is the pointer here, so it cannot simply be removed — replace the
     pulse with a steady ring that says the same thing without moving. */
  .gm-tutor-pulse {
    animation: none;
    box-shadow: 0 0 0 3px rgba(232,192,105,.55);
  }
}

/* "How to play" — a quiet text link under Slow mode, not a button competing
   with "Sit at the wheel". */
/* A real secondary action: full width, bordered, 44px tall. It was an
   underlined text link floating between a checkbox and a copyright line, which
   read as fine print rather than as the way into the tutorial — and at 36px it
   was under the tap-target floor. */
.gm-replay-tutor {
  display: block; width: 100%; margin: 14px 0 0;
  padding: 12px 14px; min-height: 44px;
  background: transparent; cursor: pointer;
  border: 1px solid var(--gm-line); border-radius: 12px;
  font-family: inherit; font-size: 13.5px; font-weight: 700;
  color: var(--gm-on-surface);
}
.gm-replay-tutor:active { color: var(--gm-gold); border-color: var(--gm-gold); }

/* the risk label turns amber when slow mode is capping the piece */
#gmRiskLabel.warn { color: var(--gm-warn); }

/* The risk read-out is a <button> so it can offer the way out of a slow-mode
   cap. Strip every button affordance until it actually has something to do —
   a control that looks tappable and is not is its own bug.
   NOTE the !important: shared/css/tools.css puts `min-height: 44px !important`
   on every <button> under 768px. That floor is right for real controls and
   wrong here, where this is a 10.5px caption inside a status row; honouring it
   would push the risk row into the pot. The tap target is restored below for
   the one state where this IS a control. */
#gmRiskLabel {
  appearance: none; -webkit-appearance: none;
  padding: 0; margin: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: left;
  min-height: 0 !important;
  cursor: default;
}
#gmRiskLabel.actionable {
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  /* Back over the tap floor only when it is genuinely a control. Measured at
     44x22 unstyled and 44x32 here, both under the 44pt minimum. The fix is to
     grow the HIT AREA and not the ink: 12px of vertical padding takes the
     touch box to 44, and -6px of margin gives the space straight back to the
     layout, so the risk row still occupies 32px and nothing moves into the
     pot. Same trick the platform guidelines describe for small controls. */
  padding: 12px 2px; margin: -6px 0; min-height: 44px !important;
}
#gmRiskLabel.actionable:active { filter: brightness(1.3); }

/* the second line of an empty shop — quieter than the reason above it */
.gm-shop-sub { margin-top: 6px; font-size: 11.5px; opacity: .8; }

/* ---------- repair metals -------------------------------------------------
 * Sits in the mend bar beside the seam being poured. Locked metals are VISIBLE,
 * dimmed and ringed — a player cannot want what they cannot see, and seeing
 * silver next to their own gold seam is the whole reason the case is worth
 * buying. Tapping a locked one opens the shop at that item.
 * ------------------------------------------------------------------------ */
.gm-metals { display: flex; justify-content: center; gap: 8px; margin: 2px 0 6px; }
.gm-metals[hidden] { display: none; }
.gm-metal {
  appearance: none; cursor: pointer;
  width: 34px; height: 34px; padding: 0;
  display: grid; place-items: center;
  background: var(--gm-surface);
  border: 1px solid var(--gm-line); border-radius: 50%;
  transition: transform .12s ease, border-color .15s ease;
  /* tools.css forces min-height:44px on every button under 768px; these are a
     row of 34px swatches inside a status strip and that floor would push the
     mend controls into the pot. The row itself stays a comfortable target. */
  min-height: 34px !important;
}
.gm-metal i { display: block; width: 18px; height: 18px; border-radius: 50%; }
.gm-metal.on { border-color: var(--gm-gold); transform: scale(1.08); }
.gm-metal.locked { opacity: .45; border-style: dashed; }
.gm-metal.locked::after {
  content: ''; position: absolute; width: 34px; height: 34px; border-radius: 50%;
}
.gm-metal:active { transform: scale(.94); }

/* the piece that just fell off the shelf — a sentence, not a banner */
.gm-shelf-lost {
  appearance: none; cursor: pointer;
  display: block; width: 100%; margin: 8px 0 0; padding: 10px 12px;
  min-height: 44px;
  text-align: left; font-family: inherit; font-size: 12px; line-height: 1.4;
  color: var(--gm-on-surface);
  background: rgba(232,192,105,.07);
  border: 1px solid rgba(232,192,105,.28); border-radius: 12px;
}
.gm-shelf-lost[hidden] { display: none; }
.gm-shelf-lost:active { filter: brightness(1.25); }

/* the row the player arrived for */
.gm-shop-row.focus { border-color: var(--gm-gold); background: rgba(232,192,105,.08); }

/* ---- how-to strip -------------------------------------------------------
   index.html uses `.vl-hint-row`, but that class is only styled in
   voltline/game.css — a file this game does not load. So the three spans
   inherited nothing, rendered as plain inline text with no separator, and read
   as one broken run-on sentence on the web page:

     "Drag across the clay to shape it Thin walls are worth more — and crack
      more Trace the cracks to pour gold"

   Styled here rather than in the kit or in voltline's stylesheet: voltline
   already has its own rule and eight other games share that folder, so this
   stays local. Bullets are drawn with ::before so the markup is untouched. */
.vl-hint-row {
  display: flex; flex-wrap: wrap; gap: 8px 20px;
  margin: 0 0 12px; padding: 0;
  font-size: 13px; line-height: 1.5;
  color: var(--gm-ink, #b8ab99);
}
.vl-hint-row > span { display: inline-flex; align-items: baseline; }
.vl-hint-row > span::before {
  content: "◆"; margin-right: 7px;
  font-size: 8px; line-height: inherit;
  color: var(--gm-gold, #e8b04b); opacity: .85;
}
@media (max-width: 600px) {
  .vl-hint-row { gap: 6px 14px; font-size: 12px; }
}


/* ---- THE WHEEL SHED ------------------------------------------------------
   Its own row, not .gm-shop-row. The studio shop's grid is tuned around a
   68px pot with three stacked text areas and a price pill; the shed row also
   carries a button, which has no area in that template and would auto-place
   itself into the middle of the copy. Two panels that look alike but hold
   different currencies are better off not sharing a layout that one of them
   has to fight.
   -------------------------------------------------------------------- */
.gm-shed-row {
  /* Left, not inherited centre. The card centres its own copy, and a
     four-line description centred under a pot reads as a poem. */
  text-align: left;
  background: var(--gm-raised); color: var(--gm-on-raised);
  border: 1px solid var(--gm-line); border-radius: 12px;
  padding: 11px 12px; display: grid; gap: 3px 11px;
  grid-template-columns: 72px 1fr auto;
  grid-template-areas: 'art name price' 'art body body' 'art act act';
  align-items: start;
}
.gm-shed-row.owned { border-color: var(--gm-gold); }
.gm-shed-art  { grid-area: art; align-self: center; justify-self: center; }
.gm-shed-name { grid-area: name; font-size: 13px; font-weight: 700; line-height: 1.3; align-self: center; }
.gm-shed-price {
  grid-area: price; font-size: 12px; font-weight: 700; white-space: nowrap;
  align-self: center; color: #2a1c07; background: var(--gm-gold);
  border-radius: 999px; padding: 3px 9px;
}
.gm-shed-row.owned .gm-shed-price { background: transparent; color: var(--gm-gold); padding-right: 0; }
/* --gm-on-raised, the pair for --gm-raised. There is no dim variant defined
   in this sheet and inventing one would be a colour picked in isolation. */
.gm-shed-body {
  grid-area: body; margin: 2px 0 0; font-size: 11.5px; line-height: 1.45;
  font-weight: 600; color: var(--gm-on-raised);
}
.gm-shed-note {
  grid-area: act; margin: 6px 0 0; font-size: 11px; font-weight: 600;
  line-height: 1.4; color: var(--gm-gold);
}
/* Full width so the target clears 44px on its short side without the row
   having to grow, and so "N more coins" never truncates. */
.gm-shed-buy { grid-area: act; margin-top: 7px; width: 100%; min-height: 44px; font-size: 13px; }
/* The shed has four rows and each carries a button, so it is taller than the
   studio shop's three. Without min-height:0 the flex item refuses to shrink
   below its content and the last row is clipped by the card instead of
   scrolling inside it -- which is how Spindle disappeared entirely. */
#gmShedList { min-height: 0; flex: 1 1 auto; }
.gm-shed-buy[disabled] { opacity: .55; cursor: default; }

/* ---- THE EXHIBITION ------------------------------------------------------
   A grid, not the shed's list. The shed row is a row because it is a purchase
   -- a name, a price and a button read left to right. A wall of finished work
   is looked at, not read, so the pot gets the space and the words sit under it
   like a label under a piece.
   -------------------------------------------------------------------- */
.gm-gal-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 9px;
  overflow-y: auto;
  margin-bottom: 12px;
  /* Same fix the shed needed: without min-height:0 the flex item will not
     shrink below its content, so the bottom row is clipped by the card instead
     of scrolling inside it. */
  min-height: 0;
  flex: 1 1 auto;
}
.gm-gal-tile {
  margin: 0;
  text-align: center;
  background: var(--gm-raised); color: var(--gm-on-raised);
  border: 1px solid var(--gm-line);
  border-radius: 12px;
  padding: 8px 8px 10px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
/* Gold border, matching .gm-shed-row.owned -- in this sheet a gold edge already
   means "this one is yours / this one is special", so a sponsored piece uses
   the same signal rather than inventing a second one. */
.gm-gal-tile.sponsored { border-color: var(--gm-gold); }
.gm-gal-art { display: block; }
.gm-gal-cap { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.gm-gal-glaze { font-size: 12.5px; font-weight: 700; line-height: 1.25; color: var(--gm-on-raised); }
.gm-gal-seams { font-size: 10.5px; line-height: 1.35; color: var(--gm-ink); }
.gm-gal-value {
  font-size: 12px; font-weight: 700; color: var(--gm-gold);
  font-variant-numeric: tabular-nums;
}
/* The sponsor's card. This one line is the reason the panel exists -- it is the
   half of "with their name beside it" that the rewarded ad was promising and
   not delivering. --gm-on-gold is the declared pair for --gm-gold; no text
   colour in this sheet is picked without its background. */
.gm-gal-card {
  margin-top: 4px;
  font-size: 10px; font-weight: 700; line-height: 1.3;
  letter-spacing: .02em;
  background: var(--gm-gold); color: var(--gm-on-gold);
  border-radius: 999px; padding: 3px 8px;
}
.gm-gal-empty { grid-column: 1 / -1; }

/* One column under ~360px: two 104px canvases plus padding and the gap do not
   fit, and the tile would clip the pot rather than the pot shrinking. */
@media (max-width: 359px) {
  .gm-gal-grid { grid-template-columns: 1fr; }
}

@media (max-width: 359px) {
  .gm-shed-row {
    grid-template-columns: 1fr auto;
    grid-template-areas: 'art art' 'name price' 'body body' 'act act';
  }
  .gm-shed-art { justify-self: center; margin-bottom: 4px; }
}
