/* =========================================================================
 * style.css — layout for canvas, HUD, menu, and death/stats screen
 * ========================================================================= */

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

:root {
  /* SODIUM & VERDIGRIS — a wet street at night rather than a synthwave preset.
     These values are OVERWRITTEN at boot by applyPalette(); they are here so
     that the first paint, before any JS has run, is already the right palette.
     A stylesheet defaulting to the old colours meant a visible magenta flash on
     every load. Keep them in step with PALETTES.sodium in config.js.

     The composition rule, which every palette in that file follows: ONE
     analogous band (petrol -> teal -> verdigris) plus ONE complement (coral),
     and the whole contrast budget is spent on the complement. That is what makes
     it read as a room instead of a colour test card. A second hot colour and it
     stops working.

     `--accent-rgb` / `--accent2-rgb` exist so rules below can compose their own
     alphas — `rgba(var(--accent-rgb), 0.26)`. Fifteen glows and washes used to
     carry a literal magenta that no palette could reach, so swapping the palette
     left the old colour bleeding through the menu. */
  --bg: #03050a;              /* wet petrol, not a dark void — see COLORS.ground */
  --panel: rgba(4, 6, 11, 0.965);
  --panel-border: rgba(var(--paper-rgb), 0.16);
  /* THE INTERFACE PALETTE IS NOT THE WORLD PALETTE.
     These used to be the sodium streetlamp, borrowed straight from the scene.
     On wet asphalt that colour is a lamp; filling menu buttons with it is a
     large flat area of saturated orange, and that is what read as cheap. A case
     file is made of bone paper and cold steel, both of which take dark text at
     13:1 — which the orange could not. The street outside still burns sodium;
     see COLORS.magenta, which is untouched. */
  --accent: #ded7c6;          /* bone — paper, primary controls */
  --accent2: #7fa6bd;         /* cold steel — secondary */
  --accent-rgb: 222,215,198;
  /* PAPER. The menu's own neutral — an aged, desaturated bone that the chrome
     (borders, rules, dividers) is built from instead of borrowing the room's hot
     sodium accent. That borrowing is what made the menu read as loud: every
     hairline in it was lit like a streetlamp. The accent still exists and is
     still used, but now only where something is meant to be looked at. */
  --paper-rgb: 160,152,132;
  --accent2-rgb: 127,166,189;
  --gold: #cbb98a;            /* pale brass — trophies */
  --text: #ded8ca;            /* bone; never pure white — 17:1 on the floor was harsh */
  --muted: #6d7d80;           /* unlit teal */
  --ink: #05090c;             /* text on bright fills */
  --rule: rgba(var(--paper-rgb), 0.13);

  /* Labels and readouts. Kept monospace — it's the one place a club poster and a
     game HUD agree, and it keeps numbers from jittering as they tick. */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  /* THE LOGO / DISPLAY FACE.
     Wants to read as a 70s club poster: fat, round, generously spaced. Rounded
     geometric grotesques are the closest thing to that reliably installed —
     Century Gothic in particular has the near-circular O and single-storey a
     that carry the whole era. Everything after it is a graceful fallback of
     decreasing roundness, never a condensed face, because condensed is what made
     the old menu feel like a lab report. */
  /* ONE VOICE. The two vector display faces that used to live here (a rounded
     geometric for headings, a heavy condensed for the finisher) are gone: the
     bitmap face does both jobs now, so the game has a single typographic voice
     instead of three competing ones. What is left is the monospace stack, which
     is what every remaining piece of running text uses — it is the closest thing
     to a pixel face that is reliably installed, it holds its shape at small
     sizes, and it keeps numbers from jittering as they tick. */
  --display: var(--mono);
  /* Fighting-game face for the finisher: the heaviest, most compressed thing
     reliably installed. Impact/Haettenschweiler is what arcade title cards
     actually look like, and it takes an outline without turning to mud. */
  --fight: var(--mono);
}

html, body {
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  user-select: none;
  -webkit-user-select: none;
}

#game {
  position: fixed;
  inset: 0;
  display: block;
  cursor: crosshair;
  /* The browser owns a touch for ~300ms deciding whether it is a scroll, a
     pinch or a double-tap zoom, and only hands it over once it has ruled those
     out. On a control surface that is a third of a second of unsteered snake
     every time you grab the wheel. `none` says there is nothing to decide.
     Also kills the double-tap-to-zoom that a rapid boost tap looks like. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  /* the grey flash Safari/Chrome paint over a tapped element */
  -webkit-tap-highlight-color: transparent;
}

.hidden { display: none !important; }

/* ---------- HUD ---------- */
.panel {
  position: fixed;
  background: rgba(20, 14, 30, 0.76);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

#leaderboard {
  top: 14px;
  right: 14px;
  width: 210px;
  padding: 10px 12px;
}
.lb-title {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}
#lb-list { list-style: none; }
#lb-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  padding: 3px 0;
}
#lb-list li.me { font-weight: 700; }
#lb-list li.me .lb-name { text-shadow: 0 0 8px currentColor; }
.lb-rank { width: 18px; color: var(--muted); text-align: right; }
.lb-tag {
  flex: none;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.04em;
  border: 1px solid currentColor;
  border-radius: 4px;
  padding: 1px 3px;
  opacity: 0.9;
}

/* Progress toward the next segment — the per-dot payoff. Labelled, because an
   unlabelled bar in the corner of a HUD is just an unanswered question. */
.hud-meter {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px solid var(--rule);
}
.grow-meter {
  height: 6px;
  background: rgba(255,255,255,0.10);
  border-radius: 3px;
  overflow: hidden;
}
.grow-meter i {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  transition: width 0.09s linear;
}

/* ---------- Drip (cosmetics) ---------- */
#randomize-btn {
  background: transparent;
  color: var(--accent2);
  border: 1px dashed rgba(var(--accent2-rgb), 0.45);
  border-radius: 2px;
  padding: 4px 10px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  font-weight: 700;
  cursor: pointer;
  margin-top: 14px;
}
#randomize-btn:hover { background: rgba(var(--accent2-rgb), 0.16); border-style: solid; }

/* the dancer under glass */
#drip-preview {
  width: 100%;
  height: 130px;
  background:
    radial-gradient(circle at 50% 55%, rgba(var(--accent-rgb), 0.10), transparent 62%),
    rgba(0,0,0,0.42);
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  margin-bottom: 7px;
}
#drip-rows { display: flex; flex-direction: column; gap: 0; }
/* rows read as a data table: hairline separators, no floating chips */
.drip-row {
  display: grid;
  grid-template-columns: 62px 22px 1fr 22px;
  align-items: center;
  gap: 4px;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 5px 2px;
}
.drip-label {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.drip-arrow {
  background: none;
  border: none;
  color: var(--muted);
  border-radius: 0;
  font-size: 16px;
  line-height: 1;
  padding: 2px 0;
  cursor: pointer;
  transition: color 0.1s;
}
.drip-arrow:hover { background: none; color: var(--accent); }
.drip-value {
  text-align: center;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lb-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-len { color: var(--muted); font-variant-numeric: tabular-nums; }

#stats-hud {
  top: 14px;
  left: 14px;
  padding: 10px 14px;
  min-width: 150px;
}
.hud-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 18px;
  font-family: var(--mono);
  font-size: 13px;
  padding: 2px 0;
}
.hud-label {
  color: var(--muted);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.hud-row span:last-child {
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

#powerup-corner {
  position: fixed;
  left: 14px;
  bottom: 14px;
  width: 250px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#powerup-bar {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#powerup-toasts {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Explainer shown briefly when a power-up is picked up, so players learn what
   each one actually does without needing a manual. */
.pu-toast {
  background: var(--panel);
  border: 1px solid var(--c);
  border-left: 3px solid var(--c);
  border-radius: 10px;
  padding: 9px 12px;
  box-shadow: 0 0 18px -6px var(--c);
  animation: puIn 0.28s ease-out;
}
.pu-toast.out { animation: puOut 0.45s ease-in forwards; }
.pu-toast-head {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 700;
  color: var(--c);
}
.pu-toast-head .pu-glyph {
  width: 20px; height: 20px;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--c);
  color: var(--ink);
  border-radius: 6px;
  font-size: 12px;
  font-weight: 800;
}
.pu-toast-desc {
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--muted);
}
@keyframes puIn {
  from { opacity: 0; transform: translateX(-14px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes puOut {
  from { opacity: 1; transform: translateX(0); max-height: 90px; }
  to   { opacity: 0; transform: translateX(-14px); max-height: 0; padding-top: 0; padding-bottom: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .pu-toast, .pu-toast.out { animation: none; }
}
.pu-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--c);
  border-radius: 10px;
  padding: 7px 10px;
  box-shadow: 0 0 14px -4px var(--c);
}
.pu-icon {
  width: 26px; height: 26px;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--c);
  color: var(--ink);
  border-radius: 7px;
  font-weight: 800;
  font-size: 15px;
}
.pu-meta { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.pu-name { font-size: 12px; color: var(--text); }
.pu-bar { height: 5px; background: rgba(255,255,255,0.12); border-radius: 3px; overflow: hidden; }
.pu-bar i { display: block; height: 100%; background: var(--c); transition: width 0.1s linear; }

#minimap {
  position: fixed;
  right: 14px;
  bottom: 14px;
  width: 190px;
  height: 190px;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(255,150,130,0.18), 0 10px 30px -12px rgba(0,0,0,0.9);
}

#boost-hint {
  position: fixed;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: rgba(20,14,30,0.74);
  padding: 6px 14px;
  border-radius: 2px;
  border: 1px solid var(--rule);
}
#boost-hint b { color: var(--accent); font-weight: 700; }

/* ---------- Overlays (menu + death) ---------- */
/* Flex + `margin:auto` on the child, NOT grid+place-items: a grid item taller
   than the viewport overflows equally in both directions and the top gets cut
   off unreachably. This centres when there's room and scrolls when there isn't. */
.overlay {
  position: fixed;
  inset: 0;
  display: flex;
  justify-content: center;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* One lamp, high and off to the side, and near-black everywhere else. The
     old version was two coloured pools and a violet centre — that is a lit
     room, and it is most of why the menu read as soft. */
  background:
    radial-gradient(ellipse at 26% 8%, rgba(var(--accent-rgb), 0.09), transparent 52%),
    radial-gradient(ellipse at 88% 96%, rgba(var(--accent2-rgb), 0.05), transparent 46%),
    radial-gradient(circle at 50% 45%, rgba(4,6,11,0.95), rgba(1,2,4,0.995));
  z-index: 10;
  padding: 20px;
}

/* ---------- Menu: a dance card ----------
   Square corners, hairline rules, graph-paper ground and a taped-down feel —
   deliberately NOT the rounded-blurred-glass card that every generated UI
   defaults to. */
.menu-card, .editor-card, .trophy-card {
  /* auto margins centre it inside the scrolling overlay without clipping */
  margin: auto;
  flex: none;
}
.menu-card, .editor-card {
  position: relative;
  border-radius: 2px;
  background:
    linear-gradient(var(--panel), var(--panel)),
    repeating-linear-gradient(0deg, transparent 0 27px, var(--rule) 27px 28px);
  border: 1px solid var(--panel-border);
  box-shadow: 0 24px 70px -24px #000, 0 0 0 4px rgba(0,0,0,0.45);
}
/* corner registration marks */
.menu-card::before, .menu-card::after,
.menu-card::before { top: 6px; left: 6px; border-right: 0; border-bottom: 0; }
.menu-card::after { bottom: 6px; right: 6px; border-left: 0; border-top: 0; }

.menu-card {
  width: min(760px, 95vw);
  padding: 14px 22px 12px;
  text-align: left;
  /* THE CARD CAN NEVER OVERFLOW THE SCREEN.
     Measured at 837px tall in a 720px viewport — 137px cut off, with the START
     button's own bottom edge 6px below the fold. The overlay scrolled, so it was
     technically reachable, but a menu whose primary button is off-screen on a
     stock laptop is broken regardless of what a scrollbar can do about it.
     Capping the card and scrolling its CONTENT means the only thing that can
     ever be out of view is optional detail, never the button. dvh, not vh, so
     mobile browser chrome is accounted for. */
  max-height: calc(100dvh - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* START stays put while the content above it scrolls. It is the one control the
   menu exists to present; it does not get to be the thing that scrolls away. */
#play-btn {
  position: sticky;
  bottom: 0;
  z-index: 2;
}

/* Identity on the left, drip on the right. Splitting the dossier in two is what
   gets the whole thing inside a laptop viewport — stacked, it ran ~1000px tall
   and the play button fell off the bottom of the screen. */
.menu-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px 30px;
  align-items: start;
}
.menu-col { min-width: 0; }
/* the column's first section heading shouldn't re-add the stacked top margin */
.menu-col > .field-label:first-child,
.menu-col > .skin-head:first-child { margin-top: 6px; }

/* Trophy button and lifetime stats share a row for the same reason. */
.menu-foot {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px 30px;
  align-items: center;
  margin-top: 10px;
}
.menu-foot #trophy-btn,
.menu-foot #profile-strip { margin: 0; }

@media (max-width: 620px) {
  .menu-cols, .menu-foot { grid-template-columns: 1fr; }
}
/* NOTE: the short-viewport compaction lives at the very BOTTOM of this file.
   Media queries carry no extra specificity, so placing it here let the later
   `.logo` / `.field-label` rules win and it silently did nothing. */

.dossier-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  color: var(--muted);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 8px;
}
.doc-id { color: var(--accent); }

/* segmented worm divider */
.worm-rule {
  height: 8px;
  margin: 12px 0 2px;
  background-image: radial-gradient(circle, var(--accent) 0 38%, transparent 42%);
  background-size: 13px 8px;
  background-repeat: repeat-x;
  opacity: 0.4;
  -webkit-mask-image: linear-gradient(90deg, #000 60%, transparent);
  mask-image: linear-gradient(90deg, #000 60%, transparent);
}
/* The marquee. Wide letter-spacing is what sells the club-poster read — the old
   -0.02em tracking made it huddle like a wordmark on a spec sheet. The gradient
   runs magenta into cyan through the two stage lights, with a soft bloom under
   it so it looks lit rather than printed. */
.logo {
  font-family: var(--display);
  font-size: 62px;
  font-weight: 700;
  line-height: 0.9;
  letter-spacing: 0.16em;
  text-indent: 0.16em;          /* balance the trailing space tracking adds */
  text-transform: uppercase;
  margin-top: 12px;
  background: linear-gradient(96deg, var(--accent) 8%, #ffd6f0 34%,
                              var(--gold) 56%, var(--accent2) 92%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 26px rgba(var(--accent-rgb), 0.45));
}
/* the second half of the word rides the same gradient — no override needed */
.logo span { color: transparent; }
.tagline {
  font-family: var(--mono);
  color: var(--muted);
  margin: 10px 0 2px;
  font-size: 11px;
  letter-spacing: 0.08em;
  line-height: 1.5;
  max-width: 40ch;
}

/* Section headings, shared with the death card's `.death-block-title`.
   Same size, same tracking, same hairline rule underneath — so moving from the
   menu to the death screen feels like one product rather than two. The little
   "▸" marker is gone: it implied a sequence that doesn't exist, and it fought
   the rule for the eye's attention. */
.field-label {
  display: block;
  text-align: left;
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 14px 0 8px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--rule);
}
/* An underlined form field, not a rounded pill input */
#nickname {
  width: 100%;
  background: rgba(0,0,0,0.30);
  border: 0;
  border-bottom: 1px solid var(--panel-border);
  border-radius: 0;
  padding: 9px 4px;
  color: var(--accent);
  font-family: var(--mono);
  font-size: 15px;
  letter-spacing: 0.06em;
  outline: none;
}
#nickname::placeholder { color: rgba(141,156,132,0.5); }
#nickname:focus { border-bottom-color: var(--accent); background: rgba(var(--accent-rgb), 0.09); }

/* A heading with an action on the right. The rule belongs to the ROW, not to
   the label, or it stops halfway across and looks broken. */
.skin-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 14px 0 8px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--rule);
}
.skin-head .field-label {
  margin: 0;
  padding: 0;
  border-bottom: 0;
}
.skin-head button { margin-top: 0 !important; }
#create-skin-btn {
  background: transparent;
  color: var(--accent);
  border: 1px dashed var(--panel-border);
  border-radius: 2px;
  padding: 4px 10px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  font-weight: 700;
  cursor: pointer;
  margin-top: 14px;
  transition: background 0.12s, border-color 0.12s;
}
#create-skin-btn:hover { background: rgba(var(--accent-rgb), 0.15); border-color: var(--accent); }

#skin-picker {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  max-height: clamp(84px, 17vh, 150px);
  overflow-y: auto;
  padding: 2px;
}
.skin-swatch {
  position: relative;
  background: rgba(0,0,0,0.3);
  border: 2px solid transparent;
  border-radius: 10px;
  padding: 8px 4px 6px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  transition: border-color 0.12s, transform 0.12s;
}
.skin-swatch:hover { transform: translateY(-2px); }
.skin-swatch.selected { border-color: var(--accent); box-shadow: 0 0 16px -4px var(--accent); }
.skin-swatch canvas { width: 96px; height: 40px; }
.skin-name { font-size: 11px; color: var(--muted); text-align: center; line-height: 1.25; }

.skin-swatch.locked { cursor: not-allowed; }
.skin-swatch.locked canvas { filter: grayscale(0.85) brightness(0.5); }
.skin-swatch.locked .skin-name { color: #7f8ba0; }
.skin-prog {
  width: 84%;
  height: 4px;
  background: rgba(255,255,255,0.1);
  border-radius: 2px;
  overflow: hidden;
  margin-top: 2px;
}
.skin-prog i { display: block; height: 100%; background: var(--accent2); }
.skin-del {
  position: absolute;
  top: 3px; right: 5px;
  width: 18px; height: 18px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(0,0,0,0.5);
  color: #ff8a9a;
  font-size: 15px;
  line-height: 1;
}
.skin-del:hover { background: rgba(255,60,80,0.35); color: #fff; }
.skin-swatch.shake, .cast-swatch.shake { animation: shake 0.3s; }
@keyframes shake {
  0%,100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

/* ---------- The cast ----------
   Two columns rather than the pigmentation grid's three: cast names are long
   ("Literally Made Of Money") and the little standing figures need the width to
   read as figures rather than as coloured blobs. */
#cast-picker {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  max-height: clamp(84px, 18vh, 150px);
  overflow-y: auto;
  padding: 2px;
}
.cast-swatch {
  position: relative;
  background: rgba(0,0,0,0.32);
  border: 2px solid transparent;
  border-radius: 2px;
  padding: 5px 3px 4px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  transition: border-color 0.12s, transform 0.12s;
}
.cast-swatch:hover { transform: translateY(-2px); }
.cast-swatch.selected { border-color: var(--accent); box-shadow: 0 0 16px -4px var(--accent); }
/* image-rendering matters here: the swatch canvases are tiny pixel art and the
   browser's default smoothing turns them to mush at any scale */
.cast-swatch canvas {
  width: 96px; height: 40px;
  image-rendering: pixelated;
}
.cast-swatch .skin-name { font-size: 10px; }
.cast-swatch.locked { cursor: not-allowed; }
.cast-swatch.locked canvas { filter: grayscale(0.9) brightness(0.42); }
.cast-swatch.locked .skin-name { color: #8a7a63; }
.cast-hint {
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0.8;
}

/* Sections a cast member has taken over. Dimmed and inert rather than hidden —
   removing them from the layout read as "the options vanished", which sent
   people hunting for a bug instead of understanding the trade. */
.lockable { transition: opacity 0.18s filter 0.18s; }
.lockable.overridden {
  opacity: 0.34;
  filter: grayscale(0.7);
  pointer-events: none;
}
.lockable.overridden .field-label::after {
  content: " — using a cast member";
  letter-spacing: 0;
  text-transform: none;
  opacity: 0.9;
}

/* lifetime history, printed like a readout footer */
#profile-strip {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
#profile-strip b {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  display: block;
  font-size: 14px;
  letter-spacing: 0;
  margin-top: 2px;
}

/* ---------- Skin editor ---------- */
.editor-card {
  width: min(400px, 92vw);
  background: var(--panel);
  border: 1px solid var(--panel-border);
  padding: 26px 26px 22px;
  backdrop-filter: blur(12px);
}
.editor-card h2 { font-size: 22px; margin-bottom: 14px; text-align: center; }
#editor-preview {
  width: 100%;
  height: 70px;
  background: rgba(0,0,0,0.35);
  border-radius: 10px;
  border: 1px solid var(--panel-border);
}
.seg-btns { display: flex; flex-wrap: wrap; gap: 6px; }
.seg-btns button {
  flex: 1 1 auto;
  background: rgba(0,0,0,0.3);
  border: 1px solid var(--panel-border);
  color: var(--muted);
  border-radius: 8px;
  padding: 8px 6px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.12s;
}
.seg-btns button:hover { color: var(--text); }
.seg-btns button.active {
  background: var(--accent);
  color: var(--ink);
  border-color: var(--accent);
}
#editor-colors { display: flex; gap: 12px; margin-top: 12px; }
.color-field {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.color-field input[type="color"] {
  width: 100%;
  height: 34px;
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  background: none;
  cursor: pointer;
  padding: 2px;
}
.editor-buttons { display: flex; gap: 12px; margin-top: 6px; }
#editor-save {
  margin-top: 18px;
  flex: 1;
  padding: 13px;
  border: none;
  border-radius: 12px;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.05em;
  cursor: pointer;
  color: var(--ink);
  background: linear-gradient(90deg, var(--accent), var(--accent2));
}
#editor-cancel {
  margin-top: 18px;
  max-width: 130px;
  padding: 13px;
  border: none;
  border-radius: 12px;
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
  background: rgba(255,255,255,0.08);
  color: var(--text);
}
#editor-cancel:hover { background: rgba(255,255,255,0.14); }

/* ---------- Trophy cabinet ---------- */
#trophy-btn {
  width: 100%;
  margin-top: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: none;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  padding: 9px 12px;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  transition: border-color 0.12s, color 0.12s;
}
#trophy-btn:hover { border-color: var(--accent); color: var(--text); }
#trophy-btn b { color: var(--accent); font-size: 12px; letter-spacing: 0; }

.trophy-card {
  width: min(660px, 94vw);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  padding: 22px 24px 20px;
}
#trophy-bar {
  height: 4px;
  background: rgba(255,255,255,0.10);
  margin-top: 12px;
  overflow: hidden;
}
#trophy-bar i {
  display: block; height: 100%; width: 0%;
  background: linear-gradient(90deg, var(--accent), var(--gold));
}
.trophy-note {
  font-family: var(--mono);
  font-size: 10px;
  line-height: 1.6;
  color: var(--muted);
  margin: 10px 0 12px;
}
.trophy-note b { color: #85bb65; }

#trophy-list {
  overflow-y: auto;
  flex: 1;
  border-top: 1px solid var(--rule);
}
.trophy {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 7px 2px;
  border-bottom: 1px solid var(--rule);
  opacity: 0.42;                    /* locked entries recede */
}
.trophy.got { opacity: 1; }
.trophy-mark { color: var(--tc); font-size: 13px; text-align: center; }
.trophy-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.trophy-text b {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.06em;
  color: var(--text);
}
.trophy.got .trophy-text b { color: var(--tc); }
.trophy-text i {
  font-style: normal;
  font-family: var(--mono);
  font-size: 9.5px;
  color: var(--muted);
}
.trophy-tier {
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--tc);
  opacity: 0.75;
}
#trophy-close {
  margin-top: 14px;
  padding: 11px;
  border: 1px solid var(--panel-border);
  border-radius: 2px;
  background: none;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.24em;
  cursor: pointer;
}
#trophy-close:hover { border-color: var(--accent); color: var(--accent); }

.trophy-badge {
  border-color: var(--tc) !important;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--tc) 18%, transparent), transparent) !important;
}
.trophy-badge b { color: var(--tc) !important; }

/* ---------- Unlock badges on death screen ---------- */
#death-unlocks:empty { display: none; }
.unlock-badge {
  background: linear-gradient(90deg, rgba(var(--accent-rgb), 0.22), rgba(var(--accent2-rgb), 0.18));
  border: 1px solid rgba(var(--accent-rgb), 0.55);
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 14px;
  color: var(--text);
}
.unlock-badge b { color: var(--accent); }

/* Hard-edged lab switch: flat chartreuse slab, mono type, no gradient sheen */
/* The big buttons use the display face too — a club flyer sets its call to
   action in the poster type, not in the small print. */
#play-btn, #replay-btn, #menu-btn {
  margin-top: 20px;
  width: 100%;
  padding: 14px;
  border: 1px solid var(--accent);
  border-radius: 2px;
  font-family: var(--display);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  cursor: pointer;
  color: var(--ink);
  background: linear-gradient(96deg, var(--accent), #ff8fd4 55%, var(--gold));
  transition: background 0.12s, color 0.12s, box-shadow 0.12s;
}
#play-btn:hover, #replay-btn:hover {
  background: transparent;
  color: var(--accent);
  box-shadow: inset 0 0 26px -8px var(--accent), 0 0 22px -10px var(--accent);
}
#play-btn:active, #replay-btn:active { transform: translateY(1px); }
#play-btn:focus-visible, #replay-btn:focus-visible, #menu-btn:focus-visible,
#create-skin-btn:focus-visible, .skin-swatch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.controls-help {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 18px;
  font-size: 12px;
  color: var(--muted);
}
.controls-help b { color: var(--text); }

/* ---------- THE FINISHER ----------
   The ONLY death callout. There used to be a second verdict slam between this
   and the stats card; three screens in a row made dying feel like credits.
   Now this holds over the frozen scene, then dissolves as the stats card fades
   up carrying the same word.

   Typography is doing the heavy lifting for the fighting-game feel: heavy
   condensed caps, a thick dark outline, a hot inner glow and a hard drop shadow,
   crashing in oversize and settling with a stepped judder. */
#exec-slam {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  z-index: 11;
  pointer-events: none;
  /* COMPOSITOR-ONLY. This transition runs while the game is still rendering a
     full scene underneath, so it must not touch a property that forces a
     repaint.  did exactly that for 1.6s and was a large part of
     why the hand-off to the stats card felt choppy. Opacity and transform are
     handled on the compositor and cost effectively nothing. */
  transition: opacity 1.5s ease-in, transform 1.5s ease-in;
}
/* the slow hand-off to the stats card */
#exec-slam.dissolve {
  opacity: 0;
  transform: scale(1.14);
}
/* DAMNING, not celebratory.
 *
 * The previous treatment was gold-to-ember on an italic slant — that's a trophy
 * screen, a "nice job" colour. A verdict should feel handed down: upright rather
 * than leaning (a slant reads as speed and energy; vertical reads as final),
 * tracked TIGHT so the word sits as one heavy block, a thicker near-black stroke
 * so it looks stamped into the frame, and bone-white bleeding into arterial red
 * instead of anything warm and encouraging. The glow is red now too — an orange
 * halo was quietly making it cheerful. */
.exec-word {
  font-family: var(--fight);
  font-size: clamp(68px, 19vw, 264px);
  font-weight: 900;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  line-height: 0.8;
  /* NOTE: the vertical stretch is baked into the keyframes below, not set
     here — execIn and execShudder both animate , so a static one
     would be overridden the moment either ran. */
  background: linear-gradient(180deg,
    #fff4f2 4%, #f0cfc9 24%, #c4232c 58%, #7d0d16 84%, #48060c 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-stroke: 5px #100206;
  paint-order: stroke fill;
  filter:
    drop-shadow(0 0 54px rgba(196, 35, 44, 0.85))
    drop-shadow(0 0 14px rgba(0, 0, 0, 0.95))
    drop-shadow(0 8px 0 rgba(0, 0, 0, 0.9));
  animation: execIn 0.42s cubic-bezier(.15,1.8,.35,1) both,
             execShudder 0.9s steps(3, end) 0.42s 3 both;
}
.exec-by {
  font-family: var(--mono);
  font-size: clamp(11px, 1.7vw, 16px);
  font-weight: 700;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--accent);
  text-shadow: 0 0 14px rgba(0,0,0,0.9), 0 2px 0 rgba(0,0,0,0.8);
  animation: execBy 0.5s ease-out 0.30s both;
}
@keyframes execIn {
  0%   { opacity: 0; transform: scale(4.2, 4.54) rotate(-9deg); }
  60%  { opacity: 1; transform: scale(0.88, 0.95) rotate(2deg); }
  100% { opacity: 1; transform: scale(1, 1.08) rotate(0); }
}
/* stepped, not smooth — a hard mechanical judder, like the frame is straining */
@keyframes execShudder {
  0%, 100% { transform: translate(0, 0) scale(1, 1.08); }
  33%      { transform: translate(-3px, 2px) scale(1, 1.08); }
  66%      { transform: translate(3px, -2px) scale(1, 1.08); }
}
@keyframes execBy {
  from { opacity: 0; letter-spacing: 1.4em; }
  to   { opacity: 1; letter-spacing: 0.42em; }
}
@media (prefers-reduced-motion: reduce) {
  .exec-word, .exec-by { animation: none; }
  #exec-slam { transition: opacity 0.4s linear; }
}

/* The finisher word repeated as the stats card's header, so the card reads as
   the same screen settling rather than a new one arriving. */
/* Same verdict, same treatment, quarter the size — so the card reads as the
   finisher settling rather than a different screen. Upright, tight, bone-into-
   blood, heavy near-black stroke. */

/* ---------- Death / stats ---------- */
/* THREE BANDS, spaced by the layout rather than by ad-hoc margins.
   The card used to be a single run of elements — verdict, headline, sub,
   unlocks, a nine-card grid — with nothing marking where one thing ended and the
   next began, so it read as a wall of stuff. Now it's flex + gap with a labelled
   section per band, and the eye has somewhere to rest. */
.death-head { display: flex; flex-direction: column; gap: 4px; }
.death-block { display: flex; flex-direction: column; gap: 10px; }
/* Section labels: small, quiet, ruled. They exist to segment, not to shout. */
.death-block-title {
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 7px;
  border-bottom: 1px solid var(--rule);
}
/* The costume's last word. Its own line and its own voice — italic, brighter
   than the verdict sub, because it's the joke rather than the summary. */
.death-killer {
  font-family: var(--display);
  font-style: italic;
  font-size: 14px;
  line-height: 1.4;
  color: var(--accent);
  margin-top: 6px;
  text-wrap: balance;
}
/* keep the actions reachable at the bottom of a long scroll */
.death-buttons {
  position: sticky;
  bottom: -24px;
  background: linear-gradient(transparent, var(--panel) 30%);
  padding-top: 14px;
  padding-bottom: 4px;
}

/* The overlay drifts in slowly over the still-running death scene rather than
   cutting, so the moment has time to land. */
/* The blur is STATIC and the opacity animates.

   Animating  from 0 to 3px meant the browser re-blurred the
   entire live canvas behind this overlay on every frame for 2.6 seconds — the
   single most expensive thing that could possibly be scheduled at that moment,
   and the main reason the death transition stuttered. Holding the blur at a
   constant radius lets it be computed once and composited, while the fade is
   pure opacity. Same look, none of the cost. */

/* The menu SLITHERS in: it swims from off-screen left along an S-curve,
   undulating and banking into each turn like a snake, then settles. */
#menu.enter { animation: menuVeil 1.5s ease-out both; }
#menu.enter .menu-card { animation: menuSlither 2.1s cubic-bezier(.33,.9,.35,1) both 0.25s; }
@keyframes menuVeil {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes menuSlither {
  0%   { opacity: 0; transform: translate(-135%, 42px) rotate(-11deg) skewX(9deg) scale(0.9); }
  22%  { opacity: 1; transform: translate(-62%, -34px) rotate(9deg) skewX(-7deg) scale(0.93); }
  44%  { transform: translate(-24%, 30px) rotate(-7deg) skewX(6deg) scale(0.96); }
  64%  { transform: translate(2%, -18px) rotate(5deg) skewX(-4deg) scale(0.99); }
  80%  { transform: translate(-4%, 10px) rotate(-3deg) skewX(2deg) scale(1.01); }
  92%  { transform: translate(1%, -4px) rotate(1.2deg) skewX(-0.8deg) scale(1); }
  100% { opacity: 1; transform: translate(0, 0) rotate(0) skewX(0) scale(1); }
}
/* the card's contents ripple in behind the slither, head-to-tail */
#menu.enter .menu-card > * { animation: rippleIn 0.6s ease-out both; }
#menu.enter .menu-card > *:nth-child(1) { animation-delay: 1.15s; }
#menu.enter .menu-card > *:nth-child(2) { animation-delay: 1.22s; }
#menu.enter .menu-card > *:nth-child(3) { animation-delay: 1.29s; }
#menu.enter .menu-card > *:nth-child(4) { animation-delay: 1.36s; }
#menu.enter .menu-card > *:nth-child(5) { animation-delay: 1.43s; }
#menu.enter .menu-card > *:nth-child(6) { animation-delay: 1.50s; }
#menu.enter .menu-card > *:nth-child(n+7) { animation-delay: 1.57s; }
@keyframes rippleIn {
  from { opacity: 0; transform: translateX(-16px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* death overlay fades out instead of vanishing when you head back to the menu */
@keyframes fadeOut { to { opacity: 0; } }

/* HUD dissolves as the camera lingers on where you died */
#hud { transition: opacity 1.4s ease; }
#hud.fading { opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  #menu.enter, #menu.enter .menu-card { animation: none; }
}

/* assay readouts: left-aligned, hairline-boxed, monospaced figures */
.stat-card {
  background: rgba(0,0,0,0.25);
  border: 0;
  border-left: 2px solid var(--rule);
  border-radius: 0;
  padding: 8px 10px;
  text-align: left;
}
.stat-value {
  font-family: var(--mono);
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  line-height: 1.1;
}
.stat-label {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 3px;
}
.stat-sub {
  font-family: var(--mono);
  font-size: 9.5px;
  color: var(--accent2);
  margin-top: 3px;
}

.death-buttons { display: flex; gap: 12px; }
.death-buttons button { margin-top: 0; }
#menu-btn.secondary {
  background: rgba(255,255,255,0.08);
  color: var(--text);
  max-width: 160px;
}
#menu-btn.secondary:hover { background: rgba(255,255,255,0.14); }

@media (max-width: 560px) {
  #death-stats { grid-template-columns: repeat(2, 1fr); }
  #leaderboard { width: 160px; }
  .logo { font-size: 32px; }
}


/* =========================================================================
 * MENU FIT — must stay LAST in the file.
 *
 * The whole dossier has to be reachable without scrolling, on a 1080p desktop
 * and on a short laptop alike. Two mechanisms, in this order:
 *
 *  1. The two flexible blocks (skin grid, drip preview) are sized in `vh`, so
 *     they shrink continuously with the window instead of stepping at
 *     hand-picked breakpoints. The skin grid already scrolls internally, so
 *     giving up height there costs nothing.
 *  2. One breakpoint trims the fixed chrome — logo, tagline, paddings — which
 *     can't sensibly scale.
 *
 * These rules live at the bottom because media queries add NO specificity: put
 * this block above the base `.logo` / `.field-label` rules and it silently does
 * nothing, which is exactly what happened the first time.
 * ========================================================================= */
#skin-picker { max-height: clamp(96px, 26vh, 210px); }
#drip-preview { height: clamp(74px, 17vh, 130px); }

@media (max-height: 780px) {
  .menu-card { padding: 12px 20px 12px; }
  .logo { font-size: clamp(30px, 6.4vh, 50px); margin-top: 4px; }
  .tagline { display: none; }
  .worm-rule { margin: 7px 0 0; }
  .field-label { margin: 9px 0 5px; }
  #play-btn { margin-top: 12px; }
  .menu-foot { margin-top: 8px; }
  .controls-help { margin-top: 9px; }
}
@media (max-height: 600px) {
  .menu-card { padding: 8px 18px 10px; }
  .dossier-head, .worm-rule { display: none; }
  .field-label { margin: 6px 0 4px; }
  #play-btn { margin-top: 8px; padding: 10px; }
}

/* ---------- Kill streaks ----------
   Positioned above centre deliberately: dead centre would sit on your own head
   at exactly the moment you most need to see it. Uses the finisher face so a
   streak feels like it belongs to the same fighting-game language as the death
   card, but at a fraction of the size — it must read in peripheral vision
   without stealing focus mid-fight. */
#streak {
  position: fixed;
  left: 50%;
  top: 17%;
  transform: translateX(-50%);
  z-index: 8;
  pointer-events: none;
  text-align: center;
}
.streak-word {
  font-family: var(--fight);
  font-size: clamp(30px, 5.5vw, 68px);
  font-weight: 900;
  font-style: italic;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  line-height: 1;
  background: linear-gradient(180deg, #fff4ea 8%, #e8a0a2 40%, #d4565a 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-stroke: 2px var(--ink);
  paint-order: stroke fill;
  filter: drop-shadow(0 0 26px rgba(var(--accent-rgb), 0.8))
          drop-shadow(0 4px 0 rgba(0, 0, 0, 0.75));
}
.streak-n {
  font-family: var(--mono);
  font-size: clamp(9px, 1.2vw, 13px);
  font-weight: 700;
  letter-spacing: 0.36em;
  text-transform: uppercase;
  color: var(--accent2);
  text-shadow: 0 0 12px rgba(var(--accent2-rgb), 0.9);
  margin-top: 3px;
}
#streak.show .streak-word { animation: streakIn 0.34s cubic-bezier(.2,1.7,.4,1) both; }
#streak.show .streak-n    { animation: streakIn 0.34s ease-out 0.06s both; }
#streak.out { animation: streakOut 0.55s ease-in both; }
@keyframes streakIn {
  0%   { opacity: 0; transform: scale(2.1) rotate(-5deg); }
  62%  { opacity: 1; transform: scale(0.93) rotate(1deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}
@keyframes streakOut {
  to { opacity: 0; transform: translateY(-16px) scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  #streak.show .streak-word, #streak.show .streak-n, #streak.out { animation: none; }
}

/* =========================================================================
 * FINISHER BLOOD
 *
 * Hard-edged and stepped, because it has to live in the same world as the
 * sprites. Nothing here is soft: the splatter is built from box-shadowed
 * squares, and every drip animates on `steps()` so it advances in visible
 * jumps rather than sliding. A smooth CSS tween would read as modern motion
 * graphics stapled onto pixel art.
 *
 * All of it is composited, not painted per frame — transforms and opacity only,
 * so it costs nothing during the freeze.
 * ========================================================================= */
.exec-stage {
  position: relative;
  display: inline-block;
  /* sits above the blood sheet */
  z-index: 2;
}

}

@media (prefers-reduced-motion: reduce) {
  .exec-splat { animation: none; opacity: 0.92; transform: scale(1); }
  .exec-drips i { animation: none; transform: scaleY(1); }
}

/* The killer's line. Sits under the "by X" credit — it's the joke, so it gets
   room to breathe and its own timing rather than being crammed into the word. */
.exec-taunt {
  max-width: min(680px, 86vw);
  margin-top: 14px;
  text-align: center;
  font-family: var(--display);
  font-size: clamp(14px, 2.1vw, 22px);
  font-style: italic;
  font-weight: 700;
  line-height: 1.35;
  color: #ffe3ef;
  text-shadow: 0 0 18px rgba(163,22,42,0.9), 0 2px 0 rgba(0,0,0,0.8);
  text-wrap: balance;
  animation: execBy 0.6s ease-out 0.62s both;
}

/* =========================================================================
 * 8-BIT CHROME — must stay LAST so it wins over earlier rules.
 *
 * The world has been genuine pixel art for a while; the interface hadn't caught
 * up. This is the pass that makes the two agree. The rule is simple and applied
 * without exception: NOTHING in the UI is allowed to be soft.
 *
 *   - no rounded corners. A radius is a curve, and there are no curves at this
 *     resolution.
 *   - no blur, anywhere. Every shadow is a hard offset block.
 *   - no smooth gradients on chrome. Fills are flat.
 *   - borders are 2px, solid, in palette colours.
 *   - text smoothing off, so glyph edges stay hard.
 *   - hovers and transitions are stepped, never eased — a 200ms ease is the
 *     most modern-feeling thing in any interface.
 *
 * Cost: this pass REMOVES work. Killing the backdrop blurs alone takes several
 * full-screen filter passes off every frame the menu is up.
 * ========================================================================= */

/* Kill every radius and every blur in one place, then re-state the few things
   that genuinely need a value. */
.panel, .menu-card, .editor-card, .trophy-card,
button, input, .skin-swatch, .cast-swatch, .stat-card, .pu-toast, .pu-chip,
.unlock-badge, .grow-meter, .grow-meter i, .skin-prog, #skin-picker,
#cast-picker, #drip-preview, #trophy-bar, .lb-tag, .drip-row, #nickname {
  border-radius: 0 !important;
}

/* Type: hard edges, no sub-pixel softening. */
body, button, input, .menu-card, .trophy-card, .editor-card {
  -webkit-font-smoothing: none;
  -moz-osx-font-smoothing: grayscale;
  font-smooth: never;
}

/* Panels become solid blocks with a hard 2px keyline and a hard offset shadow —
   the classic 8-bit dialog box. */
.panel {
  background: var(--panel);
  border: 2px solid var(--accent);
  box-shadow: 4px 4px 0 rgba(0,0,0,0.75);
}
.menu-card, .editor-card, .trophy-card {
  /* Palette-driven, and it has to be: this block sits last in the file, so a
     literal here silently beats every rule above it. It did exactly that with
     the original violet for three darkening passes in a row. */
  background-color: var(--panel);
  border: 2px solid rgba(var(--paper-rgb), 0.30);
  box-shadow: 6px 6px 0 rgba(0,0,0,0.85), 0 0 0 2px rgba(0,0,0,0.7);
}

/* Buttons: flat fill, hard shadow, and a real "press" that moves the block onto
   its own shadow instead of fading a colour. */
button {
  border-radius: 0;
  transition: none;
  image-rendering: pixelated;
}
#play-btn, #replay-btn, #menu-btn, #trophy-close, #editor-save, #editor-cancel {
  background: var(--accent);
  color: var(--ink);
  border: 2px solid var(--ink);
  box-shadow: 4px 4px 0 rgba(0,0,0,0.8);
  text-shadow: none;
  filter: none;
}
#play-btn:hover, #replay-btn:hover, #trophy-close:hover, #editor-save:hover {
  background: var(--gold);
  color: var(--ink);
  box-shadow: 4px 4px 0 rgba(0,0,0,0.8);
}
#play-btn:active, #replay-btn:active, #menu-btn:active,
#trophy-close:active, #editor-save:active, #editor-cancel:active {
  transform: translate(4px, 4px);
  box-shadow: 0 0 0 rgba(0,0,0,0);
}
#menu-btn, #editor-cancel {
  /* Secondary: outline only. The primary control is the one that gets a filled
     block; making both solid was half of why the menu read as a row of orange
     boxes rather than as a hierarchy. */
  background: transparent;
  color: var(--accent);
  border: 2px solid rgba(var(--accent-rgb), 0.45);
}
#menu-btn:hover, #editor-cancel:hover {
  background: rgba(var(--accent-rgb), 0.14);
  border-color: var(--accent);
}

/* Swatches: 2px keyline, hard selected state, no lift-on-hover (a translate of
   -2px is a modern affordance; an inverted block is an 8-bit one). */
.skin-swatch, .cast-swatch {
  border: 2px solid #362a48;
  background: #1c1528;
  transition: none;
}
.skin-swatch:hover, .cast-swatch:hover { transform: none; border-color: var(--muted); }
.skin-swatch.selected, .cast-swatch.selected {
  border-color: var(--gold);
  background: #2c2240;
  box-shadow: none;
}
.skin-swatch canvas, .cast-swatch canvas { image-rendering: pixelated; }

/* Meters and bars: blocky, flat, no rounded caps or gradients. */
.grow-meter, #trophy-bar, .skin-prog {
  background: #2c2240;
  border: 2px solid var(--ink);
  height: 10px;
}
.grow-meter i, #trophy-fill, .skin-prog i {
  background: var(--accent);
  border-radius: 0;
  transition: none;
}

/* Inputs read as a slot cut into the panel. */
#nickname, #editor-name {
  background: var(--ink);
  border: 2px solid var(--accent);
  color: var(--gold);
}
#nickname:focus, #editor-name:focus { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Overlays: flat wash, no radial gradients bleeding through. */
.overlay {
  background: var(--ink);
}

/* The logo canvas is bitmap type — never let the browser resample it smoothly. */
.logo canvas, .exec-word canvas, .streak-word canvas {
  image-rendering: pixelated;
}
.logo { display: block; width: 100%; filter: none; background: none; }
.exec-word, .streak-word {
  background: none;
  -webkit-text-stroke: 0;
  filter: none;
  transform: none;
}
.exec-word { display: flex; justify-content: center; }
.streak-word { display: flex; justify-content: center; }

/* Stepped, not eased. */
.drip-arrow, #randomize-btn, #create-skin-btn, .skin-del {
  transition: none;
  border-radius: 0;
}

/* =========================================================================
 * ATTRACT MODE — the menu sits over a live match.
 *
 * The overlay was very nearly opaque (var(--ink)), which was correct when there
 * was nothing behind it. Now there is. It drops to a wash with a vignette, so
 * the demo reads at the edges of the frame while the card itself stays on a
 * dark enough ground to be perfectly legible — the game sells itself in the
 * periphery without ever competing with the thing you came here to click.
 * ========================================================================= */
#menu.overlay {
  background:
    radial-gradient(ellipse at 50% 50%, rgba(13,10,20,0.40) 0%,
                                        rgba(13,10,20,0.86) 62%,
                                        rgba(13,10,20,0.96) 100%);
}
/* The card gets its own solid ground so nothing shows through the text. */
#menu .menu-card {
  background-color: var(--ink);
  box-shadow: 6px 6px 0 rgba(0,0,0,0.85), 0 0 0 2px var(--ink),
              0 0 90px 30px rgba(13,10,20,0.9);
}

/* ---------- Steering slider ---------- */
.sens-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.sens-end {
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: 0.16em;
  color: var(--muted);
}
/* Blocky track and a square thumb — a rounded pill slider is the one control
   that would give the whole 8-bit pass away. */
#sens {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 10px;
  background: #2c2240;
  border: 2px solid var(--ink);
  cursor: pointer;
}
#sens::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 18px;
  background: var(--accent);
  border: 2px solid var(--ink);
  cursor: pointer;
}
#sens::-moz-range-thumb {
  width: 14px; height: 18px;
  border-radius: 0;
  background: var(--accent);
  border: 2px solid var(--ink);
  cursor: pointer;
}

/* ---------- Colour palette (replaces the free colour pickers) ---------- */
.pal-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 4px;
  margin-bottom: 10px;
}
.pal {
  aspect-ratio: 1;
  border: 2px solid var(--ink);
  cursor: pointer;
  padding: 0;
  transition: none;
}
.pal.selected { border-color: var(--gold); box-shadow: inset 0 0 0 2px var(--ink); }
.pal.locked { cursor: not-allowed; filter: grayscale(1) brightness(0.35); }
.pal.shake { animation: shake 0.3s; }

/* =========================================================================
 * REDUCED MOTION — ambient motion is the first thing to go.
 *
 * Everything here is atmosphere: the ball spinning, its beams sweeping, the
 * menu slithering in. None of it carries information, so honouring the OS
 * preference costs the player nothing and spares anyone who gets motion-sick
 * or eye-strained by persistent background movement.
 *
 * The canvas side reads the same preference in JS (see Game.ambientMotion).
 * ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  #menu.enter, #menu.enter .menu-card, #menu.enter .menu-card > * { animation: none; }
  .exec-word, .exec-by, .exec-taunt { animation: none; }
}

/* =========================================================================
 * THE DOSSIER — the numbers, folded into the barrel screen.
 *
 * There used to be a second full-screen card behind this one carrying eleven
 * stat tiles. Two screens for one death is one too many, so the numbers moved
 * in here and got cut down to a readout you take in at a glance.
 *
 * The look is a case file being closed, not a scoreboard: a single hairline
 * rule with a stamped caption sitting in a gap in it, then two tight rows of
 * label-over-value in mono. No boxes. Boxes are what made the old card feel
 * like a spreadsheet, and eleven of them is a wall.
 *
 * It fades up on a 900ms cross rather than sliding, because the barrel is still
 * sitting there behind it — anything that moves would fight the frozen scene.
 * ========================================================================= */
.exec-dossier {
  margin-top: clamp(18px, 3.5vh, 40px);
  width: min(700px, 90vw);
  /* the parent is pointer-events:none so the frozen scene stays untouchable —
     the dossier is the one part of it you're allowed to click */
  pointer-events: auto;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.9s ease-out, transform 0.9s cubic-bezier(.2,.8,.25,1);
}
.exec-dossier.in { opacity: 1; transform: translateY(0); }
.exec-dossier.hidden { display: none; }

/* Hairline with the caption sitting IN it, not above it. */
.dossier-rule {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: clamp(12px, 2vh, 20px);
}
.dossier-rule::before, .dossier-rule::after {
  content: '';
  flex: 1;
  height: 2px;
  background: rgba(240, 197, 101, 0.34);
}
.dossier-rule span {
  font-family: var(--mono);
  text-transform: uppercase;   /* it is a stamp on a file, not a sentence */
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.42em;
  text-indent: 0.42em;      /* letter-spacing pads the right; put it back left */
  color: rgba(240, 197, 101, 0.8);
  white-space: nowrap;
}

/* Wrapping, not a fixed column count: the row is six cells normally and up to
   nine when the streak/saves/buff lines earn their place, and a rigid grid
   would leave a hole on the common case. */
.dossier-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(10px, 2.4vw, 28px) clamp(14px, 3.2vw, 38px);
}
.ds {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
.ds i {
  font-family: var(--mono);
  font-style: normal;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  color: rgba(230, 220, 242, 0.46);
}
.ds b {
  font-family: var(--mono);
  font-size: clamp(17px, 2.6vw, 25px);
  font-weight: 700;
  color: #e6dcf2;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.75);
  font-variant-numeric: tabular-nums;
}

.dossier-unlocks {
  margin-top: clamp(10px, 2vh, 18px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.dossier-unlocks:empty { display: none; }
.unlock-line {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--tc, var(--gold));
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.8);
}

/* The only two things on this screen you can touch. */
.exec-actions {
  margin-top: clamp(18px, 3.4vh, 34px);
  display: flex;
  justify-content: center;
  gap: 12px;
}
.exec-actions button {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  padding: 13px 26px;
  border: 2px solid var(--gold);
  background: transparent;
  color: var(--gold);
  cursor: pointer;
  transition: none;          /* stepped, never eased — see the 8-bit pass */
}
.exec-actions button:hover { background: var(--gold); color: #150d20; }
.exec-actions button.secondary {
  border-color: rgba(230, 220, 242, 0.34);
  color: rgba(230, 220, 242, 0.66);
}
.exec-actions button.secondary:hover {
  background: rgba(230, 220, 242, 0.86);
  color: #150d20;
}

@media (prefers-reduced-motion: reduce) {
  .exec-dossier { transition: opacity 0.3s linear; transform: none; }
  .exec-dossier.in { transform: none; }
}

/* Short viewports: the word is already enormous, so the dossier gives ground
   first rather than pushing the buttons off the bottom. */
@media (max-height: 700px) {
  .exec-dossier { margin-top: 12px; }
  .dossier-rule { margin-bottom: 8px; }
  .ds b { font-size: 17px; }
  .exec-actions { margin-top: 14px; }
  .exec-actions button { padding: 10px 20px; }
}

/* The grade line, under the numbers. One sentence of editorial about how the
   run went, set small and dim so it reads as a note somebody added to the file
   rather than as a second headline competing with the verdict. */
.dossier-grade {
  margin-top: clamp(8px, 1.6vh, 14px);
  text-align: center;
  font-family: var(--display);
  font-size: clamp(12px, 1.6vw, 15px);
  font-style: italic;
  color: rgba(230, 220, 242, 0.52);
  text-wrap: balance;
}
.dossier-grade:empty { display: none; }

/* The two-line marquee. "LINE OF" is set quiet and small above a loud "DUTY" —
   article line above noun, the standard title-card hierarchy, and the only way
   a 12-glyph name survives a 5x7 bitmap face at a scale that still resolves. */
.logo-a, .logo-b { display: block; }
.logo-a { margin-bottom: 4px; opacity: 0.85; }
.logo-b { margin-bottom: 2px; }

/* ---------- Population readout ----------
   Lower-left, above the minimap. Counts bots as players deliberately — it is
   standing in for the lobby size once this is multiplayer, and a number that
   silently changes meaning on launch day is worse than one that is honest from
   the start. */
#population {
  position: fixed;
  left: 14px;
  bottom: 178px;
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-family: var(--mono);
  pointer-events: none;
}
.pop-n {
  font-size: 20px;
  font-weight: 700;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 0 rgba(0, 0, 0, 0.8);
}
.pop-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
@media (max-height: 640px) { #population { bottom: 150px; } }

/* The "??? OPENED" line above a mystery-box result, so the payoff is attached
   to the box it came out of rather than arriving unexplained. */
.pu-toast-from {
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 3px;
}
.pu-toast.from-mystery { border-left-width: 4px; }


/* ---------- Bitmap-typed controls ----------
   A button whose label has been replaced by baked bitmap type. The canvas is
   the label, so the button's own padding and border still do the work — this
   only stops the two from fighting over vertical rhythm. */
.pf-btn { line-height: 0; padding-top: 14px; padding-bottom: 14px; }
.pf-btn canvas { image-rendering: pixelated; display: block; margin: 0 auto; }

/* ---------- The nearest unlock ----------
   One target, not a wall. A list of everything you have not got is a list; the
   single thing you are closest to is a reason to press play. */
#next-unlock {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 12px 0 2px;
  padding: 8px 11px;
  border: 2px solid rgba(var(--paper-rgb), 0.16);
  background: rgba(var(--accent-rgb), 0.045);
}
#next-unlock:empty { display: none; }
.nu-label {
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.nu-name {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* takes the slack so the bar is pinned right whatever the name's length */
  flex: 1 1 auto;
}
/* HOW CLOSE, not just what. The label says what to do and the name says what it
   buys; without a bar between them there is no sense of nearly-there, which is
   the only thing that actually makes someone press play one more time. */
.nu-bar {
  flex: 0 0 64px;
  height: 5px;
  background: rgba(var(--paper-rgb), 0.14);
  overflow: hidden;
  align-self: center;
}
.nu-bar > i {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width 0.5s ease;
}

/* What a locked trophy buys you. Only rendered on the next one you would earn,
   so the cabinet points at one target rather than fifty. */
.trophy-text u {
  display: block;
  margin-top: 3px;
  font-family: var(--mono);
  font-size: 9.5px;
  font-style: normal;
  text-decoration: none;
  letter-spacing: 0.1em;
  color: var(--accent);
}


/* Population, hard into the lower-left corner.
   It was floating at 178px up the screen because it was placed above the
   minimap — which made it read as a stray label rather than as part of the HUD
   frame. Anchored to the corner it becomes chrome, which is what it is. */
#population {
  left: 10px;
  bottom: 8px;
  gap: 6px;
  align-items: baseline;
}
.pop-n { font-size: 17px; }
.pop-label { font-size: 8.5px; }
@media (max-height: 640px) { #population { bottom: 8px; } }

/* ---------- Bitmap label safety net ----------
   A baked canvas has a fixed pixel width, and if it is ever baked against a
   stale or unmeasurable container it will happily overflow its button — which is
   exactly what CANCEL did (a 160px canvas in a 100px button) because it was
   baked while the editor was still display:none. Re-baking on open is the real
   fix; this makes overflow impossible regardless. */
.pf-btn canvas { max-width: 100%; height: auto; }

/* Dialog buttons share the row evenly, so their width is decided by the layout
   rather than by whatever the label happened to bake to. */
.editor-buttons { display: flex; gap: 10px; }
.editor-buttons button { flex: 1 1 0; min-width: 0; }
.exec-actions button { flex: 1 1 0; min-width: 0; }

/* ---------- touch ----------
   Keyed on `pointer: coarse`, not on a width breakpoint, because the thing
   these rules compensate for is the size of a fingertip — which does not change
   when you rotate a tablet into landscape and cross 768px. A desktop window
   narrowed to phone width gets none of this, and correctly so: a mouse can hit
   a 20px arrow.

   Everything here is a MINIMUM, so nothing that is already comfortable moves.
   ~44px is the smallest reliably hittable target. */
@media (pointer: coarse) {
  .drip-arrow {
    min-width: 44px;
    min-height: 44px;
    font-size: 22px;
  }
  #create-skin-btn,
  #trophy-btn,
  .trophy-summary,
  .menu-row button,
  .skin-swatch,
  .cast-swatch {
    min-height: 44px;
  }

  /* Notched and gesture-bar phones eat the corners the HUD is anchored to: the
     population count sits under the home indicator and the control hint is
     behind it entirely. `env()` resolves to 0 everywhere else, so this is inert
     on a desktop and on older hardware. Requires viewport-fit=cover on the
     meta tag, which index.html sets. */
  #population {
    margin-left: env(safe-area-inset-left);
    margin-bottom: env(safe-area-inset-bottom);
  }
  #boost-hint {
    margin-bottom: env(safe-area-inset-bottom);
  }
  .panel {
    margin-top: env(safe-area-inset-top);
    margin-right: env(safe-area-inset-right);
  }
}

/* ---------- connection state ----------
   Centre-top, over everything, and hidden unless there is something to say. */
#net-status {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  padding: 7px 14px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text);
  background: rgba(0, 0, 0, 0.72);
  border: 2px solid rgba(var(--accent-rgb), 0.5);
  pointer-events: none;
}
#net-status.good { border-color: rgba(var(--accent2-rgb), 0.6); }

/* Manual trace upload for ?netdebug. Kept separate from #net-status because
   that readout deliberately ignores pointer events; this control has to work
   under a thumb while the canvas is receiving steering touches. */
#net-report {
  position: fixed;
  top: 54px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 41;
  margin: 0;
  padding: 9px 13px;
  min-width: 132px;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.10em;
  color: var(--ink);
  background: var(--accent);
  border: 2px solid var(--ink);
  box-shadow: 3px 3px 0 rgba(0,0,0,0.82);
  touch-action: manipulation;
}
#net-report:active {
  transform: translate(calc(-50% + 3px), 3px);
  box-shadow: none;
}
@supports (top: env(safe-area-inset-top)) {
  #net-report { margin-top: env(safe-area-inset-top); }
}

/* A failed online join is a decision, not a toast. This sits above the game
   until the player retries or knowingly chooses the private bot match. */
#online-unavailable {
  position: fixed; inset: 0; z-index: 70; display: flex;
  align-items: center; justify-content: center;
  background: rgba(6, 7, 10, 0.88);
}
#online-unavailable.hidden { display: none; }
.online-card {
  width: min(430px, calc(100vw - 32px));
  padding: 22px;
  color: var(--text);
  background: var(--panel);
  border: 2px solid rgba(var(--accent-rgb), 0.65);
  box-shadow: 8px 8px 0 rgba(0, 0, 0, 0.38);
}
.online-card h3 {
  margin: 10px 0 8px;
  font-family: var(--display);
  font-size: 18px;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.online-card p { margin: 0; color: var(--muted); line-height: 1.5; }
.online-actions { display: flex; gap: 10px; margin-top: 18px; }
.online-actions button { flex: 1; }

/* ---- FEEDBACK ---------------------------------------------------------
 * Deliberately quiet: a link, not a call to action. It should be findable
 * the moment somebody has an opinion and invisible the rest of the time. */
.linkish {
  background: none; border: 0; padding: 6px 2px; margin-top: 8px;
  color: #8b8b93; font: inherit; font-size: 11px; letter-spacing: .06em;
  text-decoration: underline; cursor: pointer;
}
.linkish:hover { color: #e8e6e3; }

#feedback-panel {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center;
  background: rgba(6, 7, 10, 0.82);
}
#feedback-panel.hidden { display: none; }
.fb-card {
  width: min(440px, calc(100vw - 32px));
  background: #14161d; border: 1px solid #2a2e39; border-radius: 10px;
  padding: 18px 18px 14px;
}
.fb-card h3 { margin: 0 0 2px; font-size: 14px; letter-spacing: .1em; }
.fb-sub { margin: 0 0 12px; color: #8b8b93; font-size: 11px; }
.fb-types { display: flex; gap: 14px; margin-bottom: 10px; font-size: 12px; }
.fb-types label { display: flex; align-items: center; gap: 5px; cursor: pointer; }
#feedback-text {
  width: 100%; box-sizing: border-box; resize: vertical;
  background: #0d0f14; color: #e8e6e3; border: 1px solid #2a2e39;
  border-radius: 6px; padding: 9px; font: inherit; font-size: 13px;
}
.fb-actions { display: flex; gap: 8px; margin-top: 10px; }
.fb-status { margin-top: 8px; font-size: 11px; color: #8b8b93; min-height: 14px; }
.fb-status.good { color: #7ee08a; }
.fb-status.bad { color: #ff9aa2; }
