/*
 * ChatDrift.Fun design system. Tokens, base styles and the shared components,
 * loaded by every page (the site, /guidelines and /admin). Page layout lives in each
 * page's own stylesheet. docs/brand.md explains every value and tests/design.test.ts
 * enforces the promises (contrast in both themes, no coloured shadows, three font
 * weights, no blur).
 *
 * THE IDEA. Two chat bubbles drift together — an angel and a devil, the site's own
 * game — and meet. The logo is playful; the interface stays calm, for ADHD and
 * autistic visitors first: neutrals, and ONE colour.
 *   Night  (neutrals)      the logo's navy ink; chrome recedes, faces stay the
 *                          brightest thing on screen. Mist is the daytime face,
 *                          with the wordmark's ink for text.
 *   Blue   (--accent)      the logo's blue, the one colour, and it means one thing:
 *                          "you can press this" (Next, Start, Send). Its quiet tint
 *                          marks your own chat messages and what is selected. The
 *                          match ring and the unread badge are the logo's sky blue.
 *   Status (--ok/--warn/--danger) state only, always with an icon and a word. Red is
 *                          for Report, real errors and bans, never routine events.
 *   No orange, no second accent: the logo's gradients, violet and pink live in the
 *   logo only. On buttons and messages a second colour asks to be decoded.
 *
 * COMPONENT API (class names are the contract; pages add layout, not new looks)
 *   Buttons       <button> is the secondary button: a 44px pill.
 *                 Variants  .primary (one per screen) · .ghost · .danger · .danger-solid
 *                           · .link (the one fix inside a notice; still a 44px target)
 *                 Sizes     .lg (52px: gate/lobby primary, Next on phones)
 *                           .sm (36px, fine pointers only; stays 44px on touch)
 *                 <a class="btn …"> is a link that looks like a button.
 *                 Disabled: use aria-disabled="true" for a locked call to action (it
 *                 still takes focus and can explain itself; link the reason with
 *                 aria-describedby), `disabled` otherwise. A disabled .primary is
 *                 "primed" (soft blue fill, blue label), never faded.
 *                 Loading: aria-busy="true", swap the leading icon for .spinner.
 *   Icon buttons  .icon-btn: a 44px circle; combine with the variants. Always an
 *                 aria-label, plus a title on hover devices.
 *   Fields        select, textarea, input (text/search/password/number/email/url/tel):
 *                 16px text, 44px tall (48 on touch). label.field above, .hint or
 *                 .field-error below; aria-invalid="true" marks the error.
 *   Choice row    <label class="consent"><input type="checkbox"><span>…</span></label>
 *                 (or type="radio"; .choice is the same component). The whole row is
 *                 the target. Never put a link inside the label.
 *   Cards         .card has no padding so media can bleed; .card-body pads it
 *                 (24px, 20 on phones); .card > header is a small card header.
 *   Notices       .notice + .ok / .warn / .error / .debug. A 20px .i first, then the
 *                 text, then at most one button (a button.link reads best). To put
 *                 the button under the words, wrap words and button in one element.
 *                 .error takes role="alert". .debug is operator-only (?debug=1) and
 *                 never shows to visitors.
 *   Small things  .tag (label badge; .danger/.warn/.ok), .pill + .dot (status, with
 *                 data-state), .chip (dark glass over video, both themes), .badge
 *                 (sky-blue unread count), kbd, .spinner (.sm/.lg; pair it with text).
 *   Overlays      <dialog> + .modal-body > .modal-head / .modal-actions;
 *                 .modal-icon.danger on Report only. On phones a
 *                 dialog holding a .modal-body becomes a bottom sheet.
 *                 .toasts is the stack (the page positions it); .toast + data-tone.
 *   Chrome        .appbar: the solid header. .brand + .brand-logo.
 *                 .skip-link, .sr-only, .spacer.
 *   Type          h1 = title, h2 = heading, h3 = sub-heading, on the --t-* scale.
 *                 .lede (one per screen), .eyebrow, .muted, .small, .hint,
 *                 .mono (admin only). A heading focused by script shows no ring.
 *   Icons         <svg class="i"> 20px; .sm 16, .md 24 (call toolbar), .lg 28.
 *   Theme art     .for-dark / .for-light show only in their theme.
 *   Over video    .on-video on a container gives its focus rings a dark inner band.
 *
 * RULES THAT KEEP IT WORKING
 *   - [hidden] always wins (below). Toggle el.hidden, never style.display.
 *   - Colours are roles. A component never names a hex; the dark literals left in
 *     page CSS are the glass over video, which stays dark in both themes.
 *   - No coloured shadows, no glow, no gradient on anything with words.
 *   - No backdrop-filter anywhere: the video glass is legible without blur (7.9:1
 *     over a white frame) and blur over live video is the costliest paint on phones.
 *   - Never a transform, filter, contain or container-type on .screen, #call, main or
 *     body: each makes the element the containing block of the phone's fixed chat
 *     sheet.
 */

/* ============================================================ tokens: night */

:root {
  color-scheme: dark;
  accent-color: var(--accent); /* native checkboxes, radios and ranges */

  /* Ground and depth, darkest to lightest. The ground is the logo's navy ink (its
     app tile), and every surface keeps its hue. */
  --bg: #10122e;
  --surface: #161a3a;
  --surface-2: #1e2247;
  --surface-3: #282d57;
  --field: #10122e; /* input fill */
  --raised: #1e2247; /* elevation 2: toasts, self-view frame, phone action bar */

  /* Lines. Only --line-strong is a control boundary (3:1 or better). */
  --line-soft: #1f2349; /* dividers inside a card */
  --line: #2f3563; /* card and dialog edges (decorative) */
  --line-strong: #7980ad; /* inputs, checkboxes, secondary buttons */

  /* Text: every one AA on every surface. --text-faint is for one-line hints. */
  --text: #eceefa;
  --text-dim: #bcc0de;
  --text-faint: #959ac4;

  /* The logo's sky blue (the angel, the start of "Fun"), the same in both themes:
     the match ring and the unread badge (navy digits on it, 9.1:1). */
  --brand: #4fc2ff;
  --on-brand: #10122e;

  /* The sky blue as the one "you can press this" colour, with navy labels (9.1:1). */
  --accent: #4fc2ff;
  --accent-hover: #72cfff;
  --accent-press: #35b0f2;
  --on-accent: #10122e;
  --accent-text: #7dd3ff; /* links, and the primed button's label */
  --accent-soft: #172f52; /* primed fill, selected rows */
  --accent-line: #2a5687; /* the primed button's edge */
  --focus: #7dd3ff;
  /* The checked box's tick, in the label colour (an SVG cannot read vars). */
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M4.5 9.5l3 3 6-6.5' fill='none' stroke='%2310122e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* The chat: your messages in a quiet blue tint, theirs neutral, both with the
     reading colour. Tinted, never solid: a column of bright blocks pulls the eye
     off the face and competes with Next. */
  --you-bubble: #1b3558;
  --on-you-bubble: #eceefa;
  --them-bubble: #282d57;

  /* Status. Tints carry --text; the icon carries the tone. */
  --ok: #5fd68e;
  --ok-bg: #15303a;
  --ok-line: #23484a;
  --warn: #f3c451;
  --warn-bg: #2c2a36;
  --warn-line: #52462d;
  --danger: #ff8080;
  --danger-bg: #35203b;
  --danger-line: #5b2d3f;
  --danger-solid: #d13440;
  --danger-solid-hover: #b82a35;
  --on-danger: #ffffff;

  --data-2: #a494ff; /* admin charts only; never a button, never status */

  /* Angel vs. Devil's two ends (game.css), over video only, the same in both themes.
     Sky for the Angel end — paler than the button blue, and only ever an edge or a
     fill on the game's own chips; the 😇 carries the gold. Rose for the Devil end,
     where the game's own ban is: never on anything outside the game. */
  --angel: #9ccfff;
  --devil: #ff7a90;

  /* Colour with transparency. The video glass and the ring gap are the same in
     both themes: video is dark whatever the page is. */
  --video-glass: rgba(16, 18, 46, 0.72);
  --on-video: #ffffff;
  --video-danger: #ff8080; /* red that reads on the video glass (3.5:1 at worst) */
  --ring-gap: #10122e;
  --media-ground: #000; /* behind a video frame (admin panes), both themes */
  --backdrop: rgba(7, 8, 24, 0.72);
  --fade-to: rgba(16, 18, 46, 0); /* the fade behind a sticky action bar */

  /* A 20px select chevron in --text-dim (one per theme: an SVG cannot read vars). */
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5.5 8l4.5 4.5L14.5 8' fill='none' stroke='%23bcc0de' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* ------------------------------------------------------------ type */
  /* System faces only: no font request (a privacy promise, and nothing between the
     tap and the camera). The emoji faces are last because strangers type emoji. */
  --font-ui:
    system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI", Roboto,
    "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji";
  --font-display:
    "Segoe UI Variable Display", system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono:
    ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;

  /* Seven steps, in rem so they follow the browser's text-size setting. Weights are
     400, 600 and 700 only: Segoe UI and static Roboto collapse everything between. */
  --t-display: clamp(1.875rem, 3vw + 1.1875rem, 3.25rem); /* 30–52, gate only */
  --t-title: clamp(1.5rem, 1.2vw + 1.1875rem, 2rem); /* 24–32, screen titles */
  --t-heading: 1.25rem; /* 20: card, dialog, section titles */
  --t-lede: 1.125rem; /* 18: one per screen */
  --t-body: 1rem; /* 16: body, buttons, inputs, chat */
  --t-small: 0.875rem; /* 14: hints, notices; the floor for reading text */
  --t-micro: 0.75rem; /* 12: badges, kbd, timer, table heads. Never sentences. */

  /* ----------------------------------------------------------- space */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-10: 40px;
  --sp-12: 48px;
  --sp-16: 64px;

  /* ---------------------------------------------------------- radius */
  /* Soft like the logo's round bubbles: corners about 40% of the height on the big
     shapes. */
  --r-xs: 6px; /* kbd, tags, checkbox, meter, a chat bubble's tail corner */
  --r-sm: 10px; /* inputs, selects, self-view, thumbnails */
  --r-md: 14px; /* notices, toasts, choice rows */
  --r-lg: 20px; /* cards, dialogs, stage, sheet tops, chat bubbles */
  --r-full: 999px; /* every button, chip, badge and the chat composer */

  /* ------------------------------------------------------------ size */
  --tap: 44px; /* the minimum target, everywhere */
  --appbar-h: 52px; /* 56 from 600px wide; add the safe area on top */

  /* Page-level layers, lowest first. Values of 1-5 inside a component (a badge
     over its video) stay local; anything that overlaps across the page takes a
     step here. An unplanned 60 once put a toast on the phone's message box. */
  --z-sticky: 20; /* the header, and the offline banner just under it */
  --z-sheet: 30; /* the phone chat sheet */
  --z-toolbar: 31; /* the call toolbar, over the sheet's edge */
  --z-banner: 40; /* the offline banner during a call */
  --z-toast: 60;
  --z-skip: 100; /* the skip link, over everything once focused */
  --z-debug: 999; /* ?debug=1 only */

  /* ------------------------------------------------------- elevation */
  /* Neutral shadows only. The primary button's highlight is white, not blue. */
  --shadow-1: inset 0 1px 0 rgba(255, 255, 255, 0.03);
  --shadow-2: 0 8px 24px rgba(0, 0, 0, 0.5);
  --shadow-3: 0 24px 64px rgba(0, 0, 0, 0.6);
  --shadow-primary: inset 0 1px 0 rgba(255, 255, 255, 0.16);
  --sheet-shadow: 0 -16px 48px rgba(0, 0, 0, 0.55);

  /* ---------------------------------------------------------- motion */
  /* Drift, not bounce. Opacity and transform only, plus colour on hover. */
  --ease-drift: cubic-bezier(0.2, 0.7, 0.3, 1); /* entering and moving */
  --ease-exit: cubic-bezier(0.4, 0, 1, 1);
  --dur-1: 90ms; /* press */
  --dur-2: 150ms; /* hover, colour, focus */
  --dur-3: 240ms; /* dialog, toast, sheet, disclosure */
  --dur-4: 360ms; /* screen change */
  --dur-match: 1200ms; /* the sky-blue ring over a new face, once per call */
  --dur-loop: 3200ms; /* the search's two bubbles meeting: the site's one loop */
  --loops: 3; /* how many times it plays before it rests (about 10 s) */
  --rise: 8px;
}

/* ============================================================= tokens: mist */

/*
 * Light theme: the same roles on a pale mist, with the wordmark's navy ink for
 * text. Chosen by /theme.js before first paint (the saved choice, else the system
 * setting) and set as data-theme on <html>. The logo's sky blue, the solid danger
 * fill and the video glass are shared with night. The button blue is the logo's
 * deeper wing blue here, with white labels: the sky blue on mist is 1.7:1, and a
 * primary's edge must reach 3:1.
 */
:root[data-theme="light"] {
  color-scheme: light;

  --bg: #eceef6;
  --surface: #ffffff;
  --surface-2: #f4f5fa;
  --surface-3: #e1e4f0;
  --field: #ffffff;
  --raised: #ffffff;

  --line-soft: #dfe2ee;
  --line: #cdd1e3;
  --line-strong: #6c7397;

  --text: #1b1d4a;
  --text-dim: #3b3f6c;
  --text-faint: #575d87;

  --accent: #2a67e8;
  --accent-hover: #2459d2;
  --accent-press: #1f4db8;
  --on-accent: #ffffff;
  --accent-text: #2357c9;
  --accent-soft: #e3ebfd;
  --accent-line: #b0c8f6;
  --focus: #2357c9;
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M4.5 9.5l3 3 6-6.5' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  --you-bubble: #dbe6fc;
  --on-you-bubble: #1b1d4a;
  --them-bubble: #e5e7f2;

  --ok: #16753f;
  --ok-bg: #e6f1ec;
  --ok-line: #bfdccb;
  --warn: #855400;
  --warn-bg: #f3eee4;
  --warn-line: #e2d0ae;
  --danger: #c0262f;
  --danger-bg: #faeeee;
  --danger-line: #f1c5c8;

  --data-2: #5a43d6;

  --backdrop: rgba(27, 29, 74, 0.4);
  --fade-to: rgba(236, 238, 246, 0);

  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5.5 8l4.5 4.5L14.5 8' fill='none' stroke='%233b3f6c' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  --shadow-1: 0 1px 2px rgba(27, 29, 74, 0.06);
  --shadow-2: 0 8px 24px rgba(27, 29, 74, 0.1);
  --shadow-3: 0 24px 64px rgba(27, 29, 74, 0.16);
  --sheet-shadow: 0 -12px 32px rgba(27, 29, 74, 0.12);
}

@media (min-width: 37.5rem) {
  :root {
    --appbar-h: 56px;
  }
}

/* Theme-specific artwork. !important for the same reason as [hidden]: component
   rules set display too. */
:root[data-theme="light"] .for-dark,
:root:not([data-theme="light"]) .for-light {
  display: none !important;
}

/* ===================================================================== base */

* {
  box-sizing: border-box;
}

/*
 * The `hidden` attribute is only a UA rule of `display: none`, so any explicit
 * `display` in a stylesheet silently beats it — an element set to `display: grid`
 * stays visible no matter how many times you set `el.hidden = true`. Several
 * components here are grid or flex, so make the attribute authoritative once.
 */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* No fixed height here: a body one viewport tall is the containing block of every
   sticky header in it, and /guidelines' header scrolled away after one screen. The
   call screen locks its own height in chat.css. */
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 var(--t-body) / 1.5 var(--font-ui);
}

/* Light text on a dark ground renders heavy with subpixel smoothing (macOS only). */
:root:not([data-theme="light"]) body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3 {
  margin: 0;
  color: inherit;
  font-family: var(--font-display);
  font-weight: 700;
  text-wrap: balance;
}
h1 {
  font-size: var(--t-title);
  line-height: 1.15;
  letter-spacing: -0.02em;
}
h2 {
  font-size: var(--t-heading);
  line-height: 1.3;
  letter-spacing: -0.01em;
}
/* A screen's own title, where the page's one h1 belongs to another screen
   (the chat page: the gate has it, and search engines count every h1 in the
   source, hidden screens included). Looks exactly like an h1. */
.screen-title {
  font-size: var(--t-title);
  line-height: 1.15;
  letter-spacing: -0.02em;
}
h3 {
  font-size: var(--t-lede);
  font-weight: 600;
  line-height: 1.35;
}

p {
  margin: 0;
}

strong {
  font-weight: 600;
}

small {
  font-size: var(--t-small);
}

a {
  color: var(--accent-text);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
@media (hover: hover) {
  a:hover {
    text-decoration-thickness: 2px;
  }
}

/* Focus is always visible and always the same: 2px, 2px away, never removed. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Over a bright video frame the ring alone can drop to about 2:1; a dark band
   under it keeps it visible on anything. */
.on-video :focus-visible,
.on-video:focus-visible {
  box-shadow: 0 0 0 2px var(--ring-gap);
}

::placeholder {
  color: var(--text-faint);
  opacity: 1;
}

.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;
}

.spacer {
  flex: 1;
}

/* Hidden until focused, then the first thing on the page. */
.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: var(--z-skip);
  padding: 11px 20px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-200%);
}
.skip-link:focus {
  transform: none;
}

/* =============================================================== typography */

/* A heading given focus by script (a new view, a dialog) is not a control: no ring,
   on every page alike. */
:is(h1, h2, h3)[tabindex="-1"]:focus {
  outline: none;
}

.lede {
  max-width: 45ch;
  color: var(--text-dim);
  font-size: var(--t-lede);
  line-height: 1.5;
  text-wrap: pretty;
}
.small {
  font-size: var(--t-small);
  line-height: 1.45;
}
.eyebrow {
  color: var(--text-dim);
  font: 600 var(--t-small) / 1.45 var(--font-ui);
}
.muted {
  color: var(--text-dim);
}
/* One line of help under a control. Faint is for single lines only. */
.hint {
  color: var(--text-faint);
  font-size: var(--t-small);
  line-height: 1.45;
}
/* Monospace is an operator's face: admin tables and the debug notice only. */
.mono {
  font-family: var(--font-mono);
}
.empty {
  padding: 24px 16px;
  color: var(--text-faint);
  font-size: var(--t-small);
  text-align: center;
}

/* ==================================================================== icons */

/* One stroke on a 24px grid: 1.67px at 20px, 2px at 24px. Outline only. */
.i {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.i.sm {
  width: 16px;
  height: 16px;
}
.i.md {
  width: 24px;
  height: 24px;
}
.i.lg {
  width: 28px;
  height: 28px;
}

/* =================================================================== chrome */

/* The header: solid ground and a hairline. Nothing scrolls under glass. */
.appbar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-height: calc(var(--appbar-h) + env(safe-area-inset-top, 0px));
  padding: env(safe-area-inset-top, 0px)
    max(16px, env(safe-area-inset-right, 0px)) 0
    max(16px, env(safe-area-inset-left, 0px));
  background: var(--bg);
  border-bottom: 1px solid var(--line-soft);
}
@media (min-width: 37.5rem) {
  .appbar {
    padding-right: max(24px, env(safe-area-inset-right, 0px));
    padding-left: max(24px, env(safe-area-inset-left, 0px));
  }
}

/* The home link around the lockup: a full target, a rounded focus ring. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  border-radius: var(--r-xs);
  color: var(--text);
  font: 700 var(--t-body) / 1.25 var(--font-display);
  letter-spacing: -0.01em;
  text-decoration: none;
}
/* Height drives the size; the width follows, so the lockup and the mark alone share
   one rule. 32px keeps the name's capitals 12px tall on a phone; 36 from 600px. */
.brand-logo {
  display: block;
  width: auto;
  height: 32px;
}
@media (min-width: 37.5rem) {
  .brand-logo {
    height: 36px;
  }
}

/* ================================================================== buttons */

/*
 * Every button is a pill. Heights come from padding around a 20px line (44 / 52 /
 * 36px with the border), never a fixed height, so a label that wraps or grows with
 * the text-size setting stays inside its button.
 */
button,
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 11px 20px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--text);
  font: 600 var(--t-body) / 1.25 var(--font-ui);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--dur-2) var(--ease-drift),
    border-color var(--dur-2) var(--ease-drift),
    color var(--dur-2) var(--ease-drift),
    transform var(--dur-1) var(--ease-drift);
}

button.lg,
.btn.lg {
  padding: 15px 28px;
}
/* The small size is for a mouse. On touch it stays a full 44px target. */
@media (pointer: fine) {
  button.sm,
  .btn.sm {
    padding: 8px 14px;
    font-size: var(--t-small);
  }
}

.icon-btn {
  flex: none;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
}

/* Hover only where there is a pointer to hover: on touch it sticks after a tap. */
@media (hover: hover) {
  button:hover:not(:disabled, [aria-disabled="true"]),
  .btn:hover:not([aria-disabled="true"]) {
    background: var(--surface-3);
  }
}
button:active:not(:disabled, [aria-disabled="true"]),
.btn:active:not([aria-disabled="true"]) {
  background: var(--surface-3);
  transform: translateY(1px);
}

/* Disabled is designed, never faded: opacity took the gate's one call to action
   down to 1.9:1. */
button:disabled,
button[aria-disabled="true"],
.btn[aria-disabled="true"] {
  border-color: var(--line);
  color: var(--text-faint);
  cursor: not-allowed;
}

button[aria-busy="true"],
.btn[aria-busy="true"] {
  cursor: progress;
}

/* Primary: the one blue, filled. */
button.primary,
.btn.primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: var(--shadow-primary);
}
@media (hover: hover) {
  button.primary:hover:not(:disabled, [aria-disabled="true"]),
  .btn.primary:hover:not([aria-disabled="true"]) {
    border-color: var(--accent-hover);
    background: var(--accent-hover);
  }
}
button.primary:active:not(:disabled, [aria-disabled="true"]),
.btn.primary:active:not([aria-disabled="true"]) {
  border-color: var(--accent-press);
  background: var(--accent-press);
}
/*
 * "Primed": a locked primary still shows it is the blue one, and its label stays
 * readable (8.1:1 night, 5.4:1 mist), so people can read what they are agreeing to.
 * The fill turning solid is the "ready" signal.
 */
button.primary:disabled,
button.primary[aria-disabled="true"],
.btn.primary[aria-disabled="true"] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent-text);
  box-shadow: none;
}

/* Ghost: header icons, Cancel. */
button.ghost,
.btn.ghost {
  border-color: transparent;
  background: transparent;
  color: var(--text-dim);
}
@media (hover: hover) {
  button.ghost:hover:not(:disabled, [aria-disabled="true"]),
  .btn.ghost:hover:not([aria-disabled="true"]) {
    background: var(--surface-2);
    color: var(--text);
  }
}
button.ghost:active:not(:disabled, [aria-disabled="true"]),
.btn.ghost:active:not([aria-disabled="true"]) {
  background: var(--surface-3);
  color: var(--text);
}
button.ghost:disabled,
button.ghost[aria-disabled="true"],
.btn.ghost[aria-disabled="true"] {
  border-color: transparent;
  color: var(--text-faint);
}

/* Danger: Report in the toolbar. Red label, red edge, no fill until pressed. */
button.danger,
.btn.danger {
  border-color: var(--danger-line);
  background: transparent;
  color: var(--danger);
}
@media (hover: hover) {
  button.danger:hover:not(:disabled, [aria-disabled="true"]),
  .btn.danger:hover:not([aria-disabled="true"]) {
    background: var(--danger-bg);
  }
}
button.danger:active:not(:disabled, [aria-disabled="true"]),
.btn.danger:active:not([aria-disabled="true"]) {
  background: var(--danger-bg);
  background: color-mix(in srgb, var(--danger-bg) 82%, var(--danger));
}
button.danger:disabled,
button.danger[aria-disabled="true"],
.btn.danger[aria-disabled="true"] {
  border-color: var(--line);
  color: var(--text-faint);
}

/* Danger-solid: Send report, and nothing else. */
button.danger-solid,
.btn.danger-solid {
  border-color: var(--danger-solid);
  background: var(--danger-solid);
  color: var(--on-danger);
}
@media (hover: hover) {
  button.danger-solid:hover:not(:disabled, [aria-disabled="true"]),
  .btn.danger-solid:hover:not([aria-disabled="true"]) {
    border-color: var(--danger-solid-hover);
    background: var(--danger-solid-hover);
  }
}
button.danger-solid:active:not(:disabled, [aria-disabled="true"]),
.btn.danger-solid:active:not([aria-disabled="true"]) {
  border-color: var(--danger-solid-hover);
  background: var(--danger-solid-hover);
}
button.danger-solid:disabled,
button.danger-solid[aria-disabled="true"],
.btn.danger-solid[aria-disabled="true"] {
  border-color: var(--danger-line);
  background: var(--danger-bg);
  color: var(--danger);
}

/*
 * Link: the one fix inside a notice ("Try again", "Open settings"). It reads as a
 * link, yet its hit area is still 44px tall: the padding makes the target and the
 * negative margin keeps it out of the layout.
 */
button.link,
.btn.link {
  margin: -12px -4px;
  padding: 12px 4px;
  border-color: transparent;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--accent-text);
  font-size: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
@media (hover: hover) {
  button.link:hover:not(:disabled, [aria-disabled="true"]),
  .btn.link:hover:not([aria-disabled="true"]) {
    background: transparent;
    text-decoration-thickness: 2px;
  }
}
button.link:active:not(:disabled, [aria-disabled="true"]),
.btn.link:active:not([aria-disabled="true"]) {
  background: transparent;
  transform: none;
}
button.link:disabled,
button.link[aria-disabled="true"],
.btn.link[aria-disabled="true"] {
  border-color: transparent;
  color: var(--text-faint);
  text-decoration: none;
}

/* A leading spinner inside a busy button follows the label's colour. */
button .spinner,
.btn .spinner {
  width: 16px;
  height: 16px;
  border-color: currentColor;
  border-color: color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
}

/* =================================================================== fields */

select,
textarea,
input:not([type]),
input[type="text"],
input[type="search"],
input[type="password"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="tel"] {
  width: 100%;
  min-height: var(--tap);
  padding: 10px 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  background-color: var(--field);
  color: var(--text);
  /* 16px everywhere: iOS Safari zooms the page into any smaller field it focuses,
     and stays zoomed after the keyboard closes. max() keeps 16 even if the browser's
     default text size is set lower. */
  font: 400 max(16px, 1rem) / 1.25 var(--font-ui);
  transition: border-color var(--dur-2) var(--ease-drift);
}

textarea {
  min-height: 96px;
  padding-top: 12px;
  padding-bottom: 12px;
  line-height: 1.5;
  resize: vertical;
}

select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 44px;
  background-image: var(--chevron);
  background-position: right 12px center;
  background-repeat: no-repeat;
  background-size: 20px 20px;
  text-overflow: ellipsis;
  cursor: pointer;
}

@media (pointer: coarse) {
  select,
  textarea,
  input:not([type]),
  input[type="text"],
  input[type="search"],
  input[type="password"],
  input[type="number"],
  input[type="email"],
  input[type="url"],
  input[type="tel"] {
    min-height: 48px;
  }
}

@media (hover: hover) {
  select:hover:not(:disabled),
  textarea:hover:not(:disabled),
  input:hover:not(:disabled, [type="checkbox"], [type="radio"]) {
    border-color: var(--text-faint);
  }
}

select:focus-visible,
textarea:focus-visible,
input:focus-visible:not([type="checkbox"], [type="radio"]) {
  border-color: var(--accent);
}

/* As specific as the field, hover and focus rules above: a bare [aria-invalid] lost
   to input[type="text"] and never showed on a text field. */
[aria-invalid="true"],
:is(select, textarea, input)[aria-invalid="true"],
:is(select, textarea, input)[aria-invalid="true"]:hover,
:is(select, textarea, input)[aria-invalid="true"]:focus-visible {
  border-color: var(--danger);
}

select:disabled,
textarea:disabled,
input:disabled:not([type="checkbox"], [type="radio"]) {
  border-color: var(--line);
  background-color: var(--surface-2);
  color: var(--text-faint);
  cursor: not-allowed;
}

/* A label above its field. */
label.field {
  display: block;
  margin-bottom: var(--sp-2);
  color: var(--text-dim);
  font: 600 var(--t-small) / 1.45 var(--font-ui);
}

/* What went wrong, under the field: an icon and words, never colour alone. */
.field-error {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: var(--sp-2);
  color: var(--danger);
  font-size: var(--t-small);
  line-height: 1.45;
}

/* =============================================================== choice row */

/*
 * The agreement tick (and any single choice that deserves a row): a label card whose
 * whole area is the target. The box is drawn, 22px with a 2px --line-strong edge
 * (3:1 or better); checked, it fills with blue and a tick in the label colour.
 */
.consent,
.choice {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 56px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-size: var(--t-body);
  line-height: 1.45;
  cursor: pointer;
  transition:
    border-color var(--dur-2) var(--ease-drift),
    background-color var(--dur-2) var(--ease-drift);
}
@media (hover: hover) {
  .consent:hover,
  .choice:hover {
    border-color: var(--line-strong);
  }
}
/* Selected: a 1px blue edge plus a 1px inset, so nothing shifts. */
.consent:has(input:checked),
.choice:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.consent:has(input:disabled),
.choice:has(input:disabled) {
  color: var(--text-faint);
  cursor: not-allowed;
}

.consent input,
.choice input {
  flex: none;
  width: 22px;
  height: 22px;
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
  border: 2px solid var(--line-strong);
  border-radius: var(--r-xs);
  background: var(--field);
  cursor: pointer;
}
.consent input[type="radio"],
.choice input[type="radio"] {
  border-radius: 50%;
}
.consent input:checked,
.choice input:checked {
  border-color: var(--accent);
  background: var(--accent) var(--tick) center / 100% no-repeat;
}
.consent input[type="radio"]:checked,
.choice input[type="radio"]:checked {
  background: var(--on-accent);
  box-shadow: inset 0 0 0 5px var(--accent);
}

/* ==================================================================== cards */

.card {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.card-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-5);
}
@media (min-width: 37.5rem) {
  .card-body {
    padding: var(--sp-6);
  }
}

.card > header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--text-dim);
  font: 600 var(--t-small) / 1.45 var(--font-ui);
}

/* ================================================================== notices */

/*
 * Tone comes from the tint and the icon; the words stay --text. Two lines at most,
 * plus one button to fix it.
 */
/* No wrapping: scripts fill notices with bare text, and a wrapping row would drop a
   long text node below its icon. To put the fix under the words, wrap both in one
   element after the icon. */
.notice {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--t-small);
  line-height: 1.45;
}
.notice > .i {
  width: 20px;
  height: 20px;
  color: var(--text-dim);
}
.notice > :not(.i, button, .btn) {
  flex: 1 1 auto;
  min-width: 0;
}
.notice > button,
.notice > .btn {
  flex: none;
  align-self: center;
}
.notice.ok {
  border-color: var(--ok-line);
  background: var(--ok-bg);
}
.notice.ok > .i {
  color: var(--ok);
}
.notice.warn {
  border-color: var(--warn-line);
  background: var(--warn-bg);
}
.notice.warn > .i {
  color: var(--warn);
}
.notice.error {
  border-color: var(--danger-line);
  background: var(--danger-bg);
}
.notice.error > .i {
  color: var(--danger);
}
/* Operator diagnostics (the relay warning, the synthetic stream). Rendered only with
   ?debug=1 or ?fake=1, and styled so nobody mistakes it for the product speaking. */
.notice.debug {
  border: 1px dashed var(--line-strong);
  background: transparent;
  color: var(--text-dim);
  font: 400 var(--t-micro) / 1.5 var(--font-mono);
}

/* ===================================================== tags, pills, chips */

/* A small label: a report reason, a role. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--surface-2);
  color: var(--text-dim);
  font: 600 var(--t-micro) / 1.33 var(--font-ui);
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.tag.danger {
  border-color: var(--danger-line);
  background: var(--danger-bg);
  color: var(--danger);
}
.tag.warn {
  border-color: var(--warn-line);
  background: var(--warn-bg);
  color: var(--warn);
}
.tag.ok {
  border-color: var(--ok-line);
  background: var(--ok-bg);
  color: var(--ok);
}

/*
 * Status pill. The dot's shape says the state before its colour does: a ring while
 * waiting (searching is not a warning, so not amber), filled when settled.
 */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  max-width: 100%;
  min-height: 32px;
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--text-dim);
  font: 600 var(--t-small) / 1.3 var(--font-ui);
  white-space: nowrap;
}
.dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-faint);
}
.pill[data-state="waiting"] .dot {
  border: 2px solid var(--text-dim);
  background: transparent;
}
.pill[data-state="connected"] .dot {
  background: var(--ok);
}

/* Over video: dark glass in both themes, white text, digits that do not jitter. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 4px 10px;
  border-radius: var(--r-full);
  background: var(--video-glass);
  color: var(--on-video);
  font: 600 var(--t-micro) / 1.33 var(--font-ui);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Unread count: the logo's sky blue, with navy digits (9.1:1). Someone is talking
   to you. */
.badge {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--r-full);
  background: var(--brand);
  color: var(--on-brand);
  font: 700 var(--t-micro) / 1 var(--font-ui);
  font-variant-numeric: tabular-nums;
}

kbd {
  display: inline-block;
  min-width: 1.75em;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: var(--surface-2);
  box-shadow: inset 0 -1px 0 var(--line);
  color: var(--text-dim);
  font: 600 var(--t-micro) / 1.5 var(--font-ui);
  text-align: center;
  white-space: nowrap;
}

/* =================================================================== tables */

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-small);
}

th {
  padding: 8px 16px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--text-faint);
  font: 600 var(--t-micro) / 1.33 var(--font-ui);
  letter-spacing: 0.06em;
  text-align: left;
  text-transform: uppercase;
}

td {
  padding: 10px 16px;
  border-bottom: 1px solid var(--line-soft);
  font-variant-numeric: tabular-nums;
  vertical-align: middle;
}

tr:last-child td {
  border-bottom: none;
}

/* ================================================================== dialogs */

dialog {
  width: min(440px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  padding: 0;
  overflow: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-3);
}
dialog[open] {
  animation: dialog-in var(--dur-3) var(--ease-drift);
}
/* No blur: behind Report there are two playing videos, and blurring them at frame
   rate is the most expensive paint a phone can do. */
dialog::backdrop {
  /* The fallback: ::backdrop only inherits custom properties in newer engines. */
  background: var(--backdrop, rgba(7, 8, 24, 0.72));
}
/* The backdrop used to snap to full-dark the instant the dialog opened, ahead of the
   box's own fade — a beat of empty dark before there was anything to look at. A
   transition cannot animate ::backdrop from "not there" to open in Chromium; a
   keyframe animation can. */
dialog[open]::backdrop {
  animation: backdrop-in var(--dur-3) var(--ease-drift);
}
@keyframes dialog-in {
  from {
    opacity: 0;
    transform: translateY(var(--rise));
  }
}
@keyframes backdrop-in {
  from {
    opacity: 0;
  }
}

.modal-body {
  --modal-pad: var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--modal-pad);
}
.modal-head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
}
.modal-head h2,
.modal-head h3 {
  font: 700 var(--t-heading) / 1.3 var(--font-display);
  letter-spacing: -0.01em;
}
.modal-head p {
  margin-top: var(--sp-1);
  color: var(--text-dim);
  font-size: var(--t-small);
  line-height: 1.45;
}
/* An icon disc on Report only: it tells you this one has consequences. */
.modal-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-dim);
}
.modal-icon.danger {
  background: var(--danger-bg);
  color: var(--danger);
}
/* The actions stay in reach: a dialog taller than the screen (Report on a small phone,
   or in landscape) scrolls under them instead of hiding them below the fold. They
   carry the body's bottom padding, so a dialog that fits looks exactly as before. */
.modal-actions {
  position: sticky;
  bottom: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin: 0 calc(-1 * var(--modal-pad, 0px)) calc(-1 * var(--modal-pad, 0px));
  padding: var(--sp-2) var(--modal-pad, 0px) var(--modal-pad, 0px);
  background: var(--surface);
}

/*
 * Phones: a dialog built from .modal-body becomes a bottom sheet, within reach of
 * the thumb, with its primary action full width at the bottom (actions keep DOM
 * order: secondary first, primary last).
 */
@media (max-width: 37.49rem) {
  dialog:has(> .modal-body) {
    width: 100%;
    max-width: 100%;
    max-height: calc(100vh - 24px - env(safe-area-inset-top, 0px));
    max-height: calc(100dvh - 24px - env(safe-area-inset-top, 0px));
    margin: auto 0 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    border-width: 1px 0 0;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    animation-name: sheet-in;
  }
  dialog:has(> .modal-body)::before {
    content: "";
    display: block;
    width: 36px;
    height: 4px;
    margin: 8px auto 0;
    border-radius: var(--r-full);
    background: var(--line-strong);
  }
  .modal-body {
    --modal-pad: var(--sp-5);
  }
  dialog:has(> .modal-body) .modal-actions {
    flex-direction: column;
    align-items: stretch;
  }
}
@keyframes sheet-in {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
}

/* =================================================================== toasts */

/* The stack. Where it sits is the page's decision (top centre on the site, above the
   toolbar during a call, bottom right in admin). One at a time; confirmations only. */
.toasts {
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 420px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--raised);
  box-shadow: var(--shadow-2);
  color: var(--text);
  font-size: var(--t-small);
  line-height: 1.45;
  pointer-events: auto;
  animation: toast-enter var(--dur-3) var(--ease-drift);
}
.toast > .i {
  width: 16px;
  height: 16px;
  color: var(--text-dim);
}
.toast[data-tone="ok"] > .i {
  color: var(--ok);
}
.toast[data-tone="error"] {
  border-color: var(--danger-line);
}
.toast[data-tone="error"] > .i {
  color: var(--danger);
}
@keyframes toast-enter {
  from {
    opacity: 0;
    transform: translateY(calc(var(--rise) * -1));
  }
}

/* ================================================================== spinner */

/* Neutral, not blue: waiting is not an action. Always beside words that say what
   is pending, because under reduced motion it stands still. */
.spinner {
  display: inline-block;
  flex: none;
  width: 24px;
  height: 24px;
  border: 2px solid var(--line);
  border-top-color: var(--text-faint);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
.spinner.sm {
  width: 16px;
  height: 16px;
}
.spinner.lg {
  width: 40px;
  height: 40px;
  border-width: 3px;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* ==================================================== Windows High Contrast */

/* Forced colours drop backgrounds: anything drawn only with a fill (dots, meter
   fills, the drawn checkbox, the badge) needs a system colour to stay visible. */
@media (forced-colors: active) {
  .consent input,
  .choice input,
  select {
    -webkit-appearance: auto;
    appearance: auto;
    background-image: none;
  }
  .dot,
  .badge,
  .spinner,
  [role="meter"] > * {
    forced-color-adjust: none;
  }
  .dot {
    background: CanvasText;
  }
  .pill[data-state="waiting"] .dot {
    border-color: CanvasText;
    background: Canvas;
  }
  .badge {
    background: Highlight;
    color: HighlightText;
  }
  .spinner {
    border-color: GrayText;
    border-top-color: Highlight;
  }
  [role="meter"] > * {
    background: Highlight;
  }
  .chip,
  .toast,
  .notice {
    border: 1px solid CanvasText;
  }
  button[aria-disabled="true"],
  .btn[aria-disabled="true"] {
    border-color: GrayText;
    color: GrayText;
  }
  :focus-visible {
    outline-color: Highlight;
  }
}

/* =========================================================== reduced motion */

/*
 * Everything stops: durations AND delays, or a staggered entrance holds content
 * invisible for its delay. 0.001ms rather than 0 so animationend and transitionend
 * still fire for any script waiting on them. Anything whose only visible state is a
 * keyframe needs its own static style (the search rings have one in chat.css).
 */
@media (prefers-reduced-motion: reduce) {
  /* The universal selector does not reach pseudo-elements other than the two named
     here — ::backdrop needs its own line, or dialog-in's fade above still plays
     instantly at full speed and reduced motion keeps a flash of dark nothing. */
  *,
  *::before,
  *::after,
  *::backdrop {
    animation-duration: 0.001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
}

/* Questions and answers: one card, a row each, folded, with the lobby's turning
   chevron. The gate's "Questions people ask" and the search pages (pages.css). */
.faq {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.faq-item + .faq-item {
  border-top: 1px solid var(--line-soft);
}
.faq-item > summary {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 3.5rem;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-lg);
  font: 600 var(--t-body) / 1.35 var(--font-ui);
  list-style: none;
  cursor: pointer;
}
.faq-item > summary::-webkit-details-marker {
  display: none;
}
.faq-item > summary:focus-visible {
  outline-offset: -3px;
}
.faq-item > summary::after {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  margin: 0 6px 0 auto;
  border-right: 2px solid var(--text-faint);
  border-bottom: 2px solid var(--text-faint);
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--dur-2) var(--ease-drift);
}
.faq-item[open] > summary::after {
  transform: translateY(2px) rotate(225deg);
}
.faq-item > p {
  padding: 0 var(--sp-4) var(--sp-4);
  color: var(--text-dim);
  font-size: var(--t-small);
  line-height: 1.5;
}
