@getvitops/generator 8.2.0-alpha.0 → 8.3.0-alpha.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +550 -25
  2. package/assets/css/index.css +7 -0
  3. package/assets/css/patterns/banner.css +20 -0
  4. package/assets/css/patterns/callout.css +39 -0
  5. package/assets/css/patterns/copy.css +5 -0
  6. package/assets/css/patterns/embed.css +39 -0
  7. package/assets/css/patterns/forms.css +19 -0
  8. package/assets/css/patterns/logos.css +45 -0
  9. package/assets/css/patterns/marquee.css +16 -0
  10. package/assets/css/patterns/section.css +53 -0
  11. package/assets/css/patterns/sitenav.css +262 -0
  12. package/assets/css/patterns/splitter.css +11 -0
  13. package/assets/css/patterns/stats.css +61 -0
  14. package/assets/css/patterns/steps.css +98 -0
  15. package/assets/css/patterns/table.css +10 -0
  16. package/assets/css/patterns/tone.css +38 -0
  17. package/assets/js/consent.js +1 -1
  18. package/assets/js/elements.js +5 -5
  19. package/assets/js/polyfills/adoptedStyleSheets.js +1 -1
  20. package/assets/js/polyfills/css-anchor-positioning-fn.js +2 -2
  21. package/assets/js/polyfills/popover.js +2 -2
  22. package/config.schema.json +257 -120
  23. package/dist/config-BfcWKH-5.mjs +1743 -0
  24. package/dist/config-BfcWKH-5.mjs.map +1 -0
  25. package/dist/{config-DvbYx3Gi.mjs → config-Bhg3Xv3D.mjs} +10 -207
  26. package/dist/config-Bhg3Xv3D.mjs.map +1 -0
  27. package/dist/config-D5xuz8--.mjs +1807 -0
  28. package/dist/config-D5xuz8--.mjs.map +1 -0
  29. package/dist/config.d.mts +157 -211
  30. package/dist/config.d.mts.map +1 -1
  31. package/dist/config.mjs +1 -1
  32. package/dist/{icons-sprite-Bf_KmKeX.d.mts → icons-sprite-CQ5Zj4go.d.mts} +4 -6
  33. package/dist/icons-sprite-CQ5Zj4go.d.mts.map +1 -0
  34. package/dist/icons-sprite.d.mts +1 -1
  35. package/dist/icons-sprite.mjs +1 -2
  36. package/dist/index.d.mts +49 -35
  37. package/dist/index.d.mts.map +1 -1
  38. package/dist/index.mjs +607 -75
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/{schema-Dmq1J7S3.d.mts → schema-BV2n2phj.d.mts} +2 -3
  41. package/dist/{schema-Dmq1J7S3.d.mts.map → schema-BV2n2phj.d.mts.map} +1 -1
  42. package/dist/schema-C-P9dF9F.d.mts +337 -0
  43. package/dist/schema-C-P9dF9F.d.mts.map +1 -0
  44. package/dist/{tokens-8SXMDAd1.d.mts → tokens-BzBbTeTi.d.mts} +2 -6
  45. package/dist/tokens-BzBbTeTi.d.mts.map +1 -0
  46. package/dist/{tokens-zi594IZ3.mjs → tokens-C1RJwFeG.mjs} +3 -5
  47. package/dist/{tokens-zi594IZ3.mjs.map → tokens-C1RJwFeG.mjs.map} +1 -1
  48. package/dist/tokens-CaIGQQLk.mjs +740 -0
  49. package/dist/tokens-CaIGQQLk.mjs.map +1 -0
  50. package/dist/tokens-DH5cpvKd.d.mts +242 -0
  51. package/dist/tokens-DH5cpvKd.d.mts.map +1 -0
  52. package/dist/tokens.d.mts +1 -1
  53. package/dist/tokens.mjs +1 -1
  54. package/package.json +6 -5
  55. package/schema.json +95 -5
  56. package/dist/config-DvbYx3Gi.mjs.map +0 -1
  57. package/dist/icons-sprite-Bf_KmKeX.d.mts.map +0 -1
  58. package/dist/tokens-8SXMDAd1.d.mts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens-CaIGQQLk.mjs","names":[],"sources":["../src/shared.ts","../src/tokens.ts"],"sourcesContent":["/**\n * Data shared between the generator (`generate.ts`) and the docs bundle\n * (`docs.ts`). Lives in its own module because `generate.ts` imports `docs.ts` —\n * importing back the other way would be a cycle.\n */\n\n/**\n * Selector the dark functional-token flip hangs off.\n *\n * `data-brx-theme` is Bricks' own attribute — Bricks sets it, so it's what the\n * WordPress target needs. Nothing sets it anywhere else, which meant the dark\n * flip was unreachable outside Bricks: the shipped `<wc-color-scheme-toggle>` web\n * component writes `documentElement.dataset.theme` (i.e. `data-theme`), so\n * clicking \"Dark\" changed an attribute no rule matched. Matching both makes the\n * component work on every target without changing what Bricks already does.\n *\n * This covers the EXPLICIT choice. The OS preference is a second, opt-in block —\n * see `SYSTEM_DARK_SEL` — because turning it on flips a site dark for dark-OS\n * users, which is the site's decision rather than the design system's.\n *\n * Shared so the docs quote the selector the generator actually emits.\n */\nexport const DARK_SEL = ':root[data-brx-theme=\"dark\"], :root[data-theme=\"dark\"]';\n\n/**\n * Selector for the OS-preference dark block, emitted inside\n * `@media (prefers-color-scheme: dark)` when a site opts in.\n *\n * Derived from `DARK_SEL`'s attributes rather than written out a third time, and\n * inverted: this must apply when NO explicit choice has been made. That is\n * exactly what `<wc-color-scheme-toggle>`'s \"System\" position produces — it removes\n * `data-theme` entirely — so with this block present System resolves to the OS\n * instead of silently falling through to light, and needs no JS to do it.\n *\n * The `:not()`s are what keep an explicit *light* choice winning over a dark OS.\n */\nexport const SYSTEM_DARK_SEL = `:root${DARK_SEL.split(', ')\n .map((s) => s.replace(/^:root\\[([\\w-]+)=\"dark\"\\]$/, '[$1=\"light\"]'))\n .map((attr) => `:not(${attr})`)\n .join('')}`;\n\n/**\n * Roles the shipped CSS partials reference with NO fallback, so a config that\n * omits one leaves those rules resolving to nothing — an invalid declaration,\n * not a graceful degrade.\n *\n * `colors.roles` is an open record: any name works and generates a full token\n * set, utilities and dark flip. But the framework's own component CSS is\n * written against these six, so \"roles are arbitrary\" is only half true — they\n * are *extensible*, over a required core. `validate()` warns when one is\n * missing; `required-roles.test.ts` re-derives this list from the partials so\n * it cannot drift as components are added.\n *\n * Kept as a constant rather than scanned at call time because `validate()` is\n * a pure function — the CLI, the Vite plugin and `toJSONSchema` all import it,\n * and none of them should touch the filesystem to answer \"is this config ok?\".\n */\nexport const REQUIRED_ROLES: readonly string[] = [\n 'brand-primary',\n 'danger',\n 'neutral',\n 'surface',\n 'ui-primary',\n 'warning',\n];\n\n/**\n * Every token key a role can resolve to, across both role kinds. Shared so the\n * required-roles drift test greps for exactly what the generator emits.\n *\n * These are *keys*, not var names: the role sits in the middle of the property\n * (`--color-bg-danger-muted`), so compose them with `tokenVar()` from `tokens.ts`\n * rather than concatenating by hand.\n */\nexport const ROLE_TOKEN_KEYS: readonly string[] = [\n // backgrounds — surface roles get the bare token and the full range,\n // chromatic roles get tints plus the solid family.\n 'bg',\n 'bg-muted',\n 'bg-x-muted',\n 'bg-bold',\n 'bg-x-bold',\n 'bg-solid',\n 'bg-solid-bold',\n 'bg-solid-x-bold',\n // foregrounds\n 'text',\n 'text-bold',\n 'text-muted',\n 'text-x-muted',\n 'text-xx-muted',\n 'text-on',\n 'text-on-bold',\n // non-text tiers\n 'icon',\n 'icon-muted',\n 'border',\n 'border-muted',\n 'border-bold',\n];\n\n/** How a role is authored in `colors.roles`: a bare hue, or a hue plus its kind. */\n// `kind` admits an explicit `undefined` because the repo runs\n// `exactOptionalPropertyTypes` and the zod-inferred config type spells optionals\n// that way — without it every call site would need a cast.\nexport interface RoleSpecObject {\n hue: string;\n kind?: 'surface' | 'chromatic' | undefined;\n /** Ramp step the solid family starts from (default 600). */\n solid?: number | string | undefined;\n /** Polarity of `text-on-<role>`; absent = whichever has more contrast. */\n foreground?: 'light' | 'dark' | undefined;\n}\nexport type RoleSpec = string | RoleSpecObject;\n\n/** The solid step / foreground polarity a role asked for (both optional). */\nexport const roleSolidOptions = (\n spec: RoleSpec,\n): { solid?: string; foreground?: 'light' | 'dark' } =>\n typeof spec === 'string'\n ? {}\n : {\n ...(spec.solid != null ? { solid: String(spec.solid) } : {}),\n ...(spec.foreground ? { foreground: spec.foreground } : {}),\n };\n\n/** The palette hue a role points at, through either authoring form. */\nexport const roleHue = (spec: RoleSpec): string => (typeof spec === 'string' ? spec : spec.hue);\n\n/**\n * A role's kind. The bare-string shorthand means `chromatic`, because signal\n * colours are the common case and surfaces are the two or three you name once.\n */\nexport const roleKind = (spec: RoleSpec): 'surface' | 'chromatic' =>\n typeof spec === 'string' ? 'chromatic' : (spec.kind ?? 'chromatic');\n\n/**\n * Pattern base geometry props → per-pattern override-hook shorthand. Each hook\n * wraps the authored value (`padding: var(--p-btn, 0.4em 0.8em)`) so a\n * consumer restyles one pattern by setting one variable. The hook is named after\n * the pattern's key, not its class.\n */\nexport const BASE_HOOK: Record<string, string> = {\n padding: 'p',\n 'border-radius': 'br',\n border: 'b',\n 'box-shadow': 'ds',\n 'font-size': 'fs',\n // Both spellings map to `bg`, because patterns author either (`card` uses the\n // shorthand, `status` the longhand). A pattern setting both would map the\n // same var twice — a config error, not something to design around.\n background: 'bg',\n 'background-color': 'bg',\n};\n\n/**\n * A typography role's declared key → [CSS property, var-name suffix]. The\n * recognised key set is CLOSED (an unrecognised key is a config error, see\n * `generate.ts`'s `ALSO_MEANT`) — shared here (rather than living only in\n * `generate.ts`) because `tokens.ts`'s `buildReverseIndex` needs the exact\n * same key → suffix mapping to stay in step with what `generate()` itself\n * builds, and `tokens.ts` can't import from `generate.ts` (the CSS-bundling\n * half, which needs lightningcss/node:fs) without losing its own\n * Worker-safety.\n */\nexport const TYPO_KEYMAP: Record<string, [string, string]> = {\n family: ['font-family', 'ff'],\n size: ['font-size', 'fs'],\n weight: ['font-weight', 'fw'],\n style: ['font-style', 'fst'],\n 'line-height': ['line-height', 'lh'],\n tracking: ['letter-spacing', 'ls'],\n 'text-decoration': ['text-decoration', 'td'],\n 'text-transform': ['text-transform', 'tt'],\n 'text-wrap': ['text-wrap', 'tw'],\n color: ['color', 'color'],\n};\n\n/**\n * Framework utility classes whose names collide with Tailwind's own defaults.\n * The `tailwind` format strips any rule targeting these — Tailwind provides\n * them, so consumers use Tailwind's utilities for these names instead.\n */\nexport const TW_CLASH: ReadonlySet<string> = new Set<string>([\n 'block',\n 'inline',\n 'inline-block',\n 'flow-root',\n 'flex',\n 'inline-flex',\n 'grid',\n 'inline-grid',\n 'table',\n 'inline-table',\n 'table-caption',\n 'table-cell',\n 'table-row',\n 'contents',\n 'hidden',\n 'list-item',\n 'static',\n 'fixed',\n 'absolute',\n 'relative',\n 'sticky',\n 'isolate',\n 'visible',\n 'invisible',\n 'collapse',\n 'sr-only',\n 'not-sr-only',\n 'flex-row',\n 'flex-row-reverse',\n 'flex-col',\n 'flex-col-reverse',\n 'flex-wrap',\n 'flex-nowrap',\n 'items-start',\n 'items-end',\n 'items-center',\n 'items-baseline',\n 'items-stretch',\n 'justify-start',\n 'justify-end',\n 'justify-center',\n 'justify-between',\n 'justify-around',\n 'justify-evenly',\n 'content-start',\n 'content-end',\n 'content-center',\n 'content-between',\n 'content-around',\n 'content-evenly',\n 'text-left',\n 'text-center',\n 'text-right',\n 'text-justify',\n 'text-start',\n 'text-end',\n 'text-wrap',\n 'text-nowrap',\n 'text-balance',\n 'text-pretty',\n 'rounded-none',\n 'rounded-full',\n]);\n\n/**\n * `layout` config key → the custom property it sets. The one table both the\n * emitters (css/bricks/tailwind) and the live-editor manifest's `reverseIndex`\n * read, so a token that is emitted is always one the editor can write back.\n */\nexport const LAYOUT_TOKENS: Record<string, string> = {\n 'widths.measure': '--width-measure',\n 'widths.breakout': '--width-breakout',\n 'widths.spotlight': '--width-spotlight',\n 'widths.nav': '--width-nav',\n 'regionSpace.min': '--region-space-min',\n 'regionSpace.preferred': '--region-space',\n 'regionSpace.max': '--region-space-max',\n};\n\n/**\n * The `:root` declarations for an authored `layout` block (only what was set —\n * an absent key leaves the framework's own default in force), keyed by the\n * custom property.\n */\nexport function layoutDecls(\n layout:\n | {\n widths?: Record<string, string | undefined> | undefined;\n regionSpace?: Record<string, string | undefined> | undefined;\n }\n | undefined,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [path, token] of Object.entries(LAYOUT_TOKENS)) {\n const [group, key] = path.split('.') as ['widths' | 'regionSpace', string];\n const value = layout?.[group]?.[key];\n if (value != null) out[token] = value;\n }\n return out;\n}\n","/**\n * Palette expansion + semantic colour tokens.\n *\n * A palette hue is authored one of two ways:\n * • `{ seed, anchors? }` — an 11-step numeric scale (50…950) generated in\n * OKLCH via @getvitops/utils/color. Lightness comes from the shared\n * `LIGHTNESS_LADDER`; the seed (and any anchors) are reproduced verbatim at\n * their steps.\n * • `{ tones }` — a fixed brand kit: authored tones placed verbatim at their\n * nearest steps + tinted endpoints; nothing else is invented (strict kits).\n *\n * **The grammar.** Every role gets tokens named `--color-<target>-<role>[-<variant>]`,\n * where target ∈ bg | text | icon | border. Putting the target *inside* the name is\n * the point: `--color-bg-danger-muted` and `--color-text-danger-muted` are distinct\n * tokens, so the two axes that used to collide over one `<family>-<role>-<modifier>\n * ` namespace cannot any more. The utility class is the token name minus `--color-`.\n *\n * Variants are ordinal — xx-muted < x-muted < muted < (bare) < bold < x-bold —\n * and the tables are **sparse**: a target only emits the cells that are actually\n * viable. `bold` means \"more emphatic than the role's default in the current\n * appearance\", not \"darker\"; the appearance decides polarity.\n *\n * Roles come in two kinds (declared in `colors.roles`):\n * • `surface` — a page/panel colour. Has a bare `bg-<role>` plus the full\n * emphasis range and the complete text scale.\n * • `chromatic` (default) — a signal colour. Backgrounds split into *tints*\n * (`bg-<role>-x-muted` / `-muted`) and *solids* (`bg-<role>-solid[-bold|-x-bold]`);\n * there is deliberately **no bare `bg-<role>`**, because \"how loud?\" is a\n * question the author has to answer.\n *\n * Dark mode re-points which step each token reads; the solid family and the\n * `text-on-<role>` foreground computed against it stay mode-stable, so a filled\n * button keeps its identity across appearances.\n */\nimport {\n LIGHTNESS_LADDER,\n contrastLc,\n generateOklchPalette,\n hexToOklch,\n oklchStringToHex,\n pickOn,\n tintedEndpoints,\n} from '@getvitops/utils/color';\nimport {\n BASE_HOOK,\n LAYOUT_TOKENS,\n TYPO_KEYMAP,\n type RoleSpec,\n roleHue,\n roleKind,\n} from './shared.ts';\nimport type { DesignSystem } from './schema.ts';\n\nexport const NUMERIC_STEPS = [\n '50',\n '100',\n '200',\n '300',\n '400',\n '500',\n '600',\n '700',\n '800',\n '900',\n '950',\n] as const;\ntype NumStep = (typeof NUMERIC_STEPS)[number];\n\n/**\n * Lightness of each numeric step. Re-exported from the engine's ladder rather\n * than restated — there used to be three copies of this curve (here, the engine,\n * and the linter) and they were free to drift.\n */\nconst STEP_L = Object.fromEntries(\n NUMERIC_STEPS.map((s) => [s, LIGHTNESS_LADDER[Number(s) as keyof typeof LIGHTNESS_LADDER]]),\n) as Record<NumStep, number>;\n\nexport interface ExpandedHue {\n /** Numeric steps (sparse for fixed-tone kits). */\n numeric: Partial<Record<NumStep, string>>;\n /** True when only authored tones + endpoints exist (no interpolation). */\n sparse?: boolean;\n /**\n * Steps the author pinned, and how far each sits from the ladder's lightness.\n * A pinned colour is reproduced exactly, so a large value here is legal — but\n * it means this hue reads heavier or lighter than its siblings at that step,\n * which the generator surfaces as a warning.\n */\n deviations?: Partial<Record<NumStep, number>>;\n}\n\ninterface SeedInput {\n seed: string;\n anchors?: Record<string, string>;\n}\ninterface TonesInput {\n tones: string[] | Record<string, string>;\n}\n\nconst isHex = (v: string) => v.startsWith('#');\nconst toOklchStr = (v: string) => {\n if (!isHex(v)) return v; // already oklch(...)\n const { l, c, h } = hexToOklch(v);\n return `oklch(${(l * 100).toFixed(2)}% ${c.toFixed(4)} ${h.toFixed(2)})`;\n};\nconst nearestStep = (l: number): NumStep =>\n NUMERIC_STEPS.reduce((best, s) =>\n Math.abs(l - STEP_L[s]) < Math.abs(l - STEP_L[best]) ? s : best,\n );\n\n/**\n * Claim `step` for `value`, refusing to overwrite a step another colour already\n * took.\n *\n * Silently dropping one of two colliding tones is the worst outcome: the palette\n * still builds, and the tone the author cared about is simply gone. The record\n * form (`tones: { \"600\": \"#…\", \"700\": \"#…\" }`) is the escape hatch, so the error\n * points at it.\n */\nfunction claim(\n into: Record<string, string>,\n step: string,\n value: string,\n hueLabel: string,\n what: string,\n): void {\n const taken = into[step];\n if (taken != null && taken !== value) {\n throw new Error(\n `palette hue \"${hueLabel}\": ${what} ${value} and ${taken} both map to step ${step}. ` +\n `Key them explicitly, e.g. { \"${step}\": \"${taken}\", \"${Number(step) + 100}\": \"${value}\" }.`,\n );\n }\n into[step] = value;\n}\n\nfunction expandSeeded(input: SeedInput, hueLabel: string): ExpandedHue {\n // The seed and any author anchors pin verbatim at their natural (or explicit)\n // steps; the engine fills every other step from the shared lightness ladder,\n // fading chroma toward the tinted ends. No implicit 50/950 anchors any more —\n // the ladder already defines the endpoints, and pinning them would override it.\n // Author anchors are placed FIRST and may collide only with each other. The\n // seed goes in afterwards, and only where an anchor hasn't already claimed the\n // step: an explicit anchor is the author overriding that step, which is exactly\n // what the live editor writes when you recolour a swatch. Treating that as a\n // collision would break the editor's save path.\n const anchors: Record<string, string> = {};\n for (const [key, value] of Object.entries(input.anchors ?? {})) {\n const step = (NUMERIC_STEPS as readonly string[]).includes(key)\n ? key\n : nearestStep(hexToOklch(value).l);\n claim(anchors, step, toOklchStr(value), hueLabel, 'anchors');\n }\n const seedStep = nearestStep(hexToOklch(input.seed).l);\n anchors[seedStep] ??= toOklchStr(input.seed);\n const scale = generateOklchPalette(anchors) as Record<string, string>;\n const numeric: Partial<Record<NumStep, string>> = {};\n for (const s of NUMERIC_STEPS) if (scale[s] != null) numeric[s] = oklchStringToHex(scale[s]);\n return { numeric, deviations: deviationsFor(anchors) };\n}\n\nfunction expandTones(input: TonesInput, hueLabel: string): ExpandedHue {\n // Strict kit: authored tones verbatim at their nearest step; add tinted\n // endpoints (the one allowed exception); interpolate nothing else.\n const entries: Array<[string | null, string]> = Array.isArray(input.tones)\n ? input.tones.map((hex) => [null, hex])\n : Object.entries(input.tones).map(([k, hex]) => [k, hex]);\n\n const placed: Record<string, string> = {};\n for (const [key, hex] of entries) {\n const step =\n key != null && (NUMERIC_STEPS as readonly string[]).includes(key)\n ? key\n : nearestStep(hexToOklch(hex).l);\n claim(placed, step, hex, hueLabel, 'tones');\n }\n\n const numeric: Partial<Record<NumStep, string>> = {};\n for (const [step, hex] of Object.entries(placed)) numeric[step as NumStep] = hex;\n\n const firstTone = entries[0]?.[1] ?? '#808080';\n const ends = tintedEndpoints(hexToOklch(firstTone).h);\n numeric['50'] ??= ends.light;\n numeric['950'] ??= ends.dark;\n\n return { numeric, sparse: true, deviations: deviationsFor(placed) };\n}\n\n/** How far each pinned colour sits from its step's ladder lightness. */\nfunction deviationsFor(pinned: Record<string, string>): Partial<Record<NumStep, number>> {\n const out: Partial<Record<NumStep, number>> = {};\n for (const [step, value] of Object.entries(pinned)) {\n const l = isHex(value) ? hexToOklch(value).l : hexToOklch(oklchStringToHex(value)).l;\n out[step as NumStep] = l - STEP_L[step as NumStep];\n }\n return out;\n}\n\n/** Snap to the nearest AVAILABLE numeric step (sparse kits). */\nfunction snap(numeric: Partial<Record<NumStep, string>>, want: NumStep): NumStep {\n if (numeric[want] != null) return want;\n const have = NUMERIC_STEPS.filter((s) => numeric[s] != null);\n return have.reduce((best, s) =>\n Math.abs(STEP_L[s] - STEP_L[want]) < Math.abs(STEP_L[best] - STEP_L[want]) ? s : best,\n );\n}\n\n/** Expand every palette hue into its numeric scale. */\nexport function expandPalette(raw: Record<string, unknown>): Record<string, ExpandedHue> {\n const out: Record<string, ExpandedHue> = {};\n for (const [name, entry] of Object.entries(raw)) {\n const e = entry as Partial<SeedInput & TonesInput>;\n if (typeof e?.seed === 'string') out[name] = expandSeeded(e as SeedInput, name);\n else if (e?.tones != null) out[name] = expandTones(e as TonesInput, name);\n else throw new Error(`palette hue \"${name}\" must have a \"seed\" or \"tones\"`);\n }\n return out;\n}\n\n/**\n * Warn about pinned colours that sit noticeably off the ladder. Not an error:\n * the author asked for that exact colour and gets it. But at ~0.03 L the hue\n * starts reading visibly heavier or lighter than its siblings at that step, and\n * that is the class of drift the fixed ladder exists to prevent.\n */\nconst LADDER_TOLERANCE = 0.03;\n\nexport function ladderWarnings(\n palette: Record<string, ExpandedHue>,\n /** The authored palette, to honour a hue's `acknowledgeOffLadder: true`. */\n authored: Record<string, unknown> = {},\n): string[] {\n const warnings: string[] = [];\n for (const [hue, expanded] of Object.entries(palette)) {\n if ((authored[hue] as { acknowledgeOffLadder?: boolean } | undefined)?.acknowledgeOffLadder)\n continue;\n for (const [step, deviation] of Object.entries(expanded.deviations ?? {})) {\n if (Math.abs(deviation as number) <= LADDER_TOLERANCE) continue;\n const dir = (deviation as number) > 0 ? 'lighter' : 'darker';\n warnings.push(\n `colors.palette.${hue}: the colour pinned at step ${step} is ${Math.abs(\n deviation as number,\n ).toFixed(3)} ${dir} than the shared lightness ladder — this hue will read ` +\n `${dir} than others at ${step}. Legal, but move it to a neighbouring step if that wasn't intended ` +\n `(or set \\`acknowledgeOffLadder: true\\` on the hue to silence this).`,\n );\n }\n }\n return warnings;\n}\n\n/**\n * Gamut mapping nudges lightness by a hair, so an exact `<` comparison would\n * report rounding as an inversion. Well below any deviation a person could see.\n */\nconst MONOTONIC_EPSILON = 1e-3;\n\n/**\n * Ramps whose lightness does not fall monotonically from 50 to 950.\n *\n * A ramp built from the ladder alone cannot invert — only pinned colours (a seed,\n * an `anchor`, a `tones` entry) can, because they are the one thing allowed off\n * it. `ladderWarnings` will not catch it: it compares each pinned step against\n * *its own* ladder rung, never against its neighbours, so it reports the wrong\n * defect (\"reads darker than others at 600\") while the real problem is that 600\n * is darker than 700. And since `LADDER_TOLERANCE` is 0.03 — the same size as the\n * ladder's own 50→100 gap — two anchors can each sit within tolerance and still\n * invert, with no warning at all.\n *\n * This is an error rather than a warning because the rest of the system assumes\n * the order: `snap` picks the nearest step by ladder lightness, the mode-stable\n * solid family hard-codes 600/700/800 as increasingly dark, and both dark tables\n * re-point steps on the same assumption. An inverted ramp therefore runs hover\n * states backwards — the failure the fixed ladder was introduced to eliminate.\n */\nexport function monotonicityErrors(palette: Record<string, ExpandedHue>): string[] {\n const errors: string[] = [];\n for (const [hue, expanded] of Object.entries(palette)) {\n const numeric = expanded.numeric ?? {};\n // Present steps only — a sparse `tones` kit is legal, and comparing against\n // absent steps would invent inversions that aren't there.\n const steps = NUMERIC_STEPS.filter((s) => numeric[s] != null);\n for (let i = 1; i < steps.length; i++) {\n const [prev, step] = [steps[i - 1] as NumStep, steps[i] as NumStep];\n // The final hex, not the nominal ladder value: a pinned colour is emitted\n // verbatim, and it is precisely the pinned steps that can invert.\n const lPrev = hexToOklch(numeric[prev] as string).l;\n const lStep = hexToOklch(numeric[step] as string).l;\n if (lStep < lPrev + MONOTONIC_EPSILON) continue;\n // Point at the colour the author actually wrote. Only a pinned step can be\n // off the ladder, so naming its correct home is the actionable half — the\n // unpinned neighbour is already exactly where it belongs, and telling\n // someone to move that one sends them the wrong way.\n const pinned = expanded.deviations ?? {};\n const culprits = ([prev, step] as NumStep[]).filter((s) => pinned[s] != null);\n const fix = culprits.length\n ? culprits\n .map((s) => {\n const l = hexToOklch(numeric[s] as string).l;\n return `${numeric[s]} (pinned at ${s}) sits at L ${l.toFixed(3)}, nearest step ${nearestStep(l)}`;\n })\n .join('; ')\n : `no step here is pinned, so this is a bug in the generator rather than in your config`;\n errors.push(\n `colors.palette.${hue}: step ${step} (${numeric[step]}, L ${lStep.toFixed(3)}) is LIGHTER ` +\n `than step ${prev} (${numeric[prev]}, L ${lPrev.toFixed(3)}). A ramp must darken from 50 ` +\n `to 950 — inverted, a hover from \\`bg-<role>-solid\\` to \\`-solid-bold\\` gets lighter, and ` +\n `the dark-mode tables re-point steps assuming the order. ${fix}.`,\n );\n }\n }\n return errors;\n}\n\n// ── the token tables ─────────────────────────────────────────────────────────\n\n/** Which shape of token set a role gets. */\nexport type RoleKind = 'surface' | 'chromatic';\n\n/** Token key → numeric step. Keys are `<target>[-<variant>]`; see `tokenVar`. */\ntype Table = Record<string, NumStep>;\n\n/**\n * Surface roles: a bare `bg` (cards, panels, inputs), `bg-muted` for the page\n * behind them, `bg-x-muted` for wells, and `bg-bold`/`-x-bold` for the inverse\n * surface a tooltip sits on. Elevation is expressed by *which* token you reach\n * for — page `bg-muted`, card `bg` — rather than by a raised/sunken pair. That\n * is what lets a future `data-surfaces` axis flatten it without touching markup.\n */\nconst SURFACE_LIGHT: Table = {\n bg: '50',\n 'bg-muted': '100',\n 'bg-x-muted': '200',\n 'bg-bold': '800',\n 'bg-x-bold': '950',\n text: '900',\n 'text-bold': '950',\n 'text-muted': '700',\n 'text-x-muted': '500',\n 'text-xx-muted': '400',\n 'text-on-bold': '50',\n icon: '600',\n 'icon-muted': '500',\n 'border-muted': '200',\n border: '300',\n 'border-bold': '600',\n};\nconst SURFACE_DARK: Table = {\n bg: '900',\n 'bg-muted': '950',\n 'bg-x-muted': '950',\n 'bg-bold': '200',\n 'bg-x-bold': '50',\n text: '100',\n 'text-bold': '50',\n // 300 and 400, not v3.1's 400 and 500: those were tuned against WCAG and come\n // up short of APCA's secondary-text and non-text bars over a dark surface.\n 'text-muted': '300',\n 'text-x-muted': '500',\n 'text-xx-muted': '600',\n 'text-on-bold': '950',\n icon: '400',\n 'icon-muted': '500',\n 'border-muted': '800',\n border: '700',\n 'border-bold': '400',\n};\n\n/**\n * Chromatic roles: tints for alert/badge backgrounds, solids for fills. No bare\n * `bg` — see the module docblock. No `text-muted` either: a de-emphasised\n * coloured text could not hold its contrast target off a white surface, so the\n * way to soften coloured text is weight/size, not another colour.\n */\nconst CHROMATIC_LIGHT: Table = {\n 'bg-x-muted': '50',\n 'bg-muted': '100',\n text: '700',\n 'text-bold': '900',\n icon: '600',\n border: '200',\n 'border-bold': '300',\n};\nconst CHROMATIC_DARK: Table = {\n 'bg-x-muted': '950',\n 'bg-muted': '900',\n text: '300',\n 'text-bold': '200',\n icon: '400',\n border: '800',\n 'border-bold': '700',\n};\n\n/**\n * The solid family — mode-stable, so a filled button keeps its identity when the\n * appearance flips. Fixed steps rather than the old scan-and-clamp: with a shared\n * lightness ladder every hue's 600 already sits at the same lightness, so there\n * is nothing left to search for. `text-on-<role>` is computed against `bg-solid`.\n */\nconst SOLID: Table = { 'bg-solid': '600', 'bg-solid-bold': '700', 'bg-solid-x-bold': '800' };\n\n/**\n * Map a token key to its CSS custom property for a given role.\n *\n * `on` is the one irregular case: the foreground for a filled surface reads\n * `--color-text-on-<role>`, with the role *after* `on`, because it names what it\n * sits on rather than what it is.\n *\n * The utility class name is this minus the `--color-` prefix, which is why the\n * emitter and the linter can both derive their vocabulary from one function.\n */\nexport function tokenVar(role: string, key: string): string {\n const dash = key.indexOf('-');\n const target = dash === -1 ? key : key.slice(0, dash);\n const variant = dash === -1 ? '' : key.slice(dash + 1);\n if (variant === 'on') return `--color-${target}-on-${role}`;\n if (variant.startsWith('on-')) return `--color-${target}-on-${role}-${variant.slice(3)}`;\n return variant ? `--color-${target}-${role}-${variant}` : `--color-${target}-${role}`;\n}\n\n/** The class name a token key produces for a role (`bg-danger-solid`, …). */\nexport const tokenClass = (role: string, key: string): string =>\n tokenVar(role, key).slice('--color-'.length);\n\nexport interface FunctionalRole {\n role: string;\n hue: string;\n kind: RoleKind;\n /** token key → CSS value, per appearance. */\n light: Record<string, string>;\n dark: Record<string, string>;\n}\n\n/** Resolve the token set for one role over its hue. */\nexport function functionalRole(\n role: string,\n hueName: string,\n hue: ExpandedHue,\n kind: RoleKind = 'chromatic',\n opts: { solid?: string; foreground?: 'light' | 'dark' } = {},\n): FunctionalRole {\n const numeric = hue.numeric;\n const v = (n: NumStep) => `var(--color-${hueName}-${snap(numeric, n)})`;\n const hex = (n: NumStep) => numeric[snap(numeric, n)] as string;\n\n const map = (table: Table): Record<string, string> => {\n const out: Record<string, string> = {};\n for (const [token, step] of Object.entries(table)) out[token] = v(step);\n return out;\n };\n\n // Solids and the foreground computed against them are identical in both\n // appearances — that mode-stability is the whole point of the family.\n //\n // A role may name its own solid step (a bright brand colour that lives at 400,\n // not 600). The family then walks DARKER from it — bold, x-bold — by ladder\n // position, so 400 → 500 → 600 as the default is 600 → 700 → 800. And\n // `foreground` pins the polarity of the on-colour (dark text on a bright fill);\n // `checkContrast` still judges the pair actually emitted.\n const solidStep = (opts.solid ?? SOLID['bg-solid']) as NumStep;\n const at = NUMERIC_STEPS.indexOf(solidStep);\n const stepAt = (i: number) => NUMERIC_STEPS[Math.min(i, NUMERIC_STEPS.length - 1)] as NumStep;\n const solids = map({\n 'bg-solid': solidStep,\n 'bg-solid-bold': stepAt(at + 1),\n 'bg-solid-x-bold': stepAt(at + 2),\n });\n const onSolid = pickOn(hex(solidStep), undefined, opts.foreground);\n\n const light = {\n ...map(kind === 'surface' ? SURFACE_LIGHT : CHROMATIC_LIGHT),\n ...solids,\n 'text-on': onSolid,\n };\n const dark = {\n ...map(kind === 'surface' ? SURFACE_DARK : CHROMATIC_DARK),\n ...solids,\n 'text-on': onSolid,\n };\n return { role, hue: hueName, kind, light, dark };\n}\n\n// ── the contrast contract ────────────────────────────────────────────────────\n\n/**\n * APCA targets. `TEXT` is the body-text bar; `SECONDARY` covers text on a\n * non-primary surface and de-emphasised text. `NON_TEXT` is the icon/boundary\n * tier — the APCA analogue of WCAG's 3:1, where a glyph may run more vivid than\n * text because shape carries meaning alongside contrast.\n */\nexport const CONTRAST = { TEXT: 75, SECONDARY: 60, NON_TEXT: 45 } as const;\n\n/**\n * Text tokens deliberately exempt from the contract. Placeholders and disabled\n * text are required to look unavailable; holding them to the body-text bar would\n * defeat the affordance. Nothing else may be exempt.\n */\nconst EXEMPT_TEXT = new Set(['text-x-muted', 'text-xx-muted']);\n\n/**\n * Verify a role's contrast targets. Returns human-readable failures.\n *\n * Chromatic roles are checked against the surface planes they actually sit on\n * (`surfaceBg`) as well as their own tints — coloured text almost never appears\n * over its own solid, it appears over the page. Checking only within the role\n * would have missed the pairing that matters most.\n */\nexport function checkContrast(\n fr: FunctionalRole,\n numericOf: (hueName: string) => Partial<Record<NumStep, string>>,\n surfaceBg?: { light: string[]; dark: string[] },\n /** Appearances to judge; a dark-only site passes `['dark']`. Default: both. */\n appearances: readonly ('light' | 'dark')[] = ['light', 'dark'],\n): string[] {\n const failures: string[] = [];\n const resolve = (val: string): string => {\n const m = /^var\\(--color-([a-z0-9-]+)-(\\d+)\\)$/.exec(val);\n if (!m) return val;\n return numericOf(m[1] as string)[m[2] as NumStep] ?? val;\n };\n const fail = (appearance: string, msg: string) =>\n failures.push(`${fr.role}/${appearance}: ${msg}`);\n\n for (const [appearance, tokens] of [\n ['light', fr.light],\n ['dark', fr.dark],\n ] as const) {\n if (!appearances.includes(appearance)) continue;\n // Backgrounds this role's own foregrounds have to work on. For a surface\n // role that's its own planes; for a chromatic role it's the page surface\n // plus its own tints.\n const ownBg = Object.keys(tokens)\n .filter((k) => k === 'bg' || k.startsWith('bg-'))\n .filter((k) => !k.startsWith('bg-solid'))\n .filter((k) => !k.startsWith('bg-bold') && !k.startsWith('bg-x-bold'));\n const planes: Array<[string, string]> =\n fr.kind === 'surface'\n ? ownBg.map((k) => [k, resolve(tokens[k] as string)])\n : [\n ...(surfaceBg?.[appearance] ?? []).map(\n (h, i) => [`surface#${i}`, h] as [string, string],\n ),\n ...ownBg.map((k) => [k, resolve(tokens[k] as string)] as [string, string]),\n ];\n\n for (const [planeName, bg] of planes) {\n // `bg` is the role's primary surface and holds the full body-text bar;\n // every other plane holds the secondary bar.\n const textMin = planeName === 'bg' ? CONTRAST.TEXT : CONTRAST.SECONDARY;\n for (const key of ['text', 'text-bold', 'text-muted']) {\n if (tokens[key] == null || EXEMPT_TEXT.has(key)) continue;\n const min = key === 'text-muted' ? CONTRAST.SECONDARY : textMin;\n const lc = contrastLc(resolve(tokens[key] as string), bg);\n if (lc < min) fail(appearance, `${key} Lc ${lc.toFixed(1)} < ${min} on ${planeName} ${bg}`);\n }\n // Icons carry meaning, so they hold the non-text bar everywhere. Borders\n // do not: a chromatic `border-<role>` is *decorative* — the tinted edge of\n // an alert, where the colour reinforces a message the text already makes.\n // Only a surface role's `border-bold` is the \"this boundary is the only\n // thing separating two regions\" token, so only that one is guaranteed.\n const nonText = fr.kind === 'surface' ? ['icon', 'border-bold'] : ['icon'];\n for (const key of nonText) {\n if (tokens[key] == null) continue;\n const lc = contrastLc(resolve(tokens[key] as string), bg);\n if (lc < CONTRAST.NON_TEXT)\n fail(\n appearance,\n `${key} Lc ${lc.toFixed(1)} < ${CONTRAST.NON_TEXT} on ${planeName} ${bg}`,\n );\n }\n }\n\n // The filled-surface pairing: text-on against the solid it names.\n const solid = resolve(tokens['bg-solid'] as string);\n const onLc = contrastLc(tokens['text-on'] as string, solid);\n if (onLc < CONTRAST.SECONDARY)\n fail(appearance, `text-on Lc ${onLc.toFixed(1)} < ${CONTRAST.SECONDARY} on bg-solid`);\n\n // The inverse surface, where it exists.\n if (tokens['bg-bold'] != null && tokens['text-on-bold'] != null) {\n const lc = contrastLc(resolve(tokens['text-on-bold'] as string), resolve(tokens['bg-bold']));\n if (lc < CONTRAST.TEXT)\n fail(appearance, `text-on-bold Lc ${lc.toFixed(1)} < ${CONTRAST.TEXT} on bg-bold`);\n }\n }\n return failures;\n}\n\n/**\n * Runs the same monotonicity + contrast contract `generate()`'s `build()`\n * enforces at build time (by throwing), standalone and non-throwing — for a\n * caller that needs to validate a `colors` subtree *before* handing it to\n * `generate()`, most notably the EmDash design-system-editor's save route\n * (see AGENTS.md's \"live theme editor\" section): a client's edit gets the\n * exact same accessibility gate a real build would apply, synchronously, at\n * save time, rather than only discovering a violation at the next deploy.\n *\n * Deliberately re-derives `expandPalette`/`functionalRole` independently\n * rather than sharing state with `build()` — both are cheap, pure\n * computations, and keeping this standalone means it has no dependency on\n * `generate.ts`'s CSS-bundling half (which needs lightningcss/node:fs) and so\n * stays reachable from a Worker.\n */\nexport function validateColorContract(colors: {\n palette: Record<string, unknown>;\n roles: Record<string, RoleSpec>;\n}): string[] {\n const expandedPalette = expandPalette(colors.palette);\n const scaleRoles: FunctionalRole[] = [];\n for (const [role, spec] of Object.entries(colors.roles)) {\n const hueName = roleHue(spec);\n const hue = expandedPalette[hueName];\n if (hue == null) return [`role \"${role}\" references unknown palette hue \"${hueName}\"`];\n scaleRoles.push(functionalRole(role, hueName, hue, roleKind(spec)));\n }\n\n const monotonicity = monotonicityErrors(expandedPalette);\n if (monotonicity.length) return monotonicity;\n\n const surfaceRole = scaleRoles.find((r) => r.role === 'surface' && 'bg' in r.light);\n const numericOf = (hueName: string) => expandedPalette[hueName]?.numeric ?? {};\n const resolve = (val: string): string => {\n const m = /^var\\(--color-([a-z0-9-]+)-(\\d+)\\)$/.exec(val);\n return m ? (numericOf(m[1] as string)[m[2] as never] ?? val) : val;\n };\n const planeKeys = ['bg', 'bg-muted', 'bg-x-muted'] as const;\n const surfaceBg = surfaceRole\n ? {\n light: planeKeys.flatMap((k) =>\n surfaceRole.light[k] ? [resolve(surfaceRole.light[k] as string)] : [],\n ),\n dark: planeKeys.flatMap((k) =>\n surfaceRole.dark[k] ? [resolve(surfaceRole.dark[k] as string)] : [],\n ),\n }\n : undefined;\n return scaleRoles.flatMap((fr) => checkContrast(fr, numericOf, surfaceBg));\n}\n\n/**\n * The `reverseIndex` half of `generate()`'s `design-manifest.json` (css\n * format), standalone — a CSS var name → the config path that produced it\n * (e.g. `--color-navy-500` → `colors.palette.navy.anchors.500`). Kept in exact\n * step with the block inside `generate.ts`'s `build()` that builds the same\n * map (both read `TYPO_KEYMAP`/`BASE_HOOK` from `shared.ts`, never duplicated\n * inline) — this is the standalone half for a caller that has a\n * `DesignSystem` but no built `design-manifest.json` to fetch, most notably\n * the EmDash design-system-editor plugin route: a deployed Worker has no\n * filesystem to read a manifest asset from over HTTP without adding a\n * `network:request` capability + a hostname declared in `allowedHosts` for\n * *the site's own, deployment-specific* origin — recomputing this from the\n * `DesignSystem` the site's own build already bundles avoids that entirely.\n */\nexport function buildReverseIndex(ds: Pick<DesignSystem, 'colors' | 'typography' | 'patterns'>) {\n const expandedPalette = expandPalette(ds.colors.palette as Record<string, unknown>);\n const reverseIndex: Record<string, string> = {};\n\n // Same reasoning as generate.ts's own comment: a numeric step maps back to\n // that step's `anchors` entry, never to the hue's `seed` (which regenerates\n // the whole ramp).\n for (const [name, hue] of Object.entries(expandedPalette))\n for (const n of Object.keys(hue.numeric))\n reverseIndex[`--color-${name}-${n}`] = `colors.palette.${name}.anchors.${n}`;\n\n const typoRoles = ds.typography?.roles ?? {};\n for (const role of Object.keys(typoRoles))\n for (const [key, [, sfx]] of Object.entries(TYPO_KEYMAP))\n reverseIndex[`--${role}-${sfx}`] = `typography.roles.${role}.${key}`;\n\n // Same mapping as generate.ts: layout tokens are always indexed, set or not.\n for (const [path, token] of Object.entries(LAYOUT_TOKENS)) reverseIndex[token] = `layout.${path}`;\n\n const pDefaults = ds.patterns?.defaults ?? {};\n const pRadii = ds.patterns?.radii ?? {};\n const pGroups = ds.patterns?.groups ?? {};\n const pItems = ds.patterns?.items ?? {};\n for (const prop of Object.keys(pDefaults))\n reverseIndex[`--${prop}-default`] = `patterns.defaults.${prop}`;\n for (const [group, props] of Object.entries(pGroups))\n for (const prop of Object.keys(props))\n reverseIndex[`--${prop}-${group}`] = `patterns.groups.${group}.${prop}`;\n for (const [name, item] of Object.entries(pItems)) {\n const base = item.base ?? {};\n for (const prop of Object.keys(base))\n if (prop in BASE_HOOK)\n reverseIndex[`--${BASE_HOOK[prop]}-${name}`] = `patterns.items.${name}.base.${prop}`;\n }\n // Radii last, and only where a pattern hasn't already claimed the var —\n // same collision rule as generate.ts.\n for (const name of Object.keys(pRadii))\n if (!(`--br-${name}` in reverseIndex)) reverseIndex[`--br-${name}`] = `patterns.radii.${name}`;\n\n return reverseIndex;\n}\n\n/**\n * Turns a design-system PATCH (paths matching `manifest.reverseIndex`'s own\n * config-path values, e.g. `colors.palette.navy.anchors.500`) into the runtime\n * `:root` override `<wc-theme-editor>` already injects client-side for its own\n * live preview — server-rendered instead, for the EmDash design-system-editor\n * plugin route to serve site-wide via a `page:fragments` `html` contribution\n * (see AGENTS.md's \"live theme editor\" + \"EmDash plugin as the design-system\n * editing surface\" sections). `''` (no matching var, or an empty patch) means\n * \"nothing to override\" — the caller should serve no fragment at all rather\n * than an empty `<style>` tag.\n *\n * Inverts `reverseIndex` (`cssVar → configPath`) once into `configPath →\n * cssVar`, so it costs one pass regardless of how deep the patch nests. A\n * patched leaf with no matching var (a `reverseIndex` miss — the same case\n * `<wc-theme-editor>`'s own `buildPatch()` calls \"skipped\") is silently\n * omitted here too, for the same reason: a value with nothing to preview is\n * not an error, just nothing to show yet.\n */\nexport function patchToOverrideCss(\n patch: Record<string, unknown>,\n reverseIndex: Record<string, string>,\n): string {\n const byPath = new Map<string, string>();\n for (const [cssVar, path] of Object.entries(reverseIndex)) byPath.set(path, cssVar);\n\n const lines: string[] = [];\n const walk = (node: unknown, path: string): void => {\n if (node == null || typeof node !== 'object') {\n const cssVar = byPath.get(path);\n if (cssVar && (typeof node === 'string' || typeof node === 'number'))\n lines.push(` ${cssVar}: ${node};`);\n return;\n }\n for (const [key, value] of Object.entries(node as Record<string, unknown>))\n walk(value, path ? `${path}.${key}` : key);\n };\n walk(patch, '');\n\n return lines.length ? `:root {\\n${lines.join('\\n')}\\n}` : '';\n}\n"],"mappings":"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"}
@@ -0,0 +1,242 @@
1
+ import { t as DesignSystem } from "./schema-C-P9dF9F.mjs";
2
+ /**
3
+ * Data shared between the generator (`generate.ts`) and the docs bundle
4
+ * (`docs.ts`). Lives in its own module because `generate.ts` imports `docs.ts` —
5
+ * importing back the other way would be a cycle.
6
+ */
7
+ /**
8
+ * Selector the dark functional-token flip hangs off.
9
+ *
10
+ * `data-brx-theme` is Bricks' own attribute — Bricks sets it, so it's what the
11
+ * WordPress target needs. Nothing sets it anywhere else, which meant the dark
12
+ * flip was unreachable outside Bricks: the shipped `<wc-color-scheme-toggle>` web
13
+ * component writes `documentElement.dataset.theme` (i.e. `data-theme`), so
14
+ * clicking "Dark" changed an attribute no rule matched. Matching both makes the
15
+ * component work on every target without changing what Bricks already does.
16
+ *
17
+ * This covers the EXPLICIT choice. The OS preference is a second, opt-in block —
18
+ * see `SYSTEM_DARK_SEL` — because turning it on flips a site dark for dark-OS
19
+ * users, which is the site's decision rather than the design system's.
20
+ *
21
+ * Shared so the docs quote the selector the generator actually emits.
22
+ */
23
+ declare const DARK_SEL = ":root[data-brx-theme=\"dark\"], :root[data-theme=\"dark\"]";
24
+ /**
25
+ * Roles the shipped CSS partials reference with NO fallback, so a config that
26
+ * omits one leaves those rules resolving to nothing — an invalid declaration,
27
+ * not a graceful degrade.
28
+ *
29
+ * `colors.roles` is an open record: any name works and generates a full token
30
+ * set, utilities and dark flip. But the framework's own component CSS is
31
+ * written against these six, so "roles are arbitrary" is only half true — they
32
+ * are *extensible*, over a required core. `validate()` warns when one is
33
+ * missing; `required-roles.test.ts` re-derives this list from the partials so
34
+ * it cannot drift as components are added.
35
+ *
36
+ * Kept as a constant rather than scanned at call time because `validate()` is
37
+ * a pure function — the CLI, the Vite plugin and `toJSONSchema` all import it,
38
+ * and none of them should touch the filesystem to answer "is this config ok?".
39
+ */
40
+ declare const REQUIRED_ROLES: readonly string[];
41
+ /**
42
+ * Every token key a role can resolve to, across both role kinds. Shared so the
43
+ * required-roles drift test greps for exactly what the generator emits.
44
+ *
45
+ * These are *keys*, not var names: the role sits in the middle of the property
46
+ * (`--color-bg-danger-muted`), so compose them with `tokenVar()` from `tokens.ts`
47
+ * rather than concatenating by hand.
48
+ */
49
+ declare const ROLE_TOKEN_KEYS: readonly string[];
50
+ /** How a role is authored in `colors.roles`: a bare hue, or a hue plus its kind. */
51
+ interface RoleSpecObject {
52
+ hue: string;
53
+ kind?: 'surface' | 'chromatic' | undefined;
54
+ /** Ramp step the solid family starts from (default 600). */
55
+ solid?: number | string | undefined;
56
+ /** Polarity of `text-on-<role>`; absent = whichever has more contrast. */
57
+ foreground?: 'light' | 'dark' | undefined;
58
+ }
59
+ type RoleSpec = string | RoleSpecObject;
60
+ /** The solid step / foreground polarity a role asked for (both optional). */
61
+ declare const roleSolidOptions: (spec: RoleSpec) => {
62
+ solid?: string;
63
+ foreground?: "light" | "dark";
64
+ };
65
+ /** The palette hue a role points at, through either authoring form. */
66
+ declare const roleHue: (spec: RoleSpec) => string;
67
+ /**
68
+ * A role's kind. The bare-string shorthand means `chromatic`, because signal
69
+ * colours are the common case and surfaces are the two or three you name once.
70
+ */
71
+ declare const roleKind: (spec: RoleSpec) => "surface" | "chromatic";
72
+ /**
73
+ * Pattern base geometry props → per-pattern override-hook shorthand. Each hook
74
+ * wraps the authored value (`padding: var(--p-btn, 0.4em 0.8em)`) so a
75
+ * consumer restyles one pattern by setting one variable. The hook is named after
76
+ * the pattern's key, not its class.
77
+ */
78
+ declare const BASE_HOOK: Record<string, string>;
79
+ /**
80
+ * A typography role's declared key → [CSS property, var-name suffix]. The
81
+ * recognised key set is CLOSED (an unrecognised key is a config error, see
82
+ * `generate.ts`'s `ALSO_MEANT`) — shared here (rather than living only in
83
+ * `generate.ts`) because `tokens.ts`'s `buildReverseIndex` needs the exact
84
+ * same key → suffix mapping to stay in step with what `generate()` itself
85
+ * builds, and `tokens.ts` can't import from `generate.ts` (the CSS-bundling
86
+ * half, which needs lightningcss/node:fs) without losing its own
87
+ * Worker-safety.
88
+ */
89
+ declare const TYPO_KEYMAP: Record<string, [string, string]>;
90
+ /**
91
+ * Framework utility classes whose names collide with Tailwind's own defaults.
92
+ * The `tailwind` format strips any rule targeting these — Tailwind provides
93
+ * them, so consumers use Tailwind's utilities for these names instead.
94
+ */
95
+ declare const TW_CLASH: ReadonlySet<string>;
96
+ declare const NUMERIC_STEPS: readonly ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
97
+ type NumStep = (typeof NUMERIC_STEPS)[number];
98
+ interface ExpandedHue {
99
+ /** Numeric steps (sparse for fixed-tone kits). */
100
+ numeric: Partial<Record<NumStep, string>>;
101
+ /** True when only authored tones + endpoints exist (no interpolation). */
102
+ sparse?: boolean;
103
+ /**
104
+ * Steps the author pinned, and how far each sits from the ladder's lightness.
105
+ * A pinned colour is reproduced exactly, so a large value here is legal — but
106
+ * it means this hue reads heavier or lighter than its siblings at that step,
107
+ * which the generator surfaces as a warning.
108
+ */
109
+ deviations?: Partial<Record<NumStep, number>>;
110
+ }
111
+ /** Expand every palette hue into its numeric scale. */
112
+ declare function expandPalette(raw: Record<string, unknown>): Record<string, ExpandedHue>;
113
+ declare function ladderWarnings(palette: Record<string, ExpandedHue>,
114
+ /** The authored palette, to honour a hue's `acknowledgeOffLadder: true`. */
115
+ authored?: Record<string, unknown>): string[];
116
+ /**
117
+ * Ramps whose lightness does not fall monotonically from 50 to 950.
118
+ *
119
+ * A ramp built from the ladder alone cannot invert — only pinned colours (a seed,
120
+ * an `anchor`, a `tones` entry) can, because they are the one thing allowed off
121
+ * it. `ladderWarnings` will not catch it: it compares each pinned step against
122
+ * *its own* ladder rung, never against its neighbours, so it reports the wrong
123
+ * defect ("reads darker than others at 600") while the real problem is that 600
124
+ * is darker than 700. And since `LADDER_TOLERANCE` is 0.03 — the same size as the
125
+ * ladder's own 50→100 gap — two anchors can each sit within tolerance and still
126
+ * invert, with no warning at all.
127
+ *
128
+ * This is an error rather than a warning because the rest of the system assumes
129
+ * the order: `snap` picks the nearest step by ladder lightness, the mode-stable
130
+ * solid family hard-codes 600/700/800 as increasingly dark, and both dark tables
131
+ * re-point steps on the same assumption. An inverted ramp therefore runs hover
132
+ * states backwards — the failure the fixed ladder was introduced to eliminate.
133
+ */
134
+ declare function monotonicityErrors(palette: Record<string, ExpandedHue>): string[];
135
+ /** Which shape of token set a role gets. */
136
+ type RoleKind = 'surface' | 'chromatic';
137
+ /**
138
+ * Map a token key to its CSS custom property for a given role.
139
+ *
140
+ * `on` is the one irregular case: the foreground for a filled surface reads
141
+ * `--color-text-on-<role>`, with the role *after* `on`, because it names what it
142
+ * sits on rather than what it is.
143
+ *
144
+ * The utility class name is this minus the `--color-` prefix, which is why the
145
+ * emitter and the linter can both derive their vocabulary from one function.
146
+ */
147
+ declare function tokenVar(role: string, key: string): string;
148
+ /** The class name a token key produces for a role (`bg-danger-solid`, …). */
149
+ declare const tokenClass: (role: string, key: string) => string;
150
+ interface FunctionalRole {
151
+ role: string;
152
+ hue: string;
153
+ kind: RoleKind;
154
+ /** token key → CSS value, per appearance. */
155
+ light: Record<string, string>;
156
+ dark: Record<string, string>;
157
+ }
158
+ /** Resolve the token set for one role over its hue. */
159
+ declare function functionalRole(role: string, hueName: string, hue: ExpandedHue, kind?: RoleKind, opts?: {
160
+ solid?: string;
161
+ foreground?: 'light' | 'dark';
162
+ }): FunctionalRole;
163
+ /**
164
+ * APCA targets. `TEXT` is the body-text bar; `SECONDARY` covers text on a
165
+ * non-primary surface and de-emphasised text. `NON_TEXT` is the icon/boundary
166
+ * tier — the APCA analogue of WCAG's 3:1, where a glyph may run more vivid than
167
+ * text because shape carries meaning alongside contrast.
168
+ */
169
+ declare const CONTRAST: {
170
+ readonly TEXT: 75;
171
+ readonly SECONDARY: 60;
172
+ readonly NON_TEXT: 45;
173
+ };
174
+ /**
175
+ * Verify a role's contrast targets. Returns human-readable failures.
176
+ *
177
+ * Chromatic roles are checked against the surface planes they actually sit on
178
+ * (`surfaceBg`) as well as their own tints — coloured text almost never appears
179
+ * over its own solid, it appears over the page. Checking only within the role
180
+ * would have missed the pairing that matters most.
181
+ */
182
+ declare function checkContrast(fr: FunctionalRole, numericOf: (hueName: string) => Partial<Record<NumStep, string>>, surfaceBg?: {
183
+ light: string[];
184
+ dark: string[];
185
+ },
186
+ /** Appearances to judge; a dark-only site passes `['dark']`. Default: both. */
187
+ appearances?: readonly ('light' | 'dark')[]): string[];
188
+ /**
189
+ * Runs the same monotonicity + contrast contract `generate()`'s `build()`
190
+ * enforces at build time (by throwing), standalone and non-throwing — for a
191
+ * caller that needs to validate a `colors` subtree *before* handing it to
192
+ * `generate()`, most notably the EmDash design-system-editor's save route
193
+ * (see AGENTS.md's "live theme editor" section): a client's edit gets the
194
+ * exact same accessibility gate a real build would apply, synchronously, at
195
+ * save time, rather than only discovering a violation at the next deploy.
196
+ *
197
+ * Deliberately re-derives `expandPalette`/`functionalRole` independently
198
+ * rather than sharing state with `build()` — both are cheap, pure
199
+ * computations, and keeping this standalone means it has no dependency on
200
+ * `generate.ts`'s CSS-bundling half (which needs lightningcss/node:fs) and so
201
+ * stays reachable from a Worker.
202
+ */
203
+ declare function validateColorContract(colors: {
204
+ palette: Record<string, unknown>;
205
+ roles: Record<string, RoleSpec>;
206
+ }): string[];
207
+ /**
208
+ * The `reverseIndex` half of `generate()`'s `design-manifest.json` (css
209
+ * format), standalone — a CSS var name → the config path that produced it
210
+ * (e.g. `--color-navy-500` → `colors.palette.navy.anchors.500`). Kept in exact
211
+ * step with the block inside `generate.ts`'s `build()` that builds the same
212
+ * map (both read `TYPO_KEYMAP`/`BASE_HOOK` from `shared.ts`, never duplicated
213
+ * inline) — this is the standalone half for a caller that has a
214
+ * `DesignSystem` but no built `design-manifest.json` to fetch, most notably
215
+ * the EmDash design-system-editor plugin route: a deployed Worker has no
216
+ * filesystem to read a manifest asset from over HTTP without adding a
217
+ * `network:request` capability + a hostname declared in `allowedHosts` for
218
+ * *the site's own, deployment-specific* origin — recomputing this from the
219
+ * `DesignSystem` the site's own build already bundles avoids that entirely.
220
+ */
221
+ declare function buildReverseIndex(ds: Pick<DesignSystem, 'colors' | 'typography' | 'patterns'>): Record<string, string>;
222
+ /**
223
+ * Turns a design-system PATCH (paths matching `manifest.reverseIndex`'s own
224
+ * config-path values, e.g. `colors.palette.navy.anchors.500`) into the runtime
225
+ * `:root` override `<wc-theme-editor>` already injects client-side for its own
226
+ * live preview — server-rendered instead, for the EmDash design-system-editor
227
+ * plugin route to serve site-wide via a `page:fragments` `html` contribution
228
+ * (see AGENTS.md's "live theme editor" + "EmDash plugin as the design-system
229
+ * editing surface" sections). `''` (no matching var, or an empty patch) means
230
+ * "nothing to override" — the caller should serve no fragment at all rather
231
+ * than an empty `<style>` tag.
232
+ *
233
+ * Inverts `reverseIndex` (`cssVar → configPath`) once into `configPath →
234
+ * cssVar`, so it costs one pass regardless of how deep the patch nests. A
235
+ * patched leaf with no matching var (a `reverseIndex` miss — the same case
236
+ * `<wc-theme-editor>`'s own `buildPatch()` calls "skipped") is silently
237
+ * omitted here too, for the same reason: a value with nothing to preview is
238
+ * not an error, just nothing to show yet.
239
+ */
240
+ declare function patchToOverrideCss(patch: Record<string, unknown>, reverseIndex: Record<string, string>): string;
241
+ export { roleHue as C, TYPO_KEYMAP as S, roleSolidOptions as T, DARK_SEL as _, RoleKind as a, RoleSpec as b, expandPalette as c, monotonicityErrors as d, patchToOverrideCss as f, BASE_HOOK as g, validateColorContract as h, NUMERIC_STEPS as i, functionalRole as l, tokenVar as m, ExpandedHue as n, buildReverseIndex as o, tokenClass as p, FunctionalRole as r, checkContrast as s, CONTRAST as t, ladderWarnings as u, REQUIRED_ROLES as v, roleKind as w, TW_CLASH as x, ROLE_TOKEN_KEYS as y };
242
+ //# sourceMappingURL=tokens-DH5cpvKd.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens-DH5cpvKd.d.mts","names":[],"sources":["../src/shared.ts","../src/tokens.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;cAsBa;;;;;;;;;;;;;;;;;cAmCA;;;;;;;;;cAiBA;;UA+BI;EACf;EACA;;EAEA;;EAEA;;KAEU,oBAAoB;;cAGnB,mBACX,MAAM;EACH;EAAgB;;;cASR,UAAW,MAAM;;;;;cAMjB,WAAY,MAAM;;;;;;;cASlB,WAAW;;;;;;;;;;;cAuBX,aAAa;;;;;;cAkBb,UAAU;cClIV;KAaR,kBAAkB;UAWN;;EAEf,SAAS,QAAQ,OAAO;;EAExB;;;;;;;EAOA,aAAa,QAAQ,OAAO;;;iBAwHd,cAAc,KAAK,0BAA0B,eAAe;iBAmB5D,eACd,SAAS,eAAe;;AAExB,WAAU;;;;;;;;;;;;;;;;;;;iBA6CI,mBAAmB,SAAS,eAAe;;KA0C/C;;;;;;;;;;;iBA8FI,SAAS,cAAc;;cAU1B,aAAc,cAAc;UAGxB;EACf;EACA;EACA,MAAM;;EAEN,OAAO;EACP,MAAM;;;iBAIQ,eACd,cACA,iBACA,KAAK,aACL,OAAM,UACN;EAAQ;EAAgB;IACvB;;;;;;;cAkDU;;;;;;;;;;;;;iBAiBG,cACd,IAAI,gBACJ,YAAY,oBAAoB,QAAQ,OAAO,mBAC/C;EAAc;EAAiB;;;AAE/B;;;;;;;;;;;;;;;;iBA2Fc,sBAAsB;EACpC,SAAS;EACT,OAAO,eAAe;;;;;;;;;;;;;;;;iBAgDR,kBAAkB,IAAI,KAAK,sDAAmD;;;;;;;;;;;;;;;;;;;iBA4D9E,mBACd,OAAO,yBACP,cAAc"}
package/dist/tokens.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as RoleKind, c as expandPalette, d as monotonicityErrors, f as patchToOverrideCss, h as validateColorContract, i as NUMERIC_STEPS, l as functionalRole, m as tokenVar, n as ExpandedHue, o as buildReverseIndex, p as tokenClass, r as FunctionalRole, s as checkContrast, t as CONTRAST, u as ladderWarnings } from "./tokens-8SXMDAd1.mjs";
1
+ import { a as RoleKind, c as expandPalette, d as monotonicityErrors, f as patchToOverrideCss, h as validateColorContract, i as NUMERIC_STEPS, l as functionalRole, m as tokenVar, n as ExpandedHue, o as buildReverseIndex, p as tokenClass, r as FunctionalRole, s as checkContrast, t as CONTRAST, u as ladderWarnings } from "./tokens-DH5cpvKd.mjs";
2
2
  export { CONTRAST, ExpandedHue, FunctionalRole, NUMERIC_STEPS, RoleKind, buildReverseIndex, checkContrast, expandPalette, functionalRole, ladderWarnings, monotonicityErrors, patchToOverrideCss, tokenClass, tokenVar, validateColorContract };
package/dist/tokens.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as expandPalette, c as monotonicityErrors, d as tokenVar, f as validateColorContract, i as checkContrast, l as patchToOverrideCss, n as NUMERIC_STEPS, o as functionalRole, r as buildReverseIndex, s as ladderWarnings, t as CONTRAST, u as tokenClass } from "./tokens-zi594IZ3.mjs";
1
+ import { a as expandPalette, c as monotonicityErrors, d as tokenVar, f as validateColorContract, i as checkContrast, l as patchToOverrideCss, n as NUMERIC_STEPS, o as functionalRole, r as buildReverseIndex, s as ladderWarnings, t as CONTRAST, u as tokenClass } from "./tokens-CaIGQQLk.mjs";
2
2
 
3
3
  export { CONTRAST, NUMERIC_STEPS, buildReverseIndex, checkContrast, expandPalette, functionalRole, ladderWarnings, monotonicityErrors, patchToOverrideCss, tokenClass, tokenVar, validateColorContract };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/generator",
3
- "version": "8.2.0-alpha.0",
3
+ "version": "8.3.0-alpha.0",
4
4
  "description": "Generate design-system outputs (Bricks, standalone CSS, Tailwind v4) from a design-system.json.",
5
5
  "homepage": "https://docs.vitops.ca",
6
6
  "bugs": "https://github.com/getvitops/design-system/issues",
@@ -45,15 +45,16 @@
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
+ "@getvitops/config": "8.3.0-alpha.0",
49
+ "@getvitops/utils": "8.3.0-alpha.0",
48
50
  "@iconify/utils": "^2.3.0",
49
51
  "@zod/mini": "^4.6.5",
50
52
  "lightningcss": "^1.32.0",
51
- "zod": "^4.6.5",
52
- "@getvitops/utils": "8.2.0-alpha.0"
53
+ "zod": "^4.6.7"
53
54
  },
54
55
  "devDependencies": {
55
- "@iconify/types": "^2.0.0",
56
- "@getvitops/core": "8.2.0-alpha.0"
56
+ "@getvitops/core": "8.3.0-alpha.0",
57
+ "@iconify/types": "^2.0.0"
57
58
  },
58
59
  "engines": {
59
60
  "node": ">=24"
package/schema.json CHANGED
@@ -48,6 +48,10 @@
48
48
  "type": "string"
49
49
  },
50
50
  "description": "Step → colour overrides (hex or oklch()) pinned VERBATIM at those steps. Every other step takes its lightness from the shared ladder, with chroma and hue interpolated between the anchors — so an anchor is reproduced exactly and is the only step allowed off the ladder. An explicit anchor overrides the seed at that step; two anchors that resolve to the same step are an error."
51
+ },
52
+ "acknowledgeOffLadder": {
53
+ "type": "boolean",
54
+ "description": "Acknowledge that this hue deliberately pins colours off the shared lightness ladder (typically to match an existing brand palette exactly). Silences the per-step \"off the ladder\" build warnings for THIS hue only; the monotonicity and contrast checks still run."
51
55
  }
52
56
  },
53
57
  "required": [
@@ -78,6 +82,10 @@
78
82
  }
79
83
  ],
80
84
  "description": "Fixed brand kit: authored tones placed verbatim at their nearest steps plus tinted off-white/off-black endpoints; no interpolation. Either an ordered light → dark array or a step → colour map."
85
+ },
86
+ "acknowledgeOffLadder": {
87
+ "type": "boolean",
88
+ "description": "Acknowledge that this hue deliberately pins colours off the shared lightness ladder (typically to match an existing brand palette exactly). Silences the per-step \"off the ladder\" build warnings for THIS hue only; the monotonicity and contrast checks still run."
81
89
  }
82
90
  },
83
91
  "required": [
@@ -116,6 +124,21 @@
116
124
  "chromatic"
117
125
  ],
118
126
  "description": "`surface` — a page/panel colour: gets a bare `bg-<role>` plus the full emphasis range and text scale. `chromatic` (default) — a signal colour: tints and solids only, no bare `bg-<role>`."
127
+ },
128
+ "solid": {
129
+ "description": "Ramp step (100…900) the role's solid family starts from; default 600. `bg-<role>-solid` is this step and `-solid-bold` / `-solid-x-bold` walk one and two rungs DARKER, so `solid: 400` gives 400 → 500 → 600. Use it when the brand colour is bright and lives lower on the ramp (a yellow at 400) instead of reaching for raw `bg-<hue>-<step>` classes. Like the default family it is the same in light and dark. The contrast check still runs on the pair this produces.",
130
+ "type": [
131
+ "number",
132
+ "string"
133
+ ]
134
+ },
135
+ "foreground": {
136
+ "type": "string",
137
+ "enum": [
138
+ "light",
139
+ "dark"
140
+ ],
141
+ "description": "Polarity of `text-on-<role>`, the foreground for a filled surface. Default is whichever of near-white / near-black has more contrast on the solid. Set `\"dark\"` so a bright fill carries dark text (or `\"light\"` for the reverse). The build still fails if the forced pair is below APCA Lc 60."
119
142
  }
120
143
  },
121
144
  "required": [
@@ -125,7 +148,7 @@
125
148
  }
126
149
  ]
127
150
  },
128
- "description": "Maps semantic role names onto palette hues. Role names are ARBITRARY — add a key and the generator emits that role's token set (`--color-<target>-<role>[-<variant>]` for target bg/text/icon/border), its dark-mode flip and its utility classes.\n\nA value is either a hue name (`\"danger\": \"rust\"`) or `{ \"hue\": …, \"kind\": \"surface\" | \"chromatic\" }`. **The kind decides the shape of the token set.** `chromatic` (the default, and what the bare-string form means) is a signal colour: its backgrounds split into tints (`bg-<role>-x-muted`/`-muted`) and solids (`bg-<role>-solid[-bold|-x-bold]`), with deliberately **no bare `bg-<role>`** — \"how loud?\" is a question the author answers. `surface` is a page/panel colour: it has a bare `bg-<role>` plus the full emphasis range and text scale.\n\nDark mode flips automatically; there is no per-appearance scheme grammar. The solid family and its computed `text-on-<role>` foreground stay mode-stable so a filled button keeps its identity. Six roles are a required core, because the shipped framework CSS references them with no fallback: brand-primary, danger, neutral, surface, ui-primary, warning. Conventional additions are ui-secondary/accent, brand-secondary, info and success."
151
+ "description": "Maps semantic role names onto palette hues. Role names are ARBITRARY — add a key and the generator emits that role's token set (`--color-<target>-<role>[-<variant>]` for target bg/text/icon/border), its dark-mode flip and its utility classes.\n\nA value is either a hue name (`\"danger\": \"rust\"`) or `{ \"hue\": …, \"kind\": \"surface\" | \"chromatic\" }`. **The kind decides the shape of the token set.** `chromatic` (the default, and what the bare-string form means) is a signal colour: its backgrounds split into tints (`bg-<role>-x-muted`/`-muted`) and solids (`bg-<role>-solid[-bold|-x-bold]`), with deliberately **no bare `bg-<role>`** — \"how loud?\" is a question the author answers. `surface` is a page/panel colour: it has a bare `bg-<role>` plus the full emphasis range and text scale.\n\nDark mode flips automatically; there is no per-appearance scheme grammar. The solid family and its computed `text-on-<role>` foreground stay mode-stable so a filled button keeps its identity. \n\n**Contrast is enforced at build time, in both appearances** (APCA Lc): body text ≥ 75, secondary and muted text (`text-muted`) ≥ **60 against the page plane** it sits on, icons and guaranteed boundaries ≥ 45, and `text-on-<role>` ≥ 60 on the role's solid. So a mockup's muted grey that lands near Lc 57 fails the build — lighten the anchor on a dark page (or darken it on a light one) until it clears 60. `text-x-muted`/`text-xx-muted` are exempt (placeholders, disabled). A role can move its fill off the default step 600 with `solid`, and pin the polarity of its on-colour with `foreground`; a dark-only site (`defaultColorScheme: \"dark\"`) is only checked in dark. Six roles are a required core, because the shipped framework CSS references them with no fallback: brand-primary, danger, neutral, surface, ui-primary, warning. Conventional additions are ui-secondary/accent, brand-secondary, info and success."
129
152
  },
130
153
  "utilities": {
131
154
  "type": "array",
@@ -152,13 +175,31 @@
152
175
  "description": "The colour system (the only required section): `palette` hues become generated OKLCH scales; `roles` map semantic roles onto those hues, from which all role tokens and dark mode derive."
153
176
  },
154
177
  "shadows": {
155
- "description": "Named shadows → `--shadow-<name>` tokens and `.drop-shadow-<name>` utilities. Values are shadow parameter lists (offset/blur/colour). Each token feeds two consumers with different grammars — `box-shadow` (pattern geometry, via the `--ds-*` group aliases) and `filter: drop-shadow(…)` (the utilities and the `shadow:` state shortcut) — so values must stay in the intersection: **one layer, no spread radius, no `inset`**. `drop-shadow()` rejects all three, and rejecting them invalidates the whole filter, so the shadow vanishes rather than degrading.",
178
+ "description": "Named shadows → `--shadow-<name>` tokens and `.drop-shadow-<name>` utilities. A value is either a string (below) or `{ \"box\"?: string, \"drop\"?: string }` for a shadow that needs the two grammars to differ: `box` is what `--shadow-<name>` holds and what `box-shadow` consumers (pattern geometry, `--ds-*`) read, so it MAY use spread, `inset` and several layers; `drop` is emitted as `--shadow-<name>-drop` and is what `.drop-shadow-<name>` and the `shadow:` state shortcut read, so it must obey the drop-shadow() limits. Omit `drop` and no drop-shadow utility is emitted for that name; omit `box` and `--shadow-<name>` takes the `drop` value. The plain string form keeps working and feeds both consumers from one value. For that string form: each token feeds two consumers with different grammars — `box-shadow` (pattern geometry, via the `--ds-*` group aliases) and `filter: drop-shadow(…)` (the utilities and the `shadow:` state shortcut) — so values must stay in the intersection: **one layer, no spread radius, no `inset`**. `drop-shadow()` rejects all three, and rejecting them invalidates the whole filter, so the shadow vanishes rather than degrading.",
156
179
  "type": "object",
157
180
  "propertyNames": {
158
181
  "type": "string"
159
182
  },
160
183
  "additionalProperties": {
161
- "type": "string"
184
+ "anyOf": [
185
+ {
186
+ "type": "string"
187
+ },
188
+ {
189
+ "type": "object",
190
+ "properties": {
191
+ "box": {
192
+ "type": "string",
193
+ "description": "`box-shadow` value: spread, `inset` and multiple layers allowed."
194
+ },
195
+ "drop": {
196
+ "type": "string",
197
+ "description": "`drop-shadow()` value: one layer, at most three lengths, no `inset`."
198
+ }
199
+ },
200
+ "additionalProperties": false
201
+ }
202
+ ]
162
203
  }
163
204
  },
164
205
  "fonts": {
@@ -172,7 +213,7 @@
172
213
  }
173
214
  },
174
215
  "typeScale": {
175
- "description": "Fluid modular TYPE scale → `--text-<name>` tokens, consumed by typography roles and text-size utilities.",
216
+ "description": "Fluid modular TYPE scale → `--text-<name>` tokens, consumed by typography roles and text-size utilities.\n\n**Recipe for marketing headings:** one ratio cannot serve both a ~16px body and a ~76px hero — a ratio big enough for the hero collapses the small end to unreadable. Extend `names` upward instead of raising the ratio: keep `base` ≈ \"1rem\" at the body step, `ratio` ≈ 1.2 at `maxVw` (≈ 1.125–1.15 `minRatio` at `minVw` so headings tame on phones), and add names (`4xl`, `5xl`) for the display sizes — the steps above `baseStep` are what headings read, the steps below it only need `xs`/`sm`. Aim the largest named step at your hero (≈ 4.5–5rem at `maxVw`), then point the `display`/`title` roles at those steps rather than hand-writing `clamp()` in a role. If a step still needs its own value, set that role's `size` literally — roles may bypass the scale.",
176
217
  "type": "object",
177
218
  "properties": {
178
219
  "base": {
@@ -233,6 +274,55 @@
233
274
  ],
234
275
  "additionalProperties": false
235
276
  },
277
+ "layout": {
278
+ "type": "object",
279
+ "properties": {
280
+ "widths": {
281
+ "type": "object",
282
+ "properties": {
283
+ "measure": {
284
+ "type": "string",
285
+ "description": "Reading column → `--width-measure` (framework default 65ch)."
286
+ },
287
+ "breakout": {
288
+ "type": "string",
289
+ "description": "`.breakout` track → `--width-breakout` (default 90ch)."
290
+ },
291
+ "spotlight": {
292
+ "type": "string",
293
+ "description": "`.spotlight` track → `--width-spotlight` (default 120ch)."
294
+ },
295
+ "nav": {
296
+ "type": "string",
297
+ "description": "Navigation column beside content → `--width-nav` (default 16rem)."
298
+ }
299
+ },
300
+ "additionalProperties": false,
301
+ "description": "Content track widths for `.centered`. In `ch` so the reading measure stays character-based; keep measure ≤ breakout ≤ spotlight."
302
+ },
303
+ "regionSpace": {
304
+ "type": "object",
305
+ "properties": {
306
+ "min": {
307
+ "type": "string",
308
+ "description": "Floor → `--region-space-min` (default 3rem)."
309
+ },
310
+ "preferred": {
311
+ "type": "string",
312
+ "description": "Preferred value → `--region-space` (default 8vh)."
313
+ },
314
+ "max": {
315
+ "type": "string",
316
+ "description": "Ceiling → `--region-space-max` (default 8rem)."
317
+ }
318
+ },
319
+ "additionalProperties": false,
320
+ "description": "Vertical padding of `.region`: `padding-block: clamp(min, preferred, max)`."
321
+ }
322
+ },
323
+ "additionalProperties": false,
324
+ "description": "Optional layout tokens. Each key you set is emitted as a `:root` custom property (css, bricks and tailwind formats); each you omit keeps the framework default. Lets the live editor tune widths and region spacing instead of a hand-written override stylesheet."
325
+ },
236
326
  "spaceScale": {
237
327
  "description": "Fluid modular SPACE scale → `--space-<name>` tokens, consumed by spacing/gap utilities and vertical rhythm.",
238
328
  "type": "object",
@@ -454,7 +544,7 @@
454
544
  "number"
455
545
  ]
456
546
  },
457
- "description": "A bag of CSS-ish keys, each mapped to a declaration plus a `--<role>-<sfx>` override hook. The recognised set is **closed**: `family`, `size`, `weight`, `style`, `line-height`, `tracking` (→ `letter-spacing`), `text-transform`, `text-decoration`, `text-wrap`, `color`. Note the last four are spelled with their full CSS property names — `transform` and `decoration` are NOT accepted. Anything unrecognised is **ignored**, not passed through, so the generator warns rather than emitting it: a silently-dropped `transform: uppercase` is how title-case navigation reaches production. Note also that `style`, `text-transform`, `text-decoration` and `text-wrap` are emitted on **every** role at their identity value (`normal`/`none`/`none`/`wrap`) whether declared or not, so applying one role class over another fully resets it — which means **omitting `text-wrap` is not \"inherit\"**: it emits `text-wrap: wrap` and cancels the `pretty` the role would otherwise inherit from a `pretty` ancestor such as a `body`-mapped role. Declare it on every role — `balance` for heading-like roles, `pretty` for copy, `wrap` for short single-line labels."
547
+ "description": "A bag of CSS-ish keys, each mapped to a declaration plus a `--<role>-<sfx>` override hook. The recognised set is **closed**: `family`, `size`, `weight`, `style`, `line-height`, `tracking` (→ `letter-spacing`), `text-transform`, `text-decoration`, `text-wrap`, `color`. The canonical spellings of the last four are their full CSS property names (`text-transform`, `text-decoration`, …); `transform` and `decoration` are accepted as aliases of the first two and normalised to them. Anything unrecognised is **ignored**, not passed through, so the generator warns rather than emitting it: a silently-dropped key (a typo for `text-transform`, say) is how title-case navigation reaches production. Note also that `style`, `text-transform`, `text-decoration` and `text-wrap` are emitted on **every** role at their identity value (`normal`/`none`/`none`/`wrap`) whether declared or not, so applying one role class over another fully resets it — which means **omitting `text-wrap` is not \"inherit\"**: it emits `text-wrap: wrap` and cancels the `pretty` the role would otherwise inherit from a `pretty` ancestor such as a `body`-mapped role. Declare it on every role — `balance` for heading-like roles, `pretty` for copy, `wrap` for short single-line labels."
458
548
  }
459
549
  },
460
550
  "headings": {