@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/dist/wave.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { kptHash01 } from "./hash.js";
|
|
2
|
+
/** Dłuższy bok bufora, w którym liczy się efekt (px). */
|
|
3
|
+
export const KPT_WAVE_RESOLUTION = 160;
|
|
4
|
+
/** Najmniejsza liczba próbek na bok bufora — chyba że sam obszar jest mniejszy. */
|
|
5
|
+
export const KPT_WAVE_MIN_SIDE = 72;
|
|
6
|
+
/** Szerokość wstęgi jako ułamek wysokości kadru. */
|
|
7
|
+
export const KPT_WAVE_BAND = 0.34;
|
|
8
|
+
/** Wykładnik opadania od linii środkowej; 2 to krzywa Gaussa. */
|
|
9
|
+
export const KPT_WAVE_SOFTNESS = 2;
|
|
10
|
+
/** Jak mocno faluje linia środkowa, w ułamkach wysokości kadru. */
|
|
11
|
+
export const KPT_WAVE_AMPLITUDE = 0.16;
|
|
12
|
+
/** Kąt, pod jakim prąd przecina kadr (stopnie). */
|
|
13
|
+
export const KPT_WAVE_ROTATION = 12;
|
|
14
|
+
/** Ile rampy barw przypada na jednostkę długości wzdłuż wstęgi. */
|
|
15
|
+
export const KPT_WAVE_SPREAD = 0.55;
|
|
16
|
+
/** Ile rampy barw przesuwa się na sekundę przy `speed = 1`. */
|
|
17
|
+
export const KPT_WAVE_DRIFT = 0.07;
|
|
18
|
+
/** Ile harmonicznych składa się na linię środkową. */
|
|
19
|
+
const HARMONICS = 3;
|
|
20
|
+
const TAU = Math.PI * 2;
|
|
21
|
+
const DEG = TAU / 360;
|
|
22
|
+
/**
|
|
23
|
+
* Wymiary bufora pod zadany obszar.
|
|
24
|
+
*
|
|
25
|
+
* Wstęga nie ma detalu — jest samą miękkością — więc liczenie jej w pełnej rozdzielczości canvasu
|
|
26
|
+
* byłoby płaceniem za piksele, które i tak zostaną rozmyte. Bufor o dłuższym boku
|
|
27
|
+
* `KPT_WAVE_RESOLUTION` daje kilkanaście tysięcy pikseli na klatkę, a rozmycie robi za darmo
|
|
28
|
+
* interpolacja przeglądarki przy skalowaniu go w górę.
|
|
29
|
+
*
|
|
30
|
+
* Krótszy bok ma osobną podłogę `KPT_WAVE_MIN_SIDE`. W szerokim kadrze proporcjonalne skalowanie
|
|
31
|
+
* zostawiłoby kilkadziesiąt wierszy, a po obróceniu wstęgi o kąt bliski prostemu krzywa biegłaby
|
|
32
|
+
* właśnie wzdłuż tej osi i zaczynała się łamać. Bufor nie musi trzymać proporcji kadru —
|
|
33
|
+
* próbkowanie chodzi po współrzędnych znormalizowanych, a geometrię niesie `aspect` w ustawieniach.
|
|
34
|
+
*/
|
|
35
|
+
export function waveBuffer(width, height, resolution = KPT_WAVE_RESOLUTION) {
|
|
36
|
+
if (width <= 0 || height <= 0)
|
|
37
|
+
return { cols: 0, rows: 0 };
|
|
38
|
+
const longest = Math.max(width, height);
|
|
39
|
+
const scale = Math.min(1, Math.max(1, resolution) / longest);
|
|
40
|
+
const fit = (size) => Math.max(1, Math.round(size * scale), Math.min(Math.round(size), KPT_WAVE_MIN_SIDE));
|
|
41
|
+
return { cols: fit(width), rows: fit(height) };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Rozkłada ustawienia na wartości, które w pętli po pikselach są już tylko odczytywane.
|
|
45
|
+
*
|
|
46
|
+
* Domyślne wartości, sinus i cosinus kąta oraz harmoniczne liczą się raz — przy zmianie wejść
|
|
47
|
+
* albo rozmiaru — bo inaczej szłyby kilkanaście tysięcy razy na klatkę.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveWave(options) {
|
|
50
|
+
const width = Math.max(0, options.width);
|
|
51
|
+
const height = Math.max(0, options.height);
|
|
52
|
+
const angle = (options.rotation ?? KPT_WAVE_ROTATION) * DEG;
|
|
53
|
+
const seed = options.seed ?? 0;
|
|
54
|
+
// Wagi opadają z numerem harmonicznej i sumują się do jedynki, żeby `amplitude` znaczyło
|
|
55
|
+
// to samo niezależnie od tego, ile ich jest.
|
|
56
|
+
const weights = Array.from({ length: HARMONICS }, (_, index) => 1 / (index + 1.6));
|
|
57
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
58
|
+
return {
|
|
59
|
+
aspect: height > 0 ? width / height : 1,
|
|
60
|
+
cos: Math.cos(angle),
|
|
61
|
+
sin: Math.sin(angle),
|
|
62
|
+
band: Math.max(0.01, options.band ?? KPT_WAVE_BAND),
|
|
63
|
+
softness: Math.max(0.5, options.softness ?? KPT_WAVE_SOFTNESS),
|
|
64
|
+
amplitude: Math.max(0, options.amplitude ?? KPT_WAVE_AMPLITUDE),
|
|
65
|
+
spread: options.spread ?? KPT_WAVE_SPREAD,
|
|
66
|
+
drift: options.drift ?? KPT_WAVE_DRIFT,
|
|
67
|
+
harmonics: weights.map((weight, index) => ({
|
|
68
|
+
// Częstotliwości rosną nierówno, żeby suma nie wpadła w powtarzalny wzór.
|
|
69
|
+
frequency: 2.1 + index * 1.7,
|
|
70
|
+
rate: 0.3 + kptHash01(index, 1, seed) * 0.45,
|
|
71
|
+
phase: kptHash01(index, 2, seed) * TAU,
|
|
72
|
+
weight: weight / total,
|
|
73
|
+
})),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Współrzędna wzdłuż wstęgi po obrocie kadru o `-rotation`. */
|
|
77
|
+
const along = (u, v, settings) => (u - 0.5) * settings.aspect * settings.cos + (v - 0.5) * settings.sin;
|
|
78
|
+
/** Współrzędna w poprzek wstęgi. */
|
|
79
|
+
const across = (u, v, settings) => -(u - 0.5) * settings.aspect * settings.sin + (v - 0.5) * settings.cos;
|
|
80
|
+
/**
|
|
81
|
+
* Jasność wstęgi w punkcie `(u, v)` znormalizowanego kadru, w chwili `time` (sekundy).
|
|
82
|
+
*
|
|
83
|
+
* Linia środkowa to suma harmonicznych o niewspółmiernych częstotliwościach — stąd prąd faluje
|
|
84
|
+
* bez powtarzalnego wzoru. Od niej jasność opada krzywą Gaussa, więc w całym kadrze nie ma ani
|
|
85
|
+
* jednej twardej krawędzi; to właśnie odróżnia prąd światła od paska.
|
|
86
|
+
*/
|
|
87
|
+
export function waveIntensity(u, v, time, settings) {
|
|
88
|
+
const s = along(u, v, settings);
|
|
89
|
+
const t = across(u, v, settings);
|
|
90
|
+
let center = 0;
|
|
91
|
+
for (const harmonic of settings.harmonics) {
|
|
92
|
+
center += harmonic.weight * Math.sin(s * harmonic.frequency + time * harmonic.rate + harmonic.phase);
|
|
93
|
+
}
|
|
94
|
+
center *= settings.amplitude;
|
|
95
|
+
const distance = Math.abs(t - center) / settings.band;
|
|
96
|
+
return Math.exp(-(distance ** settings.softness));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Miejsce na rampie barw (0–1) dla punktu `(u, v)` w chwili `time`.
|
|
100
|
+
*
|
|
101
|
+
* Barwy przesuwają się wzdłuż wstęgi, nie w poprzek — dlatego liczy się tylko współrzędna `s`.
|
|
102
|
+
* Wynik zawsze zawija się do 0–1, więc rosnący czas nie wyprowadza go poza rampę.
|
|
103
|
+
*/
|
|
104
|
+
export function waveRamp(u, v, time, settings) {
|
|
105
|
+
const position = along(u, v, settings) * settings.spread + time * settings.drift;
|
|
106
|
+
return position - Math.floor(position);
|
|
107
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@konce-pt/backdrop",
|
|
3
|
+
"version": "0.8.5",
|
|
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, zero runtime dependencies.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "konce.pt",
|
|
7
|
+
"homepage": "https://ui.konce.pt/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://gitlab.com/konce-pt/koncept-ui.git",
|
|
11
|
+
"directory": "packages/backdrop"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://gitlab.com/konce-pt/koncept-ui/-/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"backdrop",
|
|
18
|
+
"background",
|
|
19
|
+
"canvas",
|
|
20
|
+
"animation",
|
|
21
|
+
"hero",
|
|
22
|
+
"design-system",
|
|
23
|
+
"koncept-ui",
|
|
24
|
+
"kpt"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"typescript": "~6.0.3"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.build.json",
|
|
44
|
+
"test": "node --test \"src/**/*.test.ts\"",
|
|
45
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
|
|
46
|
+
}
|
|
47
|
+
}
|