/* ===== tokens.css ===== */
/* ============================================================
   Perkins SmartOps — Design Tokens v3.10
   Dark-first. Three brand colours: deep ocean, orange, bone.
   Teal is a secondary and means "live and working".
   Premium-flat discipline. Nothing glows. No background gradients.

   v3.10 changes (2026-07-23 pm, David's review of the light theme):
   One orange on light, and it is the brand orange.

   - NEW --pso-orange-print (#F0550A). Brand hue, brightness pulled 4%,
     3.09:1 on bone. It is now the ONLY orange on a light page, for
     every role: labels, rules, bullets, left accents, chart series 1.
   - ORANGE INK RETIRED as a light-page option. --pso-orange-ink on
     light no longer drops to --pso-orange-deep. The deep orange passed
     AA as text at 4.58:1 and looked like rust doing it, so a light page
     read as a different brand to the dark cover facing it. The full
     argument, including why the accessibility exception still holds
     without it, is written against --pso-orange-ink in the light block.
   - --pso-orange-deep keeps its original job: pressed states and the
     focus ring on interactive web surfaces. It is no longer ink.
   - NO DOT GRID ON REPORT PAGES, dark or light. The v3.8 sanction for
     dot-grid texture covers WEB cover surfaces only. A report page is
     flat colour. Stated here because an external rebuild reintroduced
     it on every dark report page and nothing caught it.

   v3.9 changes (2026-07-23, external design review actioned):
   Twelve findings from a Claude Design review of the library, plus five
   gaps. The theme running through nearly all of them: a rule was stated
   in one place and broken in another, usually in the same file. Almost
   nothing here is a design problem. It is documentation drift, which is
   what v3.8 already named the cure for, applied now to the copy and the
   specimens as well as the CSS.

   - NOTHING GLOWS, and now that is actually true. The slide cover and
     the social pull quote both carried an orange radial glow on the
     strength of a comment in one showcase file declaring them "reserved"
     exceptions. That reservation existed nowhere in the Brand Kit. Both
     removed. The two surfaces prospects see most were the two still
     rendering a different brand.
   - NEW --pso-chart-1..4. Chart series had been borrowing the TEXT ramp
     for their fills, which is how the docs came to describe a 45% step
     the token no longer had. They are separate now because they answer
     to different floors: text is 4.5:1 (WCAG 1.4.3), a bar or ring
     segment is a graphical object at 3:1 (1.4.11). That is what lets the
     chart keep four steps where the text ramp only supports three.
   - NEW --pso-text-2xs = 10px. Sixty-five hardcoded 10px and 9px values
     across the library, all of them the same uppercase mono label.
     Constraint is written on the token: uppercase, tracked, never body.
   - NEW --pso-scrim and --pso-header-bg. Both were raw rgba of the page
     hex, retyped, so a page-colour change would have left them behind.
   - NEW --pso-orange-ink, and this one is a measured failure not a tidy
     -up. --pso-orange on bone is 2.74:1. It fails AA text at 4.5:1 and
     it fails the 3:1 graphical floor, so orange text AND orange
     hairlines were both unreliable on every light page we have ever
     produced. Orange now splits by role: --pso-orange stays the FILL
     (navy on orange is 6.01:1 either way), --pso-orange-ink is orange as
     the mark itself and drops to --pso-orange-deep on light at 4.58:1.
     The focus ring had inherited the same failure, because it is built
     from --pso-page and --pso-orange and only the first of those flipped.
   - LIGHT THEME filled out: chart fills, scrim, header, disabled states
     and focus ring all have light values instead of silently inheriting
     dark ones. The light chart ramp is genuinely tighter than the dark
     one, three steps rather than four, because the fourth misses 3:1.
   - LIGHT ELEVATION LEFT UNRESOLVED, deliberately. All three raised
     steps are the same white, so the ladder collapses to one. See the
     note in the light block for the measurements and the two options.
     Booked for a session on 27 July rather than decided here: it flips
     every light surface including client reports already produced, and
     that is a judgement to make with the pages open.

   v3.8 changes (2026-07-22, brand library review actioned):
   - INK RETIRED. --pso-ink (#060910) is DELETED, not deprecated.
     It was two percent darker than the page and served as the
     endpoint of a fade that, rendered side by side against a flat
     band, was near-indistinguishable. Its three sanctioned uses
     (header fade, footer fade, input well) now resolve to
     --pso-page. Cost, stated plainly: the input well loses its
     slight recess, so inputs lean on their border to read as
     fields. Teal was considered alongside it and KEPT: 52 live
     uses, and it carries a meaning orange cannot also carry.
   - Page-edge fades are now flat at both ends, so the site
     carries no background gradients at all. Two things stay
     sanctioned because they are not decoration: the dot-grid
     texture on cover surfaces, and the scrim under text set on a
     photograph.
   - NEW --pso-accent-bar = 3px. The orange left-rule is sanctioned
     on exactly two components, the pull quote and the callout, and
     is tokenised so the width stops being retyped. It was removed
     from page furniture, which is where six copies came from.
   - --pso-w-black stays an alias for 700. Rajdhani has no 800.
   - ch WIDTHS: the v3.7 header claimed every ch-based width had
     been re-measured under Rajdhani. That was not true and the
     claim is withdrawn. See the ch note below.

   v3.7 changes (2026-07-22, display face replaced again):
   Bevellier lasted one day. Rajdhani replaces it as the display
   face, chosen by elimination against Chillax, Tanker, ten Google
   faces and eight neighbours of the leader, each tested on an
   identical hero so only the type varied. Licence verified before
   shipping: SIL Open Font License 1.1, Indian Type Foundry, free
   for commercial use and self-hosting.

     Display  Bevellier -> Rajdhani  (Google/ITF, self-hosted)

   Body, label and quote faces are UNCHANGED (Switzer, Martian Mono,
   Literata). Only the display face moved.

   - Weights 500/600/700 only. Bevellier's 400 had no Rajdhani role.
   - --pso-track-display 0.024em -> 0.01em, matching the live site.
   - --pso-w-black is an ALIAS for 700, not 800. Rajdhani has no
     weight above 700, so 800 could only ever be a synthesised bold
     or a fake outline. Neither is the face doing the work.
   - CAPS RULE CHANGED: capitals on H1 only. H2, H3 and below are
     sentence case. This replaces the 21 Jul every-display-heading
     -in-caps rule, which itself replaced a length test. Third rule
     in three days on the same question.
   - WARNING, ch UNITS. `ch` is the width of the "0" glyph and it
     moves with the face. The live service hero was tuned to 28ch
     under Bevellier; under Rajdhani that same 28ch resolved to
     961px against a design intent of 1164px, so the hero was
     silently 200px narrow. If the face changes again: measure it,
     never carry the number over. Use
     `node scripts/brand/measure-ch-widths.js`. Do not write that
     the widths have been re-measured unless the tool has been run
     and the output read.

   v3.6 changes (2026-07-21, type system replaced):
   The whole type system changed. Geist and Inter Tight are on a
   large share of the web and had started to read as default rather
   than chosen. Decided after three rounds of comparison.

     Display  Geist         -> Bevellier     (Fontshare, self-hosted)
     Body     Inter Tight   -> Switzer       (Fontshare, self-hosted)
     Label    JetBrains Mono-> Martian Mono  (Google, self-hosted)
     Quote    Newsreader it.-> Literata      (Google, self-hosted)

   - All four faces self-hosted from assets/fonts/. The Google Fonts
     @import in base.css is gone: Bevellier and Switzer are Fontshare
     licences and were never on Google Fonts, so a CDN import could
     not have served the system anyway.
   - NEW --pso-track-display = 0.024em. Display tracking is now
     POSITIVE and fixed at every size. --pso-track-tight (-0.03em) and
     --pso-track-snug (-0.015em) are REMOVED, not deprecated: Bevellier
     is drawn tight and negative tracking closes it up. All 51 hard
     -coded negative letter-spacing values across the library were
     swept to this token in the same pass.
   - --pso-w-black (800) REMOVED. Bevellier ships 400/500/600/700 and
     has no 800; the token was an instruction to synthesise a weight.
   - The weight ladder is now a role ladder: 700 hero and page titles,
     600 H2s and section headings, 500 small display (card titles,
     names). 500 and 600 are new roles that had no equivalent under
     Geist, where weight was chosen by eye.
   - NEW --pso-font-code. Martian Mono is the LABEL face, not a code
     face, so code samples get their own system stack. Under
     JetBrains Mono these two roles were the same font by accident.
   - --pso-track-label 0.16em -> 0.18em, matching the live kicker.
   - --pso-font-display-alt1 / -alt2 removed. Declared, never used.
   - Buttons moved from display to body (Switzer 500), and quotes from
     display to Literata upright. See components.css.

   v3.5 changes (2026-04-29, component pass):
   - NEW token --pso-on-orange = --pso-navy. Single source of truth
     for any "dark thing on top of orange" (button label, switch
     thumb, radio dot, pagination active text). Replaces the
     previous trio of #1A0A00 / --pso-navy / --pso-ink ad-hoc
     values. See proposals.html C01 + C05.
   - NEW token --pso-input-bg = rgba(0,0,0,0.18). Was inlined as
     rgba(8, 16, 28,0.5) (Ink at half-alpha) which leaked the
     header/footer-only Ink reservation. C07.
   - Text opacity steps re-spaced: muted 60% (was 62), faint 45%
     (was 52). Wider gap between roles. Same change applied to
     light-theme equivalents.
   - --pso-blue removed. Was declared, never used. C12.

   v3.4 changes (2026-04-25, post-rebuild promotion):
   - No token changes. v3.4 was rule-only: data-viz single-colour,
     title+lede column, mega-menu animation, mega capability foot,
     uniform service-wall variant, locked service-icon set, and a
     new `.pso-link-arrow` primitive. See index.html v3.4 changelog
     for the full list.

   v3.3 changes (2026-04-25 PM, final):
   - --pso-page = Shade A #08101C (deep ocean, slight blue cast).
     Confirmed by visual side-by-side test on the AI Audit shade
     comparison page. Sits below Navy in tone.
   - Surface stack re-tuned: #131C2E → #1B273C → #22304A.
     Even step rhythm above the new darker page. Bigger jumps
     than the v3.0 ramp; cards lift cleanly on the darker bg.
   - --pso-navy retained at #0B1320 as legacy anchor — used for
     slides, email, light-on-dark contexts. No longer the page bg.
   - --pso-ink reserved for header/footer fade endpoint only.

   v3.1 / v3.2 history:
   - v3.1: First introduced --pso-page as Shade A.
   - v3.2: Mistakenly reverted --pso-page to Navy in a parallel chat;
     visual test had already chosen Shade A. Reverted in v3.3.
   ============================================================ */

:root {
  /* ---- CANVAS & SURFACES (depth by layering, never by shadow) ---- */
  --pso-page:             #08101C;  /* page background — Shade A (canonical) */
  --pso-navy:             #0B1320;  /* legacy anchor — slides, email */
  --pso-navy-raised:      #131C2E;  /* +1 layer (cards) */
  --pso-navy-elevated:    #1B273C;  /* +2 layer */
  --pso-navy-highest:     #22304A;  /* +3 layer, max elevation */

  /* ---- ACCENTS ---- */
  --pso-orange:           #FE5D0E;  /* primary action, key accent */
  --pso-orange-hot:       #FF7A3A;  /* hover */
  --pso-orange-deep:      #C43F00;  /* pressed / dark-on-orange text */
  /* The print orange. Same hue as --pso-orange, brightness pulled 4%,
     visually indistinguishable from the brand orange side by side, and
     it clears the 3:1 graphical floor on bone where the raw brand
     orange does not. This is the ONLY orange on a light page. It is
     not a compliance shade: see the light block for the decision. */
  --pso-orange-print:     #F0550A;

  /* The single "dark on orange" token. Used by every surface that
     places a dark mark on an orange fill: button labels, switch
     thumb, checkbox tick, radio dot, pagination active text. */
  --pso-on-orange:        var(--pso-navy);

  /* Deep red — destructive hover, never routes through orange */
  --pso-red-deep:         #B8382C;

  /* Focus ring — single token, used by every interactive element */
  --pso-focus-ring:       0 0 0 2px var(--pso-page), 0 0 0 4px var(--pso-orange);

  /* Disabled — opaque, not opacity. Measured 4.5:1 on navy + navy-raised */
  --pso-disabled-bg:      rgba(242, 241, 238, 0.06);
  --pso-disabled-border:  rgba(242, 241, 238, 0.10);
  --pso-disabled-text:    rgba(242, 241, 238, 0.50);

  /* ---- NEUTRALS (near-white family) ---- */
  --pso-bone:             #F2F1EE;  /* primary light surface */
  --pso-bone-dim:         #E4E2DD;  /* secondary light surface */
  --pso-bone-deeper:      #CFCCC5;  /* bordered light surface */

  /* ---- FUNCTIONAL (feedback only, never brand) ---- */
  --pso-teal:             #2DB5A5;  /* info / ai signifier */
  --pso-green:            #3EB56B;
  --pso-amber:            #E0A63A;
  --pso-red:              #E05A4B;

  /* ---- TEXT (opacity system on dark) ---- */
  --pso-text:             rgba(242, 241, 238, 1.00);  /* headings */
  --pso-text-body:        rgba(242, 241, 238, 0.72);  /* paragraph */
  --pso-text-muted:       rgba(242, 241, 238, 0.60);  /* meta, captions */
  /* FAINT RETIRED as a distinct step, 22 Jul 2026. Measured, not judged:
     bone on the page needs 48% alpha to clear WCAG AA 4.5:1, and 55% to clear
     it on navy-highest, so widening the gap to muted and passing the audit are
     incompatible. Faint now equals muted. The token stays so nothing breaks. */
  --pso-text-faint:       rgba(242, 241, 238, 0.60);  /* = muted */
  --pso-text-disabled:    rgba(242, 241, 238, 0.18);

  /* ---- CHART FILLS (v3.9, 2026-07-23) ----
     Chart series had been borrowing the TEXT ramp for their fills, which
     is what let the documentation drift: R.2 described a quaternary at 45%
     while --pso-text-faint had already been retired up to 60%, so the page
     and the token disagreed and neither was wrong on its own terms.

     They are separated because they are measured against different floors.
     Text is 4.5:1 (WCAG 1.4.3). A bar, ring segment or gridline is a
     graphical object at 3:1 (WCAG 1.4.11), which is why step 4 is allowed
     to sit below where any text token may go. That is the whole reason the
     chart ramp can keep four legible steps when the text ramp cannot.

     NEVER set type in these. They are fills. */
  --pso-chart-1:  var(--pso-orange);              /* the point of the chart */
  --pso-chart-2:  rgba(242, 241, 238, 0.72);      /* context, prior period */
  --pso-chart-3:  rgba(242, 241, 238, 0.60);      /* supporting series */
  --pso-chart-4:  rgba(242, 241, 238, 0.42);      /* noise, out of scope */

  /* ---- BORDERS (hairline over heavy, always) ---- */
  --pso-border-faint:     rgba(242, 241, 238, 0.04);
  --pso-border:           rgba(242, 241, 238, 0.08);
  --pso-border-strong:    rgba(242, 241, 238, 0.14);
  --pso-border-interact:  rgba(242, 241, 238, 0.22);  /* buttons, inputs */
  --pso-border-orange:    rgba(254, 93, 14, 0.26);

  /* ---- INPUT FILL (neutral darken; no Ink leak) ---- */
  --pso-input-bg:         rgba(0, 0, 0, 0.18);

  /* ---- SCRIM (v3.9, 2026-07-23) ----
     The one gradient the system still sanctions, alongside the dot grid.
     It exists so type stays legible over a photograph, which is
     legibility rather than decoration, and that is the only reason it
     survives a no-gradient rule. Tokenised because it had been retyped
     as a raw rgba of the page colour wherever it was needed. */
  --pso-scrim:            linear-gradient(180deg, transparent 40%, rgba(8, 16, 28, 0.8) 100%);

  /* Sticky header fill. Page colour at 82%, so content passing underneath
     is felt rather than seen. Tokenised 23 July 2026; it had been a raw
     rgba of the page hex, which meant a page-colour change would have
     left the header behind. */
  --pso-header-bg:        rgba(8, 16, 28, 0.82);

  /* ---- TYPE (v3.7, 2026-07-22 — four-font system) ----
     Display  Rajdhani   headings, hero, names. Self-hosted, OFL 1.1.
     Body     Switzer    paragraphs and buttons. Self-hosted, Fontshare.
     Label    Martian Mono  kickers, eyebrows, card numbers, bylines. NOT code.
     Quote    Literata   testimonials and pull quotes. Upright, never italic.
     Fallbacks stay listed so an un-served page degrades to the old system
     rather than to system-ui. */
  --pso-font-display:     "Rajdhani", ui-sans-serif, system-ui, sans-serif;
  --pso-font-body:        "Switzer", ui-sans-serif, system-ui, sans-serif;
  --pso-font-mono:        "Martian Mono", ui-monospace, SFMono-Regular, monospace;
  --pso-font-quote:       "Literata", Georgia, "Times New Roman", serif;

  /* Code is not a brand role. Nothing in the four-font system is a code face:
     Martian Mono is the LABEL font. Any code sample uses the stack below. */
  --pso-font-code:        ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* Code surface. FIXED VALUES, deliberately not var() references, and
     deliberately NOT overridden in the light block below. A code block is
     dark on every surface, including a light print page. Pointing it at
     --pso-navy looked right until the light theme flipped that token to
     bone-dim and the block rendered off-white text on an off-white box.
     Caught in a printed report, 23 Jul 2026. */
  --pso-code-bg:          #0B1320;
  --pso-code-text:        #E4E2DD;

  /* Type scale — fluid clamp on display, fixed px on chrome */
  --pso-text-6xl: clamp(64px, 3rem + 6vw, 140px);   /* hero display */
  --pso-text-5xl: clamp(48px, 2.5rem + 3.5vw, 96px); /* h1 */
  --pso-text-4xl: clamp(36px, 1.9rem + 2vw, 64px);  /* h2 */
  --pso-text-3xl: clamp(28px, 1.5rem + 1.2vw, 44px);/* h3 */
  --pso-text-2xl: clamp(22px, 1.2rem + 0.6vw, 30px);/* h4 */
  --pso-text-xl:  clamp(18px, 1rem + 0.3vw, 22px);  /* lead body */
  --pso-text-lg:  16px;
  --pso-text-base:16px;   /* was 15px until 22 Jul 2026. 15 read small on the dark page and 16 is the browser default. */
  --pso-text-sm:  13px;
  --pso-text-xs:  11px;  /* label eyebrow */

  /* 2xs, added 23 July 2026. This is a real role, not a convenience.
     Roughly sixty places across the library were setting font-size: 10px
     by hand, and one was setting 9px, all of them on the same thing: an
     uppercase Martian Mono label at 0.18em tracking. Bumping them all to
     the 11px step would have reflowed chrome that is deliberately tight,
     so the honest fix is to name the size that was already in use.

     CONSTRAINT, and it is the whole reason this token is safe: UPPERCASE
     ONLY, tracked at --pso-track-label or wider. Capitals plus open
     tracking are what carry legibility at this size; take either away and
     10px is simply too small to set. So: never body copy, never sentence
     case, never a paragraph, never a measure longer than a few words.

     Two sanctioned uses beyond mono labels, both checked 23 July 2026:
     the caret glyph in the header nav, which is an icon sized to sit with
     its label rather than type; and the "previous" specimens on the
     Proposals page, which are uppercase display at label tracking and are
     deliberately showing a design that was replaced. */
  --pso-text-2xs: 10px;  /* UPPERCASE, tracked. See constraint above. */

  /* Weights — Rajdhani ships 300/400/500/600/700. There is NO 800 and
     no 900. The display ladder is a role ladder, not a free choice:
       700  hero and page titles
       600  H2s and section headings
       500  small display text — card titles, names, stat labels
     Only 500/600/700 are loaded as web fonts. A heavier hero is not
     available: it could only come from a synthesised bold or a fake
     -webkit-text-stroke outline, which is a trick rather than the
     face doing the work. That is why 800 is an alias, not a weight. */
  --pso-w-regular:   400;
  --pso-w-medium:    500;   /* display: card titles, names */
  --pso-w-semibold:  600;   /* display: H2s, section headings */
  --pso-w-bold:      700;   /* display: hero, page titles */
  --pso-w-black:     700;   /* ALIAS. Rajdhani has no 800. Never synthesise. */

  /* Tracking
     Display tracking is POSITIVE and fixed. +0.01em is what opens
     Rajdhani up, and it is the same value at every size.
     Never set negative tracking on display type. */
  --pso-track-display: 0.01em;
  --pso-track-normal:  0;
  --pso-track-wide:    0.08em;
  --pso-track-label:   0.18em;  /* was 0.16em — matches the live kicker */

  /* Leading */
  --pso-lead-flat:  1.0;
  --pso-lead-display: 1.05;
  --pso-lead-snug:  1.2;
  --pso-lead-normal:1.5;
  --pso-lead-body:  1.65;

  /* ---- SPACING (4pt + 5pt hybrid — same bones as current system) ---- */
  --pso-s-0:   0;
  --pso-s-1:   4px;
  --pso-s-2:   8px;
  --pso-s-3:   12px;
  --pso-s-4:   16px;
  --pso-s-5:   20px;
  --pso-s-6:   24px;
  --pso-s-8:   32px;
  --pso-s-10:  40px;
  --pso-s-12:  48px;
  --pso-s-16:  64px;
  --pso-s-20:  80px;
  --pso-s-24:  96px;
  --pso-s-32:  128px;
  --pso-s-40:  160px;

  /* ---- RADII ---- */
  --pso-r-xs:  4px;
  --pso-r-sm:  8px;
  --pso-r-md:  10px;   /* buttons, inputs */
  --pso-r-lg:  14px;   /* cards */
  --pso-r-xl:  20px;   /* hero panels */
  --pso-r-2xl: 28px;
  --pso-r-full: 999px; /* avatars + tags only */

  /* ---- RADII, PRINT ----
     Print runs tighter than screen and always has. A screen card is
     viewed at arm's length on a backlit surface where a 14px corner
     reads as soft; the same corner on A4 at reading distance reads as
     a bubble, and a page of them reads as a leaflet rather than a
     document. So print has its own scale rather than being forced onto
     the screen one.

     It has three steps, deliberately. Before this was tokenised the
     print chrome carried six values between 2px and 8px, most of them
     a single pixel apart and none of them chosen: 2, 3, 4, 5, 6 and 8.
     A pixel of difference at that size is not a decision anybody can
     see, it is just drift with a number on it. Rationalised to three
     steps on 23 July 2026 when the chrome moved into the library. */
  --pso-r-print-sm: 3px;   /* severity badges, inline chips, tags */
  --pso-r-print-md: 5px;   /* code blocks, small cards */
  --pso-r-print-lg: 8px;   /* cards, banners, score panels */

  /* ---- ACCENT BAR ----
     The orange left-rule. Sanctioned on the pull quote and the
     callout, and nowhere else. Tokenised in v3.8 so the width is
     not retyped into page furniture. */
  --pso-accent-bar: 3px;

  /* ---- MOTION ---- */
  --pso-ease:         cubic-bezier(0.22, 1, 0.36, 1);
  --pso-ease-io:      cubic-bezier(0.65, 0, 0.35, 1);
  --pso-ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);  /* v3.5 (2026-07-21): signature moments. Was already hand-rolled on three surfaces. */
  --pso-dur-fast:     120ms;
  --pso-dur:          200ms;
  --pso-dur-slow:     360ms;
  --pso-dur-enter:    520ms;  /* v3.5 (2026-07-21): entrances only. Matches the existing mega-menu value. */
  --pso-stagger:      60ms;   /* v3.5 (2026-07-21): step between siblings. Cap the total at ~600ms. */

  /* ---- LAYOUT ---- */
  --pso-max-content:  1240px;
  --pso-max-prose:    680px;
  --pso-gutter:       clamp(20px, 4vw, 48px);

  /* ---- ORANGE AS INK (v3.9, 2026-07-23) ----
     Orange splits into two roles because the two roles measure very
     differently once the background is light, and until now the system
     had one token doing both.

       FILL   an orange surface with a dark mark on it. Buttons, the
              accent bar, the dot on a chart. Stays --pso-orange in both
              themes: navy on orange is 6.01:1 either way.
       INK    orange as the mark itself. Body text, a link, a kicker, a
              hairline border. This is the one that breaks.

     Measured 23 July 2026: --pso-orange on bone is 2.74:1. That is not
     a near miss. It fails AA text at 4.5:1 and it fails even the 3:1
     floor for a meaningful graphical object, so an orange hairline on a
     light page is not reliably visible either. --pso-orange-deep on bone
     is 4.58:1 and passes as text.

     So: on dark, ink and fill are the same orange. On light, ink drops
     to the deep orange and fill does not move. */
  --pso-orange-ink:   var(--pso-orange);
}

/* ============================================================
   LIGHT TOKENS — print, slide covers, email only

   `.page--light` is listed alongside the attribute selector so a
   light report page picks these up with no extra markup. Report
   chrome sets light and dark pages by class, and if the light
   values needed a `data-theme` attribute as well, a page missing
   it would silently render dark text on a light background. Added
   2026-07-23 when the report chrome moved into the library.
   ============================================================ */
[data-theme="light"], .theme-light, .page--light {
  /* ---- SURFACES ----
     SETTLED 23 July 2026: these stay as they are. Recording the argument
     because the obvious reading of the numbers is the wrong answer, and
     without this note someone will "fix" it again.

     All three raised steps are the same white, so the elevation ladder
     the dark theme runs on collapses to one step here. Measured:
       bone vs white                              1.130:1
       light stack as it stands, total range      1.130:1
       dark stack, page to highest, total range   1.444:1
     Inverting the direction, so a light page starts at white and each
     layer goes DARKER, measures better than the dark stack does:
       white -> bone 1.130, bone -> bone-dim 1.146,
       bone-dim -> deeper 1.239, total range 1.603:1

     Rejected anyway, on David's steer and on the evidence. The light
     theme lives in exactly one place, the brand PDF reports, and the
     three report stylesheets reference these elevation tokens ONCE in
     total. Nothing nests, so the ladder is collapsing under no weight.
     Inverting would change how every existing report looks in order to
     fix a problem no real artefact has. A measurement that improves a
     number nobody is using is not an improvement.

     Revisit only if a report genuinely nests three levels, and then with
     that page open rather than from this file. */
  --pso-page:          var(--pso-bone);
  --pso-navy:          var(--pso-bone-dim);
  --pso-navy-raised:   #FFFFFF;
  --pso-navy-elevated: #FFFFFF;
  --pso-navy-highest:  #FFFFFF;

  --pso-text:          rgba(11, 19, 32, 1.00);
  --pso-text-body:     rgba(11, 19, 32, 0.72);
  --pso-text-muted:    rgba(11, 19, 32, 0.60);
  /* Faint equals muted, see the dark ramp above. On bone-dim, 60% measures
     4.46:1 and just misses AA, so muted text is not set on bone-dim. */
  --pso-text-faint:    rgba(11, 19, 32, 0.60);   /* = muted */
  --pso-text-disabled: rgba(11, 19, 32, 0.22);

  /* ONE ORANGE ON LIGHT. --pso-orange itself resolves to the print
     orange here, so every orange on a light surface is the same
     colour whichever token a component happens to reach for, and the
     rule stops depending on authors picking the right name. This is
     the whole point: the chrome's light pages reference --pso-orange
     directly in twenty-odd places and never once referenced
     --pso-orange-ink, so a light-only ink token was a rule with no
     enforcement. Measured 23 July 2026: navy on the print orange is
     5.46:1, so fills keep their AA-passing label text. */
  --pso-orange:        var(--pso-orange-print);

  /* Orange as ink goes to the PRINT orange, not the deep orange.
     Fill stays orange, and on light that is the same value.

     DECIDED 23 July 2026 by David, and this reverses the v3.9 answer.
     The deep orange measures 4.58:1 and passes AA as text, so on the
     numbers it was the right call. On the page it was not: #C43F00 is
     a rust, and a light page carrying rust labels next to a dark cover
     carrying #FE5D0E reads as two brands, which is exactly what the
     morning's report-chrome fix had to undo. --pso-orange-print holds
     the brand hue and measures 3.09:1 on bone: it clears the 3:1
     graphical floor (WCAG 1.4.11), so orange rules, bullets, left
     accents and 18pt-and-up display type are compliant outright.

     Small orange TEXT below 18pt does not reach 4.5:1, and we accept
     that as a logged, deliberate exception rather than fixing it with
     a second orange. The defence is not that the reader prints it. It
     is that ORANGE NEVER CARRIES INFORMATION ALONE: every small orange
     label in the chrome is a waypoint duplicated by ink text beside
     it, so nothing is lost if the orange is hard to read. That defence
     survives a screen reader and a client reading on glass; "they will
     print it" does not. Keep the pairing and the exception holds.

     Orange Ink (the deep-orange AA reserve shade) is RETIRED as a
     light-page option, not kept in reserve. A reserve shade nobody has
     ever swapped in is a second orange waiting to be used by accident.
     If a document ever has to hold AA end to end (public-sector or
     accessibility-contracted work), the answer is to set that
     document's labels in ink and drop the orange, not to reintroduce a
     rust. --pso-orange-deep survives for pressed states and the focus
     ring on interactive web surfaces, which is what it was made for.

     Rule: light surface, one orange, --pso-orange-print, every role. */
  --pso-orange-ink:    var(--pso-orange-print);
  --pso-border-orange: rgba(240, 85, 10, 0.34);

  /* Chart fills invert to navy, and the fourth step has to rise. At the
     dark theme's 0.42 it measures 2.70:1 on bone and misses the 3:1
     graphical floor; 0.50 clears it at 3.42:1. So the light chart ramp is
     genuinely tighter than the dark one, three steps of separation rather
     than four, which is a fact about a light page and not a bug.
     Series 1 moved off the deep orange with the ink decision above: a
     chart is a graphical object at 3:1, --pso-orange-print measures
     3.09:1 on bone and clears it, so the rust bought nothing here
     except a bar that did not match the cover. */
  --pso-chart-1:  var(--pso-orange-print);
  --pso-chart-2:  rgba(11, 19, 32, 0.72);
  --pso-chart-3:  rgba(11, 19, 32, 0.60);
  --pso-chart-4:  rgba(11, 19, 32, 0.50);

  /* Scrim and header fill flip to the light page colour. */
  --pso-scrim:     linear-gradient(180deg, transparent 40%, rgba(242, 241, 238, 0.88) 100%);
  --pso-header-bg: rgba(242, 241, 238, 0.86);

  /* Disabled is opaque on light too, measured against bone and white. */
  --pso-disabled-bg:     rgba(11, 19, 32, 0.06);
  --pso-disabled-border: rgba(11, 19, 32, 0.12);
  --pso-disabled-text:   rgba(11, 19, 32, 0.42);

  /* Focus ring. The dark definition resolves to plain --pso-orange, which
     on bone is the same 2.74:1 that fails everywhere else, and a focus
     indicator has a hard 3:1 floor of its own under WCAG 2.4.11. It was
     inheriting silently because the ring is built from --pso-page and
     --pso-orange and only the first of those flips. Rebuilt on the deep
     orange at 4.58:1. */
  --pso-focus-ring: 0 0 0 2px var(--pso-page), 0 0 0 4px var(--pso-orange-deep);

  --pso-border-faint:    rgba(11, 19, 32, 0.04);
  --pso-border:          rgba(11, 19, 32, 0.08);
  --pso-border-strong:   rgba(11, 19, 32, 0.14);
  --pso-border-interact: rgba(11, 19, 32, 0.22);

  /* Light-theme input fill: subtle warm bone, not transparent black */
  --pso-input-bg:        rgba(11, 19, 32, 0.04);

  /* On-orange flips on light backgrounds — orange is bright enough,
     navy still reads cleanly. No override needed; left here for clarity. */
}


/* ===== base.css ===== */
/* Base layer: reset + page + helpers
   ------------------------------------------------------------------
   Fonts, v3.7 (2026-07-22). All four faces are SELF-HOSTED from
   assets/fonts/ — no Google Fonts @import, no Fontshare CDN call.
   Switzer is a Fontshare licence and is not on Google Fonts at all,
   so a CDN import could never have served the whole system; hosting
   all four keeps one loading path and one failure mode.
   ------------------------------------------------------------------ */

/* Display — Rajdhani. Three discrete weights, no variable axis.
   Rajdhani tops out at 700: there is no 800 and no 900. A heavier
   hero can only come from a synthesised weight or a fake outline,
   so it does not exist here. Ask for 700 and stop.
   SIL Open Font License 1.1, Indian Type Foundry. */
/* @ds-font-face-dropped: unresolvable src */
/* @ds-font-face-dropped: unresolvable src */
/* @ds-font-face-dropped: unresolvable src */

/* Body — Switzer. Also carries buttons. */
/* @ds-font-face-dropped: unresolvable src */
/* @ds-font-face-dropped: unresolvable src */
/* @ds-font-face-dropped: unresolvable src */
/* @ds-font-face-dropped: unresolvable src */

/* Label — Martian Mono. Variable weight axis. Labels only, never code. */
/* @ds-font-face-dropped: unresolvable src */

/* Quote — Literata. Upright only; no italic file is loaded on purpose,
   so a stray font-style:italic cannot be faked by the browser. */
/* @ds-font-face-dropped: unresolvable src */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

html {
  background: var(--pso-page);
  color: var(--pso-text);
  font-family: var(--pso-font-body);
  font-size: var(--pso-text-base);
  line-height: var(--pso-lead-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body {
  min-height: 100vh;
  background: var(--pso-page);
  color: var(--pso-text-body);
  font-feature-settings: "ss01", "cv11";
}

/* ------------------------------------------------------------------
   Type primitives — Rajdhani weight ladder (v3.7)

   The ladder is a role ladder. Pick the weight from what the text IS,
   never from how heavy it needs to look on the day:
     700  hero, page titles                  h1, .pso-display-hero
     600  H2s, section headings              h2, h3
     500  small display — card titles, names h4, h5, .pso-display-sm
   Tracking is +0.01em everywhere and never negative.
   ------------------------------------------------------------------ */
h1, h2, h3, h4, h5, h6, .pso-display {
  font-family: var(--pso-font-display);
  font-weight: var(--pso-w-semibold);
  color: var(--pso-text);
  letter-spacing: var(--pso-track-display);
  line-height: var(--pso-lead-snug);
  margin: 0;
  text-wrap: balance;
}
h1, .pso-h1 { font-size: var(--pso-text-5xl); line-height: var(--pso-lead-display); font-weight: var(--pso-w-bold); }
h2, .pso-h2 { font-size: var(--pso-text-4xl); font-weight: var(--pso-w-semibold); }
h3, .pso-h3 { font-size: var(--pso-text-3xl); font-weight: var(--pso-w-semibold); }
h4, .pso-h4 { font-size: var(--pso-text-2xl); font-weight: var(--pso-w-medium); }
h5, .pso-h5 { font-size: var(--pso-text-xl);  font-weight: var(--pso-w-medium); }
h6, .pso-h6 { font-size: var(--pso-text-lg);  font-weight: var(--pso-w-medium); }

/* Small display — card titles, person names, stat labels. Weight 500. */
.pso-display-sm {
  font-family: var(--pso-font-display);
  font-weight: var(--pso-w-medium);
  letter-spacing: var(--pso-track-display);
  color: var(--pso-text);
}

p { margin: 0; text-wrap: pretty; }

.pso-display-hero {
  font-size: var(--pso-text-6xl);
  font-weight: var(--pso-w-bold);
  letter-spacing: var(--pso-track-display);
  line-height: var(--pso-lead-display);
}

/* ------------------------------------------------------------------
   The caps rule (v3.7, 2026-07-22). CAPITALS ON H1 ONLY.
   H2, H3 and below are sentence case.

   This is a LEVEL test, not a length test, so it can be derived in
   CSS and is applied automatically below. It replaces two earlier
   rules in quick succession: the 21 Jul every-display-heading-in-caps
   rule, and before that a six-words-or-fewer length test that needed
   hand-application because CSS cannot count words.
   ------------------------------------------------------------------ */
h1, .pso-h1, .pso-display-hero { text-transform: uppercase; }
h2, h3, h4, h5, h6,
.pso-h2, .pso-h3, .pso-h4, .pso-h5, .pso-h6 { text-transform: none; }

/* Escape hatch, both directions. Use sparingly and only with a reason. */
.pso-caps    { text-transform: uppercase; }
.pso-no-caps { text-transform: none; }

.pso-accent {
  color: var(--pso-orange);
}

/* Labels — v3.6: Martian Mono, not display. This is the whole reason
   the label font exists as a separate role: kickers, eyebrows, card
   numbers and bylines are machine-voice, and they should not read as
   a small heading. Weight 500 — Martian Mono is wide, and 600 blocks up. */
.pso-label {
  font-family: var(--pso-font-mono);
  font-size: var(--pso-text-xs);
  font-weight: var(--pso-w-medium);
  text-transform: uppercase;
  letter-spacing: var(--pso-track-label);
  color: var(--pso-orange);
}
.pso-label--neutral { color: var(--pso-text-muted); }
.pso-label--teal    { color: var(--pso-teal); }

/* Martian Mono is the LABEL face. Code uses the system stack instead —
   see --pso-font-code. Do not set code in Martian Mono. */
.pso-mono { font-family: var(--pso-font-mono); }
.pso-code { font-family: var(--pso-font-code); }

.pso-text-body  { color: var(--pso-text-body); }
.pso-text-muted { color: var(--pso-text-muted); }
.pso-text-faint { color: var(--pso-text-faint); }

a { color: var(--pso-text); text-decoration: none; }
a.pso-link {
  color: var(--pso-text);
  border-bottom: 1px solid var(--pso-border-interact);
  transition: border-color var(--pso-dur) var(--pso-ease), color var(--pso-dur) var(--pso-ease);
}
a.pso-link:hover { color: var(--pso-orange); border-bottom-color: var(--pso-orange); }

hr.pso-rule { border: 0; border-top: 1px solid var(--pso-border); margin: 0; }

/* Layout helpers */
.pso-container {
  max-width: var(--pso-max-content);
  margin: 0 auto;
  padding-left: var(--pso-gutter);
  padding-right: var(--pso-gutter);
}

.pso-section {
  padding-top: var(--pso-s-20);
  padding-bottom: var(--pso-s-20);
}
.pso-section--tight { padding-top: var(--pso-s-12); padding-bottom: var(--pso-s-12); }

.pso-stack > * + * { margin-top: var(--pso-s-4); }
.pso-stack-lg > * + * { margin-top: var(--pso-s-8); }

/* Selection */
::selection { background: var(--pso-orange); color: var(--pso-navy); }

/* Scrollbar */
html { scrollbar-width: thin; scrollbar-color: var(--pso-orange) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--pso-orange);
  border-radius: 0;
}
::-webkit-scrollbar-thumb:hover { background: var(--pso-orange-deep); }

/* Focus ring */
:focus-visible {
  outline: 2px solid var(--pso-orange);
  outline-offset: 2px;
  border-radius: 2px;
}


/* ===== components.css ===== */
/* ============================================================
   Perkins SmartOps — Components
   ============================================================ */

/* -------- BUTTONS -------- */
.pso-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--pso-s-2);
  height: 48px;
  padding: 0 var(--pso-s-6);
  /* v3.6: buttons are BODY type, not display. Switzer at 500.
     Rajdhani is drawn for size and reads shouty in a 48px control. */
  font-family: var(--pso-font-body);
  font-size: var(--pso-text-base);
  font-weight: var(--pso-w-medium);
  letter-spacing: var(--pso-track-normal);
  border-radius: var(--pso-r-md);
  border: 1px solid transparent;
  background: transparent;
  color: var(--pso-text);
  cursor: pointer;
  transition: background var(--pso-dur) var(--pso-ease),
              border-color var(--pso-dur) var(--pso-ease),
              color var(--pso-dur) var(--pso-ease),
              transform var(--pso-dur-fast) var(--pso-ease);
  position: relative;
  white-space: nowrap;
  text-decoration: none;
}
.pso-btn:active { transform: scale(0.985); }
.pso-btn--mini { height: 36px; padding: 0 var(--pso-s-4); font-size: var(--pso-text-sm); }

.pso-btn--primary {
  background: var(--pso-orange);
  color: var(--pso-on-orange);
  border-color: var(--pso-orange);
}
.pso-btn--primary:hover { background: var(--pso-orange-hot); border-color: var(--pso-orange-hot); }

.pso-btn--outline {
  border-color: var(--pso-border-interact);
  color: var(--pso-text);
}
.pso-btn--outline:hover { border-color: var(--pso-orange); color: var(--pso-orange); }

/* Ghost — quiet text-style action that still occupies a button box.
   v3.5: locked to the same 48px chrome height as primary so CTA rows
   align cleanly. The hover affordance is the underline + colour shift,
   not a bg fill. */
.pso-btn--ghost {
  color: var(--pso-text);
  background: transparent;
  border-color: transparent;
  position: relative;
}
.pso-btn--ghost::after {
  content: "";
  position: absolute;
  left: var(--pso-s-6); right: var(--pso-s-6);
  bottom: calc(50% - 12px);
  border-bottom: 1px solid var(--pso-border-interact);
  transition: border-bottom-color var(--pso-dur) var(--pso-ease);
}
.pso-btn--ghost:hover { color: var(--pso-orange); }
.pso-btn--ghost:hover::after { border-bottom-color: var(--pso-orange); }
.pso-btn--ghost.pso-btn--mini::after { left: var(--pso-s-4); right: var(--pso-s-4); bottom: calc(50% - 10px); }

/* Destructive scope: there is no filled-red "primary" button.
   --danger is outline-only. If a future surface genuinely needs to
   shout, that's a deliberate rule break and should be discussed. */

.pso-btn--danger {
  background: transparent;
  border-color: var(--pso-red);
  color: var(--pso-red);
}
/* Hover deepens the red. Never routes through orange (principle 02). */
.pso-btn--danger:hover { background: var(--pso-red-deep); border-color: var(--pso-red-deep); color: var(--pso-bone); }

.pso-btn--icon {
  width: 48px; padding: 0;
  justify-content: center;
  border: 1px solid var(--pso-border-interact);
  border-radius: var(--pso-r-md);
}
.pso-btn--icon.pso-btn--round { border-radius: var(--pso-r-full); }
.pso-btn--icon.pso-btn--mini { width: 36px; }
.pso-btn--icon:hover { border-color: var(--pso-orange); color: var(--pso-orange); }

.pso-btn--filled {
  background: var(--pso-navy-elevated);
  border-color: var(--pso-border);
}
.pso-btn--filled:hover { background: var(--pso-navy-highest); border-color: var(--pso-orange); color: var(--pso-orange); }

/* Arrow: straight, translates slightly right on hover. No rotation. */
.pso-btn .pso-arrow { transition: transform var(--pso-dur) var(--pso-ease); width: 16px; height: 16px; }
.pso-btn:hover .pso-arrow { transform: translateX(3px); }

/* Read More — right-facing arrow that slides */
.pso-link-more {
  display: inline-flex; align-items: center; gap: var(--pso-s-2);
  font-family: var(--pso-font-display);
  font-weight: var(--pso-w-semibold);
  color: var(--pso-text);
  font-size: var(--pso-text-base);
}
.pso-link-more .pso-arrow { transition: transform var(--pso-dur) var(--pso-ease); }
.pso-link-more:hover { color: var(--pso-orange); }
.pso-link-more:hover .pso-arrow { transform: translateX(4px); }

/* Disabled — opaque tokens, measured contrast. Not opacity. */
.pso-btn:disabled,
.pso-btn[aria-disabled="true"] {
  background: var(--pso-disabled-bg);
  border-color: var(--pso-disabled-border);
  color: var(--pso-disabled-text);
  pointer-events: none;
  cursor: not-allowed;
}

/* Focus ring — applies to every interactive element. WCAG 2.4.7. */
.pso-btn:focus-visible,
.pso-input:focus-visible,
.pso-textarea:focus-visible,
.pso-tab:focus-visible,
.pso-pagination button:focus-visible,
.pso-breadcrumbs a:focus-visible,
.pso-accordion-item summary:focus-visible,
.pso-link-more:focus-visible,
.pso-card--interact:focus-visible,
.pso-check:focus-within .pso-check__box,
.pso-radio:focus-within .pso-radio__box,
.pso-switch:focus-within .pso-switch__track {
  outline: none;
  box-shadow: var(--pso-focus-ring);
}

/* -------- CARDS -------- */
/* TWO DEPTH SIGNALS, NOT THREE (proposal 02, approved 22 July 2026).
   A card used to say "container" three times over: a raised background, a
   hairline border, and generous padding. Premium-flat picks two. The default
   card now carries the hairline and the padding, and the surface stays at the
   page colour, so a card lifts by its edge rather than by its fill. The
   raised, elevated and highest variants are still there for the cases where a
   card genuinely needs to sit above something; they drop the border, because
   that would be the third signal again. */
.pso-card {
  background: transparent;
  border: 1px solid var(--pso-border);
  border-radius: var(--pso-r-lg);
  padding: var(--pso-s-8);
  transition: background var(--pso-dur) var(--pso-ease), border-color var(--pso-dur) var(--pso-ease);
}
.pso-card--filled    { background: var(--pso-navy-raised); border-color: transparent; }
.pso-card--raised    { background: var(--pso-navy-raised); border-color: transparent; }
.pso-card--highest   { background: var(--pso-navy-highest); border-color: transparent; }
.pso-card--hero      { background: var(--pso-navy-raised); border-color: transparent; border-radius: var(--pso-r-xl); padding: var(--pso-s-12); }
.pso-card--interact:hover { background: var(--pso-navy-elevated); border-color: var(--pso-border-strong); }

/* -------- BADGES / TAGS -------- */
.pso-badge {
  display: inline-flex; align-items: center; gap: 6px;
  height: 24px; padding: 0 10px;
  font-family: var(--pso-font-display);
  font-size: var(--pso-text-xs);
  font-weight: var(--pso-w-semibold);
  text-transform: uppercase;
  letter-spacing: var(--pso-track-label);
  border-radius: var(--pso-r-full);
  background: transparent;
  color: var(--pso-text-body);
  border: 1px solid var(--pso-border-strong);
}
.pso-badge--orange { color: var(--pso-orange); border-color: var(--pso-border-orange); background: rgba(254,93,14,0.08); }
.pso-badge--teal   { color: var(--pso-teal); border-color: rgba(45,181,165,0.28); background: rgba(45,181,165,0.08); }
.pso-badge--green  { color: var(--pso-green); border-color: rgba(62,181,107,0.28); background: rgba(62,181,107,0.08); }
.pso-badge--amber  { color: var(--pso-amber); border-color: rgba(224,166,58,0.30); background: rgba(224,166,58,0.08); }
.pso-badge--red    { color: var(--pso-red); border-color: rgba(224,90,75,0.30); background: rgba(224,90,75,0.08); }
.pso-badge .pso-dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }

/* -------- INPUTS -------- */
.pso-field { display: flex; flex-direction: column; gap: 6px; }
.pso-field__label {
  font-family: var(--pso-font-display);
  font-size: var(--pso-text-xs);
  font-weight: var(--pso-w-semibold);
  letter-spacing: var(--pso-track-label);
  text-transform: uppercase;
  color: var(--pso-text-muted);
}
/* Boxed is the default — a frame visible whether empty or filled. */
.pso-input,
.pso-textarea {
  width: 100%;
  font-family: var(--pso-font-body);
  font-size: var(--pso-text-base);
  color: var(--pso-text);
  background: var(--pso-input-bg);
  border: 1px solid var(--pso-border-strong);
  border-radius: var(--pso-r-md);
  padding: 14px 16px;
  outline: none;
  transition: border-color var(--pso-dur) var(--pso-ease), box-shadow var(--pso-dur) var(--pso-ease);
}
.pso-input::placeholder,
.pso-textarea::placeholder { color: var(--pso-text-faint); }
.pso-input:hover,
.pso-textarea:hover { border-color: var(--pso-border-interact); }
.pso-input:focus,
.pso-textarea:focus { border-color: var(--pso-orange); outline: none; }

/* Underline variant — for prose-like inline forms only. */
.pso-input--underline,
.pso-textarea--underline {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--pso-border-interact);
  border-radius: 0;
  padding: 10px 0;
}
.pso-input--underline:focus,
.pso-textarea--underline:focus { border-bottom-color: var(--pso-orange); }

/* Back-compat: .pso-input--boxed now no-ops, the boxed style is the default. */
.pso-input--boxed { /* default */ }

.pso-textarea { min-height: 120px; resize: vertical; }

/* Checkbox + radio */
.pso-check,
.pso-radio {
  display: inline-flex; align-items: center; gap: 10px;
  cursor: pointer; user-select: none;
  font-size: var(--pso-text-base);
  color: var(--pso-text-body);
}
.pso-check input, .pso-radio input { display: none; }
.pso-check__box,
.pso-radio__box {
  width: 18px; height: 18px;
  border: 1px solid var(--pso-border-interact);
  display: inline-grid; place-items: center;
  transition: all var(--pso-dur) var(--pso-ease);
  background: transparent;
}
.pso-check__box { border-radius: 4px; }
.pso-radio__box { border-radius: 999px; }
.pso-check input:checked + .pso-check__box,
.pso-radio input:checked + .pso-radio__box {
  background: var(--pso-orange); border-color: var(--pso-orange);
}
.pso-check input:checked + .pso-check__box::after { content: ""; width: 10px; height: 10px; background: var(--pso-on-orange); clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0, 43% 62%); }
.pso-radio input:checked + .pso-radio__box::after { content: ""; width: 7px; height: 7px; border-radius: 999px; background: var(--pso-on-orange); }

/* Toggle */
.pso-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; font-size: var(--pso-text-base); color: var(--pso-text-body); }
.pso-switch input { display: none; }
.pso-switch__track {
  width: 36px; height: 20px;
  background: var(--pso-navy-highest);
  border: 1px solid var(--pso-border-interact);
  border-radius: 999px;
  position: relative;
  transition: background var(--pso-dur) var(--pso-ease), border-color var(--pso-dur) var(--pso-ease);
}
.pso-switch__track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 999px;
  background: var(--pso-text);
  transition: transform var(--pso-dur) var(--pso-ease);
}
.pso-switch input:checked + .pso-switch__track { background: var(--pso-orange); border-color: var(--pso-orange); }
.pso-switch input:checked + .pso-switch__track::after { transform: translateX(16px); background: var(--pso-on-orange); }

/* Range */
.pso-range { -webkit-appearance: none; appearance: none; width: 100%; height: 2px; background: var(--pso-border-interact); outline: none; }
.pso-range::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 999px; background: var(--pso-orange); cursor: pointer; border: 3px solid var(--pso-page); box-sizing: content-box; }
.pso-range::-moz-range-thumb { width: 16px; height: 16px; border-radius: 999px; background: var(--pso-orange); border: 3px solid var(--pso-page); }

/* -------- ACCORDION -------- */
.pso-accordion { border-top: 1px solid var(--pso-border); }
.pso-accordion-item { border-bottom: 1px solid var(--pso-border); }
.pso-accordion-item summary {
  list-style: none; cursor: pointer;
  padding: var(--pso-s-5) 0;
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--pso-font-display);
  font-weight: var(--pso-w-semibold);
  font-size: var(--pso-text-lg);
  color: var(--pso-text);
  transition: color var(--pso-dur) var(--pso-ease);
}
.pso-accordion-item summary::-webkit-details-marker { display: none; }
.pso-accordion-item summary:hover { color: var(--pso-orange); }
.pso-accordion-item summary .pso-plus {
  width: 16px; height: 16px; position: relative;
  transition: transform var(--pso-dur) var(--pso-ease);
}
.pso-accordion-item summary .pso-plus::before,
.pso-accordion-item summary .pso-plus::after {
  content: ""; position: absolute; background: currentColor;
  top: 50%; left: 0; width: 100%; height: 1.5px; transform: translateY(-50%);
}
.pso-accordion-item summary .pso-plus::after { transform: translateY(-50%) rotate(90deg); transition: transform var(--pso-dur) var(--pso-ease); }
.pso-accordion-item[open] summary .pso-plus::after { transform: translateY(-50%) rotate(0); }
.pso-accordion-item__body {
  padding: 0 0 var(--pso-s-5);
  color: var(--pso-text-body);
  font-size: var(--pso-text-base);
  max-width: 80ch;   /* long-form cap. Full Column Rule, DESIGN.md. */
}

/* -------- TABS -------- */
.pso-tabs { display: flex; gap: var(--pso-s-6); border-bottom: 1px solid var(--pso-border); }
.pso-tab {
  padding: 12px 0;
  font-family: var(--pso-font-display);
  font-weight: var(--pso-w-semibold);
  font-size: var(--pso-text-base);
  color: var(--pso-text-muted);
  border-bottom: 2px solid transparent;
  cursor: pointer;
  transition: color var(--pso-dur) var(--pso-ease), border-color var(--pso-dur) var(--pso-ease);
  background: transparent; border-top: 0; border-left: 0; border-right: 0;
  margin-bottom: -1px;
}
.pso-tab:hover { color: var(--pso-text); }
.pso-tab.is-active { color: var(--pso-orange); border-bottom-color: var(--pso-orange); }

/* -------- PROGRESS -------- */
.pso-progress { height: 4px; background: var(--pso-border); overflow: hidden; border-radius: 999px; }
.pso-progress__bar { height: 100%; background: var(--pso-orange); transition: width var(--pso-dur-slow) var(--pso-ease); }
@keyframes pso-indet { 0%{transform:translateX(-100%)} 100%{transform:translateX(250%)} }
.pso-progress--indet .pso-progress__bar { width: 40%; animation: pso-indet 1.4s infinite var(--pso-ease-io); }

/* -------- NOTIFICATION BANNER -------- */
/* v3.5: status meaning carried by the leading icon, not a 3px stripe.
   Hairline border on all four sides; status colour rides the icon dot. */
.pso-banner {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--pso-s-4);
  align-items: flex-start;
  padding: var(--pso-s-4) var(--pso-s-5);
  background: var(--pso-navy-raised);
  border: 1px solid var(--pso-border);
  border-radius: var(--pso-r-md);
  color: var(--pso-text-body);
}
.pso-banner strong { color: var(--pso-text); font-family: var(--pso-font-display); font-weight: var(--pso-w-semibold); }
.pso-banner__ico {
  width: 18px; height: 18px;
  border-radius: 999px;
  display: inline-grid; place-items: center;
  font-family: var(--pso-font-display); font-weight: var(--pso-w-bold);
  font-size: 11px;
  background: var(--pso-orange); color: var(--pso-on-orange);
  margin-top: 2px;
}
.pso-banner--success .pso-banner__ico { background: var(--pso-green); color: var(--pso-navy); }
.pso-banner--warning .pso-banner__ico { background: var(--pso-amber); color: var(--pso-navy); }
.pso-banner--error   .pso-banner__ico { background: var(--pso-red);   color: var(--pso-bone); }
.pso-banner--info    .pso-banner__ico { background: var(--pso-teal);  color: var(--pso-navy); }

/* Bare SVG fallback \u2014 if you skip the dot wrapper and pass an SVG
   directly, it picks up the variant colour via currentColor. */
.pso-banner > svg:first-child { flex-shrink: 0; margin-top: 2px; color: var(--pso-orange); }
.pso-banner--success > svg:first-child { color: var(--pso-green); }
.pso-banner--warning > svg:first-child { color: var(--pso-amber); }
.pso-banner--error   > svg:first-child { color: var(--pso-red); }
.pso-banner--info    > svg:first-child { color: var(--pso-teal); }

/* -------- TOOLTIP -------- */
/* v3.5: caps width and wraps. Long tips no longer punch through the
   viewport on narrow screens. */
.pso-tip { position: relative; display: inline-block; }
.pso-tip[data-tip]::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  background: var(--pso-navy-highest);
  border: 1px solid var(--pso-border);
  color: var(--pso-text);
  font-size: var(--pso-text-xs);
  line-height: 1.4;
  padding: 6px 10px; border-radius: 6px;
  max-width: 280px;
  white-space: normal;
  text-align: center;
  pointer-events: none;
  opacity: 0; transition: opacity var(--pso-dur) var(--pso-ease);
}
.pso-tip:hover::after { opacity: 1; }

/* -------- SKELETON -------- */
/* v3.5: solid raised surface, opacity pulse only. Replaces the
   horizontal gradient — system permits two gradients (header/footer),
   skeletons aren't on the list. */
@keyframes pso-pulse { 0%,100% { opacity:0.5 } 50% { opacity:1 } }
.pso-skeleton {
  background: var(--pso-navy-raised);
  border-radius: 4px;
  animation: pso-pulse 1.6s ease-in-out infinite;
}

/* -------- SPINNER -------- */
@keyframes pso-spin { to { transform: rotate(360deg); } }
.pso-spinner {
  width: 20px; height: 20px; border-radius: 999px;
  border: 2px solid var(--pso-border-strong);
  border-top-color: var(--pso-orange);
  animation: pso-spin 0.8s linear infinite;
  display: inline-block;
}

/* Pulsing dot */
@keyframes pso-dot-pulse { 0%,100% { opacity: 0.25 } 50% { opacity: 1 } }
.pso-dot-pulse { width: 10px; height: 10px; border-radius: 999px; background: var(--pso-orange); display: inline-block; animation: pso-dot-pulse 1.2s var(--pso-ease-io) infinite; }

/* Three-dot chase */
.pso-dot-chase { display: inline-flex; gap: 6px; }
.pso-dot-chase span { width: 6px; height: 6px; border-radius: 999px; background: var(--pso-orange); display: inline-block; animation: pso-dot-pulse 1s ease-in-out infinite; }
.pso-dot-chase span:nth-child(2) { animation-delay: 0.15s; }
.pso-dot-chase span:nth-child(3) { animation-delay: 0.3s; }

/* -------- AVATARS -------- */
.pso-avatar {
  width: 40px; height: 40px; border-radius: 999px;
  background: var(--pso-navy-highest);
  display: inline-grid; place-items: center;
  font-family: var(--pso-font-display); font-weight: var(--pso-w-semibold);
  font-size: 14px; color: var(--pso-text);
  border: 1px solid var(--pso-border);
}
.pso-avatar--60 { width: 60px; height: 60px; font-size: 18px; }
.pso-avatar--80 { width: 80px; height: 80px; font-size: 24px; }
.pso-avatar--100 { width: 100px; height: 100px; font-size: 30px; }
.pso-avatar-stack { display: inline-flex; }
.pso-avatar-stack .pso-avatar { margin-left: -12px; border: 2px solid var(--pso-page); }
.pso-avatar-stack .pso-avatar:first-child { margin-left: 0; }

/* -------- BREADCRUMBS -------- */
.pso-breadcrumbs { display: inline-flex; gap: 8px; align-items: center; font-family: var(--pso-font-display); font-size: var(--pso-text-sm); font-weight: var(--pso-w-medium); color: var(--pso-text-muted); }
.pso-breadcrumbs a { color: var(--pso-text-muted); transition: color var(--pso-dur) var(--pso-ease); }
.pso-breadcrumbs a:hover { color: var(--pso-orange); }
.pso-breadcrumbs .pso-sep { opacity: 0.5; }
/* Current page: bone text at full weight. Not orange — keeps orange for actions. */
.pso-breadcrumbs .is-current { color: var(--pso-text); font-weight: var(--pso-w-bold); }

/* -------- PAGINATION -------- */
.pso-pagination { display: inline-flex; gap: 6px; }
.pso-pagination button {
  width: 36px; height: 36px;
  border-radius: var(--pso-r-sm);
  background: transparent;
  border: 1px solid var(--pso-border);
  color: var(--pso-text-body);
  font-family: var(--pso-font-display); font-weight: var(--pso-w-semibold);
  font-size: var(--pso-text-sm);
  cursor: pointer;
  transition: all var(--pso-dur) var(--pso-ease);
}
.pso-pagination button:hover { border-color: var(--pso-orange); color: var(--pso-orange); }
.pso-pagination button.is-active { background: var(--pso-orange); color: var(--pso-on-orange); border-color: var(--pso-orange); }

/* -------- QUOTE BLOCK -------- */
/* v3.5: glyph in --pso-text-faint. Orange reserved for action,
   active state, or key headline word — not ornament. */
.pso-quote {
  position: relative;
  padding: var(--pso-s-8) var(--pso-s-10);
  background: var(--pso-navy-raised);
  border: 1px solid var(--pso-border);
  border-radius: var(--pso-r-lg);
}
.pso-quote__glyph {
  position: absolute;
  top: 18px; left: 24px;
  font-family: var(--pso-font-display);
  font-weight: var(--pso-w-bold);
  font-size: 72px; line-height: 1;
  color: var(--pso-text-faint);
}
.pso-quote__body {
  /* v3.6: Literata, UPRIGHT. Never italic — there is no italic
     Literata loaded, so font-style:italic would be synthesised by the
     browser as a mechanical slant. That is why the rule is absolute. */
  font-family: var(--pso-font-quote);
  font-style: normal;
  font-size: var(--pso-text-2xl);
  font-weight: var(--pso-w-regular);
  line-height: 1.35;
  color: var(--pso-text);
  letter-spacing: var(--pso-track-display);
  padding-top: var(--pso-s-4);
  text-wrap: balance;
}
.pso-quote__meta { margin-top: var(--pso-s-5); display: flex; gap: var(--pso-s-4); align-items: center; }
.pso-quote__name { color: var(--pso-text); font-family: var(--pso-font-display); font-weight: var(--pso-w-semibold); }
.pso-quote__role { color: var(--pso-text-muted); font-size: var(--pso-text-sm); }

/* -------- CORNER MARKER (signature motif — hairline crosshair) -------- */
/* CORNER MARKER (proposal 03, approved 22 July 2026, with one correction).
   Two short orange brackets at opposite corners. The signature move: it costs
   nothing, breaks no rule, and makes the system recognisable at a glance.
   Reserved for hero panels, scorecards and the assessment block, never on an
   ordinary card, because scarcity is the whole mechanism.

   THE CORRECTION: the original was a square L-bracket sitting on a rounded
   corner, which reads as a mistake rather than a mark. The bracket now carries
   the same radius as the surface it sits on, so it traces the corner instead
   of cutting across it. If a surface uses a different radius, pass it in via
   --pso-corner-r. */
.pso-corner { position: relative; --pso-corner-r: var(--pso-r-lg); }
.pso-corner::before, .pso-corner::after {
  content: ""; position: absolute; width: 22px; height: 22px;
  border-color: var(--pso-orange); border-style: solid; border-width: 0;
  pointer-events: none;
}
.pso-corner--tl::before { top: -1px; left: -1px; border-top-width: 1px; border-left-width: 1px; border-top-left-radius: var(--pso-corner-r); }
.pso-corner--tr::before { top: -1px; right: -1px; border-top-width: 1px; border-right-width: 1px; border-top-right-radius: var(--pso-corner-r); }
.pso-corner--bl::after { bottom: -1px; left: -1px; border-bottom-width: 1px; border-left-width: 1px; border-bottom-left-radius: var(--pso-corner-r); }
.pso-corner--br::after { bottom: -1px; right: -1px; border-bottom-width: 1px; border-right-width: 1px; border-bottom-right-radius: var(--pso-corner-r); }
.pso-card--hero.pso-corner { --pso-corner-r: var(--pso-r-xl); }

/* Utility grids */
.pso-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--pso-s-6); }
.pso-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--pso-s-6); }
.pso-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--pso-s-6); }
@media (max-width: 800px) {
  .pso-grid-2, .pso-grid-3, .pso-grid-4 { grid-template-columns: 1fr; }
}

/* Inline link with arrow — gap grows on hover instead of moving the
   whole arrow, which keeps the eye on the label. Used for "See it
   working", "Read the case", "Start with the assessment", etc. */
.pso-link-arrow {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--pso-font-display);
  font-size: var(--pso-text-sm);
  font-weight: var(--pso-w-medium);
  color: var(--pso-orange);
  transition: gap var(--pso-dur) var(--pso-ease);
}
.pso-link-arrow:hover { gap: 10px; }
.pso-link-arrow svg { width: 13px; height: 13px; }
