@tenphi/glaze 1.0.0 → 1.1.1

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/docs/api.md CHANGED
@@ -32,21 +32,37 @@ Full reference for every public method, option, and type exported by `@tenphi/gl
32
32
  | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
33
33
  | `glaze(hue, saturation?, config?)` | Create a theme from hue (0–360) and saturation (0–100). Optional `config` overrides the global config for this theme. |
34
34
  | `glaze({ hue, saturation }, config?)` | Create a theme from an options object, with optional per-theme config override. |
35
- | `glaze.from(data)` | Create a theme from an exported configuration (`theme.export()` snapshot). |
35
+ | `glaze.themeFrom(data)` | Create a theme from a `theme.export()` snapshot (`kind: 'theme'`). |
36
+ | `glaze.from(data)` | Compat alias for `glaze.themeFrom`. |
36
37
  | `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`). Extracts hue and saturation. |
37
38
  | `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255). Extracts hue and saturation. |
39
+ | `glaze.paletteFrom(data)` | Create a palette from a `palette.export()` snapshot (`kind: 'palette'`). |
40
+ | `glaze.colorFrom(data)` | Create a color token from a `token.export()` snapshot (`kind: 'color'`). |
41
+ | `glaze.isThemeExport(data)` | Type guard for theme authoring snapshots. |
42
+ | `glaze.isColorTokenExport(data)` | Type guard for color-token authoring snapshots. |
43
+ | `glaze.isPaletteExport(data)` | Type guard for palette authoring snapshots. |
38
44
 
39
45
  ```ts
40
46
  const a = glaze(280, 80);
41
47
  const b = glaze({ hue: 280, saturation: 80 });
42
48
  const c = glaze.fromHex('#7a4dbf');
43
49
  const d = glaze.fromRgb(122, 77, 191);
44
- const e = glaze.from(a.export());
50
+ const e = glaze.themeFrom(a.export());
45
51
 
46
52
  // Per-theme config override:
47
53
  const rawTheme = glaze(280, 80, { lightTone: false, darkTone: false });
48
54
  ```
49
55
 
56
+ Authoring restore triad (parallel to `.export()` on each instance):
57
+
58
+ | Export | Restore |
59
+ | ------ | ------- |
60
+ | `theme.export()` | `glaze.themeFrom()` |
61
+ | `token.export()` | `glaze.colorFrom()` |
62
+ | `palette.export()` | `glaze.paletteFrom()` |
63
+
64
+ Every snapshot includes `kind` + `version` (`GLAZE_EXPORT_VERSION`, currently `1`). Legacy snapshots without those fields still restore. Wrong `kind`, or a `version` outside `1..=GLAZE_EXPORT_VERSION`, throws.
65
+
50
66
  The optional `config` parameter is a `GlazeConfigOverride` — see [Per-instance config override](#per-instance-config-override).
51
67
 
52
68
  ---
@@ -66,7 +82,7 @@ A `GlazeTheme` exposes:
66
82
  | `theme.has(name)` | Check if a color is defined. |
67
83
  | `theme.list()` | List all defined color names. |
68
84
  | `theme.reset()` | Clear all color definitions. |
69
- | `theme.export()` | Export the theme configuration as a JSON-safe object. |
85
+ | `theme.export(override?)` | Export the theme configuration as a JSON-safe object (optional config override at export time). |
70
86
  | `theme.extend(options)` | Create a child theme inheriting all color definitions (see [`extend`](#themeextendoptions) below). |
71
87
  | `theme.resolve()` | Resolve all colors and return a `Map<string, ResolvedColor>`. |
72
88
  | `theme.tokens(options?)` | Export as a flat token map grouped by scheme variant. |
@@ -334,16 +350,27 @@ A Tailwind CSS v4 `@theme` block (light baseline) plus dark / high-contrast over
334
350
  | `highContrastSelector` | `'.high-contrast'` | Selector wrapping the light high-contrast overrides. The combined dark + high-contrast block uses `${darkSelector}${highContrastSelector}` (e.g. `.dark.high-contrast`). |
335
351
  | `modes` | global config | Which scheme variants to include. The `@theme` block (light) is always emitted when colors exist. |
336
352
 
337
- ### `theme.export()`
353
+ ### `theme.export(override?)`
338
354
 
339
355
  ```ts
340
356
  const snapshot = theme.export();
341
- // → { hue: 280, saturation: 80, colors: { surface: { ... }, ... } }
357
+ // → {
358
+ // kind: 'theme',
359
+ // version: 1,
360
+ // hue: 280,
361
+ // saturation: 80,
362
+ // colors: { surface: { ... }, ... },
363
+ // config: { lightTone: {...}, darkTone: {...}, pastel: false, ... },
364
+ // }
342
365
 
343
- const restored = glaze.from(snapshot);
366
+ const restored = glaze.themeFrom(snapshot);
344
367
  ```
345
368
 
346
- The export contains only the configuration not resolved color values. Resolved values are recomputed on demand.
369
+ Returns a deep-cloned, JSON-safe authoring snapshot (definitions + frozen
370
+ effective config — not resolved color strings). Freezes
371
+ `getConfig() ∪ instance local ∪ override` at call time. Restored themes pin
372
+ that freeze as their local override. Distinct from `theme.json()`, which
373
+ emits resolved color strings.
347
374
 
348
375
  ---
349
376
 
@@ -395,7 +422,7 @@ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
395
422
  | `mode` | `'auto' \| 'fixed' \| 'static'` | Adaptation mode. Default: `'auto'`. See [Adaptation modes](#adaptation-modes). |
396
423
  | `autoFlip` | `boolean` | Flip out-of-bounds results (relative `tone` overshoot / unmet `contrast`) to the opposite side instead of clamping. Default: the global `autoFlip` (`true`). See [`autoFlip`](#autoflip). |
397
424
  | `opacity` | `number` | Fixed alpha 0–1. Output includes alpha in the CSS value. Combining with `contrast` is not recommended (a `console.warn` is emitted). |
398
- | `pastel` | `boolean` | Per-color override for the hue-independent "safe" chroma limit used in OKHSL↔sRGB conversions (luminance, contrast solving, output formatting). Falls through to the global / per-theme `pastel` config when omitted. Default: unset. See [Per-color `pastel`](#per-color-pastel). |
425
+ | `pastel` | `boolean` | Per-color override for the hue-independent "safe" chroma limit used in OKHSL↔sRGB conversions (luminance, contrast solving, output formatting). Falls through to the per-theme / per-token `pastel` override when omitted. Default: unset. See [Per-color `pastel`](#per-color-pastel). |
399
426
  | `role` | `RoleInput` | Semantic role against `base` (`'text'` / `'surface'` / `'border'` or an alias). Fixes APCA contrast polarity. Resolved via: explicit `role` → name inference → opposite of the base's role → `'text'`. See [Roles](#roles). |
400
427
  | `inherit` | `boolean` | Whether this color is inherited by child themes via `extend()`. Default: `true`. Set to `false` to make the color local to the current theme. |
401
428
 
@@ -420,7 +447,8 @@ It gives an exact contrast step for neutrals and a stable visual progression for
420
447
  chromatic colors. In dark mode with `mode: 'auto'`, it is anchored to the
421
448
  base's per-scheme tone. If `base + delta` falls outside `[0, 100]`, the result
422
449
  is clamped to the boundary, or — with `autoFlip` (default on) — mirrored to the
423
- other side of the base.
450
+ other side of the base. If the mirrored target is also out of range, the original
451
+ delta is kept and clamped on the authored side.
424
452
 
425
453
  **Extreme tone** (`'max'` / `'min'`) forces the color to the scheme's tone extreme without a contrast hack or a magic number. `'max'` resolves to author tone 100 and `'min'` to 0; both flow through scheme mapping like an absolute tone, so under `mode: 'auto'` they invert in dark (`'max'` is lightest in light, darkest in dark). Use `mode: 'static'` to pin the same extreme across schemes, or `mode: 'fixed'` to keep the same end without inverting. No `base` required.
426
454
 
@@ -430,7 +458,7 @@ A dependent color with `base` but no `tone` inherits the base's tone (equivalent
430
458
 
431
459
  `autoFlip` governs what happens when a result would fall outside its valid range:
432
460
 
433
- - **Relative `tone` overshoot:** when `base ± delta` exceeds `[0, 100]`, `autoFlip` mirrors the delta to the other side of the base (e.g. `'+30'` becomes `'-30'`) instead of clamping to the boundary.
461
+ - **Relative `tone` overshoot:** when `base ± delta` exceeds `[0, 100]`, `autoFlip` mirrors the delta to the other side of the base (e.g. `'+30'` becomes `'-30'`) instead of clamping to the boundary. If the mirrored target is also outside `[0, 100]`, the original delta is kept and clamped on the authored side.
434
462
  - **`contrast` direction:** when the requested tone direction can't meet the floor, `autoFlip` lets the solver try the opposite side (the same behavior as the global `autoFlip`).
435
463
 
436
464
  `autoFlip` defaults to the global `autoFlip` (`true`). Set `autoFlip: false` on a color to clamp instead of mirror — useful when you want a relative offset to stay on the authored side of the base, or to keep an unmet contrast pinned to one direction's extreme.
@@ -523,7 +551,7 @@ Relative hue is always relative to the **theme seed hue**, not to a base color.
523
551
 
524
552
  #### Per-color `pastel`
525
553
 
526
- `pastel: true` on a single color def overrides the global / per-theme `pastel` config for that color only. It toggles the hue-independent "safe" chroma limit used in every OKHSL↔sRGB conversion that touches this color: luminance calculations during contrast solving, gamut clamping during sRGB blend / mix edges, and output formatting. The effective flag is carried on the resolved variant (`ResolvedColorVariant.pastel`) so formatting matches the gamut mapping applied during resolution.
554
+ `pastel: true` on a single color def overrides the per-theme / per-token `pastel` override for that color only. It toggles the hue-independent "safe" chroma limit used in every OKHSL↔sRGB conversion that touches this color: luminance calculations during contrast solving, gamut clamping during sRGB blend / mix edges, and output formatting. The effective flag is carried on the resolved variant (`ResolvedColorVariant.pastel`) so formatting matches the gamut mapping applied during resolution.
527
555
 
528
556
  ```ts
529
557
  const theme = glaze(280, 80);
@@ -535,7 +563,7 @@ theme.colors({
535
563
  // theme.css().light contains different rgb() triples for `--plain` and `--soft`
536
564
  ```
537
565
 
538
- Omit the field to inherit the global / per-theme `pastel` config — useful for keeping the default behavior while opting a single accent into the pastel gamut.
566
+ Omit the field to inherit the theme/token `pastel` override (default `false`) — useful for keeping the default behavior while opting a single accent into the pastel gamut.
539
567
 
540
568
  The flag is part of the def object, so `extend()` copies it through to child themes alongside the rest of the def. Override it again on the child to flip a single color back:
541
569
 
@@ -665,7 +693,7 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
665
693
  | `opacity` | `number` | Fixed alpha 0–1. |
666
694
  | `base` | `GlazeColorToken \| GlazeColorValue` | Optional dependency. See [Pairing colors](#pairing-colors). |
667
695
  | `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base` (WCAG or APCA). Without `base`, anchored to the literal seed. |
668
- | `pastel` | `boolean` | Per-color `pastel` override. Falls through to the global / per-theme `pastel` config when omitted. See [Per-color `pastel`](#per-color-pastel). |
696
+ | `pastel` | `boolean` | Per-color `pastel` override. Falls through to the per-theme / per-token `pastel` override when omitted. See [Per-color `pastel`](#per-color-pastel). |
669
697
  | `role` | `RoleInput` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
670
698
  | `name` | `string` | Debug label for warnings; doesn't change output keys. Reserved names (`'value'`, `'seed'`, `'externalBase'`) are rejected. |
671
699
 
@@ -683,7 +711,7 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
683
711
  | `contrast` | Contrast floor (WCAG or APCA). Without `base`, anchored to the literal seed; with `base`, solved per scheme. |
684
712
  | `base` | `GlazeColorToken` or raw `GlazeColorValue`. See [Pairing colors](#pairing-colors). |
685
713
  | `opacity` | Fixed alpha 0–1. Combining with `contrast` is not recommended — `console.warn` is emitted. |
686
- | `pastel` | Per-color `pastel` override. Falls through to the global / per-theme `pastel` config when omitted. See [Per-color `pastel`](#per-color-pastel). |
714
+ | `pastel` | Per-color `pastel` override. Falls through to the per-theme / per-token `pastel` override when omitted. See [Per-color `pastel`](#per-color-pastel). |
687
715
  | `role` | Semantic role against `base` / the seed (see [Roles](#roles)). Fixes APCA polarity. |
688
716
  | `name` | Debug label only — surfaces in warnings/errors. Does not change output keys. |
689
717
 
@@ -691,14 +719,14 @@ Named CSS colors (`'red'`, `'blueviolet'`) are not supported.
691
719
 
692
720
  ### Defaults
693
721
 
694
- Every input form defaults to `mode: 'auto'` so the resolved token adapts between light and dark like an ordinary theme color. The config snapshot taken at create time differs by input form:
722
+ Every input form defaults to `mode: 'auto'` so the resolved token adapts between light and dark like an ordinary theme color. Tokens store a **sparse local** config override; omitted fields fall through to the live global at resolve time (same as themes). Authoring `.export(override?)` freezes `getConfig() ∪ local ∪ override` at call time.
695
723
 
696
724
  - **Value-shorthand** (bare strings, value objects, and `{ from, ...overrides }`):
697
- - Light variant preserves the input tone exactly (`lightTone: false`).
698
- - All other config fields (`darkTone`, `darkDesaturation`, `autoFlip`) snapshot from `globalConfig` at create time.
725
+ - Light variant preserves the input tone exactly (`lightTone: false` as a local default).
726
+ - Other omitted fields track the live global config.
699
727
  - **Structured input** (`{ hue, saturation, tone, ... }`):
700
- - Both tone windows snapshot from `globalConfig` at create time (same as a theme color).
701
- - All fields are **snapshotted at color-creation time** later `glaze.configure()` calls don't retroactively change existing tokens.
728
+ - Omitted tone windows and other fields track the live global config (same as a theme color).
729
+ - `pastel` is instance-only set via the config override or per-color `pastel`, not `glaze.configure()`.
702
730
 
703
731
  ```ts
704
732
  // Bare string — adapts automatically
@@ -731,25 +759,27 @@ A `GlazeColorToken` exposes:
731
759
  | `token.dtcg(options?)` | DTCG color tokens, one per scheme variant (no color-name key). Each entry is a full `{ $type: 'color', $value }` token. Options: `colorSpace` (`'srgb'` \| `'oklch'`), `modes`. |
732
760
  | `token.dtcgResolver({ name, ... })` | A single DTCG Resolver-Module document for this color, keyed by `name` across all scheme variants. `name` is **required**. Same options as `theme.dtcgResolver()` plus `name`. |
733
761
  | `token.tailwind({ name, ... })` | Tailwind v4 `@theme` block + dark / high-contrast overrides for this color. `name` is **required** (forms `--color-<name>`). Same options as `theme.tailwind()` plus `name`. |
734
- | `token.export()` | JSON-safe snapshot — pass to `glaze.colorFrom(...)` to rehydrate. |
762
+ | `token.export(override?)` | JSON-safe snapshot — freezes effective config at call time; pass to `glaze.colorFrom(...)` to rehydrate. Optional `override` merges over the instance local (and nested `base` exports). |
735
763
 
736
764
  ### Per-instance config override
737
765
 
738
766
  The optional `config` argument (`GlazeConfigOverride`) overrides
739
- resolve-relevant global fields for a token or theme. A tone window can be
740
- `[lo, hi]`, `{ lo, hi, eps }`, or `false` (full range). Standalone tokens
741
- snapshot omitted fields at creation; themes continue reading omitted fields
742
- from the live global config at resolve time.
767
+ resolve-relevant fields for a token or theme. A tone window can be
768
+ `[lo, hi]`, `{ lo, hi, eps }`, or `false` (full range). Both themes and
769
+ standalone tokens keep a sparse local override omitted fields (except
770
+ instance-only `pastel`) fall through to the live global at resolve time.
743
771
 
744
772
  `GlazeConfigOverride`:
745
773
 
746
- | Field | Default (from global) | Description |
747
- | ------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
748
- | `lightTone` | `[10, 100]` | Light tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
749
- | `darkTone` | `[15, 95]` | Dark tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
750
- | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
751
- | `autoFlip` | `true` | Default for each color's `autoFlip`: when solving `contrast` (or applying a relative `tone` that overshoots), allow crossing to the opposite side instead of clamping. |
752
- | `shadowTuning` | `undefined` | Default shadow tuning (meaningful for themes; harmless on color tokens). |
774
+ | Field | Default (from global / fixed) | Description |
775
+ | ------------------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
776
+ | `lightTone` | `[10, 100]` | Light tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
777
+ | `darkTone` | `[15, 95]` | Dark tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
778
+ | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
779
+ | `autoFlip` | `true` | Default for each color's `autoFlip`: when solving `contrast` (or applying a relative `tone` that overshoots), allow crossing to the opposite side instead of clamping. |
780
+ | `pastel` | `false` (instance-only) | Theme/token-level pastel default for colors that omit per-color `pastel`. Not available on `glaze.configure()`. |
781
+ | `inferRole` | `true` | Infer color `role` from the name when unset. |
782
+ | `shadowTuning` | `undefined` | Default shadow tuning (meaningful for themes; harmless on color tokens). |
753
783
 
754
784
  Config overrides apply to both `glaze.color()` tokens and `glaze()` themes:
755
785
 
@@ -764,16 +794,13 @@ glaze.color('#000000', {
764
794
  });
765
795
 
766
796
  // Structured form with config override
767
- glaze.color({ hue: 152, saturation: 95, tone: 74 }, { darkTone: false });
797
+ glaze.color({ hue: 152, saturation: 95, tone: 74 }, { darkTone: false, pastel: true });
768
798
 
769
799
  // Theme with config override
770
- const rawTheme = glaze(280, 80, { lightTone: false });
800
+ const rawTheme = glaze(280, 80, { lightTone: false, pastel: true });
771
801
  ```
772
802
 
773
- Standalone color tokens snapshot their effective override at creation time.
774
- Themes behave differently: overridden fields stay fixed, while fields omitted
775
- from the override are read from the live global config at resolve time. See
776
- [Theme config override](#theme-config-override).
803
+ See [Theme config override](#theme-config-override).
777
804
 
778
805
  ### Theme config override
779
806
 
@@ -797,11 +824,17 @@ const child = t.extend({ config: { darkTone: false } });
797
824
  // child: lightTone { lo: 0, hi: 50 } (inherited) + darkTone: false (added)
798
825
  ```
799
826
 
800
- `theme.export()` includes `config`; `glaze.from(data)` restores it.
827
+ `theme.export(override?)` freezes `getConfig() ∪ instance local ∪ override` at
828
+ call time. Restoring via `glaze.themeFrom(data)` (or the compat alias
829
+ `glaze.from`) pins that freeze as the restored theme's local override — matching
830
+ standalone color-token behavior.
801
831
 
802
832
  ### `glaze.colorFrom(data)`
803
833
 
804
- Inverse of `token.export()`. The exported snapshot includes the original input, all overrides (with any `base` token recursively serialized), and the full effective config — so later `glaze.configure()` calls don't change rehydrated tokens.
834
+ Inverse of `token.export()`. The exported snapshot includes the original input,
835
+ all overrides (with any `base` token recursively serialized), and the effective
836
+ config freeze from export time — so later `glaze.configure()` calls don't change
837
+ rehydrated tokens.
805
838
 
806
839
  ```ts
807
840
  const text = glaze.color({ from: '#1a1a1a', contrast: 'AA' });
@@ -1085,15 +1118,41 @@ const palette = glaze.palette(
1085
1118
 
1086
1119
  A `GlazePalette` exposes:
1087
1120
 
1088
- | Method | Description |
1089
- | -------------------------------- | ------------------------------------------------------------ |
1090
- | `palette.tokens(options?)` | Flat token map grouped by scheme variant. |
1091
- | `palette.tasty(options?)` | [Tasty](https://tasty.style) style-to-state bindings. |
1092
- | `palette.json(options?)` | Per-theme JSON map (no prefix needed — keyed by theme name). |
1093
- | `palette.css(options?)` | CSS custom property declaration strings. |
1094
- | `palette.dtcg(options?)` | Per-scheme W3C DTCG token trees. |
1095
- | `palette.dtcgResolver(options?)` | One DTCG Resolver-Module document for every scheme. |
1096
- | `palette.tailwind(options?)` | One Tailwind CSS v4 theme with scheme overrides. |
1121
+ | Method | Description |
1122
+ | -------------------------------- | ---------------------------------------------------------------------------------------------------- |
1123
+ | `palette.list()` | Theme names in insertion order. |
1124
+ | `palette.primary` | Primary theme name, if set. |
1125
+ | `palette.theme(name)` | Get a live theme instance by name. |
1126
+ | `palette.themes()` | Shallow copy of the theme map (same instances the palette holds). |
1127
+ | `palette.export(override?)` | Authoring snapshot restorable via `glaze.paletteFrom()`. Optional override forwarded to themes. |
1128
+ | `palette.tokens(options?)` | Flat token map grouped by scheme variant. |
1129
+ | `palette.tasty(options?)` | [Tasty](https://tasty.style) style-to-state bindings. |
1130
+ | `palette.json(options?)` | Per-theme **resolved** color JSON (not restorable as authoring config). |
1131
+ | `palette.css(options?)` | CSS custom property declaration strings. |
1132
+ | `palette.dtcg(options?)` | Per-scheme W3C DTCG token trees. |
1133
+ | `palette.dtcgResolver(options?)` | One DTCG Resolver-Module document for every scheme. |
1134
+ | `palette.tailwind(options?)` | One Tailwind CSS v4 theme with scheme overrides. |
1135
+
1136
+ ### `palette.export(override?)` / `glaze.paletteFrom()`
1137
+
1138
+ ```ts
1139
+ const snapshot = palette.export();
1140
+ // → {
1141
+ // kind: 'palette',
1142
+ // version: 1,
1143
+ // primary: 'brand',
1144
+ // themes: { brand: { kind: 'theme', ... }, danger: { ... } },
1145
+ // }
1146
+
1147
+ const restored = glaze.paletteFrom(JSON.parse(JSON.stringify(snapshot)));
1148
+ const brand = restored.theme('brand')!;
1149
+ ```
1150
+
1151
+ Optional `override` is forwarded to each nested `theme.export(override)`.
1152
+ Config snapshot vs resolved output: use `export()` / `paletteFrom()` to
1153
+ persist and restore the authoring graph (themes, color defs, relations). Use
1154
+ `json()` / `tokens()` / `css()` / … to emit resolved color strings for apps
1155
+ and design tools.
1097
1156
 
1098
1157
  ### `GlazePaletteExportOptions`
1099
1158
 
@@ -1297,7 +1356,7 @@ On `theme.css()`, `theme.tasty()`, `palette.css()`, `palette.tasty()`, and stand
1297
1356
  --accent-color: oklch(0.62 0.03 var(--accent-hue));
1298
1357
  ```
1299
1358
 
1300
- **Requirements:** every exported color must be pastel (`pastel: true` globally or per-color). Pastel mode bounds chroma by the hue-independent safe chroma at each lightness, so emitted `C` stays in sRGB for any rotated hue. Non-pastel palettes throw rather than emit values that would clip under rotation.
1359
+ **Requirements:** every exported color must be pastel (`pastel: true` on the theme/token override or per-color). Pastel mode bounds chroma by the hue-independent safe chroma at each lightness, so emitted `C` stays in sRGB for any rotated hue. Non-pastel palettes throw rather than emit values that would clip under rotation.
1301
1360
 
1302
1361
  **Limitations:** `oklch` only (native CSS `var()` in the hue slot). Shadow and mix colors stay inline (blended hue). Standalone `.token()` / `.tasty()` do not support `splitHue` (return shape cannot carry the `$name-hue` declaration).
1303
1362
 
@@ -1332,7 +1391,7 @@ Light: accent-fill tone=52, accent-text tone='+20' → lighter than the fill
1332
1391
  Dark: accent-fill maps into the dark window, sign preserved
1333
1392
  ```
1334
1393
 
1335
- Offsets that would push past `[0, 100]` clamp to the boundary, or — with `autoFlip` (default on) — mirror to the other side of the base. Set `autoFlip: false` to keep the authored side and clamp instead.
1394
+ Offsets that would push past `[0, 100]` clamp to the boundary, or — with `autoFlip` (default on) — mirror to the other side of the base. If the mirror also overshoots, the original side is kept and clamped. Set `autoFlip: false` to keep the authored side and clamp instead.
1336
1395
 
1337
1396
  **`static`** — no adaptation, same tone in every scheme.
1338
1397
 
@@ -1433,7 +1492,6 @@ boundaries, not the tone transfer.
1433
1492
  | `modes.highContrast` | `false` | Include HC variants. |
1434
1493
  | `shadowTuning` | `undefined` | Default tuning for all shadow colors. Per-color tuning merges field-by-field. |
1435
1494
  | `autoFlip` | `true` | Default for each color's `autoFlip`. When solving `contrast` (or applying a relative `tone` that overshoots `[0, 100]`), allow crossing to the opposite side instead of clamping. With `false`, only the requested direction is considered; unmet contrasts pin the tone to that direction's extreme (and emit a warning) and overshooting offsets clamp to the boundary. Override per color via [`autoFlip`](#autoflip). |
1436
- | `pastel` | `false` | Hue-independent "safe" chroma limit across all colors so scaling saturation never exceeds the sRGB boundary at any hue for the given lightness. Override per color via [`pastel`](#per-color-pastel). |
1437
1495
  | `inferRole` | `true` | Infer each color's [`role`](#roles) from its name when no explicit `role` is set. Set to `false` to opt out of name-based inference (the base-opposite and foreground-default fallbacks still apply). |
1438
1496
 
1439
1497
  | Method | Description |
@@ -1442,7 +1500,7 @@ boundaries, not the tone transfer.
1442
1500
  | `glaze.getConfig()` | Snapshot the current resolved config (shallow copy). |
1443
1501
  | `glaze.resetConfig()` | Reset to defaults (also bumps the version counter). |
1444
1502
 
1445
- Standalone `glaze.color()` tokens snapshot the resolve-relevant fields at create time, so later `configure()` calls don't change already-created tokens. Themes merge the live global at resolve time for fields not overridden via `GlazeConfigOverride`.
1503
+ Themes and standalone color tokens keep a sparse local `GlazeConfigOverride` and merge the live global at resolve time for omitted fields. Authoring `.export(override?)` freezes the effective merge at call time; restored instances pin that freeze. `pastel` is instance-only (theme/token override or per-color) — not set via `configure()`.
1446
1504
 
1447
1505
  ---
1448
1506
 
@@ -1484,7 +1542,7 @@ available result.
1484
1542
  | Relative `tone` without `base` in a **theme** color | Validation error |
1485
1543
  | `contrast` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1486
1544
  | Relative `tone` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1487
- | Relative `tone` overshoots `[0, 100]` | Mirror to the other side of the base (`autoFlip` on, default), or clamp to the boundary (`autoFlip` off) |
1545
+ | Relative `tone` overshoots `[0, 100]` | Mirror to the other side of the base (`autoFlip` on, default), or clamp to the boundary (`autoFlip` off). If the mirror also overshoots, clamp on the authored side. |
1488
1546
  | `tone` resolves outside 0–100 | Clamp silently |
1489
1547
  | `'max'` / `'min'` without `base` | Allowed — resolves to the scheme's tone extreme (root color) |
1490
1548
  | `saturation` outside 0–1 | Clamp silently |
package/docs/migration.md CHANGED
@@ -16,6 +16,11 @@ If you're starting from scratch, see [methodology.md](methodology.md) first —
16
16
 
17
17
  ## Choosing an export
18
18
 
19
+ Glaze has two layers of “export”:
20
+
21
+ 1. **Authoring snapshots** — `theme.export(override?)` / `token.export(override?)` / `palette.export(override?)`, restored with `glaze.themeFrom` / `colorFrom` / `paletteFrom`. These carry definitions, relations, and a config freeze taken at export time (`kind` + `version`). Live instances keep only a sparse local override; omitted global fields track `configure()` until export.
22
+ 2. **Resolved output** — the seven shapes below. These are color strings / design-token documents for apps and tools, **not** restorable as authoring config.
23
+
19
24
  Glaze emits the same resolved colors in seven shapes. Pick one based on your
20
25
  renderer or tooling.
21
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/glaze",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "OKHST-based color theme generator with WCAG contrast solving for light, dark, and high-contrast schemes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",