@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 +155 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +454 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -3
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
|
-
|
|
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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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":";;AASA;;;;;UAAiB,GAAA;EACf,CAAA;EACA,CAAA;EACA,CAAA;AAAA;AAAA,iBAGc,QAAA,CAAS,GAAA,WAAc,GAAA;AAAA,iBAmCvB,WAAA,CAAY,GAAA,EAAK,GAAA;AAAA,KAUrB,SAAA;AAAA,KAEP,SAAA;AAAA,KAEA,WAAA,GAAc,MAAA,CAAO,SAAA;AAAA,cAEb,WAAA,EAAa,MAAA,CAAO,SAAA,EAAW,WAAA;AAAA,UAsE3B,WAAA;EACf,OAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA;EACA,OAAA;EACA,IAAA,GAAO,SAAA;AAAA;AAAA,cAKI,WAAA;AAAA,KACD,SAAA,WAAoB,WAAA;AAAA,KACpB,UAAA,GAAa,MAAA,CAAO,SAAA;AAAA,UAEf,WAAA;EACf,OAAA,EAAS,UAAA;EACT,SAAA,EAAW,UAAA;EACX,cAAA,EAAgB,UAAA;EAChB,SAAA,EAAW,UAAA;EACX,OAAA,EAAS,UAAA;AAAA;AA1FX;;;;;AAAA,iBAoHgB,kBAAA,CAAmB,GAAA,WAAc,UAAA;;;;iBA2BjC,mBAAA,CAAoB,KAAA,EAAO,WAAA,GAAc,WAAA;;;;AAzEzD;iBA0FgB,gBAAA,CAAiB,MAAA,EAAQ,WAAA;;;;;;cAiB5B,qBAAA;AAAA,iBAIG,wBAAA,CAAA,GAA4B,UAAA;;iBAQ5B,2BAAA,CAAA;AAAA,UAOC,eAAA;EACf,UAAA;EACA,UAAA;EACA,IAAA;EACA,iBAAA;EACA,kBAAA;EACA,OAAA;EACA,oBAAA;EACA,OAAA;EACA,oBAAA;EACA,SAAA;EACA,sBAAA;EACA,MAAA;EACA,mBAAA;EACA,cAAA;EACA,yBAAA;EACA,SAAA;EACA,sBAAA;EACA,OAAA;EACA,oBAAA;EACA,KAAA;EACA,kBAAA;EACA,mBAAA;EACA,WAAA;EACA,wBAAA;EACA,MAAA;EACA,eAAA;EACA,eAAA;EACA,KAAA;EACA,IAAA;AAAA;
|
|
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
|
-
|
|
326
|
-
|
|
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["
|
|
361
|
-
"content-secondary": grey["
|
|
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
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
const
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bison-lab/tokens",
|
|
3
|
-
"version": "3.
|
|
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
|
}
|