@tenphi/glaze 0.0.0-snapshot.c8281e2 → 0.0.0-snapshot.cdd8acc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,8 +70,8 @@ const danger = primary.extend({ hue: 23 });
70
70
  const success = primary.extend({ hue: 157 });
71
71
 
72
72
  // Compose into a palette and export
73
- const palette = glaze.palette({ primary, danger, success });
74
- const tokens = palette.tokens({ primary: 'primary' });
73
+ const palette = glaze.palette({ primary, danger, success }, { primary: 'primary' });
74
+ const tokens = palette.tokens();
75
75
  // → { light: { 'primary-surface': 'okhsl(...)', 'surface': 'okhsl(...)', ... }, dark: { ... } }
76
76
  ```
77
77
 
@@ -265,13 +265,70 @@ Create a single color token without a full theme:
265
265
  ```ts
266
266
  const accent = glaze.color({ hue: 280, saturation: 80, lightness: 52, mode: 'fixed' });
267
267
 
268
- accent.resolve(); // → ResolvedColor with light/dark/lightContrast/darkContrast
269
- accent.token(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (tasty format)
270
- accent.tasty(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (same as token)
271
- accent.json(); // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
268
+ accent.resolve(); // → ResolvedColor with light/dark/lightContrast/darkContrast
269
+ accent.token(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (tasty format)
270
+ accent.tasty(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (same as token)
271
+ accent.json(); // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
272
+ accent.css({ name: 'accent' });
273
+ // → { light: '--accent-color: rgb(...);', dark: '--accent-color: rgb(...);', ... }
274
+ ```
275
+
276
+ ### Value Shorthand
277
+
278
+ The first argument can also be a color value — Glaze extracts the seed
279
+ hue/saturation/lightness for you. All forms support the same exports
280
+ (`resolve / token / tasty / json / css`):
281
+
282
+ ```ts
283
+ // Hex (3 or 6 digits)
284
+ glaze.color('#26fcb2').tasty();
285
+
286
+ // CSS color functions Glaze itself emits (`rgb()`, `hsl()`, `okhsl()`, `oklch()`)
287
+ // — anything from theme.tasty()/json()/css() round-trips back in.
288
+ glaze.color('rgb(38 252 178)').tasty();
289
+ glaze.color('hsl(152 97% 57%)').tasty();
290
+ glaze.color('okhsl(152 95% 74%)').tasty();
291
+ glaze.color('oklch(0.85 0.18 152)').tasty();
292
+
293
+ // OKHSL object — Glaze's native shape (h: 0–360, s/l: 0–1)
294
+ glaze.color({ h: 152, s: 0.95, l: 0.74 }).tasty();
295
+
296
+ // RGB tuple, 0–255 (same range as glaze.fromRgb)
297
+ glaze.color([38, 252, 178]).tasty();
272
298
  ```
273
299
 
274
- Standalone colors are always root colors (no `base`/`contrast`).
300
+ Optional second argument supplies overrides including `base` (any color
301
+ value), the WCAG `contrast` solver, and relative `hue` / `lightness`:
302
+
303
+ ```ts
304
+ // Brand color seeded from a hex, with seed/lightness/mode overrides
305
+ glaze.color('#26fcb2', { saturation: 80, mode: 'fixed' }).tasty();
306
+
307
+ // Brand text guaranteed AAA against a brand-tinted dark surface
308
+ glaze.color('#26fcb2', {
309
+ base: '#1a1a2e', // any GlazeColorValue
310
+ lightness: '+48', // relative offset from base lightness
311
+ contrast: 'AAA',
312
+ mode: 'fixed',
313
+ }).tasty();
314
+ ```
315
+
316
+ All overrides:
317
+
318
+ | Option | Notes |
319
+ |---|---|
320
+ | `hue` | Number (absolute 0–360) or `'+N'`/`'-N'` (relative to seed) |
321
+ | `saturation` | Override seed saturation (0–100) |
322
+ | `lightness` | Number (absolute 0–100) or `'+N'`/`'-N'` (relative to base — requires `base`). Supports `[normal, hc]` pairs |
323
+ | `saturationFactor` | Multiplier on seed (0–1, default 1) |
324
+ | `mode` | `'auto'` (default) / `'fixed'` / `'static'` — see [Adaptation Modes](#adaptation-modes) |
325
+ | `base` | Any `GlazeColorValue` — required for relative `lightness` and `contrast` |
326
+ | `contrast` | WCAG floor against `base`. Same shape as `RegularColorDef.contrast` |
327
+
328
+ Alpha components in `rgb(... / A)` / `hsl(... / A)` / `rgba(...)` / `hsla(...)` are
329
+ parsed but the alpha channel is dropped with a `console.warn` (standalone
330
+ colors have no opacity field — use `opacity` on a theme color if you need
331
+ alpha). Named CSS colors (`'red'`, `'blueviolet'`) are not supported.
275
332
 
276
333
  ## From Existing Colors
277
334
 
@@ -567,7 +624,7 @@ theme.tokens({ format: 'hsl' }); // → 'hsl(270.5 45.2% 95.8%)'
567
624
  theme.tokens({ format: 'oklch' }); // → 'oklch(0.965 0.0123 280)'
568
625
  ```
569
626
 
570
- The `format` option works on all export methods: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `palette.tokens()`, `palette.tasty()`, `palette.json()`, `palette.css()`, and standalone `glaze.color().token()` / `.tasty()` / `.json()`.
627
+ The `format` option works on all export methods: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `palette.tokens()`, `palette.tasty()`, `palette.json()`, `palette.css()`, and standalone `glaze.color().token()` / `.tasty()` / `.json()` / `.css()`.
571
628
 
572
629
  Colors with `alpha < 1` (shadow colors, or regular colors with `opacity`) include an alpha component:
573
630
 
@@ -603,7 +660,7 @@ Modes control how colors adapt across schemes:
603
660
 
604
661
  ```ts
605
662
  // Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
606
- // Dark: surface inverts to L≈14, sign flips → L=14+52=66
663
+ // Dark: surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
607
664
  // contrast solver may push further (light text on dark bg)
608
665
  ```
609
666
 
@@ -639,26 +696,29 @@ Both `auto` and `fixed` modes use the same linear formula. `static` mode and hig
639
696
 
640
697
  ### Lightness
641
698
 
642
- **`auto`** — inverted within the configured window:
699
+ **`auto`** — inverted with a Möbius transformation within the configured window:
643
700
 
644
701
  ```ts
645
702
  const [lo, hi] = darkLightness; // default: [15, 95]
646
- const invertedL = ((100 - lightness) * (hi - lo)) / 100 + lo;
703
+ const t = (100 - lightness) / 100;
704
+ const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5
647
705
  ```
648
706
 
649
- **`fixed`** mapped without inversion:
707
+ The `darkCurve` parameter (default `0.5`, range 0–1) controls how much the dark-mode inversion expands lightness deltas. Lower values produce stronger expansion; `1` gives linear (legacy) behavior. Accepts a `[normal, highContrast]` pair for separate HC tuning (e.g. `darkCurve: [0.5, 0.3]`); a single number applies to both. Unlike a power curve, the Möbius transformation provides **proportional expansion** — small and large deltas are scaled by similar ratios, preserving the visual hierarchy of the light theme.
708
+
709
+ **`fixed`** — mapped without inversion (not affected by `darkCurve`):
650
710
 
651
711
  ```ts
652
712
  const mappedL = (lightness * (hi - lo)) / 100 + lo;
653
713
  ```
654
714
 
655
- | Color | Light L | Auto (inverted) | Fixed (mapped) |
656
- |---|---|---|---|
657
- | surface (L=97) | 97 | 17.4 | 92.6 |
658
- | accent-fill (L=52) | 52 | 53.4 | 56.6 |
659
- | accent-text (L=100) | 100 | 15 | 95 |
715
+ | Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
716
+ |---|---|---|---|---|
717
+ | surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
718
+ | accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
719
+ | accent-text (L=100) | 100 | 15 | 15 | 95 |
660
720
 
661
- In high-contrast variants, the `darkLightness` window is bypassed. Auto uses pure inversion (`100 - L`), fixed uses identity (`L`). This allows HC colors to reach the full 0–100 range.
721
+ In high-contrast variants, the `darkLightness` window is bypassed auto uses the Möbius curve over the full [0, 100] range, and fixed uses identity (`L`). To use a different curve shape for HC, pass a `[normal, hc]` pair to `darkCurve` (e.g. `darkCurve: [0.5, 0.3]`).
662
722
 
663
723
  ### Saturation
664
724
 
@@ -698,6 +758,15 @@ Combine multiple themes into a single palette:
698
758
  const palette = glaze.palette({ primary, danger, success, warning });
699
759
  ```
700
760
 
761
+ Optionally designate a primary theme at creation time:
762
+
763
+ ```ts
764
+ const palette = glaze.palette(
765
+ { primary, danger, success, warning },
766
+ { primary: 'primary' },
767
+ );
768
+ ```
769
+
701
770
  ### Prefix Behavior
702
771
 
703
772
  Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
@@ -716,15 +785,28 @@ Custom prefix mapping:
716
785
  palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
717
786
  ```
718
787
 
719
- To disable prefixing entirely, pass `prefix: false` explicitly. Note that tokens with the same name will overwrite each other (last theme wins).
788
+ To disable prefixing entirely, pass `prefix: false` explicitly.
789
+
790
+ ### Collision Detection
791
+
792
+ When two themes produce the same output key (via `prefix: false`, custom prefix maps, or primary unprefixed aliases), the first-written value wins and a `console.warn` is emitted:
793
+
794
+ ```ts
795
+ const palette = glaze.palette({ a, b });
796
+ palette.tokens({ prefix: false });
797
+ // ⚠ glaze: token "surface" from theme "b" collides with theme "a" — skipping.
798
+ ```
720
799
 
721
800
  ### Primary Theme
722
801
 
723
- Use the `primary` option to designate one theme as the primary. Its tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions:
802
+ The primary theme's tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions. Set at palette creation to apply to all exports automatically:
724
803
 
725
804
  ```ts
726
- const palette = glaze.palette({ primary, danger, success });
727
- const tokens = palette.tokens({ primary: 'primary' });
805
+ const palette = glaze.palette(
806
+ { primary, danger, success },
807
+ { primary: 'primary' },
808
+ );
809
+ const tokens = palette.tokens();
728
810
  // → {
729
811
  // light: {
730
812
  // 'primary-surface': 'okhsl(...)', // prefixed (all themes)
@@ -735,21 +817,32 @@ const tokens = palette.tokens({ primary: 'primary' });
735
817
  // }
736
818
  ```
737
819
 
820
+ Override or disable per-export:
821
+
822
+ ```ts
823
+ palette.tokens({ primary: 'danger' }); // use danger as primary for this call
824
+ palette.tokens({ primary: false }); // no primary for this call
825
+ ```
826
+
738
827
  The `primary` option works on `tokens()`, `tasty()`, and `css()`. It combines with any prefix mode — when using a custom prefix map, primary tokens are still duplicated without prefix:
739
828
 
740
829
  ```ts
741
- palette.tokens({ prefix: { primary: 'p-', danger: 'd-' }, primary: 'primary' });
742
- // → 'p-surface' + 'surface' (alias) + 'd-surface'
830
+ palette.tokens({ prefix: { primary: 'p-', danger: 'd-' } });
831
+ // → 'p-surface' + 'surface' (alias from palette-level primary) + 'd-surface'
743
832
  ```
744
833
 
745
834
  An error is thrown if the primary name doesn't match any theme in the palette.
746
835
 
747
- ### Tasty Export (for [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style system)
836
+ ### Tasty Export (for [Tasty](https://tasty.style) style system)
748
837
 
749
- The `tasty()` method exports tokens in the [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state binding format — `#name` color token keys with state aliases (`''`, `@dark`, etc.):
838
+ The `tasty()` method exports tokens in the [Tasty](https://tasty.style/docs) style-to-state binding format — `#name` color token keys with state aliases (`''`, `@dark`, etc.). See the [Playground](https://tasty.style/playground) for live examples of Glaze integration:
750
839
 
751
840
  ```ts
752
- const tastyTokens = palette.tasty({ primary: 'primary' });
841
+ const palette = glaze.palette(
842
+ { primary, danger, success },
843
+ { primary: 'primary' },
844
+ );
845
+ const tastyTokens = palette.tasty();
753
846
  // → {
754
847
  // '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
755
848
  // '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
@@ -845,7 +938,11 @@ const css = theme.css();
845
938
  Use in a stylesheet:
846
939
 
847
940
  ```ts
848
- const css = palette.css({ primary: 'primary' });
941
+ const palette = glaze.palette(
942
+ { primary, danger, success },
943
+ { primary: 'primary' },
944
+ );
945
+ const css = palette.css();
849
946
 
850
947
  const stylesheet = `
851
948
  :root { ${css.light} }
@@ -862,7 +959,7 @@ Options:
862
959
  | `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
863
960
  | `suffix` | `'-color'` | Suffix appended to each CSS property name |
864
961
  | `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
865
- | `primary` | | (palette only) Theme name to duplicate without prefix |
962
+ | `primary` | inherited | (palette only) Override or disable (`false`) the palette-level primary for this call |
866
963
 
867
964
  ```ts
868
965
  // Custom suffix
@@ -873,8 +970,8 @@ theme.css({ suffix: '' });
873
970
  theme.css({ format: 'hsl' });
874
971
  // → "--surface-color: hsl(...);"
875
972
 
876
- // Palette with primary
877
- palette.css({ primary: 'primary' });
973
+ // Palette with primary (inherited from palette creation)
974
+ palette.css();
878
975
  // → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
879
976
  ```
880
977
 
@@ -911,6 +1008,7 @@ glaze.configure({
911
1008
  lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
912
1009
  darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
913
1010
  darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
1011
+ darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1); or [normal, hc] pair
914
1012
  states: {
915
1013
  dark: '@dark', // State alias for dark mode tokens
916
1014
  highContrast: '@high-contrast',
@@ -1045,17 +1143,20 @@ const success = primary.extend({ hue: 157 });
1045
1143
  const warning = primary.extend({ hue: 84 });
1046
1144
  const note = primary.extend({ hue: 302 });
1047
1145
 
1048
- const palette = glaze.palette({ primary, danger, success, warning, note });
1146
+ const palette = glaze.palette(
1147
+ { primary, danger, success, warning, note },
1148
+ { primary: 'primary' },
1149
+ );
1049
1150
 
1050
1151
  // Export as flat token map grouped by variant (prefix defaults to true)
1051
- const tokens = palette.tokens({ primary: 'primary' });
1152
+ const tokens = palette.tokens();
1052
1153
  // tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
1053
1154
 
1054
1155
  // Export as tasty style-to-state bindings (for Tasty style system)
1055
- const tastyTokens = palette.tasty({ primary: 'primary' });
1156
+ const tastyTokens = palette.tasty();
1056
1157
 
1057
1158
  // Export as CSS custom properties (rgb format by default)
1058
- const css = palette.css({ primary: 'primary' });
1159
+ const css = palette.css();
1059
1160
  // css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
1060
1161
 
1061
1162
  // Standalone shadow computation
@@ -1083,7 +1184,8 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
1083
1184
  | `glaze.from(data)` | Create a theme from an exported configuration |
1084
1185
  | `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`) |
1085
1186
  | `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255) |
1086
- | `glaze.color(input)` | Create a standalone color token |
1187
+ | `glaze.color(input)` | Create a standalone color token from `{ hue, saturation, lightness, ... }` |
1188
+ | `glaze.color(value, overrides?)` | Create a standalone color token from a hex string, an `rgb()` / `hsl()` / `okhsl()` / `oklch()` string, an `{ h, s, l }` OKHSL object, or an `[r, g, b]` (0–255) tuple. Overrides accept absolute or relative `hue` / `lightness`, `saturation`, `mode`, `base` (any value form), and `contrast` |
1087
1189
  | `glaze.shadow(input)` | Compute a standalone shadow color (returns `ResolvedColorVariant`) |
1088
1190
  | `glaze.format(variant, format?)` | Format any `ResolvedColorVariant` as a CSS string |
1089
1191
 
@@ -1102,7 +1204,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
1102
1204
  | `theme.extend(options)` | Create a child theme |
1103
1205
  | `theme.resolve()` | Resolve all colors |
1104
1206
  | `theme.tokens(options?)` | Export as flat token map grouped by variant |
1105
- | `theme.tasty(options?)` | Export as [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state bindings |
1207
+ | `theme.tasty(options?)` | Export as [Tasty](https://tasty.style/docs) style-to-state bindings |
1106
1208
  | `theme.json(options?)` | Export as plain JSON |
1107
1209
  | `theme.css(options?)` | Export as CSS custom property declarations |
1108
1210
 
@@ -1111,7 +1213,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
1111
1213
  | Method | Description |
1112
1214
  |---|---|
1113
1215
  | `glaze.configure(config)` | Set global configuration |
1114
- | `glaze.palette(themes)` | Compose themes into a palette |
1216
+ | `glaze.palette(themes, options?)` | Compose themes into a palette (options: `{ primary? }`) |
1115
1217
  | `glaze.getConfig()` | Get current global config |
1116
1218
  | `glaze.resetConfig()` | Reset to defaults |
1117
1219