/* flux-ui.css: the look the Flux app and the website genuinely SHARE.

   THE SOURCE OF TRUTH IS THIS FILE, IN THE APP REPO. The website holds a
   byte-for-byte copy made by `node scripts/sync-ui.js`; `npm test` runs
   the same script with `--check` and fails when the copy drifts, when the
   app stops linking this file, when a shelf class goes missing, or when a
   site-only component creeps back in. Never edit the website's copy by
   hand (profiles-plan.md, D7 and section 9).

   What is in here (decided 2026-09-10, after the audit found the sheet was
   "shared" in name only): the tokens, the fonts, and THE SHELF PIECES -
   the mod tile, its chips and patch-check verdict, the buttons on it, the
   empty state, and the creator avatar, stats and breadcrumb the creator
   view uses. The app loads this file for its Browse and creator views;
   the website loads it on every page. Everything else - the site's fields,
   forms, dialogs, toasts, progress bar, drop zone, thumbnails and tabs -
   lives in the site's own site.css; the app's own chrome (Settings, its
   switches, dialogs and toasts) lives in the app's index.html. Neither is
   the other's business.

   Three layers, top to bottom:
     1. tokens   - colours (the app's own variable names, Bloodmoon values),
                   fonts, radii, easing, density
     2. base     - the focus ring and the reduced-motion rule
     3. components, every class prefixed `fx-`, and NOTHING that targets a
                   bare element: loading this file must never restyle a
                   page's own markup (the check enforces that too)

   Colour tokens keep the APP'S names on purpose: the app's theme engine
   sets them inline on <html> at run time and inline wins over any rule
   here, so a themed app feeds these components its accent for free. In
   the app this file is linked BEFORE the page's own styles, so where both
   declare a token the app's value wins. On the website nothing overrides
   them, so they are the site's fixed palette. Values are the app's default
   theme (Bloodmoon), which is what the website was hand-matched to
   (profiles-plan.md 7b, R4-R6).

   Every decision this file encodes is listed in profiles-plan.md 7b, one
   row per disagreement, with the developer's pick (2026-09-07). */

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

/* The three UI fonts, shipped with both the app and the site (R1) and
   loaded from fonts/ui next to this file - never from Google, so no
   visitor's browser calls out for them. The files are the latin and
   latin-ext subsets Google Fonts serves, at exactly the weights the
   pages use; the unicode-range lines let a browser fetch only the subset
   a page needs. All three are SIL Open Font Licence (OFL.txt beside
   each family in fonts/ui). Fallbacks in the --fx- font stacks cover a
   file that fails to load. */

@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/BricolageGrotesque-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/BricolageGrotesque-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/BricolageGrotesque-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 700;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/BricolageGrotesque-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/ui/IBMPlexMono-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/ui/IBMPlexMono-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ui/IBMPlexMono-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ui/IBMPlexMono-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/IBMPlexSans-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/IBMPlexSans-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/IBMPlexSans-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: swap;
  src: url("fonts/ui/IBMPlexSans-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* colours (the app's token names) */
  --bg: #0a0506;
  --panel: rgba(38, 16, 20, 0.62);
  --panel-hover: rgba(58, 22, 28, 0.78);
  --panel-solid: #1a0a0d;
  --edge: rgba(255, 120, 130, 0.13);
  --edge-strong: rgba(255, 120, 130, 0.32);
  --text: #f5ebec;
  --text-muted: #ad888c;
  --text-faint: #805558;
  --accent: #ef4444;
  --accent-hot: #fb7185;
  --accent-deep: #7f1d1d;
  --accent-2: #f59e0b;
  /* The second accent darkened until white text clears 3:1 on it. The app
     computes this from the live theme; this is the Bloodmoon result. */
  --accent-2-deep: #ca8208;
  --glow: rgba(239, 68, 68, 0.5);
  --good: #34e5b0;
  --bad: #ff6b6b;
  --warn: #ffb020;

  /* shared-only tokens, `fx-` so they never shadow a host variable */
  --fx-display: "Bricolage Grotesque", "Segoe UI Variable Display", "Segoe UI", system-ui, sans-serif;
  --fx-body: "IBM Plex Sans", "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif;
  --fx-mono: "IBM Plex Mono", "Cascadia Mono", Consolas, monospace;
  /* Small uppercase labels are most of this look's character: buttons,
     chips, eyebrows, tabs, stat captions all set this and adjust size. */
  --fx-label: 500 11px/1 var(--fx-mono);

  /* radii: 6 / 8 / 12 / 16 / round. Nothing else (R10). */
  --fx-r-1: 6px;
  --fx-r-2: 8px;
  --fx-r-3: 12px;
  --fx-r-4: 16px;

  /* the house easing curve: every lift, slide and spring */
  --fx-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* density (R2): compact is the app; the site opts into comfortable */
  --fx-text: 14px;
  --fx-lh: 1.45;

  /* the solid surface cards sit on; the app may point this at its glass
     panel where art shows through (R3), the site leaves it solid */
  --fx-surface: var(--panel-solid);
  /* the dark fill of fields, tracks and tab bars */
  --fx-field: rgba(0, 0, 0, 0.28);
}
:root[data-density="comfortable"] {
  --fx-text: 16px;
  --fx-lh: 1.65;
}

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

/* One focus ring (R12): a soft accent halo, never the browser outline,
   drawn as a shadow so it follows the control's own corners. */
.fx-btn:focus-visible,
.fx-chip:focus-visible,
.fx-tile:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  [class^="fx-"], [class*=" fx-"] {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
  .fx-rail-group::details-content { transition: none !important; }
}

/* ---------- labels ---------- */

/* Two roles, two colours (R13): an eyebrow names a section and wears the
   hot accent; a label captions a field or a number and stays faint. */
.fx-eyebrow,
.fx-label {
  font: var(--fx-label);
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-hot);
}
.fx-label { color: var(--text-faint); }

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

/* The app's button, in two sizes (R8) with the app's hover glow and its
   press: down fast, back slow, transform only (R9). The plain class is
   the outlined button; add -primary for the gradient, -ghost for the
   quiet one, -lg for the web's hero and rail size. */
.fx-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--edge-strong);
  background: rgba(255, 255, 255, 0.035);
  color: var(--text);
  font: var(--fx-label);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  padding: 10px 16px;
  border-radius: var(--fx-r-2);
  cursor: pointer;
  transition:
    background 140ms ease,
    border-color 140ms ease,
    box-shadow 140ms ease,
    color 140ms ease,
    opacity 140ms ease,
    transform 170ms var(--fx-ease);
}
.fx-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-color: var(--accent);
  box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 22%, transparent);
}
.fx-btn:active:not(:disabled) { transform: scale(0.97); transition-duration: 60ms; }
.fx-btn:disabled { opacity: 0.4; cursor: default; }
.fx-btn-lg { font-size: 13px; padding: 15px 26px; }
.fx-btn svg { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* Ends in the second accent (R7): the two-tone tail is the brand's
   signature. 100deg, not 135: on a wide, short box a diagonal gradient
   bunches its last colour into a corner stripe. background-origin keeps
   the gradient from repeating under the transparent border. */
.fx-btn-primary {
  background: linear-gradient(100deg, var(--accent-hot), var(--accent) 50%, var(--accent-2-deep));
  background-origin: border-box;
  border-color: transparent;
  color: #fff;
  box-shadow: 0 0 22px var(--glow), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.fx-btn-primary:hover:not(:disabled) {
  background: linear-gradient(100deg, var(--accent-hot), var(--accent) 50%, var(--accent-2-deep));
  border-color: transparent;
  box-shadow: 0 0 34px var(--glow), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.fx-btn-primary:disabled { box-shadow: none; }

/* Quieter than the outlined button, never invisible: it keeps an edge. */
.fx-btn-ghost {
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border-color: var(--edge);
  color: var(--text-muted);
}
.fx-btn-ghost:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.05);
  border-color: var(--edge);
  box-shadow: none;
  color: var(--text);
}

/* ---------- chips ---------- */

/* One chip for both sides (R14): the app's size (it is a real button when
   it filters) with the site's firmer border. A chip that is a link or a
   button answers hover and press; a plain span does not. */
.fx-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.fx-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--edge-strong);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-muted);
  font: var(--fx-label);
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  padding: 6px 11px;
  border-radius: 999px;
  transition:
    background 160ms ease,
    color 160ms ease,
    border-color 160ms ease,
    transform 170ms var(--fx-ease);
}
a.fx-chip, button.fx-chip { cursor: pointer; appearance: none; }
a.fx-chip:hover, button.fx-chip:hover { color: var(--text); border-color: var(--text-faint); }
a.fx-chip:active, button.fx-chip:active { transform: scale(0.95); transition-duration: 60ms; }
.fx-chip.active {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  border-color: var(--accent);
  color: var(--accent-hot);
}
.fx-chip svg { width: 13px; height: 13px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.fx-chip-count { opacity: 0.6; margin-left: 2px; }

/* ---------- the patch-check verdict ---------- */

/* The one thing only Flux can say about a skin. Always the status
   colours, never the accent (R6): in the app the accent can be green. */
.fx-verdict {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: var(--fx-label);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 5px 9px;
  border-radius: 999px;
  border: 1px solid;
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  color: var(--text-faint);
  border-color: var(--edge-strong);
}
.fx-verdict.applies { color: var(--good); border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.fx-verdict.broken { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 50%, transparent); }
.fx-verdict.noop { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }

.fx-verdict-box {
  border-radius: var(--fx-r-3);
  padding: 12px 14px;
  border: 1px solid var(--edge-strong);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-muted);
}
.fx-verdict-box .fx-verdict { margin-bottom: 8px; }
.fx-verdict-box.applies { border-color: color-mix(in srgb, var(--good) 30%, transparent); background: color-mix(in srgb, var(--good) 6%, transparent); }
.fx-verdict-box.broken { border-color: color-mix(in srgb, var(--bad) 35%, transparent); background: color-mix(in srgb, var(--bad) 7%, transparent); }
.fx-verdict-box.noop { border-color: color-mix(in srgb, var(--warn) 30%, transparent); background: color-mix(in srgb, var(--warn) 5%, transparent); }

/* ---------- avatar ---------- */

/* Circles mean people; mods keep their square thumbnails. With no
   picture the initial sits on the accent gradient, as the site's small
   one always has. Three sizes: 26 in a list, 46 in a box, 96 up top. */
.fx-avatar {
  --fx-avatar-size: 46px;
  width: var(--fx-avatar-size);
  height: var(--fx-avatar-size);
  flex: none;
  border-radius: 50%;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, var(--accent-hot), var(--accent));
  color: #fff;
  font: 700 calc(var(--fx-avatar-size) * 0.39) / 1 var(--fx-display);
  /* A capital sits a little above the middle of its line box, and any
     letter-spacing inherited from a nav pushes it left; both nudged out so
     the initial is optically centred. The picture, when there is one,
     covers the whole disc regardless. */
  letter-spacing: 0;
  padding-top: 0.16em;
  position: relative;
  user-select: none;
}
.fx-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.fx-avatar-sm { --fx-avatar-size: 26px; }
.fx-avatar-lg { --fx-avatar-size: 96px; }

/* ---------- stat block ---------- */

/* A number over its caption. Tabular digits so a row of them lines up. */
.fx-stats { display: flex; gap: 32px; flex-wrap: wrap; }
.fx-stat { display: grid; gap: 6px; align-content: start; }
.fx-stat b {
  font: 700 26px / 1 var(--fx-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--text);
}
.fx-stat span {
  font: var(--fx-label);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ---------- cards and tiles ---------- */

.fx-card {
  background: var(--fx-surface);
  border: 1px solid var(--edge);
  border-radius: var(--fx-r-3);
  padding: 20px;
}

/* The image-first tile: the cover, with the chips and the name resting
   on its lower edge. A tile that is a link lifts 3px (R11) with the
   site's shadow and the app's glow; the art clips inside the border. */
.fx-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px;
}
.fx-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--fx-surface);
  border: 1px solid var(--edge);
  border-radius: var(--fx-r-3);
  overflow: hidden;
  color: var(--text);
  text-decoration: none;
  transition:
    transform 200ms var(--fx-ease),
    border-color 180ms ease,
    box-shadow 200ms ease;
}
a.fx-tile:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  box-shadow:
    0 18px 40px rgba(0, 0, 0, 0.45),
    0 0 28px color-mix(in srgb, var(--accent) 16%, transparent);
}
/* after the hover rule on purpose: pressed AND hovered, the press wins */
a.fx-tile:active { transform: translateY(-1px) scale(0.985); transition-duration: 60ms; }
.fx-tile-art {
  position: relative;
  aspect-ratio: 16 / 9;
  background-size: cover;
  background-position: center;
  background-clip: padding-box;
  background-color: var(--fx-field);
}
.fx-tile-art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 50%, color-mix(in srgb, var(--fx-surface) 96%, transparent));
}
.fx-tile-body {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 16px 16px;
  margin-top: -44px;
}
.fx-tile-top { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.fx-tile h3 {
  font: 700 20px / 1.15 var(--fx-display);
  letter-spacing: -0.01em;
  margin: 4px 0 0;
  overflow-wrap: anywhere;
}
.fx-tile-meta {
  display: flex;
  gap: 4px 12px;
  flex-wrap: wrap;
  align-items: center;
  color: var(--text-muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.fx-tile-meta b { color: var(--text); font-weight: 600; }
/* The creator's name on a tile (and on the mod page's card) leads to their
   page. Quiet until pointed at: same colour as the rest of the line, then
   the hot accent and an underline, so the tile does not read as two links
   at once. Inside an <a> tile it is a role=link span with its own key and
   click handling (a nested <a> is not allowed); on its own it is an <a>. */
.fx-tile-by {
  color: var(--text);
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 2px;
  transition: color 150ms ease, text-decoration-color 150ms ease;
}
.fx-tile-by:hover,
.fx-tile-by:focus-visible {
  color: var(--accent-hot);
  text-decoration-color: currentColor;
  outline: none;
}
.fx-tile-meta svg { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* The list view: the same tile laid on its side, one to a row, the art at
   the left, so a long shelf scans like a table. A page opts in by adding
   .fx-list to the grid; the tile's markup does not change. The words
   overlap the art's fading right edge the way they overlap its lower edge
   in the grid, and a featured tile gives up its double width. */
/* The champion's portrait resting in the corner of the art (site: from
   Riot's public image server, 2026-09-12; the app's image door is
   same-origin, so its tiles carry none yet), and the art's position when
   it is the champion's splash standing in for a missing cover. */
.fx-tile-face { position: absolute; z-index: 1; top: 10px; left: 10px; width: 36px; height: 36px; border-radius: 50%; object-fit: cover; background: var(--fx-surface); border: 2px solid rgba(255, 255, 255, 0.85); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); }
.fx-tile-art-stock { background-position: center 20%; }
.fx-grid.fx-list { grid-template-columns: minmax(0, 1fr); gap: 10px; }
.fx-grid.fx-list .fx-tile { flex-direction: row; align-items: stretch; }
.fx-grid.fx-list .fx-tile-featured { grid-column: auto; }
.fx-grid.fx-list .fx-tile-art { width: 220px; flex: none; aspect-ratio: 16 / 9; }
.fx-grid.fx-list .fx-tile-art::after { background: linear-gradient(90deg, transparent 55%, color-mix(in srgb, var(--fx-surface) 96%, transparent)); }
.fx-grid.fx-list .fx-tile-body { flex: 1; min-width: 0; margin: 0 0 0 -40px; padding: 14px 16px 14px 0; justify-content: center; }
.fx-grid.fx-list .fx-tile h3 { font-size: 18px; }
@media (max-width: 560px) {
  .fx-grid.fx-list .fx-tile-art { width: 132px; }
  .fx-grid.fx-list .fx-tile-body { margin-left: -24px; }
}

/* ---------- empty state ---------- */

.fx-empty {
  border: 1px dashed var(--edge-strong);
  border-radius: var(--fx-r-3);
  padding: 46px 20px;
  text-align: center;
  color: var(--text-muted);
  background: rgba(255, 255, 255, 0.015);
}
.fx-empty strong {
  display: block;
  color: var(--text);
  margin-bottom: 5px;
  font: var(--fx-label);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* ---------- breadcrumb ---------- */

.fx-crumbs {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  font: var(--fx-label);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.fx-crumbs a { color: var(--text-muted); text-decoration: none; transition: color 150ms ease; }
.fx-crumbs a:hover { color: var(--text); }
.fx-crumbs .cur { color: var(--text); }

/* ---------- tone aliases on the small pill ---------- */

/* The verdict pill doubles as a status pill (draft, in review, published):
   the same shape with a tone instead of a verdict. */
.fx-verdict.good { color: var(--good); border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.fx-verdict.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.fx-verdict.bad { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 50%, transparent); }
.fx-verdict.faint { color: var(--text-faint); border-color: var(--edge-strong); }

/* ---------- the filter rail (the website's library, the app's Browse) ---------- */

/* A stack of tall buttons, after the pattern every big shelf uses: a
   search box, a sort, then one button per group - icon, bold label, a
   chevron on the ones that open - with the choices inside as quiet rows
   (icon, short name, the count that choice would leave). Where the rail
   sits (sticky beside a grid on the site, inside a scrolling panel in the
   app) is the page's business; this is the rail itself. Shared since
   2026-09-14, when the app's Browse view took the website's rail. */
.fx-rail { display: grid; gap: 12px; align-content: start; }
.fx-rail > * { min-width: 0; max-width: 100%; box-sizing: border-box; }
.fx-searchbox {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 15px 16px;
  background: var(--fx-surface);
  border: 1px solid var(--edge);
  border-radius: var(--fx-r-3);
  color: var(--text-faint);
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.fx-searchbox:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }
.fx-searchbox svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.fx-searchbox input { flex: 1; min-width: 0; background: none; border: 0; color: var(--text); font: inherit; font-size: 15px; outline: none; }
.fx-searchbox input::placeholder { color: var(--text-faint); }
/* A select with our own chevron: the browser's hugs the border. */
.fx-select {
  appearance: none;
  /* the OPENED list is drawn by the system: without this it comes up
     white while the options keep our light text - unreadable */
  color-scheme: dark;
  background: var(--fx-surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b7a9ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 14px center / 12px 12px;
  border: 1px solid var(--edge);
  color: var(--text-muted);
  font-family: var(--fx-mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
  border-radius: var(--fx-r-2);
  padding: 11px 38px 11px 12px;
  cursor: pointer;
}
/* and if the system ignores the scheme, the rows still read */
.fx-select option { color: var(--text); background-color: var(--panel-solid); }
.fx-select { transition: border-color 140ms ease, color 140ms ease; }
.fx-select:hover { border-color: var(--edge-strong); color: var(--text); }
/* sort: one control, "Sort by · Newest" */
.fx-rail-sort { display: flex; align-items: center; gap: 10px; padding: 0 0 0 16px; border: 1px solid var(--edge); border-radius: var(--fx-r-3); background: var(--fx-surface); color: var(--text-muted); font: 600 14px / 1 var(--fx-display); }
.fx-rail-sort .fx-select { flex: 1; min-width: 0; width: auto; border: 0; background-color: transparent; color: var(--text); font: 600 14px / 1 var(--fx-display); letter-spacing: 0; text-transform: none; padding: 17px 36px 17px 0; background-position: right 14px center; }
.fx-rail-sort { transition: border-color 140ms ease; }
.fx-rail-sort:hover { border-color: var(--edge-strong); }
.fx-rail-sort:focus-within { border-color: var(--accent); }
.fx-rail-sort .fx-select:focus-visible { box-shadow: none; outline: none; }
/* icons on the rail: small line drawings of our own */
.fx-ico { width: 20px; height: 20px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* one tall button per group: icon, bold label, chevron when it opens */
.fx-rail-group { border: 1px solid var(--edge); border-radius: var(--fx-r-3); background: var(--fx-surface); padding: 0; overflow: hidden; transition: border-color 140ms ease; }
.fx-rail-group:hover { border-color: var(--edge-strong); }
.fx-rail-group summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 12px; padding: 17px 16px; font: 600 16px / 1 var(--fx-display); letter-spacing: 0; text-transform: none; color: var(--text); user-select: none; }
.fx-rail-group summary::-webkit-details-marker { display: none; }
.fx-rail-group summary { transition: background 140ms ease; }
.fx-rail-group summary:hover { background: rgba(255, 255, 255, 0.03); }
/* Groups open and close smoothly where the engine can animate a height to
   "auto" (Chromium 131 and up, so the app and current Chrome); anywhere
   else they open at once, as before. */
.fx-rail-group { interpolate-size: allow-keywords; }
.fx-rail-group::details-content { height: 0; overflow: clip; transition: height 240ms var(--fx-ease), content-visibility 240ms allow-discrete; }
.fx-rail-group[open]::details-content { height: auto; }
.fx-rail-group summary::after { content: ''; flex: none; width: 8px; height: 8px; border-right: 2px solid var(--text-muted); border-bottom: 2px solid var(--text-muted); transform: translateY(-2px) rotate(45deg); margin-right: 3px; transition: transform 160ms ease; }
.fx-rail-group[open] summary::after { transform: translateY(2px) rotate(-135deg); }
.fx-rail-group[open] summary { border-bottom: 1px solid var(--edge); }
.fx-rail-group summary .fx-ico { color: var(--text); }
/* the header names what is chosen inside, and lights up while it is */
.fx-rail-group.has-active { border-color: color-mix(in srgb, var(--accent) 45%, var(--edge)); }
.fx-rail-cur { margin-left: auto; padding-left: 8px; color: var(--accent-hot); font-family: var(--fx-display); font-size: 13px; font-weight: 500; letter-spacing: 0; text-transform: none; max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* a one-click filter in the same clothes, lit while it is on */
.fx-rail-direct { appearance: none; display: flex; align-items: center; gap: 12px; width: 100%; padding: 17px 16px; font: 600 16px / 1 var(--fx-display); color: var(--text); text-align: left; cursor: pointer; }
.fx-rail-direct[aria-pressed="true"] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--fx-surface)); color: var(--accent-hot); }
/* the choices inside a group: one quiet row each, lit when pressed */
.fx-rail-opts { display: grid; gap: 1px; margin: 0; padding: 6px 8px 8px; }
.fx-rail-opt { appearance: none; display: flex; align-items: center; gap: 9px; width: 100%; min-width: 0; padding: 7px 8px; border: 0; border-radius: 8px; background: none; color: var(--text-muted); font: inherit; font-size: 13px; text-align: left; cursor: pointer; transition: background 120ms ease, color 120ms ease, transform 170ms var(--fx-ease); }
.fx-rail-opt:hover { background: rgba(255, 255, 255, 0.04); color: var(--text); }
.fx-rail-opt:active { transform: scale(0.97); transition-duration: 60ms; }
.fx-rail-opt[aria-pressed="true"] { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-hot); }
.fx-rail-opt svg { width: 14px; height: 14px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.fx-rail-opt b { font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fx-rail-opt .n { margin-left: auto; padding-left: 8px; font-family: var(--fx-mono); font-size: 11px; color: var(--text-faint); }
.fx-rail-opt[aria-pressed="true"] .n { color: inherit; opacity: 0.8; }
.fx-rail-opt .fx-mono { width: 24px; height: 24px; font-size: 10px; }
.fx-v-applies svg { color: var(--good); }
.fx-v-broken svg { color: var(--bad); }
.fx-v-noop svg { color: var(--warn); }
.fx-rail-none { display: block; color: var(--text-faint); font-size: 12.5px; padding: 10px 16px 14px; }
.fx-rail-find { width: calc(100% - 24px); box-sizing: border-box; margin: 10px 12px 0; padding: 8px 10px; background: var(--fx-field); border: 1px solid var(--edge); border-radius: var(--fx-r-2); color: var(--text); font: inherit; font-size: 13px; outline: none; }
.fx-rail-find:focus { border-color: var(--accent); }
.fx-rail-more { appearance: none; background: none; border: 0; padding: 0 16px 14px; margin-top: -2px; color: var(--text-muted); font-family: var(--fx-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; }
.fx-rail-more:hover { color: var(--text); }
.fx-rail-clear { appearance: none; width: 100%; padding: 12px; border: 1px dashed var(--edge-strong); border-radius: var(--fx-r-3); background: none; color: var(--text-muted); font: 600 13px / 1 var(--fx-display); cursor: pointer; transition: color 120ms ease, border-color 120ms ease; }
.fx-rail-clear:hover { color: var(--accent-hot); border-color: var(--accent); }
/* the same act as .fx-rail-clear, worn small beside the active chips */
.fx-clear-link { appearance: none; border: 0; background: none; padding: 6px 4px; color: var(--text-faint); font-family: var(--fx-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; white-space: nowrap; }
.fx-clear-link:hover { color: var(--accent-hot); text-decoration: underline; text-underline-offset: 3px; }
/* a champion's disc: the portrait once it arrives, the initial until then
   (and for good if it never does) */
.fx-mono { position: relative; display: inline-grid; place-items: center; width: 16px; height: 16px; border-radius: 50%; overflow: hidden; background: hsl(var(--h, 350) 55% 48% / 0.32); color: var(--text); font: 700 9.5px / 1 var(--fx-display); letter-spacing: 0; text-transform: uppercase; font-style: normal; }
.fx-mono img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.fx-chip.active .fx-mono { background: hsl(var(--h, 350) 60% 52% / 0.55); }
/* search suggestions: the words typed, offered as exact filters */
.fx-rail-search { position: relative; }
.fx-suggest[hidden] { display: none; }
.fx-suggest { position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 20; display: grid; padding: 6px; background: var(--fx-surface); border: 1px solid var(--edge-strong); border-radius: var(--fx-r-2); box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45); }
.fx-suggest-item { appearance: none; display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 10px; border: 0; border-radius: 8px; background: none; color: var(--text); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
.fx-suggest-item:hover, .fx-suggest-item[aria-selected="true"] { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.fx-suggest-item b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fx-suggest-item svg { width: 13px; height: 13px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.fx-suggest-kind { flex: none; min-width: 78px; font-family: var(--fx-mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-faint); }
.fx-suggest-item .fx-chip-count { margin-left: auto; font-family: var(--fx-mono); font-size: 11px; color: var(--text-faint); opacity: 1; }

/* ---------- the gallery (the website's mod page, the app's detail page) ---------- */

/* Cover and screenshots in one stage, one picture at a time. The strip
   inside snaps a picture at a time so a finger can swipe it; the arrows,
   the thumbnails below and the arrow keys just move the strip. */
.fx-gallery { display: grid; gap: 10px; }
.fx-gallery-stage { position: relative; aspect-ratio: 16 / 9; border-radius: 16px; overflow: hidden; border: 1px solid var(--edge); background: #050203; box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5); outline: none; }
.fx-gallery-stage:focus-visible { border-color: var(--accent); }
.fx-gallery-track { display: flex; height: 100%; overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain; scrollbar-width: none; }
.fx-gallery-track::-webkit-scrollbar { display: none; }
.fx-gallery-slide { flex: 0 0 100%; width: 100%; height: 100%; object-fit: contain; scroll-snap-align: start; scroll-snap-stop: always; user-select: none; background: #050203; }
.fx-gallery-slide.cover { object-fit: cover; }
.fx-gallery-slide.stock { object-fit: cover; object-position: center 22%; }
.fx-gallery-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 42px; height: 42px; display: grid; place-items: center; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.18); background: rgba(10, 5, 6, 0.62); color: #fff; padding: 0; cursor: pointer; backdrop-filter: blur(6px); opacity: 0.85; transition: opacity 120ms ease, background 120ms ease, transform 120ms ease; }
.fx-gallery-nav svg { width: 22px; height: 22px; display: block; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.fx-gallery-nav:hover { opacity: 1; background: rgba(10, 5, 6, 0.85); }
.fx-gallery-nav:active { transform: translateY(-50%) scale(0.94); }
.fx-gallery-nav.prev { left: 12px; }
.fx-gallery-nav.next { right: 12px; }
.fx-gallery-nav[hidden] { display: none; }
.fx-gallery-counter { position: absolute; right: 12px; bottom: 10px; padding: 4px 9px; border-radius: 999px; background: rgba(10, 5, 6, 0.62); color: #fff; font-family: var(--fx-mono); font-size: 11px; letter-spacing: 0.08em; backdrop-filter: blur(6px); }
.fx-gallery-thumbs { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: thin; padding: 2px 2px 6px; }
.fx-gallery-thumb { appearance: none; flex: 0 0 104px; aspect-ratio: 16 / 9; padding: 0; border: 2px solid transparent; border-radius: 10px; overflow: hidden; background: #050203; cursor: pointer; opacity: 0.6; transition: opacity 120ms ease, border-color 120ms ease, transform 170ms var(--fx-ease); }
.fx-gallery-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fx-gallery-thumb:hover { opacity: 0.9; }
.fx-gallery-thumb:active { transform: scale(0.95); transition-duration: 60ms; }
.fx-gallery-thumb.active { opacity: 1; border-color: var(--accent); }
.fx-gallery-thumb:focus-visible { outline: none; border-color: var(--accent-hot); }
