/* ═══════════════════════════════════════════════════════════════════════════
   Synapse UI — design tokens
   design/unified-experience/03-ui-system/tokens.md

   THREE LAYERS, ONE DIRECTION. Brand tokens → semantic tokens → component
   styles. A component style never names a brand token directly, which is what
   makes an endorsed brand possible: a product overrides the brand layer and
   every component follows without a single component rule changing.

   The brand layer is VERBATIM from website/styles.css. If that palette moves,
   mirror it here — the same standing instruction compass-p1/frontend/styles.css
   already carries, and for the same reason.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── The three faces, declared HERE rather than in Compass's own stylesheet ──
   Because this file is what an embedding product loads (patterns.md §12), and
   a product loads nothing else of Compass's. Declaring the faces in
   compass-p1/frontend/styles.css — which is where they were — meant that a
   product could adopt the tokens, name var(--f-serif), and silently get
   Georgia: the CSS is valid, the cascade is correct, and the page renders,
   just not in the portfolio's type. Nothing errors and nothing logs.

   `--f-hyper` was worse than a fallback, because nothing was vendored at all.
   The comment below claimed the face was "vendored as woff2 beside Compass's
   own fonts"; there was no Atkinson woff2 anywhere in this repository and no
   @font-face for it, so every family, patient and teacher surface in the
   portfolio — DD-46's whole point — has been rendering in Inter since the
   library shipped. Found by reading what the browser actually loads rather
   than what this file says it loads.

   ABSOLUTE paths, not ../fonts/. This stylesheet is loaded from two different
   directory depths (Compass's own pages and, now, seventeen products' pages),
   and a relative URL resolves against the STYLESHEET's URL — which is the same
   for both, so relative would in fact work — but /auth/fonts/ is what the
   files are, and an absolute path survives this file being copied somewhere
   else, which syncUi.js does on every deploy. Same origin for every consumer,
   so font-src 'self' holds portfolio-wide with no CSP change.

   Atkinson Hyperlegible is SIL OFL 1.1 (Braille Institute of America); see
   fonts/OFL-Atkinson-Hyperlegible.txt beside the woff2 files. */

@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/auth/fonts/fraunces-normal.woff2') format('woff2');
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 300 500;
  font-display: swap;
  src: url('/auth/fonts/fraunces-italic.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/auth/fonts/inter-var.woff2') format('woff2');
}

/* Atkinson is not a variable font, so it is three weights/styles x two
   unicode ranges, exactly as its upstream splits them. The latin-ext files
   are what carry an accented family name; dropping them to save 28KB would
   drop the face for precisely the readers who most need it. */
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/auth/fonts/atkinson-normal-latin.woff2') format('woff2');
  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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/auth/fonts/atkinson-normal-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/auth/fonts/atkinson-bold-latin.woff2') format('woff2');
  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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/auth/fonts/atkinson-bold-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/auth/fonts/atkinson-italic-latin.woff2') format('woff2');
  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+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Atkinson Hyperlegible';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/auth/fonts/atkinson-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ── Brand layer (inherited, not invented) ─────────────────────────────── */
  --paper:      #F7F1E3;
  --paper-warm: #EFE5D0;
  --white:      #FFFDF7;
  --ink:        #14121A;
  --ink-mute:   #55524A;
  --ochre:      #E89A2E;
  --rust:       #C8492E;
  --plum:       #3A1F42;
  --line:       #D8CDB6;
  --teal:       #1E7A6D;

  --f-serif: 'Fraunces', Georgia, serif;
  --f-sans:  'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* Added by Synapse UI: the UI face for patient, family and teacher surfaces
     (DD-46). Vendored as woff2 above and served from /auth/fonts/, so
     font-src 'self' holds — no cross-origin font request anywhere in the
     portfolio. */
  --f-hyper: 'Atkinson Hyperlegible', var(--f-sans);

  --radius:    18px;
  --radius-sm: 12px;

  /* ── Semantic layer ────────────────────────────────────────────────────── */

  /* Surfaces */
  --sy-surface:          var(--paper);
  --sy-surface-raised:   var(--white);
  --sy-surface-recessed: var(--paper-warm);
  --sy-surface-inverse:  var(--ink);

  /* Text */
  --sy-text:            var(--ink);
  --sy-text-quiet:      var(--ink-mute);
  --sy-text-on-inverse: var(--paper);
  --sy-text-link:       var(--teal);
  /* Rust deepened for TEXT (2026-09-27): --rust itself (#C8492E) is 4.19:1
     on --paper and 3.77:1 on --paper-warm — below AA for body text — and the
     contrast test only ever checked it on the raised card, where it passes.
     Still the rust family (DD-45); the status FILLS keep --rust itself. */
  --sy-text-danger:     #A63A22;

  /* Lines */
  --sy-line:       var(--line);
  --sy-line-width: 1px;

  /* Status — five, never six. Each is a colour, a foreground and an icon name;
     a status is never communicated by colour alone (DD-44). `overdue` and
     `alert` share rust deliberately: both mean now, and the icon plus the
     label carry the distinction. Nothing else on a screen may be rust (DD-45). */
  --sy-status-ok:          var(--teal);
  --sy-status-ok-on:       var(--white);
  /* Not var(--ochre): brand ochre is 2.27:1 against a card, below the 3:1 floor
     WCAG 2.2 AA sets for a non-text UI component, so the swatch would be
     invisible to a low-vision reader while looking fine to everyone else. This
     is ochre darkened 15% — 3.10:1 against a card, and ink still reads 5.88:1
     on it. The brand token is untouched; only the semantic role moves. */
  --sy-status-due:         #C58327;
  --sy-status-due-on:      var(--ink);
  --sy-status-overdue:     var(--rust);
  --sy-status-overdue-on:  var(--white);
  --sy-status-alert:       var(--rust);
  --sy-status-alert-on:    var(--white);
  --sy-status-none:        var(--ink-mute);
  --sy-status-none-on:     var(--white);

  /* Spacing — seven steps, nothing between them. */
  --sy-s1: 4px;  --sy-s2: 8px;  --sy-s3: 12px; --sy-s4: 16px;
  --sy-s5: 24px; --sy-s6: 32px; --sy-s7: 48px;

  /* Type — base matches the marketing site so a product does not shrink when
     it adopts. */
  --sy-t-xs:   12px;
  --sy-t-s:    14px;
  --sy-t-base: 16.5px;
  --sy-t-m:    19px;
  --sy-t-l:    24px;
  --sy-t-xl:   32px;
  --sy-lh:     1.65;

  /* Faces, set per surface mode below. */
  --sy-face:         var(--f-sans);
  --sy-face-heading: var(--f-serif);

  /* Motion — two transitions exist in the whole library, and the reduced-motion
     block below removes them entirely rather than shortening them. */
  --sy-motion-fast: 120ms;
  --sy-motion-base: 200ms;

  /* Focus — two rings so it reads on both raised and recessed surfaces. Never
     removed, never replaced by a colour change alone. */
  --sy-focus-ring: 0 0 0 2px var(--sy-surface), 0 0 0 4px var(--teal);

  --sy-shadow: 0 2px 8px rgba(20, 18, 26, 0.06);
  --sy-maxw:   1160px;

  /* Touch targets. 44px on family and teacher surfaces, 32 on clinical —
     WCAG 2.2 AA's floor is 24 and both clear it. */
  --sy-target: 44px;
}

/* ── Surface modes ────────────────────────────────────────────────────────
   A page declares exactly one of these on its root element. It sets the face,
   the line height and the density; within a surface, density never varies by
   component, and no component takes a density prop. */

.sy-surface-family {
  --sy-face: var(--f-hyper);
  --sy-lh: 1.65;
  --sy-density: 1;
  --sy-target: 44px;
}
.sy-surface-clinical,
.sy-surface-research {
  --sy-face: var(--f-sans);
  --sy-lh: 1.5;
  --sy-density: 0.75;
  --sy-target: 32px;
}
.sy-surface-ops {
  --sy-face: var(--f-sans);
  --sy-lh: 1.45;
  --sy-density: 0.625;
  --sy-target: 32px;
}

/* ── Dark ─────────────────────────────────────────────────────────────────
   Only the SEMANTIC layer is redefined. Brand tokens are not touched, so a
   product overriding the brand still gets a coherent dark mode. The two brand
   colours that do not clear 4.5:1 on a dark ground — ochre and teal — get
   lightened semantic values rather than new brand values.

   Guarded with :not([data-theme="light"]) so an explicit light choice wins,
   and repeated under [data-theme="dark"] so an explicit dark choice wins in
   the other direction. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sy-surface:          #14121A;
    --sy-surface-raised:   #1E1B24;
    --sy-surface-recessed: #100E15;
    --sy-surface-inverse:  #F7F1E3;
    --sy-text:             #F2ECDD;
    --sy-text-quiet:       #ADA79B;
    --sy-text-on-inverse:  #14121A;
    --sy-text-link:        #5FC3B2;
    --sy-text-danger:      #F0846A;
    --sy-line:             #37323F;
    /* Each status carries its OWN foreground in dark. Inheriting the light
       --white gave 3.31:1 on the ok and overdue chips — the icon glyph would
       have been unreadable on exactly the two statuses that matter most. */
    --sy-status-ok:        #4FC3AE;
    --sy-status-ok-on:     #10231F;
    --sy-status-due:       #F0AC48;
    --sy-status-due-on:    #14121A;
    --sy-status-overdue:   #EE8267;
    --sy-status-overdue-on:#2A0E07;
    --sy-status-alert:     #EE8267;
    --sy-status-alert-on:  #2A0E07;
    --sy-status-none:      #9C968D;
    --sy-status-none-on:   #14121A;
    --sy-focus-ring: 0 0 0 2px var(--sy-surface), 0 0 0 4px #5FC3B2;
    --sy-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  }
}
:root[data-theme="dark"] {
  --sy-surface:          #14121A;
  --sy-surface-raised:   #1E1B24;
  --sy-surface-recessed: #100E15;
  --sy-surface-inverse:  #F7F1E3;
  --sy-text:             #F2ECDD;
  --sy-text-quiet:       #ADA79B;
  --sy-text-on-inverse:  #14121A;
  --sy-text-link:        #5FC3B2;
  --sy-text-danger:      #F0846A;
  --sy-line:             #37323F;
  /* Each status carries its OWN foreground in dark. Inheriting the light
     --white gave 3.31:1 on the ok and overdue chips — the icon glyph would
     have been unreadable on exactly the two statuses that matter most. */
  --sy-status-ok:        #4FC3AE;
  --sy-status-ok-on:     #10231F;
  --sy-status-due:       #F0AC48;
  --sy-status-due-on:    #14121A;
  --sy-status-overdue:   #EE8267;
  --sy-status-overdue-on:#2A0E07;
  --sy-status-alert:     #EE8267;
  --sy-status-alert-on:  #2A0E07;
  --sy-status-none:      #9C968D;
  --sy-status-none-on:   #14121A;
  --sy-focus-ring: 0 0 0 2px var(--sy-surface), 0 0 0 4px #5FC3B2;
  --sy-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}

/* ── More contrast ────────────────────────────────────────────────────────
   Hairlines thicken, quiet text goes to full contrast, and every colour-mix
   tint becomes a solid fill. One block, in the library, so no product has to
   remember (standard §2.6). */
@media (prefers-contrast: more) {
  :root {
    --sy-line-width: 2px;
    --sy-text-quiet: var(--sy-text);
    --sy-shadow: none;
  }
}

/* ── Reduced motion ───────────────────────────────────────────────────────
   Removed entirely, not shortened. A 1ms animation is still an animation to a
   vestibular system.

   KEYED ON THE SURFACE CLASSES, not on .sy-root. This block used to select
   `.sy-root *` only, and .sy-root exists on nine pages in the whole
   portfolio — all of them Compass's own. Every one of the ~80 product pages
   marks its root element with a surface class instead, exactly as the
   "Surface modes" section above says to ("a page declares exactly one of
   these on its root element"), and so carried no .sy-root and never got this
   rule. The zeroed --sy-motion-* custom properties still applied, because
   those are on :root, which hid the gap: anything animating through a token
   did stop, and anything with its own @keyframes or a hand-written
   transition kept moving for every reduced-motion user on every product.

   The surface class is on the element itself, so it is selected as well as
   its descendants — a transition on <body> is still a transition. .sy-root
   stays in the list: Compass's pages carry both, and dropping it would be a
   silent behaviour change on the one product that was working. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --sy-motion-fast: 0ms;
    --sy-motion-base: 0ms;
  }
  .sy-root,
  .sy-root *,
  .sy-root *::before,
  .sy-root *::after,
  .sy-surface-family,
  .sy-surface-family *,
  .sy-surface-family *::before,
  .sy-surface-family *::after,
  .sy-surface-clinical,
  .sy-surface-clinical *,
  .sy-surface-clinical *::before,
  .sy-surface-clinical *::after,
  .sy-surface-research,
  .sy-surface-research *,
  .sy-surface-research *::before,
  .sy-surface-research *::after,
  .sy-surface-ops,
  .sy-surface-ops *,
  .sy-surface-ops *::before,
  .sy-surface-ops *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}
