@tenphi/glaze 0.16.2 → 0.18.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
@@ -345,7 +345,7 @@ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
345
345
  | `base` | `string` | Name of another color in the same theme — makes this a *dependent* color. |
346
346
  | `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base`. Requires `base`. See [`contrast`](#contrast-floor). |
347
347
  | `mode` | `'auto' \| 'fixed' \| 'static'` | Adaptation mode. Default: `'auto'`. See [Adaptation modes](#adaptation-modes). |
348
- | `flip` | `boolean` | Flip out-of-bounds results (relative `tone` overshoot / unmet `contrast`) to the opposite side instead of clamping. Default: the global `autoFlip` (`true`). See [`flip`](#flip). |
348
+ | `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). |
349
349
  | `opacity` | `number` | Fixed alpha 0–1. Output includes alpha in the CSS value. Combining with `contrast` is not recommended (a `console.warn` is emitted). |
350
350
  | `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). |
351
351
  | `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). |
@@ -364,20 +364,20 @@ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
364
364
 
365
365
  **Absolute tone** on a dependent color (`base` set) positions the color independently. In dark mode it is tone-mapped (inverted + windowed) on its own. The `contrast` solver acts as a safety net.
366
366
 
367
- **Relative tone** applies a signed delta to the base color's resolved tone. Because tone is contrast-uniform, a fixed delta yields a fixed contrast step. In dark mode with `mode: 'auto'`, the offset is anchored to the base's per-scheme tone. If `base + delta` falls outside `[0, 100]`, the result is clamped to the boundary, or — with `flip` (default on) — mirrored to the other side of the base.
367
+ **Relative tone** applies a signed delta to the base color's resolved tone. Because tone is contrast-uniform, a fixed delta yields a fixed contrast step. In dark mode with `mode: 'auto'`, the offset is anchored to the base's per-scheme tone. If `base + delta` falls outside `[0, 100]`, the result is clamped to the boundary, or — with `autoFlip` (default on) — mirrored to the other side of the base.
368
368
 
369
369
  **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.
370
370
 
371
371
  A dependent color with `base` but no `tone` inherits the base's tone (equivalent to a delta of 0).
372
372
 
373
- #### `flip`
373
+ #### `autoFlip`
374
374
 
375
- `flip` governs what happens when a result would fall outside its valid range:
375
+ `autoFlip` governs what happens when a result would fall outside its valid range:
376
376
 
377
- - **Relative `tone` overshoot:** when `base ± delta` exceeds `[0, 100]`, `flip` mirrors the delta to the other side of the base (e.g. `'+30'` becomes `'-30'`) instead of clamping to the boundary.
378
- - **`contrast` direction:** when the requested tone direction can't meet the floor, `flip` lets the solver try the opposite side (the same behavior as the global `autoFlip`).
377
+ - **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.
378
+ - **`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`).
379
379
 
380
- `flip` defaults to the global `autoFlip` (`true`). Set `flip: 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.
380
+ `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.
381
381
 
382
382
  #### `contrast` (floor)
383
383
 
@@ -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),
@@ -417,7 +434,7 @@ APCA preset keywords (Bronze Simple Mode conformance levels, role-independent):
417
434
 
418
435
  The floor is applied independently per scheme — if the `tone` already satisfies it the tone is kept, otherwise the solver searches in tone (contrast-uniform → a closed-form WCAG seed and fast convergence) until the target is met.
419
436
 
420
- By default, the solver crosses to the opposite side of the base color when the requested tone direction cannot satisfy the floor. This is controlled per-color by [`flip`](#flip) (which defaults to the global `autoFlip`). Set `glaze.configure({ autoFlip: false })` — or `flip: false` on a single color — to keep strict directionality: unmet colors pin to that direction's 0 or 100 tone extreme instead of falling back to the original requested value.
437
+ By default, the solver crosses to the opposite side of the base color when the requested tone direction cannot satisfy the floor. This is controlled per-color by [`autoFlip`](#autoflip) (which defaults to the global `autoFlip`). Set `glaze.configure({ autoFlip: false })` — or `autoFlip: false` on a single color — to keep strict directionality: unmet colors pin to that direction's 0 or 100 tone extreme instead of falling back to the original requested value.
421
438
 
422
439
  **Full tone spectrum in HC mode:** in high-contrast variants the `lightTone` and `darkTone` window constraints are bypassed entirely (the window is forced to `[0, 100]`). Colors can reach the full range, maximizing perceivable contrast.
423
440
 
@@ -576,7 +593,7 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
576
593
  | `tone` | `HCPair<number \| ExtremeValue>` | 0–100 (contrast-uniform) or `'max'`/`'min'`, optional HC pair. |
577
594
  | `saturationFactor` | `number` | Multiplier on the seed (0–1). Default: `1`. |
578
595
  | `mode` | `AdaptationMode` | Default: `'auto'`. |
579
- | `flip` | `boolean` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
596
+ | `autoFlip` | `boolean` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
580
597
  | `opacity` | `number` | Fixed alpha 0–1. |
581
598
  | `base` | `GlazeColorToken \| GlazeColorValue` | Optional dependency. See [Pairing colors](#pairing-colors). |
582
599
  | `contrast` | `HCPair<ContrastSpec>` | Contrast floor against `base` (WCAG or APCA). Without `base`, anchored to the literal seed. |
@@ -594,7 +611,7 @@ glaze.color(color: GlazeFromInput | GlazeColorInput | GlazeColorValue, config?:
594
611
  | `tone` | Number (absolute 0–100), `'+N'`/`'-N'`, or `'max'`/`'min'`. Without `base`, relative anchors to the seed; with `base`, anchors to `base`'s tone per scheme. |
595
612
  | `saturationFactor` | Multiplier on the seed (0–1). |
596
613
  | `mode` | `'auto'` (default) / `'fixed'` / `'static'`. |
597
- | `flip` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
614
+ | `autoFlip` | Flip out-of-bounds results instead of clamping. Default: global `autoFlip`. |
598
615
  | `contrast` | Contrast floor (WCAG or APCA). Without `base`, anchored to the literal seed; with `base`, solved per scheme. |
599
616
  | `base` | `GlazeColorToken` or raw `GlazeColorValue`. See [Pairing colors](#pairing-colors). |
600
617
  | `opacity` | Fixed alpha 0–1. Combining with `contrast` is not recommended — `console.warn` is emitted. |
@@ -659,7 +676,7 @@ The optional `config` second argument (`GlazeConfigOverride`) overrides the reso
659
676
  | `lightTone` | `[10, 100]` | Light tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
660
677
  | `darkTone` | `[15, 95]` | Dark tone window: `[lo, hi]`, `{ lo, hi, eps }`, or `false` (disable clamping). |
661
678
  | `darkDesaturation` | `0.1` | Saturation reduction in dark scheme (0–1). |
662
- | `autoFlip` | `true` | Default for each color's `flip`: when solving `contrast` (or applying a relative `tone` that overshoots), allow crossing to the opposite side instead of clamping. |
679
+ | `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. |
663
680
  | `shadowTuning` | `undefined` | Default shadow tuning (meaningful for themes; harmless on color tokens). |
664
681
 
665
682
  Config overrides apply to both `glaze.color()` tokens and `glaze()` themes:
@@ -1216,7 +1233,7 @@ Light: accent-fill tone=52, accent-text tone='+20' → lighter than the fill
1216
1233
  Dark: accent-fill maps into the dark window, sign preserved
1217
1234
  ```
1218
1235
 
1219
- Offsets that would push past `[0, 100]` clamp to the boundary, or — with `flip` (default on) — mirror to the other side of the base. Set `flip: false` to keep the authored side and clamp instead.
1236
+ 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.
1220
1237
 
1221
1238
  **`static`** — no adaptation, same tone in every scheme.
1222
1239
 
@@ -1304,7 +1321,7 @@ A `ToneWindow` is `[lo, hi]` (OKHSL-lightness endpoints, reference eps — the c
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. |
1307
- | `autoFlip` | `true` | Default for each color's `flip`. 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 [`flip`](#flip). |
1324
+ | `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). |
1308
1325
  | `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). |
1309
1326
  | `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). |
1310
1327
 
@@ -1350,7 +1367,7 @@ Resolution priority (highest first):
1350
1367
  | Relative `tone` without `base` in a **theme** color | Validation error |
1351
1368
  | `contrast` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1352
1369
  | Relative `tone` without `base` in `glaze.color()` | Anchors against the literal seed (no error) |
1353
- | Relative `tone` overshoots `[0, 100]` | Mirror to the other side of the base (`flip` on, default), or clamp to the boundary (`flip` off) |
1370
+ | Relative `tone` overshoots `[0, 100]` | Mirror to the other side of the base (`autoFlip` on, default), or clamp to the boundary (`autoFlip` off) |
1354
1371
  | `tone` resolves outside 0–100 | Clamp silently |
1355
1372
  | `'max'` / `'min'` without `base` | Allowed — resolves to the scheme's tone extreme (root color) |
1356
1373
  | `saturation` outside 0–1 | Clamp silently |
@@ -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 |
@@ -209,14 +209,14 @@ defaultTheme.colors({
209
209
  base: 'disabled-surface',
210
210
  tone: '+18',
211
211
  saturation: 0.3,
212
- flip: false,
212
+ autoFlip: false,
213
213
  inherit: false,
214
214
  },
215
215
  });
216
216
  ```
217
217
 
218
218
  This says exactly what the pair should do: the chip sits a few tone steps off
219
- the page, and the label sits a muted distance from the chip. `flip: false` keeps
219
+ the page, and the label sits a muted distance from the chip. `autoFlip: false` keeps
220
220
  the relative label offset on the authored side when it reaches the edge.
221
221
 
222
222
  Use contrast instead when the chip must hit an explicit accessibility floor:
@@ -324,7 +324,7 @@ defaultTheme.colors({
324
324
  base: 'accent-disabled-surface',
325
325
  tone: '+18',
326
326
  saturation: 0.4,
327
- flip: false,
327
+ autoFlip: false,
328
328
  },
329
329
  });
330
330
  ```
package/docs/migration.md CHANGED
@@ -59,12 +59,14 @@ For colors that should sit at the scheme's tone extreme (pure-white knockouts, n
59
59
  // before — low contrast as a proxy for "stay near the surface"
60
60
  'disabled-text': { base: 'chip', tone: '+1', contrast: 1.51, mode: 'fixed' }
61
61
  // after — say it directly with tone
62
- 'disabled-text': { base: 'chip', tone: '+18', saturation: 0.4, flip: false }
62
+ 'disabled-text': { base: 'chip', tone: '+18', saturation: 0.4, autoFlip: false }
63
63
  ```
64
64
 
65
- ### The `flip` prop
65
+ ### The `autoFlip` prop (previously `flip`)
66
66
 
67
- Relative `tone` offsets that overshoot `[0, 100]` now **mirror to the other side of the base by default** (controlled by the new per-color `flip`, which inherits the global `autoFlip`, default `true`). Previously such offsets always clamped to the boundary. If you relied on clamping — e.g. `tone: '+48'` to stack a color up to 100 set `flip: false` on that color (or `glaze.configure({ autoFlip: false })` globally) to restore the clamping behavior. `flip` also governs the contrast solver's direction (its previous sole role).
67
+ The per-color configuration property `flip` has been renamed to `autoFlip` to align with the global `autoFlip` configuration option and to avoid confusion with dark-mode/scheme tone inversion (which is handled automatically by the system).
68
+
69
+ Relative `tone` offsets that overshoot `[0, 100]` now **mirror to the other side of the base by default** (controlled by the per-color `autoFlip`, which inherits the global `autoFlip`, default `true`). Previously such offsets always clamped to the boundary. If you relied on clamping — e.g. `tone: '+48'` to stack a color up to 100 — set `autoFlip: false` on that color (or `glaze.configure({ autoFlip: false })` globally) to restore the clamping behavior. `autoFlip` also governs the contrast solver's direction (its previous sole role).
68
70
 
69
71
  ### Resolved variants store tone
70
72
 
@@ -362,7 +364,7 @@ After migration, mark every default-only token (borders, shadows, disabled chip,
362
364
  | Brand color flips to its complement in dark mode. | Default `mode: 'auto'` inverts the tone. | Set `mode: 'fixed'` so the tone is remapped (not inverted). |
363
365
  | Brand text washes out against the dark surface. | Foreground was anchored to `accent-surface` (the brand fill), so contrast was only enforced against that fill — not the actual surface. | Anchor `accent-text` etc. to `surface` with `mode: 'auto'`. |
364
366
  | Tokens look right in light, broken in HC. | The HC pass bypasses the tone window — solver runs over the full `[0, 100]` range. | Add explicit `[normal, hc]` pairs to `tone` / `contrast` for the affected tokens. |
365
- | A relative `tone` like `'+48'` lands on the *wrong* (darker) side of its base. | Overshooting offsets now mirror to the other side of the base by default (`flip` inherits `autoFlip`). | Set `flip: false` on the color to clamp to the boundary instead, or use `tone: 'max'`/`'min'` to force the extreme. |
367
+ | A relative `tone` like `'+48'` lands on the *wrong* (darker) side of its base. | Overshooting offsets now mirror to the other side of the base by default (`autoFlip` inherits `autoFlip`). | Set `autoFlip: false` on the color to clamp to the boundary instead, or use `tone: 'max'`/`'min'` to force the extreme. |
366
368
  | `palette.tokens()` emits unexpected unprefixed names. | A `primary` was set on the palette (or per-call) and is duplicating the theme's tokens without prefix. | Pass `primary: false` to disable for that export, or rename `glaze.palette(themes, { primary })`. |
367
369
  | `console.warn: token "foo" collides with theme "bar"`. | Two themes resolved to the same output key under your prefix config. | Adjust the prefix map so each token is unique, or accept the first-write-wins behavior. |
368
370
  | `console.warn: color "X" cannot meet contrast`. | The requested contrast target is physically unreachable for the color's hue/saturation against its base. | Lower the floor, change the base, or accept the closest passing variant. Use the `name` override on standalone colors to make the warning identifiable. |
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.16.2",
3
+ "version": "0.18.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",