/**
 * Draw! page styles. Port of the app's draw.module.css.
 *
 * This page is deliberately NOT the dark site theme: it is a white page in a
 * handdrawn register. Three rules hold the surface together; breaking any one
 * of them is what makes it look assembled rather than drawn:
 *
 *   ONE INK.    Black on white, and nothing in between. No greys, no
 *               hairlines, no dimmed captions. Anything that needs to recede
 *               does it by being small or by being said once. The single
 *               exception is --accent, a highlighter that only ever sits
 *               BEHIND black.
 *   TWO SIZES.  --fs-title and --fs-body in the handwritten face. The typed
 *               face (Roboto Mono) is a separate voice at its own single
 *               small size, for the game's own apparatus.
 *   ONE PEN.    Every line is --hand-stroke wide and every line is squiggly:
 *               nothing here may use a CSS border, text-decoration, or a
 *               native control that draws its own track.
 */

@font-face {
  font-family: 'Architects Daughter';
  src: url('../fonts/architects-daughter-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Roboto Mono';
  src: url('../fonts/roboto-mono-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Roboto Mono';
  src: url('../fonts/roboto-mono-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

:root {
  --ink: #000000;
  --paper: #ffffff;
  /* Highlighter, not ink. Only ever behind black. */
  --accent: #ffe500;

  /* Architects Daughter has no CJK glyphs: rounded/hand system faces carry
     zh/ja. Per-language overrides below put the right family first. */
  --hand: 'Architects Daughter', 'Yuanti SC', 'Hiragino Maru Gothic ProN', 'Kaiti SC',
    'Comic Sans MS', cursive, sans-serif;
  --type: 'Roboto Mono', ui-monospace, Menlo, monospace, sans-serif;

  /* The handwritten scale, and the whole of it. */
  --fs-title: 58px;
  --fs-body: 28px;
  /* The typed face is the printed apparatus around the game, not anything
     either player said. */
  --fs-typed: 16px;

  /* Width of EVERY hand-drawn line on this page. This is the fallback, used
     where no canvas is on screen (landing, pick, gallery). While the game
     canvas is live, canvas.js republishes it at the pen's true on-screen
     width (pencilCssWidth) so the chrome matches the strokes exactly. */
  --hand-stroke: 4.4px;

  /* Glyph outlines are lines too, and the face is a single fixed weight, so
     the title is drawn with a visibly heavier pen than the body beside it.
     ONLY the title gets the correction: at body size it reads as bold. */
  --hand-text-stroke-title: 0.3px;
}

/* The language picker sets the html lang attribute; these keep the CJK glyphs
   in a face that reads as the same pen as Architects Daughter. */
html[lang='zh'] {
  --hand: 'Architects Daughter', 'Yuanti SC', 'Kaiti SC', 'YouYuan', 'KaiTi',
    'Microsoft YaHei', 'Comic Sans MS', cursive, sans-serif;
}

html[lang='ja'] {
  --hand: 'Architects Daughter', 'Hiragino Maru Gothic ProN', 'Yu Gothic', 'Meiryo',
    'Comic Sans MS', cursive, sans-serif;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--hand);
  font-size: var(--fs-body);
  -webkit-font-smoothing: antialiased;
}

[hidden] {
  display: none !important;
}

/* Touch hygiene, no effect on desktop pixels: buttons and the chat input must
   never trigger double-tap zoom mid-game, and the grey tap flash is a second
   ink this page does not have. */
button,
input {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.root {
  width: 100%;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
}

.title {
  margin: 0;
  font-size: var(--fs-title);
  font-weight: 400;
  letter-spacing: 1px;
  -webkit-text-stroke: var(--hand-text-stroke-title) currentColor;
}

/* The start page's "draw!" only: a wordmark on a splash screen, not a heading
   in a hierarchy. Nothing else may use it. */
.title-big {
  font-size: calc(var(--fs-title) * 2);
}

/* ── the typed half ─────────────────────────────────────────────────────── */

.typed {
  font-family: var(--type);
  font-size: var(--fs-typed);
  line-height: 1.5;
  -webkit-text-stroke: 0;
}

/* ── the corner buttons: how-to-play and language ───────────────────────── */

.corner-btns {
  position: fixed;
  top: 14px;
  right: 16px;
  z-index: 10;
  display: flex;
  gap: 10px;
}

/* Same anatomy as the canvas tools: a squiggle ring is the outline, the
   marker swipe is hover, the glyph sits on top. */
.corner-btn {
  position: relative;
  width: 39px;
  height: 39px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--hand);
  cursor: pointer;
  display: grid;
  place-items: center;
}

.corner-btn:hover .sq-highlight,
.corner-btn:focus-visible .sq-highlight {
  opacity: 1;
}

/* The globe: the button's own ring is the sphere, the glyph adds the meridian
   and the equator, all at --hand-stroke like every other line here. */
.globe-glyph {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

/* ── shared hand-drawn chrome ───────────────────────────────────────────── */

.sq-frame {
  position: absolute;
  inset: 0;
  pointer-events: none;
  color: var(--ink);
  /* Above the canvas: the drawing surface paints an opaque white background
     over its whole box, and the frame is its exact size. */
  z-index: 1;
}

.sq-frame svg {
  display: block;
}

/* The single width for every squiggle. Set here rather than per call site so
   nothing can pass its own number. */
.sq-path {
  stroke-width: var(--hand-stroke);
}

.sq-rule {
  width: 100%;
  height: 8px;
  color: var(--ink);
  flex: 0 0 auto;
}

/* The rule svg must not be inline: an inline svg sits on the text baseline of
   the host's line box, and at the handwritten face's 22px metrics that paints
   the line ~18px BELOW the 8px host (the host's rect stays put, so DOM
   measurements look aligned while the pixels are not). Block pins the svg to
   the host's top, where the alignment math actually holds. */
.sq-rule svg {
  display: block;
}

/* Replaces text-decoration, the one straight line CSS draws for you. */
.sq-underline {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 8px;
  color: var(--ink);
  pointer-events: none;
}

/* The marker swipe. Hidden until its control is hovered or on. */
.sq-highlight {
  position: absolute;
  inset: 0;
  color: var(--accent);
  pointer-events: none;
  opacity: 0;
  z-index: 0;
}

/* Anything that must stay readable on top of the swipe. A bare text node has
   no box to raise, so labels are wrapped. */
.btn-label {
  position: relative;
  z-index: 2;
}

.hand-btn {
  position: relative;
  padding: 15px 32px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--hand);
  font-size: var(--fs-body);
  cursor: pointer;
  transition: transform 90ms ease;
}

.hand-btn:hover:not(:disabled) {
  transform: translateY(-1px);
}

.hand-btn:active:not(:disabled) {
  transform: translateY(1px);
}

.hand-btn:disabled {
  cursor: default;
}

.hand-btn:hover:not(:disabled) .sq-highlight,
.hand-btn[data-on='true'] .sq-highlight {
  opacity: 1;
}

.hand-btn-quiet {
  position: relative;
  padding: 6px 12px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--hand);
  font-size: var(--fs-body);
  cursor: pointer;
}

.hand-btn-quiet .sq-underline {
  opacity: 0;
}

.hand-btn-quiet:hover .sq-underline {
  opacity: 1;
}

/* ── landing ────────────────────────────────────────────────────────────── */

.setup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  width: min(700px, 88%);
  padding: 40px 0;
  text-align: center;
}

/* The art block rides higher while Start stays exactly where it was: the
   title pulls everything after it up 32px, the tagline another 8, and the
   button gives the full 40 back. */
.setup .title-big {
  margin-top: -32px;
}

.setup .hand-btn {
  margin-top: 40px;
}

.tagline {
  /* Extra air above, so the copy and the controls sit clear of the drawings
     rather than reading as a caption hanging off the horse's feet. */
  margin: 6px 0 0;
  /* Wide enough that the one-sentence pitch stays on one line. */
  max-width: 72ch;
  white-space: nowrap;
}

/* Long translations still must not push the page sideways. */
@media (max-width: 720px) {
  .tagline {
    white-space: normal;
  }
}

.error {
  margin: 0;
}

/* Bottom-aligned so the drawings sit on a shared line despite their different
   proportions, with the middle one lifted off it. */
.doodles {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 10px;
  margin: 4px 0;
}

.doodle {
  height: 231px;
  /* Auto width off the viewBox, so each drawing keeps its own proportions. */
  width: auto;
  color: var(--ink);
  user-select: none;
  /* A flipbook, not a wobble: the transform SNAPS between two angles and is
     never interpolated. 0.66s over two states is three flips a second. */
  animation: doodle-tilt 0.66s infinite;
}

/* The pen, at the same width as every other line here. non-scaling-stroke
   takes stroke-width as screen px instead of scaling it with the viewBox. */
.doodle-path {
  stroke-width: var(--hand-stroke);
}

/* Small, raised, and half a period out of step with its neighbours. */
.doodle-mid {
  height: 44px;
  margin-bottom: 178px;
  animation-delay: -0.33s;
}

/* The outer two lean AWAY from the middle, each snapping within its own
   one-sided range, so the row opens outwards like three things propped up. */
.doodle-left {
  animation-name: doodle-tilt-left;
}

.doodle-right {
  animation-name: doodle-tilt-right;
}

@keyframes doodle-tilt {
  0%,
  49.999% {
    transform: rotate(-3.5deg);
  }
  50%,
  100% {
    transform: rotate(3.5deg);
  }
}

@keyframes doodle-tilt-left {
  0%,
  49.999% {
    transform: rotate(-24deg);
  }
  50%,
  100% {
    transform: rotate(-17deg);
  }
}

@keyframes doodle-tilt-right {
  0%,
  49.999% {
    transform: rotate(17deg);
  }
  50%,
  100% {
    transform: rotate(24deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .doodle {
    animation: none;
  }
}

/* ── pick a word ────────────────────────────────────────────────────────── */

.pick {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  width: min(760px, 90%);
  padding: 40px 0;
  text-align: center;
}

.pick-words {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 22px;
}

.pick-word {
  position: relative;
  min-width: 238px;
  padding: 30px 35px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--hand);
  font-size: var(--fs-body);
  cursor: pointer;
  transition: transform 90ms ease;
}

.pick-word:hover:not(:disabled) {
  transform: translateY(-2px);
}

.pick-word:hover:not(:disabled) .sq-highlight {
  opacity: 1;
}

/* ── game layout: canvas beside chat ────────────────────────────────────── */

.game {
  /* The two columns are ruled off against each other (260730, from the app):
     equal fixed heads plus an equal gap is what puts the side rule on the
     same line as the top edge of the drawn canvas box. Without the fixed
     height the stage header breathes with its content (the word is 22px, the
     turn-over note is typed 13px), the canvas box rises, and the rule reads
     as hanging below the corner. */
  --head-h: 32px;
  --col-gap: 10px;

  display: flex;
  gap: 26px;
  align-items: stretch;
  width: min(1650px, 96%);
  height: min(1075px, 94vh);
  height: min(1075px, 94dvh);
  padding: 18px 0;
}

.stage {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  gap: var(--col-gap);
}

.header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex: 0 0 auto;
  height: var(--head-h);
}

.round {
  min-width: 110px;
}

.clock {
  min-width: 110px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.word {
  flex: 1 1 auto;
  text-align: center;
  font-size: var(--fs-body);
  letter-spacing: 1px;
}

.header-note {
  flex: 1 1 auto;
  text-align: center;
  font-size: var(--fs-body);
}

/* ONE drawn box holds the paper and the tools (ported from the app, 260728):
   the squiggle frame sits on this box, and the toolbar lives in the leftover
   height inside it instead of hanging off the outside. */
.canvas-box {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* Inside the drawn line, so neither the paper nor the tools sit on top of
     it. */
  padding: 10px;
  box-sizing: border-box;
}

.canvas-frame {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  /* Top-aligned, not centred: whatever height the 10:7 paper does not use
     belongs at the bottom, where the tools already are. Centred, it opens a
     dead white band along the top edge of the box. */
  align-items: flex-start;
  justify-content: center;
}

/* Hosts the canvas AND its border, so the two are the same box by
   construction. */
.canvas-wrap {
  position: relative;
  max-width: 100%;
  max-height: 100%;
  height: 100%;
  aspect-ratio: 1000 / 700;
}

.canvas {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--paper);
  /* One finger draws: no scroll, no pinch, no double-tap zoom, no
     pull-to-refresh may ever start on the paper. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.toolbar {
  display: flex;
  justify-content: center;
  gap: 18px;
  flex: 0 0 auto;
  height: 65px;
}

.toolbar-spacer {
  height: 65px;
  flex: 0 0 auto;
}

/* Both tools are always drawn, ring and all: the ring is the tool's outline,
   not its selected state. Selection is the marker swipe behind it. */
.tool {
  position: relative;
  width: 58px;
  height: 58px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--hand);
  cursor: pointer;
  display: grid;
  place-items: center;
}

.tool:hover .sq-highlight,
.tool[data-on='true'] .sq-highlight {
  opacity: 1;
}

.tool-glyph {
  position: relative;
  z-index: 2;
  font-size: var(--fs-body);
  line-height: 1;
  color: var(--ink);
}

/* ── chat column ────────────────────────────────────────────────────────── */

.side {
  display: flex;
  flex-direction: column;
  flex: 0 0 400px;
  min-height: 0;
  /* No gap: the rule carries its own margins so it lands on an exact line. */
  gap: 0;
}

/* The same fixed band as the stage header, so the rule under it starts level
   with the top of the drawn canvas box. */
.side-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex: 0 0 auto;
  height: var(--head-h);
}

.score {
  display: flex;
  align-items: center;
  height: 100%;
  font-size: var(--fs-body);
  line-height: 1;
  margin: 0;
}

/* The rule is the right column's version of the canvas box's top edge, and
   the two have to read as one line across the page (260730). Both are drawn
   4px down inside an 8px SVG band (the rule) or 2px down inside the frame
   (squiggleRect's inset), so the rule is lifted by that 2px difference on
   top of the shared column gap. */
#sideRule {
  margin-top: calc(var(--col-gap) - 2px);
  margin-bottom: 4px;
}

.chat {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 12px;
  /* Clear of the rule at every browser zoom: the rule's squiggle has real
     amplitude and zoom rounding eats a 2px gap, so ascenders ran through the
     line again at some zoom levels. */
  margin-top: 8px;
}

.chat-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Breathing room INSIDE the scroll box: without it a scrolled line is
     clipped flush against the rule above and the input frame below. The
     right pad also keeps a highlighted line off the scrollbar. */
  padding: 8px 8px 8px 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.chat-line {
  position: relative;
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.3;
  word-break: break-word;
}

/* Gap between speakers rather than between lines: consecutive lines from one
   person are one block with one name on it. */
.chat-turn {
  margin-top: 10px;
}

/* A system line announces what comes NEXT (a turn, a reveal), so the air
   goes ABOVE it: it used to sit symmetric and read as a caption for the
   message before it. */
.chat-system {
  margin: 16px 0 5px;
  text-align: center;
}

/* The speaker's name is apparatus, not speech. */
.chat-who {
  display: block;
  margin-bottom: 1px;
  /* Above the highlight swipe on a winning line: a static element paints
     BELOW a positioned sibling in the same stacking context. */
  position: relative;
  z-index: 2;
}

/* A correct guess is highlighted, not recoloured: a third ink would break the
   one-ink rule, and the highlighter is already the game's "this one" mark. */
.chat-correct .sq-highlight {
  opacity: 1;
}

/* Host for the word-level swipe: the highlight is absolutely positioned, so
   the word needs its own box, and its text needs to sit above the swipe. */
.chat-word {
  position: relative;
  display: inline-block;
}

.chat-hint {
  margin: 0;
  flex: 0 0 auto;
}

.chat-input-row {
  position: relative;
  flex: 0 0 auto;
  color: var(--ink);
  margin: 0;
}

.chat-input {
  width: 100%;
  padding: 12px 14px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--hand);
  font-size: var(--fs-body);
  outline: none;
}

.chat-input::placeholder {
  color: var(--ink);
  opacity: 1;
}

/* ── gallery ────────────────────────────────────────────────────────────── */

/* The gallery must FIT, never scroll: the save button has to be reachable
   without hunting for it. The page is height-bounded and the cells are sized
   by the space left over, rather than the page growing to fit the cells. */
.gallery {
  display: flex;
  flex-direction: column;
  /* Everything on this page is centred as a block; the row NAMES read
     left-aligned because each row section shrinks to its drawings. */
  align-items: center;
  gap: 8px;
  width: min(1650px, 94%);
  height: 100vh;
  height: 100dvh;
  padding: 24px 0;
  overflow: hidden;
}

/* Desktop lost the gallery header to give the drawings its space (260729);
   mobile scrolls, so it keeps the title (and it is the tap home there). */
.gallery-title {
  display: none;
  flex: 0 0 auto;
  cursor: pointer;
}

.gallery-rows {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* #gallery with nothing saved yet: the invitation, centred where the
   drawings would be. */
.gallery-empty {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
}

.gallery-empty-msg {
  margin: 0;
  font-size: var(--fs-body);
  text-align: center;
}

/* The two rows share whatever the header and actions leave behind, and are
   squeezed before the actions are: the save button is the reason the screen
   exists. */
.gallery-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

.row-name {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 400;
  flex: 0 0 auto;
}

/* A flex row, not a 1fr grid: the drawings are height-driven (aspect ratio
   off the row height), so equal-width columns left a moat of slack around
   each one. Flex lets every cell hug its art and the row shrink to fit,
   which is what makes the centred sections read as one tight block. */
.cells {
  display: flex;
  justify-content: flex-start;
  gap: 10px;
  flex: 1 1 auto;
  min-height: 0;
  max-width: 100%;
}

.cells > .cell {
  flex: 0 1 auto;
  min-width: 0;
}

.cell {
  margin: 0;
  display: flex;
  flex-direction: column;
  /* Centred so the art takes its width from its aspect ratio rather than
     being stretched to the column, which is what lets height drive size. */
  align-items: center;
  gap: 4px;
  min-height: 0;
}

.cell-art {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  max-width: 100%;
  color: var(--ink);
}

/* A finished drawing opens as its own share tile. The affordance is the
   cursor (and the keyboard focus ring); a hover tint would be a second ink. */
.cell-art--click {
  cursor: pointer;
}

.cell-canvas {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--paper);
}

.cell-word {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  font-size: var(--fs-body);
  flex: 0 0 auto;
}

/* A guessed word wears the highlighter. */
.cell-word-text {
  position: relative;
  padding: 0 4px;
}

.cell-word-text.guessed .sq-highlight {
  opacity: 1;
}

.gallery-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  /* 26 (260729): the "draw!" header is gone, so the air it occupied is spent
     here, between the drawings and the buttons. */
  margin-top: 26px;
  flex: 0 0 auto;
}

/* A button now, not a link: it opens the download flow. */
.made-with {
  align-self: flex-end;
  position: relative;
  color: var(--ink);
  text-decoration: none;
  border: none;
  background: transparent;
  cursor: pointer;
  flex: 0 0 auto;
  padding: 0 0 2px;
}

.made-with .sq-underline {
  opacity: 1;
}

/* The home page's copy of the line sits in the page corner, not in a flow. */
.home-more {
  position: fixed;
  right: 20px;
  bottom: 16px;
  z-index: 10;
}

/* The how-to-play credit line and its link. A plain underline by request
   (260729): the credit is set in the typed face, and the squiggle read as a
   drawing under text that is not handwritten. */
.popup-inspired {
  display: inline-block;
  margin-top: 8px;
}

.popup-link {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ── popups ─────────────────────────────────────────────────────────────── */

.popup {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  /* A veil, not a tint: the page behind is already white, this just pushes
     it back far enough that the card reads as on top. */
  background: rgba(255, 255, 255, 0.88);
}

.popup-card {
  position: relative;
  width: min(575px, 92%);
  padding: 45px 38px 38px;
  text-align: center;
  background: var(--paper);
}

/* The language menu is a short list, not an announcement. */
.popup-card--menu {
  width: auto;
  min-width: 250px;
  padding: 28px 30px 22px;
}

.popup-title {
  margin: 0 0 12px;
}

.popup-x {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 40px;
  height: 40px;
  border: none;
  background: transparent;
  color: var(--ink);
  font-family: var(--hand);
  cursor: pointer;
  display: grid;
  place-items: center;
  z-index: 3;
}

.popup-x:hover .sq-highlight,
.popup-x:focus-visible .sq-highlight {
  opacity: 1;
}

/* The share preview: the tile itself, framed by the same pen. */
.popup-media {
  margin: 0 0 20px;
}

.popup-media-frame {
  position: relative;
}

.popup-img {
  display: block;
  width: 100%;
  height: auto;
}

.popup-body {
  margin: 0 0 22px;
  font-size: var(--fs-body);
  line-height: 1.4;
}

.popup-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

/* ── narrow screens: canvas above chat ──────────────────────────────────── */

@media (max-width: 900px) {
  .root {
    align-items: flex-start;
  }

  .game {
    flex-direction: column;
    height: auto;
    width: 94%;
    /* Clear the fixed corner buttons. */
    padding-top: 72px;
  }

  .canvas-frame {
    flex: 0 0 auto;
  }

  /* Width drives the box on a narrow screen; aspect-ratio turns it into a
     height. */
  .canvas-wrap {
    width: 100%;
    height: auto;
    max-height: none;
  }

  .side {
    flex: 1 1 auto;
    width: 100%;
  }

  .chat-list {
    min-height: 150px;
    max-height: 40vh;
  }

  .gallery {
    height: auto;
    min-height: 100vh;
    min-height: 100dvh;
    overflow: visible;
  }

  .gallery-row {
    flex: 0 0 auto;
    overflow: visible;
    align-self: stretch;
  }

  /* Width-driven on small screens, so the cells go back to equal columns:
     a flex row of height-driven cells has no height to drive here. */
  .cells {
    display: grid;
    grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
    gap: 14px;
  }

  .cell-art {
    width: 100%;
    flex: 0 0 auto;
  }
}

/* 768 is the whole mobile band (the JS desktop test is min-width: 769px), so
   the small-screen type scale applies across all of it. */
@media (max-width: 768px) {
  :root {
    --fs-title: 34px;
    --fs-body: 19px;
  }

  .doodle {
    height: 120px;
  }

  .doodle-mid {
    height: 32px;
    margin-bottom: 118px;
  }

  .pick-word {
    min-width: 140px;
    padding: 18px 20px;
  }

  .gallery-actions {
    flex-wrap: wrap;
  }
}

/* ── mobile: one-finger play (≤768px, matching the JS mobile band) ──────────
   Everything below ADDS to the ≤900px column rules; desktop (≥769px) never
   reaches any of it. */

@media (max-width: 768px) {
  :root {
    /* The apparatus voice steps down with the rest of the page. */
    --fs-typed: 13px;
  }

  /* No rubber-banding and no pull-to-refresh anywhere near a drawing finger. */
  html,
  body {
    overscroll-behavior: none;
  }

  .root {
    overscroll-behavior: none;
  }

  /* Notched phones (viewport-fit=cover): the fixed corners step inside the
     safe area instead of under it. */
  .corner-btns {
    top: max(14px, env(safe-area-inset-top));
    right: max(16px, env(safe-area-inset-right));
  }

  .home-more {
    right: max(20px, env(safe-area-inset-right));
    bottom: max(16px, env(safe-area-inset-bottom));
  }

  /* Landing and pick: centred while they fit, top-anchored and scrollable
     when they do not (auto margins beat align-items: flex-start and never
     push content off the top). */
  .setup,
  .pick {
    margin-top: auto;
    margin-bottom: auto;
  }

  .setup {
    padding: 64px 0 56px;
  }

  /* The desktop offset dance assumed a tall page; on a phone it clips. */
  .setup .title-big {
    margin-top: 0;
  }

  .setup .hand-btn {
    margin-top: 18px;
  }

  .pick {
    padding: 72px 0 48px;
  }

  /* Word choices stack full-width: three big thumb targets, not a wrap. */
  .pick-words {
    flex-direction: column;
    align-items: stretch;
    width: min(360px, 100%);
    gap: 14px;
  }

  .pick-word {
    min-width: 0;
    padding: 16px 20px;
  }

  /* ── the game is ONE viewport, never a scrolling page ──────────────────
     Canvas on top, chat at the bottom; the keyboard resizes the column
     (interactive-widget=resizes-content in the viewport meta). */
  .game {
    flex-direction: column;
    width: 100%;
    height: 100vh;
    height: 100dvh;
    padding: 10px 10px calc(6px + env(safe-area-inset-bottom));
    gap: 6px;
    overflow: hidden;
  }

  .stage {
    flex: 0 0 auto;
    gap: 6px;
  }

  .header {
    gap: 8px;
  }

  .round,
  .clock {
    min-width: 44px;
  }

  .canvas-box {
    flex: 0 0 auto;
    padding: 6px;
  }

  /* Width-bound ALWAYS: a max-height on an aspect-ratio box breaks the 10:7
     shape and puts the ink away from the finger (see canvas.js fitWrap), so
     the height cap is folded into the width. 240px is the rest of the
     column: header, toolbar, input and a floor of chat lines (the score row
     is hidden on mobile, its space deliberately left to the chat). */
  .canvas-wrap {
    width: min(100%, calc((100vh - 240px) * 10 / 7));
    width: min(100%, calc((100dvh - 240px) * 10 / 7));
    height: auto;
    max-height: none;
    margin: 0 auto;
  }

  .toolbar,
  .toolbar-spacer {
    height: 52px;
  }

  .tool {
    width: 46px;
    height: 46px;
  }

  /* The chat takes whatever the paper leaves: the last few lines visible,
     the rest a scroll away, the input always at the bottom. The score row and
     its rule are desktop furniture (260729): hiding them buys the chat their
     lines back. */
  .side {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    gap: 2px;
  }

  .side-head,
  #sideRule {
    display: none;
  }

  .chat {
    margin-top: 2px;
    gap: 8px;
  }

  .chat-list {
    min-height: 0;
    max-height: none;
    padding: 4px 4px 4px 0;
    /* The chat scrolls, the page never does: overscroll stops here instead of
       chaining into a pull-to-refresh. */
    overscroll-behavior: contain;
  }

  .chat-system {
    margin: 10px 0 4px;
  }

  .chat-input {
    padding: 8px 12px;
  }

  /* ── gallery: two columns side by side, yours and the AI's ─────────────
     Each desktop ROW turns into a COLUMN of drawings. */
  .gallery {
    padding: 18px 0 20px;
    gap: 4px;
  }

  .gallery-title {
    display: block;
  }

  .gallery-rows {
    flex-direction: row;
    align-items: flex-start;
    justify-content: center;
    align-self: stretch;
    gap: 16px;
  }

  .gallery-row {
    flex: 1 1 0;
    min-width: 0;
    align-self: auto;
    margin-top: 0;
  }

  .row-name {
    text-align: center;
  }

  .cells {
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
  }

  /* The empty state is the row area's only child: give it the full width. */
  .gallery-empty {
    flex: 1 1 auto;
    align-self: stretch;
  }

  .gallery-actions {
    justify-content: center;
    margin-top: 18px;
  }

  /* ── popups: fit small screens, scroll only if they must ─────────────── */
  .popup {
    padding: 12px;
  }

  .popup-card {
    width: min(575px, 100%);
    padding: 38px 22px 26px;
    max-height: calc(100vh - 24px);
    max-height: calc(100dvh - 24px);
    overflow-y: auto;
  }
}
