:root,
.light {
  /* NOTE: NSAI-6259 follow-up — 14px, was 10px. Every surface in the app
     derives from this one number: tailwind.config.ts maps `rounded-lg` to it,
     `rounded-md` to it minus 2px and `rounded-sm` to it minus 4px, so a card
     now sits at 14px, a control at 12px and a menu row at 10px. Raising the
     token rather than re-rounding components one at a time is what keeps the
     whole app on one curve — and what makes the next adjustment a one-line
     change. Values written as explicit utilities (`rounded-full` pills, the
     composer's `rounded-[28px]`) are deliberate exceptions and do not move. */
  --radius: 0.875rem;
  /* NOTE: NSAI-5822 — the top strip was 72px tall around a row of 36px
     controls, which left a band of dead space above every page's breadcrumb;
     the footer had the same slack under the last row. 56px and 32px fit their
     contents with breathing room and nothing more. */
  --top-navbar-height: 3.5rem;
  --footer-height: 2rem;
  --toolbar-height: 2.5rem;
  --tabs-height: 2.5rem;
  --chat-max-width: 54rem;
  --recording-play-bar-height: 4.1rem;
  --logo-height-auth: 2.5rem;
  --logo-height-chat: 3.5rem;
  --logo-height-assistant-preview: 3.5rem;

  --background: 0 0% 100%;
  --background-secondary: 0 0% 100%;

  --foreground: 0 0% 13%;
  --foreground-secondary: 0 0% 38%;

  --card: 0 0% 100%;
  --card-foreground: 248 40% 6%;

  --popover: 0 0% 100%;
  --popover-foreground: 248 40% 6%;
  --popover-input-background: 0 0% 100%;
  --popover-title: 0 0% 38%;

  /* NOTE: NSAI-6259 — the brand purple #604ED4, replacing the blue this token
     carried since NSAI-5632 (208 79% 42%). Pavel named #604ED4 for the chat
     bubble, and --chat-user-message-background is var(--primary), so pointing
     the root at it gives the bubble that exact shade without a second token.
     Everything aliased off --primary turns purple with it: --ring,
     --accent-foreground, --empty-section-foreground, --list-item-foreground,
     --info, --chart-primary and --chart-series-1.

     The WCAG AA obligation NSAI-5632 opened still holds and is met with room
     to spare: 5.67:1 against --primary-foreground (was 4.83:1 on the blue) and
     5.94:1 as text on --background (was 5.05:1). --checkbox-background, which
     the .light block below hard-codes, moves onto the same purple rather than
     staying the paler blue NSAI-5632 had to leave behind.

     This no longer matches apps/nsai/mobile/src/global.css or the browser
     extension's src/styles/globals.css: NSAI-6259 is scoped to web, and mobile
     was explicitly placed out of scope for the redesign. The three files are
     expected to converge when the palette is carried to the other clients.
     tests/common/theme-contrast.spec.ts re-derives both ratios from this file
     and fails if either regresses. */
  --primary: 248.1 60.9% 56.9%;
  --primary-foreground: 210 40% 98%;

  --primary-dark: 245 71% 61%;
  --primary-dark-foreground: var(--primary-foreground);

  /* NOTE: NSAI-6259 — the neutral surfaces carried a blue cast (210/215/222 at
     low saturation). They keep their lightness and only move onto the 248 axis,
     so nothing that sits on them changes weight and the page reads as one
     purple family rather than purple accents over grey-blue. */
  /* NOTE: NSAI-6290 follow-up — brand-derived, not a fixed purple. These were
     literal 248-hue values in `.light`, which is NOT gated on
     `data-custom-branding`, and the theme generator does not emit them — so a
     white-labelled organisation with, say, a green brand still got NodeShift
     purple here whatever it configured. Each now points at a token the
     generator DOES emit, so it follows the organisation's own colour with no
     generator or allow-list change to keep in step.
     `--muted` carries exactly the value `--secondary` used to. */
  --secondary: var(--muted);
  --secondary-foreground: var(--foreground);

  --success: 145 100% 39%;
  --success-foreground: 0 0% 100%;

  --info: var(--primary);
  --info-foreground: var(--primary-foreground);

  --warning: 48 100% 75%;
  --warning-foreground: 0 0% 0%;

  /* NOTE: NSAI-5851 — the warning colour for TEXT, as opposed to --warning,
     which is a pale surface fill (see the ramp note further down: it measures
     1.25:1 and is only ever legible behind --warning-foreground). Painting a
     sentence in --warning left it at 1.25:1 on --card in light mode, which is
     what made the catalogue's "deprecated" line unreadable. This clears WCAG AA
     for body text on both --card/--background (5.07:1) and --muted (4.63:1).
     Callers that need a warning-coloured word use this; callers that need a
     warning-coloured surface still use --warning.

     Mobile has carried this token since NSAI-3311; the name and both values are
     kept byte-identical to apps/nsai/mobile/src/global.css so a warning sentence
     reads the same on every client. */
  --warning-strong: 30 100% 34%;

  --muted: 248 40% 96.5%;
  --muted-foreground: 248 12% 46%;

  --accent: 248 60% 94.5%;
  /* NOTE: NSAI-6259 follow-up — the ink on a hovered/selected row stays
     ordinary. It used to flip to brand purple, so every menu turned purple
     under the pointer.
     NSAI-6245 had pushed --accent to 96% precisely because
     --accent-foreground resolved to --primary, making this pair body-sized
     text on every hover and selected row at 4.61:1. That constraint is gone
     now the ink is --foreground: the pair measures 13.40:1, so the fill is
     free to sit at 94.5% and read as an actual highlight against the white
     page rather than the faint wash 96% gave. */
  --accent-foreground: var(--foreground);

  --destructive: 14 82% 46%;
  --destructive-foreground: 6 71% 95%;

  --border: 248 50% 92%;
  --ring: var(--primary);

  --checkbox-background: var(--primary);
  --checkbox-foreground: var(--primary-foreground);

  --input: 248 30% 92%;
  --input-background: 0 0% 98%;

  /* NOTE: NSAI-6349 — the NEUTRAL surface, not `--accent`. The composer fill
     was the tenant's accent, which is a tinted panel by construction: on the
     stock palette that is `248 60% 94.5%`, a lavender box, and the report was
     that the textbox reads coloured on the inside when it should read as
     paper. The colour cue on this control is its border, its focus ring and
     the wash BEHIND it — never its fill. `--card` is the same surface every
     other raised box in the app uses, so the composer now matches them and
     follows a rebranded theme without carrying the brand inside the input. */
  --chat-input-background: var(--card);
  --chat-input-border: var(--primary) / 0.3;

  --sidebar-background: var(--background-secondary);
  --sidebar-foreground: var(--foreground);
  --sidebar-foreground-secondary: var(--foreground-secondary);
  --sidebar-primary: var(--primary-dark);
  --sidebar-primary-foreground: var(--primary-dark-foreground);
  --sidebar-accent: var(--accent);
  /* NOTE: NSAI-5822 — day keeps the row's ink at --foreground when hovered or
     selected; only the pill behind it changes. */
  --sidebar-accent-foreground: var(--foreground);
  --sidebar-border: var(--border);
  --sidebar-ring: var(--ring);

  /* NOTE: NSAI-6259 — the resting tile is gone; the hover/open fill stays,
     because there it is feedback rather than decoration. */
  --dropdown-menu-icon-background: var(--primary) / 0;
  --dropdown-menu-icon-background-hover: var(--primary) / 0.15;

  /* NOTE: NSAI-6259 follow-up — a drop target is a container, not a call to
     action. These painted it as a brand-tinted panel inside a brand border,
     which is why the Uploads dropzone looked nothing like the neutral dashed
     one on Knowledge. Neutral surface, ordinary border; the ACTIVE overlay
     below keeps the brand colour, which is where it means something. */
  --dropzone-background: var(--primary) / 0;
  --dropzone-border: var(--border);
  --dropzone-active-overlay: var(--primary) / 0.1;

  /* NOTE: NSAI-6259 follow-up — an empty state is the quietest thing on a
     page, and this trio was painting it in the loudest colour the palette has:
     brand text on a brand-tinted panel inside a brand dashed border. Neutral
     surface, ordinary ink, ordinary border. The purple is still available to
     whatever ACTION the empty state offers — the button keeps it — which is
     where it does some work. */
  --empty-section-background: var(--muted);
  --empty-section-foreground: var(--foreground);
  --empty-section-border: var(--border);

  --list-item-background: var(--accent);
  --list-item-foreground: var(--foreground);
  --list-item-border: var(--primary) / 0.3;
  --list-item-icon-background: var(--primary) / 0;

  --tabs-accent: var(--accent);

  --stepper-steps-background: var(--muted);

  --plan-card-accent: var(--accent);

  --attachment-preview-background: var(--muted);

  --toast-success-background: var(--success);
  --toast-success-foreground: 0 0% 0%;
  --toast-error-background: var(--destructive);
  --toast-error-foreground: var(--destructive-foreground);
  --toast-info-background: var(--info);
  --toast-info-foreground: var(--info-foreground);
  --toast-warning-background: var(--warning);
  --toast-warning-foreground: var(--warning-foreground);
  --toast-close-icon-foreground: 0 0% 0%;

  /* NOTE: NSAI-5632 — BOTH values are hand-kept literals, never var(), so an
     organization's custom brand colour cannot repaint a semantic permission
     chip. Because they are literals, the generator's accent guard does not
     reach them, so this pair is only ever as readable as the two values right
     here.

     NSAI-6259 follow-up — a NEUTRAL slate here, with NodeShift's purple pair
     moved to the `.light:not([data-custom-branding])` block below. The purple
     move was right for NodeShift's own product and wrong as a default: a
     literal cannot be rebranded, so every white-labelled organization was
     being handed NodeShift purple on a chip in THEIR product, whatever their
     own palette. `white-label-theme.spec.ts` is the guard that caught it.
     Scoping keeps both halves: the stock theme stays purple, a branded tenant
     gets a colour that is nobody's brand. 240 20% 96% under 240 20% 40%
     measures 6.67:1 — comfortably past AA, and below the guard's saturation
     floor, so it reads as slate rather than as a washed-out purple. */
  --permission-read-background: 240 20% 96%;
  --permission-read-foreground: 240 20% 40%;
  --permission-write-background: 119 86% 89%;
  --permission-write-foreground: 0 0% 13%;
  --permission-delete-background: 6 71% 95%;
  --permission-delete-foreground: 14 82% 46%;

  --chat-user-message-background: var(--primary);
  --chat-user-message-foreground: var(--primary-foreground);
  --chat-user-message-file-type-foreground: var(--primary-foreground) / 0.8;

  --layout-gap-background: var(--accent);

  --search-input-background: var(--background);

  /* NOTE: NSAI-6259 follow-up — a neutral grey. It aliased --border, which is
     purple-tinted, so every scrollbar in the app read as a brand element. */
  /* NOTE: NSAI-6259 — segmented switcher. The track used to be `--muted`,
     which is purple-tinted, with a white/near-black active pill: the INACTIVE
     half of the control carried the brand colour and the active half carried
     none, which reads backwards. The track is now neutral and the colour moves
     to the pill that is actually selected. */
  /* NOTE: NSAI-6259 — the composer's ambient wash has its own hue in light
     theme. `--primary` (#604ED4, L 57%) is tuned for a filled button; spread
     across a halo behind a white composer it reads as a bruise rather than as
     light. This is #7A6FE0 opened up a few points (L 72%), which is what the
     review asked to try: same family, enough lift to sit under white without
     dirtying it. Dark theme keeps `--primary`, where the wash is lifting a
     near-black surface and needs the saturation. */
  /* NOTE: NSAI-6290 follow-up — `var(--primary)`, as the dark theme already
     used. This was #7A6FE0 opened to L 72%, chosen so the wash sat lightly
     behind a white composer — but a fixed hue means a green-branded
     organisation got a purple glow. The lightness it was buying is bought with
     alpha instead, in ComposerAmbientGlow, which costs nothing in fidelity and
     follows whatever brand colour the organisation sets. */
  --composer-glow: var(--primary);
  /* NOTE: NSAI-6349 — the wash is a RAMP, not one colour repeated. It shipped
     with the flat base, the first lobe and the second lobe all on
     `--composer-glow`, and the third on `--highlighted`, which in light theme
     is `--primary-dark` — 245 71% 61% against `--primary`'s 248.1 60.9% 56.9%,
     a difference no eye resolves through a 40px blur. Four layers, one visible
     colour: "the gradient is missing most of its colours" is precisely right.
     There is nothing to drift THROUGH when every stop is the same stop.

     The three shades below are mixed from `--composer-glow` toward the two
     ends of the theme itself, so the ramp is brand-derived (NSAI-6290: a
     green-branded organisation must not get a purple wash) AND theme-derived,
     which is why it is declared once here rather than per block. Mixing toward
     `--background` recedes a stop and toward `--foreground` advances it, and
     because both tokens flip between the themes, one formula produces the
     right ramp in each: on white, `pale` opens to lilac and `deep` drops to a
     violet shadow; on graphite, `pale` sinks into the surface and `deep`
     lifts to a bright lavender. Per-theme WEIGHT is still per theme — it is
     the `opacity` on each layer in ComposerAmbientGlow, where the headroom
     differs.

     oklab, not sRGB: mixing #604ED4 toward white in sRGB runs through a
     washed-out grey-violet at the midpoint, which is the "flat" this ticket is
     about. Held in a perceptual space the ramp stays purple end to end. */
  --composer-glow-pale: color-mix(
    in oklab,
    hsl(var(--composer-glow)) 34%,
    hsl(var(--background))
  );
  --composer-glow-mid: hsl(var(--composer-glow));
  --composer-glow-deep: color-mix(
    in oklab,
    hsl(var(--composer-glow)) 64%,
    hsl(var(--foreground))
  );
  /* NOTE: NSAI-6526 — the track is a WASH OF THE THEME'S OWN INK, not a fixed
     grey. It shipped as the literal `240 5% 95.5%` here and `240 4% 21%` in
     `.dark`, and the generator emits no `--switcher-track` at all, so a branded
     organisation got that grey whatever background it had set: on a navy
     organisation the switcher read as a grey-brown slab laid over the page
     rather than as part of it, in every theme the design review tried.

     `--foreground` at low alpha is the one form that cannot drift, because it
     composites against whatever surface the switcher happens to sit on — the
     page, a card, a branded navy — and `--foreground` is itself derived from
     the organisation's background by the generator. No per-theme literal to
     keep in step, and nothing to emit: a saved theme adapts without the
     generator knowing this token exists.

     The alphas reproduce the two greys they replace, so the default palette is
     unchanged to the eye: 0.05 of #212121 on white lands at L 95.6% (was
     95.5%), and 0.11 of the dark `--foreground` on the graphite lands at
     L 20.9% (was 21%). They are tuned per theme because the headroom is: dark
     lifts a near-black surface, light darkens a white one. */
  --switcher-track: var(--foreground) / 0.05;
  --switcher-thumb: var(--primary);
  --switcher-thumb-foreground: 0 0% 100%;
  --scroll-area-thumb-background: 240 6% 78%;

  --skeleton-background: var(--muted);

  /* NOTE: NSAI-6259 — no fill. A saturated tile behind a plain glyph was louder
     than the glyph itself, so the tile is gone rather than recoloured: alpha 0
     on the brand colour, which keeps the token meaningful (and white-labelled)
     if a tile is ever wanted back. Icons that carry a real company or model
     mark keep a tile, but it is --logo-tile-background below, which is a tenth
     of this one's weight and exists only to separate the mark from the page. */
  --icon-background: var(--primary) / 0;
  /* NOTE: NSAI-6259 follow-up — a NEUTRAL tile, and a little more present than
     the 6% brand wash it replaces. Two problems with that wash: at 6% it read
     as a smudge rather than as a deliberate surface, and being purple it tinted
     the company marks it sits behind — which is the one thing a logo tile must
     not do. Grey at 5% reads as an avatar plate and leaves the mark its own
     colours. */
  --logo-tile-background: 248 20% 50% / 0.08;

  --link: var(--primary-dark);

  --highlighted: var(--primary-dark);

  --chart-primary: var(--primary);
  --chart-primary-dark: var(--primary-dark);
  --chart-destructive: var(--destructive);
  --chart-muted: var(--muted);
  --chart-accent: var(--accent);
  --chart-warning: var(--warning);
  --chart-info: var(--info);
  --chart-success: 145 63% 42%;

  /* NOTE: NSAI-4673 — the categorical chart ramp, one entry per allowed pie
     slice. Not aliases of status tokens: the ramp used to borrow --accent,
     --muted and --warning, which are surface/pale fills and measured 1.14:1,
     1.09:1 and 1.25:1 against the chart card, so those slices were invisible.
     Every entry below clears 3:1 on --card with a pairwise CIE76 dE of at
     least 25. --chart-series-1 stays var(--primary) so a branded chart still
     leads with the org's own colour. Override any of them per organization. */
  --chart-series-1: var(--primary);
  --chart-series-2: 4 75% 48%;
  --chart-series-3: 145 70% 30%;
  /* NOTE: NSAI-6259 — was 272 60% 55%, which sat at a CIE76 dE of 15 from the
     new purple --chart-series-1 and broke the ramp's 25 floor. 295 60% 42% is
     30.4 away from it and at least 25 from every other entry. */
  --chart-series-4: 295 60% 42%;
  --chart-series-5: 32 95% 44%;
  --chart-series-6: 184 90% 28%;
  --chart-series-7: 325 70% 47%;
  --chart-series-8: 68 65% 28%;
  --chart-series-9: 18 40% 40%;
  --chart-series-10: 215 16% 45%;

  /* NOTE: NSAI-6259 — the multi-tone glyph ramps, moved off the 205-212 blues
     onto the 248-252 purple family that --primary now leads. The lightness of
     each stop is carried over unchanged, so a glyph keeps the internal
     light/dark structure it was drawn with and only its hue moves. */
  --sidebar-icon-fill-1: hsl(248, 70%, 62%);
  --sidebar-icon-fill-2: hsl(248, 75%, 80%);
  --sidebar-icon-fill-3: hsl(250, 60%, 58%);
  --sidebar-icon-fill-4: hsl(252, 65%, 48%);
  /* NOTE: NSAI-6259 — day paints the nav glyphs with the row's ink, the way
     night already did. Keeping the brand colour here meant every icon in the
     sidebar was purple in light theme and neutral in dark, which is the
     asymmetry the review kept hitting: the same screen read as "covered in
     purple" in one theme and clean in the other. The multi-tone ramps
     (--sidebar-icon-fill-*) are untouched — those belong to illustrative
     glyphs, not to the nav list. */
  --sidebar-nav-icon: hsl(var(--foreground));
  --sidebar-nav-icon-active: hsl(var(--sidebar-accent-foreground));
  --sidebar-icon-fill-1-hover: hsl(248, 52%, 47%);
  --sidebar-icon-fill-2-hover: hsl(248, 46%, 74%);
  --sidebar-icon-fill-3-hover: hsl(248, 52%, 47%);
  --sidebar-icon-fill-4-hover: hsl(248, 51%, 48%);

  --capability-icon-fill-1: hsl(248, 75%, 80%);
  --capability-icon-fill-2: hsl(248, 70%, 62%);
  --capability-icon-fill-3: hsl(250, 60%, 58%);
  --capability-icon-fill-4: hsl(250, 61%, 59%);
  --capability-icon-fill-5: hsl(248, 76%, 80%);
  --capability-icon-fill-disabled: hsl(0, 0%, 62%);

  --chat-history-icon-fill-1: hsl(248, 75%, 80%);
  --chat-history-icon-fill-2: hsl(248, 70%, 62%);

  --user-icon-fill-1: hsl(248, 100%, 85%);
  --user-icon-fill-2: hsl(248, 70%, 68%);
  --user-icon-fill-3: hsl(0, 0%, 100%);

  --spinning-loader-fill-1: hsl(252, 65%, 48%);
  --spinning-loader-fill-2: hsl(248, 100%, 90%);
  --spinning-loader-fill-3: hsl(248, 85%, 74%);
}

/* NOTE: NSAI-4345 — the tinted sidebar is NodeShift's own light surface, so it
   is scoped away from any org that ships branding CSS. A hand-written tenant
   theme sets --background-secondary without necessarily declaring
   --sidebar-background, and a flat value here would keep such a sidebar
   NodeShift blue instead of following the tenant's secondary background. */
.light:not([data-custom-branding]) {
  --sidebar-background: 250 100% 98.5%;

  /* NOTE: NSAI-6259 follow-up — NodeShift's own purple read-permission chip,
     scoped away from any organization that ships branding CSS for the reason
     spelled out beside the neutral default in `:root`. Still literals, never
     var(), so NSAI-5632 holds: a tenant cannot repaint this, and neither can
     it leak NodeShift's brand into a tenant's product.
     248 60% 96% under 248.1 60.9% 56.9% measures 5.19:1, clearing AA for
     normal text with more room than the 4.62:1 the blue managed. */
  --permission-read-background: 248 60% 96%;
  --permission-read-foreground: 248.1 60.9% 56.9%;
}

/* NOTE: Generated, flavor, and custom light themes must reset or override every token below so these NodeShift light fixes do not leak through the cascade. */
.light {
  /* NOTE: NSAI-6259 — onto the brand purple. NSAI-5632 had to leave this as a
     paler blue than --primary because moving it was out of that ticket's
     scope; the palette swap settles it, and a checked box now reads as the
     same purple as every other primary fill. */
  --checkbox-background: 248.1 60.9% 56.9%;

  /* NOTE: NSAI-6349 — the composer's fill has no hue of its own. It went
     `var(--accent)` → `248 60% 94.5%` (a lavender panel) → `248 100% 99%` (a
     hint of the hue) and was still reported as "the textbox is blue inside":
     99% lightness at 100% saturation is #FBFAFF, and against the pure-white
     page it sits on, a 5-point cast on the blue channel alone is exactly the
     kind of difference an eye picks up at a boundary. The purple cue is the
     border, the focus ring and the wash outside — never the fill.

     The declaration itself lives in the `:root.light` rule below, not here,
     and that is deliberate: see the note on it. */
  /* NOTE: NSAI-6259 — a NEUTRAL resting border, like the dark theme's. It was
     `248 80% 82%`, a saturated lavender, which cost twice over: it put standing
     purple around the composer in the theme the review asked to keep clean, and
     it sat so close to `--ring` at 40% that hover and focus changed nothing you
     could see. Dark theme's resting border is neutral grey, which is exactly
     why its hover and focus read. Light now matches, so the accent means
     "you are interacting with this" in both themes rather than "this is a
     composer". */
  --chat-input-border: 240 6% 87%;

  /* NOTE: NSAI-5822 — nav rows and chat-history rows share one hovered/selected
     pill: --sidebar-chat-active-background at 70%. */
  --sidebar-accent: 248 65% 92% / 0.7;
  --sidebar-chat-active-background: 248 65% 92%;

  --dropdown-menu-icon-background: var(--primary) / 0;
  --dropdown-menu-icon-background-hover: 248 80% 82% / 0.4;

  --dropzone-border: var(--border);

  --empty-section-border: var(--border);
}

/* NOTE: NSAI-6427 — the composer's fill is NEUTRAL FOR EVERY ORGANIZATION, and
   this one rule is what makes that true rather than a hope.

   `--chat-input-background` was `var(--accent)` in the generator's light reset
   until NSAI-6349 changed it to `var(--card)`. That fixed the themes generated
   AFTERWARDS and nothing else: an organization's palette is generated once in
   the browser and SAVED, so every tenant themed before that date still serves
   the old value and still shows a composer washed in its brand colour — pink
   for one of them, which is the report this rule answers. Dark never had it,
   because the dark reset never tinted the fill; that asymmetry, not a cleverer
   technique on the dark side, is the whole of "but dark looks right".

   Re-saving each theme would fix them one at a time. A selector fixes them all
   at once, and it has to be THIS selector: the organization's stylesheet is
   injected as a <style> tag appended to <head> at runtime, after this file,
   under `:root, .light` — the same specificity (0,1,0) as a bare `.light`
   here, so document order hands it the win. `:root.light` is (0,2,0) and takes
   it back, for saved themes nobody will ever re-generate as much as for new
   ones.

   The trade-off, accepted deliberately: `--chat-input-background` is on the
   documented list of customizable variables, and a tenant stylesheet that sets
   it under `:root, .light` (the fleet has at least one, at `186 33% 96%`) no
   longer takes effect. A tenant that genuinely wants a tinted composer needs
   `:root.light` or `!important` of their own — or to move `--card`, which the
   fill follows on purpose, so the composer stays one surface with the app
   instead of a colour of its own.

   Dark is deliberately NOT pinned. It is already neutral everywhere, and its
   generated value is derived from the organization's own dark card; pinning it
   would take that away to fix nothing. */
:root.light {
  --chat-input-background: var(--card);
}

/* NOTE: NSAI-5822 — the night sidebar is scoped away from custom branding
   exactly like the day one above. A generated theme emits the organization's
   own dark --background-secondary but never --sidebar-background, so a flat
   value inside .dark would pin every branded org's night sidebar to NodeShift
   navy instead of letting it follow the org's secondary surface through the
   :root fallback. */
.dark:not([data-custom-branding]) {
  /* NOTE: NSAI-6259 — #2B2C2F, the graphite sidebar Pavel specified. */
  --sidebar-background: 225 4.4% 17.6%;
}

.dark {
  /* NOTE: NSAI-5822 — one flat app surface at night. The shell used to stack
     three near-identical surfaces (--background-secondary behind the header and
     the main element, --background on the toolbar and the page wrapper,
     --layout-gap-background on the inset box), which read as banding rather
     than as structure. All three now resolve to one content colour; --card,
     --popover and --sidebar-background still sit above it, so raised surfaces
     keep their separation.

     NOTE: NSAI-6259 — that colour is now #1E1E1E, the graphite Pavel asked for
     in place of the navy #1A223F this theme shipped with: a near-black navy is
     eye strain over a long session, and the whole dark family moves off the
     227-235 hues with it. Every surface below is a neutral graphite, and the
     purple shows only where it is doing work (--primary, the user bubble, the
     active row's ink). */
  --background: 0 0% 11.8%;
  --background-secondary: var(--background);

  --foreground: 270 14% 93%;
  --foreground-secondary: 248 21% 75%;

  --card: 225 4.4% 17.6%;
  --card-foreground: 270 14% 93%;

  --popover: 228 5% 23%;
  --popover-foreground: 270 14% 93%;
  --popover-input-background: 225 4.4% 17.6%;
  --popover-title: 264 14% 93%;

  /* NOTE: NSAI-6259 — the pale purple the dark theme leads with, replacing the
     pale blue 207 90% 77%. 5.02:1 behind --primary-foreground and 6.63:1 as
     text on the graphite --background. */
  --primary: 248 85% 78%;
  --primary-foreground: 0 0% 20%;

  /* NOTE: NSAI-6259 — #604ED4. This was #5722EF (255.5 86.5% 53.5%), which at
     86.5% saturation read as aggressive on the new graphite and is the "too
     bright in dark mode ... it should feel softer" Pavel called out. Same
     purple family, 25 points less saturation, and now the one shade shared
     with the light theme's --primary and with the user bubble in both themes.
     The tour accent and the primary-dark button variant follow it. */
  --primary-dark: 248.1 60.9% 56.9%;
  --primary-dark-foreground: 270 14% 93%;

  --destructive: 0 93% 59%;

  --accent: 248 16% 30%;
  --accent-foreground: 270 14% 93%;

  --muted: 248 16% 30%;
  --muted-foreground: 248 21% 75%;

  --border: 240 5% 27%;

  --input: 240 5% 27%;
  --input-background: 225 4.4% 17.6%;

  /* NOTE: NSAI-6349 — the same value it already carried, said once. #2B2C2F is
     `--card`, the graphite Pavel specified, so the dark composer was already
     the neutral surface the report asks for; writing it as the token rather
     than as a third copy of the literal keeps it that way. */
  --chat-input-background: var(--card);
  --chat-input-border: 240 5% 27%;

  /* NOTE: NSAI-4345 — the night frames paint the active nav row with a raised
     pill and a pale ink; the chat-history row reuses it. NSAI-6259 moved the
     pill onto graphite-with-a-purple-cast and the ink onto pale purple, so the
     row reads as selected without the navy block it used to be.

     NOTE: NSAI-6259 follow-up — at 16% saturation the pill was barely
     distinguishable from the graphite behind it, so a hovered or selected row
     did not announce itself. 35% is unmistakably purple while still sitting
     under the ink: 7.87:1 against --sidebar-accent-foreground, so nothing that
     lands on it loses contrast. */
  /* NOTE: NSAI-6259 — semi-transparent, like the day token above
     (248 65% 92% / 0.7). Night carried a SOLID fill, so the active nav row
     read as a painted block of purple against the rail while the same row in
     light theme was a tint the surface showed through. 0.5 keeps the row
     clearly marked and lets the sidebar read behind it, which is what makes
     the two themes feel like one design. */
  --sidebar-accent: 250 35% 36% / 0.5;
  --sidebar-accent-foreground: 250 60% 94%;
  --sidebar-chat-active-background: var(--sidebar-accent);

  --dropdown-menu-icon-background: var(--primary) / 0;
  --dropdown-menu-icon-background-hover: 248 16% 30%;

  --dropzone-background: var(--primary) / 0;
  --dropzone-border: 240 5% 27%;
  --dropzone-active-overlay: 248 85% 78% / 0.1;

  --empty-section-background: 225 4.4% 17.6%;
  --empty-section-foreground: var(--foreground);
  --empty-section-border: 240 5% 27%;

  --list-item-background: 225 4.4% 17.6%;
  --list-item-foreground: var(--foreground);
  --list-item-border: 240 5% 27%;
  --list-item-icon-background: var(--primary) / 0;

  --tabs-accent: var(--sidebar-accent);

  --stepper-steps-background: 228 5% 21%;

  --plan-card-accent: 228 5% 23%;

  --attachment-preview-background: 228 5% 23%;

  /* NOTE: NSAI-6259 — #604ED4 in both themes. Light gets it through
     var(--primary); dark states it because --primary here is the pale purple.
     White ink on it measures 5.94:1. */
  --chat-user-message-background: 248.1 60.9% 56.9%;
  --chat-user-message-foreground: 0 0% 100%;
  --chat-user-message-file-type-foreground: 0 0% 100% / 0.8;

  --layout-gap-background: var(--background);

  --search-input-background: 225 4.4% 17.6%;

  /* NOTE: Neutral track, brand-coloured pill — see the light block above, which
     carries the reasoning for the alpha form. The track was a literal neutral
     grey here rather than `--muted` (248 16% 30%), which is visibly violet
     against this background; a wash of `--foreground` keeps that neutrality
     against the default palette AND follows a branded one. */
  --composer-glow: var(--primary);
  --switcher-track: var(--foreground) / 0.11;
  --switcher-thumb: var(--primary);
  --switcher-thumb-foreground: var(--primary-foreground);
  --scroll-area-thumb-background: 240 5% 38%;

  --skeleton-background: 225 4.4% 17.6%;

  --icon-background: var(--primary) / 0;
  /* NOTE: NSAI-6259 — white rather than the brand purple at night: a purple
     wash under a company logo tints the mark itself. Lifted from 6% to 9% in
     the follow-up so the plate is visibly a plate on the graphite. */
  --logo-tile-background: 0 0% 100% / 0.09;

  --link: 248 75% 80%;

  /* NOTE: NSAI-5822 — #A1A6FF, the designer's primary for TEXT on dark
     surfaces. Rounded, this rendered #A3A8FF. */
  --highlighted: 236.8 100% 81.6%;

  --chart-success: 145 55% 60%;

  /* NOTE: NSAI-5851 — the dark background carries the pale token fine (10:1), so
     it doubles as the strong variant here, exactly as mobile does it. The light
     theme's 30 100% 34% would sit at roughly 1.7:1 on this background. */
  --warning-strong: var(--warning);

  /* NOTE: NSAI-4673 — dark variants of the categorical ramp, lifted so they
     clear 3:1 on the dark --card. In this theme --accent and --muted are the
     same value, so the old ramp drew slices six and seven identically. */
  --chart-series-2: 4 85% 68%;
  --chart-series-3: 145 60% 55%;
  /* NOTE: NSAI-6259 — see the light ramp; 272 75% 75% collided with the new
     purple --chart-series-1 at a dE of 12. */
  --chart-series-4: 290 75% 70%;
  --chart-series-5: 32 90% 60%;
  --chart-series-6: 184 65% 55%;
  --chart-series-7: 325 80% 72%;
  --chart-series-8: 68 55% 58%;
  --chart-series-9: 18 45% 62%;
  --chart-series-10: 220 6% 60%;

  /* NOTE: NSAI-6259 — the night glyph ramps, off the 205-212 blues and onto the
     same purple family as the day ones, lightness per stop unchanged. */
  --sidebar-icon-fill-1: hsl(250, 90%, 68%);
  --sidebar-icon-fill-2: hsl(248, 75%, 80%);
  --sidebar-icon-fill-3: hsl(252, 65%, 48%);
  --sidebar-icon-fill-4: hsl(252, 65%, 48%);
  /* NOTE: NSAI-5822 — night paints the nav glyphs with the row's ink, not the
     brand colour. */
  --sidebar-nav-icon: hsl(var(--foreground));
  --sidebar-nav-icon-active: hsl(var(--sidebar-accent-foreground));
  --sidebar-icon-fill-1-hover: hsl(248, 89%, 72%);
  --sidebar-icon-fill-2-hover: hsl(250, 86%, 83%);
  --sidebar-icon-fill-3-hover: hsl(252, 65%, 48%);
  --sidebar-icon-fill-4-hover: hsl(248, 52%, 52%);

  --capability-icon-fill-1: hsl(250, 60%, 94%);
  --capability-icon-fill-2: hsl(250, 90%, 68%);
  --capability-icon-fill-3: hsl(252, 65%, 48%);
  --capability-icon-fill-4: hsl(250, 61%, 59%);
  --capability-icon-fill-5: hsl(250, 60%, 94%);
  --capability-icon-fill-disabled: hsl(249, 21%, 75%);
}
