/* ============================================================================
   Yeepabase — bright, chunky, playful. Fonts and colours lifted from
   yeeps.com: Lilita One + Londrina Solid, red topbar, periwinkle + navy on a
   cool off-white, rounded cards with hard offset shadows.
   ========================================================================== */

/* ---- Fonts (self-hosted, latin subset) ---------------------------------- */
@font-face {
  font-family: 'Lilita One';
  src: url('/assets/fonts/LilitaOne-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Londrina Solid';
  src: url('/assets/fonts/LondrinaSolid-300.woff2') format('woff2');
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Londrina Solid';
  src: url('/assets/fonts/LondrinaSolid-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Nunito Sans';
  src: url('/assets/fonts/NunitoSans-var.woff2') format('woff2');
  font-weight: 200 1000; font-style: normal; font-display: swap;
}

/* ---- Tokens ------------------------------------------------------------- */
:root {
  /* Yeeps brand hues */
  --red:       #df5454;   /* topbar + primary CTA */
  --red-deep:  #b83f3f;
  --peri:      #5d6bbd;   /* periwinkle — links, secondary actions, data accent */
  --peri-deep: #454d8b;
  --peri-soft: #eceefb;
  --navy:      #2c2f4b;   /* ink + heading colour */
  --navy-2:    #3c4066;
  --blue:      #3d84e6;   /* true blue — claim actions, distinct from periwinkle */
  --blue-deep: #2861b8;
  --sand:      #f9d7af;   /* warm highlight — top rank */
  --sand-deep: #e9b476;
  --sand-ink:  #9a6f34;

  /* Surfaces (cool off-white, tinted toward periwinkle) */
  --bg:        #eef0f9;
  --bg-2:      #e6e8f4;
  --surface:   #ffffff;
  --surface-2: #f6f7fc;
  --border:    #dcdef0;
  --border-2:  #c9cce4;

  /* Text — a four-step ramp, every step ≥4.5:1 on white, --bg, --surface-2 and
     --peri-soft (measured, per PRODUCT.md "verified, not assumed").
     --muted was #71769a (4.41:1 on white, 3.88:1 on --bg) and --faint was
     #9a9fbd (2.60:1) — both failed AA everywhere they appeared, and --faint
     carries every micro-label on the site: META USERNAME, JOINED YEEPS, the
     leaderboard units. Hue and saturation are unchanged; only lightness moved,
     so the ramp reads the same, just legibly. */
  --ink:    #2c2f4b;   /* 13.00:1 on white */
  --body:   #474b6b;   /*  8.45:1 */
  --muted:  #5c6081;   /*  6.10:1 */
  --faint:  #636a97;   /*  5.20:1 */
  --on-red: #ffffff;

  /* Semantic */
  --ok:      #2f9e5c;
  --ok-bg:   #e4f5ea;
  --ok-bd:   #b7e3c6;
  --warn:    #b9791d;
  --warn-bg: #fdf1dc;
  --warn-bd: #f2d9a6;

  /* Type */
  --font-display: 'Lilita One', system-ui, sans-serif;
  --font-accent:  'Londrina Solid', system-ui, sans-serif;
  --font-body:    'Nunito Sans', system-ui, -apple-system, sans-serif;

  /* Shape */
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-xl: 26px;
  --bw: 2px;

  /* Chunky offset shadows — the Yeeps signature */
  --pop:    0 3px 0 var(--border-2);
  --pop-lg: 0 5px 0 var(--border-2);
  --lift:   0 10px 24px rgba(44, 47, 75, 0.12);

  /* Motion */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur:  180ms;

  --z-sticky: 100;
  --z-toast:  600;
  --shell: 1180px;
}

/* ---- Reset / base ------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  /* Lilita One and Londrina Solid each ship exactly one weight and no italic.
     Ask for anything else and the browser fakes it — smearing the glyphs sideways
     for bold, shearing them for italic. That is not a heavier cut of the font, it
     is the same cut drawn badly, and it read as "the text is bold" on the brand,
     the nav, and every roster and leaderboard name, all of which inherit
     `a { font-weight: 700 }` below. Refusing the fake means those fall back to the
     real 400 face. Only Nunito Sans (variable, 200–1000) has weights to give, and
     it needs no synthesis to give them. */
  font-synthesis: none;
}

body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-body);
  font-size: 15.5px;
  line-height: 1.5;
  color: var(--body);
  background-color: var(--bg);
  /* Soft dotted play-mat texture, very faint. */
  background-image: radial-gradient(var(--border-2) 1.2px, transparent 1.3px);
  background-size: 22px 22px;
  background-position: -11px -11px;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--peri); text-decoration: none; font-weight: 700; }
a:hover { color: var(--peri-deep); }
/* Display-font text that sits inside a link, and so inherits the 700 above.
   Lilita One has only a 400 face; these say so rather than leaving the
   declaration lying and relying on font-synthesis to clean up after it. */
.brand, .topnav a, .backlink, .linked-go, .pc-name, .lb-name, .world-code, .person-name {
  font-weight: 400;
}
img, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; cursor: pointer; }

:focus-visible {
  outline: 3px solid var(--peri);
  outline-offset: 2px;
  border-radius: 6px;
}
::selection { background: var(--peri); color: #fff; }

.shell { max-width: var(--shell); margin: 0 auto; padding: 0 24px; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- Skip link ---------------------------------------------------------- */
.skip {
  position: absolute; left: 12px; top: -60px; z-index: var(--z-toast);
  background: var(--peri); color: #fff; padding: 10px 16px;
  border-radius: var(--r-sm); font-weight: 800; transition: top var(--dur) var(--ease);
}
.skip:focus { top: 12px; color: #fff; }

/* ---- Top bar (the red Yeeps bar) ---------------------------------------- */
.topbar {
  position: sticky; top: 0; z-index: var(--z-sticky);
  background: var(--red);
  border-bottom: 3px solid var(--red-deep);
}
.topbar-inner { display: flex; align-items: center; gap: 20px; height: 66px; }
.brand {
  display: inline-flex; align-items: center; gap: 0;
  font-family: var(--font-display); font-size: 27px; letter-spacing: 0.4px;
  color: #fff; line-height: 1; text-shadow: 0 2px 0 var(--red-deep);
}
.brand:hover { color: #fff; }
.brand-mark {
  width: 34px; height: 34px; border-radius: 10px;
  background: #fff; display: grid; place-items: center; flex: none;
  box-shadow: 0 3px 0 var(--red-deep); border: 2px solid #fff;
}
.brand-mark svg { width: 22px; height: 22px; }
.brand-mark svg path[fill="#fff"], .brand-mark svg path[opacity] { fill: var(--peri) !important; }
.brand em { color: var(--sand); font-style: normal; }

.topnav { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.topnav a {
  font-family: var(--font-display); font-size: 18px; letter-spacing: 0.3px;
  color: rgba(255,255,255,0.82); padding: 8px 13px; border-radius: var(--r-sm);
  line-height: 1; transition: color var(--dur), background var(--dur);
}
/* Splits "browse the base" from "your account". Sits on the darker underside of
   the red bar so it reads as a groove rather than a stray light line. */
.topnav-rule {
  width: 2px; height: 24px; flex: none; margin: 0 9px;
  border-radius: 2px; background: var(--red-deep);
}
.topnav a:hover { color: #fff; background: rgba(255,255,255,0.16); }
.topnav a[aria-current="page"] { color: var(--red); background: #fff; }
.topnav-cta {
  background: var(--peri) !important; color: #fff !important;
  box-shadow: 0 3px 0 var(--peri-deep);
}
.topnav-cta:hover { background: var(--peri-deep) !important; color: #fff !important; }
.topnav-cta:active { transform: translateY(3px); box-shadow: 0 0 0 var(--peri-deep); }

/* ---- Page frame --------------------------------------------------------- */
main { padding: 44px 0 96px; flex: 1 0 auto; }
.page-head { margin-bottom: 30px; }
.page-title {
  font-family: var(--font-display);
  font-size: clamp(2.3rem, 5.5vw, 3.4rem);
  line-height: 0.98; letter-spacing: 0.4px; color: var(--ink); text-wrap: balance;
}
.page-title em { color: var(--peri); font-style: normal; }
.page-sub { margin-top: 12px; color: var(--muted); font-size: 15.5px; max-width: 62ch; }
.page-sub b { color: var(--ink); font-weight: 800; }

/* ---- Section label (Londrina) ------------------------------------------- */
/* The gap belongs between a section and whatever precedes it, so it is set on
   the sibling combinator rather than on the label itself. A flat margin-top also
   pushed the label down when it was the first thing in an account panel, leaving
   a dead band under the panel's own padding. */
.section-label {
  display: flex; align-items: center; gap: 12px;
  font-family: var(--font-accent); font-weight: 400; font-size: 22px;
  letter-spacing: 0.4px; color: var(--navy-2); margin: 0 0 16px;
}
/* Not :where() — that zeroes specificity, so the `margin: 0 0 16px` shorthand
   above (0,1,0) beat it and the gap silently never applied. */
* + .section-label { margin-top: 56px; }
.section-label::after {
  content: ""; flex: 1; height: 3px; border-radius: 3px;
  background: repeating-linear-gradient(90deg, var(--border-2) 0 8px, transparent 8px 14px);
}

/* ---- Buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-display); font-weight: 400; font-size: 16px; letter-spacing: 0.3px;
  padding: 11px 20px; border-radius: var(--r-md);
  border: var(--bw) solid transparent; min-height: 46px;
  /* border-color and color are in here deliberately. Without them .btn-ghost —
     Log out, Unlink Discord — snapped its outline and text straight to the hover
     colour while the background eased, which reads as the button not animating at
     all: the parts that change most are the parts that were jumping. */
  transition: transform var(--dur) var(--ease), background var(--dur),
              box-shadow var(--dur), border-color var(--dur), color var(--dur);
  white-space: nowrap;
}
.btn svg { width: 19px; height: 19px; flex: none; }

/* Chunky 3D buttons: a solid base sits under them; pressing pushes the button
   down onto its base so the base "disappears". translate distance == base depth.

   The dark ring is an inset shadow, not a border, and that is deliberate. A real
   border is painted as its own ring path with its own antialiased edges; where it
   meets the drop shadow neither reaches full coverage, and the page bleeds through
   as white specks in the bottom corners (worst at fractional browser zoom). An
   inset ring shares the background's single edge, so the silhouette rasterises
   once — measured 0 specks vs 9, where spread and blur both fixed nothing.

   Inset shadows paint inside the PADDING box, so these variants zero the border
   and hand the same 2px back as padding: the ring lands on the real edge and the
   box keeps its exact size. Don't swap it back to border-color. */
.btn-primary, .btn-red, .btn-claim, .btn-danger {
  border-width: 0;
  padding: calc(11px + var(--bw)) calc(20px + var(--bw));
}
.btn-primary {
  background: var(--peri); color: #fff;
  box-shadow: inset 0 0 0 var(--bw) var(--peri-deep), 0 4px 0 var(--peri-deep);
}
.btn-primary:hover { background: #6975c9; color: #fff; }
.btn-primary:active { transform: translateY(4px); box-shadow: inset 0 0 0 var(--bw) var(--peri-deep); }

.btn-red {
  background: var(--red); color: #fff;
  box-shadow: inset 0 0 0 var(--bw) var(--red-deep), 0 4px 0 var(--red-deep);
}
.btn-red:hover { background: #e46464; color: #fff; }
.btn-red:active { transform: translateY(4px); box-shadow: inset 0 0 0 var(--bw) var(--red-deep); }

.btn-ghost {
  background: var(--surface); color: var(--ink); border-color: var(--border-2);
  box-shadow: var(--pop);
}
.btn-ghost:hover { background: var(--surface-2); border-color: var(--peri); color: var(--peri-deep); }
.btn-ghost:active { transform: translateY(3px); box-shadow: 0 0 0 var(--border-2); }

/* Claim: a distinct true-blue, set apart from the periwinkle primary. */
.btn-claim {
  background: var(--blue); color: #fff;
  box-shadow: inset 0 0 0 var(--bw) var(--blue-deep), 0 4px 0 var(--blue-deep);
}
.btn-claim:hover { background: #2f7ae6; color: #fff; }
.btn-claim:active { transform: translateY(4px); box-shadow: inset 0 0 0 var(--bw) var(--blue-deep); }

.btn-danger {
  background: var(--surface); color: var(--red-deep);
  box-shadow: inset 0 0 0 var(--bw) #f0c2c2, 0 3px 0 #f0c2c2;
}
.btn-danger:hover { background: #fdeaea; box-shadow: inset 0 0 0 var(--bw) var(--red), 0 3px 0 #f0c2c2; color: var(--red-deep); }
.btn-danger:active { transform: translateY(3px); box-shadow: inset 0 0 0 var(--bw) #f0c2c2; }

.btn[disabled] { opacity: 0.55; cursor: not-allowed; transform: none; box-shadow: none; }

/* ---- Controls row ------------------------------------------------------- */
.controls { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-bottom: 28px; }
.search { position: relative; flex: 1; min-width: 220px; }
.search svg {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  width: 19px; height: 19px; color: var(--faint); pointer-events: none; transition: color var(--dur);
}
.search[data-busy="true"] svg { color: var(--peri); }
.input {
  width: 100%; height: 50px;
  background: var(--surface); color: var(--ink);
  border: var(--bw) solid var(--border-2); border-radius: var(--r-md);
  padding: 0 16px; font-size: 15.5px; font-family: var(--font-body); font-weight: 600;
  box-shadow: var(--pop); transition: border-color var(--dur), box-shadow var(--dur);
}
.search .input { padding-left: 46px; }
.input::placeholder { color: var(--faint); font-weight: 600; }
.input:focus { outline: none; border-color: var(--peri); box-shadow: 0 3px 0 var(--peri-soft); }
.input:focus-visible { outline: 3px solid var(--peri); outline-offset: 1px; }

.count-pill {
  font-family: var(--font-display); font-size: 15px; color: var(--peri-deep);
  background: var(--peri-soft); border: var(--bw) solid var(--border-2);
  border-radius: 999px; padding: 10px 18px; white-space: nowrap; box-shadow: var(--pop);
}
.count-pill b { color: var(--red); }

/* ---- Roster grid -------------------------------------------------------- */
.roster {
  list-style: none; padding: 0;
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.roster > li { display: flex; }
.player-card {
  display: flex; align-items: center; gap: 15px; width: 100%; min-height: 96px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-lg); padding: 15px;
  box-shadow: var(--pop);
  transition: transform var(--dur) var(--ease), border-color var(--dur), box-shadow var(--dur);
}
.player-card:hover {
  transform: translateY(-4px); border-color: var(--peri);
  box-shadow: 0 12px 22px rgba(44,47,75,0.14);
}
.avatar {
  width: 66px; height: 66px; flex: none;
  border-radius: 16px; overflow: hidden;
  border: var(--bw) solid var(--border-2); background: var(--bg);
  transition: border-color var(--dur);
}
.player-card:hover .avatar { border-color: var(--peri); }
.avatar img, .avatar svg { width: 100%; height: 100%; }
.avatar--lg { width: 128px; height: 128px; border-radius: 24px; box-shadow: var(--pop-lg); }

/* ---- Yeep colour --------------------------------------------------------- */
/* The portrait is matched to a player's skin colour on hue, out of three
   renders — so it is a likeness, not a reading. The swatch carries the real
   value, which is why it sits next to every portrait and never replaces the
   hex beside it.

   A circle, and a single dark ring. It was a rounded square with a border AND a
   white inner ring, which on the pale skins this game actually produces (#ccdeff
   and friends) left a near-white square framed in white inside a grey box — the
   exact drawing of an unchecked checkbox, sat next to text, inside a clickable
   card. A round dot is the colour-sample idiom and cannot be mistaken for a
   control. The ring is inset so it shares the circle's own edge rather than
   adding a second antialiased one, and it is tinted ink rather than white so it
   defines the edge for pale and dark skins alike. */
.yeep-swatch {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  background: var(--swatch);
  box-shadow: inset 0 0 0 1.5px rgba(44, 47, 75, 0.28);
}
.yeep-swatch--sm { width: 12px; height: 12px; box-shadow: inset 0 0 0 1.25px rgba(44, 47, 75, 0.28); }

.meta-colour { display: flex; align-items: center; gap: 8px; font-variant-numeric: tabular-nums; }

.badge-colour {
  background: var(--surface-2); color: var(--body); border-color: var(--border-2);
  font-variant-numeric: tabular-nums;
}

.pc-body { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.pc-heading {
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.pc-heading .badge { flex: none; }
.pc-name {
  display: block; min-width: 0;
  font-family: var(--font-display); font-size: 21px; line-height: 1.05;
  color: var(--ink); letter-spacing: 0.2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pc-user { display: block; font-size: 13.5px; color: var(--muted); font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pc-tags { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }

/* ---- Badges ------------------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-body); font-weight: 800; font-size: 11px;
  letter-spacing: 0.3px; padding: 3px 10px; border-radius: 999px;
  border: 1.5px solid transparent; line-height: 1.5;
}
.badge-role  { background: var(--peri-soft); color: var(--peri-deep); border-color: var(--border-2); }
.badge-dev   { background: var(--sand); color: var(--sand-ink); border-color: var(--sand-deep); }
.badge-live  { background: var(--ok-bg); color: var(--ok); border-color: var(--ok-bd); }
.badge-preview { background: #e7f0fd; color: var(--blue-deep); border-color: #c3dbf7; }
.badge-pending { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-bd); }

/* ---- Note --------------------------------------------------------------- */
.note {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--warn-bg); border: var(--bw) solid var(--warn-bd);
  border-radius: var(--r-md); padding: 15px 17px; color: var(--ink);
}
.note svg { width: 21px; height: 21px; color: var(--warn); flex: none; margin-top: 1px; }
.note b { font-weight: 800; }
.note p { color: var(--navy-2); font-size: 14px; margin-top: 2px; }

/* ---- Leaderboard -------------------------------------------------------- */
.lb-tabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px;
  padding-bottom: 22px; border-bottom: 3px dashed var(--border-2); }
.lb-tab {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 15px; letter-spacing: 0.2px;
  color: var(--navy-2); background: var(--surface);
  border: var(--bw) solid var(--border-2); border-radius: var(--r-md);
  padding: 9px 16px; min-height: 44px; box-shadow: var(--pop); transition: all var(--dur);
}
.lb-tab:hover { color: var(--peri-deep); border-color: var(--peri); transform: translateY(-2px); }
/* Same inset-ring trick as .btn-primary: zero the border and put the width into
   padding so the label stays centred on the coloured face. */
.lb-tab[aria-current="true"] {
  color: #fff; background: var(--peri); border-width: 0;
  padding: calc(9px + var(--bw)) calc(16px + var(--bw));
  box-shadow: inset 0 0 0 var(--bw) var(--peri-deep), 0 4px 0 var(--peri-deep);
}
.lb-tab:active { transform: translateY(3px); box-shadow: 0 0 0 var(--border-2); }
.lb-tab[aria-current="true"]:active { box-shadow: inset 0 0 0 var(--bw) var(--peri-deep); }

.lb { display: flex; flex-direction: column; gap: 10px; list-style: none; padding: 0; }
.lb-row {
  display: grid; grid-template-columns: 58px 64px 1fr auto;
  align-items: center; gap: 16px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-lg); padding: 13px 20px 13px 14px; box-shadow: var(--pop);
  transition: transform var(--dur) var(--ease), border-color var(--dur), box-shadow var(--dur);
}
.lb-row:hover { transform: translateX(4px); border-color: var(--peri); box-shadow: 0 10px 20px rgba(44,47,75,0.12); }
.lb-rank { display: flex; flex-direction: column; align-items: center; gap: 2px; font-family: var(--font-display); color: var(--faint); line-height: 1; }
.lb-rank-n { font-size: 28px; }
.lb-medal { display: inline-flex; width: 19px; height: 19px; }
.lb-medal svg { width: 100%; height: 100%; }
.lb-row--1 { border-color: var(--sand-deep); background: linear-gradient(100deg, #fff7ec, var(--surface) 55%); }
.lb-row--1 .lb-rank { color: var(--sand-ink); }
.lb-row--2 { border-color: var(--border-2); }
.lb-row--2 .lb-rank { color: var(--navy-2); }
.lb-row--3 { border-color: var(--sand-deep); }
.lb-row--3 .lb-rank { color: var(--sand-ink); }
.lb-medal { font-size: 16px; }
.lb-avatar { width: 48px; height: 48px; border-radius: 13px; overflow: hidden; border: var(--bw) solid var(--border-2); }
.lb-name { font-family: var(--font-display); font-size: 20px; color: var(--ink); line-height: 1.05; letter-spacing: 0.2px; }
.lb-user { font-size: 12.5px; color: var(--muted); font-weight: 700; }
.lb-stat { text-align: right; }
.lb-stat b { font-family: var(--font-display); font-size: 26px; color: var(--peri); letter-spacing: 0.4px; line-height: 1; font-variant-numeric: tabular-nums; }
.lb-row--1 .lb-stat b { color: var(--sand-ink); }
.lb-stat span { display: block; font-size: 11px; color: var(--faint); font-weight: 800; text-transform: uppercase; letter-spacing: 0.6px; margin-top: 3px; }

/* ---- Player page -------------------------------------------------------- */
.profile-head {
  display: flex; gap: 26px; align-items: center; flex-wrap: wrap;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-xl); padding: 26px; margin-bottom: 26px; box-shadow: var(--pop-lg);
}
.profile-id { min-width: 0; flex: 1; }
.profile-name { font-family: var(--font-display); font-size: clamp(2rem, 4.5vw, 2.8rem); line-height: 1; color: var(--ink); letter-spacing: 0.3px; text-wrap: balance; }
.profile-user { color: var(--peri); font-weight: 800; font-size: 16px; margin-top: 6px; }
.profile-bio { margin-top: 10px; max-width: 42ch; }
.profile-bio-text {
  display: block; width: 100%; margin: 0; padding: 0;
  font-family: var(--font-body); font-size: 15px; font-weight: 700;
  line-height: 1.4; color: var(--body); text-align: left;
  background: none; border: none; white-space: pre-wrap; overflow-wrap: anywhere;
}
/* display:block above outweighs the browser's [hidden] rule, so the read-only
   text kept showing over the editor (both the ?edit=bio render and the JS
   toggle set it). Restore what hidden means for this element. */
.profile-bio-text[hidden] { display: none; }
a.profile-bio-text { color: var(--body); font-weight: 700; }
a.profile-bio-text:hover { color: var(--peri-deep); }
.profile-bio--mine a.profile-bio-text {
  cursor: pointer; border-radius: 8px; padding: 6px 8px; margin: -6px -8px;
  border: 1.5px dashed transparent; transition: border-color var(--dur), background var(--dur), color var(--dur);
}
.profile-bio--mine a.profile-bio-text:hover {
  background: var(--peri-soft); border-color: var(--border-2); color: var(--ink);
}
.profile-bio-text--empty { color: var(--faint); font-style: italic; font-weight: 700; }
.profile-bio--mine a.profile-bio-text--empty:hover { color: var(--muted); }
.profile-bio-form { display: grid; gap: 10px; margin-top: 2px; }
.profile-bio-form[hidden] { display: none; }
.profile-bio-input {
  width: 100%; min-height: 72px; resize: vertical;
  font-family: var(--font-body); font-size: 15px; font-weight: 600; line-height: 1.4;
  color: var(--ink); background: var(--surface-2);
  border: var(--bw) solid var(--border-2); border-radius: var(--r-md);
  padding: 10px 12px; box-shadow: var(--pop);
}
.profile-bio-input:focus { outline: none; border-color: var(--peri); box-shadow: 0 3px 0 var(--peri-soft); }
.profile-bio-input:focus-visible { outline: 3px solid var(--peri); outline-offset: 1px; }
.profile-bio-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.profile-bio-actions[hidden] { display: none; }
.profile-bio-hint { margin: 6px 0 0; font-size: 12px; color: var(--faint); font-weight: 600; }
.profile-bio-hint[hidden] { display: none; }
.profile-bio-hint kbd {
  font-family: var(--font-body); font-size: 11px; font-weight: 800;
  background: var(--surface-2); border: 1.5px solid var(--border);
  border-radius: 5px; padding: 0 4px; color: var(--body);
}
.profile-bio .err { font-size: 12.5px; color: var(--red-deep); font-weight: 800; }
.profile-tags { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.profile-meta { display: flex; gap: 28px; flex-wrap: wrap; margin-top: 18px; padding-top: 18px; border-top: 3px dashed var(--border-2); }

/* Small button + the bottom-of-profile claim strip */
/* Overrides the base depth, so it must re-state the inset ring or lose it; the
   padding carries the zeroed border width, same as the variants above. */
.btn-sm { min-height: 38px; padding: calc(8px + var(--bw)) calc(15px + var(--bw)); font-size: 15px; box-shadow: inset 0 0 0 var(--bw) var(--blue-deep), 0 3px 0 var(--blue-deep); }
.btn-sm:active { box-shadow: inset 0 0 0 var(--bw) var(--blue-deep); }
.btn-sm svg { width: 16px; height: 16px; }
.claim-strip {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 30px; padding: 16px 20px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--pop);
}
.claim-strip span { font-family: var(--font-body); font-weight: 800; color: var(--muted); font-size: 14px; }
.claim-strip .btn { margin-left: auto; }

/* Claim page target header */
.claim-target { display: flex; align-items: center; gap: 14px; margin: 4px 0 20px; padding-bottom: 20px; border-bottom: 3px dashed var(--border-2); }
.claim-target-name { font-family: var(--font-display); font-size: 20px; color: var(--ink); line-height: 1.1; }
.claim-target-user { color: var(--peri); font-weight: 800; font-size: 14px; }

.unlink-form { margin-top: 18px; }
.meta-item dt { font-size: 11px; text-transform: uppercase; letter-spacing: 0.7px; color: var(--faint); font-weight: 800; }
.meta-item dd { font-family: var(--font-body); font-weight: 800; color: var(--ink); font-size: 15px; margin-top: 3px; }

/* Sized so the nine stats break 5+4 instead of stranding the last one alone on
   its own row. Retune if the stat list grows again. */
.stat-grid { display: grid; gap: 15px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.stat-tile {
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-lg); padding: 19px 21px; box-shadow: var(--pop);
  display: flex; flex-direction: column; gap: 3px;
}
.stat-tile.is-ranked { border-color: var(--peri); }
.stat-label { font-family: var(--font-accent); font-size: 19px; color: var(--navy-2); letter-spacing: 0.3px; }
.stat-value { font-family: var(--font-display); font-size: clamp(2rem, 3vw, 2.5rem); color: var(--ink); line-height: 1; letter-spacing: 0.5px; font-variant-numeric: tabular-nums; }
.stat-value.is-empty { color: var(--faint); }
.stat-foot { display: flex; align-items: center; gap: 8px; margin-top: 9px; font-size: 12.5px; color: var(--muted); }
.rank-chip { font-family: var(--font-display); font-size: 13px; color: var(--peri-deep); background: var(--peri-soft); border: 1.5px solid var(--border-2); border-radius: 999px; padding: 2px 10px; }
.rank-chip--top { color: var(--sand-ink); background: #fdf1dc; border-color: var(--sand-deep); }

/* ---- Outfit ------------------------------------------------------------- */
/* Shared by Outfit and Purchases: same card, same affordance, different facts. */
.slot-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }

/* States a limit of the data, so it sits quiet under the grid it qualifies. */
.section-note {
  margin: 12px 2px 0; font-size: 12.5px; font-weight: 700;
  color: var(--body); max-width: 68ch; line-height: 1.5;
}
.slot-card {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-md); padding: 12px 14px; box-shadow: var(--pop);
}
.slot-icon {
  width: 40px; height: 40px; flex: none; border-radius: 11px;
  display: grid; place-items: center;
  background: var(--peri-soft); color: var(--peri-deep); border: 1.5px solid var(--border-2);
}
.slot-icon svg { width: 22px; height: 22px; }
.slot-swatch {
  width: 40px; height: 40px; flex: none; border-radius: 11px;
  border: 2px solid var(--border-2); box-shadow: inset 0 0 0 2px #fff;
}
.slot-text { min-width: 0; display: flex; flex-direction: column; }
.slot-name { font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px; color: var(--faint); font-weight: 800; }
.slot-item { font-family: var(--font-display); font-size: 16px; color: var(--ink); line-height: 1.1; letter-spacing: 0.2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.backlink { display: inline-flex; align-items: center; gap: 7px; font-family: var(--font-display); font-size: 15px; color: var(--muted); margin-bottom: 20px; }
.backlink:hover { color: var(--peri); }
.backlink svg { width: 17px; height: 17px; }

/* ---- Nav CTA / flash ---------------------------------------------------- */
.flash {
  margin: 22px 0 -6px; padding: 13px 17px;
  background: var(--ok-bg); border: var(--bw) solid var(--ok-bd);
  border-radius: var(--r-md); color: #1f7a45; font-weight: 800; font-size: 14.5px; box-shadow: var(--pop);
}

/* ---- CTA band ----------------------------------------------------------- */
.cta-band {
  display: flex; align-items: center; gap: 24px; flex-wrap: wrap; justify-content: space-between;
  background: linear-gradient(115deg, var(--peri) 0%, #6f7cca 100%);
  border: var(--bw) solid var(--peri-deep); border-radius: var(--r-xl);
  padding: 24px 28px; margin-bottom: 30px; box-shadow: var(--pop-lg);
}
.cta-band--linkable { background: linear-gradient(115deg, var(--sand) 0%, #fbe4c4 100%); border-color: var(--sand-deep); }
.cta-copy h2 { font-family: var(--font-display); font-size: 26px; color: #fff; letter-spacing: 0.3px; }
.cta-copy p { color: rgba(255,255,255,0.9); margin-top: 4px; font-size: 14.5px; font-weight: 600; }
.cta-band--linkable .cta-copy h2 { color: var(--navy); }
.cta-band--linkable .cta-copy p { color: var(--sand-ink); }
.cta-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.cta-band .btn-primary { background: #fff; color: var(--peri-deep); border-color: #fff; box-shadow: 0 4px 0 var(--peri-deep); }
.cta-band .btn-primary:hover { background: #f3f4ff; color: var(--peri-deep); }
.cta-band .btn-primary:active { transform: translateY(4px); box-shadow: 0 0 0 var(--peri-deep); }
.cta-band--linkable .btn-primary:active { box-shadow: 0 0 0 #1a1c30; }
.cta-band .btn-ghost { background: rgba(255,255,255,0.16); color: #fff; border-color: rgba(255,255,255,0.5); box-shadow: none; }
.cta-band .btn-ghost:hover { background: rgba(255,255,255,0.28); color: #fff; }
.cta-band--linkable .btn-primary { background: var(--navy); color: #fff; border-color: var(--navy); box-shadow: 0 4px 0 #1a1c30; }
.cta-band--linkable .btn-primary:hover { background: var(--navy-2); color: #fff; }

/* ---- Auth pages --------------------------------------------------------- */
.auth-shell { display: grid; place-items: center; padding: 30px 0 48px; }
.auth-shell--wide .auth-card { max-width: 640px; }
.auth-card {
  width: 100%; max-width: 452px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-xl); padding: 34px; box-shadow: var(--lift);
}
.auth-title { font-family: var(--font-display); font-size: 32px; color: var(--ink); letter-spacing: 0.3px; line-height: 1.05; text-wrap: balance; }
.auth-title em { color: var(--peri); font-style: normal; }
.auth-sub { color: var(--muted); margin-top: 8px; font-size: 15.5px; }

/* The provider buttons. This wrapper owns the spacing and the top margin, so the
   buttons themselves carry neither — .btn-google used to set its own margin-top,
   which was invisible while it was the only button and collided the moment
   Discord appeared beside it. */
.auth-oauth { display: grid; gap: 10px; margin-top: 22px; }
.auth-oauth > .btn,
.auth-oauth > .btn-google { min-height: 50px; margin-top: 0; width: 100%; }

.btn-google {
  display: flex; align-items: center; justify-content: center; gap: 11px;
  width: 100%; min-height: 50px;
  background: #fff; color: #1f2430; font-family: var(--font-body); font-weight: 800; font-size: 15px;
  border: var(--bw) solid var(--border-2); border-radius: var(--r-md); box-shadow: var(--pop);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur);
}
.btn-google:hover { color: #1f2430; transform: translateY(-2px); box-shadow: 0 6px 16px rgba(44,47,75,0.14); }
.btn-google svg { width: 20px; height: 20px; }

.auth-or { display: flex; align-items: center; gap: 14px; margin: 22px 0 6px; color: var(--faint); font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.6px; }
.auth-or::before, .auth-or::after { content: ""; flex: 1; height: 2px; background: var(--border-2); border-radius: 2px; }

.auth-form { display: grid; gap: 16px; margin-top: 18px; }
.auth-form--grid { grid-template-columns: 1fr 1fr; }
.auth-form--grid .field.field--full,
.auth-form--grid .privacy-note,
.auth-form--grid .auth-submit { grid-column: 1 / -1; }
.auth-submit { margin-top: 4px; width: 100%; }
.auth-error { margin-top: 18px; padding: 12px 15px; font-size: 14px; font-weight: 800; background: #fdeaea; border: var(--bw) solid #f3c4c4; border-radius: var(--r-md); color: var(--red-deep); }
.auth-alt { margin-top: 22px; text-align: center; color: var(--muted); font-size: 14.5px; }
.auth-alt a { font-weight: 800; }

.check { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; font-weight: 700; font-size: 14px; color: var(--ink); }
.check input { position: absolute; opacity: 0; width: 0; height: 0; }
.check span { position: relative; padding-left: 32px; line-height: 22px; }
.check span::before {
  content: ""; position: absolute; left: 0; top: 0; width: 22px; height: 22px;
  border: var(--bw) solid var(--border-2); border-radius: 7px; background: var(--surface);
  box-shadow: var(--pop); transition: background var(--dur), border-color var(--dur);
}
.check span::after {
  content: ""; position: absolute; left: 7px; top: 3px; width: 7px; height: 12px;
  border: solid #fff; border-width: 0 3px 3px 0; transform: rotate(45deg) scale(0);
  transition: transform var(--dur) var(--ease);
}
.check input:checked + span::before { background: var(--peri); border-color: var(--peri-deep); }
.check input:checked + span::after { transform: rotate(45deg) scale(1); }
.check input:focus-visible + span::before { outline: 3px solid var(--peri); outline-offset: 2px; }

/* ---- Fields ------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 7px; }
.field label { font-family: var(--font-body); font-weight: 800; font-size: 13.5px; color: var(--ink); }
.field label .req { color: var(--red); }
.field label .hint { color: var(--muted); font-weight: 700; }
.field .hint { font-size: 12.5px; color: var(--muted); font-weight: 700; }
.field .err { font-size: 12.5px; color: var(--red-deep); font-weight: 800; }
.field[data-invalid="true"] .input { border-color: var(--red); }
.privacy-note { font-size: 12.5px; color: var(--muted); display: flex; gap: 9px; align-items: flex-start; background: var(--surface-2); border: 2px solid var(--border); border-radius: var(--r-md); padding: 12px 14px; }
.privacy-note svg { width: 17px; height: 17px; color: var(--peri); flex: none; margin-top: 1px; }

/* ---- Account ------------------------------------------------------------ */
/* Two columns of panels, not a pool of panels flowed into two tracks. Auto-flow
   fills left-to-right, so an odd count strands the last panel and leaves the
   opposite cell empty — a visible hole. Each column is its own stack, so the
   count no longer decides the shape. */
.account-grid { display: grid; gap: 20px; grid-template-columns: 1.4fr 1fr; align-items: start; }
.account-col { display: grid; gap: 20px; align-content: start; }
.account-panel { background: var(--surface); border: var(--bw) solid var(--border); border-radius: var(--r-xl); padding: 26px; box-shadow: var(--pop); }
/* The Session panel used .profile-meta with its border, padding and margin all
   zeroed inline. It is a plain stack of facts inside a panel that already has
   its own frame, so it gets its own rule instead of borrowing one and undoing it. */
.account-meta { display: grid; gap: 15px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.account-action { margin-top: 22px; }

/* Whether credentials are encrypted is a fact, so it is stated as one — a state
   line, not a reassuring green tick. The unset case is a warning, not an error:
   nothing is broken, the operator just hasn't set a key. */
.cred-state {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 8px 14px; border-radius: 999px;
  font-weight: 800; font-size: 13.5px; border: 2px solid;
}
.cred-state svg { width: 17px; height: 17px; flex: none; }
.cred-state--on  { color: #1f7a45; background: var(--ok-bg);   border-color: var(--ok-bd); }
.cred-state--off { color: #8a5a12; background: var(--warn-bg); border-color: var(--warn-bd); }
.account-panel code {
  font-family: ui-monospace, monospace; font-size: 12.5px;
  background: var(--surface-2); border: 1.5px solid var(--border);
  border-radius: 6px; padding: 1px 5px; color: var(--ink);
}
.link-empty p { color: var(--muted); margin-bottom: 16px; }
.linked-card { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; background: var(--surface-2); border: var(--bw) solid var(--border); border-radius: var(--r-lg); padding: 16px; transition: border-color var(--dur); }
.linked-card:hover { border-color: var(--peri); }
.linked-go { margin-left: auto; font-family: var(--font-display); font-size: 14px; color: var(--peri); }
.link-hint { color: var(--muted); font-size: 13.5px; margin-top: 14px; }

/* ---- Discord ------------------------------------------------------------ */
:root {
  --discord:      #5865f2;   /* Discord Blurple, their own brand value */
  --discord-deep: #4450d4;
}

/* The chip under a profile's badges: wide blurple pill, white disc, glyph in the
   disc. It leaves the site, so it carries an external-link mark and says so to a
   screen reader — the rest of the site's links stay put, and this one does not. */
.discord-chip {
  display: inline-flex; align-items: center; gap: 10px;
  margin-top: 12px; padding: 5px 16px 5px 5px;
  background: var(--discord); border-radius: 999px;
  color: #fff; font-weight: 400;
  box-shadow: inset 0 0 0 2px var(--discord-deep), 0 4px 0 var(--discord-deep);
  transition: transform var(--dur) var(--ease), background var(--dur), box-shadow var(--dur);
}
.discord-chip:hover { background: #6b77f5; color: #fff; }
.discord-chip:active { transform: translateY(4px); box-shadow: inset 0 0 0 2px var(--discord-deep); }
.discord-mark {
  width: 32px; height: 32px; flex: none; border-radius: 50%;
  background: #fff; display: grid; place-items: center;
}
.discord-mark svg { width: 19px; height: 15px; color: var(--discord); }
.discord-name {
  font-family: var(--font-display); font-size: 16px; letter-spacing: 0.3px;
  line-height: 1; color: #fff;
}
.discord-out { width: 14px; height: 14px; flex: none; opacity: 0.75; }

/* ---- Customizable page: accent + background + social links -------------- */
/* The owner picks an accent (as --accent/-deep/-soft on .profile-head) and a
   background preset (as data-bg). Everything below reads those, so a themed page
   is just a few CSS variables and one attribute — no per-page stylesheet. */
.profile-head { position: relative; overflow: hidden; border-top: 4px solid var(--accent, var(--peri)); }
.profile-head .profile-user { color: var(--accent, var(--peri)); }
.profile-head .avatar--lg { box-shadow: 0 0 0 3px var(--accent-soft, var(--peri-soft)), var(--pop-lg); }

/* Background presets — subtle, accent-tinted, always behind the content. */
.profile-head[data-bg]::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0.55;
}
.profile-head[data-bg="plain"]::before { background: var(--accent-soft, var(--peri-soft)); opacity: 0.35; }
.profile-head[data-bg="dots"]::before {
  background: radial-gradient(var(--accent, var(--peri)) 1.4px, transparent 1.5px) 0 0 / 16px 16px;
  opacity: 0.16;
}
.profile-head[data-bg="grid"]::before {
  background:
    linear-gradient(var(--accent, var(--peri)) 1px, transparent 1px) 0 0 / 22px 22px,
    linear-gradient(90deg, var(--accent, var(--peri)) 1px, transparent 1px) 0 0 / 22px 22px;
  opacity: 0.12;
}
.profile-head[data-bg="gradient"]::before {
  background: linear-gradient(135deg, var(--accent-soft, var(--peri-soft)), transparent 70%);
  opacity: 0.9;
}
.profile-head[data-bg="rays"]::before {
  background: repeating-conic-gradient(from 0deg at 100% 0, var(--accent, var(--peri)) 0deg 6deg, transparent 6deg 14deg);
  opacity: 0.08;
}
.profile-head > * { position: relative; z-index: 1; }

/* Social link chips — the accent-coloured cousins of the Discord chip. */
.profile-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.social-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 14px 6px 10px; border-radius: 999px;
  background: var(--accent, var(--peri)); color: #fff; font-weight: 700; font-size: 14px;
  box-shadow: inset 0 0 0 2px var(--accent-deep, var(--peri-deep)), 0 3px 0 var(--accent-deep, var(--peri-deep));
  transition: transform var(--dur) var(--ease), box-shadow var(--dur);
}
.social-chip:hover { color: #fff; transform: translateY(-2px); box-shadow: inset 0 0 0 2px var(--accent-deep, var(--peri-deep)), 0 5px 0 var(--accent-deep, var(--peri-deep)); }
.social-chip:active { transform: translateY(3px); box-shadow: inset 0 0 0 2px var(--accent-deep, var(--peri-deep)); }
.social-chip svg { width: 17px; height: 17px; flex: none; }
.profile-customize-btn { margin-top: 14px; }

/* Customize form */
.customize {
  background: var(--surface); border: var(--bw) solid var(--border); border-radius: var(--r-xl);
  padding: 26px; margin-bottom: 26px; box-shadow: var(--pop);
}
.cz-form { display: grid; gap: 22px; }
.cz-group { border: 0; padding: 0; margin: 0; }
.cz-group legend {
  font-family: var(--font-accent); font-size: 17px; color: var(--navy); letter-spacing: 0.3px;
  padding: 0; margin-bottom: 10px;
}
.cz-legend-note { font-family: var(--font-body); font-size: 12px; color: var(--faint); font-weight: 700; }
.cz-swatches { display: flex; flex-wrap: wrap; gap: 10px; }
.cz-swatch { cursor: pointer; }
.cz-swatch input { position: absolute; opacity: 0; pointer-events: none; }
.cz-swatch-dot {
  display: inline-flex; width: 34px; height: 34px; border-radius: 12px;
  border: var(--bw) solid var(--border-2); box-shadow: var(--pop);
  transition: transform var(--dur) var(--ease);
}
.cz-swatch-label { display: none; }
.cz-swatch:hover .cz-swatch-dot { transform: translateY(-2px); }
.cz-swatch input:checked + .cz-swatch-dot { outline: 3px solid var(--ink); outline-offset: 2px; }
.cz-swatch input:focus-visible + .cz-swatch-dot { outline: 3px solid var(--peri); outline-offset: 2px; }

.cz-bgs { display: flex; flex-wrap: wrap; gap: 12px; }
.cz-bgopt { cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 5px; }
.cz-bgopt input { position: absolute; opacity: 0; pointer-events: none; }
.cz-bgopt-prev {
  width: 66px; height: 44px; border-radius: 10px; border: var(--bw) solid var(--border-2);
  background: var(--surface-2); position: relative; overflow: hidden;
  --accent: var(--peri); --accent-soft: var(--peri-soft);
}
.cz-bgopt-prev[data-bg="plain"] { background: var(--peri-soft); }
.cz-bgopt-prev[data-bg="dots"] { background: radial-gradient(var(--peri) 1.4px, transparent 1.5px) 0 0 / 12px 12px; }
.cz-bgopt-prev[data-bg="grid"] {
  background:
    linear-gradient(var(--peri) 1px, transparent 1px) 0 0 / 14px 14px,
    linear-gradient(90deg, var(--peri) 1px, transparent 1px) 0 0 / 14px 14px;
}
.cz-bgopt-prev[data-bg="gradient"] { background: linear-gradient(135deg, var(--peri-soft), var(--surface-2)); }
.cz-bgopt-prev[data-bg="rays"] { background: repeating-conic-gradient(from 0deg at 100% 0, var(--peri) 0deg 6deg, transparent 6deg 14deg); }
.cz-bgopt-label { font-size: 12.5px; color: var(--muted); font-weight: 700; }
.cz-bgopt input:checked + .cz-bgopt-prev { outline: 3px solid var(--ink); outline-offset: 2px; }
.cz-bgopt input:focus-visible + .cz-bgopt-prev { outline: 3px solid var(--peri); outline-offset: 2px; }

.cz-links { display: grid; gap: 8px; }
.cz-link-row { display: flex; gap: 8px; flex-wrap: wrap; }
.cz-link-row select {
  flex: 0 0 auto; min-width: 120px; padding: 10px 12px; font-size: 14px; font-family: var(--font-body);
  color: var(--ink); background: var(--surface-2); border: var(--bw) solid var(--border-2); border-radius: var(--r-md);
}
.cz-link-row input {
  flex: 1 1 200px; min-width: 0; padding: 10px 12px; font-size: 14px; font-family: var(--font-body);
  color: var(--ink); background: var(--surface-2); border: var(--bw) solid var(--border-2); border-radius: var(--r-md);
}
.cz-link-row input:focus { outline: none; border-color: var(--peri); box-shadow: 0 3px 0 var(--peri-soft); }
.cz-hint { font-size: 12.5px; color: var(--faint); margin-top: 8px; }
.cz-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* The same brand, as a full-width button in the auth cards. */
.btn-discord {
  background: var(--discord); color: #fff; border-width: 0;
  padding: calc(11px + var(--bw)) calc(20px + var(--bw)); width: 100%;
  box-shadow: inset 0 0 0 var(--bw) var(--discord-deep), 0 4px 0 var(--discord-deep);
}
.btn-discord:hover { background: #6b77f5; color: #fff; }
.btn-discord:active { transform: translateY(4px); box-shadow: inset 0 0 0 var(--bw) var(--discord-deep); }
.btn-discord svg { width: 20px; height: 16px; }

/* Footer invite: readable on navy without competing with the nav links. */
.foot-discord {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 14px;
  padding: 7px 14px 7px 7px; border-radius: 999px;
  background: rgba(255,255,255,0.09); border: 2px solid rgba(255,255,255,0.16);
  color: #fff; font-weight: 700; font-size: 13px;
  transition: background var(--dur), border-color var(--dur), transform var(--dur) var(--ease);
}
.foot-discord:hover { background: var(--discord); border-color: var(--discord-deep); color: #fff; transform: translateY(-2px); }
.foot-discord .discord-mark { width: 24px; height: 24px; }
.foot-discord .discord-mark svg { width: 14px; height: 11px; }

/* ---- Page-level note ---------------------------------------------------- */
/* A caveat about the whole page, not a caption under one section — .section-note
   is 12.5px/700, which is right beneath a section label and shouts when it is
   carrying a paragraph. Same shape as .privacy-note, one size up. */
.page-note {
  display: flex; gap: 11px; align-items: flex-start; max-width: 82ch;
  margin-bottom: 22px; padding: 14px 16px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--pop);
  font-size: 13.5px; line-height: 1.6; color: var(--body);
}
.page-note svg { width: 18px; height: 18px; color: var(--peri); flex: none; margin-top: 2px; }

/* ---- Community worlds --------------------------------------------------- */
/* Two up on a wide screen. Full-width cards stretched three short role columns
   across 1100px, so most of every card was empty and the eye had to travel the
   whole width to read "RD13 / None linked / None linked". Halving the card puts
   the three roles back within one glance. */
.worlds {
  list-style: none; padding: 0; margin-top: 22px;
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(430px, 1fr));
}
.world-card {
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-xl); padding: 20px 22px; box-shadow: var(--pop);
}
.world-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
/* The code IS the world's name — Yeeps has no other label for it — so it gets
   the weight a title gets, not the look of a reference number. */
.world-code {
  font-family: var(--font-display); font-size: 24px; color: var(--ink);
  letter-spacing: 0.6px; line-height: 1; word-break: break-all;
}
.world-known { margin-left: auto; font-size: 13px; color: var(--body); font-weight: 700; white-space: nowrap; }
.world-known b { color: var(--ink); font-weight: 800; }

.world-roles {
  display: grid; gap: 14px 16px; margin-top: 14px; padding-top: 14px;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  border-top: 2px dashed var(--border);
}
.world-role dt {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.7px;
  color: var(--faint); font-weight: 800; margin-bottom: 8px;
}
.role-n {
  background: var(--surface-2); border: 1.5px solid var(--border);
  border-radius: 999px; padding: 0 6px; font-size: 10.5px; color: var(--body);
  letter-spacing: 0.3px;
}
.world-role dd { display: flex; flex-wrap: wrap; gap: 6px; }
/* Favourites are a tie, not a role — tint the label so the card reads that way. */
.world-role--fav dt { color: var(--peri-deep); }
.world-role--fav .role-n { border-color: var(--peri); color: var(--peri-deep); background: var(--peri-soft); }

.person {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--surface-2); border: 1.5px solid var(--border);
  border-radius: 999px; padding: 3px 11px 3px 3px;
  transition: border-color var(--dur), background var(--dur), transform var(--dur) var(--ease);
}
.person:hover { border-color: var(--peri); background: var(--peri-soft); transform: translateY(-2px); }
.person-av {
  width: 30px; height: 30px; border-radius: 50%; overflow: hidden; flex: none;
  background: var(--bg-2); display: block;
}
.person-av img { width: 100%; height: 100%; object-fit: cover; }
.person-name { font-family: var(--font-display); font-size: 14.5px; color: var(--ink); line-height: 1; letter-spacing: 0.2px; }
.person:hover .person-name { color: var(--peri-deep); }
/* An absence, stated. Never an empty cell — a blank here would read as a
   rendering bug rather than as "we don't know". */
.person-none {
  font-size: 13px; color: var(--body); font-weight: 700; font-style: normal;
  background: var(--surface-2); border: 1.5px dashed var(--border-2);
  border-radius: 999px; padding: 5px 12px;
}

@media (max-width: 460px) {
  .world-known { margin-left: 0; width: 100%; }
  .world-roles { gap: 14px; }
}

/* ---- Empty state -------------------------------------------------------- */
.empty { text-align: center; padding: 64px 24px; border: 3px dashed var(--border-2); border-radius: var(--r-xl); background: var(--surface); }
.empty-yeep { width: 100px; height: 100px; margin: 0 auto 18px; border-radius: 24px; overflow: hidden; box-shadow: var(--pop); }
.empty h3 { font-family: var(--font-display); font-size: 27px; color: var(--ink); }
.empty p { color: var(--muted); margin-top: 8px; max-width: 42ch; margin-inline: auto; }

/* ---- Toast -------------------------------------------------------------- */
.toast-stack { position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%); z-index: var(--z-toast); display: flex; flex-direction: column; gap: 10px; width: min(92vw, 420px); pointer-events: none; }
.toast { display: flex; align-items: center; gap: 12px; background: var(--surface); border: var(--bw) solid var(--peri); border-radius: var(--r-md); padding: 14px 17px; color: var(--ink); font-weight: 800; box-shadow: var(--lift); animation: toast-in 320ms var(--ease); }
.toast.is-err { border-color: var(--red); }
.toast svg { width: 20px; height: 20px; color: var(--ok); flex: none; }
.toast.is-err svg { color: var(--red); }
@keyframes toast-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }

/* ---- Footer ------------------------------------------------------------- */
/* Navy floor to the red ceiling: the page is bright and the old white footer
   just trailed off into the background, so the shell had a lid and no base.
   Navy is already the ink colour, so this bookends the page without adding a
   hue. The three flat spans it replaces all carried the same weight — brand,
   links and legal fine print now rank in that order. */
.foot {
  margin-top: auto;
  background: var(--navy); color: rgba(255,255,255,0.72);
  border-top: 3px solid var(--peri-deep); font-size: 13.5px;
}
/* Horizontal padding must stay 24px: this element is also a .shell, and the
   shorthand `padding: 34px 0 26px` silently zeroed it, pulling the brand 24px
   left of every other thing on the page while the fine print below (a plain
   .shell) stayed correct. Set the vertical sides only and let .shell own the
   sides it aligns the whole site to. */
.foot-inner {
  display: flex; gap: 24px 32px; align-items: flex-start;
  flex-wrap: wrap; justify-content: space-between;
  padding-top: 34px; padding-bottom: 26px;
}
/* The links carry 12px of their own padding for the hover pill; pulling the row
   out by that much lands the link TEXT on the shell edge, not the pill's box. */
.foot-nav { margin-right: -12px; }
.foot-brand { max-width: 34ch; }
.foot-word {
  font-family: var(--font-display); font-weight: 400; font-size: 22px;
  color: #fff; letter-spacing: 0.4px; line-height: 1;
}
.foot-word em { color: var(--sand); font-style: normal; }
.foot-brand p { margin-top: 7px; color: rgba(255,255,255,0.66); line-height: 1.5; }
.foot-brand a { color: var(--sand); }
.foot-brand a:hover { color: #fff; }

.foot-nav { display: flex; gap: 6px; flex-wrap: wrap; }
.foot-nav a {
  color: rgba(255,255,255,0.8); font-weight: 700; padding: 6px 12px;
  border-radius: var(--r-sm); transition: color var(--dur), background var(--dur);
}
.foot-nav a:hover { color: #fff; background: rgba(255,255,255,0.12); }

/* The disclaimer is the quietest thing here, but it is load-bearing, so it gets
   its own band rather than being squeezed in beside the links. */
.foot-fine { border-top: 2px solid rgba(255,255,255,0.12); }
/* .60 not .50 — at 50% this lands on exactly 4.50:1 against --navy, which is the
   AA threshold with no margin for the rounding either side of it. */
.foot-fine p {
  padding: 16px 0 22px; font-size: 12px; line-height: 1.55;
  color: rgba(255,255,255,0.6); max-width: 76ch;
}

@media (max-width: 560px) {
  /* Same trap as above: vertical only, or .shell's side padding is lost. */
  .foot-inner { gap: 18px; padding-top: 26px; padding-bottom: 20px; }
  /* Stacked here, so the links start at the left edge instead of ending at the right. */
  .foot-nav { margin-left: -12px; margin-right: 0; }
}

/* ---- Long-form prose (privacy / terms) ---------------------------------- */
.prose { max-width: 68ch; color: var(--body); }
.prose p { line-height: 1.65; text-wrap: pretty; }
.prose p + p { margin-top: 13px; }
.prose b { color: var(--ink); font-weight: 800; }
.prose ul { margin-top: 12px; padding: 0; list-style: none; display: grid; gap: 9px; }
.prose li { position: relative; padding-left: 22px; line-height: 1.6; }
.prose li::before {
  content: ""; position: absolute; left: 3px; top: 0.6em;
  width: 7px; height: 7px; border-radius: 2px; background: var(--peri);
}
.prose ul + p { margin-top: 13px; }
.prose-meta { margin-top: 16px; color: var(--muted); font-size: 13px; font-weight: 700; }

/* ---- Refresh strip (bottom of a player page) ---------------------------- */
.refresh-strip {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-top: 30px; padding: 18px 20px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--pop);
}
.refresh-copy { min-width: 0; flex: 1; }
.refresh-copy b { font-family: var(--font-display); font-size: 18px; color: var(--ink); letter-spacing: 0.2px; }
.refresh-copy p { margin-top: 2px; color: var(--body); font-size: 13.5px; font-weight: 700; }
.refresh-strip form { margin-left: auto; }

/* ---- Error page --------------------------------------------------------- */
.error-page { text-align: center; padding: 80px 24px; }
.error-code { font-family: var(--font-display); font-size: clamp(4rem, 14vw, 8rem); color: var(--peri); line-height: 0.9; }
.error-title { font-family: var(--font-display); font-size: 32px; color: var(--ink); margin-top: 6px; }
.error-msg { color: var(--muted); margin-top: 12px; }
.error-detail { margin-top: 18px; font-family: ui-monospace, monospace; font-size: 12px; color: var(--faint); }

/* ---- Responsive --------------------------------------------------------- */
@media (max-width: 820px) { .account-grid { grid-template-columns: 1fr; } }
@media (max-width: 720px) {
  .auth-form--grid { grid-template-columns: 1fr; }
  .cta-band { padding: 20px; }
  .auth-card { padding: 26px; }
  .lb-row { grid-template-columns: 42px 1fr auto; gap: 12px; padding: 13px 16px; }
  .lb-avatar { display: none; }
  .lb-rank { font-size: 23px; }
  .topnav a { padding: 8px 10px; font-size: 16px; }
  .topnav { gap: 2px; }
  .brand { font-size: 23px; }
  main { padding: 30px 0 72px; }
}
@media (max-width: 460px) { .roster { grid-template-columns: 1fr; } .shell { padding: 0 16px; } }

/* ---- Motion ------------------------------------------------------------- */
/* `backwards` holds the start frame through the stagger delay, so nothing
   flashes in before its turn. Fill is only applied while an animation exists —
   if animations never run, every element renders at its normal, visible state,
   so no content is ever gated behind a reveal. */
@keyframes rise { from { opacity: 0; transform: translateY(9px); } }
@keyframes spin { to { transform: rotate(360deg); } }

/* One entrance, shared. Cards, rows and worlds all arrive the same way, because
   the stagger is meant to read as "the page is laying itself out", not as each
   component having its own idea. */
.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel {
  animation: rise 300ms var(--ease) backwards;
}
.page-head { animation: rise 260ms var(--ease) backwards; }

/* The stagger is capped at the 7th child: past that the delay is longer than the
   animation and late cards visibly wait their turn on screen. Everything from
   the 8th on shares the last step, so a 200-player roster still settles in
   ~0.5s instead of ~7s. */
:is(.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel):nth-child(2) { animation-delay: 35ms; }
:is(.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel):nth-child(3) { animation-delay: 70ms; }
:is(.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel):nth-child(4) { animation-delay: 105ms; }
:is(.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel):nth-child(5) { animation-delay: 140ms; }
:is(.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel):nth-child(6) { animation-delay: 175ms; }
:is(.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel):nth-child(7) { animation-delay: 210ms; }
:is(.stat-tile, .slot-card, .player-card, .lb-row, .world-card, .account-panel):nth-child(n+8) { animation-delay: 245ms; }

/* Roster cards sit inside <li>, so :nth-child on the card itself is always 1 —
   the stagger has to come off the list item. */
.roster li:nth-child(2) .player-card { animation-delay: 35ms; }
.roster li:nth-child(3) .player-card { animation-delay: 70ms; }
.roster li:nth-child(4) .player-card { animation-delay: 105ms; }
.roster li:nth-child(5) .player-card { animation-delay: 140ms; }
.roster li:nth-child(6) .player-card { animation-delay: 175ms; }
.roster li:nth-child(7) .player-card { animation-delay: 210ms; }
.roster li:nth-child(n+8) .player-card { animation-delay: 245ms; }
.worlds li:nth-child(2) .world-card { animation-delay: 35ms; }
.worlds li:nth-child(3) .world-card { animation-delay: 70ms; }
.worlds li:nth-child(4) .world-card { animation-delay: 105ms; }
.worlds li:nth-child(5) .world-card { animation-delay: 140ms; }
.worlds li:nth-child(n+6) .world-card { animation-delay: 175ms; }

/* Nav: the active page keeps a solid white pill (set above); hovering another
   grows its underline out from the centre, so the bar responds without the
   layout twitching. */
.topnav a { position: relative; }
.topnav a::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: 4px; height: 2px;
  border-radius: 2px; background: currentColor;
  transform: scaleX(0); transition: transform var(--dur) var(--ease);
}
.topnav a:hover::after { transform: scaleX(1); }
.topnav a[aria-current="page"]::after, .topnav-cta::after { content: none; }

/* World cards lift like roster cards do — same direction, same easing. The
   shadow grows with the lift so the card reads as rising off the page rather
   than sliding up it. (.player-card and .lb-row already define their own hover
   above; they are deliberately not repeated here.) */
.world-card {
  transition: transform var(--dur) var(--ease), box-shadow var(--dur), border-color var(--dur);
}
.world-card:hover { transform: translateY(-3px); border-color: var(--peri); box-shadow: 0 12px 22px rgba(44,47,75,0.14); }

/* The stat a leaderboard is ranked on is the one that earns emphasis. */
.stat-tile.is-ranked { transition: transform var(--dur) var(--ease), box-shadow var(--dur); }
.stat-tile.is-ranked:hover { transform: translateY(-3px); box-shadow: var(--pop-lg); }
.avatar img, .avatar svg { transition: transform var(--dur) var(--ease); }
.player-card:hover .avatar img, .player-card:hover .avatar svg { transform: scale(1.06); }

/* Submitting state: the icon spins while the request is in flight, so a slow
   API call reads as working rather than broken. */
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn:disabled:active { transform: none; }
.btn.is-busy { pointer-events: none; opacity: 0.75; }
.btn.is-busy svg { animation: spin 850ms linear infinite; transform-origin: 50% 50%; }

/* ---- Reduced motion ----------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; animation-delay: 0ms !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
  .player-card:hover, .lb-row:hover, .lb-tab:hover, .btn-google:hover,
  .world-card:hover, .person:hover { transform: none; }
  .player-card:hover .avatar img, .player-card:hover .avatar svg, .stat-tile.is-ranked:hover { transform: none; }
  /* The nav underline is a transform, so it would be the one motion that still
     played. Show it instantly rather than not at all — it is a state, not decoration. */
  .topnav a:hover::after { transition: none; }
  /* The spinner is the only signal that a refresh is running; keep it, slowly. */
  .btn.is-busy svg { animation-duration: 1600ms !important; animation-iteration-count: infinite !important; }
}
