:root {
  /* Depth comes from tonal steps, not from borders. Five surfaces rather than two. */
  --sink: #0b0a09;
  --ground: #12100e;
  --raise: #1b1815;
  --raise-2: #242019;
  --edge-light: rgba(255, 248, 230, 0.07);
  --edge-dark: rgba(0, 0, 0, 0.55);
  --brass-deep: #a8781c;
  --brass-pale: #f5d98a;
  --paper: #f4efe3;
  --paper-2: #e9e1cf;

  /* Fine grain, so large flat areas do not read as plastic. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.35'/%3E%3C/svg%3E");

  --bg: #12100e;
  --panel: #1c1917;
  --panel-2: #262220;
  --line: #38322d;
  --ink: #17150f;
  --cream: #f4efe3;
  --cream-2: #e7dfcd;
  --muted: #a29888;
  --brass: #e8b23a;
  --called: rgba(232, 178, 58, 0.28);
  --ink-red: #c0281e;   /* the red marker. Only ever used to correct or to circle. */

  /* Tier stickers are the only saturated colour in the app — glossy enamel pins.
     Withheld is the deliberate exception: the one sticker that is NOT glossy, because
     that card is not for showing. It reads as redacted rather than as a rarity. */
  --t-common: #facc15;
  --t-uncommon: #4ade80;
  --t-rare: #3b82f6;
  --t-epic: #c084fc;
  --t-legendary: #fb7185;
  --t-withheld: #2f2a24;

  --tab-h: 74px;
  --topbar-h: 46px;

  /* Card scale. The board has to show two duel cards, a headline, the commentary and
     your hand between the masthead and the tabs — on a 667px-tall phone that does not
     fit at the design size, so these shrink with the viewport and stop at it. */
  --em-duel: clamp(8.4px, 1.26vh, 11.8px);
  --em-hand: clamp(5.6px, 0.81vh, 7.6px);
  /* A single card shown on its own: as big as the phone allows, never wider than it. */
  /* Trimmed from 4.7vw: at that size the card plus its field notes pushed Add to deck
     below the fold, so the one action the screen exists for needed a scroll. */
  --em-full: clamp(13px, 4.25vw, 20px);
  /* Four registers, per the design language. Nothing is "rendered on a screen" —
     it is printed, stamped or cut out. */
  --display: Inter, system-ui, -apple-system, sans-serif;   /* Sharpie: 900, caps */
  --body: Inter, system-ui, -apple-system, sans-serif;
  --receipt: VT323, ui-monospace, "Courier New", monospace; /* thermal paper */
  --label: "Barlow Semi Condensed", "Arial Narrow", "Helvetica Neue", sans-serif;  /* label-maker tape, without the typewriter */
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* --- mobile hygiene --- */

html {
  /* iOS inflates text in portrait on some layouts. Every size here is already chosen
     for the phone, so it must not be second-guessed. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* Removes the 300ms wait before a tap registers, and the grey flash over the tapped
   element. Both make a card game feel broken rather than slow. */
a, button, .card, .tab, .round-chip {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* The shutter sits alone here now. It used to share the row with a way in for a
   photograph you already had. */
.shoot-row {
  display: flex;
  align-items: center;
  justify-content: center;
}


.shutter {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* The board is a thing you handle, not a page you read. Dragging a card should not
   start selecting the text printed on it. */
.view--play, .view--scan, .tabs, .card, .manual__sheet {
  -webkit-user-select: none;
  user-select: none;
}

/* The one place selecting text is useful: a share code you want to copy out. */
.versus__msg, .versus__msg b { -webkit-user-select: text; user-select: text; }

/* Any author `display` beats the UA's [hidden] rule, and most components here set one.
   Without this a hidden element stays on screen. */
[hidden] { display: none !important; }

/* Not height:100%. On mobile Safari a percentage height resolves against the large
   viewport — the one you get with the address bar hidden — so the page is taller than
   what is actually on screen and the shutter ends up below the fold. dvh is the visible
   viewport, which is the whole point of it. */
html, body {
  height: 100vh;
  height: 100dvh;
}

body {
  overscroll-behavior: none;
  display: flex;
  flex-direction: column;
}

/* ---------- materials ---------- */

/* Everything here is generated. The mockup hotlinked six Unsplash photographs for the
   cardboard and the coffee stain; those cannot ship — third-party hosts, extra
   requests, and nothing to stop them disappearing. Gradients and SVG noise get close
   enough and cost nothing. */

:root {
  /* The ground. A self-healing cutting mat: the surface you would actually be on if
     you were cutting letters out of magazines. Cardboard brown put a muddy mid-tone
     behind every screen and killed the cream of the cards; this is dark and neutral, so
     the paper, the yellow tape, the red marker and the neon all read against it. */
  --mat: #20262a;
  --mat-deep: #151a1d;
  --mat-line: rgba(150, 178, 190, 0.09);
  --tape-grey: #8c9296;
  --tape-cream: #e3dcb8;

  /* Paper fibre. Used at low opacity over anything that should feel physical. */
  --fibre: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23f)' opacity='0.42'/%3E%3C/svg%3E");
}

/* The mat, under a hard lamp off to one side. The grid is the printed one every
   cutting mat has: a fine 1cm rule with a heavier line every fifth. */
.board-ground {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background-color: var(--mat);
  background-image:
    radial-gradient(125% 85% at 72% 2%, rgba(214, 236, 245, 0.11) 0%,
      rgba(214, 236, 245, 0.03) 34%, transparent 56%, rgba(0, 0, 0, 0.6) 100%),
    repeating-linear-gradient(90deg, var(--mat-line) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg, var(--mat-line) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(90deg, rgba(150, 178, 190, 0.045) 0 1px, transparent 1px 8px),
    repeating-linear-gradient(0deg, rgba(150, 178, 190, 0.045) 0 1px, transparent 1px 8px),
    linear-gradient(168deg, #262d32 0%, #1d2327 58%, #141a1d 100%);
}

.board-ground::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--fibre);
  opacity: 0.28;
  mix-blend-mode: overlay;
}

/* Duct tape. Torn ends, cloth weave, a hard specular streak. */
.tape-duct {
  position: relative;
  background-color: var(--tape-grey);
  background-image:
    linear-gradient(108deg, transparent 18%, rgba(255, 255, 255, 0.22) 38%, transparent 58%),
    repeating-linear-gradient(0deg, transparent 0 2px, rgba(0, 0, 0, 0.11) 2px 4px),
    repeating-linear-gradient(90deg, transparent 0 2px, rgba(255, 255, 255, 0.06) 2px 4px);
  box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.55), 0 4px 7px rgba(0, 0, 0, 0.55);
  clip-path: polygon(0% 6%, 2% 0%, 5% 5%, 9% 0%, 100% 3%, 98% 94%, 95% 100%, 91% 95%, 5% 98%, 0% 100%);
}

/* Masking tape. Softer, creamier, fibres showing. */
.tape-masking {
  position: relative;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.45);
  clip-path: polygon(2% 0, 11% 3%, 21% 0, 31% 3%, 98% 1%, 100% 97%, 89% 95%, 79% 100%, 68% 96%, 2% 99%);
}

/* A ring left by a mug, drawn rather than photographed. */
.stain {
  position: absolute;
  pointer-events: none;
  border-radius: 50%;
  mix-blend-mode: multiply;
  opacity: 0.5;
  background:
    radial-gradient(circle at 50% 50%,
      transparent 0 58%,
      rgba(78, 46, 20, 0.55) 58% 66%,
      rgba(96, 60, 28, 0.28) 66% 72%,
      transparent 73%),
    radial-gradient(circle at 47% 53%, rgba(84, 50, 22, 0.18) 0 62%, transparent 64%);
  filter: blur(0.4px);
}

/* --- the four registers as utilities --- */

.dymo {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: #eee;
  background: #131110;
  padding: 0.25em 0.6em;
  border-radius: 2px;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.9);
  box-shadow:
    inset 1px 1px 2px rgba(255, 255, 255, 0.34),
    inset -1px -1px 2px rgba(0, 0, 0, 0.85),
    0 3px 5px rgba(0, 0, 0, 0.55);
}

.sharpie {
  font-family: var(--display);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  color: #16140f;
}

/* ---------- chrome ---------- */

body {
  margin: 0;
  /* dvh, for the same reason the height rule above uses it — and this one sits AFTER
     that rule, so a 100vh here quietly undid it. On iOS Safari vh is the LARGE viewport,
     the one you get with the toolbar hidden, so a min-height of 100vh forces the body
     taller than what is actually on screen whenever the toolbar is showing. The symptom
     is having to swipe down a little to reach the shutter. */
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--cream);
  font-family: var(--body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* The whole app sits on the mat. */
body { background-color: var(--mat); }

body {
  background-image:
    radial-gradient(135% 100% at 70% 0%, rgba(214, 236, 245, 0.12) 0%,
      rgba(214, 236, 245, 0.035) 32%, transparent 54%, rgba(0, 0, 0, 0.55) 100%),
    repeating-linear-gradient(90deg, var(--mat-line) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg, var(--mat-line) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(90deg, rgba(150, 178, 190, 0.045) 0 1px, transparent 1px 8px),
    repeating-linear-gradient(0deg, rgba(150, 178, 190, 0.045) 0 1px, transparent 1px 8px),
    linear-gradient(168deg, #262d32 0%, #1d2327 58%, #141a1d 100%);
  background-attachment: fixed;
}

/* Grain over everything, so the flat areas are a surface rather than a fill. Lighter
   than it was on the cardboard — the mat is dark and does not need the help. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 200;
  background-image: var(--fibre);
  opacity: 0.22;
  mix-blend-mode: overlay;
}

.topbar {
  position: relative;
  z-index: 3;
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 14px 16px 10px;
}

.wordmark {
  font-family: var(--display);
  font-size: 26px;
  font-weight: 900;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: #f6f1e4;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.55), 0 6px 14px rgba(0, 0, 0, 0.6);
}

.wordmark__tcg {
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.2em;
  color: #d9c48a;
  margin-left: 7px;
  vertical-align: 0.28em;
}

.tagline { display: none; }

/* Stamped on in red, at an angle, over the top of the wordmark. It is ink on the page
   rather than a badge beside it — a neat pill would read as a feature, and this is a
   warning. */
.stamp-alpha {
  display: inline-block;
  margin-left: 9px;
  padding: 1px 7px 2px;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-red);
  border: 2px solid var(--ink-red);
  border-radius: 2px;
  transform: rotate(-6deg);
  vertical-align: 0.5em;
  opacity: 0.85;
  mix-blend-mode: screen;
}

/* Tabs are strips of masking tape stuck to the bottom of the table. */
/* In the layout, not floating over it.
   These used to be position:fixed, and every screen had to reserve the right amount of
   space underneath by guessing at --tab-h. The guess was always slightly wrong — the
   tape is rotated, the safe-area inset varies — so the shutter ended up under the tabs
   and the page had to be scrolled to reach it. As a flex row at the bottom of the body
   they take exactly the space they take, and nothing else has to know anything. */
.tabs {
  position: relative;
  z-index: 60;
  flex: 0 0 auto;
  display: flex;
  justify-content: space-evenly;
  align-items: flex-end;
  gap: 6px;
  padding: 14px 10px calc(14px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(to top, rgba(16, 20, 22, 0.98) 55%, rgba(16, 20, 22, 0.86));
  border-top: 1px solid rgba(226, 244, 250, 0.07);
}

.tab {
  pointer-events: auto;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.6));
  position: relative;
  border: 0;
  cursor: pointer;
  padding: 10px 24px 12px;
  font-family: var(--display);
  font-size: 18px;
  font-weight: 900;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: #16140f;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  box-shadow: 0 4px 7px rgba(0, 0, 0, 0.55);
  clip-path: polygon(2% 0, 11% 3%, 21% 0, 31% 3%, 98% 1%, 100% 97%, 89% 95%, 79% 100%, 68% 96%, 2% 99%);
  opacity: 0.55;
  transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1), opacity 0.18s ease;
}

/* Each strip is stuck on at its own angle. Nothing is ever aligned. */
.tab:nth-child(1) { transform: rotate(-3.5deg); }
.tab:nth-child(2) { transform: rotate(1.8deg); }
.tab:nth-child(3) { transform: rotate(-1.6deg); }

.tab.is-active { opacity: 1; }

/* Taking a photograph is the one thing that costs you something, so it is the one
   word on the furniture written in red. */
.tab[data-view="scan"] { color: #b91c1c; }
.tab:nth-child(1).is-active { transform: rotate(-3.5deg) translateY(-7px) scale(1.04); }
.tab:nth-child(2).is-active { transform: rotate(1.8deg) translateY(-7px) scale(1.04); }
.tab:nth-child(3).is-active { transform: rotate(-1.6deg) translateY(-7px) scale(1.04); }

.tab:focus-visible { outline: 3px solid var(--brass); outline-offset: 3px; }

.view {
  display: none;
  position: relative;
  z-index: 2;
}

.view.is-active { display: block; }

#views { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }

#views > .view.is-active { flex: 1 1 auto; min-height: 0; }

/* Only the scan screen and the board are fixed surfaces; the list screens scroll. */
#views > .view--deck.is-active,
#views > .view--play.is-active,
#views > .view--challenge.is-active,
#views > .view--errands.is-active,
#views > .view--reveal.is-active { overflow-y: auto; }

/* The board is a fixed surface inside a scrolling view, so the view stops scrolling
   while it is up. */
#views > .view--play.is-active:has(#tcg-board:not([hidden])) {
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* ---------- buttons ---------- */

/* Nothing here is a button in the UI sense. A primary action is a strip of duct tape
   with the word written on it; a secondary action is masking tape. Both are torn, so
   neither has a rectangle's corners. */
.btn {
  position: relative;
  border: 0;
  cursor: pointer;
  padding: 12px 26px 14px;
  font-family: var(--display);
  font-size: 16px;
  font-weight: 900;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: #16140f;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);
  clip-path: polygon(2% 0, 11% 3%, 21% 0, 31% 3%, 98% 1%, 100% 97%, 89% 95%, 79% 100%, 68% 96%, 2% 99%);
  transform: rotate(-1deg);
  transition: transform 0.14s cubic-bezier(0.2, 0.9, 0.3, 1);
}

.btn:nth-of-type(even) { transform: rotate(1.2deg); }

/* Hazard-yellow gaffer tape: the one thing on the screen to press. Grey duct tape read
   as a switched-off button next to everything else in the room. */
.btn--primary {
  color: #17140c;
  text-shadow: 0 1px 0 rgba(255, 240, 190, 0.45);
  background-color: #e3ad2f;
  background-image:
    linear-gradient(108deg, transparent 18%, rgba(255, 255, 255, 0.22) 38%, transparent 58%),
    repeating-linear-gradient(0deg, transparent 0 2px, rgba(0, 0, 0, 0.11) 2px 4px),
    repeating-linear-gradient(90deg, transparent 0 2px, rgba(255, 255, 255, 0.06) 2px 4px);
  background-blend-mode: normal;
  box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.5), 0 4px 8px rgba(0, 0, 0, 0.6);
  clip-path: polygon(0% 6%, 2% 0%, 5% 5%, 9% 0%, 100% 3%, 98% 94%, 95% 100%, 91% 95%, 5% 98%, 0% 100%);
}

/* A ghost action is written straight on the table in marker — no tape at all. */
.btn--ghost {
  background: transparent;
  background-image: none;
  color: #ddd2b8;
  box-shadow: none;
  clip-path: none;
  /* Written on the table rather than taped to it, but still something you tap, so it
     keeps a 44px target under the ink. */
  min-height: 44px;
  padding: 11px 14px;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  text-decoration-color: rgba(221, 210, 184, 0.45);
}

.btn--small { font-size: 13px; padding: 8px 15px 10px; }
.btn--ghost.btn--small { min-height: 44px; padding: 9px 12px; white-space: nowrap; flex-shrink: 0; }

.btn:active { transform: rotate(-1deg) translateY(2px) scale(0.98); }
.btn:nth-of-type(even):active { transform: rotate(1.2deg) translateY(2px) scale(0.98); }
.btn:focus-visible { outline: 3px solid var(--brass); outline-offset: 3px; }
.btn[disabled] { opacity: 0.42; cursor: default; transform: rotate(-1deg); }

/* ---------- scan ---------- */

/* The viewfinder is the screen. It takes whatever is left after the masthead and the
   controls, rather than sitting in a 240px letterbox. */
.view--scan { padding-bottom: 0; }

.view--scan.is-active {
  display: flex;
  flex-direction: column;
  /* Same as the board: a fixed surface, not a document. The body is already the height
     of the screen, so taking the remaining flex space is exact — subtracting a guessed
     topbar height left a few pixels of scroll and put the shutter on the tabs. */
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.stage {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  background: #000;
  overflow: hidden;
}

#preview { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The shot you just took, held in place of the live feed while it is read.
   It lands sharp for a beat so you register what you got, then blurs while it is
   being worked on. The slight scale-up is not decoration: a blur samples past the
   element's edge, so without it you get soft transparent borders. */
.stage__freeze {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 1;
  transform: scale(1);
  filter: blur(0) saturate(1);
  transition: filter 0.65s ease, transform 0.65s ease;
}

/* While the print is out, the frame behind it drops back so the print is the picture. */
.stage.is-reading .stage__freeze,
.stage.is-reading video {
  filter: brightness(0.28) saturate(0.4);
  transition: filter 0.6s ease;
}

/* Three wobbles while the catch hangs in the balance. Only ever runs when the
   outcome is genuinely uncertain, so the shake itself tells you it is worth
   something before the card does. */
/* An uncertain catch: the print shakes on the table. It is the print that is in doubt,
   not the room behind it. */
/* Three classes, not two: the eject rule is on the same element and comes later in the
   file, and at equal specificity it won — the print never shook and never slipped. */
.stage.is-ejected.is-wobbling .print { animation: wobble 0.5s ease-in-out 3; }

@keyframes wobble {
  0%, 100% { transform: translate(-50%, -50%) rotate(-2deg); }
  25% { transform: translate(-50%, -50%) rotate(-4.2deg) translateX(-6px); }
  75% { transform: translate(-50%, -50%) rotate(0.6deg) translateX(6px); }
}

/* It got away: the print slides off the table. */
.stage.is-ejected.is-escaping .print { animation: escape 0.55s cubic-bezier(0.5, 0, 0.9, 0.4) forwards; }

@keyframes escape {
  to { transform: translate(-150%, -30%) rotate(-28deg); opacity: 0.6; }
}

.stage__flash {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}

.stage__flash.is-firing { animation: flash 0.3s ease-out; }

@keyframes flash {
  from { opacity: 0.85; }
  to { opacity: 0; }
}

/* The viewfinder brackets are a framing guide, so they go once the shot is taken. */
.stage.is-frozen .stage__frame { display: none; }

.stage__frame {
  position: absolute;
  inset: 8% 8%;
  border: 2px solid rgba(244, 239, 227, 0.35);
  border-radius: 6px;
  pointer-events: none;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  clip-path: polygon(0 0,34% 0,34% 2px,66% 2px,66% 0,100% 0,100% 100%,66% 100%,66% calc(100% - 2px),34% calc(100% - 2px),34% 100%,0 100%);
}

.stage__msg {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: grid;
  place-content: center;
  text-align: center;
  padding: 30px;
  font-family: var(--receipt);
  font-size: 19px;
  line-height: 1.15;
  letter-spacing: 0.02em;
  color: #e3d9c1;
  background: #16130f;
}

.stage__thinking {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 14px;
  /* The blur underneath does the separating, so this only needs to take the edge
     off. Heavier and you lose the shot, which is the point of freezing it. */
  background: rgba(18, 16, 14, 0.32);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85);
  font-family: var(--display);
  font-size: 20px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.spinner {
  width: 26px; height: 26px;
  border: 3px solid var(--line);
  border-top-color: var(--brass);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

.controls {
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 14px max(18px, env(safe-area-inset-left, 0px)) 16px;
}

/* What to go and find. Taped to the viewfinder like a note to self, because that is
   what it is — the camera had no direction once you owned three playable things. */
.hunt {
  display: grid;
  gap: 4px;
  justify-items: center;
  max-width: 32ch;
  padding: 9px 15px 12px;
  text-align: center;
  transform: rotate(-0.8deg);
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);
  clip-path: polygon(2% 0, 11% 3%, 21% 0, 31% 3%, 98% 1%, 100% 97%, 89% 95%, 79% 100%, 68% 96%, 2% 99%);
}

/* Somebody is waiting on you to go and find a team. Louder than the hunt advice, and a
   different colour, because it is not advice — it is a person. */
.awaited {
  display: grid;
  gap: 4px;
  justify-items: center;
  max-width: 32ch;
  padding: 10px 15px 12px;
  text-align: center;
  transform: rotate(0.6deg);
  background-color: #8f2f22;
  background-image: var(--grain);
  background-blend-mode: multiply;
  color: #f7efdc;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.55);
}

.awaited__who {
  margin: 0;
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  line-height: 1;
}

.awaited__sub {
  margin: 0;
  font-family: var(--receipt);
  font-size: 13px;
  line-height: 1.2;
  opacity: 0.9;
}

#awaited-go { margin-top: 4px; }

/* The diagnosis, demoted to an eyebrow. It is the least useful of the three lines and
   it used to be the biggest. */
.hunt__tag {
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8f2f22;
}

/* The instruction. The one thing on this screen you can act on, sized like it. */
.hunt__head {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 900;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 0.98;
  color: #17140f;
  text-wrap: balance;
}

.hunt__why {
  font-family: var(--receipt);
  font-size: 13px;
  line-height: 1.15;
  color: #5d5445;
  text-wrap: pretty;
}

#hunt-go { margin-top: 4px; }

.hint {
  font-family: var(--receipt);
  font-size: 17px;
  line-height: 1.1;
  letter-spacing: 0.02em;
  color: #e3d9c1;
  text-align: center;
  max-width: 32ch;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}

/* Film. The dots are the number you feel; the label is the number you read. */
.film { display: grid; justify-items: center; gap: 7px; }

.film__dots { display: flex; gap: 7px; }

.film__dot {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.42);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.8),
    inset 0 -1px 1px rgba(255, 255, 255, 0.2);
  transition: background 0.25s ease, transform 0.25s ease;
}

.film__dot.is-loaded {
  background:
    radial-gradient(circle at 34% 28%, #fff6cf 0%, var(--brass) 46%, var(--brass-deep) 100%);
  box-shadow:
    inset 0 -1.5px 2px rgba(0, 0, 0, 0.55),
    inset 0 1.5px 1px rgba(255, 255, 255, 0.7),
    0 2px 3px rgba(0, 0, 0, 0.6);
}

.film__label {
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #eee;
  background: #131110;
  padding: 3px 10px 4px;
  border-radius: 2px;
  transform: rotate(-1.4deg);
  box-shadow:
    inset 1px 1px 2px rgba(255, 255, 255, 0.32),
    inset -1px -1px 2px rgba(0, 0, 0, 0.85),
    0 3px 5px rgba(0, 0, 0, 0.55);
}

/* Out of film: the label is the only red thing on the screen. */
.film.is-empty .film__label { color: #ff8b82; }

/* Alpha only. In a finished game this is where watching an advert would sit, which is
   why it reads as a thing you are being offered rather than a cheat. */
.film__topup {
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #08240a;
  background: #39ff14;
  border: 0;
  border-bottom: 3px solid #1b7a0c;
  border-right: 3px solid #1b7a0c;
  border-radius: 2px;
  min-height: 40px;
  padding: 8px 16px;
  cursor: pointer;
}

.film__topup:active {
  transform: translate(2px, 2px);
  border-bottom-width: 1px;
  border-right-width: 1px;
}

.film__topup[disabled] {
  background: #3a423f;
  color: #8fa0a8;
  border-color: #232a28;
  cursor: default;
}

.shutter {
  width: 74px; height: 74px;
  border-radius: 50%;
  border: 0;
  padding: 0;
  background: linear-gradient(160deg, #6b6560, #2c2926 62%, #191715);
  display: grid;
  place-content: center;
  cursor: pointer;
  box-shadow:
    inset 0 1px 1px rgba(255, 255, 255, 0.4),
    inset 0 -2px 4px rgba(0, 0, 0, 0.8),
    0 6px 12px rgba(0, 0, 0, 0.7);
}

.shutter span {
  width: 54px; height: 54px;
  border-radius: 50%;
  display: block;
  background: radial-gradient(circle at 36% 28%, #fff4d8 0%, var(--brass) 44%, var(--brass-deep) 100%);
  box-shadow:
    inset 0 -2px 4px rgba(0, 0, 0, 0.45),
    inset 0 2px 2px rgba(255, 255, 255, 0.65),
    0 2px 5px rgba(0, 0, 0, 0.6);
  transition: transform 0.12s ease;
}

.shutter:active span { transform: scale(0.88); }
.shutter[disabled] { opacity: 0.4; }

/* ---------- card ---------- */

/* Everything scales from one font-size, so the same markup serves the full card, the
   duel, the deck grid and the hand. 13.5em wide throughout. */
.card {
  position: relative;
  width: 13.5em;
  /* The mockup draws this at 19.5em, which is the right proportion but was drawn
     against four stat rows and a one-line ability. Five rows and two lines of ability
     genuinely need 21.4em; everything else on the card is the mockup's. */
  /* Measured, not chosen. Five stat rows plus a two-line ability need this much paper:
     at 22.3em the fifth stat, the value and the ability were clipped off the bottom of
     the hero card. 13.5:23.6 is longer than a real trading card, and a clipped stat is
     worse than a long card. */
  height: 23.6em;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  padding: 0.4em;
  color: #16140f;
  /* The paper is drawn by ::after, not by this element.
     The guillotined corners used to be a clip-path on the card itself, which also clipped
     everything deliberately hanging off it — the tier pin at -0.7em and the type stamp at
     -0.5em were both sliced flat against the edge. It was clipping the box-shadow too, so
     the card had almost no drop shadow. Putting the cut on a layer behind the content
     lets the stickers overhang, and drop-shadow follows the cut shape the way box-shadow
     never could. */
  isolation: isolate;
  filter:
    drop-shadow(-0.04em 0.1em 0.16em rgba(0, 0, 0, 0.35))
    drop-shadow(-0.14em 0.32em 0.55em rgba(0, 0, 0, 0.45));
}

/* The paper itself. */
.card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: #fdfdfd;
  border-radius: 0.12em;
  /* Guillotined corners rather than rounded — cut, not moulded. */
  clip-path: polygon(0.8% 0, 99.2% 0.4%, 100% 99.2%, 0.4% 100%);
}

/* Stock glare. Sits above the photo, below the stickers.
   BOTH of the card's pseudo-elements are taken: ::after is the paper, ::before is this.
   Never style either from a modifier class — the box (inset: 0) comes with them, so a
   small badge written as .card--whatever::before becomes a full-card rectangle. Use a
   real element. This has caught .card--hurt and .card--first already. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  background: linear-gradient(135deg, rgba(255,255,255,0.4) 0%, transparent 40%, rgba(255,255,255,0.1) 100%);
}

/* Nothing. A borrowed card used to be desaturated and dimmed, which gave the house away
   on sight — and now that the house deck is real photographs served from the same store
   as everyone else's, the tell was the only thing left distinguishing them. The class
   stays because the builder still counts borrowed cards; it just does not mark them. */
.card--borrowed { }

/* What happened to a card caught mid-fight. Boxed off from the score, because it is the
   only line on the receipt that says something about tomorrow rather than about the
   fight that has just finished. */
.receipt-sheet__kept {
  margin: 10px 0 0;
  padding: 8px 10px;
  font-size: 13px;
  line-height: 1.35;
  color: #2a2419;
  background: rgba(180, 85, 63, 0.12);
  border-left: 3px solid #b4553f;
}

/* A card that does not exist.
   Caught for a wildcard slot, fights once, and is never added to anybody's deck. It is
   the only thing on the board that is not somebody's property, so it should not look
   like the rest of the table: bleached, lifted off the mat, and very faintly moving. */
.card--ghost {
  filter: saturate(0.55) brightness(1.06);
  opacity: 0.93;
  box-shadow:
    0 0 0 0.08em rgba(208, 232, 245, 0.55),
    0 0 1.4em rgba(150, 205, 235, 0.45),
    0 0.5em 1.2em rgba(0, 0, 0, 0.55);
  animation: ghost-breathe 3.4s ease-in-out infinite;
}

.card--ghost .card__photo img { mix-blend-mode: luminosity; opacity: 0.88; }

@keyframes ghost-breathe {
  0%, 100% { opacity: 0.93; }
  50% { opacity: 0.78; }
}

@media (prefers-reduced-motion: reduce) {
  .card--ghost { animation: none; }
}

/* Teach: what a move could become. Four rows, each one an answer rather than a setting. */
.teacher__now {
  margin: 0 0 10px;
  font-family: var(--receipt);
  font-size: 14px;
  color: var(--muted, #8d8573);
}

.teacher { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }

.teacher__pick {
  display: grid;
  gap: 4px;
  width: 100%;
  padding: 12px 13px;
  text-align: left;
  color: var(--cream, #f2ede1);
  background: rgba(255, 246, 214, 0.06);
  border: 0;
  border-left: 3px solid var(--brass, #e8b23a);
  cursor: pointer;
}

.teacher__pick:active { background: rgba(255, 246, 214, 0.12); }

.teacher__head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.teacher__name { font-family: var(--display); font-size: 17px; font-weight: 900; }

.teacher__stat {
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted, #8d8573);
}

.teacher__foot { display: flex; flex-wrap: wrap; gap: 10px; font-size: 12px; }
.teacher__hit { color: var(--brass, #e8b23a); font-variant-numeric: tabular-nums; }
.teacher__leaves { color: #a98fd0; }
.teacher__new { color: #5f9e6a; text-transform: uppercase; letter-spacing: 0.08em; }

/* The offer, on the move itself. */
.notes__teach {
  grid-column: 2;
  justify-self: start;
  margin-top: 2px;
  padding: 4px 9px 5px;
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #2a1f08;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 60%, var(--brass-deep));
  border: 0;
  border-radius: 2px;
  cursor: pointer;
}

/* Where this one stands against every other of its kind. */
.notes__rank {
  margin: 6px 0 0;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #2a1f08;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 70%);
  padding: 4px 8px 5px;
  justify-self: start;
}

.notes__rank--under {
  color: #5d5445;
  background: none;
  padding: 0;
  text-transform: none;
  letter-spacing: 0.02em;
  font-family: var(--receipt);
  font-size: 12px;
}

/* First edition. Nobody had caught one of these before you, which is the rarest thing
   that can happen in this game — and it was a line of grey text in the notes. */
.card--first .card__frame {
  box-shadow:
    inset 0 0 0 0.1em rgba(232, 178, 58, 0.9),
    inset 0 0.4em 0.8em rgba(0, 0, 0, 0.55);
}

/* No.1 in brass. The stamp is already on the card; this makes it mean something. */
.card--first .card__edition {
  color: #2a1f08;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 60%, var(--brass-deep));
  box-shadow: 0 0.1em 0.3em rgba(0, 0, 0, 0.45);
}

/* The pass: one job, on the screen where somebody is deciding what to do next. */
.job {
  position: relative;
  display: grid;
  gap: 4px;
  width: 100%;
  padding: 13px 15px 14px;
  text-align: left;
  cursor: pointer;
  color: var(--cream, #f2ede1);
  background: rgba(232, 178, 58, 0.09);
  border: 0;
  border-left: 3px solid var(--brass, #e8b23a);
}

.job:active { background: rgba(232, 178, 58, 0.16); }

.job__tag {
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass, #e8b23a);
}

.job__title {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.01em;
  line-height: 1.05;
}

.job__note {
  font-family: var(--receipt);
  font-size: 13px;
  line-height: 1.3;
  color: var(--muted, #8d8573);
}

.job__bar {
  display: block;
  height: 4px;
  margin-top: 5px;
  background: rgba(0, 0, 0, 0.4);
}

.job__bar i { display: block; height: 100%; background: var(--brass, #e8b23a); transition: width 0.3s ease-out; }

.job__at {
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted, #8d8573);
  font-variant-numeric: tabular-nums;
}

/* The whole track. */
.track { display: grid; gap: 7px; margin: 0; padding: 0; list-style: none; }

.track__row {
  display: grid;
  gap: 3px;
  padding: 10px 12px 11px;
  background: rgba(255, 246, 214, 0.05);
  border-left: 3px solid var(--muted, #8d8573);
}

.track__row.is-got { border-left-color: #5f9e6a; opacity: 0.55; }

.track__title {
  font-family: var(--display);
  font-size: 16px;
  font-weight: 900;
  color: var(--cream, #f2ede1);
}

.track__note {
  font-family: var(--receipt);
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted, #8d8573);
}

.track__film {
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brass, #e8b23a);
}

.track__row.is-got .track__film { color: #7fc08d; }

/* A band heading inside the track. Sticky, because the list is a hundred rows long and
   losing track of which half you are reading is the only way to get lost in a list. */
.track__band {
  position: sticky;
  top: -1px;
  z-index: 2;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-top: 8px;
  padding: 8px 12px 7px;
  background: var(--panel);
  border-bottom: 1px solid rgba(232, 178, 58, 0.35);
}

.track__band:first-child { margin-top: 0; }

.track__band-name {
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brass, #e8b23a);
}

.track__band-count {
  font-family: var(--receipt);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--muted, #8d8573);
}

/* The pass card's tag line while it is announcing a finished job. */
#job-tag.is-won { color: #7fc08d; }

/* The power score, in the field notes. A headline number, set like one. */
.notes__power {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 8px 0 0;
  padding-top: 8px;
  border-top: 1px solid rgba(0, 0, 0, 0.12);
}

.notes__powerlabel {
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.65;
}

.notes__powern {
  font-family: var(--display);
  font-size: 26px;
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* The three moves, on the card rather than only on the tray you get eight seconds with. */
.notes__moves { display: grid; gap: 6px; margin: 8px 0 0; padding: 0; list-style: none; }

.notes__move {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  column-gap: 8px;
  row-gap: 2px;
}

.notes__kind {
  grid-row: span 2;
  align-self: center;
  padding: 3px 6px 4px;
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #f4efe3;
  background: #4a443a;
  border-radius: 2px;
}

.notes__move--heavy .notes__kind { background: #8f2f22; }
.notes__move--quick .notes__kind { background: #2f5d8f; }
.notes__move--trick .notes__kind { background: #6b3f8f; }

.notes__movename { font-size: 15px; line-height: 1.1; }

.notes__movefoot {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  font-size: 11px;
  letter-spacing: 0.04em;
  opacity: 0.7;
}

.notes__miss { color: #8f2f22; opacity: 0.9; }
.notes__leaves { color: #6b3f8f; opacity: 0.9; }

/* What the last fight left on it.
   A strip across the foot of the frame, reading left to right like the health bar on the
   board so the two mean the same thing in both places. The card itself is left alone —
   greying out a wounded card would say "you cannot use this", and the whole point is
   that you can, and that it is a bad idea. */
.card__hurt {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9;
  /* Was 0.34em, which at grid size is two pixels and invisible in a wall of forty
     cards. This is a health bar, not a hairline — and it carries the word HURT at its
     right-hand end, because every corner of the card is already spoken for. */
  height: 0.95em;
  background: rgba(20, 14, 10, 0.7);
  box-shadow: inset 0 0.08em 0.16em rgba(0, 0, 0, 0.6);
}

.card__hurt-fill {
  display: block;
  height: 100%;
  background: #b4553f;
  transition: width 0.3s ease-out;
}

.card--hurt .card__frame { box-shadow: inset 0 0 0 0.12em rgba(180, 85, 63, 0.85), inset 0 0.4em 0.8em rgba(0, 0, 0, 0.9); }

/* And a tear of tape across the corner, because the bar alone reads as decoration until
   you already know what it means.
   A real element, NOT .card--hurt::after: .card::after is the card's paper, and styling
   the same pseudo-element here would have replaced the background of every wounded card
   with a red rectangle. */
.card__sore {
  position: absolute;
  right: 0.35em;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--label);
  font-size: 0.42em;
  font-weight: 800;
  letter-spacing: 0.16em;
  line-height: 1;
  color: #f4efe3;
  text-shadow: 0 0 0.3em rgba(0, 0, 0, 0.9);
  pointer-events: none;
}


/* What a spare would pass on, on the spare you are choosing. The decision is entirely
   which stat you want, so the stat goes on the thing being picked. */
.card__gives {
  position: absolute;
  left: 50%;
  bottom: 0.6em;
  transform: translateX(-50%) rotate(-1.5deg);
  z-index: 13;
  padding: 0.2em 0.7em;
  font-family: var(--label);
  font-size: 0.62em;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #2a1f08;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 60%, var(--brass-deep));
  box-shadow: 0 0.12em 0.3em rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

/* What a spare does to this card, said next to the buttons that do it. */
.offer {
  width: 100%;
  max-width: 22em;
  margin: 2px auto 0;
  padding: 11px 13px 12px;
  text-align: left;
  background: rgba(232, 178, 58, 0.08);
  border-left: 3px solid var(--brass, #e8b23a);
}

.offer__head {
  margin: 0 0 7px;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--brass, #e8b23a);
}

.offer__list {
  display: grid;
  gap: 7px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--receipt);
  font-size: 13px;
  line-height: 1.3;
  color: #e6dfce;
}

.offer__list b { color: #f7f2e4; }

.offer__cost {
  margin: 8px 0 0;
  font-family: var(--receipt);
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted, #8d8573);
}

/* What just went into it. */
.feed-note {
  margin: 0 auto 6px;
  max-width: 34ch;
  font-family: var(--receipt);
  font-size: 14px;
  line-height: 1.25;
  text-align: center;
  color: var(--brass);
}

/* Writing somebody onto the list. A strip of paper with a ruled line, because that is
   what adding a name to a list looks like — it was a bare input on a dark page. */
#friend-add {
  max-width: none;
  padding: 11px 13px 12px;
  background-color: var(--tape-cream, #efe7d5);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  transform: rotate(-0.5deg);
  box-shadow: 0 3px 7px rgba(0, 0, 0, 0.45);
}

#friend-add input {
  color: #17140f;
  border-bottom-color: rgba(23, 20, 15, 0.45);
  caret-color: #17140f;
  text-align: left;
}

#friend-add input::placeholder { color: rgba(23, 20, 15, 0.4); }

/* Dark ink on paper. The ghost button is written for a dark ground and went invisible
   the moment this strip became cream. */
#friend-add .btn--ghost {
  color: #17140f;
  background: rgba(23, 20, 15, 0.08);
  text-shadow: none;
}

/* An enemy is a fight bill like any other, not a row in a contacts list. */
/* minmax(0, 1fr), not 1fr: a 1fr track carries an implicit auto minimum, so the bill
   refused to shrink below its own content and pushed the remove button eight pixels off
   the right-hand edge of the screen. */
.friend { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; }
.friend .rival { padding-right: 14px; }

.rival--foe { background: var(--paper-2, #efe7d5); }

/* A rival card is a bill on a wall. Sharper type, a hint of a lean, and a shadow that
   puts it ON the board rather than in it. */
.rival { transform: rotate(-0.35deg); transition: transform 0.12s ease-out; }
.picks > li:nth-child(even) .rival { transform: rotate(0.4deg); }
.rival:active { transform: rotate(0deg) translateY(2px); }

.rival__name { font-size: 24px; letter-spacing: -0.02em; }

/* The one section that is asking something of you. Everything else on this tab is a
   choice; this is a job, so it gets the colour and the top of the screen. */
.board-lot--urgent {
  border-left: 3px solid #8f2f22;
  padding-left: 11px;
}

/* The quiet footer. Reference and escape hatches, gathered rather than scattered down
   the page as two more things competing with the buttons that start a fight. */
.quiet-row {
  display: flex;
  gap: 18px;
  justify-content: center;
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 246, 214, 0.08);
}

.quiet-row .linky {
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.5;
}

/* --- the marquee --- */

/* The top of the Play tab. It had no focal point at all — a strip of tape and then a
   stack of slabs — and this is the screen where a fight gets arranged, so it opens like
   a bill: a torn sheet pinned to the board with your name cut out of magazines. */
.bill {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 7px;
  padding: 20px 18px 22px;
  text-align: center;
  background-color: #141210;
  background-image:
    radial-gradient(120% 80% at 50% -10%, rgba(232, 178, 58, 0.14), transparent 62%),
    repeating-linear-gradient(90deg, transparent 0 3px, rgba(255, 255, 255, 0.018) 3px 6px),
    var(--grain);
  border-top: 2px solid rgba(232, 178, 58, 0.5);
  border-bottom: 2px solid rgba(232, 178, 58, 0.5);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
  /* Torn along the bottom, the way every other sheet in this game is. */
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 5px), 92% 100%, 78% calc(100% - 4px),
    61% 100%, 44% calc(100% - 5px), 27% 100%, 12% calc(100% - 3px), 0 100%);
}

.bill__eyebrow {
  margin: 0;
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgba(232, 178, 58, 0.75);
}

/* The name, cut from the same magazines as every card title in the game. */
.bill__name {
  margin: 0;
  --em-ransom: 32px;
  font-size: 32px;
  line-height: 1;
}

.bill__name .ransom { justify-content: center; }
.bill__name.is-nameless { opacity: 0.45; }

/* THE TALE OF THE TAPE.
   Three figures under the billing, ruled off like a programme. The line above it is a
   sentence; this is the same fact as numbers, which is the half somebody actually checks
   before picking a fight. */
.tape {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;
  width: 100%;
  max-width: 320px;
  margin: 10px 0 0;
  padding: 9px 0 2px;
  border-top: 1px solid rgba(232, 178, 58, 0.3);
}

.tape__cell { display: grid; gap: 2px; justify-items: center; }
.tape__cell + .tape__cell { border-left: 1px solid rgba(232, 178, 58, 0.18); }

.tape dt {
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(232, 178, 58, 0.7);
}

.tape dd {
  margin: 0;
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  line-height: 1;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

/* A run is the one figure that is ever good or bad news. */
.tape dd.is-hot { color: #7fc08d; }
.tape dd.is-cold { color: #d08878; }

.bill__form {
  margin: 2px 0 0;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9aa2a0;
  font-variant-numeric: tabular-nums;
}

/* The board itself. A cutting mat is where you make cards; a fight is arranged on a
   corkboard, so the Play tab gets one — the same grid, warmer, with a wash of light
   coming from where the marquee is. */
.view--play {
  background-image:
    radial-gradient(90% 45% at 50% 0%, rgba(232, 178, 58, 0.05), transparent 60%);
}

/* The record is a slate.
   Tally marks want a wall to be chalked on. As rows on the same dark ground as
   everything else they read as a table using tally marks for decoration; on a slab of
   slate, with a wiped-chalk bloom across it, they read as somebody keeping score — which
   is what they are. */
#record {
  padding: 13px 13px 14px;
  background-color: #171a1a;
  background-image:
    radial-gradient(120% 70% at 20% 0%, rgba(255, 255, 255, 0.055), transparent 60%),
    radial-gradient(90% 60% at 85% 100%, rgba(255, 255, 255, 0.035), transparent 60%),
    var(--grain);
  background-blend-mode: normal, normal, overlay;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 2px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    inset 0 -2px 10px rgba(0, 0, 0, 0.5),
    0 6px 14px rgba(0, 0, 0, 0.45);
}

/* The heading sits on the slate rather than floating above it. */
#record > .lot-tape { justify-self: start; margin-bottom: 3px; }

/* Chalk is not ink: softer edges, and it sits ON the surface. */
#record .tally__mark,
#record .tally__group.is-five::after {
  box-shadow: 0 0 3px currentColor;
  opacity: 0.92;
}

#record .record__row {
  background: rgba(255, 255, 255, 0.028);
  border-left-width: 2px;
}

/* The totals, chalked along the bottom of the slate. */
#record .record__all {
  margin-top: 11px;
  padding-top: 10px;
  border-top: 1px dashed rgba(255, 255, 255, 0.12);
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9aa2a0;
}

/* The record. A results column on a wall, not a leaderboard: the score is the thing you
   read, the note under it is why it matters. */
/* Left, not centred. The Play tab centres its contents, which turned a results column
   into a poem. */
.record { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; text-align: left; }

.record__row {
  position: relative;
  display: grid;
  gap: 5px;
  padding: 11px 13px 12px;
  overflow: hidden;
  background: rgba(255, 246, 214, 0.045);
  border-left: 3px solid var(--muted, #8d8573);
}

.record__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* --- five-bar gate --- */

.record__marks { display: flex; align-items: center; gap: 9px; }
.record__v {
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.4;
}

.tally { display: flex; align-items: flex-end; gap: 7px; min-width: 1.4em; }
.tally__group { position: relative; display: flex; gap: 3px; padding: 0 1px; }

.tally__mark {
  display: block;
  width: 2px;
  height: 21px;
  background: currentColor;
  border-radius: 1px;
  /* Chalk does not draw straight. */
  transform: rotate(var(--lean, 2deg));
}

.tally__group:nth-child(even) .tally__mark { --lean: -2.5deg; }

/* The fifth stroke, through the other four. */
.tally__group.is-five::after {
  content: "";
  position: absolute;
  left: -2px;
  right: -2px;
  top: 50%;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
  transform: rotate(-18deg);
}

.tally--won { color: #7fc08d; }
.tally--lost { color: #c9795f; }
.tally__none { font-family: var(--label); font-size: 13px; opacity: 0.4; }
.tally__many { font-family: var(--label); font-size: 15px; font-variant-numeric: tabular-nums; }

/* A run, stamped across the corner the way a result gets stamped on paper. */
.record__stamp {
  /* Bottom corner, not the top: at the top it landed straight across the tally marks,
     which are the one thing on this row worth looking at. */
  position: absolute;
  right: -20px;
  bottom: 6px;
  padding: 2px 22px 3px;
  font-family: var(--label);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  transform: rotate(-6deg);
  opacity: 0.9;
  pointer-events: none;
}

.record__stamp--hot { color: #0f1a12; background: #7fc08d; }
.record__stamp--cold { color: #1a0f0c; background: #c9795f; }

.record__row.is-up { border-left-color: #5f9e6a; }
.record__row.is-down { border-left-color: #8f2f22; }
.record__row.is-level { border-left-color: var(--brass, #e8b23a); }

.record__who {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 900;
  letter-spacing: -0.01em;
  color: var(--cream, #f4efe3);
}

.record__note {
  /* Room for the stamp to sit beside it rather than over it. */
  padding-right: 92px;
  font-family: var(--receipt);
  font-size: 12px;
  line-height: 1.25;
  color: var(--muted, #8d8573);
}

.record__all {
  margin: 8px 0 0;
  text-align: left;
  font-family: var(--receipt);
  font-size: 12px;
  line-height: 1.3;
  color: var(--muted, #8d8573);
}

/* Something has gone wrong. Sits over everything, because it is asked at the moment the
   rest of the screen has stopped being any use. */
.wrong {
  position: fixed;
  inset: 0;
  z-index: 600;
  display: grid;
  align-content: safe center;
  justify-items: center;
  overflow-y: auto;
  padding: 24px;
  background: rgba(0, 0, 0, 0.78);
  backdrop-filter: blur(4px);
}

.wrong__sheet {
  display: grid;
  gap: 9px;
  width: 100%;
  max-width: 340px;
  padding: 18px 18px 16px;
  background: #12100d;
  border-left: 4px solid #8f2f22;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.75);
}

.wrong__head {
  margin: 0;
  font-family: var(--display);
  font-size: 22px;
  font-weight: 900;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: #f2ede1;
}

.wrong__sub {
  margin: 0;
  font-family: var(--receipt);
  font-size: 14px;
  line-height: 1.3;
  color: #c9c1b0;
}

.wrong__field { display: grid; gap: 5px; margin-top: 2px; }

.wrong__field span {
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #8d8573;
}

.wrong__field input {
  min-height: 44px;
  padding: 8px 10px;
  font-family: var(--receipt);
  font-size: 16px;
  color: var(--cream, #f2ede1);
  background: rgba(255, 255, 255, 0.06);
  border: 0;
  border-bottom: 2px solid rgba(242, 237, 225, 0.4);
  border-radius: 0;
  -webkit-appearance: none;
  appearance: none;
}

.wrong__field input::placeholder { color: rgba(242, 237, 225, 0.38); }
.wrong__field input:focus { outline: 0; border-bottom-color: var(--ink-red); }

.wrong__row { display: flex; gap: 10px; justify-content: flex-end; margin-top: 4px; }

.wrong__done {
  margin: 2px 0 0;
  font-family: var(--receipt);
  font-size: 13px;
  color: var(--brass, #e8b23a);
}

/* How many of your lot are still recovering, said in words above the grid. */
.deck-sore {
  margin: 0 0 6px;
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #c9795f;
}

/* Whose photograph it is. Same chit as the edition number, in the same corner, because
   it answers the same question — where did this come from — and a house card never has
   an edition to clash with. Deliberately not a colour or a tint: it has to survive the
   card being 60px wide on the board. */
.card__house {
  position: absolute;
  left: 0.35em;
  bottom: 0.35em;
  z-index: 8;
  font-family: var(--label);
  font-size: 0.62em;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.18em 0.5em;
  color: #f3efe3;
  background: #12100c;
  border: 0.08em solid #f3efe3;
}

/* --- the containment frame --- */

.card__frame {
  position: relative;
  width: 100%;
  /* The photo is the product: fixed height, never shrunk by flex to make room for
     text. Everything else gives way instead. */
  flex: 0 0 auto;
  height: 8.6em;
  background: #111;
  border: 0.08em solid #ccc;
  overflow: visible;
  box-shadow: inset 0 0.4em 0.8em rgba(0, 0, 0, 0.9);
}

.card__photo { position: absolute; inset: 0; overflow: hidden; }

.card__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: contrast(1.12) saturate(1.06) sepia(0.08);
}

/* --- googly eyes --- */

/* Stuck on the photograph. Every pupil in the app reads the same two custom properties,
   set once a frame by googly.js from the phone's tilt, so twenty eyes cost one write
   and the movement happens on the compositor. */
.googly-pair {
  position: absolute;
  z-index: 7;
  touch-action: none;
  display: flex;
  gap: 0.1em;
  align-items: flex-start;
  pointer-events: none;
  /* font-size is set per card, so everything below scales with the eye itself. */
}

.googly {
  position: relative;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  background: #fbfbfa;
  overflow: hidden;
  box-shadow:
    inset 0 -0.08em 0.14em rgba(0, 0, 0, 0.3),
    inset 0 0.06em 0.1em rgba(255, 255, 255, 0.9),
    0 0.06em 0.12em rgba(0, 0, 0, 0.55);
}

/* The plastic dome over the top. A googly eye is a bead under a lens, and without the
   lens it reads as a printed dot. */
.googly::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle at 32% 26%, rgba(255, 255, 255, 0.95) 0 14%,
      rgba(255, 255, 255, 0.28) 22%, transparent 44%),
    radial-gradient(circle at 68% 78%, rgba(0, 0, 0, 0.16) 0 28%, transparent 52%);
}

/* The bead. It sits at the bottom at rest and rolls toward whichever way you tip. */
.googly__pupil {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0.46em;
  height: 0.46em;
  margin: -0.23em 0 0 -0.23em;
  border-radius: 50%;
  background: radial-gradient(circle at 36% 30%, #4a4a4a 0 18%, #121212 60%, #000 100%);
  box-shadow: 0 0.02em 0.04em rgba(0, 0, 0, 0.6);
  /* Where the bead has got to.
     --bx/--by are this bead's own simulated position inside its socket, written by
     googly.js for the beads currently on screen. Not a direction to point in: the
     numbers already include the overshoot and the bounce off the rim, so all this
     rule does is put the bead where the physics says it is. */
  transform: translate(calc(var(--bx, 0) * 0.27em), calc(var(--by, 1) * 0.27em));
  /* The bead is the only thing that moves, so it gets its own layer and the card
     underneath is never repainted for it. */
  will-change: transform;
}

/* Face down, withheld, or printed to PNG: no eyes. The back of a card is blank, a
   withheld card is deliberately not decorated, and the export is the card itself. */
.card--back .googly-pair,
.card--tier-Withheld .googly-pair { display: none; }

/* While the eyes can be moved, they get a handle you can actually see and aim at. */
.card--eyes-movable .googly-pair::after {
  content: "";
  position: absolute;
  inset: -0.35em;
  border: 1px dashed rgba(255, 255, 255, 0.55);
  border-radius: 0.6em;
  pointer-events: none;
}

.eye-tools {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  padding: 8px 12px 9px;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(226, 244, 250, 0.08);
}

/* The two nudges are the same square; Reset is the odd one out and says so. */
.eye-tools button { min-width: 44px; }
.eye-tools button:last-child { min-width: 0; padding-left: 14px; padding-right: 14px; }

.eye-tools__label {
  font-family: var(--receipt);
  font-size: 15px;
  color: #cfc5ad;
}

.eye-tools button {
  font-family: var(--label);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #17140f;
  background: #e6e0d0;
  border: 0;
  border-bottom: 3px solid #8b8578;
  border-right: 3px solid #8b8578;
  border-radius: 2px;
  min-width: 42px;
  min-height: 42px;
  padding: 6px 10px;
  cursor: pointer;
}

.eye-tools button:active {
  transform: translate(2px, 2px);
  border-bottom-width: 1px;
  border-right-width: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .googly__pupil { transform: translate(0, 0.27em); }
}

/* --- tier: glossy sticker, colour AND letter --- */

/* The rarity, in words.
   It was a glossy circular pin with a single letter on it — which is a code, and the
   card carried nothing to decode it against. As a label it says the thing outright and
   still reads as something stuck onto the card rather than printed on it. */
/* Type and grade: the first two lines of the receipt. Printed, not stuck on — they used
   to be a badge and a stamp over the photograph. Smaller than the numbers, because they
   are the heading and the numbers are the point. */
.stat--meta { font-size: 0.76em; }
.stat--meta dt { letter-spacing: 0.06em; color: #6a6558; }
.stat--meta dd {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-weight: 700;
  letter-spacing: 0.06em;
}
.stat__mark { width: 1.1em; height: 1.1em; display: inline-block; flex: 0 0 auto; }
.stat__mark svg { width: 100%; height: 100%; display: block; }
.stat--grade + .stat { margin-top: 0.18em; }
/* The rarer grades print in a different ink. Black for the two you see every day. */
.stat--grade-rare dd { color: var(--ink-red); }
.stat--grade-epic dd { color: #6f2bab; }
.stat--grade-legendary dd { color: var(--brass-deep); }
.card--duel .stat--meta { font-size: 0.66em; }
/* A shiny carries a power line under its ability, and the two rows above the numbers
   were the paper it had spare. It gets a little more paper rather than a clipped power:
   full size is one card on the screen, so an odd height costs nothing. */
.card--full.card--shiny { height: 25em; }
.card--full.card--evo-2.card--shiny, .card--full.card--evo-3.card--shiny { height: 29.1em; }
.stat--what dt { display: inline-flex; align-items: center; gap: 0.35em; color: #23201a; font-weight: 700; }
/* On the board and the roster a shiny's power gets one line, like its ability does. */
.card--duel .card__power, .card--grid .card__power {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  overflow: hidden;
}
.card__receipt--sealed { flex: 0 0 auto; }

.card__edition {
  position: absolute;
  left: 0.35em;
  bottom: 0.35em;
  z-index: 8;
  font-family: var(--label);
  font-size: 0.62em;
  font-weight: 800;
  letter-spacing: 0.1em;
  padding: 0.18em 0.5em;
  color: #12100c;
  background: #f3efe3;
  border: 0.08em solid #12100c;
  transform: rotate(-2.5deg);
  box-shadow: 0.1em 0.14em 0.22em rgba(0, 0, 0, 0.5);
}

.card--early { box-shadow: 0 0 0 0.08em rgba(232,178,58,.55), -0.18em 0.4em 0.9em rgba(0,0,0,.55); }

/* Hold-to-look, on covered cards only. */
.card__peek {
  position: absolute;
  left: 0.4em;
  bottom: 0.4em;
  z-index: 9;
  font-family: var(--label);
  font-size: 0.6em;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.3em 0.6em;
  border: 0.08em solid #12100c;
  background: #f3efe3;
  color: #12100c;
  cursor: pointer;
  touch-action: none;
  user-select: none;
}

.card__peek.is-open { background: #12100c; color: #f3efe3; }

/* --- the title: cut out and pasted on --- */

/* A fixed slot. The title scales to fit it rather than pushing into the photograph.
   min-height:0 is load-bearing — a flex item's automatic minimum size otherwise
   refuses to go below its content, and the slot silently grew to 3.3em, eating the
   receipt's space and clipping two of the five stats.

   font-size:1em is equally load-bearing: this is an <h2>, so the UA default of 1.5em
   applied and then `height: 2.2em` resolved against THAT — 11.8 x 1.5 x 2.2 = 38.9px
   instead of 26px. Every em on the card has to resolve against the card's own base. */
.card__title {
  flex: 0 0 auto;
  min-height: 0;
  font-size: 1em;
  font-weight: 400;
  height: 2.6em;
  margin: 0.35em 0 0;
  display: flex;
  justify-content: center;
  /* Grows UPWARD, into the photograph.
     This was `align-items: center` inside `overflow: hidden`, so a name that needed a
     third line was cut off at both ends and the top row of clippings lost its heads.
     Anchoring to the bottom means the box keeps its 2.6em — nothing below it moves —
     while anything that does not fit spills over the bottom of the image, which is a
     far better thing for a ransom note to do than to be trimmed.
     The padding reproduces where a single line used to sit when it was centred, so the
     common case looks exactly as it did. */
  align-items: flex-end;
  padding-bottom: 0.62em;
  text-align: center;
  overflow: visible;
  /* Above the photograph, so the overlap reads as pasted on rather than behind. */
  position: relative;
  z-index: 3;
}

.ransom {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  /* The gap between words has to be obviously bigger than the gap between letters or
     "Bust Umbrella" reads as BUSTUMBRELLA. */
  gap: 0.12em 0.5em;
  line-height: 1;
}

/* One unbreakable unit per word. Without this a line breaks between any two letters
   and "BURNT AIR FRYER" comes out as "BURNT AI / R FRYER". */
.ransom__word { display: inline-flex; flex-wrap: nowrap; gap: 0.04em; }

.ransom__l {
  position: relative;
  display: inline-block;
  padding: 0.1em 0.16em;
  font-size: 1.15em;
  line-height: 1;
  text-transform: uppercase;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), 0 1px 1px rgba(0, 0, 0, 0.3);
}

/* Print grain on the clipping itself. Without it the letters read as coloured boxes. */
.ransom__l::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--fibre);
  mix-blend-mode: multiply;
  opacity: 0.5;
}

/* Below the legibility floor the cut-out letters are noise, so the title is stamped
   instead. Same slot, same weight, readable at 88px wide. */
.ransom--plain {
  display: block;
  font-family: var(--display);
  font-size: 1.1em;
  font-weight: 800;
  letter-spacing: 0.01em;
  line-height: 1.02;
  text-transform: uppercase;
  color: #16140f;
}

/* Clippings from dry sources: forms, manuals, obituaries. Monochrome only, and each
   one is cut at its own angle with its own wonky scissor line. */
.rl-1 {
  font-family: "Playfair Display", Georgia, serif; font-weight: 900;
  background: #f0f0f0; color: #111;
  transform: translateY(var(--lift, 0)) rotate(-3deg);
  clip-path: polygon(2% 1%, 98% 3%, 99% 97%, 1% 99%);
}

.rl-2 {
  font-family: var(--display); font-weight: 900;
  background: #111; color: #f4f4f4;
  transform: translateY(var(--lift, 0)) rotate(2deg);
  clip-path: polygon(1% 2%, 99% 0%, 97% 99%, 3% 98%);
}

.rl-3 {
  font-family: "Courier New", monospace; font-weight: 700;
  background: #e5e5e5; color: #111;
  border-bottom: 1px solid #999;
  transform: translateY(var(--lift, 0)) rotate(-1deg);
  clip-path: polygon(0% 0%, 100% 2%, 98% 100%, 2% 98%);
}

.rl-4 {
  font-family: Arial, Helvetica, sans-serif; font-weight: 700;
  background: #fff; color: #000;
  transform: translateY(var(--lift, 0)) rotate(4deg);
  clip-path: polygon(3% 1%, 97% 2%, 100% 98%, 0% 100%);
}

.rl-5 {
  font-family: Georgia, serif; font-style: italic;
  background: #dcdcdc; color: #111;
  transform: translateY(var(--lift, 0)) rotate(-2deg);
  clip-path: polygon(1% 1%, 99% 0%, 98% 99%, 2% 98%);
}

/* --- the receipt --- */

.card__receipt {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  margin-top: 0.5em;
  padding: 0.3em 0.5em;
  background: #fdfcf7;
  color: #23201a;
  font-family: var(--receipt);
  box-shadow: inset 0 0 1em rgba(0, 0, 0, 0.03), 0 0.1em 0.3em rgba(0, 0, 0, 0.15);
  /* Torn off a thermal printer at both ends. */
  clip-path: polygon(
    0 2%, 6% 0, 12% 2%, 18% 0, 25% 2%, 31% 0, 37% 2%, 43% 0, 50% 2%, 56% 0,
    62% 2%, 68% 0, 75% 2%, 81% 0, 87% 2%, 93% 0, 100% 2%,
    100% 98%, 93% 100%, 87% 98%, 81% 100%, 75% 98%, 68% 100%, 62% 98%, 56% 100%,
    50% 98%, 43% 100%, 37% 98%, 31% 100%, 25% 98%, 18% 100%, 12% 98%, 6% 100%, 0 98%);
}

.card__cat {
  font-size: 0.86em;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: center;
  color: #6a6558;
  border-bottom: 0.08em dashed #b9b3a3;
  padding-bottom: 0.24em;
  margin-bottom: 0.3em;
}

.card__stats { display: grid; gap: 0.02em; margin: 0; }

.stat {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  border-bottom: 0.07em dotted #aaa;
  line-height: 0.98;
  color: #222;
}

.stat dt { font-size: 0.95em; letter-spacing: -0.02em; }
.stat dd { margin: 0; font-size: 1.05em; font-weight: 700; font-variant-numeric: tabular-nums; }

/* The contested stat: highlighted in yellow and then ringed in red marker, round the
   whole line rather than just the number — this is the one someone reached over and
   circled, and you circle a line, not a digit. */
.stat.is-contested {
  position: relative;
  color: #000;
  font-weight: 700;
  background: rgba(254, 240, 138, 0.62);
}

.stat.is-contested dd { color: #b91c1c; }

.stat.is-contested::before {
  content: "";
  position: absolute;
  inset: -0.1em -0.3em;
  border: 0.15em solid #dc2626;
  border-radius: 50% 40% 60% 30% / 30% 50% 40% 60%;
  opacity: 0.8;
  mix-blend-mode: multiply;
  transform: rotate(-1deg);
  pointer-events: none;
}

.stat__mod {
  font-family: var(--display);
  font-size: 0.62em;
  font-weight: 800;
  margin-left: 0.3em;
  padding: 0 0.28em;
  background: #23201a;
  color: #fdfcf7;
}

.card__total,
.card__cost-line {
  margin-top: 0.34em;
  padding-top: 0.24em;
  border-top: 0.08em dashed #b9b3a3;
  font-size: 0.94em;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  color: #4a463c;
}

/* --- the ability: stamped, not printed --- */

/* The ability is printed at the foot of the same slip, on a grey panel. */
.card__ability {
  flex: 0 0 auto;
  margin-top: 0.22em;
  padding: 0.3em;
  font-family: var(--display);
  font-size: 0.68em;
  font-weight: 700;
  line-height: 1.12;
  text-align: center;
  text-transform: uppercase;
  color: #1a1712;
  background: #e5e7eb;
  border-radius: 0.2em;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Two lines, always. flex-basis:auto let this get squeezed to one and clipped
     mid-word. These ems resolve against 0.78em, not the card's base. */
  height: 2.7em;
}

/* --- developing --- */

/* Staged by develop.js, one class per beat. Every rule here is additive: without the
   classes the card renders finished, so a failure in the sequence costs the animation
   and never the card. */

.card.developing {
  animation: print-lands 0.42s cubic-bezier(0.2, 1.3, 0.4, 1) both;
}

@keyframes print-lands {
  0% { opacity: 0; transform: translateY(-38px) rotate(-7deg) scale(1.1); }
  70% { opacity: 1; transform: translateY(4px) rotate(1.5deg) scale(0.99); }
  100% { opacity: 1; transform: none; }
}

/* Undeveloped: the print is there, the image is not. */
.card.developing .card__photo img {
  filter: blur(14px) saturate(0) brightness(2.4) contrast(0.3);
  opacity: 0.25;
  transform: scale(1.08);
}

/* And it comes up, slowly, the way it actually does. */
.card.developing.is-surfacing .card__photo img {
  transition:
    filter 1.15s cubic-bezier(0.3, 0.7, 0.3, 1),
    opacity 0.9s ease-out,
    transform 1.15s cubic-bezier(0.3, 0.7, 0.3, 1);
  filter: blur(0) saturate(1.06) brightness(1) contrast(1.12);
  opacity: 1;
  transform: none;
}

/* The receipt runs out from under the photograph. */
.card.developing .card__receipt {
  clip-path: inset(0 0 100% 0);
  opacity: 0;
}

.card.developing.is-printed .card__receipt {
  transition: clip-path 0.55s cubic-bezier(0.2, 0.9, 0.3, 1), opacity 0.2s ease-out;
  clip-path: inset(0 0 0 0);
  opacity: 1;
}

/* Each clipping is pressed on in turn. */
.card.developing .ransom__l,
.card.developing .ransom--plain { opacity: 0; }

.card.developing.is-titled .ransom__l {
  animation: letter-lands 0.3s cubic-bezier(0.2, 1.5, 0.4, 1) both;
  animation-delay: var(--land, 0ms);
}

.card.developing.is-titled .ransom--plain {
  animation: letter-lands 0.3s cubic-bezier(0.2, 1.5, 0.4, 1) both;
}

@keyframes letter-lands {
  0% { opacity: 0; transform: translateY(-9px) scale(1.5) rotate(-8deg); }
  100% { opacity: 1; }
}

/* The edition gets pressed on last, because it is the answer. */
.card.developing .card__edition { opacity: 0; }

.card.developing.is-stamped .card__edition {
  animation: letter-lands 0.3s ease-out 0.26s both;
}

/* A good find gets the room lit up for a moment. */
.card.developing--loud.is-stamped .card__frame::before {
  content: "";
  position: absolute;
  inset: -320%;
  z-index: 40;
  pointer-events: none;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 250, 220, 0.85) 0%, transparent 62%);
  animation: flare 0.75s ease-out both;
}

@keyframes flare {
  0% { opacity: 0; transform: scale(0.3); }
  22% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.35); }
}

@media (prefers-reduced-motion: reduce) {
  .card.developing,
  .card.developing * { animation: none !important; transition: none !important; }
  .card.developing .card__photo img { filter: none; opacity: 1; transform: none; }
  .card.developing .card__receipt { clip-path: none; opacity: 1; }
  .card.developing .ransom__l,
  .card.developing .card__edition { opacity: 1; }
}

/* --- field notes, full size only --- */

.card__flavour {
  margin-top: 0.45em;
  padding: 0.4em 0.5em;
  font-family: var(--receipt);
  font-size: 0.86em;
  line-height: 1.2;
  text-align: center;
  color: #2b271f;
  background: #fbf8ee;
  border: 0.05em solid #ddd6c4;
  box-shadow: 0 0.08em 0.2em rgba(0,0,0,.14);
}

/* --- field notes: a separate scrap, not part of the card --- */

.notes {
  position: relative;
  isolation: isolate;
  width: 100%;
  max-width: 17em;
  margin: 1.6em auto 0;
  padding: 1.1em 1.1em 0.9em;
  color: #221f18;
  transform: rotate(-1.4deg);
  filter: drop-shadow(0 0.35em 0.9em rgba(0, 0, 0, 0.55));
}

/* The torn sheet, behind the content — the clip-path used to be on .notes itself and
   sliced the "Field notes" tab in half where it overhangs the top edge. */
.notes::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: #fbf8ee;
  clip-path: polygon(0 1.5%, 4% 0, 9% 2%, 15% 0, 22% 1.5%, 30% 0, 38% 2%, 46% 0,
    55% 1.5%, 63% 0, 72% 2%, 80% 0, 88% 1.5%, 95% 0, 100% 2%,
    100% 97%, 94% 100%, 86% 98%, 78% 100%, 69% 97.5%, 60% 100%, 51% 98%,
    42% 100%, 33% 97.5%, 24% 100%, 15% 98%, 7% 100%, 0 97.5%);
}

.notes__tab {
  position: absolute;
  top: -0.55em;
  left: 50%;
  transform: translateX(-50%) rotate(-2.5deg);
  font-family: var(--display);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #3b372c;
  background: #e3dcb8;
  padding: 0.3em 0.9em;
  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);
}

.notes__meta {
  text-align: center;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #6b6254;
  margin-bottom: 5px;
}

.notes__meta b { color: #17140f; font-weight: 400; }

.notes__body {
  font-family: var(--receipt);
  font-size: 20px;
  line-height: 1.15;
  text-align: center;
  margin: 0;
}

/* --- sealed and face down --- */

.card--concealed .card__frame { filter: none; }

.card__sealed {
  margin-top: 0.4em;
  padding: 0.7em 0.4em;
  text-align: center;
  font-family: var(--display);
  font-size: 0.72em;
  font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #56513f;
  background-color: #e7e1cf;
  background-image: repeating-linear-gradient(-45deg,
    rgba(22, 20, 15, 0.1) 0 0.4em, transparent 0.4em 0.8em);
}

.card--back {
  aspect-ratio: 13.5 / 20;
  display: grid;
  place-content: center;
  background:
    repeating-linear-gradient(45deg, rgba(232,178,58,.06) 0 0.5em, transparent 0.5em 1em),
    linear-gradient(168deg, #2a241c, #15120e);
  border: 0.12em solid #3a332a;
}

.card__backmark {
  font-family: var(--display);
  font-size: 0.78em;
  font-weight: 800;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: #6c6352;
}

/* --- what each size drops --- */

.card--hand {
  padding: 0.4em 0.4em 0.45em;
  /* Four things on it instead of eight, so it is shorter than the others. Measured after
     the stat rows were tightened: at 17.2em the cost line dropped off the bottom of every
     card on the bench. */
  height: 18.4em;
}
.card--hand .card__frame { height: 7em; }
.card--hand .card__cat { display: none; }
.card--hand .card__title { margin-top: 0.4em; min-height: 1.2em; }

.card--grid .card__frame { height: 7.8em; }

/* Duel size exists to make five numbers readable at 159px. Everything that is not a
   number gets out of the way — including the second line of the ability, which was
   pushing the fifth stat out of the receipt. */
.card--duel .card__cat,
.card--duel .card__total { display: none; }
.card--duel .card__ability { -webkit-line-clamp: 1; }
.card--duel .card__receipt { padding: 0.45em 0.55em; }
.card--duel .card__ability { font-size: 0.6em; }

/* ---------- reveal ---------- */

/* This was written as a flex column — align-items, justify-content, gap — but
   `.view.is-active` sets display:block, so none of it ever applied. Everything on the
   screen stacked flush against everything else: the card, the field notes, the eye
   tools and the buttons all touched, with measured gaps of exactly zero. */
.view--reveal { padding: 14px 16px 14px; }

.view--reveal.is-active {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 13px;
}

/* One column for everything under the card.
   The card itself is card-shaped and sets its own width, but the three rows below it
   were each sized by whatever they happened to contain — insets of 51px, 43px and 28px
   on a 375px screen. Four different edges stacked down the screen reads as broken
   rather than as layout, so they share a width now and cannot drift apart again. */
.view--reveal { --reveal-col: min(20em, 100%); }
.view--reveal .notes,
.view--reveal .eye-tools,
.view--reveal .reveal-actions {
  width: 100%;
  max-width: var(--reveal-col);
}

.reveal-slot {
  display: grid;
  place-items: center;
  gap: 11px;
  perspective: 1000px;
  width: 100%;
}

.reveal-slot .card { animation: flip-in 0.55s cubic-bezier(0.2, 0.9, 0.25, 1) both; }

/* The one that got away: you see exactly what you missed, which is the point. */
.reveal-slot.is-escaped .card {
  filter: grayscale(0.85) brightness(0.62);
  transform: rotate(-2.5deg);
  animation: none;
}

.reveal-slot.is-escaped { position: relative; }

.reveal-slot.is-escaped::after {
  content: "Got away";
  position: absolute;
  top: 28%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-7deg);
  font-family: var(--display);
  font-size: 36px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cream);
  border: 4px solid var(--cream);
  border-radius: 6px;
  padding: 6px 18px;
  white-space: nowrap;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.7);
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.5);
}

@keyframes flip-in {
  from { transform: rotateY(88deg) scale(0.9); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.reveal-note {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brass);
  text-align: center;
}

/* The buttons hold off until the card has finished arriving — offering "Bin it" over
   a half-developed photograph undercuts the whole moment. */
/* Two or four buttons, depending on whether the card can be fed and shared.
   A flex row was fine at two and cramped four into three lines of one-word-per-line
   stacks. A grid puts the thing you came here to press across the bottom and the
   secondary actions in a row above it, at any count. */
.reveal-actions {
  display: grid;
  /* 5.6em so three ghosts fit across a 375px phone. Above that the row broke after two
     and left the third stranded on its own line. */
  grid-template-columns: repeat(auto-fit, minmax(5.6em, 1fr));
  align-items: stretch;
  justify-content: center;
  gap: 8px;
  width: 100%;
  max-width: 22em;
  transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1);
}

/* The primary always gets its own full-width row under the rest. */
.reveal-actions .btn--primary { grid-column: 1 / -1; }

.reveal-actions .btn { white-space: normal; text-wrap: balance; }
.reveal-actions .btn--ghost { padding-left: 6px; padding-right: 6px; font-size: 15px; }

.reveal-actions.is-waiting {
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
}

.reveal-slot.is-developing { cursor: pointer; }

/* ---------- deck ---------- */

.view--deck {
  padding: 16px max(16px, env(safe-area-inset-right, 0px)) 24px
           max(16px, env(safe-area-inset-left, 0px));
  gap: 14px;
}

.deck-head { display: flex; align-items: baseline; justify-content: space-between; }

.deck-head h2 {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: #17140f;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  padding: 5px 14px 7px;
  transform: rotate(-2deg);
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.45);
  clip-path: polygon(2% 0, 11% 3%, 21% 0, 31% 3%, 98% 1%, 100% 97%, 89% 95%, 79% 100%, 68% 96%, 2% 99%);
}

.deck-head p {
  font-family: var(--receipt);
  font-size: 16px;
  line-height: 1.1;
  color: #ddd2b8;
  text-align: right;
}

.deck-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px;
  width: 100%;
  align-content: start;
  justify-items: center;
}

.empty {
  font-family: var(--receipt);
  font-size: 17px;
  line-height: 1.2;
  color: #ddd2b8;
  max-width: 32ch;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}

/* ---------- play: the board ---------- */

.view--play { padding: 14px 12px 24px; }

/* The board provides its own edges, and takes the masthead's height too. */
.view--play:has(#tcg-board:not([hidden])) { padding: 0; }

body:has(#tcg-board:not([hidden])) .topbar { display: none; }

.play-screen {
  display: grid;
  gap: 13px;
  justify-items: center;
  text-align: center;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
}

.play-screen--wide { max-width: 720px; gap: 10px; }

/* The board is the screen. Full bleed, a column, and the tray sits on the bottom edge
   however tall the phone is — a grid let it float in the middle with dead table under it. */
/* A play surface, not a document. It is exactly the screen minus the tabs, it does not
   scroll, and every part of it is sized from what is left rather than from a guess at
   how tall the phone is. min-height:0 on the flexible rows is load-bearing — without it
   a flex item refuses to shrink below its content and the hand slides under the tabs. */
#tcg-board {
  display: flex;
  flex-direction: column;
  gap: 7px;
  max-width: none;
  width: 100%;
  margin-left: 0;
  padding:
    env(safe-area-inset-top, 0px)
    env(safe-area-inset-right, 0px)
    0
    env(safe-area-inset-left, 0px);
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

#tcg-board > * { position: relative; z-index: 1; }

/* Old scalpel cuts. A mat that has been used has these; a coffee ring belongs on the
   cardboard this replaced, and multiply blend made it invisible on a dark ground. */
#tcg-board::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.5;
  background-image:
    linear-gradient(97deg, transparent calc(50% - 0.5px),
      rgba(226, 244, 250, 0.12) 50%, transparent calc(50% + 0.5px)),
    linear-gradient(84deg, transparent calc(50% - 0.5px),
      rgba(226, 244, 250, 0.09) 50%, transparent calc(50% + 0.5px)),
    linear-gradient(6deg, transparent calc(50% - 0.5px),
      rgba(226, 244, 250, 0.07) 50%, transparent calc(50% + 0.5px));
  background-repeat: no-repeat;
  background-size: 150px 88px, 104px 132px, 70px 190px;
  background-position: 84% 13%, 11% 62%, 62% 74%;
}

.play-eyebrow {
  justify-self: center;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #eee;
  background: #131110;
  padding: 3px 10px 4px;
  border-radius: 2px;
  transform: rotate(-1.2deg);
  box-shadow:
    inset 1px 1px 2px rgba(255, 255, 255, 0.32),
    inset -1px -1px 2px rgba(0, 0, 0, 0.85),
    0 3px 5px rgba(0, 0, 0, 0.55);
}

/* Written straight onto the table in marker. */
.play-head {
  font-family: var(--display);
  font-size: 32px;
  font-weight: 900;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 0.98;
  text-wrap: balance;
  color: #f6f2e6;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.5), 0 8px 18px rgba(0, 0, 0, 0.65);
}

.play-sub {
  font-family: var(--receipt);
  font-size: 17px;
  line-height: 1.15;
  letter-spacing: 0.02em;
  color: #e3d9c1;
  max-width: 36ch;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}

/* Two cards, put up against each other on one stat. */
.duel {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 8px;
  align-items: center;
  width: 100%;
}

.duel__v {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.duel__side { display: grid; gap: 6px; justify-items: center; }

.duel__side .card { max-width: 100%; }

.duel__label {
  font-family: var(--display);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.duel__side--won .duel__label { color: var(--brass); }
.duel__side--lost { opacity: 0.62; }

.view--challenge { padding: 18px 14px 28px; }

/* --- the dare: a note stuck to the front of the card someone sent you --- */

/* Highlighter yellow with a hard black keyline and a solid offset shadow. It is the
   loudest object in the app because it is the only one that arrived from someone else. */
.dare {
  align-self: stretch;
  margin: 0 2px;
  padding: 9px 14px 11px;
  background: #ffeb3b;
  border: 2px solid #000;
  box-shadow: 4px 4px 0 #111;
  transform: rotate(-2deg);
  text-align: left;
}

.dare__line {
  font-family: var(--receipt);
  font-size: 17px;
  line-height: 1.05;
  text-transform: uppercase;
  color: #16140f;
}

.dare__line--big { font-size: 23px; font-weight: 700; }

.dare b { color: #c0281e; font-weight: 700; }

/* A single card under a bare bulb. */
.spotlit { position: relative; }

.spotlit::before {
  content: "";
  position: absolute;
  top: -14%;
  left: 50%;
  width: 78%;
  aspect-ratio: 1;
  transform: translateX(-50%);
  border-radius: 50%;
  background: rgba(255, 250, 226, 0.14);
  filter: blur(38px);
  pointer-events: none;
}

.spotlit > * { position: relative; }

/* --- the field manual, shown once --- */

.manual {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: grid;
  /* Centred when it fits, scrollable when it does not. `place-items: center` alone
     centres an over-tall sheet on the viewport, which crops it equally at BOTH ends —
     so on a short phone the type rules and the Next button were both off-screen and
     the manual could not be got past. */
  align-content: safe center;
  justify-items: center;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 24px;
  background: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(4px);
}

.manual__sheet {
  position: relative;
  width: 100%;
  max-width: 340px;
  padding: 26px 22px 22px;
  background: #fcfcfc;
  background-image: var(--fibre);
  background-blend-mode: multiply;
  border: 1px solid #ddd;
  transform: rotate(1deg);
  box-shadow: 0 15px 30px rgba(0, 0, 0, 0.8);
}

/* Stuck on over the top edge, half on the sheet and half off it. */
.manual__tab {
  position: absolute;
  top: -14px;
  left: 50%;
  transform: translateX(-50%) rotate(-2deg);
  padding: 5px 16px 7px;
  font-family: var(--display);
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #17140f;
  white-space: nowrap;
}

/* One step at a time. The whole thing used to be a single card of four paragraphs,
   which explained the controls and never once said why anybody would point a camera at
   a bin. The first thing on screen is now a finished card, so the output is understood
   before anything is asked of the player. */
.manual__step {
  display: none;
  gap: 12px;
  margin-top: 16px;
  font-family: var(--receipt);
  color: #17140f;
}

.manual__step.is-on { display: grid; }

.manual__lead {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  line-height: 1.05;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  text-wrap: balance;
}

.manual__body-text,
.manual__note {
  font-size: 17px;
  line-height: 1.15;
  letter-spacing: 0.02em;
}

.manual__note { color: #5d5445; }

.manual__list { display: grid; gap: 5px; list-style: none; font-size: 16px; line-height: 1.1; }
.manual__list b { font-weight: 400; text-transform: uppercase; letter-spacing: 0.04em; }

/* The example card, sat on the page at the size it would be in a deck. */
.manual__demo { display: grid; place-items: center; padding: 2px 0; }
.manual__demo .card { --em-grid: 11.4px; transform: rotate(-1.6deg); }

.manual__field {
  display: grid;
  gap: 5px;
  margin-top: 4px;
  font-family: var(--receipt);
  font-size: 16px;
  line-height: 1.1;
  color: #4a4234;
}

/* A line ruled on the page, not a form control. */
.manual__field input,
.code-entry input {
  font-family: var(--label);
  font-size: 17px;
  letter-spacing: 0.06em;
  color: #17140f;
  background: transparent;
  border: 0;
  border-bottom: 2px solid #17140f;
  border-radius: 0;
  padding: 5px 2px;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
}

/* Ink for the ground it is actually on.
   These two inputs — add an enemy, find somebody by tag, change your own tag — all sit
   on the dark Play tab and on the dark account sheet, and they were inheriting the ink
   written for the field manual's cream paper: #17140f typed onto #14110d. You could
   type a whole gamertag and see nothing happen. The manual keeps the black, because the
   manual is on paper. */
.code-entry input {
  color: var(--cream, #f2ede1);
  border-bottom-color: rgba(242, 237, 225, 0.5);
  caret-color: var(--cream, #f2ede1);
}

.manual__field input:focus,
.code-entry input:focus { outline: 0; border-bottom-color: var(--ink-red); }

.manual__field input::placeholder { color: #b3aa97; }
.code-entry input::placeholder { color: rgba(242, 237, 225, 0.42); }

.code-entry {
  display: flex;
  gap: 8px;
  align-items: center;
  width: 100%;
  max-width: 280px;
}

.code-entry input { text-align: center; }

.manual__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 20px;
}

.manual__dots { display: flex; gap: 6px; }

.manual__dots i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(23, 20, 15, 0.22);
}

.manual__dots i.is-on { background: var(--ink-red); }

.manual__back {
  font-family: var(--receipt);
  font-size: 16px;
  color: #5d5445;
  background: none;
  border: 0;
  padding: 8px 4px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.manual__go {
  font-family: var(--label);
  font-size: 14px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #eee;
  background: #131110;
  border: 0;
  padding: 9px 22px 11px;
  border-radius: 2px;
  cursor: pointer;
  min-height: 44px;
  box-shadow:
    inset 1px 1px 2px rgba(255, 255, 255, 0.34),
    inset -1px -1px 2px rgba(0, 0, 0, 0.85),
    0 3px 5px rgba(0, 0, 0, 0.55);
}

.manual__go:active { transform: translateY(1px); }

/* --- deck builder --- */

.build-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  justify-content: center;
}

.build-count {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-red);
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  padding: 5px 14px 7px;
  transform: rotate(-1.8deg);
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.45);
  clip-path: polygon(2% 0, 11% 3%, 21% 0, 31% 3%, 98% 1%, 100% 97%, 89% 95%, 79% 100%, 68% 96%, 2% 99%);
}

.build-curve { display: flex; align-items: flex-end; gap: 3px; height: 30px; }

.build-curve span {
  width: 14px;
  min-height: 3px;
  position: relative;
  background-color: var(--tape-grey);
  background-image:
    linear-gradient(108deg, transparent 18%, rgba(255, 255, 255, 0.22) 42%, transparent 62%),
    repeating-linear-gradient(0deg, transparent 0 2px, rgba(0, 0, 0, 0.12) 2px 4px);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.45), 0 2px 4px rgba(0, 0, 0, 0.5);
  clip-path: polygon(0 3%, 40% 0, 100% 3%, 100% 100%, 0 100%);
}

.build-curve span::after {
  content: attr(data-cost);
  position: absolute;
  bottom: -14px;
  left: 0;
  right: 0;
  text-align: center;
  font-family: var(--receipt);
  font-size: 13px;
  line-height: 1;
  color: #cdc0a5;
}

/* The two things you came here to do, kept on the screen while the grid scrolls past
   them. Before this the whole of a small phone was header and you had to scroll to
   reach either button. */
.build-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }

/* Sticky, not fixed: it rides the bottom of the screen it belongs to, so it can never
   sit on top of the tabs or need to know how tall they are. */
.build-dock {
  position: sticky;
  bottom: 0;
  z-index: 55;
  /* Stretch, don't shrink. left/right do not size a sticky element — they are offsets —
     so under .play-screen's `justify-items: center` this bar sized itself to its two
     buttons: 234px in the middle of a 375px screen, while every other row on the tab
     was full width.
     justify-self, not align-self: in a grid the inline axis is the justify one, and
     align-self only moves it up and down. */
  justify-self: stretch;
  width: auto;
  margin: 0 -12px -24px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 12px max(12px, env(safe-area-inset-left, 0px)) 14px;
  /* Solid the whole way under the buttons. The fade is a separate strip above, because
     putting it on the dock itself left the buttons sitting in the transparent half of
     their own gradient with card art showing through the words. */
  background: rgba(12, 9, 5, 0.98);
  border-top: 1px solid rgba(255, 246, 214, 0.08);
}

/* A full team. The Fight button takes the bar over: Pick for me shrinks back to the
   footnote it now is, and the thing you came here to press gets the room. */
.build-dock.is-ready #build-auto {
  flex: 0 0 auto;
  font-size: 12px;
  opacity: 0.55;
  padding: 8px 10px;
}

.btn--primary.is-ready {
  flex: 1 1 auto;
  padding: 15px 18px;
  font-size: 21px;
  letter-spacing: 0.06em;
  background-color: #8f2f22;
  box-shadow:
    inset 0 1px 2px rgba(255, 255, 255, 0.45),
    0 5px 14px rgba(143, 47, 34, 0.5),
    0 4px 8px rgba(0, 0, 0, 0.6);
  animation: ready-nudge 2.4s ease-in-out infinite;
}

@keyframes ready-nudge {
  0%, 92%, 100% { transform: none; }
  96% { transform: translateY(-2px) rotate(-0.5deg); }
}

@media (prefers-reduced-motion: reduce) {
  .btn--primary.is-ready { animation: none; }
}

.build-dock::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 34px;
  pointer-events: none;
  background: linear-gradient(to top, rgba(12, 9, 5, 0.98), rgba(12, 9, 5, 0));
}

.build-dock .btn--ghost { color: #e6dcc2; text-decoration-color: rgba(230, 220, 194, 0.5); }

/* The grid has to clear the dock as well as the tabs. */
.view--play:has(#tcg-setup:not([hidden])) { padding-bottom: 0; }

/* Picked, and obvious about it: lifted off the table, ringed, and numbered with the
   order it went in.
   The ring used to be neon green. It did the job and looked like a debug highlight —
   the one colour in the app that belongs to no material. Brass is already the colour of
   every pin, chit and stamp here, and a warm glow reads as the card being held up to
   the light rather than being selected in a form. */
.card--picked {
  outline: 4px solid var(--brass);
  outline-offset: 3px;
  transform: translateY(-8px) rotate(-1.4deg);
  box-shadow:
    0 0 20px rgba(232, 178, 58, 0.32),
    -0.2em 0.8em 1.5em rgba(0, 0, 0, 0.7);
}

/* The order it went in, stamped on the same brass chit as the counter above the grid —
   so the thing that says "two of three" and the thing that says "this is the second
   one" are visibly the same object. */
.card__picked-n {
  position: absolute;
  top: -0.7em;
  right: -0.55em;
  z-index: 32;
  width: 2.1em;
  height: 2.1em;
  display: grid;
  place-content: center;
  border-radius: 50%;
  font-family: var(--display);
  font-size: 0.95em;
  font-weight: 900;
  color: #2a1f08;
  background: radial-gradient(circle at 34% 28%, var(--brass-pale) 0 22%, var(--brass) 55%, var(--brass-deep) 100%);
  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.6), inset 0 0.06em 0 rgba(255, 255, 255, 0.5);
  transform: rotate(-6deg);
}

/* Once the team is full, everything not in it steps back rather than looking available.
   Named --unpicked, not --full: renderCard already emits card--full for the full SIZE,
   and the collision dimmed every big card in the app to 42%. */
.card--unpicked { opacity: 0.42; filter: saturate(0.55); }

.deck-grid .card {
  transition: transform 0.16s cubic-bezier(0.2, 0.9, 0.3, 1), opacity 0.16s ease;
  cursor: pointer;
}

/* Bottom-right is the only free corner: tier is top-right, edition top-left, type
   bottom-left. The cost badge used to sit on top of the edition stamp. */
.card__cost {
  position: absolute;
  bottom: 6px;
  right: 6px;
  z-index: 3;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--brass);
  border: 1px solid var(--brass);
  font-family: var(--display);
  font-size: 14px;
  font-weight: 800;
  display: grid;
  place-content: center;
}

/* The advantage loop. Reads left to right and wraps round. */
.cycle {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 9px 11px 10px;
  background: #fbf9f2;
  background-image: var(--fibre);
  background-blend-mode: multiply;
  transform: rotate(-0.8deg);
  box-shadow: -0.1em 0.2em 0.3em rgba(0, 0, 0, 0.3), 0 0.5em 1em rgba(0, 0, 0, 0.5);
  clip-path: polygon(1% 0, 99% 1%, 100% 99%, 0 100%);
}

.cycle__type {
  font-family: var(--display);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #fff;
  padding: 3px 7px;
  border-radius: 3px;
  white-space: nowrap;
}

.cycle__arrow {
  font-family: var(--receipt);
  font-size: 14px;
  line-height: 1;
  color: #5d5445;
  white-space: nowrap;
}

.chip__counter {
  position: absolute;
  top: 1px;
  right: 2px;
  font-size: 9px;
  color: var(--brass);
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
  line-height: 1;
}

/* --- the setup screen: who, then your three --- */

/* It used to be a count, a legend, a "versus" docket and a grid, in that order, with no
   indication of which came first or that the docket was where you chose an opponent.
   Two labelled steps, in the order you do them. */
.setup-step {
  display: grid;
  gap: 9px;
  justify-items: center;
  width: 100%;
}

.setup-step__n {
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #eee;
  background: #131110;
  padding: 3px 11px 4px;
  border-radius: 2px;
  transform: rotate(-1.2deg);
  box-shadow:
    inset 1px 1px 2px rgba(255, 255, 255, 0.32),
    inset -1px -1px 2px rgba(0, 0, 0, 0.85),
    0 3px 5px rgba(0, 0, 0, 0.55);
}

/* --- duels ---
   Named .duel-card, not .duel: .duel is already the two-cards-compared layout on the
   challenge result screen, and it is grid-template-columns: 1fr auto 1fr — which laid
   this note out in three columns. */

.duel-list { display: grid; gap: 9px; width: 100%; }

/* A challenge waiting on you is a note somebody left. It is the loudest thing on the
   screen because it is the only part of this game that came from another person. */
.duel-card {
  display: grid;
  gap: 5px;
  padding: 11px 14px 13px;
  background: #f2eee2;
  background-image: var(--fibre);
  background-blend-mode: multiply;
  color: #17140f;
  transform: rotate(-0.5deg);
  box-shadow: 0 0.4em 1em rgba(0, 0, 0, 0.55);
  clip-path: polygon(0 2%, 100% 0, 99% 100%, 1% 98%);
}

.duel-card:nth-child(even) { transform: rotate(0.7deg); }

.duel-card--open {
  background: #ffeb3b;
  border: 2px solid #000;
  box-shadow: 4px 4px 0 #111;
  clip-path: none;
}

.duel-card__who {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 900;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  text-wrap: balance;
}

.duel-card__line {
  font-family: var(--receipt);
  font-size: 16px;
  line-height: 1.1;
  color: #4a4234;
}

.duel-card--open .duel-card__line { color: #17140f; }

.duel-card__go {
  justify-self: start;
  margin-top: 4px;
  font-family: var(--label);
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #eee;
  background: #131110;
  border: 0;
  border-radius: 2px;
  min-height: 44px;
  padding: 9px 18px;
  cursor: pointer;
}

.duel-card__go:active { transform: translateY(1px); }

/* Settled: who took it. */
.duel-card__verdict {
  font-family: var(--display);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  justify-self: start;
  padding: 2px 8px 3px;
  border-radius: 2px;
}

.duel-card__verdict--won { background: #39ff14; color: #08240a; }
.duel-card__verdict--lost { background: #c0281e; color: #fff; }
.duel-card__verdict--drew { background: #6b6254; color: #fff; }

.duel-card__tally {
  font-family: var(--receipt);
  font-size: 15px;
  color: #ddd2b8;
  text-align: center;
}

.duel-card__tally b { color: #39ff14; font-weight: 400; }

/* Who you are playing, on a slip stuck to the table. */
.opponent {
  display: grid;
  gap: 8px;
  justify-items: center;
  width: 100%;
  padding: 11px 14px 13px;
  background: #f2eee2;
  background-image: var(--fibre);
  background-blend-mode: multiply;
  transform: rotate(0.6deg);
  box-shadow: 0 0.4em 1em rgba(0, 0, 0, 0.55);
  clip-path: polygon(0 2%, 100% 0, 99% 100%, 1% 98%);
}

.opponent__who {
  font-family: var(--display);
  font-size: 16px;
  font-weight: 900;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: #17140f;
  text-align: center;
  text-wrap: balance;
}

.opponent__who b { color: var(--ink-red); }
.opponent__who.is-challenge { cursor: pointer; }

.opponent .btn--ghost { color: #4a4234; text-decoration-color: rgba(74, 66, 52, 0.45); }

.versus__msg {
  /* It now sits on its own on the Play tab rather than inside a disclosure, so it needs
     to look like something that just happened rather than like body text. */
  margin: 10px 0 0;
  padding: 9px 12px;
  font-family: var(--receipt);
  font-size: 15px;
  text-align: center;
  line-height: 1.2;
  color: #f2ede1;
  background: rgba(255, 246, 214, 0.07);
  border-left: 3px solid var(--brass, #e8b23a);
}

/* A queue you are actually in, said on the button you are in it with. */
.rival.is-waiting {
  outline: 2px solid var(--brass, #e8b23a);
  outline-offset: -2px;
  animation: waiting-pulse 1.6s ease-in-out infinite;
}

/* The share code is stamped, because it is the one thing you read out loud. */
.versus__msg b {
  font-family: var(--label);
  font-size: 19px;
  letter-spacing: 0.2em;
  color: #eee;
  background: #131110;
  display: inline-block;
  margin-top: 7px;
  padding: 4px 12px 5px;
  border-radius: 2px;
  box-shadow:
    inset 1px 1px 2px rgba(255, 255, 255, 0.3),
    inset -1px -1px 2px rgba(0, 0, 0, 0.85),
    0 3px 5px rgba(0, 0, 0, 0.5);
}

.btn--small { font-size: 12.5px; padding: 8px 14px 10px; }

/* What you just put down, and what it had to say about itself. This is where the card
   writing gets back into the game — during play you otherwise only see numbers. */
.played { display: grid; gap: 5px; width: 100%; }

.played__row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px;
  align-items: baseline;
  text-align: left;
  font-size: 12.5px;
  line-height: 1.35;
  padding: 5px 7px;
  border-left: 2px solid var(--brass);
  background: rgba(232, 178, 58, 0.06);
}

.played__name {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--cream);
  white-space: nowrap;
}

.played__said { color: var(--muted); font-style: italic; }

.card--borrowed { }

.hand__card.is-borrowed .hand__name::after {
  content: " · house";
  color: #8d8573;
  font-weight: 400;
}

/* --- the heads-up display --- */

/* --- the strip across the top --- */

/* Round number on duct tape at the left, the five rounds as tiles at the right. */
.hud {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 14px 14px 0;
  background: none;
  box-shadow: none;
  clip-path: none;
  transform: none;
}

.hud__tape {
  padding: 5px 12px 7px;
  transform: rotate(1deg);
}

.hud__turn {
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #eee;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 1);
}

/* Who is on the other side. Pushed to the right so it sits over their squad dots, and
   deliberately quiet: it is a label you glance at, not a headline. A person's name gets
   the brighter ink, because "The bot" and "Karl's deck" are not the same news. */
.hud__vs {
  flex: 1 1 auto;
  min-width: 0;
  align-self: center;
  overflow: hidden;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: rgba(238, 238, 238, 0.5);
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 1);
}

.hud__vs.is-person { color: rgba(255, 226, 150, 0.95); }

/* --- the docket --- */

/* Five numbered tiles, the way you would number rounds on a scrap of plastic. The
   mockup has the number alone; the stat code underneath is the one addition, because
   knowing round four is Whiff is the whole of the strategy and a bare number hides it. */
.schedule { display: flex; gap: 6px; }

.round-chip {
  position: relative;
  width: 34px;
  height: 34px;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 0;
  border-radius: 4px;
  background: #f4f4f4;
  border: 1px solid #aaa;
  color: #17140f;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5);
}

.round-chip__n {
  font-family: var(--display);
  font-size: 13px;
  font-weight: 900;
  line-height: 1;
}

.round-chip__stat {
  font-family: var(--display);
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.1;
  text-transform: uppercase;
  opacity: 0.6;
}

/* Played rounds go grey and drop back. */
.round-chip.is-done {
  background: #ccc;
  border-color: #888;
  opacity: 0.5;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35);
}

/* The one being played is the only coloured thing up here. */
.round-chip.is-now {
  background: var(--t-common);
  border: 2px solid #a16207;
  opacity: 1;
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.5);
}

/* Ticked or crossed in pen, over the top of the number. */
.round-chip__mark {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  line-height: 1;
  color: transparent;
  pointer-events: none;
}

.round-chip.is-won .round-chip__mark { color: rgba(21, 128, 61, 0.7); }
.round-chip.is-lost .round-chip__mark { color: rgba(220, 38, 38, 0.7); }

/* A round with a condition is dog-eared. */
.round-chip.is-special::after {
  content: "";
  position: absolute;
  top: -1px;
  right: -1px;
  border-width: 0 7px 7px 0;
  border-style: solid;
  border-color: transparent #ff1744 transparent transparent;
}

/* --- the contest --- */

.contest {
  display: grid;
  gap: 8px;
  justify-items: center;
  width: 100%;
  padding: 0 10px;
}

/* Cut from five different magazines and stuck down. Set by ransom.js. */
.contest__name {
  font-size: clamp(30px, 9.5vw, 40px);
  line-height: 1.15;
  max-width: 100%;
  padding: 2px 10px;
  transform: rotate(-1deg);
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.55));
}

/* Printed white-on-black so it separates from the table without a box round it. */
.contest__blurb {
  font-family: var(--receipt);
  font-size: 15px;
  line-height: 1.2;
  letter-spacing: 0.03em;
  color: #fff;
  background: #000;
  border: 1px solid rgba(255, 255, 255, 0.2);
  padding: 2px 12px 3px;
  transform: rotate(1deg);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.6);
}

/* The condition is the only red on the board, on a slip of laser-printed paper. */
.lane__cond {
  margin: 0;
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.1em;
  line-height: 1.3;
  text-transform: uppercase;
  text-align: center;
  color: #ff1744;
  background: #f4f4f4;
  border: 1px solid #bbb;
  padding: 4px 9px 5px;
  transform: rotate(-2deg);
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.5);
}

.lane__cond b { font-weight: 400; color: #17140f; }

/* --- the arena: two slots marked out on the table --- */

.arena {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 8px;
  align-items: stretch;
  justify-items: center;
  width: 100%;
  padding: 0 4px;
  flex: 1 1 auto;
  min-height: 0;
}

.arena__side {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
  min-height: 0;
  width: 100%;
}

/* A slot is a rectangle marked on the cardboard. It keeps its size whether or not there
   is a card in it, so the board does not jump when one lands — and it is a size query
   container, which is how the card inside knows how big it is allowed to be. */
.arena__slot {
  container-type: size;
  flex: 1 1 auto;
  width: 100%;
  max-width: 180px;
  min-height: 0;
  display: grid;
  place-items: center;
  border: 4px dashed rgba(255, 255, 255, 0.3);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.1);
  box-shadow: inset 0 10px 20px rgba(0, 0, 0, 0.3);
}

/* The card is 13.5em wide and 22.3em tall, so this is the largest em that fits the slot
   in both directions. Measured, not guessed from the viewport — a 667px phone and a
   932px phone both get the biggest card their board can hold. */
.arena__slot .card {
  --em-duel: min(4.48cqh, 7.4cqw);
  max-width: 100%;
}

.arena__side--you .arena__slot {
  border-color: rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.05);
}

.arena__slot .card { max-width: 100%; }

.arena__side--them .arena__slot .card { transform: rotate(-4deg); }
.arena__side--you .arena__slot .card { transform: rotate(3deg); }

.arena__empty {
  font-family: var(--display);
  font-size: 9px;
  font-weight: 900;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

/* Both names are the same small hard-set pill. Yours is the blue one — the only piece
   of the board that is about you rather than about the cards. */
.arena__who {
  font-family: var(--display);
  font-size: 9px;
  font-weight: 900;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  background: rgba(0, 0, 0, 0.8);
  padding: 2px 10px 3px;
  border-radius: 4px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.arena__side--you .arena__who { color: #fff; background: #1d4ed8; }

.arena__mid { display: grid; place-items: center; }

.arena__v {
  font-family: var(--receipt);
  font-size: 28px;
  font-weight: 900;
  line-height: 1;
  text-transform: uppercase;
  color: #fff;
  opacity: 0.3;
}

/* --- the score, stamped on after the fact --- */

/* Losing side: a dull grey label. Winning side: the one piece of neon in the app. */
.arena__score {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--label);
  font-size: 19px;
  line-height: 1.2;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: #4b5563;
  background: #e5e5e5;
  border: 1px solid #9ca3af;
  padding: 3px 11px 4px;
  border-radius: 2px;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);
}

.arena__side--won .arena__score {
  color: #000;
  background: #39ff14;
  border-color: #14532d;
  box-shadow: 0 0 16px rgba(57, 255, 20, 0.4), 0 3px 6px rgba(0, 0, 0, 0.5);
}

.arena__win {
  font-size: 0.58em;
  letter-spacing: 0.2em;
  padding: 2px 4px 3px;
  border-radius: 2px;
  background: #000;
  color: #39ff14;
}

.arena__side--lost { opacity: 0.9; }

/* --- the ring --- */

/* Two cards standing opposite each other for as long as they last. The plates sit
   between them, so the numbers that change are in the middle of the screen where you
   are already looking rather than out at the edges. */
/* Side by side rather than one above the other. Stacked, two cards plus their plates
   had 250px each and came out 118px wide; across, each gets the full height of the ring
   and lands at 180px. On a phone that is the difference between reading a card and
   squinting at one. */
.ring {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  justify-items: center;
  gap: 6px;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  padding: 2px 8px;
}

.fighter {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  min-width: 0;
  min-height: 0;
  width: 100%;
}

/* Only the slot is a size container, so the card measures the space actually left for
   it after the plate rather than the whole column. */
.fighter__slot {
  container-type: size;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: grid;
  place-items: center;
}

.fighter__slot .card {
  --em-duel: min(4.48cqh, 7.4cqw);
  max-width: 100%;
}

.fighter--them .card { transform: rotate(-3deg); }
.fighter--you .card { transform: rotate(2.5deg); }

/* Down to a quarter: the card starts shaking where you can see it. */
.fighter.is-hurt .card { animation: wince 1.1s ease-in-out infinite; }

@keyframes wince {
  0%, 100% { transform: rotate(-3deg); }
  50% { transform: rotate(-3deg) translateX(2px); }
}

.fighter--you.is-hurt .card { animation-name: wince-you; }

@keyframes wince-you {
  0%, 100% { transform: rotate(2.5deg); }
  50% { transform: rotate(2.5deg) translateX(-2px); }
}

.ring__vs { display: grid; place-items: center; z-index: 2; }

.ring__vs .arena__v {
  font-size: 22px;
  opacity: 0.45;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.9);
}

/* The name plate: a Dymo label, a health bar and what it is. */
.fighter__plate {
  flex: 0 0 auto;
  display: grid;
  gap: 3px;
  justify-items: center;
  width: 100%;
  max-width: 210px;
}

.fighter__name {
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #eee;
  background: #131110;
  padding: 2px 9px 3px;
  border-radius: 2px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  box-shadow:
    inset 1px 1px 2px rgba(255, 255, 255, 0.3),
    inset -1px -1px 2px rgba(0, 0, 0, 0.85),
    0 2px 4px rgba(0, 0, 0, 0.5);
}

/* The one number that matters, as a bar you read without reading. */
.health {
  position: relative;
  width: 100%;
  height: 13px;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(226, 244, 250, 0.22);
  border-radius: 2px;
  overflow: hidden;
}

.health__fill {
  display: block;
  height: 100%;
  background: linear-gradient(180deg, #6dfc4a, #39c41a);
  transition: width 0.35s cubic-bezier(0.2, 0.9, 0.3, 1);
}

.health__fill.is-half { background: linear-gradient(180deg, #ffd64a, #e2a012); }
.health__fill.is-low { background: linear-gradient(180deg, #ff7a6e, #d3291b); }

.health__num {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  font-family: var(--receipt);
  font-size: 12px;
  line-height: 1;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
  font-variant-numeric: tabular-nums;
}

.fighter__tags { display: flex; gap: 5px; flex-wrap: wrap; justify-content: center; }

.tag {
  font-family: var(--display);
  font-size: 9px;
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 2px 7px 3px;
  border-radius: 3px;
}

.tag--type { background: rgba(0, 0, 0, 0.66); color: rgba(255, 255, 255, 0.66); }
.tag--status { background: #ff1744; color: #fff; }

/* --- things landing --- */

/* Damage used to be a number that quietly changed on a bar. Now it is thrown at the
   card that took it, in the marker red, and the card flinches. Keeping the design
   language and adding the thud. */
.damage {
  position: absolute;
  left: 50%;
  top: 34%;
  z-index: 12;
  pointer-events: none;
  font-family: var(--label);
  font-size: 34px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: #fff;
  text-shadow:
    0 0 3px #000, 0 3px 0 rgba(0, 0, 0, 0.65), 0 6px 16px rgba(0, 0, 0, 0.8);
  animation: thud 0.85s cubic-bezier(0.16, 0.9, 0.3, 1) both;
}

.damage--super { color: #ff4d3d; font-size: 42px; }
.damage--weak { color: #cfc5ad; font-size: 26px; }

@keyframes thud {
  0% { opacity: 0; transform: translate(-50%, -6px) scale(2.1) rotate(-9deg); }
  16% { opacity: 1; transform: translate(-50%, 0) scale(1) rotate(-4deg); }
  70% { opacity: 1; transform: translate(-50%, -10px) scale(1) rotate(-4deg); }
  100% { opacity: 0; transform: translate(-50%, -34px) scale(0.92) rotate(-4deg); }
}

/* The card takes it. Short, sharp, and it stops — a card that keeps wobbling reads as
   broken rather than hurt. */
.fighter.is-struck .card { animation: struck 0.34s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }

@keyframes struck {
  0%, 100% { transform: translateX(0) rotate(var(--lean, 0deg)); }
  20% { transform: translateX(-7px) rotate(calc(var(--lean, 0deg) - 2deg)); }
  45% { transform: translateX(6px) rotate(calc(var(--lean, 0deg) + 2deg)); }
  75% { transform: translateX(-3px) rotate(var(--lean, 0deg)); }
}

.fighter--them .card { --lean: -3deg; }
.fighter--you .card { --lean: 2.5deg; }

/* The squad dot going out is how a card leaving is shown. A topple animation was tried
   and removed: the fallen card is replaced within the same turn, so it played over the
   replacement and left a healthy card lying face down. */
.squad__dot.is-out { animation: went 0.5s ease-out both; }

@keyframes went {
  0% { transform: scale(1.5); background: #ff4d3d; }
  100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .damage, .fighter.is-struck .card, .squad__dot.is-out { animation: none; }
  .damage { opacity: 1; }
}

/* --- who is left --- */

.squad { display: flex; gap: 5px; align-items: center; }

.squad__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: rgba(226, 244, 250, 0.3);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.6);
}

.squad__dot.is-active {
  background: radial-gradient(circle at 34% 28%, #fff6cf 0%, var(--brass) 46%, var(--brass-deep) 100%);
  box-shadow: 0 0 7px rgba(232, 178, 58, 0.7), inset 0 -1px 2px rgba(0, 0, 0, 0.5);
}

.squad__dot.is-empty {
  background: transparent;
  border: 1.5px dashed rgba(226, 244, 250, 0.45);
  box-shadow: none;
}

.squad__dot.is-out {
  background: rgba(0, 0, 0, 0.5);
  box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.9);
}

/* --- the message box --- */

/* Where the turn actually happens. The board used to resolve everything at once and
   redraw showing the aftermath, so pressing a move felt like submitting a form and
   being told the outcome. Now each beat lands here in turn, at reading speed, and the
   health bar moves on the line that explains why. */
.teleprompt {
  position: relative;
  margin: 0 6px;
  align-self: stretch;
  min-height: 3.6em;
  display: grid;
  align-content: center;
  padding: 10px 30px 11px 14px;
  background: rgba(0, 0, 0, 0.86);
  border: 2px solid rgba(226, 244, 250, 0.16);
  border-radius: 5px;
  cursor: pointer;
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.6);
}

.teleprompt__line {
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(13px, 3.6vw, 15.5px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: #fff;
  text-wrap: balance;
  text-align: center;
}

/* The verdict lines get the colour, because they are the ones worth reacting to. */
.teleprompt.is-super .teleprompt__line { color: #39ff14; }
.teleprompt.is-weak .teleprompt__line { color: #9fb0b8; }
.teleprompt.is-down .teleprompt__line { color: #ff6b60; }
.teleprompt.is-call .teleprompt__line { color: #ffd64a; text-transform: none; font-size: 15px; }

/* The nudge that says there is more to come. */
.teleprompt__more {
  position: absolute;
  right: 10px;
  bottom: 7px;
  font-size: 11px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.75);
  animation: nudge 0.9s ease-in-out infinite;
}

@keyframes nudge {
  0%, 100% { transform: translateY(0); opacity: 0.45; }
  50% { transform: translateY(3px); opacity: 1; }
}

/* Each line arrives rather than switching. */
.teleprompt__line.is-new { animation: line-in 0.16s ease-out both; }

@keyframes line-in {
  from { opacity: 0; transform: translateY(3px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .teleprompt__more, .teleprompt__line.is-new { animation: none; }
}

/* While the turn is playing out, the moves are not yours to press. */
.moves.is-locked { opacity: 0.4; pointer-events: none; }

/* --- the tray: three moves and a swap --- */

.tray {
  flex: 0 0 auto;
  min-height: 0;
  display: grid;
  gap: 7px;
  width: 100%;
  padding: 9px 8px 8px;
  border-top: 1px solid rgba(226, 244, 250, 0.06);
  /* No backdrop-filter. It was blurring what is behind an almost opaque gradient — so
     it cost a full-screen readback every frame and changed roughly nothing on screen.
     This is on the board for the whole battle, which made it the most expensive thing
     in the app. */
  background: linear-gradient(to top,
    rgba(0, 0, 0, 0.97), rgba(0, 0, 0, 0.86) 62%, rgba(0, 0, 0, 0.45));
}

.tray__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 4px;
}

/* Three buttons, always the same three shapes, so the decision is about the numbers on
   them and never about finding them. */
.moves { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }

.move {
  position: relative;
  display: grid;
  gap: 1px;
  justify-items: center;
  align-content: start;
  min-height: 92px;
  padding: 9px 5px 10px;
  cursor: pointer;
  border: 0;
  border-radius: 3px;
  color: #17140f;
  background: #f0ede3;
  border-bottom: 4px solid #8b8578;
  border-right: 4px solid #8b8578;
  text-align: center;
}

/* Swung last turn, and not available again yet. */
/* A move that cannot be used has to look UNUSABLE.
   This was #cfc9bd against cream paper with an 11px note underneath — close enough to
   an available move that people tapped it, got nothing, and moved on thinking the game
   had ignored them. It is the one reported symptom that turned out to be a real bug:
   "I'd select a move and it wouldn't do anything so I'd try another one."
   Now it is visibly switched off: flat, faded, and labelled. */
.move.is-cooling {
  background: #b9b3a8;
  color: #5c5449;
  border-bottom-color: #8a847a;
  border-right-color: #8a847a;
  cursor: default;
  opacity: 0.55;
  filter: saturate(0.35);
}

/* Struck through, so it reads as unavailable at a glance and not merely as a dull one. */
.move.is-cooling .move__name { text-decoration: line-through; text-decoration-thickness: 2px; }

.move.is-cooling .move__kind { background: #6b6254; }
.move.is-cooling .move__hit { color: #8b8578; }

/* The reason, said plainly and big enough to read. It was 11px. */
.move__cooling {
  font-family: var(--label);
  font-size: 12px;
  line-height: 1.1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #4a443a;
}

.move:active {
  transform: translate(2px, 2px);
  border-bottom-width: 2px;
  border-right-width: 2px;
}

.move.is-cooling:active { transform: none; border-bottom-width: 4px; border-right-width: 4px; }

.move__kind {
  font-family: var(--label);
  font-size: 8.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #fff;
  background: #131110;
  padding: 1px 6px 2px;
  border-radius: 2px;
}

.move--heavy .move__kind { background: #8f2116; }
.move--quick .move__kind { background: #1d4ed8; }
.move--trick .move__kind { background: #6b3fa0; }

.move__name {
  font-family: var(--display);
  font-size: 12px;
  font-weight: 900;
  line-height: 1.06;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  text-wrap: balance;
}

.move__foot {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-top: 2px;
}

.move__stat {
  font-family: var(--receipt);
  font-size: 12px;
  line-height: 1;
  color: #5d5445;
}

/* The expected hit against the thing currently opposite, matchup included. A base
   power the player has to multiply themselves is a number nobody multiplies. */
.move__hit {
  font-family: var(--label);
  font-size: 19px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.move__miss,
.move__leaves {
  font-family: var(--receipt);
  font-size: 11px;
  line-height: 1;
  color: #6b6254;
}

.move__leaves { color: #b91c1c; }

/* Whether the matchup is with you or against you, said on the button rather than after
   you have already committed to it. */
.move.is-super { background: #e9f7e4; box-shadow: inset 0 0 0 2px #39c41a; }
.move.is-super .move__hit { color: #1b6b0d; }
.move.is-weak { background: #eee9e4; opacity: 0.86; }
.move.is-weak .move__hit { color: #8b8578; }

/* This one finishes it. */
/* The FINISHES badge used to sit here. It told you a move would win the fight before
   you chose it, which is the decision the turn is made of — the expected damage is
   already on every move, so anyone who wants to compare it to the health bar still can.
   Doing that comparison for them was the part that gave the game away. */
/* --- the wildcard call --- */

/* The battle stops dead and the only way on is the camera. It borrows the bench's
   full-cover treatment on purpose: both are moments where the board is not the thing
   you are looking at any more. */
/* `#tcg-board > .field-call`, not `.field-call`.
   The board sets `#tcg-board > * { position: relative }`, and an ID beats a class — so
   this panel was written as a full-board overlay and has been laying out in the flow
   underneath the move tray ever since, landing "Go and find something" exactly where a
   move button had been a moment earlier. Fifth time this selector has caught something
   in this project, after .card--full, .duel, .manual__note and .standoff. */
#tcg-board > .field-call {
  position: absolute;
  inset: 0;
  z-index: 45;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 12px;
  padding: 26px;
  text-align: center;
  background: rgba(10, 13, 15, 0.95);
  backdrop-filter: blur(5px);
}

/* The other side, off looking. Same overlay, same reason it has to out-specify the
   board's own rule.
   It sits BELOW the hud on purpose: covering the whole board also covered Leave, which
   turned a stuck opponent into a stuck app with no way off the screen. */
#tcg-board > .hud { z-index: 46; }

#tcg-board > .their-hunt {
  position: absolute;
  inset: 0;
  z-index: 44;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 10px;
  padding: 26px;
  text-align: center;
  background: rgba(10, 13, 15, 0.93);
  backdrop-filter: blur(5px);
}

.their-hunt__head {
  margin: 0;
  font-family: var(--display);
  font-size: 24px;
  font-weight: 900;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 0.98;
  color: #f2ede1;
  text-wrap: balance;
}

.their-hunt__sub {
  margin: 0;
  /* 24ch put five words on five lines. */
  max-width: 32ch;
  font-family: var(--receipt);
  font-size: 14px;
  line-height: 1.3;
  color: #c9c1b0;
}

.their-hunt__clock,
.field-call__clock {
  margin: 0;
  font-family: var(--label);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  color: #e8b23a;
}

.field-call__head {
  font-family: var(--display);
  font-size: 26px;
  font-weight: 900;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 0.98;
  color: #f6f2e6;
  text-wrap: balance;
}

.field-call__sub {
  font-family: var(--receipt);
  font-size: 18px;
  line-height: 1.15;
  color: #d9cfb6;
  max-width: 30ch;
}

.field-call__actions { display: grid; gap: 8px; justify-items: center; margin-top: 6px; }

/* --- the bench --- */

/* Swapping costs the turn, so it is a deliberate trip to a separate sheet rather than
   a fourth button sitting next to the moves. */
.bench {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 14px;
  padding: 20px;
  background: rgba(10, 13, 15, 0.93);
  backdrop-filter: blur(5px);
}

.bench__head {
  font-family: var(--display);
  font-size: 17px;
  font-weight: 900;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: #f6f2e6;
}

.bench__row { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

.bench__pick {
  display: grid;
  gap: 5px;
  justify-items: center;
  width: 104px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.bench__pick .card { --em-hand: 7.4px; }
.bench__pick:active { transform: translateY(2px); }

.bench__name {
  font-family: var(--display);
  font-size: 10px;
  font-weight: 900;
  text-transform: uppercase;
  color: #f6f2e6;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bench__none { font-family: var(--receipt); font-size: 16px; color: #cfc5ad; }

/* The back of a card nobody has turned over yet. Same paper stock as the front, but
   blank: a card face down on a table is just an off-white rectangle. */
.card-back {
  width: 100%;
  aspect-ratio: 3 / 4;
  border-radius: 2px;
  background:
    repeating-linear-gradient(45deg,
      rgba(0, 0, 0, 0.05) 0 6px, transparent 6px 12px),
    linear-gradient(168deg, #cfc6ae, #b3a88c);
  background-image: var(--fibre), linear-gradient(168deg, #cfc6ae, #b3a88c);
  background-blend-mode: multiply, normal;
  display: grid;
  place-content: center;
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, 0.2),
    -0.1em 0.2em 0.3em rgba(0, 0, 0, 0.3),
    -0.2em 0.5em 0.9em rgba(0, 0, 0, 0.6);
  clip-path: polygon(1% 0, 99% 0, 100% 99%, 0 100%);
}

.card-back span {
  font-family: var(--display);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(0, 0, 0, 0.28);
}

/* An empty zone is a rectangle someone stencilled onto the cardboard. */
.card-slot--empty {
  width: 100%;
  aspect-ratio: 3 / 4;
  border: 2px dashed rgba(239, 230, 207, 0.28);
  border-radius: 2px;
  display: grid;
  place-content: center;
  padding: 8px;
  text-align: center;
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(239, 230, 207, 0.42);
  background: rgba(0, 0, 0, 0.18);
  box-shadow: inset 0 3px 12px rgba(0, 0, 0, 0.4);
}

.hand__card {
  flex: 0 0 auto;
  width: 84px;
  border: 1px solid rgba(0, 0, 0, 0.7);
  border-radius: 7px;
  overflow: hidden;
  background: linear-gradient(168deg, var(--paper), var(--paper-2));
  color: var(--ink);
  cursor: pointer;
  position: relative;
  padding: 0;
  text-align: left;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 4px 10px -2px rgba(0, 0, 0, 0.65);
  transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1), box-shadow 0.18s ease;
  animation: deal 0.4s cubic-bezier(0.2, 0.9, 0.3, 1) both;
}

/* Dealt left to right, so a new hand arrives rather than appears. */
@keyframes deal {
  from { transform: translateY(16px) rotate(2deg); opacity: 0; }
}

.hand__card:hover:not([disabled]) {
  transform: translateY(-5px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 10px 20px -4px rgba(0, 0, 0, 0.7);
}

.hand__card img { width: 100%; height: 52px; object-fit: cover; display: block; }

.hand__name {
  font-family: var(--display);
  font-size: 10px;
  font-weight: 700;
  line-height: 1.15;
  padding: 4px 5px 2px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  height: 26px;
  overflow: hidden;
}

.hand__stats {
  display: flex;
  justify-content: space-between;
  padding: 0 5px 4px;
  font-family: var(--display);
  font-size: 9px;
  color: #6d6555;
  letter-spacing: 0.04em;
}

.hand__type {
  font-family: var(--display);
  font-size: 8.5px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #fff;
  padding: 2px 5px;
  text-align: center;
}

.hand__card.is-selected {
  outline: 2px solid var(--brass);
  outline-offset: 1px;
  transform: translateY(-9px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 14px 26px -6px rgba(0, 0, 0, 0.75),
    0 0 26px -6px rgba(232, 178, 58, 0.6);
}
.hand__card[disabled] { opacity: 0.4; cursor: default; }

.hand__cost {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--brass);
  font-family: var(--display);
  font-size: 12px;
  font-weight: 800;
  display: grid;
  place-content: center;
}

/* --- result: the five rounds, replayed --- */

/* --- the result: one long till receipt --- */

/* The game hands you a printout. Every number that decided it is on the paper, in the
   order it happened, and nothing else is. */
.receipt-sheet {
  width: 100%;
  max-width: 340px;
  padding: 26px 20px 30px;
  color: #1b1812;
  text-align: left;
  background: #fdfcf7;
  background-image: var(--fibre);
  background-blend-mode: multiply;
  transform: rotate(-0.6deg);
  box-shadow: 0 18px 34px rgba(0, 0, 0, 0.7);
  clip-path: polygon(0% 0%, 3% 1%, 6% 0%, 9% 1%, 12% 0%, 15% 1%, 18% 0%, 21% 1%, 24% 0%, 27% 1%, 30% 0%, 33% 1%, 36% 0%, 39% 1%, 42% 0%, 45% 1%, 48% 0%, 51% 1%, 54% 0%, 57% 1%, 60% 0%, 63% 1%, 66% 0%, 69% 1%, 72% 0%, 75% 1%, 78% 0%, 81% 1%, 84% 0%, 87% 1%, 90% 0%, 93% 1%, 96% 0%, 99% 1%, 100% 1%, 100% 99%, 97% 99%, 94% 99%, 91% 99%, 88% 99%, 85% 99%, 82% 99%, 79% 99%, 76% 99%, 73% 99%, 70% 99%, 67% 99%, 64% 99%, 61% 99%, 58% 99%, 55% 99%, 52% 99%, 49% 99%, 46% 99%, 43% 99%, 40% 99%, 37% 99%, 34% 99%, 31% 99%, 28% 99%, 25% 99%, 22% 99%, 19% 99%, 16% 99%, 13% 99%, 10% 99%, 7% 99%, 4% 99%, 1% 99%, 0 99%);
}

.receipt-sheet__head {
  text-align: center;
  padding-bottom: 14px;
  margin-bottom: 14px;
  border-bottom: 2px dashed #b6b0a2;
}

.receipt-sheet__title {
  font-family: var(--receipt);
  font-size: 34px;
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  text-wrap: balance;
}

.receipt-sheet__sub {
  font-family: var(--receipt);
  font-size: 19px;
  line-height: 1.1;
  opacity: 0.72;
  margin-top: 4px;
}

/* What the duel did, printed under the result like a note added by hand. */
.receipt-sheet__duel {
  font-family: var(--display);
  font-size: 13px;
  font-weight: 900;
  line-height: 1.15;
  text-transform: uppercase;
  text-align: center;
  color: var(--ink-red);
  padding-top: 10px;
  margin-top: 10px;
  border-top: 2px dashed #b6b0a2;
  text-wrap: balance;
}

.receipt-sheet__foot {
  font-family: var(--receipt);
  font-size: 19px;
  line-height: 1.15;
  text-align: center;
  padding-top: 12px;
  margin-top: 12px;
  border-top: 2px dashed #b6b0a2;
}

/* --- the round-by-round, printed as lines on the receipt --- */

.recap { display: grid; gap: 9px; width: 100%; }

.recap__row {
  display: grid;
  gap: 1px;
  padding-bottom: 7px;
  border-bottom: 1px dotted #b6b0a2;
}

.recap__row:last-child { border-bottom: 0; padding-bottom: 0; }

/* A round you lost is struck through the way you would cross it off yourself. */
.recap__row--lost .recap__what { text-decoration: line-through; opacity: 0.6; }

.recap__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-family: var(--receipt);
  font-size: 19px;
  line-height: 1.05;
}

.recap__what { text-transform: uppercase; }

.recap__score { font-variant-numeric: tabular-nums; white-space: nowrap; }
.recap__score b { font-weight: 400; }
.recap__row--won .recap__score b { color: var(--ink-red); }

/* The verdict is the one thing on the receipt that was added afterwards, by hand. */
.recap__flag {
  font-family: var(--display);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #6b6254;
  white-space: nowrap;
}

.recap__row--won .recap__flag {
  color: var(--ink-red);
  border: 2px solid var(--ink-red);
  border-radius: 46% 54% 50% 48%;
  padding: 1px 7px 2px;
  transform: rotate(-3deg);
  display: inline-block;
}

/* The small print. It is small print because it only matters on the rounds it ran. */
.recap__rule {
  font-family: var(--display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.25;
  color: var(--ink-red);
}

.recap__story {
  font-family: var(--receipt);
  font-size: 16px;
  line-height: 1.05;
  color: #4d453a;
}

.lanes--final { width: 100%; }

/* --- the running record, printed at the bottom like a subtotal --- */

.ledger {
  display: grid;
  gap: 0;
  width: 100%;
  text-align: left;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 2px dashed #b6b0a2;
}

.ledger__row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-family: var(--receipt);
  font-size: 18px;
  line-height: 1.2;
}

.ledger__name { text-transform: uppercase; opacity: 0.72; }
.ledger__stat { font-variant-numeric: tabular-nums; }
.ledger__stat b { font-weight: 400; color: var(--ink-red); }

@media (prefers-reduced-motion: reduce) {
  .chip--new,
  .hand__card { animation: none; }
  .card--tier-Epic .card__art::before,
  .card--tier-Legendary .card__art::before,
  .card--tier-Withheld .card__art::before { animation: none; }
  .hand__card, .lane { transition: none; }
}

/* ---------- sheet ---------- */

.sheet {
  position: fixed;
  inset: 0;
  background: rgba(10, 9, 8, 0.72);
  /* Above the tab strip, which sits at 60. It used to be 20, so the bottom 73px of every
     sheet was covered by the tabs — on the sign-in sheet that was the Sign in button
     itself, sitting there unreachable with nothing to scroll. A sheet is modal: while
     one is open the tabs are not where anybody is trying to go. */
  z-index: 70;
  display: grid;
  align-items: end;
}

.sheet__inner {
  background: var(--panel);
  border-top: 1px solid var(--line);
  border-radius: 14px 14px 0 0;
  padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
  max-height: 78vh;
  overflow-y: auto;
  display: grid;
  gap: 14px;
}

.sheet__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

.sheet__head h2 {
  font-family: var(--display);
  font-size: 21px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
  border-radius: 3px;
}

/* First run. Three lines, once, then never again — the game is not complicated,
   it is just unfamiliar. */
.intro {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(10, 9, 8, 0.9);
  display: grid;
  place-items: center;
  padding: 24px;
}

.intro__inner {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 26px 22px;
  max-width: 420px;
  display: grid;
  gap: 18px;
  justify-items: center;
  text-align: center;
}

.intro__title {
  font-family: var(--display);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  line-height: 1.05;
}

.intro__steps { list-style: none; display: grid; gap: 14px; text-align: left; }

.intro__steps li { font-size: 14px; line-height: 1.5; color: var(--muted); }

.intro__steps b {
  display: block;
  font-family: var(--display);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brass);
  margin-bottom: 2px;
}

/* Grain sits over everything and is never in the way. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 100;
  background-image: var(--grain);
  opacity: 0.5;
  mix-blend-mode: overlay;
}

[hidden] { display: none !important; }

@media (min-width: 620px) {
  .view--scan .stage { border-radius: 10px; margin: 16px 16px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .reveal-slot .card, .card__art::after, .spinner { animation: none; }
  .stage.is-wobbling .stage__freeze, .stage.is-escaping .stage__freeze { animation: none; }
  .stage__flash.is-firing { animation: none; }
  /* Keep the blur, drop the movement into it. */
  .stage__freeze { transition: none; }
  .stage.is-reading .stage__freeze { transform: none; }
}

/* ---------- signing in ----------
   Reuses .sheet and .btn so an account looks like part of the game rather than a
   dialog bolted to the front of it. The deck head is a flex row of three now, so the
   count is allowed to shrink before the button does. */

.deck-head { gap: 10px; flex-wrap: wrap; }
.deck-head p { flex: 1 1 auto; min-width: 0; }
.acct-open { flex: 0 0 auto; }

/* Only shown while signed out and holding cards, because that is the only time it is
   true and the only time it is worth interrupting anybody about. */
.acct-warn {
  font-family: var(--label);
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
  border-left: 2px solid var(--brass-deep);
  padding: 2px 0 2px 10px;
  margin-top: 10px;
}

.acct-form { display: grid; gap: 12px; }

.acct-why {
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}

.acct-google { justify-self: stretch; }

/* A rule with the word sat in it. */
.acct-or {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  margin: 2px 0;
}

.acct-or::before,
.acct-or::after {
  content: "";
  height: 1px;
  background: var(--line);
}

.acct-or span {
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.acct-field { display: grid; gap: 5px; }

.acct-field > span {
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.acct-field input {
  font-family: var(--receipt);
  font-size: 18px;
  color: var(--cream);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  /* 44px so it is a comfortable target, and 16px+ so iOS does not zoom the page on
     focus — which on this layout scrolls the shutter off the screen. */
  min-height: 44px;
  padding: 8px 11px;
}

.acct-field input::placeholder { color: #6c6459; }
.acct-field input:focus-visible { outline: 2px solid var(--brass); outline-offset: 1px; }

.acct-msg {
  font-family: var(--label);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-red);
  background: rgba(192, 40, 30, 0.09);
  border-left: 2px solid var(--ink-red);
  padding: 7px 10px;
}

.acct-msg.is-good {
  color: var(--brass);
  background: rgba(232, 178, 58, 0.08);
  border-left-color: var(--brass);
}

.acct-alt { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

.acct-link {
  font-family: var(--label);
  font-size: 12.5px;
  letter-spacing: 0.04em;
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 6px 2px;
  min-height: 44px;
  cursor: pointer;
}

.acct-link:hover { color: var(--cream); border-bottom-color: var(--brass-deep); }

.acct-who {
  font-family: var(--display);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--cream);
  overflow-wrap: anywhere;
}

.acct-state {
  font-family: var(--receipt);
  font-size: 16px;
  color: var(--brass);
}

/* The sheet is a form now, so a phone keyboard must not cover the button being typed
   toward. Shrinking the cap lets the sheet scroll clear of it. */
@media (max-height: 620px) {
  #acct .sheet__inner { max-height: 92vh; }
}

/* --- a live match --- */

/* Somebody is waiting on you, so this outranks whatever you were doing. Fixed rather
   than in the flow because a challenge can land on any screen. */
.summons {
  position: fixed;
  left: 12px;
  right: 12px;
  top: calc(env(safe-area-inset-top, 0px) + 12px);
  z-index: 60;
  display: grid;
  gap: 8px;
  padding: 14px 16px;
  background: var(--paper, #e7e0cd);
  color: #17150f;
  border-radius: 3px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  transform: rotate(-0.4deg);
  animation: summons-in 260ms cubic-bezier(0.2, 1.1, 0.4, 1);
}

@keyframes summons-in {
  from { opacity: 0; transform: translateY(-14px) rotate(-1.4deg); }
  to   { opacity: 1; transform: translateY(0) rotate(-0.4deg); }
}

@media (prefers-reduced-motion: reduce) {
  .summons { animation: none; }
}

.summons__who { margin: 0; font-family: var(--display, inherit); font-size: 18px; }
.summons__clock { margin: 0; font-size: 13px; opacity: 0.72; font-variant-numeric: tabular-nums; }

/* A challenge that is out, on the button that sent it. Reads as a state rather than as
   an action, but stays live to the touch, because it is also the way to call it off. */
.btn.is-waiting {
  background: rgba(232, 178, 58, 0.16);
  color: #e8b23a;
  font-variant-numeric: tabular-nums;
  animation: waiting-pulse 1.6s ease-in-out infinite;
}

@keyframes waiting-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.62; }
}

@media (prefers-reduced-motion: reduce) {
  .btn.is-waiting { animation: none; }
}

/* Who else is standing behind them. Brighter than the clock, because a queue is news. */
.summons__queue {
  margin: 2px 0 0;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #e8b23a;
}
.summons__row { display: flex; gap: 8px; }

/* The turn clock. Only ever on screen in a live match, which is the only time a
   countdown is information rather than pressure for its own sake. */
.clock {
  display: inline-grid;
  place-items: center;
  min-width: 2.4em;
  padding: 2px 8px;
  border-radius: 2px;
  background: rgba(0, 0, 0, 0.4);
  font-variant-numeric: tabular-nums;
  font-size: 15px;
  color: #e7e0cd;
}

/* Under ten seconds it starts to matter. */
.clock.is-late { color: #ff9a7a; background: rgba(120, 30, 10, 0.55); }
.clock.is-late .clock__n { animation: tick 1s steps(2) infinite; }

@keyframes tick { 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) { .clock.is-late .clock__n { animation: none; } }

.waiting {
  margin: 0 0 8px;
  text-align: center;
  font-size: 14px;
  opacity: 0.75;
}

.live-ways { display: grid; gap: 6px; justify-items: start; margin-top: 8px; }
.live-note { margin: 0; font-size: 13px; opacity: 0.75; }
.linky {
  font: inherit; color: inherit; background: none; border: 0; padding: 0;
  text-decoration: underline; cursor: pointer; opacity: 0.8;
}

/* The gamertag, where you can read it out to somebody. */
.my-tag, .manual__tag { margin: 0 0 8px; font-size: 14px; opacity: 0.8; }
.my-tag b, .manual__tag b { font-family: var(--receipt, inherit); letter-spacing: 0.02em; opacity: 1; }
/* Not .manual__note — that is already three photo captions in the field manual, and
   reusing it shrank all of them. */
.field-note { display: block; margin-top: 6px; font-size: 12px; opacity: 0.7; line-height: 1.4; }

/* Asked once, at the moment it earns the asking. */
.nudge {
  position: fixed;
  left: 12px; right: 12px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 86px);
  z-index: 55;
  display: grid; gap: 10px;
  padding: 13px 15px;
  background: #1d1a15;
  border: 1px solid #35301f;
  border-radius: 4px;
  box-shadow: 0 8px 26px rgba(0,0,0,0.5);
}
.nudge__text { margin: 0; font-size: 14px; line-height: 1.35; }
.nudge__row { display: flex; gap: 8px; }

/* --- friends --- */

.friends { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 7px; }
.friend__drop {
  font: inherit; font-size: 12px; color: #c08b7a;
  background: none; border: 0; padding: 6px; cursor: pointer; opacity: 0.75;
}
.friends__none { margin: 10px 0 0; font-size: 13px; opacity: 0.6; }

.acct-tag { margin: 0; font-size: 14px; opacity: 0.8; }
.acct-tag b { font-family: var(--receipt, inherit); opacity: 1; }
.acct-notify {
  display: grid; gap: 8px; justify-items: start;
  padding: 11px 12px;
  background: rgba(0,0,0,0.25);
  border: 1px solid rgba(226,244,250,0.08);
  border-radius: 3px;
}
.acct-notify__state { margin: 0; font-size: 14px; }
.acct-notify__how { margin: 0; font-size: 12.5px; line-height: 1.45; opacity: 0.72; }

/* Off-screen cards cost nothing.
   A deck of forty cards is 2,000 DOM nodes carrying about 880 box-shadows and 520
   clip-paths between them — measured, not guessed — and laying all of that out took
   162ms before a single pixel was painted. content-visibility lets the browser skip
   layout and paint entirely for anything scrolled out of view; contain-intrinsic-size
   keeps the scrollbar honest by reserving the space a card would have taken. */
.deck-grid > *,
.build-grid > * {
  content-visibility: auto;
  contain-intrinsic-size: auto 15em;
}

/* --- the walk-on --- */

/* #tcg-board > * sets position:relative and z-index:1 on every child, and an id beats a
   class however far down the file the class appears — so a plain `.standoff` rule was
   silently overridden and the overlay laid itself out in flow, underneath the fight it
   was supposed to be introducing. Matching the id is what it takes to win. */
#tcg-board > .standoff {
  position: absolute;
  inset: 0;
  z-index: 40;
}

/* Nothing here may ever leave the board.
   `align-content: center` centres overflow, which means content taller than the box
   spills off BOTH ends — the line-up went 377px above the top of the screen and took
   the bill with it. `safe center` centres only while it fits and falls back to the
   start edge when it does not, and the overflow rule catches anything still left. */
.standoff {
  align-content: safe center;
  overflow: hidden;
}

/* One card tall however many are dealt, so it can no longer outgrow the board. */
.standoff__line { max-height: 52%; }

.standoff {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 18px;
  padding: 20px;
  background: rgba(10, 8, 5, 0.93);
  cursor: pointer;
  animation: standoff-in 240ms ease-out;
}

@keyframes standoff-in { from { opacity: 0; } to { opacity: 1; } }

.standoff.is-leaving { animation: standoff-out 240ms ease-in forwards; }
@keyframes standoff-out { to { opacity: 0; } }

/* The bill. Set like a fight poster, because that is what it is. */
.standoff__bill { display: grid; justify-items: center; gap: 3px; text-align: center; }

.standoff__tonight {
  margin: 0;
  font-family: var(--label, inherit);
  font-size: 12px;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: #8d8368;
  opacity: 0;
  animation: bill-drop 420ms 60ms cubic-bezier(0.2, 1.2, 0.4, 1) forwards;
}

.standoff__who {
  margin: 0;
  font-family: var(--display, inherit);
  font-weight: 900;
  font-size: clamp(26px, 8vw, 42px);
  line-height: 1.02;
  color: #f2ead5;
  text-wrap: balance;
  overflow-wrap: anywhere;
  opacity: 0;
  animation: bill-drop 420ms cubic-bezier(0.2, 1.2, 0.4, 1) forwards;
}

.standoff__who--you  { animation-delay: 180ms; color: #d8b463; font-size: clamp(21px, 6.4vw, 32px); }
.standoff__who--them { animation-delay: 420ms; }

.standoff__v {
  margin: 0;
  font-family: var(--receipt, inherit);
  font-size: 15px;
  letter-spacing: 0.2em;
  text-transform: lowercase;
  color: #8d8368;
  opacity: 0;
  animation: bill-drop 300ms 320ms ease-out forwards;
}

@keyframes bill-drop {
  from { opacity: 0; transform: translateY(-12px) rotate(-1.2deg); }
  to   { opacity: 1; transform: none; }
}

/* Whose cards these are. Stated rather than implied: the stack below is the opponent's
   and you have just come from picking your own, so without a caption it reads as yours. */
.standoff__bringing {
  margin: 0;
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #8d8368;
  opacity: 0;
  animation: bill-drop 300ms 520ms ease-out forwards;
}

/* Their team, dealt onto a pile.
   A grid means one card's worth of space whatever the team size, which is the only
   way four readable cards fit on a phone. Each slot occupies the same cell and is
   nudged and turned by its own custom properties. */
.standoff__line {
  display: grid;
  place-items: center;
  --em-duel: min(9.2px, 2.3vw, 1.35vh);
}

.standoff__slot {
  grid-area: 1 / 1;
  transform: translateX(var(--shift, 0)) rotate(var(--tilt, 0deg));
  animation: deal 380ms cubic-bezier(0.15, 1.15, 0.35, 1) both;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.55));
}

/* Lands from above and slightly off, so it reads as being put down rather than
   appearing. The end state has to repeat the slot's own transform or the card snaps
   back to square the moment the animation finishes. */
@keyframes deal {
  from {
    opacity: 0;
    transform: translate(calc(var(--shift, 0px) + 26px), -34px)
               rotate(calc(var(--tilt, 0deg) + 14deg)) scale(1.06);
  }
  to {
    opacity: 1;
    transform: translateX(var(--shift, 0)) rotate(var(--tilt, 0deg));
  }
}

/* Four of them have to read as a line-up, not as one card and a queue.
   A card is 13.5em wide whatever size it is asked for, and renderCard sets that em
   inline as `var(--em-hand, 7.6px)` — so this is the hook the stylesheet is meant to
   use. Setting `font-size` here would lose to the inline declaration. */
.standoff__card { pointer-events: none; }

/* The count. */
.standoff__count {
  margin: 0;
  min-height: 1.1em;
  font-family: var(--display, inherit);
  font-weight: 900;
  font-size: clamp(44px, 16vw, 76px);
  line-height: 1;
  color: #f2ead5;
}

.standoff__count.is-beat { animation: count-beat 560ms ease-out; }
@keyframes count-beat {
  0%   { opacity: 0; transform: scale(1.5); }
  22%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0.55; transform: scale(0.96); }
}

.standoff__count.is-go {
  color: #e2603f;
  animation: count-go 460ms cubic-bezier(0.2, 1.3, 0.4, 1);
}
@keyframes count-go {
  0%   { opacity: 0; transform: scale(0.6) rotate(-4deg); }
  60%  { opacity: 1; transform: scale(1.12) rotate(1.5deg); }
  100% { opacity: 1; transform: scale(1) rotate(-1deg); }
}

/* --- a card arriving in the ring --- */

.fighter.is-entering { animation: ring-in 420ms cubic-bezier(0.2, 1.15, 0.35, 1); }
@keyframes ring-in {
  0%   { transform: translateY(14px) scale(0.88); opacity: 0.3; }
  55%  { transform: translateY(-4px) scale(1.04); opacity: 1; }
  100% { transform: none; }
}

/* Its name, called out as it lands. */
.ringside {
  position: absolute;
  left: 50%;
  top: 46%;
  z-index: 6;
  margin: 0;
  padding: 5px 12px;
  transform: translate(-50%, -50%) rotate(-2deg);
  font-family: var(--display, inherit);
  font-weight: 900;
  font-size: 17px;
  letter-spacing: 0.01em;
  white-space: nowrap;
  color: #17150f;
  background: #e7e0cd;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  animation: ringside 1050ms cubic-bezier(0.2, 1.1, 0.4, 1) forwards;
}

.ringside--loud {
  font-size: 20px;
  color: #f6efdc;
  background: #b4402a;
  animation-duration: 1500ms;
}

@keyframes ringside {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.7) rotate(-6deg); }
  14%  { opacity: 1; transform: translate(-50%, -50%) scale(1.06) rotate(-2deg); }
  24%  { transform: translate(-50%, -50%) scale(1) rotate(-2deg); }
  78%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -62%) scale(0.98) rotate(-2deg); }
}

@media (prefers-reduced-motion: reduce) {
  .standoff, .standoff__who, .standoff__tonight, .standoff__v,
  .standoff__slot, .standoff__count, .fighter.is-entering, .ringside {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
  }
}

/* The sound switch. Small, always there, never in the way of the shutter. */
.sound {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 8px);
  right: 10px;
  z-index: 70;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  padding: 0;
  font-size: 15px;
  line-height: 1;
  color: #cfc5ad;
  background: rgba(12, 9, 5, 0.72);
  border: 1px solid rgba(226, 244, 250, 0.12);
  border-radius: 50%;
  cursor: pointer;
}

/* Muted is the same note with a line through it. It used to be a note beside an ✖,
   which sat in the top-right corner and read as a close button. */
.sound__off { display: none; }
.sound[aria-pressed="false"]::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 22px;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  box-shadow: 0 0 0 1.5px rgba(12, 9, 5, 0.9);
  transform: translate(-50%, -50%) rotate(-45deg);
  pointer-events: none;
}
.sound[aria-pressed="false"] .sound__on { display: none; }
.sound[aria-pressed="false"] .sound__off { display: block; }
.sound[aria-pressed="false"] { color: #7d7566; }

/* Each card is its own paint unit.
   Measured on a 24-card deck: 495 box-shadows, 400 transforms and 327 clip-paths on one
   screen. content-visibility already skips the ones scrolled out of view, but without
   containment a card entering the viewport can invalidate layout and paint for the
   whole grid — which is what scrolling felt like. `contain: content` promises the
   browser that nothing inside a card affects anything outside it, so the work stays
   card-sized. */
.deck-grid > .card,
.build-grid > .card,
.deck-grid > * > .card,
.build-grid > * > .card {
  contain: content;
}


/* A new version, mentioned rather than announced. */
.newver {
  position: fixed;
  left: 12px; right: 12px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 86px);
  z-index: 58;
  display: grid;
  gap: 10px;
  padding: 13px 15px;
  background: #1d1a15;
  border: 1px solid #35301f;
  border-radius: 4px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.5);
}
.newver__head { margin: 0 0 6px; font-weight: 700; font-size: 14px; }
.newver__line { margin: 0; font-size: 12.5px; line-height: 1.4; opacity: 0.75; }
.newver__row { display: flex; gap: 8px; }

/* --- the wordmark, cut out --- */

.wordmark__cut { display: inline-flex; vertical-align: middle; }

/* The letters sit tighter and bigger than a card title, because this is the one place
   the lettering is the thing rather than a label on something else. */
.wordmark__cut .ransom { gap: 0.1em 0.34em; font-size: 1em; }
.wordmark__cut .ransom__l {
  padding: 0.06em 0.13em;
  font-size: 1.02em;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5), 0 1px 0 rgba(0, 0, 0, 0.35);
}

/* Colour.
   A card title is cut from newsprint and stays grey so the photograph is the loudest
   thing on it. The wordmark has no photograph to lose to, so it takes the glossy pages:
   one vivid ground per clipping style, with the ink chosen for contrast rather than for
   variety — a magazine cut-out is legible first and colourful second. */
.wordmark__cut .rl-1 { background: #e8442f; color: #fff4e2; }
.wordmark__cut .rl-2 { background: #f2c14e; color: #1b1508; }
.wordmark__cut .rl-3 { background: #2f7d72; color: #f2fbf6; }
.wordmark__cut .rl-4 { background: #f0efe8; color: #15130e; }
.wordmark__cut .rl-5 { background: #3f63ad; color: #eef3ff; }

/* The grain still sits on top, so they read as printed paper rather than as swatches. */
.wordmark__cut .ransom__l::after { opacity: 0.34; }

/* --- a hunt, running --- */

.onthe {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 9px 14px 10px;
  background: rgba(120, 40, 18, 0.22);
  border: 1px solid rgba(226, 122, 80, 0.28);
  border-radius: 3px;
}

.onthe__clock {
  margin: 0;
  font-family: var(--display, inherit);
  font-weight: 900;
  font-size: 27px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: #f0c9a8;
}

/* Under thirty seconds it stops being information and starts being a nudge. */
.onthe.is-late .onthe__clock { color: #ff9a7a; animation: onthe-tick 1s steps(2) infinite; }
@keyframes onthe-tick { 50% { opacity: 0.5; } }
@media (prefers-reduced-motion: reduce) {
  .onthe.is-late .onthe__clock { animation: none; }
}

.onthe__ask { margin: 0; font-size: 13.5px; opacity: 0.85; text-align: center; }
.onthe__ask b { font-weight: 700; }
.onthe .linky { font-size: 12px; opacity: 0.6; }

/* Won against the clock. A stamp in the corner rather than a border, so it reads as
   something added to the card afterwards — which is exactly what it is. */
.card--hunted .card__frame::after {
  content: "HUNTED";
  position: absolute;
  right: -0.35em;
  top: 32%;
  z-index: 8;
  padding: 0.12em 0.4em;
  font-family: var(--label, inherit);
  font-size: 0.52em;
  letter-spacing: 0.14em;
  color: #f6efdc;
  background: #b4402a;
  transform: rotate(-7deg);
  box-shadow: 0 0.1em 0.3em rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

/* --- things you have to hit --- */

/* 44px is the smallest target most guidance agrees on, and the audit found two under
   it: the sound button at 38 square, and every text input at 29 tall. A 29px input is
   fine with a mouse and a nuisance with a thumb. */
.sound { width: 44px; height: 44px; }

.code-entry input,
.manual__field input,
.acct-field input {
  min-height: 44px;
}

/* The way back into the field manual. A plain link rather than a button: it is
   reference, and it should not compete with the thing that starts a fight. */
.howto {
  display: block;
  margin: 14px 0 0;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.55;
}

/* The small underlined links — change, remove, stop, give up — were text-sized hit
   areas. The padding is negative-margined back out so nothing moves on the page; only
   the area your thumb can land on grows. */
.linky {
  padding: 12px 9px;
  margin: -12px -9px;
}

/* --- moving between screens --- */

/* Tabs used to swap instantly, which on a phone reads as the app blinking rather than
   as you moving. A short fade-and-lift is enough to say one screen replaced another.
   Only the incoming screen animates: cross-fading both means two screens on top of each
   other, and this layout has fixed surfaces (the camera, the board) that cannot overlap
   without tearing. */
.view.is-active {
  animation: view-in 180ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes view-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* The board is excluded, but only the board. Disabling the whole Play tab also robbed
   the builder and the result receipt of the transition; a fight is already
   mid-animation when you arrive at it and a second transform on the container fights
   the walk-on for the same pixels, but neither of the other two screens has that
   problem. So the play screens animate themselves and the view does not. */
.view--play.is-active { animation: none; }

#tcg-setup:not([hidden]),
#tcg-over:not([hidden]) {
  animation: view-in 180ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  .view.is-active,
  #tcg-setup:not([hidden]),
  #tcg-over:not([hidden]) { animation: none; }
}

/* A card you can press.
   They were static targets: you tapped and the next screen arrived, with nothing in
   between to say the tap landed. */
.deck-grid .card,
.build-grid .card {
  transition: transform 120ms ease, filter 120ms ease;
}

.deck-grid .card:active,
.build-grid .card:active {
  transform: scale(0.97);
  filter: brightness(1.08);
}

@media (prefers-reduced-motion: reduce) {
  .deck-grid .card, .build-grid .card { transition: none; }
  .deck-grid .card:active, .build-grid .card:active { transform: none; }
}

/* ==========================================================================
   The Play tab
   --------------------------------------------------------------------------
   It was a settings menu: grey rows in rounded boxes with uppercase labels and
   a disclosure triangle. Clear, and joyless — you were configuring a match
   rather than picking a fight.
   It is a noticeboard now. Everything on it is an object somebody stuck there,
   using the materials the rest of the game is already made of: Dymo tape,
   masking tape, brass pins, paper, and the red marker.
   ========================================================================== */

/* The Play tab is eight sections stacked in a column, and it was one flat gap between
   all of them — so a challenge waiting on you, the button that starts a fight, and a
   link to the field manual all sat at the same level of importance. The rhythm now says
   which is which: a wide break between groups, a tight one inside them. */
.step { display: grid; gap: 26px; width: 100%; justify-items: stretch; }

/* A heading belongs to the thing under it, not to the gap above it. */
.step > .board-lot { gap: 11px; }
.step > .board-lot > .lot-tape { margin-bottom: -2px; }

/* The one thing on this screen you are here to do. */
#picks { gap: 12px; }

/* Nothing between the question and the answers. */
.step > .lot-tape + .picks { margin-top: -16px; }

/* Your own name is a caption, not a section. */
.step > .dymo { margin-bottom: -14px; }

/* Your name, punched into a strip of Dymo tape. */
.dymo {
  justify-self: center;
  margin: 0;
  padding: 7px 14px 6px;
  font-family: var(--label);
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #ece6d6;
  background: linear-gradient(180deg, #2f2a25, #211d19);
  border-radius: 3px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    inset 0 -1px 0 rgba(0, 0, 0, 0.5),
    0 3px 8px rgba(0, 0, 0, 0.45);
  transform: rotate(-0.8deg);
}

.dymo b { color: var(--brass-pale); font-weight: 400; }

/* Section headings are torn masking tape, the same as everywhere else in the app. */
.lot-tape {
  justify-self: start;
  margin: 0;
  padding: 7px 15px 8px;
  font-family: var(--display);
  font-weight: 800;
  font-size: 15px;
  color: var(--ink);
  transform: rotate(-1.1deg);
}

.board-lot { display: grid; gap: 10px; justify-items: stretch; }

/* --- an opponent, pinned to the board --- */

.picks { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.picks > li { display: grid; gap: 6px; }

.rival {
  position: relative;
  width: 100%;
  display: grid;
  gap: 3px;
  padding: 16px 18px 15px 64px;
  text-align: left;
  color: var(--ink);
  background: var(--paper);
  background-image: var(--grain);
  background-blend-mode: multiply;
  border: 0;
  border-radius: 2px;
  cursor: pointer;
  /* Pinned by hand, so never quite square. */
  transform: rotate(-0.7deg);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5), 0 1px 0 rgba(255, 255, 255, 0.25) inset;
  transition: transform 120ms ease, box-shadow 120ms ease;
}

.picks > li:nth-child(even) .rival { transform: rotate(0.9deg); }

.rival:active {
  transform: rotate(0deg) translateY(2px) scale(0.995);
  box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
}

.rival__name {
  font-family: var(--display);
  font-weight: 900;
  font-size: 20px;
  line-height: 1.05;
  letter-spacing: -0.01em;
}

.rival__sub {
  font-family: var(--receipt);
  font-size: 15px;
  line-height: 1.15;
  color: #5d5445;
}

/* The pin holding it on. */
.rival__pin {
  position: absolute;
  left: 26px;
  top: 50%;
  width: 15px;
  height: 15px;
  margin-top: -7px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 28%, #ffe9a8 0 16%, var(--brass) 46%, var(--brass-deep) 100%);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.55), inset 0 -1px 2px rgba(0, 0, 0, 0.3);
}

.rival__pin--red {
  background: radial-gradient(circle at 34% 28%, #ff9a8a 0 16%, #d1483a 46%, #7e241b 100%);
}

/* The bot's is a deck, face down, fanned. */
.rival--bot { padding-left: 82px; }
.rival--bot .rival__pin { left: 14px; top: 15px; margin-top: 0; }

.rival__deck {
  position: absolute;
  left: 26px;
  top: 50%;
  width: 34px;
  height: 46px;
  margin-top: -23px;
}

.rival__deck i {
  position: absolute;
  inset: 0;
  border-radius: 2px;
  background:
    repeating-linear-gradient(45deg, #2a2620 0 3px, #221f1a 3px 6px);
  border: 1px solid #4a4238;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}

.rival__deck i:nth-child(1) { transform: rotate(-9deg) translate(-3px, 1px); }
.rival__deck i:nth-child(2) { transform: rotate(-3deg); }
.rival__deck i:nth-child(3) { transform: rotate(4deg) translate(3px, -1px); }

/* --- friends, pinned the same way --- */

.friends { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }

/* An enemy row is a .rival--foe bill inside the li, same as the bot and the queue.
   Two older rulesets for .friend survived here — one dark row, one paper card with its
   own blue pin — from back when the li WAS the row and carried a .friend__tag. Nothing
   has emitted .friend__tag for a long time, but the paper one still won the cascade, so
   every enemy rendered as a card inside a card with two pins on it. The live rule is the
   two-column grid up by .friends; these were scenery. */

.friend__drop {
  font-family: var(--receipt);
  font-size: 14px;
  color: var(--ink-red);
  background: none;
  border: 0;
  cursor: pointer;
  opacity: 0.8;
  /* A 44px tap target that does not cost the layout 44px: padding out, margin back in.
     The negative margin used to be on both sides, which meant the painted button was
     8px wider than the grid track it sat in and hung that far off the right-hand edge of
     the screen. It bleeds to the LEFT only now, into the gap, where there is room. */
  padding: 12px 8px;
  margin: -12px 0 -12px -8px;
}

.friends__none {
  margin: 0;
  font-family: var(--receipt);
  font-size: 15px;
  color: var(--muted);
}

/* The rarest way in, folded away — but written like a person rather than a form. */
.oneoff > summary,
.cheats > summary {
  cursor: pointer;
  padding: 10px 0;
  font-family: var(--receipt);
  font-size: 15px;
  color: var(--muted);
  list-style: none;
}

.oneoff > summary::before,
.cheats > summary::before { content: "+ "; color: var(--brass); }
.oneoff[open] > summary::before,
.cheats[open] > summary::before { content: "− "; }
.oneoff > summary::-webkit-details-marker,
.cheats > summary::-webkit-details-marker { display: none; }

/* --- step two: who you are against --- */

/* A strip of tape with a name on it, not a fight poster. The poster is the walk-on's
   job and it earns the room; doing it here as well pushed the cards off the bottom of
   the screen, and the cards are the entire point of this step. */
.facing {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 9px;
  flex-wrap: wrap;
  padding: 8px 14px 9px;
  background: var(--paper-2);
  background-image: var(--grain);
  background-blend-mode: multiply;
  color: var(--ink);
  border-radius: 2px;
  transform: rotate(-0.6deg);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
}

.facing__v {
  font-family: var(--receipt);
  font-size: 15px;
  color: #6d6252;
}

.facing__who {
  font-family: var(--display);
  font-weight: 900;
  font-size: 19px;
  line-height: 1.05;
  overflow-wrap: anywhere;
}

.facing__change { font-family: var(--receipt); font-size: 14px; color: var(--ink-red); }

/* The counter, stamped on a brass chit. */
.build-bar { display: grid; gap: 7px; justify-items: center; }

.chit {
  padding: 5px 15px 4px;
  font-family: var(--display);
  font-weight: 900;
  font-size: 17px;
  color: #2a1f08;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 55%, var(--brass-deep));
  border-radius: 2px;
  box-shadow: 0 3px 7px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.5);
  transform: rotate(-1.4deg);
}

/* A FIGHT ME, stencilled on a strip of duct tape.
   It is the loudest thing on the board on purpose: the whole game only works if people
   actually invite each other, and a polite grey "Share" button has never made anybody
   do anything. */
.fightme {
  position: relative;
  width: 100%;
  display: grid;
  gap: 1px;
  padding: 15px 18px 16px;
  text-align: center;
  color: #f7efdc;
  background-color: #8f2f22;
  background-image:
    linear-gradient(104deg, transparent 20%, rgba(255, 255, 255, 0.14) 40%, transparent 60%),
    repeating-linear-gradient(0deg, transparent 0 2px, rgba(0, 0, 0, 0.13) 2px 4px),
    var(--grain);
  background-blend-mode: normal, normal, multiply;
  border: 0;
  cursor: pointer;
  transform: rotate(-1.3deg);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.2);
  /* Torn at both ends, like it came off a roll. */
  clip-path: polygon(0% 8%, 3% 0%, 7% 6%, 12% 1%, 100% 4%, 97% 93%, 93% 100%, 88% 94%, 4% 97%, 0% 100%);
}

.fightme:active { transform: rotate(-0.4deg) translateY(2px); }

.fightme__shout {
  font-family: var(--display);
  font-weight: 900;
  font-size: 25px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  line-height: 1;
}

.fightme__sub {
  font-family: var(--receipt);
  font-size: 14px;
  opacity: 0.82;
}

/* Somebody arrived on a FIGHT ME link. */
.summoned {
  display: grid;
  gap: 3px;
  justify-items: center;
  padding: 13px 16px 14px;
  background: var(--paper);
  background-image: var(--grain);
  background-blend-mode: multiply;
  color: var(--ink);
  border-radius: 2px;
  transform: rotate(0.7deg);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
}

.summoned__who { margin: 0; font-family: var(--display); font-weight: 900; font-size: 20px; }
.summoned__sub { margin: 0; font-family: var(--receipt); font-size: 15px; color: #5d5445; }

/* The grid has to clear the dock.
   The dock is sticky and solid, so without this the last row of cards sits underneath
   it — on a three-card team that is often a card you were about to pick. */
#pick-three .deck-grid { padding-bottom: 76px; }

/* A slot nobody filled. Stated, not scored — it never fought, so it has no HP and no
   verdict, and printing one made it look like a card that survived without existing. */
.recap__row--empty { opacity: 0.55; }
.recap__row--empty .recap__flag { color: #6d6252; }

/* The receipt is taller than a phone, so its buttons were below the fold — you finished
   a game and could not see how to start another without scrolling for it. */
.over-dock {
  position: sticky;
  bottom: 0;
  z-index: 20;
  justify-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Three across a 375px phone now that there is a way out as well as a way on. */
  gap: 8px;
  margin: 8px -12px -24px;
  padding: 12px max(10px, env(safe-area-inset-left, 0px)) 14px;
  background: rgba(12, 9, 5, 0.98);
  border-top: 1px solid rgba(255, 246, 214, 0.08);
}

/* --- sorting and filtering the deck --- */

.sift {
  display: grid;
  gap: 9px;
  margin: 2px 0 4px;
}

.sift__types {
  display: flex;
  gap: 7px;
  overflow-x: auto;
  /* The chips can outrun a phone once somebody holds all six types, so the row scrolls
     rather than wrapping into a block that pushes the cards down. */
  scrollbar-width: none;
  padding-bottom: 2px;
  -webkit-overflow-scrolling: touch;
}

.sift__types::-webkit-scrollbar { display: none; }

/* A fade on the right edge, so a row that runs off the screen looks like one. Seven
   chips do not fit a phone and the scrollbar is hidden, which left two types reachable
   only by somebody who guessed the row could be swiped. */
.sift__types { -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent); mask-image: linear-gradient(90deg, #000 88%, transparent); }

/* A paper chit, same as everything else you can pick up in this game. */
.sift__chip {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 8px 11px;
  min-height: 40px;
  font-family: var(--label);
  font-size: 13px;
  color: #3a342a;
  background: var(--paper-2);
  background-image: var(--grain);
  background-blend-mode: multiply;
  border: 0;
  border-radius: 2px;
  cursor: pointer;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
  transform: rotate(-0.5deg);
}

.sift__types > .sift__chip:nth-child(even) { transform: rotate(0.7deg); }

.sift__chip-name { font-size: 13px; }

.sift__chip-n {
  font-family: var(--label);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  opacity: 0.6;
}

/* None of this type. Kept on the row rather than hidden, because the zero is the
   interesting number — it is what sends somebody out with the camera. */
.sift__chip.is-none {
  opacity: 0.38;
  box-shadow: none;
  background: none;
  color: var(--cream, #f2ede1);
  border: 1px dashed rgba(242, 237, 225, 0.35);
  cursor: default;
}

.sift__chip.is-none .sift__chip-n { color: #c9795f; opacity: 1; }

.sift__chip.is-on {
  color: #2a1f08;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 60%, var(--brass-deep));
  box-shadow: 0 3px 7px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

.sift__sort { display: flex; align-items: center; gap: 9px; }

.sift__sortlabel {
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.sift__select {
  flex: 1;
  min-height: 40px;
  padding: 6px 10px;
  font-family: var(--receipt);
  font-size: 16px;
  color: var(--cream);
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(226, 244, 250, 0.12);
  border-radius: 3px;
}

/* --- a move being called --- */

/* The point of the game is that a watering can turns out to have a move called FUR THE
   ELEMENT. That was being delivered as the shortest beat on the board, in body text, in
   a line that also contained the card's name and a full stop. It gets its own moment. */
/* Matching the id, because `#tcg-board > *` sets position:relative and z-index:1 on
   every child and an id beats a class wherever the class appears in the file. Without
   this the call-out lays itself out in flow at the bottom of the board, behind the move
   tray — which is exactly what the standoff did for the same reason. */
#tcg-board > .called,
.called {
  position: absolute;
  left: 0;
  right: 0;
  top: 38%;
  z-index: 30;
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 0 14px;
  pointer-events: none;
}

.called__by {
  margin: 0;
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #cdbf9e;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.9);
  opacity: 0;
  animation: called-by 260ms ease-out forwards;
}

.called__move {
  margin: 0;
  padding: 6px 16px 7px;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(23px, 8.5vw, 40px);
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  text-align: center;
  text-wrap: balance;
  color: #17150f;
  background: var(--paper);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.65);
  transform: rotate(-2deg);
  /* Torn off a sheet, like everything else here. */
  clip-path: polygon(0% 7%, 3% 0%, 8% 6%, 14% 1%, 100% 4%, 97% 94%, 92% 100%, 86% 95%, 4% 98%, 0% 100%);
  animation: called-move 300ms cubic-bezier(0.15, 1.25, 0.35, 1) forwards;
}

/* A heavy move lands red, so the three kinds are told apart at a glance. */
.called--heavy .called__move { color: #f7efdc; background: #8f2f22; }
.called--trick .called__move { color: #f2fbf6; background: #2f6b5f; }

@keyframes called-by {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 0.9; transform: none; }
}

@keyframes called-move {
  0%   { opacity: 0; transform: scale(0.72) rotate(-8deg); }
  62%  { opacity: 1; transform: scale(1.06) rotate(-1deg); }
  100% { opacity: 1; transform: scale(1) rotate(-2deg); }
}

.called.is-going .called__move { animation: called-out 220ms ease-in forwards; }
.called.is-going .called__by { animation: called-out 220ms ease-in forwards; }

@keyframes called-out { to { opacity: 0; transform: translateY(-8px) rotate(-2deg); } }

@media (prefers-reduced-motion: reduce) {
  .called__by, .called__move, .called.is-going .called__move, .called.is-going .called__by {
    animation-duration: 1ms;
  }
}

/* --- swapping, as an actual option --- */

/* It was underlined text at 63x45, beside a row of dots, next to three full-size move
   cards — so it read as a footnote and nobody found it. Bringing somebody in is one of
   the two things a turn can be spent on and it should carry that weight. */
.swapper {
  display: grid;
  gap: 0;
  padding: 9px 14px 10px;
  text-align: center;
  color: var(--ink);
  background: var(--paper-2);
  background-image: var(--grain);
  background-blend-mode: multiply;
  border: 0;
  border-radius: 2px;
  min-height: 48px;
  cursor: pointer;
  transform: rotate(-0.8deg);
  box-shadow: 0 4px 11px rgba(0, 0, 0, 0.5);
}

.swapper:active { transform: rotate(0deg) translateY(2px); }

.swapper__word {
  font-family: var(--display);
  font-weight: 900;
  font-size: 16px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  line-height: 1.05;
}

.swapper__sub { font-family: var(--receipt); font-size: 13px; color: #6d6252; line-height: 1.1; }

.swapper[disabled] { opacity: 0.42; cursor: default; box-shadow: none; }
.swapper[disabled]:active { transform: rotate(-0.8deg); }

/* --- leaving --- */

/* Deliberately small and off to one side. It is a real option, but it is not one
   anybody should hit while reaching for a move. */
.bail {
  margin-left: auto;
  padding: 7px 11px;
  min-height: 36px;
  font-family: var(--receipt);
  font-size: 13px;
  color: #b08a7e;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(226, 244, 250, 0.1);
  border-radius: 3px;
  cursor: pointer;
}

.bail:active { transform: translateY(1px); }

/* --- notifications in the manual --- */

.manual__notify { display: grid; gap: 7px; justify-items: start; margin: 12px 0 4px; }
.manual__notify-state {
  margin: 0;
  font-family: var(--receipt);
  font-size: 15px;
  color: #5d5445;
}

/* --- playing with the sound off, so to speak --- */

/* Not a dismissible nudge. Somebody playing with notifications off cannot really be
   challenged at all, and the Play tab is where that costs them something — so it sits
   there until it is on, or until the browser has refused it and there is nothing left
   to ask. */
.deaf {
  width: 100%;
  display: grid;
  gap: 2px;
  padding: 12px 15px 13px;
  text-align: left;
  color: #f6e9d6;
  background: #7a3418;
  background-image: var(--grain);
  background-blend-mode: multiply;
  border: 0;
  border-radius: 2px;
  cursor: pointer;
  transform: rotate(-0.5deg);
  box-shadow: 0 5px 14px rgba(0, 0, 0, 0.5);
}

.deaf:active { transform: rotate(0deg) translateY(1px); }

.deaf__head {
  font-family: var(--display);
  font-weight: 900;
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 0.01em;
}

.deaf__sub { font-family: var(--receipt); font-size: 14px; line-height: 1.2; opacity: 0.88; }

/* Nothing to tap when the browser has already refused: it becomes a statement. */
.deaf.is-blocked { background: #4a3a30; cursor: default; }
.deaf.is-blocked:active { transform: rotate(-0.5deg); }

/* --- swaps --- */

/* The list on the Play tab. Same shape as a duel row, because it is the same fact:
   somebody is waiting on an answer. */
.swaps { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 8px; }

.swap-row {
  display: grid;
  gap: 3px;
  width: 100%;
  text-align: left;
  padding: 11px 13px;
  background: rgba(255, 246, 214, 0.05);
  border: 0;
  border-left: 3px solid var(--brass, #e8b23a);
  color: inherit;
  cursor: pointer;
  font: inherit;
}

.swap-row__who {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 900;
  color: var(--cream, #f4efe3);
}

.swap-row__what {
  font-family: var(--receipt);
  font-size: 12px;
  line-height: 1.3;
  color: var(--cream-2, #e7dfcd);
}

.swap-row__at {
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted, #8d8573);
}

/* The two cards, side by side. This IS the decision, so they get equal width and sit
   on one line at every size — stacking them turns a comparison into a scroll. */
.swap__pair {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
}

.swap__half { display: grid; gap: 6px; justify-items: center; min-width: 0; }

.swap__label {
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted, #8d8573);
}

.swap__slot { display: grid; place-items: center; min-height: 150px; width: 100%; }

.swap__arrows { font-size: 20px; color: var(--brass, #e8b23a); }

.swap__empty {
  font-family: var(--receipt);
  font-size: 12px;
  text-align: center;
  color: var(--muted, #8d8573);
  border: 1px dashed rgba(232, 178, 58, 0.35);
  padding: 22px 10px;
  margin: 0;
  width: 100%;
}

.swap__line, .swap__note {
  font-family: var(--receipt);
  font-size: 12.5px;
  line-height: 1.4;
  margin: 0;
  color: var(--cream-2, #e7dfcd);
}

.swap__note { color: var(--muted, #8d8573); }

/* The deck picker, holding people instead of cards for one job. */
.deck-grid--people { display: grid; grid-template-columns: 1fr; gap: 8px; }

/* --- the fight card --- */

/* The main event is people; the undercard is whatever is available when nobody is. That
   ordering is the redesign: the bot sat first on this screen for months, so the default
   read of a game about your mates was "play the computer". */
.lot-tape--main {
  background: var(--ink-red);
  color: var(--cream);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
}

.board-lot--main { margin-top: 4px; }

/* Everything below the fights. Not hidden — a record you cannot find is a record nobody
   keeps — but visibly the back of the programme. */
.board-lot--quiet { opacity: 0.82; }

/* A bout carries its own score. It used to be a sentence only ("You are 4-2 up"), which
   reads fine and scans like nothing: on a screen of five bills the eye wants a number in
   the same place every time. */
.rival__score {
  position: absolute;
  right: 13px;
  top: 11px;
  display: flex;
  align-items: baseline;
  gap: 2px;
  font-family: var(--display);
  font-size: 17px;
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.rival__w { color: #2f7a43; }
.rival__l { color: var(--ink-red); }
.rival__dash { color: rgba(23, 21, 15, 0.4); font-size: 13px; }

/* The run, stamped on at an angle the way anything triumphant in this game is. */
.rival__run {
  position: absolute;
  right: 10px;
  bottom: 9px;
  padding: 2px 7px 3px;
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transform: rotate(-2.5deg);
}

.rival__run--hot { background: #bfe0c4; color: #1d4427; }
.rival__run--cold { background: #e6c3ba; color: #6b241c; }

/* The bills need room on the right for the score and the stamp.
   Written as `.friend .rival--foe` on purpose: `.friend .rival` further up sets
   padding-right: 14px and is two classes deep, so a single-class rule here lost and the
   score sat on top of the name.
   `min-width: 0` is the other half. The row is a `1fr auto` grid, and a grid item with
   `width: 100%` will not shrink below its min-content — so the remove button was pushed
   eight pixels off the right-hand edge of the screen. */
.friend .rival--foe { padding-right: 76px; min-width: 0; }

/* The remove button must not be squeezed. The auto track was shrinking it to 34px — less
   than the width of the word — so "remove" spilled out of its own cell and over the edge
   of the screen. `min-width: 0` here made it worse; nowrap is what holds the column open,
   and the bill beside it gives way because that track is minmax(0, 1fr). */
.friend__drop { white-space: nowrap; }

/* The undercard is the same bill, smaller and paler. A warm-up should not shout as loud
   as somebody who is going to take the mickey out of you afterwards. */
.picks .rival { padding-top: 12px; padding-bottom: 12px; }
.picks .rival__name { font-size: 20px; }
.picks .rival__sub { font-size: 12.5px; }

/* The pass, as a stub rather than a card. It is a reason to go out, not an opponent, and
   it was competing with the fights for the same weight of attention. */
.job { border-left-width: 3px; }
.job__title { font-size: 17px; }

/* FIGHT ME lives below the record now, which makes full-strength red a second thing
   competing with the main event's tape. Same object, quieter ink. */
.fightme { opacity: 0.9; transform: scale(0.97); }
.fightme__shout { font-size: 25px; }

/* The remove control is a gutter mark, not a word competing with the bill beside it.
   At 390px "remove" took a quarter of the row for something nobody presses twice. */
.friend__drop {
  font-family: var(--display);
  font-size: 19px;
  line-height: 1;
  opacity: 0.45;
}

.friend__drop:active { opacity: 0.9; }

/* The record, folded. */
#record > summary { cursor: pointer; list-style: none; display: inline-block; }
#record > summary::-webkit-details-marker { display: none; }
#record[open] > summary { margin-bottom: 4px; }

/* The add field is scenery, not a fight. It was the heaviest slab on the screen. */
#friend-add { opacity: 0.72; transform: scale(0.97); transform-origin: left center; }
#friend-add:focus-within { opacity: 1; transform: none; }

/* ============================================================================
   THE ROSTER — character select
   ============================================================================
   The play screen used to be a column of cream paper bills, which is what every
   other surface in this app is made of. On a screen whose whole job is "pick
   somebody and go", that read as a list of paragraphs: nothing was a face, nothing
   was pickable at a glance, and the only way to tell two opponents apart was to
   read them.
   These are plates on a wall. Dark, ruled like a height chart, name cut out of a
   magazine, record stamped in the corner. The paper bills still exist everywhere
   else — this is the one screen that is a board rather than a noticeboard. */

.roster {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 9px;
}

.roster-wrap { display: grid; gap: 10px; }

/* Two grids, one board: the people and the rest sit flush, so the only thing
   separating them is the heading. */
#picks { margin-top: 9px; }

.seat-slot { position: relative; display: grid; }

.seat {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 3.7;
  display: grid;
  align-content: end;
  gap: 3px;
  padding: 10px 10px 11px;
  text-align: left;
  color: var(--cream);
  border: 1px solid rgba(232, 178, 58, 0.22);
  border-radius: 2px;
  cursor: pointer;
  overflow: hidden;
  /* A height chart behind glass. The rules are what make it a plate rather than a
     dark rectangle, and they are what the name sits against. */
  background-color: #131110;
  background-image:
    repeating-linear-gradient(0deg, transparent 0 17px, rgba(232, 178, 58, 0.07) 17px 18px),
    radial-gradient(90% 60% at 50% 0%, rgba(232, 178, 58, 0.13), transparent 70%),
    var(--grain);
  background-blend-mode: normal, normal, multiply;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.6), 0 6px 16px rgba(0, 0, 0, 0.5);
  transition: transform 0.1s ease-out, border-color 0.1s ease-out;
}

.seat:active { transform: translateY(2px) scale(0.985); border-color: rgba(232, 178, 58, 0.6); }

/* What they are, in one dot. Red is a person. */
.seat__pip {
  position: absolute;
  left: 10px;
  top: 10px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 28%, #ffe9a8 0 18%, #e8b23a 48%, #7a5a12 100%);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

.seat__pip--red {
  background: radial-gradient(circle at 34% 28%, #ffc9c0 0 18%, #c0281e 48%, #5c120c 100%);
}

/* The name, cut from a magazine. Sized down from the card title so two fit across. */
.seat__name {
  --em-ransom: 15px;
  font-size: 15px;
  line-height: 1;
  overflow-wrap: anywhere;
}

.seat__name--plain {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 900;
  letter-spacing: -0.01em;
  color: var(--cream);
}

.seat__line {
  font-family: var(--receipt);
  font-size: 11px;
  line-height: 1.25;
  color: var(--muted);
}

/* The score, stamped in the corner the way a case number would be. */
.seat__score {
  position: absolute;
  right: 9px;
  top: 8px;
  display: flex;
  align-items: baseline;
  gap: 1px;
  font-family: var(--display);
  font-size: 16px;
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.seat__w { color: #7fc08d; }
.seat__l { color: #d08878; }
.seat__dash { color: rgba(244, 239, 227, 0.35); font-size: 12px; }

.seat__run {
  position: absolute;
  right: 8px;
  top: 28px;
  padding: 2px 6px 3px;
  font-family: var(--label);
  font-size: 8.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transform: rotate(-3deg);
}

.seat__run--hot { background: #bfe0c4; color: #1d4427; }
.seat__run--cold { background: #e6c3ba; color: #6b241c; }

/* The unknown challenger. */
.seat__mark {
  position: absolute;
  right: 12px;
  top: 30%;
  font-family: var(--display);
  font-size: 58px;
  font-weight: 900;
  line-height: 1;
  color: rgba(232, 178, 58, 0.16);
}

/* The house deck, face down, as the bot's portrait. */
.seat__deck {
  position: absolute;
  right: 14px;
  top: 26%;
  width: 42px;
  height: 56px;
}

.seat__deck i {
  position: absolute;
  inset: 0;
  border-radius: 2px;
  background:
    repeating-linear-gradient(45deg, #2a2723 0 4px, #211e1b 4px 8px);
  border: 1px solid rgba(232, 178, 58, 0.2);
}

.seat__deck i:nth-child(1) { transform: rotate(-9deg) translate(-5px, 2px); }
.seat__deck i:nth-child(2) { transform: rotate(-3deg); }
.seat__deck i:nth-child(3) { transform: rotate(5deg) translate(5px, -2px); }

/* Taking somebody off the board. A mark in the corner, over the plate. */
.seat__drop {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 9px 11px;
  font-family: var(--display);
  font-size: 17px;
  line-height: 1;
  color: var(--cream);
  background: none;
  border: 0;
  opacity: 0.3;
  cursor: pointer;
}

.seat__drop:active { opacity: 0.95; }

/* The queue note belongs under its own tile, not across the board. */
.picks__live .live-note { grid-column: 1 / -1; }

/* The monogram. Big, ghosted, behind the ruling — a plate with nothing on it is just a
   dark rectangle, and two dark rectangles side by side are indistinguishable at the
   distance somebody actually holds a phone. */
.seat__ghost {
  position: absolute;
  right: 4px;
  bottom: 22px;
  font-family: var(--display);
  font-size: 96px;
  font-weight: 900;
  line-height: 0.78;
  color: rgba(232, 178, 58, 0.13);
  pointer-events: none;
}

/* Shorter plates. At 3/3.7 a pair filled the screen on their own and the board needed
   scrolling to see four opponents, which defeats the point of a board. */
.seat { aspect-ratio: 3 / 3.05; }

.seat__name { --em-ransom: 16px; font-size: 16px; }

/* The board is one thing. The add field goes under all of it. */
.adder { margin-top: 12px; display: grid; gap: 8px; }
.adder .code-entry { opacity: 0.75; }
.adder .code-entry:focus-within { opacity: 1; }

/* A folded record should not draw a panel around nothing. */
#record { background: none; border: 0; padding-left: 0; }
#record[open] { padding-bottom: 6px; }

/* Anything that is not a plate spans the board rather than taking one column of it. */
.friends__none, .roster .live-note { grid-column: 1 / -1; }

/* ============================================================================
   THE TABLE — the play screen
   ============================================================================
   A fixed surface, the way the board is: exactly the screen minus the tabs, and it
   does not scroll. Your hand is held at the bottom edge, the one thing this screen
   exists for is stamped across it, and the people you play sit on a shelf under
   that. What it replaced was a column of cream paper bills — eight sections of
   equal weight where nothing was the main thing, which read as a settings page. */

#views > .view--play.is-active:has(#pick-who:not([hidden])) { overflow: hidden; }
.view--play:has(#pick-who:not([hidden])) { padding: 0; }
.play-screen:has(#pick-who:not([hidden])) { height: 100%; max-width: none; gap: 0; }

/* The mat is a printed cutting grid — right under a camera, wrong under a fight,
   where the ruling competed with every card on the screen. Cloth, lit from above. */
.step--table {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
  text-align: left;
  isolation: isolate;
  background:
    radial-gradient(78% 40% at 50% 6%, rgba(255, 226, 170, 0.10), transparent 66%),
    radial-gradient(72% 26% at 50% 50%, rgba(192, 40, 30, 0.22), transparent 74%),
    radial-gradient(128% 80% at 50% 46%, rgba(38, 54, 48, 0.55) 0%, rgba(12, 17, 15, 0.96) 78%),
    #141a18;
  box-shadow: inset 0 0 180px rgba(0, 0, 0, 0.8);
}

.step--table::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: var(--fibre);
  opacity: 0.3;
  mix-blend-mode: overlay;
}

.step--table > * { position: relative; z-index: 1; }

/* ---------- the billing ---------- */

.tb-top {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 14px 0;
}

.tb-top .tape { display: none; }        /* the figures live in the form line here */
.tb-tag { --em-ransom: 13px; font-size: 13px; margin: 0; text-align: left; }
.tb-tag .ransom { justify-content: flex-start; }
.tb-right { display: grid; gap: 4px; justify-items: end; }

.tb-form {
  margin: 0;
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The roll. An empty one means the wildcard slot is dead before the fight starts,
   which makes this the one screen it has to be on besides the camera. */
.tb-film { display: flex; gap: 3px; }

.tb-film i {
  width: 8px;
  height: 11px;
  border-radius: 1px;
  background: rgba(244, 239, 227, 0.14);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5);
}

.tb-film i.on { background: var(--brass); box-shadow: 0 0 6px rgba(232, 178, 58, 0.5); }

/* ---------- anything with somebody at the other end ---------- */

.tb-urgent {
  flex: none;
  display: grid;
  gap: 7px;
  max-height: 21%;
  overflow-y: auto;
  padding: 6px 14px 0;
  -webkit-mask-image: linear-gradient(180deg, #000 0 80%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0 80%, transparent 100%);
}

/* NOTHING URGENT MEANS NO ROW AT ALL.
   :empty was the wrong test and never once matched: this holds three sections — the
   notifications nag, your turn, swaps — which are always present and individually
   [hidden]. So it stayed a six-pixel grid of nothing, and because the table is a flex
   column with a 26px gap it took a gap on each side too: fifty-eight pixels of blank
   between the header and "Your three", every time there was nothing going on, which is
   nearly always. :has is the test that means what :empty was reaching for. */
.tb-urgent:empty,
.tb-urgent:not(:has(> :not([hidden]))) { display: none; }
.tb-urgent .deaf { padding: 9px 12px; }
.tb-urgent .deaf__head { font-size: 14px; }
.tb-urgent .deaf__sub { font-size: 11px; line-height: 1.25; }

/* ---------- the hand ---------- */

.tb-handbar {
  flex: none;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 14px 2px;
  z-index: 6;
}

.tb-handbar__k {
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}

.tb-handbar__k b { color: #d08878; font-family: var(--receipt); font-weight: 400; }

.tb-change {
  font-family: var(--display);
  font-size: 12.5px;
  font-weight: 900;
  color: var(--cream);
  background: rgba(255, 246, 214, 0.08);
  border: 1px solid rgba(232, 178, 58, 0.4);
  border-radius: 2px;
  padding: 6px 11px;
  cursor: pointer;
}

.tb-hand {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;
  z-index: 4;
  --em-duel: clamp(12px, 5.25vw, 20.5px);
}

/* A REAL FAN PIVOTS BELOW THE CARDS. Rotating about the middle just overlapped them
   and buried the outer two; pivoting under the bottom edge splays the TOPS apart —
   and the top of one of these is the tier, the photograph and the ransom name. */
.tb-hand .card {
  position: absolute;
  left: 50%;
  bottom: -26px;
  transform-origin: 50% 150%;
  cursor: pointer;
  filter: drop-shadow(0 -7px 20px rgba(0, 0, 0, 0.62));
}

.tb-hand .card:nth-child(1) { transform: translateX(-50%) rotate(-16deg) translateX(34px); }
.tb-hand .card:nth-child(2) { transform: translateX(-50%) rotate(0deg) translateY(-10px) scale(1.03); z-index: 2; }
.tb-hand .card:nth-child(3) { transform: translateX(-50%) rotate(16deg) translateX(-34px); }

/* The hurt bar sits at the foot of a card, and in a hand every foot is off screen. */
.hand-hurt {
  position: absolute;
  right: 0.55em;
  top: 0.55em;
  z-index: 4;
  padding: 0.14em 0.4em 0.2em;
  border-radius: 1px;
  font-family: var(--label);
  font-size: 0.62em;
  letter-spacing: 0.06em;
  background: #8f2f22;
  color: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
}

/* A fan is mirrored, so the badges have to be: that card's top-right is off screen. */
.tb-hand .card:nth-child(3) .hand-hurt { right: auto; left: 0.55em; }

/* NOT a fan when it is empty. A fan of empty frames is a pile of overlapping dashed
   lines — the shape only reads when there are cards holding it apart. */
.tb-hand--empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.slots { display: flex; align-items: center; justify-content: center; gap: 11px; }

.slot {
  position: relative;
  width: 29vw;
  max-width: 126px;
  aspect-ratio: 13.2 / 19;
  border: 2px dashed rgba(232, 178, 58, 0.3);
  border-radius: 2px;
  background: rgba(255, 246, 214, 0.03);
  display: grid;
  place-items: center;
  transform: rotate(-1.6deg);
}

.slot:nth-child(2) { transform: rotate(1.4deg); }
.slot:nth-child(3) { transform: rotate(-0.8deg); }

.slot i {
  font-style: normal;
  font-family: var(--display);
  font-size: 26px;
  font-weight: 900;
  color: rgba(232, 178, 58, 0.24);
}

.tb-emptynote {
  margin: 16px 0 0;
  text-align: center;
  font-family: var(--receipt);
  font-size: 12.5px;
  color: var(--muted);
}

/* ---------- the stamp ---------- */

.tb-mid {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  z-index: 5;
  padding: 0 8px;
  margin-top: -18px;
}

/* A contained button is polite. This one runs off both edges at an angle — the
   loudest thing you can do with a rectangle is refuse to let it be one. */
.tb-go {
  position: relative;
  width: 112vw;
  padding: 18px 16px 20px;
  border: 0;
  cursor: pointer;
  color: #fff;
  font-family: var(--display);
  font-size: 37px;
  font-weight: 900;
  line-height: 1;
  text-transform: uppercase;
  transform: rotate(-2.4deg) skewX(-1.3deg);
  background: var(--ink-red);
  background-image: var(--grain);
  background-blend-mode: multiply;
  clip-path: polygon(0 4px, 3% 0, 26% 3px, 52% 0, 74% 3px, 97% 0, 100% 5px,
    100% calc(100% - 4px), 96% 100%, 71% calc(100% - 4px), 48% 100%,
    24% calc(100% - 3px), 3% 100%, 0 calc(100% - 5px));
  box-shadow: 0 18px 42px rgba(0, 0, 0, 0.62), 0 0 48px rgba(192, 40, 30, 0.5);
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.4);
}

.tb-go::after {
  content: "";
  position: absolute;
  inset: 6px;
  border: 1.5px solid rgba(255, 255, 255, 0.28);
}

/* The line explaining the button belongs ON the button. Underneath it, on bare
   cloth, it was an orphan. */
.tb-go small {
  display: block;
  margin-bottom: 4px;
  font-family: var(--label);
  font-size: 9.5px;
  letter-spacing: 0.2em;
  font-weight: 400;
  opacity: 0.82;
  text-shadow: none;
}

.tb-go:active { transform: rotate(-1.6deg) translateY(2px); }
.tb-mid .versus__msg { margin: 0; text-align: center; }

/* ---------- the shelf ---------- */

.tb-foes {
  flex: none;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 9px;
  z-index: 4;
  padding: 11px 0 calc(9px + env(safe-area-inset-bottom));
  border-top: 1px solid rgba(232, 178, 58, 0.16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.34), rgba(0, 0, 0, 0.52));
}

.tb-shelfhead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 14px;
}

.tb-shelfhead h3 {
  margin: 0;
  white-space: nowrap;
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The chips give way before the heading does: "Your enemies" names the shelf, and a
   heading pushed out of its own row by its own furniture reads as a bug.
   They scroll rather than clip, because a fourth appears whenever a hunt is going and
   three-and-a-bit chips with the last one sliced off reads as breakage. Same treatment
   as the enemies list under it. */
.tb-chips {
  display: flex;
  gap: 7px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.tb-chips::-webkit-scrollbar { display: none; }
.tb-chips .chip { flex: 0 0 auto; }

.chip {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(255, 246, 214, 0.05);
  border: 1px solid rgba(232, 178, 58, 0.18);
  border-radius: 2px;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
}

.chip__k {
  font-family: var(--label);
  font-size: 8.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* One line, always. A chip is a label on a button and a value long enough to wrap turns
   the shelf head into three rows of chip with the heading squeezed into a corner — which
   is exactly what happened when the Find Me chip started carrying a dare's name. Capped
   and clipped rather than trusted: the fix above is the right value, this is so that the
   wrong one can only ever look wrong rather than break the row. */
.chip__v {
  max-width: 11ch;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-family: var(--display);
  font-size: 12px;
  font-weight: 900;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

.tb-foelist {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 3px 14px 4px;
}

.tb-foelist::-webkit-scrollbar { display: none; }

/* One size, always. A taller tile in a row of short ones is what made this shelf
   read as leftovers rather than a place. */
.tb-foe {
  flex: 0 0 auto;
  position: relative;
  display: grid;
  gap: 2px;
  justify-items: start;
  align-content: center;
  width: 104px;
  height: 70px;
  padding: 0 11px;
  cursor: pointer;
  font: inherit;
  text-align: left;
  color: var(--ink);
  border: 0;
  background: var(--paper-2);
  background-image: var(--grain);
  background-blend-mode: multiply;
  box-shadow: 0 5px 13px rgba(0, 0, 0, 0.5);
  transform: rotate(-1.6deg);
}

.tb-foelist > .tb-foe:nth-child(even) { transform: rotate(1.8deg); }

.tb-foe::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -4px;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  border-radius: 50%;
  background: radial-gradient(circle at 34% 28%, #ffc9c0 0 18%, #c0281e 48%, #5c120c 100%);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.55);
}

.tb-foe b { font-family: var(--display); font-size: 15px; }
.tb-foe span { font-family: var(--display); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.tb-w { color: #2f7a43; }
.tb-l { color: var(--ink-red); }
.tb-foe em { font-style: normal; font-family: var(--receipt); font-size: 10px; opacity: 0.62; }

/* Something of theirs is waiting. A corner fold keeps every tile the same shape. */
.tb-foe--says::after {
  content: "";
  position: absolute;
  right: 0;
  top: 0;
  border-width: 0 15px 15px 0;
  border-style: solid;
  border-color: transparent var(--ink-red) transparent transparent;
}

.tb-foe--add {
  width: 80px;
  background: none;
  box-shadow: none;
  transform: none;
  border: 1px dashed rgba(232, 178, 58, 0.3);
  color: var(--muted);
}

.tb-foe--add::before { display: none; }
.tb-foe--add b { color: var(--muted); }

.tb-none {
  margin: 0;
  align-self: center;
  padding-left: 2px;
  font-family: var(--receipt);
  font-size: 12.5px;
  color: var(--muted);
  white-space: nowrap;
}

/* The stamp is 112vw on purpose, and without clipping it widens the whole surface and
   shoves every row on the screen to the right. */
.step--table { overflow: hidden; }

/* The table takes the masthead's height, the same way the board does. Forty-six pixels
   is most of a card's photograph. */
body:has(#pick-who:not([hidden])) .topbar { display: none; }

/* `.step--table > *` puts every row in its own stacking context at the same level, and
   the hand comes after the bar in the document — so the cards painted over "Your three"
   whatever z-index the bar asked for. */
/* The label belongs to the cards under it, so it sits against them rather than floating
   a full gap away — 26px between a heading and the thing it names reads as two separate
   pieces of furniture. Negative margin against the column gap, since the gap is right
   for everything else on the table. */
.tb-handbar { z-index: 10; margin-bottom: -19px; }

/* Long names and long form lines must not push the row wider than the screen. */
.tb-top > * { min-width: 0; }
.tb-form { max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tb-tag { overflow: hidden; }

/* Sized for the real surface, not the mock's: the hand is taller here because the shelf
   is shorter, so the same em produced cards that climbed over the header. */
/* THE FAN HAS TO KNOW HOW MUCH ROOM IT HAS.
   This was clamp(10px, 4.05vw, 16px) — sized off the viewport WIDTH and nothing else —
   so the cards were the same size whether the hand had three hundred and eighty pixels
   of height or two hundred. The moment anything appeared above it (a swap offered, a
   turn waiting, the notifications nag) the hand was squeezed, the cards were not, and
   four hundred pixels of absolutely-positioned card spilled straight up over whatever
   had squeezed it.
   A size container lets the height have a say. Height wins when it is the tighter of
   the two, which is exactly when the overlap used to happen. */
.tb-hand {
  container-type: size;
  --em-duel: clamp(8px, min(4.05vw, 3.7cqh), 16px);
}

/* And above the fan regardless, because a swap nobody can read is a swap nobody answers.
   The sizing above should mean the cards never reach it; this is so that a case I have
   not thought of degrades into "slightly cramped" rather than "buried". */
.tb-urgent { position: relative; z-index: 6; }
.tb-hand .card { bottom: -18px; }

/* And if a card ever does reach the bar, the bar stays readable. */
.tb-handbar::before {
  content: "";
  position: absolute;
  inset: -2px -8px;
  z-index: -1;
  background: linear-gradient(180deg, rgba(12, 17, 15, 0.92), rgba(12, 17, 15, 0.55));
}

/* The music button floats over the masthead, which this screen hides — so it lands on
   the form line instead. Keep clear of it. */
.step--table .tb-right { padding-right: 44px; }

/* Today's deal. On the camera screen, because that is where it is acted on. */
/* --- today's challenge, announced --- */

.chal {
  position: fixed;
  inset: 0;
  z-index: 62;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(8, 7, 5, 0.72);
  animation: chal-fade 180ms ease-out;
}

.chal__card {
  width: min(340px, 100%);
  display: grid;
  gap: 7px;
  padding: 20px 20px 17px;
  background: var(--paper, #e7e0cd);
  color: #17150f;
  border-radius: 3px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
  /* Off-square, like everything else made of paper in here. */
  transform: rotate(-0.6deg);
  animation: chal-in 280ms cubic-bezier(0.2, 1.1, 0.4, 1);
}

/* The four registers, same as everything else on paper in here: the tag is stamped
   tape, the type is sharpie, and the game narrates in typewriter. Setting --display
   without the 900 is not the sharpie register, it is just Inter — which is how this
   first went out looking like a cookie banner in the middle of a game made of tape. */
.chal__top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }

/* Stamped, like the deadline, because it is the other thing here that can be lost. */
.chal__run {
  margin: 0;
  flex: 0 0 auto;
  padding: 2px 6px;
  background: #17150f;
  color: var(--paper, #e7e0cd);
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transform: rotate(-1deg);
}

.chal__eyebrow {
  margin: 0;
  font-family: var(--label);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.62;
}

/* The type is the whole instruction, so it is the whole of the size. */
/* The name is the whole reason this is open, so it is the whole of the size. */
.chal__name {
  margin: 0;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(30px, 9.5vw, 40px);
  line-height: 0.94;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  text-wrap: balance;
}

.chal__line {
  margin: 0;
  font-family: var(--label);
  font-size: 13px;
  line-height: 1.35;
  opacity: 0.72;
}

/* A deadline is the one thing here that can run out, so it is the one thing stamped. */
.chal__when {
  margin: 6px 0 0;
  justify-self: start;
  padding: 3px 7px;
  background: #17150f;
  color: var(--paper, #e7e0cd);
  font-family: var(--label);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transform: rotate(-1.2deg);
}

/* Ruled off from the flavour above it: this is the half that is a rule. */
.chal__rule {
  margin: 5px 0 0;
  padding-top: 9px;
  border-top: 1px solid rgba(23, 21, 15, 0.18);
  font-family: var(--label);
  font-size: 12.5px;
  line-height: 1.5;
}

.chal__rule b { display: block; font-weight: 700; }
.chal__rule span { opacity: 0.75; }

.chal__row { display: flex; gap: 8px; margin-top: 9px; }

@keyframes chal-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes chal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.97) rotate(-1.6deg); }
  to   { opacity: 1; transform: translateY(0) scale(1) rotate(-0.6deg); }
}

@media (prefers-reduced-motion: reduce) {
  .chal, .chal__card { animation: none; }
}

.chip.is-done .chip__v { color: var(--muted); }


/* --- which phone this is --- */

.acct-device { display: grid; gap: 5px; justify-items: start; margin-top: 4px; }

.acct-device__k {
  margin: 0;
  font-family: var(--label);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.6;
}

/* Wraps rather than scrolls, and selectable: copy may be blocked, and then reading it
   off the screen is the fallback. Thirty-two characters of hex has to break somewhere. */
.acct-device__v {
  margin: 0;
  max-width: 100%;
  font-family: var(--receipt);
  font-size: 14px;
  line-height: 1.25;
  word-break: break-all;
  user-select: all;
  -webkit-user-select: all;
  opacity: 0.85;
}


/* Diagnostics, read off a phone that has no console. Small, wrapped, selectable. */
.acct-diag {
  margin: 6px 0 0;
  padding: 8px 10px;
  max-width: 100%;
  background: rgba(255, 246, 214, 0.05);
  border: 1px solid rgba(232, 178, 58, 0.18);
  border-radius: 2px;
  color: var(--muted);
  font-family: var(--receipt);
  font-size: 13px;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  user-select: all;
  -webkit-user-select: all;
}


/* --- today's dare, on the camera screen --- */

/* Taped on, like everything else that is a piece of paper in this game, and deliberately
   not shaped like the hint underneath it: that one is prose the game says to you, this
   one is a job with a state on it. */
.quest {
  display: flex;
  align-items: center;
  gap: 9px;
  width: calc(100% - 28px);
  margin: 0 14px 8px;
  padding: 8px 11px;
  background: var(--paper, #e7e0cd);
  color: #17150f;
  border: 0;
  border-radius: 2px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  transform: rotate(-0.5deg);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.quest__k {
  flex: 0 0 auto;
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.6;
}

/* The dare's name takes the room. One line: some of them are long, and a strip that
   grows to three lines stops being a strip. */
.quest__v {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-family: var(--display);
  font-weight: 900;
  font-size: 14px;
  letter-spacing: -0.01em;
  text-transform: uppercase;
}

.quest__state {
  flex: 0 0 auto;
  padding: 2px 6px;
  background: #17150f;
  color: var(--paper, #e7e0cd);
  font-family: var(--label);
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Done is a result, so it stops shouting and says so. */
.quest.is-done { opacity: 0.62; }
.quest.is-done .quest__state { background: #3f6f45; }
.quest.is-closed .quest__state { background: #7a4038; }


/* --- what your three actually do --- */

/* Left aligned against a centred parent. Three abilities, a warning and a definition
   are read, not admired, and centred prose in a column this narrow makes every line
   start somewhere different. */
.teamread { display: grid; gap: 7px; margin: 9px 0 0; padding: 0 14px; text-align: left; }
.teamread__rows { display: grid; gap: 5px; }

.teamread__row { display: flex; align-items: baseline; gap: 8px; }

.teamread__type {
  flex: 0 0 auto;
  min-width: 58px;
  padding: 2px 6px;
  border-radius: 2px;
  color: #17150f;
  font-family: var(--label);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.teamread__what {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--label);
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--cream);
  opacity: 0.85;
}

/* The one line on this screen that is a warning, so it is the one line that is red. */
.teamread__risk {
  margin: 2px 0 0;
  padding-top: 6px;
  border-top: 1px solid rgba(232, 178, 58, 0.16);
  font-family: var(--label);
  font-size: 11.5px;
  line-height: 1.35;
  color: #d08878;
}

.teamread__power {
  margin: 0;
  font-family: var(--label);
  font-size: 11px;
  line-height: 1.35;
  color: var(--muted);
}


/* Who claimed this kind. Small, stamped, and never louder than the card. */
.notes__claim {
  margin: 3px 0 0;
  font-family: var(--label);
  font-size: 0.82em;
  line-height: 1.3;
  opacity: 0.7;
}

/* Yours reads as a result rather than as a fact about somebody else. */
.notes__claim--mine { opacity: 0.95; font-weight: 700; }


/* How much of the collection is done. Quiet: it is a reason to keep going, not a score. */
.chal__count {
  margin: 4px 0 0;
  font-family: var(--label);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  opacity: 0.55;
}

/* --- a first edition being claimed --- */

/* Full bleed and opaque. Every other overlay in this game lets the screen show through
   because everything else is an interruption you might not want. This is the one event
   that can never happen again, so it gets the whole screen for a second and a half. */
.claimed {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  place-items: center;
  padding: 24px;
  background: #0d0b07;
  animation: claimed-in 160ms ease-out;
}

.claimed__inner {
  display: grid;
  gap: 6px;
  justify-items: center;
  text-align: center;
  animation: claimed-settle 600ms cubic-bezier(0.15, 1.3, 0.4, 1) 120ms both;
}

/* Brass, and stamped on at an angle like the No.1 on the card it is about to become. */
.claimed__stamp {
  margin: 0;
  padding: 10px 18px;
  border: 3px solid var(--brass, #e8b23a);
  border-radius: 3px;
  color: var(--brass, #e8b23a);
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(38px, 13vw, 58px);
  line-height: 0.88;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  transform: rotate(-7deg);
  animation: claimed-slam 340ms cubic-bezier(0.2, 1.6, 0.35, 1) both;
}

.claimed__kind {
  margin: 14px 0 0;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(22px, 7vw, 30px);
  text-transform: uppercase;
  color: var(--cream, #efe7d2);
}

.claimed__line {
  margin: 0;
  font-family: var(--label);
  font-size: 13px;
  color: var(--muted);
}

.claimed__by {
  margin: 8px 0 0;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brass, #e8b23a);
  opacity: 0.85;
}

@keyframes claimed-in { from { opacity: 0; } to { opacity: 1; } }

/* Down hard and slightly past, like a stamp hitting a desk. */
@keyframes claimed-slam {
  from { opacity: 0; transform: rotate(-16deg) scale(2.4); }
  60%  { opacity: 1; transform: rotate(-5deg) scale(0.94); }
  to   { opacity: 1; transform: rotate(-7deg) scale(1); }
}

@keyframes claimed-settle {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .claimed, .claimed__inner, .claimed__stamp { animation: none; }
}


/* What being first is worth. Brass, because it is the same fact as the No.1 stamp. */
.notes__original {
  margin: 3px 0 0;
  color: var(--brass, #e8b23a);
  font-family: var(--label);
  font-size: 0.82em;
  line-height: 1.3;
  font-weight: 700;
}


/* A hunt going begging. The only chip that is ever urgent, so it is the only one lit. */
.chip--hunt {
  border-color: rgba(232, 178, 58, 0.5);
  background: rgba(232, 178, 58, 0.12);
}

.chip--hunt .chip__k { opacity: 0.85; }
.chip--hunt .chip__v { color: var(--brass, #e8b23a); }

/* --- the hunt --- */

.view--race { overflow-y: auto; }

.race {
  display: grid;
  gap: 14px;
  padding: 18px 16px calc(var(--tabbar, 72px) + 18px);
  max-width: 560px;
  margin: 0 auto;
}

.race__eyebrow {
  margin: 0;
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.race__head {
  margin: 0;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(26px, 8vw, 36px);
  line-height: 0.96;
  text-transform: uppercase;
  color: var(--cream);
  text-wrap: balance;
}

/* Numbered because it is a sequence and the order is the rule. */
.race__how {
  margin: 0;
  padding-left: 1.1em;
  display: grid;
  gap: 5px;
  font-family: var(--label);
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
}

.race__blurb, .race__or {
  margin: 0;
  font-family: var(--label);
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
}

.race__or { text-align: center; opacity: 0.7; margin: 8px 0 2px; }

/* The code is the whole of the invitation, so it is the biggest thing on the screen. */
.race__code {
  display: grid;
  gap: 3px;
  justify-items: center;
  padding: 12px;
  background: rgba(255, 246, 214, 0.05);
  border: 1px solid rgba(232, 178, 58, 0.25);
  border-radius: 3px;
}

.race__code__k {
  margin: 0;
  font-family: var(--label);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.6;
}

.race__code__v {
  margin: 0;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(38px, 14vw, 56px);
  letter-spacing: 0.1em;
  line-height: 1;
  color: var(--brass, #e8b23a);
}

.race__code__how { margin: 2px 0 0; font-family: var(--label); font-size: 11px; opacity: 0.6; }

.race__seats { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }

.race__seat {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 10px;
  background: rgba(255, 246, 214, 0.04);
  border-radius: 2px;
  font-family: var(--label);
  font-size: 13px;
  color: var(--cream);
}

.race__seat b { font-family: var(--display); font-weight: 900; }
.race__seat__tag { margin-left: auto; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.55; }

.race__row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.race__waiting { margin: 0; font-family: var(--label); font-size: 12.5px; color: var(--muted); }

.race__clockrow { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }

.race__clock {
  margin: 0;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(34px, 11vw, 46px);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--cream);
}

/* Under a minute it stops being information and starts being pressure. */
.race__clock.is-low { color: #d08878; }

.race__at { margin: 0; font-family: var(--label); font-size: 12px; color: var(--muted); }

.race__dare {
  margin: 0;
  font-family: var(--display);
  font-weight: 900;
  font-size: clamp(28px, 9vw, 40px);
  line-height: 0.94;
  text-transform: uppercase;
  color: var(--brass, #e8b23a);
  text-wrap: balance;
}

.race__ask { margin: 0; font-family: var(--label); font-size: 14px; line-height: 1.35; color: var(--cream); }
.race__said { margin: 0; font-family: var(--label); font-size: 13px; color: #d08878; }

.race__board { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 5px; }

.race__line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 10px;
  background: rgba(255, 246, 214, 0.04);
  border-radius: 2px;
  font-family: var(--label);
  font-size: 13px;
  color: var(--muted);
}

.race__line--me { background: rgba(232, 178, 58, 0.12); color: var(--cream); }
.race__line__n { min-width: 1.4em; opacity: 0.55; }
.race__line__score { margin-left: auto; font-family: var(--display); font-weight: 900; color: var(--cream); }

/* --- holo --- */

/* Foil across the WHOLE card, not just the photo window — a real foil card is printed on
   the stock, so the sheen has to cross the paper and the stats as well as the picture.
   The first attempt put it on .card__frame with mix-blend-mode: screen, which is
   invisible: screen over near-white paper returns near-white. */
/* A REAL ELEMENT, because both of the card's pseudo-elements are the paper and the
   stock glare, and the comment above .card::before says in as many words never to style
   them from a modifier class. It has now caught .card--hurt, .card--first and this. */
.card__foil {
  position: absolute;
  inset: 0;
  z-index: 7;
  pointer-events: none;
  clip-path: polygon(0.8% 0, 99.2% 0.4%, 100% 99.2%, 0.4% 100%);
  background:
    linear-gradient(115deg,
      transparent 14%,
      rgba(255, 90, 190, 0.40) 28%,
      rgba(80, 210, 255, 0.44) 41%,
      rgba(150, 255, 160, 0.38) 52%,
      rgba(255, 215, 90, 0.42) 64%,
      transparent 80%),
    linear-gradient(62deg,
      transparent 32%,
      rgba(255, 255, 255, 0.30) 49%,
      transparent 64%);
  background-size: 250% 250%, 190% 190%;
  /* Overlay rather than screen: it darkens as well as lightens, so the sheen survives on
     white paper and on a dark photograph both. */
  mix-blend-mode: overlay;
  animation: holo-tilt 7s ease-in-out infinite alternate;
}

/* Slow, and never all the way across. A foil catches the light when the card moves; a
   shine that sweeps end to end on a timer reads as a loading skeleton. */
@keyframes holo-tilt {
  from { background-position: 16% 38%, 72% 28%; }
  to   { background-position: 80% 62%, 18% 72%; }
}

/* The edge, so it is still legible as foil in the deck grid at thumbnail size where the
   sheen itself is only a few pixels. A first edition keeps its brass: being first
   outranks being lucky. */
.card--holo .card__frame {
  box-shadow:
    inset 0 0 0 0.09em rgba(190, 235, 255, 0.85),
    inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5);
}

.card--first.card--holo .card__frame {
  box-shadow:
    inset 0 0 0 0.1em rgba(232, 178, 58, 0.9),
    inset 0 0.4em 0.8em rgba(0, 0, 0, 0.55);
}

@media (prefers-reduced-motion: reduce) {
  .card__foil { animation: none; }
}

/* --- evolution --------------------------------------------------------------
   The same photograph, put back through the model and come out as something that
   would eat the original. Three stages: the print, then two.

   A SEPARATE VISUAL LANGUAGE FROM EVERY OTHER RARITY. First edition is brass, holo
   is iridescent, tier is a word in a corner. Evolution is arcane violet, because the
   art itself goes that way and a frame arguing with its own picture looks broken.
   A card can be all four at once and still read.

   THE FRAME IS A CHILD, NOT A PSEUDO-ELEMENT. .card::before and .card::after are the
   paper and the stock glare and the stylesheet says plainly never to take either from
   a modifier class. This has caught .card--hurt and .card--first already. */

.card--evo-2 .card__frame {
  box-shadow:
    inset 0 0 0 0.09em rgba(168, 106, 226, 0.85),
    inset 0 0 1.1em rgba(126, 61, 194, 0.55),
    inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5);
}

.card--evo-3 .card__frame {
  box-shadow:
    inset 0 0 0 0.12em rgba(226, 122, 240, 0.95),
    inset 0 0 1.9em rgba(150, 62, 220, 0.75),
    inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5);
}

/* The charge in the paper itself. Sits under the photograph rather than over it, so
   the art is never tinted — only the edge of the card glows. */
.card--evo-2 { --evo-glow: rgba(140, 74, 208, 0.45); }
.card--evo-3 { --evo-glow: rgba(196, 84, 232, 0.72); }

.card--evo-2,
.card--evo-3 {
  filter: drop-shadow(0 0 0.5em var(--evo-glow));
}

/* The stage, stamped. Roman, because II and III read as stages where 2 and 3 read as
   a quantity of something. */
.card__evo {
  position: absolute;
  right: 0.35em;
  bottom: 0.35em;
  z-index: 8;
  font-family: var(--label);
  font-size: 0.62em;
  font-weight: 800;
  letter-spacing: 0.12em;
  padding: 0.18em 0.5em;
  color: #f7ecff;
  background: linear-gradient(180deg, #6f34ad, #3f1a6b 70%, #2a0f4a);
  border: 0.08em solid #150826;
  box-shadow: 0 0.1em 0.3em rgba(0, 0, 0, 0.5);
}

.card--evo-3 .card__evo {
  background: linear-gradient(180deg, #c05ce0, #7a2bb8 70%, #45146f);
  color: #fff;
}

/* Stage three is awake. Slow, because a card in a hand of four should not be the thing
   your eye keeps going back to mid-fight. */
@keyframes evo-pulse {
  0%, 100% { filter: drop-shadow(0 0 0.45em rgba(196, 84, 232, 0.55)); }
  50%      { filter: drop-shadow(0 0 0.95em rgba(226, 122, 240, 0.9)); }
}

.card--evo-3 { animation: evo-pulse 4.5s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .card--evo-3 { animation: none; }
}

/* An evolved card opens out to a square window.
   
   The model returns a square and the standard window is 12.7x8.6em, about 3:2, so a
   third of every evolution was being cropped off — in practice the legs and whatever the
   creature was standing on, which is the half that makes it look like a monster rather
   than a portrait. The frame grows to 12.7em square and the card grows by the same 4.1em
   so the receipt keeps all five stat rows.

   FULL SIZE ONLY. Every other size has its own height and its own frame height —
   .card--hand is 18.4em with a 7em window — so the board, the bench and the deck grid
   are untouched and stay uniform. A thumbnail crops, which is what thumbnails do; open
   the card and it opens out. */
.card--full.card--evo-2,
.card--full.card--evo-3 { height: 27.7em; }

.card--full.card--evo-2 .card__frame,
.card--full.card--evo-3 .card__frame { height: 12.7em; }

/* What evolving bought, on every stat row. Violet so it reads as the same thing the
   frame and the stamp are saying, and distinct from a Find Me mod, which is brass-dark
   and permanent. */
.stat__mod--evo {
  background: linear-gradient(180deg, #7a35c0, #4a1a84);
  color: #f6ecff;
}

.notes__evo {
  margin: 3px 0 0;
  color: #b478e8;
  font-family: var(--label);
  font-size: 0.82em;
  line-height: 1.3;
  font-weight: 700;
}

/* The design evolves with the card, not just the border.

   STAGE II CHANGES THE EDGE. Stage III changes the object: the tier band, the paper and
   the print all go over. The jump from II to III should be the one that makes somebody
   turn the phone round, which it cannot do if II has already spent everything. */

/* The paper takes the charge. Kept very low — the receipt carries five stat rows and an
   ability line and all of it has to stay readable on a phone in a pub. */
.card--evo-3 .card__receipt {
  background:
    radial-gradient(120% 70% at 50% 0%, #efe4fb 0%, #fdfcf7 62%);
}

.card--evo-3 .card__title {
  text-shadow: 0 0 0.35em rgba(176, 106, 232, 0.55);
}

/* --- shiny ------------------------------------------------------------------
   The rarest thing in the game, and it has to look like a prize rather than a fault.

   The first version rotated the hue 155 degrees, which turned a brown dog teal. That is
   a palette swap in the literal sense and it read as haunted — an object that had gone
   WRONG rather than one that had come back precious. So the colour goes gold and the
   work is done by movement instead: a sweep, and glints, which is what actually makes a
   foil card feel like a foil card when you tilt it under a light.

   It sits above .card__foil at z-index 7, so a card that is both gets both. */

/* Gilded, not sick. Sepia lays down the gold, saturation and contrast put the life back
   in, and the small negative hue rotation keeps skin and fur warm rather than green. */
.card--shiny .card__photo img {
  filter:
    sepia(0.55)
    saturate(2.1)
    hue-rotate(-14deg)
    contrast(1.18)
    brightness(1.12);
}

.card__shine {
  position: absolute;
  inset: 0;
  z-index: 8;
  pointer-events: none;
  clip-path: polygon(0.8% 0, 99.2% 0.4%, 100% 99.2%, 0.4% 100%);
  overflow: hidden;
}

/* The sweep. A hard specular band that crosses the whole card and then waits, rather
   than a gradient drifting about — the wait is what makes it read as a catch of light
   instead of a loading skeleton. */
.card__shine::before {
  content: '';
  position: absolute;
  inset: -40%;
  background: linear-gradient(
    104deg,
    transparent 38%,
    rgba(255, 246, 214, 0.20) 45%,
    rgba(255, 255, 255, 0.85) 50%,
    rgba(255, 238, 180, 0.30) 55%,
    transparent 62%);
  mix-blend-mode: overlay;
  animation: shine-sweep 3.6s cubic-bezier(0.6, 0, 0.35, 1) infinite;
}

@keyframes shine-sweep {
  0%   { transform: translateX(-85%); }
  /* Across in the first third, then off the edge and still for the rest. */
  34%  { transform: translateX(85%); }
  100% { transform: translateX(85%); }
}

/* The glints. Four points that pulse out of step, so it twinkles rather than blinks. */
.card__shine::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle 0.42em at 22% 26%, rgba(255, 255, 255, 0.95), transparent 70%),
    radial-gradient(circle 0.3em at 71% 18%, rgba(255, 244, 200, 0.9), transparent 70%),
    radial-gradient(circle 0.34em at 84% 63%, rgba(255, 255, 255, 0.9), transparent 70%),
    radial-gradient(circle 0.26em at 34% 74%, rgba(255, 240, 190, 0.85), transparent 70%);
  mix-blend-mode: screen;
  animation: shine-glint 2.4s ease-in-out infinite;
}

@keyframes shine-glint {
  0%, 100% { opacity: 0.25; }
  30%      { opacity: 1; }
  55%      { opacity: 0.4; }
  78%      { opacity: 0.9; }
}

/* Gold at the edge, so it still reads as a shiny in the deck grid where the sweep is
   only a few pixels wide. */
.card--shiny .card__frame {
  box-shadow:
    inset 0 0 0 0.12em rgba(255, 216, 120, 0.95),
    inset 0 0 1.4em rgba(255, 190, 70, 0.55),
    inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5);
}

.card--shiny { filter: drop-shadow(0 0 0.6em rgba(255, 200, 90, 0.55)); }

/* Said out loud. Half the value of a shiny is being able to point at it. */
.card__shiny {
  position: absolute;
  left: 0.35em;
  /* Clear of the tier band, which sits at top -0.55em and overhangs the corner. The
     first version put them on top of each other and COMMON won. */
  top: 1.6em;
  z-index: 9;
  font-family: var(--label);
  font-size: 0.6em;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 0.18em 0.5em;
  color: #3a2504;
  background: linear-gradient(180deg, #fff3c4, #ffd166 55%, #e0a020);
  border: 0.08em solid #3a2504;
  box-shadow: 0 0.1em 0.35em rgba(0, 0, 0, 0.5);
}

/* The moment.

   The develop sequence brings the image up with a TRANSITION on filter — blur and
   desaturate to sharp — so a keyframe animation on the same property fights it and the
   Polaroid stops working. Instead the transition simply lands somewhere else: the print
   comes up exactly as it always does and settles into gold while you watch.

   Specificity is deliberate. .card.developing.is-surfacing is three classes, so this
   needs four to land on top of it rather than relying on source order. */
.card.developing.developing--shiny.is-surfacing .card__photo img {
  filter: blur(0) sepia(0.55) saturate(2.1) hue-rotate(-14deg) contrast(1.18) brightness(1.12);
}

/* And the card flares once as it arrives, on the root rather than the photograph, so
   nothing here competes with the develop for the same property. */
@keyframes shiny-arrive {
  0%   { filter: drop-shadow(0 0 0 rgba(255, 200, 90, 0)); }
  30%  { filter: drop-shadow(0 0 1.8em rgba(255, 230, 150, 1)); }
  100% { filter: drop-shadow(0 0 0.6em rgba(255, 200, 90, 0.55)); }
}

.card.developing--shiny.is-surfacing { animation: shiny-arrive 2.2s ease-out both; }

/* The sweep is the whole point, so it is not removed under reduced motion — it is
   slowed to something that no longer reads as motion at the edge of vision. */
@media (prefers-reduced-motion: reduce) {
  .card.developing--shiny.is-surfacing { animation: none; }
  .card__shine::before { animation-duration: 14s; }
  .card__shine::after { animation: none; opacity: 0.55; }
}

/* The second stage. The card has landed and the picture is being made — see
   netlify/functions/shine.mjs for why that is a separate request. This is the wait, and
   the wait is the point: something is happening to the card in front of them. */
.reveal-slot.is-gilding .card {
  animation: gilding 1.5s ease-in-out infinite;
}

@keyframes gilding {
  0%, 100% { filter: drop-shadow(0 0 0.4em rgba(255, 200, 90, 0.35)); }
  50%      { filter: drop-shadow(0 0 1.5em rgba(255, 226, 150, 0.95)); }
}

/* And it takes. One flare as the new picture swaps in. */
.reveal-slot.is-gilded .card__photo img { animation: gilded 1.1s ease-out both; }

@keyframes gilded {
  0%   { filter: brightness(3.2) saturate(0.4); transform: scale(1.04); }
  100% { filter: none; transform: none; }
}

/* The gilded picture is already gold, so the stylesheet stops pretending. The tint
   below is the FALLBACK — what a shiny looks like when the model would not take, which
   has to still be unmistakable because the card is shiny either way. */
.card--shiny.card--gilded .card__photo img { filter: contrast(1.05) saturate(1.05); }

@media (prefers-reduced-motion: reduce) {
  .reveal-slot.is-gilding .card,
  .reveal-slot.is-gilded .card__photo img { animation: none; }
}

/* --- the roll, in the print -------------------------------------------------
   Whether it hatched, told by the photograph coming up.

   THIS REPLACED THREE SEPARATE CEREMONIES. A dial, then a fruit machine, then a deck of
   card backs — each of them a second event standing in front of the one this game
   already has. The print coming up IS the moment. It simply never had anything at stake.

   --near is how close the roll actually came, 0 to 1, set by develop.js. The gold floods
   that far up the frame and then drains, so a card that nearly went is a card you
   watched nearly go. A hit is 1, and it does not drain. */
.card__roll {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  opacity: 0;
  /* Two layers doing different jobs: a solid gold that hard-light drives into the
     photograph so it actually changes colour, and a bright lip riding the top edge so
     there is a visible waterline climbing the frame. overlay alone was far too polite —
     on a mid-tone photograph it barely registered, which is no use for the rarest thing
     in the game. */
  background:
    linear-gradient(0deg,
      rgba(255, 190, 40, 0.30) 0%,
      rgba(255, 210, 90, 0.34) 62%,
      rgba(255, 246, 214, 0.85) 92%,
      rgba(255, 255, 255, 0.95) 100%),
    linear-gradient(0deg, #ffb42a 0%, #ffd166 60%, #ffe9a8 100%);
  /* Only ever as tall as the roll got. */
  clip-path: inset(calc(100% - (var(--near, 0) * 100%)) 0 0 0);
  mix-blend-mode: hard-light;
}

/* It rises with the image and then falls back. The rise is slower than the fall: coming
   up is the hope and going is the answer, and the answer should not linger. */
.developing--rolling.is-surfacing .card__roll {
  animation: roll-rise 1.5s cubic-bezier(0.3, 0.7, 0.3, 1) 0.15s both;
}

@keyframes roll-rise {
  0%   { opacity: 0; transform: translateY(16%); }
  50%  { opacity: 0.92; transform: translateY(0); }
  62%  { opacity: 0.92; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(8%); }
}

/* The card itself lifts as the gold climbs, in proportion to how far it got. Barely
   perceptible on a bad roll, unmistakable on one that nearly took. */
.developing--rolling.is-surfacing {
  animation: roll-hope 1.5s cubic-bezier(0.3, 0.7, 0.3, 1) 0.15s both;
}

@keyframes roll-hope {
  0%, 100% { filter: drop-shadow(0 0 0 rgba(255, 200, 90, 0)); }
  55% {
    filter: drop-shadow(0 0 calc(var(--near, 0) * 2.2em) rgba(255, 214, 120, calc(var(--near, 0) * 0.95)));
  }
}

/* Unless it took, in which case it floods the whole frame and stays. */
.developing--shiny.is-surfacing .card__roll {
  clip-path: inset(0 0 0 0);
  animation: roll-take 1.9s cubic-bezier(0.25, 0.8, 0.3, 1) 0.15s both;
}

@keyframes roll-take {
  0%   { opacity: 0; transform: translateY(20%); }
  40%  { opacity: 1; transform: translateY(0); }
  58%  { opacity: 1; }
  100% { opacity: 0.62; transform: translateY(0); }
}

/* And the card itself flares once as it takes. */
.developing--shiny.is-surfacing {
  animation: shiny-arrive 2.1s ease-out both;
}

@keyframes shiny-arrive {
  0%   { filter: drop-shadow(0 0 0 rgba(255, 200, 90, 0)); }
  40%  { filter: drop-shadow(0 0 2em rgba(255, 232, 160, 1)); }
  100% { filter: drop-shadow(0 0 0.6em rgba(255, 200, 90, 0.55)); }
}

@media (prefers-reduced-motion: reduce) {
  .developing--rolling.is-surfacing .card__roll,
  .developing--rolling.is-surfacing,
  .developing--shiny.is-surfacing .card__roll,
  .developing--shiny.is-surfacing { animation: none; }
  .developing--shiny.is-surfacing .card__roll { opacity: 0.5; }
}

/* --- the machine --------------------------------------------------------------
   Whether it hatched, as a slot machine. NAMED .machine, NOT .slot: .slot is already the
   team builder's card slot — position: relative, 126px wide, a dashed border — and the
   first version of this borrowed the name, inherited that width and aspect ratio, and
   drew as a 126px box with the cabinet spilling out of it and the app showing beneath. See public/src/slot.js for why three reels and
   why it is still a readout. This is the cabinet.

   A REAL OBJECT, NOT A WIDGET. The first cabinet was flat rectangles with thin lines and
   read as a prototype. A machine somebody wants to pull has bulbs that chase while the
   reels run, a backlit sign that only comes on when it is earned, a lever, drums behind
   curved glass, and a brass rail the result stops on. All of it in this game's own
   materials — lacquer, brass, chrome, paper grain — so it belongs next to the cards. */
.machine {
  position: fixed;
  inset: 0;
  /* Above every sheet and the manual, below #wrong at 600 — the frozen-game report has
     to stay reachable over anything. At 90 the sign-in sheet drew over the reels. */
  z-index: 520;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0;
  padding: 24px 18px;
  background:
    radial-gradient(90% 60% at 50% 38%, rgba(232, 178, 58, 0.10) 0%, transparent 70%),
    radial-gradient(120% 90% at 50% 30%, #181410 0%, var(--sink) 70%),
    var(--sink);
  touch-action: none;
  overscroll-behavior: none;
  --machine-cell: 96px;
  --bulb: #3b2b16;                  /* off */
  --bulb-on: #ffd98a;
  --bulb-glow: rgba(255, 200, 90, 0);
}

/* The cabinet: lacquer with the grain in it, a brass frame, and a heavy shadow. */
.machine__cab {
  position: relative;
  /* 88vw rather than 92: the lever hangs off the right edge and at 92 its knob was on
     the very edge of the screen. */
  width: min(88vw, 372px);
  padding: 20px 16px 18px;
  border-radius: 12px;
  background:
    var(--grain),
    linear-gradient(180deg, #2a221a 0%, #171310 48%, #0d0a08 100%);
  background-blend-mode: soft-light, normal;
  box-shadow:
    inset 0 0 0 2px #060504,
    inset 0 0 0 5px rgba(232, 178, 58, 0.75),
    inset 0 0 0 6px rgba(120, 80, 20, 0.9),
    inset 0 3px 0 7px rgba(255, 240, 200, 0.07),
    0 30px 70px rgba(0, 0, 0, 0.75);
  transition: box-shadow 0.5s ease;
}

/* Bulb rails, top and bottom. The dots are a repeating radial gradient; chasing them is
   a background-position animation, so the whole rail costs one property per frame. */
.machine__cab::before,
.machine__cab::after {
  content: '';
  position: absolute;
  left: 14px;
  right: 14px;
  height: 12px;
  pointer-events: none;
  background-image: radial-gradient(circle at 6px 6px, var(--bulb) 0 3.2px, transparent 4px);
  background-size: 22px 12px;
  background-repeat: repeat-x;
  filter: drop-shadow(0 0 4px var(--bulb-glow));
  transition: filter 0.3s ease;
}
.machine__cab::before { top: 6px; }
.machine__cab::after { bottom: 6px; }

.machine__cab.is-spinning { --bulb: #b98a3a; --bulb-glow: rgba(255, 200, 90, 0.45); }
.machine__cab.is-spinning::before { animation: bulbs-chase 0.7s linear infinite; }
.machine__cab.is-spinning::after { animation: bulbs-chase 0.7s linear infinite reverse; }
.machine__cab.is-tense { --bulb: #ffcf6a; --bulb-glow: rgba(255, 210, 110, 0.8); }
.machine__cab.is-tense::before,
.machine__cab.is-tense::after { animation-duration: 0.32s; }
.machine__cab.is-hit { --bulb: var(--bulb-on); --bulb-glow: rgba(255, 220, 140, 1); }
.machine__cab.is-hit::before,
.machine__cab.is-hit::after { animation: bulbs-blaze 0.5s steps(2, end) infinite; }
.machine__cab.is-near { --bulb: #6b4d1f; --bulb-glow: rgba(255, 200, 90, 0.15); }

@keyframes bulbs-chase { from { background-position-x: 0; } to { background-position-x: 22px; } }
@keyframes bulbs-blaze { 0% { opacity: 1; } 50% { opacity: 0.35; } 100% { opacity: 1; } }

/* The maker's plate and the sign. The sign is a lightbox: unlit it is a dark red-brown
   glass with the letters barely there; lit it is warm white with a gold halo. */
.machine__brand {
  margin: 8px 0 6px;
  text-align: center;
  font-family: var(--label);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: rgba(232, 178, 58, 0.55);
}

.machine__marquee {
  position: relative;
  margin: 0 auto 14px;
  width: 78%;
  padding: 9px 0 10px;
  border-radius: 6px;
  text-align: center;
  font-family: var(--display);
  font-size: 24px;
  font-weight: 800;
  letter-spacing: 0.44em;
  text-indent: 0.44em;
  text-transform: uppercase;
  color: rgba(150, 70, 40, 0.55);
  background:
    linear-gradient(180deg, #1c120e 0%, #0e0907 100%);
  box-shadow:
    inset 0 0 0 1px #000,
    inset 0 0 0 3px rgba(232, 178, 58, 0.45),
    inset 0 6px 14px rgba(0, 0, 0, 0.8),
    inset 0 -2px 0 rgba(255, 255, 255, 0.04);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.9);
  transition: color 0.35s ease, text-shadow 0.35s ease, background 0.35s ease;
}

/* The drums. A bevelled chrome window each, with the reel darkened hard at the ends and
   a highlight band across the upper third so it reads as a cylinder behind glass. */
.machine__reels {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  padding: 10px;
  border-radius: 6px;
  background: linear-gradient(180deg, #050403, #0b0907);
  box-shadow:
    inset 0 0 0 1px #000,
    inset 0 2px 0 rgba(255, 255, 255, 0.05),
    inset 0 8px 22px rgba(0, 0, 0, 0.95);
}

.machine__reel {
  position: relative;
  height: calc(var(--machine-cell) * 3);
  overflow: hidden;
  border-radius: 5px;
  background: #0a0806;
  /* Chrome bevel: light on the top-left, dark on the bottom-right. */
  box-shadow:
    inset 1px 1px 0 rgba(255, 255, 255, 0.22),
    inset -1px -1px 0 rgba(0, 0, 0, 0.9),
    inset 0 0 0 2px #1c1815,
    inset 0 0 0 3px rgba(190, 190, 190, 0.18),
    inset 14px 0 18px -10px rgba(0, 0, 0, 0.9),
    inset -14px 0 18px -10px rgba(0, 0, 0, 0.9);
  transition: box-shadow 0.35s ease;
}

.machine__strip {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  will-change: transform, filter;
}

/* One symbol: the photograph that was just taken. Plain ones are pulled right back so
   the gold is the only thing on the drum with any life in it. */
.machine__cell {
  position: relative;
  height: var(--machine-cell);
  margin: 0 6px;
  box-sizing: border-box;
  border-top: 1px solid rgba(0, 0, 0, 0.7);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  background-color: #1a1511;
  background-size: cover;
  background-position: center;
  filter: saturate(0.3) brightness(0.5) contrast(1.08);
  transition: filter 0.25s ease;
}

.machine__cell--gold {
  filter: sepia(0.55) saturate(2.2) hue-rotate(-14deg) contrast(1.2) brightness(1.18);
  box-shadow:
    inset 0 0 0 2px rgba(255, 224, 150, 0.95),
    inset 0 0 22px rgba(255, 190, 70, 0.6);
}
/* A sheen on the gold so it catches light like foil rather than reading as a tint. */
.machine__cell--gold::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.28) 48%, transparent 62%);
  mix-blend-mode: screen;
}

/* The one that landed on the line. Pops once and a highlight sweeps it. */
.machine__cell.is-landed { animation: land-pop 0.42s cubic-bezier(0.2, 1.4, 0.4, 1) both; z-index: 2; }
.machine__cell.is-landed::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 20%, rgba(255, 255, 255, 0.55) 50%, transparent 80%);
  background-size: 250% 100%;
  mix-blend-mode: screen;
  animation: land-sweep 0.7s ease-out 0.05s both;
}
@keyframes land-pop { 0% { transform: scale(1); } 45% { transform: scale(1.06); } 100% { transform: scale(1); } }
@keyframes land-sweep { from { background-position-x: 120%; } to { background-position-x: -120%; } }

/* Curved glass over the drum. */
.machine__glass {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: 5px;
  background:
    linear-gradient(180deg,
      rgba(0, 0, 0, 0.92) 0%,
      rgba(0, 0, 0, 0.45) 20%,
      rgba(0, 0, 0, 0) 33%,
      rgba(0, 0, 0, 0) 67%,
      rgba(0, 0, 0, 0.45) 80%,
      rgba(0, 0, 0, 0.92) 100%),
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.16) 0%,
      rgba(255, 255, 255, 0.05) 14%,
      rgba(255, 255, 255, 0) 26%,
      rgba(255, 255, 255, 0) 60%,
      rgba(255, 255, 255, 0.04) 100%);
}

/* The rail the result stops on: brass, across all three drums, with a pointer each side. */
.machine__payline {
  position: absolute;
  left: 22px;
  right: 22px;
  top: calc(20px + 8px + 6px + 10px + 6px + 14px + 45px + 10px + var(--machine-cell));
  height: var(--machine-cell);
  pointer-events: none;
  border-top: 2px solid rgba(255, 214, 120, 0.9);
  border-bottom: 2px solid rgba(255, 214, 120, 0.9);
  box-shadow: 0 0 0 0 rgba(232, 178, 58, 0);
  transition: box-shadow 0.3s ease, border-color 0.3s ease;
}
.machine__payline::before,
.machine__payline::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 0;
  height: 0;
  margin-top: -7px;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  filter: drop-shadow(0 0 3px rgba(255, 214, 120, 0.7));
}
.machine__payline::before { left: -8px; border-left: 9px solid #ffd166; }
.machine__payline::after { right: -8px; border-right: 9px solid #ffd166; }

/* The lever. A chrome rod with a red knob, hinged at the bottom, on the cabinet's right. */
.machine__lever {
  position: absolute;
  right: -22px;
  top: 34%;
  width: 10px;
  height: 118px;
  border-radius: 5px;
  background: linear-gradient(90deg, #6b6b6b, #f2f2f2 35%, #9a9a9a 60%, #4a4a4a);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.6);
  transform-origin: 50% 100%;
  transform: rotate(0deg);
}
.machine__lever::before {
  content: '';
  position: absolute;
  left: 50%;
  top: -16px;
  width: 30px;
  height: 30px;
  margin-left: -15px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #ff8a7a, #c0281e 45%, #6d120c 100%);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6), inset 0 -3px 6px rgba(0, 0, 0, 0.35);
}
.machine__lever::after {
  content: '';
  position: absolute;
  left: -4px;
  bottom: -6px;
  width: 18px;
  height: 12px;
  border-radius: 3px;
  background: linear-gradient(180deg, #d9b25c, #8a6420);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
}
.machine__lever.is-pulled { animation: lever-pull 0.75s cubic-bezier(0.3, 0.9, 0.3, 1) both; }
@keyframes lever-pull {
  0%   { transform: rotate(0deg); }
  38%  { transform: rotate(46deg); }
  100% { transform: rotate(0deg); }
}

/* Locked drums. */
.machine__reel.is-plain .machine__cell { filter: saturate(0.2) brightness(0.42); }
.machine__reel.is-plain .machine__cell--gold { filter: sepia(0.55) saturate(1.4) hue-rotate(-14deg) brightness(0.75); }
.machine__reel.is-gold {
  box-shadow:
    inset 1px 1px 0 rgba(255, 255, 255, 0.25),
    inset -1px -1px 0 rgba(0, 0, 0, 0.9),
    inset 0 0 0 2px #2a1f0a,
    inset 0 0 0 3px rgba(255, 214, 120, 0.9),
    inset 0 0 30px rgba(255, 190, 70, 0.6),
    0 0 22px rgba(255, 200, 90, 0.5);
}

/* Two down, one to go. The rail breathes and the cabinet leans in. */
.machine__cab.is-tense .machine__payline { animation: payline-breathe 0.8s ease-in-out infinite; }
@keyframes payline-breathe {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232, 178, 58, 0); border-color: rgba(255, 214, 120, 0.9); }
  50%      { box-shadow: 0 0 26px 3px rgba(232, 178, 58, 0.6); border-color: #fff1c8; }
}
.machine__cab.is-tense .machine__marquee { animation: marquee-breathe 0.8s ease-in-out infinite; }
@keyframes marquee-breathe {
  0%, 100% { color: rgba(150, 70, 40, 0.55); text-shadow: 0 1px 0 rgba(0, 0, 0, 0.9); }
  50%      { color: rgba(255, 190, 120, 0.7); text-shadow: 0 0 10px rgba(255, 170, 90, 0.5); }
}

.machine__cab.is-near .machine__payline { border-color: rgba(255, 214, 120, 0.5); }

/* All three. */
.machine__cab.is-hit { animation: cab-blaze 2.4s ease-out both; }
.machine__cab.is-hit .machine__marquee {
  color: #fff4d0;
  background: linear-gradient(180deg, #4a2c14 0%, #24140a 100%);
  text-shadow:
    0 0 10px rgba(255, 224, 150, 1),
    0 0 26px rgba(255, 190, 70, 0.9),
    0 0 60px rgba(255, 170, 40, 0.6);
  animation: marquee-flicker 1.6s steps(1, end) both;
}
@keyframes cab-blaze {
  0%   { box-shadow: inset 0 0 0 2px #060504, inset 0 0 0 5px #ffd166, inset 0 0 0 6px #6b4a10, 0 0 0 rgba(255, 210, 90, 0), 0 30px 70px rgba(0, 0, 0, 0.75); }
  22%  { box-shadow: inset 0 0 0 2px #060504, inset 0 0 0 5px #fff6d6, inset 0 0 0 6px #b98a3a, 0 0 110px 14px rgba(255, 220, 120, 0.95), 0 30px 70px rgba(0, 0, 0, 0.75); }
  100% { box-shadow: inset 0 0 0 2px #060504, inset 0 0 0 5px #ffd166, inset 0 0 0 6px #6b4a10, 0 0 50px 6px rgba(255, 210, 90, 0.6), 0 30px 70px rgba(0, 0, 0, 0.75); }
}
@keyframes marquee-flicker {
  0% { opacity: 0.2; } 8% { opacity: 1; } 14% { opacity: 0.35; } 20% { opacity: 1; }
  31% { opacity: 0.6; } 38% { opacity: 1; } 100% { opacity: 1; }
}

/* The odds, on a brass plate under the cabinet. */
.machine__odds {
  margin: 20px 0 0;
  padding: 5px 14px 6px;
  border-radius: 3px;
  font-family: var(--label);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  text-transform: uppercase;
  color: #2a1c06;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 55%, var(--brass-deep));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), inset 0 -1px 0 rgba(0, 0, 0, 0.4), 0 3px 8px rgba(0, 0, 0, 0.6);
}

.machine__said {
  margin: 12px 0 0;
  min-height: 1.5em;
  max-width: 26ch;
  text-align: center;
  font-family: var(--label);
  font-size: 17px;
  line-height: 1.35;
  color: var(--cream-2);
  opacity: 0;
}
.machine__said.is-in { animation: said-in 0.45s ease-out both; }
@keyframes said-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.machine__cab.is-hit ~ .machine__said {
  color: var(--brass-pale);
  font-family: var(--display);
  font-size: 26px;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  text-shadow: 0 0 18px rgba(255, 200, 90, 0.7);
}

@media (max-width: 380px) {
  .machine { --machine-cell: 82px; }
  .machine__lever { right: -16px; height: 100px; }
}

@media (prefers-reduced-motion: reduce) {
  .machine__cab::before, .machine__cab::after,
  .machine__cab.is-tense .machine__payline,
  .machine__cab.is-tense .machine__marquee,
  .machine__cab.is-hit, .machine__cab.is-hit .machine__marquee,
  .machine__lever.is-pulled, .machine__cell.is-landed, .machine__cell.is-landed::before,
  .machine__said.is-in { animation: none; }
  .machine__said { opacity: 1; }
}


/* --- the camera ---------------------------------------------------------------
   The shoot screen as an instant camera. See the markup note in index.html.

   The stage was a bare video with a spinner over it. Now it is a viewfinder with grain
   and a level, shutter blades that close over the frame on the press, a print that
   motors out of the bottom and develops while the model works, and a readout that
   types. The thing you are waiting on is a physical object in front of you, not a
   spinner. */
.stage {
  border-radius: 10px;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.06),
    inset 0 0 40px rgba(0, 0, 0, 0.7);
}

.stage__grain {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: var(--grain);
  opacity: 0.35;
  mix-blend-mode: overlay;
}

/* Vignette and a faint light-leak at one corner, so it reads as glass and not a stream. */
.stage__grain::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 90% at 50% 50%, transparent 55%, rgba(0, 0, 0, 0.55) 100%),
    radial-gradient(40% 30% at 100% 0%, rgba(255, 200, 120, 0.08), transparent 70%);
}

/* Corner brackets. They breathe while the camera is live and settle when the shot is
   taken. */
.stage__corners { position: absolute; inset: 10%; z-index: 2; pointer-events: none; }
.stage__corners i {
  position: absolute;
  width: 22px;
  height: 22px;
  border: 2px solid rgba(244, 239, 227, 0.75);
  filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.8));
  animation: corners-breathe 2.6s ease-in-out infinite;
}
.stage__corners i:nth-child(1) { left: 0; top: 0; border-right: 0; border-bottom: 0; }
.stage__corners i:nth-child(2) { right: 0; top: 0; border-left: 0; border-bottom: 0; }
.stage__corners i:nth-child(3) { left: 0; bottom: 0; border-right: 0; border-top: 0; }
.stage__corners i:nth-child(4) { right: 0; bottom: 0; border-left: 0; border-top: 0; }
@keyframes corners-breathe {
  0%, 100% { transform: translate(0, 0); opacity: 0.75; }
  50%      { transform: translate(var(--cx, 0), var(--cy, 0)); opacity: 1; }
}
.stage__corners i:nth-child(1) { --cx: -3px; --cy: -3px; }
.stage__corners i:nth-child(2) { --cx: 3px; --cy: -3px; }
.stage__corners i:nth-child(3) { --cx: -3px; --cy: 3px; }
.stage__corners i:nth-child(4) { --cx: 3px; --cy: 3px; }
.stage.is-frozen .stage__corners i { animation: none; opacity: 0.35; }
.stage.is-frozen .stage__frame { display: block; }

/* The old frame outline stays under the corners but is quieter now. */
.stage__frame { border-color: rgba(244, 239, 227, 0.14); }

/* A bubble level. The bubble rides the phone's tilt, which the googly eyes already read
   every frame; it goes brass when the phone is held still and level. */
.stage__level {
  position: absolute;
  left: 50%;
  top: 14px;
  z-index: 2;
  width: 88px;
  height: 10px;
  margin-left: -44px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.55);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.9), inset 0 -1px 0 rgba(255, 255, 255, 0.08);
  pointer-events: none;
}
.stage__level::before {
  content: '';
  position: absolute;
  left: 50%;
  top: -3px;
  bottom: -3px;
  width: 2px;
  margin-left: -1px;
  background: rgba(232, 178, 58, 0.55);
}
.stage__bubble {
  position: absolute;
  top: 1px;
  left: 50%;
  width: 8px;
  height: 8px;
  margin-left: -4px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fff8e6, var(--brass) 60%, var(--brass-deep));
  transform: translateX(calc(var(--gx, 0) * 34px));
  transition: transform 0.12s ease-out, box-shadow 0.3s ease;
}
.stage.is-frozen .stage__level { opacity: 0.35; }

/* Held still for a moment: the bubble lights and the brackets settle. That is the cue to
   press, and it costs nothing — googly.js already knows when the phone stopped moving. */
:root.is-still .stage__bubble {
  box-shadow: 0 0 8px 2px rgba(232, 178, 58, 0.85), 0 0 18px rgba(232, 178, 58, 0.5);
}
:root.is-still .stage__corners i { animation-play-state: paused; opacity: 1; }

/* The shutter blades. Two black leaves that meet over the frame on the press and open
   again on the print. */
.stage__blades { position: absolute; inset: 0; z-index: 5; pointer-events: none; }
.stage__blade {
  position: absolute;
  left: 0;
  right: 0;
  height: 50%;
  background: #07060a;
  transform: scaleY(0);
}
.stage__blade--top { top: 0; transform-origin: 50% 0; }
.stage__blade--bottom { bottom: 0; transform-origin: 50% 100%; }
.stage.is-snapping .stage__blade { animation: blade 0.42s cubic-bezier(0.7, 0, 0.3, 1) both; }
@keyframes blade {
  0%   { transform: scaleY(0); }
  30%  { transform: scaleY(1); }
  62%  { transform: scaleY(1); }
  100% { transform: scaleY(0); }
}

/* The flash: hotter and shorter, with a warm decay. */
.stage__flash.is-firing { animation: flash 0.42s ease-out; }
@keyframes flash {
  0%   { opacity: 1; background: #fff; }
  18%  { opacity: 0.9; background: #fff3d6; }
  100% { opacity: 0; background: #ffd9a0; }
}

/* THE PRINT. Sits centred in the stage once it is out. It comes up from the bottom edge
   on a motor, blank, and develops towards the photograph over the seconds the model
   takes — so the wait is spent watching something happen. */
.print {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 4;
  width: min(64%, 236px);
  aspect-ratio: 5 / 6.1;
  transform: translate(-50%, -50%) rotate(-2deg);
  transform-origin: 50% 50%;
  pointer-events: none;
}
.print__paper {
  position: absolute;
  inset: 0;
  padding: 5.5% 5.5% 21%;
  border-radius: 2px;
  background:
    var(--grain),
    linear-gradient(180deg, #fbf8f0 0%, #f1ebdd 100%);
  background-blend-mode: multiply, normal;
  box-shadow:
    0 22px 46px rgba(0, 0, 0, 0.75),
    0 2px 4px rgba(0, 0, 0, 0.5),
    inset 0 0 0 1px rgba(0, 0, 0, 0.06);
}
.print__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #1a1612;
  /* Blank. Not white: a print comes out grey-green and lifts. */
  filter: brightness(2.2) contrast(0.18) saturate(0) sepia(0.4);
  opacity: 0.22;
}
.stage.is-ejected .print { animation: eject 0.95s cubic-bezier(0.25, 0.9, 0.3, 1) both; }
@keyframes eject {
  0%   { transform: translate(-50%, 75%) rotate(-2deg); }
  70%  { transform: translate(-50%, -54%) rotate(-2deg); }
  100% { transform: translate(-50%, -50%) rotate(-2deg); }
}
/* Developing: the transition is what does the work. It is set long, because the
   model takes about seven seconds and the print should still be coming up when the
   answer arrives. */
.stage.is-developing .print__img {
  transition: filter 7.5s linear, opacity 7.5s linear;
  filter: brightness(1.12) contrast(0.8) saturate(0.65) sepia(0.12);
  opacity: 0.86;
}

/* The readout. A strip across the top of the camera that types. */
.stage__thinking {
  position: absolute;
  inset: auto 12px 12px 12px;
  z-index: 6;
  display: block;
  place-content: unset;
  gap: 0;
  padding: 0;
  background: none;
  text-shadow: none;
  font-size: 13px;
}
.stage__lcd {
  margin: 0;
  padding: 7px 11px 8px;
  border-radius: 3px;
  font-family: var(--receipt);
  font-size: 13px;
  letter-spacing: 0.06em;
  line-height: 1.3;
  color: #d8ffb2;
  background: rgba(7, 12, 5, 0.86);
  border: 1px solid rgba(160, 220, 120, 0.22);
  box-shadow: inset 0 0 14px rgba(120, 200, 90, 0.16), 0 2px 6px rgba(0, 0, 0, 0.6);
  text-shadow: 0 0 6px rgba(160, 255, 120, 0.55);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
}
.stage__lcd::after {
  content: '▮';
  margin-left: 2px;
  animation: lcd-cursor 0.9s steps(1, end) infinite;
}
@keyframes lcd-cursor { 50% { opacity: 0; } }

/* The shutter, with travel. */
.shutter::before {
  content: '';
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 30%, #7a7a7a, #2a2a2a 70%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35), inset 0 -2px 4px rgba(0, 0, 0, 0.8), 0 6px 14px rgba(0, 0, 0, 0.7);
  z-index: -1;
}
.shutter { position: relative; }
.shutter:active span,
.shutter.is-pressed span {
  transform: translateY(3px) scale(0.97);
  box-shadow:
    inset 0 -1px 2px rgba(0, 0, 0, 0.45),
    inset 0 2px 3px rgba(0, 0, 0, 0.5),
    0 1px 2px rgba(0, 0, 0, 0.6);
}

@media (prefers-reduced-motion: reduce) {
  .stage__corners i, .stage.is-snapping .stage__blade, .stage.is-ejected .print,
  .stage__lcd::after { animation: none; }
  .stage.is-ejected .print { transform: translate(-50%, -50%) rotate(-2deg); }
}


/* --- the foil card ------------------------------------------------------------
   What a shiny looks like once it is in the hand.

   The gilded photograph is the model's work and it is spectacular; the card around it
   has to earn it. So: real foil stock rather than a gold border. A prismatic ring round
   the whole card that turns, and turns with the phone. A metallic name plate. A sheen on
   the paper that slides as the phone tilts, because the googly eyes already read the
   tilt every frame and put it on the root as --gx/--gy. An embossed seal naming the
   finish. And each finish gets its own foil — gold, chrome, crystal, opal — so a chrome
   one is not a gold one with a different label. */
@property --spin { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

.card--shiny {
  --foil-a: #fff4cc;
  --foil-b: #e8b23a;
  --foil-c: #a8781c;
  --foil-glow: rgba(255, 200, 90, 0.55);
  --foil-ink: #3a2604;
  filter: drop-shadow(0 0 0.6em var(--foil-glow));
}
.card--finish-chrome  { --foil-a: #ffffff; --foil-b: #cfd5dd; --foil-c: #6e767f; --foil-glow: rgba(200, 215, 235, 0.6); --foil-ink: #1c2026; }
.card--finish-crystal { --foil-a: #ffffff; --foil-b: #c6ebff; --foil-c: #5aa9dc; --foil-glow: rgba(160, 220, 255, 0.6); --foil-ink: #12384f; }
.card--finish-opal    { --foil-a: #fff7fc; --foil-b: #f0c4ff; --foil-c: #7fd8c8; --foil-glow: rgba(235, 190, 255, 0.6); --foil-ink: #3a1f47; }

/* The window edge takes the finish colour. */
.card--shiny .card__frame {
  box-shadow:
    inset 0 0 0 0.12em var(--foil-b),
    inset 0 0 1.4em var(--foil-glow),
    inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5);
}

/* The prismatic ring. A real child, masked to a border, turning slowly on its own and
   swinging with the phone. */
.card__edge {
  position: absolute;
  inset: -0.26em;
  z-index: 0;
  pointer-events: none;
  border-radius: 0.4em;
  padding: 0.26em;
  --spin: 0deg;
  background: conic-gradient(
    from calc(var(--spin) + var(--gx, 0) * 160deg + var(--gy, 0) * 90deg),
    var(--foil-c), var(--foil-a) 14%, var(--foil-b) 28%, #ff9ad6 42%, #8fd7ff 56%,
    var(--foil-a) 70%, var(--foil-b) 84%, var(--foil-c));
  -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;
  filter: saturate(1.35) brightness(1.08) drop-shadow(0 0 0.5em var(--foil-glow));
  animation: edge-turn 9s linear infinite;
}
@keyframes edge-turn { to { --spin: 360deg; } }

/* The name plate in metal. The letters carry their own paper colours inline, so this
   has to be !important to reach them. */
.card--shiny .ransom__l {
  background: linear-gradient(180deg, var(--foil-a) 0%, var(--foil-b) 52%, var(--foil-c) 100%) !important;
  color: var(--foil-ink) !important;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.45), inset 0 0 0 1px rgba(0, 0, 0, 0.18);
}

/* The paper takes a sheen that slides with the phone. */
.card--shiny .card__receipt {
  /* Two thin lines of light a hand's width apart, the way foil actually catches it. A
     single wide band read as glare on plastic. */
  background-image:
    linear-gradient(115deg,
      transparent 44%, rgba(255, 255, 255, 0.55) 47%, transparent 49%,
      transparent 52%, rgba(255, 255, 255, 0.35) 54%, transparent 57%),
    linear-gradient(115deg, transparent 30%, var(--foil-glow) 50%, transparent 70%),
    linear-gradient(180deg, #fdfcf7, #f6f2e6);
  background-size: 260% 100%, 260% 100%, 100% 100%;
  background-position: calc(50% + var(--gx, 0) * 70%) 0, calc(50% - var(--gx, 0) * 40%) 0, 0 0;
  background-blend-mode: normal, soft-light, normal;
  transition: background-position 0.14s ease-out;
}

/* The sweep already on the card catches the tilt as well as the clock. */
.card--shiny .card__shine::before {
  margin-left: calc(var(--gx, 0) * 28%);
  transition: margin-left 0.15s ease-out;
}

/* The seal. Round, embossed, in the finish, naming it. Top left under the tier sticker,
   clear of the edition stamp and the evolution mark at the bottom. */
.card--shiny .card__shiny {
  /* Bottom right of the window, riding the edge of the paper. Big enough to read the
     finish at card size — the first cut was 3em at half a font size and said nothing. */
  top: auto;
  left: auto;
  right: 0.3em;
  bottom: -0.9em;
  z-index: 12;
  width: 4.3em;
  height: 4.3em;
  padding: 0.3em;
  border-radius: 50%;
  display: grid;
  place-items: center;
  text-align: center;
  font-size: 0.62em;
  line-height: 1.05;
  letter-spacing: 0.08em;
  background: radial-gradient(circle at 38% 30%, var(--foil-a), var(--foil-b) 55%, var(--foil-c));
  color: var(--foil-ink);
  border: 0.12em solid var(--foil-ink);
  box-shadow:
    inset 0 0 0 0.24em rgba(255, 255, 255, 0.35),
    inset 0 0 0 0.36em var(--foil-c),
    0 0.2em 0.5em rgba(0, 0, 0, 0.5);
  transform: rotate(-9deg);
}

@media (prefers-reduced-motion: reduce) {
  .card__edge { animation: none; }
}


/* --- the eyes, with a character --------------------------------------------------
   A blink now and then, on each pair's own clock; a glance toward a tap; and a squint
   when the card takes a hit. The lid is the eye's own ::before, sliding down over the
   bead. The bead physics are untouched — this is what happens around them. */
.googly::before {
  content: '';
  position: absolute;
  left: -2%;
  right: -2%;
  top: -104%;
  height: 104%;
  border-radius: 50%;
  background: #e9e4d6;
  box-shadow: inset 0 -0.05em 0.06em rgba(0, 0, 0, 0.25);
  z-index: 2;
  transition: top 0.07s ease-in;
}
.googly.is-blink::before { top: 0; transition-duration: 0.05s; }
/* Wincing: the lid comes halfway down and stays while the card is hurt. */
.card--hurt .googly::before { top: -55%; }
.card--hurt .googly-pair { transform: rotate(var(--skew, 0deg)) scale(0.96); }


/* --- eyes with intent -----------------------------------------------------------
   Stuck on by a person, not printed. A thick white bezel like a real plastic eye, a
   slap-on pop when the print comes up, and lids that say what the thing is: a Beast
   is wide awake, Refuse can barely keep them open, a Relic has seen it all. */
.googly {
  box-shadow:
    inset 0 -0.08em 0.14em rgba(0, 0, 0, 0.3),
    inset 0 0.06em 0.1em rgba(255, 255, 255, 0.9),
    0 0 0 0.09em rgba(255, 255, 255, 0.85),
    0 0.08em 0.16em rgba(0, 0, 0, 0.6);
}
/* They land on the print a beat after the image surfaces: squash, overshoot, settle. */
.card.developing .googly-pair { opacity: 0; transform: scale(0.2) rotate(var(--skew, 0deg)); }
.card.developing.is-surfacing .googly-pair {
  animation: eyes-slap 0.5s cubic-bezier(0.2, 1.6, 0.4, 1) 0.55s both;
}
@keyframes eyes-slap {
  0%   { opacity: 0; transform: scale(0.2) rotate(-14deg); }
  60%  { opacity: 1; transform: scale(1.22) rotate(4deg); }
  100% { opacity: 1; transform: scale(1) rotate(var(--skew, 0deg)); }
}
/* What kind of thing it is, in the lids. */
.card--type-Refuse .googly::before { top: -62%; }
.card--type-Relic .googly::before { top: -84%; }
.card--type-Beast .googly { transform: scale(1.06); }
.card--type-Refuse .googly-pair { transform: rotate(calc(var(--skew, 0deg) + 6deg)); }

/* --- polish ----------------------------------------------------------------------
   Views arrive rather than switch; buttons go down when pressed; the reveal's actions
   land after the card does; the deck deals itself out. */
.view.is-active { animation: view-in 0.26s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
@keyframes view-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

.btn:active:not(:disabled), .tab:active { transform: translateY(2px) scale(0.985); filter: brightness(0.94); }
.btn, .tab { transition: transform 0.08s ease, filter 0.08s ease; }

.reveal-actions:not(.is-waiting) > * { animation: rise-in 0.36s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
.reveal-actions:not(.is-waiting) > :nth-child(2) { animation-delay: 0.06s; }
.reveal-actions:not(.is-waiting) > :nth-child(3) { animation-delay: 0.12s; }
.reveal-actions:not(.is-waiting) > :nth-child(4) { animation-delay: 0.18s; }
@keyframes rise-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

.deck-grid .card { animation: deal-in 0.4s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
.deck-grid .card:nth-child(2) { animation-delay: 0.04s; }
.deck-grid .card:nth-child(3) { animation-delay: 0.08s; }
.deck-grid .card:nth-child(4) { animation-delay: 0.12s; }
.deck-grid .card:nth-child(5) { animation-delay: 0.16s; }
.deck-grid .card:nth-child(6) { animation-delay: 0.20s; }
.deck-grid .card:nth-child(n+7) { animation-delay: 0.24s; }
@keyframes deal-in { from { opacity: 0; transform: translateY(14px) rotate(-1.5deg); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .view.is-active, .reveal-actions:not(.is-waiting) > *, .deck-grid .card,
  .card.developing.is-surfacing .googly-pair { animation: none; }
  .card.developing .googly-pair { opacity: 1; transform: rotate(var(--skew, 0deg)); }
}


/* --- navigation -------------------------------------------------------------------
   The bar: three equal places. The label keeps its torn-paper character; the mark above
   it makes it a place; the active one is lit and lifted; the others are legible but
   quiet. A badge is a red ink dot with a count, for things waiting there. */
.tabs {
  justify-content: stretch;
  gap: 8px;
  padding: 8px 10px calc(10px + env(safe-area-inset-bottom, 0px));
}
.tab {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  justify-items: center;
  gap: 4px;
  padding: 6px 2px 8px;
  /* Four places and the sound toggle across a 375px phone: at 18px ERRANDS alone was
     wider than its quarter and DEPLOY ran under the note. */
  font-size: clamp(12px, 3.4vw, 18px);
  border-radius: 6px;
  opacity: 0.62;
  transition: opacity 0.18s ease, transform 0.18s ease, background 0.18s ease;
}
.tab__mark {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}
.tab__label { position: relative; }
.tab.is-active {
  /* Bright tape, lifted, with a brass underline. The first cut swapped the tape for a
     faint brass tint on the dark strip and kept the ink text — dark on dark. */
  opacity: 1;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  box-shadow: inset 0 -3px 0 var(--brass), 0 6px 12px rgba(0, 0, 0, 0.65);
}
.tab:not(.is-active) { opacity: 0.48; }
.tab.is-active .tab__mark { color: var(--brass); }
/* The lift stays only on the active one, and gentler now the bar is a grid. */
.tab:nth-child(1).is-active,
.tab:nth-child(2).is-active,
.tab:nth-child(3).is-active { transform: translateY(-3px); }
.tab:nth-child(1).is-active .tab__label { transform: rotate(-3deg); }
.tab:nth-child(2).is-active .tab__label { transform: rotate(1.6deg); }
.tab:nth-child(3).is-active .tab__label { transform: rotate(-1.4deg); }

.tab__badge {
  position: absolute;
  top: 2px;
  right: calc(50% - 26px);
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  font-family: var(--label);
  font-size: 11px;
  font-weight: 800;
  color: #fff;
  background: var(--ink-red);
  box-shadow: 0 0 0 2px rgba(16, 20, 22, 0.98), 0 2px 6px rgba(0, 0, 0, 0.6);
  animation: badge-in 0.35s cubic-bezier(0.2, 1.5, 0.4, 1) both;
}
@keyframes badge-in { from { transform: scale(0); } to { transform: scale(1); } }

/* The Play sub-row: a segmented control rather than a scatter of chips. Equal segments,
   the name above, the state beneath, one of them lit when it is live. */
.tb-chips { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.tb-chips .chip {
  flex: unset;
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 8px 4px 7px;
  border-radius: 4px;
  text-align: center;
  background: rgba(255, 246, 214, 0.04);
  border: 1px solid rgba(232, 178, 58, 0.16);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.tb-chips .chip:active { background: rgba(232, 178, 58, 0.14); }
.tb-chips .chip__k {
  font-family: var(--label);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass);
}
.tb-chips .chip__v { font-size: 12px; color: var(--cream-2); white-space: nowrap; }
.tb-chips .chip--hunt { border-color: rgba(232, 178, 58, 0.4); }

@media (prefers-reduced-motion: reduce) { .tab__badge { animation: none; } }


/* "Back again to leave." Just above the bar, in the voice of the furniture. */
.leave-toast {
  position: fixed;
  left: 50%;
  bottom: calc(86px + env(safe-area-inset-bottom, 0px));
  z-index: 65;
  margin: 0;
  padding: 7px 14px 8px;
  transform: translateX(-50%);
  border-radius: 3px;
  font-family: var(--label);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--paper);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.55);
  animation: rise-in 0.25s ease-out both;
}


/* Play with nothing to play with: the builder, the sub-row and the enemies list go, and
   the screen is the one call to action. They come back with the first card. */
#view-play.is-bare #hand,
#view-play.is-bare .tb-handbar,
#view-play.is-bare #friends-step { display: none; }
#view-play.is-bare .tb-mid { padding-top: 18px; }


/* --- the reveal's rewards, stacked ------------------------------------------------
   One line per thing the catch did, each a receipt tag, landing one after the other. */
.reveal-notes {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.reveal-notes__line {
  display: inline-block;
  padding: 5px 11px 6px;
  border-radius: 2px;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--label);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.06em;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  transform: rotate(-0.8deg);
  animation: rise-in 0.3s cubic-bezier(0.2, 0.9, 0.3, 1) both;
}
.reveal-notes__line:nth-child(2) { transform: rotate(0.9deg); animation-delay: 0.12s; }
.reveal-notes__line:nth-child(3) { transform: rotate(-0.5deg); animation-delay: 0.24s; }
.reveal-notes__line:nth-child(n+4) { animation-delay: 0.36s; }

/* --- Play, with a hierarchy ----------------------------------------------------------
   The fight button is the point of the screen and now looks like it. */
.tb-go {
  display: grid;
  gap: 2px;
  width: 100%;
  padding: 16px 18px 17px;
  border-radius: 6px;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 55%, var(--brass-deep));
  color: #2a1c06;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55), inset 0 -2px 0 rgba(0, 0, 0, 0.25), 0 8px 20px rgba(0, 0, 0, 0.55);
}
.tb-go #tb-go-word { font-family: var(--display); font-size: 22px; font-weight: 900; letter-spacing: 0.02em; }
.tb-go #tb-go-sub { font-family: var(--label); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.75; }
.tb-shelfhead h3 { font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--muted); }


/* --- the reveal's two rows -----------------------------------------------------------
   Doing (bulk, brag, offer) above deciding (bin, recruit). Whichever row is empty of
   visible buttons folds away, so a fresh catch still reads as one clean decision. */
.reveal-actions { grid-template-columns: 1fr; gap: 10px; }
.reveal-actions__more,
.reveal-actions__decide { display: flex; justify-content: center; gap: 8px; }
.reveal-actions__more:not(:has(button:not([hidden]))) { display: none; }
.reveal-actions__more .btn { flex: 0 1 auto; padding: 8px 12px; font-size: 14px; }
.reveal-actions__decide .btn { flex: 1 1 0; }
.reveal-actions__decide .btn--primary { flex-grow: 1.6; }
.reveal-actions .btn--primary { grid-column: auto; }

.reveal-notes--over { margin-top: 8px; }
.reveal-notes--over .reveal-notes__line { font-size: 11px; }


/* --- a shiny at deck size ------------------------------------------------------------
   The foil work is tuned for the full card. In the grid it shrank to a faint tint, so a
   shiny sat among the commons like one of them. At grid size it breathes: a pulsing gold
   halo, a bright gleam crossing the whole card every few seconds, and the edge ring
   thick enough to read from across the room. */
.deck-grid .card--shiny {
  animation: shiny-halo 2.6s ease-in-out infinite;
  position: relative;
  z-index: 1;
}
@keyframes shiny-halo {
  0%, 100% { filter: drop-shadow(0 0 0.5em rgba(255, 205, 96, 0.55)); }
  50%      { filter: drop-shadow(0 0 1.3em rgba(255, 215, 110, 0.95)); }
}
.deck-grid .card--shiny .card__edge {
  inset: -0.35em;
  padding: 0.35em;
  opacity: 1;
}
.deck-grid .card--shiny::after {
  /* A real child of nothing: this is the card's own ::after (paper glare), which for a
     shiny becomes the gleam. */
  background: linear-gradient(115deg, transparent 38%, rgba(255, 250, 225, 0.85) 48%, rgba(255, 225, 140, 0.9) 52%, transparent 62%);
  background-size: 260% 100%;
  mix-blend-mode: screen;
  opacity: 1;
  animation: shiny-gleam 3.4s cubic-bezier(0.6, 0, 0.4, 1) infinite;
}
@keyframes shiny-gleam {
  0%, 55% { background-position: 120% 0; }
  100%    { background-position: -40% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .deck-grid .card--shiny { animation: none; filter: drop-shadow(0 0 0.9em rgba(255, 210, 100, 0.8)); }
  .deck-grid .card--shiny::after { animation: none; }
}


/* --- the sound toggle lives in the tab strip ------------------------------------------
   Fixed in the top-right corner it sat on whatever a screen put there: the Sign in
   button on the deck, Change on the play screen, and when muted its ✖ read as a close.
   The strip is the one piece of chrome that is always there and never has a header. */
.sound {
  top: auto;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
  right: 10px;
  z-index: 61;
}
.tabs { padding-right: 56px; gap: 4px; }


/* --- eyes on the real eyes --------------------------------------------------------------
   When the photograph already has a face, the pair lands on it. The frame is a container
   so the eye size and spacing can be a fraction of the real eye distance, which the scan
   returns as a percentage of the frame width. */
.card__frame { container-type: inline-size; }
.googly-pair--mapped {
  /* Eye size follows the real spacing, floored so close-set eyes still get proper
     googly eyes, capped so a face filling the frame does not get dinner plates. */
  font-size: clamp(9cqw, calc(var(--eye-gap, 12) * 0.62cqw), 22cqw);
  gap: 0;
}
/* The centre-to-centre distance is the real one exactly, whatever the size: half an
   eye, this margin, half an eye. Negative when the eyes are bigger than the spacing,
   which is how two googly eyes on a close-set face are meant to look. */
.googly-pair--mapped .googly:first-child { margin-right: calc(var(--eye-gap, 12) * 1cqw - 1em); }

/* --- the play strip: who you are, in one readable line ------------------------------
   The form line was nine-pixel capitals that cut off after four words. */
.tb-top { align-items: flex-start; }
.tb-form { font-size: 10px; text-align: right; white-space: normal; max-width: 46vw; line-height: 1.3; }


/* --- the modes, as tiles --------------------------------------------------------------
   Find me, Hunt, Pass, More: a mark, a name, and the state it is in. They were four
   ten-pixel labels squeezed beside the enemies heading; now they read like the tab
   strip does, and the row is the first thing on the shelf. */
.tb-modes {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  padding: 0 12px;
}
.tb-modes .chip {
  display: grid;
  grid-template-rows: auto auto auto;
  justify-items: center;
  align-content: start;
  gap: 3px;
  padding: 9px 4px 8px;
  border-radius: 8px;
  text-align: center;
  color: var(--cream-2);
  background: linear-gradient(180deg, rgba(255, 246, 214, 0.07), rgba(255, 246, 214, 0.02));
  border: 1px solid rgba(232, 178, 58, 0.22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 3px 8px rgba(0, 0, 0, 0.45);
  transition: transform 0.12s ease, background 0.15s ease, border-color 0.15s ease;
}
.tb-modes .chip:active { transform: translateY(1px) scale(0.98); background: rgba(232, 178, 58, 0.16); }
.tb-modes .chip--hunt { border-color: rgba(232, 178, 58, 0.5); }
.mode__mark {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: var(--brass);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}
.tb-modes .chip__k {
  font-family: var(--label);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream);
}
.tb-modes .chip__v {
  font-family: var(--label);
  font-size: 10px;
  letter-spacing: 0.04em;
  padding: 1px 7px 2px;
  border-radius: 999px;
  color: var(--muted);
  background: rgba(0, 0, 0, 0.38);
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tb-foes { gap: 10px; }
.tb-shelfhead { padding-top: 2px; }

/* --- the locker -----------------------------------------------------------------------
   Everything else, as rows you can hit: a title, a line of what it is for, a chevron. It
   was two underlined words in a paragraph. */
.locker { display: grid; margin-top: 6px; border-top: 1px solid rgba(232, 178, 58, 0.16); }
.locker__row {
  display: grid;
  gap: 2px;
  justify-items: start;
  width: 100%;
  padding: 13px 34px 13px 2px;
  position: relative;
  text-align: left;
  color: var(--cream);
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(232, 178, 58, 0.16);
  cursor: pointer;
  font: inherit;
}
.locker__row::after {
  content: '›';
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--display);
  font-size: 22px;
  color: var(--brass);
  opacity: 0.8;
}
.locker__row:active { background: rgba(232, 178, 58, 0.08); }
.locker__t { font-family: var(--display); font-size: 16px; font-weight: 800; letter-spacing: 0.01em; }
.locker__s { font-family: var(--label); font-size: 12px; color: var(--muted); }


/* --- a shiny's power -----------------------------------------------------------------
   Printed under the type ability in the metal it is made of, and flaring on the card
   in the fight the moment it fires. */
.card__power {
  margin: 0.25em 0 0;
  padding: 0.3em 0.5em 0.35em;
  border-radius: 2px;
  font-family: var(--label);
  font-size: 0.55em;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #2a1c06;
  background: linear-gradient(100deg, var(--foil-a, #f3d27a), var(--foil-b, #b8862b) 60%, var(--foil-a, #f3d27a));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5), 0 1px 2px rgba(0, 0, 0, 0.35);
  text-wrap: balance;
}
#teleprompt.is-power { color: var(--brass); text-shadow: 0 0 12px rgba(232, 178, 58, 0.6); }
@keyframes power-flare {
  0%   { filter: brightness(1) drop-shadow(0 0 0 rgba(255, 215, 110, 0)); transform: scale(1); }
  18%  { filter: brightness(1.9) drop-shadow(0 0 1.4em rgba(255, 220, 120, 1)); transform: scale(1.06); }
  100% { filter: brightness(1) drop-shadow(0 0 0 rgba(255, 215, 110, 0)); transform: scale(1); }
}
.is-power .card { animation: power-flare 1.3s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
@media (prefers-reduced-motion: reduce) { .is-power .card { animation: none; } }


/* --- the news sheet -------------------------------------------------------------------
   One sheet, once: what a shiny is and what it does. The powers are listed in the
   metal they come in. */
.news .sheet__inner { display: grid; gap: 10px; }
.news__lead { margin: 0; font-size: 15px; line-height: 1.45; color: var(--cream); }
.news__line { margin: 0; font-size: 14px; line-height: 1.45; color: var(--cream-2); }
.news__line--quiet { font-size: 12.5px; color: var(--muted); }
.news__sub {
  margin: 6px 0 0;
  font-family: var(--label);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brass);
}
.news__powers { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.news__power {
  position: relative;
  padding: 9px 12px 9px 34px;
  border-radius: 6px;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--cream);
  background: rgba(255, 246, 214, 0.05);
  border: 1px solid rgba(232, 178, 58, 0.18);
}
.news__power b { font-family: var(--display); font-weight: 900; letter-spacing: 0.02em; }
.news__power::before {
  content: '';
  position: absolute;
  left: 11px;
  top: 50%;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  transform: translateY(-50%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 1px 3px rgba(0, 0, 0, 0.5);
}
.news__power--gold::before    { background: linear-gradient(135deg, #fbe7a1, #c9932e); }
.news__power--chrome::before  { background: linear-gradient(135deg, #ffffff, #8f98a3 55%, #dfe6ee); }
.news__power--crystal::before { background: linear-gradient(135deg, #e8fbff, #6fc3d9 55%, #b9eaf5); }
.news__power--opal::before    { background: linear-gradient(135deg, #f6e6ff, #9fd8c8 45%, #f7c8de); }
.news #news-ok { margin-top: 6px; }


/* --- the refusal stamp -----------------------------------------------------------------
   Red ink, slammed on the print at an angle. It has to be missable by nobody. */
.print__stamp {
  position: absolute;
  left: 50%;
  top: 46%;
  z-index: 6;
  padding: 0.28em 0.55em;
  border: 4px solid #b4402a;
  border-radius: 4px;
  font-family: var(--display);
  font-size: clamp(22px, 7.5vw, 34px);
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #b4402a;
  background: rgba(255, 246, 236, 0.18);
  mix-blend-mode: multiply;
  transform: translate(-50%, -50%) rotate(-12deg);
  pointer-events: none;
}
.stage.is-refused .print__stamp { animation: stamp-slam 0.32s cubic-bezier(0.2, 1.4, 0.4, 1) both; }
@keyframes stamp-slam {
  0%   { opacity: 0; transform: translate(-50%, -50%) rotate(-24deg) scale(2.4); }
  60%  { opacity: 1; transform: translate(-50%, -50%) rotate(-10deg) scale(0.96); }
  100% { opacity: 1; transform: translate(-50%, -50%) rotate(-12deg) scale(1); }
}
/* No jolt on the print itself: its position IS a transform, and animating it sent the
   print to the corner of the screen on a real phone. The stamp does the moving. */


/* The hint under the camera in the label face, not the receipt's thermal type. */
.hint { font-family: var(--label); font-size: 15px; letter-spacing: 0.01em; }

/* The stamp fits the print: two lines if it must, never off the paper. */
.print__stamp {
  white-space: normal;
  text-align: center;
  max-width: 86%;
  line-height: 0.95;
  font-size: clamp(18px, 6vw, 30px);
}


/* --- the parcel ---------------------------------------------------------------------
   A kraft box under a spotlight, tied with twine. Real faces — front, back, side, lid —
   at fixed dimensions so the geometry holds, scaled for small screens. It rocks while
   it waits and knocks from inside. Unwrapped, the twine snaps, the lid blows off its
   hinge, light pours out, the screen flashes and the card rises out of the box under a
   storm of paper and foil. No images. */
.parcel {
  position: fixed;
  inset: 0;
  z-index: 515;
  display: grid;
  grid-template-rows: 1fr auto auto;
  justify-items: center;
  align-items: center;
  gap: 16px;
  padding: 24px 18px calc(28px + env(safe-area-inset-bottom, 0px));
  background: rgba(6, 5, 4, 0.94);
  touch-action: none;
}
.parcel::after {
  /* the flash on the pop */
  content: '';
  position: absolute;
  inset: 0;
  background: #fff6dc;
  opacity: 0;
  pointer-events: none;
}
.parcel__spot {
  position: absolute;
  left: 50%;
  top: -10%;
  width: 140vw;
  height: 80vh;
  transform: translateX(-50%);
  background: radial-gradient(45% 60% at 50% 20%, rgba(255, 224, 150, 0.22), rgba(255, 210, 120, 0.06) 45%, transparent 70%);
  pointer-events: none;
}
.parcel__scene {
  --w: 220px; --h: 170px; --d: 80px;
  position: relative;
  width: var(--w);
  height: 400px;
  perspective: 1100px;
  perspective-origin: 50% 30%;
}
@media (max-width: 360px) { .parcel__scene { transform: scale(0.86); } }
.parcel__shadow {
  position: absolute;
  left: -10%;
  right: -10%;
  bottom: 18px;
  height: 36px;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(0, 0, 0, 0.8), transparent 72%);
  animation: parcel-shadow 3.4s ease-in-out infinite;
}
.parcel__box {
  position: absolute;
  left: 0;
  top: 190px;
  width: var(--w);
  height: var(--h);
  transform-style: preserve-3d;
  transform: rotateX(-14deg) rotateY(24deg);
  animation: parcel-rock 3.4s ease-in-out infinite;
}
.parcel__face, .parcel__lid {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--w);
  height: var(--h);
  border-radius: 3px;
  background:
    repeating-linear-gradient(112deg, rgba(0, 0, 0, 0.045) 0 2px, transparent 2px 7px),
    repeating-linear-gradient(28deg, rgba(255, 255, 255, 0.035) 0 1px, transparent 1px 5px),
    linear-gradient(165deg, #cfa273, #b4855a 55%, #9a6c44);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), inset 0 -10px 18px rgba(0, 0, 0, 0.18);
  backface-visibility: hidden;
}
.parcel__face--front { transform: translateZ(calc(var(--d) / 2)); }
.parcel__face--back { transform: rotateY(180deg) translateZ(calc(var(--d) / 2)); filter: brightness(0.5); }
.parcel__face--side {
  width: var(--d);
  left: calc(var(--w) - var(--d) / 2);
  transform-origin: 50% 50%;
  transform: translateX(calc(var(--d) / -2)) rotateY(90deg) translateZ(calc(var(--d) / 2));
  filter: brightness(0.74);
}
.parcel__face--side::after {
  /* a twine line runs round the side too */
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: calc(50% - 4px);
  height: 8px;
  background: linear-gradient(180deg, #2f2113, #6b4d2c 40%, #8a6a44 50%, #6b4d2c 60%, #2f2113);
}
/* the lid: the top face, hinged at its back edge */
.parcel__lid {
  height: var(--d);
  top: calc(var(--d) / -2);
  transform-origin: 50% 0%;
  transform: translateY(calc(var(--d) / 2)) rotateX(90deg) translateZ(calc(var(--d) / 2));
  background:
    repeating-linear-gradient(112deg, rgba(0, 0, 0, 0.04) 0 2px, transparent 2px 7px),
    linear-gradient(180deg, #e2b98a, #c99a68);
  transform-style: preserve-3d;
  backface-visibility: visible;
}
.parcel__twine {
  position: absolute;
  background: linear-gradient(90deg, #2f2113, #6b4d2c 40%, #8a6a44 50%, #6b4d2c 60%, #2f2113);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}
.parcel__twine--a { left: calc(50% - 4px); top: -2px; bottom: -2px; width: 8px; }
.parcel__twine--b { top: calc(50% - 4px); left: -2px; right: -2px; height: 8px; background: linear-gradient(180deg, #2f2113, #6b4d2c 40%, #8a6a44 50%, #6b4d2c 60%, #2f2113); }
.parcel__twine--front { left: calc(50% - 4px); top: 0; height: var(--h); width: 8px; transform: translateZ(calc(var(--d) / 2 + 1px)); }
.parcel__knot { position: absolute; left: 50%; top: 50%; width: 0; height: 0; transform: translateZ(3px); }
.parcel__knot i {
  position: absolute;
  width: 34px;
  height: 18px;
  border: 5px solid #5a3f22;
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  top: -9px;
}
.parcel__knot i:first-child { left: -36px; transform: rotate(-12deg); }
.parcel__knot i:last-child { left: 2px; transform: rotate(12deg); }
.parcel__label {
  position: absolute;
  left: 9%;
  bottom: 12%;
  width: 66%;
  padding: 9px 10px 9px;
  display: grid;
  gap: 2px;
  background: var(--paper);
  color: var(--ink);
  border-radius: 2px;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.45);
  transform: rotate(-3deg) translateZ(1px);
  font-family: var(--label);
}
.parcel__label::before, .parcel__label::after {
  content: '';
  position: absolute;
  top: -9px;
  width: 30px;
  height: 12px;
  background: rgba(255, 246, 214, 0.55);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.parcel__label::before { left: -14px; transform: rotate(-38deg); }
.parcel__label::after { right: -14px; transform: rotate(38deg); }
.parcel__k { font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase; color: #6b5a3d; }
.parcel__label b { font-family: var(--display); font-size: 18px; font-weight: 900; text-transform: uppercase; letter-spacing: 0.02em; }
.parcel__from { font-size: 11px; color: #4a3d2a; }
@keyframes parcel-rock {
  0%, 100% { transform: rotateX(-14deg) rotateY(24deg) rotateZ(-1deg); }
  40% { transform: rotateX(-14deg) rotateY(20deg) rotateZ(1.2deg); }
  44% { transform: rotateX(-14deg) rotateY(20deg) rotateZ(1.2deg) translateY(-8px); }
  48% { transform: rotateX(-14deg) rotateY(21deg) rotateZ(-0.6deg); }
  52% { transform: rotateX(-14deg) rotateY(22deg) rotateZ(0.4deg) translateY(-3px); }
  56% { transform: rotateX(-14deg) rotateY(23deg) rotateZ(-0.2deg); }
}
@keyframes parcel-shadow {
  0%, 100% { transform: scale(1); opacity: 0.9; }
  44% { transform: scale(0.82); opacity: 0.6; }
  48% { transform: scale(1); opacity: 0.9; }
}
.parcel__line { margin: 0; max-width: 34ch; text-align: center; font-size: 16px; line-height: 1.4; color: var(--cream); text-wrap: balance; }
.parcel__row { display: grid; gap: 10px; justify-items: center; }

/* what comes out */
.parcel__beam {
  position: absolute;
  left: 6%;
  right: 6%;
  bottom: 30%;
  height: 220%;
  transform: translateZ(calc(var(--d) / -4)) scaleY(0);
  transform-origin: 50% 100%;
  background: linear-gradient(to top, rgba(255, 240, 190, 1), rgba(255, 220, 130, 0.7) 30%, rgba(255, 210, 120, 0.25) 60%, transparent 85%);
  mix-blend-mode: screen;
  filter: blur(2px);
  pointer-events: none;
}
.parcel__rays {
  position: absolute;
  left: 50%;
  top: 66%;
  width: 200vmax;
  height: 200vmax;
  transform: translate(-50%, -50%) scale(0);
  background: conic-gradient(from 0deg, rgba(255, 220, 130, 0) 0deg, rgba(255, 220, 130, 0.34) 7deg, rgba(255, 220, 130, 0) 15deg, rgba(255, 220, 130, 0.26) 27deg, rgba(255, 220, 130, 0) 36deg, rgba(255, 220, 130, 0.3) 48deg, rgba(255, 220, 130, 0) 58deg);
  -webkit-mask-image: radial-gradient(circle, #000 0%, rgba(0, 0, 0, 0.6) 25%, transparent 55%);
  mask-image: radial-gradient(circle, #000 0%, rgba(0, 0, 0, 0.6) 25%, transparent 55%);
  pointer-events: none;
}
.parcel__slot {
  position: absolute;
  left: 8%;
  right: 8%;
  top: -220px;
  height: calc(var(--h) + 220px);
  display: grid;
  place-items: end center;
  transform: translateZ(calc(var(--d) / -4));
  clip-path: inset(66% 0 0 0);
  pointer-events: none;
}
.parcel__slot .card { transform: translateY(60%) scale(0.72); opacity: 0; }

/* the beats */
.parcel.is-shaking .parcel__box { animation: parcel-shake 0.7s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; }
@keyframes parcel-shake {
  10%, 90% { transform: rotateX(-14deg) rotateY(24deg) translate3d(-2px, 0, 0) rotateZ(-2deg); }
  20%, 80% { transform: rotateX(-14deg) rotateY(24deg) translate3d(4px, -2px, 0) rotateZ(2deg); }
  30%, 50%, 70% { transform: rotateX(-14deg) rotateY(24deg) translate3d(-9px, -4px, 0) rotateZ(-5deg); }
  40%, 60% { transform: rotateX(-14deg) rotateY(24deg) translate3d(9px, -6px, 0) rotateZ(5deg); }
}
.parcel.is-snapped .parcel__twine--a, .parcel.is-snapped .parcel__twine--front, .parcel.is-snapped .parcel__face--side::after { animation: twine-snap-v 0.26s ease-in both; }
.parcel.is-snapped .parcel__twine--b { animation: twine-snap-h 0.26s ease-in both; }
.parcel.is-snapped .parcel__knot { animation: knot-fly 0.5s ease-out both; }
@keyframes twine-snap-v { to { transform: scaleY(0); opacity: 0; } }
@keyframes twine-snap-h { to { transform: scaleX(0); opacity: 0; } }
@keyframes knot-fly { to { transform: translate(40px, -140px) rotate(200deg) translateZ(3px); opacity: 0; } }
.parcel.is-open .parcel__box { animation: parcel-open-rock 1.6s ease-out both; }
@keyframes parcel-open-rock {
  0% { transform: rotateX(-14deg) rotateY(24deg) translateY(0); }
  12% { transform: rotateX(-8deg) rotateY(24deg) translateY(12px); }
  100% { transform: rotateX(-14deg) rotateY(24deg) translateY(0); }
}
.parcel.is-open .parcel__lid { animation: lid-blow 1.2s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
@keyframes lid-blow {
  0% { transform: translateY(calc(var(--d) / 2)) rotateX(90deg) translateZ(calc(var(--d) / 2)); }
  30% { transform: translateY(calc(var(--d) / 2 - 30px)) rotateX(150deg) translateZ(calc(var(--d) / 2)); }
  100% { transform: translateY(-320px) rotateX(240deg) rotateZ(30deg) translateZ(120px); opacity: 0; }
}
.parcel.is-open::after { animation: pop-flash 0.5s ease-out 0.05s both; }
@keyframes pop-flash { 0% { opacity: 0; } 12% { opacity: 0.9; } 100% { opacity: 0; } }
.parcel.is-open .parcel__beam { animation: beam-up 1.4s cubic-bezier(0.2, 0.8, 0.3, 1) both; }
@keyframes beam-up { 0% { transform: translateZ(calc(var(--d) / -4)) scaleY(0); opacity: 0; } 25% { opacity: 1; } 100% { transform: translateZ(calc(var(--d) / -4)) scaleY(1); opacity: 0.9; } }
.parcel.is-open .parcel__rays { animation: rays-in 0.8s ease-out both, rays-spin 9s linear 0.8s infinite; }
@keyframes rays-in { to { transform: translate(-50%, -50%) scale(1); } }
@keyframes rays-spin { from { transform: translate(-50%, -50%) scale(1) rotate(0); } to { transform: translate(-50%, -50%) scale(1) rotate(360deg); } }
.parcel.is-open .parcel__slot { animation: slot-release 1.3s cubic-bezier(0.2, 0.8, 0.3, 1) 0.25s both; }
@keyframes slot-release { to { clip-path: inset(-20% 0 0 0); } }
.parcel.is-open .parcel__slot .card { animation: card-rise 1.4s cubic-bezier(0.2, 0.9, 0.25, 1) 0.25s both; }
@keyframes card-rise {
  0% { transform: translateY(60%) scale(0.72); opacity: 0; }
  20% { opacity: 1; }
  100% { transform: translateY(-44%) scale(1.1); opacity: 1; }
}
.parcel.is-open .parcel__shadow { animation: shadow-flash 1.2s ease-out both; }
@keyframes shadow-flash { 20% { transform: scale(1.25); opacity: 0.3; } 100% { transform: scale(1); opacity: 0.9; } }
.parcel__burst { position: absolute; left: 50%; top: 68%; width: 0; height: 0; pointer-events: none; transform-style: preserve-3d; }
.parcel__burst i {
  position: absolute;
  left: calc(var(--w, 8px) / -2);
  top: calc(var(--h, 14px) / -2);
  width: var(--w, 8px);
  height: var(--h, 14px);
  border-radius: 1px;
  opacity: 0;
}
.parcel__burst i.is-paper { background: var(--paper); }
.parcel__burst i.is-foil { background: linear-gradient(135deg, #fff7d6, #f3d27a 40%, #b8862b); }
.parcel__burst i.is-brass { background: linear-gradient(135deg, #e8b23a, #8d6038); }
.parcel.is-open .parcel__burst i { animation: confetti-3d 1.6s cubic-bezier(0.15, 0.7, 0.35, 1) both; animation-delay: calc(0.1s + var(--delay, 0ms)); }
@keyframes confetti-3d {
  0% { opacity: 1; transform: translate3d(0, 0, 0) rotateX(0) rotateY(0); }
  55% { opacity: 1; }
  100% { opacity: 0; transform: translate3d(var(--dx), calc(var(--dy) + 340px), 60px) rotateX(var(--rx)) rotateY(var(--ry)); }
}
@media (prefers-reduced-motion: reduce) {
  .parcel__box, .parcel__shadow, .parcel.is-shaking .parcel__box, .parcel.is-open .parcel__box, .parcel.is-open .parcel__rays, .parcel.is-open .parcel__burst i, .parcel.is-open::after { animation: none; }
  .parcel.is-open .parcel__lid { animation: none; opacity: 0; }
  .parcel.is-open .parcel__slot { clip-path: inset(-20% 0 0 0); }
  .parcel.is-open .parcel__slot .card { animation: none; transform: translateY(-44%) scale(1.1); opacity: 1; }
}


/* the note that came with it */
.parcel__note {
  margin: 4px 0 0;
  font-family: var(--receipt);
  font-size: 15px;
  line-height: 1.15;
  color: #2b2418;
  font-style: italic;
}
.parcel__note:empty { display: none; }

/* a present says so, for ever: a ribbon across the top of the photograph */
.card__present {
  position: absolute;
  left: -0.4em;
  top: 0.9em;
  z-index: 9;
  padding: 0.18em 0.6em 0.22em;
  font-family: var(--label);
  font-size: 0.5em;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #fff6e6;
  background: linear-gradient(180deg, #c8402a, #8f2a1c);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
  transform: rotate(-6deg);
  white-space: nowrap;
  max-width: 90%;
  overflow: hidden;
  text-overflow: ellipsis;
}


/* --- the parcel, front-on -----------------------------------------------------------
   The angled box read as skew on a phone. Front-on, with the depth suggested by the lid
   flap along the top edge and the shading, not by perspective on the box itself. The
   flap still swings up on its hinge when it opens; everything else stays. */
.parcel__box { transform: none; }
.parcel__face--front { transform: none; }
.parcel__face--back, .parcel__face--side { display: none; }
.parcel__lid {
  height: 24px;
  top: -24px;
  left: 3px;
  width: calc(var(--w) - 6px);
  border-radius: 3px 3px 0 0;
  transform: none;
  transform-origin: 50% 100%;
  overflow: visible;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), inset 0 -4px 6px rgba(0, 0, 0, 0.12);
}
.parcel__twine--a { top: -2px; bottom: auto; height: calc(var(--h) + 28px); }
.parcel__twine--b { top: calc(24px + var(--h) / 2 - 4px); left: -5px; right: -5px; }
.parcel__knot { top: calc(24px + var(--h) / 2); transform: none; }
.parcel__twine--front { display: none; }
@keyframes parcel-rock {
  0%, 100% { transform: rotate(-1.2deg); }
  40% { transform: rotate(1.4deg); }
  44% { transform: rotate(1.4deg) translateY(-8px); }
  48% { transform: rotate(-0.6deg); }
  52% { transform: rotate(0.5deg) translateY(-3px); }
  56% { transform: rotate(-0.2deg); }
}
@keyframes parcel-shake {
  10%, 90% { transform: translate3d(-2px, 0, 0) rotate(-2deg); }
  20%, 80% { transform: translate3d(4px, -2px, 0) rotate(2deg); }
  30%, 50%, 70% { transform: translate3d(-9px, -4px, 0) rotate(-5deg); }
  40%, 60% { transform: translate3d(9px, -6px, 0) rotate(5deg); }
}
@keyframes parcel-open-rock {
  0% { transform: translateY(0); }
  12% { transform: translateY(12px) rotate(0.6deg); }
  100% { transform: translateY(0); }
}
@keyframes lid-blow {
  0% { transform: rotateX(0); }
  30% { transform: rotateX(-75deg); }
  100% { transform: translateY(-320px) rotateX(-170deg) rotateZ(22deg); opacity: 0; }
}
.parcel__beam { transform: scaleY(0); }
@keyframes beam-up { 0% { transform: scaleY(0); opacity: 0; } 25% { opacity: 1; } 100% { transform: scaleY(1); opacity: 0.9; } }
.parcel__slot { transform: none; }


/* --- sending a present ---------------------------------------------------------------- */
.giftbox {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 14px 6px;
  padding: 8px 12px;
  border-radius: 4px;
  color: #fff6e6;
  background: linear-gradient(90deg, #c8402a, #8f2a1c);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 4px 10px rgba(0, 0, 0, 0.45);
  font-family: var(--label);
}
.giftbox__k { font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; opacity: 0.85; }
.giftbox b { font-family: var(--display); font-size: 16px; font-weight: 900; letter-spacing: 0.02em; flex: 1; }
.giftbox__x { background: none; border: 0; color: #fff6e6; font: inherit; font-size: 12px; text-decoration: underline; cursor: pointer; padding: 4px 0; }
.locker__row.is-picked { background: rgba(232, 178, 58, 0.14); }
.locker__row.is-picked::after { content: '✓'; font-size: 18px; }


/* goodwill, under the record: what generosity has made of you */
.tb-goodwill { color: var(--brass); }


/* --- presents & swaps on the table ------------------------------------------------------
   The urgent block was capped at a fifth of the table and faded out, and each row was
   three lines in three faces. Rows are one line of what, one of who, a status pill on
   the right, and a mark that says present or swap. */
.tb-urgent {
  max-height: 38%;
  -webkit-mask-image: none;
  mask-image: none;
  gap: 8px;
}
.swaps { display: grid; gap: 6px; list-style: none; margin: 0; padding: 0; }
.swap-row {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  grid-template-areas: "mark what at" "mark who at";
  column-gap: 10px;
  row-gap: 2px;
  align-items: center;
  padding: 9px 10px 9px 8px;
  border-radius: 6px;
  border-left: 0;
  background: linear-gradient(180deg, rgba(255, 246, 214, 0.07), rgba(255, 246, 214, 0.03));
  border: 1px solid rgba(232, 178, 58, 0.22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.swap-row::before {
  grid-area: mark;
  content: '⇄';
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 15px;
  color: var(--brass);
  background: rgba(232, 178, 58, 0.12);
}
.swap-row--gift::before { content: '🎁'; font-size: 14px; background: rgba(200, 64, 42, 0.18); }
.swap-row__what {
  grid-area: what;
  font-family: var(--display);
  font-size: 14px;
  font-weight: 800;
  letter-spacing: 0.01em;
  color: var(--cream);
  line-height: 1.2;
}
.swap-row__who {
  grid-area: who;
  font-family: var(--label);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.swap-row__at {
  grid-area: at;
  justify-self: end;
  max-width: 34vw;
  padding: 3px 8px 4px;
  border-radius: 999px;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-align: center;
  color: var(--cream-2);
  background: rgba(0, 0, 0, 0.38);
  line-height: 1.2;
}
.swap-row--parked .swap-row__at { color: var(--brass); }


/* --- no enemies yet: a door, not a dead end --------------------------------------------- */
.tb-empty {
  flex: 1 1 100%;
  min-width: 0;
  display: grid;
  gap: 6px;
  padding: 12px 14px 13px;
  border-radius: 8px;
  background: linear-gradient(180deg, rgba(255, 246, 214, 0.07), rgba(255, 246, 214, 0.03));
  border: 1px solid rgba(232, 178, 58, 0.22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  transform: none;
}
.tb-empty__t { font-family: var(--display); font-size: 15px; font-weight: 900; color: var(--cream); }
.tb-empty__s { font-family: var(--label); font-size: 12px; line-height: 1.4; color: var(--muted); }
.tb-empty__row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.tb-foelist > .tb-empty:nth-child(even), .tb-foelist > .tb-empty:nth-child(odd) { transform: none; }


/* --- the roster's filters, and what a tile tells you -------------------------------- */
.sift__flags { margin-top: -2px; }
.sift__q {
  width: 100%;
  padding: 9px 12px;
  border-radius: 6px;
  border: 1px solid rgba(232, 178, 58, 0.22);
  background: rgba(0, 0, 0, 0.32);
  color: var(--cream);
  font-family: var(--label);
  font-size: 14px;
}
.sift__q::placeholder { color: var(--muted); }
.card__wait {
  position: absolute;
  left: 0.5em;
  bottom: 0.55em;
  z-index: 9;
  padding: 0.16em 0.5em 0.2em;
  border-radius: 999px;
  font-family: var(--label);
  font-size: 0.5em;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fff0e6;
  background: #b4402a;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
.card__beats {
  position: absolute;
  right: 0.4em;
  top: 3.1em;
  z-index: 9;
  padding: 0.14em 0.45em 0.18em;
  border-radius: 2px;
  font-family: var(--label);
  font-size: 0.48em;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #2a1c06;
  background: var(--brass);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}

/* --- the builder: filters and intel --------------------------------------------------- */
.build-sift { margin: 4px 0 8px; }
.intel {
  display: grid;
  gap: 6px;
  margin: 2px 0 8px;
  padding: 10px 12px 11px;
  border-radius: 6px;
  background: rgba(200, 64, 42, 0.1);
  border: 1px solid rgba(200, 64, 42, 0.35);
}
.intel__k { margin: 0; font-family: var(--label); font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: #e08a78; }
.intel__rows { display: grid; gap: 4px; }
.intel__row { display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: baseline; font-size: 13px; color: var(--cream-2); }
.intel__type { font-family: var(--display); font-weight: 900; font-size: 13px; color: var(--cream); }
.intel__name { font-family: var(--label); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.intel__mark { font-style: normal; color: var(--muted); }
.intel__row.is-covered .intel__mark { color: var(--brass); font-weight: 900; }
.intel__gap { margin: 2px 0 0; font-family: var(--label); font-size: 12px; color: var(--brass); }


/* --- the roster as a gallery ---------------------------------------------------------
   Photographs with eyes, three across, a name and nothing else. Tier is the colour of
   the frame edge, shiny is a glow, wounded is a bar along the foot, a present has its
   ribbon corner. The words live on the card, one tap away. */
/* the grid is cards again */
.tile {
  --edge: var(--t-common, #8a8474);
  display: grid;
  gap: 5px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--cream);
  font: inherit;
  font-size: 11px;
  text-align: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tile:active .tile__frame { transform: scale(0.97); }
.tile__frame {
  position: relative;
  height: auto;
  aspect-ratio: 1;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: inset 0 0 0 2px var(--edge), 0 6px 14px rgba(0, 0, 0, 0.5);
  background: #17140f;
  transition: transform 0.12s ease;
}
.tile__photo { position: absolute; inset: 0; }
.tile__photo img { width: 100%; height: 100%; object-fit: cover; display: block; filter: contrast(1.1) saturate(1.05); }
.tile__name {
  font-family: var(--label);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.2;
  color: var(--cream-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 2px;
}
.tile--tier-Common { --edge: var(--t-common); }
.tile--tier-Uncommon { --edge: var(--t-uncommon); }
.tile--tier-Rare { --edge: var(--t-rare); }
.tile--tier-Legendary { --edge: var(--t-legendary); }
.tile--tier-Withheld { --edge: #444; }
.tile--shiny .tile__frame {
  box-shadow: inset 0 0 0 2px #f3d27a, 0 0 0.9em rgba(255, 210, 100, 0.7), 0 6px 14px rgba(0, 0, 0, 0.5);
  animation: shiny-halo 2.6s ease-in-out infinite;
}
.tile--shiny .tile__frame::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 38%, rgba(255, 250, 225, 0.55) 48%, rgba(255, 225, 140, 0.6) 52%, transparent 62%);
  background-size: 260% 100%;
  mix-blend-mode: screen;
  animation: shiny-gleam 3.4s cubic-bezier(0.6, 0, 0.4, 1) infinite;
  pointer-events: none;
}
.tile__hurt { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; background: rgba(0, 0, 0, 0.55); }
.tile__hurt::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: var(--left, 50%); background: #b4402a; }
.tile--present .tile__frame::before {
  content: '';
  position: absolute;
  top: 0; right: 0;
  width: 0; height: 0;
  border-style: solid;
  border-width: 0 22px 22px 0;
  border-color: transparent #c8402a transparent transparent;
  z-index: 3;
}
.sift__chip-mark { display: inline-grid; place-items: center; width: 18px; height: 18px; }
.sift__chip-mark svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.sift__chip { padding: 7px 9px; }
.acct-warn { font-family: var(--label); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; }


/* --- the builder's captions: under the card, never on it --------------------------- */
.pick { display: grid; gap: 4px; align-content: start; }
.pick__cap {
  font-family: var(--label);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  color: var(--brass);
  min-height: 1.2em;
}
.pick__cap.is-hurt { color: #e08a78; }
.card__beats, .card__wait { display: none; }

/* the builder's tools: filters left, order right */
.build-tools { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; margin: 4px 0 8px; }
.build-tools .build-sift { margin: 0; }
.build-order { max-width: 44vw; padding: 7px 9px; font-family: var(--label); font-size: 12px; }


/* --- the alpha stamp, so nobody misses it -------------------------------------------
   Red ink, thick, twice the size, inked unevenly like a real stamp. It is a warning
   and it should look like one. */
.stamp-alpha {
  margin-left: 12px;
  padding: 3px 9px 4px;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 900;
  letter-spacing: 0.14em;
  color: #d8402a;
  border: 3px solid #d8402a;
  border-radius: 3px;
  transform: rotate(-8deg) translateY(-2px);
  opacity: 1;
  box-shadow: inset 0 0 0 1px rgba(216, 64, 42, 0.35);
  background:
    repeating-linear-gradient(115deg, rgba(255, 255, 255, 0.12) 0 2px, transparent 2px 5px);
  -webkit-mask-image: radial-gradient(120% 90% at 40% 50%, #000 60%, rgba(0, 0, 0, 0.75) 100%);
  mask-image: radial-gradient(120% 90% at 40% 50%, #000 60%, rgba(0, 0, 0, 0.75) 100%);
  text-shadow: 0 0 1px rgba(216, 64, 42, 0.6);
}

/* the builder's top is the facing, the count and the three type rows — no essays */
#build-advice, #teamread-risk, #teamread-power { display: none !important; }


/* --- the alpha sash -------------------------------------------------------------------
   A band of barrier tape pinned across the top-right corner of every screen. Fixed, so it
   is there whatever is on show; taps go straight through it. */
.stamp-alpha {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 18px);
  right: -44px;
  z-index: 66;
  width: 170px;
  margin: 0;
  padding: 5px 0 6px;
  border: 0;
  border-top: 3px solid transparent;
  border-bottom: 3px solid transparent;
  border-radius: 0;
  font-family: var(--display);
  font-size: 13px;
  font-weight: 900;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  text-align: center;
  text-indent: 0.32em;
  color: #fff3e2;
  background:
    linear-gradient(#c8402a, #c8402a) padding-box,
    repeating-linear-gradient(135deg, #1a1612 0 6px, #e8b23a 6px 12px) border-box;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.55);
  transform: rotate(45deg);
  transform-origin: 50% 50%;
  opacity: 1;
  -webkit-mask-image: none;
  mask-image: none;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);
  pointer-events: none;
  /* The badge this grew out of blended with `screen`, which over the dark table looked
     like ink and over a sheet of pale paper looked like nothing at all. The sash is a
     sticker on the glass: it is opaque wherever it lands. */
  mix-blend-mode: normal;
}

/* the builder's top is the facing and the count. That is all. */
#teamread { display: none !important; }

/* The wordmark is not an in-app header: every tab has its own. It lives on the manual and
   the sheets, and the builder no longer shows it alone. */
.topbar { display: none; }

/* --- the run, as a loyalty card ---
   Seven circles on the paper, stamped in ink as the days go, the seventh ringed in brass
   with a star: the coffee-shop card, which everybody already knows how to read. */
.loyal { display: grid; gap: 6px; justify-items: start; margin: 6px 0 2px; }
.loyal__row { display: flex; gap: 7px; align-items: center; }
.loyal__cell {
  position: relative;
  width: 30px;
  height: 30px;
  display: grid;
  place-content: center;
  border-radius: 50%;
  border: 1.5px dashed rgba(23, 21, 15, 0.38);
  color: transparent;
}
.loyal__cell svg { width: 16px; height: 16px; display: block; }
/* Inked, and never quite square to the paper. */
.loyal__cell.is-stamped {
  border: 0;
  background: #17150f;
  color: var(--paper, #e7e0cd);
  box-shadow: inset 0 0 0 2px var(--paper, #e7e0cd), inset 0 0 0 3.5px #17150f;
  transform: rotate(-8deg);
}
.loyal__cell:nth-child(2n).is-stamped { transform: rotate(6deg); }
.loyal__cell:nth-child(3n).is-stamped { transform: rotate(-3deg) scale(1.04); }
/* Today's, still to do. */
.loyal__cell.is-next { border-style: solid; border-color: #17150f; animation: loyal-next 1.6s ease-in-out infinite; }
@keyframes loyal-next {
  0%, 100% { box-shadow: 0 0 0 0 rgba(23, 21, 15, 0.3); }
  50% { box-shadow: 0 0 0 6px rgba(23, 21, 15, 0); }
}
/* The seventh: the star shows faintly as the promise, and fills in brass when kept. */
.loyal__cell--shiny { border-color: var(--brass-deep); color: var(--brass-deep); }
.loyal__cell--shiny svg { opacity: 0.55; }
.loyal__cell--shiny.is-stamped {
  color: #2a1f08;
  background: radial-gradient(circle at 34% 28%, var(--brass-pale) 0 22%, var(--brass) 55%, var(--brass-deep) 100%);
  box-shadow: 0 0 14px rgba(232, 178, 58, 0.55), inset 0 0.06em 0 rgba(255, 255, 255, 0.5);
}
.loyal__cell--shiny.is-stamped svg { opacity: 1; }
.loyal__cap {
  font-family: var(--label);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.72;
}
.loyal__cap b { font-weight: 700; }
.loyal__best { font-style: normal; margin-left: 8px; opacity: 0.6; }

/* On the camera strip: seven dots, no reading. */
.loyal--mini { margin: 0; flex: 0 0 auto; display: block; }
.loyal__row--mini { gap: 3px; }
.loyal__row--mini .loyal__cell {
  width: 7px;
  height: 7px;
  border-width: 1px;
  transform: none !important;
  box-shadow: none !important;
  animation: none;
}
.loyal__row--mini .loyal__cell--shiny { width: 9px; height: 9px; }
.loyal__row--mini .loyal__cell.is-stamped { background: #17150f; }
.loyal__row--mini .loyal__cell--shiny.is-stamped { background: var(--brass); }
@media (prefers-reduced-motion: reduce) { .loyal__cell.is-next { animation: none; } }

/* Which Find Me this card answered: a strip of tape along the foot of the photograph.
   The answer is the joke, and the joke is the card. */
.card__findme {
  position: absolute;
  left: 50%;
  bottom: 0.35em;
  z-index: 9;
  max-width: 92%;
  padding: 0.16em 0.55em 0.2em;
  font-family: var(--label);
  font-size: 0.5em;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #17140f;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  box-shadow: 0 0.15em 0.3em rgba(0, 0, 0, 0.45);
  transform: translateX(-50%) rotate(-2deg);
}
.card__findme::before { content: 'Find me · '; opacity: 0.55; }

/* --- opal ---------------------------------------------------------------------------
   The rare one: one shiny in ten. The other three are a colour; opal is play of colour —
   patches of green, pink, blue and orange fire that move as the card moves, the way a real
   opal does. Nothing about it is one hue, so nothing about it is drawn with one. */
.card--finish-opal {
  --foil-a: #fffaff;
  --foil-b: #f3c8ff;
  --foil-c: #79d9c9;
  --foil-glow: rgba(255, 190, 230, 0.75);
  --foil-ink: #3a1f47;
  animation: opal-breathe 5s ease-in-out infinite;
}
@keyframes opal-breathe {
  0%, 100% { filter: drop-shadow(0 0 0.5em rgba(255, 110, 205, 0.85)) drop-shadow(0 0 1.2em rgba(70, 225, 200, 0.5)); }
  50% { filter: drop-shadow(0 0 0.6em rgba(70, 225, 200, 0.85)) drop-shadow(0 0 1.3em rgba(255, 110, 205, 0.55)); }
}
/* The ring is the whole spectrum in patches, and turns twice as fast. */
.card--finish-opal .card__edge {
  background: conic-gradient(
    from calc(var(--spin) + var(--gx, 0) * 200deg + var(--gy, 0) * 120deg),
    #fff7fb, #8ff0d2 8%, #fff7fb 15%, #ff9ad6 24%, #fff7fb 31%, #7fc8ff 40%, #fff7fb 46%,
    #ffb56b 55%, #fff7fb 62%, #b48cff 72%, #fff7fb 79%, #8ff0d2 88%, #fff7fb);
  filter: saturate(1.5) brightness(1.12) drop-shadow(0 0 0.6em rgba(255, 190, 240, 0.7));
  animation: edge-turn 4.5s linear infinite;
}
/* The name plate drifts through the fire rather than sitting in one metal. */
.card--finish-opal .ransom__l {
  background: linear-gradient(120deg, #fff7fb, #b6f5e4 20%, #ffc4e8 40%, #c9e2ff 60%, #ffe0b8 80%, #fff7fb) !important;
  background-size: 300% 100% !important;
  animation: opal-fire 6s ease-in-out infinite;
}
@keyframes opal-fire {
  0%, 100% { background-position: 0% 0; }
  50% { background-position: 100% 0; }
}
/* The seal too. */
.card--shiny.card--finish-opal .card__shiny {
  background: conic-gradient(from 200deg, #fff7fb, #b6f5e4, #ffc4e8, #c9e2ff, #ffe0b8, #fff7fb);
  animation: opal-seal 7s linear infinite;
}
@keyframes opal-seal { to { filter: hue-rotate(360deg); } }
/* And the window's edge moves through the colours with it. */
.card--shiny.card--finish-opal .card__frame { animation: opal-frame 8s ease-in-out infinite; }
@keyframes opal-frame {
  0%, 100% { box-shadow: inset 0 0 0 0.12em #ffc4e8, inset 0 0 1.4em rgba(255, 190, 230, 0.7), inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5); }
  33% { box-shadow: inset 0 0 0 0.12em #b6f5e4, inset 0 0 1.4em rgba(140, 240, 210, 0.7), inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5); }
  66% { box-shadow: inset 0 0 0 0.12em #c9e2ff, inset 0 0 1.4em rgba(150, 200, 255, 0.7), inset 0 0.4em 0.8em rgba(0, 0, 0, 0.5); }
}
@media (prefers-reduced-motion: reduce) {
  .card--finish-opal,
  .card--finish-opal .card__edge,
  .card--finish-opal .ransom__l,
  .card--finish-opal .card__shiny,
  .card--finish-opal .card__frame { animation: none; }
}

/* --- errands ------------------------------------------------------------------------
   Gary at the door, and his jobs board. The idle half of the game. */
/* Four tabs now. The tape has to shrink a touch to fit a phone. */


.view--errands {
  padding: 16px max(16px, env(safe-area-inset-right, 0px)) 24px
           max(16px, env(safe-area-inset-left, 0px));
}
.gary {
  display: grid;
  grid-template-columns: 82px 1fr;
  gap: 12px;
  align-items: end;
  margin: 4px 0 14px;
}
.gary__face { width: 82px; height: 90px; filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5)); }
.gary__bubble {
  position: relative;
  padding: 10px 13px 11px;
  background: var(--paper, #e7e0cd);
  background-image: var(--grain);
  background-blend-mode: multiply;
  color: #17140f;
  border-radius: 3px;
  transform: rotate(-0.6deg);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
}
.gary__bubble::before {
  content: '';
  position: absolute;
  left: -9px;
  bottom: 22px;
  border: 9px solid transparent;
  border-right-color: var(--paper, #e7e0cd);
  border-left: 0;
}
.gary__name { font-family: var(--display); font-weight: 900; font-size: 17px; text-transform: uppercase; }
.gary__who { margin-left: 6px; font-family: var(--label); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.6; }
.gary__says { margin: 4px 0 0; font-family: var(--receipt); font-size: 17px; line-height: 1.15; }
.gary__says--sheet { color: #ddd2b8; font-size: 18px; margin: 6px 0 10px; }

.errands-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 14px 0 6px; }
.errands-head h2 {
  margin: 0;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 900;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #e6dcc2;
}
.errands-head__s { font-family: var(--label); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.errands { display: grid; gap: 8px; }
.errands__empty { font-size: 15px; margin: 2px 0 0; }

/* A brief: a job card on paper. */
.errand {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 10px;
  padding: 10px 12px 11px;
  background: var(--paper, #e7e0cd);
  background-image: var(--grain);
  background-blend-mode: multiply;
  color: #17140f;
  border-radius: 2px;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);
}
.errand:nth-child(even) { transform: rotate(0.4deg); }
.errand:nth-child(odd) { transform: rotate(-0.3deg); }
.errand__name { grid-column: 1; font-family: var(--display); font-weight: 900; font-size: 16px; text-transform: uppercase; letter-spacing: -0.01em; }
.errand__line { grid-column: 1; margin: 0; font-family: var(--receipt); font-size: 15px; line-height: 1.1; color: #4a4336; }
.errand__meta { grid-column: 1; display: flex; flex-wrap: wrap; gap: 5px 10px; align-items: center; margin-top: 3px; font-family: var(--label); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #5a5245; }
.errand__meta .mk { display: inline-block; width: 1.2em; height: 1.2em; vertical-align: -0.25em; margin-right: 0.2em; }
.errand__meta .mk svg { width: 100%; height: 100%; }
.errand__risk--some { color: #8a5a10; }
.errand__risk--real { color: var(--ink-red); }
.errand__pay {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: start;
  padding: 4px 9px 5px;
  background: #17140f;
  color: var(--paper, #e7e0cd);
  font-family: var(--label);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  transform: rotate(2deg);
}
.errand__pay--stat { background: #2d5a3a; }
.errand__pay--rank { background: #5a2d7a; }
.errand__pay--ticket { background: linear-gradient(180deg, var(--brass), var(--brass-deep)); color: #2a1f08; }
.errand__go { grid-column: 2; grid-row: 3; align-self: end; justify-self: end; }
.errand.is-taken { opacity: 0.55; }
.errand.is-taken .errand__go { visibility: hidden; }
.errand__done { grid-column: 2; grid-row: 3; align-self: end; font-family: var(--label); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: #3f6f45; }

/* Somebody out: the same paper, the card's name where the job was. */
.errand--out .errand__name { font-size: 15px; }
.errand__clock { grid-column: 2; grid-row: 1 / span 2; align-self: start; font-family: var(--receipt); font-size: 18px; text-align: right; color: #4a4336; }
.errand--back .errand__clock { color: #3f6f45; font-family: var(--label); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 700; }
.errand__recall { grid-column: 1 / -1; grid-row: auto; justify-self: start; margin-top: 8px; font-family: var(--receipt); font-size: 14px; color: var(--ink-red); }
.errand--back { box-shadow: 0 0 0 2px #3f6f45, 0 3px 10px rgba(0, 0, 0, 0.45); }

/* A card that is out, on the roster. */
.card--away { opacity: 0.6; filter: saturate(0.6); }
.card__away {
  position: absolute;
  left: 50%;
  top: 45%;
  z-index: 12;
  padding: 0.2em 0.7em 0.24em;
  font-family: var(--label);
  font-size: 0.6em;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #fff6e6;
  background: var(--ink-red);
  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.5);
  transform: translate(-50%, -50%) rotate(-8deg);
}

/* The receipt. */
.receipt-lines { display: grid; gap: 6px; padding: 10px 12px; background: #fdfcf7; color: #23201a; font-family: var(--receipt); font-size: 16px; line-height: 1.15; }
.receipt-lines p { margin: 0; }
.receipt-pay { margin: 12px 0 0; font-family: var(--display); font-weight: 900; font-size: 17px; text-transform: uppercase; color: #e6dcc2; }
.receipt-pay.is-bad { color: #e08a78; }

/* The briefing: whoever is asking, in their own words, then the terms. */
.brief__who { margin: 2px 0 0; display: flex; align-items: baseline; gap: 8px; }
.brief__who b { font-family: var(--display); font-weight: 900; font-size: 18px; text-transform: uppercase; color: #e6dcc2; }
.brief__who span { font-family: var(--label); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.brief__pitch {
  margin: 8px 0 0;
  padding: 12px 14px 13px;
  background: var(--paper, #e7e0cd);
  background-image: var(--grain);
  background-blend-mode: multiply;
  color: #17140f;
  border-radius: 3px;
  transform: rotate(-0.4deg);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
  font-family: var(--receipt);
  font-size: 18px;
  line-height: 1.16;
}
.brief__terms { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin: 14px 0 16px; }
.brief__terms dt { font-family: var(--label); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); align-self: baseline; }
.brief__terms dd { margin: 0; font-family: var(--display); font-weight: 800; font-size: 14px; text-transform: uppercase; color: #e6dcc2; }
.brief__terms dd .mk { display: inline-block; width: 1.1em; height: 1.1em; vertical-align: -0.2em; margin-right: 0.3em; }
.brief__terms dd .mk svg { width: 100%; height: 100%; }
.brief__terms dd.is-warm { color: #e0b070; }
.brief__terms dd.is-hot { color: #e08a78; }
/* The whole job is the way in now, so it has to look like one. */
.errand--open { cursor: pointer; text-align: left; border: 0; font: inherit; width: 100%; }
.errand--open:active { transform: scale(0.995); }
.errand__more { grid-column: 2; grid-row: 3; align-self: end; justify-self: end; font-family: var(--label); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: #5a5245; }

.brief__who-for { display: block; margin-top: 2px; font-family: var(--label); font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }

/* The running commentary on somebody who is out, and how far through they are. */
.errand__beat { grid-column: 1 / -1; margin: 5px 0 0; font-family: var(--receipt); font-size: 16px; line-height: 1.12; color: #3c3529; }
.errand__bar { grid-column: 1 / -1; height: 4px; margin-top: 7px; background: rgba(23, 20, 15, 0.16); border-radius: 2px; overflow: hidden; }
.errand__bar i { display: block; height: 100%; background: #17140f; transition: width 0.6s ease-out; }
.errand--back .errand__bar i { background: #3f6f45; }

/* Health, while they are out. The number you are deciding on, so it reads before the
   job's own progress does. */
.errand__hp {
  grid-column: 1 / -1;
  position: relative;
  display: block;
  height: 15px;
  margin-top: 8px;
  background: rgba(23, 20, 15, 0.16);
  border-radius: 2px;
  overflow: hidden;
}
.errand__hp i { display: block; height: 100%; background: linear-gradient(180deg, #4e8c52, #3f6f45); transition: width 0.6s ease-out; }
.errand__hp.is-low i { background: linear-gradient(180deg, #c8513a, #a33323); }
.errand__hp b {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  font-family: var(--label);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: #fff6e6;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
.errand__bar { margin-top: 5px; height: 3px; }

/* He has been waiting to be asked. */
.gary__more { margin-top: 5px; font-family: var(--receipt); font-size: 14px; color: var(--ink-red); }
#gary-sheet .brief__pitch p { margin: 0 0 9px; }
#gary-sheet .brief__pitch p:last-child { margin-bottom: 0; }

/* Somebody out on a job: the card, with its health under it, then what it is doing. */
.errand--out { grid-template-columns: auto 1fr; align-items: start; gap: 4px 12px; --em-grid: 5.6px; }
.errand__who { grid-column: 1; grid-row: 1 / span 9; display: grid; gap: 6px; justify-items: center; }
.errand__who .card { margin: 0; cursor: default; }
.errand__what { grid-column: 2; display: grid; gap: 4px; align-content: start; }
.errand--out .errand__name { grid-column: auto; }
.errand--out .errand__clock { grid-column: auto; grid-row: auto; text-align: left; }
.errand--out .errand__beat,
.errand--out .errand__bar,
.errand--out .errand__recall,
.errand--out .errand__go { grid-column: auto; grid-row: auto; justify-self: start; margin: 0; }
.errand--out .errand__hp { grid-column: auto; margin: 0; width: 100%; }
/* A job nobody on the roster can take. Shown, not hidden: it is a reason to go out. */
.errand.is-locked { opacity: 0.62; }
.errand__done--locked { color: var(--ink-red); }
.errand__needs { color: #7a4038; font-weight: 700; }

/* A crew out on a job: one card and one bar each, side by side. */
.errand__who--crew { grid-auto-flow: column; gap: 8px; align-items: start; }
.errand__member { display: grid; gap: 6px; justify-items: center; }
.errand__crew-n { color: #5a2d7a; font-weight: 700; }
.brief__slot { display: block; margin-top: 3px; font-family: var(--label); font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.brief__slot b { color: #e6dcc2; }
@media (max-width: 420px) { .errand--out { --em-grid: 4.4px; } }

.errand__member--gone { align-self: center; text-align: center; padding: 10px 8px; }
.errand__gone { display: block; margin-top: 4px; font-family: var(--label); font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-red); }

/* --- scrap --- */
.scrap-chip {
  margin: 0;
  padding: 4px 10px 5px;
  font-family: var(--label);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #2a1f08;
  background: linear-gradient(180deg, var(--brass-pale), var(--brass) 60%, var(--brass-deep));
  border-radius: 2px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
  transform: rotate(-1deg);
  white-space: nowrap;
}
.scrap-chip b { font-family: var(--display); font-weight: 900; font-size: 15px; font-variant-numeric: tabular-nums; }
.scrap-chip span { opacity: 0.7; margin-left: 4px; }
.welcome__n { margin: 10px 0 0; font-family: var(--label); font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase; color: #e6dcc2; }
.welcome__n b { display: block; font-family: var(--display); font-size: 56px; line-height: 1; font-weight: 900; color: var(--brass); font-variant-numeric: tabular-nums; }
.welcome__s { margin: 6px 0 0; font-family: var(--receipt); font-size: 16px; color: var(--muted); }
#reveal-level:not([disabled]) { border-color: var(--brass); color: var(--brass-pale); }

.scrap-chip__k { font-weight: 700; letter-spacing: 0.14em; opacity: 0.75; margin-right: 2px; }

/* --- the wad: a banknote, not a badge --- */
.wad {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin: 2px 0 4px;
  padding: 10px 14px 10px 10px;
  border: 2px solid #2f5a2b;
  border-radius: 4px;
  color: #173515;
  font: inherit;
  text-align: left;
  cursor: pointer;
  background-color: #cfe6c0;
  background-image:
    radial-gradient(circle at 85% 50%, rgba(255, 255, 255, 0.35), transparent 45%),
    repeating-linear-gradient(135deg, rgba(47, 90, 43, 0.09) 0 1px, transparent 1px 5px),
    repeating-linear-gradient(45deg, rgba(47, 90, 43, 0.06) 0 1px, transparent 1px 7px);
  box-shadow: inset 0 0 0 3px #cfe6c0, inset 0 0 0 4px rgba(47, 90, 43, 0.45), 0 3px 0 #2f5a2b, 0 6px 14px rgba(0, 0, 0, 0.45);
  transform: rotate(-0.4deg);
}
.wad:active { transform: rotate(-0.4deg) translateY(2px); box-shadow: inset 0 0 0 3px #cfe6c0, inset 0 0 0 4px rgba(47, 90, 43, 0.45), 0 1px 0 #2f5a2b, 0 3px 8px rgba(0, 0, 0, 0.4); }
.wad__icon { width: 52px; height: 36px; display: block; filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3)); }
.wad__body { display: grid; line-height: 1; }
.wad__k { font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: #2f5a2b; }
.wad__n { margin-top: 3px; font-family: var(--display); font-weight: 900; font-size: 30px; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.wad__side { display: grid; justify-items: end; line-height: 1.1; }
.wad__side b { font-family: var(--display); font-weight: 900; font-size: 15px; color: #2f5a2b; }
.wad__hint { font-family: var(--label); font-size: 10.5px; letter-spacing: 0.04em; color: #3e5f3a; }
@media (max-width: 380px) { .wad__hint { display: none; } .wad__n { font-size: 26px; } }

.wad-intro__art { display: grid; place-items: center; margin: 6px 0 2px; }
.wad-intro__icon { width: 120px; height: 80px; filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.5)); transform: rotate(-4deg); }
.wad-intro__how { margin: 10px 0 12px; padding: 0; list-style: none; display: grid; gap: 8px; }
.wad-intro__how li { padding: 9px 12px; background: rgba(207, 230, 192, 0.1); border-left: 3px solid #9fc48e; font-family: var(--label); font-size: 14px; line-height: 1.35; color: #e6dcc2; }
.wad-intro__how b { color: #cfe6c0; }
/* The old corner chip style is no longer used by anything. */

/* How to switch notifications back on. */
.fix-steps { margin: 12px 0 10px; padding-left: 0; list-style: none; counter-reset: step; display: grid; gap: 8px; }
.fix-steps li {
  counter-increment: step;
  position: relative;
  padding: 10px 12px 10px 44px;
  background: rgba(255, 246, 214, 0.06);
  border-radius: 3px;
  font-family: var(--label);
  font-size: 15px;
  line-height: 1.35;
  color: #e6dcc2;
}
.fix-steps li::before {
  content: counter(step);
  position: absolute;
  left: 10px;
  top: 9px;
  width: 24px;
  height: 24px;
  display: grid;
  place-content: center;
  border-radius: 50%;
  background: var(--brass);
  color: #2a1f08;
  font-family: var(--display);
  font-weight: 900;
  font-size: 13px;
}
.fix-result { margin: 0 0 10px; font-family: var(--label); font-size: 14px; color: #e08a78; }

/* --- the estate ----------------------------------------------------------------------
   A map taped to the table. Pins where the jobs are, cards standing where they have been
   sent. The list is still there behind "See it as a list". */
.view--errands:not(.is-list) #errands-jobs-head,
.view--errands:not(.is-list) #errands-board { display: none; }
.view--errands.is-list .estate__sheet { display: none; }

.estate { margin: 2px 0 10px; }
.estate__sheet {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  max-height: 72vh;
  margin: 0 auto;
  border-radius: 2px;
  overflow: hidden;
  background: #efe6cf;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.25);
  transform: rotate(-0.6deg);
}
.estate__draw, .estate__pins { position: absolute; inset: 0; }
.estate__map { width: 100%; height: 100%; display: block; }
.estate__label {
  font-family: var(--display);
  font-weight: 900;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-anchor: middle;
  fill: #2b241b;
  paint-order: stroke;
  stroke: #efe6cf;
  stroke-width: 3px;
}
.estate__smoke { animation: smoke 4s ease-in-out infinite; transform-origin: 202px 272px; }
@keyframes smoke { 0%, 100% { opacity: 0.35; transform: translateY(0); } 50% { opacity: 0.8; transform: translateY(-4px); } }
.estate__tape {
  position: absolute;
  top: -6px;
  width: 78px;
  height: 22px;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  opacity: 0.92;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.25);
  z-index: 3;
}
.estate__tape--l { left: 10px; transform: rotate(-9deg); }
.estate__tape--r { right: 12px; transform: rotate(7deg); }
.estate__key { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.estate__legend { display: flex; align-items: center; gap: 4px 6px; flex-wrap: wrap; font-family: var(--label); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.estate__legend .k { width: 10px; height: 10px; border-radius: 50%; border: 1.5px solid #17140f; margin-left: 4px; }

/* Colour is the length of the job. */
.k--errand, .pin--errand .pin__head { background: #4e8c52; }
.k--job, .pin--job .pin__head { background: #2f6fa0; }
.k--shift, .pin--shift .pin__head { background: #c8402a; }
.k--night, .pin--night .pin__head { background: #2b2346; }
.k--crew, .pin--crew .pin__head { background: linear-gradient(180deg, var(--brass-pale), var(--brass-deep)); color: #2a1f08; }

/* A pin: a round head on a point, standing on the place. */
.pin {
  position: absolute;
  z-index: 2;
  display: grid;
  justify-items: center;
  gap: 3px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
  transform: translate(-50%, -100%) translate(var(--dx, 0), var(--dy, 0));
  animation: pin-drop 0.5s cubic-bezier(0.2, 1.4, 0.4, 1) both;
  animation-delay: calc(var(--i, 0) * 70ms);
}
@keyframes pin-drop { from { opacity: 0; transform: translate(-50%, -100%) translate(var(--dx, 0), calc(var(--dy, 0) - 22px)); } }
.pin__head {
  position: relative;
  width: 34px;
  height: 34px;
  display: grid;
  place-content: center;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  border: 2px solid #17140f;
  color: #fff6e6;
  font-family: var(--display);
  font-weight: 900;
  font-size: 11px;
  box-shadow: 2px 3px 0 rgba(0, 0, 0, 0.35);
}
/* The head is turned to make the point; what is written in it is turned back. */
.pin__head > span { display: block; transform: rotate(45deg); line-height: 1; }
.pin__head svg { width: 14px; height: 14px; display: block; }
.pin__name {
  max-width: 84px;
  padding: 2px 6px 3px;
  background: #fffaf0;
  border: 1.5px solid #17140f;
  font-family: var(--label);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.1;
  color: #17140f;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: 1px 2px 0 rgba(0, 0, 0, 0.3);
  transform: rotate(-2deg);
}
.pin:active .pin__head { transform: rotate(-45deg) scale(0.92); }
.pin.is-locked .pin__head { background: #6f675a; color: #efe6cf; }
.pin.is-locked .pin__name { opacity: 0.75; }
.pin.is-taken { opacity: 0.45; cursor: default; }
.pin.is-taken .pin__head { background: #8a8170; }

/* A card out on a job: its photo in a disc, googly eyes, health round the rim. */
.token {
  position: absolute;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
  transform: translate(-50%, -50%) translate(var(--dx, 0), calc(var(--dy, 0) + 18px));
  animation: token-bob 2.6s ease-in-out infinite;
}
@keyframes token-bob { 50% { transform: translate(-50%, -50%) translate(var(--dx, 0), calc(var(--dy, 0) + 14px)); } }
.token__face {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background-color: #cfc3a6;
  background-size: cover;
  background-position: center;
  box-shadow:
    0 0 0 3px #fffaf0,
    0 0 0 6px color-mix(in srgb, var(--hpc) 100%, transparent),
    0 4px 8px 6px rgba(0, 0, 0, 0.3);
  -webkit-mask: none;
}
/* The health ring: the rim is only as full as they are. */
.token__face::after {
  content: '';
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  background: conic-gradient(var(--hpc) calc(var(--hp, 1) * 360deg), rgba(23, 20, 15, 0.55) 0);
  -webkit-mask: radial-gradient(circle, transparent 27px, #000 27.5px);
  mask: radial-gradient(circle, transparent 27px, #000 27.5px);
  z-index: -1;
}
.token__face { box-shadow: 0 0 0 3px #fffaf0, 0 4px 10px rgba(0, 0, 0, 0.4); }
.token__face--blank { background: #2b241b; }
.token__eye {
  position: absolute;
  top: 9px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: radial-gradient(circle at 55% 62%, #17140f 0 3.2px, #fff 3.6px);
  border: 1px solid #17140f;
}
.token__eye:first-child { left: 9px; }
.token__eye:last-child { right: 9px; }
.token__tag {
  max-width: 84px;
  margin-top: 6px;
  padding: 1px 5px 2px;
  background: #17140f;
  color: #fffaf0;
  font-family: var(--label);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.token.is-back .token__tag { background: #3f6f45; }
.token.is-back .token__face { animation: token-home 1.4s ease-out infinite; }
@keyframes token-home { 0% { box-shadow: 0 0 0 3px #fffaf0, 0 0 0 3px rgba(78, 140, 82, 0.7); } 100% { box-shadow: 0 0 0 3px #fffaf0, 0 0 0 18px rgba(78, 140, 82, 0); } }
.token.is-walking { opacity: 0.8; }

@media (max-width: 400px) {
  .pin__head { width: 30px; height: 30px; }
  .pin__name { max-width: 78px; font-size: 10px; }
}
@media (prefers-reduced-motion: reduce) {
  .pin, .token, .token.is-back .token__face, .estate__smoke { animation: none; }
}

/* The alpha sash is gone: it was intrusive, and the game says what it is well enough. */
.stamp-alpha { display: none !important; }

/* --- tills: a note hanging over each card, filling --- */
/* The roster's cards let their till hang above them: paint containment would clip it. */
.roster-cell { display: grid; justify-items: center; align-content: start; }
.till {
  position: relative;
  z-index: 5;
  margin-bottom: -7px;
  display: grid;
  place-items: center;
  min-width: 58px;
  height: 26px;
  padding: 0 10px;
  border: 2px solid #2f5a2b;
  border-radius: 3px;
  background: #cfe6c0;
  color: #173515;
  font: inherit;
  cursor: pointer;
  overflow: hidden;
  transform: rotate(-3deg);
  box-shadow: 0 2px 0 #2f5a2b, 0 4px 8px rgba(0, 0, 0, 0.45);
  animation: till-hang 3s ease-in-out infinite;
}
.till.is-empty { visibility: hidden; animation: none; }
@keyframes till-hang { 50% { transform: rotate(2deg); } }
.till b { position: relative; font-family: var(--display); font-weight: 900; font-size: 13px; font-variant-numeric: tabular-nums; }
/* How full it is, as a band rising behind the number. */
.till__fill { position: absolute; left: 0; bottom: 0; width: 100%; height: calc(var(--fill, 0) * 100%); background: #9fc48e; transition: height 1s linear; }
.till.is-full { background: #f3d9a4; border-color: #8a5a10; box-shadow: 0 2px 0 #8a5a10, 0 4px 8px rgba(0, 0, 0, 0.45); animation: till-full 0.9s ease-in-out infinite; }
.till.is-full .till__fill { display: none; }
.till.is-full b { color: #6b3f06; }
@keyframes till-full { 0%, 100% { transform: rotate(-5deg) scale(1); } 50% { transform: rotate(5deg) scale(1.07); } }
.till:active { transform: scale(0.92); }

/* The wad has something waiting: it says so. */
.wad.has-waiting .wad__side b { color: #173515; }
.wad.has-waiting { animation: wad-ready 2.4s ease-in-out infinite; }
@keyframes wad-ready { 50% { box-shadow: inset 0 0 0 3px #cfe6c0, inset 0 0 0 4px rgba(47, 90, 43, 0.45), 0 3px 0 #2f5a2b, 0 6px 20px rgba(159, 196, 142, 0.55); } }

/* A note flying into the wad. */
.wad-fly {
  position: fixed;
  z-index: 200;
  padding: 3px 9px;
  border: 2px solid #2f5a2b;
  border-radius: 3px;
  background: #cfe6c0;
  color: #173515;
  font-family: var(--display);
  font-weight: 900;
  font-size: 14px;
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: wad-fly 0.7s cubic-bezier(0.5, 0, 0.3, 1) forwards;
}
@keyframes wad-fly {
  0% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  30% { transform: translate(-50%, calc(-50% - 24px)) scale(1.15); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) scale(0.5); opacity: 0.2; }
}
.wad__n.is-bumped { animation: wad-bump 0.35s cubic-bezier(0.3, 1.6, 0.5, 1); display: inline-block; }
@keyframes wad-bump { 40% { transform: scale(1.22); color: #2f5a2b; } }
@media (prefers-reduced-motion: reduce) { .till, .till.is-full, .wad.has-waiting { animation: none; } }

/* Fired up after a fight. */
.till.is-hot { background: #ffd89a; border-color: #b0541a; box-shadow: 0 2px 0 #b0541a, 0 0 12px rgba(255, 150, 60, 0.6); }
.till.is-hot .till__fill { background: #ffb35c; }
.till.is-hot b { color: #6b2a06; }

.till.is-away { background: #d9d2c2; border-color: #6f675a; box-shadow: 0 2px 0 #6f675a; animation: none; filter: saturate(0.4); }
.till.is-away .till__fill { background: #c2b9a5; }
.till.is-away b { color: #4a4336; font-size: 11px; }


/* --- the card screen's buttons stay in reach ------------------------------------------
   Level up, Bulk, Brag and Recruit sat under the whole field-notes sheet, a full screen
   below the card. Pinned to the bottom of the scroll, they are there whatever is open. */
.view--reveal .reveal-actions {
  position: sticky;
  bottom: -14px;
  z-index: 5;
  max-width: none;
  margin: 0 -16px -14px;
  width: calc(100% + 32px);
  padding: 18px 16px 14px;
  background: linear-gradient(to top, var(--ground) 82%, rgba(18, 16, 14, 0));
  padding-top: 26px;
}
.view--reveal .reveal-actions > * { max-width: 22em; margin-inline: auto; width: 100%; }
.reveal-actions__more { flex-wrap: wrap; row-gap: 0; }
.view--reveal .reveal-actions__more .btn { padding: 6px 10px; font-size: 13px; min-height: 40px; }

/* --- the fan sits in the middle of its room ---------------------------------------------
   Pinned to the floor, a tall phone got a dead band above the three and their feet under
   the arena button. The fan's full splay is about 27em tall with 2.7em of it below the
   middle card's foot, so this centres the whole shape in whatever height the hand gets.
   The outer two also tuck in further so their corners stay on the screen. */
.tb-hand .card { bottom: max(18px, calc((100cqh - 27 * var(--em-duel)) / 2 + 1.5 * var(--em-duel))); }
.tb-hand .card:nth-child(1) { transform: translateX(-50%) rotate(-15deg) translateX(52px); }
.tb-hand .card:nth-child(3) { transform: translateX(-50%) rotate(15deg) translateX(-52px); }

/* Names wrap instead of stopping at "Something wi…": the cheek is in the whole name. */
.pin__name,
.pin__name:where(*) {
  max-width: 92px;
  white-space: normal;
  text-align: center;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  text-wrap: balance;
}

/* --- the roster's header, in three rows rather than five ---------------------------------
   Title, wad, then one row of types with the order at its end. The cards started a
   third of the way down the phone. "Not backed up" said what the Sign in button beside
   the title already says, so the button carries it as a red dot instead. */
.acct-warn { display: none !important; }
#acct-open { position: relative; }
.deck-head:has(~ .acct-warn:not([hidden])) #acct-open::after {
  content: "";
  position: absolute;
  top: 9px;
  right: 3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink-red);
}
.sift { grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; }
.sift__sortlabel {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
}
.sift__select {
  flex: 0 0 auto;
  width: 7.2em;
  min-height: 40px;
  padding: 6px 6px;
  font-size: 15px;
}


/* --- the shutter in the middle of the bar ----------------------------------------------
   Roster, Errands, the camera, Deploy, and the sound toggle in the corner. The camera is
   a real button: a black body, a steel ring, an iris that opens when you are on it. */
.tabs {
  display: grid;
  grid-template-columns: 1fr 1fr 86px 1fr;
  align-items: end;
  gap: 6px;
  padding-right: 58px;
}
.tab { font-size: clamp(11px, 3.25vw, 17px); letter-spacing: -0.02em; padding-inline: 2px; }
.tab--shutter {
  position: relative;
  align-self: stretch;
  display: grid;
  justify-items: center;
  align-content: end;
  gap: 2px;
  padding: 0 0 2px;
  background: none !important;
  box-shadow: none !important;
  clip-path: none;
  filter: none;
  opacity: 1 !important;
  color: var(--cream);
  transform: none !important;
}
.tab--shutter .tab__label {
  font-family: var(--label);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: #d9cfb8;
  transform: none !important;
}
.tab--shutter.is-active .tab__label { color: var(--brass); }
.shutter-btn {
  position: relative;
  width: 70px;
  height: 70px;
  margin-top: calc(-1 * var(--shutter-rise) - 4px);
  border-radius: 50%;
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at 35% 28%, rgba(255, 255, 255, 0.35), transparent 38%),
    conic-gradient(from 200deg, #6b6f72, #d9dcdd, #75797c, #c3c6c8, #5d6164, #b9bcbe, #6b6f72);
  box-shadow:
    0 0 0 3px #0e0c0a,
    0 8px 18px rgba(0, 0, 0, 0.7),
    inset 0 -3px 6px rgba(0, 0, 0, 0.45);
  transition: transform 0.14s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.shutter-btn::before {
  content: "";
  position: absolute;
  inset: 7px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, #2a2622, #0b0907 70%);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.9);
}
/* Six blades, drawn as a conic fan, round a red dot: the record light. */
.shutter-btn__iris {
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background:
    radial-gradient(circle, #d8322a 0 5px, #6a120e 6px, transparent 7px),
    repeating-conic-gradient(from 12deg, #3b3631 0 30deg, #24201c 30deg 60deg);
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, 0.08), 0 0 0 1px #000;
  transition: transform 0.4s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.tab--shutter:active .shutter-btn { transform: scale(0.93); }
.tab--shutter.is-active .shutter-btn { box-shadow: 0 0 0 3px var(--brass), 0 8px 22px rgba(232, 178, 58, 0.35), inset 0 -3px 6px rgba(0, 0, 0, 0.45); }
.tab--shutter.is-active .shutter-btn__iris { transform: rotate(60deg) scale(1.08); }
.tab--shutter .tab__badge { top: -30px; right: calc(50% - 40px); }
@media (prefers-reduced-motion: reduce) { .shutter-btn, .shutter-btn__iris { transition: none; } }

/* --- sheets are paper ------------------------------------------------------------------
   The notes, briefs, receipts and sign-in were plain dark panels, the one thing in the
   game that looked like any other app. Now they are a page torn off a pad and slid
   across the table: cream stock, faint rules, a red margin, a strip of tape holding it.
   Most of what sits on them is drawn in tokens, so the tokens turn to ink here and the
   handful of hard-coded creams are put right below. */
.sheet--paper .sheet__inner {
  --cream: #1d1a14;
  --cream-2: #2e2920;
  --muted: #6d6352;
  --line: rgba(40, 30, 15, 0.2);
  --panel-2: #fffaf0;
  --brass: #9a6a10;
  position: relative;
  color: #1d1a14;
  background-color: #f3ecda;
  background-image:
    linear-gradient(90deg, transparent 30px, rgba(192, 40, 30, 0.22) 30px 31px, transparent 31px),
    linear-gradient(180deg, rgba(255, 255, 255, 0.35), transparent 40%);
  border: 0;
  border-radius: 0;
  padding: 26px 18px calc(18px + env(safe-area-inset-bottom)) 42px;
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.55);
  /* The top edge is torn off the pad. */
  clip-path: polygon(0 10px, 4% 4px, 9% 9px, 15% 2px, 22% 8px, 29% 3px, 36% 9px, 43% 4px, 50% 8px, 57% 2px, 64% 7px, 71% 3px, 78% 9px, 85% 4px, 92% 8px, 100% 3px, 100% 100%, 0 100%);
}
.sheet--paper .sheet__head h2 { color: #17140f; }
.sheet--paper .btn--ghost { color: #2b261d; text-decoration-color: rgba(43, 38, 29, 0.45); }
.sheet--paper .btn--primary { box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.5), 0 3px 5px rgba(60, 40, 10, 0.35); }
.sheet--paper .btn:not(.btn--primary):not(.btn--ghost) { box-shadow: 0 2px 4px rgba(60, 40, 10, 0.3); }
.sheet--paper :is(.gary__says--sheet, .receipt-pay, .brief__who b, .brief__terms dd, .brief__slot b, .welcome__n, .wad-intro__how li) { color: #1d1a14; }
.sheet--paper .welcome__n b { color: #1d1a14; text-shadow: 2px 2px 0 rgba(227, 173, 47, 0.8); }
.sheet--paper :is(.receipt-pay.is-bad, .brief__terms dd.is-hot, .fix-result) { color: #a8321f; }
.sheet--paper .brief__terms dd.is-warm { color: #9a5c0c; }
.sheet--paper .wad-intro__how li { background: rgba(159, 196, 142, 0.25); border-left-color: #4e8c52; }
.sheet--paper .wad-intro__how b { color: #24461f; }
.sheet--paper .brief__pitch { background: #fffdf6; box-shadow: 1px 2px 0 rgba(60, 40, 10, 0.15); }
.sheet--paper .receipt-lines { box-shadow: 1px 2px 4px rgba(60, 40, 10, 0.2); }
.sheet--paper .fix-steps li { background: rgba(255, 255, 255, 0.45); }
.sheet--paper .acct-field input { background: #fffaf0; color: #1d1a14; border-color: rgba(40, 30, 15, 0.3); }
.sheet--paper .acct-field input::placeholder { color: #9a8f7c; }
.sheet--paper .acct-notify,
.sheet--paper .acct-diag { background: rgba(255, 255, 255, 0.4); border-color: rgba(40, 30, 15, 0.15); }
.sheet--paper .sift__select { background: #fffaf0; color: #1d1a14; border-color: rgba(40, 30, 15, 0.3); }
/* Cards keep their own colours: the ink tokens stop at the card's edge. */
.sheet--paper .card { --cream: #f4efe3; --cream-2: #e7dfcd; --muted: #a29888; --line: #38322d; --panel-2: #262220; --brass: #e8b23a; }

/* --- headings are stencilled, like the side of a skip ---------------------------------
   Everything that is a title of a place or a sheet. Card names stay ransom-note and body
   text stays as it was: the stencil is for the signs, not the talk. */
:root { --stencil: "Big Shoulders Stencil Display", "Arial Narrow", Impact, sans-serif; }
.sheet__head h2,
.deck-head h2,
.view--errands h2,
.play-head,
.receipt-sheet__title,
.spoils__head,
.intro__title,
.race__head,
.tb-go #tb-go-word {
  font-family: var(--stencil);
  font-weight: 900;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.sheet__head h2 { font-size: 28px; line-height: 0.95; letter-spacing: 0.03em; }
.deck-head h2 { font-size: 26px; }
.tb-go #tb-go-word { font-size: 30px; letter-spacing: 0.05em; }

/* --- each place has its own floor --------------------------------------------------------
   Everything sat on the same cutting mat, so the only way to tell the roster from the
   jobs was to read the title. Now: the roster is pinned to a corkboard and Gary's jobs
   are out on the tarmac. Deploy already had its card table; the fight stays on the mat. */
.view--deck.is-active {
  background: radial-gradient(130% 60% at 50% 0%, rgba(255, 214, 150, 0.09), transparent 62%), linear-gradient(180deg, #2a221b, #1a1511);
}
.view--errands.is-active {
  background: radial-gradient(130% 60% at 30% 0%, rgba(255, 245, 225, 0.06), transparent 62%), linear-gradient(180deg, #252624, #191a19);
}


/* The shutter stands 30px proud of the bar, so every screen keeps that much clear at the
   bottom or the last thing on it (Enter the arena, the Done button) sits under the lens. */
:root { --shutter-rise: 30px; }
.view--deck.is-active::after,
.view--errands.is-active::after { content: ""; display: block; height: var(--shutter-rise); }
.tb-mid { margin-bottom: var(--shutter-rise); }
.view--reveal .reveal-actions { padding-bottom: calc(14px + var(--shutter-rise)); }

/* On the camera screen the bar's shutter is the shutter, so the one in the view goes. */
.view--scan.is-active .shoot-row { display: none; }
.tab--shutter.is-active .tab__label { font-size: 0; }
.tab--shutter.is-active .tab__label::after { content: "Shoot"; font-size: 11px; }
.tab--shutter.is-active.is-off .shutter-btn { filter: grayscale(0.8) brightness(0.7); }
.tab--shutter.is-active:not(.is-off) .shutter-btn__iris { animation: shutter-ready 2.2s ease-in-out infinite; }
@keyframes shutter-ready { 50% { transform: rotate(60deg) scale(1.14); } }
@media (prefers-reduced-motion: reduce) { .tab--shutter .shutter-btn__iris { animation: none !important; } }
.view--scan.is-active #scan-hint { margin-bottom: calc(var(--shutter-rise) + 6px); }
.sheet--paper :is(.teacher__pick, .record__row) { background: rgba(255, 255, 255, 0.5); box-shadow: 0 1px 0 rgba(60, 40, 10, 0.12); }
.sheet--paper .teacher__leaves { color: #6a4a9a; }
.sheet--paper .teacher__new { color: #2f6e3a; }
.sheet--paper .locker__row:active,
.sheet--paper .locker__row.is-picked { background: rgba(154, 106, 16, 0.14); }

/* Deploy's header: "Your three" is a sign, not 9px small print, and Change is a strip
   of masking tape like every other thing you press, not a grey box. */
.tb-handbar::before { background: linear-gradient(180deg, rgba(12, 17, 15, 0.7), transparent); }
.tb-handbar__k {
  font-family: var(--stencil);
  font-size: 17px;
  font-weight: 900;
  letter-spacing: 0.08em;
  color: #e6dcc2;
}
.tb-change {
  font-family: var(--display);
  font-size: 13px;
  font-weight: 900;
  text-transform: uppercase;
  color: #16140f;
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  border: 0;
  border-radius: 0;
  padding: 7px 14px 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
  clip-path: polygon(2% 0, 11% 3%, 21% 0, 31% 3%, 98% 1%, 100% 97%, 89% 95%, 79% 100%, 68% 96%, 2% 99%);
  transform: rotate(1.5deg);
}
.tb-change:active { transform: rotate(1.5deg) translateY(1px) scale(0.97); }

/* Anything that pops up over the bar clears the shutter too. The nudge is a slip of
   paper like the sheets, not a dark box. */
.leave-toast { bottom: calc(86px + var(--shutter-rise) + 8px + env(safe-area-inset-bottom, 0px)); }
.nudge {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 86px + var(--shutter-rise) + 8px);
  color: #1d1a14;
  background-color: #efe7d2;
  background-image: var(--fibre);
  background-blend-mode: multiply;
  border: 0;
  border-radius: 0;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.55);
  transform: rotate(-0.6deg);
}
.nudge .btn--ghost { color: #2b261d; text-decoration-color: rgba(43, 38, 29, 0.45); }


/* --- the bar is symmetrical ----------------------------------------------------------
   The sound toggle was fixed in the corner with padding held open for it, so the right
   side of the bar was 58px heavier and the shutter sat left of centre. It is a slot in
   the bar now: two places, the shutter, a place and the toggle, with the outer pairs
   the same width so the lens is dead centre. */
.tabs {
  grid-template-columns: 1fr 1fr 84px 1fr 1fr;
  padding-left: 8px;
  padding-right: 8px;
  gap: 4px;
}
.tabs .sound {
  position: static;
  align-self: center;
  justify-self: center;
  width: 44px;
  height: 44px;
}

/* Less grit over everything: the grain was reading as dirt on a phone screen. */
body::after { opacity: 0.1; }

/* The order is a small button with a sort mark, not a box with a word in it, so the
   type chips get the width. The chosen order still shows when the list opens. */
.sift__select {
  width: 44px;
  min-width: 44px;
  height: 44px;
  padding: 0;
  color: transparent;
  text-indent: -999px;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  border-radius: 4px;
  background: rgba(255, 246, 214, 0.08) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e6dcc2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 4v16M7 20l-3-3M7 20l3-3M17 20V4M17 4l-3 3M17 4l3 3'/%3E%3C/svg%3E") center / 22px no-repeat;
  border: 1px solid rgba(232, 178, 58, 0.35);
}
.sift__select option { color: #1d1a14; text-indent: 0; }
.sift__types {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
  padding-right: 24px;
}

/* Map or list is a small switch under the map, set like the legend beside it, not a
   line of underlined body text. */
#estate-list {
  font-family: var(--label);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #e6dcc2;
  padding: 8px 12px;
  min-height: 36px;
  border: 1px solid rgba(232, 178, 58, 0.4);
  border-radius: 18px;
  text-decoration: none;
}
#estate-list:active { background: rgba(232, 178, 58, 0.12); }

/* The cutting mat, quieter: the heavy 1cm rule stays faint and the fine 2mm rule goes.
   It was reading as texture laid over everything rather than a surface underneath. */
:root { --mat-line: rgba(150, 178, 190, 0.045); }
body {
  background-image:
    radial-gradient(135% 100% at 70% 0%, rgba(214, 236, 245, 0.1) 0%, rgba(214, 236, 245, 0.03) 32%, transparent 54%, rgba(0, 0, 0, 0.5) 100%),
    repeating-linear-gradient(90deg, var(--mat-line) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg, var(--mat-line) 0 1px, transparent 1px 40px),
    linear-gradient(168deg, #262d32 0%, #1d2327 58%, #141a1d 100%);
}

/* --- the bar, rebuilt ------------------------------------------------------------------
   Four strips of tape at four angles plus a floating button read as clutter at the bottom
   of every screen. The bar is one solid piece now: flat places with a mark and a small
   label, lit brass when you are there, and the shutter in the middle as the only object
   that stands up off it. The tape stays where things are stuck on; the bar is not. */
.tabs {
  display: grid;
  grid-template-columns: 1fr 1fr 88px 1fr 1fr;
  align-items: stretch;
  gap: 0;
  padding: 0 6px env(safe-area-inset-bottom, 0px);
  min-height: 62px;
  background: #15120f;
  border-top: 1px solid rgba(255, 240, 210, 0.08);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.45);
}
.tabs .tab:not(.tab--shutter),
.tabs .sound {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 4px;
  min-width: 0;
  width: auto;
  height: auto;
  padding: 8px 2px 9px;
  margin: 0;
  font-family: var(--label);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #8f8676;
  background: none !important;
  background-image: none !important;
  border: 0;
  border-radius: 0;
  box-shadow: none !important;
  clip-path: none !important;
  filter: none !important;
  opacity: 1 !important;
  transform: none !important;
  animation: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tabs .tab .tab__label { transform: none !important; }
.tabs .tab:not(.tab--shutter) .tab__mark {
  width: 24px;
  height: 24px;
  color: inherit;
  filter: none;
  transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.tabs .tab:not(.tab--shutter).is-active { color: #f1e8d2; }
.tabs .tab:not(.tab--shutter).is-active .tab__mark { color: var(--brass); transform: translateY(-1px); }
/* A short brass bar along the top edge marks where you are. */
.tabs .tab:not(.tab--shutter)::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 50%;
  width: 28px;
  height: 3px;
  border-radius: 0 0 3px 3px;
  background: var(--brass);
  transform: translateX(-50%) scaleX(0);
  transition: transform 0.2s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.tabs .tab:not(.tab--shutter).is-active::before { transform: translateX(-50%) scaleX(1); }
.tabs .tab:not(.tab--shutter):active .tab__mark { transform: scale(0.9); }
.tabs .tab--shutter .tab__label { font-size: 11px; letter-spacing: 0.12em; color: #8f8676; }
.tabs .tab--shutter.is-active .tab__label::after { font-size: 11px; }
.tabs .tab--shutter { padding-bottom: 9px; }
:root { --shutter-rise: 26px; }
.shutter-btn { width: 66px; height: 66px; }
.tabs .tab__badge { top: 4px; right: calc(50% - 22px); box-shadow: 0 0 0 2px #15120f; }

/* The sound toggle is a place-shaped slot like the others: a note and a word. */
.tabs .sound { font-size: 11px; }
.tabs .sound > span { font-size: 20px; line-height: 24px; height: 24px; letter-spacing: 0; }
.tabs .sound::before {
  content: "Sound";
  order: 2;
  font-size: 11px;
  letter-spacing: 0.12em;
}
.tabs .sound[aria-pressed="false"]::before { content: "Muted"; }
.tabs .sound[aria-pressed="false"] { color: #5f584c; }
.tabs .sound[aria-pressed="false"]::after { top: calc(50% - 8px); box-shadow: 0 0 0 1.5px #15120f; }
.tabs .sound { align-self: stretch; justify-self: stretch; }
.tabs .sound > span { display: grid; place-items: center; width: 24px; }
.tabs .sound[aria-pressed="false"] .sound__on, .tabs .sound:not([aria-pressed="false"]) .sound__off { display: none; }
.tabs .sound { top: auto; right: auto; bottom: auto; left: auto; z-index: auto; }
#estate-list { margin-left: auto; }

/* --- the fight, polished --------------------------------------------------------------- */
/* Health: a rounded gauge with a sunk track and a tick every tenth, so a hit reads as
   losing notches rather than a flat bar getting shorter. */
.health {
  height: 14px;
  border-radius: 7px;
  background: #0d0f10;
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.7);
}
.health::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(90deg, transparent 0 calc(10% - 1px), rgba(0, 0, 0, 0.35) calc(10% - 1px) 10%);
}
.health__fill {
  border-radius: 7px;
  background: linear-gradient(180deg, #8be08f 0%, #4fb35a 45%, #2f8a3d 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
.health__fill.is-half { background: linear-gradient(180deg, #ffdf7a 0%, #e8b23a 45%, #b07d12 100%); }
.health__fill.is-low { background: linear-gradient(180deg, #ff9b8f 0%, #df4a3a 45%, #a3281b 100%); }
.health__num { z-index: 1; font-family: var(--label); font-weight: 700; font-size: 11px; letter-spacing: 0.06em; }

/* The commentary is a caption strip stuck under the fighters: paper, ink, a bit of tape.
   The verdict colours move from neon-on-black to inks that read on cream. */
.teleprompt {
  margin: 0 10px;
  padding: 12px 30px 12px 16px;
  background-color: #f3ecda;
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.4), transparent 50%);
  border: 0;
  border-radius: 0;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
  transform: rotate(-0.6deg);
}
.teleprompt::before {
  content: "";
  position: absolute;
  top: -8px;
  left: 50%;
  width: 70px;
  height: 16px;
  transform: translateX(-50%) rotate(2deg);
  background-color: var(--tape-cream);
  background-image: var(--fibre);
  background-blend-mode: multiply;
  opacity: 0.9;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.teleprompt__line { color: #1d1a14; }
.teleprompt.is-super .teleprompt__line { color: #1f7a32; }
.teleprompt.is-weak .teleprompt__line { color: #6d6352; }
.teleprompt.is-down .teleprompt__line { color: #b3281b; }
.teleprompt.is-call .teleprompt__line { color: #8a5a06; }
.teleprompt__more { color: #6d6352; }

/* Flee is written, in red, not a grey box asking to be pressed. */
.bail {
  min-height: 36px;
  padding: 6px 8px;
  font-family: var(--label);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #e0806f;
  background: none;
  border: 0;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  text-decoration-color: rgba(224, 128, 111, 0.45);
}

/* The board's mat, as quiet as the rest of the app's. */
.board-ground {
  background-image:
    radial-gradient(125% 85% at 72% 2%, rgba(214, 236, 245, 0.09) 0%, rgba(214, 236, 245, 0.03) 34%, transparent 56%, rgba(0, 0, 0, 0.55) 100%),
    repeating-linear-gradient(90deg, var(--mat-line) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(0deg, var(--mat-line) 0 1px, transparent 1px 40px),
    linear-gradient(168deg, #262d32 0%, #1d2327 58%, #141a1d 100%);
}

/* You are on the left, always; they are on the right. The HUD follows: their name and
   squad top-right over their card, yours bottom-left under yours. */
.hud { align-items: center; }
.hud .hud__vs { order: 3; margin-left: auto; text-align: right; }
.hud .squad--them { order: 4; }
.hud .hud__tape { order: 0; }
.hud .clock { order: 1; }
.hud .bail { order: 2; margin-left: 0; }
.hud .hud__vs { flex: 0 1 auto; min-width: 0; text-align: left; }
.tray__bar { margin-bottom: var(--shutter-rise); }
.over-dock {
  padding-bottom: calc(14px + var(--shutter-rise));
  background: linear-gradient(180deg, rgba(21, 18, 15, 0.92), #15120f 40%);
  border-top: 0;
  box-shadow: 0 -14px 24px rgba(0, 0, 0, 0.45);
  isolation: isolate;
  z-index: 30;
}
.over-dock { bottom: -4px; }
#build-dock { padding-bottom: calc(14px + var(--shutter-rise)); }
/* Next is the way on, so it is the yellow tape like every other way on. */
.manual__go {
  color: #17140c;
  background-color: #e3ad2f;
  background-image: linear-gradient(108deg, transparent 18%, rgba(255, 255, 255, 0.22) 38%, transparent 58%), repeating-linear-gradient(0deg, transparent 0 2px, rgba(0, 0, 0, 0.08) 2px 4px);
  border: 0;
  border-radius: 0;
  font-weight: 900;
  clip-path: polygon(0% 6%, 2% 0%, 5% 5%, 9% 0%, 100% 3%, 98% 94%, 95% 100%, 91% 95%, 5% 98%, 0% 100%);
  box-shadow: 0 3px 5px rgba(60, 40, 10, 0.35);
}
/* The colour key belongs to the map; the list says each job's length in words. And the
   switch stays inside the gutter whichever line it lands on. */
.view--errands.is-list .estate__legend { display: none; }
.estate__key { max-width: 100%; }
#estate-list { flex: 0 0 auto; max-width: 100%; }
.view--errands.is-list .estate__key { margin-top: 0; }
#estate-list { margin-right: 0; transform: none; }

/* --- the screens a second player opens ---------------------------------------------------
   Challenge cards, the arena score and the duel tally used a neon green from an early
   pass that nothing else in the game uses any more; it becomes the same garden green as
   the health gauge and the wad. The challenge card's Go is the yellow tape. */
.duel-card__verdict--won { background: #bfe0c4; color: #1d4427; }
.duel-card__tally b { color: #7fcf8a; }
.arena__side--won .arena__score { color: #0f2a14; background: #8be08f; border-color: #2f8a3d; box-shadow: 0 0 14px rgba(111, 207, 122, 0.35), 0 3px 6px rgba(0, 0, 0, 0.5); }
.arena__win { background: #15120f; color: #8be08f; }
.duel-card__go {
  color: #17140c;
  background-color: #e3ad2f;
  background-image: linear-gradient(108deg, transparent 18%, rgba(255, 255, 255, 0.22) 38%, transparent 58%);
  font-family: var(--display);
  font-weight: 900;
  border-radius: 0;
  clip-path: polygon(0% 6%, 2% 0%, 5% 5%, 9% 0%, 100% 3%, 98% 94%, 95% 100%, 91% 95%, 5% 98%, 0% 100%);
  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.35);
}
.versus__msg b { background: #15120f; }
.chal__run, .chal__when { background: #15120f; }

/* The dark sheets (card pickers, who to fight, swaps, the news) are dark card stock with
   the same torn top as the paper ones, so the two kinds read as one family: paper for
   words, card for cards. */
.sheet:not(.sheet--paper) .sheet__inner {
  background-color: #1d1915;
  background-image: linear-gradient(180deg, rgba(255, 240, 210, 0.05), transparent 120px);
  border: 0;
  border-radius: 0;
  padding-top: 24px;
  box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.6);
  clip-path: polygon(0 10px, 4% 4px, 9% 9px, 15% 2px, 22% 8px, 29% 3px, 36% 9px, 43% 4px, 50% 8px, 57% 2px, 64% 7px, 71% 3px, 78% 9px, 85% 4px, 92% 8px, 100% 3px, 100% 100%, 0 100%);
}
.tabs .tab--shutter.is-active .tab__label { font-size: 0; letter-spacing: 0; }
.tabs .tab--shutter.is-active .tab__label::after { font-size: 11px; letter-spacing: 0.12em; }

/* --- the estate, in its light ------------------------------------------------------------
   The drawing is a daylight illustration; the time of day is a wash over it plus the
   windows and lamps. Dawn is pink and low, dusk is amber going violet, and at night the
   place goes blue and everything with a bulb in it comes on. */
.estate__sheet { background: #8fa862; transform: none; border-radius: 6px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.35); }
.estate__tape { display: none; }
.estate__label { font-size: 9.5px; letter-spacing: 0.08em; stroke: none; }
.est-win { fill: #9cc0cf; transition: fill 0.6s ease; }
.est-lamp__glow { opacity: 0; transition: opacity 0.6s ease; }
.est-tint { fill: transparent; pointer-events: none; mix-blend-mode: multiply; transition: fill 0.6s ease; }
.est-sign rect { transition: fill 0.6s ease; }

.estate__sheet[data-time="dawn"] .est-tint { fill: rgba(240, 170, 170, 0.28); }
.estate__sheet[data-time="dusk"] .est-tint { fill: rgba(214, 120, 70, 0.34); }
.estate__sheet[data-time="dusk"] .est-win { fill: #f3c76a; }
.estate__sheet[data-time="dusk"] .est-lamp__glow { opacity: 0.55; }
.estate__sheet[data-time="night"] .est-tint { fill: rgba(40, 52, 110, 0.62); }
.estate__sheet[data-time="night"] .est-win { fill: #ffd46b; filter: drop-shadow(0 0 3px rgba(255, 210, 110, 0.9)); }
.estate__sheet[data-time="night"] .est-lamp__glow { opacity: 1; mix-blend-mode: screen; }
.estate__sheet[data-time="night"] .est-neon { fill: #3a1d2a; }
.estate__sheet[data-time="night"] .est-sign rect { fill: #e9e0c8; }
/* Windows and lamps are drawn above the wash, so after dark they light it. */

/* Pins stand up off the map: a ring, a hard shadow at the foot. */
.pin__head { box-shadow: 0 0 0 2.5px #fffaf0, 3px 5px 0 rgba(0, 0, 0, 0.35); }
.pin::after {
  content: "";
  position: absolute;
  bottom: -3px;
  left: 50%;
  width: 16px;
  height: 5px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  transform: translateX(-50%);
  z-index: -1;
}
.pin__name { border-radius: 3px; box-shadow: 2px 3px 0 rgba(0, 0, 0, 0.3); }
@media (prefers-reduced-motion: reduce) { .est-lorry { display: none; } }
.pin__need {
  margin-top: -1px;
  padding: 1px 5px 2px;
  font-family: var(--label);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fffaf0;
  background: #6f675a;
  border-radius: 2px;
  white-space: nowrap;
}

/* === polish, batch 1: how the whole thing feels ======================================== */
/* 1. Sheets slide up on a spring, and the room behind dims in. */
.sheet:not([hidden]) { animation: sheet-dim 0.22s ease-out both; }
.sheet:not([hidden]) > .sheet__inner { animation: sheet-up 0.34s cubic-bezier(0.2, 0.95, 0.3, 1.05) both; }
@keyframes sheet-dim { from { background-color: rgba(10, 9, 8, 0); } }
@keyframes sheet-up { from { transform: translateY(40%); opacity: 0.4; } to { transform: none; opacity: 1; } }
/* 2. A grab handle, so a sheet reads as something lifted off the table. */
.sheet__inner::before {
  content: "";
  position: absolute;
  top: 12px;
  left: 50%;
  width: 38px;
  height: 4px;
  border-radius: 2px;
  transform: translateX(-50%);
  background: currentColor;
  opacity: 0.18;
  pointer-events: none;
}
.sheet__inner { position: relative; }
/* 3. The bar's labels brighten on press, so a tap is felt before the screen changes. */
.tabs .tab:not(.tab--shutter):active { color: #f1e8d2; }
/* 8. Selected text in brass. */
::selection { background: rgba(232, 178, 58, 0.45); color: inherit; }
/* 9. Thin scrollbars that belong to the room (desktop). */
* { scrollbar-width: thin; scrollbar-color: rgba(232, 178, 58, 0.35) transparent; }
/* 12. Smoother type on dark. */
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
/* 13. Counters that do not jiggle as they tick. */
.wad__n, #scrap-rate, .till, .build-count, .health__num, .tab__badge, .welcome__n b, #errands-count, .chit { font-variant-numeric: tabular-nums; }
/* 14. Disabled looks disabled and does not pretend to press. */
button:disabled, .btn[disabled] { cursor: not-allowed; }
button:disabled:active, .btn[disabled]:active { transform: none !important; filter: none !important; }
/* 15. One focus ring everywhere a keyboard can land. */
.tabs .tab:focus-visible, .pin:focus-visible, .sift__chip:focus-visible, .till:focus-visible, .wad:focus-visible, .rival:focus-visible, .sift__select:focus-visible, #estate-list:focus-visible {
  outline: 2.5px solid var(--brass);
  outline-offset: 3px;
  border-radius: 4px;
}
/* 17. A badge hops when its number changes. */
.tab__badge.is-bump { animation: badge-bump 0.5s cubic-bezier(0.2, 1.6, 0.4, 1); }
@keyframes badge-bump { 0% { transform: scale(0.6); } 60% { transform: scale(1.25); } 100% { transform: scale(1); } }
/* 18. The shutter catches the light every so often, so the eye finds it. */
.tabs .tab--shutter:not(.is-active) .shutter-btn::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: linear-gradient(115deg, transparent 35%, rgba(255, 255, 255, 0.55) 50%, transparent 65%);
  background-size: 250% 100%;
  background-position: 120% 0;
  animation: shutter-shine 7s ease-in-out 2s infinite;
  pointer-events: none;
}
@keyframes shutter-shine { 0%, 82% { background-position: 120% 0; } 100% { background-position: -120% 0; } }
/* 20. Offline strip, sat on the bar. */
.offline {
  position: fixed;
  left: 50%;
  bottom: calc(76px + var(--shutter-rise) + env(safe-area-inset-bottom, 0px));
  z-index: 66;
  margin: 0;
  padding: 7px 14px;
  transform: translateX(-50%);
  font-family: var(--label);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #fffaf0;
  background: #8f2f22;
  border-radius: 14px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
  white-space: nowrap;
}
/* 19. Motion off means motion off, for all of the above. */
@media (prefers-reduced-motion: reduce) {
  .sheet:not([hidden]), .sheet:not([hidden]) > .sheet__inner, .tab__badge.is-bump, .tabs .tab--shutter .shutter-btn::after { animation: none !important; }
}

/* === polish, batch 2: the roster, the wad, the card ===================================== */
/* 23. A till gives under your thumb. */
.till { transition: transform 0.12s cubic-bezier(0.2, 0.9, 0.3, 1); }
.till:active { transform: scale(0.9) rotate(-2deg); }
/* 24. Levelling up rings the card in gold for a moment. */
.reveal-slot { position: relative; }
.reveal-slot.is-levelled::after {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 14px;
  pointer-events: none;
  box-shadow: 0 0 0 3px rgba(232, 178, 58, 0.9), 0 0 40px 10px rgba(232, 178, 58, 0.55);
  animation: level-ring 1.1s ease-out forwards;
}
@keyframes level-ring { 0% { opacity: 0; transform: scale(0.94); } 20% { opacity: 1; } 100% { opacity: 0; transform: scale(1.06); } }
/* 26. The sign-in dot breathes, so it reads as a nudge rather than a stain. */
.deck-head:has(~ .acct-warn:not([hidden])) #acct-open::after { animation: dot-breathe 2.4s ease-in-out infinite; }
@keyframes dot-breathe { 50% { transform: scale(1.35); opacity: 0.7; } }
/* 27. The count reads as a count. */
#deck-count { font-family: var(--label); font-size: 12px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: #b8ad97; }
/* 28. A card you press gives a little (on its cell, never on the card itself). */
.roster-cell { transition: filter 0.12s ease; }
.roster-cell:active { filter: brightness(0.9); }
/* 29. Filter chips press and settle. */
.sift__chip { transition: transform 0.12s cubic-bezier(0.2, 0.9, 0.3, 1), background-color 0.18s ease, box-shadow 0.18s ease; }
.sift__chip:active { transform: translateY(1px) scale(0.95); }
/* 30. An empty roster is a note pointing at the shutter, not a line of grey text. */
#deck-empty:not([hidden]) {
  position: relative;
  margin: 40px auto 0;
  max-width: 17em;
  padding: 18px 20px 20px;
  text-align: center;
  font-family: var(--receipt);
  font-size: 18px;
  line-height: 1.3;
  color: #1d1a14;
  background: #f3ecda;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
  transform: rotate(-1.2deg);
}
#deck-empty:not([hidden])::after {
  content: "↓";
  display: block;
  margin-top: 10px;
  font-family: var(--display);
  font-size: 34px;
  font-weight: 900;
  color: #c8402a;
  animation: point-down 1.4s ease-in-out infinite;
}
@keyframes point-down { 50% { transform: translateY(8px); } }
/* 31. The wad row glints when there is money waiting in the tills. */
.wad.has-waiting { position: relative; overflow: hidden; }
.wad.has-waiting::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, 0.35) 48%, transparent 66%);
  background-size: 250% 100%;
  animation: wad-glint 4.5s ease-in-out infinite;
  pointer-events: none;
}
@keyframes wad-glint { 0%, 70% { background-position: 130% 0; } 100% { background-position: -130% 0; } }
/* 35. The row itself jumps when money lands. */
.wad.is-paid { animation: wad-paid 0.5s cubic-bezier(0.2, 1.5, 0.4, 1); }
@keyframes wad-paid { 30% { transform: scale(1.03); box-shadow: 0 0 0 3px rgba(159, 196, 142, 0.7), 0 0 26px rgba(159, 196, 142, 0.5); } }
/* 32. A level you cannot afford says so without shouting. */
#reveal-level.is-poor { opacity: 0.55; text-decoration-style: dashed; }
/* 33. The card screen fades up as it opens (the slot, never the card's own motion). */
.view--reveal.is-active .reveal-notes, .view--reveal.is-active .reveal-slot > .notes { animation: card-arrive 0.42s cubic-bezier(0.2, 1.1, 0.3, 1) 0.08s both; }
@keyframes card-arrive { from { opacity: 0; transform: translateY(14px); } }
/* 34. Back, top left, where a thumb looks for it. */
.reveal-back {
  position: sticky;
  top: 0;
  z-index: 8;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 40px;
  padding: 6px 14px 6px 8px;
  margin: -4px 0 -44px -4px;
  font-family: var(--label);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #f1e8d2;
  background: rgba(21, 18, 15, 0.82);
  border: 1px solid rgba(255, 240, 210, 0.12);
  border-radius: 20px;
  backdrop-filter: blur(6px);
  cursor: pointer;
}
.reveal-back svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.reveal-back:active { transform: scale(0.95); }
/* 38. The "still hurt" line is a chit, not loose text. */
.deck-sore:not([hidden]) { padding: 8px 12px; background: rgba(192, 40, 30, 0.12); border-left: 3px solid #c0281e; border-radius: 2px; }
/* 39. Room between cards so each till reads as its own. */
.view--deck .deck-grid { row-gap: 22px; }
/* 40. The wad on its intro sheet has a wobble to it. */
.wad-intro__icon { animation: wad-wobble 3s ease-in-out infinite; transform-origin: 50% 70%; }
@keyframes wad-wobble { 0%, 100% { transform: rotate(-4deg); } 50% { transform: rotate(3deg) translateY(-3px); } }
@media (prefers-reduced-motion: reduce) {
  .reveal-slot.is-levelled::after, .wad.has-waiting::after, .wad.is-paid, #deck-empty::after, .wad-intro__icon, .view--reveal.is-active .reveal-slot > .notes, .deck-head #acct-open::after { animation: none !important; }
}

/* === polish, batch 3: the camera and the estate ========================================= */
/* 41. A LIVE light in the corner while the camera is really seeing. */
.stage.is-live::before {
  content: "● LIVE";
  position: absolute;
  top: 12px;
  left: 14px;
  z-index: 6;
  font-family: var(--label);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  animation: live-blink 1.6s steps(2, jump-none) infinite;
  pointer-events: none;
}
.stage.is-live:is(.is-frozen, .is-reading, .is-developing)::before { display: none; }
@keyframes live-blink { 50% { opacity: 0.45; } }
/* 42. No camera: a note with the fix on it, not grey text on black. */
#camera-msg:not([hidden]) {
  max-width: 17em;
  padding: 16px 18px;
  color: #1d1a14;
  background: #f3ecda;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.55);
  transform: rotate(-1deg);
  font-family: var(--receipt);
  font-size: 17px;
  line-height: 1.3;
}
/* 43. The Find Me strip presses, and a new one nudges. */
.quest { transition: transform 0.12s ease; }
.quest:active { transform: scale(0.97); }
.quest__state { animation: none; }
.quest:not(.is-done) .quest__state { animation: quest-nudge 2.6s ease-in-out infinite; }
@keyframes quest-nudge { 0%, 80%, 100% { transform: none; } 88% { transform: rotate(-6deg) scale(1.08); } 94% { transform: rotate(4deg); } }
/* 44. The hint stays readable over any picture. */
#scan-hint { text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85); }
/* 45. A soft vignette, like looking through a lens. */
.stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  background: radial-gradient(120% 90% at 50% 45%, transparent 55%, rgba(0, 0, 0, 0.45) 100%);
}
/* 47. Gary blinks, and has a look round. */
.gary__eyes { transform-box: fill-box; transform-origin: 50% 50%; animation: gary-blink 5.5s ease-in-out infinite; }
.gary__pupils { animation: gary-look 9s ease-in-out infinite; }
@keyframes gary-blink { 0%, 94%, 100% { transform: none; } 96% { transform: scaleY(0.1); } }
@keyframes gary-look { 0%, 30% { transform: none; } 38%, 55% { transform: translate(-3px, 1px); } 63%, 85% { transform: translate(2px, -1px); } }
/* 48. He hops when you ask him something. */
.gary:has(#gary-more:active) .gary__face { transform: translateY(-4px) rotate(-3deg); transition: transform 0.1s ease; }
/* 49. Jobs going bob on their pins; 50. locked ones go flat and grey; 51. done ones are ticked off. */
.pin:not(.is-locked):not(.is-taken) .pin__head { animation: pin-bob 3.2s ease-in-out infinite; animation-delay: calc(var(--i, 0) * -0.6s); }
@keyframes pin-bob { 50% { translate: 0 -3px; } }
.pin.is-locked .pin__head { filter: saturate(0.2); }
.pin.is-locked .pin__name { background: #e9e2d0; }
.pin.is-taken .pin__name { text-decoration: line-through; text-decoration-thickness: 2px; color: #6f675a; }
/* 52. A pin squashes under the thumb. */
.pin:active .pin__head { scale: 0.9; }
/* 53. Clouds by day; 54. stars by night. */
.est-clouds { transition: opacity 0.6s ease; }
.estate__sheet[data-time="night"] .est-clouds { opacity: 0; }
.estate__sheet[data-time="dusk"] .est-clouds { opacity: 0.35; }
.est-stars circle { opacity: 0; }
.estate__sheet[data-time="night"] .est-stars circle { animation: star-twinkle 3s ease-in-out infinite; }
@keyframes star-twinkle { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
/* 55. The key uses little pins, the same shape as the map. */
.estate__legend .k { border-radius: 50% 50% 50% 0; transform: rotate(-45deg); width: 11px; height: 11px; }
/* 56. The slots as pips beside the count. */
.out-pips { display: inline-flex; gap: 5px; margin-left: 10px; vertical-align: middle; }
.out-pips i { width: 10px; height: 10px; border-radius: 50%; border: 2px solid var(--brass); }
.out-pips i.is-on { background: var(--brass); box-shadow: 0 0 8px rgba(232, 178, 58, 0.6); }
/* 57. What a job pays is the loudest line on the brief. */
.brief__terms dd.is-prize { font-size: 18px; color: #1f7a32; }
/* 58. The receipt's pay line lands like a stamp. */
#errand-receipt:not([hidden]) .receipt-pay { animation: stamp-in 0.45s cubic-bezier(0.2, 1.6, 0.4, 1) 0.35s both; }
@keyframes stamp-in { from { opacity: 0; transform: scale(1.8) rotate(-8deg); } to { opacity: 1; transform: rotate(-2deg); } }
/* 59. Job rows in the list press like paper. */
.errand { transition: transform 0.12s ease, box-shadow 0.12s ease; }
.errand:active { transform: translateY(2px) !important; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
/* 60. A card at work has a moving bar, so "busy" reads at a glance. */
.errand__bar > * { background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.45), transparent); background-size: 60px 100%; background-repeat: no-repeat; animation: bar-sweep 1.8s linear infinite; }
@keyframes bar-sweep { from { background-position: -60px 0; } to { background-position: 260px 0; } }
@media (prefers-reduced-motion: reduce) {
  .stage.is-live::before, .quest__state, .gary__eyes, .gary__pupils, .pin__head, .est-stars circle, .receipt-pay, .errand__bar > * { animation: none !important; }
  .est-clouds animateTransform { display: none; }
}
.errands-head:has(.out-pips) .chit { margin-left: auto; }
.errands-head .out-pips { margin-left: 10px; }

/* === polish, batch 4: Deploy, the fight, the result ===================================== */
/* 61. "3 hurt" is a red chip, not loose red text. */
#hand-hurt:not(:empty) { margin-left: 8px; padding: 2px 7px; font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; color: #fffaf0; background: #a3281b; border-radius: 10px; vertical-align: 2px; }
/* 62. Enter the arena catches the light; 63. and squashes when pressed. */
.tb-go { position: relative; overflow: hidden; transition: transform 0.12s cubic-bezier(0.2, 0.9, 0.3, 1); }
.tb-go::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg, transparent 35%, rgba(255, 255, 255, 0.5) 50%, transparent 65%);
  background-size: 250% 100%;
  animation: go-shine 5s ease-in-out 1s infinite;
  pointer-events: none;
}
@keyframes go-shine { 0%, 75% { background-position: 130% 0; } 100% { background-position: -130% 0; } }
.tb-go:active { transform: scale(0.97) translateY(2px); }
/* 64. A move presses into the table; its hit number is the thing to read. */
.move { transition: transform 0.1s ease, border-width 0.1s ease; }
.move:not(:disabled):not(.is-cooling):active { transform: translate(2px, 2px); border-bottom-width: 2px; border-right-width: 2px; }
.move__hit { font-size: 1.15em; }
/* 65. A move you cannot use this turn is hatched out. */
.move.is-cooling { background-image: repeating-linear-gradient(135deg, transparent 0 8px, rgba(0, 0, 0, 0.06) 8px 10px); }
/* 66. A fighter that takes a hit has its gauge shake. */
.fighter.is-hurt .health { animation: hp-shake 0.35s ease-in-out; }
@keyframes hp-shake { 20% { transform: translateX(-3px); } 40% { transform: translateX(3px); } 60% { transform: translateX(-2px); } 80% { transform: translateX(1px); } }
/* 67. The turn tape flips over for a new turn. */
#hud-turn.is-new { display: inline-block; animation: turn-flip 0.45s cubic-bezier(0.2, 1.3, 0.4, 1); }
@keyframes turn-flip { from { transform: rotateX(90deg); opacity: 0; } to { transform: none; opacity: 1; } }
/* 68. Commentary eases in a line at a time. */
#teleprompt-line.is-new { animation: line-in 0.28s ease-out; }
@keyframes line-in { from { opacity: 0; transform: translateY(4px); } }
/* 69. The vs between the fighters is a stencilled badge. */
.ring__vs .arena__v {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  font-family: var(--stencil);
  font-size: 18px;
  font-weight: 900;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #17140c;
  background: var(--brass);
  border-radius: 50%;
  box-shadow: 0 0 0 3px #15120f, 0 4px 10px rgba(0, 0, 0, 0.6);
  transform: rotate(-8deg);
}
/* 70. Swap presses like paper. */
.swapper { transition: transform 0.12s ease; }
.swapper:active { transform: translateY(2px) rotate(-1deg); }
/* 71. The fighter you have out is the dot that glows. */
.squad__dot.is-active { animation: dot-live 1.8s ease-in-out infinite; }
@keyframes dot-live { 50% { box-shadow: 0 0 0 3px rgba(232, 178, 58, 0.35), 0 0 10px rgba(232, 178, 58, 0.6); } }
/* 72. Flee brightens under a finger. */
.bail:active { color: #ff9b8f; }
/* 73. The receipt prints out, top first. */
#tcg-over:not([hidden]) .receipt-sheet { animation: receipt-print 0.7s cubic-bezier(0.2, 0.9, 0.3, 1) both; transform-origin: 50% 0; }
@keyframes receipt-print { from { clip-path: inset(0 0 100% 0); transform: translateY(-12px); } to { clip-path: inset(0 0 0 0); transform: none; } }
/* 74. A win throws confetti. */
.confetti { position: fixed; inset: 0; z-index: 90; pointer-events: none; overflow: hidden; }
.confetti i { position: absolute; top: -12px; left: var(--x); width: 8px; height: 12px; border-radius: 1px; animation: confetti-fall 2.2s cubic-bezier(0.25, 0.6, 0.4, 1) var(--d) forwards; }
@keyframes confetti-fall { to { transform: translate(var(--dx), 105vh) rotate(var(--r)); opacity: 0.8; } }
/* 75. After a win, Again! is the obvious next thing. */
#tcg-over.is-won .over-dock .btn--primary { animation: again-pulse 1.8s ease-in-out 1.2s infinite; }
@keyframes again-pulse { 50% { transform: rotate(-1deg) scale(1.05); } }
/* 76. A full team: the count chit gives a little cheer. */
#build-count { transition: background-color 0.2s ease; }
#build-dock.is-ready ~ * #build-count, #pick-three:has(#build-dock.is-ready) #build-count { animation: count-cheer 0.5s cubic-bezier(0.2, 1.6, 0.4, 1); }
@keyframes count-cheer { 50% { transform: scale(1.15) rotate(-3deg); } }
/* 77. Draft for me has a die on it. */
#build-auto::before { content: "⚄ "; font-size: 1.15em; }
/* 78. The form line under your name is a quiet label, not grey small print. */
.tb-form { font-family: var(--label); font-weight: 700; letter-spacing: 0.12em; color: #b8ad97; }
/* 79. Rivals press like the paper they are. */
.rival { transition: transform 0.12s ease; }
.rival:active { transform: translateY(2px) rotate(-0.5deg); }
/* 80. The fight board's gauges ease rather than jump. */
.health__fill { transition: width 0.55s cubic-bezier(0.2, 0.9, 0.3, 1), background 0.3s ease; }
@media (prefers-reduced-motion: reduce) {
  .tb-go::after, .fighter.is-hurt .health, #hud-turn.is-new, #teleprompt-line.is-new, .squad__dot.is-active, #tcg-over .receipt-sheet, #tcg-over .over-dock .btn--primary, #build-count { animation: none !important; }
}

/* === polish, batch 5: sheets, sign-in, the manual, touch ================================= */
/* 81. Continue with Google carries the G. */
.acct-google::before {
  content: "";
  display: inline-block;
  width: 18px;
  height: 18px;
  margin-right: 10px;
  vertical-align: -3px;
  border-radius: 50%;
  background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23EA4335' d='M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.4 30.2 0 24 0 14.6 0 6.6 5.4 2.7 13.3l7.8 6C12.4 13.6 17.7 9.5 24 9.5z'/%3E%3Cpath fill='%234285F4' d='M46.1 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.4c-.5 2.9-2.2 5.3-4.6 6.9l7.4 5.7c4.3-4 6.9-9.9 6.9-17.1z'/%3E%3Cpath fill='%23FBBC05' d='M10.5 28.7c-.5-1.4-.8-3-.8-4.7s.3-3.2.8-4.7l-7.8-6C1 16.6 0 20.2 0 24s1 7.4 2.7 10.7l7.8-6z'/%3E%3Cpath fill='%2334A853' d='M24 48c6.5 0 11.9-2.1 15.9-5.8l-7.4-5.7c-2.1 1.4-4.8 2.3-8.5 2.3-6.3 0-11.6-4.1-13.5-9.8l-7.8 6C6.6 42.6 14.6 48 24 48z'/%3E%3C/svg%3E") center / 13px no-repeat;
}
/* 82. The field you are typing in is ringed in brass. */
.acct-field input { transition: border-color 0.15s ease, box-shadow 0.15s ease; }
.acct-field input:focus { outline: none; border-color: var(--brass); box-shadow: 0 0 0 3px rgba(232, 178, 58, 0.25); }
/* 83. Show / hide sits inside the password box. */
.acct-field:has(#acct-peek) { position: relative; }
.acct-peek {
  position: absolute;
  right: 6px;
  bottom: 6px;
  min-height: 34px;
  padding: 4px 10px;
  font-family: var(--label);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #6d6352;
  background: none;
  border: 0;
  cursor: pointer;
}
#acct-pass { padding-right: 64px; }
/* 84. The manual's progress: the page you are on is a longer bar. */
.manual__dots i { transition: width 0.25s ease, background-color 0.25s ease; }
.manual__dots i.is-on { width: 20px; border-radius: 4px; }
/* 86. Each page of the manual slides in. */
.manual__step:not([hidden]) { animation: page-in 0.3s cubic-bezier(0.2, 0.9, 0.3, 1); }
@keyframes page-in { from { opacity: 0; transform: translateX(18px); } }
/* 87-88. Things the game says, said on the screen you are on: a slip that goes away. */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(86px + var(--shutter-rise) + 14px + env(safe-area-inset-bottom, 0px));
  z-index: 75;
  max-width: min(22em, calc(100vw - 32px));
  margin: 0;
  padding: 11px 16px 12px;
  transform: translateX(-50%) rotate(-0.8deg);
  font-family: var(--receipt);
  font-size: 17px;
  line-height: 1.25;
  text-align: center;
  color: #1d1a14;
  background: #f3ecda;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.55);
  cursor: pointer;
}
.toast.is-in { animation: toast-in 0.32s cubic-bezier(0.2, 1.2, 0.4, 1); }
@keyframes toast-in { from { opacity: 0; transform: translateX(-50%) translateY(14px) rotate(-0.8deg); } }
/* 89. No double-tap zoom delay on anything you tap. */
button, .btn, .tab, .pin, .till, .card, .rival, .sift__chip, a { touch-action: manipulation; }
/* 90. Tapping the furniture does not select it like text. */
.tabs, button, .btn, .pin, .till, .wad, .card, .chit, .sift__chip, .hud, .moves { -webkit-user-select: none; user-select: none; }
/* 91. Card photos do not ghost-drag on a desktop. */
.card img { -webkit-user-drag: none; user-drag: none; }
/* 93. A locked job's button carries a padlock. */
#brief-send:disabled::before { content: "🔒 "; filter: grayscale(1); }
/* 94. Since you ask presses. */
#gary-more:active { color: #8f2f22; }
/* 95. The picker's title stays put while the cards scroll under it. */
#picker .sheet__head { position: sticky; top: -24px; z-index: 3; margin: -24px -16px 0; padding: 24px 16px 10px; background: linear-gradient(180deg, #1d1915 80%, rgba(29, 25, 21, 0)); }
/* 96. A card in the picker dims under your finger. */
#picker-grid > .card:active { opacity: 0.8; }
/* 97. The welcome's big number glows like money should. */
.welcome__n b { text-shadow: 0 0 22px rgba(232, 178, 58, 0.35); }
/* 98. The main button on a sheet is full width, where a thumb can find it. */
.sheet__inner > .btn--primary { width: 100%; }
/* 100. Signed in is a tick, not just a colour. */
.acct-msg.is-good::before { content: "✓ "; font-weight: 900; }
@media (prefers-reduced-motion: reduce) { .manual__step, .toast.is-in { animation: none !important; } }
.toast { z-index: 69; }

/* === polish, batch 6: behaviour ========================================================== */
/* 108. Where you are in the roster, beside Back. */
.reveal-pos {
  position: sticky;
  top: 8px;
  z-index: 8;
  align-self: flex-end;
  margin: -40px 0 12px;
  padding: 5px 10px;
  font-family: var(--label);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #b8ad97;
  background: rgba(21, 18, 15, 0.7);
  border-radius: 12px;
  font-variant-numeric: tabular-nums;
}
/* 107. Swiping slides the next card in from the side you swiped towards. */
#view-reveal[data-swipe="left"] .reveal-slot { animation: slide-from-right 0.28s cubic-bezier(0.2, 0.9, 0.3, 1); }
#view-reveal[data-swipe="right"] .reveal-slot { animation: slide-from-left 0.28s cubic-bezier(0.2, 0.9, 0.3, 1); }
@keyframes slide-from-right { from { opacity: 0.2; transform: translateX(40px); } }
@keyframes slide-from-left { from { opacity: 0.2; transform: translateX(-40px); } }
/* 109. A sheet being dragged follows the finger without lag. */
.sheet__inner[style*="translateY"] { transition: none !important; animation: none !important; }
.sheet__inner { transition: transform 0.2s ease; }
/* 103. The last minutes count down in brass. */
.errand__clock.is-soon { color: #9a6a10; font-variant-numeric: tabular-nums; font-weight: 700; }
@media (prefers-reduced-motion: reduce) { #view-reveal .reveal-slot { animation: none !important; } }

/* === polish, batch 7: roster and card-screen detail ===================================== */
/* 121. A till gives under the thumb. Its hanging sway owns transform, so the press uses
   scale, which stacks on top instead of being overridden (item 23 never showed). */
.till { transition: scale 0.1s ease; }
.till:active { scale: 0.88; }
/* 122. A fired-up till glows orange. */
.till.is-hot:not(.is-full) { border-color: #b4541c; box-shadow: 0 2px 0 #b4541c, 0 0 14px rgba(236, 120, 40, 0.55); }
.till.is-hot:not(.is-full) .till__fill { background: #f0b27a; }
/* 123. A till whose card is out on a job is dashed, waiting. */
.till.is-away { border-style: dashed; }
/* 124. A full till sparkles. */
.till.is-full::after {
  content: "✦";
  position: absolute;
  top: -3px;
  right: 2px;
  font-size: 10px;
  color: #fff6d0;
  text-shadow: 0 0 4px #e8b23a;
  animation: till-spark 1.4s ease-in-out infinite;
}
@keyframes till-spark { 0%, 100% { opacity: 0.2; scale: 0.7; } 50% { opacity: 1; scale: 1.2; } }
/* 125. The title tape is stuck on as the roster opens. */
.view--deck.is-active .deck-head h2 { animation: tape-on 0.4s cubic-bezier(0.2, 1.3, 0.4, 1) both; }
@keyframes tape-on { from { opacity: 0; transform: rotate(-8deg) scale(1.1); } to { opacity: 1; transform: rotate(-2deg); } }
/* 126. Signed in, the button wears your initial. */
#acct-open.is-in { display: inline-flex; align-items: center; gap: 8px; }
#acct-open.is-in::before {
  content: attr(data-initial);
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-family: var(--display);
  font-size: 13px;
  font-weight: 900;
  color: #17140c;
  background: var(--brass);
  text-decoration: none;
}
/* 127. The notes on the wad fan out when you press it. */
.wad__icon { transition: transform 0.18s cubic-bezier(0.2, 1.4, 0.4, 1); }
.wad:active .wad__icon { transform: rotate(-8deg) scale(1.1); }
/* 128. The chosen filter reads as chosen: bolder and lifted. */
.sift__chip.is-on { box-shadow: 0 3px 0 rgba(0, 0, 0, 0.35), 0 0 0 2px rgba(255, 246, 214, 0.4); }
.sift__chip.is-on .sift__chip-n { font-weight: 800; opacity: 1; }
/* 129. The sort button says which order it is in. */
.sift__sort { position: relative; }
.sift__sort::after {
  content: attr(data-order);
  position: absolute;
  left: 50%;
  bottom: -14px;
  transform: translateX(-50%);
  font-family: var(--label);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #b8ad97;
  white-space: nowrap;
  pointer-events: none;
}
/* 130. "Out on a job" hangs on a card like a luggage tag. */
.card__away { border-radius: 3px 3px 3px 12px; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.45); }
/* 132. What you have, under the price of the next level. */
#reveal-level[data-bank] { display: inline-grid; justify-items: center; line-height: 1.1; }
#reveal-level[data-bank]::after { content: attr(data-bank); font-size: 10px; letter-spacing: 0.08em; opacity: 0.65; text-decoration: none; }
/* 133. The card's actions carry a mark each. */
#reveal-level::before { content: "▲ "; color: var(--brass); }
#reveal-feed::before { content: "+ "; font-weight: 900; }
#reveal-share::before { content: "↗ "; }
#reveal-swap::before { content: "⇄ "; }
#reveal-gift::before { content: "✉ "; }
/* 134. The moves in the field notes are banded, so each reads as one row. */
.notes__move:nth-child(even) { background: rgba(0, 0, 0, 0.035); border-radius: 3px; }
/* 135. Teach presses. */
.notes__teach { transition: transform 0.1s ease; }
.notes__teach:active { transform: translateY(1px) scale(0.95); }
/* 136. Binning is red ink. */
#reveal-discard { color: #e0806f; text-decoration-color: rgba(224, 128, 111, 0.45); }
/* 137. On a new catch, Recruit it is the thing to press. */
.view--reveal:not(:has(#reveal-back:not([hidden]))) #reveal-keep:not([hidden]) { animation: again-pulse 1.8s ease-in-out 0.8s infinite; }
/* 138. The wad explainer's points arrive one after another. */
#wad-intro:not([hidden]) .wad-intro__how li { animation: rise-in 0.35s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
#wad-intro:not([hidden]) .wad-intro__how li:nth-child(2) { animation-delay: 0.08s; }
#wad-intro:not([hidden]) .wad-intro__how li:nth-child(3) { animation-delay: 0.16s; }
#wad-intro:not([hidden]) .wad-intro__how li:nth-child(4) { animation-delay: 0.24s; }
/* 139. The tab you pick pops its icon. */
.tabs .tab.is-active:not(.tab--shutter) .tab__mark { animation: tab-pop 0.35s cubic-bezier(0.2, 1.6, 0.4, 1); }
@keyframes tab-pop { 0% { transform: scale(0.8); } 60% { transform: scale(1.18) translateY(-2px); } 100% { transform: translateY(-1px); } }
/* 140. Gary's speech bubble floats a little, like he is still talking. */
.gary__bubble { animation: bubble-float 5s ease-in-out infinite; }
@keyframes bubble-float { 50% { transform: translateY(-2px) rotate(0.3deg); } }
@media (prefers-reduced-motion: reduce) {
  .till.is-full::after, .view--deck .deck-head h2, #reveal-keep, #wad-intro .wad-intro__how li, .tabs .tab .tab__mark, .gary__bubble { animation: none !important; }
}

/* === polish, batch 8: errands and the estate ============================================ */
/* 141. The pin you opened stays lit; the rest of the map steps back. */
.estate__pins.has-focus .pin:not(.is-focus), .estate__pins.has-focus .token { opacity: 0.35; transition: opacity 0.2s ease; }
.pin.is-focus .pin__head { scale: 1.15; }
/* 142. The brief is signed with a little bin, so you know who is asking. */
.brief__who b::before {
  content: "";
  display: inline-block;
  width: 16px;
  height: 18px;
  margin-right: 6px;
  vertical-align: -2px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 110'%3E%3Crect x='10' y='16' width='80' height='13' rx='3' fill='%234e6352' stroke='%2317140f' stroke-width='5'/%3E%3Cpath d='M18 29h64l-6 68H24z' fill='%233b4a3e' stroke='%2317140f' stroke-width='5'/%3E%3Ccircle cx='38' cy='52' r='11' fill='%23fff'/%3E%3Ccircle cx='63' cy='52' r='11' fill='%23fff'/%3E%3Ccircle cx='40' cy='54' r='5' fill='%2317140f'/%3E%3Ccircle cx='61' cy='54' r='5' fill='%2317140f'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* 143. The health a job costs, as a bar. */
.brief__drain { display: block; width: 100%; max-width: 160px; height: 6px; margin: 2px 0 4px; border-radius: 3px; background: #4fb35a; overflow: hidden; }
.brief__drain i { display: block; height: 100%; margin-left: auto; background: #c8402a; }
/* 144. A card coming home low on health pulses red. */
.errand__hp.is-low { animation: hp-low 1.4s ease-in-out infinite; }
@keyframes hp-low { 50% { box-shadow: 0 0 0 2px rgba(200, 64, 42, 0.45); } }
/* 145. Calling them back is red ink with a turn-round mark. */
.errand__recall { color: #e0806f; }
.errand__recall::before { content: "↩ "; }
/* 146. A receipt waiting to be opened pulses. */
.errand--back .errand__go { animation: again-pulse 1.8s ease-in-out infinite; }
/* 147. Cards out on the map bob as they work; 148. home ones hop. */
.token:not(.is-back) .token__face { animation: token-work 2.2s ease-in-out infinite; }
@keyframes token-work { 50% { translate: 0 -2px; } }
.token.is-back { animation: token-hop 1.2s cubic-bezier(0.2, 1.6, 0.4, 1) infinite; }
@keyframes token-hop { 0%, 60%, 100% { translate: 0 0; } 30% { translate: 0 -6px; } }
/* 149. The map has an edge of shade, so it sits in the page rather than on it. */
.estate__sheet::after { content: ""; position: absolute; inset: 0; pointer-events: none; box-shadow: inset 0 0 28px rgba(0, 0, 0, 0.35); border-radius: inherit; }
/* 151. In the list, each job has a stripe in its length's colour, same as the pins. */
.errand[class*="errand--len-"] { border-left: 5px solid transparent; }
.errand--len-errand { border-left-color: #4e8c52 !important; }
.errand--len-job { border-left-color: #2f6fa0 !important; }
.errand--len-shift { border-left-color: #c8402a !important; }
.errand--len-night { border-left-color: #2b2346 !important; }
.errand--len-crew { border-left-color: var(--brass) !important; }
/* 153. Gary's line eases in when it changes. */
#gary-says { transition: opacity 0.2s ease; }
/* 157. The pitch reads as something said: an opening quote mark. */
.brief__pitch { position: relative; }
.brief__pitch::before { content: "“"; position: absolute; top: -14px; left: 8px; font-family: Georgia, serif; font-size: 42px; line-height: 1; color: rgba(29, 26, 20, 0.25); }
/* 159. A crew job's pin says so. */
.pin--crew .pin__name::after { content: " · crew"; font-weight: 600; opacity: 0.7; }
/* 160. The names on tokens stay on one line and trim neatly. */
.token__tag { max-width: 84px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .errand__hp.is-low, .errand--back .errand__go, .token__face, .token.is-back { animation: none !important; }
}
/* 156. The receipt lines tear off at the bottom like a till roll. */
.receipt-lines { padding-bottom: 16px; clip-path: polygon(0 0, 100% 0, 100% calc(100% - 6px), 95% 100%, 90% calc(100% - 6px), 85% 100%, 80% calc(100% - 6px), 75% 100%, 70% calc(100% - 6px), 65% 100%, 60% calc(100% - 6px), 55% 100%, 50% calc(100% - 6px), 45% 100%, 40% calc(100% - 6px), 35% 100%, 30% calc(100% - 6px), 25% 100%, 20% calc(100% - 6px), 15% 100%, 10% calc(100% - 6px), 5% 100%, 0 calc(100% - 6px)); }

/* === polish, batch 9: deploy, the picker, the fight ===================================== */
/* 161. The three in your hand breathe a little, like they are being held. */
.tb-hand { animation: hand-breathe 6s ease-in-out infinite; transform-origin: 50% 100%; }
@keyframes hand-breathe { 50% { transform: rotate(0.6deg) translateY(-2px); } }
/* 162. Your name settles onto the bill as Deploy opens. */
.view--play.is-active #marquee-name { animation: tape-on 0.45s cubic-bezier(0.2, 1.3, 0.4, 1) both; }
/* 163. Change presses. */
.tb-change:active { scale: 0.95; }
/* 164. Adding someone by tag is a clear + row, not a bare disclosure. */
#whofight summary { cursor: pointer; list-style: none; padding: 10px 12px; border: 1px dashed rgba(232, 178, 58, 0.45); border-radius: 4px; color: #e6dcc2; }
#whofight summary::-webkit-details-marker { display: none; }
#whofight summary::before { content: "+ "; color: var(--brass); font-weight: 900; }
/* 165. In the picker, the count and advice stay in view while you scroll the roster. */
#pick-three .build-bar { position: sticky; top: 0; z-index: 6; padding: 8px 0; background: linear-gradient(180deg, rgba(21, 24, 26, 0.96) 75%, rgba(21, 24, 26, 0)); }
/* 166. The advice line is a chit, so it reads as advice. */
#build-advice { padding: 6px 10px; border-left: 3px solid var(--brass); background: rgba(255, 246, 214, 0.05); border-radius: 2px; }
/* 167. A full team: the count shakes when you try to add a fourth. */
#build-count.is-full-shake { animation: count-no 0.4s ease-in-out; }
@keyframes count-no { 20% { translate: -5px 0; } 40% { translate: 5px 0; } 60% { translate: -3px 0; } 80% { translate: 2px 0; } }
/* 168. Who you are facing, in stencil, like a bill on a wall. */
#opponent-who { font-family: var(--stencil); font-weight: 900; letter-spacing: 0.04em; text-transform: uppercase; }
/* 169. A fighter on its last legs: the gauge pulses red. */
.health__fill.is-low { animation: hp-low-pulse 1s ease-in-out infinite; }
@keyframes hp-low-pulse { 50% { filter: brightness(1.35); } }
/* 170. A knocked-out squad member is crossed through. */
.squad__dot.is-out { position: relative; }
.squad__dot.is-out::after { content: "✕"; position: absolute; inset: 0; display: grid; place-items: center; font-size: 9px; font-weight: 900; color: #e0806f; }
/* 171. Nothing left to swap in: Swap goes quiet. */
.swapper:disabled { opacity: 0.45; filter: grayscale(1); }
/* 172. The "more" arrow under commentary nudges you to tap. */
.teleprompt__more:not([hidden]) { animation: more-nudge 1.1s ease-in-out infinite; }
@keyframes more-nudge { 50% { translate: 0 3px; } }
/* 173-174. Any clock that is running out pulses. */
.is-late, .clock.is-late, .field-call__clock.is-low { animation: clock-late 0.8s ease-in-out infinite; }
@keyframes clock-late { 50% { opacity: 0.55; } }
/* 175. The receipt's lines land one after another. */
#tcg-over:not([hidden]) .lanes > * { animation: rise-in 0.3s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
#tcg-over:not([hidden]) .lanes > *:nth-child(2) { animation-delay: 0.1s; }
#tcg-over:not([hidden]) .lanes > *:nth-child(3) { animation-delay: 0.2s; }
#tcg-over:not([hidden]) .lanes > *:nth-child(4) { animation-delay: 0.3s; }
#tcg-over:not([hidden]) .lanes > *:nth-child(5) { animation-delay: 0.4s; }
#tcg-over:not([hidden]) .lanes > *:nth-child(n+6) { animation-delay: 0.5s; }
/* 176. STANDING stamps on with a twist. */
.recap__flag { display: inline-block; animation: stamp-in 0.4s cubic-bezier(0.2, 1.6, 0.4, 1) 0.6s both; }
/* 177. Brag carries a share mark. */
.over-dock .btn--ghost:nth-of-type(2)::before { content: "↗ "; }
/* 178. The result dock's buttons press. */
.over-dock .btn:active { scale: 0.96; }
/* 179. Who you are fighting, over their side, in stencil. */
.hud__vs { font-family: var(--stencil); font-weight: 900; letter-spacing: 0.08em; font-size: 15px; }
/* 180. Health numbers in a steadier face, easier to read mid-fight. */
.health__num { font-size: 12px; letter-spacing: 0.04em; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95); }
@media (prefers-reduced-motion: reduce) {
  .tb-hand, #marquee-name, #build-count, .health__fill.is-low, .teleprompt__more, .is-late, .clock.is-late, .field-call__clock, #tcg-over .lanes > *, .recap__flag { animation: none !important; }
}

/* === polish, batch 10: access, speed, finish ============================================= */
/* 181-183: sheets are named dialogs, focus moves in and back (app.js); tills are labelled. */
/* 184. Asked for more contrast: text goes brighter and edges firmer. */
@media (prefers-contrast: more) {
  :root { --muted: #d6ccb8; }
  .tabs .tab:not(.tab--shutter) { color: #d6ccb8; }
  .btn--ghost { text-decoration-color: currentColor; }
  .sheet--paper .sheet__inner { --muted: #3d3528; }
}
/* 185. Windows high-contrast mode: buttons keep an outline you can see. */
@media (forced-colors: active) {
  .btn, .tab, .pin__head, .till, .sift__chip { border: 1px solid ButtonText; }
  .shutter-btn { forced-color-adjust: none; }
}
/* 186. Scrolling a sheet never scrolls the page behind it. */
.sheet__inner { overscroll-behavior: contain; }
/* 187. Flying notes get their own layer, so they fly smoothly. */
.wad-fly, .confetti i { will-change: transform; }
/* 188: card photos load as they scroll into view (card.js), no visual change. */
/* 189. "Tills filling" has a living ellipsis. */
.wad:not(.has-waiting) #scrap-rate::after { content: "…"; display: inline-block; width: 1em; text-align: left; animation: ellipsis 1.6s steps(4, end) infinite; overflow: hidden; vertical-align: bottom; }
@keyframes ellipsis { from { width: 0; } to { width: 1em; } }
/* 190. A filter with nothing in it is a note, like the empty roster. */
#deck-none:not([hidden]) { margin: 30px auto 0; max-width: 16em; padding: 14px 18px; text-align: center; font-family: var(--receipt); font-size: 17px; color: #1d1a14; background: #f3ecda; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45); transform: rotate(0.8deg); }
/* 191. The wad explainer's close is a proper button at the bottom, not only a word up top. */
#wad-intro .btn--primary { margin-top: 6px; }
/* 192-193. The sound slot presses (and blips when turned on). */
.tabs .sound:active > span { transform: scale(0.85); }
.tabs .sound > span { transition: transform 0.1s ease; }
/* 194: "Back online." when the signal returns (app.js). */
/* 195. Everything else: each row gets an arrow that nudges on press. */
.locker__row { transition: background-color 0.15s ease; }
.locker__row:active::after { translate: 3px 0; }
/* 196: the manual's Next gives a tick of feedback (app.js). */
/* 197. The picker's cards sit in the same rhythm as the roster. */
#picker-grid { gap: 16px 14px; }
/* 198. An empty swap list is a dashed slot waiting to be filled. */
.swap__empty { padding: 16px; text-align: center; border-radius: 4px; }
/* 199. Native bits (dropdowns, scrollbars, date pickers) render dark to match. */
:root { color-scheme: dark; }
.sheet--paper { color-scheme: light; }
/* 200. A browser's autofill does not paint the sign-in boxes yellow on the paper. */
.sheet--paper input:-webkit-autofill { -webkit-box-shadow: 0 0 0 40px #fffaf0 inset; -webkit-text-fill-color: #1d1a14; caret-color: #1d1a14; }
@media (prefers-reduced-motion: reduce) { #scrap-rate::after { animation: none !important; } }

/* --- the estate, refined -----------------------------------------------------------------
   Grown up: fine outlines, a muted palette, printed-map lettering with a paper halo
   instead of plaques, and slimmer markers in inks that sit with the drawing. */
.estate__label {
  font-family: var(--label);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-anchor: middle;
  fill: #2d2a24;
  paint-order: stroke;
  stroke: rgba(236, 230, 214, 0.92);
  stroke-width: 2.6px;
  stroke-linejoin: round;
}
.estate__sheet[data-time="night"] .estate__label { fill: #ece6d4; stroke: rgba(20, 22, 34, 0.85); }
.estate__sheet[data-time="dusk"] .estate__label { stroke: rgba(236, 222, 200, 0.9); }
.est-win { fill: #a3b5bb; }

/* Markers: slimmer, finer edges, muted inks, a soft shadow. No bobbing. */
.pin__head {
  width: 26px;
  height: 26px;
  border: 1.5px solid #2a241c;
  box-shadow: 0 0 0 1.5px rgba(246, 241, 228, 0.95), 2px 3px 4px rgba(0, 0, 0, 0.35);
  font-size: 10px;
}
@media (max-width: 400px) { .pin__head { width: 26px; height: 26px; } }
.pin:not(.is-locked):not(.is-taken) .pin__head { animation: none; }
.k--errand, .pin--errand .pin__head { background: #5b8a5e; }
.k--job, .pin--job .pin__head { background: #3f6d93; }
.k--shift, .pin--shift .pin__head { background: #a54e3a; }
.k--night, .pin--night .pin__head { background: #3a3452; }
.k--crew, .pin--crew .pin__head { background: linear-gradient(180deg, #d9bd78, #a9822f); color: #2a1f08; }
.pin.is-locked .pin__head { background: #7a7366; filter: none; }
.pin__name {
  padding: 2px 6px 3px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #2a241c;
  background: rgba(246, 241, 228, 0.96);
  border: 0.5px solid rgba(42, 36, 28, 0.35);
  border-radius: 2px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transform: none;
}
.pin__need { font-size: 8.5px; letter-spacing: 0.1em; background: rgba(90, 84, 74, 0.92); border-radius: 1px; }
.pin::after { width: 12px; height: 4px; background: rgba(0, 0, 0, 0.28); filter: blur(1px); }
/* Cards out on the map: a smaller token, a finer ring. */
.token__face { box-shadow: 0 0 0 2px rgba(246, 241, 228, 0.95), 0 2px 5px rgba(0, 0, 0, 0.4); }
.token__tag { font-size: 9px; letter-spacing: 0.04em; border-radius: 2px; }
.token:not(.is-back) .token__face { animation-duration: 3s; }
.estate__label { font-size: 10.5px; letter-spacing: 0.14em; stroke-width: 3px; }

/* --- the jobs board --------------------------------------------------------------------
   One ticket per job: a picture of where, what it is, three facts, and one thing to do.
   A stripe in the job's length colour down the left, the same inks as the map. */
#errands-board { display: grid; gap: 10px; }
.ticket {
  position: relative;
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr) auto;
  align-items: stretch;
  gap: 12px;
  width: 100%;
  padding: 10px 10px 10px 14px;
  text-align: left;
  font: inherit;
  color: #1d1a14;
  background: #f3ecda;
  border: 0;
  border-left: 5px solid #5b8a5e;
  border-radius: 4px;
  box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4);
  cursor: pointer;
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.ticket:active { transform: translateY(2px); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4); }
.ticket--errand { border-left-color: #5b8a5e; }
.ticket--job { border-left-color: #3f6d93; }
.ticket--shift { border-left-color: #a54e3a; }
.ticket--night { border-left-color: #3a3452; }
.ticket--crew { border-left-color: #b8923e; }
.ticket__art { position: relative; display: block; align-self: start; border-radius: 3px; overflow: hidden; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15); }
.ticket__art svg { display: block; width: 76px; height: 57px; }
.ticket__where { display: block; margin-top: 4px; font-family: var(--label); font-size: 9px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: #6d6352; line-height: 1.1; }
.ticket__body { display: grid; align-content: start; gap: 3px; min-width: 0; }
.ticket__name { font-family: var(--display); font-weight: 900; font-size: 16px; line-height: 1.1; text-transform: uppercase; letter-spacing: -0.01em; }
.ticket__line { font-family: var(--receipt); font-size: 15px; line-height: 1.2; color: #3d3528; }
.ticket__chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 4px; }
.ticket__chips i { font-style: normal; font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; padding: 2px 7px; border-radius: 10px; background: rgba(29, 26, 20, 0.07); color: #3d3528; white-space: nowrap; }
.ticket__chips .ticket__pay { background: #2f6b3c; color: #f3ecda; }
.ticket__go { display: grid; align-content: center; justify-items: center; gap: 2px; min-width: 64px; padding-left: 10px; border-left: 1px dashed rgba(29, 26, 20, 0.2); text-align: center; }
.ticket__go b { font-family: var(--stencil); font-size: 24px; font-weight: 900; letter-spacing: 0.04em; text-transform: uppercase; line-height: 1; color: #1d1a14; }
.ticket__go small { font-family: var(--label); font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: #6d6352; line-height: 1.15; max-width: 76px; }
.ticket.is-locked { background: #e3dccb; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3); }
.ticket.is-locked .ticket__art svg { filter: saturate(0.3) brightness(0.95); }
.ticket.is-locked .ticket__go b { font-size: 18px; filter: grayscale(1); }
.ticket.is-locked .ticket__go small { color: #8f2f22; }
.ticket.is-taken { opacity: 0.55; cursor: default; }
.ticket.is-taken .ticket__name { text-decoration: line-through; }
@media (max-width: 360px) { .ticket { grid-template-columns: 60px minmax(0, 1fr) auto; } .ticket__art svg { width: 60px; height: 45px; } }

/* The map is an extra now: its switch sits small and to the right. */
.view--errands.is-list .estate__key { justify-content: flex-end; margin: -4px 0 6px; }
.view--errands.is-list #estate-list { min-height: 32px; padding: 6px 12px; font-size: 11px; }
.ticket__go { min-width: 52px; padding-left: 8px; }
.ticket:not(.is-locked):not(.is-taken) .ticket__go b::after { content: " ›"; font-family: var(--display); }
.ticket { gap: 10px; padding: 10px 8px 10px 12px; }

/* --- your three, always in view --------------------------------------------------------- */
.build-tray { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; width: 100%; margin-top: 8px; }
.tray-slot {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  min-height: 40px;
  padding: 6px 22px 6px 7px;
  font: inherit;
  text-align: left;
  color: #b8ad97;
  background: rgba(255, 246, 214, 0.04);
  border: 1.5px dashed rgba(232, 178, 58, 0.4);
  border-radius: 6px;
  cursor: default;
}
.tray-slot.is-filled { color: #17140c; background: #f3ecda; border: 1.5px solid var(--brass); cursor: pointer; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4); animation: slot-in 0.3s cubic-bezier(0.2, 1.4, 0.4, 1); }
@keyframes slot-in { from { transform: scale(0.85); opacity: 0; } }
.tray-slot:active.is-filled { transform: scale(0.96); }
.tray-slot__n { display: grid; place-items: center; flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%; font-family: var(--display); font-size: 11px; font-weight: 900; color: #2a1f08; background: var(--brass); }
.tray-slot:not(.is-filled) .tray-slot__n { background: transparent; color: #b8ad97; border: 1.5px solid rgba(232, 178, 58, 0.4); }
.tray-slot .mk { flex: 0 0 auto; width: 14px; height: 14px; display: inline-grid; }
.tray-slot .mk svg { width: 14px; height: 14px; }
.tray-slot__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--label); font-size: 12px; font-weight: 700; letter-spacing: 0.02em; }
.tray-slot__x { position: absolute; top: 50%; right: 6px; transform: translateY(-50%); font-style: normal; font-size: 16px; font-weight: 900; color: #8f2f22; line-height: 1; }
.build-tray.is-full-shake { animation: count-no 0.4s ease-in-out; }
/* Under each chosen card in the grid, the same words: it is in. */
.pick:has(.card--picked) .pick__cap::before { content: "In your three · "; color: var(--brass); font-weight: 800; }
@media (prefers-reduced-motion: reduce) { .tray-slot.is-filled, .build-tray.is-full-shake { animation: none !important; } }
.build-bar { flex-wrap: wrap; }
.build-bar .build-tray { flex: 1 0 100%; order: 3; }
#pick-three .build-bar { background: rgba(14, 17, 19, 0.82); backdrop-filter: blur(6px); border-radius: 0 0 10px 10px; }

/* === deploy/draft/fight polish A: the Deploy screen and the picker ======================= */
/* 201. For keeps is a real switch, not a bare checkbox; 202. switched on, it looks dangerous. */
.stakes:not([hidden]) { display: flex; align-items: center; gap: 12px; margin: 12px 14px 0; padding: 12px 14px; border-radius: 6px; background: rgba(255, 246, 214, 0.05); border: 1px solid rgba(255, 246, 214, 0.1); cursor: pointer; }
.stakes input { appearance: none; -webkit-appearance: none; flex: 0 0 auto; width: 44px; height: 26px; border-radius: 13px; background: #3a3530; position: relative; cursor: pointer; transition: background-color 0.2s ease; }
.stakes input::after { content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #f3ecda; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); transition: transform 0.2s cubic-bezier(0.2, 1.3, 0.4, 1); }
.stakes input:checked { background: #a3281b; }
.stakes input:checked::after { transform: translateX(18px); }
.stakes__body { display: grid; gap: 2px; }
.stakes__body b { font-family: var(--stencil); font-size: 17px; letter-spacing: 0.06em; text-transform: uppercase; color: #f1e8d2; }
.stakes__body small { font-size: 12px; line-height: 1.35; color: #b8ad97; }
.stakes:has(input:checked) { border-color: rgba(200, 64, 42, 0.7); background: rgba(163, 40, 27, 0.14); box-shadow: 0 0 18px rgba(200, 64, 42, 0.25); }
.stakes:has(input:checked) .stakes__body b { color: #ff9b8f; }
/* 203. Your team's read-out rows are paper chits. */
.teamread__row { padding: 5px 8px; border-radius: 3px; background: rgba(255, 246, 214, 0.05); }
/* 204. The risk line carries a warning mark. */
.teamread__risk:not([hidden])::before { content: "⚠ "; }
/* 205. What they have brought reads like a dossier. */
.intel:not([hidden]) { position: relative; padding-top: 22px; }
.intel:not([hidden])::before { content: "What they have brought"; position: absolute; top: 7px; left: 12px; font-family: var(--label); font-size: 10px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: #e0806f; }
/* 206. Covered: a green tick. Not covered: a red mark. */
.intel__row.is-covered .intel__mark { color: #7fcf8a; font-weight: 900; }
.intel__row:not(.is-covered) .intel__mark { color: #e0806f; font-weight: 900; }
/* 207. Who you are facing has a face: their initial in a disc. */
#opponent-who[data-initial]::before { content: attr(data-initial); display: inline-grid; place-items: center; width: 24px; height: 24px; margin-right: 8px; border-radius: 50%; font-family: var(--display); font-size: 12px; color: #fffaf0; background: #8f2f22; vertical-align: 2px; }
/* 208. Change is a small pill, not a stray underline. */
.facing__change { padding: 4px 10px; border: 1px solid rgba(143, 47, 34, 0.45); border-radius: 12px; text-decoration: none; font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
/* 209. With three chosen, the rest step further back. */
#pick-three:has(.tray-slot:nth-child(3).is-filled) .card--unpicked { opacity: 0.32; }
/* 210. A card still hurt says so under it in red. */
.pick__cap.is-hurt { color: #e0806f; }
/* 211. What it beats is a chip under the card. */
.pick__cap:not(:empty) { display: inline-block; margin-top: 6px; padding: 2px 8px; border-radius: 10px; background: rgba(232, 178, 58, 0.12); }
/* 212. Deploy says what it needs when it cannot go. */
#build-go:disabled { font-size: 15px; }
/* 214. No cards to pick is a note. */
#build-short:not([hidden]) { padding: 12px 16px; margin: 12px 14px; font-family: var(--receipt); font-size: 16px; color: #1d1a14; background: #f3ecda; box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4); }
/* 215. Your enemies are tickets you can pick. */
.tb-foelist .rival { box-shadow: 0 4px 10px rgba(0, 0, 0, 0.35); }
.tb-foelist .rival::after { content: "›"; position: absolute; right: 12px; top: 50%; transform: translateY(-50%); font-size: 22px; font-weight: 900; color: rgba(29, 26, 20, 0.35); }
.rival { position: relative; }
/* 216. A turn waiting for you is tape that pulses. */
.board-lot--urgent .lot-tape { animation: urgent-tape 1.6s ease-in-out infinite; }
@keyframes urgent-tape { 50% { box-shadow: 0 0 0 3px rgba(200, 64, 42, 0.45), 0 0 18px rgba(200, 64, 42, 0.4); } }
/* 217. The Hunt chip is a proper badge. */
.chip--hunt { border-radius: 14px; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35); }
/* 218. Your record line has room to breathe. */
.tb-right { gap: 6px; }
/* 219. Rivals in the picker sheet have a way-in arrow. */
#whofight .rival::after { content: "›"; position: absolute; right: 12px; top: 50%; transform: translateY(-50%); font-size: 22px; font-weight: 900; color: rgba(29, 26, 20, 0.35); }
/* 220. With nothing to fight with, the arena button points at the camera. */
#view-play.is-bare .tb-go #tb-go-word::before { content: "◉ "; color: #c8402a; }
@media (prefers-reduced-motion: reduce) { .board-lot--urgent .lot-tape { animation: none !important; } }

/* === deploy/draft/fight polish B: the camera and a new catch ============================ */
/* 221-222: a full roster or a recruit is said as a slip (app.js), with a buzz. */
/* 223. A fresh catch is labelled as new above the card, in brass. */
.reveal-pos.is-new { margin-top: -4px; align-self: center; color: #17140c; background: var(--brass); box-shadow: 0 3px 10px rgba(232, 178, 58, 0.35); }
/* 224. The viewfinder corners breathe while the camera is looking. */
.stage.is-live .stage__corners i { animation: corner-breathe 2.4s ease-in-out infinite; }
@keyframes corner-breathe { 50% { opacity: 0.55; } }
/* 225. A fine crosshair in the middle, to aim at the thing. */
.stage.is-live:not(.is-frozen) .stage__corners::after { content: ""; position: absolute; left: 50%; top: 50%; width: 22px; height: 22px; transform: translate(-50%, -50%); background: linear-gradient(rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.55)) center / 1.5px 100% no-repeat, linear-gradient(rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.55)) center / 100% 1.5px no-repeat; opacity: 0.7; pointer-events: none; }
/* 226. The thinking readout has a blinking cursor; 227. and a phosphor glow. */
.stage__lcd::after { content: "▌"; margin-left: 2px; animation: lcd-cursor 1s steps(2, jump-none) infinite; }
@keyframes lcd-cursor { 50% { opacity: 0; } }
.stage__lcd { text-shadow: 0 0 6px rgba(160, 255, 120, 0.55); }
/* 228. The print coming out has weight: a shadow and a slight curl. */
.print__paper { box-shadow: 0 14px 28px rgba(0, 0, 0, 0.55), 0 2px 0 rgba(255, 255, 255, 0.4) inset; border-radius: 2px 2px 6px 6px; }
/* 229. A stamp on the print slams on. */
.print__stamp:not([hidden]) { animation: stamp-in 0.4s cubic-bezier(0.2, 1.6, 0.4, 1) 0.15s both; }
/* 230. Refused: the frame flashes red once. */
.stage.is-refused { animation: refused-flash 0.6s ease-out; }
@keyframes refused-flash { 30% { box-shadow: inset 0 0 0 4px rgba(200, 64, 42, 0.9), inset 0 0 60px rgba(200, 64, 42, 0.45); } }
/* 231. The hint eases in when it changes. */
#scan-hint.is-new { animation: line-in 0.3s ease-out; }
/* 232. The flash is warm, like a real bulb. */
.stage__flash { background: radial-gradient(circle at 50% 40%, #fffdf2, #fff3d6 60%, #ffe7b3); }
/* 233. While it thinks, the world around the photo dims. */
.stage.is-reading::after { background: radial-gradient(120% 90% at 50% 45%, transparent 40%, rgba(0, 0, 0, 0.65) 100%); transition: background 0.4s ease; }
/* 234. Eye tools are round chips you can hit. */
.eye-tools button { min-width: 38px; min-height: 38px; border-radius: 19px; border: 1px solid rgba(255, 246, 214, 0.2); background: rgba(255, 246, 214, 0.08); color: #f1e8d2; font-weight: 800; cursor: pointer; }
.eye-tools button:active { scale: 0.92; }
/* 235. The feeding explainer is a paper chit. */
.offer:not([hidden]) { background: #f3ecda; color: #1d1a14; border-left-color: var(--brass-deep); border-radius: 3px; box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4); }
.offer__head { font-family: var(--stencil); letter-spacing: 0.05em; text-transform: uppercase; }
/* 236. The feed note is a quiet brass line. */
.feed-note:not([hidden]) { padding: 6px 10px; border-left: 3px solid var(--brass); background: rgba(232, 178, 58, 0.08); border-radius: 2px; }
/* 237. Notes about a new catch arrive one by one. */
.reveal-notes__line { animation: rise-in 0.32s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
.reveal-notes__line:nth-child(2) { animation-delay: 0.12s; }
.reveal-notes__line:nth-child(3) { animation-delay: 0.24s; }
.reveal-notes__line:nth-child(n+4) { animation-delay: 0.36s; }
/* 238. A Find Me that is done goes green with a tick. */
.quest.is-done { border-color: #4fb35a; }
.quest.is-done .quest__state { background: #2f6b3c; color: #f3ecda; }
/* 239. The spirit level lights green in the middle, so level reads at a glance. */
.stage__level::after { content: ""; position: absolute; left: 50%; top: 1px; bottom: 1px; width: 2px; margin-left: -1px; background: rgba(127, 207, 138, 0.7); border-radius: 1px; }
/* 240. The shutter area under the camera sits on a soft dark shelf, so the Find Me strip
   and hint do not float on the photo. */
.view--scan .quest, .view--scan #scan-hint { position: relative; z-index: 2; }
@media (prefers-reduced-motion: reduce) { .stage__corners i, .stage__lcd::after, .print__stamp, .stage.is-refused, #scan-hint, .reveal-notes__line { animation: none !important; } }

/* === deploy/draft/fight polish C: the fight board ======================================= */
/* 241. The called move is a stencilled headline with a brass swipe under it. */
.called__move { font-family: var(--stencil); letter-spacing: 0.04em; position: relative; }
.called__move::after { content: ""; display: block; height: 4px; margin: 6px auto 0; width: 60%; background: var(--brass); border-radius: 2px; animation: swipe-in 0.35s cubic-bezier(0.2, 0.9, 0.3, 1) both; transform-origin: left; }
@keyframes swipe-in { from { transform: scaleX(0); } }
/* 242. Who called it, in small spaced capitals. */
.called__by { font-family: var(--label); letter-spacing: 0.16em; text-transform: uppercase; }
/* 243. Your turn: a warm pool of light under your fighter; 244. theirs: a cold one. */
.fighter { position: relative; isolation: isolate; }
.fighter::after { content: ""; position: absolute; left: 10%; right: 10%; bottom: -10px; height: 22px; border-radius: 50%; filter: blur(10px); opacity: 0; transition: opacity 0.3s ease; pointer-events: none; z-index: -1; }
#tcg-board:has(#moves .move:not(:disabled):not(.is-cooling)) .fighter--you::after { opacity: 0.8; background: rgba(232, 178, 58, 0.55); }
#tcg-board:has(#waiting:not([hidden])) .fighter--them::after { opacity: 0.7; background: rgba(120, 170, 220, 0.5); }
/* 245. Waiting for them has dots that walk. */
.waiting:not([hidden])::after { content: "…"; display: inline-block; width: 1.1em; overflow: hidden; vertical-align: bottom; animation: ellipsis 1.4s steps(4, end) infinite; }
/* 246. A move you can take lifts a little under the cursor. */
@media (hover: hover) { .move:not(:disabled):not(.is-cooling):hover { transform: translateY(-2px); } }
/* 247. The stat a move uses is in small capitals. */
.move__stat { font-family: var(--label); letter-spacing: 0.06em; text-transform: uppercase; font-size: 11px; }
/* 248. Its miss chance is a muted red. */
.move__miss { color: #a8503b; }
/* 249. Three moves, three equal tiles. */
.moves { align-items: stretch; }
.moves .move { height: 100%; }
/* 250. Swap shows how many are waiting on the bench as a round badge. */
.swapper__sub { display: inline-block; margin-top: 2px; padding: 1px 7px; border-radius: 9px; background: rgba(29, 26, 20, 0.08); }
/* 251. Your squad's dots have a tiny label, so they read as yours. */
.tray__bar .squad--you::before { content: "Yours"; margin-right: 6px; font-family: var(--label); font-size: 9px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: #8f8676; align-self: center; }
.tray__bar .squad--you { display: flex; align-items: center; }
/* 252. The turn tape is stencilled. */
.hud__turn { font-family: var(--stencil); letter-spacing: 0.08em; }
/* 253. Fighter names sit on neat dark plates. */
.fighter__name { border-radius: 3px; letter-spacing: 0.06em; }
/* 254. Type tags under the gauges get a little breathing room. */
.fighter__tags { margin-top: 4px; }
/* 255. A health number that changes flashes red. */
.health__num.is-hit { animation: num-hit 0.5s ease-out; }
@keyframes num-hit { 0% { color: #ffb4a8; transform: scale(1.25); } 100% { color: #fff; transform: none; } }
/* 256. A fighter that takes a hit has its name plate flash red. */
.fighter.is-struck .fighter__name { animation: plate-hit 0.45s ease-out; }
@keyframes plate-hit { 0% { background: #a3281b; } 100% { background: inherit; } }
/* 257. Knocked out: the fighter slumps and greys. */
.fighter.is-down .fighter__slot { filter: grayscale(0.85) brightness(0.8); rotate: -4deg; transition: filter 0.5s ease, rotate 0.5s ease; }
/* 258. The mid-fight catch is a paper call to action. */
.field-call:not([hidden]) { background: rgba(21, 18, 15, 0.94); border-top: 3px solid var(--brass); box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.6); }
.field-call__head { font-family: var(--stencil); letter-spacing: 0.04em; text-transform: uppercase; }
/* 259. Their last-chance hunt reads as a countdown board. */
.their-hunt__head { font-family: var(--stencil); letter-spacing: 0.04em; text-transform: uppercase; }
.their-hunt__clock, .field-call__clock { font-variant-numeric: tabular-nums; font-family: var(--display); font-weight: 900; }
/* 260. A soft spotlight falls on the middle of the ring. */
.ring { position: relative; }
.ring::before { content: ""; position: absolute; inset: -10% 5% 0; background: radial-gradient(ellipse at 50% 45%, rgba(255, 240, 210, 0.08), transparent 65%); pointer-events: none; z-index: 0; }
@media (prefers-reduced-motion: reduce) { .called__move::after, .waiting::after, .health__num.is-hit, .fighter.is-struck .fighter__name { animation: none !important; } }

/* === deploy/draft/fight polish D: results, prizes, the stand-off, asking ================= */
/* 261. The result's headline drops on like a stamp. */
#tcg-over:not([hidden]) .receipt-sheet__title { animation: stamp-in 0.45s cubic-bezier(0.2, 1.5, 0.4, 1) 0.4s both; }
/* 262. Its subtitle is set small and spaced. */
.receipt-sheet__sub { letter-spacing: 0.04em; }
/* 263. Whoever is still standing has a faint green band behind their line;
   264. whoever went down is quieter. */
.lanes > *:has(.recap__flag.is-standing) { background: rgba(79, 179, 90, 0.1); border-radius: 3px; }
.lanes > *:has(.recap__flag.is-out) { opacity: 0.75; }
/* 265. The foot line (what the fight bought you) is a brass note. */
#over-sub { padding: 8px 12px; border: 1.5px dashed rgba(154, 106, 16, 0.55); border-radius: 4px; }
/* 266. Your record against them is a tidy ledger. */
.ledger { font-variant-numeric: tabular-nums; }
/* 267. The duel line sits in a box of its own. */
.receipt-sheet__duel:not([hidden]) { padding: 8px 10px; background: rgba(29, 26, 20, 0.05); border-radius: 3px; }
/* 268. A card you won glows gold. */
.receipt-sheet__kept:not([hidden]) { box-shadow: 0 0 0 2px var(--brass), 0 0 18px rgba(232, 178, 58, 0.45); border-radius: 4px; animation: kept-glow 2s ease-in-out infinite; }
@keyframes kept-glow { 50% { box-shadow: 0 0 0 2px var(--brass), 0 0 30px rgba(232, 178, 58, 0.7); } }
/* 269-270. The prize screen: stencilled heading, and the claim is the thing to press. */
.spoils__head { font-family: var(--stencil); letter-spacing: 0.04em; }
#spoils:not([hidden]) .btn--primary { animation: again-pulse 1.8s ease-in-out 0.8s infinite; }
/* 271. The stand-off bill is stencilled; 272. its countdown pops. */
.standoff__bill { font-family: var(--stencil); letter-spacing: 0.04em; }
.standoff__count { animation: count-pop 0.9s cubic-bezier(0.2, 1.4, 0.4, 1) infinite; }
@keyframes count-pop { 0% { transform: scale(1.4); opacity: 0.4; } 30% { transform: scale(1); opacity: 1; } }
/* 273. The Hunt's code is a proper paper ticket; 274. its seats are tickets too. */
.race__code { background: #f3ecda; color: #1d1a14; border: 0; box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4); }
.race__seat { border-radius: 4px; box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3); }
/* 275. A challenge card has weight. */
.chal__card { box-shadow: 0 14px 30px rgba(0, 0, 0, 0.55); }
/* 276. The live clock is a ring that reads at a glance. */
.clock:not([hidden]) { border-radius: 50%; box-shadow: 0 0 0 2px rgba(232, 178, 58, 0.5); }
/* 277. When a move is called, the board behind it dims. */
.called:not([hidden]) { backdrop-filter: brightness(0.7); }
/* 278. Tapping the commentary to move on gives a little. */
.teleprompt:active { transform: rotate(-0.6deg) scale(0.99); }
/* 279-280. Leaving a fight and feeding a card ask on paper, not in a browser box. */
.ask .ask__body { margin: 4px 0 6px; font-size: 15px; line-height: 1.45; color: #2e2920; }
.ask .ask__danger { background-color: #a3281b; color: #fffaf0; text-shadow: none; }

/* === deploy/draft/fight polish E: feel, keys, access ===================================== */
/* 281-284, 294: buzzes on a move, a swap, a hit taken, a new catch (by rarity), a refusal (app.js). */
/* 285. A rare catch lights the edge of the screen in its tier's colour. */
#view-reveal[data-tier="Rare"] { box-shadow: inset 0 0 0 3px var(--t-rare), inset 0 0 50px rgba(59, 130, 246, 0.35); }
#view-reveal[data-tier="Epic"] { box-shadow: inset 0 0 0 3px var(--t-epic), inset 0 0 60px rgba(192, 132, 252, 0.4); }
#view-reveal[data-tier="Legendary"] { box-shadow: inset 0 0 0 3px var(--t-legendary), inset 0 0 70px rgba(251, 113, 133, 0.45); animation: legend-edge 2s ease-in-out infinite; }
@keyframes legend-edge { 50% { box-shadow: inset 0 0 0 3px var(--t-legendary), inset 0 0 110px rgba(251, 113, 133, 0.6); } }
/* 286. Moves are labelled for screen readers by what they show (native button text). */
/* 287-289, 292-293, 296: keys (app.js). */
/* 290. When it is your turn, the moves are titled "Your move". */
.moves:has(.move:not(:disabled):not(.is-cooling))::before { content: "Your move"; grid-column: 1 / -1; justify-self: center; margin-bottom: 2px; font-family: var(--label); font-size: 10px; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; color: var(--brass); }
.moves { display: grid; }
/* 291. The stand-off says it can be skipped. */
.standoff::after { content: "Tap to skip"; position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%); font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: rgba(255, 246, 214, 0.5); pointer-events: none; }
/* 295. The print lands with a little bounce when the reading is done. */
.stage.is-developing .print__paper { animation: print-land 0.5s cubic-bezier(0.2, 1.5, 0.4, 1); }
@keyframes print-land { 0% { translate: 0 -8px; } 100% { translate: 0 0; } }
/* 297. Tray slots show focus for a keyboard. */
.tray-slot:focus-visible { outline: 2.5px solid var(--brass); outline-offset: 2px; }
/* 298. Motion off stops everything added for these screens. */
@media (prefers-reduced-motion: reduce) {
  #view-reveal[data-tier], .stage.is-developing .print__paper, .receipt-sheet__kept, #spoils .btn--primary, .standoff__count, #tcg-over .receipt-sheet__title { animation: none !important; }
}
/* 299. Asked for more contrast: the moves get firmer edges. */
@media (prefers-contrast: more) { .move { border-bottom-color: #3a342b; border-right-color: #3a342b; } .move__miss { color: #7a2415; } }
/* 300. Long-pressing a card mid-fight does not open the phone's image menu. */
#tcg-board .card, #hand .card, #build-grid .card, .reveal-slot .card { -webkit-touch-callout: none; }

/* --- the Estate League ---------------------------------------------------------------- */
.rival--league { background: linear-gradient(135deg, #f3ecda, #eadfbf); box-shadow: 0 0 0 2px var(--brass), 0 6px 14px rgba(0, 0, 0, 0.4); }
.rival--league .rival__pin { background: radial-gradient(circle at 34% 28%, #fff4c8 0 16%, var(--brass) 46%, var(--brass-deep) 100%); }
.rival--league .rival__name { font-family: var(--stencil); letter-spacing: 0.04em; text-transform: uppercase; }
.chip--league { box-shadow: 0 0 0 1.5px var(--brass), 0 3px 8px rgba(0, 0, 0, 0.35); }
.league__intro { margin: 0; font-size: 13px; line-height: 1.45; color: #b8ad97; }
.league__list { display: grid; gap: 10px; margin: 0; padding: 0; list-style: none; }
.league-row {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  font: inherit;
  text-align: left;
  color: #1d1a14;
  background: #f3ecda;
  border: 0;
  border-left: 5px solid var(--rival, #6d6352);
  border-radius: 4px;
  box-shadow: 0 5px 12px rgba(0, 0, 0, 0.4);
  cursor: pointer;
  transition: transform 0.12s ease;
}
.league-row:active { transform: translateY(2px); }
.league-row__face { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 50%; font-family: var(--stencil); font-size: 20px; font-weight: 900; color: #fffaf0; background: var(--rival, #6d6352); box-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.3); }
.league-row__body { display: grid; gap: 3px; min-width: 0; }
.league-row__name { font-family: var(--display); font-weight: 900; font-size: 15px; text-transform: uppercase; line-height: 1.1; }
.league-row__bio { font-family: var(--receipt); font-size: 14px; line-height: 1.2; color: #3d3528; }
.league-row__meta { display: flex; align-items: center; gap: 8px; margin-top: 2px; font-family: var(--label); font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #6d6352; }
.league-row__skill { display: inline-flex; gap: 2px; }
.league-row__skill i { width: 6px; height: 6px; border-radius: 50%; background: rgba(29, 26, 20, 0.18); }
.league-row__skill i.is-on { background: var(--rival, #8f2f22); }
.league-row__wl { font-variant-numeric: tabular-nums; }
.league-row__stars { font-size: 16px; letter-spacing: 1px; color: var(--brass-deep); white-space: nowrap; }
.league-row.is-beaten .league-row__face::after { content: "✓"; position: absolute; }
.league-row__face { position: relative; }
.league-row.is-locked { background: #d9d2c2; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); }
.league-row.is-locked .league-row__face { background: #8a8374; }
.league-row.is-locked .league-row__name { color: #6d6352; }
.league-row__lock { font-size: 18px; filter: grayscale(1); opacity: 0.7; }
.league-row.is-beaten { border-left-color: #4fb35a; }

/* --- Deploy: fewer buttons ---------------------------------------------------------------
   Your three and one big button. Everything about who and how lives in the fight sheet;
   the rest of the old shelf now lives where it belongs (Find me on the camera, presents
   on each card) and More is a small icon in the corner. */
.tb-holder { display: none !important; }
.tb-more { display: grid; place-items: center; width: 36px; height: 36px; margin-left: 4px; padding: 0; border: 1px solid rgba(255, 246, 214, 0.18); border-radius: 50%; background: rgba(255, 246, 214, 0.06); cursor: pointer; }
.tb-more svg { width: 18px; height: 18px; fill: #e6dcc2; }
.tb-more:active { scale: 0.92; }
.tb-right { display: flex; align-items: center; }
.step--table .tb-right { padding-right: 0; }

/* --- the fight sheet ------------------------------------------------------------------ */
.who { gap: 18px; }
.who__section { display: grid; gap: 10px; }
.who__h { margin: 0; font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; color: #8f8676; }
.who__ways { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.who__ways .rival, .who__grid .rival, .who__grid .chip { width: 100%; }
.who__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
/* The two "other ways" as matching tiles: the house and the Hunt. */
.who__grid > .rival, .who__grid > .chip {
  display: grid;
  align-content: center;
  justify-items: start;
  gap: 3px;
  min-height: 84px;
  padding: 12px 12px 12px 14px;
  text-align: left;
  font: inherit;
  color: #1d1a14;
  background: #f3ecda;
  border: 0;
  border-radius: 6px;
  box-shadow: 0 5px 12px rgba(0, 0, 0, 0.4);
  cursor: pointer;
}
.who__grid > .rival .rival__pin, .who__grid > .rival .rival__deck { display: none; }
.who__grid > .rival .rival__name, .who__grid > .chip .chip__k { font-family: var(--display); font-weight: 900; font-size: 15px; text-transform: uppercase; }
.who__grid > .rival .rival__sub { font-size: 12px; line-height: 1.3; color: #5d5445; }
.who__grid > .chip .mode__mark { width: 22px; height: 22px; stroke: #1d1a14; }
.who__grid > .chip .chip__v { font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #6d6352; }
.who__grid > .chip .chip__v::before { content: "Race · "; }
.who__grid > *:active { transform: translateY(2px); }
/* Friends as rows with a record. */
.who__people { display: grid; gap: 8px; padding: 0; }
.who__people .tb-foe { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; min-height: 52px; padding: 10px 14px; text-align: left; color: #1d1a14; background: #f3ecda; border: 0; border-radius: 6px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.35); }
.who__people .tb-foe b { font-family: var(--display); font-weight: 900; font-size: 15px; text-transform: uppercase; }
.who__people .tb-foe b::before { content: attr(data-i); }
.who__people .tb-foe em { font-family: var(--label); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #6d6352; }
.who__people .tb-foe--add { justify-content: center; background: transparent; color: #e6dcc2; border: 1.5px dashed rgba(232, 178, 58, 0.45); box-shadow: none; }
.who__people .tb-foe--add b, .who__people .tb-foe--add em { color: #e6dcc2; }

/* The league card: next rival, big, with one clear fight button. */
.who-hero {
  display: grid;
  gap: 12px;
  padding: 16px;
  color: #1d1a14;
  background: linear-gradient(160deg, #f6efdc, #eadfbf);
  border-radius: 8px;
  border-top: 6px solid var(--rival, var(--brass));
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.5), 0 0 0 1.5px rgba(232, 178, 58, 0.5);
}
.who-hero__head { display: grid; grid-template-columns: 58px minmax(0, 1fr); gap: 12px; align-items: center; }
.who-hero__face { display: grid; place-items: center; width: 58px; height: 58px; border-radius: 50%; font-family: var(--stencil); font-size: 24px; font-weight: 900; color: #fffaf0; background: var(--rival, #6d6352); box-shadow: inset 0 -4px 0 rgba(0, 0, 0, 0.25), 0 3px 8px rgba(0, 0, 0, 0.35); }
.who-hero__text { display: grid; gap: 2px; min-width: 0; }
.who-hero__kicker { font-family: var(--label); font-size: 10px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: #8f6a1c; }
.who-hero__name { font-family: var(--stencil); font-size: 24px; font-weight: 900; letter-spacing: 0.02em; text-transform: uppercase; line-height: 1; }
.who-hero__bio { font-family: var(--receipt); font-size: 15px; line-height: 1.2; color: #3d3528; }
.who-hero__foot { display: flex; align-items: center; gap: 10px; }
.who-hero__stars { flex: 1; height: 8px; border-radius: 4px; background: rgba(29, 26, 20, 0.12); overflow: hidden; }
.who-hero__stars i { display: block; height: 100%; background: linear-gradient(90deg, var(--brass), #f5d98a); border-radius: 4px; }
.who-hero__starn { font-family: var(--label); font-size: 12px; font-weight: 800; letter-spacing: 0.06em; color: #8f6a1c; white-space: nowrap; }
.who-hero__go { width: 100%; font-size: 18px; }
.who-hero__ladder { justify-self: center; font-family: var(--label); font-size: 12px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #5d5445; }
.who__grid > .chip .chip__v::before { content: none; }
.who__grid > .chip .chip__v { white-space: normal; line-height: 1.3; letter-spacing: 0.04em; text-transform: none; font-size: 12px; font-weight: 600; color: #5d5445; }
#whofight .who__grid .rival::after { content: none; }
.who__mark { width: 22px; height: 22px; fill: none; stroke: #1d1a14; stroke-width: 1.8; }
.who__grid > .rival .rival__sub { font-family: var(--label); font-size: 12px; font-weight: 600; letter-spacing: 0.04em; }
/* Welcome back: "You earned 1,240 while you were away, 3h 20m". */
.welcome__n { display: grid; gap: 2px; }
.welcome__n b { order: 0; }
#welcome-away { font-family: var(--label); font-size: 13px; letter-spacing: 0.06em; text-transform: none; color: #6d6352; }
.welcome__jobs:not([hidden]) { margin: 0; padding: 8px 10px; border-left: 3px solid #4e8c52; background: rgba(78, 140, 82, 0.12); border-radius: 2px; font-size: 14px; }

/* --- onboarding --------------------------------------------------------------------- */
.whatsnew { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.whatsnew button { display: block; width: 100%; padding: 10px 30px 10px 12px; text-align: left; font: inherit; font-size: 14px; line-height: 1.4; color: #2e2920; background: rgba(255, 255, 255, 0.55); border: 0; border-radius: 4px; box-shadow: 0 1px 0 rgba(60, 40, 10, 0.15); cursor: pointer; position: relative; }
.whatsnew button b { display: block; font-family: var(--display); font-weight: 900; font-size: 14px; text-transform: uppercase; color: #1d1a14; }
.whatsnew button::after { content: "›"; position: absolute; right: 12px; top: 50%; transform: translateY(-50%); font-size: 20px; font-weight: 900; color: rgba(29, 26, 20, 0.35); }
.whatsnew button:active { transform: translateY(1px); }
#whatsnew:not([hidden]) .whatsnew li { animation: rise-in 0.32s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
#whatsnew .whatsnew li:nth-child(2) { animation-delay: 0.07s; }
#whatsnew .whatsnew li:nth-child(3) { animation-delay: 0.14s; }
#whatsnew .whatsnew li:nth-child(4) { animation-delay: 0.21s; }
#whatsnew .whatsnew li:nth-child(5) { animation-delay: 0.28s; }

.tip {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(86px + var(--shutter-rise) + 10px + env(safe-area-inset-bottom, 0px));
  z-index: 68;
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 10px 10px 10px 12px;
  color: #1d1a14;
  background: #f3ecda;
  border-radius: 6px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.55);
  animation: toast-in-tip 0.35s cubic-bezier(0.2, 1.2, 0.4, 1);
}
@keyframes toast-in-tip { from { opacity: 0; transform: translateY(14px); } }
.tip__gary { width: 38px; height: 42px; }
.tip__text { margin: 0; font-family: var(--receipt); font-size: 15px; line-height: 1.25; }
.tip__ok { align-self: center; min-height: 36px; padding: 6px 12px; font-family: var(--label); font-size: 12px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #17140c; background: var(--brass); border: 0; border-radius: 18px; cursor: pointer; }
@media (prefers-reduced-motion: reduce) { .tip, #whatsnew .whatsnew li { animation: none !important; } }
#gary-says.is-changing { opacity: 0; }

/* The measured visible height (set in index.html) wins over the viewport units, which on
   some phones start out including the address bar. */
html, body { height: var(--app-h, 100dvh); }
body { min-height: 0; }
.about-copy { margin: 6px 0 0; font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #6d6352; text-align: center; }
/* The Android app, offered once on the roster and always in Everything else. */
.get-app-banner:not([hidden]) { display: grid; gap: 10px; padding: 12px 14px; color: #1d1a14; background: #f3ecda; border-left: 5px solid #4e8c52; border-radius: 4px; box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4); }
.get-app-banner p { margin: 0; font-size: 14px; line-height: 1.4; }
.get-app-banner__row { display: flex; gap: 10px; align-items: center; }
.get-app-banner .btn--ghost { color: #2b261d; text-decoration-color: rgba(43, 38, 29, 0.45); }
a.locker__row.get-app { text-decoration: none; }
a.locker__row.get-app .locker__t::before { content: "⬇ "; color: #4e8c52; }

/* --- progress, drawn -------------------------------------------------------------------
   Under the wad, what the roster earns and the next level-up; on a card, its level out of
   thirty and the wad toward the next; on a locked job, the cards that are nearly there. */
.wad-goal {
  display: grid; gap: 5px; width: 100%; margin: 6px 0 2px; padding: 8px 12px;
  border: 1px solid rgba(232, 178, 58, 0.22); border-radius: 8px;
  background: rgba(20, 18, 15, 0.55); color: #d9d2c3; text-align: left; font: inherit; cursor: pointer;
}
.wad-goal__rate { font-size: 12px; opacity: 0.8; }
.wad-goal__next { font-size: 13px; font-weight: 600; color: #f1e6c9; }
.wad-goal__bar, .lvlprog__bar, .nearly__bar {
  display: block; height: 6px; border-radius: 3px; background: rgba(255, 255, 255, 0.09); overflow: hidden;
}
.wad-goal__bar b, .lvlprog__bar b, .nearly__bar b {
  display: block; height: 100%; width: 0; border-radius: inherit;
  background: linear-gradient(90deg, #b8862a, #e8b23a); transition: width 0.5s ease;
}
.wad-goal.is-ready { border-color: rgba(232, 178, 58, 0.6); }
.wad-goal.is-ready .wad-goal__bar b { background: #7fc46a; }
.wad-goal.is-ready .wad-goal__next { color: #a8e08f; }

.lvlprog { display: grid; gap: 6px; margin: 4px 0 10px; padding: 10px 12px; border-radius: 8px; background: rgba(20, 18, 15, 0.5); color: #d9d2c3; }
.lvlprog__head { display: flex; justify-content: space-between; align-items: baseline; margin: 0; font-size: 13px; }
.lvlprog__head b { font-size: 15px; color: #f1e6c9; }
.lvlprog__bar--steps { height: 8px; background-image: repeating-linear-gradient(90deg, transparent 0 calc(100% / 30 - 1px), rgba(0, 0, 0, 0.45) calc(100% / 30 - 1px) calc(100% / 30)); }
.lvlprog__bar--steps b { background: linear-gradient(90deg, #6f8fb0, #a9c6e0); }
.lvlprog__note { font-size: 12px; opacity: 0.85; }
.lvlprog__gig { display: block; font-size: 12px; font-style: italic; opacity: 0.9; }

.nearly { display: grid; gap: 10px; }
.nearly__row { display: grid; grid-template-columns: 1fr auto; gap: 3px 10px; align-items: center; }
.nearly__who { grid-column: 1; padding: 0; border: 0; background: none; color: inherit; font: inherit; font-weight: 700; text-align: left; text-decoration: underline dotted; cursor: pointer; }
.nearly__gap { grid-column: 1; font-size: 12px; opacity: 0.8; }
.nearly__bar { grid-column: 1; }
.nearly__go { grid-column: 2; grid-row: 1 / span 3; white-space: nowrap; }
.ticket.is-nearly .ticket__go b { color: #e8b23a; }
@media (prefers-reduced-motion: reduce) { .wad-goal__bar b, .lvlprog__bar b, .nearly__bar b { transition: none; } }

/* --- display bug hunt, 24 Sep ------------------------------------------------------- */
/* The Bulk it up panel went cream in a polish pass and kept its dark-mode ink. */
#offer .offer__list li { color: #3a342b; }
#offer .offer__list li b { color: #1d1a14; }
#offer .offer__cost { color: #6b6255; }
#offer .offer__head { color: #8a5f12; }
/* The level box sat shrink-wrapped in the middle of the column. */
.lvlprog { justify-self: stretch; width: 100%; box-sizing: border-box; }
/* The Hunt tile's line was capped at 11ch by the one-line chip rule. */
.who__grid > .chip .chip__v { max-width: none; overflow: visible; text-overflow: clip; }
/* No camera: the note is centred in the viewfinder, not pinned to its left edge. */
#camera-msg:not([hidden]) {
  inset: auto;
  top: 50%;
  left: 50%;
  width: calc(100% - 56px);
  box-sizing: border-box;
  transform: translate(-50%, -50%) rotate(-1deg);
}
/* The record: the stamp is wider than the room the note left for it, and the totals ran
   to the very edge of the slate. */
.record__note { padding-right: 132px; }
#record .record__all { padding-left: 13px; padding-right: 13px; }
/* A job's "nearly" arrow was drawn at the padlock's size in a faint ink. */
.ticket.is-nearly .ticket__go b { font-size: 26px; line-height: 1; color: #b8862a; }
.ticket.is-nearly .ticket__go small { color: #8a5f12; }
/* A rival's three on the ladder. */
.league-row__deck { display: flex; gap: 5px; margin-top: 6px; }
.league-row__card { width: 34px; height: 34px; object-fit: cover; border-radius: 4px; border: 2px solid #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); background: #ddd4c0; }
.league-row__card:nth-child(1) { rotate: -4deg; }
.league-row__card:nth-child(3) { rotate: 3deg; }
/* A first-time tip sat just above the bar, which is exactly where each screen keeps the
   button the tip is talking about (Enter the Arena, the shutter). It sits at the top. */
.tip { top: calc(env(safe-area-inset-top, 0px) + 10px); bottom: auto; }
@keyframes toast-in-tip { from { opacity: 0; transform: translateY(-14px); } }
/* The House chit sat in the art's bottom corner, where a two-line cut-out title rises into
   the picture, and covered its first letters ("TEAPOT IN A COSY" read "EAPOT"). Same chit,
   top corner of the art, where nothing else lives. */
.card__house { top: 0.35em; bottom: auto; }
/* A long name on a shiny card runs to three lines and climbs to where the seal rides the
   window's edge; the seal was drawn over the end of the name. The name reads first. */
.card--shiny .card__title { z-index: 13; }
/* The sort button's order word hangs 14px below it and ran into whatever came next (the
   Android banner, the first row of cards). The row leaves room for it. */
.sift { margin-bottom: 18px; }
/* A 320px phone: two cards across, not one lonely card in a wide gutter. All five stats
   still fit at this size (checked with the longest names and the gold receipt). */
@media (max-width: 359px) {
  .deck-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 8px; --em-grid: 10.2px; }
}
/* On a phone the picked tray's three slots are ~95px each, and the type mark and the
   remove cross left the name about twenty pixels: "G…", "T…". The mark goes (the card
   below says its type), the number shrinks, and the name gets two lines. */
@media (max-width: 420px) {
  .tray-slot { gap: 4px; padding: 5px 18px 5px 5px; }
  .tray-slot .mk { display: none; }
  .tray-slot__n { flex: 0 0 auto; }
  .tray-slot__name {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
    white-space: normal; text-overflow: clip; font-size: 11px; line-height: 1.1; letter-spacing: 0;
    overflow-wrap: anywhere;
  }
  .tray-slot__x { right: 4px; font-size: 14px; }
}
/* The pick number rides above the card's top edge; the sticky bar above the grid was
   painting over it on the first row. */
#build-grid { padding-top: 12px; }
/* contain: content includes paint containment, which clips anything drawn past the card's
   edge — and the pick number is drawn riding over the top edge. The picker keeps layout
   containment and lets the number show. */
#build-grid .card { contain: layout style; }
/* The bench card was held at 18.4em for four rows it no longer prints (one stat, no cost
   line), which left a blank third on every card and pushed the health bars under the bar. */
.card--hand { height: auto; }
.card--hand .card__receipt { min-height: 0; }
/* Hold the line sat under the bench's two cards, which on a short phone is under the bar.
   It sits beside the heading instead, where it can always be reached. */
#bench-close { position: absolute; top: 10px; right: 12px; margin: 0; width: auto; min-height: 36px; padding: 6px 12px; font-size: 12px; }
/* The wad goal names a card: its photograph beside the words, and a go arrow, so it reads
   as a way to that card rather than a caption. */
.wad-goal { grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; }
.wad-goal > * { grid-column: 1; }
.wad-goal.has-card:has(.wad-goal__thumb:not([hidden])) { grid-template-columns: 40px minmax(0, 1fr) auto; }
.wad-goal__thumb { grid-column: 1; grid-row: 1 / span 3; width: 40px; height: 40px; align-self: center; object-fit: cover; border-radius: 5px; border: 2px solid #f1e6c9; }
.wad-goal:has(.wad-goal__thumb:not([hidden])) > :not(.wad-goal__thumb):not(.wad-goal__go) { grid-column: 2; }
.wad-goal__go { grid-column: -2 / -1 !important; grid-row: 1 / span 3; align-self: center; font-family: var(--label); font-size: 12px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #e8b23a; }
.wad-goal.is-ready .wad-goal__go { color: #a8e08f; }
#reveal-level.is-pointed { animation: level-pointed 1.2s ease-in-out 2; }
@keyframes level-pointed { 50% { transform: scale(1.08); filter: drop-shadow(0 0 8px rgba(232, 178, 58, 0.8)); } }
@media (prefers-reduced-motion: reduce) { #reveal-level.is-pointed { animation: none; } }
/* A just-emptied till shows the card's rate instead of vanishing, quieter than a till
   with something in it. */
.till.is-empty.is-rate { visibility: visible; opacity: 0.75; }

/* === the draft (Deploy's pick-three), polished ========================================
   One header row instead of a floating chit; the tray shows photographs; a single line
   says what the three add up to; the dead space above the grid goes. Cards untouched. */
#pick-three { gap: 12px; }
#build-grid { padding-top: 6px; }

/* Header: "YOUR THREE  2/3  Power 111 · Fears Beast", one row, left to right. */
#pick-three .build-bar { justify-items: stretch; gap: 8px; padding: 6px 0 10px; }
.build-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 0 2px; }
.build-head__k { font-family: var(--label); font-size: 11px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: #b8ad97; }
.build-head .build-count { font-size: 13px; padding: 2px 8px; transform: rotate(-2deg); }
.build-sum { margin-left: auto; display: flex; align-items: center; gap: 6px; font-family: var(--label); font-size: 12px; color: #b8ad97; }
.build-sum__power { color: #f1e6c9; font-weight: 800; letter-spacing: 0.04em; }
.build-sum__risk { padding: 2px 7px; border-radius: 10px; background: rgba(232, 178, 58, 0.14); color: #e8c46a; font-weight: 700; }
.build-sum__risk.is-bad { background: rgba(200, 64, 42, 0.2); color: #f08a74; }

/* Tray: a photo in each filled slot, the pick number on its corner; the next empty slot
   says where to tap and glows faintly; later empty ones sit back. */
.tray-slot__face { position: relative; flex: 0 0 auto; width: 30px; height: 30px; }
.tray-slot__face img { width: 100%; height: 100%; object-fit: cover; border-radius: 4px; border: 1.5px solid #f1e6c9; display: block; }
.tray-slot__face .tray-slot__n { position: absolute; left: -6px; top: -6px; width: 17px; height: 17px; font-size: 10px; line-height: 17px; }
.tray-slot:has(.tray-slot__face) .mk { display: none; }
.tray-slot:not(.is-filled) { border-style: dashed; background: transparent; color: #8d8573; }
.tray-slot.is-next { border-color: rgba(232, 178, 58, 0.7); color: #e8c46a; animation: slot-next 1.8s ease-in-out infinite; }
@keyframes slot-next { 50% { box-shadow: 0 0 0 3px rgba(232, 178, 58, 0.18); } }

/* No opponent yet: the line becomes a question with one clear answer. */
.facing.is-nobody .facing__v { display: none; }
.facing.is-nobody .facing__who { font-size: 16px; color: #6d6252; }
.facing.is-nobody .facing__who::after { content: "?"; }
.facing.is-nobody .facing__change { padding: 4px 12px; border-radius: 14px; background: var(--ink-red, #b8402a); color: #fff; text-decoration: none; font-family: var(--label); font-weight: 800; font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; }

/* The dock: a solid shelf with an edge, not a smear over the cards. */
#build-dock { background: #15120f; box-shadow: 0 -10px 18px rgba(0, 0, 0, 0.55); border-top: 1px solid rgba(255, 246, 214, 0.08); }
#build-dock:not(.is-ready) #build-go:not(:disabled) { opacity: 0.92; }

@media (prefers-reduced-motion: reduce) { .tray-slot.is-next { animation: none; } }
.facing.is-nobody .facing__who { font-size: 0; }
.facing.is-nobody .facing__who::after { content: "Who are you fighting?"; font-size: 16px; }

/* === shinies, louder ==================================================================
   A shiny read as "a gold card". It now carries a real holo: a rainbow foil laid over
   the picture that slides with the phone and the clock, four star sparkles that flare
   in turn, and a halo that breathes wherever the card is shown, not only in the roster.
   The layout, the seal and the name plate are as they were. */
.card__holo {
  position: absolute;
  inset: 0;
  z-index: 7;
  pointer-events: none;
  border-radius: inherit;
  background:
    repeating-linear-gradient(115deg,
      rgba(255, 90, 160, 0.55) 0%, rgba(255, 210, 90, 0.55) 7%, rgba(120, 255, 170, 0.55) 14%,
      rgba(90, 190, 255, 0.55) 21%, rgba(190, 120, 255, 0.55) 28%, rgba(255, 90, 160, 0.55) 35%),
    repeating-linear-gradient(25deg, rgba(255, 255, 255, 0.18) 0 2px, transparent 2px 6px);
  background-size: 300% 300%, auto;
  background-position: calc(30% + var(--gx, 0) * 60%) calc(30% + var(--gy, 0) * 40%), 0 0;
  mix-blend-mode: color-dodge;
  opacity: 0.32;
  /* Strongest over the picture, fading out before the numbers so they stay readable. */
  -webkit-mask: linear-gradient(180deg, #000 0%, #000 42%, rgba(0, 0, 0, 0.25) 62%, rgba(0, 0, 0, 0.12) 100%);
  mask: linear-gradient(180deg, #000 0%, #000 42%, rgba(0, 0, 0, 0.25) 62%, rgba(0, 0, 0, 0.12) 100%);
  animation: holo-drift 7s ease-in-out infinite alternate;
}
@keyframes holo-drift { to { background-position: calc(80% + var(--gx, 0) * 60%) calc(70% + var(--gy, 0) * 40%), 0 0; } }
.card--finish-chrome .card__holo { opacity: 0.24; filter: saturate(0.4); }
.card--finish-opal .card__holo { opacity: 0.4; }

/* Four-point stars, not dots. */
.card__sparkle {
  position: absolute;
  width: 1.3em;
  height: 1.3em;
  z-index: 9;
  background: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 0 C11 7 13 9 20 10 C13 11 11 13 10 20 C9 13 7 11 0 10 C7 9 9 7 10 0Z' fill='white'/%3E%3C/svg%3E");
  filter: drop-shadow(0 0 0.25em var(--foil-glow, rgba(255, 220, 120, 0.9)));
  opacity: 0;
  transform: scale(0.2) rotate(0deg);
  animation: sparkle-flare 3.2s ease-in-out infinite;
}
.card__sparkle:nth-child(1) { left: 12%; top: 10%; animation-delay: 0s; }
.card__sparkle:nth-child(2) { right: 10%; top: 26%; animation-delay: 0.8s; width: 0.9em; height: 0.9em; }
.card__sparkle:nth-child(3) { left: 20%; top: 44%; animation-delay: 1.6s; width: 1em; height: 1em; }
.card__sparkle:nth-child(4) { right: 16%; bottom: 14%; animation-delay: 2.4s; width: 0.8em; height: 0.8em; }
@keyframes sparkle-flare {
  0%, 60%, 100% { opacity: 0; transform: scale(0.2) rotate(0deg); }
  70% { opacity: 1; transform: scale(1) rotate(45deg); }
  82% { opacity: 0.2; transform: scale(0.5) rotate(90deg); }
}

/* The halo, everywhere a shiny is shown. */
.card--shiny { animation: shiny-halo 2.6s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .card__holo { animation: none; }
  .card__sparkle { animation: none; opacity: 0.8; transform: scale(0.7) rotate(45deg); }
  .card--shiny { animation: none; }
}

/* The shiny's paper. The card's ::after IS its paper, and the roster's gleam replaced it
   with a streak that is transparent everywhere else, so a shiny in the grid had no paper
   at all and the mat showed through: shinies looked see-through. The paper is now solid
   foil in the finish's colours, and the gleam rides on top of it. */
.card--shiny::after,
.deck-grid .card--shiny::after {
  background:
    linear-gradient(115deg, transparent 38%, rgba(255, 252, 235, 0.9) 48%, rgba(255, 236, 170, 0.9) 52%, transparent 62%),
    linear-gradient(160deg, var(--foil-a) 0%, var(--foil-b) 38%, var(--foil-a) 62%, var(--foil-c) 100%);
  background-size: 260% 100%, 100% 100%;
  background-position: 120% 0, 0 0;
  background-color: var(--foil-b);
  mix-blend-mode: normal;
  opacity: 1;
}
.card--shiny::after { animation: shiny-gleam 3.4s cubic-bezier(0.6, 0, 0.4, 1) infinite; }
@keyframes shiny-gleam {
  0%, 55% { background-position: 120% 0, 0 0; }
  100%    { background-position: -40% 0, 0 0; }
}
/* The rainbow tints the picture rather than bleaching it. */
.card__holo { mix-blend-mode: overlay; opacity: 0.5; }
.card--finish-chrome .card__holo { opacity: 0.35; }
.card--finish-opal .card__holo { opacity: 0.6; }
@media (prefers-reduced-motion: reduce) { .card--shiny::after { animation: none; background-position: 50% 0, 0 0; } }

/* Faces: a Google photo where the letter disc was. */
#acct-open.is-in.has-face::before,
#opponent-who.has-face[data-initial]::before {
  content: "";
  background: var(--face) center / cover no-repeat, var(--brass);
  box-shadow: 0 0 0 2px rgba(255, 246, 214, 0.85);
}
.tb-foe__face { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; box-shadow: 0 0 0 2px rgba(255, 246, 214, 0.8); }
.tb-foe:has(.tb-foe__face) { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.acct-face { display: flex; align-items: center; gap: 8px; margin: 6px 0 10px; font-size: 14px; }
.acct-face input { width: 18px; height: 18px; accent-color: var(--brass, #e8b23a); }

/* Faces: a Google photo where the letter disc was. */
#acct-open.is-in.has-face::before,
#opponent-who.has-face[data-initial]::before {
  content: "";
  background: var(--face) center / cover no-repeat, var(--brass);
  box-shadow: 0 0 0 2px rgba(255, 246, 214, 0.85);
}
.tb-foe__face { position: absolute; top: 6px; right: 6px; width: 24px; height: 24px; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 2px rgba(255, 246, 214, 0.8); }
.acct-face { display: flex; align-items: center; gap: 8px; margin: 6px 0 10px; font-size: 14px; }
.acct-face input { width: 18px; height: 18px; accent-color: var(--brass, #e8b23a); }

/* Sound, in More: a row with a switch rather than a chevron. */
.locker__row--sound::after { content: none; }
.locker__row--sound .switch {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 44px; height: 26px; border-radius: 13px; background: rgba(0, 0, 0, 0.25);
  box-shadow: inset 0 0 0 1.5px rgba(0, 0, 0, 0.25); transition: background-color 0.2s ease;
}
.locker__row--sound .switch i {
  position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%;
  background: #fffaf0; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); transition: transform 0.2s ease;
}
.locker__row--sound[aria-pressed="true"] .switch { background: var(--brass, #e8b23a); }
.locker__row--sound[aria-pressed="true"] .switch i { transform: translateX(18px); }
@media (prefers-reduced-motion: reduce) { .locker__row--sound .switch, .locker__row--sound .switch i { transition: none; } }

/* === roster plates ====================================================================
   Under each card: level, a bar to the next one, and the move worth making. Big enough to
   read at two cards to a row, which the card's own numbers are not. */
.roster-cell { display: grid; justify-items: center; gap: 6px; }
.plate {
  width: 100%;
  box-sizing: border-box;
  display: grid;
  gap: 5px;
  padding: 7px 8px 8px;
  border-radius: 8px;
  background: rgba(20, 18, 15, 0.72);
  border: 1px solid rgba(255, 246, 214, 0.08);
  color: #d9d2c3;
}
.plate__top { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.plate__lv { font-family: var(--display); font-size: 16px; font-weight: 900; color: #f1e6c9; }
.plate__what { font-family: var(--label); font-size: 12px; font-weight: 700; color: #b8ad97; text-align: right; }
.plate__bar { display: block; height: 7px; border-radius: 4px; background: rgba(255, 255, 255, 0.09); overflow: hidden; }
.plate__bar b { display: block; height: 100%; width: 0; border-radius: inherit; background: linear-gradient(90deg, #b8862a, #e8b23a); transition: width 0.5s ease; }
.plate.is-ready { border-color: rgba(127, 196, 106, 0.55); box-shadow: 0 0 0 1px rgba(127, 196, 106, 0.25), 0 0 14px rgba(127, 196, 106, 0.18); }
.plate.is-ready .plate__bar b { background: #7fc46a; }
.plate.is-ready .plate__what { color: #a8e08f; }
.plate__go {
  min-height: 34px;
  padding: 6px 8px;
  border: 0;
  border-radius: 6px;
  font-family: var(--label);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #17140c;
  background: var(--brass, #e8b23a);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.plate.is-ready .plate__go { background: #7fc46a; }
.plate__go[data-kind="job"] { color: #e8f4f1; background: #2f5f5a; }
.plate__go:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) { .plate__bar b { transition: none; } }

/* Roster cards fill their column instead of sitting at a fixed size in it: bigger type on
   every phone wider than 360px. */
@media (min-width: 360px) {
  #deck-grid { --em-grid: clamp(10.2px, calc((100vw - 52px) / 27), 13px); }
}
/* In the grid the shiny's power line is one line, ellipsed: its second line was peeking
   out under the card's bottom edge. The full text is on the card's own screen. */
.card--grid .card__power { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-wrap: nowrap; }

/* === evolution ======================================================================== */
#reveal-evolve.is-ready { color: #1a0f24; background: linear-gradient(135deg, #c9a3ff, #8a5cd6); border-radius: 6px; text-decoration: none; padding: 6px 12px; }
#reveal-evolve:disabled { opacity: 0.7; }
.plate__go[data-kind="evo-start"], .plate__go[data-kind="evo-collect"] { color: #1a0f24; background: linear-gradient(135deg, #c9a3ff, #8a5cd6); }
.plate.is-evolving { border-color: rgba(170, 130, 240, 0.5); }
.plate.is-evolving .plate__what { color: #c9a3ff; }
.evolve__lead { font-family: var(--receipt); font-size: 17px; line-height: 1.35; margin: 4px 0 8px; }
.evolve__small { font-size: 14px; color: #6d6352; margin: 0 0 14px; }

.evolve-show {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center; align-content: center; gap: 14px;
  padding: 24px 16px calc(24px + env(safe-area-inset-bottom, 0px));
  background: radial-gradient(circle at 50% 42%, rgba(138, 92, 214, 0.45), rgba(10, 8, 14, 0.96) 62%);
  text-align: center; color: #f1e6c9;
}
.evolve-show__k { margin: 0; font-family: var(--label); font-size: 12px; font-weight: 800; letter-spacing: 0.2em; text-transform: uppercase; color: #c9a3ff; }
.evolve-show__stage { position: relative; display: grid; place-items: center; min-height: 60vh; }
.evolve-show__old, .evolve-show__new { grid-area: 1 / 1; }
.evolve-show__line { margin: 0; max-width: 32ch; font-family: var(--receipt); font-size: 16px; line-height: 1.35; }
.evolve-show.is-on .evolve-show__old { animation: evo-out 1.6s ease-in forwards; }
.evolve-show.is-on .evolve-show__new { animation: evo-in 1.4s cubic-bezier(0.2, 1.3, 0.4, 1) 1.3s both; }
.evolve-show.is-on .evolve-show__line, .evolve-show.is-on #evolve-show-ok { animation: evo-fade 0.5s ease 2.4s both; }
@keyframes evo-out {
  0% { transform: scale(1); filter: none; }
  30% { transform: scale(1.04) rotate(-2deg); filter: brightness(1.4); }
  45% { transform: scale(1.02) rotate(2deg); filter: brightness(2); }
  70% { transform: scale(1.1); filter: brightness(4) saturate(0); opacity: 1; }
  100% { transform: scale(1.3); filter: brightness(6) saturate(0); opacity: 0; }
}
@keyframes evo-in { from { transform: scale(0.4); opacity: 0; filter: brightness(4); } to { transform: none; opacity: 1; filter: none; } }
@keyframes evo-fade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .evolve-show.is-on .evolve-show__old { animation: none; display: none; }
  .evolve-show.is-on .evolve-show__new, .evolve-show.is-on .evolve-show__line, .evolve-show.is-on #evolve-show-ok { animation: none; }
}
.evolve-show { background: radial-gradient(circle at 50% 42%, #3a2560 0%, #1a1226 45%, #0b0910 80%); }
/* The Find Me tape sat on the foot of the photograph, which is where a two-line cut-out
   name rises into the picture, and covered its top letters. Same tape, top edge. */
.card__findme { top: 0.35em; bottom: auto; }
/* The picker's cells skip rendering off screen (content-visibility: auto), which also
   clips paint to the cell, and the 1-2-3 pick number rides over the card's top corner.
   The clip is widened rather than the optimisation dropped. */
#build-grid .pick { overflow-clip-margin: 14px; }

/* === the cocoon: a card evolving in Kevin's shed ====================================== */
.cocoon {
  position: absolute; inset: 0; z-index: 40;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: inherit; cursor: pointer; overflow: hidden;
  background: radial-gradient(ellipse at 50% 45%, rgba(170, 130, 240, 0.28), rgba(18, 12, 28, 0.62) 72%);
  backdrop-filter: blur(2.5px) grayscale(0.7) brightness(0.8);
  -webkit-backdrop-filter: blur(2.5px) grayscale(0.7) brightness(0.8);
  font: inherit; color: #f4ecff;
}
/* Silk: two sets of pale bands wound round it at crossing angles, drifting slowly. */
.cocoon__silk {
  position: absolute; inset: -20%;
  background:
    repeating-linear-gradient(28deg, rgba(245, 240, 255, 0) 0 0.9em, rgba(245, 240, 255, 0.5) 0.9em 1.6em, rgba(245, 240, 255, 0) 1.6em 2.6em),
    repeating-linear-gradient(-34deg, rgba(225, 212, 250, 0) 0 1.3em, rgba(225, 212, 250, 0.38) 1.3em 1.9em, rgba(225, 212, 250, 0) 1.9em 3.1em);
  mix-blend-mode: screen;
  animation: silk-drift 9s linear infinite;
}
@keyframes silk-drift { to { transform: translate3d(2.6em, -1.3em, 0); } }
/* Something alive inside: a glow that breathes. */
.cocoon__glow {
  position: absolute; left: 50%; top: 46%; width: 70%; aspect-ratio: 1; transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(201, 163, 255, 0.75), rgba(138, 92, 214, 0.25) 45%, transparent 70%);
  animation: cocoon-breathe 2.8s ease-in-out infinite;
}
@keyframes cocoon-breathe { 50% { transform: translate(-50%, -50%) scale(1.18); opacity: 0.65; } }
/* The ring: how far along, and how long to go. */
.cocoon__ring {
  position: relative; z-index: 1;
  display: grid; place-items: center; align-content: center;
  width: 5.2em; height: 5.2em; border-radius: 50%;
  background:
    radial-gradient(circle, rgba(24, 16, 36, 0.92) 0 62%, transparent 63%),
    conic-gradient(#c9a3ff calc(var(--p, 0) * 360deg), rgba(255, 255, 255, 0.18) 0);
  box-shadow: 0 0 1.2em rgba(170, 130, 240, 0.55);
}
.cocoon__t { font-family: var(--display); font-size: 1.05em; font-weight: 900; line-height: 1; font-variant-numeric: tabular-nums; }
.cocoon__s { margin-top: 0.2em; font-family: var(--label); font-size: 0.5em; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: #c9a3ff; }
/* Due: it shivers and lights up. */
.cocoon.is-ready { animation: cocoon-shiver 1.6s ease-in-out infinite; }
.cocoon.is-ready .cocoon__glow { background: radial-gradient(circle, rgba(255, 236, 170, 0.9), rgba(201, 163, 255, 0.45) 45%, transparent 70%); animation-duration: 1.2s; }
.cocoon.is-ready .cocoon__ring { background: radial-gradient(circle, rgba(24, 16, 36, 0.92) 0 62%, transparent 63%), conic-gradient(#ffe7a0 360deg, #ffe7a0 0); box-shadow: 0 0 1.6em rgba(255, 220, 130, 0.8); }
.cocoon.is-ready .cocoon__s { color: #ffe7a0; }
@keyframes cocoon-shiver {
  0%, 60%, 100% { transform: none; }
  64% { transform: rotate(-1.2deg) translateX(-1px); }
  68% { transform: rotate(1.2deg) translateX(1px); }
  72% { transform: rotate(-0.8deg); }
  76% { transform: rotate(0.6deg); }
}
.card--full .cocoon__ring { font-size: 1.1em; }
@media (prefers-reduced-motion: reduce) {
  .cocoon__silk, .cocoon__glow, .cocoon.is-ready { animation: none; }
}

/* === the reveal: the cocoon hatches =====================================================
   0-1.7s the wrapped card shivers harder and cracks of light break through; 1.7s a white
   flash, rays and sparks burst out and the screen jolts; 1.9s the evolved card spins in
   out of the light; 2.5s EVOLVED! slams down; 2.9s the line and the button. */
.evolve-show { overflow: hidden; }
.evolve-show__stage { position: relative; display: grid; place-items: center; min-height: 62vh; }
.evolve-show__old, .evolve-show__new, .evolve-show__cracks, .evolve-show__sparks, .evolve-show__stamp { grid-area: 1 / 1; }
.evolve-show__rays {
  position: absolute; left: 50%; top: 46%; width: 180vmax; height: 180vmax; transform: translate(-50%, -50%) scale(0);
  background: repeating-conic-gradient(from 0deg, rgba(201, 163, 255, 0.22) 0deg 8deg, transparent 8deg 22deg);
  -webkit-mask: radial-gradient(circle, #000 0 18%, transparent 55%); mask: radial-gradient(circle, #000 0 18%, transparent 55%);
  pointer-events: none; opacity: 0;
}
.evolve-show__flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; }
.evolve-show__cracks { position: relative; width: 60%; height: 50%; pointer-events: none; }
.evolve-show__cracks i {
  position: absolute; left: 50%; top: 50%; width: 3px; height: 0; background: #fffbe8;
  box-shadow: 0 0 10px #ffe7a0, 0 0 22px #c9a3ff; transform-origin: top center; opacity: 0;
}
.evolve-show__cracks i:nth-child(1) { --r: 20deg; }
.evolve-show__cracks i:nth-child(2) { --r: 95deg; }
.evolve-show__cracks i:nth-child(3) { --r: 160deg; }
.evolve-show__cracks i:nth-child(4) { --r: 230deg; }
.evolve-show__cracks i:nth-child(5) { --r: 300deg; }
.evolve-show__sparks { position: relative; width: 0; height: 0; pointer-events: none; }
.evolve-show__sparks i {
  position: absolute; left: 0; top: 0; width: 10px; height: 10px; border-radius: 50%;
  background: #ffe7a0; box-shadow: 0 0 12px #ffe7a0; opacity: 0;
}
.evolve-show__sparks i:nth-child(3n) { background: #c9a3ff; box-shadow: 0 0 12px #c9a3ff; }
.evolve-show__sparks i:nth-child(1) { --a: 0deg; } .evolve-show__sparks i:nth-child(2) { --a: 30deg; }
.evolve-show__sparks i:nth-child(3) { --a: 60deg; } .evolve-show__sparks i:nth-child(4) { --a: 90deg; }
.evolve-show__sparks i:nth-child(5) { --a: 120deg; } .evolve-show__sparks i:nth-child(6) { --a: 150deg; }
.evolve-show__sparks i:nth-child(7) { --a: 180deg; } .evolve-show__sparks i:nth-child(8) { --a: 210deg; }
.evolve-show__sparks i:nth-child(9) { --a: 240deg; } .evolve-show__sparks i:nth-child(10) { --a: 270deg; }
.evolve-show__sparks i:nth-child(11) { --a: 300deg; } .evolve-show__sparks i:nth-child(12) { --a: 330deg; }
.evolve-show__stamp {
  z-index: 3; margin: 0; align-self: end; margin-bottom: -0.4em;
  padding: 0.1em 0.5em; font-family: "Big Shoulders Stencil Display", var(--display); font-size: 44px; font-weight: 900;
  letter-spacing: 0.04em; text-transform: uppercase; color: #1a0f24;
  background: linear-gradient(135deg, #ffe7a0, #e8b23a); border: 3px solid #1a0f24; border-radius: 4px;
  transform: rotate(-8deg) scale(3); opacity: 0; box-shadow: 0 8px 22px rgba(0, 0, 0, 0.5);
}

.evolve-show.is-on .evolve-show__old { animation: hatch-shiver 1.7s ease-in forwards; }
.evolve-show.is-on .evolve-show__cracks i { animation: crack-open 0.5s ease-out forwards; animation-delay: calc(0.7s + var(--i, 0) * 0.15s); }
.evolve-show.is-on .evolve-show__cracks i:nth-child(1) { --i: 0; } .evolve-show.is-on .evolve-show__cracks i:nth-child(2) { --i: 1; }
.evolve-show.is-on .evolve-show__cracks i:nth-child(3) { --i: 2; } .evolve-show.is-on .evolve-show__cracks i:nth-child(4) { --i: 3; }
.evolve-show.is-on .evolve-show__cracks i:nth-child(5) { --i: 4; }
.evolve-show.is-on .evolve-show__flash { animation: hatch-flash 0.9s ease-out 1.7s forwards; }
.evolve-show.is-on .evolve-show__rays { animation: rays-burst 0.7s cubic-bezier(0.2, 1.2, 0.4, 1) 1.7s forwards, rays-turn 24s linear 2.4s infinite; }
.evolve-show.is-on .evolve-show__sparks i { animation: spark-fly 1.1s cubic-bezier(0.1, 0.8, 0.3, 1) 1.72s forwards; }
.evolve-show.is-on .evolve-show__new { animation: hatch-land 1s cubic-bezier(0.2, 1.3, 0.4, 1) 1.85s both; }
.evolve-show.is-on .evolve-show__stamp { animation: stamp-slam 0.35s cubic-bezier(0.3, 1.6, 0.5, 1) 2.5s forwards; }
.evolve-show.is-on .evolve-show__stage { animation: hatch-jolt 0.4s ease-out 1.7s; }
.evolve-show.is-on .evolve-show__k { animation: evo-fade 0.4s ease 0.1s both; }
.evolve-show.is-on .evolve-show__line, .evolve-show.is-on #evolve-show-ok { animation: evo-fade 0.5s ease 2.9s both; }

@keyframes hatch-shiver {
  0% { transform: none; filter: none; }
  20% { transform: rotate(-1deg); } 25% { transform: rotate(1deg); }
  45% { transform: rotate(-2deg) scale(1.01); } 50% { transform: rotate(2deg) scale(1.01); }
  65% { transform: rotate(-3deg) scale(1.03); filter: brightness(1.3); } 70% { transform: rotate(3deg) scale(1.03); }
  80% { transform: rotate(-4deg) scale(1.05); } 85% { transform: rotate(4deg) scale(1.06); filter: brightness(2); }
  92% { transform: rotate(-5deg) scale(1.08); } 97% { transform: scale(1.12); filter: brightness(4); opacity: 1; }
  100% { transform: scale(1.3); filter: brightness(8); opacity: 0; }
}
@keyframes crack-open { from { height: 0; opacity: 1; transform: rotate(var(--r)); } to { height: 46%; opacity: 1; transform: rotate(var(--r)); } }
@keyframes hatch-flash { 0% { opacity: 0; } 12% { opacity: 1; } 100% { opacity: 0; } }
@keyframes rays-burst { to { transform: translate(-50%, -50%) scale(1); opacity: 1; } }
@keyframes rays-turn { from { transform: translate(-50%, -50%) scale(1) rotate(0deg); } to { transform: translate(-50%, -50%) scale(1) rotate(360deg); } }
@keyframes spark-fly {
  0% { opacity: 1; transform: rotate(var(--a)) translateX(0) scale(1); }
  100% { opacity: 0; transform: rotate(var(--a)) translateX(46vmin) scale(0.3); }
}
@keyframes hatch-land {
  0% { opacity: 0; transform: perspective(900px) rotateY(540deg) scale(0.3); filter: brightness(5); }
  60% { opacity: 1; filter: brightness(1.6); }
  100% { opacity: 1; transform: perspective(900px) rotateY(0) scale(1); filter: none; }
}
@keyframes stamp-slam { to { opacity: 1; transform: rotate(-8deg) scale(1); } }
@keyframes hatch-jolt { 25% { transform: translate(-6px, 4px); } 50% { transform: translate(5px, -3px); } 75% { transform: translate(-3px, 2px); } }
@media (prefers-reduced-motion: reduce) {
  .evolve-show.is-on .evolve-show__old, .evolve-show.is-on .evolve-show__cracks, .evolve-show.is-on .evolve-show__sparks,
  .evolve-show.is-on .evolve-show__flash { display: none; }
  .evolve-show.is-on .evolve-show__rays, .evolve-show.is-on .evolve-show__new, .evolve-show.is-on .evolve-show__stamp,
  .evolve-show.is-on .evolve-show__stage { animation: none; opacity: 1; transform: none; }
  .evolve-show.is-on .evolve-show__rays { transform: translate(-50%, -50%) scale(1); }
}
/* Room under the card for the stamp, so it never sits on the line below. */
.evolve-show__stage { padding-bottom: 40px; }
.evolve-show__stamp { margin-bottom: 0; }
/* The cracks go in the flash, not after it. */
.evolve-show.is-on .evolve-show__cracks i {
  animation: crack-open 0.5s ease-out calc(0.7s + var(--i, 0) * 0.15s) forwards, crack-gone 0.15s linear 1.72s forwards;
}
@keyframes crack-gone { to { opacity: 0; } }

/* === reveal, act two: what changed ===================================================== */
.evolve-show__details {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 5;
  max-height: 64vh; overflow-y: auto; box-sizing: border-box;
  display: grid; gap: 14px; padding: 18px 18px calc(84px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(180deg, rgba(20, 14, 30, 0.96), #0d0a12);
  border-top: 1px solid rgba(201, 163, 255, 0.35);
  box-shadow: 0 -20px 40px rgba(0, 0, 0, 0.6);
  transform: translateY(105%); transition: transform 0.55s cubic-bezier(0.2, 0.9, 0.3, 1);
  text-align: left; color: #efe6ff;
}
.evolve-show.is-details .evolve-show__details { transform: none; }
.evolve-show.is-details .evolve-show__new { scale: 0.5; translate: 0 -21vh; transition: scale 0.6s cubic-bezier(0.2, 0.9, 0.3, 1), translate 0.6s cubic-bezier(0.2, 0.9, 0.3, 1); }
.evolve-show.is-details .evolve-show__stamp { visibility: hidden; }
.evolve-show.is-details .evolve-show__line { visibility: hidden; }
.evolve-show.is-details .evolve-show__k { translate: 0 -2vh; }
.evolve-show.is-details #evolve-show-ok {
  position: absolute; left: 50%; bottom: calc(18px + env(safe-area-inset-bottom, 0px)); z-index: 6; translate: -50% 0;
  animation: evo-fade 0.45s ease var(--done, 5s) both;
}
.evd { opacity: 0; transform: translateY(10px); }
.evolve-show.is-details .evd, .evolve-show.is-details .evd__stat, .evolve-show.is-details .evd__move {
  animation: evd-in 0.4s ease-out calc(0.2s + var(--i, 0) * 0.26s) both;
}
.evd--stats, .evd--moves { opacity: 1; transform: none; animation: none !important; display: grid; gap: 7px; }
@keyframes evd-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.evd__k { margin: 0; font-family: var(--label); font-size: 11px; font-weight: 800; letter-spacing: 0.18em; text-transform: uppercase; color: #c9a3ff; }
.evd--name { display: grid; justify-items: center; gap: 2px; text-align: center; }
.evd__was { color: #8d7fa6; text-decoration-color: #f08a74; text-decoration-thickness: 2px; font-family: var(--label); font-weight: 700; font-size: 15px; }
.evd--name .evd__now { font-family: var(--display); font-size: 24px; font-weight: 900; color: #fff; text-wrap: balance; }
.evd__stat { display: grid; grid-template-columns: 34px minmax(0, 1fr) 30px 30px; align-items: center; gap: 8px; opacity: 0; }
.evd__lbl { font-family: var(--label); font-size: 12px; font-weight: 800; letter-spacing: 0.08em; color: #cfc3e6; }
.evd__bar { position: relative; display: block; height: 9px; border-radius: 5px; background: rgba(255, 255, 255, 0.08); overflow: hidden; }
.evd__bar-was, .evd__bar-now { position: absolute; left: 0; top: 0; bottom: 0; border-radius: inherit; }
.evd__bar-was { background: #6f5b92; }
.evd__bar-now { width: var(--from); background: linear-gradient(90deg, #8a5cd6, #c9a3ff); }
.evolve-show.is-details .evd__bar-now { animation: bar-grow 0.7s cubic-bezier(0.2, 0.9, 0.3, 1) calc(0.35s + var(--i, 0) * 0.26s) forwards; }
@keyframes bar-grow { to { width: var(--w); } }
.evd__num { font-family: var(--display); font-weight: 900; font-size: 15px; text-align: right; font-variant-numeric: tabular-nums; }
.evd__up { font-style: normal; font-family: var(--label); font-size: 12px; font-weight: 800; color: #a8e08f; }
.evd__stat.is-lean .evd__bar-now { background: linear-gradient(90deg, #e8b23a, #ffe7a0); }
.evd__stat.is-lean .evd__up { color: #ffe7a0; }
.evd__move { display: grid; grid-template-columns: 58px minmax(0, 1fr); align-items: baseline; column-gap: 10px; opacity: 0; }
.evd__kind { grid-row: span 2; align-self: center; padding: 2px 0; border-radius: 3px; text-align: center; font-family: var(--label); font-size: 10px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: #fff; background: #8f2f22; }
.evd__kind--quick { background: #2b5fb5; }
.evd__kind--trick { background: #6b3fa0; }
.evd__move .evd__was { font-size: 13px; }
.evd__move .evd__now { font-family: var(--display); font-size: 17px; font-weight: 900; color: #fff; }
@media (prefers-reduced-motion: reduce) {
  .evolve-show__details, .evolve-show.is-details .evolve-show__new { transition: none; }
  .evolve-show.is-details .evd, .evolve-show.is-details .evd__stat, .evolve-show.is-details .evd__move, .evolve-show.is-details .evd__bar-now { animation: none; opacity: 1; transform: none; width: var(--w); }
}
/* The deal plays once a session: after it, moving or reusing a card never replays it. */
#deck-grid[data-dealt] .card { animation: none; }
#deck-grid[data-dealt] .card--shiny { animation: shiny-halo 2.6s ease-in-out infinite; }
.roster-cell.is-arriving { animation: card-arrive 0.45s cubic-bezier(0.2, 0.9, 0.3, 1) both; }
@keyframes card-arrive { from { opacity: 0; transform: translateY(12px) scale(0.96); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .roster-cell.is-arriving { animation: none; } }

/* === the hatch: one card becoming the next =============================================
   charging: the old card, wrapped, hums (while the shed hands over and the picture loads).
   morph   0-1.0s the shiver builds; 0.9-1.4s the silk unwinds and flies off;
           1.3-2.8s a scan line sweeps down and the card changes as it passes;
           2.8s the burst: flash, rays, sparks; 3.2s EVOLVED!; 4.1s what changed. */
@property --y { syntax: '<length>'; inherits: true; initial-value: 0px; }
.hatch { position: relative; display: grid; align-items: start; justify-items: center; --y: 0px; }
.hatch__old, .hatch__new, .hatch__silk { grid-area: 1 / 1; }
.hatch__new { clip-path: inset(0 0 calc(100% - var(--y)) 0); }
.hatch__old { clip-path: inset(var(--y) 0 0 0); }
.hatch__scan {
  position: absolute; left: -6%; right: -6%; top: var(--y); height: 6px; margin-top: -3px; z-index: 4;
  border-radius: 3px; opacity: 0; pointer-events: none;
  background: linear-gradient(90deg, transparent, #fffbe8 12%, #fff 50%, #fffbe8 88%, transparent);
  box-shadow: 0 0 18px 6px rgba(255, 231, 160, 0.85), 0 0 50px 14px rgba(201, 163, 255, 0.6);
}
.hatch__silk { position: relative; width: 100%; height: 100%; pointer-events: none; }
.hatch__silk i {
  position: absolute; left: 10%; right: 10%; height: 1.4em; top: calc(10% + var(--k, 0) * 10%);
  background: rgba(245, 240, 255, 0.75); border-radius: 0.7em; opacity: 0;
  box-shadow: 0 0 12px rgba(201, 163, 255, 0.6);
}
.hatch__silk i:nth-child(1) { --k: 0; --dx: -60vw; --rot: -24deg; } .hatch__silk i:nth-child(2) { --k: 1; --dx: 60vw; --rot: 18deg; }
.hatch__silk i:nth-child(3) { --k: 2; --dx: -55vw; --rot: 30deg; } .hatch__silk i:nth-child(4) { --k: 3; --dx: 58vw; --rot: -20deg; }
.hatch__silk i:nth-child(5) { --k: 4; --dx: -62vw; --rot: -34deg; } .hatch__silk i:nth-child(6) { --k: 5; --dx: 54vw; --rot: 26deg; }
.hatch__silk i:nth-child(7) { --k: 6; --dx: -58vw; --rot: 16deg; } .hatch__silk i:nth-child(8) { --k: 7; --dx: 60vw; --rot: -28deg; }

/* Charging: humming in its cocoon. */
.evolve-show.is-charging .hatch { animation: hatch-hum 1.1s ease-in-out infinite; }
@keyframes hatch-hum { 0%, 100% { transform: none; } 30% { transform: rotate(-0.8deg); } 60% { transform: rotate(0.8deg); } }

/* Morph. */
.evolve-show.is-morph .hatch { animation: hatch-build 1.3s ease-in forwards, hatch-scan 1.5s cubic-bezier(0.45, 0, 0.35, 1) 1.3s forwards; }
@keyframes hatch-build {
  0% { transform: none; } 20% { transform: rotate(-1.5deg); } 30% { transform: rotate(1.5deg); }
  50% { transform: rotate(-2.5deg) scale(1.02); } 60% { transform: rotate(2.5deg) scale(1.02); }
  75% { transform: rotate(-3.5deg) scale(1.04); } 85% { transform: rotate(3.5deg) scale(1.05); }
  100% { transform: scale(1.03); }
}
@keyframes hatch-scan { from { --y: 0px; transform: scale(1.03); } to { --y: var(--y-end, 900px); transform: none; } }
.evolve-show.is-morph .hatch__old .cocoon { animation: cocoon-off 0.5s ease-in 0.9s forwards; }
@keyframes cocoon-off { to { opacity: 0; transform: scale(1.15); } }
.evolve-show.is-morph .hatch__silk i { animation: silk-fly 0.7s cubic-bezier(0.3, 0, 0.3, 1) calc(0.9s + var(--k) * 0.04s) forwards; }
@keyframes silk-fly {
  0% { opacity: 0.85; transform: none; }
  100% { opacity: 0; transform: translateX(var(--dx)) rotate(var(--rot)) scaleX(1.6); }
}
.evolve-show.is-morph .hatch__scan { animation: scan-on 1.6s linear 1.25s forwards; }
@keyframes scan-on { 0% { opacity: 0; } 6% { opacity: 1; } 92% { opacity: 1; } 100% { opacity: 0; } }
.evolve-show.is-morph .evolve-show__flash { animation: hatch-flash 0.8s ease-out 2.8s forwards; }
.evolve-show.is-morph .evolve-show__rays { animation: rays-burst 0.7s cubic-bezier(0.2, 1.2, 0.4, 1) 2.8s forwards, rays-turn 24s linear 3.5s infinite; }
.evolve-show.is-morph .evolve-show__sparks i { animation: spark-fly 1.1s cubic-bezier(0.1, 0.8, 0.3, 1) 2.82s forwards; }
.evolve-show.is-morph .evolve-show__stamp { animation: stamp-slam 0.35s cubic-bezier(0.3, 1.6, 0.5, 1) 3.2s forwards; }
.evolve-show.is-morph .evolve-show__stage { animation: hatch-jolt 0.4s ease-out 2.8s; }
.evolve-show.is-morph .evolve-show__line { animation: evo-fade 0.5s ease 3.5s both; }
.evolve-show #evolve-show-ok { opacity: 0; pointer-events: none; }
.evolve-show.is-details #evolve-show-ok { pointer-events: auto; }
.evolve-show.is-details .hatch { scale: 0.5; translate: 0 -21vh; transition: scale 0.6s cubic-bezier(0.2, 0.9, 0.3, 1), translate 0.6s cubic-bezier(0.2, 0.9, 0.3, 1); }
.evolve-show__stage { min-height: 0; }
@media (prefers-reduced-motion: reduce) {
  .evolve-show.is-charging .hatch, .evolve-show.is-morph .hatch__silk i, .evolve-show.is-morph .hatch__scan { animation: none; }
  .evolve-show.is-morph .hatch { animation: none; --y: 900px; }
  .evolve-show.is-morph .evolve-show__rays { animation: none; opacity: 1; transform: translate(-50%, -50%) scale(1); }
  .evolve-show.is-morph .evolve-show__stamp { animation: none; opacity: 1; transform: rotate(-8deg); }
}

/* --- hatch polish -------------------------------------------------------------------- */
/* Sparks shed off the scan line as it sweeps. */
.hatch__scan b {
  position: absolute; top: 2px; width: 5px; height: 5px; border-radius: 50%;
  background: #fffbe8; box-shadow: 0 0 8px #ffe7a0; opacity: 0;
}
.hatch__scan b:nth-child(1) { left: 8%; --d: 0s; } .hatch__scan b:nth-child(2) { left: 20%; --d: 0.12s; }
.hatch__scan b:nth-child(3) { left: 33%; --d: 0.05s; } .hatch__scan b:nth-child(4) { left: 47%; --d: 0.18s; }
.hatch__scan b:nth-child(5) { left: 58%; --d: 0.08s; } .hatch__scan b:nth-child(6) { left: 70%; --d: 0.22s; }
.hatch__scan b:nth-child(7) { left: 82%; --d: 0.02s; } .hatch__scan b:nth-child(8) { left: 93%; --d: 0.15s; }
.evolve-show.is-morph .hatch__scan b { animation: scan-spark 0.45s ease-in calc(1.3s + var(--d)) 3; }
@keyframes scan-spark { 0% { opacity: 1; transform: none; } 100% { opacity: 0; transform: translateY(-34px) scale(0.4); } }
/* An aura round the new card once it has landed. */
.evolve-show.is-morph .hatch__new { animation: aura 2.4s ease-in-out 2.9s infinite; }
@keyframes aura {
  0%, 100% { filter: drop-shadow(0 0 10px rgba(201, 163, 255, 0.55)); }
  50% { filter: drop-shadow(0 0 26px rgba(201, 163, 255, 0.95)) drop-shadow(0 0 6px rgba(255, 231, 160, 0.6)); }
}
/* The stamp says which stage. */
.evolve-show__stamp small { display: block; margin-top: -0.1em; font-family: var(--label); font-size: 0.34em; letter-spacing: 0.24em; text-align: center; }
/* A tap skipped the show: everything at its end state, straight to what changed. */
.evolve-show.is-skip .hatch { animation: none; --y: var(--y-end, 900px); transform: none; }
.evolve-show.is-skip .hatch__scan, .evolve-show.is-skip .hatch__silk, .evolve-show.is-skip .hatch__old .cocoon,
.evolve-show.is-skip .evolve-show__flash, .evolve-show.is-skip .evolve-show__sparks { display: none; }
.evolve-show.is-skip .evolve-show__rays { animation: rays-turn 24s linear infinite; opacity: 1; transform: translate(-50%, -50%) scale(1); }
/* Back on the roster, the card that just evolved catches the light once. */
.roster-cell.is-fresh-evo { position: relative; animation: fresh-evo 2.6s ease-out 1; }
.roster-cell.is-fresh-evo::after {
  content: ""; position: absolute; inset: 0; z-index: 30; pointer-events: none; border-radius: 10px;
  background: linear-gradient(115deg, transparent 35%, rgba(255, 251, 232, 0.85) 50%, transparent 65%) no-repeat;
  background-size: 250% 100%; animation: fresh-sweep 1.2s ease-in-out 0.3s 2 both;
}
@keyframes fresh-evo {
  0% { filter: drop-shadow(0 0 0 rgba(201, 163, 255, 0)); }
  30% { filter: drop-shadow(0 0 22px rgba(201, 163, 255, 0.95)); }
  100% { filter: drop-shadow(0 0 0 rgba(201, 163, 255, 0)); }
}
@keyframes fresh-sweep { from { background-position: 130% 0; } to { background-position: -30% 0; } }
@media (prefers-reduced-motion: reduce) {
  .evolve-show.is-morph .hatch__scan b, .evolve-show.is-morph .hatch__new, .roster-cell.is-fresh-evo, .roster-cell.is-fresh-evo::after { animation: none; }
}

/* === looking closer ====================================================================== */
.reveal-zoom {
  position: absolute; top: calc(env(safe-area-inset-top, 0px) + 14px); right: 14px; z-index: 20;
  display: grid; place-items: center; width: 42px; height: 42px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(20, 18, 15, 0.72); color: #f1e6c9; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.45); cursor: pointer;
}
.reveal-zoom svg { width: 22px; height: 22px; }
#view-reveal .reveal-pos { margin-right: 54px; }
.reveal-zoom { position: fixed; }
#view-reveal:not(.is-active) .reveal-zoom { display: none; }
.zoom { position: fixed; inset: 0; z-index: 95; display: grid; grid-template-rows: 1fr auto; background: #0b0a09; color: #f1e6c9; }
.zoom__stage { position: relative; overflow: hidden; display: grid; place-items: center; touch-action: none; }
.zoom__item { transform-origin: center center; will-change: transform; display: grid; place-items: center; }
.zoom__item.is-easing { transition: transform 0.25s ease-out; }
.zoom__photo { max-width: 96vw; max-height: 80vh; object-fit: contain; border-radius: 6px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6); user-select: none; -webkit-user-drag: none; }
.zoom__bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 16px calc(14px + env(safe-area-inset-bottom, 0px)); background: rgba(20, 18, 15, 0.9); }
.zoom__hint { font-family: var(--label); font-size: 12px; color: #8d8573; }
.zoom__btn { min-height: 40px; padding: 8px 16px; border: 0; border-radius: 8px; background: #2a2620; color: #f1e6c9; font: 800 14px var(--label); letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer; }
#zoom-close { background: var(--brass, #e8b23a); color: #17140c; }

/* === googly eyes on evolved cards ======================================================
   The pair evolves with the card: a strip of black tape stuck over it for a brow at stage
   II (determined, slightly cross), and a third, smaller eye above at stage III. */
.googly-pair { position: absolute; }
.googly-brow {
  position: absolute; left: -0.08em; right: -0.08em; top: -0.34em; height: 0.2em; z-index: 2;
  border-radius: 0.04em;
  background: linear-gradient(180deg, #2a2724, #111);
  box-shadow: 0 0.04em 0.06em rgba(0, 0, 0, 0.5);
  transform: rotate(-4deg);
  clip-path: polygon(0 30%, 48% 100%, 52% 100%, 100% 30%, 100% 0, 0 0);
}
.googly-pair--evo-3 .googly-brow { top: -0.3em; }
.googly--third {
  position: absolute; left: 50%; top: -0.78em; width: 0.62em; height: 0.62em; margin-left: -0.31em;
  font-size: 1em; z-index: 3;
}
.googly--third .googly__pupil { width: 0.3em; height: 0.3em; margin: -0.15em 0 0 -0.15em; transform: translate(calc(var(--bx, 0) * 0.16em), calc(var(--by, 1) * 0.16em)); }
/* Thick enough to read at thumbnail size, with a pale edge so it shows on dark art too. */
.googly-brow { height: 0.3em; top: -0.42em; box-shadow: 0 0 0 0.035em rgba(255, 250, 235, 0.75), 0 0.05em 0.08em rgba(0, 0, 0, 0.55); }
/* Held until the boot screen lifts, so the opening deal is seen. */
.is-booting #deck-grid .card { animation-play-state: paused; }

/* === scrolling, made smooth ============================================================
   background-attachment: fixed on the body makes Chrome scroll on the main thread for the
   whole page, which is the lag you feel on every drag. The body never scrolls itself (the
   views do), so a plain background looks identical and lets scrolling go back to the GPU.
   Blur behind sticky bars is recomputed every scroll frame; they get solid grounds. */
body { background-attachment: scroll; min-height: 100vh; }
#pick-three .build-bar { backdrop-filter: none; -webkit-backdrop-filter: none; background: #15181a; }
.view { overscroll-behavior: contain; }
/* A roster card off screen is not drawn until it nears the viewport. The intrinsic size
   stands in for it meanwhile, so the scroll bar and the grid do not jump. */
#deck-grid .roster-cell { content-visibility: auto; contain-intrinsic-size: auto 360px; overflow-clip-margin: 24px; }
/* While a finger is scrolling, the decoration holds still; it carries on when you stop. */
.is-scrolling .card__shine::before, .is-scrolling .card__shine::after, .is-scrolling .card__holo,
.is-scrolling .card__edge, .is-scrolling .card__sparkle, .is-scrolling .card--shiny, .is-scrolling .card--shiny::after,
.is-scrolling .cocoon__silk, .is-scrolling .cocoon__glow, .is-scrolling .till, .is-scrolling .roster-cell.is-fresh-evo,
.is-scrolling .tray-slot.is-next, .is-scrolling .plate__bar b { animation-play-state: paused !important; transition: none !important; }
/* Everything that animates holds still for the length of a scroll. The ones left running
   (the wad's glint, the shutter's shine, a breathing dot) repaint every frame on the main
   thread, which is time taken from the scroll. They carry on the moment it stops. */
.is-scrolling *, .is-scrolling *::before, .is-scrolling *::after { animation-play-state: paused !important; }
/* Drawing roster cards only as they near the screen made them arrive blank and fill in
   on a quick scroll: flicker. The other scroll fixes carry the smoothness without it. */
#deck-grid .roster-cell { content-visibility: visible; contain-intrinsic-size: none; }

/* === a card's history: every form it has taken ========================================= */
.history { margin: 6px 0 14px; padding: 12px 14px 14px; border-radius: 10px; background: rgba(20, 16, 30, 0.6); border: 1px solid rgba(201, 163, 255, 0.25); color: #efe6ff; }
.history__k { margin: 0 0 10px; font-family: var(--label); font-size: 11px; font-weight: 800; letter-spacing: 0.18em; text-transform: uppercase; color: #c9a3ff; }
.history__list { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(84px, 1fr); gap: 18px; margin: 0; padding: 0; list-style: none; overflow-x: auto; }
.history__form { position: relative; display: grid; justify-items: center; gap: 4px; text-align: center; }
.history__form:not(:last-child)::after { content: "›"; position: absolute; right: -14px; top: 30px; font: 900 18px var(--display); color: #8a5cd6; }
.history__pic { position: relative; width: 72px; height: 72px; padding: 0; border: 2px solid #3a2e52; border-radius: 8px; overflow: hidden; background: #1a1424; cursor: zoom-in; }
.history__pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.history__pic:disabled { cursor: default; }
.history__none { display: grid; place-items: center; height: 100%; padding: 4px; font-size: 10px; line-height: 1.2; color: #8d7fa6; }
.history__stage { position: absolute; right: 3px; bottom: 3px; padding: 1px 5px; border-radius: 3px; font: 800 10px var(--label); color: #fff; background: #6b3fa0; }
.history__form.is-now .history__pic { border-color: #c9a3ff; box-shadow: 0 0 12px rgba(201, 163, 255, 0.45); }
.history__name { font-family: var(--display); font-size: 12px; font-weight: 900; line-height: 1.15; }
.history__meta { font-family: var(--label); font-size: 10px; color: #b3a6cc; }

/* --- the reveal's details as a drawer ---------------------------------------------------
   The new card keeps the screen (it only eases back a little); what changed waits in a
   drawer with a handle, and slides up when asked. Its rows play when it first opens. */
.evolve-show.is-details .hatch { scale: 0.84; translate: 0 -7vh; }
.evolve-show__details { max-height: 72vh; padding-top: 0; transform: translateY(105%); }
.evolve-show.is-details .evolve-show__details { transform: translateY(calc(100% - 58px - env(safe-area-inset-bottom, 0px))); }
.evolve-show.is-details .evolve-show__details.is-open { transform: none; }
.evd__handle {
  position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 0 -18px 4px; padding: 16px 18px 14px; border: 0; background: linear-gradient(180deg, rgba(26, 18, 38, 0.98), rgba(20, 14, 30, 0.96));
  color: #e9dcff; font: 800 13px var(--label); letter-spacing: 0.14em; text-transform: uppercase; cursor: pointer;
}
.evd__handle::before { content: ""; position: absolute; top: 7px; left: 50%; width: 38px; height: 4px; margin-left: -19px; border-radius: 2px; background: rgba(201, 163, 255, 0.5); }
.evd__chev { font-style: normal; font-size: 11px; transition: transform 0.25s ease; }
.evolve-show__details.is-open .evd__chev { transform: rotate(180deg); }
/* Rows play when the drawer first opens, not while it is closed. */
.evolve-show.is-details .evolve-show__details:not(.is-open) .evd,
.evolve-show.is-details .evolve-show__details:not(.is-open) .evd__stat,
.evolve-show.is-details .evolve-show__details:not(.is-open) .evd__move,
.evolve-show.is-details .evolve-show__details:not(.is-open) .evd__bar-now { animation-play-state: paused; }
.evolve-show.is-details #evolve-show-ok { bottom: calc(72px + env(safe-area-inset-bottom, 0px)); }
.evolve-show.is-details .evolve-show__details.is-open ~ #evolve-show-ok,
.evolve-show:has(.evolve-show__details.is-open) #evolve-show-ok { bottom: calc(18px + env(safe-area-inset-bottom, 0px)); }
.evolve-show.is-details .hatch::after {
  content: "Tap the card to look closer"; position: absolute; left: 0; right: 0; bottom: -26px; text-align: center;
  font: 700 12px var(--label); letter-spacing: 0.08em; color: rgba(233, 220, 255, 0.7); scale: calc(1 / 0.84);
}

/* Kevin's shed: the parts row. An empty dashed slot until a card goes in. */
.evoparts { display: grid; grid-template-columns: 56px 1fr auto; gap: 12px; align-items: center; margin: 12px 0; padding: 10px; border: 2px dashed rgba(23, 20, 12, 0.3); border-radius: 10px; }
.evoparts.is-set { border-style: solid; border-color: #b58a2a; background: rgba(232, 178, 58, 0.12); }
.evoparts__slot { width: 56px; height: 56px; overflow: hidden; border-radius: 6px; background: rgba(23, 20, 12, 0.08); }
.evoparts__slot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.evoparts__words { min-width: 0; }
.evoparts__k { margin: 0; font-weight: 800; font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; opacity: 0.7; }
.evoparts__what { margin: 2px 0 0; font-size: 13px; line-height: 1.35; }

/* THE ROSTER'S RHYTHM. The heading, wad, goal and filters had grown their own margins
   one at a time (1px, 5px, 2px, then 18px) and read as one lump. One step between each
   block, a bigger one before the cards. */
#view-deck > .deck-head { margin-bottom: 14px; }
#view-deck > #scrap-chip { margin: 0 0 12px; }
#view-deck > #wad-goal { margin: 0 0 16px; }
#view-deck > #sift { margin: 0 0 22px; }
#view-deck > #get-app-banner { margin: 0 0 24px; }

/* Boss rungs: Kevin (boss) and Gary (final boss). Their whole side is evolved. */
.league-row.is-boss { border: 2px solid #e8b23a; box-shadow: 0 0 0 3px rgba(232, 178, 58, 0.18), 0 10px 24px rgba(0, 0, 0, 0.4); }
.league-row__boss { padding: 2px 8px; border-radius: 4px; background: #e8b23a; color: #17140c; font: 900 11px var(--label, sans-serif); letter-spacing: 0.14em; text-transform: uppercase; }
.league-row.is-boss.is-locked .league-row__boss { background: rgba(232, 178, 58, 0.45); }

/* ---- Polish, batch 1 --------------------------------------------------------------- */
/* Filters: the row scrolls sideways, so it fades out at the edge rather than chopping. */
#sift .sift__types { scrollbar-width: none; -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent); mask-image: linear-gradient(90deg, #000 88%, transparent); }
#sift .sift__types::-webkit-scrollbar { display: none; }
/* Numbers that change should not jiggle as they do. */
.scrap-chip, #scrap-chip, .till, .wad-goal, .lvlprog, .welcome__n, .notes__powern, .league-row__power, .tb-handbar__power { font-variant-numeric: tabular-nums; }
/* Photos are not for dragging off the page by accident. */
.card img, .league-row__card, .history img, .evoparts__slot img { -webkit-user-drag: none; user-select: none; }
/* Card screen. */
.notes__powerfrom { margin: 2px 0 0; font-size: 12px; opacity: 0.7; }
.lvlprog.is-affordable .lvlprog__note { color: #a8e08f; font-weight: 700; opacity: 1; }
#reveal-progress { margin-top: 12px; }
/* Deploy: the three's combined power. */
.tb-handbar__power { opacity: 0.75; font-weight: 700; }
/* League: their power against your best three. */
.league-row__power { font-weight: 800; font-size: 12px; opacity: 0.8; }
.league-row__power.is-tough { color: #e58a6f; opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .league-row.is-boss:not(.is-locked) .league-row__boss { background: linear-gradient(100deg, #e8b23a 40%, #fff3c4 50%, #e8b23a 60%) 0 0 / 250% 100%; animation: boss-shine 3.2s linear infinite; }
}
@keyframes boss-shine { to { background-position: -250% 0; } }
/* Shed and More: credits. */
.shed-credits { display: inline-block; margin-left: 6px; padding: 2px 8px; border-radius: 999px; background: #e8b23a; color: #17140c; font: 800 12px var(--label, sans-serif); letter-spacing: 0.06em; vertical-align: middle; }
.shed-credits[hidden] { display: none; }
.locker__row--static { cursor: default; }
.locker__n { display: inline-block; min-width: 1.6em; margin-left: 6px; padding: 0 6px; border-radius: 999px; background: #e8b23a; color: #17140c; text-align: center; font-size: 13px; }
.welcome__credits { margin: 6px 0 0; font-weight: 700; color: #b58a2a; }

/* Power on every card. The small seal on a grid card; with its label on a full one. */
/* Top-right, inside the card's width: over the right edge it was cut off on a card at the
   screen's edge, and over the bottom the roster's level box covered it. */
.card__powerseal { top: 6px; bottom: auto; right: 6px; width: 28px; height: 28px; font-variant-numeric: tabular-nums; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45); }
.card--full .card__powerseal { top: 14px; bottom: auto; right: 14px; width: auto; min-width: 46px; height: 46px; padding: 0 8px; border-radius: 23px; font-size: 20px; line-height: 1; grid-auto-flow: row; justify-items: center; align-content: center; }
.card--full .card__powerseal small { display: block; font: 800 8px var(--label, sans-serif); letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.8; }

/* ---- Polish, batch 2 --------------------------------------------------------------- */
.standoff__tonight--boss { color: #e8b23a; font-weight: 900; letter-spacing: 0.3em; }
.standoff__powers { margin: 6px 0 0; font: 800 14px var(--label, sans-serif); letter-spacing: 0.12em; text-transform: uppercase; opacity: 0.8; font-variant-numeric: tabular-nums; }
.league-row__next, .league-row__champ { padding: 2px 8px; border-radius: 4px; font: 900 11px var(--label, sans-serif); letter-spacing: 0.12em; text-transform: uppercase; }
.league-row__next { background: #7fc46a; color: #13240f; }
.league-row__champ { background: #f3ecda; color: #17140c; }
.wad__credits { display: inline-block; margin-top: 3px; padding: 1px 7px; border-radius: 999px; background: #e8b23a; color: #17140c; font: 800 10px var(--label, sans-serif); letter-spacing: 0.08em; text-transform: uppercase; }
.wad__credits[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  #rp-level.is-pop, #evolve-ask-go.is-pop { animation: pop-once 0.5s cubic-bezier(.3, 1.6, .5, 1); }
}
@keyframes pop-once { 40% { transform: scale(1.12); } }
#rp-level { display: inline-block; }
.wad__credits { justify-self: start; }

/* ---- Polish, batch 3 --------------------------------------------------------------- */
/* Power seals by band: gold from 80, silver from 60. */
.card__powerseal.is-high { background: linear-gradient(145deg, #f6d27a, #b8861f); color: #17140c; border-color: #fff1c2; }
.card__powerseal.is-mid { background: linear-gradient(145deg, #e9e6df, #9a968c); color: #17140c; border-color: #fff; }
/* Evolved filter chip in the evolution violet. */
.sift__chip[aria-label^="Evolved"] .sift__chip-name { color: #6b4fa0; }
.sift__chip.is-on[aria-label^="Evolved"] { background: #c9a3ff; }
/* League: your best three, and the next rival breathing gently. */
.league-yours { list-style: none; margin: 0 0 10px; padding: 8px 12px; border-radius: 8px; background: rgba(127, 196, 106, 0.14); color: #a8e08f; font: 800 13px var(--label, sans-serif); letter-spacing: 0.08em; text-transform: uppercase; font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: no-preference) {
  .league-row:has(.league-row__next) { animation: next-breathe 2.4s ease-in-out infinite; }
}
@keyframes next-breathe { 50% { box-shadow: 0 0 0 3px rgba(127, 196, 106, 0.35); } }
/* Toasts sit above the card screen's and the team builder's action bars. */
body:has(#view-reveal.is-active) .toast, body:has(#view-play.is-active) .toast { bottom: calc(86px + var(--shutter-rise) + 150px + env(safe-area-inset-bottom, 0px)); }
/* Shed: clearing the parts. */
.evoparts { grid-template-columns: 56px 1fr auto auto; }
.evoparts__clear { width: 30px; height: 30px; border: 0; border-radius: 50%; background: rgba(23, 20, 12, 0.12); color: inherit; font-size: 18px; line-height: 1; }
.evoparts__clear[hidden] { display: none; }
#reveal-level-max[hidden] { display: none; }

/* ---- Polish, batch 4 --------------------------------------------------------------- */
.standoff__powers.is-ahead { color: #a8e08f; opacity: 1; }
.standoff__powers.is-behind { color: #e58a6f; opacity: 1; }
.league-row__card.is-evolved { outline: 2px solid #c9a3ff; outline-offset: 1px; }
.evolve__thumb { float: left; width: 52px; height: 52px; margin: 2px 12px 4px 0; border-radius: 8px; object-fit: cover; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25); }
#evolve-ask-lead::after { content: ''; display: block; clear: both; }
#reveal-slot .card__powerseal { cursor: help; }
.reveal-actions #reveal-level-max { align-self: center; }
.locker__row--danger .locker__t { color: #c8402a; }

/* Report / block. */
.choose { display: grid; gap: 8px; margin-top: 6px; }
.choose__opt { display: grid; gap: 2px; padding: 12px 14px; border: 1px solid rgba(23, 20, 12, 0.18); border-radius: 10px; background: rgba(255, 255, 255, 0.5); color: inherit; text-align: left; font: inherit; }
.choose__opt b { font-size: 16px; }
.choose__opt span { font-size: 13px; opacity: 0.7; }
.choose__opt--danger b { color: #c8402a; }
.tb-foe { position: relative; }
.tb-foe__more { position: absolute; top: 2px; right: 4px; padding: 0 6px; font-size: 16px; line-height: 1.2; opacity: 0.6; border-radius: 6px; }
.tb-foe__more:focus-visible { outline: 2px solid #e8b23a; }
.zoom__btn--quiet { opacity: 0.7; }
/* A link row in More looks like the others, not like a link. */
a.locker__row, a.locker__row:visited { color: inherit; text-decoration: none; }
a.locker__row .locker__t, a.locker__row .locker__s { text-decoration: none; }
.locker__row[hidden] { display: none !important; }
/* The record is out of More (Callum, 25 Sep 2026). */
#more #record { display: none !important; }

/* THE WELCOME: sign in before playing. Full screen and opaque, above everything, so the
   game does not start behind it. */
#acct.acct--welcome { z-index: 9000; background: #14120f; background-image: linear-gradient(#262a2e 1px, transparent 1px), linear-gradient(90deg, #262a2e 1px, transparent 1px); background-size: 40px 40px; }
#acct.acct--welcome .sheet__inner { max-width: 440px; margin: 0 auto; }
#acct.acct--welcome .acct-why--local, #acct.acct--welcome #acct-gate { display: none; }
.acct-welcome { display: grid; justify-items: center; gap: 10px; margin: 0 0 14px; text-align: center; }
.acct-welcome[hidden] { display: none; }
.acct-welcome__logo { width: 132px; height: 132px; margin-top: -6px; }
.acct-welcome__lede { margin: 0; font: 700 19px/1.3 var(--label, sans-serif); text-wrap: balance; }
.acct-welcome__why { margin: 0; font-size: 14px; opacity: 0.8; max-width: 34ch; }
