@tenphi/glaze 0.0.0-snapshot.432b23c → 0.0.0-snapshot.4e8eab7
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 +394 -43
- package/dist/index.cjs +782 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +328 -31
- package/dist/index.d.mts +328 -31
- package/dist/index.mjs +780 -103
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -7
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(
|
|
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
|
|
|
@@ -194,6 +194,8 @@ A single value applies to both modes. All control is local and explicit.
|
|
|
194
194
|
'muted': { base: 'surface', lightness: ['-35', '-50'], contrast: ['AA-large', 'AA'] }
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
**Full lightness spectrum in HC mode:** In high-contrast variants, the `lightLightness` and `darkLightness` window constraints are bypassed entirely. Colors can reach the full 0–100 lightness range, maximizing perceivable contrast. Normal (non-HC) variants continue to use the configured windows.
|
|
198
|
+
|
|
197
199
|
## Theme Color Management
|
|
198
200
|
|
|
199
201
|
### Adding Colors
|
|
@@ -261,15 +263,306 @@ The export contains only the configuration — not resolved color values. Resolv
|
|
|
261
263
|
Create a single color token without a full theme:
|
|
262
264
|
|
|
263
265
|
```ts
|
|
264
|
-
const accent = glaze.color({ hue: 280, saturation: 80, lightness: 52
|
|
266
|
+
const accent = glaze.color({ hue: 280, saturation: 80, lightness: 52 });
|
|
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(...)' }
|
|
272
|
+
accent.css({ name: 'accent' });
|
|
273
|
+
// → { light: '--accent-color: rgb(...);', dark: '--accent-color: rgb(...);', ... }
|
|
274
|
+
accent.export(); // → JSON-safe snapshot — pass to `glaze.colorFrom(...)` to rehydrate
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Defaults
|
|
278
|
+
|
|
279
|
+
`glaze.color()` is tuned for "render this exact color, but adapt the
|
|
280
|
+
dark variant" — different from theme colors, which are seeds that
|
|
281
|
+
adapt to both lightness windows. The defaults vary by input form,
|
|
282
|
+
because string inputs are typically end-user values (color pickers,
|
|
283
|
+
theme settings) where natural light/dark inversion is the expectation:
|
|
284
|
+
|
|
285
|
+
Every input form defaults to **`mode: 'auto'`** so the resolved token
|
|
286
|
+
adapts between light and dark like an ordinary theme color. The
|
|
287
|
+
*scaling* snapshot taken at create time differs by input form:
|
|
288
|
+
|
|
289
|
+
- **String value-shorthand** (hex, `rgb()`, `hsl()`, `okhsl()`,
|
|
290
|
+
`oklch()`):
|
|
291
|
+
- Light variant preserves the input lightness exactly.
|
|
292
|
+
- Dark variant is **Möbius-inverted** into `[globalConfig.darkLightness[0], 100]`,
|
|
293
|
+
so `glaze.color('#000')` renders as `#fff` in dark mode and
|
|
294
|
+
`glaze.color('#fff')` falls to the dark `lo` floor (default `0.15`).
|
|
295
|
+
- The dark `lo` is snapshotted from `globalConfig` at color-creation
|
|
296
|
+
time, matching how an explicit `scaling.darkLightness: [lo, hi]`
|
|
297
|
+
behaves.
|
|
298
|
+
|
|
299
|
+
- **Object / tuple value-shorthand** (`{ h, s, l }`, `[r, g, b]`) and
|
|
300
|
+
the **structured form** (`{ hue, saturation, lightness, ... }`):
|
|
301
|
+
- Both light and dark variants are mapped through
|
|
302
|
+
`globalConfig.lightLightness` / `globalConfig.darkLightness`
|
|
303
|
+
(defaults `[10, 100]` / `[15, 95]`) — the same windows a theme color
|
|
304
|
+
uses. With the `'auto'` default the dark variant is Möbius-inverted
|
|
305
|
+
into that dark window, so a near-white seed lands at a near-dark
|
|
306
|
+
dark variant.
|
|
307
|
+
- Both windows are snapshotted at color-creation time so later
|
|
308
|
+
`glaze.configure()` calls don't retroactively change exported tokens.
|
|
309
|
+
|
|
310
|
+
To opt back into the legacy fixed-linear default (no Möbius inversion),
|
|
311
|
+
pass `{ mode: 'fixed' }` as the second arg, or supply an explicit
|
|
312
|
+
`scaling` as the third arg (see [Lightness scaling](#lightness-scaling)).
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
// Default: pure black inverts to pure white in dark mode.
|
|
316
|
+
glaze.color('#000000').tasty();
|
|
317
|
+
// → { '': 'okhsl(0 0% 0%)', '@dark': 'okhsl(... 100%)' }
|
|
318
|
+
|
|
319
|
+
// Opt back into the fixed-linear behavior:
|
|
320
|
+
glaze.color('#000000', { mode: 'fixed' }).tasty();
|
|
321
|
+
// → { '': 'okhsl(0 0% 0%)', '@dark': 'okhsl(... 15%)' }
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Value Shorthand
|
|
325
|
+
|
|
326
|
+
The first argument can also be a color value — Glaze extracts the seed
|
|
327
|
+
hue/saturation/lightness for you. All forms support the same exports
|
|
328
|
+
(`resolve / token / tasty / json / css`):
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
// Hex (3, 6, or 8 digits — alpha dropped with warning)
|
|
332
|
+
glaze.color('#26fcb2').tasty();
|
|
333
|
+
glaze.color('#26fcb2ff').tasty(); // alpha dropped
|
|
334
|
+
|
|
335
|
+
// CSS color functions Glaze itself emits (`rgb()`, `hsl()`, `okhsl()`, `oklch()`)
|
|
336
|
+
// — anything from theme.tasty()/json()/css() round-trips back in.
|
|
337
|
+
glaze.color('rgb(38 252 178)').tasty();
|
|
338
|
+
glaze.color('hsl(152 97% 57%)').tasty();
|
|
339
|
+
glaze.color('okhsl(152 95% 74%)').tasty();
|
|
340
|
+
glaze.color('oklch(0.85 0.18 152)').tasty();
|
|
341
|
+
|
|
342
|
+
// OKHSL object — Glaze's native shape (h: 0–360, s/l: 0–1).
|
|
343
|
+
// Passing 0–100 values for s/l throws with a hint to use the
|
|
344
|
+
// structured form { hue, saturation, lightness }.
|
|
345
|
+
glaze.color({ h: 152, s: 0.95, l: 0.74 }).tasty();
|
|
265
346
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
accent.tasty(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (same as token)
|
|
269
|
-
accent.json(); // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
|
|
347
|
+
// RGB tuple, 0–255 (same range as glaze.fromRgb).
|
|
348
|
+
glaze.color([38, 252, 178]).tasty();
|
|
270
349
|
```
|
|
271
350
|
|
|
272
|
-
|
|
351
|
+
The optional second argument supplies overrides — the WCAG `contrast`
|
|
352
|
+
solver, relative `hue` / `lightness`, plus the usual seed knobs:
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
// Brand color seeded from a hex, with saturation/mode overrides
|
|
356
|
+
glaze.color('#26fcb2', { saturation: 80, mode: 'fixed' }).tasty();
|
|
357
|
+
|
|
358
|
+
// Brand text guaranteed AAA against the seed itself.
|
|
359
|
+
// Relative `lightness: '+48'` is anchored to the literal seed value.
|
|
360
|
+
glaze.color('#1a1a2e', {
|
|
361
|
+
lightness: '+48',
|
|
362
|
+
contrast: 'AAA',
|
|
363
|
+
}).tasty();
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
By default, relative `lightness: '+N'` and `contrast: <ratio>` are
|
|
367
|
+
anchored to the literal seed (the value passed to `glaze.color()`).
|
|
368
|
+
Internally Glaze synthesizes a hidden `mode: 'static'` reference of
|
|
369
|
+
the seed so the contrast solver compares against the unmapped color
|
|
370
|
+
across every variant. Pass `base` (another `glaze.color()` token) to
|
|
371
|
+
anchor against another color's resolved variant per scheme instead —
|
|
372
|
+
see [Pairing Colors](#pairing-colors).
|
|
373
|
+
|
|
374
|
+
All overrides:
|
|
375
|
+
|
|
376
|
+
| Option | Notes |
|
|
377
|
+
|---|---|
|
|
378
|
+
| `hue` | Number (absolute 0–360) or `'+N'`/`'-N'` (relative to seed — never to `base`) |
|
|
379
|
+
| `saturation` | Override seed saturation (0–100) |
|
|
380
|
+
| `lightness` | Number (absolute 0–100) or `'+N'`/`'-N'`. Without `base`, relative is anchored to the literal seed; with `base`, anchored to `base`'s lightness per scheme. Supports `[normal, hc]` pairs |
|
|
381
|
+
| `saturationFactor` | Multiplier on seed (0–1, default 1) |
|
|
382
|
+
| `mode` | `'auto'` (default for every input form) / `'fixed'` / `'static'` — see [Adaptation Modes](#adaptation-modes) |
|
|
383
|
+
| `contrast` | WCAG floor. Without `base`, anchored to the literal seed; with `base`, solved per scheme against `base`'s resolved variant. Same shape as `RegularColorDef.contrast`. When the target can't be physically met, `glaze` emits a `console.warn` and returns the closest passing variant |
|
|
384
|
+
| `base` | Another `glaze.color()` token **or** a raw `GlazeColorValue` (hex / `rgb()` / `OkhslColor` / `[r, g, b]`). Raw values are auto-wrapped via `glaze.color(value)` so they pick up the same auto-invert defaults as an explicit wrap. When set, `contrast` and relative `lightness` anchor to it per scheme; relative `hue` still anchors to the seed |
|
|
385
|
+
| `opacity` | Fixed alpha 0–1 applied to every variant. Surfaces in `rgb(... / A)`, `okhsl(... / A)`, etc. Combining with `contrast` is not recommended (perceived lightness becomes unpredictable) — `glaze` emits a `console.warn` |
|
|
386
|
+
| `name` | **Debug label only** — surfaces in error and `console.warn` messages instead of the internal `"value"` sentinel. Does **not** change `.token()` / `.tasty()` / `.json()` / `.css()` output keys (those still use `''`, `light`, etc.). Reserved names (`"value"`, `"seed"`, `"externalBase"`) are rejected |
|
|
387
|
+
|
|
388
|
+
Alpha components in `rgb(... / A)` / `hsl(... / A)` / `rgba(...)` /
|
|
389
|
+
`hsla(...)` and 8-digit hex (`#rrggbbaa` / `#rgba`) are parsed but the
|
|
390
|
+
alpha channel is dropped with a `console.warn`. To set a fixed alpha
|
|
391
|
+
on a standalone color, use the `opacity` override (or `opacity` on a
|
|
392
|
+
theme color). Named CSS colors (`'red'`, `'blueviolet'`) are not
|
|
393
|
+
supported.
|
|
394
|
+
|
|
395
|
+
### Lightness Scaling
|
|
396
|
+
|
|
397
|
+
The optional third positional argument lets you override the lightness
|
|
398
|
+
windows used by `glaze.color()`. Both keys mirror the field names from
|
|
399
|
+
`GlazeConfig`:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
// Preserve raw lightness in dark mode too:
|
|
403
|
+
glaze.color('#26fcb2', undefined, { darkLightness: false }).tasty();
|
|
404
|
+
|
|
405
|
+
// Or opt back into a theme-style window:
|
|
406
|
+
glaze.color('#26fcb2', undefined, {
|
|
407
|
+
lightLightness: [10, 100],
|
|
408
|
+
darkLightness: [15, 95],
|
|
409
|
+
}).tasty();
|
|
410
|
+
|
|
411
|
+
// Structured form takes scaling as the second positional arg:
|
|
412
|
+
glaze
|
|
413
|
+
.color({ hue: 152, saturation: 95, lightness: 74 }, { darkLightness: false })
|
|
414
|
+
.tasty();
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
| Key | Default for `glaze.color()` (string input) | Default for `glaze.color()` (object / tuple / structured) | Effect |
|
|
418
|
+
|---|---|---|---|
|
|
419
|
+
| `lightLightness` | `false` | `false` | `false` = preserve input. Pass `[lo, hi]` to opt into a remap window. |
|
|
420
|
+
| `darkLightness` | `[globalConfig.darkLightness[0], 100]` (snapshotted; default `[15, 100]`) | `globalConfig.darkLightness` (snapshotted; default `[15, 95]`) | `false` = preserve input in dark too. Pass `[lo, hi]` to override the window. |
|
|
421
|
+
|
|
422
|
+
> Note: `scaling` is all-or-nothing — passing it replaces both fields
|
|
423
|
+
> at once. To keep one field's default, restate it explicitly. The
|
|
424
|
+
> default windows are snapshotted from `globalConfig` at color-creation
|
|
425
|
+
> time, so later `glaze.configure()` calls don't retroactively change
|
|
426
|
+
> already-created tokens (and `token.export()` round-trips
|
|
427
|
+
> byte-for-byte across `configure()` changes).
|
|
428
|
+
|
|
429
|
+
### Pairing Colors
|
|
430
|
+
|
|
431
|
+
`glaze.color()` accepts an optional `base` override that ties one
|
|
432
|
+
standalone color to another. When you set `base`, the WCAG contrast
|
|
433
|
+
solver and relative `lightness` offsets switch their anchor from the
|
|
434
|
+
literal seed to the base's resolved variant per scheme — so the same
|
|
435
|
+
text color automatically lands at AA against its background in light,
|
|
436
|
+
dark, and high-contrast modes.
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
const bg = glaze.color('#1a1a2e');
|
|
440
|
+
|
|
441
|
+
// Text guaranteed AA against `bg` in every scheme.
|
|
442
|
+
const text = glaze.color('#ffffff', { base: bg, contrast: 'AA' });
|
|
443
|
+
|
|
444
|
+
// Border 8 lightness units lighter than `bg` in each scheme.
|
|
445
|
+
const border = glaze.color('#000000', {
|
|
446
|
+
base: bg,
|
|
447
|
+
lightness: '+8',
|
|
448
|
+
mode: 'fixed',
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
`base` also accepts a raw `GlazeColorValue` for one-off pairs without
|
|
453
|
+
a separate token binding:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
// Equivalent to `base: glaze.color('#1a1a2e')` — `glaze` auto-wraps it.
|
|
457
|
+
const text = glaze.color('#ffffff', { base: '#1a1a2e', contrast: 'AA' });
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Behavior with `base`:
|
|
461
|
+
|
|
462
|
+
- `contrast` is solved per scheme against `base`'s resolved variant
|
|
463
|
+
(light / dark / lightContrast / darkContrast).
|
|
464
|
+
- Relative `lightness: '+N'` / `'-N'` is anchored to `base`'s lightness
|
|
465
|
+
per scheme (matches theme behavior).
|
|
466
|
+
- Relative `hue: '+N'` still anchors to the **seed** (the value passed
|
|
467
|
+
to `glaze.color()`), not the base. Absolute hue overrides take
|
|
468
|
+
precedence as usual.
|
|
469
|
+
- `mode` works as a per-pair knob — pass `mode: 'fixed'` to disable
|
|
470
|
+
Möbius inversion for the dependent color, or `mode: 'auto'` to keep
|
|
471
|
+
it (defaults follow the same string-vs-object rules as standalone).
|
|
472
|
+
- The base token's `.resolve()` is called lazily on the first resolve
|
|
473
|
+
of the dependent and the result is captured by reference; later
|
|
474
|
+
mutations to the base don't apply (matches existing snapshot
|
|
475
|
+
semantics for `scaling.darkLightness`).
|
|
476
|
+
- Raw value bases (`base: '#fff'`, `base: { h, s, l }`, `base: [r, g, b]`)
|
|
477
|
+
are auto-wrapped via `glaze.color(value)` and inherit the same
|
|
478
|
+
string-vs-object defaults. To skip auto-invert on the base, wrap it
|
|
479
|
+
yourself: `base: glaze.color(value, undefined, { darkLightness: false })`.
|
|
480
|
+
- When the contrast target is physically unreachable (e.g. AAA against
|
|
481
|
+
a mid-grey base), `glaze` emits a single `console.warn` per
|
|
482
|
+
`(name, scheme, target)` triple and returns the closest passing
|
|
483
|
+
variant. Use the `name` override to make the warning more
|
|
484
|
+
identifiable in your logs.
|
|
485
|
+
|
|
486
|
+
Chains compose:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
const bg = glaze.color('#000000');
|
|
490
|
+
const surface = glaze.color('#222222', { base: bg, contrast: 'AAA' });
|
|
491
|
+
const text = glaze.color('#ffffff', { base: surface, contrast: 'AA' });
|
|
492
|
+
// Each level meets its contrast budget against its base in every scheme.
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Naming Standalone Colors
|
|
496
|
+
|
|
497
|
+
The `name` override is a **debug label**, not an output key:
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
const cardBg = glaze.color('#1a1a2e', {
|
|
501
|
+
name: 'card-bg', // surfaces in `console.warn` / Error messages
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
cardBg.token(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' }
|
|
505
|
+
cardBg.json(); // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
|
|
506
|
+
cardBg.css({ name: 'card' }); // CSS variable name comes from `css({ name })`,
|
|
507
|
+
// NOT from the override above
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Use it to make warnings traceable when you have many `glaze.color()`
|
|
511
|
+
calls in a project — without it, `glaze` falls back to the internal
|
|
512
|
+
sentinel `"value"`:
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
// With name:
|
|
516
|
+
// > glaze: color "card-bg" cannot meet contrast "AAA" (7.00) in dark scheme...
|
|
517
|
+
|
|
518
|
+
// Without name:
|
|
519
|
+
// > glaze: color "value" cannot meet contrast "AAA" (7.00) in dark scheme...
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
The reserved internal sentinels (`"value"`, `"seed"`, `"externalBase"`)
|
|
523
|
+
are rejected with a clear error pointing at the conflict.
|
|
524
|
+
|
|
525
|
+
### Persisting Standalone Colors
|
|
526
|
+
|
|
527
|
+
`glaze.color()` tokens can be serialized to JSON-safe data and
|
|
528
|
+
rehydrated later — useful for color pickers, theme settings UIs, and
|
|
529
|
+
URL state.
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
const text = glaze.color('#1a1a1a', {
|
|
533
|
+
contrast: 'AA',
|
|
534
|
+
opacity: 0.9,
|
|
535
|
+
name: 'profile-text',
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const data = text.export(); // JSON-safe snapshot
|
|
539
|
+
const json = JSON.stringify(data); // ship to localStorage / API / URL
|
|
540
|
+
const restored = glaze.colorFrom(JSON.parse(json));
|
|
541
|
+
// `restored.resolve()` matches `text.resolve()` byte-for-byte.
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
The export captures the original `value`, all overrides, and the
|
|
545
|
+
effective `scaling` (snapshotted from `globalConfig` at create time so
|
|
546
|
+
later `glaze.configure()` calls don't change exported tokens).
|
|
547
|
+
Token-typed `base` is recursively serialized, value-typed `base` is
|
|
548
|
+
preserved as the raw value.
|
|
549
|
+
|
|
550
|
+
Both forms round-trip:
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
// Value form
|
|
554
|
+
const a = glaze.color('#26fcb2', { contrast: 'AA' });
|
|
555
|
+
const aBack = glaze.colorFrom(a.export());
|
|
556
|
+
|
|
557
|
+
// Structured form
|
|
558
|
+
const b = glaze.color({
|
|
559
|
+
hue: 280,
|
|
560
|
+
saturation: 50,
|
|
561
|
+
lightness: 50,
|
|
562
|
+
opacity: 0.5,
|
|
563
|
+
});
|
|
564
|
+
const bBack = glaze.colorFrom(b.export());
|
|
565
|
+
```
|
|
273
566
|
|
|
274
567
|
## From Existing Colors
|
|
275
568
|
|
|
@@ -391,7 +684,10 @@ Available tuning parameters:
|
|
|
391
684
|
|
|
392
685
|
### Standalone Shadow Computation
|
|
393
686
|
|
|
394
|
-
Compute a shadow outside of a theme
|
|
687
|
+
Compute a shadow outside of a theme. `bg` and `fg` accept any
|
|
688
|
+
`GlazeColorValue`: hex (`#rgb` / `#rrggbb` / `#rrggbbaa`), `rgb()` /
|
|
689
|
+
`hsl()` / `okhsl()` / `oklch()` strings, OKHSL objects, or `[r, g, b]`
|
|
690
|
+
(0–255) tuples.
|
|
395
691
|
|
|
396
692
|
```ts
|
|
397
693
|
const v = glaze.shadow({
|
|
@@ -401,6 +697,13 @@ const v = glaze.shadow({
|
|
|
401
697
|
});
|
|
402
698
|
// → { h: 280, s: 0.14, l: 0.2, alpha: 0.1 }
|
|
403
699
|
|
|
700
|
+
// Equivalent with non-hex inputs:
|
|
701
|
+
glaze.shadow({
|
|
702
|
+
bg: 'rgb(240 238 245)',
|
|
703
|
+
fg: { h: 280, s: 0.06, l: 0.13 },
|
|
704
|
+
intensity: 10,
|
|
705
|
+
});
|
|
706
|
+
|
|
404
707
|
const css = glaze.format(v, 'oklch');
|
|
405
708
|
// → 'oklch(0.15 0.014 280 / 0.1)'
|
|
406
709
|
```
|
|
@@ -565,7 +868,7 @@ theme.tokens({ format: 'hsl' }); // → 'hsl(270.5 45.2% 95.8%)'
|
|
|
565
868
|
theme.tokens({ format: 'oklch' }); // → 'oklch(0.965 0.0123 280)'
|
|
566
869
|
```
|
|
567
870
|
|
|
568
|
-
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()`.
|
|
871
|
+
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()`.
|
|
569
872
|
|
|
570
873
|
Colors with `alpha < 1` (shadow colors, or regular colors with `opacity`) include an alpha component:
|
|
571
874
|
|
|
@@ -601,7 +904,7 @@ Modes control how colors adapt across schemes:
|
|
|
601
904
|
|
|
602
905
|
```ts
|
|
603
906
|
// Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
|
|
604
|
-
// Dark: surface inverts to L≈
|
|
907
|
+
// Dark: surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
|
|
605
908
|
// contrast solver may push further (light text on dark bg)
|
|
606
909
|
```
|
|
607
910
|
|
|
@@ -625,7 +928,7 @@ const [lo, hi] = lightLightness; // default: [10, 100]
|
|
|
625
928
|
const mappedL = (lightness * (hi - lo)) / 100 + lo;
|
|
626
929
|
```
|
|
627
930
|
|
|
628
|
-
Both `auto` and `fixed` modes use the same linear formula. `static` mode
|
|
931
|
+
Both `auto` and `fixed` modes use the same linear formula. `static` mode and high-contrast variants bypass the mapping entirely (identity: `mappedL = l`).
|
|
629
932
|
|
|
630
933
|
| Color | Raw L | Mapped L (default [10, 100]) |
|
|
631
934
|
|---|---|---|
|
|
@@ -637,24 +940,29 @@ Both `auto` and `fixed` modes use the same linear formula. `static` mode bypasse
|
|
|
637
940
|
|
|
638
941
|
### Lightness
|
|
639
942
|
|
|
640
|
-
**`auto`** — inverted within the configured window:
|
|
943
|
+
**`auto`** — inverted with a Möbius transformation within the configured window:
|
|
641
944
|
|
|
642
945
|
```ts
|
|
643
946
|
const [lo, hi] = darkLightness; // default: [15, 95]
|
|
644
|
-
const
|
|
947
|
+
const t = (100 - lightness) / 100;
|
|
948
|
+
const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5
|
|
645
949
|
```
|
|
646
950
|
|
|
647
|
-
|
|
951
|
+
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.
|
|
952
|
+
|
|
953
|
+
**`fixed`** — mapped without inversion (not affected by `darkCurve`):
|
|
648
954
|
|
|
649
955
|
```ts
|
|
650
956
|
const mappedL = (lightness * (hi - lo)) / 100 + lo;
|
|
651
957
|
```
|
|
652
958
|
|
|
653
|
-
| Color | Light L | Auto (
|
|
654
|
-
|
|
655
|
-
| surface (L=97) | 97 | 17.4 | 92.6 |
|
|
656
|
-
| accent-fill (L=52) | 52 | 53.4 | 56.6 |
|
|
657
|
-
| accent-text (L=100) | 100 | 15 | 95 |
|
|
959
|
+
| Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
|
|
960
|
+
|---|---|---|---|---|
|
|
961
|
+
| surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
|
|
962
|
+
| accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
|
|
963
|
+
| accent-text (L=100) | 100 | 15 | 15 | 95 |
|
|
964
|
+
|
|
965
|
+
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]`).
|
|
658
966
|
|
|
659
967
|
### Saturation
|
|
660
968
|
|
|
@@ -694,6 +1002,15 @@ Combine multiple themes into a single palette:
|
|
|
694
1002
|
const palette = glaze.palette({ primary, danger, success, warning });
|
|
695
1003
|
```
|
|
696
1004
|
|
|
1005
|
+
Optionally designate a primary theme at creation time:
|
|
1006
|
+
|
|
1007
|
+
```ts
|
|
1008
|
+
const palette = glaze.palette(
|
|
1009
|
+
{ primary, danger, success, warning },
|
|
1010
|
+
{ primary: 'primary' },
|
|
1011
|
+
);
|
|
1012
|
+
```
|
|
1013
|
+
|
|
697
1014
|
### Prefix Behavior
|
|
698
1015
|
|
|
699
1016
|
Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
|
|
@@ -712,15 +1029,28 @@ Custom prefix mapping:
|
|
|
712
1029
|
palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
|
|
713
1030
|
```
|
|
714
1031
|
|
|
715
|
-
To disable prefixing entirely, pass `prefix: false` explicitly.
|
|
1032
|
+
To disable prefixing entirely, pass `prefix: false` explicitly.
|
|
1033
|
+
|
|
1034
|
+
### Collision Detection
|
|
1035
|
+
|
|
1036
|
+
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:
|
|
1037
|
+
|
|
1038
|
+
```ts
|
|
1039
|
+
const palette = glaze.palette({ a, b });
|
|
1040
|
+
palette.tokens({ prefix: false });
|
|
1041
|
+
// ⚠ glaze: token "surface" from theme "b" collides with theme "a" — skipping.
|
|
1042
|
+
```
|
|
716
1043
|
|
|
717
1044
|
### Primary Theme
|
|
718
1045
|
|
|
719
|
-
|
|
1046
|
+
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:
|
|
720
1047
|
|
|
721
1048
|
```ts
|
|
722
|
-
const palette = glaze.palette(
|
|
723
|
-
|
|
1049
|
+
const palette = glaze.palette(
|
|
1050
|
+
{ primary, danger, success },
|
|
1051
|
+
{ primary: 'primary' },
|
|
1052
|
+
);
|
|
1053
|
+
const tokens = palette.tokens();
|
|
724
1054
|
// → {
|
|
725
1055
|
// light: {
|
|
726
1056
|
// 'primary-surface': 'okhsl(...)', // prefixed (all themes)
|
|
@@ -731,21 +1061,32 @@ const tokens = palette.tokens({ primary: 'primary' });
|
|
|
731
1061
|
// }
|
|
732
1062
|
```
|
|
733
1063
|
|
|
1064
|
+
Override or disable per-export:
|
|
1065
|
+
|
|
1066
|
+
```ts
|
|
1067
|
+
palette.tokens({ primary: 'danger' }); // use danger as primary for this call
|
|
1068
|
+
palette.tokens({ primary: false }); // no primary for this call
|
|
1069
|
+
```
|
|
1070
|
+
|
|
734
1071
|
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:
|
|
735
1072
|
|
|
736
1073
|
```ts
|
|
737
|
-
palette.tokens({ prefix: { primary: 'p-', danger: 'd-' }
|
|
738
|
-
// → 'p-surface' + 'surface' (alias) + 'd-surface'
|
|
1074
|
+
palette.tokens({ prefix: { primary: 'p-', danger: 'd-' } });
|
|
1075
|
+
// → 'p-surface' + 'surface' (alias from palette-level primary) + 'd-surface'
|
|
739
1076
|
```
|
|
740
1077
|
|
|
741
1078
|
An error is thrown if the primary name doesn't match any theme in the palette.
|
|
742
1079
|
|
|
743
|
-
### Tasty Export (for [Tasty](https://
|
|
1080
|
+
### Tasty Export (for [Tasty](https://tasty.style) style system)
|
|
744
1081
|
|
|
745
|
-
The `tasty()` method exports tokens in the [Tasty](https://
|
|
1082
|
+
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:
|
|
746
1083
|
|
|
747
1084
|
```ts
|
|
748
|
-
const
|
|
1085
|
+
const palette = glaze.palette(
|
|
1086
|
+
{ primary, danger, success },
|
|
1087
|
+
{ primary: 'primary' },
|
|
1088
|
+
);
|
|
1089
|
+
const tastyTokens = palette.tasty();
|
|
749
1090
|
// → {
|
|
750
1091
|
// '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
751
1092
|
// '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
@@ -841,7 +1182,11 @@ const css = theme.css();
|
|
|
841
1182
|
Use in a stylesheet:
|
|
842
1183
|
|
|
843
1184
|
```ts
|
|
844
|
-
const
|
|
1185
|
+
const palette = glaze.palette(
|
|
1186
|
+
{ primary, danger, success },
|
|
1187
|
+
{ primary: 'primary' },
|
|
1188
|
+
);
|
|
1189
|
+
const css = palette.css();
|
|
845
1190
|
|
|
846
1191
|
const stylesheet = `
|
|
847
1192
|
:root { ${css.light} }
|
|
@@ -858,7 +1203,7 @@ Options:
|
|
|
858
1203
|
| `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
|
|
859
1204
|
| `suffix` | `'-color'` | Suffix appended to each CSS property name |
|
|
860
1205
|
| `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
|
|
861
|
-
| `primary` |
|
|
1206
|
+
| `primary` | inherited | (palette only) Override or disable (`false`) the palette-level primary for this call |
|
|
862
1207
|
|
|
863
1208
|
```ts
|
|
864
1209
|
// Custom suffix
|
|
@@ -869,8 +1214,8 @@ theme.css({ suffix: '' });
|
|
|
869
1214
|
theme.css({ format: 'hsl' });
|
|
870
1215
|
// → "--surface-color: hsl(...);"
|
|
871
1216
|
|
|
872
|
-
// Palette with primary
|
|
873
|
-
palette.css(
|
|
1217
|
+
// Palette with primary (inherited from palette creation)
|
|
1218
|
+
palette.css();
|
|
874
1219
|
// → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
875
1220
|
```
|
|
876
1221
|
|
|
@@ -904,9 +1249,10 @@ Resolution priority (highest first):
|
|
|
904
1249
|
|
|
905
1250
|
```ts
|
|
906
1251
|
glaze.configure({
|
|
907
|
-
lightLightness: [10, 100], // Light scheme lightness window [lo, hi]
|
|
908
|
-
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi]
|
|
1252
|
+
lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
|
|
1253
|
+
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
|
|
909
1254
|
darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
|
|
1255
|
+
darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1); or [normal, hc] pair
|
|
910
1256
|
states: {
|
|
911
1257
|
dark: '@dark', // State alias for dark mode tokens
|
|
912
1258
|
highContrast: '@high-contrast',
|
|
@@ -1041,17 +1387,20 @@ const success = primary.extend({ hue: 157 });
|
|
|
1041
1387
|
const warning = primary.extend({ hue: 84 });
|
|
1042
1388
|
const note = primary.extend({ hue: 302 });
|
|
1043
1389
|
|
|
1044
|
-
const palette = glaze.palette(
|
|
1390
|
+
const palette = glaze.palette(
|
|
1391
|
+
{ primary, danger, success, warning, note },
|
|
1392
|
+
{ primary: 'primary' },
|
|
1393
|
+
);
|
|
1045
1394
|
|
|
1046
1395
|
// Export as flat token map grouped by variant (prefix defaults to true)
|
|
1047
|
-
const tokens = palette.tokens(
|
|
1396
|
+
const tokens = palette.tokens();
|
|
1048
1397
|
// tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
|
|
1049
1398
|
|
|
1050
1399
|
// Export as tasty style-to-state bindings (for Tasty style system)
|
|
1051
|
-
const tastyTokens = palette.tasty(
|
|
1400
|
+
const tastyTokens = palette.tasty();
|
|
1052
1401
|
|
|
1053
1402
|
// Export as CSS custom properties (rgb format by default)
|
|
1054
|
-
const css = palette.css(
|
|
1403
|
+
const css = palette.css();
|
|
1055
1404
|
// css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
1056
1405
|
|
|
1057
1406
|
// Standalone shadow computation
|
|
@@ -1079,8 +1428,10 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
|
|
|
1079
1428
|
| `glaze.from(data)` | Create a theme from an exported configuration |
|
|
1080
1429
|
| `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`) |
|
|
1081
1430
|
| `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255) |
|
|
1082
|
-
| `glaze.color(input)` | Create a standalone color token |
|
|
1083
|
-
| `glaze.
|
|
1431
|
+
| `glaze.color(input, scaling?)` | Create a standalone color token from `{ hue, saturation, lightness, opacity?, contrast?, base?, name?, ... }`. Optional `scaling` overrides the lightness windows |
|
|
1432
|
+
| `glaze.color(value, overrides?, scaling?)` | Create a standalone color token from a hex string (3/6/8 digits), 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`, `contrast`, `opacity`, `name`, and `base` (a `GlazeColorToken` or any `GlazeColorValue`; raw values are auto-wrapped). When `base` is set, `contrast` and relative `lightness` are anchored to the base per scheme — see [Pairing Colors](#pairing-colors). Every input form defaults to `mode: 'auto'`; string inputs additionally preserve light lightness exactly and extend the dark window to `[lo, 100]`, while object / tuple / structured inputs snapshot both windows from `globalConfig.lightLightness` / `globalConfig.darkLightness`. |
|
|
1433
|
+
| `glaze.colorFrom(data)` | Rehydrate a `glaze.color()` token from a `.export()` snapshot. Inverse of `token.export()` — see [Persisting Standalone Colors](#persisting-standalone-colors) |
|
|
1434
|
+
| `glaze.shadow(input)` | Compute a standalone shadow color (returns `ResolvedColorVariant`). `bg` / `fg` accept any `GlazeColorValue` form |
|
|
1084
1435
|
| `glaze.format(variant, format?)` | Format any `ResolvedColorVariant` as a CSS string |
|
|
1085
1436
|
|
|
1086
1437
|
### Theme Methods
|
|
@@ -1098,7 +1449,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
|
|
|
1098
1449
|
| `theme.extend(options)` | Create a child theme |
|
|
1099
1450
|
| `theme.resolve()` | Resolve all colors |
|
|
1100
1451
|
| `theme.tokens(options?)` | Export as flat token map grouped by variant |
|
|
1101
|
-
| `theme.tasty(options?)` | Export as [Tasty](https://
|
|
1452
|
+
| `theme.tasty(options?)` | Export as [Tasty](https://tasty.style/docs) style-to-state bindings |
|
|
1102
1453
|
| `theme.json(options?)` | Export as plain JSON |
|
|
1103
1454
|
| `theme.css(options?)` | Export as CSS custom property declarations |
|
|
1104
1455
|
|
|
@@ -1107,7 +1458,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
|
|
|
1107
1458
|
| Method | Description |
|
|
1108
1459
|
|---|---|
|
|
1109
1460
|
| `glaze.configure(config)` | Set global configuration |
|
|
1110
|
-
| `glaze.palette(themes)` | Compose themes into a palette |
|
|
1461
|
+
| `glaze.palette(themes, options?)` | Compose themes into a palette (options: `{ primary? }`) |
|
|
1111
1462
|
| `glaze.getConfig()` | Get current global config |
|
|
1112
1463
|
| `glaze.resetConfig()` | Reset to defaults |
|
|
1113
1464
|
|