/* ==========================================================================
   NuajDownload — admin & public UI
   One design system in two themes. Every colour is a token and every token is
   defined twice: the dark set on :root, the light set in the block below it.
   A component never names a colour, so a new surface is themed by construction
   rather than by remembering to theme it. Class names are shared with the Go
   templates; keep them stable.

   Which theme renders is decided by the server, which stamps data-theme on
   <html> from the account's preference (see internal/server/theme.go). The
   value "system" is resolved to data-resolved="light|dark" by a three-line
   script in the page head before first paint.
   ========================================================================== */

:root {
  color-scheme: dark;

  /* Surfaces (low → high elevation) */
  --bg: #0b0d12;
  --surface: #14171f;
  --surface-2: #191d27;
  --surface-3: #20242f;

  /* Lines & text */
  --line: #252b38;
  --line-strong: #333a4a;
  --line-hover: #414a5c;
  --fg: #e8ebf2;
  --fg-dim: #b6bdcc;
  --fg-muted: #8b93a6;
  /* Maximum-contrast text: the wordmark and the headings that carry a page. */
  --fg-strong: #ffffff;

  /* Brand accent + semantics */
  --accent: #5b8cff;
  --accent-hover: #7aa2ff;
  --accent-press: #4577f0;
  --accent-weak: rgba(91, 140, 255, .14);
  --accent-wash: rgba(91, 140, 255, .06);
  --accent-ring: rgba(91, 140, 255, .45);
  /* The two ends of a filled button's gradient, and the ink that sits on it.
     Separate from the hover token because "more prominent" means lighter in
     dark and darker in light — the gradient must not flip with it. */
  --accent-top: #7aa2ff;
  --accent-top-hover: #8fb0ff;
  --on-accent: #0a1020;
  /* Tinted cast under an accent-coloured control, so the colour appears to
     light the surface beneath it rather than float on top of it. */
  --accent-glow: rgba(91, 140, 255, .38);
  --accent-glow-strong: rgba(91, 140, 255, .6);

  --ok: #34d399;
  --ok-weak: rgba(52, 211, 153, .12);
  --ok-line: rgba(52, 211, 153, .35);
  --warn: #fbbf24;
  --warn-weak: rgba(251, 191, 36, .12);
  --warn-line: rgba(251, 191, 36, .35);
  --danger: #f87171;
  --danger-weak: rgba(248, 113, 113, .12);
  --danger-hot: rgba(248, 113, 113, .18);
  --danger-line: rgba(248, 113, 113, .38);

  /* Ambient page light, fixed to the viewport. */
  --glow-1: rgba(91, 140, 255, .11);
  --glow-2: rgba(120, 90, 255, .05);
  --select-bg: rgba(91, 140, 255, .28);

  /* The sticky bar is glass: a translucent fill over blurred content, closed by
     a half-strength hairline rather than a solid border. */
  --topbar-bg: rgba(11, 13, 18, .66);
  --topbar-line: rgba(37, 43, 56, .8);
  --topbar-sheen: inset 0 -1px 0 rgba(255, 255, 255, .015);

  /* Terminal surfaces: <pre>, .cmd, .token. Recessed — cut into the card rather
     than stacked on it. The light theme keeps the same relationship by tinting
     rather than by going white: a paper-white block on a white card has no edge
     of its own. */
  --code-bg: #0a0c11;
  --cmd-from: #090b10;
  --cmd-to: #0a0c12;
  --code-line: var(--line);
  --code-head-bg: linear-gradient(180deg, rgba(255, 255, 255, .035), rgba(255, 255, 255, .015));
  --code-inset: inset 0 1px 0 rgba(255, 255, 255, .025);
  --cmd-inset: inset 0 1px 3px rgba(0, 0, 0, .4);

  /* A recessed well inside a card (the package-manager block). */
  --well-bg: rgba(0, 0, 0, .22);
  --well-inset: inset 0 1px 2px rgba(0, 0, 0, .3);
  --press-inset: inset 0 1px 2px rgba(0, 0, 0, .3);
  --row-hover: rgba(255, 255, 255, .022);
  --zebra: rgba(255, 255, 255, .015);
  /* Page-title gradient: a whisper of light through wordmark-scale type. */
  --title-from: #ffffff;
  --title-to: #cdd4e4;
  /* Hover ink on a control that borrows the accent for its border. */
  --hover-fg: #ffffff;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238b93a6' d='M6 8.2 1.8 4l.9-.9L6 6.4l3.3-3.3.9.9z'/%3E%3C/svg%3E");

  /* Radii, shadows, motion */
  --r-sm: 7px;
  --r: 11px;
  --r-lg: 16px;
  --r-pill: 999px;
  --shadow-1: 0 1px 2px rgba(0, 0, 0, .3);
  --shadow-2: 0 6px 24px -8px rgba(0, 0, 0, .5);
  /* Ambient depth for raised surfaces: a tight contact shadow plus a wide, soft
     cast. Two layers read as a lit object; one reads as a drop shadow. */
  --shadow-card: 0 1px 2px rgba(0, 0, 0, .35), 0 12px 32px -16px rgba(0, 0, 0, .55);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, .3), 0 20px 44px -20px rgba(0, 0, 0, .65);
  /* The hairline of light along the top of a raised surface. Cheap, and it is
     most of what separates a flat panel from one that looks physical. */
  --sheen: inset 0 1px 0 rgba(255, 255, 255, .04);
  --ease: 140ms cubic-bezier(.2, .6, .3, 1);
  --ease-out: 220ms cubic-bezier(.16, .84, .28, 1);

  --maxw: 960px;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
}

/* --- Light ----------------------------------------------------------------
   Not the dark theme inverted. Light needs its own decisions: shadows carry
   the elevation that a lighter surface carried in dark, the accent darkens to
   stay legible as text on white, and every semantic colour is re-picked for
   contrast against paper rather than against ink.

   Both selectors, one block: an explicit choice (data-theme) and the resolved
   system preference (data-resolved) are the same rendering. */
:root[data-theme="light"],
:root[data-resolved="light"] {
  color-scheme: light;

  --bg: #f4f6fa;
  --surface: #fbfcfe;
  --surface-2: #ffffff;
  --surface-3: #eef1f7;

  --line: #e2e6ef;
  --line-strong: #ccd3e1;
  --line-hover: #b3bdd0;
  --fg: #101623;
  --fg-dim: #3c4457;
  --fg-muted: #69718a;
  --fg-strong: #080c15;

  /* Darkened until it reads as text on white, not as a highlight over it. */
  --accent: #2f62e0;
  --accent-hover: #1f4cc0;
  --accent-press: #173e9f;
  --accent-weak: rgba(47, 98, 224, .10);
  --accent-wash: rgba(47, 98, 224, .05);
  --accent-ring: rgba(47, 98, 224, .32);
  --accent-top: #4b7aea;
  --accent-top-hover: #3a68df;
  --on-accent: #ffffff;
  --accent-glow: rgba(47, 98, 224, .30);
  --accent-glow-strong: rgba(47, 98, 224, .42);

  --ok: #0c8a5c;
  --ok-weak: rgba(12, 138, 92, .10);
  --ok-line: rgba(12, 138, 92, .30);
  --warn: #9a6008;
  --warn-weak: rgba(154, 96, 8, .10);
  --warn-line: rgba(154, 96, 8, .28);
  --danger: #c4302c;
  --danger-weak: rgba(196, 48, 44, .08);
  --danger-hot: rgba(196, 48, 44, .14);
  --danger-line: rgba(196, 48, 44, .26);

  --glow-1: rgba(47, 98, 224, .07);
  --glow-2: rgba(120, 90, 255, .04);
  --select-bg: rgba(47, 98, 224, .18);

  --topbar-bg: rgba(255, 255, 255, .72);
  --topbar-line: rgba(203, 211, 226, .9);
  --topbar-sheen: inset 0 -1px 0 rgba(255, 255, 255, .7);

  --code-bg: #eef1f8;
  --cmd-from: #f0f3fa;
  --cmd-to: #e9edf6;
  --code-line: #d5dbe8;
  --code-head-bg: linear-gradient(180deg, rgba(16, 22, 35, .045), rgba(16, 22, 35, .02));
  --code-inset: inset 0 1px 0 rgba(255, 255, 255, .8);
  --cmd-inset: inset 0 1px 3px rgba(16, 22, 35, .07);

  --well-bg: rgba(16, 22, 35, .04);
  --well-inset: inset 0 1px 2px rgba(16, 22, 35, .06);
  --press-inset: inset 0 1px 2px rgba(16, 22, 35, .12);
  --row-hover: rgba(16, 22, 35, .035);
  --zebra: rgba(16, 22, 35, .022);
  --title-from: #0b1120;
  --title-to: #47506b;
  --hover-fg: var(--accent-hover);
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2369718a' d='M6 8.2 1.8 4l.9-.9L6 6.4l3.3-3.3.9.9z'/%3E%3C/svg%3E");

  /* Elevation in light is shadow, not surface colour: the card is barely
     lighter than the page, so the shadow has to do the whole job. */
  --shadow-1: 0 1px 2px rgba(16, 24, 40, .06);
  --shadow-2: 0 6px 24px -8px rgba(16, 24, 40, .18);
  --shadow-card: 0 1px 2px rgba(16, 24, 40, .05), 0 12px 30px -18px rgba(16, 24, 40, .20);
  --shadow-lift: 0 2px 6px rgba(16, 24, 40, .07), 0 20px 44px -22px rgba(16, 24, 40, .26);
  --sheen: inset 0 1px 0 rgba(255, 255, 255, .9);
}

/* ==========================================================================
   Type scale
   Every size in the product is one of these steps, and every step is a rem —
   so the whole scale moves together when the root size changes. That is the
   entire mechanism behind the text-size setting: no component knows about it.

   The steps are what the design already used, rationalized. Sizes expressed in
   em are deliberately left alone: a chevron at 1.15em or inline code at .875em
   is sized against the text it sits in, not against the page.
   ========================================================================== */
:root {
  --fs-micro: .7rem;   /* table headers, uppercase eyebrow labels */
  --fs-tiny: .74rem;   /* command-block labels, badge text */
  --fs-mini: .78rem;   /* drawer notes, dense meta */
  --fs-xs: .82rem;     /* hints, footnotes, small print */
  --fs-sm: .86rem;     /* secondary UI text, compact controls */
  --fs-md: .9rem;      /* buttons, table cells */
  --fs-base: .94rem;   /* body copy */
  --fs-lg: 1rem;       /* lead paragraphs, list-row titles */
  --fs-xl: 1.06rem;    /* h2 / section headings */
  --fs-2xl: 1.15rem;   /* prominent headings */
  --fs-d1: 1.6rem;     /* h1 */
  --fs-d2: 1.9rem;     /* page title */
  --fs-d3: 2.1rem;     /* product title */
  --fs-d4: 2.3rem;     /* hero */
  --fs-brand: 1.28rem; /* the wordmark, which answers to nothing else */

  /* Weights. Named by role so a heading is never "620" at one call site and
     "640" at another for no reason. */
  --fw-light: 350;
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-mid: 550;
  --fw-semibold: 620;
  --fw-bold: 660;
  --fw-black: 700;

  /* The text-size setting, in two parts.

     --font-base is what the product renders at when nobody has touched the
     setting. It is 1.2 because the old default was too small to read
     comfortably; that size is now simply "100%", and the slider runs either
     side of it rather than starting there.

     --font-step is the reader's own adjustment. The server stamps data-font on
     <html> and each stop is spelled out rather than interpolated, so an unknown
     value can only ever fall through to the default. */
  --font-base: 1.2;
  --font-step: 1;
  --font-scale: calc(var(--font-base) * var(--font-step));
}

:root[data-font="80"]  { --font-step: .8; }
:root[data-font="90"]  { --font-step: .9; }
:root[data-font="100"] { --font-step: 1; }
:root[data-font="110"] { --font-step: 1.1; }
:root[data-font="120"] { --font-step: 1.2; }

* { box-sizing: border-box; }

/* The one place the scale is applied. Everything downstream is rem, so this
   single multiplication resizes the product — including spacing expressed in
   rem, which is why the layout grows in proportion rather than stretching. */
html {
  font-size: calc(100% * var(--font-scale));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  /* Two light sources, fixed to the viewport so the page feels like content
     moving under a lit surface rather than a gradient scrolling with it. */
  background:
    radial-gradient(1100px 520px at 50% -300px, var(--glow-1), transparent 72%),
    radial-gradient(900px 700px at 100% 0%, var(--glow-2), transparent 70%),
    var(--bg);
  background-attachment: fixed;
  color: var(--fg);
  /* .9375rem = the 15px this has always been, expressed against the root so it
     travels with the scale. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, system-ui, sans-serif;
  font-size: .9375rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
}

a { color: var(--accent); text-decoration: none; transition: color var(--ease); }
a:hover { color: var(--accent-hover); }
a:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: none; border-radius: 5px;
  box-shadow: 0 0 0 3px var(--accent-ring);
}

::selection { background: var(--select-bg); }

/* Anchored targets must clear the sticky top bar. */
[id] { scroll-margin-top: 5rem; }

code {
  font-family: var(--mono);
  font-size: .875em;
  background: var(--surface-3);
  color: var(--fg-dim);
  padding: .12em .42em;
  border-radius: 5px;
  border: 1px solid var(--line);
}

pre {
  background: var(--code-bg);
  border: 1px solid var(--code-line);
  border-radius: var(--r);
  padding: 1rem 1.1rem;
  overflow-x: auto;
  margin: .6rem 0 0;
  box-shadow: var(--code-inset);
}
pre code {
  background: none; border: none; padding: 0;
  color: var(--fg-dim); font-size: var(--fs-xs); line-height: 1.78;
  /* Commands are read character by character; keep ligatures out of the way. */
  font-variant-ligatures: none;
}

/* --- Headings -------------------------------------------------------------
   Optical tracking: the larger the type, the tighter it wants to be. */
h1, h2, h3, h4 { line-height: 1.22; text-wrap: balance; }
h1 { font-size: var(--fs-d1); font-weight: var(--fw-bold); letter-spacing: -.024em; margin: 0; }
h2 { font-size: var(--fs-xl); font-weight: var(--fw-semibold); letter-spacing: -.014em; margin: 0 0 .2rem; }
h3 { font-size: var(--fs-base); font-weight: var(--fw-semibold); letter-spacing: -.008em; margin: 0; }
h4 { font-size: var(--fs-base); font-weight: var(--fw-semibold); letter-spacing: -.006em; margin: 0; }

/* ==========================================================================
   Top bar
   ========================================================================== */
.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: .7rem 1.5rem;
  background: var(--topbar-bg);
  backdrop-filter: saturate(1.6) blur(16px);
  -webkit-backdrop-filter: saturate(1.6) blur(16px);
  /* A hairline, not a border: 1px of solid --line reads as a hard edge under a
     blurred bar. Half-strength plus a faint inner highlight reads as glass. */
  border-bottom: 1px solid var(--topbar-line);
  box-shadow: var(--topbar-sheen);
}
/* The wordmark is the one thing on the page that is not sized by its role in a
   document — it is the product's signature, and it has to hold the left end of
   the bar against a nav of five items. Hence its own step above the heading
   scale, and a mark sized to the wordmark's full ascender-to-descender height
   rather than to its cap height. */
.brand {
  display: inline-flex; align-items: center;
  font-weight: var(--fw-black); font-size: var(--fs-brand); color: var(--fg-strong); letter-spacing: -.022em;
}
.brand:hover { color: var(--fg-strong); }
.brand span { color: var(--accent); font-weight: var(--fw-light); }
/* Space sits between the logo and the wordmark only — "Nuaj" and "Download"
   stay flush so the mark reads as one word. */
.brand-mark { width: 2.05rem; height: 2.05rem; display: block; margin-right: .55rem; }

.userbar { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.userbar a {
  color: var(--fg-muted); font-size: var(--fs-md); font-weight: var(--fw-medium);
  padding: .38rem .6rem; border-radius: var(--r-sm); transition: color var(--ease), background var(--ease);
}
.userbar a:hover { color: var(--fg); background: var(--surface-2); }
/* Current section. Filled rather than underlined so it survives the blur of
   the sticky bar over scrolling content. */
.userbar a.on { color: var(--fg); background: var(--surface-3); }
.userbar .sep { width: 1px; align-self: stretch; margin: .25rem .45rem; background: var(--line); }
.who {
  display: inline-flex; align-items: center; gap: .35rem;
  color: var(--fg-muted); font-size: var(--fs-md); padding: 0 .3rem;
}
.who svg { opacity: .7; }
.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; cursor: pointer;
  color: var(--fg-muted); padding: .38rem; border-radius: var(--r-sm);
  transition: color var(--ease), background var(--ease);
}
.iconbtn:hover { color: var(--fg); background: var(--surface-2); }

/* ==========================================================================
   Layout
   ========================================================================== */
.wrap { max-width: var(--maxw); margin: 2.2rem auto 4rem; padding: 0 1.5rem; }
.wrap > h1 { margin-bottom: 1rem; }

/* ==========================================================================
   Cards
   ========================================================================== */
.card {
  position: relative;
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 1.35rem 1.45rem;
  margin: 1.1rem 0;
  /* Contact + ambient shadow, plus the top sheen. Together these are what make
     a card read as raised rather than as a differently-coloured rectangle. */
  box-shadow: var(--shadow-card), var(--sheen);
}
.card > h2:first-child, .card > h3:first-child { margin-top: 0; }
.card.narrow { max-width: 380px; margin: 12vh auto 0; }
.card.ok { border-color: var(--ok-line); background: linear-gradient(180deg, var(--ok-weak), var(--surface)); }
/* Work waiting on the admin (e.g. pending access requests): warm-tinted so it
   reads as a queue to clear, not as another neutral panel. */
.card.attention { border-color: var(--warn-line); background: linear-gradient(180deg, var(--warn-weak), var(--surface)); }
/* Irreversible actions, kept visually quarantined at the foot of a page. */
.card.danger-zone { border-color: var(--danger-line); background: linear-gradient(180deg, var(--danger-weak), var(--surface)); }
.card.danger-zone h2 { color: var(--danger); }
/* What an irreversible action would destroy, itemized. Unbulleted and led by a
   rule so the list reads as a manifest of consequences rather than as prose
   with decoration: each line is one thing that stops existing. */
.losslist { list-style: none; margin: .8rem 0 0; padding: 0; font-size: var(--fs-base); }
.losslist li {
  color: var(--fg-dim); line-height: 1.5;
  padding: .42rem 0 .42rem .85rem;
  border-left: 2px solid var(--danger-line);
}
.losslist li + li { margin-top: .15rem; }
.losslist strong { color: var(--fg); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }
/* A drawer whose contents are destructive says so on the closed summary. */
.drawer > summary .note.danger { color: var(--danger); }
/* ...and one holding a problem says so before it is opened, so a silent failure
   is visible from the collapsed page rather than only to whoever went looking. */
.drawer > summary .note.warn { color: var(--warn); border-color: var(--warn-line); background: var(--warn-weak); }

/* A condition the operator has to act on, stated where the thing it affects is
   configured. Warm-tinted like .card.attention — same meaning, drawer scale. */
.warn-block {
  margin-bottom: 1rem; padding: .85rem 1rem;
  border: 1px solid var(--warn-line); border-radius: var(--r-md, 8px);
  background: var(--warn-weak);
  font-size: var(--fs-base);
}
.warn-block > :first-child { margin-top: 0; }
.warn-block > :last-child { margin-bottom: 0; }
.warn-block strong { color: var(--warn); font-weight: var(--fw-semibold); }
.warn-block ul { list-style: none; margin: .6rem 0; padding: 0; }
.warn-block li {
  color: var(--fg-dim); line-height: 1.5;
  padding: .3rem 0 .3rem .85rem; border-left: 2px solid var(--warn-line);
}

/* ==========================================================================
   Auth screens (sign in / two-factor) — a centered, elevated single card.
   The base topbar is hidden on these pages, so the card is the whole stage.
   ========================================================================== */
.auth {
  /* Offsets .wrap's vertical margins and the site footer, so the card is
     centred in what is left of the viewport rather than pushed into a scroll. */
  min-height: calc(100vh - 10rem);
  display: flex; align-items: center; justify-content: center;
}
.auth-card {
  position: relative; overflow: hidden;
  width: 100%; max-width: 384px; margin: 0;
  padding: 2.1rem 2rem 1.7rem;
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 80% at 50% -20%, var(--accent-weak), transparent 60%),
    linear-gradient(180deg, var(--surface-2), var(--surface));
  box-shadow: var(--shadow-2), var(--sheen);
}
/* Hairline of brand light along the very top edge. */
.auth-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent-ring), transparent);
}
.auth-head { text-align: center; margin-bottom: 1.6rem; }
/* Bare logo mark (no badge) — sized to carry the header on its own, lifted by a
   soft brand-tinted glow so it reads with depth against the card. */
.auth-mark {
  width: 4.25rem; height: 4.25rem; display: block; margin: 0 auto 1rem;
  filter: drop-shadow(0 6px 20px var(--accent-glow));
}
.auth-sub { margin: 0; color: var(--fg-dim); font-size: var(--fs-lg); }
/* Lead paragraph on the invite acceptance card. */
.auth-lead { margin: 0 0 1.2rem; color: var(--fg-dim); font-size: var(--fs-base); line-height: 1.55; text-align: center; }
.auth-lead b { color: var(--fg); font-weight: var(--fw-semibold); }
.auth-card .ok-note { text-align: center; }
.auth-card .ok-note h2 { color: var(--ok); font-size: var(--fs-2xl); margin-bottom: .5rem; }
.auth-sub b { font-weight: var(--fw-semibold); color: var(--fg-dim); }
.auth-sub b span { color: var(--accent); font-weight: var(--fw-regular); }
.auth-card .stack { gap: .85rem; }
.auth-card label { font-size: var(--fs-xs); }
.auth-card input { padding: .62rem .75rem; font-size: var(--fs-base); }
.auth-foot {
  margin: 1.2rem 0 0; padding-top: 1.05rem;
  border-top: 1px solid var(--line);
  text-align: center; color: var(--fg-muted); font-size: var(--fs-md);
}
/* Larger primary CTA for the auth stage. */
button.primary.lg, a.btn.primary.lg { width: 100%; padding: .7rem 1rem; font-size: var(--fs-base); margin-top: .15rem; }
a.btn.primary.lg { display: block; text-align: center; }
.or-sep { text-align: center; margin: 1rem 0; position: relative; }

.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; margin-top: 1.2rem; }
.card.product { display: block; text-decoration: none; color: var(--fg); margin: 0; transition: border-color var(--ease), transform var(--ease), box-shadow var(--ease); }
.card.product:hover { border-color: var(--line-strong); transform: translateY(-2px); box-shadow: var(--shadow-2); color: var(--fg); }
.card.product h2 { color: var(--fg-strong); }

/* ==========================================================================
   Layout helpers
   ========================================================================== */
.row { display: flex; align-items: center; }
.row.between { justify-content: space-between; gap: 1rem; }
.row.gap { gap: .6rem; }
.wrap-row { flex-wrap: wrap; }
.stack { display: flex; flex-direction: column; gap: 1rem; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.span2 { grid-column: 1 / -1; }
.inline { display: inline; margin: 0; }
.center { text-align: center; }

/* Honeypot: kept in the DOM for bots but removed from the visual/AT flow. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* "I'm not a robot" style checkbox row — horizontal, not the stacked label. */
label.check {
  flex-direction: row; align-items: center; gap: .55rem;
  font-size: var(--fs-md); color: var(--fg); font-weight: var(--fw-medium);
  padding: .7rem .8rem; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface-3); cursor: pointer;
}
label.check input { width: 1.05rem; height: 1.05rem; margin: 0; accent-color: var(--accent); }

/* The consent row is a checkbox with a sentence next to it rather than a label:
   it wraps to two lines, so the box aligns to the first line instead of to the
   middle of the paragraph, and the sentence keeps reading weight — this is the
   line someone is agreeing to, not a caption. */
label.check.consent { align-items: flex-start; font-size: var(--fs-sm); font-weight: var(--fw-regular); line-height: 1.5; }
label.check.consent input { margin-top: .12rem; flex: none; }
label.check.consent a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }

/* The documents themselves, offered as things to open before agreeing — not as
   fine print under the button that assumes you did. */
.doclinks { list-style: none; margin: 1rem 0 1.2rem; padding: 0; display: flex; flex-direction: column; gap: .5rem; }
.doclinks a {
  display: flex; align-items: center; justify-content: space-between; gap: .8rem;
  padding: .65rem .8rem; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface-3); color: var(--fg); font-size: var(--fs-md);
  font-weight: var(--fw-medium); text-decoration: none;
  transition: border-color var(--ease), background var(--ease);
}
.doclinks a:hover { border-color: var(--line-hover); background: var(--surface-2); }
.doclink-go { color: var(--fg-muted); }

/* A button that has to read as a link: the way out of a page that is otherwise
   a single choice. */
.linkbtn {
  background: none; border: none; padding: 0; margin: 0;
  color: var(--accent); font: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.linkbtn:hover { color: var(--accent-hover); }

/* ==========================================================================
   Forms
   ========================================================================== */
label { display: flex; flex-direction: column; gap: .35rem; font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--fg-muted); }

input, select, textarea {
  background: var(--surface-3);
  border: 1px solid var(--line-strong);
  color: var(--fg);
  padding: .55rem .7rem;
  border-radius: var(--r-sm);
  font-size: var(--fs-base);
  font-family: inherit;
  transition: border-color var(--ease), box-shadow var(--ease), background var(--ease);
  min-width: 0;
}
input::placeholder, textarea::placeholder { color: var(--fg-muted); opacity: .7; }
input:hover, select:hover, textarea:hover { border-color: var(--line-hover); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--surface-2);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
textarea { resize: vertical; line-height: 1.55; min-height: 4.5rem; }
select { appearance: none; -webkit-appearance: none;
  background-image: var(--select-arrow);
  background-repeat: no-repeat; background-position: right .6rem center; padding-right: 1.8rem; }

/* ==========================================================================
   Buttons
   ========================================================================== */
button, .btn {
  --bh: transparent;
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  background: var(--surface-3);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  padding: .5rem .9rem;
  border-radius: var(--r-sm);
  cursor: pointer;
  font-size: var(--fs-md); font-weight: var(--fw-mid); font-family: inherit;
  white-space: nowrap;
  transition: background var(--ease), border-color var(--ease), color var(--ease), transform var(--ease), box-shadow var(--ease);
}
button:hover { border-color: var(--line-hover); background: var(--surface-2); }
button:active { transform: translateY(1px); }
button:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }

button.primary, a.btn.primary {
  background: linear-gradient(180deg, var(--accent-top), var(--accent));
  border-color: transparent; color: var(--on-accent); font-weight: var(--fw-semibold);
  /* A tinted cast under the button, so the accent colour appears to light the
     surface beneath it instead of floating on top of it. */
  box-shadow: 0 1px 0 rgba(255, 255, 255, .18) inset,
              var(--shadow-1),
              0 8px 20px -10px var(--accent-glow-strong);
}
button.primary:hover, a.btn.primary:hover {
  background: linear-gradient(180deg, var(--accent-top-hover), var(--accent-hover));
  border-color: transparent; color: var(--on-accent);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .22) inset,
              var(--shadow-1),
              0 10px 24px -10px var(--accent-glow-strong);
}
button.primary:active, a.btn.primary:active {
  background: var(--accent-press);
  box-shadow: var(--press-inset);
}

button.danger { color: var(--danger); border-color: var(--danger-line); background: var(--danger-weak); }
button.danger:hover { background: var(--danger-hot); border-color: var(--danger); }

/* Secondary actions that sit beside a primary one. Borderless until hovered,
   so a row of choices reads as one action plus alternatives — not three
   competing buttons. */
button.quiet { background: none; border-color: transparent; color: var(--fg-muted); }
button.quiet:hover { background: var(--surface-3); border-color: var(--line-strong); color: var(--fg); }
button.quiet.danger { background: none; border-color: transparent; color: var(--fg-muted); }
button.quiet.danger:hover { background: var(--danger-weak); border-color: var(--danger-line); color: var(--danger); }

button.link { background: none; border: none; color: var(--accent); padding: .2rem .1rem; font-weight: var(--fw-mid); }
button.link:hover { color: var(--accent-hover); background: none; text-decoration: underline; }

/* Compact action buttons inside tables */
.list button { padding: .3rem .6rem; font-size: var(--fs-xs); }
.list td form { margin: 0; }

/* ==========================================================================
   Tables
   ========================================================================== */
.list {
  width: 100%; border-collapse: collapse; margin-top: .3rem; font-size: var(--fs-md);
  /* Aligned digits down a column: dates, sizes and counts stop dancing. */
  font-variant-numeric: tabular-nums;
}
.list th, .list td { text-align: left; padding: .68rem .7rem; border-bottom: 1px solid var(--line); vertical-align: middle; }
.list thead th {
  color: var(--fg-muted); font-weight: var(--fw-bold); font-size: var(--fs-micro);
  text-transform: uppercase; letter-spacing: .075em;
  padding-bottom: .5rem;
  border-bottom-color: var(--line-strong);
}
.list tbody tr { transition: background var(--ease); }
.list tbody tr:hover { background: var(--row-hover); }
.list tbody tr:last-child td { border-bottom: none; }
.list td code { font-size: .82em; }

/* ==========================================================================
   Badges
   ========================================================================== */
.badge {
  display: inline-flex; align-items: center;
  font-size: var(--fs-micro); font-weight: var(--fw-semibold); letter-spacing: .012em;
  font-variant-numeric: tabular-nums;
  padding: .2rem .55rem; border-radius: var(--r-pill);
  border: 1px solid var(--line-strong); color: var(--fg-dim);
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  box-shadow: var(--sheen);
  vertical-align: middle; line-height: 1.4; white-space: nowrap;
}
.badge.public, .badge.ok { color: var(--ok); border-color: var(--ok-line); background: var(--ok-weak); }
.badge.protected, .badge.warn { color: var(--warn); border-color: var(--warn-line); background: var(--warn-weak); }
.badge.danger { color: var(--danger); border-color: var(--danger-line); background: var(--danger-weak); }
.badge.plat { font-family: var(--mono, monospace); font-weight: var(--fw-medium); text-transform: none; }
.badge.plat.muted { color: var(--fg-dim); }
.changelog { margin-top: 1.2rem; border-top: 1px solid var(--line); padding-top: .8rem; }
.changelog summary { cursor: pointer; color: var(--fg-muted); font-weight: var(--fw-mid); font-size: var(--fs-md); list-style: none; }
.changelog summary::-webkit-details-marker { display: none; }
.changelog summary::before { content: "▸ "; color: var(--accent); }
.changelog[open] summary::before { content: "▾ "; }
.changelog .release { background: transparent; border: none; border-bottom: 1px solid var(--line); border-radius: 0; padding: .8rem 0; margin: 0; }

/* ==========================================================================
   Access requests
   Each pending request is a triage unit, not a table row: the identity reads
   first, then the note, then the three actions. A table forced the approve
   form into a cell and made the note unreadable.
   ========================================================================== */
.reqlist { list-style: none; margin: .9rem 0 0; padding: 0; display: flex; flex-direction: column; gap: .7rem; }
.req {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: .85rem .95rem;
}
.req.empty { color: var(--fg-muted); font-size: var(--fs-md); background: transparent; border-style: dashed; }

/* Arriving from the "Review this request" link in a notification email: the row
   the admin was sent to identifies itself, then settles. The ring is permanent
   (it survives the fade) so the answer to "which one?" doesn't expire. */
.req:target {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
  animation: req-arrive 1.6s ease-out;
}
@keyframes req-arrive {
  0%, 40% { background: var(--accent-weak); }
  100%    { background: var(--surface); }
}
@media (prefers-reduced-motion: reduce) {
  .req:target { animation: none; }
}

.req-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; min-width: 0; }
.req-email { font-size: var(--fs-base); color: var(--fg); overflow-wrap: anywhere; }
.req-when { font-size: var(--fs-xs); }

.req-note {
  margin: .55rem 0 0;
  font-size: var(--fs-md); line-height: 1.5; color: var(--fg-dim);
  padding-left: .7rem; border-left: 2px solid var(--line-strong);
  overflow-wrap: anywhere;
}

/* History on a repeat request: the reason this row is worth a second look, so
   it sits above the note rather than in the fine print below it. */
.req-prior {
  margin: .5rem 0 0;
  font-size: var(--fs-sm); line-height: 1.5; color: var(--fg-dim);
}

/* Already-decided rows are a record, not work: no accent, no pressure, and they
   recede until you go looking. */
.req.handled { background: transparent; border-style: dashed; }
.req.handled .req-head { align-items: center; }
.req.handled .req-note { margin-top: .5rem; font-size: var(--fs-sm); }
.reqlist.handled { gap: .45rem; }

/* One primary action on the left, the two quiet ones pushed to the right. */
.req-actions { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-top: .8rem; }
.req-approve { display: flex; align-items: center; gap: .5rem; margin: 0; }
.req-approve select { min-width: 11rem; }
.req-actions .push { margin-left: auto; }
.req-actions button { padding: .4rem .75rem; font-size: var(--fs-sm); }

/* A destructive link in a table row: quiet until hovered, so a list of users
   doesn't read as a row of red buttons. */
.dangerlink { color: var(--fg-muted); font-size: var(--fs-sm); font-weight: var(--fw-medium); }
.dangerlink:hover { color: var(--danger); }
td.right, th.right { text-align: right; }

/* ==========================================================================
   Consumer portal
   Two pages with one job each: a catalog of what you can download, and a
   once-per-machine setup flow. The type scale below is the whole point —
   before it, every role on the page shared one grey 0.8rem style, so nothing
   was scannable. Now: lede → body → hint, in that order of weight.
   ========================================================================== */
.pagehead { margin-bottom: .2rem; }
.pagehead h1 {
  font-size: var(--fs-d2); font-weight: var(--fw-black); letter-spacing: -.032em;
  /* A whisper of brand light through the wordmark-scale type. */
  background: linear-gradient(180deg, var(--title-from), var(--title-to));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--fg);
}

/* Body copy inside a card — the explanation a step genuinely needs. Scoped to
   the portal so the admin UI keeps its own denser rhythm. */
.portal .card > p:not(.hint) {
  margin: .45rem 0 0; max-width: 72ch;
  color: var(--fg-dim); font-size: var(--fs-base); line-height: 1.6;
}
/* Everything secondary: caveats, alternatives, footnotes. One style, so a
   reader learns to skim it — and so it can never be mistaken for a step. */
.hint { margin: .55rem 0 0; max-width: 74ch; color: var(--fg-muted); font-size: var(--fs-xs); line-height: 1.55; }
.hint code { font-size: .82em; }


/* --- Downloads index ------------------------------------------------------
   The shelf. One plaque per product, full width, because two products on a
   960px page are a shelf — not a grid, and certainly not a hairline list
   stranded at the top of an empty screen.

   Each plaque is identifiable before it is read. Its hue is derived from the
   slug in Go and set once on the anchor as --tint-rgb, from which everything
   else is mixed: the tile, the wash that lights the card from its top-left
   corner, the hairline along the top edge, the version chip, the fill the call
   to action takes on approach. One light source, one variable — a product is
   the same colour on every visit and from every account.

   Then the facts a choice turns on, in three registers: the name and tagline
   (what it is), the release column (what is current, how recently it moved),
   and the plinth along the bottom (what is in the box, and the way in).

   Shown only when the account holds more than one entitlement; with one, the
   product page is the landing page. */
.shelfhead { margin-bottom: 1.5rem; }
.shelfhead h1 { font-size: var(--fs-d3); letter-spacing: -.034em; }
/* The count is a statement about the account, not a footnote to the title:
   body-scale and dim, where .hint would have whispered it. */
.lede { margin: .45rem 0 0; color: var(--fg-dim); font-size: var(--fs-lg); line-height: 1.5; }

.shelf { display: flex; flex-direction: column; gap: 1rem; }

.pcard {
  /* Default tint, overridden per data-tint below. Held as an unparenthesized
     triple so every derived colour can pick its own alpha. */
  --tint-rgb: 91 140 255;
  --tint-a: #6f9bff;
  --tint-b: #3b64e0;

  position: relative; overflow: hidden; isolation: isolate;
  display: grid; align-items: start;
  grid-template-columns: auto minmax(0, 1fr) auto;
  /* Explicit areas, not auto-placement: an item with a definite row and an auto
     column is placed before the fully-automatic ones, which silently swapped
     the release column and the name. Named areas cannot drift. */
  grid-template-areas:
    "mark head rel"
    "rail rail rail";
  column-gap: 1.15rem;
  padding: 1.45rem 1.5rem;
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--sheen);
  color: var(--fg); text-decoration: none;
  transition: transform var(--ease-out), border-color var(--ease), box-shadow var(--ease-out);
}
.pcard[data-tint="0"] { --tint-rgb: 111 155 255; --tint-a: #6f9bff; --tint-b: #3b64e0; }
.pcard[data-tint="1"] { --tint-rgb: 127 124 245; --tint-a: #8f8cf8; --tint-b: #5142d6; }
.pcard[data-tint="2"] { --tint-rgb:  79 182 216; --tint-a: #5cc0e0; --tint-b: #2b7fb4; }
.pcard[data-tint="3"] { --tint-rgb:  79 191 165; --tint-a: #58c8ad; --tint-b: #268b7d; }
.pcard[data-tint="4"] { --tint-rgb: 155 123 234; --tint-a: #a68df0; --tint-b: #6d47c4; }
.pcard[data-tint="5"] { --tint-rgb:  90 168 242; --tint-a: #63aef5; --tint-b: #2f6fcf; }

/* The wash: the product's own light, cast from behind its tile across the top
   of the card. Present at rest — this is the card's identity, not a hover
   trick — and it opens up on approach. */
.pcard-wash {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(58% 130% at 4% -18%, rgb(var(--tint-rgb) / .17), transparent 64%),
    radial-gradient(40% 100% at 98% 0%, rgb(var(--tint-rgb) / .06), transparent 70%);
  opacity: .85; transition: opacity var(--ease-out);
}
/* A hairline of that same light along the very top edge — the device the
   sign-in card uses, at plaque scale. */
.pcard::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgb(var(--tint-rgb) / .75), transparent);
  opacity: .5; transition: opacity var(--ease-out);
}
.pcard:hover {
  transform: translateY(-3px);
  border-color: rgb(var(--tint-rgb) / .42);
  box-shadow: var(--shadow-lift), var(--sheen), 0 24px 60px -34px rgb(var(--tint-rgb) / .7);
  color: var(--fg);
}
.pcard:hover .pcard-wash { opacity: 1; }
.pcard:hover::before { opacity: 1; }
.pcard:focus-visible { outline: none; border-color: transparent; box-shadow: 0 0 0 3px var(--accent-ring), var(--shadow-card); }

/* The tile. Six accent-adjacent hues — a family around the brand blue rather
   than a paintbox — under a specular highlight, so it reads as a lit object at
   the scale an app icon is read at. */
.pcard-mark {
  grid-area: mark;
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  width: 3.4rem; height: 3.4rem;
  border-radius: 15px;
  background: linear-gradient(155deg, var(--tint-a), var(--tint-b));
  color: #fff; font-size: var(--fs-d1); font-weight: var(--fw-black);
  letter-spacing: -.03em; line-height: 1;
  text-shadow: 0 1px 2px rgb(var(--tint-rgb) / .5);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .3) inset,
              0 0 0 1px rgb(var(--tint-rgb) / .35),
              0 10px 22px -10px rgb(var(--tint-rgb) / .75);
  transition: transform var(--ease-out), box-shadow var(--ease-out);
}
.pcard-mark::after {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(115% 80% at 28% -12%, rgba(255, 255, 255, .42), transparent 62%);
}
.pcard:hover .pcard-mark {
  transform: translateY(-1px) scale(1.03) rotate(-2deg);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .3) inset,
              0 0 0 1px rgb(var(--tint-rgb) / .45),
              0 16px 30px -12px rgb(var(--tint-rgb) / .85);
}

.pcard-head { grid-area: head; min-width: 0; padding-top: .1rem; }
.pcard-name {
  display: block;
  font-size: var(--fs-2xl); font-weight: var(--fw-bold);
  letter-spacing: -.022em; line-height: 1.2;
}
/* Two lines, then clipped: the full text is one hover — or one click — away,
   and a card that grows to fit a tagline stops lining up with its neighbour. */
.pcard-tag {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden;
  margin-top: .34rem; max-width: 54ch;
  font-size: var(--fs-sm); line-height: 1.5; color: var(--fg-muted);
}

/* The release column: the version answered at chip scale, the age under it, so
   a stack of plaques reads its versions down one edge. */
.pcard-rel {
  grid-area: rel;
  display: flex; flex-direction: column; align-items: flex-end; gap: .42rem;
  padding-top: .15rem; white-space: nowrap;
}
.pcard-ver {
  font-family: var(--mono); font-size: var(--fs-md); font-weight: var(--fw-mid);
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
  color: var(--fg);
  padding: .3rem .6rem; border-radius: var(--r-sm);
  background: rgb(var(--tint-rgb) / .13);
  border: 1px solid rgb(var(--tint-rgb) / .3);
  box-shadow: var(--sheen);
}
/* No single current version to name — a package count instead, which is a fact
   about the product rather than a version, and is not dressed as one. */
.pcard-ver.quiet {
  font-family: inherit; font-size: var(--fs-sm); color: var(--fg-dim);
  background: var(--surface-3); border-color: var(--line-strong);
}
.pcard-age {
  display: inline-flex; align-items: center; gap: .38rem;
  font-size: var(--fs-xs); color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
/* Shipped inside the week. A date is a fact; this week is news, and news gets
   a light on it. */
.pcard-age.fresh { color: var(--ok); font-weight: var(--fw-mid); }
.pulse {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--ok); box-shadow: 0 0 0 0 var(--ok-line);
  animation: pulse 2.4s ease-out infinite;
}
@keyframes pulse {
  0%       { box-shadow: 0 0 0 0 var(--ok-line); }
  70%,100% { box-shadow: 0 0 0 6px rgba(0, 0, 0, 0); }
}

/* The plinth. Cut into the foot of the plaque — full bleed, recessed, closed by
   a hairline — so "what is in the box" and "the way in" sit together on their
   own shelf instead of trailing the tagline. */
.pcard-rail {
  grid-area: rail;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin: 1.25rem -1.5rem -1.45rem; padding: .8rem 1.5rem;
  border-top: 1px solid var(--line);
  background: var(--well-bg);
}
.pcard-facts {
  display: flex; align-items: center; flex-wrap: wrap;
  min-width: 0;
  font-size: var(--fs-xs); color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
.fact { white-space: nowrap; }
.fact + .fact::before { content: "·"; margin: 0 .5rem; opacity: .55; }

/* Quiet at rest, filled with the product's own colour on approach: the card is
   the button, and this is where the click lands. */
.pcard-cta {
  display: inline-flex; align-items: center; gap: .3rem;
  margin-left: auto; padding: .34rem .55rem .34rem .75rem;
  border-radius: var(--r-pill);
  font-size: var(--fs-sm); font-weight: var(--fw-semibold); letter-spacing: -.006em;
  color: var(--fg-dim); background: transparent;
  border: 1px solid transparent;
  transition: color var(--ease), background var(--ease-out), border-color var(--ease), box-shadow var(--ease-out);
}
.pcard-cta svg { transition: transform var(--ease-out); }
.pcard:hover .pcard-cta {
  color: #fff;
  background: linear-gradient(180deg, var(--tint-a), var(--tint-b));
  border-color: rgb(var(--tint-rgb) / .5);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .25) inset,
              0 8px 18px -8px rgb(var(--tint-rgb) / .8);
}
.pcard:hover .pcard-cta svg { transform: translateX(2px); }

/* Arrival: the shelf assembles rather than appearing. Six steps is more
   products than any account has, and the last one still lands inside 300ms. */
.shelf .pcard {
  animation: shelf-in 420ms cubic-bezier(.16, .84, .28, 1) both;
}
.shelf .pcard:nth-child(2) { animation-delay: 55ms; }
.shelf .pcard:nth-child(3) { animation-delay: 110ms; }
.shelf .pcard:nth-child(4) { animation-delay: 165ms; }
.shelf .pcard:nth-child(5) { animation-delay: 220ms; }
.shelf .pcard:nth-child(n+6) { animation-delay: 275ms; }
@keyframes shelf-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .shelf .pcard { animation: none; }
  .pulse { animation: none; }
  .pcard:hover { transform: none; }
  .pcard:hover .pcard-mark { transform: none; }
}

/* --- Next step -------------------------------------------------------------
   One row at the foot of the shelf, after the products rather than over them:
   downloading by hand is the fallback, and a machine that is set up upgrades
   itself. Quieter than a card so it never competes with the catalog. */
.nextstep {
  display: flex; align-items: center; gap: .95rem;
  margin-top: 1.5rem; padding: 1rem 1.15rem;
  background: var(--accent-wash);
  border: 1px solid var(--line);
  border-radius: var(--r);
  color: var(--fg); text-decoration: none;
  transition: border-color var(--ease), background var(--ease), transform var(--ease);
}
.nextstep:hover {
  border-color: var(--accent-ring); background: var(--accent-weak);
  color: var(--fg); transform: translateY(-1px);
}
.nextstep:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
.nextstep-icon {
  display: flex; align-items: center; justify-content: center;
  width: 2.4rem; height: 2.4rem; flex: none;
  border-radius: var(--r-sm);
  background: var(--surface-3); border: 1px solid var(--line);
  color: var(--accent);
}
.nextstep-body { min-width: 0; }
.nextstep-body strong { display: block; font-size: var(--fs-md); font-weight: var(--fw-semibold); }
.nextstep-body .hint { margin-top: .12rem; }
.nextstep-go {
  margin-left: auto; color: var(--fg-muted);
  font-size: 1.3em; font-weight: var(--fw-black); line-height: 1;
  transition: transform var(--ease), color var(--ease);
}
.nextstep:hover .nextstep-go { color: var(--accent); transform: translateX(2px); }

/* The way back to the index, above the product title. Quiet enough that it is
   never mistaken for the page's first action. */
.backlink {
  display: inline-flex; align-items: center; gap: .3rem;
  margin-bottom: 1.1rem;
  font-size: var(--fs-sm); font-weight: var(--fw-mid); color: var(--fg-muted);
}
.backlink:hover { color: var(--fg-dim); }
.backlink span { font-size: 1.15em; line-height: 1; }

/* --- Quiet disclosure -----------------------------------------------------
   The shared shape for everything on a product page that is reference rather
   than action: requirements, release notes, the long description, the pinned
   install. A rule and a line of text; the prose only exists once someone has
   said the question is theirs. One component, so three sections that are read
   the same way cannot drift into three appearances. */
details.quiet { border-top: 1px solid var(--line); }
details.quiet > summary {
  display: flex; align-items: center; gap: .5rem;
  list-style: none; cursor: pointer;
  padding: .9rem 0;
  font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--fg-dim);
  transition: color var(--ease);
}
details.quiet > summary::-webkit-details-marker { display: none; }
details.quiet > summary::before {
  content: "›"; color: var(--fg-muted);
  font-size: 1.1em; font-weight: var(--fw-black); line-height: 1;
  transition: transform var(--ease), color var(--ease);
}
details.quiet > summary:hover { color: var(--fg); }
details.quiet > summary:hover::before, details.quiet[open] > summary::before { color: var(--accent); }
details.quiet[open] > summary::before { transform: rotate(90deg); }
details.quiet > summary:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); border-radius: 5px; }
.quiet-body { padding: .15rem 0 1.5rem; }
.quiet-body .prose { max-width: 72ch; font-size: var(--fs-base); }
/* Nested inside a section that already owns its heading — the pinned install
   under Install — so it carries no rule of its own and sits at the weight of a
   footnote rather than a section. */
details.quiet.tight { border-top: none; }
details.quiet.tight > summary { padding: .5rem 0; font-size: var(--fs-sm); font-weight: var(--fw-mid); }
details.quiet.tight .quiet-body { padding-bottom: .35rem; }

/* --- Product page ---------------------------------------------------------
   The product is the page. No card wraps it: a customer with one entitlement
   should not be reading a list of one, and the heading they came for should be
   the largest thing on screen — not the word "Software". */
.product { padding: .5rem 0 2.5rem; }
.product + .product { margin-top: 2.5rem; padding-top: 2.75rem; border-top: 1px solid var(--line); }
.product-head { margin-bottom: 1.5rem; }
.product-head h1 {
  font-size: var(--fs-d3); font-weight: var(--fw-black); letter-spacing: -.035em;
  background: linear-gradient(180deg, var(--title-from), var(--title-to));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: var(--fg);
}
.product-part { font-weight: var(--fw-medium); color: var(--fg-muted); -webkit-text-fill-color: var(--fg-muted); }
/* One line under the title, ahead of the version: what the product is, before
   which build of it you have. Measured so it stays a subtitle rather than
   becoming the first paragraph of the page. */
.product-tagline {
  margin: .55rem 0 0; max-width: 62ch;
  font-size: var(--fs-lg); line-height: 1.5; color: var(--fg-dim);
}
.product-sub {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  margin: .6rem 0 0; font-size: var(--fs-base); color: var(--fg-dim);
}

/* The long description, below everything actionable and closed. It reads as an
   appendix to the project rather than another step. */
.about { margin-top: 2.2rem; }
/* The version is the identity of the download; it reads as a value, not a tag. */
.ver { font-family: var(--mono); font-size: var(--fs-base); font-weight: var(--fw-mid); color: var(--fg); }

/* --- Install, the primary action -----------------------------------------
   Ops engineers install from the repository; the package file is the
   exception. The section that carries the install command leads. */
.install { margin-top: 1.75rem; }
.install h2, .files-section h2, .notes h2 { font-size: var(--fs-xl); margin-bottom: .55rem; }
.install .pm { margin-top: .7rem; }
.cta-row { margin: .95rem 0 0; }
.cta-row .btn { padding: .55rem 1.1rem; font-size: var(--fs-base); }

.files-section { margin-top: 2.25rem; }
.get-os {
  margin: 1rem 0 .1rem; font-size: var(--fs-tiny); font-weight: var(--fw-bold);
  letter-spacing: .07em; text-transform: uppercase; color: var(--fg-muted);
}
.verify { margin: .8rem 0 0; font-size: var(--fs-xs); color: var(--fg-muted); }
.verify a { color: var(--fg-dim); text-decoration: underline; text-underline-offset: 2px; }
.verify a:hover { color: var(--accent-hover); }

/* --- Release notes, the content the portal used to omit entirely ----------
   Named with their version so the line says which build it is describing; the
   reason to take an upgrade, and of no use at all to a first install. */
.notes { margin-top: 1.9rem; }

/* What the host has to provide, ahead of the command that puts software on it:
   the question is asked before the command runs, not after. Closed, so a
   reader who already provisioned the host does not scroll a spec table to
   reach the one line they came for. */
.requirements { margin: 1.5rem 0 1.9rem; }
.small-prose { font-size: var(--fs-md); margin-top: .5rem; }

/* The package-manager route, offered beside the files it installs. One row per
   distribution: "apt" versus "dnf" is not the question a reader is answering,
   "Debian or Fedora" is — so the distribution is the label and the command is
   the value. */
.pm {
  margin-top: .95rem; padding: .35rem .75rem .5rem;
  background: var(--well-bg);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  box-shadow: var(--well-inset);
}
.pm-row {
  display: grid; align-items: center; gap: .75rem;
  grid-template-columns: 8.5rem minmax(0, 1fr) auto;
  padding: .4rem 0;
}
.pm-row + .pm-row { border-top: 1px solid var(--line); }
.pm-os { font-size: var(--fs-mini); font-weight: var(--fw-semibold); color: var(--fg-muted); }
.pm-cmd {
  background: none; border: none; padding: 0;
  color: var(--fg); font-size: var(--fs-sm); overflow-wrap: anywhere;
}

/* --- File list ------------------------------------------------------------
   A grid, not a table: four columns that stay aligned without a header row to
   explain them. Platform leads, because that is what a reader is matching
   against; the filename and size follow; the action closes the row. */
.filelist { list-style: none; margin: .95rem 0 0; padding: 0; }
.file {
  display: grid; align-items: center; gap: .5rem .9rem;
  grid-template-columns: 8.5rem minmax(0, 1fr) 5rem auto;
  padding: .5rem 0;
}
.file + .file { border-top: 1px solid var(--line); }
.file-plat { min-width: 0; }
.file-name { color: var(--fg-dim); overflow-wrap: anywhere; font-size: var(--fs-sm); }
.file-size { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.file-act { display: flex; align-items: center; gap: .4rem; justify-content: flex-end; }

/* Release version + channel, beside a heading and in the history list. */
.badge.ver { font-family: var(--mono); font-weight: var(--fw-mid); }
.badge.chan { color: var(--fg-muted); background: none; border-color: var(--line); box-shadow: none; }
/* A channel badge on the admin release row carries its own removal control. The
   × stays dim until the badge is hovered, so a row of channels reads as labels
   first and only offers the action when it is being looked at. */
.badge.chan:has(.chan-x) { padding-right: .28rem; gap: .18rem; }
.chan-x {
  all: unset; cursor: pointer; line-height: 1;
  padding: 0 .22rem; border-radius: var(--r-pill);
  font-size: var(--fs-xs); color: var(--fg-dim); opacity: .5;
  transition: opacity .12s ease, color .12s ease, background .12s ease;
}
.badge.chan:hover .chan-x { opacity: 1; }
.chan-x:hover { color: var(--danger); background: var(--danger-weak); opacity: 1; }
.chan-x:focus-visible { outline: 2px solid var(--focus, var(--danger)); outline-offset: 1px; opacity: 1; }

.rel { padding: .9rem 0; }
.rel + .rel { border-top: 1px solid var(--line); }
.rel-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }

/* --- Release row ----------------------------------------------------------
   On the admin page and in the portal's version history alike, a release is a
   single line that opens. The marker leads so
   every version starts at the same x whatever its channels; the size summary is
   pushed right, which puts a scannable column of "what is in here" against the
   list's right edge. The row highlight bleeds past the content box so a hovered
   line reads as one target rather than a band of text. */
details.rel { padding: 0; }
details.rel > summary {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  list-style: none; cursor: pointer;
  margin: 0 -.7rem; padding: .72rem .7rem;
  border-radius: var(--r-sm);
  transition: background var(--ease);
}
details.rel > summary::-webkit-details-marker { display: none; }
details.rel > summary::before {
  content: "›"; color: var(--fg-muted);
  font-size: 1.1em; font-weight: var(--fw-black); line-height: 1;
  transition: transform var(--ease), color var(--ease);
}
details.rel > summary:hover { background: var(--surface-2); }
details.rel > summary:hover::before, details.rel[open] > summary::before { color: var(--accent); }
details.rel[open] > summary::before { transform: rotate(90deg); }
details.rel > summary:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
/* The right-hand summary: pushed away from the badges, and the first thing to
   drop to its own line when the row runs out of width. */
.rel-sum { margin-left: auto; font-variant-numeric: tabular-nums; }
.rel-body { padding: .1rem 0 1.15rem; }
.rel-body .filelist { margin-top: .5rem; }
/* Controls live inside the disclosure: channels on the left, state on the
   right, so Delete is never a stray click away from a row being read. */
.rel-body .rel-act {
  margin-top: .9rem; padding-top: .8rem;
  border-top: 1px solid var(--line);
  gap: .35rem;
}
.rel-chans { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-right: auto; }
/* A withdrawn release is still downloadable, so it must be legible — dimmed
   enough to read as superseded, not so dim it looks disabled. */
.rel.withdrawn { opacity: .78; }
.rel.withdrawn .file-name { text-decoration: line-through; text-decoration-color: var(--line-strong); }

/* --- Release list (portal) -------------------------------------------------
   Every version the account may download, newest first, one line each. The
   current release is the first row and is marked, not shaped differently: a
   reader taking the newest build and a reader going back three versions are
   reading the same list, and only one of them is served by making the top row
   a special case. */
.rel-list { margin-top: .95rem; }
.rel-list > details.rel + details.rel { border-top: 1px solid var(--line); }
.rel-current > summary .ver { color: var(--fg); }
.badge.cur {
  color: var(--accent); border-color: var(--accent-ring);
  background: var(--accent-weak); box-shadow: none;
}

/* Divider that fades at both ends, so a stack of packages reads as one list
   rather than a set of boxed rows. */
.pkg {
  padding-top: 1.25rem; margin-top: 1.25rem;
  border-top: 1px solid transparent;
  border-image: linear-gradient(90deg, transparent, var(--line) 12%, var(--line) 88%, transparent) 1;
}
.pkg:first-of-type { border-top: none; border-image: none; padding-top: .55rem; margin-top: .75rem; }

a.dl.mini { padding: .27rem .62rem; font-size: var(--fs-mini); font-weight: var(--fw-semibold); gap: .35rem; }
a.dl.mini::before { font-size: .95em; }

.card.empty {
  text-align: center; padding: 3rem 1.6rem;
  border-style: dashed; border-color: var(--line-strong);
  background: none; box-shadow: none;
}
.card.empty h2 { margin-bottom: .4rem; font-size: var(--fs-2xl); }
.card.empty p { margin-left: auto; margin-right: auto; }

/* ==========================================================================
   Admin project page
   The page is opened to see what is published. Everything else it can do —
   settings, credentials, the repository explanation, the audit log — is read
   once and then only when something is wrong, so it sits in the drawer stack
   at the bottom instead of eight cards competing at one weight.
   ========================================================================== */
.projhead { margin-bottom: 1.7rem; }
.projmeta {
  display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
  margin: .6rem 0 0; font-size: var(--fs-sm); color: var(--fg-muted);
}
.projmeta > code { font-size: var(--fs-sm); }
.projmeta > a { color: var(--fg-dim); font-size: var(--fs-xs); font-weight: var(--fw-medium); }
.projmeta > a:hover { color: var(--accent-hover); }

.sec { margin-top: 2rem; }
.sec-head { display: flex; align-items: baseline; gap: .8rem; flex-wrap: wrap; }
.sec-head h2 { font-size: var(--fs-2xl); font-weight: var(--fw-bold); }
.sec-head .hint { margin: 0; }

.pkg-head { display: flex; align-items: baseline; gap: .55rem; margin-bottom: -.2rem; }
.pkg-head h3 { font-size: var(--fs-lg); font-weight: var(--fw-semibold); }

/* Written once, read by customers many times — so it sits under the package
   name as one quiet line, not as a panel competing with the releases below it.
   The "set / not set" note carries the only fact worth seeing while collapsed. */
.pkg-notes { margin: .45rem 0 .2rem; }
.pkg-notes > summary .note { margin-left: .5rem; font-size: var(--fs-mini); font-weight: var(--fw-medium); color: var(--fg-muted); }
.pkg-notes form { margin: .7rem 0 .9rem; max-width: 62rem; }
.pkg-notes textarea { font-family: var(--mono, monospace); font-size: var(--fs-sm); line-height: 1.55; }

/* Release controls: three destructive-ish actions per row, times every release
   on the page. Borderless until hovered, so a release reads as a fact with
   actions available — not as a row of buttons with a version attached. */
.rel-act { display: flex; align-items: center; gap: .2rem; flex-wrap: wrap; margin-left: auto; }
.rel-act button { padding: .3rem .62rem; font-size: var(--fs-xs); }
.rel-move { display: flex; align-items: center; gap: .3rem; }
.rel-move select {
  padding: .27rem 1.55rem .27rem .55rem; font-size: var(--fs-xs);
  background-position: right .45rem center;
}
/* A blocked release is refused at download; the rail says so without tinting
   the whole row and drowning out the files underneath. */
.rel.blocked { border-left: 2px solid var(--danger); padding-left: .75rem; }
a.file-name { text-decoration: none; transition: color var(--ease); }
a.file-name:hover { color: var(--accent-hover); text-decoration: underline; text-underline-offset: 2px; }

.handupload { margin-top: .9rem; }
.handupload input[type=file] { padding: .42rem .5rem; font-size: var(--fs-sm); }

/* --- Drawers ---------------------------------------------------------------
   One grouped list, hairline-separated: the title on the left, the one fact
   that decides whether to open it on the right. Closed, the whole stack costs
   five lines; open, each is a full panel. */
.drawers { margin-top: 3rem; border-top: 1px solid var(--line); }
.drawer { border-bottom: 1px solid var(--line); }
.drawer > summary {
  display: flex; align-items: center; gap: .7rem;
  list-style: none; cursor: pointer;
  padding: .95rem .3rem;
  font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--fg-dim);
  transition: color var(--ease);
}
.drawer > summary::-webkit-details-marker { display: none; }
.drawer > summary .note {
  margin-left: auto; font-size: var(--fs-mini); font-weight: var(--fw-medium); color: var(--fg-muted);
}
.drawer > summary::after {
  content: "›"; color: var(--fg-muted);
  font-size: 1.15em; font-weight: var(--fw-black); line-height: 1;
  transition: transform var(--ease), color var(--ease);
}
.drawer > summary:hover, .drawer[open] > summary { color: var(--fg); }
.drawer > summary:hover::after, .drawer[open] > summary::after { color: var(--accent); }
.drawer[open] > summary::after { transform: rotate(90deg); }
.drawer > summary:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); border-radius: var(--r-sm); }
.drawer-body { padding: .1rem .3rem 1.9rem; }
.drawer-body > :first-child { margin-top: 0; }
.drawer-body .list { margin-top: 1.1rem; }
.drawer-body h3.sub { margin: 2rem 0 .1rem; font-size: var(--fs-base); font-weight: var(--fw-semibold); }
.drawer-body .hint:last-child { margin-top: 1rem; }

/* --- Progressive disclosure ------------------------------------------------
   The manual path, the API details, the uninstall commands: real content, but
   not what the page is for. Collapsed by default, one click deep. */
.more { margin-top: 1rem; }
.teardown { margin-top: 2.4rem; padding-top: 1.3rem; border-top: 1px solid var(--line); }
.more > summary, .teardown > summary {
  display: inline-flex; align-items: center; gap: .45rem;
  cursor: pointer; list-style: none; padding: .2rem 0;
  font-size: var(--fs-sm); font-weight: var(--fw-mid); color: var(--fg-muted);
  transition: color var(--ease);
}
.more > summary::-webkit-details-marker, .teardown > summary::-webkit-details-marker { display: none; }
.more > summary::before, .teardown > summary::before {
  content: "›"; color: var(--accent); font-weight: var(--fw-black); font-size: 1.05em;
  display: inline-block; transition: transform var(--ease);
}
.more[open] > summary::before, .teardown[open] > summary::before { transform: rotate(90deg); }
.more > summary:hover, .teardown > summary:hover { color: var(--fg); }
.more > summary:focus-visible, .teardown > summary:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); border-radius: 4px; }

/* --- Platform tabs ---------------------------------------------------------
   One platform's instructions at a time. Stacking apt, dnf and curl made the
   reader do the filtering the page should have done for them. */

/* --- Setup: token stage ----------------------------------------------------- */
/* The first-run state. Nothing else on the page can happen before this, so it
   carries brand light and the page's only large heading. */
.card.cta {
  overflow: hidden;
  border-color: var(--line-strong);
  background:
    radial-gradient(130% 110% at 0% 0%, var(--accent-weak), transparent 58%),
    linear-gradient(180deg, var(--surface-2), var(--surface));
  box-shadow: var(--shadow-lift), var(--sheen);
}
/* Brand light along the top edge — the same device as the sign-in card, so the
   two most important moments in the product are lit the same way. */
.card.cta::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent-ring), transparent);
}
.card.cta h3 { font-size: var(--fs-2xl); }
.card.cta .tokenform { margin-top: 1.15rem; }


.tokenform { margin-top: .95rem; }
.tokenform input { min-width: 17rem; flex: 0 1 22rem; }


/* ==========================================================================
   Restored: command blocks, small buttons, and the shared text/prose utilities
   the admin and marketing pages depend on.
   ========================================================================== */
h2.section {
  display: flex; align-items: center;
  margin: 2.6rem 0 -.2rem;
  font-size: var(--fs-2xl); font-weight: var(--fw-bold); color: var(--fg);
}
h2.section:first-of-type { margin-top: 1.9rem; }
.muted { color: var(--fg-muted); }
.small { font-size: var(--fs-xs); }
.mono { font-family: var(--mono); font-size: .82em; }
.cmd {
  position: relative;
  margin: .6rem 0 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  /* Recessed: darker than the card it sits in, so a command reads as a well cut
     into the surface rather than another panel stacked on top of it. */
  background: linear-gradient(180deg, var(--cmd-from), var(--cmd-to));
  box-shadow: var(--cmd-inset);
  overflow: hidden;
  transition: border-color var(--ease);
}
.cmd:hover { border-color: var(--line-strong); }
.cmd-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1.25rem;
  padding: .4rem .45rem .4rem .95rem;
  background: var(--code-head-bg);
  border-bottom: 1px solid var(--code-line);
}
.cmd-file, .cmd-label {
  font-size: var(--fs-tiny); letter-spacing: .01em;
  color: var(--fg-muted); overflow-wrap: anywhere;
}
.cmd-file { font-family: var(--mono); }
.cmd-label { font-weight: var(--fw-mid); letter-spacing: .02em; }
.cmd-file code { background: none; border: none; padding: 0; font-size: 1em; color: var(--fg-dim); }
.cmd pre { border: none; border-radius: 0; background: none; margin: 0; box-shadow: none; }
.cmd > .copy { position: absolute; top: .5rem; right: .5rem; opacity: .6; }
.cmd:hover > .copy { opacity: 1; }
.cmd > pre { padding-right: 5.5rem; }
.cmd-head + pre { padding-right: 1.05rem; }
.error { color: var(--danger); font-size: var(--fs-md); }
.back { color: var(--fg-muted); font-size: var(--fs-md); font-weight: var(--fw-medium); }
.back:hover { color: var(--fg); }
.break { word-break: break-all; }
.token { display: block; word-break: break-all; user-select: all; background: var(--code-bg); border: 1px solid var(--line-strong); padding: .7rem .8rem; border-radius: var(--r-sm); font-size: var(--fs-md); color: var(--accent-hover); }
.codes { list-style: none; padding: 0; margin: .4rem 0 0; display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; }
.codes li { background: var(--surface-3); border: 1px solid var(--line); border-radius: var(--r-sm); padding: .45rem .6rem; text-align: center; }
.codes code { background: none; border: none; font-size: var(--fs-base); letter-spacing: .06em; color: var(--fg); }
.release {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: .85rem .95rem;
  margin: .7rem 0;
  background: var(--surface-2);
}
.hero { margin: 1.5rem 0 2rem; }
.hero h1 { font-size: var(--fs-d4); font-weight: var(--fw-black); letter-spacing: -.03em; }
.tagline { color: var(--fg-dim); font-size: var(--fs-2xl); margin: .5rem 0 .2rem; max-width: 60ch; }
.prose { line-height: 1.7; color: var(--fg-dim); }
.prose > :first-child { margin-top: 0; }
.prose h1, .prose h2, .prose h3 { color: var(--fg); margin: 1.2rem 0 .5rem; }
.prose a { text-decoration: underline; text-underline-offset: 2px; }
.prose ul, .prose ol { padding-left: 1.2rem; }
.prose code { color: var(--accent-hover); }
/* Markdown tables — how a requirements block is actually written (CPU / RAM /
   disk per deployment shape). Scrolls inside its own box on a narrow screen so
   a wide table never widens the page. */
.prose .tablewrap, .prose table { display: block; max-width: 100%; overflow-x: auto; }
.prose table { border-collapse: collapse; margin: .9rem 0; font-size: var(--fs-base); }
.prose th, .prose td { padding: .45rem .8rem; border: 1px solid var(--line); text-align: left; vertical-align: top; }
.prose th { color: var(--fg); font-weight: var(--fw-semibold); background: var(--surface-2); white-space: nowrap; }
.prose tbody tr:nth-child(even) td { background: var(--zebra); }
.channel { margin: 1rem 0; padding-top: 1rem; border-top: 1px solid var(--line); }
.channel:first-of-type { border-top: none; padding-top: 0; }
.channel h3 { display: inline-flex; align-items: center; gap: .5rem; font-size: var(--fs-lg); }
.downloads { list-style: none; padding: 0; margin: .7rem 0 0; display: flex; flex-direction: column; gap: .5rem; }
.downloads li { display: flex; align-items: center; gap: .7rem; }
a.dl {
  display: inline-flex; align-items: center; gap: .5rem;
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  border: 1px solid var(--line-strong);
  padding: .5rem .8rem; border-radius: var(--r-sm); font-weight: var(--fw-mid); color: var(--fg);
  box-shadow: var(--sheen);
  transition: border-color var(--ease), background var(--ease), box-shadow var(--ease);
}
a.dl::before { content: "↓"; color: var(--accent); font-weight: var(--fw-black); }
a.dl:hover {
  border-color: var(--accent); background: var(--accent-weak); color: var(--hover-fg);
  box-shadow: var(--sheen), 0 6px 16px -8px var(--accent-glow);
}


/* The pinned install command, marked as the deliberate version-locked variant
   rather than a second way to do the same thing. */
.pm.pm-pin { margin-top: .5rem; background: var(--accent-wash); border-color: var(--accent-ring); }

/* --- Small buttons --------------------------------------------------------
   These sit beside text, so they need enough padding to read as a control
   rather than as another word in the sentence, and a surface of their own. */
.copy, .mini {
  padding: .34rem .8rem;
  font-size: var(--fs-tiny); font-weight: var(--fw-semibold); letter-spacing: .01em;
  border-radius: var(--r-sm);
  color: var(--fg-dim);
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  border: 1px solid var(--line-strong);
  box-shadow: var(--sheen), var(--shadow-1);
  transition: color var(--ease), background var(--ease), border-color var(--ease),
              box-shadow var(--ease), transform var(--ease);
}
.copy:hover, .mini:hover {
  color: var(--hover-fg); border-color: var(--accent);
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  box-shadow: var(--sheen), 0 4px 12px -4px var(--accent-glow);
  transform: translateY(-1px);
}
.copy:active, .mini:active { transform: none; box-shadow: var(--press-inset); }
.copy.ok, .copy.ok:hover {
  color: var(--ok); border-color: var(--ok-line); background: var(--ok-weak);
  box-shadow: var(--sheen); transform: none;
}
.copy:disabled, .copy:disabled:hover {
  opacity: .4; cursor: not-allowed; transform: none;
  color: var(--fg-muted); border-color: var(--line);
  background: var(--surface-2); box-shadow: none;
}

/* ==========================================================================
   Appearance control
   A segmented control, not a toggle: "follow the system" is a real choice and a
   two-state switch cannot express it. Each option is a submit button, so the
   choice is the action — there is no Save to forget, and it works with
   scripting off. The selected option is a raised thumb rather than a tint: on a
   row of three, elevation says "this one" faster than colour does.
   ========================================================================== */
.appearance { margin: .9rem 0 0; }
.seg {
  display: inline-flex; align-items: stretch; gap: .15rem;
  padding: .2rem;
  background: var(--surface-3);
  border: 1px solid var(--line);
  border-radius: calc(var(--r) + 2px);
  box-shadow: var(--well-inset);
}
.seg-opt {
  display: inline-flex; align-items: center; gap: .42rem;
  padding: .44rem .85rem;
  background: none; border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--fg-muted); font-size: var(--fs-sm); font-weight: var(--fw-mid);
  box-shadow: none;
}
.seg-opt svg { opacity: .75; transition: opacity var(--ease); }
.seg-opt:hover { color: var(--fg); background: none; border-color: transparent; }
.seg-opt:hover svg { opacity: 1; }
/* The thumb. Same recipe as a raised card, at control scale. */
.seg-opt.on, .seg-opt.on:hover {
  color: var(--fg);
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  border-color: var(--line-strong);
  box-shadow: var(--shadow-1), var(--sheen);
}
.seg-opt.on svg { opacity: 1; color: var(--accent); }
.seg-opt:active { transform: none; }
.seg-opt:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }

/* --- Text size -------------------------------------------------------------
   A slider with five detents, flanked by the two A's that say what it does.
   The track is the same recessed well as the segmented control's, so the two
   settings read as one pair rather than as two borrowed widgets. */
.fontsize { margin-top: 1.1rem; }
.fs-row { display: flex; align-items: center; gap: .8rem; max-width: 26rem; }
.fs-end { color: var(--fg-muted); font-weight: var(--fw-semibold); line-height: 1; }
.fs-end-sm { font-size: var(--fs-xs); }
.fs-end-lg { font-size: var(--fs-2xl); }
.fs-readout { margin-top: .5rem; }
.fs-readout output { color: var(--fg-dim); font-variant-numeric: tabular-nums; }

input[type=range] {
  flex: 1 1 auto; min-width: 0;
  appearance: none; -webkit-appearance: none;
  background: none; border: none; padding: 0;
  height: 1.4rem; cursor: pointer;
}
input[type=range]:hover { border-color: transparent; }
input[type=range]:focus { outline: none; background: none; box-shadow: none; }
input[type=range]:focus-visible { box-shadow: 0 0 0 3px var(--accent-ring); border-radius: var(--r-pill); }

/* Track and thumb have to be declared per engine — a combined selector list is
   dropped wholesale by both when either half is unknown. */
input[type=range]::-webkit-slider-runnable-track {
  height: .4rem; border-radius: var(--r-pill);
  background: var(--surface-3);
  border: 1px solid var(--line);
  box-shadow: var(--well-inset);
}
input[type=range]::-moz-range-track {
  height: .4rem; border-radius: var(--r-pill);
  background: var(--surface-3);
  border: 1px solid var(--line);
  box-shadow: var(--well-inset);
}
input[type=range]::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none;
  width: 1.05rem; height: 1.05rem; margin-top: -.36rem;
  border-radius: 50%;
  background: linear-gradient(180deg, var(--accent-top), var(--accent));
  border: 1px solid transparent;
  box-shadow: 0 1px 0 rgba(255, 255, 255, .25) inset, var(--shadow-1),
              0 4px 10px -4px var(--accent-glow);
  transition: transform var(--ease);
}
input[type=range]::-moz-range-thumb {
  width: 1.05rem; height: 1.05rem;
  border-radius: 50%;
  background: linear-gradient(180deg, var(--accent-top), var(--accent));
  border: 1px solid transparent;
  box-shadow: 0 1px 0 rgba(255, 255, 255, .25) inset, var(--shadow-1),
              0 4px 10px -4px var(--accent-glow);
}
input[type=range]:active::-webkit-slider-thumb { transform: scale(1.08); }

/* --- Definition list -------------------------------------------------------
   Label and value in two columns, for the handful of facts an account page
   states rather than edits. */
.deflist {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: .4rem 1.4rem; margin: .9rem 0 0;
}
.deflist dt { color: var(--fg-muted); font-size: var(--fs-xs); font-weight: var(--fw-medium); }
.deflist dd { margin: 0; color: var(--fg); font-size: var(--fs-base); overflow-wrap: anywhere; }

/* ==========================================================================
   Governing documents (/legal/…)
   A page whose only job is to be read, so it is built around the measure of a
   line rather than around cards: one column of prose at reading width, its
   contents list parked in the margin. The chrome is deliberately quieter than
   anywhere else in the product — nothing here competes with the text.
   ========================================================================== */
.dochead { margin: .6rem 0 1.8rem; }
.dochead .eyebrow {
  margin: 0 0 .5rem; color: var(--fg-muted); font-size: var(--fs-micro);
  font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: .11em;
}
.dochead h1 {
  font-size: var(--fs-d2); font-weight: var(--fw-black); letter-spacing: -.032em;
  color: var(--fg-strong); margin: 0;
}
/* Version and effective date: the two facts that decide whether this text is
   the one that governs you. They belong with the title, not in the body. */
.docmeta {
  display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
  margin: .55rem 0 0; color: var(--fg-muted); font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}
.docmeta .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .55; }

/* A draft says so before it says anything else, in the one colour the product
   reserves for "do not act on this yet". */
.docdraft {
  border: 1px solid var(--warn-line); background: var(--warn-weak);
  border-radius: var(--r); padding: .95rem 1.1rem; margin: 0 0 1.8rem;
}
.docdraft h2 {
  margin: 0; font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--warn);
}
.docdraft p { margin: .35rem 0 0; color: var(--fg-dim); font-size: var(--fs-base); line-height: 1.6; max-width: 68ch; }

/* Contents in the right margin on a wide screen; above the text, as a block
   worth reading, when there is no margin to put it in. */
.docgrid {
  display: grid; grid-template-columns: minmax(0, 1fr) 15rem;
  gap: 0 3rem; align-items: start;
}
.doctoc {
  grid-column: 2; grid-row: 1;
  position: sticky; top: 5rem;
  max-height: calc(100vh - 7rem); overflow-y: auto;
  padding-left: 1.1rem; border-left: 1px solid var(--line);
}
.doctoc-title {
  margin: 0 0 .6rem; color: var(--fg-muted); font-size: var(--fs-micro);
  font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: .11em;
}
.doctoc ol { list-style: none; margin: 0; padding: 0; counter-reset: none; }
.doctoc li { margin: 0 0 .38rem; }
.doctoc a {
  display: block; color: var(--fg-muted); font-size: var(--fs-sm); line-height: 1.4;
  text-decoration: none; transition: color var(--ease);
}
.doctoc a:hover { color: var(--fg); }

/* The document itself. Larger and looser than product copy: this is read in
   paragraphs, not scanned in rows. */
.prose.doc {
  grid-column: 1; grid-row: 1;
  max-width: 68ch; font-size: var(--fs-lg); line-height: 1.75; color: var(--fg-dim);
}
.prose.doc h2 {
  margin: 2.4rem 0 .7rem; padding-top: 1.6rem; border-top: 1px solid var(--line);
  font-size: var(--fs-2xl); font-weight: var(--fw-bold); letter-spacing: -.015em;
  color: var(--fg-strong); scroll-margin-top: 5rem;
}
.prose.doc h2:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.prose.doc h3 { margin: 1.6rem 0 .5rem; font-size: var(--fs-xl); scroll-margin-top: 5rem; }
.prose.doc p, .prose.doc li { margin: .8rem 0; }
.prose.doc strong { color: var(--fg); font-weight: var(--fw-semibold); }
.prose.doc a { color: var(--accent); }
/* Placeholders in a draft read as [BRACKETS]; nothing marks them up, so they
   are left to the eye — but the surrounding text must not make them look
   resolved, which is why draft pages carry the banner above. */

/* ==========================================================================
   Site footer
   The end of every page: who this is, and the documents that govern it. A
   hairline and small type — a footer that shouts is a footer in the way.
   ========================================================================== */
.sitefoot { border-top: 1px solid var(--line); margin-top: auto; }
/* Centred column: the links first, then the fine print under them. Ranking the
   two vertically rather than pinning them to opposite ends means the copyright
   reads as a closing statement instead of a stray item in a toolbar. */
.sitefoot-in {
  display: flex; flex-direction: column; align-items: center; gap: .5rem;
  margin: 0 auto; padding-top: 1.4rem; padding-bottom: 1.4rem;
  color: var(--fg-muted); font-size: var(--fs-xs); text-align: center;
}
.sitefoot-mark { margin: 0; }
/* The build is a technical fact sitting beside a legal one, so it is set apart
   by texture rather than by size — same weight, monospace, one notch dimmer. */
.sitefoot-ver { font-family: var(--mono, monospace); opacity: .75; }
.sitefoot-sep { padding: 0 .15rem; opacity: .5; }
.sitefoot-nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 0 1.2rem; }
.sitefoot-nav a { color: var(--fg-muted); text-decoration: none; transition: color var(--ease); }
.sitefoot-nav a:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 2px; }

/* The footer sits at the foot of a short page, not halfway up it. */
body { display: flex; flex-direction: column; min-height: 100vh; }
body > main { flex: 1 0 auto; }

/* ==========================================================================
   Responsive
   ========================================================================== */
/* Below the width that affords a margin, the contents list stops being a margin
   note and becomes the block the page opens with — same markup, moved. */
@media (max-width: 900px) {
  .docgrid { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .doctoc {
    grid-column: 1; grid-row: 1;
    position: static; max-height: none; overflow: visible;
    padding: 0 0 1.2rem; margin-bottom: 1.6rem;
    border-left: none; border-bottom: 1px solid var(--line);
  }
  .doctoc ol { columns: 2; column-gap: 1.6rem; }
  .prose.doc { grid-column: 1; grid-row: 2; max-width: none; }
}

@media (max-width: 640px) {
  .doctoc ol { columns: 1; }
  .dochead { margin-bottom: 1.4rem; }
  /* The tile and the name keep their line; the version drops beneath them and
     lies down, because a right-hand column three characters wide is not a
     column. The plinth stacks with it. */
  .pcard {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "mark head"
      "rel  rel"
      "rail rail";
    column-gap: .9rem; padding: 1.15rem 1.15rem;
  }
  .pcard-mark { width: 2.9rem; height: 2.9rem; border-radius: 13px; font-size: var(--fs-2xl); }
  .pcard-name { font-size: var(--fs-xl); }
  .pcard-rel {
    flex-direction: row; align-items: center; justify-content: flex-start;
    gap: .55rem; margin-top: .95rem; padding-top: 0;
  }
  .pcard-rail { margin: 1rem -1.15rem -1.15rem; padding: .75rem 1.15rem; }
  .pcard-cta { padding-left: .6rem; }

  /* Three options no longer fit one line beside a 1rem gutter; the control
     stretches instead of scrolling. */
  .seg { display: flex; width: 100%; }
  .seg-opt { flex: 1 1 0; justify-content: center; padding: .44rem .5rem; }

  /* The files table is the page's content, not a dense admin grid — let it
     wrap instead of inheriting the horizontal-scroll treatment. */
  /* File and command rows collapse to two lines rather than scrolling. */
  .file { grid-template-columns: 1fr auto; row-gap: .3rem; }
  .file-name { grid-column: 1 / -1; order: 2; }
  .file-size { text-align: left; order: 1; }
  .file-act { grid-column: 1 / -1; order: 3; justify-content: flex-start; }
  .pm-row { grid-template-columns: 1fr auto; }
  .pm-os { grid-column: 1 / -1; }

  /* Release actions drop under the version rather than squeezing beside it. */
  .rel-act { margin-left: 0; margin-top: .5rem; width: 100%; }

  /* The index row keeps its chevron — it is what says the row is a way in —
     and drops the version underneath the name, where there is room for it. */
  .proj-row { grid-template-columns: minmax(0, 1fr) auto; padding: .9rem 1rem; }
  .proj-meta { grid-column: 1; grid-row: 2; text-align: left; }
  .proj-meta .ver { display: inline; }
  .proj-age::before { content: " · "; }
  .proj-go { grid-column: 2; grid-row: 1 / span 2; }

  .req-approve { width: 100%; }
  .req-approve input, .req-approve select, .req-approve button { flex: 1 1 100%; }
  .wrap { margin-top: 1.4rem; }
  .grid2 { grid-template-columns: 1fr; }
  .card { padding: 1.1rem; border-radius: var(--r); }
  .hero h1 { font-size: var(--fs-d2); }
  .list { display: block; overflow-x: auto; white-space: nowrap; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Forced-colors mode paints its own palette; a transparent text fill would
   erase the page title entirely. */
@media (forced-colors: active) {
  .pagehead h1 { -webkit-text-fill-color: currentColor; background: none; }
}

/* Release notes page: one column, generous measure — it exists to be read by an
   operator deciding whether to take an update, not scanned like a dashboard. */
.wrap.narrow { max-width: 46rem; }
.notes-hero { margin: 1.5rem 0 1.25rem; }
.notes-hero h1 { font-size: var(--fs-d2); font-weight: var(--fw-black); letter-spacing: -.03em; margin: .35rem 0 .6rem; }
.crumb { color: var(--fg-muted); font-size: var(--fs-sm); margin: 0; }
.notes-meta { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin: 0; }

/* Channel list: one row per track, its settings one click away. A flat table
   could not carry visibility, label and description without becoming unreadable
   at exactly the moment the page matters most — deciding who can see what. */
.chanlist { list-style: none; margin: 0; padding: 0; }
.chanrow + .chanrow { border-top: 1px solid var(--bh); }
.chanrow > details > summary { display: flex; align-items: center; gap: .5rem; padding: .7rem .2rem; cursor: pointer; }
.chanrow > details > summary::-webkit-details-marker { display: none; }
.chanrow .chan-name { font-weight: var(--fw-semibold); }
.chanrow .note { margin-left: auto; }
.chanactions { margin-top: .9rem; padding-top: .9rem; border-top: 1px solid var(--bh); }
