:root {
  --ink: #fdf6e6;
  --ink-dim: rgba(253, 246, 230, 0.62);
  --ink-faint: rgba(253, 246, 230, 0.38);
  --gold: #ffc93c;
  --gold-hi: #ffe89a;
  --gold-lo: #e08c10;
  --gold-deep: #a86a09;
  --pink: #ff4d9d;
  --teal: #28c3b8;
  --lime: #9ee34f;
  --red: #ff6b6b;
  --bg: #1a1024;
  --panel: rgba(20, 12, 30, 0.86);
  --line: rgba(255, 255, 255, 0.14);
  --line-hi: rgba(255, 255, 255, 0.26);
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);

  /* One shadow language: a tight contact shadow plus a wide ambient one. */
  --lift-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
  --lift-2: 0 2px 6px rgba(0, 0, 0, 0.45), 0 14px 34px rgba(0, 0, 0, 0.45);
  --lift-3: 0 4px 10px rgba(0, 0, 0, 0.4), 0 28px 70px rgba(0, 0, 0, 0.62);
  --ease-out: cubic-bezier(0.22, 0.8, 0.28, 1);
  --ease-back: cubic-bezier(0.18, 1.28, 0.36, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: ui-rounded, "SF Pro Rounded", system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
  touch-action: none;
}

#stage {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

#ui {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

/* ---------------------------------------------------------------- screens */

.screen {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(var(--safe-t) + 14px) 14px calc(var(--safe-b) + 14px);
  /* Warm rim at the edges so the sunset still bleeds around the sheet. */
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(255, 140, 60, 0.10), transparent 60%),
    radial-gradient(ellipse at 50% 40%, rgba(26, 16, 36, 0.5), rgba(8, 4, 14, 0.94));
  backdrop-filter: blur(5px) saturate(0.85);
  -webkit-backdrop-filter: blur(5px) saturate(0.85);
  pointer-events: auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y;
  animation: fade 0.26s ease both;
}

.screen.hidden { display: none; }

/* `.hidden` is a UTILITY, and until this rule existed it was not one: hiding was
   written per element (`.found.hidden`, `#acct-name.hidden`, `.pb.hidden`, …)
   and any element whose selector nobody remembered to add simply stayed on
   screen with the class doing nothing. That is how a signed-out player on a
   cloud-configured build got an INVITE A FRIEND block, an empty invite field
   and a completely blank gold button — js/account.js was hiding all three
   correctly and the stylesheet had no rule for any of them.

   !important because the class has to beat whatever display the element's own
   rule sets (`.btn` is display:block, `.url-row` is flex) regardless of where
   that rule sits in the file. The specific rules below are now redundant but
   harmless; this is the one that has to be true. */
.hidden { display: none !important; }

.screen::-webkit-scrollbar { width: 5px; }
.screen::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: 3px;
}

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

@keyframes sheet-in {
  from { opacity: 0; transform: translateY(20px) scale(0.94); }
  to { opacity: 1; transform: none; }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

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

.sheet {
  position: relative;
  /* Explicit stacking context: the brand ghost below sits at z-index -1 and
     blends, and neither should reach past the sheet. */
  isolation: isolate;
  width: min(420px, 100%);
  border: 1px solid var(--line);
  border-radius: 24px;
  padding: 24px 20px 20px;
  text-align: center;
  margin: auto;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0) 34%),
    linear-gradient(170deg, rgba(58, 34, 82, 0.9), rgba(16, 9, 26, 0.94) 62%, rgba(12, 7, 20, 0.95));
  box-shadow:
    var(--lift-3),
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    inset 0 -1px 0 rgba(0, 0, 0, 0.5);
  animation: sheet-in 0.42s var(--ease-back) both;
}

/* Glass sheen across the top edge — sits above content but is nearly invisible. */
.sheet::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), transparent 22%);
}

/* Oversized brand mark bleeding off the bottom-right corner. `screen` drops
   the art's black disc to nothing and lifts only the face out of the panel, so
   this needs no second asset — and where the blend mode is unsupported it
   degrades to a barely-there dark vignette rather than a black blob.
   z-index -1 keeps it above the sheet's own gradients but under every word. */
.sheet::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background-image: url("../art/primos-logo.png");
  background-repeat: no-repeat;
  background-position: 118% 108%;
  background-size: 62%;
  mix-blend-mode: screen;
  /* Kept this low because body copy sits on top of it. Screen over a near-black
     panel turns each percent into ~2.4 levels of lift, so 0.065 is already a
     readable ghost — going louder starts eating the .fine print. */
  opacity: 0.065;
}

.sheet-small { width: min(340px, 100%); }
.sheet-wide { width: min(560px, 100%); }

/* ------------------------------------------------------- the primo browser */

/* ⚠ `grid-auto-rows` IS THE FIX FOR THE OVERLAP BUG, and it must stay.

   The rows used to take their height from `aspect-ratio: 1` on the tile. That
   works everywhere except the place it shipped to: `.primo-tile` is a <button>,
   and Safari lays form controls out through their own intrinsic-size path where
   aspect-ratio is not honoured. The tile's only content is an <img> with no src
   (zero intrinsic height) and an absolutely-positioned number, so every row
   collapsed to a couple of pixels and 3,069 of them stacked into five unreadable
   vertical stripes. It looked like a rendering bug and it was a sizing one.

   Stating the row height outright makes the grid's geometry independent of what
   the tile happens to be made of. `aspect-ratio` stays below as a belt-and-
   braces for wider columns, but nothing depends on it any more. */
.primo-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(62px, 1fr));
  grid-auto-rows: 68px;
  gap: 6px;
  /* One page of 24 fits without scrolling on a phone; the cap is a guard for
     very narrow columns, not the way the grid is meant to be read. */
  max-height: min(46vh, 380px);
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 10px 0 4px;
  padding: 4px;
  border-radius: 14px;
  background: rgba(0, 0, 0, 0.28);
  box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.45);
  /* The grid scrolls; the page behind it must not rubber-band with it. */
  -webkit-overflow-scrolling: touch;
}

/* The pager. `justify-content: space-between` would push the arrows to the
   sheet edges and leave the range floating; a fixed centre column keeps the
   label still while the two buttons flank it. */
.pager {
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
  gap: 8px;
  margin: 6px 0 2px;
}

/* Ghost weight, not gold: gold is the sheet's verb (GO, RUN AS THIS ONE) and
   turning a page is not it. Two gold squares flanking the range read as two
   more calls to action and drown the one that matters. */
.pager .btn {
  margin-top: 0;
  min-height: 38px;
  color: var(--ink);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04));
  border: 1px solid var(--line);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
.pager .btn::after { inset: 1px 1px 60%; background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent); }
.pager .btn::before { display: none; }
.pager .btn:active,
.pager .btn.pressed {
  transform: translateY(2px);
  box-shadow: 0 0 0 rgba(0, 0, 0, 0.35), inset 0 2px 6px rgba(0, 0, 0, 0.35);
}
.pager .btn:disabled { opacity: 0.35; cursor: default; box-shadow: 0 2px 0 rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.14); }

.pager-range {
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

.primo-tile {
  position: relative;
  height: 100%;
  aspect-ratio: 1;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 10px;
  /* Held until the art lands, so the grid never reflows as images arrive. */
  background: linear-gradient(180deg, #3a2a46, #1d1229);
  overflow: hidden;
  cursor: pointer;
  /* Tiles outnumber every other element on screen by two orders of magnitude.
     No transition, no shadow: 3,069 of either is a scroll that judders. */
  transition: none;
}

.primo-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.18s ease;
}

.primo-tile img.in { opacity: 1; }

.primo-tile.on {
  border-color: var(--gold);
  box-shadow: 0 0 0 2px var(--gold), 0 0 14px rgba(255, 201, 60, 0.5);
}

/* The number, bottom-left, so a tile still identifies itself before its art
   arrives — and after, since scrolling 3,069 faces is otherwise unnavigable. */
.primo-tile b {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 1px 0 2px;
  background: linear-gradient(180deg, transparent, rgba(8, 4, 14, 0.82));
  font-size: 9px;
  font-weight: 800;
  color: rgba(253, 246, 230, 0.8);
  text-align: center;
  pointer-events: none;
}

.browse-pick {
  display: flex;
  gap: 12px;
  align-items: center;
  margin: 10px 0 4px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: rgba(0, 0, 0, 0.3);
}

.browse-pick canvas {
  flex: 0 0 74px;
  width: 74px;
  height: 74px;
  border-radius: 13px;
  border: 2px solid var(--gold);
  background: #1d1229;
}

/* The small sheets carry headlines, not paragraphs, so the ghost can push. */
.sheet-small::after {
  background-position: 122% 118%;
  background-size: 78%;
  opacity: 0.085;
}

#screen-pause .sheet { animation-duration: 0.3s; }

/* ------------------------------------------------------------------ logo */

.logo {
  margin: 2px 0 6px;
  line-height: 0.86;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

/* The mark, sat above the wordmark like a crest. The art already IS a black
   disc edge to edge, so a 50% radius hugs it exactly and the ring + glow can
   be pure box-shadow — no plate, no second asset, no clipping mask. */
.logo-mark {
  display: block;
  width: 78px;
  height: 78px;
  margin: 0 auto 9px;
  border-radius: 50%;
  background-image: url("../art/primos-logo.png");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  box-shadow:
    0 0 0 2px rgba(255, 201, 60, 0.26),
    0 0 34px rgba(255, 201, 60, 0.2),
    var(--lift-2);
  animation: mark-float 6s ease-in-out infinite;
}

/* Transform-only, so it never costs a layout — same rule as the button sweep. */
@keyframes mark-float {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-3px) scale(1.02); }
}

/* Pause sheet: same crest, smaller and still. A paused screen should settle. */
.mark-sm {
  width: 46px;
  height: 46px;
  margin-bottom: 14px;
  box-shadow: 0 0 0 2px rgba(255, 201, 60, 0.2), var(--lift-1);
  animation: none;
}

.logo-top {
  font-size: clamp(38px, 13vw, 54px);
  font-weight: 900;
  letter-spacing: 0.04em;
  background: linear-gradient(105deg,
    #ffe9a8 0%, #fffaea 16%, var(--gold) 34%, #e0761f 56%, var(--gold) 74%, #ffe9a8 100%);
  background-size: 280% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter:
    drop-shadow(0 3px 0 rgba(0, 0, 0, 0.32))
    drop-shadow(0 10px 26px rgba(255, 138, 30, 0.3));
  animation: logo-sheen 7s linear infinite;
}

@keyframes logo-sheen { to { background-position: -280% 0; } }

.logo-bot {
  font-size: clamp(15px, 5vw, 20px);
  font-weight: 800;
  letter-spacing: 0.34em;
  color: var(--teal);
  padding-left: 0.34em;
  text-shadow: 0 0 18px rgba(40, 195, 184, 0.45), 0 2px 0 rgba(0, 0, 0, 0.35);
}

.tagline {
  /* 10px not 16: the language switch now sits between this and the crew and
     carries the rest of the gap itself. */
  margin: 0 0 10px;
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  color: var(--ink-dim);
  text-transform: uppercase;
}

/* ------------------------------------------------------------------ crew */

.crew {
  display: flex;
  gap: 8px;
  justify-content: center;
  /* No wrap: the crew shrinks to one row rather than orphaning a tile. */
  flex-wrap: nowrap;
  margin-bottom: 8px;
}

.crew canvas {
  flex: 0 1 62px;
  min-width: 0;
  height: auto;
  aspect-ratio: 1;
  border-radius: 16px;
  border: 2px solid var(--line);
  background: linear-gradient(180deg, #3a2547, #1d1229);
  box-shadow: var(--lift-1), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  cursor: pointer;
  transition:
    transform 0.16s var(--ease-back),
    border-color 0.16s ease,
    box-shadow 0.16s ease;
}

.crew canvas:hover { transform: translateY(-3px) scale(1.03); }
.crew canvas:active { transform: translateY(0) scale(0.96); transition-duration: 0.06s; }

.crew canvas.on {
  border-color: var(--gold);
  transform: translateY(-3px);
  animation: crew-ring 2.2s ease-in-out infinite;
}

@keyframes crew-ring {
  0%, 100% { box-shadow: 0 0 0 3px rgba(255, 201, 60, 0.24), var(--lift-2), inset 0 1px 0 rgba(255, 255, 255, 0.14); }
  50% { box-shadow: 0 0 0 6px rgba(255, 201, 60, 0.1), var(--lift-2), inset 0 1px 0 rgba(255, 255, 255, 0.14); }
}

.crew-name {
  margin: 8px 0 0;
  font-weight: 900;
  font-size: 17px;
  letter-spacing: 0.12em;
  text-indent: 0.12em;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}

.crew-tag {
  margin: 3px 0 16px;
  font-size: 11px;
  color: var(--ink-dim);
  letter-spacing: 0.04em;
}

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

.btn {
  position: relative;
  overflow: hidden;
  display: block;
  width: 100%;
  min-height: 52px;
  margin: 10px 0 0;
  padding: 14px 18px;
  border: none;
  border-radius: 15px;
  font: inherit;
  font-weight: 900;
  font-size: 17px;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  color: #2a1a08;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.35);
  background: linear-gradient(180deg, var(--gold-hi), var(--gold) 52%, var(--gold-lo));
  /* Chunky bottom lip = the button has a body to press into. */
  box-shadow:
    0 4px 0 var(--gold-deep),
    0 10px 22px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.6);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  transition: transform 0.09s var(--ease-out), box-shadow 0.09s var(--ease-out), filter 0.16s ease;
}

.btn::after {                      /* top gloss */
  content: "";
  position: absolute;
  inset: 1px 1px 52%;
  border-radius: 14px 14px 7px 7px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0));
  pointer-events: none;
}

.btn:hover { filter: brightness(1.05); }

/* `.pressed` is `:active` painted by hand — js/ui-feedback.js adds it on
   pointerdown. It is not a duplicate: iOS Safari withholds `:active` until it
   knows the touch is not a scroll, and inside `.screen` (overflow-y: auto,
   touch-action: pan-y) that verdict usually lands after the finger is gone, so
   a quick tap on a phone never moves the button at all. Both selectors share
   one rule so the two can never drift into two different presses. */
.btn:active,
.btn.pressed {
  transform: translateY(4px);
  box-shadow:
    0 0 0 var(--gold-deep),
    0 4px 10px rgba(0, 0, 0, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.4);
}

.btn:focus-visible {
  outline: 3px solid rgba(255, 255, 255, 0.75);
  outline-offset: 3px;
}

/* Slow attract sweep so the main call to action never looks dead. */
.btn-primary::before {
  content: "";
  position: absolute;
  top: -60%;
  bottom: -60%;
  left: 0;
  width: 38%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.6), transparent);
  pointer-events: none;
  will-change: transform;
  animation: sweep 4.2s ease-in-out 1.2s infinite;
}

/* Transform-only so the sweep never triggers layout. */
@keyframes sweep {
  0% { transform: translateX(-180%) skewX(-18deg); }
  32%, 100% { transform: translateX(330%) skewX(-18deg); }
}

.btn-ghost {
  color: var(--ink);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04));
  border: 1px solid var(--line);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.14);
  font-size: 14px;
  min-height: 46px;
  letter-spacing: 0.12em;
  text-indent: 0.12em;
}

.btn-ghost::after { inset: 1px 1px 60%; background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent); }
.btn-ghost::before { display: none; }

.btn-ghost:active,
.btn-ghost.pressed {
  transform: translateY(2px);
  box-shadow: 0 0 0 rgba(0, 0, 0, 0.35), inset 0 2px 6px rgba(0, 0, 0, 0.35);
}

.btn-small {
  width: auto;
  min-height: 42px;
  padding: 10px 16px;
  font-size: 13px;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  margin: 0;
  border-radius: 12px;
}

.btn-small::after { border-radius: 11px 11px 6px 6px; }

/* -------------------------------------------------------------- language */

/* Segmented switch, sat high on the sheet where it cannot be missed — but
   built one step DOWN the hierarchy from CORRE on purpose: the recessed well
   is the same one the URL inputs use, so it reads as a setting rather than a
   second call to action, and only the live half wears the gold.

   Inline-flex, so the sheet's own text-align: center does the centring. */
.lang {
  display: inline-flex;
  gap: 3px;
  margin: 0 0 14px;
  padding: 3px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.42);
}

.lang button {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  padding: 8px 15px;
  border: none;
  border-radius: 999px;
  font: inherit;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.12em;
  text-indent: 0.12em;
  color: var(--ink-dim);
  background: transparent;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  transition:
    color 0.16s ease,
    background 0.22s var(--ease-out),
    box-shadow 0.22s var(--ease-out),
    transform 0.09s var(--ease-out);
}

.lang button:hover { color: var(--ink); }
.lang button:active,
.lang button.pressed { transform: translateY(1px); }

.lang button:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.75);
  outline-offset: 2px;
}

/* Same gold body and bottom lip as .btn, shrunk — one shadow language. */
.lang button[aria-pressed="true"] {
  color: #2a1a08;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.35);
  background: linear-gradient(180deg, var(--gold-hi), var(--gold) 55%, var(--gold-lo));
  box-shadow:
    0 2px 0 var(--gold-deep),
    var(--lift-1),
    inset 0 1px 0 rgba(255, 255, 255, 0.55);
}

/* On the pause sheet it is the last thing in the stack, under the buttons. */
#screen-pause .lang { margin: 14px 0 0; }

/* ----------------------------------------------------------------- stats */

.stats {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 18px 0 4px;
}

.stats > div {
  flex: 1;
  min-width: 0;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.02));
  border: 1px solid var(--line);
  border-radius: 13px;
  padding: 10px 4px 9px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 2px 8px rgba(0, 0, 0, 0.3);
}

.stats b {
  display: block;
  font-size: 20px;
  font-weight: 900;
  line-height: 1.1;
  color: var(--gold);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4), 0 0 16px rgba(255, 201, 60, 0.35);
  font-variant-numeric: tabular-nums;
}

.stats span {
  display: block;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  color: var(--ink-dim);
  margin-top: 3px;
}

/* ------------------------------------------------------------------ how */

.how {
  margin-top: 16px;
  text-align: left;
  border-top: 1px solid var(--line);
  padding-top: 12px;
}

.how summary {
  cursor: pointer;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--teal);
  text-transform: uppercase;
  list-style: none;
  padding: 4px 0;
  transition: color 0.16s ease, letter-spacing 0.2s ease;
  user-select: none;
  -webkit-user-select: none;
}

.how summary:hover { color: #5ee2d8; }
.how summary:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; border-radius: 6px; }

.how summary::-webkit-details-marker { display: none; }
.how summary::before { content: "▸ "; display: inline-block; transition: transform 0.2s var(--ease-out); }
.how[open] summary::before { content: "▾ "; }

.how[open] ul, .how[open] .hint, .how[open] .url-row,
.how[open] .file-btn, .how[open] .btn { animation: rise 0.28s var(--ease-out) backwards; }

.how ul {
  margin: 10px 0 2px;
  padding-left: 18px;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--ink-dim);
}

.how ul li { padding-left: 2px; }
.how ul li::marker { color: rgba(255, 201, 60, 0.55); }
.how ul b { color: var(--ink); font-weight: 800; }

.hint {
  font-size: 11.5px;
  color: var(--ink-dim);
  line-height: 1.55;
  margin: 10px 0;
}

/* The top margin is load bearing, not taste. `.btn` paints a 4px solid lip and
   a 22px blur BELOW its border box, and box-shadow reserves no layout space —
   so a following element with no top margin of its own lands inside the lip and
   the button appears to sit on top of it. This one follows COPY CODE on the
   ACCOUNT screen, where it did exactly that. Same reason `.sec` carries one. */
.file-btn {
  display: inline-block;
  margin-top: 10px;
  padding: 10px 16px;
  border-radius: 11px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
  /* Caps like every other control — this was the one sentence-case button in
     the game, and one lowercase button on an all-caps screen reads as a
     different, cheaper kind of thing. */
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform 0.1s var(--ease-out), border-color 0.16s ease;
}

.file-btn:hover { border-color: var(--line-hi); }
.file-btn:active,
.file-btn.pressed { transform: translateY(2px); }

/* The gameplay-stats opt-out on ACCOUNT. A whole-row label so the tap target is
   the sentence and not just the 16px box — this is a privacy control, and one
   that is fiddly to hit on a phone is one that does not really exist. */
.opt-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 11px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.03);
  font-size: 13px;
  cursor: pointer;
}
.opt-row:hover { border-color: var(--line-hi); }
.opt-row:active,
.opt-row.pressed { border-color: var(--line-hi); background: rgba(255, 255, 255, 0.08); }
.opt-row input { width: 17px; height: 17px; accent-color: var(--gold, #ffc93c); flex: none; }

.url-row {
  display: flex;
  gap: 6px;
  margin: 10px 0;
}

.url-row input {
  flex: 1;
  min-width: 0;
  padding: 11px 12px;
  border-radius: 11px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}

.url-row input:focus {
  outline: none;
  border-color: rgba(255, 201, 60, 0.7);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4), 0 0 0 3px rgba(255, 201, 60, 0.18);
}

.url-row input::placeholder { color: var(--ink-faint); }

.btn.wide { width: 100%; margin: 8px 0 0; }

.hint.sep {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

/* number input without the spinner arrows */
#primo-num::-webkit-outer-spin-button,
#primo-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#primo-num { -moz-appearance: textfield; appearance: textfield; }

/* ------------------------------------------------- found & claim a Primo */

/* The card between searching and claiming: the art you asked for, its number,
   and whether it is actually yours to take. Sits inside #primo-panel. */
.found {
  display: flex;
  gap: 12px;
  align-items: center;
  margin: 12px 0 4px;
  padding: 10px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.02));
  box-shadow: var(--lift-1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  animation: rise 0.28s var(--ease-out) both;
}

.found.hidden { display: none; }

.found canvas {
  flex: 0 0 74px;
  width: 74px;
  height: 74px;
  border-radius: 13px;
  border: 2px solid var(--gold);
  background: linear-gradient(180deg, #3a2547, #1d1229);
  box-shadow: 0 0 0 3px rgba(255, 201, 60, 0.16), var(--lift-1);
}

/* min-width: 0 so a long holder handle wraps instead of shoving the art out
   of the sheet — the flex default would let this track overflow. */
.found-meta {
  flex: 1;
  min-width: 0;
  text-align: left;
}

.found-num {
  margin: 0;
  font-weight: 900;
  font-size: 15px;
  letter-spacing: 0.1em;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}

.found-state {
  margin: 3px 0 8px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--ink-dim);
  overflow-wrap: anywhere;
}

.found-state.ok { color: var(--lime); }
.found-state.no { color: var(--red); }

#btn-claim { min-height: 38px; padding: 8px 16px; font-size: 12px; }

/* Spoken for, or already yours — either way there is nothing to tap, and the
   button says so rather than looking live and doing nothing.
   This predates the general `.btn:disabled` at the foot of the file and stays
   its own rule: CLAIM is a short button with a 2px lip, so it needs its own
   shadow, and grayscale+brightness is tuned against the picker's panel rather
   than the account sheet. `opacity: 1` is the opt-out — without it the general
   rule's 0.5 would stack on top of the brightness already here and fade this
   one to a smudge. */
#btn-claim:disabled {
  filter: grayscale(0.85) brightness(0.66);
  opacity: 1;
  cursor: default;
  transform: none;
  box-shadow: 0 2px 0 var(--gold-deep), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

#btn-claim:disabled::before { display: none; }

/* Status lines are SPOKEN, not boxed. The rank line on game over set the
   register — teal, bold, bare — and every inline status follows it: one
   voice for "here is what just happened", no callout furniture around it. */
#primo-status:not(:empty) {
  margin-top: 10px;
  font-weight: 700;
  color: var(--teal);
  animation: rise 0.24s var(--ease-out) both;
}

.fine {
  margin: 16px 0 0;
  font-size: 10px;
  line-height: 1.55;
  color: var(--ink-faint);
}

/* ------------------------------------------------------------------ help */

/* The ? in the corner of the menu.
 *
 * Round, small, and pinned to the VIEWPORT rather than to the sheet, so
 * scrolling the menu never takes it away. It has to be a sibling of the
 * screens to manage that — every `.screen` sets a backdrop-filter, which makes
 * it the containing block for anything fixed inside it, and the button would
 * scroll with the content. `pointer-events: auto` because #ui above it is
 * `none`; the z-index because .screen's backdrop-filter gives each screen a
 * stacking context of its own. */
.help-fab {
  position: fixed;
  top: calc(var(--safe-t) + 12px);
  right: calc(env(safe-area-inset-right, 0px) + 14px);
  z-index: 60;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line-hi);
  background: linear-gradient(180deg, rgba(74, 47, 88, 0.94), rgba(24, 14, 36, 0.95));
  box-shadow: var(--lift-1), inset 0 1px 0 rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--gold);
  font: inherit;
  font-size: 21px;
  font-weight: 900;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  transition: transform 0.12s var(--ease-out), border-color 0.16s ease, color 0.16s ease;
  animation: fab-in 0.42s var(--ease-back) both;
}

.help-fab:hover { border-color: rgba(255, 201, 60, 0.8); color: var(--gold-hi); }
/* `.pressed` is the hand-painted `:active` — see the note on `.btn.pressed`. */
.help-fab:active,
.help-fab.pressed { transform: scale(0.92); }
.help-fab:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

@keyframes fab-in {
  from { opacity: 0; transform: scale(0.4); }
  to { opacity: 1; transform: none; }
}

/* This sheet is the whole manual, and it is all left-aligned reading matter —
   the section labels and the two body lines have to agree with the lists they
   sit above, not with the centred sheet they are in. */
#screen-help .sec,
#screen-help .hint { text-align: left; }

/* Same rows as the menu's `how` list used to be, minus the disclosure triangle
   — this sheet IS the disclosure, so nothing here should need a second tap. */
.help-list {
  margin: 0 0 4px;
  padding-left: 18px;
  text-align: left;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--ink-dim);
}

.help-list li { padding-left: 2px; }
.help-list li::marker { color: rgba(255, 201, 60, 0.55); }
.help-list b { color: var(--ink); font-weight: 800; }

/* The rule that gets new players killed, pulled out of the bullets and given a
   red box of its own. As a sixth bullet it read as trivia; the players who most
   need it are the ones who skim. */
.help-rule {
  margin: 14px 0 4px;
  padding: 11px 13px;
  text-align: left;
  border-radius: 14px;
  border: 1px solid rgba(255, 107, 107, 0.4);
  background: linear-gradient(180deg, rgba(255, 107, 107, 0.12), rgba(255, 107, 107, 0.04));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

.help-rule-tag {
  margin: 0;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: rgba(255, 107, 107, 0.85);
}

.help-rule-title {
  margin: 4px 0 0;
  font-size: 13.5px;
  font-weight: 900;
  letter-spacing: 0.08em;
  color: var(--ink);
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}

.help-rule-body {
  margin: 5px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-dim);
}

/* What the alley hands you. A legend rather than a fifth bullet list: each row
   is dotted in the colour that pickup actually is on screen, so the sheet and
   the run are visibly naming the same objects. Beer and chancla share a gold
   because they share it in the game — the dots follow the alley, not vice
   versa. Colours are the POWERUP entries in js/config.js. */
.help-loot {
  margin: 0 0 4px;
  padding: 0;
  list-style: none;
  text-align: left;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-dim);
}

.help-loot li {
  position: relative;
  padding: 3px 0 3px 18px;
}

.help-loot li::before {
  content: "";
  position: absolute;
  left: 2px;
  /* Against the cap height of the first line, not its box, so the dot sits on
     the word rather than floating above a row that has wrapped. */
  top: 0.75em;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gold);
}

.help-loot b { color: var(--ink); font-weight: 800; }

/* Qualified by the list, and not because the rows are nested anywhere: bare
   `.loot-taco::before` is a class and a pseudo-element, which LOSES to the
   `.help-loot li::before` above it, and every dot comes out gold. */
.help-loot .loot-taco::before { background: var(--lime); }
.help-loot .loot-magnet::before { background: #ff4d9d; }
.help-loot .loot-chancla::before { background: #ffcf3d; }
.help-loot .loot-skateboard::before { background: #4dd8ff; }

#btn-help-back { margin-top: 16px; }

/* --------------------------------------------------------------- feedback */

/* The way in: Corrupt's face on the HELP sheet, with the ask next to it.
   A bare icon would have been quieter and would also have been ignored —
   nobody taps a portrait to find out what it does. The face is the draw and
   the two lines beside it are the instruction, so the whole row is one target
   rather than a picture with a caption. */
.corrupt-cta {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin-top: 16px;
  padding: 10px 12px;
  text-align: left;
  border-radius: 16px;
  border: 1px solid rgba(255, 201, 60, 0.32);
  background: linear-gradient(180deg, rgba(255, 201, 60, 0.13), rgba(255, 201, 60, 0.04));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  transition: transform 0.1s var(--ease-out), border-color 0.16s ease, background 0.16s ease;
}

.corrupt-cta:hover {
  border-color: rgba(255, 201, 60, 0.55);
  background: linear-gradient(180deg, rgba(255, 201, 60, 0.2), rgba(255, 201, 60, 0.06));
}

.corrupt-cta:active { transform: translateY(2px); }
.corrupt-cta:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.corrupt-cta.hidden { display: none; }

.corrupt-cta-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.corrupt-cta-text b {
  font-size: 12.5px;
  font-weight: 900;
  letter-spacing: 0.12em;
  color: var(--gold);
}

.corrupt-cta-text span {
  font-size: 11px;
  line-height: 1.4;
  color: var(--ink-dim);
}

/* js/art/trainer.js paints the badge — gold ring, vignette and all — straight
   into these. The CSS size is the layout size; main.js sets the backing store
   to that × dpr, so the face is not a soft 72px bitmap on a phone. */
.corrupt-face {
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: 50%;
}

.corrupt-face-lg {
  display: block;
  width: 76px;
  height: 76px;
  margin: 0 auto 10px;
}

.fb-message {
  width: 100%;
  margin: 4px 0 0;
  padding: 11px 12px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  line-height: 1.5;
  text-align: left;
  resize: vertical;
  transition: border-color 0.16s ease;
}

.fb-message:focus,
.fb-contact:focus {
  outline: none;
  border-color: rgba(255, 201, 60, 0.6);
}

.fb-contact {
  width: 100%;
  padding: 11px 12px;
  border-radius: 11px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  transition: border-color 0.16s ease;
}

/* The counter only ever appears once it is worth knowing — a 1000-character
   budget shown against an empty box reads as a demand for an essay. */
.fb-count {
  margin: 4px 0 0;
  text-align: right;
  font-size: 10px;
}

.fb-count.near { color: var(--gold); }

#btn-feedback-send { margin-top: 14px; }

/* Same voice as #primo-status — see the note there. */
#fb-status:not(:empty) {
  margin-top: 12px;
  font-weight: 700;
  color: var(--teal);
  animation: rise 0.24s var(--ease-out) both;
}

#fb-status.bad:not(:empty) { color: var(--red); }

/* ------------------------------------------------------------- game over */

.bust {
  margin: 0 0 16px;
  font-size: 15px;
  font-weight: 900;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  color: var(--red);
  text-shadow: 0 0 20px rgba(255, 60, 60, 0.55), 0 2px 0 rgba(0, 0, 0, 0.45);
  animation: bust-in 0.5s var(--ease-out) both;
}

@keyframes bust-in {
  0% { opacity: 0; transform: translateY(-8px) scale(1.1); letter-spacing: 0.36em; }
  100% { opacity: 1; transform: none; letter-spacing: 0.16em; }
}

.final-label {
  margin: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  color: var(--ink-dim);
  animation: rise 0.34s var(--ease-out) 0.1s both;
}

.final {
  position: relative;
  margin: 4px 0 0;
  font-size: clamp(50px, 16vw, 62px);
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--gold);
  background: linear-gradient(180deg, #fff6d2, var(--gold) 52%, #e07d10);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  font-variant-numeric: tabular-nums;
  animation:
    score-in 0.66s var(--ease-back) 0.16s both,
    score-glow 3s ease-in-out 0.82s infinite both;
}

/* The payoff: slam in, overshoot, settle, then breathe. */
@keyframes score-in {
  0% { opacity: 0; transform: scale(0.45); }
  55% { opacity: 1; transform: scale(1.16); }
  78% { transform: scale(0.97); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes score-glow {
  0%, 100% { filter: drop-shadow(0 3px 0 rgba(0, 0, 0, 0.32)) drop-shadow(0 0 16px rgba(255, 180, 40, 0.35)); }
  50% { filter: drop-shadow(0 3px 0 rgba(0, 0, 0, 0.32)) drop-shadow(0 0 30px rgba(255, 190, 60, 0.62)); }
}

.pb {
  margin: 10px 0 0;
  font-size: 13px;
  font-weight: 900;
  letter-spacing: 0.18em;
  text-indent: 0.18em;
  color: var(--lime);
  text-shadow: 0 0 18px rgba(158, 227, 79, 0.6);
  animation: pop 0.5s ease 0.5s both, pb-pulse 1.6s ease-in-out 1s infinite;
}

.pb.hidden { display: none; }

@keyframes pop {
  0% { transform: scale(0.6); opacity: 0; }
  60% { transform: scale(1.12); }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes pb-pulse {
  0%, 100% { text-shadow: 0 0 18px rgba(158, 227, 79, 0.55); }
  50% { text-shadow: 0 0 30px rgba(158, 227, 79, 0.95); }
}

/* Staggered reveal — the score lands before the rest of the sheet catches up. */
#screen-over .stats { animation: rise 0.4s var(--ease-out) 0.3s both; }
#screen-over .btn { animation: rise 0.36s var(--ease-out) 0.4s backwards; }
#screen-over .btn-ghost { animation-delay: 0.46s; }

h2 {
  margin: 0 0 18px;
  font-size: 26px;
  font-weight: 900;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.4), 0 0 24px rgba(255, 201, 60, 0.2);
}

/* ------------------------------------------------------------ responsive */

@media (max-height: 700px) {
  .sheet { padding: 16px 16px 14px; border-radius: 20px; }
  .crew canvas { flex-basis: 54px; border-radius: 14px; }
  .logo-mark { width: 56px; height: 56px; margin-bottom: 6px; }
  .mark-sm { width: 40px; height: 40px; margin-bottom: 10px; }
  .logo-top { font-size: 34px; }
  .tagline { margin-bottom: 8px; }
  .lang { margin-bottom: 10px; padding: 2px; }
  .lang button { padding: 6px 13px; }
  #screen-pause .lang { margin-top: 10px; }
  .crew-tag { margin-bottom: 12px; }
  .stats { margin-top: 12px; }
  .btn { min-height: 48px; margin-top: 8px; }
  .final { font-size: clamp(42px, 14vw, 54px); }
  /* At 320x568 in Spanish this sheet is the tallest thing in the game and was
     pushing GOT IT off the bottom by about the height of one line — and that
     was before it absorbed the menu's how-to, the pickups and the training
     button. It scrolls now rather than fits, so this is about how much of it
     lands in the first screenful, not about avoiding a cut-off. */
  .help-list { font-size: 12px; line-height: 1.55; }
  .help-loot { font-size: 12px; line-height: 1.45; }
  .help-loot li { padding-top: 2px; padding-bottom: 2px; }
  .help-rule { margin-top: 10px; padding: 9px 11px; }
  .help-rule-body { margin-top: 4px; font-size: 11px; line-height: 1.45; }
  #screen-help .sec { margin: 12px 0 5px; }
  #screen-help .sec.sep { margin-top: 13px; padding-top: 10px; }
  #screen-help .hint { margin: 8px 0; font-size: 11px; }
  #btn-help-back { margin-top: 12px; }
  /* HELP is the tallest sheet in the game and the Corrupt row is new weight on
     it, so on a 568px screen it gives back roughly what it costs. `.screen`
     scrolls, but a GOT IT you have to scroll to find is one nobody finds — the
     comment above this block exists because that already happened once.
     Measured at 320×568 with the row present: GOT IT sat 34px below the fold in
     English, 20px in Spanish. Dropping the small wordmark — pure decoration on
     a sheet whose <h2> already says HOW TO PLAY — buys back 50px and lands both
     languages inside the viewport with room to spare. It is the cheapest thing
     on this sheet and the only one that carries no information. */
  #screen-help .mark-sm { display: none; }
  .corrupt-cta { margin-top: 10px; padding: 8px 10px; gap: 10px; }
  .corrupt-face { width: 44px; height: 44px; }
  .corrupt-cta-text span { font-size: 10.5px; }
  .corrupt-face-lg { width: 60px; height: 60px; margin-bottom: 8px; }
  .fb-message { font-size: 12.5px; }
  #btn-feedback-send { margin-top: 10px; }
  /* The ? is NOT shrunk with the rest of it. Everything else in this block is
     buying vertical space back, and a fixed element takes none — all shrinking
     it would cost is the tap target on the one control that leads to any of
     this. It only moves up, out of the way of the shorter sheet. */
  .help-fab { top: calc(var(--safe-t) + 9px); }
}

@media (min-width: 760px) {
  .sheet { padding: 28px 26px 22px; }
  .crew canvas { flex-basis: 68px; }
  .logo-mark { width: 90px; height: 90px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===================================================== la tiendita + continue
   Corrupt's corner store and the offer he makes at the bust. Appended as one
   block on purpose: everything above is untouched, so this file merges. */

/* The spendable balance, riding on the button that spends it. Not the same
   number as the lifetime CHELAS stat — see js/wallet.js. */
.wallet-chip {
  display: inline-block;
  margin-left: 9px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.38);
  border: 1px solid rgba(255, 201, 60, 0.4);
  color: var(--gold);
  font-size: 13px;
  font-weight: 900;
  letter-spacing: 0.04em;
  text-indent: 0;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 12px rgba(255, 201, 60, 0.4);
  vertical-align: 1px;
}

.shop-kicker {
  margin: 0 0 6px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: var(--teal);
  text-transform: uppercase;
}

#screen-shop h2 { margin-bottom: 10px; }

.shop-blurb {
  margin: 0 0 14px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-dim);
}

/* The till. Reads as a receipt line rather than a stat tile, so it never gets
   mistaken for the lifetime counter on the menu. */
.shop-wallet {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 14px;
  padding: 9px 14px;
  border-radius: 13px;
  border: 1px solid rgba(255, 201, 60, 0.28);
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.45);
}

.shop-wallet span {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.16em;
  color: var(--ink-dim);
}

.shop-wallet b {
  font-size: 24px;
  font-weight: 900;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4), 0 0 18px rgba(255, 201, 60, 0.4);
}

.shop-list { display: flex; flex-direction: column; gap: 8px; }

/* One shelf row: the goods, what they do, the price. --tone is set per item
   from the catalog, so a new row needs no new CSS.

   The item's tone lives on the ICON PLATE (frame + inner glow), not on an
   accent bar down the row's edge. A border-left stripe is furniture from a
   different product — an alert box — and it bends around the row's rounded
   corners besides. The icon is already the thing the tone describes. */
.shop-item {
  --tone: var(--gold);
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  text-align: left;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.02));
  box-shadow: var(--lift-1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  animation: rise 0.28s var(--ease-out) both;
}

/* Only goods you can actually reach for look alive on the shelf. */
.shop-item.broke { opacity: 0.62; }

.shop-icon {
  flex: 0 0 58px;
  width: 58px;
  height: 58px;
  border-radius: 13px;
  border: 1px solid var(--line);
  background: linear-gradient(180deg, #33203f, #1a1026);
}

/* The tone, worn by the goods themselves. color-mix so the catalog's solid
   hex softens against the glass; where color-mix is unsupported these two
   declarations drop and the plate keeps its neutral frame above — quieter,
   never broken. `.broke`'s grayscale filter mutes the tint along with the
   art, so an unaffordable row dims as one object with no extra rule. */
.shop-item .shop-icon {
  border-color: color-mix(in srgb, var(--tone) 52%, transparent);
  box-shadow: inset 0 0 16px -6px color-mix(in srgb, var(--tone) 55%, transparent);
}

.shop-item.broke .shop-icon { filter: grayscale(0.6) brightness(0.8); }

.shop-meta { flex: 1; min-width: 0; }

.shop-name {
  margin: 0;
  font-size: 13.5px;
  font-weight: 900;
  letter-spacing: 0.06em;
  text-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
}

.shop-blurb-i {
  margin: 2px 0 0;
  font-size: 11px;
  line-height: 1.35;
  color: var(--ink-dim);
}

.shop-stock {
  margin: 4px 0 0;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--lime);
}

.shop-buy {
  flex: 0 0 auto;
  min-width: 60px;
  min-height: 40px;
  padding: 9px 12px;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

.shop-item.broke .shop-buy {
  min-width: 74px;
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-indent: 0.06em;
  color: var(--ink-dim);
}

/* Same voice as #primo-status — see the note there. */
.shop-note:not(:empty) {
  margin: 14px 0 0;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--teal);
  animation: rise 0.24s var(--ease-out) both;
}

#btn-shop-back { margin-top: 16px; }

/* ------------------------------------------------------------- continue */

.cont-kicker {
  margin: 0 0 8px;
  font-size: 10.5px;
  font-weight: 900;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  color: var(--red);
  text-shadow: 0 0 18px rgba(255, 60, 60, 0.5);
  animation: bust-in 0.5s var(--ease-out) both;
}

#screen-continue h2 { margin-bottom: 10px; }

.cont-body {
  margin: 0 0 14px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--ink-dim);
}

.cont-caption {
  margin: 0;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: var(--ink-faint);
}

/* Both rows reuse .stats, so the run you are being sold back is shown in the
   exact tiles the game over sheet uses. Same numbers, same furniture. */
.cont-keep { margin: 6px 0 14px; }
.cont-price { margin: 0 0 4px; }
.cont-price b { font-size: 26px; }
.cont-price .cost b { color: var(--gold); }
.cont-price .short b { color: var(--red); text-shadow: 0 0 16px rgba(255, 107, 107, 0.45); }

.cont-short {
  margin: 12px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--red);
}

.cont-short.hidden, #btn-cont-shop.hidden, #btn-continue.hidden { display: none; }

@media (max-height: 700px) {
  .shop-item { gap: 9px; padding: 7px 8px; }
  .shop-icon { flex-basis: 48px; width: 48px; height: 48px; }
  .shop-blurb, .cont-body { font-size: 11.5px; }
  .shop-wallet { margin-bottom: 10px; padding: 7px 12px; }
  .shop-wallet b { font-size: 20px; }
  .cont-keep { margin-bottom: 10px; }
}

/* At 320 the shelf row has about 110px left for words once the icon and the
   price have taken theirs, and ONE MORE LIFE breaks over three lines in it.
   Everything here buys that column back. */
@media (max-width: 360px) {
  .shop-item { gap: 8px; padding: 8px 7px; }
  .shop-icon { flex-basis: 46px; width: 46px; height: 46px; }
  .shop-name { font-size: 12.5px; letter-spacing: 0.03em; }
  .shop-blurb-i { font-size: 10.5px; }
  .shop-buy { min-width: 52px; padding: 9px 9px; font-size: 13px; }
  .shop-item.broke .shop-buy {
    min-width: 0;
    padding: 9px 8px;
    font-size: 9.5px;
    letter-spacing: 0.02em;
    text-indent: 0.02em;
  }
  .cont-price b { font-size: 23px; }
}

/* ------------------------------------------------- cuenta + la tabla */

/* Two ghost buttons side by side under CORRE. `.btn` carries its own top
   margin, so the row zeroes it and owns the spacing itself. */
.row-2 {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

.row-2 .btn { flex: 1; margin-top: 0; }

/* Small-caps section heading — the gold counterpart to `.hint`.
   The top margin clears the drop lip of whatever button precedes it: RUNNER
   NAME sits directly under SIGN IN WITH GOOGLE, in a different wrapper div, so
   no adjacent-sibling rule can catch that pairing — the heading has to carry
   the gap itself. See the note on `.file-btn`. */
.sec {
  margin: 16px 0 6px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  color: var(--gold);
}

.sec.sep {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

/* --- board + feedback tabs ---

   The SAME segmented control as the language switch, stretched to the sheet:
   one recessed well, one gold thumb with the house lip. Two different
   pick-one controls on one game is one too many — a player who has used
   ENGLISH/ESPAÑOL has already learned this one. */

.tabs {
  display: flex;
  gap: 3px;
  margin-bottom: 12px;
  padding: 3px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.42);
}

.tab {
  flex: 1;
  padding: 8px 10px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-dim);
  font: inherit;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.12em;
  text-indent: 0.12em;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  transition:
    color 0.16s ease,
    background 0.22s var(--ease-out),
    box-shadow 0.22s var(--ease-out),
    transform 0.09s var(--ease-out);
}

.tab:hover { color: var(--ink); }
.tab:active,
.tab.pressed { transform: translateY(1px); }
.tab:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.75); outline-offset: 2px; }

.tab.on {
  color: #2a1a08;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.35);
  background: linear-gradient(180deg, var(--gold-hi), var(--gold) 55%, var(--gold-lo));
  box-shadow:
    0 2px 0 var(--gold-deep),
    var(--lift-1),
    inset 0 1px 0 rgba(255, 255, 255, 0.55);
}

/* Which board you are looking at. Without it a daily board reads as a broken
   all-time board every midnight. */
/* Tracking is tight here because the Spanish string is the long one —
   "se reinicia a medianoche UTC" orphans the UTC onto its own line at 0.18em. */
.board-key {
  margin: 0 0 10px;
  font-size: 10px;
  letter-spacing: 0.08em;
  line-height: 1.5;
  color: var(--ink-faint);
}

/* --- the rows --- */

.board {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 46vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.board li {
  display: grid;
  grid-template-columns: 26px 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 8px 9px;
  border-radius: 10px;
  font-size: 13px;
  animation: rise 0.24s var(--ease-out) backwards;
}

.board li + li { margin-top: 3px; }

.board .r {
  font-size: 11px;
  font-weight: 800;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* The podium reads at a glance, so it does not need the number to be legible. */
.board li:nth-child(1) .r { color: var(--gold); }
.board li:nth-child(2) .r { color: #cfd6e0; }
.board li:nth-child(3) .r { color: #d69a62; }

/* `.sheet` centres its text, which is right for everything else on it and wrong
   for a ranked list: centred names leave the column edge ragged and give the eye
   nothing to run down. */
.board .r { text-align: left; }

.board .n {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  color: var(--ink);
}

.board .v {
  text-align: right;
  font-weight: 800;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

/* Your own row — the first thing anyone looks for. */
.board li.you {
  background: rgba(255, 201, 60, 0.13);
  box-shadow: inset 0 0 0 1px rgba(255, 201, 60, 0.34);
}

.board li.you .n { font-weight: 800; }

/* Pinned own-rank readout, for when you are outside the top rows — otherwise
   the board is a wall of strangers and says nothing about you. Dressed
   EXACTLY like `.board li.you` above: "you" is one visual word on this
   screen, whether your row is in the list or summarised under it. */
.board-me {
  margin: 10px 0 0;
  padding: 9px 10px;
  border-radius: 10px;
  background: rgba(255, 201, 60, 0.13);
  box-shadow: inset 0 0 0 1px rgba(255, 201, 60, 0.34);
  font-size: 12px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.board-me.hidden, .board-empty.hidden { display: none; }

/* Signed out, unconfigured, offline and genuinely-empty all land here. */
.board-empty {
  margin: 18px 0;
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-dim);
  text-align: center;
}

/* --- game-over standing --- */

.rank-line {
  margin: 10px 0 0;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--teal);
  animation: rise 0.34s var(--ease-out) both;
}

.rank-line.hidden { display: none; }

/* --- account --- */

#acct-auth .hint { margin-bottom: 10px; }
#acct-name.hidden { display: none; }

.backup-out {
  width: 100%;
  margin: 8px 0 0;
  padding: 9px 11px;
  border-radius: 11px;
  border: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4);
  color: var(--ink);
  font: inherit;
  font-size: 11px;
  word-break: break-all;
  resize: vertical;
}

.backup-out.hidden { display: none; }

/* Same voice as #primo-status — see the note there. */
#acct-status:not(:empty) {
  margin-top: 12px;
  font-weight: 700;
  color: var(--teal);
  animation: rise 0.24s var(--ease-out) both;
}

#acct-status.bad:not(:empty) { color: var(--red); }

/* ------------------------------------------------- the continue mark

   Continued runs are eligible for the boards, so every place one of their
   scores is shown carries this. Deliberately quiet — it is a fact about the
   run, not an accusation, and the score beside it still counts. */

.board .cont {
  margin-left: 5px;
  font-style: normal;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--ink-faint);
  vertical-align: 1px;
}

/* Sits under the rows, in the same register as .board-key. */
.board-note {
  margin: 8px 0 0;
  font-size: 10px;
  letter-spacing: 0.06em;
  line-height: 1.5;
  color: var(--ink-faint);
}

.board-note.hidden { display: none; }

/* The game-over standing when the run was bought. Same line, same place —
   dimmed off the teal so it reads as qualified rather than celebrated. */
.rank-line.bought { color: var(--ink-dim); }

/* ================================================== press feedback + toast

   The states a button needs so it never reads as dead. `.pressed` lives with
   the `:active` rules further up, next to the shadow it cancels; these three
   are their own block because nothing above them knew they existed.
   js/ui-feedback.js is what adds all of them. */

/* Disabled. Until this rule existed, `btn.disabled = true` — which sign-in,
   sign-out and COLLECT all do — changed absolutely nothing on screen: the
   button kept its full gold, kept the attract sweep, and simply stopped
   responding. A control that looks pressable and is not is worse than a
   greyed-out one, because the player concludes the whole screen is broken.

   Placed after `.btn:hover` on purpose: same specificity, so being later is
   what stops a hover from lighting a dead button back up. */
.btn:disabled {
  cursor: default;
  opacity: 0.5;
  filter: saturate(0.45);
  box-shadow: 0 4px 0 var(--gold-deep), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

/* The attract sweep says "press me". Not while it cannot be pressed. */
.btn:disabled::before { animation: none; opacity: 0; }

/* A disabled button must not depress, by either route. */
.btn:disabled:active,
.btn:disabled.pressed { transform: none; }

/* Working. The label already says so; this is what keeps saying it 400ms
   later, when a static "COLLECTING…" starts to read as stuck instead. The
   animation beats the 0.5 opacity above, which is the point — busy is a live
   state and plain disabled is not. */
.btn.busy { animation: busy-pulse 1.15s ease-in-out infinite; }

@keyframes busy-pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

/* Landed. COPY becoming COPIED is the confirmation the eye is already pointed
   at; the pop is what makes the swap register as an event rather than as text
   that was always there. */
.btn.flash { animation: flash-pop 0.42s var(--ease-back); }

@keyframes flash-pop {
  0% { transform: translateY(3px) scale(0.99); }
  55% { transform: translateY(-2px) scale(1.035); }
  100% { transform: none; }
}

/* The toast. Fixed to the VIEWPORT, not to the sheet, which is the whole
   point: the ACCOUNT sheet is taller than a phone and its inline status line
   sits at the bottom of it, so a confirmation for a button in the middle of
   the scroll was written several hundred pixels below the fold. Bottom-centre
   because that is where a thumb is not — the sheet's own buttons are under the
   finger that just pressed one. */
#ui-toast {
  position: fixed;
  /* Centred by equal offsets and auto margins, NOT by `left: 50%` — a fixed box
     with no width shrinks to fit the space left of its containing block's right
     edge, so `left: 50%` silently caps it at HALF the screen and every message
     of more than about four words wrapped for no visible reason. */
  left: 14px;
  right: 14px;
  margin-inline: auto;
  width: fit-content;
  /* --toast-lift clears the PWA update bar when that is up. Set from JS. */
  bottom: calc(var(--safe-b) + 18px + var(--toast-lift, 0px));
  z-index: 90;
  max-width: min(360px, calc(100vw - 28px));
  /* A pill, like the wallet chip and the language switch — the round ends
     want a touch more side padding than the old rounded box did. */
  padding: 11px 19px;
  border-radius: 999px;
  border: 1px solid var(--line-hi);
  background: rgba(18, 10, 28, 0.96);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: var(--lift-2), inset 0 1px 0 rgba(255, 255, 255, 0.1);
  color: var(--ink);
  font-size: 13px;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
  /* It lands over a scrolling sheet, so it may never eat the next tap. */
  pointer-events: none;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.22s ease, transform 0.28s var(--ease-out);
}

#ui-toast.show { opacity: 1; transform: translateY(0); }

/* A confirmation is a quiet dark pill; only FAILURE changes its clothes —
   the same red-tinted card the help sheet's warning wears, so "that did not
   work" and "this will get you caught" are one colour, one surface. */
#ui-toast.bad {
  border-color: rgba(255, 107, 107, 0.55);
  background: linear-gradient(180deg, rgba(255, 107, 107, 0.16), rgba(255, 107, 107, 0.07)),
    rgba(18, 10, 28, 0.96);
}

/* ------------------------------------------------------------------ el fit */
/* The gear half of la tiendita shares .shop-item wholesale; these are only
   the states the consumables never had. */

.shop-sect {
  margin: 14px 2px 4px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.16em;
  color: var(--ink-dim);
  text-transform: uppercase;
}

/* Owned reads as settled, not dead: full opacity (unlike .broke), a faint
   gold edge — the same "yours" tint the racha chip and your board row wear —
   and the button carries the WEAR/TAKE OFF verb instead of a price. */
.shop-item.owned { border-color: rgba(255, 201, 60, 0.3); }
.shop-item.owned .shop-name::after {
  content: ' ✓';
  color: var(--lime);
}

/* -------------------------------------------------- coming back tomorrow */
/* The menu's daily card. Same glass the sheets use, deliberately quieter
   than the RUN button above it — it supports the run, it does not compete. */

.daily { margin-top: 10px; display: grid; gap: 8px; }

.racha-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-radius: 12px;
  background: rgba(255, 201, 60, 0.10);
  border: 1px solid rgba(255, 201, 60, 0.28);
  font-size: 12px;
}
.racha-chip b { color: var(--gold); letter-spacing: 0.08em; white-space: nowrap; }
/* The count and the word are separate spans and the i18n swap rewrites the
   word's textContent, which eats the leading space the markup carried — "4LA
   RACHA". The gap lives here in CSS where no translation can lose it. */
#racha-len { margin-right: 0.34em; }
.racha-chip .racha-note { color: var(--ink-dim); font-size: 11px; line-height: 1.35; }

/* At risk: the border warms up and the flame does one slow breath. Loss
   aversion needs to be SEEN, but a strobe on the main menu is a migraine. */
.racha-chip.risk { border-color: rgba(255, 107, 107, 0.5); background: rgba(255, 107, 107, 0.08); }
.racha-chip.risk .racha-note { color: var(--ink); }
.racha-chip.risk .racha-flame { animation: racha-breathe 1.8s ease-in-out infinite; }
@keyframes racha-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.18); }
}

.jales-card {
  padding: 10px 12px 8px;
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid rgba(253, 246, 230, 0.10);
  text-align: left;
}
.jales-title {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.16em;
  color: var(--ink-dim);
}
.jale-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 3px 0;
  font-size: 12px;
}
.jale-row .jale-name { color: var(--ink); letter-spacing: 0.04em; }
.jale-row .jale-prog { color: var(--ink-dim); font-weight: 700; white-space: nowrap; }
/* Done wears the check the shop's owned rows wear — one mark for "settled"
   everywhere. Strikethrough is how a to-do list talks, not a game. */
.jale-row.done .jale-name { color: var(--ink-faint); }
.jale-row.done .jale-name::before { content: '✓ '; color: var(--lime); font-weight: 800; }
.jale-row.done .jale-prog { color: var(--lime); }
.jales-sweep {
  margin: 6px 0 2px;
  font-size: 11px;
  color: var(--ink-faint);
}
.jales-sweep.done { color: var(--lime); }

/* The game-over payout lines. Gold for money, one row per thing that landed —
   the sheet is where "one more run" gets decided, so these are loud. Same
   tinted card the racha chip wears on the menu: the promise up there and the
   payout down here are the same object in the same clothes. */
.over-bonos { margin: 10px 0 2px; display: grid; gap: 5px; }
.over-bono {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 11px;
  border-radius: 11px;
  background: rgba(255, 201, 60, 0.10);
  border: 1px solid rgba(255, 201, 60, 0.28);
  font-size: 12px;
}
.over-bono span { color: var(--ink); letter-spacing: 0.04em; }
.over-bono b { color: var(--gold); }
.over-bono.sweep { border-color: rgba(40, 195, 184, 0.36); background: rgba(40, 195, 184, 0.09); }
.over-bono.sweep b { color: var(--teal); }
