/* ============================================================================
   STAGE — THE ONE LAYOUT RULE (shared by app.html and preview.html so the game
   and the lab can never diverge). See app/shell/stage.js for the numbers.

   Every screen is a 390×780 ARTBOARD (ratio 2.0 = the engine's 1080×2160):
     • anchored at the TOP, just below the safe-area inset (--sat), centred horizontally
     • scaled as ONE unit by --sc = min(vw/390, (vh − sat − sab)/780)  (contain — never cropped)
     • the leftover space is a band at the BOTTOM (phones taller than 2:1, e.g. S24 with the cutout
       inset) or thin strips at the SIDES (iPhone: with the opaque status bar the usable area is
       wider than 2:1). The cover background (#appbg) fills them; engine screens paint them dark.
   So the layout is IDENTICAL on every phone — same shape, same place, only the size differs.
   --sat/--sab come from env() in the real app; the lab injects a device's real values instead.
   ONE EXCEPTION (owner 2026-10-03): the DESKTOP LOBBY (html.desk at mount → .screen-lobby.lb-desk, app/screens/lobby_desk.js) drops the
   artboard and lays out on a 1080-tall canvas scaled to the window (shrunk further if its content is taller) — every other screen,
   and every phone, keeps the rule.
   ============================================================================ */
/* LUCKIEST GUY, served by the game itself (owner 2026-10-03, Windows: the card amounts sat too low, cut by the banner). The font has two
   sets of line metrics — hhea 1440/608 of 2048 (Mac, iOS, Android) and OS/2 win 2006/504 (Windows) — so Windows made every line taller
   and set the letters 0.28 em lower. The overrides pin the Mac metrics for every system. Files = Google Fonts v25 (latin, latin-ext),
   Apache 2.0 (assets/fonts/luckiest-guy-LICENSE.txt). Test: tools/_chk_font_metrics.mjs. */
@font-face{font-family:"Luckiest Guy";font-style:normal;font-weight:400;font-display:swap;src:url(../assets/fonts/luckiest-guy-latin-ext.woff2) format("woff2");
  ascent-override:70.3125%;descent-override:29.6875%;line-gap-override:0%;
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Luckiest Guy";font-style:normal;font-weight:400;font-display:swap;src:url(../assets/fonts/luckiest-guy-latin.woff2) format("woff2");
  ascent-override:70.3125%;descent-override:29.6875%;line-gap-override:0%;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
:root{--sat:env(safe-area-inset-top,0px);--sab:env(safe-area-inset-bottom,0px);--aw:390px;--ah:780px;--es:0.3611111;--sc:1}
.screen{position:absolute;top:var(--sat,0px);left:var(--sl,0px);width:var(--aw);height:var(--ah);
  transform:scale(var(--sc));transform-origin:top left}   /* --sl + scale from the top-left corner: both artboard edges on whole device pixels (stage.js SNAP) */
/* EXPAND mode (Godot "expand" / Unity edge anchors) for a splash that must fill the phone side to side even
   with browser bars (intro, seen BEFORE fullscreen): same --sc, same vertical layout, but the width becomes
   the viewport's width in artboard px (--awx ≥ 390). left:/right: anchored elements sit flush to the real
   edges; centred ones stay centred. Opt in with `.screen-x{width:var(--awx)}` (intro.js). */
/* ENGINE frames (1080×2160) nest INSIDE the artboard at the fixed ratio 390/1080 — same 2.0 ratio, exact fit.
   Screens that borrow the engine #stage (tower / lose / win) plus the tower's own 1080-space HUD frame. */
.screen .tw-stage,.screen>#stage{transform:scale(var(--es));transform-origin:0 0}
/* The engine screens paint their own field art inside the artboard; around it (the leftover band) they get a
   solid dark ground — exactly the old look — instead of a second, cover-scaled copy of the art (seam). The
   engine's legacy full-bleed layer (#bgfs, from /old) is never used in the app. */
#egnhost #bgfs{display:none}
#view:has(.screen-tower,.screen-lose){background:#0c1720}
/* BLEED: the tower field continues into the bands (game.js bleed(), art from tools/extend_bg.py) — same engine space as
   #stage, behind it. Pop-ups dim it like their own scrim; the win overlays (own scene) get the plain dark band. */
/* it sits OUTSIDE the (clipped) tower screen, as its sibling in #view, with the screen's own box + scale (--sat/--sc) */
#view>#bgxw{position:absolute;top:var(--sat,0px);left:var(--sl,0px);width:var(--aw);height:var(--ah);transform:scale(var(--sc));transform-origin:top left;   /* same snapped box as .screen (stage.js --sl) — the bleed must sit on the exact same device pixels as the screen */
  z-index:0;pointer-events:none;transition:filter .3s,opacity .3s}
#bgxw>.bx{position:absolute;left:0;top:0;width:1080px;height:2160px;transform:scale(var(--es));transform-origin:0 0}
#bgxw>.bx>.bi{position:absolute;left:0;top:0;width:1080px;height:2160px}
/* the engine field may spill left / right / up into the bands (the lose layout slides the tower past the artboard edge);
   the bottom stays clipped (stones rise from below) */
.screen-tower>#stage{overflow:visible;clip-path:inset(-600px -600px 0 -600px)}
.screen-lose>#stage{overflow:visible;clip-path:inset(-600px -600px 0 -600px)}   /* the lose board's tower slides past the left artboard edge on side-band phones — same rule as the tower (audit 2026-09-29) */
#view:has(#stage.mx)>#bgxw>.bx>.bi{filter:brightness(.72) saturate(.85)}   /* = loseBgFromField(): only the field is dimmed .72 / .85 (not the shade on top) */
/* + the lose shade and tint, continued into the bands: #loseShade's vertical gradient mapped to engine px 0…2160 of the
   artboard, its end colours held above and below, the #stage.mx #tint purple (.26) on top — no step at the artboard edge */
#bgxw>.bx>.lshade,#bgxw>.bx>.ltint,#bgxw>.bx>.bflash,#bgxw>.bx>.bdim{position:absolute;left:-3000px;width:7080px;top:-1600px;height:5360px;pointer-events:none}
/* RULE (owner 2026-09-28): when a pop-up / screen shows, NOTHING of what is under it may peek out anywhere — the HUD and the
   engine field may spill into the side/top bands during play, so every cover must reach the bands too:
   · full-screen overlays (TOP REACHED / YOU WON): the tower screen is clipped to the artboard, bands = plain dark;
   · white flash, TIME'S UP scrim, message box scrim: extended to the sides/top from INSIDE the screen (they then also cover
     the spilled HUD/tower); the BOTTOM band (below the clipped screen) gets the same cover from the bleed box (.bflash/.bdim
     start at the artboard bottom, so nothing is covered twice). */
.screen-tower>#stage>#flash{inset:-600px -600px 0 -600px}
.screen-tower .tw-stage>#modal{box-shadow:0 0 0 1600px rgba(40,28,52,.7)}   /* the message box lives in the HUD layer now (tower.js moves it there) — its scrim covers the HUD, the top bar and every band */
#bgxw>.bx>.bflash,#bgxw>.bx>.bdim{top:2160px;height:3200px}
#bgxw>.bx>.bdim{display:none}
#view:has(#modal[style*="block"])>#bgxw>.bx>.bdim{display:block;background:rgba(40,28,52,.7)}
/* (TIME'S UP's own spread shadow now reaches the bottom band too — the screen is no longer clipped at the bottom) */
/* the engine's #tint mirrored: same colour per state, same .6 s fade (PLAY AGAIN fades it on the artboard — the bands follow) */
#bgxw>.bx>.ltint{background:rgba(84,62,104,.88);opacity:0;transition:opacity .6s}
#view:has(#tint.on)>#bgxw>.bx>.ltint{opacity:1}
#view:has(#stage.mx)>#bgxw>.bx>.ltint{background:rgba(84,62,104,.26)}
/* the engine's white #flash mirrored (game.js flash() animates both) */
#bgxw>.bx>.bflash{background:#fff;opacity:0}
#bgxw>.bx>.lshade{display:none;
  background:linear-gradient(180deg,rgba(12,8,24,.62) 0,rgba(12,8,24,.62) 1600px,rgba(12,8,24,.30) 2420.8px,rgba(12,8,24,.38) 2939.2px,rgba(12,8,24,.82) 3760px,rgba(12,8,24,.82) 5360px)}
#view:has(#stage.mx)>#bgxw>.bx>.lshade{display:block}
#view:has(.ww.on,.wn.on)>#bgxw{opacity:0}

/* ── DESKTOP (owner 2026-10-03, built step by step in labs/desktop_lab.html) ────────────────────────────────────────────────────────
   html.desk (shell/stage.js: wide side bands — a monitor, never a phone) + html.deskwide (shell/desktop.js: the tower's field has a
   wide picture — the bronze original bg_<time>, and the painted silver / gold sets): the game does not draw that field; #deskbg shows
   the WIDE picture of it instead (assets/desk/*_3840.webp, painted wider by Gemini — its middle IS the field), behind the whole monitor, scaled to the HEIGHT
   only and lined up with the artboard (owner: "slika se ne resiza, samo se manje vidi levo/desno"), its left and right edges easing to
   black over --desk-fade of its width (1150 of 3840 px = 29.95 %, the owner's pick); past the picture the monitor is black.
   The lose board dims it like loseBgFromField() (.72 / .85); tint, flash and pop-up scrims already reach the bands (#bgxw, above).
   Phones never change; a field without a wide picture keeps today's look. */
#deskbg{display:none}
html.deskwide #deskbg{display:block;position:fixed;z-index:0;pointer-events:none;top:var(--sat,0px);height:calc(780px * var(--sc,1));aspect-ratio:3840/2160;
  left:calc(var(--sl,0px) + 195px * var(--sc,1));transform:translateX(-50%);box-shadow:0 0 0 100vw #000}
html.deskwide #deskbg>img{position:absolute;inset:0;width:100%;height:100%;display:none;filter:var(--desk-f,brightness(1))}   /* --desk-f = the field's own colour filter (desktop.js) */
html.deskwide #deskbg>img.on{display:block}
html.deskwide #deskbg>.fade{position:absolute;top:0;bottom:0;left:-2px;right:-2px;   /* 2 px past the picture: its edge never shows as a hairline */
  background:linear-gradient(to right,rgba(0,0,0,1) 0,rgba(0,0,0,.82) calc(var(--desk-fade,29.95%) * .25),rgba(0,0,0,.5) calc(var(--desk-fade,29.95%) * .5),
    rgba(0,0,0,.18) calc(var(--desk-fade,29.95%) * .75),rgba(0,0,0,0) var(--desk-fade,29.95%),rgba(0,0,0,0) calc(100% - var(--desk-fade,29.95%)),
    rgba(0,0,0,.18) calc(100% - var(--desk-fade,29.95%) * .75),rgba(0,0,0,.5) calc(100% - var(--desk-fade,29.95%) * .5),
    rgba(0,0,0,.82) calc(100% - var(--desk-fade,29.95%) * .25),rgba(0,0,0,1) 100%)}
html.deskwide:has(#stage.mx) #deskbg>img{filter:var(--desk-f,brightness(1)) brightness(.72) saturate(.85)}
html.deskwide #view:has(.screen-tower,.screen-lose){background:transparent}
html.deskwide #bg,html.deskwide #loseBg,html.deskwide #bgx{visibility:hidden}
html.deskwide .tw-stage>.tw-topbar{left:0;right:0}   /* the top bar (balance · price · LOBBY) only over the game */
html.desk .tw-stage>.tw-topbar{background:none;box-shadow:none}   /* a monitor: no bar behind balance · price · LOBBY, the items stay (owner 2026-10-03: "obriši pozadinu gore… izgledaće čistije") */
/* lose board: ONE even dim over the whole picture (owner 2026-10-03: "NEMA pozadine iza ključeva… zatamni se cela pozadina"). With the
   field hidden the bleed's .lshade + .ltint (full width, behind the stage) also lie behind the game, so the stage's own #loseShade + #tint
   shaded it a second time (a darker panel) — here they paint nothing and the bleed alone shades game + bands, so there is no edge at all
   (a hole in the bleed left a 1-px light line where the two layers met). Nothing visible on the board sits under them (only the tower,
   z 25, and the board, z 30, above); #tint keeps its click guard. */
html.deskwide #stage>#loseShade{visibility:hidden}
html.deskwide #stage>#tint{background:transparent}
