:root {
    /* =============================================
       WESTAY SRC — DESIGN TOKENS
       Single source of truth for all visual values.
       Change here = change everywhere.

       Theme: Liquid Glass on Ice — cool marble surface,
       midnight-voyage brand anchoring, SRC Crisp Formula.
       Source: SRC-Design/SD-src/DESIGN.md
       ============================================= */

    /* --- Brand Palette --- */
    --src-primary: #02243c;
    --src-primary-hover: #0d3552;
    --src-primary-tint: rgba(2, 36, 60, 0.08);
    --src-midnight: var(--src-primary);

    --src-ruby: #a3011e;
    --src-ruby-hover: #7a0016;
    --src-ruby-light: #c85368;
    --src-ruby-tint: rgba(163, 1, 30, 0.08);

    --src-crimson: #EE444F;
    /* The crimson family's TINT — background behind a negative signal
       (an expired travel document, a refused row). 0.08 is not a new
       ratio: it is the step every other signal hue in this file already
       takes for its own ground (--src-success-bg, --src-warning-bg,
       --src-info-bg, --src-ruby-tint, --src-sapphire-tint are all 0.08),
       so crimson joins the family at the family's value rather than
       inventing one. Added 2026-09-11 for the client-profile read
       surface, which is the first page to need a crimson GROUND; before
       it the only crimson consumers painted a border or a glyph and the
       one that did need a ground hardcoded rgba(238,68,79,0.1) inline
       (travel.css .rb-alert-badge--red).

       TEXT ON THIS TINT IS NOT --src-crimson. Measured 2026-09-11 the
       same way the success/warning pairs below were: #EE444F reads
       3.77:1 on white and lower over the page, so it fails the AA text
       floor exactly as --src-success and --src-warning do. There is
       deliberately no --src-crimson-text, because the readable dark red
       already exists one family up — --src-ruby (#a3011e) reads 8.14:1
       on white — and a second name for a colour this file already
       carries would be the duplication, not the fix. */
    --src-crimson-bg: rgba(238, 68, 79, 0.08);

    --src-sandstone: #b18b53;
    --src-sandstone-hover: #8b6a3a;
    --src-sandstone-light: #e8d5b0;
    --src-sandstone-tint: rgba(177, 139, 83, 0.10);
    /* The tint's LOUDER step — a sandstone ground that has to read as a
       filled shape in its own right (an avatar plate, a selected cell)
       rather than as a wash behind text. 0.22 is the ratio this file
       already uses wherever a tint family needs its firm member:
       --src-success-border, --src-warning-border and --src-info-border
       are all 0.22 over the same 0.08 grounds. Added 2026-09-11 for the
       client-profile read surface (the relationship-manager avatar).
       Text on it is still --src-sandstone-text, for the reason below. */
    --src-sandstone-tint-strong: rgba(177, 139, 83, 0.22);
    /* The family's readable member — text/glyph ON --src-sandstone-tint, never the
       signalling hue. Measured 2026-08-12 over both light surfaces the tint composites
       on (#ffffff card, #f0f4f7 page): --src-sandstone reads 2.85:1 / 2.59:1 and
       --src-sandstone-hover 4.52:1 / 4.11:1 — so the hover value PASSES on a card and
       FAILS on the page, which is why neither is the answer and why eyeballing one
       backdrop could not have found this. #7a5c2e reads 5.61:1 / 5.11:1, clearing AA
       on both with margin. The signalling hue is deliberately NOT re-tuned: it is
       correct wherever it paints a border, a dot or an icon against the page.
       Same shape as --src-success-text / --src-warning-text below. */
    --src-sandstone-text: #7a5c2e;

    --src-sapphire: #2a617f;
    --src-sapphire-hover: #1e4d66;
    --src-sapphire-light: #daeaf3;
    --src-sapphire-tint: rgba(42, 97, 127, 0.08);

    /* --- Surfaces --- */
    --src-surface-page: #f0f4f7;
    --src-surface-glass: rgba(255, 255, 255, 0.88);
    --src-surface-glass-thick: rgba(255, 255, 255, 0.94);
    /* Warm frosted grade of --src-surface-warm (#f7f6f2) — the canonical
       central-panel surface; content-area owns it in layouts.css */
    --src-surface-glass-warm: rgba(247, 246, 242, 0.92);
    --src-surface-solid: #ffffff;
    --src-surface-raised: #f8f9fa;
    --src-surface-card: #ffffff;
    --src-surface-card-raised: #f8f9fa;
    --src-surface-card-hover: rgba(2, 36, 60, 0.02);
    --src-surface-sidebar: #ffffff;
    --src-surface-topbar: rgba(255, 255, 255, 0.95);
    --src-surface-content: rgba(255, 255, 255, 0.88);
    --src-surface-input: #ffffff;
    --src-surface-input-disabled: #f0efed;
    --src-surface-overlay: rgba(2, 36, 60, 0.4);
    /* The BRIGHT wash. A dark scrim is the language of "concentrate" — it dims
       the page to push focus onto a decision. Some overlays are celebrations,
       not decisions, and reaching for dark is wrong for those (ruled 2026-07-27
       on the release announce). This is its own token rather than a hardcoded
       rgba at the one call site, because --src-surface-overlay governs every
       other modal and the pair has to be readable as a choice between two
       registers. Consequence for any consumer: a white card on a bright wash
       loses its boundary — shadows read by darkening — so it needs a hairline
       edge as well as a shadow. */
    --src-surface-overlay-bright: rgba(255, 255, 255, 0.86);
    --src-surface-warm: #f7f6f2;
    --src-surface-warm-hover: #fbfaf6;
    --src-border-warm: #e4e1d8;

    /* --- Text --- */
    --src-text-heading: #02243c;
    --src-text-primary: rgba(2, 36, 60, 0.9);
    --src-text-secondary: rgba(2, 36, 60, 0.72); /* WCAG-AA floor: >=6.1:1 on all light surfaces (was 0.6 = 4.2:1, failed) */
    --src-text-tertiary: rgba(2, 36, 60, 0.63);  /* WCAG-AA floor: >=4.6:1 on all light surfaces (was 0.45 = 2.8:1, failed) — muted look preserved, hierarchy intact */
    --src-text-disabled: rgba(2, 36, 60, 0.30);
    --src-text-on-dark: #ffffff;
    --src-text-on-dark-muted: rgba(255, 255, 255, 0.60);
    --src-text-link: #2a617f;
    --src-text-link-hover: #1e4d66;

    /* --- Borders & Dividers --- */
    --src-border-subtle: rgba(2, 36, 60, 0.06);
    --src-border: rgba(2, 36, 60, 0.06);
    --src-border-medium: rgba(2, 36, 60, 0.10);
    --src-border-strong: rgba(2, 36, 60, 0.16);
    --src-border-focus: var(--src-primary);
    --src-border-error: var(--src-ruby);
    --src-border-input: rgba(2, 36, 60, 0.12);
    --src-border-ice: rgba(255, 255, 255, 0.6);
    --src-divider: rgba(2, 36, 60, 0.08);

    /* --- Functional / Semantic Colors --- */
    --src-success: #01d1c1;
    --src-success-bg: rgba(1, 209, 193, 0.08);
    /* Text and border ON --src-success-bg. The base hue is a bright cyan and
       cannot be read on its own tint (well under AA), so the success family
       needs a darkened member the way the error family already has one
       (--src-ruby is readable, and --src-border-error points at it). These two
       are the values four module stylesheets had each written raw; the contrast
       derivation is admin-email.css's (deleted 2026-08-15; see admin.css) — #0a7368 on --src-success-bg over white
       measures 4.6:1, WCAG AA. Homed here rather than repeated per module: one
       brand value, one declaration. */
    --src-success-text: #0a7368;
    --src-success-border: rgba(1, 209, 193, 0.22);

    --src-error: #a3011e;
    --src-error-bg: rgba(163, 1, 30, 0.08);
    --src-error-border: rgba(163, 1, 30, 0.22);

    --src-warning: #f59e0b;
    --src-warning-bg: rgba(245, 158, 11, 0.08);
    /* Text and border ON --src-warning-bg — the same gap one hue over, and the
       amber is the worse of the two: #f59e0b on its own tint measures 2.02:1,
       against the cyan's 1.81:1. #78350f is the value admin-email.css had (that sheet is deleted 2026-08-15; the value lives here now)
       already derived raw (8.5:1 on this tint), homed here for the same reason
       the success pair is.
       The TEXT half of the family splits, and the asymmetry is measured, not an
       oversight: --src-error (#a3011e, 7.01:1) and --src-info (#2a617f, 5.99:1)
       read on their own tints unaided, so neither takes a -text member. Minting
       one for them would be token proliferation. Re-measure before adding to
       the -text list; do not "complete the family" by symmetry.

       The BORDER half does NOT split, and this sentence bundled the two facts
       under one measurement until 2026-08-12 — a -border member is owed by the
       component's grammar, never by a contrast floor, so no measurement was
       ever going to decide it. All four families now carry -border at α 0.22,
       because a notice body's box outline is one grammar across the whole
       family; the two that had one only had it because the 2026-08-11 contrast
       pass minted -text and -border as a pair and repointed both declarations.
       That left .alert bordering success/warning soft and error/info solid —
       one component rendering one situation two ways, split exactly along the
       previous day's contrast boundary rather than along anything a reader
       could derive. uiux vault → NOTICE-BOX-OUTLINE.

       The NEUTRAL-surface arm needs no per-site floor judgement, and that is
       what separates these two from sandstone. --src-success reads 1.93:1 on
       opaque white and 1.74:1 on --src-surface-page; --src-warning reads
       2.15:1 and 1.94:1. Both sit under 3:1 on every light surface, so a
       success/warning hue painting TEXT OR A GLYPH fails whichever floor
       applies — there is no pass-as-an-icon survivor to preserve. --src-error
       (8.14 / 7.36) and --src-info (6.74 / 6.10) pass both floors on both.
       The readable members clear everywhere: -text 5.73 / 5.18 (success),
       9.07 / 8.20 (warning).
       A BORDER in the raw hue is outside this: it is neither text nor glyph,
       and --src-*-border is the low-alpha box outline, not an accent stripe. */
    --src-warning-text: #78350f;
    --src-warning-border: rgba(245, 158, 11, 0.22);

    --src-info: #2a617f;
    --src-info-bg: rgba(42, 97, 127, 0.08);
    --src-info-border: rgba(42, 97, 127, 0.22);

    /* --- Plane identity: WHICH deployment plane the user is on ---
       Keyed off the ROLE a plane holds, never its name (planes are named,
       colours are roles — ruling 2026-08-01), so these travel with the role
       the moment deployment_colors moves it: whichever plane is `live` wears
       the green, whichever is `next` wears the blue. Two consumers only —
       the sidebar footer badge and the topbar colour-switch cup — declared
       here rather than in either stylesheet so the two can never disagree
       about where the user is.

       Green here is SEMANTIC STATE, not theme. The palette ban governs
       decoration (Martin 2026-08-05: "green and red carry universal meanings
       we did not invent"); the standing counter-example is
       --src-sidebar-presence-green marking a user online, and "a plane being
       served" is named in that same ruling. Blue is the approved
       informational hue, darkened to the readable member the way
       --src-success-text / --src-warning-text are.

       Contrast, measured on white: the raw hues carry the GLYPH (a 1.9px
       stroke, so it needs the text floor, not the 3:1 graphics one) —
       #15803d 5.02:1, #1d4ed8 8.59:1. On their own tints over white
       (#e8f2eb / #e9edfb) they read 4.99:1 and 7.63:1. Both clear WCAG AA on
       both grounds, which the sidebar footer badly needed either way: its
       rgba(2,36,60,0.16) wordmark measures 1.38:1 (ns 28106). */
    --src-plane-live: #15803d;
    --src-plane-live-bg: rgba(21, 128, 61, 0.10);
    --src-plane-next: #1d4ed8;
    --src-plane-next-bg: rgba(29, 78, 216, 0.10);

    /* --- Status Palette (8 categories) --- */
    --src-status-positive: #01d1c1;
    --src-status-active: #02243c;
    --src-status-attention: #f59e0b;
    --src-status-caution: #b18b53;
    --src-status-inform: #2a617f;
    --src-status-negative: #EE444F;
    --src-status-critical: #a3011e;
    --src-status-neutral: #9ca3af;

    /* --- Gray Scale (cool only) --- */
    --src-gray-50: #f9fafb;
    --src-gray-100: #f3f4f6;
    --src-gray-200: #e5e7eb;
    --src-gray-300: #d1d5db;
    --src-gray-400: #9ca3af;
    --src-gray-500: #6b7280;
    --src-gray-600: #4b5563;
    --src-gray-700: #374151;
    --src-gray-800: #1f2937;
    --src-gray-900: #111827;

    /* --- Typography --- */
    --src-font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --src-font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --src-font-mono: 'JetBrains Mono', 'Fira Code', monospace;

    --src-text-xs: 0.6875rem;   /* 11px */
    --src-text-sm: 0.75rem;     /* 12px */
    --src-text-base: 0.8125rem; /* 13px — app default */
    --src-text-md: 1rem;        /* 16px */
    --src-text-lg: 1.125rem;    /* 18px */
    --src-text-xl: 1.25rem;     /* 20px */
    --src-text-2xl: 1.5rem;     /* 24px */
    --src-text-3xl: 1.875rem;   /* 30px */
    --src-text-4xl: 2.25rem;    /* 36px */

    --src-leading-tight: 1.25;
    --src-leading-normal: 1.5;
    --src-leading-relaxed: 1.625;

    --src-tracking-tight: -0.01em;
    --src-tracking-normal: 0;
    --src-tracking-wide: 0.05em;
    --src-tracking-widest: 0.1em;

    --src-weight-regular: 400;
    --src-weight-medium: 500;
    --src-weight-semibold: 600;
    --src-weight-bold: 700;
    --src-weight-extrabold: 800;

    /* --- Spacing (4px base) --- */
    --src-space-1: 0.25rem;
    --src-space-2: 0.5rem;
    --src-space-3: 0.75rem;
    --src-space-4: 1rem;
    --src-space-5: 1.25rem;
    --src-space-6: 1.5rem;
    --src-space-8: 2rem;
    --src-space-10: 2.5rem;
    --src-space-12: 3rem;
    --src-space-16: 4rem;
    --src-space-20: 5rem;
    --src-space-24: 6rem;

    /* The ONE vertical rhythm between page-level elements — DESIGN.md:
       "Floating gap: 16px between every element. The air IS the design."
       Flexed content areas use it as their column gap; section/card grids
       reference it so the whole page breathes at a single interval. */
    --src-gap: var(--src-space-4);

    /* --- Border Radius (tiered by role) --- */
    --src-radius-micro: 6px;
    --src-radius-interactive: 8px;
    --src-radius-medium: 12px;
    --src-radius-secondary: 16px;
    --src-radius-structural: 24px;
    --src-radius-pill: 100px;
    --src-radius-circle: 50%;
    /* Legacy aliases */
    --src-radius-sm: 4px;
    --src-radius-md: 6px;
    --src-radius-lg: 8px;
    --src-radius-xl: 12px;
    --src-radius-2xl: 16px;
    --src-radius-3xl: 24px;
    --src-radius-round: 50%;

    /* --- Shadows (midnight-tinted only) --- */
    --src-shadow-card:
        0 24px 80px rgba(2, 36, 60, 0.08),
        inset 0 0 0 1px rgba(255, 255, 255, 0.5);
    --src-shadow-card-hover:
        0 32px 80px rgba(2, 36, 60, 0.12),
        inset 0 0 0 1px rgba(255, 255, 255, 0.5);
    --src-shadow-dropdown: 0 4px 16px rgba(2, 36, 60, 0.08), 0 1px 3px rgba(2, 36, 60, 0.06);
    --src-shadow-panel:
        0 20px 40px rgba(2, 36, 60, 0.15),
        0 8px 16px rgba(2, 36, 60, 0.10),
        0 0 1px rgba(2, 36, 60, 0.2);
    --src-shadow-modal: 0 20px 60px rgba(2, 36, 60, 0.20), 0 4px 12px rgba(2, 36, 60, 0.10);
    --src-shadow-toast: 0 8px 32px rgba(2, 36, 60, 0.12), 0 2px 4px rgba(2, 36, 60, 0.08);
    /* Simple scale */
    --src-hover-bg: rgba(2, 36, 60, 0.04);
    --src-active-bg: rgba(2, 36, 60, 0.07);

    --src-shadow-1: 0 1px 3px rgba(2, 36, 60, 0.04);
    --src-shadow-2: 0 4px 12px rgba(2, 36, 60, 0.08);
    --src-shadow-3: 0 8px 24px rgba(2, 36, 60, 0.12);
    --src-shadow-inset: inset 0 2px 4px rgba(2, 36, 60, 0.04);

    /* --- Transitions --- */
    --src-transition-state: 120ms cubic-bezier(0.4, 0, 0.2, 1);
    --src-transition-transform: 200ms cubic-bezier(0.4, 0, 0.2, 1);
    --src-transition-structural: 350ms cubic-bezier(0.16, 1, 0.3, 1);
    /* Legacy aliases */
    --src-transition-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
    --src-transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
    --src-transition-slow: 350ms cubic-bezier(0.16, 1, 0.3, 1);
    --src-transition-panel: 350ms cubic-bezier(0.16, 1, 0.3, 1);

    /* --- Z-Index Scale --- */
    --src-z-dropdown: 100;
    --src-z-sticky: 200;
    --src-z-topbar: 300;
    --src-z-sidebar: 300;
    --src-z-panel: 400;
    --src-z-modal-overlay: 500;
    --src-z-modal: 510;
    --src-z-toast: 600;

    /* --- Sidebar contextual-swap tokens (C2 design) --- */
    /* Glass over the fixed alpine backdrop (base.css body::before); the
       backdrop behind the sidebar is static, so its blur costs one paint. */
    /* Solid — sidebar carries nav text; only the content-area is glass */
    --src-surface-sidebar-shell: #ffffff;
    --src-sidebar-active-fill: rgba(163,1,30,0.06);
    --src-sidebar-tan-ghost: rgba(177,139,83,0.10);
    --src-sidebar-module-active-fill: rgba(177,139,83,0.08);
    --src-sidebar-ghost-text: rgba(2,36,60,0.22);
    --src-sidebar-ghost-icon: rgba(2,36,60,0.15);
    --src-sidebar-ghost-label: rgba(2,36,60,0.14);
    /* Receded (off-path) sidebar group. Recede = de-emphasised, not hidden.
       The set moved up from 0.10-0.16 alpha (~1.25:1, illegible) the day the
       first live page groups shipped, and stays under the active group's 0.88.

       MEASURED 2026-09-06 against --src-surface-sidebar-shell (#ffffff), which
       is what .sidebar paints: -text 0.60 = 4.37:1, MISSING the 4.5:1 AA floor
       for normal text by 0.13. -icon 0.40 = 2.45:1 and -label 0.30 = 1.91:1,
       both under the 3:1 non-text floor. This comment claimed the set "clears
       the WCAG text floor" until the measurement was run; it did not, and the
       claim is what stopped anyone running it.

       THE VALUES ARE NOT CHANGED HERE ON PURPOSE, and the reason is the
       hierarchy, not caution. .sb-row — the ON-path row these are supposed to
       recede FROM — is rgba(2,36,60,0.48) = 3.05:1 at 13px/400, itself a
       FOURTH AA miss against the same 4.5:1 floor (components.css .sb-row),
       so the receded band is ALREADY the darker of the two and the recession
       reads inverted at the current values. The residual is four values, not
       three. Raising -text to 0.63 (4.80:1) conforms and widens that
       inversion; conforming .sb-row instead moves the whole sidebar. Which way
       it resolves is an intent-vs-standard fork and it is Martin's, open since
       2026-08-16 (LeoMemoryDB ns 26640 / 28088). Correcting a false claim is
       owed immediately and separately; bundling it into the fork is how the
       correction waits. */
    --src-sidebar-receded-text: rgba(2,36,60,0.60);
    --src-sidebar-receded-icon: rgba(2,36,60,0.40);
    --src-sidebar-receded-label: rgba(2,36,60,0.30);
    --src-sidebar-receded-hover-text: rgba(2,36,60,0.88);
    --src-sidebar-receded-hover-icon: rgba(2,36,60,0.62);
    --src-sidebar-receded-hover-fill: rgba(2,36,60,0.04);
    --src-sidebar-wake-text: rgba(2,36,60,0.80);
    --src-sidebar-full-text: rgba(2,36,60,0.88);
    --src-sidebar-presence-green: #22c55e;
    --src-sidebar-dur-wake: 150ms;
    --src-sidebar-dur-reveal: 180ms;
    --src-sidebar-ease: cubic-bezier(0.4, 0, 0.2, 1);

    /* --- Layout --- */
    --src-sidebar-width: 260px;
    --src-sidebar-collapsed: 70px;
    --src-topbar-height: 70px;
    --src-floating-gap: 16px;
    --src-panel-width: 560px;
    --src-modal-width: 480px;
    --src-modal-width-lg: 640px;
    --src-modal-width-sm: 360px;
    --src-modal-height-tall: 80vh;   /* .modal--tall: a sheet that owns a tall box */
    --src-content-max-width: none; /* retired: content is fluid by default, no page-level cap (kept as a safe tombstone for any stray reference) */
    --src-grid-columns: 12;
    --src-grid-gutter: 25px;

    /* --- Screen-derived geometry ---
       Every size taken from the screen is written HERE; modules read, never re-derive. */
    --src-viewport-clear-height: calc(100vh - var(--src-topbar-height)); /* screen left below the sticky topbar */
    --src-rail-max-height: calc(100vh - var(--src-space-16)); /* a sticky in-content rail */

    /* The tallest a bounded scroller may be, by role. There is no fourth role
       and no per-module number; the -sm step is the same role at <=768px. */
    --src-scroll-cap-major: 68vh;     /* a page's primary grid or table region */
    --src-scroll-cap-major-sm: 55vh;
    --src-scroll-cap-panel: 60vh;     /* a dialog body, or a card's list */
    --src-scroll-cap-panel-sm: 70vh;
    --src-scroll-cap-minor: 40vh;     /* a composer or secondary list inside a panel */

    /* --- Field widths ---
       The three width steps a form field may cap itself at. A field that needs
       to be narrower than its row is one of these; there is no fourth step and
       no per-field number. Consumed through .form-input-w-{sm,md,lg}. */
    --src-field-w-sm: 7.5rem;   /* 120px — small integers: order, rank, count */
    --src-field-w-md: 10rem;    /* 160px — dates, times, months, short numerics */
    --src-field-w-lg: 13.75rem; /* 220px — short text and choice controls */
}
