/* ============================================================
   FHC PTC Solver — see docs/brand.md.
   Values are measured from the game, not chosen. The generic token
   names below (--bg, --panel, --line…) are kept so the whole app
   shifts at once; the FHC ramps beneath them are what components
   reach for when they need a specific stop.
   ============================================================ */
:root {
  /* grounds — chromatically dead, no hue bias */
  --void: #000000;
  --ground: #050505;
  --bg: #0a0a0a;
  --panel: #131414;
  --panel-2: #1c1c1c;
  --rail: #222222;
  --row: #353535;
  --row-gap: #1e1e1e;
  --line: #262626;

  /* text */
  --text: #ffffff;
  --text-2: #c6c6c6;
  --muted: #737473;
  --dim: #484848;
  --ink-gold: #f9e995;

  /* red — identity, progress, destructive */
  --red-deep: #410305;
  --red-body: #8f1717;
  --red-mid: #a61b1b;
  --red: #de2424;
  --red-peak: #ed6d70;

  /* cyan — interaction, selected, neutral commit */
  --cy-deep: #062f34;
  --cy-body: #07505c;
  --cy-mid: #077c85;
  --blue: #06bada;
  --cy-peak: #67def5;

  /* blue — secondary, unselected, cancel */
  --bl-body: #072c4a;
  --bl-mid: #14446a;
  --bl-rim: #44759c;

  /* gold — reward, value, primary commit */
  --gd-body: #806609;
  --gd-mid: #b69610;
  --amber: #f2d21f;
  --gd-peak: #ffe4af;

  /* green — acquire, apply, add */
  --gr-body: #0b3b02;
  --gr-mid: #116b01;
  --green: #3caf27;
  --gr-peak: #66ee55;

  /* grey — disabled desaturates, never fades */
  --dis-body: #393c39;
  --dis-mid: #565957;
  --dis-rim: #bebfbe;

  /* status colours, text only — never reuse the button greens here */
  --lime: #7ae950;
  --event: #b2ed39;

  /* chalk — the WIZ half of the identity (brand.md §1). Our voice, not the
     game's: the logo and anything we authored rather than transcribed. Never
     takes a gloss, a rim or a metal, and never sits on a card. */
  --board: #0d1410;
  --board-rim: #2c3a30;
  --chalk: #e8f3ea;
  --chalk-2: #9bb0a2;

  /* type */
  --display: "Anton", "Arial Narrow", Impact, sans-serif;
  --cond: "Barlow Condensed", "Arial Narrow", system-ui, sans-serif;
  --body: "Barlow", "Segoe UI", system-ui, -apple-system, sans-serif;

  --radius: 10px;
  color-scheme: dark;

  /* --- texture: five layers, all inline, no image assets --- */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='0.055'/%3E%3C/svg%3E");
  --person: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0 2c-5 0-9 2.8-9 6.2V22h18v-1.8c0-3.4-4-6.2-9-6.2Z'/%3E%3C/svg%3E");
  --torn: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 24' preserveAspectRatio='none'%3E%3Cpolygon fill='%23DCDCDC' points='0,8 40,5 80,9 120,6 160,10 200,7 240,4 280,8 320,11 360,6 400,9 440,5 480,10 520,7 560,4 600,9 640,6 680,11 720,8 760,5 800,9 840,12 880,7 920,4 960,9 1000,6 1040,10 1080,7 1120,5 1160,9 1200,7 1200,16 1160,19 1120,15 1080,18 1040,14 1000,17 960,20 920,16 880,13 840,18 800,15 760,19 720,16 680,13 640,17 600,20 560,16 520,14 480,18 440,15 400,19 360,16 320,13 280,17 240,20 200,15 160,18 120,14 80,17 40,20 0,16'/%3E%3C/svg%3E");

  /* prestige frame — card and OVR chip share this so they read as one object.
     To recolour it for a set, redeclare --frame itself on that set's rule, not
     just the three stops: a custom property is substituted where it is
     DECLARED, so --frame resolves against these values here and descendants
     inherit it already resolved. Overriding --frame-1/2/3 further down does
     nothing. Keep the same nine-stop banding so the sets stay a family. */
  --frame-1: #fff8dc;
  --frame-2: #e8c24a;
  --frame-3: #806112;
  --frame: linear-gradient(122deg,
    var(--frame-1) 0%,  var(--frame-3) 11%, var(--frame-2) 22%,
    var(--frame-1) 33%, var(--frame-3) 45%, var(--frame-2) 56%,
    var(--frame-1) 68%, var(--frame-3) 80%, var(--frame-2) 100%);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  background-image: var(--grain);
  color: var(--text);
  font-family: var(--body);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Bold is the baseline weight in FHC; regular is for prose only. */
h2 {
  font-family: var(--cond); font-weight: 700; font-size: 19px;
  letter-spacing: .045em; text-transform: uppercase; margin: 0;
}
h3.sub {
  font-family: var(--cond); font-weight: 700; font-size: 13px;
  text-transform: uppercase; letter-spacing: .16em; color: var(--muted);
  margin: 20px 0 8px;
}
[hidden] { display: none !important; }

/* torn paper edge — the signature divider */
.torn {
  height: 12px; flex: none;
  background: var(--torn) repeat-x center / 100% 100%;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,.7));
}
.torn.flip { transform: scaleY(-1); }

/* hazard stripe + red halftone — the most recognisable FHC texture */
.hazard {
  background-color: var(--void);
  background-image:
    radial-gradient(circle at 3px 3px, rgba(222,36,36,.85) 1.4px, transparent 1.6px),
    repeating-linear-gradient(-45deg, #e8e8e8 0 22px, #e8e8e8 22px, #111111 22px 44px);
  background-size: 8px 8px, auto;
  background-blend-mode: multiply, normal;
}

/* ---------- top bar + tabs ----------
   The game's header: scuffed dark band, red hairline on top, and the torn
   paper edge underneath doing the separating rather than a border. */
.topbar {
  display: flex; align-items: center; gap: 20px;
  padding: 0 18px;
  background: var(--panel);
  background-image: var(--grain);
  border-top: 2px solid var(--red);
  position: sticky; top: 0; z-index: 30;
  flex-wrap: wrap;
}
.topbar-torn { position: sticky; top: 52px; z-index: 29; }

/* The FHCWiz lockup — chalk mark, then the wordmark with its red rule.
   See docs/brand.md §1: FHC is set muted and WIZ lit, because the acronym
   names the subject and the brand is the half that is lit. That weighting
   is the trademark position, so it is not a styling choice to revisit. */
.brand { display: flex; align-items: center; gap: 10px; padding: 9px 0 8px; }
.brand-mark { width: 30px; height: 26px; flex: none; display: block; }
.brand-name {
  font-family: var(--display); font-size: 21px;
  letter-spacing: .02em; text-transform: uppercase; line-height: 1;
  color: var(--text);
  border-bottom: 3px solid var(--red); padding-bottom: 4px;
}
.brand-name .acronym { color: var(--muted); }

.tabs { display: flex; gap: 2px; }
/* Five tabs do not fit a phone. The game scrolls its nav rather than
   stacking or shrinking it, so the bar takes its own row and slides. */
@media (max-width: 900px) {
  .tabs {
    flex: 1 1 100%; order: 3;
    overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none; -ms-overflow-style: none;
  }
  .tabs::-webkit-scrollbar { display: none; }
  .topbar { padding-inline: 0; }
  .brand { padding-left: 18px; }
  .profile-btn { margin-right: 12px; }
  .tab { flex: none; white-space: nowrap; padding: 12px 14px 9px; }
  .tab:first-child { padding-left: 18px; }
  .tab:last-child { padding-right: 18px; }
  /* The sticky torn edge sits under a two-row bar now. */
  .topbar-torn { top: 96px; }
}
.tab {
  background: none; border: none; border-bottom: 3px solid transparent;
  color: var(--text-2);
  font-family: var(--cond); font-weight: 700; font-size: 15px;
  letter-spacing: .13em; text-transform: uppercase;
  padding: 15px 16px 11px; cursor: pointer;
}
.tab:hover { color: var(--text); background: rgba(255,255,255,.04); }
.tab.active {
  color: var(--text); border-bottom-color: var(--red);
  background: linear-gradient(180deg, rgba(222,36,36,.16), rgba(222,36,36,0));
}
.tab:focus-visible { outline: 2px solid var(--blue); outline-offset: -4px; }
/* On a standalone page (privacy, 404) a tab or a button is an <a> rather than
   a <button>, so each needs the things a button got for free — chiefly not
   being underlined, and not falling back to the browser's own link colour. */
a.tab { text-decoration: none; display: inline-flex; align-items: center; }
a.btn { text-decoration: none; }
.empty-state p a {
  color: var(--blue); text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.empty-state p a:hover { color: var(--cy-peak); }

/* The game's header chip: a crest on the left, the name over a thin level
   bar. A circle-avatar-plus-label belonged to a different product. */
.profile-btn {
  display: flex; align-items: center; gap: 10px;
  margin-left: auto;
  background: linear-gradient(180deg, #1e1e1e, #101010);
  border: 1px solid #2e2e2e; border-radius: 8px;
  padding: 5px 12px 5px 5px; cursor: pointer; font: inherit; color: var(--text);
}
.profile-btn:hover { border-color: var(--cy-mid); background: linear-gradient(180deg, #262626, #131313); }
.profile-btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* Hexagon crest in the prestige frame, so it reads as the same family as a
   Rare Gold card and the OVR chip. The face nests inside the frame because a
   hexagon cannot carry an even border. */
.profile-btn .crest {
  flex: none; width: 34px; height: 31px;
  display: grid; place-items: center; padding: 2px;
  clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
  background: var(--frame);
}
.profile-btn .crest > i {
  display: grid; place-items: center; width: 100%; height: 100%;
  clip-path: inherit; font-style: normal;
  background: linear-gradient(160deg, #2d2d2d, #0d0d0d);
  font-family: var(--cond); font-weight: 700; font-size: 12px;
  letter-spacing: .06em; color: var(--ink-gold);
}
.profile-btn:hover .crest > i { color: var(--gd-peak); }

.profile-text { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.profile-btn .profile-label {
  font-family: var(--cond); font-weight: 700; font-size: 13px;
  letter-spacing: .14em; text-transform: uppercase; line-height: 1;
}
/* The level bar under the name, straight off the game's header. Decorative
   for now — it has nothing to measure yet. */
.profile-bar {
  display: block; width: 62px; height: 4px; border-radius: 2px;
  background: #000; box-shadow: inset 0 0 0 1px #333;
}
.profile-bar > i {
  display: block; width: 38%; height: 100%; border-radius: 2px;
  background: linear-gradient(90deg, var(--red-body), var(--red));
}
@media (max-width: 560px) { .profile-text { display: none; } }

/* ---------- views ---------- */
/* The help button is fixed bottom-right, so every page keeps enough room
   beneath it that it never sits over the last line of content. */
.view { max-width: 1500px; margin: 0 auto; padding: 16px 16px 84px; }

.ptc-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 16px;
  align-items: start;
}
.col-main, .col-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
@media (max-width: 1050px) {
  .ptc-layout { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- the create bar ---------- */
.ptc-bar { margin-bottom: 16px; }
.create-bar {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; gap: 12px;
  padding: 16px 20px;
  background: linear-gradient(90deg, rgba(6,186,218,.18), rgba(6,186,218,.03));
  border: 1px solid var(--blue); border-radius: var(--radius);
  color: var(--text); font: inherit; font-size: 16px; font-weight: 700;
  cursor: pointer; text-align: left;
}
.create-bar:hover { background: linear-gradient(90deg, rgba(6,186,218,.3), rgba(6,186,218,.07)); }
.create-bar .chev { font-size: 22px; color: var(--blue); line-height: 1; }

.ptc-bar-active {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
}
.ptc-bar-active select { width: auto; min-width: 200px; max-width: 340px; font-weight: 600; }
.ptc-bar-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; flex-wrap: wrap; }
.action-divider { width: 1px; align-self: stretch; min-height: 20px; background: var(--line); margin: 0 2px; }

/* ---------- panels ----------
   Section header pattern, straight from the game: scuffed band, caps title
   with a grey sentence-case subtitle on the same baseline, torn edge below. */
.panel {
  background: var(--ground); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
/* Nothing here is sticky. Requirements used to be, and Details sits directly
   below it in the same column, so pinning one slid it over the other and
   eventually covered it. Sticking the whole column instead fixes the overlap
   but never fires: requirements plus details is reliably taller than the
   board beside it, so the column has no travel to stick within. A rule that
   cannot act is worse than none, so both panels simply scroll. The Hide
   controls on each head are the answer to a long page. */
.panel-head {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px 16px;
  background: var(--panel); background-image: var(--grain);
}
/* The torn edge replaces the head's bottom border. */
.panel-head::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 10px;
  background: var(--torn) repeat-x center / 100% 100%;
  pointer-events: none;
}
.panel-head h2 { line-height: 1.1; }
.panel-head .sub-label {
  font-family: var(--body); font-weight: 500; font-size: 13px;
  letter-spacing: 0; text-transform: none; color: var(--muted);
}
.head-actions { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
.panel-head .collapse {
  margin-left: auto; background: none; border: none; color: var(--muted);
  cursor: pointer; font-size: 12px; text-decoration: underline;
}
.panel-body { padding: 14px; }

/* ---------- forms ---------- */
.grid-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
.grid-form label, .req-row label, .filters label {
  display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted);
}
.grid-form label.full { grid-column: 1 / -1; }
.grid-form label.checkbox { flex-direction: row; align-items: center; gap: 8px; color: var(--text); }
.grid-form label.checkbox input { width: auto; }

/* Black fill, light grey rim — the game's text field. */
input, select {
  background: var(--void); color: var(--text);
  border: 2px solid #6a6a6a; border-radius: 6px;
  padding: 6px 9px; font-size: 14px; font-family: var(--body); font-weight: 600;
  width: 100%;
}
input::placeholder { color: #8f8f8f; font-weight: 500; }
input:focus, select:focus { outline: none; border-color: var(--blue); }
input:disabled { color: var(--muted); background: transparent; cursor: default; }
/* A filter that's locked until its parent is chosen reads as blocked out,
   label included, rather than just inert. */
.filters input:disabled, .filters select:disabled {
  background: rgba(255,255,255,.02); border-style: dashed;
  color: var(--muted); opacity: .5; cursor: not-allowed;
}
.filters label:has(:disabled) { opacity: .55; }
input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--blue); }
.req-star { color: var(--red); }

/* Every interactive surface: vertical gloss, specular band over the top ~40%,
   and a bright rim in its own hue. Roles carry meaning — see docs/brand.md.
   Default is the neutral/secondary blue; .primary is the cyan commit. */
.btn {
  --b-body: var(--bl-body); --b-mid: var(--bl-mid); --b-rim: var(--bl-rim);
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  min-height: 34px; padding: 0 14px;
  font-family: var(--cond); font-weight: 700; font-size: 14px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text);
  text-shadow: 0 1px 3px rgba(0,0,0,.65);
  border: 2px solid var(--b-rim); border-radius: 9px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--b-rim) 52%, transparent) 0%,
    var(--b-mid) 42%, var(--b-body) 55%, #060606 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35), 0 2px 0 rgba(0,0,0,.5);
  cursor: pointer; white-space: nowrap;
}
.btn::after {
  content: ""; position: absolute; inset: 2px 3px auto; height: 38%;
  border-radius: 6px 6px 40% 40%;
  background: linear-gradient(180deg, rgba(255,255,255,.32), rgba(255,255,255,0));
  pointer-events: none;
}
.btn:hover { filter: brightness(1.13); }
.btn:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

.btn.primary     { --b-body: var(--cy-body); --b-mid: var(--cy-mid); --b-rim: var(--blue); }
.btn.commit      { --b-body: var(--gd-body); --b-mid: var(--gd-mid); --b-rim: var(--amber); }
.btn.acquire     { --b-body: var(--gr-body); --b-mid: var(--gr-mid); --b-rim: var(--green); }
.btn.destructive,
.btn.danger      { --b-body: #710a0a; --b-mid: #b20a0d; --b-rim: #d83030; }
.btn.ghost       { --b-body: #101010; --b-mid: #1f1f1f; --b-rim: #3a3a3a; }
.btn.small       { min-height: 28px; padding: 0 10px; font-size: 12.5px; }

/* Disabled desaturates at full opacity — same gloss, same rim, white label.
   Nothing is ever faded down; this matches the game's locked states. */
.btn:disabled,
.btn.is-off {
  --b-body: #242724; --b-mid: var(--dis-mid); --b-rim: var(--dis-rim);
  cursor: not-allowed;
}
.btn:disabled:hover, .btn.is-off:hover { filter: none; }

.icon-btn { background: none; border: none; color: var(--muted); font-size: 22px; cursor: pointer; line-height: 1; }
.hint { color: var(--muted); font-size: 12px; margin: 0 0 12px; }
.hint code { background: var(--bg); border: 1px solid var(--line); border-radius: 4px; padding: 0 4px; font-size: 11px; }
.error-text { color: var(--red); font-size: 12px; }
.pill {
  background: var(--panel); border: 1px solid var(--line);
  padding: 1px 8px; border-radius: 20px; font-size: 12px; color: var(--muted); font-weight: 400;
}
.empty { color: var(--muted); font-size: 13px; }

/* ---------- requirement editor ---------- */
.req-editor-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.req-row {
  display: grid; grid-template-columns: 150px minmax(0,1fr) 80px auto;
  gap: 8px; align-items: end;
  background: var(--panel-2); padding: 10px; border-radius: 8px; border: 1px solid var(--line);
}
@media (max-width: 700px) { .req-row { grid-template-columns: 1fr 1fr; } }

/* ---------- the card face ---------- */
.canvas, .card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 12px;
}
/* A grid of search results may end on a short row without anyone minding. The
   board may not: it is a fixed set of slots, and a last row holding one of
   them reads as a fault. balanceCanvas() places it instead, measuring these
   two properties — so the breakpoints stay the source of truth for card size,
   and the auto-fill rule above is what shows before the first measure. */
.canvas { --slot-min: 148px; --slot-gap: 12px; gap: var(--slot-gap); }
.card-grid.compact { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); max-height: 52vh; overflow-y: auto; padding: 2px; }

.fcard {
  --metal-1: #e9edf2; --metal-2: #a8b3c0; --metal-3: #78828f; --ink: #1e242c;
  position: relative;
  display: flex; flex-direction: column;
  padding: 8px 8px 14px;
  border-radius: 12px 12px 4px 4px;
  background: linear-gradient(158deg, var(--metal-1) 0%, var(--metal-2) 50%, var(--metal-3) 100%);
  /* Rounded top, straight sides, chamfered bottom corners meeting a wide
     shallow point — the game's silhouette, not a sharp V. */
  clip-path: polygon(0 0, 100% 0, 100% 86%, 63% 100%, 37% 100%, 0 86%);
  color: var(--ink);
  min-height: 178px;
  box-shadow: inset 0 0 0 3px rgba(0,0,0,.5);
  filter: drop-shadow(0 3px 5px rgba(0,0,0,.7));
}
/* Diagonal brushed-metal sheen across the top-left corner. */
.fcard::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(112deg, rgba(255,255,255,.32) 0 18%, rgba(255,255,255,0) 34%);
  pointer-events: none;
}

/* The prestige frame: a transparent border painted from the shared --frame
   gradient, so the silhouette clip still cuts card and frame as one shape and
   no extra markup is needed. Recolour per set via --frame-1/2/3. */
.fcard.framed {
  border: 6px solid transparent;
  background:
    linear-gradient(158deg, var(--metal-1) 0%, var(--metal-2) 50%, var(--metal-3) 100%) padding-box,
    var(--frame) border-box;
  box-shadow: inset 0 0 0 2px rgba(0,0,0,.5);
}
.fcard.gold, .tpl-art.gold, .chal-metal.gold { --metal-1: #f6e6ad; --metal-2: #d0a63f; --metal-3: #906a1c; --ink: #2c2208; }
/* Rare Gold sits a touch brighter and yellower than Gold — measured at hue 46
   against Gold's 41, with the rim lifted from #cea23e to #e4c960. */
.fcard.raregold, .tpl-art.raregold, .chal-metal.raregold { --metal-1: #fff6cf; --metal-2: #f2d21f; --metal-3: #9c7a0b; --ink: #241d03; }
.fcard.silver, .tpl-art.silver, .chal-metal.silver { --metal-1: #eef2f7; --metal-2: #adb8c5; --metal-3: #7b8794; --ink: #1c222a; }
.fcard.bronze, .tpl-art.bronze, .chal-metal.bronze { --metal-1: #f3cfa4; --metal-2: #c78f57; --metal-3: #8c5e2e; --ink: #2e1d0c; }
.fcard.special, .tpl-art.special, .chal-metal.special { --metal-1: #cfdcff; --metal-2: #7b99e0; --metal-3: #46639f; --ink: #101a33; }

/* Every host that paints a metal lists itself here: the card face, the
   template tile art, and the PTC Database row chip. A new host shows
   silver until it is added to these five lines. */
/* Per-set card colours. Each special set gets its own entry — these are not
   a shared "special" look, so a new set means a new block here plus a line
   in SPECIAL_SET_STYLES. */
/* PTC — a warm copper, distinct from the Bronze band it would otherwise sit
   in, carrying a polished bronze frame. The album prints these cards darker
   than a Bronze and captions them in white, so the base sits well down the
   copper ramp and the ink is white rather than the near-black the other warm
   metals use. That contrast is what lets a bronze frame read against a bronze
   card instead of merging into it. */
.fcard.set-ptc, .tpl-art.set-ptc {
  --metal-1: #a97544; --metal-2: #7a4d26; --metal-3: #452710;
  --ink: #ffffff;
  /* Same nine-stop banding as the gold frame, copper stops. Declared whole
     rather than as three variables — see the note on --frame in :root. */
  --frame: linear-gradient(122deg,
    #fff1da 0%,  #603410 11%, #e0a463 22%,
    #fff1da 33%, #603410 45%, #e0a463 56%,
    #fff1da 68%, #603410 80%, #e0a463 100%);
}
/* The shared sheen is a white wash, which reads as chalk on copper. Tinted
   warm, with a faint second band standing in for the football-and-laces
   motif the game prints across the top-left of the art. */
.fcard.set-ptc::before {
  background:
    linear-gradient(112deg, rgba(255,231,199,.34) 0 17%, rgba(255,231,199,0) 34%),
    linear-gradient(122deg,
      rgba(255,231,199,0) 8%,  rgba(255,231,199,.12) 12%, rgba(255,231,199,0) 16%,
      rgba(255,231,199,0) 21%, rgba(255,231,199,.07) 24%, rgba(255,231,199,0) 28%);
}
/* White caps need the band to stay mid-tone; the default lightens it to a
   near-white that the name would disappear into. */
.fcard.set-ptc .fc-name {
  background: linear-gradient(180deg, rgba(255,226,186,.30), rgba(44,20,4,.42));
  border-top-color: rgba(255,231,199,.34);
  border-bottom-color: rgba(44,20,4,.5);
}
.fcard.set-ptc .fc-portrait {
  background: linear-gradient(180deg, rgba(40,19,5,.42), rgba(40,19,5,.62));
  color: rgba(255,231,199,.5);
}
.fcard.set-ptc .fc-stat { border-right-color: rgba(255,231,199,.24); }
.fcard.set-ptc .fc-chip { background: rgba(255,231,199,.18); color: #ffeeda; }
.fcard.set-ptc .fc-chip.set { background: rgba(40,19,5,.45); color: #ffe4bd; }
/* The row chip takes the frame too, so a PTC card reads the same in the list
   as it does on the board. */
.hex.set-ptc {
  --chip: linear-gradient(160deg, #f0cfa8, #c98a52 55%, #8a5528);
  --chip-ink: #2a1608;
  --frame: linear-gradient(122deg,
    #fff1da 0%,  #603410 11%, #e0a463 22%,
    #fff1da 33%, #603410 45%, #e0a463 56%,
    #fff1da 68%, #603410 80%, #e0a463 100%);
}

/* Weekend League — near-black base, acid green type, green metallic frame.
   The frame recolours by redeclaring --frame whole on the set, not by
   overriding its three stops: a custom property is substituted where it is
   DECLARED, so the stops in :root have already resolved by the time this
   rule is read. Keep the nine-stop banding so the sets stay a family. */
.fcard.set-weekend-league, .tpl-art.set-weekend-league {
  /* Lifted off pure black on purpose: at #030603 the lower half of the card
     vanished into the app's own near-black ground and the bottom point read
     as a detached shape. */
  --metal-1: #2c3d23; --metal-2: #16210f; --metal-3: #0b1208;
  --ink: #8dff3a;
  /* Same nine-stop banding as the gold frame, green stops. Declared whole
     rather than as three variables — see the note on --frame in :root. */
  --frame: linear-gradient(122deg,
    #eaffd2 0%,  #1b5207 11%, #5ce41f 22%,
    #eaffd2 33%, #1b5207 45%, #5ce41f 56%,
    #eaffd2 68%, #1b5207 80%, #5ce41f 100%);
}
/* Two jobs in one layer. The shared sheen is a white wash that reads as grey
   smoke on a black card, so it is tinted green; and the game's own art is cut
   through with bright diagonal slashes, which are what stop a near-black card
   reading as a hole in the page. Irregular spacing rather than a repeating
   gradient, because evenly spaced stripes look like a pattern swatch. */
.fcard.set-weekend-league::before {
  background:
    linear-gradient(112deg, rgba(190,255,140,.18) 0 16%, rgba(141,255,58,0) 32%),
    linear-gradient(114deg,
      rgba(141,255,58,0) 12%,   rgba(141,255,58,.22) 15%, rgba(141,255,58,0) 19%,
      rgba(141,255,58,0) 27%,   rgba(141,255,58,.10) 31%, rgba(141,255,58,0) 33%,
      rgba(141,255,58,0) 52%,   rgba(141,255,58,.16) 57%, rgba(141,255,58,0) 61%,
      rgba(141,255,58,0) 74%,   rgba(141,255,58,.09) 77%, rgba(141,255,58,0) 80%);
}
/* The name band lightens by default — a grey strip across a black card looks
   like a rendering fault, so it darkens here instead. */
.fcard.set-weekend-league .fc-name {
  background: linear-gradient(180deg, rgba(141,255,58,.10), rgba(0,0,0,.5));
  border-top-color: rgba(141,255,58,.3);
  border-bottom-color: rgba(141,255,58,.3);
}
.fcard.set-weekend-league .fc-portrait {
  background: linear-gradient(180deg, rgba(0,0,0,.45), rgba(0,0,0,.65));
  color: rgba(141,255,58,.5);
}
.fcard.set-weekend-league .fc-stat { border-right-color: rgba(141,255,58,.22); }
/* A near-black chip on a near-black card is invisible; this one glows faintly. */
.fcard.set-weekend-league .fc-chip {
  background: rgba(141,255,58,.15); color: #c8ff9a;
}
.fcard.set-weekend-league .fc-chip.set {
  background: rgba(141,255,58,.26); color: #eaffd2;
}
/* The chip is a separate host, so it needs its own --frame: the one on the
   card rule never reaches here, and without this the chip frames itself gold
   while its card frames green. */
.hex.set-weekend-league {
  --chip: linear-gradient(160deg, #a8ff5e, #4fc016 55%, #1b5207);
  --chip-ink: #06140a;
  --frame: linear-gradient(122deg,
    #eaffd2 0%,  #1b5207 11%, #5ce41f 22%,
    #eaffd2 33%, #1b5207 45%, #5ce41f 56%,
    #eaffd2 68%, #1b5207 80%, #5ce41f 100%);
}

/* Prospect — purple base, purple metallic frame, and a blue banner behind the
   name. The banner is the set's signature, so it is the one place a card face
   departs from the shared lighten-the-name-band treatment. */
.fcard.set-prospect, .tpl-art.set-prospect {
  --metal-1: #8b48e6; --metal-2: #5a1fae; --metal-3: #35116e;
  --ink: #ffffff;
  --frame: linear-gradient(122deg,
    #f0dcff 0%,  #3d0f7a 11%, #a855f7 22%,
    #f0dcff 33%, #3d0f7a 45%, #a855f7 56%,
    #f0dcff 68%, #3d0f7a 80%, #a855f7 100%);
}
/* The game stacks pale chevrons up the left of the art. Approximated with two
   angled bands rather than drawn, which is enough to read as the same motif at
   card size. */
.fcard.set-prospect::before {
  background:
    linear-gradient(112deg, rgba(255,255,255,.22) 0 16%, rgba(255,255,255,0) 34%),
    linear-gradient(58deg,
      rgba(255,255,255,0) 14%, rgba(255,255,255,.13) 17%, rgba(255,255,255,0) 20%,
      rgba(255,255,255,0) 24%, rgba(255,255,255,.10) 27%, rgba(255,255,255,0) 30%,
      rgba(255,255,255,0) 34%, rgba(255,255,255,.07) 37%, rgba(255,255,255,0) 40%);
}
/* The blue banner. Bright enough to hold white type against the purple. */
.fcard.set-prospect .fc-name {
  background: linear-gradient(180deg, #47bdff 0%, #229ef5 45%, #1467d6 100%);
  border-top-color: rgba(255,255,255,.45);
  border-bottom-color: rgba(6,40,94,.65);
  box-shadow: 0 1px 6px rgba(8,44,102,.5);
}
.fcard.set-prospect .fc-first { opacity: .9; }
.fcard.set-prospect .fc-portrait {
  background: linear-gradient(180deg, rgba(20,4,48,.35), rgba(20,4,48,.6));
  color: rgba(255,255,255,.55);
}
.fcard.set-prospect .fc-stat { border-right-color: rgba(255,255,255,.22); }
.fcard.set-prospect .fc-chip { background: rgba(255,255,255,.18); color: #f3e8ff; }
.fcard.set-prospect .fc-chip.set { background: rgba(34,158,245,.5); color: #ffffff; }
.hex.set-prospect {
  --chip: linear-gradient(160deg, #c89bff, #7c3aed 55%, #3d0f7a);
  --chip-ink: #ffffff;
  --frame: linear-gradient(122deg,
    #f0dcff 0%,  #3d0f7a 11%, #a855f7 22%,
    #f0dcff 33%, #3d0f7a 45%, #a855f7 56%,
    #f0dcff 68%, #3d0f7a 80%, #a855f7 100%);
}

/* Kickoff — a deep purple base slashed with orange, an orange banner behind
   the name, and a frame that runs cream through orange into purple. The game
   prints this set in two colours rather than one, so the frame carries both
   and the name band is the orange half stated plainly. */
.fcard.set-kickoff, .tpl-art.set-kickoff {
  --metal-1: #7b3fc4; --metal-2: #4a1d86; --metal-3: #2a0d52;
  --ink: #ffffff;
  --frame: linear-gradient(122deg,
    #ffe7c9 0%,  #3d1273 11%, #f07a1e 22%,
    #ffe7c9 33%, #3d1273 45%, #f07a1e 56%,
    #ffe7c9 68%, #3d1273 80%, #f07a1e 100%);
}
/* The game cuts the art with wide orange wedges out of the top-left. Two
   angled bands stand in for them, over a warm-tinted version of the shared
   sheen — the plain white wash reads as haze on this purple. */
.fcard.set-kickoff::before {
  background:
    linear-gradient(112deg, rgba(255,231,201,.26) 0 16%, rgba(255,231,201,0) 34%),
    linear-gradient(56deg,
      rgba(240,122,30,0) 10%, rgba(240,122,30,.38) 14%, rgba(240,122,30,0) 21%,
      rgba(240,122,30,0) 26%, rgba(240,122,30,.24) 30%, rgba(240,122,30,0) 35%,
      rgba(255,255,255,0) 40%, rgba(255,255,255,.10) 43%, rgba(255,255,255,0) 47%);
}
/* The signature: a solid orange banner, not the shared lighten-the-band. */
.fcard.set-kickoff .fc-name {
  background: linear-gradient(180deg, #ff9d3c 0%, #f07a1e 48%, #cf5a09 100%);
  border-top-color: rgba(255,231,201,.5);
  border-bottom-color: rgba(90,34,2,.6);
  box-shadow: 0 1px 6px rgba(60,20,0,.45);
}
.fcard.set-kickoff .fc-first { opacity: .92; }
.fcard.set-kickoff .fc-portrait {
  background: linear-gradient(180deg, rgba(20,5,45,.4), rgba(20,5,45,.65));
  color: rgba(255,231,201,.5);
}
.fcard.set-kickoff .fc-stat { border-right-color: rgba(240,122,30,.45); }
.fcard.set-kickoff .fc-chip { background: rgba(255,255,255,.16); color: #ffe7c9; }
.fcard.set-kickoff .fc-chip.set { background: rgba(240,122,30,.6); color: #ffffff; }
.hex.set-kickoff {
  --chip: linear-gradient(160deg, #ffb866, #f07a1e 55%, #8a3a06);
  --chip-ink: #2a0d52;
  --frame: linear-gradient(122deg,
    #ffe7c9 0%,  #3d1273 11%, #f07a1e 22%,
    #ffe7c9 33%, #3d1273 45%, #f07a1e 56%,
    #ffe7c9 68%, #3d1273 80%, #f07a1e 100%);
}

/* 90's Retro — teal ground, magenta diagonals and a halftone dot screen, with
   the name on a bone panel. It is the one set whose name band is lighter than
   its card rather than darker, so it is also the one place the ink flips: the
   OVR and stats stay cream on teal, and the name alone goes dark. */
.fcard.set-90s-retro, .tpl-art.set-90s-retro {
  --metal-1: #2f959c; --metal-2: #18626b; --metal-3: #0c3a42;
  --ink: #fdf7e9;
  --frame: linear-gradient(122deg,
    #fdf7e9 0%,  #123f47 11%, #c93f9c 22%,
    #fdf7e9 33%, #123f47 45%, #c93f9c 56%,
    #fdf7e9 68%, #123f47 80%, #c93f9c 100%);
}
/* Three layers doing what the printed card does: a halftone dot screen, the
   magenta diagonals slashed across the art, and the shared sheen warmed to
   bone so it does not read as grey haze on teal. */
.fcard.set-90s-retro::before {
  background:
    radial-gradient(circle at 1px 1px, rgba(201,63,156,.26) 1px, transparent 1.3px) 0 0 / 7px 7px,
    linear-gradient(112deg, rgba(253,247,233,.2) 0 15%, rgba(253,247,233,0) 33%),
    linear-gradient(58deg,
      rgba(201,63,156,0) 12%, rgba(201,63,156,.42) 16%, rgba(201,63,156,0) 22%,
      rgba(201,63,156,0) 28%, rgba(201,63,156,.22) 32%, rgba(201,63,156,0) 37%,
      rgba(253,247,233,0) 44%, rgba(253,247,233,.12) 47%, rgba(253,247,233,0) 51%);
}
/* Bone panel with a magenta sliver down one edge and teal down the other,
   the way the printed band is cut. */
.fcard.set-90s-retro .fc-name {
  background: linear-gradient(100deg,
    #c93f9c 0 7%, #f6eeda 7% 89%, #2aa8ad 89% 100%);
  color: #10333b;
  border-top-color: rgba(253,247,233,.6);
  border-bottom-color: rgba(8,38,44,.6);
  box-shadow: 0 1px 6px rgba(6,32,38,.5);
}
.fcard.set-90s-retro .fc-first { opacity: .72; }
.fcard.set-90s-retro .fc-portrait {
  background: linear-gradient(180deg, rgba(6,32,38,.4), rgba(6,32,38,.62));
  color: rgba(253,247,233,.5);
}
.fcard.set-90s-retro .fc-stat { border-right-color: rgba(253,247,233,.28); }
.fcard.set-90s-retro .fc-chip { background: rgba(253,247,233,.18); color: #fdf7e9; }
.fcard.set-90s-retro .fc-chip.set { background: rgba(201,63,156,.65); color: #ffffff; }
.hex.set-90s-retro {
  --chip: linear-gradient(160deg, #7fd6d8, #2aa8ad 52%, #0c3a42);
  --chip-ink: #fdf7e9;
  --frame: linear-gradient(122deg,
    #fdf7e9 0%,  #123f47 11%, #c93f9c 22%,
    #fdf7e9 33%, #123f47 45%, #c93f9c 56%,
    #fdf7e9 68%, #123f47 80%, #c93f9c 100%);
}

/* Future Stars #1 — a deep navy card lit from the lower left by a stack of
   rising cyan chevrons, with a teal name band and a luminous cyan frame. The
   chevrons are the set's whole identity: they point up because the rating
   does, and they are the one motif here that is drawn rather than shaded.
   Ink is white throughout — this is the darkest card face in the app and the
   near-black lower right will not hold the usual dark ink. */
.fcard.set-future-stars-1, .tpl-art.set-future-stars-1 {
  --metal-1: #1b5490; --metal-2: #0a2347; --metal-3: #020714;
  --ink: #ffffff;
  --frame: linear-gradient(122deg,
    #e4fbff 0%,  #06304f 11%, #4fd8e8 22%,
    #e4fbff 33%, #06304f 45%, #4fd8e8 56%,
    #e4fbff 68%, #06304f 80%, #4fd8e8 100%);
}
/* Three layers: the shared sheen cooled to cyan, the chevron stack itself as
   one inline SVG, and a glow anchored bottom-left so the arrows read as
   coming up out of that corner rather than sitting flat on the card. */
.fcard.set-future-stars-1::before {
  background:
    linear-gradient(112deg, rgba(214,250,255,.2) 0 14%, rgba(214,250,255,0) 32%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 170' preserveAspectRatio='none'%3E%3Cg fill='none' stroke='%237FEEFF' stroke-width='5'%3E%3Cpath d='M-40 58 L60 -32 L160 58' opacity='.08'/%3E%3Cpath d='M-40 82 L60 -8 L160 82' opacity='.15'/%3E%3Cpath d='M-40 106 L60 16 L160 106' opacity='.28'/%3E%3Cpath d='M-40 130 L60 40 L160 130' opacity='.42'/%3E%3Cpath d='M-40 154 L60 64 L160 154' opacity='.32'/%3E%3Cpath d='M-40 178 L60 88 L160 178' opacity='.19'/%3E%3Cpath d='M-40 202 L60 112 L160 202' opacity='.09'/%3E%3C/g%3E%3C/svg%3E") 0 0 / 100% 100% no-repeat,
    radial-gradient(105% 72% at 24% 80%, rgba(79,216,232,.40), rgba(79,216,232,0) 64%);
}
/* The band is the one warm-bright surface on the card, so it darkens the ink
   nowhere — white caps on teal, the way the printed card sets them. */
.fcard.set-future-stars-1 .fc-name {
  background: linear-gradient(100deg, #45d4e6 0%, #1d8ba2 52%, #0a3d50 100%);
  border-top-color: rgba(214,250,255,.5);
  border-bottom-color: rgba(3,20,34,.6);
  box-shadow: 0 1px 6px rgba(3,24,40,.5);
}
.fcard.set-future-stars-1 .fc-first { opacity: .92; }
.fcard.set-future-stars-1 .fc-portrait {
  background: linear-gradient(180deg, rgba(3,12,30,.42), rgba(3,12,30,.66));
  color: rgba(150,238,255,.5);
}
.fcard.set-future-stars-1 .fc-stat { border-right-color: rgba(111,233,247,.42); }
.fcard.set-future-stars-1 .fc-chip { background: rgba(214,250,255,.16); color: #d6faff; }
.fcard.set-future-stars-1 .fc-chip.set { background: rgba(47,182,200,.55); color: #ffffff; }
/* The game prints this OVR as a dark blue hex with white numerals, which is
   the one chip in the app that is darker than its own ink. */
.hex.set-future-stars-1 {
  --chip: linear-gradient(160deg, #2a74b8, #11406f 55%, #06203f);
  --chip-ink: #eafaff;
  --frame: linear-gradient(122deg,
    #e4fbff 0%,  #06304f 11%, #4fd8e8 22%,
    #e4fbff 33%, #06304f 45%, #4fd8e8 56%,
    #e4fbff 68%, #06304f 80%, #4fd8e8 100%);
}

.fc-head { display: flex; align-items: flex-start; gap: 6px; }
/* Where a player photo would sit. It holds a blank silhouette rather than
   the player's initials — two letters in a picture frame read as data the
   card is telling you, not as a picture it is missing.
   `color` is still the portrait's own property because the silhouette is
   painted with currentColor: every set already tints this one line, so the
   figure recolours per card type without a rule of its own. */
.fc-portrait {
  flex: 1; min-width: 0; height: 54px;
  background: linear-gradient(180deg, rgba(0,0,0,.26), rgba(0,0,0,.42));
  border-radius: 6px 0 0 6px; position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  color: rgba(255,255,255,.5);
}
/* One icon for the whole app: the same --person mask the empty slot uses.
   Sized off the box rather than in pixels, so the shorter portrait on a
   prize card scales its figure to match instead of keeping a big one. */
.fc-portrait::after {
  content: ""; height: 74%; aspect-ratio: 1; flex: none;
  background: currentColor;
  -webkit-mask: var(--person) center / contain no-repeat;
  mask: var(--person) center / contain no-repeat;
}
.fc-rating { text-align: right; line-height: .92; padding: 2px 2px 0 0; position: relative; z-index: 1; }
.fc-rating b {
  display: block; font-family: var(--display); font-size: 29px;
  letter-spacing: .01em; font-variant-numeric: tabular-nums;
}
.fc-rating span {
  display: block; font-family: var(--cond); font-size: 14px; font-weight: 700;
  letter-spacing: .07em; margin-top: 1px;
}

.fc-name {
  text-align: center; margin-top: 7px; padding: 3px 2px;
  background: linear-gradient(180deg, rgba(255,255,255,.42), rgba(0,0,0,.12));
  border-top: 1px solid rgba(0,0,0,.3); border-bottom: 1px solid rgba(0,0,0,.3);
  line-height: 1.15; min-height: 34px;
  display: flex; flex-direction: column; justify-content: center;
}
.fc-first { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .09em; opacity: .74; }
.fc-last {
  font-family: var(--cond); font-size: 15px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .035em; word-break: break-word;
}

.fc-stats { display: grid; grid-template-columns: repeat(3, 1fr); margin-top: 6px; }
.fc-stats.empty { display: block; text-align: center; font-size: 10px; opacity: .6; }
.fc-stat { text-align: center; line-height: 1.1; border-right: 1px solid rgba(0,0,0,.18); }
.fc-stat:last-child { border-right: none; }
.fc-stat b { display: block; font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
.fc-stat span {
  display: block; font-family: var(--cond); font-size: 9px; font-weight: 600;
  letter-spacing: .1em; opacity: .74;
}

.fc-meta { display: flex; flex-wrap: wrap; gap: 3px; justify-content: center; margin-top: 7px; }
.fc-chip {
  font-size: 8.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px;
  background: rgba(0,0,0,.2); border-radius: 20px; padding: 1px 6px;
}
.fc-chip.set { background: rgba(0,0,0,.42); }
.fc-chip.book { background: #8a2f2f; color: #fff; }

.fc-actions {
  position: absolute; top: 5px; left: 5px; z-index: 2;
  display: flex; gap: 3px; opacity: 0; transition: opacity .12s;
}
.fcard:hover .fc-actions, .fcard:focus-within .fc-actions { opacity: 1; }
.fc-actions button {
  background: rgba(12,16,22,.86); border: 1px solid rgba(255,255,255,.24); color: #fff;
  border-radius: 5px; width: 21px; height: 21px; cursor: pointer;
  font-size: 11px; line-height: 1; padding: 0;
}
.fc-actions button:hover { background: var(--blue); border-color: var(--blue); }

.fcard.overflow { outline: 2px solid var(--red); outline-offset: -2px; }
.pick-card, .db-card { cursor: pointer; transition: transform .1s; }
.pick-card:hover, .db-card:hover { transform: translateY(-2px); }
.pick-card:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* empty slot — a visible backdrop so an open slot reads as clearly as a
   filled one instead of nearly disappearing into the panel behind it. */
/* The empty slot is the card silhouette blacked out with a person mark behind
   it — not a dashed box. Same shape as a real card so the board reads as a set
   of slots waiting to be filled. */
.fcard.empty {
  background: linear-gradient(170deg, #171717 0%, #0c0c0c 60%, #080808 100%);
  border: none;
  box-shadow: inset 0 0 0 3px #000;
  color: var(--text-2);
  align-items: center; justify-content: center; gap: 4px;
  text-align: center; cursor: pointer; font: inherit;
  padding: 14px 10px 22px;
}
.fcard.empty::before { background: none; }
/* The same figure a filled card shows, so an empty slot reads as the same
   kind of thing waiting to be filled. It is a flow item ordered ahead of the
   labels rather than a mark placed behind them: centred absolutely it sat on
   top of "Select player" and the two became unreadable. */
.fcard.empty::after {
  content: ""; order: -1; flex: none;
  width: 34px; height: 34px; margin-bottom: 2px;
  background: #3a3a3a;
  -webkit-mask: var(--person) center / contain no-repeat;
  mask: var(--person) center / contain no-repeat;
  pointer-events: none;
}
.fcard.empty:hover::after { background: #4f4f4f; }
.fcard.empty:hover {
  color: var(--text);
  background: linear-gradient(170deg, #1f1f1f 0%, #121212 60%, #0c0c0c 100%);
  box-shadow: inset 0 0 0 3px #000, inset 0 0 0 5px rgba(6,186,218,.35);
}
.fc-empty-pos {
  font-family: var(--cond); font-weight: 700; font-size: 15px;
  color: var(--blue); letter-spacing: .1em; text-transform: uppercase;
}
.fc-empty-label { font-size: 13px; font-weight: 700; }
.fc-empty-ovr { font-size: 15px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.fc-empty-ovr small { font-size: 9px; font-weight: 400; color: var(--muted); display: block; }
.fc-empty-meta { font-size: 9.5px; line-height: 1.25; }

/* ---------- requirements panel ---------- */
.req-panel-head {
  position: relative;
  padding: 14px 14px 10px;
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border-bottom: 1px solid var(--line);
  text-align: center;
}
.req-panel-name {
  font-size: 19px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .6px; line-height: 1.15; text-wrap: balance;
}
/* The head is centred, so the control is placed rather than pushed: the
   same affordance Details carries, in the same corner. */
.req-panel-head .collapse {
  position: absolute; top: 12px; right: 12px;
  background: none; border: none; color: var(--muted);
  cursor: pointer; font-size: 12px; text-decoration: underline;
  padding: 0; font-family: inherit;
}
.req-panel-head .collapse:hover { color: var(--text); }
.req-panel-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: 1.4px;
  color: var(--muted); margin-top: 6px;
  border-top: 1px solid var(--line); padding-top: 6px;
}

/* Requirement rows are pure black — recessed, not raised. When met, BOTH the
   label and the value turn the status lime; never the action green. */
.req-row-track {
  background: var(--void); border: none;
  border-radius: 14px; padding: 11px 15px; margin-bottom: 8px;
}
.req-row-track:last-child { margin-bottom: 0; }
.rrt-top { display: flex; align-items: baseline; gap: 10px; }
.rrt-label { flex: 1; font-size: 14px; font-weight: 700; line-height: 1.3; }
.rrt-count {
  font-variant-numeric: tabular-nums; font-size: 16px; font-weight: 700;
  color: var(--text); white-space: nowrap;
}
.rrt-count i { font-style: normal; opacity: .45; margin: 0 1px; }
.rrt-bar { height: 3px; background: #1c1c1c; border-radius: 3px; margin-top: 8px; overflow: hidden; }
.rrt-bar span {
  display: block; height: 100%; border-radius: 3px; transition: width .18s;
  background: linear-gradient(180deg, var(--red), var(--red-mid));
}
.rrt-note { font-size: 11px; color: var(--muted); margin-top: 5px; }
.req-row-track.complete .rrt-count,
.req-row-track.complete .rrt-label { color: var(--lime); }
.req-row-track.complete .rrt-bar span { background: var(--lime); }

/* ---------- status ---------- */
.verdict {
  padding: 10px 12px; border-radius: 8px; font-weight: 700; text-align: center;
  margin-bottom: 12px; border: 1px solid;
}
.verdict.pass { background: rgba(122,233,80,.12); border-color: var(--lime); color: var(--lime); }
.verdict.fail { background: rgba(242,210,31,.1); border-color: var(--amber); color: var(--amber); }
.verdict.error { background: rgba(222,36,36,.14); border-color: var(--red); color: var(--red); }

.stat-row { display: flex; justify-content: space-between; gap: 10px; padding: 5px 0; border-bottom: 1px solid var(--line); }
.stat-row:last-child { border-bottom: none; }
.stat-row .k { color: var(--muted); font-size: 12px; }
.stat-row .v { font-variant-numeric: tabular-nums; font-weight: 600; }
.v.good { color: var(--green); }
.v.bad { color: var(--red); }

.maths { background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 10px; margin: 12px 0; font-size: 12px; color: var(--muted); }
.maths code { color: var(--text); font-family: ui-monospace, Consolas, monospace; }
.maths div { padding: 2px 0; }

.warn-list { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.warn { font-size: 12px; padding: 8px 10px; border-radius: 7px; border-left: 3px solid; }
.warn.error { background: rgba(222,36,36,.12); border-color: var(--red); color: #ffc9c9; }
.warn.warn { background: rgba(242,210,31,.1); border-color: var(--amber); color: #ffe1b0; }
.warn.info { background: rgba(6,186,218,.12); border-color: var(--blue); color: #c9dcff; }

/* ---------- optimiser (coming soon) ---------- */
.optimiser-banner .panel-head { border-bottom: none; }
.optimiser-banner h2 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.soon-tag {
  font-size: 10px; font-weight: 800; letter-spacing: .6px;
  color: var(--amber); border: 1px solid rgba(242,210,31,.4);
  border-radius: 20px; padding: 2px 8px;
}

/* ---------- database filters ---------- */
.filters {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: end;
  padding: 12px; margin-bottom: 14px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
}
.filters label { min-width: 140px; }
.filters label.grow { flex: 1 1 220px; }
.filters label.narrow { min-width: 92px; width: 92px; }
.filters.compact { margin-bottom: 10px; padding: 10px; }

/* Three grouped sections, divided, each exactly two rows deep so the rows
   line up straight across all three. */
.filters-grouped { align-items: stretch; gap: 16px; }
.filters-grouped label { min-width: 0; width: auto; }
/* A select renders a couple of pixels taller than an input at the same
   padding, which would knock the second row out of line between sections. */
.filters input, .filters select, .sort-bar select, .ms-toggle { height: 34px; }

/* ---------- multi-select ---------- */
.multiselect { position: relative; width: 100%; }
.ms-toggle {
  display: flex; align-items: center; gap: 6px; width: 100%;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: 7px;
  padding: 7px 9px; font: inherit; font-size: 13px; cursor: pointer; text-align: left;
}
.ms-toggle:hover:not(:disabled) { border-color: var(--blue); }
.ms-toggle[aria-expanded="true"] { border-color: var(--blue); }
.ms-toggle:disabled { opacity: .5; cursor: not-allowed; border-style: dashed; background: rgba(255,255,255,.02); }
.ms-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.multiselect:not(.has-selection) .ms-label { color: var(--muted); }
.ms-caret { color: var(--muted); font-size: 10px; }

.ms-panel {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 20;
  min-width: 200px; max-height: 260px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--blue); border-radius: 8px;
  box-shadow: 0 10px 26px rgba(0,0,0,.5); padding: 6px;
}
.ms-clear {
  display: block; width: 100%; margin-bottom: 4px;
  background: none; border: none; border-bottom: 1px solid var(--line);
  color: var(--blue); font: inherit; font-size: 11.5px; text-align: left;
  padding: 4px 8px 7px; cursor: pointer;
}
.ms-clear:hover { color: var(--text); }
.ms-group + .ms-group { margin-top: 4px; }
.ms-group-label {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: .9px;
  color: var(--muted); padding: 6px 8px 3px;
}
/* Scoped under .ms-panel so these beat the column layout that .filters label
   otherwise imposes on every label inside a filter box. */
.ms-panel .ms-opt {
  display: flex; flex-direction: row; align-items: center; gap: 8px;
  padding: 5px 8px; border-radius: 6px; cursor: pointer; min-width: 0;
  font-size: 12.5px; color: var(--text); white-space: nowrap;
}
.ms-panel .ms-opt:hover { background: var(--panel-2); }
.ms-panel .ms-opt.on { color: var(--blue); font-weight: 600; }
.ms-panel .ms-opt input {
  width: 15px; height: 15px; flex: 0 0 15px; margin: 0; accent-color: var(--blue);
}
.ms-panel .ms-opt span { overflow: hidden; text-overflow: ellipsis; }

/* The dropdown has to escape the panel that would otherwise clip it. */
.panel.allow-overflow { overflow: visible; }
.panel.allow-overflow > .panel-head { border-radius: var(--radius) var(--radius) 0 0; }

/* ---------- action menu ---------- */
.menu-wrap { position: relative; }
.menu-caret { font-size: 9px; color: var(--muted); margin-left: 2px; }
#btnBatch[aria-expanded="true"] { border-color: var(--blue); color: var(--blue); }
#btnBatch[aria-expanded="true"] .menu-caret { color: var(--blue); }
.menu-list {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 25;
  width: 290px; padding: 6px;
  background: var(--panel); border: 1px solid var(--blue); border-radius: 10px;
  box-shadow: 0 14px 36px rgba(0,0,0,.55);
}
.menu-list button {
  display: block; width: 100%; text-align: left;
  background: none; border: none; border-radius: 7px;
  padding: 9px 10px; cursor: pointer; font: inherit; color: var(--text);
}
.menu-list button:hover { background: var(--panel-2); }
.menu-item-title { display: block; font-size: 14px; font-weight: 700; }
.menu-item-sub { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; line-height: 1.4; }
/* The one destructive item, set apart from the three routine ones above it
   so it can't be picked by momentum. */
.menu-list .menu-item-danger {
  margin-top: 6px; padding-top: 11px;
  border-top: 1px solid var(--line);
  border-radius: 0 0 7px 7px;
}
.menu-list .menu-item-danger .menu-item-title { color: var(--red); }
.menu-list .menu-item-danger:hover { background: rgba(222,36,36,.12); }
.filter-section { display: flex; flex-direction: column; gap: 10px; flex: 0 0 auto; width: 196px; }
.filter-section.wide { flex: 1 1 400px; width: auto; }
.filter-section.stat { width: 168px; }
.filter-row { display: flex; gap: 10px; }
.filter-row > label { flex: 1 1 0; min-width: 0; white-space: nowrap; }
.filter-divider { flex: 0 0 1px; align-self: stretch; background: var(--line); }

.db-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }

/* Selection brightens the rim; the fill barely moves. */
.view-toggle { display: flex; gap: 4px; margin-left: auto; }
.vt-btn {
  background: linear-gradient(180deg, #1f1f1f 0%, #111 55%, #070707 100%);
  border: 2px solid #3a3a3a; border-radius: 8px;
  color: var(--text-2); cursor: pointer;
  font-family: var(--cond); font-weight: 700; font-size: 13px;
  letter-spacing: .12em; text-transform: uppercase;
  padding: 5px 14px 4px;
}
.vt-btn:hover { color: var(--text); }
.vt-btn.is-on {
  color: var(--text); border-color: var(--blue);
  box-shadow: 0 0 12px rgba(6,186,218,.45);
}
.vt-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }

/* ---------- the dense list ----------
   The one place in the system with no gloss, no rim and no grain: flat panels
   separated by black gaps, exactly as the game draws League Standings. */
.db-list { display: block; }
.lhead, .lrow {
  display: grid;
  grid-template-columns: 46px minmax(120px, 1fr) 64px 56px repeat(3, 58px) 62px;
  align-items: center;
}
.lhead {
  background: var(--void); padding: 9px 12px 8px;
  border-radius: 6px 6px 0 0;
}
/* Column heads are body-face bold sentence case, not condensed caps — the
   game's own choice on its dense screens, and what keeps them readable. */
.lhead span { font-family: var(--body); font-weight: 700; font-size: 13.5px; color: var(--text); }
.lhead span.n { text-align: center; }
.lhead span.sorted::after {
  content: "\25BE"; display: block; font-size: 11px; line-height: .7; color: var(--text-2);
}
.lrows { display: flex; flex-direction: column; gap: 6px; background: var(--row-gap); padding: 6px 0; }
.lrow { background: var(--row); min-height: 54px; padding-right: 10px; }
.lrow:hover { background: #3d3d3d; }

/* The list's stand-in for a player photo, carrying the same blank figure the
   card portrait does. It stays grey whatever the card's rarity: this surface
   is deliberately flat and the hex chip beside it is what carries the colour,
   so tinting the figure here would only compete with it. */
.lthumb {
  width: 46px; align-self: stretch;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(150deg, #4a4a4a, #262626);
  color: rgba(255,255,255,.55);
}
.lthumb::after {
  content: ""; width: 26px; height: 26px; flex: none;
  background: currentColor;
  -webkit-mask: var(--person) center / contain no-repeat;
  mask: var(--person) center / contain no-repeat;
}
.lname { padding-left: 13px; line-height: 1.05; min-width: 0; }
.lname small { display: block; font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.lname b {
  font-family: var(--cond); font-weight: 700; font-size: 18px;
  letter-spacing: .03em; text-transform: uppercase;
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lovr { display: flex; justify-content: center; }
.lpos {
  text-align: center; font-family: var(--cond); font-weight: 700;
  font-size: 17px; letter-spacing: .05em;
}
.lstat { text-align: center; line-height: 1.02; }
.lstat span {
  display: block; font-family: var(--cond); font-weight: 600; font-size: 10px;
  letter-spacing: .1em; color: var(--text-2);
}
.lstat b { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.lactions { display: flex; gap: 4px; justify-content: flex-end; }
.lactions button {
  width: 26px; height: 26px; border-radius: 6px; cursor: pointer;
  background: rgba(0,0,0,.45); border: 1px solid #555; color: var(--text-2);
  font-size: 13px; line-height: 1; padding: 0;
}
.lactions button:hover { border-color: var(--blue); color: var(--text); }

/* OVR chip — the same rarity split as the card metal, and the same prestige
   frame. A hexagon can't carry an even border, so a framed chip nests its
   face inside the frame shape. */
.hex {
  --chip: linear-gradient(160deg, #ead499, #cea23e 55%, #b18120);
  --chip-ink: #1b1505;
  width: 42px; height: 38px;
  display: grid; place-items: center;
  clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
  font-weight: 700; font-size: 15px; font-variant-numeric: tabular-nums;
  color: var(--chip-ink); background: var(--chip);
}
.hex.silver   { --chip: linear-gradient(160deg, #e4e6e8, #b4bac0 55%, #72777a); --chip-ink: #17191b; }
.hex.bronze   { --chip: linear-gradient(160deg, #e0af86, #c58d59 55%, #885a30); --chip-ink: #23140a; }
.hex.raregold { --chip: linear-gradient(160deg, #f3df7d, #e4c960 55%, #9f8832); --chip-ink: #241d03; }
.hex.special { --chip: linear-gradient(160deg, #cfe2ff, #4d8ef7 55%, #1b4d9e); --chip-ink: #f4f8ff; }
.hex.framed { background: var(--frame); padding: 3px; }
.hex.framed > i {
  display: grid; place-items: center; width: 100%; height: 100%;
  clip-path: inherit; background: var(--chip); font-style: normal;
}

@media (max-width: 720px) {
  /* Let the row scroll rather than crushing the stat columns. */
  .db-list { overflow-x: auto; }
  .lhead, .lrow { min-width: 560px; }
}

/* Its own band between the filters and the cards, with room to grow. */
.sort-bar {
  display: flex; align-items: end; gap: 12px; flex-wrap: wrap;
  padding: 12px; margin-bottom: 14px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
}
.sort-bar label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12px; color: var(--muted); min-width: 190px;
}

@media (max-width: 900px) {
  .filter-section, .filter-section.wide, .filter-section.stat { flex: 1 1 100%; width: 100%; }
  .filter-divider { flex-basis: 100%; height: 1px; align-self: auto; }
}

/* ---------- modal ---------- */
.modal {
  position: fixed; inset: 0; background: rgba(0,0,0,.7);
  display: flex; align-items: center; justify-content: center; z-index: 50; padding: 16px;
}
/* Square panel, heavy black border, a red hairline on the TOP edge only, and
   torn edges doing the dividing — the game's dialog, exactly. */
.modal-box {
  background: var(--ground); border: 6px solid #000; border-top: 2px solid var(--red);
  border-radius: 0;
  padding: 0; width: 100%; max-width: 640px; max-height: 90vh; overflow-y: auto;
  box-shadow: 0 18px 50px rgba(0,0,0,.8);
}
.modal-box.wide { max-width: 940px; }
.modal-box.narrow { max-width: 460px; }
.modal-head {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  padding: 13px 16px 17px; margin-bottom: 0;
  background: var(--panel); background-image: var(--grain);
}
.modal-head::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 10px;
  background: var(--torn) repeat-x center / 100% 100%;
  pointer-events: none;
}
.modal-head h2 { flex: 1; }
/* The box itself has no padding so the head and footer bands can run edge to
   edge; every direct child takes the gutter instead. */
.modal-box > * { padding-inline: 16px; }
.modal-head { margin-bottom: 15px; }
.modal-box > *:last-child { padding-bottom: 16px; }
.modal-actions {
  position: relative;
  display: flex; gap: 10px; justify-content: flex-end; flex-wrap: wrap;
  margin-top: 15px; padding-block: 16px;
}
/* The footer's torn edge points the other way. */
.modal-actions::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 10px;
  background: var(--torn) repeat-x center / 100% 100%;
  transform: scaleY(-1); pointer-events: none;
}

/* The confirm stacks over whichever modal raised it. */
.confirm-modal { z-index: 60; }
.confirm-modal .modal-head h2 { font-size: 19px; line-height: 1.25; text-wrap: balance; }
.confirm-modal .confirm-body {
  margin: 0; padding: 26px 22px;
  font-size: 15px; font-weight: 700; line-height: 1.5; color: var(--text);
  white-space: pre-line; text-align: center;
  background: none; border: none;
}

/* ---------- first-run disclaimer ----------
   Long-form prose, which is the one place regular weight is allowed. The
   lead-in to each point is what carries the scan, so it takes the emphasis
   gold and the rest sits back in the secondary text colour. */
/* At phone height the last point falls below the fold, and a notice that
   looks finished when it is not is the one failure mode worth spending CSS
   on. A lift in the ground rather than the usual drop shadow: this surface
   is already near-black, so a dark shadow on it is invisible. The cue sits
   in the scroll layer and a ground-coloured cover rides the content layer
   above it, so it shows only while there is more below and is covered as
   you reach the end — no script, and nothing at all on a screen tall enough
   to fit the whole notice. */
.disclaimer-body {
  padding: 18px 22px 4px; max-height: 60vh; overflow-y: auto;
  background:
    linear-gradient(rgba(5,5,5,0), var(--ground) 76%) bottom / 100% 30px no-repeat local,
    linear-gradient(rgba(255,255,255,0), rgba(255,255,255,.13)) bottom / 100% 26px no-repeat scroll;
}
.disclaimer-body p {
  margin: 0 0 14px; font-size: 14.5px; font-weight: 500;
  line-height: 1.55; color: var(--text-2);
}
.disclaimer-body p:last-child { margin-bottom: 0; }
.disclaimer-body b {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 13px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-gold);
  margin-bottom: 2px;
}
.disclaimer-body i { font-style: italic; color: var(--text); }
/* One button, and it is the only way out, so it spans rather than sitting in
   the corner where a Cancel would have been. */
#disclaimerModal .modal-actions { justify-content: stretch; }
#disclaimerModal .modal-actions .btn { flex: 1; }

/* ---------- empty state ---------- */
.empty-state { text-align: center; padding: 70px 20px; color: var(--muted); }
.empty-state h2 { font-size: 22px; color: var(--text); margin-bottom: 8px; }
.empty-state p { margin: 0 auto 18px; max-width: 52ch; line-height: 1.6; }
.empty-state .empty-hint { font-size: 12px; margin: 18px auto 0; }
.empty-state .empty-hint strong { color: var(--blue); }

/* ---------- submit ----------
   Gold, not green. Green means "acquire" in FHC; gold means "commit for a
   reward", which is exactly what submitting a PTC does. Disabled desaturates
   to grey at full opacity rather than fading — see docs/brand.md. */
.submit-btn {
  --b-body: #242724; --b-mid: var(--dis-mid); --b-rim: var(--dis-rim);
  position: relative;
  display: block; width: 100%; margin-top: 14px;
  min-height: 48px; padding: 13px;
  font-family: var(--cond); font-weight: 700; font-size: 17px;
  letter-spacing: .11em; text-transform: uppercase; color: var(--text);
  text-shadow: 0 1px 3px rgba(0,0,0,.65);
  border: 2px solid var(--b-rim); border-radius: 9px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--b-rim) 52%, transparent) 0%,
    var(--b-mid) 42%, var(--b-body) 55%, #060606 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35), 0 2px 0 rgba(0,0,0,.5);
  cursor: not-allowed;
}
.submit-btn::after {
  content: ""; position: absolute; inset: 2px 3px auto; height: 38%;
  border-radius: 6px 6px 40% 40%;
  background: linear-gradient(180deg, rgba(255,255,255,.32), rgba(255,255,255,0));
  pointer-events: none;
}
.submit-btn.ready {
  --b-body: var(--gd-body); --b-mid: var(--gd-mid); --b-rim: var(--amber);
  cursor: pointer;
}
.submit-btn.ready:hover { filter: brightness(1.12); }
.submit-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

/* ---------- archived ---------- */
.archived-list { display: flex; flex-direction: column; gap: 8px; max-height: 60vh; overflow-y: auto; }
.archived-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 11px 13px;
}
.archived-row-main { flex: 1; min-width: 160px; }
.archived-row-name { font-weight: 700; font-size: 13px; }
.archived-row-meta { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.archived-row-actions { display: flex; gap: 6px; }

/* ---------- blog ---------- */
.blog-intro {
  margin: 4px 0 18px; max-width: 60ch;
  font-size: 15px; color: var(--muted); line-height: 1.5;
}
/* The hub tile, which is the game's one structural pattern: art, torn edge,
   dark label band, and a 2px red rule terminating the bottom edge. */
.spotlight {
  position: relative;
  display: block; width: 100%; text-align: left;
  background: var(--ground); background-image: var(--grain);
  border: none; border-bottom: 2px solid var(--red);
  border-radius: 0; overflow: hidden; cursor: pointer;
  padding-top: 96px;
}
.spotlight::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 96px; opacity: .5;
  background-color: var(--void);
  background-image:
    radial-gradient(circle at 3px 3px, rgba(222,36,36,.85) 1.4px, transparent 1.6px),
    repeating-linear-gradient(-45deg, #e8e8e8 0 22px, #e8e8e8 22px, #111111 22px 44px);
  background-size: 8px 8px, auto;
  background-blend-mode: multiply, normal;
}
.spotlight::after {
  content: ""; position: absolute; left: 0; right: 0; top: 90px; height: 12px;
  background: var(--torn) repeat-x center / 100% 100%;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,.7));
}
.spotlight:hover { filter: brightness(1.12); }
.spotlight:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
/* The game's flag chip: a cyan banner with a pointed right end. */
.spotlight-flag {
  position: relative; z-index: 1;
  display: inline-block; margin: 18px 0 0 26px;
  background: linear-gradient(180deg, #5cd2f5, #1a9fc4);
  color: #04222c; border: 1px solid #9fe6fb;
  font-family: var(--cond); font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .16em;
  padding: 3px 16px 2px 10px; border-radius: 3px;
  clip-path: polygon(0 0, 100% 0, calc(100% - 8px) 50%, 100% 100%, 0 100%);
}
.spotlight-body { position: relative; z-index: 1; padding: 10px 26px 28px; }
.spotlight-title {
  font-family: var(--display); font-size: 38px; line-height: 1.02;
  letter-spacing: .01em; text-transform: uppercase;
  margin: 12px 0; text-wrap: balance;
}
.spotlight-excerpt { color: var(--muted); font-size: 15.5px; line-height: 1.6; max-width: 62ch; margin: 0 0 16px; }
.read-link { color: var(--blue); font-weight: 600; font-size: 13px; }

.post-meta { display: flex; align-items: center; gap: 10px; font-size: 11px; color: var(--muted); flex-wrap: wrap; }
.post-tag {
  text-transform: uppercase; letter-spacing: .8px; font-weight: 700;
  color: var(--amber); border: 1px solid rgba(242,210,31,.4);
  border-radius: 20px; padding: 1px 8px; font-size: 10px;
}
/* A series takes its own colour, one hue apart from the next so the rail
   reads as sorted at a glance. Each is a ramp the app already owns — gold for
   news, red for the rules a board imposes, cyan for the tool, green for how
   to play it — with Future Stars borrowing the chevron cyan off its own card
   face so the tag and the cards it writes about match. Gold stays the
   fallback for a tag with no colour of its own. */
.post-tag.tag-update {
  color: var(--amber); border-color: rgba(242,210,31,.45);
  background: rgba(242,210,31,.09);
}
.post-tag.tag-requirements {
  color: #ff8a8c; border-color: rgba(255,138,140,.45);
  background: rgba(222,36,36,.12);
}
.post-tag.tag-solver {
  color: #3fd7ef; border-color: rgba(63,215,239,.45);
  background: rgba(6,186,218,.12);
}
.post-tag.tag-strategy {
  color: var(--lime); border-color: rgba(122,233,80,.45);
  background: rgba(122,233,80,.1);
}
.post-tag.tag-future-stars {
  color: #7feeff; border-color: rgba(127,238,255,.45);
  background: rgba(127,238,255,.09);
}
.post-author { margin-left: auto; }

/* Earlier articles sit in a scroll-snapped rail rather than a stacked list,
   so the archive reads as a secondary shelf under the spotlight. */
.carousel { margin-top: 26px; }
/* flex-wrap so nav, on the longest titles at a phone width, drops to its own
   line rather than squeezing the heading — without it, a flex item with no
   flex-basis still shrinks under pressure, and the longest heading here
   ("Progressive group challenges") was breaking itself across two lines
   rather than letting nav give way, at every width from 375 to 430px. */
.carousel-head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
/* No longer flex:1 — the heading used to stretch across the whole row so
   that whatever came after it (nav, or nothing) landed flush right. Now the
   collapse link sits right next to the name instead, so the heading takes
   only its own width; flex-shrink: 0 keeps it at that width even when the
   row is tight, which is what pushes nav to a second line instead. */
.carousel-head .sub { margin: 0; flex-shrink: 0; }
/* margin-left: auto pins nav to the row's trailing edge while there is room
   on the heading's own line, and to the start of its own line once wrapped —
   both read as "nav lives at the end", which is the point. */
.carousel-nav { display: flex; gap: 6px; margin-left: auto; }
/* Every section subheading carries one of these now — nav or not, the same
   muted underlined link the Requirements and Details panels already use for
   Hide/Show, so a minimise reads the same wherever it appears. Placed right
   after the heading text rather than at the far end of the row, so it reads
   as part of the heading rather than as a fourth nav control. */
.carousel-head .collapse {
  background: none; border: none; color: var(--muted);
  cursor: pointer; font-size: 12px; text-decoration: underline;
  padding: 0; font-family: inherit; white-space: nowrap;
}
.carousel-head .collapse:hover { color: var(--text); }
.carousel-btn {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  font-size: 17px; line-height: 1; cursor: pointer; font-family: inherit;
  display: flex; align-items: center; justify-content: center;
}
.carousel-btn:hover:not(:disabled) { border-color: var(--blue); color: var(--blue); }
.carousel-btn:disabled { opacity: .3; cursor: default; }

.blog-archive {
  display: flex; gap: 12px; margin-top: 10px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  padding-bottom: 10px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.blog-archive::-webkit-scrollbar { height: 6px; }
.blog-archive::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }

.post-card {
  flex: 0 0 268px; scroll-snap-align: start;
  display: flex; flex-direction: column; gap: 7px;
  background: #221f1f; background-image: var(--grain);
  border: none; border-bottom: 2px solid var(--red); border-radius: 0;
  padding: 14px 16px 13px; cursor: pointer; text-align: left;
}
.post-card:hover { filter: brightness(1.18); }
.post-card:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.post-card h3 {
  font-family: var(--cond); font-weight: 700; font-size: 17px;
  letter-spacing: .045em; text-transform: uppercase;
  margin: 0; line-height: 1.15; text-wrap: balance;
}
.post-card p {
  margin: 0; font-size: 12.5px; color: var(--muted); line-height: 1.5;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.post-card .read-link { margin-top: auto; padding-top: 4px; }

#blogArticle { display: flex; flex-direction: column; gap: 12px; align-items: start; }
.article { padding: 26px 28px; max-width: 760px; }
.article h1 { font-size: 30px; line-height: 1.18; margin: 10px 0 14px; text-wrap: balance; }
.article .standfirst { font-size: 16px; color: var(--text); border-left: 3px solid var(--blue); padding-left: 14px; margin: 0 0 20px; }
.article p { max-width: 65ch; margin: 0 0 15px; color: #cbd6e4; }
/* Lists sit tighter than paragraphs — a roster read as a block, not as a run
   of separate statements — and take the blue rule the standfirst uses so they
   read as one lifted item rather than a gap in the prose. */
.post-list {
  max-width: 65ch; margin: 0 0 18px; padding: 2px 0 2px 18px;
  list-style: none; border-left: 2px solid var(--cy-body);
}
.post-list li {
  position: relative; padding-left: 14px; margin-bottom: 5px;
  color: #cbd6e4; line-height: 1.5;
}
.post-list li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--blue);
}
.post-list li:last-child { margin-bottom: 0; }

/* A section break inside a long article. Condensed caps like every other
   section header in the app, with the red identity rule under it. */
.article .post-head {
  font-family: var(--cond); font-weight: 700; font-size: 17px;
  letter-spacing: .09em; text-transform: uppercase; color: var(--text);
  margin: 26px 0 12px; padding-bottom: 7px; max-width: 65ch;
  border-bottom: 2px solid var(--red);
}
.article .post-head:first-child { margin-top: 0; }

/* ---------- chalk (brand.md 7) ----------
   The WIZ half's drawing language, and the only thing in the stylesheet not
   measured from the game. The board is the <rect>, not the <svg>, so its rim
   and corner scale with the figure exactly as the doc specifies. No grain, no
   gloss, no metal: a board is a figure we drew, not a piece of chrome. */
/* A board is a figure, so it is narrower than the column it sits in. The cap
   also holds the drawing's proportions: everything inside is in user units, so
   an unbounded board would scale its own labels past the body copy. */
.chalk { margin: 0 0 22px; max-width: 460px; }
.chalk-board { display: block; width: 100%; height: auto; }
.chalk-rim { fill: var(--board); stroke: var(--board-rim); stroke-width: 2; }

/* One weight, no contrast. Round caps and joins are the tell — everything else
   in the system mitres — and the dash is reserved for a path not taken yet. */
.chalk-line, .chalk-point {
  fill: none; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.chalk-line { stroke: var(--chalk); }
.chalk-line.dashed { stroke-dasharray: 4 2.5; }
/* The coach's red pen, and the only red on a board. */
.chalk-point { stroke: var(--red); }
.chalk-label {
  fill: var(--chalk-2); font-family: var(--cond); font-weight: 700;
  font-size: 3.4px; letter-spacing: .06em; text-transform: uppercase;
}

/* The caption sits outside the board in the page's own type, because the board
   holds the drawing and the article holds the words about it. */
.chalk figcaption {
  margin-top: 9px; max-width: 65ch;
  font-size: 13px; line-height: 1.5; color: var(--muted);
}

/* A written board — chalk's other half (brand.md 7). A coach's chalkboard
   with a roster on it rather than a drawing: real text on the board surface,
   because fourteen names cannot be placed by hand in a viewBox and still wrap
   on a phone. The rule under each row is the dashed run, reused as a divider,
   and nothing the game owns is ever written here. */
.chalk-table { margin: 0 0 22px; max-width: 760px; }
.ct-board {
  background: var(--board);
  border: 2px solid var(--board-rim);
  border-radius: 6px;
  padding: 2px 20px 8px;
}
/* The board's own heading, underlined with a solid chalk run. */
.ct-head {
  font-family: var(--cond); font-weight: 700; font-size: 13px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--chalk-2);
  padding: 14px 0 8px; margin-bottom: 2px;
  border-bottom: 2px solid var(--chalk);
}
.ct-row {
  display: grid; grid-template-columns: 150px minmax(0, 1fr);
  gap: 5px 20px; align-items: baseline;
  padding: 13px 0 12px;
  border-bottom: 2px dashed rgba(155, 176, 162, .34);
}
.ct-row:last-child { border-bottom: none; }
.ct-tier {
  font-family: var(--cond); font-weight: 700; font-size: 15.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--chalk);
  line-height: 1.15;
}
/* The verdict is the annotation on the row, so it takes the secondary chalk
   and drops the tracking — it is a phrase, not a label. */
.ct-tier small {
  display: block; margin-top: 3px;
  font-family: var(--body); font-weight: 500; font-size: 12.5px;
  letter-spacing: 0; text-transform: none; color: var(--chalk-2);
  line-height: 1.35;
}
.ct-names {
  display: flex; flex-wrap: wrap; gap: 4px 16px;
  margin: 0; padding: 0; list-style: none;
}
.ct-names li {
  display: inline-flex; align-items: baseline; gap: 5px;
  color: var(--chalk); font-size: 14.5px; line-height: 1.35;
}
.ct-names b { font-weight: 700; }
.ct-names small {
  font-family: var(--cond); font-weight: 700; font-size: 11px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--chalk-2);
}
.chalk-table figcaption {
  margin-top: 9px; max-width: 65ch;
  font-size: 13px; line-height: 1.5; color: var(--muted);
}
/* A 150px label column plus a wrapping name list does not fit a phone, so the
   tier heads its own line and the names sit under it. */
@media (max-width: 620px) {
  .ct-board { padding-inline: 15px; }
  .ct-row { grid-template-columns: minmax(0, 1fr); gap: 7px; }
}

/* ---------- Boost Watch ----------
   The weekly Future Stars board. It is a dense list of many rows, so rule 3
   applies: flat panels on black gaps, no gloss and no grain. The head is the
   one lifted surface, and it carries the hazard stripe because this is the
   article's own section header rather than a table dropped into prose. */
.bw {
  margin: 0 0 22px; max-width: 760px;
  background: var(--ground); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.bw-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 13px 15px 16px;
  background-color: var(--void);
  background-image:
    radial-gradient(circle at 3px 3px, rgba(222,36,36,.5) 1.4px, transparent 1.6px),
    repeating-linear-gradient(-45deg, rgba(232,232,232,.10) 0 22px, rgba(17,17,17,.10) 22px 44px);
  background-size: 8px 8px, auto;
}
.bw-title { display: flex; flex-direction: column; gap: 1px; margin-right: auto; }
/* Cyan rather than the identity red: the band beneath it is already a red
   halftone, and the set this board tracks is the blue one. */
.bw-eyebrow {
  font-family: var(--cond); font-weight: 700; font-size: 11px;
  letter-spacing: .16em; text-transform: uppercase; color: #7feeff;
}
.bw-title strong {
  font-family: var(--display); font-size: 25px; letter-spacing: .01em;
  text-transform: uppercase; line-height: 1; color: var(--text);
}
.bw-tally { display: flex; gap: 8px; flex-wrap: wrap; }
/* Pure black tiles, the requirement-row treatment: the number is the content
   and the label underneath is the quiet part. */
.bw-count {
  min-width: 62px; padding: 5px 11px 6px; border-radius: 10px;
  background: rgba(0,0,0,.72); border: 1px solid var(--line); text-align: center;
}
.bw-count b {
  display: block; font-family: var(--display); font-size: 22px; line-height: 1.05;
  font-variant-numeric: tabular-nums; color: var(--text);
}
.bw-count span {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 9.5px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin-top: 1px;
}
.bw-count.played b { color: var(--lime); }
.bw-count.missed b { color: var(--red-peak); }

.bw-rows { display: flex; flex-direction: column; gap: 4px; padding: 10px; background: var(--void); }
/* The card's own chip leads the row, so the board reads as a row of cards.
   Status moves to a coloured left edge and the mark beside the verdict —
   a chip and a status pip side by side was two circles saying two things. */
.bw-row {
  display: grid; grid-template-columns: 42px minmax(0, 1fr) minmax(0, 1.05fr) auto;
  align-items: center; gap: 11px;
  background: var(--row); border-radius: 9px; padding: 8px 11px;
  border-left: 3px solid var(--dis-mid);
}
.bw-row.played { border-left-color: var(--lime); }
.bw-row.missed, .bw-row.out { border-left-color: var(--red); }
.bw-row .hex { width: 42px; height: 38px; font-size: 14px; }
.bw-mark { margin-right: 5px; font-weight: 700; }

.bw-who { min-width: 0; }
.bw-who b { display: block; font-size: 14px; font-weight: 700; line-height: 1.2; }
.bw-who small {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 10px;
  letter-spacing: .13em; text-transform: uppercase; color: var(--muted); margin-top: 1px;
}
.bw-verdict { min-width: 0; font-size: 12.5px; font-weight: 700; color: var(--text-2); }
.bw-verdict small {
  display: block; font-size: 11px; font-weight: 500; color: var(--muted);
  line-height: 1.35; margin-top: 1px;
}
.bw-row.played .bw-verdict { color: var(--lime); }
.bw-row.missed .bw-verdict,
.bw-row.out .bw-verdict { color: var(--red-peak); }
.bw-row.pending .bw-verdict { color: var(--muted); }

.bw-tier { text-align: right; }
.bw-badge {
  display: inline-block; min-width: 34px; padding: 2px 8px; border-radius: 7px;
  font-family: var(--display); font-size: 16px; line-height: 1.25;
  font-variant-numeric: tabular-nums;
}
/* The boost is one scale with three sizes, so it gets one hue at three
   strengths rather than three different colours — the ladder is readable
   without reading the number. Red is the exception because a lost month is
   a different kind of outcome, not a smaller boost. */
.bw-badge.t3 { background: linear-gradient(180deg, #8cf063, #3caf27); color: #05230a; }
.bw-badge.t2 { background: linear-gradient(180deg, #3caf27, #177a06); color: #f2fff0; }
.bw-badge.t1 { background: linear-gradient(180deg, #17720a, #0b3b02); color: #8fdc77; }
.bw-badge.t0 { background: rgba(222,36,36,.16); color: var(--red-peak); box-shadow: inset 0 0 0 1px rgba(222,36,36,.45); }
.bw-badge.tx { background: none; color: var(--dim); box-shadow: inset 0 0 0 1px var(--line); }
.bw-tier small {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 8.5px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted); margin-top: 2px;
}
.bw figcaption {
  padding: 11px 15px 13px; font-size: 11.5px; color: var(--muted);
  line-height: 1.5; border-top: 1px solid var(--line);
}

/* A four-column row will not survive a phone. The verdict drops under the
   name and the badge holds the right edge, which keeps the one thing the
   board exists to show — who played and what it is worth — on one line. */
@media (max-width: 620px) {
  .bw-row {
    grid-template-columns: 42px minmax(0, 1fr) auto;
    row-gap: 4px;
  }
  .bw-verdict { grid-column: 2 / 4; }
  .bw-tier { grid-row: 1; grid-column: 3; }
}

@media (max-width: 560px) {
  .view { padding: 12px; }
  .spotlight-title { font-size: 21px; }
  .article { padding: 18px; }
  .article h1 { font-size: 24px; }
  .canvas, .card-grid { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 8px; }
  .canvas { --slot-min: 128px; --slot-gap: 8px; }
}

/* ---------- help ---------- */
/* The game's own help button: a glossy cyan square with a bevelled rim and a
   heavy white "?", not a flat material circle. Same gloss recipe as .btn —
   vertical ramp, specular band over the top 40%, bright rim. */
.help-fab {
  position: fixed; right: 20px; bottom: 20px; z-index: 40;
  width: 52px; height: 52px; padding: 0;
  border: 2px solid var(--blue); border-radius: 12px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--blue) 52%, transparent) 0%,
    var(--cy-mid) 42%, var(--cy-body) 55%, #041016 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 4px 0 rgba(0,0,0,.55), 0 8px 22px rgba(0,0,0,.5);
  cursor: pointer; font: inherit;
  display: grid; place-items: center;
}
.help-fab::after {
  content: ""; position: absolute; inset: 3px 4px auto; height: 38%;
  border-radius: 8px 8px 40% 40%;
  background: linear-gradient(180deg, rgba(255,255,255,.36), rgba(255,255,255,0));
  pointer-events: none;
}
.help-fab-face {
  position: relative; z-index: 1;
  font-family: var(--display); font-size: 26px; line-height: 1;
  color: #fff; text-shadow: 0 2px 4px rgba(0,0,0,.7);
}
.help-fab:hover { filter: brightness(1.14); }
.help-fab:active { transform: translateY(2px); box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 2px 0 rgba(0,0,0,.55); }
.help-fab:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
/* Open reads as pressed in, and switches to the red identity ramp so it is
   obvious which state you are in. */
.help-fab[aria-expanded="true"] {
  border-color: var(--red);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--red) 52%, transparent) 0%,
    var(--red-mid) 42%, var(--red-body) 55%, #160202 100%);
}

/* ---------- ad slots ----------
   Reserved before there is anything to put in them, which is the point: an ad
   that arrives into an unreserved box shoves the page down under whoever is
   reading it. Declaring the height now means the unit drops into a space that
   already existed.

   Two placements, both chosen so nothing an ad does can interrupt a task:
   under a finished article, and under the board's result panels rather than
   between them. Deliberately not inside a card rail — a rail is a horizontal
   scroller with its own nav arrows, and an ad in one would fight both.

   To enable a slot: drop the ad code inside it and remove `hidden` from the
   element. The size is the standard responsive leaderboard: 90px on a phone,
   where only a mobile banner fits, and 250px above that. */
.ad-slot {
  display: block; width: 100%; max-width: 760px;
  margin: 0 0 16px;
  background: var(--ground);
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden;
}
.ad-leaderboard { min-height: 250px; }
@media (max-width: 560px) {
  .ad-leaderboard { min-height: 90px; }
}
/* Says what the box is, for anyone wondering why there is a gap, and for the
   disclosure rules most ad networks impose. Sits behind the unit, so a filled
   slot covers it. */
.ad-slot::before {
  content: "Advertisement";
  display: block; padding: 10px 14px;
  font-family: var(--cond); font-weight: 600; font-size: 10px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--dim);
}

/* ---------- site footer ----------
   The quietest thing on the page: flat, muted, no gloss. It carries the
   non-affiliation line on every tab rather than only in the first-run notice,
   which is read once and then never again. */
.site-footer {
  max-width: 1500px; margin: 0 auto; padding: 22px 16px 28px;
  border-top: 1px solid var(--line);
}
.site-footer-note {
  margin: 0 0 12px; max-width: 78ch;
  font-size: 12.5px; line-height: 1.55; color: var(--muted);
}
.site-footer-note b { color: var(--text-2); }
.site-footer-note i { font-style: italic; }
.site-footer-links { display: flex; flex-wrap: wrap; gap: 18px; }
.site-footer-links a {
  font-family: var(--cond); font-weight: 700; font-size: 12.5px;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-2); text-decoration: none;
}
.site-footer-links a:hover { color: var(--blue); }
.site-footer-links a:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

/* ---------- help ----------
   Advisory content inverts: tooltips, coaching hints and empty states are
   light boxes with dark text in the game, in three separate contexts. Making
   the help panel light both matches FHC and separates it from the app chrome. */
.help-panel {
  position: fixed; right: 20px; bottom: 78px; z-index: 44;
  width: min(370px, calc(100vw - 32px)); max-height: min(560px, calc(100vh - 120px));
  display: flex; flex-direction: column;
  background: #eaeaeb; color: #141414;
  border: 2px solid #000; border-radius: 10px;
  box-shadow: 0 16px 44px rgba(0,0,0,.65); overflow: hidden;
}
.help-head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 14px; background: #d5d5d7; border-bottom: 1px solid #b9b9bb;
}
.help-heading { flex: 1; }
.help-title {
  font-family: var(--cond); font-weight: 700; font-size: 16px;
  letter-spacing: .07em; text-transform: uppercase; color: #101010;
}
.help-sub { font-size: 12px; color: #5a5a5c; }
.help-head .icon-btn { color: #4a4a4c; }
.help-head .icon-btn:hover { color: #000; }

.help-thread {
  flex: 1; overflow-y: auto; padding: 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.help-msg { max-width: 92%; font-size: 13.5px; line-height: 1.5; }
.help-msg p { margin: 0 0 8px; }
.help-msg p:last-child { margin-bottom: 0; }
.help-msg.bot {
  align-self: flex-start; background: #fff; color: #141414;
  border: 1px solid #c6c6c8; border-radius: 10px 10px 10px 3px; padding: 10px 12px;
}
.help-msg.you {
  align-self: flex-end;
  background: linear-gradient(180deg, #0fc2df, #07505c); color: #fff;
  border: 1px solid #06bada;
  border-radius: 10px 10px 3px 10px; padding: 8px 12px; font-weight: 700;
}

.help-choices {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 12px 12px; border-top: 1px solid #b9b9bb; background: #dededf;
  max-height: 210px; overflow-y: auto;
}
.help-chip {
  text-align: left; background: #fff; color: #141414;
  border: 1px solid #b0b0b2; border-radius: 8px;
  padding: 8px 13px; font-family: var(--body); font-size: 13px; font-weight: 600;
  cursor: pointer;
}
.help-chip:hover { border-color: #0b7f92; background: #f3fdff; }
.help-chip.tour {
  border-color: #0b7f92; color: #064854; font-weight: 700;
  background: linear-gradient(180deg, #e6fbff, #cdf2f9);
}
.help-chip.tour:hover { background: #d8f6fc; }
.help-chip:focus-visible { outline: 2px solid #0b7f92; outline-offset: 2px; }

/* ---------- guided tour ---------- */
.tour-layer { position: fixed; inset: 0; z-index: 46; }
.tour-catch { position: absolute; inset: 0; }
/* One element makes the whole effect: the box is transparent, and an
   enormous spread shadow darkens everything outside it. */
.tour-spot {
  position: absolute; border-radius: 10px;
  box-shadow: 0 0 0 9999px rgba(6,9,14,.78);
  outline: 2px solid var(--blue); outline-offset: 0;
  pointer-events: none; transition: all .22s ease;
}
.tour-bubble {
  position: absolute;
  background: var(--panel); border: 1px solid var(--blue); border-radius: 12px;
  box-shadow: 0 14px 40px rgba(0,0,0,.6); padding: 14px 16px 12px;
}
.tour-count {
  font-size: 10px; text-transform: uppercase; letter-spacing: 1.1px; color: var(--muted);
}
.tour-title { font-size: 15px; margin: 5px 0 6px; }
.tour-text { margin: 0; font-size: 12.5px; line-height: 1.55; color: #cbd6e4; }
.tour-actions { display: flex; gap: 6px; margin-top: 13px; align-items: center; }
.tour-actions .tour-skip { margin-left: auto; color: var(--muted); }
.tour-hint {
  margin-top: 9px; font-size: 10px; text-transform: uppercase;
  letter-spacing: 1.1px; color: var(--dim); text-align: center;
}
/* Deliberately not locking body scroll while touring — steps further down
   the page need scrollIntoView to reach them, and the spotlight re-places
   itself on scroll anyway. */

@media (max-width: 560px) {
  .help-fab { right: 14px; bottom: 14px; width: 44px; height: 44px; font-size: 20px; }
  .help-panel { right: 14px; left: 14px; bottom: 68px; width: auto; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; scroll-behavior: auto !important; }
}

/* ---------- board actions ----------
   The game puts the reward next to the commit rather than in a detail panel,
   so the two sit on one row with Submit taking the room. */
.board-actions { display: flex; align-items: stretch; gap: 10px; margin-top: 14px; }
.board-actions .submit-btn { flex: 1 1 auto; margin-top: 0; }
.board-actions #btnViewReward { flex: 0 0 auto; min-height: 48px; padding: 0 18px; }
@media (max-width: 560px) {
  .board-actions { flex-direction: column; }
}

/* ---------- popular PTCs ----------
   Hub tiles on a scroll-snapped rail: art, torn edge, label band, red rule. */
.template-rail {
  display: flex; gap: 12px; margin-top: 10px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  padding-bottom: 10px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.template-rail::-webkit-scrollbar { height: 6px; }
.template-rail::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }

.tpl-card {
  flex: 0 0 254px; scroll-snap-align: start;
  display: flex; flex-direction: column;
  background: var(--ground); background-image: var(--grain);
  border: none; border-bottom: 2px solid var(--red); border-radius: 0;
  cursor: pointer; text-align: left; overflow: hidden;
}
.tpl-card:hover { filter: brightness(1.14); }
.tpl-card:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* The tile's art is the metal it pays out in, with the entry bar on it. */
.tpl-art {
  --metal-1: #e9edf2; --metal-2: #a8b3c0; --metal-3: #78828f; --ink: #1e242c;
  /* A row, not a stack: the entry average on the left and the repeat badge on
     the right, so neither has to sit on top of the other. */
  position: relative; height: 74px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 0 13px 8px;
  background: linear-gradient(158deg, var(--metal-1) 0%, var(--metal-2) 50%, var(--metal-3) 100%);
  color: var(--ink);
}
.tpl-art::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 10px;
  background: var(--torn) repeat-x center / 100% 100%;
}
/* The reward floor, set the way the reward modal and a group's prize slot set
   it — the display face with the plus riding small beside it, so a pack reads
   the same wherever it appears. */
.tpl-prize { display: inline-flex; align-items: baseline; gap: 6px; }
.tpl-floor { font-family: var(--display); font-size: 34px; line-height: 1; letter-spacing: .01em; }
.tpl-floor i { font-style: normal; font-size: 21px; margin-left: 1px; }
.tpl-prize-label {
  font-family: var(--cond); font-weight: 700; font-size: 13px;
  letter-spacing: .16em; text-transform: uppercase; opacity: .72;
}
/* A tile with no recorded reward takes no metal at all — flat grey, so it
   never reads as a Silver pack it is not. */
.tpl-art.unknown {
  background: linear-gradient(158deg, #4a4d4a 0%, #303230 55%, #1e201e 100%);
  color: var(--text-2);
}

.tpl-band {
  /* The name has the band to itself, and still ellipses rather than wraps so
     a long one can never push a tile's body out of line along the rail. */
  display: flex; align-items: baseline; gap: 10px;
  background: #221f1f; background-image: var(--grain);
  padding: 11px 14px 10px; min-height: 44px;
}
.tpl-band b {
  font-family: var(--cond); font-weight: 700; font-size: 17px;
  letter-spacing: .09em; text-transform: uppercase; line-height: 1.12;
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Badged on the art rather than in the band, so the name gets the full
   width and a two-word tier never has to wrap or truncate. Repeatable is the
   good news and takes the status lime; one-time is stated, not warned about. */
.tpl-repeat {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; gap: 4px;
  background: rgba(0,0,0,.72); border-radius: 20px;
  padding: 3px 9px 2px;
  font-family: var(--cond); font-weight: 700; font-size: 11px;
  letter-spacing: .1em; text-transform: uppercase; white-space: nowrap;
}
.tpl-repeat i { font-style: normal; font-size: 13px; line-height: 1; }
.tpl-repeat.is-repeat { color: var(--lime); }
.tpl-repeat.is-once { color: var(--text-2); }

.tpl-body { display: flex; flex-direction: column; gap: 10px; padding: 11px 14px 13px; flex: 1; }
.tpl-blurb { margin: 0; font-size: 12.5px; font-weight: 500; color: var(--text-2); line-height: 1.45; }
.tpl-facts { display: flex; gap: 8px; margin-top: auto; }
.tpl-facts div {
  flex: 1; background: var(--void); border-radius: 8px; padding: 7px 9px 6px;
}
.tpl-facts span {
  display: block; font-family: var(--cond); font-weight: 600; font-size: 10px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted);
}
.tpl-facts b { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---------- reward ---------- */
.reward-body { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.reward-tile { display: flex; flex-direction: column; align-items: center; gap: 10px; }
/* The pack art reuses the card silhouette, so a reward reads as the thing it
   hands you rather than as a generic badge. */
.fcard.reward-pack {
  width: 132px; min-height: 158px; padding: 0;
  align-items: center; justify-content: center;
}
.fcard.reward-pack::before { background: linear-gradient(112deg, rgba(255,255,255,.36) 0 18%, rgba(255,255,255,0) 36%); }
.reward-floor {
  position: relative; z-index: 1;
  font-family: var(--display); font-size: 46px; line-height: 1;
  color: var(--ink); letter-spacing: .01em;
}
.reward-floor i { font-style: normal; font-size: 28px; margin-left: 2px; }
.reward-name {
  font-family: var(--cond); font-weight: 700; font-size: 19px;
  letter-spacing: .07em; text-transform: uppercase;
}
.reward-note {
  margin: 6px 0 0; max-width: 34ch; text-align: center;
  font-size: 13.5px; font-weight: 500; color: var(--text-2); line-height: 1.5;
}
.reward-note strong { color: var(--text); }

/* ---------- challenge groups ----------
   A set of one-time boards with one shared unlock. Panel anatomy follows the
   game's: scuffed header, torn edge, then the content. */
.groups { margin-top: 28px; }

/* A rail rather than a stack: there will eventually be far more of these than
   fit down a page. Same mechanics as the blog archive and the template rail —
   native scroll, snap points, arrows in the section head. */
.group-rail {
  display: flex; gap: 12px; margin-top: 10px; align-items: start;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  padding-bottom: 10px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.group-rail::-webkit-scrollbar { height: 6px; }
.group-rail::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
.rail-empty { padding: 22px 16px; background: var(--panel); flex: 1 1 auto; }

.group-card {
  /* Wide enough that a member name — "PLATINUM CAREER #1" is the longest —
     sits on one line beside its button, and two cards still fit a desktop. */
  flex: 0 0 min(680px, 88vw); scroll-snap-align: start;
  background: var(--ground); background-image: var(--grain);
  border-bottom: 2px solid var(--red);
}
.group-card.is-claimed { opacity: .82; }
.group-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  background: var(--panel); background-image: var(--grain);
  padding: 12px 16px 14px;
}
.group-head h2 { font-size: 21px; }
.group-progress {
  font-family: var(--body); font-weight: 700; font-size: 17px;
  font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap;
}
.group-progress.is-complete { color: var(--lime); }

.group-body {
  display: grid; grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
  gap: 16px; padding: 16px 14px 18px;
}
@media (max-width: 760px) { .group-body { grid-template-columns: minmax(0, 1fr); } }

.group-prize { display: flex; gap: 14px; align-items: flex-start; }
.fcard.prize-card { flex: 0 0 118px; min-height: 152px; padding: 6px 6px 14px; }
.fcard.prize-card .fc-portrait { height: 40px; }
.fcard.prize-card .fc-rating b { font-size: 23px; }
/* A surname has to fit on one line at this width — "LINDSTROM" broke across
   two as "LINDSTRO / M", which reads as a different name. The condensed face
   is narrow enough that a step down buys the room without looking smaller. */
.fcard.prize-card .fc-last { font-size: 13px; letter-spacing: .01em; }
/* A pack in the prize slot: the same footprint a prize card has, with the
   floor stacked over the pack's name instead of a portrait and stat line. */
.fcard.prize-pack { flex: 0 0 118px; min-height: 152px; gap: 8px; padding: 10px 8px 18px; }
.fcard.prize-pack .reward-floor { font-size: 38px; }
.fcard.prize-pack .reward-floor i { font-size: 22px; }
.prize-pack-name {
  position: relative; z-index: 1; text-align: center;
  font-family: var(--cond); font-weight: 700; font-size: 12px;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink);
}
.group-prize-copy { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.group-note { margin: 0; font-size: 14px; font-weight: 700; line-height: 1.35; }
.group-claim-note { margin: 0; font-size: 12.5px; font-weight: 500; color: var(--muted); line-height: 1.45; }
.group-prize-copy .btn { align-self: flex-start; margin-top: 2px; }

/* Member rows are flat on black gaps — the dense-surface rule again. */
.group-members { display: flex; flex-direction: column; gap: 6px; }
.gm-row {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: center; gap: 12px;
  background: var(--row); padding: 10px 12px; min-height: 52px;
}
.gm-row.is-done { background: #2b2b2b; }
.gm-tick { color: var(--lime); font-size: 16px; font-weight: 700; text-align: center; }
.gm-name {
  font-family: var(--cond); font-weight: 700; font-size: 17px;
  letter-spacing: .05em; text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gm-row.is-done .gm-name { color: var(--lime); }
.gm-done {
  font-family: var(--cond); font-weight: 700; font-size: 12.5px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--lime);
}
@media (max-width: 560px) {
  /* A name and one button fit a phone row as they are; only the name needs
     permission to wrap. */
  .gm-name { white-space: normal; }
}

/* ---------- PTC Database ----------
   Three reference sections. The rail on the PTC Tool tab sells a challenge;
   these rows state it, so they follow the flat dense pattern rather than the
   glossy tile one. */
.pdb-section { margin-top: 22px; }
/* Every section heading now sits in a .carousel-head (nav or not), which
   already zeroes the heading's own margin — so the 10px gap above each
   content block is this rule, matching what the four rails already carry
   on themselves (.template-rail, .group-rail, .blog-archive, .rank-ladder). */
.chal-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; background: var(--row-gap); padding: 6px; }

.chal-row {
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr) auto auto;
  align-items: center; gap: 14px;
  background: var(--row); padding: 12px 14px; min-height: 66px;
}
.chal-row:hover { background: #3d3d3d; }

/* The average, on the reward's metal — the one piece of colour in the row,
   and the number you scan for. */
.chal-metal {
  --metal-1: #eef2f7; --metal-2: #adb8c5; --metal-3: #7b8794; --ink: #1c222a;
  align-self: stretch; min-height: 48px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  border-radius: 6px; color: var(--ink);
  background: linear-gradient(158deg, var(--metal-1), var(--metal-2) 55%, var(--metal-3));
  box-shadow: inset 0 0 0 2px rgba(0,0,0,.35);
}
.chal-metal b {
  font-family: var(--display); font-size: 22px; line-height: .9;
  font-variant-numeric: tabular-nums;
}
.chal-metal b i { font-style: normal; font-size: 15px; margin-left: 1px; }
/* No recorded reward, and every limited-time board: a flat grey tile carrying
   a question mark rather than a metal it has not earned. */
.chal-metal.unknown {
  --metal-1: #4a4d4a; --metal-2: #303230; --metal-3: #1e201e; --ink: #b9bdb9;
  box-shadow: inset 0 0 0 2px rgba(0,0,0,.5);
}
.chal-metal span {
  font-family: var(--cond); font-weight: 700; font-size: 9px;
  letter-spacing: .14em; text-transform: uppercase; opacity: .75;
}

.chal-main { min-width: 0; }
.chal-name {
  font-family: var(--cond); font-weight: 700; font-size: 18px;
  letter-spacing: .04em; text-transform: uppercase; display: block; line-height: 1.1;
}
.chal-blurb { margin: 3px 0 0; font-size: 12.5px; color: var(--text-2); line-height: 1.4; }
.chal-pos {
  margin: 4px 0 0; font-family: var(--cond); font-weight: 600; font-size: 12px;
  letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
}

.chal-facts { display: flex; gap: 18px; }
.chal-facts > div { text-align: center; line-height: 1.05; }
.chal-facts span {
  display: block; font-family: var(--cond); font-weight: 600; font-size: 9.5px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-2);
  /* "Total OVR" is two words and wraps the moment the row is tight, which
     knocks its value out of line with the columns either side of it. */
  white-space: nowrap;
}
.chal-facts b { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }

.chal-action { display: flex; align-items: center; gap: 10px; }
@media (max-width: 760px) {
  .chal-row {
    grid-template-columns: 58px minmax(0, 1fr) auto;
    grid-template-areas: 'metal main action' 'metal facts action';
    row-gap: 8px; gap: 12px;
  }
  .chal-metal { grid-area: metal; }
  .chal-main { grid-area: main; }
  .chal-facts { grid-area: facts; gap: 14px; }
  .chal-action { grid-area: action; flex-direction: column; gap: 6px; }
  .chal-blurb { display: none; }
}

/* ---------- community leaderboard ----------
   The game's League Standings, exactly: sentence-case column heads on black,
   flat grey rows floating in a black gutter, no gloss and no grain. */
.leaderboard { display: block; }
.lb-head, .lb-row {
  display: grid;
  grid-template-columns: 52px minmax(110px, 1fr) 168px repeat(4, 76px);
  align-items: center;
}
.lb-head {
  background: var(--void); padding: 10px 14px 9px; border-radius: 6px 6px 0 0;
}
.lb-head span { font-family: var(--body); font-weight: 700; font-size: 13.5px; color: var(--text); }
.lb-head span.n { text-align: center; }

/* Capped at five rows so the panel stays a fixed size however long the board
   gets, and scrolls past that. --lb-row and --lb-gap are the row box and the
   gap between rows; the sum is exactly five rows plus the container padding,
   so the sixth is cut mid-row and visibly invites a scroll. */
/* The whole board scrolls sideways as one piece. This has to be the
   horizontal scroller rather than .lb-rows, because overflow-y: auto down
   there promotes overflow-x from visible to auto: the rows became their own
   horizontal scroller and slid out from under the column heads, which stayed
   put. Vertical stays on .lb-rows so the head is never scrolled away. */
.lb-scroll { overflow-x: auto; overscroll-behavior-x: contain; }
.lb-rows {
  --lb-row: 56px; --lb-gap: 6px;
  display: flex; flex-direction: column; gap: var(--lb-gap);
  background: var(--row-gap); padding: 6px 0;
  max-height: calc((var(--lb-row) * 5) + (var(--lb-gap) * 4) + 12px);
  overflow-y: auto; overflow-x: clip; overscroll-behavior-y: contain;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.lb-rows::-webkit-scrollbar { width: 6px; }
.lb-rows::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
.lb-row { flex: none; background: var(--row); height: var(--lb-row); padding: 0 14px; }
.lb-row:hover { background: #3d3d3d; }

.lb-pos {
  font-family: var(--display); font-size: 19px; text-align: center;
  font-variant-numeric: tabular-nums; color: var(--text-2);
}
/* Top three get the game's shield; everyone else keeps a plain numeral. */
.lb-pos.medal {
  width: 34px; height: 38px; margin: 0 auto;
  display: grid; place-items: center; font-size: 16px;
  clip-path: polygon(50% 0, 100% 18%, 100% 72%, 50% 100%, 0 72%, 0 18%);
  color: #1a1503; text-shadow: none;
}
.lb-pos.m1 { background: linear-gradient(160deg, #ffe9a8, #d8a92e 55%, #9b7315); }
.lb-pos.m2 { background: linear-gradient(160deg, #f2f4f6, #b4bac0 55%, #767c82); }
.lb-pos.m3 { background: linear-gradient(160deg, #f0c39a, #c5854b 55%, #85552a); }

.lb-user {
  font-family: var(--cond); font-weight: 700; font-size: 18px;
  letter-spacing: .04em; text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lb-n {
  text-align: center; font-size: 16px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--text-2);
}
.lb-total { color: var(--text); }

/* ---------- community rank ----------
   Ten tiers that have to read as a ladder at a glance, in a table where the
   badge is 30px wide. Hue alone cannot carry that far, so the treatment
   escalates with it, in four steps:

     1-3   flat face, dull rim, no frame        — earned nothing yet
     4-6   metal face, brighter rim             — the metals begin
     7-8   metallic frame around the face       — the prestige frame arrives
     9-10  frame plus a glow                    — and only the top two glow

   Every colour is an existing brand stop (see docs/brand.md): iron, bronze,
   silver, gold, cyan, platinum white, red. Nothing new enters the system.

   The badge is a frame hexagon with a face hexagon nested inside, because a
   hexagon cannot carry an even border — the same construction as the OVR chip.
*/
.rank-chip { display: inline-flex; align-items: center; gap: 9px; min-width: 0; }
.rank-badge {
  --rk-frame: #2a2a2a;
  --rk-face: linear-gradient(160deg, #4a4d4a, #303230);
  --rk-ink: #cfd2cf;
  --rk-glow: none;
  flex: none; position: relative;
  width: 30px; height: 27px; padding: 2px;
  display: grid; place-items: center;
  clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
  background: var(--rk-frame);
  filter: var(--rk-glow);
}
.rank-badge > i {
  display: grid; place-items: center; width: 100%; height: 100%;
  clip-path: inherit; font-style: normal;
  background: var(--rk-face); color: var(--rk-ink);
  font-family: var(--cond); font-weight: 700; font-size: 10.5px;
  letter-spacing: .04em;
}
.rank-badge.is-unknown { background: var(--dim); color: var(--text-2); }

/* 1-3 — iron and bronze. Flat faces, plain rims, nothing to show off. */
.rk-1 { --rk-frame: #4c4f4c; --rk-face: linear-gradient(160deg, #3c3f3c, #232523); --rk-ink: #b9bdb9; }
.rk-2 { --rk-frame: #7a5427; --rk-face: linear-gradient(160deg, #6b4a22, #3b2711); --rk-ink: #e6c9a6; }
.rk-3 { --rk-frame: #b1783d; --rk-face: linear-gradient(160deg, #c78f57, #7a4c22); --rk-ink: #2e1d0c; }

/* 4-6 — the metals. A real gradient face and a bright rim. */
.rk-4 { --rk-frame: #8d979f; --rk-face: linear-gradient(160deg, #9aa4ae, #5c656e); --rk-ink: #12161a; }
.rk-5 { --rk-frame: #dfe4ea; --rk-face: linear-gradient(160deg, #eef2f7, #9aa5b1); --rk-ink: #14181d; }
.rk-6 { --rk-frame: #d8ab2c; --rk-face: linear-gradient(160deg, #e9cb62, #8f6d13); --rk-ink: #1a1503; }

/* 7-8 — the prestige frame arrives, banded metal rather than a flat rim. */
.rk-7 {
  --rk-frame: linear-gradient(122deg, #fff8dc 0%, #806112 18%, #e8c24a 36%, #fff8dc 54%, #806112 72%, #e8c24a 100%);
  --rk-face: linear-gradient(160deg, #fff3b8, #c79f18);
  --rk-ink: #241d03;
}
.rk-8 {
  --rk-frame: linear-gradient(122deg, #d6f7ff 0%, #06505c 18%, #23c3e0 36%, #d6f7ff 54%, #06505c 72%, #23c3e0 100%);
  --rk-face: linear-gradient(160deg, #8fe8fb, #0a7d92);
  --rk-ink: #032830;
}

/* 9-10 — frame and glow. Only the top two light up, so the jump is visible. */
.rk-9 {
  --rk-frame: linear-gradient(122deg, #ffffff 0%, #8e99a6 18%, #e8eef5 36%, #ffffff 54%, #8e99a6 72%, #e8eef5 100%);
  --rk-face: linear-gradient(160deg, #ffffff, #b6c2ce);
  --rk-ink: #12161a;
  --rk-glow: drop-shadow(0 0 5px rgba(233,240,247,.55));
}
.rk-10 {
  --rk-frame: linear-gradient(122deg, #fff8dc 0%, #8f1717 16%, #e8c24a 32%, #ffffff 48%, #8f1717 64%, #e8c24a 82%, #fff8dc 100%);
  --rk-face: linear-gradient(160deg, #ff6a6a, #a61b1b 55%, #5d0a0a);
  --rk-ink: #fff3f3;
  --rk-glow: drop-shadow(0 0 7px rgba(222,36,36,.7));
}

/* The name picks up the tier too, so the ladder still reads in a mono list. */
.rank-name {
  font-family: var(--cond); font-weight: 700; font-size: 14.5px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rn-7  { color: var(--amber); }
.rn-8  { color: var(--cy-peak); }
.rn-9  { color: #ffffff; }
.rn-10 { color: var(--red-peak); }

.rank-chip.big .rank-badge { width: 44px; height: 40px; padding: 3px; }
.rank-chip.big .rank-badge > i { font-size: 13px; }
.rank-chip.big .rank-name { font-size: 16px; }
.rank-chip.big .rank-name:not([class*="rn-7"]):not([class*="rn-8"]):not([class*="rn-9"]):not([class*="rn-10"]) { color: var(--text); }

.rank-ladder {
  display: flex; gap: 6px; margin-top: 10px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  background: var(--row-gap); padding: 6px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.rank-ladder::-webkit-scrollbar { height: 6px; }
.rank-ladder::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
.rank-step {
  /* Wide enough for "Intermediate", the longest name on the ladder. The CP
     floor a rank requires rides along as this element's title rather than
     printed text — a hover fact, not part of the minimal layout. */
  flex: 0 0 214px; scroll-snap-align: start;
  display: flex; align-items: center; gap: 12px;
  background: var(--row); padding: 10px 14px;
}
.rank-tier {
  font-family: var(--display); font-size: 16px; color: var(--muted);
  font-variant-numeric: tabular-nums; min-width: 20px;
}

@media (max-width: 860px) {
  /* Let the board scroll rather than crushing four number columns. .lb-rows
     is listed too: without a width of its own it would sit at the scroller's
     visible width and clip the rows that overflow it. */
  .lb-head, .lb-rows, .lb-row { min-width: 700px; }
}


/* ---------- contribute ----------
   The one place the app asks something of the reader, so it is a panel rather
   than another dense row: the address has to be findable at a glance. */
.contribute {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 300px);
  gap: 0; margin-top: 10px; overflow: hidden;
}
.contribute-body { padding: 20px 22px 22px; min-width: 0; }
.contribute-lead {
  margin: 0 0 18px; font-size: 15px; line-height: 1.55; color: var(--text);
  max-width: 56ch;
}

.contribute-steps {
  list-style: none; counter-reset: step; margin: 0 0 20px; padding: 0;
  display: flex; flex-direction: column; gap: 12px;
}
.contribute-steps li {
  counter-increment: step; position: relative; padding-left: 42px; min-height: 30px;
}
/* The numeral is the display face, as every other count in the app is. */
.contribute-steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: -2px;
  width: 30px; height: 27px;
  display: grid; place-items: center;
  clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
  background: var(--cy-body); color: var(--cy-peak);
  font-family: var(--display); font-size: 15px;
}
.contribute-steps b {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 16px;
  letter-spacing: .04em; text-transform: uppercase; line-height: 1.2;
}
.contribute-steps span {
  display: block; font-size: 13.5px; color: var(--text-2); line-height: 1.5; margin-top: 2px;
}

/* Black fill and a cyan rim: the app's input treatment, because this is the
   one string a reader has to take away with them. */
.contribute-mail {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  background: var(--void); border: 2px solid var(--cy-mid); border-radius: 8px;
  padding: 12px 16px;
}
.contribute-mail-label {
  font-family: var(--cond); font-weight: 700; font-size: 11.5px;
  letter-spacing: .18em; text-transform: uppercase; color: var(--muted);
}
.contribute-mail-address {
  font-family: var(--body); font-weight: 700; font-size: 16.5px;
  color: var(--blue); text-decoration: none; word-break: break-all;
  /* Selectable is the point — a mailto may be blocked wherever this is
     embedded, and the address still has to be copyable by hand. */
  user-select: all;
}
.contribute-mail-address:hover { color: var(--cy-peak); text-decoration: underline; }

.contribute-wanted {
  background: var(--panel); background-image: var(--grain);
  border-left: 1px solid var(--line); padding: 20px 22px 22px;
}
.contribute-wanted h4 {
  margin: 0 0 12px; font-family: var(--cond); font-weight: 700; font-size: 12.5px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
}
.contribute-wanted ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.contribute-wanted li { border-left: 2px solid var(--cy-body); padding-left: 11px; }
.contribute-wanted b {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 14.5px;
  letter-spacing: .05em; text-transform: uppercase;
}
.contribute-wanted span { display: block; font-size: 12.5px; color: var(--text-2); line-height: 1.45; }

@media (max-width: 860px) {
  .contribute { grid-template-columns: minmax(0, 1fr); }
  .contribute-wanted { border-left: none; border-top: 1px solid var(--line); }
}
.contribute-copied { font-family: var(--cond); font-weight: 700; font-size: 12.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--lime); }


/* ---------- profile ---------- */
.profile-save { justify-content: flex-end; align-items: center; }
.profile-saved {
  font-family: var(--cond); font-weight: 700; font-size: 12.5px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--lime);
  margin-right: auto;
}
