/* =============================================================================
 * gyro-chrome.css — Gyro Suite page-composition layer. Sprint 25.
 *
 * WHY A SECOND FILE AND NOT AN EDIT TO gyro-theme.css.
 * gyro-theme.css (P0-081) is the COMPONENT layer: it binds design-tokens.json
 * to Filament's fi-* components, and it works — measured on the live panel,
 * table rows are 35px with 7px cell padding, exactly what size.row.table and
 * size.pad.cell.y mandate. Nothing here re-does that work.
 *
 * What was never done is the PAGE layer: the chrome around the components.
 * A 47px table header sitting on top of 35px rows, a 25px/700 page heading
 * where the type scale stops at 22px/600, and two full-width toolbar bands
 * above every table are not component faults - each component is correct and
 * the page they compose into is loose. That is what this file fixes.
 *
 * Loaded after gyro-theme.css. Both are unlayered, so both outrank every
 * @layer rule in Filament's compiled app.css regardless of specificity; within
 * the two, source order decides, and this one is second on purpose.
 *
 * EVERY VALUE HERE IS A TOKEN. If you find yourself typing a hex or a pixel
 * count that is not in tokens.css, the answer is a token, not a literal.
 * ========================================================================== */

/* -----------------------------------------------------------------------------
 * 1. PAGE HEADING — the type scale stops at 22px. Nothing may exceed it.
 *
 * Filament renders the page title at text-2xl/700, which computes to 25.3px
 * here because the root font-size is the 13.5px base. type.size.display is
 * 22px at weight 600 and it is the largest step in the system; a heading that
 * outruns its own scale is the loudest possible signal that a design system
 * was declared and not applied.
 * -------------------------------------------------------------------------- */
.fi-header-heading {
  font-size: var(--type-size-display);
  line-height: var(--type-line-display);
  font-weight: var(--type-weight-semibold);
  letter-spacing: -0.01em;
  color: var(--color-text-primary);
}

.fi-header-subheading {
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  color: var(--color-text-secondary);
}

/* The breadcrumb is orientation, not content. Micro size, muted, and it stops
   competing with the heading directly beneath it. */
.fi-breadcrumbs,
.fi-breadcrumbs-item-label {
  font-size: var(--type-size-micro);
  line-height: var(--type-line-micro);
  color: var(--color-text-muted);
}

/* Page header block: one section gap below, not the browser's guess. */
.fi-header {
  padding-block-end: var(--space-4);
  margin-block-end: 0;
  border-block-end: 1px solid var(--color-border-hairline);
  /* A page with several header actions must not crush its own heading.

     `.fi-header` is a flex row holding the heading column and the actions.
     The actions take their natural width and the heading column, having no
     floor, shrinks to whatever is left - on /admin/field-visibility, which
     carries FIVE actions, that was 141px measured, so a subheading whose own
     `max-width` is 567px wrapped down six lines in a ribbon while ~500px of
     empty space sat beside the buttons.

     `flex-wrap` lets the actions drop to their own line instead, and the
     min-width is the point at which they do. 22rem is a readable measure for
     the sentence under a page title and still leaves two or three buttons
     beside it on a normal screen. Found by opening the page. */
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* The heading column inside that row: allowed to grow, never squeezed below a
   readable measure. `min-width: 0` is the usual flexbox advice and is exactly
   wrong here - it is what permits the collapse. */
.fi-header > :first-child {
  flex: 1 1 22rem;
  min-width: min(22rem, 100%);
}

/* -----------------------------------------------------------------------------
 * 2. TABLE HEADER ROW — 34px, to match the 34px body rows beneath it.
 *
 * Measured before this file: header cell 47px, body cell 35px. A header a
 * third taller than its rows reads as a separate object floating above the
 * table rather than the top edge of it.
 *
 * The label itself becomes a micro-caps column label. This is the one piece of
 * deliberate typographic character in the table: it separates "what this
 * column is" from "what the data says" by shape, so the eye stops re-reading
 * the header when scanning values.
 * -------------------------------------------------------------------------- */
.fi-ta-header-cell {
  height: var(--size-row-table);
  padding-block: var(--size-pad-cell-y);
  padding-inline: var(--size-pad-cell-x);
}

.fi-ta-header-cell,
.fi-ta-header-cell button,
.fi-ta-header-cell .fi-ta-header-cell-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-muted);
  white-space: nowrap;
}

/* -----------------------------------------------------------------------------
 * 3. NUMERALS — tabular, everywhere a number can line up under another number.
 *
 * type.numeric.featureSettings is 'tnum' 1, 'lnum' 1 and it exists for exactly
 * this: proportional digits make a column of currency ragged, and a ragged
 * money column is the difference between a ledger and a list of strings. The
 * token was authored and never bound to anything.
 * -------------------------------------------------------------------------- */
.fi-ta-table,
.fi-ta-text,
.fi-wi-stats-overview-stat-value,
.fi-in-text {
  font-variant-numeric: var(--type-numeric-variant);
  font-feature-settings: var(--type-numeric-feature-settings);
}

/* -----------------------------------------------------------------------------
 * 4. THE TOOLBAR — one band, not three.
 *
 * Stock Filament stacks the header-actions row, then the filter/search row,
 * then the table. On the invoices screen that is ~110px of chrome before the
 * first row of data, on a page whose entire job is showing rows of data.
 * -------------------------------------------------------------------------- */
.fi-ta-header-ctn {
  border-block-end: 1px solid var(--color-border-hairline);
}

.fi-ta-header-ctn > .fi-ta-actions,
.fi-ta-header-ctn > div > .fi-ta-actions {
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
}

/* Filament's search/filter strip. Collapse its generous block padding to the
   4px rhythm and let the controls define the height instead of the container. */
.fi-ta-header-toolbar {
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
  gap: var(--space-2);
  min-height: 0;
}

.fi-ta-search-field .fi-input-wrp,
.fi-ta-search-field input {
  height: var(--size-control-input);
}

/* The footer carries a count and a pager; it is not a section. */
.fi-ta-footer {
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  color: var(--color-text-secondary);
  border-block-start: 1px solid var(--color-border-hairline);
}

/* -----------------------------------------------------------------------------
 * 5. ROW ACTIONS — an icon, not the word "Edit" ten times.
 *
 * A text link repeated once per row is ten pieces of identical, coloured,
 * left-to-right-read noise in the busiest column of the screen. The label is
 * kept in the accessible name (Filament already sets one); only the visible
 * text is dropped, and only in a table body, and only when the action carries
 * an icon to fall back to. An icon-less action keeps its words.
 * -------------------------------------------------------------------------- */
.fi-ta-row .fi-ta-actions .fi-btn:has(.fi-btn-icon) .fi-btn-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.fi-ta-row .fi-ta-actions .fi-btn:has(.fi-btn-icon) {
  padding-inline: var(--space-1);
  min-width: var(--a11y-target-row-action);
  min-height: var(--a11y-target-row-action);
  justify-content: center;
}

/* Row actions are quiet until the row is under the pointer. They are still
   focusable and still meet the 24px target; they simply stop shouting. */
.fi-ta-row .fi-ta-actions .fi-btn {
  color: var(--color-text-muted);
  transition: color var(--motion-duration-fast) var(--motion-easing-out);
}

.fi-ta-row:hover .fi-ta-actions .fi-btn,
.fi-ta-row .fi-ta-actions .fi-btn:focus-visible {
  color: var(--color-text-link);
}

/* -----------------------------------------------------------------------------
 * 6. STATUS CHIPS — legible at 11px, which outline-only chips are not.
 *
 * With the semantic palette registered in the panel, Filament now emits a real
 * colour per status. What is left here is what this PAGE layer adds: a word
 * that is not touching its own border, a little more weight, and a label that
 * cannot wrap.
 *
 * THE HEIGHT IS NOT SET HERE ANY MORE, AND THAT IS THE FIX.
 * Until sprint 48 this rule and gyro-theme.css section 5 BOTH declared
 * `height: var(--size-control-chip)`, plus the same `font-size` and the same
 * `line-height`, with only `padding-inline` differing. Two rules setting one
 * geometry in two files, and this one loads second, so a correction made in
 * gyro-theme.css changed nothing and read as a fix that did not work. The chip
 * is now sized by border + line-height in gyro-theme.css, which is also the
 * only one of the two stylesheets the customer portal loads -
 * PortalPanelProvider ships tokens + gyro-theme and NOT this file, so a chip
 * geometry that lived here would simply be absent over there. Add nothing to
 * this rule that a portal chip would also need. */
.fi-badge {
  padding-inline: var(--space-2);
  font-weight: var(--type-weight-medium);
  /* Three sources of tracking meet on this element - vendor's size modifiers
   * set `--tw-tracking` to tighter/tight, gyro-theme.css sets none, and this
   * wins by source order. Left as it was: 0.02em is what an 11px all-caps-ish
   * status word needs, and changing it is a legibility decision, not a
   * de-duplication. */
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* -----------------------------------------------------------------------------
 * 7. FORMS — a form is a set of sections, not a column of inputs.
 *
 * The create screens render every field at full width down a single column,
 * with no containing surface. Two changes: give the form a surface so it stops
 * floating on the page background, and hold the section rhythm at the mandated
 * 24px so groups read as groups.
 * -------------------------------------------------------------------------- */
.fi-fo-component-ctn > .fi-sc,
.fi-form > .fi-sc {
  gap: var(--size-gap-section);
}

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

.fi-fo-field-wrp-hint,
.fi-fo-field-wrp-helper-text {
  font-size: var(--type-size-micro);
  line-height: var(--type-line-micro);
  color: var(--color-text-muted);
}

/* A repeater item is a record inside a record. Give it the surface and the
   hairline that says so, instead of an unlabelled band with a lone bin icon. */
.fi-fo-repeater-item {
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border-hairline);
  border-radius: var(--radius-control);
}

.fi-fo-repeater-item-header {
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
  border-block-end: 1px solid var(--color-border-hairline);
  background-color: var(--color-bg-sunken);
  border-start-start-radius: var(--radius-control);
  border-start-end-radius: var(--radius-control);
  min-height: var(--size-row-field);
}

/* -----------------------------------------------------------------------------
 * 8. SECTION HEADINGS — 15px, the "medium" step. Not another display size.
 * -------------------------------------------------------------------------- */
.fi-section-header-heading {
  font-size: var(--type-size-medium);
  line-height: var(--type-line-medium);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-primary);
}

.fi-section-header-description {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  color: var(--color-text-muted);
}

/* -----------------------------------------------------------------------------
 * 9. STAT TILES — a label is a label, a number is the point.
 *
 * The dashboard's seven tiles currently render label, value and caption at
 * nearly the same visual weight, so the eye has nowhere to land. The value is
 * the display step; the label above it is a micro-caps annotation, matching
 * the table column labels so the two read as the same language.
 * -------------------------------------------------------------------------- */
.fi-wi-stats-overview-stat-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-muted);
}

.fi-wi-stats-overview-stat-value {
  font-size: var(--type-size-display);
  line-height: var(--type-line-display);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-primary);
}

/* NO `color` HERE, AND THAT IS THE POINT. Filament v5 puts a Stat's ->color()
   on the DESCRIPTION element (fi-color fi-color-danger …), not on the value —
   verified by walking the live node tree, because the stat root carries no
   colour class at all and looking there suggests the colours were never set.
   This file is unlayered, so a `color` declaration here beats Filament's
   layered colour class and silently erases the only colour signal the
   dashboard has. Set size here; let the colour class through. */
.fi-wi-stats-overview-stat-description {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
}

/* Carry the same signal on the FIGURE, which is where the eye actually lands.
   A red caption under a white "2" is a footnote; a red "2" is the point of
   having a dashboard. Keyed off the description's colour class so PHP stays
   the single source of what is urgent. */
.fi-wi-stats-overview-stat:has(.fi-color-danger) .fi-wi-stats-overview-stat-value {
  color: var(--color-status-danger-text);
}

.fi-wi-stats-overview-stat:has(.fi-color-warning) .fi-wi-stats-overview-stat-value {
  color: var(--color-status-warning-text);
}

.fi-wi-stats-overview-stat:has(.fi-color-success) .fi-wi-stats-overview-stat-value {
  color: var(--color-status-success-text);
}

.fi-wi-stats-overview-stat:has(.fi-color-info) .fi-wi-stats-overview-stat-value {
  color: var(--color-status-info-text);
}

.fi-wi-stats-overview-stat {
  padding: var(--space-3);
  gap: var(--space-1);
}

/* -----------------------------------------------------------------------------
 * 10. LINKS — the accent, and only where a link actually is.
 * -------------------------------------------------------------------------- */
/* P0-085, AND THIS WAS A LEVEL A FAILURE, NOT A REFINEMENT.
 *
 * `text-decoration-color: transparent` at rest meant an inline link was
 * distinguished from the words around it by COLOUR AND NOTHING ELSE. WCAG
 * 1.4.1 (Use of Colour, Level A) permits that only under technique G183, which
 * requires the link colour to clear 3:1 against the SURROUNDING TEXT - not
 * against the background, which is the check everybody runs instead - and a
 * non-colour cue on hover AND on focus.
 *
 * Measured against tokens.css, both themes, and it fails every way it can:
 *
 *     light  text.link #0a5cc4  vs  text.primary   #1a1d21  = 2.69   FAIL
 *     light  text.link #0a5cc4  vs  text.secondary #565a61  = 1.10   FAIL
 *     dark   text.link #4d9bf0  vs  text.primary   #e9eaec  = 2.40   FAIL
 *     dark   text.link #4d9bf0  vs  text.muted     #8d9198  = 1.09   FAIL
 *
 * 1.10:1 is the same colour. In a table cell's description text a reader with
 * a colour deficiency had no way at all to tell there was a link there.
 *
 * The underline is now permanent. On hover and on FOCUS - the old rule had
 * hover only, which is half of what G183 asks and is the half a keyboard user
 * never gets - it thickens, so the interactive cue survives without the link
 * shouting at rest. `text-decoration-thickness` rather than a colour change so
 * the emphasis is a shape, not a second colour.
 *
 * The transition property changes with it: the old one animated
 * text-decoration-color, which no longer moves. */
.fi-link,
.fi-ta-text a:not(.fi-btn) {
  color: var(--color-text-link);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: text-decoration-thickness var(--motion-duration-fast) var(--motion-easing-out);
}

.fi-link:hover,
.fi-link:focus-visible,
.fi-ta-text a:not(.fi-btn):hover,
.fi-ta-text a:not(.fi-btn):focus-visible {
  text-decoration-thickness: 2px;
}

/* -----------------------------------------------------------------------------
 * 11. CORRECTIONS FROM THE FIRST DEPLOY — measured, not guessed.
 *
 * Two rules in this file missed, and both missed for the same reason: I wrote
 * the selector from what Filament's markup looked like rather than from what it
 * IS. Measuring the live DOM after deploy gave the real class names and the
 * real numbers. Recording that here because it is the third time this project
 * has paid for a guess about a class name.
 * -------------------------------------------------------------------------- */

/* MEASURED: .fi-ta-header was 67px with 13.5px/20.25px padding, holding one
   right-aligned button in an otherwise empty band. Section 4 above targeted
   `.fi-ta-header-ctn > .fi-ta-actions`, which is not where the actions live —
   the real child is `.fi-ta-actions` directly inside `.fi-ta-header`. */
.fi-ta-header {
  padding-block: var(--space-2);
  padding-inline: var(--space-3);
  min-height: 0;
}

.fi-ta-header .fi-ta-actions {
  min-height: 0;
  row-gap: var(--space-2);
}

/* MEASURED: .fi-ta-header-cell computed to 47px even though its own padding was
   already the mandated 7px — the height came from the sort button inside it,
   which carries its own padding, and `height` on a table-cell is a minimum, not
   a maximum. The fix has to reach the button, not the cell. */
.fi-ta-header-cell button,
.fi-ta-header-cell .fi-ta-header-cell-label {
  padding: 0;
  min-height: 0;
  line-height: var(--type-line-micro);
}

/* The sort indicator is an annotation on a micro-caps label, so it takes the
   list icon size rather than the toolbar size it inherits. */
.fi-ta-header-cell .fi-icon {
  width: var(--icon-list);
  height: var(--icon-list);
}

/* -----------------------------------------------------------------------------
 * 12. THE DASHBOARD GRID.
 *
 * The MEASURED structure, on a fully hydrated dashboard (0 pending x-intersect,
 * scrolled to the bottom and back — a lazy widget that never intersected
 * reports skeleton numbers and they are fiction):
 *
 *   .fi-page-main
 *     div.fi-grid.fi-sc                          outer page grid, 6 columns
 *       div.fi-grid-col                          ONE child, and it wrapped
 *         div                                    (an unclassed div)
 *           div.fi-grid.lg:fi-grid-cols.fi-sc    the WIDGET grid
 *             div.fi-wi-widget                     Welcome
 *             div.fi-wi-widget.fi-wi-stats-overview Overview
 *               div.fi-grid.fi-sc                    the 7 stat tiles
 *             div.fi-wi-widget                     Pipeline by stage
 *             div.fi-section.fi-section-has-header  Needs attention
 *
 * Two things in that dump decide every rule below.
 *
 * ONE: the whole dashboard sits inside a single .fi-grid-col that Filament
 * inserts when the LAST lazy widget morphs in. It measured `span 3` of 6 — half
 * the screen unused, and invisible while the page is still loading. It cannot be
 * fixed in PHP: $columnSpan and getColumnSpan() are no-ops for custom Widget
 * classes, so setting a span there changes nothing while reading, to the next
 * person, as though the layout were configured.
 *
 * TWO: "Needs attention" is a .fi-section. It has NO .fi-wi-widget class, while
 * its three siblings do — the same widget loses that class between the skeleton
 * and the morphed DOM. So no rule may name a widget by class. Everything here
 * is structural, because structure is what survives the morph:
 *
 *   - a grid child that itself contains a grid is a WRAPPER  -> full row
 *   - a grid that has widget children is the WIDGET grid     -> 6 columns
 *   - a grid that has stat children is a STATS STRIP         -> auto-fit
 *
 * `:has(> .fi-wi-widget)` still identifies the widget grid even though one child
 * has lost the class, because the other three have not.
 *
 * SCOPE IS LOAD-BEARING. Every grid in Filament is .fi-sc.fi-grid — the page
 * grid, the widget grid, and the form grid on every resource page. An unscoped
 * version of these rules is exactly how the table screens once rendered at
 * 501px. Verified before writing: data-admin-dash is "1" on /admin and null on
 * /admin/invoices.
 * -------------------------------------------------------------------------- */

/* The widget grid: six columns, and a widget takes half a row unless something
   below says otherwise. Six because it divides by 2 and 3, so a half-row and a
   third-row are both expressible without a second breakpoint. */
html[data-admin-dash] .fi-sc.fi-grid:has(> .fi-wi-widget) {
  grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
}

html[data-admin-dash] .fi-sc.fi-grid:has(> .fi-wi-widget) > * {
  grid-column: span 3 !important;
}

/* A stats strip is a strip. Let it use the width the screen actually has rather
   than a column count someone picked while looking at a laptop. 12rem at the
   13.5px root is ~162px, which is the narrowest a label-plus-figure tile reads
   at without the caption wrapping. */
html[data-admin-dash] .fi-sc.fi-grid:has(> .fi-wi-stats-overview-stat) {
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)) !important;
}

html[data-admin-dash] .fi-sc.fi-grid:has(> .fi-wi-stats-overview-stat) > * {
  grid-column: auto !important;
}

/* LAST, and deliberately so: anything holding another grid is a container, not
   content — the morph wrapper and the stats overview both are. This has to
   outrank the half-row default above, so it is written after it and carries a
   heavier selector. */
html[data-admin-dash] .fi-sc.fi-grid > :has(.fi-sc.fi-grid),
html[data-admin-dash] .fi-sc.fi-grid:has(> .fi-wi-widget) > :has(.fi-sc.fi-grid) {
  grid-column: 1 / -1 !important;
}

/* A StatsOverview is wrapped in .fi-section-not-contained. Any blanket
   .fi-section background paints a stray plate behind tiles that already carry
   their own surface — two nested cards, one of them invisible until you notice
   the edge. */
.fi-section-not-contained {
  background-color: transparent;
  border: 0;
  box-shadow: var(--elevation-none);
}

/* -----------------------------------------------------------------------------
 * 13. THE NEEDS-ATTENTION LIST — twelve rows that have to be scannable.
 *
 * Measured on the live dashboard: the widget rendered a bare <ul> of <li>, so
 * badge, link and detail ran together into one wrapping paragraph per row with
 * nothing between rows. At four sources that was survivable. Sprint 32 took it
 * to seven and interleaved them, which is precisely when a wall of text stops
 * being readable — every row now opens with a different module's name, so the
 * eye needs a per-row anchor and a boundary.
 *
 * It cannot be done in the Blade with utility classes: the panel stylesheet is
 * purged, so anything Filament does not already emit compiles away silently.
 * The page layer is the right home for it, and this file's rule holds — every
 * value below is a token.
 * -------------------------------------------------------------------------- */
.gyro-attention {
  display: flex;
  flex-direction: column;
}

.gyro-attention__row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding-block: var(--space-2);
  /* The hairline is documented in tokens.css as the list separator, and is the
     density device this system uses in place of shadow. */
  border-top: 1px solid var(--color-border-hairline);
}

.gyro-attention__row:first-child {
  border-top: 0;
  padding-block-start: var(--space-0);
}

/* The source chip must NOT shrink.
 *
 * Measured in the owner's browser on the dashboard: the chip is a flex child at
 * the default `flex: 0 1 auto`, so the row's link took the space and squeezed
 * it to 40px - and because `.fi-badge-label` carries `overflow: hidden;
 * text-overflow: ellipsis`, "Support" rendered as "Supp...". A chip that
 * ellipsises its own one-word label is worse than no chip: the reader cannot
 * tell "Support" from "Supplier", which is the exact grouping this element
 * exists to do.
 *
 * Fixed at the container rather than by widening the badge, because the badge
 * component is shared and its width is correct everywhere it is allowed to keep
 * it. */
.gyro-attention__row > .fi-badge {
  flex-shrink: 0;
}

/* min-width:0 is load-bearing: without it a long subject refuses to wrap inside
   a flex child and pushes the row wider than the section. */
.gyro-attention__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.gyro-attention__detail {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  color: var(--color-text-muted);
}

/* -----------------------------------------------------------------------------
 * 14. THE APPEARANCE PAGE — picking a layout should look like picking a layout.
 *
 * A stack of bare radios with the option text beside them reads as a form
 * question; this is a choice between four shapes and each needs room for a
 * sentence explaining which product it comes from. Same constraint as section
 * 13: the panel stylesheet is purged, so the page cannot reach for utilities.
 * Every value below is a token.
 * -------------------------------------------------------------------------- */
.gyro-appearance {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border: 0;
  padding: var(--space-0);
  margin: var(--space-0);
}

.gyro-appearance__legend {
  /* The section already carries a heading; this exists for screen readers and
     must not paint a second visible title over it. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.gyro-appearance__option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--color-border-hairline);
  border-radius: var(--radius-control);
  cursor: pointer;
}

.gyro-appearance__option:hover {
  border-color: var(--color-border-strong);
}

/* :has, so the whole card reads as selected rather than just the dot. The accent
   is the one place primary belongs here - it marks the active choice, which is
   exactly one of its four permitted uses. */
.gyro-appearance__option:has(.gyro-appearance__radio:checked) {
  border-color: var(--color-accent-base);
}

.gyro-appearance__radio {
  margin-block-start: var(--space-1);
  flex-shrink: 0;
}

.gyro-appearance__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.gyro-appearance__label {
  font-size: var(--type-size-base);
  line-height: var(--type-line-base);
  font-weight: var(--type-weight-semibold);
  color: var(--color-text-primary);
}

.gyro-appearance__description {
  font-size: var(--type-size-small);
  line-height: var(--type-line-small);
  color: var(--color-text-muted);
}

.gyro-appearance__actions {
  margin-block-start: var(--space-4);
}
