/* Ninefall — the look.

   A dark stage the hero stands on, with everything else floating over it: an uppercase eyebrow, a
   serif name, rarity told by a pill, and a glass panel for whatever the screen lets you choose. The
   direction is the Mythic Model Lab tester: vignette and grain over a lit ground, gold on deep blue,
   micro-labels in wide uppercase, one characterful serif against a plain interface sans.

   Three rules hold it together:
     · one shape - a rounded rectangle at one of four radii; nothing is a circle or a pill
     · one control height - every button is --control tall, whatever it says
     · one spacing and tracking scale - the space between letters and parts comes from the tokens
       below and nowhere else, which is what makes it read as made rather than assembled.

   Nothing scrolls: every screen is sized to the display (see fit() in the sprite module). */

/* Inter for every sans-serif text (owner, 10 Oct 2026, after a three-way sheet against Noto Sans and Nunito: "Inter and
   Playfair"): self-hosted, a Latin subset of the variable face at optical size 14 with its weight axis (font-src is 'self');
   the licence sits beside it */
@font-face{
  font-family:"Inter";
  src:url("/fonts/inter.woff2") format("woff2-variations");
  font-weight:400 900;font-style:normal;font-display:swap;
}
/* three weights only, everywhere (owner, 10 Oct 2026: "3 weights and body 12"): 400 regular, 600 semibold, 700 bold */
@font-face{
  font-family:"Playfair Display";
  src:url("/fonts/playfair-display.woff2") format("woff2-variations");
  font-weight:400 900;font-style:normal;font-display:swap;
}

:root{
  /* the five alphas every interface surface is drawn at (owner, 11 Oct 2026: "Standardize the transparency please"; graphic-2a's tiers -
     "don't make the card solid, the only solid one is the toast"): solid the toast alone; glass every surface (panels, cards, tiles, the
     dock, buttons, pills, chips, the result card, sheet cards, battle tags); scrim the dark behind a sheet or the result card; wash an
     interior tint (bar tracks, row fills, a resting toggle); wash-on a pressed, selected or lit tint. Each keeps its own hue - only the
     alpha is shared. Never blur. Art (the Grounds, the boot, the vignette, the Limit and crit light) and indicators (bar fills, pips,
     marks) are drawn as they are; scripts/tests/transparency.mjs holds every other background to these five */
  --a-solid:96%;--a-glass:35%;--a-scrim:70%;--a-wash:6%;--a-wash-on:12%;--a-popup:88%;   /* every POPUP: anything that opens over the screen and is read there (owner, 11 Oct 2026: "Raise the opacity of the pop-up bg" -> "centralize what is popup sheet too. The result popup is not a sheet?") */
  /* text on glass over a busy picture keeps 4.5:1 against the worst pixel behind it through a dark halo, never a darker tier
     (scripts/tests/transparency.mjs lists where it is worn) */
  --glass-shadow:0 0 2px #000,0 0 4px #000000b3;
  /* the toast's own ground: nearly solid, so a message reads over the busiest picture */
  --toast-a:color-mix(in srgb,#1b2338 var(--a-solid),transparent);--toast-b:color-mix(in srgb,#0d1220 var(--a-solid),transparent);--toast-edge:#ffffff2e;
  --popup-a:color-mix(in srgb,#151b2d var(--a-popup),transparent);--popup-b:color-mix(in srgb,#0a0e1a var(--a-popup),transparent);
  /* THE popup surface, one definition for all of them, tinted by each mood above: sheet cards (.sheet .card: help, Saved Bands,
     Accords, Lines, the Auto Band test, the first-fight prepare popup, the no-WebGL message), the result and briefing card
     (.verdict .card) and the hero palette editor. A new popup takes var(--popup); panels and cards that sit IN a screen stay glass. */
  --popup:linear-gradient(180deg,var(--popup-a),var(--popup-b));
  --bg:#070912;
  --ink:#f8f4ea;
  --muted:#9a9cab;
  --line:#ffffff1c;
  --line-soft:#ffffff0e;
  /* Glass, and it has to actually be glass. This was #111625df - 87% opaque - which is a painted
     panel with a blur behind it that nobody can see. The stage, its stars and the hero's own light
     are supposed to come through; the blur is what makes it readable, not the fill. */
  --panel-a:color-mix(in srgb,#151b2d var(--a-glass),transparent);
  --panel-b:color-mix(in srgb,#0a0e1a var(--a-glass),transparent);
  --panel:linear-gradient(180deg,var(--panel-a),var(--panel-b));
  --panel-line:#ffffff24;
  /* Frosted glass is back on the reading screens (owner, 10 Oct 2026: "Can we bring back frosted glass
     interface?"), see the rules at the end of the sheet. It stays OFF on the Spritefall and the Field:
     backdrop-filter re-samples what is behind it every frame, and during the Spritefall nine tiles do
     that at once over a live 3D stage - measured, it doubled the worst frame of the ceremony, 300ms to
     617ms (scratchpad/fall-perf.mjs). On those two screens the panels lean on opacity alone, as before. */
  --gold:#d9bd75;
  --gold-hi:#f0d58d;
  /* Loom is gold whatever the ground: the rarity marks read --loom, which never moves, while --gold is
     the interface's accent and takes the colour of the difficulty (see "the mood" below). */
  --loom:#d9bd75;
  --grey:#9aa0ae;
  --green:#7cc08d;
  --blue:#91b4ff;
  --violet:#9277ff;
  --rarity:var(--grey);

  --serif:"Playfair Display",Georgia,"Times New Roman",serif;
  --sans:"Inter",ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  /* type: three roles, and the tracking each one wants. One scale, every screen - the interface is
     not the thing you came to look at, so it stays out of the hero's way and the serif never becomes
     a billboard. A desk is not a reason to make the chrome bigger; it is a reason to leave more dark
     around it, which is what --column below does. */
  --t-display:clamp(23px,calc(var(--fvw,1vw) * 6.2),30px);
  --t-body:12px;   /* owner, 10 Oct 2026: "Overall text is too small" - the three sizes up a step */
  --t-label:10.5px;   /* buttons and chips read big in Inter's uppercase (owner, 10 Oct 2026: "Clear save fight and chips text a bit too big"): half a step down, body stays 13 */
  --t-micro:10px;
  --track-display:0;
  --track-label:0;
  --track-micro:0;
  --track-caption:0;
  --lead-display:.95;
  --lead-body:1.62;
  --lead-label:1.25;

  /* space: one scale, for every gap and every padding */
  --s1:6px; --s2:10px; --s3:14px; --s4:18px; --s5:24px;
  /* shape: rounded rectangles only */
  --r-panel:18px;
  --r-tile:14px;
  --r-control:12px;
  --r-pill:9px;
  --control:34px;        /* every button on a page, whatever it says */
  --dock-control:40px;   /* the dock is not a page control and keeps its own */
  /* The game is one column wide, on every screen. A desk gets more room around it, never a wider
     interface - so the dock and the page are the same width and nothing has to be sized twice. */
  --column:420px;

  --ease:cubic-bezier(.22,1,.36,1);
  --ease-inout:ease-in-out;
  --move:300ms;
  --mood-fade:900ms;     /* how long the interface takes to become the colour of the difficulty */
  --travel:44px;
  color-scheme:dark;
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;overflow:hidden;display:grid;place-items:center;
  background:var(--bg);color:var(--ink);
  font-family:var(--sans);font-size:var(--t-body);line-height:var(--lead-body);
  -webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;
  user-select:none;-webkit-user-select:none;
  touch-action:manipulation;overscroll-behavior:none;
  text-rendering:optimizeLegibility;
}
[hidden]{display:none!important}
button{font:inherit;color:inherit;cursor:pointer}
img{display:block;max-width:100%}

/* ---------- cold boot ----------
   The first WebGL figures have real construction/upload work to do after Android starts a fresh process.
   Cover that unavoidable cold-start work instead of showing Home with nine empty places. The app renders
   normally behind this layer; 90-boot-screen removes it only after the initial screen's live figures have
   completed their first frame. Nothing here participates in later navigation. */
.boot-screen{
  position:fixed;inset:0;z-index:1000;display:grid;place-items:center;overflow:hidden;
  background:
    radial-gradient(60% 22% at 50% 100%,#d9bd7524 0%,transparent 70%),
    radial-gradient(70% 46% at 50% 56%,#1b1f3a66 0%,transparent 72%),
    linear-gradient(180deg,#05070f 0%,#0b1024 46%,#070912 78%,#05060b 100%);
  opacity:1;transition:opacity 260ms var(--ease);pointer-events:auto;
}
.boot-screen::after{content:"";position:absolute;inset:-12%;pointer-events:none;background:radial-gradient(ellipse at center,transparent 34%,#00000066 80%,#000 100%)}
/* the night: nine stars (the Nine who fell), four-pointed like the logo's, each on its own slow twinkle */
.boot-sky{position:absolute;inset:0;pointer-events:none}
.boot-sky i{
  position:absolute;width:3px;height:3px;background:#f0d58d;border-radius:50%;
  box-shadow:0 0 6px 1px #d9bd7580;opacity:.55;animation:boot-twinkle 3.2s ease-in-out infinite;
}
.boot-sky i::before,.boot-sky i::after{content:"";position:absolute;background:linear-gradient(90deg,transparent,#f0d58dcc,transparent);left:50%;top:50%;transform:translate(-50%,-50%)}
.boot-sky i::before{width:14px;height:1px}
.boot-sky i::after{width:1px;height:14px}
.boot-sky i:nth-child(1){left:14%;top:12%;animation-delay:-.4s}
.boot-sky i:nth-child(2){left:78%;top:9%;animation-delay:-1.9s;transform:scale(.7)}
.boot-sky i:nth-child(3){left:88%;top:27%;animation-delay:-2.6s}
.boot-sky i:nth-child(4){left:7%;top:33%;animation-delay:-1.1s;transform:scale(.8)}
.boot-sky i:nth-child(5){left:24%;top:70%;animation-delay:-2.2s}
.boot-sky i:nth-child(6){left:70%;top:66%;animation-delay:-.8s;transform:scale(.75)}
.boot-sky i:nth-child(7){left:91%;top:52%;animation-delay:-3s;transform:scale(.6)}
.boot-sky i:nth-child(8){left:10%;top:86%;animation-delay:-1.5s;transform:scale(.7)}
.boot-sky i:nth-child(9){left:60%;top:88%;animation-delay:-.2s;transform:scale(.85)}
/* the halo sits where Android's splash draws the icon (screen centre), so the star seems to stay as the app takes over */
.boot-core{position:relative;z-index:1;display:grid;justify-items:center;gap:10px;transform:translateY(0)}
.boot-halo{margin-bottom:6px}
/* the halo: the logo's ring of light, a fine gold line with a breath of glow inside it */
.boot-halo{
  position:relative;width:240px;height:240px;border-radius:50%;border:1px solid #d9bd7566;
  box-shadow:0 0 36px #d9bd7514,inset 0 0 40px #d9bd7512;
}
.boot-halo::before{content:"";position:absolute;inset:14px;border-radius:50%;border:1px solid #d9bd7526}
/* the falling star: the logo's comet, crossing the ring from the upper left to its heart, over and over - the fall of the name */
.boot-comet{position:absolute;inset:0;overflow:visible}
.boot-fall{transform-origin:150px 150px;animation:boot-fall 3s cubic-bezier(.3,.1,.4,1) infinite}
.boot-name{
  font-family:var(--serif);font-size:24px;font-weight:700;line-height:1;letter-spacing:.01em;
  margin:6px -.22em 0 0;color:var(--gold-hi);
  background:linear-gradient(180deg,#fff2c8 0%,#f0d58d 42%,#d9bd75 60%,#a8883f 100%);
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 2px 14px #00000099) drop-shadow(0 0 18px #d9bd7533);
}
.boot-rule{width:72px;height:1px;background:linear-gradient(90deg,transparent,#d9bd7599,transparent)}
/* the loading screen (90, 89zzzc firstWarmBoot): while the band's effects are readied the rule widens into a line that fills */
.boot-screen[data-warming="true"] .boot-rule{position:relative;width:132px;background:#d9bd752e}
.boot-screen[data-warming="true"] .boot-rule::after{content:"";position:absolute;inset:0;transform-origin:left center;transform:scaleX(var(--boot-p,0));background:linear-gradient(90deg,#d9bd7599,#f0d58d);box-shadow:0 0 6px #d9bd7566;transition:transform .2s var(--ease)}
.boot-status{
  color:var(--gold);font-size:var(--t-micro);font-weight:700;letter-spacing:.01em;text-transform:uppercase;
  margin-right:-.3em;opacity:.72;
}
.boot-status::after{content:"...";display:inline-block;width:1.9em;text-align:left;animation:boot-dots 1.35s ease-in-out infinite}
.boot-screen[data-leaving="true"]{opacity:0;pointer-events:none}
@keyframes boot-fall{
  0%{transform:translate(-120px,-120px);opacity:0}
  12%{opacity:1}
  62%{transform:translate(0,0);opacity:1}
  82%,100%{transform:translate(0,0);opacity:0}
}
@keyframes boot-twinkle{0%,100%{opacity:.35;transform:scale(.9)}50%{opacity:1;transform:scale(1.05)}}
@keyframes boot-dots{0%,100%{opacity:.25}50%{opacity:1}}
/* the loading lines keep moving while the page is busy (owner, 13 Oct 2026: "100% avoided ui freeze"): their fill is set from script
   and stands still through a long task, so a sheen runs along each on the compositor, which a busy page never holds (measured: a
   2.5 s held main thread, the cover's animations kept at 60 fps) - the boot rule's, and the first fight's "Preparing for the fight" */
.boot-screen[data-warming="true"] .boot-rule::before{content:"";position:absolute;top:-1px;bottom:-1px;left:0;width:34px;z-index:1;pointer-events:none;
  background:linear-gradient(90deg,transparent,#fff6d8b3,transparent);opacity:0;animation:boot-sheen 1.8s cubic-bezier(.45,0,.25,1) infinite}
@keyframes boot-sheen{0%{transform:translateX(-17px);opacity:0}15%,85%{opacity:1}100%{transform:translateX(115px);opacity:0}}
.first-warm-sheet .autorefine-track{position:relative}
.first-warm-sheet .autorefine-track::after{content:"";position:absolute;top:0;bottom:0;left:0;width:28%;pointer-events:none;
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--gold-hi) 55%,transparent),transparent);transform:translateX(-100%);animation:warm-sheen 1.6s cubic-bezier(.45,0,.25,1) infinite}
@keyframes warm-sheen{0%{transform:translateX(-100%)}100%{transform:translateX(360%)}}
@media (prefers-reduced-motion:reduce){
  .boot-fall,.boot-sky i,.boot-status::after,.boot-screen[data-warming="true"] .boot-rule::before,.first-warm-sheet .autorefine-track::after{animation:none}
  .boot-status::after{content:"...";opacity:.78}
  .boot-screen{transition-duration:120ms}
}

/* ---------- the stage: the world behind everything ---------- */
.stage{position:fixed;inset:0;z-index:0;overflow:hidden;background:var(--bg)}
.sky{position:absolute;inset:0;width:100%;height:100%;display:block;opacity:0;transition:opacity .5s var(--ease)}
.stage[data-ready="true"] .sky{opacity:1}
/* an effect's light on the ground (89zzzh): the floor's last layer, blended with it as light, shown only while a pool is lit */
/* Brickfall (64-atmosphere): the block world is laid into the sky canvas at half resolution and shown pixel-sharp */
.stage .sky{image-rendering:pixelated;transform:translate3d(var(--tilt-x,0%),var(--tilt-y,0%),0) scale(1.05);transition:opacity .5s var(--ease),transform .9s cubic-bezier(.2,.7,.2,1)}   /* tilt: the compositor slides the painted world */
/* Authored Ground backgrounds: one fixed WebP per scene. The painting itself never moves.
   Lobby keeps a narrow compositor-only haze plus petals; battle uses particles only. */
.ground-scene-world{position:absolute;inset:0;pointer-events:none;opacity:0;background-size:cover;background-position:center center;background-repeat:no-repeat;transition:opacity .34s var(--ease);overflow:hidden}
.stage[data-ground="drift"][data-scene-art-ready="true"] .ground-scene-world,
.stage[data-ground="sump"][data-scene-art-ready="true"] .ground-scene-world,
.stage[data-ground="cinder"][data-scene-art-ready="true"] .ground-scene-world,
.stage[data-ground="spine"][data-scene-art-ready="true"] .ground-scene-world,
.stage[data-ground="glare"][data-scene-art-ready="true"] .ground-scene-world,
.stage[data-ground="hollow"][data-scene-art-ready="true"] .ground-scene-world{opacity:1}
/* the Drift in 3D (89zzzzz, a trial): its own canvas in the painting's place, faded in over it; the painting goes once it is covered */
.ground-3d{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none;opacity:0;transition:opacity .34s var(--ease)}
.stage[data-ground3d="on"] .ground-3d{opacity:1}
.stage[data-ground3d="on"] .ground-scene-world{visibility:hidden;transition:opacity .34s var(--ease),visibility 0s linear .34s}
.stage[data-ground="drift"][data-scene-art-ready="true"] .sky,
.stage[data-ground="sump"][data-scene-art-ready="true"] .sky,
.stage[data-ground="cinder"][data-scene-art-ready="true"] .sky,
.stage[data-ground="spine"][data-scene-art-ready="true"] .sky,
.stage[data-ground="glare"][data-scene-art-ready="true"] .sky,
.stage[data-ground="hollow"][data-scene-art-ready="true"] .sky{opacity:0}
.ground-scene-world[data-scene="lobby"]::before{content:"";position:absolute;left:-12%;top:27%;width:124%;height:28%;opacity:.20;
  background:linear-gradient(90deg,transparent 2%,rgba(188,199,219,.05) 22%,rgba(213,220,232,.11) 50%,rgba(176,188,211,.05) 78%,transparent 98%);animation:none;will-change:transform}
.stage[data-ground="drift"][data-scene-art-ready="true"] .ground-scene-world[data-scene="lobby"]::before,
.stage[data-ground="sump"][data-scene-art-ready="true"] .ground-scene-world[data-scene="lobby"]::before,
.stage[data-ground="cinder"][data-scene-art-ready="true"] .ground-scene-world[data-scene="lobby"]::before,
.stage[data-ground="spine"][data-scene-art-ready="true"] .ground-scene-world[data-scene="lobby"]::before,
.stage[data-ground="glare"][data-scene-art-ready="true"] .ground-scene-world[data-scene="lobby"]::before,
.stage[data-ground="hollow"][data-scene-art-ready="true"] .ground-scene-world[data-scene="lobby"]::before{animation:drift-scene-haze 26s ease-in-out infinite alternate}
.ground-scene-world::after{content:"";position:absolute;inset:0;pointer-events:none}
.ground-scene-world[data-scene="lobby"]::after{background:linear-gradient(180deg,rgba(2,4,10,.24),rgba(2,4,10,.06) 40%,rgba(2,4,10,.20))}
.ground-scene-world[data-scene="battle"]::after{background:linear-gradient(180deg,rgba(2,4,10,.10),transparent 28%,rgba(2,4,10,.14))}
/* a 3D Ground's still (89zzzzz) is its live scene drawn once: nothing laid over it that the scene does not have, so the scene takes over unseen */
.stage[data-ground="drift"] .ground-scene-world::before,.stage[data-ground="drift"] .ground-scene-world::after,
.stage[data-ground="cinder"] .ground-scene-world::before,.stage[data-ground="cinder"] .ground-scene-world::after,
.stage[data-ground="sump"] .ground-scene-world::before,.stage[data-ground="sump"] .ground-scene-world::after,
.stage[data-ground="spine"] .ground-scene-world::before,.stage[data-ground="spine"] .ground-scene-world::after,
.stage[data-ground="glare"] .ground-scene-world::before,.stage[data-ground="glare"] .ground-scene-world::after,
.stage[data-ground="hollow"] .ground-scene-world::before,.stage[data-ground="hollow"] .ground-scene-world::after{display:none}
.stage[data-ground="drift"] .sky-fx .mote.drift-mote,
.stage[data-ground="sump"] .sky-fx .mote.sump-mote,
.stage[data-ground="cinder"] .sky-fx .mote.cinder-mote,
.stage[data-ground="spine"] .sky-fx .mote.spine-mote,
.stage[data-ground="glare"] .sky-fx .mote.glare-mote,
.stage[data-ground="hollow"] .sky-fx .mote.hollow-mote{width:2px;height:2px;border-radius:50%;box-shadow:none}
.stage[data-ground="drift"][data-scene="battle"] .sky-fx .mote.drift-mote,
.stage[data-ground="sump"][data-scene="battle"] .sky-fx .mote.sump-mote,
.stage[data-ground="cinder"][data-scene="battle"] .sky-fx .mote.cinder-mote,
.stage[data-ground="spine"][data-scene="battle"] .sky-fx .mote.spine-mote,
.stage[data-ground="glare"][data-scene="battle"] .sky-fx .mote.glare-mote,
.stage[data-ground="hollow"][data-scene="battle"] .sky-fx .mote.hollow-mote{width:3px;height:3px}

@keyframes drift-scene-haze{from{transform:translate3d(-1.5%,0,0)}to{transform:translate3d(1.5%,2%,0)}}
/* ---------- Ground weather (64-atmosphere GROUND_WEATHER; owner, 5 Oct 2026: "make each ground distinct from each other") ----------
   Three depths per Ground - .wx-far, .wx-mid, .wx-near - each Ground with its own shapes, colours and way of moving. Compositor-only:
   every animation moves transform and opacity; soft and out-of-focus edges are painted by gradients, never by a filter or a blur.
   Keyed by [data-ground], so the same rules draw in the stage (behind) and in .sky-fx-front (in front of the fighters, battle only). */
#view > .sky-fx-front{position:fixed;left:0;top:0;width:100vw;height:100vh;z-index:1;pointer-events:none;overflow:hidden}
.sky-fx .wx{position:absolute;left:var(--x);top:var(--y,0);pointer-events:none;opacity:0;will-change:transform,opacity;animation-delay:var(--dl,0s);animation-iteration-count:infinite}

/* the Drift: wind. Everything travels ACROSS the screen, left to right, sinking a little as it goes */
[data-ground="drift"] .drift-far{width:var(--s);height:calc(var(--s) * .62);border-radius:62% 38% 60% 40%;background:rgba(206,232,250,.92);animation:drift-wind var(--d) linear infinite var(--dl)}
[data-ground="drift"] .drift-petal{width:var(--s);height:calc(var(--s) * 1.43);transform-origin:50% 72%;
  background:linear-gradient(90deg,rgba(143,202,237,.88) 0%,rgba(229,247,255,.98) 48%,rgba(161,215,245,.94) 100%);
  clip-path:polygon(50% 100%,33% 83%,17% 61%,7% 38%,10% 18%,24% 5%,41% 4%,50% 18%,59% 4%,76% 5%,90% 18%,93% 38%,83% 61%,67% 83%);
  animation:drift-sakura-wind var(--d) linear infinite var(--dl)}
[data-ground="drift"] .drift-petal::after{content:"";position:absolute;left:50%;bottom:11%;width:1px;height:65%;transform:translateX(-50%) rotate(-2deg);
  background:linear-gradient(180deg,rgba(249,253,255,.14),rgba(255,255,255,.90) 48%,rgba(105,173,216,.46));opacity:.82}
[data-ground="drift"] .drift-gust{width:var(--w);height:1.5px;background:linear-gradient(90deg,transparent,rgba(226,242,255,.75) 45%,rgba(226,242,255,.9) 55%,transparent);animation:drift-gust var(--d) ease-in-out infinite var(--dl)}
[data-ground="drift"] .drift-near{width:var(--s);height:calc(var(--s) * .58);border-radius:50%;
  background:radial-gradient(ellipse at 50% 50%,rgba(222,240,253,.85) 0%,rgba(176,214,242,.42) 36%,transparent 70%);animation:drift-wind var(--d) linear infinite var(--dl)}
@keyframes drift-wind{
  0%{opacity:0;transform:translate3d(0,0,0) rotate(0deg)}
  10%{opacity:var(--o)}
  30%{transform:translate3d(calc(var(--dx) * .3),calc(var(--dy) * .12 - 1.5vh),0) rotate(calc(var(--r1) * .3))}
  65%{transform:translate3d(calc(var(--dx) * .65),calc(var(--dy) * .62),0) rotate(calc(var(--r1) * .65))}
  90%{opacity:var(--o)}
  100%{opacity:0;transform:translate3d(var(--dx),var(--dy),0) rotate(var(--r1))}
}
@keyframes drift-sakura-wind{
  0%{opacity:0;transform:translate3d(0,0,0) rotate(var(--r0)) rotateX(18deg) rotateY(-24deg)}
  9%{opacity:var(--o)}
  32%{transform:translate3d(calc(var(--dx) * .32),calc(var(--dy) * .1 - 2vh),0) rotate(var(--rm)) rotateX(72deg) rotateY(34deg)}
  60%{transform:translate3d(calc(var(--dx) * .6),calc(var(--dy) * .55),0) rotate(var(--rm)) rotateX(-58deg) rotateY(76deg)}
  90%{opacity:var(--o)}
  100%{opacity:0;transform:translate3d(var(--dx),var(--dy),0) rotate(var(--r1)) rotateX(18deg) rotateY(20deg)}
}
@keyframes drift-gust{
  0%{opacity:0;transform:translate3d(-40vw,0,0) rotate(-3deg) scaleX(.6)}
  8%{opacity:var(--o)}
  26%{opacity:var(--o);transform:translate3d(70vw,-1.5vh,0) rotate(-3deg) scaleX(1.25)}
  32%,100%{opacity:0;transform:translate3d(110vw,-2vh,0) rotate(-3deg) scaleX(1.25)}
}

/* the Sump: rain, straight down and fast, and rings spreading on the floor where it lands */
[data-ground="sump"] .sump-far,[data-ground="sump"] .sump-mid,[data-ground="sump"] .sump-near{top:-14%;border-radius:2px;animation:sump-fall var(--d) linear infinite var(--dl)}
[data-ground="sump"] .sump-far{width:1.4px;height:var(--h);background:linear-gradient(180deg,transparent,rgba(168,206,206,.85))}
[data-ground="sump"] .sump-mid{width:2.2px;height:var(--h);background:linear-gradient(180deg,transparent,rgba(196,232,232,.95))}
[data-ground="sump"] .sump-near{width:7px;height:var(--h);border-radius:5px;background:linear-gradient(180deg,transparent,rgba(196,234,238,.42) 48%,rgba(196,234,238,.22) 70%,transparent)}
[data-ground="sump"] .sump-ripple{width:var(--w);height:calc(var(--w) * .32);border-radius:50%;border:1.5px solid rgba(196,236,236,.85);animation:sump-ripple var(--d) ease-out infinite var(--dl)}
[data-ground="sump"] .sump-ripple::after{content:"";position:absolute;inset:26%;border-radius:50%;border:1px solid rgba(190,232,232,.6)}
@keyframes sump-fall{0%{opacity:0;transform:translate3d(0,0,0) rotate(6deg)}8%{opacity:var(--o)}92%{opacity:var(--o)}100%{opacity:0;transform:translate3d(var(--dx),122vh,0) rotate(6deg)}}
@keyframes sump-ripple{0%{opacity:0;transform:scale(.08)}6%{opacity:var(--o)}55%{opacity:calc(var(--o) * .45)}100%{opacity:0;transform:scale(1)}}

/* Cinderfall: sparks and embers RISE and flicker; grey ash falls through them */
[data-ground="cinder"] .cinder-far,[data-ground="cinder"] .cinder-ember,[data-ground="cinder"] .cinder-near{top:104%;border-radius:50%}
[data-ground="cinder"] .cinder-far{width:var(--s);height:var(--s);background:#ffd98a;animation:cinder-spark var(--d) linear infinite var(--dl)}
[data-ground="cinder"] .cinder-ember{width:var(--s);height:var(--s);background:radial-gradient(circle,#fff4b8 0 16%,#ffa040 38%,rgba(232,72,30,.7) 58%,transparent 72%);animation:cinder-rise var(--d) linear infinite var(--dl)}
[data-ground="cinder"] .cinder-near{width:var(--s);height:var(--s);background:radial-gradient(circle,rgba(255,178,92,.78) 0,rgba(240,96,44,.36) 34%,transparent 68%);animation:cinder-rise var(--d) linear infinite var(--dl)}
[data-ground="cinder"] .cinder-ash{top:-6%;width:var(--s);height:calc(var(--s) * .8);background:rgba(156,146,140,.85);clip-path:polygon(10% 20%,60% 0,100% 35%,82% 90%,30% 100%,0 60%);animation:cinder-ash var(--d) linear infinite var(--dl)}
@keyframes cinder-spark{
  0%{opacity:0;transform:translate3d(0,0,0)}
  8%{opacity:var(--o)}
  20%{transform:translate3d(calc(var(--dx) * .4),-24vh,0)}
  30%{opacity:calc(var(--o) * .35)}
  35%{transform:translate3d(calc(var(--dx) * -.3),-41vh,0)}
  42%{opacity:var(--o)}
  50%{transform:translate3d(var(--dx),-59vh,0)}
  60%{opacity:calc(var(--o) * .3)}
  66%{opacity:var(--o);transform:translate3d(calc(var(--dx) * .1),-77vh,0)}
  100%{opacity:0;transform:translate3d(calc(var(--dx) * .6),-118vh,0)}
}
@keyframes cinder-rise{
  0%{opacity:0;transform:translate3d(0,0,0) scale(.7)}
  10%{opacity:var(--o)}
  35%{transform:translate3d(calc(var(--dx) * .5),-40vh,0) scale(1)}
  48%{opacity:calc(var(--o) * .55)}
  55%{opacity:var(--o)}
  70%{transform:translate3d(calc(var(--dx) * -.2),-80vh,0) scale(.9)}
  100%{opacity:0;transform:translate3d(var(--dx),-118vh,0) scale(.55)}
}
@keyframes cinder-ash{0%{opacity:0;transform:translate3d(0,0,0) rotate(0deg)}10%{opacity:var(--o)}50%{transform:translate3d(var(--dx),55vh,0) rotate(calc(var(--r1) * .5))}90%{opacity:var(--o)}100%{opacity:0;transform:translate3d(calc(var(--dx) * .4),112vh,0) rotate(var(--r1))}}

/* the Stone Spine: still air - moss spores hang and wander; dust settles; now and then a cluster of small stones breaks loose and tumbles down */
[data-ground="spine"] .spine-spore{width:var(--s);height:var(--s);border-radius:50%;background:radial-gradient(circle,#f6ffc4 0 14%,rgba(206,236,120,.85) 26%,rgba(150,196,84,.32) 46%,transparent 70%);animation:spine-hover var(--d) ease-in-out infinite alternate var(--dl)}
[data-ground="spine"] .spine-dust{width:var(--s);height:var(--s);border-radius:50%;background:radial-gradient(circle,rgba(214,200,168,.7) 0,rgba(190,176,140,.3) 40%,transparent 70%);animation:spine-settle var(--d) linear infinite var(--dl)}
[data-ground="spine"] .spine-pebble{top:-4%;width:var(--s);height:calc(var(--s) * .82);clip-path:polygon(14% 10%,58% 0,90% 18%,100% 62%,76% 100%,30% 96%,0 64%);
  background:linear-gradient(140deg,#e2dac4 0%,#b3aa94 28%,#7e7664 62%,#3f3a31 100%);animation:spine-pebble var(--d) cubic-bezier(.45,0,1,.7) infinite var(--dl)}
[data-ground="spine"] .spine-near{width:var(--s);height:calc(var(--s) * .5);border-radius:50%;background:radial-gradient(ellipse,rgba(176,186,138,.34) 0,rgba(150,160,116,.14) 42%,transparent 70%);animation:spine-cloud var(--d) linear infinite var(--dl)}
@keyframes spine-hover{0%{opacity:var(--o);transform:translate3d(0,0,0)}33%{transform:translate3d(var(--ax),var(--ay),0)}66%{transform:translate3d(var(--bx),var(--by),0)}100%{opacity:var(--o);transform:translate3d(var(--cx),var(--cy),0)}}
@keyframes spine-settle{0%{opacity:0;transform:translate3d(0,0,0)}15%{opacity:var(--o)}85%{opacity:var(--o)}100%{opacity:0;transform:translate3d(var(--dx),var(--dy),0)}}
@keyframes spine-pebble{0%,64%{opacity:0;transform:translate3d(0,0,0) rotate(0deg)}66%{opacity:var(--o)}98%{opacity:var(--o)}100%{opacity:0;transform:translate3d(var(--dx),112vh,0) rotate(var(--r1))}}
@keyframes spine-cloud{0%{opacity:0;transform:translate3d(0,0,0)}18%{opacity:var(--o)}82%{opacity:var(--o)}100%{opacity:0;transform:translate3d(var(--dx),-2vh,0)}}

/* the Glare: crystals that hover in place, eyes opening in the dark, sparks that crack */
[data-ground="glare"] .glare-eyes{width:var(--s);height:var(--s);border-radius:50%;background:#efe4ff;box-shadow:var(--g) 0 0 0 #efe4ff;animation:glare-blink var(--d) linear infinite var(--dl)}
[data-ground="glare"] .glare-crystal{width:var(--s);height:var(--h);transform-origin:center;
  background:linear-gradient(118deg,rgba(225,196,255,.92) 0 8%,transparent 9% 30%,rgba(151,79,201,.76) 31% 40%,transparent 41% 100%),linear-gradient(70deg,#21072f 0 28%,#3b0d55 29% 48%,#68258f 49% 67%,#321046 68% 100%);
  border:1px solid rgba(160,92,211,.42);clip-path:polygon(50% 0,88% 24%,78% 77%,50% 100%,16% 80%,4% 31%);animation:glare-hover var(--d) ease-in-out infinite alternate var(--dl)}
[data-ground="glare"] .glare-crystal::before{content:"";position:absolute;inset:10% 18% 12% 42%;background:linear-gradient(165deg,rgba(240,220,255,.76),rgba(111,47,151,.12) 58%,transparent 59%);clip-path:polygon(0 0,100% 18%,72% 100%,18% 78%)}
[data-ground="glare"] .glare-spark{width:7px;height:var(--h);background:linear-gradient(180deg,#ffffff,#e8dcff 60%,#b892ff);
  clip-path:polygon(55% 0,20% 46%,52% 46%,12% 100%,88% 38%,56% 38%,84% 0);transform:rotate(var(--r0));animation:glare-crack var(--d) steps(1,end) infinite var(--dl)}
[data-ground="glare"] .glare-near{width:var(--s);height:var(--s);border-radius:50%;background:radial-gradient(circle,rgba(232,212,255,.7) 0,rgba(160,104,220,.26) 34%,transparent 68%);animation:glare-hover var(--d) ease-in-out infinite alternate var(--dl)}
@keyframes glare-blink{
  0%,38%{opacity:0;transform:scaleY(.1)}
  42%{opacity:var(--o);transform:scaleY(1)}
  66%{opacity:var(--o);transform:scaleY(1)}
  68%{opacity:var(--o);transform:scaleY(.1)}
  70%{opacity:var(--o);transform:scaleY(1)}
  86%{opacity:var(--o);transform:scaleY(1)}
  90%,100%{opacity:0;transform:scaleY(.1)}
}
@keyframes glare-hover{0%{opacity:var(--o);transform:translate3d(0,0,0) rotate(var(--r0,0deg))}50%{transform:translate3d(var(--hx),var(--hy),0) rotate(var(--rm,0deg))}100%{opacity:var(--o);transform:translate3d(calc(var(--hx) * -.6),calc(var(--hy) * .4),0) rotate(var(--r1,0deg))}}
@keyframes glare-crack{0%{opacity:0}90%{opacity:var(--o)}92%{opacity:0}94%{opacity:var(--o)}97%{opacity:0}}

/* the Hollow bleeds (owner, 7 Oct 2026, with the new paintings of a cavern of flesh: "change ... environment effect to match the new bg"):
   warm dust lifts off the flesh, beads of blood swell as they climb, drips gather on the roof and fall, dark red blurs pass close */
[data-ground="hollow"] .hollow-mote,[data-ground="hollow"] .hollow-void,[data-ground="hollow"] .hollow-near{top:104%;border-radius:50%}
[data-ground="hollow"] .hollow-mote{width:var(--s);height:var(--s);background:radial-gradient(circle,#fff0d8 0 30%,rgba(232,196,150,.85) 52%,transparent 74%);animation:hollow-climb var(--d) linear infinite var(--dl)}
[data-ground="hollow"] .hollow-void{width:var(--s);height:var(--s);background:radial-gradient(circle at 34% 30%,rgba(255,214,196,.85) 0 8%,#901c20 17%,#5a080e 44%,#2e0306 60%,transparent 70%);animation:hollow-breathe var(--d) linear infinite var(--dl)}
[data-ground="hollow"] .hollow-drip{top:-5%;width:var(--s);height:var(--h);border-radius:50% 50% 50% 50%/34% 34% 66% 66%;transform-origin:50% 0;
  background:linear-gradient(90deg,#1e0204 0,#4e070c 34%,#8e2228 48%,#560a10 62%,#1e0204 100%);animation:hollow-drip-fall var(--d) linear infinite var(--dl)}
[data-ground="hollow"] .hollow-near{width:var(--s);height:var(--s);background:radial-gradient(circle,rgba(22,2,4,.76) 0,rgba(48,6,9,.52) 34%,rgba(96,20,22,.16) 54%,transparent 68%);animation:hollow-climb var(--d) linear infinite var(--dl)}
.stage[data-ground="hollow"][data-scene-art-ready="true"] .ground-scene-world[data-scene="lobby"]::before{background:linear-gradient(90deg,transparent 2%,rgba(150,48,36,.06) 22%,rgba(206,104,74,.12) 50%,rgba(150,48,36,.06) 78%,transparent 98%)}
@keyframes hollow-climb{
  0%{opacity:0;transform:translate3d(0,0,0)}
  10%{opacity:var(--o)}
  25%{transform:translate3d(var(--sw),-30vh,0)}
  50%{transform:translate3d(0,-60vh,0)}
  75%{transform:translate3d(calc(var(--sw) * -1),-90vh,0)}
  90%{opacity:var(--o)}
  100%{opacity:0;transform:translate3d(0,-120vh,0)}
}
@keyframes hollow-breathe{
  0%{opacity:0;transform:translate3d(0,0,0) scale(.8)}
  10%{opacity:var(--o)}
  25%{transform:translate3d(var(--sw),-30vh,0) scale(1.2)}
  50%{transform:translate3d(0,-60vh,0) scale(.85)}
  75%{transform:translate3d(calc(var(--sw) * -1),-90vh,0) scale(1.15)}
  90%{opacity:var(--o)}
  100%{opacity:0;transform:translate3d(0,-120vh,0) scale(.7)}
}
/* a drip gathers on the roof (stretching down from its root), lets go, and falls faster and faster */
@keyframes hollow-drip-fall{
  0%{opacity:0;transform:translate3d(0,0,0) scale(.6,.2)}
  8%{opacity:var(--o)}
  56%{opacity:var(--o);transform:translate3d(0,0,0) scale(1,.9);animation-timing-function:cubic-bezier(.5,0,1,.55)}
  79%{opacity:var(--o)}
  80%,100%{opacity:0;transform:translate3d(var(--dx),112vh,0) scale(.85,1.3)}
}

/* no reduced-motion rule here: the owner chose full motion for every player (24 Sep 2026, 60-state `reduced`) */

/* the air: a sign's or a rarity's tint, and a cast's flare, as a layer over the world rather than a redraw */
.stage .sky-tint{position:absolute;inset:0;pointer-events:none;opacity:.38;transition:opacity .45s var(--ease);
  background:radial-gradient(130% 75% at 50% 32%,color-mix(in srgb,var(--sky-tint,#d8b56a) 14%,transparent),transparent 72%)}   /* plain alpha: a blend mode here cost every composited frame */
.stage[data-flare] .sky-tint{opacity:.95}
.stage .sky-fx{position:absolute;inset:0;pointer-events:none}
.stage .sky-fx .mote{position:absolute;left:var(--x);top:var(--y);width:3px;height:3px;background:var(--mote,#fff);opacity:0;animation:sky-mote var(--d,9s) linear infinite;animation-delay:var(--dl,0s)}
.stage .sky-fx .twinkle{position:absolute;left:var(--x);top:var(--y);width:2px;height:2px;background:#fff;animation:sky-twinkle var(--d,3s) ease-in-out infinite;animation-delay:var(--dl,0s)}
@keyframes sky-mote{0%{opacity:0;transform:translate(0,0)}15%{opacity:var(--o,.6)}85%{opacity:var(--o,.6)}100%{opacity:0;transform:translate(var(--dx,10px),var(--dy,-120px))}}
@keyframes sky-twinkle{0%,100%{opacity:.15}50%{opacity:.9}}
.vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(circle at 50% 42%,transparent 38%,#03040a55 74%,#010207e6 112%)}
/* Hero detail is intentionally presented directly over the live world. The global stage vignette is
   useful for gameplay/readability elsewhere, but it becomes a visible black veil behind the Hero
   cards. Remove it only while the Hero screen owns the frame. */
body[data-screen="hero"] .vignette{display:none}
.grain{position:absolute;inset:0;pointer-events:none;opacity:.05;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E")}

/* ---------- the frame: the phone, whatever you are holding it on ----------
   The game is one screen and that screen has a shape. A desk used to get the same 456px column
   stretched down the whole height of a monitor: measured, the frame drifted from 1:1.97 on a laptop
   to 1:3.15 on a large display against the phone's own 1:2.22, the type stayed 11.5px at every size,
   and the height the column could not use came out as a hole above the dock.

   So the frame is locked to the reference phone's shape and scaled up as ONE piece. Every size, gap
   and line measured on the phone still holds - one set of numbers, magnified, never a second set
   worked out for a desk, which is the rule this sheet has always kept.

   `zoom` and not a transform, deliberately. A transform captures a position:fixed child and carries
   it off: on a probe at 1.5 the dock landed at y=1446 on an 800px display. zoom leaves a fixed child
   anchored and scales it. What it does NOT scale is getComputedStyle, offsetTop and clientHeight,
   which stay in the frame's own units while every rect comes back visual - so anything mixing the two
   (fit(), the Anchor's cap) brings them into one space through FRAME_K.

   The dock, the toast and the verdict are absolute against the frame rather than fixed against the
   window: they belong to the phone, not to the display behind it. The stage stays outside and fills
   the whole display, because the sky is the world the phone is being held up in front of.

   On a phone --k is 1 and the frame IS the viewport, so none of this changes what a phone does. */
#frame{position:relative;zoom:var(--k,1);width:min(100vw,var(--frame-w,100vw));height:var(--vh,100dvh)}

/* ---------- the screen ---------- */
/* The screen is the frame. A boss is meant to run past the edge of it - that is the whole effect - and
   clipping HERE rather than at the battle means it runs off the phone rather than being cut by a box
   inside it. It also keeps the document from growing, which is the first rule of this shell. */
#view{
  overflow:clip;
  position:relative;z-index:1;
  /* A fixed height, deliberately. Letting the column grow was tried and reverted: the Sky's memorial
     and Nightmare's record scroll INSIDE themselves, and they can only do that while the column that
     holds them is bounded - with a minimum instead they simply grew, the page scrolled, and a list
     2,500px long ran straight through the dock. A screen fits the screen; what does not fit scrolls
     within its own box. */
  height:var(--vh,100dvh);width:100%;max-width:var(--frame-w,calc(var(--column) + 2 * var(--s4)));margin:0 auto;
  /* 78px cleared the dock by one or two pixels, which is not clearance - it is a coincidence. The nine
     above the dock are drawn bigger than their row on purpose, so their spill had nowhere to go and
     landed on the dock the moment anything else on the screen grew. */
  padding:max(var(--s4),env(safe-area-inset-top)) max(var(--s4),env(safe-area-inset-left)) calc(env(safe-area-inset-bottom) + 92px);
  display:flex;flex-direction:column;gap:var(--s3);
}
/* Slack is the room left over once a screen is laid out, and it is given to the thing being looked at
   so a lone figure sits centred rather than hard against the head. A body with NOTHING in it is not a
   thing being looked at: on Home it is an empty div, and centring it split 154px into two dead holes -
   one under the title, one over the stack. Only centre a body that actually holds something. */
#view[data-slack] .grid,#view[data-slack] .frame,#view[data-slack] .body:has(*),#view[data-slack] .state{margin-block:auto}
/* A screen with no sprite on it never gets `data-slack` - fit() returns before setting it when there is
   nothing to fit - so on the hero's READING panes the leftover height fell below the control strip and
   left a hole: measured, 236px of nothing between the pane switcher and the dock, the switcher sitting
   at 498px on Stats against 708px on Paths. A row of controls that moves when you change tab is a row
   you have to look for.

   The slack is SHARED above and below the reading, the same way a screen with a sprite on it shares
   what the sprite could not use - so three short cards sit in air rather than hanging off the head with
   a hole beneath them, and the strip stands in the same place on every pane. Scoped to these panes on
   purpose: doing it to every sprite-less screen shrank the Paths grid 173px and pushed 81px of it out
   of its own box, because fit() measures the room from where the strip ends. */
/* And they stack like every other body that holds more than one thing - the campaign's does the same
   two lines. Without it the panes were plain blocks: measured, a 0px gap between the stats grid, the
   race/class/seed row and the Loom, so three cards read as one card with lines ruled across it. */
#view > .body.read{
  margin-block:auto;display:flex;flex-direction:column;gap:var(--s2);
  overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
  scrollbar-width:none;background:transparent
}
#view > .body.read::-webkit-scrollbar{display:none}
/* Hero reading cards use the same clean glass surface as Wear's state card. The generic .panel shadow
   reads as a black overlay against the landscape, so Hero Stats/Skill/Wear content explicitly drops it. */
#view > .body.read > .panel{
  border-color:var(--line);
  box-shadow:none
}
.hero-skills{gap:var(--s2)}
.hero-skills>.eyebrow{margin:0;color:var(--muted)}
.hero-skills .rows{display:grid;gap:var(--s2)}
.hero-skills .rows div{display:grid;gap:2px}
.hero-skills .rows b{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;text-transform:uppercase;color:var(--gold)}
.hero-skills .rows p{margin:0;font-size:var(--t-body);line-height:1.4;color:#c9cbd4}
/* and an empty one becomes room instead. Home has TWO such gaps - over the tally and over the nine -
   and they share the slack equally (`flex:1 1 0` from a zero basis, so neither is favoured by what it
   happens to contain). The sky reads as even air above and below the stack rather than one hole. */
#view > .body:not(:has(*)),#view > .room{flex:1 1 0;min-height:0}
/* Home: an empty lead slot should not consume half the spare height above the control stack. */
#view > .body.home-lead:not(:has(*)){display:none}
/* One view gives way to the next: the incoming screen arrives a short way off its place and eases in.
   Transforms only, so the glass over the stage never flattens mid-move. */
[data-view]{visibility:hidden;animation:var(--move) var(--ease-inout) both paused}
[data-view][data-run="true"]{visibility:visible;animation-play-state:running}
[data-view="push"]{animation-name:view-push}
[data-view="pop"]{animation-name:view-pop}
[data-view="cover"]{animation-name:view-cover}
[data-view="dismiss"]{animation-name:view-dismiss}
@keyframes view-push{from{transform:translateX(var(--travel))}to{transform:none}}
@keyframes view-pop{from{transform:translateX(calc(-1 * var(--travel)))}to{transform:none}}
@keyframes view-cover{from{transform:translateY(var(--travel)) scale(.982)}to{transform:none}}
@keyframes view-dismiss{from{transform:translateY(calc(-1 * var(--travel))) scale(.982)}to{transform:none}}

/* ---------- the head: what this is, whose it is, how rare ---------- */
/* An Anchor is drawn past the top of the field and would otherwise put an arm through the round
   counter. What the screen is and which round it is stay above everything. */
.head{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s2);flex:0 0 auto;position:relative;z-index:5}   /* the column is the screen's width: a wide row inside clips or gives way, never pushes the tools off (the synergy rail, 15 Oct; the fight's pills, 15 Oct) */
.headtop{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s3)}

.brand{min-width:0;flex:1 1 auto;text-align:left}
.tools{flex:0 0 auto;display:flex;gap:var(--s1)}
.eyebrow{margin:0;font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:#b9b2a2;line-height:var(--lead-label)}
.title{
  margin:var(--s1) 0 0;font-family:var(--serif);font-weight:600;
  font-size:var(--t-display);line-height:var(--lead-display);letter-spacing:.01em;
  text-wrap:balance;overflow-wrap:anywhere;
}
/* The build id says which code is running. It is a fact about the app, not a fact about the save, so it
   sits under the name as a quiet line rather than in the pill row beside how many heroes are seated. */
.hnote{display:block;margin:var(--s1) 0 0;font-size:var(--t-micro);letter-spacing:.01em;
  text-transform:uppercase;color:var(--muted);line-height:var(--lead-label);font-variant-numeric:tabular-nums}
.hnote+.hnote{margin-top:2px}
/* the pills are their own row under the head now, so the margin they used to need is the grid's gap.
   A control on that row sits hard right, away from the facts. */
.pills{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-start;gap:var(--s1)}
.pills>button.auto{margin-left:auto}
/* the page count of a grid sits at the far end of the pill row, where the pager row it replaces would
   have cost the cards a line */
.pill.count{margin-left:auto;font-variant-numeric:tabular-nums;color:var(--muted)}
.pills>button.move{margin-left:0}
/* Band controls read like the leader/accord pills: each one is only as wide as its own text,
   with a clean fixed gap between them. Do not stretch them into equal quarters. The row holds the CP and the two Auto
   presses (Move stands with Leader and Awakening below, owner 6 Oct 2026); it wraps rather than run past the screen. */
.pills:has(>button[data-act="arrange"]){
  display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-start;gap:4px
}
.pills:has(>button[data-act="arrange"])>.pill{
  width:auto;min-width:max-content;flex:0 0 auto;margin-left:0;padding-inline:8px;
  justify-content:center;overflow:visible;text-overflow:clip
}
.pills:has(>button[data-act="arrange"])>button.auto{padding-inline:7px}   /* CP · Auto band · Auto kit fit a 360 px frame with 4 px to spare */
.accords>button.pill.move{margin-left:0}
.pill{
  min-height:26px;padding:0 var(--s2);display:inline-flex;align-items:center;
  border:1px solid var(--line);border-radius:var(--r-pill);
  background:color-mix(in srgb,#090c14 var(--a-glass),transparent);
  font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:#d6d2c9;white-space:nowrap;line-height:1;
}
.pill[data-rarity]{border-color:color-mix(in srgb,var(--rarity) 42%,transparent);color:var(--rarity);box-shadow:inset 0 0 18px color-mix(in srgb,var(--rarity) 9%,transparent)}
.pill[data-rarity="0"]{--rarity:var(--grey)}
.pill[data-rarity="1"]{--rarity:var(--green)}
.pill[data-rarity="2"]{--rarity:var(--blue)}
.pill[data-rarity="3"]{--rarity:var(--violet)}
.pill[data-rarity="4"]{--rarity:var(--loom)}

/* ---------- controls: one height, one shape ---------- */
.icon,.util,.btn,.seg button{
  min-height:var(--control);
  border-radius:var(--r-control);
  font-size:var(--t-label);letter-spacing:.01em;text-transform:uppercase;line-height:1.2;
  transition:color .2s ease,border-color .2s ease,background-color .2s ease,transform .16s var(--ease);
}
.icon,.util{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s1);
  border:1px solid var(--line);background:color-mix(in srgb,#ffffff var(--a-wash),transparent);color:#c9cbd4;
}
.icon{width:var(--control);flex:0 0 auto}
.util{padding:0 var(--s3)}
.icon:active,.util:active,.btn:active,.seg button:active,.dock button:active{transform:scale(.96)}
.icon:hover,.util:hover{color:var(--ink)}
.icon svg,.util svg,.btn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.icon:focus-visible,.util:focus-visible,.seg button:focus-visible,.dock button:focus-visible,.btn:focus-visible,.tile:focus-visible,.frame:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.btn{
  padding:0 var(--s4);display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  border:1px solid color-mix(in srgb,var(--gold) 34%,transparent);
  background:linear-gradient(180deg,color-mix(in srgb,var(--gold) var(--a-wash-on),transparent),color-mix(in srgb,var(--gold) var(--a-wash),transparent)),var(--panel);color:var(--gold-hi);
}
/* a control is one line tall, so its label is one line - a wrapped label was overflowing the box */
.btn span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.btn[data-tone="quiet"]{border-color:var(--line);background:color-mix(in srgb,#090c14 var(--a-glass),transparent);color:#c9cbd4}

/* ---------- the mood: the interface takes the colour of the difficulty ----------
   Gold on deep blue is the ordinary ground. Hard is ember on smoked glass, Bloodbath is arterial red on
   near-black, and Hellgate is ash/old-iron over glass that is almost black. Nightmare is the consequence
   tint: it bends whichever selected ground toward rose without replacing that ground. Eight palettes now
   cover Normal/Hard/Bloodbath/Hellgate with and without Nightmare. The same tokens every surface already
   reads - accent, highlight, glass and hairlines - are redefined on the root by data-mood.

   The swap is a crossfade, not a cut: each token is registered as a colour so the root can transition
   it, and every consumer - a pill's border, a panel's gradient, a node's number - follows the one
   interpolation on the root, together, over --mood-fade. A browser without @property still swaps;
   it just does not fade. Somebody who asked for less motion gets the cut.

   The field wears the same palette as the ground it is fighting. This changes UI chrome only; fighter,
   skill and enemy render palettes remain owned by their battle renderers. */
@property --gold{syntax:"<color>";inherits:true;initial-value:#d9bd75}
@property --gold-hi{syntax:"<color>";inherits:true;initial-value:#f0d58d}
@property --panel-a{syntax:"<color>";inherits:true;initial-value:#151b2d4d}
@property --panel-b{syntax:"<color>";inherits:true;initial-value:#0a0e1a59}
@property --panel-line{syntax:"<color>";inherits:true;initial-value:#ffffff24}
@property --line{syntax:"<color>";inherits:true;initial-value:#ffffff1c}
:root{transition:--gold var(--mood-fade) var(--ease-inout),--gold-hi var(--mood-fade) var(--ease-inout),
  --panel-a var(--mood-fade) var(--ease-inout),--panel-b var(--mood-fade) var(--ease-inout),
  --panel-line var(--mood-fade) var(--ease-inout),--line var(--mood-fade) var(--ease-inout)}
:root[data-mood="hard"]{--gold:#e6a271;--gold-hi:#f5c08e;--panel-a:color-mix(in srgb,#2e1d1c var(--a-glass),transparent);--panel-b:color-mix(in srgb,#170c0e var(--a-glass),transparent);--panel-line:#ffd3bd26;--line:#ffc9b01c;--toast-a:color-mix(in srgb,#2a1c1a var(--a-solid),transparent);--toast-b:color-mix(in srgb,#150c0b var(--a-solid),transparent);--toast-edge:#ffd3bd3d;--popup-a:color-mix(in srgb,#241a19 var(--a-popup),transparent);--popup-b:color-mix(in srgb,#120a09 var(--a-popup),transparent)}
:root[data-mood="nightmare"]{--gold:#e07f8c;--gold-hi:#f3a0ab;--panel-a:color-mix(in srgb,#2a1a2e var(--a-glass),transparent);--panel-b:color-mix(in srgb,#120a16 var(--a-glass),transparent);--panel-line:#f0c0d026;--line:#e8b8c81c;--toast-a:color-mix(in srgb,#281a2b var(--a-solid),transparent);--toast-b:color-mix(in srgb,#130a15 var(--a-solid),transparent);--toast-edge:#f5c8d43d;--popup-a:color-mix(in srgb,#22182b var(--a-popup),transparent);--popup-b:color-mix(in srgb,#0f0814 var(--a-popup),transparent)}
:root[data-mood="both"]{--gold:#ef6f66;--gold-hi:#ff9a8c;--panel-a:color-mix(in srgb,#34161a var(--a-glass),transparent);--panel-b:color-mix(in srgb,#1a080c var(--a-glass),transparent);--panel-line:#ffc0b826;--line:#ffb0a81c;--toast-a:color-mix(in srgb,#31171a var(--a-solid),transparent);--toast-b:color-mix(in srgb,#19080b var(--a-solid),transparent);--toast-edge:#ffc0b83d;--popup-a:color-mix(in srgb,#2a1418 var(--a-popup),transparent);--popup-b:color-mix(in srgb,#150709 var(--a-popup),transparent)}
/* Bloodbath: the third ground, and the only one named for what it costs. Red where Hard is ember -
   a deeper, colder crimson on a ground that has gone nearly black, so the two never read alike at a
   glance. Under Nightmare it bleeds toward the rose the Sky wears. */
:root[data-mood="blood"]{--gold:#e5414f;--gold-hi:#ff7683;--panel-a:color-mix(in srgb,#3d0e13 var(--a-glass),transparent);--panel-b:color-mix(in srgb,#1e0508 var(--a-glass),transparent);--panel-line:#ffa8ae26;--line:#ff98a01c;--toast-a:color-mix(in srgb,#390f14 var(--a-solid),transparent);--toast-b:color-mix(in srgb,#1c0609 var(--a-solid),transparent);--toast-edge:#ffa8ae3d;--popup-a:color-mix(in srgb,#320d12 var(--a-popup),transparent);--popup-b:color-mix(in srgb,#190508 var(--a-popup),transparent)}
:root[data-mood="bloodmare"]{--gold:#f2495f;--gold-hi:#ff8296;--panel-a:color-mix(in srgb,#3a0f1f var(--a-glass),transparent);--panel-b:color-mix(in srgb,#1b0610 var(--a-glass),transparent);--panel-line:#ffb0c026;--line:#ffa0b41c;--toast-a:color-mix(in srgb,#360f1d var(--a-solid),transparent);--toast-b:color-mix(in srgb,#1a0610 var(--a-solid),transparent);--toast-edge:#ffb0c03d;--popup-a:color-mix(in srgb,#2f0d1a var(--a-popup),transparent);--popup-b:color-mix(in srgb,#17050e var(--a-popup),transparent)}
/* Hellgate: nearly lightless rather than another red step. The accent is ash/old iron; the red is only
   a trace in the edge. Panels, toast and sheets are substantially darker than Bloodbath. */
:root[data-mood="hell"]{--gold:#98847f;--gold-hi:#c0aaa4;--panel-a:color-mix(in srgb,#09090b var(--a-glass),transparent);--panel-b:color-mix(in srgb,#020203 var(--a-glass),transparent);--panel-line:#9b77752b;--line:#896c6a1d;--toast-a:color-mix(in srgb,#0b0a0c var(--a-solid),transparent);--toast-b:color-mix(in srgb,#020203 var(--a-solid),transparent);--toast-edge:#9b77753b;--popup-a:color-mix(in srgb,#0a090b var(--a-popup),transparent);--popup-b:color-mix(in srgb,#020203 var(--a-popup),transparent)}
:root[data-mood="hellmare"]{--gold:#ad727d;--gold-hi:#cf929e;--panel-a:color-mix(in srgb,#0d090d var(--a-glass),transparent);--panel-b:color-mix(in srgb,#030203 var(--a-glass),transparent);--panel-line:#b783902d;--line:#aa76831f;--toast-a:color-mix(in srgb,#100a0e var(--a-solid),transparent);--toast-b:color-mix(in srgb,#030203 var(--a-solid),transparent);--toast-edge:#bd899641;--popup-a:color-mix(in srgb,#0e080c var(--a-popup),transparent);--popup-b:color-mix(in srgb,#030203 var(--a-popup),transparent)}
:root[data-mood="hell"] .vignette{background:radial-gradient(circle at 50% 42%,transparent 25%,#02020488 67%,#000107ed 108%)}
:root[data-mood="hellmare"] .vignette{background:radial-gradient(circle at 50% 42%,transparent 24%,#0802088c 67%,#010106ef 108%)}

/* ---------- the glass panel, and the segmented rows it holds ---------- */
.panel{
  flex:0 0 auto;padding:var(--s2);
  border:1px solid var(--panel-line);border-radius:var(--r-panel);
  background:var(--panel);
  box-shadow:0 18px 70px #0007;
  display:grid;gap:var(--s1);
}
.seg{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:var(--s1)}
.seg[data-cols="3"]{grid-auto-flow:row;grid-template-columns:repeat(3,1fr)}
.seg[data-cols="4"]{grid-auto-flow:row;grid-template-columns:repeat(4,1fr)}
.seg button{
  padding:0 var(--s1);min-width:0;
  border:1px solid var(--line);background:color-mix(in srgb,#090c14 var(--a-glass),transparent);color:#b4b7c1;
  font-size:var(--t-micro);
}
.seg button[aria-pressed="true"],.seg button[aria-current]{
  color:var(--gold-hi);border-color:color-mix(in srgb,var(--gold) 38%,transparent);
  background:linear-gradient(180deg,color-mix(in srgb,var(--gold) var(--a-wash-on),transparent),color-mix(in srgb,var(--gold) var(--a-wash),transparent)),var(--panel);box-shadow:inset 0 0 18px color-mix(in srgb,var(--gold) 7%,transparent);
}
.seg button:disabled,.btn:disabled{opacity:.34;transform:none;cursor:default}
/* minmax(0,1fr), not a bare 1fr: `1fr` will not go below the width of its own content, so the column
   holding the longest label quietly takes a few pixels off the other one and the pair stops being a
   pair. Zero as the floor makes both columns exactly half, whatever is written in them. */
.pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s1)}

/* ---------- where a hero stands ----------
   The shot is the hero's frame: the sprite is scaled so its frame lands here. Anything that reaches
   past it - a wide weapon, one that hangs below the feet - keeps its full size and hangs outside,
   over whatever is around it, rather than being cropped or shrunk to fit. */
.frame,.tile{overflow:visible}
/* the figure is art, not a target: a weapon hanging over a button never swallows the tap */
.shot{position:relative;width:100%;height:var(--shot-h,0px);pointer-events:none}
.frame{position:relative;flex:0 0 auto;margin:0}

/* ---------- grids of heroes ---------- */
/* Rows share their height, so a caption that runs to two lines cannot make its row taller than the
   one beside it. Before this the band's nine were visibly three different sizes. */
.grid{flex:0 0 auto;display:grid;gap:var(--s3);grid-auto-rows:1fr;align-items:stretch;position:relative}
/* ---------- turning a page of cards ----------
   An arrow at either edge of the grid, over the cards, in place of a Previous / Next row beneath them.
   Absolute, so the grid's rows do not know it is there, and above a caption so a name never sits on
   it. Half off the grid's edge into the screen's own margin: it marks the edge rather than a card. */
.grid .turn{position:absolute;top:50%;z-index:7;width:34px;height:34px;margin-top:-17px;padding:0;
  display:grid;place-items:center;border:1px solid var(--line-soft);border-radius:50%;
  background:color-mix(in srgb,#0d1020 var(--a-glass),transparent);color:#e6e7ec;box-shadow:0 6px 18px #0006;cursor:pointer;
  transition:opacity .2s ease,transform .14s var(--ease)}
.grid .turn[data-dir="-1"]{left:-9px}
.grid .turn[data-dir="1"]{right:-9px}
.grid .turn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.grid .turn:active{transform:scale(.92)}
.grid .turn:disabled{opacity:.22;cursor:default;transform:none}
.grid[data-cols="2"]{grid-template-columns:repeat(2,1fr)}
.grid[data-cols="3"]{grid-template-columns:repeat(3,1fr)}
.tile{
  position:relative;margin:0;padding:var(--s2) var(--s1) var(--s1);display:grid;gap:var(--s1);
  border:1px solid var(--line-soft);border-radius:var(--r-tile);
  background:var(--panel);
  box-shadow:0 10px 30px #0004;
  transition:border-color .2s ease,background-color .2s ease,box-shadow .2s ease,transform .14s var(--ease);
}
.tile:active{transform:scale(.965);border-color:#ffffff3d}
/* A hero's art spills over its own caption on purpose - nothing is cropped to fit a frame - but the
   name still has to be readable, and a sprite's gold aura is a terrible thing to set grey text on.
   Measured before this: tile captions ran as low as 2.41:1 against a hero's own glow. So the words
   get their own ground, and the art keeps spilling over everything except them. This is also why the
   names looked overdrawn on the band. */
.tile figcaption,.tile .seatskill{position:relative;z-index:2}
/* a limit-broken hero's pips: one gem lit per break of the five (owner, 28 Sep 2026: "lets go with ladder and pips") */
.lbpips{margin-left:.4em;letter-spacing:.01em;color:var(--muted)}
.lbpips b{font-weight:inherit;color:var(--gold-hi)}
/* T3+5 prestige is presentation chrome, not another battle VFX. On full hero cards the +5 edge deliberately replaces
   the ordinary Legendary gold edge so it reads as PRISM at phone scale; Home lineup pips stay clean. The frame lives at
   z-index 0 so the shared hero canvas / Look figure (z-index 1) always paints over it, and its colour sweep is deliberately
   slow. One CSS gradient mask only: no particles, lights, extra canvas or battle layer. */
.tile[data-limit="5"]:not(.fighter),
[data-hero-pane="look"] .frame[data-limit="5"]{border-color:transparent}
.tile[data-limit="5"]:not(.fighter)::before,
[data-hero-pane="look"] .frame[data-limit="5"]::before{
  content:"";position:absolute;inset:-2px;z-index:0;pointer-events:none;padding:2px;
  border-radius:var(--lb-prism-radius,var(--r-tile));
  background:linear-gradient(118deg,
    rgba(255,255,255,1) 0%,rgba(83,221,255,.98) 11%,rgba(142,112,255,.96) 25%,
    rgba(255,91,211,.96) 40%,rgba(255,216,79,.98) 56%,rgba(86,255,195,.96) 72%,
    rgba(108,174,255,.98) 87%,rgba(255,255,255,1) 100%);
  background-size:320% 100%;opacity:.94;
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask-composite:exclude;
  animation:lb-prism-travel 12s linear infinite,lb-prism-breathe 5.5s ease-in-out infinite alternate
}
[data-hero-pane="look"] .frame[data-limit="5"]::before{--lb-prism-radius:var(--r-panel);inset:-3px}
@keyframes lb-prism-travel{to{background-position:320% 0}}
@keyframes lb-prism-breathe{from{opacity:.80}to{opacity:1}}
@media (prefers-reduced-motion:reduce){
  .tile[data-limit="5"]:not(.fighter)::before,
  [data-hero-pane="look"] .frame[data-limit="5"]::before{animation:none;background-position:50% 0;opacity:.94}
}
.tile::after{
  /* only as tall as the words it is there for. Measured: the caption and skill line occupy the lowest
     23% of a tile while the hero's art runs to 88%, so a 46% scrim was covering the model's lower
     third to no purpose (scratchpad/scrim.mjs). Lighter, too - it has to carry the text, not hide the
     hero behind it. */
  content:"";position:absolute;left:0;right:0;bottom:0;height:27%;pointer-events:none;
  background:linear-gradient(180deg,#07091200,color-mix(in srgb,#070912 var(--a-scrim),transparent) 44%,color-mix(in srgb,#070912 var(--a-scrim),transparent));
  border-radius:0 0 var(--r-tile) var(--r-tile);
}
@keyframes seat-take{0%{transform:scale(1)}38%{transform:scale(1.07)}100%{transform:scale(1)}}
.seat[data-took]{animation:seat-take .36s var(--ease)}
/* A card says its own tier. At 24% on a dark panel over a lit world the rarity edge was invisible -
   the only thing telling you a hero was legendary was the letters "T3" in grey - so the edge does the
   work instead. It stays an EDGE rather than tinting the panel: the glass is a window onto the world
   behind it, and colouring it in would quietly undo that. The sprite already carries rarity in its
   floor ring and its rays; this is the chrome agreeing with the art rather than a second opinion. */
.tile[data-rarity]{border-color:color-mix(in srgb,var(--rarity) 78%,transparent)}
.tile[data-rarity="0"]{border-color:color-mix(in srgb,var(--rarity) 42%,transparent)}
.tile[data-rarity="3"],.tile[data-rarity="4"]{
  box-shadow:inset 0 1px 0 color-mix(in srgb,var(--rarity) 60%,transparent),0 10px 30px #0004}
.tile[data-rarity="4"]{border-width:2px}
.tile[data-rarity="0"]{--rarity:var(--grey)}
.tile[data-rarity="1"]{--rarity:var(--green)}
.tile[data-rarity="2"]{--rarity:var(--blue)}
.tile[data-rarity="3"]{--rarity:var(--violet)}
.tile[data-rarity="4"]{--rarity:var(--loom);box-shadow:inset 0 -30px 40px -30px var(--loom)}
/* One line each, everywhere. A name or a skill that does not fit is trimmed rather than allowed to
   wrap, because a wrapped caption changes the height of the card it is under and the cards have to
   match - on this screen and between screens. */
.tile figcaption{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;
  color:#c3c5cd;text-align:center;line-height:var(--lead-label);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
/* The skill line is the longest thing on a card and the card is 109px wide. It keeps its own tighter
   tracking and uses the padding either side, which is the difference between "Shadowstep · Lv 10"
   fitting and being cut. */
.tile .seatskill{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;text-align:center;
  letter-spacing:.01em;margin-inline:-4px}
/* the art takes the room the words leave, so every card is the same shape */
.tile{grid-template-rows:1fr auto auto}
.tile .shot{min-height:0}

/* ---------- numbers ---------- */
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s3)}
.stat-note{margin:var(--s3) 0 0;font-size:var(--t-micro);letter-spacing:.01em;line-height:1.4;color:var(--muted)}
.stat-note b{font-weight:600;color:var(--ink,inherit)}
.stat{display:grid;gap:var(--s1);min-width:0}
.stat span{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);line-height:1}
.stat strong{font-size:21px;font-weight:600;line-height:1;letter-spacing:.01em;font-variant-numeric:tabular-nums}
.stat small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted);font-variant-numeric:tabular-nums;line-height:1.2}
.bar{height:3px;border-radius:2px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);overflow:hidden}
.bar i{display:block;height:100%;background:linear-gradient(90deg,color-mix(in srgb,var(--gold) 60%,transparent),var(--gold));transform-origin:left center;transform:scaleX(var(--fill,0));transition:transform .9s var(--ease)}
.facts{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2)}
.fact{display:grid;gap:4px;min-width:0}
.fact span{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);line-height:1}
.fact b{font-weight:600;font-size:var(--t-body);line-height:var(--lead-label);overflow-wrap:anywhere}

/* ---------- states, hints, toast ---------- */
.state{
  padding:var(--s5);display:grid;gap:var(--s2);justify-items:start;
  border:1px solid var(--line);border-radius:var(--r-panel);background:var(--panel);
}
.state p{margin:0}
.state h2{margin:0;font-family:var(--serif);font-weight:600;font-size:18px;line-height:1.15;letter-spacing:.01em}
.state small{color:var(--muted);font-size:var(--t-body);line-height:var(--lead-body);max-width:46ch}
.state .btn{margin-top:var(--s1)}
.hint{flex:0 0 auto;margin:0;text-align:center;font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:#8e929f}
.toast{
  /* the centring is on the element itself, not left to the animation's fill, so it is centred even if
     the animation never runs */
  position:absolute;z-index:40;left:50%;transform:translateX(-50%);
  /* At the top, where a phone puts its own notifications. It used to sit above the dock, which is the
     busiest end of every screen in this game: it landed on the nine standing over the dock on Home, on
     the foot of a verdict card, and on the button a sheet was asking you to press. Nothing the player
     acts on lives up here - the head is a title and two chips - so a message can pass across it and
     take nothing with it. */
  top:calc(env(safe-area-inset-top) + var(--s2));
  animation:toast-in .22s var(--ease) both;
  /* as wide as it needs and no wider: without a width a flex row lets the message shrink under its
     own text, and "A new version of Ninefall is ready." came out four words tall next to its button */
  width:max-content;max-width:min(calc(var(--frame-w,100vw) * .92),420px);padding:var(--s2) var(--s3);
  display:flex;align-items:center;gap:var(--s3);
  /* A toast is a MESSAGE, not a surface to look through. It was taking --panel, which is about a third
     opaque - right for a card sitting over the world, wrong for the one thing on screen that has to be
     read at a glance and is often over the busiest part of the picture. It gets its own ground: nearly
     solid, blurred behind for the sliver that is left, a brighter edge and brighter text. Measured over
     terrain at its worst, this takes it from 2.5:1 to better than 12:1.

     Those numbers named literals. The ground is three tokens now, because the owner asked for the
     message to wear the difficulty too (17 Sep 2026), so each mood answers with its OWN dark rather
     than with --gold, which is an accent and would have turned a near-solid surface into a wash. The
     text stays #eceef4 and the alphas stay where they were, so the ratio below travels with them. */
  border:1px solid var(--toast-edge);border-radius:var(--r-control);
  background:linear-gradient(180deg,var(--toast-a),var(--toast-b));
  box-shadow:0 16px 54px #000b,0 0 0 1px #0006;
  font-size:var(--t-body);color:#eceef4;
}
/* It arrives the way a notification does: from off the top edge, and only a little way, so it reads as
   something sliding in rather than something appearing. */
@keyframes toast-in{from{opacity:0;transform:translate(-50%,-14px)}to{opacity:1;transform:translate(-50%,0)}}
.toast>span{min-width:0;flex:1 1 auto;text-align:center}   /* the words centred in the toast, one line or two (owner, 11 Oct 2026) */
.toast{justify-content:center;text-align:center}
.toast .util{flex:0 0 auto;white-space:nowrap}

/* ---------- the dock ---------- */
.dock{
  position:absolute;z-index:20;left:50%;transform:translateX(-50%);
  bottom:calc(env(safe-area-inset-bottom) + 12px);
  padding:var(--s1);display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:var(--s1);
  width:min(var(--column),calc(var(--frame-w,100vw) - 28px));
  border:1px solid var(--panel-line);border-radius:var(--r-panel);background:var(--panel);
  box-shadow:0 16px 50px #0008;
}
.dock button{
  min-height:var(--dock-control);border-radius:var(--r-control);border:0;background:transparent;color:#9497a3;
  font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;line-height:1.2;
  transition:color .2s ease,background-color .2s ease,transform .16s var(--ease);
  display:flex;align-items:center;justify-content:center;gap:var(--s1);
  font-size:var(--t-micro);
}
.dock button svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.dock button[aria-current="page"]{color:var(--gold-hi);background:linear-gradient(180deg,color-mix(in srgb,var(--gold) var(--a-wash-on),transparent),color-mix(in srgb,var(--gold) var(--a-wash),transparent)),var(--panel)}


/* ---------- the band: nine seats, and the lines drawn across them ----------
   The grid takes the pointer itself (touch-action:none) so drawing a line is never read as a scroll.
   The strokes sit in an overlay above the seats and below nothing: they are not interactive. */
.band{position:relative;flex:0 0 auto;display:grid}
.band .grid{touch-action:none;-webkit-user-select:none;user-select:none}
.band .lines{position:absolute;inset:0;pointer-events:none;overflow:visible}
/* the lines come with the cards: while the seats wait for their figures (89zg holds them out of sight, data-fig3d="wait") the band
   showed three lit lines over an empty ground (owner, 14 Oct 2026: "There are lines while waiting for the band to load") */
.band:has([data-fig3d="wait"]) .lines{visibility:hidden}
.band .lines line{stroke-linecap:round;fill:none}
.band .lines .drawn{stroke:var(--gold);stroke-width:3;opacity:.85}
.band .lines .drawn.n1{stroke:var(--blue)}
.band .lines .drawn.n2{stroke:var(--green)}
/* A chain is drawn as one unbroken bright stroke rather than in its line's own colour: it is not a
   third thing to read, it is the same line saying it holds together. It breathes rather than blinks -
   the glow swells and falls and never goes out, because the cover it stands for never blinks either. */
.band .lines .drawn.chain{stroke:#fff;stroke-width:4;opacity:1;animation:chain-lit 2.6s var(--ease-inout) infinite}
@keyframes chain-lit{
  0%,100%{filter:drop-shadow(0 0 3px var(--gold))}
  50%{filter:drop-shadow(0 0 11px var(--gold)) drop-shadow(0 0 3px #fff9)}
}
/* The motes: one more stroke over the same two points, dashed to zero length with the round cap the
   lines already have, so every dash is a dot. Moving the dash offset walks them along it. */
.band .lines .motes{stroke:var(--gold);stroke-width:6;stroke-linecap:round;fill:none;
  stroke-dasharray:0 19;animation:chain-motes 1.9s linear infinite}
@keyframes chain-motes{from{stroke-dashoffset:0}to{stroke-dashoffset:-19}}
.band .lines .drawing{stroke:#ffffff8c;stroke-width:2;stroke-dasharray:2 6}
/* Active interaction modes use the board itself as the mode indicator. No extra panel or control is
   added: the same nine seats simply become an unmistakable selection surface, while drawn lines step
   back so they cannot compete with Move or Leader selection. */
.band[data-mode="move"]::before,.band[data-mode="leader"]::before{
  content:"";position:absolute;inset:-4px;z-index:0;pointer-events:none;border-radius:10px;
  border:1px solid color-mix(in srgb,var(--gold) 58%,transparent);
  box-shadow:inset 0 0 24px color-mix(in srgb,var(--gold) 7%,transparent),0 0 14px #0007
}
.band[data-mode="move"] .lines,.band[data-mode="leader"] .lines{opacity:.16}
.band[data-mode="leader"] .seat[data-mode-state="selected"]{
  opacity:1;border-color:var(--gold);box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--gold) 55%,transparent),0 0 14px color-mix(in srgb,var(--gold) 24%,transparent)
}
.band[data-mode="leader"] .seat[data-mode-state="available"]{
  opacity:1;border-color:color-mix(in srgb,var(--green) 78%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--green) 34%,transparent)
}
.band[data-mode="leader"] .seat[data-mode-state="unavailable"]{opacity:.34;filter:saturate(.55) brightness(.78)}
.band[data-mode="move"] .seat[data-mode-state="available"]{border-color:#ffffff3d}
.band[data-mode="move"] .seat[data-move-source="true"]{
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--blue) 38%,transparent)
}
.band[data-mode="move"] .seat[data-move-source="false"]{opacity:.72;border-style:dashed}
.band[data-mode="move"] .seat[data-lifted]{opacity:.42;transform:scale(.92);
  box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--gold) 70%,transparent)}
.band[data-mode="move"] .seat[data-over]{opacity:1;border-color:var(--gold);
  box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--gold) 62%,transparent),0 0 14px color-mix(in srgb,var(--gold) 28%,transparent)}
/* Somebody who has asked for less movement still gets to see which line is the chain: the glow holds
   at its brightest and the motes stand still on it rather than running. */

.seat{cursor:pointer}
[data-swipe-pages="heroes"]{touch-action:pan-y}
.seat[data-in]{border-color:color-mix(in srgb,var(--gold) 55%,transparent)}
.seat[data-in="2"]{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--gold) 30%,transparent)}
.seat[data-in="3"]{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--gold) 42%,transparent)}
.seat[data-first]{border-color:#fff;box-shadow:inset 0 0 0 2px #ffffff38,0 0 10px #ffffff24}
.seat .seatskill{display:block;text-align:center;font-size:var(--t-micro);letter-spacing:.01em;
  color:var(--muted);line-height:var(--lead-label);overflow-wrap:anywhere}
.seat figcaption{display:flex;align-items:center;justify-content:center;gap:5px}

/* a sign is a square of its own colour - the five never mix, so the shape stays the same */
.sign{width:8px;height:8px;border-radius:3px;flex:none;background:var(--sign,var(--grey))}
.sign[data-sign="lightning"]{--sign:#e0c84a}
.sign[data-sign="water"]{--sign:#6f9ce8}
.sign[data-sign="fire"]{--sign:#e2463f}   /* red, as the heroes wear it (owner, 27 Sep 2026); Burn keeps its own orange */
.sign[data-sign="earth"]{--sign:#7fbb7a}
.sign[data-sign="wind"]{--sign:#74c2cc}
.prism-sign{display:inline-grid;place-items:center;width:10px;height:10px;flex:none;font:700 10px/1 var(--serif);
  color:#effcff;text-shadow:0 0 5px #7de7ff,0 0 7px #c07cff,0 0 9px #ff8fce}

/* the three lines you have drawn, named */
.lineup-chosen{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s1)}
/* What the nine share, above the fight button: nought to three pills, so a wrapping row rather than a
   fixed grid - the drawn lines below are always three and get the grid. */
.accords{display:flex;flex-wrap:wrap;justify-content:flex-start;gap:var(--s1)}
.accords .pill{white-space:nowrap}
/* Keep each chosen line easy to scan on narrow phones: Line identity and Chain share the small
   top row; Top/Middle/Bottom owns the full row below. The accent is the whole border/text treatment,
   never a decorative edge stripe. */
.chosen{--line-accent:var(--gold);display:grid;grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:"label state" "name name";align-content:center;column-gap:6px;row-gap:4px;
  height:var(--control);padding:0 10px;border:1px solid var(--line-soft);
  border-radius:var(--r-control);text-align:left;min-width:0;background:color-mix(in srgb,#090c14 var(--a-glass),transparent)}
.chosen[data-line="2"]{--line-accent:var(--blue)}
.chosen[data-line="3"]{--line-accent:var(--green)}
.chosen[data-on]{border-color:color-mix(in srgb,var(--line-accent) 68%,transparent)}
.chosen[data-chain]{border-color:color-mix(in srgb,#fff 58%,var(--line-accent))}
.chosen span{grid-area:label;font-size:var(--t-micro);letter-spacing:.01em;
  text-transform:uppercase;color:var(--line-accent);line-height:1;white-space:nowrap}
.chosen em{grid-area:state;font-style:normal;font-size:7px;letter-spacing:.01em;text-transform:uppercase;
  color:#ffffffb8;line-height:1;white-space:nowrap}
.chosen b{grid-area:name;font-size:var(--t-label);font-weight:700;line-height:1;min-width:0;
  max-width:100%;white-space:nowrap}
/* the next Line to draw says how, in gold where its empty mark was (UI/UX audit 05): the same row, the same size */
.chosen b[data-hint]{color:var(--gold)}

/* A short screen cannot hold nine seats, their names, their skills and a panel. The skill line is the
   first thing to go - it is a reminder, not a decision - and the chosen lines stack tighter. */
/* A title sized only by width grows on a wide-but-short screen until it pushes the page off the
   bottom. Height gets a say: the display face is the largest thing on any screen, so it is the first
   thing that should notice the screen is short. */

/* ---------- bare: the model gets the room ----------
   On a screen whose subject is one figure, the head stays where it is and everything you can press is
   reduced to one slim strip, so the figure has the rest. The art is then fitted to the HEIGHT it was
   given rather than the width - the no-crop rule solved the other way round - and whatever reaches past
   the frame sideways hangs outside it as it always did. */
#view[data-bare="true"]{gap:var(--s2)}
#view[data-bare="true"] > .body{flex:1 1 auto;min-height:0;display:grid;place-items:center}
#view[data-bare="true"] .frame{margin:0;height:100%;width:100%;display:grid;place-items:center}
/* the Look page: every hero at one scale on one ground (89zf); the shot is the pane */
#view[data-bare="true"] .shot{height:100%}
#view[data-bare="true"]{overflow-x:clip}   /* a weapon running off the screen never scrolls the page sideways */
#view[data-bare="true"] > .head{position:relative;z-index:2}   /* the name stays over a weapon that reaches up past the card */
#view[data-bare="true"] .frame{cursor:pointer}
#view[data-bare="true"] .body[data-hero-pane="look"] .frame{cursor:default}
/* Hero tabs all stay on the same transparent world surface. Reading panes opt out of the bare model
   grid geometry while keeping the bare page's no-overlay state. */
#view[data-bare="true"]{background:transparent}
#view[data-bare="true"] > .body.read{
  flex:1 1 auto;min-height:0;display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;
  place-items:initial;background:transparent
}
.float{display:grid;gap:var(--s2)}
.float .hint{margin:0}
/* the battle's narration always holds two lines: a line that wraps (the Oculist's "...and the band bears the backlash") must not
   take its second line out of the field and refit every figure smaller for that one step (owner, 26 Sep 2026) */
/* its one column the screen's width, never its content's: the narration's older row is held to one line, and a one-line row's
   least width is its whole text, so a long row (a blow, a mend, critical and a Sign reaction - the owner's Rogue Ones band, 23 Oct
   2026) widened the column, the log and Withdraw past the screen's edge; held to the width, it ends in its ellipsis */
.battle + .float{grid-template-columns:minmax(0,1fr)}
.battle + .float > .hint{min-height:2lh;display:grid;align-content:center}
/* the narration in colour (89za): a number in its dealer's Sign, a heal in green, names lit, the words between quiet */
[data-part="say"] .say-num{--say:#f4f5f8;font-weight:700;color:var(--say);text-shadow:0 0 9px color-mix(in srgb,var(--say) 50%,transparent)}
[data-part="say"] .say-num[data-sign="lightning"]{--say:#f5dc5c}
[data-part="say"] .say-num[data-sign="water"]{--say:#8fb8ff}
[data-part="say"] .say-num[data-sign="fire"]{--say:#ffa066}
[data-part="say"] .say-num[data-sign="earth"]{--say:#9ad890}
[data-part="say"] .say-num[data-sign="wind"]{--say:#86dfe8}
[data-part="say"] .say-num[data-kind="heal"]{--say:var(--green)}
[data-part="say"] .say-hero{color:#e9ebf2}
[data-part="say"] .say-foe{color:#eda3a0}
/* the narration log (89zaa, owner 13 Oct 2026): one row an action, two at once ("only allowed 2 lines"), newest at the bottom, the one
   before it dimmer above; a long newest row takes both lines (its number is at its end) and the older one leaves the window. The old
   line stays in the page, unseen, for everything that reads it */
.battle + .float > [data-part="say"][data-logged]{position:absolute;width:1px;height:1px;min-height:0;margin:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.battle + .float > .narr-log{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;justify-content:flex-end;height:2lh;overflow:clip;overflow-clip-margin:4px;font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;text-align:center;color:#8e929f}
.narr-row{flex:0 0 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:opacity .3s ease}
.narr-row[data-age="0"]{white-space:normal;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}
.narr-row[data-age="1"]{opacity:.55}
.narr-row[data-gone]{opacity:0}
.narr-log[data-shift="a"] .narr-row{animation:narr-rise-a .24s ease-out}
.narr-log[data-shift="b"] .narr-row{animation:narr-rise-b .24s ease-out}
.narr-log[data-shift="a"] .narr-row[data-age="0"]{animation:narr-enter-a .24s ease-out}
.narr-log[data-shift="b"] .narr-row[data-age="0"]{animation:narr-enter-b .24s ease-out}
@keyframes narr-rise-a{from{transform:translateY(1lh)}}
@keyframes narr-rise-b{from{transform:translateY(1lh)}}
@keyframes narr-enter-a{from{transform:translateY(1lh);opacity:0}}
@keyframes narr-enter-b{from{transform:translateY(1lh);opacity:0}}
.narr-row:is([data-kind="cast"],[data-kind="cast-foe"]){font-weight:600;color:#b9bcc8}
.narr-row[data-kind="round"]{color:#6f7380}
.narr-row :is(.say-num,.say-react){--say:#f4f5f8;font-weight:700;color:var(--say);text-shadow:0 0 9px color-mix(in srgb,var(--say) 50%,transparent)}
.narr-row :is(.say-num,.say-react)[data-sign="lightning"]{--say:#f5dc5c}
.narr-row :is(.say-num,.say-react)[data-sign="water"]{--say:#8fb8ff}
.narr-row :is(.say-num,.say-react)[data-sign="fire"]{--say:#ffa066}
.narr-row :is(.say-num,.say-react)[data-sign="earth"]{--say:#9ad890}
.narr-row :is(.say-num,.say-react)[data-sign="wind"]{--say:#86dfe8}
.narr-row .say-num[data-kind="heal"]{--say:var(--green)}
.narr-row .say-hero{color:#e9ebf2}
.narr-row .say-foe{color:#eda3a0}
.float .panel{padding:var(--s2);gap:var(--s1)}
/* the fight's way out (owner, 15 Oct 2026, option A of three: "Option A"): Skip and Withdraw are two plain words at the ends of
   one slim row, no frames - a box around two boxes above the dock read as a second dock, for two acts a fight rarely needs.
   Each keeps a 44 px tap area (the framed buttons were 34). */
.float .panel.exits{padding:0;border:0;background:none;box-shadow:none}
.float .panel.exits .pair{display:flex;justify-content:space-between;align-items:center}
.float .panel.exits .btn{min-height:44px;height:44px;min-width:96px;width:auto;padding:0 4px;border:0;background:none;box-shadow:none;opacity:.78}
.float .panel.exits .btn:first-child{justify-content:flex-start}
.float .panel.exits .btn:last-child{justify-content:flex-end;margin-left:auto}   /* the words sit at the edges, the tap areas reach in; Withdraw keeps the right edge when a first visit has no Skip */
/* A row that is nothing but buttons does not get a box round it - that is a frame containing only
   other frames. The store, the archive and the account are each one segmented row, so they stand in
   the stack at the same height as every other button and the panel is kept for things that hold
   actual content. The account joined them the hard way: given the full box it stood 56px instead of
   34, which is 9px more than the shortest frame has, and since the dock is positioned over the
   content rather than beside it, the bottom of Reset account sat underneath the dock. */
.float .panel[data-part="stores"],.float .panel[data-part="archive"],.float .panel[data-part="account"]{
  padding:0;border:0;background:none;box-shadow:none;}

/* A provision is a name and a price. On one line the three chips wrapped differently from each other
   at narrow widths - two lines by design reads the same at every width. */
.float .panel[data-part="stores"] .seg button{
  display:grid;align-content:center;gap:1px;padding:4px var(--s1);line-height:1.15;height:auto;min-height:var(--control);
}
.float .panel[data-part="stores"] .seg button b{font-weight:600;color:#c9cbd4}
.float .panel[data-part="stores"] .seg button small{font-size:var(--t-micro);color:var(--muted);font-variant-numeric:tabular-nums}
.float .panel[data-part="stores"] .seg button:disabled small{color:var(--muted)}

/* the smallest a control can be and still be worth aiming at */
.mini{display:grid;grid-template-columns:auto 1fr;gap:var(--s1);align-items:center}

/* an icon that is currently true wears the gold */
.icon[data-on]{color:var(--gold);border-color:color-mix(in srgb,var(--gold) 45%,transparent);background:color-mix(in srgb,var(--gold) var(--a-wash-on),transparent)}

/* A narrow screen tightens the smallest type rather than taking another row: a row costs the model
   more height than the letters cost legibility. */
:root[data-w370] .seg button{font-size:8.5px;letter-spacing:.01em;padding:0 3px}

/* ---------- the field ----------
   Two sides, the far one above the near one, each fighter a sprite with its life under it. The rows
   share the room the rest of the screen leaves, and the fallen stay where they fell, dimmed. */
/* Two columns facing each other: the band on the left, what it is facing on the right. Each side is a
   stack, so a fighter's whole height is its own and the two lines look across the gap between them. */
.battle{flex:1 1 auto;min-height:0;display:grid;grid-template-columns:1fr 1fr;gap:var(--s2)}
.side{display:grid;grid-auto-flow:row;grid-auto-rows:1fr;gap:var(--s2);justify-items:center;align-items:stretch;min-height:0}
.fighter{position:relative;margin:0;padding:0;width:100%;min-width:0;height:100%;
  display:grid;grid-template-rows:minmax(0,1fr) auto;gap:4px;
  transition:transform .22s var(--ease),opacity .4s ease}
/* the art is fitted to the row's HEIGHT, and whatever spills sideways passes behind its neighbours */
.battle .shot{height:100%;min-height:0;z-index:1}
/* ---------- an Anchor stands in its own shot ----------
   Its minifigure (89zg) is framed by the shot, head and feet inside it; the vitals and intent chrome stay above it. */
.battle .fighter[data-rank="boss"] .shot{overflow:visible;z-index:0}
/* Give the boss side most of the grid for its own vitals. Its body position is independent of this
   column: blit() pins painted left to the hero HP edge and lets the body continue rightward. */
.battle:has(.fighter[data-rank="boss"]){grid-template-columns:.58fr 1.42fr}
.battle .fighter[data-rank="boss"] .vitals{z-index:4}
.battle [data-part="near"] .fighter{z-index:2}  /* the heroes stay in front of what is looming at them */
.vitals{position:relative;z-index:2}
.fighter[data-acting]{transform:translateY(-6px) scale(1.04)}
/* Battle motion belongs to the art. Its name, marks and HP stay on their seat. */
.battle .fighter[data-acting]{transform:none}
.battle .fighter[data-gesture] .shot{transition:none;transform-origin:50% 78%}
/* Going down was a cut: full colour on one frame, a grey ghost at a quarter opacity on the next. It
   is a fall now - the colour drains, the figure settles, and it takes long enough to see happen. */
.fighter{transition:opacity .34s var(--ease),filter .34s var(--ease)}
.fighter .shot{transition:transform .40s var(--ease)}
.fighter[data-down]{opacity:.26;filter:grayscale(1)}
.fighter[data-down] .shot{transform:translateY(7px) scale(.965)}

/* ---------- the band turning over ----------
   Three fought and three come up. The ones who fought step back and to the left - behind, out of the
   light - and the next three arrive from the same place into the ground they left, a beat apart down
   the line so it reads as a band rotating rather than a cut. Both halves move the same way, because
   they are the same band walking past itself. */
/* Per figure, not per side: a hero who is standing in both lines does not leave and come back, so
   only the ones actually changing are animated. The stagger is the seat's own delay. */
.fighter[data-going]{animation:line-out .22s var(--ease) both;animation-delay:var(--turn-delay,0s)}
.fighter[data-coming]{animation:line-in .32s var(--ease) both;animation-delay:var(--turn-delay,0s)}
@keyframes line-out{from{opacity:1;transform:none}to{opacity:0;transform:translateX(-20px) scale(.93)}}
@keyframes line-in{from{opacity:0;transform:translateX(-26px) scale(.94)}to{opacity:1;transform:none}}
/* asked for no motion: the line still changes, it just does not travel to do it */
.vitals{display:grid;gap:2px;min-width:0;justify-items:center}
/* Both sides speak the same combat UI language. Foes show role + intent; heroes show job + their
   deterministic skill charge. The strip stays qualitative: the player sees what is coming and when,
   without adding exact-damage clutter to an auto-resolving battle. */
.battle-mechanic{max-width:96%;min-width:0;overflow:hidden;display:flex;align-items:center;justify-content:center;min-height:12px;
  padding:1px 4px;border:1px solid #ffffff1c;border-radius:4px;background:color-mix(in srgb,#070912 var(--a-glass),transparent);
  font-size:7px;line-height:1.15;letter-spacing:.01em;text-transform:uppercase;white-space:nowrap}
/* a tag wraps its line, centred under its fighter; a line longer than the cap (one value for heroes and foes, the boss's on its
   own cell by the same rule) takes exactly the cap and runs through it (60a, owner 11 Oct 2026: "fix the width and put the long
   tag as running text inside it" / "if it's short, wrap the tag around the text") - never cut */
.battle-mechanic .run-line{gap:4px}
.battle-mechanic b{flex:0 0 auto;white-space:nowrap;font-weight:700;color:#eee6d5}
.battle-mechanic span{flex:0 0 auto;white-space:nowrap;color:#aeb2c0;font-variant-numeric:tabular-nums}
.battle-mechanic .counter-rate{flex:0 0 auto;padding:0 3px;border-radius:3px;border:1px solid #ffffff18;
  color:color-mix(in srgb,var(--gold) 78%,#d9dce6);font-style:normal;font-weight:700;letter-spacing:.01em;font-variant-numeric:tabular-nums}
.battle-mechanic[data-state*="READY"]{border-color:color-mix(in srgb,var(--gold) 38%,transparent)}
.fighter[data-countering] .battle-mechanic{border-color:color-mix(in srgb,var(--gold) 76%,transparent);
  box-shadow:0 0 9px color-mix(in srgb,var(--gold) 28%,transparent)}
.fighter[data-countering] .counter-rate{background:color-mix(in srgb,var(--gold) var(--a-wash-on),transparent);color:#fff4cf}
.hero-intent[data-state="FROZEN"]{border-color:#7ccbd866;box-shadow:0 0 7px #7ccbd822}
.hero-intent[data-state="DOWN"]{opacity:.46}
.battle .fighter[data-rank="boss"] .foe-intent{font-size:7.5px;border-color:color-mix(in srgb,var(--gold) 48%,transparent)}

/* one pip per mark, in the mark's own colour */
.marks{display:flex;flex-wrap:wrap;justify-content:center;gap:3px;min-height:9px}
.mark{width:9px;height:9px;border-radius:3px;background:color-mix(in srgb,var(--tint) 78%,transparent);
  display:grid;place-items:center;font-size:7px;font-style:normal;font-weight:700;color:#0b0d16;line-height:1}
.mark[data-hard]{box-shadow:0 0 0 1px var(--tint),0 0 8px var(--tint)}

/* Persistent battle status rail: stable information chrome directly above HP.
   Four chips stay visible by default; overflow expands in place instead of covering the fighter body. */
.status-rail{
  width:96%;max-width:96%;min-height:13px;display:flex;align-items:center;justify-content:center;gap:2px;
  flex-wrap:nowrap;overflow:visible;outline:none;touch-action:manipulation
}
.status-chip,.status-overflow{
  min-width:0;height:11px;padding:0 3px;border-radius:3px;border:1px solid #ffffff18;
  display:inline-flex;align-items:center;justify-content:center;white-space:nowrap;
  font-size:6.5px;line-height:1;font-style:normal;font-weight:700;letter-spacing:.01em;
  font-variant-numeric:tabular-nums;text-transform:uppercase;text-shadow:var(--glass-shadow);
  background:color-mix(in srgb,#070912 var(--a-glass),transparent);color:#d7d9e2;box-sizing:border-box
}
.status-chip[data-kind="buff"]{border-color:#78d9ee52;background:color-mix(in srgb,#4cc8e5 var(--a-wash-on),transparent);color:#dff9ff}
.status-chip[data-kind="debuff"]{border-color:#e47b875e;background:color-mix(in srgb,#cf4554 var(--a-wash-on),transparent);color:#ffdfe3}
.status-rail .status-chip.mark{
  width:auto;height:11px;padding:0 3px;border-radius:3px;
  border-color:color-mix(in srgb,var(--tint) 62%,transparent);
  background:color-mix(in srgb,color-mix(in srgb,var(--tint) 26%,#070912) var(--a-glass),transparent);
  color:color-mix(in srgb,var(--tint) 55%,#ffffff)
}
.status-rail .status-chip.mark[data-hard]{
  border-color:var(--tint);box-shadow:0 0 0 1px color-mix(in srgb,var(--tint) 42%,transparent),0 0 8px color-mix(in srgb,var(--tint) 52%,transparent);
  color:#f0feff
}
.status-overflow{appearance:none;-webkit-appearance:none;cursor:pointer;color:#b8bdca;border-color:#ffffff22;background:color-mix(in srgb,#0a0d16 var(--a-glass),transparent)}
.status-chip[data-overflow-item]{display:none}
.status-rail[data-expanded="true"]{flex-wrap:wrap;row-gap:2px;z-index:6}
.status-rail[data-expanded="true"] .status-chip[data-overflow-item]{display:inline-flex}
.status-rail[data-expanded="true"] .status-overflow{display:none}
.status-rail:focus-visible{filter:drop-shadow(0 0 5px #ffffff38)}
@keyframes status-chip-in{
  0%{transform:translateY(2px) scale(.84);opacity:.18}
  55%{transform:translateY(-1px) scale(1.08);opacity:1}
  100%{transform:none;opacity:1}
}
.status-chip[data-changed="true"]{animation:status-chip-in .42s var(--ease) both}
.battle .side{align-items:stretch}
.hp{height:2px;border-radius:2px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);overflow:hidden}
.battle .hp{width:62%;justify-self:center;margin:0 auto}
.battle .fighter .hp{
  position:relative;height:5px;overflow:visible;border-radius:3px;
  background:linear-gradient(to bottom,color-mix(in srgb,#ffffff var(--a-wash),transparent) 0 40%,transparent 40% 60%,color-mix(in srgb,#ffffff var(--a-wash),transparent) 60% 100%)
}
/* One fixed-width stitched meter: HP owns the upper lane, Exceed Armor the lower lane.
   Armour never extends the bar horizontally; values above Max HP saturate the lower lane and glow. */
.battle .fighter .hp>i{
  position:absolute;left:0;top:0;height:40%;border-radius:3px 2px 1px 1px
}
.battle .fighter .hp .exceedbar{
  position:absolute;left:0;bottom:0;top:auto;height:40%;width:min(calc(var(--exceed,0) * 100%),100%);
  border-radius:1px 1px 3px 2px;pointer-events:none;opacity:0;
  background:linear-gradient(90deg,#80f4ff,#c49cff 38%,#ff9fda 68%,#ffe99b);
  box-shadow:0 0 4px #7de7ffaa,0 0 7px #be8cff66;
  transition:width .38s var(--ease),opacity .18s ease
}
.battle .fighter[data-exceed] .hp .exceedbar{opacity:1}
/* where an Anchor will wake (the boss fight audit, 12 Oct 2026): a hairline at half life on its bar until it wakes (89zee3) */
.battle .fighter[data-wakes] .hp::after{content:"";position:absolute;left:50%;top:-2px;width:1px;height:calc(40% + 4px);border-radius:1px;pointer-events:none;
  background:color-mix(in srgb,#ffffff var(--a-scrim),transparent);box-shadow:0 0 3px color-mix(in srgb,#ffffff var(--a-glass),transparent)}
/* what a blow took, kept for a beat on an Anchor's bar (the boss fight audit, 12 Oct 2026; 75d2 writes --trail on the bar after each
   reading): a paler lane of the fill under it stays at the old width, waits .35 s and drains to the new one in .45 s; a heal takes it
   along with the fill at once (data-trail="rise"). Its own pseudo-element (::after is the wake's hairline); its ink never white */
.battle .fighter[data-rank="boss"] .hp::before{content:"";position:absolute;left:0;top:0;height:40%;border-radius:3px 2px 1px 1px;pointer-events:none;
  width:calc(var(--trail,0) * 100%);background:color-mix(in srgb,var(--gold) 45%,#f0e6d6);transition:width .45s cubic-bezier(.4,0,.2,1) .35s}
.battle .fighter[data-rank="boss"] .hp[data-trail="rise"]::before{transition:width .38s var(--ease)}
.battle .fighter[data-exceed-over] .hp .exceedbar{box-shadow:0 0 5px #fff,0 0 9px #88edff,0 0 13px #d29cff}
.hp i{display:block;height:100%;border-radius:2px;background:linear-gradient(90deg,#b2553f,var(--gold));
  /* The fill is a WIDTH. It was a scaleX() reading a custom property, and when that substitution does
     not take - which is device-dependent and silent - the fallback is 1 and every bar in the game
     reads full. A width cannot fail that way: if the number is wrong the bar is wrong by the same
     amount, and the percentage printed beside it says which. */
  width:calc(var(--fill,1) * 100%);transition:width .38s var(--ease)}
/* A row that writes its own width wins: inline beats the rule, and the experience bar writes a plain
   percentage because a custom property did not resolve on the one device that matters. */
.gain .hp i{width:0}
.fighter figcaption{display:flex;align-items:center;justify-content:center;gap:4px;
  font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:#c3c5cd;
  line-height:var(--lead-label);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* a number that leaves the fighter it landed on */
@keyframes rise{from{transform:translate(-50%,0);opacity:0}
  18%{opacity:1}to{transform:translate(-50%,-46px);opacity:0}}
.num{position:absolute;left:50%;top:22%;z-index:4;pointer-events:none;white-space:nowrap;text-align:center;   /* one line, centred: from left:50% a label had only half its box and wrapped off-centre (owner, 27 Sep 2026: "align reflect damage number center") */

  font-family:var(--sans);font-size:16px;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.01em;   /* five sizes, one weight: 89zzz7-number-sizes owns every size; a tone is colour only (owner, 10 Oct 2026) */
  text-shadow:0 2px 6px #000a;animation:rise 1.05s var(--ease) forwards;color:#f3ede0}
.num[data-tone="edge"]{color:var(--gold)}
.num[data-tone="crit"]{color:#eadfff;text-shadow:0 2px 7px #000d,0 0 8px #fff,0 0 15px #a96dff}
.num[data-tone="weak"]{color:#9aa0ae}
.num[data-tone="mend"]{color:var(--green)}
.num[data-tone="exceed"]{color:#eafcff;text-shadow:0 2px 7px #000d,0 0 8px #79ebff,0 0 13px #c47eff,0 0 17px #ff87d1}
.num[data-tone="link"]{color:#d9f2ff;text-shadow:0 2px 7px #000d,0 0 9px #7fd6ff88}
/* reflected damage: a damage number in the shield's blue, the word small above it (owner, 10 Oct 2026) */
.num[data-tone="reflect"]{color:#d9f2ff;text-shadow:0 2px 7px #000d,0 0 9px #7fd6ff88}
.num[data-tone="reflect"]::before{content:"REFLECT";display:block;font-size:12px;line-height:1;letter-spacing:.01em;text-transform:uppercase;margin-bottom:2px}
/* the two heals that are not obvious wear a word the same way (owner, 10 Oct 2026): EXCEED (past max, the cyan +n) and DRAIN (the
   Necromancer's green pulses); plain heals stay a bare +n */
.num[data-tone="exceed"]::before{content:"EXCEED";display:block;font-size:12px;line-height:1;letter-spacing:.01em;text-transform:uppercase;margin-bottom:2px}
.num[data-tone="mend"][data-necromancer-drain="absorb"]::before{content:"DRAIN";display:block;font-size:12px;line-height:1;letter-spacing:.01em;text-transform:uppercase;margin-bottom:2px}
/* a Sign reaction's word over the blow that set it off (89zzzg; owner, 22 Oct 2026), in the hitting Sign's ink lifted toward white. The
   number keeps a Skill's size and its own slot above the burst it rode in (--react-lift: the burst's ticks under it, 14 px each, then a
   gap), where 74yz1 would have shrunk it to a tick with the next tick on its word */
.num[data-react]{translate:0 calc(0px - var(--react-lift,30px))!important}
.num[data-react]::before{content:attr(data-react);display:block;font-size:12px;line-height:1;letter-spacing:.01em;text-transform:uppercase;margin-bottom:2px;
  color:color-mix(in srgb,var(--react,#d9bd75) 78%,#fff);text-shadow:0 2px 7px #000d,0 0 8px color-mix(in srgb,var(--react,#d9bd75) 60%,transparent)}
/* Khione's Frostbite is one number a foe a round (owner, 11 Oct 2026): the DOT tick in the frost tint, its word small above it */
.num[data-dot="frostbite"]::before{content:"FROSTBITE";display:block;font-size:12px;line-height:1;letter-spacing:.01em;text-transform:uppercase;margin-bottom:2px}
.num[data-tone="buff"]{color:var(--blue)}
.num[data-tone="debuff"]{color:#f08b98;text-shadow:0 2px 7px #000d,0 0 7px #cf455455}

#view[data-over] .hint{color:var(--gold)}

/* ---------- what a player has ---------- */
.tally{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s1) var(--s1);row-gap:var(--s2);
  padding:var(--s2);border:1px solid var(--panel-line);border-radius:var(--r-panel);
  background:var(--panel);}
/* Three across, two rows, six facts. It was four across, and a fifth did not fit - at 390px a fifth of
   the row is 70px and Spriteshard ran straight into Energy. Three gives every cell room for a second
   line under its number, which is what each of them now has: what the energy is doing, how many heroes
   are finished, how far the rank has come, what the Loom could raise, how many relics are being worn.
   Six is deliberate - it fills both rows, so nothing ever has a hole in it. */
.tell{display:grid;gap:2px;justify-items:center;text-align:center;min-width:0}
.tell span{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);line-height:1}
.shard{width:1.05em;height:1.05em;display:inline-block;vertical-align:-.17em;margin-right:.34em;flex:0 0 auto}
.tell strong{font-size:15px;font-weight:600;line-height:1.1;font-variant-numeric:tabular-nums;color:var(--gold)}
.tell small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted);line-height:1.2}

/* the daily three */
.deeds{display:grid;gap:var(--s1);padding:var(--s2);border:1px solid var(--panel-line);
  border-radius:var(--r-panel);background:var(--panel);}
.deed{display:grid;grid-template-columns:1fr auto;gap:var(--s2);align-items:center;min-height:var(--control)}
.deed-say{display:grid;gap:2px;min-width:0}
.deed-say b{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;
  text-transform:uppercase;line-height:1.2;overflow-wrap:anywhere}
.deed-say small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted);font-variant-numeric:tabular-nums}
.deed .hp{width:76px}
.deed[data-done] .deed-say b{color:var(--gold)}
.deed[data-paid]{opacity:.5}

/* the ground: nine fields to an area */
.nodes{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s2);flex:0 0 auto}
.node{display:grid;gap:2px;place-items:center;min-height:56px;padding:var(--s2) var(--s1);
  border:1px solid var(--line);border-radius:var(--r-tile);background:color-mix(in srgb,#090c14 var(--a-glass),transparent);color:#c9cbd4;
  transition:border-color .2s ease,background-color .2s ease,transform .14s var(--ease)}
.node:active{transform:scale(.965)}
.node span{font-family:var(--serif);font-size:19px;line-height:1;font-variant-numeric:tabular-nums}
.node small{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);line-height:1.15}
/* A number alone tells a new player nothing. Every figure on a field says what it is. */
.node .what{color:#c9cbd4;font-weight:600}
.node[data-done] .what,.node[data-here] .what{color:inherit}
.node[data-done]{border-color:color-mix(in srgb,var(--green) 40%,transparent);color:var(--green)}
.node[data-here]{border-color:color-mix(in srgb,var(--gold) 55%,transparent);color:var(--gold);
  background:linear-gradient(180deg,color-mix(in srgb,var(--gold) var(--a-wash-on),transparent),color-mix(in srgb,var(--gold) var(--a-wash),transparent)),var(--panel)}
/* what the ground wants, and whether you have it. Colour is the signal; the number is the detail. */
.node .need{font-size:var(--t-micro);font-weight:600;font-variant-numeric:tabular-nums;line-height:1;
  color:var(--muted);letter-spacing:.01em}
.node[data-read="ready"] .need{color:var(--green)}
.node[data-read="near"] .need{color:var(--gold)}
.node[data-read="under"] .need{color:#b4707a}
.node:disabled{opacity:.3;transform:none}
.node:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* A pill that is also a control: the leader is a fact about the band, so it sits with the other two,
   but it is the only one you can press. */
button.pill{font:inherit;font-size:var(--t-micro);letter-spacing:.01em;
  text-transform:uppercase;cursor:pointer;max-width:calc(var(--fvw,1vw) * 52);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* the pill says what it IS before it says what it holds, or it reads as a label rather than a control */
button.pill.lead i{font-style:normal;color:var(--muted);margin-right:.5em;letter-spacing:.01em}
button.pill.lead[data-r="4"] i{color:color-mix(in srgb,var(--loom) 62%,var(--muted))}
button.pill[data-picking]{border-color:var(--gold);color:var(--gold-hi);
  background:linear-gradient(180deg,color-mix(in srgb,var(--gold) var(--a-wash-on),transparent),color-mix(in srgb,var(--gold) var(--a-wash),transparent)),var(--panel);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--gold) 36%,transparent),0 0 12px color-mix(in srgb,var(--gold) 22%,transparent)}
button.pill:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ---------- the ultimate ----------
   Its own surface over the field, above the fighters and below the head. Smooth light, not pixels:
   the one thing in the game that does not pretend to be made of voxels. */
.ultfx{position:absolute;inset:0;z-index:6;pointer-events:none;width:100%;height:100%;
  image-rendering:auto;mix-blend-mode:screen}
.ultname{
  position:absolute;left:50%;top:44%;transform:translate(-50%,-50%);z-index:7;pointer-events:none;
  display:grid;justify-items:center;gap:var(--s1);width:min(92%,460px);text-align:center;
  animation:ultname 1.75s var(--ease) both;
}
.ultname small{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;
  color:var(--muted);line-height:1}
.ultname b{font-family:var(--serif);font-weight:600;font-size:clamp(25px,7.6vw,40px);line-height:1.02;
  color:var(--ink);text-wrap:balance;
  text-shadow:0 0 26px var(--tint,#d9bd75),0 0 70px var(--tint,#d9bd75),0 3px 18px #000a}
.ultname i{font-style:normal;font-size:var(--t-label);letter-spacing:.01em;
  text-transform:uppercase;color:color-mix(in srgb,var(--tint,#d9bd75) 78%,var(--ink))}
@keyframes ultname{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.86)}
  16%{opacity:1;transform:translate(-50%,-50%) scale(1.04)}
  26%{transform:translate(-50%,-50%) scale(1)}
  78%{opacity:1}
  100%{opacity:0;transform:translate(-50%,-52%) scale(1.02)}
}
/* The Ultimate reads as a cast: the same name, moved to the top with a bar under it that fills to the
   rupture, gone the moment the screen breaks so the aftermath belongs to the foes and the numbers.
   While it runs the chrome steps back - nothing of the HUD is painted over by rays or shards. */
.ultname[data-cast]{top:13%;transform:translate(-50%,0);gap:6px;animation-name:ultcast}
.ultname[data-cast] b{font-size:clamp(22px,6.4vw,32px)}
.ultname .ultcast{width:min(72%,300px);height:2px;border-radius:1px;background:color-mix(in srgb,#ffffff var(--a-wash-on),transparent);overflow:hidden;margin-top:4px}
.ultname .ultcast>span{display:block;height:100%;background:var(--tint,#d9bd75);box-shadow:0 0 10px var(--tint,#d9bd75);
  transform-origin:left;transform:scaleX(0);animation:ultcastfill 2560ms linear both}
@keyframes ultcastfill{to{transform:scaleX(1)}}
/* Opacity only, and gone by 1.76 s: a layer that scales or fades across the rupture frame is a
   repaint on the one frame the ceremony is timed to. The bar fills to the same moment. */
@keyframes ultcast{
  0%{opacity:0;transform:translate(-50%,-10px)}
  4%{opacity:1;transform:translate(-50%,0)}
  57%{opacity:1;transform:translate(-50%,0)}
  61%{opacity:0;transform:translate(-50%,0)}
  100%{opacity:0;transform:translate(-50%,0)}
}
body[data-ult] .head,body[data-ult] .dock,body[data-ult] [data-part=deeds]{opacity:.14;transition:opacity .28s var(--ease)}

/* ---------- the Sky ----------
   A record, so it reads like one: no controls, nothing pressable, one card a name. */
/* The record is the only thing on this screen that scrolls, and it stops at the dock. Bounded by a
   class rather than :has(.sky-list) - the rule has to hold on whatever browser this is opened in, and
   a layout that depends on a selector to not run off the bottom of the phone is a layout with a
   condition on it. */
.sky-body{flex:1 1 auto;min-height:0;display:grid}
.sky-list{display:grid;gap:var(--s2);align-content:start;overflow-y:auto;min-height:0;
  -webkit-overflow-scrolling:touch;
  /* It ends by fading rather than by being cut. The padding is the same height as the fade, so when you
     reach the bottom the soft edge falls on empty space and the last card is whole. */
  padding-bottom:52px;
  -webkit-mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 52px),transparent 100%);
  mask-image:linear-gradient(180deg,#000 0,#000 calc(100% - 52px),transparent 100%);}
/* No accent down the edge. The rarity is already in the words - "Rare Berserker" - and a record reads
   as a record, not as a card with a status stripe on it. */
.gonecard{display:grid;gap:2px;padding:var(--s3);border:1px solid var(--panel-line);
  border-radius:var(--r-panel);background:var(--panel)}
.gonecard b{font-family:var(--serif);font-weight:600;font-size:19px;line-height:1.1;color:var(--ink)}
.gonecard small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted);line-height:1.4}
/* not .tally: that is the four-fact card on Home, and a name collision in one stylesheet is a box
   around a line of text that nobody asked for */
.gonecard .did{color:#c9cbd4}
/* the epitaph (78b): the lore's saying voice (.lore-say p), without its gold edge - a record has no status stripe */
.gonecard .epitaph{margin:4px 0 2px;font-family:var(--serif);font-style:italic;font-size:14px;line-height:1.35;letter-spacing:.01em;color:#efe4c8}
.gonecard .seed{opacity:.55}

/* Difficulty switches keep their own identity even when Nightmare is layered over them. */
button.pill.nm[data-on]{border-color:color-mix(in srgb,var(--mode-pill,var(--gold)) 58%,transparent);color:var(--mode-pill-hi,var(--gold-hi));
  background:linear-gradient(180deg,color-mix(in srgb,var(--mode-pill,var(--gold)) var(--a-wash-on),transparent),color-mix(in srgb,var(--mode-pill,var(--gold)) var(--a-wash),transparent)),var(--panel)}
button.pill[data-act="hard"]{--mode-pill:#e6a271;--mode-pill-hi:#f5c08e}
button.pill[data-act="blood"]{--mode-pill:#e5414f;--mode-pill-hi:#ff7683}
button.pill[data-act="nm"]{--mode-pill:#e07f8c;--mode-pill-hi:#f3a0ab}
button.pill[data-act="hell"]{--mode-pill:#98847f;--mode-pill-hi:#c0aaa4}
button.pill[data-act="hell"][data-on]{background:linear-gradient(180deg,color-mix(in srgb,#0b0a0c var(--a-glass),transparent),color-mix(in srgb,#030304 var(--a-glass),transparent));box-shadow:inset 0 0 18px #0009}

/* what climbs when a hero is Returned: its own pixels, on their own surface, above the field */
.skyward{position:absolute;inset:0;z-index:8;pointer-events:none;width:100%;height:100%;image-rendering:pixelated}
.fighter[data-gone]{opacity:0;transition:opacity .18s linear}

/* ---------- the camera layer ----------
   Hit stop: everything inside the field holds still for 45-85ms at contact. Pausing the animations is
   the whole trick - nothing is rescheduled, nothing is re-rendered, the frame simply stops being
   redrawn for a moment and the brain reads the pause as weight. */
.battle[data-stop] *{animation-play-state:paused!important}

/* the screen moves ALONG the blow, not around it */
.battle{will-change:transform}
.battle[data-shake]{animation:shake .26s cubic-bezier(.36,.07,.19,.97) both}
.battle[data-shake="hard"]{animation-duration:.3s}
@keyframes shake{
  0%{transform:none}
  18%{transform:translate(calc(var(--sx,0px) * 1),calc(var(--sy,0px) * 1))}
  42%{transform:translate(calc(var(--sx,0px) * -.62),calc(var(--sy,0px) * -.62))}
  68%{transform:translate(calc(var(--sx,0px) * .34),calc(var(--sy,0px) * .34))}
  100%{transform:none}
}

/* ---------- the leader ----------
   One seat lends the band its job's skill. The row says what that is worth before the fight, and the
   seat itself wears the mark, because a rule you cannot see on the board is a rule nobody uses. */
/* ---------- the kit ----------
   A list of what you own inside the sheet, and the nine seats when one is chosen. */
/* Two to a row, three lines to a card, and it scrolls. A single column of thirty relics is a column
   you scroll past rather than read, and the sheet only has so much height to give it. */
.kitlist{display:grid;grid-template-columns:1fr 1fr;gap:var(--s1);width:100%;
  /* as much of the sheet as it can have: a list that shows five rows of a long collection and moves
     seventy pixels reads as a list that does not scroll */
  /* Leaves room for the sheet's own close button. At .54/430 a full kit pushed "Got it" 29px past the
     card's bottom edge, and .kitlist contains its overscroll and takes touch-action:pan-y, so a drag
     inside the list never scrolls the card to reach it: the way out of the sheet was unreachable with
     101 relics in the list. Measured on a 390x800 phone - the card needed 48px it did not have. */
  max-height:min(calc(var(--vh,100dvh) * .46),370px);overflow-y:auto;-webkit-overflow-scrolling:touch;
  overscroll-behavior:contain;touch-action:pan-y;
  padding:var(--s1) 2px 0;border-top:1px solid var(--panel-line)}
/* The kit sheet and the hero's Wear pane are the same list of the same things, so they are one card.
   Three fixed lines and every one of them truncates: the Wear pane used to let two growing lines wrap,
   which printed the tail of "on this seat" outside the card and onto the row beneath it. */
.kitlist .relic,.relics .relic{display:grid;gap:1px;align-content:start;min-width:0;text-align:left;min-height:58px}
/* rows sized to their text, never squeezed: in a height-limited scrolling grid a card's clipped lines have no minimum height,
   so the card was crushed to 67px and every line lost its top or bottom (owner, 28 Sep 2026) */
.kitlist,.relics{grid-auto-rows:max-content;padding-bottom:1px}   /* the last row sits flush with the scroller, never a fraction under it (kit-reach) */
.kitlist .relic,.relics .relic{grid-auto-rows:max-content;height:auto}
.kitlist .relic>*,.relics .relic>*{min-height:1.45em}
.kitlist .relic b,.kitlist .relic small,.relics .relic b,.relics .relic small{min-width:0;max-width:100%;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;line-height:1.45;padding-block:1px}   /* the ellipsis hides overflow: a line needs room for its whole glyphs (owner, 28 Sep 2026: the kit's lines were cut through) */
.kitlist .relic .where,.relics .relic .where{color:var(--muted)}
.kitlist .relic[data-on] .where,.relics .relic[data-on] .where{color:var(--gold)}
.seatpick{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s1);width:100%}
.seatpick[data-placement]{border-radius:var(--r-panel);
  box-shadow:0 0 0 1px color-mix(in srgb,var(--gold) 32%,transparent),inset 0 0 20px #0004}
.seatbtn{display:grid;gap:1px;justify-items:start;text-align:left;padding:var(--s2) var(--s1);min-width:0;
  border:1px solid var(--line);border-radius:var(--r-control);background:color-mix(in srgb,#090c14 var(--a-glass),transparent);color:#c9cbd4}
.seatbtn span{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted);line-height:1}
.seatbtn b{font-size:var(--t-label);font-weight:600;line-height:1.2;min-width:0;max-width:100%;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.seatbtn small{font-size:var(--t-micro);color:var(--muted);line-height:1.2;min-width:0;max-width:100%;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.seatbtn[data-state="available"]{border-color:#ffffff32;background:color-mix(in srgb,#0b0e17 var(--a-glass),transparent)}
.seatbtn[data-state="selected"],.seatbtn[data-on]{border-color:var(--gold);
  background:linear-gradient(180deg,color-mix(in srgb,var(--gold) var(--a-wash-on),transparent),color-mix(in srgb,var(--gold) var(--a-wash),transparent)),var(--panel);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--gold) 36%,transparent),0 0 12px color-mix(in srgb,var(--gold) 16%,transparent)}
.seatbtn[data-state="selected"] b,.seatbtn[data-on] b{color:var(--gold)}
.seatbtn[data-state="unavailable"]{opacity:.38;filter:saturate(.45)}
.seatpick[data-placement] .seatbtn[data-state="available"]:active{border-color:var(--gold);transform:scale(.98)}
.seatpick[data-placement="rune"] .seatbtn[data-fit-state="awake"][data-state="available"]{
  border-color:color-mix(in srgb,var(--green) 62%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--green) 24%,transparent)
}
.seatpick[data-placement="rune"] .seatbtn[data-fit-state="awake"] small{color:color-mix(in srgb,var(--green) 74%,#d9dce6)}
.seatpick[data-placement="rune"] .seatbtn[data-fit-state="asleep"][data-state="available"]{opacity:.72}
.seatbtn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* A seat cell stands its hero up: number, the avatar Home's nine use, sign and name, then the line. */
.seatbtn{justify-items:center;text-align:center;gap:2px;padding:var(--s2) 4px}
.seatbtn .pip{width:44px;height:44px;margin:0}
.seatbtn .pip .shot{width:100%}
.seatbtn .pip[data-empty]{border:1px dashed var(--line-soft);border-radius:6px}
.seatbtn b{display:inline-flex;align-items:center;gap:4px;max-width:100%}
.seatbtn b .sign{flex:0 0 auto}

/* moving heroes about: the lifted card is held above the board, the seat under the finger says it
   will take it. Both are transforms, so nothing reflows while a finger is down. */
.seat[data-lifted]{opacity:.5;transform:scale(.94)}
.seat[data-over]{border-color:var(--gold);box-shadow:0 0 0 2px color-mix(in srgb,var(--gold) 40%,transparent)}
.band[data-moving] .seat{cursor:grab}

/* what a seat carries: one stone in the corner opposite the leader's badge, in the relic's own colour.
   A seat holds one relic, so one mark says everything there is to say at a glance. */
.worn{position:absolute;top:5px;right:5px;z-index:3;width:9px;height:9px;border-radius:2px;
  background:var(--rarity,var(--grey));box-shadow:0 0 0 1px #07091299, 0 0 8px -1px var(--rarity,var(--grey))}
.worn[data-rarity="0"]{--rarity:var(--grey)}
.worn[data-rarity="1"]{--rarity:var(--green)}
.worn[data-rarity="2"]{--rarity:var(--blue)}
.worn[data-rarity="3"]{--rarity:var(--violet)}
.worn[data-rarity="4"]{--rarity:var(--loom)}
/* the rune socket: beside the relic, not opposite it. A letter for the word, in the mark's colour where
   it leaves one, gold where it does not; dark while it sleeps, because a rune below Legendary is a
   promise, not a power.

   It sat in the seat's LEFT corner first, which is where the seat number lives - measured on a real
   band, seat one read "E" and seat three read "B", and the picker that says "Seat 1" had covered the
   only thing that says which seat that is. The two badges share the right corner instead, the relic
   outermost because it was there first. */
.tile .rune{position:absolute;top:2px;right:17px;z-index:3;width:15px;height:15px;border-radius:50%;
  font:700 9px/15px var(--serif);text-align:center;color:#070912;background:var(--rune,var(--gold));
  box-shadow:0 0 0 1px #07091299,0 0 8px -1px var(--rune,var(--gold))}
.tile .rune[data-lit="0"]{opacity:.38;filter:saturate(.3)}
.tile .rune[data-key="frost"]{--rune:#74c2cc}
.tile .rune[data-key="shock"]{--rune:#e0c84a}
.tile .rune[data-key="sunder"]{--rune:#b08968}
.tile .rune[data-key="bleed"]{--rune:#c2493f}
.tile .rune[data-key="burn"]{--rune:#e08a52}
.tile .rune[data-key="ward"]{--rune:var(--green)}
.tile .rune[data-key="preempt"]{--rune:#9cc8f0}

/* The seat that leads, and the seats that could while you are choosing. The badge is a real element,
   not a ::after - the tile already spends ::after on its caption scrim, and a second one inherits that
   box rather than replacing it. */
.seat[data-leads]{border-color:color-mix(in srgb,var(--gold) 80%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--gold) 40%,transparent)}
/* The seat's number, worn by the tile. A chip in the crown's own language rather than a border tint,
   because every tier-three tile already has a gold border and a tint on top of it was invisible - on
   a finished roster seven seated and two unseated looked identical. The leader's seat is the gold one. */
.seatno{position:absolute;top:4px;left:4px;z-index:3;
  font-size:8px;line-height:1;letter-spacing:.01em;font-weight:600;text-transform:uppercase;
  color:var(--gold);background:color-mix(in srgb,#0b0f1a var(--a-glass),transparent);border:1px solid color-mix(in srgb,var(--gold) 45%,transparent);
  padding:2px 4px;border-radius:3px;white-space:nowrap}
.seatno[data-lead]{color:#1a1408;background:var(--gold);border-color:var(--gold)}
/* role first (70a): the first thing read on every card and seat - its role's glyph in its role's tone, top left; a seat's number
   steps right of it. Tones, not primaries: steel, sage, terracotta, heather, khaki, teal */
.rolemark{--role:#c9c3b4;display:inline-grid;place-items:center;flex:0 0 auto;color:var(--role)}
.rolemark svg{width:100%;height:100%;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.rolemark[data-role="tank"]{--role:#8ea7d1}
.rolemark[data-role="healer"]{--role:#8fc6a0}
.rolemark[data-role="melee"]{--role:#d6926f}
.rolemark[data-role="rogue"]{--role:#b59ad0}
.rolemark[data-role="ranged"]{--role:#cfc47f}
.rolemark[data-role="caster"]{--role:#79c1cf}
.tile .rolemark{position:absolute;top:4px;left:4px;z-index:3;width:22px;height:22px;padding:4px;box-sizing:border-box;border-radius:6px;
  background:color-mix(in srgb,color-mix(in srgb,var(--role) 22%,#0b0f1a) var(--a-scrim),transparent);border:1px solid color-mix(in srgb,var(--role) 75%,transparent)}
.tile .rolemark svg{stroke-width:2}
.tile .rolemark ~ .seatno{left:29px;top:8px}
.battle-mechanic .rolemark{width:10px;height:10px}
.crown{position:absolute;top:4px;left:4px;z-index:3;
  font-size:8px;line-height:1;letter-spacing:.01em;font-weight:600;text-transform:uppercase;
  color:#1a1408;background:var(--gold);padding:2px 4px;border-radius:3px}
.seat[data-canlead]{border-color:color-mix(in srgb,var(--green) 66%,transparent)}

/* A scroller the app owns draws no bar. These are panels inside a card - a sheet, the relics, the
   result's list, the Sky - where the platform's own bar lands ON the card's rounded edge and reads as
   a seam in the panel rather than a control. Everything in them scrolls under a finger, and the last
   row is always cut mid-height, which is what says there is more. The page itself keeps its bar. */
.sky-list, .kitlist, .relics, .sheet .card, .verdict .gains { scrollbar-width: none; }
/* and the edge softens only where there is more under it. Both stops start at zero, so a list that
   fits shows no fade at all and a list scrolled to its end shows none at the bottom - which is the
   whole reason this is a pair of properties rather than a fixed gradient. */
.sky-list, .kitlist, .relics, .verdict .gains {
  --fade-top: 0; --fade-bot: 0;
  -webkit-mask-image: linear-gradient(to bottom, #0000 0,
    #000 calc(var(--fade-top) * 18px), #000 calc(100% - var(--fade-bot) * 18px), #0000 100%);
  mask-image: linear-gradient(to bottom, #0000 0,
    #000 calc(var(--fade-top) * 18px), #000 calc(100% - var(--fade-bot) * 18px), #0000 100%);
}
.sky-list::-webkit-scrollbar, .kitlist::-webkit-scrollbar, .relics::-webkit-scrollbar,
.sheet .card::-webkit-scrollbar, .verdict .gains::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* ---------- the (i) sheet ----------
   What a screen is for, over the screen itself, dismissed by the one button or by the ground around it. */
.sheet{position:absolute;inset:0;z-index:60;display:grid;place-items:center;padding:var(--s4);
  background:color-mix(in srgb,#04060c var(--a-scrim),transparent);animation:verdict-in .26s var(--ease) both}
.sheet[data-going]{animation:verdict-out .26s var(--ease) both}
/* a sheet lifted into the top layer, above the skill effects (89zw): the popover's own box is undone and it covers the view as
   before, at #view's rectangle in frame pixels */
.sheet[popover]{position:fixed;inset:auto;left:var(--sheet-x,0);top:var(--sheet-y,0);width:var(--sheet-w,100%);height:var(--sheet-h,100%);
  margin:0;border:0;max-width:none;max-height:none;overflow:visible;color:inherit}
.sheet[popover]::backdrop{background:transparent}
/* an Anchor's Hearts (89zzh): laid over the Anchor's front in its own cell, so the Anchor keeps its whole column and its size;
   drawn after it (the figure grid paints in card order), below its HP strip (z 4) */
.battle [data-part=far]:has(> .fighter[data-heart]){position:relative}
.battle [data-part=far] > .fighter[data-heart]{position:absolute;left:0;width:44%;height:27%;z-index:3}
.battle [data-part=far] > .fighter[data-heart][data-heart-index="0"]{top:28%}
.battle [data-part=far] > .fighter[data-heart][data-heart-index="1"]{top:56%}
.battle [data-part=far] > .fighter[data-heart-warm]{position:absolute;left:0;top:28%;width:44%;height:27%;opacity:0;pointer-events:none}
.sheet .card{
  width:min(100%,420px);max-height:calc(var(--vh,100dvh) * .78);overflow-y:auto;display:grid;gap:var(--s2);justify-items:start;
  padding:var(--s4);border:1px solid var(--panel-line);border-radius:var(--r-panel);
  /* A sheet is a MESSAGE the way the toast is: nearly opaque, because it is read over whatever the
     screen was showing. So it kept its own literals and stayed navy while everything around it took
     the difficulty. Same fix as the toast: its own dark per mood, not --gold, which is an accent and
     would wash a surface carrying body text. */
  background:var(--popup);box-shadow:0 30px 90px #000a;
}
.sheet .eyebrow{margin:0;color:var(--muted)}
.sheet h2{margin:0;font-family:var(--serif);font-size:var(--t-display);line-height:1.05;font-weight:400}
.sheet .rows{display:grid;gap:var(--s2);width:100%}
.sheet .rows div{display:grid;gap:2px}
.sheet .rows b{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;
  text-transform:uppercase;color:var(--gold)}
.sheet .rows p{margin:0;font-size:var(--t-body);line-height:1.45;color:#c9cbd4}
.sheet .btn{width:100%}
/* news over Home (80b): its screenshot, the card's width, in the panels' corner */
.sheet .news-art{display:block;width:100%;height:auto;aspect-ratio:2/1;object-fit:cover;border-radius:var(--r-tile)}
/* a sheet too long to show its foot keeps a way out in its top corner, held there while the card scrolls (89zw2, audit 04).
   The holder is a zero-height sticky row; the row's gap is given back by the line after it, so a short sheet is unchanged. */
.sheet .card > .sheet-shut{display:none}
.sheet .card:focus{outline:none}
/* running text (60a): a box that wraps its line, centred and still, while it fits its cap, and runs it through the cap when not -
   the line, a gap, an aria-hidden copy, looping; clipped at the box's edges with a short fade; transform only */
.run-view,.run-track,.run-line{font-style:normal}
.run-view{display:block;flex:1 1 auto;min-width:0;overflow:hidden}
.run-track{display:flex;width:max-content;margin:0 auto}
.run-line{display:flex;flex:0 0 auto;align-items:center;white-space:nowrap}
.run-copy{margin-left:var(--run-gap,18px)}
[data-running] > .run-view{-webkit-mask-image:linear-gradient(90deg,transparent,#000 6px,#000 calc(100% - 6px),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 6px,#000 calc(100% - 6px),transparent)}
[data-running] .run-track{margin:0;animation:run-text var(--run-s,8s) linear infinite;will-change:transform}
@keyframes run-text{to{transform:translate3d(calc(-1 * var(--run-d,0px)),0,0)}}
[data-snug].battle-mechanic{padding-left:2px;padding-right:2px} [data-snug] .run-line{gap:2px}   /* a line a hair over the cap fits snug instead of running (60a) */
/* no WebGL (89zw3): where a card's or a fighter's figure would stand, a quiet plate with its job's name */
/* a card whose figure is still being made (89zg mfGridMount, a figure a frame) - a seat, a card, a fighter with its name and its bar:
   out of sight until it comes with its figure */
[data-fig3d="wait"]{visibility:hidden}
[data-fig3d="off"] .shot[data-plate]{display:grid;place-items:center}
/* the plate wraps its name; a name longer than the cap takes exactly the cap and runs through it (60a), never cut */
.shot > .nogl-plate{display:none}
[data-fig3d="off"] .shot[data-plate] > .nogl-plate{grid-area:1/1;display:flex;max-width:92%;min-width:0;overflow:hidden;white-space:nowrap;
  padding:2px 5px;border:1px solid var(--line);border-radius:var(--r-control);background:color-mix(in srgb,#090c14 var(--a-glass),transparent);color:#8e929f;
  font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase}
.sheet[data-long] .card > .sheet-shut{display:block;position:sticky;top:0;z-index:3;justify-self:end;width:0;height:0}
.sheet[data-long] .card > .sheet-shut + *{margin-top:calc(-1 * var(--s2))}
.sheet[data-long] .card > .sheet-shut ~ .eyebrow,.sheet[data-long] .card > .sheet-shut ~ h2{padding-right:calc(var(--control) + var(--s2) - var(--s4))}
.sheet .sheet-shut > .icon{position:absolute;right:calc(var(--s2) - var(--s4));top:calc(var(--s2) - var(--s4));background:var(--popup-a)}
/* the five numbers that decide a fight, read across rather than down */
.statgrid{display:grid;grid-template-columns:repeat(5,1fr);gap:var(--s1);width:100%}
.statgrid div{display:grid;gap:2px;justify-items:center;text-align:center;
  padding:var(--s1) 2px;border:1px solid var(--panel-line);border-radius:var(--r-control);background:color-mix(in srgb,#090c14 var(--a-glass),transparent)}
.statgrid span{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);line-height:1}
.statgrid b{font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--gold);line-height:1.1}
/* a held press must not start a text selection on the way to the sheet */
[data-seed]{-webkit-touch-callout:none;user-select:none;-webkit-user-select:none}

/* the odds, on the screen where the seed is still in your hand */
.odds{display:grid;gap:4px;width:min(100%,320px);margin-top:var(--s3);
  /* Ten pixels down the sides put LEGENDARY and 14% almost on the border. A panel that holds a table
     of its own needs more room across than a panel that holds a row of buttons, so the sides get s4
     and the ends keep s2 - the card does not grow, the words stop touching it. */
  padding:var(--s2) var(--s4);border:1px solid var(--panel-line);border-radius:var(--r-panel);background:var(--panel)}
.odds .eyebrow{margin:0 0 2px;color:var(--muted);text-align:left}
.odd{display:grid;grid-template-columns:1fr auto;gap:var(--s2) var(--s1);align-items:baseline;
  padding:2px 0;border-bottom:1px solid var(--panel-line)}
.odd:last-of-type{border-bottom:0}
.odd span{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;
  text-transform:uppercase;text-align:left;color:var(--rarity,var(--ink))}
.odd b{font-size:15px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--ink)}
.odd small{grid-column:1/-1;font-size:var(--t-micro);color:var(--muted);text-align:left;
  letter-spacing:.01em;margin-top:-2px}
.odd[data-rarity="2"]{--rarity:var(--blue)}
.odd[data-rarity="3"]{--rarity:var(--violet)}
.odd[data-rarity="4"]{--rarity:var(--loom)}
.odds .note{font-size:var(--t-micro);color:var(--muted);text-align:left;margin-top:2px;line-height:1.3}
/* what a ground pays (77a): the fall's odds table, twice, under the rows; full width in a sheet */
.sheet .pays-odds{width:100%;box-sizing:border-box}
.pays-foot{display:block;margin-top:var(--s2);font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted)}

/* ---------- Spritefall reads at arm's length ----------
   Reported as too small on the phone, and measured at 360x800 it was: every line on this screen except
   the title and the three percentages sat at 9-9.5px, because the hint, the eyebrow, the rarity names,
   the tier/ceiling lines and the footnote all take --t-micro or --t-label. This is the screen a player
   reads while deciding whether to spend, so it gets its own step up here rather than moving the global
   type scale under every other screen in the app. */
body[data-screen="fall"] .throw .hint{font-size:11px}
body[data-screen="fall"] .odds .eyebrow{font-size:10.5px}
body[data-screen="fall"] .odd span{font-size:11px}
body[data-screen="fall"] .odd b{font-size:17px}
body[data-screen="fall"] .odd small{font-size:10.5px}
body[data-screen="fall"] .odds .note{font-size:10.5px}
/* The page's own eyebrow and the one button on it were left behind by the step-up above: measured
   9px and 9.5px while everything beside them moved. */
body[data-screen="fall"] .head .eyebrow{font-size:10.5px}
body[data-screen="fall"] .throw .btn{font-size:11px}

/* ---------- fusing: what can be fed to what ----------
   The rule is strict, the food is destroyed, so the screen answers before the tap rather than after. */
.tile[data-food="no"]{opacity:.22;filter:saturate(.3)}
.tile[data-food="yes"]{border-color:color-mix(in srgb,var(--green) 70%,transparent)}
.tile[data-food="yes"]::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--green) 45%,transparent);
}

/* ---------- Refall: selection must be unmistakable ---------- */
.tile[data-refall-selected]{
  border-color:var(--gold-hi);
  box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--gold) 58%,transparent),0 0 18px color-mix(in srgb,var(--gold) 18%,transparent)
}
/* Refall never changes hero rendering. Locked/seated heroes use the exact same tile, caption scrim,
   shot and canvas styles as Open; the IN BAND badge alone communicates that they cannot be spent. */
.tile[data-refall-locked]{cursor:not-allowed}
.refall-mark{
  position:absolute;top:5px;right:5px;z-index:4;display:flex;align-items:center;gap:4px;
  min-height:18px;padding:2px 5px;border-radius:5px;border:1px solid color-mix(in srgb,var(--gold) 60%,transparent);
  background:color-mix(in srgb,#0b0f1a var(--a-glass),transparent);color:var(--gold-hi);font-size:8px;line-height:1;letter-spacing:.01em;text-transform:uppercase;
  box-shadow:0 4px 12px #0008;pointer-events:none
}
.refall-mark b{font-size:9px;font-weight:700;color:#171106;background:var(--gold-hi);border-radius:3px;padding:2px 4px}
.refall-mark small{font:700 8px/1 var(--sans);letter-spacing:.01em;color:inherit}
.refall-mark.locked{color:#c3c6d0;border-color:#ffffff2b;background:color-mix(in srgb,#0b0f1a var(--a-glass),transparent);box-shadow:none}
.refall-tools{display:grid;gap:var(--s1);padding-top:2px}
.refall-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s2);padding:1px 2px}
.refall-head>div{display:grid;gap:1px;min-width:0}
.refall-head b{font-size:var(--t-label);letter-spacing:.01em;text-transform:uppercase;color:#e8e2d7}
.refall-head small{font-size:var(--t-micro);line-height:1.25;color:var(--muted)}
.refall-head>strong{flex:0 0 auto;font:700 14px/1 var(--sans);font-variant-numeric:tabular-nums;color:var(--gold-hi);padding-top:2px}
.refall-auto .btn{min-width:0}
.refall-progress{height:5px;overflow:hidden;border-radius:5px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);border:1px solid #ffffff12}
.refall-progress>i{display:block;height:100%;width:calc(var(--refall-p,0) * 100%);background:linear-gradient(90deg,var(--gold),var(--gold-hi));transition:width .18s var(--ease)}
.refall-tools>.hint{line-height:1.25;text-align:left}

/* Fuse uses the same focused control language as Refall once a keeper is chosen. */
.fuse-tools{display:grid;gap:var(--s1);padding-top:2px}
.fuse-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--s2);padding:1px 2px}
.fuse-head>div{display:grid;gap:1px;min-width:0}
.fuse-head b{font-size:var(--t-label);letter-spacing:.01em;text-transform:uppercase;color:#e8e2d7}
.fuse-head small{font-size:var(--t-micro);line-height:1.25;color:var(--muted)}
.fuse-tools>.hint{line-height:1.25;text-align:left}
/* the seated toggle sits in the fuse head's empty right side, so it costs the grid no row (audit 01) */
.fuse-head>.seg{flex:0 0 auto;display:grid}
.fuse-head>.seg button{padding:0 var(--s2);white-space:nowrap}
/* the fuse confirm: keeper and food side by side, the food's card marked as the one that goes */
.fuse-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s2);width:100%}
.fuse-side{display:grid;align-content:start;gap:2px;min-width:0;padding:var(--s2);border:1px solid var(--line);border-radius:var(--r-control)}
.fuse-side[data-gone]{border-color:color-mix(in srgb,#e2463f 45%,transparent)}
.fuse-side small{font:600 var(--t-micro)/1.2 var(--sans);letter-spacing:.01em;text-transform:uppercase;color:var(--muted)}
.fuse-side[data-gone] small{color:#f0a49d}
.fuse-side b{font-size:var(--t-label);font-weight:700;letter-spacing:.01em;overflow-wrap:anywhere}
.fuse-side p{margin:0;font-size:var(--t-micro);line-height:1.3;letter-spacing:.01em;color:#c9cbd4;display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.fuse-side .fuse-seated{color:var(--gold-hi)}
/* the roster filter sheet (audit 13): labelled chip groups, a live count and Reset; chips stay chip-sized */
.rf{display:grid;gap:var(--s2);width:100%}
.rf-group{display:grid;gap:4px}
.rf-group>b{font:600 var(--t-micro)/1.2 var(--sans);letter-spacing:.01em;text-transform:uppercase;color:var(--muted)}
.rf-group>small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted)}
.rf .seg button{display:flex;align-items:center;justify-content:center;gap:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rf-foot{display:flex;align-items:center;justify-content:space-between;gap:var(--s2)}
.rf-foot strong{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;font-variant-numeric:tabular-nums;white-space:nowrap}
.rf-foot .btn{flex:0 0 auto;width:auto;padding-inline:var(--s4)}
/* an empty filter says so inside the nine blank cells, never in a row of its own */
.rf-empty{position:absolute;inset:0;z-index:6;display:grid;place-content:center;justify-items:center;gap:var(--s2);text-align:center;padding:var(--s3)}
.rf-empty p{margin:0;font-size:var(--t-body,12px);letter-spacing:.01em;color:#d9dbe2}
.tile[data-food="yes"]{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--green) 38%,transparent),0 0 12px color-mix(in srgb,var(--green) 10%,transparent)}
/* a hero standing at its own ceiling is the one worth choosing first */
.tile[data-ready]{border-color:color-mix(in srgb,var(--gold) 62%,transparent)}
/* The Loom could raise this one right now. A corner mark rather than a border, because a border is
   already how "ready to fuse" speaks and the two can be true of different heroes on the same screen. */
.tile[data-loom]::after{
  content:"";position:absolute;top:6px;right:6px;width:7px;height:7px;border-radius:50%;
  background:var(--gold);box-shadow:0 0 0 2px #060910cc,0 0 8px 1px color-mix(in srgb,var(--gold) 70%,transparent);
  pointer-events:none;z-index:2;
}

/* an empty seat in the frame: the same box as a filled one, so nothing reflows when it is taken */
.seat[data-empty]{border-style:dashed;border-color:#ffffff1c;background:
  repeating-linear-gradient(135deg,color-mix(in srgb,#ffffff var(--a-wash),transparent) 0 6px,transparent 6px 12px)}
.seat[data-empty] figcaption{color:var(--muted)}

/* the ground takes the room the rest leaves rather than a fixed nine rows of its own */
#view .nodes{flex:1 1 auto;min-height:0;grid-auto-rows:1fr}
/* the ground (owner, 30 Sep 2026: "Everything is overlay on top of each other ... rearrange the page spacing"): the nine fields still
   grow into the room the page has, but never shrink below their own height - a tall ground scrolls rather than stacking its rows */
.campaign-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--s3);overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none}
.campaign-body::-webkit-scrollbar{display:none}
#view .campaign-body .nodes{min-height:min-content;grid-auto-rows:minmax(min-content,1fr)}

/* ---------- a fall ----------
   Each shape comes down out of the dark a moment after the one before it, and settles when it is
   touched. Transform and filter only: the tiles themselves are glass. */
/* The ceremony, in four beats. A Spritefall is the one moment the game asks you to wait, so nothing
   in it snaps and no two things happen at once that could happen in order.

     1  an empty stage and one button, so the fall is something you DO rather than something that has
        already happened by the time the screen arrives
     2  the cards come down out of the dark, one at a time, beginning at the bottom right
     3  a silhouette dissolves - it does not swap
     4  and only then does the hero itself come down, out of the same sky the card did
 */
@keyframes descend{
  from{transform:translateY(-62vh) scale(.90);opacity:0;filter:blur(3px)}
  50%{opacity:1}
  74%{transform:translateY(7px) scale(1.03);filter:blur(0)}
  88%{transform:translateY(-2px) scale(.995)}
  to{transform:none;opacity:1;filter:blur(0)}
}
/* what the landing throws off, in the colour of how intact the seed was */
@keyframes land{
  from{box-shadow:0 0 0 0 color-mix(in srgb,var(--rarity) 70%,transparent)}
  to{box-shadow:0 0 40px 14px transparent}
}
/* the grain letting go */
@keyframes dissolve{
  from{opacity:1;transform:scale(1)}
  45%{opacity:.9}
  to{opacity:0;transform:scale(1.14)}
}
/* and the hero itself, arriving the way the card did */
@keyframes arrive{
  from{opacity:0;transform:translateY(-44vh) scale(.92)}
  54%{opacity:1}
  80%{transform:translateY(5px) scale(1.02)}
  to{opacity:1;transform:none}
}
.fell[data-fell]{
  animation:descend 1.05s var(--ease) both, land .8s var(--ease) both;
  animation-delay:calc(var(--i,0) * 240ms), calc(var(--i,0) * 240ms + .74s);
}
/* the card's box carries its minifigure (89zg), so the box is what drops, fades and whitens away */
.fell[data-dissolving] .shot{animation:dissolve .34s var(--ease) both}
.fell[data-arriving] .shot{animation:arrive .66s var(--ease) both}
/* a shape is not a control any more: nothing is revealed by tapping it */
.fell[data-veil] .seatskill{color:var(--rarity);letter-spacing:.01em}
/* ---------- the Spritefall rework (75-fall turn and skip, 75b the moment; owner, 17 Oct 2026) ----------
   A card's frame takes its tier colour at its own turn; Skip stands the rest at once - the cards still falling land where they rest;
   a job never held says New; the best of the fall is outlined; a Tier 3's card lifts to the middle by its own translate and scale
   (its transform belongs to the fall's animation). */
.fell{transition:border-color .24s var(--ease),background-color .2s ease,translate .42s var(--ease),scale .42s var(--ease)}
.grid[data-skip] .fell[data-fell]{animation:none}
.fell[data-instant] .shot{animation:none}
.fell[data-new] .seatskill::after{content:" · New";color:var(--loom)}
.fell[data-best]{outline:1px solid color-mix(in srgb,var(--rarity) 60%,transparent);outline-offset:3px}
/* a Tier 3's card lifts to the middle for its moment ("true"), and glides back to its place over the scrim as the moment goes ("back") */
.fell[data-moment="true"]{translate:var(--mx,0) var(--my,0);scale:var(--ms,1);background-color:transparent;border-color:transparent}
.fell[data-moment="true"] figcaption,.fell[data-moment="true"] .seatskill{visibility:hidden}   /* its name stands under it, in the moment's own words */
/* the rest of the fall stands aside while the moments are up: hidden, so the figure grid leaves its figures undrawn (a batched figure
   keeps its own opaque material, so it never dims with its card) - and comes back as the scrim lifts after the last */
#view[data-fall-moment] .fell:not([data-moment]){visibility:hidden}
/* and the moments stand over the screen's own words and buttons (the head 5, the action panel 8), each hero's figure over them: the
   figures' canvas lifts above the scrim while it is up (the dock, outside the view, stays where it is) */
#view[data-fall-moment] > .figure-grid{z-index:41}

/* ---------- The Sky Opens (75c): the Spritefall's ceremony - on the compositor, transform and opacity only ----------
   The throw lays the nine out hidden (#view[data-sky="on"], so the figure grid leaves them undrawn) while their figures load under
   the sky; each appears as its meteor strikes, in its tier. No card comes down again: the meteor was the fall. */
#view[data-sky] .fell[data-fell]{animation:none}
#view[data-sky="on"] .fell:not([data-struck]){visibility:hidden}
#view[data-sky] .fell[data-struck]{animation:fs-card .42s var(--ease) both}
@keyframes fs-card{from{opacity:0;transform:scale(.95)}}
.fall-sky{position:fixed;inset:0;z-index:45;overflow:hidden;contain:strict;-webkit-tap-highlight-color:transparent}
.fall-sky i,.fall-sky .fs-back,.fall-sky .fs-warps,.fall-sky .fs-meteors,.fall-sky .fs-strikes{position:absolute;display:block;pointer-events:none}
.fall-sky .fs-back,.fall-sky .fs-warps,.fall-sky .fs-meteors,.fall-sky .fs-strikes,.fs-night,.fs-sky,.fs-warp,.fs-aurora{inset:0}
.fall-sky svg{display:block;overflow:visible}
/* the back: night, sky, the eye, the warps and the aurora - one group the land fades off the cards */
.fs-back{transition:opacity .5s var(--ease)}
.fall-sky[data-beat="land"] .fs-back{opacity:.5}          /* half the night while meteors still fall: the last (the best) strikes against it */
.fall-sky[data-beat="clear"] .fs-back{opacity:0}
.fs-night{background:linear-gradient(180deg,#070912f7 0%,#070912e8 46%,#070912a6 100%)}
.fs-sky{will-change:transform,opacity}
.fs-eye{left:0;right:0;top:calc(var(--vy,152px) - 110px);height:220px;transform-origin:50% 110px;will-change:transform,opacity}
/* laid out ahead on the stage, hidden ("ready"); its entrance plays from the throw */
.fall-sky[data-beat="ready"]{visibility:hidden}
.fall-sky:not([data-beat="ready"]) .fs-night{animation:fs-fade .45s var(--ease) both}
.fall-sky:not([data-beat="ready"]) .fs-sky{animation:fs-sky .8s var(--ease) both}
.fall-sky:not([data-beat="ready"]) .fs-eye{animation:fs-eye .62s .14s var(--ease) both}
@keyframes fs-fade{from{opacity:0}}
@keyframes fs-sky{from{opacity:0;transform:translateY(-14%)}}
@keyframes fs-eye{from{opacity:0;transform:scaleX(.08)}}
/* the warp: two of it, half a turn apart, racing out from the opening; held while the nine figures load */
.fs-warps{opacity:0;transition:opacity .45s var(--ease)}
.fall-sky[data-beat="warp"] .fs-warps{opacity:1}
.fs-warp{transform-origin:50% 18%;animation:fs-warp 1.6s linear infinite;will-change:transform,opacity}
.fs-warp.fs-b{animation-delay:-.8s}
@keyframes fs-warp{from{opacity:0;transform:scale(.55)}30%{opacity:1}to{opacity:0;transform:scale(1.65)}}
.fs-aurora{opacity:0;transition:opacity .6s var(--ease);will-change:transform,opacity}
.fall-sky[data-beat="warp"] .fs-aurora{opacity:1;animation:fs-aurora 2.6s ease-in-out infinite alternate}
@keyframes fs-aurora{from{transform:translateX(-4%) skewY(-2deg)}to{transform:translateX(4%) skewY(2deg)}}
/* the meteors: each turned onto the slant about its head, sliding down it to its slot */
.fs-meteor{left:var(--x);top:var(--y);transform-origin:var(--ox) var(--oy);animation:fs-meteor var(--dur) cubic-bezier(.3,.55,.35,1) var(--at) both;will-change:transform,opacity}
@keyframes fs-meteor{from{opacity:0;transform:translate(var(--dx),var(--dy)) rotate(var(--rot))}14%{opacity:1}to{opacity:1;transform:rotate(var(--rot))}}
.fs-meteor[data-landed]{animation:fs-meteor-out .24s ease-out both}
@keyframes fs-meteor-out{from{opacity:1;transform:rotate(var(--rot))}to{opacity:0;transform:rotate(var(--rot)) scale(.5)}}
/* a strike on a card (the ceremony's land, and the reveal's turn): the ripple and its sparks, a light up the frame, and on the
   reveal a streak coming down into the card ahead of its hero */
.fs-strike{position:fixed;left:var(--x);top:var(--y);width:var(--w);height:var(--h);z-index:30;pointer-events:none}
.fs-strikes .fs-strike{position:absolute}
.fs-strike i{position:absolute;display:block}
.fs-ripple{left:var(--sx);top:var(--sy);transform-origin:var(--ox) var(--oy);animation:fs-ripple .56s var(--ease) both;will-change:transform,opacity}
@keyframes fs-ripple{from{opacity:0;transform:scale(.5)}25%{opacity:1}to{opacity:0;transform:scale(1.12)}}
/* graded by tier (owner: "The impact on the card should be varied by the tier too"): an Epic's strike lasts longer and spreads further,
   with a pillar up its card; a Legendary's longest of all, its pillar up past the card into the sky, a shockwave out across its
   neighbours, and a flash over the screen in its own gold (an overlay laid over the frame, never a grade) */
.fs-t2 .fs-ripple{animation-duration:.72s}
.fs-t3 .fs-ripple{animation:fs-ripple-gold 1s var(--ease) both}
@keyframes fs-ripple-gold{from{opacity:0;transform:scale(.4)}18%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(1.25)}}
.fs-pillar{left:calc(var(--w) / 2 - 23px);bottom:calc(var(--h) - var(--py));transform-origin:50% 100%;animation:fs-pillar .7s var(--ease) both;will-change:transform,opacity}
.fs-t3 .fs-pillar{animation-duration:1s}
@keyframes fs-pillar{from{opacity:0;transform:scaleY(.08)}18%{opacity:1;transform:scaleY(1)}55%{opacity:.85}to{opacity:0;transform:scaleY(1.06)}}
.fs-shock{left:calc(var(--w) / 2 - 210px);top:calc(var(--py) - 60px);transform-origin:210px 60px;animation:fs-shock .8s cubic-bezier(.2,.7,.3,1) both;will-change:transform,opacity}
@keyframes fs-shock{from{opacity:0;transform:scale(.2)}12%{opacity:1}to{opacity:0;transform:scale(1.6)}}
.fs-flash{position:fixed;inset:0;z-index:30;pointer-events:none;background:radial-gradient(circle at var(--fx) var(--fy),color-mix(in srgb,var(--fc) 62%,transparent) 0,color-mix(in srgb,var(--fc) 22%,transparent) 30%,transparent 72%);animation:fs-flash .62s ease-out both;will-change:opacity}
.fs-strikes .fs-flash{position:absolute}
@keyframes fs-flash{from{opacity:0}22%{opacity:1}to{opacity:0}}
/* the card lands harder the higher its tier */
#view[data-sky] .fell[data-struck][data-rarity="3"]{animation:fs-card-epic .5s var(--ease) both}
#view[data-sky] .fell[data-struck][data-rarity="4"]{animation:fs-card-gold .72s var(--ease) both}
@keyframes fs-card-epic{from{opacity:0;transform:scale(.92)}60%{opacity:1;transform:scale(1.025)}to{opacity:1;transform:none}}
@keyframes fs-card-gold{from{opacity:0;transform:scale(.86)}45%{opacity:1;transform:scale(1.05)}70%{transform:scale(.99)}to{opacity:1;transform:none}}
.fs-frame{left:0;top:0;animation:fs-frame .72s var(--ease) both;will-change:opacity}
@keyframes fs-frame{from{opacity:0}30%{opacity:1}to{opacity:0}}
.fs-trail{left:calc(var(--w) / 2 - 20px);top:-240px;animation:fs-trail .46s ease-in both;will-change:transform,opacity}
.fs-echo .fs-ripple{animation-delay:.3s}.fs-echo .fs-frame{animation-delay:.24s}
@keyframes fs-trail{from{opacity:0;transform:translateY(-140px)}35%{opacity:1}to{opacity:0;transform:translateY(calc(var(--h) * .78))}}

/* every Tier 3's moment. The stage (.fall-moment) is one scrim for all of a fall's moments, so one hero gives way to the next with
   nothing between them; each moment's act (.fm-act) brings its Sign's atmosphere behind the hero (75b fmAtmosphere, a 2D canvas),
   the Sign's emblem drawn in over it - inner ring first, then the petals, a hairline core over a soft band of its colour (lifted .3
   toward white at most, never white) - and its name under it */
.fall-moment{position:fixed;inset:0;z-index:40;cursor:pointer;-webkit-tap-highlight-color:transparent}
.fall-moment .fm-scrim{position:absolute;inset:0;background:#06080fe6;opacity:0;transition:opacity .26s var(--ease)}
.fall-moment[data-on] .fm-scrim{opacity:1}
.fall-moment[data-going] .fm-scrim{opacity:0;transition-duration:.3s}
.fm-act{position:absolute;inset:0;--sign:var(--loom);--fm-ink:var(--sign);--fm-hot:color-mix(in srgb,var(--sign) 70%,#fff)}
.fm-act[data-sign="lightning"]{--sign:#e0c84a}
.fm-act[data-sign="water"]{--sign:#6f9ce8}
.fm-act[data-sign="fire"]{--sign:#e2463f}
.fm-act[data-sign="earth"]{--sign:#7fbb7a}
.fm-act[data-sign="wind"]{--sign:#74c2cc}
.fm-act .fm-motes{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;opacity:0;transition:opacity .45s var(--ease)}
.fm-act[data-on] .fm-motes{opacity:1}
.fm-act .fm-emblem{position:absolute;left:calc(var(--fx) - var(--fs) / 2);top:calc(var(--fy) - var(--fs) / 2);width:var(--fs);height:var(--fs);overflow:visible;
  transition:opacity .3s var(--ease),scale .36s var(--ease)}
.fm-emblem .fm-spin{transform-box:view-box;transform-origin:0 0}   /* view-box's origin is the viewBox's (0,0): the emblem's own centre */
.fm-act[data-on] .fm-spin{animation:fm-spin 28s linear infinite}
.fm-emblem .fm-glow,.fm-emblem .fm-core,.fm-special path,.fm-special circle{fill:none;stroke-dasharray:1;stroke-dashoffset:1;stroke-linecap:round;stroke-linejoin:round}
.fm-emblem .fm-glow{stroke:var(--fm-ink);stroke-width:5;stroke-opacity:.22}
.fm-emblem .fm-core{stroke:var(--fm-hot);stroke-width:1.1}
.fm-act[data-on] .fm-r1{animation:fm-draw .42s var(--ease) .05s forwards}
.fm-act[data-on] .fm-r2{animation:fm-draw .46s var(--ease) .17s forwards}
.fm-act[data-on] .fm-r3{animation:fm-draw .56s var(--ease) .3s forwards}
.fm-act[data-on] .fm-petal{animation:fm-draw .6s var(--ease) .48s forwards}
.fm-special path,.fm-special circle{stroke:var(--fm-hot);stroke-width:1.3}
.fm-eye{transform-box:fill-box;transform-origin:50% 50%}
.fm-act[data-on] .fm-eye{animation:fm-eye .6s var(--ease) .2s both}
.fm-act[data-on] .fm-eye path,.fm-act[data-on] .fm-eye circle{animation:fm-draw .5s var(--ease) .2s forwards}
.fm-eye .fm-iris{fill:var(--fm-hot);stroke:none;opacity:0}
.fm-act[data-on] .fm-eye .fm-iris{animation:fm-fade .3s var(--ease) .7s forwards}
.fm-act[data-on] .fm-bolt path{stroke-width:2.4;animation:fm-draw .22s cubic-bezier(.2,.7,.2,1) .08s forwards}
.fm-bolt .fm-bolt-glow{stroke:var(--fm-ink);stroke-opacity:.3}
.fm-act[data-on] .fm-bolt .fm-bolt-glow{stroke-width:7}
.fm-bolt .fm-flash{stroke:none;stroke-dasharray:none;opacity:0}   /* its fill is its own act's gradient (75b fmSpecial) */
.fm-act[data-on] .fm-bolt .fm-flash{animation:fm-flash .8s var(--ease) .26s forwards}
.fm-frost path{stroke:#a9d3ea}
.fm-act[data-on] .fm-frost path{animation:fm-draw .8s var(--ease) .3s forwards}
.fm-stop-hot{stop-color:var(--fm-hot);stop-opacity:.9}
.fm-stop-ink{stop-color:var(--fm-ink);stop-opacity:0}
.fm-act .fm-cap{position:absolute;left:0;right:0;top:var(--fcap);display:grid;justify-items:center;gap:4px;padding:0 var(--s3);text-align:center;
  opacity:0;translate:0 8px;transition:opacity .3s var(--ease) .62s,translate .36s var(--ease) .62s}
.fm-act[data-on] .fm-cap{opacity:1;translate:0 0}
.fm-new{font-size:10.5px;letter-spacing:.01em;text-transform:uppercase;color:var(--loom);border:1px solid color-mix(in srgb,var(--loom) 55%,transparent);border-radius:var(--r-pill);padding:1px 8px}
.fm-name{margin:0;display:flex;align-items:center;gap:8px;font-family:var(--serif);font-weight:600;font-size:24px;line-height:1.15;letter-spacing:.01em;color:var(--ink)}
.fm-sub{margin:0;font-size:12px;letter-spacing:.01em;color:var(--muted)}
.fm-act .fm-tap{position:absolute;left:0;right:0;bottom:96px;margin:0;text-align:center;font-size:10.5px;letter-spacing:.01em;color:var(--muted);opacity:0;transition:opacity .3s var(--ease) 1.2s}
.fm-act[data-on] .fm-tap{opacity:.8}
/* an act going: the emblem closes, the atmosphere and the words fade (the scrim stays for the next, and lifts with the last) */
.fm-act[data-going] .fm-motes,.fm-act[data-going] .fm-cap,.fm-act[data-going] .fm-tap{opacity:0;transition-delay:0s;transition-duration:.3s}
.fm-act[data-going] .fm-emblem{opacity:0;scale:.9}
@keyframes fm-draw{to{stroke-dashoffset:0}}
@keyframes fm-spin{to{transform:rotate(360deg)}}
@keyframes fm-eye{from{transform:scaleY(.05)}to{transform:scaleY(1)}}
@keyframes fm-flash{0%{opacity:0}30%{opacity:.5}100%{opacity:.12}}
@keyframes fm-fade{to{opacity:1}}

/* the seed in hand wears the guarantees: nine grains toward the next sure Tier 3, ninety notches toward Limit Break 5 */
.seedring{width:136px;height:136px;flex:0 0 auto}
.seedring svg{width:100%;height:100%;overflow:visible}
.seedring .tick{stroke:color-mix(in srgb,var(--loom) 16%,transparent);stroke-width:1.2;stroke-linecap:round}
.seedring .tick.on{stroke:color-mix(in srgb,var(--loom) 62%,transparent)}
.seedring .grain{fill:none;stroke:color-mix(in srgb,var(--loom) 20%,transparent);stroke-width:4.5;stroke-linecap:round}
.seedring .grain.on{stroke:var(--loom)}
.seedring .seed{fill:color-mix(in srgb,var(--loom) 12%,transparent);stroke:color-mix(in srgb,var(--loom) 55%,transparent);stroke-width:1}
.seedring .seed-core{fill:color-mix(in srgb,var(--loom) 75%,transparent)}
@media (max-height:700px){.seedring{width:104px;height:104px}}
/* The line under the fall changes when the last hero lands - and a shorter line is a shorter panel,
   which pushed all nine cards nineteen pixels down the screen at the exact moment the ceremony was
   supposed to be over. It reads as the whole grid dropping into place a second time. The line keeps
   room for two either way, so what it says can change without anything moving. */
/* Two lines, always, so what the title says can change without moving the nine cards under it. */
.title.hold2{min-height:calc(2 * var(--lead-display) * 1em)}
.fallsay{min-height:34px;display:flex;align-items:center;justify-content:center}

/* The fall screen is the one column whose nine cards, hint and action panel can together outgrow the
   frame: .grid is sized to its cards (flex:0 0 auto), so the strip below it settled 4px UNDER the dock.
   Scoped here only - let the grid give up exactly the height the strip needs, and nowhere else. */
body[data-screen="fall"] #view > .grid{
  flex:0 1 auto;min-height:0;
  /* A fell card is drawn BIGGER than its grid row on purpose, so its art spills ~16px below the row's
     box. Letting the grid shrink lifted the strip off the dock but pulled the hint up into that spill:
     measured, the hint's top sat 2px ABOVE the last card's painted bottom. Reserve the spill here so
     the line below the nine clears the art instead of the box, and let the shrinkable rows pay for it. */
  padding-bottom:18px
}

/* the untouched stage: one thing to press, and a long way to fall into */
.throw{flex:1 1 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s3);min-height:0}
.throw .hint{max-width:34ch}
.throw .btn{min-width:min(260px,calc(var(--fvw,1vw) * 72))}

/* ---------- what a hero carries ---------- */
/* Three across when there are three of them. A hero wears ONE relic - the seat's - and that single
   card was still being laid out in a three-column grid, so it stood in the left third of a full-width
   panel with two thirds of nothing beside it. One card, one column. */
.slots{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s1)}
.slots[data-one]{grid-template-columns:1fr}
.slot{display:grid;gap:3px;justify-items:center;text-align:center;min-width:0;padding:var(--s2) var(--s1);
  border:1px solid var(--line-soft);border-radius:var(--r-tile);background:color-mix(in srgb,#ffffff var(--a-wash),transparent)}
.slot[data-rarity]{border-color:color-mix(in srgb,var(--rarity) 42%,transparent)}
.slot[data-rarity="0"]{--rarity:var(--grey)} .slot[data-rarity="1"]{--rarity:var(--green)}
.slot[data-rarity="2"]{--rarity:var(--blue)} .slot[data-rarity="3"]{--rarity:var(--violet)}
.slot[data-rarity="4"]{--rarity:var(--loom)}
.slot span{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);line-height:1}
.slot b{font-size:var(--t-label);font-weight:600;line-height:1.2;overflow-wrap:anywhere}
.slot b.dim{color:var(--muted);font-weight:400}
.slot small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted);line-height:1.2}
.slot .btn{margin-top:4px;padding:0 var(--s2);width:100%}

/* Everything you own, and it scrolls. Thirty-six relics is 583px past the bottom of the screen with
   557px of it behind the dock - measured - because nothing here scrolled and #view clips. Same rule as
   the kit list it is a twin of: as much height as the pane can spare, and the list moves inside it. */
.relics{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s1);width:100%;
  /* sized so the strip below it keeps the same air over the dock that every other pane has */
  max-height:min(calc(var(--vh,100dvh) * .42),340px);overflow-y:auto;-webkit-overflow-scrolling:touch;
  overscroll-behavior:contain;touch-action:pan-y;
  padding:var(--s1) 2px 0;border-top:1px solid var(--panel-line)}
.relic{display:grid;gap:2px;justify-items:start;text-align:left;padding:var(--s2);min-height:var(--control);
  border:1px solid var(--line);border-radius:var(--r-control);background:color-mix(in srgb,#ffffff var(--a-wash),transparent);color:var(--ink);
  transition:border-color .2s ease,transform .14s var(--ease)}
.relic:active{transform:scale(.97)}
.relic:disabled{opacity:.4}
.relic[data-rarity]{border-color:color-mix(in srgb,var(--rarity) 38%,transparent)}
.relic[data-rarity="0"]{--rarity:var(--grey)} .relic[data-rarity="1"]{--rarity:var(--green)}
.relic[data-rarity="2"]{--rarity:var(--blue)} .relic[data-rarity="3"]{--rarity:var(--violet)}
.relic[data-rarity="4"]{--rarity:var(--loom)}
.relic b{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;text-transform:uppercase;line-height:1.2}
.relic small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted)}
.relic:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* today's ground and the week's Anchor sit above the nine, because they are not part of the nine */
.special{display:grid;gap:var(--s2);flex:0 0 auto}
.node.wide{grid-auto-flow:row;min-height:46px;padding:var(--s2) var(--s3);justify-items:start;text-align:left;gap:3px}
.node.wide span{font-family:var(--sans);font-size:var(--t-label);font-weight:600;letter-spacing:.01em;text-transform:uppercase}
.node.wide .hp{width:100%;margin-top:3px}


/* ---------- the wider screen ----------
   Last, for the same reason the short-screen blocks are: a media query carries no extra
   specificity, so this has to sit after the base rules it raises. It stays ABOVE the
   short-screen blocks, which are allowed to take the title back down on a wide-but-short one. */
/* A bigger screen gets more ROOM AROUND the game, never a bigger game. The page is the same column
   the dock is, centred, so every size and gap measured on a phone holds on a desk without being
   worked out a second time - which is also why there is no longer a min-width:760px block here
   raising the type, the gaps and the padding. One column, one set of numbers. */


/* ---------- short screens ----------
   These sit at the END of the sheet on purpose. A media query adds no specificity, so a block
   written earlier loses to an identical selector defined later in the file - which is exactly what
   had happened: six rules here (.tally, .tell strong, .tell small, .deeds, .deed) were dead the
   whole time, beaten by their own base rules further down. Keep these last. */
/* A title sized only by width grows on a WIDE-but-short screen until it pushes the page off the
   bottom - a laptop, not a phone. Scoped to match: a phone is short by nature and already has its own
   smaller scale, and this rule was quietly undoing it. */
/* (There was a rule here that shrank the title on a wide-but-short screen, back when the display face
   ran to 58px and pushed the page off the bottom. The scale tops out at 30px now, so the same rule had
   turned into the opposite of itself - it was INFLATING the title to 44px on exactly those screens.
   Deleted rather than adjusted: a smaller face never needed rescuing from its own size.) */
/* These ask the FRAME's height and width, not the window's - the router sets data-h860/700/640 and
   data-w370 from the frame's own box, so a desk showing a 360x800 frame on a 1080px display folds away
   exactly what the phone folds away. They still sit at the END of the sheet for the reason the media
   queries did: order decides between equals, and six rules here (.tally, .tell strong, .tell small,
   .deeds, .deed) were once dead for having been written too early. Keep these last. */
/* The archive is a thing to look at, so a short frame may keep it folded away. The store is NOT -
   provisions are bought, and a laptop is about 800px tall, so hiding it here meant a player on a
   MacBook could not buy a poultice at all. Content you act on does not get to be the thing that
   gives way; the display face above is what makes the room. */
:root[data-h860] [data-part="archive"]{display:none}
/* the archive is a thing to look at, so it is the first thing a short frame gives up */
:root[data-h700] [data-part="archive"]{display:none}
:root[data-h700] [data-part="dev"] .hint{display:none}
:root[data-h700] [data-part="signin"] .hint{display:none}
:root[data-h700] .tally{padding:var(--s1)}
:root[data-h700] .tell strong{font-size:15px}
:root[data-h700] .tell small{display:none}
/* The deeds close up rather than lose a row. They were crushed flat when the type was bigger; the
   smaller phone scale paid that back, so this is now a gentle tightening with about 30px still in
   hand rather than a squeeze with 6 (scratchpad/relax.mjs). */
:root[data-h700] .deeds{gap:3px;padding:var(--s1)}
:root[data-h700] .deed{min-height:30px}
:root[data-h700] .tally{gap:0}
:root[data-h700] .state{padding:var(--s3);gap:var(--s1)}
:root[data-h700] .state h2{font-size:19px}
:root[data-h700] .state small{display:none}
:root[data-h640] .seat .seatskill{display:none}
:root[data-h640] .band .grid{gap:var(--s2)}
:root[data-h640] .chosen{padding:0 4px}
:root[data-h640] .lineup-chosen{gap:var(--s1)}

/* ---------- what a blow looks like from the other end ----------
   The caster already plays its skill. What was missing was everything that happens to whoever it
   landed on: a hit that only prints a number is arithmetic, not a fight. So a struck fighter recoils
   and flashes in the colour of what hit it, a mended one lifts and glows, and anything carrying a
   mark keeps showing it for as long as it has it rather than only in a chip. */
@keyframes struck{
  0%{transform:none;filter:none}
  14%{transform:translate(-4px,1px);filter:brightness(2.4) saturate(.4)}
  30%{transform:translate(3px,-1px);filter:brightness(1.5)}
  52%{transform:translate(-2px,0)}
  74%{transform:translate(1px,0)}
  to{transform:none;filter:none}
}
@keyframes struck-hard{
  0%{transform:none;filter:none}
  10%{transform:translate(-7px,2px) scale(1.04);filter:brightness(3) saturate(.3)}
  26%{transform:translate(6px,-2px) scale(.985);filter:brightness(1.8)}
  46%{transform:translate(-4px,1px)}
  68%{transform:translate(2px,0)}
  to{transform:none;filter:none}
}
@keyframes mended{
  0%{transform:none;filter:none}
  40%{transform:translateY(-5px);filter:brightness(1.5) saturate(1.35)}
  to{transform:none;filter:none}
}
.fighter[data-struck] .shot{animation:struck .42s var(--ease)}
.fighter[data-struck="hard"] .shot{animation:struck-hard .52s var(--ease)}
.fighter[data-mended] .shot{animation:mended .5s var(--ease)}
/* a burst where the blow landed, in the colour the blow was */
.fighter .blow{
  position:absolute;left:50%;top:42%;width:54px;height:54px;margin:-27px 0 0 -27px;
  border-radius:50%;pointer-events:none;z-index:2;   /* a hot point and torn rays, never a flat coloured disc (27 Sep 2026) */
  background:radial-gradient(circle,color-mix(in srgb,#fff 85%,transparent) 0 5%,color-mix(in srgb,var(--tint,#fff) 70%,transparent) 13%,transparent 34%),
    repeating-conic-gradient(from 9deg,color-mix(in srgb,var(--tint,#fff) 60%,transparent) 0 4deg,transparent 7deg 23deg);
  -webkit-mask:radial-gradient(circle,#000 0 22%,transparent 62%);mask:radial-gradient(circle,#000 0 22%,transparent 62%);
  animation:blow .44s var(--ease) forwards;
}
.battle .fighter .blow{width:var(--blow-size,54px);height:var(--blow-size,54px);
  margin:calc(var(--blow-size,54px) / -2) 0 0 calc(var(--blow-size,54px) / -2)}
/* A critical must not read as the normal Sign-coloured contact burst. The violet-white X sits over
   that burst on every critical target, including multi-target hits. */
.fighter .blow[data-critical="true"]{
  overflow:visible;
  background:radial-gradient(circle,#fff 0 7%,#eadfff 10%,#b98cffaa 22%,transparent 48%),
    repeating-conic-gradient(from 0deg,#f7efffcc 0 5deg,transparent 7deg 18deg);
  -webkit-mask:none;mask:none;
  box-shadow:0 0 12px #efe4ffcc,0 0 28px #a96dff88;
}
.fighter .blow[data-critical="true"]::before,
.fighter .blow[data-critical="true"]::after{
  content:"";position:absolute;left:50%;top:50%;width:142%;height:10%;
  border-radius:999px;transform-origin:center;
  background:linear-gradient(90deg,transparent,#c9a7ff 17%,#fff 48% 52%,#c9a7ff 83%,transparent);
  box-shadow:0 0 7px #fff,0 0 14px #b17cff;
}
.fighter .blow[data-critical="true"]::before{transform:translate(-50%,-50%) rotate(45deg)}
.fighter .blow[data-critical="true"]::after{transform:translate(-50%,-50%) rotate(-45deg)}
@keyframes blow{from{opacity:.95;transform:scale(.25)}to{opacity:0;transform:scale(1.9)}}
/* ---------- the skill, named ----------
   A skill that only shows its light asks the player to guess what happened. The name arrives with it
   and leaves before the next one, so the fight reads as a sequence of decisions rather than flashes. */
.skillname{
  /* the middle of the field, not the top of it: at 8% it sat under the header and behind anything tall */
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:6;pointer-events:none;
  display:grid;justify-items:center;gap:2px;text-align:center;white-space:nowrap;
  animation:skillname .9s var(--ease) both;
}
.skillname b{
  font-family:var(--serif);font-size:clamp(17px,calc(var(--fvw,1vw) * 4.6),23px);font-weight:600;letter-spacing:.01em;
  color:var(--ink);text-shadow:0 2px 18px #000,0 0 34px color-mix(in srgb,var(--tint,var(--gold)) 70%,transparent);
}
.skillname span{
  font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;
  color:color-mix(in srgb,var(--tint,var(--gold)) 85%,var(--ink));
}
/* Hellstorm carries shooter + off-screen + power + drain in one subtitle. On a phone that sentence is
   wider than the field, so this one announcement may wrap into two centred lines; normal skill names stay single-line. */
.skillname[data-hellstorm] span{
  width:min(88vw,360px);max-width:calc(100vw - 32px);white-space:normal;line-height:1.35;text-wrap:balance;
}
/* in the top layer, over the job-VFX canvas (89d2): the popover's own box undone, the battle's middle its origin */
.skillname[popover]{position:fixed;inset:auto;left:var(--lift-x,50%);top:var(--lift-y,50%);margin:0;padding:0;border:0;background:none;color:inherit;overflow:visible;width:auto;height:auto;max-width:none;max-height:none}
/* a combo's name carries its accent (74zzd): the glow of the pair's two powers meeting */
.skillname[data-combo] b{text-shadow:0 2px 18px #000,0 0 22px var(--tint),0 0 44px color-mix(in srgb,var(--tint) 60%,transparent)}
@keyframes skillname{
  0%{opacity:0;transform:translate(-50%,-50%) translateY(10px) scale(.94)}
  14%{opacity:1;transform:translate(-50%,-50%) translateY(0) scale(1)}
  52%{opacity:1;transform:translate(-50%,-50%) translateY(0)}
  to{opacity:0;transform:translate(-50%,-50%) translateY(-8px)}
}

/* ---------- before, and after ----------
   A fight used to start the moment the screen arrived and end as an attribute on the host. Both
   moments are worth standing still for: one says what you are walking into, the other says what it
   cost and asks where you want to go next. */
.verdict{
  position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:var(--s4);
  background:color-mix(in srgb,#070912 var(--a-scrim),transparent);
  animation:verdict-in .34s var(--ease) both;
}
@keyframes verdict-in{from{opacity:0}to{opacity:1}}
.verdict[data-going]{animation:verdict-out .3s var(--ease) both}
@keyframes verdict-out{from{opacity:1}to{opacity:0}}
.verdict .card{
  width:min(var(--column),100%);display:grid;gap:var(--s2);justify-items:center;text-align:center;
  padding:var(--s4) var(--s3);   /* tighter (owner, 11 Oct 2026: "the result page is too long and runs through the dock") */border:1px solid var(--panel-line);border-radius:var(--r-panel);
  background:var(--popup);box-shadow:0 24px 80px #000a;   /* the result card is a popup (owner, 11 Oct 2026) */
}
.verdict h2{
  margin:0;font-family:var(--serif);font-weight:600;font-size:clamp(26px,calc(var(--fvw,1vw) * 7),34px);
  line-height:1.05;letter-spacing:.01em
}
.verdict .eyebrow{color:color-mix(in srgb,var(--tint,var(--gold)) 80%,var(--ink))}
.verdict .lines{display:grid;gap:2px;width:100%}
.verdict .lines div{
  display:flex;justify-content:space-between;gap:var(--s3);
  font-size:var(--t-label);letter-spacing:.01em;text-transform:uppercase;color:var(--muted)
}
.verdict .lines b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums;text-align:right}
/* a long answer (an Anchor's "It is", its ground, its wake) wraps under itself; the label keeps its one line */
.verdict .lines span{flex:none;white-space:nowrap}
/* the card's own (i), out of the way in its corner */
.verdict .card{position:relative}
/* three buttons a row (owner, 11 Oct 2026: "Can we make the buttons 3 per row?"): Go on · Retry · Home, then Auto · Repeat · Loop */
.verdict[data-cols="3"] .pair{grid-template-columns:repeat(3,minmax(0,1fr))}
.verdict[data-cols="3"] .pair .btn{padding:0 var(--s2);gap:var(--s1)}   /* a two-button card (the briefing) keeps two a row (owner, 11 Oct 2026) */
.verdict .card > .icon{position:absolute;top:var(--s2);right:var(--s2);z-index:1}
/* The columns are .pair's, and are not restated here. They were - as a bare `1fr 1fr` - which quietly
   overrode the rule above it: with "Auto retry on" in the right-hand column the two came out 136 and
   140 wide, and fixing the general rule changed nothing at all because this one won. One definition. */
.verdict .pair{gap:var(--s2);width:100%}
/* the card keeps a clear margin above and below whatever it holds, and gives the list the squeeze */
.verdict{padding:var(--s5) var(--s4)}
.verdict .card{max-height:calc(var(--vh,100dvh) - 2 * var(--s5));overflow:hidden}
.verdict .pair .btn{width:100%}
.verdict[data-cols="3"] .pair{gap:var(--s1)}

/* ---------- the result card (owner, 11 Oct 2026: "The result page looks really bad") ----------
   The card keeps its panel fill and the scrim stays as it is (owner, 11 Oct 2026: the figures and the ground may show softly through).
   What must not read through it is words: while the result is up the field's text layer - the head, its round pills, fighter chips,
   names and HP numbers, the end strip and its old button row - is hidden. No blur (it lags the phone). */
#view:has(> .verdict[data-kind="result"]) :is(.head,.battle .fighter figcaption,.battle .fighter .vitals,.battle .status-chip,.battle-mechanic,[data-part="say"],[data-part="narr-log"],[data-part="deeds"]){visibility:hidden}
.verdict[data-kind="result"] .card{gap:var(--s2);padding:var(--s4) var(--s3) var(--s3)}
/* the (i) at the size of the small icon buttons, in its corner, clear of the eyebrow and the title */
.verdict[data-kind="result"] .card > .icon{top:var(--s1);right:var(--s1);width:28px;min-height:28px;height:28px;border-color:transparent;background:transparent;color:var(--muted)}
.verdict[data-kind="result"] .card > .icon svg{width:15px;height:15px}
.verdict[data-kind="result"] .card > .eyebrow{padding:0 30px}
.verdict .meta{margin:-4px 0 0;font-size:var(--t-label);letter-spacing:.01em;line-height:1.35;color:var(--muted);text-wrap:balance}
/* two or three figures, a small label over each value */
.verdict .figs{display:flex;justify-content:center;width:100%;margin-top:var(--s1)}
.verdict .figs>div{display:grid;gap:2px;justify-items:center;padding:0 var(--s3);min-width:0}
.verdict .figs>div+div{border-left:1px solid var(--panel-line)}
.verdict .figs span{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);line-height:1.2}
.verdict .figs b{font-size:var(--t-body);font-weight:600;letter-spacing:.01em;color:var(--ink);font-variant-numeric:tabular-nums;line-height:1.3}
/* what the ground left: the relic's name in gold */
.verdict .loot{margin:0;font-size:var(--t-body);letter-spacing:.01em;line-height:1.35;color:var(--muted);text-wrap:balance;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
.verdict .loot b{font-weight:600;color:var(--ink)}
.verdict .loot[data-tone="gold"] b{color:var(--gold)}
.verdict .loot[data-tone="lost"] b{color:#d89aa3}
/* the band: three lines of three in seat order, one cell each - name, level, a thin bar */
.verdict .gains{display:grid;gap:var(--s1);width:100%;padding-top:var(--s2);border-top:1px solid var(--panel-line)}
.verdict .gains .grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px}
.gain{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:4px;row-gap:4px;min-width:0;
  padding:5px 6px 6px;border:1px solid var(--line);border-radius:6px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);text-align:left;letter-spacing:.01em}
.gain[data-empty]{border-style:dashed;background:none}
.gain .who{min-width:0;font-size:var(--t-micro);line-height:1.2;color:#c9cbd4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.gain .lv{display:inline-flex;align-items:center;gap:1px;font-size:var(--t-micro);line-height:1.2;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
.gain .lv svg{width:8px;height:8px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.gain .hp{height:3px;width:100%}
.gain .pct{font-size:var(--t-micro);line-height:1;color:var(--muted);font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
.gain[data-up] .lv{color:var(--gold)}
.gain[data-maxed] .pct{grid-column:1/-1;text-align:left;line-height:1.2;margin-top:-3px}
.verdict .gains .cap{margin:0;font-size:var(--t-micro);letter-spacing:.01em;line-height:1.3;color:var(--muted)}
/* Auto, Repeat and Loop are toggles: the second row is quieter until one is on, which lights as before */
.verdict[data-kind="result"] .pair .btn[data-act^="auto"][data-tone="quiet"]{background:transparent;border-color:var(--line);color:var(--muted)}

/* the nine on Home: a compact formation, four behind and five across the front, right above the dock */
.lineup{
  display:grid;gap:0;align-items:end;
  flex:0 0 auto;cursor:pointer;padding:2px var(--s2);border-radius:var(--r-control);
  transition:background-color .2s ease;
}
.lineup-line{display:grid;gap:6px;align-items:stretch;justify-self:center;min-height:1px}
/* The nine stand larger than they first did, spaced across the screen (owner, 21 Oct 2026: "enlarge the line up heroes on home
   page to 1.25x and space them nicely"; 22 Oct: "a bit too big ... decrease the size a bit but bigger than the original" - 1.15x).
   A figure is framed by its box's height (89zg) and fit() gives Home's boxes the 36px floor, so the box is made 15% taller. The
   front row spans the screen; the rear row is four-fifths of it less a fifth of a gap, so each of the four stands in a gap of the
   five, its head clear of theirs. */
.lineup-line[data-line="rear"]{grid-template-columns:repeat(4,minmax(0,1fr));width:calc(89.6% - 1.2px);margin-bottom:18px}
.lineup-line[data-line="front"]{grid-template-columns:repeat(5,minmax(0,1fr));width:112%}
.lineup .pip .shot{height:calc(var(--shot-h,0px) * 1.15)}
/* a frame 640 tall has 99px between Home's panels and the dock, so the nine stand a tenth taller there */
:root[data-h640] .lineup .pip .shot{height:calc(var(--shot-h,0px) * 1.1)}
:root[data-h640] .lineup-line[data-line="rear"]{margin-bottom:14px}
.lineup:hover{background:color-mix(in srgb,#ffffff var(--a-wash-on),transparent)}
.lineup:active{transform:scale(.99)}
.lineup:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.pip{margin:0;position:relative;min-width:0;border-radius:6px}
.pip[data-rarity]{box-shadow:inset 0 -2px 0 color-mix(in srgb,var(--rarity) 85%,transparent)}
/* Home's formation stands directly on the authored ground. The yellow rarity underline reads as a
   display base here, so remove it only from this presentation; cards and seat pickers keep rarity chrome. */
.lineup .pip[data-rarity]{box-shadow:none}
.pip[data-rarity="0"]{--rarity:var(--grey)}
.pip[data-rarity="1"]{--rarity:var(--green)}
.pip[data-rarity="2"]{--rarity:var(--blue)}
.pip[data-rarity="3"]{--rarity:var(--violet)}
.pip[data-rarity="4"]{--rarity:var(--loom)}
/* an empty seat stays visibly empty: it is the most useful thing Home can say */
.pip[data-empty]{
  align-self:stretch;min-height:44px;border:1px dashed var(--line);border-radius:6px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);
}

/* ---------- battle presentation, moved out of the modules ----------
   These rules were written by the battle passes below and used to be appended to the page at runtime as a
   <style> element. The production Content-Security-Policy (static/_headers, style-src 'self') discards any
   style that is not a file of the site's own, so none of them ever applied live, and the two suites that
   depend on them failed against the harness, which sends the same header. They live here now, unchanged
   and in module order, and the build refuses a runtime <style> so this cannot come back. Each block's own
   reasoning stays in its module. */
/* Pass 16 - reaction grammar: action chrome stays foreground; combat information stays on HP chrome. */
#view .float{position:relative;z-index:8}
.battle .fighter .hp>.num[data-hp-owned="1"]{
  top:auto!important;bottom:36px!important;left:50%!important;z-index:8;
  font-weight:700!important;
  text-shadow:0 2px 8px #000d,0 0 10px color-mix(in srgb,var(--pass16-num-tint,#fff) 24%,transparent)!important
}
.battle .fighter .hp>.num[data-hp-owned="1"][data-tone="hit"],
.battle .fighter .hp>.num[data-hp-owned="1"][data-tone="edge"],
.battle .fighter .hp>.num[data-hp-owned="1"][data-tone="weak"]{
  color:var(--pass16-num-tint,#f3ede0)!important}
.battle .fighter .hp>.num[data-hp-owned="1"][data-tone="weak"]{opacity:.9}
.battle .fighter[data-reaction-owned][data-struck] .shot,
.battle .fighter[data-reaction-owned][data-struck="hard"] .shot,
.battle .fighter[data-reaction-owned][data-mended] .shot{animation:none!important}

/* Pass 16C - presentation guards: a boss cover that never scales pixels; HP-owned numbers never follow it. */
/* The normal cover transition scales the whole view from .982. On a huge voxel Anchor that means
   one soft resampled second. Boss fields keep the vertical arrival but never scale the pixels. */
#view[data-view="cover"][data-run="true"]:has(.battle .fighter[data-rank="boss"]){animation-name:pass16c-anchor-cover!important}
@keyframes pass16c-anchor-cover{from{transform:translateY(var(--travel))}to{transform:translateY(0)}}

/* Interactive chrome always wins the stacking contest. The body may spill off its frame, never
   through Withdraw or the dock. */
#view:has(.battle .fighter[data-rank="boss"]) .float{position:relative;z-index:12!important;isolation:isolate}
body:has(#view .battle .fighter[data-rank="boss"]) .dock{z-index:30!important;isolation:isolate}

/* Pass 17 readability remains typography-only. Position is owned exclusively by the HP bar. */
.battle .fighter .hp>.num[data-hp-owned="1"]{
  filter:saturate(1.28) brightness(1.06);
  -webkit-text-stroke:.55px rgba(8,9,13,.72);
  text-shadow:-1px 0 #090b10,1px 0 #090b10,0 -1px #090b10,0 1px #090b10,
    0 3px 10px #000e,0 0 12px color-mix(in srgb,var(--pass16-num-tint,#fff) 44%,transparent)!important
}

/* ---------- hit numbers above the job-VFX canvas (89d) ----------
   A number in the top layer keeps its node in the HP bar; the UA's popover box is stripped and the HP-owned
   lane's origin is restated in fixed terms: --lift-x is the bar's centre, --lift-b the bar's bottom plus the
   lane's 36px, both read from the bar every frame. The lane's box is shrink-to-fit in half the bar's width
   (--lift-w): min-width:min-content + max-width restate that formula, since Chromium has no fit-content().
   Everything else about the number is untouched. */
.num[popover]{margin:0;border:0;padding:0;background:transparent;overflow:visible;inset:auto;width:fit-content;min-width:min-content;max-width:var(--lift-w,none);height:auto}
.num[popover]::backdrop{display:none}
.battle .fighter .hp>.num[data-lifted="1"]:popover-open{
  position:fixed!important;top:auto!important;right:auto!important;
  left:var(--lift-x)!important;bottom:var(--lift-b)!important
}

/* Every battle header uses the same compact row, across campaign difficulties, raids, events and
   practice. Foe Band is optional; its absence must not change the other chips' size or spacing. */
#view:has(>.battle)>.head .pills{flex-wrap:nowrap;gap:4px}
#view:has(>.battle)>.head .pills .pill{
  padding-inline:5px;font-size:clamp(8px,calc(var(--fvw,1vw) * 2.5),9px);letter-spacing:.01em;flex:0 0 auto
}
#view:has(>.battle)>.head .pills .retribution-wrap{flex:0 0 auto}
/* The row can never be wider than the screen (owner, 15 Oct 2026: "Sometimes the whole row flows offscreen"). Round 3/9 ·
   Awakening ready, a seven-figure Retribution and Hellgate's Foe Band came to 366 px on a 360 px phone, and the header's grid
   column grew with them, pushing the pause, speed and (i) off too. The column now takes the screen's width (.head, below), and
   the Round chip - the one whose words grow - gives way with an ellipsis; a block, because a flex box cannot end in one. */
#view:has(>.battle)>.head .pills{min-width:0}
#view:has(>.battle)>.head .pills .pill[data-part=round]{display:block;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;line-height:24px}
#view:has(>.battle)>.head .pills .retribution{gap:4px}

/* ---------- Retribution (57i, 89p): the band's pain, returned with the Ultimate of round 9 ----------
   An ember gauge beside the round: the fill grows with each hit on a hero (a pulse and a rising +n), a slow glint runs
   while it holds anything, it beats through the Ultimate of round 9, then flares, streaks to the foes and drains. */
/* the round chip's height exactly (owner, 24 Sep 2026: "the same height of the round chip"): it never stretches with its
   row, and every glow stays inside the border so the outline never grows past the chip beside it */
.retribution-wrap{position:relative;display:inline-flex;align-self:center}
.pill.retribution{position:relative;overflow:hidden;isolation:isolate;gap:var(--s1);padding-inline:var(--s2);
  height:26px;min-height:26px;box-sizing:border-box;align-self:center;
  border-color:#ff7a4d3d;color:#ffe3d6;background:color-mix(in srgb,#140a0b var(--a-glass),transparent)}
.pill.retribution .ret-fill{position:absolute;inset:0;z-index:-2;transform-origin:left center;transform:scaleX(var(--ret,0));
  background:linear-gradient(90deg,#4a0f0a 0%,#9c2a16 55%,#ff7043 100%);transition:transform .75s var(--ease)}
.pill.retribution .ret-glint{position:absolute;inset:0;z-index:-1;opacity:0;pointer-events:none;
  background:linear-gradient(100deg,transparent 35%,#ffd9c466 50%,transparent 65%);background-size:240% 100%;background-position:130% 0}
.pill.retribution[data-live] .ret-glint{opacity:1;animation:ret-glint 2.8s linear infinite}
.pill.retribution .ret-label{font-weight:700;color:#f7b79c}
.pill.retribution .ret-value{font-variant-numeric:tabular-nums;font-weight:700;min-width:3ch;text-align:right}
.pill.retribution[data-full]{border-color:#ff9a6e}
.pill.retribution[data-ready]{animation:ret-ready 1.1s ease-in-out infinite}
.pill.retribution[data-released] .ret-fill{transition-duration:.5s}
.pill.retribution[data-released] .ret-value{color:#f7b79c}
@keyframes ret-glint{from{background-position:130% 0}to{background-position:-130% 0}}
@keyframes ret-ready{0%,100%{box-shadow:inset 0 0 6px #ff6a3d55;border-color:#ff7a4d88}50%{box-shadow:inset 0 0 14px 3px #ff6a3dcc;border-color:#ffb08a}}
.pill.retribution[data-gather] .ret-fill{animation:ret-gather 1.1s ease-in-out infinite}
@keyframes ret-gather{0%,100%{filter:brightness(1)}50%{filter:brightness(1.7) saturate(1.3)}}
.ret-ember{position:absolute;left:0;top:50%;width:5px;height:5px;margin:-2.5px 0 0 -2.5px;border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,#fff3e8 0%,#ffb08a 45%,#ff5a2a00 75%);box-shadow:0 0 8px 2px #ff6a3d99}
.ret-plus{position:absolute;right:4px;top:0;pointer-events:none;font-size:var(--t-micro);font-weight:700;letter-spacing:.01em;
  color:#ffb393;text-shadow:0 1px 4px #000c;white-space:nowrap}
.ret-streak{position:fixed;left:0;top:0;width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;pointer-events:none;z-index:12;
  background:radial-gradient(circle,#fff5ec 0%,#ffb08a 35%,#ff5a2a 60%,#ff5a2a00 72%);box-shadow:0 0 14px 4px #ff6a3d99}
/* The practice field (89q): its hero stands in the middle seat of a three-seat line, so it keeps its size in battle
   rather than filling a whole side on its own */
.battle[data-practice] [data-part="near"]{grid-template-rows:repeat(3,minmax(0,1fr))}
.battle[data-practice] [data-part="near"] .fighter{grid-row:2}

/* ---------- Auto band testing in real fights (src/ui/72a-auto-band-rework.js) ----------
   A sheet (owner, 25 Sep 2026: "Maybe popup window"): where it fights, a bar, three figures, one button. */
.autotest{display:grid;gap:var(--s2);margin:var(--s1) 0 var(--s2);justify-self:stretch;width:100%}
.autotest-sheet .card>.btn{justify-self:stretch;width:100%}
.autotest-sheet h2{text-wrap:balance}   /* "Tested on Cinderfall · Field 5" broke with the 5 alone on a line at 360 px */
.autotest-acts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s2);justify-self:stretch;width:100%}   /* Restore previous | Keep best (audit 09, 11 Oct 2026) */
.autotest-acts .btn{width:100%;padding:0 var(--s2)}
.autotest-acts .btn[hidden]{display:none}
.autotest-acts:has(.btn[hidden]){grid-template-columns:minmax(0,1fr)}
.autotest-where{margin:0;color:#b9bcc8;font-size:.86rem}
.autotest-count{margin:-4px 0 0;font-size:.74rem;color:#9497a3;font-variant-numeric:tabular-nums}
.autotest-stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s1);margin:0}
.autotest-stats>div{display:flex;flex-direction:column;gap:4px;padding:8px 10px;border:1px solid var(--panel-line);border-radius:10px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);min-width:0}
.autotest-stats dt,.autotest-stats dd{display:block;float:none;position:static;margin:0;line-height:1.2}
.autotest-stats dt{font-size:.6rem;letter-spacing:.01em;text-transform:uppercase;color:#9497a3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.autotest-stats dd{font-size:1.15rem;font-variant-numeric:tabular-nums;color:var(--gold-hi);white-space:nowrap}
.autotest-note{margin:0;font-size:.8rem;line-height:1.45;color:#9497a3}
.autostage-lede{margin:0 0 var(--s2);color:#b9bcc8;font-size:.84rem;line-height:1.45}
/* the card lays its children out at their own width (justify-items:start); the picker's blocks take the card's */
.autostage-sheet .card>.autostage-lede,.autostage-sheet .card>.autostage-quick,.autostage-sheet .card>.autostage-areas,.autostage-sheet .card>.autostage-nodes,.autostage-sheet .card>.btn{justify-self:stretch;width:100%}
.autostage-quick{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s1)}   /* one quick pick since the Weight left (30 Sep 2026) */
.autostage-quick .btn{display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;text-align:left}
.autostage-quick .btn{height:auto;min-height:var(--control);padding-block:8px}
.autostage-quick .btn small{font-size:.7rem;letter-spacing:.01em;text-transform:none;color:#9497a3;white-space:normal;line-height:1.25;max-width:100%}
.autostage-farm{margin:var(--s3) 0 var(--s1)}
.autostage-areas{display:flex;flex-wrap:wrap;gap:6px}
.autostage-areas .pill{flex:none}
.autostage-areas .pill[aria-selected="true"]{border-color:var(--gold);color:var(--gold-hi)}
.autostage-nodes{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:6px;margin:var(--s1) 0 var(--s2)}
.autostage-nodes .util{width:100%;min-height:44px;padding:0;font-variant-numeric:tabular-nums}
.autostage-nodes .util.anchor{grid-column:span 2}
.autostage-nodes .util:disabled{opacity:.35}
/* put back after the band redrew under it: no second entrance */
.sheet[data-settled]:not([data-going]){animation:none}
.autotest-sheet[data-state="done"] .autorefine-track>i{background:linear-gradient(90deg,var(--gold-hi),var(--gold-hi))}
.autorefine-track{grid-column:1 / -1;height:5px;overflow:hidden;border-radius:5px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);border:1px solid #ffffff12}
.autorefine-track>i{display:block;height:100%;width:calc(var(--autorefine-p,0) * 100%);background:linear-gradient(90deg,var(--gold),var(--gold-hi));transition:width .18s var(--ease)}

/* ---------- the Sound sheet (89y): on/off, then a row per level ---------- */
.sheet .mixer{display:grid;gap:var(--s3)}
.sheet .mixer .btn.on{border-color:var(--gold)}
.sheet .mix{display:grid;grid-template-columns:5.5em 1fr 3.2em;align-items:center;gap:var(--s3)}
.sheet .mix span{font:600 .8rem/1 var(--sans, inherit);letter-spacing:.01em;text-transform:uppercase;color:var(--muted)}
.sheet .mix output{text-align:right;font-variant-numeric:tabular-nums;color:var(--ink, inherit)}
.sheet .mix input[type=range]{width:100%;accent-color:var(--gold);height:28px;margin:0}
.sheet .mix input[type=range]:focus-visible{outline:2px solid var(--gold-hi);outline-offset:3px}
/* the announcer's voices, credited as their terms ask (89zzy2a) */
.sheet .mixer .mix-credit{margin:0;font-size:var(--t-micro);line-height:1.45;letter-spacing:.01em;color:var(--muted)}

/* the Developer chip's neighbour (76-home): a chip that is a button */
button.pill{font-family:inherit;cursor:pointer;background:transparent}
button.pill.off{color:var(--muted);border-color:var(--panel-line)}
button.pill:focus-visible{outline:2px solid var(--gold-hi);outline-offset:2px}

/* the Skylancer in his Skydive's leap (57o, 74zzzzzzzzzzzzzzz-skylancer-jump): risen for the dive, back down with the lance. Solid: the
   faint of the old Jump ("out of reach", .28) is the 3D figure's own opacity on the field (89zg mfGridFade reads the shot's), and at .28
   it drew see-through and lost its line - "looks like a flat paper in the fight" (owner, 26 Oct 2026) */
.fighter[data-airborne] .shot{transform:translateY(-58%) scale(.86);transition:transform .32s cubic-bezier(.2,.8,.2,1)}
/* ---------- the Invoker's familiar standing guard (89zb) ----------
   In front of its Invoker, toward the foe, hovering; its own health bar under it. While the Invoker rests on the bench
   it perches, small, over the band's side. Formed at the summon's contact, aside while its 3D self dives, shattered by
   the blow that empties it. */
.battle [data-part="near"]{position:relative}
.familiar{position:absolute;right:-40%;bottom:12%;width:59%;z-index:3;pointer-events:none;display:grid;justify-items:center;gap:2px;
  transition:opacity .35s var(--ease),transform .45s var(--ease)}
.familiar canvas{width:100%;height:auto;aspect-ratio:1;animation:familiar-hover 2.6s ease-in-out infinite;filter:drop-shadow(0 6px 10px #0009)}
.familiar-hp{display:block;width:46%;height:3px;margin-top:-16%;border-radius:2px;background:color-mix(in srgb,#000000 var(--a-glass),transparent);overflow:hidden}
.familiar-hp b{display:block;height:100%;width:100%;background:linear-gradient(90deg,#9fd8ff,#e9f6ff);transition:width .35s var(--ease)}
.familiar[data-state="forming"]{opacity:0;transform:scale(.3) translateY(20%)}
.familiar[data-state="up"]{opacity:1;transform:none}
.familiar[data-state="away"]{opacity:0;transform:scale(.7)}
.familiar[data-state="broken"]{animation:familiar-shatter .7s var(--ease) forwards}
.familiar[data-perch]{right:auto;bottom:auto;top:2px;left:calc(var(--perch,0) * 44px + 14px);width:40px}
.familiar[data-perch] .familiar-hp{width:70%}
@keyframes familiar-hover{0%,100%{transform:translateY(0)}50%{transform:translateY(-6%)}}
@keyframes familiar-shatter{0%{opacity:1;transform:none;filter:brightness(1)}25%{opacity:1;transform:scale(1.18);filter:brightness(2.2) blur(0)}100%{opacity:0;transform:scale(1.5) rotate(8deg);filter:brightness(3) blur(6px)}}

/* ---------- the Shadow's critical (74zzz...-shinobi-shadow): a gold burst and the word on the struck foe ---------- */
.crit-burst{position:absolute;left:50%;top:38%;z-index:7;pointer-events:none;transform:translate(-50%,-50%);
  font-family:var(--font-display,serif);font-weight:700;font-size:21px;letter-spacing:.01em;color:#fff4c4;
  text-shadow:0 0 4px #fff,0 0 10px #ffc23a,0 0 26px #ff8a1f,0 2px 0 #3a1a00;animation:crit-word 1.05s cubic-bezier(.2,.8,.3,1) both}
.crit-burst::before{content:"";position:absolute;left:50%;top:50%;width:230px;height:230px;transform:translate(-50%,-50%) scale(.2);border-radius:50%;filter:brightness(1.4);
  background:conic-gradient(from 0deg,#fff0 0 4%,#ffd76a 5% 7%,#fff0 8% 12%,#ffd76a 13% 15%,#fff0 16% 21%,#ffd76a 22% 24%,#fff0 25% 29%,#ffd76a 30% 32%,#fff0 33% 37%,#ffd76a 38% 40%,#fff0 41% 46%,#ffd76a 47% 49%,#fff0 50% 54%,#ffd76a 55% 57%,#fff0 58% 62%,#ffd76a 63% 65%,#fff0 66% 71%,#ffd76a 72% 74%,#fff0 75% 79%,#ffd76a 80% 82%,#fff0 83% 87%,#ffd76a 88% 90%,#fff0 91% 100%);
  -webkit-mask:radial-gradient(circle,#0000 18%,#000 30%,#0000 70%);mask:radial-gradient(circle,#0000 18%,#000 30%,#0000 70%);
  z-index:-1;animation:crit-star .7s ease-out both}
@keyframes crit-word{0%{opacity:0;transform:translate(-50%,-30%) scale(.6)}15%{opacity:1;transform:translate(-50%,-50%) scale(1.25)}35%{transform:translate(-50%,-55%) scale(1)}100%{opacity:0;transform:translate(-50%,-110%) scale(1)}}
@keyframes crit-star{0%{opacity:1;transform:translate(-50%,-50%) scale(.2) rotate(0)}100%{opacity:0;transform:translate(-50%,-50%) scale(1.3) rotate(25deg)}}
/* ---------- Earthbreaker's charge push (74zzz...-juggernaut-earthbreaker): the turn a foe lost, rising off it ---------- */
.charge-back{position:absolute;left:50%;top:30%;z-index:7;pointer-events:none;transform:translate(-50%,-50%);white-space:nowrap;
  font-weight:700;font-size:13px;letter-spacing:.01em;color:#f3d2a0;text-shadow:0 0 6px #b86a2a,0 2px 0 #2a1406;
  animation:charge-back 1.25s cubic-bezier(.2,.8,.3,1) both}
@keyframes charge-back{0%{opacity:0;transform:translate(-50%,-10%) scale(.7)}18%{opacity:1;transform:translate(-50%,-50%) scale(1.15)}40%{transform:translate(-50%,-60%) scale(1)}100%{opacity:0;transform:translate(-50%,-140%)}}
.crit-burst.venom-burst{color:#e6ffd8;text-shadow:0 0 4px #fff,0 0 10px #5fe06a,0 0 24px #1f9a3a,0 2px 0 #062a10}
.crit-burst.venom-burst::before{filter:hue-rotate(75deg) brightness(1.2)}
/* the Look page's live minifigure (89zf-minifig-look.js): over the pane, with its 360 control */
.frame .figure-look{position:absolute;inset:0;z-index:1;pointer-events:none} /* art paints over the card edge; overflow never steals a UI tap */
.frame .figure-look canvas{display:block;width:100%;height:100%}
.frame[data-look3d="on"] .shot{visibility:hidden}
.frame .look-turn{position:absolute;right:14px;top:62px;z-index:2;cursor:pointer;font-variant-numeric:tabular-nums}
/* Lv80 Look palette editor (89zs): opposite the 360 control so neither competes with the colourway pill. */
.frame .look-palette{position:absolute;left:14px;top:62px;z-index:3;cursor:pointer}
/* The editor is a true modal over the fixed Hero surface. It owns its own scroll; the game screen remains still. */
.hero-palette-editor{position:fixed;inset:0;z-index:80;display:grid;place-items:end center;pointer-events:auto}
.hero-palette-editor .palette-backdrop{position:absolute;inset:0;border:0;background:color-mix(in srgb,#020306 var(--a-scrim),transparent)}
.hero-palette-editor .palette-card,.hero-palette-editor .palette-picker{
  position:relative;width:min(100% - 24px,520px);max-height:min(82vh,720px);margin:0 12px 12px;padding:14px;
  border:1px solid color-mix(in srgb,var(--gold) 35%,transparent);border-radius:18px;
  background:var(--popup);box-shadow:0 -18px 60px #0009;
  display:flex;flex-direction:column;gap:12px;overflow:hidden
}
.hero-palette-editor .palette-card>header,.hero-palette-editor .palette-picker>header{
  display:flex;align-items:center;gap:10px;min-height:42px
}
.hero-palette-editor header>div{min-width:0;flex:1}
.hero-palette-editor header small,.palette-help{color:var(--muted);letter-spacing:.01em;text-transform:uppercase;font-size:10px}
.hero-palette-editor header h3{margin:2px 0 0;font:600 22px/1.05 var(--serif);color:var(--ink)}
.palette-help{margin:0;line-height:1.45;text-transform:none;letter-spacing:.01em}
.palette-parts{display:grid;gap:7px;overflow:auto;padding-right:2px}
.palette-part{
  min-height:52px;width:100%;display:grid;grid-template-columns:32px minmax(0,1fr) auto;align-items:center;gap:10px;
  border:1px solid color-mix(in srgb,var(--line) 88%,transparent);border-radius:12px;padding:7px 9px;
  background:color-mix(in srgb,rgb(255,255,255) var(--a-wash),transparent);color:var(--ink);text-align:left;cursor:pointer
}
.palette-part>span{display:grid;gap:2px;min-width:0}.palette-part b{font-size:12px;letter-spacing:.01em;text-transform:uppercase}
.palette-part small{font-size:10px;color:var(--muted);font-variant-numeric:tabular-nums}
.palette-swatch{display:block;width:26px;height:26px;border-radius:50%;background:var(--sw,#fff);border:2px solid #fff9;box-shadow:0 0 0 1px #0008,0 3px 9px #0008}
.palette-swatch.big{width:34px;height:34px;flex:0 0 auto}
.palette-presets{display:flex;gap:4px;font-style:normal}
.palette-preset{width:16px;height:16px;padding:0;border-radius:50%;border:1px solid #fff6;background:var(--sw,#fff);box-shadow:0 0 0 1px #0008;cursor:pointer}
.hero-palette-editor footer{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.hero-palette-editor footer .btn{margin:0;min-height:42px}
.hero-palette-editor .palette-picker{position:absolute;left:50%;bottom:0;transform:translateX(-50%);z-index:2;max-height:min(88vh,760px)}
.hero-palette-editor .palette-picker[hidden]{display:none}
.palette-picker canvas[data-palette-wheel]{display:block;width:min(224px,70vw);height:min(224px,70vw);aspect-ratio:1;margin:0 auto;border-radius:50%;touch-action:none;box-shadow:0 8px 24px #0009}
.palette-rgb{display:grid;gap:8px}
.palette-rgb label{display:grid;grid-template-columns:18px minmax(0,1fr) 34px;align-items:center;gap:8px;font-size:11px}
.palette-rgb b{color:var(--muted);letter-spacing:.01em}
.palette-rgb input[type=range]{width:100%;accent-color:var(--gold)}
.palette-rgb input[type=number]{width:42px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,#090b12 var(--a-glass),transparent);color:var(--ink);padding:6px 4px;text-align:center;font-variant-numeric:tabular-nums}
.palette-hex{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:10px;color:var(--muted);font-size:10px;letter-spacing:.01em}
.palette-hex input{width:100%;border:1px solid var(--line);border-radius:10px;background:color-mix(in srgb,#090b12 var(--a-glass),transparent);color:var(--ink);padding:10px 12px;font:600 14px/1 var(--mono);letter-spacing:.01em;text-transform:uppercase}
@media (max-height:680px){
  .hero-palette-editor .palette-card,.hero-palette-editor .palette-picker{max-height:91vh;padding:10px;gap:8px}
  .palette-picker canvas[data-palette-wheel]{width:min(176px,54vw);height:min(176px,54vw)}
}
/* the grids' live minifigures (89zg-minifig-grid.js): one canvas over the screen, touches pass through */
/* in the view's own layers (owner, 27 Sep 2026: "Make sure all the ui is always on top including skill name display and
   damage display"): above the cards' glass, under the head (5), the field (2), the foot panel (2), captions (2) and sheets */
/* opacity .999, deliberately (1 Oct 2026): on the owner's Android (Chrome 154, Mali-G57) every additive glow on the figures - the
   Necromancer's rising ghosts first - showed as a pale solid disc, while the same objects on a probe page and a 2D copy of this very
   canvas (89zzu, ?figcopy=1) were soft. A full-screen WebGL canvas with nothing applied to it is what Chrome may hand to the
   hardware overlay compositor, which mishandles its premultiplied alpha; any blending the compositor must do itself keeps it
   on the proper path. The effects canvas stopped showing the discs when #606 gave it a blend mode, for the same reason. */
.figure-grid{position:fixed;left:0;top:0;width:100vw;height:100vh;pointer-events:none;z-index:1;opacity:.999}
/* the effects canvas too (owner, 1 Oct 2026, after the figures were fixed: "boss skill still has the bug ... object with solid bg and
   some glow render as disc"): #606's blend mode had kept it off that path by accident; without it, it is bare again */
.battle-jobfx{opacity:.999}
.figure-over{position:fixed;left:0;top:0;width:100vw;height:100vh;pointer-events:none;z-index:1}
/* what the skill effects lay on the ground, just under the figures (89zh) */
.figure-ground{position:fixed;left:0;top:0;width:100vw;height:100vh;pointer-events:none;z-index:1}
.battle{position:relative;z-index:2}
#view > .float{position:relative;z-index:2}

/* the Bastion catches a blow from the bench (89zzv1, owner 7 Oct 2026): a ring of her colour on the hero she caught it for,
   and her name under the number */
.fighter .shot>.catch{position:absolute;inset:-6%;border-radius:50%;pointer-events:none;border:2px solid var(--catch,#ffd27a);
  box-shadow:0 0 16px var(--catch,#ffd27a),inset 0 0 12px var(--catch,#ffd27a);animation:bastion-catch .52s var(--ease) forwards}
@keyframes bastion-catch{0%{opacity:0;transform:scale(.7)}25%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(1.18)}}
.num[data-caught]::after{content:attr(data-caught);display:block;font-size:.5em;letter-spacing:.01em;text-transform:uppercase;color:var(--catch,#ffd27a);text-shadow:0 0 6px var(--catch,#ffd27a)}
/* her reflect (74zzb): a bolt of her light from where the blow was taken to the foe it answers */
.battle>.shield-bolt{position:absolute;left:var(--x);top:var(--y);width:var(--w);height:3px;margin-top:-1.5px;transform-origin:0 50%;transform:rotate(var(--r));pointer-events:none;z-index:1;
  --shield:#9ad8ff;background:linear-gradient(90deg,color-mix(in srgb,#fff 80%,var(--shield)),var(--shield) 60%,transparent);box-shadow:0 0 10px var(--shield);animation:shield-bolt .5s var(--ease) forwards}
.battle>.shield-bolt[data-sign="fire"]{--shield:#ff7a6a}.battle>.shield-bolt[data-sign="water"]{--shield:#7fb0ff}.battle>.shield-bolt[data-sign="earth"]{--shield:#9fe08a}
.battle>.shield-bolt[data-sign="wind"]{--shield:#8ee8f0}.battle>.shield-bolt[data-sign="lightning"]{--shield:#ffe07a}
@keyframes shield-bolt{0%{opacity:0;transform:rotate(var(--r)) scaleX(0)}25%{opacity:1;transform:rotate(var(--r)) scaleX(1)}100%{opacity:0;transform:rotate(var(--r)) scaleX(1)}}
/* ---------- line combos (74zzd): a gold beam links the pair, both glow ---------- */
.battle>.combo-link{position:absolute;left:var(--x);top:var(--y);width:var(--w);height:4px;margin-top:-2px;transform-origin:0 50%;transform:rotate(var(--r)) scaleX(0);pointer-events:none;z-index:3;
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--combo,#ffe7a8) 70%,#fff) 12%,#fff6dc 50%,color-mix(in srgb,var(--combo,#ffe7a8) 70%,#fff) 88%,transparent);box-shadow:0 0 12px var(--combo,#ffcf6a),0 0 26px color-mix(in srgb,var(--combo,#ffb84a) 55%,transparent);border-radius:2px;animation:combo-link 1.2s var(--ease) forwards}
.battle>.combo-link[data-sig]{height:6px;margin-top:-3px;box-shadow:0 0 16px #ffd27a,0 0 34px #ff9f3aaa}
@keyframes combo-link{0%{opacity:0;transform:rotate(var(--r)) scaleX(0)}22%{opacity:1;transform:rotate(var(--r)) scaleX(1)}70%{opacity:1}100%{opacity:0;transform:rotate(var(--r)) scaleX(1)}}
.battle .fighter[data-combo] .vitals .hp{box-shadow:0 0 10px #ffcf6a}
/* a help row that is a list (the signature combos): name above, what it does below */
/* Help leads with the basics (UI/UX audit 12): three things to do, then everything else folded under More. */
.sheet .info-basics{margin:0;padding:0 0 0 1.4em;display:grid;gap:var(--s1);width:100%;font-size:var(--t-body);line-height:1.4;color:#e4e2dc}
.sheet .info-basics li::marker{color:var(--gold);font-weight:600}
.sheet .info-more{width:100%}
.sheet .info-more > summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px;min-height:var(--control);
  font-size:var(--t-label);font-weight:600;letter-spacing:.01em;text-transform:uppercase;color:var(--gold)}
.sheet .info-more > summary::-webkit-details-marker{display:none}
.sheet .info-more > summary::before{content:"+";font-weight:700}
.sheet .info-more[open] > summary::before{content:"−"}
.sheet .info-list{list-style:none;margin:var(--s1) 0 0;padding:0;display:grid;gap:var(--s1)}
.sheet .info-list li{display:grid;gap:1px;padding-left:var(--s2);border-left:2px solid color-mix(in srgb,var(--gold) 45%,transparent)}
.sheet .info-list li b{font-size:var(--t-body);letter-spacing:.01em;text-transform:none;color:var(--ink)}
.sheet .info-list li span{font-size:var(--t-micro);color:var(--muted);line-height:1.4}
/* pause and 2x beside the field's (i) (74zze): lit while on */
.icon[data-act="pace-pause"][aria-pressed="true"],.icon.pace-speed[aria-pressed="true"]{border-color:color-mix(in srgb,var(--gold) 70%,transparent);color:var(--gold);box-shadow:0 0 10px color-mix(in srgb,var(--gold) 35%,transparent)}
.icon.pace-speed b{font-size:11px;font-weight:700;letter-spacing:.01em;line-height:1;white-space:nowrap}

/* ---------- Stunned (89zm, rules 57nz; owner, 28 Sep 2026) ----------
   A stunned fighter reels from side to side (the shot's box carries its minifigure, 89zg) and three daze stars blink over its
   head, in place - nothing circles. A staggered Anchor shows the stars only. The floating word is the numbers' own style. */
.battle .fighter[data-stunned] .shot,.battle .fighter[data-reeling] .shot{animation:stun-reel 1.1s ease-in-out infinite;transform-origin:50% 90%}
@keyframes stun-reel{0%,100%{transform:translateX(0) rotate(0)}25%{transform:translateX(-5%) rotate(-3deg)}75%{transform:translateX(5%) rotate(3deg)}}
.stun-daze{position:absolute;left:50%;top:4%;width:64%;height:18%;transform:translateX(-50%);z-index:3;pointer-events:none;display:flex;justify-content:space-between;align-items:center}
.fighter[data-rank="boss"] .stun-daze{top:0}
.stun-daze b{font-style:normal;font-size:15px;line-height:1;color:#ffe9a8;text-shadow:0 0 6px #ffd76a,0 0 12px #ffb54a99,0 1px 3px #000c;animation:stun-twinkle .9s ease-in-out infinite}
.stun-daze b:nth-child(2){transform:translateY(-40%);animation-delay:-.3s;font-size:18px}
.stun-daze b:nth-child(3){animation-delay:-.6s}
@keyframes stun-twinkle{0%,100%{opacity:.35;scale:.7}50%{opacity:1;scale:1.15}}
.num[data-tone="stun"]{color:#ffe08a;text-shadow:0 2px 7px #000d,0 0 9px #ffb54a99}

/* ---------- the band's actions on one row, and the saved-bands sheet (72b; owner, 29 Sep 2026) ---------- */
.band-actions[data-three="true"]{grid-template-columns:repeat(3,minmax(0,1fr))}
.band-actions[data-three="true"] .btn{padding:0 var(--s2)}
/* Ten saved slots live in their OWN scroller. The sheet title, explanation and Close button never move,
   so a long saved-band list cannot strand the way out below the phone viewport. */
.savedbands-sheet .card>.savedbands-lede,.savedbands-sheet .card>.savedbands{justify-self:stretch;width:100%}
.savedbands-sheet:not(.savedband-preview) .card{
  height:min(calc(var(--vh,100dvh) * .78),720px);overflow:hidden;
  grid-template-rows:auto auto auto minmax(0,1fr) auto;
}
.savedbands-lede{margin:0 0 var(--s2);color:#b9bcc8;font-size:.84rem;line-height:1.45}
.savedbands{
  display:grid;gap:var(--s2);margin-bottom:var(--s2);min-height:0;
  max-height:calc(var(--vh,100dvh) * .48);overflow-y:auto;overscroll-behavior:contain;touch-action:pan-y;
  padding:1px var(--s1) 1px 0;scrollbar-width:none;
}
.savedbands::-webkit-scrollbar{width:0;height:0;display:none}
.savedband{display:grid;gap:var(--s2);padding:var(--s3);border:1px solid color-mix(in srgb,var(--gold) 22%,transparent);border-radius:14px;background:color-mix(in srgb,#ffffff var(--a-wash),transparent);min-width:0}
.savedband[data-full="true"]{border-color:color-mix(in srgb,var(--gold) 42%,transparent)}
.savedband-head{display:grid;gap:3px;min-width:0}
.savedband-name{
  width:100%;min-width:0;margin:0;padding:2px 0 4px;border:0;border-bottom:1px solid transparent;
  border-radius:0;background:transparent;color:var(--ink);font:700 14px/1.2 var(--sans,inherit);letter-spacing:.01em;
  outline:none;
}
.savedband-name:hover{border-bottom-color:color-mix(in srgb,var(--gold) 28%,transparent)}
.savedband-name:focus{border-bottom-color:var(--gold);color:var(--gold-hi)}
.savedband-head small{font-size:12px;color:#9497a3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* a slot's three actions on one row (owner, 29 Sep 2026: "Save, load, preview in one row"), and the preview: the Seat sheet's nine */
.savedband-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s1)}
.savedband-actions .btn{padding:0 var(--s1)}
.savedbands-sheet .card>.savedband-grid,.savedbands-sheet .card>.savedband-lines,.savedband-preview .card>.btn{justify-self:stretch;width:100%}
.savedband-seat{cursor:default}
.savedband-lines{margin:var(--s1) 0 var(--s2);font-size:var(--t-label);letter-spacing:.01em;text-transform:uppercase;color:var(--gold)}

/* ---------- the full moon over an Ultimate with a Moonblade in it (74zzz...-moonblade-waltz); opacity is animated from JS
   and held flat across the rupture; everything else here is still ---------- */
.ult-moon{position:absolute;top:3%;left:74%;width:28%;aspect-ratio:1;translate:-50% 0;z-index:7;pointer-events:none;border-radius:50%;opacity:0;mix-blend-mode:screen;
  background:radial-gradient(circle at 44% 40%,#fbfdff 0 22%,#e6edf8 38%,#c9d6ee 58%,rgba(201,214,238,.55) 66%,rgba(170,190,230,0) 72%),
    radial-gradient(circle at 36% 58%,rgba(120,136,170,.35) 0 7%,transparent 8%),radial-gradient(circle at 60% 36%,rgba(120,136,170,.3) 0 5%,transparent 6%),radial-gradient(circle at 58% 64%,rgba(120,136,170,.28) 0 9%,transparent 10%);
  box-shadow:0 0 36px 14px rgba(200,216,255,.45),0 0 110px 40px rgba(160,190,255,.22)}
/* the colourway pill under the 360 pill on the Look pane (89zr) */
.frame .look-colour{position:absolute;right:14px;top:106px;z-index:2;cursor:pointer;max-width:46%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* the Awakened chip, and before Level 80 the Limit Break 5 Outfit chip in its place (89zzx) */
.frame .look-awaken,.frame .look-outfit{position:absolute;left:14px;top:106px;z-index:3;cursor:pointer}
.frame .look-colour.is-awakened{cursor:default;border-color:#c9a04a;color:#f6e2b0}
.frame .look-colour.is-awakened.is-set-switch{cursor:pointer}
/* worn, the outfit's name sits on the same glass as the Awakened chip beside it: the figure's weapon and its aura rise behind it
   (owner, 24 Oct 2026, a Necromancer's Grave Tide and The Black Goat: "Fix the chip by adding translucent bg same as awakened chip") */
.frame .look-awaken[aria-pressed="true"],.frame .look-outfit[aria-pressed="true"],.frame .look-colour.is-awakened{border-color:#c9a04a;background:linear-gradient(180deg,color-mix(in srgb,#a8222e var(--a-glass),transparent),color-mix(in srgb,#7a141d var(--a-glass),transparent));color:#f6e2b0}
/* the Ground Loop (58y): its strip over the nodes of a completed ground, and the field it waits on */
.campaign-body .loop{display:flex;flex-direction:column;gap:6px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--gold) 38%,transparent);border-radius:12px;background:color-mix(in srgb,var(--gold) var(--a-wash),transparent)}
.campaign-body .loop p{margin:0;display:flex;justify-content:space-between;align-items:baseline;letter-spacing:.01em;text-transform:uppercase;font-size:12px}
.campaign-body .loop p span{font-variant-numeric:tabular-nums;color:var(--gold)}
.campaign-body .loop small{opacity:.72;font-size:11px;line-height:1.35}
/* the description and the Auto loop button share a row, the button on the right */
.campaign-body .loop .loop-row{display:flex;align-items:center;gap:10px}
.campaign-body .loop .loop-row small{flex:1 1 auto;min-width:0}
.campaign-body .loop [data-act=autoloop]{flex:0 0 auto;white-space:nowrap}
.campaign-body .loop .pips{display:grid;grid-template-columns:repeat(9,1fr);gap:4px}
.campaign-body .loop .pips i{height:6px;border-radius:3px;background:color-mix(in srgb,var(--gold) 16%,transparent)}
.campaign-body .loop .pips i[data-on]{background:var(--gold)}
.campaign-body .loop .pips i[data-next]{background:color-mix(in srgb,var(--gold) 55%,transparent);box-shadow:0 0 8px color-mix(in srgb,var(--gold) 60%,transparent)}
/* drawn inside the card's edge: the ground scrolls, and a scroll box clips what is outside it (owner, 30 Sep 2026: "Dotted line is gone on the left side") */
.node[data-loop-next]{outline:1.5px dashed color-mix(in srgb,var(--gold) 85%,transparent);outline-offset:-5px}

.figure-grid-copy{visibility:visible}





/* ---------- Job lore (89zzl, owner 5 Oct 2026) ----------
   The nameplate sits on the figure's pedestal inside .shot, so the card keeps its height; .shot takes no pointers, the
   plate does, and its ::before widens the tap past the small type. */
.tile .jobplate{position:absolute;left:50%;bottom:3px;transform:translateX(-50%);z-index:2;pointer-events:auto;cursor:pointer;
  max-width:94%;display:block;
  font-family:inherit;font-size:8px;line-height:1;font-weight:600;letter-spacing:.01em;text-transform:uppercase;color:var(--gold);
  background:color-mix(in srgb,#0b0f1a var(--a-glass),transparent);border:1px solid color-mix(in srgb,var(--gold) 40%,transparent);border-radius:3px;padding:3px 4px}
.tile .jobplate::before{content:"";position:absolute;inset:-7px -4px}   /* the tap, wider than the type; the clip is on the span so it does not clip this */
.tile .jobplate span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tile .jobplate:focus-visible{outline:1px solid var(--gold);outline-offset:2px}
.lore{display:grid;gap:var(--s2);width:100%}
.lore p{margin:0;font-size:var(--t-body);line-height:1.5;color:#c9cbd4}
.lore strong{font-weight:600;color:#f1e7cf}
.lore em{font-style:italic}
.lore .lore-epithet{font-family:var(--serif);font-size:15px;line-height:1.3;color:var(--gold)}
.lore h3{margin:0;font-family:var(--serif);font-size:22px;line-height:1.1;font-weight:400}
.lore-say{margin:2px 0;padding:var(--s2) var(--s3);border-left:2px solid var(--gold);background:color-mix(in srgb,#ffffff var(--a-wash),transparent);border-radius:0 6px 6px 0}
.lore-say p{font-family:var(--serif);font-style:italic;font-size:14px;line-height:1.4;color:#efe4c8}
.hero-lore{display:grid;gap:var(--s2)}
.hero-lore>.eyebrow{margin:0;color:var(--muted)}
.hero-lore .rows{display:grid;gap:var(--s2)}
.hero-lore .rows>div{display:grid;gap:2px}
.hero-lore .rows b{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;text-transform:uppercase;color:var(--gold)}
.hero-lore .btn{width:100%}
.lore-index{display:grid;width:100%;border-top:1px solid var(--panel-line)}
.lore-row{display:grid;gap:2px;text-align:left;width:100%;padding:var(--s2) 2px;background:none;border:0;border-bottom:1px solid var(--panel-line);color:inherit;cursor:pointer}
.lore-row b{font-size:var(--t-label);font-weight:600;letter-spacing:.01em;text-transform:uppercase;color:var(--gold)}
.lore-row span{font-family:var(--serif);font-style:italic;font-size:13px;line-height:1.3;color:#c9cbd4}
.lore-row:active{background:color-mix(in srgb,#ffffff var(--a-wash-on),transparent)}
.lore-door{display:grid;gap:var(--s1);width:100%}
.lore-door .eyebrow{margin:0;color:var(--muted)}
/* ---------- Awakening (57zz-awakening rules, 74z{20}-awakening field, 89zzw-awakening-ui) ----------
   Above a skill and below the Ultimate: its name is a skill name with AWAKENING over it, larger and held a little longer;
   the chrome steps back less than for the Ultimate. On the Band it is a fact chosen like the Leader, worn by one seat. */
.skillname.awaken{gap:3px;animation:awakenname 1.25s var(--ease) both}
.skillname.awaken small{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;line-height:1;
  color:color-mix(in srgb,var(--tint,var(--gold)) 82%,var(--ink))}
.skillname.awaken b{font-size:clamp(21px,calc(var(--fvw,1vw) * 6.2),30px);
  text-shadow:0 2px 18px #000,0 0 26px var(--tint,var(--gold)),0 0 60px color-mix(in srgb,var(--tint,var(--gold)) 55%,transparent)}
@keyframes awakenname{
  0%{opacity:0;transform:translate(-50%,-50%) translateY(12px) scale(.92)}
  14%{opacity:1;transform:translate(-50%,-50%) scale(1.03)}
  24%{transform:translate(-50%,-50%) scale(1)}
  80%{opacity:1}
  100%{opacity:0;transform:translate(-50%,-52%) scale(1)}
}
body[data-awakening] .head,body[data-awakening] .dock,body[data-awakening] [data-part=deeds]{opacity:.42;transition:opacity .24s var(--ease)}
.fighter[data-risen] .shot{animation:awakenrise .9s var(--ease) both}
@keyframes awakenrise{0%{filter:brightness(2.1) saturate(.45);transform:translateY(7px)}100%{filter:none;transform:none}}
/* Heavenfall: the Skylancer leaves the top of the screen at full size and drops back into its seat */
.fighter[data-awaken-leap] .shot{transform:translateY(calc(var(--vh,100dvh) * -1.1));opacity:0;transition:transform .26s cubic-bezier(.55,0,.9,.45),opacity .12s .16s}
/* the Awakening practice keeps each hero of its line in its own row, as a fight does: never three in one squeezed row */
.battle[data-awaken-practice][data-mates] [data-part="near"] .fighter{grid-row:auto}
/* the Band: a pill beside the Leader's, a selection mode in its language, and the chosen seat's chip */
button.pill.awaken i{font-style:normal;color:var(--muted);margin-right:.5em;letter-spacing:.01em}
button.pill.awaken[data-on] i{color:color-mix(in srgb,var(--gold) 70%,var(--muted))}
.band[data-mode="awaken"]::before{
  content:"";position:absolute;inset:-4px;z-index:0;pointer-events:none;border-radius:10px;
  border:1px solid color-mix(in srgb,var(--gold) 58%,transparent);
  box-shadow:inset 0 0 24px color-mix(in srgb,var(--gold) 7%,transparent),0 0 14px #0007
}
.band[data-mode="awaken"] .lines{opacity:.16}
.band[data-mode="awaken"] .seat[data-mode-state="selected"]{
  opacity:1;border-color:var(--gold);box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--gold) 55%,transparent),0 0 14px color-mix(in srgb,var(--gold) 24%,transparent)
}
.band[data-mode="awaken"] .seat[data-mode-state="available"]{
  opacity:1;border-color:color-mix(in srgb,var(--green) 78%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--green) 34%,transparent)
}
.band[data-mode="awaken"] .seat[data-mode-state="unavailable"]{opacity:.34;filter:saturate(.55) brightness(.78)}
.awaken-mark{position:absolute;top:4px;left:4px;right:4px;z-index:4;font-size:var(--t-micro);line-height:1;letter-spacing:.01em;font-weight:700;text-align:center;
  text-transform:uppercase;color:#1a1408;background:linear-gradient(180deg,var(--gold-hi,#f3dc9a),var(--gold));
  padding:4px 4px 3px;border-radius:4px;white-space:nowrap;pointer-events:none;box-shadow:0 1px 4px #0008}
.band[data-mode="awaken"] .seat[data-mode-state="selected"] .awaken-mark{box-shadow:0 0 0 1px #1a1408,0 0 10px color-mix(in srgb,var(--gold) 70%,transparent)}
.band[data-mode="awaken"] .seat[data-mode-state="selected"] .worn,.band[data-mode="awaken"] .seat[data-mode-state="unavailable"] .worn{display:none}
.band[data-mode="awaken"] .seat[data-mode-state="available"] .awaken-can{position:absolute;top:4px;left:4px;right:4px;z-index:4;font-size:var(--t-micro);line-height:1;letter-spacing:.01em;font-weight:600;text-align:center;
  text-transform:uppercase;color:#d9f5df;background:color-mix(in srgb,color-mix(in srgb,var(--green) 55%,#0b0e17) var(--a-glass),transparent);padding:4px 4px 3px;border-radius:4px;pointer-events:none}
/* the Skill pane: the Awakening is its own panel under the skills, with its two presses */
.hero-awakening .rows p em{font-style:normal;color:var(--gold)}
.hero-awakening .pair{margin-top:var(--s1)}

/* ---------- Home, one screen (owner, 10 Oct 2026: "modern and make sure it fits the phone height without scrolling") ----------
   The stack is a column that never scrolls: facts, the first-fall card (until the first fall), the actions, provisions and
   archive in one quiet panel; the nine standing over the dock take whatever room is left (62-figure-boxes fit). */
.home2{display:flex;flex-direction:column;gap:var(--s2);flex:0 0 auto}
.home2 .tally{padding:var(--s1) var(--s2);row-gap:var(--s1);background:transparent;box-shadow:none;border-color:var(--line)}
.home2 .tell strong{font-size:14px}
.home2 .tell small{font-size:10px}
.home2 .state{padding:var(--s3) var(--s4);gap:var(--s1)}
.home2 .state h2{font-size:17px}
.home2 .panel[data-part="actions"]{gap:var(--s1)}
.home2 .panel.compact{padding:var(--s1);gap:var(--s1);background:transparent;box-shadow:none;border-color:var(--line)}
.home2 .panel.compact .seg button{min-height:34px}
.home2 .link{border:0;background:transparent;color:var(--muted);font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;padding:var(--s1) 0 0;cursor:pointer}
.home2 .link:hover{color:var(--gold-hi)}
.sheet .card .link{border:0;background:transparent;color:var(--muted);font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;min-height:44px;padding:0 var(--s2);justify-self:center;margin:0 auto;display:block;cursor:pointer;text-shadow:var(--glass-shadow)}   /* Delete account… under Sign out: the quiet way to the one act with no way back */
.sheet .card .link:hover{color:var(--gold-hi)}
.sheet .rows a{color:var(--gold-hi);text-decoration:underline;text-decoration-color:color-mix(in srgb,var(--gold) 50%,transparent);text-underline-offset:2px}   /* the privacy page's contact: the game's gold, not the browser's blue */
.auto-because{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--s1)}   /* Auto band says why (72d) */
.auto-because-lead{font-size:var(--t-micro);letter-spacing:.01em;text-transform:uppercase;color:var(--muted);margin-right:2px}

/* ---------- clear glass (owner, 10 Oct 2026: "remove frosted glass and revert back to clear glass. Frosted make it lag") ----------
   No backdrop-filter anywhere: every glass surface re-samples what is behind it every frame, and on the owner's phone the
   frosted pass lagged. The panels lean on their translucent fill alone, the fill a touch fuller than under frost so text
   reads; a light edge along the top is kept (it is a shadow, not a filter). */
body{--panel-a:color-mix(in srgb,#151b2d var(--a-glass),transparent);--panel-b:color-mix(in srgb,#0a0e1a var(--a-glass),transparent)}
:is(.panel,.tally,.state,.tile,.node,.card){box-shadow:inset 0 1px 0 #ffffff1c,0 18px 70px #0007}
/* the Heroes page's empty cells: the grid stays 3x3 (owner, 10 Oct 2026) */
.tile.blank{border-color:transparent;background:transparent;box-shadow:none;pointer-events:none}
.tile.blank::after{content:none}   /* no caption scrim on an empty cell (owner, 10 Oct 2026: "hide the black shadow overlay cards") */

/* the awakened passive (89zzw1): the same passive at Level 80 - a gold tag once awake, greyed until then (the Skills pane and the held-card sheet) */
.rows .awakened-passive b{display:flex;align-items:center;gap:.5em}
.awakened-tag{display:inline-block;padding:1px 6px;border:1px solid var(--gold);border-radius:999px;color:var(--gold-hi);
  font-size:var(--t-micro);font-weight:700;letter-spacing:.01em;text-transform:uppercase;line-height:1.4}
.rows .awakened-passive:not(.is-awake) b,.rows .awakened-passive:not(.is-awake) p{color:var(--muted)}
.rows .awakened-passive:not(.is-awake) .awakened-tag{border-color:var(--muted);color:var(--muted)}
.rows .awakened-passive:not(.is-awake) p{opacity:.72}
.rows .awakened-passive .awakened-wait{opacity:1;font-size:var(--t-micro);letter-spacing:.01em}
/* the transparency standard's guard (owner, 11 Oct 2026): text on a glass surface over a busy picture wears a dark halo, so it keeps 4.5:1
   against the worst pixel behind it at 360 px - never a darker tier. Where it is worn: the panels and tallies (Home's stats over the
   heroes and the Ground), buttons, the result card, the battle tags, Home's sign-in line (on no surface, over the art). A rule of the element's own text-shadow still wins */
:is(.panel,.tally,.state,.btn,.verdict .card,.battle-mechanic,.status-chip,.status-overflow,.home2 .link){text-shadow:var(--glass-shadow)}

/* an Anchor's rounds in the briefing (89zzze): one cell a round - the band's nine seats with that round's line lit, its Skill's blow
   where it lands */
.anchor-plan{margin-top:var(--s2)}
.anchor-plan .eyebrow{margin:0 0 4px;color:var(--muted);text-align:left}
.anchor-plan .cells{display:grid;grid-template-columns:repeat(9,minmax(0,1fr));gap:3px}
.anchor-plan .cell{display:grid;justify-items:center;gap:3px;padding:4px 0 3px;border:1px solid var(--panel-line);border-radius:5px;
  background:color-mix(in srgb,#0b0f1a var(--a-glass),transparent)}
.anchor-plan .cell small{font-size:var(--t-micro);letter-spacing:.01em;color:var(--muted);font-variant-numeric:tabular-nums;line-height:1}
.anchor-plan .seats{display:grid;grid-template-columns:repeat(3,3px);gap:2px}
.anchor-plan .seats i{width:3px;height:3px;border-radius:1px;background:color-mix(in srgb,#c3c5cd 28%,transparent)}
.anchor-plan .seats i[data-on]{background:#e7e1d2}
.anchor-plan .cell b{font-size:var(--t-micro);font-weight:700;letter-spacing:.01em;color:var(--gold);min-height:10px;line-height:1;font-variant-numeric:tabular-nums}
.anchor-plan .cell[data-cast]{border-color:color-mix(in srgb,var(--gold) 62%,transparent);background:color-mix(in srgb,var(--gold) var(--a-wash-on),transparent)}
.anchor-plan .cell[data-cast] .seats i[data-on]{background:var(--gold)}
/* the synergy rail (72c): one track of chips across the rest of the band's second row; it scrolls inside itself and never wraps */
.accords .syn-wrap{flex:1 1 100%;display:flex;align-items:center;gap:4px;min-width:0}   /* its own line, arrows at both ends */
.accords .syn-rail{display:flex;gap:var(--s1);flex:1 1 0;min-width:0;overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:none;touch-action:pan-x;
  contain:inline-size}   /* its chips never widen the head: the rail takes the line between its arrows and scrolls the rest */
.syn-turn{flex:0 0 auto;width:28px;height:28px;padding:0;display:grid;place-items:center;border:1px solid var(--line-soft);border-radius:50%;
  background:color-mix(in srgb,#0d1020 var(--a-glass),transparent);color:#e6e7ec;cursor:pointer;transition:opacity .2s ease,transform .14s var(--ease)}
.syn-turn svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.syn-turn:active{transform:scale(.92)}
.syn-turn:disabled{opacity:.22;cursor:default;transform:none}
.syn-wrap[data-fits] .syn-turn{display:none}
.accords .syn-rail::-webkit-scrollbar{display:none}
.syn-rail .pill{flex:0 0 auto}
.syn-rail .pill[data-lit]:not([data-rarity]){color:var(--gold);border-color:color-mix(in srgb,var(--gold) 55%,transparent)}
.syn-rail .pill[data-near]{color:var(--muted);border-style:dashed;border-color:color-mix(in srgb,var(--muted) 50%,transparent)}
/* a held hero in Move: what swapping at each other seat gains (sage) or only loses (terracotta) */
.seat .syn-delta{position:absolute;left:4px;right:4px;bottom:4px;z-index:4;padding:2px 4px;border-radius:4px;font-size:var(--t-micro);font-weight:600;
  letter-spacing:.01em;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#9fd3ab;
  background:color-mix(in srgb,#0b0f1a var(--a-scrim),transparent);border:1px solid color-mix(in srgb,#9fd3ab 40%,transparent)}
.seat .syn-delta[data-gain="0"]{color:#e0a08a;border-color:color-mix(in srgb,#e0a08a 40%,transparent)}
.seat .syn-delta i{font-style:normal;opacity:.7}
