@tenphi/glaze 0.0.0-snapshot.304844 → 0.0.0-snapshot.3cd9458

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 CHANGED
@@ -603,7 +603,7 @@ Modes control how colors adapt across schemes:
603
603
 
604
604
  ```ts
605
605
  // Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
606
- // Dark: surface inverts to L≈29 (power curve), sign flips → L=29+52=81
606
+ // Dark: surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
607
607
  // contrast solver may push further (light text on dark bg)
608
608
  ```
609
609
 
@@ -639,15 +639,15 @@ Both `auto` and `fixed` modes use the same linear formula. `static` mode and hig
639
639
 
640
640
  ### Lightness
641
641
 
642
- **`auto`** — inverted with a power curve within the configured window:
642
+ **`auto`** — inverted with a Möbius transformation within the configured window:
643
643
 
644
644
  ```ts
645
645
  const [lo, hi] = darkLightness; // default: [15, 95]
646
- const d = (100 - lightness) / 100;
647
- const invertedL = lo + (hi - lo) * Math.pow(d, darkCurve); // darkCurve default: 0.5
646
+ const t = (100 - lightness) / 100;
647
+ const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5
648
648
  ```
649
649
 
650
- The `darkCurve` exponent (default `0.5`) expands small light-theme deltas near white into larger usable deltas in dark mode. This preserves subtle surface hierarchy (e.g. L=97 vs L=95) that would otherwise collapse to near-identical dark values. Set `darkCurve: 1` for linear (legacy) behavior.
650
+ 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. 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.
651
651
 
652
652
  **`fixed`** — mapped without inversion (not affected by `darkCurve`):
653
653
 
@@ -657,11 +657,11 @@ const mappedL = (lightness * (hi - lo)) / 100 + lo;
657
657
 
658
658
  | Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
659
659
  |---|---|---|---|---|
660
- | surface (L=97) | 97 | 28.9 | 17.4 | 92.6 |
661
- | accent-fill (L=52) | 52 | 70.4 | 53.4 | 56.6 |
660
+ | surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
661
+ | accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
662
662
  | accent-text (L=100) | 100 | 15 | 15 | 95 |
663
663
 
664
- In high-contrast variants, the `darkLightness` window is bypassed. Auto uses pure inversion (`100 - L`), fixed uses identity (`L`). This allows HC colors to reach the full 0–100 range.
664
+ In high-contrast variants, the `darkLightness` window is bypassed. Auto uses the Möbius curve over the full [0, 100] range with a squared beta (`darkCurve²`) for stronger expansion. Fixed uses identity (`L`). This allows HC colors to reach the full 0–100 range.
665
665
 
666
666
  ### Saturation
667
667
 
@@ -914,7 +914,7 @@ glaze.configure({
914
914
  lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
915
915
  darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
916
916
  darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
917
- darkCurve: 0.5, // Power-curve exponent for dark auto-inversion (0–1)
917
+ darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1, lower = more expansion)
918
918
  states: {
919
919
  dark: '@dark', // State alias for dark mode tokens
920
920
  highContrast: '@high-contrast',
package/dist/index.cjs CHANGED
@@ -967,28 +967,30 @@ function mapLightnessLight(l, mode, isHighContrast) {
967
967
  const [lo, hi] = globalConfig.lightLightness;
968
968
  return l * (hi - lo) / 100 + lo;
969
969
  }
970
+ function mobiusCurve(t, beta) {
971
+ if (beta >= 1) return t;
972
+ return t / (t + beta * (1 - t));
973
+ }
970
974
  function mapLightnessDark(l, mode, isHighContrast) {
971
975
  if (mode === "static") return l;
976
+ const beta = globalConfig.darkCurve;
972
977
  if (isHighContrast) {
973
978
  if (mode === "fixed") return l;
974
- const t = (100 - l) / 100;
975
- return 100 * Math.pow(t, globalConfig.darkCurve);
979
+ return 100 * mobiusCurve((100 - l) / 100, beta * beta);
976
980
  }
977
981
  const [darkLo, darkHi] = globalConfig.darkLightness;
978
982
  if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
979
983
  const [lightLo, lightHi] = globalConfig.lightLightness;
980
984
  const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
981
- return darkLo + (darkHi - darkLo) * Math.pow(t, globalConfig.darkCurve);
985
+ return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
982
986
  }
983
987
  function lightMappedToDark(lightL, isHighContrast) {
984
- if (isHighContrast) {
985
- const t = (100 - lightL) / 100;
986
- return 100 * Math.pow(t, globalConfig.darkCurve);
987
- }
988
+ const beta = globalConfig.darkCurve;
989
+ if (isHighContrast) return 100 * mobiusCurve((100 - lightL) / 100, beta * beta);
988
990
  const [lightLo, lightHi] = globalConfig.lightLightness;
989
991
  const [darkLo, darkHi] = globalConfig.darkLightness;
990
992
  const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
991
- return darkLo + (darkHi - darkLo) * Math.pow(t, globalConfig.darkCurve);
993
+ return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
992
994
  }
993
995
  function mapSaturationDark(s, mode) {
994
996
  if (mode === "static") return s;