/** * Color tokens. * * Source of truth: AiStrike Complete Design System (Figma) * https://www.figma.com/design/LJdrBxmaROpXg0hLW3GKe6/AiStrike-Complete-Design-System?node-id=23-476 * * All hex values below were extracted directly from the Figma "Colours" frame. * This is a dark-first security design system. */ /** * Text colors — a monotonic ramp. * * The tiers descend in lightness in the order they are declared, so picking a * tier is picking an emphasis level. This was not previously true: `tertiary` * used to be brighter than `secondary`, and `muted` brighter than `subtle`, * which is why call sites reached past them for an ad-hoc grey. * * Contrast against `background.widget` (#262626), the rung most body text sits * on, is pinned in `textRamp.contrast.test.ts`: * * | tier | ratio | usable for | * |-----------|-------|---------------------------------------------| * | primary | 12.93 | body and headings | * | secondary | 9.14 | body | * | tertiary | 6.00 | body | * | subtle | 4.74 | body, at the AA floor — see the caveat below | * | muted | 3.63 | large or bold text only | * | disabled | 2.33 | disabled affordances, exempt by design | * * `subtle` clears 4.5:1 on `page`, `primary` and `widget` only. On `surface` * and above it drops to 4.25 and below, so the surface scope switches * `--ds-text-quiet` from `subtle` to `tertiary` at that point. Do not hand-pick * `subtle` for text on an overlay. * * ## What used to be here * * Six entries were removed once the ramp became monotonic and the severity * scale grew a text variant, because each was a second name for a decision made * elsewhere — and a name is where drift starts: * * - `tableLabel` and `inactiveTab` were both `#909090`, the same value as * `subtle`. The spec folds `inactiveTab` into `subtle` explicitly. Anything * quiet should read `var(--ds-text-quiet)`, which also swaps tier where * `subtle` stops being legible — a fixed `#909090` cannot. * - `highlight` was `#00C4AC`, which is `brand.main`. * - `critical`, `high` and `medium` were severity *fills* being offered as ink. * They are unusable that way: `#BB010B` on `widget` is 2.49:1. Severity as * text is `severityTextColors`, which is the same hues lightened until each * clears 4.5:1 on every rung. * - `chartText1..3` restated three series colours. Series identity belongs to * `chartTokens[mode].series`, which is validated as a categorical palette. */ export declare const textColors: { readonly primary: "#EDEDED"; readonly secondary: "#C9C9C9"; /** One step below `secondary`. Now genuinely dimmer than it, which it was not. */ readonly tertiary: "#A3A3A3"; /** * Supporting text sitting directly under a title — a row's description line, * a card's subtitle. Sits at the 4.5:1 floor on `widget`, so it is the last * tier usable for body text, and only up to that rung. */ readonly subtle: "#909090"; /** * Incidental metadata: timestamps, counts, authors, "last seen" columns. * Below the AA floor for normal text at 3.63:1 — large or bold only. */ readonly muted: "#7C7C7C"; /** * Text *on* a tooltip. Tooltips are dark (`background.raised`), so this is a * light ink. It was a near-black, which only made sense on a light tooltip. */ readonly tooltip: "#EDEDED"; readonly disabled: "#5E5E5E"; /** * Link text. Nine percent lighter than the spec's `#6699FF`, which cleared * 4.5:1 on every rung except `overlay` (4.10:1). A link inside a dialog is * common enough that certifying the token below the top rung was not worth * the footgun; this holds from `page` (6.99:1) to `overlay` (4.53:1). */ readonly link: "#74A2FF"; }; /** * Surface / background colors — the six-rung ladder. * * The first six entries are an ordered scale, not a set of names. Each rung is * 2–3 L\* above the one below it, which is enough to read as a step without * turning a dense page into stripes. Containment goes *up*: a card on `primary` * is `widget`, a popover over that card is `surface`. Recession goes *down*: an * input inside a `widget` is `primary`, one rung below its host. * * ``` * page #191919 the app shell, and recessed bands (table headers) * primary #1F1F1F page body, and fields hosted on widget * widget #262626 cards, panels, dashboard tiles * surface #2E2E2E popovers, menus, drawers, dialogs * raised #333333 tooltips, snackbars, floating elements over an overlay * overlay #3A3A3A the ceiling — a second dialog, a menu inside a drawer * ``` * * `page` is no longer pure black. That is deliberate and the ladder depends on * it: a field on the app shell has to recess *below* its host, and nothing sits * below `#000000`. * * Three levels of containment is the supported maximum. `Surface` clamps a * fourth and warns in development — see `src/tokens/surfaceScopes.ts`. */ export declare const backgroundColors: { /** Rung 0. App shell, and bands that recess below the body. */ readonly page: "#191919"; /** Rung 1. Primary content background, and fields hosted on `widget`. */ readonly primary: "#1F1F1F"; /** Rung 2. Widget / card background. Where most body text lives. */ readonly widget: "#262626"; /** Rung 3. Pop-up / filter / side panel / AI chat. */ readonly surface: "#2E2E2E"; /** Rung 4. Tooltips, snackbars, and anything floating above an overlay. */ readonly raised: "#333333"; /** Rung 5, the ceiling. A dialog over a dialog, a menu inside a drawer. */ readonly overlay: "#3A3A3A"; /** Rule between table rows. Subtler than `border` so the outline still reads. */ readonly tableDivider: "#262626"; /** Subtle success-tinted surface. */ readonly darkGreen: "#182724"; /** * Default border / divider. * * Rung-agnostic, and therefore only correct on the lower rungs: it is 1.13:1 * on `widget` and near-invisible on `surface`. Prefer `borderByRung` or * `var(--ds-border)` anywhere the rung is known. Kept because `palette.divider` * points at it and every MUI `Divider` reads that. */ readonly border: "#2F2F2F"; /** A divider that should read as a hint rather than a rule. */ readonly borderSubtle: "#272727"; /** * Card border / outline. Kept for explicit opt-in, but a card on `primary` or * above should not carry one — the rung step already separates it. */ readonly cardBorder: "#414141"; }; /** * Border colour per rung, at hairline-divider weight (~1.35:1 against its host). * * A border cannot be one value on a ladder this tight. The rungs are 2–3 L\* * apart, so a fixed `border` that reads on `page` is invisible two rungs up: * `#2F2F2F` on `widget` is 1.13:1, and on `surface` it is almost the background * itself. The flat tokens are kept for existing consumers, but anything that * knows its rung should use this — or `var(--ds-border)`, which resolves it. * * 1.35:1 is calibrated against what already ships rather than invented: the old * `border` ran 1.11–1.32:1 depending on surface, and MUI's own dark divider * (`rgba(255,255,255,0.12)` over `#121212`) is 1.38:1. */ export declare const borderByRung: { readonly page: "#313131"; readonly primary: "#363636"; readonly widget: "#3B3B3B"; readonly surface: "#424242"; readonly raised: "#474747"; readonly overlay: "#4E4E4E"; }; /** * Border colour per rung at outline weight (~1.7:1), for a deliberate edge * rather than a divider — an outlined chip, a focusable container, a selected * card. * * The `page` entry is `#414141`, which is exactly the historical `cardBorder`. * That is a coincidence worth keeping: it means this ramp is the same weight the * system already used for outlines, generalised across the ladder. */ export declare const outlineByRung: { readonly page: "#414141"; readonly primary: "#454545"; readonly widget: "#4A4A4A"; readonly surface: "#515151"; readonly raised: "#565656"; readonly overlay: "#5C5C5C"; }; /** * The ordered ladder, lowest rung first. * * Exported as data so the monotonicity guarantee can be tested and the * Storybook docs page can render the scale without restating the values. */ export declare const SURFACE_LADDER: readonly ["page", "primary", "widget", "surface", "raised", "overlay"]; export type SurfaceRung = (typeof SURFACE_LADDER)[number]; /** * Row-separator colour per rung (~1.12:1 against its host). * * Lighter than `borderByRung` on purpose: container outlines and header * underlines stay on the heavier border, while the rules between table rows * are rhythm, not structure. Keyed by the surface the separator sits on so a * table derives its row rule from the host without picking a flat grey. * * Source: Table Separators spec (dark values). Light counterparts live on the * light surface scope — light has a different surface ladder today. */ export declare const separatorByRung: { readonly page: "#212121"; readonly primary: "#282828"; readonly widget: "#2F2F2F"; readonly surface: "#383838"; readonly raised: "#3D3D3D"; readonly overlay: "#454545"; }; /** * Table header fill per rung (~1.08:1 against its host). * * Moves *away* from the elevation direction — darker in light, lighter in dark — * so the header reads as recessed relative to its rows. A flat `tableHeader` * only worked on the rung it was drawn against; inside a card (widget) it read * as a trench, and on a dialog it read as a bar. * * Source: Table Separators spec. `primary` reuses `widget` rather than minting * a second hex. Light counterparts live on the light surface scope. */ export declare const tableHeaderByRung: { readonly page: "#202020"; /** Same hex as `widget` — reuse rather than add a value. */ readonly primary: "#262626"; readonly widget: "#2C2C2C"; readonly surface: "#343434"; readonly raised: "#383838"; readonly overlay: "#3F3F3F"; }; /** Three levels of containment. A fourth is clamped and warned about. */ export declare const MAX_SURFACE_DEPTH = 3; /** * Scrim opacities over black, by what the scrim is covering for. * * A dialog demands more separation than a menu because it takes over the * page. These are opacities, not colors, because the scrim always sits on * black regardless of rung. */ export declare const scrim: { /** A menu or popover. Barely there — it exists to catch the outside click. */ readonly menu: 0.32; /** A drawer or dismissible overlay. */ readonly drawer: 0.5; /** A modal dialog, which owns the page while it is open. */ readonly dialog: 0.6; /** * A busy veil over content that is already open — the loading state inside a * popover or menu, not a scrim between layers. Named rather than inlined so * the two places that draw one cannot drift apart. */ readonly loading: 0.4; }; /** * Ink for text sitting on a *saturated* fill — a brand or severity colour — * rather than on a rung. * * Fixed rather than derived from the ladder, because the fill it sits on does * not move with the ladder either. Named by the fill it goes on, not by the * theme mode: a light fill wants dark ink in either theme. */ export declare const fillInk: { /** On a light saturated fill: brand teal, warning amber. */ readonly onLight: "#191919"; /** On a dark saturated fill: critical red. */ readonly onDark: "#FFFFFF"; }; /** * Interaction state opacities, applied over the host rung rather than as fixed * colors, so a hover on `page` and a hover on `overlay` both read as one step. */ export declare const stateAlpha: { readonly hover: 0.06; readonly selected: 0.12; readonly pressed: 0.16; readonly focus: 0.12; readonly disabled: 0.38; }; /** * Shared chrome for outlined TextField, Select, Search, Autocomplete, and * the secondary Button. Kept separate from `backgroundColors.border` so * restyling these controls does not move every divider in the system. * Hover and focus use the same outline — not `text.primary` (white) and * not the brand teal. */ export declare const inputColors: { /** * Field / secondary-button fill (dark). * * This is the fallback for a field with no surface context. A field inside a * scoped surface recesses one rung below its host instead, via * `var(--ds-field)` — that is what makes an input read as inset rather than * as a differently-coloured box. The one exception is a field directly on * `page`: there is no rung below it, so it goes transparent with a hairline. * * `page` rather than `primary`, because the unscoped case is a field on the * document body, and `palette.background.default` is `primary`. Setting this * to `primary` made the field exactly match the body and vanish, leaving only * its outline. This is also within a point of the historical `#1A1A1A`, so * the fallback path barely moves. * * Light mode uses `lightInputColors` — never fall back to this hex in light, * or fields render as black bars on a grey page. */ readonly background: "#191919"; /** Outline at rest. */ readonly border: "#2F2F2F"; /** Outline on hover. One step up, so the control acknowledges the pointer. */ readonly borderHover: "#3A3A3A"; /** * Outline on focus. * * Deliberately equal to `border`: focus is currently *not* indicated on * fields in dark. Both specs proposed a ring — neutral `#E8E8E8` in Tokens * §03, brand `#00C4AC` in Component System §05 — and neither was adopted for * dark. The token exists, and components reference it, so turning focus * indication on later is a change to this one value rather than a change to * every input component. * * Light mode's `lightInputColors.focusRing` is distinct (`#1A1A1A`), so focus * is visible there. Note the accessibility cost on dark while it stays here: * a keyboard user gets no visible focus indication on a field (WCAG 2.4.7). */ readonly focusRing: "#2F2F2F"; /** Outline for a field in the error state. */ readonly errorBorder: "#EB2D36"; }; /** * Light-theme input chrome — Tokens Option A Light §03. * * Same roles as `inputColors`, inverted. `background` is the unscoped / * primary-host fallback (one step onto `page`). Per-host fills live on the * light surface scope as `--ds-field`. */ export declare const lightInputColors: { readonly background: "#E9E9E9"; readonly border: "#D4D4D4"; /** −8 L* on the line only; the fill never moves on hover. */ readonly borderHover: "#ADADAD"; /** Neutral focus ring — distinct from `border`, so focus is visible in light. */ readonly focusRing: "#1A1A1A"; /** Severity high, darkened to clear 3:1 against white. */ readonly errorBorder: "#C41B23"; }; /** * Light surface ladder — Tokens Option A Light §01. * * Elevation still means lighter. Four tonal steps fit above `#E9E9E9`; `raised` * and `overlay` share white and separate by shadow + border weight instead. */ export declare const lightBackgroundColors: { readonly page: "#E9E9E9"; readonly primary: "#F1F1F1"; readonly widget: "#F8F8F8"; readonly surface: "#FCFCFC"; readonly raised: "#FFFFFF"; readonly overlay: "#FFFFFF"; /** Below the ladder — table rules and section edges that must read on white. */ readonly tableDivider: "#E2E2E2"; readonly borderSubtle: "#E2E2E2"; readonly border: "#D4D4D4"; readonly cardBorder: "#BDBDBD"; readonly tintGreen: "#E8F2EF"; }; /** * Light text ramp — Tokens Option A Light §02. * * Contrast-checked against `widget` (#F8F8F8). `subtle` holds 4.5:1 on every * light rung, so `--ds-text-quiet` does not need to swap tier the way it does * in dark. */ export declare const lightTextColors: { readonly primary: "#1A1A1A"; readonly secondary: "#3D3D3D"; readonly tertiary: "#5C5C5C"; readonly subtle: "#6E6E6E"; readonly muted: "#8A8A8A"; readonly disabled: "#ABABAB"; readonly link: "#0B5FCC"; readonly highlight: "#00786B"; /** Ink on the inverted (dark) light-theme tooltip. */ readonly tooltip: "#F1F1F1"; }; /** * Brand tint per rung, for selected rows and brand-emphasised panels. * * A resolved fill, not brand-at-16%-alpha. An alpha tint drifts with whatever * it happens to sit on, which on a six-rung ladder means a selected row looks * like a different colour in a card than in a dialog. Keyed by host rung so the * tint lands the same distance above its background everywhere. */ export declare const brandTintByRung: { readonly page: "#152F2C"; readonly primary: "#1B3532"; readonly widget: "#223C39"; readonly surface: "#2A4441"; readonly raised: "#2F4946"; readonly overlay: "#334A47"; }; /** * Background usage guidance. * * Maps each surface token to the role it plays in the product, so humans (via * Storybook) and LLMs (via the published package) can pick the correct * background instead of guessing or hardcoding hex. Keyed by `backgroundColors` * so the guidance can never drift from the tokens. * * Source: AiStrike "Background Colors" reference. */ export declare const backgroundUsage: { readonly page: { readonly hex: "#191919"; readonly label: "Page (rung 0)"; readonly useFor: "The app shell, and bands that should recess below the body such as a table header. No longer pure black, so that a field on the shell has a rung to recess into."; readonly examples: readonly ["App shell background", "Table header band", "Area behind primary surfaces"]; }; readonly primary: { readonly hex: "#1F1F1F"; readonly label: "Primary (rung 1)"; readonly useFor: "Primary content background for a page or section, the recessed fill for a field hosted on a widget, and the unscoped table body / user chat bubble."; readonly examples: readonly ["Page body", "Main content area", "Input inside a card", "User chatbox"]; }; readonly widget: { readonly hex: "#262626"; readonly label: "Widget (rung 2)"; readonly useFor: "Widget / card surfaces sitting on the primary background. The rung most body text sits on, and what the text ramp is measured against."; readonly examples: readonly ["Cards", "Dashboard widgets", "Panels"]; }; readonly surface: { readonly hex: "#2E2E2E"; readonly label: "Surface (rung 3) — Pop-up / Filter / Side Panel / AI chat"; readonly useFor: "Elevated overlays and side panels that sit above content. From this rung up, quiet text must use `tertiary` rather than `subtle`."; readonly examples: readonly ["Dialogs and pop-ups", "Filter panels", "Side panels", "AI chat surface"]; }; readonly raised: { readonly hex: "#333333"; readonly label: "Raised (rung 4)"; readonly useFor: "Elements floating above an overlay, and portalled elements that cannot inherit a rung from the DOM."; readonly examples: readonly ["Tooltips", "Snackbars", "A menu opened from a popover"]; }; readonly overlay: { readonly hex: "#3A3A3A"; readonly label: "Overlay (rung 5, the ceiling)"; readonly useFor: "The top of the ladder. Reaching it means three levels of containment; a fourth is clamped here rather than continuing to lighten."; readonly examples: readonly ["A dialog opened from a dialog", "A menu inside a drawer"]; }; readonly tableDivider: { readonly hex: "#262626"; readonly label: "Table Divider"; readonly useFor: "The rule between table rows. Subtler than `border` so the outline still reads."; readonly examples: readonly ["Row separator"]; }; readonly darkGreen: { readonly hex: "#182724"; readonly label: "Dark Green"; readonly useFor: "Subtle success-tinted surface for positive emphasis."; readonly examples: readonly ["Success-tinted panels", "Attack-path success emphasis"]; }; }; export type BackgroundToken = keyof typeof backgroundUsage; /** * Brand / primary accent. One accent, deliberately. * * There is no `brand.link` here. Link blue is *not* brand — the two are * separate on purpose, because a teal link inside teal-accented UI is * unfindable. Filing it under `brand` was a category error with a real * consequence: `palette.secondary` read it, so every MUI component rendered * with `color="secondary"` painted link ink as a background fill. Links read * `textColors.link`, which is the only place that colour lives. */ export declare const brand: { readonly main: "#00C4AC"; }; /** Success / Failure / Info / Warning semantic colors. */ export declare const statusColors: { readonly success: "#199F0F"; readonly failure: "#EB2D36"; readonly warning: "#E99C16"; readonly info: "#1266CC"; readonly general: "#00C4AC"; }; /** The ordered severity magnitude scale. `info` and `untriaged` are separate states, not rungs. */ export declare const SEVERITY_ORDER: readonly ["critical", "high", "medium", "low"]; export type SeverityKey = (typeof SEVERITY_ORDER)[number] | 'info' | 'untriaged'; /** * Severity colors used across the security product. * * Severity is a reserved *status* scale, not a categorical series palette, and * it is validated as one. The categorical checks cannot be satisfied at any * assignment — critical/high share a hue, as do medium/low — and the one-hue * ordinal checks fail too, because red through yellow spans ~90 degrees. Red, * amber and yellow are an industry domain convention for severity; recolouring * them to satisfy a validator would break recognition for no accessibility gain. * * What is enforced instead (see `colors.severity.test.ts`): every step clears a * contrast floor against its surface, and lightness increases monotonically * across the ordered scale with adjacent dL >= 0.06 so it reads as ordered. * * Movement from the historical values is deliberately minimal: * - dark: only `critical` moves, 2.74:1 -> 3.04:1. * - light: only `medium`, `low` and `untriaged` move, to clear 2:1 on white * while preserving the ordering gaps. * * For series identity use `chartTokens[mode].series` instead — never these. */ export declare const severityColorsByMode: { readonly dark: { readonly critical: "#c80009"; readonly high: "#EB2D36"; readonly medium: "#E99C16"; readonly low: "#D0C800"; readonly info: "#1266CC"; /** Not-yet-triaged / unknown severity. */ readonly untriaged: "#C0C0C0"; }; readonly light: { readonly critical: "#BB010B"; readonly high: "#EB2D36"; readonly medium: "#db9000"; readonly low: "#c2bb01"; readonly info: "#1266CC"; /** Not-yet-triaged / unknown severity. */ readonly untriaged: "#b7b7b7"; }; }; /** Dark-mode severity colors. Retained flat for existing consumers. */ export declare const severityColors: { readonly critical: "#c80009"; readonly high: "#EB2D36"; readonly medium: "#E99C16"; readonly low: "#D0C800"; readonly info: "#1266CC"; /** Not-yet-triaged / unknown severity. */ readonly untriaged: "#C0C0C0"; }; /** * Severity as *text* or as an icon glyph, rather than as a fill. * * The fills above are tuned to carry white or dark ink on top of them. Used as * ink themselves they are far too dark — `critical` on `widget` is 2.49:1. These * are the same hues lightened until each clears 4.5:1 on `overlay`, the highest * rung, so one value works on every rung rather than needing a per-rung table. * * These deliberately do **not** preserve the lightness ordering that the fills * do: forcing two saturated reds to the same contrast ratio necessarily puts * them at the same lightness, so `critical` and `high` land 0.002 apart. That is * acceptable here and only here, because this token is used where a word * ("Critical") or a distinct glyph carries the rank. Never use these as * unlabelled indicator fills — see `severityIndicatorCeiling`. */ export declare const severityTextColors: { readonly critical: "#E68A8E"; readonly high: "#F38287"; readonly medium: "#E99C16"; readonly low: "#D0C800"; readonly info: "#76A6E1"; readonly untriaged: "#C0C0C0"; }; /** * The highest rung at which each severity *fill* may be used as an unlabelled * indicator — a bare dot, a bar, a chart segment with no text. * * Two different accessibility rules apply to a severity colour depending on * whether it is labelled, and conflating them is what made the ladder look like * it broke the palette: * * - A **labelled** chip is governed by WCAG 1.4.3: the label against the fill. * Every severity clears that, so a labelled chip works on every rung. * - An **unlabelled** indicator is governed by WCAG 1.4.11: the fill against its * background, at 3:1. Contrast falls as the ladder lightens, so this one has a * ceiling. * * `null` means there is no rung where the fill clears 3:1, so the indicator must * carry a label or a shape difference. `critical` cannot be fixed by lightening: * the amount needed closes its lightness gap to `high` below the 0.06 the * ordering test requires, and recolouring the scale to satisfy a validator would * break the domain recognition the palette exists for. The correct fix for an * unlabelled critical dot is to label it. * * Pinned in `colors.severity.test.ts`, which recomputes this from the tokens — * so if a fill moves, the test fails until this table is updated. */ export declare const severityIndicatorCeiling: { readonly critical: null; readonly high: "surface"; readonly medium: "overlay"; readonly low: "overlay"; readonly info: "page"; readonly untriaged: "overlay"; }; /** * Three whole scales were removed here, not just individual entries. Each one * was a parallel palette for a job another scale already owned, and every one of * them had drifted: * * - **`priorityColors`** — a six-key sibling of severity, for "triage/priority * badges where the emphasis ramp differs slightly". Three of its values * existed nowhere else (`high #F0352A`, `medium #FE8C00`, and `info #32DEB5`, * a teal-green that is neither the brand nor the info hue). Nothing consumed * it. Priority renders as severity; there is one ordered scale, validated as * one, in `severityColorsByMode`. * - **`chartColors`** — already deprecated in place, with its own docstring * saying it "must not be used to colour multiple series" because its entries * are severity synonyms. A scale that cannot be used for the thing it is named * after is not a scale. Series identity is `chartTokens[mode].series`. * - **`inventoryColors`** — seven categorical hues with no consumers and no * contrast validation, overlapping the chart palette's job. Its one value that * does appear on screen, `#3D7FFF`, is declared independently in * `attackPath.ts` for the resource node, so nothing changes visually. * * Icon colors. * * These are *derived* rather than restated. Every entry below used to be its own * hex, which meant eleven of the fourteen were a second copy of a value owned by * `statusColors`, `brand` or `neutral` — free to drift from the original with * nothing to catch it. The keys are unchanged, because `Icon`'s `tone` prop is * `keyof typeof iconColors` and that is public API. * * Note that per the spec an icon should normally take the *text* tier it sits in * rather than a colour from here, and reach for a tone only when the icon itself * carries status. `alert` and `vulnerability` are the two genuinely independent * values left; both are domain hues with no owner elsewhere. */ export declare const iconColors: { readonly success: "#199F0F"; readonly failure: "#EB2D36"; readonly warning: "#E99C16"; readonly info: "#1266CC"; readonly general: "#00C4AC"; readonly tabIcon: "#888888"; readonly actionActive: "#FFFFFF"; readonly inactiveToggle: "#D3D3D3"; readonly activeTabIcon: "#00C4AC"; readonly alert: "#EF073A"; readonly vulnerability: "#EA5515"; readonly attackPathDefault: "#FFFFFF"; readonly attackPathSuccess: "#199F0F"; readonly attackPathFailure: "#EB2D36"; }; /** * Neutral grey scale, used to generate MUI palette steps and hover/active * overlays. * * Deliberately frozen at its historical values and **not** aligned to the * surface ladder. Several entries are the ladder's own former values — `900` * was `widget`, `800` was `primary`, `700` was `surface` — so reading a surface * colour out of here now gets a rung that no longer exists. Remapping it was * considered and rejected: these feed MUI's internal grey steps, where a shift * shows up in places the ladder has no opinion about. * * For any surface, use `backgroundColors` / the ladder. For a step in a * generated scale, this is still the right source. * * Every entry is a true grey (R = G = B). `600` was `#2B2B33`, which is not — * it carries an 8-point blue lift, reading as blue-violet at hue 240. That * mattered because MUI resolves a dark `Avatar`'s default background to * `grey[600]`, so every initials avatar in the system was tinted blue by a * value in a scale named "neutral". It is now `#2B2B2B`: the same step, with * the hue removed. Pinned by `colors.neutral.test.ts`. */ export declare const neutral: { readonly 0: "#000000"; readonly 900: "#141414"; readonly 850: "#171717"; readonly 800: "#191919"; readonly 700: "#242424"; readonly 600: "#2B2B2B"; readonly 500: "#363636"; readonly 400: "#5E5E5E"; readonly 300: "#888888"; readonly 200: "#AEAEAE"; readonly 100: "#D3D3D3"; readonly 50: "#FDFDFD"; readonly white: "#FFFFFF"; }; export declare const colors: { readonly text: { readonly primary: "#EDEDED"; readonly secondary: "#C9C9C9"; /** One step below `secondary`. Now genuinely dimmer than it, which it was not. */ readonly tertiary: "#A3A3A3"; /** * Supporting text sitting directly under a title — a row's description line, * a card's subtitle. Sits at the 4.5:1 floor on `widget`, so it is the last * tier usable for body text, and only up to that rung. */ readonly subtle: "#909090"; /** * Incidental metadata: timestamps, counts, authors, "last seen" columns. * Below the AA floor for normal text at 3.63:1 — large or bold only. */ readonly muted: "#7C7C7C"; /** * Text *on* a tooltip. Tooltips are dark (`background.raised`), so this is a * light ink. It was a near-black, which only made sense on a light tooltip. */ readonly tooltip: "#EDEDED"; readonly disabled: "#5E5E5E"; /** * Link text. Nine percent lighter than the spec's `#6699FF`, which cleared * 4.5:1 on every rung except `overlay` (4.10:1). A link inside a dialog is * common enough that certifying the token below the top rung was not worth * the footgun; this holds from `page` (6.99:1) to `overlay` (4.53:1). */ readonly link: "#74A2FF"; }; readonly background: { /** Rung 0. App shell, and bands that recess below the body. */ readonly page: "#191919"; /** Rung 1. Primary content background, and fields hosted on `widget`. */ readonly primary: "#1F1F1F"; /** Rung 2. Widget / card background. Where most body text lives. */ readonly widget: "#262626"; /** Rung 3. Pop-up / filter / side panel / AI chat. */ readonly surface: "#2E2E2E"; /** Rung 4. Tooltips, snackbars, and anything floating above an overlay. */ readonly raised: "#333333"; /** Rung 5, the ceiling. A dialog over a dialog, a menu inside a drawer. */ readonly overlay: "#3A3A3A"; /** Rule between table rows. Subtler than `border` so the outline still reads. */ readonly tableDivider: "#262626"; /** Subtle success-tinted surface. */ readonly darkGreen: "#182724"; /** * Default border / divider. * * Rung-agnostic, and therefore only correct on the lower rungs: it is 1.13:1 * on `widget` and near-invisible on `surface`. Prefer `borderByRung` or * `var(--ds-border)` anywhere the rung is known. Kept because `palette.divider` * points at it and every MUI `Divider` reads that. */ readonly border: "#2F2F2F"; /** A divider that should read as a hint rather than a rule. */ readonly borderSubtle: "#272727"; /** * Card border / outline. Kept for explicit opt-in, but a card on `primary` or * above should not carry one — the rung step already separates it. */ readonly cardBorder: "#414141"; }; readonly backgroundUsage: { readonly page: { readonly hex: "#191919"; readonly label: "Page (rung 0)"; readonly useFor: "The app shell, and bands that should recess below the body such as a table header. No longer pure black, so that a field on the shell has a rung to recess into."; readonly examples: readonly ["App shell background", "Table header band", "Area behind primary surfaces"]; }; readonly primary: { readonly hex: "#1F1F1F"; readonly label: "Primary (rung 1)"; readonly useFor: "Primary content background for a page or section, the recessed fill for a field hosted on a widget, and the unscoped table body / user chat bubble."; readonly examples: readonly ["Page body", "Main content area", "Input inside a card", "User chatbox"]; }; readonly widget: { readonly hex: "#262626"; readonly label: "Widget (rung 2)"; readonly useFor: "Widget / card surfaces sitting on the primary background. The rung most body text sits on, and what the text ramp is measured against."; readonly examples: readonly ["Cards", "Dashboard widgets", "Panels"]; }; readonly surface: { readonly hex: "#2E2E2E"; readonly label: "Surface (rung 3) — Pop-up / Filter / Side Panel / AI chat"; readonly useFor: "Elevated overlays and side panels that sit above content. From this rung up, quiet text must use `tertiary` rather than `subtle`."; readonly examples: readonly ["Dialogs and pop-ups", "Filter panels", "Side panels", "AI chat surface"]; }; readonly raised: { readonly hex: "#333333"; readonly label: "Raised (rung 4)"; readonly useFor: "Elements floating above an overlay, and portalled elements that cannot inherit a rung from the DOM."; readonly examples: readonly ["Tooltips", "Snackbars", "A menu opened from a popover"]; }; readonly overlay: { readonly hex: "#3A3A3A"; readonly label: "Overlay (rung 5, the ceiling)"; readonly useFor: "The top of the ladder. Reaching it means three levels of containment; a fourth is clamped here rather than continuing to lighten."; readonly examples: readonly ["A dialog opened from a dialog", "A menu inside a drawer"]; }; readonly tableDivider: { readonly hex: "#262626"; readonly label: "Table Divider"; readonly useFor: "The rule between table rows. Subtler than `border` so the outline still reads."; readonly examples: readonly ["Row separator"]; }; readonly darkGreen: { readonly hex: "#182724"; readonly label: "Dark Green"; readonly useFor: "Subtle success-tinted surface for positive emphasis."; readonly examples: readonly ["Success-tinted panels", "Attack-path success emphasis"]; }; }; readonly input: { /** * Field / secondary-button fill (dark). * * This is the fallback for a field with no surface context. A field inside a * scoped surface recesses one rung below its host instead, via * `var(--ds-field)` — that is what makes an input read as inset rather than * as a differently-coloured box. The one exception is a field directly on * `page`: there is no rung below it, so it goes transparent with a hairline. * * `page` rather than `primary`, because the unscoped case is a field on the * document body, and `palette.background.default` is `primary`. Setting this * to `primary` made the field exactly match the body and vanish, leaving only * its outline. This is also within a point of the historical `#1A1A1A`, so * the fallback path barely moves. * * Light mode uses `lightInputColors` — never fall back to this hex in light, * or fields render as black bars on a grey page. */ readonly background: "#191919"; /** Outline at rest. */ readonly border: "#2F2F2F"; /** Outline on hover. One step up, so the control acknowledges the pointer. */ readonly borderHover: "#3A3A3A"; /** * Outline on focus. * * Deliberately equal to `border`: focus is currently *not* indicated on * fields in dark. Both specs proposed a ring — neutral `#E8E8E8` in Tokens * §03, brand `#00C4AC` in Component System §05 — and neither was adopted for * dark. The token exists, and components reference it, so turning focus * indication on later is a change to this one value rather than a change to * every input component. * * Light mode's `lightInputColors.focusRing` is distinct (`#1A1A1A`), so focus * is visible there. Note the accessibility cost on dark while it stays here: * a keyboard user gets no visible focus indication on a field (WCAG 2.4.7). */ readonly focusRing: "#2F2F2F"; /** Outline for a field in the error state. */ readonly errorBorder: "#EB2D36"; }; readonly lightText: { readonly primary: "#1A1A1A"; readonly secondary: "#3D3D3D"; readonly tertiary: "#5C5C5C"; readonly subtle: "#6E6E6E"; readonly muted: "#8A8A8A"; readonly disabled: "#ABABAB"; readonly link: "#0B5FCC"; readonly highlight: "#00786B"; /** Ink on the inverted (dark) light-theme tooltip. */ readonly tooltip: "#F1F1F1"; }; readonly lightBackground: { readonly page: "#E9E9E9"; readonly primary: "#F1F1F1"; readonly widget: "#F8F8F8"; readonly surface: "#FCFCFC"; readonly raised: "#FFFFFF"; readonly overlay: "#FFFFFF"; /** Below the ladder — table rules and section edges that must read on white. */ readonly tableDivider: "#E2E2E2"; readonly borderSubtle: "#E2E2E2"; readonly border: "#D4D4D4"; readonly cardBorder: "#BDBDBD"; readonly tintGreen: "#E8F2EF"; }; readonly lightInput: { readonly background: "#E9E9E9"; readonly border: "#D4D4D4"; /** −8 L* on the line only; the fill never moves on hover. */ readonly borderHover: "#ADADAD"; /** Neutral focus ring — distinct from `border`, so focus is visible in light. */ readonly focusRing: "#1A1A1A"; /** Severity high, darkened to clear 3:1 against white. */ readonly errorBorder: "#C41B23"; }; readonly status: { readonly success: "#199F0F"; readonly failure: "#EB2D36"; readonly warning: "#E99C16"; readonly info: "#1266CC"; readonly general: "#00C4AC"; }; readonly severity: { readonly critical: "#c80009"; readonly high: "#EB2D36"; readonly medium: "#E99C16"; readonly low: "#D0C800"; readonly info: "#1266CC"; /** Not-yet-triaged / unknown severity. */ readonly untriaged: "#C0C0C0"; }; readonly severityText: { readonly critical: "#E68A8E"; readonly high: "#F38287"; readonly medium: "#E99C16"; readonly low: "#D0C800"; readonly info: "#76A6E1"; readonly untriaged: "#C0C0C0"; }; readonly icon: { readonly success: "#199F0F"; readonly failure: "#EB2D36"; readonly warning: "#E99C16"; readonly info: "#1266CC"; readonly general: "#00C4AC"; readonly tabIcon: "#888888"; readonly actionActive: "#FFFFFF"; readonly inactiveToggle: "#D3D3D3"; readonly activeTabIcon: "#00C4AC"; readonly alert: "#EF073A"; readonly vulnerability: "#EA5515"; readonly attackPathDefault: "#FFFFFF"; readonly attackPathSuccess: "#199F0F"; readonly attackPathFailure: "#EB2D36"; }; readonly neutral: { readonly 0: "#000000"; readonly 900: "#141414"; readonly 850: "#171717"; readonly 800: "#191919"; readonly 700: "#242424"; readonly 600: "#2B2B2B"; readonly 500: "#363636"; readonly 400: "#5E5E5E"; readonly 300: "#888888"; readonly 200: "#AEAEAE"; readonly 100: "#D3D3D3"; readonly 50: "#FDFDFD"; readonly white: "#FFFFFF"; }; readonly brand: { readonly main: "#00C4AC"; }; readonly brandTintByRung: { readonly page: "#152F2C"; readonly primary: "#1B3532"; readonly widget: "#223C39"; readonly surface: "#2A4441"; readonly raised: "#2F4946"; readonly overlay: "#334A47"; }; readonly scrim: { /** A menu or popover. Barely there — it exists to catch the outside click. */ readonly menu: 0.32; /** A drawer or dismissible overlay. */ readonly drawer: 0.5; /** A modal dialog, which owns the page while it is open. */ readonly dialog: 0.6; /** * A busy veil over content that is already open — the loading state inside a * popover or menu, not a scrim between layers. Named rather than inlined so * the two places that draw one cannot drift apart. */ readonly loading: 0.4; }; readonly stateAlpha: { readonly hover: 0.06; readonly selected: 0.12; readonly pressed: 0.16; readonly focus: 0.12; readonly disabled: 0.38; }; }; export type Colors = typeof colors; export type SeverityLevel = keyof typeof severityColors; export type StatusLevel = keyof typeof statusColors;