/* ============================================================================
   Saltado Sessions - Account (web)

   One stylesheet, no build step, no CDN. Sibling of infra/vault/web/assets/app.css:
   a person moving from account.saltadosessions.com to vault.saltadosessions.com
   should not be able to tell they changed origin.

   ⚠️ THE TOKEN BLOCK BELOW IS A VERBATIM COPY of the Vault drive UI's, values
   and comments included. It is duplicated rather than shared because these are
   two Workers with two static roots and no build step between them, so there is
   nothing to import from. That means it can DRIFT: if you change a colour here,
   change it there in the same commit, or the two halves of one product stop
   matching.

   Three deliberate additions, none of which changes an existing value:
     - the four layout variables at the end of the block, this page's furniture
     - --danger-ink and --danger-hover, because the Vault paints white on the
       --error fill and that measures 3.68:1 in the dark theme
     - --page-max, since this page is a column of cards rather than a table

   THEMING: index.html resolves auto/dark/light before first paint and writes
   the RESULT to <html data-theme>. So there is exactly one light block below,
   not two identical ones (a media query for auto plus an attribute rule for the
   manual override). Never add a `prefers-color-scheme` block for tokens here;
   it would silently fight the attribute.

   ⚠️ TYPE: there are SIX sizes and no others. Every `font-size` in this file is
   one of the six --fs-* tokens, and a literal px font-size anywhere below is a
   bug. If a new role seems to need a seventh size, it needs an existing one.

   ⚠️ CONTRAST: every text colour here was measured COMPOSITED, against the
   ancestor fills and overlays it actually sits on, in both themes. Two rules
   this page follows and a later author must not undo:

     1. --faint is for --bg, --sidebar and --well ONLY. On --surface in the dark
        theme it measures 4.51:1 at rest and 4.2:1 the moment a row is hovered,
        i.e. a fail in the state a row is read in. Secondary text on a card uses
        --muted (4.96:1) or --text (7.8:1).
     2. Rows that hover live inside a --well, never directly on a card's
        --surface. The well is darker in dark and lighter in light, which is
        what buys the hover overlay somewhere to go.

   ⚠️ Never express "unavailable" with `opacity` on anything containing text.
   Alpha on a subtree composites the text toward whatever is behind it, so the
   colour you declared is not the colour that gets measured, and it fails
   silently. Dim with a weaker ink token and keep opacity: 1. Native :disabled
   may still use alpha, because such a control is genuinely inert, unfocusable
   and exempt from the contrast minimum.
   ========================================================================= */

@font-face {
  font-family: 'Space Grotesk';
  src: url('fonts/SpaceGrotesk-latin-var.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens */

:root {
  --font: 'Space Grotesk', 'Helvetica Neue', 'Arial Nova', Arial, 'Segoe UI', system-ui, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  /* THE SCALE. Six values, each with one job.
       11  micro    uppercase tracked labels: column heads, section labels, tags
       12  meta     second lines, hints, clocks, anything explaining something else
       14  body     the default: row names, cells, buttons, inputs, prose
       16  lg       section headings: card titles, dialog titles, empty-state titles
       20  title    page titles
       24  hero     the sign-in headline, and the no-JS message */
  --fs-micro: 11px;
  --fs-meta: 12px;
  --fs-body: 14px;
  --fs-lg: 16px;
  --fs-title: 20px;
  --fs-hero: 24px;

  /* dark is the default, matching the app */
  color-scheme: dark;
  --bg: #2b2b2b;
  --surface: #363636;
  --surface-2: #424242;
  --sidebar: #232323;
  --border: #4d4d4d;
  --border-soft: #3d3d3d;
  --text: #cfcfcf;
  --text-strong: #ececec;
  --muted: #a6a6a6;          /* 5.8:1 on --bg, 5.3:1 on a hovered row, 4.96:1 on --surface */
  --faint: #9e9e9e;          /* 5.2:1 on --bg, 5.8:1 on --sidebar. NOT for --surface. */
  --icon: #b0b0b0;           /* 6.5:1 on --bg */
  --accent: #d99146;
  --accent-hover: #e6a25c;
  --accent-ink: #1a1206;      /* text that sits ON the accent */
  /* ⚠️ ACCENT AS TEXT, which is not the same colour as accent as a fill.
     The signed-out screens paint .backdrop behind the content, and text that
     sits on it (the footer links, the sign in / create switch) is composited
     over the glow, not over --bg. The glow tops out at #453a2f at the bottom
     centre, where --accent measures 4.24:1 and fails. Position decides it: the
     same link passes on a short card and fails on a tall one, which is why this
     cannot be judged from a token pair alone.
     This value clears 5.27:1 at the glow's worst point and 5.79:1 on --surface.
     ⚠️ Deliberately NOT the same literal as --accent-hover even though they are
     neighbours: one is a text colour and one is a fill state, and coupling them
     would mean a hover tweak silently changing a contrast result. */
  --accent-text: #e8a663;
  --accent-soft: rgba(217, 145, 70, 0.18);
  --accent-line: rgba(217, 145, 70, 0.34);
  --ok: #67b84b;
  --warn: #e2b13a;
  --error: #e5544b;
  --warn-ink: #eec25c;
  --error-ink: #f0837b;
  --ok-ink: #8ad06f;
  /* ⚠️ Ink ON the --error fill, and the direction its hover moves. Both flip
     per theme and they are COUPLED, exactly like --accent-ink: --error is a
     bright red here and a deep one in light, so dark ink wins here and white
     wins there, and the hover has to move AWAY from the ink each time.
     Measured: white on this fill is 3.68:1, a fail; this ink is 5.2:1. */
  --danger-ink: #1c0b09;
  --danger-hover: color-mix(in srgb, var(--error) 88%, #ffffff);

  --hover: rgba(255, 255, 255, 0.035);
  --hover-2: rgba(255, 255, 255, 0.075);
  --well: rgba(0, 0, 0, 0.22);
  --scrim: rgba(0, 0, 0, 0.6);

  --radius-sm: 4px;
  --radius: 6px;
  --radius-lg: 10px;
  --sh-1: 0 1px 2px rgba(0, 0, 0, 0.3);
  --sh-2: 0 10px 30px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.3);
  --sh-3: 0 24px 70px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);

  --sidebar-w: 258px;
  --header-h: 60px;
  --row-h: 48px;
  --page-max: 760px;
}

:root[data-theme='light'] {
  color-scheme: light;
  --bg: #f3f2ef;
  --surface: #ffffff;
  --surface-2: #f7f6f3;
  --sidebar: #eae8e3;
  --border: #d8d4cc;
  --border-soft: #e4e0d8;
  --text: #23211e;
  --text-strong: #100f0d;
  --muted: #5f5b55;          /* 6.0:1 on --bg, 7.0:1 on --surface */
  --faint: #63605a;          /* 5.1:1 on --sidebar, 5.5:1 on --bg */
  --icon: #57534c;           /* 7.0:1 on --bg */
  /* ⚠️ The light accent is darker than the dark theme's on purpose, because it
     is also INK here: links in hints, the support address. The prettier #a5631a
     measured 4.26:1 as text on --bg, a fail at body size. */
  --accent: #8f5514;
  --accent-hover: #7a4710;
  --accent-ink: #ffffff;
  /* Light is UNCHANGED and measured: --accent as text reads 4.65:1 at this
     theme's worst glow point (#e8e1d7) and 6.04:1 on --surface. In light the
     glow DARKENS the bed, which moves dark ink the safe way. */
  --accent-text: #8f5514;
  --accent-soft: rgba(143, 85, 20, 0.13);
  --accent-line: rgba(143, 85, 20, 0.4);
  --ok: #3f8f2f;
  --warn: #a67c10;
  --error: #c0392b;
  --warn-ink: #6d5008;
  --error-ink: #8c2418;
  --ok-ink: #2b6520;
  --danger-ink: #ffffff;
  --danger-hover: color-mix(in srgb, var(--error) 86%, #000000);

  --hover: rgba(0, 0, 0, 0.035);
  --hover-2: rgba(0, 0, 0, 0.06);
  --well: rgba(0, 0, 0, 0.05);
  --scrim: rgba(28, 26, 23, 0.42);

  --sh-1: 0 1px 2px rgba(35, 31, 26, 0.07);
  --sh-2: 0 10px 28px rgba(35, 31, 26, 0.13), 0 1px 3px rgba(35, 31, 26, 0.08);
  --sh-3: 0 24px 60px rgba(35, 31, 26, 0.18), 0 2px 6px rgba(35, 31, 26, 0.1);
}

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

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.5;
  letter-spacing: 0.002em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;               /* the shell owns its own scroll regions */
}

h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

/* ⚠️ EVERY icon is an <svg><use href="#i-..."> with no intrinsic size, so an
   svg that no rule happens to size falls back to the CSS replaced-element
   default of 300x150 and paints a glyph the size of a paragraph across the
   layout. icon() in app.js also writes width/height attributes as a floor;
   this is the second belt. */
svg { display: block; width: 16px; height: 16px; flex: none; }

/* ⚠️ The UA sheet's `[hidden] { display: none }` loses to ANY display
   declaration in this file, so a `display: flex` rule keeps a hidden node in
   the layout. Global, and !important, because the next author to write
   `display:` on a toggled element should not have to rediscover this. */
[hidden] { display: none !important; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent-soft); color: var(--text-strong); }

#root { position: relative; z-index: 1; height: 100%; }

.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  padding: 9px 14px; border-radius: var(--radius);
  background: var(--accent); color: var(--accent-ink); font-weight: 600;
  transform: translateY(-160%);
  transition: transform 0.14s ease;
}
.skip-link:focus { transform: none; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* thin, quiet scrollbars: the default Windows bar is loud next to this palette */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border); border-radius: 8px;
  border: 3px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: padding-box; }

/* ------------------------------------------------------- first paint / no JS */

.boot {
  position: fixed; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px;
}
.boot-mark {
  width: 46px; height: 41px;
  background: var(--muted);
  -webkit-mask: url('favicon.svg') center / contain no-repeat;
  mask: url('favicon.svg') center / contain no-repeat;
  opacity: 0.5;
}
.boot-text { color: var(--faint); font-size: var(--fs-meta); }

.noscript {
  position: fixed; inset: 0; z-index: 300;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 32px; text-align: center; background: var(--bg);
}
.noscript h1 { font-size: var(--fs-hero); font-weight: 700; letter-spacing: -0.02em; }
.noscript p { max-width: 46ch; color: var(--muted); }

/* --------------------------------------------------------------- backdrop */

/* The signed-out screens are mostly empty space, so they get the same quiet
   composition as the Vault's sign-in: one warm pool from below, one cool wash
   from the top left. Static gradients rather than the Vault's WebGL flourish:
   this page is a form and a list, and 11 KB of shader to decorate a login is
   not a trade worth making twice. */
.backdrop {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  /* ⚠️ The accent layer is mixed down to 75%. At full strength the glow lifts
     the bed to #453a2f, where every --muted line on it lands on 4.52:1: a pass
     with no margin at all, on the one bed whose value depends on WHERE an
     element sits. At 75% the worst bed is #3e362e and the same lines read
     4.87:1. The wash is decoration; the text is the product, so the decoration
     is what gives way. */
  background:
    radial-gradient(120% 80% at 50% 108%, color-mix(in srgb, var(--accent-soft) 75%, transparent) 0%, transparent 62%),
    radial-gradient(90% 60% at 18% -10%, var(--hover-2) 0%, transparent 70%);
}

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

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 34px; padding: 0 14px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  font-size: var(--fs-body); font-weight: 600; letter-spacing: 0.005em;
  cursor: pointer; white-space: nowrap;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}
.btn:hover:not(:disabled):not([aria-disabled='true']) { background: var(--surface-2); border-color: var(--muted); }
.btn:active:not(:disabled) { background: var(--hover-2); }
/* Native :disabled is genuinely inert, unfocusable and exempt from contrast
   minimums, so alpha is honest there. aria-disabled is NOT: that control is
   still focusable and still answers with a reason when pressed, so it has to
   stay readable. Dimmer ink, never lower alpha. */
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn[aria-disabled='true'] {
  opacity: 1; cursor: not-allowed;
  color: var(--muted); background: var(--surface); border-color: var(--border-soft);
}
.btn svg { width: 16px; height: 16px; }

.btn.primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.btn.primary:hover:not(:disabled):not([aria-disabled='true']) {
  background: var(--accent-hover); border-color: var(--accent-hover);
  box-shadow: 0 4px 14px var(--accent-soft);
}
/* An aria-disabled PRIMARY must stop looking like the loudest thing on the
   page, or the reason it gives reads as a glitch rather than a state. It drops
   to the plain button's skin and keeps its full-strength ink. */
.btn.primary[aria-disabled='true'] {
  background: var(--surface); border-color: var(--border-soft); color: var(--muted);
  box-shadow: none;
}
.btn.danger { color: var(--error-ink); }
.btn.danger:hover:not(:disabled) { border-color: var(--error); background: var(--hover); }
/* The confirm button of a destructive dialog. Declared after .btn.primary so
   it wins the tie, or it would paint the amber fill with red ink on it. */
.btn.primary.danger {
  background: var(--error); border-color: var(--error); color: var(--danger-ink);
}
.btn.primary.danger:hover:not(:disabled) {
  background: var(--danger-hover);
  border-color: var(--danger-hover);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--error) 30%, transparent);
}
.btn.primary.danger[data-busy='1']::after { color: var(--danger-ink); }
.btn.block { width: 100%; }
.btn.lg { height: 42px; padding: 0 18px; }

/* Google's mark is fixed-colour, so the button around it stays neutral in both
   themes rather than tinting toward the accent. */
.btn.google svg { width: 17px; height: 17px; }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  background: transparent; border: 1px solid transparent; border-radius: var(--radius);
  color: var(--icon); cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.icon-btn:hover:not(:disabled):not([aria-disabled='true']) { background: var(--hover-2); color: var(--text-strong); }
.icon-btn svg { width: 17px; height: 17px; }
.icon-btn.sm { width: 28px; height: 28px; }
.icon-btn.sm svg { width: 16px; height: 16px; }

/* a button whose whole job is to spin while it works */
.btn[data-busy='1'] { position: relative; color: transparent; }
.btn[data-busy='1'] svg { visibility: hidden; }
.btn[data-busy='1']::after {
  content: ''; position: absolute; width: 15px; height: 15px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  color: var(--text);
  animation: spin 0.7s linear infinite;
}
.btn.primary[data-busy='1']::after { color: var(--accent-ink); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------- inputs */

.field { display: flex; flex-direction: column; gap: 7px; }
/* ⚠️ BOTH selectors. The sign-in password label lives inside .field-head so it
   can sit on a row with 'Forgot it?', and a child combinator missed it: that one
   label rendered 14px/400/none beside an 11px/700/uppercase one. A shipped
   inconsistency that no contrast or scale check can see, because both values are
   legal on their own. */
.field > label,
.field-head > label {
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--muted);
}
.field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.field-head a { font-size: var(--fs-meta); color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.field-head a:hover { color: var(--text-strong); }

.input {
  width: 100%; height: 42px; padding: 0 13px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  transition: border-color 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
/* ⚠️ --muted, not --faint. --faint on --surface measures 4.51:1 in the dark
   theme, which is this file own rule about where --faint may be used. */
.input::placeholder { color: var(--muted); }
.input:hover:not(:focus) { border-color: var(--muted); }
.input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input[aria-invalid='true'] { border-color: var(--error); }
.input[aria-invalid='true']:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 30%, transparent); }
.input.key { font-family: var(--mono); }

/* A password field with a reveal toggle parked inside its right edge. The
   padding is what stops a long password sliding under the button. */
.pw-wrap { position: relative; }
.pw-wrap .input { padding-right: 46px; }
.pw-toggle { position: absolute; right: 6px; top: 6px; }

.hint { font-size: var(--fs-meta); color: var(--muted); line-height: 1.5; }
.hint a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }

.err-line {
  display: flex; align-items: flex-start; gap: 6px;
  font-size: var(--fs-meta); color: var(--error-ink); line-height: 1.4;
}
.err-line svg { width: 14px; height: 14px; margin-top: 2px; }

/* ---- password strength: advice, never a gate ----
   Four segments and a word. It NEVER blocks the submit and it never turns the
   button off: the only hard rule is the length requirement below it, which is
   the server's rule restated, not a preference of this page. */
.pw-meter { display: flex; flex-direction: column; gap: 6px; }
.pw-bars { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.pw-bars i { height: 4px; border-radius: 2px; background: var(--well); }
.pw-bars i.on { background: var(--muted); }
.pw-meter[data-level='1'] .pw-bars i.on { background: var(--error); }
.pw-meter[data-level='2'] .pw-bars i.on { background: var(--warn); }
.pw-meter[data-level='3'] .pw-bars i.on { background: var(--accent); }
.pw-meter[data-level='4'] .pw-bars i.on { background: var(--ok); }
.pw-word { font-size: var(--fs-meta); color: var(--muted); }
.pw-word b { font-weight: 700; color: var(--text); }
/* Ink tokens, not the fill hues: --warn and --ok are fills and fail as text. */
.pw-meter[data-level='1'] .pw-word b { color: var(--error-ink); }
.pw-meter[data-level='2'] .pw-word b { color: var(--warn-ink); }
.pw-meter[data-level='4'] .pw-word b { color: var(--ok-ink); }

.reqs { display: flex; flex-direction: column; gap: 4px; }
.req { display: flex; align-items: center; gap: 7px; font-size: var(--fs-meta); color: var(--muted); }
/* Fixed box so swapping the bullet for a tick cannot shift the text sideways. */
.req .req-mark { display: flex; width: 14px; height: 14px; flex: none; }
.req svg { width: 14px; height: 14px; color: var(--muted); }
.req.met { color: var(--ok-ink); }
.req.met svg { color: var(--ok-ink); }

/* --------------------------------------------------------------- notices */

.notice {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 11px 13px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface);
  line-height: 1.45;
}
.notice svg { width: 17px; height: 17px; margin-top: 2px; flex: none; }
/* ⚠️ The tint is 9% because the INK has to survive it. A heavier wash pulls the
   composite toward the hue and takes --error-ink under 4.5:1 in the light
   theme; measured, both ways. */
.notice.error { border-color: var(--error); background: color-mix(in srgb, var(--error) 5%, var(--surface)); color: var(--error-ink); }
.notice.warn { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 5%, var(--surface)); color: var(--warn-ink); }
.notice strong { font-weight: 700; }
/* A notice that carries its own way forward: the duplicate-signup answer puts
   Sign in and Reset password inside it, because that is the whole reason the
   honest 409 is worth showing instead of a polite lie. */
.notice .notice-body { min-width: 0; }
.notice .notice-body p { line-height: 1.45; }
.notice .notice-body p + p { margin-top: 8px; color: var(--muted); }
.notice .card-acts { margin-top: 11px; }

/* The banner that rides above every page while an email is unverified. Same
   frame as the Vault's read-only banner so the two products warn the same way. */
/* Same measure and the same centring as .page-pad, or the banner and the cards
   it sits above would be on two different left edges. */
.top-banner {
  display: flex; align-items: flex-start; gap: 11px; flex-wrap: wrap;
  width: calc(100% - 40px); max-width: var(--page-max);
  margin: 16px auto 0;
  padding: 11px 14px;
  border: 1px solid var(--warn); border-radius: var(--radius);
  background: color-mix(in srgb, var(--warn) 8%, var(--bg));
  color: var(--warn-ink); line-height: 1.45;
}
.top-banner svg { width: 17px; height: 17px; margin-top: 2px; flex: none; }
.top-banner .banner-text { flex: 1 1 260px; min-width: 0; }
.top-banner b { font-weight: 700; }
.top-banner .btn { flex: none; }

/* ============================================================ auth screens */

.auth {
  position: relative; z-index: 1;
  height: 100%; overflow-y: auto;
  display: grid; grid-template-rows: 1fr auto;
  padding: 24px;
}
.auth-inner {
  align-self: center; justify-self: center;
  width: 100%; max-width: 420px;
  padding: 24px 0;
}
.lockup { display: flex; align-items: center; gap: 11px; margin-bottom: 30px; }
.lockup .mark { width: 40px; height: 36px; color: var(--accent); }
.lockup .words { display: flex; flex-direction: column; line-height: 1; }
.lockup .kicker {
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.3em; color: var(--muted);
  margin-bottom: 3px;
}
.lockup .product { font-size: var(--fs-title); font-weight: 700; letter-spacing: 0.06em; color: var(--text-strong); }

.auth h1 {
  font-size: var(--fs-hero); font-weight: 700; letter-spacing: -0.022em; line-height: 1.14;
  color: var(--text-strong); margin-bottom: 10px;
}
.auth .lede { color: var(--muted); font-size: var(--fs-lg); line-height: 1.5; margin-bottom: 28px; max-width: 36ch; }

.auth-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--sh-2);
  padding: 22px;
  display: flex; flex-direction: column; gap: 15px;
}
.auth-card .stack { display: flex; flex-direction: column; gap: 15px; }

.auth-foot {
  justify-self: center;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: center;
  padding-top: 20px;
  /* ⚠️ --muted, NOT --faint. This line sits on the .backdrop glow with no opaque
     ancestor, and --faint measures 4.1:1 there. The stylesheet already says
     --faint is for --bg, --sidebar and --well only; the glow is the fourth bed
     and it was not on that list until it failed here. */
  font-size: var(--fs-meta); color: var(--muted);
}
.auth-foot a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.auth-foot a:hover { color: var(--accent-text); }
.auth-foot .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: 0.6; }

/* The switch between sign in and create, directly under the card. It is a
   destination, not a footnote, so it gets body size and full ink. */
.auth-switch { margin-top: 16px; text-align: center; color: var(--muted); }
.auth-switch a { color: var(--accent-text); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }

.or {
  display: flex; align-items: center; gap: 12px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted);
}
.or::before, .or::after { content: ''; flex: 1 1 auto; height: 1px; background: var(--border); }

/* The terms line, last thing inside the card on both screens where an account
   can be created. Inside the card on purpose: it has to sit under the buttons
   it qualifies, and it has to be legible, so it goes on the opaque --surface
   bed rather than out on the .backdrop glow that cost .auth-foot its --faint.
   Meta size, but --muted rather than --faint for the same reason it reads as a
   term of the deal and not a footnote nobody was meant to finish. */
.auth-legal {
  margin-top: -3px; text-align: center; text-wrap: balance;
  font-size: var(--fs-meta); line-height: 1.45; color: var(--muted);
}
.auth-legal a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }

/* The result screens (check your email, link sent, verified, password changed)
   share one centred composition. */
.auth-result { text-align: center; }
.auth-result .glyph { width: 42px; height: 42px; margin: 0 auto 14px; color: var(--accent); }
.auth-result .glyph.good { color: var(--ok-ink); }
.auth-result .glyph.bad { color: var(--warn-ink); }
.auth-result h1 { margin-bottom: 12px; }
.auth-result p { color: var(--text); line-height: 1.55; }
.auth-result p + p { margin-top: 10px; }
.auth-result .who {
  display: inline-block; margin-top: 2px;
  font-weight: 700; color: var(--text-strong); overflow-wrap: anywhere;
}
.auth-result .acts { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; }

/* ============================================================== app shell */

.shell {
  position: relative; z-index: 1;
  height: 100%;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: 1fr;
}

/* ------------------------------------------------------------- sidebar */

.sidebar {
  grid-column: 1;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--sidebar);
  border-right: 1px solid var(--border-soft);
}
.sidebar-head {
  display: flex; align-items: center; gap: 10px;
  height: var(--header-h); padding: 0 16px; flex: none;
  border-bottom: 1px solid var(--border-soft);
}
.sidebar-head .mark { width: 26px; height: 24px; color: var(--accent); }
.sidebar-head .words { display: flex; flex-direction: column; line-height: 1; min-width: 0; }
.sidebar-head .kicker {
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.26em; color: var(--faint);
  margin-bottom: 3px; transform: scale(0.78); transform-origin: left center;
}
.sidebar-head .product { font-size: var(--fs-lg); font-weight: 700; letter-spacing: 0.05em; color: var(--text-strong); }

.sidebar-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 16px 10px 10px; }
.side-label {
  display: block; padding: 0 8px 8px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint);
}
.side-group + .side-group { margin-top: 22px; }

.side-link {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px; border-radius: var(--radius);
  border: 1px solid transparent; background: transparent; cursor: pointer;
  font-size: var(--fs-body); font-weight: 600; color: var(--text); text-align: left;
  transition: background 0.12s ease;
}
.side-link:hover { background: var(--hover); }
.side-link[aria-current='page'] { background: var(--hover-2); border-color: var(--border-soft); }
.side-link[aria-current='page'] svg { color: var(--accent); }
.side-link svg { width: 18px; height: 18px; color: var(--icon); }
.side-link .count {
  margin-left: auto; font-size: var(--fs-meta); font-weight: 600; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Who is signed in. It sits at the top of the nav because on a page whose whole
   subject is "your licenses", which account you are in is the first fact. */
.side-who {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 9px 13px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border-soft);
  min-width: 0;
}
.side-who svg { width: 18px; height: 18px; color: var(--icon); }
.side-who .who-body { min-width: 0; }
.side-who .who-mail {
  display: block; font-size: var(--fs-body); font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.side-who .who-state { display: block; font-size: var(--fs-meta); color: var(--faint); margin-top: 1px; }
.side-who .who-state.bad { color: var(--warn-ink); }

.sidebar-foot { flex: none; padding: 14px 16px 16px; border-top: 1px solid var(--border-soft); }
.side-appearance { margin-top: 4px; }
.side-appearance > span {
  display: block; margin-bottom: 6px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--faint);
}
.theme-seg {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px;
  padding: 2px; border-radius: var(--radius);
  background: var(--well); border: 1px solid var(--border-soft);
}
.theme-seg button {
  height: 26px; padding: 0 4px;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--muted); font-size: var(--fs-meta); font-weight: 600; cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.theme-seg button:hover { color: var(--text-strong); }
.theme-seg button[aria-pressed='true'] {
  background: var(--surface); color: var(--text-strong); box-shadow: var(--sh-1);
}
.sidebar-foot > .btn { margin-top: 14px; }

/* -------------------------------------------------------------- content */

.main { grid-column: 2; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.main:focus { outline: none; }

/* ⚠️ PHONE ONLY. Above the drawer breakpoint this bar held one thing, the page
   name, 63px above the same words at twice the size. Below it the bar earns its
   place: it carries the drawer toggle and is the only thing naming the page.
   Hidden rather than emptied, because a 60px empty strip is not a header. */
.topbar {
  /* ⚠️ ONE display declaration. The first version of this rule kept the
     original  further down the same block, which quietly won
     over the  added above it, and the bar carried on rendering
     at every width. The mobile query below is the only thing that turns it on. */
  display: none;
  flex: none; min-height: var(--header-h);
  align-items: center; gap: 12px;
  padding: 0 20px;
  border-bottom: 1px solid var(--border-soft);
  background: var(--bg);
}
.nav-toggle { display: none; }
.topbar .where {
  font-size: var(--fs-body); font-weight: 600; color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1 1 auto;
}
.topbar-actions { display: flex; align-items: center; gap: 8px; flex: none; }

.page-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; }
/* Centred, not left-anchored. The column is capped at a readable measure, and
   at 1400px a left-anchored cap leaves a third of the window as dead space on
   one side only, which reads as a layout that failed rather than one that chose.
   The sidebar is the app frame; the page is a document inside it. */
.page-pad {
  padding: 20px 20px 48px; flex: 0 0 auto;
  width: 100%; max-width: calc(var(--page-max) + 40px);
  margin-inline: auto;
}
/* Empty and error panels get the whole area and sit optically centred in it,
   rather than parked a quarter of the way down a 700px void. */
.page-pad.center {
  flex: 1 1 auto; max-width: none;
  display: flex; align-items: center; justify-content: center;
  padding-bottom: 8vh;
}

.page-head { margin: 0 0 18px; }
.page-head h1 {
  font-size: var(--fs-title); font-weight: 700; letter-spacing: -0.018em; line-height: 1.2;
  color: var(--text-strong);
  overflow-wrap: anywhere;
}
.page-head .page-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 5px; color: var(--muted);
}
.page-head .page-meta .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: 0.6; }
.page-head > p { color: var(--muted); margin-top: 7px; max-width: 62ch; line-height: 1.55; }

/* ----------------------------------------------------------------- cards */

.cards { display: flex; flex-direction: column; gap: 14px; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--sh-1);
}
.card-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 15px 16px 0;
}
.card-head .card-title { min-width: 0; flex: 1 1 auto; }
.card-head h2 {
  font-size: var(--fs-lg); font-weight: 700; color: var(--text-strong); letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}
.card-head .sub { display: block; font-size: var(--fs-meta); color: var(--muted); margin-top: 3px; }
.card-head .card-icon { width: 20px; height: 20px; color: var(--accent); margin-top: 2px; }
.card-body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 13px; }

/* A refunded, revoked or expired key. Its controls stay usable on purpose: the
   key is still the reference on a receipt and in a support email, so hiding it
   would be hiding the one thing that identifies the purchase. But the card must
   not look like a working licence with a small badge on it.
   ⚠️ Quieted with a RECESSED FILL and weaker ink, never with opacity on the
   subtree: alpha composites every child toward the background, so the colour
   declared stops being the colour measured and the whole card fails contrast
   silently. The well runs the right way in both themes, darker in dark and
   greyer in light. */
.card.is-void { background: var(--well); box-shadow: none; }
.card.is-void .card-head h2 { color: var(--text); }
.card.is-void .card-head .card-icon { color: var(--muted); }
.card.is-void .keyline { background: transparent; }
.void-note { color: var(--muted); line-height: 1.5; }
.card-body > p { color: var(--text); line-height: 1.55; }
.card-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 16px; border-top: 1px solid var(--border-soft);
  /* ⚠️ A well, not --surface-2. --surface-2 is the LIGHTEST fill in the dark
     theme, and --muted on it measures 4.13:1. The well runs the other way in
     each theme (darker in dark, lighter in light) and reads 6.4:1 in both. */
  background: var(--well); border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  font-size: var(--fs-meta); color: var(--muted);
}
.card-foot .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: 0.6; }
.card-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ---- the license key line ----
   The key sits in a well so the mono string reads as a value rather than as
   prose, and so the row's hover has somewhere to go. */
.keyline {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 8px 8px 12px; border-radius: var(--radius);
  background: var(--well); border: 1px solid var(--border-soft);
  min-width: 0;
}
.keyline .keyval {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--mono); font-size: var(--fs-body); color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  letter-spacing: 0.01em;
}
/* Masked, so the dots are clearly not the key. */
.keyline .keyval.masked { color: var(--muted); }
/* Revealed, it WRAPS instead of truncating. A 36-character key at body size is
   about 300px of monospace and the well is ~250px wide on a phone once the two
   icon buttons have taken theirs, so ellipsis would hide the end of the very
   thing the button was pressed to see. Copy still exists; being able to read it
   is the point. */
.keyline .keyval:not(.masked) {
  white-space: normal; overflow: visible; overflow-wrap: anywhere;
}

.tag {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  height: 21px; padding: 0 7px; border-radius: 3px;
  border: 1px solid var(--border); background: var(--surface);
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
}
.tag.ok { border-color: var(--ok); color: var(--ok-ink); }
.tag.warn { border-color: var(--warn); color: var(--warn-ink); }
.tag.bad { border-color: var(--error); color: var(--error-ink); }

/* ---- a disclosure (computers using this key) ---- */
.disclose {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 10px; border-radius: var(--radius);
  background: transparent; border: 1px solid var(--border-soft); cursor: pointer;
  font-size: var(--fs-body); font-weight: 600; color: var(--text); text-align: left;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.disclose:hover { background: var(--hover); border-color: var(--border); }
.disclose svg.chev { width: 15px; height: 15px; color: var(--icon); transition: transform 0.14s ease; }
.disclose[aria-expanded='true'] svg.chev { transform: rotate(90deg); }
.disclose .n { margin-left: auto; font-size: var(--fs-meta); color: var(--muted); font-variant-numeric: tabular-nums; }
/* ⚠️ The hover overlay lifts the card fill to #3d3d3d, where --muted is 4.46:1.
   Hover strengthens the ink rather than leaving it to be read through a wash. */
.disclose:hover .n { color: var(--text); }
.disclose-panel { margin-top: 10px; }

/* ---- rows in a well ----
   ⚠️ These hover, so they must NOT sit directly on --surface: --faint and
   --muted both lose too much when the hover overlay lifts a card fill. The
   well is darker in dark and lighter in light, and both directions leave the
   ink room. */
.well {
  border-radius: var(--radius); overflow: hidden;
  background: var(--well); border: 1px solid var(--border-soft);
}
.wrow {
  display: flex; align-items: center; gap: 12px;
  min-height: var(--row-h); padding: 9px 12px;
  border-bottom: 1px solid var(--border-soft);
}
.wrow:last-child { border-bottom: none; }
.wrow:hover { background: var(--hover); }
.wrow .wrow-icon { width: 18px; height: 18px; color: var(--icon); flex: none; }
.wrow .wrow-body { min-width: 0; flex: 1 1 auto; }
.wrow .wrow-name {
  display: block; font-size: var(--fs-body); font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wrow .wrow-sub {
  display: block; font-size: var(--fs-meta); color: var(--faint); margin-top: 1px;
  overflow-wrap: anywhere;
}
.wrow .wrow-acts { display: flex; align-items: center; gap: 8px; flex: none; }

.well-note {
  padding: 10px 12px; font-size: var(--fs-meta); color: var(--muted); line-height: 1.5;
}

/* ------------------------------------------------------------- downloads */

.dl-primary { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.dl-primary .btn { flex: none; }
.dl-primary .dl-what { min-width: 0; flex: 1 1 220px; }
.dl-primary .dl-what b { display: block; font-weight: 600; color: var(--text-strong); overflow-wrap: anywhere; }
.dl-primary .dl-what span { display: block; font-size: var(--fs-meta); color: var(--muted); margin-top: 2px; }

/* --------------------------------------------------------------- plans */

.plan-price { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.plan-price b { font-size: var(--fs-title); font-weight: 700; color: var(--text-strong); letter-spacing: -0.01em; }
.plan-price span { font-size: var(--fs-meta); color: var(--muted); }
.plan-list { display: flex; flex-direction: column; gap: 6px; }
.plan-list li { display: flex; align-items: flex-start; gap: 8px; color: var(--text); line-height: 1.45; }
.plan-list li svg { width: 15px; height: 15px; color: var(--ok-ink); margin-top: 3px; }

/* ------------------------------------------------------- empty / states */

.empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 10px;
  padding: 24px;
  max-width: 48ch;
}
.empty .glyph { width: 44px; height: 44px; color: var(--faint); opacity: 0.55; margin-bottom: 4px; }
.empty h2 { font-size: var(--fs-lg); font-weight: 700; color: var(--text-strong); letter-spacing: -0.01em; }
.empty p { color: var(--muted); line-height: 1.55; }
.empty .btn { margin-top: 12px; }

/* A failure is not an absence. Amber frame, amber glyph, the retry as the
   loudest thing in the panel, and the code plus a way to reach a human. */
.empty.failure {
  max-width: 52ch;
  padding: 30px 32px 32px;
  border: 1px solid var(--warn); border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--warn) 7%, var(--surface));
  box-shadow: var(--sh-1);
}
.empty.failure .glyph { color: var(--warn); opacity: 1; }
.empty.failure h2 { color: var(--text-strong); }
.empty.failure p { color: var(--text); }
.fail-foot {
  display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap;
  margin-top: 14px; font-size: var(--fs-meta); color: var(--muted);
}
.fail-code {
  font-family: var(--mono); font-size: var(--fs-meta);
  padding: 2px 7px; border-radius: var(--radius-sm);
  background: var(--well); border: 1px solid var(--border-soft); color: var(--muted);
}
.fail-foot a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }
/* ⚠️ The amber wash lightens --surface enough that --muted (4.34:1) and the
   amber link (4.07:1) both fail in the dark theme. The wash is the panel's
   whole signal, so the INK moves instead of the fill. */
.empty.failure .fail-foot { color: var(--text); }
.empty.failure .fail-foot a { color: var(--text-strong); }

/* The claim box on the empty state: the first thing someone with an old key
   needs, so it is a card in the middle of the page rather than a link. */
.claim-panel { width: 100%; max-width: 460px; }
.claim-panel .empty { max-width: none; padding: 0 0 4px; }
.claim-form { display: flex; flex-direction: column; gap: 10px; }
.claim-row { display: flex; gap: 8px; }
.claim-row .input { flex: 1 1 auto; min-width: 0; }

.sk {
  height: 10px; border-radius: 3px;
  background: linear-gradient(90deg, var(--hover) 25%, var(--hover-2) 37%, var(--hover) 63%);
  background-size: 300% 100%;
  animation: sheen 1.4s ease-in-out infinite;
}
.sk.icon { width: 18px; height: 18px; border-radius: var(--radius-sm); flex: none; }
@keyframes sheen { 0% { background-position: 130% 0; } 100% { background-position: -30% 0; } }
/* A skeleton card is the shape of the card that replaces it: title bar, a key
   line, a disclosure. Seven identical bars read as a loading graphic; a shape
   reads as a card arriving. */
.sk-card { padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.sk-line { display: flex; align-items: center; gap: 11px; }
.sk-key { height: 40px; border-radius: var(--radius); }
.sk-strip { height: 36px; border-radius: var(--radius); }

/* ---------------------------------------------------------------- modal */

.scrim {
  position: fixed; inset: 0; z-index: 90;
  background: var(--scrim);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  animation: fade 0.14s ease;
}
@keyframes fade { from { opacity: 0; } }
.modal {
  width: 100%; max-width: 470px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--sh-3);
  animation: rise 0.16s ease;
  max-height: calc(100vh - 48px); overflow-y: auto;
}
@keyframes rise { from { opacity: 0; transform: translateY(8px) scale(0.985); } }
.modal-head { display: flex; align-items: center; gap: 12px; padding: 16px 16px 12px; }
.modal-head h2 { font-size: var(--fs-lg); font-weight: 700; color: var(--text-strong); flex: 1 1 auto; }
.modal-body { padding: 0 16px 16px; display: flex; flex-direction: column; gap: 14px; }
.modal-body p { line-height: 1.55; }
.modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 13px 16px; border-top: 1px solid var(--border-soft);
  background: var(--well); border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.modal-foot .spacer { flex: 1 1 auto; }

.subject {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: var(--radius);
  background: var(--well); border: 1px solid var(--border-soft);
  min-width: 0;
}
.subject svg { width: 18px; height: 18px; color: var(--accent); }
.subject div { min-width: 0; }
.subject b { display: block; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.subject span { display: block; font-size: var(--fs-meta); color: var(--muted); overflow-wrap: anywhere; }

/* --------------------------------------------------------------- toasts */

/* ⚠️ BOTTOM RIGHT, not bottom left. Bottom left is where the sidebar foot puts
   Sign out, and a two-line toast covered 84 to 98% of that button: measured with
   elementFromPoint, the toast was what a click landed on. Nothing persistent
   lives bottom right. */
.toasts {
  position: fixed; right: 20px; bottom: 20px; z-index: 120;
  display: flex; flex-direction: column-reverse; align-items: flex-end; gap: 8px;
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 9px;
  padding: 10px 14px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: var(--sh-2); font-weight: 500;
  animation: toastin 0.18s ease;
  /* ⚠️ NONE. A toast with no dismiss control has nothing to click, so accepting
     a click only means stealing one from whatever it is covering. Belt to the
     bottom-right braces above. */
  pointer-events: none;
  max-width: min(460px, calc(100vw - 40px));
}
.toast svg { width: 16px; height: 16px; color: var(--ok-ink); }
.toast.bad { border-color: var(--error); }
.toast.bad svg { color: var(--error-ink); }
@keyframes toastin { from { opacity: 0; transform: translateY(8px); } }

/* ------------------------------------------------------------ responsive */

@media (max-width: 760px) {
  :root { --sidebar-w: 0px; --header-h: 56px; }

  .shell { grid-template-columns: 1fr; }

  /* The sidebar becomes a drawer. It keeps its DOM position so focus order
     stays sane, and the scrim is a sibling button so a tap outside closes it. */
  .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 100;
    width: min(304px, 86vw);
    transform: translateX(-102%);
    transition: transform 0.2s ease;
    box-shadow: var(--sh-3);
  }
  body.nav-open .sidebar { transform: none; }
  .nav-scrim {
    position: fixed; inset: 0; z-index: 95;
    background: var(--scrim); border: none; padding: 0; cursor: pointer;
    animation: fade 0.16s ease;
  }
  .main { grid-column: 1; }
  .nav-toggle { display: inline-flex; }

  /* Below the drawer breakpoint the bar is the app bar: it carries the drawer
     toggle and is the only thing naming the page. */
  .topbar { display: flex; padding: 0 12px; gap: 8px; }
  .page-pad { padding: 14px 12px 40px; }
  .top-banner { margin: 12px 12px 0; }

  .auth { padding: 16px; }
  .auth h1 { font-size: var(--fs-title); }
  .auth .lede { font-size: var(--fs-body); }
  .auth-card { padding: 18px; }

  .card-head { padding: 14px 13px 0; }
  .card-body { padding: 13px 13px 14px; }
  .card-foot { padding: 10px 13px; }
  .wrow { padding: 10px; gap: 10px; flex-wrap: wrap; }
  .wrow .wrow-body { flex: 1 1 100%; order: 1; }
  .wrow .wrow-icon { order: 0; }
  .wrow .wrow-acts { order: 2; margin-left: auto; }

  /* Stacked, because a 96px input beside a button is not a place to type a
     36-character key. */
  .claim-row { flex-direction: column; }
  .claim-row .btn { width: 100%; }

  .toasts { left: 10px; right: 10px; }
}

/* --------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .sk { background: var(--hover); }
}
