/* SkillBridge - Design Tokens
 *
 * The static :root design-token layer, split out of main.css so public
 * booking surfaces (base_public.html, base_embed.html) can load JUST the
 * tokens without pulling the whole app stylesheet. main.css and every
 * component sheet read these var(--token) refs.
 *
 * Per-org brand tokens (--brand-primary, foregrounds, etc.) are injected
 * separately at runtime by apps/core/branding.py (ADR-124); the defaults
 * here are the fallbacks those overrides sit on top of.
 */

:root {
    /* Brand Colors (per-org customizable via context processor).
       Defaults are neutral ink — the pre-injection baseline a brand-free org
       sits on. branding.py (ADR-124) overrides these per-tenant at runtime, so
       existing orgs (e.g. a wine primary) are unaffected; only the neutral baseline
       moved off wine. */
    --brand-primary: #24292f;
    --brand-secondary: #32383e;
    --brand-accent: #0d6efd;
    --brand-highlight: #24292f;
    --event-color: #3B82F6;
    --due-date-color: #D4A017;

    /* Semantic tones, four slots each: the base color, a soft notice fill, the
       edge drawn on that fill, and a text tone that clears 4.5:1 on it
       (success 6.65:1, warning 4.79:1, error 6.6:1). The ramps are flat values,
       not tints mixed off the base — the --status-* recipe below is a 10% badge
       tint, and these are the deeper notice surfaces the components were drawn
       against, which a tint does not land on. Error's fill keeps its
       pre-existing -subtle name; its consumers predate the slot grammar.
       Success's -bg/-border/-text and error's -border/-text are ADDITIVE: no
       refs yet — the notice codemod adopts them. */
    --color-success: #00823a;
    --color-success-bg: #f4fcf7;
    --color-success-border: #97dbae;
    --color-success-text: #006831;
    --color-warning: #d39913;
    /* Hover/press step for warning-tinted controls. Same recipe as
       --color-neutral-ink-hover (88% of the base, 12% black), so the two hover
       steps darken by the same amount and no new hex enters the file. */
    --color-warning-hover: color-mix(in srgb, var(--color-warning) 88%, black);
    --color-warning-bg: #fcf6e6;
    --color-warning-border: #fccb57;
    --color-warning-text: #956309;
    --color-error: #d9002b;
    --color-error-subtle: #fcece9;
    --color-error-border: #ffa499;
    --color-error-text: #ac0020;
    --color-info: #6431da;

    /* Organizational tag palette. The fixed set of hues an org-picked label —
       booking category colors today; any future stage or event-type picker —
       may take, replacing free color wheels that let every org improvise.
       Tags organize; they carry no meaning, so the set skips purple (focus and
       info own it) and its red/green/amber are distinct hexes from the
       semantic ramps above, which a tag must never impersonate. Pickers offer
       exactly these ten and store the resolved hex; slate is the unset
       default. Blue and amber deliberately match --event-color and
       --due-date-color so tagged items sit naturally beside them on the
       calendar. */
    --tag-slate: #64748b;
    --tag-red: #cc4245;
    --tag-orange: #e0762e;
    --tag-amber: #d4a017;
    --tag-green: #33875a;
    --tag-teal: #12808a;
    --tag-blue: #3b82f6;
    --tag-navy: #35508c;
    --tag-pink: #c74f8e;
    --tag-brown: #8d6248;

    /* Inbox bubbles (ADR-181, Nick 2026-10-03): the colours a phone shows, so a
       text reads the way it did on the client's screen. Our texts are iMessage
       blue or SMS green; theirs are the light grey with dark words. Only the
       Inbox wears these; they are not the button ink or a tag. */
    --texts-imessage: #0a84ff;
    --texts-sms: #34c759;
    --texts-theirs: #e9e9eb;
    --texts-theirs-text: #1c1c1e;

    /* Chart series colours (ADR-174, amended 2026-09-28). A chart's first
       series wears --chart-1, its second --chart-2, and so on. The series
       wear the platform's own colours, as Nick chose on 2026-09-28: the ink
       first, then the green, the red and the amber, the grey, then the pale
       green, red and amber edges. Each one aliases a house token that is the
       same for every org, so no new colour is minted and org branding never
       colours a series. The purple stays the focus and info colour, which
       nothing else may wear, so it is never a series. The tag colours stay
       for tags. */
    --chart-1: var(--color-action);
    --chart-2: var(--color-success);
    --chart-3: var(--color-error);
    --chart-4: var(--color-warning);
    --chart-5: var(--color-grey-500);
    --chart-6: var(--color-success-border);
    --chart-7: var(--color-error-border);
    --chart-8: var(--color-warning-border);

    /* Tone tints (ADR-131, UI Component Service). Every soft surface the
       platform paints — the .global-badge pill, the .global-alert box, the
       .global-notice strip — takes its fill and its edge from here, so a
       palette change moves all of them together. The edge is the same hue at
       twice the strength; --color-border is the neutral page rule and reads as
       a seam under a tinted fill. Red (danger) is reserved for genuine
       failures — cancellations are neutral, not red. */
    --status-success-bg: color-mix(in srgb, var(--color-success) 10%, transparent);
    --status-success-border: color-mix(in srgb, var(--color-success) 20%, transparent);
    --status-warning-bg: color-mix(in srgb, var(--color-warning) 10%, transparent);
    --status-warning-border: color-mix(in srgb, var(--color-warning) 20%, transparent);
    --status-warning-fg: color-mix(in srgb, var(--color-warning) 45%, var(--color-grey-700));
    --status-danger-bg: color-mix(in srgb, var(--color-error) 10%, transparent);
    --status-danger-border: color-mix(in srgb, var(--color-error) 20%, transparent);
    --status-info-bg: color-mix(in srgb, var(--color-info) 10%, transparent);
    --status-info-border: color-mix(in srgb, var(--color-info) 20%, transparent);
    /* Availability — deliberately teal, NOT success green, so the "Available"
       highlight never reads as the green "Signed Up" pill. -bg is the soft fill
       for a highlighted row; the base token is the pill border/text. */
    --color-availability: #0f766e;
    --color-availability-bg: #e6f4f2;

    /* Value pill — the green numeric value badge (see components/value-pill.css).
       These are the original signup-pill colors. Defined here so an org can
       override the pill without touching the component file. -start/-end are the
       gradient top/bottom; -fg is the text; -shadow is the pill's own colored
       glow, which is derived from -end so an override recolors it too. */
    /* Money-pill gradient. White text stays — cleaner; the stops
       are deepened from the old #32d583/#16a34a so white reads a bit better
       without turning murky. White-on-gradient ~2.4:1 top, ~3.7:1 bottom. */
    --global-value-pill-start: #22b06a;
    --global-value-pill-end: #0f8a3c;
    --global-value-pill-fg: #fff;
    --global-value-pill-shadow: 0 2px 6px
        color-mix(in srgb, var(--global-value-pill-end) 25%, transparent);

    /* Focus Ring (fixed - not per-org, uses info color for accessibility) */
    --color-focus-ring: #6431da;
    --color-focus-ring-alpha: rgba(100, 49, 218, 0.25);

    --color-page-bg: #f0f0f0;
    --color-card-bg: #ffffff;

    /* What is actually behind a card inside .global-app-content. The same value as
       --color-page-bg until an org picks its own "Every page" background, which
       base/app.html republishes on .global-app-main — above both the content and the
       sticky .global-app-header that paints over it — so a rule that has to repaint
       a piece of the canvas reads THIS and follows the tenant, where
       --color-page-bg would paint every such org the default grey. Not a synonym
       for --color-page-bg: that one is also a hairline, a shimmer stop and a
       calendar cell, none of which follow a tenant's canvas. */
    --color-canvas: var(--color-page-bg);
    --color-text: #232323;
    --color-text-light: #33232c;
    --color-border: #d0d4db;
    --color-border-dark: #232323;

    /* Exact-match tokens for the owner-approved OAuth consent page
       (apps/mcp_oauth/templates/mcp_oauth/authorize.html). Each value is the
       exact literal that design shipped with, moved here so the page carries no
       raw literal. Do NOT retune — changing one moves an approved pixel. */
    --color-page-bg-soft: #f4f5f7;    /* consent page background (cool off-white) */
    --color-text-cool: #1f2937;       /* consent body text (cool slate) */
    --color-text-muted-cool: #6b7280; /* consent subtitle + scope note (cool grey) */
    --color-border-hairline: #e6e8eb; /* consent card + settings-scope-group hairline */

    /* Table / list-header band. Live on `.payments-ledger__table thead th`, a
       one-off table with no Bootstrap `.table` class, so no cell rule competes
       with it. On a Bootstrap `.table`, set this as --bs-table-bg on the thead —
       as a `background` the cells paint over it. */
    --color-header-band: #eef1f5;

    /* Warm neutral grey ramp (50 lightest → 900 darkest).
       The app today mixes ~six grey systems (Tailwind-gray, Tailwind-slate,
       Material grey, Bootstrap #6c757d, cool blue-greys, stale #0f1116) totalling
       ~1,000 literals. This is the single ramp the grey codemod collapses them to.
       Warm (R >= G > B, a faint taupe) so it sits under the wine brand instead of
       fighting it with cool blue-grey. ADDITIVE: no refs yet — pure no-op. The
       broken --color-text-light fix (separate gated PR) points it at --color-grey-600. */
    --color-grey-50: #faf9fa;
    --color-grey-100: #f3f1f2;
    --color-grey-200: #e7e3e5;
    --color-grey-300: #d4cfd1;
    --color-grey-400: #b3acaf;
    --color-grey-500: #8d8588;
    --color-grey-600: #6c6467;
    --color-grey-700: #4e4749;
    --color-grey-800: #332e30;
    --color-grey-900: #211d1f;

    /* Sidebar Variables.
       Defaults assume a dark brand. The inline <style> from branding_context
       overrides --sidebar-text / --sidebar-text-muted / --sidebar-hover-bg
       per-tenant via brand_tokens so light-brand tenants get dark text. */
    --sidebar-width: 256px;
    /* The collapsed icon rail. Anything positioned against the sidebar has to
       clear BOTH widths, and there was no name for this one — so each of those
       places carried its own 56, and the global toast cleared the rail at the
       wrong width for as long as it had been doing it. */
    --sidebar-collapsed-width: 56px;
    --sidebar-bg: var(--brand-primary);
    --sidebar-text: #ffffff;
    --sidebar-text-muted: rgba(255, 255, 255, 0.7);
    --sidebar-border: rgba(255, 255, 255, 0.1);
    --sidebar-hover-bg: rgba(255, 255, 255, 0.1);
    --sidebar-active-bg: var(--brand-secondary);
    /* RGB triple of --sidebar-text — lets sidebar overlays build any opacity
       (rgba(var(--sidebar-fg-rgb), 0.08)) and flip cleanly between white
       and dark foregrounds based on brand luminance. */
    --sidebar-fg-rgb: 255, 255, 255;

    /* Brand foreground tokens.
       Defaults match the historical hardcoded #fff. The inline <style> from
       branding_context overrides per-tenant via brand_tokens, computing the
       correct contrast color for any brand including white/monochrome. */
    --brand-primary-fg: #ffffff;
    --brand-secondary-fg: #ffffff;
    --brand-highlight-fg: #ffffff;

    /* Brand-color text safe against fixed surfaces.
       Default to brand-primary itself (correct for dark brands).
       Overridden per-tenant when brand-primary fails 4.5:1 contrast against
       the page or card surface (e.g., white-primary tenants). */
    --brand-primary-on-page-bg: var(--brand-primary);
    --brand-primary-on-card-bg: var(--brand-primary);

    /* Hover/active variant of primary that is always visibly different.
       Default falls back to brand-secondary (the historical hover bg —
       was named "accent" before the 2026-05-04 secondary/accent split). */
    --brand-primary-hover: var(--brand-secondary);

    /* Brand-primary alpha overlays (replaces hardcoded rgba(70, 6, 43, ...)
       lurkers that previously baked one tenant's exact burgundy into shared CSS).
       Default is the neutral-ink primary (rgb 36 41 47 == #24292f); branding.py
       overrides per-tenant, so a tenant's own overlays are unaffected. */
    --brand-primary-alpha-05: rgba(36, 41, 47, 0.05);
    --brand-primary-alpha-10: rgba(36, 41, 47, 0.1);

    /* Header — SINGLE SOURCE OF TRUTH for the top-chrome band height. Everything
       that lines up with the header's bottom edge keys off THIS token and nothing
       else: the inverted corner (::before top:100%), the sidebar hairline
       (.global-sidebar::after top), .global-sidebar-brand (min-height), and — one hop away, via
       --drawer-top below — every content-area overlay/drawer. Do NOT hardcode a pixel height for
       any of those, and do NOT leave a stale fallback — a `var(--header-height)`
       with the wrong N is how this drifted before.

       The value is DERIVED, not magic: header controls are 28px (--filter-ctrl-h)
       and we want a 6px gutter top and bottom → 28 + 6 + 6 = 40px. Keep this in
       sync with .global-app-header's vertical padding (0.375rem = 6px) and --filter-ctrl-h;
       if you change the control height, change this and the padding together so the
       band keeps hugging its contents. (Previously 44px, sized for 32px controls
       that no longer exist — that left an 8px dead gutter and read as "too tall".) */
    --header-height: 40px;
    /* Compact-shell (<1024px) header height: a thumb needs 44px controls and
       the bar keeps 6px above and below them. The swap happens on :root, in
       the block right after this one, because --header-bottom and
       --drawer-top resolve here — a height on .global-app-header is inert. */
    --header-height-touch: 56px;
    /* The phone's bottom bar. 0px everywhere but a phone: every rule that
       lifts something by the bar (toasts, sheets, the fill lock, the portal
       frame) subtracts it unconditionally and pays nothing off the phone.
       Set on :root under the phone query below. */
    --bottom-bar-h: 0px;
    --header-bg: var(--sidebar-bg);
    /* Bottom-sheet furniture (drawer.css / main.css phone blocks): the drag
       handle pill and the dim wash over the page above a phone menu. */
    --sheet-handle-w: 36px;
    --sheet-handle-h: 4px;
    --color-scrim: rgba(17, 24, 39, 0.4);
    /* Swipe strip (main.css phone block, .global-swipe-strip): the width of one
       card in a sideways row of cards. Under half the row, so the next card
       always peeks in from the right — the peek IS the "swipe me" cue, no
       arrows or dots. Capped so a wide phone in landscape still shows three. */
    --swipe-card-w: min(44%, 12rem);
    /* 288px — room for about two quick filter chips side by side */
    --reports-chip-strip-w: 18rem;

    /* Drawer/overlay top edge — the ONE place the "start below the header" offset
       is computed. Every content-area drawer, scrim, and offcanvas keys off this
       instead of re-deriving `safe-top + header-height` (and drifting on the
       fallback). Adopted by drawer.css; a css-standards test fails the build if a
       drawer selector hardcodes a `--header-height, N` fallback again.

       --header-bottom is that same edge on its own — where the header ends and
       sticky content below it begins. The notice band pins here
       (components/notice-strip.css); --drawer-top then adds the band's height so
       everything deeper clears the band too. Both read this one place instead of
       re-deriving `safe-top + header-height` inline (the documented drift vector).

       --notice-height is the live height of the pinned notice band (the
       profile-photo / operator strips in .global-notice-slot), measured by the
       shell in base/app.html and 0px whenever the slot is empty. Folding it in
       here means every content-area drawer, the settings sub-nav, and the scrim
       all start below a pinned banner too — with the same header spacing they
       already use — and no per-consumer rule has to know the banner exists. */
    --header-bottom: calc(var(--safe-top) + var(--header-height));
    --notice-height: 0px;
    --drawer-top: calc(var(--header-bottom) + var(--notice-height));

    /* Header controls — FIXED, not per-org. The header shares the sidebar
       (brand) surface, so to keep every tenant's top bar reading identically
       regardless of brand, controls render EXACTLY like the sidebar org-switcher
       toggle (.global-sidebar-org-toggle): a flat translucent WHITE veil over the dark
       brand bar (white-at-opacity, so the wine shows through warm and muted),
       white text, hairline white border, brightening on hover. Built from
       white-on-dark opacity — NOT brand tints — so every tenant's bar reads
       identically and stays legible on any brand.

       Values mirror the org toggle: fill rgba(white,.10) [toggle uses .10],
       border rgba(white,.15), hover fill .20 / hover border .30. CRITICAL: do
       NOT add a backdrop-filter saturate() here — the chips are ~90% transparent,
       so saturate() boosts the wine seen THROUGH them into a vivid magenta/blue
       cast (measured: fill went (88,31,64) warm-mauve → (114,26,75) vivid-pink,
       which read as "super blue, not white"). A flat veil with no backdrop-filter
       lands exactly on the org toggle's (88,31,64). Was solid white #fff / dark
       #232323 text; briefly had a blur+saturate "liquid glass" material (removed
       — it caused the cast and its blur was inert over the opaque bar anyway). */
    --header-control-bg: rgba(255, 255, 255, 0.10);
    --header-control-fg: #ffffff;
    --header-control-border: rgba(255, 255, 255, 0.15);
    --header-control-hover-bg: rgba(255, 255, 255, 0.20);
    --header-control-hover-fg: #ffffff;
    --header-control-focus-ring: rgba(255, 255, 255, 0.30);

    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);

    /* The platform's own body face, named so a surface that must read as
       SkillBridge rather than as the tenant can point back at it. base.html
       overrides --font-family per-org from Organization.body_font; this holds
       the pre-injection stack, and branding.py never writes it, so it stays
       the platform's whatever the tenant sets. Same job --color-neutral-ink
       does for fill: the fixed value a platform surface reaches for. An alias
       is the only way to keep it — a var() resolves at USE time, so
       --platform-font-family: var(--font-family) would just follow the org
       override rather than capture the value underneath it. */
    --platform-font-family: 'Poppins', 'Avenir', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-family: var(--platform-font-family);
    --font-family-heading: 'Poppins', 'Avenir', sans-serif;
    --font-family-subheading: var(--font-family-heading);
    --font-size-base: 16px;
    /* Type scale — the small step set the app sizes text from. Values match the
       ad-hoc rem sizes already in use so adopting a token never changes the render. */
    --font-size-2xs: 0.6875rem; /* 11px — micro chips, meta lines */
    --font-size-xs: 0.75rem;    /* 12px — small labels, counts */
    --font-size-sm: 0.8125rem;  /* 13px — dense body / secondary text */
    --font-size-md: 0.875rem;   /* 14px — standard body */
    --font-size-lg: 0.9375rem;  /* 15px — emphasized body / lead lines */
    --font-size-xl: 1.25rem;    /* 20px — large glyphs / avatars */
    /* Heading scale — fixed sizes pinned on h1-h6 in main.css so Bootstrap's
       reboot defaults (2.5rem h1, viewport-scaled) stop reaching the render.
       h5/h6 read the body ladder above rather than owning a rung. display-* is
       for hero and public surfaces only, never an app page heading. */
    --font-size-h1: 1.75rem;        /* 28px — page headings */
    --font-size-h2: 1.5rem;         /* 24px */
    --font-size-h3: 1.25rem;        /* 20px */
    --font-size-h4: 1.125rem;       /* 18px */
    --font-size-display: 2rem;      /* 32px — hero / public surfaces */
    --font-size-display-lg: 2.5rem; /* 40px — hero / public surfaces */
    /* Structural leading. Dense data surfaces (the kanban frame, the agenda
       sheet) pin this at their roots so their row-geometry proofs hold; reading
       surfaces take the roomier --line-height-body below. */
    --line-height: 1.5;
    --font-weight-heading: 600;
    --font-style-heading: normal;
    --text-transform-heading: none;
    --letter-spacing-heading: normal;
    --line-height-heading: 1.2;
    --font-weight-subheading: var(--font-weight-heading);
    --font-style-subheading: var(--font-style-heading);
    --text-transform-subheading: var(--text-transform-heading);
    --letter-spacing-subheading: var(--letter-spacing-heading);
    --line-height-subheading: 1.3;
    --font-weight-body: 400;
    --font-style-body: normal;
    --text-transform-body: none;
    --letter-spacing-body: normal;
    --line-height-body: 1.65;
    --font-weight-accent: 400;
    --font-style-accent: normal;
    --text-transform-accent: none;
    --letter-spacing-accent: normal;
    --line-height-accent: 1.4;

    /* Utility emphasis weight (semibold). NOT a type role — it's the weight the
       app already reaches for ~200 times as a raw `font-weight: 600` for labels,
       active states, and table headers. The font-weight codemod maps those
       literals here. Distinct from --font-weight-heading — the two coincide at
       600 today but must stay free to move independently, and heading weight is
       org-overridable where this is not. */
    --font-weight-medium: 600;

    /* The one weight every button wears. FIXED at 400 and deliberately NOT part
       of the per-tenant brand-config injection in base.html — real `.btn` reads
       Bootstrap's own 400 default no matter the org, so a button-shaped custom
       control must resolve to that same fixed 400, not to the tenant-overridable
       --font-weight-body. Real buttons read this via --bs-btn-font-weight; the
       bespoke button-like controls read it directly. Never inject this. */
    --font-weight-button: 400;

    --spacing-2xs: 0.125rem; /* 2px  — hairline nudges */
    --spacing-xs: 0.25rem;   /* 4px  */
    --spacing-xsm: 0.375rem; /* 6px  — between xs and sm; chip padding and tight
                                control gaps sit here across the app. */
    --spacing-sm: 0.5rem;    /* 8px  */
    --spacing-smd: 0.75rem;  /* 12px — between sm and md; the app's most common
                                missing step (gaps/padding sit at 12 constantly).
                                Named "smd" (not the brief's "2xs") because this
                                scale runs xs<sm<md<lg<xl and a 12px "2xs" would be
                                3x larger than xs — backwards. Rename in review if
                                preferred; additive, so no refs depend on the name. */
    --spacing-md: 1rem;      /* 16px */
    --spacing-mdlg: 1.25rem;  /* 20px — between md and lg, same bridge pattern as
                                smd; card gutters and section padding sit here
                                (payment dashboard, template builder). */
    --spacing-lg: 1.5rem;    /* 24px */
    --spacing-xl: 2rem;      /* 32px */
    --spacing-2xl: 2.5rem;   /* 40px — above xl; section gaps / hero padding. */

    /* The one gap between the app chrome (header, sidebar) and page content,
       and everything aligned to it: content padding, the docked drawer's top
       edge, the pinned header's sticky offset and canvas band. 16px matches
       HubSpot's tight chrome-to-content gutter (Nick, 09-03). Change it here
       and every coupled surface moves together. */
    --page-gutter: var(--spacing-md);
    /* The gutter pulled back out: a full-bleed surface (a phone swipe strip)
       cancels the page padding with this, then pads itself with the gutter so
       its first item still lines up with the text above it. */
    --page-gutter-bleed: calc(-1 * var(--page-gutter));

    /* Border radius.
       Ladder matched to HubSpot's (Nick 09-02): 4px / 6px small components,
       16px cards and floating containers, pill for buttons and search fields.
       --radius-card is the one corner radius for content cards, panels, and
       tiles; migrate raw card radii to it. --radius-pill is the fully-rounded
       value (999px renders identically to any large value at control heights).
       Buttons read --radius-button, never a rung directly, so the whole
       product's button shape flips in one line. */
    --radius-sm: 0.25rem;    /* 4px — controls (inputs, selects) */
    --radius-button: var(--radius-pill);  /* pill buttons (Nick 09-02, matching HubSpot's own button token) */
    --radius-card: 1rem;     /* 16px — content cards (HubSpot card radius)  */
    --radius-md: 6px;        /* small components — menu items, tags, chips  */
    --radius-lg: 1rem;       /* 16px — floating panels and dropdowns        */
    --radius-xl: 1rem;       /* 16px — hero/modal shells                    */
    --radius-pill: 999px;    /* fully rounded — switches, badges, pills     */
    --radius-none: 0;        /* squared — overrides a rounded base so adjacent cells join into one stripe */
    --radius-card-lg: 14px;  /* exact-match for the approved OAuth consent card */

    --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08);
    --shadow-md: 0 4px 6px rgba(15, 23, 42, 0.10);
    --shadow-lg: 0 10px 15px rgba(15, 23, 42, 0.10);
    --shadow-modal: 0 12px 32px rgba(15, 23, 42, 0.18); /* overlay elevation — modals, dialogs, drawers */
    /* Guided-tour spotlight: the scrim is the spread of the spotlit element's
       own shadow, so the hole in it is the element's shape — the 100vmax spread
       covers any viewport from any anchor position. The hairline second layer
       is the card colour, which lifts the anchor off the scrim. */
    --shadow-tour-spotlight: 0 0 0 100vmax rgba(15, 23, 42, 0.45), 0 0 0 2px var(--color-card-bg);
    /* The popup a select hangs off its control. Two casts of one geometry: the
       menu normally drops below the control, but flips above it near a fold
       (.global-cs-menu--up), and the light has to keep coming from the same place or
       the flipped menu reads as sitting under the page. Value is the one
       .global-cs-menu already shipped, lifted here verbatim — pure black rather than
       the ladder's slate, deliberately unchanged so nothing re-renders. */
    --shadow-menu: 0 10px 25px rgba(0, 0, 0, 0.1);
    --shadow-menu-up: 0 -10px 25px rgba(0, 0, 0, 0.1);
    /* Hero-avatar halo — a soft spread ring, not a drop shadow, so it sits
       beside the elevation ladder rather than in it. Avatars are neutral
       (people are not the org's marketing surface), so the ring is a fixed
       ink wash on every tenant. Pair it with --shadow-sm at the call site;
       that pairing is exactly what the retired .worker-hero-avatar drew. */
    --shadow-avatar-ring: 0 0 0 5px color-mix(in srgb, var(--color-action) 14%, transparent);

    /* Focus ring — the 3px spread halo a control draws on :focus-visible, over
       the translucent --color-focus-ring-alpha. Lives here beside the avatar
       ring so the ring geometry stays out of component files (ADR-125); a
       component references the token instead of re-typing the raw 0 0 0 3px. */
    --shadow-focus-ring: 0 0 0 3px var(--color-focus-ring-alpha);

    /* The same ring drawn tight and solid, for a small control that sits inside
       a row and would have its 3px halo clipped by the row's own edge — the
       quick channel buttons on an alert, the permission row's segmented
       control. Two rungs, not a free choice: 3px translucent for a field, 2px
       solid for a control in a row. */
    --shadow-focus-ring-tight: 0 0 0 2px var(--color-focus-ring);

    /* Row hover/selected ring — a hairline inset ring in the same ink wash as
       the avatar halo, so a list row lifts as one card on hover (14%) and holds
       a stronger ring while its drawer is open (22%). Inset, so it rings the
       row without nudging layout the way a real border would. */
    --shadow-inset-ring: inset 0 0 0 1px color-mix(in srgb, var(--color-action) 14%, transparent);
    --shadow-inset-ring-strong: inset 0 0 0 1px color-mix(in srgb, var(--color-action) 22%, transparent);

    /* The same hairline in neutral ink — the well an editable grid cell draws.
       Controls here are greyscale, never the org's fill (Nick, 2026-09-01). */
    --shadow-inset-ring-neutral: inset 0 0 0 1px var(--color-border);

    /* Exact-match shadows for the owner-approved OAuth consent page
       (apps/mcp_oauth). Frozen to that design's approved pixels — do not retune. */
    --shadow-card-soft: 0 1px 3px rgba(16, 24, 40, 0.06), 0 8px 24px rgba(16, 24, 40, 0.05);
    --shadow-focus-ring-soft: 0 0 0 0.2rem rgba(99, 102, 241, 0.15);

    /* The stream viewer's locked key (a modifier tapped twice to stay down):
       a 2px ring in the viewer's own light ink (#f2f2f2) at 45%, drawn around
       the filled key so locked reads apart from on-for-one-press. The viewer
       paints on black with its own dark palette, so none of the app rings fit. */
    --shadow-stream-key-locked: 0 0 0 2px rgba(242, 242, 242, 0.45);

    /* The stream viewer's touch ring: a 2px edge in the viewer's dark ink at
       60% around the ring's light line, so the ring reads on light and dark
       photos alike. */
    --shadow-stream-touch-ring: 0 0 0 2px rgba(20, 20, 30, 0.6);

    /* ── Milestone / stage status ─────────────────────────────────────────
       ONE status grammar for pipelines and milestones, used at every zoom
       level: the check in the opportunity-detail list, the dot on a card, the
       dot on a kanban column, the badge in the builder. Before this, "done"
       green had five different hex values across those surfaces and "active"
       had four; every surface now derives its status color from here, so a
       theme change flows everywhere and nothing drifts. Color always means
       the same thing; components pair it with a shape (fill / ring / dot /
       dash) so status survives at small sizes and for colorblind viewers.
       Active is the fixed action ink, not the tenant's brand: stage progress
       is working state, and it reads identically on every org. */
    --ms-pending: var(--color-border);        /* not started — grey ring       */
    --ms-active: var(--color-action);         /* current stage / in progress    */
    --ms-active-soft: var(--ink-alpha-05);
    --ms-active-ring: var(--ink-alpha-15);
    --ms-done: var(--color-success);          /* completed                      */
    --ms-done-soft: color-mix(in srgb, var(--color-success) 12%, var(--color-card-bg));
    --ms-skipped: var(--color-grey-500);      /* optional, passed over          */
    --ms-skipped-soft: var(--color-grey-200);
    --ms-blocked: var(--color-warning);       /* waiting on a dependency        */
    --ms-blocked-soft: color-mix(in srgb, var(--color-warning) 14%, var(--color-card-bg));

    /* Black-ink alpha overlays. The rgba(0,0,0,.05/.1/.15/.2) ladder is hand-typed
       dozens of times for hairlines, scrims, and pressed states (self-flagged as a
       gap in assignments-groups/global-split-button). These are the neutral counterpart to the
       --brand-primary-alpha-* set above. ADDITIVE: the shadow/overlay codemod maps
       the literals here. NOT the same material as --color-action-tint below:
       these are translucent black, so they darken whatever they sit on and are
       right for shadows, scrims, and hairlines over unknown ground. A hover or
       selected FILL takes the opaque tint trio instead — folding the two
       vocabularies together would change paint. */
    --ink-alpha-05: rgba(0, 0, 0, 0.05);
    --ink-alpha-10: rgba(0, 0, 0, 0.10);
    --ink-alpha-15: rgba(0, 0, 0, 0.15);
    --ink-alpha-20: rgba(0, 0, 0, 0.20);

    /* Neutral ink surface — a fixed dark bar that carries the SAME weight for
       every tenant, so it holds ZERO brand or status color. Used by the
       platform-standard toast; reusable for any dark neutral overlay that must
       read identically across brands. Primary text is white, secondary text is
       the muted grey below, and a faint white hairline separates actions. */
    --color-neutral-ink: #24292f;             /* dark neutral surface           */
    --color-neutral-ink-fg: #ffffff;          /* primary text on the ink surface */
    --color-neutral-ink-muted-fg: #9aa4af;    /* secondary text on the ink surface */
    --neutral-ink-border: rgba(255, 255, 255, 0.15);
    /* Interaction states for the neutral-ink primary CTA (.global-btn-ink) on the public
       auth / invite / error surfaces. Derived from the ink surface so the set
       moves together and introduces no new hex: a darker press ink and a
       translucent ink focus ring. color-mix means these only apply on engines
       that support it — main.css references them behind an @supports gate and
       falls back to the flat ink token, so pre-2023 engines degrade legibly. */
    --color-neutral-ink-hover: color-mix(in srgb, var(--color-neutral-ink) 88%, black);
    --neutral-ink-focus-ring: color-mix(in srgb, var(--color-neutral-ink) 40%, transparent);

    /* Action ink — the interaction ramp for quiet controls (checkboxes, radios,
       switches, ghost/outline buttons, icon buttons). Fixed neutral for every
       tenant: brand color belongs to chrome and the filled CTA, purple to focus
       and info, so this ramp is the ONLY color a quiet control may take. It
       LIGHTENS on hover/press — a dark ink control reads "released", not
       "pressed harder" — unlike the -hover above, which darkens a CTA fill.
       The tint trio is the warm-grey wash behind hovered icon buttons and
       selected rows, warm to match the grey ramp's undertone. */
    --color-action: var(--color-neutral-ink);
    /* The same two colors as bare `r, g, b` triples, for the Bootstrap
       utilities that build their own opacity out of one (`rgba(var(--bs-
       primary-rgb), …)`) — a hex cannot be poured into that. Pinned to their
       hexes by test_css_standards: a triple that drifts from its hex is a
       color nobody chose. */
    --color-action-rgb: 36, 41, 47;
    --color-action-hover-rgb: 61, 68, 76;
    --color-action-hover: #3d444c;
    --color-action-pressed: #565e66;
    --color-action-disabled-bg: #f5f5f5;
    --color-action-disabled-fg: #8a8a8a;
    /* The tint trio is OPAQUE warm grey, not translucent black: it paints a
       flat wash that matches the grey ramp's undertone on any surface. The
       --ink-alpha-* ladder above is the translucent counterpart for shadows and
       scrims; the two never substitute for each other. */
    --color-action-tint: #f1efec;
    --color-action-tint-pressed: #e7e4df;
    --color-action-selected: #d9d5cd;

    /* The stream viewer's two inks (stream.css). The viewer paints on black
       with its own palette, so the app's neutral inks, made for a white page
       or the dark toast bar, do not fit. The light ink is the text on the
       canvas, its bars and its prompts; the inverse is the dark text on a key
       filled with the light ink, the one place the viewer puts text on light. */
    --color-stream-ink: #f2f2f2;
    --color-stream-ink-inverse: #14141e;

    /* Lightroom's four colour labels, as the iPad keys mockup draws them: the
       dots on the stream's label keys (stream.css), and nowhere else. */
    --color-stream-label-red: #f0564f;
    --color-stream-label-yellow: #f2c230;
    --color-stream-label-green: #3fbf6b;
    --color-stream-label-blue: #4a8ff7;

    --transition-fast: 0.15s ease;
    --transition-normal: 0.2s ease;
    --transition-slow: 0.3s ease;

    /* Progress bar (.global-progress, components/progress.css): the one
       track-and-fill vocabulary for a bar that says how far. The fill is
       the fixed action ink (brand stays on chrome and the filled CTA — the
       button under a bar); done reads --color-success at the bar. Scripts
       that wait for a fill to land read --progress-fill-ms rather than
       typing the number twice. */
    --progress-h: 0.5rem;
    --progress-track: var(--color-grey-200);
    --progress-fill: var(--color-action);
    --progress-fill-ms: 900ms;

    --form-group-gap: 1rem;
    --color-text-muted: #9ca3af;
    --color-text-secondary: #64748b;
    /* A third party's own mark, kept as they draw it: the Google blue on
       the account row's icon. Not ours to theme. */
    --color-brand-google: #4285f4;
    --color-surface-subtle: #f8f9fa;
    --color-surface-hover: #e9ecef;
    /* The dark counterpart to the two surfaces above — the fill for inverted
       chrome (dark bars, inverted panels, ink-on-dark overlays). NOT a new
       color: it points at --color-neutral-ink (#24292f), the fixed brand-free
       dark surface the platform toast already stands on, so inverted chrome and
       the toast can never drift apart. Named into the --color-surface-* family
       because that is where a developer looks for "what fill do I use". */
    --color-surface-inverse: var(--color-neutral-ink);
    --color-border-subtle: var(--color-surface-hover); /* same grey as surface-hover, for resting borders */

    /* Control-height scale — the ONE height vocabulary for controls (ADR-125).
       Controls today scatter across 28/32/34/36/40/44/48/52px with fragmented
       half-tokens (--filter-ctrl-h in filter-bar.css = 36px). --control-h-base
       (36px) is the standard row and lines up with global-filter-bar's existing
       --filter-ctrl-h so the codemod can unify them. --control-h-touch (44px)
       is the WCAG 2.5.5 tap target: the minimum height touch-first inputs
       (phone, file upload) rest on, and the floor every rung rises to on
       coarse pointers, enforced below the :root. */
    --control-h-sm: 32px;    /* compact controls (dense toolbars)        */
    --control-h-base: 36px;  /* standard control row (== --filter-ctrl-h) */
    --control-h-touch: 44px; /* touch target / minimum input height       */

    /* Z-index ladder. The app used ~24 ad-hoc z-index values (up to 1100, plus
       9999/10000 outliers) with no shared scale, so overlays fought unpredictably.
       This ladder is Bootstrap-5-compatible (its dropdown=1000, modal=1055,
       tooltip=1080 sit inside these bands) and is what the overlay codemod maps the
       literals onto. The 1070 / 1080 / 10000 one-offs are now wired to rungs; each
       kept its EXACT previous value, so no stacking order moved. */
    /* The bottom of the ladder, and the only rung that is not an overlay: an
       element lifted above a SIBLING inside its own stacking context, not above
       the page. The overflow-scroll treatment needs it — the chevron slivers are
       absolutely positioned over the list they scroll, and the list's own edge
       mask would otherwise paint across them. Deliberately not folded into the
       10xx band: a local raise must stay under every real overlay, so a chevron
       never outranks a dropdown that opens over it. */
    --z-raised: 2;            /* raised above a sibling, below every overlay */
    --z-under-sticky: 1019;   /* sticky, one under a pinned header card     */
    --z-sticky: 1020;        /* sticky table headers / toolbars            */
    --z-dropdown: 1030;       /* custom select & menu popups                */
    --z-scrim: 1045;          /* drawer / offcanvas backdrop                */
    --z-modal-backdrop: 1050; /* centered-modal backdrop                    */
    --z-modal: 1060;          /* modal & drawer panels (above their scrim)  */
    --z-popover: 1070;        /* autocomplete / popovers above modals       */
    /* The rung the 1080 literals land on. There was no rung between popover
       (1070) and toast (1090), and four components independently picked 1080
       for a panel that must clear the popover layer: the impersonate modal and
       assignments-day-preview panels (each stacked over their OWN 1070 backdrop), the
       referral-breakdown modal, and the pipelines move-confirm popover. Named
       by HEIGHT, not by component, so it stays true for all four — and it is
       still Bootstrap-5-compatible (BS tooltip is also 1080). Added rather than
       squeezing those panels down onto 1070, which would have flattened them
       into their own backdrops. */
    --z-overlay-top: 1080;    /* panel raised above the popover layer       */
    --z-toast: 1090;          /* toasts — always on top                     */
    /* Body-portaled third-party overlays. Google's Maps JS appends the Places
       .pac-container to <body>, outside every app stacking context, so it has
       to clear the whole ladder including toasts; both copies of that rule
       (components/places-autocomplete.css, features/booking/address-step.css)
       already hand-typed 10000. Kept at its existing 10000 rather than folded
       into the 10xx band so its stacking is unchanged. The .global-skip-link's 9999 in
       main.css is deliberately left alone — it is the a11y escape hatch and
       sits just under this by design; retokenizing it would tie the two. */
    --z-portal: 10000;        /* third-party overlays appended to <body>    */
    /* The stream viewer's prompts (still there? / leave this session?). The
       viewer stacks its own layers from 5 to 40 in stream.css: input ghost,
       loading overlay, controls and exit bar, notice, reconnect pill. A prompt
       sits on top of all of them, and still under the app's 10xx overlays, so
       it keeps the 50 it always had instead of borrowing --z-modal. */
    --z-stream-prompt: 50;    /* the viewer's prompts, above its own layers */
    /* The ring under a resting finger or Pencil: above the viewer's input
       layer (5) and loading cover (10), under its bars and keys (20). */
    --z-stream-touch-ring: 15;
}

/* Compact shell: the same 1023px line main.css draws for the off-canvas
   sidebar. The header grows to the touch height HERE, on :root, so every
   value derived from it (--header-bottom, --drawer-top) follows. */
@media (max-width: 1023px) {
    :root {
        --header-height: var(--header-height-touch);
    }
}

/* Phone: the app bar leaves the top of the screen and becomes the bottom bar
   (main.css phone block re-pins .global-app-header). --header-height reads 0
   HERE so --header-bottom and --drawer-top, "the top of the screen once the
   header is paid", stay honest: nothing sits under a header that is not there.
   --bottom-bar-h is the bar's seat: the touch bar plus the home indicator. */
@media (max-width: 767.98px) {
    :root {
        --header-height: 0px;
        --bottom-bar-h: calc(var(--header-height-touch) + var(--safe-bottom));
    }
}

/* Touch floor: on coarse pointers every control grows to a 44px tap target
   (WCAG 2.5.5). ADDITIVE — a control only grows if its own rule is sized off
   --control-h-*. The Scheduling meeting controls are wired up; the rest of the
   app still waits on the control-height codemod. */
@media (pointer: coarse) {
    :root {
        --control-h-sm: 44px;
        --control-h-base: 44px;
    }
}

/* High-contrast support. Lives here, not main.css, so public pages
   (base_public / base_embed / standalone errors) get it too — they load
   tokens.css but not the app's main.css. */
@media (prefers-contrast: more) {
    :root {
        --color-border: var(--color-border-dark);
        --shadow-sm: none;
        --shadow-md: none;
        --shadow-lg: none;
    }

    .btn {
        border-width: 2px;
    }

    a:focus,
    button:focus,
    input:focus,
    select:focus,
    textarea:focus {
        outline-width: 3px;
    }
}
