/*
 * TRG core design tokens and component classes.
 *
 * High-contrast monochrome system. Pure black backgrounds, white text,
 * thin borders, sharp corners, uppercase letter-spaced labels, slow 0.5s
 * hover inversions. Inspired by editorial/luxury real estate sites.
 *
 * This file is the single source of truth for visual primitives. It is
 * shared by both the React app and the static styleguide page. Theme-
 * specific usage tokens (--bg-*, --fg-*, --border-*, intent tokens)
 * live in theme-dark.css and theme-light.css — loaded via <link> with
 * only one active at a time.
 *
 * Used by:
 *   - index.html           (React app entry)
 *   - styleguide/index.html (visual reference page)
 */

/* ---------- Tokens ---------- */

:root {
  /* ---------- Layer 1: palette ----------
   * Material-named mono ramp from black to white. These values are
   * constant across themes — the palette is the "raw materials" layer.
   */
  --color-black: #000000;
  --color-near-black: #0a0a0a;
  --color-charcoal: #111111;
  --color-graphite: #161616;
  --color-iron: #242424;
  --color-onyx: #333333;    /* structural border floor for dark — one step above iron, below gunmetal */
  --color-gunmetal: #3a3a3a;
  --color-smoke: #555555;
  --color-fog: #767676;     /* subtle-foreground floor — clears AA (≈4.6:1) on both canvases */
  --color-ash: #888888;
  --color-stone: #a0a0a0;   /* structural border for light — one step below silver, above ash */
  --color-silver: #b8b8b8;
  --color-pearl: #ededed;
  --color-snow: #f7f7f7;
  --color-white: #ffffff;

  /* Status — desaturated to coexist with the mono palette */
  --color-red: #ff5959;
  --color-red-dim: #1a0808;
  --color-red-border: #4a1414;
  --color-green: #4ade80;
  --color-green-dim: #08180e;
  --color-green-border: #1a3a22;
  --color-amber: #f5c451;
  --color-amber-dim: #1a1408;
  --color-amber-border: #3a2c0e;
  --color-info: #d4d4d4;
  --color-info-dim: #141414;
  --color-info-border: #2a2a2a;

  /* Modes — distinct accents for elevated user states */
  --color-admin: #5eead4;
  --color-admin-dim: #06201c;
  --color-admin-border: #144037;
  --color-dev: #a78bfa;
  --color-dev-dim: #14082a;
  --color-dev-border: #2e1f5c;

  /* Onboarding role accent (sky) */
  --color-onboarding: #60a5fa;
  --color-onboarding-dim: #08111f;
  --color-onboarding-border: #1a2e4a;

  /* Typography — League Spartan is the official font of TRG.
     Light weights (200–300) dominate; 500 is reserved for chrome labels. */
  --font-family: 'League Spartan', sans-serif;
  --font-family-mono: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
  --font-size-xs: 0.6875rem;
  --font-size-sm: 0.75rem;
  --font-size-base: 0.8125rem;
  --font-size-md: 0.9375rem;
  --font-size-input: 1rem;
  --font-size-logo: 1.125rem;
  /* Subheading tier — fills the gap between body (0.9375rem) and the page
     title, used for card titles and section-level headings. */
  --font-size-lg: 1.25rem;

  /* Letter spacing — generous for uppercase chrome */
  --tracking-tight: 0.02em;
  --tracking-wide: 0.1em;
  --tracking-wider: 0.18em;

  /* Line height. tight (1.2) for chrome that wraps, normal (1.5) for body
     and most components, relaxed (1.7) for long-form prose. */
  --leading-tight: 1.2;
  --leading-normal: 1.5;
  --leading-relaxed: 1.7;

  /* Max line length for prose blocks. ~60 characters keeps descriptions
     readable without stretching the full panel width. */
  --measure: 60ch;

  /* Radii — sharp by default. Nothing rounds. */
  --radius-sm: 0;
  --radius-md: 0;

  /* Spacing tokens */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;

  /* Motion tokens. Four-step duration scale for state changes and
     entrances/exits — see `/styleguide/loading.html` for loop rhythms
     (spinner, dots, skeleton, progress) which sit outside this scale. */
  --duration-instant: 80ms;    /* hover, focus, theme swap on a single element */
  --duration-quick: 160ms;     /* button states, banner inline open/close, small layout shifts */
  --duration-standard: 240ms;  /* modal, toast, page transitions — anything traversing distance */
  --duration-slow: 360ms;      /* full-page reveals, large reorders (rare) */

  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);  /* default, balanced */
  --ease-out: cubic-bezier(0, 0, 0.2, 1);         /* entrances — fast start, soft land */
  --ease-in: cubic-bezier(0.4, 0, 1, 1);          /* exits — soft start, fast leave */

  /* Z-index scale. Three tiers cover everything the system actually layers:
     dropdowns and tooltips that escape a panel, toasts that float fixed,
     and modal overlays that sit above both. Local stacking inside a card
     (z-index: 1 / 2) does not need a token. */
  --z-dropdown: 100;
  --z-toast: 1000;
  --z-overlay: 1100;
}

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

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

html {
  scroll-behavior: smooth;
  overflow-y: scroll;
}

/* ---------- Scrollbar ---------- */

html {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) var(--bg-page);
}

*::-webkit-scrollbar {
  width: 0.625rem;
  height: 0.625rem;
}

*::-webkit-scrollbar-track {
  background: var(--bg-page);
  border-left: 1px solid var(--border-default);
}

*::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border: 2px solid var(--bg-page);
  border-radius: 0;
  transition: background var(--duration-quick) var(--ease-standard);
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--fg-muted);
}

*::-webkit-scrollbar-corner {
  background: var(--bg-page);
}

body {
  margin: 0;
  font-family: var(--font-family);
  color: var(--fg-default);
  background: var(--bg-page);
  font-size: 1rem;
  font-weight: 300;
  line-height: var(--leading-normal);
  /* Long unbroken strings (file IDs, hashes, URLs) wrap inside their
     container instead of forcing horizontal scroll on the panel. */
  overflow-wrap: anywhere;
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--fg-emphasis);
  color: var(--fg-on-emphasis);
}

/* Keyboard focus ring. One rule covers every interactive element that
   doesn't define its own :focus-visible treatment (buttons, links, cards,
   nav links, icon buttons). Mouse/touch focus — :focus without
   :focus-visible — stays ring-free, so the ring only appears for keyboard
   users. Elements with bespoke focus handling (inputs, tooltips, info
   icons) override below. */
:focus-visible {
  outline: 2px solid var(--border-emphasis);
  outline-offset: 2px;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-weight: 300;
  color: var(--fg-emphasis);
  letter-spacing: var(--tracking-tight);
}

a {
  color: var(--fg-emphasis);
  text-decoration: none;
  border-bottom: 1px solid var(--link-underline);
  transition: border-color var(--duration-quick) var(--ease-standard);
}

a:hover {
  border-bottom-color: var(--fg-emphasis);
}

/* The system uses weight 200–300 for prose and 500 for chrome — bold is
   intentionally absent. <strong> shifts color rather than weight beyond 500. */
strong {
  font-weight: 500;
  color: var(--fg-emphasis);
}

em {
  font-style: italic;
}

code {
  font-family: var(--font-family-mono);
}

/* ---------- Buttons ---------- */

.btn {
  border: 1px solid var(--border-emphasis);
  background: transparent;
  color: var(--fg-emphasis);
  cursor: pointer;
  font-weight: 500;
  font-family: inherit;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 0;
  transition: background var(--duration-quick) var(--ease-standard), color var(--duration-quick) var(--ease-standard), border-color var(--duration-quick) var(--ease-standard);
  --spinner-color: currentColor;
  position: relative;
}

.btn:disabled {
  cursor: default;
  opacity: 0.35;
}

.btn-sm { padding: 0.5rem 1rem;     font-size: var(--font-size-xs); }
.btn-md { padding: 0.75rem 1.5rem;  font-size: var(--font-size-sm); }
.btn-lg { padding: 1rem 2.25rem;    font-size: var(--font-size-base); }

.btn-primary {
  background: var(--bg-emphasis);
  color: var(--fg-on-emphasis);
  border-color: var(--fg-emphasis);
  --spinner-color: var(--fg-on-emphasis);
}
.btn-primary:not(:disabled):hover {
  background: transparent;
  color: var(--fg-emphasis);
}

.btn-secondary {
  background: transparent;
  color: var(--fg-emphasis);
  border-color: var(--fg-emphasis);
  --spinner-color: var(--fg-emphasis);
}
.btn-secondary:hover {
  background: var(--bg-emphasis);
  color: var(--fg-on-emphasis);
}
.btn-secondary.is-active {
  background: var(--bg-emphasis);
  color: var(--fg-on-emphasis);
}

.btn-danger {
  background: transparent;
  color: var(--danger-emphasis);
  border-color: var(--danger-emphasis);
  --spinner-color: var(--danger-emphasis);
}
.btn-danger:hover {
  background: var(--danger-emphasis);
  color: var(--fg-on-emphasis);
}

.btn-dashed {
  background: transparent;
  color: var(--fg-muted);
  border: 1px dashed var(--border-strong);
}
.btn-dashed:hover {
  border-color: var(--fg-emphasis);
  color: var(--fg-emphasis);
}

.btn-plain {
  background: none;
  color: var(--fg-emphasis);
  border: none;
  border-bottom: 1px solid var(--fg-subtle);
  padding: 0 0 0.125rem;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-size: var(--font-size-xs);
}
.btn-plain:hover {
  border-bottom-color: var(--fg-emphasis);
}

.btn-fullwidth { width: 100%; }

/* Button loading state */

.btn.is-loading {
  pointer-events: none;
  color: transparent;
}

.btn.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0.875rem;
  height: 0.875rem;
  margin: -0.4375rem 0 0 -0.4375rem;
  border: 1px solid var(--spinner-color);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spinner-rotate 0.8s linear infinite;
}

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

.icon-btn {
  background: none;
  border: none;
  cursor: pointer;
  flex-shrink: 0;
  font-family: inherit;
  transition: color var(--duration-instant) var(--ease-standard);
}
.icon-btn-danger {
  color: var(--danger-emphasis);
  font-size: 1rem;
}
.icon-btn-danger:hover {
  color: var(--fg-emphasis);
}
.icon-btn-neutral {
  color: var(--fg-muted);
  font-size: 1.25rem;
}
.icon-btn-neutral:hover {
  color: var(--fg-emphasis);
}

/* ---------- Layout shell ---------- */

.layout {
  display: flex;
  min-height: 100vh;
  background: var(--bg-page);
}

.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: 15rem;
  height: 100vh;
  padding: var(--space-8) var(--space-6);
  border-right: 1px solid var(--border-default);
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  background: var(--bg-page);
  overflow-y: auto;
}

.sidebar-logo {
  width: 100%;
  height: 4.5rem;
  margin-bottom: var(--space-10);
  display: flex;
  align-items: center;
  justify-content: center;
}

.nav-link {
  display: block;
  padding: 0.75rem 0;
  text-decoration: none;
  color: var(--fg-muted);
  font-size: var(--font-size-sm);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  border: none;
  transition: color var(--duration-quick) var(--ease-standard);
}

.nav-link:hover,
.nav-link.is-active {
  color: var(--fg-emphasis);
}

/* Groups a run of nav links under an uppercase label with a hairline rule
   above — used to separate admin/ops entries from the content pages. */
.nav-section {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-default);
  font-size: var(--font-size-xs);
  color: var(--fg-subtle);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
}

.sidebar-bottom {
  margin-top: auto;
  font-size: var(--font-size-xs);
  color: var(--fg-subtle);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.sidebar-email {
  margin-top: 0.5rem;
}

.signout {
  background: none;
  border: none;
  color: var(--fg-muted);
  cursor: pointer;
  padding: 0;
  margin-top: 0.75rem;
  font-size: var(--font-size-xs);
  font-family: inherit;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  transition: color var(--duration-quick) var(--ease-standard);
}

.signout:hover {
  color: var(--fg-emphasis);
}

.main {
  flex: 1;
  max-width: 80rem;
  margin-left: max(15rem, calc(50% - 40rem));
  margin-right: auto;
  padding: var(--space-12);
}

/* Sidebar-less variant — used on public surfaces (e.g. signed-out /store)
   that share the .main width cap and padding but have no fixed sidebar to
   clear. .main centers within the viewport instead of offsetting past 15rem. */
.layout.no-sidebar .main {
  margin-left: auto;
}

/* ---------- Content layout ---------- */

.content {
  flex: 1;
}

.content:has(> .panel) {
  display: grid;
  grid-template-columns: 1fr minmax(0, 45rem) 1fr;
  gap: 1.5rem;
}

.content > .panel {
  grid-column: 2;
}

.content > .rail {
  grid-column: 3;
  justify-self: start;
}

/* Multi-panel: switch to flex, panels share space equally */
.content:has(> .panel + .panel) {
  display: flex;
}

.content:has(> .panel + .panel) > .panel {
  flex: 1;
  max-width: none;
}

.panel {
  flex: 1;
  width: 100%;
  max-width: 45rem;
  margin: 0 auto;
  animation: anim-slide-up var(--duration-standard) var(--ease-out) both;
}

.rail {
  flex: none;
  width: 15rem;
  position: sticky;
  top: 3rem;
  align-self: start;
  max-height: calc(100vh - 6rem);
  overflow-y: auto;
  scrollbar-gutter: stable;
  scrollbar-width: thin;
}

.rail.is-wide { width: 22rem; }

/* ---------- Toggle switch ---------- */

.toggle {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem 0;
  margin-bottom: 0.5rem;
  width: 100%;
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--fg-muted);
  font-family: inherit;
  transition: color var(--duration-quick) var(--ease-standard);
}

.toggle.is-on {
  color: var(--fg-emphasis);
}

.toggle-track {
  display: inline-block;
  width: 2rem;
  height: 1rem;
  border-radius: 0;
  background: transparent;
  border: 1px solid var(--fg-subtle);
  position: relative;
  flex-shrink: 0;
  transition: border-color var(--duration-quick) var(--ease-standard);
}

.toggle.is-on .toggle-track {
  border-color: var(--fg-emphasis);
}

.toggle-thumb {
  position: absolute;
  top: 50%;
  left: 1px;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 0;
  background: var(--fg-subtle);
  transform: translateY(-50%);
  transition: left var(--duration-quick) var(--ease-standard), background var(--duration-quick) var(--ease-standard);
}

.toggle.is-on .toggle-thumb {
  left: calc(100% - 0.75rem - 1px);
  background: var(--bg-emphasis);
}

.toggle.is-admin {
  color: var(--admin-border);
}
.toggle.is-admin .toggle-track {
  border-color: var(--admin-border);
}
.toggle.is-admin .toggle-thumb {
  background: var(--admin-border);
}

.toggle.is-admin.is-on {
  color: var(--admin-emphasis);
}
.toggle.is-admin.is-on .toggle-track {
  border-color: var(--admin-emphasis);
}
.toggle.is-admin.is-on .toggle-thumb {
  background: var(--admin-emphasis);
}

.toggle.is-dev {
  color: var(--dev-border);
}
.toggle.is-dev .toggle-track {
  border-color: var(--dev-border);
}
.toggle.is-dev .toggle-thumb {
  background: var(--dev-border);
}

.toggle.is-dev.is-on {
  color: var(--dev-emphasis);
}
.toggle.is-dev.is-on .toggle-track {
  border-color: var(--dev-emphasis);
}
.toggle.is-dev.is-on .toggle-thumb {
  background: var(--dev-emphasis);
}

/* ---------- Page header ---------- */

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  padding-bottom: var(--space-8);
  margin-bottom: var(--space-10);
  border-bottom: 1px solid var(--border-default);
}
.page-header.is-loading {
  border-bottom: none;
  margin-bottom: 0;
}
.page-header-progress {
  margin-bottom: var(--space-10);
}

.page-header h2 {
  margin: 0;
  font-size: 2rem;
  font-weight: 200;
  letter-spacing: var(--tracking-tight);
  color: var(--fg-emphasis);
}

/* Decorative script accent (e.g. a name in a greeting). Imperial Script is
   loaded via <link> in index.html; `cursive` is the fallback. */
.cursive {
  font-family: 'Imperial Script', cursive;
  font-weight: 400;
}

.page-header-subtitle {
  color: var(--fg-muted);
  margin: 0.5rem 0 0;
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.page-header-description {
  color: var(--fg-muted);
  margin: 1rem 0 0;
  font-size: var(--font-size-base);
  font-weight: 300;
  max-width: var(--measure);
  line-height: var(--leading-relaxed);
}

.backlink {
  display: inline-block;
  margin-bottom: 2rem;
  color: var(--fg-muted);
  font-size: var(--font-size-xs);
  text-decoration: none;
  border: none;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  transition: color var(--duration-quick) var(--ease-standard);
}

.backlink:hover {
  color: var(--fg-emphasis);
}

/* ---------- Section header ---------- */

.section-header {
  margin: 0;
  font-size: var(--font-size-xs);
  color: var(--fg-default);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  font-weight: 500;
}

/* Section header rendered as a click-to-collapse button. Used for in-rail
   collapsible sections (e.g., version history) where a bordered Disclosure
   would feel too heavy. */
.section-header-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  background: transparent;
  border: none;
  padding: 0;
  font-family: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: color var(--duration-instant) var(--ease-standard);
}

.section-header-toggle:hover .section-header {
  color: var(--fg-emphasis);
}

.section-header-toggle-chevron {
  color: var(--fg-subtle);
  font-size: var(--font-size-xs);
  flex-shrink: 0;
  transition: transform var(--duration-instant) var(--ease-standard);
}

.section-header-toggle[aria-expanded="true"] .section-header-toggle-chevron {
  transform: rotate(90deg);
}

/* ---------- Code chip ---------- */

.code-chip {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  color: var(--fg-default);
  font-family: var(--font-family-mono);
  font-size: var(--font-size-xs);
  border-radius: 0;
  white-space: nowrap;
  line-height: 1.4;
  vertical-align: baseline;
}

/* ---------- Keyboard ----------
 * Sibling of .code-chip for keyboard shortcuts. The thicker bottom border
 * reads as a key cap so kbd doesn't get confused with inline code. */

kbd {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  border-bottom-width: 2px;
  color: var(--fg-emphasis);
  font-family: var(--font-family-mono);
  font-size: var(--font-size-xs);
  line-height: 1.4;
  vertical-align: baseline;
  white-space: nowrap;
}

/* ---------- Text utilities ---------- */

/* Single-line ellipsis. Pair with min-width:0 on a flex parent so the
   container can actually shrink below its content's intrinsic width. */
.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Tabular numerals for column-aligned digits in tables, counts, durations. */
.tabular {
  font-variant-numeric: tabular-nums;
}

/* Empty-state text — applied to any text element standing in for absent
   content ("No submissions yet", "—", etc.). */
.empty {
  color: var(--fg-subtle);
  font-style: italic;
}

/* ---------- Info icon ---------- */

.info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  padding: 0;
  background: transparent;
  border: 1px solid var(--fg-muted);
  color: var(--fg-muted);
  border-radius: 50%;
  font-size: 0.625rem;
  font-weight: 600;
  font-style: italic;
  font-family: serif;
  line-height: 1;
  cursor: help;
  vertical-align: middle;
  transition: color var(--duration-instant) var(--ease-standard), border-color var(--duration-instant) var(--ease-standard);
}

.info-icon:hover,
.info-icon:focus-visible {
  color: var(--fg-emphasis);
  border-color: var(--fg-emphasis);
  outline: none;
}

.field-label .info-icon {
  pointer-events: auto;
}

/* ---------- Tooltips ---------- */

.tooltip {
  position: relative;
}

/* Promote the trigger to its own stacking context while activated so the
 * tooltip pseudo-element can paint above sibling cards/rows that create
 * their own stacking contexts (opacity < 1, transforms, etc.). Without
 * this, the tooltip's own z-index is trapped inside the trigger's parent
 * stacking context and a later sibling with any stacking context will
 * occlude it. */
.tooltip:hover,
.tooltip:focus-visible {
  z-index: var(--z-dropdown);
}

.tooltip::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 0.5rem);
  left: 50%;
  transform: translateX(-50%) translateY(0.25rem);
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  color: var(--fg-default);
  padding: 0.5rem 0.75rem;
  font-size: var(--font-size-xs);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-instant) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
  z-index: var(--z-dropdown);
}

.tooltip:hover::after,
.tooltip:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.tooltip-bottom::after {
  bottom: auto;
  top: calc(100% + 0.5rem);
  transform: translateX(-50%) translateY(-0.25rem);
}

.tooltip-bottom:hover::after,
.tooltip-bottom:focus-visible::after {
  transform: translateX(-50%) translateY(0);
}

/* ---------- Rich tooltip ---------- */

.tooltip-rich {
  position: relative;
  display: inline-block;
}

/* See the same-named rule on `.tooltip` above for why the trigger needs
 * its own stacking context while active. */
.tooltip-rich:hover,
.tooltip-rich:focus-within {
  z-index: var(--z-dropdown);
}

/* Cards override the inline-block default so .card-layout's flex stays intact. */
.card-layout.tooltip-rich { display: flex; }

.tooltip-rich-content {
  position: absolute;
  bottom: calc(100% + 0.5rem);
  left: 50%;
  transform: translateX(-50%) translateY(0.25rem);
  background: var(--bg-elevated);
  border: 1px solid var(--border-strong);
  color: var(--fg-default);
  padding: 0.75rem 0.875rem;
  font-size: var(--font-size-sm);
  font-weight: 300;
  line-height: var(--leading-normal);
  min-width: 12.5rem;
  max-width: 17.5rem;
  text-align: left;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-instant) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
  z-index: var(--z-dropdown);
}

.tooltip-rich:hover .tooltip-rich-content,
.tooltip-rich:focus-within .tooltip-rich-content {
  opacity: 1;
  /* Once visible, the content (and any links inside it) must be
   * interactive. While hidden it keeps `pointer-events: none` so it never
   * intercepts clicks meant for elements beneath it. */
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}

/* Transparent bridge spanning the 0.5rem gap between the trigger and the
 * content, so moving the cursor from the trigger to a link inside the
 * tooltip doesn't drop `:hover` and dismiss it mid-traverse. `pointer-events`
 * is inherited, so the bridge is inert while the content is hidden
 * (`none`) and live once it's shown (`auto`) — no extra rules needed.
 * Positioned for the default top placement; overridden per variant below. */
.tooltip-rich-content::before {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 0.5rem;
}

.tooltip-rich-bottom .tooltip-rich-content {
  bottom: auto;
  top: calc(100% + 0.5rem);
  transform: translateX(-50%) translateY(-0.25rem);
}

/* Gap sits above the content for downward placements. */
.tooltip-rich-bottom .tooltip-rich-content::before,
.tooltip-rich-bottom-right .tooltip-rich-content::before {
  top: auto;
  bottom: 100%;
}

.tooltip-rich-bottom:hover .tooltip-rich-content,
.tooltip-rich-bottom:focus-within .tooltip-rich-content {
  transform: translateX(-50%) translateY(0);
}

/* Right / left: vertically centered on the trigger. */
.tooltip-rich-right .tooltip-rich-content {
  bottom: auto;
  top: 50%;
  left: calc(100% + 0.5rem);
  transform: translateY(-50%) translateX(-0.25rem);
}

/* Gap sits to the left of the content for right placement. */
.tooltip-rich-right .tooltip-rich-content::before {
  top: 0;
  bottom: 0;
  left: auto;
  right: 100%;
  height: auto;
  width: 0.5rem;
}

.tooltip-rich-right:hover .tooltip-rich-content,
.tooltip-rich-right:focus-within .tooltip-rich-content {
  transform: translateY(-50%) translateX(0);
}

.tooltip-rich-left .tooltip-rich-content {
  bottom: auto;
  top: 50%;
  left: auto;
  right: calc(100% + 0.5rem);
  transform: translateY(-50%) translateX(0.25rem);
}

/* Gap sits to the right of the content for left placement. */
.tooltip-rich-left .tooltip-rich-content::before {
  top: 0;
  bottom: 0;
  left: 100%;
  right: auto;
  height: auto;
  width: 0.5rem;
}

.tooltip-rich-left:hover .tooltip-rich-content,
.tooltip-rich-left:focus-within .tooltip-rich-content {
  transform: translateY(-50%) translateX(0);
}

/* Corner variant: top edge aligned, extending right from the trigger.
   Useful when a centered tooltip would overflow the viewport to the left. */
.tooltip-rich-top-right .tooltip-rich-content {
  left: 0;
  transform: translateY(0.25rem);
}

.tooltip-rich-top-right:hover .tooltip-rich-content,
.tooltip-rich-top-right:focus-within .tooltip-rich-content {
  transform: translateY(0);
}

/* Corner variant: bottom edge of the trigger, extending right and down.
   Use when the trigger sits at the top of an `overflow: hidden` container
   (e.g. the body of a `.collapsible`) where the default upward tooltip
   would be clipped. */
.tooltip-rich-bottom-right .tooltip-rich-content {
  bottom: auto;
  top: calc(100% + 0.5rem);
  left: 0;
  transform: translateY(-0.25rem);
}

.tooltip-rich-bottom-right:hover .tooltip-rich-content,
.tooltip-rich-bottom-right:focus-within .tooltip-rich-content {
  transform: translateY(0);
}

/* Width modifier: for tooltips carrying example tables or code chips that
   don't fit the prose width. `width: max-content` sizes to the content
   (absolute positioning would otherwise shrink-wrap to the tiny trigger),
   capped well below a full panel. Combine with any placement variant. */
.tooltip-rich-wide .tooltip-rich-content {
  width: max-content;
  max-width: 30rem;
}

/* ---------- Form fields ---------- */

.field {
  position: relative;
  display: block;
  margin-bottom: 1.75rem;
  padding-top: 1.25rem;
}

.field-label {
  position: absolute;
  left: 0;
  top: 1.875rem;
  margin: 0;
  pointer-events: none;
  font-size: var(--font-size-input);
  font-weight: 300;
  font-family: inherit;
  color: var(--fg-subtle);
  text-transform: none;
  letter-spacing: 0;
  transition:
    top var(--duration-quick) var(--ease-standard),
    left var(--duration-quick) var(--ease-standard),
    font-size var(--duration-quick) var(--ease-standard),
    font-weight var(--duration-quick) var(--ease-standard),
    color var(--duration-quick) var(--ease-standard);
}

.field > .input:focus ~ .field-label,
.field > .input:not(:placeholder-shown) ~ .field-label,
.field > .textarea:focus ~ .field-label,
.field > .textarea:not(:placeholder-shown) ~ .field-label,
.field > .select ~ .field-label,
.field.is-floating > .field-label {
  top: 0;
  font-size: var(--font-size-xs);
  font-weight: 500;
  color: var(--fg-muted);
}

.field > .textarea ~ .field-label {
  top: 2.125rem;
  left: 0.75rem;
}
.field > .textarea:focus ~ .field-label,
.field > .textarea:not(:placeholder-shown) ~ .field-label {
  left: 0;
}

.field-label-optional {
  margin-left: 0.4rem;
  font-style: italic;
  font-weight: 300;
  color: var(--fg-subtle);
  letter-spacing: 0;
}

/* $/% affix for usd / percentage number fields. The symbol is a static visual
   adornment on the field baseline; the input value itself stays a bare number.
   Prefix ($) sits left and the resting label shifts clear of it; suffix (%)
   sits right. Both reserve input padding so typed digits never overlap. */
.field-affix {
  position: absolute;
  top: 1.7rem;
  font-size: var(--font-size-input);
  font-weight: 300;
  color: var(--fg-muted);
  pointer-events: none;
}
.field-affix-prefix { left: 0; }
.field-affix-suffix { right: 0; }

.field.has-prefix > .input { padding-left: 0.9rem; }
.field.has-prefix > .field-label { left: 0.9rem; }
.field.has-prefix > .input:focus ~ .field-label,
.field.has-prefix > .input:not(:placeholder-shown) ~ .field-label { left: 0; }

.field.has-suffix > .input { padding-right: 1.25rem; }

.field-legend {
  display: block;
  margin-bottom: 0.5rem;
  font-size: var(--font-size-xs);
  font-weight: 500;
  font-family: inherit;
  color: var(--fg-muted);
  text-transform: none;
  letter-spacing: 0;
}

.input,
.textarea,
.select {
  width: 100%;
  padding: 0.625rem 0;
  margin-top: 0;
  border: none;
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0;
  font-size: var(--font-size-input);
  font-weight: 300;
  font-family: inherit;
  background: transparent;
  color: var(--fg-emphasis);
  transition: border-color var(--duration-instant) var(--ease-standard);
}

.select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  padding-right: 1.5rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--fg-muted) 50%),
                    linear-gradient(135deg, var(--fg-muted) 50%, transparent 50%);
  background-position: calc(100% - 0.75rem) calc(50% - 0.125rem),
                       calc(100% - 0.4375rem) calc(50% - 0.125rem);
  background-size: 0.3125rem 0.3125rem, 0.3125rem 0.3125rem;
  background-repeat: no-repeat;
}

.select option {
  background: var(--bg-surface);
  color: var(--fg-emphasis);
}

.textarea {
  border: 1px solid var(--border-strong);
  padding: 0.75rem;
  resize: vertical;
}

/* Date/time fields use the browser's native picker indicator as their only
 * icon. We intentionally don't overlay a Material glyph: there's no way to
 * hide the native control off-WebKit (Firefox exposes no pseudo-element for
 * it), so an overlay would double up. The native icon themes correctly via
 * `color-scheme`, set per theme in theme-{dark,light}.css. */
.field > .input[type="date"]::-webkit-calendar-picker-indicator,
.field > .input[type="time"]::-webkit-calendar-picker-indicator,
.field > .input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
}

.input[type="number"]::-webkit-outer-spin-button,
.input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.input[type="number"] {
  -moz-appearance: textfield;
}

.input::placeholder,
.textarea::placeholder {
  color: transparent;
}

.input:hover,
.textarea:hover,
.select:hover {
  border-color: var(--fg-muted);
}

.input:focus,
.textarea:focus,
.select:focus {
  outline: none;
  border-color: var(--fg-emphasis);
}

/* Keyboard focus adds a ring on top of the emphasis border so it reads
   distinctly from the muted-border hover state. Pointer focus keeps the
   ring-free treatment above. */
.input:focus-visible,
.textarea:focus-visible,
.select:focus-visible {
  outline: 2px solid var(--border-emphasis);
  outline-offset: 2px;
}

.input.is-invalid,
.textarea.is-invalid,
.select.is-invalid {
  border-color: var(--danger-emphasis);
}

.field-error {
  color: var(--danger-emphasis);
  font-size: var(--font-size-xs);
  margin-top: 0.5rem;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.field-description {
  display: block;
  margin-top: 0.5rem;
  margin-bottom: 0.5rem;
  font-size: var(--font-size-sm);
  font-weight: 300;
  color: var(--fg-muted);
  line-height: var(--leading-normal);
}

.field-description:last-child {
  margin-bottom: 0;
}


.fieldset {
  border: none;
  padding: 0;
  margin: 0 0 1.5rem;
}

.legend {
  font-weight: 500;
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--fg-muted);
  margin-bottom: 0.75rem;
}

.option-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin-bottom: 0.5rem;
  color: var(--fg-default);
  font-size: var(--font-size-md);
  font-weight: 300;
}

.option-row input[type="checkbox"],
.option-row input[type="radio"] {
  accent-color: var(--fg-emphasis);
}

/* Rich option (checkbox_rich) that carries a secondary description line.
   The description wraps onto its own row (flex-basis) and indents to align
   under the label, past the control + gap. */
.option-row-rich {
  flex-wrap: wrap;
}

.option-description {
  flex-basis: 100%;
  margin-left: 1.625rem;
  font-size: var(--font-size-sm);
  font-weight: 300;
  color: var(--fg-muted);
  line-height: var(--leading-normal);
}

/* ---------- Section divider ---------- */

.section-divider {
  border-top: 1px solid var(--border-default);
  margin-top: 0.75rem;
  padding-top: 0.75rem;
}

/* ---------- Section ---------- */

.section {
  padding: var(--space-10) 0;
  border-bottom: 1px solid var(--border-default);
  scroll-margin-top: var(--space-4);
}

.section:first-child {
  padding-top: 0;
}

.section:last-child {
  border-bottom: none;
}

/* ---------- Read-only field ---------- */

.field-readonly {
  display: block;
  margin-bottom: 1rem;
}

.field-readonly-label {
  display: block;
  font-size: var(--font-size-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--fg-muted);
  margin-bottom: 0.5rem;
}

.field-readonly-value {
  display: block;
  padding: 0.625rem 0.875rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  color: var(--fg-default);
  font-size: var(--font-size-sm);
  font-family: inherit;
  overflow-wrap: anywhere;
  min-height: 1.5em;
  margin: 0;
}

.field-readonly-value-pre {
  font-family: var(--font-family-mono);
  font-size: var(--font-size-xs);
  white-space: pre-wrap;
  min-height: 5rem;
  overflow-x: auto;
}

.field-readonly-value.is-empty {
  color: var(--fg-subtle);
  font-style: italic;
}

/* ---------- Cards ---------- */

.card {
  position: relative;
  border: 1px solid var(--border-default);
  border-radius: 0;
  padding: 2rem;
  cursor: pointer;
  background: var(--bg-surface);
  color: var(--fg-default);
  transition:
    border-color var(--duration-quick) var(--ease-standard),
    background var(--duration-quick) var(--ease-standard),
    transform var(--duration-instant) var(--ease-standard),
    box-shadow var(--duration-instant) var(--ease-standard),
    opacity var(--duration-quick) var(--ease-standard);
}

/* Opt-in centering for cards that present centered content (e.g. the
   interactive tile cards rendered by <Card>, whose .card-header sits
   centered above the body). The base .card is left-aligned so content
   cards — form-editor steps, version history, demo stages — don't
   inherit unwanted centering. */
.card-centered {
  text-align: center;
  justify-content: center;
}

.card:not(.is-static):not(.is-locked):hover {
  border-color: var(--fg-emphasis);
  background: var(--bg-elevated);
}

/* Static cards opt out of hover entirely — no border lift, no background
   change, no cursor. Used for styleguide demos and skeleton loading cards
   (CardLoading) where a hover transition would otherwise paint over the
   skeleton gradient and kill the shimmer. */
.card.is-static {
  cursor: default;
}

/* Locked card — non-navigable variant used by the onboarding home grid.
   Hover lift is suppressed via the .is-locked exclusion above; opacity
   dampens to read as inert, then lifts slightly on hover to acknowledge
   the cursor without faking interactivity (the tooltip carries the
   actual "why is this locked" message). Pair with .tooltip-rich-bottom
   to surface the unlock reason. React layer should omit the wrapping
   <Link> so the card doesn't navigate. */
.card.is-locked {
  cursor: default;
  opacity: 0.55;
}

.card.is-locked:hover {
  opacity: 0.75;
}

/* Completed card — still clickable (navigates to the read-only
   submission so the user can review their answers); inherits the
   normal card border-lift hover. Opacity dampens to 0.75 at rest so
   the card steps back from available siblings, then animates to 1.0
   on hover (--duration-quick --ease-standard) — together with the
   border lift, hover "brings it forward" to full attention. The
   corner check_circle stays at --success-emphasis throughout. */
.card.is-completed {
  opacity: 0.75;
}

.card.is-completed:hover {
  opacity: 1;
}

/* Top-right corner Material Symbols slot on a card. Carries the lock
   glyph on .is-locked cards (and check_circle on .is-completed). Color
   is muted by default; intent overrides live alongside the state class
   that owns them. */
.card-state-icon {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  font-family: "Material Symbols Outlined";
  font-size: 1.25rem;
  line-height: 1;
  color: var(--fg-muted);
  pointer-events: none;
}

.card.is-completed .card-state-icon {
  color: var(--success-emphasis);
}

/* Optional link-card glyph in the same top-right slot as .card-state-icon.
   Kept separate because that slot owns the lock/completed onboarding state;
   link cards carry an admin-chosen icon and never both. */
.card-icon {
  position: absolute;
  top: 1rem;
  right: 1rem;
  font-family: "Material Symbols Outlined";
  font-size: 1.5rem;
  line-height: 1;
  color: var(--fg-muted);
  pointer-events: none;
}

.card a,
a.card-link {
  text-decoration: none;
  color: inherit;
  display: block;
  border: none;
}

/* ---------- Drag handle pattern ----------
 * Generalized primitive for rows that contain interactive content
 * (inputs, buttons, links) and need to be drag-reorderable. The
 * handle is the *only* drag origin; the row body stays fully
 * interactive. See /styleguide/drag-and-drop.html.
 *
 * Predecessors live in deprecated.css: .field-row / .field-row-handle
 * (specialization for the form editor). The legacy .card-drag-handle on
 * .card-grid-item (always-visible top-left grip) has been retired; the
 * name has been reclaimed for the modern hover-revealed handle on
 * draggable Cards — see the "Draggable card" block further down.
 */
.drag-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  position: relative;
  transition:
    transform var(--duration-instant) var(--ease-standard),
    box-shadow var(--duration-instant) var(--ease-standard);
}

/* Dragged-row treatment: children fade out so the row's content
 * vanishes; a ::before pseudo-element fades in as a full-shape drop
 * zone in the same slot. The slot keeps its original size in the
 * layout so cross-row dragging doesn't shift layout under the cursor
 * (which used to cause hover/dragover oscillation). The dragged row
 * is physically repositioned in the DOM by JS as the cursor crosses
 * row midpoints — the slot itself IS the drop zone. */
.drag-row > * {
  transition: opacity var(--duration-quick) var(--ease-standard);
}

.drag-row::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 2px dashed var(--border-strong);
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-quick) var(--ease-standard);
}

.drag-row.is-dragging > * {
  opacity: 0;
  pointer-events: none;
}

.drag-row.is-dragging::before {
  opacity: 1;
}

.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  color: var(--fg-subtle);
  flex-shrink: 0;
  user-select: none;
  line-height: 1;
  padding: 0.25rem 0.125rem;
  /* Suppresses the browser's default touch-scroll on the handle so a
   * touch press-and-move triggers the pointer-event drag rather than
   * scrolling the page. */
  touch-action: none;
  transition:
    color var(--duration-instant) var(--ease-standard),
    opacity var(--duration-quick) var(--ease-standard);
}

.drag-handle:hover {
  color: var(--fg-default);
}

/* Inside a .drag-row, the handle is hover-revealed — discoverable on
 * row hover/focus, invisible at rest so it doesn't compete with the
 * row's content. */
.drag-row > .drag-handle {
  opacity: 0;
}

.drag-row:hover > .drag-handle,
.drag-row:focus-within > .drag-handle,
.drag-row.is-selected > .drag-handle {
  opacity: 1;
}

/* Subtle "ready to lift" feedback — only when the drag origin is
 * targeted, not on any row hover. The :has() selector matches when
 * either the handle (gutter variant) or the header strip (card variant)
 * is hovered, so both placements get the lift. */
.drag-row:has(> .drag-handle:hover),
.drag-row:has(> .drag-row-header:hover) {
  transform: translateY(-1px);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}

.drag-row.is-dragging > .drag-handle {
  cursor: grabbing;
}

/* Mid-drag: suppress the hover affordances on every row in the same
 * collection as the dragged row. The 1px lift translate shifts layout
 * under the cursor, bouncing it in and out of hover and re-triggering
 * the lift — visible flicker. The handle hover-reveal has the same
 * problem at the opacity layer. The drop-zone placeholder is the
 * intended mid-drag affordance; row hover is not. */
*:has(> .drag-row.is-dragging) > .drag-row,
body[data-field-dragging] .drag-row,
body[data-option-dragging] .drag-row {
  transform: none;
  box-shadow: none;
}

*:has(> .drag-row.is-dragging) > .drag-row > .drag-handle,
body[data-field-dragging] .drag-row > .drag-handle,
body[data-option-dragging] .drag-row > .drag-handle {
  opacity: 0;
  pointer-events: none;
}

/* Header-strip variant: handle lives in a top bar instead of a left
 * gutter. For card-shaped items with natural top chrome (kanban cards,
 * attachment cards, expandable panels). The whole strip is the drag
 * origin via cursor: grab; the handle inside is a confirmation glyph
 * rather than the only grab target. Body stays fully interactive.
 *
 * Apply .drag-row--card on the row to switch from horizontal layout
 * (handle + body) to vertical (header + body). All other .drag-row
 * mechanics (::before drop zone, .is-dragging fade) still apply
 * because the header is just another direct child of the row. */
.drag-row.drag-row--card {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
}

.drag-row-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border-default);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  cursor: grab;
  user-select: none;
  font-size: var(--font-size-sm);
  font-weight: 500;
  color: var(--fg-default);
}

.drag-row-header > .drag-handle {
  opacity: 1;
}

.drag-row.is-dragging > .drag-row-header {
  cursor: grabbing;
}

/* Drag-over highlight for card-variant rows — left accent bar signals a
 * valid drop position. Matches the legacy .card-grid-item.is-drag-over
 * treatment from deprecated.css. */
.drag-row.drag-row--card.is-drag-over {
  box-shadow: inset 0.25rem 0 0 0 var(--color-admin);
}

/* Ghost "+ Add card" slot: dashed-outline button sized like a real card.
 * Renders as the trailing element of every category grid (including
 * Disabled). Click-only; drops are no-ops. Replaces .card-grid-item-ghost
 * and .card-grid-item-ghost-icon from deprecated.css. */
.card-grid-ghost {
  background: none;
  font-family: inherit;
  color: var(--fg-muted);
  cursor: pointer;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 10rem;
  padding: 0;
  transition:
    color var(--duration-quick) var(--ease-standard),
    border-color var(--duration-quick) var(--ease-standard);
}

.card-grid-ghost:hover,
.card-grid-ghost.is-drag-over {
  color: var(--fg-emphasis);
  border-color: var(--fg-emphasis);
}

.card-grid-ghost-icon {
  font-size: 2rem;
  line-height: 1;
}

/* Form-editor field-row helpers. These live here (not in the form-editor
 * resource) because they sit on top of .drag-row — the content wrapper,
 * the click-to-activate overlay, and the condition-badge slot that applies
 * to any drag-row that contains a form question. */

/* Content wrapper: fills the remaining row width and provides a positioned
 * ancestor for .drag-row-activate and .drag-row-badge. */
.drag-row-content {
  flex: 1;
  min-width: 0;
  position: relative;
}

/* Transparent overlay that covers the Input when the row is not selected.
 * Clicking it activates the row for editing. Removed when selected so
 * pointer events reach the Input directly. */
.drag-row-activate {
  position: absolute;
  inset: 0;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  z-index: 1;
}

/* Absolutely-positioned badge in the top-right corner of the content area.
 * Used for the "Condition not met" indicator. */
.drag-row-badge {
  position: absolute;
  top: 0.25rem;
  right: 0.25rem;
  z-index: 2;
  pointer-events: none;
}

/* Invalid state: red-tinted background and a left accent bar. */
.drag-row.is-invalid {
  background: var(--color-red-dim);
  border-left: 2px solid var(--color-red-border);
  padding-left: 0.5rem;
}

/* Textareas have a full border that starts at the top of the content box,
 * so a badge in the top-right corner overlaps it. Push the textarea down
 * (and its placeholder label with it) to clear the badge. */
.drag-row-content:has(> .drag-row-badge):has(.textarea) > .field {
  padding-top: 2rem;
}
.drag-row-content:has(> .drag-row-badge):has(.textarea) > .field > .textarea:placeholder-shown:not(:focus) ~ .field-label {
  top: 2.875rem;
}

/* ---------- Drop targets ----------
 * The drop zone visual is owned by .drag-row::before above — the dragged
 * row's own slot becomes the dashed placeholder, so no separate element
 * is created. The classes below are the trailing-slot affordance, the
 * between-items separator, and the empty-container drag-over treatment.
 * See /styleguide/drag-and-drop.html#dnd-drop-zones.
 */

/* Trailing add-button slot doubles as the "place last" drop target.
 * Same shape as the items in the collection so the affordance is
 * consistent with a real item. */
.drop-trailing {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.75rem 1rem;
  border: 1px dashed var(--border-default);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--font-size-sm);
  text-align: left;
  transition:
    border-color var(--duration-instant) var(--ease-standard),
    background var(--duration-instant) var(--ease-standard),
    color var(--duration-instant) var(--ease-standard);
}

.drop-trailing:hover {
  border-color: var(--border-strong);
  background: var(--bg-elevated);
  color: var(--fg-default);
}

/* Drag-over state for both the trailing slot and an empty-container body.
 * The container body becomes a drop target meaning "insert at end inside
 * this container." */
.drop-trailing.is-drag-over,
.drop-container.is-drag-over {
  border-color: var(--fg-emphasis);
  background: var(--bg-elevated);
  color: var(--fg-emphasis);
}

/* Generic spring-expanded modifier — a closed container that has been
 * hovered for the dwell duration during a nested-item drag and has opened
 * to allow the drop. Subtle accent outline distinguishes it from a
 * normally-open container. The category-specific predecessor is in
 * deprecated.css; this is the generic version that should replace it. */
.is-spring-expanded {
  outline: 2px solid var(--admin-border);
  outline-offset: -2px;
}


/* .field-row family moved to deprecated.css — see notes there. */

/* Animated collapsible row body. The grid-template-rows trick lets the
   element transition between 0fr and 1fr while keeping `auto`-sized
   content (no need to know the height up front). */
.collapsible {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-slow) var(--ease-standard);
}

.collapsible.is-open {
  grid-template-rows: 1fr;
}

.collapsible > .collapsible-inner {
  overflow: hidden;
  min-height: 0;
}

/* Category body wrapper. Same grid-template-rows idiom as .collapsible, but
   defaults to *open* (1fr) and collapses when the surrounding category drag
   begins — see body[data-category-dragging] rule below. The collapse turns
   the page into a short list of category headers so reordering large
   categories is a short drag instead of a long one. */
.category-body {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--duration-quick) var(--ease-standard);
}

.category-body > .category-body-inner {
  overflow: hidden;
  min-height: 0;
}

/* Flex (not grid) so a partial last row centers instead of hanging left.
   Trade-off vs. a grid: cards no longer stretch to fill the row — they sit
   at a fixed 20rem basis and every row centers via justify-content. */
.card-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-6);
  margin-top: var(--space-8);
}

/* Every direct child (card, card-link wrapper, ghost "+ Add" slot) gets the
   same fixed 20rem width; no grow/shrink so the row count stays stable. */
.card-grid > .card-link,
.card-grid > .card,
.card-grid > .card-grid-ghost {
  flex: 0 0 20rem;
}

/* Attachment card markup is generated by src/shared/attachment-card-html.ts —
   it ships inline-styled HTML for cross-email-client compatibility, and the
   same string is reused in-app via dangerouslySetInnerHTML so the admin's
   view matches what the recipient sees. No CSS classes here.

   The .attachment-card-host wrapper neutralizes the global `* { box-sizing:
   border-box }` reset for the injected markup. Email clients compute cell
   widths in content-box, and the inline pixel widths in the generator assume
   that — under border-box, the 36px type-badge cell loses 16px of padding
   from its content area, squeezing the badge so it overflows into the
   filename column. */

.attachment-card-host,
.attachment-card-host *,
.attachment-card-host *::before,
.attachment-card-host *::after {
  box-sizing: content-box;
}

.card-grid > .card-link {
  display: flex;
  text-decoration: none;
  color: inherit;
  border: none;
  min-height: 10rem;
}

.card-grid > .card-link > .card {
  width: 100%;
  height: 100%;
}

.card-grid > .card {
  min-height: 10rem;
}

/* ---------- Draggable card ----------
 * Card opts into drag-reorder by carrying .is-draggable plus a single
 * .card-drag-handle child as its first element. The handle is absolutely
 * positioned over the card's top-left corner and is hover-revealed. For
 * the pointer-driven path (CardDragProvider), the dragged card is filtered
 * out of the grid entirely while a drag is in flight — a fixed-position
 * .card-drag-ghost in a portal tracks the cursor, and a .card-placeholder
 * div is inserted at the predicted drop slot so the rest of the grid
 * reflows to show where the drop will land. For the legacy HTML5 path
 * (`drag` prop on Card, used by GroupedCardGrid), the dragged card
 * stays in place with .is-dragging fading its children. See
 * /styleguide/drag-and-drop.html. */
.card.is-draggable > * {
  transition: opacity var(--duration-quick) var(--ease-standard);
}

.card.is-draggable.is-dragging > * {
  opacity: 0;
  pointer-events: none;
}

.card-drag-handle {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.25rem 0.125rem;
  opacity: 0;
  /* Invisible at rest, so don't intercept pointer events either — otherwise
   * a click at the card's top-left corner hits the invisible handle and gets
   * swallowed instead of navigating via the wrapping Link. The handle becomes
   * interactive the moment the card is hovered/focused (paired with the
   * opacity fade-in below). */
  pointer-events: none;
  cursor: grab;
  color: var(--fg-subtle);
  user-select: none;
  /* Touch screens: prevent the browser's native pan/scroll from hijacking the
   * drag gesture before our pointermove listener sees it. */
  touch-action: none;
  line-height: 1;
  background: none;
  border: none;
  transition:
    opacity var(--duration-quick) var(--ease-standard),
    color var(--duration-instant) var(--ease-standard);
}

.card.is-draggable:hover > .card-drag-handle,
.card.is-draggable:focus-within > .card-drag-handle {
  opacity: 1;
  pointer-events: auto;
}

.card-drag-handle:hover {
  color: var(--fg-default);
}

/* Subtle "ready to lift" feedback when the drag handle is targeted. Mirrors
 * the .drag-row pattern (core.css ~1328). The lift uses --duration-instant
 * so it tracks the cursor rather than feeling like a tween. */
.card.is-draggable:has(> .card-drag-handle:hover) {
  transform: translateY(-1px);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}

/* While a drag is in flight, lock the cursor to "grabbing" globally and
 * disable text selection so the page doesn't paint a selection range while
 * the user drags. Set on <body> by CardDragProvider. */
body[data-card-dragging],
body[data-category-dragging],
body[data-field-dragging],
body[data-step-dragging],
body[data-option-dragging] {
  cursor: grabbing;
  user-select: none;
}

body[data-card-dragging] *,
body[data-category-dragging] *,
body[data-field-dragging] *,
body[data-step-dragging] *,
body[data-option-dragging] * {
  cursor: grabbing !important;
}

/* While a drag is in flight, give every card grid a minimum hit area so an
 * empty bucket (e.g. Disabled with no cards, or a source bucket whose only
 * card just lifted) stays a viable drop target for the provider's
 * elementFromPoint hit-test. */
body[data-card-dragging] .card-grid {
  min-height: 10rem;
}

/* Collapse-on-drag. When a category drag begins, collapse every category's
 * body to header-only height so the user reorders a short list of headers.
 * Same for a step drag: collapse every step's open body to header-only.
 * Both rules animate via the existing grid-template-rows: 0fr/1fr idiom on
 * .category-body and .collapsible respectively. The ghost stays full-size
 * (it's a snapshot of what was lifted); the placeholder consumer reads
 * `collapsedHeight` from the provider to match the new compact layout. */
body[data-category-dragging] .category-body {
  grid-template-rows: 0fr;
}

body[data-step-dragging] .card.is-static > .collapsible.is-open {
  grid-template-rows: 0fr;
}

/* ---------- Drag ghost ----------
 * Fixed-position floating clone of the dragged card. Portaled to <body>
 * by CardDragProvider; transform is updated imperatively on pointermove
 * to follow the cursor. The HTML is captured from the card's outerHTML
 * at grab time, so the ghost is visually identical to the source.
 * `pointer-events: none` keeps elementFromPoint resolving to the cards
 * underneath, not the ghost. */
.card-drag-ghost {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;
  pointer-events: none;
  opacity: 0.95;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  will-change: transform;
}

/* The dashed-outline slot that marks the drop target. Inserted into the
 * grid at the preview drop index while a drag is in flight. */
.card-placeholder {
  border: 2px dashed var(--border-strong);
  background: var(--bg-elevated);
  cursor: default;
  pointer-events: none;
}

.card-placeholder:hover {
  border-color: var(--border-strong);
  background: var(--bg-elevated);
}

/* Drop-target preview for the form editor. The provider filters the
 * dragged step / field out of the render (the portal ghost stands in
 * for it) and splices one of these placeholders in at the predicted
 * drop slot. Width/height are set inline by the consumer to match the
 * captured source rect so the layout doesn't lurch on grab. */
.step-placeholder {
  border: 2px dashed var(--border-strong);
  background: var(--bg-elevated);
  border-radius: var(--radius-md);
  margin-bottom: 1rem;
  pointer-events: none;
}

.field-placeholder {
  border: 2px dashed var(--border-strong);
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
  pointer-events: none;
}

.card-layout {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.card-header {
  display: block;
  font-size: var(--font-size-lg);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

/* ---------- Card image ----------
 * A link card can render an image edge-to-edge in place of its text header.
 * The <img> is absolutely positioned to fill the whole card (covering the
 * 2rem padding); object-fit: cover crops rather than distorts. When the name
 * is shown it rides a bottom scrim band so it stays legible over any image.
 * On <img> load error the React layer drops the image and falls back to the
 * normal .card-header text path. */
/* Image tiles are square: the grid already fixes width at 20rem, so a
   matching 20rem height makes the card a 20x20 tile. min-height (not height)
   so it grows the flex row against the grid's default 10rem min. */
.card-image-tile {
  min-height: 20rem;
}

.card-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card-image-label {
  position: absolute;
  inset: auto 0 0 0;
  padding: 0.75rem 1rem;
  background: var(--bg-scrim);
  color: var(--fg-on-scrim);
  font-size: var(--font-size-lg);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.card-edit {
  position: absolute;
  bottom: 2rem;
  right: 2rem;
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--fg-muted);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  transition: color var(--duration-quick) var(--ease-standard);
}

.card-edit:hover {
  color: var(--fg-emphasis);
}

/* ---------- Card grid header ----------
 * Stacked layout: Material Symbols icon centered above the section name.
 * Rendered by `<CardGrid>` when `icon` and `name` are supplied, and used
 * by `GroupedCardGrid` to title each bucket on the workflows and links
 * pages. Tokens only — no bespoke colors or spacing.
 */

.card-grid-section + .card-grid-section {
  margin-top: var(--space-8);
}

/* Drop-target preview for category reorders. The provider filters the
 * dragged section out of the render (the ghost stands in for it) and
 * splices this placeholder in at the predicted drop slot. The inline
 * height/width matches the captured source rect so the layout doesn't
 * lurch on grab. */
.card-grid-section-placeholder {
  border: 2px dashed var(--border-strong);
  background: var(--bg-elevated);
  border-radius: var(--radius-md);
  pointer-events: none;
}

.card-grid-section-placeholder + .card-grid-section,
.card-grid-section + .card-grid-section-placeholder {
  margin-top: var(--space-8);
}

/* .card-grid-section.is-spring-expanded moved to deprecated.css — replace
 * with the generic .is-spring-expanded primitive in the drag-handle
 * pattern block above. */

.card-grid-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  padding-bottom: var(--space-4);
}

/* When the header is also a drag source (admin mode, reorderable), the
 * nested <h3> can otherwise capture mousedown as a text-selection gesture,
 * which competes with native HTML5 drag-initiation on the parent <button>.
 * Disabling user-select lets the drag win consistently across categories. */
.card-grid-header.is-reorderable,
.card-grid-header.is-reorderable * {
  user-select: none;
  -webkit-user-select: none;
}

.card-grid-header.is-reorderable {
  cursor: grab;
}

.card-grid-header.is-reorderable:active {
  cursor: grabbing;
}

.card-grid-header-icon {
  position: relative;
  font-size: 2rem;
  color: var(--fg-emphasis);
  line-height: 1;
}

/* Hover-revealed grab handle floating to the left of the section icon.
 * Matches the card-drag-handle behavior: hidden at rest, fades in when the
 * section is hovered/focused, and stays out of the way mid-drag so only the
 * ghost reads as live. */
.card-grid-header-drag {
  position: absolute;
  top: 50%;
  right: 100%;
  transform: translateY(-50%);
  margin-right: var(--space-2);
  font-size: 1.25rem;
  opacity: 0;
  transition: opacity var(--duration-quick) var(--ease-standard);
}

.card-grid-section:hover .card-grid-header-drag,
.card-grid-section:focus-within .card-grid-header-drag {
  opacity: 1;
}

body[data-category-dragging] .card-grid-header-drag {
  opacity: 0;
  pointer-events: none;
}

.card-grid-header-name {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 200;
  color: var(--fg-emphasis);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* Edit affordance floating just right of the section icon (admin mode).
 * Mirrors `.card-grid-header-drag` on the opposite side, but unlike that
 * hover-revealed handle this stays visible at rest so admins have a clear
 * signal that the section is editable. Hidden during category drag so
 * the drag preview reads as the only live affordance. */
.card-grid-header-edit {
  position: absolute;
  top: 50%;
  left: 100%;
  transform: translateY(-50%);
  margin-left: var(--space-2);
  font-size: 1.25rem;
  background: none;
  border: none;
  padding: 0;
  line-height: 1;
  color: var(--fg-muted);
  cursor: pointer;
  transition: color var(--duration-quick) var(--ease-standard);
}

.card-grid-header-edit:hover {
  color: var(--fg-emphasis);
}

body[data-category-dragging] .card-grid-header-edit {
  opacity: 0;
  pointer-events: none;
}

/* Admin variant: clicking the header opens the edit modal. The whole
 * header becomes a button styled to look like the header itself.
 * `font: inherit` (not just `font-family`) is required because `<button>`
 * carries a UA `font:` shorthand that sets size/line-height/family in one
 * go — without this, the admin header renders at a different size than
 * the non-admin `<header>` variant. Padding is left to `.card-grid-header`
 * so the header-to-cards gap stays the same in both modes. */
.card-grid-header-button {
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  color: inherit;
  width: 100%;
  transition: opacity var(--duration-quick) var(--ease-standard);
}

.card-grid-header-button:hover {
  opacity: 0.7;
}

/* Ghost "+ New Category" slot: matches `.card-grid-header` layout but with
 * a dashed outline to read as a creation affordance. */
.card-grid-header-ghost {
  background: none;
  font-family: inherit;
  color: var(--fg-muted);
  cursor: pointer;
  border: 1px dashed var(--border-strong);
  padding: var(--space-6) var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  width: 100%;
  transition: color var(--duration-quick) var(--ease-standard), border-color var(--duration-quick) var(--ease-standard);
}

.card-grid-header-ghost:hover {
  color: var(--fg-emphasis);
  border-color: var(--fg-emphasis);
}

/* ---------- Icon picker ----------
 * Used inside `CategoryFormModal`. A search input on top of a scrollable
 * grid of Material Symbols cells. `content-visibility: auto` lets the
 * browser skip painting offscreen cells, which keeps ~2k icons responsive
 * without a virtualization library.
 */

.icon-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.icon-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
  gap: var(--space-1);
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid var(--border-default);
  padding: var(--space-2);
  background: var(--bg-surface);
}

.icon-picker-cell {
  background: none;
  border: 1px solid transparent;
  color: var(--fg-default);
  cursor: pointer;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-size: 1.5rem;
  padding: 0;
  content-visibility: auto;
  contain-intrinsic-size: 2.5rem 2.5rem;
  transition: border-color var(--duration-instant) var(--ease-standard), color var(--duration-instant) var(--ease-standard);
}

.icon-picker-cell:hover {
  color: var(--fg-emphasis);
  border-color: var(--border-strong);
}

.icon-picker-cell.is-selected {
  border-color: var(--fg-emphasis);
  color: var(--fg-emphasis);
}

.icon-picker-empty {
  color: var(--fg-muted);
  font-size: var(--font-size-sm);
  text-align: center;
  padding: var(--space-4);
}

/* ---------- Material Symbols utility ----------
 * Canonical Google class for inline icons. Pair with the font loaded in
 * index.html (the same `Material Symbols Outlined` family also referenced
 * via `font-family` in `.file-input-icon`, etc.).
 */

.material-symbols-outlined {
  font-family: "Material Symbols Outlined";
  font-weight: normal;
  font-style: normal;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-smoothing: antialiased;
  -webkit-font-feature-settings: "liga";
  font-feature-settings: "liga";
}

/* ---------- Disclosure ---------- */

.disclosure {
  border: 1px solid var(--border-default);
  border-radius: 0;
  overflow: hidden;
  background: var(--bg-surface);
}

.disclosure-trigger {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  background: transparent;
  color: var(--fg-muted);
  border: none;
  border-bottom: 1px solid transparent;
  border-radius: 0;
  padding: 0.875rem 1rem;
  font-size: var(--font-size-xs);
  font-weight: 500;
  font-family: inherit;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  cursor: pointer;
  transition: color var(--duration-instant) var(--ease-standard),
              background var(--duration-instant) var(--ease-standard),
              border-color var(--duration-instant) var(--ease-standard);
}

.disclosure-trigger:hover {
  color: var(--fg-emphasis);
}

.disclosure.is-open .disclosure-trigger {
  background: var(--bg-elevated);
  color: var(--fg-emphasis);
  border-bottom-color: var(--border-default);
}

.disclosure-chevron {
  color: var(--fg-subtle);
  flex-shrink: 0;
  transition: transform var(--duration-instant) var(--ease-standard);
}

.disclosure.is-open .disclosure-chevron {
  transform: rotate(90deg);
}

/* Reveal: drive the inner .collapsible off the wrapper's .is-open so a
   single toggle on .disclosure animates trigger color, chevron rotation,
   AND height all together. */
.disclosure.is-open > .collapsible {
  grid-template-rows: 1fr;
}

.disclosure-content {
  padding: 1rem;
  color: var(--fg-default);
}

/* ---------- Accordion ---------- */

/* Multiple progressive-disclosure sections presented as one unified
   component. The outer border wraps the whole strip; items abut with
   no gap, separated by a single shared border. Triggers carry a
   tinted background so the strip reads as one bar with sections, not
   a stack of independent cards. */
.accordion {
  border: 1px solid var(--border-default);
  background: var(--bg-surface);
}

.accordion-item + .accordion-item {
  border-top: 1px solid var(--border-default);
}

.accordion-trigger {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  background: var(--bg-elevated);
  color: var(--fg-default);
  border: none;
  border-bottom: 1px solid transparent;
  border-radius: 0;
  padding: 1rem 1.25rem;
  font-size: var(--font-size-sm);
  font-weight: 500;
  font-family: inherit;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  cursor: pointer;
  transition: color var(--duration-instant) var(--ease-standard),
              border-color var(--duration-instant) var(--ease-standard);
}

.accordion-trigger:hover {
  color: var(--fg-emphasis);
}

.accordion-item.is-open .accordion-trigger {
  color: var(--fg-emphasis);
  border-bottom-color: var(--border-default);
}

.accordion-chevron {
  color: var(--fg-subtle);
  flex-shrink: 0;
  transition: transform var(--duration-instant) var(--ease-standard);
}

.accordion-item.is-open .accordion-chevron {
  transform: rotate(90deg);
}

/* Reveal: drive the inner .collapsible off the item's .is-open. */
.accordion-item.is-open > .collapsible {
  grid-template-rows: 1fr;
}

.accordion-content {
  padding: 1rem 1.25rem;
  color: var(--fg-default);
  background: var(--bg-surface);
}

/* ---------- Stepper ---------- */

.stepper {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.stepper-step {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--fg-subtle);
  transition: color var(--duration-quick) var(--ease-standard);
}

.stepper-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--fg-subtle);
  font-size: var(--font-size-xs);
  font-weight: 500;
  flex-shrink: 0;
  transition: background var(--duration-quick) var(--ease-standard),
              border-color var(--duration-quick) var(--ease-standard),
              color var(--duration-quick) var(--ease-standard);
}

.stepper-step.is-current {
  color: var(--fg-emphasis);
}

.stepper-step.is-current .stepper-marker {
  border-color: var(--fg-emphasis);
  color: var(--fg-emphasis);
}

.stepper-step.is-done {
  color: var(--fg-default);
}

.stepper-step.is-done .stepper-marker {
  background: var(--fg-emphasis);
  border-color: var(--fg-emphasis);
  color: var(--bg-surface);
}

.stepper-connector {
  flex: 1;
  height: 1px;
  background: var(--border-default);
  min-width: 1.5rem;
  transition: background var(--duration-quick) var(--ease-standard);
}

.stepper-connector.is-done {
  background: var(--fg-emphasis);
}

/* ---------- Banners ---------- */

.banner {
  padding: 1rem 1.25rem;
  border-radius: 0;
  border: 1px solid;
  border-left-width: 3px;
  font-size: var(--font-size-sm);
  font-weight: 400;
  margin-bottom: 1rem;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.banner-warning {
  background: var(--warning-bg);
  border-color: var(--warning-border);
  border-left-color: var(--warning-fg);
  color: var(--warning-fg);
}

.banner-error {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  border-left-color: var(--danger-fg);
  color: var(--danger-fg);
}

.banner-success {
  background: var(--success-bg);
  border-color: var(--success-border);
  border-left-color: var(--success-fg);
  color: var(--success-fg);
}

.banner-info {
  background: var(--bg-elevated);
  border-color: var(--border-default);
  border-left-color: var(--fg-emphasis);
  color: var(--fg-default);
}

/* ---------- Badges ----------
 *
 * Fill model: two tiers by semantic weight.
 *
 *   Outlined — structural / neutral: .badge-neutral, .badge-primary
 *     Transparent background, border-only. For counts, labels, and
 *     tab indicators where the badge is chrome, not a status signal.
 *     Exception: inside .tab, .badge-primary is filled (bg-emphasis)
 *     to mark the selected tab unambiguously — see .tab rules below.
 *
 *   Filled — semantic / status: .badge-success, .badge-danger,
 *     .badge-admin, .badge-dev, .badge-onboarding
 *     Tinted background + matching border. For status indicators that
 *     carry meaning (success/failure, elevated access level, role). The
 *     tint is intentionally desaturated to coexist with the mono palette.
 */

.badge {
  display: inline-block;
  padding: 0.25rem 0.625rem;
  border-radius: 0;
  font-size: var(--font-size-xs);
  font-weight: 500;
  border: 1px solid;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.badge-neutral {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--fg-muted);
}

.badge-primary {
  background: transparent;
  border-color: var(--fg-emphasis);
  color: var(--fg-emphasis);
}

.badge-success {
  background: var(--success-bg);
  border-color: var(--success-border);
  color: var(--success-fg);
}

.badge-danger {
  background: var(--danger-bg);
  border-color: var(--danger-border);
  color: var(--danger-fg);
}

.badge-admin {
  background: var(--admin-bg);
  border-color: var(--admin-border);
  color: var(--admin-fg);
}

.badge-dev {
  background: var(--dev-bg);
  border-color: var(--dev-border);
  color: var(--dev-fg);
}

.badge-onboarding {
  background: var(--onboarding-bg);
  border-color: var(--onboarding-border);
  color: var(--onboarding-fg);
}

/* ---------- Tab strip ---------- */

.tab {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.tab .badge {
  text-decoration: none;
  transition: color var(--duration-instant) var(--ease-standard),
              border-color var(--duration-instant) var(--ease-standard),
              background var(--duration-instant) var(--ease-standard);
}

/* A state-driven tab renders as a <button>; strip the UA chrome so it sits
   flush with the anchor/span badges (buttons don't inherit font-family). */
.tab button.badge {
  margin: 0;
  font-family: inherit;
  line-height: normal;
  cursor: pointer;
}

/* Filled active tab — gives an unambiguous "this is the selected view"
   marker. Inactive tabs stay outlined via .badge-neutral. */
.tab .badge-primary {
  background: var(--fg-emphasis);
  color: var(--bg-surface);
  border-color: var(--fg-emphasis);
}

/* ---------- Auth layout ---------- */

.auth-layout {
  max-width: 22.5rem;
  margin: 5rem auto;
  padding: 0 1rem;
}

.auth-layout h2 {
  margin-bottom: 0.25rem;
  font-weight: 200;
  color: var(--fg-emphasis);
}

.auth-layout .auth-subtitle {
  color: var(--fg-muted);
  margin-bottom: 1.5rem;
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

/* ---------- Loading ---------- */

.spinner {
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  border: 1px solid var(--border-strong);
  border-top-color: var(--fg-emphasis);
  border-radius: 50%;
  animation: spinner-rotate 0.8s linear infinite;
}

.spinner-sm { width: 0.875rem; height: 0.875rem; }
.spinner-lg { width: 2rem; height: 2rem; border-width: 2px; }

/* ---------- Avatar ---------- */

/* Round profile image. The same class styles the <img> and the empty
   placeholder circle shown when a user has no avatar yet. */
.avatar {
  width: 5rem;
  height: 5rem;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  flex-shrink: 0;
}

/* ---------- Brand imagery ---------- */

.brand-logo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.brand-logo-lg {
  display: block;
  width: 100%;
  height: 6rem;
  object-fit: contain;
  margin-bottom: 2rem;
}

.page-header-main {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.page-header-leading {
  flex-shrink: 0;
  width: 4rem;
  height: 4rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.page-header-leading img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}


@keyframes spinner-rotate {
  to { transform: rotate(360deg); }
}

.dots {
  display: inline-flex;
  gap: 0.4rem;
  align-items: center;
}

.dots > span {
  display: inline-block;
  width: 0.375rem;
  height: 0.375rem;
  background: var(--border-strong);
  animation: dots-pulse 1.2s ease-in-out infinite;
}

.dots > span:nth-child(2) { animation-delay: 0.15s; }
.dots > span:nth-child(3) { animation-delay: 0.3s; }

@keyframes dots-pulse {
  0%, 100% { background: var(--border-strong); }
  50%      { background: var(--bg-emphasis); }
}

.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--skeleton-bg);
}

.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--skeleton-shimmer) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
}

.skeleton-bar {
  display: block;
  height: 0.875rem;
}

.skeleton-bar + .skeleton-bar {
  margin-top: 0.5rem;
}

.skeleton-bar.skeleton-sm    { height: 0.75rem; max-width: 40%; }
.skeleton-bar.skeleton-md    { height: 0.875rem; max-width: 70%; }
.skeleton-bar.skeleton-lg    { height: 1.25rem; }
.skeleton-bar.skeleton-block { height: 5rem; }

@keyframes skeleton-shimmer {
  100% { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton::after { animation: none; }
}

.progress {
  display: block;
  width: 100%;
  height: 0.125rem;
  background: var(--border-default);
  position: relative;
  overflow: hidden;
}

.progress::after {
  content: "";
  position: absolute;
  left: -35%;
  top: 0;
  width: 35%;
  height: 100%;
  background: var(--bg-emphasis);
  animation: progress-slide 1.4s ease-in-out infinite;
}

@keyframes progress-slide {
  0%   { left: -35%; }
  100% { left: 100%; }
}

/* ---------- Animations ---------- */

@keyframes anim-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes anim-slide-up {
  from { opacity: 0; transform: translateY(0.5rem); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes anim-slide-in {
  from { opacity: 0; transform: translateX(-0.5rem); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes anim-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

@keyframes anim-slide-down {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(0.75rem); }
}

@keyframes anim-slide-out {
  from { opacity: 1; transform: translateX(0); }
  to   { opacity: 0; transform: translateX(0.5rem); }
}

.anim-fade-in   { animation: anim-fade-in 0.5s ease both; }
.anim-slide-up  { animation: anim-slide-up 0.5s ease both; }
.anim-slide-in  { animation: anim-slide-in 0.5s ease both; }
.anim-fade-out  { animation: anim-fade-out 0.5s ease both; }
.anim-slide-down { animation: anim-slide-down 0.5s ease both; }
.anim-slide-out { animation: anim-slide-out 0.5s ease both; }

/* Home entrance sequence: the avatar scales into focus first, then the card
   grid cascades in beneath it a beat later (140ms lead). Reads top-to-bottom
   as one motion — you resolve into focus, your cards follow. Pure CSS — fires
   once as nodes insert, so React Query refetches (reusing keyed DOM nodes)
   don't replay it. --duration-standard collapses to ~0 under reduced-motion. */
@keyframes anim-scale-in {
  from { opacity: 0; transform: scale(0.92); }
  to   { opacity: 1; transform: scale(1); }
}
.home-enter .avatar {
  animation: anim-scale-in var(--duration-standard) var(--ease-out) both;
}

/* Cards slide up in sequence, offset by the avatar's 140ms lead. Stagger is
   capped at 8 so a long list doesn't animate for seconds; cards past the cap
   share the last delay. */
.home-enter .card-grid > .card,
.home-enter .card-grid > .card-link {
  animation: anim-slide-up var(--duration-standard) var(--ease-out) both;
}
.home-enter .card-grid > :nth-child(1) { animation-delay: 140ms; }
.home-enter .card-grid > :nth-child(2) { animation-delay: 200ms; }
.home-enter .card-grid > :nth-child(3) { animation-delay: 260ms; }
.home-enter .card-grid > :nth-child(4) { animation-delay: 320ms; }
.home-enter .card-grid > :nth-child(5) { animation-delay: 380ms; }
.home-enter .card-grid > :nth-child(6) { animation-delay: 440ms; }
.home-enter .card-grid > :nth-child(7) { animation-delay: 500ms; }
.home-enter .card-grid > :nth-child(n + 8) { animation-delay: 560ms; }

/* Default page entrance: content that isn't already animated fades in on
   navigation. `:has(.panel)` pages already slide their panel up (see .panel);
   `.home-enter` runs its own avatar+card sequence — both are excluded so
   nothing double-animates. Fires once on route mount (in-page refetches reuse
   the node), and collapses to instant under reduced-motion via the token. */
.content:not(:has(.panel)):not(.home-enter) {
  animation: anim-fade-in var(--duration-quick) var(--ease-out) both;
}

/* ---------- Toasts ---------- */

.toast {
  display: inline-flex;
  align-items: center;
  gap: 0.875rem;
  padding: 0.875rem 1.125rem;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--border-emphasis);
  color: var(--fg-default);
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  min-width: 16.25rem;
  animation: anim-slide-up 0.5s ease both;
}

.toast.anim-slide-down {
  animation: anim-slide-down 0.5s ease both;
}

.toast-success {
  border-color: var(--success-border);
  border-left-color: var(--success-fg);
  color: var(--success-fg);
  background: var(--success-bg);
}

.toast-error {
  border-color: var(--danger-border);
  border-left-color: var(--danger-fg);
  color: var(--danger-fg);
  background: var(--danger-bg);
}

.toast-info {
  border-left-color: var(--fg-emphasis);
}

.toast-warning {
  border-color: var(--warning-border);
  border-left-color: var(--warning-fg);
  color: var(--warning-fg);
  background: var(--warning-bg);
}

.toast-stack {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  align-items: flex-start;
}

.toast-stack-fixed {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  z-index: 1000;
  align-items: flex-end;
  pointer-events: none;
}

.toast-stack-fixed .toast-slot {
  pointer-events: auto;
}

.toast-message {
  flex: 1;
  min-width: 0;
}

/* Keyboard-focusable dismiss control; inherits the variant color from .toast. */
.toast-dismiss {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: -0.25rem -0.25rem -0.25rem 0;
  padding: 0.25rem;
  background: none;
  border: none;
  color: inherit;
  font-size: var(--font-size-sm);
  line-height: 1;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--duration-instant) var(--ease-standard);
}

.toast-dismiss:hover {
  opacity: 1;
}

/* ---------- Modal ---------- */

/* Legacy div-based modal — used by the static styleguide demo. The live
   React Modal component uses a native <dialog> (rules below). */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.75);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1100;
  animation: anim-fade-in var(--duration-standard) var(--ease-out) both;
}

.modal-backdrop.is-exiting {
  animation: anim-fade-out var(--duration-standard) var(--ease-in) both;
}

.modal-dialog {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  padding: var(--space-8);
  max-width: 28rem;
  width: calc(100% - 2rem);
}

.modal-dialog:not(dialog) {
  animation: anim-slide-up var(--duration-standard) var(--ease-out) both;
}

.modal-dialog:not(dialog).is-exiting {
  animation: anim-slide-down var(--duration-standard) var(--ease-in) both;
}

/* Native <dialog> usage — enter/exit driven by the [open] attribute,
   @starting-style, and transition-behavior: allow-discrete so the
   browser can transition display/overlay across the discrete switch. */
dialog.modal-dialog {
  color: inherit;
  opacity: 0;
  transform: translateY(0.5rem);
  transition:
    opacity var(--duration-standard) var(--ease-in),
    transform var(--duration-standard) var(--ease-in),
    overlay var(--duration-standard) var(--ease-in) allow-discrete,
    display var(--duration-standard) var(--ease-in) allow-discrete;
}

dialog.modal-dialog[open] {
  opacity: 1;
  transform: translateY(0);
  transition-timing-function: var(--ease-out);
}

@starting-style {
  dialog.modal-dialog[open] {
    opacity: 0;
    transform: translateY(0.5rem);
  }
}

dialog.modal-dialog::backdrop {
  background: rgba(0, 0, 0, 0.75);
  opacity: 0;
  transition:
    opacity var(--duration-standard) var(--ease-in),
    overlay var(--duration-standard) var(--ease-in) allow-discrete,
    display var(--duration-standard) var(--ease-in) allow-discrete;
}

dialog.modal-dialog[open]::backdrop {
  opacity: 1;
  transition-timing-function: var(--ease-out);
}

@starting-style {
  dialog.modal-dialog[open]::backdrop {
    opacity: 0;
  }
}

/* Lock background scroll while a modal is open — replaces the JS
   body.style.overflow hack the old Modal component had to do. */
body:has(dialog.modal-dialog[open]) {
  overflow: hidden;
}

/* Wider variant for modals that host a grid or large picker. */
.modal-dialog.is-wide {
  max-width: 36rem;
}

.modal-title {
  font-weight: 200;
  font-size: 1.25rem;
  color: var(--fg-emphasis);
  margin: 0 0 var(--space-4) 0;
}

.modal-description {
  color: var(--fg-muted);
  font-size: var(--font-size-sm);
  margin: 0 0 var(--space-6) 0;
}

.modal-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
}

/* ---------- File input ---------- */

.file-input-area {
  /* Rendered as a <button> so it's keyboard-operable and gets the global
     focus ring; these resets strip the native button chrome. */
  width: 100%;
  font: inherit;
  color: inherit;
  border: 2px dashed var(--border-default);
  background: none;
  padding: var(--space-6) var(--space-4);
  text-align: center;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  transition:
    border-color var(--duration-quick) var(--ease-standard),
    background var(--duration-quick) var(--ease-standard);
}

.file-input-progress {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--fg-muted);
}
.file-input-area:hover {
  border-color: var(--border-strong);
  background: var(--bg-surface);
}
.file-input-area.is-dragging {
  border-color: var(--fg-emphasis);
  background: var(--bg-elevated);
}
.file-input-area.is-disabled {
  cursor: default;
  opacity: 0.5;
}
.file-input-area.is-invalid {
  border-color: var(--danger-emphasis);
}

.file-input-icon {
  font-family: "Material Symbols Outlined";
  font-size: 1.5rem;
  color: var(--fg-muted);
  line-height: 1;
  font-weight: normal;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
}

.file-input-label {
  font-size: var(--font-size-sm);
  color: var(--fg-muted);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.file-input-status {
  font-size: var(--font-size-sm);
  color: var(--fg-muted);
}

.file-input-list {
  list-style: none;
  padding: 0;
  margin: var(--space-2) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.file-input-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
}

.file-input-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-sm);
  color: var(--fg-default);
}

.file-input-remove {
  background: none;
  border: none;
  color: var(--danger-fg);
  font-size: var(--font-size-xs);
  cursor: pointer;
  padding: var(--space-1);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}
.file-input-remove:hover {
  color: var(--danger-emphasis);
}

.file-input-error {
  margin: var(--space-1) 0 0;
  font-size: var(--font-size-xs);
  color: var(--danger-emphasis);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

/* ---------- Data table ---------- */

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-sm);
  color: var(--fg-default);
}

.data-table thead tr {
  border-bottom: 1px solid var(--border-strong);
  text-align: left;
}

.data-table th {
  padding: var(--space-3);
  font-size: var(--font-size-xs);
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-weight: 500;
}

.data-table th.is-sortable {
  cursor: pointer;
  user-select: none;
}

.data-table th.is-sortable::after {
  font-family: "Material Symbols Outlined";
  font-size: 1.125rem;
  vertical-align: middle;
  margin-left: var(--space-1);
  content: "swap_vert";
  opacity: 0.4;
}

.data-table th.is-sortable.sort-asc::after {
  content: "arrow_upward_alt";
  opacity: 1;
}

.data-table th.is-sortable.sort-desc::after {
  content: "arrow_downward_alt";
  opacity: 1;
}

.data-table td {
  padding: var(--space-4) var(--space-3);
  font-variant-numeric: tabular-nums;
}

/* Anchor the lead column so rows scan as records, not undifferentiated bands. */
.data-table td:first-child {
  color: var(--fg-emphasis);
}

.data-table tbody tr {
  border-bottom: 1px solid var(--border-default);
}

.data-table tbody tr:hover {
  background: var(--bg-elevated);
}

.data-table .data-table-empty {
  padding: var(--space-8);
  color: var(--fg-muted);
  text-align: center;
}

/* Trailing actions column: right-aligned button group, consistent spacing,
   no wrapping so row heights stay uniform when actions would otherwise
   break onto a second line. Pair with .actions on the matching <th> to
   right-align the header label. */
.data-table th.actions {
  text-align: right;
}

.data-table td.actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  white-space: nowrap;
}

/* Toolbar — horizontal row of controls with grouped sections */

.toolbar {
  display: flex;
  align-items: flex-end;
  gap: var(--space-4);
}

.toolbar-group {
  display: flex;
  align-items: flex-end;
  gap: var(--space-4);
}

.toolbar-group:last-child {
  margin-left: auto;
}

.toolbar .field {
  margin-bottom: 0;
  flex: 1;
  max-width: 17.5rem;
}

/* Bare selects in a toolbar (no .field wrapper — e.g. pagination's
   "25 / page" picker) must size to their content. The base .select
   sets width: 100%, which inside a flex toolbar-group makes the
   select greedy and collapses sibling buttons to min-content, where
   short labels like "Prev"/"Next" wrap one character per line. */
.toolbar .select {
  width: auto;
}

/* Data table: toolbar + pagination wrappers */

.data-table-toolbar {
  padding-bottom: var(--space-4);
}

.data-table-pagination {
  padding-top: var(--space-4);
}

.data-table-pagination .toolbar-group {
  align-items: center;
  gap: var(--space-2);
}

.data-table-pagination-info {
  font-size: var(--font-size-xs);
  color: var(--fg-muted);
  letter-spacing: var(--tracking-wide);
}

/* Token menu — floating listbox used by `@`-trigger autocomplete in the
   trigger editor. Sits over an input/textarea, anchored at the caret.
   Pairs `--bg-page` with `--fg-default` so the surface contrasts against
   inline content and reads correctly in both themes. */

.token-menu {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--bg-page);
  color: var(--fg-default);
  border: 1px solid var(--border-strong);
  max-height: 12rem;
  overflow-y: auto;
  min-width: 12rem;
  z-index: 100;
  box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.25);
}

.token-menu-item {
  padding: 0.4rem 0.75rem;
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--fg-default);
}

.token-menu-item.is-selected {
  background: var(--bg-elevated);
}

.token-menu-empty {
  padding: 0.5rem 0.75rem;
  color: var(--fg-muted);
  font-size: var(--font-size-sm);
}

/* ---------- Markdown content ----------
   Scoped wrapper for author-written prose rendered inside form fields
   (kind === "markdown"). Headings are offset h3-h6 by the render module,
   so this block tunes vertical rhythm and the block-level primitives
   (lists, blockquote, code, tables, images). All colors come from
   semantic tokens; no palette tokens or hex are used. */
.markdown-content {
  color: var(--fg-default);
  font-size: var(--font-size-input);
  line-height: var(--leading-relaxed);
  max-width: var(--measure);
  /* Prose is always left-aligned. Set explicitly so it doesn't inherit
     centering from a container that opts into `.card-centered`. */
  text-align: left;
}

.markdown-content > * + * {
  margin-top: var(--space-3);
}

.markdown-content h3,
.markdown-content h4,
.markdown-content h5,
.markdown-content h6 {
  margin-top: var(--space-6);
  line-height: var(--leading-normal);
}

.markdown-content h3 { font-size: var(--font-size-md); }
.markdown-content h4 { font-size: var(--font-size-base); letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--fg-muted); }
.markdown-content h5 { font-size: var(--font-size-sm); letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--fg-muted); }
.markdown-content h6 { font-size: var(--font-size-xs); letter-spacing: var(--tracking-wider); text-transform: uppercase; color: var(--fg-muted); }

.markdown-content p {
  margin: 0;
}

.markdown-content ul,
.markdown-content ol {
  margin: 0;
  padding-left: 1.5rem;
}

.markdown-content li + li {
  margin-top: var(--space-1);
}

.markdown-content a {
  /* Inherit the base anchor styling so links inside prose match links
     elsewhere in the app (subtle underline, emphasis on hover). */
  color: var(--fg-emphasis);
  border-bottom: 1px solid var(--fg-subtle);
}

.markdown-content blockquote {
  margin: 0;
  padding: var(--space-1) var(--space-4);
  border-left: 2px solid var(--border-strong);
  color: var(--fg-muted);
}

.markdown-content pre {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  font-family: var(--font-family-mono);
  font-size: var(--font-size-sm);
  line-height: var(--leading-normal);
  overflow-x: auto;
}

.markdown-content code {
  font-family: var(--font-family-mono);
  font-size: 0.9em;
}

.markdown-content pre code {
  font-size: inherit;
}

.markdown-content hr {
  margin: var(--space-6) 0;
  border: 0;
  border-top: 1px solid var(--border-default);
}

.markdown-content img {
  max-width: 100%;
  height: auto;
  display: block;
}

.markdown-content table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--font-size-base);
}

.markdown-content th,
.markdown-content td {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-default);
  text-align: left;
}

.markdown-content th {
  color: var(--fg-emphasis);
  font-weight: 500;
  background: var(--bg-elevated);
}

/* ---------- Reduced motion ---------- */

/* Honors the user's `prefers-reduced-motion: reduce` setting.
   Strategy: collapse decorative motion (entrance/exit helpers, hover
   transitions) to near-zero by retargeting the duration tokens, but keep
   loading-feedback loops at full rhythm — those are functional, not
   decorative, and disabling them removes the only signal the user has
   that work is in flight. The skeleton shimmer is the one exception
   (already disabled in its own block) since the placeholder shape itself
   carries the meaning. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-instant: 0.01ms;
    --duration-quick: 0.01ms;
    --duration-standard: 0.01ms;
    --duration-slow: 0.01ms;
  }

  .anim-fade-in,
  .anim-fade-out,
  .anim-slide-up,
  .anim-slide-down,
  .anim-slide-in,
  .anim-slide-out,
  .home-enter .avatar,
  .home-enter .card-grid > .card,
  .home-enter .card-grid > .card-link {
    animation: none;
  }

  /* Cancel the staggered entrance delays too — the duration collapses via
     the token, but the hardcoded delays would otherwise hold cards hidden. */
  .home-enter .card-grid > * {
    animation-delay: 0ms;
  }
}

