/* pvp.surf design system — "Tideglass".
 *
 * One file, copied byte-for-byte into siteweb/, statsweb/ and storeweb/ (there is no build
 * step to share it any other way). shared_assets_test.go fails the build if the copies drift,
 * so edit it in siteweb/ and copy it across.
 *
 * The idea: the network is seen through deep water. The page is a lit abyss, and everything
 * that holds content is a sheet of clear glass floating in it, with a bright top edge where
 * light catches it. Colour is kept for meaning only:
 * the brand blue is the one accent, green and red are wins and losses, violet is Shards, gold
 * is reserved for ranks and "popular". Everything else is neutral, so content is the only
 * bright thing on screen.
 *
 * Rules this file keeps, and every page stylesheet should too:
 *  - Spacing comes from --s1…--s8 and radii from --r-*; nothing invents a value outside them.
 *  - One elevation model: .ui-glass. A surface separates from what is behind it by blur, a
 *    1px edge and a top highlight, never by a flat fill alone.
 *  - Controls are filled flat. No gradients on buttons, and nothing glows under the cursor.
 *  - Component classes are prefixed ui-. The editor sub-apps bring their own un-prefixed
 *    .panel/.btn/.field, and nothing here may style those by accident.
 *  - Fonts are served immutable without a stamp (CSS cannot carry __V__), so a changed font
 *    file gets a new name (…-2.woff2), never new bytes under the old one.
 */

/* ------------------------------------------------------------------ fonts */

@font-face {
  font-family: "Archivo";
  src: url("fonts/archivo-1.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("fonts/jetbrains-mono-1.woff2") format("woff2");
  font-weight: 100 800;
  font-display: swap;
}
@font-face {
  font-family: "Departure";
  src: url("fonts/departure-mono-1.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

/* ----------------------------------------------------------------- tokens */

:root {
  color-scheme: dark;

  /* water */
  --abyss: #030813;
  --trench: #06122a;
  --shelf: #0b2448;
  --floor: #020510;

  /* glass: fills are translucent on purpose; every text colour below was checked against
     these composited over --trench, the lightest thing they ever sit on */
  --glass: rgba(150, 190, 255, .055);
  --glass-2: rgba(150, 190, 255, .09);
  --glass-3: rgba(170, 205, 255, .14);
  --glass-solid: rgba(9, 22, 44, .82);
  --edge: rgba(175, 210, 255, .14);
  --edge-2: rgba(175, 210, 255, .26);
  --catch: rgba(255, 255, 255, .20);
  --blur: 22px;

  /* ink */
  --ink-hi: #f2f7ff;
  --ink-mid: #b9c8df;
  --ink-lo: #8a9dba;

  /* meaning */
  --surf: #0a84ff;
  --surf-hi: #5cb0ff;
  --surf-ink: #6db8ff;
  /* One flat fill for a primary control; dark ink on it clears AA. */
  --surf-fill: #5cb0ff;
  --surf-fill-hover: #86c7ff;
  --on-surf: #021327;
  --foam: #8be9ff;
  --win: #3fe3a0;
  --loss: #ff7584;
  --shard: #b99bff;
  --gold: #f6c362;
  --warn: #ffb35c;

  /* type */
  /* Archivo carries both roles: tight and heavy at display sizes, even at body sizes. */
  --f-display: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-body: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Figures, labels and code. It has to read at 10px in a table. */
  --f-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  /* The pixel face is the game's own voice, so it is kept for one thing: the nametag that
     floats above a player model. Nowhere else. */
  --f-tag: "Departure", var(--f-mono);

  --t-xs: .75rem;
  --t-sm: .8125rem;
  --t-md: .9375rem;
  --t-lg: 1.125rem;
  --t-xl: clamp(1.5rem, 1.1rem + 1.4vw, 2rem);
  --t-2xl: clamp(2rem, 1.2rem + 3vw, 3.25rem);
  --t-3xl: clamp(2.6rem, 1.2rem + 5.4vw, 5.4rem);

  /* space and shape */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;
  --s8: 72px;
  --r-xs: 6px;
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-xl: 28px;
  --page: 1200px;
  --gutter: clamp(16px, 3.4vw, 32px);

  /* motion */
  --ease: cubic-bezier(.22, 1, .36, 1);
  --ease-io: cubic-bezier(.65, 0, .35, 1);
  --spring: cubic-bezier(.34, 1.56, .64, 1);
  --fast: 160ms;
  --mid: 320ms;
  --slow: 700ms;

  --lift-1: 0 10px 30px -12px rgba(0, 0, 0, .55);
  --lift-2: 0 24px 60px -20px rgba(0, 0, 0, .7);

  /* ---- the contract the editor sub-apps (editweb2/) were drawn against ----
     Their stylesheets read these names. Mapped onto the system above so the editors wear
     the same material as the rest of the site without a line of theirs changing. */
  --bg: var(--abyss);
  --panel: rgba(12, 28, 54, .72);
  --panel-2: rgba(22, 44, 78, .72);
  --card: var(--panel);
  --surface: var(--glass-2);
  --line: var(--edge);
  --line-strong: var(--edge-2);
  --ink-strong: var(--ink-hi);
  --ink: var(--ink-mid);
  --muted: var(--ink-lo);
  --red: #3d9bff;
  --red-dark: #6db8ff;
  --on-fill: var(--on-surf);
  --link: var(--surf-ink);
  --accent-soft: rgba(61, 155, 255, .16);
  --accent-soft-hover: rgba(61, 155, 255, .28);
  --win-soft: rgba(63, 227, 160, .14);
  --loss-soft: rgba(255, 117, 132, .14);
  --maroon: rgba(255, 255, 255, .06);
  --maroon-deep: rgba(255, 255, 255, .1);
  --effect: var(--shard);
  --slot: #8b8b8b;
  --slot-dark: #373737;
  --shadow: var(--lift-1);
  --shadow-lift: var(--lift-2);
  --mono: var(--f-mono);
  --r: var(--r-md);
}

/* ------------------------------------------------------------------- base */

*, *::before, *::after { box-sizing: border-box; }

/* clip, not hidden: hidden would make these scroll containers and break sticky. Both
   elements, because body's overflow propagates to the viewport while html's is visible. */
html, body { overflow-x: clip; }

html {
  background: var(--floor);
  /* Reserve the scrollbar on every page: without it a short page (sign-in, an error state)
     is 15px wider than a long one, and the centred bar jumps between the two. */
  scrollbar-gutter: stable;
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: 96px;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--abyss);
  color: var(--ink-mid);
  font: 400 var(--t-md)/1.6 var(--f-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 {
  margin: 0;
  color: var(--ink-hi);
  font-family: var(--f-display);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -.02em;
  text-wrap: balance;
}
p { margin: 0; text-wrap: pretty; }
a { color: var(--surf-ink); text-decoration: none; }
a:hover { color: var(--ink-hi); }
img { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
code, kbd { font-family: var(--f-mono); }
ol, ul { margin: 0; padding: 0; }
[hidden] { display: none !important; }
::selection { background: rgba(92, 176, 255, .38); color: #fff; }

:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--foam);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

.ui-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.ui-skip {
  position: fixed; left: var(--s4); top: -60px; z-index: 100;
  padding: var(--s2) var(--s4); border-radius: var(--r-sm);
  background: var(--ink-hi); color: var(--abyss); font-weight: 600;
  transition: top var(--fast) var(--ease);
}
.ui-skip:focus-visible { top: var(--s4); color: var(--abyss); }

/* ------------------------------------------------------------ the water */

/* Fixed behind everything. Pure decoration, so it is a background and never an <img>. */
.ui-tide {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  background:
    radial-gradient(120% 60% at 50% -12%, rgba(52, 140, 255, .42), transparent 62%),
    radial-gradient(70% 50% at 100% 0%, rgba(0, 190, 255, .16), transparent 70%),
    radial-gradient(60% 50% at 0% 100%, rgba(40, 90, 200, .18), transparent 70%),
    linear-gradient(180deg, var(--shelf) 0%, var(--trench) 38%, var(--abyss) 78%, var(--floor) 100%);
}
/* the network's own hero art, when grove.yml supplies one, sits faint under the water */
.ui-tide::before {
  content: "";
  position: absolute;
  inset: 0 0 40% 0;
  background: var(--hero-art, none) center 30% / cover no-repeat;
  opacity: .16;
  mix-blend-mode: luminosity;
  -webkit-mask-image: linear-gradient(180deg, #000 10%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 10%, transparent 100%);
}
/* grain: breaks up gradient banding on 8-bit panels */
.ui-tide::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: .07;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 .6 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}

/* light shafts from the surface, swaying slowly */
.ui-rays {
  position: absolute;
  top: -20%;
  left: 50%;
  width: 160vmax;
  height: 110vh;
  translate: -50% 0;
  background: repeating-conic-gradient(from 180deg at 50% 0%,
    transparent 0deg 7deg, rgba(150, 210, 255, .11) 8deg 10deg, transparent 11deg 17deg);
  -webkit-mask-image: radial-gradient(60% 80% at 50% 0%, #000 0%, transparent 75%);
  mask-image: radial-gradient(60% 80% at 50% 0%, #000 0%, transparent 75%);
  filter: blur(6px);
  transform-origin: 50% 0;
  animation: tide-sway 26s var(--ease-io) infinite alternate;
}
@keyframes tide-sway {
  from { transform: rotate(-4deg) scaleX(1); }
  to { transform: rotate(4deg) scaleX(1.12); }
}

/* drifting pools of light: caustics, at the scale of a whole page */
.ui-pool {
  position: absolute;
  width: 46vmax;
  height: 46vmax;
  border-radius: 50%;
  filter: blur(60px);
  opacity: .85;
  will-change: transform;
}
.ui-pool.p1 {
  left: -12vmax; top: 18vh;
  background: radial-gradient(circle, rgba(10, 132, 255, .45), transparent 65%);
  animation: tide-drift-a 38s var(--ease-io) infinite alternate;
}
.ui-pool.p2 {
  right: -16vmax; top: 44vh;
  background: radial-gradient(circle, rgba(0, 200, 255, .26), transparent 65%);
  animation: tide-drift-b 46s var(--ease-io) infinite alternate;
}
.ui-pool.p3 {
  left: 30vw; top: 90vh;
  background: radial-gradient(circle, rgba(120, 90, 255, .20), transparent 65%);
  animation: tide-drift-a 52s var(--ease-io) -20s infinite alternate-reverse;
}
@keyframes tide-drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to { transform: translate3d(14vw, -10vh, 0) scale(1.25); }
}
@keyframes tide-drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to { transform: translate3d(-18vw, 8vh, 0) scale(.9); }
}

/* rising specks; anim.js scatters a few dozen */
.ui-mote {
  position: absolute;
  bottom: -10px;
  width: var(--size, 3px);
  height: var(--size, 3px);
  border-radius: 50%;
  background: rgba(190, 230, 255, .55);
  box-shadow: 0 0 8px rgba(140, 210, 255, .6);
  opacity: 0;
  animation: tide-rise var(--dur, 18s) linear var(--delay, 0s) infinite;
}
@keyframes tide-rise {
  0% { transform: translate3d(0, 0, 0); opacity: 0; }
  10% { opacity: var(--alpha, .5); }
  90% { opacity: var(--alpha, .5); }
  100% { transform: translate3d(var(--sway, 20px), -105vh, 0); opacity: 0; }
}

/* ------------------------------------------------------------- the glass */

.ui-glass {
  position: relative;
  isolation: isolate;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .045), rgba(255, 255, 255, 0) 40%),
    var(--glass);
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(1.5);
  backdrop-filter: blur(var(--blur)) saturate(1.5);
}
/* the bright rim where light catches the top edge */
.ui-glass::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(180deg, var(--catch), rgba(255, 255, 255, .02) 38%, rgba(255, 255, 255, 0) 60%, rgba(140, 200, 255, .08));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}
.ui-glass.is-raised { background-color: var(--glass-2); box-shadow: var(--lift-2); }
.ui-glass.is-solid { background-color: var(--glass-solid); }

/* Browsers without backdrop-filter get a denser fill so text never sits on raw gradient. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .ui-glass { background-color: rgba(10, 24, 48, .9); }
}

.ui-card { padding: var(--s5); }
.ui-card-head {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}
.ui-card-head > h2, .ui-card-head > h3 { font-size: var(--t-lg); letter-spacing: -.015em; }
.ui-card-head .ui-spacer { flex: 1; }
.ui-card.is-flush { padding: 0; overflow: hidden; }
.ui-card.is-flush > .ui-card-head { padding: var(--s4) var(--s5); margin: 0; border-bottom: 1px solid var(--edge); }

/* ------------------------------------------------------------------ layout */

.ui-main {
  flex: 1;
  width: 100%;
  max-width: calc(var(--page) + var(--gutter) * 2);
  margin: 0 auto;
  padding: var(--s5) var(--gutter) var(--s8);
}
.ui-stack { display: flex; flex-direction: column; gap: var(--s4); }
.ui-stack.is-loose { gap: var(--s6); }
.ui-row-flex { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.ui-spacer { flex: 1; }
.ui-narrow { max-width: 680px; margin-inline: auto; width: 100%; }

.ui-sec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  margin: var(--s7) 0 var(--s4);
}
.ui-sec:first-child { margin-top: 0; }
.ui-sec h2 { font-size: var(--t-xl); }
.ui-sec-note { color: var(--ink-lo); font-size: var(--t-sm); }

.ui-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font: 400 var(--t-xs)/1 var(--f-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--surf-ink);
}

/* ------------------------------------------------------------------ top bar */

.ui-top {
  position: sticky;
  top: 0;
  z-index: 40;
  padding: var(--s3) var(--gutter) 0;
}
/* The bar must be the same bar on every page of every site: the wordmark, the nav and the
   right-hand controls may not move a pixel when you cross from the forums to stats to the
   store. The nav is centred in what is left between the two side slots, so that only holds if
   those slots match everywhere — hence --bar-side, which every site's right slot reserves
   whatever it happens to put in it. */
.ui-top-inner {
  --bar-side: 260px;
  max-width: var(--page);
  margin: 0 auto;
  height: 60px;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: 0 var(--s2) 0 var(--s4);
  border-radius: var(--r-lg);
  transition: background-color var(--mid) var(--ease), box-shadow var(--mid) var(--ease);
}
html.is-scrolled .ui-top-inner { background-color: rgba(8, 20, 42, .66); box-shadow: var(--lift-2); }

.ui-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  flex-shrink: 0;
  color: var(--ink-hi);
  font: 700 1.25rem/1 var(--f-display);
  letter-spacing: -.03em;
}
.ui-brand:hover { color: var(--ink-hi); }
/* Half the mark is white, so it needs a dark ground — never a white tile. */
.ui-brand-mark {
  width: 34px;
  height: 34px;
  padding: 3px;
  border-radius: var(--r-sm);
  background: rgba(6, 18, 40, .85);
  box-shadow: 0 0 0 1px rgba(175, 210, 255, .18) inset, 0 6px 18px -8px rgba(10, 132, 255, .7);
  transition: transform var(--mid) var(--spring);
}
.ui-brand:hover .ui-brand-mark { transform: rotate(-8deg) scale(1.06); }
.ui-brand-dot { color: var(--surf-hi); }

.ui-nav {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0 auto;
  padding: 4px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, .03);
  box-shadow: 0 0 0 1px rgba(175, 210, 255, .08) inset;
}
.ui-nav a {
  position: relative;
  z-index: 1;
  padding: 7px 14px;
  border-radius: var(--r-sm);
  color: var(--ink-mid);
  font-size: var(--t-sm);
  font-weight: 500;
  white-space: nowrap;
  transition: color var(--fast) var(--ease);
}
.ui-nav a:hover { color: var(--ink-hi); }
.ui-nav a.is-on { color: var(--ink-hi); }
/* fallback for the indicator before anim.js places it, and with it off */
.ui-nav a.is-on:not(.has-ind *) { background: var(--glass-3); }
.ui-nav-ind {
  position: absolute;
  top: 4px;
  left: 0;
  height: calc(100% - 8px);
  border-radius: var(--r-sm);
  background: rgba(160, 205, 255, .16);
  box-shadow: 0 0 0 1px rgba(190, 220, 255, .18) inset, 0 6px 16px -8px rgba(10, 132, 255, .9);
  transition: transform var(--mid) var(--ease), width var(--mid) var(--ease), opacity var(--fast);
  pointer-events: none;
}

/* Reserved to one width on all three sites — a search hint and a viewer chip here, a search
   field there, a wallet and a basket on the store — so the nav beside it centres in the same
   place on each. Content is right-aligned in it, so the last control ends on the same pixel. */
.ui-top-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  flex-shrink: 0;
  min-width: var(--bar-side);
}

.ui-menu-btn {
  display: none;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--r-sm);
  background: var(--glass-2);
  color: var(--ink-hi);
  place-items: center;
}
.ui-menu-btn svg { width: 20px; height: 20px; }
.ui-menu-btn .x { display: none; }
html.nav-open .ui-menu-btn[aria-controls] .bars { display: none; }
html.nav-open .ui-menu-btn[aria-controls] .x { display: block; }

.ui-kbd-hint {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  height: 38px;
  padding: 0 var(--s2) 0 var(--s3);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .03);
  color: var(--ink-lo);
  font-size: var(--t-sm);
  white-space: nowrap;
}
.ui-kbd-hint:hover { color: var(--ink-hi); border-color: var(--edge-2); }
.ui-kbd-hint svg { width: 16px; height: 16px; }

@media (max-width: 1100px) {
  .ui-kbd-hint .ui-kbd-label { display: none; }
}
/* Below this the wordmark is dropped to the mark alone — on every site, so the bar narrows
   the same way whichever one you are on. */
@media (max-width: 420px) {
  .ui-brand > span { display: none; }
}

/* 960, not 820: below this the six-link nav plus the two slots no longer fit the bar, and the
   right-hand controls were being clipped. */
@media (max-width: 960px) {
  /* the nav is a dropdown here, so there is nothing to centre and nothing to reserve */
  .ui-top-inner { --bar-side: 0px; }
  .ui-menu-btn { display: grid; }
  .ui-kbd-hint { display: none; }
  .ui-top-inner { padding-right: 6px; }
  .ui-nav {
    position: absolute;
    left: var(--gutter);
    right: var(--gutter);
    top: calc(100% + var(--s2));
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: var(--s2);
    border-radius: var(--r-lg);
    background: rgba(8, 20, 42, .92);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(1.5);
    backdrop-filter: blur(var(--blur)) saturate(1.5);
    box-shadow: 0 0 0 1px var(--edge) inset, var(--lift-2);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px) scale(.98);
    transform-origin: top center;
    transition: opacity var(--fast) var(--ease), transform var(--mid) var(--ease), visibility 0s linear var(--mid);
  }
  html.nav-open .ui-nav {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity var(--fast) var(--ease), transform var(--mid) var(--ease), visibility 0s;
  }
  .ui-nav a { padding: 12px 16px; font-size: var(--t-md); }
  .ui-nav a.is-on { background: var(--glass-3); }
  .ui-nav-ind { display: none; }
  .ui-top-right { margin-left: auto; }
}

/* ------------------------------------------------------------------ footer */

.ui-foot {
  margin-top: auto;
  padding: var(--s6) var(--gutter) var(--s6);
  border-top: 1px solid rgba(175, 210, 255, .07);
  background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .35));
}
.ui-foot-inner {
  max-width: var(--page);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.2fr repeat(3, minmax(0, 1fr));
  gap: var(--s5);
}
.ui-foot h4 {
  margin-bottom: var(--s3);
  font: 400 var(--t-xs)/1 var(--f-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-lo);
}
.ui-foot ul { list-style: none; display: grid; gap: var(--s2); }
.ui-foot a { color: var(--ink-mid); font-size: var(--t-sm); }
.ui-foot a:hover { color: var(--ink-hi); }
.ui-foot-brand p { margin-top: var(--s3); color: var(--ink-lo); font-size: var(--t-sm); max-width: 32ch; }
.ui-foot-legal {
  max-width: var(--page);
  margin: var(--s6) auto 0;
  padding-top: var(--s4);
  border-top: 1px solid rgba(175, 210, 255, .07);
  color: var(--ink-lo);
  font-size: var(--t-xs);
  line-height: 1.6;
}
@media (max-width: 720px) {
  .ui-foot-inner { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ui-foot-brand { grid-column: 1 / -1; }
}

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

.ui-btn {
  --btn-bg: var(--glass-2);
  --btn-ink: var(--ink-hi);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 40px;
  padding: 0 var(--s4);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  background: var(--btn-bg);
  color: var(--btn-ink);
  font-size: var(--t-sm);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--fast) var(--ease), background-color var(--fast) var(--ease),
    border-color var(--fast) var(--ease), box-shadow var(--mid) var(--ease), opacity var(--fast);
  -webkit-tap-highlight-color: transparent;
}
.ui-btn:hover { color: var(--btn-ink); border-color: var(--edge-2); background-color: var(--glass-3); }
.ui-btn:active { transform: translateY(1px) scale(.985); }
.ui-btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.ui-btn[disabled], .ui-btn.is-disabled {
  opacity: .45;
  cursor: not-allowed;
  transform: none;
}

.ui-btn--primary {
  --btn-ink: var(--on-surf);
  border-color: transparent;
  background: var(--surf-fill);
  box-shadow: 0 10px 26px -10px rgba(10, 132, 255, .95);
}
.ui-btn--primary:hover {
  border-color: transparent;
  background: var(--surf-fill-hover);
  box-shadow: 0 14px 34px -10px rgba(10, 132, 255, 1);
}

.ui-btn--ghost { --btn-bg: transparent; border-color: transparent; color: var(--ink-mid); }
.ui-btn--ghost:hover { color: var(--ink-hi); background-color: var(--glass-2); border-color: var(--edge); }
.ui-btn--danger { --btn-ink: #ffd6db; --btn-bg: rgba(255, 117, 132, .12); border-color: rgba(255, 117, 132, .3); }
.ui-btn--danger:hover { background-color: rgba(255, 117, 132, .22); border-color: rgba(255, 117, 132, .5); color: #fff; }
.ui-btn--shard {
  --btn-ink: #1a0b3a;
  border-color: rgba(255, 255, 255, .35);
  background: #bda4ff;
  box-shadow: 0 10px 24px -10px rgba(150, 110, 255, .9);
}
.ui-btn--shard:hover { background: #d3c2ff; }
.ui-btn--sm { min-height: 32px; padding: 0 var(--s3); font-size: var(--t-xs); }
.ui-btn--lg { min-height: 52px; padding: 0 var(--s5); font-size: var(--t-md); }
.ui-btn--block { display: flex; width: 100%; }
.ui-btn--icon { width: 40px; padding: 0; }
.ui-btn--icon.ui-btn--sm { width: 32px; }
.ui-btn.is-done { --btn-ink: #03261a; background: #52e2a6; border-color: rgba(255, 255, 255, .4); }
.ui-btn.is-busy { color: transparent; pointer-events: none; }
.ui-btn.is-busy::before {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--btn-ink);
  border-right-color: transparent;
  animation: ui-turn 700ms linear infinite;
}

.ui-link {
  border: 0;
  padding: 0;
  background: none;
  color: var(--surf-ink);
  font-size: inherit;
  font-weight: 500;
}
.ui-link:hover { color: var(--ink-hi); }
.ui-link.is-quiet { color: var(--ink-lo); }
.ui-link.is-quiet:hover { color: var(--ink-hi); }
.ui-link.is-danger:hover { color: var(--loss); }

/* ------------------------------------------------------------------ forms */

.ui-label {
  display: block;
  margin-bottom: var(--s2);
  color: var(--ink-mid);
  font-size: var(--t-sm);
  font-weight: 500;
}
.ui-field + .ui-field { margin-top: var(--s4); }
.ui-input {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  background: rgba(2, 8, 20, .45);
  color: var(--ink-hi);
  font-size: var(--t-md);
  line-height: 1.5;
  box-shadow: 0 1px 0 rgba(255, 255, 255, .03), 0 2px 6px rgba(0, 0, 0, .25) inset;
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease), background-color var(--fast);
}
.ui-input::placeholder { color: rgba(138, 157, 186, .75); }
.ui-input:hover { border-color: var(--edge-2); }
.ui-input:focus, .ui-input:focus-visible {
  outline: none;
  border-color: rgba(92, 176, 255, .7);
  background-color: rgba(2, 8, 20, .6);
  box-shadow: 0 0 0 4px rgba(10, 132, 255, .18);
}
.ui-input[readonly] { color: var(--ink-mid); background: rgba(255, 255, 255, .03); }
textarea.ui-input { min-height: 120px; resize: vertical; }
select.ui-input {
  appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='%23b9c8df' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 18px;
}
select.ui-input option { background: #0a1a33; }
.ui-input-row { display: flex; gap: var(--s2); flex-wrap: wrap; }
.ui-input-row > .ui-input { flex: 1 1 180px; width: auto; }
.ui-input-row > .ui-input.is-short { flex: 0 1 140px; }

.ui-form-error {
  min-height: 0;
  color: var(--loss);
  font-size: var(--t-sm);
}
.ui-form-error:not(:empty) {
  margin: var(--s3) 0;
  padding: var(--s2) var(--s3);
  border-radius: var(--r-sm);
  background: rgba(255, 117, 132, .1);
  animation: ui-shake 360ms var(--ease);
}
@keyframes ui-shake {
  20%, 60% { transform: translateX(-4px); }
  40%, 80% { transform: translateX(4px); }
}
.ui-hint { color: var(--ink-lo); font-size: var(--t-sm); line-height: 1.6; }

/* ------------------------------------------------------------ small parts */

.ui-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 8px;
  border-radius: var(--r-xs);
  background: rgba(175, 210, 255, .08);
  box-shadow: 0 0 0 1px rgba(175, 210, 255, .12) inset;
  color: var(--ink-mid);
  font: 400 11px/1 var(--f-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}
.ui-tag.is-accent { color: var(--surf-ink); background: rgba(10, 132, 255, .12); box-shadow: 0 0 0 1px rgba(10, 132, 255, .3) inset; }
.ui-tag.is-win { color: var(--win); background: rgba(63, 227, 160, .1); box-shadow: 0 0 0 1px rgba(63, 227, 160, .28) inset; }
.ui-tag.is-loss { color: var(--loss); background: rgba(255, 117, 132, .1); box-shadow: 0 0 0 1px rgba(255, 117, 132, .28) inset; }
.ui-tag.is-warn { color: var(--warn); background: rgba(255, 179, 92, .1); box-shadow: 0 0 0 1px rgba(255, 179, 92, .28) inset; }
.ui-tag.is-gold { color: var(--gold); background: rgba(246, 195, 98, .1); box-shadow: 0 0 0 1px rgba(246, 195, 98, .3) inset; }
.ui-tag.is-shard { color: var(--shard); background: rgba(185, 155, 255, .1); box-shadow: 0 0 0 1px rgba(185, 155, 255, .3) inset; }
.ui-tag svg { width: 12px; height: 12px; }

/* A rank, in the rank's own colour. --rank arrives inline from the network; legacy ranks
   without a hex fall back to the accent. The colour is used for a dot and a tinted edge,
   and the label stays light, so any hex the network sends stays readable. */
.ui-rank {
  --rank-c: var(--rank, var(--surf-hi));
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 24px;
  padding: 0 10px 0 8px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--rank-c) 16%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--rank-c) 45%, transparent) inset;
  color: color-mix(in srgb, var(--rank-c) 35%, #fff);
  font: 400 11px/1 var(--f-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}
.ui-rank::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background: var(--rank-c);
  box-shadow: 0 0 10px var(--rank-c);
  transform: rotate(45deg);
}
.ui-rank:empty { display: none; }

/* A player's face. Heads come from a third party that goes away for minutes at a time, so the
   frame carries its own colour and the image fades in only once decoded (ui.js). */
.ui-head {
  --size: 32px;
  position: relative;
  display: inline-block;
  flex-shrink: 0;
  width: var(--size);
  height: var(--size);
  border-radius: calc(var(--size) * .22);
  background: linear-gradient(135deg, #1b3a66, #0c1d38);
  overflow: hidden;
  vertical-align: middle;
}
.ui-head img {
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  opacity: 0;
  transition: opacity var(--mid) var(--ease);
}
.ui-head img.is-loaded { opacity: 1; }
.ui-head.is-ranked {
  overflow: visible;
  box-shadow: 0 0 0 2px var(--abyss), 0 0 0 4px var(--rank, var(--surf-hi)), 0 0 18px -2px var(--rank, var(--surf-hi));
}
.ui-head.is-ranked img { border-radius: inherit; }
.ui-head .ui-presence {
  position: absolute;
  right: -3px;
  bottom: -3px;
}

.ui-presence {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--ink-lo);
  box-shadow: 0 0 0 2px var(--abyss);
}
.ui-presence.is-on { background: var(--win); animation: ui-ping 2.4s var(--ease) infinite; }
@keyframes ui-ping {
  0% { box-shadow: 0 0 0 2px var(--abyss), 0 0 0 2px rgba(63, 227, 160, .6); }
  70%, 100% { box-shadow: 0 0 0 2px var(--abyss), 0 0 0 9px rgba(63, 227, 160, 0); }
}

.ui-live {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--ink-mid);
  font: 400 var(--t-xs)/1 var(--f-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ui-live::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--win);
  box-shadow: 0 0 0 0 rgba(63, 227, 160, .6);
  animation: ui-live 2s var(--ease) infinite;
}
.ui-live.is-off::before { background: var(--ink-lo); animation: none; }
@keyframes ui-live {
  0% { box-shadow: 0 0 0 0 rgba(63, 227, 160, .55); }
  80%, 100% { box-shadow: 0 0 0 8px rgba(63, 227, 160, 0); }
}

.ui-kbd, .cmd {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--r-xs);
  background: rgba(175, 210, 255, .1);
  box-shadow: 0 -1px 0 rgba(0, 0, 0, .35) inset, 0 0 0 1px rgba(175, 210, 255, .16) inset;
  color: var(--ink-hi);
  font: 400 .85em/1.5 var(--f-mono);
  white-space: nowrap;
}

.ui-meter {
  position: relative;
  height: 6px;
  border-radius: var(--r-sm);
  background: rgba(175, 210, 255, .1);
  overflow: hidden;
}
.ui-meter > i {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--v, 0%);
  border-radius: inherit;
  background: linear-gradient(90deg, var(--surf), var(--foam));
  box-shadow: 0 0 12px rgba(92, 176, 255, .6);
  transition: width 900ms var(--ease);
}
.ui-meter.is-win > i { background: linear-gradient(90deg, #1fb57a, var(--win)); box-shadow: 0 0 12px rgba(63, 227, 160, .45); }
.ui-meter.is-split { background: rgba(255, 117, 132, .35); }

.ui-stat {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  min-width: 0;
}
.ui-stat-label {
  font: 400 11px/1 var(--f-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-lo);
}
.ui-stat-value {
  color: var(--ink-hi);
  font: 400 clamp(1.4rem, 1rem + 1.4vw, 2rem)/1 var(--f-mono);
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.ui-stat-sub { color: var(--ink-lo); font-size: var(--t-xs); }
.is-win-ink { color: var(--win); }
.is-loss-ink { color: var(--loss); }
.is-accent-ink { color: var(--surf-ink); }
.is-shard-ink { color: var(--shard); }

/* A row of a list inside a flush card: forum boards, threads, matches, bans. */
.ui-list { display: flex; flex-direction: column; }
.ui-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  color: inherit;
  transition: background-color var(--fast) var(--ease);
}
.ui-item + .ui-item { border-top: 1px solid rgba(175, 210, 255, .07); }
a.ui-item:hover, .ui-item.is-link:hover { background: rgba(150, 190, 255, .05); color: inherit; cursor: pointer; }
a.ui-item::before, .ui-item.is-link::before {
  content: "";
  position: absolute;
  left: 0;
  top: 18%;
  bottom: 18%;
  width: 2px;
  border-radius: 2px;
  background: var(--surf-hi);
  box-shadow: 0 0 12px var(--surf);
  transform: scaleY(0);
  transition: transform var(--mid) var(--ease);
}
a.ui-item:hover::before, .ui-item.is-link:hover::before { transform: scaleY(1); }
.ui-item-main { flex: 1; min-width: 0; }
.ui-item-title {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  color: var(--ink-hi);
  font-weight: 600;
  line-height: 1.35;
}
.ui-item-sub { margin-top: 2px; color: var(--ink-lo); font-size: var(--t-sm); }
.ui-item-side { flex-shrink: 0; text-align: right; color: var(--ink-lo); font-size: var(--t-sm); }
.ui-item.is-gone { opacity: .5; }
.ui-num {
  font: 400 var(--t-lg)/1 var(--f-mono);
  color: var(--ink-hi);
  font-variant-numeric: tabular-nums;
}
.ui-num small {
  display: block;
  margin-top: 4px;
  font: 400 10px/1 var(--f-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-lo);
}

.ui-crumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-bottom: var(--s4);
  color: var(--ink-lo);
  font-size: var(--t-sm);
}
.ui-crumbs a { color: var(--ink-mid); }
.ui-crumbs a:hover { color: var(--ink-hi); }
.ui-crumbs svg { width: 14px; height: 14px; opacity: .6; }

.ui-back {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s4);
  color: var(--ink-mid);
  font-size: var(--t-sm);
}
.ui-back svg { width: 16px; height: 16px; transition: transform var(--fast) var(--ease); }
.ui-back:hover svg { transform: translateX(-3px); }

/* Segmented control. anim.js slides a thumb under the active option. */
.ui-tabs {
  position: relative;
  display: inline-flex;
  max-width: 100%;
  padding: 4px;
  gap: 2px;
  border-radius: var(--r-md);
  background: rgba(2, 8, 20, .4);
  box-shadow: 0 0 0 1px var(--edge) inset;
  overflow-x: auto;
  scrollbar-width: none;
}
.ui-tabs::-webkit-scrollbar { display: none; }
.ui-tabs > a, .ui-tabs > button {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  padding: 8px 16px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink-mid);
  font-size: var(--t-sm);
  font-weight: 500;
  white-space: nowrap;
  transition: color var(--fast);
}
.ui-tabs > a:hover, .ui-tabs > button:hover { color: var(--ink-hi); }
.ui-tabs > .is-on { color: var(--on-surf); }
.ui-tabs:not(.has-ind) > .is-on { background: var(--surf-fill); }
.ui-tabs-ind {
  position: absolute;
  top: 4px;
  left: 0;
  height: calc(100% - 8px);
  border-radius: var(--r-sm);
  background: var(--surf-fill);
  box-shadow: 0 8px 20px -8px rgba(10, 132, 255, .9);
  transition: transform var(--mid) var(--ease), width var(--mid) var(--ease);
}

.ui-steps { list-style: none; counter-reset: step; display: grid; gap: var(--s3); }
.ui-steps li {
  counter-increment: step;
  position: relative;
  padding: var(--s3) var(--s4) var(--s3) 56px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, .025);
  box-shadow: 0 0 0 1px rgba(175, 210, 255, .08) inset;
  color: var(--ink-mid);
}
.ui-steps li::before {
  content: counter(step);
  position: absolute;
  left: var(--s3);
  top: 50%;
  translate: 0 -50%;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: rgba(10, 132, 255, .16);
  box-shadow: 0 0 0 1px rgba(10, 132, 255, .35) inset;
  color: var(--surf-ink);
  font: 400 var(--t-sm)/1 var(--f-mono);
}
.ui-steps b { color: var(--ink-hi); font-weight: 600; }

/* ----------------------------------------------- loading, empty, error */

.ui-skel {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: rgba(175, 210, 255, .07);
}
.ui-skel::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(175, 210, 255, .09), transparent);
  animation: ui-shimmer 1.4s var(--ease-io) infinite;
}
@keyframes ui-shimmer { to { transform: translateX(100%); } }
.ui-skel.is-line { height: 12px; }
.ui-skel.is-title { height: 22px; width: 40%; }
.ui-skel.is-block { height: 120px; border-radius: var(--r-lg); }
.ui-skel.is-round { border-radius: 50%; }
.ui-skel-stack { display: grid; gap: var(--s3); }

.ui-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  padding: var(--s7) var(--s5);
  text-align: center;
}
.ui-empty-icon {
  width: 56px;
  height: 56px;
  margin-bottom: var(--s2);
  display: grid;
  place-items: center;
  border-radius: var(--r-md);
  background: rgba(175, 210, 255, .06);
  box-shadow: 0 0 0 1px var(--edge) inset;
  color: var(--ink-lo);
}
.ui-empty-icon svg { width: 26px; height: 26px; }
.ui-empty h3 { font-size: var(--t-lg); }
.ui-empty p { color: var(--ink-lo); font-size: var(--t-sm); max-width: 44ch; }
.ui-empty .ui-btn { margin-top: var(--s3); }
.ui-empty.is-compact { padding: var(--s5); }
.ui-empty.is-error .ui-empty-icon { color: var(--loss); background: rgba(255, 117, 132, .08); box-shadow: 0 0 0 1px rgba(255, 117, 132, .25) inset; }

.ui-notice {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: rgba(10, 132, 255, .09);
  box-shadow: 0 0 0 1px rgba(10, 132, 255, .28) inset;
  color: var(--ink-hi);
  font-size: var(--t-sm);
}
.ui-notice svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; color: var(--surf-ink); }
.ui-notice.is-warn { background: rgba(255, 179, 92, .08); box-shadow: 0 0 0 1px rgba(255, 179, 92, .3) inset; }
.ui-notice.is-warn svg { color: var(--warn); }
.ui-notice.is-bad { background: rgba(255, 117, 132, .08); box-shadow: 0 0 0 1px rgba(255, 117, 132, .3) inset; }
.ui-notice.is-bad svg { color: var(--loss); }
.ui-notice.is-ok { background: rgba(63, 227, 160, .08); box-shadow: 0 0 0 1px rgba(63, 227, 160, .3) inset; }
.ui-notice.is-ok svg { color: var(--win); }

/* The names the editor sub-apps render into without styling themselves. Kept un-prefixed
   on purpose; nothing in the new pages uses them. */
.loading { display: grid; place-items: center; min-height: 240px; }
.spin, .ui-spin {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 3px solid rgba(175, 210, 255, .14);
  border-top-color: var(--surf-hi);
  animation: ui-turn 800ms linear infinite;
}
@keyframes ui-turn { to { transform: rotate(360deg); } }
.error-box {
  max-width: 520px;
  margin: var(--s7) auto;
  padding: var(--s6);
  border-radius: var(--r-lg);
  background: var(--panel);
  box-shadow: 0 0 0 1px var(--edge) inset;
  text-align: center;
}
.error-box h2 { font-size: var(--t-xl); margin-bottom: var(--s2); }
.error-box p { color: var(--ink-lo); }

/* ------------------------------------------------------------ toasts */

.ui-toasts {
  position: fixed;
  left: 50%;
  bottom: var(--s5);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  width: min(460px, calc(100vw - 32px));
  translate: -50% 0;
  pointer-events: none;
}
.ui-toast {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: rgba(10, 24, 48, .9);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(1.5);
  backdrop-filter: blur(var(--blur)) saturate(1.5);
  box-shadow: 0 0 0 1px var(--edge-2) inset, var(--lift-2);
  color: var(--ink-hi);
  font-size: var(--t-sm);
  pointer-events: auto;
  animation: ui-toast-in var(--mid) var(--spring) both;
}
.ui-toast.is-leaving { animation: ui-toast-out var(--fast) var(--ease) both; }
.ui-toast svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--surf-ink); }
.ui-toast.is-ok svg { color: var(--win); }
.ui-toast.is-bad svg { color: var(--loss); }
@keyframes ui-toast-in { from { opacity: 0; transform: translateY(16px) scale(.96); } }
@keyframes ui-toast-out { to { opacity: 0; transform: translateY(8px) scale(.98); } }

/* the editor's own toast, restyled */
.toast {
  position: fixed;
  left: 50%;
  bottom: var(--s5);
  z-index: 90;
  max-width: calc(100vw - 32px);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  translate: -50% 0;
  background: rgba(10, 24, 48, .92);
  box-shadow: 0 0 0 1px var(--edge-2) inset, var(--lift-2);
  color: var(--ink-hi);
  font-size: var(--t-sm);
  animation: ui-toast-in var(--mid) var(--spring) both;
}
.toast.error, .toast.bad { box-shadow: 0 0 0 1px rgba(255, 117, 132, .5) inset, var(--lift-2); }

/* ------------------------------------------------ command palette */

.ui-pal {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: grid;
  align-items: start;
  justify-items: center;
  padding: 12vh var(--s4) var(--s4);
  background: rgba(1, 4, 12, .55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  animation: ui-fade var(--fast) var(--ease) both;
}
@keyframes ui-fade { from { opacity: 0; } }
.ui-pal-box {
  width: min(600px, 100%);
  overflow: hidden;
  border-radius: var(--r-lg);
  background: rgba(10, 24, 48, .88);
  animation: ui-pop var(--mid) var(--spring) both;
}
@keyframes ui-pop { from { opacity: 0; transform: translateY(-8px) scale(.97); } }
.ui-pal-field {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 0 var(--s4);
  border-bottom: 1px solid var(--edge);
}
.ui-pal-field svg { width: 18px; height: 18px; color: var(--ink-lo); }
.ui-pal-field input {
  flex: 1;
  height: 56px;
  border: 0;
  background: none;
  color: var(--ink-hi);
  font-size: var(--t-lg);
  outline: none;
}
.ui-pal-list { max-height: min(52vh, 420px); overflow-y: auto; padding: var(--s2); }
.ui-pal-group {
  padding: var(--s3) var(--s3) var(--s1);
  font: 400 10px/1 var(--f-mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-lo);
}
.ui-pal-opt {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: 10px var(--s3);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink-mid);
  text-align: left;
}
.ui-pal-opt .ui-head { --size: 22px; }
.ui-pal-opt.is-on { background: rgba(10, 132, 255, .18); color: var(--ink-hi); box-shadow: 0 0 0 1px rgba(10, 132, 255, .35) inset; }
.ui-pal-opt small { margin-left: auto; color: var(--ink-lo); font: 400 11px/1 var(--f-mono); }
.ui-pal-foot {
  display: flex;
  gap: var(--s4);
  padding: var(--s2) var(--s4);
  border-top: 1px solid var(--edge);
  color: var(--ink-lo);
  font-size: var(--t-xs);
}
.ui-pal-empty { padding: var(--s5); text-align: center; color: var(--ink-lo); font-size: var(--t-sm); }

/* --------------------------------------------------- lightbox, totop */

.ui-lightbox {
  position: fixed;
  inset: 0;
  z-index: 96;
  display: grid;
  place-items: center;
  padding: var(--s5);
  background: rgba(1, 4, 12, .86);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  cursor: zoom-out;
  animation: ui-fade var(--fast) var(--ease) both;
}
.ui-lightbox img {
  max-width: 100%;
  max-height: 88vh;
  border-radius: var(--r-md);
  box-shadow: var(--lift-2);
  animation: ui-pop var(--mid) var(--spring) both;
}

.ui-totop {
  position: fixed;
  right: var(--s5);
  bottom: var(--s5);
  z-index: 30;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  color: var(--ink-hi);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity var(--mid) var(--ease), transform var(--mid) var(--ease), visibility 0s linear var(--mid);
}
.ui-totop.is-shown {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity var(--mid) var(--ease), transform var(--mid) var(--ease), visibility 0s;
}
.ui-totop svg { width: 18px; height: 18px; }
.ui-totop .ring {
  position: absolute;
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  transform: rotate(-90deg);
}
.ui-totop .ring circle { fill: none; stroke: var(--surf-hi); stroke-width: 2; stroke-linecap: round; }

/* --------------------------------------------------------- motion */

/* Each view animates in when the router swaps it. anim.js adds .ui-enter. */
.ui-enter > * { animation: ui-view-in 560ms var(--ease) both; }
.ui-enter > *:nth-child(2) { animation-delay: 50ms; }
.ui-enter > *:nth-child(3) { animation-delay: 100ms; }
.ui-enter > *:nth-child(4) { animation-delay: 150ms; }
.ui-enter > *:nth-child(n+5) { animation-delay: 200ms; }
@keyframes ui-view-in {
  from { opacity: 0; transform: translateY(14px); filter: blur(4px); }
}

/* Scroll reveal. Only hidden when the script that reveals it is running (html.js-anim is
   set by anim.js), so no script means nothing is hidden. */
html.js-anim [data-reveal]:not(.is-in) { opacity: 0; transform: translateY(22px); }
html.js-anim [data-reveal] {
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
  transition-delay: calc(var(--i, 0) * 60ms);
}

/* ------------------------------------------------ the player rig */

/* A Minecraft player, built from cubes in CSS 3D and skinned with the player's own texture
   (rig.js). Every length is a multiple of --p, one skin pixel. */
.rig-stage {
  --p: 9px;
  position: relative;
  display: grid;
  place-items: center;
  width: calc(var(--p) * 30);
  height: calc(var(--p) * 44);
  perspective: calc(var(--p) * 180);
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}
.rig-stage.is-dragging { cursor: grabbing; }
.rig-floor {
  position: absolute;
  left: 50%;
  bottom: calc(var(--p) * 2);
  width: calc(var(--p) * 26);
  height: calc(var(--p) * 6);
  translate: -50% 0;
  border-radius: 50%;
  background:
    radial-gradient(closest-side, rgba(0, 0, 0, .65), transparent 70%),
    radial-gradient(closest-side, rgba(92, 176, 255, .35), transparent 100%);
  box-shadow: 0 0 0 1px rgba(160, 210, 255, .18), 0 0 60px -10px rgba(10, 132, 255, .8);
}
.rig-floor::after {
  content: "";
  position: absolute;
  inset: -30%;
  border-radius: 50%;
  border: 1px solid rgba(160, 210, 255, .12);
  animation: rig-ripple 3.6s var(--ease) infinite;
}
@keyframes rig-ripple {
  from { transform: scale(.7); opacity: 1; }
  to { transform: scale(1.25); opacity: 0; }
}
.rig-world {
  position: absolute;
  left: 50%;
  bottom: calc(var(--p) * 5);
  width: calc(var(--p) * 16);
  height: calc(var(--p) * 32);
  margin-left: calc(var(--p) * -8);
  transform-style: preserve-3d;
  transform: rotateX(calc(var(--tilt, -8) * 1deg)) rotateY(calc(var(--yaw, -28) * 1deg));
}
.rig-body-wrap {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  animation: rig-bob 3.2s var(--ease-io) infinite;
}
@keyframes rig-bob {
  50% { transform: translateY(calc(var(--p) * -.35)); }
}
.rig-part {
  position: absolute;
  transform-style: preserve-3d;
}
.rig-face {
  position: absolute;
  background-image: var(--skin);
  background-repeat: no-repeat;
  background-size: calc(var(--p) * 64) calc(var(--p) * var(--skin-h, 64));
  image-rendering: pixelated;
  backface-visibility: hidden;
}
/* The overlay's far faces stay hidden. Left visible, you see the inside of the back of a hat
   or sleeve through its transparent front, which reads as a dark smear across the model. */
.rig-part.is-layer .rig-face { backface-visibility: hidden; }
/* inner faces are shaded like Minecraft's own lighting: top brightest, bottom darkest */
.rig-face.f-top { filter: brightness(1.08); }
.rig-face.f-bottom { filter: brightness(.55); }
.rig-face.f-left, .rig-face.f-right { filter: brightness(.8); }
.rig-face.f-back { filter: brightness(.7); }

.rig-head { transform-origin: 50% 100%; transform: rotateY(calc(var(--look-x, 0) * 1deg)) rotateX(calc(var(--look-y, 0) * 1deg)); transition: transform 240ms var(--ease); }
.rig-arm, .rig-leg { transform-origin: 50% calc(var(--p) * 2); }
.rig-leg { transform-origin: 50% 0; }

/* idle: arms sway, as in game */
.rig-stage[data-pose="idle"] .rig-arm.r { animation: rig-arm-r 3.2s var(--ease-io) infinite; }
.rig-stage[data-pose="idle"] .rig-arm.l { animation: rig-arm-l 3.2s var(--ease-io) infinite; }
@keyframes rig-arm-r { 50% { transform: rotateZ(4deg) rotateX(4deg); } }
@keyframes rig-arm-l { 50% { transform: rotateZ(-4deg) rotateX(-4deg); } }

/* walk */
.rig-stage[data-pose="walk"] .rig-arm.r, .rig-stage[data-pose="walk"] .rig-leg.l { animation: rig-swing-a 900ms var(--ease-io) infinite; }
.rig-stage[data-pose="walk"] .rig-arm.l, .rig-stage[data-pose="walk"] .rig-leg.r { animation: rig-swing-b 900ms var(--ease-io) infinite; }
@keyframes rig-swing-a { 0%, 100% { transform: rotateX(32deg); } 50% { transform: rotateX(-32deg); } }
@keyframes rig-swing-b { 0%, 100% { transform: rotateX(-32deg); } 50% { transform: rotateX(32deg); } }

/* A wave: the right arm up and rocking, which is what the front page's player does.
   The sign matters. .rig-arm.r hangs on the viewer's left, so a POSITIVE rotateZ swings it
   up and outward; negative swings it inward, straight through the head. */
.rig-stage[data-pose="wave"] .rig-arm.r { animation: rig-wave 2.4s var(--ease-io) infinite; transform-origin: 50% calc(var(--p) * 2); }
.rig-stage[data-pose="wave"] .rig-arm.l { animation: rig-arm-l 3.2s var(--ease-io) infinite; }
@keyframes rig-wave {
  0%, 100% { transform: rotateZ(152deg) rotateX(6deg); }
  25% { transform: rotateZ(164deg) rotateX(6deg); }
  50% { transform: rotateZ(142deg) rotateX(6deg); }
  75% { transform: rotateZ(161deg) rotateX(6deg); }
}
/* Under reduced motion the arm still reads as a wave: it is simply held up. */
@media (prefers-reduced-motion: reduce) {
  .rig-stage[data-pose="wave"] .rig-arm.r { transform: rotateZ(152deg) rotateX(6deg); }
}

/* Nobody in particular: a visitor who is not signed in gets a blank grey figure rather than
   somebody else's skin. brightness(0) flattens the texture to black, and the invert lifts it
   to an even grey; the alpha of the skin is untouched, so the shape stays exact. */
.rig-stage.is-anon .rig-face { filter: brightness(0) invert(.42); }
.rig-stage.is-anon .rig-face.f-top { filter: brightness(0) invert(.5); }
.rig-stage.is-anon .rig-face.f-left, .rig-stage.is-anon .rig-face.f-right { filter: brightness(0) invert(.34); }
.rig-stage.is-anon .rig-face.f-back, .rig-stage.is-anon .rig-face.f-bottom { filter: brightness(0) invert(.28); }

/* Combat stance: a ready arm and a periodic swing. Positive rotateX brings a limb FORWARD,
   toward whoever is being hit; negative throws it out behind the player, which is what made
   the swing look like it was running backwards. */
.rig-stage[data-pose="fight"] .rig-arm.r { animation: rig-fight 1.6s var(--ease) infinite; }
.rig-stage[data-pose="fight"] .rig-arm.l { transform: rotateX(18deg) rotateZ(-6deg); }
.rig-stage[data-pose="fight"] .rig-leg.r { transform: rotateX(10deg); }
.rig-stage[data-pose="fight"] .rig-leg.l { transform: rotateX(-12deg); }
@keyframes rig-fight {
  0%, 55%, 100% { transform: rotateX(24deg) rotateZ(4deg); }
  66% { transform: rotateX(110deg) rotateZ(-14deg) rotateY(20deg); }
  78% { transform: rotateX(10deg) rotateZ(12deg) rotateY(-20deg); }
}
/* a one-off swing, on click */
.rig-stage .rig-arm.r.is-swinging { animation: rig-hit 380ms var(--ease) 1 !important; }
@keyframes rig-hit {
  0% { transform: rotateX(0); }
  40% { transform: rotateX(120deg) rotateZ(-18deg) rotateY(26deg); }
  100% { transform: rotateX(0); }
}
/* the red flash of taking a hit. On the faces, never on .rig-world: a filter on a
   preserve-3d element flattens it. */
.rig-stage.is-hit .rig-face { animation: rig-hurt 420ms var(--ease); }
@keyframes rig-hurt { 20% { filter: sepia(1) saturate(7) hue-rotate(-45deg) brightness(.85); } }

.rig-stage:not(.is-ready) .rig-world { opacity: 0; }
.rig-stage .rig-world { transition: opacity 600ms var(--ease); }
.rig-stage:not(.is-ready)::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: calc(var(--p) * 6);
  width: calc(var(--p) * 10);
  height: calc(var(--p) * 30);
  translate: -50% 0;
  border-radius: calc(var(--p) * 2);
  background: linear-gradient(180deg, rgba(175, 210, 255, .1), rgba(175, 210, 255, .03));
  animation: ui-breathe 1.6s var(--ease-io) infinite;
}
@keyframes ui-breathe { 50% { opacity: .45; } }
.rig-stage.is-failed::before { display: none; }

.rig-tag {
  position: absolute;
  left: 50%;
  top: calc(var(--p) * 1);
  translate: -50% 0;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 5px 10px;
  border-radius: var(--r-xs);
  background: rgba(0, 0, 0, .45);
  color: #fff;
  font: 400 13px/1 var(--f-tag);
  white-space: nowrap;
  pointer-events: none;
}

/* -------------------------------------------- reduced motion */

/* A blanket kill switch: wildcards, so an animation added later is opted out by default.
   Functional pieces (palette, lightbox, back-to-top, menu) still work; they only lose their
   transitions, and anything revealed by a transition is handed its end state outright. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    transition-duration: .001ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  html.js-anim [data-reveal] { opacity: 1 !important; transform: none !important; }
  .ui-enter > * { filter: none !important; }
  .ui-pool, .ui-rays, .ui-mote { animation: none !important; }
  .ui-mote { display: none; }
}
