/* ═══════════════════════════════════════════════════════════════════
   KARTI — extra.css
   Styling for the three modes added on top of the base game:
   story mode (js/story.js), pass-and-play + online (js/mp.js).
   Uses the same tokens as the inline stylesheet in index.html.
   ═══════════════════════════════════════════════════════════════════ */

/* ══════════════════ ICONS on the mode screens ══════════════════
   The base .ico rule lives in index.html; these only cover the places where
   the old emoji carried its own size and the icon has to match it. Every
   glyph is currentColor, so it takes the colour of whatever it sits in. */
.tag .ico{font-size:1.3em;vertical-align:-.22em}
.deckopt .e .ico,.deckcard .e .ico,.deckface .e .ico,.ub-sum .big-e .ico,
.boss .lockicon .ico,.handover .eyes .ico{width:1em;height:1em}
.deckface .a .ico,.deckopt .k .ico{font-size:1.3em;vertical-align:-.22em}
.storyhdr .prog .ico{font-size:1.05em;vertical-align:-.16em}
.bossline .said .ico,.blurb .ico,.okbox .ico,.warnbox .ico,.badbox .ico{font-size:1.15em;vertical-align:-.18em}
.taunt .who .ico{font-size:1.35em;vertical-align:-.26em}

/* ══════════════════ shared little bits ══════════════════ */
.blurb{font-size:12.5px;color:var(--dim);line-height:1.55;margin:0 0 10px}
.blurb b{color:var(--txt)}
.warnbox{border:1px solid rgba(255,197,66,.45);background:rgba(255,197,66,.09);border-radius:13px;
  padding:11px 13px;font-size:12.5px;line-height:1.55;color:#F3E4BE;margin:0 0 12px}
.warnbox b{color:var(--gold)}
.okbox{border:1px solid rgba(61,220,132,.4);background:rgba(61,220,132,.09);border-radius:13px;
  padding:11px 13px;font-size:12.5px;line-height:1.55;color:#CFEFD9;margin:0 0 12px}
.badbox{border:1px solid rgba(255,84,104,.45);background:rgba(255,84,104,.1);border-radius:13px;
  padding:11px 13px;font-size:12.5px;line-height:1.55;color:#F6D2D7;margin:0 0 12px}
.seg2{display:flex;background:var(--panel);border:1px solid var(--line);border-radius:13px;padding:4px;
  gap:4px;margin-bottom:10px;flex:0 0 auto}
.seg2 button{flex:1;min-height:42px;border-radius:10px;font-family:var(--disp);font-weight:900;
  font-size:11.5px;letter-spacing:.06em;color:var(--dim);transition:.15s var(--ease)}
.seg2 button.on{background:var(--gold);color:#241800}

/* ══════════════════ HOME: two ways in ══════════════════ */
.btn.pick{min-height:78px;font-size:17px}
.btn.pick .sub{font-size:10px;opacity:.8}
.navrow{display:flex;flex-wrap:wrap;gap:7px;justify-content:center;margin-top:14px}
.navrow .btn{flex:0 1 auto;min-height:40px;font-size:10.5px;letter-spacing:.04em;padding:4px 11px;
  border-radius:10px;flex-direction:row}
.navrow .btn .sub{display:none}

/* ══════════════════ HOME: who is online ══════════════════
   Filled in by js/mp.js. The ONLY part of KARTI that needs the internet, so it
   has to be obvious when it cannot reach the server — an empty list and a dead
   server must never look the same. Hence three visually distinct states:
   .on-live (green), .on-empty (grey) and .on-bad (red, with a real explanation). */
/* Deliberately does NOT grow to fill. Stretching it just produced a tall empty
   panel, and the leftover height is better spent showing the street art behind
   than on a box with two names in it. It scrolls internally once it is busy. */
/* 0 0 auto — must NOT shrink. As `0 1 auto` flexbox crushed it to 19px when the
   column ran out of room, so its rows spilled out and collided with whatever
   sat below. The column is meant to OVERFLOW and scroll, not compress. */
.onlinebox{flex:0 0 auto;max-height:38vh;border-radius:15px;
  border:1px solid color-mix(in srgb,var(--kx-ink,#F4EFFF) 14%,transparent);
  background:linear-gradient(180deg,color-mix(in srgb,var(--kx-surface,rgba(34,22,40,.66)) 88%,#fff 3%),
    color-mix(in srgb,var(--kx-bg,#0E0A1A) 82%,transparent));
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.07),0 10px 24px -14px rgba(0,0,0,.7);
  padding:9px 10px 8px;min-height:0;display:flex;flex-direction:column;gap:7px}
.onlinebox .on-list{flex:1 1 auto;min-height:0;overflow-y:auto;scrollbar-width:none}
.onlinebox .on-list::-webkit-scrollbar{display:none}
.onlinebox:empty{display:none}
.on-head{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.on-head h2{font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:#EFE7FF;
  flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.on-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto;background:var(--dim2)}
.on-live .on-dot{background:var(--ok);box-shadow:0 0 8px var(--ok)}
.on-bad .on-dot{background:var(--bad);box-shadow:0 0 8px var(--bad)}
.on-count{font-family:var(--disp);font-weight:900;font-size:11px;letter-spacing:.06em;
  color:var(--gold);flex:0 0 auto}
.on-ref{min-width:34px;min-height:34px;margin:-6px -4px -6px 0;display:grid;place-items:center;
  border-radius:9px;color:var(--dim);flex:0 0 auto}
.on-ref .ico{font-size:15px}
.on-ref:active{transform:scale(.9);color:var(--txt)}
.on-list{display:flex;flex-direction:column;gap:5px;min-height:0;
  max-height:clamp(76px,24vh,208px);overflow-y:auto;overflow-x:hidden;scrollbar-width:thin}
.on-list::-webkit-scrollbar{width:5px}
.on-list::-webkit-scrollbar-thumb{background:var(--line2);border-radius:5px}
.onrow{display:flex;align-items:center;gap:8px;width:100%;text-align:left;flex:0 0 auto;
  min-height:38px;padding:5px 8px;border-radius:11px;background:rgba(255,255,255,.045);
  border:1px solid transparent;font-size:12px;line-height:1.25}
.onrow .onmark{width:20px;height:20px;flex:0 0 auto;display:grid;place-items:center;
  border-radius:6px;color:var(--dim2);background:rgba(0,0,0,.3)}
.onrow .onmark .ico{font-size:13px}
.onrow.s-waiting .onmark{color:var(--gold);background:rgba(255,197,66,.16)}
.onrow.s-playing .onmark{color:var(--hot);background:rgba(232,69,44,.18)}
.onrow.s-idle .onmark{color:var(--ok);background:rgba(61,220,132,.14)}
.onrow .onwho{flex:1;min-width:0}
.onrow .onname{display:block;font-family:var(--disp);font-weight:900;font-size:11.5px;
  letter-spacing:.04em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#F4EFFF}
.onrow .onstate{display:block;font-size:9.5px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--dim);margin-top:1px}
.onrow .onme{color:var(--dim2);font-weight:700}
.onrow.join{min-height:44px;border-color:rgba(255,197,66,.45);background:rgba(255,197,66,.10);
  transition:transform .14s var(--ease),background .14s}
.onrow.join:active{transform:scale(.985);background:rgba(255,197,66,.18)}
.onrow .ongo{flex:0 0 auto;display:inline-flex;align-items:center;gap:4px;font-family:var(--disp);
  font-weight:900;font-size:9.5px;letter-spacing:.1em;color:#241800;background:var(--gold);
  border-radius:99px;padding:5px 9px}
.on-note{font-size:10.5px;line-height:1.5;color:var(--dim);margin:0}
.on-note b{color:#F0E4FF}
.on-bad .on-note{color:#F6D2D7}
.on-bad .on-note b{color:#FFB3BD}
.on-bad{border-color:rgba(255,84,104,.4);background:rgba(30,10,18,.84)}
.on-more{font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim2);
  text-align:center;margin:0}

/* ══════════════════ STORY MODE ══════════════════ */
.storyhdr{text-align:center;padding:2px 0 10px;flex:0 0 auto}
.storyhdr .prog{font-family:var(--disp);font-weight:900;font-size:12px;letter-spacing:.12em;color:var(--gold)}
.ladder{display:grid;gap:11px;padding:2px 0 26px}
.boss{
  position:relative;display:flex;align-items:center;gap:12px;text-align:left;width:100%;
  border-radius:17px;padding:12px;overflow:hidden;
  background:linear-gradient(120deg,color-mix(in srgb,var(--bc,#666) 30%,#160F26),#120C1E 72%);
  border:1.5px solid color-mix(in srgb,var(--bc,#666) 45%,transparent);
  transition:transform .16s var(--ease),border-color .16s;
}
.boss:active{transform:scale(.985)}
.boss.locked{filter:grayscale(.85) brightness(.6);pointer-events:none}
.boss.cleared{border-color:rgba(61,220,132,.55)}
.boss .face{width:54px;height:54px;flex:0 0 auto;border-radius:15px;display:grid;place-items:center;
  font-size:29px;background:rgba(0,0,0,.42);border:1px solid rgba(255,255,255,.14);
  position:relative;overflow:hidden}
.boss .face img,.bossline .face img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.boss .face img+.em,.bossline .face img+.em{display:none}
.boss .body{flex:1;min-width:0}
.boss .rank{font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--dim2);font-weight:800}
.boss .nm{font-family:var(--disp);font-weight:900;font-size:15px;line-height:1.15;margin:1px 0 3px}
.boss .li{font-size:11.5px;color:var(--dim);font-style:italic;line-height:1.4;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.boss .meta{display:flex;gap:5px;flex-wrap:wrap;margin-top:6px}
.tag{font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;font-weight:800;padding:3px 7px;
  border-radius:99px;background:rgba(0,0,0,.4);border:1px solid var(--line);color:var(--dim)}
.tag.diff{color:#FFD79A;border-color:rgba(255,197,66,.4)}
.tag.won{color:var(--ok);border-color:rgba(61,220,132,.5)}
.boss .lockicon{position:absolute;right:12px;top:12px;font-size:15px;opacity:.8}

/* ── THE ROAD ──────────────────────────────────────────────────────────
   Story Mode is a map you walk, not a list you scroll. The road and the
   stops are placed from ONE source of truth in js/story.js (nodePos), so
   the SVG and the portraits cannot drift apart. Everything here is
   absolutely positioned against .smap, whose height js/story.js sets. */
.smap{position:relative;width:100%;margin:0 auto 20px;max-width:460px}
.sroad{position:absolute;inset:0;width:100%;pointer-events:none;z-index:0;overflow:visible}
.sroad .rd-base{fill:none;stroke:rgba(0,0,0,.55);stroke-width:22;stroke-linecap:round;stroke-linejoin:round}
.sroad .rd-dash{fill:none;stroke:rgba(255,197,66,.52);stroke-width:3.4;stroke-linecap:round;
  stroke-linejoin:round;stroke-dasharray:2 13}
/* a stop on the road: portrait in a ring, number on the shoulder */
.snode{position:absolute;transform:translate(-50%,-50%);z-index:2;
  width:74px;height:74px;padding:0;border:0;background:none;cursor:pointer;
  display:grid;place-items:center;transition:transform .16s var(--ease)}
.snode:active{transform:translate(-50%,-50%) scale(.93)}
.snode .sn-ring{position:absolute;inset:0;border-radius:50%;
  background:linear-gradient(160deg,#F5D06A,#8A6508 62%,#E7C26A);
  box-shadow:0 6px 18px rgba(0,0,0,.6)}
.snode .sn-face{position:absolute;inset:4px;border-radius:50%;overflow:hidden;
  display:grid;place-items:center;font-size:27px;background:#120C1E;
  border:1px solid rgba(0,0,0,.5)}
.snode .sn-face img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:50% 18%}
.snode .sn-face img+.em{display:none}
.snode .sn-no{position:absolute;left:-4px;bottom:-2px;z-index:3;min-width:21px;height:21px;
  border-radius:99px;display:grid;place-items:center;font-size:11px;font-weight:900;
  background:#120C1E;color:var(--gold);border:1.5px solid var(--gold);padding:0 4px}
.snode .sn-tick,.snode .sn-lock{position:absolute;right:-3px;top:-3px;z-index:3;
  width:23px;height:23px;border-radius:50%;display:grid;place-items:center;
  background:#120C1E;border:1.5px solid var(--line2)}
.snode .sn-tick{border-color:var(--ok)}
.snode .sn-tick .ico{width:13px;height:13px;color:var(--ok)}
.snode .sn-lock .ico{width:12px;height:12px;color:var(--dim2)}
/* beaten reads calm, locked reads shut, and the one you are standing on
   is the only thing on the screen that moves */
.snode.done .sn-ring{background:linear-gradient(160deg,#7BE8A8,#1E7A46 62%,#9CF0C0)}
.snode.lock{cursor:default}
.snode.lock .sn-face{filter:grayscale(.9) brightness(.45)}
.snode.lock .sn-ring{background:linear-gradient(160deg,#4A4459,#241F31 62%,#4A4459)}
.snode.lock .sn-no{color:var(--dim2);border-color:var(--line2)}
.snode.here{z-index:4}
.snode.here .sn-ring{animation:snHere 2.2s ease-in-out infinite}
@keyframes snHere{
  0%,100%{box-shadow:0 6px 18px rgba(0,0,0,.6),0 0 0 0 rgba(255,197,66,.5)}
  50%{box-shadow:0 6px 18px rgba(0,0,0,.6),0 0 0 11px rgba(255,197,66,0)}
}
body.reduced .snode.here .sn-ring{animation:none;
  box-shadow:0 6px 18px rgba(0,0,0,.6),0 0 0 4px rgba(255,197,66,.45)}
/* you, on the road */
.swalker{position:absolute;z-index:5;transform:translate(-50%,-50%);pointer-events:none;
  width:20px;height:20px;display:grid;place-items:center}
.swalker .sw-dot{width:13px;height:13px;border-radius:50%;
  background:radial-gradient(circle at 36% 30%,#FFF3CF,#E0A800 62%,#A87400);
  box-shadow:0 0 0 3px rgba(18,12,30,.9),0 0 14px 3px rgba(255,197,66,.55)}
/* the two games a level is played over */
.gvs{display:grid;gap:6px}
.gvs-row{display:flex;align-items:center;gap:10px;padding:8px 11px;border-radius:12px;
  background:rgba(0,0,0,.34);border:1px solid var(--line);font-size:13.5px}
.gvs-row b{flex:0 0 20px;text-align:center;color:var(--gold);font-size:12px}
.gvs-row small{color:var(--dim2)}
.gvs-row.dec{border-style:dashed;opacity:.72}
@media (max-height:700px){
  .snode{width:64px;height:64px}
  .snode .sn-face{font-size:23px}
}

/* boss intro / taunt card */
.bossline{display:flex;gap:11px;align-items:flex-start;text-align:left;margin:2px 0 4px}
.bossline .face{width:52px;height:52px;flex:0 0 auto;border-radius:15px;display:grid;place-items:center;
  font-size:28px;background:rgba(0,0,0,.4);border:1px solid rgba(255,255,255,.16);
  position:relative;overflow:hidden}
.bossline .said{flex:1;font-size:13.5px;line-height:1.5;color:#E4DAFA;font-style:italic}
.bossline .said b{display:block;font-family:var(--disp);font-style:normal;font-size:12px;
  letter-spacing:.08em;color:var(--gold);margin-bottom:3px}
.taunt{position:fixed;left:12px;right:12px;bottom:calc(var(--sab) + 128px);z-index:420;
  background:linear-gradient(180deg,#2C2048,#1A1230);border:1px solid var(--line2);border-left:4px solid var(--hot);
  border-radius:13px;padding:10px 13px;font-size:12.5px;line-height:1.45;font-style:italic;color:#E9E0FF;
  box-shadow:0 12px 34px rgba(0,0,0,.6);opacity:0;pointer-events:none;max-width:520px;margin:0 auto}
.taunt.on{animation:tauntIn 3.4s var(--ease) forwards}
@keyframes tauntIn{0%{opacity:0;transform:translate3d(0,12px,0)}
  9%{opacity:1;transform:none}86%{opacity:1}100%{opacity:0;transform:translate3d(0,-8px,0)}}
.taunt .who{display:block;font-family:var(--disp);font-style:normal;font-weight:900;font-size:10px;
  letter-spacing:.12em;color:var(--hot);margin-bottom:3px}

/* ══════════════════ PASS AND PLAY ══════════════════ */
.seatgrid{display:grid;gap:12px;padding-bottom:20px}
.seatcard{border:1.5px solid var(--line);border-radius:17px;padding:12px;background:var(--panel)}
.seatcard.a{border-color:rgba(255,197,66,.45)}
.seatcard.b{border-color:rgba(138,92,255,.5)}
.seatcard h3{font-size:13px;letter-spacing:.1em;margin-bottom:8px;display:flex;align-items:center;gap:7px}
.seatcard h3 .dot{width:11px;height:11px;border-radius:50%;background:var(--gold);flex:0 0 auto}
.seatcard.b h3 .dot{background:var(--neon)}
.seatname{margin-bottom:9px}
.deckpick{display:flex;gap:8px;overflow-x:auto;scrollbar-width:none;padding:2px 0 6px}
.deckpick::-webkit-scrollbar{display:none}
.deckopt{flex:0 0 auto;width:98px;min-height:96px;border-radius:14px;padding:9px 6px;text-align:center;
  background:linear-gradient(170deg,color-mix(in srgb,var(--dc,#666) 32%,#160F26),#120C1E);
  border:2px solid transparent;transition:transform .15s var(--ease),border-color .15s}
.deckopt.on{border-color:var(--dc,var(--gold));box-shadow:0 0 18px -5px var(--dc,var(--gold))}
.deckopt:active{transform:scale(.95)}
.deckopt .e{font-size:23px;line-height:1}
.deckopt .n{font-family:var(--disp);font-weight:900;font-size:10.5px;margin-top:5px;line-height:1.15}
.deckopt .k{font-size:9px;color:var(--dim);margin-top:3px}

/* the pass-the-phone curtain */
.handover{position:fixed;inset:0;z-index:600;display:none;flex-direction:column;align-items:center;
  justify-content:center;gap:16px;text-align:center;padding:26px 20px;
  background:radial-gradient(900px 600px at 50% 20%,#2B1B49 0%,#0C0813 70%),var(--bg)}
.handover.on{display:flex;animation:scrIn .3s var(--ease) both}
.handover .eyes{font-size:52px;line-height:1;filter:drop-shadow(0 6px 14px rgba(0,0,0,.6))}
.handover h2{font-size:clamp(21px,7vw,30px);line-height:1.15}
.handover .to{font-family:var(--disp);font-weight:900;font-size:clamp(26px,9vw,40px);letter-spacing:.05em;
  background:linear-gradient(180deg,#FFF3CF,var(--gold) 48%,#C77A00);-webkit-background-clip:text;
  background-clip:text;color:transparent;filter:drop-shadow(0 3px 0 #6E3B00);word-break:break-word}
.handover p{margin:0;color:var(--dim);font-size:13.5px;line-height:1.6;max-width:420px}
.handover .btn{width:100%;max-width:340px}
.handover .lpline{display:flex;gap:8px;flex-wrap:wrap;justify-content:center}

/* ══════════════════ ONLINE ══════════════════ */
.roomcode{font-family:var(--disp);font-weight:900;font-size:clamp(40px,15vw,64px);letter-spacing:.22em;
  text-align:center;color:var(--gold);text-shadow:0 0 30px rgba(255,197,66,.35);margin:6px 0 2px;
  padding-left:.22em}
.netdot{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--dim2);margin-right:7px;
  vertical-align:middle}
.netdot.on{background:var(--ok);box-shadow:0 0 8px var(--ok)}
.netdot.bad{background:var(--bad);box-shadow:0 0 8px var(--bad)}
.netstat{font-size:12px;font-weight:700;color:var(--dim);text-align:center;margin:0 0 10px}
.codein{text-align:center;font-family:var(--disp);font-weight:900;font-size:26px;letter-spacing:.3em;
  text-transform:uppercase;padding-left:.3em}

/* ══════════════════ STARTER GACHA ══════════════════ */
.gacha-hdr{text-align:center;padding:8px 0 4px}
#scr-gacha .rlabel{top:2%}
/* Always one row of three. `min-width:150px` meant three cells needed 482px, so on
   a 390px phone the third wrapped underneath and its "Tap to flip" label landed on
   top of the hint text. nowrap + a shrinkable basis keeps the roll on one line. */
/* .tapme is absolute at bottom:-24px, so it adds no height and used to sit on top of
   the hint below. The 40px reserves room for it. */
.gacha-row{display:flex;flex-wrap:nowrap;gap:10px;justify-content:center;padding:22px 0 40px}
.gacha-cell{display:flex;flex-direction:column;align-items:center;gap:10px;
  width:min(190px,calc((100% - 20px)/3));min-width:0;flex:0 1 auto}
/* the gacha borrows .slot/.flipper/.aura from the pack opener; it only has to
   undo the absolute fan positioning that the pack stage relies on */
.slot.gslot{position:relative;transform:none;width:100%}
.slot.gslot .flipper{width:100%}
.deckface{position:absolute;inset:0;border-radius:9px;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:3px;text-align:center;padding:10px 8px;
  background:linear-gradient(165deg,color-mix(in srgb,var(--dc,#666) 46%,#160F26),#100B1C 78%);
  border:2px solid color-mix(in srgb,var(--dc,#666) 70%,transparent);
  box-shadow:inset 0 0 34px -8px var(--dc,#666)}
.deckface .packart{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:.5;
  border-radius:7px}
.deckface>*{position:relative;z-index:1}
.deckface .e{font-size:40px;line-height:1;filter:drop-shadow(0 3px 7px rgba(0,0,0,.65))}
.deckface .n{font-family:var(--disp);font-weight:900;font-size:15px;letter-spacing:.03em;line-height:1.1}
.deckface .a{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim)}
.deckface .vs{font-size:9.5px;line-height:1.45;color:var(--dim);margin-top:2px}
.deckface .vs b{color:var(--ok)}
.deckface .vs i{color:var(--bad);font-style:normal}
.gacha-info{width:100%;opacity:0;transform:translate3d(0,8px,0);transition:.32s var(--ease);
  display:flex;flex-direction:column;align-items:center;gap:8px}
.gacha-info.on{opacity:1;transform:none}
.gtag{font-size:11px;line-height:1.45;color:var(--dim);text-align:center;margin:0;font-style:italic}
.gcards{display:flex;gap:5px;justify-content:center;flex-wrap:wrap}
.gcards .card{--cw:52px}
.gkeep{width:100%}

/* ══════════════════ STARTER UNBOXING ══════════════════ */
.ub-stage{position:relative;min-height:270px;display:grid;place-items:center;padding:8px 0 4px}
#scr-gacha .ub-stage .rlabel{top:0}
.slot.ubslot{position:relative;transform:none}
.slot.ubslot .flipper{width:min(182px,49vw)}
.ub-tray{padding:0 0 20px}
.ub-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(74px,1fr));gap:14px 7px;padding:4px 0 10px}
.ub-sum{display:flex;flex-direction:column;align-items:center;gap:7px;text-align:center;width:100%;
  padding:6px 2px}
.ub-sum .big-e{font-size:56px;line-height:1;filter:drop-shadow(0 5px 12px rgba(0,0,0,.6));
  animation:pop .45s var(--ease) both}
.ub-sum h2{font-size:clamp(20px,7vw,28px)}
.ub-sum .stats{margin:0}

/* ══════════════════ ART HOOKS ══════════════════
   The playmat / zone / pile artwork is still on the GPU. Everything below
   stays CSS-and-emoji until the files land; then set the variables once,
   here, and the board picks them up. No file is referenced until it exists,
   so nothing 404s in the meantime.
     --art-playmat     e.g. url('../art/ui/playmat-green.jpg')
     --art-zone-m      e.g. url('../art/ui/zone-monster.png')
     --art-zone-s      e.g. url('../art/ui/zone-spell.png')
     --art-pile-deck / --art-pile-grave / --art-pile-banish
   Card faces are separate: set KARTI.ART.base = 'art/cards/' once and every
   cardEl() everywhere emits an <img> over the emoji (see .card .art img). */
.card .art img.artimg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  border-radius:inherit}
.card .art.hasart span{display:none}
:root{ --art-playmat:none; --art-zone-m:none; --art-zone-s:none; --art-cardback:none;
       /* Home must NEVER be flat black. Until (or unless) the real street art
          loads — first launch offline, probe still retrying, art pack absent —
          --art-home is a designed gradient in the brand palette, so a missing
          image reads as a calmer skin, not a broken screen. detectArt()
          overwrites this with the photo the moment the probe succeeds. */
       /* The photo is named DIRECTLY here, not switched on by JavaScript. It used
          to depend on detectArt() probing an Image(), which depended on the
          service worker having cached it, which a version bump could wipe — three
          things that can fail, for a background picture. A plain url() just paints
          when the file loads. The gradients sit underneath as the fallback, so a
          missing file still reads as a designed screen rather than flat black. */
       /* THE THEME HOOK RIDES ON TOP: --kx-home-bg (see the THEME TOKENS block
          in index.html) defaults to `none`, an empty background layer — the
          stock photo underneath paints exactly as before. When a cosmetic
          theme sets it to a url(...), that image covers the stock art while
          the #scr-home::before scrim stays above BOTH, so text keeps its
          contrast whatever picture a theme brings. */
       --art-home:var(--kx-home-bg,none),
                  url("../art/ui/home-bg.jpg"),
                  radial-gradient(140% 72% at 50% -14%, color-mix(in srgb,var(--kx-bg-2,#372259) 78%,#4A2E77) 0%, rgba(20,14,34,0) 62%),
                  radial-gradient(120% 58% at 50% 118%, color-mix(in srgb,var(--kx-bg-2,#2A1640) 72%,#000) 0%, rgba(20,14,34,0) 58%),
                  linear-gradient(180deg,color-mix(in srgb,var(--kx-bg,#171126) 82%,var(--kx-bg-2,#2A1B47)) 0%,var(--kx-bg,#0E0B14) 55%,color-mix(in srgb,var(--kx-bg,#130D20) 90%,var(--kx-bg-2,#2A1B47)) 100%);
       --art-pile-deck:none; --art-pile-grave:none; --art-pile-banish:none; }
/* background-origin:border-box — see the note on #scr-home below. Without it
   `cover` is measured against the PADDING box, so the safe-area padding shows up
   as bare colour at the edges of the playmat too. */
#scr-duel{background-image:var(--art-playmat);background-size:cover;background-position:center;
  background-origin:border-box}
/* the board art is busy — keep the cards and numbers on top of it readable */
#scr-duel::before{content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(6,4,12,.66),rgba(6,4,12,.28) 32%,rgba(6,4,12,.28) 68%,rgba(6,4,12,.72))}
#scr-duel>*{position:relative;z-index:1}
/* FULL-BLEED. The art and its scrim run edge to edge and up under the notch,
   while the CONTENT still sits inside the safe-area insets.
   `background-origin:border-box` is what makes that true and it is NOT optional.
   The default is padding-box, and `background-size:cover` is measured against
   the ORIGIN box — so with padding-bottom:calc(var(--sab) + 6px) the image was
   being scaled to cover only 844-53-40=751px and anchored to the top of the
   padding box, leaving a 40px band of flat --bg along the bottom of the phone
   under everything. `background-clip` was already border-box, which is why the
   band was the background COLOUR rather than transparent, and why this read as
   "the art is not filling my screen" rather than as a hole. */
/* NB: no `position` here on purpose. `.screen` is position:absolute;inset:0 and
   that is exactly what makes Home fill the viewport — an earlier `position:
   relative` on this rule quietly dropped it back into normal flow, so the
   screen was only ever as tall as its own content. */
#scr-home{background-image:var(--art-home);background-size:cover;background-position:center top;
  background-origin:border-box;
  background-repeat:no-repeat;background-color:var(--bg);overflow:hidden;
  /* The tab bar is position:fixed now, so it is out of flow and would sit on top
     of the content. Reserve its height here instead — --tabbar-h is the row plus
     its padding, and the safe-area inset is already inside it. */
  padding-top:calc(var(--sat) + 6px);
  /* In a browser tab the bar is position:fixed and out of flow, so its height
     has to be reserved here. In the installed app it is back in normal flow as
     the last flex child, and reserving it a second time pushed the bar 100px up
     the screen. */
  padding-bottom:calc(var(--tabbar-h, 62px) + var(--sab) + 6px)}
/* The home art is a bright, busy street scene and the duel screen was the only one
   with a scrim, so the logo and the buttons were unreadable on top of it.
   The bottom used to be crushed to .92 alpha because a row of ghost buttons sat
   there and needed the contrast. The tab bar brings its own opaque surface now,
   so the scrim can ease off and let the actual artwork show all the way down to
   the bar instead of fading into a dead black band. */
#scr-home::before{content:'';position:absolute;inset:0;pointer-events:none;z-index:0;
  background:
    /* the splash's warmth, carried in: a hearth glow bleeding up from the tab
       bar and a faint festa light at the crown — both read --kx-glow so a
       theme recolours the atmosphere in one variable */
    radial-gradient(110% 42% at 50% -8%, var(--kx-glow,rgba(255,170,60,.20)), transparent 60%),
    radial-gradient(96% 36% at 50% 112%, var(--kx-glow,rgba(255,170,60,.20)), transparent 64%),
    linear-gradient(180deg,rgba(8,5,16,.70),rgba(8,5,16,.50) 30%,
             rgba(8,5,16,.60) 64%,rgba(8,5,16,.74))}
/* :not(.tabbar) matters. This rule lifts the content above the scrim, but an ID
   selector outranks a class, so it was quietly overriding .tabbar's
   position:fixed back to relative — which is why the bar sat 68px up the screen
   instead of on the bottom edge. The bar carries its own z-index. */
#scr-home>*:not(.tabbar){position:relative;z-index:1}
/* The tab bar carries its own opaque surface (see .tabbar in index.html), so it
   is legible over the street scene without the scrim having to do the work.
   What it does need over the art is a hard top edge: the scrim now stops at .74
   so the cobbles still show, and against that the bar's own near-black would
   otherwise have no line to read as a bar. */
#scr-home .tabbar{border-top-color:rgba(255,255,255,.22)}
#scr-home .logo,#scr-home .tagline{text-shadow:0 2px 10px rgba(0,0,0,.75)}

/* ══════════════════ THE APP-WIDE BACKDROP ══════════════════
   An equipped scene used to stop at Home: --kx-home-bg is only read by
   #scr-home, so the Store, the Collection, Friends, Story and the lobbies
   stayed stock dark and the app changed skin every time a tab was tapped.
   --kx-app-bg is the same scene FOR EVERYWHERE ELSE: applyKx() (js/
   progress-ui.js) composes the scene's art with a readability veil — dark
   veil on the dark shell, pale rose veil under a light theme — into this one
   image value, and every MENU screen paints it behind its content.
   Defaults to `none`, so with nothing equipped these screens render exactly
   as before (transparent over the body gradient).
   The GAME surfaces are deliberately NOT in this list: #scr-duel keeps its
   playmat, #scr-party hosts live boards and #scr-kiri is a board itself — a
   photograph under a hand of cards costs readability mid-game, so games keep
   the neutral shell and the theme reaches them through the colour tokens
   only. */
#scr-pack,#scr-coll,#scr-deck,#scr-story,#scr-pnp,#scr-mp,#scr-gacha,
#scr-tutor,#scr-friends,#scr-stats,#scr-kx{
  background-image:var(--kx-app-bg,none);
  background-size:cover;background-position:center top;
  background-repeat:no-repeat;background-origin:border-box}
.card.back .in{background-image:var(--art-cardback);background-size:cover;background-position:center}
/* only drop the CSS-drawn back once the real card-back art is actually there */
.art-cardback .card.back .in::before{display:none}
.zrow:not(.st) .zone{background-image:var(--art-zone-m);background-size:cover;background-position:center}
.zrow.st .zone{background-image:var(--art-zone-s);background-size:cover;background-position:center}
/* dim grey type vanishes on the bright green field — give the running
   commentary and the player names something solid to sit on */
.ticker{color:#EDE6FF;text-shadow:0 1px 3px rgba(0,0,0,.85);
  background:rgba(10,6,20,.55);padding:4px 9px;border-radius:8px}
.pinfo .who{text-shadow:0 1px 3px rgba(0,0,0,.9)}
.zrow.st .zone>span{text-shadow:0 1px 3px rgba(0,0,0,.9)}
/* the empty-slot chrome has to stay legible on a bright green playmat */
.zone::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  box-shadow:inset 0 0 12px rgba(0,0,0,.55)}
.zone .card + .atkv,.zone .card{position:relative;z-index:1}

@media (min-width:760px){
  .ladder{grid-template-columns:1fr 1fr}
  .seatgrid{grid-template-columns:1fr 1fr}
  .gacha-cell{width:200px}
}

/* ══════════════════ PACK SET PICKER ══════════════════
   Four sets of 50 with identical rarity spreads, so this picks which jokes you
   want, never which pack is worth more. Scrolls rather than wraps: four chips
   wrapping to two rows pushed the pack itself off a short screen. */
.setpick{display:flex;gap:7px;overflow-x:auto;scrollbar-width:none;
  padding:6px 2px 8px;flex:0 0 auto}
.setpick::-webkit-scrollbar{display:none}
.setchip{flex:0 0 auto;max-width:46vw;min-height:44px;padding:6px 12px;border-radius:12px;
  background:rgba(255,255,255,.05);border:1.5px solid var(--line);
  display:flex;flex-direction:column;align-items:flex-start;gap:1px;
  transition:border-color .15s var(--ease),background .15s var(--ease)}
.setchip.on{border-color:var(--dc,var(--gold));background:rgba(255,255,255,.09);
  box-shadow:0 0 16px -6px var(--dc,var(--gold))}
.setchip:active{transform:scale(.97)}
.setchip .sc{font-family:var(--disp);font-weight:900;font-size:11px;letter-spacing:.12em;
  color:var(--dc,var(--gold))}
.setchip .sn{font-size:9.5px;color:var(--dim);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;max-width:100%}

/* The pack face now carries real set art, so the code and set name sit on top of
   a busy illustration. A scrim behind the text keeps them readable without
   hiding the art. */
.pack .fc.front .plogo,.pack .fc.front .psub{position:relative;z-index:2;
  text-shadow:0 2px 6px rgba(0,0,0,.85)}
.pack .fc.front::after{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,rgba(8,5,16,.62),rgba(8,5,16,.12) 42%,
             rgba(8,5,16,.18) 62%,rgba(8,5,16,.72))}
.pack .fc.front .pemo,.pack .fc.front .pcount,.pack .fc.front .foil{position:relative;z-index:2}

/* ══════════════════ THE BACK-BUTTON CONFIRM ══════════════════
   js/nav.js's own yes/no, asked when an Android back press — or the
   visible control the same guard protects — would throw away a game that
   is still going. It is nav's own rather than party.js's confirm() because
   it has to be askable on #scr-duel too, where there is no board context,
   and because the question should be worded and placed identically
   wherever it comes up.

   Positioned against #app, which is the 100dvh shell, so it is centred on
   what is actually visible rather than on a layout viewport Safari
   sometimes mis-sizes. z-index 1200 clears everything it could ever be
   asked over — scrim 150, sheet 200, modal 300, toast 500, the card
   viewer 600 — and stays under the scanline overlay at 9000, which is
   decorative and must remain on top of everything.
   The padding carries the safe-area insets so the card never lands under a
   notch in landscape, where the left/right insets are the real ones. */
.knav-ask{
  position:absolute;inset:0;z-index:1200;
  display:flex;align-items:center;justify-content:center;
  padding:calc(var(--sat) + 16px) calc(var(--sar) + 16px)
          calc(var(--sab) + 16px) calc(var(--sal) + 16px);
  background:rgba(6,4,12,.74);
  -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
  animation:knavIn .16s var(--ease) both;
}
@keyframes knavIn{from{opacity:0}to{opacity:1}}
.knav-ask .knav-card{
  width:100%;max-width:340px;max-height:100%;overflow-y:auto;
  padding:18px 16px 14px;border-radius:18px;
  background:var(--panel);border:1px solid var(--line2);
  box-shadow:0 24px 60px -18px rgba(0,0,0,.85);
  text-align:center;
}
.knav-ask h3{font-size:17px;letter-spacing:.04em;text-transform:uppercase;margin:0 0 8px}
.knav-ask p{margin:0;font-size:13.5px;line-height:1.5;color:var(--dim)}
/* Column, not a row: two targets side by side are ~150px each at 360px
   wide once the padding is off, which is where "Leave it" starts getting
   tapped by mistake. The destructive one sits ON TOP and the safe one at
   the bottom, because a thumb rests low on a phone — so the button under
   it, the one a fumbled tap lands on, is "No, carry on". */
.knav-ask .knav-acts{display:grid;gap:9px;margin-top:16px}
.knav-ask .knav-acts button{
  min-height:46px;padding:0 14px;border-radius:12px;
  font-family:var(--disp);font-weight:900;font-size:13.5px;letter-spacing:.06em;
  text-transform:uppercase;border:1px solid var(--line2);
  background:rgba(255,255,255,.06);color:var(--txt);
}
.knav-ask .knav-acts .knav-yes{background:var(--hot);border-color:var(--hot);color:#fff}
.knav-ask .knav-acts button:active{transform:scale(.97)}
@media (prefers-reduced-motion:reduce){
  .knav-ask{animation:none}
  .knav-ask .knav-acts button:active{transform:none}
}
/* Landscape: a 360px-tall window has no room for 18px of padding above a
   card with three stacked blocks in it, so the card tightens rather than
   scrolling the page — the shell never scrolls. */
@media (max-height:460px){
  .knav-ask .knav-card{padding:13px 14px 11px;max-width:420px}
  .knav-ask h3{font-size:15px;margin-bottom:5px}
  .knav-ask p{font-size:12.5px}
  .knav-ask .knav-acts{grid-template-columns:1fr 1fr;margin-top:11px}
  .knav-ask .knav-acts .knav-yes{order:2}
  .knav-ask .knav-acts button{min-height:42px;font-size:12.5px}
}

/* ══════════════════ LIGHT THEME (html.kx-light) — mode surfaces ══════════
   Counterpart of the html.kx-light block in index.html, for the surfaces this
   file owns. Only bites while a light theme (Roża Ħelwa) is equipped; stock
   is untouched. Two kinds of fix:
   1) HARD WHITES on what used to be a guaranteed-dark field (online names,
      the info boxes, the taunt, the handover curtain) → dark rose ink.
   2) DARK ISLANDS that keep their own dark art (boss tiles, deck cards, the
      gacha faces) now sit on a light page and INHERIT dark body ink → they
      get their light ink back explicitly. */
html.kx-light .on-head h2{color:var(--txt)}
html.kx-light .onrow .onname{color:var(--txt)}
html.kx-light .on-note b{color:var(--txt)}
html.kx-light .on-count{color:#C2255C}
html.kx-light .onrow{background:rgba(122,32,72,.07)}
html.kx-light .onrow .onmark{background:rgba(122,32,72,.10)}
html.kx-light .warnbox{color:#6E4E00;border-color:rgba(158,110,0,.45);
  background:rgba(255,197,66,.16)}
html.kx-light .warnbox b{color:#5C4100}
html.kx-light .okbox{color:#14532D}
html.kx-light .badbox{color:#8E1B2E}
html.kx-light .storyhdr .prog{color:#C2255C}
html.kx-light .roomcode{color:#C2255C;text-shadow:0 0 24px rgba(194,37,92,.25)}
/* the dark islands: tiles that carry their own near-black art */
html.kx-light .boss{color:#F6EAF0}
html.kx-light .boss .li{color:#C9AFC0}
html.kx-light .boss .rank{color:#B08CA0}
html.kx-light .deckcard,html.kx-light .deckopt,html.kx-light .deckface{color:#F6EAF0}
html.kx-light .deckface .a,html.kx-light .deckopt .k,
html.kx-light .deckface .vs{color:#C9AFC0}
html.kx-light .bossline .said{color:#5C1B3C}
html.kx-light .bossline .said b{color:#C2255C}
html.kx-light .taunt{background:linear-gradient(180deg,#FFF6FA,#FFE9F2);
  color:#5C1B3C;border-color:rgba(122,32,72,.28);border-left-color:var(--hot)}
html.kx-light .taunt .who{color:#A61E4D}
html.kx-light .handover{
  background:radial-gradient(900px 600px at 50% 20%,#FFD9E7 0%,#FBE3ED 70%),var(--bg)}
html.kx-light .handover .to{
  background:linear-gradient(180deg,#E0447F,#C2255C 48%,#8E1B44);
  -webkit-background-clip:text;background-clip:text;
  filter:drop-shadow(0 2px 0 rgba(255,255,255,.6))}
/* friends.js injects its own scoped stylesheet with hard #F4EFFF whites and
   two dark radial cards; under the light theme those become the same class
   of fix as above — ink follows the shell, the two cards go cream. */
html.kx-light #scr-friends .fr-elead,html.kx-light #scr-friends .fr-name,
html.kx-light #scr-friends .fr-notify,html.kx-light #scr-friends .fr-bubble.them,
html.kx-light #scr-friends .fr-chip,html.kx-light #scr-friends .fr-profname,
html.kx-light .fr-sheet-h,html.kx-light .fr-gametile{color:var(--txt)}
html.kx-light #scr-friends .fr-codecard,
html.kx-light #scr-friends .fr-profcard{
  background:radial-gradient(120% 130% at 100% 0%,rgba(214,51,108,.12),rgba(255,255,255,.62))}
html.kx-light #scr-friends .fr-code{color:#C2255C;text-shadow:none}
/* the tagline mixes the ACCENT into the dim ink — with a light accent that
   mix LIGHTENS it below AA (measured 3.2:1); under the light theme it takes
   the dim ink straight (7.5:1). */
html.kx-light #scr-home .tagline,html.kx-light #scr-auth .tagline{color:var(--kx-dim)}
