/*
 * @bsuite/theme — Tailwind v4 @theme bridge
 * Version 0.7.0
 *
 * Usage (every D2C app; braden imports braden-css instead of css):
 *
 *   @import 'tailwindcss';
 *   @import '@bsuite/theme/css';           ← defines --role-* (vars.css)
 *   @import '@bsuite/theme/preset-v4.css'; ← this file, maps them to utilities
 *
 * ─────────────────────────────────────────────────────────────────────────
 * WHY `@theme inline` AND NOT `@theme`
 *
 * Until 0.7.0 this file used a plain `@theme` block containing hardcoded
 * oklch literals — a full second copy of every value in vars.css. Two
 * consequences, both real bugs:
 *
 *   1. DARK MODE DID NOT REACH THESE UTILITIES. Plain `@theme` emits
 *      `--color-card: <literal>` at :root and makes `bg-card` resolve to
 *      `var(--color-card)`. vars.css rebinds `--role-bg-panel` under
 *      `.dark`, but nothing rebound `--color-card`, so `bg-card` stayed on
 *      its light-mode literal in dark mode.
 *   2. IT DRIFTED. The duplicate copy still said `--color-card: oklch(1 0
 *      0.5)` (pure white) and `--color-destructive: <purple>` after the
 *      token source had moved on from both.
 *
 * `@theme inline` emits the `var()` reference into the utility itself, so
 * `bg-card` resolves through `--role-bg-panel` and follows every `.dark`
 * and BrandingProvider rebinding for free. vars.css is the ONLY source of
 * truth for values; this file contains no colour literals at all.
 *
 * Adding a colour literal to this file re-creates both bugs. Don't.
 * ─────────────────────────────────────────────────────────────────────────
 *
 * Token naming: `--color-{name}` → Tailwind generates `bg-{name}`,
 * `text-{name}`, `border-{name}` etc. automatically.
 */

@theme inline {
  /* ============================================================
     LAYER 1 — Palette. Exposed for decorative use only.
     Bind components to the role tokens below, never to these:
     white-labelling mutates roles, so a palette-bound component
     silently ignores tenant branding.
     ============================================================ */
  --color-neon-electric-blue:     var(--neon-electric-blue);
  --color-neon-electric-cyan:     var(--neon-electric-cyan);
  --color-neon-electric-indigo:   var(--neon-electric-indigo);
  --color-neon-electric-purple:   var(--neon-electric-purple);
  --color-neon-electric-magenta:  var(--neon-electric-magenta);
  --color-neon-electric-pink:     var(--neon-electric-pink);
  --color-neon-electric-coral:    var(--neon-electric-coral);
  --color-neon-electric-orange:   var(--neon-electric-orange);
  --color-neon-electric-yellow:   var(--neon-electric-yellow);
  --color-neon-electric-green:    var(--neon-electric-green);
  --color-neon-electric-lavender: var(--neon-electric-lavender);
  --color-neon-electric-red:      var(--neon-electric-red);
  --color-neon-electric-amber:    var(--neon-electric-amber);
  --color-neon-electric-teal:     var(--neon-electric-teal);
  --color-neon-electric-deep:     var(--neon-electric-deep);

  /* ============================================================
     LAYER 2 — Surfaces. Role-bound, so they flip with .dark.
     ============================================================ */
  --color-bg-body:       var(--role-bg-body);
  --color-bg-surface:    var(--role-bg-surface);
  --color-bg-panel:      var(--role-bg-panel);
  --color-bg-input:      var(--role-bg-input);
  --color-bg-sunken:     var(--role-bg-sunken);
  --color-border-strong: var(--role-border-strong);
  /* `--role-border-interactive` was minted for bsuite#1958 and bound to
     shadcn's `--input` in vars.css, but never given a Tailwind utility here —
     so `border-interactive` did not exist and anything reaching for it fell
     back to `border-border` at 2.26:1 dark / 1.27:1 light. Exposing it is what
     lets an interactive boundary actually be picked. */
  --color-border-interactive: var(--role-border-interactive);

  /* ============================================================
     LAYER 3 — Text scale
     ============================================================ */
  --color-text-heading:   var(--role-text-heading);
  --color-text-body:      var(--role-text-body);
  --color-text-secondary: var(--role-text-secondary);
  --color-text-muted:     var(--role-text-muted);
  --color-text-subtle:    var(--role-text-subtle);
  --color-text-disabled:  var(--role-text-disabled);

  /* ============================================================
     LAYER 3 — Semantic roles.
     Error is RED because D2C's primary is blue and error must be
     maximally separated from primary under dichromacy — see the
     separation policy in vars.css. Not a fixed-hue rule.
     ============================================================ */
  --color-role-primary:     var(--role-primary);
  --color-role-secondary:   var(--role-secondary);
  --color-role-accent:      var(--role-accent);
  --color-role-error:       var(--role-error);
  --color-role-destructive: var(--role-destructive);
  --color-role-warning:     var(--role-warning);
  --color-role-success:     var(--role-success);
  --color-role-info:        var(--role-info);

  /* AA-safe text variants — the only correct token for coloured type.
     A saturated fill colour is not a legible text colour. */
  --color-primary-text: var(--role-primary-text);
  --color-accent-text:  var(--role-accent-text);
  --color-error-text:   var(--role-error-text);
  --color-warning-text: var(--role-warning-text);
  --color-success-text: var(--role-success-text);
  --color-info-text:    var(--role-info-text);
  --color-secondary-text: var(--role-secondary-text);

  /* Informational scale — CATEGORICAL, never state. `bg-info-3`, `text-info-5`.
     Numbered because they are interchangeable slots with no meaning of their
     own; naming one invites it to be used as a semantic. Pair every one with a
     label or icon — their separation is deliberately below the semantic
     standard and cannot carry meaning alone. See --info-* in vars.css. */
  --color-info-1: var(--info-1);
  --color-info-2: var(--info-2);
  --color-info-3: var(--info-3);
  --color-info-4: var(--info-4);
  --color-info-5: var(--info-5);
  --color-info-6: var(--info-6);
  --color-info-7: var(--info-7);
  --color-info-8: var(--info-8);

  /* Stage ramp — ORDERED progression. `bg-stage-1` .. `bg-stage-6`, lightness
     descending, so "further along" reads at a glance and survives greyscale.
     Outcomes are NOT stages: hired is --role-success, rejected --role-error. */
  --color-stage-1: var(--stage-1);
  --color-stage-2: var(--stage-2);
  --color-stage-3: var(--stage-3);
  --color-stage-4: var(--stage-4);
  --color-stage-5: var(--stage-5);
  --color-stage-6: var(--stage-6);

  /* Per-app accent (Layer 5). Resolves through [data-app] on <body>,
     falling back to the shared accent when no app is declared. */
  --color-app-accent:      var(--app-accent, var(--role-accent));
  --color-app-accent-text: var(--app-accent-text, var(--role-accent-text));

  /* ============================================================
     LAYER 4 — shadcn/ui bridge
     ============================================================ */
  --color-background:             var(--role-bg-body);
  --color-foreground:             var(--role-text-body);
  --color-card:                   var(--role-bg-panel);
  --color-card-foreground:        var(--role-text-body);
  --color-popover:                var(--role-bg-panel);
  --color-popover-foreground:     var(--role-text-body);
  --color-primary:                var(--role-primary);
  --color-primary-foreground:     var(--text-on-primary);
  --color-secondary:              var(--role-secondary);
  --color-secondary-foreground:   var(--text-on-primary);
  --color-muted:                  var(--role-bg-input);
  --color-muted-foreground:       var(--role-text-muted);
  --color-accent:                 var(--role-accent);
  --color-accent-foreground:      var(--text-on-accent);
  --color-destructive:            var(--role-destructive);
  --color-destructive-foreground: var(--text-on-error);
  --color-border:                 var(--role-border);
  --color-input:                  var(--role-border);
  --color-ring:                   var(--role-primary);
  /* Modal scrim. OPAQUE — always write an explicit alpha at the call site
     (`bg-overlay/50`), never bare `bg-overlay`, which would hide the page
     entirely. Pairs with `text-text-on-overlay`. */
  --color-overlay:                var(--role-overlay);
  --color-text-on-overlay:        var(--text-on-overlay);

  /* ============================================================
     LAYER 5 — Inverse text on coloured fills
     ============================================================ */
  --color-text-on-primary: var(--text-on-primary);
  --color-text-on-accent:  var(--text-on-accent);
  --color-text-on-error:   var(--text-on-error);
  --color-text-on-success: var(--text-on-success);
  --color-text-on-warning: var(--text-on-warning);

  /* The two BASE inverse tones, exposed because the semantic roles above do
     not cover every coloured fill in the system. The stage ramp is the case
     that forced this: --stage-1..6 is a FIXED light-to-dark scale that does
     NOT flip with the theme, so a chip painted `bg-stage-1` (L 0.86) needs
     dark type in BOTH modes. Captioning it `text-foreground` — which DOES
     flip — measured 1.34:1 in dark mode on conduit's candidate list, the
     worst reading anywhere in the estate.

     Read the name as describing the TEXT, not the fill: --text-on-dark-fill
     is the DARK tone, and it is what goes on a LIGHT fill (see
     --text-on-accent, cyan at L 0.77, which uses it).

     For the stage ramp specifically, the crossover is between 4 and 5:

       stage-1 L 0.86   light 1.52:1   dark 11.49:1
       stage-2 L 0.78   light 1.93:1   dark  9.01:1
       stage-3 L 0.70   light 2.55:1   dark  6.84:1
       stage-4 L 0.62   light 3.48:1   dark  5.01:1   <- still takes DARK
       stage-5 L 0.54   light 4.85:1   dark  3.59:1   <- flips to LIGHT
       stage-6 L 0.46   light 6.86:1   dark  2.54:1

     stage-4 is the trap: it reads as "mid, so white type is fine" and is not. */
  --color-text-on-light-fill: var(--text-on-light-fill);
  --color-text-on-dark-fill:  var(--text-on-dark-fill);

  /* ============================================================
     TYPE — the contract's stack, bridged to Tailwind's font-* utilities.
     `font-sans` and `font-mono` are what components actually write, so
     without this bridge the tokens exist and nothing uses them. Every D2C
     app had been declaring its own --font-sans locally instead, which is how
     the estate came to run system-ui, Inter and JetBrains Mono at once.
     ============================================================ */
  --font-sans:    var(--font-body);
  --font-display: var(--font-display);
  --font-mono:    var(--font-mono);

  /* ============================================================
     ELEVATION — `shadow-elev-0` .. `shadow-elev-4`
     ============================================================
     Added 2026-08-12. Before this, `shadow-elev-*` was a PHANTOM: 25 call
     sites across crm7 and business-suite-unified, four CLAUDE.md files and
     the canonical bsuite-brand-system skill all instructed it, and no rule
     existed anywhere to match. Every one of them computed
     `box-shadow: none`. Cards drawn as elevated had no elevation.

     crm7 and BSU had each noticed and each written `--shadow-elev-0..4` into
     their own `:root` — but a `:root` custom property registers no Tailwind
     v4 utility, so the class still did not exist. Two divergent copies of the
     ramp, both inert. They are now redundant and should be deleted from both
     apps (see the rollout note in the PR); this block is the single source.

     WHY DEFINE IT RATHER THAN MIGRATE THE CALL SITES
     The alternative was rewriting 25 sites onto `shadow-sm/md/lg`. That scale
     block exists to override; the value is Tailwind's, not ours.
     theme-audit-ok: prose — the next line quotes the upstream default.
     is Tailwind's default, which is `rgb(0 0 0 / a)` — the pure black the
     contract bans in every role. Migrating would have moved working-as-
     intended markup onto a banned value, and left the docs to be rewritten
     too. A five-step ramp with an explicit 0 is also the right primitive for
     a card system whose cards are draggable and resizable: "resting" needs a
     name, not the absence of a class.

     GEOMETRY is Tailwind's xs/md/lg/xl, which is what crm7 and BSU had both
     chosen. COLOUR is --shadow-ink-* (vars.css): OKLCH, hue-matched to the
     surface, never pure black, and scaled by --shadow-strength in both modes.

     `@theme inline`, per this file's doctrine, for a second reason here: the
     apps still carry their own `:root --shadow-elev-*`. Plain `@theme` would
     emit ours at :root too, where the app's later copy would silently win and
     the package would appear to have no effect. `inline` puts the resolved
     value inside the utility, out of reach of that collision.

     elev-0 is `0 0 0 0 transparent` and NOT `none`. `none` inside the v4
     composite (`box-shadow: var(--tw-inset-shadow), …, var(--tw-shadow)`)
     makes the whole declaration invalid, so the browser drops it — taking
     --tw-ring-shadow with it. Measured: `shadow-elev-0 ring-2` rendered NO
     FOCUS RING. Tailwind's own `shadow-none` sidesteps this with `0 0 #0000`;
     `transparent` is the same trick without a pure-black alpha form. */
}

/* Page appearance stores a depth, then reads var(--shadow-elev-N) at runtime.
   Keep the entire ramp even when the app has no matching utility class.
   `inline` still resolves each utility through the current role-bound ink. */
@theme inline static {
  --shadow-elev-0: 0 0 0 0 transparent;
  --shadow-elev-1: 0 1px 2px 0 var(--shadow-ink-soft);
  --shadow-elev-2: 0 4px 6px -1px var(--shadow-ink), 0 2px 4px -2px var(--shadow-ink-soft);
  --shadow-elev-3: 0 10px 15px -3px var(--shadow-ink), 0 4px 6px -4px var(--shadow-ink-soft);
  --shadow-elev-4: 0 20px 25px -5px var(--shadow-ink), 0 8px 10px -6px var(--shadow-ink-faint);
}

@theme inline {
  /* ------------------------------------------------------------
     Tailwind's OWN shadow scale, rebound onto the same ink.
     ------------------------------------------------------------
     Not cosmetic. `--shadow-*`, `--inset-shadow-*`, `--drop-shadow-*` and
     theme-audit-ok: prose — the next line quotes the upstream default.
     `--text-shadow-*` ship from Tailwind as `rgb(0 0 0 / a)`, and 292
     `shadow-sm|md|lg|xl|2xl` call sites across the five D2C apps and
     packages/ were painting exactly that. Pure black is banned in every
     role, alpha forms included — and the theme-conformance scanner could
     never catch these, because the literal is in node_modules, not in any
     file it reads. Same class of blind spot as the HSL sidebar tokens.

     In dark mode this is also a correctness fix, not a tint: black at 10%
     alpha over an L 0.166 navy page is invisible, so every one of those 292
     sites had no shadow in dark mode at all.

     GEOMETRY IS UNCHANGED from Tailwind's defaults, so nothing shifts
     position; only the paint changes. The one deliberate departure is 2xl,
     which drops from 0.25 to 0.16 base alpha (--shadow-ink-deep): at a 50px
     blur, contract ink at Tailwind's density reads as a smudge rather than
     a lift. */
  --shadow-2xs: 0 1px var(--shadow-ink-soft);
  --shadow-xs:  0 1px 2px 0 var(--shadow-ink-soft);
  --shadow-sm:  0 1px 3px 0 var(--shadow-ink), 0 1px 2px -1px var(--shadow-ink);
  --shadow-md:  0 4px 6px -1px var(--shadow-ink), 0 2px 4px -2px var(--shadow-ink);
  --shadow-lg:  0 10px 15px -3px var(--shadow-ink), 0 4px 6px -4px var(--shadow-ink);
  --shadow-xl:  0 20px 25px -5px var(--shadow-ink), 0 8px 10px -6px var(--shadow-ink);
  --shadow-2xl: 0 25px 50px -12px var(--shadow-ink-deep);

  --inset-shadow-2xs: inset 0 1px var(--shadow-ink-soft);
  --inset-shadow-xs:  inset 0 1px 1px var(--shadow-ink-soft);
  --inset-shadow-sm:  inset 0 2px 4px var(--shadow-ink-soft);

  --drop-shadow-xs:  0 1px 1px var(--shadow-ink-soft);
  --drop-shadow-sm:  0 1px 2px var(--shadow-ink);
  --drop-shadow-md:  0 3px 3px var(--shadow-ink);
  --drop-shadow-lg:  0 4px 4px var(--shadow-ink);
  --drop-shadow-xl:  0 9px 7px var(--shadow-ink);
  --drop-shadow-2xl: 0 25px 25px var(--shadow-ink-deep);

  /* THE BARE DEFAULT KEYS. Added 0.11.1.
     0.11.0 rebound 26 SUFFIXED keys and missed the two unsuffixed ones, so the
     `shadow` and `drop-shadow` utilities (no size suffix) kept painting pure
     black after the upgrade. Found by measuring crm7's compiled bundle after
     the rollout: pure-black-painting declarations fell 41 -> 18 instead of to
     the expected floor, and two of the survivors were `.shadow` rules.
     Verified against the INSTALLED tailwindcss@4.3.3 theme.css, which defines
     exactly these two bare keys and no bare --inset-shadow or --text-shadow —
     so only two are rebound here. Do not invent the other two.
     Geometry and alpha ratios are Tailwind's; only the paint changes.
     Upstream --shadow equals its --shadow-sm, and that equality is preserved. */
  --shadow: 0 1px 3px 0 var(--shadow-ink), 0 1px 2px -1px var(--shadow-ink);
  --drop-shadow: 0 1px 2px var(--shadow-ink), 0 1px 1px var(--shadow-ink-soft);

  --text-shadow-2xs: 0px 1px 0px var(--shadow-ink);
  --text-shadow-xs:  0px 1px 1px var(--shadow-ink-deep);
  --text-shadow-sm:  0px 1px 0px var(--shadow-ink-soft), 0px 1px 1px var(--shadow-ink-soft), 0px 2px 2px var(--shadow-ink-soft);
  --text-shadow-md:  0px 1px 1px var(--shadow-ink), 0px 1px 2px var(--shadow-ink), 0px 2px 4px var(--shadow-ink);
  --text-shadow-lg:  0px 1px 2px var(--shadow-ink), 0px 3px 2px var(--shadow-ink), 0px 4px 8px var(--shadow-ink);

  /* ============================================================
     ANIMATIONS
     ============================================================ */
  --animate-pulse-soft:  pulse-soft  3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
  --animate-glow:        glow        2s ease-in-out infinite alternate;
  --animate-neon-pulse:  neon-pulse  2s ease-in-out infinite;
  --animate-float:       float       3s ease-in-out infinite;
  --animate-shimmer:     shimmer     2s infinite;
  --animate-typing:      typing      1.5s infinite;
  --animate-shine:       shine       3s ease-in-out infinite;

  /* ============================================================
     RADIUS — the one part of the contract that was never mapped.

     Measured 2026-08-30: 125 vars were bridged here and radius was not one
     of them, so all 3,400+ `rounded-*` uses across the six apps resolved to
     Tailwind's defaults. Braden Corporate has defined `4/6/10` since the
     brand landed and NOTHING rendered it.

     `inline` matters here for the same reason it does for colour: the value
     stays a `var()`, so a theme that rebinds --role-radius-* (Corporate does,
     D2C does not yet) reaches the utilities without a second copy.

     The fallback is Tailwind's own value, so an app whose theme defines no
     scale renders EXACTLY as it does today. This adds Corporate's language;
     it changes nothing for D2C.
     ============================================================ */
  --radius-sm:   var(--role-radius-sm,   0.125rem);
  --radius-md:   var(--role-radius-md,   0.375rem);
  --radius-lg:   var(--role-radius-lg,   0.5rem);
  --radius-xl:   var(--role-radius-xl,   0.75rem);
  --radius-2xl:  var(--role-radius-2xl,  1rem);
  --radius-3xl:  var(--role-radius-3xl,  1.5rem);
}

/* ============================================================
   KEYFRAMES — outside @theme (v4 doesn't expose them there).
   Colour-bearing frames derive from --role-accent, so they follow
   per-app accent and tenant branding instead of pinning cyan.
   ============================================================ */
@keyframes pulse-soft {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.7; }
}

@keyframes glow {
  0%   { box-shadow: 0 0  5px oklch(from var(--role-accent) l c h / 0.2); }
  100% { box-shadow: 0 0 20px oklch(from var(--role-accent) l c h / 0.6); }
}

@keyframes neon-pulse {
  0%, 100% { text-shadow: 0 0 10px oklch(from var(--role-accent) l c h / 0.3); }
  50%      { text-shadow: 0 0 20px oklch(from var(--role-accent) l c h / 0.8); }
}

@keyframes float {
  0%, 100% { transform: translateY(0px); }
  50%      { transform: translateY(-10px); }
}

@keyframes shimmer {
  0%   { background-position: -1000px 0; }
  100% { background-position:  1000px 0; }
}

@keyframes typing {
  0%, 60% { opacity: 1; }
  30%     { opacity: 0.4; }
}

@keyframes shine {
  0%   { transform: translateX(-100%) skewX(-15deg); }
  100% { transform: translateX(200%)  skewX(-15deg); }
}

/* ============================================================
   NATIVE <dialog> — restore the centring Preflight removes.

   A modal dialog is centred by the user-agent stylesheet, via two rules
   working together:

     dialog        { margin: auto; }
     dialog:modal  { position: fixed; inset: 0; }

   `inset: 0` alone would stretch the box; it is `margin: auto` that resolves
   the free space equally on both axes and centres it. Tailwind Preflight
   emits `*, ::before, ::after, ::backdrop { margin: 0 }` from the AUTHOR
   origin, which beats the user-agent origin, so the margin collapses and every
   `showModal()` dialog resolves to the top-left corner of the viewport.

   Measured on the deployed schema builder before this rule existed: the
   relationship dialog sat at x:0, y:0 with `margin: 0px`, on top of the
   settings nav. A synthetic dialog injected with the same class string and
   nothing else measured identically, which isolates the cause to the cascade
   rather than to any component. All five native dialogs in
   @bsuite/schema-builder were affected.

   `dialog:modal` is specificity (0,1,1) against Preflight's (0,0,0), so this
   wins on specificity and does not depend on import order. Scoped to `:modal`
   so a non-modal `dialog` used as an inline popover or an edge-anchored drawer
   keeps whatever positioning its author gave it.
   ============================================================ */
dialog:modal {
  margin: auto;
}
