/* ===========================================================================
   HAOS design system
   ---------------------------------------------------------------------------
   Apple's app/support craft, not its marketing language.  Every value traces to
   apple.com's served CSS (docs/haos-apple-redesign/APPLE-MEASURED.md) or to the
   HIG; nothing here is invented.

   SAFETY CONTRACT — read before editing.

   1.  FACE NAMES.  home.html and tracker.html already list `Inter` in their
       --sans stack, so a face called "Inter" would switch them the moment it
       ships.  These are "HAOS Sans" / "HAOS Arabic", verified unused.

   2.  TOKEN NAMES.  Defining a token a page already REFERENCES activates a
       dormant var() fallback branch.  Two dangling references exist and are
       never defined here:
         --panel-2   atlas.js:1833, inside a 3-deep var() chain
         --s-*       tasks.html:1002 builds these at RUNTIME by string
                     concatenation, so they cannot be enumerated statically
       Re-check any new name with:  bin/haos_tests/token-refs.py --ds-<name>

   3.  EVERYTHING VISUAL IS SCOPED TO  html.haos-ds.
       A page opts in by adding that class.  Until it does, this file changes
       nothing about it.  That is how 29 pages migrate one at a time on a
       surface served live with no isolation — a build marker, invisible to
       users, flipped exactly once per page.
       The :root token block is NOT scoped: tokens are inert until consumed.

   4.  TRACKING IS LTR-ONLY.  Negative letter-spacing breaks Arabic cursive
       joining, and the deck carries 64,723 Arabic characters.

   Licences: both faces are SIL OFL 1.1, shipped beside them.  SF Pro is never
   used or redistributed.
   =========================================================================== */

/* ---------------------------------------------------------------- faces --- */

@font-face {
  font-family: "HAOS Sans";
  src: url("HAOSSans.22f0f6602adf.woff2") format("woff2-variations"),
       url("HAOSSans.22f0f6602adf.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2190-21BB, U+2212, U+2215, U+2300-23FF, U+25A0-25FF,
                 U+2600-26FF, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "HAOS Arabic";
  src: url("HAOSArabic.fdc31c1c128a.woff2") format("woff2-variations"),
       url("HAOSArabic.fdc31c1c128a.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF,
                 U+FE70-FEFF, U+200C-200F, U+2010-2011, U+204F, U+2E41;
}

/* ---------------------------------------------------------------- tokens --- */

:root {
  color-scheme: light;
  /* type — 8 roles, 2 weights.  Apple: heading = size+4, reading = size+8.  */
  --ds-font-sans: "HAOS Sans", "HAOS Arabic", -apple-system, BlinkMacSystemFont,
                  "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --ds-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
                  "Liberation Mono", monospace;

  --ds-size-display: 32px;    --ds-line-display: 36px;    --ds-track-display: .004em;
  --ds-size-title:   24px;    --ds-line-title:   28px;    --ds-track-title:   .009em;
  --ds-size-heading: 19px;    --ds-line-heading: 27px;    --ds-track-heading: .012em;
  --ds-size-body:    15px;    --ds-line-body:    23px;    --ds-track-body:   -.012em;
  --ds-size-secondary:14px;   --ds-line-secondary:20px;   --ds-track-secondary:-.016em;
  --ds-size-caption: 13px;    --ds-line-caption: 18px;    --ds-track-caption:-.014em;
  --ds-size-label:   12px;    --ds-line-label:   16px;    --ds-track-label:  -.010em;

  --ds-weight-regular: 400;
  --ds-weight-strong:  600;

  /* colour — Apple's grey ladder.  The ROLES invert between themes:
     #6E6E73 is secondary in light and tertiary in dark, #86868B the reverse. */
  /* An APP ladder, not a document one. apple.com puts white on the page and
     grey on the card; a dense deck inverts that so content separates by
     SURFACE instead of by a border -- which is what macOS itself does, and
     what HAOS already did. --ds-bg stays as an alias so nothing breaks. */
  --ds-canvas:    #F5F5F7;   /* the page ground                             */
  --ds-surface:   #FFFFFF;   /* a raised card                               */
  --ds-surface-2: #E8E8ED;   /* an inset -- MUST differ from the canvas, or  */
                             /* anything using it outside a card vanishes    */
  --ds-bg:        var(--ds-canvas);
  --ds-ink:       #1D1D1F;   /* on --ds-canvas: 15.46:1 light                */
  --ds-ink-2:     #6E6E73;   /* on --ds-canvas:  4.66:1 light  -- AA, thin   */
  --ds-ink-3:     #757579;   /* 4.59:1 on a white card — safe at body size.  */
                             /* Was #86868B/3.62:1, shipped as "large only"; */
                             /* pages used it for dense meta text anyway, so */
                             /* 222 of 749 contrast failures came from one   */
                             /* token documented as unsafe instead of fixed. */
  --ds-hairline:  rgba(0,0,0,.16);   /* alpha, not grey — sits on any ground */
  --ds-hairline-soft: rgba(0,0,0,.08);

  /* accent — Signal Teal, owner ruling 2026-09-21.  Hue 192: 18 deg from
     Apple blue, 51 from Tailwind indigo, near-complementary to HEMFA's brand
     red #D51C28 so the two sharpen each other — and it LEAVES RED FREE TO
     MEAN DANGER, which is the whole reason it is not the brand colour.      */
  --ds-accent:      #0881A0;   /* white on it 4.51:1 — fills, one role only  */
  --ds-accent-text: #07728D;   /* on white   5.53:1 — links, icons           */
  --ds-accent-tint: rgba(8,129,160,.12);

  /* separate tokens per role, so the accent stops doing five jobs at once
     (council R2-6: the ">=5 roles" finding is about the TOKEN MODEL).       */
  --ds-focus:     #0881A0;
  --ds-selection: rgba(8,129,160,.20);

  /* signals — the --haos-* set already injected into every page by
     haos-session.js:202-220, adopted rather than reinvented.  Text sits at
     full strength on an OPAQUE tint, never coloured text on a low-alpha wash. */
  --ds-ok:     #1B6B45;  --ds-ok-bg:     #E4F2EA;  --ds-ok-line:     #9CCBB4;
  --ds-warn:   #8A5A00;  --ds-warn-bg:   #FAEFD9;  --ds-warn-line:   #D8B978;
  --ds-danger: #A82A1F;  --ds-danger-bg: #FAE6E3;  --ds-danger-line: #DBA097;
  --ds-info:   #15568F;  --ds-info-bg:   #E2EDF8;  --ds-info-line:   #9BBEDD;

  /* shape — 5 radii + a pill, down from 44 declarations */
  --ds-radius-badge:   6px;
  --ds-radius-control: 8px;
  --ds-radius-field:  12px;
  --ds-radius-card:   18px;
  --ds-radius-modal:  28px;
  --ds-radius-pill:  980px;

  /* depth — offset, not centred, as Apple does it. 3 values, down from 60. */
  --ds-shadow-1: 0 1px 2px rgba(0,0,0,.04);
  --ds-shadow-2: 8px 8px 16px rgba(0,0,0,.08);
  --ds-shadow-3: 0 12px 32px rgba(0,0,0,.12);

  /* space — a 4pt grid, down from 61 values.  Hairlines and optical
     corrections are EXEMPT: these are defaults, not a straitjacket.         */
  --ds-space-1:  4px;  --ds-space-2:  8px;  --ds-space-3: 12px;
  --ds-space-4: 16px;  --ds-space-5: 20px;  --ds-space-6: 24px;
  --ds-space-7: 32px;  --ds-space-8: 40px;  --ds-space-9: 48px;
  --ds-space-10:64px;

  /* motion — Apple's two curves are 78% of its own usage */
  --ds-ease:      cubic-bezier(.4, 0, .6, 1);
  --ds-ease-in:   cubic-bezier(0, 0, .2, 1);
  --ds-dur-fast:  100ms;
  --ds-dur-base:  160ms;
  --ds-dur-slow:  240ms;
  --ds-dur-enter: 400ms;

  /* glass — FLOATING LAYER ONLY (liquid-glass.md > The two layers).
     Apple's own nav values.                                                 */
  --ds-glass-blur: blur(20px) saturate(180%);
  --ds-glass-bg:   rgba(250,250,252,.92);
  --ds-nav-h:      44px;

  /* targets */
  --ds-target:     44px;
  --ds-target-min: 28px;
}

/* dark — pure black ground, and text is #F5F5F7, never #FFF (Apple's own
   softened white).  Declared for BOTH the media query and the explicit stamp,
   so an OS preference and a user choice each win in their own direction.    */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ds-canvas:    #000000;
    --ds-surface:   #161617;
    --ds-surface-2: #1D1D1F;
    --ds-bg:        var(--ds-canvas);
    --ds-ink:       #F5F5F7;
    --ds-ink-2:     #86868B;
    --ds-ink-3:     #8E8E93;
    --ds-hairline:  rgba(255,255,255,.24);
    --ds-hairline-soft: rgba(255,255,255,.10);
    --ds-accent:      #087F9C;
    --ds-accent-text: #6FC5DC;
    --ds-accent-tint: rgba(111,197,220,.16);
    --ds-focus:     #6FC5DC;
    --ds-selection: rgba(111,197,220,.24);
    --ds-ok:     #79D3A2;  --ds-ok-bg:     #14291F;  --ds-ok-line:     #2F6B4C;
    --ds-warn:   #E3B667;  --ds-warn-bg:   #2C2415;  --ds-warn-line:   #6E5B2E;
    --ds-danger: #F0907A;  --ds-danger-bg: #3B211B;  --ds-danger-line: #7C4235;
    --ds-info:   #87BCF0;  --ds-info-bg:   #17293A;  --ds-info-line:   #395E80;
    --ds-shadow-1: 0 1px 2px rgba(0,0,0,.4);
    --ds-shadow-2: 8px 8px 24px rgba(0,0,0,.5);
    --ds-shadow-3: 0 12px 40px rgba(0,0,0,.6);
    --ds-glass-bg: rgba(22,22,23,.88);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --ds-canvas:    #000000;
  --ds-surface:   #161617;
  --ds-surface-2: #1D1D1F;
  --ds-bg:        var(--ds-canvas);
  --ds-ink:       #F5F5F7;
  --ds-ink-2:     #86868B;
  --ds-ink-3:     #8E8E93;
  --ds-hairline:  rgba(255,255,255,.24);
  --ds-hairline-soft: rgba(255,255,255,.10);
  --ds-accent:      #087F9C;
  --ds-accent-text: #6FC5DC;
  --ds-accent-tint: rgba(111,197,220,.16);
  --ds-focus:     #6FC5DC;
  --ds-selection: rgba(111,197,220,.24);
  --ds-ok:     #79D3A2;  --ds-ok-bg:     #14291F;  --ds-ok-line:     #2F6B4C;
  --ds-warn:   #E3B667;  --ds-warn-bg:   #2C2415;  --ds-warn-line:   #6E5B2E;
  --ds-danger: #F0907A;  --ds-danger-bg: #3B211B;  --ds-danger-line: #7C4235;
  --ds-info:   #87BCF0;  --ds-info-bg:   #17293A;  --ds-info-line:   #395E80;
  --ds-shadow-1: 0 1px 2px rgba(0,0,0,.4);
  --ds-shadow-2: 8px 8px 24px rgba(0,0,0,.5);
  --ds-shadow-3: 0 12px 40px rgba(0,0,0,.6);
  --ds-glass-bg: rgba(22,22,23,.88);
}

/* =========================================================================
   Everything below is scoped to html.haos-ds and applies ONLY to a page that
   has opted in.  Nothing here reaches an un-migrated page.
   ========================================================================= */

/* ------------------------------------------------------------- typography --- */

html.haos-ds {
  background: var(--ds-canvas);     /* the root paints too: a view transition never shows the UA default */
  font-family: var(--ds-font-sans);
  font-optical-sizing: auto;
  font-size: var(--ds-size-body);
  line-height: var(--ds-line-body);
  -webkit-text-size-adjust: 100%;
}
html.haos-ds body {
  margin: 0;
  background: var(--ds-canvas);
  color: var(--ds-ink);
  font-family: var(--ds-font-sans);
  font-size: var(--ds-size-body);
  line-height: var(--ds-line-body);
  font-weight: var(--ds-weight-regular);
  -webkit-font-smoothing: antialiased;
}

/* Tracking is LTR-only: negative letter-spacing breaks Arabic joining. */
html.haos-ds:not([dir="rtl"]) body { letter-spacing: var(--ds-track-body); }
html.haos-ds[dir="rtl"] body { letter-spacing: normal; }

html.haos-ds ::selection { background: var(--ds-selection); }

html.haos-ds .ds-display   { font-size: var(--ds-size-display);   line-height: var(--ds-line-display);   font-weight: var(--ds-weight-strong); }
html.haos-ds .ds-title     { font-size: var(--ds-size-title);     line-height: var(--ds-line-title);     font-weight: var(--ds-weight-strong); }
html.haos-ds .ds-heading   { font-size: var(--ds-size-heading);   line-height: var(--ds-line-heading);   font-weight: var(--ds-weight-strong); }
html.haos-ds .ds-body      { font-size: var(--ds-size-body);      line-height: var(--ds-line-body); }
html.haos-ds .ds-secondary { font-size: var(--ds-size-secondary); line-height: var(--ds-line-secondary); color: var(--ds-ink-2); }
html.haos-ds .ds-caption   { font-size: var(--ds-size-caption);   line-height: var(--ds-line-caption);   color: var(--ds-ink-2); }
html.haos-ds .ds-label     { font-size: var(--ds-size-label);     line-height: var(--ds-line-label);     font-weight: var(--ds-weight-strong); color: var(--ds-ink-2); }

html.haos-ds:not([dir="rtl"]) .ds-display   { letter-spacing: var(--ds-track-display); }
html.haos-ds:not([dir="rtl"]) .ds-title     { letter-spacing: var(--ds-track-title); }
html.haos-ds:not([dir="rtl"]) .ds-heading   { letter-spacing: var(--ds-track-heading); }
html.haos-ds:not([dir="rtl"]) .ds-secondary { letter-spacing: var(--ds-track-secondary); }
html.haos-ds:not([dir="rtl"]) .ds-caption   { letter-spacing: var(--ds-track-caption); }
html.haos-ds:not([dir="rtl"]) .ds-label     { letter-spacing: var(--ds-track-label); }

/* Bare headings are a DEFAULT, not an override — note :where(), which has
   specificity ZERO.  Styling them as `html.haos-ds h1` (0,1,2) outranked every
   page's own heading rule (a bare `h1{}` is (0,0,1), and even `.card h2` is
   only (0,1,1)), which is the same mechanism that inverted the canvas.

   Measured damage before this fix, on versions.html:
     .card h2   a 14px uppercase eyebrow rendered at 24px, still uppercase,
                with its own .05em now scaled against 24px = 1.2px tracking.
     h1         took the DS's 32px but kept the page's own -.02em, which is
                relative to the NEW size: -0.64px, where this scale intends
                +0.13px at 32px. Wrong magnitude and wrong sign.

   At specificity zero these give a page with no heading rules the full correct
   treatment, and never argue with a page that has its own. Tracking ships
   alongside the size so the two can never be set by different rules again.

   The line-height here is UNITLESS on purpose. A px leading paired with a size
   that a page overrides is the same defect one level down: versions.html kept
   its own 22px h1 and took the system's 36px leading, a 1.64 ratio on a
   heading. Unitless scales with whichever size wins. Apple's rule is size+4px,
   which is 1.125 at 32px and 1.18 at 22px -- 1.15 holds across that range. */
:where(html.haos-ds) h1 {
  font-size: var(--ds-size-display); line-height: 1.15;
  font-weight: var(--ds-weight-strong); margin: 0 0 var(--ds-space-3);
  text-wrap: balance;
}
:where(html.haos-ds) h2 {
  font-size: var(--ds-size-title); line-height: 1.16;
  font-weight: var(--ds-weight-strong); margin: 0 0 var(--ds-space-3);
  text-wrap: balance;
}
:where(html.haos-ds) h3 {
  font-size: var(--ds-size-heading); line-height: 1.2;
  font-weight: var(--ds-weight-strong); margin: 0 0 var(--ds-space-2);
}
:where(html.haos-ds:not([dir="rtl"])) h1 { letter-spacing: var(--ds-track-display); }
:where(html.haos-ds:not([dir="rtl"])) h2 { letter-spacing: var(--ds-track-title); }
:where(html.haos-ds:not([dir="rtl"])) h3 { letter-spacing: var(--ds-track-heading); }

/* The UA default for <b>/<strong> is 700, which is off a two-weight scale and
   is where the last stray weights on the deck came from -- no stylesheet
   declares them, so no sweep can find them. */
html.haos-ds b,
html.haos-ds strong,
html.haos-ds th { font-weight: var(--ds-weight-strong); }

/* Identifiers: tabular figures align the DIGITS; a grid column aligns the
   variable-length prefixes.  bdi keeps QA-84 and 19.0.5.4.15 LTR under RTL. */
html.haos-ds .ds-id {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  unicode-bidi: isolate;
  direction: ltr;
}
html.haos-ds code, html.haos-ds kbd, html.haos-ds samp, html.haos-ds .ds-mono {
  font-family: var(--ds-font-mono);
  font-size: .95em;
}

/* --------------------------------------------------------------- focus --- */
/* One ring, everywhere, keyboard-only — replacing three different treatments
   and 20 `outline:none` rules.                                              */
html.haos-ds :focus-visible {
  outline: 2px solid var(--ds-focus);
  outline-offset: 2px;
  border-radius: var(--ds-radius-control);
}
html.haos-ds :focus:not(:focus-visible) { outline: none; }

/* --------------------------------------------------------------- layout --- */
html.haos-ds .ds-wrap {
  width: 100%;
  max-width: 1180px;
  margin-inline: auto;
  padding-inline: var(--ds-space-5);
}
html.haos-ds .ds-stack  { display: flex; flex-direction: column; gap: var(--ds-space-4); }
html.haos-ds .ds-row    { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-3); }
html.haos-ds .ds-spacer { flex: 1 1 auto; }

/* ------------------------------------------------------------------ nav --- */
/* THE ONLY GLASS ON THE DECK.  Content scrolls under it; cards and tables are
   opaque.  Apple's exact nav values.                                        */
html.haos-ds .ds-nav {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  min-height: var(--ds-nav-h);
  padding: 0 var(--ds-space-5);
  background: var(--ds-glass-bg);
  -webkit-backdrop-filter: var(--ds-glass-blur);
  backdrop-filter: var(--ds-glass-blur);
  border-bottom: 1px solid var(--ds-hairline-soft);
}
html.haos-ds .ds-nav a,
html.haos-ds .ds-nav .ds-nav-item {
  display: inline-flex;
  align-items: center;
  min-height: var(--ds-target-min);
  padding: var(--ds-space-1) var(--ds-space-3);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-size-secondary);
  line-height: var(--ds-line-secondary);
  color: var(--ds-ink);
  opacity: .72;                       /* Apple changes OPACITY, not colour */
  text-decoration: none;
  white-space: nowrap;
  transition: opacity var(--ds-dur-base) var(--ds-ease);
}
html.haos-ds .ds-nav a:hover { opacity: 1; }
html.haos-ds .ds-nav a[aria-current],
html.haos-ds .ds-nav .is-current {
  opacity: 1;
  font-weight: var(--ds-weight-strong);
  background: var(--ds-accent-tint);
  color: var(--ds-accent-text);
}

/* The shared chrome's own controls must clear the floor the system sets:
   the two skip links measured 20px and the brand wordmark 23px. */
html.haos-ds .haos-skip,
html.haos-ds .haos-brand {
  min-height: var(--ds-target-min);
  display: inline-flex;
  align-items: center;
}

/* --------------------------------------------- legacy page headers --- */
/* Seven pages (home, tracker, hq, floor, mission, brain, council) predate the
   shared nav and carry their own `header.bar` + `nav.nav` markup. Their LINKS
   are already rebuilt from the shared list by HAOS.navLinks(), so the menu
   cannot drift -- but their CHROME still had its own look: a 61-64px bar with
   blur(12px) saturate(1.2), against the shared bar's 44px and Apple's
   blur(20px) saturate(1.8). That is the 2026-07-16 "two separate systems" bug
   showing through as appearance rather than as content.

   Styled here rather than by rewriting seven pages' markup:
   `html.haos-ds header.bar` is (0,1,2) and a page's own `header.bar` is (0,1,1),
   so these win without touching the pages at all.                           */
html.haos-ds header.bar,
html.haos-ds header.topbar {
  position: sticky;
  top: 0;
  z-index: 60;
  gap: var(--ds-space-4);
  /* The height patch above was never going to hold on its own: these headers
     keep flex-wrap:wrap from their own CSS, so at 390px they grew to 75-125px
     against a 48px spec -- up to 2.8x. min-height cannot shrink a bar that has
     wrapped onto three rows. The tab strip scrolls instead (nav.nav below). */
  flex-wrap: nowrap;
  min-height: var(--ds-nav-h);
  padding: 0 var(--ds-space-5);
  background: var(--ds-glass-bg);
  -webkit-backdrop-filter: var(--ds-glass-blur);
  backdrop-filter: var(--ds-glass-blur);
  border-bottom: 1px solid var(--ds-hairline-soft);
  align-items: center;
}
/* ---- bar layout: correct at EVERY width -------------------------------
   flex-wrap only engages when a row genuinely cannot fit, so a roomy desktop
   bar is still one 44px row -- but a cramped one wraps into clean rows instead
   of painting its children on top of each other. Locking these behind a phone
   breakpoint left 834-1100px with nowrap and nothing to give, which is where
   the densest page still collided. */
html.haos-ds header.bar,
html.haos-ds header.topbar,
html.haos-ds .haos-nav {
  flex-wrap: wrap;
  row-gap: var(--ds-space-2);
  align-items: center;
}
/* brand first and pinned left; it yields first, truncating rather than
   pushing the controls onto a row of their own */
html.haos-ds header.bar > .wordmark,
html.haos-ds header.bar > .brand,
html.haos-ds header.topbar > .wordmark,
html.haos-ds header.topbar > .brand,
html.haos-ds .haos-nav > .haos-brand {
  order: 1;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* the controls cluster second, pinned RIGHT -- never stranded mid-strip, which
   is how the bell and avatar ended up between the brand and the tabs */
html.haos-ds header.bar > .bar-right,
html.haos-ds header.topbar > .topbar-right,
html.haos-ds header.bar > .haos-user,
html.haos-ds header.topbar > .haos-user,
html.haos-ds .haos-nav > .haos-user {
  order: 2;
  margin-inline-start: auto;
  flex: 0 0 auto;
}
/* the tab strip last; when it wraps it takes a whole row and scrolls */
html.haos-ds header.bar > nav.nav,
html.haos-ds header.topbar > nav.nav,
html.haos-ds .haos-nav > .haos-tabs {
  order: 3;
  min-width: 0;
  overflow-x: auto;
}

@media (max-width: 1100px) {
  html.haos-ds header.bar,
  html.haos-ds header.topbar,
  html.haos-ds .haos-nav { padding-block: var(--ds-space-2); }

  /* A SPAN chip in the bar is a status READOUT ("Saved - 19d ago"); a BUTTON
     chip is an ACTION. Below this width the actions stay and the readouts go:
     the readout's information is on the page itself, and a brand plus five
     controls cannot share one row.
     DESCENDANT, not child -- the clock sits INSIDE .bar-right, so a `>`
     selector misses it entirely, which is why the first attempt still wrapped
     to three rows. */
  html.haos-ds header.bar .clock,
  html.haos-ds header.topbar .clock,
  html.haos-ds header.bar .live,
  html.haos-ds header.topbar .live,
  html.haos-ds header.bar .haos-clock,
  html.haos-ds header.topbar .haos-clock,
  html.haos-ds .haos-nav .haos-clock,
  html.haos-ds header.bar span.chip,
  html.haos-ds header.topbar span.chip,
  html.haos-ds .haos-nav span.chip { display: none; }
}

@media (max-width: 833px) {
  html.haos-ds header.bar,
  html.haos-ds header.topbar { min-height: 48px; }
  /* once it has wrapped, the strip owns its row outright */
  html.haos-ds header.bar > nav.nav,
  html.haos-ds header.topbar > nav.nav,
  html.haos-ds .haos-nav > .haos-tabs { flex: 1 0 100%; }
}

/* A flex child's default min-width is `auto`, which means "never shrink below
   your content". tracker.html's right-hand cluster carries one more control
   than home.html's, so at 390px it measured 429px wide and pushed the page to
   a 449px scrollWidth -- a real horizontal scrollbar. home.html, on the exact
   same code path, stayed at 390, which is what proves it is shrinkability and
   not the breakpoint. */
html.haos-ds .bar-right,
html.haos-ds .topbar-right,
html.haos-ds .haos-user {
  min-width: 0;
  flex-shrink: 1;
  /* ⛔ NEVER `overflow: hidden` here. It was added as belt-and-braces with the
     shrink fix and it CLIPPED the notification bell's dropdown, which is
     position:absolute inside this cluster -- the panel opened and was cut off
     at the header's edge, reading as "it opens behind the page". min-width:0
     and flex-shrink do all the shrinking on their own; the clip did nothing
     except break every popover anchored in here. */
}

/* The header carries backdrop-filter, which CREATES A STACKING CONTEXT -- so a
   panel inside it can never escape above the header's own z-index no matter
   how large its own is. Keep the header's context above page content, and give
   the popovers a z-index that wins inside it. */
html.haos-ds .hbell-dd,
html.haos-ds .haos-nav-menu { z-index: 90; }
html.haos-ds .bar-right > *,
html.haos-ds .topbar-right > * { flex: none; }

html.haos-ds nav.nav {
  display: flex;
  gap: var(--ds-space-1);
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  scroll-snap-type: x proximity;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 10px,
                      #000 calc(100% - 14px), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 10px,
                      #000 calc(100% - 14px), transparent 100%);
}
html.haos-ds nav.nav::-webkit-scrollbar { display: none; }

html.haos-ds nav.nav a,
html.haos-ds nav.nav .nav-link {
  scroll-snap-align: start;
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: var(--ds-target-min);
  padding: var(--ds-space-1) var(--ds-space-3);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-size-secondary);
  line-height: var(--ds-line-secondary);
  font-weight: var(--ds-weight-regular);
  letter-spacing: normal;
  color: var(--ds-ink-2);
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--ds-dur-base) var(--ds-ease),
              color var(--ds-dur-base) var(--ds-ease);
}
html.haos-ds nav.nav a:hover,
html.haos-ds nav.nav .nav-link:hover { color: var(--ds-ink); }
html.haos-ds nav.nav a.active,
html.haos-ds nav.nav .nav-link.active,
html.haos-ds nav.nav a[aria-current] {
  font-weight: var(--ds-weight-strong);
  color: var(--ds-accent-text);
  background: var(--ds-accent-tint);
}

@media (prefers-reduced-transparency: reduce) {
  html.haos-ds header.bar,
  html.haos-ds header.topbar {
    background: var(--ds-canvas);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* --------------------------------------------------------------- button --- */
html.haos-ds .ds-btn {
  --_bw: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  min-height: 36px;
  /* Apple's mechanic: bordered and borderless buttons match optically. */
  padding-block: calc(var(--ds-space-2) - var(--_bw));
  padding-inline: calc(var(--ds-space-4) - var(--_bw));
  border: var(--_bw) solid transparent;
  border-radius: var(--ds-radius-pill);      /* pill ONLY when auto-width */
  font-family: inherit;
  font-size: var(--ds-size-secondary);
  line-height: var(--ds-line-secondary);
  font-weight: var(--ds-weight-regular);
  cursor: pointer;
  background: var(--ds-surface-2);
  color: var(--ds-ink);
  transition: opacity var(--ds-dur-base) var(--ds-ease),
              background-color var(--ds-dur-base) var(--ds-ease);
}
html.haos-ds .ds-btn:hover  { opacity: .84; }
html.haos-ds .ds-btn:active { opacity: .68; }
html.haos-ds .ds-btn[disabled],
html.haos-ds .ds-btn[aria-disabled="true"] { opacity: .4; cursor: default; }
html.haos-ds .ds-btn--primary { background: var(--ds-accent); color: #fff; }
html.haos-ds .ds-btn--quiet   { background: transparent; color: var(--ds-accent-text); }
html.haos-ds .ds-btn--danger  { background: var(--ds-danger); color: #fff; }
html.haos-ds .ds-btn--large   { min-height: var(--ds-target); }
/* Full-width buttons are rounded rects, not pills — Apple's own rule. */
html.haos-ds .ds-btn--block   { display: flex; width: 100%; border-radius: var(--ds-radius-control); }

/* Icon-only controls still reach a 44px HIT REGION even when the visual box is
   smaller — the deck has 3 buttons under 28px on every page today.          */
html.haos-ds .ds-icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--ds-target-min);
  height: var(--ds-target-min);
  border: 0;
  background: transparent;
  color: var(--ds-ink-2);
  border-radius: var(--ds-radius-control);
  cursor: pointer;
}
html.haos-ds .ds-icon-btn::before {
  content: "";
  position: absolute;
  inset: calc((var(--ds-target-min) - var(--ds-target)) / 2);
}
html.haos-ds .ds-icon-btn:hover { color: var(--ds-ink); background: var(--ds-surface-2); }

/* ------------------------------------------- shape, bridged --------------
   The component rules below this point are named .ds-card / .ds-badge / .ds-nav,
   and the pages do not use those class names -- they use .card, .kpi, .badge,
   .chip, .pill, .detail, built by each page's own JS. So the COLOUR half of the
   migration reached them (their tokens were re-pointed at --ds-*) and the SHAPE
   half reached nothing: cards still carried 14px, 12px and 8px radii, badges a
   20px pill, against a declared scale of 6/8/12/18/28.

   These rules key on the classes that actually exist. They deliberately OUTRANK
   a page's own rule -- html.haos-ds .card is (0,2,1) against a bare .card at
   (0,1,0) -- which is the opposite of how headings are handled a few hundred
   lines above, and the difference is intentional:

     a heading size is a per-page DESIGN DECISION, often calibrated against that
     page's content, so the system offers it as a :where() default;
     a card radius of 14 vs 12 vs 8 is DRIFT, not intent -- 44 radius
     declarations across 29 pages with no system behind them -- and collapsing
     it is the entire point.                                                  */

html.haos-ds .card,
html.haos-ds .kpi,
html.haos-ds .panel,
html.haos-ds .detail { border-radius: var(--ds-radius-card); }

html.haos-ds .badge,
html.haos-ds .chip,
html.haos-ds .pill,
html.haos-ds .tag { border-radius: var(--ds-radius-badge); }

html.haos-ds .btn,
html.haos-ds .icon-btn,
html.haos-ds .seg-btn { border-radius: var(--ds-radius-control); }

html.haos-ds input,
html.haos-ds select,
html.haos-ds textarea { border-radius: var(--ds-radius-field); }

html.haos-ds .modal,
html.haos-ds .sheet,
html.haos-ds .drawer,
html.haos-ds .modalcard { border-radius: var(--ds-radius-modal); }

/* A hairline OR a shadow, never both -- the Calm generation ships both. */
html.haos-ds .card { box-shadow: var(--ds-shadow-1); }

/* ---------------------------------------------------------------- card --- */
/* A hairline OR a shadow — never both.  The Calm generation ships both. */
html.haos-ds .ds-card {
  background: var(--ds-surface);   /* white on a grey canvas */
  border-radius: var(--ds-radius-card);
  padding: var(--ds-space-5);
  box-shadow: var(--ds-shadow-1);
}
html.haos-ds .ds-card--flat { box-shadow: none; border: 1px solid var(--ds-hairline-soft); }

/* --------------------------------------------------------------- badge --- */
/* One component.  Replaces .chip/.pill/.badge — three names, 29 definitions. */
html.haos-ds .ds-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  padding: 3px 9px;
  border-radius: var(--ds-radius-badge);
  font-size: var(--ds-size-label);
  line-height: var(--ds-line-label);
  font-weight: var(--ds-weight-strong);
  background: var(--ds-surface-2);
  color: var(--ds-ink-2);
  white-space: nowrap;
}
html.haos-ds .ds-badge--ok     { background: var(--ds-ok-bg);     color: var(--ds-ok); }
html.haos-ds .ds-badge--warn   { background: var(--ds-warn-bg);   color: var(--ds-warn); }
html.haos-ds .ds-badge--danger { background: var(--ds-danger-bg); color: var(--ds-danger); }
html.haos-ds .ds-badge--info   { background: var(--ds-info-bg);   color: var(--ds-info); }

/* ---------------------------------------------------- the ledger row ------ */
/* The signature.  Reserved for REGISTERS — queues, audit trails, QA records,
   versions, promotions.  Never forms, never prose.
   The dot NEVER carries the state alone: the written state is always present
   (accessibility.md — nothing is conveyed by colour alone).                 */
html.haos-ds .ds-ledger { display: flex; flex-direction: column; }
html.haos-ds .ds-ledger-row {
  display: grid;
  grid-template-columns: 8px minmax(72px, auto) 1fr auto;
  align-items: center;
  gap: var(--ds-space-3);
  min-height: var(--ds-target);
  padding: var(--ds-space-2) var(--ds-space-3);
  border-bottom: 1px solid var(--ds-hairline-soft);
  color: inherit;
  text-decoration: none;
}
html.haos-ds .ds-ledger-row:last-child { border-bottom: 0; }
html.haos-ds .ds-ledger-row:hover { background: var(--ds-surface-2); }
html.haos-ds .ds-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ds-ink-3); }
html.haos-ds .ds-dot--ok     { background: var(--ds-ok); }
html.haos-ds .ds-dot--warn   { background: var(--ds-warn); }
html.haos-ds .ds-dot--danger { background: var(--ds-danger); }
html.haos-ds .ds-dot--info   { background: var(--ds-info); }
html.haos-ds .ds-ledger-what {
  color: var(--ds-ink-2);
  font-size: var(--ds-size-caption);
  line-height: var(--ds-line-caption);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------- table --- */
/* The deck is mostly tables, already its most consistent component.
   No striping — hairlines do the work.                                      */
html.haos-ds .ds-table { width: 100%; border-collapse: collapse; }
html.haos-ds .ds-table th {
  font-size: var(--ds-size-label);
  line-height: var(--ds-line-label);
  font-weight: var(--ds-weight-strong);
  text-transform: none;              /* the deck shouts on 23 of 29 pages */
  color: var(--ds-ink-2);
  text-align: start;
  padding: var(--ds-space-2) var(--ds-space-4);
  border-bottom: 1px solid var(--ds-hairline);
  white-space: nowrap;
}
html.haos-ds .ds-table td {
  font-size: var(--ds-size-body);
  line-height: var(--ds-line-body);
  padding: var(--ds-space-3) var(--ds-space-4);
  border-bottom: 1px solid var(--ds-hairline-soft);
}
html.haos-ds .ds-table tbody tr:hover { background: var(--ds-surface-2); }
html.haos-ds .ds-table thead th[aria-sort] { cursor: pointer; }
/* Sticky header gains a scroll edge effect — 12 sticky pages lack one. */
html.haos-ds .ds-table--sticky thead th {
  position: sticky;
  top: var(--ds-nav-h);
  z-index: 2;
  background: var(--ds-surface);
  box-shadow: 0 1px 0 var(--ds-hairline);
}
html.haos-ds .ds-table-scroll { overflow-x: auto; }

/* ---------------------------------------------------------------- field --- */
html.haos-ds .ds-field input,
html.haos-ds .ds-field select,
html.haos-ds .ds-field textarea {
  font-family: inherit;
  font-size: var(--ds-size-body);
  line-height: var(--ds-line-body);
  color: var(--ds-ink);
  background: var(--ds-surface);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-field);
  padding: var(--ds-space-2) var(--ds-space-3);
  min-height: var(--ds-target);
  width: 100%;
  box-sizing: border-box;
}
html.haos-ds .ds-field select { appearance: none; padding-inline-end: var(--ds-space-7); }
html.haos-ds .ds-field textarea { min-height: calc(var(--ds-target) * 2); resize: vertical; }
html.haos-ds .ds-field input::placeholder,
html.haos-ds .ds-field textarea::placeholder { color: var(--ds-ink-3); }
html.haos-ds .ds-field input[aria-invalid="true"],
html.haos-ds .ds-field textarea[aria-invalid="true"] {
  border-color: var(--ds-danger);
  background: var(--ds-danger-bg);
}
/* iOS Safari auto-zooms any focused field under 16px. */
@media (max-width: 767px) {
  html.haos-ds .ds-field input,
  html.haos-ds .ds-field select,
  html.haos-ds .ds-field textarea { font-size: 16px; }
}

/* --------------------------------------------------------------- modal --- */
html.haos-ds .ds-scrim {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(0,0,0,.28);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  display: grid;
  place-items: center;
  padding: var(--ds-space-4);
}
html.haos-ds .ds-modal {
  background: var(--ds-surface);
  border-radius: var(--ds-radius-modal);
  box-shadow: var(--ds-shadow-3);
  padding: var(--ds-space-6);
  width: min(560px, 100%);
  max-height: calc(100dvh - var(--ds-space-8));
  overflow: auto;
}

/* ----------------------------------------------------------- tab strip --- */
html.haos-ds .ds-tabs {
  display: flex;
  gap: var(--ds-space-1);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* fade the edge so labels stop clipping mid-word with no affordance */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 12px,
                      #000 calc(100% - 12px), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 12px,
                      #000 calc(100% - 12px), transparent 100%);
}
html.haos-ds .ds-tab {
  scroll-snap-align: start;
  flex: none;
  min-height: var(--ds-target-min);
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--ds-size-secondary);
  font-weight: var(--ds-weight-regular);
  color: var(--ds-ink-2);
  cursor: pointer;
  border-radius: var(--ds-radius-control);
  white-space: nowrap;
}
html.haos-ds .ds-tab[aria-selected="true"] { color: var(--ds-ink); background: var(--ds-surface-2); }

/* --------------------------------------------------- toast / empty --- */
html.haos-ds .ds-toast {
  position: fixed;
  inset-block-end: var(--ds-space-5);
  inset-inline-end: var(--ds-space-5);
  z-index: 300;
  background: var(--ds-glass-bg);
  -webkit-backdrop-filter: var(--ds-glass-blur);
  backdrop-filter: var(--ds-glass-blur);
  border: 1px solid var(--ds-hairline-soft);
  border-radius: var(--ds-radius-field);
  box-shadow: var(--ds-shadow-2);
  padding: var(--ds-space-3) var(--ds-space-4);
  font-size: var(--ds-size-secondary);
  max-width: min(420px, calc(100vw - var(--ds-space-8)));
}
html.haos-ds .ds-empty {
  padding: var(--ds-space-8) var(--ds-space-5);
  text-align: center;
  color: var(--ds-ink-2);
  font-size: var(--ds-size-body);
}
html.haos-ds .ds-empty .ds-btn { margin-top: var(--ds-space-4); }

/* ------------------------------------------------- accessibility states --- */
@media (prefers-reduced-motion: reduce) {
  html.haos-ds *,
  html.haos-ds *::before,
  html.haos-ds *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}
/* Glass gets an opaque fallback — 0 of 29 pages implement this today, yet 9
   already use backdrop-filter.                                              */
@media (prefers-reduced-transparency: reduce) {
  html.haos-ds .ds-nav,
  html.haos-ds .ds-toast {
    background: var(--ds-bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  html.haos-ds .ds-scrim {
    background: rgba(0,0,0,.72);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
@media (prefers-contrast: more) {
  html.haos-ds { --ds-hairline: rgba(0,0,0,.42); --ds-hairline-soft: rgba(0,0,0,.24); }
  html.haos-ds .ds-nav,
  html.haos-ds .ds-toast { background: var(--ds-bg); backdrop-filter: none; }
  html.haos-ds .ds-badge { outline: 1px solid currentColor; }
}
@media (forced-colors: active) {
  html.haos-ds .ds-nav,
  html.haos-ds .ds-card,
  html.haos-ds .ds-modal { border: 1px solid CanvasText; }
  html.haos-ds .ds-dot { forced-color-adjust: none; }
}

/* =========================================================================
   COMPOSITION — what makes a page read as Apple rather than as a clean admin
   panel. The tokens were never the gap; this is.

   Measured on the OR-000017 detail page: a grey client bar INSIDE a white
   card WITH a 1px border, FOUR equal-weight buttons in a row (two red-outlined),
   metadata set as a table, and the title repeated three times. Every one of
   those is a composition choice, and each is the opposite of what Apple does:

     Apple separates with SURFACE AND SPACE, never a border inside a border.
     Apple has ONE filled action; everything else is quiet; destructive is text.
     Apple sets metadata as a definition list with air around it.

   These rules deliberately outrank a page's own -- composition drift is the
   defect, exactly as radius drift was. Display and cursor are never touched,
   because on some pages a .card is a link.
   ========================================================================= */

/* ---- cards: a surface, not a box ----------------------------------------- */
html.haos-ds .card,
html.haos-ds .panel,
html.haos-ds .detail {
  border: 0 !important;                    /* the 1px box is the admin-panel tell */
  box-shadow: var(--ds-shadow-1);
  background: var(--ds-surface);
  padding: var(--ds-space-6);
}
/* a box inside a box becomes an inset: a tone, never a second border */
html.haos-ds .card .card,
html.haos-ds .card .box,
html.haos-ds .card .cli,
html.haos-ds .card .client-bar,
html.haos-ds .card .sub-card {
  border: 0 !important;
  box-shadow: none;
  background: var(--ds-surface-2);
  border-radius: var(--ds-radius-field);
  padding: var(--ds-space-3) var(--ds-space-4);
}
html.haos-ds .card > * + * { margin-top: 0; }

/* ---- section labels inside a card: a caption, not a heading ------------- */
html.haos-ds .card > .grp,
html.haos-ds .card > h4,
html.haos-ds .card > .section-title,
html.haos-ds .card .label {
  font-size: var(--ds-size-caption);
  line-height: var(--ds-line-caption);
  font-weight: var(--ds-weight-strong);
  color: var(--ds-ink-2);
  text-transform: none;
  letter-spacing: normal;
  margin: 0 0 var(--ds-space-3);
}

/* ---- metadata: a definition list with air, never a table ---------------- */
html.haos-ds dl.kv,
html.haos-ds .kv {
  display: grid;
  grid-template-columns: minmax(120px, max-content) minmax(0, 1fr);
  column-gap: var(--ds-space-6);
  row-gap: var(--ds-space-3);
  align-items: baseline;
  margin: var(--ds-space-4) 0 0;
}
html.haos-ds dl.kv dt,
html.haos-ds .kv dt {
  font-size: var(--ds-size-caption);
  line-height: var(--ds-line-body);
  font-weight: var(--ds-weight-regular);
  color: var(--ds-ink-2);
  margin: 0;
}
html.haos-ds dl.kv dd,
html.haos-ds .kv dd {
  font-size: var(--ds-size-body);
  line-height: var(--ds-line-body);
  color: var(--ds-ink);
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}
html.haos-ds dl.kv dd code,
html.haos-ds .kv dd code { font-size: .9em; color: var(--ds-ink-2); }

/* ---- actions: one filled primary, quiet secondaries, destructive as text - */
html.haos-ds .actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ds-space-2);
  margin-top: var(--ds-space-6);
  padding-top: var(--ds-space-4);
  border-top: 1px solid var(--ds-hairline-soft);   /* the ONE hairline a card gets */
}
html.haos-ds .actions .btn,
html.haos-ds .btn {
  min-height: 36px;
  padding: var(--ds-space-2) var(--ds-space-4);
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: var(--ds-surface-2);
  color: var(--ds-ink);
  font-size: var(--ds-size-secondary);
  font-weight: 500;
  box-shadow: none;
  transition: opacity var(--ds-dur-base) var(--ds-ease),
              background-color var(--ds-dur-base) var(--ds-ease);
}
html.haos-ds .btn:hover { opacity: .84; }
html.haos-ds .btn.primary,
html.haos-ds .btn.pri,
html.haos-ds .btn.ok,
html.haos-ds .btn[data-primary],
html.haos-ds button.primary {
  background: var(--ds-accent);
  color: #fff;
  font-weight: var(--ds-weight-strong);
}
/* Destructive is text, pushed to the far edge, and only turns red on hover.
   Two red-outlined buttons beside the primary is a fire alarm, not a UI. */
html.haos-ds .btn.danger,
html.haos-ds .btn.err,
html.haos-ds .btn.warn,
html.haos-ds .btn.ghost,
html.haos-ds .btn.cancel {
  background: transparent;
  color: var(--ds-ink-2);
  border: 0;
}
html.haos-ds .actions .btn.danger:first-of-type { margin-inline-start: auto; }
html.haos-ds .btn.sm { min-height: 28px; padding: var(--ds-space-1) var(--ds-space-3); font-size: var(--ds-size-caption); }
html.haos-ds .btn.danger:hover,
html.haos-ds .btn.err:hover,
html.haos-ds .btn.cancel:hover { color: var(--ds-danger); background: var(--ds-danger-bg); opacity: 1; }

/* ---- drop zones: an invitation, not a fenced lot ------------------------- */
html.haos-ds .drop,
html.haos-ds .dropzone {
  border: 0 !important;
  background: var(--ds-surface-2);
  border-radius: var(--ds-radius-field);
  padding: var(--ds-space-6);
  text-align: center;
  color: var(--ds-ink-2);
  font-size: var(--ds-size-secondary);
  transition: background-color var(--ds-dur-base) var(--ds-ease);
}
html.haos-ds .drop:hover,
html.haos-ds .drop.over,
html.haos-ds .dropzone.over { background: var(--ds-accent-tint); color: var(--ds-accent-text); }

/* ---- page title block ---------------------------------------------------- */
html.haos-ds .page-head,
html.haos-ds .head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--ds-space-4);
  margin-bottom: var(--ds-space-6);
}
html.haos-ds .page-head .sub,
html.haos-ds .head .sub,
html.haos-ds h1 + .sub,
html.haos-ds h1 + p.sub {
  color: var(--ds-ink-2);
  font-size: var(--ds-size-secondary);
  margin: var(--ds-space-1) 0 0;
}

/* ---- list rows that are links: hover as a surface, never a lift --------- */
html.haos-ds a.card:hover,
html.haos-ds .card[href]:hover { background: var(--ds-surface); box-shadow: var(--ds-shadow-2); }

/* ---- forms inside cards -------------------------------------------------- */
html.haos-ds .card label {
  display: block;
  font-size: var(--ds-size-caption);
  color: var(--ds-ink-2);
  margin-bottom: var(--ds-space-1);
}
html.haos-ds .card input:not([type=checkbox]):not([type=radio]),
html.haos-ds .card select,
html.haos-ds .card textarea {
  width: 100%;
  min-height: var(--ds-target);
  padding: var(--ds-space-2) var(--ds-space-3);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-field);
  background: var(--ds-surface);
  color: var(--ds-ink);
  font: inherit;
  font-size: var(--ds-size-body);
}
html.haos-ds .card textarea { min-height: 96px; resize: vertical; }
html.haos-ds .card input:focus-visible,
html.haos-ds .card select:focus-visible,
html.haos-ds .card textarea:focus-visible {
  outline: none;
  border-color: var(--ds-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--ds-accent) 24%, transparent);
}

/* =========================================================================
   SIGN-IN SURFACES
   -------------------------------------------------------------------------
   The one place on this product where apple.com's MARKETING language is the
   right answer. A sign-in page has a single job and no data, so it can afford
   the hero scale, the depth field and the motion that would be actively wrong
   on a QA queue. Every working screen stays calm; this is the arrival.

   Type here breaks the 32px display ceiling on purpose -- Apple's own scale
   runs to 80px, and its tracking is NEGATIVE above 40px, which is why the
   clamp below carries -.015em rather than the +.004em the 32px role uses.
   ========================================================================= */

html.haos-ds body.haos-signin,
html.haos-ds body.portal-login {
  min-height: 100dvh;
  box-sizing: border-box;           /* or the padding adds a 48px scroll */
  display: grid;
  place-items: center;
  padding: var(--ds-space-6) var(--ds-space-5);
  padding-block-start: max(var(--ds-space-6), env(safe-area-inset-top, 0px));
  padding-block-end: max(var(--ds-space-6), env(safe-area-inset-bottom, 0px));
  overflow-x: hidden;
}

/* ---- the depth field ---------------------------------------------------- */
/* Fixed, behind everything, pointer-transparent. Each layer is oversized so a
   parallax translate never reveals an edge.                                  */
.haos-depth {
  position: fixed;
  inset: -12vmax;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.haos-depth-layer { position: absolute; inset: 0; will-change: transform; }

/* Far: one broad wash that sets the mood and never competes with the card. */
.haos-depth-far {
  background:
    radial-gradient(48vmax 40vmax at 18% 12%,
      color-mix(in srgb, var(--ds-accent) 9%, transparent), transparent 62%),
    radial-gradient(42vmax 38vmax at 86% 82%,
      color-mix(in srgb, var(--ds-info) 7%, transparent), transparent 64%);
}
/* Mid: HEMFA's brand red, present but not in charge. The mark's colour belongs
   on the arrival surface even though it is deliberately not the accent. */
.haos-depth-mid {
  background:
    radial-gradient(30vmax 26vmax at 76% 16%,
      color-mix(in srgb, #D51C28 6%, transparent), transparent 66%),
    radial-gradient(26vmax 24vmax at 12% 78%,
      color-mix(in srgb, var(--ds-accent) 7%, transparent), transparent 68%);
}
/* The fleet canvas: the one object. Above the wash, below the content. */
canvas.haos-fleet {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
/* Near layer retired: the lattice IS the thing the eye measures motion
   against now, and a grid under a lattice is noise. */
.haos-depth-near { display: none; }
.haos-depth-near-retired {
  background-image:
    linear-gradient(to right, var(--ds-hairline-soft) 1px, transparent 1px),
    linear-gradient(to bottom, var(--ds-hairline-soft) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(70vmax 60vmax at 50% 40%, #000, transparent 72%);
          mask-image: radial-gradient(70vmax 60vmax at 50% 40%, #000, transparent 72%);
  opacity: .55;
}

/* ---- the stage ----------------------------------------------------------- */
/* ONE owner. The portal's own sheet used to lay this screen out as well, and
   its #main rule outranked these by id: the title rendered at this sheet's
   64px inside that sheet's 10ch box, four lines deep, beside a 300px card,
   and on a phone the title box sat 38px off the centre its siblings shared.
   Geometry that matches NEITHER sheet's numbers is the tell for two owners.
   The portal keeps its components (status dot, trust line); the layout is
   here, in px -- the portal root is 15px, so its rem was never 16.        */
html.haos-ds .haos-signin-stage,
html.haos-ds .login-stage {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1060px;
  min-height: 0;
  margin: 0 auto;
  padding: 0;                       /* the body gutter is the gutter */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  /* one column: hero, then the object (drawn into this gap), then the card --
     the phone hero pattern: headline, product, action */
  row-gap: min(64vw, 420px);
}
/* Two columns only where there is genuinely room. The card column is a fixed
   420 -- a sign-in form does not get wider on a wider screen -- and the hero
   takes the rest. */
@media (min-width: 900px) {
  html.haos-ds .haos-signin-stage,
  html.haos-ds .login-stage {
    grid-template-columns: minmax(0, 1fr) 420px;
    column-gap: var(--ds-space-10);
    row-gap: 0;
  }
}

/* The portal has a header row above the stage. Pin it: an auto row for the
   header and the rest for the stage, so free space never splits across the
   two rows and floats the brand down the page. The header shares the stage's
   1060px column, so the wordmark and the headline start on one x. */
html.haos-ds body.portal-login {
  grid-template-rows: auto minmax(0, 1fr);
  align-content: stretch;
}
html.haos-ds body.portal-login > header {
  width: 100%;
  max-width: 1060px;
  justify-self: center;
  align-self: start;
  border: 0;
  background: transparent;
  position: relative;
  z-index: 2;
}
html.haos-ds body.portal-login > header .wrap {
  max-width: none;
  margin: 0;
  padding: 0 0 var(--ds-space-6);
}
html.haos-ds body.portal-login > header .brand {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  color: var(--ds-ink);
  font-size: var(--ds-size-label);
  line-height: var(--ds-line-label);
  font-weight: var(--ds-weight-strong);
  letter-spacing: normal;
  text-transform: none;
}
html.haos-ds body.portal-login > header .brand::before {
  content: "";
  width: 8px; height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--ds-accent);
  animation: none;
  box-shadow: none;
}
html.haos-ds #main.login-stage { align-self: center; }

/* The portal's Arabic mode set Tahoma on body after this sheet loaded; the
   Arabic face ships with the system and is the one the owner chose. */
html.haos-ds[dir="rtl"] body { font-family: var(--ds-font-sans); }

/* ---- the hero ----------------------------------------------------------- */
html.haos-ds .haos-signin .hero,
html.haos-ds .login-hero {
  max-width: 520px;
  min-width: 0;
  padding: 0;
  text-shadow: none;
}
html.haos-ds .haos-signin h1,
html.haos-ds .login-hero h1 {
  font-size: clamp(40px, 4.4vw, 56px);
  line-height: 1.08;
  font-weight: var(--ds-weight-strong);
  max-width: none;
  margin: 0 0 var(--ds-space-4);
  text-wrap: balance;
}
html.haos-ds:not([dir="rtl"]) .haos-signin h1,
html.haos-ds:not([dir="rtl"]) .login-hero h1 { letter-spacing: -.02em; }

html.haos-ds .haos-signin .tag,
html.haos-ds .login-lede {
  font-size: clamp(17px, 1.4vw, 19px);
  line-height: 1.45;
  color: var(--ds-ink-2);
  margin: 0;
  max-width: 42ch;
  text-wrap: pretty;
}
html.haos-ds .login-ar {
  width: fit-content;
  margin: var(--ds-space-5) 0 0;
  font-family: var(--ds-font-sans);
  font-size: 17px;
  line-height: 1.5;
  font-weight: var(--ds-weight-strong);
  color: var(--ds-ink);
}
html.haos-ds .login-status {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  margin: var(--ds-space-7) 0 0;
  font-size: var(--ds-size-label);
  line-height: var(--ds-line-label);
  font-weight: var(--ds-weight-strong);
  color: var(--ds-ink-2);
  letter-spacing: normal;
}
html.haos-ds .haos-signin .brand,
html.haos-ds .login-kicker {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-2);
  font-size: var(--ds-size-label);
  line-height: var(--ds-line-label);
  font-weight: var(--ds-weight-strong);
  color: var(--ds-ink-2);
  letter-spacing: normal;
  margin: 0 0 var(--ds-space-4);
}
html.haos-ds .haos-signin .brand .mark,
html.haos-ds .login-kicker::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--ds-accent);
  flex: none;
}

/* ---- the card ----------------------------------------------------------- */
/* The one glass surface here, and it is the floating functional layer the
   guideline permits: the depth field and the object are content behind it,
   which is exactly the relationship glass exists for. */
html.haos-ds .haos-signin .card,
html.haos-ds .portal-login .card.login {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 420px;
  min-width: 0;
  box-sizing: border-box;
  margin: 0;
  justify-self: end;
  background: var(--ds-glass-bg);
  -webkit-backdrop-filter: var(--ds-glass-blur);
  backdrop-filter: var(--ds-glass-blur);
  border: 1px solid var(--ds-hairline-soft);
  border-radius: var(--ds-radius-modal);
  box-shadow: var(--ds-shadow-3);
  padding: var(--ds-space-7);
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
}
/* ---- the sheen: the light on the glass follows the pointer -------------- */
/* The card is a fixed plane; what moves is the highlight on it. An oversized
   gradient moved by transform only (compositor work, no repaint), written once
   per frame by haos-fleet.js while the pointer moves. It carries no meaning and
   takes no pointer; there is nothing to announce. Fine pointers only, and
   haos-fleet.js never installs it under reduced motion. */
html.haos-ds .card.haos-sheen { overflow: hidden; }
html.haos-ds .card.haos-sheen::after {
  content: "";
  position: absolute;
  inset: -60%;
  pointer-events: none;
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--ds-accent) 9%, transparent), transparent 72%);
  transform: translate(var(--sheen-x, 0px), var(--sheen-y, 0px));
  opacity: var(--sheen-o, 0);
  transition: opacity var(--ds-dur-enter) var(--ds-ease-in);
  will-change: transform, opacity;
}
html.haos-ds[data-theme="dark"] .card.haos-sheen::after {
  background: radial-gradient(closest-side, rgba(255, 255, 255, .10), transparent 72%);
}
@media (prefers-color-scheme: dark) {
  html.haos-ds:not([data-theme="light"]) .card.haos-sheen::after {
    background: radial-gradient(closest-side, rgba(255, 255, 255, .10), transparent 72%);
  }
}

/* the portal builds its fields inside a <form> inside the card */
html.haos-ds .portal-login .card.login form {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
  margin: 0;
}
html.haos-ds .portal-login .card.login .fld {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-1);
  margin: 0;
}
html.haos-ds .login-card-kicker {
  font-size: var(--ds-size-label);
  line-height: var(--ds-line-label);
  font-weight: var(--ds-weight-strong);
  color: var(--ds-ink-3);
  letter-spacing: normal;
  margin: 0;
}
html.haos-ds .portal-login .card.login h1,
html.haos-ds .portal-login .card.login h2,
html.haos-ds .haos-signin .card h2 {
  font-size: 28px;
  line-height: 32px;
  font-weight: var(--ds-weight-strong);
  letter-spacing: var(--ds-track-title);
  color: var(--ds-ink);
  margin: 0;
}
html.haos-ds .portal-login .card.login .sub {
  font-size: var(--ds-size-secondary);
  line-height: var(--ds-line-secondary);
  color: var(--ds-ink-2);
  margin: 0 0 var(--ds-space-2);
}
/* One filled action, full width; the secondary is a text link beneath it;
   no hairline above -- the detail-page composition rule does not apply to a
   sign-in form. */
html.haos-ds .portal-login .card.login .actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--ds-space-2);
  margin-top: var(--ds-space-2);
  padding-top: 0;
  border-top: 0;
}
html.haos-ds .portal-login .card.login .actions .btn {
  min-height: var(--ds-target);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-size-body);
  margin: 0;
}
html.haos-ds .portal-login .card.login .actions .btn:not(.pri) {
  background: transparent;
  color: var(--ds-accent-text);
  font-weight: var(--ds-weight-regular);
  min-height: 36px;
}
html.haos-ds .portal-login .login-trust {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--ds-space-2);
  margin: var(--ds-space-2) 0 0;
  font-size: var(--ds-size-caption);
  line-height: var(--ds-line-caption);
  color: var(--ds-ink-3);
}
html.haos-ds .portal-login .login-trust > :first-child { color: var(--ds-accent); font-size: 8px; }

/* ---- one column ---------------------------------------------------------- */
/* Read what this is, then sign in. Every width-capped block centres by its
   own margins: a max-width inside text-align:center centres the TEXT, not the
   box, which is how the title sat 38px off its siblings. */
@media (max-width: 899px) {
  html.haos-ds body.haos-signin,
  html.haos-ds body.portal-login { position: relative; }
  /* the object becomes a page element here, drawn into the gap between hero
     and card, and scrolls with them */
  canvas.haos-fleet.is-page { position: absolute; top: 0; left: 0; }

  html.haos-ds .haos-signin .hero,
  html.haos-ds .login-hero {
    max-width: 544px;
    justify-self: center;
    text-align: center;
  }
  html.haos-ds .haos-signin h1,
  html.haos-ds .login-hero h1 {
    font-size: clamp(34px, 9vw, 44px);
    margin-inline: auto;
  }
  html.haos-ds .haos-signin .tag,
  html.haos-ds .login-lede { margin-inline: auto; max-width: 36ch; }
  html.haos-ds .login-ar { margin-inline: auto; }
  html.haos-ds .haos-signin .card,
  html.haos-ds .portal-login .card.login { justify-self: center; max-width: 480px; }
}
@media (max-width: 479px) {
  html.haos-ds .haos-signin .card,
  html.haos-ds .portal-login .card.login { padding: var(--ds-space-6) var(--ds-space-5); }
}

html.haos-ds .haos-signin label,
html.haos-ds .portal-login .card.login label {
  font-size: var(--ds-size-label);
  line-height: var(--ds-line-label);
  font-weight: var(--ds-weight-strong);
  color: var(--ds-ink-2);
  margin-bottom: calc(var(--ds-space-1) * -1);
}
html.haos-ds .haos-signin input,
html.haos-ds .haos-signin select,
html.haos-ds .portal-login .card.login input {
  width: 100%;
  min-height: var(--ds-target);
  box-sizing: border-box;
  /* 16px, or iOS Safari zooms the whole page on focus -- on the one screen
     where that is most jarring, because the keyboard is already up. */
  font-size: 16px;
  font-family: inherit;
  color: var(--ds-ink);
  background: var(--ds-surface);
  border: 1px solid var(--ds-hairline);
  border-radius: var(--ds-radius-field);
  padding: var(--ds-space-3);
  transition: border-color var(--ds-dur-base) var(--ds-ease),
              box-shadow var(--ds-dur-base) var(--ds-ease);
}
html.haos-ds .haos-signin input:focus-visible,
html.haos-ds .portal-login .card.login input:focus-visible {
  outline: none;
  border-color: var(--ds-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--ds-accent) 28%, transparent);
}
html.haos-ds .haos-signin button[type=submit],
html.haos-ds .portal-login .card.login button[type=submit] {
  min-height: var(--ds-target);
  margin-top: var(--ds-space-2);
  border: 0;
  border-radius: var(--ds-radius-pill);
  background: var(--ds-accent);
  color: #fff;
  font-family: inherit;
  font-size: var(--ds-size-body);
  font-weight: var(--ds-weight-strong);
  cursor: pointer;
  transition: opacity var(--ds-dur-base) var(--ds-ease);
}
html.haos-ds .haos-signin button[type=submit]:hover,
html.haos-ds .portal-login .card.login button[type=submit]:hover { opacity: .88; }

html.haos-ds .haos-signin .foot,
html.haos-ds .portal-login .card.login .foot {
  font-size: var(--ds-size-caption);
  line-height: var(--ds-line-caption);
  color: var(--ds-ink-2);
  margin-top: var(--ds-space-2);
}
html.haos-ds .haos-signin .foot a,
html.haos-ds .portal-login .card.login .foot a { color: var(--ds-accent-text); }
/* the recovery frames put a status card here: a paragraph and a link */
html.haos-ds .haos-signin .card p {
  margin: 0;
  font-size: var(--ds-size-secondary);
  line-height: var(--ds-line-secondary);
  color: var(--ds-ink-2);
}
html.haos-ds .haos-signin .card > a { color: var(--ds-accent-text); font-weight: var(--ds-weight-strong); }

html.haos-ds .haos-signin .err,
html.haos-ds .portal-login .card.login .err {
  background: var(--ds-danger-bg);
  color: var(--ds-danger);
  border: 1px solid var(--ds-danger-line);
  border-radius: var(--ds-radius-control);
  padding: var(--ds-space-3);
  font-size: var(--ds-size-secondary);
}

/* The portal built its own drifting mesh before this system existed
   (body:has(.card.login)::before/::after) and its own glass at blur(22-24px).
   With the shared depth field mounted there would be TWO background systems
   moving against each other on one screen. The shared one wins; the older
   pair is retired here rather than deleted from portal.css, so the portal
   still renders correctly on its own if this sheet ever fails to load. */
html.haos-ds body:has(.card.login)::before,
html.haos-ds body:has(.card.login)::after { content: none; }

html.haos-ds body.portal-login .card.login,
html.haos-ds #main .card.login {
  background: var(--ds-glass-bg);
  -webkit-backdrop-filter: var(--ds-glass-blur);
  backdrop-filter: var(--ds-glass-blur);
  border-radius: var(--ds-radius-modal);
  box-shadow: var(--ds-shadow-3);
}

/* One orchestrated arrival rather than scattered effects: hero and card rise
   together, 80ms apart -- Apple's own primary stagger. */
@media (prefers-reduced-motion: no-preference) {
  html.haos-ds .haos-signin .hero,
  html.haos-ds .login-hero,
  html.haos-ds .haos-signin .card,
  html.haos-ds .portal-login .card.login {
    animation: haosSigninIn var(--ds-dur-enter) var(--ds-ease-in) both;
  }
  html.haos-ds .haos-signin .card,
  html.haos-ds .portal-login .card.login { animation-delay: 80ms; }
  @keyframes haosSigninIn {
    from { opacity: 0; transform: translate3d(0, 12px, 0); }
    to   { opacity: 1; transform: none; }
  }
}

@media (prefers-reduced-transparency: reduce) {
  html.haos-ds .haos-signin .card,
  html.haos-ds .portal-login .card.login {
    background: var(--ds-surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .haos-depth-near { display: none; }
}

/* =========================================================================
   MOTION GRAMMAR — three families on the three duration tokens, so a motion
   says what KIND of thing just happened (council 057eaa8c4ed7, 2026-09-21):
     navigation          --ds-dur-slow  240ms  --ds-ease     the place changed
     direct manipulation --ds-dur-base  160ms  --ds-ease     you did that
     status              --ds-dur-enter 400ms  --ds-ease-in  the system did that
   Anything decorative is outside the grammar and must not borrow its tokens.
   ========================================================================= */

/* ---- cross-document view transitions: 29 pages that navigate like one app -- */
/* Both documents must opt in, so the sign-in family -- which opts OUT in its
   own sheet -- never transitions into or out of a protected page: a logout
   must not leave the old page's picture on screen. Browsers without support
   navigate exactly as before. */
@view-transition { navigation: auto; }

/* The page bar holds still. On 22 pages it is rendered by haos-session.js
   AFTER the session answers, so at capture time only the OLD document has it:
   its snapshot is held, unanimated, over the new page while the new page's
   bar renders live underneath it (the new state is a live capture), and the
   two coincide -- same component, same 44px, same place. Where both exist
   (the two static bars) the new one simply shows. No morph, ever. */
/* Named only where its height is constant. Below 834px the tabs wrap (640)
   and the bar grows 44->48 (833), and a name on a bar whose height differs
   between the two documents is a jump, not a hold; there the bar takes part
   in the root fade like everything else. Both documents share the viewport,
   so the name is present in both or in neither. */
@media (min-width: 834px) {
  html.haos-ds nav.haos-nav,
  html.haos-ds header.bar,
  html.haos-ds header.topbar { view-transition-name: haos-nav; }
}
::view-transition-group(haos-nav) { animation-duration: 0s; }
::view-transition-old(haos-nav),
::view-transition-new(haos-nav) { animation: none; }

/* The title hands over: it is the one element that says where you arrived. */
html.haos-ds h1 { view-transition-name: haos-title; }
::view-transition-group(haos-title) {
  animation-duration: var(--ds-dur-slow);
  animation-timing-function: var(--ds-ease);
}
::view-transition-old(haos-title),
::view-transition-new(haos-title) {
  animation-duration: var(--ds-dur-base);
  animation-timing-function: var(--ds-ease);
}

/* Everything else: one plain fade in the navigation family. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--ds-dur-slow);
  animation-timing-function: var(--ds-ease);
}

/* The bar's mount reserves the bar's height before the session answers, so
   the page never shifts 44px when the bar arrives -- with or without a
   transition. */
html.haos-ds #nav:not([data-haos-nav="ready"]) { min-height: 44px; }

@media (prefers-reduced-motion: reduce) {
  /* no transition object is created at all; the pseudo rules are the floor
     for an engine that does not honour a nested @view-transition */
  @view-transition { navigation: none; }
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
@media print {
  ::view-transition { display: none; }
}

/* The portal's own sheet still paints body from its legacy --bg; the canvas
   is the system's, on both surfaces. */
html.haos-ds body.portal-login,
html.haos-ds body:has(> #main.wrap) { background: var(--ds-canvas); }


/* ---------------------------------------------------------------- print --- */
@media print {
  html.haos-ds .ds-nav,
  html.haos-ds .ds-toast,
  html.haos-ds .ds-scrim { display: none !important; }
  html.haos-ds body { background: #fff; color: #000; }
}
