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

@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{
  --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:linear-gradient(180deg,#151b2d4d,#0a0e1a59);
  --panel-line:#ffffff24;
  /* No blur anywhere. backdrop-filter is the most expensive thing an interface can ask for - every
     glass surface 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. The panels stay translucent and lean on opacity alone (scratchpad/fall-perf.mjs). */
  --gold:#d9bd75;
  --gold-hi:#f0d58d;
  --grey:#9aa0ae;
  --green:#7cc08d;
  --blue:#91b4ff;
  --violet:#9277ff;
  --rarity:var(--grey);

  --serif:"Playfair Display",Georgia,"Times New Roman",serif;
  --sans: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,6.2vw,30px);
  --t-body:11.5px;
  --t-label:9.5px;
  --t-micro:9px;
  --track-display:-.015em;
  --track-label:.16em;
  --track-micro:.22em;
  --track-caption:.1em;
  --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;
  --travel:44px;
  color-scheme:dark;
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;overflow:hidden;
  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%}

/* ---------- 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}
.vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(circle at 50% 42%,transparent 38%,#03040a55 74%,#010207e6 112%)}
.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 screen ---------- */
#view{
  position:relative;z-index:1;
  height:100dvh;width:100%;max-width:calc(var(--column) + 2 * var(--s4));margin:0 auto;
  padding:max(var(--s4),env(safe-area-inset-top)) max(var(--s4),env(safe-area-inset-left)) calc(env(safe-area-inset-bottom) + 78px);
  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}
/* 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}
/* 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}}
@media (prefers-reduced-motion:reduce){[data-view]{animation:none!important;visibility:visible}}

/* ---------- the head: what this is, whose it is, how rare ---------- */
.head{display:grid;gap:var(--s2);flex:0 0 auto}
.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:var(--track-micro);text-transform:uppercase;color:#b9b2a2;line-height:var(--lead-label)}
.title{
  margin:var(--s1) 0 0;font-family:var(--serif);font-weight:500;
  font-size:var(--t-display);line-height:var(--lead-display);letter-spacing:var(--track-display);
  text-wrap:balance;overflow-wrap:anywhere;
}
/* 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.move{margin-left:auto}
.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:#090c1499;
  font-size:var(--t-micro);letter-spacing:var(--track-label);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(--gold)}

/* ---------- 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:var(--track-label);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:#ffffff06;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,#d9bd7524,#d9bd750a);color:#f6e6bf;
}
/* 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:#090c1440;color:#c9cbd4}

/* ---------- 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:#090c1459;color:#b4b7c1;
  font-size:var(--t-micro);
}
.seg button[aria-pressed="true"],.seg button[aria-current]{
  color:#fff4d9;border-color:color-mix(in srgb,var(--gold) 38%,transparent);
  background:linear-gradient(180deg,#d9bd7520,#d9bd750a);box-shadow:inset 0 0 18px #d9bd7512;
}
.seg button:disabled,.btn:disabled{opacity:.34;transform:none;cursor:default}
.pair{display:grid;grid-template-columns:1fr 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 sprite 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}
.shot canvas{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:calc(100% * var(--spill,1));max-width:none;height:auto;
  image-rendering:pixelated;
}
.frame{position:relative;flex:0 0 auto;margin:0}
.frame .shot canvas{filter:drop-shadow(0 22px 34px #0006)}

/* ---------- 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}
.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:linear-gradient(180deg,#ffffff12,#ffffff06);
  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:1}
.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,#07091299 44%,#070912cc);
  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(--gold);box-shadow:inset 0 -30px 40px -30px var(--gold)}
/* 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:var(--track-caption);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:.02em;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{display:grid;gap:var(--s1);min-width:0}
.stat span{font-size:var(--t-micro);letter-spacing:var(--track-micro);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:var(--track-caption);color:var(--muted);font-variant-numeric:tabular-nums;line-height:1.2}
.bar{height:3px;border-radius:2px;background:#ffffff14;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:var(--track-micro);text-transform:uppercase;color:var(--muted);line-height:1}
.fact b{font-weight:500;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:500;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:var(--track-micro);text-transform:uppercase;color:#8e929f}
.toast{
  position:fixed;z-index:40;left:50%;transform:translateX(-50%);
  bottom:calc(env(safe-area-inset-bottom) + 86px);
  max-width:min(92vw,420px);padding:var(--s2) var(--s3);
  display:flex;align-items:center;gap:var(--s3);
  border:1px solid var(--panel-line);border-radius:var(--r-control);background:var(--panel);box-shadow:0 14px 50px #0008;
  font-size:var(--t-body);color:#dcdde3;
}

/* ---------- the dock ---------- */
.dock{
  position:fixed;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(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:var(--track-label);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:#fff4d9;background:linear-gradient(180deg,#d9bd751f,#d9bd7508)}


/* ---------- 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}
.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)}
.band .lines .drawing{stroke:#ffffff8c;stroke-width:2;stroke-dasharray:2 6}

.seat{cursor:pointer}
.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:#ffffff6b}
.seat .seatskill{display:block;text-align:center;font-size:var(--t-micro);letter-spacing:var(--track-caption);
  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:#e08a52}
.sign[data-sign="earth"]{--sign:#7fbb7a}
.sign[data-sign="wind"]{--sign:#74c2cc}

/* the three lines you have drawn, named */
.lineup-chosen{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s1)}
/* A drawn line is one fact, so it reads on one line at the same height as the button under it -
   three stacked blocks were spending a third of the panel saying "1 2 3". */
.chosen{display:flex;align-items:center;justify-content:center;gap:var(--s1);
  height:var(--control);padding:0 var(--s1);border:1px solid var(--line-soft);
  border-radius:var(--r-control);text-align:center;min-width:0;background:#090c1440}
.chosen[data-on]{border-color:color-mix(in srgb,var(--gold) 45%,transparent)}
.chosen span{font-size:var(--t-micro);letter-spacing:var(--track-micro);text-transform:uppercase;color:var(--muted);line-height:1}
.chosen b{font-size:var(--t-label);font-weight:600;line-height:1;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* 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}
#view[data-bare="true"] .shot{height:calc(100% / var(--spill-y,1))}
#view[data-bare="true"] .shot canvas{width:auto;height:calc(100% * var(--spill-y,1))}
#view[data-bare="true"] .frame{cursor:pointer}
.float{display:grid;gap:var(--s2)}
.float .hint{margin:0}
.float .panel{padding:var(--s2);gap:var(--s1)}
/* A row that is nothing but buttons does not get a box round it - that is a frame containing only
   other frames. The store and the archive are both 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. */
.float .panel[data-part="stores"],.float .panel[data-part="archive"]{
  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:#d9bd7514}

/* A narrow screen tightens the smallest type rather than taking another row: a row costs the model
   more height than the letters cost legibility. */
@media (max-width:370px){
  .seg button{font-size:8.5px;letter-spacing:.03em;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}
.battle .shot canvas{width:auto;height:calc(100% * var(--spill-y,1));max-width:none}
/* A boss stands alone on its side and does not get to eat the screen with it. Everything else keeps
   the rule - art may exceed its frame and nothing is cropped - but a boss frame is already 400x300
   against a normal foe's 256, and with the width unbounded an Anchor covered the header, the band and
   the log. It is SCALED to the room it has rather than cropped to it, because the band opposite has to
   stay readable and that is the entire point of the screen. */
.battle .fighter[data-rank="boss"] .shot canvas{
  width:auto;height:auto;max-width:100%;max-height:100%;
}
/* and it is given more of the field to be big IN. Contained inside half a screen a boss came out
   smaller than the heroes it was fighting, which is its own kind of wrong - so when one is present the
   field stops being two equal columns. It still cannot reach the band, the header or the log. */
.battle:has(.fighter[data-rank="boss"]){grid-template-columns:.66fr 1.34fr}
.vitals{position:relative;z-index:2}
.fighter[data-acting]{transform:translateY(-6px) scale(1.04)}
.fighter[data-down]{opacity:.26;filter:grayscale(1)}
.vitals{display:grid;gap:2px;min-width:0;justify-items:center}

/* 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)}
.fighter[data-frozen] .shot canvas{filter:saturate(.45) brightness(1.15) drop-shadow(0 0 6px #74c2cc)}
.battle .side{align-items:stretch}
.hp{height:2px;border-radius:2px;background:#ffffff14;overflow:hidden}
.battle .hp{width:62%;justify-self:center;margin:0 auto}
.hp i{display:block;height:100%;border-radius:2px;background:linear-gradient(90deg,#b2553f,var(--gold));
  transform-origin:left center;transform:scaleX(var(--fill,1));transition:transform .38s var(--ease)}
.fighter figcaption{display:flex;align-items:center;justify-content:center;gap:4px;
  font-size:var(--t-micro);letter-spacing:var(--track-caption);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;
  font-size:15px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.01em;
  text-shadow:0 2px 6px #000a;animation:rise 1.05s var(--ease) forwards;color:#f3ede0}
.num[data-tone="edge"]{color:var(--gold);font-size:18px}
.num[data-tone="weak"]{color:#9aa0ae;font-size:13px}
.num[data-tone="mend"]{color:var(--green)}
.num[data-tone="buff"]{color:var(--blue);font-size:11px;letter-spacing:var(--track-caption)}

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

/* ---------- what a player has ---------- */
.tally{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s1);
  padding:var(--s2);border:1px solid var(--panel-line);border-radius:var(--r-panel);
  background:var(--panel);}
.tell{display:grid;gap:2px;justify-items:center;text-align:center;min-width:0}
.tell span{font-size:var(--t-micro);letter-spacing:var(--track-micro);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:var(--track-caption);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:var(--track-caption);
  text-transform:uppercase;line-height:1.2;overflow-wrap:anywhere}
.deed-say small{font-size:var(--t-micro);letter-spacing:var(--track-caption);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:#090c1459;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:var(--track-caption);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,#d9bd7520,#d9bd7508)}
/* 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:var(--track-caption)}
.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:var(--track-label);
  text-transform:uppercase;cursor:pointer;max-width:52vw;
  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:var(--track-micro)}
button.pill.lead[data-r="4"] i{color:color-mix(in srgb,var(--gold) 62%,var(--muted))}
button.pill[data-picking]{border-color:var(--gold);color:var(--gold);
  background:linear-gradient(180deg,#d9bd7526,#d9bd750a)}
button.pill:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ---------- 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 struck figure, one or two frames as a flat white shape. The sprite is a canvas, so this costs a
   filter rather than a rebuild. */
.fighter[data-flash] .shot canvas{filter:brightness(0) invert(1);opacity:.92}

/* 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}
}

/* anticipation: the wind-up, then the release. It fits inside the 380ms that already passes between a
   skill firing and its numbers landing, so the telegraph costs no time at all. */
.fighter[data-wind]{animation:wind .41s cubic-bezier(.22,1,.36,1) both}
@keyframes wind{
  0%{transform:none}
  46%{transform:translateX(var(--wx,-6px)) scale(.965)}
  62%{transform:translateX(calc(var(--wx,-6px) * -.85)) scale(1.035)}
  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. */
/* 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(--gold)}

/* 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)}
.crown{position:absolute;top:4px;left:4px;z-index:3;
  font-size:8px;line-height:1;letter-spacing:.08em;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)}

/* ---------- 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:#04060cb0;animation:verdict-in .26s var(--ease) both}
.sheet[data-going]{animation:verdict-out .26s var(--ease) both}
.sheet .card{
  width:min(100%,420px);max-height:78dvh;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);
  background:linear-gradient(180deg,#151b2df2,#0a0e1af7);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:var(--track-caption);
  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%}
/* 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:#090c1440}
.statgrid span{font-size:var(--t-micro);letter-spacing:var(--track-micro);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}
@media (prefers-reduced-motion:reduce){.sheet,.sheet[data-going]{animation: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);
  padding:var(--s2);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:var(--track-caption);
  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:var(--track-caption);margin-top:-2px}
.odd[data-rarity="2"]{--rarity:var(--blue)}
.odd[data-rarity="3"]{--rarity:var(--violet)}
.odd[data-rarity="4"]{--rarity:var(--gold)}
.odds .note{font-size:var(--t-micro);color:var(--muted);text-align:left;margin-top:2px;line-height:1.3}

/* ---------- 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);
}
/* 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)}

/* 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,#ffffff05 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}
.campaign-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--s2)}

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

   The canvas inside a shot is centred with translate(-50%,-50%), so every keyframe here carries that
   with it; dropping it is how an animated sprite jumps half its own width on the first frame. */
@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:translate(-50%,-50%) scale(1);filter:brightness(1)}
  45%{opacity:.9;filter:brightness(2.6) saturate(1.4)}
  to{opacity:0;transform:translate(-50%,-50%) scale(1.14);filter:brightness(3)}
}
/* and the hero itself, arriving the way the card did */
@keyframes arrive{
  from{opacity:0;transform:translate(-50%,-50%) translateY(-44vh) scale(.92);filter:blur(2px)}
  54%{opacity:1;filter:blur(0)}
  80%{transform:translate(-50%,-50%) translateY(5px) scale(1.02)}
  to{opacity:1;transform:translate(-50%,-50%);filter:blur(0)}
}
.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);
}
.fell[data-dissolving] .shot canvas{animation:dissolve .34s var(--ease) both}
.fell[data-arriving] .shot canvas{animation:arrive .66s var(--ease) both}
.fell[data-veil]{cursor:pointer}
.fell[data-veil] .shot canvas{filter:drop-shadow(0 0 14px color-mix(in srgb,var(--rarity) 55%,transparent))}
.fell[data-veil] .seatskill{color:var(--rarity);letter-spacing:.4em}
/* 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,72vw)}
@media (prefers-reduced-motion:reduce){.fell[data-fell],.fell[data-dissolving] .shot canvas,.fell[data-arriving] .shot canvas{animation:none}}

/* ---------- what a hero carries ---------- */
.slots{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s1)}
.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:#ffffff05}
.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(--gold)}
.slot span{font-size:var(--t-micro);letter-spacing:var(--track-micro);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:var(--track-caption);color:var(--muted);line-height:1.2}
.slot .btn{margin-top:4px;padding:0 var(--s2);width:100%}

.relics{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s1)}
.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:#ffffff07;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(--gold)}
.relic b{font-size:var(--t-label);font-weight:600;letter-spacing:var(--track-caption);text-transform:uppercase;line-height:1.2}
.relic small{font-size:var(--t-micro);letter-spacing:var(--track-caption);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:var(--track-caption);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.) */
@media (max-height:860px){
  /* The archive is a thing to look at, so a short screen 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. */
  [data-part="archive"]{display:none}
}
@media (max-height:700px){
  /* the archive is a thing to look at, so it is the first thing a short screen gives up */
  [data-part="archive"]{display:none}
  [data-part="dev"] .hint{display:none}
  [data-part="signin"] .hint{display:none}
  .tally{padding:var(--s1)}
  .tell strong{font-size:15px}
  .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). */
  .deeds{gap:3px;padding:var(--s1)}
  .deed{min-height:30px}
  .tally{gap:0}
  .state{padding:var(--s3);gap:var(--s1)}
  .state h2{font-size:19px}
  .state small{display:none}
}
@media (max-height:640px){
  .seat .seatskill{display:none}
  .band .grid{gap:var(--s2)}
  .chosen{padding:0 4px}
  .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;
  background:radial-gradient(circle,color-mix(in srgb,var(--tint,#fff) 85%,transparent) 0%,transparent 62%);
  animation:blow .44s var(--ease) forwards;
}
@keyframes blow{from{opacity:.95;transform:scale(.25)}to{opacity:0;transform:scale(1.9)}}

/* a mark is not only a chip: whoever carries one is tinted by it the whole time it lasts */
.fighter[data-marked]{position:relative}
.fighter[data-marked] .shot::after{
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:var(--r-tile);
  background:radial-gradient(ellipse at 50% 72%,color-mix(in srgb,var(--marktint) 26%,transparent),transparent 70%);
  animation:marked 2.1s ease-in-out infinite;
}
@keyframes marked{0%,100%{opacity:.45}50%{opacity:.95}}

/* ---------- 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 1.5s var(--ease) both;
}
.skillname b{
  font-family:var(--serif);font-size:clamp(17px,4.6vw,23px);font-weight:500;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:var(--track-micro);text-transform:uppercase;
  color:color-mix(in srgb,var(--tint,var(--gold)) 85%,var(--ink));
}
@keyframes skillname{
  0%{opacity:0;transform:translate(-50%,-50%) translateY(10px) scale(.94)}
  14%{opacity:1;transform:translate(-50%,-50%) translateY(0) scale(1)}
  72%{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:fixed;inset:0;z-index:30;display:grid;place-items:center;padding:var(--s4);
  background:radial-gradient(ellipse at 50% 45%,#070912cc,#070912f2 70%);
  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(--s3);justify-items:center;text-align:center;
  padding:var(--s5) var(--s4);border:1px solid var(--panel-line);border-radius:var(--r-panel);
  background:var(--panel);box-shadow:0 24px 80px #000a;
}
.verdict h2{
  margin:0;font-family:var(--serif);font-weight:500;font-size:clamp(26px,7vw,34px);
  line-height:1.05;letter-spacing:var(--track-display)
}
.verdict .eyebrow{color:color-mix(in srgb,var(--tint,var(--gold)) 80%,var(--ink))}
.verdict .lines{display:grid;gap:4px;width:100%}
.verdict .lines div{
  display:flex;justify-content:space-between;gap:var(--s3);
  font-size:var(--t-label);letter-spacing:var(--track-caption);text-transform:uppercase;color:var(--muted)
}
.verdict .lines b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums}
/* nine rows of progress, kept inside the card: it scrolls rather than pushing the buttons off a
   short screen, because the buttons are the only way out of this panel */
/* the card's own (i), out of the way in its corner */
.verdict .card{position:relative}
.verdict .card > .icon{position:absolute;top:var(--s2);right:var(--s2);z-index:1}
.verdict .gains{display:grid;gap:3px;width:100%;max-height:34dvh;overflow-y:auto;
  padding-top:var(--s1);border-top:1px solid var(--panel-line)}
.verdict .gains .eyebrow{margin:0 0 2px;color:var(--muted);text-align:left}
/* The bar was 62px by 2px, where a tenth of a level is six pixels and reads as nothing at all. Wider
   and thicker: this row exists to show a bar moving, so the bar has to be visible. */
.gain{display:grid;grid-template-columns:1fr auto 84px auto;gap:var(--s2);align-items:center;min-height:20px}
.gain .hp{height:5px}
/* the card centres its content; a column of names has to read down its own left edge */
.gain .who{font-size:var(--t-micro);color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left}
.gain .lv{font-size:var(--t-micro);font-weight:600;color:#c9cbd4;font-variant-numeric:tabular-nums}
.gain small{font-size:var(--t-micro);color:var(--muted);font-variant-numeric:tabular-nums;min-width:26px;text-align:right}
.gain[data-up] .who,.gain[data-up] .lv{color:var(--gold)}
.gain[data-maxed] .lv{color:var(--muted)}
/* a hero at its ceiling shows its real progress, greyed - it is not going anywhere with it */
.gain[data-maxed] .hp i{background:var(--muted);opacity:.55}
.verdict .pair{display:grid;grid-template-columns:1fr 1fr;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(100dvh - 2 * var(--s5));overflow:hidden}
.verdict .gains{max-height:min(34dvh,240px)}
.verdict .pair .btn{width:100%}
@media (prefers-reduced-motion:reduce){
  .fighter[data-struck] .shot,.fighter[data-struck="hard"] .shot,.fighter[data-mended] .shot,
  .fighter .blow,.skillname,.verdict,.fighter[data-marked] .shot::after{animation:none}
  .battle[data-shake],.fighter[data-wind]{animation:none}
}

/* the nine on Home: one row, nine across, right above the dock */
.lineup{
  display:grid;grid-template-columns:repeat(9,1fr);gap:3px;align-items:end;
  flex:0 0 auto;cursor:pointer;padding:2px var(--s2);border-radius:var(--r-control);
  transition:background-color .2s ease;
}
.lineup:hover{background:#ffffff05}
.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)}
.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(--gold)}
/* an empty seat stays visibly empty: it is the most useful thing Home can say */
/* an empty seat stands as tall as a filled one, or the row reads as two rows */
.pip[data-empty]{
  align-self:stretch;min-height:44px;border:1px dashed var(--line);border-radius:6px;background:#ffffff04;
}
.lineup{align-items:stretch}
