@tenphi/glaze 0.17.0 → 0.19.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/docs/api.md CHANGED
@@ -127,12 +127,12 @@ theme.tokens()
127
127
 
128
128
  ### `theme.tasty(options?)`
129
129
 
130
- Tasty style-to-state bindings for the [Tasty style system](https://tasty.style/docs). Uses `#name` color token keys and state aliases (`''`, `@dark`, etc.).
130
+ Tasty style-to-state bindings for the [Tasty style system](https://tasty.style/docs). Uses `#name` color token keys and state aliases. By default the dark and high-contrast variants are keyed by media-query states (`'@media(prefers-color-scheme: dark)'`, `'@media(prefers-contrast: more)'`) so tokens work without registering custom states.
131
131
 
132
132
  ```ts
133
133
  theme.tasty()
134
134
  // → {
135
- // '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
135
+ // '#surface': { '': 'okhsl(...)', '@media(prefers-color-scheme: dark)': 'okhsl(...)' },
136
136
  // ...
137
137
  // }
138
138
  ```
@@ -143,13 +143,13 @@ theme.tasty()
143
143
  |---|---|---|
144
144
  | `format` | `'okhsl'` | Output color format. `'okhsl'` and `'okhst'` are supported here (Tasty-only spaces). |
145
145
  | `modes` | global config | Which scheme variants to include. |
146
- | `states.dark` | `'@dark'` (or global config) | State alias for dark mode tokens. |
147
- | `states.highContrast` | `'@high-contrast'` (or global config) | State alias for high-contrast tokens. |
146
+ | `states.dark` | `'@media(prefers-color-scheme: dark)'` (or global config) | State alias for dark mode tokens. |
147
+ | `states.highContrast` | `'@media(prefers-contrast: more)'` (or global config) | State alias for high-contrast tokens. |
148
148
  | `splitHue` | `false` | Emit hue as a separate custom property (`$name-hue` token + `var()` in `oklch` values). Requires `format: 'oklch'` and every color to be pastel. |
149
149
  | `name` | `'theme'` | Base name for the theme-level hue var (`$theme-hue` / `--theme-hue`). Palette export auto-derives this from the theme name. |
150
150
  | `prefix` | (palette only) | See [Palette](#palette). |
151
151
 
152
- When both `dark` and `highContrast` modes are enabled, dark high-contrast variants are emitted under the combined key `<dark> & <highContrast>` (e.g. `'@dark & @high-contrast'`).
152
+ When both `dark` and `highContrast` modes are enabled, dark high-contrast variants are emitted under the combined key `<dark> & <highContrast>` (e.g. `'@media(prefers-color-scheme: dark) & @media(prefers-contrast: more)'`).
153
153
 
154
154
  ### `theme.json(options?)`
155
155
 
@@ -403,12 +403,29 @@ A bare number or preset means **WCAG**. Use `{ wcag }` / `{ apca }` to pick the
403
403
  contrast: 4.5 // WCAG 4.5
404
404
  contrast: 'AAA' // WCAG 7
405
405
  contrast: { wcag: 6 } // WCAG 6
406
- contrast: { wcag: [4.5, 7] } // WCAG 4.5 normal / 7 high-contrast
407
- contrast: { apca: 60 } // APCA Lc 60
408
- contrast: { apca: [45, 60] } // APCA Lc 45 normal / 60 high-contrast
409
- contrast: { apca: 'content' } // APCA preset -> Lc 60
410
- contrast: { apca: ['content', 'body'] } // Lc 60 normal / 75 high-contrast
411
- ```
406
+ contrast: { wcag: [4.5, 7] } // WCAG 4.5 normal / 7 high-contrast (explicit)
407
+ contrast: { apca: 60 } // APCA Lc 60 normal / 75 high-contrast (auto)
408
+ contrast: { apca: [45, 60] } // APCA Lc 45 normal / 60 high-contrast (explicit)
409
+ contrast: { apca: 'content' } // APCA preset -> Lc 60 normal / 75 high-contrast (auto)
410
+ contrast: { apca: ['content', 'body'] } // Lc 60 normal / 75 high-contrast (explicit)
411
+ ```
412
+
413
+ **WCAG HC auto-promotion:** a bare WCAG preset (no `[normal, hc]` pair at either
414
+ the outer `contrast` or inner `wcag` level) is automatically promoted to its
415
+ spec-defined "Enhanced" successor in high-contrast mode — `AA` → `AAA` (4.5 → 7)
416
+ and `AA-large` → `AAA-large` (3 → 4.5), per WCAG SC 1.4.3 → 1.4.6. `AAA` and
417
+ `AAA-large` are already the top WCAG tier and are left unchanged; bare numeric
418
+ targets have no successor tier and are also left unchanged. An explicit HC value
419
+ via either pair overrides and skips the promotion.
420
+
421
+ **APCA Enhanced Level (HC auto-boost):** a bare APCA scalar (no `[normal, hc]`
422
+ pair at either the outer `contrast` or inner `apca` level) is automatically
423
+ boosted by **+15 Lc** in high-contrast mode, the APCA analog of WCAG's
424
+ AAA-over-AA step. On by default; an explicit HC value via either pair
425
+ overrides it and skips the boost. The enhanced target is clamped to 106 Lc.
426
+ For large/bold text (where APCA caps contrast at Lc 90 to avoid glare), pass
427
+ an explicit HC pair to hold that ceiling — see
428
+ [`docs/okhst.md`](okhst.md) §Enhanced Level.
412
429
 
413
430
  APCA preset keywords (Bronze Simple Mode conformance levels, role-independent):
414
431
  `'preferred'` (Lc 90), `'body'` (75), `'content'` (60, ~AA), `'large'` (45, ~3:1),
@@ -1276,8 +1293,8 @@ glaze.configure({
1276
1293
  darkTone: [15, 95], // [lo, hi]; or { lo, hi, eps } / false to disable clamping
1277
1294
  darkDesaturation: 0.1,
1278
1295
  states: {
1279
- dark: '@dark',
1280
- highContrast: '@high-contrast',
1296
+ dark: '@media(prefers-color-scheme: dark)',
1297
+ highContrast: '@media(prefers-contrast: more)',
1281
1298
  },
1282
1299
  modes: {
1283
1300
  dark: true,
@@ -1299,8 +1316,8 @@ A `ToneWindow` is `[lo, hi]` (OKHSL-lightness endpoints, reference eps — the c
1299
1316
  | `lightTone` | `[10, 100]` | Light scheme tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` to disable clamping. Bypassed in HC. |
1300
1317
  | `darkTone` | `[15, 95]` | Dark scheme tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` to disable clamping. Bypassed in HC. |
1301
1318
  | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
1302
- | `states.dark` | `'@dark'` | State alias for dark mode tokens (Tasty export). |
1303
- | `states.highContrast` | `'@high-contrast'` | State alias for HC tokens. |
1319
+ | `states.dark` | `'@media(prefers-color-scheme: dark)'` | State alias for dark mode tokens (Tasty export). Defaults to a media query so tokens react to the OS preference without registering custom states. |
1320
+ | `states.highContrast` | `'@media(prefers-contrast: more)'` | State alias for HC tokens (Tasty export). |
1304
1321
  | `modes.dark` | `true` | Include dark variants in exports. |
1305
1322
  | `modes.highContrast` | `false` | Include HC variants. |
1306
1323
  | `shadowTuning` | `undefined` | Default tuning for all shadow colors. Per-color tuning merges field-by-field. |
@@ -1465,10 +1482,12 @@ import {
1465
1482
  |---|---|
1466
1483
  | `findToneForContrast(opts)` | Binary-search for the tone (0–1) that meets a contrast floor (WCAG or APCA) against a base color. Returns `{ tone, contrast, met, branch, flipped? }`. |
1467
1484
  | `findValueForMixContrast(opts)` | Same, but searches for a mix `value` (0–1) that meets a contrast floor between a base and a target. |
1468
- | `resolveContrastForMode(spec, isHC)` | Resolves a `ContrastSpec` to `{ metric: 'wcag' \| 'apca', target }` for the requested mode (picks the normal or HC entry of any pair). |
1485
+ | `resolveContrastForMode(spec, isHC, polarity?, outerExplicitHC?)` | Resolves a `ContrastSpec` to `{ metric: 'wcag' \| 'apca', target }` for the requested mode (picks the normal or HC entry of any pair). In HC, applies the metric's auto-enhancement unless `outerExplicitHC` is set or the inner metric pair carries an explicit HC value: APCA +15 Lc (clamped to 106); WCAG AA → AAA / AA-large → AAA-large (AAA-family and bare numbers unchanged). |
1469
1486
  | `resolveMinContrast(value)` | Resolves a `MinContrast` (WCAG preset or number) to a numeric ratio. |
1470
1487
  | `apcaContrast(yText, yBg)` | APCA Lc magnitude (0–106) for two relative luminances. |
1471
1488
 
1489
+ Exported constants: `APCA_PRESETS`, `APCA_HC_ENHANCEMENT` (`15`, the Enhanced Level delta), `APCA_MAX_LC` (`106`).
1490
+
1472
1491
  `findToneForContrast` options:
1473
1492
 
1474
1493
  | Option | Default | Description |
package/docs/migration.md CHANGED
@@ -106,7 +106,7 @@ Glaze emits the same resolved colors in six shapes. Pick one based on your rende
106
106
 
107
107
  | Method | Output shape | Use it for |
108
108
  |---|---|---|
109
- | `palette.tasty(options?)` | `{ '#name': { '': value, '@dark': value, '@hc': value } }` | The [Tasty](https://tasty.style/docs) style system. Single object, state aliases keyed inside each token. |
109
+ | `palette.tasty(options?)` | `{ '#name': { '': value, '@media(prefers-color-scheme: dark)': value, '@media(prefers-contrast: more)': value } }` | The [Tasty](https://tasty.style/docs) style system. Single object, state aliases keyed inside each token. |
110
110
  | `palette.tokens(options?)` | `{ light: { name: value }, dark: { name: value }, ... }` | Most CSS-in-JS systems. Per-variant flat maps, easy to feed into a `:root { ... }` selector via your framework's globals. |
111
111
  | `palette.css(options?)` | `{ light: '--name-color: rgb(...);', dark: '...', ... }` | Framework-free CSS / static stylesheets. Variant-grouped CSS custom property strings ready to wrap in `:root` and `prefers-color-scheme` queries. |
112
112
  | `palette.json(options?)` | `{ themeName: { name: { light, dark, ... } } }` | Tooling, JSON pipelines. |
@@ -136,11 +136,14 @@ useGlobalStyles('body', PALETTE_TOKENS);
136
136
  tastyStatic('body', PALETTE_TOKENS);
137
137
  ```
138
138
 
139
- For the `@dark` / `@hc` state aliases to do anything, your app needs to register what those states *mean*:
139
+ By default the dark / high-contrast variants are keyed by media-query states — `'@media(prefers-color-scheme: dark)'` and `'@media(prefers-contrast: more)'` so the tokens react to the OS preference out of the box, with no extra Tasty setup.
140
+
141
+ If you'd rather drive the schemes from custom aliases (e.g. a manual toggle that also falls back to the OS preference), set your own [`glaze.configure({ states })`](api.md#configuration) and register what those states *mean*:
140
142
 
141
143
  ```ts
142
144
  import { setGlobalPredefinedStates } from '@tenphi/tasty';
143
145
 
146
+ // glaze.configure({ states: { dark: '@dark', highContrast: '@hc' } });
144
147
  setGlobalPredefinedStates({
145
148
  '@dark':
146
149
  '@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
package/docs/okhst.md CHANGED
@@ -239,6 +239,55 @@ contrast: { apca: 'content' } // Lc 60
239
239
  contrast: { apca: ['content', 'body'] } // HC pair: 60 normal, 75 high-contrast
240
240
  ```
241
241
 
242
+ #### Enhanced Level (high-contrast auto-boost)
243
+
244
+ When an APCA target is given as a **bare scalar** (no `[normal, hc]` pair at
245
+ either the outer `contrast` level or the inner `apca` level), Glaze
246
+ automatically applies the APCA-W3 "Enhanced Level" delta — **+15 Lc** — in
247
+ high-contrast mode, analogous to WCAG's AAA-over-AA step. This is on by
248
+ default; provide an explicit HC value via either pair to override it.
249
+
250
+ ```ts
251
+ contrast: { apca: 60 } // Lc 60 normal, Lc 75 high-contrast (auto)
252
+ contrast: { apca: 'content' } // Lc 60 normal, Lc 75 high-contrast (auto)
253
+ contrast: { apca: [60, 90] } // Lc 60 normal, Lc 90 high-contrast (explicit)
254
+ contrast: [{ apca: 60 }, { apca: 90 }] // outer pair: explicit HC, no boost
255
+ contrast: { apca: 'preferred' } // Lc 90 normal, Lc 105 high-contrast (auto)
256
+ contrast: { apca: 100 } // Lc 100 normal, Lc 106 high-contrast (clamped)
257
+ ```
258
+
259
+ The enhanced target is clamped to `APCA_MAX_LC` (106).
260
+
261
+ > **Large-text glare caveat.** APCA-W3 also defines Lc 90 as a *maximum* for
262
+ > very large/bold text (>36px bold) and large areas of color, to prevent
263
+ > excessive glare. The +15 HC boost can therefore push a high baseline (e.g.
264
+ > `preferred` Lc 90 → 105) past that glare ceiling. This is intentional for
265
+ > small/standard text in HC (low-vision readability trumps glare, and small
266
+ > fonts have no APCA maximum), but if your HC tokens serve large/bold text,
267
+ > pass an explicit HC pair (`{ apca: [90, 90] }`) to hold the glare ceiling.
268
+ > Glaze does not model font size, so it can't enforce the large-text cap
269
+ > automatically.
270
+
271
+ #### WCAG HC auto-promotion
272
+
273
+ A bare WCAG preset (no `[normal, hc]` pair at either the outer `contrast` or
274
+ inner `wcag` level) is automatically promoted to its spec-defined "Enhanced"
275
+ successor in high-contrast mode — WCAG SC 1.4.3 (Minimum) → SC 1.4.6
276
+ (Enhanced), the direct analog of APCA's Enhanced Level:
277
+
278
+ ```ts
279
+ contrast: 'AA' // 4.5 normal, 7 high-contrast (auto -> AAA)
280
+ contrast: 'AA-large' // 3 normal, 4.5 high-contrast (auto -> AAA-large)
281
+ contrast: 'AAA' // 7 both modes (top tier, unchanged)
282
+ contrast: 'AAA-large' // 4.5 both modes (top tier, unchanged)
283
+ contrast: 5.5 // 5.5 both modes (bare number, no successor tier)
284
+ contrast: { wcag: ['AA', 'AA'] } // 4.5 both modes (explicit HC, no promotion)
285
+ ```
286
+
287
+ `AAA` and `AAA-large` are already the top WCAG tier and are left unchanged; bare
288
+ numeric targets have no defined successor tier and are also left unchanged. An
289
+ explicit HC value via either pair overrides and skips the promotion.
290
+
242
291
  ## Verification (APCA / WCAG drift)
243
292
 
244
293
  Because chromatic swatches inherit gray's tone-derived lightness but drift in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/glaze",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
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",