/* ============================================================
   Tile Memory: game-specific visuals only.
   Chrome (top bar, menu, night mode, fullscreen, celebration)
   comes from shared/shell.css and the --g-* variables, so the
   board survives every background and theme pick.

   Faces are real mahjong tiles from the shared renderer
   (games/shared/tiles.js -> window.SchoolTiles.makeTile): dots,
   bams, craks, winds, dragons, flowers and the joker. Each
   numbered tile keeps its small top-left index numeral, as a
   physical tile does. That numeral is styled HERE because this
   page does not load assets/site.css, and left alone it renders
   inline and tears through the face.

   Layout follows games/multiply: a full-width console of rows,
   nothing floating in a half-empty space.
   ============================================================ */

.tm-wrap { width: 100%; max-width: 640px; margin: 0 auto; }

/* Rows swap between modes with the hidden attribute. Every one of them sets its
   own display (flex / grid), which beats the browser's [hidden] rule, so the
   switch needs to win outright; otherwise solo shows the versus size picker
   and the score chips on top of the campaign readouts. */
.tm-wrap [hidden] { display: none !important; }

/* The shell hides its turn chip with the hidden attribute, but shell.css gives
   .mj-turn display:inline-flex, which outranks [hidden]. After a pass-and-play
   round that leaves a stale "Turn: Player 1 of 2" sitting in the bar during a
   solo game and pushes the toolbar onto a third row on a phone. Putting the
   attribute's intent back costs one line here and touches no shell file. */
.mj-game .mj-turn[hidden] { display: none; }

/* The shell title is allowed to break, which on a phone drew "Tile / Memory"
   over two lines and pushed the toolbar down with it. The name is short enough
   to hold on one line, so the bar wraps its buttons instead. */
.mj-game .mj-title { white-space: nowrap; }

/* Brand block: title + a one-line tagline. The tagline gives the header the
   substance the bare title-and-buttons bar was missing, so the top isn't so
   plain. The bar is fixed at 58px in shell.css; this page lets it grow to fit
   the tagline instead of clipping it. Scoped to this page only. */
.mj-game .mj-topbar { height: auto; min-height: var(--g-topbar-h); }
.mj-brand { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
.mj-sub { margin: 2px 0 0; font-size: 11.5px; line-height: 1.25; color: var(--g-muted);
  letter-spacing: .01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The help affordance uses shell's round .mj-iconbtn (built for exactly this).
   Sitting at the far right of the toolbar puts it in the top-right corner the
   old layout used for Help. The shell already styles the button; only a small
   gap nudge keeps it off the edge of the group. */
.mj-tools .mj-iconbtn { margin-left: 2px; }

/* The shell bar is a fixed 58px tall while its toolbar wraps to two or three
   rows on a phone (measured 104px at 390, 141px at 320). A centred flex child
   taller than its container overflows BOTH ends, so the top row of buttons was
   cut off above the viewport and the last one sat on top of the stage. Letting
   the bar grow pushes the stage down instead. Scoped to :not(.is-fullscreen)
   because in full screen the bar is a floating overlay with its own rules.
   This is a shared-shell condition, not a Tile Memory one: the same wrap
   happens on games/multiply. It is fixed here for this page only until the
   shell itself is changed. */
@media (max-width: 700px) {
  .mj-game:not(.is-fullscreen) .mj-topbar { height: auto; min-height: var(--g-topbar-h); }
}

/* ---- row 1: level / board pick plus the two actions ----
   Both groups sit on one joined row and split the track, so neither can
   leave a dead gap the way loose chips in a space-between flex did. */
.tm-hud { display: grid; gap: 10px; margin-bottom: 12px; }
.tm-hud-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.tm-hud-row.is-split { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }

.tm-seg { display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 3px 10px; border-radius: 13px; background: var(--g-bg2);
  border: 1px solid var(--g-line); box-shadow: inset 0 1px 3px rgba(58,46,32,.10); }
.tm-pick-label { flex: 0 0 auto; font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--g-muted); font-weight: 700; white-space: nowrap; }
.tm-pick-btns { flex: 1 1 auto; min-width: 0; display: grid;
  grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 4px; }
.tm-pill { min-width: 0; border: 0; background: transparent; box-shadow: none; border-radius: 9px;
  font: inherit; font-size: 12.5px; font-weight: 700; color: var(--g-muted);
  padding: 6px 2px; cursor: pointer; transition: background .15s, color .15s, box-shadow .15s; }
.tm-pill:hover:not(:disabled) { color: var(--g-fg); }
.tm-pill.is-on { color: var(--g-on-accent, #fff);
  background: linear-gradient(180deg, var(--g-accent-lt), var(--g-accent));
  box-shadow: 0 1px 2px var(--g-accent-soft), inset 0 1px 0 rgba(255,255,255,.30); }
.tm-pill:disabled { opacity: .40; cursor: not-allowed; }
.tm-pill:focus-visible { outline: 2px solid var(--g-accent); outline-offset: 2px; }

.tm-ctrls { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 8px; }
.tm-ctrls .mj-btn { margin: 0; justify-content: center; padding: 7px 8px; font-size: 12.5px; }
.tm-ctrls .mj-btn[disabled] { opacity: .45; cursor: not-allowed; }

/* ---- row 2: one console holding the back picker and the readouts ----
   The picker is a raised readout box; beside it either the campaign stat grid
   or the versus score chips fill the rest of the width. */
.tm-console { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px;
  align-items: stretch; margin-bottom: 10px; }
.tm-back-box { display: flex; flex-direction: column; justify-content: center; gap: 7px;
  padding: 8px 12px; border-radius: 13px; background: linear-gradient(180deg, var(--g-bg2), var(--g-bg));
  border: 1px solid var(--g-line); box-shadow: var(--g-shadow-sm); }
.tm-back-label { font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--g-muted); font-weight: 700; }
.tm-backs { display: flex; gap: 6px; }

/* Tile backs are built from the live theme tokens only, so a background or
   theme change carries them along. Each design pairs one token background with
   the token guaranteed to read against it (card / heading, bg2 / fg, accent /
   on-accent) and adds a faint weave so the four never look alike.
   No hue of its own anywhere: a strong red back would compete with red on the
   tile faces, which is where that color belongs. */
.tm-board, .tm-swatch {
  --tm-back-a: var(--g-card); --tm-back-b: var(--g-card); --tm-back-ink: var(--g-heading);
  --tm-back-pat: repeating-linear-gradient(45deg, rgba(58,46,32,.05) 0 6px, transparent 6px 12px);
}
[data-back="evergreen"] {
  --tm-back-a: var(--g-heading); --tm-back-b: var(--g-heading); --tm-back-ink: var(--g-card);
  --tm-back-pat: repeating-linear-gradient(45deg, rgba(255,255,255,.07) 0 5px, transparent 5px 10px);
}
[data-back="brass"] {
  --tm-back-a: var(--g-accent); --tm-back-b: var(--g-accent); --tm-back-ink: var(--g-on-accent, #fff);
  --tm-back-pat: repeating-linear-gradient(90deg, rgba(255,255,255,.10) 0 3px, transparent 3px 8px);
}
[data-back="midnight"] {
  --tm-back-a: var(--g-fg); --tm-back-b: var(--g-fg); --tm-back-ink: var(--g-bg);
  --tm-back-pat: repeating-linear-gradient(45deg, rgba(255,255,255,.08) 0 3px, transparent 3px 9px),
                 repeating-linear-gradient(-45deg, rgba(0,0,0,.10) 0 3px, transparent 3px 9px);
}
[data-back="ivory"] {
  --tm-back-a: var(--g-card); --tm-back-b: var(--g-card); --tm-back-ink: var(--g-heading);
  --tm-back-pat: repeating-linear-gradient(45deg, rgba(58,46,32,.07) 0 6px, transparent 6px 12px);
}
.tm-swatch { flex: 0 0 auto; width: 26px; height: 26px; padding: 0; border-radius: 8px;
  border: 2px solid var(--g-line); cursor: pointer;
  background-image: var(--tm-back-pat), linear-gradient(150deg, var(--tm-back-a), var(--tm-back-b));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), var(--g-shadow-sm);
  transition: transform .12s, border-color .15s, box-shadow .15s; }
.tm-swatch:hover { transform: translateY(-1px); }
.tm-swatch.is-on { border-color: var(--g-accent);
  box-shadow: 0 0 0 3px var(--g-accent-soft), var(--g-shadow-sm); }
.tm-swatch:focus-visible { outline: 2px solid var(--g-accent); outline-offset: 2px; }

/* ---- readouts: evenly sized cells, no ragged last cell ---- */
.tm-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; min-width: 0; }
.tm-stat { display: flex; flex-direction: column; justify-content: center; min-width: 0;
  padding: 6px 10px; border-radius: 10px; background: var(--g-card);
  border: 1px solid var(--g-line); box-shadow: var(--g-shadow-sm); }
.tm-stat-k { font-size: 10px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--g-muted); font-weight: 700; }
.tm-stat-v { font-size: 17px; font-weight: 800; color: var(--g-fg); line-height: 1.15;
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.tm-stat-sub { font-size: 11px; font-weight: 600; color: var(--g-muted); line-height: 1.2;
  font-variant-numeric: tabular-nums; }
.tm-stat-sub:empty { display: none; }

.tm-scores { display: grid; gap: 8px; min-width: 0;
  grid-template-columns: repeat(2, minmax(0, 1fr)); }
.tm-scores[data-players="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tm-scores[data-players="4"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.tm-scores[data-players="5"], .tm-scores[data-players="6"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tm-score { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; min-width: 0; padding: 6px 8px; border-radius: 10px;
  background: var(--g-card); border: 1px solid var(--g-line); box-shadow: var(--g-shadow-sm); }
.tm-score-n { font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--g-muted); font-weight: 700; white-space: nowrap; }
.tm-score-v { font-size: 19px; font-weight: 800; color: var(--g-fg); line-height: 1.1;
  font-variant-numeric: tabular-nums; }
.tm-score-u { font-size: 10px; color: var(--g-muted); font-weight: 600; }
/* The active player gets the accent fill and a ring; a name change alone is
   too quiet to read across the table while everyone is watching one board. */
.tm-score.is-active { border-color: var(--g-accent); background: var(--g-accent-soft);
  box-shadow: 0 0 0 2px var(--g-accent-soft), var(--g-shadow-md); }
.tm-score.is-active .tm-score-n,
.tm-score.is-active .tm-score-v { color: var(--g-heading); }

/* ---- row 3: campaign progress dots ---- */
.tm-dots { display: flex; gap: 9px; justify-content: center; margin-bottom: 10px; }
.tm-dot { width: 11px; height: 11px; border-radius: 50%;
  background: var(--g-bg2); border: 1px solid var(--g-line); transition: background .2s, border-color .2s; }
.tm-dot.is-done { background: var(--g-accent-soft); border-color: var(--g-accent); }
.tm-dot.is-cur { background: var(--g-accent); border-color: var(--g-accent);
  box-shadow: 0 0 0 3px var(--g-accent-soft); }

/* ---- the table: a framed felt panel ---- */
.tm-board-frame { padding: 14px; border-radius: 20px; width: fit-content; max-width: 100%;
  margin: 0 auto; background: linear-gradient(150deg, var(--g-bg2), var(--g-card) 60%, var(--g-bg));
  border: 1px solid var(--g-line);
  box-shadow: 0 0 0 2px var(--g-accent), var(--g-shadow-lg); }

/* The board: tiles size themselves to the viewport. The column count is written
   out per level from JS (repeat(N, 1fr)), so the tracks always share the width
   evenly; the board's own width is min(N * 72px, 100%), which gives a sensible
   tile size on a desktop but caps at 100% on a phone, so 7- and 8-column boards
   shrink their tiles instead of pushing sideways. A column count carried in a
   custom property was the earlier failure mode (it collapsed to one column when
   the variable was missing), so the count is always a literal integer here. */
.tm-board { --tm-gap: 10px;
  display: grid; gap: var(--tm-gap); justify-content: center; }

/* ---- one tile: a flip card made of two faces ---- */
.tm-tile { position: relative; width: 100%; aspect-ratio: 3 / 4; border: 0; padding: 0;
  background: none; font: inherit; color: inherit; cursor: pointer;
  perspective: 700px; -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  -webkit-user-select: none; user-select: none;
  transition: transform .12s ease, filter .2s ease; }
.tm-tile:hover:not(.is-matched) { transform: translateY(-2px); }
.tm-tile:focus-visible { outline: 2px solid var(--g-accent); outline-offset: 2px; }

.tm-inner { position: absolute; inset: 0; transform-style: preserve-3d;
  transition: transform .34s cubic-bezier(.2,.75,.3,1.05); }
.tm-tile.is-up .tm-inner, .tm-tile.is-matched .tm-inner { transform: rotateY(180deg); }

.tm-face { position: absolute; inset: 0; border-radius: 10px; overflow: hidden;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35), var(--g-shadow-sm); }
.tm-back-face { display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--g-line);
  background-image: var(--tm-back-pat), linear-gradient(150deg, var(--tm-back-a), var(--tm-back-b)); }
.tm-back-face::after { content: "?"; font-family: var(--serif, Georgia, serif);
  font-size: 22px; font-weight: 700; line-height: 1; color: var(--tm-back-ink);
  text-shadow: 0 1px 1px rgba(0,0,0,.22); }
/* The tile SLAB is always ivory, hard-coded the way every physical tile is:
   the shared renderer paints its own dotted green bamboo and dark coins, and
   those marks only read against ivory. A themed slab would sink on the dark
   backgrounds, which is why this body opts out of the palette switch. Every
   other surface in the game moves with the theme. */
.tm-front-face { transform: rotateY(180deg); border: 1px solid rgba(60,50,30,.22);
  background: linear-gradient(165deg, #fdfcf7, #ece4d2);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), 0 2px 5px rgba(0,0,0,.16); }

/* Constrain BOTH the inline SVG and the PNG override: some faces ship as art,
   and without an explicit size that image renders at its pixel width and blows
   out the board. */
.tm-tile .tl { position: relative; width: 92%; height: 95%; margin: 2.5% auto 0;
  display: block; background: none; border: 0; box-shadow: none; }
.tm-tile .tl svg { width: 100%; height: 100%; display: block; }
.tm-tile .tl .tl-img { width: 100%; height: 100%; display: block; object-fit: contain; pointer-events: none; }

/* Corner numeral: the small top-left index a real tile carries, so anyone can
   name a tile without counting nine thin bamboo sticks at 56px. It is styled
   here in plain ink: assets/site.css never loads on this page, and an
   unstyled <em> falls into the flow and shoves the face off-center. No white
   chip and no drop shadow: that reads as a sticker stuck onto the tile, and
   against ivory the bare ink already carries about 10:1. Warm ink rather than
   a themed color because the slab below it is always ivory. */
.tm-tile .tl .cnr { position: absolute; top: 1px; left: 3px; z-index: 3;
  display: block !important;
  font-family: var(--sans, system-ui, -apple-system, "Segoe UI", sans-serif);
  font-size: 11px; font-weight: 800; line-height: 1.2; text-align: center;
  color: #3A2E20; background: none; box-shadow: none; }

/* Blocked stones (fog obstacles): a cross on the back so the obstacle is fair
   from rest, and a carved stone with a cross on the front once turned. They are
   inert -- a memory game never pairs them, they only fill the board and waste a
   flip. Kept in the muted stone family so they read as "blocked", not as a tile
   that belongs to the set. */
.tm-tile.is-fog .tm-back-face { background-image: var(--tm-back-pat),
  linear-gradient(150deg, #8d867b, #6f685d); border-color: rgba(40,34,24,.45); }
.tm-tile.is-fog .tm-back-face::after { content: "\2715"; color: rgba(245,240,230,.85);
  text-shadow: 0 1px 1px rgba(0,0,0,.35); }
.tm-tile.is-fog .tm-front-face { background: linear-gradient(165deg, #b3ada1, #8d867b);
  border-color: rgba(40,34,24,.30); }
.tm-tile.is-fog .tm-fog-mark { position: absolute; inset: 16%; border-radius: 8px;
  border: 2px solid rgba(40,34,24,.35); display: flex; align-items: center; justify-content: center;
  background:
    repeating-linear-gradient(45deg, rgba(255,255,255,.14) 0 5px, transparent 5px 10px),
    linear-gradient(160deg, #9a948a, #7c756a);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35), inset 0 -3px 6px rgba(0,0,0,.22); }
.tm-tile.is-fog .tm-fog-mark::after { content: "\2715"; color: rgba(245,240,230,.9);
  font-size: 22px; font-weight: 700; line-height: 1; text-shadow: 0 1px 1px rgba(0,0,0,.3); }
/* A fog stone never matches, so it never carries the matched ring. */
.tm-tile.is-fog.is-matched .tm-front-face { box-shadow: none; }
.tm-tile.is-fog.is-matched .tm-fog-mark { opacity: .7; }

/* Inert slots that pad the last row to a clean rectangle. They read as empty
   tray space, not broken tiles, so a 22-tile board in a 6-wide grid just has
   two blank corners. */
.tm-tile.tm-slot { background: none; border: 0; box-shadow: none; cursor: default;
  pointer-events: none; }

/* Matched: stays face up, dimmed but still readable at 56px, with a themed
   ring so ownership reads without another color. */
.tm-tile.is-matched .tm-front-face { box-shadow: inset 0 0 0 2px var(--g-accent),
  inset 0 0 0 4px var(--g-card), 0 2px 5px rgba(0,0,0,.14); }
.tm-tile.is-matched .tl { opacity: .74; }
.tm-tile.is-matched { cursor: default; }

/* Whose pair it is: a small P-mark in the same ink family as the corner digit.
   Four players cannot be told apart by four new hues, and every themed hue
   would move under them, so each tile carries its owner's initials instead. */
.tm-tile[data-owner="1"] .tm-front-face::after { content: "P1"; }
.tm-tile[data-owner="2"] .tm-front-face::after { content: "P2"; }
.tm-tile[data-owner="3"] .tm-front-face::after { content: "P3"; }
.tm-tile[data-owner="4"] .tm-front-face::after { content: "P4"; }
.tm-tile[data-owner="5"] .tm-front-face::after { content: "P5"; }
.tm-tile[data-owner="6"] .tm-front-face::after { content: "P6"; }
.tm-tile[data-owner] .tm-front-face::after { position: absolute; right: 3px; bottom: 2px; z-index: 4;
  display: block; font-family: var(--sans, system-ui, -apple-system, "Segoe UI", sans-serif);
  font-size: 9px; font-weight: 800; line-height: 1.1; letter-spacing: .02em; color: #3A2E20; }

/* small pop when a pair lands */
.tm-tile.is-pop { animation: tm-pop .42s ease; }
@keyframes tm-pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.08); }
  100% { transform: scale(1); }
}

/* ---- status line ---- */
/* Kept small and quiet: it is the running instruction under the board, not a
   headline, so it does not fight the tiles for attention or eat vertical space. */
.tm-status { text-align: center; margin: 8px auto 0; max-width: 520px; min-height: 16px;
  font-size: 12.5px; color: var(--g-muted); line-height: 1.45; }
.tm-status b { color: var(--g-heading); }

/* ---- campaign finale: a sustained, warmer close for clearing the last board ----
   The shell already centers and transitions .mj-win; these only add flair on top
   of the per-level clear, which is just a brief toast. */
.tm-finale .mj-win-card { padding: 30px 28px 26px; }
.tm-finale-medal { font-size: 66px; line-height: 1; animation: tm-finale-bob 1.15s ease-in-out infinite; }
@keyframes tm-finale-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
.tm-finale-title { font-size: 26px; margin: 6px 0 8px; }
.tm-finale-sub { font-size: 14px; }
.tm-finale-sub2 { font-size: 12px; color: var(--g-muted); margin: 10px 0 4px; line-height: 1.5; }
.tm-finale-btn { font-size: 15px; padding: 10px 22px; }

/* ---- help modal (was the inline <details> below the board) ----
   A native <dialog>: the (?) button opens it, the X or a tap on the dimmed
   backdrop closes it, and Esc closes a modal dialog on its own. The body
   scrolls, so the long how-to copy never overflows a short phone screen. */
.tm-help { width: min(560px, 92vw); max-height: 86vh; padding: 0; border: 0; color: var(--g-fg);
  border-radius: 18px; background: var(--g-card); box-shadow: var(--g-shadow-lg);
  overflow: hidden; }
.tm-help::backdrop { background: rgba(30, 22, 12, .55); }
.tm-help-card { display: flex; flex-direction: column; max-height: 86vh; }
.tm-help-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 15px 16px; border-bottom: 1px solid var(--g-line);
  background: linear-gradient(180deg, var(--g-bg2), var(--g-card)); }
.tm-help-head h2 { margin: 0; font-family: var(--serif, Georgia, serif); font-size: 18px;
  color: var(--g-heading); line-height: 1.25; }
.tm-help-x { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--g-line); background: var(--g-bg2); color: var(--g-fg);
  font-size: 22px; line-height: 1; cursor: pointer; transition: background .15s, border-color .15s; }
.tm-help-x:hover { background: var(--g-accent-soft); border-color: var(--g-accent); }
.tm-help-x:focus-visible { outline: 2px solid var(--g-accent); outline-offset: 2px; }
.tm-help-body { padding: 14px 16px 20px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.tm-help-body h2 { margin: 16px 0 6px; font-family: var(--serif, Georgia, serif);
  font-size: 16px; color: var(--g-heading); }
.tm-help-body h2:first-child { margin-top: 0; }
.tm-help-body p { font-size: 13.5px; color: var(--g-muted); line-height: 1.6; margin: 0 0 9px; }
.tm-help-body b { color: var(--g-fg); }
.tm-help-body .tm-note { font-size: 12px; color: var(--g-muted); font-style: italic; }
/* Fallback when showModal is unsupported: keep the panel on top and centred. */
.tm-help[open]:not(:modal) { position: fixed; inset: 0; display: flex; align-items: center;
  justify-content: center; z-index: 60; background: rgba(30, 22, 12, .55); }

/* ---- responsive ----
   Below 520px the board gap and tile drop together so a six-wide board fits a
   390px phone without any sideways scroll, and the readout grid becomes 2x2
   next to the picker rather than four cramped slivers. */
@media (max-width: 520px) {
  /* Stack the picker and the buttons instead of splitting the row: side by side
     they squeezed both button labels onto two lines and left the pills as
     slivers. Full width each, and the labels hold one line. */
  .tm-hud-row.is-split { grid-template-columns: minmax(0, 1fr); }
  .tm-ctrls .mj-btn { white-space: nowrap; }
  .tm-board { --tm-gap: 6px; }
  .tm-board-frame { padding: 10px; border-radius: 16px; }
  .tm-console { gap: 8px; }
  .tm-back-box { padding: 7px 9px; }
  .tm-swatch { width: 22px; height: 22px; }
  .tm-backs { gap: 5px; }
  .tm-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tm-stat-v { font-size: 15px; }
  .tm-score-v { font-size: 17px; }
  .tm-pill { font-size: 11px; padding: 6px 1px; }
  .tm-pick-label { font-size: 9px; }
  .tm-ctrls .mj-btn { padding: 7px 4px; font-size: 11.5px; }
  .tm-dots { gap: 7px; }
  /* The tagline is short; let it wrap rather than ellipsize on a narrow phone. */
  .mj-sub { white-space: normal; }
  .tm-help { width: 94vw; max-height: 88vh; }
  .tm-help-head h2 { font-size: 16px; }
}

/* ---- very small screens: hand the board back its vertical room ----
   On phones <=360px the fixed HUD + console already eat ~170px, which starves
   the board frame and forces sub-15px tiles. Tightening the chrome's own
   vertical spacing (never the tiles) returns that room to the frame, so the
   board stays tappable AND still fits on one screen with no scroll.
   fitBoardToFrame caps the tile size to the frame, so a taller frame only ever
   grows tiles; this can never reintroduce a scroll. */
@media (max-width: 360px) {
  .tm-hud { gap: 6px; margin-bottom: 6px; }
  .tm-hud-row { gap: 6px; }
  .tm-pill { padding: 4px 1px; font-size: 10px; }
  .tm-ctrls .mj-btn { padding: 5px 3px; font-size: 10.5px; }
  .tm-console { gap: 6px; margin-bottom: 6px; }
  .tm-back-box { padding: 5px 8px; gap: 4px; }
  .tm-stats { gap: 5px; }
  .tm-stat { padding: 4px 6px; }
  .tm-stat-k { font-size: 9px; }
  .tm-stat-v { font-size: 13px; }
  .tm-swatch { width: 18px; height: 18px; }
  .tm-status { margin-top: 4px; font-size: 11px; line-height: 1.3; }
  .tm-board-frame { padding: 8px; }
  .tm-board { --tm-gap: 5px; }
  .tm-dots { margin-bottom: 6px; }
}

@media (prefers-reduced-motion: reduce) {
  .tm-inner { transition: none !important; }
  .tm-tile, .tm-tile.is-pop { transition: none !important; animation: none !important; }
  .tm-tile:hover:not(.is-matched) { transform: none; }
  .tm-dot { transition: none; }
}

/* ---- no vertical scroll: fit the board to the stage ----
   The shell locks the page to 100vh and lets .mj-stage scroll on its own, so a
   tall board is what pushes the screen up and down. Here the wrap fills the
   stage as a flex column and the board frame takes the leftover height; the JS
   (fitBoardToFrame) then sizes the tiles to that box. Result: whatever the
   level -- even the 46-tile finale -- the board sits on one screen with no
   up/down scroll, and a wider grid just means a flatter, shorter board. Scoped
   to this page so it never touches the shared shell or games/multiply. */
/* The shell never resets the default 8px body margin, which leaves a 16px gap
   the document can scroll into. Kill it so the game truly fills the viewport. */
body { margin: 0; }
#gameRoot .tm-wrap { display: flex; flex-direction: column; height: 100%; max-height: 100%; }
#gameRoot .tm-hud,
#gameRoot .tm-console,
#gameRoot .tm-dots,
#gameRoot .tm-status { flex: 0 0 auto; }
#gameRoot .tm-board-frame {
  width: auto; max-width: none; margin: 0;
  flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center;
}
