/* Hangman — Play Hub sibling. Two modes on ONE finger-drawing sheet:
   Whiteboard (freehand) and Game (tap the top-right alphabet to guess; every
   wrong guess DRAWS the next piece of the hangman on the locked layer itself).
   Standalone static page; the
   conventions live in ../_GAMES_CONTRACT.md.

   Theme tokens come from css/gamekit.css (body.gk-theme aliases --brand,
   --accent, --green, … to the bright-green pitch palette), so this file
   declares NO :root block and no slate. Orange stays the primary CTA colour
   (GameKit's .gk-next) and is used nowhere else. */

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font: 16px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--ink, #16291c);
  -webkit-text-size-adjust: 100%;
}

.app {
  max-width: 640px; margin: 0 auto; min-height: 100vh;
  padding: 10px 16px 24px;
  display: flex; flex-direction: column; gap: 10px;
}
/* Landscape tablet (≈1194x834): use the width, so the alphabet panel can stay
   in the top-right corner without ever reaching the hangman area on the left. */
@media (min-width: 900px) { .app { max-width: 1180px; } }

.hm-loading {
  text-align: center; color: var(--brand-2); font-weight: 700; padding: 28px 0;
}

/* ---------------- start screen: the mode picker ---------------- */
.hm-ask {
  margin: 6px 0 0; text-align: center;
  font-size: 24px; font-weight: 800; color: var(--brand);
}
.hm-mascot-mid { display: flex; justify-content: center; }
.hm-mascot-mid .gk-mascot { padding: 6px 14px; }
.hm-mascot-mid .gk-mascot-emoji { font-size: 38px; }

.hm-modes { display: grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 720px) { .hm-modes { grid-template-columns: 1fr 1fr; } }

.hm-mode {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; min-height: 118px; padding: 16px 14px;
  font: inherit; color: var(--brand); text-align: center; cursor: pointer;
  background: var(--card); border: 3px solid transparent; border-bottom: 4px solid var(--gk-pitch);
  border-radius: var(--radius); box-shadow: var(--shadow);
  transition: transform .12s cubic-bezier(.3, 1.4, .5, 1);
}
.hm-mode:active { transform: scale(.97); }
.hm-mode .ico { font-size: 40px; line-height: 1; }
.hm-mode .nm { font-size: 21px; font-weight: 800; }
.hm-mode .ds { font-size: 14px; font-weight: 600; color: var(--brand-2); }
.hm-mode[disabled] { opacity: .5; }

.hm-secret {
  align-self: center; min-height: 48px; padding: 10px 18px;
  font: inherit; font-size: 15px; font-weight: 800; color: var(--brand-2);
  background: #fff; border: 2px solid rgba(20, 83, 45, .3); border-radius: 999px;
  cursor: pointer;
}
.hm-secret:active { transform: scale(.97); }

/* ---------------- secret word form (2 players) ---------------- */
.hm-form {
  background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 16px; display: flex; flex-direction: column; gap: 12px;
}
.hm-field { display: block; }
.hm-field span {
  display: block; font-size: 14px; font-weight: 800; color: var(--brand); margin-bottom: 6px;
}
.hm-field input {
  width: 100%; min-height: 54px; padding: 12px 14px;
  font: inherit; font-size: 18px; font-weight: 700; color: var(--ink);
  background: #fff; border: 2px solid var(--gk-green-700); border-radius: 12px;
  text-transform: uppercase;
}
.hm-field--hint input { text-transform: none; font-size: 16px; font-weight: 600; }
.hm-err { color: var(--danger); font-weight: 800; margin: 0; min-height: 20px; }

/* ---------------- the strip (pills + mascot) ---------------- */
.hm-strip { position: relative; display: flex; align-items: center; justify-content: center; min-height: 38px; }
.hm-note { margin: 0; font-size: 15px; font-weight: 700; color: var(--brand-2); text-align: center; }

/* ---------------- touch toolbar -------------------------------------------------
   DOM buttons ABOVE the sheet (never overlapped by a canvas): 8 pen colours
   (min 48px each, the active one ringed), the eraser, undo and clear — plus the
   screen buttons (Modes / New sheet), which share this one container so the
   phone layout can re-line them up as two compact rows (see the portrait-phone
   block at the end of this file). */
.hm-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.hm-btn {
  display: inline-flex; align-items: center; gap: 8px; min-height: 48px; padding: 8px 14px;
  font: inherit; font-size: 15px; font-weight: 800; color: var(--brand);
  background: #fff; border: 2px solid rgba(20, 83, 45, .3); border-radius: 14px;
  box-shadow: 0 2px 8px rgba(20, 83, 45, .10); cursor: pointer;
  transition: transform .12s cubic-bezier(.3, 1.4, .5, 1);
}
.hm-btn:active { transform: scale(.95); }
.hm-btn .ico { font-size: 20px; line-height: 1; }

/* The toolbar: one flex row container, so the screens buttons and the pens can
   share the lines the phone layout needs. On tablets/desktops `.hm-br` (a
   0-height item with flex-basis:100%) forces the line break that keeps EXACTLY
   the two rows this game has always had: [Modes, New sheet] then the pens.
   row-gap is 5px because that break line counts twice (half above, half below),
   so the two visual rows are still 10px apart — like the two DOM rows were. */
.hm-tools { display: flex; flex-wrap: wrap; column-gap: 8px; row-gap: 5px; align-items: center; }
.hm-br { flex: 0 0 100%; width: 100%; height: 0; margin: 0; padding: 0; }

.hm-swatch {
  width: 48px; height: 48px; min-width: 48px; padding: 0; cursor: pointer;
  border: 3px solid #fff; border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(20, 83, 45, .28), 0 2px 6px rgba(20, 83, 45, .22);
  -webkit-tap-highlight-color: transparent;
}
.hm-swatch:active { transform: scale(.94); }
.hm-swatch.is-active {
  box-shadow: 0 0 0 4px var(--gk-green), 0 4px 12px rgba(20, 83, 45, .34);
  transform: scale(1.06);
}

.hm-tool {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-width: 48px; min-height: 48px; padding: 8px 12px;
  font: inherit; font-size: 15px; font-weight: 800; color: var(--brand);
  background: #fff; border: 2px solid rgba(20, 83, 45, .3); border-radius: 14px;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: transform .12s cubic-bezier(.3, 1.4, .5, 1);
}
.hm-tool .ico { font-size: 20px; line-height: 1; }
.hm-tool:active { transform: scale(.95); }
.hm-tool.is-active {
  background: #eefaf1; border-color: var(--gk-green);
  box-shadow: inset 0 0 0 2px var(--gk-green);
}
.hm-tool--wide { flex: 1 1 auto; }

/* ---------------- the sheet: TWO canvases + a mark layer --------------------
   z-order (this is the whole layering contract of the game):
     1 .hm-cv--bg   the LOCKED background layer: gallows, alphabet panel, word
                    blanks — painted from the model, never erased by the pen.
     2 .hm-mark     transparent DOM layer that GameKit.mark() paints green over
                    the blanks; pointer-events:none, so it never eats a stroke.
     3 .hm-cv--pen  the stroke layer on top — the only element that takes input. */
.hm-sheet {
  position: relative; width: 100%; overflow: hidden; touch-action: none;
  background: #fffdf6;                     /* the worksheet paper */
  border-radius: 14px; box-shadow: var(--shadow);
  border: 1px solid rgba(20, 83, 45, .14);
}
.hm-cv { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: block; }
.hm-cv--bg { z-index: 1; }
.hm-cv--pen { z-index: 3; touch-action: none; pointer-events: auto; cursor: crosshair; }

.hm-mark { position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 2; pointer-events: none; }
.hm-blank-mark { position: absolute; display: none; border-radius: 12px; }
/* GameKit.mark adds .gk-correct/.gk-reveal with `background:#eefaf1 !important`,
   which would hide the word the child just revealed (the letters are painted on
   the bg canvas UNDER this layer). Two classes beat one, so the shared green
   look is kept but made translucent — the letters stay readable. */
.hm-blank-mark.gk-correct,
.hm-blank-mark.gk-reveal { background: rgba(46, 158, 79, .14) !important; }
.hm-blank-mark.gk-correct,
.hm-blank-mark.gk-reveal,
.hm-blank-mark.gk-wrong { border: 3px solid transparent; }

/* ---------------- guess feedback overlays (Game mode) -----------------------
   ONE topmost overlay div (`.hm-fx`, z-index 4 -> above the stroke canvas)
   carries the SHORT feedback of a letter guess: the cell pop-flash, the letter
   flying to the blank it revealed, the red ✗ + shake of a miss and the
   "The HEAD appears!" chip. It is aria-hidden and pointer-events:none — the pen
   canvas underneath stays the only input surface, and nothing here is a canvas.
   Every effect removes itself (timer + animation/transition end), so fast
   tapping can never stack them. */
.hm-fx {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  z-index: 4; pointer-events: none; overflow: hidden;
}

/* the tapped cell: green ring + scale pop on a hit, red flash + ✗ + shake on a miss */
.hm-fx-cell {
  position: absolute; pointer-events: none; border-radius: 14px;
  animation: hmCellOk .46s cubic-bezier(.3, 1.4, .5, 1) both;
}
.hm-fx-cell--ok {
  background: rgba(46, 158, 79, .22);
  box-shadow: inset 0 0 0 3px var(--gk-green), 0 0 0 3px rgba(46, 158, 79, .40);
}
.hm-fx-cell--no {
  background: rgba(210, 67, 77, .20);
  box-shadow: inset 0 0 0 3px var(--gk-danger), 0 0 0 3px rgba(210, 67, 77, .40);
  animation: hmCellNo .36s ease-out both, hmCellShake .36s ease-in-out both;
}
.hm-fx-cell .mk {
  position: absolute; right: -6px; top: -9px;
  min-width: 22px; height: 22px; padding: 0 4px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 900; line-height: 1; color: #fff;
  box-shadow: 0 2px 6px rgba(20, 83, 45, .22);
}
.hm-fx-cell--ok .mk { background: var(--gk-green); }
.hm-fx-cell--no .mk { background: var(--gk-danger); }
@keyframes hmCellOk {
  0%   { opacity: 0; transform: scale(.72); }
  45%  { opacity: 1; transform: scale(1.16); }
  100% { opacity: 0; transform: scale(1.04); }
}
@keyframes hmCellNo {
  0%   { opacity: 0; }
  20%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes hmCellShake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-4px); }
  75%      { transform: translateX(4px); }
}

/* the letter that flies from the tapped cell to the blank it just revealed */
.hm-fx-fly {
  position: absolute; pointer-events: none;
  transform: translate(-50%, -50%);
  font-weight: 900; line-height: 1; color: var(--gk-green);
  opacity: .95;
  transition: left .40s cubic-bezier(.3, 1.25, .5, 1),
              top .40s cubic-bezier(.3, 1.25, .5, 1),
              opacity .42s ease-in,
              font-size .40s ease-out;
}
.hm-fx-fly--go { opacity: 0; font-size: 20px; }

/* the "The HEAD appears! (n of 6)" chip over the banner line: pops in and stays
   until the next guess replaces it. It is the SINGLE render of that line (the
   canvas banner suppresses its copy while the chip is up), so the chip must be
   fully SOLID: an opaque theme background + a solid border cover every pixel
   the locked layer repainted underneath it — nothing may show through. */
.hm-fx-chip {
  position: absolute; pointer-events: none;
  padding: 6px 12px; border-radius: 999px;
  background: var(--gk-card, #fff);             /* opaque (never rgba/transparent) */
  color: var(--gk-danger); border: 2px solid var(--gk-danger);
  font-size: 15px; font-weight: 800; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  box-shadow: 0 4px 12px rgba(20, 83, 45, .16);
  animation: hmChipIn .30s cubic-bezier(.3, 1.4, .5, 1) both;
}
@keyframes hmChipIn {
  0%   { opacity: 0; transform: translateY(9px) scale(.9); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* ONE short shake of the whole sheet on a miss. The offset is deliberately tiny
   (±2px) and short (~180ms): pos() re-reads getBoundingClientRect() on every
   pointer event, so drawing coordinates stay correct while it wobbles. */
.hm-sheet.hm-fx-shake { animation: hmSheetShake .18s ease-in-out both; }
@keyframes hmSheetShake {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-2px); }
  60%      { transform: translateX(2px); }
}

/* ---------------- end-of-round block ---------------- */
.hm-end {
  background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 16px; display: flex; flex-direction: column; gap: 10px; text-align: center;
}
.hm-end h2 { margin: 0; font-size: 22px; color: var(--brand); }
.hm-end p { margin: 0; font-size: 16px; font-weight: 700; color: var(--brand-2); }
.hm-end p b { color: var(--brand); }

/* ---------------- shared mascot + album (js/gamekit-stickers.js) ----------------
   Same pattern as the other contract siblings: the mascot rides in the strip row
   and is ABSOLUTELY positioned in its right-hand gap, so the row keeps the pills'
   height and the chip adds NO scroll height. Its bubble opens to the LEFT and is
   clamped to the viewport, so it can neither run off-screen nor cover the header. */
.gk-mascot-host {
  position: absolute; top: 50%; right: 0; transform: translateY(-50%);
  display: flex; align-items: center; z-index: 6; pointer-events: none;
}
.gk-mascot-host .gk-mascot { padding: 3px 8px; }
.gk-mascot-host .gk-mascot-emoji { font-size: 26px; }
.gk-mascot-host .gk-bubble {
  left: auto; right: 0; top: calc(100% + 8px); bottom: auto; transform: none;
  width: max-content; max-width: min(240px, calc(100vw - 24px));
  overflow-wrap: normal; font-size: 14px;
}
.gk-mascot-host .gk-bubble::after {
  left: auto; right: 20px; margin: 0; top: auto; bottom: 100%;
  border-color: transparent; border-bottom-color: var(--gk-green);
}
.gk-mascot-host .gk-bubble--show { animation: none; }
@media (max-width: 640px) { .gk-mascot-host .gk-mascot-name { display: none; } }
@media (max-width: 360px) {
  .gk-mascot-host .gk-mascot { padding: 1px 2px; }
  .gk-mascot-host .gk-mascot-emoji { font-size: 18px; }
}

/* ---------------- portrait phones: ONE screen, no scrolling -----------------
   The real-device bug this fixes: at 360x740 the chrome eaten ABOVE the sheet
   (header 60 + note strip + two 48px toolbar rows + full-width clear) left the
   stacked sheet ~366px of a 666px need, so the page scrolled under the child's
   finger. Everything below is confined to the SAME condition the game's own
   compact geometry uses (matchMedia 'max-width 640 + portrait'), so the canvas,
   the tap hit-test and this chrome always agree.
   Budget at 360x740, worst case (Game mode, 5 pills row):
     8 (app padding-top) + 60 (GameKit header, frozen) + 3x8 gaps + 38 (strip)
     + 88 (toolbar: 38 swatch row + 6 row-gap + 44 button row) = 218 above the
     sheet; the sheet then gets 740 - 218 - 12 = 510px, while the compact
     geometry needs only 490 (10 PAD + 236 panel + 10 + 224 gallows + 10).
     218 + 490 + 12 = 720 <= 740.  390x844 has 126px to spare. */
@media (max-width: 640px) and (orientation: portrait) {
  /* GameKit pins body.gk-theme to 100vh; on a phone with a visible URL bar that
     is the LARGE viewport, so the page would keep a stray scrollbar. dvh is the
     height that is really on screen. */
  body.gk-theme {
    min-height: 100vh;      /* fallback for browsers without dvh */
    min-height: 100dvh;
  }
  .app {
    padding: 8px 12px 12px; gap: 8px; max-width: none;
    min-height: 100vh;
    min-height: 100dvh;
  }
  .hm-ask { font-size: 20px; }
  .hm-mascot-mid .gk-mascot-emoji { font-size: 30px; }
  .hm-mode { min-height: 96px; padding: 12px 10px; gap: 4px; }
  .hm-mode .ico { font-size: 32px; }
  .hm-mode .nm { font-size: 19px; }
  .hm-strip { min-height: 34px; }

  /* The Whiteboard hint is rendered ONCE on a phone: the `@media
     (max-width: 640px)` block below takes the strip's copy out of the flow at
     every width the fix covers, so the CANVAS banner is the single visible one. */

  /* toolbar: two compact rows — the 8 swatches, then the five buttons */
  .hm-tools { column-gap: 4px; row-gap: 6px; }
  .hm-br { display: none; }                     /* the tablet line break is off: order does it */
  .hm-swatch {
    order: 1;
    width: min(38px, calc((100% - 28px) / 8));  /* 8 swatches in ONE row, 360px wide */
    height: 38px; min-width: 0; border-width: 2px;
  }
  @supports (aspect-ratio: 1 / 1) {
    .hm-swatch { height: auto; aspect-ratio: 1 / 1; }
  }
  .hm-tools .hm-btn,
  .hm-tools .hm-tool { order: 2; min-width: 44px; min-height: 44px; padding: 0 8px; border-radius: 12px; }
  /* At this size the emoji carries the meaning: the label stays in the DOM (and
     in the button's aria-label) but is off screen, so five buttons fit one row. */
  .hm-tools .hm-btn span:not(.ico),
  .hm-tools .hm-tool span:not(.ico) { display: none; }
  /* The eraser and clear-my-drawing buttons shipped the SAME broom glyph (their
     labels were the only difference): give clear its own bin icon, css only. */
  .hm-tools .hm-tool[data-act="clear"] .ico { display: none; }
  .hm-tools .hm-tool[data-act="clear"]::before { content: "\1F5D1\FE0F"; font-size: 20px; line-height: 1; }

  /* the sheet FLEXES into the height that is left; the canvas + its geometry are
     re-derived from the STROKES model on every resize, so the ink survives */
  .hm-sheet { flex: 1 1 auto; min-height: 0; }

  /* The mascot's speech bubble (Mascot.say() -> .gk-bubble, auto-hidden after
     2.5s). Anchored just under the chip it opens LEFT, which on a phone dropped
     its rect straight onto the pen swatches (measured at 360x740:
     [201,108,346,146] over 4 of the 8 colours) and, for a longer line, onto the
     tool buttons under them.
     On a compact portrait screen the bubble hangs LOWER instead, on the sheet's
     DRAWING area — the one band of the sheet that holds no tap target: the
     alphabet panel is pinned top-right (its cells end at sheet y 246, viewport
     y 464) and every pen/tool control lives ABOVE the sheet.
       offset = the drawing area's top (chrome 218 + gallows box 256 + the banner
                strip, at most 30, + 8 = 512) minus the chip's bottom edge (the
                34px strip sits at y 76, so a 32px chip centred in it ends at
                ~102-113) = ~410
     Only the vertical anchor moves: the host rule keeps right: 0 / left: auto, so
     the bubble still hangs under the mascot and its tail still points UP at it.
     A position:fixed copy was measured first and rejected: .gk-mascot and
     .gk-mascot-host both carry a transform (centring + the greet bounce) and a
     transformed ancestor becomes the containing block of a fixed child, which
     threw the bubble off-screen. 60vw keeps the line inside the viewport on the
     narrowest phones. The auto-hide, the opaque card look and the tail are
     untouched, and ALL of this lives inside the media query, so tablet and
     landscape keep the original mascot-hugging anchor byte for byte. */
  .hm-strip .gk-mascot-host .gk-bubble {
    top: calc(100% + 410px); bottom: auto;
    max-width: min(60vw, calc(100vw - 24px));
  }
}

/* ---------------- phones: the Whiteboard hint renders ONCE ------------------
   The reported bug at 360x740 (measured in a real browser against THIS repo):
   the Whiteboard strip's note ('Free drawing — invent your own game!') is
   centred across the whole strip while the mascot chip is pinned to that row's
   right edge, so the tail of the line ran UNDER the chip — the note's box ended
   at x 346, the chip starts at x 318, i.e. the last ~28px were hidden. The SAME
   line is also printed by the sheet itself on its banner strip
   (bannerText() -> t('wbHint'), below the alphabet) — the very place Game mode
   prints 'Tap a letter to guess the word' — so the hint appeared TWICE and one
   of the two copies was clipped.
   Fix: at every width <= 640px the strip's copy leaves the flow (the classic
   1px clipped 'sr-only' pattern, NOT display:none). It therefore cannot overlap
   the chip whatever the font does, it adds no strip height (the phone chrome
   budget in the block above still holds at 34px), and the line stays in the
   accessibility tree — the canvas banner is a bitmap, so it is invisible to a
   screen reader; the sr-only node is the ONLY accessible copy of the hint. The
   CANVAS banner is the single VISIBLE render, in the same spot in both modes,
   where no control can cover it.
   Scope: Game mode renders no .hm-note at all, so this can only ever touch the
   Whiteboard strip; and because this is a STANDALONE query (not the compact
   portrait block), it also covers a <=640px landscape window, while >= 641px
   (tablet + desktop) keeps the original centred row byte for byte. */
@media (max-width: 640px) {
  .hm-strip .hm-note {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    border: 0; overflow: hidden; white-space: nowrap;
    clip: rect(0, 0, 0, 0); clip-path: inset(50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hm-mode, .hm-btn, .hm-tool, .hm-swatch { transition: none !important; }
  /* every NEW guess effect loses its motion: the cell keeps its colour tint and
     the chip still appears, but nothing scales, shakes, flies or slides. */
  .hm-fx-cell, .hm-fx-cell--ok, .hm-fx-cell--no, .hm-fx-fly, .hm-fx-chip,
  .hm-sheet.hm-fx-shake {
    animation: none !important;
    transition: none !important;
    transform: none !important;
  }
  /* the static variants the js adds when matchMedia says reduced motion */
  .hm-fx-static { opacity: 1; }
}
