/* @fairleadhq/design-system@0.2.5 - target components.css */
/* Fairlead components. CSS first: classes and data attributes, no framework. Generated by concatenation. */

/* Button. <button class="fl-button" data-variant="primary|secondary|quiet|ghost|danger" data-size="sm|md|lg">
   One primary per zone. `quiet` is a tinted resting fill for low-emphasis actions inside a card.
   Icon-only buttons add data-icon-only and need an aria-label. */
.fl-button {
  --_bg: var(--fl-surface-card);
  --_fg: var(--fl-text-strong);
  --_bd: var(--fl-border-control);
  --_bg-hover: var(--fl-surface-hover);
  --_bg-active: var(--fl-surface-inset);
  appearance: none;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--fl-space-2);
  min-height: 2.25rem;
  padding: 0 var(--fl-space-4);
  border: var(--fl-border-width) solid var(--_bd);
  border-radius: var(--fl-radius-md);
  background: var(--_bg);
  color: var(--_fg);
  font: inherit;
  font-size: var(--fl-font-size-md);
  font-weight: var(--fl-font-weight-medium);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--fl-duration-fast) var(--fl-ease-out), border-color var(--fl-duration-fast) var(--fl-ease-out), color var(--fl-duration-fast) var(--fl-ease-out);
}
.fl-button:hover { background: var(--_bg-hover); }
.fl-button:active { background: var(--_bg-active); }
.fl-button:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: var(--fl-focus-ring-offset); }
.fl-button:disabled, .fl-button[aria-disabled='true'] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

.fl-button[data-variant='primary'] { --_bg: var(--fl-accent); --_fg: var(--fl-on-accent); --_bd: transparent; --_bg-hover: var(--fl-accent-hover); --_bg-active: var(--fl-accent-active); }
.fl-button[data-variant='danger'] { --_bg: var(--fl-status-danger-solid); --_fg: var(--fl-on-status-solid); --_bd: transparent; --_bg-hover: color-mix(in oklab, var(--fl-status-danger-solid) 88%, black); --_bg-active: color-mix(in oklab, var(--fl-status-danger-solid) 78%, black); }
.fl-button[data-variant='quiet'] { --_bg: var(--fl-surface-subtle); --_bd: transparent; --_bg-hover: var(--fl-surface-hover); --_bg-active: var(--fl-border-subtle); }
.fl-button[data-variant='ghost'] { --_bg: transparent; --_fg: var(--fl-accent); --_bd: transparent; --_bg-hover: var(--fl-accent-wash); --_bg-active: var(--fl-accent-wash); }

.fl-button[data-size='sm'] { min-height: 1.75rem; padding: 0 var(--fl-space-3); font-size: var(--fl-font-size-sm); gap: 6px; }
.fl-button[data-size='lg'] { min-height: 2.75rem; padding: 0 var(--fl-space-6); font-size: var(--fl-font-size-base); }
.fl-button[data-icon-only] { padding: 0; aspect-ratio: 1; }
.fl-button[data-full-width] { width: 100%; }
.fl-button[aria-busy='true'] { color: transparent; position: relative; pointer-events: none; }
.fl-button[aria-busy='true']::after { content: ''; position: absolute; inline-size: 1em; block-size: 1em; border-radius: 50%; border: 2px solid var(--_fg); border-right-color: transparent; animation: fl-spin 0.7s linear infinite; }
.fl-button > svg { inline-size: 1.15em; block-size: 1.15em; flex: none; }
@keyframes fl-spin { to { transform: rotate(1turn); } }

/* Mode toggle. <button class="fl-button fl-mode-toggle" data-variant="ghost" data-icon-only data-fl-mode-toggle hidden></button>
   Ships hidden: without script it could do nothing. `enhanceModeToggle()` reveals it, keeps its label
   current, and stores the choice under the key the head snippet restores. The icon shows the mode a press leads to. */
.fl-mode-toggle {
  --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401'/%3E%3C/svg%3E");
  --_fg: var(--fl-text-body);
}
.fl-mode-toggle[data-fl-mode-current='dark'] {
  --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2'/%3E%3Cpath d='M12 20v2'/%3E%3Cpath d='m4.93 4.93 1.41 1.41'/%3E%3Cpath d='m17.66 17.66 1.41 1.41'/%3E%3Cpath d='M2 12h2'/%3E%3Cpath d='M20 12h2'/%3E%3Cpath d='m6.34 17.66-1.41 1.41'/%3E%3Cpath d='m19.07 4.93-1.41 1.41'/%3E%3C/svg%3E");
}
.fl-mode-toggle::before { content: ''; inline-size: 1.15em; block-size: 1.15em; background: currentColor; mask: var(--_icon) center / contain no-repeat; -webkit-mask: var(--_icon) center / contain no-repeat; }
.fl-mode-toggle[hidden] { display: none; }

/* Badge. <span class="fl-badge" data-status="success|danger|warning|info|neutral">Passing</span>
   Status never relies on colour alone. The icon is drawn by the stylesheet from the status, so a
   badge cannot ship without one. Add data-icon="none" only for a badge that is not a status. */
.fl-badge {
  --_fg: var(--fl-status-neutral-fg);
  --_bg: var(--fl-status-neutral-bg);
  --_bd: var(--fl-status-neutral-border);
  --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M8 12h8'/%3E%3C/svg%3E");
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px 2px 7px;
  border: var(--fl-border-width) solid var(--_bd);
  border-radius: var(--fl-radius-full);
  background: var(--_bg);
  color: var(--_fg);
  font-size: var(--fl-font-size-xs);
  font-weight: var(--fl-font-weight-medium);
  line-height: 1.5;
  white-space: nowrap;
}
.fl-badge::before { content: ''; inline-size: 1.05em; block-size: 1.05em; flex: none; background: currentColor; mask: var(--_icon) center / contain no-repeat; -webkit-mask: var(--_icon) center / contain no-repeat; }
.fl-badge[data-icon='none'] { padding-inline-start: 9px; }
.fl-badge[data-icon='none']::before { display: none; }
.fl-badge[data-status='success'] { --_fg: var(--fl-status-success-fg); --_bg: var(--fl-status-success-bg); --_bd: var(--fl-status-success-border); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m9 12 2 2 4-4'/%3E%3C/svg%3E"); }
.fl-badge[data-status='danger'] { --_fg: var(--fl-status-danger-fg); --_bg: var(--fl-status-danger-bg); --_bd: var(--fl-status-danger-border); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m15 9-6 6'/%3E%3Cpath d='m9 9 6 6'/%3E%3C/svg%3E"); }
.fl-badge[data-status='warning'] { --_fg: var(--fl-status-warning-fg); --_bg: var(--fl-status-warning-bg); --_bd: var(--fl-status-warning-border); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E"); }
.fl-badge[data-status='info'] { --_fg: var(--fl-status-info-fg); --_bg: var(--fl-status-info-bg); --_bd: var(--fl-status-info-border); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E"); }

/* Forced colours replace every background, and the icon is a background seen through a mask. Draw it in the text colour. */
@media (forced-colors: active) {
  .fl-badge::before { forced-color-adjust: none; background: CanvasText; }
}

/* Banner. <div class="fl-banner" data-status="..." role="status|alert"><p class="fl-banner__title">…</p><p>…</p></div>
   An inline message in the page flow. Same statuses and the same enforced icon as the badge. */
.fl-banner {
  --_fg: var(--fl-status-neutral-fg);
  --_bg: var(--fl-status-neutral-bg);
  --_bd: var(--fl-status-neutral-border);
  --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E");
  position: relative;
  padding: var(--fl-space-3) var(--fl-space-4) var(--fl-space-3) calc(var(--fl-space-4) + 1.25rem + var(--fl-space-3));
  border: var(--fl-border-width) solid var(--_bd);
  border-radius: var(--fl-radius-lg);
  background: var(--_bg);
  color: var(--fl-text-body);
  font-size: var(--fl-font-size-md);
  line-height: var(--fl-line-height-normal);
}
.fl-banner::before { content: ''; position: absolute; inset-inline-start: var(--fl-space-4); inset-block-start: calc(var(--fl-space-3) + 0.1em); inline-size: 1.25rem; block-size: 1.25rem; background: var(--_fg); mask: var(--_icon) center / contain no-repeat; -webkit-mask: var(--_icon) center / contain no-repeat; }
.fl-banner > * { margin: 0; }
.fl-banner > * + * { margin-block-start: var(--fl-space-1); }
.fl-banner__title { color: var(--fl-text-strong); font-weight: var(--fl-font-weight-semibold); }
.fl-banner a { color: var(--fl-text-strong); font-weight: var(--fl-font-weight-medium); }
.fl-banner[data-status='success'] { --_fg: var(--fl-status-success-fg); --_bg: var(--fl-status-success-bg); --_bd: var(--fl-status-success-border); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m9 12 2 2 4-4'/%3E%3C/svg%3E"); }
.fl-banner[data-status='danger'] { --_fg: var(--fl-status-danger-fg); --_bg: var(--fl-status-danger-bg); --_bd: var(--fl-status-danger-border); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m15 9-6 6'/%3E%3Cpath d='m9 9 6 6'/%3E%3C/svg%3E"); }
.fl-banner[data-status='warning'] { --_fg: var(--fl-status-warning-fg); --_bg: var(--fl-status-warning-bg); --_bd: var(--fl-status-warning-border); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E"); }
.fl-banner[data-status='info'] { --_fg: var(--fl-status-info-fg); --_bg: var(--fl-status-info-bg); --_bd: var(--fl-status-info-border); }

/* Forced colours replace every background, and the icon is a background seen through a mask. Draw it in the text colour. */
@media (forced-colors: active) {
  .fl-banner::before { forced-color-adjust: none; background: CanvasText; }
}

/* Field. Label above, control, then a hint or an error below. Never a placeholder as a label.
   <div class="fl-field"><label class="fl-field__label" for="x">…</label><input id="x" class="fl-input">
     <p class="fl-field__hint">…</p><p class="fl-field__error" role="alert">…</p></div>
   Mark a failing control with aria-invalid="true". */
.fl-field { display: grid; gap: 6px; min-inline-size: 0; }
.fl-field__label { color: var(--fl-text-strong); font-size: var(--fl-font-size-sm); font-weight: var(--fl-font-weight-medium); }
.fl-field__label [data-optional] { color: var(--fl-text-muted); font-weight: var(--fl-font-weight-regular); }
.fl-field__hint, .fl-field__error { margin: 0; font-size: var(--fl-font-size-sm); line-height: var(--fl-line-height-snug); }
.fl-field__hint { color: var(--fl-text-muted); }
.fl-field__error { color: var(--fl-status-danger-fg); }

.fl-input, .fl-textarea, .fl-select {
  appearance: none;
  box-sizing: border-box;
  inline-size: 100%;
  min-inline-size: 0;
  min-block-size: 2.25rem;
  padding: 0 var(--fl-space-3);
  border: var(--fl-border-width) solid var(--fl-border-control);
  border-radius: var(--fl-radius-md);
  background: var(--fl-surface-raised);
  color: var(--fl-text-strong);
  font: inherit;
  font-size: var(--fl-font-size-md);
  transition: border-color var(--fl-duration-fast) var(--fl-ease-out);
}
.fl-textarea { padding-block: var(--fl-space-2); min-block-size: 5rem; line-height: var(--fl-line-height-normal); resize: vertical; }
.fl-input::placeholder, .fl-textarea::placeholder { color: var(--fl-text-muted); opacity: 1; }
.fl-input:hover, .fl-textarea:hover, .fl-select:hover { border-color: var(--fl-text-muted); }
.fl-input:focus-visible, .fl-textarea:focus-visible, .fl-select:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: 0; border-color: var(--fl-focus-ring); }
.fl-input[aria-invalid='true'], .fl-textarea[aria-invalid='true'], .fl-select[aria-invalid='true'] { border-color: var(--fl-status-danger-fg); }
.fl-input:disabled, .fl-textarea:disabled, .fl-select:disabled { background: var(--fl-surface-inset); color: var(--fl-text-muted); cursor: not-allowed; }
.fl-input[data-mono], .fl-textarea[data-mono] { font-family: var(--fl-font-mono); font-size: var(--fl-font-size-sm); }
.fl-input[data-size='sm'], .fl-select[data-size='sm'] { min-block-size: 1.75rem; padding-inline: var(--fl-space-2); font-size: var(--fl-font-size-sm); }
.fl-select {
  padding-inline-end: calc(var(--fl-space-3) + 1.25rem);
  background-image: linear-gradient(45deg, transparent 50%, var(--fl-text-muted) 50%), linear-gradient(135deg, var(--fl-text-muted) 50%, transparent 50%);
  background-position: calc(100% - 1rem) 50%, calc(100% - 0.7rem) 50%;
  background-size: 0.3rem 0.3rem;
  background-repeat: no-repeat;
}

.fl-select[data-size='sm'] { padding-inline-end: calc(var(--fl-space-2) + 1.25rem); background-position: calc(100% - 0.85rem) 50%, calc(100% - 0.55rem) 50%; }

.fl-checkbox { display: inline-flex; align-items: center; gap: var(--fl-space-2); color: var(--fl-text-body); font-size: var(--fl-font-size-md); cursor: pointer; }
.fl-checkbox > input { inline-size: 1rem; block-size: 1rem; margin: 0; accent-color: var(--fl-accent); cursor: inherit; }
.fl-checkbox:has(> input:disabled) { color: var(--fl-text-muted); cursor: not-allowed; }

/* Combobox. A text input that suggests values and always accepts free text. Suggestions are advisory: they
   save typing and typos, they do not restrict the field. Choosing from a fixed set is a select.
   <div class="fl-combobox"><input class="fl-input" data-mono role="combobox" aria-expanded="true" aria-controls="l"
     aria-autocomplete="list" aria-activedescendant="l-0" autocomplete="off">
   <div class="fl-combobox__list" id="l" popover="manual" role="listbox" data-mono>
     <div class="fl-combobox__option" id="l-0" role="option" aria-selected="true">com.example.billing</div></div></div>
   The input is an ordinary .fl-input, so every state comes from the field. The list is a manual popover: it sits
   in the top layer, so no card or scrolling ancestor clips it, and focus stays in the input. The React layer
   places it under the input at the input's width and flips it above when there is no room below. The highlighted
   option is the active descendant, on the hover surface: a highlight is neither a selection nor the primary
   action, so it takes no accent. Long values wrap; the list never scrolls sideways. */
.fl-combobox { position: relative; inline-size: 100%; }
.fl-combobox > .fl-input { inline-size: 100%; }
.fl-combobox__list { position: fixed; inset: auto; box-sizing: border-box; max-block-size: 12rem; margin: 0; padding: var(--fl-space-1); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; border: var(--fl-border-width) solid var(--fl-border-default); border-radius: var(--fl-radius-lg); background: var(--fl-surface-raised); color: var(--fl-text-body); box-shadow: var(--fl-shadow-popover); z-index: var(--fl-layer-dropdown); font-size: var(--fl-font-size-md); }
.fl-combobox__list:popover-open { display: grid; align-content: start; }
/* Closed, the list is empty. Hiding it then also covers a browser without the Popover API, which would draw it. */
.fl-combobox__list:empty { display: none; }
.fl-combobox__option { box-sizing: border-box; display: block; align-content: center; min-block-size: 2rem; min-inline-size: 0; padding: var(--fl-space-1) var(--fl-space-3); border-radius: var(--fl-radius-sm); color: var(--fl-text-body); overflow-wrap: anywhere; cursor: pointer; user-select: none; }
.fl-combobox__option:hover, .fl-combobox__option[aria-selected='true'] { background: var(--fl-surface-hover); }
/* What the typed text matched, so a long name is read by its matching part: weight and the strong text, no fill. */
.fl-combobox__match { color: var(--fl-text-strong); font-weight: var(--fl-font-weight-medium); }
.fl-combobox__list[data-mono] { font-family: var(--fl-font-mono); font-size: var(--fl-font-size-sm); }

/* Card. A flat surface on a hairline. No shadow: shadows are for things that float.
   Use a card only when grouping needs a boundary. Whitespace and a divider are often enough. */
.fl-card { border: var(--fl-border-width) solid var(--fl-border-subtle); border-radius: var(--fl-radius-lg); background: var(--fl-surface-card); color: var(--fl-text-body); }
.fl-card__header { display: flex; align-items: center; justify-content: space-between; gap: var(--fl-space-3); padding: var(--fl-space-4) var(--fl-space-5); border-block-end: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-card__title { margin: 0; color: var(--fl-text-strong); font-size: var(--fl-font-size-base); font-weight: var(--fl-font-weight-semibold); }
.fl-card__body { padding: var(--fl-space-5); }
.fl-card__footer { display: flex; justify-content: flex-end; gap: var(--fl-space-2); padding: var(--fl-space-3) var(--fl-space-5); border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-card[data-padding='compact'] > .fl-card__body { padding: var(--fl-space-4); }

/* Section card: a titled region of a detail page, such as Accounting lines or History. The title sits inside the
   card's inset with no rule under it, with an optional count (.fl-card__meta) and actions at the end. A table or
   row summaries inside lose their own frame and run the card's full width, so a hover, a selection, or a total
   row reaches both edges, while the inset moves into their first and last cells and their text lines up with
   the title. The card is the one boundary. The inset tightens by the
   card's own width, so the same card fits a side column and a wide main column.
   <section class="fl-card" data-layout="section" aria-labelledby="t"><header class="fl-card__header">
     <h2 class="fl-card__title" id="t">Accounting lines <span class="fl-card__meta">3 lines</span></h2>…actions</header>
     <div class="fl-card__body">…</div></section> */
.fl-card[data-layout='section'] { --_inset: var(--fl-space-5); container: fl-card / inline-size; }
.fl-card[data-layout='section'] > .fl-card__header { flex-wrap: wrap; align-items: baseline; gap: var(--fl-space-1) var(--fl-space-3); padding: var(--_inset) var(--_inset) 0; border-block-end: 0; }
.fl-card[data-layout='section'] > .fl-card__body { padding: var(--fl-space-4) 0 var(--_inset); }
.fl-card[data-layout='section'] > .fl-card__body > :not(.fl-table-wrap):not(.fl-summary) { margin-inline: var(--_inset); }
.fl-card[data-layout='section'] > .fl-card__body:only-child { padding-block-start: var(--_inset); }
.fl-card__meta { margin-inline-start: var(--fl-space-2); color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); font-weight: var(--fl-font-weight-regular); }
.fl-card__empty { margin: 0; color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); }
.fl-card[data-layout='section'] .fl-table-wrap, .fl-card[data-layout='section'] .fl-summary { border: 0; border-radius: 0; background: none; }
.fl-card[data-layout='section'] .fl-table th:first-child, .fl-card[data-layout='section'] .fl-table td:first-child { padding-inline-start: var(--_inset); }
.fl-card[data-layout='section'] .fl-table th:last-child, .fl-card[data-layout='section'] .fl-table td:last-child { padding-inline-end: var(--_inset); }
.fl-card[data-layout='section'] .fl-summary__row { padding-inline: var(--_inset); }
@container fl-card (max-width: 24rem) {
  .fl-card[data-layout='section'] > * { --_inset: var(--fl-space-4); }
}

/* Table. <div class="fl-table-wrap"><table class="fl-table" data-density="compact|default|relaxed">
   A quiet header, hairline rows, and a hover surface. No dark header, no zebra stripes.
   Numbers go in <td data-numeric>: mono, tabular, right aligned. A chosen row takes aria-selected. */
.fl-table-wrap { overflow-x: auto; border: var(--fl-border-width) solid var(--fl-border-subtle); border-radius: var(--fl-radius-lg); background: var(--fl-surface-card); }
.fl-table { --_py: 0.625rem; --_px: var(--fl-space-4); inline-size: 100%; border-collapse: collapse; color: var(--fl-text-body); font-size: var(--fl-font-size-md); }
.fl-table[data-density='compact'] { --_py: 0.3125rem; --_px: var(--fl-space-3); font-size: var(--fl-font-size-sm); }
.fl-table[data-density='relaxed'] { --_py: 0.875rem; }
.fl-table th, .fl-table td { padding: var(--_py) var(--_px); text-align: start; vertical-align: middle; }
.fl-table thead th { color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); font-weight: var(--fl-font-weight-medium); white-space: nowrap; border-block-end: var(--fl-border-width) solid var(--fl-border-default); }
.fl-table tbody tr + tr > * { border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-table tbody tr:hover > * { background: var(--fl-surface-hover); }
.fl-table tbody tr[aria-selected='true'] > * { background: var(--fl-surface-selected); }
.fl-table tbody th { color: var(--fl-text-strong); font-weight: var(--fl-font-weight-medium); }
.fl-table [data-numeric] { font-family: var(--fl-font-mono); font-variant-numeric: tabular-nums; text-align: end; white-space: nowrap; }
.fl-table thead [data-numeric] { font-family: inherit; }
/* A sticky header for long lists. The wrap must be the scroll container: give it a max block size. */
.fl-table[data-sticky] thead th { position: sticky; inset-block-start: 0; z-index: 1; background: var(--fl-surface-card); }
.fl-table__actions { display: inline-flex; gap: 2px; justify-content: flex-end; }
/* A highlighted column, such as the product's own column in a comparison: mark every cell of it with
   data-highlight. It takes the selected surface, and its head the accent, which is selection. A row
   that carries the conclusion, such as a total or the primary cost, takes data-emphasis on the <tr>:
   the subtle surface and strong text. Hovering a row keeps both, so the column does not flicker. */
.fl-table [data-highlight], .fl-table tbody tr:hover > [data-highlight] { background: var(--fl-surface-selected); }
.fl-table thead [data-highlight] { color: var(--fl-accent); }
.fl-table tbody tr[data-emphasis] > * { background: var(--fl-surface-subtle); color: var(--fl-text-strong); font-weight: var(--fl-font-weight-medium); }
.fl-table tbody tr[data-emphasis] > [data-highlight] { background: var(--fl-surface-selected); }
/* Wrap long text in a comparison instead of forcing it onto one line. */
.fl-table[data-wrap] th, .fl-table[data-wrap] td { white-space: normal; vertical-align: top; }

/* Sorting. Every sortable column has one header trigger with an always-visible indicator.
   <th aria-sort="ascending|descending|none"><button class="fl-table__sort">Run</button></th> */
.fl-table__sort { appearance: none; display: inline-flex; align-items: center; gap: 4px; padding: 0; border: 0; background: none; color: inherit; font: inherit; cursor: pointer; }
.fl-table__sort::after { content: ''; inline-size: 0.875rem; block-size: 0.875rem; flex: none; background: var(--fl-border-control); mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 15 5 5 5-5'/%3E%3Cpath d='m7 9 5-5 5 5'/%3E%3C/svg%3E") center / contain no-repeat; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 15 5 5 5-5'/%3E%3Cpath d='m7 9 5-5 5 5'/%3E%3C/svg%3E") center / contain no-repeat; }
.fl-table__sort:hover { color: var(--fl-text-strong); }
.fl-table__sort:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: 2px; border-radius: var(--fl-radius-sm); }
.fl-table [aria-sort='ascending'] > .fl-table__sort, .fl-table [aria-sort='descending'] > .fl-table__sort { color: var(--fl-text-strong); }
.fl-table [aria-sort='ascending'] > .fl-table__sort::after { background: var(--fl-accent); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3Cpath d='M12 19V5'/%3E%3C/svg%3E"); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3Cpath d='M12 19V5'/%3E%3C/svg%3E"); }
.fl-table [aria-sort='descending'] > .fl-table__sort::after { background: var(--fl-accent); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='m19 12-7 7-7-7'/%3E%3C/svg%3E"); -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='m19 12-7 7-7-7'/%3E%3C/svg%3E"); }
.fl-table thead [data-numeric] > .fl-table__sort { flex-direction: row-reverse; }

/* Column resizing. The handle sits on the column boundary. It works with a pointer and, when
   focused, with the Left and Right arrow keys. Set table-layout: fixed through data-resizable. */
.fl-table[data-resizable] { table-layout: fixed; }
.fl-table[data-resizable] th, .fl-table[data-resizable] td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fl-table thead th { position: relative; }
.fl-table__resizer { position: absolute; inset-block: 0; inset-inline-end: -4px; inline-size: 9px; z-index: 2; cursor: col-resize; touch-action: none; }
.fl-table__resizer::after { content: ''; position: absolute; inset-block: 25%; inset-inline-start: 4px; inline-size: 1px; background: var(--fl-border-default); }
.fl-table__resizer:hover::after, .fl-table__resizer[data-dragging]::after { inset-block: 0; inline-size: 2px; inset-inline-start: 3px; background: var(--fl-accent); }
.fl-table__resizer:focus-visible { outline: none; }
.fl-table__resizer:focus-visible::after { inset-block: 0; inline-size: 2px; inset-inline-start: 3px; background: var(--fl-focus-ring); }

/* Pinned columns form a sticky block at the start edge. Set the offset per cell with --fl-pin-offset. */
.fl-table [data-pinned] { position: sticky; inset-inline-start: var(--fl-pin-offset, 0px); z-index: 1; background: var(--fl-surface-card); }
.fl-table thead [data-pinned] { z-index: 3; }
.fl-table [data-pinned='last'] { box-shadow: 1px 0 0 var(--fl-border-default); }
.fl-table tbody tr:hover > [data-pinned] { background: var(--fl-surface-hover); }
.fl-table tbody tr[aria-selected='true'] > [data-pinned] { background: var(--fl-surface-selected); }

/* Expandable rows. <tr data-expanded> is the open row; <tr class="fl-table__detail-row" data-detail> follows
   it, one cell spanning every column, holding <div class="fl-table__detail">. The two read as one unit on
   the subtle surface, with no rule between them. The detail keeps to the visible width of the table while
   its columns scroll sideways, so a table nested in it scrolls on its own. The wrap becomes a size
   container only when it holds a detail row, so no other table's sizing changes. */
.fl-table-wrap:has(.fl-table__detail) { container-type: inline-size; }
.fl-table tbody tr[data-expanded] > *, .fl-table tbody tr[data-expanded]:hover > * { background: var(--fl-surface-subtle); }
.fl-table tbody tr[data-detail] > td { padding: 0; border-block-start: 0; background: var(--fl-surface-subtle); }
.fl-table tbody tr[data-detail]:hover > td { background: var(--fl-surface-subtle); }
.fl-table__detail { position: sticky; inset-inline-start: 0; box-sizing: border-box; inline-size: 100cqi; max-inline-size: 100%; padding: var(--fl-space-3) var(--fl-space-4) var(--fl-space-4); white-space: normal; }
.fl-table__expand > svg { transition: transform var(--fl-duration-fast) var(--fl-ease-out); }
.fl-table__expand[aria-expanded='true'] > svg { transform: rotate(90deg); }
.fl-table__expand:dir(rtl) > svg { transform: scaleX(-1); }
.fl-table__expand:dir(rtl)[aria-expanded='true'] > svg { transform: rotate(90deg); }
/* A row that is present but not the point, such as a duplicate the import skipped. Its text recedes;
   its actions keep their own colours. */
.fl-table tbody tr[data-muted] > * { color: var(--fl-text-muted); }

/* Column filter, inside a column's menu below its sort items: search, values with counts, then Clear,
   Select all, and Apply. */
.fl-colfilter { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--fl-space-2); min-inline-size: 15rem; padding: var(--fl-space-2); }
.fl-colfilter__options { display: grid; gap: 2px; max-block-size: 14rem; overflow-y: auto; }
/* A long value truncates: the label is a flex row held to the menu's width, and only the value's text
   may shrink. The count never does. */
.fl-colfilter__options > .fl-checkbox { display: flex; min-inline-size: 0; min-block-size: 1.75rem; padding-inline: var(--fl-space-1); border-radius: var(--fl-radius-sm); }
.fl-colfilter__options > .fl-checkbox:hover { background: var(--fl-surface-hover); }
.fl-colfilter__option { display: flex; flex: 1; justify-content: space-between; gap: var(--fl-space-3); min-inline-size: 0; }
.fl-colfilter__option > :first-child { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fl-colfilter__option > [data-numeric] { flex: none; color: var(--fl-text-muted); font-family: var(--fl-font-mono); font-size: var(--fl-font-size-sm); font-variant-numeric: tabular-nums; }
.fl-colfilter__note { margin: 0; padding: var(--fl-space-2) var(--fl-space-1); color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); }
/* A date or time range: "On or after" above "On or before", always stacked, because a menu is narrow
   and a datetime input is wide. Each input takes the full width of the filter. */
.fl-colfilter__range { display: grid; gap: var(--fl-space-3); }
.fl-colfilter__range > .fl-field { gap: var(--fl-space-1); }
.fl-colfilter__range .fl-input { inline-size: 100%; }
.fl-colfilter__range > .fl-field__error, .fl-colfilter__range > .fl-colfilter__note { margin: 0; padding: 0; }
.fl-colfilter__actions { display: flex; justify-content: flex-end; gap: var(--fl-space-2); padding-block-start: var(--fl-space-2); border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }

/* Column settings dialog rows: grip, visibility with the column's group under its name, pin, move. */
.fl-colconfig { display: grid; gap: 2px; margin: var(--fl-space-3) 0 0; padding: 0; list-style: none; }
.fl-colconfig__row { display: flex; align-items: center; gap: var(--fl-space-1); padding-inline-end: var(--fl-space-1); border-radius: var(--fl-radius-sm); }
.fl-colconfig__row[draggable='true']:hover { background: var(--fl-surface-hover); }
.fl-colconfig__row[data-dragging] { opacity: 0.5; }
.fl-colconfig__grip { display: inline-flex; padding: var(--fl-space-1); color: var(--fl-text-muted); cursor: grab; }
.fl-colconfig__row:not([draggable='true']) .fl-colconfig__grip { visibility: hidden; }
.fl-colconfig__check { flex: 1; min-inline-size: 0; }
.fl-colconfig__name { display: grid; min-inline-size: 0; }
.fl-colconfig__name > small { color: var(--fl-text-muted); font-size: var(--fl-font-size-xs); }
.fl-colconfig__note { margin: var(--fl-space-3) 0 0; color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); }

/* Forced colours replace every background: a selected row would look like the others and the sort marks would vanish.
   A selected row takes an outline in the system highlight, and the marks are drawn in the text colour. */
@media (forced-colors: active) {
  .fl-table tbody tr[aria-selected='true'] { outline: var(--fl-focus-ring-width) solid Highlight; outline-offset: calc(var(--fl-focus-ring-width) * -1); }
  .fl-table__sort::after, .fl-table [aria-sort] > .fl-table__sort::after { forced-color-adjust: none; background: CanvasText; }
}

/* Row summaries: a table's rows at the compact tier, one record per row. Each row reads as a short card: a primary
   line with the identity and its figure, then one or two secondary lines of key facts. The identity is a real link,
   stretched over the row, so a pointer opens the record anywhere on the row and the keyboard reaches it; a selection
   checkbox and a row action sit above the stretch. Render it in place of the table below 40rem, from the same rows
   with the same sort, filter, and selection, and keep a sort control in the toolbar, because the headers are gone.
   <ul class="fl-summary" aria-label="Documents"><li class="fl-summary__row" data-selected>
     <label class="fl-checkbox fl-summary__select"><input type="checkbox" aria-label="Select Kestrel Freight"></label>
     <div class="fl-summary__body">
       <div class="fl-summary__line"><a class="fl-summary__link" href="…">Kestrel Freight</a><span data-numeric>1,164.00</span></div>
       <div class="fl-summary__line" data-secondary><span>Bill 2026-0412</span><span class="fl-badge" data-status="info">Submitted</span></div>
     </div>
     <span class="fl-summary__action">…menu button</span></li></ul> */
.fl-summary { margin: 0; padding: 0; list-style: none; border: var(--fl-border-width) solid var(--fl-border-subtle); border-radius: var(--fl-radius-lg); background: var(--fl-surface-card); overflow: hidden; }
.fl-summary__row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; gap: var(--fl-space-3); padding: var(--fl-space-3) var(--fl-space-4); color: var(--fl-text-body); }
.fl-summary__row:has(> .fl-summary__select) { grid-template-columns: auto minmax(0, 1fr); }
.fl-summary__row:has(> .fl-summary__action) { grid-template-columns: minmax(0, 1fr) auto; }
.fl-summary__row:has(> .fl-summary__select):has(> .fl-summary__action) { grid-template-columns: auto minmax(0, 1fr) auto; }
.fl-summary__row + .fl-summary__row { border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-summary__row:hover { background: var(--fl-surface-hover); }
.fl-summary__row[data-selected] { background: var(--fl-surface-selected); }
.fl-summary__row[data-muted] { color: var(--fl-text-muted); }
.fl-summary__row:has(.fl-summary__link:focus-visible) { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: calc(var(--fl-focus-ring-width) * -1); }
/* The checkbox sits on the first line's text, not the row's top edge. */
.fl-summary__select { position: relative; z-index: 1; margin-block-start: calc((1lh - 1rem) / 2); }
.fl-summary__action { position: relative; z-index: 1; display: flex; align-self: center; }
.fl-summary__body { display: grid; gap: var(--fl-space-1); min-inline-size: 0; }
.fl-summary__line { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--fl-space-3); min-inline-size: 0; font-size: var(--fl-font-size-md); }
.fl-summary__line > :first-child { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fl-summary__line > :last-child:not(:first-child) { justify-self: end; white-space: nowrap; }
.fl-summary__line[data-secondary] { color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); }
.fl-summary [data-numeric] { font-family: var(--fl-font-mono); font-variant-numeric: tabular-nums; }
.fl-summary__line:not([data-secondary]) [data-numeric] { color: var(--fl-text-strong); }
.fl-summary__link { color: var(--fl-text-strong); font-weight: var(--fl-font-weight-medium); text-decoration: none; }
.fl-summary__link::after { content: ''; position: absolute; inset: 0; }
.fl-summary__link:focus-visible { outline: none; }
.fl-summary__row[data-muted] .fl-summary__link { color: var(--fl-text-muted); }

/* Forced colours replace every background: a selected row takes an outline in the system highlight. */
@media (forced-colors: active) {
  .fl-summary__row[data-selected] { outline: var(--fl-focus-ring-width) solid Highlight; outline-offset: calc(var(--fl-focus-ring-width) * -1); }
}

/* Section tabs. Navigation between sections of a page: links, with aria-current on the active one.
   <nav class="fl-tabs" aria-label="Sections"><a class="fl-tabs__tab" aria-current="page" href="…">Overview</a>…</nav>
   Underline style. These are links, so they need no script. A tablist that swaps panels in place is
   a different component, with keyboard behaviour, and lives in the React layer. */
.fl-tabs { display: flex; gap: var(--fl-space-5); align-self: stretch; overflow-x: auto; }
.fl-tabs__tab { position: relative; display: inline-flex; align-items: center; gap: 6px; padding-block: var(--fl-space-2); color: var(--fl-text-body); font-size: var(--fl-font-size-md); font-weight: var(--fl-font-weight-medium); text-decoration: none; white-space: nowrap; }
.fl-tabs__tab:hover { color: var(--fl-text-strong); }
.fl-tabs__tab:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: 2px; border-radius: var(--fl-radius-sm); }
.fl-tabs__tab[aria-current='page'], .fl-tabs__tab[aria-selected='true'] { color: var(--fl-text-strong); }
.fl-tabs__tab[aria-current='page']::after, .fl-tabs__tab[aria-selected='true']::after { content: ''; position: absolute; inset-inline: 0; inset-block-end: 0; block-size: 2px; background: var(--fl-accent); }
.fl-tabs__count { color: var(--fl-text-muted); font-family: var(--fl-font-mono); font-size: var(--fl-font-size-xs); font-variant-numeric: tabular-nums; }

/* Tablist. Swaps panels in place. Same look as section tabs, different semantics and behaviour:
   <div class="fl-tabs" role="tablist" aria-label="…"><button class="fl-tabs__tab" role="tab" aria-selected="true" aria-controls="p1" id="t1">…</button>…</div>
   <div class="fl-tabs__panel" role="tabpanel" id="p1" aria-labelledby="t1">…</div>
   Arrow keys, Home, and End move between tabs. The React Tabs component and behaviors.js both do this. */
.fl-tabs__tab:is(button) { appearance: none; border: 0; background: none; font: inherit; font-size: var(--fl-font-size-md); font-weight: var(--fl-font-weight-medium); cursor: pointer; }
.fl-tabs[role='tablist'] { border-block-end: var(--fl-border-width) solid var(--fl-border-subtle); align-self: auto; }
.fl-tabs__panel { padding-block-start: var(--fl-space-5); }
.fl-tabs__panel[hidden] { display: none; }
.fl-tabs__panel:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: 4px; border-radius: var(--fl-radius-sm); }

/* Segmented control. Switches the rendering of one result set: table, cards, insights.
   <div class="fl-segmented" role="group" aria-label="View"><button aria-pressed="true">Table</button>…</div> */
.fl-segmented { display: inline-flex; padding: 2px; gap: 2px; border-radius: var(--fl-radius-md); background: var(--fl-surface-subtle); }
.fl-segmented > button { appearance: none; border: 0; min-block-size: 1.75rem; padding: 0 var(--fl-space-3); border-radius: var(--fl-radius-sm); background: transparent; color: var(--fl-text-body); font: inherit; font-size: var(--fl-font-size-sm); font-weight: var(--fl-font-weight-medium); cursor: pointer; }
.fl-segmented > button:hover { color: var(--fl-text-strong); }
.fl-segmented > button:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: 1px; }
.fl-segmented > button[aria-pressed='true'] { background: var(--fl-surface-raised); color: var(--fl-text-strong); box-shadow: 0 0 0 var(--fl-border-width) var(--fl-border-default); }

/* Forced colours replace every background: the current tab's underline and the pressed segment would vanish.
   The system's highlight marks them instead. */
@media (forced-colors: active) {
  .fl-tabs__tab[aria-current='page']::after, .fl-tabs__tab[aria-selected='true']::after { forced-color-adjust: none; background: Highlight; }
  .fl-segmented > button[aria-pressed='true'] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
}

/* Accordion. Native <details>, so it needs no script and works without one.
   <div class="fl-accordion"><details class="fl-accordion__item" name="faq"><summary>…</summary><div class="fl-accordion__body">…</div></details>…</div>
   Give the items one `name` to let only one stay open at a time. */
.fl-accordion { border-block: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-accordion__item + .fl-accordion__item { border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-accordion__item > summary { display: flex; align-items: center; justify-content: space-between; gap: var(--fl-space-4); padding-block: var(--fl-space-4); color: var(--fl-text-strong); font-size: var(--fl-font-size-base); font-weight: var(--fl-font-weight-medium); cursor: pointer; list-style: none; }
.fl-accordion__item > summary::-webkit-details-marker { display: none; }
.fl-accordion__item > summary::after { content: ''; flex: none; inline-size: 1.125rem; block-size: 1.125rem; background: var(--fl-text-muted); mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; transition: rotate var(--fl-duration-base) var(--fl-ease-out); }
.fl-accordion__item[open] > summary::after { rotate: 180deg; }
.fl-accordion__item > summary:hover { color: var(--fl-accent); }
.fl-accordion__item > summary:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: 2px; border-radius: var(--fl-radius-sm); }
.fl-accordion__body { padding-block-end: var(--fl-space-5); color: var(--fl-text-body); line-height: var(--fl-line-height-relaxed); max-inline-size: var(--fl-layout-measure); }
.fl-accordion__body > :first-child { margin-block-start: 0; }
.fl-accordion__body > :last-child { margin-block-end: 0; }

/* Modal. Native <dialog>, opened with showModal(), which gives the focus trap, Escape to close, and
   an inert page behind it for free. Use a modal only when the task needs interruption or protected
   focus. One of the few things that floats, so it takes the dialog shadow.
   <dialog class="fl-modal" data-size="sm|md|lg" aria-labelledby="t"><header class="fl-modal__header"><h2 id="t" class="fl-modal__title">…</h2>
     <button class="fl-button" data-variant="ghost" data-size="sm" data-icon-only aria-label="Close">×</button></header>
     <div class="fl-modal__body">…</div><footer class="fl-modal__footer">…</footer></dialog> */
.fl-modal { box-sizing: border-box; inline-size: min(32rem, calc(100vw - 2rem)); max-block-size: min(85dvh, 48rem); padding: 0; border: var(--fl-border-width) solid var(--fl-border-default); border-radius: var(--fl-radius-lg); background: var(--fl-surface-raised); color: var(--fl-text-body); box-shadow: var(--fl-shadow-dialog); overflow: hidden; }
.fl-modal[data-size='sm'] { inline-size: min(24rem, calc(100vw - 2rem)); }
.fl-modal[data-size='lg'] { inline-size: min(48rem, calc(100vw - 2rem)); }
.fl-modal[open] { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; opacity: 1; translate: 0 0; transition: opacity var(--fl-duration-base) var(--fl-ease-out), translate var(--fl-duration-base) var(--fl-ease-out); }
@starting-style { .fl-modal[open] { opacity: 0; translate: 0 0.5rem; } }
.fl-modal::backdrop { background: var(--fl-surface-overlay); }
.fl-modal__header { display: flex; align-items: center; justify-content: space-between; gap: var(--fl-space-4); padding: var(--fl-space-4) var(--fl-space-4) var(--fl-space-4) var(--fl-space-5); border-block-end: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-modal__title { margin: 0; color: var(--fl-text-strong); font-size: var(--fl-font-size-lg); font-weight: var(--fl-font-weight-semibold); letter-spacing: var(--fl-tracking-snug); }
.fl-modal__body { padding: var(--fl-space-5); overflow-y: auto; font-size: var(--fl-font-size-md); line-height: var(--fl-line-height-normal); }
.fl-modal__body > :first-child { margin-block-start: 0; }
.fl-modal__body > :last-child { margin-block-end: 0; }
.fl-modal__footer { display: flex; justify-content: flex-end; gap: var(--fl-space-2); padding: var(--fl-space-3) var(--fl-space-5); border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); background: var(--fl-surface-card); }

/* Empty, error, and loading states. A view is not done until it has all three.
   <div class="fl-state" data-status="danger"><svg class="fl-state__icon">…</svg><h3 class="fl-state__title">…</h3>
     <p class="fl-state__text">Say what happened, then how to recover.</p><div class="fl-state__actions">…</div></div>
   An empty state says how to fill it. An error names the problem and the way out. */
.fl-state { display: grid; justify-items: center; gap: var(--fl-space-2); padding: var(--fl-space-12) var(--fl-space-6); text-align: center; color: var(--fl-text-body); }
.fl-state[data-align='start'] { justify-items: start; text-align: start; padding-inline: 0; }
.fl-state__icon { inline-size: 1.75rem; block-size: 1.75rem; margin-block-end: var(--fl-space-2); color: var(--fl-text-muted); }
.fl-state[data-status='danger'] .fl-state__icon { color: var(--fl-status-danger-fg); }
.fl-state__title { margin: 0; color: var(--fl-text-strong); font-size: var(--fl-font-size-base); font-weight: var(--fl-font-weight-semibold); }
.fl-state__text { margin: 0; max-inline-size: 44ch; font-size: var(--fl-font-size-md); line-height: var(--fl-line-height-normal); }
.fl-state__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--fl-space-2); margin-block-start: var(--fl-space-3); }

/* A spinner for a short, local wait: a button, an inline fetch. */
.fl-spinner { display: inline-block; inline-size: 1.25rem; block-size: 1.25rem; border-radius: 50%; border: 2px solid var(--fl-border-default); border-block-start-color: var(--fl-accent); animation: fl-spin 0.7s linear infinite; }
.fl-spinner[data-size='lg'] { inline-size: 1.75rem; block-size: 1.75rem; }

/* A skeleton for a whole region. Shape it like the content that is coming, so nothing jumps.
   <div class="fl-skeleton" style="inline-size: 12rem"></div>  or  data-shape="line|block|circle" */
.fl-skeleton { display: block; block-size: 0.875rem; border-radius: var(--fl-radius-sm); background: linear-gradient(90deg, var(--fl-surface-subtle) 25%, var(--fl-surface-hover) 50%, var(--fl-surface-subtle) 75%) 0 0 / 200% 100%; animation: fl-shimmer 1.4s ease-in-out infinite; }
.fl-skeleton[data-shape='block'] { block-size: 6rem; border-radius: var(--fl-radius-lg); }
.fl-skeleton[data-shape='circle'] { inline-size: 2rem; block-size: 2rem; border-radius: 50%; }
@keyframes fl-shimmer { to { background-position: -200% 0; } }

/* Fact list. Facts inside a card: a label, its value, and an icon where one exists.
   <dl class="fl-facts" data-layout="inline|stacked" data-icons>
     <div class="fl-facts__item"><span class="fl-facts__icon" aria-hidden="true"><svg>…</svg></span>
       <dt class="fl-facts__label">Employees</dt><dd class="fl-facts__value" data-mono>201-500</dd></div>
   </dl>
   Inline puts the label in a fixed column beside the value, 7rem or data-label="wide" for 10rem. Stacked
   puts the label above the value, in data-columns="2" or "auto" columns. data-icons on the list opens
   the icon column; without it no column is reserved. data-density="compact" for dense review panels.
   data-divided draws a hairline between rows of a one-column list; a card needs it rarely.

   The list owns no outer spacing: the card owns its inset. It is its own size container, so inline
   rows stack and columns fold by the list's width, not the window's: the same card sits in a 21rem
   rail and a wide column. A size container takes its width from its parent and never from its
   content, so give the list a width in a shrink-to-fit parent, such as a popover.

   A value is words, or a number or identifier in mono (data-mono, or data-numeric as in a table).
   An absent value is words too, "Not recorded", never a dash; data-absent mutes it. A value may hold
   a link, a badge, or a copy button, and a .fl-facts__detail line under it. */
.fl-facts {
  --_label: 7rem; --_icon: 0px; --_label-pad: var(--fl-space-3); --_gap: var(--fl-space-3); --_inner: var(--fl-space-1); --_min: 12rem;
  --_track: [icon] var(--_icon) [label] calc(var(--_label) + var(--_label-pad)) [value] minmax(0, 1fr);
  container: fl-facts / inline-size;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--_gap) var(--fl-space-6); margin: 0;
  color: var(--fl-text-body); font-size: var(--fl-font-size-md); line-height: var(--fl-line-height-normal);
}
.fl-facts[data-label='wide'] { --_label: 10rem; }
.fl-facts[data-icons] { --_icon: calc(1rem + var(--fl-space-2)); }
.fl-facts[data-layout='stacked'] { --_gap: var(--fl-space-4); --_label-pad: 0px; --_track: [icon] var(--_icon) [label value] minmax(0, 1fr); }
.fl-facts[data-density='compact'] { --_gap: var(--fl-space-2); font-size: var(--fl-font-size-sm); }
.fl-facts[data-layout='stacked'][data-density='compact'] { --_gap: var(--fl-space-3); }
/* Two columns fold to one when a column would drop below 12rem; auto fits as many as hold 12rem. */
.fl-facts[data-columns='2'] { grid-template-columns: repeat(auto-fill, minmax(min(100%, max(var(--_min), (100% - var(--fl-space-6)) / 2)), 1fr)); }
.fl-facts[data-columns='auto'] { grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--_min)), 1fr)); }

.fl-facts__item { display: grid; grid-template-columns: var(--_track); align-items: baseline; align-content: start; row-gap: var(--_inner); min-inline-size: 0; }
.fl-facts__icon { grid-column: icon; align-self: start; display: flex; inline-size: 1rem; block-size: 1rem; margin-block-start: calc((1lh - 1rem) / 2); color: var(--fl-text-muted); }
.fl-facts__icon > svg { inline-size: 1rem; block-size: 1rem; }
.fl-facts__label { grid-column: label; padding-inline-end: var(--_label-pad); color: var(--fl-text-muted); overflow-wrap: break-word; }
.fl-facts__value { grid-column: value; margin: 0; min-inline-size: 0; color: var(--fl-text-strong); overflow-wrap: anywhere; }
.fl-facts__value[data-mono], .fl-facts__value[data-numeric] { font-family: var(--fl-font-mono); font-variant-numeric: tabular-nums; }
.fl-facts__value[data-absent] { color: var(--fl-text-muted); }
.fl-facts__detail { display: block; margin-block-start: var(--_inner); color: var(--fl-text-muted); font-family: var(--fl-font-sans); font-size: var(--fl-font-size-xs); }

.fl-facts[data-divided]:not([data-columns='2']):not([data-columns='auto']) > .fl-facts__item + .fl-facts__item { padding-block-start: var(--_gap); border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }

/* Narrow: an inline row stacks once the value column would drop below about 8rem. */
@container fl-facts (max-width: 16rem) {
  .fl-facts[data-layout='inline'] > .fl-facts__item, .fl-facts:not([data-layout]) > .fl-facts__item { --_label-pad: 0px; --_track: [icon] var(--_icon) [label value] minmax(0, 1fr); }
}
@container fl-facts (max-width: 20rem) {
  .fl-facts[data-label='wide']:not([data-layout='stacked']) > .fl-facts__item { --_label-pad: 0px; --_track: [icon] var(--_icon) [label value] minmax(0, 1fr); }
}

/* Application layout. Implements docs/guidelines/application-interface.md: where things go.
   Shell: a fixed navigation rail and a main column. The top bar belongs to the page, not the app.
   <div class="fl-shell"><nav class="fl-rail">…</nav><div class="fl-shell__main"><header class="fl-topbar">…</header><main class="fl-page">…</main></div></div>
   How each part gives way in a narrow, short, or forced-colour window is docs/guidelines/adaptive-behaviour.md.
   Media queries here use the tiers in tokens.source.mjs: medium is 60rem, short is 30rem tall. */
.fl-shell { --_rail: 14rem; display: grid; grid-template-columns: var(--_rail) minmax(0, 1fr); min-block-size: 100dvh; background: var(--fl-surface-page); color: var(--fl-text-body); }
.fl-shell[data-rail='collapsed'] { --_rail: 3.5rem; }
/* One explicit column, so no row, such as a top bar full of actions, can widen the page past the window. */
.fl-shell__main { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); min-inline-size: 0; }

.fl-rail { position: sticky; inset-block-start: 0; display: flex; flex-direction: column; gap: 2px; block-size: 100dvh; padding: var(--fl-space-3) var(--fl-space-2); border-inline-end: var(--fl-border-width) solid var(--fl-border-subtle); background: var(--fl-surface-card); overflow: hidden auto; }
.fl-rail__brand { display: flex; align-items: center; min-block-size: 2.5rem; padding-inline: var(--fl-space-2); margin-block-end: var(--fl-space-3); }
.fl-rail__item { display: flex; align-items: center; gap: var(--fl-space-3); min-block-size: 2.25rem; padding-inline: var(--fl-space-3); border-radius: var(--fl-radius-md); color: var(--fl-text-body); font-size: var(--fl-font-size-md); font-weight: var(--fl-font-weight-medium); text-decoration: none; white-space: nowrap; }
.fl-rail__item > svg { inline-size: 1.125rem; block-size: 1.125rem; flex: none; color: var(--fl-text-muted); }
.fl-rail__item:hover { background: var(--fl-surface-hover); color: var(--fl-text-strong); }
.fl-rail__item:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: -2px; }
/* The active destination takes the tinted wash, never a filled block. */
.fl-rail__item[aria-current='page'] { background: var(--fl-surface-selected); color: var(--fl-accent); }
.fl-rail__item[aria-current='page'] > svg { color: currentColor; }
.fl-rail__end { margin-block-start: auto; display: grid; gap: 2px; padding-block-start: var(--fl-space-3); border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-shell[data-rail='collapsed'] .fl-rail__item { justify-content: center; padding-inline: 0; }
.fl-shell[data-rail='collapsed'] .fl-rail__item > :not(svg), .fl-shell[data-rail='collapsed'] .fl-rail__brand > :not(:first-child) { display: none; }

/* One row at 3.5rem while it fits. When it does not, it wraps in source order, title first, rather than push the
   actions off the page, and the padding keeps the wrapped rows off its edges. */
.fl-topbar { position: sticky; inset-block-start: 0; z-index: var(--fl-layer-sticky); box-sizing: border-box; display: flex; flex-wrap: wrap; align-items: center; gap: var(--fl-space-2) var(--fl-space-6); min-block-size: 3.5rem; padding: var(--fl-space-2) var(--fl-space-6); border-block-end: var(--fl-border-width) solid var(--fl-border-subtle); background: var(--fl-surface-page); }
/* A long title truncates, so it never pushes the actions off the page. Put the full title in its title attribute. */
.fl-topbar__title { min-inline-size: 0; margin: 0; overflow: hidden; color: var(--fl-text-strong); font-size: var(--fl-font-size-lg); font-weight: var(--fl-font-weight-semibold); letter-spacing: var(--fl-tracking-snug); text-overflow: ellipsis; white-space: nowrap; }
.fl-topbar__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--fl-space-2); margin-inline-start: auto; }

/* List and table pages run edge to edge. Never centre a narrow column on them. */
.fl-page { padding: var(--fl-space-6); min-inline-size: 0; }
.fl-page[data-width='reading'] { max-inline-size: var(--fl-layout-measure); }

/* Browse page: filters, then results. <div class="fl-browse"><aside class="fl-filters">…</aside><section>…</section></div> */
.fl-browse { --_filters: 15rem; display: grid; grid-template-columns: var(--_filters) minmax(0, 1fr); gap: var(--fl-space-6); align-items: start; }
.fl-browse[data-filters='collapsed'] { grid-template-columns: minmax(0, 1fr); }
.fl-browse[data-filters='collapsed'] > .fl-filters { display: none; }
.fl-filters { position: sticky; inset-block-start: calc(3.5rem + var(--fl-space-6)); display: grid; gap: var(--fl-space-5); }
.fl-filters__group { display: grid; gap: var(--fl-space-2); }
.fl-filters__legend { color: var(--fl-text-strong); font-size: var(--fl-font-size-sm); font-weight: var(--fl-font-weight-semibold); }

/* Detail page: the record's primary content, then a side column of context.
   <div class="fl-detail"><div class="fl-detail__main">…</div><aside class="fl-detail__side" aria-label="…">…</aside></div>
   Write the primary content first: it is what the page is for, such as an expense's lines or a contact's
   profile. The side column holds context: sources, history, integrations. When the two no longer fit side
   by side, the side column moves below the primary content, never above it. The layout decides by its own
   width, not the window's, so beside an open rail it stacks as early as it must, and no media query can
   fall out of step with the shell's. Each column is a stack of cards; stacked, the side column lays its
   cards out in two columns when it has the room.
   data-side="start" puts the side column first at wide widths, for context read before the primary content,
   such as a receipt before its lines. The source, the keyboard order, and the stacked order keep the
   primary content first. Status, the primary action, and the figure the page is about belong in the page
   header, never only in the side column, because the side column is the part that moves. */
.fl-detail { --_side: 21rem; --_main: 32rem; display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--fl-space-6); }
.fl-detail[data-side='start'] { flex-direction: row-reverse; }
.fl-detail__main { display: grid; flex: 999 1 0; grid-template-columns: minmax(0, 1fr); align-content: start; gap: var(--fl-space-4); min-inline-size: min(100%, var(--_main)); }
.fl-detail__side { display: grid; flex: 1 1 var(--_side); grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); align-items: start; gap: var(--fl-space-4); min-inline-size: 0; }

/* Results header: a live count at the start, view tabs, then actions at the end. One row. */
.fl-toolbar { display: flex; align-items: center; gap: var(--fl-space-3); flex-wrap: wrap; min-block-size: 2.25rem; margin-block-end: var(--fl-space-3); }
.fl-toolbar__count { color: var(--fl-text-body); font-size: var(--fl-font-size-md); }
.fl-toolbar__count > [data-numeric] { color: var(--fl-text-strong); font-family: var(--fl-font-mono); font-variant-numeric: tabular-nums; font-weight: var(--fl-font-weight-medium); }
.fl-toolbar__end { display: flex; align-items: center; gap: var(--fl-space-2); margin-inline-start: auto; }

.fl-pagination { display: flex; align-items: center; justify-content: space-between; gap: var(--fl-space-3); padding-block-start: var(--fl-space-3); color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); }

/* Medium and narrower. The rail becomes a strip above the page: the strip takes the height it needs and the
   page the rest. The rail's end group, identity, sign-out, and the mode toggle, stays at the strip's end as
   icons, pinned there while the destinations scroll under it, because a person must never lose the way out.
   Each item keeps its name in a title, as RailItem sets it.
   The filter panel never stacks above the results, where it would push them below the fold. It overlays
   them from the start edge, over a scrim, and scrolls on its own. Start it collapsed at this width; the
   toolbar's Filters control, which shows the active count, opens it. The panel carries its own Close at its
   top, because the scrim covers the toolbar. A click on the scrim lands on .fl-browse itself, so close the
   panel when the event's target is the browse element. Escape closes it, and focus returns to Filters. */
@media (max-width: 60rem) {
  .fl-shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .fl-rail { position: static; flex-direction: row; align-items: center; block-size: auto; overflow: auto hidden; border-inline-end: 0; border-block-end: var(--fl-border-width) solid var(--fl-border-subtle); }
  .fl-rail__brand { display: none; }
  .fl-rail__end { position: sticky; inset-inline-end: 0; display: flex; align-items: center; gap: 2px; margin-block-start: 0; margin-inline-start: auto; padding-block-start: 0; padding-inline-start: var(--fl-space-2); border-block-start: 0; border-inline-start: var(--fl-border-width) solid var(--fl-border-subtle); background: var(--fl-surface-card); }
  .fl-rail__end .fl-rail__item { justify-content: center; min-inline-size: 2.25rem; padding-inline: 0; }
  .fl-rail__end .fl-rail__item > :not(svg) { display: none; }
  .fl-browse { grid-template-columns: minmax(0, 1fr); }
  .fl-browse:not([data-filters='collapsed']) > .fl-filters { position: fixed; inset-block: 0; inset-inline-start: 0; z-index: var(--fl-layer-overlay); box-sizing: border-box; inline-size: min(20rem, calc(100vw - var(--fl-space-12))); align-content: start; padding: var(--fl-space-4); overflow-y: auto; overscroll-behavior: contain; border-inline-end: var(--fl-border-width) solid var(--fl-border-default); background: var(--fl-surface-raised); box-shadow: var(--fl-shadow-dialog); }
  .fl-browse:not([data-filters='collapsed'])::before { content: ''; position: fixed; inset: 0; z-index: var(--fl-layer-overlay); background: var(--fl-surface-overlay); }
  .fl-topbar, .fl-page { padding-inline: var(--fl-space-4); }
}

/* A shell locked to the window, for a workspace whose regions scroll on their own, such as a list beside an open
   record: data-height="window". The page region scrolls; mark any other region that scrolls on its own with
   data-scroll. Never lock body overflow for this: the shell does it. In a short window a locked shell would leave
   the working surface a sliver, so below 30rem tall it releases: the whole page scrolls, every data-scroll region
   takes its natural height, and only a region marked data-stay="end" or "start", such as a footer that carries the
   primary action, stays in view. Mark one at most. */
.fl-shell[data-height='window'] { block-size: 100dvh; grid-template-rows: minmax(0, 1fr); overflow: hidden; }
@media (max-width: 60rem) {
  .fl-shell[data-height='window'] { grid-template-rows: auto minmax(0, 1fr); }
}
.fl-shell[data-height='window'] .fl-shell__main { min-block-size: 0; }
.fl-shell[data-height='window'] .fl-page { min-block-size: 0; overflow: auto; }
.fl-shell[data-height='window'] [data-scroll] { min-block-size: 0; overflow: auto; }

/* Short windows: a landscape phone, a laptop at 200% zoom. Sticky regions would take the height the task
   needs and hide the focused control, so the top bar and the filter panel scroll with the page. */
@media (max-height: 30rem) {
  .fl-topbar, .fl-filters { position: static; }
  .fl-shell[data-height='window'] { block-size: auto; min-block-size: 100dvh; overflow: visible; }
  .fl-shell[data-height='window'] .fl-page, .fl-shell[data-height='window'] [data-scroll] { flex: none; block-size: auto; max-block-size: none; overflow: visible; }
  .fl-shell[data-height='window'] [data-stay='end'] { position: sticky; inset-block-end: 0; z-index: var(--fl-layer-sticky); }
  .fl-shell[data-height='window'] [data-stay='start'] { position: sticky; inset-block-start: 0; z-index: var(--fl-layer-sticky); }
}

/* Forced colours replace every background, so the current destination would look like every other.
   The system's highlight pair marks it instead. */
@media (forced-colors: active) {
  .fl-rail__item[aria-current='page'] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  .fl-rail__item[aria-current='page']:focus-visible { outline-color: HighlightText; }
}

/* Logo. <span class="fl-logo" data-variant="wordmark">{the inline SVG}</span>
   Inline the SVG from the package's `logo` export so its fills follow the mode and the ink band.

   Size it with --fl-logo-size, which is the height of the artwork itself: the letters for the
   wordmark, the mark for the rope mark. It is never the height of the box. The wordmark's box is
   centred on the rope and so is half again as tall as the letters it holds, and that ratio is the
   package's to keep. A product that sets a height on the <svg> owns that geometry instead, and a
   change to it silently resizes their logo.

   data-fit="width" makes --fl-logo-size the width, for the rope mark in a narrow column: it is
   1.64 times as wide as it is tall, so given a height it runs past a rail and is clipped. */
.fl-logo { display: inline-flex; align-items: center; }
.fl-logo > svg { display: block; inline-size: auto; block-size: calc(var(--fl-logo-size, 1rem) * var(--_box, 1)); }
/* Kept in step with the artwork by a test, which recomputes it from the master and this file. */
.fl-logo[data-variant='wordmark'] { --_box: 1.5031; }
.fl-logo[data-fit='width'] > svg { inline-size: var(--fl-logo-size, 1rem); block-size: auto; }

/* Code block. <pre class="fl-code"><code>…</code></pre>
   For code, logs, bundle excerpts, and terminal output. It sits on --fl-surface-code and follows the
   mode like every other surface: a terminal is not a dark box on a light page. Mark a shell prompt
   with <span class="fl-code__prompt">$</span>, which is not copied with the command, and a comment
   with .fl-code__comment. Put what the reader is following, such as a session ID, in <mark>: base.css
   draws it with --fl-highlight-wash. A block that can scroll sideways takes tabindex="0" and an
   aria-label, so a keyboard can reach it. data-wrap wraps long lines instead. */
.fl-code {
  box-sizing: border-box;
  margin: 0;
  padding: var(--fl-space-4);
  border: var(--fl-border-width) solid var(--fl-border-subtle);
  border-radius: var(--fl-radius-md);
  background: var(--fl-surface-code);
  color: var(--fl-text-body);
  font-family: var(--fl-font-mono);
  font-size: var(--fl-font-size-sm);
  line-height: var(--fl-line-height-normal);
  font-variant-numeric: tabular-nums;
  white-space: pre;
  overflow-x: auto;
  tab-size: 2;
}
.fl-code > code { font: inherit; color: inherit; background: none; padding: 0; }
.fl-code[data-wrap] { white-space: pre-wrap; overflow-wrap: anywhere; }
.fl-code:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: var(--fl-focus-ring-offset); }
.fl-code__prompt { color: var(--fl-text-muted); user-select: none; }
.fl-code__comment { color: var(--fl-text-muted); }

/* Evidence: real output shown as proof on a website or in docs.
   <figure class="fl-evidence"><pre class="fl-code" tabindex="0" aria-label="…">…</pre><figcaption>…</figcaption></figure>
   Inside the block, text roles say what a span is, each on a token, and never a status colour for
   something that is not a status:
   .fl-code__ok      a passing result: success text
   .fl-code__bad     a failure: danger text, medium weight
   .fl-code__thread  the thing the reader follows, such as a session ID: the highlight colour. <mark>
                     marks the same thing with the highlight wash instead, for a value inside prose-like lines
   .fl-code__mask    a value the product removed: muted, on the subtle surface, so it reads as blanked
                     out rather than emphasised */
/* One column at the visible width. An automatic column would take the longest log line as its
   width, and the block could never scroll inside itself: the page would scroll instead. */
.fl-evidence { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--fl-space-2); align-content: start; min-inline-size: 0; margin: 0; }
.fl-evidence > figcaption { color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); line-height: var(--fl-line-height-normal); }
.fl-code__ok { color: var(--fl-status-success-fg); }
.fl-code__bad { color: var(--fl-status-danger-fg); font-weight: var(--fl-font-weight-medium); }
.fl-code__thread { color: var(--fl-highlight); font-weight: var(--fl-font-weight-medium); }
.fl-code__mask { padding-inline: 0.2em; border-radius: var(--fl-radius-sm); background: var(--fl-surface-subtle); color: var(--fl-text-muted); }

/* Menu. A list of actions on the native Popover API, which supplies the top layer, light dismiss, and Escape.
   <button class="fl-button" popovertarget="m" aria-haspopup="menu">Actions</button>
   <div class="fl-menu" id="m" popover role="menu" aria-label="Actions"><div class="fl-menu__label" role="presentation">Run</div>
     <button class="fl-menu__item" role="menuitem" tabindex="-1">Run again</button><a class="fl-menu__item" role="menuitem" tabindex="-1" href="…">Open report</a>
     <hr class="fl-menu__separator"><button class="fl-menu__item" role="menuitem" tabindex="-1" data-variant="danger">Delete run</button></div>
   Actions only: choosing a value is a select. The trigger is the implicit anchor. Where anchor positioning is
   missing, the menu rests at its place in the flow, so write it right after the trigger, and behaviors.js
   and the React Menu then place it. Both add arrow keys, Home, End, and typeahead. */
.fl-menu { --_pad: var(--fl-space-1); position: absolute; inset: auto; box-sizing: border-box; min-inline-size: 11rem; max-inline-size: min(20rem, calc(100vw - 1rem)); max-block-size: min(24rem, calc(100dvh - 1rem)); margin: var(--fl-space-1) 0; padding: var(--_pad); overflow-y: auto; border: var(--fl-border-width) solid var(--fl-border-default); border-radius: var(--fl-radius-lg); background: var(--fl-surface-raised); color: var(--fl-text-body); box-shadow: var(--fl-shadow-popover); z-index: var(--fl-layer-dropdown); font-size: var(--fl-font-size-md); }
.fl-menu:popover-open { display: grid; opacity: 1; translate: 0 0; transition: opacity var(--fl-duration-fast) var(--fl-ease-out), translate var(--fl-duration-fast) var(--fl-ease-out); }
@starting-style { .fl-menu:popover-open { opacity: 0; translate: 0 -0.25rem; } }
@supports (position-area: block-end) { .fl-menu { position-area: block-end span-inline-end; position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; } }
.fl-menu__item { appearance: none; box-sizing: border-box; display: flex; align-items: center; gap: var(--fl-space-2); inline-size: 100%; min-block-size: 2rem; padding: 0 var(--fl-space-3); border: 0; border-radius: var(--fl-radius-sm); background: transparent; color: var(--fl-text-strong); font: inherit; text-align: start; text-decoration: none; white-space: nowrap; cursor: pointer; user-select: none; }
.fl-menu__item:hover { background: var(--fl-surface-hover); }
.fl-menu__item:focus-visible { background: var(--fl-surface-hover); outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: calc(var(--fl-focus-ring-width) * -1); }
.fl-menu__item:disabled, .fl-menu__item[aria-disabled='true'] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
.fl-menu__item[data-variant='danger'] { color: var(--fl-status-danger-fg); }
.fl-menu__item > svg { inline-size: 1.15em; block-size: 1.15em; flex: none; }
.fl-menu__label { padding: var(--fl-space-2) var(--fl-space-3) var(--fl-space-1); color: var(--fl-text-muted); font-size: var(--fl-font-size-2xs); font-weight: var(--fl-font-weight-semibold); letter-spacing: var(--fl-tracking-wide); text-transform: uppercase; }
.fl-menu__separator { block-size: var(--fl-border-width); margin: var(--_pad) calc(var(--_pad) * -1); border: 0; background: var(--fl-border-subtle); }

/* Tooltip. A short hint for a control that already has a name. Never the only carrier of essential information:
   touch has no hover, so anything a person must know belongs in the label or in the page.
   <button class="fl-button" data-icon-only aria-label="Export" aria-describedby="tip">…</button>
   <span class="fl-tooltip" id="tip" role="tooltip" data-surface="ink" popover="manual">Export as CSV</span>
   It shows on hover and on keyboard focus, and hides on leave, blur, and Escape. behaviors.js and the React
   Tooltip do that, and place it. data-surface="ink" makes it the navy band in either mode, so its text pairs
   are the gated ones. popover="manual" lifts it into the top layer, above a modal and out of any clipping. */
.fl-tooltip { position: fixed; inset: auto; box-sizing: border-box; display: none; inline-size: max-content; max-inline-size: min(18rem, calc(100vw - 1rem)); margin: 0; padding: var(--fl-space-1) var(--fl-space-2); border: var(--fl-border-width) solid var(--fl-border-default); border-radius: var(--fl-radius-md); background: var(--fl-surface-raised); color: var(--fl-text-strong); box-shadow: var(--fl-shadow-popover); z-index: var(--fl-layer-toast); font-size: var(--fl-font-size-xs); font-weight: var(--fl-font-weight-medium); line-height: var(--fl-line-height-snug); text-align: start; white-space: normal; }
.fl-tooltip[data-open] { display: block; opacity: 1; transition: opacity var(--fl-duration-fast) var(--fl-ease-out); }
@starting-style { .fl-tooltip[data-open] { opacity: 0; } }

/* Toast. A brief message that floats over the page and leaves on its own. For the result of an action the person
   just took. Anything they must read or act on later belongs in a banner. One region per page, written once:
   <div class="fl-toast-region" role="region" aria-label="Notifications">
     <div class="fl-toast" data-status="success|danger|warning|info|neutral" role="status"><div class="fl-toast__content"><p class="fl-toast__title">…</p><p>…</p></div>
       <button class="fl-button fl-toast__action" data-variant="ghost" data-size="sm">Undo</button>
       <button class="fl-button fl-toast__close" data-variant="ghost" data-size="sm" data-icon-only aria-label="Dismiss"></button></div></div>
   role="alert" for danger, which also never leaves on its own. Same statuses and the same enforced icon as the
   badge and the banner. The close icon is drawn here too. toast() in behaviors.js and useToast in React build this. */
.fl-toast-region { position: fixed; inset-block-end: var(--fl-space-5); inset-inline-end: var(--fl-space-5); z-index: var(--fl-layer-toast); box-sizing: border-box; display: flex; flex-direction: column; gap: var(--fl-space-2); inline-size: min(24rem, calc(100vw - var(--fl-space-5) * 2)); pointer-events: none; }
.fl-toast {
  --_fg: var(--fl-status-neutral-fg);
  --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M8 12h8'/%3E%3C/svg%3E");
  position: relative;
  box-sizing: border-box;
  display: flex;
  align-items: flex-start;
  gap: var(--fl-space-2);
  padding: var(--fl-space-3) var(--fl-space-2) var(--fl-space-3) calc(var(--fl-space-4) + 1.25rem + var(--fl-space-3));
  border: var(--fl-border-width) solid var(--fl-border-default);
  border-radius: var(--fl-radius-lg);
  background: var(--fl-surface-raised);
  color: var(--fl-text-body);
  box-shadow: var(--fl-shadow-popover);
  font-size: var(--fl-font-size-md);
  line-height: var(--fl-line-height-normal);
  pointer-events: auto;
  opacity: 1;
  translate: 0 0;
  transition: opacity var(--fl-duration-base) var(--fl-ease-out), translate var(--fl-duration-base) var(--fl-ease-out);
}
@starting-style { .fl-toast { opacity: 0; translate: 0 0.5rem; } }
.fl-toast::before { content: ''; position: absolute; inset-inline-start: var(--fl-space-4); inset-block-start: calc(var(--fl-space-3) + 0.1em); inline-size: 1.25rem; block-size: 1.25rem; background: var(--_fg); mask: var(--_icon) center / contain no-repeat; -webkit-mask: var(--_icon) center / contain no-repeat; }
.fl-toast[data-leaving] { opacity: 0; translate: 0 0.5rem; }
.fl-toast__content { flex: 1; min-inline-size: 0; overflow-wrap: anywhere; }
.fl-toast__content > * { margin: 0; }
.fl-toast__content > * + * { margin-block-start: var(--fl-space-1); }
.fl-toast__title { color: var(--fl-text-strong); font-weight: var(--fl-font-weight-semibold); }
.fl-toast__action, .fl-toast__close { flex: none; margin-block: -0.2rem; }
.fl-toast .fl-toast__close { --_fg: var(--fl-text-muted); --_close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E"); }
.fl-toast__close::before { content: ''; inline-size: 1.15em; block-size: 1.15em; background: currentColor; mask: var(--_close) center / contain no-repeat; -webkit-mask: var(--_close) center / contain no-repeat; }
.fl-toast .fl-toast__close:hover, .fl-toast .fl-toast__close:focus-visible { --_fg: var(--fl-text-strong); }
.fl-toast[data-status='success'] { --_fg: var(--fl-status-success-fg); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m9 12 2 2 4-4'/%3E%3C/svg%3E"); }
.fl-toast[data-status='danger'] { --_fg: var(--fl-status-danger-fg); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='m15 9-6 6'/%3E%3Cpath d='m9 9 6 6'/%3E%3C/svg%3E"); }
.fl-toast[data-status='warning'] { --_fg: var(--fl-status-warning-fg); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/%3E%3Cpath d='M12 9v4'/%3E%3Cpath d='M12 17h.01'/%3E%3C/svg%3E"); }
.fl-toast[data-status='info'] { --_fg: var(--fl-status-info-fg); --_icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4'/%3E%3Cpath d='M12 8h.01'/%3E%3C/svg%3E"); }

/* Forced colours replace every background, and both icons are backgrounds seen through a mask. Draw them in the text
   colours, or the close button is an empty box. */
@media (forced-colors: active) {
  .fl-toast::before { forced-color-adjust: none; background: CanvasText; }
  .fl-toast__close::before { forced-color-adjust: none; background: ButtonText; }
}

/* Website chrome: the container, the site header, the site footer, and the scroll hint.
   For websites only. An application uses the shell, the rail, and the top bar in layout.css.

   Container. <div class="fl-container">. It owns the page gutters and nothing inside it adds inline
   padding of its own: 48 pixels, 32 below 1180, 20 below 680. */
.fl-container { box-sizing: border-box; inline-size: 100%; max-inline-size: var(--fl-layout-wide); margin-inline: auto; padding-inline: var(--fl-space-12); }

/* Site header.
   <header class="fl-siteheader">
     <div class="fl-container fl-siteheader__row">
       <a class="fl-siteheader__brand" href="/">{logo}</a>
       <nav class="fl-siteheader__nav" aria-label="Primary">
         <div class="fl-siteheader__group">{a product lockup}<a class="fl-siteheader__link" aria-current="page">Docs</a>…</div>
         <div class="fl-siteheader__group"><a class="fl-siteheader__link">Platform</a></div>
       </nav>
       <div class="fl-siteheader__actions">{mode toggle}<a class="fl-button" data-variant="primary" data-hide-narrow>Add to Chrome. Free</a></div>
       <details class="fl-siteheader__menu"><summary class="fl-button" data-variant="ghost" data-icon-only aria-label="Menu">{menu icon}</summary>
         <nav class="fl-siteheader__panel" aria-label="Primary">{the same groups}</nav></details>
     </div>
   </header>
   Groups are separated by a hairline, so the links beside a product's name read as its menu. A group
   may hold a dropdown: <details class="fl-siteheader__drop"><summary class="fl-siteheader__link">Products</summary>
   <div class="fl-siteheader__drop-panel">{links}</div></details>. Below 1000 the nav folds into the menu,
   which repeats the groups. Below 680 an action with data-hide-narrow leaves the header, because the
   logo must keep its size on a phone. */
.fl-siteheader { border-block-end: var(--fl-border-width) solid var(--fl-border-default); background: var(--fl-surface-page); color: var(--fl-text-body); }
.fl-siteheader__row { display: flex; align-items: center; gap: var(--fl-space-8); min-block-size: 4rem; }
.fl-siteheader__brand { display: inline-flex; align-items: center; flex: none; color: var(--fl-text-strong); text-decoration: none; }
.fl-siteheader__brand:focus-visible, .fl-siteheader__link:focus-visible, .fl-siteheader__panel a:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: var(--fl-focus-ring-offset); border-radius: var(--fl-radius-sm); }
.fl-siteheader__nav { display: flex; align-items: center; min-inline-size: 0; }
.fl-siteheader__group { display: flex; align-items: center; gap: var(--fl-space-6); }
.fl-siteheader__group + .fl-siteheader__group { margin-inline-start: var(--fl-space-6); padding-inline-start: var(--fl-space-6); border-inline-start: var(--fl-border-width) solid var(--fl-border-default); }
.fl-siteheader__link { display: inline-flex; align-items: center; gap: var(--fl-space-1); color: var(--fl-text-body); font-size: var(--fl-font-size-md); text-decoration: none; white-space: nowrap; cursor: pointer; }
.fl-siteheader__link:hover { color: var(--fl-text-strong); }
/* The current page: the strong colour and an accent underline, which is current selection. */
.fl-siteheader__link[aria-current='page'] { color: var(--fl-text-strong); text-decoration: underline; text-decoration-color: var(--fl-accent); text-decoration-thickness: 2px; text-underline-offset: 0.45em; }
.fl-siteheader__actions { display: flex; align-items: center; gap: var(--fl-space-3); margin-inline-start: auto; }

.fl-siteheader__drop { position: relative; }
.fl-siteheader__drop > summary { list-style: none; }
.fl-siteheader__drop > summary::-webkit-details-marker { display: none; }
.fl-siteheader__drop > summary::after { content: ''; inline-size: 0.875rem; block-size: 0.875rem; background: currentColor; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; transition: transform var(--fl-duration-fast) var(--fl-ease-out); }
.fl-siteheader__drop[open] > summary::after { transform: rotate(180deg); }
.fl-siteheader__drop-panel, .fl-siteheader__panel { position: absolute; z-index: var(--fl-layer-dropdown); inset-block-start: calc(100% + var(--fl-space-2)); display: grid; gap: 2px; min-inline-size: 13rem; padding: var(--fl-space-2); border: var(--fl-border-width) solid var(--fl-border-default); border-radius: var(--fl-radius-lg); background: var(--fl-surface-raised); box-shadow: var(--fl-shadow-popover); }
.fl-siteheader__drop-panel { inset-inline-start: 0; }
.fl-siteheader__drop-panel a, .fl-siteheader__panel a { display: flex; align-items: center; gap: var(--fl-space-3); padding: var(--fl-space-2) var(--fl-space-3); border-radius: var(--fl-radius-sm); color: var(--fl-text-body); font-size: var(--fl-font-size-md); text-decoration: none; }
.fl-siteheader__drop-panel a:hover, .fl-siteheader__panel a:hover { background: var(--fl-surface-hover); color: var(--fl-text-strong); }
.fl-siteheader__drop-panel a:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: -2px; }
.fl-siteheader__drop-panel a[aria-current='page'], .fl-siteheader__panel a[aria-current='page'] { background: var(--fl-surface-selected); color: var(--fl-accent); }

.fl-siteheader__menu { display: none; position: relative; }
.fl-siteheader__menu > summary { list-style: none; }
.fl-siteheader__menu > summary::-webkit-details-marker { display: none; }
.fl-siteheader__menu[open] > summary { background: var(--fl-surface-hover); }
.fl-siteheader__panel { inset-inline-end: 0; min-inline-size: 15rem; }
/* In the menu each group stacks, and the hairline between groups turns horizontal. */
.fl-siteheader__panel .fl-siteheader__group { flex-direction: column; align-items: stretch; gap: 2px; }
.fl-siteheader__panel .fl-siteheader__group + .fl-siteheader__group { margin: var(--fl-space-1) 0 0; padding: var(--fl-space-1) 0 0; border-inline-start: 0; border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-siteheader__panel .fl-siteheader__drop-panel { position: static; min-inline-size: 0; padding: 0; border: 0; box-shadow: none; background: none; }

/* Site footer. Put it on the ink band: <footer class="fl-sitefooter" data-surface="ink">.
   <div class="fl-container"><div class="fl-sitefooter__grid">
     <div class="fl-sitefooter__brand">{logo}<p>…</p></div>
     <nav class="fl-sitefooter__col" aria-label="Recorder"><p class="fl-sitefooter__heading">Recorder</p><a>…</a><p class="fl-sitefooter__heading" data-sub>Platform legal</p>…</nav>
   </div><div class="fl-sitefooter__floor"><span>© 2026 Allyx, Inc.</span><span>…</span></div></div>
   The floor's rule sits inside the container's content box, so it starts where the text starts. */
.fl-sitefooter { padding-block: var(--fl-space-12) var(--fl-space-6); font-size: var(--fl-font-size-md); }
.fl-sitefooter__grid { display: grid; grid-template-columns: minmax(0, 2fr) repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--fl-space-10); }
.fl-sitefooter__brand, .fl-sitefooter__col { display: flex; flex-direction: column; align-items: flex-start; gap: var(--fl-space-2); }
.fl-sitefooter__brand p { max-inline-size: 40ch; margin: 0; color: var(--fl-text-muted); }
.fl-sitefooter__heading { margin: 0 0 var(--fl-space-1); color: var(--fl-text-strong); font-weight: var(--fl-font-weight-semibold); }
.fl-sitefooter__heading[data-sub] { margin-block-start: var(--fl-space-4); }
.fl-sitefooter__col a { color: var(--fl-text-body); text-decoration: none; }
.fl-sitefooter__col a:hover { color: var(--fl-text-strong); text-decoration: underline; }
.fl-sitefooter__col a:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: var(--fl-focus-ring-offset); border-radius: var(--fl-radius-sm); }
.fl-sitefooter__floor { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--fl-space-2) var(--fl-space-4); margin-block-start: var(--fl-space-10); padding-block-start: var(--fl-space-5); border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); }

/* Scroll hint, for a table or a drawing that scrolls sideways inside its own frame. Hidden until the
   width at which that happens: data-show="below-desktop" (1180) or data-show="phone" (680).
   <p class="fl-scroll-hint" data-show="phone">{arrow icon}Swipe the diagram</p> */
.fl-scroll-hint { display: none; align-items: center; gap: var(--fl-space-2); margin: 0 0 var(--fl-space-3); color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); }
.fl-scroll-hint > svg { inline-size: 1rem; block-size: 1rem; flex: none; }

@media (max-width: 73.75rem) {
  .fl-container { padding-inline: var(--fl-space-8); }
  .fl-scroll-hint[data-show='below-desktop'] { display: flex; }
}
@media (max-width: 62.5rem) {
  .fl-siteheader__nav { display: none; }
  .fl-siteheader__menu { display: block; }
  .fl-sitefooter__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 42.5rem) {
  .fl-container { padding-inline: var(--fl-space-5); }
  .fl-siteheader__row { min-block-size: 3.5rem; gap: var(--fl-space-3); }
  .fl-siteheader__actions { gap: var(--fl-space-2); }
  .fl-siteheader__actions [data-hide-narrow] { display: none; }
  .fl-sitefooter__grid { grid-template-columns: minmax(0, 1fr); gap: var(--fl-space-8); }
  .fl-sitefooter__floor { flex-direction: column; }
  .fl-scroll-hint[data-show='phone'] { display: flex; }
}

/* Section, for website pages.
   <section class="fl-section" data-layout="split|stack|bare|ledger" aria-labelledby="x-title">
     <div class="fl-container fl-section__inner">
       <header class="fl-section__head"><h2 class="fl-section__title" id="x-title">…</h2><p class="fl-section__lede">…</p></header>
       <div class="fl-section__body">…</div>
     </div>
   </section>
   The section owns its block padding and the container owns the gutters, so nothing inside adds
   either. split puts the heading column beside the body, 5 to 7, or 8 to 4 with data-ratio="8-4".
   stack puts it above, within the measure. bare has a body only. ledger is split with the column
   rule drawn by the section: the padding moves into the columns so the rule runs the full height,
   and consecutive ledger sections draw one unbroken line down the page. Every split stacks below
   900, heading first. Sections are divided by colour: put data-surface="tint" on a section to give it
   the section tone, a mist blue in light and a deeper navy in dark, and alternate it with the page.
   The ink band is the one strong navy section, the accent band the one filled one. Where two
   sections of the same ground meet, divide them with a hairline, data-divider, or the heavier
   control rule, data-divider="strong", before a page's close. */
.fl-section { --_pad: var(--fl-space-16); padding-block: var(--_pad); color: var(--fl-text-body); }
.fl-section[data-layout='bare'] { --_pad: var(--fl-space-12); }
.fl-section[data-divider] { border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-section[data-divider='strong'] { border-block-start-color: var(--fl-border-control); }
.fl-section__inner { display: grid; gap: var(--fl-space-10) var(--fl-space-16); }
.fl-section[data-layout='split'] > .fl-section__inner, .fl-section[data-layout='ledger'] > .fl-section__inner { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: start; }
.fl-section[data-ratio='8-4'] > .fl-section__inner { grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); }
.fl-section__head { display: grid; gap: var(--fl-space-4); align-content: start; }
.fl-section[data-layout='stack'] .fl-section__head { max-inline-size: 52rem; }
.fl-section__title { margin: 0; color: var(--fl-text-strong); font-size: var(--fl-font-size-3xl); font-weight: var(--fl-font-weight-semibold); line-height: var(--fl-line-height-snug); letter-spacing: var(--fl-tracking-tight); text-wrap: balance; }
.fl-section__lede { margin: 0; max-inline-size: var(--fl-layout-measure); color: var(--fl-text-muted); font-size: var(--fl-font-size-base); text-wrap: pretty; }
.fl-section__body { min-inline-size: 0; }

/* The ledger's rule sits on the body column's edge and spans the section's full height. */
.fl-section[data-layout='ledger'] { padding-block: 0; }
.fl-section[data-layout='ledger'] > .fl-section__inner { align-items: stretch; column-gap: 0; }
.fl-section[data-layout='ledger'] .fl-section__head { padding-block: var(--_pad); padding-inline-end: var(--fl-space-12); }
.fl-section[data-layout='ledger'] .fl-section__body { padding-block: var(--_pad); padding-inline-start: var(--fl-space-12); border-inline-start: var(--fl-border-width) solid var(--fl-border-default); }

@media (max-width: 73.75rem) {
  .fl-section__inner { column-gap: var(--fl-space-10); }
  .fl-section[data-layout='ledger'] .fl-section__head { padding-inline-end: var(--fl-space-8); }
  .fl-section[data-layout='ledger'] .fl-section__body { padding-inline-start: var(--fl-space-8); }
}
@media (max-width: 56.25rem) {
  .fl-section[data-layout='split'] > .fl-section__inner, .fl-section[data-layout='ledger'] > .fl-section__inner, .fl-section[data-ratio='8-4'] > .fl-section__inner { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the ledger has no second column to rule, so it becomes an ordinary section. */
  .fl-section[data-layout='ledger'] { padding-block: var(--_pad); }
  .fl-section[data-layout='ledger'] .fl-section__head, .fl-section[data-layout='ledger'] .fl-section__body { padding: 0; border: 0; }
}
@media (max-width: 42.5rem) {
  .fl-section { --_pad: var(--fl-space-10); }
  .fl-section[data-layout='bare'] { --_pad: var(--fl-space-8); }
  .fl-section__title { font-size: var(--fl-font-size-2xl); }
}

/* Product lockup. How a product is named anywhere: its icon, its name in the brand face, and its
   status when that matters. <span class="fl-product-lockup" data-size="sm|md|lg">
   <img class="fl-product-lockup__icon" src="…/recorder-icon.svg" alt=""><span class="fl-product-name">Recorder</span>
   <span class="fl-badge" data-status="neutral">In development</span></span>
   On a system surface, which includes the platform pages, the Control Plane, and the CRM, a product is
   told apart by this lockup and never by its colour: the lockup carries no accent. The icon is
   decorative beside the name, so its alt is empty. Only the Recorder has an icon today; the others
   render name and status alone until theirs exist. `products` in the package lists every product. */
.fl-product-lockup { --_size: var(--fl-font-size-base); display: inline-flex; align-items: center; gap: 0.5em; color: var(--fl-text-strong); font-size: var(--_size); line-height: 1.2; white-space: nowrap; }
.fl-product-lockup[data-size='sm'] { --_size: var(--fl-font-size-md); }
.fl-product-lockup[data-size='lg'] { --_size: var(--fl-font-size-xl); }
.fl-product-lockup__icon { display: block; inline-size: 1.25em; block-size: 1.25em; flex: none; }
.fl-product-lockup > .fl-badge { margin-inline-start: 0.25em; }

/* Product list. The products a page points to, grouped by where each one is.
   <section class="fl-product-list" aria-labelledby="g1"><h3 class="fl-product-list__heading" id="g1">Available</h3>
   <ul class="fl-product-list__items"><li><a class="fl-product-list__item" href="/products/fix/">{lockup}
   <span class="fl-product-list__summary">…</span><span class="fl-product-list__more">Read more</span></a></li></ul></section>
   Rows are separated by hairlines. data-density="compact" drops the summary to one line and the row
   height with it, for products in development or a "more from Fairlead" list. */
.fl-product-list { display: grid; gap: var(--fl-space-3); }
.fl-product-list__heading { margin: 0; color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); font-weight: var(--fl-font-weight-medium); }
.fl-product-list__items { display: grid; margin: 0; padding: 0; list-style: none; border-block-start: var(--fl-border-width) solid var(--fl-border-default); }
.fl-product-list__items > li { border-block-end: var(--fl-border-width) solid var(--fl-border-subtle); }
.fl-product-list__item { display: grid; grid-template-columns: minmax(12rem, 3fr) minmax(0, 7fr) auto; gap: var(--fl-space-2) var(--fl-space-8); align-items: baseline; padding-block: var(--fl-space-5); color: var(--fl-text-body); text-decoration: none; }
.fl-product-list__item:hover .fl-product-list__more { color: var(--fl-text-strong); text-decoration: underline; }
.fl-product-list__item:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: var(--fl-focus-ring-offset); border-radius: var(--fl-radius-sm); }
.fl-product-list__summary { color: var(--fl-text-body); font-size: var(--fl-font-size-base); text-wrap: pretty; }
.fl-product-list__more { color: var(--fl-text-link); font-size: var(--fl-font-size-md); white-space: nowrap; }
.fl-product-list[data-density='compact'] .fl-product-list__item { padding-block: var(--fl-space-3); }
.fl-product-list[data-density='compact'] .fl-product-list__summary { color: var(--fl-text-muted); font-size: var(--fl-font-size-md); }

@media (max-width: 42.5rem) {
  .fl-product-list__item { grid-template-columns: minmax(0, 1fr); gap: var(--fl-space-2); }
}

/* Stat. One figure: <div class="fl-stat" data-size="md|lg"><p class="fl-stat__value">72</p>
   <p class="fl-stat__label">of 332 tests run on this pull request</p><p class="fl-stat__source">Sample repository</p></div>
   The value is a number, set in mono with tabular figures, because mono carries data. Words go in
   the label: "Custom", "About a week", and "Never once" are labels or sentences, not values. Every
   figure has a source, or says it is a sample or a target; the source line may hold a link.

   A row of figures: <div class="fl-stats" data-columns="2|3|4">…</div>. Choose the column count so the
   grid has exactly as many cells as figures. Cells are separated by hairlines, never an accent
   stripe. Below 680 every row becomes one column. */
.fl-stat { display: grid; gap: var(--fl-space-2); align-content: start; min-inline-size: 0; }
.fl-stat__value { margin: 0; color: var(--fl-text-strong); font-family: var(--fl-font-mono); font-size: var(--fl-font-size-3xl); font-weight: var(--fl-font-weight-medium); font-variant-numeric: tabular-nums; line-height: var(--fl-line-height-tight); letter-spacing: var(--fl-tracking-tight); }
.fl-stat[data-size='md'] .fl-stat__value { font-size: var(--fl-font-size-2xl); }
.fl-stat[data-size='lg'] .fl-stat__value { font-size: var(--fl-font-size-5xl); }
.fl-stat__label { margin: 0; max-inline-size: 32ch; color: var(--fl-text-body); font-size: var(--fl-font-size-base); line-height: var(--fl-line-height-normal); }
.fl-stat__source { margin: 0; color: var(--fl-text-muted); font-size: var(--fl-font-size-xs); line-height: var(--fl-line-height-normal); }

.fl-stats { --_cols: 3; display: grid; grid-template-columns: repeat(var(--_cols), minmax(0, 1fr)); gap: var(--fl-space-8); }
.fl-stats[data-columns='2'] { --_cols: 2; }
.fl-stats[data-columns='4'] { --_cols: 4; }
.fl-stats > .fl-stat { padding-block-start: var(--fl-space-5); border-block-start: var(--fl-border-width) solid var(--fl-border-subtle); }

@media (max-width: 56.25rem) {
  .fl-stats[data-columns='4'] { --_cols: 2; }
}
@media (max-width: 42.5rem) {
  .fl-stats { --_cols: 1; }
  .fl-stat[data-size='lg'] .fl-stat__value { font-size: var(--fl-font-size-4xl); }
}

/* Diagram. A drawing of a real mechanism, in inline SVG, coloured only through these roles so it
   follows the mode, the theme, and the ink band.
   <figure class="fl-diagram" data-fig="wide|narrow">
     <p class="fl-scroll-hint" data-show="phone">…Swipe the diagram</p>
     <svg viewBox="…" role="img" aria-label="{the claim the drawing makes, in one sentence}">…</svg>
     <figcaption>…</figcaption>
   </figure>
   A drawing that needs a different layout in a narrow column is drawn twice: data-fig="wide" shows
   above 900 and data-fig="narrow" below. The figure is one column at the visible width, so a drawing
   wider than a phone scrolls inside it while the caption still wraps. One caption per figure: the
   aria-label states the claim, the caption explains it, and nothing inside the drawing repeats it.

   Roles, as classes on SVG elements:
   fl-d-node        a thing: raised fill, control outline
   fl-d-node        with data-emphasis, the thing the drawing is about: accent wash, accent outline
   fl-d-group       a boundary around nodes: no fill, default outline
   fl-d-missing     something that does not exist: inset fill, dashed control outline
   fl-d-edge        a connection: control stroke. data-emphasis for the path that matters, in accent.
                    data-missing for one that does not exist, dashed
   fl-d-thread      the one thing a reader follows across the drawing, such as a session ID: the
                    highlight stroke. fl-d-thread-node is a node on it, on the highlight wash
   fl-d-arrow       a marker head, filled to match its edge: add data-emphasis or data-thread
   fl-d-cross       a struck path, drawn in the danger colour because it is a real negation
   fl-d-title       a node's name: strong, sans, medium
   fl-d-label       running words: body, sans
   fl-d-note        secondary words: muted, sans
   fl-d-data        a value, identifier, path, or file name: muted, mono. Never words
   fl-d-on-accent   text on a filled accent bar
   Status colours never decorate a drawing. A warning hue on a thread says something is wrong with it. */
.fl-diagram { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--fl-space-4); margin: 0; overflow-x: auto; }
.fl-diagram > svg { display: block; inline-size: 100%; block-size: auto; min-inline-size: 40rem; }
.fl-diagram[data-fig='narrow'] { display: none; }
.fl-diagram > figcaption { max-inline-size: 60rem; color: var(--fl-text-muted); font-size: var(--fl-font-size-sm); line-height: var(--fl-line-height-normal); }
.fl-diagram:focus-visible { outline: var(--fl-focus-ring-width) solid var(--fl-focus-ring); outline-offset: var(--fl-focus-ring-offset); }

.fl-diagram .fl-d-node { fill: var(--fl-surface-raised); stroke: var(--fl-border-control); }
.fl-diagram .fl-d-node[data-emphasis] { fill: var(--fl-accent-wash); stroke: var(--fl-accent); }
.fl-diagram .fl-d-group { fill: none; stroke: var(--fl-border-default); }
.fl-diagram .fl-d-missing { fill: var(--fl-surface-inset); stroke: var(--fl-border-control); stroke-dasharray: 5 5; }
.fl-diagram .fl-d-edge { fill: none; stroke: var(--fl-border-control); }
.fl-diagram .fl-d-edge[data-emphasis] { stroke: var(--fl-accent); }
.fl-diagram .fl-d-edge[data-missing] { stroke-dasharray: 5 5; }
.fl-diagram .fl-d-thread { fill: none; stroke: var(--fl-highlight); }
.fl-diagram .fl-d-thread-node { fill: var(--fl-highlight-wash); stroke: var(--fl-highlight); }
.fl-diagram .fl-d-arrow { fill: var(--fl-border-control); stroke: none; }
.fl-diagram .fl-d-arrow[data-emphasis] { fill: var(--fl-accent); }
.fl-diagram .fl-d-arrow[data-thread] { fill: var(--fl-highlight); }
.fl-diagram .fl-d-cross { fill: none; stroke: var(--fl-status-danger-fg); }

.fl-diagram .fl-d-title { fill: var(--fl-text-strong); font-family: var(--fl-font-sans); font-weight: var(--fl-font-weight-medium); }
.fl-diagram .fl-d-label { fill: var(--fl-text-body); font-family: var(--fl-font-sans); }
.fl-diagram .fl-d-note { fill: var(--fl-text-muted); font-family: var(--fl-font-sans); }
.fl-diagram .fl-d-data { fill: var(--fl-text-muted); font-family: var(--fl-font-mono); }
.fl-diagram .fl-d-thread-text { fill: var(--fl-highlight); font-family: var(--fl-font-mono); }
.fl-diagram .fl-d-on-accent { fill: var(--fl-on-accent); font-family: var(--fl-font-sans); }

@media (max-width: 56.25rem) {
  .fl-diagram[data-fig='wide'] { display: none; }
  .fl-diagram[data-fig='narrow'] { display: grid; }
  .fl-diagram > svg { min-inline-size: 0; }
}
@media (max-width: 42.5rem) {
  .fl-diagram[data-fig='narrow'] > svg { min-inline-size: 29rem; }
}

/* Steps. An ordered process: onboarding phases, a product's stages, a lifecycle.
   <ol class="fl-steps">
     <li class="fl-steps__item">
       <p class="fl-steps__head"><span class="fl-steps__label">Reproduce</span><span class="fl-steps__meta">About a week</span></p>
       <div class="fl-steps__body">…one or two sentences…</div>
       <div class="fl-steps__figure">…an optional drawing or evidence…</div>
     </li>
   </ol>
   Each step is named by its verb, not a number: the list is ordered, and the order is the only thing a
   number would add. One line joins the steps down the left, through a quiet marker on each; the last
   step's line stops at its marker. No numbered circles, no accent stripes, no card around a step.
   A duration is words, so it is set in the sans face, muted. */
.fl-steps { --_marker: 0.5rem; --_rail: 1.5rem; --_mid: calc(var(--fl-font-size-lg) * 0.75); display: grid; margin: 0; padding: 0; list-style: none; }
.fl-steps__item { position: relative; display: grid; gap: var(--fl-space-2); padding-inline-start: calc(var(--_rail) + var(--fl-space-4)); padding-block-end: var(--fl-space-8); }
.fl-steps__item:last-child { padding-block-end: 0; }
/* The joining line, from this marker to the next. */
.fl-steps__item::before { content: ''; position: absolute; inset-inline-start: calc(var(--_rail) / 2 - var(--fl-border-width) / 2); inset-block: var(--_mid) 0; inline-size: var(--fl-border-width); background: var(--fl-border-default); }
.fl-steps__item:last-child::before { display: none; }
/* The marker sits on the middle of the label's first line: the label is lg at a 1.5 line height,
   so its middle is three quarters of the lg size down. A solid dot, so it needs no background to
   hide the line and works on any surface. */
.fl-steps__item::after { content: ''; position: absolute; inset-inline-start: calc(var(--_rail) / 2 - var(--_marker) / 2); inset-block-start: calc(var(--_mid) - var(--_marker) / 2); inline-size: var(--_marker); block-size: var(--_marker); border-radius: var(--fl-radius-full); background: var(--fl-border-control); }
.fl-steps__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--fl-space-1) var(--fl-space-3); margin: 0; font-size: var(--fl-font-size-lg); line-height: 1.5; }
.fl-steps__label { color: var(--fl-text-strong); font-weight: var(--fl-font-weight-semibold); }
.fl-steps__meta { color: var(--fl-text-muted); font-size: var(--fl-font-size-md); }
.fl-steps__body { max-inline-size: var(--fl-layout-measure); color: var(--fl-text-body); }
.fl-steps__body > :first-child { margin-block-start: 0; }
.fl-steps__body > :last-child { margin-block-end: 0; }
.fl-steps__figure { min-inline-size: 0; margin-block-start: var(--fl-space-3); }
