/* ============================================================
   INKVILLE — menu.css
   the pixel-era makeover, the character creator and the jukebox

   Part of the game.css split. Loaded as <link> tags from index.html in
   THIS ORDER — the cascade depends on it. Do not reorder the links.
   ============================================================ */
/* ============================================================
   PIXEL ERA — Game-Boy-generation makeover
   ============================================================ */
#app{image-rendering:pixelated;}
svg{shape-rendering:crispEdges;}
.title-xl{font-size:30px!important; line-height:1.5; text-shadow:3px 3px 0 var(--paper3);}
.subtitle{font-size:11px;}
.menu-label{font-size:14px!important; line-height:1.3;}
.menu-sub{font-size:10px!important; letter-spacing:0;}
.btn{font-size:13px; transform:none; padding:13px 20px;}
.btn>span{transform:none;}
.btn:hover{transform:scale(1.04);}
.btn:active{transform:scale(.94);}
.btn.sm{font-size:12px; padding:10px 14px;}
.bar-title{font-size:12px;}
.backbtn{font-size:12px; transform:none; padding:6px 10px;}
.backbtn:hover{transform:scale(1.06);}
.chip{transform:none; font-size:12px;}
.chip>*{transform:none;}
.menu-card:nth-child(odd),.menu-card:nth-child(even){transform:none;}
.menu-card:hover{filter:brightness(1.04);}
.mission:nth-child(odd),.mission:nth-child(even){transform:none;}
.card .nm{font-size:10px; line-height:1.3;}
.card .stats{font-size:12px;}
.cost{font-size:14px;}
.rar{font-size:9px;}
.lvl{font-size:11px;}
.tiny{font-size:10px; letter-spacing:0;}
/* ⚠⚠ v0.711 — THIS FILE LOADS AFTER base.css AND THE SELECTOR IS IDENTICAL, so the 12px that used
   to live here silently beat #toast's own size and every notification in the game was 12px no
   matter what base.css said. The message strip is deliberately the LARGEST small text in the game
   now (Odaroo: "larger font"), so it is sized in ONE place — css/base.css — and this sheet must not
   restate it. Do not put a font-size on #toast here again. */
.modal .disp{font-size:15px!important; line-height:1.5;}
.modal{outline:3px solid var(--cream); outline-offset:-8px; border-width:4px;}
.panel{outline:3px solid var(--cream); outline-offset:-8px;}
.pity{transform:none; font-size:11px;}
.pprice{transform:none!important; font-size:12px!important;}
.packbox .pname{font-size:13px;}
.vsname{font-size:12px;}
/* ⭐⭐⭐ v0.712 — THE GOLD SCOREBOARD BOX IS RETIRED. #bClock is the HEADER FILM REEL now, and the
   whole ask was that the time wear "the same frame, the same size as the film reel behind the
   username" — so every declaration here fought the thing it is supposed to match: a gold border
   against the reel's ink one, `!important` 16px gold type against the fitted cream at --nmsz, and a
   84px min-width against the fixed 6.3-character box vsMidReserve books.
   ⚠⚠ THIS FILE LOADS AFTER hub.css, so leaving it would simply have WON — the reel would have been
   built and then painted over as a gold bar sitting where the clock used to be sized to sit. A rule
   for an element that moved house does not stop applying; it stops being right.
   ⚠ The two ANIMATIONS (#bClock.clkBig / .clkShine, css/base.css) are deliberately KEPT: the DING
   DING step-up and the count-in shine are the brawl announcing its own clock, and they are pure
   `transform`/`animation` — they compose with the reel instead of restyling it. */
#bigClock{position:fixed; left:50%; top:38%; transform:translate(-50%,-50%); z-index:80; pointer-events:none;
  font-family:var(--fontD); font-size:54px; color:#ffe9a0; text-shadow:4px 4px 0 #1b1510, 0 0 24px #f4c542;
  animation:bigClockIn 1.6s ease-in forwards;}
@keyframes bigClockIn{0%{opacity:0; transform:translate(-50%,-50%) scale(.4)}18%{opacity:1; transform:translate(-50%,-50%) scale(1.15)}55%{opacity:1; transform:translate(-50%,-50%) scale(1)}100%{opacity:0; transform:translate(-50%,-340%) scale(.22)}}   /* the count shrinks UP into the HUD clock */
.sdBanner{position:fixed; left:0; right:0; top:34%; z-index:81; text-align:center; pointer-events:none;
  font-family:var(--fontD); font-size:30px; color:#ff5c4a; letter-spacing:3px; text-shadow:3px 3px 0 #1b1510, 0 0 22px #c93a22;
  animation:sdIn 2s ease-out forwards;}
@keyframes sdIn{0%{opacity:0; transform:scale(2.4)}16%{opacity:1; transform:scale(1)}80%{opacity:1}100%{opacity:0}}
#battleTop #bYou,#battleTop #bFoe,.bhp-cups,.bhp-lv{font-family:var(--fontD)!important; font-size:11px!important; letter-spacing:.5px;}   /* cups + LV live in #battleHP — selector kept generic */
#enum{font-size:14px;}
.disp{letter-spacing:0;}
.gcard .disp{font-size:15px!important;}
.gcard .tiny{font-size:10px;}
.mdone{font-size:12px;}
.sday b{font-size:12px;} .sday{font-size:10px;}
#marquee{font-size:12px;}
#superBtn{font-size:15px;}
.btn,.panel,.modal,.card,.mission,.packbox,.chip,.menu-card,.dslot,.qbtn,.lrbtn,.ddr-btn,#ebar,.mbar,.sday,.gcard,#toast,#qte{border-radius:0!important;}
.bulb{border-radius:0;}
/* dither dots everywhere */
.halftone{background-image:radial-gradient(#1b151022 1px, transparent 1.2px); background-size:4px 4px;}
#grain{
  background-image:radial-gradient(#1b1510 .8px, transparent 1px);
  background-size:3px 3px; opacity:.05;
}
/* smash-style menu tiles */
.menu-stack{display:grid!important; grid-template-columns:repeat(12,1fr); gap:8px; align-content:start;
  /* ⚠ v0.746 — 20px → 24px of side room, because the notification badges hang OUTSIDE their tiles now
     (see .ndot below) and the selected tile scales up 3% on top of that. Four pixels a side buys the
     overhang somewhere to be; without it the act's badge left the screen on the unfolded phone. */
  padding-left:24px; padding-right:24px;}
.mtile.quad{grid-column:span 6; min-height:86px; flex-direction:column; justify-content:center; gap:5px; padding:8px 4px; text-align:center;}
/* the ONLINE tile packs FOUR rows into the same box — gloves, BIT-BRAWL, ONLINE, ビット・ブロール, tight but whole */
.mtile:has(.bb-online){gap:0; padding-top:4px; padding-bottom:4px;}
.mtile:has(.bb-online) .menu-art{width:38px; height:30px; margin:0 0 -4px;}   /* the gloves sit ~1px off the BIT-BRAWL letters — the icon's lower whitespace is pulled out */
.mtile:has(.bb-online) .menu-label{line-height:1.05; margin:0;}
.mtile:has(.bb-online) .bb-online{margin:1px auto; height:16px; font-size:7px; padding:0 7px;}
.mtile:has(.bb-online) .jplab{margin:1px 0 0; line-height:1.05;}
.mtile.quad .menu-art{width:44px; height:44px;}
.mtile.quad .menu-label{font-size:11px!important; line-height:1.4;}
.mtile.tix2{grid-column:span 6; min-height:56px; flex-direction:column; justify-content:center; gap:2px; padding:6px 14px 6px 34px; position:relative;
  background:
    radial-gradient(circle at 0 50%, #efe2c5 9px, transparent 9.5px),
    radial-gradient(circle at 100% 50%, #efe2c5 9px, transparent 9.5px),
    repeating-linear-gradient(90deg, var(--cream) 0 8px, #f4e8cd 8px 16px);}
.mtile.tix2::before{content:'ADMIT ONE'; position:absolute; left:-9px; top:50%;
  transform:translateY(-50%) rotate(-90deg); font-family:var(--fontD); font-size:6px; letter-spacing:2px; opacity:.55;}
.mtile.tix2::after{content:''; position:absolute; left:26px; top:5px; bottom:5px; border-left:2px dashed var(--ink); opacity:.45;}
.mtile.tix2 .menu-art{width:26px; height:26px;}
.mtile.tix2 .menu-label{font-size:10px!important;}
.mtile.trio{grid-column:span 4; min-height:90px; flex-direction:column; justify-content:center; gap:6px; padding:8px 3px; text-align:center;}
.mtile.duo{grid-column:span 6; min-height:66px; flex-direction:column; justify-content:center; gap:4px; padding:6px 4px;}
.mtile.sq4{grid-column:span 3; min-height:64px; flex-direction:column; justify-content:center; gap:4px; padding:6px 2px;}
.mtile.trio .menu-art{width:46px; height:46px;} .mtile.duo .menu-art{width:32px; height:32px;} .mtile.sq4 .menu-art{width:26px; height:26px;}
.mtile.trio .menu-label{font-size:11px!important; line-height:1.4;}
.mtile.duo .menu-label{font-size:11px!important;} .mtile.sq4 .menu-label{font-size:9.5px!important; line-height:1.35;}
/* ⭐⭐⭐ v0.746 — GOLDEN RULE: A NOTIFICATION DOT LIVES OUTSIDE THE ARTWORK, NOT INSIDE IT.
   (Odaroo, 2026-08-07: "I'm not seeing the actual red dot on top of the whole square, I'm seeing it
   INSIDE the square corner… I almost missed I had a notification.")
   It is a phone-icon badge: its CENTRE sits ON the tile's top-left corner, so half of it hangs off
   the tile entirely and it can never be read as part of the picture.
   ⚠⚠ THE OFFSET IS ARITHMETIC, NOT A GUESS — half the diameter PLUS the border. A hand-typed -4px on
   a 13px pip left 9 of its 13 pixels inside the square, which is exactly the fault reported.
   ⚠⚠ THE 3px BORDER IS PART OF THE SUM (`--ndot-bw`). An absolutely-positioned child is placed
   against its parent's PADDING box, which on a tile starts 3px inside the black rule — so a plain
   `-50%` offset still leaves 70% of the pip inside the artwork, which is barely better than before.
   ⚠⚠ AND z-index WAS 0, which is the other half of the bug: a positioned sibling at z-index 0 loses
   to every later positioned sibling on the tile (the stripe, the wallpaper, the lock), so the pip was
   not merely tucked in — it was painted OVER. It rides at 14 now, under nothing.
   ⚠ ANY TILE CARRYING ONE MUST BE overflow:visible (see the .mtile.feat note in css/hub.css). */
.ndot{--ndot-sz:15px; --ndot-bw:3px;
  position:absolute; right:auto;
  top:calc(var(--ndot-sz) / -2 - var(--ndot-bw)); left:calc(var(--ndot-sz) / -2 - var(--ndot-bw));
  width:var(--ndot-sz); height:var(--ndot-sz); border-radius:50%;
  background:var(--red); border:2px solid var(--ink); animation:pulse 1s infinite; z-index:14;
  /* ⚠ THE OUTER RING IS CREAM, NOT A RED GLOW. Half the pip now hangs over whatever is behind the
     tile, and the boulevard's wallpaper is a tan check while the act's own tile is pitch black — a
     black ring alone vanished into one and a red halo muddied into the other. Red · black · cream
     reads on both, which is the whole point of moving it outside. */
  box-shadow:0 0 0 2px var(--cream), 2px 2px 0 rgba(27,21,16,.5);}
/* ⭐ v0.401 — THE APP BADGE. On the GIGS bar the dot is not a tucked-in pip any more, it is a
   proper notification badge: it straddles the top-left CORNER — half on the bar, half off it —
   so the eye catches it the way it catches one on a phone icon. It carries its own shine, and it
   counter-skews the bar's -3deg so it stays a circle, not an egg. */
.ndot.pop{--ndot-sz:calc(19px * var(--gs,1)); width:var(--ndot-sz); height:var(--ndot-sz);
  top:calc(var(--ndot-sz) / -2 - var(--ndot-bw)); left:calc(var(--ndot-sz) / -2 - var(--ndot-bw));
  border-width:calc(2.5px * var(--gs,1)); z-index:12; overflow:hidden;
  box-shadow:0 0 0 calc(2px * var(--gs,1)) rgba(201,58,34,.28), calc(1.5px * var(--gs,1)) calc(1.5px * var(--gs,1)) 0 rgba(27,21,16,.45);
  transform:skewX(3deg); transform-origin:50% 50%;
  animation:ndotBeat 1.6s ease-in-out infinite;}
/* the shine — a pale sweep that runs across the badge every few seconds */
.ndot.pop::after{content:''; position:absolute; top:-40%; left:-120%; width:70%; height:180%;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.85), transparent);
  animation:ndotShine 2.6s ease-in-out infinite;}
@keyframes ndotBeat{0%,72%,100%{transform:skewX(3deg) scale(1);} 82%{transform:skewX(3deg) scale(1.22);} 90%{transform:skewX(3deg) scale(.96);}}
/* ⚠⚠ v0.834 — a sweep rides `translate`, never `left`. See the rinkSheen note in menu.css:
   `left` is a LAYOUT property and an infinite sweep on it re-solves the whole page every frame.
   ⚠ The travel is expressed against the element's OWN box, not the parent's. */
/* travel: left -120% -> 150% = 270% of the parent dot; the bar is `width:70%` of it, so in its
   own box that is 270/70 = 386%. ⚠ This one runs on the HOME screen on every unread badge. */
@keyframes ndotShine{0%,58%{translate:0;} 88%,100%{translate:386%;}}
@media (prefers-reduced-motion:reduce){ .ndot.pop{animation:none;} .ndot.pop::after{animation:none; opacity:0;} }
.jplab{font-family:var(--fontJ); font-size:11px; opacity:.8; letter-spacing:0; text-align:center; white-space:nowrap; max-width:100%; overflow:visible; line-height:1;}   /* retro dot-pixel JP, on every tile */
/* corner "?" on the game-mode tiles — mirrors the booster-pack .pkq */
/* cup-locked game modes — the marquee stays dark until you earn the cups */
.mtile.cuplocked{filter:grayscale(.85) brightness(.7);}
.cuplock{position:absolute; inset:auto 0 34% 0; margin:0 auto; width:max-content; z-index:5; font-family:var(--fontD); font-size:10px;
  background:#1b1510ee; color:var(--gold); border:2px solid var(--gold); border-radius:6px; padding:4px 8px; display:flex; align-items:center; gap:4px; box-shadow:0 2px 0 #000a;}
.cuplock-ico{width:13px; height:13px; display:inline-block;}
.cuplock-ico svg{width:100%; height:100%;}
.mtq{position:absolute; top:3px; right:4px; z-index:3; width:20px; height:20px; padding:0;
  border:2px solid var(--ink); background:var(--cream); color:var(--ink);
  font-family:var(--fontD); font-size:12px; line-height:1; cursor:pointer;}
.mtq:hover{background:var(--gold);}
.mtile.poster{grid-column:span 6; min-height:120px; flex-direction:column; justify-content:center; gap:5px; padding:16px 6px 10px; text-align:center;
  background:linear-gradient(150deg, var(--cream), #efe0bd 70%, #e6d3a8); border:3px solid var(--ink); box-shadow:5px 5px 0 var(--ink);
  /* ⭐⭐⭐ v0.746 — overflow:hidden → VISIBLE. THIS WAS THE ALBUM AND THE MART, the two squares Odaroo
     named. Their badge was being CUT OFF AT THE CORNER by the poster's own clip, so the pip could only
     ever appear inside the artwork however it was positioned — a hit-test at the badge's own centre
     came back with the tile, and one 3px outside it came back with the empty stack behind.
     ⚠ The NOW SHOWING banner (::before) and the wallpaper are already pinned to the edges with
     left:0/right:0 and inset:0, so nothing here was relying on the clip. */
  overflow:visible;}
.mtile.poster::before{content:'NOW SHOWING'; position:absolute; top:0; left:0; right:0; font-family:var(--fontD); font-size:7px; letter-spacing:2px; background:var(--red); color:var(--cream); padding:1.5px 0; z-index:3;}
.mtile.poster .menu-art{width:54px; height:54px;}
.mtile.poster .menu-label{font-size:16px!important; letter-spacing:1px;}
.mtile.poster .jplab{font-size:9px; opacity:.7;}
.mtile.wide{grid-column:span 12; min-height:52px; flex-direction:row; align-items:center; justify-content:center; gap:10px; padding:6px 10px;}
.mtile.wide .menu-art{width:30px; height:30px;}
.mtile.wide .menu-label{font-size:14px!important;}
.mtile.wide .jplab{display:block; font-size:8px; opacity:.6; margin-left:5px; max-width:none;}
/* HOME TILES v3.18 — one icon size everywhere, English only, everything centred */
#menuStack .mtile .menu-art{width:46px; height:46px; margin:0 auto;}
/* ⭐ v0.386 — ONE SIZE FOR EVERY GAME TILE. Odaroo: the only tiles allowed to look different are the
   two non-games (CARTS + MART), which now sit on top. Every game below them is this exact box —
   a fixed height (not a min-height) so a two-line name can never make one row taller than the next. */
#menuStack .mtile.quad{height:calc(102px * var(--gs,1)); min-height:0; padding:7px 4px;}
#menuStack .mtile.quad .menu-art{width:38px; height:38px;}
#menuStack .mtile.quad .menu-label{font-size:10px!important; line-height:1.35; word-break:break-word;}
#menuStack .mtile.poster{height:calc(88px * var(--gs,1)); min-height:0; padding:15px 6px 8px;}
#menuStack .mtile.poster .menu-art{width:34px; height:34px;}
#menuStack .mtile.poster .menu-label{font-size:15px!important;}
#menuStack .mtile.wide{flex-direction:column; gap:4px; text-align:center; padding:8px 8px; min-height:74px;}
#menuStack .mtile .menu-label{text-align:center;}
.mtile.poster,.mtile.wide{transform:skewX(-3deg);}
.mtile.rowstart{grid-column:1 / span 6;}   /* ALBUM drops to a fresh row (col 1) and KEEPS its half-width span so ALBUM + SHOP ride together as two equal big buttons */
.mtile.selm.poster,.mtile.selm.wide{animation:selJit .45s steps(2) infinite;}
.mtile.trio,.mtile.duo,.mtile.sq4{transform:skewX(-3deg);}
.mtile.trio .menu-label,.mtile.duo .menu-label,.mtile.sq4 .menu-label{font-style:italic; font-weight:bold;}
.mtile.selm.trio,.mtile.selm.duo,.mtile.selm.sq4,.mtile.selm.quad,.mtile.selm.tix2{animation:selJit .45s steps(2) infinite;}
.mtile.quad,.mtile.tix2{transform:skewX(-3deg);}
@keyframes selJit{0%,100%{transform:skewX(-6deg) scale(1.05) translate(-2px,-3px)}50%{transform:skewX(-5deg) scale(1.06) translate(-1px,-2px)}}
/* ⚠⚠ v0.887 — SMOOTH MODE NEVER REACHED THIS ONE, AND IT IS THE LAST CONTINUOUS SIDEWAYS MOVER ON
   THE MAIN MENU. `selJit` runs for ever on whichever tile the cursor is sitting on, translating it
   1px left and right twice a second. SMOOTH MODE works by re-emitting every
   `prefers-reduced-motion` block under `html.lowmo` (v0.859) — so an animation with no such block
   is invisible to the toggle, which is exactly why Odaroo reported SMOOTH MODE making "no
   difference" while he could still see the menu adjusting itself.
   ⚠ It is left ON by default: 1px of life on the highlighted tile is the menu's character, and the
   pathological surfaces (the sunburst, the grain, the scratch) were made static outright in
   css/base.css. This only makes the toggle honest for the seasoning that remains. */
@media (prefers-reduced-motion:reduce){ .mtile.selm{animation:none!important;} }
#homeManny{grid-column:span 12; width:44px; height:64px; justify-self:center; transition:transform .3s;}
#homeManny svg{width:100%; height:100%; animation:mannyIdle 3.4s ease-in-out infinite;}

.mtile.sq::after{content:''; position:absolute; left:14%; right:14%; bottom:4px; height:4px;
  background:var(--tac); opacity:.8; z-index:1;}
.mtile.kin:not(.selm){opacity:.97!important; border-color:var(--tac);}
.mtile.kin:not(.selm) .menu-art{filter:drop-shadow(0 0 3px var(--tac));}
.shinyttl{
  background:linear-gradient(100deg,var(--ink) 0%,var(--ink) 43%,#fff7d0 48%,#ffffff 50%,#fff7d0 52%,var(--ink) 57%,var(--ink) 100%);
  background-size:300% 100%; -webkit-background-clip:text; background-clip:text;
  color:transparent; -webkit-text-fill-color:transparent; text-shadow:2px 2px 0 var(--paper3);
  animation:ttlShine 5s ease-in-out infinite;}
@keyframes ttlShine{0%,70%{background-position:130% 0}100%{background-position:-30% 0}}
/* ============================================================
   ⭐⭐⭐ v0.683 — THE WALLPAPER MOVES (Odaroo, 2026-08-06). "Bring back the basic and the little
   animations as you scroll — a nice background with different patterns. Every game's pattern is
   different. Have fun with it!"

   This block REPLACES the rear-projection movies (deleted from js/cards.js and css/hub.css). Each
   game's tile wears its own drifting pattern in its own accent colour, and no two are the same
   drawing OR the same direction — that difference is the whole point, so keep them distinct if a
   ninth game ever arrives.

   ⚠⚠ EVERY LOOP TRAVELS EXACTLY ONE TILE OF ITS OWN PATTERN. A drift of any other distance jumps
   at the wrap, and on a background that never stops moving a jump reads as a stutter in the whole
   menu. If you change a `background-size`, change its keyframe's distance in the same edit.
   ⚠⚠ `background-position` ONLY — never `transform` and never a JS timer. The tile is a positioned
   backdrop the compositor can hand to the GPU; twelve of these are on screen at once on the
   boulevard, so anything that costs layout costs all twelve.
   ⚠ THE TYPE STILL OUTRANKS THE PATTERN. `.mpat` is positioned at z 0, so it paints over every
   NON-positioned sibling (the standing paint-order trap) — that is why the label, the JP line and
   the featured tile's art are all lifted above it. Do not raise this above them.
   ⚠ `prefers-reduced-motion` stops the lot dead at the bottom of this block. The patterns still
   read perfectly still; the motion is seasoning, not information.
   ============================================================ */
.mpat{position:absolute; inset:0; z-index:0; pointer-events:none; opacity:.15;}
.mtile.selm .mpat{opacity:.28; mix-blend-mode:screen;}
/* OCTA-THON — the ring ropes, marching up the diagonal */
.pbrawl{background:repeating-linear-gradient(45deg,var(--tac) 0 5px,transparent 5px 11px);
  background-size:15.56px 15.56px; animation:patDiag 3.4s linear infinite;}
/* PANEL-INK — a wall of panels, rising the way the stack does */
.pshow{background:repeating-linear-gradient(0deg,var(--tac) 0 3px,transparent 3px 8px);
  background-size:100% 8px; animation:patUp 1.9s linear infinite;}
/* ICE-INK — skate lines cut across the rink, sliding sideways */
.prink{background:repeating-linear-gradient(90deg,var(--tac) 0 2px,transparent 2px 13px),
                  repeating-linear-gradient(0deg,var(--tac) 0 2px,transparent 2px 13px);
  background-size:13px 13px; opacity:.12; animation:patSlide 2.6s linear infinite;}
/* DISC-INK — the board's own fine grid, drifting slowly on both axes like a table being circled */
.pcrown{background:repeating-linear-gradient(0deg,var(--tac) 0 1px,transparent 1px 6px),
                   repeating-linear-gradient(90deg,var(--tac) 0 1px,transparent 1px 6px);
  background-size:6px 6px; opacity:.16; animation:patDrift 5.5s linear infinite;}
/* v3.19 — a distinct artwork pattern for EVERY tile (all invert on select via .mtile.selm) */
.pstar{background:repeating-linear-gradient(-45deg,var(--tac) 0 5px,transparent 5px 11px);
  background-size:15.56px 15.56px; animation:patDiagB 3.9s linear infinite;}                /* reverse chevrons */
/* TETR-INK — the checkerboard well, falling */
.pgrid2{background:repeating-conic-gradient(var(--tac) 0 25%,transparent 0 50%) 0 0/16px 16px;
  opacity:.12; animation:patFall 2.8s linear infinite;}
/* INK-PUT — the dot grid, pressed in and out on the beat instead of travelling */
.pcells{background:radial-gradient(var(--tac) 1.6px,transparent 2.2px) 0 0/11px 11px;
  animation:patPulse 1.5s ease-in-out infinite;}
/* DANCE DANCE INK — the arrow cross-hatch, walking up the lane */
.pddi{background:repeating-linear-gradient(45deg,var(--tac) 0 3px,transparent 3px 12px),
                 repeating-linear-gradient(-45deg,var(--tac) 0 3px,transparent 3px 12px);
  background-size:21.21px 21.21px; opacity:.12; animation:patUpBig 2.2s linear infinite;}
/* INK-TAR HERO — the frets, running down the neck the way the notes do */
.pnote{background:repeating-linear-gradient(90deg,var(--tac) 0 3px,transparent 3px 16px);
  background-size:16px 100%; animation:patSlide16 1.6s linear infinite;}
/* LUMEN INK — the light itself: a square lattice with a sweep passing over it */
.plum{background:linear-gradient(115deg,transparent 0 42%,var(--tac) 50%,transparent 58% 100%),
                 repeating-conic-gradient(var(--tac) 0 25%,transparent 0 50%) 0 0/22px 22px;
  background-size:280% 100%, 22px 22px; opacity:.13; animation:patSweep 4.2s linear infinite;}
.pdrop{background:radial-gradient(circle at 50% 50%,var(--tac) 2.4px,transparent 3.4px) 0 0/15px 15px;
  background-size:15px 15px; animation:patDrop15 3.2s linear infinite;}                                              /* ink drops */
.pgig{background:repeating-linear-gradient(0deg,var(--tac) 0 2px,transparent 2px 10px);
  background-size:100% 10px; animation:patUpTen 2.4s linear infinite;}                      /* GIGS — checklist rows */
.pfilm{background:repeating-linear-gradient(90deg,var(--tac) 0 3px,transparent 3px 13px);
  background-size:13px 13px; animation:patSlide 2.9s linear infinite;}                      /* ALBUM — film-strip holes */
.ptag{background:repeating-linear-gradient(135deg,var(--tac) 0 4px,transparent 4px 14px);
  background-size:19.8px 19.8px; animation:patDiagB 4.4s linear infinite;}                  /* SHOP — price-tag diagonal */
/* ⚠ one tile of travel each, and nothing eases except the two that are meant to breathe */
@keyframes patDiag   {to{background-position:15.56px -15.56px}}
@keyframes patDiagB  {to{background-position:19.8px 19.8px}}
@keyframes patUp     {to{background-position:0 -8px}}
@keyframes patUpTen  {to{background-position:0 -10px}}
@keyframes patUpBig  {to{background-position:0 -21.21px}}
@keyframes patFall   {to{background-position:0 16px}}
@keyframes patSlide16{to{background-position:16px 0}}
@keyframes patDrop15 {to{background-position:0 15px}}
@keyframes patSlide  {to{background-position:13px 0}}
@keyframes patDrift  {to{background-position:6px 6px}}
@keyframes patPulse  {0%,100%{background-size:11px 11px; opacity:.15} 50%{background-size:12.6px 12.6px; opacity:.22}}
@keyframes patSweep  {0%{background-position:180% 0, 0 0} 100%{background-position:-80% 0, 0 22px}}
@media (prefers-reduced-motion:reduce){ .mpat{animation:none!important;} }
.mtile .menu-label,.mtile .jplab{position:relative; z-index:1;}
/* --- 1950s marquee bulbs on Album & Pixel Shop --- */
.mtile.lit{box-shadow:0 0 12px 1px var(--tac), 3px 3px 0 var(--ink);}
.mlights{position:absolute; inset:1px; z-index:0; pointer-events:none;
  background:
    radial-gradient(circle,#ffe08a 1.5px,transparent 2.4px) 0 0/10px 10px repeat-x,
    radial-gradient(circle,#ffe08a 1.5px,transparent 2.4px) 0 100%/10px 10px repeat-x,
    radial-gradient(circle,#ffe08a 1.5px,transparent 2.4px) 0 0/10px 10px repeat-y,
    radial-gradient(circle,#ffe08a 1.5px,transparent 2.4px) 100% 0/10px 10px repeat-y;
  animation:bulbBlink .9s steps(2) infinite;}
@keyframes bulbBlink{50%{opacity:.35}}
/* --- pack ? help badge --- */
.pkq{position:absolute; top:5px; right:6px; z-index:4; width:23px; height:23px; padding:0;
  border:2px solid var(--ink); background:var(--cream); color:var(--ink);
  font-family:var(--fontD); font-size:14px; line-height:1; cursor:pointer;}
.pkq:hover{background:var(--gold);}
.pityrow{display:flex; flex-direction:column; gap:6px; align-items:center; margin-bottom:8px;}
.mtile{position:relative; border:3px solid var(--ink); background:var(--cream); cursor:pointer;
  display:flex; align-items:center; gap:8px; padding:6px 10px; box-shadow:4px 4px 0 var(--ink);
  overflow:visible; transition:transform .14s var(--pop), background .12s, box-shadow .12s, border-color .12s, opacity .15s;}
.mclip{position:absolute; inset:0; overflow:hidden; pointer-events:none;}
.mtile .mstripe{position:absolute; right:-16px; top:-12px; bottom:-12px; width:44px;
  background:var(--tac); transform:skewX(-18deg); opacity:.85;}
.mdeck{position:absolute; right:40px; bottom:-2px; display:flex; gap:2px; align-items:flex-end;
  z-index:2; pointer-events:none;}
/* ⚠ v0.802 — `position:relative` is load-bearing: the deck fan shows real card faces now, and
   `.holo` is `position:absolute; inset:0`. Without it the hologram escapes to the nearest
   positioned ancestor and paints the whole menu tile black. */
.mdk{position:relative; width:32px; height:42px; transform:translateY(9px) rotate(calc((var(--k) - 2.5)*3deg));
  transition:transform .2s var(--pop); transition-delay:calc(var(--k)*.035s);}
.mdeck.small{right:34px;}
.mdeck.small .mdk{width:24px; height:32px;}
.mtile.selm .mdk{transform:translateY(-9px) rotate(calc((var(--k) - 2.5)*5deg)) scale(1.08);}
#menuStack.hasSel .mtile:not(.selm){opacity:.82; transform:scale(.985);}
.mtile.selm{transform:translate(-2px,-4px) scale(1.03)!important; opacity:1!important;}
.mtile.selm .mstripe{animation:stripeSlide 1.1s linear infinite;}
@keyframes stripeSlide{from{transform:skewX(-18deg) translateX(-9px)}to{transform:skewX(-18deg) translateX(9px)}}
.mtile.sq .menu-art{transition:transform .16s var(--pop);}
.mtile.sq.selm .menu-art{transform:translateY(-10px) scale(1.4);}
.menu-stack{padding-top:16px!important;}
.pzwrap{position:relative; width:min(72vw,290px); margin:0 auto; flex:none;}
.pizza{width:100%; display:block; filter:drop-shadow(5px 5px 0 #1b151066);}
.pizza .pz{cursor:pointer; transition:opacity .15s, filter .15s;}
.pizza .pz:not(.selp){opacity:.8;}
.pizza .pz.selp{opacity:1; filter:brightness(1.18);}
.pzwrap svg{animation:pzBreathe 4s ease-in-out infinite;}
@keyframes pzBreathe{50%{transform:scale(1.015)}}
.pzlab{position:absolute; transform:translate(-50%,-50%); font-family:var(--fontD); font-size:11px;
  text-align:center; width:46%; cursor:pointer; color:var(--cream); line-height:1.45; z-index:2;
  text-shadow:1px 1px 0 var(--ink),-1px 1px 0 var(--ink),1px -1px 0 var(--ink),-1px -1px 0 var(--ink),2px 2px 0 var(--ink);}
.pzlab.sellab{color:#fff; font-size:12px;}
#homeManny{width:56px; height:80px; margin:4px auto; z-index:6; position:relative; transition:transform .3s; flex:none; cursor:pointer;}
#homeManny:hover{transform:scale(1.08) rotate(-3deg);}
#homeManny svg{width:100%; height:100%; animation:mannyIdle 3.4s ease-in-out infinite;}
@keyframes mannyIdle{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
.inkspot-glow{position:absolute; inset:-8px 0 -2px; z-index:0; pointer-events:none;
  background:radial-gradient(58% 52% at 50% 40%, rgba(122,196,255,.30), transparent 70%);
  animation:inkBreathe 5s ease-in-out infinite;}
.inkspot-fig{position:relative; z-index:1; width:100%; height:100%;}
@keyframes inkBreathe{0%,100%{opacity:.55}50%{opacity:1}}
.lucky{position:fixed; z-index:99; color:var(--gold); font-size:32px; pointer-events:none;
  animation:luckyPop 1.2s ease-out forwards; text-shadow:0 0 9px #ffd24a, 2px 2px 0 #1b1510;}
@keyframes luckyPop{from{transform:scale(.3)}40%{transform:scale(1.7) rotate(22deg)}to{transform:scale(1) translateY(-64px); opacity:0}}
.mtile.hero{grid-column:span 6; min-height:96px;}
.mtile.hero .menu-art{animation:mbob 1.15s steps(2) infinite;}
@keyframes mbob{50%{transform:translateY(-3px)}}
.mtile.hero2 .menu-art svg{animation:emblemSpin 24s linear infinite;}
@keyframes emblemSpin{to{transform:rotate(360deg)}}
.mtile.hero .menu-art{width:54px;height:54px;}
.mtile.hero .menu-label{font-size:19px!important;}
.mtile.hero2{grid-column:span 8; min-height:58px;}
.mtile.hero2 .menu-label{font-size:14px!important;}
.mtile.side{grid-column:span 4; min-height:58px; padding:6px; width:min(100%,118px); justify-self:center; align-self:center;}
.mtile.side .menu-label{font-size:12px!important;}
.mtile.sq{grid-column:span 3; min-height:56px; flex-direction:column; justify-content:center; gap:3px; padding:4px;}
.mtile.sq .menu-art{width:20px;height:20px;}
.sqlab{font-size:11px!important; text-align:center; line-height:1.2;}
.mlab,.mtile .menu-art{position:relative; z-index:1;}
.mtile.selm{background:var(--ink); color:var(--cream); border-color:var(--tac);
  transform:translate(-2px,-2px); box-shadow:6px 6px 0 var(--tac);}
.mtile.selm .menu-sub{color:var(--paper2)!important;}
/* T d-pads */
.dpad{display:flex; flex-direction:column; align-items:center; gap:5px; width:100%;}
.dpad-r{display:flex; gap:5px; justify-content:center;}
.dpad-b{width:46px; max-width:46px; height:38px; flex:none;}
/* dual d-pads for two-thumb mashing */
.dualpad{display:flex; flex-direction:column; align-items:center; gap:4px; width:100%;}
.dualpad-lbl{font-family:var(--fontD); font-size:8px; color:#f4c542; letter-spacing:.5px; animation:pulse 1s ease-in-out infinite;}
.dualpad-row{display:flex; gap:16px; justify-content:center; width:100%;}
.dualpad-row .dpad{width:auto;}
.dualpad-row .dpad-b{width:40px; max-width:40px; height:34px;}
/* ===== CHARACTER CREATOR ===== */
.cc-stage{position:relative; margin:4px auto 6px; width:100px; height:112px;}
.cc-name{position:absolute; left:50%; top:0; transform:translateX(-50%); font-family:var(--fontD); font-size:11px; color:var(--ink); background:var(--gold); padding:2px 7px; border-radius:5px; white-space:nowrap; min-height:14px; box-shadow:1px 1px 0 #0007; z-index:2; max-width:130px; overflow:hidden; text-overflow:ellipsis;}
.cc-drop{width:100px; height:98px; margin-top:16px; image-rendering:pixelated; animation:ccFloat 2.6s ease-in-out infinite; filter:drop-shadow(0 4px 4px #0003);}
.cc-drop svg{width:100%; height:100%;}
@keyframes ccFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
.cc-row{display:flex; align-items:center; gap:6px; margin:2px 0; width:100%; justify-content:center;}
.cc-lbl2{font-family:var(--fontD); font-size:9px; text-align:center; margin:7px 0 5px; letter-spacing:1.5px; color:var(--ink);}   /* COLOR sits centred between the name box and the swatches */
.cc-lbl{font-family:var(--fontD); font-size:8px; line-height:1.2; width:52px; text-align:right; color:var(--ink); flex:none;}
.cc-opts{display:grid; grid-template-columns:repeat(4,1fr); gap:8px; width:78%; margin:0 auto;}   /* the swatches line up EXACTLY with the name box, any screen */
#ccColors{display:grid!important; grid-template-columns:repeat(4,34px); gap:9px; width:auto; justify-content:center;}   /* the CALCULATOR — 16 tight circles, 4×4 */
.cc-dot{width:30px; height:30px; border-radius:50%; border:2px solid var(--ink); padding:0; cursor:pointer; image-rendering:pixelated;}
.cc-dot.sel{border-color:var(--gold); box-shadow:0 0 0 3px var(--gold);}
.cc-dot.glz{animation:ccGlz .3s steps(2) 1;}
@keyframes ccGlz{33%{transform:translate(1px,-1px) scale(1.15); filter:hue-rotate(120deg)}66%{transform:translate(-1px,1px); filter:invert(.3)}}
.cc-opt{background:var(--cream); border:2px solid var(--ink); border-radius:6px; padding:0; cursor:pointer; width:100%; aspect-ratio:1; display:flex; align-items:center; justify-content:center; overflow:hidden;}
.cc-opt.sel{border-color:var(--gold); background:#fff6df; box-shadow:0 0 0 2px var(--gold);}
.cc-mini{width:30px; height:30px; image-rendering:pixelated; animation:ccBob var(--bob,1.3s) ease-in-out infinite;}
.cc-mini svg{width:100%; height:100%;}
.cc-elbl{font-size:6px; font-family:var(--fontD); line-height:1; color:var(--ink); margin-top:-2px;}
@keyframes ccBob{0%,100%{transform:translateY(2px)}50%{transform:translateY(-2px)}}   /* symmetric — the drops bob AROUND centre, not pinned to the ceiling */
/* daily prize — click to claim */
.dailyPrize{display:flex; align-items:center; gap:8px; background:linear-gradient(90deg,var(--gold),var(--gold2)); border:3px solid var(--ink); border-radius:8px; padding:7px 10px; margin:0 auto 8px; cursor:pointer; box-shadow:3px 3px 0 var(--ink); animation:pulse 1.3s ease-in-out infinite; max-width:340px; position:relative; z-index:6;}
.dailyPrize:active{transform:scale(.97);}
.dailyPrize .dp-ico{width:30px; height:30px; flex:none; animation:ccBob .9s ease-in-out infinite;}
.dailyPrize .dp-ico.dp-big{width:60px; height:60px;}   /* the prize INK DROP at 2× — you can't miss it */
.dailyPrize .dp-ico svg{width:100%; height:100%;}
.dailyPrize .dp-txt{flex:1; text-align:left; color:var(--ink);}
.dailyPrize .dp-arrow{font-family:var(--fontD); font-size:15px; color:var(--ink); animation:pulse .8s infinite;}
/* pause + gremlins */
#pauseBtn{position:absolute; top:46px; right:6px; z-index:60; opacity:.9; width:30px; height:30px;
  background:var(--ink); color:var(--cream); border:2px solid var(--paper); font-family:var(--fontD);
  font-size:12px; cursor:pointer; display:none; align-items:center; justify-content:center; padding:4px;}
#pauseBtn svg{width:100%; height:100%;}
/* ⭐⭐⭐ v0.641 — THE CAMERA IS GONE; THE FRAME IS THE WHOLE SHOW. (Odaroo, 2026-08-05: "remove the
   camera icon. Instead show a BIGGER frame and a BIGGER inklet featured on the screen. This is the
   showcase position.")
   .hud-cam used to be a little brown camera BODY — a 75×56 box with a shutter button and a flash
   bulb — with the inklet's lens sitting inside it. All of that skin is removed. The element stays,
   and that is deliberate rather than lazy: it is the positioning context the lens is centred on, it
   is the tap target (onclick="avatarTap"), and every screen's HUD builds it by name. Deleting the
   box would mean re-homing the lens and the tap in the same change; emptying it is one edit.
   ⚠⚠ IT IS SIZED TO THE LENS NOW, not to the old camera. A 75×56 mount with a 92px lens centred on
   it would hang the portrait ~9px off the left edge of the screen. Square, at --inkav, and the lens
   lands exactly inside it at any size the var ever takes.
   ⚠ THE FLASH GOES WITH IT. `camFlash`/`hud-flash.go` and the daily star that rides the pop
   (js/ui.js) are left in place and simply never fire — that path already guards on the element
   being on screen, so a hidden bulb is a quiet no-op, not a crash. */
.hud-cam{position:absolute; left:8px; top:50%; transform:translateY(-50%);
  width:var(--inkav-lg,92px); height:var(--inkav-lg,92px); z-index:46; cursor:pointer;
  background:none; border:0; border-radius:0; box-shadow:none;}
.hud-cam::before{content:none;}
.hud-flash{display:none;}
.hud-flash.go{animation:camPop 1.1s steps(2) 1;}
@keyframes camPop{0%,55%{background:#ffffff; box-shadow:0 0 24px #ffffff, 0 0 44px #ffe9b0}100%{background:#fff7dd; box-shadow:0 0 5px #ffe9b0}}
/* (camera eyes retired — the lens does the talking) */
@keyframes camFlash{0%,92%,100%{background:#fff7dd; box-shadow:0 0 5px #ffe9b0}94%{background:#ffffff; box-shadow:0 0 18px #ffffff, 0 0 34px #ffe9b0}}
.hud-av{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); width:42px; height:42px; cursor:pointer;
  background:radial-gradient(circle at 34% 28%, #56628a, #141824 72%); border:3px solid #d9d2c0; border-radius:50%;
  box-shadow:inset 0 0 8px #000c, 0 0 0 2px var(--ink);
  display:flex; align-items:center; justify-content:center; overflow:visible;}   /* the LENS — a FRAME HOLDER: sparkles, hats + magnifiers overflow freely */
/* ⚠⚠⚠ v0.567 — THE HOME BAR’S LEVEL CHIP IS DELETED (.hud-lv, .hud-name .hud-lvin and its gold
   level-up shine). The player level and XP are removed from the game, so the bar is the portrait,
   the name and the two currencies. Do not restate the reel’s black box here again. */
.defDance{animation:defDanceA .55s ease-in-out infinite alternate; transform-origin:50% 90%;}   /* the rival gloats — a little victory jig on your defeat card */
@keyframes defDanceA{from{transform:rotate(-8deg) translateY(0)}to{transform:rotate(8deg) translateY(-4px)}}
.cupMinus{position:fixed; transform:translateX(-50%); z-index:210; font-family:var(--fontD); font-size:13px; color:var(--red);
  text-shadow:1px 1px 0 #000; pointer-events:none; animation:cupMinusA 2.6s ease-out forwards;}   /* the −15, in red, right under the count */
@keyframes cupMinusA{0%{opacity:0; transform:translateX(-50%) translateY(-4px)}10%,80%{opacity:1; transform:translateX(-50%) translateY(0)}100%{opacity:0; transform:translateX(-50%) translateY(4px)}}
.hud-av svg{width:104%; height:104%; image-rendering:pixelated; filter:drop-shadow(0 0 5px #7ab6ffaa);}   /* bigger still — fills the lens without touching the ring */
/* the GLITCH CROWN shines and cycles the whole rainbow, always */
.gcRainbow{animation:gcHue 1.8s linear infinite;}
@keyframes gcHue{0%{filter:hue-rotate(0deg) drop-shadow(0 0 1.5px #fff) brightness(1.25)}50%{filter:hue-rotate(180deg) drop-shadow(0 0 2.5px #fff) brightness(1.5)}100%{filter:hue-rotate(360deg) drop-shadow(0 0 1.5px #fff) brightness(1.25)}}
/* the glitch crown corrupts its wearer — mild rgb-split + jitter (photo-safe, no full-screen strobe) */
.iwear-gc{animation:iwearGl 1.1s steps(2) infinite;}
@keyframes iwearGl{0%,74%,100%{filter:none; transform:translateX(0)}80%{filter:drop-shadow(1.6px 0 0 #ff0040aa) drop-shadow(-1.6px 0 0 #00e6ffaa); transform:translateX(1px)}88%{filter:drop-shadow(-1.4px 0 0 #ff0040aa) drop-shadow(1.4px 0 0 #00e6ffaa); transform:translateX(-1px)}}
/* ⚠⚠⚠ v0.834 — THE HALO PULSES; IT NO LONGER BOBS. Measured on the credits screen at 616x816 this
   ONE animation was the whole of a **240 layout/s** idle cost — the screen went to 0 with it off
   and to 0 with nothing else off. It is the v0.832 rule arriving on a wearable: `.haloGlow` is a
   `<g>` INSIDE the inklet's `<svg>`, and an SVG child can never be promoted to a compositor layer,
   so a 1px `translateY` on it puts the WHOLE PAGE back through layout sixty times a second.
   ⭐⭐⭐ AND THIS SPLITS THE RULE FINER THAN v0.832 COULD: the sibling `.gcRainbow` animates a
   `filter` on the same kind of `<g>` and measured **0**. On an SVG child a FILTER is free (paint
   only) and a TRANSFORM is not. So the fix is to drop the 1px bob — imperceptible at the size a
   halo is ever drawn — and keep the glow, which is the half you can actually see and the half the
   class is named for. Do not "restore the bob" without re-measuring: it costs 240/s. */
.haloGlow{animation:haloBob 2s ease-in-out infinite;}
@keyframes haloBob{0%,100%{filter:drop-shadow(0 0 2px #f4c542)}50%{filter:drop-shadow(0 0 4px #ffe58a) brightness(1.12)}}
/* ===== THE JUKEBOX — a rubberhose radio that dances while a record spins ===== */
.jb-radio{width:190px; height:146px; margin:2px auto 0; filter:drop-shadow(4px 5px 0 #1b151033);}
.jb-radio svg{width:100%; height:100%; display:block; overflow:visible;}
.jb-radio .jb-ant{transform-origin:71px 20px; animation:jbAnt 2.6s ease-in-out infinite;}
@keyframes jbAnt{0%,100%{transform:rotate(0)}50%{transform:rotate(-9deg)}}
.jb-radio.playing{animation:jbDance .48s ease-in-out infinite;}
@keyframes jbDance{0%,100%{transform:rotate(-2deg) translateY(0)}50%{transform:rotate(2deg) translateY(-4px)}}
.jb-radio.playing .jb-needle{animation:jbNeedle 1.9s ease-in-out infinite alternate;}
@keyframes jbNeedle{from{transform:translateX(0)}to{transform:translateX(32px)}}
.jb-radio.playing .jb-noteA{animation:jbNote 1.5s ease-out infinite;}
.jb-radio.playing .jb-noteB{animation:jbNote 1.5s ease-out .7s infinite;}
@keyframes jbNote{0%{opacity:0; transform:translateY(4px)}25%{opacity:1}100%{opacity:0; transform:translateY(-12px)}}
.jb-now{text-align:center; font-family:var(--fontD); font-size:11px; color:var(--ink); background:var(--paper2); border:2px solid var(--ink); border-radius:7px; padding:5px 8px; margin:8px auto 0; max-width:270px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; box-shadow:2px 2px 0 #1b151055;}
.jb-side{font-family:var(--fontD); font-size:10px; color:var(--cream); background:var(--ink); padding:4px 9px; border-radius:6px; margin:12px 0 5px; letter-spacing:1px; display:inline-block;}
.jb-track{display:flex; align-items:center; gap:8px; padding:6px 9px; margin:3px 0; background:var(--paper2); border:2px solid var(--ink); border-radius:7px; cursor:pointer; box-shadow:2px 2px 0 #1b151033;}
.jb-track:active{transform:translateY(1px);}
.jb-track.on{background:var(--gold); box-shadow:0 0 0 2px var(--gold2) inset, 2px 2px 0 #1b151055;}
.jb-track.on .jb-go{animation:pulse .6s infinite;}
.jb-tnm{flex:1; font-size:14px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.jb-bpm{font-family:var(--fontD); font-size:8px; opacity:.55;}
.jb-bpm::after{content:'BPM'; margin-left:2px;}
.jb-go{font-size:13px; color:var(--red);}
.jb-set{display:flex; align-items:center; gap:8px; margin:8px 0; flex-wrap:wrap; justify-content:center;}
.jb-set input[type=range]{flex:1; min-width:120px; accent-color:var(--gold); height:20px;}   /* MUSIC and SFX bars: identical */
.jb-lbl{width:76px; text-align:right; letter-spacing:1px;}
.jb-val{width:38px; text-align:left;}
/* ⚠ v0.641 — the ring thickens with the lens. A 3px ring that read as a FRAME around a 69px disc
   reads as a hairline around a 92px one; the frame is meant to be the featured thing now. */
.hud-av.hud-av-lg{border-width:5px;}
.hud-av.hud-av-lg{width:var(--inkav-lg,92px); height:var(--inkav-lg,92px);}   /* 75% lens — still the trophy shelf. v0.640: --inkav (css/base.css) is the ONE size, shared with every VS corner. */
/* ⭐⭐⭐ v0.583 — `margin-top` IS MEASURED NOW, NOT GUESSED. --hudlift is written by hudLift()
   (js/ui.js) and is exactly the amount the avatar lens overhangs the top of this strip, so the reel
   lands flush in the corner with the inkling's head fully on the glass and no dead air above it.
   ⚠ THE 12px FALLBACK IS LOAD-BEARING: it is what applies for the one frame before the first
   measurement lands, and it is the old value, so the worst case is the layout this replaced rather
   than a cropped head. Do not change it to 0. */
/* ⭐⭐⭐ v0.641 — AND THE STRIP TAKES THE UNIVERSAL TOP GAP. (Odaroo, 2026-08-05: "keep the gap at
   the top but make it FOUR TIMES bigger, pushing the film reel down.")
   --hudlift is NOT that gap and never was: it is the measured overhang of the lens above the bar,
   the thing that stops the inklet's head being cropped, and it grows on its own now that --inkav
   has. The AIR above the head is --hudtop (css/base.css), the same 16px every VS corner and the
   connecting reel now sit at — which is what makes the home screen's position the standard one.
   ⚠ padding-left is derived from --inkav for the same reason the ≤430px block's is (css/base.css):
   the bar has to clear whatever the portrait currently is, not whatever it was when this was typed. */
.hudreel{overflow:visible; clip-path:none; position:relative; padding-left:calc(8px + var(--inkav-lg,92px) + 8px); gap:3px; margin-top:calc(var(--hudlift,12px) + var(--hudtop,16px)); border-top:3px solid var(--ink);}   /* v0.399 — the GIGS/BADGES screen wears the boulevard film reel too */   /* the film reel lives on HOME only — shop + gigs wear the classic bar */   /* slim headroom — full circle visible, black line above the top dots to mirror the bottom */
.hudreel::before,.hudreel::after{content:''; position:absolute; left:0; right:0; height:7px; pointer-events:none;
  /* ⭐ v0.567 — CENTRED AND MIRRORED. The hole now sits in the middle of its own 21px period and the
     layer is centred, so the top row and the bottom row line up with each other AND the whole run
     is symmetric about the middle of the bar. It used to start hard against the left edge, so the
     bar finished on a hole at one end and a gap at the other — the "sloppy placement" note. */
  /* ⭐⭐⭐ v0.573 — WHITE DOTS ON BLACK, THE SAME FILM AS THE CONNECTING STRIP. (Odaroo, 2026-08-04:
     the home reel must match the connecting/connected bar.) These holes were GOLD, which made the
     one bar the player looks at most the only strip in the game wearing a different film — and
     gold on the ink bar sat inside a gold-and-red marquee, so the run half-disappeared into its
     own surround. The colour and the period now come from the shared recipe (--film-hole,
     --film-per, --film-w in css/base.css), so this bar and the reconnect sign are one film by
     construction and cannot drift apart again.
     ⚠ THE HOLES ARE DRAWN AT DOUBLE THE SHARED PERIOD. This band is 7px tall against the name
     strip's 3px, and a 3px hole on a bar this thick reads as dust rather than perforation; the
     ratio of hole to gap is the shared one, scaled. Change the recipe and this follows.
     ⚠ CENTRED, as v0.567 made it — the hole sits in the middle of its own period, so the run is
     symmetric about the middle of the bar and the top row mirrors the bottom. */
  /* ⚠ v0.734 — the shared big gauge (--film2-*, css/base.css), and the holes are at FULL strength.
     The .95 opacity here made the one bar the player looks at most print a slightly greyer white
     than the connecting banner and the notification strip lying directly over it. */
  background-image:linear-gradient(90deg,
    transparent 0 calc((var(--film2-per) - var(--film2-w))/2),
    var(--film-hole) calc((var(--film2-per) - var(--film2-w))/2) calc((var(--film2-per) + var(--film2-w))/2),
    transparent calc((var(--film2-per) + var(--film2-w))/2) var(--film2-per));
  background-size:var(--film2-per) var(--film2-h); background-position:center; background-repeat:repeat-x; opacity:1;}   /* square sprocket holes — the bar IS a strip of film */
.hudreel::before{top:0;}
.hudreel::after{bottom:0;}
/* ⭐⭐⭐ v0.591 — THE REEL RUNS. (Odaroo, 2026-08-04: "the white dots move laterally like a real film
   reel; the black background stays still; the inkling, cups and ink do not move.")
   ⚠⚠ IT IS THE SPROCKET LAYER THAT MOVES, NOT THE BAR. The holes are a background-IMAGE on these two
   pseudo-elements, so scrolling background-position-x slides the perforations THROUGH a stationary
   black bar — which is exactly the projector illusion. Animating the bar itself (transform, or a
   moving child) would drag the portrait and the chips with it, which is the thing he asked against.
   ⚠ THE SHIFT IS EXACTLY ONE PERIOD (--film-per*2), so the loop is seamless: hole N lands precisely
   where hole N+1 was, and there is no visible jump at the wrap. Any other distance stutters.
   ⚠ background-position-x ONLY — the shorthand above writes `center` for BOTH axes, and restating
   the whole shorthand in the keyframe would drop the vertical centring the rows are drawn with. */
.hudreel::before,.hudreel::after{
  animation:hudFilmRoll 1.6s linear infinite;
  will-change:background-position;}
@keyframes hudFilmRoll{
  from{ background-position-x:0; }
  to  { background-position-x:var(--film2-per); }
}
/* ⭐⭐⭐ v0.567 — THE BAR PRINTS YOUR NAME AT THE MATCH-REEL SIZE. (Odaroo, 2026-08-04: "the username
   should be very large — the size it is in online play is the size it should be everywhere.")
   It was 16px on a wide screen and 10px on the folded phone, while the same phone printed the same
   name at 13px the moment a match started. Both numbers are now --nmsz (css/base.css), so the home
   bar, the profile hero and the game reel are one size by construction and cannot drift again.
   ⚠ The name still gives ground by ELLIPSIS, never the level chip — see the ≤430px block below. */
/* ⭐⭐ v0.588 — THE NAME IS A MEMBER OF THIS BAR, NOT A HEADLINE OVER IT. (Odaroo: "same spacing,
   same line height — it should look like it is PART OF the same bar as cups/ink. Must be CENTRED.
   Place the username right after the inkling.")
   It already sat right after the inkling; what made it read as a separate thing was the size (now
   --nmsz = the chip size, see css/base.css) and two layout hacks that are deleted here:
   ⚠ `align-self:stretch` + `padding-top:6px` was a hand-nudged vertical centring — it stretched the
     box to the full bar height and then pushed the text down by eye. The chips beside it are
     centred by the bar's own `align-items:center`, so the name now is too, and the two sit on one
     line by construction rather than by a number that was right for one font size.
   ⚠ `flex:1` + `justify-content:center` is what CENTRES it: `.hud-cur` is pushed to the far end by
     `margin-left:auto`, so the name takes the space left between the portrait and the currencies
     and sits in the middle of it. It gives ground by shrinking (hudNameFit), never by clipping. */
/* ⭐⭐ v0.591 — THE NAME SITS FLUSH AGAINST THE INKLING, NOT IN THE MIDDLE. (Odaroo, 2026-08-04:
   "put it right next to the inkling — inkling, username, cups, ink, all on one line, left.")
   v0.588's `flex:1 1 auto` + `justify-content:center` + `.hud-cur{margin-left:auto}` was a
   THREE-PART centring: the name took every spare pixel between the portrait and the currencies and
   sat in the middle of it. Undoing it takes all three, so the name is now a box exactly as wide as
   the word (`flex:0 1 auto`), packed left, and the chips follow it immediately instead of being
   thrown to the far edge.
   ⚠ `min-width:0` + nowrap STAY — hudNameFit (js/ui.js) steps the size down until a long name fits,
   and it measures the span, not the bar. Shrinking is still how this gives ground; it never clips.
   ⚠ The bar's own `padding-left:92px` is what clears the camera, so "flush against the inkling"
   needs no offset here — remove that padding and the name lands ON the portrait. */
.hud-name{font-family:var(--fontD); font-size:var(--nmsz); line-height:1; color:var(--cream);
  display:flex; align-items:center; justify-content:flex-start; flex:0 1 auto; min-width:0;
  white-space:nowrap; text-shadow:1px 1px 0 #000a;}
/* ⭐⭐ v0.594 — THE CURRENCIES ARE PINNED TO THE RIGHT EDGE, CUPS LAST. (Odaroo, 2026-08-04: "cups
   must always be at the top-right corner so the player can always see how many they've accumulated.")
   v0.591's `margin-right:auto` packed the pair immediately after the name and threw the SPARE room
   to the right — so the cups chip floated wherever the username happened to end, and a short name
   left it stranded near the middle. It is `margin-left:auto` now: the spare room goes BETWEEN the
   name and the chips, which is what puts the last chip hard against the edge no matter how long the
   name is. ⚠ The ORDER is set in hudHTML (js/ui.js) — ink first, cups last — not here; do not
   re-introduce a `row-reverse` to flip it or the two will disagree the next time one is edited. */
.hud-cur{display:flex; gap:2px; margin-left:auto; margin-right:0; padding-left:8px; align-items:center;}   /* the padding is the MINIMUM air between a long name and the ink chip — margin-left:auto collapses to nothing once the name fills the bar */
.hud-avname{position:absolute; left:50%; top:-15px; transform:translateX(-50%); font-family:var(--fontD); font-size:9px; color:#fff; background:var(--ink); padding:2px 5px; border-radius:4px; white-space:nowrap; opacity:0; pointer-events:none; z-index:20; box-shadow:1px 1px 0 #0007;}
.curfly{position:fixed; width:22px; height:22px; z-index:9990; pointer-events:none; image-rendering:pixelated;
  will-change:transform,opacity; transition:transform .55s cubic-bezier(.5,-0.2,.3,1), opacity .55s ease;   /* GPU-only — no left/top layout thrash */
  filter:drop-shadow(0 0 6px #fff8);}
.curfly svg{width:100%; height:100%;}
.chip.curbump{animation:none;}   /* the CHIP itself never balloons out of the bar */
.chip.curbump svg,.chip.curbump span{display:inline-block; animation:curBump .28s var(--pop);}   /* only the icon + the number pop — quick */
@keyframes curBump{0%{transform:scale(1)}40%{transform:scale(1.35); filter:brightness(1.6)}100%{transform:scale(1)}}
/* the count number swells as it climbs, then settles back — FAST, no lingering */
.curchip span.curgrow{display:inline-block; animation:curGrow .4s ease-out;}
@keyframes curGrow{0%{transform:scale(1)}25%{transform:scale(1.7); color:#fff; text-shadow:0 0 8px #f4c542}100%{transform:scale(1)}}
/* a shine sweep across the whole chip once the count settles */
.curchip.curshine{position:relative; overflow:hidden;}
.curchip.curshine::after{content:''; position:absolute; inset:0; pointer-events:none; background:linear-gradient(105deg, transparent 32%, #ffffffe0 50%, transparent 68%); transform:translateX(-130%); animation:curShine .62s ease-out;}
@keyframes curShine{to{transform:translateX(130%)}}
/* ambient collectible currency orbs on HOME */
.amborb{position:absolute; width:48px; height:48px; z-index:9996; cursor:pointer; touch-action:manipulation;}   /* v3.91 — ABOVE .modal-bg (400): a bubble stays tappable even while a message box is up */
/* ⭐ v3.63 — LOBBY BUBBLES. The comment above always claimed orbs ride "above dialogs", but .modal-bg is
   z-index 400, so anything at 75 was buried behind the CONNECTING window and impossible to tap. These float
   over the Bit-Brawl lobby while the match is being found — pop them in time and they pay out. */
/* v3.66 — 9996 puts a bubble above EVERY overlay (lobby dialog 400, VICTORY card 9990, toast 9995) so it is
   always tappable; the only things above it are pointer-events:none, so nothing can ever swallow the tap. */
.lobbub{position:fixed; z-index:9996; width:46px; height:46px; cursor:pointer; touch-action:manipulation; pointer-events:auto;
  animation:lobBubDrift linear forwards;}
.lobbub .orb-bub{animation:lobBubWobble 1.6s ease-in-out infinite;}
@keyframes lobBubDrift{from{transform:translate(0,0)} to{transform:translate(var(--bdx,40px),var(--bdy,-120vh))}}
@keyframes lobBubWobble{0%,100%{transform:scale(1)}50%{transform:scale(1.08)}}
.lobbub.gone{transition:opacity .3s, transform .3s; opacity:0; transform:scale(.4);}
/* ⭐ v0.379 — THE WAITING-ROOM SCRAMBLE. The same bubble, on the shared online connecting screen, but
   it is a RACE: both players are shown the very same bubbles and only the first tap on each one pays.
   It is a separate class from .lobbub on purpose — dialupStop() clears every .lobbub the moment the
   handshake lands, and these have to live on through the reveal. */
.crbub{position:fixed; z-index:9996; width:46px; height:46px; cursor:pointer; touch-action:manipulation; pointer-events:auto;
  animation:lobBubDrift linear forwards;}
.crbub .orb-bub{animation:lobBubWobble 1.6s ease-in-out infinite;}
.crbub.crbub-lost{transition:opacity .38s, transform .38s; opacity:0; transform:scale(.55);}
.crbub.crbub-lost .orb-bub{border-color:#c93a2288; box-shadow:none;}
/* the small line under the stage that says what the scramble IS — dark ink on the cream plate (4.5:1+) */
.crScramble{font-size:8px; color:#3a2f26; text-shadow:none; letter-spacing:.4px; margin-top:3px; opacity:.9;}
.orb-bub{position:absolute; inset:0; border-radius:50%; background:radial-gradient(circle at 33% 27%, #ffffffcc 0%, #ffffff3a 20%, #cfeaff2a 48%, #8fc4ff1f 72%, #4a90d214 100%); border:1.5px solid #ffffff9a; box-shadow:inset 0 0 8px #ffffff6b, 0 0 8px #9fd0ff55;}
.orb-bub::after{content:''; position:absolute; left:22%; top:15%; width:26%; height:19%; border-radius:50%; background:#ffffffe0;}
.orb-ico{position:absolute; inset:21%; image-rendering:pixelated; filter:drop-shadow(0 0 2px #f4c54288);}
.orb-ico svg{width:100%; height:100%;}
/* ⚠⚠⚠ v0.834 — THE BUBBLE RISES ON `translate`, NOT ON `top`. `transition:top` is a LAYOUT
   property in motion, so every frame of a 2.2-10s rise cost a FULL PAGE LAYOUT — measured at
   144-203 layout/s in bursts on scr-home, with long stretches of 0 in between while no bubble was
   up. ⚠⚠ THAT BURSTINESS IS WHY IT SURVIVED SO LONG: a bisect that kills one animation at a time
   blames whichever one happened to be under test while a bubble was rising, and nine unrelated
   animations each "saved 142 layout/s" on the run that found this. A cost that comes and goes has
   to be watched over TIME, not bisected.
   ⚠⚠ It must be `translate` and NOT `transform`: orbSway / orbGlitch / orbLegend / orbPop all own
   `transform` on this element, and the shorthand would replace them (the trap this project has hit
   five times). The two compose — the bubble sways sideways while it rises. */
.amborb{transition:translate linear;}   /* carbonation — the bubble RISES smoothly; JS sets the duration (faster = richer) */
.amborb.slow{animation:orbSway var(--drift,3s) ease-in-out infinite;}
.amborb.fast{animation:orbSway 1.1s ease-in-out infinite;}
.amborb.glitch{animation:orbSway 1.1s ease-in-out infinite, orbGlitch .18s steps(2) infinite;}
.amborb.legend{width:62px; height:62px;}
.amborb.legend .orb-bub{border-color:#f4c542cc; box-shadow:inset 0 0 10px #f4c54277, 0 0 12px #f4c542aa; animation:orbLegend .7s ease-in-out infinite;}
.amborb.pop{animation:orbPop .3s ease-out forwards!important;}
@keyframes orbSway{0%,100%{transform:translateX(0)}50%{transform:translateX(var(--dx,8px))}}
@keyframes orbHop{0%{transform:translate(0,0)}50%{transform:translate(var(--dx,0),-9px)}100%{transform:translate(calc(var(--dx,0)*-1),0)}}
@keyframes orbGlitch{0%{filter:drop-shadow(0 0 5px #fff9)}50%{filter:drop-shadow(2px 0 3px #ff004088) hue-rotate(90deg); transform:translateX(2px)}100%{filter:drop-shadow(-2px 0 3px #00e6ff88)}}
@keyframes orbLegend{0%,100%{transform:scale(1) rotate(-5deg)}50%{transform:scale(1.2) rotate(5deg)}}
@keyframes orbPop{0%{transform:scale(1)}50%{transform:scale(1.6)}100%{transform:scale(0); opacity:0}}
.hud-avname.show{animation:avNameShow 1.6s ease-out;}
@keyframes avNameShow{0%{opacity:0; transform:translateX(-50%) translateY(6px)}15%{opacity:1; transform:translateX(-50%) translateY(0)}80%{opacity:1}100%{opacity:0}}
/* ⚠⚠ v0.554 — THE 64px CAP IS GONE. It was a fixed ceiling on a box whose contents are a name of
   unknown length, so it clipped: measured on the folded phone, "LV25 ODAROOOOOO" wanted 163px and
   was given 64. (It was already clipping long names before the level became a box — the box only
   made it obvious.) The bar is avatar · name · currencies, so the name simply takes the room left
   between the two of them and gives ground by ELLIPSIS rather than by being cut mid-letter.
   (v0.567: the level chip that used to sit beside the name is deleted with the level system.) */
@media (max-width:430px){
  /* ⚠ v0.567 — NO font-size HERE ANY MORE. This block used to drop the name to 10px on the folded
     phone, which is the whole reason the home bar looked smaller than a match. The layout rules
     (it flexes, it ellipsises) still belong at this width; the SIZE does not. */
  /* ⚠⚠ v0.591 — `flex:1 1 auto` HERE WOULD UNDO THE LEFT-PACKING ON THE FOLDED PHONE, which is the
     only screen that matters most. A grown box packs its text left now (justify-content:flex-start)
     but it still SHOVES the cups/ink chips back out to the right edge, which is exactly the gap the
     centred bar had. It shrinks-only, like the rule above it. */
  .hud-name{max-width:none; flex:0 1 auto; min-width:0; overflow:hidden;}
  /* ⚠⚠⚠ v0.856 — `clip`, NEVER `ellipsis`. hudNameFit (js/ui.js) now guarantees the whole name is
     printed — it shrinks to a readable floor and then squeezes the horizontal scale — so the dots
     could only ever appear on a build where that function did not run. When it does not run, a name
     that is a few pixels long is a far smaller lie than a name that has visibly been cut short:
     three dots is the game telling the player their own name is too long, and it is not.
     ⚠ `overflow:hidden` STAYS. It is what stops a runaway name pushing the ink and cup chips off
     the right-hand edge of the bar; it is a containment rule, not a truncation rule. */
  .hud-name>span{min-width:0; overflow:hidden; text-overflow:clip; white-space:nowrap;}
}
/* bubble pop when catching a floating collectible */
.bubblepop{position:fixed; width:46px; height:46px; margin:-23px 0 0 -23px; border-radius:50%; z-index:145; pointer-events:none;
  border:4px solid #dff0ffdd; background:radial-gradient(circle,#ffffff44,transparent 62%); animation:bubbleRing .44s ease-out forwards;}
@keyframes bubbleRing{0%{transform:scale(.3);opacity:1}70%{opacity:.9}100%{transform:scale(1.9);opacity:0}}
.bubblepop i{position:absolute; left:50%; top:50%; width:7px; height:7px; margin:-3.5px 0 0 -3.5px; border-radius:50%; background:#dff0ff;
  transform:rotate(var(--a)) translateY(-4px); animation:bubbleSpeck .46s ease-out forwards;}
@keyframes bubbleSpeck{0%{transform:rotate(var(--a)) translateY(-4px) scale(1);opacity:1}100%{transform:rotate(var(--a)) translateY(-34px) scale(0);opacity:0}}
#saveBtn{position:absolute; top:46px; right:42px; z-index:60; opacity:.92; width:30px; height:30px;
  background:var(--cream); border:2px solid var(--ink); cursor:pointer; display:none; align-items:center; justify-content:center; padding:3px;}
#saveBtn svg{width:100%; height:100%;}
.helpq{position:absolute; top:4px; right:4px; z-index:6; width:26px; height:26px; border-radius:50%;
  border:2px solid var(--ink); background:var(--cream); color:var(--ink); font-family:var(--fontD);
  font-size:14px; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0;
  box-shadow:2px 2px 0 #1b151066;}
.helpq:hover{background:var(--gold);}
/* Ink Rink orb circles */
.orbrow{display:inline-flex; gap:5px; vertical-align:middle;}
.orbdot{width:26px; height:26px; border-radius:50%; border:3px solid var(--ink); background:transparent; display:inline-block; image-rendering:pixelated; overflow:hidden;}   /* big HAND-PIXELLED orbs */
.orbdot svg{width:100%; height:100%; display:block;}
.orbdot.full{border-color:#3a6a8a; box-shadow:0 0 9px #cfe8ffdd;}
#rinkHud{font-family:var(--fontD); font-size:14px!important; line-height:1.9;}   /* FLOOR + timer, readable at a glance */
#rinkHud .orbrow{vertical-align:-7px; display:inline-flex; gap:5px;}
.pstat{display:flex; justify-content:space-between; gap:10px; font-size:11px; padding:3px 6px; border-bottom:1px dotted #1b151044;}
.pstat span{opacity:.85;} .pstat b{white-space:nowrap;}
.pauseSeal{position:absolute; top:52%; left:50%; transform:translate(-50%,-50%) rotate(-8deg); width:118px; height:150px; opacity:.5; z-index:0; pointer-events:none;}
.pauseSeal svg{width:100%; height:100%;}
.saveGlitch{animation:inkGl .1s steps(2) infinite;}
/* cartridge-blowout save shake — small 2px jitter only, no colour strobe (photo-safe) */
#app.saveShake{animation:saveShakeA .085s steps(2) infinite;}
@keyframes saveShakeA{0%{transform:translate(0,0)}20%{transform:translate(-2px,1px)}40%{transform:translate(2px,-1px)}60%{transform:translate(-1px,-2px)}80%{transform:translate(1px,2px)}100%{transform:translate(-1px,1px)}}
/* ⭐ v3.64 — NO CLIPPING. This used to be overflow:hidden, so the idle breathing bob (inkBounce lifts the
   sprite ~4px and stretches it) and any tall headwear — hats, crowns, ears — were sliced off by the tile's
   own edge. The skater now overflows freely: it sits on the BOTTOM of its tile and grows upward into
   transparent space, drawing over the ice block above it (z-index 7 already outranks tiles/fringe/glare). */
#rinkP.rinkInk{display:flex; align-items:flex-end; justify-content:center; overflow:visible; z-index:7;}
#rinkP.rinkInk svg{width:88%; height:88%; display:block; overflow:visible; image-rendering:pixelated; filter:drop-shadow(0 0 4px #7ab6ffaa);}
.rkLand{animation:rkLandA .5s ease-out 1;}
@keyframes rkLandA{0%{transform:scale(.7); filter:brightness(2.2)}50%{transform:scale(1.16)}100%{transform:scale(1); filter:none}}
/* magical card-secure "GOT IT!" animation */
.cardMagic{animation:cardMagicIn .8s var(--pop) both; filter:drop-shadow(0 0 12px #f4c542cc);}
@keyframes cardMagicIn{0%{transform:scale(0) rotate(-25deg); opacity:0; filter:brightness(3)}55%{transform:scale(1.14) rotate(6deg); opacity:1; filter:brightness(1.3)}100%{transform:scale(1) rotate(0)}}
.rinkGot h3{animation:gotPulse .6s ease-in-out infinite alternate; text-shadow:2px 2px 0 var(--ink),0 0 12px #f4c542;}
/* ⚠⚠ v0.412 — THE CUP THAT ATE THE VICTORY SCREEN. Every currency icon in this game is a bare inline
   <svg viewBox="0 0 16 16"> with NO width and NO height on it — which is fine everywhere it is used,
   because every one of those places (.chip, .bhp-cups, .cupFall, .ftn-cupico) sizes it in CSS. The
   stage-clear wheel dropped one straight into a .tiny with nothing sizing it at all, and an SVG with
   a viewBox and no dimensions does not fall back to 16px: it takes the full width it is offered. It
   was rendering 184x184 — taller than the prize card — which is what pushed the odds, the payout and
   the NEXT STAGE button off the bottom of a panel you then had to scroll.
   The payout line is sized explicitly here, and the two guards below make sure a bare icon dropped
   into a caption or a receipt row can never do this again. */
/* ⚠ the receipt panel is auto-scaled to fit the glass (often to about two thirds), so these numbers
   are the DRAWN size, not the size on screen — 34px here lands at roughly 21px on a folded phone.
   It is a badge you read at a glance beside the words, not an illustration. */
/* ⚠ the plate is OPAQUE. At 40% over the cream receipt it let the paper through and the gold read
   1.3:1 against it — caught by crReadable, which is exactly what it is for. */
.cupPay{margin-top:6px; display:flex; align-items:center; justify-content:center; gap:6px;
  background:#1b1510; border:2px solid var(--gold); padding:4px 10px; width:fit-content; margin-left:auto; margin-right:auto;}
.cupPay b, .cupPay{color:var(--gold); font-size:12px!important; letter-spacing:.5px;}
.cupPay-ic{display:block; width:34px; height:34px; flex:none; line-height:0;}
.cupPay-ic svg{width:100%; height:100%; display:block;}
#modalBox .tiny > svg, #modalBox .rc-line svg{width:1.1em; height:1.1em; vertical-align:-.15em; display:inline-block; flex:none;}
.modal:has(.rinkGot){max-width:400px; padding:22px 20px;}   /* the prize box gets room — nothing squeezes, nothing cuts */
.rinkGot .tiny{line-height:1.7;}
.rinkGot .btn{margin-top:10px;}
/* ⭐ v3.64 — the CLEAR roulette is deliberately COMPACT: on the folded Fold the whole card (title → wheel →
   odds → NEXT → HOME) has to fit above the D-pad without a scroll. Tighter paddings, smaller wheel, and the
   buttons keep their tap target. */
.modal:has(.rkRouWin){max-width:330px; padding:12px 14px;}
.rkRouWin .tiny{line-height:1.45;}
.rkRouWin .btn{margin-top:0;}
.rkRouWin .rk-raterows{gap:2px; width:min(88%,220px); margin:5px auto 0;}
.rkRouWin .rk-rate2{font-size:8.5px; padding:2px 7px;}
.rkRouWin .rk-rate2 b{font-size:10px;}
.rkRouWin #rkRoulette .card{width:84px;}
@media (min-width:640px){ .modal:has(.rkRouWin){max-width:360px;} }
@keyframes gotPulse{to{transform:scale(1.09)}}
.magicSpark{position:absolute; width:16px; height:16px; opacity:0; pointer-events:none; z-index:3;}
.magicSpark svg{width:100%; height:100%;}
.magicSpark.s1{top:-9px; left:-9px; animation:sparkPop 1.2s ease-out .15s infinite;}
.magicSpark.s2{top:-9px; right:-9px; animation:sparkPop 1.2s ease-out .5s infinite;}
.magicSpark.s3{bottom:-6px; left:-5px; animation:sparkPop 1.2s ease-out .85s infinite;}
.magicSpark.s4{bottom:-6px; right:-5px; animation:sparkPop 1.2s ease-out 1.15s infinite;}
@keyframes sparkPop{0%{transform:scale(0) rotate(0); opacity:0}30%{transform:scale(1.25) rotate(90deg); opacity:1}100%{transform:scale(0) rotate(200deg); opacity:0}}
.saveGlitch svg{width:100%; height:100%;}
.bar-title{margin-right:32px;}
#app.g-shift{transform:translateX(3px) skewX(.6deg);}
#app.g-hue{filter:hue-rotate(28deg) saturate(1.5);}
#app.g-tear{/* v3.75 — the tear is the BAR overlay alone now; the old translateY(-2px) moved the whole game 2px mid-combo */}
#glitchBar{position:absolute; left:0; right:0; height:3px; z-index:97; display:none; pointer-events:none;
  background:repeating-linear-gradient(90deg,#c93a22 0 6px,#4a6fa5 6px 11px,#fdf6e3 11px 14px,#1b1510 14px 22px);
  mix-blend-mode:difference;}
/* ============================================================
   ⭐⭐⭐ v0.637 — THE GLITCH CROWN'S WEATHER (js/rink.js drives it)
   Ink rains on the glass at 50%, the picture splices like a stretched VHS, the tracking rolls and
   the palette drops through inverted hues. Player-only, and deliberately hard to live with.
   ⚠⚠ THE RAIN LAYER MUST NEVER EAT A TAP. `pointer-events:none` is not decoration here — the layer
   covers the entire app including the D-pad, so without it the crown would silently make the game
   unplayable and read as a control bug, not a cosmetic.
   ⚠ z-index 96 puts it UNDER #glitchBar (97) and under the pad, so the controls stay legible — the
   v0.409/v0.437 legibility floors outrank any effect (same rule as the connecting dim).
   ============================================================ */
#gcLayer{position:absolute; inset:0; z-index:96; pointer-events:none; display:none;}
#gcLayer.on{display:block;}
#gcRain{position:absolute; inset:0; width:100%; height:100%; opacity:.5;}   /* 50% — you can still read the game through it */
/* ⭐⭐ v0.652 — THE INK STAIN LAYER. `mix-blend-mode:hue` is doing the whole job: it takes the COLOUR
   of the blob and the lightness of the game underneath, so a landing drop re-tints that patch of
   screen instead of covering it. ⚠ It must be a separate canvas from #gcRain — a hue-blended black
   raindrop is mathematically invisible, so the drops and the stains cannot share one surface.
   ⚠ 50%, per Odaroo: enough to read as ink soaking in, not enough to lose the playfield under it. */
#gcStain{position:absolute; inset:0; width:100%; height:100%; opacity:.5; mix-blend-mode:hue;}
/* the hue drops — three different inversions so it never settles on one look */
#app.gcInvA{filter:invert(1) hue-rotate(190deg) saturate(1.4);}
#app.gcInvB{filter:invert(.85) hue-rotate(60deg) saturate(1.8);}
#app.gcInvC{filter:hue-rotate(300deg) saturate(2.2) contrast(1.2);}
/* ⚠ THE SPLICE IS A clip-path, NOT A TRANSFORM. Two halves torn apart by translating #app would
   move the D-pad with them and put the buttons somewhere the finger is not. Slicing the picture in
   half and offsetting only the drawn pixels leaves every hit-box exactly where it was. */
#app.gcSplit{filter:hue-rotate(160deg); clip-path:polygon(0 0,100% 0,100% 48%,2% 48%,2% 52%,100% 52%,100% 100%,0 100%);}
#app.gcTrack{animation:gcRoll .16s steps(3) infinite; filter:contrast(1.25) saturate(1.5);}
@keyframes gcRoll{0%{filter:contrast(1.25) saturate(1.5) hue-rotate(0deg);} 50%{filter:contrast(1.4) saturate(2) hue-rotate(28deg);} 100%{filter:contrast(1.25) saturate(1.5) hue-rotate(-18deg);}}
#glitchBar.on{display:block;}
/* shell game */
.shCup{position:absolute; top:26px; width:56px; height:62px; background:var(--red);
  border:3px solid var(--ink); cursor:pointer; z-index:2;
  transition:left .24s linear, transform .2s;
  background-image:linear-gradient(#fdf6e322 0 12px, transparent 12px);}
#shBall{position:absolute; top:76px; width:18px; height:18px; background:var(--gold);
  border:2px solid var(--ink); display:none; z-index:1;}
/* the ink rink */
#rinkWrap{flex:1; display:flex; align-items:flex-start; justify-content:center; z-index:5; position:relative; min-height:0; padding-top:8px; padding-right:10px;}   /* v3.58 — the timer left the top: the ice starts right up top again */
/* ⭐⭐⭐ v0.511 — content-box, AND IT IS WHY THE RIGHT-HAND ICE WAS CLIPPED. (Odaroo: "the artwork on
   the right edge of the ice blocks is being cut off.") The board's width is set in JS to exactly
   W×tile — but base.css makes every element border-box, so those pixels had to pay for the 4px
   border on each side first. The content box came out 8px narrower than the tiles laid inside it,
   `overflow:hidden` took the difference off the far edge, and the rightmost column of tiles lost
   4px of itself (the bottom row lost the same 4px, less visibly).
   ⚠ THE OVERFLOW IS NOT THE BUG AND MUST STAY — it is what clips the glare sweep and the icicle
   fringe to the rink. The bug was measuring the frame as though it were the ice. */
#rinkBoard,#rmBoard{box-sizing:content-box; position:relative; background:#e2f4fb; border:4px solid #7fc4de; outline:2px solid #4a89a8; touch-action:none; overflow:hidden;
  box-shadow:0 0 14px #9fdcf477, inset 0 0 26px #ffffffbb;   /* frozen-cave frame — icicle blues, not black */
  background-image:linear-gradient(135deg, transparent 0 44%, #ffffffb0 44% 48%, transparent 48% 76%, #ffffff85 76% 79%, transparent 79%),
  linear-gradient(45deg, transparent 0 61%, #ffffff55 61% 63%, transparent 63%),
  radial-gradient(#7fb8d022 1px, transparent 1.2px);
  background-size:100% 100%, 100% 100%, 5px 5px;}
#rinkBoard::before,#rmBoard::before{content:''; position:absolute; left:0; right:0; top:0; height:11px; z-index:3; pointer-events:none;   /* icicle fringe dripping off the top wall */
  background:linear-gradient(#d8f1fb,#8fc9e2);
  clip-path:polygon(0 0,100% 0,100% 30%,96% 100%,92% 32%,88% 78%,84% 30%,80% 92%,76% 32%,72% 70%,68% 30%,64% 100%,60% 32%,56% 75%,52% 30%,48% 95%,44% 32%,40% 68%,36% 30%,32% 88%,28% 32%,24% 72%,20% 30%,16% 98%,12% 32%,8% 70%,4% 30%,0 85%);}
#rinkBoard::after,#rmBoard::after{content:''; position:absolute; top:-20%; bottom:-20%; left:-38%; width:26%; z-index:4; pointer-events:none;   /* the glare skates by every few seconds */
  background:linear-gradient(90deg, transparent, #ffffff66 45%, #ffffffc4 50%, #ffffff66 55%, transparent); transform:skewX(-18deg);
  animation:rinkSheen 6.5s ease-in-out infinite;}
/* ⚠⚠⚠ v0.834 — THE GLARE SKATES ON `translate`, NOT ON `left`. Measured on the real rink at
   616x816: `left` cost **68 layout/s** of the screen's 86, and it is BURSTY — the glare only moves
   for 26% of its 6.5s cycle, so a 3s sample reads anywhere between 20 and 154 and a bisect blames
   seven innocent animations (v0.834's lesson, hit again here).
   ⚠⚠ THE TRAVEL IS 600%, NOT 156%. `left` moved -38% → 118% = 156% of the PARENT's width; a
   transform percentage resolves against the element's OWN box, which is `width:26%` of that parent
   — 156/26 = 600. Writing 156% here would crawl the glare a quarter of the way across the ice.
   ⚠⚠ It is the independent `translate` property, NEVER `transform`: `transform:skewX(-18deg)`
   above owns the shorthand, and a keyframe on `transform` would REPLACE the skew and stand the
   glare up straight. Same trap this project has now hit seven times. */
@keyframes rinkSheen{0%,58%{translate:0}84%,100%{translate:600%}}
.rtile{position:absolute; box-sizing:border-box; z-index:2;}   /* blocks ride OVER the cracks */
.rwall{background-color:#a9d9ec;   /* cave walls = bevelled ICE blocks */
  background-image:radial-gradient(#ffffff80 1px, transparent 1.3px), linear-gradient(180deg,#e6f7ff 0 24%, #a9d9ec 24% 66%, #79b6d2 66%);
  background-size:5px 5px, 100% 100%;
  border:1px solid #57a0c0; box-shadow:inset 2px 2px 0 #ffffffbb, inset -2px -2px 0 #4a89a866;}
.rrock{background-color:#cfeefb;   /* frosted ice cubes — the sliding stops */
  background-image:radial-gradient(circle at 30% 26%, #ffffffee 0 3px, transparent 4px), linear-gradient(160deg,#f6fcff 0 30%, #cfeefb 30% 70%, #98cde4 70%);
  border:2px solid #4a89a8; box-shadow:inset 0 -3px 0 #7cb8d4, 2px 2px 0 #4a89a855;}
.rexit{background:var(--gold); border:2px solid var(--ink);}
.rexit.rgift{overflow:visible; background:#f4c542; animation:rgShine 1.6s ease-in-out infinite, rexitGl 3.1s steps(1) infinite;}   /* the landing square: pixel STAIRS going up, glitching — the ribbon and the colour disco are retired */
@keyframes rexitGl{0%,88%{transform:none; filter:none}90%{transform:translate(1px,-1px) skewX(7deg); filter:hue-rotate(120deg)}94%{transform:translate(-1px,0); filter:invert(.15)}97%,100%{transform:none; filter:none}}
/* the EXIT — a glitchy RGB floor waiting at the top, a tiny red arrow, 出口 only */
.rexit{overflow:hidden;}
.rg-glow{position:absolute; inset:0; z-index:1; pointer-events:none;
  background:repeating-linear-gradient(45deg,#ff5a5a3d 0 6px,#4ad7ff3d 6px 12px,#7dff6a3d 12px 18px);
  animation:rgHue 2.8s linear infinite, rgJit 1.2s steps(2) infinite;}
@keyframes rgHue{to{filter:hue-rotate(360deg)}}
@keyframes rgJit{50%{transform:translate(1px,-1px)}}
/* ⭐⭐ v0.507 — THE ARROW HUGS THE DOOR INSTEAD OF SITTING IN THE MIDDLE OF A SQUARE. (Odaroo asked
   a second time for "the extra tile before the exit" to go. The framed plate went in v0.491 and the
   Japanese with it — what was still reading as a tile was the ARROW's own footprint: a big triangle
   centred in a full 30px square looks like a marked square, which on a board whose entire puzzle is
   which squares you may stop on is one more thing to work out. It is now a small mark tucked under
   the gold exit, touching it, filling about a fifth of the square instead of a third of it. */
.rg-cue{position:absolute; z-index:4; pointer-events:none; display:flex; align-items:flex-start; justify-content:center;}
/* ⭐ v0.491 — ONE RED TRIANGLE POINTING AT THE EXIT. Drawn with a clip-path rather than typed as a
   ▲, so it is the same shape and the same size on every device instead of whatever the fallback
   font happens to think a triangle looks like. It sits inside ONE square and wears no box. */
.rg-tri{width:40%; height:32%; margin-top:6%; background:var(--red); clip-path:polygon(50% 0, 100% 100%, 0 100%);
  filter:drop-shadow(0 1px 0 #1b1510) drop-shadow(0 0 4px #c93a2288);
  animation:rgArr2 .85s ease-in-out infinite;}
@keyframes rgArr2{50%{transform:translateY(-22%)}}
/* the QUIET flex — an epic/legendary run doesn't shout, it SHINES */
.rk-shine{position:relative; overflow:hidden; animation:rkGlowP 1.7s ease-in-out infinite;}
@keyframes rkGlowP{50%{filter:brightness(1.3) drop-shadow(0 0 7px #f4c542bb)}}
.rk-shine::after{content:''; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(105deg, transparent 32%, #ffffffa8 50%, transparent 68%);
  transform:translateX(-130%); animation:rkSweep 1.9s ease-in-out infinite;}
@keyframes rkSweep{55%,100%{transform:translateX(130%)}}
.rk-bestrun .rk-brline{font-family:var(--fontD); font-size:8px; letter-spacing:.5px; line-height:1.9; text-align:left; padding:0 4px;}
.rk-bestrun .rk-brline b{color:var(--gold2,#d9a232); font-size:10px;}
.rg-art{position:absolute; inset:6%; z-index:2; transition:opacity .3s; filter:drop-shadow(0 0 3px #fff8);}
.rg-art svg{width:100%; height:100%;}
@keyframes rgShine{0%,100%{box-shadow:0 0 7px #f4c542aa}50%{box-shadow:0 0 17px #ffe9a0}}
#rinkWrap{gap:8px;}
#rinkSide{display:flex; flex-direction:column; gap:4px; width:88px; z-index:5; padding-top:2px; scrollbar-width:none; order:-1;}   /* stats ride on the LEFT of the rink */
#rinkSide::-webkit-scrollbar{width:0; display:none;}
.rkbox{border:3px solid var(--ink); background:var(--paper2); padding:3px 3px 4px; text-align:center; box-shadow:2px 2px 0 #1b151055;}
.rkbox .rk-l{font-family:var(--fontD); font-size:7px; letter-spacing:1px; opacity:.65;}
.rkbox .rk-v{font-family:var(--fontD); font-size:14px; margin-top:3px;}
.rkbox .rk-av{width:38px; height:44px; margin:3px auto 0;}
#rinkInkAv{display:none!important;}   /* inklet now rides at the TOP of the left column, not floating over the boxes */
/* (.rk-inkbox / #rkLogo retired in v3.63 — the rink uses the shared .gink-game drop like every other game) */
#scr-rink .gink-game{top:6px !important; left:6px !important;}   /* the same corner seat as Superstars / Tetr-Ink / Ink Panik */
/* #rinkSide is now a pure spacer — it reserves the left column so the ice never slides under the inkling */
.rkbox .rk-av svg{width:100%; height:100%;}
/* ⭐ v0.452 — THE ALBUM'S SORT RAIL + the LEVEL UP badge.
   Three orders that are always offered and one filter that appears only when it has something to
   show; the badge is the same red dot the gigs bar wears, so "there is something waiting" reads the
   same way everywhere in the game. */
.collsorts{display:flex; justify-content:center; flex-wrap:wrap; gap:5px; margin:2px 0 8px; grid-column:1/-1;}
.collsort{font-family:var(--fontD); font-size:8px; letter-spacing:1px; padding:5px 10px; cursor:pointer;
  background:var(--paper2); border:2px solid var(--ink); color:var(--ink); position:relative;}
.collsort.on{background:var(--ink); color:var(--cream);}
@media (min-width:560px){ .collsort{font-size:11px; padding:7px 15px;} }
.collup-dot{position:absolute; top:-7px; right:-7px; min-width:15px; height:15px; padding:0 3px;
  display:flex; align-items:center; justify-content:center; box-sizing:border-box;
  background:var(--red); color:#fff; border:2px solid var(--ink); border-radius:50%;
  font-family:var(--fontD); font-size:8px; font-style:normal; line-height:1;}
.btn.collup{overflow:visible; position:relative;}
.btn.collup span{overflow:visible; position:relative;}

/* ⭐ v0.536 — THE HOME HUD CHIPS ARE WHITE, NOT PAPER. (Odaroo.)
   The marquee frame around the home screen is already gold and red, so cream-yellow chips sitting
   inside it blended into their own surround. White separates them from the frame and reads easier.
   ⚠ HOME ONLY — the shop's and the badge wall's bars keep the paper chip on purpose; those screens
   have no marquee to compete with. */
.hudreel .chip{background:#fff;}

/* ============================================================
   ⭐⭐⭐ v0.850 — THE SAVE CODE PLATE (saveCodeModal, js/ui.js)
   ------------------------------------------------------------
   Eight characters a player has to be able to read off a phone and write down
   on paper, so this is the largest type in any dialog in the game — and the
   only place a monospace face is right, because O and 0 have to be told apart
   by their shapes rather than by guessing from the word they are in.

   ⚠⚠ THE SIZE IS `clamp()` AGAINST `vw`, NOT A FIXED px. Eight characters at
   2.2px of tracking is a wide box, and the modal is only ~86% of a 375px screen
   at the folded size — a fixed size that fits the unfolded Fold 8 overruns the
   narrowest screen this game has to survive, which is exactly the fault the
   golden-rule sweep exists to catch.
   ⚠ The tracking is on the RIGHT of every glyph, so the last character carries
   a gap the first one does not; the matching left padding is what keeps the
   plate optically centred rather than sitting 2px left of its own box.
   ============================================================ */
/* ============================================================
   ⭐⭐⭐ v0.856 — ONE TYPEFACE FOR THE PASSKEY, WORN BY ALL THREE PLACES IT IS PRINTED.
   (Odaroo, 2026-08-25: "whatever the font has to say, it has to say the same, not a weird,
   different font from what is shown and what is to be entered.")

   The readout, the input box and the line under the username on the PROFILE are the same eight
   characters, and every one of them is set by THIS RULE AND NOTHING ELSE. v0.850 had `.scode` and
   `.scode-in` each carrying their own copy of the family and their own font-size clamp — 34 against
   26 — so the code a player read and the code they typed were two different pieces of typography on
   one card, which is exactly what he photographed.

   ⚠⚠ NOTHING WEARING `.pkface` MAY RESTATE font-family, font-weight, letter-spacing OR
   text-transform. The SIZE is the one thing allowed to differ, and only through `--pksz`, so a
   smaller line on the profile is still visibly the same lettering. Restating the family "just for
   this one" is how these two drifted apart in the first place.
   ⚠ The tracking sits on the RIGHT of every glyph, so the last character carries a gap the first
   one does not — the plate reads ~2px left of centre. The v0.850 cure for that was a padding-left
   on the same rule as a padding SHORTHAND, which silently overwrote it; it is not restored here,
   because a correction that has never once taken effect is not a correction.
   ============================================================ */
/* ⭐⭐⭐ v0.859 — THE PASSKEY IS SET IN THE NES FACE, WHICH THIS GAME ALREADY OWNS.
   (Odaroo, 2026-08-25: "change the passkey display font to a retro Nintendo NES style — pixel /
   bitmap font. Press Start 2P is the classic NES-style one.")

   ⚠⚠⚠ IT ASKS FOR `--fontD`, IT DOES NOT NAME THE FONT. `--fontD` (css/base.css) IS ALREADY
   'Press Start 2P' — it is the display face the whole game is lettered in, loaded once from Google
   Fonts in index.html. Re-typing the family here would be a second copy of a name that has moved
   before, and would silently stop following the game the next time it moves. v0.858 set the
   passkey in `ui-monospace` on the reasoning that a code wants a typewriter face; that was wrong
   for this game — the passkey is the most Inkville thing a player owns.
   ⚠⚠ 'Press Start 2P' IS A FULL-WIDTH FACE: every glyph advances about 1em, where the monospace it
   replaces advanced ~0.6em. Eight characters plus tracking is therefore ~1.7x wider at the same
   point size, so the sizes below came DOWN with the font change. They are measured against the
   narrowest screen this game must survive (375px), where the card's inner width is ~300px.
   ⚠ Nothing wearing `.pkface` may restate the family, the weight or the tracking. The SIZE is the
   one thing allowed to differ, and only through `--pksz`. */
:root{ --pksz:clamp(14px,5vw,22px); }
.pkface{
  font-family:var(--fontD); font-weight:400;
  font-size:var(--pksz); letter-spacing:2.2px;
  text-transform:uppercase;
}
.scode{
  color:var(--ink); background:var(--cream); border:3px solid var(--ink);
  padding:9px 6px; margin:8px auto 8px; max-width:96%;
  cursor:pointer; user-select:all; -webkit-user-select:all;
}
/* no code yet — the plate keeps its exact size so the card does not jump when one arrives */
.scode-off{opacity:.45; cursor:default; user-select:none; -webkit-user-select:none;}
/* ⚠⚠ NO FONT HERE — see .pkface above. The field carries its BOX (a field has a caret and a fill;
   a readout has neither) and the type comes from the shared class both of them wear. */
.scode-in{
  text-align:center;
  width:96%; box-sizing:border-box; padding:8px 4px;
  color:var(--ink); background:var(--paper2); border:3px solid var(--ink);
}
/* ⚠ A PLACEHOLDER MUST NOT READ AS A REAL CODE. At this size and this weight the sample
   "ABCD1234" is indistinguishable from an issued one, and a player would type it back in. */
/* ⚠⚠⚠ v0.856 — AND IT MUST BE SET IN THE PASSKEY FACE, WHICH TAKES AN EXPLICIT RESTATEMENT.
   `input::placeholder{font-family:var(--fontD)}` in css/wjam.css applies to EVERY input in the
   game and beats an inherited family, so the sample the player reads was drawn in the chunky pixel
   display face while the eight characters they typed over it came out in monospace — two different
   fonts inside ONE box. That is the sharp end of what Odaroo photographed, and it is invisible
   until somebody actually types into the field.
   ⚠ This is the ONE place `.pkface`'s family may be restated, and only because a pseudo-element
   cannot wear a class. Do not take it as licence anywhere else. */
/* ⚠ v0.859 — the family here is now the SAME one css/wjam.css's blanket `input::placeholder` rule
   already imposes, so this restatement no longer changes anything. It STAYS: it is what makes the
   agreement deliberate rather than a coincidence of two rules happening to name one font, and it is
   what will keep the placeholder on `.pkface` if that blanket rule is ever edited. */
/* ⭐⭐ v0.867 — THE DISCORD ROW ON THE GATE CARD.
   ⚠⚠ THE WHOLE ROW IS THE TAP TARGET, not the little mark. At 22px the logo alone is well under the
   ~44px a thumb reliably hits, and this is the one control on the card that a locked-out player
   actually needs — a fiddly one would read as the game refusing them twice. The row is padded to a
   comfortable height and the mark simply rides inside it.
   ⚠ `text-align:left` on the words with the mark pinned left: centred prose beside a fixed icon
   makes the second line start under the icon, which reads as a hanging indent nobody intended. */
.pk-disc{
  display:flex; align-items:center; gap:9px; text-decoration:none; cursor:pointer;
  width:92%; margin:2px auto 10px; padding:8px 10px; min-height:44px; box-sizing:border-box;
  background:#5865F214; border:2px solid #5865F2; border-radius:6px;
}
.pk-disc:active{ background:#5865F22e; transform:translateY(1px); }
.pk-disc-mark{ flex:0 0 auto; display:flex; align-items:center; }
.pk-disc-txt{
  flex:1 1 auto; min-width:0; text-align:left;
  font-family:var(--fontD); font-size:8px; line-height:1.55; color:var(--ink);
  overflow-wrap:anywhere;
}
.pk-disc-txt b{ color:#4752c4; }
@media (min-width:431px){ .pk-disc-txt{ font-size:9px; } }
.scode-in::placeholder{opacity:.3; letter-spacing:2.2px;
  font-family:var(--fontD); font-weight:400; text-transform:uppercase;}

/* ============================================================
   ⭐⭐⭐ v0.878 — .optline — RESTORE SAVE, IN OPTIONS, AS A LINE.
   (Odaroo, 2026-08-30: move it off the pause menu; it is a manual recovery tool, not a thing to
   meet on every pause.) See the note beside it in optionsMenu() (js/rink.js) for why it is a LINE
   and not a fourth `optbig` button: the card is already 528px of content in 332px of room on the
   folded Fold 8, so a fourth big button puts the new control below the fold.
   ⚠ It is deliberately LOUDER and taller than the SMOOTH MODE line directly under it. That one
     reports a STATE you glance at; this is a door somebody is being talked through over a phone,
     and it has to read as pressable and take a thumb (44px, the same tap floor #ledgInk keeps).
   ⚠ NO `filter` and no never-ending animation (v0.859) — it only lifts on press. */
#modalBox .optline{width:88%; margin:0 auto 6px; box-sizing:border-box;
  min-height:44px; display:flex; align-items:center; justify-content:center;
  padding:11px 12px; font-family:var(--fontD); font-size:11px; letter-spacing:1px;
  color:var(--cream); background:#6b4a86; border:3px solid var(--ink); border-radius:8px;
  box-shadow:3px 3px 0 var(--ink); cursor:pointer; user-select:none;
  -webkit-tap-highlight-color:transparent; transition:translate .1s, box-shadow .1s;}
#modalBox .optline:active{translate:2px 2px; box-shadow:1px 1px 0 var(--ink);}
#modalBox .optline.gpsel,#modalBox .optline:focus-visible{outline:3px solid var(--gold); outline-offset:2px;}   /* ⚠ `gpsel` is what initModalNav toggles — NOT `navsel`, which is the plain-SCREEN cursor in navScope. Two cursors, two class names (CLAUDE.md). */
