/* Affiliate dashboard — same dark "instrument panel" language as the internal
   dashboard (palette lifted from dashboard-webinar/styles.css). */

:root {
  color-scheme: dark;

  --bg:        #0a0a0a;
  --surface:   #161616;
  --surface-2: #1e1e1e;
  /* A divider may whisper; the edge of a control may not. --line is decorative
     (card and table rules); anything you can type in or click gets --line-strong,
     which clears WCAG 1.4.11's 3:1 against all three surfaces above. */
  --line:        #2e2e2e;
  --line-soft:   #202020;
  --line-strong: #6a6a74;
  --ink:       #ffffff;
  /* Three text tiers, each ≥4.5:1 on every surface, and cool-tinted — dead-neutral
     grey reads lifeless on a dark panel. The tertiary tier used to be #5a5a5a: 2.6:1,
     and it carried every field label, table header, placeholder, help line and empty
     state in the app. Raising it moved the other two up a rung to keep the steps apart. */
  --text:      #d4d4dc;  /* 12.3:1 on surface */
  --muted:     #a1a1aa;  /*  7.1:1 */
  --faint:     #8a8a94;  /*  4.9:1 */

  --accent:     #ffffff;
  --accent-soft: rgba(255,255,255,0.10);
  --accent-ink: #0a0a0a;
  /* State only — a confirmation, a status, a thing that went wrong. Never decoration:
     a column of identical green $1.00s is colour spent to say nothing. Rank with size
     and weight first; reach for hue only when it carries information. */
  --good:      #58cc9a;
  --warn:      #e5645e;

  --rail: 210px;
  /* One shadow, for the two things that genuinely float. Everything else conveys
     elevation the way a dark UI should — by getting lighter (--bg → --surface → --surface-2).
     There was a --shadow-1 here too; nothing used it. */
  --shadow-2: 0 10px 30px rgba(0,0,0,0.6);

  /* Type scale. Every font-size in this file is one of these — there is no 11.5px,
     because at these sizes half a pixel is a distinction the renderer can't draw. */
  --t-xs:   11px;   /* uppercase micro-labels, pills */
  --t-sm:   12px;   /* help text, metadata, small controls */
  --t-base: 14px;   /* body, tables, inputs, buttons */
  --t-md:   16px;   /* card titles */
  --t-lg:   20px;   /* page title */
  --t-xl:   24px;   /* auth headline */
  --t-2xl:  30px;   /* tile values */
  --t-3xl:  36px;   /* the one lead tile per view */

  /* Two control heights, set here rather than back-computed from padding, so a button
     and the input beside it land on the same baseline without a flex stretch. */
  --control:    40px;
  --control-sm: 32px;

  --r-sm: 6px; --r-md: 10px; --r-lg: 14px;
  --space-2: 4px; --space-3: 8px; --space-4: 12px; --space-5: 16px;
  --space-6: 24px; --space-7: 32px; --space-8: 48px;

  --font: "Outfit", system-ui, -apple-system, sans-serif;
  --mono: "Geist Mono", "JetBrains Mono", ui-monospace, "SF Mono", monospace;
  /* A strong ease-out. Everything that enters, exits or responds to a press uses it —
     it starts fast, so the UI answers on the first frame the user is watching. */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --ring: 0 0 0 3px rgba(255,255,255,0.07);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
[hidden] { display: none !important; }
h1, h2 { color: var(--ink); font-weight: 600; margin: 0; letter-spacing: -0.01em; text-wrap: balance; }

/* ── Login ─────────────────────────────────────────────────────────────────── */
/* Column flex, not grid: the help line sits BELOW the card as a second child, and
   place-items on a grid would give each its own stretched row and split them apart. */
.login-wrap {
  min-height: 100%; padding: var(--space-6);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.login-card {
  width: 100%; max-width: 380px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--space-7);
  box-shadow: var(--shadow-2);
}
.login-brand { margin-bottom: var(--space-6); }
/* Logo is white-on-transparent, so it sits directly on the dark card. */
.login-brand img { height: 30px; width: auto; display: block; }
.login-card h1 { font-size: var(--t-xl); }
.login-sub { color: var(--muted); margin: var(--space-2) 0 var(--space-6); font-size: var(--t-base); }

.field { display: block; margin-bottom: var(--space-5); }
.field > span {
  display: block; font-size: var(--t-xs); text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--faint); margin-bottom: var(--space-3);
}

input, select {
  width: 100%; min-height: var(--control); padding: var(--space-3) var(--space-4);
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  font-family: var(--font); font-size: var(--t-base);
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
input:focus, select:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
input::placeholder { color: var(--faint); }
/* The row already draws focus via :focus-within — a second ring inside it would double up. */
.slug-row input:focus, .slug-row input:focus-visible { box-shadow: none; outline: none; }

/* ── Buttons ───────────────────────────────────────────────────────────────── */
/* inline-flex so --control drives the height and centres the label, instead of the
   height falling out of whatever padding and line-height happen to sum to. */
.btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--text);
  min-height: var(--control); padding: var(--space-3) var(--space-5);
  font-family: var(--font); font-size: var(--t-base); font-weight: 500;
  cursor: pointer; white-space: nowrap;
  user-select: none; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease),
              color 0.15s var(--ease), opacity 0.15s, transform 0.14s var(--ease);
}
/* The press is the only feedback that lands before the network does. */
.btn:active:not(:disabled) { transform: scale(0.97); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); font-weight: 600; }
.btn-ghost { background: transparent; }
.btn-sm { min-height: var(--control-sm); padding: var(--space-2) var(--space-4); font-size: var(--t-sm); }
.btn-block { width: 100%; }
a.btn { text-decoration: none; }
a.btn.btn-block { display: flex; }
/* Copy buttons swap their label to "Copied" — reserve the wider width so nothing shifts. */
[data-copy], [data-copy-text] { min-width: 68px; text-align: center; }
.btn.is-copied { color: var(--good); border-color: rgba(88,204,154,0.35); animation: label-swap 0.18s var(--ease); }

/* Busy: keep the button's exact size, trade the label for a spinner. A fast spinner
   makes the wait read as shorter than a slow one, for the same wait. */
.btn.is-busy { color: transparent; opacity: 1; }
.btn.is-busy::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 13px; height: 13px; border-radius: 50%;
  border: 1.5px solid rgba(255,255,255,0.22); border-top-color: var(--text);
  animation: spin 0.6s linear infinite;
}
.btn-primary.is-busy::after { border-color: rgba(10,10,10,0.25); border-top-color: var(--accent-ink); }

:where(button, a, input, select):focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* Keyboard escape hatch past the eight rail links. position:fixed also keeps it out of
   the .app grid's auto-placement, which would otherwise hand it the rail's column. */
.skip-link {
  position: fixed; top: var(--space-4); left: var(--space-4); z-index: 60;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: var(--space-3) var(--space-5); font-size: var(--t-base); font-weight: 500; text-decoration: none;
  transform: translateY(-250%);
  transition: transform 0.18s var(--ease);
}
.skip-link:focus { transform: none; }
/* Focused only as the skip link's target — a ring around the whole page is noise. */
#main:focus { outline: none; }

/* "New here? Create an account" / "Already have an account? Sign in" */
.alt-action { margin: var(--space-5) 0 0; text-align: center; color: var(--muted); font-size: var(--t-sm); }
.link-btn {
  background: none; border: 0; padding: 0; border-radius: 3px;
  color: var(--ink); font: inherit; font-weight: 500; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px; text-decoration-color: var(--faint);
  transition: text-decoration-color 0.15s var(--ease);
}
/* The way back in for anyone locked out — there is no self-serve password reset, so this
   line is the whole recovery path. Quieter than .alt-action; it's a fallback, not a choice. */
/* Outside the card, so it wraps at the card's width rather than the viewport's. */
.card-help {
  width: 100%; max-width: 380px;
  margin: var(--space-5) 0 0; text-align: center; color: var(--faint); font-size: var(--t-sm);
}
.card-help a {
  color: var(--muted); border-radius: 3px;
  text-decoration: underline; text-underline-offset: 2px; text-decoration-color: var(--line);
  transition: color 0.15s var(--ease), text-decoration-color 0.15s var(--ease);
  overflow-wrap: anywhere;
}

.form-error {
  margin-top: var(--space-4); padding: var(--space-4) var(--space-5);
  background: rgba(229,100,94,0.10); border: 1px solid rgba(229,100,94,0.35);
  border-radius: var(--r-sm); color: var(--warn); font-size: var(--t-base);
}

/* ── App shell ─────────────────────────────────────────────────────────────── */
/* dvh, not vh: on iOS Safari the URL bar makes 100vh taller than the visible
   viewport, which leaves the sticky rail scrolling a few px under the chrome. */
.app { display: grid; grid-template-columns: var(--rail) 1fr; min-height: 100dvh; }

.rail {
  border-right: 1px solid var(--line); background: var(--surface);
  display: flex; flex-direction: column; padding: var(--space-6) 0;
  position: sticky; top: 0; height: 100dvh;
}
.rail-brand { padding: 0 var(--space-6) var(--space-7); }
/* 26px matches the internal dashboard's rail logo. */
.rail-logo { height: 26px; width: auto; display: block; }
/* Sublabel must sit under the wordmark, not compete with it. */
.rail-brand span {
  display: block; margin-top: var(--space-3);
  color: var(--faint); font-weight: 500; font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: 0.14em;
}

.rail-nav { display: flex; flex-direction: column; gap: 2px; padding: 0 var(--space-4); flex: 1; }
.rail-link {
  text-align: left; background: none; border: 0; cursor: pointer;
  padding: var(--space-3) var(--space-4); border-radius: var(--r-sm);
  color: var(--muted); font-family: var(--font); font-size: var(--t-base); font-weight: 500;
  user-select: none; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: background 0.15s var(--ease), color 0.15s var(--ease), transform 0.14s var(--ease);
}
.rail-link:active { transform: scale(0.985); }
.rail-link.is-active { background: var(--accent-soft); color: var(--ink); font-weight: 600; }

.rail-foot { padding: var(--space-5) var(--space-6) 0; border-top: 1px solid var(--line-soft); margin-top: var(--space-5); }
.rail-user { margin-bottom: var(--space-4); min-width: 0; }
.rail-user-name { color: var(--text); font-weight: 500; font-size: var(--t-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-user-email { color: var(--faint); font-size: var(--t-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Capped so nine-column tables and a five-tile row don't sprawl the full width of an
   ultrawide monitor. The auto margins centre the column in whatever the rail leaves. */
.main {
  padding: var(--space-6) var(--space-7) var(--space-8);
  min-width: 0; width: 100%; max-width: 1400px; margin-inline: auto;
}

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-5); margin-bottom: var(--space-6);
}
.topbar h1 { font-size: var(--t-lg); }
.topbar-actions { display: flex; align-items: center; gap: var(--space-4); }
/* Sits beside a .btn-sm, so it takes the same small-control height rather than
   whatever its own padding would have produced. */
.rate-chip {
  display: inline-flex; align-items: center;
  min-height: var(--control-sm); padding: var(--space-2) var(--space-4);
  font-family: var(--mono); font-size: var(--t-sm); color: var(--muted);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px;
}

.banner {
  margin-bottom: var(--space-5); padding: var(--space-4) var(--space-5); border-radius: var(--r-sm);
  background: rgba(229,100,94,0.10); border: 1px solid rgba(229,100,94,0.35);
  color: var(--warn); font-size: var(--t-base);
}

/* ── Tiles ─────────────────────────────────────────────────────────────────── */
/* The gap has to beat the tile's own padding, or the row reads as one banded strip
   instead of five separate figures. Same reason the row is 32px clear of what follows
   while the cards inside it are only 24px deep. */
.tiles {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-5); margin-bottom: var(--space-7);
}
.tile {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--space-5);
}
.tile-label {
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--faint); margin-bottom: var(--space-3);
}
.tile-value { font-size: var(--t-2xl); font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }

/* One tile per view answers the question the user actually came with — what am I owed.
   It's ranked by size, which survives a squint and a greyscale print; it used to be
   ranked by being red, which survives neither and also read as an error on a healthy
   balance. Colour here is left to say something: see --good / --warn. */
.tile.is-lead { background: var(--surface-2); border-color: var(--line-strong); }
.tile.is-lead .tile-value { font-size: var(--t-3xl); }

/* ── Cards ─────────────────────────────────────────────────────────────────── */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--space-6); margin-bottom: var(--space-7);
}
.card-head { margin-bottom: var(--space-5); }
.card-head h2 { font-size: var(--t-md); }
/* Capped measure — the invite blurb is a full paragraph and ran to one 1200px line. */
.card-sub { color: var(--muted); font-size: var(--t-sm); margin: var(--space-2) 0 0; max-width: 68ch; text-wrap: pretty; }

/* ── Create form ───────────────────────────────────────────────────────────── */
/* Stacked, labelled form — the two fields do different jobs and a single row hid that. */
.create-stack { display: flex; flex-direction: column; gap: var(--space-5); max-width: 560px; }
.create-stack > button { align-self: flex-start; }
.field-block { display: flex; flex-direction: column; }
.field-label {
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--faint); margin-bottom: var(--space-3);
}
.field-help { display: block; color: var(--faint); font-size: var(--t-sm); margin: var(--space-3) 0 0; }
/* Inside a .field the uppercase label rule (.field > span) would otherwise claim this too. */
.field > span.field-help { text-transform: none; letter-spacing: normal; font-size: var(--t-sm); }
.field-help b { color: var(--muted); font-weight: 500; overflow-wrap: anywhere; }

.create-form { display: flex; gap: var(--space-4); align-items: stretch; flex-wrap: wrap; }
.create-form.wrap > * { flex: 1 1 170px; min-width: 0; }
.create-form.wrap > button { flex: 0 0 auto; }

/* Lives inside a COLUMN flex container (.field-block). A flex-basis here would be read as
   a height, which is what once made this box 340px tall. Keep it auto-height. */
.slug-row {
  display: flex; align-items: stretch; min-width: 0;
  border: 1px solid var(--line-strong); border-radius: var(--r-sm); background: var(--bg);
  transition: border-color 0.15s var(--ease);
}
.slug-row:focus-within { border-color: var(--accent); }
.slug-prefix {
  display: flex; align-items: center; padding: 0 var(--space-2) 0 var(--space-4);
  color: var(--faint); font-family: var(--mono); font-size: var(--t-base); white-space: nowrap;
}
.slug-row input { border: 0; background: transparent; padding-left: 0; font-family: var(--mono); }
.slug-row input:focus { outline: none; }

.hint { color: var(--faint); font-size: var(--t-sm); margin: var(--space-4) 0 0; max-width: 68ch; text-wrap: pretty; }
.hint.center { text-align: center; margin-inline: auto; }

.create-success {
  display: flex; align-items: center; gap: var(--space-4);
  margin-top: var(--space-5); padding: var(--space-4) var(--space-5);
  background: rgba(88,204,154,0.08); border: 1px solid rgba(88,204,154,0.35);
  border-radius: var(--r-sm);
}
.create-success code { font-family: var(--mono); font-size: var(--t-base); color: var(--good); flex: 1; overflow-wrap: anywhere; }
.ok-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); flex: none; }

.invite-body { flex: 1; min-width: 0; }
.invite-for { color: var(--muted); font-size: var(--t-sm); margin-bottom: var(--space-2); }
.invite-body code { display: block; }

.row-actions { display: flex; gap: var(--space-3); }

/* ── Tables ────────────────────────────────────────────────────────────────── */
.table-wrap { overflow-x: auto; margin: 0 calc(-1 * var(--space-6)); padding: 0 var(--space-6); }
.tbl { width: 100%; border-collapse: collapse; font-size: var(--t-base); }
.tbl th, .tbl td {
  text-align: left; padding: var(--space-4);
  border-bottom: 1px solid var(--line-soft); white-space: nowrap;
}
.tbl thead th {
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--faint); font-weight: 500; border-bottom-color: var(--line);
}
.tbl tfoot th { color: var(--ink); font-weight: 600; border-bottom: 0; border-top: 1px solid var(--line); }
.tbl tbody tr:last-child td { border-bottom: 0; }
.tbl .num { text-align: right; font-variant-numeric: tabular-nums; }
.tbl td.num { font-family: var(--mono); }
.tbl td:first-child, .tbl th:first-child { padding-left: 0; }
.tbl td:last-child, .tbl th:last-child { padding-right: 0; }

.cell-link { display: flex; align-items: center; gap: var(--space-3); }
.cell-link code { font-family: var(--mono); font-size: var(--t-sm); color: var(--ink); }
.mono { font-family: var(--mono); font-size: var(--t-sm); color: var(--muted); }
/* Currency cells were green. In the opt-ins table every row is the same $1.00, so that
   was a whole column of colour carrying no information — and it spent the one hue that
   should mean "this went well". Alignment and tabular-nums already mark a money column. */
.dim { color: var(--faint); }

.pill {
  display: inline-block; padding: var(--space-2) var(--space-3); border-radius: 999px;
  font-size: var(--t-xs); font-weight: 500; border: 1px solid var(--line); color: var(--muted);
}
.pill.admin { color: var(--ink); border-color: var(--line-strong); background: var(--surface-2); }
.pill.on  { color: var(--good); border-color: rgba(88,204,154,0.35); }
.pill.off { color: var(--warn); border-color: rgba(229,100,94,0.35); }
.pill.pending { color: #e5b45e; border-color: rgba(229,180,94,0.35); }

.empty { color: var(--faint); font-size: var(--t-base); padding: var(--space-6) 0 var(--space-2); text-align: center; }

/* ── Toast ─────────────────────────────────────────────────────────────────── */
/* Transitions, not keyframes: a second toast fired mid-flight retargets from where the
   first one is instead of restarting from zero. It enters and leaves through the same
   edge it lives on, so the motion explains where it came from.
   translateX(-50%) is the centring — it must survive every state, including reduced motion. */
.toast {
  position: fixed; bottom: var(--space-6); left: 50%;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
  padding: var(--space-4) var(--space-6); border-radius: 999px; font-size: var(--t-base);
  box-shadow: var(--shadow-2); z-index: 50;
  pointer-events: none;
  transform-origin: bottom center;
  opacity: 0; visibility: hidden;
  transform: translateX(-50%) translateY(10px) scale(0.97);
  /* Leaving: quicker than arriving — the system is done, get out of the way. */
  transition: opacity 0.15s var(--ease), transform 0.15s var(--ease), visibility 0s linear 0.15s;
}
.toast[data-show] {
  opacity: 1; visibility: visible;
  transform: translateX(-50%) translateY(0) scale(1);
  transition: opacity 0.24s var(--ease), transform 0.24s var(--ease), visibility 0s;
}

/* ── Motion ────────────────────────────────────────────────────────────────── */
/* Nothing enters from scale(0) — things that were never there don't have a size.
   These fire on rare, meaningful moments only: signing in, a link you just made,
   an error you need to see. Tab switches stay instant; they happen all day. */
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rise-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes card-in {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes value-in {
  from { opacity: 0; transform: translateY(-4px); filter: blur(3px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
@keyframes label-swap {
  from { opacity: 0.55; filter: blur(2px); }
  to   { opacity: 1; filter: blur(0); }
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Every direct child of a visible auth screen — the card AND the help line under it —
   so the pair arrives as one object instead of the card moving and the text popping. */
.login-wrap:not([hidden]) > * { animation: card-in 0.34s var(--ease) both; }

/* Opacity only: a transform here would create a containing block and break the sticky rail. */
#app-view:not([hidden]) { animation: fade-in 0.26s var(--ease) both; }

#create-success:not([hidden]),
#invite-result:not([hidden]),
.form-error:not([hidden]),
.banner:not([hidden]) { animation: rise-in 0.2s var(--ease) both; }

/* Only values that actually changed get this — see setStat() in app.js. Blur bridges the
   two numbers so the eye reads one value changing, not two values swapping. */
.tile-value.is-fresh { animation: value-in 0.26s var(--ease) both; }

/* ── Loading ───────────────────────────────────────────────────────────────── */
/* First paint only (see state.loaded in app.js). A refresh keeps the numbers already on
   screen — swapping them for grey bars would read as data loss, and the Refresh button
   is already spinning. A calm opacity pulse, not a sweeping shimmer: this is a panel
   of numbers, and the placeholder shouldn't be livelier than the data. */
@keyframes skeleton-pulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.85; } }

.skeleton-bar {
  display: block; height: var(--space-3); border-radius: 999px; background: var(--line);
  animation: skeleton-pulse 1.5s ease-in-out infinite;
}
/* Uneven widths so a loading table reads as text, not as a chart. */
.skeleton-row td:nth-child(odd)  .skeleton-bar { width: 80%; }
.skeleton-row td:nth-child(even) .skeleton-bar { width: 56%; }

/* Keeps the tile's real line box, so nothing reflows when the number lands. */
.tile-value.is-skeleton { color: transparent; position: relative; }
/* Sized in em so it tracks the tile's own type size — the lead tile's number is bigger,
   and its placeholder has to be too or the row jumps when the data lands. */
.tile-value.is-skeleton::after {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 2.6em; height: 0.6em; margin-top: -0.3em; border-radius: var(--r-sm);
  background: var(--line); animation: skeleton-pulse 1.5s ease-in-out infinite;
}

/* Touch devices fire :hover on tap and keep it until you tap elsewhere. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover:not(:disabled) { background: #262626; border-color: var(--faint); }
  .btn-primary:hover:not(:disabled) { background: #e8e8e8; border-color: #e8e8e8; }
  .btn.is-copied:hover:not(:disabled) { background: var(--surface-2); border-color: rgba(88,204,154,0.35); }
  .rail-link:hover { background: var(--surface-2); color: var(--text); }
  .tbl tbody tr:hover td { background: rgba(255,255,255,0.02); }
  .link-btn:hover { text-decoration-color: var(--ink); }
  .card-help a:hover { color: var(--ink); text-decoration-color: var(--muted); }
}

/* Fewer and gentler, not none: fades and colour stay, movement and blur go. */
@media (prefers-reduced-motion: reduce) {
  .btn:active:not(:disabled), .rail-link:active { transform: none; }
  .toast, .toast[data-show] { transform: translateX(-50%); }
  .login-wrap:not([hidden]) > *,
  #create-success:not([hidden]),
  #invite-result:not([hidden]),
  .form-error:not([hidden]),
  .banner:not([hidden]),
  .tile-value.is-fresh,
  .btn.is-copied { animation-name: fade-in; }
  .btn.is-busy::after { animation-duration: 1.4s; }
  /* The bars stay — they say "not loaded yet". Only the pulse goes. */
  .skeleton-bar, .tile-value.is-skeleton::after { animation: none; opacity: 0.65; }
  .skip-link { transition: none; }
}

/* ── Responsive ────────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
  .app { grid-template-columns: 1fr; }
  .rail { position: static; height: auto; flex-direction: row; align-items: center; flex-wrap: wrap; padding: var(--space-4); gap: var(--space-4); }
  .rail-brand { padding: 0 var(--space-4) 0 var(--space-3); }
  /* Horizontal rail: the stacked "Affiliates" sublabel would double its height, and a
     full-size wordmark would crowd the scrolling nav on a narrow screen. */
  .rail-brand span { display: none; }
  .rail-logo { height: 18px; }
  .rail-nav { flex-direction: row; overflow-x: auto; flex-wrap: nowrap; padding: 0; }
  .rail-foot { border-top: 0; margin: 0; padding: 0; display: flex; align-items: center; gap: var(--space-4); }
  .rail-user { margin: 0; }
  .main { padding: var(--space-5); }
}
