@odla-ai/brand 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/chunk-APLECQBR.js +1010 -0
- package/dist/chunk-APLECQBR.js.map +1 -0
- package/dist/index-bj73h3qo.d.cts +409 -0
- package/dist/index-bj73h3qo.d.ts +409 -0
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +3 -409
- package/dist/index.d.ts +3 -409
- package/dist/index.js +57 -957
- package/dist/index.js.map +1 -1
- package/dist/tokens/index.cjs +912 -0
- package/dist/tokens/index.cjs.map +1 -0
- package/dist/tokens/index.d.cts +1 -0
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.js +29 -0
- package/dist/tokens/index.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tokens/roles.ts","../src/color/hex.ts","../src/color/convert.ts","../src/color/contrast.ts","../src/color/harmony.ts","../src/color/ramp.ts","../src/color/delta.ts","../src/color/names.ts","../src/color/palette.ts","../src/tokens/map.ts","../src/tokens/dark.ts","../src/tokens/css.ts","../src/tokens/compile.ts"],"sourcesContent":["// The --ui-* custom-property names @odla-ai/brand emits, as data.\n//\n// PROVENANCE: mirrors @odla-ai/ui — the REQUIRED_TOKENS / ACCENT_TOKENS /\n// CHART_TOKENS / CHAT_TOKENS tiers in js/tokens.js, and the\n// :where([data-ui-accent]) re-declaration block in css/tokens.css.\n//\n// Why more than the required tier: a compiled token object doubles as a\n// runtime override-island payload, and CSS custom properties substitute\n// var() at computed-value time ON THE DECLARING ELEMENT. An island that\n// overrides --ui-accent alone would keep the :root-computed --ui-chart-1,\n// --ui-accent-glow, … stale. So besides the required tier the compiler\n// re-declares the whole accent-composing derived family — everything the\n// ui root's :where([data-ui-accent]) block re-declares, plus the remaining\n// chart series and chat roles so charts and chat surfaces recompute against\n// the brand palette too. test/tokens/map.test.ts cross-checks these lists\n// against the real @odla-ai/ui export, so they cannot drift silently.\n\n/**\n * The @odla-ai/ui required tier (js/tokens.js REQUIRED_TOKENS), in the ui\n * contract's order. The compiler always emits a concrete value for every\n * one of these.\n */\nexport const BRAND_REQUIRED_TOKENS = [\n \"--ui-bg\",\n \"--ui-surface\",\n \"--ui-surface-2\",\n \"--ui-text\",\n \"--ui-text-muted\",\n \"--ui-text-faint\",\n \"--ui-border\",\n \"--ui-border-strong\",\n \"--ui-accent\",\n \"--ui-accent-strong\",\n \"--ui-accent-soft\",\n \"--ui-on-accent\",\n \"--ui-good\",\n \"--ui-good-soft\",\n \"--ui-warn\",\n \"--ui-warn-soft\",\n \"--ui-danger\",\n \"--ui-danger-soft\",\n \"--ui-code-bg\",\n \"--ui-code-text\",\n \"--ui-shadow\",\n \"--ui-font-sans\",\n \"--ui-font-serif\",\n \"--ui-font-mono\",\n \"--ui-font-display\",\n] as const;\n\n/**\n * Accent-composing derived roles from the ui defaulted tier. All five\n * accent-family members of the :where([data-ui-accent]) re-declaration set\n * (--ui-accent-glow, --ui-accent-2, --ui-accent-2-soft, --ui-highlight,\n * --ui-focus) plus --ui-shadow-strong, which composes var(--ui-shadow) —\n * a token this compiler re-declares, so the island rule applies to it too.\n */\nexport const BRAND_DERIVED_TOKENS = [\n \"--ui-accent-glow\",\n \"--ui-accent-2\",\n \"--ui-accent-2-soft\",\n \"--ui-highlight\",\n \"--ui-focus\",\n \"--ui-shadow-strong\",\n] as const;\n\n/**\n * Chart roles the compiler emits: the six series slots (real palette\n * swatches when the palette carries them, ui var() compositions otherwise)\n * and the four accent-composing chart roles the :where([data-ui-accent])\n * block re-declares (band, band-strong, flow, glow).\n */\nexport const BRAND_CHART_TOKENS = [\n \"--ui-chart-1\",\n \"--ui-chart-2\",\n \"--ui-chart-3\",\n \"--ui-chart-4\",\n \"--ui-chart-5\",\n \"--ui-chart-6\",\n \"--ui-chart-band\",\n \"--ui-chart-band-strong\",\n \"--ui-chart-flow\",\n \"--ui-chart-glow\",\n] as const;\n\n/**\n * Chat surface roles (js/tokens.js CHAT_TOKENS, complete). Two are in the\n * accent-swap set (user-bg, tool-accent); the other four compose surface /\n * text tokens the compiler also re-declares, so an island stays coherent.\n */\nexport const BRAND_CHAT_TOKENS = [\n \"--ui-chat-user-bg\",\n \"--ui-chat-user-text\",\n \"--ui-chat-assistant-bg\",\n \"--ui-chat-thinking-bg\",\n \"--ui-chat-thinking-text\",\n \"--ui-chat-tool-accent\",\n] as const;\n\n/**\n * Every token the compiler emits, in emission order — also the deterministic\n * declaration order renderTokensCss uses, so golden CSS fixtures are stable.\n */\nexport const BRAND_EMITTED_TOKENS: readonly string[] = [\n ...BRAND_REQUIRED_TOKENS,\n ...BRAND_DERIVED_TOKENS,\n ...BRAND_CHART_TOKENS,\n ...BRAND_CHAT_TOKENS,\n];\n","// Hex color parsing and formatting.\n//\n// Accepts the CSS `#rgb` and `#rrggbb` forms (case-insensitive, surrounding\n// whitespace tolerated) and normalizes to lowercase `#rrggbb`. Alpha forms\n// (`#rgba` / `#rrggbbaa`) are rejected on purpose: the brand color engine\n// works in opaque colors only — translucency is expressed downstream by the\n// token compiler via `color-mix()` strings, never baked into swatches.\n//\n// Errors are plain RangeError so the color engine stays dependency-free;\n// the package's validate layer wraps them in BrandInputError where a typed\n// HTTP-facing error is needed.\n\n/** An sRGB color with channels as fractions in [0, 1]. */\nexport interface Rgb {\n /** Red channel, 0..1. */\n r: number;\n /** Green channel, 0..1. */\n g: number;\n /** Blue channel, 0..1. */\n b: number;\n}\n\n/** Clamps a number into [0, 1]; NaN clamps to 0. */\nexport function clamp01(x: number): number {\n return Number.isNaN(x) ? 0 : x < 0 ? 0 : x > 1 ? 1 : x;\n}\n\nconst HEX3 = /^#[0-9a-f]{3}$/;\nconst HEX6 = /^#[0-9a-f]{6}$/;\nconst HEX_ALPHA = /^#([0-9a-f]{4}|[0-9a-f]{8})$/;\n\n/**\n * Parses `#rgb` or `#rrggbb` (case-insensitive) into channel fractions.\n *\n * @throws RangeError for anything else — named colors, missing `#`, wrong\n * digit counts; alpha forms (`#rgba`/`#rrggbbaa`) get a dedicated message.\n */\nexport function parseHex(hex: string): Rgb {\n const s = hex.trim().toLowerCase();\n if (HEX_ALPHA.test(s)) {\n throw new RangeError(\n `parseHex: alpha hex \"${hex}\" is not supported — use an opaque \"#rrggbb\" value`,\n );\n }\n if (HEX3.test(s)) {\n return {\n r: parseInt(s[1]! + s[1]!, 16) / 255,\n g: parseInt(s[2]! + s[2]!, 16) / 255,\n b: parseInt(s[3]! + s[3]!, 16) / 255,\n };\n }\n if (HEX6.test(s)) {\n return {\n r: parseInt(s.slice(1, 3), 16) / 255,\n g: parseInt(s.slice(3, 5), 16) / 255,\n b: parseInt(s.slice(5, 7), 16) / 255,\n };\n }\n throw new RangeError(`parseHex: expected \"#rgb\" or \"#rrggbb\", got \"${hex}\"`);\n}\n\n/** Formats channel fractions as lowercase `#rrggbb`, clamping each into [0, 1]. */\nexport function toHex(rgb: Rgb): string {\n const ch = (c: number): string =>\n Math.round(clamp01(c) * 255)\n .toString(16)\n .padStart(2, \"0\");\n return `#${ch(rgb.r)}${ch(rgb.g)}${ch(rgb.b)}`;\n}\n\n/** Normalizes any accepted hex form to lowercase `#rrggbb` (throws like parseHex). */\nexport function normalizeHex(hex: string): string {\n return toHex(parseHex(hex));\n}\n","// Color space conversions: sRGB ↔ HSL, sRGB ↔ linear-light, sRGB ↔ OKLab,\n// OKLab ↔ OKLCH — plus an sRGB gamut clamp that reduces OKLCH chroma while\n// preserving lightness and hue.\n//\n// Algorithm sources:\n// - sRGB transfer function: IEC 61966-2-1 (electro-optical form: c ≤ 0.04045\n// → c/12.92, else ((c+0.055)/1.055)^2.4), as restated in CSS Color 4.\n// - HSL: CSS Color 4 § 7 (the hue-sextant algorithm inherited from CSS3\n// Color § 4.2.4).\n// - OKLab: Björn Ottosson, \"A perceptual color space for image processing\"\n// (bottosson.github.io/posts/oklab, 2020; sRGB matrices as updated\n// 2021-01-25): linear sRGB → LMS via M1, per-component cube root, → OKLab\n// via M2, and his published inverse matrices for the way back.\n// - OKLCH: CSS Color 4 § 9.2 — the polar form of OKLab (C = chroma radius,\n// h = hue angle in degrees).\n\nimport { clamp01, parseHex, toHex, type Rgb } from \"./hex\";\n\n/** A color in HSL: hue in degrees [0, 360), saturation and lightness 0..1. */\nexport interface Hsl {\n /** Hue angle in degrees, [0, 360); 0 when achromatic. */\n h: number;\n /** Saturation, 0..1. */\n s: number;\n /** Lightness, 0..1. */\n l: number;\n}\n\n/** A color in OKLab: L 0..1, a/b roughly within ±0.4 for sRGB colors. */\nexport interface Oklab {\n /** Perceived lightness, 0..1. */\n L: number;\n /** Green–red axis. */\n a: number;\n /** Blue–yellow axis. */\n b: number;\n}\n\n/** A color in OKLCH — OKLab in polar form. */\nexport interface Oklch {\n /** Perceived lightness, 0..1. */\n L: number;\n /** Chroma (radius in the a/b plane), ≥ 0. */\n C: number;\n /** Hue angle in degrees, [0, 360); 0 when achromatic. */\n h: number;\n}\n\n/** IEC 61966-2-1 sRGB decoding: gamma-encoded channel → linear-light, both 0..1. */\nexport function srgbToLinear(c: number): number {\n return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n}\n\n/** IEC 61966-2-1 sRGB encoding: linear-light channel → gamma-encoded, both 0..1. */\nexport function linearToSrgb(c: number): number {\n return c <= 0.0031308 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055;\n}\n\n/** Converts sRGB channel fractions to HSL (CSS Color 4 § 7). */\nexport function rgbToHsl({ r, g, b }: Rgb): Hsl {\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n const d = max - min;\n if (d === 0) return { h: 0, s: 0, l };\n const s = d / (1 - Math.abs(2 * l - 1));\n let h: number;\n if (max === r) h = 60 * (((g - b) / d) % 6);\n else if (max === g) h = 60 * ((b - r) / d + 2);\n else h = 60 * ((r - g) / d + 4);\n return { h: (h + 360) % 360, s, l };\n}\n\n/** Converts HSL to sRGB channel fractions (CSS Color 4 § 7; any hue angle accepted). */\nexport function hslToRgb({ h, s, l }: Hsl): Rgb {\n const hue = ((h % 360) + 360) % 360;\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));\n const m = l - c / 2;\n const sextants: ReadonlyArray<readonly [number, number, number]> = [\n [c, x, 0],\n [x, c, 0],\n [0, c, x],\n [0, x, c],\n [x, 0, c],\n [c, 0, x],\n ];\n const [r, g, b] = sextants[Math.floor(hue / 60) % 6]!;\n return { r: r + m, g: g + m, b: b + m };\n}\n\n/** Converts gamma-encoded sRGB to OKLab (Ottosson M1 → cbrt → M2). */\nexport function rgbToOklab({ r, g, b }: Rgb): Oklab {\n const R = srgbToLinear(r);\n const G = srgbToLinear(g);\n const B = srgbToLinear(b);\n const l = Math.cbrt(0.4122214708 * R + 0.5363325363 * G + 0.0514459929 * B);\n const m = Math.cbrt(0.2119034982 * R + 0.6806995451 * G + 0.1073969566 * B);\n const s = Math.cbrt(0.0883024619 * R + 0.2817188376 * G + 0.6299787005 * B);\n return {\n L: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,\n a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,\n b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,\n };\n}\n\n/**\n * Converts OKLab to gamma-encoded sRGB (Ottosson's inverse matrices).\n * Out-of-gamut inputs yield channels outside [0, 1] — feed the OKLCH form\n * through {@link clampToGamut} first when hue fidelity matters (toHex would\n * clip per channel, which distorts hue).\n */\nexport function oklabToRgb({ L, a, b }: Oklab): Rgb {\n const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;\n const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;\n const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;\n return {\n r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),\n g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),\n b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),\n };\n}\n\n/** OKLab → OKLCH (polar form). Hue is 0 for achromatic colors (C ≈ 0). */\nexport function oklabToOklch({ L, a, b }: Oklab): Oklch {\n const C = Math.hypot(a, b);\n const h = C < 1e-9 ? 0 : ((Math.atan2(b, a) * 180) / Math.PI + 360) % 360;\n return { L, C, h };\n}\n\n/** OKLCH → OKLab (rectangular form). */\nexport function oklchToOklab({ L, C, h }: Oklch): Oklab {\n const rad = (h * Math.PI) / 180;\n return { L, a: C * Math.cos(rad), b: C * Math.sin(rad) };\n}\n\n/** Convenience: hex → OKLCH (throws like parseHex on bad input). */\nexport function hexToOklch(hex: string): Oklch {\n return oklabToOklch(rgbToOklab(parseHex(hex)));\n}\n\n/**\n * Convenience: OKLCH → lowercase `#rrggbb`. Out-of-gamut input is clipped\n * per channel by toHex; call {@link clampToGamut} first to reduce chroma\n * instead (preserving hue) when the input may be out of gamut.\n */\nexport function oklchToHex(lch: Oklch): string {\n return toHex(oklabToRgb(oklchToOklab(lch)));\n}\n\nconst GAMUT_EPS = 1e-6;\n\n/** True when every sRGB channel is within [0, 1] (tiny epsilon for float noise). */\nexport function inSrgbGamut({ r, g, b }: Rgb): boolean {\n const ok = (c: number): boolean => c >= -GAMUT_EPS && c <= 1 + GAMUT_EPS;\n return ok(r) && ok(g) && ok(b);\n}\n\n/**\n * Brings an OKLCH color into the sRGB gamut by reducing chroma only —\n * lightness and hue are preserved exactly (binary search, 24 iterations,\n * chroma precision well under one 8-bit quantum). L is clamped into [0, 1]\n * first; C = 0 (a pure gray) is always representable, so the search always\n * lands in gamut.\n */\nexport function clampToGamut(lch: Oklch): Oklch {\n const L = clamp01(lch.L);\n const h = lch.h;\n if (inSrgbGamut(oklabToRgb(oklchToOklab({ L, C: lch.C, h })))) return { L, C: lch.C, h };\n let lo = 0;\n let hi = lch.C;\n for (let i = 0; i < 24; i++) {\n const mid = (lo + hi) / 2;\n if (inSrgbGamut(oklabToRgb(oklchToOklab({ L, C: mid, h })))) lo = mid;\n else hi = mid;\n }\n return { L, C: lo, h };\n}\n","// WCAG 2.1 contrast — relative luminance, contrast ratio, and AA/AAA checks\n// per technique G18 and success criteria 1.4.3 / 1.4.6 / 1.4.11.\n//\n// relativeLuminance: Y = 0.2126·R + 0.7152·G + 0.0722·B over linear-light\n// sRGB channels. Channels are linearized with the IEC 61966-2-1 constant\n// (0.04045) rather than WCAG's 0.03928; the two never disagree for 8-bit\n// channel values (no n/255 falls between them) and CSS Color 4 standardizes\n// on 0.04045.\n// contrastRatio: (L1 + 0.05) / (L2 + 0.05) with the lighter luminance on\n// top — white on black is exactly 21:1, a color against itself 1:1.\n\nimport { parseHex } from \"./hex\";\nimport { srgbToLinear } from \"./convert\";\n\n/** Contrast contexts with distinct WCAG thresholds. */\nexport type ContrastKind = \"text\" | \"large-text\" | \"ui\";\n\n/** WCAG 2.1 relative luminance of a hex color: 0 = black, 1 = white. */\nexport function relativeLuminance(hex: string): number {\n const { r, g, b } = parseHex(hex);\n return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);\n}\n\n/** WCAG 2.1 contrast ratio between two hex colors, in [1, 21]; order-independent. */\nexport function contrastRatio(hexA: string, hexB: string): number {\n const la = relativeLuminance(hexA);\n const lb = relativeLuminance(hexB);\n return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);\n}\n\nconst AA: Record<ContrastKind, number> = { text: 4.5, \"large-text\": 3, ui: 3 };\nconst AAA: Record<Exclude<ContrastKind, \"ui\">, number> = { text: 7, \"large-text\": 4.5 };\n\n/**\n * WCAG 2.1 level-AA check for a contrast ratio: 4.5:1 for body text\n * (SC 1.4.3), 3:1 for large text, 3:1 for UI components and graphical\n * objects (SC 1.4.11).\n */\nexport function meetsAA(ratio: number, kind: ContrastKind = \"text\"): boolean {\n return ratio >= AA[kind];\n}\n\n/**\n * WCAG 2.1 level-AAA check for a contrast ratio (SC 1.4.6): 7:1 for body\n * text, 4.5:1 for large text. WCAG defines no AAA bar for non-text UI, so\n * \"ui\" is not an accepted kind here.\n */\nexport function meetsAAA(ratio: number, kind: Exclude<ContrastKind, \"ui\"> = \"text\"): boolean {\n return ratio >= AAA[kind];\n}\n\n/**\n * Default {@link pickTextOn} candidates: white and a near-black chosen so\n * that the better of the two clears 4.5:1 against ANY background. The worst\n * case is a mid-gray backdrop (luminance ≈ 0.18), where this pair still\n * yields ≈ 4.51:1; a softer near-black (e.g. #111111) would drop that floor\n * below 4.5.\n */\nexport const PICK_TEXT_DEFAULT_CANDIDATES: readonly string[] = [\"#ffffff\", \"#050505\"];\n\n/**\n * Picks the candidate with the highest contrast ratio against `bgHex`\n * (earliest candidate wins ties). With the default candidates the winner is\n * always ≥ 4.5:1 — see {@link PICK_TEXT_DEFAULT_CANDIDATES}.\n *\n * @throws RangeError when `candidates` is empty.\n */\nexport function pickTextOn(\n bgHex: string,\n candidates: readonly string[] = PICK_TEXT_DEFAULT_CANDIDATES,\n): string {\n if (candidates.length === 0) throw new RangeError(\"pickTextOn: candidates must be non-empty\");\n let best = candidates[0]!;\n let bestRatio = -1;\n for (const candidate of candidates) {\n const ratio = contrastRatio(bgHex, candidate);\n if (ratio > bestRatio) {\n best = candidate;\n bestRatio = ratio;\n }\n }\n return best;\n}\n","// Classic color-harmony companions, computed as OKLCH hue rotations at\n// constant lightness and chroma so companions keep the seed's perceived\n// lightness and vividness (the same rotations in HSL shift both wildly —\n// compare HSL yellow vs blue at \"50%\"). Results are chroma-clamped into the\n// sRGB gamut, which preserves hue and lightness exactly.\n//\n// Angles follow the traditional color-wheel definitions: complementary 180°,\n// analogous ±30°, triadic ±120°, split-complementary ±150°, tetradic\n// (square) +90°/180°/270°. Hue math per CSS Color 4 § 9.2 OKLCH.\n\nimport { clampToGamut, hexToOklch, oklchToHex } from \"./convert\";\n\n/**\n * Rotates a color's OKLCH hue by `degrees` (any sign/magnitude), keeping\n * lightness and chroma constant, then chroma-clamps into sRGB. Rotating an\n * achromatic color is a no-op up to 8-bit rounding (its chroma is ~0).\n */\nexport function rotateHue(hex: string, degrees: number): string {\n const { L, C, h } = hexToOklch(hex);\n return oklchToHex(clampToGamut({ L, C, h: (((h + degrees) % 360) + 360) % 360 }));\n}\n\n/** The 180° opposite on the OKLCH hue wheel. */\nexport function complementary(hex: string): string {\n return rotateHue(hex, 180);\n}\n\n/** The two neighbors at ±`angle` (default 30°), as [minus, plus]. */\nexport function analogous(hex: string, angle = 30): [string, string] {\n return [rotateHue(hex, -angle), rotateHue(hex, angle)];\n}\n\n/** The two colors completing an equilateral triad (±120°), as [minus, plus]. */\nexport function triadic(hex: string): [string, string] {\n return [rotateHue(hex, -120), rotateHue(hex, 120)];\n}\n\n/** The two colors flanking the complement (±150°), as [minus, plus]. */\nexport function splitComplementary(hex: string): [string, string] {\n return [rotateHue(hex, -150), rotateHue(hex, 150)];\n}\n\n/** The three colors completing a square (+90°, +180°, +270°). */\nexport function tetradic(hex: string): [string, string, string] {\n return [rotateHue(hex, 90), rotateHue(hex, 180), rotateHue(hex, 270)];\n}\n\n/**\n * A monochrome chroma scale: `steps` colors at the seed's lightness and hue,\n * fading chroma linearly from the seed's own chroma down to 0 (a pure gray).\n * The first entry is the seed itself (normalized).\n *\n * @throws RangeError when `steps` is not an integer ≥ 2.\n */\nexport function monochrome(hex: string, steps = 5): string[] {\n if (!Number.isInteger(steps) || steps < 2) {\n throw new RangeError(`monochrome: steps must be an integer >= 2, got ${steps}`);\n }\n const { L, C, h } = hexToOklch(hex);\n const out: string[] = [];\n for (let i = 0; i < steps; i++) {\n out.push(oklchToHex(clampToGamut({ L, C: C * (1 - i / (steps - 1)), h })));\n }\n return out;\n}\n","// Tint/shade ramps and predicate-driven lightness adjustment, both in OKLCH\n// so steps are perceptually even (equal OKLab ΔL per step; OKLab per Björn\n// Ottosson — see convert.ts for the matrices and citations).\n//\n// tintShadeRamp walks lightness from RAMP_L_MAX down to RAMP_L_MIN with a\n// chroma taper: full chroma mid-ramp, fading toward both ends. Very light\n// tints and very dark shades can't hold much chroma in sRGB anyway, and a\n// deliberate taper reads better than the hard cut the gamut clamp would\n// otherwise apply.\n//\n// adjustLightnessUntil binary-searches lightness (constant chroma and hue,\n// gamut-clamped) for the value nearest the input that satisfies a caller\n// predicate — the workhorse behind \"darken this accent until it clears\n// 4.5:1 on the page background\".\n\nimport { normalizeHex } from \"./hex\";\nimport { clampToGamut, hexToOklch, oklchToHex } from \"./convert\";\n\n/** Lightest OKLab L emitted by {@link tintShadeRamp} (first step). */\nexport const RAMP_L_MAX = 0.96;\n\n/** Darkest OKLab L emitted by {@link tintShadeRamp} (last step). */\nexport const RAMP_L_MIN = 0.27;\n\n/** Fraction of the seed's chroma kept at the ramp's endpoints. */\nconst CHROMA_FLOOR = 0.25;\n\n/**\n * A light-to-dark ramp of `steps` colors (default 9) built from the seed's\n * hue and chroma: OKLab lightness runs linearly from {@link RAMP_L_MAX} down\n * to {@link RAMP_L_MIN}, and chroma tapers from 100% of the seed's chroma at\n * mid-ramp to 25% at both ends (then gamut-clamps, preserving hue).\n *\n * @throws RangeError when `steps` is not an integer ≥ 2.\n */\nexport function tintShadeRamp(hex: string, steps = 9): string[] {\n if (!Number.isInteger(steps) || steps < 2) {\n throw new RangeError(`tintShadeRamp: steps must be an integer >= 2, got ${steps}`);\n }\n const { C, h } = hexToOklch(hex);\n const out: string[] = [];\n for (let i = 0; i < steps; i++) {\n const t = i / (steps - 1);\n const L = RAMP_L_MAX + (RAMP_L_MIN - RAMP_L_MAX) * t;\n const taper = CHROMA_FLOOR + (1 - CHROMA_FLOOR) * (1 - Math.abs(2 * t - 1));\n out.push(oklchToHex(clampToGamut({ L, C: C * taper, h })));\n }\n return out;\n}\n\n/** Direction {@link adjustLightnessUntil} may move lightness. */\nexport type LightnessDirection = \"lighten\" | \"darken\";\n\nconst SCAN_STEPS = 16;\nconst BISECT_STEPS = 22;\n\n/**\n * Moves a color's OKLCH lightness toward white (\"lighten\") or black\n * (\"darken\") until `predicate(hex)` holds, returning the passing color\n * nearest the input (chroma and hue constant, gamut-clamped per step).\n *\n * Returns the input itself (normalized) when it already passes, and null\n * when no lightness in that direction satisfies the predicate. Work is\n * bounded: a 16-step coarse scan plus 22 bisection rounds. The predicate is\n * always evaluated on exact 8-bit hex strings, including the returned one.\n */\nexport function adjustLightnessUntil(\n hex: string,\n predicate: (hex: string) => boolean,\n direction: LightnessDirection,\n): string | null {\n const start = normalizeHex(hex);\n if (predicate(start)) return start;\n const { L, C, h } = hexToOklch(start);\n const bound = direction === \"lighten\" ? 1 : 0;\n const at = (l: number): string => oklchToHex(clampToGamut({ L: l, C, h }));\n let lastFail = L;\n let firstPass = Number.NaN;\n for (let i = 1; i <= SCAN_STEPS; i++) {\n const l = L + ((bound - L) * i) / SCAN_STEPS;\n if (predicate(at(l))) {\n firstPass = l;\n break;\n }\n lastFail = l;\n }\n if (Number.isNaN(firstPass)) return null;\n for (let i = 0; i < BISECT_STEPS; i++) {\n const mid = (lastFail + firstPass) / 2;\n if (predicate(at(mid))) firstPass = mid;\n else lastFail = mid;\n }\n return at(firstPass);\n}\n","// ΔEOK — the OKLab color-difference metric adopted by CSS Color 4 (§ \"the\n// deltaEOK function\"): plain Euclidean distance in OKLab coordinates.\n// Scale intuition: ~0.02 is roughly one just-noticeable difference, and\n// white ↔ black is exactly 1.\n\nimport { parseHex } from \"./hex\";\nimport { rgbToOklab, type Oklab } from \"./convert\";\n\n/** Euclidean distance between two OKLab coordinates (CSS Color 4 ΔEOK). */\nexport function deltaEOKLab(x: Oklab, y: Oklab): number {\n return Math.hypot(x.L - y.L, x.a - y.a, x.b - y.b);\n}\n\n/**\n * ΔEOK between two hex colors. 0 = identical; ~0.02 ≈ one just-noticeable\n * difference; white ↔ black = 1. Symmetric in its arguments.\n *\n * @throws RangeError on malformed hex (see parseHex).\n */\nexport function deltaEOK(hexA: string, hexB: string): number {\n return deltaEOKLab(rgbToOklab(parseHex(hexA)), rgbToOklab(parseHex(hexB)));\n}\n","// The 148 CSS named colors — CSS Color 4 § 6.1 \"Named colors\" (the CSS3\n// keyword list plus rebeccapurple), with the spec's exact hex values —\n// and nearest-name lookup by ΔEOK (CSS Color 4's OKLab distance metric,\n// see delta.ts). Alphabetical order; gray/grey-style aliases are separate\n// entries sharing one hex.\n\nimport { parseHex } from \"./hex\";\nimport { rgbToOklab, type Oklab } from \"./convert\";\nimport { deltaEOKLab } from \"./delta\";\n\n/** One CSS named color: the keyword and its spec hex value. */\nexport interface NamedColor {\n /** The CSS keyword, e.g. \"rebeccapurple\". */\n name: string;\n /** The spec's `#rrggbb` value, lowercase. */\n hex: string;\n}\n\nconst TABLE: ReadonlyArray<readonly [string, string]> = [\n [\"aliceblue\", \"#f0f8ff\"],\n [\"antiquewhite\", \"#faebd7\"],\n [\"aqua\", \"#00ffff\"],\n [\"aquamarine\", \"#7fffd4\"],\n [\"azure\", \"#f0ffff\"],\n [\"beige\", \"#f5f5dc\"],\n [\"bisque\", \"#ffe4c4\"],\n [\"black\", \"#000000\"],\n [\"blanchedalmond\", \"#ffebcd\"],\n [\"blue\", \"#0000ff\"],\n [\"blueviolet\", \"#8a2be2\"],\n [\"brown\", \"#a52a2a\"],\n [\"burlywood\", \"#deb887\"],\n [\"cadetblue\", \"#5f9ea0\"],\n [\"chartreuse\", \"#7fff00\"],\n [\"chocolate\", \"#d2691e\"],\n [\"coral\", \"#ff7f50\"],\n [\"cornflowerblue\", \"#6495ed\"],\n [\"cornsilk\", \"#fff8dc\"],\n [\"crimson\", \"#dc143c\"],\n [\"cyan\", \"#00ffff\"],\n [\"darkblue\", \"#00008b\"],\n [\"darkcyan\", \"#008b8b\"],\n [\"darkgoldenrod\", \"#b8860b\"],\n [\"darkgray\", \"#a9a9a9\"],\n [\"darkgreen\", \"#006400\"],\n [\"darkgrey\", \"#a9a9a9\"],\n [\"darkkhaki\", \"#bdb76b\"],\n [\"darkmagenta\", \"#8b008b\"],\n [\"darkolivegreen\", \"#556b2f\"],\n [\"darkorange\", \"#ff8c00\"],\n [\"darkorchid\", \"#9932cc\"],\n [\"darkred\", \"#8b0000\"],\n [\"darksalmon\", \"#e9967a\"],\n [\"darkseagreen\", \"#8fbc8f\"],\n [\"darkslateblue\", \"#483d8b\"],\n [\"darkslategray\", \"#2f4f4f\"],\n [\"darkslategrey\", \"#2f4f4f\"],\n [\"darkturquoise\", \"#00ced1\"],\n [\"darkviolet\", \"#9400d3\"],\n [\"deeppink\", \"#ff1493\"],\n [\"deepskyblue\", \"#00bfff\"],\n [\"dimgray\", \"#696969\"],\n [\"dimgrey\", \"#696969\"],\n [\"dodgerblue\", \"#1e90ff\"],\n [\"firebrick\", \"#b22222\"],\n [\"floralwhite\", \"#fffaf0\"],\n [\"forestgreen\", \"#228b22\"],\n [\"fuchsia\", \"#ff00ff\"],\n [\"gainsboro\", \"#dcdcdc\"],\n [\"ghostwhite\", \"#f8f8ff\"],\n [\"gold\", \"#ffd700\"],\n [\"goldenrod\", \"#daa520\"],\n [\"gray\", \"#808080\"],\n [\"green\", \"#008000\"],\n [\"greenyellow\", \"#adff2f\"],\n [\"grey\", \"#808080\"],\n [\"honeydew\", \"#f0fff0\"],\n [\"hotpink\", \"#ff69b4\"],\n [\"indianred\", \"#cd5c5c\"],\n [\"indigo\", \"#4b0082\"],\n [\"ivory\", \"#fffff0\"],\n [\"khaki\", \"#f0e68c\"],\n [\"lavender\", \"#e6e6fa\"],\n [\"lavenderblush\", \"#fff0f5\"],\n [\"lawngreen\", \"#7cfc00\"],\n [\"lemonchiffon\", \"#fffacd\"],\n [\"lightblue\", \"#add8e6\"],\n [\"lightcoral\", \"#f08080\"],\n [\"lightcyan\", \"#e0ffff\"],\n [\"lightgoldenrodyellow\", \"#fafad2\"],\n [\"lightgray\", \"#d3d3d3\"],\n [\"lightgreen\", \"#90ee90\"],\n [\"lightgrey\", \"#d3d3d3\"],\n [\"lightpink\", \"#ffb6c1\"],\n [\"lightsalmon\", \"#ffa07a\"],\n [\"lightseagreen\", \"#20b2aa\"],\n [\"lightskyblue\", \"#87cefa\"],\n [\"lightslategray\", \"#778899\"],\n [\"lightslategrey\", \"#778899\"],\n [\"lightsteelblue\", \"#b0c4de\"],\n [\"lightyellow\", \"#ffffe0\"],\n [\"lime\", \"#00ff00\"],\n [\"limegreen\", \"#32cd32\"],\n [\"linen\", \"#faf0e6\"],\n [\"magenta\", \"#ff00ff\"],\n [\"maroon\", \"#800000\"],\n [\"mediumaquamarine\", \"#66cdaa\"],\n [\"mediumblue\", \"#0000cd\"],\n [\"mediumorchid\", \"#ba55d3\"],\n [\"mediumpurple\", \"#9370db\"],\n [\"mediumseagreen\", \"#3cb371\"],\n [\"mediumslateblue\", \"#7b68ee\"],\n [\"mediumspringgreen\", \"#00fa9a\"],\n [\"mediumturquoise\", \"#48d1cc\"],\n [\"mediumvioletred\", \"#c71585\"],\n [\"midnightblue\", \"#191970\"],\n [\"mintcream\", \"#f5fffa\"],\n [\"mistyrose\", \"#ffe4e1\"],\n [\"moccasin\", \"#ffe4b5\"],\n [\"navajowhite\", \"#ffdead\"],\n [\"navy\", \"#000080\"],\n [\"oldlace\", \"#fdf5e6\"],\n [\"olive\", \"#808000\"],\n [\"olivedrab\", \"#6b8e23\"],\n [\"orange\", \"#ffa500\"],\n [\"orangered\", \"#ff4500\"],\n [\"orchid\", \"#da70d6\"],\n [\"palegoldenrod\", \"#eee8aa\"],\n [\"palegreen\", \"#98fb98\"],\n [\"paleturquoise\", \"#afeeee\"],\n [\"palevioletred\", \"#db7093\"],\n [\"papayawhip\", \"#ffefd5\"],\n [\"peachpuff\", \"#ffdab9\"],\n [\"peru\", \"#cd853f\"],\n [\"pink\", \"#ffc0cb\"],\n [\"plum\", \"#dda0dd\"],\n [\"powderblue\", \"#b0e0e6\"],\n [\"purple\", \"#800080\"],\n [\"rebeccapurple\", \"#663399\"],\n [\"red\", \"#ff0000\"],\n [\"rosybrown\", \"#bc8f8f\"],\n [\"royalblue\", \"#4169e1\"],\n [\"saddlebrown\", \"#8b4513\"],\n [\"salmon\", \"#fa8072\"],\n [\"sandybrown\", \"#f4a460\"],\n [\"seagreen\", \"#2e8b57\"],\n [\"seashell\", \"#fff5ee\"],\n [\"sienna\", \"#a0522d\"],\n [\"silver\", \"#c0c0c0\"],\n [\"skyblue\", \"#87ceeb\"],\n [\"slateblue\", \"#6a5acd\"],\n [\"slategray\", \"#708090\"],\n [\"slategrey\", \"#708090\"],\n [\"snow\", \"#fffafa\"],\n [\"springgreen\", \"#00ff7f\"],\n [\"steelblue\", \"#4682b4\"],\n [\"tan\", \"#d2b48c\"],\n [\"teal\", \"#008080\"],\n [\"thistle\", \"#d8bfd8\"],\n [\"tomato\", \"#ff6347\"],\n [\"turquoise\", \"#40e0d0\"],\n [\"violet\", \"#ee82ee\"],\n [\"wheat\", \"#f5deb3\"],\n [\"white\", \"#ffffff\"],\n [\"whitesmoke\", \"#f5f5f5\"],\n [\"yellow\", \"#ffff00\"],\n [\"yellowgreen\", \"#9acd32\"],\n];\n\n/**\n * All 148 CSS named colors in alphabetical order. Aliases (aqua/cyan,\n * fuchsia/magenta, the gray/grey pairs) are separate entries sharing a hex.\n */\nexport const CSS_NAMED_COLORS: readonly NamedColor[] = TABLE.map(([name, hex]) => ({\n name,\n hex,\n}));\n\n/** A nearest-name match: the keyword, its spec hex, and the ΔEOK distance. */\nexport interface NearestNamedColor extends NamedColor {\n /** ΔEOK from the query color to this named color (0 = exact hit). */\n deltaEOK: number;\n}\n\nlet labCache: Oklab[] | null = null;\n\n/**\n * The CSS named color perceptually closest to `hex`, by ΔEOK in OKLab.\n * Exact hits return distance 0. Ties — including shared-hex aliases like\n * aqua/cyan — go to the alphabetically first keyword. As a rough guide,\n * distances ≲ 0.02 are visually indistinguishable and ≳ 0.1 is only a loose\n * \"same family\" match.\n *\n * @throws RangeError on malformed hex (see parseHex).\n */\nexport function nearestNamedColor(hex: string): NearestNamedColor {\n const target = rgbToOklab(parseHex(hex));\n labCache ??= TABLE.map(([, value]) => rgbToOklab(parseHex(value)));\n let bestIdx = 0;\n let bestD = Infinity;\n for (let i = 0; i < labCache.length; i++) {\n const d = deltaEOKLab(target, labCache[i]!);\n if (d < bestD) {\n bestD = d;\n bestIdx = i;\n }\n }\n const [name, value] = TABLE[bestIdx]!;\n return { name, hex: value, deltaEOK: bestD };\n}\n","// Full-palette derivation from a single seed color. All deterministic —\n// no randomness, no clock — so the same seed always yields the same\n// palette (proposals are reviewable and reproducible).\n//\n// Strategy:\n// - primary: the seed itself (normalized).\n// - secondary / highlight: classic harmony rotations of the seed in OKLCH\n// (complementary 180°, triadic ±120°, split-complementary ±150°).\n// secondary maximizes ΔEOK from the primary; highlight maximizes the\n// minimum ΔEOK to both, so the three accents spread apart. Near-achromatic\n// seeds (C < 0.02) have no usable hue, so rotations run from a synthetic\n// chromatic anchor at ANCHOR_HUE instead (a gray seed still deserves\n// real accents).\n// - good / warn / danger: hue-anchored at 145° / 85° / 25° OKLCH (green /\n// amber / red), darkened via adjustLightnessUntil until they clear WCAG's\n// 3:1 non-text bar (SC 1.4.11) against the derived background.\n// - chart set: hue rotations at a constant, gamut-safe (CHART_L, CHART_C)\n// chosen so every hue fits sRGB without chroma clamping. Adjacent 60°\n// hues then sit 2·C·sin(30°) = C apart in OKLab, so with C = 0.115 the\n// pairwise ΔEOK floor CHART_DELTA_MIN = 0.1 holds by construction.\n// - neutrals bg / surface / neutral / text: a near-achromatic ladder tinted\n// with the seed hue (C ≤ 0.012), L 0.985 → 0.24; text vs bg lands well\n// past 7:1 (AAA).\n\nimport type { Swatch, SwatchRole } from \"../types\";\nimport { normalizeHex } from \"./hex\";\nimport { clampToGamut, hexToOklch, oklchToHex } from \"./convert\";\nimport { contrastRatio } from \"./contrast\";\nimport { rotateHue } from \"./harmony\";\nimport { adjustLightnessUntil } from \"./ramp\";\nimport { deltaEOK } from \"./delta\";\nimport { nearestNamedColor } from \"./names\";\n\n/**\n * Documented floor for pairwise ΔEOK between derived chart colors. The\n * default chart constants guarantee it by construction (see file header);\n * deriveChartColors also enforces it as a filter.\n */\nexport const CHART_DELTA_MIN = 0.1;\n\n/** Chart lightness: chosen with CHART_C so all hues are in-gamut (min max-chroma over hues ≈ 0.119 at L = 0.7). */\nconst CHART_L = 0.7;\n/** Chart chroma: gamut-safe at CHART_L for every hue, so no clamping ever shifts a chart color. */\nconst CHART_C = 0.115;\n/** Hue used when a seed is too gray to have one (a mid blue). */\nconst ANCHOR_HUE = 250;\n/** Below this OKLCH chroma a seed is treated as achromatic. */\nconst ACHROMATIC_C = 0.02;\n/** Status swatch anchors (OKLCH hue degrees): green / amber / red. */\nconst STATUS_HUES = { good: 145, warn: 85, danger: 25 } as const;\nconst STATUS_L = 0.7;\nconst STATUS_C = 0.14;\n/** Harmony rotations tried for secondary/highlight, in tie-break order. */\nconst ROTATIONS = [180, 120, -120, 150, -150] as const;\n\n/** Options for {@link deriveChartColors}. */\nexport interface DeriveChartOptions {\n /** How many colors to return, 1..12 (default 6). */\n count?: number;\n /** Pairwise ΔEOK floor (default {@link CHART_DELTA_MIN}). Best-effort above the default. */\n deltaMin?: number;\n}\n\n/**\n * Derives categorical chart colors from a seed: hue rotations (60° grid,\n * then 30° offsets) around the seed's hue at a constant, gamut-safe\n * lightness/chroma. Candidates are kept only if they stay at least\n * `deltaMin` ΔEOK from every accepted color; if the requested floor is\n * unattainable for `count` colors, remaining distinct candidates fill the\n * set in hue order so callers always get `count` colors back.\n *\n * @throws RangeError when `count` is not an integer in 1..12, or on\n * malformed hex.\n */\nexport function deriveChartColors(seedHex: string, opts: DeriveChartOptions = {}): string[] {\n const count = opts.count ?? 6;\n const deltaMin = opts.deltaMin ?? CHART_DELTA_MIN;\n if (!Number.isInteger(count) || count < 1 || count > 12) {\n throw new RangeError(`deriveChartColors: count must be an integer in 1..12, got ${count}`);\n }\n const lch = hexToOklch(normalizeHex(seedHex));\n const base = lch.C < ACHROMATIC_C ? ANCHOR_HUE : lch.h;\n const offsets = [0, 60, 120, 180, 240, 300, 30, 90, 150, 210, 270, 330];\n const candidates = offsets.map((deg) =>\n oklchToHex(clampToGamut({ L: CHART_L, C: CHART_C, h: (base + deg) % 360 })),\n );\n const picked: string[] = [];\n for (const hex of candidates) {\n if (picked.length >= count) break;\n if (picked.includes(hex)) continue;\n if (picked.every((p) => deltaEOK(p, hex) >= deltaMin)) picked.push(hex);\n }\n for (const hex of candidates) {\n if (picked.length >= count) break;\n if (!picked.includes(hex)) picked.push(hex);\n }\n return picked;\n}\n\n/** Options for {@link derivePalette}. */\nexport interface DerivePaletteOptions {\n /** Attach nearest-CSS-keyword `name`s to every swatch (default true). */\n names?: boolean;\n}\n\n/**\n * Derives a complete brand palette from one seed color: one swatch each for\n * primary, secondary, highlight, good, warn, danger, bg, surface, text and\n * neutral, plus six chart swatches (16 total) — see the file header for the\n * derivation strategy and its WCAG guarantees.\n *\n * @throws RangeError on malformed seed hex (see parseHex).\n */\nexport function derivePalette(seedHex: string, opts: DerivePaletteOptions = {}): Swatch[] {\n const withNames = opts.names !== false;\n const seed = normalizeHex(seedHex);\n const seedLch = hexToOklch(seed);\n const achromatic = seedLch.C < ACHROMATIC_C;\n const hue = achromatic ? ANCHOR_HUE : seedLch.h;\n\n const neutralAt = (L: number, C: number): string => oklchToHex(clampToGamut({ L, C, h: hue }));\n const bg = neutralAt(0.985, 0.005);\n const surface = neutralAt(0.955, 0.008);\n const text = neutralAt(0.24, 0.012);\n const neutral = neutralAt(0.62, 0.012);\n\n const accentBase = achromatic\n ? oklchToHex(\n clampToGamut({ L: Math.min(Math.max(seedLch.L, 0.45), 0.75), C: CHART_C, h: ANCHOR_HUE }),\n )\n : seed;\n const cands = ROTATIONS.map((deg) => {\n const hex = rotateHue(accentBase, deg);\n return { deg, hex, d: deltaEOK(seed, hex) };\n });\n let secondary = cands[0]!;\n for (const c of cands) if (c.d > secondary.d) secondary = c;\n let highlight = cands[0] === secondary ? cands[1]! : cands[0]!;\n let hiScore = Math.min(highlight.d, deltaEOK(highlight.hex, secondary.hex));\n for (const c of cands) {\n if (c === secondary || c === highlight) continue;\n const score = Math.min(c.d, deltaEOK(c.hex, secondary.hex));\n if (score > hiScore) {\n highlight = c;\n hiScore = score;\n }\n }\n\n const status = (anchor: number): string => {\n const start = oklchToHex(clampToGamut({ L: STATUS_L, C: STATUS_C, h: anchor }));\n return adjustLightnessUntil(start, (c) => contrastRatio(c, bg) >= 3, \"darken\") ?? text;\n };\n\n const sw = (role: SwatchRole, hex: string, rationale: string): Swatch => ({\n role,\n hex,\n rationale,\n });\n const swatches: Swatch[] = [\n sw(\"primary\", seed, \"seed color\"),\n sw(\"secondary\", secondary.hex, `harmony rotation ${secondary.deg}°, ΔEOK ${secondary.d.toFixed(3)} from primary`),\n sw(\"highlight\", highlight.hex, `harmony rotation ${highlight.deg}°, spread from primary and secondary`),\n sw(\"good\", status(STATUS_HUES.good), \"green anchor 145°, ≥3:1 on bg\"),\n sw(\"warn\", status(STATUS_HUES.warn), \"amber anchor 85°, ≥3:1 on bg\"),\n sw(\"danger\", status(STATUS_HUES.danger), \"red anchor 25°, ≥3:1 on bg\"),\n sw(\"bg\", bg, \"near-white tinted with the seed hue\"),\n sw(\"surface\", surface, \"raised surface, one step below bg\"),\n sw(\"text\", text, \"near-black tinted with the seed hue\"),\n sw(\"neutral\", neutral, \"mid neutral for borders and muted marks\"),\n ...deriveChartColors(seed).map((hex, i) => sw(\"chart\", hex, `chart series ${i + 1}`)),\n ];\n return withNames\n ? swatches.map((s) => ({ ...s, name: nearestNamedColor(s.hex).name }))\n : swatches;\n}\n","// Palette → light-mode @odla-ai/ui tokens.\n//\n// Every name in BRAND_EMITTED_TOKENS is always emitted. Real swatches map\n// directly; missing roles fall back to derivePalette() output seeded from\n// the accent (with a warning); derived/composed roles use the EXACT\n// var()/color-mix() composition strings @odla-ai/ui css/tokens.css uses as\n// defaults, so the object works as a scoped override-island payload (the\n// compositions recompute at the declaring element — see roles.ts).\n//\n// Contrast guarantees (WCAG 2.1, enforced with adjustLightnessUntil and\n// recorded as warnings with `adjustedFrom` whenever a color had to move):\n// --ui-text ≥ 4.5:1 and --ui-text-muted ≥ 4.5:1 on --ui-bg (SC 1.4.3),\n// --ui-accent-strong ≥ 4.5:1 on --ui-bg (darkened on a light bg),\n// --ui-good/warn/danger ≥ 3:1 on --ui-bg (SC 1.4.11), and --ui-on-accent\n// via pickTextOn (≥ 4.5:1 with the default white/near-black candidates).\n\nimport type { Swatch, SwatchRole } from \"../types\";\nimport {\n adjustLightnessUntil,\n clamp01,\n clampToGamut,\n contrastRatio,\n deriveChartColors,\n derivePalette,\n hexToOklch,\n normalizeHex,\n oklchToHex,\n parseHex,\n pickTextOn,\n relativeLuminance,\n type LightnessDirection,\n} from \"../color\";\nimport type { BrandTokens, CompileInput, TokenWarning } from \"./types\";\n\n/**\n * Seed used when the palette has no usable chromatic swatch at all — the\n * @odla-ai/ui neutral default accent (css/tokens.css restrained blue).\n */\nexport const DEFAULT_ACCENT_SEED = \"#3b5e8c\";\n\n/** Below this OKLCH chroma a swatch is too gray for accent/chart duty. */\nconst CHROMATIC_C = 0.02;\n\n// System font stacks, verbatim from @odla-ai/ui css/tokens.css.\nconst SANS_STACK = `ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif`;\nconst SERIF_STACK = `\"Iowan Old Style\", \"Palatino Linotype\", Palatino, Georgia, serif`;\nconst MONO_STACK = `ui-monospace, \"SF Mono\", Menlo, Consolas, monospace`;\nconst DISPLAY_STACK = `ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif`;\n\n// ui css/tokens.css chart defaults — emitted when the palette lacks ≥3\n// chromatic chart swatches, so an override island never keeps root values.\nconst UI_CHART_DEFAULTS = [\n \"var(--ui-accent)\",\n \"var(--ui-good)\",\n \"var(--ui-warn)\",\n \"var(--ui-danger)\",\n \"color-mix(in srgb, var(--ui-accent) 45%, var(--ui-text))\",\n \"var(--ui-text-faint)\",\n] as const;\n\n/** The token a missing swatch role is reported against. */\nconst ROLE_TOKEN: Partial<Record<SwatchRole, string>> = {\n primary: \"--ui-accent\",\n secondary: \"--ui-accent-2\",\n highlight: \"--ui-highlight\",\n bg: \"--ui-bg\",\n surface: \"--ui-surface\",\n text: \"--ui-text\",\n neutral: \"--ui-border-strong\",\n good: \"--ui-good\",\n warn: \"--ui-warn\",\n danger: \"--ui-danger\",\n chart: \"--ui-chart-1\",\n};\n\nconst mixVar = (name: string, pct: number): string =>\n `color-mix(in srgb, var(${name}) ${pct}%, transparent)`;\n\n/** Shift OKLab lightness by dL, constant chroma/hue, gamut-clamped. */\nfunction shiftL(hex: string, dL: number): string {\n const { L, C, h } = hexToOklch(hex);\n return oklchToHex(clampToGamut({ L: clamp01(L + dL), C, h }));\n}\n\n/** Move `fromHex` a fraction `t` of the way to `toHex` in OKLab lightness only. */\nfunction lerpL(fromHex: string, toHex: string, t: number): string {\n const a = hexToOklch(fromHex);\n const b = hexToOklch(toHex);\n return oklchToHex(clampToGamut({ L: a.L + (b.L - a.L) * t, C: a.C, h: a.h }));\n}\n\n/**\n * Push `hex` away from `bg` in lightness until it clears `min`:1. Tries the\n * away-from-bg direction (darken on a light bg), then the other direction,\n * then falls back to pickTextOn(bg) — which always clears 4.5:1.\n */\nfunction ensureOnBg(hex: string, bg: string, min: number): string {\n const pred = (c: string): boolean => contrastRatio(c, bg) >= min;\n const away: LightnessDirection =\n relativeLuminance(bg) >= relativeLuminance(hex) ? \"darken\" : \"lighten\";\n return (\n adjustLightnessUntil(hex, pred, away) ??\n adjustLightnessUntil(hex, pred, away === \"darken\" ? \"lighten\" : \"darken\") ??\n pickTextOn(bg)\n );\n}\n\nconst rgbaOf = (hex: string, alpha: number): string => {\n const { r, g, b } = parseHex(hex);\n const c = (v: number): number => Math.round(v * 255);\n return `rgba(${c(r)}, ${c(g)}, ${c(b)}, ${alpha})`;\n};\n\n/** Brand family + system stack; quotes a single family name when needed. */\nfunction fontValue(family: string | undefined, stack: string): string {\n const f = family?.trim() ?? \"\";\n if (f === \"\") return stack;\n if (f.includes(\",\") || /^[\"']/.test(f) || /^[a-zA-Z][a-zA-Z0-9-]*$/.test(f))\n return `${f}, ${stack}`;\n return `\"${f.replaceAll('\"', \"\")}\", ${stack}`;\n}\n\ninterface Collected {\n byRole: Partial<Record<SwatchRole, string>>;\n charts: string[];\n}\n\n/** First-wins role → hex map + ordered chart swatches; bad hexes dropped. */\nfunction collect(swatches: unknown, warnings: TokenWarning[]): Collected {\n const byRole: Partial<Record<SwatchRole, string>> = {};\n const charts: string[] = [];\n if (!Array.isArray(swatches)) {\n warnings.push({ token: \"--ui-accent\", message: \"swatches is not an array; compiling from defaults\" });\n return { byRole, charts };\n }\n (swatches as Array<Swatch | null>).forEach((s, i) => {\n let hex: string;\n try {\n hex = normalizeHex(String(s?.hex));\n } catch {\n warnings.push({\n token: (s?.role !== undefined && ROLE_TOKEN[s.role]) || \"--ui-accent\",\n message: `swatches[${i}] dropped: invalid hex ${String(s?.hex)}`,\n });\n return;\n }\n const role = s?.role;\n if (role === \"chart\") charts.push(hex);\n else if (role !== undefined && role !== \"custom\" && byRole[role] === undefined) byRole[role] = hex;\n });\n return { byRole, charts };\n}\n\n/** What {@link mapPaletteToTokens} returns. */\nexport interface MapResult {\n /** The light-mode token map — every name in BRAND_EMITTED_TOKENS. */\n tokens: BrandTokens;\n /** Fallbacks taken and contrast adjustments made. */\n warnings: TokenWarning[];\n}\n\n/**\n * Maps a palette (+ optional typography) onto the full light-mode\n * @odla-ai/ui token set. Never throws on bad palettes: invalid swatches are\n * dropped, missing roles derive from the accent via derivePalette, and every\n * degradation is recorded as a {@link TokenWarning}.\n */\nexport function mapPaletteToTokens(input: CompileInput): MapResult {\n const warnings: TokenWarning[] = [];\n const { byRole, charts } = collect(input?.swatches, warnings);\n\n let accent = byRole.primary;\n if (accent === undefined) {\n const chromatic = [...Object.values(byRole), ...charts].find(\n (h) => h !== undefined && hexToOklch(h).C >= CHROMATIC_C,\n );\n accent = chromatic ?? DEFAULT_ACCENT_SEED;\n warnings.push({\n token: \"--ui-accent\",\n message: `no primary swatch; using ${chromatic !== undefined ? \"the first chromatic swatch\" : \"the neutral default seed\"} ${accent}`,\n });\n }\n\n const fb = new Map<SwatchRole, string>();\n for (const s of derivePalette(accent, { names: false })) if (!fb.has(s.role)) fb.set(s.role, s.hex);\n const pick = (role: SwatchRole): string => {\n const own = byRole[role];\n if (own !== undefined) return own;\n const derived = fb.get(role)!;\n warnings.push({ token: ROLE_TOKEN[role]!, message: `no ${role} swatch; derived ${derived} from ${accent}` });\n return derived;\n };\n\n const bg = pick(\"bg\");\n const surface = pick(\"surface\");\n const neutral = pick(\"neutral\");\n const surface2 = shiftL(surface, -0.035);\n\n const rawText = pick(\"text\");\n const text = ensureOnBg(rawText, bg, 4.5);\n if (text !== rawText)\n warnings.push({ token: \"--ui-text\", message: \"adjusted to reach 4.5:1 on --ui-bg\", adjustedFrom: rawText });\n\n const accentStrong = ensureOnBg(accent, bg, 4.5);\n if (accentStrong !== accent)\n warnings.push({\n token: \"--ui-accent-strong\",\n message: \"moved --ui-accent in lightness to reach 4.5:1 on --ui-bg\",\n adjustedFrom: accent,\n });\n\n const status = (role: \"good\" | \"warn\" | \"danger\"): string => {\n const own = pick(role);\n const fixed = ensureOnBg(own, bg, 3);\n if (fixed !== own)\n warnings.push({ token: ROLE_TOKEN[role]!, message: \"adjusted to reach 3:1 on --ui-bg\", adjustedFrom: own });\n return fixed;\n };\n\n const chromaticCharts = charts.filter((h) => hexToOklch(h).C >= CHROMATIC_C);\n let chartValues: readonly string[];\n if (chromaticCharts.length >= 3) {\n const picked = chromaticCharts.slice(0, 6);\n for (const c of deriveChartColors(accent, { count: 12 })) {\n if (picked.length >= 6) break;\n if (!picked.includes(c)) picked.push(c);\n }\n chartValues = picked;\n } else {\n warnings.push({\n token: \"--ui-chart-1\",\n message: `fewer than 3 chromatic chart swatches (${chromaticCharts.length}); using the ui derived chart defaults`,\n });\n chartValues = UI_CHART_DEFAULTS;\n }\n\n const secondary = byRole.secondary;\n if (secondary === undefined)\n warnings.push({ token: \"--ui-accent-2\", message: \"no secondary swatch; --ui-accent-2 collapses onto var(--ui-accent) (ui default)\" });\n const highlight = byRole.highlight;\n if (highlight === undefined)\n warnings.push({ token: \"--ui-highlight\", message: \"no highlight swatch; --ui-highlight aliases var(--ui-accent-strong) (ui default)\" });\n\n const t = input?.typography ?? {};\n const tokens: BrandTokens = {\n \"--ui-bg\": bg,\n \"--ui-surface\": surface,\n \"--ui-surface-2\": surface2,\n \"--ui-text\": text,\n \"--ui-text-muted\": ensureOnBg(lerpL(text, bg, 0.35), bg, 4.5),\n \"--ui-text-faint\": lerpL(text, bg, 0.58),\n \"--ui-border\": lerpL(neutral, bg, 0.55),\n \"--ui-border-strong\": neutral,\n \"--ui-accent\": accent,\n \"--ui-accent-strong\": accentStrong,\n \"--ui-accent-soft\": mixVar(\"--ui-accent\", 10),\n \"--ui-on-accent\": pickTextOn(accent),\n \"--ui-good\": status(\"good\"),\n \"--ui-good-soft\": mixVar(\"--ui-good\", 12),\n \"--ui-warn\": status(\"warn\"),\n \"--ui-warn-soft\": mixVar(\"--ui-warn\", 12),\n \"--ui-danger\": status(\"danger\"),\n \"--ui-danger-soft\": mixVar(\"--ui-danger\", 10),\n \"--ui-code-bg\": surface2,\n \"--ui-code-text\": text,\n \"--ui-shadow\": `0 1px 2px ${rgbaOf(text, 0.04)}, 0 8px 24px ${rgbaOf(text, 0.06)}`,\n \"--ui-font-sans\": fontValue(t.fontBody, SANS_STACK),\n \"--ui-font-serif\": SERIF_STACK,\n \"--ui-font-mono\": fontValue(t.fontMono, MONO_STACK),\n \"--ui-font-display\": fontValue(t.fontDisplay ?? t.fontBody, DISPLAY_STACK),\n \"--ui-accent-glow\": mixVar(\"--ui-accent\", 16),\n \"--ui-accent-2\": secondary ?? \"var(--ui-accent)\",\n \"--ui-accent-2-soft\": mixVar(\"--ui-accent-2\", 10),\n \"--ui-highlight\": highlight ?? \"var(--ui-accent-strong)\",\n \"--ui-focus\": \"0 0 0 3px var(--ui-accent-soft)\",\n \"--ui-shadow-strong\": \"var(--ui-shadow)\",\n \"--ui-chart-1\": chartValues[0]!,\n \"--ui-chart-2\": chartValues[1]!,\n \"--ui-chart-3\": chartValues[2]!,\n \"--ui-chart-4\": chartValues[3]!,\n \"--ui-chart-5\": chartValues[4]!,\n \"--ui-chart-6\": chartValues[5]!,\n \"--ui-chart-band\": mixVar(\"--ui-accent\", 10),\n \"--ui-chart-band-strong\": mixVar(\"--ui-accent\", 22),\n \"--ui-chart-flow\": \"var(--ui-accent)\",\n \"--ui-chart-glow\": \"var(--ui-accent-strong)\",\n \"--ui-chat-user-bg\": \"var(--ui-accent-soft)\",\n \"--ui-chat-user-text\": \"var(--ui-text)\",\n \"--ui-chat-assistant-bg\": \"var(--ui-surface)\",\n \"--ui-chat-thinking-bg\": \"var(--ui-surface-2)\",\n \"--ui-chat-thinking-text\": \"var(--ui-text-muted)\",\n \"--ui-chat-tool-accent\": \"var(--ui-accent)\",\n };\n return { tokens, warnings };\n}\n","// Light → dark token derivation.\n//\n// Neutrals (bg / text ladder / borders / code text) flip around OKLab L 0.5:\n// L' = 1 − L reflected, then squeezed into [DARK_FLIP_L_MIN,\n// DARK_FLIP_L_MAX] so the background lands dark but never black and text\n// lands light but never pure white. Chroma and hue are untouched, so tinted\n// neutrals keep their brand tint.\n//\n// Surfaces are the one place a mirror is wrong: light surfaces sit ABOVE\n// the light bg (lighter), so mirroring would drop them BELOW the dark bg —\n// inverted elevation. Instead the surface ladder is rebuilt upward from the\n// derived dark bg (the @odla-ai/ui dark-neutral pattern: bg < surface <\n// surface-2), reusing the light ladder's own lightness gaps as rung heights\n// (with a small minimum so equal-ish inputs still separate).\n//\n// Accents (accent family, statuses) and chart series keep their hue/chroma\n// and are re-lightened with adjustLightnessUntil until they clear WCAG bars\n// against the derived dark bg: 4.5:1 for the accent family and statuses\n// (SC 1.4.3), 3:1 for chart series (SC 1.4.11 graphics). --ui-on-accent is\n// re-picked against the dark accent. Everything else — var()/color-mix()\n// compositions, font stacks, focus ring — passes through unchanged and\n// recomputes in CSS against the dark values (--ui-shadow is the one literal\n// that gets the ui dark shadow instead).\n\nimport {\n adjustLightnessUntil,\n clampToGamut,\n contrastRatio,\n hexToOklch,\n oklchToHex,\n pickTextOn,\n} from \"../color\";\nimport type { BrandTokens } from \"./types\";\n\n/** Darkest OKLab L a flipped neutral may take — the dark bg floor (never black). */\nexport const DARK_FLIP_L_MIN = 0.2;\n\n/** Lightest OKLab L a flipped neutral may take — keeps flipped text off pure white. */\nexport const DARK_FLIP_L_MAX = 0.95;\n\n/** The ui neutral dark shadow (css/tokens.css `[data-theme=\"dark\"]` value). */\nconst DARK_SHADOW = \"0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35)\";\n\nconst HEX6 = /^#[0-9a-f]{6}$/;\n\n/** Neutral ladder: flipped around L 0.5 with the floor/ceiling squeeze.\n * Surfaces/code-bg are here only as a fallback — with a hex bg present they\n * are rebuilt upward from the dark bg instead (see file header). */\nconst NEUTRAL_FLIP = new Set([\n \"--ui-bg\",\n \"--ui-surface\",\n \"--ui-surface-2\",\n \"--ui-text\",\n \"--ui-text-muted\",\n \"--ui-text-faint\",\n \"--ui-border\",\n \"--ui-border-strong\",\n \"--ui-code-bg\",\n \"--ui-code-text\",\n]);\n\n/** Minimum OKLab ΔL between dark surface rungs, so elevation always reads. */\nconst MIN_RUNG = 0.02;\n\n/** Re-lightened until ≥ 4.5:1 on the dark bg (readable as text). */\nconst ACCENT_AA = [\n \"--ui-accent\",\n \"--ui-accent-strong\",\n \"--ui-accent-2\",\n \"--ui-highlight\",\n \"--ui-good\",\n \"--ui-warn\",\n \"--ui-danger\",\n] as const;\n\n/** Re-lightened until ≥ 3:1 on the dark bg (graphics bar). */\nconst CHART_UI = [\n \"--ui-chart-1\",\n \"--ui-chart-2\",\n \"--ui-chart-3\",\n \"--ui-chart-4\",\n \"--ui-chart-5\",\n \"--ui-chart-6\",\n] as const;\n\nfunction flipNeutral(hex: string): string {\n const { L, C, h } = hexToOklch(hex);\n const flipped = DARK_FLIP_L_MIN + (1 - L) * (DARK_FLIP_L_MAX - DARK_FLIP_L_MIN);\n return oklchToHex(clampToGamut({ L: flipped, C, h }));\n}\n\n/**\n * Rebuild bg < surface < surface-2 upward from the dark bg, using the light\n * ladder's own |ΔL| gaps as rung heights (floored at MIN_RUNG). code-bg\n * follows surface-2 when the light map aliased them, else takes its own\n * bg-relative rung. Mutates `out`; skips any token that is absent/non-hex.\n */\nfunction rebuildSurfaces(out: BrandTokens, light: BrandTokens, lightBgL: number, darkBgL: number): void {\n const lightSurface = light[\"--ui-surface\"];\n const lightSurface2 = light[\"--ui-surface-2\"];\n let surfaceDarkL: number | undefined;\n let surfaceLightL = lightBgL;\n if (lightSurface !== undefined && HEX6.test(lightSurface)) {\n const { L, C, h } = hexToOklch(lightSurface);\n surfaceLightL = L;\n surfaceDarkL = darkBgL + Math.max(Math.abs(L - lightBgL), MIN_RUNG);\n out[\"--ui-surface\"] = oklchToHex(clampToGamut({ L: surfaceDarkL, C, h }));\n }\n if (lightSurface2 !== undefined && HEX6.test(lightSurface2)) {\n const { L, C, h } = hexToOklch(lightSurface2);\n const base = surfaceDarkL ?? darkBgL + MIN_RUNG;\n const L2 = base + Math.max(Math.abs(L - surfaceLightL), MIN_RUNG);\n out[\"--ui-surface-2\"] = oklchToHex(clampToGamut({ L: L2, C, h }));\n }\n const lightCodeBg = light[\"--ui-code-bg\"];\n if (lightCodeBg !== undefined && HEX6.test(lightCodeBg)) {\n if (lightCodeBg === lightSurface2 && out[\"--ui-surface-2\"] !== undefined) {\n out[\"--ui-code-bg\"] = out[\"--ui-surface-2\"];\n } else {\n const { L, C, h } = hexToOklch(lightCodeBg);\n const codeL = darkBgL + Math.max(Math.abs(L - lightBgL), MIN_RUNG);\n out[\"--ui-code-bg\"] = oklchToHex(clampToGamut({ L: codeL, C, h }));\n }\n }\n}\n\n/** Lighten (else darken; else pickTextOn) until ≥ `min`:1 on `bg`. */\nfunction relight(hex: string, bg: string, min: number): string {\n const pred = (c: string): boolean => contrastRatio(c, bg) >= min;\n return (\n adjustLightnessUntil(hex, pred, \"lighten\") ??\n adjustLightnessUntil(hex, pred, \"darken\") ??\n pickTextOn(bg)\n );\n}\n\n/**\n * Derives the dark-mode token map from a light one (see the file header for\n * the flip / re-lighten / passthrough rules). Pure and total: tokens the\n * rules don't recognize — including non-hex values in recognized slots —\n * pass through unchanged, and the result always has exactly the input's\n * key set. Deterministic, never throws.\n */\nexport function deriveDarkTokens(light: BrandTokens): BrandTokens {\n const out: BrandTokens = {};\n for (const [name, value] of Object.entries(light)) {\n if (name === \"--ui-shadow\") out[name] = DARK_SHADOW;\n else if (NEUTRAL_FLIP.has(name) && HEX6.test(value)) out[name] = flipNeutral(value);\n else out[name] = value;\n }\n\n const bg = out[\"--ui-bg\"];\n const lightBg = light[\"--ui-bg\"];\n if (bg === undefined || !HEX6.test(bg) || lightBg === undefined || !HEX6.test(lightBg)) return out;\n\n rebuildSurfaces(out, light, hexToOklch(lightBg).L, hexToOklch(bg).L);\n\n for (const name of [\"--ui-text\", \"--ui-text-muted\"]) {\n const v = out[name];\n if (v !== undefined && HEX6.test(v)) out[name] = relight(v, bg, 4.5);\n }\n const codeBg = out[\"--ui-code-bg\"];\n const codeText = out[\"--ui-code-text\"];\n if (codeText !== undefined && HEX6.test(codeText))\n out[\"--ui-code-text\"] = relight(codeText, codeBg !== undefined && HEX6.test(codeBg) ? codeBg : bg, 4.5);\n\n for (const name of ACCENT_AA) {\n const v = out[name];\n if (v !== undefined && HEX6.test(v)) out[name] = relight(v, bg, 4.5);\n }\n for (const name of CHART_UI) {\n const v = out[name];\n if (v !== undefined && HEX6.test(v)) out[name] = relight(v, bg, 3);\n }\n\n const accent = out[\"--ui-accent\"];\n const onAccent = out[\"--ui-on-accent\"];\n if (accent !== undefined && HEX6.test(accent) && onAccent !== undefined && HEX6.test(onAccent))\n out[\"--ui-on-accent\"] = pickTextOn(accent);\n\n return out;\n}\n","// Compiled tokens → theme CSS text.\n//\n// The output's shape mirrors a real @odla-ai/ui theme tokens file\n// (themes/juniper/tokens.css): a light block, a `[data-theme=\"dark\"]`\n// attribute block, the same dark values repeated under\n// `@media (prefers-color-scheme: dark)` guarded by\n// `:root:not([data-theme=\"light\"])` (so an explicit light preference beats\n// the system's dark scheme), and optionally a `.ui-invert` island block.\n// The attribute/media blocks carry only the tokens that CHANGE in dark —\n// the ui convention, since var()/color-mix() compositions declared in the\n// light block recompute on the same element. The `.ui-invert` island gets\n// the FULL dark payload: on an island, root-computed composites would be\n// stale (see roles.ts).\n//\n// Declarations render in BRAND_EMITTED_TOKENS order (unknown tokens last,\n// alphabetically), so output is deterministic and golden-testable.\n\nimport { BRAND_EMITTED_TOKENS } from \"./roles\";\nimport type { BrandTokens } from \"./types\";\n\n/** Options for {@link renderTokensCss}. */\nexport interface RenderTokensCssOptions {\n /** Dark-mode tokens (deriveDarkTokens output). Omit for a light-only sheet. */\n dark?: BrandTokens;\n /**\n * Selector the light block declares on (default \":root\"). A scoped\n * selector (e.g. `[data-brand=\"acme\"]`) gets its dark blocks nested under\n * the document-level theme guards instead of replacing them.\n */\n selector?: string;\n /**\n * Also emit a `.ui-invert` block carrying the full dark payload, so the\n * subtree renders dark regardless of the global mode. Requires `dark`.\n */\n includeInvert?: boolean;\n}\n\nconst HEADER =\n \"/* Generated by @odla-ai/brand — @odla-ai/ui token overrides; pair with @odla-ai/ui css/tokens.css. */\";\n\nconst ORDER = new Map<string, number>(\n BRAND_EMITTED_TOKENS.map((name, i): [string, number] => [name, i]),\n);\n\n/** Emission order: BRAND_EMITTED_TOKENS index, then unknown names A→Z. */\nfunction orderedNames(tokens: BrandTokens): string[] {\n return Object.keys(tokens).sort((a, b) => {\n const ia = ORDER.get(a);\n const ib = ORDER.get(b);\n if (ia !== undefined && ib !== undefined) return ia - ib;\n if (ia !== undefined) return -1;\n if (ib !== undefined) return 1;\n return a < b ? -1 : a > b ? 1 : 0;\n });\n}\n\nfunction renderBlock(\n selector: string,\n tokens: BrandTokens,\n names: readonly string[],\n indent: string,\n lead?: string,\n): string {\n const lines: string[] = [];\n if (lead !== undefined) lines.push(`${indent} ${lead}`);\n for (const name of names) lines.push(`${indent} ${name}: ${tokens[name]};`);\n if (lines.length === 0) return `${indent}${selector} {\\n${indent}}`;\n return `${indent}${selector} {\\n${lines.join(\"\\n\")}\\n${indent}}`;\n}\n\n/**\n * Renders a compiled token map (plus optional dark map) as CSS text shaped\n * like an @odla-ai/ui theme tokens file — see the file header for the block\n * structure and ordering guarantees. Deterministic for a given input.\n */\nexport function renderTokensCss(light: BrandTokens, opts: RenderTokensCssOptions = {}): string {\n const selector = opts.selector ?? \":root\";\n const scoped = selector !== \":root\";\n const parts: string[] = [HEADER, renderBlock(selector, light, orderedNames(light), \"\")];\n\n const dark = opts.dark;\n if (dark !== undefined) {\n const diffNames = orderedNames(dark).filter((name) => light[name] !== dark[name]);\n if (diffNames.length > 0) {\n const attrSelector = scoped\n ? `[data-theme=\"dark\"] ${selector}, ${selector}[data-theme=\"dark\"]`\n : `[data-theme=\"dark\"]`;\n const mediaSelector = scoped\n ? `:root:not([data-theme=\"light\"]) ${selector}`\n : `:root:not([data-theme=\"light\"])`;\n parts.push(renderBlock(attrSelector, dark, diffNames, \"\"));\n parts.push(\n `@media (prefers-color-scheme: dark) {\\n${renderBlock(mediaSelector, dark, diffNames, \" \")}\\n}`,\n );\n }\n if (opts.includeInvert === true) {\n parts.push(renderBlock(\".ui-invert\", dark, orderedNames(dark), \"\", \"color-scheme: dark;\"));\n }\n }\n return `${parts.join(\"\\n\\n\")}\\n`;\n}\n","// The compiler facade: palette + typography (+ author overrides) →\n// { light, dark, warnings }. Light comes from mapPaletteToTokens, overrides\n// are layered on last (so authors can pin any token, including ones this\n// compiler doesn't own, like --ui-radius-md), and dark derives from the\n// FINAL light map — an overridden accent flows into the dark derivation.\n//\n// Never throws on bad palettes: map.ts drops invalid swatches and derives\n// missing roles (each degradation becomes a TokenWarning), and malformed\n// overrides are skipped with a warning rather than rejected.\n\nimport { deriveDarkTokens } from \"./dark\";\nimport { mapPaletteToTokens } from \"./map\";\nimport type { CompiledBrandTokens, CompileInput, TokenWarning } from \"./types\";\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** Layer `overrides` onto `light` in place; malformed entries warn + skip. */\nfunction applyOverrides(\n light: Record<string, string>,\n overrides: unknown,\n warnings: TokenWarning[],\n): void {\n if (overrides === undefined) return;\n if (!isRecord(overrides)) {\n warnings.push({ token: \"--ui-accent\", message: \"overrides ignored: not an object\" });\n return;\n }\n for (const [name, value] of Object.entries(overrides)) {\n if (!name.startsWith(\"--\")) {\n warnings.push({ token: name, message: \"override ignored: token names must start with --\" });\n continue;\n }\n if (typeof value !== \"string\" || value.trim() === \"\") {\n warnings.push({ token: name, message: \"override ignored: value must be a non-empty string\" });\n continue;\n }\n light[name] = value.trim();\n }\n}\n\n/**\n * Compiles a brand palette into @odla-ai/ui design tokens: a light map\n * covering every name in BRAND_EMITTED_TOKENS (plus overrides), a dark map\n * derived from it (same key set), and the warnings accumulated along the\n * way — missing-role fallbacks and contrast adjustments (`adjustedFrom`).\n *\n * Both maps double as runtime override-island payloads: every derived\n * default that composes the accent family is re-declared, so applying the\n * map on any element recomputes charts, softs, focus and chat roles against\n * the brand palette (see roles.ts). Deterministic; never throws on bad\n * palettes.\n */\nexport function compileBrandTokens(input: CompileInput): CompiledBrandTokens {\n const { tokens: light, warnings } = mapPaletteToTokens(input);\n applyOverrides(light, input?.overrides, warnings);\n const dark = deriveDarkTokens(light);\n return { light, dark, warnings };\n}\n"],"mappings":"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","names":["value","toHex","HEX6"]}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/** The odla-db namespaces @odla-ai/brand installs and writes. */
|
|
2
|
+
declare const BRAND_NS: {
|
|
3
|
+
readonly book: "brand_book";
|
|
4
|
+
readonly section: "brand_section";
|
|
5
|
+
readonly palette: "brand_palette";
|
|
6
|
+
readonly proposal: "brand_proposal";
|
|
7
|
+
readonly asset: "brand_asset";
|
|
8
|
+
};
|
|
9
|
+
/** Brand-book lifecycle: authored → adopted → retired (delete is owner-only). */
|
|
10
|
+
declare const BOOK_STATUSES: readonly ["draft", "active", "archived"];
|
|
11
|
+
/** Where a brand book stands in its lifecycle. */
|
|
12
|
+
type BookStatus = (typeof BOOK_STATUSES)[number];
|
|
13
|
+
/** The section kinds a book carries — one row per kind, upserted by natural key. */
|
|
14
|
+
declare const SECTION_KINDS: readonly ["palette", "typography", "voice", "logo", "imagery"];
|
|
15
|
+
/** Which facet of the brand a section documents. */
|
|
16
|
+
type SectionKind = (typeof SECTION_KINDS)[number];
|
|
17
|
+
/** Section review state: agent drafts, a human approves. */
|
|
18
|
+
declare const SECTION_STATUSES: readonly ["draft", "approved"];
|
|
19
|
+
/** Whether a section's content has human sign-off. */
|
|
20
|
+
type SectionStatus = (typeof SECTION_STATUSES)[number];
|
|
21
|
+
/** Palette lifecycle — palettes are never row-deleted, only archived. */
|
|
22
|
+
declare const PALETTE_STATUSES: readonly ["active", "archived"];
|
|
23
|
+
/** Whether a palette is the live one or a retired predecessor. */
|
|
24
|
+
type PaletteStatus = (typeof PALETTE_STATUSES)[number];
|
|
25
|
+
/** How a palette came to be: pulled from an asset, derived from a seed color,
|
|
26
|
+
* or entered by hand. */
|
|
27
|
+
declare const PALETTE_SOURCES: readonly ["extracted", "derived", "manual"];
|
|
28
|
+
/** Provenance of a palette's swatches. */
|
|
29
|
+
type PaletteSource = (typeof PALETTE_SOURCES)[number];
|
|
30
|
+
/** What a proposal proposes (palettes are the first-class case). */
|
|
31
|
+
declare const PROPOSAL_KINDS: readonly ["palette", "typography", "voice", "logo"];
|
|
32
|
+
/** Which brand facet a proposal targets. */
|
|
33
|
+
type ProposalKind = (typeof PROPOSAL_KINDS)[number];
|
|
34
|
+
/** Proposal lifecycle: open until a HUMAN accepts/rejects it (or a newer
|
|
35
|
+
* proposal supersedes it) — agents never resolve their own proposals. */
|
|
36
|
+
declare const PROPOSAL_STATUSES: readonly ["open", "accepted", "rejected", "superseded"];
|
|
37
|
+
/** Where a proposal stands in the human-gated review flow. */
|
|
38
|
+
type ProposalStatus = (typeof PROPOSAL_STATUSES)[number];
|
|
39
|
+
/** What kind of upload an asset row records. */
|
|
40
|
+
declare const ASSET_KINDS: readonly ["logo", "wordmark", "inspiration", "document", "font", "other"];
|
|
41
|
+
/** The declared purpose of an uploaded asset. */
|
|
42
|
+
type AssetKind = (typeof ASSET_KINDS)[number];
|
|
43
|
+
/** Every swatch role a palette may assign. `chart` may repeat (a series);
|
|
44
|
+
* `custom` is the escape hatch for roles the token compiler doesn't map. */
|
|
45
|
+
declare const SWATCH_ROLES: readonly ["primary", "secondary", "highlight", "bg", "surface", "text", "neutral", "good", "warn", "danger", "chart", "custom"];
|
|
46
|
+
|
|
47
|
+
/** Scalar values odla-db attrs and where-clauses accept. */
|
|
48
|
+
type BrandScalar = string | number | boolean | null;
|
|
49
|
+
/** Natural-key lookup ref for idempotent upserts (odla-db `Lookup` shape). */
|
|
50
|
+
interface BrandLookup {
|
|
51
|
+
ns: string;
|
|
52
|
+
attr: string;
|
|
53
|
+
value: BrandScalar;
|
|
54
|
+
}
|
|
55
|
+
/** How an op names its row: a raw entity id, or a natural-key lookup. */
|
|
56
|
+
type BrandEntityRef = string | BrandLookup;
|
|
57
|
+
/** Attr payload of one op. Typed `any` on purpose: the real odla-db
|
|
58
|
+
* `transact` constrains attrs to its own `Value` type, and `any` is the one
|
|
59
|
+
* shape assignable in both directions with no cast. */
|
|
60
|
+
type BrandAttrs = Record<string, any>;
|
|
61
|
+
/** One transact operation (update = attr-merge upsert; merge = deep-merge
|
|
62
|
+
* into a json attribute; retract = clear the named attributes, since a typed
|
|
63
|
+
* scalar can't store null). */
|
|
64
|
+
type BrandOp = {
|
|
65
|
+
t: "update";
|
|
66
|
+
ns: string;
|
|
67
|
+
id: BrandEntityRef;
|
|
68
|
+
attrs: BrandAttrs;
|
|
69
|
+
} | {
|
|
70
|
+
t: "merge";
|
|
71
|
+
ns: string;
|
|
72
|
+
id: BrandEntityRef;
|
|
73
|
+
attrs: BrandAttrs;
|
|
74
|
+
} | {
|
|
75
|
+
t: "retract";
|
|
76
|
+
ns: string;
|
|
77
|
+
id: BrandEntityRef;
|
|
78
|
+
attrs: string[];
|
|
79
|
+
} | {
|
|
80
|
+
t: "delete";
|
|
81
|
+
ns: string;
|
|
82
|
+
id: BrandEntityRef;
|
|
83
|
+
};
|
|
84
|
+
/** A row as odla-db returns it: hydrated id plus attrs. */
|
|
85
|
+
type BrandRow = {
|
|
86
|
+
id: string;
|
|
87
|
+
} & Record<string, unknown>;
|
|
88
|
+
/** Query result: namespace → rows. */
|
|
89
|
+
type BrandResult = Record<string, BrandRow[]>;
|
|
90
|
+
/** The role a swatch plays in the palette (what the token compiler maps). */
|
|
91
|
+
type SwatchRole = (typeof SWATCH_ROLES)[number];
|
|
92
|
+
/** One palette color: a role, a `#rrggbb` hex, and optional naming/why. */
|
|
93
|
+
interface Swatch {
|
|
94
|
+
role: SwatchRole;
|
|
95
|
+
hex: string;
|
|
96
|
+
name?: string;
|
|
97
|
+
rationale?: string;
|
|
98
|
+
}
|
|
99
|
+
/** `palette`-kind section content: the accepted palette, denormalized. */
|
|
100
|
+
interface PaletteSection {
|
|
101
|
+
paletteId: string;
|
|
102
|
+
name: string;
|
|
103
|
+
swatches: Swatch[];
|
|
104
|
+
}
|
|
105
|
+
/** `typography`-kind section content. `scale` is a modular type-scale ratio. */
|
|
106
|
+
interface TypographySection {
|
|
107
|
+
fontDisplay?: string;
|
|
108
|
+
fontBody?: string;
|
|
109
|
+
fontMono?: string;
|
|
110
|
+
scale?: number;
|
|
111
|
+
notes?: string;
|
|
112
|
+
}
|
|
113
|
+
/** `voice`-kind section content: tone plus writing principles/examples. */
|
|
114
|
+
interface VoiceSection {
|
|
115
|
+
tone: string;
|
|
116
|
+
principles: string[];
|
|
117
|
+
examples?: string[];
|
|
118
|
+
}
|
|
119
|
+
/** `logo`-kind section content: usage rules and don'ts. */
|
|
120
|
+
interface LogoSection {
|
|
121
|
+
clearspace?: string;
|
|
122
|
+
minSize?: string;
|
|
123
|
+
usage: string[];
|
|
124
|
+
donts: string[];
|
|
125
|
+
}
|
|
126
|
+
/** `imagery`-kind section content: photographic/illustration direction. */
|
|
127
|
+
interface ImagerySection {
|
|
128
|
+
style: string;
|
|
129
|
+
guidance: string[];
|
|
130
|
+
}
|
|
131
|
+
/** Agent-produced description of an uploaded asset (validated + capped). */
|
|
132
|
+
interface AssetAnalysis {
|
|
133
|
+
description: string;
|
|
134
|
+
dominantColors: string[];
|
|
135
|
+
tags: string[];
|
|
136
|
+
}
|
|
137
|
+
/** The compiled-token cache stored on `brand_book.tokens`. `warnings` carries
|
|
138
|
+
* the compiler's `TokenWarning[]` (typed loosely here so the row types don't
|
|
139
|
+
* depend on the token compiler's module). */
|
|
140
|
+
interface BrandTokensSnapshot {
|
|
141
|
+
light: Record<string, string>;
|
|
142
|
+
dark: Record<string, string>;
|
|
143
|
+
warnings: unknown[];
|
|
144
|
+
compiledAt: number;
|
|
145
|
+
}
|
|
146
|
+
/** A brand book row: the auth roster (`memberIds`, including the bot agent
|
|
147
|
+
* id) plus the active palette and compiled-token cache. */
|
|
148
|
+
interface BrandBook {
|
|
149
|
+
id: string;
|
|
150
|
+
slug: string;
|
|
151
|
+
name: string;
|
|
152
|
+
status: BookStatus;
|
|
153
|
+
ownerId: string;
|
|
154
|
+
memberIds: string[];
|
|
155
|
+
channelId?: string;
|
|
156
|
+
activePaletteId?: string;
|
|
157
|
+
tokens?: BrandTokensSnapshot;
|
|
158
|
+
summary?: string;
|
|
159
|
+
createdAt: number;
|
|
160
|
+
updatedAt: number;
|
|
161
|
+
}
|
|
162
|
+
/** A section row — one per (book, kind), upserted by its natural `key`. */
|
|
163
|
+
interface BrandSection {
|
|
164
|
+
id: string;
|
|
165
|
+
key: string;
|
|
166
|
+
bookId: string;
|
|
167
|
+
kind: SectionKind;
|
|
168
|
+
status: SectionStatus;
|
|
169
|
+
content: Record<string, unknown>;
|
|
170
|
+
audience: string[];
|
|
171
|
+
updatedBy: string;
|
|
172
|
+
updatedAt: number;
|
|
173
|
+
}
|
|
174
|
+
/** A palette row. Swatches are json ON the palette, not a namespace: the
|
|
175
|
+
* palette is the atomic proposal/approval unit and swatches are never
|
|
176
|
+
* queried across palettes. */
|
|
177
|
+
interface BrandPalette {
|
|
178
|
+
id: string;
|
|
179
|
+
bookId: string;
|
|
180
|
+
name: string;
|
|
181
|
+
status: PaletteStatus;
|
|
182
|
+
swatches: Swatch[];
|
|
183
|
+
seedHex?: string;
|
|
184
|
+
source: PaletteSource;
|
|
185
|
+
rationale?: string;
|
|
186
|
+
proposalId?: string;
|
|
187
|
+
audience: string[];
|
|
188
|
+
createdAt: number;
|
|
189
|
+
updatedAt: number;
|
|
190
|
+
}
|
|
191
|
+
/** A proposal row: agent output parked for explicit human resolution. */
|
|
192
|
+
interface BrandProposal {
|
|
193
|
+
id: string;
|
|
194
|
+
bookId: string;
|
|
195
|
+
kind: ProposalKind;
|
|
196
|
+
status: ProposalStatus;
|
|
197
|
+
payload: Record<string, unknown>;
|
|
198
|
+
rationale: string;
|
|
199
|
+
sourceAssetId?: string;
|
|
200
|
+
messageId?: string;
|
|
201
|
+
audience: string[];
|
|
202
|
+
createdBy: string;
|
|
203
|
+
createdAt: number;
|
|
204
|
+
resolvedBy?: string;
|
|
205
|
+
resolvedAt?: number;
|
|
206
|
+
resolutionNote?: string;
|
|
207
|
+
}
|
|
208
|
+
/** An asset row mirroring one real uploaded file (worker-mediated; rules
|
|
209
|
+
* close the namespace). Delete is a tombstone (`deletedAt`), never a row
|
|
210
|
+
* delete, so analyses/proposals referencing it stay coherent. */
|
|
211
|
+
interface BrandAsset {
|
|
212
|
+
id: string;
|
|
213
|
+
bookId: string;
|
|
214
|
+
kind: AssetKind;
|
|
215
|
+
path: string;
|
|
216
|
+
url: string;
|
|
217
|
+
contentType: string;
|
|
218
|
+
size: number;
|
|
219
|
+
title?: string;
|
|
220
|
+
analysis?: AssetAnalysis;
|
|
221
|
+
analyzedAt?: number;
|
|
222
|
+
audience: string[];
|
|
223
|
+
uploadedBy: string;
|
|
224
|
+
createdAt: number;
|
|
225
|
+
deletedAt?: number;
|
|
226
|
+
}
|
|
227
|
+
/** What `db.storage.upload` resolves to (odla-db's file record shape). */
|
|
228
|
+
interface BrandFileRecord {
|
|
229
|
+
id: string;
|
|
230
|
+
path: string;
|
|
231
|
+
url: string;
|
|
232
|
+
size: number;
|
|
233
|
+
contentType: string;
|
|
234
|
+
}
|
|
235
|
+
/** Bodies `storage.upload` accepts — a practical subset of the platform's
|
|
236
|
+
* `BodyInit` (which has no ambient type under a DOM-less lib). The real
|
|
237
|
+
* @odla-ai/db client accepts a superset, so it still structurally satisfies
|
|
238
|
+
* {@link BrandStorage}. */
|
|
239
|
+
type BrandUploadBody = string | Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>;
|
|
240
|
+
/** The injected file store — @odla-ai/db's `storage` API satisfies this. */
|
|
241
|
+
interface BrandStorage {
|
|
242
|
+
upload(path: string, data: BrandUploadBody, contentType?: string): Promise<BrandFileRecord>;
|
|
243
|
+
delete(path: string): Promise<void>;
|
|
244
|
+
}
|
|
245
|
+
/** The injected odla client (structural — a real @odla-ai/db `AdminDb`
|
|
246
|
+
* satisfies it). `transact` accepts flat op arrays and `mutationId` gives
|
|
247
|
+
* exactly-once application (`duplicate: true` on replay). */
|
|
248
|
+
interface BrandDb {
|
|
249
|
+
query(q: Record<string, unknown>): Promise<Record<string, unknown[]>>;
|
|
250
|
+
transact(ops: unknown[], opts?: {
|
|
251
|
+
mutationId?: string;
|
|
252
|
+
}): Promise<{
|
|
253
|
+
txId: number;
|
|
254
|
+
duplicate?: boolean;
|
|
255
|
+
}>;
|
|
256
|
+
storage: BrandStorage;
|
|
257
|
+
}
|
|
258
|
+
/** Who is acting: a human member or the book's bot agent. */
|
|
259
|
+
interface BrandActor {
|
|
260
|
+
id: string;
|
|
261
|
+
kind: "human" | "agent";
|
|
262
|
+
email?: string;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The @odla-ai/ui required tier (js/tokens.js REQUIRED_TOKENS), in the ui
|
|
267
|
+
* contract's order. The compiler always emits a concrete value for every
|
|
268
|
+
* one of these.
|
|
269
|
+
*/
|
|
270
|
+
declare const BRAND_REQUIRED_TOKENS: readonly ["--ui-bg", "--ui-surface", "--ui-surface-2", "--ui-text", "--ui-text-muted", "--ui-text-faint", "--ui-border", "--ui-border-strong", "--ui-accent", "--ui-accent-strong", "--ui-accent-soft", "--ui-on-accent", "--ui-good", "--ui-good-soft", "--ui-warn", "--ui-warn-soft", "--ui-danger", "--ui-danger-soft", "--ui-code-bg", "--ui-code-text", "--ui-shadow", "--ui-font-sans", "--ui-font-serif", "--ui-font-mono", "--ui-font-display"];
|
|
271
|
+
/**
|
|
272
|
+
* Accent-composing derived roles from the ui defaulted tier. All five
|
|
273
|
+
* accent-family members of the :where([data-ui-accent]) re-declaration set
|
|
274
|
+
* (--ui-accent-glow, --ui-accent-2, --ui-accent-2-soft, --ui-highlight,
|
|
275
|
+
* --ui-focus) plus --ui-shadow-strong, which composes var(--ui-shadow) —
|
|
276
|
+
* a token this compiler re-declares, so the island rule applies to it too.
|
|
277
|
+
*/
|
|
278
|
+
declare const BRAND_DERIVED_TOKENS: readonly ["--ui-accent-glow", "--ui-accent-2", "--ui-accent-2-soft", "--ui-highlight", "--ui-focus", "--ui-shadow-strong"];
|
|
279
|
+
/**
|
|
280
|
+
* Chart roles the compiler emits: the six series slots (real palette
|
|
281
|
+
* swatches when the palette carries them, ui var() compositions otherwise)
|
|
282
|
+
* and the four accent-composing chart roles the :where([data-ui-accent])
|
|
283
|
+
* block re-declares (band, band-strong, flow, glow).
|
|
284
|
+
*/
|
|
285
|
+
declare const BRAND_CHART_TOKENS: readonly ["--ui-chart-1", "--ui-chart-2", "--ui-chart-3", "--ui-chart-4", "--ui-chart-5", "--ui-chart-6", "--ui-chart-band", "--ui-chart-band-strong", "--ui-chart-flow", "--ui-chart-glow"];
|
|
286
|
+
/**
|
|
287
|
+
* Chat surface roles (js/tokens.js CHAT_TOKENS, complete). Two are in the
|
|
288
|
+
* accent-swap set (user-bg, tool-accent); the other four compose surface /
|
|
289
|
+
* text tokens the compiler also re-declares, so an island stays coherent.
|
|
290
|
+
*/
|
|
291
|
+
declare const BRAND_CHAT_TOKENS: readonly ["--ui-chat-user-bg", "--ui-chat-user-text", "--ui-chat-assistant-bg", "--ui-chat-thinking-bg", "--ui-chat-thinking-text", "--ui-chat-tool-accent"];
|
|
292
|
+
/**
|
|
293
|
+
* Every token the compiler emits, in emission order — also the deterministic
|
|
294
|
+
* declaration order renderTokensCss uses, so golden CSS fixtures are stable.
|
|
295
|
+
*/
|
|
296
|
+
declare const BRAND_EMITTED_TOKENS: readonly string[];
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* A compiled token map: `--ui-*` custom-property name → CSS value. Values
|
|
300
|
+
* are either concrete (hex, font stack, shadow) or composition strings
|
|
301
|
+
* (`var(--ui-accent)`, `color-mix(in srgb, var(--ui-accent) 10%, transparent)`)
|
|
302
|
+
* that recompute wherever the map is declared — which is what lets one
|
|
303
|
+
* object serve as both a :root theme and a scoped override-island payload.
|
|
304
|
+
*/
|
|
305
|
+
type BrandTokens = Record<string, string>;
|
|
306
|
+
/**
|
|
307
|
+
* A non-fatal compiler note: a missing swatch role that fell back to a
|
|
308
|
+
* derived value, a dropped invalid input, or a color the compiler had to
|
|
309
|
+
* move to clear a WCAG contrast bar (then `adjustedFrom` carries the
|
|
310
|
+
* original hex).
|
|
311
|
+
*/
|
|
312
|
+
interface TokenWarning {
|
|
313
|
+
/** The `--ui-*` token the note is about. */
|
|
314
|
+
token: string;
|
|
315
|
+
/** Human-readable explanation, actionable for the brand author. */
|
|
316
|
+
message: string;
|
|
317
|
+
/** The original hex, when the compiler adjusted a color for contrast. */
|
|
318
|
+
adjustedFrom?: string;
|
|
319
|
+
}
|
|
320
|
+
/** Input to {@link import("./compile").compileBrandTokens}. */
|
|
321
|
+
interface CompileInput {
|
|
322
|
+
/** The palette to compile — typically a brand_palette row's swatches. */
|
|
323
|
+
swatches: Swatch[];
|
|
324
|
+
/** Optional typography section; fonts get system-stack fallbacks appended. */
|
|
325
|
+
typography?: TypographySection;
|
|
326
|
+
/** Author escape hatch: applied verbatim onto the light map, last. */
|
|
327
|
+
overrides?: BrandTokens;
|
|
328
|
+
}
|
|
329
|
+
/** The compiler's result: light + derived dark token maps, plus notes. */
|
|
330
|
+
interface CompiledBrandTokens {
|
|
331
|
+
/** Light-mode tokens (every name in BRAND_EMITTED_TOKENS, plus overrides). */
|
|
332
|
+
light: BrandTokens;
|
|
333
|
+
/** Dark-mode tokens derived from `light` — same key set. */
|
|
334
|
+
dark: BrandTokens;
|
|
335
|
+
/** Fallbacks taken and contrast adjustments made; empty on a clean palette. */
|
|
336
|
+
warnings: TokenWarning[];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Seed used when the palette has no usable chromatic swatch at all — the
|
|
341
|
+
* @odla-ai/ui neutral default accent (css/tokens.css restrained blue).
|
|
342
|
+
*/
|
|
343
|
+
declare const DEFAULT_ACCENT_SEED = "#3b5e8c";
|
|
344
|
+
/** What {@link mapPaletteToTokens} returns. */
|
|
345
|
+
interface MapResult {
|
|
346
|
+
/** The light-mode token map — every name in BRAND_EMITTED_TOKENS. */
|
|
347
|
+
tokens: BrandTokens;
|
|
348
|
+
/** Fallbacks taken and contrast adjustments made. */
|
|
349
|
+
warnings: TokenWarning[];
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Maps a palette (+ optional typography) onto the full light-mode
|
|
353
|
+
* @odla-ai/ui token set. Never throws on bad palettes: invalid swatches are
|
|
354
|
+
* dropped, missing roles derive from the accent via derivePalette, and every
|
|
355
|
+
* degradation is recorded as a {@link TokenWarning}.
|
|
356
|
+
*/
|
|
357
|
+
declare function mapPaletteToTokens(input: CompileInput): MapResult;
|
|
358
|
+
|
|
359
|
+
/** Darkest OKLab L a flipped neutral may take — the dark bg floor (never black). */
|
|
360
|
+
declare const DARK_FLIP_L_MIN = 0.2;
|
|
361
|
+
/** Lightest OKLab L a flipped neutral may take — keeps flipped text off pure white. */
|
|
362
|
+
declare const DARK_FLIP_L_MAX = 0.95;
|
|
363
|
+
/**
|
|
364
|
+
* Derives the dark-mode token map from a light one (see the file header for
|
|
365
|
+
* the flip / re-lighten / passthrough rules). Pure and total: tokens the
|
|
366
|
+
* rules don't recognize — including non-hex values in recognized slots —
|
|
367
|
+
* pass through unchanged, and the result always has exactly the input's
|
|
368
|
+
* key set. Deterministic, never throws.
|
|
369
|
+
*/
|
|
370
|
+
declare function deriveDarkTokens(light: BrandTokens): BrandTokens;
|
|
371
|
+
|
|
372
|
+
/** Options for {@link renderTokensCss}. */
|
|
373
|
+
interface RenderTokensCssOptions {
|
|
374
|
+
/** Dark-mode tokens (deriveDarkTokens output). Omit for a light-only sheet. */
|
|
375
|
+
dark?: BrandTokens;
|
|
376
|
+
/**
|
|
377
|
+
* Selector the light block declares on (default ":root"). A scoped
|
|
378
|
+
* selector (e.g. `[data-brand="acme"]`) gets its dark blocks nested under
|
|
379
|
+
* the document-level theme guards instead of replacing them.
|
|
380
|
+
*/
|
|
381
|
+
selector?: string;
|
|
382
|
+
/**
|
|
383
|
+
* Also emit a `.ui-invert` block carrying the full dark payload, so the
|
|
384
|
+
* subtree renders dark regardless of the global mode. Requires `dark`.
|
|
385
|
+
*/
|
|
386
|
+
includeInvert?: boolean;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Renders a compiled token map (plus optional dark map) as CSS text shaped
|
|
390
|
+
* like an @odla-ai/ui theme tokens file — see the file header for the block
|
|
391
|
+
* structure and ordering guarantees. Deterministic for a given input.
|
|
392
|
+
*/
|
|
393
|
+
declare function renderTokensCss(light: BrandTokens, opts?: RenderTokensCssOptions): string;
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Compiles a brand palette into @odla-ai/ui design tokens: a light map
|
|
397
|
+
* covering every name in BRAND_EMITTED_TOKENS (plus overrides), a dark map
|
|
398
|
+
* derived from it (same key set), and the warnings accumulated along the
|
|
399
|
+
* way — missing-role fallbacks and contrast adjustments (`adjustedFrom`).
|
|
400
|
+
*
|
|
401
|
+
* Both maps double as runtime override-island payloads: every derived
|
|
402
|
+
* default that composes the accent family is re-declared, so applying the
|
|
403
|
+
* map on any element recomputes charts, softs, focus and chat roles against
|
|
404
|
+
* the brand palette (see roles.ts). Deterministic; never throws on bad
|
|
405
|
+
* palettes.
|
|
406
|
+
*/
|
|
407
|
+
declare function compileBrandTokens(input: CompileInput): CompiledBrandTokens;
|
|
408
|
+
|
|
409
|
+
export { SWATCH_ROLES as $, type AssetAnalysis as A, type BrandDb as B, type BrandStorage as C, type BrandTokens as D, type BrandTokensSnapshot as E, type BrandUploadBody as F, type CompileInput as G, type CompiledBrandTokens as H, DARK_FLIP_L_MAX as I, DARK_FLIP_L_MIN as J, DEFAULT_ACCENT_SEED as K, type ImagerySection as L, type LogoSection as M, type MapResult as N, PALETTE_STATUSES as O, PALETTE_SOURCES as P, PROPOSAL_KINDS as Q, PROPOSAL_STATUSES as R, type Swatch as S, type PaletteSection as T, type PaletteSource as U, type PaletteStatus as V, type ProposalKind as W, type ProposalStatus as X, type RenderTokensCssOptions as Y, SECTION_KINDS as Z, SECTION_STATUSES as _, type BrandBook as a, type SwatchRole as a0, type TokenWarning as a1, type TypographySection as a2, type VoiceSection as a3, compileBrandTokens as a4, deriveDarkTokens as a5, mapPaletteToTokens as a6, renderTokensCss as a7, type BrandOp as b, type BrandEntityRef as c, type SectionKind as d, type SectionStatus as e, type BrandProposal as f, type AssetKind as g, type BrandActor as h, ASSET_KINDS as i, BOOK_STATUSES as j, BRAND_CHART_TOKENS as k, BRAND_CHAT_TOKENS as l, BRAND_DERIVED_TOKENS as m, BRAND_EMITTED_TOKENS as n, BRAND_NS as o, BRAND_REQUIRED_TOKENS as p, type BookStatus as q, type BrandAsset as r, type BrandAttrs as s, type BrandFileRecord as t, type BrandLookup as u, type BrandPalette as v, type BrandResult as w, type BrandRow as x, type BrandScalar as y, type BrandSection as z };
|