/* ═══════════════════════════════════════════════════════════════════
   Jetfolio Component Kit — Design System 2.0 (Phase 2)
   Shared primitives available on every page (linked right after
   css/tokens.css). Pages migrate onto these classes during their
   Phase 3 sweep; page-local re-implementations are then deleted.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Accessibility primitives (also for pages without global.css) ─── */
.sr-only {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.skip-to-content, .skip-link {
    position: absolute; top: -100px; left: 0;
    background: var(--accent); color: #fff;
    padding: 0.75rem 1.5rem; text-decoration: none; font-weight: 600;
    border-radius: 0 0 var(--radius-md) 0; z-index: var(--z-tooltip);
    transition: top var(--transition-normal);
}
.skip-to-content:focus, .skip-link:focus { top: 0; outline: 3px solid var(--accent-light); outline-offset: 2px; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Icons: one inline-SVG language (2px stroke, currentColor) ────── */
.jf-icon { width: 16px; height: 16px; flex: none; vertical-align: -3px; }
.jf-icon-sm { width: 14px; height: 14px; }
.jf-icon-lg { width: 20px; height: 20px; }
.jf-icon-xl { width: 32px; height: 32px; }

/* ── Status pills: the five states, reused by every subsystem ─────── */
.jf-pill {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 11.5px; font-weight: 600; letter-spacing: .02em;
    border-radius: var(--radius-full); padding: 3.5px 10px;
    white-space: nowrap;
}
.jf-pill-ok      { background: var(--ok-bg);      color: var(--ok-ink); }
.jf-pill-warn    { background: var(--warn-bg);    color: var(--warn-ink); }
.jf-pill-bad     { background: var(--bad-bg);     color: var(--bad-ink); }
.jf-pill-info    { background: var(--info-bg);    color: var(--info-ink); }
.jf-pill-neutral { background: var(--neutral-bg); color: var(--neutral-ink); }

/* ── Empty state: icon + one sentence + one action ────────────────── */
.jf-empty {
    display: flex; flex-direction: column; align-items: center;
    text-align: center; padding: 48px 24px; gap: 6px;
}
.jf-empty .jf-empty-icon { color: var(--line-strong); margin-bottom: 6px; }
.jf-empty .jf-empty-icon svg { width: 40px; height: 40px; }
.jf-empty h3, .jf-empty .jf-empty-title {
    font-size: 15px; font-weight: 700; color: var(--ink); margin: 0;
}
.jf-empty p, .jf-empty .jf-empty-hint {
    font-size: 13.5px; color: var(--text-secondary); margin: 0; max-width: 44ch;
}
.jf-empty .jf-empty-action { margin-top: 12px; }

/* ── Skeleton loading blocks ──────────────────────────────────────── */
.jf-skeleton {
    background: linear-gradient(90deg, var(--line) 25%, var(--bg-card-alt) 50%, var(--line) 75%);  /* highlight sweep follows the ground, not a cool literal (18.1b) */
    background-size: 200% 100%;
    animation: jf-shimmer 1.4s ease infinite;
    border-radius: var(--radius-sm);
    min-height: 12px;
    color: transparent; user-select: none;
}
@keyframes jf-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

/* ── Overline label (KPI/table-header style) ──────────────────────── */
.jf-label {
    font-size: 11px; font-weight: 600; letter-spacing: .09em;
    text-transform: uppercase; color: var(--ink-label);
}

/* ── Motion respect ───────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .jf-skeleton { animation: none; }
    .skip-to-content, .skip-link { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   Phase 2 — control surfaces (2026-08-29)
   ═══════════════════════════════════════════════════════════════════ */

/* Form controls do not inherit the page font by default, and no shipped
   stylesheet ever reset them. Measured: 196 rendered elements across 40 of 73
   pages fell back to Chrome's Arial — almost all of them BUTTONS AND TABS, i.e.
   the primary action on most screens — and textareas rendered in Courier.

   Deliberately font-FAMILY only, not the `font:` shorthand. The shorthand also
   resets size/weight/line-height, which would resize buttons on those 40 pages
   as a side effect. The defect is the typeface; this fixes the typeface.

   Pages that already style their controls still win — kit.css loads early, so
   this only reaches controls nobody styled. */
button,
input,
select,
textarea,
optgroup {
    font-family: inherit;
}

/* Controls must show focus. 78 of 182 visible form fields had no focus
   indicator at all. :focus-visible above covers keyboard focus generally;
   this makes the ring unmistakable on form controls specifically. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* ── Buttons ──────────────────────────────────────────────────────────
   264 distinct page-content button treatments were measured (1,105 buttons);
   69 signatures were needed to cover just 80% of them. These three are the
   whole intended vocabulary: one primary action, everything else secondary,
   destructive only for irreversible actions. */
.jf-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    font-family: var(--font-family);
    font-size: var(--text-body);
    font-weight: 600;
    line-height: 1;
    padding: 10px 16px;
    min-height: var(--touch-target-min);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    cursor: pointer;
    text-decoration: none;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}
.jf-btn:disabled, .jf-btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }

.jf-btn-primary   { background: var(--accent); color: #fff; }
.jf-btn-primary:hover:not(:disabled)   { background: var(--accent-deep); }

.jf-btn-secondary { background: var(--surface); color: var(--ink-body); border-color: var(--line-strong); }
.jf-btn-secondary:hover:not(:disabled) { background: var(--wash); }

.jf-btn-danger    { background: var(--danger-color); color: #fff; }
.jf-btn-danger:hover:not(:disabled)    { background: var(--danger-red-dark); }

/* A destructive action that is not the page's main action — "Remove", "Deny",
   "Archive" sitting beside other controls. Added 2026-09-08 (Initiative 20)
   because the scheduler and the crew profile had each invented it. */
.jf-btn-danger-outline { background: var(--bad-bg); color: var(--bad-ink); border-color: var(--bad-ink); }
.jf-btn-danger-outline:hover:not(:disabled) { background: var(--bad-ink); color: #fff; }

/* Dense contexts — a table row, a card footer, a toolbar. Keeps the touch
   target; trims the padding and the type only. */
.jf-btn-sm { padding: 6px 12px; font-size: var(--text-support); gap: 6px; }

/* Even denser than .jf-btn-sm — a toolbar packing several control groups
   onto one row (the scheduler's consolidated toolbar, 2026-09-13) has no
   room for .jf-btn-sm's still-44px touch floor. Toolbar-only, like the
   scheduler's own .sched-minical-nav/.rv-mini-cal-nav already go below
   the floor for the same reason. */
.jf-btn-xs { padding: 3px 8px; font-size: var(--text-label); gap: 4px; min-height: 26px; }

/* ── Page header ──────────────────────────────────────────────────────
   Phase 3 uses these. 27 of 96 pages render no visible page title at all and
   18 hide their h1 with .sr-only, so a deep link lands on an unlabelled
   screen — the single worst gap for a toolkit where every tool is an entry
   point. One title treatment, one purpose line. */
.jf-page-header { margin: 0 0 var(--spacing-lg); }
/* The page title, however it is marked up.
   Owner ruling 2026-09-03: keep weight 700 and make it deliberate. 22 of the 50
   survivor tools take it from --weight-title via .jf-page-title; the other 28
   were landing on 700 only because that is the UA default for <h1> and NO
   stylesheet set an h1 weight at all. Same pixels, but the token now owns the
   page title on every page, so a future reset cannot silently change it and the
   scale cannot split.

   Scoped to the main landmark, which is safe as of 18.3: <user-modals> used to
   inject its own <h2>s inside #main-content on 27 pages and now sits beside
   <nav-bar> (tests/unit/user-modals-placement.unit.test.js pins that). Sizes are
   deliberately NOT set here — pages that legitimately differ (a sidebar title,
   an sr-only heading) keep their own. */
#main-content > h1,
#main-content > * > h1,
#main-content > * > * > h1 { font-weight: var(--weight-title); }

.jf-page-title {
    font-size: var(--text-title);
    font-weight: var(--weight-title);
    line-height: var(--leading-tight);
    color: var(--ink);
    margin: 0;
}
.jf-page-sub {
    font-size: var(--text-support);
    color: var(--ink-muted);
    margin: 4px 0 0;
    max-width: 68ch;
}

/* ── Operational data ─────────────────────────────────────────────────
   Tail numbers, ICAO codes, Zulu times, money. DM Sans has NO 600/700 face —
   see the note in tokens.css — so strong data caps at 500 rather than asking
   the browser to synthesise a bold that does not exist. */
.jf-data        { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: var(--text-data); font-weight: var(--weight-data); }
.jf-data-strong { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: var(--text-data); font-weight: var(--weight-data-strong); }

/* ── Uppercase labels ─────────────────────────────────────────────────
   131 distinct uppercase-label size/weight/tracking combos were measured
   across 341 rules. 11px is legible ONLY with the tracking. */
.jf-label-caps {
    font-size: var(--text-label);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--ink-muted);
}

/* ── Dialog ───────────────────────────────────────────────────────────
   Built on the native <dialog> element. showModal() supplies focus
   containment, Escape-to-dismiss, background inertness and top-layer
   stacking — all of which this product previously hand-rolled in
   navbar.js against a hard-coded list of close-button selectors.

   Because the dialog renders in the top layer, it needs no z-index. The
   17 competing z-index values elsewhere in the product cannot fight it.

   Structure:
     <dialog class="jf-dialog">
       <form method="dialog" class="jf-dialog-panel">   <- form gives us
         <h2 class="jf-dialog-title">…</h2>                returnValue for
         <div class="jf-dialog-body">…</div>               free on submit
         <div class="jf-dialog-actions">…</div>
       </form>
     </dialog>
   ------------------------------------------------------------------- */
.jf-dialog {
    /* Centring, stated explicitly rather than inherited from the UA sheet.
       The UA centres a modal <dialog> with `margin: auto` inside `inset: 0`,
       and most pages here ship a `* { margin: 0; padding: 0 }` reset that
       zeroes it — which pins the dialog to the top-left corner. A class
       selector outranks `*`, so this survives every page's reset. Measured on
       tools/CRM-Pipeline.html, where it was the difference between a centred
       dialog and one wedged under the navbar. */
    margin: auto;
    /* Reset the UA's border/padding; the panel carries the chrome. */
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--ink-body);
    max-width: min(92vw, 30rem);
    width: 100%;
    font-family: var(--font-family);
}

.jf-dialog::backdrop {
    background: var(--scrim);
}

/* ── Adopted dialogs (JFDialog.adopt) ─────────────────────────────────
   A hand-rolled `.modal-overlay` div converted to a real <dialog> in place.

   SPECIFICITY IS THE WHOLE PROBLEM HERE. kit.css loads BEFORE a page's own
   <style>, and the overlay rules these pages ship are single classes:

       .modal-overlay { position: fixed; inset: 0; display: none; }
       .modal-overlay.show { display: flex; }

   A bare `.jf-adopted` therefore LOSES to `.modal-overlay` on equal
   specificity and source order — and the failure is invisible in the worst
   way: showModal() succeeds, the element reports `open` and matches `:modal`,
   and the dialog paints nothing, takes no focus, and measures 0×0. Every rule
   below is written with an attribute selector so it outranks the page.

   Do NOT try to un-position it. A modal <dialog> is centred by the UA with
   `position: fixed; inset: 0; margin: auto` — the auto margins distribute the
   slack inside that inset rectangle. Setting `position: static` looks like the
   tidy thing to do and is the one change that breaks centring: a top-layer
   element coerces `static` to `absolute`, and with `inset: auto` the auto
   margins have no slack to distribute, so the dialog pins to the top-left
   corner. Restated here rather than inherited only because the page's
   `* { margin: 0 }` reset would otherwise zero the margins that do the work. */
dialog.jf-adopted[open],
dialog.jf-adopted:not([open]) {
    position: fixed;
    inset: 0;
    /* The overlay painted its own scrim; ::backdrop does it now. */
    background: transparent;
    padding: 0;
    border: 0;
    /* The UA centres a modal dialog with margin:auto, which these pages'
       `* { margin: 0 }` reset zeroes — the same trap .jf-dialog documents. */
    margin: auto;
    /* The overlay stretched across the viewport to paint its own scrim, so it
       set width/height to 100%. The dialog must shrink-wrap its panel instead.
       fit-content, not auto: with `inset: 0` on a fixed box, `width: auto`
       resolves to the CONTAINING BLOCK's width — so the dialog stayed
       viewport-wide (capped at max-width) with the panel adrift inside it.
       fit-content is what the UA sheet gives a dialog, restated here only to
       outrank the page's `width: 100%`. */
    width: fit-content;
    height: fit-content;
    max-width: min(96vw, 1100px);
    max-height: 92vh;
    overflow: visible;
    /* `display` is not the only way a hand-rolled overlay hides itself. The
       other house pattern fades one in — `opacity: 0; visibility: hidden`
       on the base class, restored by `.active` — and that is a SEPARATE
       trap from the display one above, because the rules further down only
       ever put `display` back. An adopted dialog never takes `.active` (it
       has `open`), so on those pages showModal() succeeded, the element
       reported `open` and `:modal`, and it painted nothing and measured
       0x0: admin.html's Edit User dialog, silently, for exactly that
       reason. Restated here with the attribute selector so it outranks the
       page's own base rule. */
    opacity: 1;
    visibility: visible;
}
dialog.jf-adopted[open] { display: block; }
dialog.jf-adopted:not([open]) { display: none; }

dialog.jf-adopted::backdrop {
    background: var(--scrim);
}

/* The panel inside kept the page's own chrome; it only needs to stop trying to
   position itself, since the dialog now does that. */
dialog.jf-adopted > .modal-content,
dialog.jf-adopted > .modal {
    position: static;
    margin: 0;
    max-height: 92vh;
    overflow-y: auto;
}

.jf-dialog-panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-xl);
    padding: var(--spacing-lg, 20px);
    display: flex;
    flex-direction: column;
    gap: 12px;
    /* Long bodies scroll inside the panel, never the page behind it. */
    max-height: 80vh;
}

.jf-dialog-title {
    font-size: var(--text-section);
    font-weight: var(--weight-title);
    line-height: var(--leading-tight);
    color: var(--ink);
    margin: 0;
}

.jf-dialog-body {
    font-size: var(--text-body);
    line-height: var(--leading-normal);
    color: var(--ink-body);
    margin: 0;
    /* Batch 16 — flex-basis 0 (batch 14) made this item contribute nothing to
       the panel's intrinsic height when the panel itself has no definite
       height (only a max-height), collapsing the body to a sliver regardless
       of content. Basis auto lets it size to content up to the panel's own
       max-height clamp below; min-height:0 is what still lets it shrink
       (and scroll) once that clamp is hit, instead of the flex:none head and
       actions ever giving way. */
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

.jf-dialog-body p { margin: 0 0 8px; }
.jf-dialog-body p:last-child { margin-bottom: 0; }

/* A consequence the reader must not miss — used for destructive actions. */
.jf-dialog-consequence {
    font-size: var(--text-support);
    color: var(--danger-color);
    margin: 8px 0 0;
}

.jf-dialog-field {
    display: block;
    width: 100%;
    margin-top: 4px;
    padding: 8px 10px;
    font: inherit;
    font-size: var(--text-body);
    color: var(--ink-body);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-md);
}

.jf-dialog-label {
    display: block;
    font-size: var(--text-support);
    color: var(--ink-muted);
}

.jf-dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    flex-wrap: wrap;
}

/* Below this the buttons stack and go full width so the primary action is
   not a thumb-sized target wedged against the edge. */
@media (max-width: 420px) {
    .jf-dialog-actions { flex-direction: column-reverse; }
    .jf-dialog-actions .jf-btn { width: 100%; justify-content: center; }
}

/* Entrance. Kept off the compositor-heavy properties and skipped entirely
   for anyone who asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
    .jf-dialog[open] { animation: jf-dialog-in 120ms ease-out; }
    .jf-dialog[open]::backdrop { animation: jf-backdrop-in 120ms ease-out; }
}
@keyframes jf-dialog-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
}
@keyframes jf-backdrop-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* -------------------------------------------------------------------
   PAGE-OWNED DIALOGS

   The block above is the confirm/alert shape: one flow of prose and a
   row of buttons. This block is the other shape the product needs — a
   modal the page builds and owns, with a header rule, a scrolling body
   and a footer rule. Four subsystems had each hand-rolled it as a
   `.modal-overlay` div (crew scheduler, rotations, crew vault + form,
   crew portal), at four widths, four scrim colours, and — the reason
   this is not only tidiness — four close buttons, three of which were
   28px or smaller against a 44px floor.

   Sitting it on <dialog> means focus containment, Escape, background
   inertness and top-layer stacking come from the platform instead of
   from navbar.js's runtime retrofit.

   Structure (built by JFDialog.panel):
     <dialog class="jf-dialog jf-dialog-md">
       <div class="jf-dialog-panel is-sectioned">
         <div class="jf-dialog-head">
           <h2 class="jf-dialog-title">…</h2>
           <button class="jf-dialog-close">×</button>
         </div>
         <div class="jf-dialog-body">…</div>
         <div class="jf-dialog-actions">…</div>
       </div>
     </dialog>
   ------------------------------------------------------------------- */

/* Widths. The default (30rem) suits a confirm; a form or a table needs
   more room. Named rather than numeric so a call site states intent. */
.jf-dialog-md { max-width: min(92vw, 36rem); }
.jf-dialog-lg { max-width: min(94vw, 46rem); }
/* Batch 36, item 3 — a wide-table modal (Conflicts from today) needs
   more room than "lg" (46rem/736px) gives: min(1100px, 100vw - 48px). */
.jf-dialog-xl { max-width: min(1100px, calc(100vw - 48px)); }

.jf-dialog-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* The close affordance. 44px because it is a control like any other —
   the versions this replaces were 28px, 24px and an unsized 20px glyph. */
.jf-dialog-close {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--touch-target-min);
    height: var(--touch-target-min);
    margin: calc(var(--touch-target-min) / -4) -8px;
    padding: 0;
    font-size: 20px;
    line-height: 1;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--ink-muted);
    cursor: pointer;
}
.jf-dialog-close:hover { background: var(--wash); color: var(--ink); }
/* Focus ring comes from the sheet-wide :focus-visible rule at the top. */

/* Sectioned: the panel gives up its own padding so the three regions can
   carry rules that run edge to edge. */
.jf-dialog-panel.is-sectioned {
    display: flex;
    flex-direction: column;
    /* Own max-height explicitly rather than relying on the flat 80vh above:
       a definite clamp here is what gives the flex-basis:auto body (below)
       something to size against, instead of growing unbounded and pushing
       the footer off-screen. dvh accounts for mobile browser chrome. */
    max-height: min(90vh, calc(100dvh - 32px));
    padding: 0;
    gap: 0;
    overflow: hidden; /* keeps the head's corners inside the radius */
}
.is-sectioned > .jf-dialog-head {
    padding: 14px 18px;
    border-bottom: 1px solid var(--line);
    /* Head and actions never shrink; .jf-dialog-body's flex:1 1 auto (above)
       is what gives way once content plus head plus actions exceeds the
       panel's own max-height, so the footer stays a normal-flow sibling
       below a body that scrolls clear of it instead of a bottom row content
       can render underneath. */
    flex: none;
}
.is-sectioned > .jf-dialog-body {
    padding: 16px 18px;
    font-size: var(--text-support);
}
.is-sectioned > .jf-dialog-actions {
    padding: 12px 18px;
    border-top: 1px solid var(--line);
    flex: none;
}

/* No fallback block for a browser without <dialog>: it would have to be
   written with :not(:modal), and :modal ships in the same browsers that
   ship showModal(). Anything old enough to need the fallback cannot parse
   the selector that would deliver it. Without <dialog>, JFDialog sets the
   `open` attribute and the panel renders in normal flow — plainer, but
   readable and dismissible. */

/* ── Stepper ──────────────────────────────────────────────────────────
   One step indicator for every multi-step flow. Before this, the product
   shipped 8 distinct designs with circle diameters of 28/32/36/36/40px,
   three of them styled from JS template strings outside the token system.

   Markup (an <ol> — it IS an ordered sequence, and a screen reader
   announces "step 2 of 5" for free from the list semantics):

     <ol class="jf-stepper">
       <li class="jf-step is-done"><span class="jf-step-dot">1</span>
           <span class="jf-step-label">Flight info</span></li>
       <li class="jf-step is-current" aria-current="step">…</li>
       <li class="jf-step">…</li>
     </ol>

   States: .is-done (completed), .is-current (+ aria-current="step"),
   default = upcoming. Clickable steps wrap dot+label in a <button
   class="jf-step-btn"> instead — same visuals, real focus ring.
   ------------------------------------------------------------------- */
.jf-stepper {
    display: flex;
    align-items: flex-start;
    list-style: none;
    margin: 0 0 var(--spacing-lg, 20px);
    padding: 0;
    gap: 4px;
}

.jf-step {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    flex: 1;
    min-width: 0;
    position: relative;
    text-align: center;
}

/* Connector: drawn by each step except the first, so the row never needs
   a separate rail element. */
.jf-step + .jf-step::before {
    content: '';
    position: absolute;
    top: 13px;               /* centre of the 28px dot, minus 1px line */
    right: calc(50% + 20px);
    left: calc(-50% + 20px);
    height: 2px;
    background: var(--line);
}
.jf-step.is-done + .jf-step::before,
.jf-step.is-done + .jf-step.is-done::before {
    background: var(--accent);
}

.jf-step-dot {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-support);
    font-weight: var(--weight-card);
    background: var(--surface);
    border: 2px solid var(--line-strong);
    color: var(--ink-muted);
    position: relative;
    z-index: 1;              /* above the connector */
    flex: none;
}
.jf-step.is-current .jf-step-dot {
    border-color: var(--accent);
    background: var(--accent);
    color: #fff;
}
.jf-step.is-done .jf-step-dot {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
}

.jf-step-label {
    font-size: var(--text-label);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--ink-muted);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.jf-step.is-current .jf-step-label { color: var(--accent-deep); }
.jf-step.is-done .jf-step-label    { color: var(--ink-body); }

/* Clickable variant: the button carries the same column layout. */
.jf-step-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    background: none;
    border: 0;
    /* No top padding: the connector sits at top:13px to bisect a 28px dot
       that starts at y=0, and a padded button would shift the dot 2px down
       and leave the line riding above centre. The focus outline draws
       outside the box, so it needs no padding to stay visible. */
    padding: 0 2px 2px;
    cursor: pointer;
    font: inherit;
    width: 100%;
}
.jf-step-btn:disabled { cursor: default; }

/* Narrow viewports: labels go, dots stay. */
@media (max-width: 560px) {
    .jf-step-label { display: none; }
}

/* ── Toolbox home tiles (18.4) ────────────────────────────────────────
   The launcher grid on index.html, rendered by js/components/toolbox-home.js
   from the registry. Hairlines, not shadows: the grid is one bordered block
   whose cells are separated by a 1px gap showing the page ground through,
   so an unfilled cell in the last row stays paper rather than reading as an
   empty card. Same treatment the approved Slate & Paper proposal used. */
.jf-tile-group { margin: 0 0 var(--spacing-xl, 2rem); scroll-margin-top: 72px; }
.jf-tile-group-head {
    display: flex; align-items: baseline; gap: 12px;
    margin: 0 0 var(--spacing-md, 1rem);
}
.jf-tile-group-title {
    font-size: var(--text-section, 17px);
    font-weight: var(--weight-title);
    color: var(--ink);
    margin: 0;
}
.jf-tile-count {
    font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    font-size: var(--text-label, 11px);
    color: var(--ink-muted);
}
.jf-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 12px;
}
.jf-tile {
    display: flex; flex-direction: column; gap: 6px;
    min-height: 112px;
    padding: var(--spacing-md, 1rem);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md, 8px);
    color: var(--ink);
    text-decoration: none;
    transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}
.jf-tile:hover {
    text-decoration: none;
    border-color: var(--accent-light);
    box-shadow: 0 2px 10px rgba(22, 32, 46, 0.07);
    transform: translateY(-1px);
}
@media (prefers-reduced-motion: reduce) { .jf-tile { transition: none; } .jf-tile:hover { transform: none; } }
.jf-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.jf-tile-name { font-size: var(--text-body, 15px); font-weight: 600; }
.jf-tile-desc {
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--ink-muted);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Figures line up in columns and tick without jitter: tabular numerals for data. (2026-09-04) */
.jf-data, .jf-data-strong, .jf-tile-count, td, th, #zuluClock, .jf-zulu { font-variant-numeric: tabular-nums; }

/* ── Time field (js/shared/time-field.js, 2026-09-06) ──────────────────
   [date] [HH:MM + slot list] [badge] replacing native datetime-local and
   time inputs. 24-hour only (owner ruling). The generated inputs carry the
   page's own control class (e.g. .form-control) plus these. */
.jf-dt { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.jf-dt input.jf-dt-date { flex: 1 1 10em; min-width: 9.5em; width: auto; }
.jf-time { position: relative; display: inline-flex; align-items: center; flex: 0 0 auto; }
.jf-dt input.jf-time-input {
    width: 6.5em;
    text-align: center;
    font-variant-numeric: tabular-nums;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
.jf-dt input.jf-time-input[aria-invalid="true"] { border-color: var(--danger-color, #b42318); color: var(--danger-color, #b42318); }
.jf-time-btn {
    appearance: none;
    border: 1px solid var(--line-strong, var(--line));
    border-left: 0;
    background: var(--wash, #faf8f4);
    color: var(--ink-muted);
    font: inherit;
    line-height: 1;
    padding: 0 8px;
    min-width: 40px;               /* the style guide's touch floor (owner, 2026-09-26, P-7); was 24px wide */
    align-self: stretch;
    border-radius: 0 var(--radius-sm, 6px) var(--radius-sm, 6px) 0;
    cursor: pointer;
}
.jf-time-btn:hover { background: var(--surface); color: var(--ink); }
.jf-time-list {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 30;
    width: 7.5em;
    max-height: 220px;
    overflow-y: auto;
    padding: 4px;
    margin: 0;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm, 6px);
    box-shadow: 0 8px 22px rgba(22, 32, 46, 0.14);
}
.jf-time-list[hidden] { display: none; }
.jf-time-slot {
    display: block;
    width: 100%;
    padding: 4px 8px;
    border: 0;
    border-radius: 4px;
    background: none;
    color: var(--ink);
    font: inherit;
    font-variant-numeric: tabular-nums;
    text-align: left;
    cursor: pointer;
}
.jf-time-slot:hover, .jf-time-slot:focus-visible { background: var(--wash, #faf8f4); outline: none; }
.jf-time-slot.is-current { background: var(--accent-soft, #e7eef7); font-weight: 600; }
.jf-dt-badge {
    margin-left: 8px;
    font-size: var(--text-label, 11px);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-muted);
    padding: 2px 6px;
    border: 1px solid var(--line);
    border-radius: 999px;
}

/* ── Switch (a labelled on/off control for pages that have no local toggle CSS) ── */
.jf-switch { display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: var(--text-support, 13px); color: var(--ink-body); }
.jf-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.jf-switch-track {
    position: relative;
    flex: none;
    width: 40px;
    height: 22px;
    border-radius: 999px;
    background: var(--neutral-bg, #d9d2c3);
    transition: background 120ms ease;
}
.jf-switch-track::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--surface, #fff);
    box-shadow: 0 1px 2px rgba(22, 32, 46, 0.25);
    transition: transform 120ms ease;
}
.jf-switch input:checked + .jf-switch-track { background: var(--accent-deep, #1e3a5f); }
.jf-switch input:checked + .jf-switch-track::after { transform: translateX(18px); }
.jf-switch input:focus-visible + .jf-switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
.jf-switch-label { flex: 1; }

/* ── Sub-navigation ───────────────────────────────────────────────────
   A subsystem's own page set, sitting under the shared bar and the page
   header: the scheduler's seven pages, the manuals' three, SMS's sections.
   Promoted into the kit 2026-09-08 (Initiative 20) from the strip the
   scheduler had been carrying, because every subsystem was inventing one.
   SMS still has its own; it adopts this in its revamp.

   The active state is a soft accent pill rather than an underline: it reads
   at a glance on a beige ground where a 3px rule under 13px text does not,
   and it survives the strip wrapping onto a second line. */
.jf-subnav {
    display: flex;
    align-items: center;
    gap: 2px;
    min-width: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.jf-subnav::-webkit-scrollbar { display: none; }
.jf-subnav-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 40px;              /* the style guide's touch floor (owner, 2026-09-26, P-7); was 32px */
    padding: 6px 11px;
    border-radius: var(--radius-md);
    font-size: var(--text-dense);
    font-weight: 500;
    color: var(--ink-muted);
    text-decoration: none;
    white-space: nowrap;
}
.jf-subnav-link:hover { background: var(--wash); color: var(--ink); }
.jf-subnav-link.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.jf-subnav-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.jf-subnav-link .jf-icon { width: 15px; height: 15px; opacity: .8; }
.jf-subnav-link.active .jf-icon { opacity: 1; }

/* ── Figure tile ──────────────────────────────────────────────────────
   The navy count tile: an icon, one number, one label. Use it for the
   handful of figures a page exists to surface — open hazards, crew needing
   attention, aircraft down — and not as a decorative header band. Four
   across is the intended maximum; beyond that they stop being read. */
.jf-tiles-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 10px;
    margin: 0 0 var(--spacing-lg);
}
.jf-stat-tile {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    background: var(--navy-bar);
    color: #fff;
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
    text-align: left;
    font-family: var(--font-family);
    text-decoration: none;
}
a.jf-stat-tile, button.jf-stat-tile { cursor: pointer; }
a.jf-stat-tile:hover, button.jf-stat-tile:hover { background: var(--accent-deep); }
.jf-stat-tile:focus-visible { outline: 2px solid var(--accent-light); outline-offset: 2px; }
.jf-stat-tile .jf-icon, .jf-stat-tile .jf-icon-lg { flex: none; opacity: .9; }
.jf-stat-tile-body { min-width: 0; }
.jf-stat-tile-value {
    font-size: 26px;
    font-weight: var(--weight-title);
    line-height: 1.05;
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
}
.jf-stat-tile-label {
    font-size: var(--text-dense);
    line-height: 1.35;
    margin-top: 3px;
    color: rgba(255, 255, 255, 0.82);
}
/* Severity is carried by a left edge, not by recolouring the whole tile:
   four tiles in four colours read as decoration rather than as a warning. */
.jf-stat-tile-warn { border-left: 4px solid var(--warn-ink); }
.jf-stat-tile-bad  { border-left: 4px solid var(--bad-ink); }

/* ── Panel ────────────────────────────────────────────────────────────
   A card that has a title: tinted header bar, white body, one action slot
   on the right. The body paints its own surface deliberately — it is a
   scrollable child of an overflow:hidden card, which in some browsers fails
   to paint the parent's background through and renders as the page ground. */
.jf-panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.jf-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 11px 16px;
    background: var(--wash);
    border-bottom: 1px solid var(--line);
}
.jf-panel-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    font-size: var(--text-card);
    font-weight: var(--weight-card);
    color: var(--ink);
}
.jf-panel-title .jf-icon { color: var(--ink-2); }
.jf-panel-actions { display: flex; align-items: center; gap: 4px; flex: none; }
.jf-panel-body {
    padding: 16px;
    background: var(--surface);
}
.jf-panel-body > :first-child { margin-top: 0; }
.jf-panel-body > :last-child { margin-bottom: 0; }
.jf-panel-scroll { max-height: 320px; overflow-y: auto; }
