@newtonedev/colors 0.0.1

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.
Files changed (88) hide show
  1. package/README.md +167 -0
  2. package/dist/__tests__/contrast.test.d.ts +2 -0
  3. package/dist/__tests__/contrast.test.d.ts.map +1 -0
  4. package/dist/__tests__/conversions.test.d.ts +2 -0
  5. package/dist/__tests__/conversions.test.d.ts.map +1 -0
  6. package/dist/__tests__/difference.test.d.ts +2 -0
  7. package/dist/__tests__/difference.test.d.ts.map +1 -0
  8. package/dist/__tests__/dynamic-range.test.d.ts +2 -0
  9. package/dist/__tests__/dynamic-range.test.d.ts.map +1 -0
  10. package/dist/__tests__/gamut.test.d.ts +2 -0
  11. package/dist/__tests__/gamut.test.d.ts.map +1 -0
  12. package/dist/__tests__/hue-grade.test.d.ts +2 -0
  13. package/dist/__tests__/hue-grade.test.d.ts.map +1 -0
  14. package/dist/__tests__/integration.test.d.ts +2 -0
  15. package/dist/__tests__/integration.test.d.ts.map +1 -0
  16. package/dist/__tests__/interpolation.test.d.ts +2 -0
  17. package/dist/__tests__/interpolation.test.d.ts.map +1 -0
  18. package/dist/__tests__/resolve-color.test.d.ts +2 -0
  19. package/dist/__tests__/resolve-color.test.d.ts.map +1 -0
  20. package/dist/__tests__/scale.test.d.ts +2 -0
  21. package/dist/__tests__/scale.test.d.ts.map +1 -0
  22. package/dist/color/difference.d.ts +6 -0
  23. package/dist/color/difference.d.ts.map +1 -0
  24. package/dist/color/interpolation.d.ts +10 -0
  25. package/dist/color/interpolation.d.ts.map +1 -0
  26. package/dist/config.d.ts +38 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/constants.d.ts +39 -0
  29. package/dist/constants.d.ts.map +1 -0
  30. package/dist/contrast/apca.d.ts +14 -0
  31. package/dist/contrast/apca.d.ts.map +1 -0
  32. package/dist/contrast/index.d.ts +3 -0
  33. package/dist/contrast/index.d.ts.map +1 -0
  34. package/dist/contrast/wcag.d.ts +14 -0
  35. package/dist/contrast/wcag.d.ts.map +1 -0
  36. package/dist/conversions/hex.d.ts +6 -0
  37. package/dist/conversions/hex.d.ts.map +1 -0
  38. package/dist/conversions/index.d.ts +7 -0
  39. package/dist/conversions/index.d.ts.map +1 -0
  40. package/dist/conversions/linear-p3.d.ts +6 -0
  41. package/dist/conversions/linear-p3.d.ts.map +1 -0
  42. package/dist/conversions/linear-srgb.d.ts +6 -0
  43. package/dist/conversions/linear-srgb.d.ts.map +1 -0
  44. package/dist/conversions/oklab.d.ts +6 -0
  45. package/dist/conversions/oklab.d.ts.map +1 -0
  46. package/dist/conversions/pipeline.d.ts +18 -0
  47. package/dist/conversions/pipeline.d.ts.map +1 -0
  48. package/dist/conversions/srgb.d.ts +10 -0
  49. package/dist/conversions/srgb.d.ts.map +1 -0
  50. package/dist/difference.d.ts +6 -0
  51. package/dist/difference.d.ts.map +1 -0
  52. package/dist/dynamic-range.d.ts +19 -0
  53. package/dist/dynamic-range.d.ts.map +1 -0
  54. package/dist/gamut/check.d.ts +6 -0
  55. package/dist/gamut/check.d.ts.map +1 -0
  56. package/dist/gamut/index.d.ts +4 -0
  57. package/dist/gamut/index.d.ts.map +1 -0
  58. package/dist/gamut/map.d.ts +8 -0
  59. package/dist/gamut/map.d.ts.map +1 -0
  60. package/dist/gamut/max-chroma.d.ts +8 -0
  61. package/dist/gamut/max-chroma.d.ts.map +1 -0
  62. package/dist/hue-grade.d.ts +66 -0
  63. package/dist/hue-grade.d.ts.map +1 -0
  64. package/dist/index.cjs +602 -0
  65. package/dist/index.cjs.map +1 -0
  66. package/dist/index.d.ts +25 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +553 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/interpolation.d.ts +10 -0
  71. package/dist/interpolation.d.ts.map +1 -0
  72. package/dist/resolve-color.d.ts +55 -0
  73. package/dist/resolve-color.d.ts.map +1 -0
  74. package/dist/scale/dynamic-range.d.ts +23 -0
  75. package/dist/scale/dynamic-range.d.ts.map +1 -0
  76. package/dist/scale/generate.d.ts +55 -0
  77. package/dist/scale/generate.d.ts.map +1 -0
  78. package/dist/scale/hue-grade.d.ts +67 -0
  79. package/dist/scale/hue-grade.d.ts.map +1 -0
  80. package/dist/scale/key-color.d.ts +53 -0
  81. package/dist/scale/key-color.d.ts.map +1 -0
  82. package/dist/scale/resolve-color.d.ts +55 -0
  83. package/dist/scale/resolve-color.d.ts.map +1 -0
  84. package/dist/scale.d.ts +61 -0
  85. package/dist/scale.d.ts.map +1 -0
  86. package/dist/types.d.ts +29 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/package.json +36 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Global hue grading — shifts each palette's base hue toward shared
3
+ * "light" and "dark" grade hues at the scale endpoints.
4
+ *
5
+ * Light grading fades from full at the lightest step to zero at GRADE_REACH.
6
+ * Dark grading fades from zero at (1 - GRADE_REACH) to full at the darkest step.
7
+ * In the overlap zone, both shifts are additive (commutative).
8
+ */
9
+ /** How far each global grade reaches into the scale (0–1, from its respective end). */
10
+ export declare const GRADE_REACH: number;
11
+ /** Maximum allowed intensity for global grading. */
12
+ export declare const MAX_GRADE_INTENSITY = 0.25;
13
+ /** How far a local (per-palette) grade reaches into the scale (0–1, from its end). */
14
+ export declare const LOCAL_GRADE_REACH: number;
15
+ /** Maximum allowed intensity for local grading. */
16
+ export declare const MAX_LOCAL_GRADE_INTENSITY = 0.5;
17
+ export interface HueGrade {
18
+ /** Target hue for the light end (0–360°). */
19
+ readonly lightHue: number;
20
+ /** Blend intensity for the light end (0–MAX_GRADE_INTENSITY). */
21
+ readonly lightIntensity: number;
22
+ /** Target hue for the dark end (0–360°). */
23
+ readonly darkHue: number;
24
+ /** Blend intensity for the dark end (0–MAX_GRADE_INTENSITY). */
25
+ readonly darkIntensity: number;
26
+ }
27
+ /**
28
+ * Compute the graded hue at position `t` in the scale.
29
+ *
30
+ * Uses vector interpolation in Cartesian space rather than angular deltas
31
+ * to avoid the shortest-arc discontinuity at 180° (where the direction
32
+ * of rotation flips abruptly). Each grade's displacement is computed as
33
+ * a vector offset from the base hue, and displacements are additive
34
+ * (commutative — order doesn't matter).
35
+ *
36
+ * @param baseHue The palette's own hue (0–360°).
37
+ * @param t Position in the scale: 0 = lightest, 1 = darkest.
38
+ * @param grade Hue grade configuration.
39
+ * @param reach How far each grade reaches into the scale (default: GRADE_REACH).
40
+ * @param maxIntensity Maximum intensity clamp (default: MAX_GRADE_INTENSITY).
41
+ * @returns The graded hue in degrees (0–360).
42
+ */
43
+ export declare function gradeHue(baseHue: number, t: number, grade: HueGrade, reach?: number, maxIntensity?: number): number;
44
+ /**
45
+ * Resolve the final hue at position `t`, applying global then local grading.
46
+ *
47
+ * This is the canonical composition: global grade (using GRADE_REACH /
48
+ * MAX_GRADE_INTENSITY) first, then local grade (using LOCAL_GRADE_REACH /
49
+ * MAX_LOCAL_GRADE_INTENSITY) on the already-shifted result.
50
+ *
51
+ * @param baseHue The palette's own hue (0–360°).
52
+ * @param t Position in the scale: 0 = lightest, 1 = darkest.
53
+ * @param globalGrade Global hue grade (shared across palettes), or undefined.
54
+ * @param localGrade Local hue grade (per-palette, typically one-sided), or undefined.
55
+ * @returns The fully graded hue in degrees (0–360).
56
+ */
57
+ export declare function resolveGradedHue(baseHue: number, t: number, globalGrade?: HueGrade, localGrade?: HueGrade): number;
58
+ /**
59
+ * Build a one-sided HueGrade (only light or dark active).
60
+ *
61
+ * @param hue Target hue for the active side (0–360°).
62
+ * @param intensity Blend intensity for the active side.
63
+ * @param side Which end of the scale to affect.
64
+ */
65
+ export declare function buildOneSidedGrade(hue: number, intensity: number, side: "light" | "dark"): HueGrade;
66
+ //# sourceMappingURL=hue-grade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hue-grade.d.ts","sourceRoot":"","sources":["../src/hue-grade.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uFAAuF;AACvF,eAAO,MAAM,WAAW,QAAQ,CAAC;AAEjC,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,WAAW,QAAQ;IACvB,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,QAAQ,EACf,KAAK,GAAE,MAAoB,EAC3B,YAAY,GAAE,MAA4B,GACzC,MAAM,CAoDR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,CAAC,EAAE,MAAM,EACT,WAAW,CAAC,EAAE,QAAQ,EACtB,UAAU,CAAC,EAAE,QAAQ,GACpB,MAAM,CAKR;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,QAAQ,CAIV"}
package/dist/index.cjs ADDED
@@ -0,0 +1,602 @@
1
+ 'use strict';
2
+
3
+ // src/constants.ts
4
+ var SRGB_GAMMA_THRESHOLD = 0.04045;
5
+ var SRGB_GAMMA_THRESHOLD_LINEAR = 31308e-7;
6
+ var SRGB_GAMMA_SLOPE = 12.92;
7
+ var SRGB_GAMMA_EXPONENT = 2.4;
8
+ var SRGB_GAMMA_OFFSET = 0.055;
9
+ var SRGB_GAMMA_SCALE = 1.055;
10
+ var LINEAR_SRGB_TO_LMS = [
11
+ [0.4122214708, 0.5363325363, 0.0514459929],
12
+ [0.2119034982, 0.6806995451, 0.1073969566],
13
+ [0.0883024619, 0.2817188376, 0.6299787005]
14
+ ];
15
+ var LMS_PRIME_TO_OKLAB = [
16
+ [0.2104542553, 0.793617785, -0.0040720468],
17
+ [1.9779984951, -2.428592205, 0.4505937099],
18
+ [0.0259040371, 0.7827717662, -0.808675766]
19
+ ];
20
+ var OKLAB_TO_LMS_PRIME = [
21
+ [1, 0.3963377774, 0.2158037573],
22
+ [1, -0.1055613458, -0.0638541728],
23
+ [1, -0.0894841775, -1.291485548]
24
+ ];
25
+ var LMS_TO_LINEAR_SRGB = [
26
+ [4.0767416621, -3.3077115913, 0.2309699292],
27
+ [-1.2684380046, 2.6097574011, -0.3413193965],
28
+ [-0.0041960863, -0.7034186147, 1.707614701]
29
+ ];
30
+ var LINEAR_P3_TO_LMS = [
31
+ [0.4813791595, 0.4621155872, 0.0565406381],
32
+ [0.2288319169, 0.6532167007, 0.1179528072],
33
+ [0.0839457149, 0.2241651052, 0.6918912614]
34
+ ];
35
+ var LMS_TO_LINEAR_P3 = [
36
+ [3.127769439, -2.2570600176, 0.1291828502],
37
+ [-1.0910091977, 2.4133065499, -0.3222615148],
38
+ [-0.0260108068, -0.5080402362, 1.5340494942]
39
+ ];
40
+ var GAMUT_EPSILON = 1e-6;
41
+ var GAMUT_MAP_JND = 0.02;
42
+ var GAMUT_MAP_EPSILON = 1e-4;
43
+ var GAMUT_MAP_MAX_ITERATIONS = 50;
44
+ var ACHROMATIC_THRESHOLD = 1e-10;
45
+ var WCAG_R = 0.2126;
46
+ var WCAG_G = 0.7152;
47
+ var WCAG_B = 0.0722;
48
+ var APCA_MAIN_TRC = 2.4;
49
+ var APCA_SRGB_R = 0.2126729;
50
+ var APCA_SRGB_G = 0.7151522;
51
+ var APCA_SRGB_B = 0.072175;
52
+ var APCA_NORM_BG = 0.56;
53
+ var APCA_NORM_TXT = 0.57;
54
+ var APCA_REV_TXT = 0.62;
55
+ var APCA_REV_BG = 0.65;
56
+ var APCA_BLK_THRS = 0.022;
57
+ var APCA_BLK_CLMP = 1.414;
58
+ var APCA_SCALE_BOW = 1.14;
59
+ var APCA_SCALE_WOB = 1.14;
60
+ var APCA_LO_BOW_OFFSET = 0.027;
61
+ var APCA_LO_WOB_OFFSET = 0.027;
62
+ var APCA_DELTA_Y_MIN = 5e-4;
63
+ var APCA_LO_CLIP = 0.1;
64
+ var DEG_TO_RAD = Math.PI / 180;
65
+ var RAD_TO_DEG = 180 / Math.PI;
66
+
67
+ // src/conversions/oklab.ts
68
+ function oklabToOklch(lab) {
69
+ const C = Math.sqrt(lab.a * lab.a + lab.b * lab.b);
70
+ if (C < ACHROMATIC_THRESHOLD) {
71
+ return { L: lab.L, C: 0, h: 0 };
72
+ }
73
+ let h = Math.atan2(lab.b, lab.a) * RAD_TO_DEG;
74
+ if (h < 0) h += 360;
75
+ return { L: lab.L, C, h };
76
+ }
77
+ function oklchToOklab(lch) {
78
+ const hRad = lch.h * DEG_TO_RAD;
79
+ return {
80
+ L: lch.L,
81
+ a: lch.C * Math.cos(hRad),
82
+ b: lch.C * Math.sin(hRad)
83
+ };
84
+ }
85
+
86
+ // src/conversions/linear-srgb.ts
87
+ function linearSrgbToOklab(color) {
88
+ const l = LINEAR_SRGB_TO_LMS[0][0] * color.r + LINEAR_SRGB_TO_LMS[0][1] * color.g + LINEAR_SRGB_TO_LMS[0][2] * color.b;
89
+ const m = LINEAR_SRGB_TO_LMS[1][0] * color.r + LINEAR_SRGB_TO_LMS[1][1] * color.g + LINEAR_SRGB_TO_LMS[1][2] * color.b;
90
+ const s = LINEAR_SRGB_TO_LMS[2][0] * color.r + LINEAR_SRGB_TO_LMS[2][1] * color.g + LINEAR_SRGB_TO_LMS[2][2] * color.b;
91
+ const lp = Math.cbrt(l);
92
+ const mp = Math.cbrt(m);
93
+ const sp = Math.cbrt(s);
94
+ return {
95
+ L: LMS_PRIME_TO_OKLAB[0][0] * lp + LMS_PRIME_TO_OKLAB[0][1] * mp + LMS_PRIME_TO_OKLAB[0][2] * sp,
96
+ a: LMS_PRIME_TO_OKLAB[1][0] * lp + LMS_PRIME_TO_OKLAB[1][1] * mp + LMS_PRIME_TO_OKLAB[1][2] * sp,
97
+ b: LMS_PRIME_TO_OKLAB[2][0] * lp + LMS_PRIME_TO_OKLAB[2][1] * mp + LMS_PRIME_TO_OKLAB[2][2] * sp
98
+ };
99
+ }
100
+ function oklabToLinearSrgb(color) {
101
+ const lp = OKLAB_TO_LMS_PRIME[0][0] * color.L + OKLAB_TO_LMS_PRIME[0][1] * color.a + OKLAB_TO_LMS_PRIME[0][2] * color.b;
102
+ const mp = OKLAB_TO_LMS_PRIME[1][0] * color.L + OKLAB_TO_LMS_PRIME[1][1] * color.a + OKLAB_TO_LMS_PRIME[1][2] * color.b;
103
+ const sp = OKLAB_TO_LMS_PRIME[2][0] * color.L + OKLAB_TO_LMS_PRIME[2][1] * color.a + OKLAB_TO_LMS_PRIME[2][2] * color.b;
104
+ const l = lp * lp * lp;
105
+ const m = mp * mp * mp;
106
+ const s = sp * sp * sp;
107
+ return {
108
+ r: LMS_TO_LINEAR_SRGB[0][0] * l + LMS_TO_LINEAR_SRGB[0][1] * m + LMS_TO_LINEAR_SRGB[0][2] * s,
109
+ g: LMS_TO_LINEAR_SRGB[1][0] * l + LMS_TO_LINEAR_SRGB[1][1] * m + LMS_TO_LINEAR_SRGB[1][2] * s,
110
+ b: LMS_TO_LINEAR_SRGB[2][0] * l + LMS_TO_LINEAR_SRGB[2][1] * m + LMS_TO_LINEAR_SRGB[2][2] * s
111
+ };
112
+ }
113
+
114
+ // src/conversions/linear-p3.ts
115
+ function linearP3ToOklab(color) {
116
+ const l = LINEAR_P3_TO_LMS[0][0] * color.r + LINEAR_P3_TO_LMS[0][1] * color.g + LINEAR_P3_TO_LMS[0][2] * color.b;
117
+ const m = LINEAR_P3_TO_LMS[1][0] * color.r + LINEAR_P3_TO_LMS[1][1] * color.g + LINEAR_P3_TO_LMS[1][2] * color.b;
118
+ const s = LINEAR_P3_TO_LMS[2][0] * color.r + LINEAR_P3_TO_LMS[2][1] * color.g + LINEAR_P3_TO_LMS[2][2] * color.b;
119
+ const lp = Math.cbrt(l);
120
+ const mp = Math.cbrt(m);
121
+ const sp = Math.cbrt(s);
122
+ return {
123
+ L: LMS_PRIME_TO_OKLAB[0][0] * lp + LMS_PRIME_TO_OKLAB[0][1] * mp + LMS_PRIME_TO_OKLAB[0][2] * sp,
124
+ a: LMS_PRIME_TO_OKLAB[1][0] * lp + LMS_PRIME_TO_OKLAB[1][1] * mp + LMS_PRIME_TO_OKLAB[1][2] * sp,
125
+ b: LMS_PRIME_TO_OKLAB[2][0] * lp + LMS_PRIME_TO_OKLAB[2][1] * mp + LMS_PRIME_TO_OKLAB[2][2] * sp
126
+ };
127
+ }
128
+ function oklabToLinearP3(color) {
129
+ const lp = OKLAB_TO_LMS_PRIME[0][0] * color.L + OKLAB_TO_LMS_PRIME[0][1] * color.a + OKLAB_TO_LMS_PRIME[0][2] * color.b;
130
+ const mp = OKLAB_TO_LMS_PRIME[1][0] * color.L + OKLAB_TO_LMS_PRIME[1][1] * color.a + OKLAB_TO_LMS_PRIME[1][2] * color.b;
131
+ const sp = OKLAB_TO_LMS_PRIME[2][0] * color.L + OKLAB_TO_LMS_PRIME[2][1] * color.a + OKLAB_TO_LMS_PRIME[2][2] * color.b;
132
+ const l = lp * lp * lp;
133
+ const m = mp * mp * mp;
134
+ const s = sp * sp * sp;
135
+ return {
136
+ r: LMS_TO_LINEAR_P3[0][0] * l + LMS_TO_LINEAR_P3[0][1] * m + LMS_TO_LINEAR_P3[0][2] * s,
137
+ g: LMS_TO_LINEAR_P3[1][0] * l + LMS_TO_LINEAR_P3[1][1] * m + LMS_TO_LINEAR_P3[1][2] * s,
138
+ b: LMS_TO_LINEAR_P3[2][0] * l + LMS_TO_LINEAR_P3[2][1] * m + LMS_TO_LINEAR_P3[2][2] * s
139
+ };
140
+ }
141
+
142
+ // src/conversions/srgb.ts
143
+ function srgbChannelToLinear(value) {
144
+ return value <= SRGB_GAMMA_THRESHOLD ? value / SRGB_GAMMA_SLOPE : ((value + SRGB_GAMMA_OFFSET) / SRGB_GAMMA_SCALE) ** SRGB_GAMMA_EXPONENT;
145
+ }
146
+ function linearChannelToSrgb(value) {
147
+ return value <= SRGB_GAMMA_THRESHOLD_LINEAR ? value * SRGB_GAMMA_SLOPE : SRGB_GAMMA_SCALE * value ** (1 / SRGB_GAMMA_EXPONENT) - SRGB_GAMMA_OFFSET;
148
+ }
149
+ function srgbToLinearSrgb(color) {
150
+ return {
151
+ r: srgbChannelToLinear(color.r),
152
+ g: srgbChannelToLinear(color.g),
153
+ b: srgbChannelToLinear(color.b)
154
+ };
155
+ }
156
+ function linearSrgbToSrgb(color) {
157
+ return {
158
+ r: linearChannelToSrgb(color.r),
159
+ g: linearChannelToSrgb(color.g),
160
+ b: linearChannelToSrgb(color.b)
161
+ };
162
+ }
163
+
164
+ // src/conversions/hex.ts
165
+ function hexToSrgb(hex) {
166
+ let h = hex.startsWith("#") ? hex.slice(1) : hex;
167
+ if (h.length === 3) {
168
+ h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
169
+ }
170
+ const n = parseInt(h, 16);
171
+ return {
172
+ r: (n >> 16 & 255) / 255,
173
+ g: (n >> 8 & 255) / 255,
174
+ b: (n & 255) / 255
175
+ };
176
+ }
177
+ function srgbToHex(color) {
178
+ const r = Math.round(Math.max(0, Math.min(1, color.r)) * 255);
179
+ const g = Math.round(Math.max(0, Math.min(1, color.g)) * 255);
180
+ const b = Math.round(Math.max(0, Math.min(1, color.b)) * 255);
181
+ return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)}`;
182
+ }
183
+
184
+ // src/conversions/pipeline.ts
185
+ function srgbToOklch(color) {
186
+ return oklabToOklch(linearSrgbToOklab(srgbToLinearSrgb(color)));
187
+ }
188
+ function oklchToSrgb(color) {
189
+ return linearSrgbToSrgb(oklabToLinearSrgb(oklchToOklab(color)));
190
+ }
191
+ function srgbToOklab(color) {
192
+ return linearSrgbToOklab(srgbToLinearSrgb(color));
193
+ }
194
+ function oklabToSrgb(color) {
195
+ return linearSrgbToSrgb(oklabToLinearSrgb(color));
196
+ }
197
+ function hexToOklch(hex) {
198
+ return srgbToOklch(hexToSrgb(hex));
199
+ }
200
+ function oklchToHex(color) {
201
+ const srgb = oklchToSrgb(color);
202
+ return srgbToHex({
203
+ r: Math.max(0, Math.min(1, srgb.r)),
204
+ g: Math.max(0, Math.min(1, srgb.g)),
205
+ b: Math.max(0, Math.min(1, srgb.b))
206
+ });
207
+ }
208
+ function p3ToOklch(color) {
209
+ return oklabToOklch(linearP3ToOklab(srgbToLinearSrgb(color)));
210
+ }
211
+ function oklchToP3(color) {
212
+ return linearSrgbToSrgb(oklabToLinearP3(oklchToOklab(color)));
213
+ }
214
+
215
+ // src/gamut/check.ts
216
+ function isInGamut(color) {
217
+ return color.r >= -GAMUT_EPSILON && color.r <= 1 + GAMUT_EPSILON && color.g >= -GAMUT_EPSILON && color.g <= 1 + GAMUT_EPSILON && color.b >= -GAMUT_EPSILON && color.b <= 1 + GAMUT_EPSILON;
218
+ }
219
+ function clampSrgb(color) {
220
+ return {
221
+ r: Math.max(0, Math.min(1, color.r)),
222
+ g: Math.max(0, Math.min(1, color.g)),
223
+ b: Math.max(0, Math.min(1, color.b))
224
+ };
225
+ }
226
+
227
+ // src/color/difference.ts
228
+ function deltaEOK(a, b) {
229
+ return deltaEOKLab(oklchToOklab(a), oklchToOklab(b));
230
+ }
231
+ function deltaEOKLab(a, b) {
232
+ const dL = a.L - b.L;
233
+ const da = a.a - b.a;
234
+ const db = a.b - b.b;
235
+ return Math.sqrt(dL * dL + da * da + db * db);
236
+ }
237
+
238
+ // src/gamut/map.ts
239
+ function gamutMap(color, gamut = "srgb") {
240
+ const toRgb = gamut === "display-p3" ? oklchToP3 : oklchToSrgb;
241
+ const rgbToOklab = gamut === "display-p3" ? (c) => linearP3ToOklab(srgbToLinearSrgb(c)) : (c) => linearSrgbToOklab(srgbToLinearSrgb(c));
242
+ const rgb = toRgb(color);
243
+ if (isInGamut(rgb)) return color;
244
+ if (color.L <= GAMUT_EPSILON) return { L: 0, C: 0, h: color.h };
245
+ if (color.L >= 1 - GAMUT_EPSILON) return { L: 1, C: 0, h: color.h };
246
+ let lo = 0;
247
+ let hi = color.C;
248
+ for (let i = 0; i < GAMUT_MAP_MAX_ITERATIONS; i++) {
249
+ const midC = (lo + hi) / 2;
250
+ const candidate = { L: color.L, C: midC, h: color.h };
251
+ const candidateRgb = toRgb(candidate);
252
+ if (isInGamut(candidateRgb)) {
253
+ lo = midC;
254
+ } else {
255
+ const clipped = clampSrgb(candidateRgb);
256
+ const clippedLab = rgbToOklab(clipped);
257
+ const candidateLab = oklchToOklab(candidate);
258
+ const dE = deltaEOKLab(clippedLab, candidateLab);
259
+ if (dE < GAMUT_MAP_JND) {
260
+ break;
261
+ }
262
+ hi = midC;
263
+ }
264
+ if (hi - lo < GAMUT_MAP_EPSILON) break;
265
+ }
266
+ return { L: color.L, C: lo, h: color.h };
267
+ }
268
+
269
+ // src/gamut/max-chroma.ts
270
+ function maxChroma(L, h, gamut = "srgb") {
271
+ if (L <= 0 || L >= 1) return 0;
272
+ const toRgb = gamut === "display-p3" ? oklchToP3 : oklchToSrgb;
273
+ let lo = 0;
274
+ let hi = gamut === "display-p3" ? 0.5 : 0.4;
275
+ for (let i = 0; i < GAMUT_MAP_MAX_ITERATIONS; i++) {
276
+ const mid = (lo + hi) / 2;
277
+ const color = { L, C: mid, h };
278
+ const rgb = toRgb(color);
279
+ if (isInGamut(rgb)) {
280
+ lo = mid;
281
+ } else {
282
+ hi = mid;
283
+ }
284
+ if (hi - lo < 1e-6) break;
285
+ }
286
+ return lo;
287
+ }
288
+
289
+ // src/contrast/wcag.ts
290
+ function wcagLuminance(color) {
291
+ return WCAG_R * srgbChannelToLinear(color.r) + WCAG_G * srgbChannelToLinear(color.g) + WCAG_B * srgbChannelToLinear(color.b);
292
+ }
293
+ function wcagContrast(a, b) {
294
+ const lA = wcagLuminance(a);
295
+ const lB = wcagLuminance(b);
296
+ const lighter = Math.max(lA, lB);
297
+ const darker = Math.min(lA, lB);
298
+ return (lighter + 0.05) / (darker + 0.05);
299
+ }
300
+ function contrastTextHex(background) {
301
+ const white = { r: 1, g: 1, b: 1 };
302
+ const black = { r: 0, g: 0, b: 0 };
303
+ return wcagContrast(white, background) > wcagContrast(black, background) ? "#ffffff" : "#000000";
304
+ }
305
+
306
+ // src/contrast/apca.ts
307
+ function apcaContrast(textColor, bgColor) {
308
+ let txtY = APCA_SRGB_R * textColor.r ** APCA_MAIN_TRC + APCA_SRGB_G * textColor.g ** APCA_MAIN_TRC + APCA_SRGB_B * textColor.b ** APCA_MAIN_TRC;
309
+ let bgY = APCA_SRGB_R * bgColor.r ** APCA_MAIN_TRC + APCA_SRGB_G * bgColor.g ** APCA_MAIN_TRC + APCA_SRGB_B * bgColor.b ** APCA_MAIN_TRC;
310
+ if (txtY < APCA_BLK_THRS) {
311
+ txtY += (APCA_BLK_THRS - txtY) ** APCA_BLK_CLMP;
312
+ }
313
+ if (bgY < APCA_BLK_THRS) {
314
+ bgY += (APCA_BLK_THRS - bgY) ** APCA_BLK_CLMP;
315
+ }
316
+ if (Math.abs(bgY - txtY) < APCA_DELTA_Y_MIN) return 0;
317
+ let sapc;
318
+ if (bgY > txtY) {
319
+ sapc = (bgY ** APCA_NORM_BG - txtY ** APCA_NORM_TXT) * APCA_SCALE_BOW;
320
+ return sapc < APCA_LO_CLIP ? 0 : (sapc - APCA_LO_BOW_OFFSET) * 100;
321
+ } else {
322
+ sapc = (bgY ** APCA_REV_BG - txtY ** APCA_REV_TXT) * APCA_SCALE_WOB;
323
+ return sapc > -APCA_LO_CLIP ? 0 : (sapc + APCA_LO_WOB_OFFSET) * 100;
324
+ }
325
+ }
326
+
327
+ // src/color/interpolation.ts
328
+ function mix(a, b, t) {
329
+ const L = a.L + (b.L - a.L) * t;
330
+ const C = a.C + (b.C - a.C) * t;
331
+ const aIsAchromatic = a.C < ACHROMATIC_THRESHOLD;
332
+ const bIsAchromatic = b.C < ACHROMATIC_THRESHOLD;
333
+ if (aIsAchromatic && bIsAchromatic) {
334
+ return { L, C, h: 0 };
335
+ }
336
+ const hA = aIsAchromatic ? b.h : a.h;
337
+ const hB = bIsAchromatic ? a.h : b.h;
338
+ let delta = hB - hA;
339
+ if (delta > 180) delta -= 360;
340
+ if (delta < -180) delta += 360;
341
+ let h = hA + delta * t;
342
+ if (h < 0) h += 360;
343
+ if (h >= 360) h -= 360;
344
+ return { L, C, h };
345
+ }
346
+
347
+ // src/config.ts
348
+ var MIN_LIGHTEST_L = 0.96;
349
+ var MAX_DARKEST_L = 0.16;
350
+ var GRADE_REACH = 3 / 5;
351
+ var MAX_GRADE_INTENSITY = 0.25;
352
+ var LOCAL_GRADE_REACH = 2 / 3;
353
+ var MAX_LOCAL_GRADE_INTENSITY = 0.5;
354
+ var PERCEPTUAL_JND = 0.02;
355
+
356
+ // src/scale/hue-grade.ts
357
+ function gradeHue(baseHue, t, grade, reach = GRADE_REACH, maxIntensity = MAX_GRADE_INTENSITY) {
358
+ const { lightHue, lightValue, darkHue, darkValue } = grade;
359
+ const li = Math.max(0, Math.min(maxIntensity, lightValue));
360
+ const di = Math.max(0, Math.min(maxIntensity, darkValue));
361
+ if (li === 0 && di === 0) return baseHue;
362
+ const lightInfluence = t <= reach ? 0.5 * (1 + Math.cos(Math.PI * t / reach)) : 0;
363
+ const darkInfluence = t >= 1 - reach ? 0.5 * (1 + Math.cos(Math.PI * (1 - t) / reach)) : 0;
364
+ const toRad = Math.PI / 180;
365
+ const baseRad = baseHue * toRad;
366
+ const bx = Math.cos(baseRad);
367
+ const by = Math.sin(baseRad);
368
+ let dx = 0;
369
+ let dy = 0;
370
+ if (lightInfluence > 0 && li > 0) {
371
+ const blend = lightInfluence * li;
372
+ const lRad = lightHue * toRad;
373
+ dx += blend * (Math.cos(lRad) - bx);
374
+ dy += blend * (Math.sin(lRad) - by);
375
+ }
376
+ if (darkInfluence > 0 && di > 0) {
377
+ const blend = darkInfluence * di;
378
+ const dRad = darkHue * toRad;
379
+ dx += blend * (Math.cos(dRad) - bx);
380
+ dy += blend * (Math.sin(dRad) - by);
381
+ }
382
+ const rx = bx + dx;
383
+ const ry = by + dy;
384
+ if (rx * rx + ry * ry < 1e-20) return baseHue;
385
+ return (Math.atan2(ry, rx) / toRad + 360) % 360;
386
+ }
387
+ function resolveGradedHue(baseHue, t, globalGrade, localGrade) {
388
+ let h = baseHue;
389
+ if (globalGrade) h = gradeHue(h, t, globalGrade);
390
+ if (localGrade) h = gradeHue(h, t, localGrade, LOCAL_GRADE_REACH, MAX_LOCAL_GRADE_INTENSITY);
391
+ return h;
392
+ }
393
+ function buildOneSidedGrade(hue, value, side) {
394
+ return side === "light" ? { lightHue: hue, lightValue: value, darkHue: 0, darkValue: 0 } : { lightHue: 0, lightValue: 0, darkHue: hue, darkValue: value };
395
+ }
396
+
397
+ // src/scale/generate.ts
398
+ function generateScale(options) {
399
+ const {
400
+ hue,
401
+ steps,
402
+ chroma,
403
+ gamut = "srgb",
404
+ lightest = 1,
405
+ darkest = 0,
406
+ grading,
407
+ gradient
408
+ } = options;
409
+ const chromaRatio = chroma?.value ?? 1;
410
+ const chromaPeak = chroma?.offset ?? 0.5;
411
+ const hueGrade = grading;
412
+ const localHueGrade = gradient ? buildOneSidedGrade(gradient.hue, gradient.value, gradient.direction) : void 0;
413
+ if (steps < 2) return [];
414
+ const ratio = Math.max(0, Math.min(1, chromaRatio));
415
+ const peak = Math.max(0, Math.min(1, chromaPeak));
416
+ const lightestL = Math.max(0, Math.min(1, lightest));
417
+ const darkestL = Math.max(0, Math.min(1, darkest));
418
+ const hueAt = (t) => resolveGradedHue(hue, t, hueGrade, localHueGrade);
419
+ if (peak === 0.5 || ratio === 0 || ratio >= 1) {
420
+ const scale2 = [];
421
+ for (let i = 0; i < steps; i++) {
422
+ const t = i / (steps - 1);
423
+ const L = lightestL - t * (lightestL - darkestL);
424
+ const h = hueAt(t);
425
+ const C = maxChroma(L, h, gamut) * ratio;
426
+ scale2.push({ L, C, h });
427
+ }
428
+ return scale2;
429
+ }
430
+ const N = 100;
431
+ let peakT = 0.5;
432
+ let peakBoundaryC = 0;
433
+ const boundarySamples = [];
434
+ for (let i = 0; i <= N; i++) {
435
+ const t = i / N;
436
+ const L = lightestL - t * (lightestL - darkestL);
437
+ const C = maxChroma(L, hueAt(t), gamut);
438
+ boundarySamples.push({ t, C });
439
+ if (C > peakBoundaryC) {
440
+ peakBoundaryC = C;
441
+ peakT = t;
442
+ }
443
+ }
444
+ const innerPeakC = peakBoundaryC * ratio;
445
+ let validMinT = peakT;
446
+ let validMaxT = peakT;
447
+ for (const { t, C } of boundarySamples) {
448
+ if (C >= innerPeakC - 1e-6) {
449
+ if (t < validMinT) validMinT = t;
450
+ if (t > validMaxT) validMaxT = t;
451
+ }
452
+ }
453
+ let targetT;
454
+ if (peak <= 0.5) {
455
+ targetT = validMinT + peak / 0.5 * (peakT - validMinT);
456
+ } else {
457
+ targetT = peakT + (peak - 0.5) / 0.5 * (validMaxT - peakT);
458
+ }
459
+ if (targetT <= 0 || targetT >= 1) {
460
+ targetT = peakT;
461
+ }
462
+ const scale = [];
463
+ for (let i = 0; i < steps; i++) {
464
+ const t = i / (steps - 1);
465
+ const L = lightestL - t * (lightestL - darkestL);
466
+ const h = hueAt(t);
467
+ let tWarped;
468
+ if (t <= targetT) {
469
+ tWarped = t * (peakT / targetT);
470
+ } else {
471
+ tWarped = peakT + (t - targetT) * ((1 - peakT) / (1 - targetT));
472
+ }
473
+ const Lwarped = lightestL - tWarped * (lightestL - darkestL);
474
+ const warpedC = maxChroma(Lwarped, hueAt(tWarped), gamut) * ratio;
475
+ const boundaryC = maxChroma(L, h, gamut);
476
+ const C = Math.min(warpedC, boundaryC);
477
+ scale.push({ L, C, h });
478
+ }
479
+ return scale;
480
+ }
481
+
482
+ // src/scale/resolve-color.ts
483
+ function resolveColor(input, gamut = "srgb") {
484
+ const isHex = typeof input === "string";
485
+ const original = isHex ? hexToOklch(input) : input;
486
+ let inGamut;
487
+ if (isHex) {
488
+ inGamut = true;
489
+ } else {
490
+ const rgb = gamut === "display-p3" ? oklchToP3(original) : oklchToSrgb(original);
491
+ inGamut = isInGamut(rgb);
492
+ }
493
+ const oklch = inGamut ? original : gamutMap(original, gamut);
494
+ const boundary = maxChroma(oklch.L, oklch.h, gamut);
495
+ const chromaRatio = boundary > 0 ? oklch.C / boundary : 0;
496
+ return {
497
+ oklch,
498
+ hex: oklchToHex(oklch),
499
+ wasRemapped: !inGamut,
500
+ original,
501
+ chromaShift: original.C - oklch.C,
502
+ hue: oklch.h,
503
+ chromaRatio: Math.min(1, chromaRatio)
504
+ };
505
+ }
506
+ function findNearest(target, scale) {
507
+ if (scale.length === 0) {
508
+ throw new Error("findNearest: scale must not be empty");
509
+ }
510
+ let bestIndex = 0;
511
+ let bestDistance = Infinity;
512
+ for (let i = 0; i < scale.length; i++) {
513
+ const d = deltaEOK(target, scale[i]);
514
+ if (d < bestDistance) {
515
+ bestDistance = d;
516
+ bestIndex = i;
517
+ }
518
+ }
519
+ return {
520
+ index: bestIndex,
521
+ color: scale[bestIndex],
522
+ distance: bestDistance
523
+ };
524
+ }
525
+
526
+ // src/scale/dynamic-range.ts
527
+ function resolveLightest(slider) {
528
+ const s = Math.max(0, Math.min(1, slider));
529
+ return MIN_LIGHTEST_L + s * (1 - MIN_LIGHTEST_L);
530
+ }
531
+ function resolveDarkest(slider) {
532
+ const s = Math.max(0, Math.min(1, slider));
533
+ return MAX_DARKEST_L * (1 - s);
534
+ }
535
+ function lightnessToScaleT(L, lightestL, darkestL) {
536
+ const range = lightestL - darkestL;
537
+ if (range <= 0) return 0.5;
538
+ return Math.max(0, Math.min(1, (lightestL - L) / range));
539
+ }
540
+
541
+ // src/scale/key-color.ts
542
+ function keyColor(color, options) {
543
+ const { gamut = "srgb", lightest = 1, darkest = 0 } = options ?? {};
544
+ const resolved = resolveColor(color, gamut);
545
+ const offset = lightnessToScaleT(resolved.oklch.L, lightest, darkest);
546
+ return {
547
+ hue: resolved.hue,
548
+ chroma: { value: resolved.chromaRatio, offset },
549
+ resolved
550
+ };
551
+ }
552
+
553
+ exports.GRADE_REACH = GRADE_REACH;
554
+ exports.LOCAL_GRADE_REACH = LOCAL_GRADE_REACH;
555
+ exports.MAX_DARKEST_L = MAX_DARKEST_L;
556
+ exports.MAX_GRADE_INTENSITY = MAX_GRADE_INTENSITY;
557
+ exports.MAX_LOCAL_GRADE_INTENSITY = MAX_LOCAL_GRADE_INTENSITY;
558
+ exports.MIN_LIGHTEST_L = MIN_LIGHTEST_L;
559
+ exports.PERCEPTUAL_JND = PERCEPTUAL_JND;
560
+ exports.apcaContrast = apcaContrast;
561
+ exports.buildOneSidedGrade = buildOneSidedGrade;
562
+ exports.clampSrgb = clampSrgb;
563
+ exports.contrastTextHex = contrastTextHex;
564
+ exports.deltaEOK = deltaEOK;
565
+ exports.deltaEOKLab = deltaEOKLab;
566
+ exports.findNearest = findNearest;
567
+ exports.gamutMap = gamutMap;
568
+ exports.generateScale = generateScale;
569
+ exports.gradeHue = gradeHue;
570
+ exports.hexToOklch = hexToOklch;
571
+ exports.hexToSrgb = hexToSrgb;
572
+ exports.isInGamut = isInGamut;
573
+ exports.keyColor = keyColor;
574
+ exports.lightnessToScaleT = lightnessToScaleT;
575
+ exports.linearChannelToSrgb = linearChannelToSrgb;
576
+ exports.linearP3ToOklab = linearP3ToOklab;
577
+ exports.linearSrgbToOklab = linearSrgbToOklab;
578
+ exports.linearSrgbToSrgb = linearSrgbToSrgb;
579
+ exports.maxChroma = maxChroma;
580
+ exports.mix = mix;
581
+ exports.oklabToLinearP3 = oklabToLinearP3;
582
+ exports.oklabToLinearSrgb = oklabToLinearSrgb;
583
+ exports.oklabToOklch = oklabToOklch;
584
+ exports.oklabToSrgb = oklabToSrgb;
585
+ exports.oklchToHex = oklchToHex;
586
+ exports.oklchToOklab = oklchToOklab;
587
+ exports.oklchToP3 = oklchToP3;
588
+ exports.oklchToSrgb = oklchToSrgb;
589
+ exports.p3ToOklch = p3ToOklch;
590
+ exports.resolveColor = resolveColor;
591
+ exports.resolveDarkest = resolveDarkest;
592
+ exports.resolveGradedHue = resolveGradedHue;
593
+ exports.resolveLightest = resolveLightest;
594
+ exports.srgbChannelToLinear = srgbChannelToLinear;
595
+ exports.srgbToHex = srgbToHex;
596
+ exports.srgbToLinearSrgb = srgbToLinearSrgb;
597
+ exports.srgbToOklab = srgbToOklab;
598
+ exports.srgbToOklch = srgbToOklch;
599
+ exports.wcagContrast = wcagContrast;
600
+ exports.wcagLuminance = wcagLuminance;
601
+ //# sourceMappingURL=index.cjs.map
602
+ //# sourceMappingURL=index.cjs.map