/* ───── Self-hosted fonts (moved off Google Fonts CDN 2026-07-11) ─────
   Files under /static/fonts/ are the woff2 subsets Google served for
   Big Shoulders Display 700/900 · JetBrains Mono 400/600 · Sora
   400/500/600. unicode-range kept verbatim so browsers only fetch
   the subsets a page actually needs. */
/* Big Shoulders Display 700 vietnamese */
@font-face {
  font-family: 'Big Shoulders Display';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/static/fonts/big-shoulders-display-700-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* Big Shoulders Display 700 latin-ext */
@font-face {
  font-family: 'Big Shoulders Display';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/static/fonts/big-shoulders-display-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;
}
/* Big Shoulders Display 700 latin */
@font-face {
  font-family: 'Big Shoulders Display';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/static/fonts/big-shoulders-display-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;
}
/* Big Shoulders Display 900 vietnamese */
@font-face {
  font-family: 'Big Shoulders Display';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/static/fonts/big-shoulders-display-900-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* Big Shoulders Display 900 latin-ext */
@font-face {
  font-family: 'Big Shoulders Display';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/static/fonts/big-shoulders-display-900-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;
}
/* Big Shoulders Display 900 latin */
@font-face {
  font-family: 'Big Shoulders Display';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/static/fonts/big-shoulders-display-900-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;
}
/* JetBrains Mono 400 cyrillic-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-400-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* JetBrains Mono 400 cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-400-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* JetBrains Mono 400 greek */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-400-greek.woff2) format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* JetBrains Mono 400 vietnamese */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-400-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* JetBrains Mono 400 latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-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;
}
/* JetBrains Mono 400 latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-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;
}
/* JetBrains Mono 600 cyrillic-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-600-cyrillic-ext.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* JetBrains Mono 600 cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-600-cyrillic.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* JetBrains Mono 600 greek */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-600-greek.woff2) format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* JetBrains Mono 600 vietnamese */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-600-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* JetBrains Mono 600 latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-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;
}
/* JetBrains Mono 600 latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/jetbrains-mono-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;
}
/* Sora 400 latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/sora-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;
}
/* Sora 400 latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/static/fonts/sora-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;
}
/* Sora 500 latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/static/fonts/sora-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;
}
/* Sora 500 latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/static/fonts/sora-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;
}
/* Sora 600 latin-ext */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/sora-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;
}
/* Sora 600 latin */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/static/fonts/sora-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 {
  /* The accent: the site's one brand colour. Every accent shade is built
     from --accent-hue, so changing that one number recolours the whole
     site (and the slight tint in the greys below), e.g. a seasonal theme:
       :root[data-season="october"] { --accent-hue: 55; }
     A season (seasons.py) can give the lighter 400 shade, used for text,
     links and labels, its own --accent-2-hue for a two-tone look.
     Default is cherry, matching RedVoice. Use var(--accent) (and the
     shades) for anything that should follow it; never type the colour in.
     --danger stays red whatever the accent is: errors, failed, delete. */
  --accent-hue: 22;
  --accent-2-hue: var(--accent-hue);   /* a second tone: the 400 shade */
  --accent-400: oklch(0.66 0.185 var(--accent-2-hue));
  --accent-500: oklch(0.58 0.190 var(--accent-hue));
  --accent-600: oklch(0.50 0.175 var(--accent-hue));
  --accent-700: oklch(0.42 0.140 var(--accent-hue));
  --accent: var(--accent-500);
  --on-accent: #fff;
  --on-accent-hover: #000;
  --accent-glow: color-mix(in oklch, var(--accent-500), transparent 70%);
  --danger: oklch(0.62 0.19 22);
  /* the OWNER / ADMIN chips: always cherry, whatever the accent */
  --role-red-400: oklch(0.66 0.185 22);
  --role-red-500: oklch(0.58 0.190 22);

  /* Older names for the accent, kept so nothing breaks; same colours. */
  --red-400: var(--accent-400);
  --red-500: var(--accent-500);
  --red-600: var(--accent-600);
  --red-700: var(--accent-700);
  --red-glow: var(--accent-glow);

  /* Warm-bias inks — deeper than neutral grey, slightly red-tinted. */
  --ink-0:   oklch(0.07 0.010 calc(var(--accent-hue) + 3));   /* page bg */
  --ink-50:  oklch(0.10 0.012 calc(var(--accent-hue) + 3));
  --ink-100: oklch(0.13 0.013 calc(var(--accent-hue) + 3));   /* card surface */
  --ink-200: oklch(0.17 0.014 calc(var(--accent-hue) + 3));   /* card hover */
  --ink-300: oklch(0.24 0.013 calc(var(--accent-hue) + 3));   /* divider */
  --ink-500: oklch(0.64 0.008 calc(var(--accent-hue) + 3));   /* readable muted text */
  --ink-700: oklch(0.72 0.005 calc(var(--accent-hue) + 3));   /* secondary text */
  --ink-800: oklch(0.88 0.004 calc(var(--accent-hue) + 3));   /* primary text */
  --ink-900: oklch(0.96 0.003 calc(var(--accent-hue) + 3));

  --grade-ss: oklch(0.85 0.13 95);   /* gold */
  --gold-text: var(--grade-ss);
  --grade-s:  oklch(0.82 0.14 95);
  --grade-a:  oklch(0.74 0.18 145);  /* green */
  --grade-b:  oklch(0.70 0.14 240);  /* blue */
  --grade-c:  oklch(0.65 0.20 320);  /* purple */
  --grade-d:  oklch(0.65 0.22 22);   /* red */

  --chart-trend: #e8d16a;            /* dashboard rolling-average overlay (dev build), gold */

  --font-display: "Big Shoulders Display", "Impact", sans-serif;
  --font-body: "Sora", -apple-system, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", "SF Mono", ui-monospace, monospace;

  --rad-sm: 6px;
  --rad-md: 10px;
  --rad-lg: 16px;
}

/* Light theme — same hue strategy (warm red-tinted neutrals), inverted
   lightness so the page reads as paper-on-paper. The reds shift slightly
   so they have enough contrast on a light surface. */
:root[data-theme="light"] {
  --accent-400: oklch(0.58 0.200 var(--accent-2-hue));
  --accent-500: oklch(0.52 0.205 var(--accent-hue));
  --accent-600: oklch(0.46 0.190 var(--accent-hue));
  --accent-700: oklch(0.40 0.160 var(--accent-hue));
  --accent-glow: color-mix(in oklch, var(--accent-500), transparent 80%);
  --on-accent-hover: #fff;
  --danger: oklch(0.54 0.20 22);
  --role-red-400: oklch(0.58 0.200 22);
  --role-red-500: oklch(0.52 0.205 22);

  --ink-0:   oklch(0.98 0.005 calc(var(--accent-hue) + 3));   /* page bg (near-white) */
  --ink-50:  oklch(0.96 0.006 calc(var(--accent-hue) + 3));
  --ink-100: oklch(0.93 0.007 calc(var(--accent-hue) + 3));   /* card surface */
  --ink-200: oklch(0.88 0.008 calc(var(--accent-hue) + 3));   /* card hover */
  --ink-300: oklch(0.82 0.010 calc(var(--accent-hue) + 3));   /* divider */
  --ink-500: oklch(0.46 0.012 calc(var(--accent-hue) + 3));   /* readable muted text */
  --ink-600: oklch(0.48 0.010 calc(var(--accent-hue) + 3));   /* (filled gap for explicit 600 uses) */
  --ink-700: oklch(0.38 0.010 calc(var(--accent-hue) + 3));   /* secondary text */
  --ink-800: oklch(0.20 0.010 calc(var(--accent-hue) + 3));   /* primary text */
  --ink-900: oklch(0.10 0.012 calc(var(--accent-hue) + 3));

  --chart-trend: #9a6f0a;            /* rolling-average overlay, darkened for the light theme */
  --gold-text: oklch(0.45 0.09 85);
}

* { box-sizing: border-box; }
/* The browser's default [hidden]{display:none} gets clobbered by any
   author-level `.foo { display: flex }` rule (class beats UA attribute
   selector). Force it back so el.hidden = true actually hides. */
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }

body {
  background: var(--ink-0);
  color: var(--ink-800);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 15px;
  line-height: 1.55;
  letter-spacing: 0;
  /* Two subtle backdrop effects: a soft cherry vignette top-left and a
     faint warm noise. Sets atmosphere without competing with content. */
  background-image:
    radial-gradient(900px 600px at 0% 0%,
                    color-mix(in oklch, var(--red-500), transparent 88%),
                    transparent 65%),
    radial-gradient(700px 500px at 100% 100%,
                    color-mix(in oklch, var(--red-500), transparent 92%),
                    transparent 70%);
  background-attachment: fixed;
  min-height: 100vh;
}

::selection { background: var(--red-500); color: var(--on-accent); }

a { color: var(--ink-900); text-decoration: none; }
a:hover { color: var(--red-400); }

.shell {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 28px;
}

/* ───── Nav ───── */
nav.top {
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: blur(12px) saturate(140%);
  background: color-mix(in oklch, var(--ink-0), transparent 25%);
  border-bottom: 1px solid color-mix(in oklch, var(--ink-300), transparent 40%);
}
nav.top .row {
  display: flex; align-items: center; justify-content: space-between;
  padding-top: 18px; padding-bottom: 18px;   /* horizontal comes from .shell */
  gap: 12px;
  min-width: 0;                          /* lets logo flex shrink */
}
nav.top .logo {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 22px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  min-width: 0;                          /* allow truncation in flex parent */
  flex: 0 1 auto;
}
/* The logo: a rounded square in the accent (lit from the top left, like
   the RedVoice icon) with the white R on top. The R is the only image, so
   any accent, a season included, recolours the logo. */
nav.top .logo .logo-mark {
  position: relative;
  flex: 0 0 auto;
  width: 28px; height: 28px;
  border-radius: 7px;
  background:
    radial-gradient(120% 90% at 20% 10%, color-mix(in oklch, white 22%, transparent), transparent 60%),
    linear-gradient(155deg, var(--accent-400), var(--accent-600) 55%, var(--accent-700));
  box-shadow: inset 0 0 0 1px color-mix(in oklch, white 10%, transparent),
              0 0 14px color-mix(in oklch, var(--accent-500), transparent 78%);
}
nav.top .logo .logo-r {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  filter: drop-shadow(0 1px 1px color-mix(in oklch, var(--accent-700), black 30%));
}
/* no R image (blocked, offline): the letter in the display face */
nav.top .logo .logo-mark.no-img::after {
  content: "R";
  position: absolute; inset: 0;
  display: grid; place-items: center;
  color: white;
  font-size: 18px; line-height: 1;
}
/* v2: Green's R3D logo as inline SVG. Its three tones are shades of the
   main accent (light top left to dark bottom right), so themes and seasons
   recolour it. Only the main hue: a season's second tone does not belong
   in the logo. The letters are cut out, with a plate behind them whose
   colour follows the mid tone: white on a dark logo, dark on a light one
   (the pastel seasons). Browsers without relative colours keep white. */
/* Sized so "R3D" matches the wordmark: the letters are 40.45% of the
   logo's height and Big Shoulders' capitals are 0.80em, so the logo is
   0.80 / 0.4045 = 1.98em, following the nav's font size on phones too. */
nav.top .logo .logo-mark.logo-mark-v2 {
  background: none;
  width: 1.98em; height: 1.98em;
  border-radius: 25%;                    /* the SVG's own corner radius */
}
nav.top .logo .logo-svg {
  display: block; width: 100%; height: 100%;
  --lg-light: color-mix(in oklch, var(--accent-500), white 18%);
  --lg-mid: var(--accent-500);
  --lg-dark: var(--accent-600);
}
nav.top .logo .logo-svg .lg-light { fill: var(--lg-light); }
nav.top .logo .logo-svg .lg-mid   { fill: var(--lg-mid); }
nav.top .logo .logo-svg .lg-dark  { fill: var(--lg-dark); }
nav.top .logo .logo-svg .lg-letters {
  fill: #fff;
  /* lightness above 0.68 gives 0.24 (dark ink), below gives 1 (white) */
  fill: oklch(from var(--lg-mid) max(0.24, min(1, (0.68 - l) * 1000)) min(c, 0.04) h);
}
/* Halloween's accent sits dark enough to read as rust in a block this
   size, so the logo gets its own pumpkin orange. */
:root[data-season="halloween"] nav.top .logo .logo-svg {
  --lg-light: oklch(0.82 0.15 70);
  --lg-mid: oklch(0.74 0.18 58);
  --lg-dark: oklch(0.65 0.19 48);
}
nav.top .logo .logo-word {
  line-height: 1;
  letter-spacing: 0.04em;
}
/* Very narrow screens: drop the wordmark so the bell + avatar can't
   collide with it. The R-square stays as the brand anchor. */
@media (max-width: 480px) {
  nav.top .logo .logo-word { display: none; }
  nav.top .row { gap: 6px; }
  .nav-actions { gap: 4px; }
}
.pill {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 50%);
  background: color-mix(in oklch, var(--red-500), transparent 88%);
  color: var(--ink-900);
  transition: 160ms ease;
}
.pill:hover {
  background: color-mix(in oklch, var(--red-500), transparent 70%);
  border-color: var(--red-500);
  color: var(--ink-900);
  transform: translateY(-1px);
}
.pill .arrow { font-size: 14px; }

/* ───── Nav action row ─────
   A small button cluster on the right side of the top nav. Primary
   (Upload) is the cherry-filled CTA; secondary actions are outline-only
   so they don't fight the primary; icon-only buttons (Invite, Discord)
   collapse to just their glyph on narrow screens. */
.nav-actions { display: flex; align-items: center; gap: 8px; }
.nav-btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase;
  height: 34px; padding: 0 14px;             /* matches search pill + profile chip */
  border-radius: var(--rad-sm);
  border: 1px solid var(--ink-300);
  background: transparent;
  color: var(--ink-800);
  transition: 140ms ease;
  white-space: nowrap;
  line-height: 1;                             /* stops font metrics inflating height */
}
.nav-btn:hover {
  border-color: var(--red-500);
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 90%);
  transform: translateY(-1px);
}
.nav-btn.primary {
  background: var(--red-500);
  border-color: var(--red-500);
  color: var(--on-accent);
}
.nav-btn.primary:hover {
  background: var(--red-400);
  border-color: var(--red-400);
  color: var(--on-accent-hover);
  box-shadow: 0 6px 22px -6px var(--red-glow);
}
/* Ko-fi support CTA (currently unused — the CTA lives inside the
   profile dropdown as .pd-kofi; kept here for future nav-bar use).
   Ko-fi brand purple #C19BFF so the mark stays on-brand and doesn't
   compete with the cherry-red primary Upload button. */
.nav-btn.support {
  color: #C19BFF;
  border-color: color-mix(in srgb, #C19BFF, transparent 30%);
  background: color-mix(in srgb, #C19BFF, transparent 92%);
  box-shadow: 0 0 12px -4px color-mix(in srgb, #C19BFF, transparent 40%);
}
.nav-btn.support .icon { color: #D6BBFF; }
.nav-btn.support:hover {
  color: #D6BBFF;
  border-color: #C19BFF;
  background: color-mix(in srgb, #C19BFF, transparent 82%);
  box-shadow: 0 0 20px -4px color-mix(in srgb, #C19BFF, transparent 30%);
  transform: translateY(-1px);
}
.nav-btn .icon { font-size: 14px; line-height: 1; }
.nav-btn.icon-only { padding: 0 9px; }
.nav-btn.icon-only .icon { font-size: 16px; }
.nav-btn.user { padding: 0 12px 0 6px; }
.nav-btn .avatar {
  width: 22px; height: 22px;
  border-radius: 999px;
  display: block;
  background: var(--ink-300);
}
.nav-btn .avatar-fallback {
  display: inline-grid; place-items: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  color: var(--ink-900);
  background: var(--red-600);
}
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.nav-btn.ghost { border-color: var(--ink-200); color: var(--ink-700); }

/* Support button — ghost frame with an always-on pink glow so it
   catches the eye without competing with Upload's filled-red CTA.
   The glow is a separate hue (magenta/pink) from the cherry brand,
   so the button reads as "extra, friendly" rather than "another
   primary action". Hover ramps both the glow and the star up.
   The nav template hides this entirely for current supporters via
   a conditional wrapper around the button element. */
.nav-btn.support {
  border-color: color-mix(in oklch, oklch(70% 0.25 350), transparent 50%);
  color: oklch(80% 0.16 350);
  background: color-mix(in oklch, oklch(70% 0.25 350), transparent 92%);
  box-shadow:
    0 0 12px color-mix(in oklch, oklch(70% 0.25 350), transparent 65%),
    inset 0 0 0 1px color-mix(in oklch, oklch(70% 0.25 350), transparent 80%);
  animation: support-pulse 2.6s ease-in-out infinite;
}
.nav-btn.support .icon {
  color: oklch(75% 0.22 350);
  font-size: 13px;
  transition: transform 140ms ease;
}
.nav-btn.support:hover {
  border-color: oklch(72% 0.28 350);
  color: oklch(90% 0.16 350);
  background: color-mix(in oklch, oklch(70% 0.25 350), transparent 80%);
  box-shadow:
    0 0 22px color-mix(in oklch, oklch(70% 0.25 350), transparent 35%),
    inset 0 0 0 1px color-mix(in oklch, oklch(70% 0.25 350), transparent 60%);
  transform: translateY(-1px);
}
.nav-btn.support:hover .icon {
  color: oklch(85% 0.24 350);
  transform: rotate(-12deg) scale(1.1);
}
@keyframes support-pulse {
  0%, 100% {
    box-shadow:
      0 0 12px color-mix(in oklch, oklch(70% 0.25 350), transparent 65%),
      inset 0 0 0 1px color-mix(in oklch, oklch(70% 0.25 350), transparent 80%);
  }
  50% {
    box-shadow:
      0 0 18px color-mix(in oklch, oklch(70% 0.25 350), transparent 45%),
      inset 0 0 0 1px color-mix(in oklch, oklch(70% 0.25 350), transparent 65%);
  }
}
@media (prefers-reduced-motion: reduce) {
  .nav-btn.support { animation: none; }
}
@media (max-width: 720px) {
  /* On phones, collapse Support to just the star to keep the row tidy. */
  .nav-btn.support .label { display: none; }
  .nav-btn.support { padding: 8px 11px; }
  .nav-btn.support .icon { font-size: 15px; }
}

/* Theme toggle: shows ☾ in dark mode and ☀ in light mode. */
.theme-toggle .theme-icon-dark  { display: none; }
.theme-toggle .theme-icon-light { display: inline; }
:root[data-theme="light"] .theme-toggle .theme-icon-dark  { display: inline; }
:root[data-theme="light"] .theme-toggle .theme-icon-light { display: none; }
.theme-toggle { cursor: pointer; }

/* ───── Avatar dropdown ───── */
.user-menu { position: relative; }
.user-menu summary { list-style: none; cursor: pointer; }
.user-menu summary::-webkit-details-marker { display: none; }
.user-summary {
  padding: 5px 12px 5px 5px;
  display: inline-flex; align-items: center; gap: 8px;
}
.user-summary .chev {
  font-size: 9px; line-height: 1;
  margin-left: -2px;
  transition: transform 160ms ease;
  color: var(--ink-600);
}
.user-menu[open] .user-summary .chev { transform: rotate(180deg); }
.user-menu[open] .user-summary {
  border-color: var(--red-500);
  color: var(--red-400);
}
.user-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 180px;
  padding: 6px;
  background: var(--ink-50);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  box-shadow: 0 18px 40px -12px rgba(0,0,0,0.6);
  z-index: 20;
  display: flex; flex-direction: column; gap: 2px;
}
.pop-item {
  display: block;
  padding: 9px 12px;
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--ink-800);
  border-radius: var(--rad-sm);
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  width: 100%;
}
.pop-item:hover {
  background: color-mix(in oklch, var(--red-500), transparent 88%);
  color: var(--red-400);
}
.pop-item.danger:hover {
  background: color-mix(in oklch, var(--red-500), transparent 78%);
  color: var(--red-300);
}
.pop-form { margin: 0; padding: 0; }

/* ───── Notification bell ─────
   Mirrors the user-menu's <details> pattern so click-out closes both.
   Badge is a tiny rounded pill anchored to the bell's top-right with
   absolute positioning. */
.notif-menu { position: relative; }
.notif-menu summary { list-style: none; cursor: pointer; }
.notif-menu summary::-webkit-details-marker { display: none; }
.notif-summary { position: relative; }
.notif-menu[open] .notif-summary {
  border-color: var(--red-500);
  color: var(--red-400);
}
.notif-badge {
  position: absolute;
  top: -4px; right: -4px;
  min-width: 16px; height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--red-500);
  color: var(--ink-0);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
  border: 2px solid var(--ink-0);
  pointer-events: none;
}
.notif-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  width: min(360px, 92vw);
  max-height: 480px;
  display: flex; flex-direction: column;
  background: var(--ink-50);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  box-shadow: 0 18px 40px -12px rgba(0,0,0,0.6);
  z-index: 20;
  overflow: hidden;
}
.notif-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 14px;
  border-bottom: 1px solid var(--ink-200);
}
.notif-head-title {
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-700);
}
.notif-head-clear {
  background: transparent; border: 0; cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-500);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.notif-head-clear:hover { color: var(--red-400); }
.notif-list {
  overflow-y: auto;
  flex: 1;
  padding: 4px;
}
.notif-empty {
  padding: 18px 14px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-500);
  text-align: center;
  letter-spacing: 0.04em;
}
.notif-row {
  display: flex; gap: 10px;
  padding: 10px;
  border-radius: var(--rad-sm);
  text-decoration: none;
  color: inherit;
  border-left: 2px solid transparent;
  cursor: pointer;
}
.notif-row:hover {
  background: color-mix(in oklch, var(--red-500), transparent 92%);
}
.notif-row.unread { border-left-color: var(--red-500); }
.notif-row .avatar {
  flex: 0 0 32px;
  width: 32px; height: 32px;
  border-radius: 999px;
  object-fit: cover;
  background: var(--ink-200);
  border: 1px solid var(--ink-300);
}
.notif-row .avatar-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-weight: 700; font-size: 13px;
  color: var(--ink-700);
}
.notif-row .body { flex: 1; min-width: 0; }
.notif-row .line {
  font-size: 13px;
  color: var(--ink-800);
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  overflow-wrap: anywhere;            /* long links wrap, never widen the menu */
}
.notif-row .line strong { color: var(--ink-800); font-weight: 700; }
.notif-row .line em {
  font-style: normal;
  color: var(--ink-700);
}
.notif-row .when {
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-500);
}
.notif-foot {
  display: block;
  padding: 10px 14px;
  border-top: 1px solid var(--ink-200);
  font-family: var(--font-mono);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--ink-700);
  text-align: center;
  text-decoration: none;
}
.notif-foot:hover { color: var(--red-400); }

/* Live toast stack — fixed bottom-left, newest on top. Each card
   slides in from below + fades out on dismiss. Pointer-events on the
   host are 'none' so the toast region doesn't block clicks behind it;
   each toast itself re-enables pointer-events. */
.notif-toast-host {
  position: fixed;
  left: 20px; bottom: 20px;
  display: flex; flex-direction: column-reverse;
  gap: 10px;
  z-index: 60;
  max-width: min(360px, 92vw);
  pointer-events: none;
}
.notif-toast {
  pointer-events: auto;
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px;
  background: var(--ink-50);
  border: 1px solid var(--ink-300);
  border-left: 3px solid var(--red-500);
  border-radius: var(--rad-md);
  box-shadow: 0 18px 40px -12px rgba(0,0,0,0.5);
  text-decoration: none;
  color: var(--ink-800);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 200ms ease, transform 200ms ease;
}
.notif-toast.show { opacity: 1; transform: translateY(0); }
.notif-toast:hover { border-color: var(--red-500); }
.notif-toast .avatar {
  flex: 0 0 32px;
  width: 32px; height: 32px;
  border-radius: 999px;
  object-fit: cover;
  background: var(--ink-200);
  border: 1px solid var(--ink-300);
}
.notif-toast .avatar-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-weight: 700; font-size: 13px;
  color: var(--ink-700);
}
.notif-toast .msg {
  flex: 1; min-width: 0;
  font-size: 13px;
  line-height: 1.35;
}
.notif-toast .msg strong { color: var(--ink-800); font-weight: 700; }
.notif-toast .msg em {
  font-style: normal;
  color: var(--ink-700);
}
.notif-toast .close {
  background: transparent;
  border: 0;
  color: var(--ink-500);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
  align-self: flex-start;
}
.notif-toast .close:hover { color: var(--red-400); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

@media (max-width: 720px) {
  .nav-btn .label { display: none; }
  .nav-btn { padding: 8px 11px; }
  .nav-btn.primary { padding: 8px 14px; }
  .nav-btn.primary .label { display: inline; }
  .user-summary .label, .user-summary .chev { display: none; }
  .user-pop { right: -8px; min-width: 200px; }
}
@media (max-width: 380px) {
  nav.top .row { padding-top: 14px; padding-bottom: 14px; gap: 8px; }
  nav.top .logo { font-size: 18px; }
  nav.top .logo .logo-mark { width: 24px; height: 24px; }
}

/* ───── Hero ───── */
.hero {
  position: relative;
  padding: 92px 0 72px;
}
.hero h1 {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(64px, 11vw, 156px);
  line-height: 0.86;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0;
  color: var(--ink-900);
}
.hero h1 .accent {
  color: var(--red-500);
  text-shadow: 0 0 36px var(--red-glow);
}
.hero h1 .stack {
  display: block;
}
.hero h1 .stack:nth-child(2) { padding-left: 8vw; }
.hero h1 .stack:nth-child(3) { padding-left: 2vw; }
.hero .kicker {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--red-400);
  margin: 0 0 28px;
  display: flex; align-items: center; gap: 14px;
}
.hero .kicker::before {
  content: ""; height: 1px; width: 60px;
  background: var(--red-500); display: inline-block;
}
.hero .lede {
  max-width: 580px;
  font-size: 18px;
  line-height: 1.5;
  color: var(--ink-700);
  margin: 36px 0 0;
}
.hero .lede strong { color: var(--ink-900); font-weight: 500; }

/* Mania-column vertical rules running down the hero — recurring motif. */
.hero::after {
  content: "";
  position: absolute; inset: 60px 0 0 0;
  background-image: repeating-linear-gradient(
    to right,
    transparent 0, transparent calc(25% - 0.5px),
    color-mix(in oklch, var(--ink-300), transparent 70%) calc(25% - 0.5px),
    color-mix(in oklch, var(--ink-300), transparent 70%) 25%);
  mask: linear-gradient(to bottom, transparent, black 40%, black 80%, transparent);
  pointer-events: none;
  z-index: -1;
}

/* ───── Steps strip ───── */
.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
  padding: 28px 0 80px;
  border-top: 1px solid var(--ink-300);
}
.step .num {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--red-400);
  letter-spacing: 0.18em;
}
.step h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 28px;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  margin: 4px 0 8px;
  color: var(--ink-900);
}
.step p {
  margin: 0;
  color: var(--ink-700);
  font-size: 14px;
  max-width: 30ch;
}

/* ───── Gallery ───── */
.gallery-head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 40px 0 22px;
}
.gallery-head .title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 56px;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  margin: 0;
}
.gallery-head .meta {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-500);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* Sub-line under the title on /p/{name} pages. Tells the visitor
   what they're filtered to + offers an out via the "All renders" link. */
.player-page-sub {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-500);
  margin: -10px 0 18px;
}
.player-page-sub strong {
  color: var(--ink-800);
  font-weight: 700;
}
.player-page-sub a {
  color: var(--red-400);
  text-decoration: none;
  margin-left: 8px;
}
.player-page-sub a:hover { color: var(--red-500); }

/* Sort + mode chip rows that sit above the grid. Two stacked rails of
   chips so the controls always wrap predictably on phones. */
.gallery-controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0 0 22px;
}
.gallery-controls .chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* Chips inherit .nav-btn's exact shape, type, and color tokens so the
   filter rail reads as the same UI language as the Upload CTA. Active
   chips reuse .nav-btn.primary's cherry fill (--red-500 / --ink-0). */
.gallery-controls .chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase;
  padding: 8px 14px;
  border-radius: var(--rad-sm);
  border: 1px solid var(--ink-300);
  background: transparent;
  color: var(--ink-800);
  cursor: pointer;
  white-space: nowrap;
  transition: 140ms ease;
}
.gallery-controls .chip:hover {
  border-color: var(--red-500);
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 90%);
  transform: translateY(-1px);
}
.gallery-controls .chip.active {
  background: var(--red-500);
  border-color: var(--red-500);
  color: var(--on-accent);
}
.gallery-controls .chip.active:hover {
  background: var(--red-400);
  border-color: var(--red-400);
  color: var(--on-accent-hover);
  box-shadow: 0 6px 22px -6px var(--red-glow);
}
.gallery-controls .chip:focus-visible {
  outline: 2px solid var(--red-500);
  outline-offset: 2px;
}
/* Tiny "N views" tag inside card.player. Same mono treatment as `when`
   so the two read as a pair, separated by " · " in the template. */
.card .views {
  color: var(--ink-500);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
}

/* ───── Comments ─────
   Each row is `[avatar] [head + body]` with the avatar pinned to the
   left and the head row carrying username + role chip + timestamp +
   delete link. All colors come from theme tokens so light mode picks
   up the inverted palette without separate rules. */
.comment {
  display: flex;
  gap: 12px;
  padding: 12px;
  background: var(--ink-100);
  border: 1px solid var(--ink-200);
  border-radius: var(--rad-md);
  font-size: 13px;
  line-height: 1.45;
}
.comment .avatar {
  flex: 0 0 36px;
  width: 36px; height: 36px;
  border-radius: 999px;
  object-fit: cover;
  background: var(--ink-200);
  border: 1px solid var(--ink-300);
}
.comment .avatar-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 14px;
  color: var(--ink-700);
  text-transform: uppercase;
}
.comment .comment-body { flex: 1; min-width: 0; }
.comment .comment-head {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 4px;
  font-family: var(--font-mono);
  font-size: 12px;
}
.comment .author {
  font-weight: 700;
  color: var(--ink-800);
  letter-spacing: 0.02em;
}
.comment .when {
  margin-left: auto;
  color: var(--ink-500);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.comment .when .comment-delete {
  color: var(--ink-500);
  text-decoration: none;
}
.comment .when .comment-delete:hover {
  color: var(--red-400);
  text-decoration: underline;
}
.comment .comment-text {
  color: var(--ink-800);
  white-space: pre-wrap;
  word-wrap: break-word;
}

/* Role chips — OWNER (cherry), ADMIN (cherry outline), SUPPORTER
   (gold). Identical pill shape + mono uppercase so they read as one
   family of tags. */
.role-chip {
  display: inline-flex; align-items: center;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 3px;
  border: 1px solid transparent;
  line-height: 1.4;
}
.role-chip.role-owner {
  background: var(--role-red-500);
  color: var(--ink-0);
}
.role-chip.role-admin {
  background: color-mix(in oklch, var(--role-red-500), transparent 80%);
  color: var(--role-red-400);
  border-color: var(--role-red-500);
}
.role-chip.role-supporter {
  /* Warm gold — not part of the red ramp so supporter status reads
     differently at a glance from admin/owner. */
  background: color-mix(in oklch, #b8932f, transparent 80%);
  color: #d8a838;
  border-color: color-mix(in oklch, #b8932f, transparent 40%);
}

/* ───── Contact form ─────
   Mirrors the placeholder page wrapper but adds a vertical form stack
   with mono labels and the standard ink tokens for inputs. */
.contact-form {
  display: flex; flex-direction: column;
  gap: 16px;
  max-width: 640px;
  margin: 24px 0;
}
.contact-field { display: flex; flex-direction: column; gap: 6px; }
.contact-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-700);
}
.contact-label em {
  font-style: normal;
  color: var(--ink-500);
}
.contact-field input,
.contact-field select,
.contact-field textarea {
  font-family: inherit;
  font-size: 14px;
  padding: 10px 12px;
  background: var(--ink-100);
  color: var(--ink-800);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.contact-field input:focus,
.contact-field select:focus,
.contact-field textarea:focus {
  outline: none;
  border-color: var(--red-500);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--red-500), transparent 88%);
}
.contact-field textarea {
  resize: vertical;
  min-height: 140px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.5;
}
.contact-hint {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-500);
}
/* Honeypot — visually + accessibility-hidden, but still present in
   the DOM for bots to fill in. */
.contact-honeypot {
  position: absolute !important;
  inset-inline-start: 0 !important;
  clip-path: inset(50%) !important;
  width: 1px !important; height: 1px !important;
  overflow: hidden !important;
}
.contact-error {
  padding: 12px 14px;
  border: 1px solid var(--red-500);
  background: color-mix(in oklch, var(--red-500), transparent 88%);
  color: var(--red-400);
  border-radius: var(--rad-sm);
  font-size: 13px;
}
.contact-success {
  padding: 18px;
  border: 1px solid var(--ink-300);
  background: var(--ink-100);
  border-radius: var(--rad-md);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-800);
  max-width: 640px;
  margin: 24px 0;
}
.contact-success code {
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--ink-200);
  padding: 2px 6px;
  border-radius: 3px;
}

.grid {
  display: grid;
  /* `min(100%, 320px)` keeps single-column behaviour on phones narrower
     than 320px without horizontal overflow; on wider screens it acts
     like a plain 320px floor. */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: 18px;
  padding-bottom: 0;
}
/* Infinite-scroll sentinel — invisible but takes layout space so the
   IntersectionObserver fires when scrolled near the bottom. The 600px
   rootMargin in JS pre-fetches before the user actually reaches it. */
.grid-sentinel { height: 1px; margin-top: 48px; }
.grid-end {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-700);
  letter-spacing: 0.2em;
  padding: 48px 0 80px;
}

.card {
  position: relative;
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  overflow: hidden;
  transition: 200ms cubic-bezier(.2,.7,.3,1);
  display: flex; flex-direction: column;
  isolation: isolate;
}
.card::before {
  /* Hairline cherry top-border that grows on hover (the "hit line"). */
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: var(--red-500);
  transform: scaleX(0);
  transform-origin: left;
  transition: 280ms cubic-bezier(.2,.7,.3,1);
}
.card:hover {
  background: var(--ink-200);
  border-color: color-mix(in oklch, var(--red-500), transparent 65%);
  transform: translateY(-3px);
  box-shadow:
    0 12px 32px -16px color-mix(in oklch, var(--red-500), transparent 70%),
    0 4px 12px -4px rgba(0,0,0,0.6);
}
.card:hover::before { transform: scaleX(1); }

.card .thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--ink-50);
  overflow: hidden;
}
.card .thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: 400ms ease;
}
.card:hover .thumb img { transform: scale(1.04); }
.card .thumb::after {
  /* Bottom gradient removed 2026-06-24 — it read as a dark "chin" on
     gallery thumbnails (esp. dark-bottomed std renders). */
  content: none;
}

.grade {
  position: absolute; top: 10px; right: 10px;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 28px;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: var(--rad-sm);
  background: color-mix(in oklch, var(--ink-0), transparent 25%);
  backdrop-filter: blur(6px);
  border: 1px solid color-mix(in oklch, currentColor, transparent 60%);
}
.grade.ss { color: var(--grade-ss); }
.grade.s  { color: var(--grade-s); }
.grade.a  { color: var(--grade-a); }
.grade.b  { color: var(--grade-b); }
.grade.c  { color: var(--grade-c); }
.grade.d  { color: var(--grade-d); }

.card .body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 8px; flex: 1 1 auto; }
.card .song {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.05;
  color: var(--ink-900);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  /* Truncate at 2 lines */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card .diff {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--red-400);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.card .stats {
  display: flex; gap: 18px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-700);
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}
.card .stats .score { color: var(--ink-900); font-weight: 600; }
.card .stats .acc { color: var(--ink-700); }
.card .player {
  display: flex; justify-content: space-between; align-items: baseline;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-500);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: auto;
  border-top: 1px solid var(--ink-300);
  padding-top: 10px;
}
.card .player .name { color: var(--ink-800); font-weight: 600; }
/* Clickable username — click runs user:NAME search instead of opening
   the render. cursor:pointer signals it. underline-on-hover matches the
   rest of the site's link styling. Outline shows up for keyboard nav. */
.card .player .name-link { cursor: pointer; }
.card .player .name-link:hover { color: var(--red-500); text-decoration: underline; }
.card .player .name-link:focus-visible {
  outline: 2px solid var(--red-500);
  outline-offset: 2px;
  border-radius: 2px;
}
.card .player .when { color: var(--ink-500); }
/* under the player, when someone else rendered their play */
.card .rendered-by {
  margin-top: 3px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--ink-500);
}
.card .rendered-by .name-link { color: var(--ink-700); cursor: pointer; }
.card .rendered-by .name-link:hover { color: var(--accent); text-decoration: underline; }
.card .rendered-by .name-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* Stagger card fade-in on initial load. */
@keyframes cardIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.card { animation: cardIn 360ms cubic-bezier(.2,.7,.3,1) backwards; }
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 60ms; }
.card:nth-child(3) { animation-delay: 120ms; }
.card:nth-child(4) { animation-delay: 180ms; }
.card:nth-child(5) { animation-delay: 240ms; }
.card:nth-child(6) { animation-delay: 300ms; }
.card:nth-child(7) { animation-delay: 360ms; }
.card:nth-child(8) { animation-delay: 420ms; }
.card:nth-child(n+9) { animation-delay: 480ms; }

.empty {
  padding: 64px 0;
  text-align: center;
  color: var(--ink-700);
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.gal-retry {
  font: inherit; margin-left: 6px; cursor: pointer;
  background: none; border: 0; padding: 0;
  color: var(--red-500); text-transform: inherit;
}
.gal-retry:hover { color: var(--red-600); text-decoration: underline; }

/* ───── Upload page ───── */
.upload-stage {
  padding: 80px 0 80px;
  max-width: 820px;
}
.up-kicker {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--red-500);
  margin: 0 0 14px;
}
.up-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(56px, 9vw, 116px);
  line-height: 0.88;
  letter-spacing: -0.02em;
  margin: 0 0 32px;
  color: var(--ink-900);
  text-transform: uppercase;
}
.up-banner {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 16px;
  border-radius: var(--rad-md);
  border: 1px solid var(--red-700);
  background: color-mix(in oklch, var(--red-500), transparent 88%);
  margin-bottom: 24px;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--ink-800);
}
.up-banner .ic {
  display: inline-grid; place-items: center;
  width: 24px; height: 24px;
  font-family: var(--font-display);
  font-weight: 900;
  background: var(--red-500);
  color: var(--ink-0);
  border-radius: 50%;
  flex: none;
}
.up-banner a { color: var(--red-400); text-decoration: underline; }

.up-drop {
  display: block;
  position: relative;
  padding: 56px 24px;
  border: 1.5px dashed var(--ink-300);
  border-radius: var(--rad-md);
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--red-500), transparent 96%),
    transparent 60%);
  text-align: center;
  cursor: pointer;
  transition: 160ms ease;
}
.up-drop:hover, .up-drop:focus-visible, .up-drop.hot {
  border-color: var(--red-500);
  background: color-mix(in oklch, var(--red-500), transparent 92%);
  outline: none;
}
.up-drop[data-disabled="1"] {
  cursor: not-allowed; opacity: 0.55;
}
.up-drop input[type="file"] {
  position: absolute; inset: 0; opacity: 0; cursor: pointer;
}
.up-drop input[type="file"]:disabled { cursor: not-allowed; }
.drop-icon {
  font-size: 44px; line-height: 1;
  color: var(--red-500);
  margin-bottom: 12px;
}
.drop-headline {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 28px;
  letter-spacing: -0.01em;
  color: var(--ink-900);
}
.drop-headline code {
  font-family: var(--font-mono);
  font-size: 22px;
  background: var(--ink-100);
  padding: 1px 8px;
  border-radius: 4px;
  color: var(--red-400);
}
.drop-sub {
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-600);
}
.drop-file {
  display: flex; align-items: center; gap: 16px;
  text-align: left;
  font-family: var(--font-mono);
}
.drop-file .file-check {
  display: inline-grid; place-items: center;
  width: 36px; height: 36px;
  border-radius: 999px;
  background: var(--red-500);
  color: var(--ink-0);
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 18px;
  flex: none;
}
.drop-file .file-meta { flex: 1 1 auto; min-width: 0; }
.drop-file .file-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-900);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drop-file .file-size {
  margin-top: 4px;
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-600);
}
.drop-file .file-clear {
  background: transparent;
  border: 1px solid var(--ink-300);
  color: var(--ink-700);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 7px 12px;
  border-radius: var(--rad-sm);
  cursor: pointer;
  flex: none;
  transition: 140ms ease;
}
.drop-file .file-clear:hover {
  border-color: var(--red-500);
  color: var(--red-400);
}

/* Render in-flight: shrink the drop zone, kill the hover, hide pointer. */
.up-drop.locked {
  padding: 18px 20px;
  cursor: default;
  border-style: solid;
  border-color: var(--ink-300);
  background: var(--ink-50);
}
.up-drop.locked .file-clear { display: none; }
.up-drop.locked input[type="file"] { pointer-events: none; }

/* ───── Inline render-settings panel ───── */
.up-settings {
  margin-top: 22px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  background: var(--ink-50);
  overflow: hidden;
}
.up-settings summary {
  list-style: none;
  cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  padding: 14px 20px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-800);
}
.up-settings summary::-webkit-details-marker { display: none; }
.up-settings .set-icon {
  display: inline-grid; place-items: center;
  width: 28px; height: 28px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--red-500), transparent 85%);
  color: var(--red-400);
  font-size: 14px;
}
.up-settings .set-label { color: var(--ink-900); }
.up-settings .set-summary {
  margin-left: auto;
  color: var(--ink-600);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0.02em;
}
.up-settings .set-chev {
  color: var(--ink-600);
  font-size: 12px;
  transition: transform 160ms ease;
  margin-left: 4px;
}
.up-settings[open] .set-chev { transform: rotate(180deg); }
.up-settings .set-body {
  padding: 6px 20px 20px;
  display: flex; flex-direction: column; gap: 18px;
}
.set-group {
  border: 0;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 12px 18px;
  align-items: start;
}
.set-group legend {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-600);
  padding-top: 8px;
  grid-column: 1;
  float: none;
}
.set-group .seg,
.set-group .seg { grid-column: 2; }
.set-group {
  display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: center;
}
.set-group legend { flex: 0 0 100%; }
.seg {
  position: relative;
  display: inline-flex; align-items: center; gap: 0;
  padding: 10px 14px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  cursor: pointer;
  background: transparent;
  transition: 140ms ease;
  min-width: 132px;
}
.seg input {
  position: absolute; opacity: 0; pointer-events: none;
}
.seg span {
  display: flex; flex-direction: column; gap: 2px;
  font-family: var(--font-mono);
}
.seg strong {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-800);
}
.seg em {
  font-style: normal;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--ink-600);
}
.seg:hover { border-color: var(--red-500); }
.seg:has(input:checked) {
  border-color: var(--red-500);
  background: color-mix(in oklch, var(--red-500), transparent 88%);
}
.seg:has(input:checked) strong { color: var(--red-400); }
/* Paywalled segment option: dim, can't be clicked, lock glyph in the title. */
.seg.locked {
  cursor: not-allowed;
  opacity: 0.55;
  border-style: dashed;
  background:
    repeating-linear-gradient(
      45deg,
      transparent 0 6px,
      color-mix(in oklch, var(--red-500), transparent 95%) 6px 12px);
}
.seg.locked:hover {
  border-color: var(--ink-300);
}
.seg.locked strong { color: var(--ink-600); }
.seg.locked .lock-glyph { font-size: 10px; margin-left: 4px; }
.seg.locked .locked-note {
  color: var(--red-400);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.set-more {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-600);
  padding-top: 4px;
  border-top: 1px dashed var(--ink-300);
}
.set-more .muted { color: var(--ink-500); }
.set-more a { color: var(--red-400); text-decoration: none; }
.set-more a:hover { color: var(--red-300); text-decoration: underline; }

/* Inline-preset block on the upload page. Uses the same .acc-section /
   .row / .seg / .toggle / .slider-row / .text-in styles as /settings so
   the included _preset_fields partial renders identically — this just adds
   the header strip above it. */
.up-preset {
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid color-mix(in oklch, var(--ink-300), transparent 50%);
}

/* --- replay-first upload layout (progressive disclosure) ------------------
   Everything below the drop zone lives in #postDropConfig, hidden until a
   replay is chosen. The skin control is hoisted into a prominent inline row;
   the rest of the settings collapse into the "Customize render" expander. */
.up-postdrop { margin-top: 18px; }
.up-postdrop[hidden] { display: none; }

/* Inline skin row — a section-style card (matches the accordion) but with a
   faint red wash so it reads as the prominent first-class control. */
.up-skin-inline { margin-bottom: 14px; }
/* Unified prominent skin card: chooser + combo colours + hitsounds in one
   bordered red-wash block under the replay (no separate SKIN accordion). */
.up-skin-inline:not(:empty) {
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--red-500), transparent 92%),
    var(--ink-50) 60%);
  padding: 4px 18px 8px;
}
.up-skin-inline .skin-picker-row,
.up-skin-inline .row {
  margin: 0; padding: 13px 0; border: none; border-radius: 0; background: none;
}
/* The skin row is display:none somewhere in its original section context;
   once hoisted into the prominent slot it must show. Scoped to :not([hidden])
   so applyMultimodeUI() can still hide it for bulk multi-mode drops. */
.up-skin-inline > .skin-picker-row:not([hidden]) { display: block !important; }
/* Inline skin thumbnail strip. */
.skin-strip { display: flex; gap: 10px; overflow-x: auto; padding: 8px 2px 4px;
  scrollbar-width: thin; scrollbar-color: var(--ink-300) transparent; }
.skin-strip::-webkit-scrollbar { height: 6px; }
.skin-strip::-webkit-scrollbar-thumb { background: var(--ink-300); border-radius: 3px; }
.skin-tile { flex: 0 0 auto; width: 96px; background: none; border: none; padding: 0;
  cursor: pointer; display: flex; flex-direction: column; gap: 6px; font: inherit; }
.skin-strip-thumb { aspect-ratio: 16 / 9; width: 100%; border-radius: var(--rad-sm);
  overflow: hidden; border: 2px solid var(--ink-300); background: var(--ink-50);
  display: grid; place-items: center; color: var(--ink-500); font-size: 22px;
  transition: border-color 150ms, box-shadow 150ms, color 150ms; }
.skin-strip-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.skin-strip-thumb.blank::before { content: "\25c8"; }
.skin-strip-thumb.add { border-style: dashed; font-size: 28px; font-weight: 300; }
.skin-tile-name { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink-700); text-align: center; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.skin-tile:hover .skin-strip-thumb { border-color: var(--red-400); color: var(--red-400); }
.skin-tile:hover .skin-tile-name { color: var(--ink-900); }
.skin-tile.is-selected .skin-strip-thumb { border-color: var(--red-500);
  box-shadow: 0 0 0 3px var(--red-glow); }
.skin-tile.is-selected .skin-tile-name { color: var(--ink-900); font-weight: 600; }
.skin-tile:focus-visible .skin-strip-thumb { outline: 2px solid var(--red-500); outline-offset: 2px; }
.up-skin-inline > .skin-picker-row + .row,
.up-skin-inline > .row + .row {
  border-top: 1px solid color-mix(in oklch, var(--ink-300), transparent 65%);
}

/* "Customize render" master expander — same card shell as a .acc-section so it
   sits natively above the section accordion it wraps. */
.up-customize {
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  background: var(--ink-50);
  overflow: hidden;
}
.up-customize-summary {
  display: flex; align-items: center; gap: 12px;
  padding: 15px 18px; cursor: pointer; list-style: none;
  font-family: var(--font-mono); font-weight: 700; font-size: 13px;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-900);
}
.up-customize-summary::-webkit-details-marker { display: none; }
.up-customize-summary:hover { color: var(--ink-900); }
.up-customize-summary .set-icon {
  display: inline-grid; place-items: center; width: 26px; height: 26px;
  border-radius: 999px; flex: none; font-size: 13px;
  background: color-mix(in oklch, var(--red-500), transparent 85%);
  color: var(--red-400);
}
.up-customize-title { flex: 1 1 auto; }
.up-customize-hint { color: var(--ink-700); font-weight: 500; }
.up-customize-summary .set-chev { transition: transform 140ms ease; }
.up-customize[open] .set-chev { transform: rotate(180deg); }
.up-customize-body {
  padding: 4px 16px 16px;
  border-top: 1px solid color-mix(in oklch, var(--ink-300), transparent 70%);
}
.up-customize-toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 12px 0 4px;
}

/* Once a replay is chosen, collapse the big dashed hero to a compact confirmed
   bar so focus shifts to the config below. Uses :has() (modern CSS, same tier
   as the color-mix/oklch already used throughout this stylesheet). */
.up-drop:has(.drop-file:not([hidden])) {
  padding: 14px 18px;
  border-style: solid;
  border-color: color-mix(in oklch, var(--red-500), transparent 55%);
  background: color-mix(in oklch, var(--red-500), transparent 95%);
  text-align: left;
  cursor: default;
}

/* Gentle reveal for the post-drop config. */
@keyframes up-reveal {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.up-postdrop:not([hidden]) { animation: up-reveal 180ms ease both; }
@media (prefers-reduced-motion: reduce) {
  .up-postdrop:not([hidden]) { animation: none; }
}

/* Render = the clear final step: a wide primary CTA, ghost "back" tucked beside. */
.up-postdrop .up-actions {
  margin-top: 18px;
  display: flex; align-items: center; gap: 12px;
}
.up-postdrop #submit-btn {
  flex: 1 1 auto;
  justify-content: center;
  padding: 15px 22px;
  font-size: 15px;
}
.up-postdrop .up-actions .btn.ghost { flex: none; }
.up-skin-inline .skin-picker-row .row-label,
.up-skin-inline .row .row-label {
  font-family: var(--font-display); font-weight: 700; font-size: 14px;
}
.up-preset-head {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 6px;
}
.up-preset-head .set-icon {
  display: inline-grid; place-items: center;
  width: 28px; height: 28px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--red-500), transparent 85%);
  color: var(--red-400);
  font-size: 13px;
  flex: none;
}
.up-preset-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 20px;
  margin: 0;
  flex: 1 1 auto;
  color: var(--ink-900);
}
.up-preset-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-700);
  text-decoration: none;
  border: 1px solid var(--ink-300);
  padding: 6px 10px;
  border-radius: var(--rad-sm);
  cursor: pointer;
  list-style: none;
  background: transparent;
  transition: 120ms ease;
}
.up-preset-link::-webkit-details-marker { display: none; }
.up-preset-link:hover { border-color: var(--red-500); color: var(--red-400); }
.up-presets-menu[open] .up-preset-link {
  border-color: var(--red-500);
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 90%);
}
.up-presets-chev {
  font-size: 10px;
  transition: transform 140ms ease;
}
.up-presets-menu[open] .up-presets-chev { transform: rotate(180deg); }

/* Presets dropdown panel */
.up-presets-menu { position: relative; }
.up-presets-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 240px;
  max-width: 320px;
  background: var(--ink-50);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  box-shadow: 0 22px 44px -16px rgba(0,0,0,0.6);
  padding: 6px;
  z-index: 25;
  display: flex; flex-direction: column;
  gap: 2px;
}
.up-presets-empty {
  padding: 14px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-600);
  line-height: 1.5;
}
.up-presets-empty a { color: var(--red-400); }
.up-preset-item {
  display: flex; align-items: center; gap: 10px;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 10px 12px;
  background: transparent;
  border: 0;
  border-radius: var(--rad-sm);
  color: var(--ink-800);
  cursor: pointer;
  width: 100%;
}
.up-preset-item:hover {
  background: color-mix(in oklch, var(--red-500), transparent 88%);
  color: var(--red-400);
}
.up-pi-name {
  flex: 1 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.up-pi-badge {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;
  padding: 2px 6px;
  background: color-mix(in oklch, var(--red-500), transparent 75%);
  color: var(--red-400);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 55%);
  border-radius: 3px;
  flex: none;
}
.up-presets-manage {
  display: block;
  margin-top: 4px;
  padding: 9px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-600);
  text-decoration: none;
  border-top: 1px solid color-mix(in oklch, var(--ink-300), transparent 50%);
  border-radius: 0 0 var(--rad-sm) var(--rad-sm);
}
.up-presets-manage:hover { color: var(--red-400); }

/* "Save changes" footer hint */
.save-hint {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--ink-600);
  margin-left: auto;
}
.save-hint .kbd {
  display: inline-grid; place-items: center;
  width: 18px; height: 18px;
  border-radius: 4px;
  background: var(--red-500);
  color: var(--ink-0);
  font-weight: 700;
  font-size: 12px;
  margin: 0 2px;
}
.up-preset-lede {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-700);
  margin: 0 0 14px;
  max-width: 56ch;
}
.up-preset-lede a { color: var(--red-400); text-decoration: underline; }

.up-actions {
  margin-top: 28px;
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
}
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 12px 18px;
  border-radius: var(--rad-sm);
  border: 1px solid var(--ink-300);
  background: transparent;
  color: var(--ink-800);
  cursor: pointer;
  transition: 140ms ease;
  text-decoration: none;
}
.btn.primary {
  background: var(--red-500);
  border-color: var(--red-500);
  color: var(--on-accent);
}
.btn.primary:disabled {
  background: var(--ink-200);
  border-color: var(--ink-200);
  color: var(--ink-500);
  cursor: not-allowed;
}
.btn.primary:not(:disabled):hover {
  background: var(--red-400);
  border-color: var(--red-400);
  box-shadow: 0 6px 22px -6px var(--red-glow);
  transform: translateY(-1px);
}
.btn.ghost:hover {
  border-color: var(--red-500);
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 90%);
}

.up-progress { margin-top: 24px; }
.up-progress.card {
  padding: 20px 22px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  background: linear-gradient(160deg,
    color-mix(in oklch, var(--red-500), transparent 94%),
    transparent 80%);
}
.up-progress.card.err { border-color: var(--red-700); }
.prog-head {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 12px;
}
.prog-dot {
  width: 8px; height: 8px;
  border-radius: 999px;
  background: var(--red-500);
  box-shadow: 0 0 0 0 color-mix(in oklch, var(--red-500), transparent 30%);
  animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in oklch, var(--red-500), transparent 30%); }
  70%  { box-shadow: 0 0 0 8px color-mix(in oklch, var(--red-500), transparent 100%); }
  100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--red-500), transparent 100%); }
}
.up-progress.err .prog-dot {
  animation: none;
  background: var(--red-700);
}
.prog-label {
  color: var(--ink-700);
  font-weight: 700;
}
.prog-pct {
  margin-left: auto;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
  color: var(--red-400);
  letter-spacing: 0;
  text-transform: none;
}
.prog-bar {
  height: 6px;
  background: var(--ink-200);
  border-radius: 999px;
  overflow: hidden;
}
.prog-fill {
  width: 0%;
  height: 100%;
  background: linear-gradient(90deg, var(--red-600), var(--red-400));
  transition: width 280ms ease;
  box-shadow: 0 0 12px var(--red-glow);
}
/* Indeterminate: some renders report no running % (overlay goes through the
   NAS queue, which only signals start + done). Rather than sit at a stuck
   0%, the bar slides a segment across so it reads as "working, duration
   unknown". The moment a real % arrives (grid/single), setPct() clears the
   class and the bar snaps to determinate. */
.prog-bar.is-indeterminate .prog-fill,
.bulk-row-bar.is-indeterminate .bulk-row-fill {
  width: 42%;
  transition: none;
  animation: progIndet 1.4s cubic-bezier(.45, 0, .55, 1) infinite;
}
@keyframes progIndet {
  0%   { transform: translateX(-105%); }
  100% { transform: translateX(245%); }
}
@media (prefers-reduced-motion: reduce) {
  .prog-bar.is-indeterminate .prog-fill,
  .bulk-row-bar.is-indeterminate .bulk-row-fill {
    animation: none; width: 100%; opacity: 0.45;
  }
}
.prog-text {
  margin-top: 12px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--ink-700);
}
.prog-text.err { color: var(--red-400); }
.prog-time {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-700);
  letter-spacing: 0.04em;
}
.bulk-row-time {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-700);
  letter-spacing: 0.04em;
}

/* Bulk render list — one row per replay when n>=2 files uploaded. */
.bulk-list {
  list-style: none;
  margin: 12px 0 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.bulk-row {
  padding: 10px 12px;
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: 8px;
}
.bulk-row.done { border-color: var(--green-500, #4ad991); }
.bulk-row.err  { border-color: var(--red-600); }
.bulk-row-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--ink-800);
  margin-bottom: 6px;
}
.bulk-row-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  margin-right: 12px;
  color: var(--ink-900);
}
.bulk-row-pct { color: var(--red-400); flex-shrink: 0; }
.bulk-row-bar {
  height: 4px;
  background: var(--ink-200);
  border-radius: 999px;
  overflow: hidden;
}
.bulk-row-fill {
  width: 0%;
  height: 100%;
  background: linear-gradient(90deg, var(--red-600), var(--red-400));
  transition: width 280ms ease;
}
.bulk-row.done .bulk-row-fill { background: var(--green-500, #4ad991); }
.bulk-row.err  .bulk-row-fill { background: var(--red-700); }
.bulk-row-text {
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-700);
}
.bulk-row-text a { color: var(--red-400); }
.bulk-cap-note {
  margin-top: 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-700);
  font-style: italic;
}

/* Multi-mode bulk skin picker — one row per detected gamemode. */
.multimode-skin-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}
.multimode-skin-entry {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: 8px;
}
.multimode-skin-entry .mode-chip {
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 4px;
  background: var(--ink-200);
  color: var(--ink-900);
  letter-spacing: 0.04em;
  flex-shrink: 0;
  min-width: 70px;
  text-align: center;
}
.multimode-skin-select {
  flex: 1 1 auto;
  background: var(--ink-200);
  color: var(--ink-900);
  border: 1px solid var(--ink-400);
  border-radius: 6px;
  padding: 6px 10px;
  font-family: var(--font-mono);
  font-size: 12px;
}

/* Selected-file list inside the drop zone when N>1 files are picked.
   Each row gets an auto-numbered prefix, a flex-1 label that truncates
   with ellipsis, and a right-aligned byte size. */
.file-list {
  list-style: none;
  margin: 12px 0 0 0;
  padding: 6px;
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: 8px;
  counter-reset: filelist;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.file-list li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 4px 8px;
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  counter-increment: filelist;
}
.file-list li:hover { background: var(--ink-200); }
.file-list .fl-num::before {
  content: counter(filelist, decimal-leading-zero) ".";
  color: var(--red-400);
  font-weight: 600;
  flex-shrink: 0;
}
.file-list .fl-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-900);
}
.file-list .fl-size {
  flex-shrink: 0;
  color: var(--ink-700);
  font-size: 11px;
}

/* Supporter banner on /settings — shows either the "Become a supporter"
   CTA or a "Thanks!" badge once is_supporter=1. */
.supporter-banner {
  display: flex; align-items: flex-start; gap: 14px;
  margin-top: 18px;
  padding: 16px 20px;
  border: 1px solid color-mix(in oklch, var(--red-500), var(--ink-300) 50%);
  border-radius: var(--rad-md);
  background:
    linear-gradient(135deg,
      color-mix(in oklch, var(--red-500), transparent 85%),
      transparent 70%),
    var(--ink-50);
  text-decoration: none;
  color: var(--ink-800);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.5;
  transition: 140ms ease;
}
a.supporter-banner:hover {
  border-color: var(--red-500);
  background:
    linear-gradient(135deg,
      color-mix(in oklch, var(--red-500), transparent 75%),
      transparent 60%),
    var(--ink-50);
  transform: translateY(-1px);
  box-shadow: 0 8px 24px -8px var(--red-glow);
}
.supporter-banner.supporter-active {
  border-color: var(--red-500);
}
.supporter-banner .sup-ic {
  display: inline-grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: 999px;
  background: var(--red-500);
  color: var(--ink-0);
  font-size: 14px;
  flex: none;
  margin-top: 1px;
}
.supporter-banner .sup-msg strong {
  display: block;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 16px;
  color: var(--ink-900);
  margin-bottom: 2px;
}
.supporter-banner code {
  font-family: var(--font-mono);
  font-size: 13px;
  background: var(--ink-100);
  padding: 1px 6px;
  border-radius: 3px;
  color: var(--red-400);
}
.supporter-banner .sup-tagline,
.supporter-block   .sup-tagline {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-600);
  margin: 8px 0 8px;
}
.supporter-banner .sup-perks,
.supporter-block   .sup-perks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex; flex-wrap: wrap;
  gap: 6px;
}
.supporter-banner .sup-perks li,
.supporter-block   .sup-perks li {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 4px 9px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--red-500), transparent 85%);
  color: var(--red-300);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 60%);
  white-space: nowrap;
}
.supporter-block .sup-howto {
  margin: 12px 0 0;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-700);
}
.supporter-banner .sup-until {
  display: inline-block;
  margin-top: 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-600);
}
.supporter-banner .sup-until time {
  color: var(--red-400);
  font-weight: 700;
}
.supporter-banner em {
  font-style: normal;
  display: inline-block;
  padding: 1px 6px;
  margin-right: 4px;
  background: var(--ink-100);
  color: var(--ink-700);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  border-radius: 3px;
}

/* Supporter tier grid — only shown on /settings to non-supporters. */
.supporter-block {
  margin-top: 18px;
  padding: 20px 22px 18px;
  border: 1px solid color-mix(in oklch, var(--red-500), var(--ink-300) 50%);
  border-radius: var(--rad-md);
  background:
    linear-gradient(135deg,
      color-mix(in oklch, var(--red-500), transparent 88%),
      transparent 70%),
    var(--ink-50);
}
.supporter-block .sup-head {
  display: flex; align-items: flex-start; gap: 14px;
  margin-bottom: 18px;
}
.sup-tiers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}
.sup-tier {
  display: flex; flex-direction: column; gap: 4px;
  padding: 14px 12px;
  text-align: center;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  text-decoration: none;
  color: var(--ink-800);
  background: var(--ink-0);
  transition: 140ms ease;
}
.sup-tier:hover {
  border-color: var(--red-500);
  background: color-mix(in oklch, var(--red-500), transparent 92%);
  transform: translateY(-2px);
  box-shadow: 0 8px 20px -8px var(--red-glow);
}
.tier-price {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 20px;
  color: var(--ink-900);
  letter-spacing: 0.01em;
}
.tier-months {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-700);
  text-transform: uppercase;
}
.tier-save {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--red-400);
}
.sup-fineprint {
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.55;
  color: var(--ink-600);
  margin: 0;
}

/* ───── Settings page ───── */
.settings-stage { padding: 64px 0 80px; max-width: 1200px; }
.settings-layout {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 36px;
  align-items: start;
}
.preset-main { min-width: 0; }   /* let grid track shrink, no overflow */

/* ───── Preset sidebar ───── */
.preset-side {
  position: sticky;
  top: 90px;
  align-self: start;
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--red-500), transparent 95%),
    transparent 40%),
    var(--ink-50);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  padding: 16px 14px;
  max-height: calc(100vh - 110px);
  overflow: auto;
}
.preset-side.collapsed { display: none; }
/* When the preset side is hidden, drop the 300px sidebar track too —
   otherwise the main pane flows into the empty 300px column and ends
   up squished. Uses :has() (widely supported in modern browsers). */
.settings-layout:has(.preset-side.collapsed) {
  grid-template-columns: minmax(0, 1fr);
}
/* When collapsed, show a floating "expand" button anchored to the
   page so the user can bring the sidebar back. The button sits below
   the sticky nav and stays visible while scrolling. */
.preset-side-expand {
  position: fixed; top: 90px; left: max(20px, calc(50vw - 580px));
  z-index: 40;
  width: 36px; height: 36px;
  border-radius: 999px;
  border: 1px solid var(--ink-300);
  background: var(--ink-100);
  color: var(--ink-900);
  cursor: pointer;
  font-size: 16px;
  display: none;
  align-items: center; justify-content: center;
}
.preset-side-expand:hover {
  border-color: var(--red-500);
  color: var(--red-500);
}
/* Body class toggled by the settings page JS — keeps the expand
   button shown only while the sidebar is collapsed. (Using a body
   class instead of a sibling selector because the button sits before
   the .settings-layout in the DOM and `~` only matches forward.) */
body.is-preset-side-collapsed .preset-side-expand {
  display: inline-flex;
}
.preset-side-head {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 4px 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid color-mix(in oklch, var(--ink-300), transparent 50%);
}
.preset-side-head .ps-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 18px;
  letter-spacing: 0.02em;
  flex: 1 1 auto;
  color: var(--ink-900);
  text-transform: uppercase;
}
.ps-action {
  width: 30px; height: 30px;
  border-radius: 999px;
  border: 1px solid var(--ink-300);
  background: transparent;
  color: var(--ink-700);
  display: grid; place-items: center;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  transition: 120ms ease;
}
.ps-action:hover {
  border-color: var(--red-500);
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 90%);
}
.ps-action#psNew {
  background: var(--red-500); color: var(--ink-0); border-color: var(--red-500);
  font-weight: 700;
}
.ps-action#psNew:hover {
  background: var(--red-400); border-color: var(--red-400);
  box-shadow: 0 0 14px color-mix(in oklch, var(--red-500), transparent 60%);
}

.preset-list { display: flex; flex-direction: column; gap: 10px; }
.preset-empty {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--ink-600);
  line-height: 1.5;
  padding: 12px 4px;
}

/* ───── Preset card ───── */
.preset-row {
  position: relative;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  background: var(--ink-0);
  transition: border-color 140ms ease, background 140ms ease;
  overflow: hidden;                 /* hide expandable section's border on collapse */
}
.preset-row:hover { border-color: color-mix(in oklch, var(--red-500), var(--ink-300) 70%); }
.preset-row.active {
  border-color: var(--red-500);
  background: color-mix(in oklch, var(--red-500), var(--ink-0) 96%);
}
/* Default = left red rail */
.preset-row.is-default::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--red-500);
}
.pr-card { padding: 12px 14px; }

.pr-top {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
}
.pr-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.01em;
  color: var(--ink-900);
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pr-badge {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;
  padding: 2px 7px;
  background: color-mix(in oklch, var(--red-500), transparent 75%);
  color: var(--red-400);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 55%);
  border-radius: 4px;
  flex: none;
}
.pr-sub {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--ink-600);
  margin-top: 6px;
}

.pr-actions {
  display: flex; gap: 6px;
  margin-top: 12px;
}
.pr-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 7px 8px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  background: transparent;
  color: var(--ink-700);
  cursor: pointer;
  flex: 1 1 auto;
  transition: 120ms ease;
}
.pr-btn:hover {
  border-color: var(--red-500);
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 92%);
}
.pr-btn.primary {
  background: var(--red-500);
  color: var(--ink-0);
  border-color: var(--red-500);
}
.pr-btn.primary:hover {
  background: var(--red-400); border-color: var(--red-400); color: var(--ink-0);
}
.pr-btn.pr-dot {
  flex: 0 0 32px;
  padding: 7px 0;
  font-size: 16px;
  letter-spacing: 0;
}
.pr-btn[aria-expanded="true"] {
  border-color: var(--red-500);
  background: color-mix(in oklch, var(--red-500), transparent 85%);
  color: var(--red-400);
}

/* ───── In-card expandable menu ───── */
.pr-extra {
  border-top: 1px solid var(--ink-300);
  background: color-mix(in oklch, var(--red-500), transparent 96%);
  display: flex; flex-direction: column;
}
.pr-extra-item {
  display: flex; align-items: center; gap: 10px;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 10px 16px;
  background: transparent;
  border: 0;
  color: var(--ink-800);
  cursor: pointer;
  transition: 120ms ease;
}
.pr-extra-item + .pr-extra-item {
  border-top: 1px solid color-mix(in oklch, var(--ink-300), transparent 60%);
}
.pr-extra-item .ic {
  display: inline-grid; place-items: center;
  width: 18px; height: 18px;
  font-size: 12px;
  color: var(--red-400);
  flex: none;
}
.pr-extra-item:hover {
  background: color-mix(in oklch, var(--red-500), transparent 85%);
  color: var(--red-400);
}
.pr-extra-item.danger:hover {
  background: color-mix(in oklch, var(--red-500), transparent 70%);
  color: var(--red-300);
}
.pr-extra-item.danger:hover .ic { color: var(--red-300); }

@media (max-width: 860px) {
  .settings-layout {
    grid-template-columns: minmax(0, 1fr);
  }
  .preset-side {
    position: static;
    max-height: 320px;
    margin-bottom: 8px;
  }
}
.set-head { margin-bottom: 28px; }
.set-lede {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-700);
  max-width: 64ch;
  margin-top: 6px;
}
.set-lede .badge-soon { vertical-align: 1px; }

.set-form { display: flex; flex-direction: column; gap: 16px; }

/* ───── Skin card (sits above the accordion) ───── */
.skin-card {
  margin: 0 0 22px;
  padding: 20px 24px 22px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  background:
    linear-gradient(155deg,
      color-mix(in oklch, var(--red-500), transparent 92%),
      transparent 70%),
    var(--ink-50);
}
.skin-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 8px;
}
.skin-head .set-icon {
  display: inline-grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--red-500), transparent 80%);
  color: var(--red-400);
  font-size: 14px;
  flex: none;
}
.skin-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 22px;
  margin: 0;
  flex: 1 1 auto;
  color: var(--ink-900);
}
.skin-state {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--red-400);
  padding: 4px 9px;
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 60%);
  border-radius: 4px;
  background: color-mix(in oklch, var(--red-500), transparent 88%);
}
.skin-lede {
  margin: 0 0 16px;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-700);
  max-width: 64ch;
}
.skin-lede code {
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--ink-100);
  padding: 1px 6px;
  border-radius: 3px;
  color: var(--red-400);
}
.skin-upload { display: flex; flex-direction: column; gap: 14px; }
.skin-drop {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px;
  border: 1.5px dashed var(--ink-300);
  border-radius: var(--rad-md, 12px);
  padding: 30px 22px;
  text-align: center;
  background: color-mix(in oklch, var(--ink-900), transparent 97%);
  cursor: pointer;
  transition: 140ms ease;
}
.skin-drop:hover, .skin-drop:focus-visible,
.skin-drop.is-dragover {
  border-color: var(--red-500);
  border-style: solid;
  background: color-mix(in oklch, var(--red-500), transparent 92%);
  outline: none;
}
.skin-drop.is-dragover { transform: scale(1.01); }
.skin-drop input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.skin-drop-icon {
  font-size: 30px; line-height: 1; color: var(--red-500);
  transition: transform 140ms ease;
}
.skin-drop:hover .skin-drop-icon, .skin-drop.is-dragover .skin-drop-icon {
  transform: translateY(-2px);
}
.skin-drop-title {
  font-family: var(--font-mono); font-size: 14px; color: var(--ink-900);
}
.skin-drop-title strong { color: var(--red-500); }
.skin-drop-link { color: var(--red-500); text-decoration: underline; }
.skin-drop-sub {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.04em;
  color: var(--ink-500);
}
.skin-drop-status:not(:empty) { margin-top: 6px; font-size: 12px; font-family: var(--font-mono); }
/* upload options row */
.skin-opt {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono); font-size: 12px; color: var(--ink-700);
}
.skin-opt-label { letter-spacing: 0.03em; }
.skin-opt select {
  font-family: var(--font-mono); font-size: 12px;
  padding: 5px 8px; border-radius: 6px;
  border: 1px solid var(--ink-300); background: var(--ink-50, transparent);
  color: var(--ink-900);
}
.skin-share em { font-style: normal; font-size: 10.5px; color: var(--ink-500); }
.skin-share.is-locked { opacity: 0.6; }
.skin-actions {
  display: flex; align-items: center; gap: 12px;
}

/* ───── Skin library: per-mode picks + library list ───── */
.skin-subhead {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-700);
  margin: 22px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid color-mix(in oklch, var(--ink-300), transparent 50%);
}
.skin-mode-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
@media (min-width: 720px) {
  .skin-mode-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.skin-mode {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 12px;
  background: var(--ink-100);
  border: 1px solid color-mix(in oklch, var(--ink-300), transparent 40%);
  border-radius: var(--rad-sm);
}
.skin-mode-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-600);
}
.skin-mode-pick {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 8px 28px 8px 10px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  background: var(--ink-50);
  color: var(--ink-900);
  cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-600) 50%),
    linear-gradient(135deg, var(--ink-600) 50%, transparent 50%);
  background-position:
    right 12px top 50%, right 7px top 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.skin-mode-pick:focus-visible {
  outline: 2px solid var(--red-500);
  outline-offset: 1px;
}

.skin-lib-list {
  list-style: none;
  margin: 0 0 4px;
  padding: 0;
  display: flex; flex-direction: column;
  gap: 6px;
}
.skin-lib-empty {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-600);
  font-style: italic;
  padding: 14px;
  text-align: center;
  border: 1px dashed color-mix(in oklch, var(--ink-300), transparent 30%);
  border-radius: var(--rad-sm);
}
.skin-lib-row {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 14px;
  background: var(--ink-100);
  border: 1px solid color-mix(in oklch, var(--ink-300), transparent 40%);
  border-radius: var(--rad-sm);
}
.skin-lib-info {
  flex: 1 1 auto;
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0;
}
.skin-lib-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  color: var(--ink-900);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.skin-lib-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--ink-600);
}
.skin-lib-meta strong {
  color: var(--red-400);
  font-weight: 700;
}
.skin-lib-del,
.skin-lib-share {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 12px;
  background: transparent;
  color: var(--ink-700);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  cursor: pointer;
  transition: 120ms ease;
}
.skin-lib-del:hover {
  color: oklch(70% 0.18 25);
  border-color: oklch(70% 0.18 25);
  background: color-mix(in oklch, oklch(70% 0.18 25), transparent 92%);
}
.skin-lib-share:hover {
  color: oklch(72% 0.14 145);
  border-color: oklch(72% 0.14 145);
  background: color-mix(in oklch, oklch(72% 0.14 145), transparent 92%);
}
.skin-lib-share.is-shared {
  color: oklch(72% 0.14 145);
  border-color: color-mix(in oklch, oklch(72% 0.14 145), transparent 50%);
}
.skin-lib-shared-pill {
  display: inline-block;
  margin-left: 8px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.12em;
  padding: 1px 6px;
  border-radius: 3px;
  background: color-mix(in oklch, oklch(72% 0.14 145), transparent 75%);
  color: oklch(82% 0.14 145);
  vertical-align: middle;
}

.skin-upload-opts {
  display: flex; align-items: center; gap: 14px;
  flex-wrap: wrap;
}
.skin-auto-select {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-700);
}
.skin-auto-select select {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 6px 22px 6px 10px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  background: var(--ink-50);
  color: var(--ink-900);
  cursor: pointer;
}
.skin-publish-toggle {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-700);
  cursor: pointer;
  user-select: none;
}
.skin-publish-toggle input[type="checkbox"] {
  width: 14px; height: 14px;
  accent-color: var(--red-500);
  cursor: pointer;
}
.skin-publish-toggle em {
  font-style: normal;
  color: var(--ink-500);
  font-size: 10px;
  letter-spacing: 0.03em;
}
.skin-publish-toggle.is-locked {
  color: var(--ink-500);
  cursor: help;
  opacity: 0.7;
}
.skin-publish-toggle.is-locked em {
  color: var(--red-500);
  text-transform: uppercase;
  font-weight: 600;
}

/* API-key reveal block (one-shot copy of newly minted key). */
.api-key-reveal {
  display: flex; gap: 10px; align-items: center;
  margin-top: 10px;
  padding: 10px 12px;
  background: var(--ink-100);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 60%);
  border-radius: var(--rad-sm);
}
.api-key-reveal code {
  flex: 1 1 auto;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--red-400);
  word-break: break-all;
  user-select: all;
}

/* ───── Dedup confirmation modal (upload page) ───── */
.dedup-modal {
  width: min(520px, 92vw);
  margin: auto;
  padding: 0;
  border: 1px solid color-mix(in oklch, var(--red-500), var(--ink-300) 60%);
  border-radius: var(--rad-md);
  background: var(--ink-50);
  color: var(--ink-900);
  box-shadow: 0 24px 60px -20px color-mix(in oklch, black, transparent 50%);
  overflow: hidden;
}
.dedup-modal::backdrop {
  background: color-mix(in oklch, black, transparent 40%);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}
.dedup-modal-head {
  padding: 18px 24px 4px;
  background:
    linear-gradient(120deg,
      color-mix(in oklch, var(--red-500), transparent 92%),
      transparent 70%),
    var(--ink-100);
}
.dedup-modal-title {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 800;
  margin: 0 0 4px;
  color: var(--ink-900);
}
.dedup-modal-body {
  padding: 12px 24px 18px;
}
.dedup-lede {
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-700);
  margin: 0 0 12px;
}
.dedup-meta {
  padding: 10px 12px;
  border: 1px solid color-mix(in oklch, var(--ink-300), transparent 50%);
  border-radius: var(--rad-sm);
  background: var(--ink-100);
}
.dedup-meta:empty { display: none; }
.dedup-map {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--ink-800);
}
.dedup-player {
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-600);
}
.dedup-modal-actions {
  display: flex; gap: 10px; justify-content: flex-end;
  padding: 14px 20px 18px;
  border-top: 1px solid color-mix(in oklch, var(--ink-300), transparent 50%);
  background: var(--ink-50);
  flex-wrap: wrap;
}

/* ───── Skin picker on upload page (button + modal w/ tabs) ───── */
.skin-picker-controls {
  display: flex; gap: 10px; align-items: center;
  flex-wrap: wrap;
}
.skin-picker-btn {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 10px 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-900);
  background: var(--ink-100);
  border: 1px solid color-mix(in oklch, var(--ink-300), transparent 30%);
  border-radius: var(--rad-sm);
  cursor: pointer;
  transition: 140ms ease;
}
.skin-picker-btn:hover {
  border-color: var(--red-500);
  background: color-mix(in oklch, var(--red-500), transparent 92%);
}
.skin-picker-icon {
  color: var(--red-400);
  font-size: 14px;
  flex: none;
}
.skin-picker-label {
  flex: 1 1 auto;
  text-align: left;
  max-width: 36ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.skin-picker-chev {
  color: var(--ink-500);
  font-size: 10px;
  flex: none;
}

/* (old upload skin-picker modal CSS removed 2026-07-12 — the modal is now the
   shared partial _skin_picker.html.j2 styled by skin_picker.css) */

.acc-section {
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  background: var(--ink-50);
  overflow: hidden;
  transition: border-color 160ms ease;
}
.acc-section:hover { border-color: var(--ink-400, var(--ink-300)); }
.acc-section[open] {
  border-color: color-mix(in oklch, var(--red-500), var(--ink-300) 80%);
}
.acc-section summary {
  list-style: none;
  cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  padding: 18px 22px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-800);
}
.acc-section summary::-webkit-details-marker { display: none; }
.acc-section .set-icon {
  display: inline-grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--red-500), transparent 86%);
  color: var(--red-400);
  font-size: 14px;
  flex: none;
}
.acc-section .set-label { flex: 1 1 auto; color: var(--ink-900); }
.acc-section .set-chev {
  color: var(--ink-600);
  font-size: 12px;
  transition: transform 160ms ease;
}
.acc-section[open] .set-chev { transform: rotate(180deg); }

/* Category labels that cluster the settings sections (show_groups). A small
   red-tinted eyebrow with a trailing rule, so the long accordion reads as a few
   organised groups instead of one flat wall. */
.acc-group-head {
  display: flex; align-items: center; gap: 12px;
  margin: 28px 2px 12px;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--red-400);
}
.acc-group-head::after {
  content: ""; flex: 1 1 auto; height: 1px;
  background: color-mix(in oklch, var(--ink-300), transparent 55%);
}
.acc-group-head:first-child { margin-top: 6px; }

/* Label that fronts the per-mode filter chips, so it's obvious they ARE the
   "show me <mode>'s settings" control. */
.mode-chips-label {
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-600); flex: none; margin-right: 2px;
}

.acc-body {
  padding: 8px 24px 24px;
  display: flex; flex-direction: column; gap: 16px;
  border-top: 1px solid color-mix(in oklch, var(--ink-300), transparent 50%);
}

.row {
  display: grid;
  grid-template-columns: minmax(180px, 220px) minmax(0, 1fr);
  gap: 10px 24px;
  align-items: center;
}
.row.toggle, .row.text { grid-template-columns: minmax(180px, 220px) minmax(0, 1fr); }
.row-head { display: flex; align-items: center; gap: 10px; min-width: 0; }
.row-label {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-800);
}
.row-help {
  grid-column: 2;
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--ink-600);
  line-height: 1.5;
}

/* Per-field help moved off an always-visible paragraph onto a small ⓘ
   hover/focus tooltip in the row head — declutters the form (Red: "its
   ALOT, kinda all over the place") while keeping the help one hover away.
   Pure CSS tooltip via data-tip; also exposed to AT via aria-label. */
.row-info {
  display: inline-grid; place-items: center;
  width: 16px; height: 16px;
  border-radius: 999px;
  font-size: 11px; line-height: 1;
  color: var(--ink-600);
  background: color-mix(in oklch, var(--ink-300), transparent 55%);
  cursor: help;
  position: relative;
  flex: none;
  transition: color 120ms ease, background 120ms ease;
}
.row-info:hover, .row-info:focus-visible {
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 80%);
  outline: none;
}
.row-info::after {
  content: attr(data-tip);
  position: absolute;
  left: 50%; bottom: calc(100% + 8px);
  transform: translateX(-50%) translateY(4px);
  width: max-content; max-width: 260px;
  white-space: normal;
  text-align: left;
  padding: 8px 10px;
  border-radius: var(--rad-sm);
  background: var(--ink-900, #16151a);
  color: var(--ink-50, #f3f2f7);
  border: 1px solid color-mix(in oklch, var(--ink-700), transparent 30%);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-transform: none;
  line-height: 1.5;
  box-shadow: 0 8px 24px rgba(0,0,0,0.35);
  opacity: 0; pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease;
  z-index: 30;
}
.row-info::before {
  content: "";
  position: absolute;
  left: 50%; bottom: calc(100% + 3px);
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--ink-900, #16151a);
  opacity: 0; pointer-events: none;
  transition: opacity 120ms ease;
  z-index: 31;
}
.row-info:hover::after, .row-info:focus-visible::after {
  opacity: 1; transform: translateX(-50%) translateY(0);
}
.row-info:hover::before, .row-info:focus-visible::before { opacity: 1; }

.row.row-soon .row-label { color: var(--ink-700); }
.row.row-soon input,
.row.row-soon select,
.row.row-soon .toggle { opacity: 0.78; }
.badge-soon {
  display: inline-block;
  padding: 2px 7px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  background: color-mix(in oklch, var(--red-500), transparent 80%);
  color: var(--red-400);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 60%);
  border-radius: 3px;
}
/* Mania-only badge — same shape as SOON but cooler, slightly muted so
   it reads as "informational" not "incomplete". */
.badge-mania {
  display: inline-block;
  padding: 2px 7px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  background: color-mix(in oklch, var(--ink-300), transparent 60%);
  color: var(--ink-700);
  border: 1px solid color-mix(in oklch, var(--ink-300), transparent 30%);
  border-radius: 3px;
  cursor: help;
}

/* Detected-mode chip next to file size in the drop zone. Picks up an
   accent colour per mode so the user can tell at a glance what was
   detected.  data-mode="3" (mania) = cherry; others = neutral. */
.mode-chip {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: color-mix(in oklch, var(--ink-300), transparent 40%);
  color: var(--ink-800);
  border: 1px solid var(--ink-300);
  border-radius: 4px;
}
.mode-chip[data-mode="3"] {
  background: color-mix(in oklch, var(--red-500), transparent 80%);
  color: var(--red-400);
  border-color: color-mix(in oklch, var(--red-500), transparent 50%);
}

/* Supporter-only badge — same shape as MANIA/STD but recoloured to
   the cherry palette + a tiny lock glyph appended in pseudo-element. */
.badge-supporter {
  display: inline-block;
  padding: 2px 7px 2px 18px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  background: color-mix(in oklch, var(--red-500), transparent 75%);
  color: var(--red-400);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 55%);
  border-radius: 3px;
  cursor: help;
  position: relative;
}
.badge-supporter::before {
  content: "🔒";
  position: absolute;
  left: 5px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 9px;
}

/* Rows for fields locked behind supporter — the input is `disabled`,
   the row gets a soft dim + a help tooltip. Caller decides which
   fields opt into supporter_only via presets.py. */
.row-supporter-locked .text-in,
.row-supporter-locked select.sel,
.row-supporter-locked input[type="checkbox"],
.row-supporter-locked input[type="range"] {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Mode badges (STD / STD-CTB) — share the same shape as .badge-mania
   but recoloured so the eye can tell them apart at a glance. */
.badge-std,
.badge-stdctb {
  display: inline-block;
  padding: 2px 7px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border-radius: 3px;
  cursor: help;
}
.badge-std {
  background: color-mix(in oklch, oklch(70% 0.13 240), transparent 70%);
  color: oklch(80% 0.13 240);
  border: 1px solid color-mix(in oklch, oklch(70% 0.13 240), transparent 50%);
}
.badge-stdctb {
  background: color-mix(in oklch, oklch(72% 0.14 145), transparent 70%);
  color: oklch(82% 0.14 145);
  border: 1px solid color-mix(in oklch, oklch(72% 0.14 145), transparent 50%);
}

/* When the upload form has detected a non-mania mode, dim mania-only
   rows and entire sections so the user knows those settings won't apply
   on this render. Detection is purely cosmetic — server still runs the
   right renderer either way.

   Mode codes (first byte of .osr):
     0 = std, 1 = taiko, 2 = ctb, 3 = mania. */

/* MANIA-only: dim for std / taiko / ctb. */
.up-form[data-detected-mode="0"] .row.mania-only,
.up-form[data-detected-mode="1"] .row.mania-only,
.up-form[data-detected-mode="2"] .row.mania-only {
  opacity: 0.4;
  pointer-events: none;
}
.up-form[data-detected-mode="0"] .acc-section.section-mania-only,
.up-form[data-detected-mode="1"] .acc-section.section-mania-only,
.up-form[data-detected-mode="2"] .acc-section.section-mania-only {
  opacity: 0.5;
  pointer-events: none;
}
.up-form[data-detected-mode="0"] .acc-section.section-mania-only summary::after,
.up-form[data-detected-mode="1"] .acc-section.section-mania-only summary::after,
.up-form[data-detected-mode="2"] .acc-section.section-mania-only summary::after {
  content: " — not applied to non-mania renders";
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-600);
  letter-spacing: 0.02em;
  text-transform: none;
}

/* STD-only: dim for taiko / ctb / mania. */
.up-form[data-detected-mode="1"] .row.std-only,
.up-form[data-detected-mode="2"] .row.std-only,
.up-form[data-detected-mode="3"] .row.std-only {
  opacity: 0.4;
  pointer-events: none;
}
.up-form[data-detected-mode="1"] .acc-section.section-std-only,
.up-form[data-detected-mode="2"] .acc-section.section-std-only,
.up-form[data-detected-mode="3"] .acc-section.section-std-only {
  opacity: 0.5;
  pointer-events: none;
}
.up-form[data-detected-mode="1"] .acc-section.section-std-only summary::after,
.up-form[data-detected-mode="2"] .acc-section.section-std-only summary::after,
.up-form[data-detected-mode="3"] .acc-section.section-std-only summary::after {
  content: " — only applies to osu!standard";
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-600);
  letter-spacing: 0.02em;
  text-transform: none;
}

/* STD + CTB: dim for taiko / mania. */
.up-form[data-detected-mode="1"] .row.std-ctb-only,
.up-form[data-detected-mode="3"] .row.std-ctb-only {
  opacity: 0.4;
  pointer-events: none;
}
.up-form[data-detected-mode="1"] .acc-section.section-std-ctb-only,
.up-form[data-detected-mode="3"] .acc-section.section-std-ctb-only {
  opacity: 0.5;
  pointer-events: none;
}
.up-form[data-detected-mode="1"] .acc-section.section-std-ctb-only summary::after,
.up-form[data-detected-mode="3"] .acc-section.section-std-ctb-only summary::after {
  content: " — only applies to std + ctb";
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-600);
  letter-spacing: 0.02em;
  text-transform: none;
}

/* CATCH-only: dim for std / taiko / mania (mode 2 = ctb). */
.up-form[data-detected-mode="0"] .row.catch-only,
.up-form[data-detected-mode="1"] .row.catch-only,
.up-form[data-detected-mode="3"] .row.catch-only {
  opacity: 0.4;
  pointer-events: none;
}
.up-form[data-detected-mode="0"] .acc-section.section-catch-only,
.up-form[data-detected-mode="1"] .acc-section.section-catch-only,
.up-form[data-detected-mode="3"] .acc-section.section-catch-only {
  opacity: 0.5;
  pointer-events: none;
}
.up-form[data-detected-mode="0"] .acc-section.section-catch-only summary::after,
.up-form[data-detected-mode="1"] .acc-section.section-catch-only summary::after,
.up-form[data-detected-mode="3"] .acc-section.section-catch-only summary::after {
  content: " — only applies to osu!catch";
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-600);
  letter-spacing: 0.02em;
  text-transform: none;
}

/* Segment rows reuse the upload-page .seg styles. */
.seg-row { display: flex; flex-wrap: wrap; gap: 10px; }

/* Native select dressed up to match the rest. Scoped to select: skin
   cards use .sel for "selected" and picked up this styling (mono font,
   padding, min-width), so a clicked card changed size. */
select.sel {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font-mono);
  font-size: 13px;
  padding: 10px 36px 10px 14px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  background: var(--ink-0)
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='%23999' d='M6 8 0 0h12z'/></svg>")
    no-repeat right 12px center;
  color: var(--ink-900);
  cursor: pointer;
  min-width: 220px;
}
select.sel:focus { outline: 2px solid var(--red-500); outline-offset: 1px; }

/* iOS-style toggle. */
.toggle {
  display: inline-flex; align-items: center; gap: 12px;
  cursor: pointer;
  user-select: none;
}
.toggle input { position: absolute; opacity: 0; pointer-events: none; }
.toggle .track {
  width: 38px; height: 22px;
  border-radius: 999px;
  background: var(--ink-300);
  position: relative;
  transition: 160ms ease;
  flex: none;
}
.toggle .thumb {
  position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px;
  border-radius: 999px;
  background: var(--ink-0);
  transition: 160ms ease;
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.toggle input:checked + .track {
  background: var(--red-500);
}
.toggle input:checked + .track .thumb {
  left: calc(100% - 20px);
  background: var(--ink-900);
}
.toggle .state-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-600);
  min-width: 26px;
}

/* Range slider. */
.slider-row {
  display: flex; align-items: center; gap: 14px;
}
.slider-row input[type=range] {
  flex: 1 1 auto;
  appearance: none;
  -webkit-appearance: none;
  height: 4px;
  background: var(--ink-300);
  border-radius: 999px;
  outline: none;
}
.slider-row input[type=range]::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--red-500);
  cursor: pointer;
  box-shadow: 0 0 12px color-mix(in oklch, var(--red-500), transparent 40%);
}
.slider-row input[type=range]::-moz-range-thumb {
  width: 18px; height: 18px;
  border: none;
  border-radius: 50%;
  background: var(--red-500);
  cursor: pointer;
  box-shadow: 0 0 12px color-mix(in oklch, var(--red-500), transparent 40%);
}
.slider-out {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  color: var(--red-400);
  min-width: 50px;
  text-align: right;
}

/* Text input. */
.text-in {
  font-family: var(--font-mono);
  font-size: 13px;
  padding: 10px 14px;
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  background: var(--ink-0);
  color: var(--ink-900);
  width: 100%;
  max-width: 360px;
}
.text-in:focus { outline: 2px solid var(--red-500); outline-offset: 1px; }

.set-actions {
  display: flex; align-items: center; gap: 12px;
  margin-top: 12px;
  flex-wrap: wrap;
}
.save-status {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-600);
}
.save-status.ok  { color: var(--red-400); }
.save-status.err { color: var(--red-600); }

@media (max-width: 720px) {
  .row { grid-template-columns: 1fr; gap: 6px; }
  .row-help { grid-column: 1; }
  .acc-body { padding: 8px 18px 20px; }
  .acc-section summary { padding: 16px 18px; }
  select.sel { min-width: 0; width: 100%; }
}

/* ───── Placeholder pages ───── */
.placeholder {
  /* horizontal padding comes from .shell (0 28px). Use padding-top /
     padding-bottom only so shorthand doesn't zero the horizontal
     (same class of bug we hit on nav.top .row and footer.bottom).
     56px top matches the canonical .shell.<stage> pattern so the
     kicker text on /about, /dmca lands at the same Y as /settings
     /my/reports /404 /login /upload — Leon 2026-07-12. */
  padding-top: 56px;
  padding-bottom: 64px;
}
.ph-kicker {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--red-400);
  margin: 0 0 18px;
}
.ph-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(54px, 8vw, 96px);
  line-height: 0.92;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0 0 24px;
  color: var(--ink-900);
}
.ph-lede {
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink-700);
  margin: 0 0 28px;
}
.ph-bullets {
  list-style: none; padding: 0; margin: 0 0 36px;
  display: flex; flex-direction: column;
  gap: 10px;
}
.ph-bullets li {
  padding: 12px 14px;
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-left: 2px solid var(--red-500);
  border-radius: var(--rad-sm);
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-800);
}
.ph-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* ───── Auth pages (RedVoice 1.1 layout, cherry-themed) ─────
   Centered card, 24 rem wide. No nav, no footer in the page body —
   only the card + a small build-info strip pinned to the viewport. */
.auth-stage {
  min-height: 100vh;
  display: grid; place-items: center;
  padding: 40px 20px;
  position: relative;
}
.auth-card {
  width: min(22rem, 100%);
  display: flex; flex-direction: column;
  gap: 22px;
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  padding: 28px 28px 22px;
  position: relative;
  isolation: isolate;
}
.auth-card::before {
  /* The same cherry hit-line motif as the gallery cards. */
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: var(--red-500);
  border-radius: var(--rad-md) var(--rad-md) 0 0;
}

.auth-brand {
  display: flex; align-items: center; justify-content: center;
  gap: 10px;
  margin: 0;
  text-decoration: none;
}
.auth-logo {
  width: 28px; height: 28px;
  border-radius: 7px;
  display: block;
  box-shadow: 0 0 16px color-mix(in oklch, var(--red-500), transparent 80%);
}
.auth-logo-fallback {
  display: none;
  width: 28px; height: 28px; border-radius: 7px;
  background: linear-gradient(155deg, var(--red-400), var(--red-600));
  color: var(--ink-900);
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 18px;
  align-items: center; justify-content: center;
  box-shadow: 0 0 16px color-mix(in oklch, var(--red-500), transparent 80%);
  line-height: 1;
}
.auth-wordmark {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--ink-900);
  font-weight: 600;
}
.auth-wordmark .dim { color: var(--ink-500); }

.auth-title-h {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 44px;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  margin: 0;
  color: var(--ink-900);
  text-align: center;
}

.auth-banner {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  margin: 0;
  background: color-mix(in oklch, var(--red-500), transparent 88%);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 55%);
  border-left-width: 3px;
  border-radius: var(--rad-sm);
  font-size: 13px;
  color: var(--ink-900);
}
.auth-banner .ic {
  flex: 0 0 18px;
  width: 18px; height: 18px;
  border-radius: 999px;
  background: var(--red-500);
  color: var(--ink-0);
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 13px;
  display: grid; place-items: center;
  line-height: 1;
}
.auth-banner .msg { flex: 1; }

.auth-primary {
  display: flex; align-items: center; justify-content: center;
  gap: 10px;
  height: 44px;
  margin: 0;
  border-radius: var(--rad-sm);
  background: var(--red-500);
  color: var(--ink-0);
  font-family: var(--font-mono);
  font-size: 13px; font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid var(--red-500);
  transition: 160ms ease;
}
.auth-primary:hover {
  background: var(--red-400);
  border-color: var(--red-400);
  color: var(--ink-0);
  transform: translateY(-1px);
  box-shadow: 0 12px 30px -10px color-mix(in oklch, var(--red-500), transparent 55%);
}
.auth-primary .osu-glyph {
  font-size: 18px;
  line-height: 1;
}
.auth-primary .arr { font-size: 16px; line-height: 1; }

.auth-divider {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 14px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-500);
}
.auth-divider::before, .auth-divider::after {
  content: ""; flex: 1; height: 1px;
  background: var(--ink-300);
}
.auth-divider span { white-space: nowrap; }

.auth-perks {
  list-style: none;
  margin: 0 0 28px;
  padding: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.auth-perks li {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-700);
  letter-spacing: 0.02em;
}
.auth-perks .b { color: var(--red-400); margin-right: 6px; }

.auth-back {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-500);
  text-decoration: none;
  margin: 0;
}
.auth-back:hover { color: var(--red-400); }

@media (max-width: 460px) {
  .auth-card { padding: 32px 24px 28px; }
  .auth-title h1 { font-size: 40px; }
}

/* ───── Footer ───── */
footer.bottom {
  position: relative;
  margin-top: 80px;
  /* horizontal padding comes from .shell so the footer content lines up
     with the hero / nav / gallery grid. Hairline above is drawn by the
     ::before pseudo instead of `border-top` — border-top would sit on
     the .shell element's edge (max 1180px), extending 28px past the
     content area on each side and reading as a stray line. */
  padding-top: 40px; padding-bottom: 60px;
  display: flex; justify-content: space-between; align-items: baseline;
  flex-wrap: wrap; gap: 16px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-500);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
footer.bottom::before {
  content: "";
  position: absolute;
  top: 0; left: 28px; right: 28px;
  height: 1px;
  background: var(--ink-300);
}
@media (max-width: 720px) {
  footer.bottom::before { left: 20px; right: 20px; }
}
footer.bottom .left { display: flex; flex-wrap: wrap; gap: 10px 16px; min-width: 0; }  /* wraps on phones instead of widening the page */
footer.bottom .storage {
  color: var(--ink-600);
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: none;
  letter-spacing: 0.02em;
  text-align: center;
  flex: 1 0 auto;
}
footer.bottom .storage strong {
  color: var(--red-400);
  font-weight: 700;
}
footer.bottom a { color: var(--ink-700); }
footer.bottom a:hover { color: var(--red-400); }
/* Tighten the '·' separator gap in the copyright so it doesn't read
   as a wide float between R3D and the year (Red 2026-09-11). */
footer.bottom .right { letter-spacing: 0.04em; word-spacing: -0.18em; }
@media (max-width: 720px) {
  footer.bottom .storage { order: 99; flex-basis: 100%; text-align: left; }
}

/* ───── Detail page ───── */
.detail-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 32px;
  padding: 32px 0 56px;
}
@media (max-width: 880px) {
  .detail-wrap { grid-template-columns: 1fr; }
}

.player-shell {
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  overflow: hidden;
  position: relative;
}
.player-shell::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: var(--red-500);
}
.player-shell video {
  display: block; width: 100%;
  background: #000;
}
.detail-head {
  padding: 24px 28px;
}
.detail-head .crumbs {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-500);
  margin-bottom: 14px;
  display: flex; gap: 10px; align-items: center;
}
.detail-head h1 {
  font-family: var(--font-display);
  font-weight: 900;
  /* clamp(min, ideal, max): 24px on phones, 64px on desktops. The
     previous min of 38px was too big for the mobile player-shell
     and forced the title to wrap into 3+ lines on iPhones. */
  font-size: clamp(24px, 5vw, 64px);
  line-height: 1.02;
  text-transform: uppercase;
  letter-spacing: -0.005em;
  margin: 4px 0 12px;
  color: var(--ink-900);
  word-break: break-word;
}
.detail-head .sub {
  color: var(--ink-700);
  font-size: 15px;
}

/* Meta row — "Player" + (optional) "Uploaded by", separated visually
   so users can tell who played from who submitted at a glance. */
.detail-head .meta-row {
  display: flex; flex-wrap: wrap;
  gap: 8px 18px;
  margin-bottom: 6px;
  font-family: var(--font-mono);
  font-size: 12px;
}
.detail-head .meta-pair {
  display: inline-flex; align-items: baseline; gap: 8px;
}
.detail-head .meta-key {
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 10px;
  color: var(--ink-500);
}
.detail-head .meta-val {
  color: var(--ink-800);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0;
}
.detail-head .meta-val.meta-link {
  text-decoration: none;
  border-bottom: 1px dotted var(--ink-500);
  transition: color 120ms ease, border-color 120ms ease;
}
.detail-head .meta-val.meta-link:hover {
  color: var(--red-400);
  border-bottom-color: var(--red-500);
}
/* Small "osu! ↗" chip that follows a player/beatmap name. Visually
   subordinate to the primary link — it's a side-trip, not the main
   action. */
.detail-head .meta-osu {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--ink-500);
  text-decoration: none;
  border: 1px solid var(--ink-300);
  border-radius: 4px;
  padding: 1px 6px;
  margin-left: 6px;
  transition: color 120ms ease, border-color 120ms ease;
}
.detail-head .meta-osu:hover {
  color: var(--red-400);
  border-color: var(--red-500);
}

/* Nav search input — sits inline with the other nav buttons; on narrow
   screens (≤480px) shrinks aggressively but stays usable. */
.nav-search {
  display: flex;
  margin: 0;
  position: relative;
}

/* Discord-style chip dropdown for the nav search. Absolutely
   positioned right under the input. Two sections — `.sd-categories`
   lists field filters when the input is empty, `.sd-suggest` lists
   value autocomplete when the cursor is inside a `field:partial`
   token. JS swaps between them. */
.search-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 280px;
  max-width: min(360px, calc(100vw - 24px));
  background: var(--ink-50);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  box-shadow: 0 12px 32px -8px color-mix(in oklch, black, transparent 70%);
  padding: 4px;
  z-index: 200;
  max-height: min(420px, 70vh);
  overflow-y: auto;
  font-family: var(--font-mono);
}
.search-dropdown[hidden] { display: none; }
.search-dropdown .sd-section + .sd-section { margin-top: 2px; }
.search-dropdown .sd-section[hidden] { display: none; }
.search-dropdown .sd-section-head {
  padding: 6px 10px 2px;
  font-size: 10px;
  color: var(--ink-500);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
/* Compact item layout — Discord-style but a touch tighter. The
   2-line title+sub block sits inside the same row height as a single
   line of body text would, giving a denser list at the same legibility.
   24×24 icon with 10px gap matches the visual rhythm of the rest of
   the nav. */
.search-dropdown .sd-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 5px 8px;
  background: transparent;
  border: 0;
  border-radius: var(--rad-sm);
  color: var(--ink-800);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  font-size: 13px;
}
.search-dropdown .sd-item + .sd-item { margin-top: 1px; }
.search-dropdown .sd-item:hover,
.search-dropdown .sd-item.sd-active {
  background: var(--ink-200);
}
.search-dropdown .sd-icon {
  flex: 0 0 22px;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  background: var(--ink-200);
  border-radius: 50%;
  color: var(--ink-600);
  font-size: 12px;
}
.search-dropdown .sd-item:hover .sd-icon,
.search-dropdown .sd-item.sd-active .sd-icon {
  background: var(--red-500);
  color: white;
}
.search-dropdown .sd-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  line-height: 1.2;
  gap: 1px;
}
.search-dropdown .sd-title { font-weight: 600; font-size: 13px; }
.search-dropdown .sd-sub {
  color: var(--ink-500);
  font-size: 10px;
  letter-spacing: 0.02em;
}
.search-dropdown .sd-suggest-list { display: flex; flex-direction: column; }
.search-dropdown .sd-suggest-empty {
  padding: 10px;
  color: var(--ink-500);
  font-size: 12px;
  text-align: center;
}

.nav-search input[type="search"] {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  padding: 7px 10px;
  width: 160px;
  background: var(--ink-100);
  color: var(--ink-800);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  transition: border-color 120ms ease, box-shadow 120ms ease, width 200ms ease;
}
.nav-search input[type="search"]:focus {
  outline: none;
  border-color: var(--red-500);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--red-500), transparent 88%);
  width: 280px;   /* match the filters dropdown (min-width:280px) */
}
@media (max-width: 720px) {
  .nav-search input[type="search"] { width: 110px; }
  .nav-search input[type="search"]:focus { width: min(280px, 60vw); }
}
/* Mobile search. The inline input can't fit the ≤480px nav row, but
   hiding it entirely (the old behaviour) left phones with NO way to
   search. Instead: a magnifier toggle button lives in the nav row
   (hidden on desktop), and tapping it expands .nav-search as a
   full-width bar pinned under the sticky nav. The existing chip
   dropdown / suggest JS keeps working — same form, same input. */
.search-toggle { display: none; }
@media (max-width: 480px) {
  .search-toggle { display: inline-flex; }
  .nav-search {
    display: none;
    position: absolute;
    top: 100%; left: 0; right: 0;
    padding: 10px 12px;
    /* Match the nav's frosted surface so the bar reads as part of it. */
    background: color-mix(in oklch, var(--ink-0), transparent 12%);
    backdrop-filter: blur(12px) saturate(140%);
    border-bottom: 1px solid color-mix(in oklch, var(--ink-300), transparent 40%);
  }
  .nav-search.open { display: flex; }
  .nav-search input[type="search"],
  .nav-search input[type="search"]:focus {
    width: 100%;
    /* ≥16px stops iOS Safari's auto-zoom-on-focus (fires below 16px). */
    font-size: 16px;
    padding: 10px 12px;
  }
  .search-dropdown {
    left: 0; right: 0;
    top: calc(100% + 2px);
    min-width: 0;
    max-width: none;
    /* Keep suggestions visible above the on-screen keyboard. */
    max-height: min(320px, 45vh);
  }
  /* Make room for the search toggle in the 360–480px row: Upload
     collapses to its icon (matching the other buttons ≤720px) and the
     Invite-bot ghost hides — it stays reachable from the user menu. */
  .nav-btn.primary .label { display: none; }
  .nav-btn.primary { padding: 8px 11px; }
  .nav-actions > .nav-btn.ghost[href="/invite"] { display: none; }
}

/* "More like this" rails on /v/{id} — compact card grid below the
   comments. Each card is a tighter version of the gallery card so the
   visual language stays consistent. */
.related-section {
  display: flex; flex-direction: column;
  gap: 28px;
  margin-top: 28px;
}
.related-rail .related-head {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-500);
  margin: 0 0 12px;
}
.related-rail .related-head a {
  color: var(--ink-700);
  text-decoration: none;
  border-bottom: 1px dotted var(--ink-500);
}
.related-rail .related-head a:hover { color: var(--red-400); }
.related-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 180px), 1fr));
  gap: 12px;
}
.related-card {
  display: flex; flex-direction: column;
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-sm);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-color 120ms ease, transform 120ms ease;
}
.related-card:hover {
  border-color: var(--red-500);
  transform: translateY(-1px);
}
.related-thumb { position: relative; aspect-ratio: 16 / 9; background: #000; }
.related-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.related-grade {
  position: absolute; top: 6px; right: 6px;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 14px;
  padding: 1px 6px;
  border-radius: 3px;
  color: #fff;
  letter-spacing: 0;
  background: color-mix(in oklch, var(--ink-0), transparent 25%);
  backdrop-filter: blur(4px);
}
.related-grade-ss { background: linear-gradient(135deg, #ffd700, #ffaa00); color: #2a1a00; }
.related-grade-s  { background: linear-gradient(135deg, #ffd700, #d4a017); color: #2a1a00; }
.related-grade-a  { background: linear-gradient(135deg, #6bd86b, #2fa82f); }
.related-grade-b  { background: linear-gradient(135deg, #5cb6ff, #1d7ad6); }
.related-grade-c  { background: linear-gradient(135deg, #d49aff, #8e57c8); }
.related-grade-d  { background: linear-gradient(135deg, #ff7a7a, #d62f2f); }
.related-body { padding: 8px 10px 10px; }
.related-song {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  color: var(--ink-800);
  line-height: 1.2;
  display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.related-sub {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--ink-500);
  margin-top: 4px;
  letter-spacing: 0.02em;
}

/* ───── /me dashboard ───── */
.me-header {
  display: flex; align-items: center; gap: 20px;
  margin: 32px 0 24px;
}
.me-avatar {
  width: 88px; height: 88px;
  border-radius: 999px;
  object-fit: cover;
  border: 2px solid var(--ink-300);
  background: var(--ink-100);
  flex: 0 0 88px;
}
.me-avatar-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 36px;
  color: var(--ink-700);
}
.me-header-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.me-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(28px, 4vw, 48px);
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0;
  color: var(--ink-900);
}
.me-header-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px 14px;
  font-family: var(--font-mono);
  font-size: 12px;
}
.me-header-meta a:not(.meta-osu) {
  color: var(--ink-700);
  text-decoration: none;
  border-bottom: 1px dotted var(--ink-500);
}
.me-header-meta a:not(.meta-osu):hover { color: var(--red-400); }

.me-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  padding: 18px;
  margin: 0 0 28px;
  background: var(--ink-50);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
}
.me-stat {
  display: flex; flex-direction: column;
  gap: 4px;
}
.me-stat-key {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-500);
}
.me-stat-val {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 22px;
  color: var(--ink-900);
  line-height: 1.1;
}

.me-section { margin: 0 0 32px; }
.me-section-head {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-500);
  margin: 0 0 6px;
}
.me-section-sub {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-500);
  margin: 0 0 14px;
  line-height: 1.4;
}

.me-failures, .me-activity {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column;
  gap: 10px;
}

.me-failure {
  padding: 12px 14px;
  background: color-mix(in oklch, var(--red-500), transparent 92%);
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 70%);
  border-radius: var(--rad-sm);
}
.me-failure-head {
  display: flex; justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}
.me-failure-title {
  color: var(--ink-800);
  font-weight: 700;
  font-size: 13px;
  font-family: var(--font-mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.me-failure-when {
  color: var(--ink-500);
  font-family: var(--font-mono);
  font-size: 11px;
  flex-shrink: 0;
}
.me-failure-body {
  font-size: 13px;
  line-height: 1.45;
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 8px;
  /* `word-break: break-word` plus `overflow-wrap` so long file paths
     in danser's error messages wrap instead of overflowing the card
     on narrow screens. */
  word-break: break-word;
  overflow-wrap: anywhere;
}
.me-failure-cls {
  background: var(--ink-200);
  color: var(--red-400);
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 1px 6px;
  border-radius: 3px;
  flex-shrink: 0;
}
.me-failure-msg {
  color: var(--ink-700);
  min-width: 0;
  word-break: break-word;
  overflow-wrap: anywhere;
}

.me-activity-row {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 10px 12px;
  background: var(--ink-100);
  border: 1px solid var(--ink-200);
  border-radius: var(--rad-sm);
}
.me-actor-avatar {
  flex: 0 0 32px;
  width: 32px; height: 32px;
  border-radius: 999px;
  object-fit: cover;
  background: var(--ink-200);
  border: 1px solid var(--ink-300);
}
.me-activity-body { flex: 1; min-width: 0; }
.me-activity-line {
  font-size: 13px;
  color: var(--ink-800);
  line-height: 1.4;
}
.me-activity-line strong { font-weight: 700; }
.me-activity-line a {
  color: var(--ink-800);
  text-decoration: none;
  border-bottom: 1px dotted var(--ink-500);
}
.me-activity-line a:hover { color: var(--red-400); }
.me-activity-preview {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--ink-700);
  font-style: italic;
}

@media (max-width: 480px) {
  .me-header { gap: 14px; margin: 20px 0 16px; }
  .me-avatar { width: 60px; height: 60px; flex: 0 0 60px; }
  .me-avatar-fallback { font-size: 24px; }
  .me-stat-val { font-size: 18px; }
}

/* About-page stats card — sits above the bullet list. */
.about-stats {
  display: grid;
  /* 1fr : 2fr so the single-number stat and the top-players list sit
     as a balanced pair. Total width still bounded by the .shell 1180
     so both cards align to the site grid on the outer edges. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: 16px;
  margin: 22px 0 32px;
}
@media (max-width: 720px) {
  .about-stats { grid-template-columns: 1fr; }
}
.about-stat {
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  padding: 14px 16px;
  display: flex; flex-direction: column;
  gap: 6px;
}
.about-stat-wide { grid-column: 1 / -1; }
.about-stat-key {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-500);
}
.about-stat-val {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 32px;
  color: var(--ink-900);
  line-height: 1;
}
.about-top-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.about-top-list li {
  display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-size: 13px;
}
.about-top-list a {
  color: var(--ink-800);
  text-decoration: none;
  font-weight: 700;
}
.about-top-list a:hover { color: var(--red-400); }
.about-top-count { color: var(--ink-500); font-size: 11px; }

/* Stat strip — small labeled cells in a flex row. Mobile-friendly:
   each cell is its own unit, wraps cleanly when the row overflows
   instead of fighting for one line. */
.detail-head .stat-strip {
  display: flex; flex-wrap: wrap;
  gap: 12px 22px;
  padding: 14px 16px;
  margin: 14px 0 16px;
  background: var(--ink-50);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
}
.detail-head .stat {
  display: flex; flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.detail-head .stat-key {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-500);
}
.detail-head .stat-val {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 18px;
  color: var(--ink-900);
  line-height: 1.15;
  white-space: nowrap;
}
/* Grade pill in the stat strip. Renamed from `.grade` to `.stat-grade`
   because the gallery already has a `.grade` class that's positioned
   absolutely on card thumbs — letting both live as `.grade` made the
   detail-head's grade letter inherit position:absolute and vanish from
   flow. The colored gradients below mirror osu!'s convention: gold for
   SS/S, green A, blue B, purple C, red D.

   Height is constrained to match the adjacent text stat-vals so the
   pill's baseline sits on the same horizontal line as "496,920",
   "91.18%", etc. — without that, the larger font + padding made the
   pill bottom dip below the row baseline. Line-height is locked at 1
   so the pill is exactly font-size + vertical padding tall. */
.detail-head .stat-val.stat-grade {
  position: static;          /* defensive — beat any inherited absolute */
  font-size: 18px;
  padding: 2px 9px;
  border-radius: 4px;
  color: #fff;
  display: inline-block;
  text-align: center;
  min-width: 1.6em;
  width: auto;
  height: auto;
  background: none;          /* overridden per-grade below */
  border: 0;
  backdrop-filter: none;
  line-height: 1.15;
  vertical-align: baseline;
}
.detail-head .stat-val.stat-grade-ss { background: linear-gradient(135deg, #ffd700, #ffaa00); color: #2a1a00; }
.detail-head .stat-val.stat-grade-s  { background: linear-gradient(135deg, #ffd700, #d4a017); color: #2a1a00; }
.detail-head .stat-val.stat-grade-a  { background: linear-gradient(135deg, #6bd86b, #2fa82f); }
.detail-head .stat-val.stat-grade-b  { background: linear-gradient(135deg, #5cb6ff, #1d7ad6); }
.detail-head .stat-val.stat-grade-c  { background: linear-gradient(135deg, #d49aff, #8e57c8); }
.detail-head .stat-val.stat-grade-d  { background: linear-gradient(135deg, #ff7a7a, #d62f2f); }

/* Judgment strip — mania's six counters as colored pills. Wraps on
   narrow screens; padding-light so it doesn't dominate. */
.detail-head .judgment-strip {
  display: flex; flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}
.detail-head .judgment-strip .j {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 5px 10px;
  border-radius: var(--rad-sm);
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  font-family: var(--font-mono);
  font-size: 12px;
}
.detail-head .judgment-strip .j .label {
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-500);
  font-size: 10px;
  font-weight: 700;
}
.detail-head .judgment-strip .j .val {
  color: var(--ink-800);
  font-weight: 700;
}
/* Hue per judgment so the eye picks "Miss" out fast. */
.detail-head .judgment-strip .j-320 .label { color: #ffd35c; }
.detail-head .judgment-strip .j-300 .label { color: #b8e3ff; }
.detail-head .judgment-strip .j-200 .label { color: #f4b66c; }
.detail-head .judgment-strip .j-100 .label { color: #71d090; }
.detail-head .judgment-strip .j-50  .label { color: #a09898; }
.detail-head .judgment-strip .j-miss .label { color: #ff7878; }

@media (max-width: 480px) {
  .detail-head { padding: 16px 16px; }
  .detail-head .stat-strip { gap: 8px 14px; padding: 10px 12px; }
  .detail-head .stat-val { font-size: 16px; }
  .detail-head .stat-val.stat-grade { font-size: 16px; padding: 1px 7px; }
  .detail-head .stat-key { font-size: 11px; }
}

.side {
  display: flex; flex-direction: column; gap: 16px;
}
.side .panel {
  background: var(--ink-100);
  border: 1px solid var(--ink-300);
  border-radius: var(--rad-md);
  padding: 18px 20px;
}
.side .panel h4 {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--red-400);
  margin: 0 0 12px;
  font-weight: 600;
}
.kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.kv dt {
  color: var(--ink-500);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 11px;
}
.kv dd { margin: 0; color: var(--ink-900); }
.kv dd.big { font-family: var(--font-display); font-weight: 700; font-size: 22px; }

.judgment-row {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 8px;
  margin-top: 8px;
}
.judgment-row .j {
  text-align: center;
  padding: 8px 0;
  border-radius: var(--rad-sm);
  background: var(--ink-50);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.judgment-row .j .label {
  display: block;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-500);
  margin-bottom: 4px;
}
.judgment-row .j .val { font-size: 14px; color: var(--ink-900); font-weight: 600; }
.judgment-row .j-320 { color: oklch(0.78 0.13 240); }
.judgment-row .j-300 { color: oklch(0.62 0.18 250); }
.judgment-row .j-200 { color: oklch(0.74 0.18 145); }
.judgment-row .j-100 { color: oklch(0.80 0.16 95); }
.judgment-row .j-50  { color: oklch(0.74 0.16 60); }
.judgment-row .j-miss { color: var(--red-400); }

.actions { display: flex; gap: 10px; flex-wrap: wrap; }
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 10px 14px;
  border-radius: var(--rad-sm);
  border: 1px solid var(--ink-300);
  background: var(--ink-200);
  color: var(--ink-900);
  transition: 140ms ease;
}
.btn:hover {
  border-color: var(--red-500);
  color: var(--red-400);
  background: color-mix(in oklch, var(--red-500), transparent 88%);
}
.btn.primary {
  background: var(--red-500);
  border-color: var(--red-500);
  color: var(--on-accent);
}
.btn.primary:hover { background: var(--red-400); color: var(--on-accent-hover); }

@media (max-width: 600px) {
  .shell { padding: 0 18px; }
  .hero { padding: 52px 0 40px; }
  .steps { grid-template-columns: 1fr; gap: 16px; padding: 22px 0 50px; }
  .gallery-head { padding: 36px 0 16px; }
  .gallery-head .title { font-size: 38px; }
}
@media (max-width: 380px) {
  /* Very small phones: tighten side padding and grid gap so cards
     don't get cramped or wrap to two narrow columns. */
  .shell { padding: 0 12px; }
  .grid { gap: 12px; }
  .gallery-head .title { font-size: 30px; }
  .empty { padding: 40px 0; font-size: 13px; }
}

/* Shared skin-thumbnail hover preview: a container marked [data-skin-clip]
   gets a muted looping <video> overlaid on hover. The container must clip and
   position its children. */
[data-skin-clip] { position: relative; overflow: hidden; }
.skin-hover-vid {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block; background: #000;
}

/* ───── Site banner ─────
   A thin strip above the nav that _base.html.j2 renders when the
   `site_banner` template global is truthy. `.kind-*` modifier picks the
   color scheme so one component covers beta / info / warning /
   maintenance without a template rewrite. Wired via EmbedServer's
   site_banner constructor arg — pass None to hide it site-wide. */
.site-banner {
  position: relative;
  z-index: 1;
  padding: 5px 34px;
  border-bottom: 1px solid transparent;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: oklch(0.97 0.02 60);
}
.site-banner a { color: inherit; text-decoration: underline; }
.site-banner.kind-beta {
  background: linear-gradient(90deg, oklch(0.32 0.12 60), oklch(0.40 0.16 50));
  border-bottom-color: oklch(0.55 0.18 50);
  box-shadow: 0 2px 12px -4px oklch(0.40 0.16 50 / 0.5);
}
.site-banner.kind-info {
  background: linear-gradient(90deg, oklch(0.28 0.10 230), oklch(0.36 0.14 220));
  border-bottom-color: oklch(0.55 0.18 220);
  box-shadow: 0 2px 12px -4px oklch(0.40 0.16 220 / 0.5);
}
.site-banner.kind-warn {
  background: linear-gradient(90deg, oklch(0.32 0.14 30), oklch(0.40 0.18 25));
  border-bottom-color: oklch(0.55 0.20 25);
  box-shadow: 0 2px 12px -4px oklch(0.40 0.20 25 / 0.5);
}
.site-banner.kind-maint {
  background: linear-gradient(90deg, oklch(0.28 0.02 260), oklch(0.34 0.03 260));
  border-bottom-color: oklch(0.48 0.03 260);
  box-shadow: 0 2px 12px -4px oklch(0.40 0.03 260 / 0.5);
}
.site-banner.kind-success {
  background: linear-gradient(90deg, oklch(0.32 0.13 152), oklch(0.42 0.16 150));
  border-bottom-color: oklch(0.58 0.19 150);
  box-shadow: 0 2px 12px -4px oklch(0.42 0.16 150 / 0.5);
}
/* Dismiss button — only rendered for banners carrying an id (announcements). */
.site-banner-close {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.6;
  border-radius: 5px;
  transition: opacity 0.12s ease, background 0.12s ease;
}
.site-banner-close:hover,
.site-banner-close:focus-visible { opacity: 1; background: oklch(1 0 0 / 0.16); }

/* ───── Score-link bar — shared component ─────
   Lives here, not in upload.css: the same bar renders in the home
   hero, which does not load that stylesheet. Unprefixed for the same
   reason — it can no longer assume a .upload-stage ancestor. */
.up-url {
  margin-top: 18px;
}
.up-url-row {
  display: flex;
  gap: 10px;
  align-items: stretch;
}
.up-url-input {
  flex: 1 1 auto;
  min-width: 0;                       /* or the flex child refuses to shrink */
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-800);
  padding: 12px 14px;
  border: 1px solid color-mix(in oklch, var(--ink-500), transparent 55%);
  border-radius: var(--rad-md);
  background: color-mix(in oklch, var(--ink-100), transparent 30%);
  transition: border-color 0.2s ease, background 0.2s ease;
}
.up-url-input::placeholder {
  color: var(--ink-500);
}
.up-url-input:focus-visible,
.up-url.hot .up-url-input {
  outline: none;
  border-color: var(--red-500);
  background: color-mix(in oklch, var(--red-500), transparent 94%);
}
.up-url-input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.up-url-go {
  flex: 0 0 auto;
  white-space: nowrap;
}
.up-url-hint {
  margin: 8px 2px 0;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-500);
}
/* Empty until score-link.js has something to say (no standing hint). */
.up-url-hint:empty { display: none; }
/* Message states. Errors here are usually actionable ("no replay stored,
   drop the .osr instead"), so they get room to be a sentence rather than
   being clipped to one line. */
.up-url-hint[data-kind="err"]  { color: var(--red-400); }
.up-url-hint[data-kind="ok"]   { color: var(--ink-800); }
.up-url-hint[data-kind="warn"] { color: var(--ink-800); opacity: 0.8; }

.up-url-prog {
  margin-top: 10px;
  height: 3px;
  border-radius: 2px;
  overflow: hidden;
  background: color-mix(in oklch, var(--ink-500), transparent 75%);
}
.up-url-prog-fill {
  height: 100%;
  background: var(--red-500);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.25s ease;
}
/* Before the first percentage arrives there is nothing honest to show,
   so sweep instead of sitting at 0% — which reads as stalled. */
.up-url-prog.is-indeterminate .up-url-prog-fill {
  transform: scaleX(0.28);
  animation: up-url-sweep 2.4s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
/* Slower and over a shorter run than the first pass, which crossed four
   viewport-widths in ~1s and read as frantic rather than as waiting. It
   should feel like something ticking over, not something racing. */
@keyframes up-url-sweep {
  0%   { transform: translateX(-40%)  scaleX(0.28); }
  100% { transform: translateX(400%)  scaleX(0.28); }
}
@media (prefers-reduced-motion: reduce) {
  .up-url-prog.is-indeterminate .up-url-prog-fill { animation: none; }
}
@media (max-width: 560px) {
  .up-url-row { flex-direction: column; }
}


/* ───── In-flight render tracker — INTERIM, see render-tracker.js ─────
   Deleted along with that script once render-completion becomes a real
   notification. Bottom-right so it never covers the nav or the upload
   drop zone, and above the fold on mobile it stacks upward. */
.r3d-track {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 60;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  max-width: min(340px, calc(100vw - 32px));
}
.r3d-track-row {
  position: relative;
  display: flex;
  gap: 10px;
  padding: 12px 34px 12px 14px;
  border: 1px solid color-mix(in oklch, var(--red-500), transparent 55%);
  background: color-mix(in oklch, var(--ink-100), transparent 4%);
  border-radius: var(--rad-md);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.35);
}
.r3d-track-body { flex: 1 1 auto; min-width: 0; }
.r3d-track-title {
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink-800);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.r3d-track-title a { color: var(--red-400); font-weight: 700; text-decoration: none; }
.r3d-track-title a:hover { text-decoration: underline; }
.r3d-track-x {
  position: absolute;
  top: 6px; right: 8px;
  padding: 2px 6px;
  border: 0; background: transparent;
  color: var(--ink-500);
  font-family: var(--font-mono); font-size: 15px; line-height: 1;
  cursor: pointer;
}
.r3d-track-x:hover { color: var(--red-500); }
.r3d-track-bar {
  height: 3px;
  border-radius: 2px;
  overflow: hidden;
  background: color-mix(in oklch, var(--ink-500), transparent 75%);
}
.r3d-track-fill {
  height: 100%;
  background: var(--red-500);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.25s ease;
}
.r3d-track-row.is-indeterminate .r3d-track-fill {
  transform: scaleX(0.28);
  animation: r3d-track-sweep 2.4s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes r3d-track-sweep {
  0%   { transform: translateX(-40%) scaleX(0.28); }
  100% { transform: translateX(400%) scaleX(0.28); }
}
.r3d-track-row.is-done { border-color: color-mix(in oklch, var(--red-400), transparent 35%); }
.r3d-track-row.is-err .r3d-track-title { color: var(--red-400); }
@media (prefers-reduced-motion: reduce) {
  .r3d-track-row.is-indeterminate .r3d-track-fill { animation: none; }
}

/* Score-link bar nested in a drop zone (both pages put it there). The
   drop zones are centred flex columns with generous padding, so the bar
   needs an explicit width or it collapses to its content, and its own
   left alignment or the hint inherits centre and reads as a caption. */
.up-drop .up-url,
.hero-upload-panel .up-url {
  width: 100%;
  max-width: 560px;
  margin-top: 24px;
  text-align: left;
  /* The drop zone is one big click target for the file picker; the bar
     is not part of that target, so give it its own cursor back. */
  cursor: auto;
}
.hero-upload-panel .up-url { max-width: none; }

/* ───── Holiday themes (seasons.py) ─────
   The season's emojis, scattered faintly behind the page. Fixed, so they
   stay put while the page scrolls; behind everything; never clickable. */
.season-sprinkles {
  position: fixed; inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  user-select: none; -webkit-user-select: none;
}
.season-sprinkles span {
  position: absolute;
  font-size: calc(var(--s, 1.5) * 1.6rem);
  line-height: 1;
  opacity: 0.13;
  transform: translate(-50%, -50%) rotate(var(--r, 0deg));
}
:root[data-theme="light"] .season-sprinkles span { opacity: 0.2; }
/* on wide screens they keep to the sides, fading out behind the content
   column so they never sit under a heading */
@media (min-width: 900px) {
  .season-sprinkles {
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 14%, transparent 30%, transparent 70%, #000 86%, #000 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 14%, transparent 30%, transparent 70%, #000 86%, #000 100%);
  }
  .season-sprinkles span { opacity: 0.18; }
  :root[data-theme="light"] .season-sprinkles span { opacity: 0.26; }
}
@media (max-width: 640px) {
  .season-sprinkles span { font-size: calc(var(--s, 1.5) * 1.1rem); }
}
/* the two tones in the page's backdrop glow */
:root[data-season] body {
  background-image:
    radial-gradient(1000px 700px at 0% 0%,
                    color-mix(in oklch, var(--accent-500), transparent 80%), transparent 65%),
    radial-gradient(1100px 800px at 100% 100%,
                    color-mix(in oklch, var(--accent-400), transparent 72%), transparent 70%);
}
.season-toggle {
  background: none; border: 0; padding: 0;
  color: inherit; font: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.season-toggle:hover { color: var(--accent-400); }

/* Structural regions keep the existing sticky header and page layout. */
#site-header, #site-page, #site-notices { display: contents; }
html[data-navigation-scroll] { scroll-behavior: auto !important; scroll-snap-type: none !important; }
[data-navigation-focus]:focus { outline: none; }
