/*
  SHIP-OS semantic design tokens: the one token layer.

  Three appearances, three blocks. `:root` carries the light one,
  `:root[data-ship-appearance="dark"]` re-points the colour roles for the dark
  one, and `@media print` states the paper palette on both. Every role is
  declared once per block, and the appearance blocks also state the
  appearance-independent values (radii, shadows, type, row heights) so no role
  depends on what an earlier block happened to leave behind.

  The control-sizing tokens are the one exception, and deliberately so: they are
  declared once on `:root`, because the coarse-pointer override at the end of
  this file has to be able to move them.

  Components consume these roles. They are the only palette source in the
  product, and a new role belongs here rather than in a template or a route.
*/

:root {
    color-scheme: light;
    /* Inter is the one type family (rollout §0.2 #5, D13). Its @font-face is
       global and comes from inter-fonts.css, loaded by base.html; only the
       family that consumes it is declared here. */
    --font-d: "Inter", ui-sans-serif, system-ui, sans-serif;
    --font-b: "Inter", ui-sans-serif, system-ui, sans-serif;
    /* The page font: html carries it and every screen inherits, which is what
       makes Inter the document typeface rather than a screen-scoped override.
       --font-d/--font-b are what any CSS consumer reads; this covers markup
       that does not route through them. */
    font-family: var(--font-b);

    /*
      Canonical rocket identity: brand primitives, not the palette. Navy and
      gold are painted as restrained brand emphasis (the selection bars, the
      console's active nav item, the flow map's hit cells) and never carry
      normal text on a light surface, as DESIGN.md §2 requires.
    */
    --ship-brand-navy: #172033;
    --ship-brand-navy-deep: #101828;
    --ship-brand-navy-mid: #243048;
    --ship-brand-blue: #0f5ea8;
    --ship-brand-blue-hover: #0b4f8f;
    --ship-brand-blue-soft: #7aa2ff;
    --ship-brand-gold: #f4b804;
    --ship-brand-gold-strong: #f8c01c;
    --ship-on-brand-gold: #172033;

    /* Surfaces. Mosaic's shell is gray-100 with white cards. */
    --ship-canvas: #f3f4f6; /* gray-100 */
    --ship-canvas-top: #f9fafb; /* gray-50 */
    --ship-canvas-bottom: #f3f4f6;
    --ship-surface: #ffffff;
    --ship-surface-elevated: #ffffff;
    --ship-surface-muted: #f9fafb; /* gray-50 - card and table headers */
    --ship-control-fill: #f3f4f6;
    --ship-action-bar: #f9fafb;
    --ship-border-subtle: #e5e7eb; /* gray-200 */
    /* gray-500, not Mosaic's gray-300: border-strong is a UI boundary and the
       contract test holds it to 3:1 against white. gray-300 is 1.75:1 and
       gray-400 is 2.60:1, so gray-500 is the first step that passes. */
    --ship-border-strong: #6b7280; /* gray-500 */
    --ship-divider: #f3f4f6; /* gray-100 */
    --ship-scrim: rgb(17 24 39 / 0.32); /* gray-900 ink */
    /* Heavy ink veil for full-bleed busy overlays. */
    --ship-scrim-strong: color-mix(in srgb, var(--ship-text-primary) 85%, transparent);

    /* Text. Mosaic body copy is gray-600, headings gray-800. Tertiary is
       gray-500, NOT Mosaic's gray-400: #9ca3af on the gray-50 surface-muted is
       2.43:1, well under the 4.5:1 floor the contract test enforces. */
    --ship-text-primary: #1f2937; /* gray-800 */
    --ship-text-secondary: #4b5563; /* gray-600 */
    /* #626975, darker than Mosaic's gray-400 and gray-500 (rollout P0.3 / D5):
       gray-500 #6b7280 fell under 4.5:1 on the selected-muted and control
       fills. This clears 4.85:1 on every light fill and stays 1.37:1 apart
       from secondary, so the hierarchy survives. */
    --ship-text-tertiary: #626975;
    --ship-text-inverse: #ffffff;
    --ship-inverse-wash: rgb(255 255 255 / 0.10);
    --ship-inverse-faint: rgb(255 255 255 / 0.20);

    /*
      Interaction. The action fill is Mosaic's near-black primary button
      (bg-gray-900 / text-gray-100) -- NOT its violet. Mosaic reserves violet
      for the accent: active/selected state, links, focus. That is also why the
      accent roles below carry it. This mapping is forced by accessibility as
      well as by fidelity: white on Mosaic's violet-500 #8470ff is 3.66:1,
      under the 4.5:1 floor enforced by tests/test_adaptive_design_contract.py,
      so a violet fill could not carry --ship-on-action at all.
    */
    --ship-action: #111827; /* gray-900 */
    --ship-action-hover: #1f2937; /* gray-800 */
    --ship-on-action: #f3f4f6; /* gray-100 */
    /* Text on a filled status or selected chip (a solid --ship-success etc.
       background). White clears 6.5:1 on every light status fill. Distinct from
       --ship-text-inverse, which is theme-stable: the dark fills are light
       tints, so these flip where inverse deliberately does not. */
    --ship-on-success: #ffffff;
    --ship-on-warning: #ffffff;
    --ship-on-danger: #ffffff;
    --ship-on-info: #ffffff;
    --ship-on-neutral: #ffffff;
    --ship-on-ambiguous: #ffffff;
    /* White on violet-500 is 3.66:1, so text on a selected fill (count badges)
       is the action ink instead: 5.2:1. */
    --ship-on-selected: #111827; /* gray-900 */

    /* Accent: violet, on the roles that express selection and links. Company
       themes override these per theme (company-branding.css §4.2), which is
       why they are their own group. */
    --ship-link: #5d47de; /* violet-700 - keeps link copy legible on white */
    --ship-link-dark: #b7acff; /* violet-300 */
    --ship-selected: #8470ff; /* violet-500 */
    --ship-selected-muted: #f1eeff; /* violet-50 */
    --ship-focus-ring: #8470ff;
    --ship-accent: #8470ff;
    /* Translucent focus halo derived from the themed ring color. Tailwind's
       slash-opacity modifier (tw-ring-ship-focus-ring/25) compiles to nothing
       on var()-based colors, so translucency is a token role, not a modifier. */
    --ship-focus-ring-soft: color-mix(in srgb, var(--ship-focus-ring) 25%, transparent);

    /*
      Semantic status: foreground / background / border, one triad per tone.

      These are the light appearance's values, and they are deliberately NOT
      Mosaic's hues. Two reasons. First, contrast: Mosaic's status foregrounds
      fail the 4.5:1 floor on their own tints (green-700 #239f52 on the green
      tint is 3.04:1), so adopting them would require inventing darkened shades
      that are no longer Mosaic's values. Second, meaning: these roles carry
      semantics, and DESIGN.md §4 keeps them independent of company branding for
      the same reason -- an operator who learns "green means delivered" should
      not have to relearn it.

      Open item for the DESIGN.md amendment: derive an accessible Mosaic-hued
      status scale, rather than either failing AA or keeping two palettes.
    */
    --ship-success: #166534;
    --ship-success-bg: #e8f7ee;
    --ship-success-border: #9fd8b6;
    --ship-warning: #854d0e;
    --ship-warning-bg: #fff4db;
    --ship-warning-border: #e7c56d;
    --ship-danger: #b42318;
    --ship-danger-bg: #fdecea;
    --ship-danger-border: #f0aea8;
    /* The retired pre-Mosaic action blue survives only here: the information
       role. It is never an action or link colour. */
    --ship-info: #0f5ea8;
    --ship-info-bg: #e5f0fb;
    --ship-info-border: #9bc1e7;
    --ship-neutral: #475569;
    --ship-neutral-bg: #eef2f7;
    --ship-neutral-border: #cbd5e1;
    --ship-ambiguous: #6b3fa0;
    --ship-ambiguous-bg: #f2e9ff;
    --ship-ambiguous-border: #c9adeb;

    /*
      Chart series. The operations Home dashboard is the first surface to plot
      data, and a plot needs paints the status roles cannot give it: two series
      that must stay separable from each other (orders in vs shipped) and an
      ordinal ramp for the pick-batch buckets (shipping -> shipped).
      Status tones cannot serve -- they carry meaning ("this is bad") that a
      series does not have -- so these are their own role family.

      The series separate by HUE, not by luminance: the steps differ by
      1.07-2.25:1 against each other, which is how a categorical palette is
      built for colour-blind readers, and is why the ramp is not read by
      lightness. MEASURED against --ship-surface, the fill every chart on Home
      is painted on: chart-1 (orders in) 4.42:1 light / 4.45:1 dark, chart-2
      (shipped) 3.20:1 / 4.17:1, seq-2 4.42:1 / 4.45:1 and seq-3 9.91:1 /
      7.67:1 all clear 3:1 (WCAG 1.4.11).

      The ramp is two steps because the bar's third bucket -- picking, the
      "remaining work" end of it -- is drawn as an OUTLINE on
      --ship-border-strong rather than as a fill. It was `--ship-chart-seq-1`
      (2.11:1 light, 2.00:1 dark on that surface, below the 3:1 floor), and the
      lightest step that would have cleared the floor in dark mode landed
      within 1.3:1 of seq-2, leaving pick and shipping indistinguishable. The
      boundary is carried by the border role instead -- MEASURED against that
      same surface, --ship-border-strong is 4.83:1 light, 6.33:1 dark and
      3.79:1 paper, so the outline clears the floor in all three appearances --
      and the token is gone rather than restated: a fill nothing paints is a
      colour waiting to be used by accident.
    */
    --ship-chart-1: #2a78d6;
    --ship-chart-2: #eb6834;
    --ship-chart-seq-2: #2a78d6;
    --ship-chart-seq-3: #104281;

    /* Spacing (4/8 rhythm) */
    --ship-space-1: 0.25rem;
    --ship-space-2: 0.5rem;
    --ship-space-3: 0.75rem;
    --ship-space-4: 1rem;
    --ship-space-5: 1.25rem;
    --ship-space-6: 1.5rem;
    --ship-space-8: 2rem;

    /*
      Desk band inset (#2120): the single sanctioned dark "terminal" surface
      for the openbot desk band, fixed across light/dark appearances so the
      console texture reads identically in both (the app palette is light-only
      by policy; the inset is deliberately theme-stable like a terminal).
      All band text derives from --ship-text-inverse / semantic -bg tints
      blended toward this surface with color-mix, keeping >=4.5:1 contrast.
    */
    --ship-desk-inset: #101828;

    /* Desk-band inset family: exact Ops Fabric band values, moved here from
       ad-hoc hexes so the token sheet stays the single palette source.
       -deep is the recessed input well inside the inset surface. */
    --ship-desk-inset-deep: #0f1624;
    --ship-desk-inset-surface: #1d2a44;
    --ship-desk-inset-border: #3d4f6f;
    --ship-desk-inset-text: #d7e3f4;
    --ship-desk-inset-text-muted: #9fb0cc;
    --ship-desk-inset-success: #7dd3a7;
    --ship-desk-inset-cmd: #f4d47c;
    --ship-desk-inset-focus: #4f8fe0;

    /*
      Web radii; native adapters keep their platform role scale.

      `xs` is the one addition here. The sheets carried literal 2px and 3px
      corners with no role to name them, so they could only be written as raw
      values — which is what the radius audit counts.

      `card` was proposed for deletion on the grounds that it had no consumers.
      It has one left: `items/form.html` uses it unconditionally. Removing
      `card` would silently drop the radius on the item form.

      `xl` completes the scale at the value `card` already carries, so a
      consumer that means the step above `lg` and a consumer that means the
      card role agree today; they can only diverge when `card` is deleted.
    */
    --ship-radius-xs: 0.125rem;
    --ship-radius-sm: 0.25rem;
    --ship-radius-md: 0.5rem;
    --ship-radius-lg: 0.75rem;
    --ship-radius-xl: 1rem;
    --ship-radius-card: 1rem;
    --ship-radius-pill: 9999px;

    --ship-shadow-xs: 0 1px 2px rgb(23 32 51 / 0.06);
    --ship-shadow-sm: 0 1px 3px rgb(23 32 51 / 0.10), 0 1px 2px rgb(23 32 51 / 0.06);
    --ship-shadow-md: 0 4px 10px rgb(23 32 51 / 0.12);
    --ship-shadow-lg: 0 8px 20px rgb(23 32 51 / 0.14), 0 2px 6px rgb(23 32 51 / 0.08);
    --ship-shadow-overlay: 0 12px 32px rgb(23 32 51 / 0.18), 0 4px 12px rgb(23 32 51 / 0.10);

    /*
      Stacking order, lowest first. Route sheets had accumulated 20+ distinct
      raw z-index values (1, 5, 6, 8, 20, 30, 35, 40, 50, 60, 70, 90, 95, 100,
      1200); these five replace them as consumers migrate.

      sticky < dropdown is deliberate and matches what the sheets already do: a
      sticky table header sits at 10 and a menu opening over it at 20, so the
      menu wins. Listing the roles in the spec in a different order does not
      change the numbers.

      These are z-index values only. They do NOT lift an element above an open
      <dialog> opened with showModal(): the top layer is painted above the whole
      z-index axis, at any value. Nothing here tries to. ui/toast.html briefly
      made its region a popover for exactly that reason and it was withdrawn —
      it only wins if the popover opens AFTER the dialog, and the region has to
      stay open to receive the first toast, so every later dialog painted over
      it. A surface that must reach the operator while its own dialog is open
      renders an alert inside the dialog.
    */
    --ship-z-sticky: 10;
    --ship-z-dropdown: 20;
    --ship-z-overlay: 30;
    --ship-z-modal: 40;
    --ship-z-toast: 50;

    /*
      Motion. There were no duration or easing tokens before these, and the
      sheets had accumulated the usual literals — 33 uses of 150ms, plus 200 and
      300.

      These three span AGENTS.md's mandatory 150–300ms range for
      micro-interactions, and none sits outside it: `fast` is the floor rather
      than a shorter "snappier" value, because 100ms reads as faster and is out
      of contract. `fast` is also the value the sheets already used most, so
      adopting it is not a repaint.
    */
    --ship-duration-fast: 150ms;
    --ship-duration-base: 200ms;
    --ship-duration-slow: 300ms;
    --ship-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
    --ship-ease-emphasised: cubic-bezier(0.2, 0, 0, 1);

    /*
      Type scale. There was none: every surface picked a Tailwind size step
      or a literal, so the same role read at different sizes on neighbouring
      screens. These are the steps the product already renders at — the
      Tailwind xs/sm/base/lg/xl/2xl values the templates use today — named
      as ship roles so a screen conversion does not have to invent them and
      a future scale change is one edit. Size and line-height travel as a
      pair per step; a size without its line-height is half a role.

      Weights are the four the templates already use (normal through bold
      in Tailwind terms), with the same reasoning.

      Focus geometry joins the ring colour here: adaptive-theme.css reads
      the width and offset, so the visible focus outline is token-driven
      rather than a second literal that can drift from the ring role.

      Rows have ONE height: `--ship-row-height-default`, 44px, on every
      pointer. It is also the touch target, so a tappable row and a default row
      are the same height by construction. There is no row-density setting and
      no compact row token (removed 2026-10-17). That row is not the control
      tier -- a fine pointer draws a control at 34px (below), and a *row* is not
      a control: `--ship-row-height-default` is the coarse-pointer value, which
      is why it stays 2.75rem while `--ship-touch-target` does not.
    */
    --ship-text-xs: 0.75rem;
    --ship-text-xs-line-height: 1rem;
    --ship-text-sm: 0.875rem;
    --ship-text-sm-line-height: 1.25rem;
    --ship-text-base: 1rem;
    --ship-text-base-line-height: 1.5rem;
    --ship-text-lg: 1.125rem;
    --ship-text-lg-line-height: 1.75rem;
    --ship-text-xl: 1.25rem;
    --ship-text-xl-line-height: 1.75rem;
    --ship-text-2xl: 1.5rem;
    --ship-text-2xl-line-height: 2rem;
    --ship-font-weight-normal: 400;
    --ship-font-weight-medium: 500;
    --ship-font-weight-semibold: 600;
    --ship-font-weight-bold: 700;
    --ship-focus-ring-width: 2px;
    --ship-focus-ring-offset: 2px;
    --ship-row-height-default: 2.75rem;

    /*
      Control sizing (Mosaic, adopted globally 2026-09-29; two tiers for a fine
      pointer, 44px for touch -- D8).

      Mosaic draws nothing at 44px. Its two control tiers are a 34px form
      control (the account / status selects) and a 38px `.btn` (Views). The
      sizes are adopted app-wide rather than screen-by-screen, so every route
      sheet that sizes a control through these tokens inherits them at once.

      Two tokens rather than one plus an offset: the 4px gap between the tiers
      is a coincidence of two independent Mosaic values, not a relationship. If
      Mosaic's form control moved to 32px, a `calc(var(--ship-touch-target) +
      0.25rem)` button would follow it to 36px and nothing would report the
      drift. Naming both also keeps `--ship-touch-target` honest as the form
      tier instead of a value that means 34px in one rule and 38px in the next.

      These are the only declarations of the three tokens, and they belong to
      no appearance: the size of a control does not change with the palette. A
      restatement in the dark block would silently defeat the coarse-pointer
      override below -- `:root[data-ship-appearance="dark"]` is (0,2,0) and
      beats a `:root` inside a media query (0,1,0) whatever the source order,
      so a dark phone would keep the 34px desktop control. The touch override
      can only win if nothing else declares the token.
      test_adaptive_design_contract.py pins both halves of that.

      The native iOS wrapper is a third value, not a third tier: it keeps the
      44pt platform minimum it renders today. The button tier never reads it
      (its `body:not(.ui-ios-shell)` guard in adaptive-theme.css means no web
      size reaches the wrapper's buttons at all, so they fall back to the
      button macro); the form tier does, for the wrapper's inputs and selects.
      Android is a native Compose app with no WebView and none of these
      stylesheets, so it keeps its own 48dp.
    */
    --ship-touch-target: 2.125rem;          /* 34px -- Mosaic form control */
    --ship-touch-target-button: 2.375rem;   /* 38px -- Mosaic .btn */
    --ship-touch-target-native: 2.75rem;    /* 44px -- iOS 44pt, form controls */
}

/*
  Reduced motion zeroes the DURATION tokens rather than each component's
  transition, so a consumer that uses `tw-duration-ship-*` opts in by
  construction: there is no per-component media query to forget, and a new
  component cannot reintroduce motion by omission. `animation` is not covered
  here — a looping animation needs `motion-reduce:tw-animate-none` at the call
  site (ui/skeleton.html and ui/toast.html both carry it), because zeroing a
  duration does not stop an animation with `infinite` iterations.
*/
@media (prefers-reduced-motion: reduce) {
  :root {
    --ship-duration-fast: 0ms;
    --ship-duration-base: 0ms;
    --ship-duration-slow: 0ms;
  }
}

/*
  Touch: with a coarse pointer both tiers return to 44px.

  D8's rule is two tiers for a mouse or trackpad and one size for a finger: the
  fine pointer gets Mosaic's 34px form control and 38px button, and a touch
  screen gets the 44px AGENTS.md has always required. Because every consumer
  sizes itself *through* these tokens -- `body :where(input…)` and the button
  tier in adaptive-theme.css, the orders workset panel and column rows, the
  app-shell rail and menu toggles, the account pill -- one declaration here
  moves all of them together, and no screen has to remember the rule.

  `:root` at (0,1,0), not a second `:root[data-ship-appearance="dark"]`. An
  override has to beat every declaration of the token it overrides, and a
  0-2-0 dark block would win regardless of source order, so the sizing tokens
  are declared once, above, and in no appearance block.

  `any-pointer`, not `pointer`: `pointer` reads only the PRIMARY device, so a
  touchscreen laptop driven by a trackpad would keep 34/38px controls while
  its screen is being tapped -- under AGENTS.md's 44px touch floor. Any coarse
  pointer gets the touch tiers; the cost is that such a laptop's mouse gets
  them too, and accessibility is first in AGENTS.md's design priority order.
*/
@media (any-pointer: coarse) {
  :root {
    --ship-touch-target: 2.75rem;        /* 44px */
    --ship-touch-target-button: 2.75rem; /* 44px */
  }
}

/*
  Mosaic DARK appearance (owner decision 2026-09-18: "dark mode ships").

  Mechanism: a persisted user choice carried as data-ship-appearance="dark" on
  <html> -- never a prefers-color-scheme query, because the choice is the
  operator's and must survive an OS flipping at dusk. base.html restores it
  before first paint and layout-shell.js owns the toggle afterwards. An attribute on the ROOT element rather than a class on
  <body> for two reasons: these selectors are :root-descendant so the platform
  can match them without a body class, and <html> is parsed before <body>, so
  the appearance is settled before the shell markup exists.

  Scope: the whole document. adaptive-theme.css paints html with
  var(--ship-canvas) and html's own background is what the browser propagates to
  the canvas, and this block declares on html, so the canvas follows.

  Company branding is unaffected: it declares its interactive roles on <body>, a
  nearer element, so a themed company keeps its accent in dark exactly as it
  does in light (see company-branding.css).

  Provenance. The neutral scale and the status triads are this product's shipped
  native dark appearance (ios/SharedNative/ShellDesignTokens.swift dark*SRGB),
  so web dark and native dark agree role for role instead of inventing a second
  dark language. Every value is measured against the floors the light block
  answers to, and every one passes with headroom:

      text-primary / surface             13.50:1   (light Mosaic 14.68:1)
      text-secondary / surface            6.33:1   (light Mosaic  7.56:1)
      text-tertiary / surface-muted       4.79:1   (light Mosaic  4.63:1)
      border-strong / surface-elevated    5.73:1   (light         4.62:1, floor 3:1)
      status fg / status bg               4.78-7.52:1, all six triads
      selected / focus-ring on any surface 4.00-4.87:1 (floor 3:1, non-text)

  Those pairs are pinned by
  test_web_dark_roles_cover_the_mosaic_appearance_with_accessible_contrast, the
  dark twin of the light contrast test, so this block cannot drift into an
  unverified palette the way Mosaic's own did.

  Accent roles keep Mosaic's violet, and the selected/focus value is the SAME
  violet-500 the light block uses (#8470ff): one accent in both appearances.
  The action FILL stays neutral, as in the light block -- violet-500 carrying
  --ship-on-action is 3.66:1, under the 4.5:1 floor, so dark inverts the light
  neutral pair (gray-900 fill on gray-100) rather than reaching for the accent.
*/
:root[data-ship-appearance="dark"] {
    color-scheme: dark;

    /* Surfaces. darkCanvas/Surface/SurfaceElevated/SurfaceMuted/ActionBar/
       ControlFill from the native dark sheet, unchanged. */
    --ship-canvas: #14181d;
    /* Derived, not copied: the light block's canvas-top is one step LIGHTER
       than its canvas (gray-50 over gray-100), and the native dark sheet is
       flat here, so the relationship is preserved by mixing rather than by
       inventing a step. */
    --ship-canvas-top: color-mix(in srgb, var(--ship-canvas) 92%, #ffffff);
    --ship-canvas-bottom: #14181d;
    --ship-surface: #1c2128;
    --ship-surface-elevated: #232932;
    --ship-surface-muted: #2a3038;
    --ship-control-fill: #2a3038;
    --ship-action-bar: #171c22;
    --ship-border-subtle: #313843;
    /* 5.73:1 on surface-elevated, well past the 3:1 boundary floor. */
    --ship-border-strong: #9aa3ad;
    --ship-divider: #313843;
    --ship-scrim: rgb(0 0 0 / 0.60);

    /* Text */
    --ship-text-primary: #e6ebf1;
    --ship-text-secondary: #9aa3ad;
    /* DEVIATION from the native sheet: native dark tertiary (#A3ACB6) is
       BRIGHTER than native dark secondary (#9AA3AD), which inverts the
       hierarchy DESIGN.md 4 gives light (4.63:1 tertiary under 7.56:1
       secondary). Dimmed one step so tertiary is again the faintest of the
       three, still 4.79:1 on the muted surface and 5.83:1 on the surface. */
    --ship-text-tertiary: #939ca8;
    /* Deliberately NOT flipped. This role means "text on a surface that is dark
       in BOTH appearances" -- the desk-band terminal inset (--ship-desk-inset)
       is the main consumer and is theme-stable on purpose. */
    --ship-text-inverse: #ffffff;
    --ship-inverse-wash: rgb(255 255 255 / 0.10);
    --ship-inverse-faint: rgb(255 255 255 / 0.20);

    /* Action: the light neutral pair inverted. */
    --ship-action: #e6ebf1;
    --ship-action-hover: #d3dae3;
    --ship-on-action: #14181d;
    /* The dark status and selected fills are light tints (white on them is
       1.2-3.7:1), so the text on them is the dark canvas ink: 4.87:1 or better. */
    --ship-on-success: #14181d;
    --ship-on-warning: #14181d;
    --ship-on-danger: #14181d;
    --ship-on-info: #14181d;
    --ship-on-neutral: #14181d;
    --ship-on-ambiguous: #14181d;
    --ship-on-selected: #14181d;

    /* Accent: violet, on the roles that express selection and links. */
    --ship-link: #b7acff; /* violet-300; 7.98:1 on the surface */
    /* Link-on-dark and link resolve to the same value here, because in this
       appearance every surface is a dark one. */
    --ship-link-dark: #b7acff;
    --ship-selected: #8470ff; /* violet-500, the light block's value */
    /* violet-500 at 16% over the dark surface -- the dark twin of the light
       block's violet-50 fill, measured: 6.47:1 under link, 10.94:1 under
       text-primary. */
    --ship-selected-muted: #2d2e4a;
    --ship-focus-ring: #8470ff;
    --ship-accent: #8470ff;

    /*
      Status triads. Unlike the light block -- which deliberately does NOT
      remap them, because Mosaic's own status hues fail AA on light -- dark
      has to remap: the light triad's dark-green-on-mint foreground is only
      legible against its own light tint, and on a dark surface both the tint
      and the fill read as light chips.

      The values are the native dark sheet's, so the hue semantics survive
      (green is still delivered, amber still warning) and DESIGN.md 3's "explicit
      foreground, background and border tokens for both appearances" is met.

      DEVIATION: native dark danger.background is the same amber-brown as its
      warning.background (#3A2A1C), which would paint a danger chip warning-
      colored. Dark red (#3B2228) instead; 5.06:1 with the danger foreground.
    */
    --ship-success: #86c9a4;
    --ship-success-bg: #1e3228;
    --ship-success-border: #86c9a4;
    --ship-warning: #e0a36a;
    --ship-warning-bg: #3a2a1c;
    --ship-warning-border: #e0a36a;
    --ship-danger: #e07a88;
    --ship-danger-bg: #3b2228;
    --ship-danger-border: #e07a88;
    --ship-info: #8bb8dc;
    --ship-info-bg: #243544;
    --ship-info-border: #8bb8dc;
    --ship-neutral: #9aa3ad;
    --ship-neutral-bg: #2a3038;
    --ship-neutral-border: #313843;
    --ship-ambiguous: #d2b7ff;
    --ship-ambiguous-bg: #362950;
    --ship-ambiguous-border: #72579a;

    /* Chart series, dark steps. Same roles as the light block, re-derived for
       the dark surface; see the light block for why the family exists. */
    --ship-chart-1: #3987e5;
    --ship-chart-2: #d95926;
    --ship-chart-seq-2: #3987e5;
    --ship-chart-seq-3: #86b6ef;

    /*
      Phase 1 token completion (rollout §4.1). These groups are
      appearance-independent geometry -- a radius, a shadow, a type step and a
      row height do not change with the palette -- so every appearance block
      states the same values rather than inheriting them silently.
      test_adaptive_design_contract.py holds every block to this list, so a
      token added to one block and forgotten in another fails there instead of
      drifting.

      Exception, 2026-09-29 (D8): the control-sizing tokens are not restated
      here. They are geometry too, but they are overridden on a coarse pointer
      by `@media (any-pointer: coarse) :root` (0,1,0), and a restatement inside
      this 0-2-0 block would beat that override -- a dark phone would keep the
      34px desktop control. They are declared once on `:root` and the same test
      file pins that instead.
    */
    --ship-radius-xl: 1rem;
    --ship-shadow-lg: 0 8px 20px rgb(23 32 51 / 0.14), 0 2px 6px rgb(23 32 51 / 0.08);
    --ship-shadow-overlay: 0 12px 32px rgb(23 32 51 / 0.18), 0 4px 12px rgb(23 32 51 / 0.10);
    --ship-text-xs: 0.75rem;
    --ship-text-xs-line-height: 1rem;
    --ship-text-sm: 0.875rem;
    --ship-text-sm-line-height: 1.25rem;
    --ship-text-base: 1rem;
    --ship-text-base-line-height: 1.5rem;
    --ship-text-lg: 1.125rem;
    --ship-text-lg-line-height: 1.75rem;
    --ship-text-xl: 1.25rem;
    --ship-text-xl-line-height: 1.75rem;
    --ship-text-2xl: 1.5rem;
    --ship-text-2xl-line-height: 2rem;
    --ship-font-weight-normal: 400;
    --ship-font-weight-medium: 500;
    --ship-font-weight-semibold: 600;
    --ship-font-weight-bold: 700;
    --ship-focus-ring-width: 2px;
    --ship-focus-ring-offset: 2px;
    --ship-row-height-default: 2.75rem;

    /* The two roles below are declared as var() references, and CSS substitutes
       a var() reference on the element the declaration sits on: left alone they
       would resolve once, at :root, to the light values and every descendant
       would inherit that resolved string. Restated here so they follow this
       block's leaves instead. */
    --ship-focus-ring-soft: color-mix(in srgb, var(--ship-focus-ring) 25%, transparent);
    --ship-scrim-strong: color-mix(in srgb, var(--ship-text-primary) 85%, transparent);
}

/*
  Form controls do not inherit the page font.

  tailwind.config.js sets corePlugins.preflight: false, so nothing applies the
  reset that normally gives form controls "font: inherit". The UA stylesheet's
  own font shorthand on button/input/select therefore beats inheritance, and a
  screen renders its buttons in the browser default face while every
  summary-based control beside them is Inter. Measured on the Orders card
  header: the Find and More-filters buttons computed font-family Arial while
  the Filter / Columns / Saved-views triggers computed Inter. Same family as
  the other preflight-off bugs -- the explicit value wins and nothing says so.

  A plain element selector, not a `:root`-prefixed one: that is what the UA
  shorthand needs to be beaten on the element itself.

  font-family only, never the font shorthand: the shorthand would also reset
  the size and weight callers set deliberately.
*/
button,
input,
select,
textarea {
    font-family: inherit;
}

/*
  Buttons do not paint a box they did not ask for.

  Preflight is off, so nothing clears the UA stylesheet's button skin either:
  a <button> that names no border and no background paints a 2px outset border
  on a rgb(239, 239, 239) fill. Most product buttons name both through
  ui_button; the hand-written ones -- menu items, sort headers, link buttons --
  named neither and kept the browser's grey box. Measured on the Orders card
  header: the kebab's group-by rows computed border-top 2px outset and
  background rgb(239, 239, 239) beside filled, bordered Mosaic controls.

  `:where()` is the point, not a shorthand: it holds this rule at zero
  specificity, so any utility or component rule that names a border or a fill
  wins whatever order the sheets load in. `tw:border tw:border-solid
  tw:border-ship-border` still paints, and so does a bordered sheet rule. This
  is not preflight: preflight stays a separate spec
  (docs/plans/mosaic-full-rollout.md §7.5), and this is its two declarations,
  on the elements the UA skins, at the one weight that cannot steal a decision.

  `border: 0 solid` rather than `border: 0`: the style lands at `solid` for a
  button that later names only a width utility (`tw:border`), which is the
  same pair ui_button states explicitly.
*/
:where(button, input[type="button"], input[type="submit"], input[type="reset"]) {
    border: 0 solid;
    background-color: transparent;
}

@media print {
    /*
      The paper palette, on the root whatever appearance is stored.

      A printed page is light: an operator prints to paper, not to a screen,
      and the browser would otherwise be left to drop dark backgrounds and
      print near-white ink on white paper.

      The dark block declares its roles on :root[data-ship-appearance="dark"],
      0-2-0. The selector list below therefore names BOTH :root and the dark
      attribute: the attribute form ties on specificity with the block it
      overrides and wins on source order -- this block is later in the file --
      while plain :root covers a light page. One rule carries the whole property
      set, the paper palette and the roles the dark block re-points alike, so a
      printed dark page cannot keep a dark border or chip inside the paper
      palette.

      The values are Mosaic's light ones, so paper and screen agree role for
      role, with three deliberate differences: the paper itself stays white
      (--ship-canvas, -top, -bottom, --ship-surface, --ship-surface-elevated),
      --ship-divider takes gray-200 because Mosaic's light divider (gray-100)
      vanishes on paper, and the status triads, chart colours, geometry and type
      are the light appearance's already and are restated unchanged.
    */
    :root,
    :root[data-ship-appearance="dark"] {
        color-scheme: light;
        --ship-canvas: #ffffff;
        --ship-canvas-top: #ffffff;
        --ship-canvas-bottom: #ffffff;
        --ship-surface: #ffffff;
        --ship-surface-elevated: #ffffff;
        --ship-surface-muted: #f9fafb;
        --ship-control-fill: #f3f4f6;
        --ship-action-bar: #f9fafb;
        --ship-text-primary: #1f2937;
        --ship-text-secondary: #4b5563;
        --ship-text-tertiary: #626975;
        --ship-border-subtle: #e5e7eb;
        --ship-border-strong: #6b7280;
        --ship-divider: #e5e7eb;
        --ship-success: #166534;
        --ship-success-bg: #e8f7ee;
        --ship-success-border: #9fd8b6;
        --ship-warning: #854d0e;
        --ship-warning-bg: #fff4db;
        --ship-warning-border: #e7c56d;
        --ship-danger: #b42318;
        --ship-danger-bg: #fdecea;
        --ship-danger-border: #f0aea8;
        --ship-info: #0f5ea8;
        --ship-info-bg: #e5f0fb;
        --ship-info-border: #9bc1e7;
        --ship-neutral: #475569;
        --ship-neutral-bg: #eef2f7;
        --ship-neutral-border: #cbd5e1;
        --ship-ambiguous: #6b3fa0;
        --ship-ambiguous-bg: #f2e9ff;
        --ship-ambiguous-border: #c9adeb;
        /* Paper is light whatever appearance is stored, so a chart printed from
           the dark appearance takes the light series steps: the dark ones were
           derived against a dark surface and would sit on white unreadable. */
        --ship-chart-1: #2a78d6;
        --ship-chart-2: #eb6834;
        --ship-chart-seq-2: #2a78d6;
        --ship-chart-seq-3: #104281;
        /* Phase 1 token completion (rollout §4.1). These groups are
           appearance-independent geometry — a radius, a shadow, a type step and
           a row height do not change with the palette — so every appearance
           block states the same values rather than inheriting them silently.
           test_adaptive_design_contract.py holds every block to this list, so
           a token added to one block and forgotten in another fails there
           instead of drifting.

           Exception, 2026-09-29 (D8): the control-sizing tokens are not
           restated here either. Paper does not have a pointer, and the
           restatement would be a fine-pointer value printed on a page that
           was laid out for touch; `:root` is the one declaration. */
        --ship-radius-xl: 1rem;
        --ship-shadow-lg: 0 8px 20px rgb(23 32 51 / 0.14), 0 2px 6px rgb(23 32 51 / 0.08);
        --ship-shadow-overlay: 0 12px 32px rgb(23 32 51 / 0.18), 0 4px 12px rgb(23 32 51 / 0.10);
        --ship-text-xs: 0.75rem;
        --ship-text-xs-line-height: 1rem;
        --ship-text-sm: 0.875rem;
        --ship-text-sm-line-height: 1.25rem;
        --ship-text-base: 1rem;
        --ship-text-base-line-height: 1.5rem;
        --ship-text-lg: 1.125rem;
        --ship-text-lg-line-height: 1.75rem;
        --ship-text-xl: 1.25rem;
        --ship-text-xl-line-height: 1.75rem;
        --ship-text-2xl: 1.5rem;
        --ship-text-2xl-line-height: 2rem;
        --ship-font-weight-normal: 400;
        --ship-font-weight-medium: 500;
        --ship-font-weight-semibold: 600;
        --ship-font-weight-bold: 700;
        --ship-focus-ring-width: 2px;
        --ship-focus-ring-offset: 2px;
            --ship-row-height-default: 2.75rem;
        --ship-scrim: rgb(17 24 39 / 0.32);
        --ship-text-inverse: #ffffff;
        --ship-inverse-wash: rgb(255 255 255 / 0.10);
        --ship-inverse-faint: rgb(255 255 255 / 0.20);
        --ship-action: #111827;
        --ship-action-hover: #1f2937;
        --ship-on-action: #f3f4f6;
        --ship-on-success: #ffffff;
        --ship-on-warning: #ffffff;
        --ship-on-danger: #ffffff;
        --ship-on-info: #ffffff;
        --ship-on-neutral: #ffffff;
        --ship-on-ambiguous: #ffffff;
        --ship-on-selected: #111827;
        --ship-link: #5d47de;
        --ship-link-dark: #b7acff;
        --ship-selected: #8470ff;
        --ship-selected-muted: #f1eeff;
        --ship-focus-ring: #8470ff;
        --ship-accent: #8470ff;
        --ship-focus-ring-soft: color-mix(in srgb, var(--ship-focus-ring) 25%, transparent);
        --ship-scrim-strong: color-mix(in srgb, var(--ship-text-primary) 85%, transparent);
    }
}
