@konce-pt/backdrop 0.8.5
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/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/color.d.ts +101 -0
- package/dist/color.js +228 -0
- package/dist/hash.d.ts +12 -0
- package/dist/hash.js +19 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5 -0
- package/dist/renderer.d.ts +176 -0
- package/dist/renderer.js +348 -0
- package/dist/signal-grid.d.ts +35 -0
- package/dist/signal-grid.js +88 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.js +1 -0
- package/dist/wave.d.ts +58 -0
- package/dist/wave.js +107 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 konce.pt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://ui.konce.pt/images/koncept_ui_lib.png" alt="Koncept UI — Angular component library" width="100%" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @konce-pt/backdrop
|
|
6
|
+
|
|
7
|
+
Animowane tło [Koncept UI](https://gitlab.com/konce-pt/koncept-ui) to dekoracyjna warstwa pod
|
|
8
|
+
sekcję — rysowana na canvasie zamiast wczytywana jako obraz, więc nie kosztuje transferu, skaluje
|
|
9
|
+
się bez rozdzielczości i bierze kolory z tokenów motywu.
|
|
10
|
+
|
|
11
|
+
Ta paczka jest jego rdzeniem: układ siatki i rampa gęstości efektu **signal-grid**, płynąca wstęga
|
|
12
|
+
i rampa barw efektu **wave**, oraz renderery canvas dla obu. Czysty TypeScript, **zero zależności
|
|
13
|
+
runtime**, bez wiązania z frameworkiem.
|
|
14
|
+
|
|
15
|
+
Zwykle nie instalujesz go samodzielnie: używa go `KptBackdrop` z `@konce-pt/angular`
|
|
16
|
+
i z `@konce-pt/react`. Sięgnij po niego wprost, gdy rysujesz ten sam efekt własnym rendererem.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm i @konce-pt/backdrop
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createBackdropRenderer } from '@konce-pt/backdrop';
|
|
24
|
+
|
|
25
|
+
// Renderer żyje tyle, co jeden zestaw ustawień — zmiana wejść buduje nowy.
|
|
26
|
+
const renderer = createBackdropRenderer({
|
|
27
|
+
effect: 'wave',
|
|
28
|
+
colors: ['#5B8AF2', '#A86DF2', '#F25B9E'],
|
|
29
|
+
rotation: 12,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
renderer.resize(1200, 480); // przy starcie i przy zmianie rozmiaru
|
|
33
|
+
renderer.draw(context, 3.4); // klatka dla 3,4 sekundy animacji
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Dwa efekty, jedna zasada
|
|
37
|
+
|
|
38
|
+
**signal-grid** bierze gradient z tego, **ile** kwadratów się pali w danej odległości od kotwicy —
|
|
39
|
+
nie przyciemnia gotowej, równomiernej siatki. **wave** opada od linii środkowej krzywą Gaussa,
|
|
40
|
+
więc w całym kadrze nie ma ani jednej twardej krawędzi; to właśnie odróżnia prąd światła od paska.
|
|
41
|
+
|
|
42
|
+
W obu wypadkach losowania wyprowadzają się z pozycji, a nie z zapamiętanego stanu, więc zmiana
|
|
43
|
+
rozmiaru okna zostawia obraz rozpoznawalnym zamiast tasować go od nowa.
|
|
44
|
+
|
|
45
|
+
## Barwy mieszają się dookoła koła
|
|
46
|
+
|
|
47
|
+
Rampa wstęgi jest zamknięta w pętlę i mieszana po biegunowej postaci OKLab. Interpolacja sRGB
|
|
48
|
+
miesza wartości po gammie i wychodzi błotem; nawet prostokątny OKLab przecina oś achromatyczną dla
|
|
49
|
+
przeciwnych odcieni, więc w połowie drogi z niebieskiego w żółty zostaje szarość — dokładnie tam,
|
|
50
|
+
gdzie prąd świeci najmocniej.
|
|
51
|
+
|
|
52
|
+
## Wstęga liczy się w małym buforze
|
|
53
|
+
|
|
54
|
+
Efekt nie ma detalu, tylko miękkość. Liczymy go więc w buforze o dłuższym boku 160 px i skalujemy
|
|
55
|
+
w górę — rozmycie robi za darmo interpolacja przeglądarki. Kilka tysięcy pikseli na klatkę
|
|
56
|
+
obsługuje kadr dowolnej wielkości.
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
- **Renderery** — `createBackdropRenderer`, `createSignalGridRenderer`, `createWaveRenderer`,
|
|
61
|
+
`resolveBackdropColors`, typy `KptBackdropRenderer`, `KptBackdropRenderOptions`,
|
|
62
|
+
`KptBackdropColors`
|
|
63
|
+
- **Siatka** — `buildSignalGrid`, `signalGridDensity`, `signalGridAlpha`, typy
|
|
64
|
+
`KptSignalGridOptions`, `KptSignalGridFrame`, `KptSignalGridCell`
|
|
65
|
+
- **Wstęga** — `resolveWave`, `waveIntensity`, `waveRamp`, `waveBuffer`, typy `KptWaveOptions`,
|
|
66
|
+
`KptWaveSettings`, `KptWaveBuffer`, `KptWaveHarmonic`
|
|
67
|
+
- **Barwy** — `mixOklch`, `shiftHue`, `readCssColor`, `parseHexColor`, `toHexColor`,
|
|
68
|
+
`srgbToOklab`, `oklabToSrgb`, typy `KptRgb`, `KptOklab`
|
|
69
|
+
- **Losowanie** — `kptHash01`
|
|
70
|
+
- **Stałe** — `KPT_SIGNAL_GRID_*`, `KPT_WAVE_*`, `KPT_BACKDROP_MAX_DPR`, `KPT_BACKDROP_MAX_STEP`
|
|
71
|
+
- **Model** — typy `KptBackdropAnchor`, `KptBackdropEffect`
|
|
72
|
+
|
|
73
|
+
Pełny opis: [`llms.txt`](https://ui.konce.pt/llms/backdrop/llms.txt) (EN)
|
|
74
|
+
i [`llms-pl.txt`](https://ui.konce.pt/llms/backdrop/llms-pl.txt) (PL).
|
|
75
|
+
|
|
76
|
+
## Testy
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm --filter @konce-pt/backdrop exec node --test "src/**/*.test.ts"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Licencja
|
|
83
|
+
|
|
84
|
+
MIT © [konce.pt](https://konce.pt)
|
package/dist/color.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
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
|
+
}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministyczna liczba 0–1 z pary współrzędnych i ziarna.
|
|
3
|
+
*
|
|
4
|
+
* Siatka nie może trzymać stanu per komórka między klatkami — przy zmianie rozmiaru komórki
|
|
5
|
+
* wędrują, a tablica `Math.random()` rozsypałaby cały obraz. Zamiast tego każda komórka wylicza
|
|
6
|
+
* swoje losowania z własnej pozycji: ta sama kolumna i wiersz zawsze dają ten sam wynik, więc
|
|
7
|
+
* po zmianie rozmiaru układ pozostaje sobą, a nie mruga od nowa.
|
|
8
|
+
*
|
|
9
|
+
* Miks jest wariantem lawiny z `Math.imul` — tanio i z porządnym rozrzutem na sąsiednich
|
|
10
|
+
* współrzędnych, gdzie zwykłe mnożenie zostawiałoby widoczne pasy.
|
|
11
|
+
*/
|
|
12
|
+
export declare function kptHash01(x: number, y: number, seed?: number): number;
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministyczna liczba 0–1 z pary współrzędnych i ziarna.
|
|
3
|
+
*
|
|
4
|
+
* Siatka nie może trzymać stanu per komórka między klatkami — przy zmianie rozmiaru komórki
|
|
5
|
+
* wędrują, a tablica `Math.random()` rozsypałaby cały obraz. Zamiast tego każda komórka wylicza
|
|
6
|
+
* swoje losowania z własnej pozycji: ta sama kolumna i wiersz zawsze dają ten sam wynik, więc
|
|
7
|
+
* po zmianie rozmiaru układ pozostaje sobą, a nie mruga od nowa.
|
|
8
|
+
*
|
|
9
|
+
* Miks jest wariantem lawiny z `Math.imul` — tanio i z porządnym rozrzutem na sąsiednich
|
|
10
|
+
* współrzędnych, gdzie zwykłe mnożenie zostawiałoby widoczne pasy.
|
|
11
|
+
*/
|
|
12
|
+
export function kptHash01(x, y, seed = 0) {
|
|
13
|
+
let h = Math.imul(x | 0, 0x1f1f_1f1f) ^ Math.imul(y | 0, 0x27d4_eb2f) ^ Math.imul(seed | 0, 0x1656_67b1);
|
|
14
|
+
h = Math.imul(h ^ (h >>> 15), 0x2545_f491);
|
|
15
|
+
h ^= h >>> 13;
|
|
16
|
+
h = Math.imul(h, 0x27d4_eb2d);
|
|
17
|
+
h ^= h >>> 16;
|
|
18
|
+
return (h >>> 0) / 4_294_967_296;
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { KptBackdropAnchor, KptBackdropEffect, KptSignalGridAlphaOptions, KptSignalGridCell, KptSignalGridFrame, KptSignalGridOptions, KptWaveHarmonic, KptWaveOptions, KptWaveSettings, } from './types.ts';
|
|
2
|
+
export { buildSignalGrid, signalGridAlpha, signalGridDensity, KPT_SIGNAL_GRID_CELL, KPT_SIGNAL_GRID_DENSITY, KPT_SIGNAL_GRID_FALLOFF, KPT_SIGNAL_GRID_FLOOR, KPT_SIGNAL_GRID_GAP, KPT_SIGNAL_GRID_RATE, } from './signal-grid.ts';
|
|
3
|
+
export { resolveWave, waveBuffer, waveIntensity, waveRamp, KPT_WAVE_AMPLITUDE, KPT_WAVE_BAND, KPT_WAVE_DRIFT, KPT_WAVE_MIN_SIDE, KPT_WAVE_RESOLUTION, KPT_WAVE_ROTATION, KPT_WAVE_SOFTNESS, KPT_WAVE_SPREAD, } from './wave.ts';
|
|
4
|
+
export type { KptWaveBuffer } from './wave.ts';
|
|
5
|
+
export { adaptLightness, adaptToSurface, mixOklch, oklabToSrgb, parseHexColor, prefersDarkContent, relativeLuminance, readCssColor, shiftHue, srgbToOklab, toHexColor, } from './color.ts';
|
|
6
|
+
export type { KptOklab, KptRgb } from './color.ts';
|
|
7
|
+
export { createBackdropRenderer, createSignalGridRenderer, adaptBackdropValue, createWaveRenderer, observeBackdropTheme, readBackdropInk, readBackdropLead, readBackdropSurface, resolveBackdropColors, resolveCssValue, KPT_BACKDROP_MAX_DPR, KPT_BACKDROP_MAX_STEP, KPT_BACKDROP_RAMP_SPREAD, } from './renderer.ts';
|
|
8
|
+
export type { KptBackdropColors, KptBackdropInk, KptBackdropRenderer, KptBackdropRenderOptions } from './renderer.ts';
|
|
9
|
+
export { kptHash01 } from './hash.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { buildSignalGrid, signalGridAlpha, signalGridDensity, KPT_SIGNAL_GRID_CELL, KPT_SIGNAL_GRID_DENSITY, KPT_SIGNAL_GRID_FALLOFF, KPT_SIGNAL_GRID_FLOOR, KPT_SIGNAL_GRID_GAP, KPT_SIGNAL_GRID_RATE, } from "./signal-grid.js";
|
|
2
|
+
export { resolveWave, waveBuffer, waveIntensity, waveRamp, KPT_WAVE_AMPLITUDE, KPT_WAVE_BAND, KPT_WAVE_DRIFT, KPT_WAVE_MIN_SIDE, KPT_WAVE_RESOLUTION, KPT_WAVE_ROTATION, KPT_WAVE_SOFTNESS, KPT_WAVE_SPREAD, } from "./wave.js";
|
|
3
|
+
export { adaptLightness, adaptToSurface, mixOklch, oklabToSrgb, parseHexColor, prefersDarkContent, relativeLuminance, readCssColor, shiftHue, srgbToOklab, toHexColor, } from "./color.js";
|
|
4
|
+
export { createBackdropRenderer, createSignalGridRenderer, adaptBackdropValue, createWaveRenderer, observeBackdropTheme, readBackdropInk, readBackdropLead, readBackdropSurface, resolveBackdropColors, resolveCssValue, KPT_BACKDROP_MAX_DPR, KPT_BACKDROP_MAX_STEP, KPT_BACKDROP_RAMP_SPREAD, } from "./renderer.js";
|
|
5
|
+
export { kptHash01 } from "./hash.js";
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderery efektów tła.
|
|
3
|
+
*
|
|
4
|
+
* Rysowanie nie potrzebuje frameworka — potrzebuje `CanvasRenderingContext2D`. Dzięki temu
|
|
5
|
+
* komponent w Angularze i w Reakcie zostaje samym cyklem życia (rAF, obserwatory rozmiaru,
|
|
6
|
+
* widoczności i motywu) wokół tego samego renderera, zamiast dwa razy powtarzać pętlę rysowania.
|
|
7
|
+
*
|
|
8
|
+
* Kolory wchodzą tu już rozwiązane: canvas nie rozwija `var()`, więc token na konkretny kolor
|
|
9
|
+
* zamienia wywołujący, który jedyny wie, na jakim elemencie go odczytać.
|
|
10
|
+
*/
|
|
11
|
+
import type { KptRgb } from './color.ts';
|
|
12
|
+
import type { KptBackdropAnchor, KptBackdropEffect } from './types.ts';
|
|
13
|
+
/** Gęstość pikseli powyżej dwóch nic nie wnosi przy tych efektach, a kosztuje kwadratowo. */
|
|
14
|
+
export declare const KPT_BACKDROP_MAX_DPR = 2;
|
|
15
|
+
/** Górna granica kroku czasu (ms) — karta wróciwszy z tła nie ma przewijać animacji do przodu. */
|
|
16
|
+
export declare const KPT_BACKDROP_MAX_STEP = 100;
|
|
17
|
+
/** O tyle stopni rozchodzą się domyślne barwy wstęgi względem koloru wiodącego. */
|
|
18
|
+
export declare const KPT_BACKDROP_RAMP_SPREAD = 28;
|
|
19
|
+
/**
|
|
20
|
+
* Renderer jednego efektu. Żyje tyle, co zestaw ustawień — zmiana wejść buduje nowy,
|
|
21
|
+
* zamiast doklejać do istniejącego ścieżki „co się zmieniło".
|
|
22
|
+
*/
|
|
23
|
+
export interface KptBackdropRenderer {
|
|
24
|
+
/** Nowy rozmiar obszaru w pikselach CSS. */
|
|
25
|
+
resize(width: number, height: number): void;
|
|
26
|
+
/** Jedna klatka; `time` w sekundach od startu animacji. */
|
|
27
|
+
draw(context: CanvasRenderingContext2D, time: number): void;
|
|
28
|
+
/** Zwalnia bufory. Po tym renderer już nie rysuje. */
|
|
29
|
+
dispose(): void;
|
|
30
|
+
}
|
|
31
|
+
/** Ustawienia renderera — płaskie, tak jak wejścia komponentu. */
|
|
32
|
+
export interface KptBackdropRenderOptions {
|
|
33
|
+
effect?: KptBackdropEffect;
|
|
34
|
+
/** signal-grid: kolor kwadratów, dowolny zapis CSS. */
|
|
35
|
+
color?: string;
|
|
36
|
+
anchor?: KptBackdropAnchor;
|
|
37
|
+
cell?: number;
|
|
38
|
+
gap?: number;
|
|
39
|
+
density?: number;
|
|
40
|
+
falloff?: number;
|
|
41
|
+
/**
|
|
42
|
+
* wave: rampa barw; zapis heksadecymalny, bo barwy trzeba wymieszać, nie tylko podać dalej.
|
|
43
|
+
* Zapis CSS zna wyłącznie `resolveBackdropColors` i to on sprowadza rampę do tej postaci.
|
|
44
|
+
*/
|
|
45
|
+
colors?: readonly string[];
|
|
46
|
+
rotation?: number;
|
|
47
|
+
band?: number;
|
|
48
|
+
amplitude?: number;
|
|
49
|
+
seed?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Kolory gotowe do podania rendererowi. */
|
|
52
|
+
export interface KptBackdropColors {
|
|
53
|
+
/** Kolor wiodący — dowolny zapis CSS, tak jak przyszedł. */
|
|
54
|
+
color: string;
|
|
55
|
+
/** Rampa wstęgi w zapisie heksadecymalnym. */
|
|
56
|
+
colors: readonly string[];
|
|
57
|
+
/** Wpisy `colors`, których przeglądarka nie uznała za kolor — do ostrzeżenia w buildzie dev. */
|
|
58
|
+
invalid: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Wyrażenie CSS rozwiązane względem drzewa: `var()` podstawione wartościami obowiązującymi w tym
|
|
62
|
+
* miejscu dokumentu. Dzięki temu wejście koloru może sięgnąć po token i samo iść za motywem,
|
|
63
|
+
* zamiast zamarzać na wartości z chwili napisania szablonu.
|
|
64
|
+
*
|
|
65
|
+
* `scope` musi być **potomkiem** hosta. `observeBackdropTheme` ogląda `style` na hoście i każdym
|
|
66
|
+
* jego przodku, więc sonda zapisana na hoście obudziłaby nasłuch, ten sięgnąłby po kolor na nowo,
|
|
67
|
+
* znów zapisał sondę — i pętla nie miałaby końca. Potomek jest poza zasięgiem nasłuchu.
|
|
68
|
+
*
|
|
69
|
+
* Bramka na `var(` nie jest kosmetyczna: bez niej każdy literał kosztowałby zapis stylu
|
|
70
|
+
* i wymuszone przeliczenie kaskady, a nie ma w nim czego rozwiązywać.
|
|
71
|
+
*
|
|
72
|
+
* Odwołanie do nieistniejącej zmiennej daje pusty napis — to sygnał „nie dało się", który
|
|
73
|
+
* wywołujący czyta jako brak wartości.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveCssValue(scope: HTMLElement, value: string): string;
|
|
76
|
+
/**
|
|
77
|
+
* Sam kolor wiodący: wejście komponentu, a bez niego token odczytany z wyliczonego stylu.
|
|
78
|
+
*
|
|
79
|
+
* Wydzielone z `resolveBackdropColors`, bo służy też za tani strażnik przy zmianie motywu —
|
|
80
|
+
* `getComputedStyle` bez sondy 1×1 i bez arytmetyki barw.
|
|
81
|
+
*
|
|
82
|
+
* Wejście przechodzi przez `resolveCssValue`, więc `var(--kpt-color-primary)` działa i zmienia się
|
|
83
|
+
* razem z motywem, a literał zostaje literałem. Zapis odwołujący się do zmiennej, której nie ma,
|
|
84
|
+
* schodzi do tokenu — tego chciałby autor literówki w nazwie.
|
|
85
|
+
*/
|
|
86
|
+
export declare function readBackdropLead(scope: HTMLElement, color: string): string;
|
|
87
|
+
/** Powierzchnia bieżącego motywu — punkt odniesienia adaptacji. */
|
|
88
|
+
export declare function readBackdropSurface(scope: HTMLElement): KptRgb | null;
|
|
89
|
+
/**
|
|
90
|
+
* Barwa przeniesiona na powierzchnię bieżącego motywu. Wartość, która nie podlega adaptacji albo
|
|
91
|
+
* której nie da się odczytać, wraca bez zmian — adaptacja nigdy nie gubi tego, co podano.
|
|
92
|
+
*/
|
|
93
|
+
export declare function adaptBackdropValue(value: string, surface: KptRgb | null): string;
|
|
94
|
+
/** Czym sekcja pisze po swoim tle. */
|
|
95
|
+
export interface KptBackdropInk {
|
|
96
|
+
/**
|
|
97
|
+
* Jasność tła sekcji: `light` znaczy jasne tło i ciemną treść. Puste znaczy „sekcja nie ma
|
|
98
|
+
* własnego tła" i wtedy nie ma czego ogłaszać.
|
|
99
|
+
*/
|
|
100
|
+
tone: '' | 'light' | 'dark';
|
|
101
|
+
/** Kolor treści dobrany do tego tła; pusty, gdy nie ma własnego tła. */
|
|
102
|
+
color: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Ton i kolor treści dobrane do tła, które sekcja naprawdę maluje.
|
|
106
|
+
*
|
|
107
|
+
* Host ustawia `color` tokenem powierzchni strony, ale sekcja z własnym `background` przestaje być
|
|
108
|
+
* tą powierzchnią — i wtedy token opisuje sąsiada, nie to, na czym treść leży. Bez tego rachunku
|
|
109
|
+
* ciemne tło w motywie jasnym daje czarny napis na czarnym.
|
|
110
|
+
*
|
|
111
|
+
* Ton wychodzi też na zewnątrz atrybutem, bo o kolor treści dopomina się nie tylko sam napis:
|
|
112
|
+
* przyciski i inne komponenty w sekcji potrzebują kompletu tokenów pod to tło, a tego nie da się
|
|
113
|
+
* odziedziczyć po `color`.
|
|
114
|
+
*/
|
|
115
|
+
export declare function readBackdropInk(scope: HTMLElement, background: string): KptBackdropInk;
|
|
116
|
+
/**
|
|
117
|
+
* Nasłuch na wszystkim, co może przestawić tokeny pod hostem. Zwraca funkcję odpinającą.
|
|
118
|
+
*
|
|
119
|
+
* Motyw bywa zakresowy — `[data-theme="dark"]` nie jest przywiązany do `:root`, więc tokeny
|
|
120
|
+
* przestawia dowolny przodek. Łańcuch od hosta w górę jest krótki i znany, więc obserwujemy go
|
|
121
|
+
* w całości, zamiast puszczać `subtree` na dokumencie, gdzie trafieniem byłaby każda zmiana klasy
|
|
122
|
+
* w aplikacji. Pętla kończy się na `<html>`, bo rodzicem `<html>` jest dokument, nie element.
|
|
123
|
+
*
|
|
124
|
+
* Do tego zapytanie o preferencję systemu: aplikacja może trzymać ciemne tokeny w media query
|
|
125
|
+
* zamiast w atrybucie i wtedy nie zmienia się nic, co da się zaobserwować w DOM.
|
|
126
|
+
*
|
|
127
|
+
* Łańcuch przodków jest brany w chwili zapisu — przeniesienie hosta w inne miejsce drzewa wymaga
|
|
128
|
+
* ponownego zapisu. Komponent takiego przypadku nie obsługuje.
|
|
129
|
+
*
|
|
130
|
+
* Nasłuch trafia też w zmiany niezwiązane z barwą (klasa nakładki na `<html>`, dowolne nadpisanie
|
|
131
|
+
* inline), więc wywołujący ma porównać `readBackdropLead` z poprzednim odczytem, zanim cokolwiek
|
|
132
|
+
* przebuduje.
|
|
133
|
+
*/
|
|
134
|
+
export declare function observeBackdropTheme(host: HTMLElement, onChange: () => void): () => void;
|
|
135
|
+
/**
|
|
136
|
+
* Zamienia wejścia koloru na wartości, które rozumie renderer.
|
|
137
|
+
*
|
|
138
|
+
* Canvas nie rozwija `var()`, więc token trzeba odczytać z wyliczonego stylu elementu — komponent
|
|
139
|
+
* jest jedynym miejscem, które wie, z którego. Pusta rampa bierze kolor wiodący i dwie barwy
|
|
140
|
+
* wyprowadzone z niego obrotem odcienia: paleta biblioteki jest neutralna, więc sięganie po tokeny
|
|
141
|
+
* statusowe (`info`, `success`) byłoby użyciem semantyki do dekoracji.
|
|
142
|
+
*
|
|
143
|
+
* `adaptive` przenosi barwy wpisane na sztywno na powierzchnię bieżącego motywu — czerń wpisana
|
|
144
|
+
* na białe tło staje się bielą, gdy tło jest ciemne. Dotyczy wyłącznie literałów: puste wejście
|
|
145
|
+
* bierze token, a zapis z `var()` sam sięga po token i jedno i drugie już idzie za motywem.
|
|
146
|
+
*
|
|
147
|
+
* Rampa podana wprost przechodzi tą samą drogą co domyślna: każdy wpis najpierw rozwiązujemy
|
|
148
|
+
* względem drzewa (`resolveCssValue`), a potem pytamy przeglądarkę o jego znaczenie. Działa więc
|
|
149
|
+
* i `oklch()`, i `color-mix()`, i nazwa własna, i odwołanie do tokenu — a `color-mix()` z tokenem
|
|
150
|
+
* w środku daje rampę, która sama idzie za motywem. Dalej, do renderera, trafia już sam hex.
|
|
151
|
+
* Wpisy, których przeglądarka nie przyjęła, wracają w `invalid` — rdzeń nie ma własnego trybu dev,
|
|
152
|
+
* więc ostrzega dopiero port. Gdy nie przeszedł żaden, schodzimy do rampy domyślnej: pusty kadr
|
|
153
|
+
* nie powiedziałby nikomu nic.
|
|
154
|
+
*
|
|
155
|
+
* `scope` musi być potomkiem hosta — powód przy `resolveCssValue`.
|
|
156
|
+
*
|
|
157
|
+
* Mieszka w rdzeniu, bo oba porty muszą rozstrzygać to samo tak samo — rozjazd znaczyłby, że ta
|
|
158
|
+
* sama sekcja ma inne barwy w Angularze niż w Reakcie.
|
|
159
|
+
*/
|
|
160
|
+
export declare function resolveBackdropColors(scope: HTMLElement, color: string, colors: readonly string[], adaptive?: boolean): KptBackdropColors;
|
|
161
|
+
/**
|
|
162
|
+
* Drobna siatka migoczących kwadratów. Cała matematyka siedzi w `signal-grid.ts`;
|
|
163
|
+
* tutaj zostaje pętla po komórkach.
|
|
164
|
+
*/
|
|
165
|
+
export declare function createSignalGridRenderer(options: KptBackdropRenderOptions): KptBackdropRenderer;
|
|
166
|
+
/**
|
|
167
|
+
* Jedna miękka wstęga światła z barwami przesuwającymi się wzdłuż niej.
|
|
168
|
+
*
|
|
169
|
+
* Liczymy ją w buforze o dłuższym boku `KPT_WAVE_RESOLUTION` i skalujemy w górę — miękkość
|
|
170
|
+
* bierze się z interpolacji przeglądarki, więc kilkanaście tysięcy pikseli na klatkę wystarcza
|
|
171
|
+
* na dowolnie duży kadr. Rampa barw idzie przez podręczną tablicę: mieszanie po kole OKLCH raz
|
|
172
|
+
* na piksel byłoby kilkoma pierwiastkami i arkusem tangensa na każdy z nich.
|
|
173
|
+
*/
|
|
174
|
+
export declare function createWaveRenderer(options: KptBackdropRenderOptions): KptBackdropRenderer;
|
|
175
|
+
/** Renderer wskazanego efektu. Nieznany efekt nie rysuje niczego, zamiast wywracać klatkę. */
|
|
176
|
+
export declare function createBackdropRenderer(options: KptBackdropRenderOptions): KptBackdropRenderer;
|