/* ============================================================
   FLIGHTLINES design tokens (GENERATED DO NOT EDIT)
   Source of truth: tokens/src/tokens/*.json (W3C DTCG).
   Regenerate: bazel build //tokens:tokens_css
   ============================================================ */

:root, [data-theme="dark"] {
  --fl-bg-0: #000000;
  --fl-bg-1: #0a0d10;
  --fl-bg-2: #11161b;
  --fl-bg-3: #1a2127;
  --fl-bg-4: #232c34;
  --fl-tx-0: #f5f7fa;
  --fl-tx-1: #c7ced5;
  --fl-tx-2: #8590a0;
  --fl-tx-3: #4a5660;
  --fl-line-2: rgba(255,255,255,0.34);
  --fl-line-3: rgba(255,255,255,0.36);
  --fl-line: rgba(255,255,255,0.13);
  --fl-overlay: rgba(0,0,0,0.66);
  --fl-shadow-1: 0 1px 0 rgba(255,255,255,0.04) inset, 0 1px 2px rgba(0,0,0,0.4);
  --fl-shadow-2: 0 1px 0 rgba(255,255,255,0.04) inset, 0 8px 24px rgba(0,0,0,0.6), 0 2px 6px rgba(0,0,0,0.4);
  --fl-shadow-3: 0 1px 0 rgba(255,255,255,0.04) inset, 0 24px 60px rgba(0,0,0,0.7), 0 8px 16px rgba(0,0,0,0.5);
}

[data-theme="light"] {
  --fl-bg-0: #f5f6f8;
  --fl-bg-1: #ffffff;
  --fl-bg-2: #f9fafb;
  --fl-bg-3: #f0f2f5;
  --fl-bg-4: #e3e7ec;
  --fl-tx-0: #04070a;
  --fl-tx-1: #1f2933;
  --fl-tx-2: #5a6671;
  --fl-tx-3: #aab3bd;
  --fl-line-2: rgba(8,12,16,0.44);
  --fl-line-3: rgba(8,12,16,0.42);
  --fl-line: rgba(8,12,16,0.16);
  --fl-overlay: rgba(8,12,16,0.45);
  --fl-shadow-1: 0 1px 2px rgba(8,12,16,0.06), 0 0 0 1px rgba(8,12,16,0.04);
  --fl-shadow-2: 0 8px 24px rgba(8,12,16,0.10), 0 1px 2px rgba(8,12,16,0.06);
  --fl-shadow-3: 0 24px 60px rgba(8,12,16,0.14), 0 4px 12px rgba(8,12,16,0.08);
  --fl-chart-series-1: #a75d00;
  --fl-chart-series-2: #0059b5;
  --fl-chart-series-3: #3ca146;
  --fl-chart-series-4: #8a0c8b;
}

:root {
  --fl-hover: var(--fl-bg-3);
  --fl-hover-veil: color-mix(in oklch, var(--fl-tx-0) 6%, transparent);
  --fl-signal: oklch(78% 0.16 220);
  --fl-signal-strong: oklch(70% 0.17 220);
  --fl-signal-ink: oklch(20% 0.05 220);
  --fl-signal-bg: oklch(78% 0.16 220 / 0.10);
  --fl-signal-line: oklch(78% 0.16 220 / 0.50);
  --fl-amber: oklch(82% 0.16 75);
  --fl-amber-bg: oklch(82% 0.16 75 / 0.10);
  --fl-amber-line: oklch(82% 0.16 75 / 0.35);
  --fl-crimson: oklch(68% 0.20 25);
  --fl-crimson-strong: oklch(58% 0.20 25);
  --fl-crimson-ink: oklch(100% 0 0);
  --fl-crimson-bg: oklch(68% 0.20 25 / 0.10);
  --fl-crimson-line: oklch(68% 0.20 25 / 0.35);
  --fl-mint: oklch(78% 0.14 162);
  --fl-mint-bg: oklch(78% 0.14 162 / 0.10);
  --fl-mint-line: oklch(78% 0.14 162 / 0.35);
  --fl-proposal: oklch(72% 0.16 285); /** RFC-0080: the ONE machine-proposal accent, a twilight indigo. Worn adjacent to a derived value's digits (never in their opacity) to mark quietly-done homework the pilot ratifies. Deliberately its own hue, NOT amber (75, the one required/warning semantic: a proposal is its opposite) and NOT signal cyan (220, navigation only). */
  --fl-proposal-bg: oklch(72% 0.16 285 / 0.10);
  --fl-proposal-line: oklch(72% 0.16 285 / 0.35);
  --fl-chart-blue: oklch(70% 0.15 252);
  --fl-chart-blue-bg: oklch(70% 0.15 252 / 0.10);
  --fl-chart-blue-line: oklch(70% 0.15 252 / 0.35);
  --fl-chart-water-open: oklch(72% 0.13 245 / 0.50);
  --fl-chart-water-inland: oklch(64% 0.14 250 / 0.50);
  --fl-chart-magenta: oklch(68% 0.16 350);
  --fl-chart-magenta-bg: oklch(68% 0.16 350 / 0.10);
  --fl-chart-magenta-line: oklch(68% 0.16 350 / 0.35);
  --fl-chart-canvas: var(--fl-bg-1);
  --fl-chart-grid: var(--fl-line-2);
  --fl-chart-grid-minor: var(--fl-line);
  --fl-chart-ink: var(--fl-tx-0);
  --fl-chart-line: var(--fl-line);
  --fl-chart-marker-bg: var(--fl-bg-1);
  --fl-chart-hud-bg: var(--fl-bg-2);
  --fl-chart-amber: var(--fl-amber);
  --fl-chart-series-1: var(--fl-amber);
  --fl-chart-series-2: oklch(58% 0.16 245);
  --fl-chart-series-3: oklch(66% 0.16 150);
  --fl-chart-series-4: oklch(52% 0.20 330);
  --fl-chart-obstruction: var(--fl-tx-0);
  --fl-chart-contour: oklch(64% 0.1 58);
  --fl-chart-tfr: oklch(62% 0.21 20); /** RFC-0071 FD-H: the TFR deep red, one of the chart's TWO new meaning-hues (the palette is already talking; the hatch, not the hue, is TFR's identity mark). Distinct from --fl-radar-50 and --fl-wx-ifr. */
  --fl-adv: oklch(68% 0.13 300); /** RFC-0071 FD-H: ONE violet for every weather advisory (SIGMET/AIRMET/G-AIRMET), the one hue family the chart has never claimed severity separates by weight (fill alpha + stroke width), never by a second hue. */
  --fl-wx-vfr: oklch(70% 0.19 150);
  --fl-wx-mvfr: oklch(66% 0.17 255);
  --fl-wx-ifr: oklch(62% 0.23 27);
  --fl-wx-lifr: oklch(60% 0.25 330);
  --fl-radar-5: oklch(72% 0.11 195);
  --fl-radar-20: oklch(70% 0.19 145);
  --fl-radar-35: oklch(80% 0.16 100);
  --fl-radar-50: oklch(62% 0.23 27);
  --fl-radar-65: oklch(60% 0.25 330);
  --fl-relief-below-sea: oklch(38% 0.05 245);
  --fl-relief-0-1k: oklch(34% 0.04 150);
  --fl-relief-1-2k: oklch(40% 0.05 140);
  --fl-relief-2-3k: oklch(45% 0.05 120);
  --fl-relief-3-5k: oklch(48% 0.06 95);
  --fl-relief-5-7k: oklch(50% 0.07 70);
  --fl-relief-7-9k: oklch(50% 0.08 55);
  --fl-relief-9-12k: oklch(48% 0.08 45);
  --fl-relief-12k: oklch(82% 0.01 90);
  --fl-font-sans: "Geist", "Geist Fallback", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --fl-font-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, "SF Mono", Menlo, monospace;
  --fl-font-brand: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --fl-text-body: 12px;
  --fl-text-title: 22px;
  --fl-text-display: 32px;
  --fl-text-badge: 10px;
  --fl-s-0: 0;
  --fl-s-1: 2px;
  --fl-s-2: 4px;
  --fl-s-3: 8px;
  --fl-s-4: 12px;
  --fl-s-5: 16px;
  --fl-s-6: 24px;
  --fl-s-7: 40px;
  --fl-r-0: 0;
  --fl-r-sm: 3px;
  --fl-r-full: 999px;
  --fl-h-sm: 24px;
  --fl-h-md: 30px;
  --fl-h-lg: 36px;
  --fl-toggle-sm: 14px;
  --fl-toggle-md: 16px;
  --fl-line-tight: 1.4;
  --fl-line-base: 1.5;
  --fl-line-loose: 1.6;
  --fl-ease: cubic-bezier(.2,.7,.2,1);
  --fl-dur-1: 90ms;
  --fl-dur-2: 160ms;
  --fl-dur-3: 240ms;
  --fl-skeleton-delay: 200ms;
  --fl-z-surface: 100;
  --fl-z-modal: 110;
  --fl-z-floating: 120;
  --fl-z-tooltip: 130;
  --fl-z-toast: 140;
  --fl-focus-ring: 0 0 0 1px var(--fl-bg-0), 0 0 0 3px var(--fl-signal-line);
}

/* ============================================================
   FLIGHTLINES base + component layer (HAND-AUTHORED)
   The framework-agnostic CSS that CONSUMES the generated tokens: the reset,
   the typography role classes (the CSS half of the React role primitives),
   keyframes, z-layer classes, and component primitives (.fl-btn, .fl-control,
   …). This is NOT token-generated it is the design system's stylesheet, and
   it ships in the same sheet as the tokens (sd.build.mjs appends it after the
   generated `:root`/theme blocks). Token VALUES live in tokens/*.json.
   ============================================================ */

/* CONTROL PLANE the height every control on a surface takes, and its matching
   inset. Inherited, and switched by a data attribute on a surface root, exactly as
   `data-theme` switches the palette: density is a property of the SURFACE, not of
   each control, and the platform already has a mechanism for a value a subtree
   shares. A control names no size and simply inherits; an explicit `size` on a
   control emits `data-size` and wins by specificity (see .fl-btn / .fl-control).

   `md` (30) is the DEFAULT (founder ruling 2026-08-11, inverting 2026-07-30's
   sm default): a field a pilot types into wants the room, and it MUST be taller
   than the affordances it hosts (a combobox's disclosure chevron and a date
   field's calendar button live INSIDE the control), which is why one universal
   control height is impossible. The inversion is a safety property, learned by
   shipping the bug the old default made possible: the import review surface
   hosted the SAME regulated entry form as /logbook/new and simply forgot its
   declaration, so a §61.51 form rendered cramped for weeks. Under md-default, a
   forgotten declaration yields a slightly-roomy read surface (visible, benign);
   it can never again yield a cramped legal form.

   DENSE READ surfaces (lists, detail panes, read-backs, chrome bands the app's
   instrument idiom, built around 24) OPT IN with `data-control-plane="sm"` on
   the surface root: density is deliberate, never a fallback. 24 is the WCAG 2.2
   SC 2.5.8 AA target minimum nothing goes below it. */
:root {
  --fl-control-h: var(--fl-h-md);
  --fl-control-px: var(--fl-s-4);
  /* A FIELD'S TEXT INSET IS NOT THE PLANE'S, and it is deliberately absent from the three
     plane blocks below. `--fl-control-px` is a BUTTON's shoulder: the air either side of a
     centred label, which is what makes a taller button wider. A text field's inset is a
     different measurement, the place its first glyph and its caret sit relative to the
     border, and that is a property of the TEXT, which is ONE size here (the 2026-07-13
     reading plane). Keying it to the plane made the same 12px text start 8, 12 or 16px in
     depending only on how tall its box was, and on the focus plane (login, onboarding,
     compose) it read as a field with its contents shoved inward: 16px on 12px text is
     1.33em, where Bootstrap ships 0.75em and Primer 0.86em on larger text than ours.

     THE VALUE is 8px (`--fl-s-3`, 0.67em on our 12px text). Taken at 12px, those two ratios
     come to 9 and 10px, which sit between our 8 and 12 steps, and the small controls both
     systems ship for text this size settle it: Primer's small text input (12px text) and
     Bootstrap's `form-control-sm` both inset 8px. The split first set it to 12px, a full
     em, wider than either ratio, which read as a box padded wider than what it holds
     (founder, 2026-09-15). One inset, every plane, one scale step. Buttons keep the plane's
     shoulder. */
  --fl-field-px: var(--fl-s-3);
  /* THE CELL LATTICE'S ONE MEASURE the narrowest a labelled form cell may be before
     the lattice drops a track. The entry form's groups are laid on
     `repeat(auto-fill, minmax(var(--fl-cell-min), 1fr))`, so the surface decides HOW MANY
     columns and this decides HOW WIDE, which is the only way round that holds.

     It replaced a hard-coded `cols={5}` on twenty grids (2026-09-12). A track COUNT makes
     a cell's width a function of the container: the same §61.51 time cell holding "1.5"
     was 210px in the import review pane and 368px on /logbook/new, and at 1440 the same
     five tracks squeezed to 157px and wrapped "Night SIC under supervision" onto two lines,
     breaking the row's baseline band. Rams #10 (as little design as possible) and the
     house rule that size is not a hierarchy tool: a wider box is louder and says no more.
     The settings name editor caught the same class one surface earlier ("a 6-character
     first name was being given a 660px input").

     13rem, in rem and not ch, because this sits on the GRID node and the ch-reservation
     law (the `ch-width-sits-on-its-text` lint (eslint.config.js)) reserves in ch only on the node that renders
     the text. At gap `--fl-s-5` it yields 1 track at 390, 3 at 840, 5 at 1117 (the import
     detail pane, unchanged by the move) and 8 at 1840. */
  --fl-cell-min: 13rem;
}
[data-control-plane='sm'] {
  --fl-control-h: var(--fl-h-sm);
  --fl-control-px: var(--fl-s-3);
}
[data-control-plane='md'] {
  --fl-control-h: var(--fl-h-md);
  --fl-control-px: var(--fl-s-4);
}
[data-control-plane='lg'] {
  --fl-control-h: var(--fl-h-lg);
  --fl-control-px: var(--fl-s-5);
}

/* color-scheme is a real CSS property (UA canvas/scrollbars/form controls track
   the theme), not a custom property so it cannot be emitted by the token
   generator and is declared here against the same selectors. */
:root,
[data-theme='dark'] {
  color-scheme: dark;
}
[data-theme='light'] {
  color-scheme: light;
}

/* Base ——————————————————————————————————————————————*/
*,
*::before,
*::after {
  box-sizing: border-box;
}
html,
body {
  height: 100%;
}
body {
  margin: 0;
  background: var(--fl-bg-0);
  color: var(--fl-tx-1);
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: 'ss01', 'ss02', 'cv11';
  transition:
    background-color var(--fl-dur-3) var(--fl-ease),
    color var(--fl-dur-3) var(--fl-ease);
}

::selection {
  background: var(--fl-signal-line);
  color: var(--fl-tx-0);
}

/* Scrollbars the STANDARD CSS Scrollbars Module Level 1 properties, NOT the legacy
   non-standard `::-webkit-scrollbar` pseudo-elements. Giving `::-webkit-scrollbar` a
   width FORCES macOS to replace its native auto-hiding OVERLAY scrollbar with a classic,
   always-present, space-reserving one (a documented Blink behavior). That permanent
   scrollbar insets every scroller's content box by ~10px, which is why a selected ledger
   row's full-bleed fill stopped short of the pane's right edge the "strip". The
   standard `scrollbar-color`/`scrollbar-width` leave macOS overlay intact (the OS draws
   and auto-hides a thumb OVER the content, reserving no space the fill reaches the
   edge), while still theming the classic scrollbar shown on Windows/Linux and on a Mac
   set to "always show scroll bars". Inherited, so `.fl-scrollband` (scrollbar-width:none)
   still overrides locally. Chrome 121+/Firefox/Safari 18.2+; older WebKit falls back to
   the default scrollbar acceptable. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--fl-bg-3) transparent;
}

:focus {
  outline: none;
}
:focus-visible {
  outline: none;
  box-shadow: var(--fl-focus-ring);
}

/* `.fl-tick` (a mono, tracked, UPPERCASE tick label) was deleted on 2026-08-01 with the rest of the
   dropped-uppercase sweep. It was the retired `Eyebrow` treatment under another name, it had ZERO
   call sites in the whole repo, and an unused rule is the cheapest possible place for a dropped
   treatment to wait for its next author. An axis tick is `<Meta mono>`; an SVG `<text>` node that
   cannot take a prop uses `.fl-mono` for the family and nothing else. */

/* Typography roles ——————————————————————————————————
   Each class is the FULL visual spec for one role: size + weight + family +
   tracking + leading + default color. The React role primitives are thin
   wrappers that apply the matching class. There is intentionally no abstract
   size scale every text element commits to a role, and this stylesheet is
   the single source of truth for what each role looks like.

   TWO AXES, AND A ROLE ENCODES ONLY THE FIRST (founder ruling, 2026-08-01).
   Every piece of text answers two independent questions: WHAT JOB it does
   (heading / label / value / prose / meta), and WHETHER IT IS A MACHINE TOKEN
   (sans vs mono). The role is the job. Mono is `mono` on the React primitive,
   which is a per-instance family swap and nothing else. The five reading-plane
   roles below therefore all declare `--fl-font-sans`: none of them bakes the
   second axis in.

   MONO MARKS THE VALUE, NEVER THE THING THAT NAMES IT. Monospace does exactly
   two jobs, aligning digits down a column and signalling a literal token from a
   system, and both are properties of DATA. A label, a heading or a section name
   is prose and takes none of it.

   UPPERCASE IS DROPPED from the role system (same ruling). Uppercase is not a
   job, so it cannot be a role. The plane already separates by weight and colour,
   caps cost legibility on a dense instrument, and it is the most dated signal in
   the set. `.fl-caps` below is the deliberate, currently-unused opt-in and the
   one place the ruling is reversed if it is ever reversed.

   The legacy classes (.fl-subtitle, .fl-caption, .fl-code, .fl-eyebrow) are
   GONE, deleted with their React primitives the day the last call site moved.
   That deletion is the closure: the old spellings are unwritable now rather than
   merely discouraged, which is why the migration was not finished until they
   went. `.fl-mono` below is what the SVG instruments that set a class by hand
   use for the family axis, since a role class carries only the job.

   Controls that wrap a native element (button, input) consume the role tokens
   directly (e.g. `font-size: var(--fl-text-body)`) rather than the class, so
   their other state styling (focus, hover, disabled) stays self-contained. */
.fl-display,
.fl-title,
.fl-heading,
.fl-label,
.fl-value,
.fl-body,
.fl-meta {
  margin: 0;
  /* A RUN OF TEXT CAN ALWAYS BREAK, so it can never force its box wider than the screen.
     It is stated on the ROLE, here, rather than in the React primitive, because the class is
     the other public door onto a role: the SVG instruments apply it by hand and would have
     been outside a guarantee that lived in the .tsx only.

     `anywhere` rather than `break-word`, and the difference is the whole fix: only `anywhere`
     is taken into account when the browser computes MIN-CONTENT, and min-content is what sizes
     a flex or grid track. Under `break-word` an unbreakable run (an address, a certificate
     number, an ident) still reports its full width as its minimum, the track refuses to shrink
     below it, and the row pushes past the surface's rail; the app shell is `overflow: hidden`,
     so there is not even a scrollbar and the text is simply cut at the screen edge. On /me at
     400px one signed-in address widened its row by ~150px and read as four unrelated bugs
     (founder, 2026-09-11). Guarded end-to-end by web/e2e/no-sideways-scroll.spec.ts.

     It only ever breaks a word with no room to fit whole, so prose and a tail number in a
     wide-enough cell are untouched. A run that should be CUT rather than wrapped says so with
     `truncate`, which sets `white-space: nowrap` inline and wins over this. */
  overflow-wrap: anywhere;
}
/* ...EXCEPT IN A TABLE CELL. An auto-layout table sizes each column to its cells' min-content, so
   under `anywhere` a cell never has room by construction: the column shrinks to its narrowest
   figure and every head breaks inside its word. The IACRA grid read "Tota|l", "Instrum|ent" and
   "Flight|s" at 1280px and one letter per line on a phone (2026-09-16). A table owns its own x
   scroller (both in the tree do: `Table` and the 8710 grid), so its columns take whole words and
   pan instead; `break-word` still breaks a run with no room at all, without lowering the column's
   floor to a single glyph. */
:is(th, td) :is(.fl-display, .fl-title, .fl-heading, .fl-label, .fl-value, .fl-body, .fl-meta) {
  overflow-wrap: break-word;
}
.fl-display {
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-display);
  font-weight: 600;
  color: var(--fl-tx-0);
  letter-spacing: -0.015em;
  line-height: 1.12;
}
.fl-title {
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-title);
  font-weight: 600;
  color: var(--fl-tx-0);
  letter-spacing: -0.015em;
  line-height: 1.15;
}

/* The five reading-plane roles ————————————————————————
   ONE size (`--fl-text-body`, the plane), five JOBS, told apart by weight and
   colour alone. Ranked loudest to quietest: Value (600 / tx-0) and Heading
   (600 / tx-0) sit on top, Label (500 / tx-1) names things, Body (400 / tx-1)
   reads as prose, Meta (400 / tx-2) supports. */

/* HEADING names a group. A card head, a section name, a dialog title. Prose,
   so it is sans and there is no prop to make it otherwise (`SectionHeader` owns
   the band that draws most of them). Weight and ink inherited verbatim from the
   heading half of the retired `.fl-subtitle`, so the restructure changes the
   semantics without moving a pixel. */
.fl-heading {
  font-family: var(--fl-font-sans);
  /* Shares the ONE reading-plane size with every other role here: a card heading
     or a readout value is NOT bigger than the data, it is HEAVIER. Weight 600
     carries the heading/value read now that size no longer does (founder ruling,
     2026-07-13). */
  font-size: var(--fl-text-body);
  font-weight: 600;
  color: var(--fl-tx-0);
  letter-spacing: -0.01em;
  line-height: 1.2;
}

/* VALUE the datum itself, and the loudest thing on a reading row: the figure a
   pilot acts on. Identical in weight and ink to `.fl-heading` because they were
   ONE class until 2026-08-01 (`.fl-subtitle`, doing both jobs), and splitting
   them is a semantic split, not a visual one. They are separate classes so a
   future divergence has a home: tabular figures are a property of VALUES, not of
   headings, and only one of these two could ever grow `font-variant-numeric`.

   It is sans HERE, in the stylesheet. Most values are mono, but that is the
   OTHER axis and it belongs on the instance: a person's name and a remark are
   values and are prose. */
.fl-value {
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-body);
  font-weight: 600;
  color: var(--fl-tx-0);
  letter-spacing: -0.01em;
  line-height: 1.2;
}

/* BODY prose. Unchanged. */
.fl-body {
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-body);
  font-weight: 400;
  color: var(--fl-tx-1);
  line-height: 1.5;
}

/* LABEL names a value. The form-control label above an input, the head of an
   editable ledger column, the left half of a readout pair. It NAMES data, so it
   is prose and never mono. Unchanged from the pre-restructure `.fl-label`. */
.fl-label {
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-body);
  font-weight: 500;
  color: var(--fl-tx-1);
  line-height: 1.35;
}

/* META the supporting register: a hint, an error, an as-of stamp, an axis tick,
   a unit, a qualifier beside a figure. Dimmer than body, same weight. Inherited
   verbatim from `.fl-caption`. */
.fl-meta {
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-body);
  font-weight: 400;
  color: var(--fl-tx-2);
  line-height: 1.4;
}

/* THE SECOND AXIS, IN CSS ——————————————————————————————
   The CSS twin of the `mono` prop, and nothing else: it swaps the family and
   touches no other property, exactly as `mono` does in `_Text`. It exists for the
   consumers React cannot reach, an SVG `<text>` node inside a hand-drawn
   instrument (RunwayWind's threshold markings, SkyColumn's ticks) which sets its
   class by hand and has no primitive to take a prop.

   It is NOT an escape hatch for React call sites: those pass `mono` on the role,
   which is one decision in one place. A `className="fl-mono"` in a .tsx file
   alongside a role primitive is a bug, the prop is right there. */
.fl-mono {
  font-family: var(--fl-font-mono);
}

/* THE UPPERCASE OPT-IN, and the single point of reversal ————————
   Uppercase was dropped from the role system on 2026-08-01 (founder's call; see
   the header above). This class is the ONLY place it survives, it is applied by
   exactly one prop (`<Label caps>`), and that prop is used NOWHERE.

   To reverse the ruling, flip `caps` to default `true` in
   ui/src/primitives/Label.tsx one line, and every label wears caps
   again. Do not scatter `text-transform` back into the role classes. */
.fl-caps {
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Identity line: the ledger's identity cell, truncated the ordinary way.
   The one line a ledger row draws its identity on: a lead ident (a tail or a
   flight designator), the route arc, and one dim adjunct.

   It is a PLAIN SINGLE-LINE TEXT BLOCK, so the browser's own `text-overflow`
   does the truncating: one ellipsis, at the end of the line, exactly where the
   room runs out, which is what every other truncating cell in the app does and
   what a reader expects. It was a flex row before, and that is the whole story
   of the bug: `text-overflow` does not apply to a flex container, so the cell
   truncated nothing. Each child was left to fend for itself, and only the last
   one (the adjunct, the sole child that shrank) ever showed an ellipsis, while
   the lead and the arc were amputated mid-glyph by the overflow clip with no
   ellipsis at all (founder report, 2026-07-30).

   `min-width: 0` so the cell may actually shrink inside its grid track; without
   it the automatic minimum floors at the content and the line never truncates
   at all, it just pushes the row wide. The children are inline, so the gap is a
   margin rather than `column-gap` (which is a flex/grid property and would have
   quietly done nothing here). */
.fl-identity-line {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.fl-identity-line > * + * {
  margin-inline-start: var(--fl-s-2);
}

/* Inline glyph an SVG that behaves like a TEXT glyph, not an icon ————
   For an SVG mark (e.g. <AircraftGlyph inline>) that lives in a run of text and
   should read as a typographic glyph rather than a fixed-px icon. It is sized to
   the cap-height of its INHERITED font (the `cap` unit), so it matches the capital
   letters of whatever role it sits in automatically, with no per-call-site size
   and baseline-aligned so it occupies the same band as those caps. Color rides
   `currentColor` on the <svg>. This is the deliberate counterpart to the fixed-px,
   flex-centered icon (ChevronIcon/CloseIcon): those are standalone affordances; a
   glyph belongs to its text. Place it INSIDE the companion text's run so it inherits
   the right size (a mark that belongs to a `Meta` must sit INSIDE it, not beside it).
   The @supports fallback approximates cap-height for engines without the `cap` unit. */
.fl-glyph-inline {
  display: inline-block;
  vertical-align: baseline;
  flex: none;
  width: 0.72em;
  height: 0.72em;
}
@supports (height: 1cap) {
  .fl-glyph-inline {
    width: 1cap;
    height: 1cap;
  }
}

/* Brand wordmark the ONE lockup, as a shared CSS contract ————————
   The mark glyph (✈︎) + the brand name on the reading plane in the brand
   typeface. BOTH web surfaces apply this class the React <Wordmark> and the
   static marketing site so the lockup can never drift; iOS renders the same
   spec from the generated Swift tokens. The element takes role="img" + an
   accessible name, so the ::before glyph and the lowercase wordmark text are
   decorative and it announces "Flightlines" once. Color is inherited
   (currentColor) so each surface sets its own. */
.fl-wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--fl-s-3);
  font-family: var(--fl-font-brand);
  /* The reading plane's own size. It read `--fl-text-subtitle` until 2026-08-01,
     an identical 12px, but that token belongs to a role now deprecated and the
     lockup should not inherit a role's fate. */
  font-size: var(--fl-text-body);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1;
  color: currentColor;
  white-space: nowrap;
  user-select: none;
}
.fl-wordmark::before {
  content: '\2708\FE0E'; /* ✈︎ = U+2708 airplane + U+FE0E text-presentation selector */
  font-family: 'Segoe UI Symbol', 'Arial Unicode MS', sans-serif;
  font-size: 1em;
  line-height: 1;
}

/* Keyframes ——————————————————————————————————————*/
@keyframes fl-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes fl-pop-in {
  from {
    opacity: 0;
    transform: translateY(4px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* The modal entrance, and it animates the ENTRANCE ONLY: how the panel comes in,
   never where it sits. It uses the independent `scale` property rather than a
   `transform`, so a panel's own positioning (whatever it is) is untouched.

   This used to bake `translate(-50%, -50%)` into both keyframes, to stop a plain
   `transform: scale()` from overwriting the centering transform the centered panel
   positions itself with. That worked for the ONE geometry it was written against and
   was wrong for every other: a keyframe that states a position imposes it. The
   §61.3(l) presentation positions with `inset: 0` and no transform, so this keyframe
   translated it half a viewport left and up for the length of the animation, then
   dropped it back when the animation ended and the transform reverted. The founder saw
   what that is: "i click the card and i see a popup way off to the left then it goes to
   the center of the screen".

   `translate` and `scale` are separate properties, applied in that order before any
   `transform`, so each layer can own its own concern: geometry owns position, this owns
   the entrance, and neither can overwrite the other. `web/layout/modalEntrance.spec.ts`
   measures that for every panel geometry, at the animation's first frame and its last. */
@keyframes fl-modal-in {
  from {
    opacity: 0;
    scale: 0.985;
  }
  to {
    opacity: 1;
    scale: 1;
  }
}
@keyframes fl-spin {
  to {
    transform: rotate(360deg);
  }
}
/* The shimmer sweeps toward the inline end, so its x carries the axis's sign. */
@keyframes fl-shimmer {
  0% {
    background-position: calc(-200% * var(--fl-inline-sign)) 0;
  }
  100% {
    background-position: calc(200% * var(--fl-inline-sign)) 0;
  }
}
@keyframes fl-skeleton-reveal {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes fl-blink {
  0%,
  60% {
    opacity: 1;
  }
  60.01%,
  100% {
    opacity: 0.2;
  }
}
/* THE INLINE AXIS. Every layout is written on the logical inline axis (inline start and end, never
   left and right; stylelint and the `inline-axis-is-logical` lint hold it), so a right-to-left
   locale mirrors with `dir="rtl"` and nothing else (RFC-0173 §2.4).

   A DRAWING'S PLANE is the one thing that must not mirror: a rect measured on a scanned page, a
   marker at a projected screen point, a column boundary over a picture. Its x is a measurement, so
   it is laid out inside `.fl-plane` (ui `Plane`), which pins the direction to left-to-right; there
   the same logical keys ARE the physical sides, and geometry is written exactly like layout. This
   is the only place a direction is pinned, and the consumers' stylesheets and style objects may
   not pin one of their own.

   `--fl-inline-sign` is the axis's sign, for what CSS has no logical form of (a `translateX`): +1
   where the inline start is the left, -1 where it is the right. */
:root,
[dir='ltr'],
.fl-plane {
  --fl-inline-sign: 1;
}
[dir='rtl'] {
  --fl-inline-sign: -1;
}
.fl-plane {
  direction: ltr;
}

/* A drawer slides in from its own edge, the inline END or START, so it is named by the logical
   side and slides along the inline axis by its sign (`--fl-inline-sign`, below). */
@keyframes fl-drawer-end {
  from {
    transform: translateX(calc(100% * var(--fl-inline-sign)));
  }
  to {
    transform: translateX(0);
  }
}
@keyframes fl-drawer-start {
  from {
    transform: translateX(calc(-100% * var(--fl-inline-sign)));
  }
  to {
    transform: translateX(0);
  }
}
@keyframes fl-indet {
  0% {
    inset-inline-start: -40%;
  }
  100% {
    inset-inline-start: 100%;
  }
}

/* Loading-skeleton reveal Put .fl-skeleton-reveal on the ROOT of a loading
   placeholder (a lone Skeleton or crucially a composite skeleton that interleaves
   real chrome like a ledger header + row hairlines with its shimmer bars). The whole
   unit stays invisible until --fl-skeleton-delay, then reveals together: a load that
   resolves faster than the delay never paints anything (kills the flash-then-content
   flicker); a slow load reveals the placeholder whole never the chrome first and the
   bars 200ms later (a per-bar delay would split the reveal and flash a barren ruled
   frame). FAILS SAFE: the hidden window lives in the keyframe (`from`), held during the
   delay by `backwards` fill there is NO resting opacity:0 so if the animation is ever
   disabled (a prefers-reduced-motion reset, a missing token/keyframe) the element rests at
   its natural opacity:1, VISIBLE, never stranded blank. Purely visual: role=status /
   aria-busy stay live the whole time, so assistive tech hears "loading" immediately. */
.fl-skeleton-reveal {
  animation: fl-skeleton-reveal var(--fl-dur-1) var(--fl-ease) var(--fl-skeleton-delay) both;
}

/* The hub grid: a multi-section record page's tiles (page-width doctrine,
   2026-07-07). Explicit column counts per breakpoint, never auto-fit (empty
   tracks stretch the survivors into letterboxing). */
.fl-hub-grid {
  display: grid;
  gap: var(--fl-s-6);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 768px) {
  .fl-hub-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1400px) {
  .fl-hub-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Stacking layer classes the ONE way a fixed/portaled element takes
   its z slot (see the --fl-z-* scale; csstype forbids string z-index, so
   the class IS the API no inline literals, no casts). */
.fl-layer-surface {
  z-index: var(--fl-z-surface);
}
.fl-layer-modal {
  z-index: var(--fl-z-modal);
}
.fl-layer-floating {
  z-index: var(--fl-z-floating);
  /* A floating popup (Combobox listbox, DatePicker calendar) portals to <body>. When it is
     opened from INSIDE a modal Dialog, Radix marks everything outside the dialog inert with
     pointer-events: none on <body>, which the portaled popup inherits: every option click fell
     through to the overlay and the picker was mouse-dead inside any sheet. The popup is part
     of the modal interaction, so it re-enables its own pointer events. */
  pointer-events: auto;
}
.fl-layer-tooltip {
  z-index: var(--fl-z-tooltip);
}
.fl-layer-toast {
  z-index: var(--fl-z-toast);
}

/* Component primitives (interaction states live here, not in JS) */

.fl-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--fl-s-3);
  font-family: var(--fl-font-sans);
  /* THE READING PLANE, stated HERE with the rest of the type spec (founder catch 2026-08-14:
     "these buttons all have diff text sizes"). A <button> does NOT inherit the document's font
     size the UA gives it its own (13.333px in Blink) so a class that sets family and weight
     and stops leaves the SIZE to the browser. It was declared only on the `md` and `lg` size
     variants, which is why it looked right wherever it was checked: those are the sizes a call
     site passes explicitly, while the DEFAULT path (no `size` at all, the documented common
     case since the 2026-08-11 plane inversion) and every `sm` button rendered at the UA's
     13.333px. A label wrapped in a role primitive got 12px from the role, a bare label got
     13.333px from the browser, and the same chip strip carried both. Size is not a per-variant
     property: a taller control is taller, never louder (the CONTROL_FONT_SIZE law, stated once
     for inputs in `_controlSurface.ts` and now once here). */
  font-size: var(--fl-text-body);
  font-weight: 500;
  letter-spacing: -0.005em;
  border-radius: var(--fl-r-sm);
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background var(--fl-dur-2) var(--fl-ease),
    border-color var(--fl-dur-2) var(--fl-ease),
    color var(--fl-dur-2) var(--fl-ease),
    filter var(--fl-dur-1) var(--fl-ease);
}
.fl-btn:disabled,
.fl-btn[data-inactive='true'] {
  cursor: not-allowed;
  opacity: 0.5;
}
/* A MARK-faced icon button's face is DATA (the north needle IS the frame declaration), so
   disabled drops the affordance and never the ink the .fl-row-btn:disabled ruling, one
   geometry over: two disabled treatments, on purpose, keyed by what the face carries. */
.fl-btn[data-face='mark']:disabled {
  cursor: default;
  opacity: 1;
}
.fl-btn:not(:disabled):active {
  filter: brightness(0.92);
}

/* No `data-size` INHERIT the surface's plane. This is the correct default: a
   control is as tall as everything else on its surface without being told. */
.fl-btn {
  height: var(--fl-control-h);
  padding-inline: var(--fl-control-px);
}
/* An explicit size opts OUT of the plane, and wins by specificity. Used by the
   library's own in-field affordances and dialog chrome (pinned `sm`, so they stay
   one step under their host) and by focus surfaces (`lg`). */
.fl-btn[data-size='sm'] {
  height: var(--fl-h-sm);
  padding-inline: var(--fl-s-3);
}
/* The size variants carry GEOMETRY only. Their `font-size` declarations are deleted, not moved:
   they restated the plane the base rule now owns, and a size that also sets type is the shape
   that hid the missing base declaration in the first place. */
.fl-btn[data-size='md'] {
  height: var(--fl-h-md);
  padding-inline: var(--fl-s-4);
}
.fl-btn[data-size='lg'] {
  height: var(--fl-h-lg);
  padding-inline: var(--fl-s-5);
}

.fl-btn[data-fullwidth='true'] {
  width: 100%;
}

.fl-btn[data-variant='primary'] {
  background: var(--fl-tx-0);
  color: var(--fl-bg-0);
  border-color: var(--fl-tx-0);
}
.fl-btn[data-variant='secondary'] {
  background: transparent;
  color: var(--fl-tx-0);
  border-color: var(--fl-line-3);
}
.fl-btn[data-variant='ghost'] {
  background: transparent;
  color: var(--fl-tx-1);
}
.fl-btn[data-variant='danger'] {
  background: var(--fl-crimson-bg);
  color: var(--fl-crimson);
  border-color: var(--fl-crimson-line);
}

.fl-btn[data-variant='secondary']:not(:disabled):hover {
  background: var(--fl-hover);
}
/* A PRESSED toggle-offer (aria-pressed) wears the segmented control's selected face: the chip
   that opened a section reads as ON, and pressing it again is the close (offered only while the
   section is blank — the entry form's "Also record" row is the first consumer). Styling the ARIA
   state itself keeps the visual and the announcement one fact. */
.fl-btn[aria-pressed='true'] {
  background: var(--fl-bg-1);
  border-color: var(--fl-line-2);
  color: var(--fl-tx-0);
}
.fl-btn[data-variant='ghost']:not(:disabled):hover {
  background: var(--fl-hover);
}
.fl-btn[data-variant='danger']:not(:disabled):hover {
  background: color-mix(in oklch, var(--fl-crimson) 18%, var(--fl-bg-1));
  border-color: var(--fl-crimson);
}

/* An icon-only control is SQUARE: no inset, and its width tracks its height. It must inherit the
   plane here too the per-size rules below only match when a call site NAMED a size, so relying on
   them for width left every unsized icon button with no width at all, collapsing it onto its glyph
   (the pager chevrons went skinny exactly this way). */
.fl-btn[data-icon-only='true'] {
  padding: 0;
  flex-shrink: 0;
  width: var(--fl-control-h);
}
/* Explicit sizes opt out of the plane, same as the labelled rules above: the library's own in-field
   affordances and dialog chrome pin `sm` so they stay one step under their host. */
.fl-btn[data-icon-only='true'][data-size='sm'] {
  width: var(--fl-h-sm);
  height: var(--fl-h-sm);
}
.fl-btn[data-icon-only='true'][data-size='md'] {
  width: var(--fl-h-md);
}
.fl-btn[data-icon-only='true'][data-size='lg'] {
  width: var(--fl-h-lg);
}

/* Row button the ROW-SHAPED press target, and the third button geometry the
   library owns (labelled .fl-btn, square .fl-btn[data-icon-only], and this). A
   full-bleed line of content that IS the control: no box, no inset, no centred
   label, so a list of them reads as rows on a page rather than as buttons dropped
   into a list. .fl-btn cannot be bent into this shape it centres its children,
   pins a height, sets `white-space: nowrap`, and insets by the plane's
   --fl-control-px, all of which are correct for a labelled action and wrong for a
   row.

   It exists because the shape was being HAND-ROLLED instead: an inline
   `background: none; border: none; font: inherit; text-align: start; width: 100%`
   reset re-typed per call site (the read-back strip, the Accordion header), which
   is the same defect the "never hand-roll .fl-btn, use the primitive" rule already
   names. A hand-roll also has no disabled state to inherit, which is how the
   read-back strip came to LOCK by degrading its <button> to a <div> the founder
   ruling of 2026-08-01 forbids. Being a native <button> is the whole point:
   Enter/Space, the focus ring, and `disabled` come from the platform.

   It carries no height of its own beyond the plane's floor a row's height is its
   host's row module (the strip's `gridAutoRows: minmax(var(--fl-control-h), auto)`),
   and the floor only keeps a standalone one on its surface's plane. */
.fl-row-btn {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--fl-control-h);
  padding: var(--fl-s-0);
  background: none;
  border: none;
  border-radius: var(--fl-r-0);
  font: inherit;
  color: inherit;
  text-align: start;
  cursor: pointer;
  transition: background var(--fl-dur-2) var(--fl-ease);
}
/* The between-rows hairline. It rides the ROW rather than the call site so the
   `border: none` this class owns is never half-overridden by an inline style from
   outside, which is the hand-roll creeping back one property at a time. */
.fl-row-btn[data-divider='true'] {
  border-bottom: 1px solid var(--fl-line);
}
/* The translucent veil, NOT --fl-hover: a row button is painted by whatever hosts
   it, and the read-back strip is already --fl-bg-2, which --fl-hover matches
   exactly and would show nothing. The veil reads on every surface. (Same reason
   .fl-press-target uses it.) */
.fl-row-btn:not(:disabled):hover {
  background: var(--fl-hover-veil);
}
.fl-row-btn:not(:disabled):active {
  filter: brightness(0.92);
}
/* HELD: the act the press started is still running (the page it opens has not arrived yet). The
   row keeps the veil it had under the finger instead of returning to rest and then jumping, which
   is the same "no idle frame between the press and the next surface" rule the labelled button's
   spinner serves. It does not dim and does not spin: a row's face is the RECORD, per the
   :disabled ruling right below. Owned by usePressBusy (ui/src/controls/_press.ts). */
.fl-row-btn[data-busy='true'] {
  background: var(--fl-hover-veil);
}
/* A disabled row button drops its AFFORDANCE the veil, the pointer, the tab stop
   and nothing else. Deliberately NOT .fl-btn's 0.5 opacity: a labelled button's
   face is the NAME OF AN ACTION, so dimming it dims a label, while a row button's
   face is the RECORD (a §61.51 figure and the column it belongs to), and a
   committed page has to read at full strength. The row was never boxed, so
   removing the veil and the cursor removes everything that ever said "press me". */
.fl-row-btn:disabled {
  cursor: default;
}

/* ────────────────────────────────────────────────────────────────────────────
   GAUGE READING LATTICE the two-column row every gauge reading is drawn on,
   and the CSS half of <GaugeReadout> / <GaugeReadoutList>.

   The anatomy is DUTY'S §117 glance, made the single truth by founder ruling on
   2026-08-02: the rule's NAME sits on the surface's own x-rail, OUTSIDE the
   wash, and the READING COLUMN beside it carries the proportion with the row's
   two figures riding on it. The row is CONTENT HEIGHT it is a line of text,
   and a line of text has no control-height floor to sit on.

   Why it is CSS and not props: the arrangement IS the ruling. A call site that
   could pass a template, a rail, an inset or a breakpoint is a call site that
   can disagree with the other surface drawing the same row, which is exactly
   how the checkride-progress rows came to render a control-height band with a
   full-bleed wash beside duty's content-height row with an inset one. There is
   nothing here to pass and therefore nothing to get wrong.

   The ROW is a SUBGRID, not a fragment of cells, and that is load-bearing three
   times over: every row's name column is one width down the whole list (the
   tracks belong to the list), the row stays a real element that can be a press
   target, and it owns its own rows so a note may hang UNDER it at the within-row
   gap. Same mechanism the transcription ledger's sticky header and footer use. */
:root {
  /* The gauge row's own height, for a placeholder standing in for one: the
     reading plane's line box (`.fl-body`'s leading over the plane's size). A
     skeleton taller than the row it precedes makes the list jump when the read
     lands, which is the one thing a skeleton exists to avoid. */
  --fl-gauge-row-h: calc(var(--fl-text-body) * 1.5);
}
/* THE LIST ASKS ABOUT ITS OWN WIDTH, NEVER THE WINDOW'S.

   The stacking rule below used to be a viewport `@media` query, and this
   section does not live in the viewport: it sits in Home's content column,
   which is ~304px while the window is 1024 and ~524px while the window is
   1280. So the two-column form stayed on in a pane far too narrow for it, and
   the reading column was crushed until the trailing cite was chopped
   mid-token ("§61.65(d)("). The founder saw it as "lines that aren't
   truncated or wrapped, they just overflow", and that is exactly what a hard
   clip with no ellipsis looks like.

   The bug was never the breakpoint's VALUE, it was the box it measured: a
   component that is dropped into panes cannot answer a question about the
   window. A container query asks the only box that can be right, so the same
   list is correct in a 300px rail, a 520px column and a full-bleed pane
   without any surface passing anything. This scope element exists solely to
   be that container ("container-type" cannot be self-querying, and the
   template lives on the list, so the query needs an ancestor). */
.fl-gauge-scope {
  container-type: inline-size;
  /* THE RAIL IS INVIOLATE, and this is where that is guaranteed rather than
     hoped for. The reading track's floor is its own content (see the list
     below), which is what makes ONE rule right for two surfaces, and the price
     of a content-derived floor is that content decides a width: a call site
     that passed PROSE where the row takes a figure would size the track to the
     sentence and push the row through the surface's x-rail, which is exactly
     what the founder saw on 2026-08-02.

     `clip`, not `hidden`: hidden makes this a scroll container, which would
     hand it a scrollport, a scroll position and an overscroll chain to reason
     about (see the scroll-region ruling), for a case that must never scroll.
     Clip paints nothing outside the box and creates no scroller. The row's own
     figures still wrap at their clause joins inside the cell; this only bounds
     the damage a contract violation can do to the surface around it. */
  overflow: clip;
}
.fl-gauge-list {
  /* The column gap, NAMED, because one row cannot inherit it: a press-target row
     is a .fl-row-btn, whose primitive writes `gap` inline, and an inline value is
     the one thing a class cannot beat. It reads this rather than restating the
     step, so the lattice still has a single owner. */
  --fl-gauge-col-gap: var(--fl-s-5);
  /* THE NAME'S FLOOR: the widest single word of the requirement vocabulary, so a
     name squeezed to it wraps between its words and never inside one. Named once
     because two rules read it: the track's floor below, and the reading's own cap
     (`.fl-gauge-read`), which is "everything the list has except this".

     The word is "Endorsement" (76px at the 12px plane, measured over every label
     `progressLexicon.ts` names, split at spaces and after hyphens, where a line
     may already break). Stated against the plane's own size so it moves with it,
     and a step over the measurement so a hinting difference cannot put the last
     glyph on a line of its own. iOS measures the same word
     (`GaugeLattice.widestNameWord`). It was 64px, a number that belonged to no
     word, and on a phone it cut every name to "Total fligh…" (#702). */
  --fl-gauge-name-min: calc(var(--fl-text-body) * 6.5);
  display: grid;
  /* THE READING COLUMN KEEPS WHAT IT MUST SHOW; THE NAME TAKES THE REST.

     Drawing floors, not spacing steps, in the currency chart's own idiom, and
     the reading floor is what the column MUST show: the datum and whatever
     trails it (a cite), so no list reserves a width for content it does not
     have. The datum's share of it is its widest CLAUSE (`.fl-gauge-datum`), and
     the qualifier is not in it at all (`.fl-gauge-qual`). The name is the
     flexible half: it takes its whole width whenever the list can pay for it,
     and wraps between its words when it cannot. A name on two lines still
     reads, while a figure cut mid-digit and a cite cut mid-paragraph-number are
     just wrong.

     THE NAME IS SIZED FROM WHAT THE READING ACTUALLY NEEDS, NEVER A SHARE OR A
     CAP. `auto` grows the name to its max-content only out of the width left
     once every track has its floor, and `1fr` takes what is left after that.
     Two things used to make a name truncate beside a reading column with room
     in it (#702, 2026-09-16), and both were a fixed quantity standing in for
     that arithmetic: a 260px cap on the name, which cut "Solo takeoffs and
     landings at a towered airp…" in an 814px list whose reading needed 477;
     and the qualifier's full width counted as floor, which cut "30 hours free
     in the last w…" to keep "34.8 h of 30 h required" whole. The tug of war
     before that ran the other way (`auto` beat `1fr`, so the name took its
     full cap and chopped the cite mid-token, "§61.65(d)("), and it was closed
     by the same principle: the floor is the content's own, which a fixed px or
     a percentage cannot be. */
  grid-template-columns: minmax(var(--fl-gauge-name-min), auto) minmax(min-content, 1fr);
  column-gap: var(--fl-gauge-col-gap);
  /* THE ROW RAIL, and it belongs to the LIST rather than to the row: stating it
     here means a row cannot carry a different one. A desktop row is ONE line, so
     8px reads as the ordinary dense 24px pitch. */
  row-gap: var(--fl-s-3);
  align-items: center;
}
/* Everything the list holds spans it: a row (which then subgrids the two tracks
   back), and any foot the list closes with a disclosure, an expander which
   therefore sits on the list's own rail without naming a step of its own. */
.fl-gauge-list > * {
  grid-column: 1 / -1;
}
.fl-gauge-row {
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
  /* The WITHIN-row gap: what hangs under a row (a note, an ask) and, on a phone,
     the reading under its own name. The list's rail is four times this, so
     proximity groups a note with the figure it qualifies rather than with the row
     below, and no hairline has to. */
  row-gap: var(--fl-s-2);
}
.fl-gauge-sub {
  grid-column: 1 / -1;
}
/* THE RAIL IS INVIOLATE: nothing a row says may paint outside the list.

   A grid item's `min-width` is `auto`, which resolves to its CONTENT's minimum,
   so one unbreakable run (the datum is `white-space: nowrap` by design) makes
   the cell wider than its track and the row paints straight through the
   surface's x-rail and off the edge of the section. That is not hypothetical: a
   named big flight's DESCRIPTION was being passed as the row's value, and the
   whole sentence ran past the rail, off the pane, and read as if the section's
   padding had broken (founder, 2026-08-02).

   The description belongs in the row's sub-line and now lives there, which is
   the MODEL fix; this is the closure, so no future author can reopen the class
   by putting a long string in a figure slot. A clipped cell also computes
   `min-width: auto` as 0, so the track stops being sized by content that cannot
   break. The metered arm already clipped, for its own unrelated reason (it hosts
   the absolute fill layers), which is exactly why only the unmetered rows
   overflowed and why this is stated once for both.

   THE CAP IS WHAT MAKES THE CLIP A LAST RESORT RATHER THAN THE ANSWER. The
   reading track floors at its content (`min-content`, above), so a datum wider
   than the whole list used to size the track past the list's own edge: the
   name fell to its floor, the grid overflowed, and the scope's clip cut the
   datum and its cite mid-glyph with no ellipsis at all (the checkride list on a
   390px phone, "0 of 10 full-stop land"). An item's max-width bounds its
   min-content contribution, so capping the reading at "the list, less the
   name's floor and the gap" keeps the track inside the list. The datum wraps at
   its clause joins inside that, so the cap only ever bites a single clause
   wider than the whole column, which no figure in the vocabulary is. `cqi` is
   the list's width because the scope is the query container. */
.fl-gauge-read {
  overflow: hidden;
  max-width: calc(100cqi - var(--fl-gauge-name-min) - var(--fl-gauge-col-gap));
}
/* HOW THE FIGURES SIT ALONG THE TRACK, and it is here rather than inline for the
   same reason the lattice is: the arrangement is the ruling. The datum holds the
   trailing edge at every width, and the qualifier fills whatever leads it.

   `position: relative` so the context slots the narrow rule takes out of flow are
   contained by the row that owns them rather than by whatever positioned ancestor
   happens to be overhead. */
.fl-gauge-figs {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--fl-s-4);
}
/* THE QUALIFIER IS NEVER FLOOR, AND NEVER CUT.

   It is "the half that may be sacrificed when the column is tight", so it may not
   be the thing that makes the column tight: it takes the leading space the datum
   leaves and sizes nothing. `contain: inline-size` gives it no intrinsic width,
   so it adds nothing to the reading track's floor, and `flex: 1 1 0` hands it the
   room that is there. Counted as floor, a 170px "34.8 h of 30 h required" held
   its whole width while the rule it qualifies was cut to "30 hours free in the
   last w…" (#702).

   And it is drawn WHOLE OR NOT AT ALL: "0 h to 11.2 h of…" is a different
   figure, not a shorter one. There is no measurement and no script. The slot is
   a one-line WRAPPING flex row that clips its second line: a zero-width first
   item holds line one, so the qualifier's one unbreakable unit (`.fl-gauge-unit`)
   either fits beside it on that line or is pushed whole onto line two, which the
   one-line height clips away. A flex line never shrinks an item below its
   content to keep it (the unit's automatic minimum is its nowrap run), so the
   browser's own line-breaking is the "fits whole" test. Clipped, not removed, so
   a screen reader still reads it, exactly as the narrow rule below hides it. The
   iOS row asks the same question with `ViewThatFits`. An empty qualifier draws
   nothing either way. */
.fl-gauge-qual {
  flex: 1 1 0;
  contain: inline-size;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  height: 1lh;
  overflow: clip;
}
/* Line one's holder. An empty flex item is zero tall, so it is given the slot's
   own line height, or a unit pushed to line two would ride up into line one's
   place instead of under it. */
.fl-gauge-qual::before {
  content: '';
  height: 1lh;
}
/* A NAMED FIGURE IS ONE UNBREAKABLE RUN. Every figure slot on the row (the datum's
   clauses, the qualifier) wraps its words in this, so a line may break only
   BETWEEN units, never inside one: "0 of 10 full-stop" over "landings" is a
   broken figure. `nowrap` also holds the hyphen in "full-stop", which normal
   wrapping would break after. */
.fl-gauge-unit {
  white-space: nowrap;
}
/* THE DATUM WRAPS AT ITS CLAUSE JOINS AND NOWHERE ELSE. A datum of one clause is
   one unit and holds one line; a dual figure ("0 of 10 takeoffs · 0 of 10
   full-stop landings") is two units with the join between them as the only
   break, so where the column is narrower than the whole figure it takes a second
   line rather than a clip. Its min-content is its widest CLAUSE, which is what
   the reading track floors at. The lines keep the trailing edge every datum
   holds. */
.fl-gauge-datum {
  text-align: end;
}
/* A row that is a DOOR to the record it reads is the same lattice worn by a
   RowButton. The pairing is spelled as one selector rather than left to source
   order, because both classes declare `display` at the same specificity.

   It is the ONE row that floors: WCAG 2.2 SC 2.5.8 puts a press target on 24px,
   and a line of text beside it has no target size to answer for. That floor
   comes from .fl-row-btn itself, so it is the platform rule rather than a
   per-surface band. */
.fl-row-btn.fl-gauge-row {
  display: grid;
}
/* THE NAME WRAPS BETWEEN ITS WORDS, and is never cut.

   No `max-width`: there was a 260px "graphic measure" here so one long name
   could not size the whole column, and it truncated names beside reading
   columns with hundreds of px of room (#702). The track already answers that
   question from the content: a long name takes only what the reading's floor
   leaves. Where that is less than the name, the name takes a second line rather
   than an ellipsis, and the track's floor (`--fl-gauge-name-min`) is the widest
   word it can meet, so the break always falls between words. */
.fl-gauge-name {
  min-width: 0;
}

.fl-link {
  text-decoration: none;
  transition: color var(--fl-dur-2) var(--fl-ease);
}
.fl-link[data-variant='default'] {
  color: var(--fl-signal);
}
.fl-link[data-variant='muted'] {
  color: var(--fl-tx-2);
}
.fl-link:hover {
  color: var(--fl-tx-0);
}

.fl-page-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 28px;
  padding-inline: var(--fl-s-3);
  background: transparent;
  color: var(--fl-tx-2);
  border: 1px solid transparent;
  border-radius: var(--fl-r-sm);
  font-family: var(--fl-font-mono);
  font-size: var(--fl-text-body);
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background var(--fl-dur-2) var(--fl-ease);
}
.fl-page-num:hover {
  background: var(--fl-hover);
}
.fl-page-num[aria-current='page'] {
  background: var(--fl-bg-3);
  color: var(--fl-tx-0);
  border-color: var(--fl-line-3);
}

.fl-card[data-interactive='true'] {
  cursor: pointer;
  transition:
    border-color var(--fl-dur-2) var(--fl-ease),
    background var(--fl-dur-2) var(--fl-ease);
}
.fl-card[data-interactive='true']:hover {
  border-color: var(--fl-line-3);
}
/* The focus ring rides the CARD, not the stretched target inside it: the target is the tab stop,
   but the thing a keyboard user is looking at is the card. `:focus-within` is what carries it
   across that seam. */
.fl-card[data-interactive='true']:focus-within {
  outline: none;
  box-shadow: var(--fl-focus-ring);
}

/* Stretched press target the whole-region activation pattern, and the fourth
   button geometry the library owns (labelled .fl-btn, square
   .fl-btn[data-icon-only], full-bleed .fl-row-btn, and this). A region of rich
   content (a band, a card, a row with its own trailing actions) becomes ONE
   press target via an absolutely-stretched <button> SIBLING of the content
   never its parent, because wrapping arbitrary slots in a <button> invites
   nested-interactive invalid HTML. The host establishes position:relative; the
   button stretches over everything, carries the accessible name, and paints
   only the translucent hover veil so it can never occlude the content it
   activates. Focus ring comes from the global :focus-visible rule.

   Reach for .fl-row-btn FIRST: a row whose content is display-only is a row
   button, and this class only takes over where the region hosts an interactive
   child of its own. The primitive is PressTarget it is never hand-rolled at a
   call site, same rule as .fl-btn.

   The radius is INHERITED rather than pinned to zero: the veil is inset:0 over
   the host, so on a rounded card a square veil would poke past the host's own
   corners. A host with no radius inherits zero, which is what this rule used to
   state outright. */
.fl-press-target {
  position: absolute;
  inset: 0;
  background: transparent;
  border: none;
  padding: var(--fl-s-0);
  border-radius: inherit;
  cursor: pointer;
  transition: background var(--fl-dur-2) var(--fl-ease);
}
.fl-press-target:not([data-paint='none']):hover {
  background: var(--fl-hover-veil);
}
/* A target inside a host that already owns the row states paints NOTHING itself: `.fl-row` has one
   hover fill, one selected fill and one zebra for every list in the product, and a veil stacked on
   top of those would make a pressable row read a shade darker than an inert one. The host's own
   `:hover` still fires, because the target is its child. */
.fl-press-target[data-paint='none']:hover {
  background: transparent;
}
/* HELD: the row keeps the fill it had under the finger until the surface it opens is up, which is
   the row-shaped form of the labelled button's spinner (`usePressBusy`). It is the ROW's own hover
   fill rather than a second treatment, so a held row and a hovered row read alike. */
.fl-row:has(.fl-press-target[data-busy='true']) {
  background: var(--fl-bg-2);
}
.fl-press-target:active {
  filter: brightness(0.92);
}
/* HELD, exactly as .fl-row-btn[data-busy]: the region keeps its veil until the surface it
   activates is on screen. */
.fl-press-target[data-busy='true'] {
  background: var(--fl-hover-veil);
}
/* The other half of the press-target contract, and the half that is silently
   wrong when it is forgotten. The stretched target paints (and therefore
   hit-tests) above the region's static content, so an interactive CHILD of the
   region a "Get signed" button, an "Attach a copy", a Remove must be
   positioned to sit back above it or the region swallows its clicks. Raising is
   also what makes stopPropagation unnecessary: a raised child is a SIBLING of
   the target event-wise, so activating it is a different element's click rather
   than the region's own click bubbling. */
.fl-press-raise {
  position: relative;
  z-index: 1;
}

.fl-control {
  /* Inherits the surface's control plane, like .fl-btn; an explicit `size` sets an
     inline height that wins over it. */
  height: var(--fl-control-h);
  /* Allow shrinking below the inner <input>'s intrinsic ~175px min-width.
     Without this, an input in a flex/grid cell forces its parent to grow
     and overflows narrower containers. */
  min-width: 0;
  background: var(--fl-bg-1);
  border: 1px solid var(--fl-line-2);
  border-radius: var(--fl-r-sm);
  /* The class owns the text color (the inner <input> inherits it) so state rules
     below disabled can restyle text and border together. */
  color: var(--fl-tx-0);
  transition:
    border-color var(--fl-dur-2) var(--fl-ease),
    box-shadow var(--fl-dur-2) var(--fl-ease);
}
/* A MULTI-LINE control's height is its row count, never the plane's single-line step.
   The plane sets a fixed `height`, which is right for every control that holds ONE line
   (an input, a select, a command trigger) and wrong for the one that does not: a
   `<textarea rows={3}>` forced to 30px has ~12px of text left after its padding and
   border, which is the whole field collapsed to a slot. This is the same argument the
   plane's own preamble makes for why a single universal height is impossible, just from
   the other side: there, a control must be TALLER than the affordance it hosts; here, it
   must be as tall as the rows it was asked for. The plane still sets the FLOOR, so a
   one-row textarea never reads shorter than the inputs beside it. */
textarea.fl-control {
  height: auto;
  min-height: var(--fl-control-h);
}
.fl-control[data-invalid='true'] {
  border-color: var(--fl-crimson-line);
}
/* A disabled control must READ disabled text and border drop a step. Two selector
   shapes, one rule: :disabled for the element-level controls (select, textarea,
   command trigger), :has(input:disabled) for Input's wrapper div. */
.fl-control:disabled,
.fl-control:has(input:disabled) {
  color: var(--fl-tx-3);
  border-color: var(--fl-line);
}
.fl-control:focus,
.fl-control:focus-within {
  border-color: var(--fl-signal-line);
  box-shadow: 0 0 0 3px var(--fl-signal-bg);
}
.fl-control[data-invalid='true']:focus,
.fl-control[data-invalid='true']:focus-within {
  border-color: var(--fl-crimson-line);
  box-shadow: none;
}
/* The wrapper owns the focus treatment (:focus-within, above). The inner control
   must NOT also paint the global :focus-visible ring (line ~232) inset from the
   wrapper's right edge by a trailing affix (e.g. the "HR" unit), that ring's edge
   renders as a stray vertical line beside the suffix. */
.fl-control :focus-visible {
  box-shadow: none;
}

/* A keyboard-focused native <select> shows the full focus ring (the wrapper's :focus-within tint
   alone is too subtle to satisfy 2.4.7); more specific than the suppress rule above, so it wins. */
.fl-control select:focus-visible {
  box-shadow: var(--fl-focus-ring);
}

/* Skip link (WCAG 2.4.1 bypass-blocks) off-screen until focused, then pinned at the top of the
   inline start */
.fl-skip-link {
  position: absolute;
  inset-inline-start: var(--fl-s-3);
  top: -200px;
  z-index: 1000;
  padding: var(--fl-s-3) var(--fl-s-5);
  background: var(--fl-bg-2);
  color: var(--fl-tx-0);
  border: 1px solid var(--fl-line-3);
  border-radius: var(--fl-r-sm);
  font-family: var(--fl-font-sans);
  font-size: var(--fl-text-body);
  text-decoration: none;
  transition: top var(--fl-dur-2) var(--fl-ease);
}
.fl-skip-link:focus {
  top: var(--fl-s-3);
  box-shadow: var(--fl-focus-ring);
}

/* type="number" inputs: strip the native OS stepper chrome. The browser default
   spin-buttons clash with the control surface and any trailing affix (e.g. a
   "HR" suffix), and increment/decrement is not part of this design language
   numeric fields are typed, not spun. Hidden cross-browser (WebKit pseudo-
   elements + Firefox/standard `appearance`). */
.fl-control input[type='number'] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.fl-control input[type='number']::-webkit-outer-spin-button,
.fl-control input[type='number']::-webkit-inner-spin-button {
  /* display:none in addition to appearance:none some Chromium/WebKit versions
     keep rendering the spin-button's 1px divider on focus/hover under
     appearance:none alone; display:none removes the element (and its divider)
     outright in every state. */
  -webkit-appearance: none;
  appearance: none;
  display: none;
  margin: 0;
}

/* CommandTrigger a button shaped like an input that opens a command palette.
   Inherits .fl-control surface; adds hover affordance because it is interactive
   like a button, not a text field. */
button.fl-command-trigger {
  background: var(--fl-bg-1);
}
button.fl-command-trigger:hover {
  background: var(--fl-hover);
  border-color: var(--fl-line-3);
}
button.fl-command-trigger:focus,
button.fl-command-trigger:focus-visible {
  outline: none;
}

/* ScrollBand the panning horizontal band hides its scrollbar (the edge fades are
   the overflow affordance). The inline style carries the Firefox/legacy-Edge
   properties (scrollbar-width / -ms-overflow-style); WebKit/Blink need this
   pseudo-element, which inline style cannot express. */
.fl-scrollband::-webkit-scrollbar {
  display: none;
}

/* THE pressable-row state machine: the cursor, the hover fill, the selected fill and the zebra,
   as ONE decision. The cursor joined it on 2026-08-14: it was hand-written beside the class at
   every row that carried it, which is a second half of one affordance that a row could carry
   without the other — and `RailRow` was exactly that row, setting a pointer, a tab stop and a key
   handler with no hover for any of it to react to. A row is pressable or it is not. */
.fl-row {
  cursor: pointer;
  transition: background var(--fl-dur-1) var(--fl-ease);
}
.fl-row:not([data-selected='true']):hover {
  background: var(--fl-bg-2);
}
.fl-row[data-selected='true'] {
  background: var(--fl-signal-bg);
}
.fl-row[data-zebra='true']:not([data-selected='true']) {
  background: var(--fl-bg-2);
}
.fl-row[data-zebra='true']:not([data-selected='true']):hover {
  background: var(--fl-bg-3);
}

/* THE PHONE BREAKPOINT, and the one place the design system still uses a media
   query phone width is a structural breakpoint, not a token-scale value.
   639px = useIsMobile's MOBILE_MAX_WIDTH.

   Responsive Grid <Grid mobileCols={n}> carries the desktop track count
   inline and the phone count as --fl-grid-mobile-cols. The !important is
   required and correct: a stylesheet !important is the one thing that overrides
   the inline grid-template-columns the primitive writes.

   It stays a MEDIA query because a page's column count is a question about the
   page: a surface author chose "two up on a phone" for the device. The gauge
   lattice below used to live in here too and does not any more it is a
   component dropped into panes, so it asks its own container instead
   (2026-08-03; the media query had it running the two-column form inside a
   304px pane on a 1024px window, which chopped the trailing cites). */
@media (max-width: 639px) {
  .fl-grid-responsive {
    /* stylelint-disable-next-line csstools/value-no-unknown-custom-properties -- set inline by Grid on every .fl-grid-responsive */
    grid-template-columns: repeat(var(--fl-grid-mobile-cols), minmax(0, 1fr)) !important;
  }
}

/* A GridCell's span, and the reason it is a component rather than an inline style: a literal
   `span N` inside a responsive grid overruns the collapsed phone template with implicit tracks
   (the entry form's weather row shipped that overflow). Below the breakpoint every cell in a
   responsive grid takes the full row — unconditional, so no span value can overflow, whatever N
   the desktop asked for.

   `.fl-grid-auto` (the `cols="auto"` cell lattice) takes the same collapse and NOT the template
   override above: its track count already follows the container, so it needs nobody to tell it
   what a phone is. What it still needs is this rule, because auto-fill hands a 390px container
   exactly one track and a `span={2}` cell would open an implicit second one. */
.fl-grid-cell {
  grid-column: span var(--fl-grid-span, 1);
}
@media (max-width: 639px) {
  .fl-grid-responsive > .fl-grid-cell,
  .fl-grid-auto > .fl-grid-cell {
    grid-column: 1 / -1;
  }
}

/* THE ROW IS ONE LINE AT EVERY WIDTH, and what a narrow column gives up is its
   CONTEXT, never its line count (founder, 2026-08-16: "can go into two line
   format, which does not look good").

   Below this width the reading can no longer carry the name, the datum AND the
   quiet halves that sit either side of it. Two answers were possible and the
   first one shipped: STACK the row, so the reading falls under its own name.
   That is what the founder was reading, and it is wrong for three reasons.
   It contradicts the row's own anatomy ("the row is CONTENT HEIGHT, it is a
   line of text"); the two lines do not share a left edge, because the reading
   is inset by its own px-3 while the name sits on the surface's rail, so the
   pair reads as a heading over a box rather than as one row; and it made the
   list four times taller exactly where there is least room for it.

   (What that ruling forbids is the reading STACKED under its name. A name that
   wraps between its words, or a dual figure that wraps at its clause join, keeps
   both columns side by side on one row and is not that shape: it is what a
   column too narrow for its text does instead of cutting it.)

   The second answer is the one the component ALREADY DECLARES, and it is now
   the only one: the qualifier "is the half that may be sacrificed when the
   column is tight", so when the column is tight it is sacrificed. Both context
   slots yield together, because they are the same kind of thing on opposite
   sides of the datum: the dim spent-of-allowed half, and whatever trails the
   figure (a cite, a stamp). What is left is the NAME and the FIGURE THE READER
   ACTS ON, which is the row's whole point, over a wash that still draws the
   proportion the dim half was wording.

   THEY ARE HIDDEN VISUALLY AND NOT REMOVED. A screen reader still reads
   "15 h of 14 h" and "§61.65(d)(2)(i)": this is a question about how much ink
   fits in 300px, not about what the row knows.

   THE THRESHOLD IS THE ROW'S OWN ARITHMETIC, not a device, and it is unchanged
   from the stacking rule it replaces: a 64px name floor, the 16px gap, and the
   ~250px the widest figure-and-cite pair asks for, which is ~330px, plus a
   little air. It catches the phone (a 390px window gives the section a 366px
   box) and Home's content column at 1024 (~304px), and leaves the 1280 column
   (~524px) carrying everything.

   It is a CONTAINER width, so a 360px pane on a 1600px window and a phone get
   the same correct answer for the same reason. Consequently the list's rail is
   now ONE step at every width: there is no taller row to give more air to. */
@container (max-width: 380px) {
  .fl-gauge-qual,
  .fl-gauge-trail {
    /* The VisuallyHidden primitive's own declarations, in CSS because the width
       that triggers them is the container's rather than a prop's. Pinned to the
       origin with a 1px box and a -1px margin, so it is layout-inert wherever
       the row is drawn (see that primitive for the scrollHeight hazard). */
    position: absolute;
    top: 0;
    inset-inline-start: 0;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  /* Nothing to restate for the datum: `.fl-gauge-figs` already holds it on the
     trailing edge at every width, so with both context slots out of flow it
     stays exactly where every other row's datum is. */
}

/* SplitPane minSize="min-content" the pane refuses to shrink below its
   content's intrinsic minimum, so a drag handle stops where the content's own
   declared floors say (nowrap idents, arc stubs, grid track minimums) instead
   of compressing into overflow. SplitPane sets this attribute on the panel's
   ROOT element (react-resizable-panels spreads rest props onto the flex item
   it sizes). The !important is required and correct for the same reason as
   .fl-grid-responsive above: the lib writes min-width: 0 inline, and a
   stylesheet !important is the one thing that overrides an inline value.

   This is the TERMINAL GUARANTEE, not the mechanism. The floor's single
   AUTHORITY is the split lib's own constraint solver, which SplitPane feeds a
   MEASURED px minimum (see useContentFloor). This rule only covers the frames
   where no measurement can exist yet: the first commit, and an autoSaveId
   layout restored from localStorage against constraints the lib zeroes while
   the group is still unmeasured. Because the measured value is ceil'd, it is
   always >= this clamp, so in steady state the clamp never binds and never
   contradicts the model. (It used to BE the mechanism, with the solver told
   nothing: the solver then drained the pane's model toward zero, the clamp held
   the pane at its real floor, and the shortfall was stolen from whichever
   sibling was still flexible.) */
[data-fl-split-pane-min='w'] {
  min-width: min-content !important;
}
[data-fl-split-pane-min='h'] {
  min-height: min-content !important;
}

/* Floating surface card the visual chrome shared by every portaled Radix
   popper surface (popover, menu, listbox, calendar). Rendered as the CHILD of a
   Radix `*.Content` element: Radix owns Content's positioning `transform`
   (floating-ui). NO entrance animation: floating surfaces are the instrument's
   working popups (typeahead listboxes above all) and must land the same frame
   they are asked for founder ruling 2026-07-27, snappiness over ceremony.
   (Tooltip/Toaster keep their own fl-pop-in; they are ambient feedback, not an
   interaction the hands wait on.) */
/* A run of rows that share seams (ui `Rows`): one hairline BETWEEN each pair and
   never one at either end. The rule is the closure, not a convention: a border-top on every
   child after the first cannot express a seam after the LAST row, so there is no isLast to
   remember and no prop to forget. It replaced twenty-one call sites that each drew their own
   `borderBottom` per row, of which the notifications panel is the recorded failure: its last
   row's hairline landed on the popover card's own border and drew that edge twice.

   The seam is a property of the LIST because a row cannot see what follows it, nor whether its
   host insets it. Set by the primitive, never by a call site. */
.fl-rows > * + * {
  border-top: 1px solid var(--fl-line);
}

/* A PINNED head inside a seamed run. `.fl-rows` puts the seam on the FOLLOWING child's top edge,
   which is correct for two rows that scroll together and wrong for a head that pins: the row
   carrying the line scrolls underneath it, so the pinned head loses its bottom edge exactly when it
   needs one. So the head draws its own bottom border (the shell rule from LedgerBand: a shell owns
   its outer edges, `Rows` owns the seams BETWEEN children), and the child after it drops the top
   border it would otherwise contribute, or the two would stack 2px at rest. */
.fl-rows > .fl-sticky-head {
  border-bottom: 1px solid var(--fl-line);
}
.fl-rows > .fl-sticky-head + * {
  border-top: 0;
}

.fl-floating-card {
  background: var(--fl-bg-1);
  border: 1px solid var(--fl-line-2);
  border-radius: var(--fl-r-sm);
  box-shadow: var(--fl-shadow-2);
  /* Overscroll containment is NOT declared here — it rides `<Box scroll="y">` on FloatingCard,
     so the whole system has exactly one owner of the overflow/overscroll pairing
     (ui/src/primitives/_scroll.ts). A second declaration here would be a drift surface. */
}

/* Dropdown-menu item Radix owns roving focus and sets `data-highlighted`
   on the active row and `data-disabled` on disabled ones, so the interaction
   states live HERE keyed off those attributes (the .fl-btn pattern: state in
   CSS, never recomputed in JS). The danger tone rides a `data-tone` attribute. */
.fl-menu-item {
  display: flex;
  align-items: center;
  gap: var(--fl-s-3);
  width: 100%;
  padding: var(--fl-s-2) var(--fl-s-3);
  border-radius: var(--fl-r-sm);
  color: var(--fl-tx-1);
  cursor: pointer;
  outline: none;
  user-select: none;
  transition: background var(--fl-dur-1) var(--fl-ease);
}
.fl-menu-item[data-highlighted] {
  background: var(--fl-bg-3);
}
.fl-menu-item[data-tone='danger'] {
  color: var(--fl-crimson);
}
.fl-menu-item[data-disabled] {
  color: var(--fl-tx-3);
  cursor: not-allowed;
}

