/* =============================================================================
 * gyro-theme.css — Gyro Suite custom Filament v5 theme layer
 * Task P0-081. Consumes tokens.css (P0-080). Loaded AFTER Filament's own
 * theme.css / compiled app.css, via a render hook (panels::styles.after or
 * panels::head.end — wired by the panel-provider half of this task, not this
 * file). Hand-authored, no build step, no @apply, no Tailwind. Plain CSS only.
 *
 * SOURCE OF TRUTH: every colour, size, radius, gap, font value below is a
 * `var(--…)` defined in tokens.css / design-tokens.json (P0-080). The only
 * literal px in this file are 1px hairlines and 2px/-1px/1px focus-ring
 * offsets — the one exception the token system itself permits (see
 * design-tokens.json focus.ring.* and tokens.css :focus-visible rules) and
 * the only magnitudes token-lint.mjs's `allowedPx` list (0/1/2) admits.
 * Nothing in this file introduces a hex colour, an rgb()/hsl() value, a named
 * colour, or an arbitrary px size. `outline: none` never appears here, in
 * any form, on any selector, under any condition — spec 28.5 / focus.lint.
 *
 * WHY THIS FILE NEEDS VERY FEW !important RULES:
 * Filament v5 is built on Tailwind v4, and Tailwind v4 ships its generated
 * utilities inside native CSS cascade layers (`@layer theme, base,
 * components, utilities;`). Plain, un-layered CSS — which is exactly what
 * this file is, and exactly why the task brief forbids a build step — wins
 * against ANY layered rule regardless of that rule's specificity. So a
 * plain `.fi-ta-cell { padding-block: var(--size-pad-cell-y); }` here beats
 * Filament's own `.fi-ta-cell { padding: ...; }` utility even though the
 * utility may be more specific, *without* `!important`, as long as
 * Filament's own utility is itself inside a Tailwind layer (true for
 * anything Tailwind's compiler emitted) and this stylesheet is not wrapped
 * in `@layer` itself. This is *assumed*, not proven from here — it is the
 * single most important thing to confirm on the hydrated DOM (see
 * P0-081-notes.md verification_checklist: "computed style wins without
 * !important"). Every `!important` that IS used below carries a comment
 * naming the specific Filament behaviour it is defending against, per the
 * task's hard rule.
 *
 * CONFIDENCE TAGS on every selector block below:
 *   [stable]  a documented/known-stable Filament or platform CSS custom
 *             property — the lowest-risk surface, survives markup changes.
 *   [likely]  a Filament class name that has been consistent across
 *             Filament v2/v3/v4 and is carried into v5 unchanged per the
 *             filament-admin playbook ("v5 = v4 on Livewire 4, officially
 *             zero functional changes"). Still a class name — still an
 *             upgrade risk, just a smaller one.
 *   [guess]   a class name, structural assumption, or attribute we could
 *             not verify without a hydrated v5.7 DOM in hand. MUST be
 *             checked per P0-081-notes.md before this ships. This is the
 *             most important list this task produces.
 *   [project] a class this stylesheet itself defines (not a Filament class)
 *             for a Blade partial owned by another task (e.g. the status
 *             ribbon) to adopt. Zero Filament-upgrade risk; the risk is
 *             purely "does the partner Blade template use this class".
 * ========================================================================== */


/* -----------------------------------------------------------------------
 * 0. BASE — page shell, typography, structural colour. [likely]/[stable]
 * --------------------------------------------------------------------- */

/* [stable] platform-level: html/body are not Filament-specific. */
html {
  font-family: var(--type-family-ui);
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  color: var(--color-text-primary);
  background-color: var(--color-bg-page);
}

/* [likely] `.fi-body` / `.fi-layout` — Filament's panel root wrapper class,
 * consistent since v2. Re-asserted here because Filament's own compiled
 * CSS sets these from its Color::-derived Tailwind theme; this is the
 * hand-off point from "stock Filament chrome" to "Gyro chrome". */
.fi-body {
  font-family: var(--type-family-ui);
  background-color: var(--color-bg-page);
  color: var(--color-text-primary);
}

/* [likely] `.fi-main` — the scrollable content region inside the layout,
 * distinct from `.fi-sidebar` and `.fi-topbar`. */
.fi-main {
  background-color: var(--color-bg-page);
}

/* [likely] `.fi-section` — Filament's generic card/panel primitive, used
 * for form sections, table wrappers inside a page, and (per the
 * filament-admin playbook) as the outer wrapper Filament puts around a
 * StatsOverview widget (`.fi-section-not-contained`). Structure via
 * border, never shadow — spec 28.1 principle 3 / 28.5 elevation.none. */
.fi-section {
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border-hairline); /* hairline: allowed literal (see file header) */
  border-radius: var(--radius-control);
  box-shadow: var(--elevation-none);
}

/* Filament's stats card is not a .fi-section, so the surface rule above never
 * reaches it and it rendered as a WHITE card on the dark shell.
 *
 * The reason is structural and will keep happening: this panel runs WITHOUT
 * Filament's `dark` class (<html class="fi">) - the dark look is painted by
 * this file over Filament's LIGHT palette. So every Filament component this
 * file has not explicitly restyled arrives light. Check each new component on
 * screen; a green test suite cannot see this. */
.fi-wi-stats-overview-stat {
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border-hairline);
  border-radius: var(--radius-control);
}
.fi-wi-stats-overview-stat-value {
  color: var(--color-text-primary);
}
.fi-wi-stats-overview-stat-label,
.fi-wi-stats-overview-stat-description {
  color: var(--color-text-muted);
}

/* Same root cause as the stats card above: these are Filament components the
 * theme had never restyled, so they arrived with the stock LIGHT palette -
 * a white repeater block in the middle of a dark form, and white dropdown
 * panels over every Select.
 *
 * Found by walking the live DOM for any element whose computed background
 * luminance is > 0.7. Re-run that sweep after adding a component this file
 * has not seen before; nothing in the test suite can catch it. */
.fi-fo-repeater-item {
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border-hairline);
  border-radius: var(--radius-control);
}
.fi-dropdown-panel,
.fi-select-input-search-ctn {
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border-hairline);
  color: var(--color-text-primary);
}

/* Native <option> is drawn by the OS, not by Filament, so it needs its own
 * colours or the list is white text on white. */
.fi-select-input option {
  background-color: var(--color-bg-surface);
  color: var(--color-text-primary);
}

/* [likely] `.fi-section-not-contained` — StatsOverview's own wrapper is
 * this class specifically so it does NOT get the card chrome above (no
 * border, no fill) and instead sits flush on the dashboard background.
 * !important justification: the filament-admin playbook documents this
 * exact trap — ".fi-section" (the generic rule directly above, which we
 * author) paints a stray "plate" behind an un-contained stats widget
 * because `.fi-section-not-contained` also carries the base `.fi-section`
 * class, and depending on DOM-phase class ordering our own `.fi-section`
 * rule can win the cascade. This is defending against OUR OWN rule above,
 * not a Filament utility, but it is the one place in this file where two
 * of our own rules can legitimately race. */
.fi-section-not-contained {
  background-color: transparent !important; /* !important: guards against .fi-section (above) racing on class order, per filament-admin playbook's documented dashboard "stray plate" trap */
  border: none !important; /* !important: same race as background-color above */
  box-shadow: var(--elevation-none);
}

/* Money/quantity cells. [project] — Filament has no built-in "this column
 * is numeric" marker; TextColumn::numeric() only formats the value, it
 * does not add a class. Consuming resource/table code must opt a column
 * into this class explicitly (e.g. ->extraAttributes(['class' =>
 * 'gyro-numeric']) on the column, or a wrapping span in a custom column
 * view). Documented as not_covered in P0-081-notes.md. */
.gyro-numeric,
.gyro-numeric * {
  font-family: var(--type-family-numeric);
  font-variant-numeric: var(--type-numeric-variant);
  font-feature-settings: var(--type-numeric-feature-settings); /* fallback for renderers that ignore font-variant-numeric, incl. mPDF */
}

/* Record IDs / SKUs / API keys. [project] companion to .gyro-numeric. */
.gyro-mono {
  font-family: var(--type-family-mono);
}


/* -----------------------------------------------------------------------
 * 1. TABLE DENSITY — spec 28.8 point 1. The single most visible gap
 *    between stock Filament and the ERPNext/Odoo target.
 * --------------------------------------------------------------------- */

/* [likely] `.fi-ta-ctn` — the scroll container Filament wraps its table
 * in. Frozen header depends on this being the nearest scrolling ancestor;
 * if Filament instead lets the whole page scroll (no bounded height on
 * this container) the sticky header below sticks to the viewport instead,
 * which is also acceptable but needs `--gyro-topbar-offset` set correctly
 * — see [guess] note on .fi-ta-header below. */
.fi-ta-ctn {
  border: 1px solid var(--color-border-hairline); /* hairline: allowed literal */
  border-radius: var(--radius-control);
}

/* [likely] `.fi-ta-table` — the actual <table> element. */
.fi-ta-table {
  font-family: var(--type-family-ui);
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  border-collapse: separate;
  border-spacing: 0;
}

/* Frozen header. [likely] `.fi-ta-header-cell` (per-th) is a stable v3/v4
 * class name pattern; the exact stacking-context / offset math is
 * [guess] — depends on whether .fi-ta-ctn or the page is the scrolling
 * ancestor, and on the real rendered topbar height. --gyro-topbar-offset
 * is OUR OWN var (not a Filament one), default 0, for the panel layout to
 * set if the table's scroll container sits under a sticky topbar. */
:root {
  --gyro-topbar-offset: 0px; /* project var; override from the panel layout if .fi-ta-header sticks under a sticky .fi-topbar rather than inside its own scroll container */
}
.fi-ta-table thead.fi-ta-header,
.fi-ta-table th.fi-ta-header-cell {
  position: sticky;
  top: var(--gyro-topbar-offset);
  z-index: 1; /* not a size/colour token target; a stacking index, not a length */
  background-color: var(--color-bg-surface);
  box-shadow: inset 0 -1px 0 0 var(--color-border-hairline); /* hairline divider, not a border, so it does not consume row box-height per size.pad.cellY's geometry proof */
}
.fi-ta-header-cell {
  height: var(--size-row-table);
  padding-block: var(--size-pad-cell-y);
  padding-inline: var(--size-pad-cell-x);
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-secondary);
  text-align: left;
  box-sizing: border-box;
}

/* Row height 34px, cell radius 0, hairline not shadow, compact padding.
 * [likely] `.fi-ta-row` / `.fi-ta-cell` — consistent Filament table-row/
 * cell class naming since v2. The exact padding split (7px/7px) is the
 * geometry design-tokens.json proves out for a 34px row + 20px mandated
 * line-height + an inset-shadow divider (not a border) — see
 * size.pad.cellY's comment in design-tokens.json. */
.fi-ta-row {
  height: var(--size-row-table);
}
.fi-ta-row .fi-ta-cell,
td.fi-ta-cell {
  height: var(--size-row-table);
  padding-block: var(--size-pad-cell-y);
  padding-inline: var(--size-pad-cell-x);
  border-radius: var(--radius-cell); /* MANDATED 0 — table cells are always sharp, spec 28.5 */
  box-sizing: border-box;
  box-shadow: inset 0 -1px 0 0 var(--color-border-hairline); /* row divider as hairline, not border-bottom, to preserve the 34px row's exact content-box arithmetic */
  border-bottom: none; /* remove Filament's own border-based row divider so it cannot double up with the box-shadow divider above */
  vertical-align: middle;
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  color: var(--color-text-primary);
}

/* [guess] the inner text wrapper Filament's TextColumn renders inside a
 * cell — believed to be `.fi-ta-text` in v3/v4, unverified for v5.7. If
 * wrong, the cell-level rule above still carries the row-height/padding
 * contract; this rule is a refinement (truncation, weight), not the
 * load-bearing one. */
.fi-ta-text {
  font-weight: var(--type-weight-normal);
  color: inherit;
}

/* Row hover / selected — neutral only, never accent-tinted per spec 28.5
 * "one accent, nothing else is coloured". [likely] */
.fi-ta-row:hover {
  background-color: var(--color-bg-hover);
}
.fi-ta-row[data-selected="true"],
.fi-ta-row.fi-ta-row-selected { /* [guess] exact selected-row class/attribute unverified for v5.7 */
  background-color: var(--color-bg-selected);
}

/* Row-action icon buttons must hit the 24px pointer-target minimum
 * (a11y.target.rowAction) while visually sitting inside a 34px row with
 * a 20px content box (size.control.iconButton = 20px, zero-slack fit).
 * [likely] `.fi-icon-btn` is Filament's icon-only button component class,
 * used for table row actions among other places. */
.fi-ta-cell .fi-icon-btn {
  width: var(--size-control-icon-button);
  height: var(--size-control-icon-button);
  min-width: var(--a11y-target-min); /* the 24x24 CSS px pointer-target floor, achieved via padding around a visually smaller glyph, not a bigger glyph */
  min-height: var(--a11y-target-min);
  border-radius: var(--radius-control);
}
.fi-ta-cell .fi-icon-btn svg {
  width: var(--icon-list);
  height: var(--icon-list);
}

/* Totals row — semibold, per type.weight.semibold's "totals/grand-total
 * rows" use case. [project] class; no stock Filament equivalent exists
 * for a table-level totals row (Filament's table footer, if used, is
 * [guess] `.fi-ta-footer` / `tfoot`). */
.gyro-ta-totals-row,
tfoot.fi-ta-footer .fi-ta-cell { /* [guess] tfoot/.fi-ta-footer existence and naming unverified for v5.7 */
  font-weight: var(--type-weight-semibold);
  box-shadow: inset 0 1px 0 0 var(--color-border-hairline); /* hairline separating totals from the body, above not below */
}


/* -----------------------------------------------------------------------
 * 2. SIDEBAR — spec 28.2 / 28.8 point 2. Dense module-group navigation.
 * --------------------------------------------------------------------- */

/* [likely] `.fi-sidebar` — the collapsible nav aside, stable class name
 * since v2/v3. Width/collapse mechanics are Alpine-driven and are
 * [guess] below; the visual density rules here apply regardless of
 * collapsed state. */
.fi-sidebar {
  background-color: var(--color-bg-page);
  border-inline-end: 1px solid var(--color-border-hairline); /* hairline: allowed literal */
}

/* [likely] `.fi-sidebar-nav` — the nav list container. */
.fi-sidebar-nav {
  padding-block: var(--space-2);
  padding-inline: var(--space-2);
}

/* [likely] `.fi-sidebar-group` / `.fi-sidebar-group-label` — module-group
 * headers (ERPNext-style module → workspace grouping, spec 28.2). */
.fi-sidebar-group {
  margin-block-end: var(--space-1);
}
.fi-sidebar-group-label {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-muted);
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
}

/* [likely] `.fi-sidebar-item` / `.fi-sidebar-item-button` — one nav
 * entry. Dense: no oversized padding, per spec 28.1 principle 1 and the
 * task brief's explicit "no oversized padding" instruction. */
.fi-sidebar-item-button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--size-control-button-compact); /* 24px — compact toolbar/nav control height, not the 32px form-field height */
  padding-block: var(--space-1);
  padding-inline: var(--space-2);
  border-radius: var(--radius-control);
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  font-weight: var(--type-weight-normal);
  color: var(--color-text-secondary);
}
.fi-sidebar-item-button:hover {
  background-color: var(--color-bg-hover);
  color: var(--color-text-primary);
}

/* Active nav item — one of the accent's four sanctioned uses (spec
 * 28.5). [likely] `.fi-active` as the active-state modifier on a sidebar
 * item is consistent Filament v3/v4 convention; [guess] whether v5.7
 * puts it on `.fi-sidebar-item` or directly on `.fi-sidebar-item-button`. */
.fi-sidebar-item.fi-active .fi-sidebar-item-button,
.fi-sidebar-item-button.fi-active {
  background-color: var(--color-accent-subtle);
  color: var(--color-accent-base);
  font-weight: var(--type-weight-medium);
}

/* 16px icons, per the task brief's explicit instruction (also
 * icon.list = 16px, "icons inside lists and navigation", design-tokens
 * .json). [likely] `.fi-sidebar-item-icon` naming; icon element itself
 * may be a direct `svg` child — both targeted for resilience. */
.fi-sidebar-item-icon,
.fi-sidebar-item-button svg {
  width: var(--icon-list);
  height: var(--icon-list);
  flex-shrink: 0;
}

/* Collapsible to icons. [guess] — Filament's desktop sidebar-collapse
 * mechanism (`->sidebarCollapsibleOnDesktop()`) is Alpine-store driven
 * and, per the filament-admin playbook's documented mobile-sidebar bug,
 * can set state via a *persisted Alpine store* rather than a CSS class —
 * meaning a class-based rule like the one below may simply not exist to
 * hook, and the real fix might need to live in JS (out of scope for a
 * CSS-only file; flagged in P0-081-notes.md not_covered). This rule is a
 * best-effort hook on the two most likely collapsed-state markers; both
 * MUST be checked against the hydrated DOM. */
.fi-sidebar[data-collapsed="true"] .fi-sidebar-item-label,
.fi-sidebar.fi-sidebar-collapsed .fi-sidebar-item-label,
.fi-sidebar[data-collapsed="true"] .fi-sidebar-group-label,
.fi-sidebar.fi-sidebar-collapsed .fi-sidebar-group-label {
  display: none;
}
.fi-sidebar[data-collapsed="true"] .fi-sidebar-item-button,
.fi-sidebar.fi-sidebar-collapsed .fi-sidebar-item-button {
  justify-content: center;
  padding-inline: var(--space-1);
}


/* -----------------------------------------------------------------------
 * 3. DASHBOARD GRID & TOP BAR — spec 28.3/28.8 point 3, 28.9.
 * --------------------------------------------------------------------- */

/* [guess] every selector in this block. The filament-admin playbook is
 * explicit and hard-won on this point: `.fi-sc.fi-grid` is used for the
 * page grid, the widgets grid, AND grids *inside* a widget, all with the
 * SAME class, and the DOM restructures at the last lazy-widget morph
 * (a second nested .fi-sc.fi-grid wraps everything once hydration
 * finishes). Naive class-only grid-column rules are the documented root
 * cause of "the dashboard collapsed to half width". The structural
 * :has()-based rule below is the playbook's own phase-proof answer,
 * reproduced here; it still needs re-verification on this project's
 * actual v5.7 hydrated DOM per P0-081-notes.md, ideally paired with the
 * `html[data-admin-dash]` marker-attribute technique the playbook
 * recommends (that marker is set by panel JS, out of scope for this CSS
 * file — see not_covered). */
.fi-main .fi-sc.fi-grid {
  gap: var(--space-5); /* MANDATED section gap between dashboard tiles */
}
/* Wrapper-vs-widget: a grid child that itself CONTAINS another schema
 * grid is the hydration-time wrapper around the whole dashboard, not a
 * real widget — force it to span the full row so it stops behaving like
 * a half-width tile. */
/* ---- DASHBOARD-ONLY GRID TIERING ----------------------------------------
 * Everything down to the end of the xl tier used to be scoped to `.fi-main`,
 * i.e. EVERY page in the panel. A resource list page is also a
 * `.fi-sc.fi-grid`, so its single child - the table - inherited
 * `grid-column: span 3` of a 6-column grid and rendered at HALF WIDTH with a
 * horizontal scrollbar. It read as a table bug; it was a dashboard rule
 * leaking onto every screen.
 *
 * `html[data-admin-dash]` is the marker technique the note above asks for.
 * It is set by the script in AdminPanelProvider panels::head.end, on load and
 * on livewire:navigated. Scope EVERY rule in this block with it, including the
 * widget-internal exemption - the prefix raises specificity, so scoping only
 * some of them silently reorders which rule wins. */
html[data-admin-dash] .fi-main .fi-sc.fi-grid > *:has(> .fi-sc.fi-grid) {
  grid-column: 1 / -1;
}
/* Exempt a widget's OWN internal grid (e.g. a stats-cards row inside one
 * widget) from the outer tiering below, else stat cards stack instead of
 * sitting side by side. */
html[data-admin-dash] .fi-main [class*="fi-wi"] .fi-sc.fi-grid {
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); /* 14rem: Tailwind/Filament's own auto-fit convention, not a Gyro token — this governs widget-internal card wrapping, not the page layout grid, and design-tokens.json has no equivalent scale step for it */
}
html[data-admin-dash] .fi-main [class*="fi-wi"] .fi-sc.fi-grid > * {
  grid-column: auto;
}
/* Default/mobile tier: single column. */
html[data-admin-dash] .fi-main .fi-sc.fi-grid > * {
  grid-column: span 2;
}
/* [guess] breakpoint values: assumed to match Tailwind v4's default
 * `md` (768px) / `xl` (1280px) scale, since design-tokens.json defines no
 * breakpoint tokens of its own (there is no size.breakpoint.* token to
 * consume) and Filament's own responsive classes are compiled against
 * Tailwind's defaults. MUST be confirmed against the compiled Filament
 * stylesheet's actual @media boundaries before relying on this tiering. */
@media (min-width: 768px) {
  html[data-admin-dash] .fi-main .fi-sc.fi-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1280px) {
  html[data-admin-dash] .fi-main .fi-sc.fi-grid { grid-template-columns: repeat(6, 1fr); }
  html[data-admin-dash] .fi-main .fi-sc.fi-grid > * { grid-column: span 3; } /* charts/most widgets: 2 per row on the 6-col xl tier */
  html[data-admin-dash] .fi-main .fi-sc.fi-grid > .gyro-widget-wide { grid-column: span 4; } /* [project] opt-in class for a wider widget */
  html[data-admin-dash] .fi-main .fi-sc.fi-grid > .gyro-widget-full { grid-column: 1 / -1; } /* [project] opt-in class for a full-bleed widget */
}

/* Explicitly forbid the hero/gradient KPI-card look — spec 28.9. Not a
 * rule that removes anything Filament renders (that is a widget-
 * registration decision, out of this CSS layer's reach — see
 * not_covered), just a hard guarantee that nothing THIS stylesheet ships
 * can produce one, and a defensive reset of the one property a
 * decorative widget author would reach for. */
.fi-main .fi-wi-widget,
.fi-main .fi-section {
  background-image: none;
}

/* Top bar. [likely] `.fi-topbar` class naming is stable since v2; the
 * `.fi-topbar-start` / `.fi-topbar-end` split and the specific ordering
 * fix below are reproduced from the filament-admin playbook's documented
 * "avatar not right-most" bug and are [guess] for v5.7 specifically. */
.fi-topbar {
  height: var(--size-control-button); /* reuse the 32px control height as the topbar's control-row height; the topbar chrome itself may be taller with padding, this governs the controls inside it */
  border-block-end: 1px solid var(--color-border-hairline); /* hairline: allowed literal */
  background-color: var(--color-bg-surface);
}
.fi-topbar-end { /* [guess] */
  order: 100;
  margin-inline-start: 0; /* per playbook: this auto-margin must be zeroed or the topbar cluster centers instead of the user-menu sitting flush right */
}
.fi-topbar .fi-icon-btn svg,
.fi-topbar-item svg { /* [guess] */
  width: var(--icon-toolbar);
  height: var(--icon-toolbar);
}


/* -----------------------------------------------------------------------
 * 4. FORM LAYER — 32px field rows, 4px control radius, focus ring.
 * --------------------------------------------------------------------- */

/* The 2px accent focus ring on :focus-visible is ALREADY provided
 * globally by tokens.css (P0-080) for every `a, button, input, select,
 * textarea, summary, [tabindex]`, and the dense negative-offset variant
 * is already provided globally for `[role=grid] td/th/[role=gridcell]`.
 * This file does not redeclare either — it only needs to stop Filament's
 * OWN focus treatment from visually competing with (or clipping) that
 * ring. [likely] Filament wraps native inputs in a `.fi-input-wrp` div
 * that carries the visible border/ring styling (the border you see
 * around a text field is on the wrapper, not the native <input> itself in
 * most Filament versions); box-shadow is Filament's own historical
 * focus-ring mechanism there. */
.fi-input-wrp:focus-within {
  box-shadow: none; /* neutralises Filament's own box-shadow focus ring on the wrapper so it cannot fight or double up with tokens.css's outline-based ring on the underlying <input> — outline draws outside the box's clip context and survives this wrapper's overflow, box-shadow does not */
}

/* 32px field rows, 4px radius. [likely] `.fi-input-wrp` / `.fi-input` are
 * the standard text-input wrapper/element class pair across Filament v2-
 * v4, carried to v5 per the "zero functional changes" LW4 upgrade. */
.fi-input-wrp {
  min-height: var(--size-row-field); /* MANDATED 32px field row */
  border: 1px solid var(--color-border-strong); /* the AA 3:1 boundary colour — input outlines are exactly what WCAG 1.4.11 governs, per design-tokens.json's border.strong vs border.hairline split */
  border-radius: var(--radius-control); /* MANDATED 4px */
  background-color: var(--color-bg-surface);
  box-sizing: border-box;
}
.fi-input {
  min-height: var(--size-control-input);
  padding-block: var(--size-pad-input-y);
  padding-inline: var(--size-pad-input-x);
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  color: var(--color-text-primary);
  background-color: transparent;
}
.fi-input::placeholder {
  color: var(--color-text-muted);
}

/* [likely] `.fi-fo-field-wrp-label` — field label element. */
.fi-fo-field-wrp-label {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  font-weight: var(--type-weight-medium);
  color: var(--color-text-primary);
  margin-block-end: var(--space-1);
}

/* Disabled/locked field — "the document is the interface", spec 28.1
 * principle 4; a submitted document's fields read as visibly locked.
 * [guess] the disabled-state selector/attribute Filament v5.7 actually
 * applies (native `:disabled` pseudo-class targeted as the safest bet;
 * a `.fi-disabled` class variant is added defensively). */
.fi-input-wrp:has(.fi-input:disabled),
.fi-input-wrp.fi-disabled { /* [guess] */
  background-color: var(--color-bg-disabled);
}
.fi-input:disabled {
  color: var(--color-text-disabled);
}

/* Buttons — 32px control height to share a baseline with the 32px field
 * row (design-tokens.json size.control.button comment). [likely]
 * `.fi-btn` is Filament's stable button-component class across versions. */
.fi-btn {
  min-height: var(--size-control-button);
  padding-inline: var(--space-3);
  border-radius: var(--radius-control);
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  font-weight: var(--type-weight-medium);
  box-shadow: var(--elevation-none); /* structure via border, never shadow — spec 28.1 principle 3 */
}

/* Primary button — "the ONLY filled button on the page" per spec 28.4;
 * everything else is a ghost or text button. [guess] whether v5.7 keys
 * the primary/filled variant off `.fi-color-primary` (colour-slot
 * convention) or a `.fi-btn-color-primary` / `.fi-btn-outlined` pairing —
 * `.fi-color-primary` is the more consistent convention across Filament's
 * colour-aware components (badges, buttons, icons alike) so it is the
 * primary target here. */
.fi-btn.fi-color-primary {
  background-color: var(--color-accent-base);
  color: var(--color-accent-on);
  border-color: var(--color-accent-base);
}
.fi-btn.fi-color-primary:hover {
  background-color: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
}
.fi-btn.fi-color-primary:active {
  background-color: var(--color-accent-active);
  border-color: var(--color-accent-active);
}

/* Ghost/outlined buttons — everything that is NOT the ribbon's primary
 * action. [guess] `.fi-btn-outlined` naming. */
.fi-btn:not(.fi-color-primary) {
  background-color: transparent;
  color: var(--color-text-primary);
  border: 1px solid var(--color-border-strong); /* hairline: allowed literal */
}
.fi-btn:not(.fi-color-primary):hover {
  background-color: var(--color-bg-hover);
}

/* State-change transitions on hover/focus — 120-160ms ease-out, spec
 * 28.5/28.9. [stable] token values, applied to our own selectors above;
 * prefers-reduced-motion is already globally handled by tokens.css
 * (its `*, *::before, *::after` block overrides duration to
 * --motion-duration-base under the media query — see tokens.css line
 * ~259 — so nothing extra is needed here beyond using the tokens). */
.fi-btn,
.fi-input-wrp,
.fi-sidebar-item-button {
  transition-property: var(--motion-allowed-hover-and-focus-state);
  transition-duration: var(--motion-duration-fast);
  transition-timing-function: var(--motion-easing-out);
}


/* -----------------------------------------------------------------------
 * 5. STATUS RIBBON & CHIPS — spec 28.4. Chips and text only, NEVER a
 *    filled coloured page region.
 * --------------------------------------------------------------------- */

/* [project] The status ribbon (`Draft → Submitted → Paid`) is a Gyro
 * Blade partial, not a stock Filament component — spec 28.4 describes
 * product behaviour Filament has no native primitive for. These classes
 * are this stylesheet's contract with that partial (owned by a different
 * task); see P0-081-notes.md not_covered. */
.gyro-status-ribbon {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-2);
  /* Deliberately no background-color here: the ribbon sits on the page's
   * own surface colour. A ribbon is a row of chips/labels, never a
   * filled coloured band — spec 28.4 "a single coloured chip, not a
   * coloured page". */
}
.gyro-status-ribbon-step {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  color: var(--color-text-muted); /* later/future states: greyed */
  font-weight: var(--type-weight-normal);
}
.gyro-status-ribbon-step[data-current="true"] {
  color: var(--color-text-primary); /* current state: emphasised */
  font-weight: var(--type-weight-semibold);
}
.gyro-status-ribbon-connector {
  color: var(--color-border-strong);
}

/* Chips — used by the ribbon's overdue/cancelled/locked marker AND by
 * Filament's own native Badge component wherever a status column/tag
 * appears elsewhere in the product (table cells, form headers). Height
 * 18px, pill radius, border not shadow, text/icon required alongside
 * colour per a11y.rules.colorOnly (that pairing is a Blade/content
 * concern, not something CSS can enforce — flagged in not_covered).
 * `.fi-badge` [likely] is Filament's stable Badge-component class;
 * `.gyro-chip` [project] is the ribbon's own chip for parity. */
/* THE CHIP IS SIZED BY ITS OWN CONTENTS, NOT BY A HEIGHT THAT DISAGREES WITH THEM.
 *
 * MEASURED on the server's own rendered HTML with the served stylesheets,
 * sprint 48 recon (s48/ui-megamenu.md section 3.1), root font 13.5px:
 *
 *   variant                     border  padding-block   line-height   needs   was forced to
 *   .fi-badge (fi-size-md)      1 + 1   3.375 + 3.375   16            24.75   18
 *   .fi-badge.fi-size-sm        1 + 1   1.6875 + 1.688  16            21.375  18
 *   .fi-badge in a dropdown row 1 + 1   1.6875 + 1.688  16            21.375  18
 *   .fi-badge.fi-size-xs        1 + 1   0 + 0           16            18      18
 *
 * The old rule declared `height: var(--size-control-chip)` and NOT
 * `padding-block`, so Filament's padding survived inside a box too short to
 * hold it - verified in vendor's compiled app.css, `.fi-badge{padding-block:
 * calc(var(--spacing) * 1)}` with `--spacing: .25rem`. Everything except
 * `fi-size-xs` was between 3.375px and 6.75px over its own box, and
 * `.fi-badge:not(.fi-wrapped){overflow:hidden}` hid the evidence.
 *
 * WHERE THE DIGIT WENT. Vendor also ships
 * `.fi-badge .fi-badge-label-ctn{align-self:baseline;display:grid}`. With one
 * flex item, baseline alignment resolves to cross-start, so the whole overflow
 * went BELOW the text: measured 8.375px above the digit and 1.625px below it,
 * a 5:1 split. That is the owner's "numbers always touch the lower wall", and
 * it is why only the badges rendered through <x-filament::badge> drift while
 * the ones `TextColumn->badge()` emits do not - only the component wraps its
 * label in `.fi-badge-label-ctn`.
 *
 * THE FIX IS THE ARITHMETIC, not a padding nudged until one screen looks
 * right: zero the block padding, and let border + line-height BE the height.
 *
 *     1px border + 16px line-height + 1px border = 18px = --size-control-chip
 *
 * so the mandated 18px is honoured exactly, `min-height` rather than `height`
 * means a wrapped chip grows instead of clipping, and the text is centred
 * because the box is the text's own box. tests/Feature/Theme/BadgeGeometryTest
 * recomputes that sum from tokens.css on every push.
 *
 * OPTICAL CENTRING, and why nothing is nudged for it. The 16px line box holds
 * an 11px font, so 2.5px of half-leading sits above the ascent and below the
 * descent. A digit and a capital both hang from the same cap line and a
 * descender ends 3px inside the bottom border (measured, not clipped). One
 * symmetric box, one line box, every glyph class centred by the same
 * construction - a per-glyph correction would only move one of them. */
.fi-badge,
.gyro-chip {
  display: inline-flex;
  align-items: center;
  /* MANDATED 18px. `min-height`, so a chip that has to wrap gets taller
   * rather than clipping - it never gets SHORTER than the mandate. */
  min-height: var(--size-control-chip);
  padding-block: var(--space-0);
  padding-inline: var(--space-1);
  border-radius: var(--radius-chip); /* pill — the one sanctioned use of a pill radius, spec 28.5 "no pills except status chips" */
  border: 1px solid; /* colour set per status modifier below; 1px hairline literal allowed */
  font-size: var(--type-size-micro);
  line-height: var(--type-line-micro);
  font-weight: var(--type-weight-normal);
}

/* Vendor's `align-self: baseline` on the label wrapper, undone. It is a no-op
 * once the box is the right height, and it is here anyway because a badge with
 * an icon has TWO flex items - and then baseline alignment of one of them is a
 * real 3.375px drift again, on a chip nobody would think to re-measure. */
.fi-badge .fi-badge-label-ctn {
  align-self: center;
}

/* THE CHIP INSIDE THE ROW - the second half of the same complaint, and a
 * different cause from the one above.
 *
 * MEASURED on the Invoices list at 1366x768 with real data (s48/ui-megamenu.md
 * section 3.3):
 *
 *   row height 35.63   row mid-line 17.81   cell padding 7.00 / 7.00
 *   a plain-text cell's ink mid    17.81   <- dead centre, correct
 *   the STATUS chip's mid          19.63   <- 1.81px BELOW the row's centre
 *   slack above the chip 3.63px    slack below the chip 0px
 *
 * A chip is an inline-level box in the cell's line box, so `vertical-align`
 * puts its centre at `baseline - x-height/2` (predicted 19.55, measured 19.63 -
 * vendor sets `align-middle` on it explicitly, verified in
 * vendor/filament/tables/resources/css/columns/text.css). The 18px chip then
 * hangs below the 20px strut, the line box grows DOWNWARD to contain it, the
 * row grows with it, and every pixel of the resulting slack lands above the
 * chip. Systematic, on every status chip in every table - which is why the
 * owner said "always".
 *
 * The fix is to stop the chip being inline-level: make the wrapper a flex
 * container and the chip becomes a flex item centred on the cross axis. The
 * strut disappears with it. Re-measured with this applied: chip mid - row mid
 * 0.00px, cell slack 8.13 / 8.13, and the row 1.38px SHORTER, which is the
 * direction spec 28.5 wants anyway.
 *
 * SCOPED TO BADGE CELLS ONLY, and that is deliberate. The recon proposed
 * `.fi-ta-text-item, .fi-in-text-item { display: inline-flex }` unconditionally
 * and then warned, correctly, that `.fi-in-text-item` is `display: -webkit-box`
 * for line clamping. Turning every text cell in the product into a flex
 * container to centre the ones containing a chip would take line-clamp and text
 * wrapping with it. Vendor already labels the cells that hold badges - read off
 * the source rather than guessed: TextColumn.php:240 and :384 add
 * `fi-ta-text-has-badges`, TextEntry.php:278 adds `fi-in-text-has-badges` - and
 * both shapes exist, the class landing on the item itself for a single value
 * and on the <ul> wrapper for a list, hence one same-element selector and one
 * descendant selector per family. A cell that holds no chip is not touched. */
.fi-ta-text-has-badges.fi-ta-text-item,
.fi-ta-text-has-badges .fi-ta-text-item,
.fi-in-text-has-badges.fi-in-text-item,
.fi-in-text-has-badges .fi-in-text-item {
  display: inline-flex;
  align-items: center;
}

/* Status modifiers. [likely] `.fi-color-success` etc. is Filament's
 * stable colour-slot modifier convention, applied by the framework to
 * any colour-aware component (badges, buttons, icons) including Badge.
 * `.gyro-chip--*` [project] mirrors the same four states for the ribbon's
 * own chip. */
.fi-badge.fi-color-success,
.gyro-chip--success {
  background-color: var(--color-status-success-bg);
  color: var(--color-status-success-text);
  border-color: var(--color-status-success-border);
}
.fi-badge.fi-color-warning,
.gyro-chip--warning {
  background-color: var(--color-status-warning-bg);
  color: var(--color-status-warning-text);
  border-color: var(--color-status-warning-border);
}
.fi-badge.fi-color-danger,
.gyro-chip--danger {
  background-color: var(--color-status-danger-bg);
  color: var(--color-status-danger-text);
  border-color: var(--color-status-danger-border);
}
.fi-badge.fi-color-info,
.gyro-chip--info {
  background-color: var(--color-status-info-bg);
  color: var(--color-status-info-text);
  border-color: var(--color-status-info-border);
}

/* Ribbon's own primary action button sits to the right of the ribbon and
 * is the ONLY filled button on the page (spec 28.4) — it is simply a
 * `.fi-btn.fi-color-primary` per section 4 above; no additional rule is
 * needed here, called out for clarity of the contract only. */


/* -----------------------------------------------------------------------
 * 6. FOCUS-VISIBLE HARDENING — belt-and-braces, never outline:none.
 * --------------------------------------------------------------------- */

/* This file never writes `outline: none`/`outline: 0` anywhere, on any
 * selector, under any pseudo-class or media condition — checked by
 * token-lint.mjs's OUTLINE_NONE rule (also matches the Tailwind
 * `outline-none` utility form, which this file — being hand-authored
 * plain CSS with no Tailwind classes — cannot emit either way). Filament
 * v5's OWN compiled CSS may still contain the utility form on some
 * elements (native browser default-outline resets sometimes reach for
 * it); this file cannot inspect Filament's compiled output for that from
 * here — flagged as a verification item, not fixed blindly, since a
 * blanket `.fi-input:focus { outline: 2px solid ... }` override risks
 * fighting the roving-tabindex bare-:focus cases tokens.css already
 * handles correctly. See P0-081-notes.md verification_checklist. */

/* ============================================================================
 * HYDRATED-DOM CORRECTIONS - measured on sandbox.gyrosuite.com, 2026-08-15.
 * Everything below was found by measuring the live DOM, not by reading markup.
 * ==========================================================================*/

/* Row height read 61.3px where the design calls for 34px. The cell's own padding
 * was already correct at 7px; the extra 27px came from .fi-ta-text-item, which
 * carries 13.5px of vertical padding of its own inside every cell. The row's
 * vertical rhythm belongs to the cell, not to a nested text wrapper.
 *   7 (cell) + 20 (line-height) + 7 (cell) = 34px = var(--size-row-table)        */
.fi-ta-text-item {
    padding-top: 0;
    padding-bottom: 0;
}
.fi-ta-col {
    min-height: 0;
}

/* Spec 28.5: tabular lining figures wherever money or quantities appear. Read as
 * absent on every cell - Filament sets no font-variant-numeric at all. Applied to
 * the cell rather than to a numeric-only class, because a misaligned column is a
 * defect whichever column it is. font-feature-settings is the fallback for
 * renderers that ignore the modern property.                                     */
.fi-ta-cell,
.fi-ta-text,
.fi-ta-text-item {
    font-variant-numeric: tabular-nums lining-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
}

/* Spec 28.3: the list view has a frozen header. Read as position:static.          */
.fi-ta-header-cell {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--color-bg-surface);
}

/* Frozen header, corrected 2026-08-15 after measuring: Filament v5.7 gives header
 * cells the SAME .fi-ta-cell class as body cells - there is no .fi-ta-header-cell.
 * The first selector shipped was a guess and it silently matched nothing, which is
 * the exact failure mode spec 28.8 warns about. Target thead by structure.        */
.fi-ta-content-ctn thead th,
.fi-ta-content-ctn thead .fi-ta-cell {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--color-bg-surface);
}

/* ==== GYRO NAV LAYER (P0-081 navigation) ==== */
/* =============================================================================
 * nav-theme.css — Gyro Suite navigation layer (P0-081, nav half)
 * Loaded AFTER gyro-theme.css (P0-081 CSS half — /tmp/sprint7/theme/
 * gyro-theme.css), which already owns base sidebar density
 * (.fi-sidebar-item-button padding/height, .fi-sidebar-item-icon sizing,
 * .fi-active state, the desktop-collapse [data-collapsed]/[.fi-sidebar-
 * collapsed] hook, and .fi-topbar height/icon sizing). THIS FILE DOES NOT
 * REPEAT THOSE RULES. It adds only what the navigation model (spec 28.2)
 * needs beyond that: pinned favourites, per-group collapse chevrons, the
 * six-control top bar (including disabled-affordance styling for controls
 * with no backing feature yet), and the module-workspace landing page that
 * replaces the widget-based dashboard.
 *
 * SOURCE OF TRUTH: every colour/size/radius/gap/font value below is a
 * `var(--…)` from tokens.css (P0-080). The only literal px in this file are
 * 1px hairlines and the 2px/-1px focus-ring offsets tokens.css itself
 * permits (see gyro-theme.css header comment for the full rationale — same
 * rule applies here unchanged). No hex/rgb/hsl/named colour, no arbitrary
 * px size, and `outline: none` (or the Tailwind `outline-none` utility)
 * never appears here, in any form, on any selector, under any condition.
 *
 * CONFIDENCE TAGS — identical convention to gyro-theme.css:
 *   [stable]  plain CSS/HTML or our own token custom properties.
 *   [likely]  Filament class name consistent across v2–v4, asserted by the
 *             filament-admin playbook to carry into v5 unchanged.
 *   [guess]   class name/attribute/structural assumption NOT verified
 *             against a hydrated Filament v5.7 DOM. MUST be checked before
 *             this ships — see P0-081-nav-notes.md `selectors`.
 *   [project] a class this stylesheet (or panel-navigation.php's Blade
 *             partials) defines itself. Zero Filament-upgrade risk — the
 *             only risk is "does the partner Blade template use this
 *             class," which is a contract with panel-navigation.php.
 * ========================================================================== */


/* -----------------------------------------------------------------------
 * 1. PINNED FAVOURITES — spec 28.2 "Pinned favourites at the top."
 * Rendered by panel-navigation.php section 6 via a render hook, ABOVE the
 * regular nav groups. Reuses .fi-sidebar-item-button / .fi-sidebar-item-icon
 * from gyro-theme.css for the row itself (so a favourite row and a normal
 * nav row look identical apart from position) — only the wrapper and the
 * label-hiding-on-collapse behaviour are new here.
 * --------------------------------------------------------------------- */

/* [project] Wrapper around the favourites list, sits above .fi-sidebar-nav's
 * first group. A slightly stronger bottom hairline than a normal group
 * separates "pinned" from "grouped" as two visually distinct zones, per
 * spec 28.2's own split between the two. */
.gyro-nav-favorites {
  padding-block-end: var(--space-2);
  margin-block-end: var(--space-2);
  border-block-end: 1px solid var(--color-border-hairline); /* hairline: allowed literal */
}

/* [project] Reuses the group-label look from gyro-theme.css's
 * .fi-sidebar-group-label by relying on that class directly in the Blade
 * partial (see panel-navigation.php) rather than redefining it here — this
 * selector only exists to confirm intent and is deliberately empty of
 * duplicate rules. Kept as a marker selector for the verification checklist
 * (see notes file) rather than removed, so a future edit has an obvious
 * place to add favourites-specific label styling if it turns out to need
 * any (e.g. an icon prefix) without hunting through gyro-theme.css. */
.gyro-nav-favorites .fi-sidebar-group-label {
  /* intentionally inherits everything from gyro-theme.css; no overrides */
}

/* 16px icons on favourite rows — same rule as ordinary nav rows (spec 28.5,
 * icons 16px in lists and nav), applied to the [project] favourite-row
 * class in case its icon element does not also carry
 * .fi-sidebar-item-icon (the Blade partial applies both classes, but this
 * is the defensive fallback if that ever drifts). */
.gyro-nav-favorite-item svg {
  width: var(--icon-list);
  height: var(--icon-list);
  flex-shrink: 0;
}

/* Collapse-to-icons must hide favourite labels exactly like it hides group
 * labels. [guess] — same two collapsed-state selectors gyro-theme.css uses
 * for `.fi-sidebar-item-label` / `.fi-sidebar-group-label`; extended here to
 * cover the [project] favourites label class. If gyro-theme.css's own
 * collapsed-state hook turns out to be dead code (per its own documented
 * risk — Alpine-store-only state with no DOM reflection), this rule is
 * dead in exactly the same way and needs the same JS-shim fix, not a CSS
 * fix; see panel-navigation.php section 5c. */
.fi-sidebar[data-collapsed="true"] .gyro-nav-favorite-label,
.fi-sidebar.fi-sidebar-collapsed .gyro-nav-favorite-label {
  display: none;
}
.fi-sidebar[data-collapsed="true"] .gyro-nav-favorites .fi-sidebar-group-label,
.fi-sidebar.fi-sidebar-collapsed .gyro-nav-favorites .fi-sidebar-group-label {
  display: none;
}


/* -----------------------------------------------------------------------
 * 2. NAVIGATION GROUP EXPAND/COLLAPSE CHEVRON — spec 28.2 module groups.
 * Distinct from desktop icon-collapse (section above / gyro-theme.css):
 * this is Filament's PER-GROUP expand/collapse toggle
 * (`NavigationGroup::make()->collapsed()`, panel-navigation.php section 3),
 * which shows/hides a group's item list while the sidebar itself stays at
 * full width. [guess] in full — no confirmed v5.7 markup for the group
 * header's clickable toggle or its chevron icon.
 * --------------------------------------------------------------------- */

/* [guess] Best-effort target for the clickable group-header row itself,
 * assumed to be the element carrying .fi-sidebar-group-label OR a sibling
 * button — Filament may render the whole label as a <button>, or a
 * separate icon-only toggle next to a non-interactive label. Both are
 * covered so at least one matches; verify on the hydrated DOM which one
 * actually exists before trusting the hover/focus affordance below. */
.fi-sidebar-group-label,
.fi-sidebar-group [aria-expanded] { /* [guess] */
  cursor: pointer;
}
.fi-sidebar-group-label:hover {
  color: var(--color-text-primary);
}

/* [guess] Chevron rotation on expand/collapse — targets a `[data-open]` /
 * `aria-expanded="true"` pairing as the most standard accessible-disclosure
 * pattern; Filament's actual attribute name for this specific control is
 * unconfirmed. If neither attribute is present on the real markup this
 * rule simply never matches (safe no-op — the chevron just won't rotate,
 * not a broken layout). Rotation duration reuses the same
 * hover-and-focus-state motion allowance as every other state change here
 * (spec 28.5: 120–160ms ease-out). */
.fi-sidebar-group [aria-expanded] svg,
.fi-sidebar-group [data-open] svg { /* [guess] */
  transition-property: var(--motion-allowed-disclosure);
  transition-duration: var(--motion-duration-fast);
  transition-timing-function: var(--motion-easing-out);
}
.fi-sidebar-group [aria-expanded="true"] svg,
.fi-sidebar-group[data-open="true"] svg { /* [guess] */
  transform: rotate(90deg);
}


/* -----------------------------------------------------------------------
 * 3. TOP BAR — exactly six controls (spec 28.2). gyro-theme.css already
 * sets .fi-topbar height/border/background and 20px icon sizing for
 * `.fi-icon-btn` / `.fi-topbar-item` svgs. This section adds: layout for
 * the [project] custom controls panel-navigation.php renders (create-new),
 * a shared disabled-affordance treatment (used by create-new until
 * resources exist, and by company/branch switcher IF a future task
 * chooses the disabled-affordance path instead of omitting them — see
 * P0-081-nav-notes.md `topbar`), and hiding stock Filament chrome that
 * falls outside the six-control allowance.
 * --------------------------------------------------------------------- */

/* [project] Shared base for any custom (non-native-Filament) topbar
 * control this task renders, sized to the same 20px toolbar icon token as
 * gyro-theme.css already applies to native `.fi-icon-btn`/`.fi-topbar-item`
 * icons, so a custom control is visually indistinguishable in size from a
 * native one. */
.gyro-topbar-control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size-control-icon-button);
  height: var(--size-control-icon-button);
  border-radius: var(--radius-control);
  background-color: transparent;
  border: none;
  color: var(--color-text-secondary);
  cursor: pointer;
}
.gyro-topbar-control:hover:not([disabled]) {
  background-color: var(--color-bg-hover);
  color: var(--color-text-primary);
}
.gyro-topbar-control svg,
.gyro-topbar-control-icon {
  width: var(--icon-toolbar); /* 20px in toolbars, spec 28.5 */
  height: var(--icon-toolbar);
}
.gyro-topbar-control,
.gyro-topbar-control svg {
  transition-property: var(--motion-allowed-hover-and-focus-state);
  transition-duration: var(--motion-duration-fast);
  transition-timing-function: var(--motion-easing-out);
}

/* Disabled affordance — a control with no backing feature yet (spec: "do
 * not fake a control that does nothing"). Visibly inert: reduced opacity,
 * `not-allowed` cursor, no hover feedback. [stable] `:disabled`/
 * `[aria-disabled]` are native/ARIA, not Filament-specific — the safest
 * possible selector pair in this whole file. NEVER pairs with
 * `outline: none` — a disabled control still needs a visible focus ring if
 * it is reachable by Tab at all; the panel-navigation.php partial uses the
 * native `disabled` attribute specifically so it is REMOVED from the tab
 * order entirely (native `disabled` buttons are not focusable), which is
 * the correct a11y behaviour here, not an outline suppression. */
.gyro-topbar-control:disabled,
.gyro-topbar-control[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}
.gyro-topbar-control:disabled:hover,
.gyro-topbar-control[aria-disabled="true"]:hover {
  background-color: transparent;
  color: var(--color-text-secondary);
}

/* [project] Spacing between the six (or fewer, until every control has a
 * backing feature — see notes file) topbar controls. Targets the [guess]
 * `.fi-topbar-end` wrapper gyro-theme.css already flags as [guess] for its
 * own `order`/`margin-inline-start` fix; reusing the same wrapper here
 * rather than introducing a second one, since both files agree on the
 * same underlying assumption and should fail together if it's wrong, not
 * separately in two different ways. */
.fi-topbar-end { /* [guess] — see gyro-theme.css section 3 for the same tag on this selector */
  gap: var(--space-3);
}

/* Explicitly forbid decoration creeping into the topbar's six controls —
 * spec 28.9's "nothing decorative" extended to this zone specifically (the
 * dashboard-grid block in gyro-theme.css already forbids background-image
 * on widgets/sections; the topbar is a separate DOM branch and gets its
 * own equivalent guarantee here rather than relying on the dashboard rule
 * reaching it, which it structurally cannot). */
.fi-topbar,
.fi-topbar * {
  background-image: none;
}

/* Stock-chrome removal beyond the six controls. [guess] — EVERY selector in
 * this block is unverified against the live hydrated topbar and must be
 * confirmed present (and confirmed to be the RIGHT element, not something
 * load-bearing) before uncommenting. Left commented deliberately: an
 * active `display: none` rule targeting the wrong element on a live panel
 * could hide something users need (e.g. the actual user-menu trigger) with
 * no error at all — the single worst failure mode a CSS-only removal can
 * have. Per spec 28.2 "nothing else" — but "nothing else" is enforced by
 * NOT REGISTERING the extra chrome in panel-navigation.php wherever a
 * config option exists to do that (preferred, see notes file `topbar`),
 * with CSS hiding reserved as the last resort for stock chrome that has no
 * config toggle at all. */
/*
.fi-topbar [data-theme-switcher],
.fi-topbar-item.fi-theme-switcher {
  display: none;
}
*/


/* -----------------------------------------------------------------------
 * 4. MODULE WORKSPACE LANDING PAGE — spec 28.2/28.9, replaces the
 * widget-based dashboard as the default module entry point.
 * panel-navigation.php section 8 builds this as a PLAIN Blade page, not a
 * Filament widget grid — so none of gyro-theme.css's `.fi-sc.fi-grid`
 * wrapper-vs-widget hydration handling applies here, and none of it is
 * duplicated below. This is a deliberate, simpler, independent grid.
 * --------------------------------------------------------------------- */

/* [project] Plain CSS grid, not Filament's schema grid — no hydration
 * phases to defend against. Mobile-first single column; tiers added below
 * at the same breakpoint assumption gyro-theme.css's dashboard block uses
 * ([guess]: Tailwind v4 default md/xl, unconfirmed against this panel's
 * actual compiled breakpoints — see gyro-theme.css section 3 for the same
 * caveat, repeated here rather than silently assumed solved because it was
 * solved once already). */
.gyro-workspace-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--size-gap-section); /* MANDATED section gap, same token as the dashboard grid */
}
@media (min-width: 768px) { /* [guess] Tailwind v4 default `md` */
  .gyro-workspace-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1280px) { /* [guess] Tailwind v4 default `xl` */
  .gyro-workspace-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Shortcut tile — one per registry module in the domain (panel-
 * navigation.php section 8). Structure via border only, no shadow, no
 * background-image — spec 28.1 principle 3 / 28.9. This is the "number
 * card / shortcut tile" spec 28.2 calls for, deliberately NOT a gradient
 * KPI card: flat surface, one hairline border, one radius token, nothing
 * else. */
.gyro-workspace-tile {
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border-hairline); /* hairline: allowed literal */
  border-radius: var(--radius-control);
  box-shadow: var(--elevation-none);
  padding: var(--space-4);
  background-image: none; /* belt-and-braces, spec 28.9, even though a plain div has no way to have picked one up */
}
.gyro-workspace-tile:hover {
  border-color: var(--color-border-strong);
  background-color: var(--color-bg-hover);
}

.gyro-workspace-tile-title {
  font-size: var(--type-size-medium);
  line-height: var(--type-line-medium);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-primary);
  margin-block-end: var(--space-2);
}

/* Number card — a plain stat (count/value), styled identically to a
 * shortcut tile's internal metric row rather than as its own decorated
 * card, so a workspace page reads as one flat grid of equal-weight tiles,
 * not a hierarchy of "important gradient numbers" vs "boring links" —
 * exactly what spec 28.9 rules out. Tabular figures per spec 28.5/28.1. */
.gyro-workspace-number {
  font-family: var(--type-family-numeric, var(--type-family-ui)); /* falls back to UI stack if a dedicated numeric family token is unset for this context */
  font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--type-size-large);
  line-height: var(--type-line-large);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-primary);
}
.gyro-workspace-number-label {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  color: var(--color-text-muted);
}

/* Grouped links list — the "grouped links to the module's documents and
 * reports" part of a workspace tile. Plain list, no card-per-link (spec
 * 28.1 principle 1: no card-per-field/per-item layouts). */
.gyro-workspace-links {
  list-style: none;
  margin: 0;
  padding: 0;
  margin-block-start: var(--space-2);
}
.gyro-workspace-links li + li {
  border-block-start: 1px solid var(--color-border-hairline); /* hairline: allowed literal */
}
.gyro-workspace-links a {
  display: block;
  padding-block: var(--space-1);
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  color: var(--color-text-link);
}
.gyro-workspace-links a:hover {
  color: var(--color-accent-hover);
}

/* "Core" chip on a mandatory module's tile (panel-navigation.php renders
 * `.gyro-chip.gyro-chip--info` for `core`, the one mandatory module) —
 * intentionally REUSES gyro-theme.css's existing `.gyro-chip*` family
 * rather than introducing a parallel one; if gyro-theme.css hasn't defined
 * `.gyro-chip--info` yet, that is the CSS half's own chip-family
 * completeness to check, not something duplicated here. No rule added in
 * this file for `.gyro-chip` itself — this comment exists purely so the
 * dependency is visible to whoever reviews this file next. */


/* -----------------------------------------------------------------------
 * 5. REDUCED MOTION — no new rule needed.
 * Every transition declared above uses the same `var(--motion-…)` tokens
 * gyro-theme.css already uses, and tokens.css's own global
 * `prefers-reduced-motion` override block (an `!important` block scoped to
 * `*, *::before, *::after`) already reduces every one of them regardless
 * of which file declared the transition. Restated here only as a
 * checklist entry, not a rule: confirm on the hydrated DOM with
 * `prefers-reduced-motion: reduce` simulated that the group chevron
 * rotation (section 2) and topbar-control hover (section 3) both still
 * respect the shorter reduced-motion duration, exactly like
 * gyro-theme.css's own verification_checklist item 17.
 * --------------------------------------------------------------------- */


/* ============================================================================
 * SPRINT 47 - WAVE 3: LIST DENSITY. MEASURED, NOT REASONED ABOUT.
 *
 * Read on the hydrated DOM of /admin/invoices on the sandbox at b22d944, dark
 * mode, 1280px viewport, after
 * `document.querySelectorAll('[x-intersect]').length === 0`:
 *
 *   body row height   35.18px on plain rows, 61.11-61.25px on rows carrying a
 *                     tag list - five of the ten rows on page one
 *   header row        47.00px
 *   cell padding      7px / 7px  (correct; --size-pad-cell-y already wins)
 *   .fi-ta-text       padding-block 13.5px, padding-inline 10.125px
 *
 * WHY THE EXISTING CORRECTION ("Row height read 61.3px", further up this file)
 * MISSED IT. That block zeroes `.fi-ta-text-item`. Filament v5.7 emits TWO
 * shapes from TextColumn - verified in
 * vendor/filament/tables/src/Columns/TextColumn.php on the server:
 *   :240  single value  ->  ONE <div class="fi-ta-text fi-ta-text-item ...">
 *   :419  value LIST    ->  <ul class="fi-ta-text ..."> of <li class="fi-ta-text-item">
 * and the 13.5px belongs to `.fi-ta-text:not(.fi-inline)` - the WRAPPER - not to
 * `.fi-ta-text-item`. On the single-value shape both classes sit on the same
 * element, so the old rule appeared to work. On a list (tags, any multi-value
 * column) the <ul> carries the padding and the <li> does not, so the rule
 * matched the wrong element and 27px survived on exactly the rows the owner
 * complained about. That is CLAUDE.md rule 1 happening again in miniature: the
 * selector matched what the markup looked like rather than what it is.
 *
 * The old block is deliberately LEFT IN PLACE - it is correct for the <li>, and
 * deleting rules is how this project loses work. These add to it.
 *
 * Every value below is a `var(--...)` from tokens.css except 1px hairlines and
 * the 0/2 magnitudes token-lint allows. No colour is declared outside a token,
 * so dark mode is inherited rather than re-declared: each colour resolves
 * through the same [data-theme="dark"], html.dark block that governs the rest
 * of this file. Both modes were checked on the live DOM.
 * ==========================================================================*/

/* 1. THE 27px. The cell owns the row's vertical rhythm; a wrapper inside the
 *    cell owns none of it. Filament pads every column-type wrapper identically
 *    (`padding-block: calc(var(--spacing) * 4)`, which is 13.5px here because
 *    --spacing is .25rem and this panel's root font-size is 13.5px), so all of
 *    them are named rather than only the one that happened to be measured.
 *    padding-inline goes with it: the cell already provides --size-pad-cell-x,
 *    and the wrapper's extra 10.125px is what put every VALUE 12px to the right
 *    of its own column HEADER - measured on the same DOM, header label at x=8px
 *    inside the cell, value at x=18.125px. Removing it also took the invoices
 *    table from 1328px wide to 1152px in a 1145px container, which is most of
 *    the "capsule sliced in half" the owner saw. */
.fi-ta-cell .fi-ta-text,
.fi-ta-cell .fi-ta-text-summary,
.fi-ta-cell .fi-ta-checkbox,
.fi-ta-cell .fi-ta-color,
.fi-ta-cell .fi-ta-icon,
.fi-ta-cell .fi-ta-image,
.fi-ta-cell .fi-ta-select,
.fi-ta-cell .fi-ta-text-input,
.fi-ta-cell .fi-ta-toggle,
.fi-ta-cell .fi-ta-icon-count-summary,
.fi-ta-cell .fi-ta-range-summary,
.fi-ta-cell .fi-ta-values-summary {
  padding-block: 0;
  padding-inline: 0;
}

/* 2. THE 47px HEADER ROW. The header's own cells were already 34px - the row
 *    was 47px because of ONE cell: the select-all checkbox header, which
 *    Filament emits as <th class="fi-ta-cell fi-ta-selection-cell">. The
 *    density block further up this file reaches `.fi-ta-row .fi-ta-cell` and
 *    `td.fi-ta-cell`; a <th> inside a <thead> is neither, so Filament's
 *    `:has(.fi-ta-record-checkbox)` 13.5px padding survived - and a table row
 *    is as tall as its tallest cell. Measured after this rule: 34.0px. */
th.fi-ta-cell {
  height: var(--size-row-table);
  padding-block: var(--size-pad-cell-y);
  padding-inline: var(--size-pad-cell-x);
  box-sizing: border-box;
}

/* 3. ROW ACTIONS MUST NOT WRAP. Filament ships `.fi-ta-actions` with
 *    `fi-wrapped` and an 8px row-gap, so four icon buttons in a narrow last
 *    column stack onto two lines and take the whole row to 51px - measured. A
 *    row-action strip is a fixed set of controls, not prose: it gets the width
 *    it needs and the table scrolls (section 4) rather than the row growing.
 *    The column gap drops to the 4px base unit at the same time. */
.fi-ta-cell .fi-ta-actions {
  flex-wrap: nowrap;
  gap: var(--space-1);
}

/* 4. THE CLIPPED CAPSULE. Filament already gives `.fi-ta-content-ctn`
 *    `overflow: auto`, so a wide table does scroll inside itself and the page
 *    never scrolls sideways - verified, documentElement.scrollWidth ===
 *    clientWidth at 1280px with a 1328px-wide table. What was missing was any
 *    sign that it scrolls, which is why a half-shown "Slow payer" chip read as
 *    a bug rather than as more table. A thin scrollbar in token colours says
 *    "there is more to the right"; overscroll containment stops a horizontal
 *    flick turning into a browser back-navigation.
 *
 *    NOT DONE, DELIBERATELY: no `mask-image` fade at the scroll edges. A mask
 *    makes its element a containing block for position:fixed descendants AND
 *    clips them - that is exactly what broke all fourteen top-bar menus in
 *    sprint 46, and this container holds every row-action dropdown in the
 *    table. See gyro-layouts.css lines 276-287, which had already written the
 *    lesson down. */
.fi-ta-content-ctn {
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--color-border-strong) transparent;
}

/* 5. FROZEN HEADER, on the elements that actually exist. `.fi-ta-header-cell`
 *    is real in v5.7 (vendor index.blade.php:1655 and :1768), but the selection
 *    column's header is a plain `.fi-ta-cell`, so a sticky rule naming only the
 *    former leaves a hole the width of the checkbox column. Both are named.
 *
 *    HONEST LIMIT: `.fi-ta-content-ctn` is a scroll container with no height
 *    bound, so `top: 0` sticks to the top of the TABLE's scrollport. That
 *    freezes the header while the table is scrolled; it cannot freeze it
 *    against PAGE scroll, because the page is a different scrollport. Making it
 *    do that needs a height bound on this container, and the only honest bound
 *    is 100dvh minus the real distance from the viewport top - measured at
 *    280px on this screen, but different on every screen with tabs, widgets or
 *    a two-line heading. A guessed constant there is the kind of rule that
 *    ships looking right on one page and wrong on a hundred, so it is not
 *    guessed here. It belongs with whoever owns the page shell. */
.fi-ta-content-ctn thead .fi-ta-header-cell,
.fi-ta-content-ctn thead th.fi-ta-cell {
  position: sticky;
  top: 0;
  z-index: 2; /* a stacking index, not a length */
  background-color: var(--color-bg-surface);
}

/* 6. THE TOTALS ROW - spec 28.3, "totals row for numeric columns". Filament's
 *    real class names, read from
 *    vendor/filament/tables/resources/views/components/summary/{index,row}.blade.php:
 *      tr.fi-ta-row.fi-ta-summary-row              the totals row itself
 *      tr.fi-ta-row.fi-ta-summary-header-row       its repeated column labels
 *      .fi-ta-cell.fi-ta-summary-row-heading-cell  the "All invoices" cell
 *      .fi-ta-text-summary / -label                the figure and its caption
 *    The `.gyro-ta-totals-row, tfoot.fi-ta-footer` pair earlier in this file
 *    matches nothing in v5.7; it is left where it is, and these are the ones
 *    that match. */
.fi-ta-row.fi-ta-summary-row .fi-ta-cell,
.fi-ta-row.fi-ta-summary-header-row .fi-ta-cell {
  background-color: var(--color-bg-sunken);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-primary);
}
.fi-ta-row.fi-ta-summary-row .fi-ta-cell {
  /* the hairline goes ABOVE the totals, separating them from the data */
  box-shadow: inset 0 1px 0 0 var(--color-border-hairline);
}
.fi-ta-cell .fi-ta-text-summary {
  row-gap: 0;
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  color: var(--color-text-primary);
  font-weight: var(--type-weight-semibold);
  /* A multi-currency total is one line per currency - MoneyTotal returns them
   * newline-separated and Filament escapes the value into a <span>, where a
   * newline would otherwise collapse to a space and read as one wrong figure.
   * `pre-line` collapses runs of spaces but keeps the line breaks, and is a
   * no-op on every single-currency total. */
  white-space: pre-line;
}
/* The caption above each figure reads as a column label, not as data - the same
 * micro-caps shape the header row uses, so the eye groups the two.
 *
 * SECONDARY, NOT MUTED, AND THE RATIO IS COMPUTED RATHER THAN JUDGED. This
 * caption is the only thing on the row that says WHICH figure is underneath -
 * "Invoiced" against "Paid" - so reading it wrong is reading the wrong number.
 * Measured against `--color-bg-sunken`, which the rule two blocks up paints the
 * summary cell:
 *
 *     text.muted      #767b82 on #eef0f1 (light)   3.73:1   FAILS 4.5:1
 *                     #8d9198 on #1c1d1f (dark)    5.33:1
 *     text.secondary  #565a61 on #eef0f1 (light)   6.06:1
 *                     #b7bac0 on #1c1d1f (dark)    8.67:1
 *
 * 11px is small text under WCAG 1.4.3, so the 3:1 large-text allowance that
 * tokens.css grants `text.muted` cannot be claimed here. The same pair, at the
 * same size, was measured and REJECTED for the Ctrl+K key cap 200 lines into
 * gyro-layouts.css section 6.1 in this same sprint - the caption is quieter
 * than the figure because it is 11px and the figure is 13.5px semibold, not
 * because the ink is faint. tests/Feature/Theme/SmallTextContrastTest.php
 * recomputes this from tokens.css on every push, in both themes. */
.fi-ta-text-summary-label {
  font-size: var(--type-size-micro);
  line-height: var(--type-line-micro);
  font-weight: var(--type-weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-secondary);
  /* `white-space: pre-line` on `.fi-ta-text-summary` INHERITS, and a caption
   * never contains a separator, so all it can do here is harm. Filament emits
   *
   *     <span class="fi-ta-text-summary-label">\n        Invoiced        </span>
   *
   * - the newline is the Blade template's own indentation - and `pre-line`
   * preserves a leading segment break rather than collapsing it. Measured in
   * Chrome 151 on those exact bytes at 11px/14px: 28px with `pre-line`, 14px
   * with `normal`. A blank line above every caption, on every totals row in
   * the product.
   *
   * THE SAME THING IS STILL TRUE OF THE FIGURE and this rule cannot reach it:
   * the value span carries the same leading newline, so a single-currency
   * total renders 40px tall where 20px is one line. CSS has no way to drop a
   * leading segment break, so the fix belongs at the source - MoneyTotal
   * returning its lines as an Htmlable joined with <br> instead of "\n", with
   * `pre-line` dropped from the block above. Written down here rather than
   * hacked around with a negative margin, which would depend on a vendor
   * template's indentation staying exactly one newline deep. */
  white-space: normal;
}
/* Right-align the figure under a right-aligned money column: the cell carries
 * fi-align-end, but the summary is its own grid and has to be told. */
.fi-ta-cell.fi-align-end .fi-ta-text-summary,
.fi-ta-cell.fi-align-right .fi-ta-text-summary {
  justify-items: end;
  text-align: end;
}

/* 7. TABULAR LINING FIGURES - spec 28.5, "everywhere money or quantities
 *    appear". The correction block further up binds them to `.fi-ta-cell` and
 *    gyro-chrome.css binds them to `.fi-ta-table`, which covers values in a
 *    list. Measured as STILL ABSENT (font-variant-numeric: normal) on every
 *    form control - so an editable line-item grid, the one place in the product
 *    where a column of numbers is typed rather than read, had proportional
 *    digits. Bound here on inputs, on the totals figures (which sit directly
 *    under the column they total, where drift is most visible) and on badges,
 *    which is where a quantity or an age most often ends up. `tnum` only
 *    affects digits, so this is a no-op on names and addresses. */
.fi-input,
.fi-input-wrp input,
.fi-fo-text-input input,
.fi-ta-text-summary,
.fi-ta-summary-row .fi-ta-cell,
.fi-badge {
  font-variant-numeric: var(--type-numeric-variant);
  font-feature-settings: var(--type-numeric-feature-settings);
}

/* ============================================================================
 * P0-085 — REDUCED MOTION, CORRECTED. Spec 28.10's last clause.
 *
 * MEASURED, NOT REASONED ABOUT. tokens.css (which is GENERATED, and loads
 * immediately before this file) carries this, verbatim:
 *
 *     @media (prefers-reduced-motion: reduce) {
 *       :root {
 *         --motion-duration: 80ms;
 *         --motion-easing: linear;
 *         --motion-transform-policy: opacity-and-color-only;
 *       }
 *       *, *::before, *::after {
 *         animation-duration: var(--motion-duration-base) !important;
 *         transition-duration: var(--motion-duration-base) !important;
 *         scroll-behavior: auto !important;
 *       }
 *     }
 *
 * Two faults, both of which make the block do the opposite of its job.
 *
 * 1. THE :root HALF IS DEAD. `--motion-duration` and `--motion-easing` are
 *    written by nothing else and read by nothing at all: a grep of public/css,
 *    resources/css and resources/views for the bare `var(--motion-duration)`
 *    and `var(--motion-easing)` forms returns 0 hits each. Every rule in this
 *    product spells the token `--motion-duration-instant|-fast|-base`, so the
 *    override lands on names no declaration mentions.
 *
 * 2. THE * HALF PINS EVERYTHING TO --motion-duration-base, WHICH IS 160ms —
 *    the NORMAL base duration. The reduced value is a different token,
 *    `--motion-reduced-duration: 80ms`, and it was never referenced. So a
 *    reader who asks their operating system for less motion gets:
 *      - every 120ms `instant` transition made SLOWER, to 160ms;
 *      - every 140ms `fast` transition made slower, to 160ms;
 *      - not one transition anywhere reduced to the declared 80ms;
 *      - and, because `!important` beats specificity, the two carefully
 *        written reduced-motion blocks in gyro-layouts.css (the topbar
 *        controls at :1351 and the overflow menu at :1617, both of which DO
 *        name --motion-reduced-duration) overridden and never once applied.
 *
 * The fix is one declaration block, and it belongs here rather than in
 * tokens.css because tokens.css is generated from resources/design-tokens/ and
 * hand-editing the served copy is how dark mode has nearly been lost twice
 * (see WorkspaceContrastTest::test_the_served_dark_gate_is_still_in_place).
 * This file loads immediately after it, so an equally-specific !important
 * declaration here wins on order. The generator should still be corrected —
 * that is reported, not fixed here, because nobody owns those files this
 * sprint.
 *
 * ONLY THE TRANSITION HALF IS CORRECTED, DELIBERATELY. The animation half of
 * tokens.css's block is also wrong — a spinner written as a 1s infinite
 * rotation is forced to 160ms, six revolutions a second, under a preference
 * that exists to ask for less — but there is no long-duration token to pin it
 * to and no way in CSS alone to tell a decorative animation from a progress
 * indicator. Making that call belongs with the token file, so the number is
 * written down here and left alone rather than guessed at. No file in
 * public/css/gyro-*.css declares a single @keyframes or animation, so nothing
 * this project authored is affected either way.
 * ==========================================================================*/
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: var(--motion-reduced-duration) !important;
    transition-timing-function: var(--motion-reduced-easing) !important;
    scroll-behavior: auto !important;
  }
}
