@tenphi/glaze 0.0.0-snapshot.0fcd6cf → 0.0.0-snapshot.12fd135

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.
@@ -76,8 +76,8 @@ A few options cut across the shapes:
76
76
  `$value` objects, not CSS strings, so they ignore `format`. Use `colorSpace`
77
77
  instead: `'srgb'` (the default) or `'oklch'`.
78
78
  - **HDR.** `modes: { hdr: true }` (`css({ hdr: true })` for `css()`) adds the
79
- values of [HDR glows](./shadows-and-glows.mdx#hdr-glows) next to the SDR
80
- ones. It is off by default; see [HDR output](#hdr-output).
79
+ values of [HDR glows](./hdr.mdx) next to the SDR ones. It is off by
80
+ default; see [Output is opt-in](./hdr.mdx#output-is-opt-in).
81
81
 
82
82
  Here is one theme and the CSS custom properties `theme.css()` emits for it.
83
83
  Switch to the Output tab to see the declarations, wrapped the way the
@@ -626,222 +626,6 @@ a standalone color's `css()`. The full rules, including which colors stay
626
626
  inline, are in the
627
627
  [Output formats](./reference/output.md#hue-channel-splitting-splithue) reference.
628
628
 
629
- ## HDR output
630
-
631
- [HDR glows](./shadows-and-glows.mdx#hdr-glows) carry a second value for
632
- displays that can show brighter than white. Every export leaves it out until
633
- you turn on `modes.hdr`. Then each format adds it next to the SDR value it
634
- already emits, for the schemes the other `modes` include. The examples below
635
- use this theme:
636
-
637
- ```ts
638
- const theme = glaze(280, 80, { glowTuning: { headroom: 1 } });
639
-
640
- theme.colors({
641
- surface: { tone: 100 },
642
- 'surface-glow': { type: 'glow', bg: 'surface', intensity: 12 },
643
- near: { tone: 95, saturation: 0, mode: 'static' },
644
- 'near-glow': { type: 'glow', bg: 'near', intensity: 12 },
645
- });
646
-
647
- // For every export:
648
- glaze.configure({ modes: { hdr: true } });
649
- // Or per call:
650
- theme.tokens({ modes: { hdr: true } });
651
- theme.css({ hdr: true });
652
- ```
653
-
654
- `surface-glow` has an HDR value in the light schemes only; on the dark
655
- surface its step stays below white. `near-glow` has one in all four. HDR
656
- values are always `color(srgb-linear R G B)`, whatever `format` you pass, and
657
- stay literal under `splitHue`. Palettes apply prefixes, the `primary` alias,
658
- and collision filtering to them like any other entry. `glaze.color()` tokens
659
- are never glows, so the HDR options don't change their output.
660
-
661
- No browser reliably paints these values past white yet
662
- ([Browsers and native apps](./shadows-and-glows.mdx#browsers-and-native-apps)).
663
- Safari, and Chrome outside layers that hold HDR images, match
664
- `dynamic-range: high` on HDR-capable displays and clamp the value, so a
665
- neutral HDR glow paints opaque white there. That matches SDR for `near-glow`,
666
- which is already opaque white, but `surface-glow` is transparent in SDR, so
667
- its clamped value covers anything drawn between the surface and the glow.
668
- Wire the CSS, Tasty, and Tailwind tiers only for glows used as fills, edge
669
- highlights, or `box-shadow` colors
670
- ([Fills, not overlays](./shadows-and-glows.mdx#fills-not-overlays)). JSON and
671
- DTCG feed native apps, which draw HDR today.
672
-
673
- ### CSS blocks
674
-
675
- `css()` has no `modes`, so it takes `hdr` (default: the global `modes.hdr`)
676
- and returns four more blocks in `hdr`. Wire each one after the SDR blocks,
677
- under the same selector plus `(dynamic-range: high)`:
678
-
679
- ```ts
680
- const css = theme.css({ hdr: true });
681
- const hdr = css.hdr!;
682
- const dark = '(prefers-color-scheme: dark)';
683
- const more = '(prefers-contrast: more)';
684
- const high = '(dynamic-range: high)';
685
-
686
- const stylesheet = `
687
- :root { ${css.light} }
688
- @media ${dark} { :root { ${css.dark} } }
689
- @media ${more} { :root { ${css.lightContrast} } }
690
- @media ${dark} and ${more} { :root { ${css.darkContrast} } }
691
-
692
- @media ${high} { :root { ${hdr.light} } }
693
- @media ${dark} and ${high} { :root { ${hdr.dark} } }
694
- @media ${more} and ${high} { :root { ${hdr.lightContrast} } }
695
- @media ${dark} and ${more} and ${high} {
696
- :root { ${hdr.darkContrast} }
697
- }
698
- `;
699
- ```
700
-
701
- The blocks are dense. Each lists every color that has an HDR value in any
702
- scheme, and repeats a color's SDR declaration where its own scheme has none:
703
-
704
- ```css
705
- /* hdr.light */
706
- --surface-glow-color: color(srgb-linear 1.4631 1.4631 1.4631);
707
- --near-glow-color: color(srgb-linear 1.2494 1.2494 1.2494);
708
-
709
- /* hdr.dark */
710
- --surface-glow-color: oklch(1 0 0 / 0.1237);
711
- --near-glow-color: color(srgb-linear 1.2494 1.2494 1.2494);
712
- ```
713
-
714
- On a dark HDR display, both the light and the dark HDR blocks match. The dark
715
- one comes later, so its `surface-glow` line puts the dark SDR value back. A
716
- block that listed only HDR values would let the light one leak into dark.
717
- When no glow passes white, all four blocks are empty strings.
718
-
719
- ### Tasty keys
720
-
721
- `tasty()` adds a key under the `states.hdr` alias (default
722
- `'@media(dynamic-range: high)'`) right after each SDR key that has an HDR
723
- value:
724
-
725
- ```ts
726
- theme.tasty({ modes: { hdr: true } })['#surface-glow'];
727
- // → {
728
- // '': 'oklch(1 0 0 / 0)',
729
- // '@media(dynamic-range: high)':
730
- // 'color(srgb-linear 1.4631 1.4631 1.4631)',
731
- // '@media(prefers-color-scheme: dark)': 'oklch(1 0 0 / 0.1237)',
732
- // }
733
- ```
734
-
735
- Tasty gives later keys priority, so the order is what keeps the light HDR
736
- value out of dark: `''`, hdr, dark, dark & hdr, hc, hc & hdr, dark & hc,
737
- dark & hc & hdr, with only the keys that exist. Don't sort the keys if you
738
- post-process the map. With your own aliases, set `states.hdr` next to `dark`
739
- and `highContrast` (for example `'@hdr'`, which emits keys such as
740
- `'@dark & @hdr'`) and register it in Tasty like the others.
741
-
742
- ### Tailwind blocks
743
-
744
- `tailwind()` appends the same dense blocks after all its SDR blocks, under
745
- `hdrSelector` (default `'@media (dynamic-range: high)'`), around
746
- `darkSelector` and `highContrastSelector`:
747
-
748
- ```css
749
- /* ...the @theme and .dark blocks, unchanged... */
750
-
751
- @media (dynamic-range: high) {
752
- :root {
753
- --color-surface-glow: color(srgb-linear 1.4631 1.4631 1.4631);
754
- --color-near-glow: color(srgb-linear 1.2494 1.2494 1.2494);
755
- }
756
- }
757
-
758
- @media (dynamic-range: high) {
759
- .dark {
760
- --color-surface-glow: oklch(1 0 0 / 0.1237);
761
- --color-near-glow: color(srgb-linear 1.2494 1.2494 1.2494);
762
- }
763
- }
764
- ```
765
-
766
- ### JSON and tokens
767
-
768
- `json()` adds a sparse `<scheme>Hdr` key after each scheme that has an HDR
769
- value. `tokens()` adds a dense `<scheme>Hdr` map after each scheme it emits,
770
- always present and possibly empty:
771
-
772
- ```ts
773
- theme.json({ modes: { hdr: true } })['surface-glow'];
774
- // → {
775
- // light: 'oklch(1 0 0 / 0)',
776
- // lightHdr: 'color(srgb-linear 1.4631 1.4631 1.4631)',
777
- // dark: 'oklch(1 0 0 / 0.1237)',
778
- // }
779
-
780
- theme.tokens({ modes: { hdr: true } }).darkHdr;
781
- // → {
782
- // 'surface-glow': 'oklch(1 0 0 / 0.1237)',
783
- // 'near-glow': 'color(srgb-linear 1.2494 1.2494 1.2494)',
784
- // }
785
- ```
786
-
787
- A dense map drops into a cascade the way the CSS blocks do; the sparse JSON
788
- keys suit code that reads one color at a time.
789
-
790
- ### DTCG and native apps
791
-
792
- `dtcg()` and `dtcgResolver()` keep `$value` SDR, since the DTCG color format
793
- has no HDR space and its components stop at 1. The HDR value goes in
794
- `$extensions`, only in the scheme documents that have one. This is
795
- `light['surface-glow']`; `dark['surface-glow']` has no `$extensions`:
796
-
797
- ```json
798
- {
799
- "$type": "color",
800
- "$value": {
801
- "colorSpace": "srgb",
802
- "components": [1, 1, 1],
803
- "hex": "#ffffff",
804
- "alpha": 0
805
- },
806
- "$extensions": {
807
- "me.tenphi.glaze": {
808
- "hdr": {
809
- "exposure": 0.548987,
810
- "tone": 112,
811
- "base": {
812
- "colorSpace": "srgb",
813
- "components": [1, 1, 1],
814
- "hex": "#ffffff"
815
- },
816
- "value": {
817
- "colorSpace": "srgb-linear",
818
- "components": [1.463058, 1.463058, 1.463058]
819
- }
820
- }
821
- }
822
- }
823
- }
824
- ```
825
-
826
- - `base` is the opaque SDR color the exposure applies to: always a DTCG sRGB
827
- color with `hex`, whatever `colorSpace` you export, and never pastel.
828
- - `exposure` is the gain over `base` in stops.
829
- - `value` is `base × 2^exposure` as linear sRGB. Only its brightest component
830
- is guaranteed above 1.
831
- - `tone` is the HDR value's tone: past 100 when the headroom covers the step,
832
- though a saturated base that the cap stops early can land below.
833
-
834
- Native platforms take one of the two forms:
835
-
836
- - **UIKit and AppKit** (iOS and macOS 26):
837
- `UIColor(red:green:blue:alpha:exposure:)`, or its `NSColor` twin, with
838
- `base.components` and `exposure`.
839
- - **SwiftUI:** `Color(.sRGB, red:green:blue:)` from `base.components`, then
840
- `.exposureAdjust(exposure)`.
841
- - **Android:** `Color.pack(r, g, b, 1f, space)` from `value.components`, with
842
- `ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)` as the space, in a
843
- window set to `COLOR_MODE_HDR`.
844
-
845
629
  ## Next steps
846
630
 
847
631
  - [Migration and upgrading](./migration.md): move an existing color system to