/* ============================================================
   INKVILLE — crown.css
   Crown Deluxe — the table, the parlour lobby and the combo pop

   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.
   ============================================================ */


/* ============================================================
   CROWN DELUXE — the disc-pool parlour (couronne, in 8 bits)
   The board is a CANVAS drawn at 170x170 real pixels and scaled
   up by a whole number, so every edge is a hard cartridge pixel.
   ============================================================ */
/* ⚠ NEVER give a .screen position:relative — it kills the inset:0 fill and the box collapses to its content */
/* FULL-BLEED TABLE: the canvas takes every pixel between the title band and the D-pad, and the whole
   HUD floats ON it. crFit() sizes and places it in JS (it measures the pad's real position). */
#crWrap{flex:1; min-height:0; position:relative; z-index:5; padding:0;}
#crCv{position:absolute; display:block; image-rendering:pixelated; image-rendering:crisp-edges;}
#crMsg{display:none !important; position:absolute; left:0; right:0; bottom:4px; z-index:6;   /* v0.340 — the field is clean: no text on the board */
  font-family:'Press Start 2P',monospace; font-size:8px; line-height:1.9; color:#fdf6e3;
  text-align:center; letter-spacing:.5px; min-height:15px; padding:5px 8px;
  text-shadow:1px 1px 0 #1b1510, 0 0 6px #000; pointer-events:none;}
#crMsg b{color:var(--gold);}
#crMsg .cr-red{color:#f0654a;} #crMsg .cr-grn{color:#6fd08a;}
/* HOUSE RULE: the rack lives on the LEFT with the inkling, never on the right. The table now runs
   edge to edge, so the rack floats over the parlour wall above it — readable, and never on a ring. */
#scr-crown .gink-game{top:6px !important; left:6px !important; width:62px;}   /* no banner here — corner seat, and the table gets the room */
/* ⭐⭐ v0.501 — THE CARD IS A REVEAL OVER A BLURRED TABLE, NOT A SCREEN OF ITS OWN. (Odaroo: "table
   loads, screen blurs slightly, title appears on top of the blurred scene, title fades, blur
   clears.") The whole scene softens for the two seconds the name is up and then comes back into
   focus, which is what makes it read as a curtain rather than as a loading screen.
   ⚠ SLIGHT IS THE WHOLE POINT — 2.5px. Enough to push the felt behind the words, not enough to stop
   you seeing the table you are about to play on.
   ⚠ The splash itself is `position:fixed` on <body>, so it is NOT a child of this screen and is
   never blurred by it — the words stay perfectly sharp over a soft table, which is the effect. */
#scr-crown.crBlur{filter:blur(2.5px) saturate(.92); transition:filter .6s ease;}
#scr-crown{transition:filter .67s ease;}
/* ⚠ the title band owns y 12-47 across the whole top — the rack MUST start below it or the two
   collide (the stats were printing straight through "CROWN DELUXE" on the folded phone). */
#scr-crown .gbar-stats{left:74px; right:auto; top:54px; text-align:left;
  width:calc(100% - 88px); display:flex; flex-wrap:wrap; align-content:flex-start; gap:2px 13px;}
#scr-crown .crst{display:flex; align-items:center; gap:5px; font-size:8px; letter-spacing:1px;
  opacity:.9; white-space:nowrap; text-shadow:1px 1px 0 #000;}
#scr-crown .crst b{font-size:11px; color:#f4c542; font-weight:normal; letter-spacing:0; opacity:1;}
/* ONE layout at every width: the rack is a strip beside the inkling, under the title band. The old
   wide-screen variant stacked it down the left margin — which was fine until the table grew to fill
   the width and started running underneath it. One rule is one thing that can break. */
#crStats .cr-rack{display:flex; gap:2px;}
#crStats .cr-pip{width:7px; height:7px; border:1px solid #1b1510;}
#crStats .cr-pip.red{background:#c93a22;} #crStats .cr-pip.grn{background:#3f7d4e;}
#crStats .cr-pip.off{background:#2a2320; opacity:.55;}
/* the parlour menu — START · ONLINE · BACK, and nothing else */
.cr-menu{display:flex; flex-direction:column; align-items:center; gap:9px; padding:6px 0;}
/* THE TOSS — one coin, red on one face and green on the other, tumbling end over end */
.cr-toss{display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px;}
/* ⚠ THE COIN IS NOT SHOWN UNTIL IT HAS BEEN PUT ON THE FELT — see crCoinToss's `anchor()`. It is
   `visibility`, deliberately, NOT `display`: the box must keep its size so the anchor can measure
   and place it while it is still hidden. */
.ssOverlay.cr-toss.cr-toss-hold{visibility:hidden;}
.cr-tosstitle{font-family:'Press Start 2P',monospace; font-size:13px; color:var(--gold);
  letter-spacing:3px; text-shadow:2px 2px 0 #1b1510;}
.cr-tossmsg{font-family:'Press Start 2P',monospace; font-size:9px; color:#fdf6e3; letter-spacing:1px;
  text-shadow:2px 2px 0 #1b1510; text-align:center; line-height:1.8; padding:0 14px;}
.cr-tossmsg .cr-red{color:#f0654a;} .cr-tossmsg .cr-grn{color:#6fd08a;}
/* ⭐⭐⭐ v0.643 — A REAL COIN, WITH A THICKNESS. (Odaroo, 2026-08-05: "not the one that has a circle
   that becomes small. Have a coin in 3D, for example, made out of pixels, that we flip around and
   that shows exactly which colour is which.")
   The old coin was ONE flat disc rotated on X — so at 90° it was a line, and the "flip" read as a
   circle squashing to nothing and back. This is a cylinder: two pixel faces held apart by eleven
   stacked edge slices, so the coin has a rim you can see when it turns side-on.
   ⚠⚠ THE ROUNDNESS IS A CLIP-PATH, NOT A BORDER-RADIUS. A radius draws a smooth anti-aliased curve —
   the one thing that cannot appear in this game. `--coinclip` is a stepped 28-point polygon: a
   circle drawn the way a pixel artist draws one, and it is shared by the faces AND every slice so
   the whole cylinder is cut identically.
   ⚠ Every slice is its own element at its own translateZ because a CSS box has no depth; there is
   no other way to make a solid-looking rim that survives being rotated. */
:root{--coinclip:polygon(30% 0,70% 0,70% 8%,86% 8%,86% 16%,92% 16%,92% 30%,100% 30%,100% 70%,
  92% 70%,92% 84%,86% 84%,86% 92%,70% 92%,70% 100%,30% 100%,30% 92%,14% 92%,14% 84%,8% 84%,
  8% 70%,0 70%,0 30%,8% 30%,8% 16%,14% 16%,14% 8%,30% 8%);}
.cr-coin{width:122px; height:122px; position:relative; transform-style:preserve-3d;   /* v0.343 — BIG, centre stage */
  animation:crFlip3d .67s linear infinite;}
.cr-coin .cc-face{position:absolute; inset:0; clip-path:var(--coinclip);
  display:flex; align-items:center; justify-content:center; backface-visibility:hidden;
  font-family:'Press Start 2P',monospace; font-size:13px; letter-spacing:1px; color:#fdf6e3;
  text-shadow:2px 2px 0 #1b1510;}
/* the two faces sit at the two ENDS of the cylinder, which is what gives the rim its depth */
.cr-coin .cc-red{background:#c93a22; transform:translateZ(9px);
  box-shadow:inset -6px -6px 0 #7d2415, inset 6px 6px 0 #f0654a;}
.cr-coin .cc-grn{background:#3f7d4e; transform:rotateX(180deg) translateZ(9px);
  box-shadow:inset -6px -6px 0 #22452c, inset 6px 6px 0 #6fd08a;}
/* THE RIM — eleven identical discs stepped through the coin's depth. Milled gold, so the edge is
   plainly a different material from either face and you can read the spin by it. */
.cr-coin .cc-sl{position:absolute; inset:0; clip-path:var(--coinclip);
  transform:translateZ(var(--z)); background:
    repeating-linear-gradient(90deg,#8a6416 0 3px,#d9a232 3px 6px);}
.cr-coin .cc-sl.hi{background:repeating-linear-gradient(90deg,#a87c1e 0 3px,#f4c542 3px 6px);}
@keyframes crFlip3d{from{transform:rotateX(0)} to{transform:rotateX(360deg)}}
.cr-coin.land{animation:crFlipLandR 1.2s cubic-bezier(.2,.8,.3,1) forwards;}
@keyframes crFlipLandR{from{transform:rotateX(0)} to{transform:rotateX(720deg)}}
.cr-coin.lands-grn.land{animation:crFlipLandG 1.2s cubic-bezier(.2,.8,.3,1) forwards;}
@keyframes crFlipLandG{from{transform:rotateX(0)} to{transform:rotateX(900deg)}}
.cr-coin.land .cc-face{box-shadow:inset -6px -6px 0 rgba(0,0,0,.45), inset 6px 6px 0 rgba(255,255,255,.28);}
/* the toss itself — the coin leaves the table, hangs, and drops back onto it */
.cr-cointoss{perspective:600px; display:flex; align-items:center; justify-content:center;
  animation:crCoinArc 1.8s cubic-bezier(.3,.05,.4,1) both;}
@keyframes crCoinArc{
  0%{transform:translateY(34px) scale(.6)}
  45%{transform:translateY(-26px) scale(1)}
  100%{transform:translateY(0) scale(1)}
}
/* the CROWN DELUXE tile wears the board itself: a grid of felt weave */
/* ⚠ v0.683 — .pcrown moved to css/menu.css with the rest of the animated wallpaper, so all the tile patterns live in one block. */

/* ============================================================
   THE PARLOUR — Crown Deluxe's online lobby.
   ⚠ EVERY word in here sits on its OWN SOLID PANEL. Text used to
   float over the halftone backdrop and was genuinely hard to
   read; nothing is transparent now, and every pair below was
   checked for contrast by crSelfTest before shipping.
   ============================================================ */
#scr-crlobby{background:#140f1e;}
/* ⭐ v0.383 — THE ONLINE ROOM IS A POP-UP OVER THE HOME MENU. It is deliberately small: a heading,
   the roster panel, one CLOSE button — and it NEVER scrolls, because a scrollbar in a window this
   size is how the CLOSE button ends up somewhere the player cannot reach. */
.crlPop{display:flex; flex-direction:column; gap:8px; text-align:center;}
.crlPop-h{font-family:var(--fontD); font-size:13px; letter-spacing:1px; color:#1b1510; line-height:1.2;}
.crlPop-h b{color:#1f3566;}
.crlPop .crl-panel{text-align:left; gap:6px;}
/* the status bar wraps rather than crushing REFRESH — a button whose own label is clipped is a
   button the player will not trust */
.crlPop .crl-bar{flex-wrap:wrap; gap:6px 8px;}
.crlPop .crl-ref{flex:0 0 auto !important; margin-left:auto !important;}
.crlPop .crl-row{padding:6px 8px; min-height:0;}
.crlPop .crl-nm{font-size:13px;}
.crlPop .crl-foot{font-size:10px; opacity:.85;}
.crlPop .crl-none{font-size:11.5px; padding:2px;}
.crlPop-back{margin:0 auto !important;}
/* the little lamp beside the heading: dark when we cannot hear the room, green when we can */
.crl-dot{flex:0 0 auto; width:9px; height:9px; background:#4d5a7a; border:2px solid #0a1020; font-style:normal;}
.crl-dot.on{background:#4fd18b; box-shadow:0 0 6px #4fd18b;}
#crlBody{flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch;
  padding:10px 12px 30px; display:flex; flex-direction:column; gap:10px; z-index:5; position:relative;}
.crl-panel{background:#1a2340; border:3px solid #0a1020; padding:10px 11px;
  box-shadow:0 3px 0 #0a1020; display:flex; flex-direction:column; gap:7px;}
/* panel headings */
.crl-bar{display:flex; align-items:center; gap:8px;}
.crl-head{font-family:'Press Start 2P',monospace; font-size:8px; letter-spacing:1.5px; color:#f4c542;
  text-shadow:1px 1px 0 #0a1020; flex:1 1 auto;}
.crl-sub{font-family:var(--fontD); font-size:11px; color:#b6c8ee; letter-spacing:1px; flex:0 0 auto;}
.crl-ref{margin:0 !important; flex:0 0 auto; font-size:9px !important; padding:6px 10px !important;}
.crl-foot{font-family:var(--fontD); font-size:11px; color:#b6c8ee; line-height:1.55;}
.crl-note{font-family:var(--fontD); font-size:11.5px; color:#dde6f7; line-height:1.6; background:#131c33;}
/* the people */
#crlList{display:flex; flex-direction:column; gap:6px;}
/* the row WRAPS rather than truncating: a 14-character name was being cut off on the folded
   phone, and a name you cannot read is no use to anybody in a lobby. */
.crl-row{display:flex; flex-wrap:wrap; align-items:center; gap:6px 8px; padding:8px 9px;
  background:#3b2f55; border:2px solid #0a1020; min-height:38px;}
.crl-row.crl-inv{border-color:#f4c542; background:#4a3a1c;}
/* the name NEVER shrinks below its own text — min-width:0 let flexbox squeeze a 14-character
   name until it clipped. It holds its width and the row wraps around it instead. */
.crl-nm{font-family:var(--fontD); font-size:14px; color:#ffffff; letter-spacing:1px;
  flex:0 0 auto; white-space:nowrap;}
.crl-tag{font-family:'Press Start 2P',monospace; font-size:6.5px; letter-spacing:1px; color:#9fe6bd;
  font-style:normal; flex:0 1 auto; white-space:nowrap;}
.crl-row.crl-inv .crl-tag{color:#ffe58a;}
/* ⭐ v0.419 — THE LOOK-BOOK CHIP. A player's real inkling, small and round, at the head of their
   roster row. flex:0 0 auto is not optional: without it the chip shrinks away to nothing the moment
   a long name and a CHALLENGE button share the row. */
.lookchip{flex:0 0 auto; width:34px; height:34px; border-radius:50%; overflow:hidden;
  background:#0a1020; border:2px solid #0a1020; box-shadow:inset 0 0 0 1px rgba(255,255,255,.12);
  display:block; font-style:normal;}
.lookchip svg{width:100%; height:100%; display:block;}
.crlPop .lookchip{width:30px; height:30px;}
.crl-btn{margin:0 !important; flex:0 0 auto; font-size:9px !important; padding:6px 9px !important;}
.crl-btn:disabled{opacity:.5; pointer-events:none;}

/* ============================================================
   ⭐⭐⭐ v0.460 — THE GREEN DOT, AND THE WHOLE CARD AS THE BUTTON.
   Odaroo: no CHALLENGE button and no status words — a face, a dot on its corner, a name, and a tap.
   The dot is the only status readout left, so it has to be unmistakable at 30px on a folded phone:
   a fat ring in the card's own background colour separates it from the inkling behind it.
   ⚠ .lookchip is a round box with overflow:hidden — a dot on its corner would be clipped straight
   off. `haslive` lets the box overflow and moves the round clipping onto the svg instead.
   ============================================================ */
.lookchip.haslive{overflow:visible; position:relative;}
.lookchip.haslive>svg{border-radius:50%; overflow:hidden;}
/* ⚠ the whole room is scaled down by lobbyFit() — measured at 0.64 on the folded phone — so these
   numbers are NOT what lands on the glass. Sized against the 30px chip rather than in the abstract:
   the dot is deliberately a good third of the face, or it disappears once the fit shrinks it. */
.crl-live{position:absolute; right:-2px; bottom:-2px; width:13px; height:13px; border-radius:50%;
  box-sizing:border-box; border:2px solid #3b2f55; background:#6f7a8c; font-style:normal;
  display:block; pointer-events:none; z-index:2;}
.crl-live.free{background:#33d17a; box-shadow:0 0 0 1px #0a102055, 0 0 6px #33d17a99;}
.crl-live.sent{background:#f4c542;}                    /* asked already — waiting on their answer */
.crl-live.busy{background:#c05a44; box-shadow:none;}   /* in a match: not to be disturbed */
.crlPop .crl-live{width:12px; height:12px; border-color:#ffffff;}   /* the room's white cards */
.crl-row.crl-me .crl-live{border-color:#4a3a1c;}
.crlPop .crl-row.crl-me .crl-live{border-color:#ffffff;}
/* a card you can act on says so by feel — everything else is inert and must not pretend otherwise */
.crl-row.crl-can{cursor:pointer;}
.crl-row.crl-can:active{transform:scale(.985); filter:brightness(1.12);}
.crl-row:not(.crl-can){cursor:default;}
.crl-none{font-family:var(--fontD); font-size:12.5px; color:#dde6f7; line-height:1.6; padding:4px 2px;}
/* the talk */
#crlChat{height:136px; overflow-y:auto; background:#15102a; border:2px solid #0a1020; padding:8px 9px;
  display:flex; flex-direction:column; gap:5px;}
.crl-line{font-family:var(--fontD); font-size:13px; color:#ffffff; line-height:1.5; word-break:break-word;}
.crl-line b{color:#9fe6bd; margin-right:3px;}
.crl-line.mine b{color:#f4c542;}
#crlSay{display:flex; gap:7px; align-items:stretch;}
#crlIn{flex:1; min-width:0; font-family:var(--fontD); font-size:15px; padding:9px;
  border:3px solid #0a1020; background:#fdf6e3; color:#1b1510;}
#crlIn::placeholder{color:#6b6355;}
#crlSay .btn{margin:0 !important; flex:0 0 auto;}
/* the green confirm button keeps the house colour but takes DARK text here — cream on green
   measured 3.2:1, which is not readable. Dark on the same green is about 8:1. */
#crlSay .btn.ok span,.crl-btn.ok span{color:#0d1a10 !important; text-shadow:none !important;}
@media (min-width:640px){
  #crlBody{max-width:680px; margin:0 auto; width:100%;}
  #crlChat{height:190px;}
}

/* ============================================================
   READABILITY FIXES — every one of these was found by
   crReadable() before shipping, not by a squint at a screenshot.
   ============================================================ */
/* the title band's small lines had no dark ring, so gold-on-cream measured 1.3:1 and the
   Japanese line 1.5:1. The wordmark already had one; now they all do. */
#scr-crown .gt-txt i, #scr-crown .gt-sub{
  text-shadow:1px 1px 0 #1b1510, -1px 1px 0 #1b1510, 1px -1px 0 #1b1510, -1px -1px 0 #1b1510;}
#scr-crown .gt-sub{color:#ffd75e;}
#scr-crown .gtitle .gt-txt i{line-height:1.15;}   /* v0.335 - the JP line measured 2px taller than the band on wide; tighter leading fits it without growing the box */
/* the parlour dialog: a gold heading on cream paper measured 1.8:1 */
.cr-menu h3.disp{text-shadow:2px 2px 0 #1b1510, -1px -1px 0 #1b1510;}
/* cream text on the green confirm button measured 3.2:1. The house colour stays; the letters
   go dark, which is about 8:1 on the same green. */
.cr-menu .btn.ok span, #crlSay .btn.ok span, .crl-btn.ok span{color:#0d1a10 !important; text-shadow:none !important;}

/* the 'where the room lives' panel — an address you can paste from anywhere */
#crlConnRow{display:flex; gap:7px; align-items:stretch;}
#crlUrl{flex:1; min-width:0; font-family:var(--fontD); font-size:13px; padding:8px;
  border:3px solid #0a1020; background:#fdf6e3; color:#1b1510;}
#crlUrl::placeholder{color:#6b6355;}
#crlConn .crl-foot{word-break:break-all;}

/* the VOID stamp measured 4.1:1 on the receipt paper — a shade deeper carries it past 5:1 */
.receipt .rc-stamp.red{color:#a82626;}

/* the simpler ONLINE page: the roster IS the page, and YOU are the first line of it */
.crl-row.crl-me{background:#4a3a1c; border-color:#f4c542;}
.crl-row.crl-me .crl-nm{color:#ffe58a;}
.crl-row.crl-me .crl-tag{color:#f4c542;}
.crl-conn{padding:7px 9px;}
.crl-conn .crl-foot{word-break:break-all; font-size:10px;}
#crlStatusTxt{flex:1 1 auto;}

/* ============================================================
   CROWN DELUXE — the room runs UNDER the D-pad.
   The canvas covers the whole screen and the pad floats on it at
   little over half strength, so the carpet carries on behind the
   buttons instead of stopping dead against a pale slab. That hard
   dark-to-light edge across the middle of the screen was the least
   comfortable thing about sitting with this game for a while.
   ============================================================ */
/* ⭐⭐ v0.676 — EVERY DURATION ON THIS SCREEN IS AT 75% SPEED (×1.33). The dial is CR_SLOW in
   js/crown.js; the table's animations were stretched to match by hand, in one pass, so the coin,
   the discs, the combo pop, the turn pill and the moods all move together. If the dial ever moves
   again, these move with it — they are the same 75%, written in the language CSS understands.
   ⚠ The CONNECTING banner (#crLineBadge) is deliberately UNTOUCHED: it is the shared online ritual
   every game walks through, not Disc-Ink's own motion, and slowing it here would slow it for five
   other games. */
/* v0.339 — the toss coin FLIES up under the inkling and a small badge stays there all match */
.cr-coin.fly{position:fixed !important; left:20px !important; top:96px !important; transform:scale(.34) !important;
  transition:left .73s cubic-bezier(.3,.8,.3,1), top .73s cubic-bezier(.3,.8,.3,1), transform .73s cubic-bezier(.3,.8,.3,1);}
/* ⚠⚠ v0.647 — THE COIN IS THE PORTRAIT'S WIDTH AND SITS ON THE PORTRAIT'S EDGE.
   These were `top:100px; left:8px; width:62px` — a guessed drop, a guessed inset and a width from
   before the universal portrait size (--inkav) existed. Three consequences, all visible in Odaroo's
   screenshot: the coin was off the inklet's centre by the 2px the insets differ plus half the 16px
   the widths differ; and on any frame where the inklet's artwork had not laid out yet, vsNamePlace's
   measured `top` never got written and this 100px stood — which is the red/green disc sitting across
   the inklet's face. The starting values now agree with the portrait, and crCoinPlace re-measures
   them on the tick so a first frame that measured nothing cannot leave it wrong. */
#crMyColor{position:absolute; top:calc(100px * var(--gs,1)); left:6px; width:var(--inkav,69px); z-index:8;
  display:flex; align-items:center; justify-content:center; gap:5px; pointer-events:none;}
#crFoeColor{position:absolute; top:calc(100px * var(--gs,1)); right:6px; width:var(--inkav,69px); z-index:8;
  display:flex; align-items:center; justify-content:center; pointer-events:none;}
#scr-crown #crFoe{position:absolute; top:6px !important; left:auto !important; right:6px !important; width:var(--inkav,69px); z-index:8; pointer-events:none;}
/* v0.350 — the wordmark rides TOP CENTRE, between the two portraits */
#crTitleMid{position:absolute; top:calc(8px + env(safe-area-inset-top,0px)); left:50%; transform:translateX(-50%);
  z-index:8; text-align:center; pointer-events:none; width:min(54vw,244px);
  display:flex; align-items:center; justify-content:center; gap:6px;}   /* v0.357 — checkered flags either side, like every other marquee */
#crTitleMid .crtm-txt{display:block; min-width:0;}
#crTitleMid .gt-flag{flex:0 0 auto;}
#crTitleMid .crtm-txt b{display:block; font-family:var(--fontD); font-size:clamp(9px,2.9vw,13px); letter-spacing:1px; color:#f4c542;
  text-shadow:2px 2px 0 #1b1510, -1px -1px 0 #1b1510, 1px -1px 0 #1b1510, -1px 1px 0 #1b1510; white-space:nowrap;}
#crTitleMid .crtm-txt i{display:block; font-family:var(--fontJ); font-style:normal; font-size:8px; letter-spacing:1px; color:#fff3c0;
  text-shadow:1px 1px 0 #1b1510; margin-top:2px; white-space:nowrap;}
#crFoe svg{width:100%; image-rendering:pixelated;}
/* ⭐⭐⭐ v0.762 — THE WINCE EASES BOTH WAYS, AND IT IS SMALLER. (Odaroo, 2026-08-07: the screen
   "glitches and resizes for a moment" between turns.) Measured on a real online table: the losing
   corner's portrait went 78px → 62.4px over 400ms, held 1.6s, and then came back to 78px IN ONE
   FRAME. That snap is the glitch, and it was structural rather than a tuning mistake: the
   `transition` was declared ON `.cr-sad`, so the moment the class came off the transition came off
   with it and there was nothing left to animate the return.
   ⚠⚠ THE TRANSITION BELONGS TO THE PORTRAIT, NOT TO THE MOOD. Any state a `.gink` can enter and
   leave must declare its easing on the element, or the exit is a jump every time.
   ⚠ It is scale .9, not .8 — a fifth of a HUD portrait is a resize you notice on a phone; a tenth
   reads as a flinch, which is what it is for.
   ⚠ It does NOT fight the reaction animations (`.hype`, `.sing`): a running animation outranks a
   transition on the same property, so the pop still pops. */
#crInk, #crFoe{transition:transform .4s ease, filter .4s ease;}
.gink.cr-sad{transform:scale(.9) !important; filter:saturate(.6) brightness(.9);}
#crMyColor span{display:none;}   /* v0.343 — no YOU tag; the inkling says who you are */
.crlob-row{display:flex; align-items:center; justify-content:center; gap:10px; margin:12px 0 10px;}
.crlob-f{width:88px; text-align:center;}
.crlob-ink{width:74px; height:84px; margin:0 auto;}
.crlob-ink svg{width:100%; height:100%; image-rendering:pixelated;}
.crlob-nm{font-family:var(--fontD); font-size:7.5px; letter-spacing:1px; color:var(--ink); margin-top:3px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.crlob-vs{font-family:var(--fontD); font-size:18px; color:var(--red); text-shadow:2px 2px 0 #00000033;}
/* v0.346 — CROWN DELUXE lobby wears its own colours: sea-green board, brass trim */
#modalBox .cr-lobby{background:linear-gradient(170deg,#dff0ea,#c8e0d6); border-color:#2b6a5f;}
#modalBox .cr-lobby h3.disp{color:#1f5a4e !important; text-shadow:2px 2px 0 #ffffffaa;}
#modalBox .cr-lobby .crlob-vs{color:#2b6a5f;}
#modalBox .cr-lobby .crlob-nm{color:#1f4a42;}
#modalBox .cr-lobby .tiny{color:#2f5c52;}
#crMyColor .cmc-ring, #crFoeColor .cmc-ring{width:32px; height:32px; flex:none; border-radius:50%; background:var(--cc,#c93a22);
  border:3px solid #1b1510; box-shadow:inset 0 0 0 3px #f2ead6cc, 1px 2px 0 #1b151055; display:block;
}   /* v0.346 — STATIC: small, clean, still */
@keyframes cmcSpin{from{transform:rotateY(0)}to{transform:rotateY(360deg)}}
#crMyColor span{font-family:var(--fontD); font-size:7px; letter-spacing:1px; color:#1b1510;
  background:#f2ead6e0; border:1px solid #1b1510; padding:2px 4px;}
body.cr-room #gpad{backdrop-filter:none;}   /* v0.353 — the pad keeps the player's own opacity here too */
/* ⭐⭐ v0.409 — THE PAD IS ALWAYS THERE IN THE PARLOUR. It used to be knocked back to .92 on top of
   the player's own (default .5) opacity, with no plate behind it, over a dark table — which on the
   rival's turn read as the controls having disappeared altogether. Full strength here, and a soft
   dark plate under the cross so it never sinks into the felt. padForce() floors the opacity to match. */
body.cr-room #gpad .gpb, body.cr-room #gpad .gpss{opacity:1;}
body.cr-room #gpad .gp-d, body.cr-room #gpad .gp-d2{filter:drop-shadow(0 0 calc(4px * var(--gs,1)) rgba(13,10,8,.85));}
body.cr-room #gpad::before{content:""; position:absolute; inset:0; background:transparent; pointer-events:none;}

/* ============================================================
   v0.358 - THE LOBBY DOORSTEP FITS ON THE GLASS, ALWAYS.
   Both lobbies (BRAWL and CROWN DELUXE) were tall enough that on a real phone - where the browser's
   own address bar eats 80-120px of the viewport the desktop preview never loses - the bottom button
   fell into the reserved D-pad strip and the box started scrolling. Odaroo had to scroll to find DIP.
   The fix is a compact variant, applied to the lobby dialogs only: shorter VS stage, smaller portraits,
   tighter tags and slimmer buttons, plus a hard cap so the box can NEVER be taller than the room above
   the pad. Everything is measured against the SHORT viewport, not the roomy one.
   ============================================================ */
#modalBox.lobfit{max-height:calc(100% - 4px); overflow:visible; padding:8px 12px;}
#modalBox.lobfit .disp{font-size:11px !important; margin:0 0 2px !important;}
#modalBox.lobfit .lob-hub{height:auto !important; min-height:0 !important; margin:2px 0 0 !important; padding:4px 0 2px;}
/* ⭐⭐ v0.383 — THE ROULETTE STAGE IS NEVER SQUASHED FLAT.
   .lobfit collapsed every .lob-hub to its own content (min-height:0) — and .lob-stage CLIPS
   (overflow:hidden, so the painted sky stays inside its frame). Together that left the stage barely
   taller than one inkling, so the bouncing silhouettes were shaved off at the top and bottom of
   every hop and the name underneath sat on top of the artwork instead of below it.
   The stage keeps a real height on both folds, the inklings stand on the floor with headroom to
   bounce into, and the name gets its own clear strip along the bottom. lobbyFit() still scales the
   whole window down if a short screen cannot take it, so nothing here can push anything off-screen. */
#modalBox.lobfit .lob-stage.lob-hub{min-height:172px !important; padding:14px 8px 6px !important;}
/* ⚠ the tag stops 15px short of the floor ON PURPOSE: #cDots hangs on its OWN line at top:100%,
   so a name flush to the bottom would push the breathing "…" straight out through the clip. */
.lob-stage .lob-fighter{justify-content:flex-end; padding-bottom:34px;}
.lob-stage .lob-tag{bottom:16px;}
#modalBox.lobfit .lob-stage .lob-fighter{padding-bottom:32px;}
#modalBox.lobfit .lob-stage .lob-tag{bottom:15px;}
@media (max-height:660px){ #modalBox.lobfit .lob-stage.lob-hub{min-height:146px !important;} }
#modalBox.lobfit .lobby .lob-fighter .lob-art,
#modalBox.lobfit .lob-art.lob-big{width:52px !important; height:58px !important;}
#modalBox.lobfit .lob-tag{font-size:8.5px !important; line-height:1.3; padding:0 6px 2px !important; min-height:0 !important; max-width:104px;}
#modalBox.lobfit .lob-vs{font-size:17px !important; bottom:auto !important; top:50%; transform:translate(-50%,-50%);}
#modalBox.lobfit .lob-actions{gap:5px !important; margin-top:7px !important;}
#modalBox.lobfit .lob-actions .btn{padding:7px 14px !important; min-height:0 !important;}
#modalBox.lobfit .lob-actions .btn span{font-size:12px !important;}
#modalBox.lobfit .connLine{font-size:8px !important; margin:1px 0 0 !important;}
@media (max-height:700px){
  #modalBox.lobfit .lobby .lob-fighter .lob-art,
  #modalBox.lobfit .lob-art.lob-big{width:44px !important; height:48px !important;}
  #modalBox.lobfit .lob-actions .btn{padding:5px 12px !important;}
  #modalBox.lobfit .lob-actions .btn span{font-size:11px !important;}
  #modalBox.lobfit .lob-vs{font-size:14px !important;}
}

/* ============================================================
   v0.360 - THE CROWN DELUXE COMBO POP. Lands over the table, grows with the run, fades on its own.
   ============================================================ */
/* ⭐ v0.409 — HEAVIER. The count is one glance long and the "x" was the thinnest thing on it, so the
   whole line now carries a stroke as well as its outline: the pixel font has no bold weight of its
   own, and -webkit-text-stroke is what gives it one. */
/* v0.409 — it drops to the middle of the felt: the speech bubbles now live in the top third and the
   count was landing on top of them. */
#crCombo{position:absolute; left:50%; top:40%; transform:translate(-50%,-50%) scale(0); z-index:9; pointer-events:none;
  font-family:var(--fontD); font-size:22px; letter-spacing:2px; color:#f4c542; font-weight:700;
  -webkit-text-stroke:1.5px #f4c542; paint-order:stroke fill;
  text-shadow:3px 3px 0 #1b1510, -2px -2px 0 #1b1510, 2px -2px 0 #1b1510, -2px 2px 0 #1b1510,
              0 3px 0 #1b1510, 3px 0 0 #1b1510, 0 -3px 0 #1b1510, -3px 0 0 #1b1510;
  opacity:0;}
#crCombo.go{animation:crComboPop 1.4s cubic-bezier(.2,1.5,.4,1) forwards;}
@keyframes crComboPop{
  0%{transform:translate(-50%,-50%) scale(.2) rotate(-8deg); opacity:0}
  22%{transform:translate(-50%,-50%) scale(calc(var(--cs,1) * 1.15)) rotate(3deg); opacity:1}
  38%{transform:translate(-50%,-50%) scale(var(--cs,1)) rotate(0deg); opacity:1}
  78%{transform:translate(-50%,-62%) scale(var(--cs,1)); opacity:1}
  100%{transform:translate(-50%,-86%) scale(calc(var(--cs,1) * .9)); opacity:0}
}

/* ============================================================
   v0.362 - THE ONLINE FURNITURE: the waiting dots and the little chat.
   Both are sized to stay OFF the felt on either fold - the chat sits under the pad's left shoulder
   on the narrow screen and in the empty left column when the table is wide.
   ============================================================ */
.crdots{display:inline-flex; gap:3px; margin-left:5px; vertical-align:middle;}
.crdots i{width:4px; height:4px; background:currentColor; display:block; border-radius:50%;
  animation:crDot 1.4s ease-in-out infinite;}
.crdots i:nth-child(2){animation-delay:.21s}
.crdots i:nth-child(3){animation-delay:.43s}
@keyframes crDot{0%,60%,100%{opacity:.25; transform:translateY(0)} 30%{opacity:1; transform:translateY(-3px)}}

/* ⭐ v0.374 - THE RECONNECTING SIGN. It appears only when the room has genuinely stopped answering,
   and takes itself away the moment a knock lands. Pinned to the TOP so it can never sit over the
   D-pad or the message plate, narrow enough for the folded phone, and it does not swallow taps -
   it is a notice, not a button. Cream on deep ink measures well past the contrast floor crReadable
   holds every screen to. */
.cr-linebadge{
  position:fixed; left:50%; top:calc(env(safe-area-inset-top, 0px) + 6px);
  transform:translateX(-50%); z-index:470; pointer-events:none;
  background:#1b1510; color:#fdf6e3; border:2px solid #c93a22;
  font:700 10px/1 'Courier New',monospace; letter-spacing:.09em;
  padding:6px 10px; white-space:nowrap; max-width:92vw;
  box-shadow:0 2px 0 #000; animation:crLineBlink 1.6s ease-in-out infinite;
}
@keyframes crLineBlink{0%,100%{opacity:1} 50%{opacity:.55}}

/* ⭐⭐⭐ v0.412 — EVERY LINE OF CHAT STYLING IS GONE. The panel, the six emoticons, the CHAT and
   SEND buttons, the text box, the speech bubbles and all five games' per-screen overrides of them.
   Odaroo pulled the whole system; nothing builds any of these elements any more. */

/* ============================================================
   v0.364 — THE ONLINE MATCH SAYS WHAT IS HAPPENING.
   ============================================================ */
/* WHOSE TURN IT IS. One banner under the wordmark, in the colour of whoever is on. Opaque plate on
   purpose: it sits over artwork and every word on it has to be readable at a glance. */
/* ⭐⭐ v0.425 — THE TURN BANNER SPANS THE FIELD, AT THE TOP. (Odaroo.)
   It was a small pill squeezed between the two portraits — `max-width:calc(100% - 150px)` is the
   two corners reserved — so on the folded phone it had about 220px to say "THE THIMBLE'S TURN" in
   and rode very close to the table. Whose turn it is is the single most important thing on the
   screen at any moment, so it now runs as a full-width bar across the top of the playing field,
   BELOW the two portraits rather than between them, and the text is centred in it.
   ⚠ Its `top` is set from JS (crTurnPlace) off the measured bottom of the portraits, not from a
   constant here: the portraits grow with --gs and a hard-coded number goes wrong on one fold or the
   other. This rule only supplies the fallback. */
/* ⭐ v0.430 — A COMPACT PLATE, NOT A BAR ACROSS THE SCREEN.
   v0.425 made this full-bleed so it would read as the bottom edge of the HUD. Odaroo's answer to
   that is that it looks like a bar across the screen, because it is one: a solid gold band edge to
   edge for the whole of your turn is the loudest thing on the table and it is saying the smallest
   thing on it. It is now a small plate that hugs its own words, centred between the two portraits.

   ⚠ HOW IT IS CENTRED MATTERS. The obvious way is left:50% + translateX(-50%), and that is exactly
   what this element used to do — and the pulse animation then clobbered the transform and threw the
   whole thing half a screen to the left every time it breathed (the note that used to live here).
   So the OUTER div stays full-width and does nothing but centre with flex; the visible plate is an
   inner element. No transform is involved in the centring, so the pulse cannot break it. */
/* ⭐⭐⭐ v0.470 — THE TURN STRIP TOUCHES THE TOP EDGE. Odaroo: every status line — YOUR TURN,
   THEIR TURN, THEIR SHOT… — lives in one film-reel strip hard against the top of the screen, not
   floating over the play area. It was parked 104px down, which put it in the middle of the field
   and cost the board that much height. Full width now, and the strip IS the reel. */
#crTurn{position:absolute; top:env(safe-area-inset-top,0px); left:0; right:0;
  z-index:9; pointer-events:none; display:flex; justify-content:center;
  background:none; border:0; box-shadow:none; padding:0;}
#crTurn .crt-in{display:inline-block; max-width:calc(100% - 24px);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-align:center;
  font-family:'Press Start 2P',monospace; font-size:9px; letter-spacing:1px;
  padding:6px 12px; background:#1b1510; border:2px solid #0d0a08; border-radius:3px;
  color:#fdf6e3; box-shadow:0 2px 0 #0d0a08;}
#crTurn.mine   .crt-in{color:#1b1510; background:#f4c542; border-color:#8a6416; animation:crTurnPulse 2s ease-in-out infinite;}
#crTurn.theirs .crt-in{color:#ffd9a0; background:#241d16;}
#crTurn.rolling .crt-in{animation:none; color:#fdf6e3; background:#1b1510; border-color:#0d0a08;}
@keyframes crTurnPulse{0%,100%{transform:scaleY(1);} 50%{transform:scaleY(1.08);}}
@media (min-width:640px){ #crTurn .crt-in{font-size:11px;} }

/* ============================================================
   ⭐ v0.365 — THE SAME BANNER, ON THE BRAWL FLOOR.
   A brawl has no turns — both players play at once — so on this screen the banner answers the
   question that DOES matter: who holds the showstopper stage, because whoever holds it has frozen
   the other one. It sits just UNDER the health bar (never over it) and never over the cards.
   ============================================================ */
/* (.screen is already position:absolute, so it is its own anchor — nothing more is needed here) */
/* the `top` here is only a sane starting point — bbTurnTag MEASURES the health bar and sets the real
   one, because a guessed offset put this banner inside the bar on the folded phone */
/* the brawl's banner is the same compact plate, just higher up its own screen */
#crTurn.bb{top:62px; z-index:11;}
#crTurn.bb.rolling .crt-in{background:#241d16; color:#ffe9c2; border-color:#0d0a08;}

/* WHO BREAKS — the second line of the coin toss, so both players read the same call. */
.cr-tossbrk{margin-top:8px; font-family:'Press Start 2P',monospace; font-size:11px; letter-spacing:2px;
  color:#f4c542; text-shadow:2px 2px 0 #1b1510; opacity:0; transform:scale(.7);
  transition:opacity .29s ease, transform .29s cubic-bezier(.2,1.6,.4,1); text-align:center;}
.cr-tossbrk.on{opacity:1; transform:none;}

/* THE RIVAL'S COMBO reads as theirs, not yours. */
#crCombo.foe{color:#f0654a;}


/* THE WAY BACK TO THE ROOM, on the end-of-match receipt. */
.cr-endcmd{display:flex; gap:5px; align-items:center; justify-content:center; margin-top:9px; flex-wrap:wrap;}
.cr-endcmd input{flex:1 1 150px; min-width:120px; max-width:200px; font-family:var(--fontB,inherit); font-size:10px;
  padding:5px 6px; background:#fdf6e3; border:2px solid #1b1510; color:#1b1510; text-align:center;}
.cr-endcmd input::placeholder{color:#7a6a52;}

/* THE ROSTER SAYS WHAT EACH PLAYER IS DOING. */
.crl-tag.free{color:#9fe6bd;}
.crl-tag.busy{color:#ffc98a;}
.crl-tag.ingame{color:#ff9a7a;}
.crl-row .crl-btn[disabled]{opacity:.5; filter:grayscale(.6);}

/* ============================================================
   v0.371 — THE CHALLENGE CARD THAT FINDS YOU ANYWHERE.
   It sits above EVERYTHING, gamepad and chat included (the pad is z460, the table chat z470), because
   a challenge you cannot reach is a challenge that never arrived. Sized in `calc(Npx * var(--gs,1))`
   so it is the same physical size on every phone — see vpNormalize().
   ============================================================ */
#crInvite{position:fixed; inset:0; z-index:9996; display:flex; align-items:center; justify-content:center;
  padding:calc(14px * var(--gs,1)); background:rgba(8,5,14,.72); pointer-events:auto;}
.crinv-card{width:100%; max-width:calc(300px * var(--gs,1)); background:#1a2340; border:calc(3px * var(--gs,1)) solid #f4c542;
  box-shadow:0 0 0 calc(3px * var(--gs,1)) #0a1020, 0 calc(8px * var(--gs,1)) calc(22px * var(--gs,1)) rgba(0,0,0,.6);
  padding:calc(13px * var(--gs,1)) calc(12px * var(--gs,1)); text-align:center;
  animation:crinvPop .35s cubic-bezier(.3,1.5,.5,1) both;}
@keyframes crinvPop{from{transform:scale(.7); opacity:0;} to{transform:none; opacity:1;}}
.crinv-top{font-family:var(--fontD); font-size:calc(11px * var(--gs,1)); letter-spacing:calc(2px * var(--gs,1));
  color:#f4c542; text-shadow:0 calc(2px * var(--gs,1)) 0 #0a1020;}
/* v0.419 — the challenger's own face over their name, so the card matches the roster row */
.crinv-face{width:calc(56px * var(--gs,1)); height:calc(56px * var(--gs,1)); margin:calc(7px * var(--gs,1)) auto calc(1px * var(--gs,1));
  border-radius:50%; overflow:hidden; background:#0a1020; border:calc(2px * var(--gs,1)) solid #f4c542;}
.crinv-face svg{width:100%; height:100%; display:block;}
.crinv-who{font-family:var(--fontD); font-size:calc(19px * var(--gs,1)); line-height:1.2; margin:calc(6px * var(--gs,1)) 0 calc(3px * var(--gs,1));
  color:#fdf6e3; text-shadow:0 calc(2px * var(--gs,1)) 0 #0a1020; word-break:break-word;}
.crinv-what{font-family:var(--fontB,inherit); font-size:calc(10px * var(--gs,1)); line-height:1.5; color:#cdbfe6;}
.crinv-what b{color:#9fe6bd;}
.crinv-row{display:flex; gap:calc(7px * var(--gs,1)); justify-content:center; margin-top:calc(11px * var(--gs,1)); flex-wrap:wrap;}
.crinv-btn{flex:1 1 calc(110px * var(--gs,1)); min-width:calc(104px * var(--gs,1)); font-size:calc(11px * var(--gs,1));}

/* v0.371 — THE RIVAL'S MOVE, ANNOUNCED. An online brawl should never be a silent screen: every card
   the other player lays down says so, on their side of the field, and takes itself away. */
.bb-rivalcue{position:absolute; right:calc(6px * var(--gs,1)); top:calc(96px * var(--gs,1)); z-index:14; pointer-events:none;
  font-family:var(--fontD); font-size:calc(9px * var(--gs,1)); letter-spacing:calc(1px * var(--gs,1)); line-height:1.5;
  color:#ffd9c8; text-align:right; text-shadow:0 calc(2px * var(--gs,1)) 0 #0a1020, 0 0 calc(3px * var(--gs,1)) #0a1020;
  max-width:calc(168px * var(--gs,1));}
.bb-rivalcue span{display:block; background:rgba(24,12,10,.72); border-left:calc(3px * var(--gs,1)) solid #f0654a;
  padding:calc(2px * var(--gs,1)) calc(5px * var(--gs,1)); margin-top:calc(3px * var(--gs,1));
  /* ⚠ IT WRAPS, IT NEVER TRUNCATES. Ellipsised to one line, "THE GREAT MARBLE Lv12" read as
     "THE GREAT MARB…" on the folded phone — a play-by-play you cannot read is not a play-by-play. */
  animation:bbCueIn .3s ease both; white-space:normal; word-break:break-word; overflow-wrap:anywhere;}
@keyframes bbCueIn{from{transform:translateX(calc(14px * var(--gs,1))); opacity:0;} to{transform:none; opacity:1;}}
.bb-rivalcue span.out{opacity:0; transition:opacity .5s ease;}
/* ⭐ v0.440 — THE TOSS SITS ON THE CENTRE SPOT. crCoinToss projects the middle of the felt and
   writes left/top; this turns the overlay from a full-screen flex box into a thing anchored at
   that point. translate(-50%,-50%) centres it ON the coordinate rather than starting from it. */
/* ⭐⭐⭐ v0.485 — THE COIN IS NAILED TO THE CENTRE SPOT. (Odaroo: it drifts left and right as it spins.)
   ⚠⚠ IT WAS THE WORDS, NOT THE COIN. The flip itself is a pure rotateX — a vertical tumble with no
   sideways component at all — but this box was `width:auto`, so its width was set by whatever line
   of text was currently in it, and `translate(-50%)` re-centres against THAT width. The message
   changes three times during a toss ("CALLING YOUR COLOUR…" → "YOU PLAY RED" → "YOU BREAK"), so the
   box got wider and narrower and the whole thing — coin included — slid across the felt each time.
   A FIXED WIDTH means the anchor point cannot move, whatever the words say; the coin is then
   centred inside it on its own margins rather than inheriting a text rule it never obeyed (a
   122px block is not centred by `text-align`, which is why it was sitting off-axis to begin with).
   ⚠ Do not put `width:auto` back. This is the same class of fault as the v0.447 zoom flicker: a
   POSITION may never depend on what the words happen to say. */
.ssOverlay.cr-toss.cr-toss-at{
  position:absolute; inset:auto; height:auto;
  width:min(84vw, calc(300px * var(--gs,1)));
  display:block; padding:0; background:none;
  transform:translate(-50%,-50%); text-align:center; pointer-events:none;}
.ssOverlay.cr-toss.cr-toss-at .cr-coin{margin-left:auto; margin-right:auto;}
.ssOverlay.cr-toss.cr-toss-at .cr-tossmsg,
.ssOverlay.cr-toss.cr-toss-at .cr-tossbrk{width:100%;}
/* ⚠⚠ THE COIN OVERFLOWS ITS OWN BOX UPWARDS, so 4px of margin was not 4px of daylight — the disc
   is drawn in 3D and rises well above `.cr-cointoss`, and it was covering the "T" of THE TOSS at
   both folds (measured: the title's whole 13px height inside the coin's painted bounds). The gap
   has to clear the OVERFLOW, not the box. */
.ssOverlay.cr-toss.cr-toss-at .cr-tosstitle{margin-bottom:calc(26px * var(--gs,1));}

/* ⭐⭐ v0.447 — THE TURN BANNER HAS A FIXED HEIGHT, AND THAT IS A LAYOUT RULE, NOT A LOOK.
   crFit measures this band to decide where the table starts. While it could grow — a two-line
   message is taller than a one-line one — every change of wording re-sized the table, which is the
   zoom flicker Odaroo photographed. Pinned, the words can say anything and the geometry never
   hears about it. The pill inside is unchanged; it simply cannot push the band open any more. */
#crTurn{height:25px; overflow:hidden; display:flex; align-items:center; justify-content:center;}
#crTurn .crt-in{white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}

/* ============================================================
   ⭐⭐ v0.448 — THE ONE CONNECTION SIGN: a film reel, centre screen.
   It replaces the faint line of text that used to sit at the top. The game stays visible behind it
   (no curtain, no dimming) — it is a sign, not a dialog — and nothing under it is clickable.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  position:fixed; left:50%; top:50%; transform:translate(-50%,-50%);
  z-index:9990; pointer-events:none; display:flex; align-items:center; gap:8px;
  /* ⭐⭐ v0.473 — THE SAME STRIP OF FILM AS EVERY OTHER ONE. It was its own invention: 5px cream
     leaders with DARK sprockets punched through them, which is the username strip inverted and more
     than twice as thick — so the one sign the player sees mid-match looked like it came from a
     different game. It wears .inkfilm now (hub.css); only the colour of the word and where it sits
     are its own. The old ::before/::after leaders are gone with it. */
  animation:crlIn .28s var(--pop,ease-out) both;}
/* ⚠ same trap as the ticker: the id+class selector must carry the film itself, or a shorthand
   elsewhere wins and the sign loses its perforations. */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  background-color:#0d0a08;
  border-top:calc(2px * var(--gs,1)) solid #0d0a08;
  border-bottom:calc(2px * var(--gs,1)) solid #0d0a08;
  font-size:calc(8px * var(--gs,1)); letter-spacing:calc(.5px * var(--gs,1));
  padding:calc(6px * var(--gs,1)) calc(12px * var(--gs,1));
  /* v0.567 - the same holes, now CENTRED and MIRRORED. The recipe is --film-tile in css/base.css
     and it is pulled in by var rather than copied, so this strip can never drift from the reel. */
  background-image:linear-gradient(to right,
     transparent 0 calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)),
     var(--film-hole) calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)) calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)),
     transparent calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)) calc(var(--film-per) * var(--gs,1))),
    linear-gradient(to right,
     transparent 0 calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)),
     var(--film-hole) calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)) calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)),
     transparent calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)) calc(var(--film-per) * var(--gs,1)));
  background-size:calc(var(--film-per) * var(--gs,1)) calc(var(--film-h) * var(--gs,1)), calc(var(--film-per) * var(--gs,1)) calc(var(--film-h) * var(--gs,1));
  background-position:center calc(var(--film-in) * var(--gs,1)), center calc(100% - var(--film-in) * var(--gs,1));
  background-repeat:repeat-x, repeat-x;}
@media (min-width:640px){ #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{font-size:calc(10px * var(--gs,1));} }
#crLineBadge.cr-linebadge::before,#foePlayLbl.cr-linebadge::before,#crLineBadge.cr-linebadge::after,#foePlayLbl.cr-linebadge::after{content:none;}
#crLineBadge.cr-linebadge.ok,#foePlayLbl.cr-linebadge.ok{color:#8ff0a8;}
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{animation:crlOut .4s ease both;}
@keyframes crlIn{from{opacity:0; transform:translate(-50%,-50%) scale(.86)}to{opacity:1; transform:translate(-50%,-50%) scale(1)}}
@keyframes crlOut{to{opacity:0; transform:translate(-50%,-50%) scale(.94)}}
/* the three dots cycle like a classic loading animation */
.crl-dots{display:inline-flex; gap:4px; font-style:normal;}
.crl-dots u{width:5px; height:5px; background:var(--cream); text-decoration:none; display:block;
  animation:crlDot 1.05s ease-in-out infinite;}
.crl-dots u:nth-child(2){animation-delay:.17s}
.crl-dots u:nth-child(3){animation-delay:.34s}
@keyframes crlDot{0%,70%,100%{opacity:.2} 35%{opacity:1}}
@media (min-width:560px){ #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{font-size:15px; padding:12px 22px;} .crl-dots u{width:7px;height:7px;} }
/* ⭐ v0.448 — ITEM 12: the turn indicator is a short film reel at the top centre */
#crTurn .crt-in{border-top:4px solid #ffffff; border-bottom:4px solid #ffffff; position:relative;}
#crTurn .crt-in::before,#crTurn .crt-in::after{content:""; position:absolute; left:0; right:0; height:4px;
  background:repeating-linear-gradient(90deg,#1b1510 0 3px,transparent 3px 9px);}
#crTurn .crt-in::before{top:-4px;} #crTurn .crt-in::after{bottom:-4px;}
/* ⚠ THE REEL MUST FIT THE PINNED BAND. #crTurn is held at 25px because crFit measures that band to
   place the table (v0.447) — so the film leader's 4px top and bottom have to come out of the pill's
   own padding, not out of the band. Grow the band and the folded table shrinks; that is not a
   trade worth making for two pixels. */
#crTurn .crt-in{padding-top:0; padding-bottom:0; line-height:13px;}
/* ⭐⭐⭐ v0.473 — THE TICKER SITS BETWEEN THE TWO INKLINGS, AND THE YELLOW BANNER IS GONE.
   v0.470 put it hard against the top edge as a full-width bar; Odaroo wants it in the gap between
   the two faces instead, on the same line as them, reading as a third piece of the same film reel.
   So: no full-width band, no yellow — it is .inkfilm like the two username strips either side of
   it, and crTurnPlace measures the two portraits and hangs it in the space between them.
   ⚠ The 25px pinned height above still applies and still matters: crFit measures this band to
   place the table, so the strip may not grow. Anything added comes out of its own padding. */
#crTurn{height:auto; overflow:visible; pointer-events:none;}
#crTurn .crt-in{display:block; max-width:100%; border-radius:0;
  border-left:0; border-right:0; text-align:center;}
/* ⚠ THE BACKGROUND HAS TO BE TAKEN BACK EXPLICITLY. The v0.448 rules further up paint .mine gold
   and .theirs dark on their own backgrounds; setting only a colour here left the yellow banner
   exactly where it was. The strip's own dark film is the background on every state now. */
/* ⚠⚠ THE SPROCKETS LIVE AT *THIS* SPECIFICITY, and that is the whole fix. The v0.448 rule
   `#crTurn.mine .crt-in{background:#f4c542}` is (1,2,0) and, being a SHORTHAND, sets
   background-image:none. A plain `#crTurn .crt-in` rule is only (1,1,0), so restating the holes
   there lost every time and the strip came out perforation-free while matching on every other
   property. Declared here, against the same selectors, they win. */
#crTurn.mine .crt-in, #crTurn.theirs .crt-in, #crTurn.rolling .crt-in{
  background-color:#0d0a08; border-color:#0d0a08; box-shadow:0 calc(2px * var(--gs,1)) 0 rgba(13,10,8,.55);
  /* v0.567 - the same holes, now CENTRED and MIRRORED. The recipe is --film-tile in css/base.css
     and it is pulled in by var rather than copied, so this strip can never drift from the reel. */
  background-image:linear-gradient(to right,
     transparent 0 calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)),
     var(--film-hole) calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)) calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)),
     transparent calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)) calc(var(--film-per) * var(--gs,1))),
    linear-gradient(to right,
     transparent 0 calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)),
     var(--film-hole) calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)) calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)),
     transparent calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)) calc(var(--film-per) * var(--gs,1)));
  background-size:calc(var(--film-per) * var(--gs,1)) calc(var(--film-h) * var(--gs,1)), calc(var(--film-per) * var(--gs,1)) calc(var(--film-h) * var(--gs,1));
  background-position:center calc(var(--film-in) * var(--gs,1)), center calc(100% - var(--film-in) * var(--gs,1));
  background-repeat:repeat-x, repeat-x;}
/* ⚠ AND THE SAME THICKNESS AND SIZE, MEASURED. The v0.448 rules give this a 4px cream leader and
   a 9px face; the username strips are 2px and 8px. Odaroo wants them identical, and #crTurn .crt-in
   is more specific than .inkfilm, so the strip's own numbers have to be restated here or the class
   is only half applied. The ::before/::after leaders from v0.448 go with them — .inkfilm draws its
   sprockets as a background, so leaving those on would print a second row of holes. */
#crTurn .crt-in{
  border-top:calc(2px * var(--gs,1)) solid #0d0a08;
  border-bottom:calc(2px * var(--gs,1)) solid #0d0a08;
  font-size:calc(8px * var(--gs,1)); line-height:1.35;
  padding:calc(6px * var(--gs,1)) calc(8px * var(--gs,1));
  /* ⚠⚠ THE SPROCKETS HAVE TO BE RESTATED, AND THIS IS THE TRAP: the v0.448 rule above sets
     `background:#1b1510` as a SHORTHAND, and a background shorthand RESETS background-image. So
     .inkfilm painted its holes and this rule silently wiped them — the strip matched on colour,
     thickness and size while having no perforations at all. Same recipe as .vsnm-c, restated. */
  /* v0.567 - the same holes, now CENTRED and MIRRORED. The recipe is --film-tile in css/base.css
     and it is pulled in by var rather than copied, so this strip can never drift from the reel. */
  background-image:linear-gradient(to right,
     transparent 0 calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)),
     var(--film-hole) calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)) calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)),
     transparent calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)) calc(var(--film-per) * var(--gs,1))),
    linear-gradient(to right,
     transparent 0 calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)),
     var(--film-hole) calc((var(--film-per) - var(--film-w))/2 * var(--gs,1)) calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)),
     transparent calc((var(--film-per) + var(--film-w))/2 * var(--gs,1)) calc(var(--film-per) * var(--gs,1)));
  background-size:calc(var(--film-per) * var(--gs,1)) calc(var(--film-h) * var(--gs,1)), calc(var(--film-per) * var(--gs,1)) calc(var(--film-h) * var(--gs,1));
  background-position:center calc(var(--film-in) * var(--gs,1)), center calc(100% - var(--film-in) * var(--gs,1));
  background-repeat:repeat-x, repeat-x;}
#crTurn .crt-in::before, #crTurn .crt-in::after{content:none;}
@media (min-width:640px){ #crTurn .crt-in{font-size:calc(10px * var(--gs,1));} }
#crTurn.mine .crt-in{color:#ffe9a8;}      /* your move reads warm-gold, but on the SAME strip */
#crTurn.theirs .crt-in{color:#ffd6cc;}    /* theirs reads warm-pink, matching their name strip */
#crTurn.rolling .crt-in{color:#fdf6e3;}
#crTurn.mine .crt-in{animation:none;}     /* no pulse: a strip of film does not throb */

/* ============================================================
   ⭐⭐⭐ v0.475 — THE SOLO RUN CLOCK, top-right, where the rival would be.
   A plain Nintendo-shaped readout: the label small and quiet, the figure big and monospaced. It sits
   in the corner the foe portrait had, so a solo run has something there instead of a hole.
   ⚠ MONOSPACE AND A FIXED SHAPE ON PURPOSE: h:mm:ss never drops its hours, so the box does not
   change width as the run passes a minute or an hour and nothing beside it reflows.
   ============================================================ */
/* ⚠ in a solo run there is no rival, so their name cell and portrait are not drawn at all — the
   run clock owns that corner. Declarative, so nothing that rebuilds the bar can put them back. */
/* ⚠⚠ v0.476 — THE SOLO READOUT IS SIZED FOR ITS GAP. It sits between the inkling and the run
   clock — about 195px on the folded phone — and it carries three figures, so at the username
   strips' 8px it clipped on every rung. Smaller, and with the letter-spacing given back, it fits
   its worst case (5-5, two-digit counts) with room to spare. The two NAME strips keep their size:
   this is the only strip with a fixed narrow gap to live in. */
#crTurn.crt-solo .crt-in{font-size:calc(6.5px * var(--gs,1)); letter-spacing:0;
  padding-left:calc(5px * var(--gs,1)); padding-right:calc(5px * var(--gs,1));}
@media (min-width:640px){ #crTurn.crt-solo .crt-in{font-size:calc(9px * var(--gs,1));} }
#scr-crown.cr-solo .vsnm .vsnm-c.r{display:none !important;}
#scr-crown.cr-solo #crFoe, #scr-crown.cr-solo #crFoeColor{display:none !important;}
/* ⭐⭐⭐ v0.849 — AND NO COLOUR COIN OF YOUR OWN. (Odaroo, 2026-08-25: "remove the colour of
   the disc you have to play as... we don't need that information because you're solo.") A solo rung
   is a mission — CLEAR N RINGS IN N SHOTS — and every ring on the rack is yours, so which team
   colour you were dealt is information about a match that is not happening. It is hidden
   DECLARATIVELY, from the class, for the v0.475 reason: crColorBadge rebuilds both coins from
   crStart and crSoloClock re-hangs them every tick, so a JS removal and the thing rebuilding them
   simply race and the corner flickers. A CSS rule cannot lose that race. */
#scr-crown.cr-solo #crMyColor{display:none !important;}
/* ============================================================
   ⭐⭐⭐ v0.647 — THE SOLO READOUT: PLAIN TEXT, UNDER THE INKLET, ON THE LEFT.
   (Odaroo, 2026-08-05: "Remove the stats box that's on the side there… just simple text that says
   stage 1-1, time, number of shots, and number of shots left. All these things should be under the
   inklet.") This is the house rule every other game already follows — one inklet top-left, every
   figure stacked beneath it — and Crown was the last screen with a boxed rack in the rival's corner.
   ⚠ NO PLATE, NO BORDER. It is text over the felt, so it is ringed in black the way every other
   figure that sits over artwork in this game is. A dark slab in the corner is what Odaroo asked to
   have taken away, and putting a smaller one on the other side is the same box moved.
   ⚠ ITS `top` AND `left` COME FROM JS (crSoloHudPlace) off the measured coin and portrait, never
   from a constant here — the inklet grows with --gs and the fold, and a constant is right on exactly
   one of them. Only the type and the colours live in this file.
   ⚠ LABEL ABOVE VALUE, not beside it: "SHOTS LEFT" beside a figure does not fit the portrait's
   column on the folded phone, and a row that wraps moves every row under it.
   ============================================================ */
.cr-solohud{position:absolute; z-index:10; pointer-events:none;
  display:flex; flex-direction:column; align-items:flex-start; gap:calc(6px * var(--gs,1));
  max-width:calc(124px * var(--gs,1));}
.cr-solohud .csh-r{display:block; white-space:nowrap;}
.cr-solohud .csh-k{display:block; font-family:'Press Start 2P',monospace;
  font-size:calc(5.5px * var(--gs,1)); letter-spacing:calc(1px * var(--gs,1));
  color:#f2ead6; opacity:.72; line-height:1.5;
  text-shadow:1px 1px 0 #0d0a08, -1px 1px 0 #0d0a08, 1px -1px 0 #0d0a08, -1px -1px 0 #0d0a08;}
.cr-solohud .csh-v{display:block; font-family:'Press Start 2P',monospace;
  font-size:calc(10px * var(--gs,1)); letter-spacing:0; color:#fdf6e3; line-height:1.5;
  font-weight:normal; margin-top:calc(2px * var(--gs,1));
  text-shadow:2px 2px 0 #0d0a08, -1px 1px 0 #0d0a08, 1px -1px 0 #0d0a08, -1px -1px 0 #0d0a08;}
.cr-solohud .csh-r:nth-child(2) .csh-v{color:#f4c542;}   /* TIME reads brass, the run-clock colour */
@media (min-width:640px){
  .cr-solohud{max-width:calc(160px * var(--gs,1));}
  .cr-solohud .csh-k{font-size:calc(7px * var(--gs,1));}
  .cr-solohud .csh-v{font-size:calc(13px * var(--gs,1));}
}
/* ⚠⚠ v0.647 — THE TURN STRIP IS HIDDEN IN A SOLO RUN, NOT REMOVED. It printed the same rung, rings
   and shots the column now carries, across the top of the felt — the second stat readout Odaroo
   asked to have cleaned up. It keeps its element and its full height because crFit MEASURES it to
   decide where the table starts: a band that comes and goes changes the table's size, which is the
   v0.447 zoom flicker. `visibility`, never `display`, for exactly that reason. */
#scr-crown.cr-solo #crTurn{visibility:hidden;}
/* ⭐⭐⭐ v0.719 — AND NOW IT IS HIDDEN IN A MATCH TOO, WHICH LEAVES DISC-INK WITH ONE CLOCK.
   (Odaroo, 2026-08-06, on a Disc-Ink screenshot: "there should only be one film reel on the time.
   Now it looks like there are two.") There were two, and this was the second: v0.643 made this band
   the match clock — its own strip of film, its own `0:00` — and then v0.705/v0.712 put THE clock in
   the header reel between the two usernames on every screen in the game. Nobody took this one down,
   so Disc-Ink printed a film-reel clock at the very top of the screen and the header reel's film-reel
   clock ten pixels under it, overlapping. Measured at 375x812: `.crt-in.crt-time` at y=0 and
   `.vsmid` at y=32, both 31px deep and both centred.
   ⚠⚠ HIDDEN, NEVER REMOVED, AND THE PAINTER IS LEFT RUNNING. crFit MEASURES this band to decide
   where the table starts (the v0.447 zoom flicker), so the room stays booked and every measurement
   stays exactly what it was. Deleting the strip — or the code that fills it — moves the table.
   ⚠ The selector is `#scr-crown #crTurn`, not `#crTurn`: OCTA-THON wears the same element as
   `#crTurn.bb` on its own screen, and that one is not a clock. */
#scr-crown #crTurn{visibility:hidden;}

/* ============================================================
   ⭐⭐ v0.454 — THE LOBBY IS A SKY YOU STEP INTO.
   The ONLINE button drifts baby-blue clouds; pressing it opened a dark purple box, so the two read
   as different places. The whole pop-up now wears the same sky the button does — same tile, same
   drift, one step slower so it feels bigger — and the cards on top of it are light instead of dark.
   ⚠ THE SKY IS ON THE POP-UP, NOT ON #modalBox. The box is shared by every dialog in the game;
   painting it here would put clouds behind the pause menu, the shop and the receipt.
   ⚠ Light cards mean the TEXT has to flip too. Names, tags and the "nobody here" line were all
   chosen to sit on #1a2340 — left alone, white-on-pale-blue fails contrast outright, which is what
   crReadable() is for. Every one of them is restated below against the new background.
   ============================================================ */
.crlPop{
  background:#7fc4f0;
  background-image:var(--inksky-clouds), var(--inksky-clouds);
  background-repeat:repeat-x, repeat-x;
  background-size:120px auto, 120px auto;
  background-position:0 14%, 60px 66%;
  animation:inkSkyDrift 30s linear infinite;
  border:3px solid var(--ink);
  padding:10px;
}
.crlPop>*{position:relative; z-index:1;}
.crlPop .crlPop-h{color:#10243f;}
.crlPop .crlPop-h b{color:#10243f;}
/* the panel and its rows go bright — cream cards on a blue sky, the album's own palette */
.crlPop .crl-panel{background:#f2ead6; border-color:var(--ink); box-shadow:0 3px 0 var(--ink);}
.crlPop .crl-row{background:#ffffff; border-color:var(--ink);}
.crlPop .crl-row .crl-nm{color:#1b1510;}
.crlPop .crl-row .crl-tag{color:#1f6b45;}
.crlPop .crl-row .crl-tag.busy{color:#9a3620;}
.crlPop .crl-row.crl-me{background:#fff3c9; border-color:#b8860b;}
.crlPop .crl-row.crl-me .crl-nm{color:#5a3a00;}
.crlPop .crl-row.crl-me .crl-tag{color:#7a5200;}
.crlPop .crl-row.crl-inv{background:#fff0c0; border-color:#b8860b;}
.crlPop .crl-row.crl-inv .crl-tag{color:#7a5200;}
.crlPop .crl-head{color:#1b1510;}
.crlPop .crl-none{color:#3a3328;}
.crlPop .crl-foot{color:#26364d;}
@media (prefers-reduced-motion:reduce){ .crlPop{animation:none;} }
/* v0.454 — the player card carries one figure now, so it centres instead of gridding four */
#modalBox .pcard-grid.pcard-one{display:flex; justify-content:center;}
#modalBox .pcard-grid.pcard-one i{min-width:120px;}

/* ============================================================
   ⭐⭐ v0.454 — THE CONNECTION SIGN, THICKER AND WITHOUT THE RED EDGES.
   ⚠⚠ WHERE THE RED CAME FROM. v0.448 rebuilt this as a film reel by setting border-TOP and
   border-BOTTOM — but the v0.374 rule above it sets the shorthand `border:2px solid #c93a22`, and a
   shorthand the newer rule never overrides leaves the LEFT and RIGHT edges red. That is the pair of
   red lines Odaroo asked to be rid of: not a decision anybody made, a leftover from the sign this
   one replaced. Killing the side borders outright is the fix; do not re-introduce a shorthand here.
   The leader is twice as thick (5px → 10px) with sprocket holes to match, because this is a status
   sign rather than a name tag and it has to read at a glance over a live game.
   ============================================================ */
/* ⚠⚠ v0.473 — THE 10px LEADER IS RETIRED. This block came AFTER the one above and quietly won,
   which is why the sign still measured 10px when everything else about it had already been matched
   to the username strip. Odaroo wants one film everywhere: same thickness, same holes. The
   ::before/::after leaders go too — .inkfilm draws its sprockets as a background, and leaving these
   on printed a second row of holes outside the first. */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  border-left:0; border-right:0;
  border-top:calc(2px * var(--gs,1)) solid #0d0a08;
  border-bottom:calc(2px * var(--gs,1)) solid #0d0a08;
}
#crLineBadge.cr-linebadge::before,#foePlayLbl.cr-linebadge::before,#crLineBadge.cr-linebadge::after,#foePlayLbl.cr-linebadge::after{content:none;}
/* the word itself breathes about once a second, so the sign reads as live rather than stuck */
@keyframes crlPulse{0%,100%{opacity:1} 50%{opacity:.42}}
#crLineBadge.cr-linebadge b,#foePlayLbl.cr-linebadge b{animation:crlPulse 1.05s ease-in-out infinite;}
/* CONNECTED is the splice mark: the strip inverts — white plate, black word — and it is gone in
   half the time, because good news does not need to sit on screen. */
/* ⚠ CONNECTED still inverts — cream plate, dark word — but as a COLOUR change on the same strip,
   not a `background:` shorthand, which would wipe the sprockets the strip draws. */
/* ⭐⭐⭐ v0.683 — A HEADLINE ON THE SAME STRIP (crLineSay in js/online.js). Challenge news — sent,
   arrived, accepted, turned down — is said here rather than in a toast at the bottom of the glass,
   so it sits on the connecting band instead of over the link-cable animation.
   ⚠ ONE LINE, NEVER WRAPPED. The strip is a band of film with a fixed height measured off the home
   HUD bar; a second line would spill straight out of it. crLineSay steps the font size down until
   the words are inside the glass, and this is the rule that makes that measurement mean something. */
#crLineBadge.cr-linebadge.say,#foePlayLbl.cr-linebadge.say{white-space:nowrap; max-width:96vw;}
#crLineBadge.cr-linebadge.say b,#foePlayLbl.cr-linebadge.say b{animation:none;}   /* news is read, not pulsed */
#crLineBadge.cr-linebadge.ok,#foePlayLbl.cr-linebadge.ok{background-color:#f2ead6f2; color:#1b1510; border-color:#f2ead6;}
#crLineBadge.cr-linebadge.ok b,#foePlayLbl.cr-linebadge.ok b{animation:none;}
#crLineBadge.cr-linebadge.ok,#foePlayLbl.cr-linebadge.ok{border-top-color:#1b1510; border-bottom-color:#1b1510;}
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{animation:crlOut .5s ease both;}
@media (min-width:560px){
  #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{border-top-width:14px; border-bottom-width:14px;}
  #crLineBadge.cr-linebadge::before,#foePlayLbl.cr-linebadge::before,#crLineBadge.cr-linebadge::after,#foePlayLbl.cr-linebadge::after{height:14px;}
  #crLineBadge.cr-linebadge::before,#foePlayLbl.cr-linebadge::before{top:-14px;} #crLineBadge.cr-linebadge::after,#foePlayLbl.cr-linebadge::after{bottom:-14px;}
}

/* ============================================================
   ⭐⭐ v0.492 — THE CONNECTION SIGN AS A THEATRE MARQUEE. (Odaroo.)
   A black card with the title screen's own square bulbs chasing round its edge. The film-strip
   treatment of v0.473 is overridden here rather than edited out above, so the old rule stays
   readable as history and this one plainly wins on specificity + source order.
   ⚠ THE PADDING IS THE FRAME. The bulbs are laid ON the box's edge by crLineBulbs, so the words
   need a clear 14px inside them or the lights sit on the text. Anything added to this sign comes
   out of that padding, never out of the bulb ring.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  background-image:none;
  background-color:#0d0a08;
  border:calc(2px * var(--gs,1)) solid #0d0a08;
  padding:calc(14px * var(--gs,1)) calc(20px * var(--gs,1));
  color:#fdf6e3;
}
/* ⚠⚠ THE SELECTORS CARRY `.cr-linebadge` DELIBERATELY, and this is not decoration. The bulbs are
   <b> elements, and this file already has `#crLineBadge.cr-linebadge b,and this file already has `#foePlayLbl.cr-linebadge b{animation:crlPulse}` and
   `…​.ok b{animation:none}` further up — id+class+type, which beats a plain id+class. Written as
   `#crLineBadge .clb` the lights inherited the old text pulse and the CONNECTED state killed their
   animation outright. Measured: 22 bulbs all running crlPulse instead of the chase. Match the
   specificity or lose. (The same trap the Tetr-Ink container query hit in v0.483.)
   the bulb: the SAME 10px square and the same gold/red alternation as #titleFrame .tfb */
#crLineBadge.cr-linebadge .clb,#foePlayLbl.cr-linebadge .clb{position:absolute; width:10px; height:10px; background:var(--gold);
  animation:clbChase 1.1s linear infinite; box-shadow:0 0 6px #d9a23288; border-radius:0;}
#crLineBadge.cr-linebadge .clb.r,#foePlayLbl.cr-linebadge .clb.r{background:var(--red); box-shadow:0 0 6px #c93a2288;}
/* RECONNECTING: the light runs round the ring — each bulb a step behind the last (the delay is
   written inline by crLineBulbs, which is what turns a blink into a chase) */
@keyframes clbChase{0%,72%{opacity:.28; transform:scale(.86)} 84%{opacity:1; transform:scale(1.18)} 100%{opacity:.28; transform:scale(.86)}}
/* CONNECTED: the chase stops and the whole frame flashes up together — the sign settling.
   ⚠ The card stays BLACK. The rule above turns .ok cream, which under gold bulbs looks like a
   different sign entirely; Odaroo asked for one black box with lights on it in both states. */
#crLineBadge.cr-linebadge.ok,#foePlayLbl.cr-linebadge.ok{background-color:#0d0a08; color:#8ff0a8;
  border-color:#2e9e4f; border-top-color:#2e9e4f; border-bottom-color:#2e9e4f;}
#crLineBadge.cr-linebadge.ok .clb,#foePlayLbl.cr-linebadge.ok .clb{animation:clbLand .8s ease-out both; animation-delay:0s!important;
  background:#8ff0a8; box-shadow:0 0 8px #8ff0a8aa;}
@keyframes clbLand{0%{opacity:1; transform:scale(1.6)} 40%{opacity:1; transform:scale(1)} 100%{opacity:.95; transform:scale(1)}}

/* ============================================================
   ⭐⭐ v0.536 — THE CONNECTION SIGN IS A FILM-REEL BANNER ACROSS THE MIDDLE. (Odaroo.)
   The v0.492 theatre marquee is retired: a black card ringed with bulbs read as a little plaque,
   and Odaroo asked for the house's own strip of film instead — edge to edge, halfway down the
   screen, about as thick as a button, sprocket holes along the top and the bottom, the word in
   white in the middle of it. RECONNECTING and CONNECTED are the SAME strip; only the word changes.

   ⚠ THE BULBS ARE GONE FROM THE JS TOO (crLineBulbs is no longer called by crLineBadge). They were
   laid as absolutely-positioned <b>s ON the box's edge, so leaving them on a full-width banner
   would have strung lights across the whole screen.
   ⚠ THE HOLES ARE A BACKGROUND, NOT BORDERS — the same recipe as .inkfilm in css/hub.css, drawn a
   little larger because this strip is a status sign four times that one's thickness. If .inkfilm's
   look ever changes, change this with it; it is meant to be the same film.
   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it would wipe the sprockets (the v0.473 trap).
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  left:0; right:0; top:50%; bottom:auto;
  transform:translateY(-50%);
  width:100%; max-width:none;
  height:calc(44px * var(--gs,1));
  box-sizing:border-box;
  display:flex; align-items:center; justify-content:center; gap:calc(7px * var(--gs,1));
  padding:0 calc(12px * var(--gs,1));
  border:0;
  background-color:#0d0a08;
  background-image:
    repeating-linear-gradient(to right, #f2ead6 0 calc(5px * var(--gs,1)), transparent calc(5px * var(--gs,1)) calc(13px * var(--gs,1))),
    repeating-linear-gradient(to right, #f2ead6 0 calc(5px * var(--gs,1)), transparent calc(5px * var(--gs,1)) calc(13px * var(--gs,1)));
  background-size:100% calc(5px * var(--gs,1)), 100% calc(5px * var(--gs,1));
  background-position:0 calc(5px * var(--gs,1)), 0 calc(100% - 5px * var(--gs,1));
  background-repeat:no-repeat, no-repeat;
  box-shadow:0 calc(3px * var(--gs,1)) 0 rgba(13,10,8,.55);
  color:#fff;
  font-family:var(--fontD); font-weight:400;
  font-size:calc(11px * var(--gs,1)); line-height:1; letter-spacing:calc(1px * var(--gs,1));
  animation:none;
}
#crLineBadge.cr-linebadge b,#foePlayLbl.cr-linebadge b{color:#fff; animation:crlPulse 1.05s ease-in-out infinite;}
/* CONNECTED is the same strip — only the word changes and it leaves twice as fast. A second look
   (a green plate, an inverted plate) was two signs pretending to be one. */
#crLineBadge.cr-linebadge.ok,#foePlayLbl.cr-linebadge.ok{background-color:#0d0a08; color:#fff; border:0;}
#crLineBadge.cr-linebadge.ok b,#foePlayLbl.cr-linebadge.ok b{color:#fff; animation:none;}
#crLineBadge.cr-linebadge .crl-dots u,#foePlayLbl.cr-linebadge .crl-dots u{background:#fff;}
@media (min-width:560px){
  #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{height:calc(52px * var(--gs,1)); font-size:calc(14px * var(--gs,1)); padding:0 calc(16px * var(--gs,1));}
  #crLineBadge.cr-linebadge .crl-dots u,#foePlayLbl.cr-linebadge .crl-dots u{width:7px; height:7px;}
}

/* ============================================================
   ⭐⭐ v0.539 — THE REEL THREADS, THEN THE PROJECTOR CATCHES. (Odaroo.)
   The banner is no longer a static plate with a word on it. It is a piece of film being fed
   through a projector, and it plays a little three-beat show:

     1. WHILE THE LINE IS DOWN the sprocket holes SCROLL left to right — the strip is threading.
        The shift is exactly one hole period (13px * gs), so the loop is seamless and the holes
        never appear to slip. A faint projector sheen sweeps the strip on a slow cycle.
     2. THE MOMENT IT CONNECTS the holes stop and the word CONNECTED holds in WHITE, then flashes
        GREEN once (v0.543 — the beam and the gate grain are deleted).
     3. THEN THE WHOLE STRIP SLIDES OFF TO THE RIGHT and fades. Nothing else.

   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it would wipe the sprockets (the v0.473 trap).
   ⚠ The thread animation targets `background-position` on the SAME two layers the sprockets are
     drawn with, so both values must be restated in the keyframe or the bottom row would fly off.
   ⚠ The strip is `overflow:hidden` so the beam and the grain cannot escape onto the game behind it.
   ⚠ The exit keyframe must restate `translateY(-50%)`; the banner is vertically centred by
     transform, so a bare `translateX` would drop it half its own height on the way out.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{overflow:hidden;}
/* 1 — THREADING. Only while the line is down; CONNECTED stops the film and holds the frame. */
#crLineBadge.cr-linebadge:not(.ok),#foePlayLbl.cr-linebadge:not(.ok){
  animation:crlThread .5s linear infinite;
}
@keyframes crlThread{
  to{ background-position:
        calc(13px * var(--gs,1)) calc(5px * var(--gs,1)),
        calc(13px * var(--gs,1)) calc(100% - 5px * var(--gs,1)); }
}
/* v0.543 — the projector sheen is deleted (Odaroo). The strip threads and that is all. */

/* ============================================================
   ⭐⭐ v0.543 — CONNECTED IS WHITE → A GREEN FLASH → GONE TO THE RIGHT. (Odaroo, 2026-08-03.)
   The projector beam, the gate grain, the LIVE tag, the red recording dot, the tape counter and the
   VHS tracking wobble are all DELETED — rules and markup both. The whole of the CONNECTED state is
   now the three rules below, and the exit is a plain slide and fade off the right-hand edge.
   ⚠ The exit keyframe must restate `translateY(-50%)`; the banner is vertically centred by
     transform, so a bare `translateX` would drop it half its own height on the way out.
   ============================================================ */
/* 2 — THE GREEN FLASH. `.lit` is added ~360ms after the word lands and is never removed; the
   animation runs once and settles back to white so the strip leaves the colour it arrived in. */
#crLineBadge.cr-linebadge.ok.lit b,#foePlayLbl.cr-linebadge.ok.lit b{ animation:crlFlash .42s ease-out 1 both; }
@keyframes crlFlash{
  0%  { color:#fff; text-shadow:none; }
  35% { color:#6cf58c; text-shadow:0 0 8px rgba(108,245,140,.75); }
  100%{ color:#fff; text-shadow:none; }
}
/* 3 — OFF TO THE RIGHT. Overrides the shared `.out` for this banner only. */
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{ animation:crlReelOut .55s cubic-bezier(.4,0,.7,.2) both; }
@keyframes crlReelOut{
  0%  { transform:translateY(-50%) translateX(0); opacity:1; }
  100%{ transform:translateY(-50%) translateX(118%); opacity:0; }
}

/* ============================================================
   ⭐ v0.555 — THE RECONNECTING STRIP IS AS THICK AS THE INKVILLE LOGO. (Odaroo, 2026-08-04.)
   The sign itself is RIGHT and nothing else about it changes — the film, the sprocket holes, the
   threading, the dots, the green flash and the exit to the right are all exactly as they were. The
   one note was that the strip is a little too thin. So its height is now the title logo's own
   height, and it is derived from the SAME arithmetic rather than eyeballed to match:

     `h1.glogo` is `font-size:clamp(26px,9vw,58px)` at `line-height:1.15` (css/wjam.css), so the
     logo stands 1.15 x that. Measured: 38.8px folded (375) and 66.7px unfolded (906).

   ⚠ THE FLOOR IS 44px, NOT 26px. A literal copy of the logo's clamp would make the strip THINNER on
     the folded Fold than the 44px it already was — the opposite of what was asked for. Floored at
     the old height, the strip is a little thicker folded (50.6px) and exactly the logo's height
     everywhere the logo is at its own full size (66.7px unfolded).
   ⚠ HEIGHT ONLY. No `background:` shorthand, no new animation, nothing else restated — the
     sprockets are drawn from the top and bottom edges by background-position, so they stay pinned
     to the edges and simply sit further apart. (See the v0.473 trap above.)
   ⚠ The ≥560px rule further up sets its own height, so that one is re-stated here too or it would
     win on the unfolded Fold by being more specific about the viewport, not about the element.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ height:calc(1.15 * clamp(44px, 9vw, 58px) * var(--gs,1)); }
@media (min-width:560px){
  #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ height:calc(1.15 * clamp(44px, 9vw, 58px) * var(--gs,1)); }
}

/* ============================================================
   ⭐⭐⭐ v0.560 — THE SIGN STEPS ASIDE FOR A DIALOG. (Odaroo: the end-of-run buttons must not be
   partly hidden behind the reconnecting bar.)
   The strip is pinned to the MIDDLE of the screen so it reads across a live board — but a bill is a
   dialog, and a dialog puts its way out at the bottom of a box that is itself centred, which is the
   same piece of screen. Measured folded: the sign occupied y 381..432 and the three buttons
   372..432. It overlapped all three.

   ⚠ IT IS NOT ENOUGH THAT THE SIGN IS `pointer-events:none`. The tap does land on HOME (verified) —
   but the player cannot READ the button they are being asked to press, and "I could not see it" is
   the fault that was reported. Being clickable while invisible is not the same as being there.

   ⚠⚠ IT IS `:has()` AND IT HAD TO BE, AND THAT COST A MEASUREMENT TO FIND OUT. The obvious version
   is the sibling combinator `#modalBg.on ~ #crLineBadge` — and it silently never matches, because
   `#crLineBadge` is created at runtime on <body> while `#modalBg` lives inside `#app`. They are not
   siblings, so `~` cannot see across, and the sign simply did not move. Asking BODY whether a dialog
   is up anywhere beneath it is the question that is actually being asked.
   ⚠ It costs the sign no JS of its own, and that is the reason to prefer it: a sign raised while a
   dialog is up moves, one raised with nothing open stays exactly where it has always been, and it
   settles back by itself the moment the dialog closes, because `.on` comes off #modalBg.
   ⚠ `top` ONLY — the transform still carries the `translate(-50%,-50%)` that centres it
   horizontally, so re-stating it here would knock the strip off the middle of the screen.
   ============================================================ */
body:has(#modalBg.on) #crLineBadge.cr-linebadge,body:has(#modalBg.on) #foePlayLbl.cr-linebadge{
  top:calc(var(--hudtop,16px) + var(--satnm, env(safe-area-inset-top,0px)));
}

/* ============================================================
   ⭐⭐⭐ v0.573 — THE SIGN IS PART OF THE HUD NOW. (Odaroo, 2026-08-04: "move the reconnecting /
   connected reel to the same position as the game HUD bar — where the inkling and the username
   strip sit — and put my inkling on it.")
   It was a banner floating across the MIDDLE of the screen. It is the same strip, the same film,
   the same threading and the same exit — it has simply moved to the top edge and taken the top of
   the HUD's place while it is up, so a connection notice reads as part of the game's own furniture
   rather than a thing thrown over it.

   ⚠⚠ THE `top` IS THE HUD STRIP'S OWN NUMBER, COPIED DELIBERATELY: `.vsnm.vsnm-top` in css/hub.css
   is `calc(4px * var(--gs,1) + env(safe-area-inset-top,0px))`. If that strip ever moves, move this
   with it — the whole point is that the two occupy the same line of screen. It outranks .vsnm on
   z-index (9990 vs 11), which is what makes it OVERLAY the bar rather than fight it.
   ⚠⚠ AND THE CENTRING TRANSFORM HAS TO GO WITH IT. The strip was vertically centred by
   `translateY(-50%)`, so leaving that on would hang it half its own height ABOVE the screen. Both
   keyframes that carried the same offset (`crlIn`, `crlReelOut`) are restated here without it —
   a keyframe that still writes `translateY(-50%)` puts the strip back where it came from for the
   whole length of its animation, which reads as the sign jumping.
   ⚠ The dialog rule above now resolves to the SAME line, and that is correct rather than redundant:
   at the top edge the sign is already clear of a centred bill's buttons, and leaving the old
   half-height offset in would have dropped it back down over them.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  top:calc(var(--hudtop,16px) + var(--satnm, env(safe-area-inset-top,0px)));
  bottom:auto;
  transform:none;
  animation:crlInTop .28s var(--pop,ease-out) both;
}
@keyframes crlInTop{ from{opacity:0; transform:scaleY(.5)} to{opacity:1; transform:none} }
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{ animation:crlOutTop .55s cubic-bezier(.4,0,.7,.2) both; }
@keyframes crlOutTop{ 0%{transform:translateX(0); opacity:1} 100%{transform:translateX(118%); opacity:0} }
/* the threading run still owns the RECONNECTING state's animation slot — restated so the entrance
   above cannot win it back by being written later in the file */
#crLineBadge.cr-linebadge:not(.ok):not(.out),#foePlayLbl.cr-linebadge:not(.ok):not(.out){ animation:crlThread .5s linear infinite; }

/* ⭐⭐ THE INKLING RIDES THE STRIP, ON THE LEFT, WHERE THE HUD KEEPS IT.
   ⚠ IT IS SIZED OFF THE STRIP'S OWN HEIGHT, not off --gs directly. The strip's height is a clamp on
   the viewport, so a portrait sized independently drifts out of it on one fold and not the other.
   ⚠ 68%, AND THAT NUMBER IS MEASURED, NOT PICKED. The sprocket rows are 5px bands inset 5px from
   each edge, so the film ends 10px in from the top and bottom. 78% left only 7.3px of clearance and
   the face sat squarely on the perforations; 68% gives 10.7px unfolded (clear) and 8.1px folded,
   where the head still grazes the last pixel or two of the top row — which is fine and intended:
   the portrait is drawn ON the film, and the holes reading THROUGH a hat is the failure, not a
   sprite whose outline touches them. Do not grow it back without re-measuring both folds.
   ⚠ `overflow:hidden` on the parent is what stops the threading film escaping, so the portrait must
   not need to spill — the inklet's viewBox already carries its own hats and haloes inside it. */
/* ⚠ THE GROUP SITS HARD LEFT, not centred, and that is the whole point of the move: the HUD keeps
   your face and your name in the top-LEFT corner, so a sign standing in for the HUD has to land in
   the same corner. A centred word over a left-hand portrait reads as two unrelated things. */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ justify-content:flex-start; }
#crLineBadge.cr-linebadge .crl-ink,#foePlayLbl.cr-linebadge .crl-ink{
  flex:none; display:flex; align-items:center; justify-content:center;
  height:68%; aspect-ratio:1; position:relative;}
#crLineBadge.cr-linebadge .crl-gink,#foePlayLbl.cr-linebadge .crl-gink{
  position:relative; width:100%; height:100%; display:flex; align-items:center; justify-content:center;}
#crLineBadge.cr-linebadge .crl-gink svg,#foePlayLbl.cr-linebadge .crl-gink svg{width:100%; height:100%; display:block;}

/* ============================================================
   ⭐⭐⭐ v0.578 — THE CONNECTING SIGN IS THE NAME REEL, EXACTLY. (Odaroo, 2026-08-04: "the
   connecting/reconnecting bar must be the same size folded and unfolded, in the same position as
   the menu bar, with the same inkling as everywhere else.")

   MEASURED BEFORE THIS BLOCK, on the two folds that matter:
       375x812  → strip 51px tall, word 11px, portrait 29px
       906x832  → strip 67px tall, word 14px, portrait 39px
   That is the complaint, and it was not one drifting number but THREE stacked width breakpoints
   this sign had grown of its own — `min-width:640px` bumping the face, and two separate
   `min-width:560px` rules re-writing the font, the padding AND the border to 14px. The name reel
   next to it has none of them: it is one face (--nmsz) times one zoom (--gs), and that is the whole
   reason the HUD looks like one system.

   So the sign is restated here in the reel's own terms rather than patched: same --gs arithmetic,
   same 2px leader, same sprocket recipe, same corner, same portrait width the HUD uses (--inkport).
   ⚠⚠ IT IS RESTATED AT THE END OF THE FILE ON PURPOSE. Three earlier blocks in here already write
   `#crLineBadge.cr-linebadge` (the film treatment, the marquee, the v0.573 move to the top edge),
   all at id+class. Editing one of them would leave the other two winning on source order — the trap
   the bulbs hit in v0.492 and the Tetr-Ink container query hit in v0.483. Match the specificity and
   come LAST, or lose.
   ⚠ The width breakpoints are overridden rather than deleted upstream for the same reason: those
   blocks are the history of this sign and they still read.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  left:0; right:0; width:auto; max-width:none;
  transform:none;
  display:flex; align-items:center; justify-content:flex-start;
  gap:calc(6px * var(--gs,1));
  /* ⚠ the reel's own numbers, not this sign's old ones */
  font-family:var(--fontD); font-size:calc(var(--nmsz) * var(--gs,1));
  letter-spacing:calc(.5px * var(--gs,1)); line-height:1.35;
  color:#fdf6e3; background-color:#0d0a08;
  border-left:0; border-right:0;
  border-top:calc(2px * var(--gs,1)) solid #0d0a08;
  border-bottom:calc(2px * var(--gs,1)) solid #0d0a08;
  padding:calc(4.75px * var(--gs,1)) calc(6px * var(--gs,1));
  box-shadow:0 calc(2px * var(--gs,1)) 0 rgba(13,10,8,.55);
  /* ⚠ the sign does not blink as a whole any more — the FACE shakes and the word pulses. A plate
     that fades in and out took the portrait's legibility with it. */
  animation:crlInTop .28s var(--pop,ease-out) both;
}
/* ⚠⚠ AND THE THREE WIDTH BREAKPOINTS ARE CANCELLED. This is the actual fix: without these three
   lines the rules above are simply outranked again at 560px and 640px and the sign grows on the
   unfolded fold exactly as it did before. */
@media (min-width:560px){
  #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
    font-size:calc(var(--nmsz) * var(--gs,1));
    padding:calc(4.75px * var(--gs,1)) calc(6px * var(--gs,1));
    border-top-width:calc(2px * var(--gs,1)); border-bottom-width:calc(2px * var(--gs,1));
  }
  #crLineBadge.cr-linebadge::before,#foePlayLbl.cr-linebadge::before,#crLineBadge.cr-linebadge::after,#foePlayLbl.cr-linebadge::after{content:none; height:0;}
}
@media (min-width:640px){
  #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{font-size:calc(var(--nmsz) * var(--gs,1));}
}
/* ⚠⚠⚠ AND THE EXIT STOPS RE-CENTRING ITSELF. `crlOut` still ended on
   `transform:translate(-50%,-50%) scale(.94)` — left over from the days when this sign floated in
   the middle of the screen. v0.573 moved the strip to the top edge and restated `crlIn`/`crlReelOut`
   without the centring, but `.out` was missed, so the LAST thing the sign did every single time was
   jump half a screen left and half its height up while it faded. Same family as the v0.573 note
   directly above: a keyframe that still writes the old transform undoes the move for the whole
   length of its animation. */
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{animation:crlOutTop .42s ease both;}
@keyframes crlOutTop{ from{opacity:1; transform:none} to{opacity:0; transform:translateX(18%)} }

/* ⭐⭐ THE PORTRAIT IS THE HUD'S PORTRAIT. Same width the name strip hangs under every playfield,
   so the face on the connecting sign and the face in the corner of the game are visibly one
   inkling — which is what "the same inkling as everywhere else" means. */
#crLineBadge.cr-linebadge .crl-ink,#foePlayLbl.cr-linebadge .crl-ink{display:flex; align-items:center; flex:0 0 auto;}
#crLineBadge.cr-linebadge .crl-gink,#foePlayLbl.cr-linebadge .crl-gink{width:calc(30px * var(--gs,1)); height:calc(30px * var(--gs,1));
  display:block; position:relative;}
#crLineBadge.cr-linebadge .crl-gink svg,#foePlayLbl.cr-linebadge .crl-gink svg{width:100%; height:100%; image-rendering:pixelated;}
/* ⭐⭐⭐ THE QUESTION MARK LIVES ON THE SIGN, NOT ON THE MASCOT. v0.576 deleted the gold `?` that
   used to be welded to the 'wonder' sprite and left a standing instruction with it: "if a marker is
   ever wanted on that screen again it belongs on the SCREEN, not welded to the mascot's sprite."
   Odaroo has now asked for it back. This is that marker, honouring that note — a real element on
   the strip, beside a deliberately BLANK face, so the sprite stays the one clean drawing every
   other screen uses. */
#crLineBadge.cr-linebadge .crl-q,#foePlayLbl.cr-linebadge .crl-q{
  font-family:var(--fontD); font-size:calc(var(--nmsz) * 1.35 * var(--gs,1));
  color:var(--gold,#d9a232); line-height:1; font-style:normal; flex:0 0 auto;
  text-shadow:calc(2px * var(--gs,1)) calc(2px * var(--gs,1)) 0 #000;
  animation:crlQ .9s steps(2) infinite;
}
@keyframes crlQ{0%,100%{transform:rotate(-8deg) scale(1)} 50%{transform:rotate(9deg) scale(1.14)}}
/* ⭐⭐ SHAKING AND GLITCHING WHILE IT IS TRYING. The portrait jitters on a step timing (never a
   smooth ease — this is a 1930s cartoon and a pixel sprite, and a sub-pixel slide reads as blur)
   and the hue tears sideways every other beat.
   ⚠ IT IS SWITCHED OFF THE MOMENT THE SIGN SAYS CONNECTED. `.ok` is the settled state; a face still
   shaking under the word CONNECTED says the opposite of the word. */
#crLineBadge.cr-linebadge:not(.ok) .crl-gink,#foePlayLbl.cr-linebadge:not(.ok) .crl-gink{animation:crlShake .28s steps(2) infinite;}
@keyframes crlShake{
  0%{transform:translate(0,0)}
  25%{transform:translate(-1.5px,1px)}
  50%{transform:translate(1.5px,-1px)}
  75%{transform:translate(-1px,-1.5px)}
  100%{transform:translate(0,0)}
}
#crLineBadge.cr-linebadge:not(.ok) .crl-gink::after,#foePlayLbl.cr-linebadge:not(.ok) .crl-gink::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, transparent 0 38%, #6ff0ff55 38% 46%, transparent 46% 100%);
  mix-blend-mode:screen; animation:crlTear 1.15s steps(3) infinite;
}
@keyframes crlTear{0%{transform:translate(0,-60%)} 60%{transform:translate(3px,140%)} 100%{transform:translate(0,140%)}}
#crLineBadge.cr-linebadge.ok .crl-gink,#foePlayLbl.cr-linebadge.ok .crl-gink{animation:none;}
#crLineBadge.cr-linebadge.ok .crl-gink::after,#foePlayLbl.cr-linebadge.ok .crl-gink::after{content:none;}
/* the dots keep their cycle and are sized off the word, so they stay in proportion at both folds */
#crLineBadge.cr-linebadge .crl-dots,#foePlayLbl.cr-linebadge .crl-dots{margin-left:calc(2px * var(--gs,1));}
#crLineBadge.cr-linebadge .crl-dots u,#foePlayLbl.cr-linebadge .crl-dots u{
  width:calc(var(--nmsz) * .38 * var(--gs,1)); height:calc(var(--nmsz) * .38 * var(--gs,1));
  background:var(--cream,#fdf6e3);
}

/* ============================================================
   ⭐⭐⭐ v0.588 — THE CONNECTING STRIP IS A SECOND REEL, A WORD, AND NOTHING ELSE.
   (Odaroo, 2026-08-04: "remove the inkling from the connecting bar — it looks weird popping up
   again. It sits at the SAME position as the film reel bar, but it is a SECOND reel that appears ON
   TOP of the existing one. Text centred, same font as the resized username. Slides IN from one
   side, sits centred while connecting, then slides OUT the other side when done. Glitchy on the way
   in, smooth on the way out. Same film styling — white dots on black.")

   This block is the whole of that, and it deliberately UNDOES three of its own predecessors:
     · v0.573 put the player's portrait on the strip — the markup is gone (js/online.js) and the
       rules for it are cancelled here so a cached stylesheet cannot leave a gap where it stood.
     · v0.578 hung a gold `?` beside that portrait — gone with it.
     · the sign sat `justify-content:flex-start` so the word lined up under a left-hand face. With
       the face gone there is nothing to line up with, so the word takes the middle.

   ⚠⚠ IT IS LAST IN THE FILE ON PURPOSE, AND THAT IS NOT TIDINESS. Five earlier blocks in here write
   `#crLineBadge.cr-linebadge` at id+class — the film treatment, the marquee, the v0.555 height, the
   v0.573 move to the top edge and the v0.578 restatement. Editing any ONE of them leaves the others
   winning on source order. Match the specificity and come last, or lose. (The v0.492 bulbs and the
   v0.483 Tetr-Ink query are the two times this was learned the hard way.)
   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it wipes the sprockets. That is the v0.473 trap and
   it is why the white-dots-on-black survives every one of these rewrites untouched.
   ============================================================ */
/* the word takes the middle now that nothing shares the strip with it */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ justify-content:center; text-align:center; }
/* ⚠ the portrait and the question mark are CANCELLED, not merely unbuilt. The markup no longer
   emits either, so these only matter for a stale cached build — but a 30px empty box and a gold `?`
   with no face beside it is exactly the kind of half-state a cache produces. */
#crLineBadge.cr-linebadge .crl-ink,#foePlayLbl.cr-linebadge .crl-ink,
#crLineBadge.cr-linebadge .crl-q,#foePlayLbl.cr-linebadge .crl-q{ display:none !important; }

/* ---- IN: a glitchy arrival from the LEFT, settling centred -----------------
   ⚠⚠ THE JITTER IS ON `steps()`, NEVER AN EASE. This is a 1930s cartoon drawn in pixels; a smooth
   sub-pixel slide reads as motion blur, and every other glitch in the game (crlShake, the title
   sign, the tear) is stepped for the same reason. The strip arrives in four hard stops with the
   opacity flicking on and off between them, which is what makes it read as film catching rather
   than as a panel animating in.
   ⚠ IT ENDS ON `transform:none`, and it must: `.cr-linebadge` at the top edge carries no centring
   transform of its own any more (the v0.573 note), so a keyframe that ends anywhere else leaves the
   strip parked off its own position for as long as the sign is up. */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ animation:crlGlitchIn .42s steps(1,end) both; }
@keyframes crlGlitchIn{
  0%  { transform:translateX(-100%); opacity:0; }
  25% { transform:translateX(-38%);  opacity:1; }
  40% { transform:translateX(-46%);  opacity:.35; }
  55% { transform:translateX(-12%);  opacity:1; }
  70% { transform:translateX(-16%);  opacity:.55; }
  85% { transform:translateX(3%);    opacity:1; }
  100%{ transform:none;              opacity:1; }
}
/* ⚠⚠ THE THREADING FILM AND THE ARRIVAL CANNOT SHARE ONE `animation` SLOT. `animation` is a
   SHORTHAND — a later rule REPLACES it rather than adding to it (the v0.447 featured-tile note), and
   `:not(.ok):not(.out)` further up this file claims that slot for `crlThread`. So the two are
   listed together, in one declaration, on the state that needs both: the strip slides in while its
   sprockets are already running. Dropping either name from this list silently deletes that effect. */
#crLineBadge.cr-linebadge:not(.ok):not(.out),#foePlayLbl.cr-linebadge:not(.ok):not(.out){
  animation:crlGlitchIn .42s steps(1,end) both, crlThread .5s linear .42s infinite;
}

/* ---- WHILE IT WAITS: the three dots cycle, and they are the "..." ----------
   ⚠ THEY ARE SIZED OFF THE WORD (--nmsz), so shrinking the username in v0.588 shrank these with it
   and the strip stayed in proportion without a second number to keep in step. */
#crLineBadge.cr-linebadge .crl-dots,#foePlayLbl.cr-linebadge .crl-dots{ margin-left:calc(3px * var(--gs,1)); }

/* ---- OUT: three green flashes, then a smooth slide off to the RIGHT --------
   ⚠⚠ THREE, AND IT IS THE ITERATION COUNT THAT SAYS SO. crLineProject (js/online.js) adds `.lit`
   once and waits 1300ms; the `3` below is the only place the number three exists. Change it and the
   1300 has to move with it, or the strip leaves mid-flash or sits idle after the last one.
   ⚠ The flash is on the WORD, not the plate: a whole black bar strobing green over a live game is
   the thing v0.543 deleted, and it is not coming back. */
#crLineBadge.cr-linebadge.ok.lit b,#foePlayLbl.cr-linebadge.ok.lit b{ animation:crlFlash .34s ease-out 3 both; }
/* ⚠ SMOOTH ON THE WAY OUT, deliberately unlike the entrance — Odaroo asked for glitchy in and a
   smooth slide out, and the contrast is the point: the line steadying IS the message. This overrides
   the v0.578 `.out` (translateX(18%), which barely moved) with a real exit off the right edge. */
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{ animation:crlSlideOutR .5s cubic-bezier(.4,0,.7,.2) both; }
@keyframes crlSlideOutR{
  0%  { transform:translateX(0);    opacity:1; }
  100%{ transform:translateX(112%); opacity:0; }
}

/* ⭐⭐⭐ v0.588 — AND IT IS THE SAME THICKNESS AS THE REEL IT LIES ON. (Odaroo: "it sits at the SAME
   position/level as the film reel bar — a SECOND reel that appears ON TOP of the existing one.")
   v0.555 pinned this strip to `1.15 * clamp(44px,9vw,58px)` — the INKVILLE logo's height — because
   back then it was a banner across the middle of the screen and Odaroo said it looked too thin.
   That number outlived its reason twice over: v0.573 moved the sign onto the HUD line, and v0.588
   dropped the word to the chip size. Measured on the folded phone with both in place: the HUD reel
   was 28.3px and this strip was still 50.6px, sharing a top edge and disagreeing about everything
   below it — which reads as a panel dropped over the HUD, not as a second reel.
   ⚠⚠ THE FIX IS TO REMOVE THE NUMBER, NOT TO COPY 28.3 INTO IT. Both strips are one line of --nmsz
   text inside the same 4.75px padding and 2px leader, so with the fixed height gone this one lands
   on the reel's height by the same arithmetic — 11 x 1.35 + 9.5 + 4 = 28.35 folded, measured 28.3.
   A copied constant would drift the moment either padding moved; shared arithmetic cannot.
   ⚠ THE ≥560px RULE HAS TO BE CANCELLED TOO — it restates the old height and would otherwise win on
   the unfolded fold by being more specific about the viewport (the v0.555 note says so itself). */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ height:auto; }
@media (min-width:560px){ #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ height:auto; } }

/* ⚠⚠⚠ v0.588 — AND CONNECTED KEEPS THE 2px LEADER, OR THE STRIP SHRINKS AS IT SETTLES.
   `#crLineBadge.cr-linebadge.ok,OR THE STRIP SHRINKS AS IT SETTLES.
   `#foePlayLbl.cr-linebadge.ok{ ... border:0 }` further up this file is left from the v0.536 film
   era, when the sign had no leader to lose. The v0.578 block gave every state a 2px top and bottom
   border, so from then on the strip quietly lost 4px of height at the exact moment it said
   CONNECTED — measured 32.4px connecting against 28.4px connected on the unfolded fold. Invisible
   while the sign floated mid-screen; obvious now that it lies on the HUD reel, because the two
   strips agree on their thickness right up until the good news arrives and then disagree.
   ⚠ Restated rather than fixed upstream, for the reason this whole block exists: three other rules
   write `.ok` at this specificity and editing the earliest would leave the later ones winning. */
#crLineBadge.cr-linebadge.ok,#foePlayLbl.cr-linebadge.ok{
  border-top:calc(2px * var(--gs,1)) solid #0d0a08;
  border-bottom:calc(2px * var(--gs,1)) solid #0d0a08;
  border-left:0; border-right:0;
}

/* ============================================================
   ⭐⭐⭐ v0.591 — THE CONNECTING STRIP IS THE HOME REEL, AND IT ROLLS. (Odaroo, 2026-08-04: "same
   thickness as the home menu film reel bar — not thin. Same background: white pixelated dots on
   black, same film reel look, same position, full width. Don't stretch or minimize the background —
   the same exact pattern. The dots move RIGHT while connecting, and EVEN FASTER once it says
   CONNECTED — the speed IS the connection state.")

   Three things change and nothing else does: the PATTERN, the THICKNESS and the SPEED.

   ⚠⚠ THE PATTERN WAS NEVER ACTUALLY THE SAME FILM, WHICH IS WHY IT READ AS A DIFFERENT SIGN.
   v0.536 drew this strip with `repeating-linear-gradient(to right, #f2ead6 0 5px, transparent
   5px 13px)` at `background-size:100% 5px` — a 5px DASH on a 13px period, stretched across the box.
   The home reel (#hudHome::before/::after, css/menu.css) draws a 6px SQUARE on a 14px period in a
   7px band. Two different holes, two different periods, two different row heights: near enough to
   pass a glance and wrong side by side, which is exactly the complaint. It is now the home bar's
   own recipe, character for character, so the second reel lies on the first as one piece of film.
   ⚠ `background-size` IS THE TILE, NOT THE BOX. The old `100%` is what "stretched" the pattern —
   one gradient scaled to the strip's width. A tile of `--film-per*2` repeated with `repeat-x` is
   what makes the run identical at 375px and at 906px instead of merely similar.

   ⚠⚠ THE THICKNESS IS THE HOME BAR'S MEASURED HEIGHT (--filmbar-h, css/base.css), NOT arithmetic.
   v0.588 removed a fixed height so this strip would land on the GAME HUD reel's height by shared
   arithmetic — correct then, and it is what makes it 28.35px. The bar being matched now is the HOME
   reel, whose height is set by the cups/ink chips inside it, not by a formula: measured 29px on
   BOTH folds. There is no arithmetic to share, so the number is named once in base.css and this
   pulls it. If the home bar's contents ever change size, re-measure it and change that one var.
   ⚠ `box-sizing:border-box` is already set on this strip, so the `.ok` state's 2px leader comes out
   of the 29 rather than adding to it — which is what keeps CONNECTING and CONNECTED the same height
   (the v0.588 note above; the fix there stays valid, it is just no longer load-bearing for height).

   ⚠⚠ SPEED IS THE STATE, AND IT IS ONE ANIMATION IN TWO DURATIONS — not two animations. 1.15s while
   the line is being made, 0.34s the moment it lands: the film visibly catches. Doing it with two
   differently-named keyframes would let the phase reset on the switch, and the holes would JUMP at
   the exact moment the player is being told everything is fine.
   ⚠⚠ `animation` IS A SHORTHAND AND THE ENTRANCE SHARES THE SLOT. The glitchy arrival (crlGlitchIn,
   v0.588) and the rolling film have to be listed TOGETHER in one declaration on the connecting
   state, or naming either one alone silently deletes the other. This is the same trap the v0.588
   note describes; it is restated because this block claims that slot from it.
   ⚠ THE SHIFT IS EXACTLY ONE PERIOD, on BOTH layers, so the loop is seamless and the top row cannot
   drift out of step with the bottom one.
   ⚠ background-position-X only — the Y half is what pins the two rows to the top and bottom edges,
   and a keyframe that wrote the shorthand would fling them both to the middle.
   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it wipes the sprockets (the v0.473 trap).
   ⚠⚠ AND IT IS LAST IN THE FILE, for the reason the v0.588 block gives: six rules now write
   `#crLineBadge.cr-linebadge` at id+class, and the only way to win is to match the specificity and
   come after them. Anything added later goes BELOW this.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  height:calc(var(--filmbar-h) * var(--gs,1));
  background-color:#0d0a08;
  /* v0.734 — the shared big gauge (--film2-*, css/base.css). Same numbers as the HUD reel and the
     notification strip by construction, not by having been typed the same way. */
  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)),
    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), var(--film2-per) var(--film2-h);
  background-position:0 0, 0 100%;
  background-repeat:repeat-x, repeat-x;
  will-change:background-position;
}
@media (min-width:560px){ #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{ height:calc(var(--filmbar-h) * var(--gs,1)); } }
/* the roll itself — one keyframe, so a change of speed never resets the phase */
@keyframes crlFilmRoll{
  from{ background-position-x:0, 0; }
  to  { background-position-x:var(--film2-per), var(--film2-per); }
}
/* CONNECTING — the strip glitches in from the left while its film is already running, slowly */
#crLineBadge.cr-linebadge:not(.ok):not(.out),#foePlayLbl.cr-linebadge:not(.ok):not(.out){
  animation:crlGlitchIn .42s steps(1,end) both, crlFilmRoll 1.15s linear infinite;
}
/* CONNECTED — the projector catches: the same film, three and a half times faster */
#crLineBadge.cr-linebadge.ok:not(.out),#foePlayLbl.cr-linebadge.ok:not(.out){
  animation:crlFilmRoll .34s linear infinite;
}
/* leaving — it keeps rolling fast on the way off, so the exit reads as film running out */
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{
  animation:crlSlideOutR .5s cubic-bezier(.4,0,.7,.2) both, crlFilmRoll .34s linear infinite;
}

/* ============================================================
   ⭐⭐⭐ v0.596 — THE CONNECTING SIGN IS A BIG BANNER ACROSS THE MIDDLE AGAIN, AND THE REEL'S
   MOTION IS THE STATE. (Odaroo, 2026-08-04: "the connecting bar is STILL too small and thin at the
   top. A LARGE film reel banner across the MIDDLE of the screen, same thickness as the INKVILLE
   title text, same film look as the home menu bar, full width, large text. The reel starts STATIC
   while connecting. When it connects the text says CONNECTED and flashes green, the WHOLE banner
   flashes green a few times, the reel starts ANIMATING at the home bar's speed, then it goes.")

   This reverses two earlier decisions on purpose, and both reversals are the actual instruction:
     · v0.573 moved the sign onto the HUD line at the top edge. It comes back to the MIDDLE.
     · v0.591 matched it to the home reel's 29px height and rolled the film WHILE CONNECTING.
       The height is now the title logo's, and the film is STILL while connecting and only runs
       once the line lands — the roll is the good news, not the waiting.
   The PATTERN v0.591 fixed is kept exactly as it is: the home bar's own 6px-square-on-a-14px-period
   recipe, tiled with repeat-x, never stretched. That was right and is untouched below.

   ⚠⚠ IT IS CENTRED WITH A NEGATIVE MARGIN, NOT WITH `translateY(-50%)`, AND THAT IS THE WHOLE
   REASON THIS BLOCK IS SHORT. Every one of this sign's keyframes writes `transform` — the glitchy
   arrival (crlGlitchIn) and the exit (crlSlideOutR) both do. A centring transform would be wiped by
   each of them for the entire length of its animation, which is precisely the fault the v0.573 and
   v0.588 notes above describe (a keyframe that still carries the old transform undoes the move).
   The height is a known number, so half of it as a margin centres the strip with no transform at
   all and the keyframes can go on owning that property untouched. Do not "tidy" this into a
   transform.
   ⚠ `--connbar-h` IS DERIVED FROM THE LOGO'S OWN CLAMP, not eyeballed to match it. `h1.glogo` is
   `font-size:clamp(26px,9vw,58px)` (css/wjam.css), so the band is 1.9x that: 64px folded (375) and
   110px unfolded (906) — against the 28.3px it was, which is the "still too thin" complaint.
   ⚠ THE WORD IS SIZED OFF THE SAME CLAMP for the same reason: one number moves and the banner stays
   in proportion on both folds. 0.72 puts CONNECTING at 24px folded / 42px unfolded — measured, the
   ten characters and the three dots occupy 168px of the folded 375, so it cannot clip.
   ⚠⚠ THE 560px MEDIA RULE HAS TO BE CANCELLED AGAIN. It restates a height and would otherwise win
   on the unfolded fold by being more specific about the viewport rather than about the element —
   the same trap v0.555 and v0.588 each had to answer here.
   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it wipes the sprockets (the v0.473 trap).
   ⚠⚠ AND THIS IS LAST IN THE FILE. Seven rules now write `#crLineBadge.cr-linebadge` at id+class;
   matching the specificity and coming after them is the only way to win. Anything later goes BELOW.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  --connbar-h:calc(1.9 * clamp(26px, 9vw, 58px) * var(--gs,1));
  top:50%; bottom:auto;
  margin-top:calc(var(--connbar-h) / -2);
  left:0; right:0; width:auto; max-width:none;
  height:var(--connbar-h);
  display:flex; align-items:center; justify-content:center;
  gap:calc(10px * var(--gs,1));
  font-family:var(--fontD);
  font-size:calc(.72 * clamp(26px, 9vw, 58px) * var(--gs,1));
  line-height:1; letter-spacing:calc(1px * var(--gs,1));
  padding:0 calc(10px * var(--gs,1));
  box-shadow:0 calc(4px * var(--gs,1)) 0 rgba(13,10,8,.55);
}
@media (min-width:560px){
  #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
    height:var(--connbar-h);
    font-size:calc(.72 * clamp(26px, 9vw, 58px) * var(--gs,1));
    padding:0 calc(10px * var(--gs,1));
  }
}
/* ⚠ THE DIALOG RULE STILL WINS AND IT MUST KEEP DOING SO — a bill's buttons may not sit behind this
   banner (the v0.560 note). It writes `top` only, so the negative margin has to be cancelled with
   it or the sign would ride half its own (now very large) height off the top of the screen. */
body:has(#modalBg.on) #crLineBadge.cr-linebadge,body:has(#modalBg.on) #foePlayLbl.cr-linebadge{ margin-top:0; }

/* ---- CONNECTING: the film is STILL. --------------------------------------
   ⚠⚠ THIS IS THE HALF OF THE INSTRUCTION THAT IS EASY TO LOSE. v0.591's rule for this state lists
   `crlFilmRoll` alongside the entrance, and `animation` is a SHORTHAND — so this declaration
   REPLACES that list rather than adding to it, and dropping the roll from it is exactly how the
   reel goes static. The entrance stays named, because naming only the roll would delete it. */
#crLineBadge.cr-linebadge:not(.ok):not(.out),#foePlayLbl.cr-linebadge:not(.ok):not(.out){
  animation:crlGlitchIn .42s steps(1,end) both;
}
/* ---- CONNECTED: the projector catches at the HOME BAR'S OWN SPEED. --------
   ⚠ 1.6s over one period (--film-per*2) is `hudFilmRoll` in css/menu.css, character for character.
   Odaroo asked for the same speed as the home reel, so the number is that one's, not a new one; if
   the home bar is ever retuned, retune it there and copy it here (the two cannot share a keyframe —
   they scroll different elements).
   ⚠⚠ TWO ANIMATIONS, ONE DECLARATION. The rolling film and the green flash of the whole banner both
   live in this slot, so they are listed together; either one written alone silently deletes the
   other. They touch different properties (background-POSITION vs background-COLOR), so they
   compose — but only because crlBannerGreen below never mentions position. */
#crLineBadge.cr-linebadge.ok:not(.out),#foePlayLbl.cr-linebadge.ok:not(.out){
  animation:crlFilmRoll 1.6s linear infinite;
}
#crLineBadge.cr-linebadge.ok.lit:not(.out),#foePlayLbl.cr-linebadge.ok.lit:not(.out){
  animation:crlFilmRoll 1.6s linear infinite, crlBannerGreen .38s ease-out 3 both;
}
/* the WHOLE strip flashes, three times, and settles black so it leaves the colour it arrived in.
   ⚠ background-COLOR only — a `background:` shorthand here would wipe the sprockets mid-flash. */
@keyframes crlBannerGreen{
  0%  { background-color:#0d0a08; box-shadow:0 calc(4px * var(--gs,1)) 0 rgba(13,10,8,.55); }
  40% { background-color:#12401f; box-shadow:0 0 calc(26px * var(--gs,1)) rgba(108,245,140,.55); }
  100%{ background-color:#0d0a08; box-shadow:0 calc(4px * var(--gs,1)) 0 rgba(13,10,8,.55); }
}
/* the word flashes with it — same three beats, same duration, so they read as one pulse */
#crLineBadge.cr-linebadge.ok.lit b,#foePlayLbl.cr-linebadge.ok.lit b{ animation:crlFlash .38s ease-out 3 both; }
/* ---- LEAVING: still rolling, off to the right. --------------------------- */
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{
  animation:crlSlideOutR .5s cubic-bezier(.4,0,.7,.2) both, crlFilmRoll 1.6s linear infinite;
}
/* the three cycling dots grow with the word — they are the "..." in "Connecting..." */
#crLineBadge.cr-linebadge .crl-dots u,#foePlayLbl.cr-linebadge .crl-dots u{
  width:calc(.16 * clamp(26px, 9vw, 58px) * var(--gs,1));
  height:calc(.16 * clamp(26px, 9vw, 58px) * var(--gs,1));
}
@media (min-width:560px){
  #crLineBadge.cr-linebadge .crl-dots u,#foePlayLbl.cr-linebadge .crl-dots u{
    width:calc(.16 * clamp(26px, 9vw, 58px) * var(--gs,1));
    height:calc(.16 * clamp(26px, 9vw, 58px) * var(--gs,1));
  }
}

/* ============================================================
   ⭐⭐⭐ v0.598 — THE BIG BANNER MOVES BACK TO THE TOP LINE, AND IT ARRIVES LEFT-TO-RIGHT.
   (Odaroo, 2026-08-04: "put the connecting banner in the SAME EXACT SPOT where CONNECTED shows —
   the top bar, not mid-screen. Same thickness, same film reel, same large text. It fades in from
   the LEFT to the RIGHT, blinks once, twice, then it starts moving forward and the FILM REEL starts
   animating — the same white dots scrolling as the home menu bar. Smooth, no jumps.")

   So this reverses v0.596's return to the middle and keeps everything else it did: the height is
   still the INKVILLE logo's clamp, the word is still large, the pattern is still the home bar's own
   6px-square-on-a-14px-period film. Only the LINE it sits on and the way it arrives change.

   ⚠⚠ IT IS PINNED WITH `top` AND NO MARGIN, AND THE NEGATIVE MARGIN MUST BE CANCELLED. v0.596
   centres this strip by `margin-top:calc(var(--connbar-h) / -2)` — leave that in place at the top
   edge and the whole banner rides half its (very large) height off the top of the screen. The
   `top` is the HUD/name strip's own number, `calc(4px * var(--gs,1) + env(safe-area-inset-top,0px))`
   (`.vsnm.vsnm-top`, css/hub.css) — the same line CONNECTED has always been read on. If that strip
   ever moves, move this with it.

   ⚠⚠⚠ THE ARRIVAL IS A `clip-path` WIPE, NOT A `transform`, AND THAT IS THE ONE STRUCTURAL DECISION
   HERE. Every previous entrance this sign has had (crlGlitchIn, crlInTop, crlIn) animated
   `transform` — and so does the EXIT (crlSlideOutR). A property can only be owned by one running
   animation, so an entrance on `transform` is an entrance that fights its own exit and its own
   position, which is the fault the v0.573 / v0.588 / v0.596 notes above each had to answer in turn.
   Wiping the strip open with `clip-path:inset(0 100% 0 0)` → `inset(0)` leaves `transform`
   completely free for the slide-off, so the two can never collide again. Do not "tidy" this back
   into a translate.

   ⚠ THE THREE BEATS ARE THREE ANIMATIONS IN ONE DECLARATION, ON DIFFERENT PROPERTIES.
     1. `crlWipeIn`   0.40s  — clip-path, left to right.
     2. `crlTwoBlink` 0.44s at 0.40s — opacity, on `steps(1)`: two hard blinks, never a fade
        (this is a 1930s cartoon in pixels; a smooth dip reads as a dying screen, not a sign
        catching). It ends fully opaque so nothing is left half-lit.
     3. `crlFilmRoll` from 0.84s — the reel starts only AFTER the blinks, which is the order Odaroo
        described and the reason the roll carries a delay rather than starting at zero.
   `animation` is a SHORTHAND: naming any one of them alone silently deletes the other two (the
   v0.447 featured-tile trap, restated here because this block claims the slot from v0.596's rule).

   ⚠ CONNECTED IS UNCHANGED AND KEEPS ROLLING at the home bar's 1.6s — the film is already running
   by then, so the word changing does not restart the phase and there is no jump at the handover.
   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it wipes the sprockets (the v0.473 trap).
   ⚠⚠ AND THIS IS LAST IN THE FILE. Eight rules now write `#crLineBadge.cr-linebadge` at id+class;
   matching the specificity and coming after them is the only way to win. Anything later goes BELOW.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  top:calc(var(--hudtop,16px) + var(--satnm, env(safe-area-inset-top,0px)));
  bottom:auto;
  margin-top:0;
  transform:none;
}
body:has(#modalBg.on) #crLineBadge.cr-linebadge,body:has(#modalBg.on) #foePlayLbl.cr-linebadge{
  top:calc(var(--hudtop,16px) + var(--satnm, env(safe-area-inset-top,0px)));
  margin-top:0;
}
/* 1+2+3 — the wipe, the two blinks, then the reel catches */
@keyframes crlWipeIn{
  from{ clip-path:inset(0 100% 0 0); }
  to  { clip-path:inset(0 0 0 0); }
}
@keyframes crlTwoBlink{
  0%,100%{ opacity:1 }
  18%    { opacity:.18 }
  36%    { opacity:1 }
  62%    { opacity:.18 }
  80%    { opacity:1 }
}
#crLineBadge.cr-linebadge:not(.ok):not(.out),#foePlayLbl.cr-linebadge:not(.ok):not(.out){
  animation:
    crlWipeIn   .40s ease-out both,
    crlTwoBlink .44s steps(1,end) .40s both,
    crlFilmRoll 1.6s linear .84s infinite;
}
/* ⚠⚠⚠ AND `.ok` KEEPS THE SAME THREE-NAME LIST, WHICH IS THE WHOLE OF "NO JUMP AT THE HANDOVER".
   Animations are matched between two computed styles BY NAME AND POSITION IN THE LIST. The roll sits
   third while connecting, so a CONNECTED rule that named `crlFilmRoll` on its own would be a
   DIFFERENT animation at a different index — the browser restarts it, and the sprockets jump up to a
   full period sideways at the exact moment the player is told everything is fine. Keeping the wipe
   and the blink named (at 0s, already finished) holds the roll at index 2 and it simply carries on.
   ⚠ The green flash is added to the END of the list for the same reason — appending cannot disturb
   what is in front of it, whereas re-ordering would. */
#crLineBadge.cr-linebadge.ok:not(.out),#foePlayLbl.cr-linebadge.ok:not(.out){
  animation:
    crlWipeIn   0s linear both,
    crlTwoBlink 0s linear both,
    crlFilmRoll 1.6s linear .84s infinite;
}
#crLineBadge.cr-linebadge.ok.lit:not(.out),#foePlayLbl.cr-linebadge.ok.lit:not(.out){
  animation:
    crlWipeIn   0s linear both,
    crlTwoBlink 0s linear both,
    crlFilmRoll 1.6s linear .84s infinite,
    crlBannerGreen .38s ease-out 3 both;
}
/* LEAVING — same list, same order, with the slide appended. The film must not restart on the way
   out either, for the reason above; and the slide goes LAST so it cannot shift the roll's index. */
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{
  animation:
    crlWipeIn   0s linear both,
    crlTwoBlink 0s linear both,
    crlFilmRoll 1.6s linear .84s infinite,
    crlSlideOutR .5s cubic-bezier(.4,0,.7,.2) both;
}

/* ============================================================
   ⭐⭐⭐ v0.611 — THE BANNER IS THE HOME HUD BAR: SAME HEIGHT, SAME LINE, ON EVERY SCREEN AND EVERY
   FOLD. (Odaroo, 2026-08-05: "I love the connecting and connected banner on top of the reel. Make
   sure the HEIGHT of the reel matches the game menus, and the POSITION too — sometimes it's bigger,
   sometimes it's smaller. On all screens — folded, unfolded, minimized and maximized and back — I
   want the connecting sign in the same position the main menu HUD is at. And make sure we can read
   it. Don't flash it away: even if it connects instantly, show CONNECTING for a second or two, then
   CONNECTED for a second or two, then glitch off — glitchy before and after.")

   ⚠⚠⚠ WHY IT WAS "SOMETIMES BIGGER, SOMETIMES SMALLER" — the sizes were never the same thing twice.
   v0.596 gave this sign `--connbar-h: 1.9 * clamp(26px, 9vw, 58px)`, tied to the INKVILLE logo. A
   `9vw` clamp is a number that CHANGES WITH THE VIEWPORT: measured 64px on the folded phone and
   110px unfolded — a 72% swing on the same sign — while the bar it is supposed to lie on measured
   54px and 57px, a 6% one. Folding the phone, or the URL bar showing and hiding, or the window
   being minimised and restored, re-evaluated that clamp and the banner visibly changed size. That
   is the whole complaint and it is a `vw` unit, not a bug in the animation.
   THE FIX IS TO STOP DERIVING THE HEIGHT FROM THE VIEWPORT AT ALL. It is `--filmbar-h` now — the
   home bar's own arithmetic (css/base.css), which steps once at 430px and is otherwise constant.
   Nothing in this sign's geometry reads `vw` any more; grep it and see.

   ⚠⚠ THE POSITION IS MEASURED, NOT GUESSED, AND THAT PART IS IN JS. `#hudHome` sits at
   `margin-top:var(--hudlift)` — a number hudLift() MEASURES from the avatar lens overhang and
   writes onto the bar itself, not onto :root — so no stylesheet rule can reach it. crLineFit()
   (js/online.js) reads the real bar and writes `top`/`height` inline. The rules here are the
   fallback for the frame before that lands and for the screens where the home bar is not in the
   layout, and they are deliberately close enough that the handover is invisible.

   ⚠ THE WORD IS SIZED OFF THE BAR, NOT OFF THE SCREEN — 0.40 of the strip's own height, so it is
   ~21.6px folded and ~22.8px unfolded. It cannot outgrow the bar it sits in, it reads at arm's
   length on the folded phone, and "CONNECTING" plus its three dots occupies ~175px of the folded
   375. (The old 0.72-of-a-9vw-clamp put it at 42px unfolded — legible, but on a banner twice the
   height of the thing it was pretending to be.)

   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it wipes the sprockets (the v0.473 trap).
   ⚠⚠ AND THIS IS LAST IN THE FILE. Nine rules now write `#crLineBadge.cr-linebadge` at id+class;
   matching the specificity and coming after them is the only way to win. Anything later goes BELOW.
   ============================================================ */
#crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
  /* ⚠ --connbar-h is CANCELLED rather than deleted upstream: v0.596's block still declares it and
     still uses it in two media rules, and a stale cached stylesheet would otherwise keep the 9vw
     banner. Redefining it AS the bar height makes every one of those rules land on the right
     number instead of having to be hunted down and edited. */
  --connbar-h:calc(var(--filmbar-h) * var(--gs,1));
  height:var(--connbar-h);
  /* ⚠⚠ NO `min-height` HERE, DELIBERATELY. It was written first and it silently BEAT the measured
     height: crLineFit sets `height` inline, a min-height in the sheet still wins when it is larger,
     and the sign came out 57px tall sitting on a 54px bar with nothing in either file obviously
     wrong. A measurement must be able to make this strip SMALLER as well as larger. */
  margin-top:0;
  font-size:calc(var(--filmbar-h) * .40 * var(--gs,1));
  line-height:1;
  letter-spacing:calc(1px * var(--gs,1));
  padding:0 calc(10px * var(--gs,1));
  gap:calc(8px * var(--gs,1));
  align-items:center; justify-content:center;
  /* the two 3px ink leaders the home bar wears, so the strips share an edge treatment as well as a
     height. box-sizing is already border-box here, so these come OUT of --filmbar-h (the v0.591
     note) and CONNECTING and CONNECTED cannot differ in height. */
  border-top:calc(var(--hudbar-bd) * var(--gs,1)) solid #0d0a08;
  border-bottom:calc(var(--hudbar-bd) * var(--gs,1)) solid #0d0a08;
  border-left:0; border-right:0;
}
/* ⚠⚠ AND THE TWO 560px RULES ARE CANCELLED FOR THE THIRD TIME IN THIS FILE'S HISTORY (v0.555,
   v0.588, v0.596 each had to). They restate a height and a font-size and would otherwise win on the
   unfolded fold by being more specific about the VIEWPORT rather than about the element — which is
   the same class of fault as the 9vw clamp above: a rule keyed on screen width re-deciding a size
   that is supposed to be constant. */
@media (min-width:560px){
  #crLineBadge.cr-linebadge,#foePlayLbl.cr-linebadge{
    height:var(--connbar-h);   /* ⚠ no min-height — see the note above; it beats the measurement */
    font-size:calc(var(--filmbar-h) * .40 * var(--gs,1));
    padding:0 calc(10px * var(--gs,1));
  }
  #crLineBadge.cr-linebadge .crl-dots u,#foePlayLbl.cr-linebadge .crl-dots u{
    width:calc(var(--filmbar-h) * .12 * var(--gs,1));
    height:calc(var(--filmbar-h) * .12 * var(--gs,1));
  }
}
/* the three cycling dots are the "..." and they scale off the bar with the word */
#crLineBadge.cr-linebadge .crl-dots u,#foePlayLbl.cr-linebadge .crl-dots u{
  width:calc(var(--filmbar-h) * .12 * var(--gs,1));
  height:calc(var(--filmbar-h) * .12 * var(--gs,1));
}
/* ⚠ the dialog rule keeps winning — a bill's buttons may not sit behind this banner (v0.560) — and
   it only writes `top`, so it needs the margin cancelled with it. */
body:has(#modalBg.on) #crLineBadge.cr-linebadge,body:has(#modalBg.on) #foePlayLbl.cr-linebadge{ margin-top:0; }

/* ---- GLITCHY BEFORE **AND** AFTER --------------------------------------------
   (Odaroo: "it transitions off glitchily before and after.")
   v0.588 made the entrance glitchy and the exit deliberately smooth, and v0.598 kept that. The exit
   is now stepped too — same request, reversed answer, so the note is restated rather than left
   contradicting the code (the v0.490 lesson: reverse the comments and the self-test as well).

   ⚠⚠⚠ IT IS THE SAME ANIMATION NAME, `crlSlideOutR`, WITH NEW KEYFRAMES — the name is NOT changed
   and the four-name lists in v0.598's rules are NOT touched. Animations are matched between two
   computed styles BY NAME AND POSITION IN THE LIST, so introducing a differently-named exit would
   shift `crlFilmRoll` off index 2 and the sprockets would jump a full period sideways at the exact
   moment the sign leaves. Rewriting the keyframes in place changes how it looks and nothing else.
   ⚠ `steps(1,end)` IS ON THE KEYFRAME'S OWN STOPS, via animation-timing-function inside the
   keyframes, so the hard cuts survive whatever easing the shorthand in v0.598 declares. */
@keyframes crlSlideOutR{
  0%  { animation-timing-function:steps(1,end); transform:translateX(0);     opacity:1;   filter:none; }
  14% { animation-timing-function:steps(1,end); transform:translateX(-4%);   opacity:.25; }
  26% { animation-timing-function:steps(1,end); transform:translateX(3%);    opacity:1;   filter:invert(1); }
  38% { animation-timing-function:steps(1,end); transform:translateX(-2%);   opacity:.45; filter:none; }
  52% { animation-timing-function:steps(1,end); transform:translateX(9%);    opacity:1; }
  66% { animation-timing-function:steps(1,end); transform:translateX(6%);    opacity:.3; }
  80% { animation-timing-function:steps(1,end); transform:translateX(46%);   opacity:1; }
  100%{ transform:translateX(118%); opacity:0; }
}

/* ============================================================
   ⭐⭐⭐ v0.615 — THE BANNER STAYS PUT. IT APPEARS, IT SITS, IT FADES. (Odaroo, 2026-08-05: "I love
   the connecting and the connected film reel location. Remove the transition from left to right
   when they appear and when they disappear — just a smooth transition. Remove the screen invert,
   it's too much. Remove the glitchy exit — it simply appears on top and then disappears when it's
   connected. When it's connected I want GREEN text saying CONNECTED, flashing three times and then
   fading away normally. It stays in place, don't animate it. The only thing that animates is the
   white dots that show the film is moving forward.")

   This block reverses, on purpose, four things earlier blocks in this file each argued for:
     · v0.598's `crlWipeIn` left-to-right arrival and its two `crlTwoBlink` beats — GONE. The sign
       fades up in place.
     · v0.611's stepped, inverting `crlSlideOutR` exit — GONE. It fades out in place.
     · v0.611's `inkInvertFlash` screen inversion on reconnect — removed at the call site
       (js/online.js); the function is left in place, unused, because a treatment on this sign has
       been asked for and withdrawn more than once already.
     · v0.596/v0.598's `crlBannerGreen` whole-plate flash — GONE. The GREEN is on the WORD now,
       which is what was actually asked for.

   ⚠⚠⚠ NOTHING IN ANY STATE TOUCHES `transform` ANY MORE, AND THAT IS THE POINT. Every previous
   version of this sign had an entrance and an exit fighting over that one property (the whole of
   the v0.598 note is about dodging that collision with clip-path). With both movements deleted the
   collision cannot exist: the strip is pinned by `top` and stays exactly there from the frame it
   appears to the frame it is removed.

   ⚠⚠ THE FILM ROLL IS FIRST IN EVERY LIST, IN ALL FOUR STATES, AND THAT ORDERING IS LEVEL WITH THE
   FEATURE. Animations are matched between two computed styles BY NAME AND POSITION IN THE LIST
   (the v0.598 note). `crlFilmRoll` at index 0 everywhere means the sprockets carry straight on
   through CONNECTING → CONNECTED → leaving without ever restarting — so the one thing that IS
   allowed to move never jumps. Anything added to a state below goes on the END of its list.

   ⚠ AND THE ROLL NO LONGER CARRIES v0.598's `.84s` DELAY. That delay existed to let the wipe and
   the two blinks go first; with them deleted it would just be the reel standing still for most of a
   second at the one moment the player is waiting to see something happening.

   ⚠ NO `background:` SHORTHAND ANYWHERE BELOW — it wipes the sprockets (the v0.473 trap).
   ⚠⚠ AND THIS IS LAST IN THE FILE. Ten rules now write `#crLineBadge.cr-linebadge` at id+class;
   matching the specificity and coming after them is the only way to win. Anything later goes BELOW.
   ============================================================ */
@keyframes crlAppear{ from{opacity:0} to{opacity:1} }
@keyframes crlVanish{ from{opacity:1} to{opacity:0} }
/* CONNECTING — the reel rolls, the strip fades up in place, and nothing else moves. */
#crLineBadge.cr-linebadge:not(.ok):not(.out),#foePlayLbl.cr-linebadge:not(.ok):not(.out){
  animation:
    crlFilmRoll 1.6s linear infinite,
    crlAppear   .22s ease-out both;
}
/* CONNECTED — same list, the fade already finished, so the roll keeps its index and its phase. */
#crLineBadge.cr-linebadge.ok:not(.out),#foePlayLbl.cr-linebadge.ok:not(.out){
  animation:
    crlFilmRoll 1.6s linear infinite,
    crlAppear   0s linear both;
}
#crLineBadge.cr-linebadge.ok.lit:not(.out),#foePlayLbl.cr-linebadge.ok.lit:not(.out){
  animation:
    crlFilmRoll 1.6s linear infinite,
    crlAppear   0s linear both;
}
/* LEAVING — a plain fade, in place. */
#crLineBadge.cr-linebadge.out,#foePlayLbl.cr-linebadge.out{
  animation:
    crlFilmRoll 1.6s linear infinite,
    crlAppear   0s linear both,
    crlVanish   .40s ease-out both;
}
/* ---- THE WORD ------------------------------------------------------------
   ⚠ CONNECTING DOES NOT BREATHE ANY MORE. `#crLineBadge.cr-linebadge b,/* ---- THE WORD ------------------------------------------------------------
   ⚠ CONNECTING DOES NOT BREATHE ANY MORE. `#foePlayLbl.cr-linebadge b{animation:crlPulse}` (the
   v0.473 block) had the word fading in and out once a second; "the only thing that animates is the
   white dots" rules that out, and it is cancelled here rather than upstream because three other
   rules in this file write `b` at id+class+type and editing the earliest would leave them winning.
   ⚠⚠ THE GREEN IS A COLOUR ON `.ok`, NOT A COLOUR INSIDE THE KEYFRAME. The flash animates OPACITY
   only, so the word cannot be caught mid-animation on a colour it is not supposed to be, and when
   the three beats finish it settles on exactly the green it started at — the state is the colour,
   the flash is only the attention. */
#crLineBadge.cr-linebadge b,#foePlayLbl.cr-linebadge b{ animation:none; }
#crLineBadge.cr-linebadge.ok,#foePlayLbl.cr-linebadge.ok{ color:#6cf58c; }
#crLineBadge.cr-linebadge.ok b,#foePlayLbl.cr-linebadge.ok b{
  color:#6cf58c; text-shadow:0 0 calc(8px * var(--gs,1)) rgba(108,245,140,.6);
  animation:none;
}
/* THREE BEATS, AND THE `3` IS THE ONLY PLACE THAT NUMBER EXISTS (the v0.588 note still holds).
   ⚠ 3 x .34s = 1020ms, and crLineProject (js/online.js) adds `.lit` at 240ms and pulls the sign at
   1900 — so the word is settled and still for ~640ms before it fades. Change one and move the
   other; they are one decision. */
#crLineBadge.cr-linebadge.ok.lit b,#foePlayLbl.cr-linebadge.ok.lit b{ animation:crlWordFlash .34s steps(1,end) 3 both; }
@keyframes crlWordFlash{ 0%{opacity:1} 45%{opacity:.12} 100%{opacity:1} }

/* ============================================================
   ⭐⭐⭐ v0.643 — THE MIDDLE STRIP IS A CLOCK, AND THE TURN IS AN ARROW.
   (Odaroo, 2026-08-05: "remove the text box, we don't need it… no text, but next to the inklet's
   username, at the end of his username, when it's his turn, put a simple arrow pointing to his
   name, the colour of that user's declared colour… make it animate left and right, left and right,
   but not too much, just subtly. …If it's their turn, have it also in the middle of the screen. I'd
   like a timer and, in the background, a film reel, just like the film reel for the usernames. Same
   font size for the numbers, and on top a simple word that says TIME.")

   ⚠⚠ THE FILM IS INHERITED, NOT RE-DRAWN. Everything above still applies to `.crt-in`: the leader,
   the sprockets, the colour, the whole shared recipe. This block only changes what is PRINTED on
   the film — two stacked lines instead of a sentence — and the size of the figure.
   ⚠ THE HEIGHT IS STILL STABLE BY CONSTRUCTION, which is the rule crFit depends on: "TIME" over
   "0:00" is the same two lines for the whole match, and m:ss only ever grows in WIDTH.
   ============================================================ */
#crTurn .crt-in.crt-time{
  display:inline-flex; flex-direction:column; align-items:center; justify-content:center;
  gap:calc(2px * var(--gs,1)); line-height:1;
  padding:calc(5px * var(--gs,1)) calc(12px * var(--gs,1));
}
/* the word is a label, deliberately small and quiet — it names the figure, it is not the figure */
#crTurn .crt-in.crt-time .crtt-k{
  font-size:calc(6px * var(--gs,1)); letter-spacing:calc(2px * var(--gs,1));
  color:#c8b89a; display:block;
}
/* ⚠⚠ THE NUMBERS ARE --nmsz — THE USERNAME'S OWN SIZE, asked for by name. Do not re-type 13/17 in
   here: --nmsz (css/base.css) is the one place that number lives, and a hand-copied edition of it is
   exactly the drift that stranded the connecting sign for three builds. */
#crTurn .crt-in.crt-time .crtt-v{
  font-size:calc(var(--nmsz) * var(--gs,1)); letter-spacing:calc(1px * var(--gs,1));
  color:#fdf6e3; display:block; font-weight:normal;
}
/* the clock is neutral cream: it belongs to the match, not to either player */
#crTurn.crt-clock .crt-in{color:#fdf6e3;}
@media (min-width:640px){ #crTurn .crt-in.crt-time .crtt-k{font-size:calc(7px * var(--gs,1));} }

/* ⭐⭐ THE TURN ARROW. A pixel triangle — clip-path, never a border-triangle and never a glyph: a
   CSS border triangle anti-aliases its slope and a ▶ character is whatever the device's font
   feels like. Sized off the username it points at, so it grows with the reel at both folds.
   ⚠ THE COLOUR COMES FROM --ac, WRITTEN BY crTurnArrow — it is the colour the coin landed on for
   that player, which changes every match. Nothing here may hard-code red or green.
   ⚠⚠ IT IS A FLEX CHILD OF THE STRIP, NOT OF A NAME CELL. See the long note on crTurnArrow: inside
   a cell it costs the NAME its width and the pre-flight reported a clipped username immediately.
   Out here it spends the daylight between the two strips, which was empty anyway.
   ⚠ `align-self:center` — the bar is `align-items:stretch`, so without it the arrow is stretched to
   the full height of the strip and stops being a triangle. */
/* ⭐⭐⭐ v0.676 — IT IS A PLAIN RED TRIANGLE NOW. (Odaroo, 2026-08-06, on a Disc-Ink screenshot:
   "the little yellow arrow next to the username is nearly invisible — make it a bold RED arrow,
   the same one Panel-Ink uses for its turn indicator.")
   ⚠⚠ THIS REVERSES v0.652's GOLD, AND THE REASONING BEHIND THE GOLD WAS THE WRONG HALF OF THE
   PROBLEM. Gold was chosen so the arrow would not read as a third disc — but the reel band it sits
   on is cream-and-gold film, so a gold arrow on gold sprockets disappeared far harder than a red
   one ever did against the felt. The house style for "whose turn" everywhere else in the project is
   the simple three-corner red triangle (`.rkp-arrow`, css/rink.css): one flat red, one dark
   drop-shadow so it holds an edge over artwork, one red glow. Same shape here.
   ⚠ THREE CORNERS, NOT A TAILED ARROW. The old ten-point polygon drew a shaft-and-flights arrow,
   which at this size is mostly shaft — the thing that reads at a glance is a solid wedge.
   ⚠ `--ac` is still written by crTurnArrow (the colour the coin landed on for whoever is up) and
   is still used, but only as the outer HALO. The FILL is always red. Do not re-hang the fill off
   it: that is what made it invisible against a table full of red discs in the first place, and
   what the two disc coins under the portraits already say. */
.vsnm .vsnm-turn{
  display:block; align-self:center; flex:0 0 auto;
  width:calc(var(--nmsz) * 1.5 * var(--gs,1)); height:calc(var(--nmsz) * 1.5 * var(--gs,1));
  background:var(--red,#e0231a);
  filter:drop-shadow(calc(1px * var(--gs,1)) calc(1px * var(--gs,1)) 0 #1b1510)
         drop-shadow(calc(-1px * var(--gs,1)) calc(-1px * var(--gs,1)) 0 #1b1510)
         drop-shadow(0 0 calc(6px * var(--gs,1)) var(--ac,#e03a24));
}
/* MINE hugs MY strip and points back at it (my name is on the left) */
.vsnm .vsnm-turn.t-l{
  margin-right:auto; margin-left:calc(6px * var(--gs,1));
  clip-path:polygon(100% 0,100% 100%,0 50%);
  animation:vsnmArrowL 1.1s ease-in-out infinite;
}
/* THEIRS hugs THEIR strip and points the other way (their name is on the right) */
.vsnm .vsnm-turn.t-r{
  margin-left:auto; margin-right:calc(6px * var(--gs,1));
  clip-path:polygon(0 0,0 100%,100% 50%);
  animation:vsnmArrowR 1.1s ease-in-out infinite;
}
/* ⚠ SUBTLE, AND THAT IS THE SPEC. Odaroo asked for "left and right, left and right, but not too
   much". The travel is a fraction of the arrow's own width, so it nudges rather than slides; a
   bigger throw reads as an animation demanding attention instead of a cue answering a question. */
@keyframes vsnmArrowL{0%,100%{transform:translateX(0)} 50%{transform:translateX(calc(-3px * var(--gs,1)))}}
@keyframes vsnmArrowR{0%,100%{transform:translateX(0)} 50%{transform:translateX(calc(3px * var(--gs,1)))}}
/* ⭐⭐⭐ v0.699 — THREE BLINKS AS THE COIN LANDS. (Odaroo, 2026-08-06: "when the 3D coin flips and
   lands, make the red arrow on the chosen player's side blink 3 times — synchronised with the
   result showing, so you know whose turn it is.")
   ⚠⚠ THIS DELIBERATELY REPLACES THE NUDGE, IT DOES NOT RUN ALONGSIDE IT. `animation` is a shorthand
   and a second rule replaces it (the standing CLAUDE.md trap) — which here is exactly what is
   wanted: for the second the arrow is announcing itself it should not also be sliding. crCoinToss
   takes the class off when the three blinks are done and the subtle nudge resumes by itself.
   ⚠ Specificity is equal to `.vsnm .vsnm-turn.t-l` (three classes), so this rule MUST stay BELOW
   those two in the file or the nudge wins and nothing blinks.
   ⚠ It scales the arrow as well as flashing it: a pure opacity blink over a busy felt reads as a
   render glitch, while a wedge that pops is plainly a cue.
   ⚠ steps(1) on nothing — a hard on/off is the arcade cue; a fade is a flicker. */
.vsnm .vsnm-turn.vsnm-blink3{
  animation:vsnmBlink3 .34s steps(1,end) 3 both;
}
@keyframes vsnmBlink3{
  0%   {opacity:0; transform:scale(1);}
  50%  {opacity:1; transform:scale(1.45);}
  100% {opacity:1; transform:scale(1);}
}
@media (prefers-reduced-motion:reduce){ .vsnm .vsnm-turn{animation:none;} .vsnm .vsnm-turn.vsnm-blink3{animation:none; opacity:1;} }

/* ============================================================
   ⭐⭐⭐ v0.809 — THE RIVAL'S TURN WEARS THE CONNECTION SIGN. (Odaroo, 2026-08-13: "just put a little
   film reel in the middle of the screen — the same one as the 'connecting… connected' banner —
   saying 'Rival's turn, please wait…'. But then we get to see the actual gameplay underneath…
   something that has opacity so we're still able to see it.")

   ⚠⚠⚠ IT IS LITERALLY THE SAME SIGN, NOT A COPY OF IT. Every `#crLineBadge.cr-linebadge` rule
   above now carries `#foePlayLbl.cr-linebadge` beside it — 108 rules, mechanically twinned, and the
   twins strip back out to the original file byte-for-byte. That is deliberate and it is the whole
   point: CLAUDE.md's standing rule is that THERE ARE EXACTLY TWO SIZES OF FILM IN THIS GAME and no
   strip may declare a gauge of its own. Four of them once did, and the notification strip ended up
   printing an 8px cream hole beside the HUD bar's 6px white one. Writing "the same recipe" out a
   fifth time here would have been the fifth drift, on the sign the player stares at mid-match.
   ⚠ The id+class specificity is not decoration either: ten rules in this file write the sign at
   id+class precisely so a later `background:` shorthand cannot wipe the sprockets. A twin has to
   match that specificity or it loses to them and shows up as a plain box.
   ⚠ SO: to restyle the connection sign, edit the block above. It changes BOTH, which is correct.
   ============================================================ */
/* the only three things that are the rival sign's own: it is see-through, it stacks its two lines,
   and it sits above the showstopper window rather than beside it. */
#foePlayLbl.cr-linebadge{
  /* ⚠⚠⚠ `filter:opacity()`, NOT THE `opacity` PROPERTY, AND THAT IS NOT A STYLE CHOICE. The sign
     wears the banner's `crlIn` entrance, whose last keyframe is `opacity:1`, with `both` fill — and
     a running animation BEATS a plain declaration, so `opacity:.82` here was simply ignored and the
     sign came up solid. (Measured: 1.0.) The filter is not animated by anything, so it composes
     with the entrance instead of losing to it. Same family as the `animation` shorthand trap this
     project has hit three times: a property already being animated cannot be set from the side.
     ⚠ It fades the film, the sprockets, the word and the dots together, which is what keeps this
     ONE recipe rather than a translucent fork of the plate colour (that colour is written by half a
     dozen rules above and inverts on `.ok`). */
  filter:opacity(.78);
  flex-direction:column; gap:calc(2px * var(--gs,1)); text-align:center;
  max-width:min(86vw, calc(320px * var(--gs,1)));
}
/* ⚠ IT STAYS DEAD-CENTRE, exactly where the connecting banner sits — Odaroo asked for it "in the
   middle of the screen" WITH the gameplay showing through, so sitting over the board is the design
   and the transparency is what makes it work. It is `pointer-events:none` (inherited from the block
   above), so it can never eat a press meant for the battlefield behind it. */
#foePlayLbl.cr-linebadge .fpl-sub{
  display:block; font-size:.68em; letter-spacing:calc(1.5px * var(--gs,1)); opacity:.9;
  animation:none;   /* the headline pulses (the `b` rule above); the detail line is read, not blinked */
}
@media (prefers-reduced-motion:reduce){ #foePlayLbl.cr-linebadge{ animation:none; } }
