@konce-pt/backdrop 0.9.0 → 0.10.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/package.json +2 -2
- package/dist/color.d.ts +0 -101
- package/dist/color.js +0 -228
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konce-pt/backdrop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Animated backdrop core for Koncept UI — canvas renderers plus the maths behind two effects: a density-ramped grid of squares and a flowing current of light with an OKLCH colour ramp. Framework-free TypeScript; the colour maths lives in @konce-pt/color.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "konce.pt",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@konce-pt/color": "0.
|
|
40
|
+
"@konce-pt/color": "0.10.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"typescript": "~6.0.3"
|
package/dist/color.d.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Barwy wstęgi. Mieszanie idzie po współrzędnych biegunowych OKLab (czyli OKLCH), nie po
|
|
3
|
-
* składowych sRGB ani nawet nie po prostokątnym OKLab.
|
|
4
|
-
*
|
|
5
|
-
* Interpolacja sRGB prowadzi mieszankę przez błoto, bo miesza wartości po gammie. Prostokątny
|
|
6
|
-
* OKLab naprawia jasność, ale dla odcieni leżących naprzeciwko siebie — a takie są w rampie
|
|
7
|
-
* wstęgi — i tak przecina oś achromatyczną: w połowie przejścia z niebieskiego w żółty zostaje
|
|
8
|
-
* szarość dokładnie tam, gdzie wstęga świeci najmocniej. Dopiero rozbicie na jasność, nasycenie
|
|
9
|
-
* i kąt odcienia pozwala obejść koło dookoła z zachowanym nasyceniem.
|
|
10
|
-
*
|
|
11
|
-
* Konwersje wg modelu OKLab Björna Ottossona.
|
|
12
|
-
*/
|
|
13
|
-
/** Kolor w sRGB, składowe 0–255. */
|
|
14
|
-
export interface KptRgb {
|
|
15
|
-
r: number;
|
|
16
|
-
g: number;
|
|
17
|
-
b: number;
|
|
18
|
-
}
|
|
19
|
-
/** Kolor w OKLab: jasność `l` oraz para składowych chromatycznych. */
|
|
20
|
-
export interface KptOklab {
|
|
21
|
-
l: number;
|
|
22
|
-
a: number;
|
|
23
|
-
b: number;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Zapis heksadecymalny (`#rgb` albo `#rrggbb`, z krzyżykiem lub bez) na składowe.
|
|
27
|
-
* Zwraca `null` na wszystkim innym — kolor przychodzi z wejścia komponentu albo z konfiguratora,
|
|
28
|
-
* więc śmieci są normalnym przypadkiem, a nie wyjątkiem.
|
|
29
|
-
*/
|
|
30
|
-
export declare function parseHexColor(hex: string): KptRgb | null;
|
|
31
|
-
/**
|
|
32
|
-
* Dowolny zapis koloru CSS na składowe — także `oklch()`, `color-mix()` czy nazwa własna.
|
|
33
|
-
*
|
|
34
|
-
* Tokeny biblioteki są zapisane w OKLCH, a barwy wstęgi trzeba wymieszać, nie tylko podać dalej.
|
|
35
|
-
* Zamiast pisać własny parser każdej składni CSS, malujemy kolor na canvasie 1×1 i odczytujemy
|
|
36
|
-
* piksel: przeglądarka rozumie każdy zapis, który sama akceptuje, i nigdy nie rozejdzie się
|
|
37
|
-
* z tym, co widać na stronie.
|
|
38
|
-
*
|
|
39
|
-
* Zapis nieprawidłowy daje `null`, tak samo jak środowisko bez DOM. `fillStyle` odrzuca taką
|
|
40
|
-
* wartość po cichu, zostawiając poprzednią, więc sam odczyt piksela nie odróżniłby śmiecia od
|
|
41
|
-
* legalnej czerni — stąd dwa różne wartowniki: przy wartości nieprawidłowej każdy z nich zostaje
|
|
42
|
-
* na swoim miejscu i oba przebiegi dają inny wynik. Getter zwraca kolor już zserializowany,
|
|
43
|
-
* więc bramka nie potrzebuje odczytu piksela.
|
|
44
|
-
*/
|
|
45
|
-
export declare function readCssColor(value: string): KptRgb | null;
|
|
46
|
-
/** Składowe na `#rrggbb`. */
|
|
47
|
-
export declare function toHexColor(rgb: KptRgb): string;
|
|
48
|
-
export declare function srgbToOklab(rgb: KptRgb): KptOklab;
|
|
49
|
-
export declare function oklabToSrgb(lab: KptOklab): KptRgb;
|
|
50
|
-
/**
|
|
51
|
-
* Obrót odcienia o zadany kąt (stopnie) przy zachowanej jasności i nasyceniu.
|
|
52
|
-
*
|
|
53
|
-
* Stąd biorą się domyślne barwy wstęgi: paleta biblioteki jest neutralna, więc zamiast sięgać
|
|
54
|
-
* po tokeny statusowe (`info`, `success`) do celów dekoracyjnych, dwie towarzyszące barwy
|
|
55
|
-
* wyprowadzamy z koloru wiodącego. Wstęga zostaje w kolorach aplikacji niezależnie od motywu.
|
|
56
|
-
*/
|
|
57
|
-
export declare function shiftHue(rgb: KptRgb, degrees: number): KptRgb;
|
|
58
|
-
/**
|
|
59
|
-
* Kolor na zamkniętej rampie `colors` w miejscu `position`.
|
|
60
|
-
*
|
|
61
|
-
* Rampa jest cykliczna: ostatni przystanek wraca do pierwszego, bo barwy przesuwają się wzdłuż
|
|
62
|
-
* wstęgi bez końca i szew w środku kadru byłby widoczny. `position` poza zakresem 0–1 zawija się,
|
|
63
|
-
* więc wywołujący nie musi pilnować rosnącego czasu.
|
|
64
|
-
*
|
|
65
|
-
* Odcień idzie krótszym łukiem koła. Przystanek bez nasycenia przejmuje odcień sąsiada — inaczej
|
|
66
|
-
* przejście do szarości i z powrotem szarpnęłoby barwą, bo `atan2` na zerowym nasyceniu zwraca
|
|
67
|
-
* kąt przypadkowy.
|
|
68
|
-
*/
|
|
69
|
-
export declare function mixOklch(colors: readonly KptRgb[], position: number): KptRgb;
|
|
70
|
-
/**
|
|
71
|
-
* Jasność przeniesiona na powierzchnię bieżącego motywu z zachowaniem odległości od niej.
|
|
72
|
-
*
|
|
73
|
-
* Barwa zapisana na biel odsuwa się od niej o `d`; na ciemnej powierzchni ma się odsunąć o to samo
|
|
74
|
-
* `d`, tyle że w drugą stronę — bo tam właśnie jest miejsce. Stąd czerń na bieli staje się bielą
|
|
75
|
-
* na ciemnym tle, a biel staje się dokładnie powierzchnią. Barwa w połowie skali zostaje mniej
|
|
76
|
-
* więcej tam, gdzie była, i dalej odcina się od tła.
|
|
77
|
-
*
|
|
78
|
-
* Na jasnej powierzchni wychodzi tożsamość: `1 - (1 - L)` to `L`.
|
|
79
|
-
*/
|
|
80
|
-
export declare function adaptLightness(lightness: number, surface: number): number;
|
|
81
|
-
/**
|
|
82
|
-
* Barwa przeniesiona na zadaną powierzchnię. Rusza wyłącznie jasność — odcień i nasycenie zostają,
|
|
83
|
-
* więc barwa marki nie zmienia się w inną barwę, tylko dobiera jasność do tła.
|
|
84
|
-
*/
|
|
85
|
-
export declare function adaptToSurface(rgb: KptRgb, surface: KptRgb): KptRgb;
|
|
86
|
-
/**
|
|
87
|
-
* Jasność względna wg WCAG (0 = czerń, 1 = biel).
|
|
88
|
-
*
|
|
89
|
-
* To nie to samo co jasność OKLab: OKLab opisuje, jak jasno barwa *wygląda*, a ta liczba mówi,
|
|
90
|
-
* ile z niej wychodzi światła — i tylko na niej stoi rachunek kontrastu, którym rozstrzygamy,
|
|
91
|
-
* czy na danym tle czytelniejszy jest napis ciemny czy jasny.
|
|
92
|
-
*/
|
|
93
|
-
export declare function relativeLuminance(rgb: KptRgb): number;
|
|
94
|
-
/**
|
|
95
|
-
* Czy na tej powierzchni czytelniejsza jest treść ciemna.
|
|
96
|
-
*
|
|
97
|
-
* Sekcja z własnym tłem przestaje być powierzchnią strony, więc `--kpt-color-on-surface` opisuje
|
|
98
|
-
* już nie to tło, tylko sąsiada. Bez tego rachunku treść w sekcji z ciemnym tłem w motywie jasnym
|
|
99
|
-
* wychodzi czarna na czarnym.
|
|
100
|
-
*/
|
|
101
|
-
export declare function prefersDarkContent(surface: KptRgb): boolean;
|
package/dist/color.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Barwy wstęgi. Mieszanie idzie po współrzędnych biegunowych OKLab (czyli OKLCH), nie po
|
|
3
|
-
* składowych sRGB ani nawet nie po prostokątnym OKLab.
|
|
4
|
-
*
|
|
5
|
-
* Interpolacja sRGB prowadzi mieszankę przez błoto, bo miesza wartości po gammie. Prostokątny
|
|
6
|
-
* OKLab naprawia jasność, ale dla odcieni leżących naprzeciwko siebie — a takie są w rampie
|
|
7
|
-
* wstęgi — i tak przecina oś achromatyczną: w połowie przejścia z niebieskiego w żółty zostaje
|
|
8
|
-
* szarość dokładnie tam, gdzie wstęga świeci najmocniej. Dopiero rozbicie na jasność, nasycenie
|
|
9
|
-
* i kąt odcienia pozwala obejść koło dookoła z zachowanym nasyceniem.
|
|
10
|
-
*
|
|
11
|
-
* Konwersje wg modelu OKLab Björna Ottossona.
|
|
12
|
-
*/
|
|
13
|
-
const TAU = Math.PI * 2;
|
|
14
|
-
const clamp255 = (value) => (value < 0 ? 0 : value > 255 ? 255 : Math.round(value));
|
|
15
|
-
/** Gamma sRGB → liniowo. */
|
|
16
|
-
const toLinear = (channel) => {
|
|
17
|
-
const value = channel / 255;
|
|
18
|
-
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
19
|
-
};
|
|
20
|
-
/** Liniowo → gamma sRGB. */
|
|
21
|
-
const toGamma = (value) => {
|
|
22
|
-
const encoded = value <= 0.0031308 ? 12.92 * value : 1.055 * value ** (1 / 2.4) - 0.055;
|
|
23
|
-
return clamp255(encoded * 255);
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Zapis heksadecymalny (`#rgb` albo `#rrggbb`, z krzyżykiem lub bez) na składowe.
|
|
27
|
-
* Zwraca `null` na wszystkim innym — kolor przychodzi z wejścia komponentu albo z konfiguratora,
|
|
28
|
-
* więc śmieci są normalnym przypadkiem, a nie wyjątkiem.
|
|
29
|
-
*/
|
|
30
|
-
export function parseHexColor(hex) {
|
|
31
|
-
const value = hex.trim().replace(/^#/, '');
|
|
32
|
-
const full = value.length === 3 ? value.replace(/./g, (char) => char + char) : value;
|
|
33
|
-
if (!/^[0-9a-f]{6}$/i.test(full))
|
|
34
|
-
return null;
|
|
35
|
-
return {
|
|
36
|
-
r: Number.parseInt(full.slice(0, 2), 16),
|
|
37
|
-
g: Number.parseInt(full.slice(2, 4), 16),
|
|
38
|
-
b: Number.parseInt(full.slice(4, 6), 16),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/** Leniwy kontekst 1×1 — służy wyłącznie do pytania przeglądarki o znaczenie zapisu koloru. */
|
|
42
|
-
let probe;
|
|
43
|
-
/**
|
|
44
|
-
* Dowolny zapis koloru CSS na składowe — także `oklch()`, `color-mix()` czy nazwa własna.
|
|
45
|
-
*
|
|
46
|
-
* Tokeny biblioteki są zapisane w OKLCH, a barwy wstęgi trzeba wymieszać, nie tylko podać dalej.
|
|
47
|
-
* Zamiast pisać własny parser każdej składni CSS, malujemy kolor na canvasie 1×1 i odczytujemy
|
|
48
|
-
* piksel: przeglądarka rozumie każdy zapis, który sama akceptuje, i nigdy nie rozejdzie się
|
|
49
|
-
* z tym, co widać na stronie.
|
|
50
|
-
*
|
|
51
|
-
* Zapis nieprawidłowy daje `null`, tak samo jak środowisko bez DOM. `fillStyle` odrzuca taką
|
|
52
|
-
* wartość po cichu, zostawiając poprzednią, więc sam odczyt piksela nie odróżniłby śmiecia od
|
|
53
|
-
* legalnej czerni — stąd dwa różne wartowniki: przy wartości nieprawidłowej każdy z nich zostaje
|
|
54
|
-
* na swoim miejscu i oba przebiegi dają inny wynik. Getter zwraca kolor już zserializowany,
|
|
55
|
-
* więc bramka nie potrzebuje odczytu piksela.
|
|
56
|
-
*/
|
|
57
|
-
export function readCssColor(value) {
|
|
58
|
-
const direct = parseHexColor(value);
|
|
59
|
-
if (direct)
|
|
60
|
-
return direct;
|
|
61
|
-
if (typeof document === 'undefined')
|
|
62
|
-
return null;
|
|
63
|
-
if (probe === undefined) {
|
|
64
|
-
const canvas = document.createElement('canvas');
|
|
65
|
-
canvas.width = 1;
|
|
66
|
-
canvas.height = 1;
|
|
67
|
-
probe = canvas.getContext('2d', { willReadFrequently: true });
|
|
68
|
-
}
|
|
69
|
-
if (!probe)
|
|
70
|
-
return null;
|
|
71
|
-
probe.fillStyle = '#000000';
|
|
72
|
-
probe.fillStyle = value;
|
|
73
|
-
const first = probe.fillStyle;
|
|
74
|
-
probe.fillStyle = '#ffffff';
|
|
75
|
-
probe.fillStyle = value;
|
|
76
|
-
if (probe.fillStyle !== first)
|
|
77
|
-
return null;
|
|
78
|
-
// Sonda musi zaczynać od przezroczystości: barwa z kanałem alfa nałożyłaby się na poprzedni
|
|
79
|
-
// odczyt i wyszłaby mieszanka dwóch niezwiązanych ze sobą wywołań.
|
|
80
|
-
probe.clearRect(0, 0, 1, 1);
|
|
81
|
-
probe.fillRect(0, 0, 1, 1);
|
|
82
|
-
const [r, g, b] = probe.getImageData(0, 0, 1, 1).data;
|
|
83
|
-
return { r, g, b };
|
|
84
|
-
}
|
|
85
|
-
/** Składowe na `#rrggbb`. */
|
|
86
|
-
export function toHexColor(rgb) {
|
|
87
|
-
const part = (value) => clamp255(value).toString(16).padStart(2, '0');
|
|
88
|
-
return `#${part(rgb.r)}${part(rgb.g)}${part(rgb.b)}`;
|
|
89
|
-
}
|
|
90
|
-
export function srgbToOklab(rgb) {
|
|
91
|
-
const r = toLinear(rgb.r);
|
|
92
|
-
const g = toLinear(rgb.g);
|
|
93
|
-
const b = toLinear(rgb.b);
|
|
94
|
-
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
95
|
-
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
96
|
-
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
97
|
-
return {
|
|
98
|
-
l: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
99
|
-
a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
100
|
-
b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
export function oklabToSrgb(lab) {
|
|
104
|
-
const l = (lab.l + 0.3963377774 * lab.a + 0.2158037573 * lab.b) ** 3;
|
|
105
|
-
const m = (lab.l - 0.1055613458 * lab.a - 0.0638541728 * lab.b) ** 3;
|
|
106
|
-
const s = (lab.l - 0.0894841775 * lab.a - 1.291485548 * lab.b) ** 3;
|
|
107
|
-
return {
|
|
108
|
-
r: toGamma(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
|
109
|
-
g: toGamma(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
|
110
|
-
b: toGamma(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Obrót odcienia o zadany kąt (stopnie) przy zachowanej jasności i nasyceniu.
|
|
115
|
-
*
|
|
116
|
-
* Stąd biorą się domyślne barwy wstęgi: paleta biblioteki jest neutralna, więc zamiast sięgać
|
|
117
|
-
* po tokeny statusowe (`info`, `success`) do celów dekoracyjnych, dwie towarzyszące barwy
|
|
118
|
-
* wyprowadzamy z koloru wiodącego. Wstęga zostaje w kolorach aplikacji niezależnie od motywu.
|
|
119
|
-
*/
|
|
120
|
-
export function shiftHue(rgb, degrees) {
|
|
121
|
-
const lab = srgbToOklab(rgb);
|
|
122
|
-
const chroma = Math.hypot(lab.a, lab.b);
|
|
123
|
-
const hue = Math.atan2(lab.b, lab.a) + (degrees / 360) * TAU;
|
|
124
|
-
return oklabToSrgb({ l: lab.l, a: Math.cos(hue) * chroma, b: Math.sin(hue) * chroma });
|
|
125
|
-
}
|
|
126
|
-
/** Poniżej tego nasycenia odcień przestaje cokolwiek znaczyć — szarość nie ma kąta. */
|
|
127
|
-
const ACHROMATIC = 1e-4;
|
|
128
|
-
/**
|
|
129
|
-
* Kolor na zamkniętej rampie `colors` w miejscu `position`.
|
|
130
|
-
*
|
|
131
|
-
* Rampa jest cykliczna: ostatni przystanek wraca do pierwszego, bo barwy przesuwają się wzdłuż
|
|
132
|
-
* wstęgi bez końca i szew w środku kadru byłby widoczny. `position` poza zakresem 0–1 zawija się,
|
|
133
|
-
* więc wywołujący nie musi pilnować rosnącego czasu.
|
|
134
|
-
*
|
|
135
|
-
* Odcień idzie krótszym łukiem koła. Przystanek bez nasycenia przejmuje odcień sąsiada — inaczej
|
|
136
|
-
* przejście do szarości i z powrotem szarpnęłoby barwą, bo `atan2` na zerowym nasyceniu zwraca
|
|
137
|
-
* kąt przypadkowy.
|
|
138
|
-
*/
|
|
139
|
-
export function mixOklch(colors, position) {
|
|
140
|
-
if (colors.length === 0)
|
|
141
|
-
return { r: 0, g: 0, b: 0 };
|
|
142
|
-
if (colors.length === 1)
|
|
143
|
-
return colors[0];
|
|
144
|
-
const wrapped = position - Math.floor(position);
|
|
145
|
-
const scaled = wrapped * colors.length;
|
|
146
|
-
const index = Math.min(Math.floor(scaled), colors.length - 1);
|
|
147
|
-
const blend = scaled - index;
|
|
148
|
-
const from = srgbToOklab(colors[index]);
|
|
149
|
-
const to = srgbToOklab(colors[(index + 1) % colors.length]);
|
|
150
|
-
const fromChroma = Math.hypot(from.a, from.b);
|
|
151
|
-
const toChroma = Math.hypot(to.a, to.b);
|
|
152
|
-
const chroma = fromChroma + (toChroma - fromChroma) * blend;
|
|
153
|
-
let hue;
|
|
154
|
-
if (fromChroma < ACHROMATIC && toChroma < ACHROMATIC) {
|
|
155
|
-
hue = 0;
|
|
156
|
-
}
|
|
157
|
-
else if (fromChroma < ACHROMATIC) {
|
|
158
|
-
hue = Math.atan2(to.b, to.a);
|
|
159
|
-
}
|
|
160
|
-
else if (toChroma < ACHROMATIC) {
|
|
161
|
-
hue = Math.atan2(from.b, from.a);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
const start = Math.atan2(from.b, from.a);
|
|
165
|
-
const end = Math.atan2(to.b, to.a);
|
|
166
|
-
// Krótszy łuk: różnicę sprowadzamy do (-π, π], zamiast iść dookoła przez pół koła.
|
|
167
|
-
const delta = ((end - start + Math.PI * 3) % TAU) - Math.PI;
|
|
168
|
-
hue = start + delta * blend;
|
|
169
|
-
}
|
|
170
|
-
return oklabToSrgb({
|
|
171
|
-
l: from.l + (to.l - from.l) * blend,
|
|
172
|
-
a: Math.cos(hue) * chroma,
|
|
173
|
-
b: Math.sin(hue) * chroma,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Jasność powierzchni, względem której zapisano barwę. Biblioteka trzyma motyw jasny w `:root`,
|
|
178
|
-
* a ciemny jako nadpisanie, więc wartość wpisana do szablonu jest z założenia dobrana do jasnego —
|
|
179
|
-
* i to jest jedyne założenie, jakie robi adaptacja.
|
|
180
|
-
*/
|
|
181
|
-
const REFERENCE_SURFACE = 1;
|
|
182
|
-
/** Powyżej tej jasności powierzchnię uznajemy za jasną, poniżej za ciemną. */
|
|
183
|
-
const SURFACE_MIDPOINT = 0.5;
|
|
184
|
-
const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);
|
|
185
|
-
/**
|
|
186
|
-
* Jasność przeniesiona na powierzchnię bieżącego motywu z zachowaniem odległości od niej.
|
|
187
|
-
*
|
|
188
|
-
* Barwa zapisana na biel odsuwa się od niej o `d`; na ciemnej powierzchni ma się odsunąć o to samo
|
|
189
|
-
* `d`, tyle że w drugą stronę — bo tam właśnie jest miejsce. Stąd czerń na bieli staje się bielą
|
|
190
|
-
* na ciemnym tle, a biel staje się dokładnie powierzchnią. Barwa w połowie skali zostaje mniej
|
|
191
|
-
* więcej tam, gdzie była, i dalej odcina się od tła.
|
|
192
|
-
*
|
|
193
|
-
* Na jasnej powierzchni wychodzi tożsamość: `1 - (1 - L)` to `L`.
|
|
194
|
-
*/
|
|
195
|
-
export function adaptLightness(lightness, surface) {
|
|
196
|
-
const distance = Math.abs(REFERENCE_SURFACE - lightness);
|
|
197
|
-
return clamp01(surface > SURFACE_MIDPOINT ? REFERENCE_SURFACE - distance : surface + distance);
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Barwa przeniesiona na zadaną powierzchnię. Rusza wyłącznie jasność — odcień i nasycenie zostają,
|
|
201
|
-
* więc barwa marki nie zmienia się w inną barwę, tylko dobiera jasność do tła.
|
|
202
|
-
*/
|
|
203
|
-
export function adaptToSurface(rgb, surface) {
|
|
204
|
-
const lab = srgbToOklab(rgb);
|
|
205
|
-
return oklabToSrgb({ ...lab, l: adaptLightness(lab.l, srgbToOklab(surface).l) });
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Jasność względna wg WCAG (0 = czerń, 1 = biel).
|
|
209
|
-
*
|
|
210
|
-
* To nie to samo co jasność OKLab: OKLab opisuje, jak jasno barwa *wygląda*, a ta liczba mówi,
|
|
211
|
-
* ile z niej wychodzi światła — i tylko na niej stoi rachunek kontrastu, którym rozstrzygamy,
|
|
212
|
-
* czy na danym tle czytelniejszy jest napis ciemny czy jasny.
|
|
213
|
-
*/
|
|
214
|
-
export function relativeLuminance(rgb) {
|
|
215
|
-
return 0.2126 * toLinear(rgb.r) + 0.7152 * toLinear(rgb.g) + 0.0722 * toLinear(rgb.b);
|
|
216
|
-
}
|
|
217
|
-
/** Próg WCAG na wybór treści ciemnej albo jasnej — powyżej niego ciemna wygrywa kontrastem. */
|
|
218
|
-
const CONTENT_THRESHOLD = 0.179;
|
|
219
|
-
/**
|
|
220
|
-
* Czy na tej powierzchni czytelniejsza jest treść ciemna.
|
|
221
|
-
*
|
|
222
|
-
* Sekcja z własnym tłem przestaje być powierzchnią strony, więc `--kpt-color-on-surface` opisuje
|
|
223
|
-
* już nie to tło, tylko sąsiada. Bez tego rachunku treść w sekcji z ciemnym tłem w motywie jasnym
|
|
224
|
-
* wychodzi czarna na czarnym.
|
|
225
|
-
*/
|
|
226
|
-
export function prefersDarkContent(surface) {
|
|
227
|
-
return relativeLuminance(surface) > CONTENT_THRESHOLD;
|
|
228
|
-
}
|