@konce-pt/motion 0.0.0-stage → 0.15.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/LICENSE +21 -0
- package/README.md +69 -2
- package/dist/config.d.ts +71 -0
- package/dist/config.js +54 -0
- package/dist/dom.d.ts +86 -0
- package/dist/dom.js +210 -0
- package/dist/flow-diff.d.ts +7 -0
- package/dist/flow-diff.js +17 -0
- package/dist/flow.d.ts +22 -0
- package/dist/flow.js +84 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +13 -0
- package/dist/keyed.d.ts +64 -0
- package/dist/keyed.js +221 -0
- package/dist/layout.d.ts +65 -0
- package/dist/layout.js +115 -0
- package/dist/loader-dom.d.ts +48 -0
- package/dist/loader-dom.js +821 -0
- package/dist/loader.d.ts +177 -0
- package/dist/loader.js +400 -0
- package/dist/spring.d.ts +16 -0
- package/dist/spring.js +41 -0
- package/dist/stepper-dom.d.ts +9 -0
- package/dist/stepper-dom.js +44 -0
- package/dist/stepper.d.ts +51 -0
- package/dist/stepper.js +60 -0
- package/dist/swap.d.ts +13 -0
- package/dist/swap.js +61 -0
- package/package.json +44 -3
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rdzeń `kpt-loader`: geometria i fizyka wariantów jako czyste funkcje czasu.
|
|
3
|
+
*
|
|
4
|
+
* Każda klatka loadera to liczby - promień orbity, pozycja kuli nad ziemią, napis ścieżki SVG.
|
|
5
|
+
* Liczymy je tutaj, bez DOM-u, więc da się je przetestować; warstwa `loader-dom.ts` tylko je
|
|
6
|
+
* nakłada na elementy. Współrzędne są w jednostkach rysunku (`LOADER_VIEWBOX`), a nie w pikselach:
|
|
7
|
+
* SVG skaluje je do rozmiaru, który poda komponent.
|
|
8
|
+
*/
|
|
9
|
+
/** Warianty: ciecz, most z cieczy, kształty, pierścień, fala i skaczące bąbelki. */
|
|
10
|
+
export type KptLoaderVariant = 'bubbles' | 'bridge' | 'shapes' | 'ring' | 'wave' | 'bounce';
|
|
11
|
+
/** `loading` kręci pętlę, `success` i `error` grają wynik - loader przechodzi w znak zamiast znikać. */
|
|
12
|
+
export type KptLoaderStatus = 'loading' | 'success' | 'error';
|
|
13
|
+
export type KptLoaderResult = 'success' | 'error';
|
|
14
|
+
/** Role motywu, które loader zamienia na tokeny `--kpt-color-*`. */
|
|
15
|
+
export type KptLoaderRole = 'primary' | 'info' | 'success' | 'warning' | 'danger';
|
|
16
|
+
/** Rola motywu albo dowolny kolor CSS (`oklch(...)`, `var(--brand)`). */
|
|
17
|
+
export type KptLoaderColor = KptLoaderRole | (string & {});
|
|
18
|
+
export declare const KPT_LOADER_VARIANTS: readonly KptLoaderVariant[];
|
|
19
|
+
/** Domyślne barwy pętli - tyle, ile wariant ma elementów w różnych kolorach. */
|
|
20
|
+
export declare const LOADER_PALETTE: Readonly<Record<KptLoaderVariant, readonly KptLoaderRole[]>>;
|
|
21
|
+
/**
|
|
22
|
+
* Pole rysunku wariantu: `[x, y, szerokość, wysokość]`. Wspólna skala - 100 jednostek to `size`
|
|
23
|
+
* komponentu - więc warianty obok siebie mają ten sam rozmiar elementów, a nie to samo pudełko.
|
|
24
|
+
*/
|
|
25
|
+
export declare const LOADER_VIEWBOX: Readonly<Record<KptLoaderVariant, readonly [number, number, number, number]>>;
|
|
26
|
+
/** Czas choreografii wyniku w ms - po nim komponent zgłasza `settled`. */
|
|
27
|
+
export declare const LOADER_RESULT_MS: Readonly<Record<KptLoaderVariant, Readonly<Record<KptLoaderResult, number>>>>;
|
|
28
|
+
/** Wynik w wersji z ograniczonym ruchem: loader gaśnie, znak pojawia się na miejscu. */
|
|
29
|
+
export declare const LOADER_QUIET_MS = 400;
|
|
30
|
+
export declare const clamp01: (x: number) => number;
|
|
31
|
+
export declare const mix: (a: number, b: number, p: number) => number;
|
|
32
|
+
export declare const easeIn: (q: number) => number;
|
|
33
|
+
export declare const easeOut: (q: number) => number;
|
|
34
|
+
export declare const easeInOut: (q: number) => number;
|
|
35
|
+
/**
|
|
36
|
+
* `cubic-bezier()` z CSS jako funkcja postępu - te same krzywe co w przejściach biblioteki, ale
|
|
37
|
+
* liczone w klatce `rAF`. Newton z bisekcją jako siatką bezpieczeństwa (płaskie odcinki krzywej).
|
|
38
|
+
*/
|
|
39
|
+
export declare function cubicBezier(x1: number, y1: number, x2: number, y2: number): (q: number) => number;
|
|
40
|
+
/** Krzywe ruchu loaderów: wyjście, wejście, przejście i cztery sprężyny o różnym odbiciu. */
|
|
41
|
+
export declare const LOADER_EASE: {
|
|
42
|
+
readonly out: (q: number) => number;
|
|
43
|
+
readonly in: (q: number) => number;
|
|
44
|
+
readonly inOut: (q: number) => number;
|
|
45
|
+
readonly splash: (q: number) => number;
|
|
46
|
+
readonly snap: (q: number) => number;
|
|
47
|
+
readonly pop: (q: number) => number;
|
|
48
|
+
readonly wobble: (q: number) => number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Wartość postępu sprowadzona do 0..1, jak w `kpt-progress` (`value`, `min`, `max`).
|
|
52
|
+
* `null` = postęp nieokreślony (brak wartości, `NaN` albo pusty zakres).
|
|
53
|
+
*/
|
|
54
|
+
export declare function loaderProgress(value: number | null | undefined, min?: number, max?: number): number | null;
|
|
55
|
+
/** Rola motywu -> `var(--kpt-color-<rola>)`; każdy inny napis idzie bez zmian jako kolor CSS. */
|
|
56
|
+
export declare function loaderColor(color: KptLoaderColor): string;
|
|
57
|
+
/** Kolor znaku na tle barwy: kontrast roli albo powierzchnia, gdy kolor jest spoza ról. */
|
|
58
|
+
export declare function loaderOnColor(color: KptLoaderColor): string;
|
|
59
|
+
/** Delikatne tło w barwie: `-subtle` roli albo ten sam kolor rozcieńczony w powierzchni. */
|
|
60
|
+
export declare function loaderSubtle(color: KptLoaderColor): string;
|
|
61
|
+
/**
|
|
62
|
+
* Barwy elementów pętli: własne `colors` powtarzane po kolei do liczby elementów wariantu,
|
|
63
|
+
* a bez nich - paleta wariantu. Zawsze tyle pozycji, ile wariant potrzebuje.
|
|
64
|
+
*/
|
|
65
|
+
export declare function loaderPalette(variant: KptLoaderVariant, colors?: readonly KptLoaderColor[] | null): string[];
|
|
66
|
+
export declare const LOADER_MARKS: {
|
|
67
|
+
readonly check: readonly ["M5 12l5 5l10 -10"];
|
|
68
|
+
readonly x: readonly ["M18 6l-12 12", "M6 6l12 12"];
|
|
69
|
+
readonly alert: readonly ["M12 7v6", "M12 17v.01"];
|
|
70
|
+
};
|
|
71
|
+
export type KptLoaderMark = keyof typeof LOADER_MARKS;
|
|
72
|
+
/**
|
|
73
|
+
* Promień orbity kropel w pętli 2,8 s: rozprysk na sprężynie, chwila w rozstawieniu, zbiegnięcie
|
|
74
|
+
* i chwila w jednej kropli. Od 6 do 36 jednostek.
|
|
75
|
+
*/
|
|
76
|
+
export declare function bubblesOrbit(t: number): number;
|
|
77
|
+
/** Prędkość kątowa (rad/s): im ciaśniej, tym szybciej - jak łyżwiarz przyciągający ręce. */
|
|
78
|
+
export declare function bubblesSpin(radius: number): number;
|
|
79
|
+
/** Kropla w pętli: wahadło przez rząd kropek, rozciągnięte w kierunku lotu. */
|
|
80
|
+
export declare function bridgeTraveler(t: number): {
|
|
81
|
+
x: number;
|
|
82
|
+
sx: number;
|
|
83
|
+
sy: number;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Kropka rzędu przy przelatującej kropli: przyciągnięta ku niej i nabrzmiała, gdy kropla jest
|
|
87
|
+
* blisko (mostek cieczy robi filtr). `gather` 1 -> 0 zbiera rząd do środka przy wyniku.
|
|
88
|
+
*/
|
|
89
|
+
export declare function bridgeDot(home: number, x: number, gather?: number): {
|
|
90
|
+
x: number;
|
|
91
|
+
scale: number;
|
|
92
|
+
};
|
|
93
|
+
/** Kapsuła wyniku: znak 18 + odstęp + napis, z marginesem 17 z każdej strony. */
|
|
94
|
+
export declare function bridgePillWidth(textWidth: number): number;
|
|
95
|
+
export declare const SHAPE_SAMPLES = 120;
|
|
96
|
+
/** Kształty pętli po kolei: ciastko, kwadrat, koniczyna, pięciokąt, pigułka, słońce, trójkąt. */
|
|
97
|
+
export declare const LOADER_SHAPES: readonly (readonly number[])[];
|
|
98
|
+
/** Kształt sukcesu (koło) i błędu (zaokrąglony kwadrat). */
|
|
99
|
+
export declare const SHAPE_CIRCLE: readonly number[];
|
|
100
|
+
export declare const SHAPE_SQUIRCLE: readonly number[];
|
|
101
|
+
export declare function mixRadii(a: readonly number[], b: readonly number[], p: number): number[];
|
|
102
|
+
/** Zamknięta ścieżka kształtu o promieniach `radii`, obrócona o `rotation` (rad) i przeskalowana. */
|
|
103
|
+
export declare function shapePath(radii: readonly number[], rotation: number, scale: number): string;
|
|
104
|
+
/**
|
|
105
|
+
* Klatka pętli kształtów: co 0,8 s morf do następnego kształtu na sprężynie i obrót o ćwierć
|
|
106
|
+
* obrotu z dociągnięciem, a pod spodem stały, wolny obrót i oddech skali.
|
|
107
|
+
*/
|
|
108
|
+
export declare function shapesFrame(t: number): {
|
|
109
|
+
radii: number[];
|
|
110
|
+
rotation: number;
|
|
111
|
+
scale: number;
|
|
112
|
+
};
|
|
113
|
+
export declare const RING_RADIUS = 44;
|
|
114
|
+
/**
|
|
115
|
+
* Łuk pierścienia od `a0` do `a1` (stopnie, 0 = godzina 3) z falą o amplitudzie `amp`.
|
|
116
|
+
* Fala gaśnie przy końcach łuku (obwiednia 10 stopni), więc końce są gładkie. Pusty napis,
|
|
117
|
+
* gdy łuk jest krótszy niż pół stopnia.
|
|
118
|
+
*/
|
|
119
|
+
export declare function ringArcPath(a0: number, a1: number, amp: number, phase: number, radius?: number): string;
|
|
120
|
+
/**
|
|
121
|
+
* Łuk nieokreślony: głowa i ogon gonią się co 1,5 s (głowa rusza pierwsza, ogon dogania),
|
|
122
|
+
* a całość obraca się stale. Kąty w stopniach, start u góry.
|
|
123
|
+
*/
|
|
124
|
+
export declare function ringIdleArc(t: number): {
|
|
125
|
+
a0: number;
|
|
126
|
+
a1: number;
|
|
127
|
+
};
|
|
128
|
+
/** Najbliższa "dwunasta" nie wcześniej niż `a0` - stąd łuk określony liczy postęp. */
|
|
129
|
+
export declare function ringBase(a0: number): number;
|
|
130
|
+
export declare const WAVE_WIDTH = 240;
|
|
131
|
+
/** Odcinek fali od `x0` do `x1` (oś na y = 0), z obwiednią 8 jednostek przy końcach. */
|
|
132
|
+
export declare function wavePath(x0: number, x1: number, amp: number, phase: number): string;
|
|
133
|
+
/** Prosty odcinek toru; pusty, gdy krótszy niż jednostka. */
|
|
134
|
+
export declare function flatPath(x0: number, x1: number): string;
|
|
135
|
+
/** Odcinek nieokreślony: wjeżdża z lewej, rośnie i ucieka w prawo co 1,8 s (nieprzycięty do toru). */
|
|
136
|
+
export declare function waveIdleSpan(t: number): {
|
|
137
|
+
x0: number;
|
|
138
|
+
x1: number;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Wynik fali w chwili `rt` (s od wyniku): sukces wygładza falę, błąd szarpie nią; potem odcinek
|
|
142
|
+
* kurczy się do kreski na środku, a kreska zgina się w ptaszek albo rozcina w krzyżyk.
|
|
143
|
+
* `mark` mówi, że rysujemy już znak (grubsza kreska).
|
|
144
|
+
*/
|
|
145
|
+
export declare function waveResultPath(kind: KptLoaderResult, rt: number, x0: number, x1: number, phase: number): {
|
|
146
|
+
d: string;
|
|
147
|
+
mark: boolean;
|
|
148
|
+
};
|
|
149
|
+
/** Poziom ziemi: na nim stoją kule i leżą ich cienie. */
|
|
150
|
+
export declare const BOUNCE_GROUND = 34;
|
|
151
|
+
/** Kula w chwili: przesunięcie, wysokość nad ziemią, spłaszczenie i mnożnik rozmiaru. */
|
|
152
|
+
export interface KptBounceBall {
|
|
153
|
+
x: number;
|
|
154
|
+
h: number;
|
|
155
|
+
sx: number;
|
|
156
|
+
sy: number;
|
|
157
|
+
k: number;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Kula `index` w pętli: skok co 0,9 s z przesunięciem fazy 0,15 s. Przy ziemi spłaszcza się
|
|
161
|
+
* (do 7% okresu od lądowania), w locie wyciąga w pionie proporcjonalnie do prędkości.
|
|
162
|
+
*/
|
|
163
|
+
export declare function bounceIdle(t: number, index: number, x: number): KptBounceBall;
|
|
164
|
+
/**
|
|
165
|
+
* Wynik kul w chwili `rt`: sukces to wspólny skok do środka, zlanie w jedną kulę i lądowanie
|
|
166
|
+
* z wygaszonym przysiadem; błąd - opadnięcie bez odbicia, kałuże bocznych kul spływają do
|
|
167
|
+
* środkowej, a ta nadmuchuje się w znak. `from` to pozycja kuli w chwili wyniku.
|
|
168
|
+
*/
|
|
169
|
+
export declare function bounceResult(kind: KptLoaderResult, rt: number, index: number, from: {
|
|
170
|
+
x: number;
|
|
171
|
+
h: number;
|
|
172
|
+
}): KptBounceBall;
|
|
173
|
+
/** Cień kuli: maleje z wysokością, rośnie ze spłaszczeniem; znika razem z kulą. */
|
|
174
|
+
export declare function bounceShadow(ball: KptBounceBall): {
|
|
175
|
+
scale: number;
|
|
176
|
+
opacity: number;
|
|
177
|
+
};
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { springProgress } from "./spring.js";
|
|
2
|
+
export const KPT_LOADER_VARIANTS = ['bubbles', 'bridge', 'shapes', 'ring', 'wave', 'bounce'];
|
|
3
|
+
const ROLES = new Set(['primary', 'info', 'success', 'warning', 'danger']);
|
|
4
|
+
/** Domyślne barwy pętli - tyle, ile wariant ma elementów w różnych kolorach. */
|
|
5
|
+
export const LOADER_PALETTE = {
|
|
6
|
+
bubbles: ['primary', 'info', 'success', 'warning'],
|
|
7
|
+
bridge: ['primary', 'info'],
|
|
8
|
+
shapes: ['primary'],
|
|
9
|
+
ring: ['info'],
|
|
10
|
+
wave: ['primary'],
|
|
11
|
+
bounce: ['info', 'success', 'warning'],
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Pole rysunku wariantu: `[x, y, szerokość, wysokość]`. Wspólna skala - 100 jednostek to `size`
|
|
15
|
+
* komponentu - więc warianty obok siebie mają ten sam rozmiar elementów, a nie to samo pudełko.
|
|
16
|
+
*/
|
|
17
|
+
export const LOADER_VIEWBOX = {
|
|
18
|
+
bubbles: [-64, -64, 128, 128],
|
|
19
|
+
bridge: [-100, -32, 200, 64],
|
|
20
|
+
shapes: [-66, -66, 132, 132],
|
|
21
|
+
ring: [-50, -50, 100, 100],
|
|
22
|
+
wave: [0, -24, 240, 48],
|
|
23
|
+
bounce: [-60, -48, 120, 92],
|
|
24
|
+
};
|
|
25
|
+
/** Czas choreografii wyniku w ms - po nim komponent zgłasza `settled`. */
|
|
26
|
+
export const LOADER_RESULT_MS = {
|
|
27
|
+
bubbles: { success: 1700, error: 2000 },
|
|
28
|
+
bridge: { success: 1300, error: 1600 },
|
|
29
|
+
shapes: { success: 1500, error: 1700 },
|
|
30
|
+
ring: { success: 1400, error: 1500 },
|
|
31
|
+
wave: { success: 1500, error: 1600 },
|
|
32
|
+
bounce: { success: 1600, error: 1700 },
|
|
33
|
+
};
|
|
34
|
+
/** Wynik w wersji z ograniczonym ruchem: loader gaśnie, znak pojawia się na miejscu. */
|
|
35
|
+
export const LOADER_QUIET_MS = 400;
|
|
36
|
+
// -- liczby --
|
|
37
|
+
export const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
|
|
38
|
+
export const mix = (a, b, p) => a + (b - a) * p;
|
|
39
|
+
export const easeIn = (q) => q * q * q;
|
|
40
|
+
export const easeOut = (q) => 1 - Math.pow(1 - q, 3);
|
|
41
|
+
export const easeInOut = (q) => (q < 0.5 ? 4 * q * q * q : 1 - Math.pow(-2 * q + 2, 3) / 2);
|
|
42
|
+
/**
|
|
43
|
+
* `cubic-bezier()` z CSS jako funkcja postępu - te same krzywe co w przejściach biblioteki, ale
|
|
44
|
+
* liczone w klatce `rAF`. Newton z bisekcją jako siatką bezpieczeństwa (płaskie odcinki krzywej).
|
|
45
|
+
*/
|
|
46
|
+
export function cubicBezier(x1, y1, x2, y2) {
|
|
47
|
+
const cx = 3 * x1;
|
|
48
|
+
const bx = 3 * (x2 - x1) - cx;
|
|
49
|
+
const ax = 1 - cx - bx;
|
|
50
|
+
const cy = 3 * y1;
|
|
51
|
+
const by = 3 * (y2 - y1) - cy;
|
|
52
|
+
const ay = 1 - cy - by;
|
|
53
|
+
const sx = (u) => ((ax * u + bx) * u + cx) * u;
|
|
54
|
+
const sy = (u) => ((ay * u + by) * u + cy) * u;
|
|
55
|
+
const dx = (u) => (3 * ax * u + 2 * bx) * u + cx;
|
|
56
|
+
return (q) => {
|
|
57
|
+
if (q <= 0)
|
|
58
|
+
return 0;
|
|
59
|
+
if (q >= 1)
|
|
60
|
+
return 1;
|
|
61
|
+
let u = q;
|
|
62
|
+
for (let i = 0; i < 8; i++) {
|
|
63
|
+
const e = sx(u) - q;
|
|
64
|
+
if (Math.abs(e) < 1e-6)
|
|
65
|
+
return sy(u);
|
|
66
|
+
const d = dx(u);
|
|
67
|
+
if (Math.abs(d) < 1e-6)
|
|
68
|
+
break;
|
|
69
|
+
u -= e / d;
|
|
70
|
+
}
|
|
71
|
+
let lo = 0;
|
|
72
|
+
let hi = 1;
|
|
73
|
+
u = q;
|
|
74
|
+
for (let i = 0; i < 40 && Math.abs(sx(u) - q) > 1e-6; i++) {
|
|
75
|
+
if (sx(u) < q)
|
|
76
|
+
lo = u;
|
|
77
|
+
else
|
|
78
|
+
hi = u;
|
|
79
|
+
u = (lo + hi) / 2;
|
|
80
|
+
}
|
|
81
|
+
return sy(u);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Krzywe ruchu loaderów: wyjście, wejście, przejście i cztery sprężyny o różnym odbiciu. */
|
|
85
|
+
export const LOADER_EASE = {
|
|
86
|
+
out: cubicBezier(0.2, 0, 0, 1),
|
|
87
|
+
in: cubicBezier(0.4, 0, 1, 1),
|
|
88
|
+
inOut: cubicBezier(0.65, 0, 0.35, 1),
|
|
89
|
+
splash: cubicBezier(0.15, 0.7, 0.3, 1),
|
|
90
|
+
snap: springProgress(0.32),
|
|
91
|
+
pop: springProgress(0.5),
|
|
92
|
+
wobble: springProgress(0.45),
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Wartość postępu sprowadzona do 0..1, jak w `kpt-progress` (`value`, `min`, `max`).
|
|
96
|
+
* `null` = postęp nieokreślony (brak wartości, `NaN` albo pusty zakres).
|
|
97
|
+
*/
|
|
98
|
+
export function loaderProgress(value, min = 0, max = 100) {
|
|
99
|
+
if (value == null || !Number.isFinite(value) || !(max > min))
|
|
100
|
+
return null;
|
|
101
|
+
return clamp01((value - min) / (max - min));
|
|
102
|
+
}
|
|
103
|
+
// -- barwy --
|
|
104
|
+
/** Rola motywu -> `var(--kpt-color-<rola>)`; każdy inny napis idzie bez zmian jako kolor CSS. */
|
|
105
|
+
export function loaderColor(color) {
|
|
106
|
+
return ROLES.has(color) ? `var(--kpt-color-${color})` : color;
|
|
107
|
+
}
|
|
108
|
+
/** Kolor znaku na tle barwy: kontrast roli albo powierzchnia, gdy kolor jest spoza ról. */
|
|
109
|
+
export function loaderOnColor(color) {
|
|
110
|
+
return ROLES.has(color) ? `var(--kpt-color-${color}-contrast)` : 'var(--kpt-color-surface)';
|
|
111
|
+
}
|
|
112
|
+
/** Delikatne tło w barwie: `-subtle` roli albo ten sam kolor rozcieńczony w powierzchni. */
|
|
113
|
+
export function loaderSubtle(color) {
|
|
114
|
+
return ROLES.has(color)
|
|
115
|
+
? `var(--kpt-color-${color}-subtle)`
|
|
116
|
+
: `color-mix(in oklch, ${color} 18%, var(--kpt-color-surface))`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Barwy elementów pętli: własne `colors` powtarzane po kolei do liczby elementów wariantu,
|
|
120
|
+
* a bez nich - paleta wariantu. Zawsze tyle pozycji, ile wariant potrzebuje.
|
|
121
|
+
*/
|
|
122
|
+
export function loaderPalette(variant, colors) {
|
|
123
|
+
const base = LOADER_PALETTE[variant];
|
|
124
|
+
const own = colors?.filter((c) => c.trim() !== '') ?? [];
|
|
125
|
+
return base.map((role, i) => loaderColor(own.length ? own[i % own.length] : role));
|
|
126
|
+
}
|
|
127
|
+
// -- znaki wyniku (pole 24 x 24, środek w 12,12) --
|
|
128
|
+
export const LOADER_MARKS = {
|
|
129
|
+
check: ['M5 12l5 5l10 -10'],
|
|
130
|
+
x: ['M18 6l-12 12', 'M6 6l12 12'],
|
|
131
|
+
alert: ['M12 7v6', 'M12 17v.01'],
|
|
132
|
+
};
|
|
133
|
+
// -- 1: bąbelki --
|
|
134
|
+
/**
|
|
135
|
+
* Promień orbity kropel w pętli 2,8 s: rozprysk na sprężynie, chwila w rozstawieniu, zbiegnięcie
|
|
136
|
+
* i chwila w jednej kropli. Od 6 do 36 jednostek.
|
|
137
|
+
*/
|
|
138
|
+
export function bubblesOrbit(t) {
|
|
139
|
+
const ph = (t % 2.8) / 2.8;
|
|
140
|
+
let k;
|
|
141
|
+
if (ph < 0.1)
|
|
142
|
+
k = 0;
|
|
143
|
+
else if (ph < 0.42)
|
|
144
|
+
k = LOADER_EASE.pop((ph - 0.1) / 0.32);
|
|
145
|
+
else if (ph < 0.62)
|
|
146
|
+
k = 1;
|
|
147
|
+
else if (ph < 0.86)
|
|
148
|
+
k = 1 - easeIn((ph - 0.62) / 0.24);
|
|
149
|
+
else
|
|
150
|
+
k = 0;
|
|
151
|
+
return 6 + 30 * k;
|
|
152
|
+
}
|
|
153
|
+
/** Prędkość kątowa (rad/s): im ciaśniej, tym szybciej - jak łyżwiarz przyciągający ręce. */
|
|
154
|
+
export function bubblesSpin(radius) {
|
|
155
|
+
return 1.4 + 4.2 * (1 - radius / 36);
|
|
156
|
+
}
|
|
157
|
+
// -- 2: płynny most --
|
|
158
|
+
/** Kropla w pętli: wahadło przez rząd kropek, rozciągnięte w kierunku lotu. */
|
|
159
|
+
export function bridgeTraveler(t) {
|
|
160
|
+
const x = 58 * Math.sin(t * 2.3);
|
|
161
|
+
const stretch = Math.min(0.35, Math.abs(58 * 2.3 * Math.cos(t * 2.3)) / 380);
|
|
162
|
+
return { x, sx: 1 + stretch, sy: 1 / (1 + stretch * 0.7) };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Kropka rzędu przy przelatującej kropli: przyciągnięta ku niej i nabrzmiała, gdy kropla jest
|
|
166
|
+
* blisko (mostek cieczy robi filtr). `gather` 1 -> 0 zbiera rząd do środka przy wyniku.
|
|
167
|
+
*/
|
|
168
|
+
export function bridgeDot(home, x, gather = 1) {
|
|
169
|
+
const dx = home * gather;
|
|
170
|
+
const near = Math.max(0, 1 - Math.abs(x - dx) / 34);
|
|
171
|
+
return { x: dx + (x - dx) * 0.18 * near, scale: 1 + 0.4 * near * gather };
|
|
172
|
+
}
|
|
173
|
+
/** Kapsuła wyniku: znak 18 + odstęp + napis, z marginesem 17 z każdej strony. */
|
|
174
|
+
export function bridgePillWidth(textWidth) {
|
|
175
|
+
return 18 + 6.4 + Math.max(0, textWidth) + 34;
|
|
176
|
+
}
|
|
177
|
+
// -- 3: kształty --
|
|
178
|
+
export const SHAPE_SAMPLES = 120;
|
|
179
|
+
function sampleRadius(fn) {
|
|
180
|
+
return Array.from({ length: SHAPE_SAMPLES }, (_, j) => fn((j / SHAPE_SAMPLES) * Math.PI * 2));
|
|
181
|
+
}
|
|
182
|
+
/** Wielokąt foremny jako promień od kąta (bok przy `cos(m) = cos(pi/n)`). */
|
|
183
|
+
function polygonRadius(n) {
|
|
184
|
+
const step = (2 * Math.PI) / n;
|
|
185
|
+
return (theta) => {
|
|
186
|
+
const m = (((theta % step) + step) % step) - step / 2;
|
|
187
|
+
return Math.cos(Math.PI / n) / Math.cos(m);
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Wygładzenie promienia (okno trójkątne 5 próbek) - zaokrągla rogi wielokątów. */
|
|
191
|
+
function smoothRadius(radii, passes) {
|
|
192
|
+
let a = radii;
|
|
193
|
+
for (let p = 0; p < passes; p++) {
|
|
194
|
+
a = a.map((_, j) => {
|
|
195
|
+
let s = 0;
|
|
196
|
+
for (let k = -2; k <= 2; k++)
|
|
197
|
+
s += a[(j + k + SHAPE_SAMPLES) % SHAPE_SAMPLES] * (3 - Math.abs(k));
|
|
198
|
+
return s / 9;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return a;
|
|
202
|
+
}
|
|
203
|
+
/** Promień o średniej 1 - wszystkie kształty mają to samo "pole", więc morf nie pompuje. */
|
|
204
|
+
function normalizeRadius(radii) {
|
|
205
|
+
const mean = radii.reduce((x, y) => x + y, 0) / SHAPE_SAMPLES;
|
|
206
|
+
return radii.map((v) => v / mean);
|
|
207
|
+
}
|
|
208
|
+
/** Kształty pętli po kolei: ciastko, kwadrat, koniczyna, pięciokąt, pigułka, słońce, trójkąt. */
|
|
209
|
+
export const LOADER_SHAPES = [
|
|
210
|
+
normalizeRadius(sampleRadius((th) => 1 + 0.085 * Math.cos(9 * th))),
|
|
211
|
+
normalizeRadius(smoothRadius(sampleRadius(polygonRadius(4)), 5)),
|
|
212
|
+
normalizeRadius(sampleRadius((th) => 1 + 0.16 * Math.cos(4 * th))),
|
|
213
|
+
normalizeRadius(smoothRadius(sampleRadius(polygonRadius(5)), 4)),
|
|
214
|
+
normalizeRadius(sampleRadius((th) => 1 / Math.sqrt(Math.pow(Math.cos(th) / 1.22, 2) + Math.pow(Math.sin(th) / 0.78, 2)))),
|
|
215
|
+
normalizeRadius(sampleRadius((th) => 1 + 0.07 * Math.cos(8 * th))),
|
|
216
|
+
normalizeRadius(smoothRadius(sampleRadius(polygonRadius(3)), 6)),
|
|
217
|
+
];
|
|
218
|
+
/** Kształt sukcesu (koło) i błędu (zaokrąglony kwadrat). */
|
|
219
|
+
export const SHAPE_CIRCLE = sampleRadius(() => 1);
|
|
220
|
+
export const SHAPE_SQUIRCLE = normalizeRadius(smoothRadius(sampleRadius(polygonRadius(4)), 3));
|
|
221
|
+
export function mixRadii(a, b, p) {
|
|
222
|
+
return a.map((v, j) => v + (b[j] - v) * p);
|
|
223
|
+
}
|
|
224
|
+
/** Zamknięta ścieżka kształtu o promieniach `radii`, obrócona o `rotation` (rad) i przeskalowana. */
|
|
225
|
+
export function shapePath(radii, rotation, scale) {
|
|
226
|
+
let d = '';
|
|
227
|
+
for (let j = 0; j < radii.length; j++) {
|
|
228
|
+
const th = (j / radii.length) * Math.PI * 2 + rotation;
|
|
229
|
+
d += `${j ? 'L' : 'M'}${(Math.cos(th) * radii[j] * scale).toFixed(2)} ${(Math.sin(th) * radii[j] * scale).toFixed(2)}`;
|
|
230
|
+
}
|
|
231
|
+
return d + 'Z';
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Klatka pętli kształtów: co 0,8 s morf do następnego kształtu na sprężynie i obrót o ćwierć
|
|
235
|
+
* obrotu z dociągnięciem, a pod spodem stały, wolny obrót i oddech skali.
|
|
236
|
+
*/
|
|
237
|
+
export function shapesFrame(t) {
|
|
238
|
+
const seg = 0.8;
|
|
239
|
+
const k = Math.floor(t / seg);
|
|
240
|
+
const q = clamp01((t - k * seg) / seg / 0.68);
|
|
241
|
+
const n = LOADER_SHAPES.length;
|
|
242
|
+
return {
|
|
243
|
+
radii: mixRadii(LOADER_SHAPES[k % n], LOADER_SHAPES[(k + 1) % n], LOADER_EASE.pop(q)),
|
|
244
|
+
rotation: ((t * 45 + 90 * (k + LOADER_EASE.snap(q))) * Math.PI) / 180,
|
|
245
|
+
scale: 30 * (1 + 0.035 * Math.sin(t * 4.4)),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// -- 4: pierścień --
|
|
249
|
+
export const RING_RADIUS = 44;
|
|
250
|
+
/**
|
|
251
|
+
* Łuk pierścienia od `a0` do `a1` (stopnie, 0 = godzina 3) z falą o amplitudzie `amp`.
|
|
252
|
+
* Fala gaśnie przy końcach łuku (obwiednia 10 stopni), więc końce są gładkie. Pusty napis,
|
|
253
|
+
* gdy łuk jest krótszy niż pół stopnia.
|
|
254
|
+
*/
|
|
255
|
+
export function ringArcPath(a0, a1, amp, phase, radius = RING_RADIUS) {
|
|
256
|
+
if (a1 - a0 < 0.5)
|
|
257
|
+
return '';
|
|
258
|
+
const n = Math.max(2, Math.ceil((a1 - a0) / 3));
|
|
259
|
+
let d = '';
|
|
260
|
+
for (let i = 0; i <= n; i++) {
|
|
261
|
+
const a = a0 + ((a1 - a0) * i) / n;
|
|
262
|
+
const rad = (a * Math.PI) / 180;
|
|
263
|
+
const env = Math.min(1, (a - a0) / 10, (a1 - a) / 10);
|
|
264
|
+
const r = radius + amp * env * Math.sin(rad * 14 - phase);
|
|
265
|
+
d += `${i ? 'L' : 'M'}${(Math.cos(rad) * r).toFixed(2)} ${(Math.sin(rad) * r).toFixed(2)}`;
|
|
266
|
+
}
|
|
267
|
+
return d;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Łuk nieokreślony: głowa i ogon gonią się co 1,5 s (głowa rusza pierwsza, ogon dogania),
|
|
271
|
+
* a całość obraca się stale. Kąty w stopniach, start u góry.
|
|
272
|
+
*/
|
|
273
|
+
export function ringIdleArc(t) {
|
|
274
|
+
const period = 1.5;
|
|
275
|
+
const n = Math.floor(t / period);
|
|
276
|
+
const ph = (t - n * period) / period;
|
|
277
|
+
const head = t * 150 + 260 * (n + easeInOut(clamp01(ph / 0.55)));
|
|
278
|
+
const tail = t * 150 + 260 * (n + easeInOut(clamp01((ph - 0.45) / 0.55)));
|
|
279
|
+
return { a0: tail - 90, a1: head - 90 + 16 };
|
|
280
|
+
}
|
|
281
|
+
/** Najbliższa "dwunasta" nie wcześniej niż `a0` - stąd łuk określony liczy postęp. */
|
|
282
|
+
export function ringBase(a0) {
|
|
283
|
+
return -90 + 360 * Math.ceil((a0 + 90) / 360);
|
|
284
|
+
}
|
|
285
|
+
// -- 6: fala --
|
|
286
|
+
export const WAVE_WIDTH = 240;
|
|
287
|
+
/** Odcinek fali od `x0` do `x1` (oś na y = 0), z obwiednią 8 jednostek przy końcach. */
|
|
288
|
+
export function wavePath(x0, x1, amp, phase) {
|
|
289
|
+
if (x1 - x0 < 0.5)
|
|
290
|
+
return '';
|
|
291
|
+
const n = Math.max(2, Math.ceil((x1 - x0) / 2));
|
|
292
|
+
let d = '';
|
|
293
|
+
for (let i = 0; i <= n; i++) {
|
|
294
|
+
const x = x0 + ((x1 - x0) * i) / n;
|
|
295
|
+
const env = Math.min(1, (x - x0) / 8, (x1 - x) / 8);
|
|
296
|
+
d += `${i ? 'L' : 'M'}${x.toFixed(2)} ${(amp * env * Math.sin(x * 0.314 - phase)).toFixed(2)}`;
|
|
297
|
+
}
|
|
298
|
+
return d;
|
|
299
|
+
}
|
|
300
|
+
/** Prosty odcinek toru; pusty, gdy krótszy niż jednostka. */
|
|
301
|
+
export function flatPath(x0, x1) {
|
|
302
|
+
return x1 - x0 > 1 ? `M${x0.toFixed(2)} 0L${x1.toFixed(2)} 0` : '';
|
|
303
|
+
}
|
|
304
|
+
/** Odcinek nieokreślony: wjeżdża z lewej, rośnie i ucieka w prawo co 1,8 s (nieprzycięty do toru). */
|
|
305
|
+
export function waveIdleSpan(t) {
|
|
306
|
+
const c = (t % 1.8) / 1.8;
|
|
307
|
+
return {
|
|
308
|
+
x0: -30 + 300 * easeInOut(clamp01((c - 0.3) / 0.7)),
|
|
309
|
+
x1: -30 + 300 * easeInOut(clamp01(c / 0.7)),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Wynik fali w chwili `rt` (s od wyniku): sukces wygładza falę, błąd szarpie nią; potem odcinek
|
|
314
|
+
* kurczy się do kreski na środku, a kreska zgina się w ptaszek albo rozcina w krzyżyk.
|
|
315
|
+
* `mark` mówi, że rysujemy już znak (grubsza kreska).
|
|
316
|
+
*/
|
|
317
|
+
export function waveResultPath(kind, rt, x0, x1, phase) {
|
|
318
|
+
const ok = kind === 'success';
|
|
319
|
+
if (ok && rt < 0.3)
|
|
320
|
+
return { d: wavePath(x0, x1, 3 * (1 - easeOut(rt / 0.3)), phase), mark: false };
|
|
321
|
+
if (!ok && rt < 0.35) {
|
|
322
|
+
return { d: wavePath(x0, x1, 3 + 5 * Math.sin(rt * 90) * (1 - rt / 0.35), phase + 2 * Math.sin(rt * 53)), mark: false };
|
|
323
|
+
}
|
|
324
|
+
const shrinkAt = ok ? 0.3 : 0.35;
|
|
325
|
+
const bendAt = ok ? 0.7 : 0.75;
|
|
326
|
+
if (rt < bendAt) {
|
|
327
|
+
const p = easeInOut((rt - shrinkAt) / 0.4);
|
|
328
|
+
return { d: wavePath(mix(x0, 80, p), mix(x1, 160, p), ok ? 0 : 3 * (1 - p), phase), mark: false };
|
|
329
|
+
}
|
|
330
|
+
const p = LOADER_EASE.pop(clamp01((rt - bendAt) / 0.6));
|
|
331
|
+
const from = ok ? [[80, 0], [120, 0], [160, 0]] : [[80, 0], [120, 0], [120, 0], [160, 0]];
|
|
332
|
+
const to = ok ? [[102, 0], [114, 12], [140, -14]] : [[106, -14], [134, 14], [106, 14], [134, -14]];
|
|
333
|
+
const pt = from.map((f, i) => `${mix(f[0], to[i][0], p).toFixed(2)} ${mix(f[1], to[i][1], p).toFixed(2)}`);
|
|
334
|
+
return { d: ok ? `M${pt[0]}L${pt[1]}L${pt[2]}` : `M${pt[0]}L${pt[1]}M${pt[2]}L${pt[3]}`, mark: true };
|
|
335
|
+
}
|
|
336
|
+
// -- 8: skaczące bąbelki --
|
|
337
|
+
/** Poziom ziemi: na nim stoją kule i leżą ich cienie. */
|
|
338
|
+
export const BOUNCE_GROUND = 34;
|
|
339
|
+
/**
|
|
340
|
+
* Kula `index` w pętli: skok co 0,9 s z przesunięciem fazy 0,15 s. Przy ziemi spłaszcza się
|
|
341
|
+
* (do 7% okresu od lądowania), w locie wyciąga w pionie proporcjonalnie do prędkości.
|
|
342
|
+
*/
|
|
343
|
+
export function bounceIdle(t, index, x) {
|
|
344
|
+
const period = 0.9;
|
|
345
|
+
let q = ((t - index * 0.15) / period) % 1;
|
|
346
|
+
if (q < 0)
|
|
347
|
+
q += 1;
|
|
348
|
+
const v = Math.abs(1 - 2 * q);
|
|
349
|
+
const c = Math.max(0, 1 - Math.min(q, 1 - q) / 0.07);
|
|
350
|
+
return {
|
|
351
|
+
x,
|
|
352
|
+
h: 48 * 4 * q * (1 - q),
|
|
353
|
+
sx: 1 + 0.38 * c - 0.1 * v * (1 - c),
|
|
354
|
+
sy: 1 - 0.32 * c + 0.14 * v * (1 - c),
|
|
355
|
+
k: 1,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Wynik kul w chwili `rt`: sukces to wspólny skok do środka, zlanie w jedną kulę i lądowanie
|
|
360
|
+
* z wygaszonym przysiadem; błąd - opadnięcie bez odbicia, kałuże bocznych kul spływają do
|
|
361
|
+
* środkowej, a ta nadmuchuje się w znak. `from` to pozycja kuli w chwili wyniku.
|
|
362
|
+
*/
|
|
363
|
+
export function bounceResult(kind, rt, index, from) {
|
|
364
|
+
if (kind === 'success') {
|
|
365
|
+
if (rt < 0.5) {
|
|
366
|
+
const p = rt / 0.5;
|
|
367
|
+
return { x: mix(from.x, 0, easeInOut(p)), h: mix(from.h, 58, easeOut(p)), sx: 1, sy: 1, k: 1 };
|
|
368
|
+
}
|
|
369
|
+
if (index > 0)
|
|
370
|
+
return { x: 0, h: 0, sx: 1, sy: 1, k: 0 };
|
|
371
|
+
if (rt < 0.85) {
|
|
372
|
+
const p = (rt - 0.5) / 0.35;
|
|
373
|
+
return { x: 0, h: 58 * (1 - p * p), sx: 0.92, sy: 1.1, k: 1 + 1.6 * easeOut(Math.min(1, p * 1.6)) };
|
|
374
|
+
}
|
|
375
|
+
const u = rt - 0.85;
|
|
376
|
+
const squash = Math.exp(-u * 6) * Math.cos(u * 18);
|
|
377
|
+
return { x: 0, h: 0, sx: 1 + 0.25 * squash, sy: 1 - 0.25 * squash, k: 2.6 };
|
|
378
|
+
}
|
|
379
|
+
if (rt < 0.35) {
|
|
380
|
+
const p = easeIn(rt / 0.35);
|
|
381
|
+
return { x: from.x, h: from.h * (1 - p), sx: 1, sy: 1 + 0.15 * p, k: 1 };
|
|
382
|
+
}
|
|
383
|
+
const flat = LOADER_EASE.wobble(clamp01((rt - 0.35) / 0.5));
|
|
384
|
+
const sx = 1 + 0.7 * flat;
|
|
385
|
+
const sy = 1 - 0.55 * flat;
|
|
386
|
+
if (index !== 1) {
|
|
387
|
+
const p = easeIn(clamp01((rt - 0.75) / 0.3));
|
|
388
|
+
return { x: mix(from.x, 0, p), h: 0, sx, sy, k: 1 - p };
|
|
389
|
+
}
|
|
390
|
+
const w = LOADER_EASE.wobble(clamp01((rt - 0.8) / 0.6));
|
|
391
|
+
return { x: 0, h: 0, sx: mix(sx, 1, w), sy: mix(sy, 1, w), k: 1 + 0.7 * w };
|
|
392
|
+
}
|
|
393
|
+
/** Cień kuli: maleje z wysokością, rośnie ze spłaszczeniem; znika razem z kulą. */
|
|
394
|
+
export function bounceShadow(ball) {
|
|
395
|
+
const f = Math.max(0.3, 1 - ball.h / 80);
|
|
396
|
+
return {
|
|
397
|
+
scale: f * Math.pow(Math.max(0, ball.k), 0.8) * (ball.sx > 1 ? ball.sx : 1),
|
|
398
|
+
opacity: ball.k < 0.05 ? 0 : 0.12 + 0.3 * f,
|
|
399
|
+
};
|
|
400
|
+
}
|
package/dist/spring.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprężyna jako funkcja postępu: tłumiony oscylator rozpięty na `q` od 0 do 1.
|
|
3
|
+
*
|
|
4
|
+
* Nie symulujemy w czasie rzeczywistym - krzywą liczy się raz i próbkuje do klatek WAAPI
|
|
5
|
+
* albo do `linear()` w CSS. Dzięki temu ruch zostaje zwykłą animacją przeglądarki (pauza,
|
|
6
|
+
* `cancel()`, `finished`), a nie pętlą `requestAnimationFrame`, którą trzeba sprzątać.
|
|
7
|
+
*
|
|
8
|
+
* `bounce` 0 = bez przestrzelenia (tłumienie krytyczne), 0.5 = wyraźne odbicie. Częstotliwość
|
|
9
|
+
* i rozciągnięcie czasu są dobrane tak, żeby przy `bounce <= 0.5` drgania wygasły przed `q = 1`
|
|
10
|
+
* (resztę ucina `q >= 1 -> 1`, więc ostatnia klatka zawsze trafia dokładnie w cel).
|
|
11
|
+
*/
|
|
12
|
+
export declare function springProgress(bounce: number): (q: number) => number;
|
|
13
|
+
/** Ta sama sprężyna jako `linear(...)` - do `transition-timing-function` i `easing` w WAAPI. */
|
|
14
|
+
export declare function springEasing(bounce: number, steps?: number): string;
|
|
15
|
+
/** Zaokrąglenie do zapisu w CSS - krótszy napis, a różnica poniżej piksela. */
|
|
16
|
+
export declare function round(value: number, digits: number): number;
|
package/dist/spring.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprężyna jako funkcja postępu: tłumiony oscylator rozpięty na `q` od 0 do 1.
|
|
3
|
+
*
|
|
4
|
+
* Nie symulujemy w czasie rzeczywistym - krzywą liczy się raz i próbkuje do klatek WAAPI
|
|
5
|
+
* albo do `linear()` w CSS. Dzięki temu ruch zostaje zwykłą animacją przeglądarki (pauza,
|
|
6
|
+
* `cancel()`, `finished`), a nie pętlą `requestAnimationFrame`, którą trzeba sprzątać.
|
|
7
|
+
*
|
|
8
|
+
* `bounce` 0 = bez przestrzelenia (tłumienie krytyczne), 0.5 = wyraźne odbicie. Częstotliwość
|
|
9
|
+
* i rozciągnięcie czasu są dobrane tak, żeby przy `bounce <= 0.5` drgania wygasły przed `q = 1`
|
|
10
|
+
* (resztę ucina `q >= 1 -> 1`, więc ostatnia klatka zawsze trafia dokładnie w cel).
|
|
11
|
+
*/
|
|
12
|
+
export function springProgress(bounce) {
|
|
13
|
+
const zeta = 1 - Math.min(Math.max(bounce, 0), 0.95);
|
|
14
|
+
const omega = 2 * Math.PI * 1.4;
|
|
15
|
+
return (q) => {
|
|
16
|
+
if (q <= 0)
|
|
17
|
+
return 0;
|
|
18
|
+
if (q >= 1)
|
|
19
|
+
return 1;
|
|
20
|
+
const t = q * 1.6;
|
|
21
|
+
if (zeta < 1) {
|
|
22
|
+
const wd = omega * Math.sqrt(1 - zeta * zeta);
|
|
23
|
+
return 1 - Math.exp(-zeta * omega * t) * (Math.cos(wd * t) + ((zeta * omega) / wd) * Math.sin(wd * t));
|
|
24
|
+
}
|
|
25
|
+
return 1 - Math.exp(-omega * t) * (1 + omega * t);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Ta sama sprężyna jako `linear(...)` - do `transition-timing-function` i `easing` w WAAPI. */
|
|
29
|
+
export function springEasing(bounce, steps = 40) {
|
|
30
|
+
const fn = springProgress(bounce);
|
|
31
|
+
const n = Math.max(2, Math.round(steps));
|
|
32
|
+
const points = [];
|
|
33
|
+
for (let i = 0; i <= n; i++)
|
|
34
|
+
points.push(i === n ? '1' : String(round(fn(i / n), 4)));
|
|
35
|
+
return `linear(${points.join(', ')})`;
|
|
36
|
+
}
|
|
37
|
+
/** Zaokrąglenie do zapisu w CSS - krótszy napis, a różnica poniżej piksela. */
|
|
38
|
+
export function round(value, digits) {
|
|
39
|
+
const k = 10 ** digits;
|
|
40
|
+
return Math.round(value * k) / k;
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KptStepperSchedule } from './stepper.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Światło przejścia kreatora wg `stepperSchedule`: kometa (`comet`, pozycjonowana względem tego
|
|
4
|
+
* samego przodka co linie) jedzie po kolejnych odcinkach `connectors`, gaśnie na ich końcu,
|
|
5
|
+
* a znacznik, do którego dociera kolor, wypuszcza falę - `::before` animowany z WAAPI, więc
|
|
6
|
+
* nie potrzebuje dodatkowego węzła. Wołaj po renderze nowego stanu. Przy "ogranicz ruch" nic
|
|
7
|
+
* nie robi.
|
|
8
|
+
*/
|
|
9
|
+
export declare function playStepperLight(schedule: KptStepperSchedule, comet: HTMLElement | null, connectors: readonly HTMLElement[], markers: readonly HTMLElement[]): void;
|