@bison-lab/tokens 3.1.0 → 3.3.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/dist/index.d.mts CHANGED
@@ -89,7 +89,37 @@ interface SemanticPalette {
89
89
  input: string;
90
90
  ring: string;
91
91
  }
92
+ /**
93
+ * The light semantic palette for a brand. This is the one derivation the
94
+ * CLI (`bison-theme.css`), `BisonProvider` and a CMS theme all share.
95
+ *
96
+ * Decided in BIS-57, where the CLI and this module had drifted:
97
+ * - `muted-foreground` is grey-600, not grey-500. grey-500 is 4.6:1 on the
98
+ * page background, and the token carries card descriptions, hints, table
99
+ * labels and footer lines; grey-600 is 7.4:1. Spinal Simplicity overrode
100
+ * it by hand for its patient audience.
101
+ * - `content-secondary` is grey-500, not grey-400. It is text: the
102
+ * progress bar's percentage label and every chart axis tick use it, and
103
+ * grey-400 is about 2.5:1 on the page background on every scale. grey-500
104
+ * is 4.5:1 or better on both the page background and a card.
105
+ */
92
106
  declare function deriveLightPalette(brand: BrandColors): SemanticPalette;
107
+ /**
108
+ * The dark semantic palette for a brand, derived from the same inputs as
109
+ * the light one: no manual dark palette.
110
+ *
111
+ * Decided in BIS-57, where the CLI and this module had drifted:
112
+ * - Brand fills lighten only below lightness 40 (`DARK_FILL_LIGHTEN_BELOW`);
113
+ * the ring lightens below 50 (`DARK_RING_LIGHTEN_BELOW`). See both.
114
+ * - `muted-foreground` is grey-300, not grey-400. grey-400 is 6.9:1 on
115
+ * `card`, where most supporting copy sits; grey-300 clears 7:1 on both
116
+ * surfaces.
117
+ * - `content-secondary` is grey-400, not grey-500. grey-500 on `card` is
118
+ * about 3.7:1, under AA; grey-400 is about 7:1.
119
+ * - `destructive` is `0 62% 30%`, not `0 84% 65%`. `destructive-foreground`
120
+ * (`0 0% 98%`) is 9.8:1 on the former and 3.2:1 on the latter, which
121
+ * fails AA for the button label.
122
+ */
93
123
  declare function deriveDarkPalette(brand: BrandColors): SemanticPalette;
94
124
  declare function generatePaletteCSS(brand: BrandColors, attribute?: "class" | "data-theme"): string;
95
125
  //#endregion
@@ -409,5 +439,129 @@ interface AnimationPresets {
409
439
  */
410
440
  declare function getAnimationPresets(preset?: MotionPreset): AnimationPresets;
411
441
  //#endregion
412
- export { type AnimationPresets, type BrandColors, type BrandScales, type ColorScale, DESTRUCTIVE_SCALE_HEX, type DensityPreset, type FontWeightMap, GREY_SCALES, type GreyScale, type HSL, type MotionPreset, type MotionVariant, type OKLCH, type RadiusPreset, SHADE_STEPS, type SemanticPalette, type ShadeStep, type ShadowPreset, type StaggerContainerVariant, type ZIndexToken, defaultFontWeights, densityPresets, deriveDarkPalette, deriveLightPalette, generateBrandScales, generateColorScale, generateDestructiveScaleCSS, generatePaletteCSS, generateScaleCSS, getAnimationPresets, getDestructiveColorScale, hexToHSL, hexToOklch, hslToString, motionPresets, oklchToHslString, radiusPresets, shadowPresets, spacingScale, typeScale, zIndexTokens };
442
+ //#region src/theme.d.ts
443
+ interface ThemeConfig {
444
+ /** `#rrggbb` */
445
+ brandPrimary: string;
446
+ brandSecondary: string;
447
+ brandAccent: string;
448
+ brandHighlight: string;
449
+ brandSuccess: string;
450
+ greyScale: GreyScale;
451
+ /**
452
+ * The selector the dark block is written under, or `"@media"` for a
453
+ * `prefers-color-scheme` query. Defaults to `[data-theme="dark"]`, which
454
+ * is what `ThemeProvider` sets and the Tailwind preset's `dark:` variant
455
+ * reads.
456
+ */
457
+ darkSelector?: string;
458
+ defaultTheme: "light" | "dark" | "system";
459
+ radius: RadiusPreset;
460
+ shadow: ShadowPreset;
461
+ motion: MotionPreset;
462
+ density: DensityPreset;
463
+ /** A font family name. Becomes a catalogue id in BIS-58. */
464
+ fontBody: string;
465
+ /** A font family name, or `null` to use `fontBody` for headings. */
466
+ fontHeading: string | null;
467
+ fontWeights: {
468
+ normal: number;
469
+ medium: number;
470
+ semibold: number;
471
+ bold: number;
472
+ };
473
+ }
474
+ interface BuildThemeCssOptions {
475
+ /**
476
+ * Overrides `config.darkSelector`: `"class"` writes the dark block under
477
+ * `.dark`, `"data-theme"` under `[data-theme="dark"]`.
478
+ */
479
+ attribute?: "class" | "data-theme";
480
+ }
481
+ declare const DEFAULT_DARK_SELECTOR = "[data-theme=\"dark\"]";
482
+ /** Whether a value is the six-digit hex colour a theme field must hold. */
483
+ declare function isThemeHex(value: unknown): value is string;
484
+ type ThemeColorFields = Pick<ThemeConfig, "brandPrimary" | "brandSecondary" | "brandAccent" | "brandHighlight" | "brandSuccess" | "greyScale">;
485
+ /** The `BrandColors` a config's five hexes and grey scale describe. Throws on a hex that is not `#rrggbb`. */
486
+ declare function brandColorsFromConfig(config: ThemeColorFields): BrandColors;
487
+ /**
488
+ * The complete theme stylesheet for a config: the light semantic palette,
489
+ * the brand and destructive scales, the four preset variable blocks, the
490
+ * typography variables, and the dark palette under the dark selector.
491
+ *
492
+ * The preset blocks mean a site needs only this file and `base.css`; the
493
+ * preset files under `css/themes/` stay for consumers composing their own
494
+ * theme, and importing both is harmless.
495
+ *
496
+ * Throws, naming the field, when a brand colour is not `#rrggbb`; a CMS
497
+ * that stored a bad value fails loudly rather than shipping a stylesheet
498
+ * with a `NaN` in it. A blank `darkSelector` means the default.
499
+ *
500
+ * Until BIS-58 lands the font catalogue, `fontBody` and `fontHeading` are
501
+ * family names quoted straight into `--font-body` / `--font-heading`, as the
502
+ * CLI has always written them.
503
+ */
504
+ declare function buildThemeCss(config: ThemeConfig, options?: BuildThemeCssOptions): string;
505
+ //#endregion
506
+ //#region src/contrast.d.ts
507
+ /** `#rrggbb`, or the token form the palettes use: `"h s% l%"`. */
508
+ type ColorInput = string;
509
+ type ContrastLevel = "fail" | "aa-large" | "aa" | "aaa";
510
+ /**
511
+ * What the pairing carries. `text` and `fill-with-text` are held to the
512
+ * body text target; `large-text` to 4.5 under a 7:1 target, else 3;
513
+ * `non-text` (a focus ring, a border) to WCAG 1.4.11's single 3:1 tier.
514
+ */
515
+ type ContrastRole = "text" | "large-text" | "fill-with-text" | "non-text";
516
+ interface ContrastRating {
517
+ aa: boolean;
518
+ aaLarge: boolean;
519
+ aaa: boolean;
520
+ level: ContrastLevel;
521
+ }
522
+ interface ContrastSample {
523
+ ratio: number;
524
+ /** The WCAG text rating of `ratio`, whatever the role. */
525
+ level: ContrastLevel;
526
+ /** The measured colours, in the token form, so a renderer can draw them. */
527
+ foreground: string;
528
+ background: string;
529
+ }
530
+ interface ContrastCheck {
531
+ /** Stable: `<foreground-token>-on-<background-token>`. */
532
+ id: string;
533
+ /** What the pairing affects, as advice: "Secondary as eyebrow text on the page background". */
534
+ label: string;
535
+ role: ContrastRole;
536
+ light: ContrastSample;
537
+ dark: ContrastSample;
538
+ /** The threshold this check was judged against, after the role's rule. */
539
+ target: number;
540
+ /** Both modes at or above `target`. */
541
+ meetsTarget: boolean;
542
+ }
543
+ type ContrastThemeInput = Pick<ThemeConfig, "brandPrimary" | "brandSecondary" | "brandAccent" | "brandHighlight" | "brandSuccess" | "greyScale">;
544
+ interface AuditThemeOptions {
545
+ /** The body text target. 4.5 is AA; a site with an older audience passes 7. */
546
+ target?: number;
547
+ }
548
+ /** WCAG relative luminance, 0 for black to 1 for white. */
549
+ declare function relativeLuminance(color: ColorInput): number;
550
+ /** WCAG contrast ratio between two colours, 1 to 21, to two decimal places. Order does not matter. */
551
+ declare function contrastRatio(a: ColorInput, b: ColorInput): number;
552
+ /** The WCAG text ratings a ratio earns: 3:1 large text, 4.5:1 AA, 7:1 AAA. */
553
+ declare function rateContrast(ratio: number): ContrastRating;
554
+ /**
555
+ * Measures every text-carrying pairing the palette derives for these
556
+ * colours, in light and dark. Advice, not enforcement: nothing here refuses
557
+ * a theme on its ratios. The checks are data; BIS-59's `ContrastStrip` draws
558
+ * them.
559
+ *
560
+ * Throws, naming the field, when a brand colour is not `#rrggbb`, the same
561
+ * contract as `buildThemeCss`. An admin field validates its own value
562
+ * before calling this, so a half-typed hex never reaches it.
563
+ */
564
+ declare function auditTheme(config: ContrastThemeInput, options?: AuditThemeOptions): ContrastCheck[];
565
+ //#endregion
566
+ export { type AnimationPresets, type AuditThemeOptions, type BrandColors, type BrandScales, type BuildThemeCssOptions, type ColorInput, type ColorScale, type ContrastCheck, type ContrastLevel, type ContrastRating, type ContrastRole, type ContrastSample, type ContrastThemeInput, DEFAULT_DARK_SELECTOR, DESTRUCTIVE_SCALE_HEX, type DensityPreset, type FontWeightMap, GREY_SCALES, type GreyScale, type HSL, type MotionPreset, type MotionVariant, type OKLCH, type RadiusPreset, SHADE_STEPS, type SemanticPalette, type ShadeStep, type ShadowPreset, type StaggerContainerVariant, type ThemeConfig, type ZIndexToken, auditTheme, brandColorsFromConfig, buildThemeCss, contrastRatio, defaultFontWeights, densityPresets, deriveDarkPalette, deriveLightPalette, generateBrandScales, generateColorScale, generateDestructiveScaleCSS, generatePaletteCSS, generateScaleCSS, getAnimationPresets, getDestructiveColorScale, hexToHSL, hexToOklch, hslToString, isThemeHex, motionPresets, oklchToHslString, radiusPresets, rateContrast, relativeLuminance, shadowPresets, spacingScale, typeScale, zIndexTokens };
413
567
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/colors.ts","../src/oklch.ts","../src/radius.ts","../src/shadows.ts","../src/motion.ts","../src/density.ts","../src/typography.ts","../src/spacing.ts","../src/z-index.ts","../src/animation-presets.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/colors.ts","../src/oklch.ts","../src/radius.ts","../src/shadows.ts","../src/motion.ts","../src/density.ts","../src/typography.ts","../src/spacing.ts","../src/z-index.ts","../src/animation-presets.ts","../src/theme.ts","../src/contrast.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -311,6 +311,11 @@ function generateDestructiveScaleCSS() {
311
311
  const scale = getDestructiveColorScale();
312
312
  return SHADE_STEPS.map((step) => ` --destructive-${step}: ${scale[step]};`).join("\n");
313
313
  }
314
+ /**
315
+ * Near-white ink on a dark fill, near-black on a light one. A lightness
316
+ * heuristic, not a measurement: `auditTheme` in `./contrast` is what says
317
+ * whether the pairing actually reads.
318
+ */
314
319
  function contrastForeground(color) {
315
320
  return color.l > 55 ? hslToString({
316
321
  h: color.h,
@@ -322,13 +327,42 @@ function contrastForeground(color) {
322
327
  l: 98
323
328
  });
324
329
  }
325
- function boostForDark(color) {
326
- if (color.l < 50) return {
330
+ /**
331
+ * A brand fill lightens in dark mode only below this lightness. The CLI
332
+ * shipped 40 and this module shipped 50 until BIS-57; 40 is the value every
333
+ * generated `bison-theme.css` in the field was built with, and Spinal
334
+ * Simplicity's `globals.css` records why a fill at 43 must not lighten: its
335
+ * derived ink flips to near-black, and the button's fixed `hover:bg-primary-700`
336
+ * then reads at 1.74:1.
337
+ */
338
+ const DARK_FILL_LIGHTEN_BELOW = 40;
339
+ /**
340
+ * The focus ring lightens below this lightness. A ring is a thin outline on
341
+ * the page background and carries no ink, so it is free to lighten where a
342
+ * fill is not: `#DB0082` at 43% is 4.1:1 on zinc-950, at 68% it is 7.1:1.
343
+ */
344
+ const DARK_RING_LIGHTEN_BELOW = 50;
345
+ function lightenForDark(color, below) {
346
+ if (color.l < below) return {
327
347
  ...color,
328
348
  l: clamp(color.l + 25, 0, 100)
329
349
  };
330
350
  return color;
331
351
  }
352
+ /**
353
+ * The light semantic palette for a brand. This is the one derivation the
354
+ * CLI (`bison-theme.css`), `BisonProvider` and a CMS theme all share.
355
+ *
356
+ * Decided in BIS-57, where the CLI and this module had drifted:
357
+ * - `muted-foreground` is grey-600, not grey-500. grey-500 is 4.6:1 on the
358
+ * page background, and the token carries card descriptions, hints, table
359
+ * labels and footer lines; grey-600 is 7.4:1. Spinal Simplicity overrode
360
+ * it by hand for its patient audience.
361
+ * - `content-secondary` is grey-500, not grey-400. It is text: the
362
+ * progress bar's percentage label and every chart axis tick use it, and
363
+ * grey-400 is about 2.5:1 on the page background on every scale. grey-500
364
+ * is 4.5:1 or better on both the page background and a card.
365
+ */
332
366
  function deriveLightPalette(brand) {
333
367
  const grey = GREY_SCALES[brand.grey ?? "slate"];
334
368
  const primary = hexToHSL(brand.primary);
@@ -357,8 +391,8 @@ function deriveLightPalette(brand) {
357
391
  success: hslToString(success),
358
392
  "success-foreground": contrastForeground(success),
359
393
  muted: grey["100"],
360
- "muted-foreground": grey["500"],
361
- "content-secondary": grey["400"],
394
+ "muted-foreground": grey["600"],
395
+ "content-secondary": grey["500"],
362
396
  destructive: "0 84% 60%",
363
397
  "destructive-foreground": "0 0% 98%",
364
398
  border: grey["200"],
@@ -368,13 +402,31 @@ function deriveLightPalette(brand) {
368
402
  ring: hslToString(primary)
369
403
  };
370
404
  }
405
+ /**
406
+ * The dark semantic palette for a brand, derived from the same inputs as
407
+ * the light one: no manual dark palette.
408
+ *
409
+ * Decided in BIS-57, where the CLI and this module had drifted:
410
+ * - Brand fills lighten only below lightness 40 (`DARK_FILL_LIGHTEN_BELOW`);
411
+ * the ring lightens below 50 (`DARK_RING_LIGHTEN_BELOW`). See both.
412
+ * - `muted-foreground` is grey-300, not grey-400. grey-400 is 6.9:1 on
413
+ * `card`, where most supporting copy sits; grey-300 clears 7:1 on both
414
+ * surfaces.
415
+ * - `content-secondary` is grey-400, not grey-500. grey-500 on `card` is
416
+ * about 3.7:1, under AA; grey-400 is about 7:1.
417
+ * - `destructive` is `0 62% 30%`, not `0 84% 65%`. `destructive-foreground`
418
+ * (`0 0% 98%`) is 9.8:1 on the former and 3.2:1 on the latter, which
419
+ * fails AA for the button label.
420
+ */
371
421
  function deriveDarkPalette(brand) {
372
422
  const grey = GREY_SCALES[brand.grey ?? "slate"];
373
- const primary = boostForDark(hexToHSL(brand.primary));
374
- const secondary = boostForDark(hexToHSL(brand.secondary));
375
- const accent = boostForDark(brand.accent ? hexToHSL(brand.accent) : hexToHSL(brand.primary));
376
- const highlight = boostForDark(brand.highlight ? hexToHSL(brand.highlight) : hexToHSL(brand.secondary));
377
- const success = boostForDark(hexToHSL(brand.success));
423
+ const primaryHsl = hexToHSL(brand.primary);
424
+ const primary = lightenForDark(primaryHsl, DARK_FILL_LIGHTEN_BELOW);
425
+ const secondary = lightenForDark(hexToHSL(brand.secondary), DARK_FILL_LIGHTEN_BELOW);
426
+ const accent = lightenForDark(brand.accent ? hexToHSL(brand.accent) : primaryHsl, DARK_FILL_LIGHTEN_BELOW);
427
+ const highlight = lightenForDark(brand.highlight ? hexToHSL(brand.highlight) : hexToHSL(brand.secondary), DARK_FILL_LIGHTEN_BELOW);
428
+ const success = lightenForDark(hexToHSL(brand.success), DARK_FILL_LIGHTEN_BELOW);
429
+ const ring = lightenForDark(primaryHsl, DARK_RING_LIGHTEN_BELOW);
378
430
  return {
379
431
  background: grey["950"],
380
432
  foreground: grey["50"],
@@ -398,13 +450,13 @@ function deriveDarkPalette(brand) {
398
450
  muted: grey["800"],
399
451
  "muted-foreground": grey["300"],
400
452
  "content-secondary": grey["400"],
401
- destructive: "0 84% 65%",
453
+ destructive: "0 62% 30%",
402
454
  "destructive-foreground": "0 0% 98%",
403
455
  border: grey["800"],
404
456
  "border-strong": grey["700"],
405
457
  "border-subtle": grey["900"],
406
458
  input: grey["800"],
407
- ring: hslToString(primary)
459
+ ring: hslToString(ring)
408
460
  };
409
461
  }
410
462
  function generatePaletteCSS(brand, attribute = "class") {
@@ -846,6 +898,396 @@ function getAnimationPresets(preset = "smooth") {
846
898
  };
847
899
  }
848
900
  //#endregion
849
- export { DESTRUCTIVE_SCALE_HEX, GREY_SCALES, SHADE_STEPS, defaultFontWeights, densityPresets, deriveDarkPalette, deriveLightPalette, generateBrandScales, generateColorScale, generateDestructiveScaleCSS, generatePaletteCSS, generateScaleCSS, getAnimationPresets, getDestructiveColorScale, hexToHSL, hexToOklch, hslToString, motionPresets, oklchToHslString, radiusPresets, shadowPresets, spacingScale, typeScale, zIndexTokens };
901
+ //#region src/theme.ts
902
+ /**
903
+ * The theme a site configures, and the stylesheet built from it.
904
+ *
905
+ * `ThemeConfig` is the `bison.config.json` shape `@bison-lab/create-theme`
906
+ * writes. `buildThemeCss` turns one into the complete theme stylesheet: the
907
+ * CLI writes it to `bison-theme.css`, and a CMS theme (BIS-60) renders it at
908
+ * request time from the same function, so the two can never disagree.
909
+ */
910
+ const DEFAULT_DARK_SELECTOR = "[data-theme=\"dark\"]";
911
+ /** Whether a value is the six-digit hex colour a theme field must hold. */
912
+ function isThemeHex(value) {
913
+ return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value);
914
+ }
915
+ const BRAND_FIELDS = [
916
+ "brandPrimary",
917
+ "brandSecondary",
918
+ "brandAccent",
919
+ "brandHighlight",
920
+ "brandSuccess"
921
+ ];
922
+ /**
923
+ * Refuses a config whose brand colours are not `#rrggbb`, naming the field.
924
+ * `hexToHSL` reads fixed substrings, so a short or malformed hex would
925
+ * otherwise become `NaN` in the palette without a word: `buildThemeCss`
926
+ * would emit a variable the browser drops, and a five-digit hex would
927
+ * measure a plausible, wrong contrast ratio.
928
+ */
929
+ function assertThemeColors(config) {
930
+ for (const field of BRAND_FIELDS) {
931
+ const value = config[field];
932
+ if (!isThemeHex(value)) throw new Error(`${field} must be a six-digit hex colour like #1e3a5f, got ${JSON.stringify(value)}`);
933
+ }
934
+ }
935
+ /** The `BrandColors` a config's five hexes and grey scale describe. Throws on a hex that is not `#rrggbb`. */
936
+ function brandColorsFromConfig(config) {
937
+ assertThemeColors(config);
938
+ return {
939
+ primary: config.brandPrimary,
940
+ secondary: config.brandSecondary,
941
+ accent: config.brandAccent,
942
+ highlight: config.brandHighlight,
943
+ success: config.brandSuccess,
944
+ grey: config.greyScale
945
+ };
946
+ }
947
+ function resolveDarkSelector(config, options) {
948
+ if (options?.attribute === "class") return ".dark";
949
+ if (options?.attribute === "data-theme") return DEFAULT_DARK_SELECTOR;
950
+ return config.darkSelector?.trim() || "[data-theme=\"dark\"]";
951
+ }
952
+ function paletteLines(palette, indent) {
953
+ return Object.entries(palette).map(([key, value]) => `${indent}--${key}: ${value};`);
954
+ }
955
+ function presetLines(vars) {
956
+ return Object.entries(vars).map(([key, value]) => ` ${key}: ${value};`);
957
+ }
958
+ /**
959
+ * The complete theme stylesheet for a config: the light semantic palette,
960
+ * the brand and destructive scales, the four preset variable blocks, the
961
+ * typography variables, and the dark palette under the dark selector.
962
+ *
963
+ * The preset blocks mean a site needs only this file and `base.css`; the
964
+ * preset files under `css/themes/` stay for consumers composing their own
965
+ * theme, and importing both is harmless.
966
+ *
967
+ * Throws, naming the field, when a brand colour is not `#rrggbb`; a CMS
968
+ * that stored a bad value fails loudly rather than shipping a stylesheet
969
+ * with a `NaN` in it. A blank `darkSelector` means the default.
970
+ *
971
+ * Until BIS-58 lands the font catalogue, `fontBody` and `fontHeading` are
972
+ * family names quoted straight into `--font-body` / `--font-heading`, as the
973
+ * CLI has always written them.
974
+ */
975
+ function buildThemeCss(config, options) {
976
+ const brand = brandColorsFromConfig(config);
977
+ const light = deriveLightPalette(brand);
978
+ const dark = deriveDarkPalette(brand);
979
+ const scales = generateBrandScales(brand);
980
+ const darkSelector = resolveDarkSelector(config, options);
981
+ const darkBlock = darkSelector === "@media" ? [
982
+ `@media (prefers-color-scheme: dark) {`,
983
+ ` :root {`,
984
+ ...paletteLines(dark, " "),
985
+ ` }`,
986
+ `}`
987
+ ] : [
988
+ `${darkSelector} {`,
989
+ ...paletteLines(dark, " "),
990
+ `}`
991
+ ];
992
+ return [
993
+ `/* Bison Lab Theme — generated by @bison-lab/tokens buildThemeCss. Do not hand-edit; regenerate from bison.config.json. */`,
994
+ `/* Brand: ${config.brandPrimary} / ${config.brandSecondary} / ${config.brandAccent} / ${config.brandHighlight} / ${config.brandSuccess} */`,
995
+ `/* Grey: ${config.greyScale} */`,
996
+ `/* Presets: radius ${config.radius} · shadow ${config.shadow} · motion ${config.motion} · density ${config.density} */`,
997
+ ``,
998
+ `:root {`,
999
+ ...paletteLines(light, " "),
1000
+ ``,
1001
+ ` /* Brand color scales (50–950) */`,
1002
+ generateScaleCSS(scales),
1003
+ ``,
1004
+ ` /* Destructive scale (50–950) — soft danger surfaces */`,
1005
+ generateDestructiveScaleCSS(),
1006
+ ``,
1007
+ ` /* Radius preset: ${config.radius} */`,
1008
+ ...presetLines(radiusPresets[config.radius]),
1009
+ ``,
1010
+ ` /* Shadow preset: ${config.shadow} */`,
1011
+ ...presetLines(shadowPresets[config.shadow]),
1012
+ ``,
1013
+ ` /* Motion preset: ${config.motion} */`,
1014
+ ...presetLines(motionPresets[config.motion]),
1015
+ ``,
1016
+ ` /* Density preset: ${config.density} */`,
1017
+ ...presetLines(densityPresets[config.density]),
1018
+ ``,
1019
+ ` /* Typography */`,
1020
+ ` --font-body: '${config.fontBody}', ui-sans-serif, system-ui, sans-serif;`,
1021
+ ` --font-heading: '${config.fontHeading ?? config.fontBody}', ui-sans-serif, system-ui, sans-serif;`,
1022
+ ` --font-weight-normal: ${config.fontWeights.normal};`,
1023
+ ` --font-weight-medium: ${config.fontWeights.medium};`,
1024
+ ` --font-weight-semibold: ${config.fontWeights.semibold};`,
1025
+ ` --font-weight-bold: ${config.fontWeights.bold};`,
1026
+ ` --font-weight-extrabold: 800;`,
1027
+ `}`,
1028
+ ``,
1029
+ ...darkBlock,
1030
+ ``
1031
+ ].join("\n");
1032
+ }
1033
+ //#endregion
1034
+ //#region src/contrast.ts
1035
+ /**
1036
+ * WCAG contrast, measured on the palette a theme derives.
1037
+ *
1038
+ * Pure functions with no DOM, so the Theme admin (BIS-60) can run them in
1039
+ * the browser as the editor types. `auditTheme` returns data only; the
1040
+ * `ContrastStrip` that draws it is BIS-59.
1041
+ */
1042
+ function hslToRgb(h, s, l) {
1043
+ const chroma = (1 - Math.abs(2 * l - 1)) * s;
1044
+ const hue = (h % 360 + 360) % 360 / 60;
1045
+ const x = chroma * (1 - Math.abs(hue % 2 - 1));
1046
+ const m = l - chroma / 2;
1047
+ let rgb;
1048
+ if (hue < 1) rgb = [
1049
+ chroma,
1050
+ x,
1051
+ 0
1052
+ ];
1053
+ else if (hue < 2) rgb = [
1054
+ x,
1055
+ chroma,
1056
+ 0
1057
+ ];
1058
+ else if (hue < 3) rgb = [
1059
+ 0,
1060
+ chroma,
1061
+ x
1062
+ ];
1063
+ else if (hue < 4) rgb = [
1064
+ 0,
1065
+ x,
1066
+ chroma
1067
+ ];
1068
+ else if (hue < 5) rgb = [
1069
+ x,
1070
+ 0,
1071
+ chroma
1072
+ ];
1073
+ else rgb = [
1074
+ chroma,
1075
+ 0,
1076
+ x
1077
+ ];
1078
+ return [
1079
+ rgb[0] + m,
1080
+ rgb[1] + m,
1081
+ rgb[2] + m
1082
+ ];
1083
+ }
1084
+ function parseColor(input) {
1085
+ const hex = /^#([0-9a-f]{6})$/i.exec(input.trim());
1086
+ if (hex) {
1087
+ const value = parseInt(hex[1], 16);
1088
+ return [
1089
+ value >> 16 & 255,
1090
+ value >> 8 & 255,
1091
+ value & 255
1092
+ ].map((channel) => channel / 255);
1093
+ }
1094
+ const triplet = /^(-?[\d.]+)\s+([\d.]+)%\s+([\d.]+)%$/.exec(input.trim());
1095
+ if (triplet) return hslToRgb(Number(triplet[1]), Number(triplet[2]) / 100, Number(triplet[3]) / 100);
1096
+ throw new Error(`Not a colour this module reads: "${input}". Pass #rrggbb or "h s% l%".`);
1097
+ }
1098
+ function linearChannel(channel) {
1099
+ return channel <= .04045 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4);
1100
+ }
1101
+ /** WCAG relative luminance, 0 for black to 1 for white. */
1102
+ function relativeLuminance(color) {
1103
+ const [r, g, b] = parseColor(color);
1104
+ return .2126 * linearChannel(r) + .7152 * linearChannel(g) + .0722 * linearChannel(b);
1105
+ }
1106
+ /** WCAG contrast ratio between two colours, 1 to 21, to two decimal places. Order does not matter. */
1107
+ function contrastRatio(a, b) {
1108
+ const la = relativeLuminance(a);
1109
+ const lb = relativeLuminance(b);
1110
+ const ratio = (Math.max(la, lb) + .05) / (Math.min(la, lb) + .05);
1111
+ return Math.round(ratio * 100) / 100;
1112
+ }
1113
+ /** The WCAG text ratings a ratio earns: 3:1 large text, 4.5:1 AA, 7:1 AAA. */
1114
+ function rateContrast(ratio) {
1115
+ const aaLarge = ratio >= 3;
1116
+ const aa = ratio >= 4.5;
1117
+ const aaa = ratio >= 7;
1118
+ return {
1119
+ aa,
1120
+ aaLarge,
1121
+ aaa,
1122
+ level: aaa ? "aaa" : aa ? "aa" : aaLarge ? "aa-large" : "fail"
1123
+ };
1124
+ }
1125
+ /** Every pairing in the system that carries text, plus the focus ring. */
1126
+ const PAIRINGS = [
1127
+ {
1128
+ foreground: "primary-foreground",
1129
+ background: "primary",
1130
+ role: "fill-with-text",
1131
+ label: "Primary button label on the primary fill"
1132
+ },
1133
+ {
1134
+ foreground: "secondary-foreground",
1135
+ background: "secondary",
1136
+ role: "fill-with-text",
1137
+ label: "Secondary button label on the secondary fill"
1138
+ },
1139
+ {
1140
+ foreground: "brand-accent-foreground",
1141
+ background: "brand-accent",
1142
+ role: "fill-with-text",
1143
+ label: "Copy on a brand-accent band"
1144
+ },
1145
+ {
1146
+ foreground: "highlight-foreground",
1147
+ background: "highlight",
1148
+ role: "fill-with-text",
1149
+ label: "Copy on a highlight fill"
1150
+ },
1151
+ {
1152
+ foreground: "success-foreground",
1153
+ background: "success",
1154
+ role: "fill-with-text",
1155
+ label: "Success badge label on its fill"
1156
+ },
1157
+ {
1158
+ foreground: "destructive-foreground",
1159
+ background: "destructive",
1160
+ role: "fill-with-text",
1161
+ label: "Destructive button label on its fill"
1162
+ },
1163
+ {
1164
+ foreground: "accent-foreground",
1165
+ background: "accent",
1166
+ role: "fill-with-text",
1167
+ label: "Hovered and selected item text on the accent surface"
1168
+ },
1169
+ {
1170
+ foreground: "muted-foreground",
1171
+ background: "muted",
1172
+ role: "fill-with-text",
1173
+ label: "Hint text on a muted surface"
1174
+ },
1175
+ {
1176
+ foreground: "card-foreground",
1177
+ background: "card",
1178
+ role: "fill-with-text",
1179
+ label: "Card body text on the card surface"
1180
+ },
1181
+ {
1182
+ foreground: "popover-foreground",
1183
+ background: "popover",
1184
+ role: "fill-with-text",
1185
+ label: "Menu and popover text on the popover surface"
1186
+ },
1187
+ {
1188
+ foreground: "foreground",
1189
+ background: "background",
1190
+ role: "text",
1191
+ label: "Body text on the page background"
1192
+ },
1193
+ {
1194
+ foreground: "foreground",
1195
+ background: "card",
1196
+ role: "text",
1197
+ label: "Body text on a card"
1198
+ },
1199
+ {
1200
+ foreground: "muted-foreground",
1201
+ background: "background",
1202
+ role: "text",
1203
+ label: "Supporting text on the page background"
1204
+ },
1205
+ {
1206
+ foreground: "muted-foreground",
1207
+ background: "card",
1208
+ role: "text",
1209
+ label: "Supporting text on a card"
1210
+ },
1211
+ {
1212
+ foreground: "content-secondary",
1213
+ background: "background",
1214
+ role: "text",
1215
+ label: "Chart axis labels and tertiary text on the page background"
1216
+ },
1217
+ {
1218
+ foreground: "content-secondary",
1219
+ background: "card",
1220
+ role: "text",
1221
+ label: "Chart axis labels and tertiary text on a card"
1222
+ },
1223
+ {
1224
+ foreground: "primary",
1225
+ background: "background",
1226
+ role: "text",
1227
+ label: "Primary as link text on the page background"
1228
+ },
1229
+ {
1230
+ foreground: "secondary",
1231
+ background: "background",
1232
+ role: "text",
1233
+ label: "Secondary as eyebrow text on the page background"
1234
+ },
1235
+ {
1236
+ foreground: "ring",
1237
+ background: "background",
1238
+ role: "non-text",
1239
+ label: "Focus ring on the page background"
1240
+ }
1241
+ ];
1242
+ function roleTarget(role, target) {
1243
+ switch (role) {
1244
+ case "non-text": return 3;
1245
+ case "large-text": return target >= 7 ? 4.5 : 3;
1246
+ default: return target;
1247
+ }
1248
+ }
1249
+ function sample(palette, pairing) {
1250
+ const foreground = palette[pairing.foreground];
1251
+ const background = palette[pairing.background];
1252
+ const ratio = contrastRatio(foreground, background);
1253
+ return {
1254
+ ratio,
1255
+ level: rateContrast(ratio).level,
1256
+ foreground,
1257
+ background
1258
+ };
1259
+ }
1260
+ /**
1261
+ * Measures every text-carrying pairing the palette derives for these
1262
+ * colours, in light and dark. Advice, not enforcement: nothing here refuses
1263
+ * a theme on its ratios. The checks are data; BIS-59's `ContrastStrip` draws
1264
+ * them.
1265
+ *
1266
+ * Throws, naming the field, when a brand colour is not `#rrggbb`, the same
1267
+ * contract as `buildThemeCss`. An admin field validates its own value
1268
+ * before calling this, so a half-typed hex never reaches it.
1269
+ */
1270
+ function auditTheme(config, options = {}) {
1271
+ const target = options.target ?? 4.5;
1272
+ const brand = brandColorsFromConfig(config);
1273
+ const light = deriveLightPalette(brand);
1274
+ const dark = deriveDarkPalette(brand);
1275
+ return PAIRINGS.map((pairing) => {
1276
+ const lightSample = sample(light, pairing);
1277
+ const darkSample = sample(dark, pairing);
1278
+ const threshold = roleTarget(pairing.role, target);
1279
+ return {
1280
+ id: `${pairing.foreground}-on-${pairing.background}`,
1281
+ label: pairing.label,
1282
+ role: pairing.role,
1283
+ light: lightSample,
1284
+ dark: darkSample,
1285
+ target: threshold,
1286
+ meetsTarget: lightSample.ratio >= threshold && darkSample.ratio >= threshold
1287
+ };
1288
+ });
1289
+ }
1290
+ //#endregion
1291
+ export { DEFAULT_DARK_SELECTOR, DESTRUCTIVE_SCALE_HEX, GREY_SCALES, SHADE_STEPS, auditTheme, brandColorsFromConfig, buildThemeCss, contrastRatio, defaultFontWeights, densityPresets, deriveDarkPalette, deriveLightPalette, generateBrandScales, generateColorScale, generateDestructiveScaleCSS, generatePaletteCSS, generateScaleCSS, getAnimationPresets, getDestructiveColorScale, hexToHSL, hexToOklch, hslToString, isThemeHex, motionPresets, oklchToHslString, radiusPresets, rateContrast, relativeLuminance, shadowPresets, spacingScale, typeScale, zIndexTokens };
850
1292
 
851
1293
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/oklch.ts","../src/colors.ts","../src/radius.ts","../src/shadows.ts","../src/motion.ts","../src/density.ts","../src/typography.ts","../src/spacing.ts","../src/z-index.ts","../src/animation-presets.ts"],"sourcesContent":["/**\n * OKLCH color space math — zero dependencies.\n * hex → sRGB → linear sRGB → OKLab (matrix) → OKLCH (polar)\n * Reverse: OKLCH → OKLab → linear sRGB → sRGB → HSL string\n * Gamut clamping via binary search on chroma (12 iterations).\n */\n\nexport interface OKLCH {\n L: number; // lightness 0–1\n C: number; // chroma ≥ 0\n H: number; // hue 0–360\n}\n\n// --- sRGB ↔ linear sRGB ---\n\nfunction srgbToLinear(c: number): number {\n return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n}\n\nfunction linearToSrgb(c: number): number {\n return c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n}\n\n// --- Hex → sRGB ---\n\nfunction hexToSrgb(hex: string): [number, number, number] {\n hex = hex.replace(\"#\", \"\");\n return [\n parseInt(hex.substring(0, 2), 16) / 255,\n parseInt(hex.substring(2, 4), 16) / 255,\n parseInt(hex.substring(4, 6), 16) / 255,\n ];\n}\n\n// --- linear sRGB ↔ OKLab ---\n\nfunction linearSrgbToOklab(r: number, g: number, b: number): [number, number, number] {\n const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;\n const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;\n const s = 0.0883024619 * r + 0.2024326059 * g + 0.6892649272 * b;\n\n const lc = Math.cbrt(l);\n const mc = Math.cbrt(m);\n const sc = Math.cbrt(s);\n\n return [\n 0.2104542553 * lc + 0.7936177850 * mc - 0.0040720468 * sc,\n 1.9779984951 * lc - 2.4285922050 * mc + 0.4505937099 * sc,\n 0.0259040371 * lc + 0.7827717662 * mc - 0.8086757660 * sc,\n ];\n}\n\nfunction oklabToLinearSrgb(L: number, a: number, b: number): [number, number, number] {\n const lc = L + 0.3963377774 * a + 0.2158037573 * b;\n const mc = L - 0.1055613458 * a - 0.0638541728 * b;\n const sc = L - 0.0894841775 * a - 1.2914855480 * b;\n\n return [\n 4.0767416621 * lc * lc * lc - 3.3077115913 * mc * mc * mc + 0.2309699292 * sc * sc * sc,\n -1.2684380046 * lc * lc * lc + 2.6097574011 * mc * mc * mc - 0.3413193965 * sc * sc * sc,\n -0.0041960863 * lc * lc * lc - 0.7034186147 * mc * mc * mc + 1.7076147010 * sc * sc * sc,\n ];\n}\n\n// --- OKLab ↔ OKLCH ---\n\nfunction oklabToOklch(L: number, a: number, b: number): OKLCH {\n const C = Math.sqrt(a * a + b * b);\n let H = (Math.atan2(b, a) * 180) / Math.PI;\n if (H < 0) H += 360;\n return { L, C, H };\n}\n\nfunction oklchToOklab(L: number, C: number, H: number): [number, number, number] {\n const hRad = (H * Math.PI) / 180;\n return [L, C * Math.cos(hRad), C * Math.sin(hRad)];\n}\n\n// --- Gamut clamping ---\n\nfunction isInGamut(r: number, g: number, b: number): boolean {\n const eps = 1e-4;\n return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;\n}\n\nfunction gamutClampChroma(L: number, C: number, H: number): number {\n if (C <= 0) return 0;\n\n const [, a, b] = oklchToOklab(L, C, H);\n const [r, g, bl] = oklabToLinearSrgb(L, a, b);\n\n if (isInGamut(r, g, bl)) return C;\n\n let lo = 0;\n let hi = C;\n\n for (let i = 0; i < 12; i++) {\n const mid = (lo + hi) / 2;\n const [, am, bm] = oklchToOklab(L, mid, H);\n const [rm, gm, bm2] = oklabToLinearSrgb(L, am, bm);\n\n if (isInGamut(rm, gm, bm2)) {\n lo = mid;\n } else {\n hi = mid;\n }\n }\n\n return lo;\n}\n\n// --- sRGB → HSL ---\n\nfunction srgbToHsl(r: number, g: number, b: number): [number, number, number] {\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n let h = 0;\n let s = 0;\n\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case r:\n h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n break;\n case g:\n h = ((b - r) / d + 2) / 6;\n break;\n case b:\n h = ((r - g) / d + 4) / 6;\n break;\n }\n }\n\n return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];\n}\n\n// --- Public API ---\n\nexport function hexToOklch(hex: string): OKLCH {\n const [r, g, b] = hexToSrgb(hex);\n const [lr, lg, lb] = [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];\n const [L, a, ob] = linearSrgbToOklab(lr, lg, lb);\n return oklabToOklch(L, a, ob);\n}\n\nexport function oklchToHslString(L: number, C: number, H: number): string {\n const clampedC = gamutClampChroma(L, C, H);\n const [, a, b] = oklchToOklab(L, clampedC, H);\n const [lr, lg, lb] = oklabToLinearSrgb(L, a, b);\n\n const r = Math.min(1, Math.max(0, linearToSrgb(lr)));\n const g = Math.min(1, Math.max(0, linearToSrgb(lg)));\n const bl = Math.min(1, Math.max(0, linearToSrgb(lb)));\n\n const [h, s, l] = srgbToHsl(r, g, bl);\n return `${h} ${s}% ${l}%`;\n}\n","/**\n * Color derivation engine.\n * Takes brand colors (hex) + a grey scale selection and derives\n * a full semantic palette for both light and dark modes,\n * plus 50–950 shade scales via OKLCH color space.\n */\n\nimport { hexToOklch, oklchToHslString } from \"./oklch\";\n\nexport interface HSL {\n h: number;\n s: number;\n l: number;\n}\n\nexport function hexToHSL(hex: string): HSL {\n hex = hex.replace(\"#\", \"\");\n const r = parseInt(hex.substring(0, 2), 16) / 255;\n const g = parseInt(hex.substring(2, 4), 16) / 255;\n const b = parseInt(hex.substring(4, 6), 16) / 255;\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n let h = 0;\n let s = 0;\n\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case r:\n h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n break;\n case g:\n h = ((b - r) / d + 2) / 6;\n break;\n case b:\n h = ((r - g) / d + 4) / 6;\n break;\n }\n }\n\n return {\n h: Math.round(h * 360),\n s: Math.round(s * 100),\n l: Math.round(l * 100),\n };\n}\n\nexport function hslToString(hsl: HSL): string {\n return `${hsl.h} ${hsl.s}% ${hsl.l}%`;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n// --- Grey scale palettes (Tailwind v4 values as HSL strings) ---\n\nexport type GreyScale = \"slate\" | \"gray\" | \"zinc\" | \"neutral\" | \"stone\";\n\ntype GreyShade = \"50\" | \"100\" | \"200\" | \"300\" | \"400\" | \"500\" | \"600\" | \"700\" | \"800\" | \"900\" | \"950\";\n\ntype GreyPalette = Record<GreyShade, string>;\n\nexport const GREY_SCALES: Record<GreyScale, GreyPalette> = {\n slate: {\n \"50\": \"210 40% 98%\",\n \"100\": \"210 40% 96%\",\n \"200\": \"214 32% 91%\",\n \"300\": \"213 27% 84%\",\n \"400\": \"215 20% 65%\",\n \"500\": \"215 16% 47%\",\n \"600\": \"215 19% 35%\",\n \"700\": \"215 25% 27%\",\n \"800\": \"217 33% 17%\",\n \"900\": \"222 47% 11%\",\n \"950\": \"229 84% 5%\",\n },\n gray: {\n \"50\": \"210 20% 98%\",\n \"100\": \"220 14% 96%\",\n \"200\": \"220 13% 91%\",\n \"300\": \"216 12% 84%\",\n \"400\": \"218 11% 65%\",\n \"500\": \"220 9% 46%\",\n \"600\": \"215 14% 34%\",\n \"700\": \"217 19% 27%\",\n \"800\": \"215 28% 17%\",\n \"900\": \"221 39% 11%\",\n \"950\": \"224 71% 4%\",\n },\n zinc: {\n \"50\": \"0 0% 98%\",\n \"100\": \"240 5% 96%\",\n \"200\": \"240 6% 90%\",\n \"300\": \"240 5% 84%\",\n \"400\": \"240 5% 65%\",\n \"500\": \"240 4% 46%\",\n \"600\": \"240 5% 34%\",\n \"700\": \"240 5% 26%\",\n \"800\": \"240 4% 16%\",\n \"900\": \"240 6% 10%\",\n \"950\": \"240 10% 4%\",\n },\n neutral: {\n \"50\": \"0 0% 98%\",\n \"100\": \"0 0% 96%\",\n \"200\": \"0 0% 90%\",\n \"300\": \"0 0% 83%\",\n \"400\": \"0 0% 64%\",\n \"500\": \"0 0% 45%\",\n \"600\": \"0 0% 32%\",\n \"700\": \"0 0% 25%\",\n \"800\": \"0 0% 15%\",\n \"900\": \"0 0% 9%\",\n \"950\": \"0 0% 4%\",\n },\n stone: {\n \"50\": \"60 9% 98%\",\n \"100\": \"60 5% 96%\",\n \"200\": \"20 6% 90%\",\n \"300\": \"24 6% 83%\",\n \"400\": \"24 5% 64%\",\n \"500\": \"25 5% 45%\",\n \"600\": \"33 5% 32%\",\n \"700\": \"30 6% 25%\",\n \"800\": \"12 6% 15%\",\n \"900\": \"24 10% 10%\",\n \"950\": \"20 14% 4%\",\n },\n};\n\n// --- Brand color types ---\n\nexport interface BrandColors {\n primary: string; // hex\n secondary: string; // hex\n accent?: string; // hex\n highlight?: string; // hex\n success: string; // hex\n grey?: GreyScale; // defaults to \"slate\"\n}\n\n// --- Shade scale types ---\n\nexport const SHADE_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nexport type ShadeStep = (typeof SHADE_STEPS)[number];\nexport type ColorScale = Record<ShadeStep, string>;\n\nexport interface BrandScales {\n primary: ColorScale;\n secondary: ColorScale;\n \"brand-accent\": ColorScale;\n highlight: ColorScale;\n success: ColorScale;\n}\n\n// --- Target OKLCH lightness per step ---\n\nconst TARGET_LIGHTNESS: Record<ShadeStep, number> = {\n 50: 0.97,\n 100: 0.93,\n 200: 0.87,\n 300: 0.79,\n 400: 0.70,\n 500: 0.59,\n 600: 0.49,\n 700: 0.39,\n 800: 0.30,\n 900: 0.21,\n 950: 0.14,\n};\n\n// --- Scale generation ---\n\n/**\n * Generate a full 50–950 color scale from a single hex value.\n * Uses OKLCH for perceptual uniformity. The input color is auto-anchored\n * to its closest shade step and reproduced pixel-perfect at that step.\n */\nexport function generateColorScale(hex: string): ColorScale {\n const { L: inputL, C: inputC, H } = hexToOklch(hex);\n\n // Sine-based chroma curve: peaks at mid-lightness, falls toward extremes\n const chromaCurve = (L: number): number => Math.sin(Math.PI * L);\n const inputCurveValue = chromaCurve(inputL);\n\n // Anchor ratio so that input chroma is preserved at input lightness\n const chromaRatio = inputCurveValue > 0.01 ? inputC / inputCurveValue : inputC;\n\n const scale = {} as ColorScale;\n\n for (const step of SHADE_STEPS) {\n const targetL = TARGET_LIGHTNESS[step];\n const targetC = inputCurveValue > 0.01\n ? chromaRatio * chromaCurve(targetL)\n : inputC * chromaCurve(targetL);\n\n scale[step] = oklchToHslString(targetL, targetC, H);\n }\n\n return scale;\n}\n\n/**\n * Generate shade scales for all brand colors.\n */\nexport function generateBrandScales(brand: BrandColors): BrandScales {\n const accentHex = brand.accent ?? brand.primary;\n const highlightHex = brand.highlight ?? brand.secondary;\n\n return {\n primary: generateColorScale(brand.primary),\n secondary: generateColorScale(brand.secondary),\n \"brand-accent\": generateColorScale(accentHex),\n highlight: generateColorScale(highlightHex),\n success: generateColorScale(brand.success),\n };\n}\n\n/**\n * Generate CSS custom property declarations for all brand scales.\n * Output is indented with 2 spaces, suitable for injection into :root {}.\n */\nexport function generateScaleCSS(scales: BrandScales): string {\n const lines: string[] = [];\n\n for (const [colorName, scale] of Object.entries(scales)) {\n for (const step of SHADE_STEPS) {\n lines.push(` --${colorName}-${step}: ${(scale as ColorScale)[step]};`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Canonical red for destructive UI (matches common “danger” hue). Used for the\n * destructive 50–950 scale so tinted surfaces (e.g. soft delete buttons) work\n * like success scales, independent of brand colors.\n */\nexport const DESTRUCTIVE_SCALE_HEX = \"#ef4444\";\n\nlet destructiveColorScaleCache: ColorScale | null = null;\n\nexport function getDestructiveColorScale(): ColorScale {\n if (!destructiveColorScaleCache) {\n destructiveColorScaleCache = generateColorScale(DESTRUCTIVE_SCALE_HEX);\n }\n return destructiveColorScaleCache;\n}\n\n/** CSS custom properties `--destructive-50` … `--destructive-950` for :root injection. */\nexport function generateDestructiveScaleCSS(): string {\n const scale = getDestructiveColorScale();\n return SHADE_STEPS.map((step) => ` --destructive-${step}: ${scale[step]};`).join(\"\\n\");\n}\n\n// --- Semantic palette ---\n\nexport interface SemanticPalette {\n background: string;\n foreground: string;\n card: string;\n \"card-foreground\": string;\n \"surface-tertiary\": string;\n popover: string;\n \"popover-foreground\": string;\n primary: string;\n \"primary-foreground\": string;\n secondary: string;\n \"secondary-foreground\": string;\n accent: string;\n \"accent-foreground\": string;\n \"brand-accent\": string;\n \"brand-accent-foreground\": string;\n highlight: string;\n \"highlight-foreground\": string;\n success: string;\n \"success-foreground\": string;\n muted: string;\n \"muted-foreground\": string;\n \"content-secondary\": string;\n destructive: string;\n \"destructive-foreground\": string;\n border: string;\n \"border-strong\": string;\n \"border-subtle\": string;\n input: string;\n ring: string;\n}\n\n// --- Foreground contrast ---\n\nfunction contrastForeground(color: HSL): string {\n return color.l > 55\n ? hslToString({ h: color.h, s: clamp(color.s - 20, 0, 100), l: 10 })\n : hslToString({ h: color.h, s: clamp(color.s - 25, 0, 100), l: 98 });\n}\n\nfunction boostForDark(color: HSL): HSL {\n if (color.l < 50) {\n return { ...color, l: clamp(color.l + 25, 0, 100) };\n }\n return color;\n}\n\n// --- Palette derivation ---\n\nexport function deriveLightPalette(brand: BrandColors): SemanticPalette {\n const grey = GREY_SCALES[brand.grey ?? \"slate\"];\n const primary = hexToHSL(brand.primary);\n const secondary = hexToHSL(brand.secondary);\n const accent = brand.accent ? hexToHSL(brand.accent) : primary;\n const highlight = brand.highlight ? hexToHSL(brand.highlight) : secondary;\n const success = hexToHSL(brand.success);\n\n return {\n background: grey[\"50\"],\n foreground: grey[\"950\"],\n card: \"0 0% 100%\",\n \"card-foreground\": grey[\"950\"],\n \"surface-tertiary\": grey[\"100\"],\n popover: \"0 0% 100%\",\n \"popover-foreground\": grey[\"950\"],\n primary: hslToString(primary),\n \"primary-foreground\": contrastForeground(primary),\n secondary: hslToString(secondary),\n \"secondary-foreground\": contrastForeground(secondary),\n accent: grey[\"100\"],\n \"accent-foreground\": grey[\"950\"],\n \"brand-accent\": hslToString(accent),\n \"brand-accent-foreground\": contrastForeground(accent),\n highlight: hslToString(highlight),\n \"highlight-foreground\": contrastForeground(highlight),\n success: hslToString(success),\n \"success-foreground\": contrastForeground(success),\n muted: grey[\"100\"],\n \"muted-foreground\": grey[\"500\"],\n \"content-secondary\": grey[\"400\"],\n destructive: \"0 84% 60%\",\n \"destructive-foreground\": \"0 0% 98%\",\n border: grey[\"200\"],\n \"border-strong\": grey[\"300\"],\n \"border-subtle\": grey[\"100\"],\n input: grey[\"200\"],\n ring: hslToString(primary),\n };\n}\n\nexport function deriveDarkPalette(brand: BrandColors): SemanticPalette {\n const grey = GREY_SCALES[brand.grey ?? \"slate\"];\n const primary = boostForDark(hexToHSL(brand.primary));\n const secondary = boostForDark(hexToHSL(brand.secondary));\n const accent = boostForDark(brand.accent ? hexToHSL(brand.accent) : hexToHSL(brand.primary));\n const highlight = boostForDark(brand.highlight ? hexToHSL(brand.highlight) : hexToHSL(brand.secondary));\n const success = boostForDark(hexToHSL(brand.success));\n\n return {\n background: grey[\"950\"],\n foreground: grey[\"50\"],\n card: grey[\"900\"],\n \"card-foreground\": grey[\"50\"],\n \"surface-tertiary\": grey[\"800\"],\n popover: grey[\"900\"],\n \"popover-foreground\": grey[\"50\"],\n primary: hslToString(primary),\n \"primary-foreground\": contrastForeground(primary),\n secondary: hslToString(secondary),\n \"secondary-foreground\": contrastForeground(secondary),\n accent: grey[\"800\"],\n \"accent-foreground\": grey[\"50\"],\n \"brand-accent\": hslToString(accent),\n \"brand-accent-foreground\": contrastForeground(accent),\n highlight: hslToString(highlight),\n \"highlight-foreground\": contrastForeground(highlight),\n success: hslToString(success),\n \"success-foreground\": contrastForeground(success),\n muted: grey[\"800\"],\n \"muted-foreground\": grey[\"300\"],\n \"content-secondary\": grey[\"400\"],\n destructive: \"0 84% 65%\",\n \"destructive-foreground\": \"0 0% 98%\",\n border: grey[\"800\"],\n \"border-strong\": grey[\"700\"],\n \"border-subtle\": grey[\"900\"],\n input: grey[\"800\"],\n ring: hslToString(primary),\n };\n}\n\nexport function generatePaletteCSS(brand: BrandColors, attribute: \"class\" | \"data-theme\" = \"class\"): string {\n const light = deriveLightPalette(brand);\n const dark = deriveDarkPalette(brand);\n const scales = generateBrandScales(brand);\n\n const toVars = (palette: SemanticPalette, indent: string) =>\n Object.entries(palette)\n .map(([key, value]) => `${indent}--${key}: ${value};`)\n .join(\"\\n\");\n\n const darkSelector = attribute === \"class\" ? \".dark\" : '[data-theme=\"dark\"]';\n\n return `:root {\\n${toVars(light, \" \")}\\n\\n${generateScaleCSS(scales)}\\n\\n${generateDestructiveScaleCSS()}\\n}\\n\\n${darkSelector} {\\n${toVars(dark, \" \")}\\n}\\n`;\n}\n","export const radiusPresets = {\n sharp: {\n \"--radius\": \"0rem\",\n \"--radius-sm\": \"0rem\",\n \"--radius-md\": \"0rem\",\n \"--radius-lg\": \"0rem\",\n \"--radius-xl\": \"0rem\",\n \"--radius-full\": \"0rem\",\n },\n subtle: {\n \"--radius\": \"0.375rem\",\n \"--radius-sm\": \"0.375rem\",\n \"--radius-md\": \"0.375rem\",\n \"--radius-lg\": \"0.5rem\",\n \"--radius-xl\": \"0.75rem\",\n \"--radius-full\": \"9999px\",\n },\n rounded: {\n \"--radius\": \"0.5rem\",\n \"--radius-sm\": \"0.5rem\",\n \"--radius-md\": \"0.5rem\",\n \"--radius-lg\": \"0.75rem\",\n \"--radius-xl\": \"1rem\",\n \"--radius-full\": \"9999px\",\n },\n pill: {\n \"--radius\": \"0.75rem\",\n \"--radius-sm\": \"0.75rem\",\n \"--radius-md\": \"0.75rem\",\n \"--radius-lg\": \"1rem\",\n \"--radius-xl\": \"1.5rem\",\n \"--radius-full\": \"9999px\",\n },\n} as const;\n\nexport type RadiusPreset = keyof typeof radiusPresets;\n","export const shadowPresets = {\n flat: {\n \"--shadow-sm\": \"none\",\n \"--shadow\": \"none\",\n \"--shadow-md\": \"none\",\n \"--shadow-lg\": \"none\",\n \"--shadow-xl\": \"none\",\n },\n subtle: {\n \"--shadow-sm\": \"0 1px 2px 0 rgb(0 0 0 / 0.03)\",\n \"--shadow\": \"0 1px 3px 0 rgb(0 0 0 / 0.05), 0 1px 2px -1px rgb(0 0 0 / 0.05)\",\n \"--shadow-md\": \"0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05)\",\n \"--shadow-lg\": \"0 10px 15px -3px rgb(0 0 0 / 0.05), 0 4px 6px -4px rgb(0 0 0 / 0.05)\",\n \"--shadow-xl\": \"0 20px 25px -5px rgb(0 0 0 / 0.05), 0 8px 10px -6px rgb(0 0 0 / 0.05)\",\n },\n elevated: {\n \"--shadow-sm\": \"0 1px 2px 0 rgb(0 0 0 / 0.06), 0 1px 3px 0 rgb(0 0 0 / 0.1)\",\n \"--shadow\": \"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)\",\n \"--shadow-md\": \"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\",\n \"--shadow-lg\": \"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)\",\n \"--shadow-xl\": \"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)\",\n },\n} as const;\n\nexport type ShadowPreset = keyof typeof shadowPresets;\n","export const motionPresets = {\n snappy: {\n \"--duration-fast\": \"100ms\",\n \"--duration-normal\": \"150ms\",\n \"--duration-slow\": \"250ms\",\n \"--ease-default\": \"cubic-bezier(0.22, 1, 0.36, 1)\",\n \"--ease-in\": \"cubic-bezier(0.55, 0, 1, 0.45)\",\n \"--ease-out\": \"cubic-bezier(0, 0.55, 0.45, 1)\",\n \"--ease-in-out\": \"cubic-bezier(0.65, 0, 0.35, 1)\",\n },\n smooth: {\n \"--duration-fast\": \"150ms\",\n \"--duration-normal\": \"200ms\",\n \"--duration-slow\": \"350ms\",\n \"--ease-default\": \"cubic-bezier(0.25, 0.1, 0.25, 1)\",\n \"--ease-in\": \"cubic-bezier(0.42, 0, 1, 1)\",\n \"--ease-out\": \"cubic-bezier(0, 0, 0.58, 1)\",\n \"--ease-in-out\": \"cubic-bezier(0.42, 0, 0.58, 1)\",\n },\n minimal: {\n \"--duration-fast\": \"75ms\",\n \"--duration-normal\": \"100ms\",\n \"--duration-slow\": \"200ms\",\n \"--ease-default\": \"linear\",\n \"--ease-in\": \"linear\",\n \"--ease-out\": \"linear\",\n \"--ease-in-out\": \"linear\",\n },\n} as const;\n\nexport type MotionPreset = keyof typeof motionPresets;\n","export const densityPresets = {\n compact: {\n \"--density-padding-xs\": \"0.125rem\",\n \"--density-padding-sm\": \"0.25rem\",\n \"--density-padding-md\": \"0.375rem\",\n \"--density-padding-lg\": \"0.5rem\",\n \"--density-padding-xl\": \"0.75rem\",\n \"--density-gap-sm\": \"0.25rem\",\n \"--density-gap-md\": \"0.5rem\",\n \"--density-gap-lg\": \"0.75rem\",\n \"--density-height-sm\": \"1.75rem\",\n \"--density-height-md\": \"2rem\",\n \"--density-height-lg\": \"2.5rem\",\n },\n default: {\n \"--density-padding-xs\": \"0.25rem\",\n \"--density-padding-sm\": \"0.5rem\",\n \"--density-padding-md\": \"0.75rem\",\n \"--density-padding-lg\": \"1rem\",\n \"--density-padding-xl\": \"1.5rem\",\n \"--density-gap-sm\": \"0.5rem\",\n \"--density-gap-md\": \"0.75rem\",\n \"--density-gap-lg\": \"1rem\",\n \"--density-height-sm\": \"2rem\",\n \"--density-height-md\": \"2.5rem\",\n \"--density-height-lg\": \"3rem\",\n },\n spacious: {\n \"--density-padding-xs\": \"0.5rem\",\n \"--density-padding-sm\": \"0.75rem\",\n \"--density-padding-md\": \"1rem\",\n \"--density-padding-lg\": \"1.5rem\",\n \"--density-padding-xl\": \"2rem\",\n \"--density-gap-sm\": \"0.75rem\",\n \"--density-gap-md\": \"1rem\",\n \"--density-gap-lg\": \"1.5rem\",\n \"--density-height-sm\": \"2.5rem\",\n \"--density-height-md\": \"3rem\",\n \"--density-height-lg\": \"3.5rem\",\n },\n} as const;\n\nexport type DensityPreset = keyof typeof densityPresets;\n","/** Locked type scale — consumers cannot override sizes, only font-family and weights. */\nexport const typeScale = {\n \"text-xs\": { fontSize: \"0.75rem\", lineHeight: \"1rem\" },\n \"text-sm\": { fontSize: \"0.875rem\", lineHeight: \"1.25rem\" },\n \"text-base\": { fontSize: \"1rem\", lineHeight: \"1.5rem\" },\n \"text-lg\": { fontSize: \"1.125rem\", lineHeight: \"1.75rem\" },\n \"text-xl\": { fontSize: \"1.25rem\", lineHeight: \"1.75rem\" },\n \"text-2xl\": { fontSize: \"1.5rem\", lineHeight: \"2rem\" },\n \"text-3xl\": { fontSize: \"1.875rem\", lineHeight: \"2.25rem\" },\n \"text-4xl\": { fontSize: \"2.25rem\", lineHeight: \"2.5rem\" },\n\n // Fluid headings using clamp()\n \"heading-sm\": {\n fontSize: \"clamp(1.125rem, 1rem + 0.5vw, 1.25rem)\",\n lineHeight: \"1.4\",\n letterSpacing: \"-0.01em\",\n },\n \"heading-md\": {\n fontSize: \"clamp(1.25rem, 1rem + 1vw, 1.875rem)\",\n lineHeight: \"1.3\",\n letterSpacing: \"-0.015em\",\n },\n \"heading-lg\": {\n fontSize: \"clamp(1.5rem, 1rem + 2vw, 2.25rem)\",\n lineHeight: \"1.2\",\n letterSpacing: \"-0.02em\",\n },\n \"heading-xl\": {\n fontSize: \"clamp(1.875rem, 1rem + 3vw, 3rem)\",\n lineHeight: \"1.1\",\n letterSpacing: \"-0.025em\",\n },\n \"heading-2xl\": {\n fontSize: \"clamp(2.25rem, 1rem + 4vw, 3.75rem)\",\n lineHeight: \"1.1\",\n letterSpacing: \"-0.03em\",\n },\n} as const;\n\nexport interface FontWeightMap {\n normal: number;\n medium: number;\n semibold: number;\n bold: number;\n extrabold?: number;\n}\n\nexport const defaultFontWeights: FontWeightMap = {\n normal: 400,\n medium: 500,\n semibold: 600,\n bold: 700,\n extrabold: 800,\n};\n","/** Locked spacing scale — 4px (0.25rem) base grid. Not consumer-overridable. */\nexport const spacingScale = {\n \"0\": \"0\",\n \"0.5\": \"0.125rem\",\n \"1\": \"0.25rem\",\n \"1.5\": \"0.375rem\",\n \"2\": \"0.5rem\",\n \"2.5\": \"0.625rem\",\n \"3\": \"0.75rem\",\n \"3.5\": \"0.875rem\",\n \"4\": \"1rem\",\n \"5\": \"1.25rem\",\n \"6\": \"1.5rem\",\n \"7\": \"1.75rem\",\n \"8\": \"2rem\",\n \"9\": \"2.25rem\",\n \"10\": \"2.5rem\",\n \"11\": \"2.75rem\",\n \"12\": \"3rem\",\n \"14\": \"3.5rem\",\n \"16\": \"4rem\",\n \"20\": \"5rem\",\n \"24\": \"6rem\",\n \"28\": \"7rem\",\n \"32\": \"8rem\",\n \"36\": \"9rem\",\n \"40\": \"10rem\",\n \"44\": \"11rem\",\n \"48\": \"12rem\",\n \"52\": \"13rem\",\n \"56\": \"14rem\",\n \"60\": \"15rem\",\n \"64\": \"16rem\",\n \"72\": \"18rem\",\n \"80\": \"20rem\",\n \"96\": \"24rem\",\n} as const;\n","/** Z-index tokens — overridable by consumers via CSS custom properties. */\nexport const zIndexTokens = {\n \"--z-dropdown\": \"50\",\n \"--z-sticky\": \"100\",\n \"--z-fixed\": \"200\",\n \"--z-modal-backdrop\": \"300\",\n \"--z-modal\": \"400\",\n \"--z-popover\": \"500\",\n \"--z-tooltip\": \"600\",\n \"--z-toast\": \"700\",\n} as const;\n\nexport type ZIndexToken = keyof typeof zIndexTokens;\n","/**\n * Framer Motion animation presets.\n *\n * Each preset is a Framer Motion `Variants` object with `hidden` and `visible`\n * states, plus optional `exit`. Durations and easings are resolved per motion\n * preset (snappy / smooth / minimal) at call time via `getAnimationPresets()`.\n */\n\nimport { motionPresets, type MotionPreset } from \"./motion\";\n\n// ---------------------------------------------------------------------------\n// Duration / easing helpers\n// ---------------------------------------------------------------------------\n\nfunction parseDuration(ms: string): number {\n return parseInt(ms, 10) / 1000;\n}\n\nfunction parseEasing(raw: string): number[] | string {\n if (raw === \"linear\") return \"linear\" as string;\n const match = raw.match(\n /cubic-bezier\\(\\s*([\\d.]+)\\s*,\\s*([\\d.-]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.-]+)\\s*\\)/\n );\n if (!match) return [0.25, 0.1, 0.25, 1];\n return [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4])];\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface MotionVariant {\n hidden: Record<string, unknown>;\n visible: Record<string, unknown>;\n exit?: Record<string, unknown>;\n}\n\nexport interface StaggerContainerVariant {\n hidden: Record<string, unknown>;\n visible: Record<string, unknown>;\n}\n\nexport interface AnimationPresets {\n fadeIn: MotionVariant;\n fadeUp: MotionVariant;\n fadeDown: MotionVariant;\n scaleIn: MotionVariant;\n slideInLeft: MotionVariant;\n slideInRight: MotionVariant;\n cardLift: MotionVariant;\n staggerContainer: StaggerContainerVariant;\n staggerItem: MotionVariant;\n pageTransition: MotionVariant;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Returns Framer Motion variant presets tuned to the given motion preset.\n *\n * @example\n * ```tsx\n * import { getAnimationPresets } from \"@bison-lab/tokens\";\n * import { motion } from \"framer-motion\";\n *\n * const presets = getAnimationPresets(\"smooth\");\n *\n * <motion.div variants={presets.fadeUp} initial=\"hidden\" animate=\"visible\">\n * Hello\n * </motion.div>\n * ```\n */\nexport function getAnimationPresets(preset: MotionPreset = \"smooth\"): AnimationPresets {\n const tokens = motionPresets[preset];\n const normal = parseDuration(tokens[\"--duration-normal\"]);\n const slow = parseDuration(tokens[\"--duration-slow\"]);\n const ease = parseEasing(tokens[\"--ease-out\"]);\n\n const transition = { duration: normal, ease };\n const slowTransition = { duration: slow, ease };\n\n return {\n fadeIn: {\n hidden: { opacity: 0 },\n visible: { opacity: 1, transition },\n },\n\n fadeUp: {\n hidden: { opacity: 0, y: 10 },\n visible: { opacity: 1, y: 0, transition },\n },\n\n fadeDown: {\n hidden: { opacity: 0, y: -10 },\n visible: { opacity: 1, y: 0, transition },\n },\n\n scaleIn: {\n hidden: { opacity: 0, scale: 0.97 },\n visible: { opacity: 1, scale: 1, transition },\n },\n\n slideInLeft: {\n hidden: { opacity: 0, x: -16 },\n visible: { opacity: 1, x: 0, transition },\n },\n\n slideInRight: {\n hidden: { opacity: 0, x: 16 },\n visible: { opacity: 1, x: 0, transition },\n },\n\n cardLift: {\n hidden: { opacity: 0, y: 6, scale: 0.99 },\n visible: {\n opacity: 1,\n y: 0,\n scale: 1,\n transition: slowTransition,\n },\n exit: { opacity: 0, y: 4, scale: 0.99, transition },\n },\n\n staggerContainer: {\n hidden: {},\n visible: {\n transition: {\n staggerChildren: 0.07,\n delayChildren: 0.1,\n },\n },\n },\n\n staggerItem: {\n hidden: { opacity: 0, y: 8 },\n visible: { opacity: 1, y: 0, transition },\n },\n\n pageTransition: {\n hidden: { opacity: 0, y: 8 },\n visible: { opacity: 1, y: 0, transition: slowTransition },\n exit: { opacity: 0, y: -6, transition },\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/oklch.ts","../src/colors.ts","../src/radius.ts","../src/shadows.ts","../src/motion.ts","../src/density.ts","../src/typography.ts","../src/spacing.ts","../src/z-index.ts","../src/animation-presets.ts","../src/theme.ts","../src/contrast.ts"],"sourcesContent":["/**\n * OKLCH color space math — zero dependencies.\n * hex → sRGB → linear sRGB → OKLab (matrix) → OKLCH (polar)\n * Reverse: OKLCH → OKLab → linear sRGB → sRGB → HSL string\n * Gamut clamping via binary search on chroma (12 iterations).\n */\n\nexport interface OKLCH {\n L: number; // lightness 0–1\n C: number; // chroma ≥ 0\n H: number; // hue 0–360\n}\n\n// --- sRGB ↔ linear sRGB ---\n\nfunction srgbToLinear(c: number): number {\n return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n}\n\nfunction linearToSrgb(c: number): number {\n return c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n}\n\n// --- Hex → sRGB ---\n\nfunction hexToSrgb(hex: string): [number, number, number] {\n hex = hex.replace(\"#\", \"\");\n return [\n parseInt(hex.substring(0, 2), 16) / 255,\n parseInt(hex.substring(2, 4), 16) / 255,\n parseInt(hex.substring(4, 6), 16) / 255,\n ];\n}\n\n// --- linear sRGB ↔ OKLab ---\n\nfunction linearSrgbToOklab(r: number, g: number, b: number): [number, number, number] {\n const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;\n const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;\n const s = 0.0883024619 * r + 0.2024326059 * g + 0.6892649272 * b;\n\n const lc = Math.cbrt(l);\n const mc = Math.cbrt(m);\n const sc = Math.cbrt(s);\n\n return [\n 0.2104542553 * lc + 0.7936177850 * mc - 0.0040720468 * sc,\n 1.9779984951 * lc - 2.4285922050 * mc + 0.4505937099 * sc,\n 0.0259040371 * lc + 0.7827717662 * mc - 0.8086757660 * sc,\n ];\n}\n\nfunction oklabToLinearSrgb(L: number, a: number, b: number): [number, number, number] {\n const lc = L + 0.3963377774 * a + 0.2158037573 * b;\n const mc = L - 0.1055613458 * a - 0.0638541728 * b;\n const sc = L - 0.0894841775 * a - 1.2914855480 * b;\n\n return [\n 4.0767416621 * lc * lc * lc - 3.3077115913 * mc * mc * mc + 0.2309699292 * sc * sc * sc,\n -1.2684380046 * lc * lc * lc + 2.6097574011 * mc * mc * mc - 0.3413193965 * sc * sc * sc,\n -0.0041960863 * lc * lc * lc - 0.7034186147 * mc * mc * mc + 1.7076147010 * sc * sc * sc,\n ];\n}\n\n// --- OKLab ↔ OKLCH ---\n\nfunction oklabToOklch(L: number, a: number, b: number): OKLCH {\n const C = Math.sqrt(a * a + b * b);\n let H = (Math.atan2(b, a) * 180) / Math.PI;\n if (H < 0) H += 360;\n return { L, C, H };\n}\n\nfunction oklchToOklab(L: number, C: number, H: number): [number, number, number] {\n const hRad = (H * Math.PI) / 180;\n return [L, C * Math.cos(hRad), C * Math.sin(hRad)];\n}\n\n// --- Gamut clamping ---\n\nfunction isInGamut(r: number, g: number, b: number): boolean {\n const eps = 1e-4;\n return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;\n}\n\nfunction gamutClampChroma(L: number, C: number, H: number): number {\n if (C <= 0) return 0;\n\n const [, a, b] = oklchToOklab(L, C, H);\n const [r, g, bl] = oklabToLinearSrgb(L, a, b);\n\n if (isInGamut(r, g, bl)) return C;\n\n let lo = 0;\n let hi = C;\n\n for (let i = 0; i < 12; i++) {\n const mid = (lo + hi) / 2;\n const [, am, bm] = oklchToOklab(L, mid, H);\n const [rm, gm, bm2] = oklabToLinearSrgb(L, am, bm);\n\n if (isInGamut(rm, gm, bm2)) {\n lo = mid;\n } else {\n hi = mid;\n }\n }\n\n return lo;\n}\n\n// --- sRGB → HSL ---\n\nfunction srgbToHsl(r: number, g: number, b: number): [number, number, number] {\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n let h = 0;\n let s = 0;\n\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case r:\n h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n break;\n case g:\n h = ((b - r) / d + 2) / 6;\n break;\n case b:\n h = ((r - g) / d + 4) / 6;\n break;\n }\n }\n\n return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];\n}\n\n// --- Public API ---\n\nexport function hexToOklch(hex: string): OKLCH {\n const [r, g, b] = hexToSrgb(hex);\n const [lr, lg, lb] = [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];\n const [L, a, ob] = linearSrgbToOklab(lr, lg, lb);\n return oklabToOklch(L, a, ob);\n}\n\nexport function oklchToHslString(L: number, C: number, H: number): string {\n const clampedC = gamutClampChroma(L, C, H);\n const [, a, b] = oklchToOklab(L, clampedC, H);\n const [lr, lg, lb] = oklabToLinearSrgb(L, a, b);\n\n const r = Math.min(1, Math.max(0, linearToSrgb(lr)));\n const g = Math.min(1, Math.max(0, linearToSrgb(lg)));\n const bl = Math.min(1, Math.max(0, linearToSrgb(lb)));\n\n const [h, s, l] = srgbToHsl(r, g, bl);\n return `${h} ${s}% ${l}%`;\n}\n","/**\n * Color derivation engine.\n * Takes brand colors (hex) + a grey scale selection and derives\n * a full semantic palette for both light and dark modes,\n * plus 50–950 shade scales via OKLCH color space.\n */\n\nimport { hexToOklch, oklchToHslString } from \"./oklch\";\n\nexport interface HSL {\n h: number;\n s: number;\n l: number;\n}\n\nexport function hexToHSL(hex: string): HSL {\n hex = hex.replace(\"#\", \"\");\n const r = parseInt(hex.substring(0, 2), 16) / 255;\n const g = parseInt(hex.substring(2, 4), 16) / 255;\n const b = parseInt(hex.substring(4, 6), 16) / 255;\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n let h = 0;\n let s = 0;\n\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n switch (max) {\n case r:\n h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n break;\n case g:\n h = ((b - r) / d + 2) / 6;\n break;\n case b:\n h = ((r - g) / d + 4) / 6;\n break;\n }\n }\n\n return {\n h: Math.round(h * 360),\n s: Math.round(s * 100),\n l: Math.round(l * 100),\n };\n}\n\nexport function hslToString(hsl: HSL): string {\n return `${hsl.h} ${hsl.s}% ${hsl.l}%`;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n// --- Grey scale palettes (Tailwind v4 values as HSL strings) ---\n\nexport type GreyScale = \"slate\" | \"gray\" | \"zinc\" | \"neutral\" | \"stone\";\n\ntype GreyShade = \"50\" | \"100\" | \"200\" | \"300\" | \"400\" | \"500\" | \"600\" | \"700\" | \"800\" | \"900\" | \"950\";\n\ntype GreyPalette = Record<GreyShade, string>;\n\nexport const GREY_SCALES: Record<GreyScale, GreyPalette> = {\n slate: {\n \"50\": \"210 40% 98%\",\n \"100\": \"210 40% 96%\",\n \"200\": \"214 32% 91%\",\n \"300\": \"213 27% 84%\",\n \"400\": \"215 20% 65%\",\n \"500\": \"215 16% 47%\",\n \"600\": \"215 19% 35%\",\n \"700\": \"215 25% 27%\",\n \"800\": \"217 33% 17%\",\n \"900\": \"222 47% 11%\",\n \"950\": \"229 84% 5%\",\n },\n gray: {\n \"50\": \"210 20% 98%\",\n \"100\": \"220 14% 96%\",\n \"200\": \"220 13% 91%\",\n \"300\": \"216 12% 84%\",\n \"400\": \"218 11% 65%\",\n \"500\": \"220 9% 46%\",\n \"600\": \"215 14% 34%\",\n \"700\": \"217 19% 27%\",\n \"800\": \"215 28% 17%\",\n \"900\": \"221 39% 11%\",\n \"950\": \"224 71% 4%\",\n },\n zinc: {\n \"50\": \"0 0% 98%\",\n \"100\": \"240 5% 96%\",\n \"200\": \"240 6% 90%\",\n \"300\": \"240 5% 84%\",\n \"400\": \"240 5% 65%\",\n \"500\": \"240 4% 46%\",\n \"600\": \"240 5% 34%\",\n \"700\": \"240 5% 26%\",\n \"800\": \"240 4% 16%\",\n \"900\": \"240 6% 10%\",\n \"950\": \"240 10% 4%\",\n },\n neutral: {\n \"50\": \"0 0% 98%\",\n \"100\": \"0 0% 96%\",\n \"200\": \"0 0% 90%\",\n \"300\": \"0 0% 83%\",\n \"400\": \"0 0% 64%\",\n \"500\": \"0 0% 45%\",\n \"600\": \"0 0% 32%\",\n \"700\": \"0 0% 25%\",\n \"800\": \"0 0% 15%\",\n \"900\": \"0 0% 9%\",\n \"950\": \"0 0% 4%\",\n },\n stone: {\n \"50\": \"60 9% 98%\",\n \"100\": \"60 5% 96%\",\n \"200\": \"20 6% 90%\",\n \"300\": \"24 6% 83%\",\n \"400\": \"24 5% 64%\",\n \"500\": \"25 5% 45%\",\n \"600\": \"33 5% 32%\",\n \"700\": \"30 6% 25%\",\n \"800\": \"12 6% 15%\",\n \"900\": \"24 10% 10%\",\n \"950\": \"20 14% 4%\",\n },\n};\n\n// --- Brand color types ---\n\nexport interface BrandColors {\n primary: string; // hex\n secondary: string; // hex\n accent?: string; // hex\n highlight?: string; // hex\n success: string; // hex\n grey?: GreyScale; // defaults to \"slate\"\n}\n\n// --- Shade scale types ---\n\nexport const SHADE_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const;\nexport type ShadeStep = (typeof SHADE_STEPS)[number];\nexport type ColorScale = Record<ShadeStep, string>;\n\nexport interface BrandScales {\n primary: ColorScale;\n secondary: ColorScale;\n \"brand-accent\": ColorScale;\n highlight: ColorScale;\n success: ColorScale;\n}\n\n// --- Target OKLCH lightness per step ---\n\nconst TARGET_LIGHTNESS: Record<ShadeStep, number> = {\n 50: 0.97,\n 100: 0.93,\n 200: 0.87,\n 300: 0.79,\n 400: 0.70,\n 500: 0.59,\n 600: 0.49,\n 700: 0.39,\n 800: 0.30,\n 900: 0.21,\n 950: 0.14,\n};\n\n// --- Scale generation ---\n\n/**\n * Generate a full 50–950 color scale from a single hex value.\n * Uses OKLCH for perceptual uniformity. The input color is auto-anchored\n * to its closest shade step and reproduced pixel-perfect at that step.\n */\nexport function generateColorScale(hex: string): ColorScale {\n const { L: inputL, C: inputC, H } = hexToOklch(hex);\n\n // Sine-based chroma curve: peaks at mid-lightness, falls toward extremes\n const chromaCurve = (L: number): number => Math.sin(Math.PI * L);\n const inputCurveValue = chromaCurve(inputL);\n\n // Anchor ratio so that input chroma is preserved at input lightness\n const chromaRatio = inputCurveValue > 0.01 ? inputC / inputCurveValue : inputC;\n\n const scale = {} as ColorScale;\n\n for (const step of SHADE_STEPS) {\n const targetL = TARGET_LIGHTNESS[step];\n const targetC = inputCurveValue > 0.01\n ? chromaRatio * chromaCurve(targetL)\n : inputC * chromaCurve(targetL);\n\n scale[step] = oklchToHslString(targetL, targetC, H);\n }\n\n return scale;\n}\n\n/**\n * Generate shade scales for all brand colors.\n */\nexport function generateBrandScales(brand: BrandColors): BrandScales {\n const accentHex = brand.accent ?? brand.primary;\n const highlightHex = brand.highlight ?? brand.secondary;\n\n return {\n primary: generateColorScale(brand.primary),\n secondary: generateColorScale(brand.secondary),\n \"brand-accent\": generateColorScale(accentHex),\n highlight: generateColorScale(highlightHex),\n success: generateColorScale(brand.success),\n };\n}\n\n/**\n * Generate CSS custom property declarations for all brand scales.\n * Output is indented with 2 spaces, suitable for injection into :root {}.\n */\nexport function generateScaleCSS(scales: BrandScales): string {\n const lines: string[] = [];\n\n for (const [colorName, scale] of Object.entries(scales)) {\n for (const step of SHADE_STEPS) {\n lines.push(` --${colorName}-${step}: ${(scale as ColorScale)[step]};`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Canonical red for destructive UI (matches common “danger” hue). Used for the\n * destructive 50–950 scale so tinted surfaces (e.g. soft delete buttons) work\n * like success scales, independent of brand colors.\n */\nexport const DESTRUCTIVE_SCALE_HEX = \"#ef4444\";\n\nlet destructiveColorScaleCache: ColorScale | null = null;\n\nexport function getDestructiveColorScale(): ColorScale {\n if (!destructiveColorScaleCache) {\n destructiveColorScaleCache = generateColorScale(DESTRUCTIVE_SCALE_HEX);\n }\n return destructiveColorScaleCache;\n}\n\n/** CSS custom properties `--destructive-50` … `--destructive-950` for :root injection. */\nexport function generateDestructiveScaleCSS(): string {\n const scale = getDestructiveColorScale();\n return SHADE_STEPS.map((step) => ` --destructive-${step}: ${scale[step]};`).join(\"\\n\");\n}\n\n// --- Semantic palette ---\n\nexport interface SemanticPalette {\n background: string;\n foreground: string;\n card: string;\n \"card-foreground\": string;\n \"surface-tertiary\": string;\n popover: string;\n \"popover-foreground\": string;\n primary: string;\n \"primary-foreground\": string;\n secondary: string;\n \"secondary-foreground\": string;\n accent: string;\n \"accent-foreground\": string;\n \"brand-accent\": string;\n \"brand-accent-foreground\": string;\n highlight: string;\n \"highlight-foreground\": string;\n success: string;\n \"success-foreground\": string;\n muted: string;\n \"muted-foreground\": string;\n \"content-secondary\": string;\n destructive: string;\n \"destructive-foreground\": string;\n border: string;\n \"border-strong\": string;\n \"border-subtle\": string;\n input: string;\n ring: string;\n}\n\n// --- Foreground contrast ---\n\n/**\n * Near-white ink on a dark fill, near-black on a light one. A lightness\n * heuristic, not a measurement: `auditTheme` in `./contrast` is what says\n * whether the pairing actually reads.\n */\nfunction contrastForeground(color: HSL): string {\n return color.l > 55\n ? hslToString({ h: color.h, s: clamp(color.s - 20, 0, 100), l: 10 })\n : hslToString({ h: color.h, s: clamp(color.s - 25, 0, 100), l: 98 });\n}\n\n/**\n * A brand fill lightens in dark mode only below this lightness. The CLI\n * shipped 40 and this module shipped 50 until BIS-57; 40 is the value every\n * generated `bison-theme.css` in the field was built with, and Spinal\n * Simplicity's `globals.css` records why a fill at 43 must not lighten: its\n * derived ink flips to near-black, and the button's fixed `hover:bg-primary-700`\n * then reads at 1.74:1.\n */\nconst DARK_FILL_LIGHTEN_BELOW = 40;\n\n/**\n * The focus ring lightens below this lightness. A ring is a thin outline on\n * the page background and carries no ink, so it is free to lighten where a\n * fill is not: `#DB0082` at 43% is 4.1:1 on zinc-950, at 68% it is 7.1:1.\n */\nconst DARK_RING_LIGHTEN_BELOW = 50;\n\nfunction lightenForDark(color: HSL, below: number): HSL {\n if (color.l < below) {\n return { ...color, l: clamp(color.l + 25, 0, 100) };\n }\n return color;\n}\n\n// --- Palette derivation ---\n\n/**\n * The light semantic palette for a brand. This is the one derivation the\n * CLI (`bison-theme.css`), `BisonProvider` and a CMS theme all share.\n *\n * Decided in BIS-57, where the CLI and this module had drifted:\n * - `muted-foreground` is grey-600, not grey-500. grey-500 is 4.6:1 on the\n * page background, and the token carries card descriptions, hints, table\n * labels and footer lines; grey-600 is 7.4:1. Spinal Simplicity overrode\n * it by hand for its patient audience.\n * - `content-secondary` is grey-500, not grey-400. It is text: the\n * progress bar's percentage label and every chart axis tick use it, and\n * grey-400 is about 2.5:1 on the page background on every scale. grey-500\n * is 4.5:1 or better on both the page background and a card.\n */\nexport function deriveLightPalette(brand: BrandColors): SemanticPalette {\n const grey = GREY_SCALES[brand.grey ?? \"slate\"];\n const primary = hexToHSL(brand.primary);\n const secondary = hexToHSL(brand.secondary);\n const accent = brand.accent ? hexToHSL(brand.accent) : primary;\n const highlight = brand.highlight ? hexToHSL(brand.highlight) : secondary;\n const success = hexToHSL(brand.success);\n\n return {\n background: grey[\"50\"],\n foreground: grey[\"950\"],\n card: \"0 0% 100%\",\n \"card-foreground\": grey[\"950\"],\n \"surface-tertiary\": grey[\"100\"],\n popover: \"0 0% 100%\",\n \"popover-foreground\": grey[\"950\"],\n primary: hslToString(primary),\n \"primary-foreground\": contrastForeground(primary),\n secondary: hslToString(secondary),\n \"secondary-foreground\": contrastForeground(secondary),\n accent: grey[\"100\"],\n \"accent-foreground\": grey[\"950\"],\n \"brand-accent\": hslToString(accent),\n \"brand-accent-foreground\": contrastForeground(accent),\n highlight: hslToString(highlight),\n \"highlight-foreground\": contrastForeground(highlight),\n success: hslToString(success),\n \"success-foreground\": contrastForeground(success),\n muted: grey[\"100\"],\n \"muted-foreground\": grey[\"600\"],\n \"content-secondary\": grey[\"500\"],\n destructive: \"0 84% 60%\",\n \"destructive-foreground\": \"0 0% 98%\",\n border: grey[\"200\"],\n \"border-strong\": grey[\"300\"],\n \"border-subtle\": grey[\"100\"],\n input: grey[\"200\"],\n ring: hslToString(primary),\n };\n}\n\n/**\n * The dark semantic palette for a brand, derived from the same inputs as\n * the light one: no manual dark palette.\n *\n * Decided in BIS-57, where the CLI and this module had drifted:\n * - Brand fills lighten only below lightness 40 (`DARK_FILL_LIGHTEN_BELOW`);\n * the ring lightens below 50 (`DARK_RING_LIGHTEN_BELOW`). See both.\n * - `muted-foreground` is grey-300, not grey-400. grey-400 is 6.9:1 on\n * `card`, where most supporting copy sits; grey-300 clears 7:1 on both\n * surfaces.\n * - `content-secondary` is grey-400, not grey-500. grey-500 on `card` is\n * about 3.7:1, under AA; grey-400 is about 7:1.\n * - `destructive` is `0 62% 30%`, not `0 84% 65%`. `destructive-foreground`\n * (`0 0% 98%`) is 9.8:1 on the former and 3.2:1 on the latter, which\n * fails AA for the button label.\n */\nexport function deriveDarkPalette(brand: BrandColors): SemanticPalette {\n const grey = GREY_SCALES[brand.grey ?? \"slate\"];\n const primaryHsl = hexToHSL(brand.primary);\n const primary = lightenForDark(primaryHsl, DARK_FILL_LIGHTEN_BELOW);\n const secondary = lightenForDark(hexToHSL(brand.secondary), DARK_FILL_LIGHTEN_BELOW);\n const accent = lightenForDark(brand.accent ? hexToHSL(brand.accent) : primaryHsl, DARK_FILL_LIGHTEN_BELOW);\n const highlight = lightenForDark(\n brand.highlight ? hexToHSL(brand.highlight) : hexToHSL(brand.secondary),\n DARK_FILL_LIGHTEN_BELOW,\n );\n const success = lightenForDark(hexToHSL(brand.success), DARK_FILL_LIGHTEN_BELOW);\n const ring = lightenForDark(primaryHsl, DARK_RING_LIGHTEN_BELOW);\n\n return {\n background: grey[\"950\"],\n foreground: grey[\"50\"],\n card: grey[\"900\"],\n \"card-foreground\": grey[\"50\"],\n \"surface-tertiary\": grey[\"800\"],\n popover: grey[\"900\"],\n \"popover-foreground\": grey[\"50\"],\n primary: hslToString(primary),\n \"primary-foreground\": contrastForeground(primary),\n secondary: hslToString(secondary),\n \"secondary-foreground\": contrastForeground(secondary),\n accent: grey[\"800\"],\n \"accent-foreground\": grey[\"50\"],\n \"brand-accent\": hslToString(accent),\n \"brand-accent-foreground\": contrastForeground(accent),\n highlight: hslToString(highlight),\n \"highlight-foreground\": contrastForeground(highlight),\n success: hslToString(success),\n \"success-foreground\": contrastForeground(success),\n muted: grey[\"800\"],\n \"muted-foreground\": grey[\"300\"],\n \"content-secondary\": grey[\"400\"],\n destructive: \"0 62% 30%\",\n \"destructive-foreground\": \"0 0% 98%\",\n border: grey[\"800\"],\n \"border-strong\": grey[\"700\"],\n \"border-subtle\": grey[\"900\"],\n input: grey[\"800\"],\n ring: hslToString(ring),\n };\n}\n\nexport function generatePaletteCSS(brand: BrandColors, attribute: \"class\" | \"data-theme\" = \"class\"): string {\n const light = deriveLightPalette(brand);\n const dark = deriveDarkPalette(brand);\n const scales = generateBrandScales(brand);\n\n const toVars = (palette: SemanticPalette, indent: string) =>\n Object.entries(palette)\n .map(([key, value]) => `${indent}--${key}: ${value};`)\n .join(\"\\n\");\n\n const darkSelector = attribute === \"class\" ? \".dark\" : '[data-theme=\"dark\"]';\n\n return `:root {\\n${toVars(light, \" \")}\\n\\n${generateScaleCSS(scales)}\\n\\n${generateDestructiveScaleCSS()}\\n}\\n\\n${darkSelector} {\\n${toVars(dark, \" \")}\\n}\\n`;\n}\n","export const radiusPresets = {\n sharp: {\n \"--radius\": \"0rem\",\n \"--radius-sm\": \"0rem\",\n \"--radius-md\": \"0rem\",\n \"--radius-lg\": \"0rem\",\n \"--radius-xl\": \"0rem\",\n \"--radius-full\": \"0rem\",\n },\n subtle: {\n \"--radius\": \"0.375rem\",\n \"--radius-sm\": \"0.375rem\",\n \"--radius-md\": \"0.375rem\",\n \"--radius-lg\": \"0.5rem\",\n \"--radius-xl\": \"0.75rem\",\n \"--radius-full\": \"9999px\",\n },\n rounded: {\n \"--radius\": \"0.5rem\",\n \"--radius-sm\": \"0.5rem\",\n \"--radius-md\": \"0.5rem\",\n \"--radius-lg\": \"0.75rem\",\n \"--radius-xl\": \"1rem\",\n \"--radius-full\": \"9999px\",\n },\n pill: {\n \"--radius\": \"0.75rem\",\n \"--radius-sm\": \"0.75rem\",\n \"--radius-md\": \"0.75rem\",\n \"--radius-lg\": \"1rem\",\n \"--radius-xl\": \"1.5rem\",\n \"--radius-full\": \"9999px\",\n },\n} as const;\n\nexport type RadiusPreset = keyof typeof radiusPresets;\n","export const shadowPresets = {\n flat: {\n \"--shadow-sm\": \"none\",\n \"--shadow\": \"none\",\n \"--shadow-md\": \"none\",\n \"--shadow-lg\": \"none\",\n \"--shadow-xl\": \"none\",\n },\n subtle: {\n \"--shadow-sm\": \"0 1px 2px 0 rgb(0 0 0 / 0.03)\",\n \"--shadow\": \"0 1px 3px 0 rgb(0 0 0 / 0.05), 0 1px 2px -1px rgb(0 0 0 / 0.05)\",\n \"--shadow-md\": \"0 4px 6px -1px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.05)\",\n \"--shadow-lg\": \"0 10px 15px -3px rgb(0 0 0 / 0.05), 0 4px 6px -4px rgb(0 0 0 / 0.05)\",\n \"--shadow-xl\": \"0 20px 25px -5px rgb(0 0 0 / 0.05), 0 8px 10px -6px rgb(0 0 0 / 0.05)\",\n },\n elevated: {\n \"--shadow-sm\": \"0 1px 2px 0 rgb(0 0 0 / 0.06), 0 1px 3px 0 rgb(0 0 0 / 0.1)\",\n \"--shadow\": \"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)\",\n \"--shadow-md\": \"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\",\n \"--shadow-lg\": \"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)\",\n \"--shadow-xl\": \"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)\",\n },\n} as const;\n\nexport type ShadowPreset = keyof typeof shadowPresets;\n","export const motionPresets = {\n snappy: {\n \"--duration-fast\": \"100ms\",\n \"--duration-normal\": \"150ms\",\n \"--duration-slow\": \"250ms\",\n \"--ease-default\": \"cubic-bezier(0.22, 1, 0.36, 1)\",\n \"--ease-in\": \"cubic-bezier(0.55, 0, 1, 0.45)\",\n \"--ease-out\": \"cubic-bezier(0, 0.55, 0.45, 1)\",\n \"--ease-in-out\": \"cubic-bezier(0.65, 0, 0.35, 1)\",\n },\n smooth: {\n \"--duration-fast\": \"150ms\",\n \"--duration-normal\": \"200ms\",\n \"--duration-slow\": \"350ms\",\n \"--ease-default\": \"cubic-bezier(0.25, 0.1, 0.25, 1)\",\n \"--ease-in\": \"cubic-bezier(0.42, 0, 1, 1)\",\n \"--ease-out\": \"cubic-bezier(0, 0, 0.58, 1)\",\n \"--ease-in-out\": \"cubic-bezier(0.42, 0, 0.58, 1)\",\n },\n minimal: {\n \"--duration-fast\": \"75ms\",\n \"--duration-normal\": \"100ms\",\n \"--duration-slow\": \"200ms\",\n \"--ease-default\": \"linear\",\n \"--ease-in\": \"linear\",\n \"--ease-out\": \"linear\",\n \"--ease-in-out\": \"linear\",\n },\n} as const;\n\nexport type MotionPreset = keyof typeof motionPresets;\n","export const densityPresets = {\n compact: {\n \"--density-padding-xs\": \"0.125rem\",\n \"--density-padding-sm\": \"0.25rem\",\n \"--density-padding-md\": \"0.375rem\",\n \"--density-padding-lg\": \"0.5rem\",\n \"--density-padding-xl\": \"0.75rem\",\n \"--density-gap-sm\": \"0.25rem\",\n \"--density-gap-md\": \"0.5rem\",\n \"--density-gap-lg\": \"0.75rem\",\n \"--density-height-sm\": \"1.75rem\",\n \"--density-height-md\": \"2rem\",\n \"--density-height-lg\": \"2.5rem\",\n },\n default: {\n \"--density-padding-xs\": \"0.25rem\",\n \"--density-padding-sm\": \"0.5rem\",\n \"--density-padding-md\": \"0.75rem\",\n \"--density-padding-lg\": \"1rem\",\n \"--density-padding-xl\": \"1.5rem\",\n \"--density-gap-sm\": \"0.5rem\",\n \"--density-gap-md\": \"0.75rem\",\n \"--density-gap-lg\": \"1rem\",\n \"--density-height-sm\": \"2rem\",\n \"--density-height-md\": \"2.5rem\",\n \"--density-height-lg\": \"3rem\",\n },\n spacious: {\n \"--density-padding-xs\": \"0.5rem\",\n \"--density-padding-sm\": \"0.75rem\",\n \"--density-padding-md\": \"1rem\",\n \"--density-padding-lg\": \"1.5rem\",\n \"--density-padding-xl\": \"2rem\",\n \"--density-gap-sm\": \"0.75rem\",\n \"--density-gap-md\": \"1rem\",\n \"--density-gap-lg\": \"1.5rem\",\n \"--density-height-sm\": \"2.5rem\",\n \"--density-height-md\": \"3rem\",\n \"--density-height-lg\": \"3.5rem\",\n },\n} as const;\n\nexport type DensityPreset = keyof typeof densityPresets;\n","/** Locked type scale — consumers cannot override sizes, only font-family and weights. */\nexport const typeScale = {\n \"text-xs\": { fontSize: \"0.75rem\", lineHeight: \"1rem\" },\n \"text-sm\": { fontSize: \"0.875rem\", lineHeight: \"1.25rem\" },\n \"text-base\": { fontSize: \"1rem\", lineHeight: \"1.5rem\" },\n \"text-lg\": { fontSize: \"1.125rem\", lineHeight: \"1.75rem\" },\n \"text-xl\": { fontSize: \"1.25rem\", lineHeight: \"1.75rem\" },\n \"text-2xl\": { fontSize: \"1.5rem\", lineHeight: \"2rem\" },\n \"text-3xl\": { fontSize: \"1.875rem\", lineHeight: \"2.25rem\" },\n \"text-4xl\": { fontSize: \"2.25rem\", lineHeight: \"2.5rem\" },\n\n // Fluid headings using clamp()\n \"heading-sm\": {\n fontSize: \"clamp(1.125rem, 1rem + 0.5vw, 1.25rem)\",\n lineHeight: \"1.4\",\n letterSpacing: \"-0.01em\",\n },\n \"heading-md\": {\n fontSize: \"clamp(1.25rem, 1rem + 1vw, 1.875rem)\",\n lineHeight: \"1.3\",\n letterSpacing: \"-0.015em\",\n },\n \"heading-lg\": {\n fontSize: \"clamp(1.5rem, 1rem + 2vw, 2.25rem)\",\n lineHeight: \"1.2\",\n letterSpacing: \"-0.02em\",\n },\n \"heading-xl\": {\n fontSize: \"clamp(1.875rem, 1rem + 3vw, 3rem)\",\n lineHeight: \"1.1\",\n letterSpacing: \"-0.025em\",\n },\n \"heading-2xl\": {\n fontSize: \"clamp(2.25rem, 1rem + 4vw, 3.75rem)\",\n lineHeight: \"1.1\",\n letterSpacing: \"-0.03em\",\n },\n} as const;\n\nexport interface FontWeightMap {\n normal: number;\n medium: number;\n semibold: number;\n bold: number;\n extrabold?: number;\n}\n\nexport const defaultFontWeights: FontWeightMap = {\n normal: 400,\n medium: 500,\n semibold: 600,\n bold: 700,\n extrabold: 800,\n};\n","/** Locked spacing scale — 4px (0.25rem) base grid. Not consumer-overridable. */\nexport const spacingScale = {\n \"0\": \"0\",\n \"0.5\": \"0.125rem\",\n \"1\": \"0.25rem\",\n \"1.5\": \"0.375rem\",\n \"2\": \"0.5rem\",\n \"2.5\": \"0.625rem\",\n \"3\": \"0.75rem\",\n \"3.5\": \"0.875rem\",\n \"4\": \"1rem\",\n \"5\": \"1.25rem\",\n \"6\": \"1.5rem\",\n \"7\": \"1.75rem\",\n \"8\": \"2rem\",\n \"9\": \"2.25rem\",\n \"10\": \"2.5rem\",\n \"11\": \"2.75rem\",\n \"12\": \"3rem\",\n \"14\": \"3.5rem\",\n \"16\": \"4rem\",\n \"20\": \"5rem\",\n \"24\": \"6rem\",\n \"28\": \"7rem\",\n \"32\": \"8rem\",\n \"36\": \"9rem\",\n \"40\": \"10rem\",\n \"44\": \"11rem\",\n \"48\": \"12rem\",\n \"52\": \"13rem\",\n \"56\": \"14rem\",\n \"60\": \"15rem\",\n \"64\": \"16rem\",\n \"72\": \"18rem\",\n \"80\": \"20rem\",\n \"96\": \"24rem\",\n} as const;\n","/** Z-index tokens — overridable by consumers via CSS custom properties. */\nexport const zIndexTokens = {\n \"--z-dropdown\": \"50\",\n \"--z-sticky\": \"100\",\n \"--z-fixed\": \"200\",\n \"--z-modal-backdrop\": \"300\",\n \"--z-modal\": \"400\",\n \"--z-popover\": \"500\",\n \"--z-tooltip\": \"600\",\n \"--z-toast\": \"700\",\n} as const;\n\nexport type ZIndexToken = keyof typeof zIndexTokens;\n","/**\n * Framer Motion animation presets.\n *\n * Each preset is a Framer Motion `Variants` object with `hidden` and `visible`\n * states, plus optional `exit`. Durations and easings are resolved per motion\n * preset (snappy / smooth / minimal) at call time via `getAnimationPresets()`.\n */\n\nimport { motionPresets, type MotionPreset } from \"./motion\";\n\n// ---------------------------------------------------------------------------\n// Duration / easing helpers\n// ---------------------------------------------------------------------------\n\nfunction parseDuration(ms: string): number {\n return parseInt(ms, 10) / 1000;\n}\n\nfunction parseEasing(raw: string): number[] | string {\n if (raw === \"linear\") return \"linear\" as string;\n const match = raw.match(\n /cubic-bezier\\(\\s*([\\d.]+)\\s*,\\s*([\\d.-]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.-]+)\\s*\\)/\n );\n if (!match) return [0.25, 0.1, 0.25, 1];\n return [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4])];\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface MotionVariant {\n hidden: Record<string, unknown>;\n visible: Record<string, unknown>;\n exit?: Record<string, unknown>;\n}\n\nexport interface StaggerContainerVariant {\n hidden: Record<string, unknown>;\n visible: Record<string, unknown>;\n}\n\nexport interface AnimationPresets {\n fadeIn: MotionVariant;\n fadeUp: MotionVariant;\n fadeDown: MotionVariant;\n scaleIn: MotionVariant;\n slideInLeft: MotionVariant;\n slideInRight: MotionVariant;\n cardLift: MotionVariant;\n staggerContainer: StaggerContainerVariant;\n staggerItem: MotionVariant;\n pageTransition: MotionVariant;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Returns Framer Motion variant presets tuned to the given motion preset.\n *\n * @example\n * ```tsx\n * import { getAnimationPresets } from \"@bison-lab/tokens\";\n * import { motion } from \"framer-motion\";\n *\n * const presets = getAnimationPresets(\"smooth\");\n *\n * <motion.div variants={presets.fadeUp} initial=\"hidden\" animate=\"visible\">\n * Hello\n * </motion.div>\n * ```\n */\nexport function getAnimationPresets(preset: MotionPreset = \"smooth\"): AnimationPresets {\n const tokens = motionPresets[preset];\n const normal = parseDuration(tokens[\"--duration-normal\"]);\n const slow = parseDuration(tokens[\"--duration-slow\"]);\n const ease = parseEasing(tokens[\"--ease-out\"]);\n\n const transition = { duration: normal, ease };\n const slowTransition = { duration: slow, ease };\n\n return {\n fadeIn: {\n hidden: { opacity: 0 },\n visible: { opacity: 1, transition },\n },\n\n fadeUp: {\n hidden: { opacity: 0, y: 10 },\n visible: { opacity: 1, y: 0, transition },\n },\n\n fadeDown: {\n hidden: { opacity: 0, y: -10 },\n visible: { opacity: 1, y: 0, transition },\n },\n\n scaleIn: {\n hidden: { opacity: 0, scale: 0.97 },\n visible: { opacity: 1, scale: 1, transition },\n },\n\n slideInLeft: {\n hidden: { opacity: 0, x: -16 },\n visible: { opacity: 1, x: 0, transition },\n },\n\n slideInRight: {\n hidden: { opacity: 0, x: 16 },\n visible: { opacity: 1, x: 0, transition },\n },\n\n cardLift: {\n hidden: { opacity: 0, y: 6, scale: 0.99 },\n visible: {\n opacity: 1,\n y: 0,\n scale: 1,\n transition: slowTransition,\n },\n exit: { opacity: 0, y: 4, scale: 0.99, transition },\n },\n\n staggerContainer: {\n hidden: {},\n visible: {\n transition: {\n staggerChildren: 0.07,\n delayChildren: 0.1,\n },\n },\n },\n\n staggerItem: {\n hidden: { opacity: 0, y: 8 },\n visible: { opacity: 1, y: 0, transition },\n },\n\n pageTransition: {\n hidden: { opacity: 0, y: 8 },\n visible: { opacity: 1, y: 0, transition: slowTransition },\n exit: { opacity: 0, y: -6, transition },\n },\n };\n}\n","/**\n * The theme a site configures, and the stylesheet built from it.\n *\n * `ThemeConfig` is the `bison.config.json` shape `@bison-lab/create-theme`\n * writes. `buildThemeCss` turns one into the complete theme stylesheet: the\n * CLI writes it to `bison-theme.css`, and a CMS theme (BIS-60) renders it at\n * request time from the same function, so the two can never disagree.\n */\n\nimport {\n deriveLightPalette,\n deriveDarkPalette,\n generateBrandScales,\n generateScaleCSS,\n generateDestructiveScaleCSS,\n type BrandColors,\n type GreyScale,\n type SemanticPalette,\n} from \"./colors\";\nimport { radiusPresets, type RadiusPreset } from \"./radius\";\nimport { shadowPresets, type ShadowPreset } from \"./shadows\";\nimport { motionPresets, type MotionPreset } from \"./motion\";\nimport { densityPresets, type DensityPreset } from \"./density\";\n\nexport interface ThemeConfig {\n /** `#rrggbb` */\n brandPrimary: string;\n brandSecondary: string;\n brandAccent: string;\n brandHighlight: string;\n brandSuccess: string;\n greyScale: GreyScale;\n /**\n * The selector the dark block is written under, or `\"@media\"` for a\n * `prefers-color-scheme` query. Defaults to `[data-theme=\"dark\"]`, which\n * is what `ThemeProvider` sets and the Tailwind preset's `dark:` variant\n * reads.\n */\n darkSelector?: string;\n defaultTheme: \"light\" | \"dark\" | \"system\";\n radius: RadiusPreset;\n shadow: ShadowPreset;\n motion: MotionPreset;\n density: DensityPreset;\n /** A font family name. Becomes a catalogue id in BIS-58. */\n fontBody: string;\n /** A font family name, or `null` to use `fontBody` for headings. */\n fontHeading: string | null;\n fontWeights: {\n normal: number;\n medium: number;\n semibold: number;\n bold: number;\n };\n}\n\nexport interface BuildThemeCssOptions {\n /**\n * Overrides `config.darkSelector`: `\"class\"` writes the dark block under\n * `.dark`, `\"data-theme\"` under `[data-theme=\"dark\"]`.\n */\n attribute?: \"class\" | \"data-theme\";\n}\n\nexport const DEFAULT_DARK_SELECTOR = '[data-theme=\"dark\"]';\n\n/** Whether a value is the six-digit hex colour a theme field must hold. */\nexport function isThemeHex(value: unknown): value is string {\n return typeof value === \"string\" && /^#[0-9a-f]{6}$/i.test(value);\n}\n\ntype ThemeColorFields = Pick<\n ThemeConfig,\n \"brandPrimary\" | \"brandSecondary\" | \"brandAccent\" | \"brandHighlight\" | \"brandSuccess\" | \"greyScale\"\n>;\n\nconst BRAND_FIELDS = [\n \"brandPrimary\",\n \"brandSecondary\",\n \"brandAccent\",\n \"brandHighlight\",\n \"brandSuccess\",\n] as const satisfies readonly (keyof ThemeColorFields)[];\n\n/**\n * Refuses a config whose brand colours are not `#rrggbb`, naming the field.\n * `hexToHSL` reads fixed substrings, so a short or malformed hex would\n * otherwise become `NaN` in the palette without a word: `buildThemeCss`\n * would emit a variable the browser drops, and a five-digit hex would\n * measure a plausible, wrong contrast ratio.\n */\nfunction assertThemeColors(config: ThemeColorFields): void {\n for (const field of BRAND_FIELDS) {\n const value = config[field];\n if (!isThemeHex(value)) {\n throw new Error(`${field} must be a six-digit hex colour like #1e3a5f, got ${JSON.stringify(value)}`);\n }\n }\n}\n\n/** The `BrandColors` a config's five hexes and grey scale describe. Throws on a hex that is not `#rrggbb`. */\nexport function brandColorsFromConfig(config: ThemeColorFields): BrandColors {\n assertThemeColors(config);\n return {\n primary: config.brandPrimary,\n secondary: config.brandSecondary,\n accent: config.brandAccent,\n highlight: config.brandHighlight,\n success: config.brandSuccess,\n grey: config.greyScale,\n };\n}\n\nfunction resolveDarkSelector(config: ThemeConfig, options?: BuildThemeCssOptions): string {\n if (options?.attribute === \"class\") return \".dark\";\n if (options?.attribute === \"data-theme\") return DEFAULT_DARK_SELECTOR;\n return config.darkSelector?.trim() || DEFAULT_DARK_SELECTOR;\n}\n\nfunction paletteLines(palette: SemanticPalette, indent: string): string[] {\n return Object.entries(palette).map(([key, value]) => `${indent}--${key}: ${value};`);\n}\n\nfunction presetLines(vars: Record<string, string>): string[] {\n return Object.entries(vars).map(([key, value]) => ` ${key}: ${value};`);\n}\n\n/**\n * The complete theme stylesheet for a config: the light semantic palette,\n * the brand and destructive scales, the four preset variable blocks, the\n * typography variables, and the dark palette under the dark selector.\n *\n * The preset blocks mean a site needs only this file and `base.css`; the\n * preset files under `css/themes/` stay for consumers composing their own\n * theme, and importing both is harmless.\n *\n * Throws, naming the field, when a brand colour is not `#rrggbb`; a CMS\n * that stored a bad value fails loudly rather than shipping a stylesheet\n * with a `NaN` in it. A blank `darkSelector` means the default.\n *\n * Until BIS-58 lands the font catalogue, `fontBody` and `fontHeading` are\n * family names quoted straight into `--font-body` / `--font-heading`, as the\n * CLI has always written them.\n */\nexport function buildThemeCss(config: ThemeConfig, options?: BuildThemeCssOptions): string {\n const brand = brandColorsFromConfig(config);\n const light = deriveLightPalette(brand);\n const dark = deriveDarkPalette(brand);\n const scales = generateBrandScales(brand);\n const darkSelector = resolveDarkSelector(config, options);\n\n const darkBlock =\n darkSelector === \"@media\"\n ? [\n `@media (prefers-color-scheme: dark) {`,\n ` :root {`,\n ...paletteLines(dark, \" \"),\n ` }`,\n `}`,\n ]\n : [`${darkSelector} {`, ...paletteLines(dark, \" \"), `}`];\n\n const lines: string[] = [\n `/* Bison Lab Theme — generated by @bison-lab/tokens buildThemeCss. Do not hand-edit; regenerate from bison.config.json. */`,\n `/* Brand: ${config.brandPrimary} / ${config.brandSecondary} / ${config.brandAccent} / ${config.brandHighlight} / ${config.brandSuccess} */`,\n `/* Grey: ${config.greyScale} */`,\n `/* Presets: radius ${config.radius} · shadow ${config.shadow} · motion ${config.motion} · density ${config.density} */`,\n ``,\n `:root {`,\n ...paletteLines(light, \" \"),\n ``,\n ` /* Brand color scales (50–950) */`,\n generateScaleCSS(scales),\n ``,\n ` /* Destructive scale (50–950) — soft danger surfaces */`,\n generateDestructiveScaleCSS(),\n ``,\n ` /* Radius preset: ${config.radius} */`,\n ...presetLines(radiusPresets[config.radius]),\n ``,\n ` /* Shadow preset: ${config.shadow} */`,\n ...presetLines(shadowPresets[config.shadow]),\n ``,\n ` /* Motion preset: ${config.motion} */`,\n ...presetLines(motionPresets[config.motion]),\n ``,\n ` /* Density preset: ${config.density} */`,\n ...presetLines(densityPresets[config.density]),\n ``,\n ` /* Typography */`,\n ` --font-body: '${config.fontBody}', ui-sans-serif, system-ui, sans-serif;`,\n ` --font-heading: '${config.fontHeading ?? config.fontBody}', ui-sans-serif, system-ui, sans-serif;`,\n ` --font-weight-normal: ${config.fontWeights.normal};`,\n ` --font-weight-medium: ${config.fontWeights.medium};`,\n ` --font-weight-semibold: ${config.fontWeights.semibold};`,\n ` --font-weight-bold: ${config.fontWeights.bold};`,\n ` --font-weight-extrabold: 800;`,\n `}`,\n ``,\n ...darkBlock,\n ``,\n ];\n\n return lines.join(\"\\n\");\n}\n","/**\n * WCAG contrast, measured on the palette a theme derives.\n *\n * Pure functions with no DOM, so the Theme admin (BIS-60) can run them in\n * the browser as the editor types. `auditTheme` returns data only; the\n * `ContrastStrip` that draws it is BIS-59.\n */\n\nimport { deriveLightPalette, deriveDarkPalette, type SemanticPalette } from \"./colors\";\nimport { brandColorsFromConfig, type ThemeConfig } from \"./theme\";\n\n/** `#rrggbb`, or the token form the palettes use: `\"h s% l%\"`. */\nexport type ColorInput = string;\n\nexport type ContrastLevel = \"fail\" | \"aa-large\" | \"aa\" | \"aaa\";\n\n/**\n * What the pairing carries. `text` and `fill-with-text` are held to the\n * body text target; `large-text` to 4.5 under a 7:1 target, else 3;\n * `non-text` (a focus ring, a border) to WCAG 1.4.11's single 3:1 tier.\n */\nexport type ContrastRole = \"text\" | \"large-text\" | \"fill-with-text\" | \"non-text\";\n\nexport interface ContrastRating {\n aa: boolean;\n aaLarge: boolean;\n aaa: boolean;\n level: ContrastLevel;\n}\n\nexport interface ContrastSample {\n ratio: number;\n /** The WCAG text rating of `ratio`, whatever the role. */\n level: ContrastLevel;\n /** The measured colours, in the token form, so a renderer can draw them. */\n foreground: string;\n background: string;\n}\n\nexport interface ContrastCheck {\n /** Stable: `<foreground-token>-on-<background-token>`. */\n id: string;\n /** What the pairing affects, as advice: \"Secondary as eyebrow text on the page background\". */\n label: string;\n role: ContrastRole;\n light: ContrastSample;\n dark: ContrastSample;\n /** The threshold this check was judged against, after the role's rule. */\n target: number;\n /** Both modes at or above `target`. */\n meetsTarget: boolean;\n}\n\nexport type ContrastThemeInput = Pick<\n ThemeConfig,\n \"brandPrimary\" | \"brandSecondary\" | \"brandAccent\" | \"brandHighlight\" | \"brandSuccess\" | \"greyScale\"\n>;\n\nexport interface AuditThemeOptions {\n /** The body text target. 4.5 is AA; a site with an older audience passes 7. */\n target?: number;\n}\n\n// --- Colour parsing ---\n\ntype Rgb = [number, number, number];\n\nfunction hslToRgb(h: number, s: number, l: number): Rgb {\n const chroma = (1 - Math.abs(2 * l - 1)) * s;\n const hue = (((h % 360) + 360) % 360) / 60;\n const x = chroma * (1 - Math.abs((hue % 2) - 1));\n const m = l - chroma / 2;\n let rgb: Rgb;\n if (hue < 1) rgb = [chroma, x, 0];\n else if (hue < 2) rgb = [x, chroma, 0];\n else if (hue < 3) rgb = [0, chroma, x];\n else if (hue < 4) rgb = [0, x, chroma];\n else if (hue < 5) rgb = [x, 0, chroma];\n else rgb = [chroma, 0, x];\n return [rgb[0] + m, rgb[1] + m, rgb[2] + m];\n}\n\nfunction parseColor(input: ColorInput): Rgb {\n const hex = /^#([0-9a-f]{6})$/i.exec(input.trim());\n if (hex) {\n const value = parseInt(hex[1], 16);\n return [(value >> 16) & 255, (value >> 8) & 255, value & 255].map((channel) => channel / 255) as Rgb;\n }\n const triplet = /^(-?[\\d.]+)\\s+([\\d.]+)%\\s+([\\d.]+)%$/.exec(input.trim());\n if (triplet) {\n return hslToRgb(Number(triplet[1]), Number(triplet[2]) / 100, Number(triplet[3]) / 100);\n }\n throw new Error(`Not a colour this module reads: \"${input}\". Pass #rrggbb or \"h s% l%\".`);\n}\n\n// --- WCAG 2.x ---\n\nfunction linearChannel(channel: number): number {\n return channel <= 0.04045 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);\n}\n\n/** WCAG relative luminance, 0 for black to 1 for white. */\nexport function relativeLuminance(color: ColorInput): number {\n const [r, g, b] = parseColor(color);\n return 0.2126 * linearChannel(r) + 0.7152 * linearChannel(g) + 0.0722 * linearChannel(b);\n}\n\n/** WCAG contrast ratio between two colours, 1 to 21, to two decimal places. Order does not matter. */\nexport function contrastRatio(a: ColorInput, b: ColorInput): number {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n const ratio = (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);\n return Math.round(ratio * 100) / 100;\n}\n\n/** The WCAG text ratings a ratio earns: 3:1 large text, 4.5:1 AA, 7:1 AAA. */\nexport function rateContrast(ratio: number): ContrastRating {\n const aaLarge = ratio >= 3;\n const aa = ratio >= 4.5;\n const aaa = ratio >= 7;\n return { aa, aaLarge, aaa, level: aaa ? \"aaa\" : aa ? \"aa\" : aaLarge ? \"aa-large\" : \"fail\" };\n}\n\n// --- The audit ---\n\ntype PaletteToken = keyof SemanticPalette;\n\ninterface Pairing {\n foreground: PaletteToken;\n background: PaletteToken;\n role: ContrastRole;\n label: string;\n}\n\n/** Every pairing in the system that carries text, plus the focus ring. */\nconst PAIRINGS: readonly Pairing[] = [\n { foreground: \"primary-foreground\", background: \"primary\", role: \"fill-with-text\", label: \"Primary button label on the primary fill\" },\n { foreground: \"secondary-foreground\", background: \"secondary\", role: \"fill-with-text\", label: \"Secondary button label on the secondary fill\" },\n { foreground: \"brand-accent-foreground\", background: \"brand-accent\", role: \"fill-with-text\", label: \"Copy on a brand-accent band\" },\n { foreground: \"highlight-foreground\", background: \"highlight\", role: \"fill-with-text\", label: \"Copy on a highlight fill\" },\n { foreground: \"success-foreground\", background: \"success\", role: \"fill-with-text\", label: \"Success badge label on its fill\" },\n { foreground: \"destructive-foreground\", background: \"destructive\", role: \"fill-with-text\", label: \"Destructive button label on its fill\" },\n { foreground: \"accent-foreground\", background: \"accent\", role: \"fill-with-text\", label: \"Hovered and selected item text on the accent surface\" },\n { foreground: \"muted-foreground\", background: \"muted\", role: \"fill-with-text\", label: \"Hint text on a muted surface\" },\n { foreground: \"card-foreground\", background: \"card\", role: \"fill-with-text\", label: \"Card body text on the card surface\" },\n { foreground: \"popover-foreground\", background: \"popover\", role: \"fill-with-text\", label: \"Menu and popover text on the popover surface\" },\n { foreground: \"foreground\", background: \"background\", role: \"text\", label: \"Body text on the page background\" },\n { foreground: \"foreground\", background: \"card\", role: \"text\", label: \"Body text on a card\" },\n { foreground: \"muted-foreground\", background: \"background\", role: \"text\", label: \"Supporting text on the page background\" },\n { foreground: \"muted-foreground\", background: \"card\", role: \"text\", label: \"Supporting text on a card\" },\n { foreground: \"content-secondary\", background: \"background\", role: \"text\", label: \"Chart axis labels and tertiary text on the page background\" },\n { foreground: \"content-secondary\", background: \"card\", role: \"text\", label: \"Chart axis labels and tertiary text on a card\" },\n { foreground: \"primary\", background: \"background\", role: \"text\", label: \"Primary as link text on the page background\" },\n { foreground: \"secondary\", background: \"background\", role: \"text\", label: \"Secondary as eyebrow text on the page background\" },\n { foreground: \"ring\", background: \"background\", role: \"non-text\", label: \"Focus ring on the page background\" },\n];\n\nfunction roleTarget(role: ContrastRole, target: number): number {\n switch (role) {\n case \"non-text\":\n return 3;\n case \"large-text\":\n return target >= 7 ? 4.5 : 3;\n default:\n return target;\n }\n}\n\nfunction sample(palette: SemanticPalette, pairing: Pairing): ContrastSample {\n const foreground = palette[pairing.foreground];\n const background = palette[pairing.background];\n const ratio = contrastRatio(foreground, background);\n return { ratio, level: rateContrast(ratio).level, foreground, background };\n}\n\n/**\n * Measures every text-carrying pairing the palette derives for these\n * colours, in light and dark. Advice, not enforcement: nothing here refuses\n * a theme on its ratios. The checks are data; BIS-59's `ContrastStrip` draws\n * them.\n *\n * Throws, naming the field, when a brand colour is not `#rrggbb`, the same\n * contract as `buildThemeCss`. An admin field validates its own value\n * before calling this, so a half-typed hex never reaches it.\n */\nexport function auditTheme(config: ContrastThemeInput, options: AuditThemeOptions = {}): ContrastCheck[] {\n const target = options.target ?? 4.5;\n const brand = brandColorsFromConfig(config);\n const light = deriveLightPalette(brand);\n const dark = deriveDarkPalette(brand);\n\n return PAIRINGS.map((pairing) => {\n const lightSample = sample(light, pairing);\n const darkSample = sample(dark, pairing);\n const threshold = roleTarget(pairing.role, target);\n return {\n id: `${pairing.foreground}-on-${pairing.background}`,\n label: pairing.label,\n role: pairing.role,\n light: lightSample,\n dark: darkSample,\n target: threshold,\n meetsTarget: lightSample.ratio >= threshold && darkSample.ratio >= threshold,\n };\n });\n}\n"],"mappings":";AAeA,SAAS,aAAa,GAAmB;AACvC,QAAO,KAAK,SAAU,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAS,OAAO,IAAI;;AAGtE,SAAS,aAAa,GAAmB;AACvC,QAAO,KAAK,WAAY,IAAI,QAAQ,QAAQ,KAAK,IAAI,GAAG,IAAI,IAAI,GAAG;;AAKrE,SAAS,UAAU,KAAuC;AACxD,OAAM,IAAI,QAAQ,KAAK,GAAG;AAC1B,QAAO;EACL,SAAS,IAAI,UAAU,GAAG,EAAE,EAAE,GAAG,GAAG;EACpC,SAAS,IAAI,UAAU,GAAG,EAAE,EAAE,GAAG,GAAG;EACpC,SAAS,IAAI,UAAU,GAAG,EAAE,EAAE,GAAG,GAAG;EACrC;;AAKH,SAAS,kBAAkB,GAAW,GAAW,GAAqC;CACpF,MAAM,IAAI,cAAe,IAAI,cAAe,IAAI,cAAe;CAC/D,MAAM,IAAI,cAAe,IAAI,cAAe,IAAI,cAAe;CAC/D,MAAM,IAAI,cAAe,IAAI,cAAe,IAAI,cAAe;CAE/D,MAAM,KAAK,KAAK,KAAK,EAAE;CACvB,MAAM,KAAK,KAAK,KAAK,EAAE;CACvB,MAAM,KAAK,KAAK,KAAK,EAAE;AAEvB,QAAO;EACL,cAAe,KAAK,aAAe,KAAK,cAAe;EACvD,eAAe,KAAK,cAAe,KAAK,cAAe;EACvD,cAAe,KAAK,cAAe,KAAK,aAAe;EACxD;;AAGH,SAAS,kBAAkB,GAAW,GAAW,GAAqC;CACpF,MAAM,KAAK,IAAI,cAAe,IAAI,cAAe;CACjD,MAAM,KAAK,IAAI,cAAe,IAAI,cAAe;CACjD,MAAM,KAAK,IAAI,cAAe,IAAI,cAAe;AAEjD,QAAO;EACJ,eAAe,KAAK,KAAK,KAAK,eAAe,KAAK,KAAK,KAAK,cAAe,KAAK,KAAK;EACtF,gBAAgB,KAAK,KAAK,KAAK,eAAe,KAAK,KAAK,KAAK,cAAe,KAAK,KAAK;EACtF,eAAgB,KAAK,KAAK,KAAK,cAAe,KAAK,KAAK,KAAK,cAAe,KAAK,KAAK;EACvF;;AAKH,SAAS,aAAa,GAAW,GAAW,GAAkB;CAC5D,MAAM,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,EAAE;CAClC,IAAI,IAAK,KAAK,MAAM,GAAG,EAAE,GAAG,MAAO,KAAK;AACxC,KAAI,IAAI,EAAG,MAAK;AAChB,QAAO;EAAE;EAAG;EAAG;EAAG;;AAGpB,SAAS,aAAa,GAAW,GAAW,GAAqC;CAC/E,MAAM,OAAQ,IAAI,KAAK,KAAM;AAC7B,QAAO;EAAC;EAAG,IAAI,KAAK,IAAI,KAAK;EAAE,IAAI,KAAK,IAAI,KAAK;EAAC;;AAKpD,SAAS,UAAU,GAAW,GAAW,GAAoB;CAC3D,MAAM,MAAM;AACZ,QAAO,KAAK,CAAC,OAAO,KAAK,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,IAAI;;AAGzF,SAAS,iBAAiB,GAAW,GAAW,GAAmB;AACjE,KAAI,KAAK,EAAG,QAAO;CAEnB,MAAM,GAAG,GAAG,KAAK,aAAa,GAAG,GAAG,EAAE;CACtC,MAAM,CAAC,GAAG,GAAG,MAAM,kBAAkB,GAAG,GAAG,EAAE;AAE7C,KAAI,UAAU,GAAG,GAAG,GAAG,CAAE,QAAO;CAEhC,IAAI,KAAK;CACT,IAAI,KAAK;AAET,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,OAAO,KAAK,MAAM;EACxB,MAAM,GAAG,IAAI,MAAM,aAAa,GAAG,KAAK,EAAE;EAC1C,MAAM,CAAC,IAAI,IAAI,OAAO,kBAAkB,GAAG,IAAI,GAAG;AAElD,MAAI,UAAU,IAAI,IAAI,IAAI,CACxB,MAAK;MAEL,MAAK;;AAIT,QAAO;;AAKT,SAAS,UAAU,GAAW,GAAW,GAAqC;CAC5E,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,EAAE;CAC7B,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,EAAE;CAC7B,MAAM,KAAK,MAAM,OAAO;CACxB,IAAI,IAAI;CACR,IAAI,IAAI;AAER,KAAI,QAAQ,KAAK;EACf,MAAM,IAAI,MAAM;AAChB,MAAI,IAAI,KAAM,KAAK,IAAI,MAAM,OAAO,KAAK,MAAM;AAC/C,UAAQ,KAAR;GACE,KAAK;AACH,UAAM,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,MAAM;AACtC;GACF,KAAK;AACH,UAAM,IAAI,KAAK,IAAI,KAAK;AACxB;GACF,KAAK;AACH,UAAM,IAAI,KAAK,IAAI,KAAK;AACxB;;;AAIN,QAAO;EAAC,KAAK,MAAM,IAAI,IAAI;EAAE,KAAK,MAAM,IAAI,IAAI;EAAE,KAAK,MAAM,IAAI,IAAI;EAAC;;AAKxE,SAAgB,WAAW,KAAoB;CAC7C,MAAM,CAAC,GAAG,GAAG,KAAK,UAAU,IAAI;CAChC,MAAM,CAAC,IAAI,IAAI,MAAM;EAAC,aAAa,EAAE;EAAE,aAAa,EAAE;EAAE,aAAa,EAAE;EAAC;CACxE,MAAM,CAAC,GAAG,GAAG,MAAM,kBAAkB,IAAI,IAAI,GAAG;AAChD,QAAO,aAAa,GAAG,GAAG,GAAG;;AAG/B,SAAgB,iBAAiB,GAAW,GAAW,GAAmB;CAExE,MAAM,GAAG,GAAG,KAAK,aAAa,GADb,iBAAiB,GAAG,GAAG,EAAE,EACC,EAAE;CAC7C,MAAM,CAAC,IAAI,IAAI,MAAM,kBAAkB,GAAG,GAAG,EAAE;CAM/C,MAAM,CAAC,GAAG,GAAG,KAAK,UAJR,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,aAAa,GAAG,CAAC,CAAC,EAC1C,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,aAAa,GAAG,CAAC,CAAC,EACzC,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,aAAa,GAAG,CAAC,CAAC,CAEhB;AACrC,QAAO,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE;;;;;;;;;;AC/IzB,SAAgB,SAAS,KAAkB;AACzC,OAAM,IAAI,QAAQ,KAAK,GAAG;CAC1B,MAAM,IAAI,SAAS,IAAI,UAAU,GAAG,EAAE,EAAE,GAAG,GAAG;CAC9C,MAAM,IAAI,SAAS,IAAI,UAAU,GAAG,EAAE,EAAE,GAAG,GAAG;CAC9C,MAAM,IAAI,SAAS,IAAI,UAAU,GAAG,EAAE,EAAE,GAAG,GAAG;CAE9C,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,EAAE;CAC7B,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,EAAE;CAC7B,MAAM,KAAK,MAAM,OAAO;CACxB,IAAI,IAAI;CACR,IAAI,IAAI;AAER,KAAI,QAAQ,KAAK;EACf,MAAM,IAAI,MAAM;AAChB,MAAI,IAAI,KAAM,KAAK,IAAI,MAAM,OAAO,KAAK,MAAM;AAC/C,UAAQ,KAAR;GACE,KAAK;AACH,UAAM,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,MAAM;AACtC;GACF,KAAK;AACH,UAAM,IAAI,KAAK,IAAI,KAAK;AACxB;GACF,KAAK;AACH,UAAM,IAAI,KAAK,IAAI,KAAK;AACxB;;;AAIN,QAAO;EACL,GAAG,KAAK,MAAM,IAAI,IAAI;EACtB,GAAG,KAAK,MAAM,IAAI,IAAI;EACtB,GAAG,KAAK,MAAM,IAAI,IAAI;EACvB;;AAGH,SAAgB,YAAY,KAAkB;AAC5C,QAAO,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,IAAI,EAAE;;AAGrC,SAAS,MAAM,OAAe,KAAa,KAAqB;AAC9D,QAAO,KAAK,IAAI,KAAK,IAAI,OAAO,IAAI,EAAE,IAAI;;AAW5C,MAAa,cAA8C;CACzD,OAAO;EACL,MAAM;EACN,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACR;CACD,MAAM;EACJ,MAAM;EACN,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACR;CACD,MAAM;EACJ,MAAM;EACN,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACR;CACD,SAAS;EACP,MAAM;EACN,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACR;CACD,OAAO;EACL,MAAM;EACN,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACR;CACF;AAeD,MAAa,cAAc;CAAC;CAAI;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAI;AAcjF,MAAM,mBAA8C;CAClD,IAAI;CACJ,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACN;;;;;;AASD,SAAgB,mBAAmB,KAAyB;CAC1D,MAAM,EAAE,GAAG,QAAQ,GAAG,QAAQ,MAAM,WAAW,IAAI;CAGnD,MAAM,eAAe,MAAsB,KAAK,IAAI,KAAK,KAAK,EAAE;CAChE,MAAM,kBAAkB,YAAY,OAAO;CAG3C,MAAM,cAAc,kBAAkB,MAAO,SAAS,kBAAkB;CAExE,MAAM,QAAQ,EAAE;AAEhB,MAAK,MAAM,QAAQ,aAAa;EAC9B,MAAM,UAAU,iBAAiB;AAKjC,QAAM,QAAQ,iBAAiB,SAJf,kBAAkB,MAC9B,cAAc,YAAY,QAAQ,GAClC,SAAS,YAAY,QAAQ,EAEgB,EAAE;;AAGrD,QAAO;;;;;AAMT,SAAgB,oBAAoB,OAAiC;CACnE,MAAM,YAAY,MAAM,UAAU,MAAM;CACxC,MAAM,eAAe,MAAM,aAAa,MAAM;AAE9C,QAAO;EACL,SAAS,mBAAmB,MAAM,QAAQ;EAC1C,WAAW,mBAAmB,MAAM,UAAU;EAC9C,gBAAgB,mBAAmB,UAAU;EAC7C,WAAW,mBAAmB,aAAa;EAC3C,SAAS,mBAAmB,MAAM,QAAQ;EAC3C;;;;;;AAOH,SAAgB,iBAAiB,QAA6B;CAC5D,MAAM,QAAkB,EAAE;AAE1B,MAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,OAAO,CACrD,MAAK,MAAM,QAAQ,YACjB,OAAM,KAAK,OAAO,UAAU,GAAG,KAAK,IAAK,MAAqB,MAAM,GAAG;AAI3E,QAAO,MAAM,KAAK,KAAK;;;;;;;AAQzB,MAAa,wBAAwB;AAErC,IAAI,6BAAgD;AAEpD,SAAgB,2BAAuC;AACrD,KAAI,CAAC,2BACH,8BAA6B,mBAAmB,sBAAsB;AAExE,QAAO;;;AAIT,SAAgB,8BAAsC;CACpD,MAAM,QAAQ,0BAA0B;AACxC,QAAO,YAAY,KAAK,SAAS,mBAAmB,KAAK,IAAI,MAAM,MAAM,GAAG,CAAC,KAAK,KAAK;;;;;;;AA4CzF,SAAS,mBAAmB,OAAoB;AAC9C,QAAO,MAAM,IAAI,KACb,YAAY;EAAE,GAAG,MAAM;EAAG,GAAG,MAAM,MAAM,IAAI,IAAI,GAAG,IAAI;EAAE,GAAG;EAAI,CAAC,GAClE,YAAY;EAAE,GAAG,MAAM;EAAG,GAAG,MAAM,MAAM,IAAI,IAAI,GAAG,IAAI;EAAE,GAAG;EAAI,CAAC;;;;;;;;;;AAWxE,MAAM,0BAA0B;;;;;;AAOhC,MAAM,0BAA0B;AAEhC,SAAS,eAAe,OAAY,OAAoB;AACtD,KAAI,MAAM,IAAI,MACZ,QAAO;EAAE,GAAG;EAAO,GAAG,MAAM,MAAM,IAAI,IAAI,GAAG,IAAI;EAAE;AAErD,QAAO;;;;;;;;;;;;;;;;AAmBT,SAAgB,mBAAmB,OAAqC;CACtE,MAAM,OAAO,YAAY,MAAM,QAAQ;CACvC,MAAM,UAAU,SAAS,MAAM,QAAQ;CACvC,MAAM,YAAY,SAAS,MAAM,UAAU;CAC3C,MAAM,SAAS,MAAM,SAAS,SAAS,MAAM,OAAO,GAAG;CACvD,MAAM,YAAY,MAAM,YAAY,SAAS,MAAM,UAAU,GAAG;CAChE,MAAM,UAAU,SAAS,MAAM,QAAQ;AAEvC,QAAO;EACL,YAAY,KAAK;EACjB,YAAY,KAAK;EACjB,MAAM;EACN,mBAAmB,KAAK;EACxB,oBAAoB,KAAK;EACzB,SAAS;EACT,sBAAsB,KAAK;EAC3B,SAAS,YAAY,QAAQ;EAC7B,sBAAsB,mBAAmB,QAAQ;EACjD,WAAW,YAAY,UAAU;EACjC,wBAAwB,mBAAmB,UAAU;EACrD,QAAQ,KAAK;EACb,qBAAqB,KAAK;EAC1B,gBAAgB,YAAY,OAAO;EACnC,2BAA2B,mBAAmB,OAAO;EACrD,WAAW,YAAY,UAAU;EACjC,wBAAwB,mBAAmB,UAAU;EACrD,SAAS,YAAY,QAAQ;EAC7B,sBAAsB,mBAAmB,QAAQ;EACjD,OAAO,KAAK;EACZ,oBAAoB,KAAK;EACzB,qBAAqB,KAAK;EAC1B,aAAa;EACb,0BAA0B;EAC1B,QAAQ,KAAK;EACb,iBAAiB,KAAK;EACtB,iBAAiB,KAAK;EACtB,OAAO,KAAK;EACZ,MAAM,YAAY,QAAQ;EAC3B;;;;;;;;;;;;;;;;;;AAmBH,SAAgB,kBAAkB,OAAqC;CACrE,MAAM,OAAO,YAAY,MAAM,QAAQ;CACvC,MAAM,aAAa,SAAS,MAAM,QAAQ;CAC1C,MAAM,UAAU,eAAe,YAAY,wBAAwB;CACnE,MAAM,YAAY,eAAe,SAAS,MAAM,UAAU,EAAE,wBAAwB;CACpF,MAAM,SAAS,eAAe,MAAM,SAAS,SAAS,MAAM,OAAO,GAAG,YAAY,wBAAwB;CAC1G,MAAM,YAAY,eAChB,MAAM,YAAY,SAAS,MAAM,UAAU,GAAG,SAAS,MAAM,UAAU,EACvE,wBACD;CACD,MAAM,UAAU,eAAe,SAAS,MAAM,QAAQ,EAAE,wBAAwB;CAChF,MAAM,OAAO,eAAe,YAAY,wBAAwB;AAEhE,QAAO;EACL,YAAY,KAAK;EACjB,YAAY,KAAK;EACjB,MAAM,KAAK;EACX,mBAAmB,KAAK;EACxB,oBAAoB,KAAK;EACzB,SAAS,KAAK;EACd,sBAAsB,KAAK;EAC3B,SAAS,YAAY,QAAQ;EAC7B,sBAAsB,mBAAmB,QAAQ;EACjD,WAAW,YAAY,UAAU;EACjC,wBAAwB,mBAAmB,UAAU;EACrD,QAAQ,KAAK;EACb,qBAAqB,KAAK;EAC1B,gBAAgB,YAAY,OAAO;EACnC,2BAA2B,mBAAmB,OAAO;EACrD,WAAW,YAAY,UAAU;EACjC,wBAAwB,mBAAmB,UAAU;EACrD,SAAS,YAAY,QAAQ;EAC7B,sBAAsB,mBAAmB,QAAQ;EACjD,OAAO,KAAK;EACZ,oBAAoB,KAAK;EACzB,qBAAqB,KAAK;EAC1B,aAAa;EACb,0BAA0B;EAC1B,QAAQ,KAAK;EACb,iBAAiB,KAAK;EACtB,iBAAiB,KAAK;EACtB,OAAO,KAAK;EACZ,MAAM,YAAY,KAAK;EACxB;;AAGH,SAAgB,mBAAmB,OAAoB,YAAoC,SAAiB;CAC1G,MAAM,QAAQ,mBAAmB,MAAM;CACvC,MAAM,OAAO,kBAAkB,MAAM;CACrC,MAAM,SAAS,oBAAoB,MAAM;CAEzC,MAAM,UAAU,SAA0B,WACxC,OAAO,QAAQ,QAAQ,CACpB,KAAK,CAAC,KAAK,WAAW,GAAG,OAAO,IAAI,IAAI,IAAI,MAAM,GAAG,CACrD,KAAK,KAAK;CAEf,MAAM,eAAe,cAAc,UAAU,UAAU;AAEvD,QAAO,YAAY,OAAO,OAAO,KAAK,CAAC,MAAM,iBAAiB,OAAO,CAAC,MAAM,6BAA6B,CAAC,SAAS,aAAa,MAAM,OAAO,MAAM,KAAK,CAAC;;;;AC9c3J,MAAa,gBAAgB;CAC3B,OAAO;EACL,YAAY;EACZ,eAAe;EACf,eAAe;EACf,eAAe;EACf,eAAe;EACf,iBAAiB;EAClB;CACD,QAAQ;EACN,YAAY;EACZ,eAAe;EACf,eAAe;EACf,eAAe;EACf,eAAe;EACf,iBAAiB;EAClB;CACD,SAAS;EACP,YAAY;EACZ,eAAe;EACf,eAAe;EACf,eAAe;EACf,eAAe;EACf,iBAAiB;EAClB;CACD,MAAM;EACJ,YAAY;EACZ,eAAe;EACf,eAAe;EACf,eAAe;EACf,eAAe;EACf,iBAAiB;EAClB;CACF;;;ACjCD,MAAa,gBAAgB;CAC3B,MAAM;EACJ,eAAe;EACf,YAAY;EACZ,eAAe;EACf,eAAe;EACf,eAAe;EAChB;CACD,QAAQ;EACN,eAAe;EACf,YAAY;EACZ,eAAe;EACf,eAAe;EACf,eAAe;EAChB;CACD,UAAU;EACR,eAAe;EACf,YAAY;EACZ,eAAe;EACf,eAAe;EACf,eAAe;EAChB;CACF;;;ACtBD,MAAa,gBAAgB;CAC3B,QAAQ;EACN,mBAAmB;EACnB,qBAAqB;EACrB,mBAAmB;EACnB,kBAAkB;EAClB,aAAa;EACb,cAAc;EACd,iBAAiB;EAClB;CACD,QAAQ;EACN,mBAAmB;EACnB,qBAAqB;EACrB,mBAAmB;EACnB,kBAAkB;EAClB,aAAa;EACb,cAAc;EACd,iBAAiB;EAClB;CACD,SAAS;EACP,mBAAmB;EACnB,qBAAqB;EACrB,mBAAmB;EACnB,kBAAkB;EAClB,aAAa;EACb,cAAc;EACd,iBAAiB;EAClB;CACF;;;AC5BD,MAAa,iBAAiB;CAC5B,SAAS;EACP,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,oBAAoB;EACpB,oBAAoB;EACpB,oBAAoB;EACpB,uBAAuB;EACvB,uBAAuB;EACvB,uBAAuB;EACxB;CACD,SAAS;EACP,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,oBAAoB;EACpB,oBAAoB;EACpB,oBAAoB;EACpB,uBAAuB;EACvB,uBAAuB;EACvB,uBAAuB;EACxB;CACD,UAAU;EACR,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,wBAAwB;EACxB,oBAAoB;EACpB,oBAAoB;EACpB,oBAAoB;EACpB,uBAAuB;EACvB,uBAAuB;EACvB,uBAAuB;EACxB;CACF;;;;ACvCD,MAAa,YAAY;CACvB,WAAW;EAAE,UAAU;EAAW,YAAY;EAAQ;CACtD,WAAW;EAAE,UAAU;EAAY,YAAY;EAAW;CAC1D,aAAa;EAAE,UAAU;EAAQ,YAAY;EAAU;CACvD,WAAW;EAAE,UAAU;EAAY,YAAY;EAAW;CAC1D,WAAW;EAAE,UAAU;EAAW,YAAY;EAAW;CACzD,YAAY;EAAE,UAAU;EAAU,YAAY;EAAQ;CACtD,YAAY;EAAE,UAAU;EAAY,YAAY;EAAW;CAC3D,YAAY;EAAE,UAAU;EAAW,YAAY;EAAU;CAGzD,cAAc;EACZ,UAAU;EACV,YAAY;EACZ,eAAe;EAChB;CACD,cAAc;EACZ,UAAU;EACV,YAAY;EACZ,eAAe;EAChB;CACD,cAAc;EACZ,UAAU;EACV,YAAY;EACZ,eAAe;EAChB;CACD,cAAc;EACZ,UAAU;EACV,YAAY;EACZ,eAAe;EAChB;CACD,eAAe;EACb,UAAU;EACV,YAAY;EACZ,eAAe;EAChB;CACF;AAUD,MAAa,qBAAoC;CAC/C,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,MAAM;CACN,WAAW;CACZ;;;;ACpDD,MAAa,eAAe;CAC1B,KAAK;CACL,OAAO;CACP,KAAK;CACL,OAAO;CACP,KAAK;CACL,OAAO;CACP,KAAK;CACL,OAAO;CACP,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACP;;;;ACnCD,MAAa,eAAe;CAC1B,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,sBAAsB;CACtB,aAAa;CACb,eAAe;CACf,eAAe;CACf,aAAa;CACd;;;;;;;;;;ACID,SAAS,cAAc,IAAoB;AACzC,QAAO,SAAS,IAAI,GAAG,GAAG;;AAG5B,SAAS,YAAY,KAAgC;AACnD,KAAI,QAAQ,SAAU,QAAO;CAC7B,MAAM,QAAQ,IAAI,MAChB,gFACD;AACD,KAAI,CAAC,MAAO,QAAO;EAAC;EAAM;EAAK;EAAM;EAAE;AACvC,QAAO;EAAC,OAAO,MAAM,GAAG;EAAE,OAAO,MAAM,GAAG;EAAE,OAAO,MAAM,GAAG;EAAE,OAAO,MAAM,GAAG;EAAC;;;;;;;;;;;;;;;;;AAkDjF,SAAgB,oBAAoB,SAAuB,UAA4B;CACrF,MAAM,SAAS,cAAc;CAC7B,MAAM,SAAS,cAAc,OAAO,qBAAqB;CACzD,MAAM,OAAO,cAAc,OAAO,mBAAmB;CACrD,MAAM,OAAO,YAAY,OAAO,cAAc;CAE9C,MAAM,aAAa;EAAE,UAAU;EAAQ;EAAM;CAC7C,MAAM,iBAAiB;EAAE,UAAU;EAAM;EAAM;AAE/C,QAAO;EACL,QAAQ;GACN,QAAQ,EAAE,SAAS,GAAG;GACtB,SAAS;IAAE,SAAS;IAAG;IAAY;GACpC;EAED,QAAQ;GACN,QAAQ;IAAE,SAAS;IAAG,GAAG;IAAI;GAC7B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;IAAY;GAC1C;EAED,UAAU;GACR,QAAQ;IAAE,SAAS;IAAG,GAAG;IAAK;GAC9B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;IAAY;GAC1C;EAED,SAAS;GACP,QAAQ;IAAE,SAAS;IAAG,OAAO;IAAM;GACnC,SAAS;IAAE,SAAS;IAAG,OAAO;IAAG;IAAY;GAC9C;EAED,aAAa;GACX,QAAQ;IAAE,SAAS;IAAG,GAAG;IAAK;GAC9B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;IAAY;GAC1C;EAED,cAAc;GACZ,QAAQ;IAAE,SAAS;IAAG,GAAG;IAAI;GAC7B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;IAAY;GAC1C;EAED,UAAU;GACR,QAAQ;IAAE,SAAS;IAAG,GAAG;IAAG,OAAO;IAAM;GACzC,SAAS;IACP,SAAS;IACT,GAAG;IACH,OAAO;IACP,YAAY;IACb;GACD,MAAM;IAAE,SAAS;IAAG,GAAG;IAAG,OAAO;IAAM;IAAY;GACpD;EAED,kBAAkB;GAChB,QAAQ,EAAE;GACV,SAAS,EACP,YAAY;IACV,iBAAiB;IACjB,eAAe;IAChB,EACF;GACF;EAED,aAAa;GACX,QAAQ;IAAE,SAAS;IAAG,GAAG;IAAG;GAC5B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;IAAY;GAC1C;EAED,gBAAgB;GACd,QAAQ;IAAE,SAAS;IAAG,GAAG;IAAG;GAC5B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG,YAAY;IAAgB;GACzD,MAAM;IAAE,SAAS;IAAG,GAAG;IAAI;IAAY;GACxC;EACF;;;;;;;;;;;;ACjFH,MAAa,wBAAwB;;AAGrC,SAAgB,WAAW,OAAiC;AAC1D,QAAO,OAAO,UAAU,YAAY,kBAAkB,KAAK,MAAM;;AAQnE,MAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAS,kBAAkB,QAAgC;AACzD,MAAK,MAAM,SAAS,cAAc;EAChC,MAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,WAAW,MAAM,CACpB,OAAM,IAAI,MAAM,GAAG,MAAM,oDAAoD,KAAK,UAAU,MAAM,GAAG;;;;AAM3G,SAAgB,sBAAsB,QAAuC;AAC3E,mBAAkB,OAAO;AACzB,QAAO;EACL,SAAS,OAAO;EAChB,WAAW,OAAO;EAClB,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,MAAM,OAAO;EACd;;AAGH,SAAS,oBAAoB,QAAqB,SAAwC;AACxF,KAAI,SAAS,cAAc,QAAS,QAAO;AAC3C,KAAI,SAAS,cAAc,aAAc,QAAO;AAChD,QAAO,OAAO,cAAc,MAAM,IAAA;;AAGpC,SAAS,aAAa,SAA0B,QAA0B;AACxE,QAAO,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,WAAW,GAAG,OAAO,IAAI,IAAI,IAAI,MAAM,GAAG;;AAGtF,SAAS,YAAY,MAAwC;AAC3D,QAAO,OAAO,QAAQ,KAAK,CAAC,KAAK,CAAC,KAAK,WAAW,KAAK,IAAI,IAAI,MAAM,GAAG;;;;;;;;;;;;;;;;;;;AAoB1E,SAAgB,cAAc,QAAqB,SAAwC;CACzF,MAAM,QAAQ,sBAAsB,OAAO;CAC3C,MAAM,QAAQ,mBAAmB,MAAM;CACvC,MAAM,OAAO,kBAAkB,MAAM;CACrC,MAAM,SAAS,oBAAoB,MAAM;CACzC,MAAM,eAAe,oBAAoB,QAAQ,QAAQ;CAEzD,MAAM,YACJ,iBAAiB,WACb;EACE;EACA;EACA,GAAG,aAAa,MAAM,OAAO;EAC7B;EACA;EACD,GACD;EAAC,GAAG,aAAa;EAAK,GAAG,aAAa,MAAM,KAAK;EAAE;EAAI;AA2C7D,QAzCwB;EACtB;EACA,aAAa,OAAO,aAAa,KAAK,OAAO,eAAe,KAAK,OAAO,YAAY,KAAK,OAAO,eAAe,KAAK,OAAO,aAAa;EACxI,YAAY,OAAO,UAAU;EAC7B,sBAAsB,OAAO,OAAO,YAAY,OAAO,OAAO,YAAY,OAAO,OAAO,aAAa,OAAO,QAAQ;EACpH;EACA;EACA,GAAG,aAAa,OAAO,KAAK;EAC5B;EACA;EACA,iBAAiB,OAAO;EACxB;EACA;EACA,6BAA6B;EAC7B;EACA,uBAAuB,OAAO,OAAO;EACrC,GAAG,YAAY,cAAc,OAAO,QAAQ;EAC5C;EACA,uBAAuB,OAAO,OAAO;EACrC,GAAG,YAAY,cAAc,OAAO,QAAQ;EAC5C;EACA,uBAAuB,OAAO,OAAO;EACrC,GAAG,YAAY,cAAc,OAAO,QAAQ;EAC5C;EACA,wBAAwB,OAAO,QAAQ;EACvC,GAAG,YAAY,eAAe,OAAO,SAAS;EAC9C;EACA;EACA,mBAAmB,OAAO,SAAS;EACnC,sBAAsB,OAAO,eAAe,OAAO,SAAS;EAC5D,2BAA2B,OAAO,YAAY,OAAO;EACrD,2BAA2B,OAAO,YAAY,OAAO;EACrD,6BAA6B,OAAO,YAAY,SAAS;EACzD,yBAAyB,OAAO,YAAY,KAAK;EACjD;EACA;EACA;EACA,GAAG;EACH;EACD,CAEY,KAAK,KAAK;;;;;;;;;;;ACxIzB,SAAS,SAAS,GAAW,GAAW,GAAgB;CACtD,MAAM,UAAU,IAAI,KAAK,IAAI,IAAI,IAAI,EAAE,IAAI;CAC3C,MAAM,OAAS,IAAI,MAAO,OAAO,MAAO;CACxC,MAAM,IAAI,UAAU,IAAI,KAAK,IAAK,MAAM,IAAK,EAAE;CAC/C,MAAM,IAAI,IAAI,SAAS;CACvB,IAAI;AACJ,KAAI,MAAM,EAAG,OAAM;EAAC;EAAQ;EAAG;EAAE;UACxB,MAAM,EAAG,OAAM;EAAC;EAAG;EAAQ;EAAE;UAC7B,MAAM,EAAG,OAAM;EAAC;EAAG;EAAQ;EAAE;UAC7B,MAAM,EAAG,OAAM;EAAC;EAAG;EAAG;EAAO;UAC7B,MAAM,EAAG,OAAM;EAAC;EAAG;EAAG;EAAO;KACjC,OAAM;EAAC;EAAQ;EAAG;EAAE;AACzB,QAAO;EAAC,IAAI,KAAK;EAAG,IAAI,KAAK;EAAG,IAAI,KAAK;EAAE;;AAG7C,SAAS,WAAW,OAAwB;CAC1C,MAAM,MAAM,oBAAoB,KAAK,MAAM,MAAM,CAAC;AAClD,KAAI,KAAK;EACP,MAAM,QAAQ,SAAS,IAAI,IAAI,GAAG;AAClC,SAAO;GAAE,SAAS,KAAM;GAAM,SAAS,IAAK;GAAK,QAAQ;GAAI,CAAC,KAAK,YAAY,UAAU,IAAI;;CAE/F,MAAM,UAAU,uCAAuC,KAAK,MAAM,MAAM,CAAC;AACzE,KAAI,QACF,QAAO,SAAS,OAAO,QAAQ,GAAG,EAAE,OAAO,QAAQ,GAAG,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG,IAAI;AAEzF,OAAM,IAAI,MAAM,oCAAoC,MAAM,+BAA+B;;AAK3F,SAAS,cAAc,SAAyB;AAC9C,QAAO,WAAW,SAAU,UAAU,QAAQ,KAAK,KAAK,UAAU,QAAS,OAAO,IAAI;;;AAIxF,SAAgB,kBAAkB,OAA2B;CAC3D,MAAM,CAAC,GAAG,GAAG,KAAK,WAAW,MAAM;AACnC,QAAO,QAAS,cAAc,EAAE,GAAG,QAAS,cAAc,EAAE,GAAG,QAAS,cAAc,EAAE;;;AAI1F,SAAgB,cAAc,GAAe,GAAuB;CAClE,MAAM,KAAK,kBAAkB,EAAE;CAC/B,MAAM,KAAK,kBAAkB,EAAE;CAC/B,MAAM,SAAS,KAAK,IAAI,IAAI,GAAG,GAAG,QAAS,KAAK,IAAI,IAAI,GAAG,GAAG;AAC9D,QAAO,KAAK,MAAM,QAAQ,IAAI,GAAG;;;AAInC,SAAgB,aAAa,OAA+B;CAC1D,MAAM,UAAU,SAAS;CACzB,MAAM,KAAK,SAAS;CACpB,MAAM,MAAM,SAAS;AACrB,QAAO;EAAE;EAAI;EAAS;EAAK,OAAO,MAAM,QAAQ,KAAK,OAAO,UAAU,aAAa;EAAQ;;;AAe7F,MAAM,WAA+B;CACnC;EAAE,YAAY;EAAsB,YAAY;EAAW,MAAM;EAAkB,OAAO;EAA4C;CACtI;EAAE,YAAY;EAAwB,YAAY;EAAa,MAAM;EAAkB,OAAO;EAAgD;CAC9I;EAAE,YAAY;EAA2B,YAAY;EAAgB,MAAM;EAAkB,OAAO;EAA+B;CACnI;EAAE,YAAY;EAAwB,YAAY;EAAa,MAAM;EAAkB,OAAO;EAA4B;CAC1H;EAAE,YAAY;EAAsB,YAAY;EAAW,MAAM;EAAkB,OAAO;EAAmC;CAC7H;EAAE,YAAY;EAA0B,YAAY;EAAe,MAAM;EAAkB,OAAO;EAAwC;CAC1I;EAAE,YAAY;EAAqB,YAAY;EAAU,MAAM;EAAkB,OAAO;EAAwD;CAChJ;EAAE,YAAY;EAAoB,YAAY;EAAS,MAAM;EAAkB,OAAO;EAAgC;CACtH;EAAE,YAAY;EAAmB,YAAY;EAAQ,MAAM;EAAkB,OAAO;EAAsC;CAC1H;EAAE,YAAY;EAAsB,YAAY;EAAW,MAAM;EAAkB,OAAO;EAAgD;CAC1I;EAAE,YAAY;EAAc,YAAY;EAAc,MAAM;EAAQ,OAAO;EAAoC;CAC/G;EAAE,YAAY;EAAc,YAAY;EAAQ,MAAM;EAAQ,OAAO;EAAuB;CAC5F;EAAE,YAAY;EAAoB,YAAY;EAAc,MAAM;EAAQ,OAAO;EAA0C;CAC3H;EAAE,YAAY;EAAoB,YAAY;EAAQ,MAAM;EAAQ,OAAO;EAA6B;CACxG;EAAE,YAAY;EAAqB,YAAY;EAAc,MAAM;EAAQ,OAAO;EAA8D;CAChJ;EAAE,YAAY;EAAqB,YAAY;EAAQ,MAAM;EAAQ,OAAO;EAAiD;CAC7H;EAAE,YAAY;EAAW,YAAY;EAAc,MAAM;EAAQ,OAAO;EAA+C;CACvH;EAAE,YAAY;EAAa,YAAY;EAAc,MAAM;EAAQ,OAAO;EAAoD;CAC9H;EAAE,YAAY;EAAQ,YAAY;EAAc,MAAM;EAAY,OAAO;EAAqC;CAC/G;AAED,SAAS,WAAW,MAAoB,QAAwB;AAC9D,SAAQ,MAAR;EACE,KAAK,WACH,QAAO;EACT,KAAK,aACH,QAAO,UAAU,IAAI,MAAM;EAC7B,QACE,QAAO;;;AAIb,SAAS,OAAO,SAA0B,SAAkC;CAC1E,MAAM,aAAa,QAAQ,QAAQ;CACnC,MAAM,aAAa,QAAQ,QAAQ;CACnC,MAAM,QAAQ,cAAc,YAAY,WAAW;AACnD,QAAO;EAAE;EAAO,OAAO,aAAa,MAAM,CAAC;EAAO;EAAY;EAAY;;;;;;;;;;;;AAa5E,SAAgB,WAAW,QAA4B,UAA6B,EAAE,EAAmB;CACvG,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,QAAQ,sBAAsB,OAAO;CAC3C,MAAM,QAAQ,mBAAmB,MAAM;CACvC,MAAM,OAAO,kBAAkB,MAAM;AAErC,QAAO,SAAS,KAAK,YAAY;EAC/B,MAAM,cAAc,OAAO,OAAO,QAAQ;EAC1C,MAAM,aAAa,OAAO,MAAM,QAAQ;EACxC,MAAM,YAAY,WAAW,QAAQ,MAAM,OAAO;AAClD,SAAO;GACL,IAAI,GAAG,QAAQ,WAAW,MAAM,QAAQ;GACxC,OAAO,QAAQ;GACf,MAAM,QAAQ;GACd,OAAO;GACP,MAAM;GACN,QAAQ;GACR,aAAa,YAAY,SAAS,aAAa,WAAW,SAAS;GACpE;GACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bison-lab/tokens",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Design tokens, CSS custom properties, and preset files for Bison Lab component library",
5
5
  "homepage": "https://components.bisonlab.ai",
6
6
  "repository": {
@@ -30,8 +30,10 @@
30
30
  "css"
31
31
  ],
32
32
  "devDependencies": {
33
+ "@types/node": "^25.5.0",
33
34
  "tsdown": "^0.21.0",
34
- "typescript": "^5.9.3"
35
+ "typescript": "^5.9.3",
36
+ "vitest": "^4.1.2"
35
37
  },
36
38
  "publishConfig": {
37
39
  "access": "public"
@@ -40,6 +42,8 @@
40
42
  "build": "tsdown",
41
43
  "dev": "tsdown --watch",
42
44
  "clean": "rm -rf dist",
43
- "typecheck": "tsc --noEmit"
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest"
44
48
  }
45
49
  }