    /* ===== v22 — care screen chrome: ONE pet pill + full-screen menu =====
       The global topbar and the bottom tab grid leave the care screen.
       What stays: a single pill (pet face + name + sticker count) in
       flow at the panel's bottom edge — thumb reach, >=56px tall.
       Tapping it opens ONE full-screen menu: a settings row (home /
       language cycle / sound / close) over a grid of big game tiles.
       All freed height goes to the stage and the mode panels. */
    body.on-pet .topbar { display: none; }
    body.on-pet .langbar { display: none; }
    #screen-pet .pet-pill {
      /* margin-top:auto pins the pill to the panel's bottom edge on
         tall screens (the freed tab-bar space collects above it);
         with no free space it collapses to 0 and the mode section's
         own bottom padding keeps the separation. */
      /* v93: the pill is the child's lifeline back to the menu —
         it paints ABOVE any panel overflow, never under it. */
      position: relative; z-index: 30;
      flex: 0 0 auto; margin-top: auto; min-height: 66px; width: 100%;
      display: flex; align-items: center; gap: 10px; text-align: left;
      padding: 6px 14px 6px 8px; border-radius: 999px;
      border: 3px solid var(--sun); background: linear-gradient(180deg, #ffffff, #fff6d8);
      box-shadow: var(--shadow); color: var(--ink);
      -webkit-tap-highlight-color: transparent;
    }
    #screen-pet .pet-pill:active { transform: translateY(2px); box-shadow: none; }
    #screen-pet .pet-pill.pop { animation: pop .45s ease; }
    #screen-pet .pill-face {
      position: relative; width: 54px; height: 54px; flex: 0 0 auto;
      border-radius: 50%; overflow: hidden; border: 2.5px solid #fff;
      background: linear-gradient(180deg, #aee9ff 0%, #c8f2ff 55%, #93e77e 56%, #63d64f 100%);
      box-shadow: 0 2px 5px rgba(35, 50, 70, .18);
    }
    #screen-pet .pill-face > svg { width: 100%; height: 100%; display: block; }
    #screen-pet .pill-face.has-live > svg { display: none; }
    #screen-pet .pill-mid { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0; }
    #screen-pet .pill-name { font-size: 19px; font-weight: 900; line-height: 1.15; white-space: nowrap; overflow: hidden; }
    #screen-pet .pill-hint { font-size: 13px; font-weight: 700; color: var(--ink-soft); line-height: 1.2; white-space: nowrap; overflow: hidden; }
    #screen-pet .pill-stars {
      flex: 0 0 auto; font-size: 19px; font-weight: 900; color: var(--ink);
      background: #fff3bf; border: 2.5px solid #ffd43b; border-radius: 999px; padding: 4px 12px;
    }
    .menu-overlay {
      position: fixed; inset: 0; z-index: 80; overflow: hidden;
      display: flex; flex-direction: column;
      padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
      /* v93 addendum 5 (bi 23:01): ONE menu mechanism — the overlay
         is ALWAYS the live yard (nav.js guarantees .yard-on by
         construction: natural fit, slimmed stage, or stepped-down
         tiles). The painted scene replica that used to back the
         non-yard regime is DELETED: no entry path (first open,
         game exit, resize, chip change) can render a stacked
         menu any more, because that rendering no longer exists. */
      background: transparent;
    }
    .menu-overlay[hidden] { display: none; }
    /* v47: tap-through guard window right after opening (see nav.js) */
    .menu-overlay.just-opened { pointer-events: none; }
    .menu-head { display: flex; gap: 8px; align-items: stretch; flex: 0 0 auto; }
    .menu-set {
      flex: 1 1 0; min-width: 0; min-height: 56px; border-radius: 999px;
      display: inline-flex; align-items: center; justify-content: center; gap: 6px;
      background: #fff; border: 3px solid var(--line); box-shadow: var(--shadow);
      font-size: 17px; font-weight: 900; color: var(--ink); padding: 6px 8px;
      -webkit-tap-highlight-color: transparent;
    }
    .menu-set:active { transform: translateY(2px); box-shadow: none; }
    .menu-set .ms-ico { font-size: 24px; line-height: 1; }
    #menuLangCur { white-space: nowrap; }
    .menu-close {
      width: 56px; height: 56px; flex: 0 0 auto; border-radius: 50%;
      border: 3px solid var(--coral); background: #fff; color: var(--coral-dark);
      font-size: 24px; font-weight: 900; line-height: 1; cursor: pointer;
      box-shadow: var(--shadow); -webkit-tap-highlight-color: transparent;
    }
    .menu-close:active { transform: translateY(2px); box-shadow: none; }
    /* ===== v44: flat menu — content chips + one frame grid =====
       The chip row picks the CONTENT (question data); the grid is
       the frame set. Everything fits ONE screen with no scrolling:
       chips wrap to two compact rows, tiles flex to fill the rest.
       The mode tiles live in #menuTilePool (hidden) and are MOVED
       into the grid once by nav.js — never cloned. */
    .menu-layer { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
    .menu-layer[hidden] { display: none; }
    .menu-chips {
      flex: 0 0 auto; width: 100%; max-width: 640px; margin: 10px auto 0;
      display: flex; flex-wrap: wrap; gap: 6px;
    }
    .menu-chip {
      flex: 1 1 17%; min-width: 0; min-height: 46px; padding: 4px 3px;
      border-radius: 999px; cursor: pointer; white-space: nowrap; overflow: hidden;
      border: 3px solid var(--line); background: #fff; box-shadow: var(--shadow);
      font-size: 14.5px; font-weight: 900; color: var(--ink);
      display: inline-flex; align-items: center; justify-content: center; gap: 3px;
      -webkit-tap-highlight-color: transparent;
    }
    .menu-chip .mc-ico { font-size: 19px; line-height: 1; }
    .menu-chip.active { background: var(--sun); border-color: var(--sun-dark); color: #5b3a00; }
    .menu-chip:active { transform: translateY(2px); box-shadow: none; }
    /* ===== v56 (Addendum 6): two visually distinct menu levels =====
       The CONTENT filter lives on its own tinted strip with a label
       and small, light, flat pills; the GAMES stay big shadowed
       cards under their own label, separated by a dotted boundary.
       Presentation only — chip/tile behaviour is untouched. */
    .menu-zone-learn {
      flex: 0 0 auto; width: 100%; max-width: 640px; margin: 8px auto 0;
      background: linear-gradient(180deg, rgba(116, 192, 252, .30), rgba(116, 192, 252, .16));
      border: 2px solid rgba(34, 139, 230, .35); border-radius: 20px;
      padding: 5px 7px 7px;
    }
    .menu-zone-label {
      display: flex; align-items: center; gap: 5px;
      width: 100%; max-width: 640px; margin: 0 auto;
      font-size: 12.5px; font-weight: 900; letter-spacing: .04em;
      color: #1864ab; line-height: 1.05; white-space: nowrap;
    }
    .menu-zone-label .mz-ico { font-size: 14px; line-height: 1; }
    .menu-zone-learn .menu-chips { margin-top: 4px; max-width: none; gap: 5px; }
    .menu-zone-learn .menu-chip {
      min-height: 37px; border-width: 2px; box-shadow: none;
      background: rgba(255, 255, 255, .82); font-size: 13.5px;
    }
    .menu-zone-learn .menu-chip.active { background: var(--sun); border-color: var(--sun-dark); box-shadow: 0 2px 0 rgba(90, 60, 0, .25); }
    .menu-zone-play { margin-top: 8px; border-top: 3px dotted rgba(24, 100, 171, .35); padding-top: 5px; }
    .menu-zone-play .menu-zone-label { color: #a61e4d; }
    .menu-zone-play .menu-grid { margin-top: 5px; }
    @media (min-height: 701px) {
      .menu-zone-play .menu-grid { gap: 9px; }
    }
    @media (min-width: 700px) {
      .menu-zone-learn { max-width: 880px; }
      .menu-zone-label { max-width: 880px; }
    }
    /* the strip chips follow the same height tiers as the plain
       chips — the zone rules above outrank the tier rules, so each
       tier restates them or short screens overflow (v56 regression:
       2 tiles pushed out at 360x740 / 360x474) */
    
    
    
    /* v48: was grid-auto-rows:1fr + align-content:stretch — with few
       tiles under a chip the rows stretched to the full panel height
       (the Clock chip's 3 games became three screen-high columns).
       The grid now hugs the top, rows size to the tile step, and any
       leftover space stays empty below the grid. */
    #menuModeGrid { grid-auto-rows: auto; align-content: start; }
    #menuModeGrid .menu-tile .mt-ico { font-size: 38px; }
    /* v51 fit-first bonus (user principle, from the v48 QA photo):
       at phone widths the longest chip labels ("Tổng hợp") sat tight
       against the pill edges — shrink the chip type a step instead
       of letting it crowd or clip. */
    @media (max-width: 400px) {
      .menu-chip { font-size: 12.5px; padding: 4px 2px; gap: 2px; }
      .menu-chip .mc-ico { font-size: 16px; }
    }
    
    .menu-grid {
      flex: 0 1 auto; min-height: 0; width: 100%; max-width: 640px; margin: 10px auto 0;
      display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px;
    }
    .menu-tile {
      min-height: 56px; border-radius: 22px; border: 3px solid var(--line);
      background: linear-gradient(180deg, #ffffff, #f2fbff);
      box-shadow: 0 4px 0 rgba(35, 50, 70, .12); color: var(--ink);
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      gap: 3px; padding: 8px 4px; cursor: pointer; min-width: 0;
      -webkit-tap-highlight-color: transparent;
    }
    .menu-tile:active { transform: translateY(3px); box-shadow: none; }
    .menu-tile .mt-ico { font-size: 44px; line-height: 1.05; }
    .menu-tile .mt-lab {
      font-size: clamp(13px, 4vw, 18px); font-weight: 800; line-height: 1.16;
      text-align: center; max-width: 100%;
    }
    /* v59: .active must be COMPLETE on its own — white text needs a
       coloured background at EVERY position. The nth-child rules
       below only cover positions 1–12; tiles past that (the menu has
       20) fell back to the near-white base gradient and their white
       labels vanished. This default background/border applies
       wherever no nth-child rule wins (they outrank it by
       specificity), so adding tile 21+ can never re-break it. */
    .menu-tile.active { color: #fff; background: #5c7cfa; border-color: #4263eb; }
    .menu-grid .menu-tile:nth-child(1).active { background: var(--coral); border-color: var(--coral-dark); }
    .menu-grid .menu-tile:nth-child(2).active { background: var(--teal); border-color: var(--teal-dark); }
    .menu-grid .menu-tile:nth-child(3).active { background: var(--sky); border-color: var(--sky-dark); }
    .menu-grid .menu-tile:nth-child(4).active { background: #a55eea; border-color: #8848c9; }
    .menu-grid .menu-tile:nth-child(5).active { background: #ff7bac; border-color: #e5558c; }
    .menu-grid .menu-tile:nth-child(6).active { background: #fd7e35; border-color: #e8590c; }
    .menu-grid .menu-tile:nth-child(7).active { background: #1098ad; border-color: #0b7285; }
    .menu-grid .menu-tile:nth-child(8).active { background: #37b24d; border-color: #2b8a3e; }
    .menu-grid .menu-tile:nth-child(9).active { background: #e64980; border-color: #c2255c; }
    .menu-grid .menu-tile:nth-child(10).active { background: #7048e8; border-color: #5f3dc4; }
    .menu-grid .menu-tile:nth-child(11).active { background: #f08c00; border-color: #d9480f; }
    .menu-grid .menu-tile:nth-child(12).active { background: #0ca678; border-color: #087f5b; }
    
    /* narrow phones: the home button goes icon-only so its label can
       never wrap over the icon (full-display rule — shrink, not clip) */
    @media (max-width: 600px) {
      #menuHomeBtn .ms-lab { display: none; }
    }
    /* v47 (real-device QA): the flat menu must fit EVERY viewport the
       kid area can appear in. The grid rows are 1fr, but each tile has
       a content minimum (icon + label + padding); on short screens
       (landscape, small phones) the minimums summed past the panel
       and the overlay's overflow:hidden clipped whole rows of games
       away — up to 10 of 19 tiles unreachable, no scrolling allowed.
       Fix on both axes: more columns when wide, and step the chrome
       and tile content down as the height shrinks (fit-first). */
    @media (min-width: 700px) {
      .menu-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); max-width: 880px; }
      .menu-chips { max-width: 880px; }
    }
    
    
    /* v48: tile size steps by visible-tile count (nav.js sets sz-m /
       sz-l on the grid; no class = sz-s for 9+ tiles). One uniform
       height per step, hard-capped — a chip with a single game shows
       one honest tile at the top of the grid, never a screen-high
       column. The max-height bands scale every step down so even
       the full 19-tile grid still fits one screen (fit-first). */
    #menuModeGrid .menu-tile { height: 64px; }
    #menuModeGrid.sz-m .menu-tile { height: 88px; }
    #menuModeGrid.sz-l .menu-tile { height: 108px; }
    #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 44px; }
    #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 54px; }
    #menuModeGrid.sz-l .menu-tile .mt-lab { font-size: 18px; }
    
    
    /* v48 band ≤500: measured on a 474px-tall screen — the ≤580
       sizes still spilled the 19-tile grid 47px past the fold, so
       this band squeezes the grid steps and the menu chrome once
       more (games themselves keep their size steps). */
    
    
    /* v48 fit tail: at the shortest screens the full 19-tile grid
       still spilled one tile past the fold — squeeze the chrome
       gaps (not the games) until it fits. */
    

/* ===== v91 — BIÊN ĐẠO LÕI (layout-standard mục H, mức core/nav) =====
   Chỉ transform/opacity. Mở menu: ô trò bay vào so le như thẻ bài,
   chip đáp từ trên. Chọn trò: ô không chọn bay ra, ô chọn nảy nhẹ.
   Chạm giữa chừng: handler nav.js chạy ngay (nhảy tới trạng thái cuối).
   prefers-reduced-motion: tắt hết biên đạo, giữ trạng thái cuối. */
@keyframes tileIn { from { opacity: 0; transform: translateY(18px) scale(.94); } to { opacity: 1; transform: none; } }
@keyframes tileOut { to { opacity: 0; transform: translateY(14px) scale(.92); } }
@keyframes tilePop { 40% { transform: scale(1.1); } 100% { transform: scale(1); } }
@keyframes chipDrop { from { opacity: 0; transform: translateY(-14px); } to { opacity: 1; transform: none; } }
#menuOverlay:not([hidden]) .menu-tile { animation: tileIn .38s ease backwards; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(1) { animation-delay: 0ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(2) { animation-delay: 30ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(3) { animation-delay: 60ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(4) { animation-delay: 90ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(5) { animation-delay: 120ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(6) { animation-delay: 150ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(7) { animation-delay: 180ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(8) { animation-delay: 210ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(9) { animation-delay: 240ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(10) { animation-delay: 270ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(11) { animation-delay: 300ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(12) { animation-delay: 330ms; }
#menuOverlay:not([hidden]) .menu-tile:nth-child(n+13) { animation-delay: 360ms; }
#menuOverlay:not([hidden]) .menu-chips { animation: chipDrop .4s ease backwards; }
#menuModeGrid.menu-closing .menu-tile:not(.menu-chosen) { animation: tileOut .22s ease both; }
#menuModeGrid .menu-tile.menu-chosen { animation: tilePop .22s ease both; }
@media (prefers-reduced-motion: reduce) {
  #menuOverlay:not([hidden]) .menu-tile, #menuOverlay:not([hidden]) .menu-chips,
  #menuModeGrid.menu-closing .menu-tile, #menuModeGrid .menu-tile.menu-chosen { animation: none; }
}

/* ===== v92 — MENU TRÊN SÂN (layout-standard H1, biến thể "ngồi xem"
   mà bi đã duyệt 16:29; v91 mới giao nhịp chọn trò, nhịp mở menu còn
   thiếu: menu vẫn là lớp phủ opaque che kín sân) =====
   YARD MODE (nav.js gắn .yard-on khi khung đủ cao — F1/F2/F3):
   overlay trong suốt, sân hiện nguyên vẹn phía dưới; đầu menu + chip
   đáp trên vùng trời của sân; thú chính đi bộ ra MÉP TRÁI của cỏ và
   ngồi xem (nav.js lái #petFace trong stage), một thú bạn ngồi ở
   MÉP PHẢI (watcher trong spacer); lưới ô đáp xuống DƯỚI sân.
   ===== v93 — MỘT CẢNH LIỀN (bi phán ảnh v92, 2026-10-28 21:47):
   v92 vẫn còn ba dải xếp chồng (hộp chip / dải sân / vùng ô nhạt).
   v93: vùng dưới sân là CỎ TIẾP NỐI (xem ::before), hộp chip bỏ
   nền, nav.js THU STAGE (slim) để chế độ sân vừa cả khung ngắn
   414x736 và khung rộng 1280x800 thay vì rơi về xếp chồng. Hình
   học ô/chip không đổi (chỉ transform/opacity chuyển động,
   trạng thái cuối là khung chuẩn).
   ===== v93 addendum 5 (bi 23:01): chế độ dự phòng XOÁ HẲN —
   .yard-on là cách render DUY NHẤT còn tồn tại; nav.js đảm bảo
   nó bằng cấu trúc (sân tự nhiên → thu stage → ô bước xuống
   sz-xs) ở MỌI đường mở menu. */
.menu-yard-spacer { display: none; }
.menu-overlay.yard-on { background: transparent; }
.menu-overlay.yard-on::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  /* v93 (bi photo verdict on v92, 2026-10-28 21:47): the v92 field
     below the stage was a near-solid PALE sheet — the menu read as
     three stacked bands (chip box / yard strip / white tile zone)
     and "more disjointed than the old menu". The field is now the
     meadow itself: a whisper of haze over the yard (the scene
     stays crisp), then from the stage's bottom edge (--stage-b,
     set by nav.js) the GRASS CONTINUATION — the exact recipe the
     approved dots board panel wears (v92 dots.css): the stage's
     bottom-edge grass (#59ca4e, the v88 seam colour) easing into
     #5ec94d -> #4cc24a -> #42b742, with the same soft sunlit patch
     behind the tiles. Grass meets grass; the tiles land IN the
     scene, and the care panel's own content never ghosts through. */
  background:
    radial-gradient(ellipse 96% 42% at 50% calc(var(--stage-b, 60%) + 148px), rgba(255, 255, 255, .50), rgba(255, 255, 255, 0) 72%),
    /* v93 fix2: EVERY stop derives from --stage-b. The old 34%
       stop sat BEFORE the stage-b stops in the list, so whenever
       the stage bottom fell under 34% of the viewport height
       (the maze strip: stage-b 160px in an 844px frame) the CSS
       stop-clamping pushed the opaque grass start down to 34%
       and the game board ghosted through the sky tint between
       the stage and the grass — one of the exact frames bi
       caught. Stage-relative stops are monotonic by
       construction at every stage height. */
    linear-gradient(180deg,
      rgba(174, 233, 255, .18) 0%,
      rgba(174, 233, 255, .06) calc(var(--stage-b, 60%) * 0.6),
      rgba(201, 242, 255, .07) var(--stage-b, 60%),
      /* v95 (bi photo QA 2026-10-29 08:21 — "still a non-seamless
         point between menu and scene"): the grass used to start
         1px BELOW --stage-b, so any few-px error in the JS-set
         --stage-b (or a settle frame measured mid-transition)
         exposed a pale sky sliver along the junction. The grass
         now starts 28px ABOVE the junction: it paints over the
         stage's bottom grass band (plain grass there — invisible)
         and the junction can never show sky. */
      #59ca4e calc(var(--stage-b, 60%) - 28px),
      #5ec94d calc(var(--stage-b, 60%) + 26px),
      #4cc24a calc(var(--stage-b, 60%) + 190px),
      #42b742 100%);
}
.menu-overlay.yard-on > * { position: relative; }
/* v94: the ground-only scenery clone nav.js hangs under the
   stage (menuFieldSceneSync) — the meadow continues behind the
   tiles instead of the field stopping at a flat gradient. The
   clone is the stage's own scene at the stage's own scale: its
   transparent sky half lies over the field gradient, its hills,
   stone path and flowers draw the continuation. Fence, sun,
   clouds and tree stay out — they belong to the stage's horizon,
   not the field. */
.menu-overlay.yard-on .menu-field-scene {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: var(--stage-b, 60%); width: var(--yw, 640px);
  pointer-events: none; overflow: hidden;
}
.menu-overlay.yard-on .menu-field-scene .scene-bg { position: absolute; inset: 0; }
.menu-field-scene .sc-fence,
.menu-field-scene .sc-sunw,
.menu-field-scene .sc-cloudw,
.menu-field-scene .sc-treew { display: none; }
.menu-overlay.yard-on .menu-head,
.menu-overlay.yard-on .menu-zone-learn,
.menu-overlay.yard-on .menu-zone-play,
.menu-overlay.yard-on .menu-yard-spacer {
  width: 100%; max-width: var(--yw, 640px); margin-left: auto; margin-right: auto;
}
.menu-overlay.yard-on .menu-grid { max-width: var(--yw, 640px); }
/* v93: at wide frames the stage column (~600px) would squeeze
   the 5-across grid to ~112px tiles and the long labels (Bảng số
   1–100, Nghe và đoán) wrapped out of their 64px tiles. The tile
   field below the stage is full-width grass, so the play zone
   and its grid keep their natural wide cap (880px, the same as
   the stacked menu) — the zones above stay on the stage column,
   and the play label stays centred in its wider zone. */
@media (min-width: 700px) {
  .menu-overlay.yard-on .menu-zone-play { max-width: 880px; }
  .menu-overlay.yard-on .menu-grid { max-width: 880px; }
}
.menu-overlay.yard-on .menu-yard-spacer { display: block; flex: 0 0 auto; }
/* v93: the chip zone loses its tinted BOX — the chips are pills
   lying directly on the scene (bi: the boxed strip was one of the
   three stacked bands). Both zone labels become soft pills
   sitting on the scene (the play label already was one in v92),
   and the dotted divider between the zones goes: the scene has
   no seam there. Applies in both regimes. */
.menu-overlay .menu-zone-learn { background: none; border-color: transparent; }
.menu-overlay .menu-zone-play { border-top: none; }
.menu-overlay .menu-zone-label {
  width: fit-content; max-width: 100%; padding: 3px 14px;
  background: rgba(255, 255, 255, .88); border-radius: 999px;
  box-shadow: 0 2px 0 rgba(35, 50, 70, .10);
}
/* friend watcher at the right end of the grass band — v93: a
   live rig instance (nav.js menuWatcherSync mounts a PetRenderer
   in .mw-in), sized as a PEER of the main pet (the old .27-width
   flat SVG in a square box cropped to a floating head). Its
   ground line matches the main pet's seat: the rig mounts with
   groundFrac 1.0 and the box bottom sits 2px past the stage's
   bottom edge, so its paws land on the same grass line. */
.menu-watcher { display: none; }
.menu-overlay.yard-on .menu-watcher.on {
  display: block; position: absolute; right: 0; bottom: -2px;
  width: calc(var(--stage-w, 366px) * .36); aspect-ratio: 1 / 1;
  pointer-events: none; animation: watcherIn .45s ease .12s backwards;
}
.menu-watcher .mw-in { width: 100%; height: 100%; }
.menu-watcher canvas, .menu-watcher svg { width: 100%; height: 100%; display: block; }
@keyframes watcherIn { from { opacity: 0; transform: translateX(34px); } to { opacity: 1; transform: none; } }
/* tiles land like tossed cards when they land on the grass */
@keyframes tileInYard { from { opacity: 0; transform: translateY(30px) scale(.92) rotate(-1.6deg); } 60% { opacity: 1; } to { opacity: 1; transform: none; } }
#menuOverlay.yard-on:not([hidden]) .menu-tile { animation-name: tileInYard; }
/* while the menu is open the stage's own chrome (bubble, name pill,
   speaker, colour button) bows out — the menu head row is the HUD's
   other face; geometry untouched, opacity only */
#screen-pet .pet-stage .bubble, #screen-pet .pet-stage .pet-head,
#screen-pet #bubbleSpeak, #screen-pet .pet-colors { transition: opacity .3s ease; }
html:has(#menuOverlay.yard-on) #screen-pet .pet-stage .bubble,
html:has(#menuOverlay.yard-on) #screen-pet .pet-stage .pet-head,
html:has(#menuOverlay.yard-on) #screen-pet #bubbleSpeak,
html:has(#menuOverlay.yard-on) #screen-pet .pet-colors { opacity: 0; pointer-events: none; }
/* v93 fix2 (lead verification): the stage chrome above only
   FADES — the game layers under the menu must leave the scene
   entirely, or their props stay part of the menu picture: the
   feeding tray stood in the yard with the watcher dog in it
   (menu opened from Cho ăn), and panel/yard content ghosted
   through the overlay's translucent sky. While the yard menu
   is up, the active mode panel, the game yard and the feeding
   tray are visibility:hidden — layout, geometry and game
   state are untouched (no reflow, no ResizeObserver churn),
   and everything returns the moment the menu closes. */
html:has(#menuOverlay.yard-on) #screen-pet .mode-panel,
html:has(#menuOverlay.yard-on) #screen-pet #gameYard { visibility: hidden; }
html:has(#menuOverlay.yard-on) #screen-pet .pet-stage .food-tray { visibility: hidden; }
/* v93 fix3 (lead verification of the final tree): fix2 restored
   the stage's canonical GEOMETRY from every entry path, but not
   its PAINT. The yard games (maze/bath/chase — every mode that
   opens #gameYard) keep the stage strip SKY-ONLY by design while
   playing (responsive.css v81/v87: plain sky gradient on the
   stage, the whole ground layer — hills, fence, stone path,
   tree, flowers — display:none, so the yard below owns the only
   horizon). Those strip rules key off
   :has(#gameYard:not([hidden])), and fix2 only hides the yard
   VISUALLY, so they kept applying under the open menu: from a
   yard game the menu's stage stayed a bare sky with both pets
   floating on it, while from home the same menu wears the full
   yard (fence, grass, tree). While the yard menu is up, the
   stage paints the canonical yard again — the care backdrop
   gradient (layout.css .pet-stage, copied verbatim) and the
   whole ground layer — and the strip rules return untouched
   the moment the menu closes (yard-on drops), so the game
   resumes its own scene exactly as it was. Paint-only, on the
   same yard-on gate as the fix2 rules above; each selector
   repeats the strip rule's own shape plus the gate, so it wins
   on specificity at any cascade order. No geometry moves: the
   scenery layer is the stage's own absolute inset-0 stack. */
html:has(#menuOverlay.yard-on) #screen-pet .pet-panel:has(#gameYard:not([hidden])) .pet-stage .scene-bg {
  background: linear-gradient(180deg, #8fd8ff 0%, #c8f2ff 54%, #93e77e 54.5%, #63d64f 100%);
}
html:has(#menuOverlay.yard-on) #screen-pet .pet-panel:has(#gameYard:not([hidden])) .pet-stage .sc-ground > path,
html:has(#menuOverlay.yard-on) #screen-pet .pet-panel:has(#gameYard:not([hidden])) .pet-stage .sc-ground .sc-fence,
html:has(#menuOverlay.yard-on) #screen-pet .pet-panel:has(#gameYard:not([hidden])) .pet-stage .sc-ground .sc-path,
html:has(#menuOverlay.yard-on) #screen-pet .pet-panel:has(#gameYard:not([hidden])) .pet-stage .sc-treew,
html:has(#menuOverlay.yard-on) #screen-pet .pet-panel:has(#gameYard:not([hidden])) .pet-stage .sc-ground .sc-flower {
  display: block;
}
/* the main pet sits and watches (nav.js adds .menu-sit once the
   walk to the edge lands, and turns it to face the tile field).
   v93 final: the seat is a REAL rig pose — petSetSit drives the
   isSitting channel in pet-pixi.js (hind legs fold, body pitches
   rear-down, head stays up) — so .menu-sit is only the state
   marker the choreography probes read; no CSS sink or bob is
   applied to the art any more (an earlier draft faked the seat
   with a 7% host sink; bi's close-up still read it as standing
   and the trick was removed). */
/* animated outro (choreoMenuClose): tiles fly back out, the
   watcher slips away, taps pass until the overlay hides */
#menuOverlay.menu-bye { pointer-events: none; }
#menuOverlay.menu-bye .menu-tile { animation: tileOut .26s ease-in both; }
#menuOverlay.menu-bye .menu-watcher.on { animation: watcherOut .3s ease both; }
@keyframes watcherOut { to { opacity: 0; transform: translateX(34px); } }
@media (prefers-reduced-motion: reduce) {
  .menu-overlay.yard-on .menu-watcher.on { animation: none !important; }
}

/* v93 addendum 5: the tile grid's last size step — nav.js adds
   .sz-xs when even a min-height stage cannot leave room for the
   sz-s grid (sub-matrix portrait heights, a window shrunk under
   ~680px). Fit-first: smaller honest tiles INSIDE the same yard
   scene, and the chip row squeezes a step with them, so the full
   menu still fits one screen. The class is re-derived from live
   geometry at every menuYardApply, so growing the window back
   restores the normal steps on its own. */
#menuModeGrid.sz-xs .menu-tile { height: 36px; min-height: 36px; }
#menuModeGrid.sz-xs .menu-tile .mt-ico { font-size: 24px; }
#menuModeGrid.sz-xs .menu-tile .mt-lab { font-size: 11px; }
.menu-overlay:has(#menuModeGrid.sz-xs) .menu-zone-learn .menu-chip { min-height: 30px; font-size: 12.5px; }

/* v93 (night order, addendum 5): the tile steps scale with the
   viewport HEIGHT — the fit-first band table the menu was built
   with (v48), restored as part of the ONE yard mechanism after
   an earlier surgery dropped the rules and left only their
   comments: at short frames every step (s/m/l) steps down
   together, icons and labels with them, so the whole menu —
   zones + the full grid — still fits one screen inside the
   scene. The <=500 bands also squeeze the menu chrome (head
   row, chips, labels, gaps): the zones hand their height to
   the tile field instead of pushing it past the fold. */
@media (max-height: 700px) {
  #menuModeGrid .menu-tile { height: 60px; min-height: 60px; }
  #menuModeGrid.sz-m .menu-tile { height: 82px; min-height: 82px; }
  #menuModeGrid.sz-l .menu-tile { height: 100px; min-height: 100px; }
  #menuModeGrid .menu-tile .mt-ico { font-size: 36px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 42px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 50px; }
}
@media (max-height: 580px) {
  #menuModeGrid .menu-tile { height: 50px; min-height: 50px; }
  #menuModeGrid.sz-m .menu-tile { height: 66px; min-height: 66px; }
  #menuModeGrid.sz-l .menu-tile { height: 84px; min-height: 84px; }
  #menuModeGrid .menu-tile .mt-ico { font-size: 32px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 38px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 46px; }
  #menuModeGrid .menu-tile .mt-lab { font-size: 13px; }
}
@media (max-height: 500px) {
  #menuModeGrid .menu-tile { height: 42px; min-height: 42px; }
  #menuModeGrid.sz-m .menu-tile { height: 58px; min-height: 58px; }
  #menuModeGrid.sz-l .menu-tile { height: 74px; min-height: 74px; }
  #menuModeGrid .menu-tile .mt-ico { font-size: 28px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 34px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 42px; }
  #menuModeGrid .menu-tile .mt-lab { font-size: 12px; }
  .menu-grid { gap: 4px; }
  .menu-set { min-height: 44px; }
  .menu-close { width: 44px; height: 44px; }
  .menu-zone-learn { margin-top: 3px; padding: 4px 6px 3px; }
  .menu-zone-learn .menu-chip { min-height: 30px; font-size: 12.5px; }
  .menu-zone-play { margin-top: 2px; padding-top: 0; }
  .menu-zone-play .menu-grid { margin-top: 2px; }
  .menu-zone-label { font-size: 11.5px; padding: 2px 10px; }
  /* the overlay's own padding hands its share to the tile field
     too — at 360x474 the full mixed grid missed fitting by ~19px
     of pure chrome (measured: deepest tile label at 491/474) */
  .menu-overlay { padding: 6px 8px calc(6px + env(safe-area-inset-bottom)); }
}
@media (max-height: 470px) {
  #menuModeGrid .menu-tile { height: 38px; min-height: 38px; }
  #menuModeGrid.sz-m .menu-tile { height: 54px; min-height: 54px; }
  #menuModeGrid.sz-l .menu-tile { height: 68px; min-height: 68px; }
  #menuModeGrid .menu-tile .mt-ico { font-size: 24px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 30px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 38px; }
  #menuModeGrid .menu-tile .mt-lab { font-size: 11px; }
}

/* ==== SW v98: the menu's field scene spans the screen ====
   The field's ground clone copies the stage's scenery layer,
   which now bleeds to 100vw (layout.css) with its ground drawn
   for that same full-width box — so the clone's wrap spans the
   screen too, and the meadow continues behind the tiles edge to
   edge instead of ending at the column. */
.menu-overlay.yard-on .menu-field-scene { left: 0; transform: none; width: 100vw; }

/* ==== SW v102 — Phương án A: hero row + 3 shelves + icon chips ====
   The flat 18-tile grid is replaced by structure a 3–7-year-old
   can parse: a "Chơi ngay" hero row (most-played / today's pick /
   random), then three tinted SHELVES grouping the same pool tiles
   by family. The chip strip becomes ONE icon-first row on its own
   labelled band. Everything below is presentation + the tile
   size ladder re-derived for the shelf geometry; the fit ladder
   in nav.js (stage slim -> sz-xs) still guarantees one screen. */

/* --- chips: one row, icon first --- */
.menu-zone-learn .menu-chips { flex-wrap: nowrap; }
.menu-zone-learn .menu-chip {
  flex: 1 1 0; min-height: 44px; padding: 3px 1px;
  flex-direction: column; gap: 1px; border-radius: 16px;
}
.menu-chip .mc-ico { font-size: 19px; }
.menu-chip .mc-ico.sym { font-size: 16px; font-weight: 900; letter-spacing: -.01em; }
.menu-chip .mc-lab { font-size: 9.5px; line-height: 1.05; font-weight: 800; white-space: nowrap; }
.menu-chip.op .mc-lab { display: none; }
.menu-chip.op .mc-ico { font-size: 21px; }
/* the selected chip's name rides the zone label on narrow frames
   (where captions hide) so the choice stays readable */
.menu-zone-label .mz-cur { display: none; }
@media (max-width: 480px) {
  .menu-zone-label .mz-cur { display: inline; font-weight: 800; }
  .menu-zone-learn .menu-chip { min-height: 42px; border-radius: 14px; }
  .menu-zone-learn .menu-chip .mc-lab { display: none; }
}
/* chips never drop under the 40px tap floor, not even in sz-xs */
.menu-overlay:has(#menuModeGrid.sz-xs) .menu-zone-learn .menu-chip { min-height: 42px; font-size: 12.5px; }

/* --- hero row --- */
.menu-hero { display: flex; gap: 8px; width: 100%; margin: 0; }
.menu-hero-tile {
  flex: 1 1 0; min-width: 0; height: 56px; border-radius: 20px;
  display: flex; align-items: center; gap: 7px; padding: 5px 9px;
  background: linear-gradient(180deg, #ffffff, #fff8dc);
  border: 3px solid var(--sun-dark); box-shadow: 0 4px 0 rgba(35, 50, 70, .14);
  color: var(--ink); cursor: pointer; text-align: left;
  -webkit-tap-highlight-color: transparent;
}
.menu-hero-tile:active { transform: translateY(3px); box-shadow: none; }
#heroToday { background: linear-gradient(180deg, #ffffff, #eaf7ff); border-color: var(--sky-dark); }
#heroRandom { background: linear-gradient(180deg, #ffffff, #fff0f0); border-color: var(--coral); }
.menu-hero-tile .mh-ico { font-size: 30px; line-height: 1; flex: 0 0 auto; }
#heroRandom .mh-ico { font-size: 34px; }
.menu-hero-tile .mh-txt { display: flex; flex-direction: column; min-width: 0; gap: 0; }
.menu-hero-tile .mh-cap { font-size: 9.5px; font-weight: 800; color: var(--ink-soft); line-height: 1.1; white-space: nowrap; }
.menu-hero-tile .mh-name { font-size: 15.5px; font-weight: 900; line-height: 1.15; white-space: nowrap; }
@media (max-height: 680px) {
  .menu-hero-tile { height: 50px; }
  .menu-hero-tile .mh-ico { font-size: 26px; }
}
#menuOverlay:not([hidden]) .menu-hero-tile { animation: tileIn .38s ease backwards; }
#menuOverlay:not([hidden]) .menu-hero-tile:nth-child(2) { animation-delay: 45ms; }
#menuOverlay:not([hidden]) .menu-hero-tile:nth-child(3) { animation-delay: 90ms; }
/* choosing from the hero flies the unchosen out exactly like a
   pool-tile choice (the .menu-closing class lives on the grid) */
.menu-overlay:has(#menuModeGrid.menu-closing) .menu-hero-tile:not(.menu-chosen) { animation: tileOut .22s ease both; }
.menu-hero-tile.menu-chosen { animation: tilePop .22s ease both; }

/* --- shelves --- */
/* the grid element becomes the shelf STACK; it never flex-shrinks,
   so nav.js's full-height measure reads the true content height
   (the pre-v102 box could shrink under its own tiles and the fit
   ladder measured the shrunk box — at 414x628 the last tile row
   silently painted past the fold). */
#menuModeGrid { display: flex; flex-direction: column; gap: 10px; flex: 0 0 auto; }
.menu-zone-play .menu-grid { margin-top: 12px; }
.menu-shelf {
  position: relative; border-radius: 20px; padding: 13px 5px 5px;
  border: 2px solid rgba(255, 255, 255, .65);
}
.menu-shelf.shelf-num { background: linear-gradient(180deg, rgba(207, 235, 255, .92), rgba(186, 226, 255, .86)); }
.menu-shelf.shelf-lit { background: linear-gradient(180deg, rgba(233, 222, 255, .92), rgba(219, 203, 255, .86)); }
.menu-shelf.shelf-pet { background: linear-gradient(180deg, rgba(255, 244, 196, .92), rgba(255, 236, 160, .86)); }
.menu-shelf-head {
  position: absolute; top: 0; left: 10px; transform: translateY(-50%); z-index: 2;
  display: inline-flex; align-items: center; gap: 5px;
  background: #fff; border-radius: 999px; padding: 2px 12px 3px 8px;
  box-shadow: 0 2px 0 rgba(35, 50, 70, .12); white-space: nowrap;
}
.menu-shelf-head .msh-ico { font-size: 19px; line-height: 1; }
.menu-shelf-head .msh-lab { font-size: 12.5px; font-weight: 900; letter-spacing: .02em; color: var(--ink); }
.shelf-num .msh-lab { color: #1864ab; }
.shelf-lit .msh-lab { color: #5f3dc4; }
.shelf-pet .msh-lab { color: #b45309; }
.menu-shelf-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
@media (min-width: 700px) {
  .menu-shelf-tiles { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* --- tile size ladder for the shelf geometry ---
   Same step contract as v48 (one uniform capped size per step,
   tiles never stretch), re-derived: the steps now key off the
   visible ROW count (nav.js) and the height bands hand height
   back as frames shrink, with sz-xs as the ladder's last step.
   Wide frames spend their width on genuinely bigger tiles. */
#menuModeGrid .menu-tile { height: 78px; min-height: 78px; }
#menuModeGrid.sz-m .menu-tile { height: 100px; min-height: 100px; }
#menuModeGrid.sz-l .menu-tile { height: 124px; min-height: 124px; }
#menuModeGrid .menu-tile .mt-ico { font-size: 46px; }
#menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 54px; }
#menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 64px; }
@media (max-height: 820px) {
  #menuModeGrid .menu-tile { height: 72px; min-height: 72px; }
  #menuModeGrid.sz-m .menu-tile { height: 92px; min-height: 92px; }
  #menuModeGrid.sz-l .menu-tile { height: 116px; min-height: 116px; }
  #menuModeGrid .menu-tile .mt-ico { font-size: 44px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 52px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 60px; }
}
@media (max-height: 740px) {
  #menuModeGrid .menu-tile { height: 63px; min-height: 63px; }
  #menuModeGrid.sz-m .menu-tile { height: 84px; min-height: 84px; }
  #menuModeGrid.sz-l .menu-tile { height: 108px; min-height: 108px; }
  /* v104: 38px, not 42 — the icon line box (42*1.05=44) + gap +
     caption (19) summed to 66px in a 63px tile, so every caption
     painted ~2px past its tile's bottom edge at 414x736
     (measured); 38*1.05+3+19 = 62 fits the box. */
  #menuModeGrid .menu-tile .mt-ico { font-size: 38px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 50px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 58px; }
}
@media (max-height: 680px) {
  #menuModeGrid .menu-tile { height: 56px; min-height: 56px; }
  #menuModeGrid.sz-m .menu-tile { height: 76px; min-height: 76px; }
  #menuModeGrid.sz-l .menu-tile { height: 100px; min-height: 100px; }
  #menuModeGrid .menu-tile .mt-ico { font-size: 38px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 46px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 54px; }
}
@media (min-width: 700px) {
  #menuModeGrid .menu-tile { height: 100px; min-height: 100px; }
  #menuModeGrid.sz-m .menu-tile { height: 118px; min-height: 118px; }
  #menuModeGrid.sz-l .menu-tile { height: 136px; min-height: 136px; }
  #menuModeGrid .menu-tile .mt-ico { font-size: 56px; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 62px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 70px; }
}
#menuModeGrid.sz-xs .menu-tile { height: 46px; min-height: 46px; }
#menuModeGrid.sz-xs .menu-tile .mt-ico { font-size: 28px; }
#menuModeGrid.sz-xs .menu-tile .mt-lab { font-size: 11px; }
@media (prefers-reduced-motion: reduce) {
  #menuOverlay:not([hidden]) .menu-hero-tile { animation: none; }
}

/* --- v102 fit pass (harness-measured on the first cut) ---
   The first shelf layout measured ~90px taller than its paper
   budget (shelf bands + hero + label rows), so the ladder fell
   back to sz-xs on every phone frame and 414x628 still spilled.
   Fit-first squeezes, chrome only — games keep their steps:
   the redundant "Chơi trò gì?" label goes (the hero row and the
   shelf captions carry the structure), the learn label moves
   INTO the chip row as its leading pill, shelf padding slims,
   and the head/hero/chips hand a few px back at short frames. */
.menu-zone-play .menu-zone-label { display: none; }
.menu-zone-learn { display: flex; align-items: center; gap: 6px; padding: 2px 7px; border: none; }
.menu-zone-learn .menu-zone-label { width: auto; max-width: none; margin: 0; flex: 0 0 auto; }
.menu-zone-learn .menu-chips { flex: 1 1 auto; margin-top: 0; }
#menuModeGrid { gap: 9px; }
.menu-zone-play .menu-grid { margin-top: 9px; }
.menu-shelf { padding: 9px 5px 4px; }
.menu-shelf-tiles { gap: 5px; }
.menu-hero-tile { height: 48px; }
.menu-hero-tile .mh-ico { font-size: 28px; }
@media (min-width: 700px) {
  .menu-hero-tile { height: 56px; }
  .menu-hero-tile .mh-ico { font-size: 30px; }
}
@media (max-height: 740px) {
  .menu-set { min-height: 50px; }
  .menu-close { width: 50px; height: 50px; }
}
@media (max-height: 740px) {
  .menu-hero-tile { height: 44px; }
}
@media (max-height: 680px) {
  .menu-hero-tile .mh-ico { font-size: 24px; }
}
@media (max-width: 480px) {
  .menu-zone-learn .menu-chip { min-width: 0; min-height: 40px; }
}
/* v102 fit pass 2 (harness-measured): inline, the label pill +
   10 nowrap chips overflowed the zone on phone widths (chips
   painted past the screen edge). At <=480px the label becomes a
   floating badge on the zone's top edge — the shelf-head trick —
   and the chip row gets the zone's full width. */
.menu-zone-learn .menu-chips { min-width: 0; }
@media (max-width: 480px) {
  .menu-zone-learn { display: block; position: relative; margin-top: 12px; padding: 3px 7px; }
  .menu-zone-learn .menu-zone-label {
    position: absolute; top: 0; left: 4px; transform: translateY(-50%); z-index: 2;
    width: auto; max-width: none; margin: 0;
  }
}
/* v102 fit pass 3 (harness-measured at 414x628): with the hero
   row inside the field, the whole field measures ~490px at the
   sz-xs step and only fits if the stage strip slims to ~125px —
   under the shell's 150px stage floor (layout.css), so the
   ladder's last slim was clamped and the yard applied over a
   full-height stage, pushing the lit/pet shelves past the fold.
   While the yard menu is open the stage is a backdrop strip for
   the seated pets, so it gets the same floor exception feed got
   in v101 (132px there): menu-open only, and closeMenu removes
   the inline height the exception applies to. nav.js's ladder
   gained the matching trySlim(118) last step. */
html:has(#menuOverlay.yard-on) #screen-pet .pet-stage { min-height: 118px; }

/* ==== SW v104 — menu THOÁNG ở khung thấp/hẹp (bi phán v102 ở
   414x628: "Loạn vậy") ====
   v102 fit the frame but read chaotic on a parent's glance: the
   chip band crowded the right edge behind its floating text pill,
   the hero's middle caption clipped mid-word ("Gợi ý hôm na…"),
   and the tile stacks sat tight with no air. v104 changes ONLY
   the calm band — narrow (<=480px wide) and/or short (<=700px
   tall) frames; wide frames keep v102 exactly. Three moves:
   (1) the learn zone's text pill goes at narrow widths — the
   selected chip already carries its state (sun-yellow), and the
   ~183px pill was pure horizontal waste; the chip row gets the
   reclaimed width, tighter gaps, and smaller symbol glyphs so
   "123"/"abc" never clip (measured 2px clip at 360px in v102).
   (2) hero tiles slim down and their captions swap to the short
   forms (nav.js syncHeroCaps — full names stay in aria-labels),
   so no caption can ever clip.
   (3) tiles breathe: wider gaps in the calm band, and at the
   shortest frames the tiles turn icon-left / caption-right, so a
   caption is a single line beside its icon instead of a squeezed
   stack — the fit ladder (stage slim / sz steps) still owns the
   one-screen guarantee. */

/* --- (1) chip band: no pill, full-width row, un-clippable --- */
@media (max-width: 480px) {
  /* the watcher pet leaves narrow frames: sized off the stage
     width, its head reaches into the chip band whenever the
     stage is slimmed for the menu (measured at 414x628 /
     414x736 / 360x800: the last three chips — abc, word,
     sentences — painted UNDER the dog). Chips are function,
     the watcher is garnish; the seated main pet stays. */
  .menu-overlay.yard-on .menu-watcher.on { display: none; }
  .menu-zone-learn { margin-top: 8px; padding: 3px 6px; }
  .menu-zone-learn .menu-zone-label { display: none; }
  .menu-zone-learn .menu-chips { gap: 4px; }
  .menu-zone-learn .menu-chip { padding: 3px 0; }
  .menu-zone-learn .menu-chip .mc-ico { font-size: 18px; }
  .menu-zone-learn .menu-chip .mc-ico.sym { font-size: 13.5px; letter-spacing: -.02em; }
  .menu-zone-learn .menu-chip.op .mc-ico { font-size: 19px; }
}

/* --- (2) hero: one low row, short caps (JS swaps the text) --- */
@media (max-width: 480px), (max-height: 700px) {
  .menu-hero { gap: 7px; }
  .menu-hero-tile { height: 42px; padding: 4px 7px; gap: 5px; border-radius: 17px; }
  .menu-hero-tile .mh-ico { font-size: 23px; }
  #heroRandom .mh-ico { font-size: 26px; }
  .menu-hero-tile .mh-cap { font-size: 10px; }
  .menu-hero-tile .mh-name { font-size: 14px; }
}

/* --- (3a) breathing gaps across the calm band --- */
@media (max-width: 480px) {
  .menu-shelf-tiles { gap: 6px; }
}
/* captions stay ONE line in the STACKED calm band (the app's
   fit-first rule): nowrap lets fitTextEl see the true text width
   and step the font down — in v102 the longest caption ("Bảng số
   1–100") wrapped to two lines at 360px and painted past its
   tile. The row band below (<=700px tall) plays by the other
   rule: captions wrap beside the icon, two lines max, because
   the longest EN caption cannot shrink into the row's slot even
   at the fit floor (measured: "Numbers 1 to 100" needs 77px at
   10px in a 62px slot). */
@media (max-width: 480px) and (min-height: 701px) {
  #menuModeGrid .menu-tile .mt-lab { white-space: nowrap; }
}
@media (max-height: 700px) {
  #menuModeGrid { gap: 11px; }
  .menu-shelf-tiles { gap: 7px; }
}

/* --- (3b) shortest frames: tiles go icon-left, caption right --- */
@media (max-height: 700px) {
  #menuModeGrid .menu-tile {
    flex-direction: row; justify-content: flex-start;
    padding: 4px 9px; gap: 7px;
  }
  #menuModeGrid .menu-tile .mt-ico { font-size: 25px; }
  #menuModeGrid .menu-tile .mt-lab { font-size: 12.5px; text-align: left; min-width: 0; }
  #menuModeGrid.sz-m .menu-tile .mt-ico { font-size: 30px; }
  #menuModeGrid.sz-l .menu-tile .mt-ico { font-size: 36px; }
  #menuModeGrid.sz-xs .menu-tile .mt-ico { font-size: 23px; }
  #menuModeGrid.sz-xs .menu-tile .mt-lab { font-size: 12px; }
}

/* ==== SW v105 — menu BỘ THẺ (bi's own proposal 2026-10-29
   22:44: "cho thẻ to ở giữa rồi lướt trái phải như chọn thẻ
   bài") ====
   Phương án A (hero + shelves as the menu face) is retired: the
   play zone now holds ONE deck of big cards — the centre card is
   the pick, its neighbours peek from both edges at 0.86 scale.
   The hero/shelf CSS above survives only where the "Tất cả"
   overlay still uses it (the shelves + tile ladder inside
   #menuModeGrid); the deck rules below are frame-independent —
   wide frames use the same deck, cards capped at 400px. */

/* --- deck field + bar --- */
.menu-deck-field { flex: 0 0 auto; width: 100%; margin: 4px auto 0; }
.deck-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; min-height: 44px; padding: 0 2px;
}
.deck-fam {
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
  background: rgba(255, 255, 255, .92); border-radius: 999px;
  padding: 5px 13px 6px 10px; box-shadow: 0 2px 0 rgba(35, 50, 70, .10);
  font-size: 15px; font-weight: 900; color: var(--ink); white-space: nowrap;
}
.deck-fam .df-ico { font-size: 19px; line-height: 1; }
.deck-fam .df-pos {
  font-size: 12.5px; font-weight: 800; color: var(--ink-soft);
  background: rgba(35, 50, 70, .07); border-radius: 999px; padding: 1px 8px;
}
.deck-bar-btns { display: inline-flex; gap: 7px; flex: 0 0 auto; }
.deck-dice {
  width: 44px; height: 44px; border-radius: 50%; padding: 0;
  border: 3px solid var(--coral); background: #fff; box-shadow: var(--shadow);
  font-size: 21px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.deck-dice:active { transform: translateY(2px); box-shadow: none; }
.deck-all-btn {
  min-height: 44px; border-radius: 999px; padding: 5px 14px;
  border: 3px solid var(--line); background: #fff; box-shadow: var(--shadow);
  font-size: 15px; font-weight: 900; color: var(--ink); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  -webkit-tap-highlight-color: transparent;
}
.deck-all-btn .dab-ico { font-size: 16px; line-height: 1; }
.deck-all-btn:active { transform: translateY(2px); box-shadow: none; }

/* --- deck viewport / track / cards --- */
.deck-viewport {
  position: relative; height: 254px; margin-top: 4px;
  overflow: hidden; touch-action: none;
}
@media (min-height: 800px) { .deck-viewport { height: 270px; } }
@media (max-height: 700px) { .deck-viewport { height: 240px; } }
.menu-deck-field.deck-xs .deck-viewport { height: 210px; }
.deck-track {
  display: flex; gap: 14px; height: 100%; box-sizing: border-box;
  padding: 8px 0 10px; align-items: stretch;
  transition: transform .34s cubic-bezier(.22, .9, .3, 1);
  will-change: transform;
}
.deck-track.no-anim { transition: none; }
.deck-card {
  flex: 0 0 auto; width: var(--card-w, 260px); height: 100%;
  display: flex; flex-direction: column; align-items: center;
  border-radius: 26px; border: 3px solid var(--line);
  background: linear-gradient(180deg, #ffffff, #f2fbff);
  box-shadow: 0 5px 0 rgba(35, 50, 70, .13);
  padding: 0 10px 12px; cursor: pointer; position: relative;
  transform: scale(.86); opacity: .7;
  transition: transform .34s ease, opacity .34s ease, box-shadow .34s ease;
  -webkit-tap-highlight-color: transparent; color: var(--ink);
  font-family: inherit;
}
.deck-card.is-center { transform: scale(1); opacity: 1; box-shadow: 0 8px 0 rgba(35, 50, 70, .16); }
.deck-card.fam-num { border-color: #74c0fc; }
.deck-card.fam-lit { border-color: #b197fc; }
.deck-card.fam-pet { border-color: #ffd43b; }
.dc-band {
  align-self: stretch; margin: 0 -10px; padding: 7px 12px 8px;
  border-radius: 23px 23px 0 0;
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 900; white-space: nowrap;
}
.fam-num .dc-band { background: #cfeaff; color: #1864ab; }
.fam-lit .dc-band { background: #e6dbff; color: #5f3dc4; }
.fam-pet .dc-band { background: #ffec99; color: #b45309; }
.dc-band .dc-band-ico { font-size: 16px; line-height: 1; }
.dc-badge { position: absolute; top: 36px; right: 10px; font-size: 21px; line-height: 1; }
.dc-ico { font-size: 76px; line-height: 1.12; margin-top: 12px; }
.dc-name { font-size: 23px; font-weight: 900; line-height: 1.16; text-align: center; margin-top: 7px; }
.dc-play {
  margin-top: auto; display: none;
  background: var(--sun); border: 2.5px solid var(--sun-dark); color: #5b3a00;
  font-size: 17px; font-weight: 900; border-radius: 999px; padding: 7px 24px;
  box-shadow: 0 3px 0 rgba(90, 60, 0, .25);
}
.deck-card.is-center .dc-play { display: inline-block; }
.menu-deck-field.deck-xs .dc-ico { font-size: 56px; margin-top: 6px; }
.menu-deck-field.deck-xs .dc-name { font-size: 20px; margin-top: 4px; }
.menu-deck-field.deck-xs .dc-play { font-size: 15px; padding: 5px 18px; }
.menu-deck-field.deck-xs .dc-band { padding: 5px 12px 6px; font-size: 12px; }

/* ‹ › edge buttons for children who have not learned to swipe */
.deck-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 44px; height: 44px; border-radius: 50%; padding: 0;
  border: 3px solid var(--line); background: rgba(255, 255, 255, .95);
  box-shadow: var(--shadow); color: var(--ink);
  font-size: 27px; font-weight: 900; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.deck-prev { left: 2px; }
.deck-next { right: 2px; }
.deck-nav:disabled { opacity: .32; box-shadow: none; cursor: default; }
.deck-nav:active:not(:disabled) { transform: translateY(-50%) translateY(2px); box-shadow: none; }

/* deck choreography: the viewport slides/fades in on open; on
   close the cards fly out with the rest; a centre-card choice
   pops while the others leave (the .menu-closing beat). */
@keyframes deckIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
#menuOverlay:not([hidden]) .deck-viewport { animation: deckIn .38s ease backwards; }
#menuOverlay.menu-bye .deck-card { animation: tileOut .26s ease-in both; }
.menu-deck-field.menu-closing .deck-card:not(.menu-chosen) { animation: tileOut .22s ease both; }
.menu-deck-field .deck-card.menu-chosen { animation: tilePop .22s ease both; }
@media (prefers-reduced-motion: reduce) {
  #menuOverlay:not([hidden]) .deck-viewport,
  #menuOverlay.menu-bye .deck-card,
  .menu-deck-field.menu-closing .deck-card,
  .menu-deck-field .deck-card.menu-chosen { animation: none; }
  .deck-track, .deck-card { transition: none; }
}

/* --- "Tất cả" overlay: the flat shelf grid, one tap from the
   deck. It covers the menu's own layers (chips included — the
   grid already shows the current chip's games) with the meadow
   the menu stands on, so it reads as the same place, flattened. */
.menu-all {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column;
  padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
  background: linear-gradient(180deg,
    rgba(174, 233, 255, .97) 0%, rgba(190, 240, 190, .98) 22%,
    rgba(89, 202, 78, .99) 52%, #42b742 100%);
}
.menu-all[hidden] { display: none; }
/* the yard regime forces position:relative on every direct
   child of the overlay (.menu-overlay.yard-on > *, v92) — the
   all-games layer must outrank it or it lays out IN FLOW under
   the stage and its grid clips at the fold (measured on the
   first v105 photos at 414x628 and 1280x800). (0,3,0) beats the
   yard rule's (0,2,0) outright. */
.menu-overlay.yard-on > .menu-all, .menu-overlay > .menu-all { position: absolute; }
.menu-all-head {
  flex: 0 0 auto; width: 100%; max-width: 640px; margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
@media (min-width: 700px) { .menu-all-head { max-width: 880px; } }
.mah-lab {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(255, 255, 255, .94); border-radius: 999px;
  padding: 6px 16px 7px 12px; box-shadow: 0 2px 0 rgba(35, 50, 70, .10);
  font-size: 16px; font-weight: 900; color: var(--ink); white-space: nowrap;
}
.mah-lab .mah-ico { font-size: 19px; line-height: 1; }
.menu-all-close {
  width: 48px; height: 48px; flex: 0 0 auto; border-radius: 50%;
  border: 3px solid var(--coral); background: #fff; color: var(--coral-dark);
  font-size: 21px; font-weight: 900; line-height: 1; cursor: pointer;
  box-shadow: var(--shadow); -webkit-tap-highlight-color: transparent;
}
.menu-all-close:active { transform: translateY(2px); box-shadow: none; }
.menu-all #menuModeGrid { flex: 1 1 auto; min-height: 0; margin-top: 12px; }
/* inside the overlay the field is the whole screen, so at short
   frames the tiles take one honest step down rather than letting
   the last shelf slip past the fold */
@media (max-height: 700px) {
  .menu-all #menuModeGrid .menu-tile { height: 56px; min-height: 56px; }
  .menu-all #menuModeGrid.sz-m .menu-tile { height: 76px; min-height: 76px; }
  .menu-all #menuModeGrid.sz-l .menu-tile { height: 96px; min-height: 96px; }
  .menu-all #menuModeGrid .menu-tile .mt-ico { font-size: 34px; }
}
