/* 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 flat abyss, and everything
 * that holds content is a sheet of clear glass floating in it, held by a single hairline edge.
 * 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.
 *  - Nothing is a gradient and nothing glows. Every fill is one flat colour, and no shadow
 *    has a blur radius: hairline edges and focus rings only, no halos, no lift, no glow.
 *  - One blue: #007ffe, taken from the logo. Tints of it are the same hue, lightened.
 *  - 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 */
  /* The logo's blue, and it is the only blue: #007ffe is lifted straight from mark.svg.
     Dark ink on it clears AA (4.9:1), which is why controls filled with it carry --on-surf
     rather than white. --surf-ink is the same hue lightened for small text on dark. */
  --surf: #007ffe;
  --surf-hi: #4da3ff;
  --surf-ink: #4da3ff;
  --surf-fill: #007ffe;
  --surf-fill-hover: #2b90ff;
  --on-surf: #01152b;
  --foam: #4da3ff;
  --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 0 #0000;   /* flat: no lift under surfaces */
  --lift-2: 0 0 #0000;   /* flat: no lift under surfaces */

  /* ---- 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: #007ffe;
  --red-dark: #4da3ff;
  --on-fill: var(--on-surf);
  --link: var(--surf-ink);
  --accent-soft: rgba(0, 127, 254, .16);
  --accent-soft-hover: rgba(0, 127, 254, .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(77, 163, 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: var(--abyss);
}
/* 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: .1;
  mix-blend-mode: luminosity;
}
/* grain: keeps a large flat field from looking like dead pixels */
.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>");
}

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

.ui-glass {
  position: relative;
  isolation: isolate;
  background: 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);
}
.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;
  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;
  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-top .ui-brand > span { display: none; }
}

/* 1024, not 820: below this the seven-link nav plus the two slots no longer fit the bar, and
   the right-hand controls were being clipped (it was 960 with six links). */
@media (max-width: 1024px) {
  /* 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);
}
.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; }
  /* room under the small print for the back-to-top button, which is fixed over it */
  .ui-foot { padding-bottom: calc(var(--s6) + 56px); }
  .ui-foot ul { gap: var(--s3); }
  .ui-totop { right: var(--s4); bottom: var(--s4); width: 40px; height: 40px; }
}

/* ----------------------------------------------------------------- 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: none;
}
.ui-btn--primary:hover {
  border-color: transparent;
  background: var(--surf-fill-hover);
  box-shadow: none;
}

.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: none;
}
.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);
  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(77, 163, 255, .7);
  background-color: rgba(2, 8, 20, .6);
  box-shadow: 0 0 0 4px rgba(0, 127, 254, .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.select: the site's own dropdown over a hidden native <select>. The button is the field;
   the list is the same raised glass as the search suggestions. */
.ui-select { position: relative; }
.ui-select-btn {
  display: flex;
  align-items: center;
  padding-right: 40px;
  text-align: left;
  cursor: pointer;
  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;
}
.ui-select.is-open .ui-select-btn {
  border-color: rgba(77, 163, 255, .7);
  box-shadow: 0 0 0 4px rgba(0, 127, 254, .18);
}
.ui-select-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-select-list {
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  z-index: 60;
  min-width: 100%;
  max-height: 320px;
  overflow-y: auto;
  padding: 6px;
  border-radius: var(--r-md);
  background-color: rgba(9, 21, 42, .95);
  -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);
  animation: ui-pop var(--fast) var(--ease) both;
  scrollbar-width: thin;
}
.ui-select-opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: 8px 10px;
  border-radius: var(--r-sm);
  color: var(--ink-mid);
  font-size: var(--t-sm);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 90ms, box-shadow 90ms;
}
.ui-select-opt.is-on { background: rgba(0, 127, 254, .14); box-shadow: 0 0 0 1px rgba(0, 127, 254, .38) inset; color: var(--ink-hi); }
.ui-select-opt[aria-selected="true"] { color: var(--ink-hi); font-weight: 600; }
/* a tick beside the chosen one */
.ui-select-opt[aria-selected="true"]::after {
  content: '';
  width: 14px;
  height: 14px;
  flex: none;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='%234da3ff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' d='m5 12.5 4.5 4.5L19 7.5'/></svg>") center / contain no-repeat;
}
.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(0, 127, 254, .12); box-shadow: 0 0 0 1px rgba(0, 127, 254, .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: none;
  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: #12294a;
  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));
}
.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); }

.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);
}
.ui-live.is-off::before { background: var(--ink-lo); }

.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: var(--surf);
  transition: width 900ms var(--ease);
}
.ui-meter.is-win > i { background: var(--win); }
.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: none;
  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: none;
  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(0, 127, 254, .16);
  box-shadow: 0 0 0 1px rgba(0, 127, 254, .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 */

/* Loading placeholders pulse rather than sweep: a sweep is a moving gradient. */
.ui-skel {
  position: relative;
  border-radius: var(--r-sm);
  background: rgba(175, 210, 255, .07);
  animation: ui-skel-pulse 1.5s var(--ease-io) infinite;
}
@keyframes ui-skel-pulse { 50% { background-color: rgba(175, 210, 255, .13); } }
.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(0, 127, 254, .09);
  box-shadow: 0 0 0 1px rgba(0, 127, 254, .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: 11vh var(--s4) var(--s4);
  background: rgba(1, 4, 12, .62);
  -webkit-backdrop-filter: blur(8px) saturate(1.2);
  backdrop-filter: blur(8px) saturate(1.2);
  animation: ui-fade var(--fast) var(--ease) both;
}
@keyframes ui-fade { from { opacity: 0; } }
.ui-pal-box {
  display: flex;
  flex-direction: column;
  width: min(640px, 100%);
  max-height: min(78vh, 640px);
  overflow: hidden;
  border-radius: var(--r-lg);
  background: rgba(9, 21, 42, .92);
  -webkit-backdrop-filter: blur(24px) saturate(1.4);
  backdrop-filter: blur(24px) saturate(1.4);
  box-shadow: 0 0 0 1px var(--edge-2) inset, 0 1px 0 rgba(255, 255, 255, .08) inset, var(--lift-2);
  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);
  flex-shrink: 0;
  padding: 0 var(--s4) 0 var(--s5);
  border-bottom: 1px solid var(--edge);
}
.ui-pal-field > svg { width: 20px; height: 20px; flex-shrink: 0; color: var(--surf-ink); }
.ui-pal-field input {
  flex: 1;
  min-width: 0;
  height: 64px;
  border: 0;
  background: none;
  color: var(--ink-hi);
  font: 500 1.1rem/1 var(--f-body);
  letter-spacing: -.01em;
  outline: none;
  caret-color: var(--surf-ink);
}
.ui-pal-field input::placeholder { color: var(--ink-lo); font-weight: 400; }
.ui-pal-clear {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border: 0;
  border-radius: var(--r-xs);
  background: rgba(175, 210, 255, .08);
  color: var(--ink-lo);
  transition: color var(--fast), background-color var(--fast);
}
.ui-pal-clear:hover { color: var(--ink-hi); background: rgba(175, 210, 255, .16); }
.ui-pal-clear[hidden] { display: none; }
.ui-pal-clear svg { width: 14px; height: 14px; }
.ui-pal-field > .ui-kbd { font-size: 11px; color: var(--ink-lo); }

.ui-pal-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s2);
  scrollbar-width: thin;
  scrollbar-color: rgba(175, 210, 255, .2) transparent;
}
.ui-pal-sec + .ui-pal-sec { margin-top: var(--s2); padding-top: var(--s2); border-top: 1px solid var(--edge); }
.ui-pal-group {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--s2) var(--s3) 6px;
  font: 500 10.5px/1 var(--f-mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-lo);
}
.ui-pal-group i { font-style: normal; letter-spacing: 0; opacity: .7; }

.ui-pal-opt {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 52px;
  padding: 8px var(--s3);
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink-mid);
  text-align: left;
  cursor: pointer;
  transition: background-color 90ms, box-shadow 90ms;
}
.ui-pal-opt .ui-head { --size: 30px; border-radius: var(--r-xs); }
.ui-pal-ico {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: var(--r-xs);
  background: rgba(175, 210, 255, .07);
  box-shadow: 0 0 0 1px var(--edge) inset;
  color: var(--ink-mid);
  transition: color 90ms, background-color 90ms, box-shadow 90ms;
}
.ui-pal-ico svg { width: 16px; height: 16px; }
.ui-pal-txt { display: grid; gap: 3px; flex: 1; min-width: 0; }
.ui-pal-txt b, .ui-pal-txt small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-pal-txt b { font-weight: 600; font-size: var(--t-sm); color: var(--ink-hi); }
.ui-pal-txt small { font: 400 11.5px/1.2 var(--f-mono); color: var(--ink-lo); }
.ui-pal-txt mark { background: none; color: var(--surf-ink); box-shadow: 0 1.5px 0 rgba(77, 163, 255, .55); }
.ui-pal-tag {
  flex-shrink: 0;
  padding: 3px 7px;
  border-radius: var(--r-xs);
  background: rgba(175, 210, 255, .07);
  color: var(--ink-lo);
  font: 500 10.5px/1.2 var(--f-mono);
  letter-spacing: .04em;
}
.ui-pal-go {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: var(--r-xs);
  color: var(--surf-ink);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 90ms, transform var(--fast) var(--ease);
}
.ui-pal-go svg { width: 15px; height: 15px; }
.ui-pal-opt.is-on { background: rgba(0, 127, 254, .14); box-shadow: 0 0 0 1px rgba(0, 127, 254, .38) inset; }
.ui-pal-opt.is-on .ui-pal-ico { color: #fff; background: var(--surf-fill); box-shadow: none; }
.ui-pal-opt.is-on .ui-pal-tag { color: var(--ink-mid); background: rgba(0, 127, 254, .2); }
.ui-pal-opt.is-on .ui-pal-go { opacity: 1; transform: none; }

.ui-pal-empty {
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: var(--s6) var(--s5);
  text-align: center;
  color: var(--ink-lo);
  font-size: var(--t-sm);
}
.ui-pal-empty .ui-pal-ico { width: 40px; height: 40px; margin-bottom: var(--s2); }
.ui-pal-empty .ui-pal-ico svg { width: 18px; height: 18px; }
.ui-pal-empty b { color: var(--ink-hi); font-weight: 600; }
.ui-pal-more {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3);
  color: var(--ink-lo);
  font-size: var(--t-xs);
}
.ui-pal-empty:has(.ui-pal-spin) { display: flex; justify-content: center; gap: var(--s2); }
.ui-pal-spin {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid rgba(175, 210, 255, .16);
  border-top-color: var(--surf-ink);
  animation: ui-pal-spin .7s linear infinite;
}
@keyframes ui-pal-spin { to { transform: rotate(360deg); } }

.ui-pal-foot {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-shrink: 0;
  padding: 10px var(--s5);
  border-top: 1px solid var(--edge);
  background: rgba(2, 8, 20, .35);
  color: var(--ink-lo);
  font-size: var(--t-xs);
}
.ui-pal-foot span { display: inline-flex; align-items: center; gap: 5px; }
.ui-pal-foot .ui-kbd { min-width: 20px; text-align: center; font-size: 10.5px; }
.ui-pal-count { margin-left: auto; font: 400 11px/1 var(--f-mono); }

@media (max-width: 600px) {
  .ui-pal { padding: var(--s3) var(--s3) 0; }
  .ui-pal-box { max-height: calc(100dvh - 24px); }
  .ui-pal-field input { height: 56px; font-size: 1rem; }
  .ui-pal-foot span:not(.ui-pal-count) { display: none; }
  .ui-pal-tag { display: none; }
}

/* --------------------------------------------------- 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; }
/* No disc under the feet: flattened it is a hard ellipse, and the frame around the figure
   does the grounding now. Kept in the markup so a page can show one if it ever wants to. */
.rig-floor { display: none; }

.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;
}
/* Only a model with an animated pose breathes. Everywhere else the figure stands still and
   the head is the only thing that moves — see rig.js, where "still" is the default pose. */
.rig-stage[data-pose="wave"] .rig-body-wrap,
.rig-stage[data-pose="idle"] .rig-body-wrap,
.rig-stage[data-pose="walk"] .rig-body-wrap,
.rig-stage[data-pose="fight"] .rig-body-wrap {
  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(22deg) rotateZ(4deg); }
  66% { transform: rotateX(96deg) rotateZ(2deg); }
  80% { transform: rotateX(6deg) rotateZ(6deg); }
}
/* 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(104deg) rotateZ(-6deg); }
  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: rgba(175, 210, 255, .07);
  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 { animation: none !important; }
}
