/* ==========================================================================
   Design System Components — Attest (Yozu)
   Ported from Compass (originally Clarity). Token layer lives in
   app/assets/tailwind/tokens.css, driven by the [data-theme] attribute on
   <html>. This file defines component classes that consume those tokens
   via var(--token-name). Only the sections consumed by the ported
   component set are carried over.

   Do NOT add new :root / :host token declarations here — extend tokens.css.

   Layer order: this file is linked BEFORE the Tailwind build, so it is
   the first stylesheet to name the layers. Declaring Tailwind's own
   order here means anything we put in @layer components sits above
   preflight (base) and below the utilities — so `w-64` or `pl-9` on a
   .form-input still wins. Unlayered rules in this file beat utilities
   outright; keep that for deliberate overrides only.
   ========================================================================== */
@layer theme, base, components, utilities;

/* --------------------------------------------------------------------------
   Data Tables (components/_table + _table_row + _data_table)
   -------------------------------------------------------------------------- */
.data-table-wrap {
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: 12px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
/* Horizontal-scroll container INSIDE the chrome. Scrolling lives on its
   own element so toolbar rows (quick filter / title) can sit inside the
   card without scrolling with the table. */
.data-table-scroll {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
  /* Scroll-shadow on the right edge when the table overflows its
     wrapper. Fixed-mode tables keep this cue on phones; card-mode tables
     clear it with their overflow below. */
  background:
    linear-gradient(to right, var(--surface-card) 30%, transparent),
    linear-gradient(to left, var(--surface-card) 30%, transparent),
    linear-gradient(to right, var(--table-scroll-cue), transparent 30%),
    linear-gradient(to left, var(--table-scroll-cue), transparent 30%);
  background-attachment: local, local, scroll, scroll;
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-size: 24px 100%, 24px 100%, 12px 100%, 12px 100%;
}
.data-table-scroll::-webkit-scrollbar {
  height: 8px;
}
.data-table-scroll::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border: 2px solid transparent;
  border-radius: 999px;
  background-clip: padding-box;
}
.data-table-wrap__header {
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border-subtle);
}
.data-table-wrap__title {
  font-family: var(--font-display);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-primary);
}

.data-table {
  width: 100%;
  border-collapse: collapse;
}
.data-table thead th {
  padding: 0.75rem 1.5rem;
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  text-align: left;
  border-bottom: 1px solid var(--border-subtle);
  position: sticky;
  top: 0;
  background: var(--surface-table-header);
  z-index: 1;
}
.data-table thead th.text-right {
  text-align: right;
}
.data-table tbody tr {
  border-bottom: 1px solid var(--border-subtle);
  transition: background var(--transition-fast);
}
.data-table tbody tr:last-child {
  border-bottom: none;
}
.data-table tbody tr:hover {
  background: var(--surface-hover);
}
.data-table tbody td {
  padding: 0.75rem 1.5rem;
  font-size: 0.875rem;
  color: var(--text-secondary);
}
.data-table tbody td.text-right {
  text-align: right;
}
.data-table tbody td .font-mono,
.data-table tbody td.font-mono {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--text-primary);
}
.data-table .link {
  color: var(--text-primary);
  font-weight: 500;
  text-decoration: none;
  transition: color var(--transition-fast);
}
.data-table .link:hover {
  color: var(--accent-dim);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Responsive table — card mode below md, sticky first column above md.
   -------------------------------------------------------------------------- */
@media (min-width: 768px) {
  /* Sticky first column on horizontal scroll for tabular layout. */
  .data-table tbody td:first-child,
  .data-table thead th:first-child {
    position: sticky;
    left: 0;
    background: var(--surface-card);
    z-index: 2;
  }
  .data-table thead th:first-child {
    background: var(--surface-table-header);
    z-index: 3;
  }
  .data-table tbody tr:hover td:first-child {
    background: var(--surface-hover);
  }
}

@media (max-width: 767px) {
  /* Stack each row as a card; visually hide the thead. */
  .data-table-scroll:not(.data-table-scroll--fixed) {
    overflow-x: visible;
    background: none;
  }
  .data-table:not(.data-table--fixed),
  .data-table:not(.data-table--fixed) thead,
  .data-table:not(.data-table--fixed) tbody,
  .data-table:not(.data-table--fixed) tr,
  .data-table:not(.data-table--fixed) th,
  .data-table:not(.data-table--fixed) td {
    display: block;
    width: 100%;
  }
  .data-table:not(.data-table--fixed) thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }
  .data-table:not(.data-table--fixed) tbody tr {
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    margin: 0.5rem 0;
    padding: 0.5rem 0.75rem;
    background: var(--surface-card);
  }
  .data-table:not(.data-table--fixed) tbody td {
    border: none;
    padding: 0.375rem 0;
    text-align: left !important;
  }
  /* `data-label="Column"` rendered as a small caption before the cell
     content so users can identify the field without the header row. */
  /* Collapsed expansion rows (components/_table expansion:) stay hidden
     in card mode too — the block rule above would otherwise beat [hidden]. */
  .data-table:not(.data-table--fixed) tr[hidden] {
    display: none;
  }
  .data-table:not(.data-table--fixed) tbody td[data-label]:not([data-label=""])::before {
    content: attr(data-label) ":\00a0";
    display: inline;
    color: var(--text-secondary);
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-right: 0.25rem;
  }
  .data-table:not(.data-table--fixed) tbody td[data-empty="true"][data-label]:not([data-label=""])::after {
    content: "—";
    color: var(--text-tertiary);
  }
  .data-table:not(.data-table--fixed) tbody td[data-empty="true"]:not([data-label]) {
    display: none;
  }
}

/* Triage carries enough columns and an expansion control that its tabular
   layout is cramped below Tailwind's lg breakpoint. Other data tables
   switch at the usual md point; lg and above use their tabular layout. */
@media (min-width: 768px) and (max-width: 1023px) {
  .data-table-scroll:has(> .data-table--cards-through-lg) {
    overflow-x: visible;
    background: none;
  }
  .data-table--cards-through-lg,
  .data-table--cards-through-lg thead,
  .data-table--cards-through-lg tbody,
  .data-table--cards-through-lg tr,
  .data-table--cards-through-lg th,
  .data-table--cards-through-lg td {
    display: block;
    width: 100%;
  }
  .data-table--cards-through-lg thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }
  .data-table--cards-through-lg tbody tr {
    border: 1px solid var(--border-subtle);
    border-radius: 8px;
    margin: 0.5rem 0;
    padding: 0.5rem 0.75rem;
    background: var(--surface-card);
  }
  .data-table--cards-through-lg tbody td,
  .data-table--cards-through-lg tbody td:first-child {
    position: static;
    border: none;
    padding: 0.375rem 0;
    text-align: left !important;
    background: transparent;
  }
  .data-table--cards-through-lg tr[hidden] {
    display: none;
  }
  .data-table--cards-through-lg tbody td[data-label]:not([data-label=""])::before {
    content: attr(data-label) ":\00a0";
    display: inline;
    margin-right: 0.25rem;
    color: var(--text-secondary);
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }
  .data-table--cards-through-lg tbody td[data-empty="true"][data-label]:not([data-label=""])::after {
    content: "—";
    color: var(--text-tertiary);
  }
  .data-table--cards-through-lg tbody td[data-empty="true"]:not([data-label]) {
    display: none;
  }
}


/* --------------------------------------------------------------------------
   Horizontally scrollable tab strips
   -------------------------------------------------------------------------- */
.scrollable-strip {
  overscroll-behavior-inline: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.scrollable-strip::-webkit-scrollbar {
  height: 6px;
}
.scrollable-strip::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border: 1px solid transparent;
  border-radius: 999px;
  background-clip: padding-box;
}
.scrollable-strip[data-scrollable-strip-left="true"][data-scrollable-strip-right="true"] {
  mask-image: linear-gradient(to right, transparent, black 1.5rem, black calc(100% - 1.5rem), transparent);
}
.scrollable-strip[data-scrollable-strip-left="true"][data-scrollable-strip-right="false"] {
  mask-image: linear-gradient(to right, transparent, black 1.5rem, black 100%);
}
.scrollable-strip[data-scrollable-strip-left="false"][data-scrollable-strip-right="true"] {
  mask-image: linear-gradient(to right, black 0, black calc(100% - 1.5rem), transparent);
}


/* --------------------------------------------------------------------------
   Data table shell — one unified card: toolbar + table + pagination.
   The shell carries the card chrome; the inner .data-table-wrap renders
   flush (no border/radius/shadow) so the pieces read as a single surface.
   -------------------------------------------------------------------------- */
.data-table-shell {
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  border-radius: 12px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.data-table-wrap--flush {
  border: none;
  border-radius: 0;
  box-shadow: none;
}

/* Server-sorted column headers: the whole header cell is the link.
   The th drops its own padding so the link's hit area fills the cell. */
.data-table thead th.data-table__th--sort {
  padding: 0;
}
.data-table__sort-link {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  width: 100%;
  padding: 0.75rem 1.5rem;
  color: inherit;
  text-decoration: none;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.data-table__sort-link--numeric {
  justify-content: flex-end;
}
.data-table__sort-link:hover,
.data-table__sort-link:focus-visible {
  background: var(--surface-hover);
  color: var(--text-primary);
}
/* Sortable-but-inactive affordance: chevron-up-down fades in on hover/focus. */
.data-table__sort-hint {
  opacity: 0;
  transition: opacity var(--transition-fast);
}
.data-table__sort-link:hover .data-table__sort-hint,
.data-table__sort-link:focus-visible .data-table__sort-hint {
  opacity: 1;
}

/* Toolbar filter controls: native selects styled as compact dropdown
   buttons with an embedded funnel icon (left) and the form-select
   chevron (right). Scoped under .data-table-filter so this beats the
   .form-select base rule regardless of source order — .form-select's
   `padding` shorthand otherwise clobbers the left inset and the text
   renders underneath the funnel icon. padding-right must clear the
   chevron background-image (12px at right 0.625rem). */
.data-table-filter .data-table-filter__select {
  height: 38px;
  max-width: 13rem;
  padding: 0 2rem 0 1.9rem;
  font-size: 0.8125rem;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
/* Pagination per-page select (components/_pagination): the compact
   sibling of the filter select, with room for the chevron. */
.pagination__per {
  height: 30px;
  width: auto;
  padding: 0 1.9rem 0 0.625rem;
  font-size: 0.75rem;
}
.data-table-filter--active .data-table-filter__icon {
  color: var(--accent);
}
.data-table-filter--active .data-table-filter__select {
  border-color: var(--accent);
  color: var(--text-primary);
}


/* --------------------------------------------------------------------------
   Form Controls (components/_form_field)

   ONE sizing contract for every control, hand-rolled or component-built:
     height        38px  (= the md button: 20px line + 2×8px pad + 2×1px border)
     radius        var(--radius-md) = 8px (the `rounded-md` utility)
     font          0.875rem (text-sm)
     border        1px var(--border-default), hover var(--border-strong)
     background    var(--surface-card)
     focus         accent border + 3px soft accent glow (no extra outline)
     error         var(--status-danger) border (aria-invalid)
   Labels are `.form-label` (text-xs font-medium text-secondary), hints
   `.form-hint`, errors `.form-error`. Inputs, selects and buttons on one
   row must sit on the same 38px baseline — change it here, nowhere else.
   The block lives in @layer components so width/padding/text utilities
   on a call site (`w-64`, `pl-9`, `text-xs`) still override it.
   -------------------------------------------------------------------------- */
@layer components {
  .form-label {
    display: block;
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--text-secondary);
  }

  .form-input,
  .form-select,
  .form-textarea {
    display: block;
    width: 100%;
    font-size: 0.875rem;
    line-height: 1.25rem;
    color: var(--text-primary);
    background-color: var(--surface-input, var(--surface-card));
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    outline: none;
  }
  .form-input,
  .form-select {
    height: 38px;
  }
  .form-input {
    padding: 0 0.75rem;
    transition: border-color var(--transition-fast),
                box-shadow var(--transition-fast);
  }
  [type="search"]::-webkit-search-cancel-button {
    appearance: none;
  }
  .form-textarea {
    min-height: 5.5rem;
    padding: 0.5rem 0.75rem;
    resize: vertical;
    transition: border-color var(--transition-fast),
                box-shadow var(--transition-fast);
  }
  .form-input::placeholder,
  .form-textarea::placeholder {
    color: var(--text-muted);
  }
  .form-input:hover,
  .form-select:hover,
  .form-textarea:hover {
    border-color: var(--border-strong);
  }
  .form-input:focus,
  .form-select:focus,
  .form-textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
  }
  .form-input[aria-invalid="true"],
  .form-select[aria-invalid="true"],
  .form-textarea[aria-invalid="true"] {
    border-color: var(--status-danger);
  }
  .form-input:disabled,
  .form-select:disabled,
  .form-textarea:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
  .form-input--mono {
    font-family: var(--font-mono);
    font-size: 0.8125rem;
  }

  /* Native date/time controls need enough inline room for their final
     digits plus the browser-owned picker button. Without both constraints,
     narrow mobile grid tracks paint the glyph over the value. */
  .form-input--native-picker {
    min-width: 10.5rem;
    padding-right: 2.5rem;
  }

  .form-select {
    padding: 0 2rem 0 0.75rem;
    /* SVG arrow uses currentColor so it tracks the text colour in light
       and dark themes. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5l3 3 3-3' stroke='currentColor' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.625rem center;
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    /* No transition: a native <select> spawns an OS picker on click, and an
       in-flight border/shadow animation underneath reads as a stutter while
       it opens (production UAT, 2026-08-21). */
  }

  /* Checkbox: 16px square on the same border/accent tokens, so a ticked
     box reads as the same family as a focused input. */
  .form-checkbox {
    width: 1rem;
    height: 1rem;
    flex: none;
    accent-color: var(--accent);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-sm);
    cursor: pointer;
  }

  /* Radio: the checkbox's round twin (profile settings' digest and theme
     choices). */
  /* appearance-none: accent-color alone leaves the UNCHECKED disc native
     white in dark (2026-08-21 audit A15); tokens draw both states. */
  .form-radio {
    appearance: none;
    -webkit-appearance: none;
    width: 1rem;
    height: 1rem;
    flex: none;
    border-radius: 9999px;
    border: 1px solid var(--border-strong);
    background-color: var(--surface-elevated);
    cursor: pointer;
  }
  .form-radio:checked {
    border-color: var(--accent);
    border-width: 5px;
    background-color: var(--surface-card);
  }
  .form-radio:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  .form-hint {
    font-size: 0.75rem;
    color: var(--text-muted);
  }
  .form-error {
    font-size: 0.75rem;
    color: var(--status-danger);
  }

  /* File input (B14): the native control restyled in place — no JS, the
     whole control stays keyboard- and drop-target-accessible. The button
     half matches the secondary button; the filename renders in the mono
     face because filenames are data. */
  .form-file {
    display: block;
    width: 100%;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    color: var(--text-secondary);
    cursor: pointer;
  }
  .form-file::file-selector-button {
    margin-right: 0.75rem;
    padding: 0.5rem 1rem;
    font-family: var(--font-sans);
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--text-primary);
    background: var(--surface-elevated);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: background var(--transition-fast);
  }
  .form-file::file-selector-button:hover {
    background: var(--surface-hover);
  }
  .form-file:focus-visible::file-selector-button {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
  }
}

/* Native picker glyphs (B14): the calendar / month / time indicator is a
   black raster glyph that vanishes on dark surfaces. Inverting it in dark
   keeps the native picker while the glyph tracks the theme. */
[data-theme="dark"] .form-input::-webkit-calendar-picker-indicator {
  filter: invert(1) brightness(0.85);
}
/* Textarea resize handle (B14): the UA-drawn corner grip ignores the
   theme; draw a token-coloured one instead. */
.form-textarea::-webkit-resizer {
  background:
    linear-gradient(135deg, transparent 50%, var(--border-strong) 50%, var(--border-strong) 60%, transparent 60%, transparent 75%, var(--border-strong) 75%, var(--border-strong) 85%, transparent 85%);
}

/* The accent border + soft glow above IS the focus indicator; the
   global *:focus-visible outline on top reads as a double box. Unlayered
   on purpose: the global rule below is unlayered and would otherwise
   beat a layered reset. */
.form-input:focus-visible,
.form-select:focus-visible,
.form-textarea:focus-visible {
  outline: none;
}

/* Modal panels never draw the global focus ring (A6/B4): the modal
   controller focuses the first control inside the dialog, and even when
   focus falls back to the panel (a modal with no focusable children) a
   ring around the whole dialog reads as a rendering error — on the purge
   modal it wrapped a destructive action in the brand accent. Unlayered so
   it beats the unlayered *:focus-visible global below. */
.modal__panel:focus,
.modal__panel:focus-visible {
  outline: none;
}

/* Row actions in dark (B12): ghost buttons inside table rows are pure
   text on a dark surface and disappear next to filled badges; a
   border-strong edge restores the affordance. Light keeps the borderless
   ghost. Unlayered so it beats the border-transparent utility. */
[data-theme="dark"] .data-table .btn--ghost,
[data-theme="dark"] .data-table .btn--ghost_danger {
  border-color: var(--border-strong);
}

/* Same rationale for the chat composer: its CONTAINER shows focus via
   focus-within:border-accent, so the inner textarea must not draw the
   global ring (autofocus counts as :focus-visible in Chromium). This
   lives here, unlayered, because Tailwind's focus:outline-none sits in
   @layer utilities and always loses to the unlayered global above. */
.composer-input:focus-visible {
  outline: none;
}


/* --------------------------------------------------------------------------
   Empty State (components/_empty_state)
   -------------------------------------------------------------------------- */
.empty-state {
  color: var(--text-tertiary);
}


/* --------------------------------------------------------------------------
   Confidence meter (triage queue). The fill width comes from a tenth
   bucket in data-level, not an inline style: style-src is nonce-only,
   so style= attributes never apply.
   -------------------------------------------------------------------------- */
.meter {
  display: inline-block;
  width: 3rem;
  height: 0.375rem;
  border-radius: 9999px;
  background: var(--surface-elevated);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  vertical-align: middle;
}
.meter__fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
}
.meter[data-level="0"] .meter__fill { width: 0; }
.meter[data-level="1"] .meter__fill { width: 10%; }
.meter[data-level="2"] .meter__fill { width: 20%; }
.meter[data-level="3"] .meter__fill { width: 30%; }
.meter[data-level="4"] .meter__fill { width: 40%; }
.meter[data-level="5"] .meter__fill { width: 50%; }
.meter[data-level="6"] .meter__fill { width: 60%; }
.meter[data-level="7"] .meter__fill { width: 70%; }
.meter[data-level="8"] .meter__fill { width: 80%; }
.meter[data-level="9"] .meter__fill { width: 90%; }
.meter[data-level="10"] .meter__fill { width: 100%; }


/* --------------------------------------------------------------------------
   Shared keyframes
   -------------------------------------------------------------------------- */

/* Toast entry (components/_toast: motion-safe:animate-[flash-enter…]). */
@keyframes flash-enter {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Pulsing status dot (components/_status_dot pulsing: true). */
@keyframes pulse-glow {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}


/* --------------------------------------------------------------------------
   Global / Misc
   -------------------------------------------------------------------------- */

/* Scrollbar — theme-aware. Mixing from --text-primary keeps the thumb
   visible on both themes (a black rgba vanishes on dark pages). */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--text-primary) 16%, transparent);
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--text-primary) 28%, transparent);
}

/* Focus visible global — keyboard-only focus indicator using the
   tokenised --ring-focus colour. 2px outline + offset reads on every
   surface; border-radius: inherit means rounded controls keep their
   shape. focus-visible (not :focus) ensures mouse users don't see
   the ring on click. */
*:focus-visible {
  outline: 2px solid var(--ring-focus);
  outline-offset: 2px;
  border-radius: inherit;
}

/* Selection colour — token-driven. Tinting the background only and
   inheriting the text colour works on every surface in both themes. */
::selection {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
}

/* Submit button loading state. When the submit_button Stimulus
   controller flips data-loading=true on a button, dim the label and
   tee up an inline spinner glyph via a pseudo-element so authors don't
   have to add markup per call site. */
button[data-loading="true"],
[data-loading="true"][role="button"] {
  pointer-events: none;
  opacity: 0.7;
  position: relative;
}
@media (prefers-reduced-motion: no-preference) {
  button[data-loading="true"]::after,
  [data-loading="true"][role="button"]::after {
    content: "";
    display: inline-block;
    width: 0.875em;
    height: 0.875em;
    margin-left: 0.5em;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: spin 600ms linear infinite;
    vertical-align: -2px;
  }
  @keyframes spin {
    to { transform: rotate(360deg); }
  }
}

/* Turbo view transitions for primary nav. Wrapped in motion-safe so
   reduced-motion users get instant page swaps. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 200ms;
    animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  }
}

/* Reduced-motion safety net. Existing rules (skeleton shimmer,
   view-transitions, submit-button spinner) are already wrapped in
   motion-safe queries. This blanket rule neutralises any remaining
   unguarded transitions / animations so a reduced-motion user never
   sees easing or keyframe motion — state changes happen instantly
   without breaking functionality. WCAG 2.1 SC 2.3.3. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Essential progress indicators are the WCAG 2.3.3 exception: a
     frozen spinner reads as "nothing is happening". Slow the spin
     right down instead of freezing it. `spin` keyframes come from
     the Tailwind build. */
  .progress-spinner {
    animation: spin 2s linear infinite !important;
  }
}
