@delightstack/components 0.1.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 +136 -0
- package/SKILL.md +149 -0
- package/bin/agents.js +63 -0
- package/dist/actions/Alert.svelte +202 -0
- package/dist/actions/Alert.svelte.d.ts +36 -0
- package/dist/actions/Alert.svelte.d.ts.map +1 -0
- package/dist/actions/Button.svelte +1450 -0
- package/dist/actions/Button.svelte.d.ts +56 -0
- package/dist/actions/Button.svelte.d.ts.map +1 -0
- package/dist/actions/ButtonGroup.svelte +111 -0
- package/dist/actions/ButtonGroup.svelte.d.ts +41 -0
- package/dist/actions/ButtonGroup.svelte.d.ts.map +1 -0
- package/dist/actions/CommandPalette.svelte +939 -0
- package/dist/actions/CommandPalette.svelte.d.ts +37 -0
- package/dist/actions/CommandPalette.svelte.d.ts.map +1 -0
- package/dist/actions/ContextMenu.svelte +138 -0
- package/dist/actions/ContextMenu.svelte.d.ts +54 -0
- package/dist/actions/ContextMenu.svelte.d.ts.map +1 -0
- package/dist/actions/Modal.svelte +474 -0
- package/dist/actions/Modal.svelte.d.ts +28 -0
- package/dist/actions/Modal.svelte.d.ts.map +1 -0
- package/dist/actions/Popover.svelte +1214 -0
- package/dist/actions/Popover.svelte.d.ts +31 -0
- package/dist/actions/Popover.svelte.d.ts.map +1 -0
- package/dist/actions/Portal.svelte +80 -0
- package/dist/actions/Portal.svelte.d.ts +17 -0
- package/dist/actions/Portal.svelte.d.ts.map +1 -0
- package/dist/actions/ThemeToggle.svelte +345 -0
- package/dist/actions/ThemeToggle.svelte.d.ts +15 -0
- package/dist/actions/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/actions/index.d.ts +13 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +10 -0
- package/dist/actions/scrollbar.d.ts +48 -0
- package/dist/actions/scrollbar.d.ts.map +1 -0
- package/dist/actions/scrollbar.js +404 -0
- package/dist/display/Accordion.svelte +586 -0
- package/dist/display/Accordion.svelte.d.ts +41 -0
- package/dist/display/Accordion.svelte.d.ts.map +1 -0
- package/dist/display/Avatar.svelte +527 -0
- package/dist/display/Avatar.svelte.d.ts +22 -0
- package/dist/display/Avatar.svelte.d.ts.map +1 -0
- package/dist/display/AvatarGroup.svelte +298 -0
- package/dist/display/AvatarGroup.svelte.d.ts +31 -0
- package/dist/display/AvatarGroup.svelte.d.ts.map +1 -0
- package/dist/display/Calendar.svelte +1366 -0
- package/dist/display/Calendar.svelte.d.ts +58 -0
- package/dist/display/Calendar.svelte.d.ts.map +1 -0
- package/dist/display/Chart.svelte +1426 -0
- package/dist/display/Chart.svelte.d.ts +35 -0
- package/dist/display/Chart.svelte.d.ts.map +1 -0
- package/dist/display/Code.svelte +780 -0
- package/dist/display/Code.svelte.d.ts +19 -0
- package/dist/display/Code.svelte.d.ts.map +1 -0
- package/dist/display/Comparison.svelte +686 -0
- package/dist/display/Comparison.svelte.d.ts +22 -0
- package/dist/display/Comparison.svelte.d.ts.map +1 -0
- package/dist/display/Counter.svelte +285 -0
- package/dist/display/Counter.svelte.d.ts +21 -0
- package/dist/display/Counter.svelte.d.ts.map +1 -0
- package/dist/display/Expand.svelte +48 -0
- package/dist/display/Expand.svelte.d.ts +9 -0
- package/dist/display/Expand.svelte.d.ts.map +1 -0
- package/dist/display/List.svelte +294 -0
- package/dist/display/List.svelte.d.ts +40 -0
- package/dist/display/List.svelte.d.ts.map +1 -0
- package/dist/display/ListContextReset.svelte +19 -0
- package/dist/display/ListContextReset.svelte.d.ts +7 -0
- package/dist/display/ListContextReset.svelte.d.ts.map +1 -0
- package/dist/display/ListItem.svelte +834 -0
- package/dist/display/ListItem.svelte.d.ts +22 -0
- package/dist/display/ListItem.svelte.d.ts.map +1 -0
- package/dist/display/QR.svelte +1193 -0
- package/dist/display/QR.svelte.d.ts +23 -0
- package/dist/display/QR.svelte.d.ts.map +1 -0
- package/dist/display/SplitPane.svelte +744 -0
- package/dist/display/SplitPane.svelte.d.ts +25 -0
- package/dist/display/SplitPane.svelte.d.ts.map +1 -0
- package/dist/display/Stat.svelte +439 -0
- package/dist/display/Stat.svelte.d.ts +24 -0
- package/dist/display/Stat.svelte.d.ts.map +1 -0
- package/dist/display/Table.svelte +4654 -0
- package/dist/display/Table.svelte.d.ts +249 -0
- package/dist/display/Table.svelte.d.ts.map +1 -0
- package/dist/display/TableCellEditor.svelte +935 -0
- package/dist/display/TableCellEditor.svelte.d.ts +58 -0
- package/dist/display/TableCellEditor.svelte.d.ts.map +1 -0
- package/dist/display/Timeline.svelte +1258 -0
- package/dist/display/Timeline.svelte.d.ts +43 -0
- package/dist/display/Timeline.svelte.d.ts.map +1 -0
- package/dist/display/Tree.svelte +1740 -0
- package/dist/display/Tree.svelte.d.ts +74 -0
- package/dist/display/Tree.svelte.d.ts.map +1 -0
- package/dist/display/Typewriter.svelte +338 -0
- package/dist/display/Typewriter.svelte.d.ts +22 -0
- package/dist/display/Typewriter.svelte.d.ts.map +1 -0
- package/dist/display/index.d.ts +24 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +18 -0
- package/dist/feedback/Callout.svelte +529 -0
- package/dist/feedback/Callout.svelte.d.ts +24 -0
- package/dist/feedback/Callout.svelte.d.ts.map +1 -0
- package/dist/feedback/Confetti.svelte +631 -0
- package/dist/feedback/Confetti.svelte.d.ts +90 -0
- package/dist/feedback/Confetti.svelte.d.ts.map +1 -0
- package/dist/feedback/Progress.svelte +382 -0
- package/dist/feedback/Progress.svelte.d.ts +25 -0
- package/dist/feedback/Progress.svelte.d.ts.map +1 -0
- package/dist/feedback/Toast.svelte +967 -0
- package/dist/feedback/Toast.svelte.d.ts +54 -0
- package/dist/feedback/Toast.svelte.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +7 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/index.js +4 -0
- package/dist/form/Checkbox.svelte +449 -0
- package/dist/form/Checkbox.svelte.d.ts +27 -0
- package/dist/form/Checkbox.svelte.d.ts.map +1 -0
- package/dist/form/Fieldset.svelte +410 -0
- package/dist/form/Fieldset.svelte.d.ts +22 -0
- package/dist/form/Fieldset.svelte.d.ts.map +1 -0
- package/dist/form/FileUpload.svelte +934 -0
- package/dist/form/FileUpload.svelte.d.ts +41 -0
- package/dist/form/FileUpload.svelte.d.ts.map +1 -0
- package/dist/form/Form.svelte +530 -0
- package/dist/form/Form.svelte.d.ts +120 -0
- package/dist/form/Form.svelte.d.ts.map +1 -0
- package/dist/form/Input.svelte +2858 -0
- package/dist/form/Input.svelte.d.ts +66 -0
- package/dist/form/Input.svelte.d.ts.map +1 -0
- package/dist/form/Radio.svelte +507 -0
- package/dist/form/Radio.svelte.d.ts +39 -0
- package/dist/form/Radio.svelte.d.ts.map +1 -0
- package/dist/form/Range.svelte +912 -0
- package/dist/form/Range.svelte.d.ts +33 -0
- package/dist/form/Range.svelte.d.ts.map +1 -0
- package/dist/form/Rating.svelte +429 -0
- package/dist/form/Rating.svelte.d.ts +28 -0
- package/dist/form/Rating.svelte.d.ts.map +1 -0
- package/dist/form/Select.svelte +1933 -0
- package/dist/form/Select.svelte.d.ts +54 -0
- package/dist/form/Select.svelte.d.ts.map +1 -0
- package/dist/form/Toggle.svelte +645 -0
- package/dist/form/Toggle.svelte.d.ts +50 -0
- package/dist/form/Toggle.svelte.d.ts.map +1 -0
- package/dist/form/index.d.ts +15 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +10 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/layout/README.md +172 -0
- package/dist/media/Carousel.svelte +2424 -0
- package/dist/media/Carousel.svelte.d.ts +47 -0
- package/dist/media/Carousel.svelte.d.ts.map +1 -0
- package/dist/media/Gallery.svelte +2881 -0
- package/dist/media/Gallery.svelte.d.ts +82 -0
- package/dist/media/Gallery.svelte.d.ts.map +1 -0
- package/dist/media/Image.svelte +389 -0
- package/dist/media/Image.svelte.d.ts +33 -0
- package/dist/media/Image.svelte.d.ts.map +1 -0
- package/dist/media/PDF.svelte +1793 -0
- package/dist/media/PDF.svelte.d.ts +44 -0
- package/dist/media/PDF.svelte.d.ts.map +1 -0
- package/dist/media/Panorama.svelte +1391 -0
- package/dist/media/Panorama.svelte.d.ts +47 -0
- package/dist/media/Panorama.svelte.d.ts.map +1 -0
- package/dist/media/Video.svelte +2501 -0
- package/dist/media/Video.svelte.d.ts +58 -0
- package/dist/media/Video.svelte.d.ts.map +1 -0
- package/dist/media/carousel.d.ts +211 -0
- package/dist/media/carousel.d.ts.map +1 -0
- package/dist/media/carousel.js +408 -0
- package/dist/media/index.d.ts +11 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +5 -0
- package/dist/navigation/BottomSheet.svelte +636 -0
- package/dist/navigation/BottomSheet.svelte.d.ts +27 -0
- package/dist/navigation/BottomSheet.svelte.d.ts.map +1 -0
- package/dist/navigation/Breadcrumbs.svelte +611 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts +28 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts.map +1 -0
- package/dist/navigation/Pagination.svelte +641 -0
- package/dist/navigation/Pagination.svelte.d.ts +27 -0
- package/dist/navigation/Pagination.svelte.d.ts.map +1 -0
- package/dist/navigation/Steps.svelte +965 -0
- package/dist/navigation/Steps.svelte.d.ts +43 -0
- package/dist/navigation/Steps.svelte.d.ts.map +1 -0
- package/dist/navigation/Tabs.svelte +698 -0
- package/dist/navigation/Tabs.svelte.d.ts +41 -0
- package/dist/navigation/Tabs.svelte.d.ts.map +1 -0
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +5 -0
- package/package.json +139 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Where a local effect (burst/cannon) should originate from. Accepts a DOM
|
|
4
|
+
* element, a CSS selector resolved against the document, or a pointer/mouse
|
|
5
|
+
* event. Resolves to a point in the viewport and overrides `origin`.
|
|
6
|
+
*/
|
|
7
|
+
export type ConfettiTarget = Element | string | MouseEvent;
|
|
8
|
+
|
|
9
|
+
export interface ConfettiOptions {
|
|
10
|
+
/** How many confetti particles to launch */
|
|
11
|
+
particle_count?: number;
|
|
12
|
+
/** How wide the burst cone is, in degrees */
|
|
13
|
+
spread?: number;
|
|
14
|
+
/** Initial particle speed (pixels per tick) */
|
|
15
|
+
start_velocity?: number;
|
|
16
|
+
/** Per-tick velocity decay factor (lower = particles slow down faster) */
|
|
17
|
+
decay?: number;
|
|
18
|
+
/** Downward acceleration applied to particles each tick */
|
|
19
|
+
gravity?: number;
|
|
20
|
+
/** Horizontal drift applied to particles (negative = left, positive = right) */
|
|
21
|
+
drift?: number;
|
|
22
|
+
/** How many ticks particles live before fading out */
|
|
23
|
+
ticks?: number;
|
|
24
|
+
/** Launch point as viewport fractions (0–1 for both `x` and `y`) */
|
|
25
|
+
origin?: { x: number; y: number };
|
|
26
|
+
/**
|
|
27
|
+
* Anchor a local effect (burst/cannon) to an element, selector, or event.
|
|
28
|
+
* Takes precedence over `origin`. Ignored by global effects (fireworks,
|
|
29
|
+
* sides, rain) which manage their own positions.
|
|
30
|
+
*/
|
|
31
|
+
target?: ConfettiTarget;
|
|
32
|
+
/** The palette of particle colors to pick from */
|
|
33
|
+
colors?: string[];
|
|
34
|
+
/** Particle size multiplier (1 = default size) */
|
|
35
|
+
scalar?: number;
|
|
36
|
+
/** z-index of the confetti canvas overlay */
|
|
37
|
+
z_index?: number;
|
|
38
|
+
/** Launch angle in degrees (90 = straight up) */
|
|
39
|
+
angle?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CannonOptions extends ConfettiOptions {
|
|
43
|
+
/** Milliseconds between successive bursts */
|
|
44
|
+
interval?: number;
|
|
45
|
+
/** Total duration of the cannon effect in milliseconds */
|
|
46
|
+
duration?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface RainOptions extends ConfettiOptions {
|
|
50
|
+
/** Total duration of the rain effect in milliseconds */
|
|
51
|
+
duration?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const DEFAULT_COLORS = [
|
|
55
|
+
'#ff577f',
|
|
56
|
+
'#ff884b',
|
|
57
|
+
'#ffd384',
|
|
58
|
+
'#fff9b0',
|
|
59
|
+
'#3ec1d3',
|
|
60
|
+
'#7c5cbf',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
type ParticleShape = 'circle' | 'square';
|
|
64
|
+
const PARTICLE_SHAPES: ParticleShape[] = ['circle', 'square'];
|
|
65
|
+
|
|
66
|
+
interface Particle {
|
|
67
|
+
x: number;
|
|
68
|
+
y: number;
|
|
69
|
+
vx: number;
|
|
70
|
+
vy: number;
|
|
71
|
+
ticks_remaining: number;
|
|
72
|
+
color: string;
|
|
73
|
+
shape: ParticleShape;
|
|
74
|
+
scalar: number;
|
|
75
|
+
decay: number;
|
|
76
|
+
gravity: number;
|
|
77
|
+
drift: number;
|
|
78
|
+
rotation: number;
|
|
79
|
+
rotation_speed: number;
|
|
80
|
+
opacity: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let canvas: HTMLCanvasElement | null = null;
|
|
84
|
+
let ctx: CanvasRenderingContext2D | null = null;
|
|
85
|
+
let particles: Particle[] = [];
|
|
86
|
+
let animation_frame: number | null = null;
|
|
87
|
+
let active_intervals: number[] = [];
|
|
88
|
+
|
|
89
|
+
function isBrowser(): boolean {
|
|
90
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function checkReducedMotion(): boolean {
|
|
94
|
+
if (!isBrowser()) return false;
|
|
95
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---- Automatic targeting for local effects ----
|
|
99
|
+
// We track the most recent interaction point so a bare `confetti()` /
|
|
100
|
+
// `confetti.burst()` looks like it erupts from whatever the user just
|
|
101
|
+
// clicked, without the caller having to thread a target through every call.
|
|
102
|
+
let last_pointer: { x: number; y: number } | null = null;
|
|
103
|
+
|
|
104
|
+
function trackPointer(event: MouseEvent): void {
|
|
105
|
+
const w = window.innerWidth;
|
|
106
|
+
const h = window.innerHeight;
|
|
107
|
+
// Keyboard-activated clicks report detail 0 (and client coords of 0); fall
|
|
108
|
+
// back to the activated element's center so confetti still erupts from it.
|
|
109
|
+
if (event.detail === 0 && event.target instanceof Element) {
|
|
110
|
+
const rect = event.target.getBoundingClientRect();
|
|
111
|
+
last_pointer = {
|
|
112
|
+
x: (rect.left + rect.width / 2) / w,
|
|
113
|
+
y: (rect.top + rect.height / 2) / h,
|
|
114
|
+
};
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
last_pointer = { x: event.clientX / w, y: event.clientY / h };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (isBrowser()) {
|
|
121
|
+
// Capture phase so the point is recorded before a click handler fires confetti.
|
|
122
|
+
document.addEventListener('click', trackPointer, true);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Resolve an explicit target to a normalized viewport point, or null. */
|
|
126
|
+
function resolveTargetOrigin(target: ConfettiTarget): { x: number; y: number } | null {
|
|
127
|
+
if (!isBrowser()) return null;
|
|
128
|
+
const w = window.innerWidth;
|
|
129
|
+
const h = window.innerHeight;
|
|
130
|
+
|
|
131
|
+
if (typeof MouseEvent !== 'undefined' && target instanceof MouseEvent) {
|
|
132
|
+
return { x: target.clientX / w, y: target.clientY / h };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
136
|
+
if (el instanceof Element) {
|
|
137
|
+
const rect = el.getBoundingClientRect();
|
|
138
|
+
return {
|
|
139
|
+
x: (rect.left + rect.width / 2) / w,
|
|
140
|
+
y: (rect.top + rect.height / 2) / h,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Resolve the origin for a local effect. Precedence: explicit `target`,
|
|
149
|
+
* then explicit `origin`, then the last interaction point, then bottom-center.
|
|
150
|
+
*/
|
|
151
|
+
function resolveLocalOrigin(options?: ConfettiOptions): { x: number; y: number } {
|
|
152
|
+
if (options?.target != null) {
|
|
153
|
+
const resolved = resolveTargetOrigin(options.target);
|
|
154
|
+
if (resolved) return resolved;
|
|
155
|
+
}
|
|
156
|
+
if (options?.origin) return options.origin;
|
|
157
|
+
if (last_pointer) return last_pointer;
|
|
158
|
+
return { x: 0.5, y: 1.0 };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ensureCanvas(z_index: number = 1000): void {
|
|
162
|
+
if (!isBrowser()) return;
|
|
163
|
+
if (canvas) {
|
|
164
|
+
canvas.style.zIndex = String(z_index);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
canvas = document.createElement('canvas');
|
|
169
|
+
canvas.setAttribute('aria-hidden', 'true');
|
|
170
|
+
canvas.style.position = 'fixed';
|
|
171
|
+
canvas.style.top = '0';
|
|
172
|
+
canvas.style.left = '0';
|
|
173
|
+
canvas.style.width = '100vw';
|
|
174
|
+
canvas.style.height = '100vh';
|
|
175
|
+
canvas.style.pointerEvents = 'none';
|
|
176
|
+
canvas.style.zIndex = String(z_index);
|
|
177
|
+
|
|
178
|
+
const dpr = window.devicePixelRatio || 1;
|
|
179
|
+
canvas.width = window.innerWidth * dpr;
|
|
180
|
+
canvas.height = window.innerHeight * dpr;
|
|
181
|
+
|
|
182
|
+
ctx = canvas.getContext('2d');
|
|
183
|
+
if (ctx) {
|
|
184
|
+
ctx.scale(dpr, dpr);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
document.body.appendChild(canvas);
|
|
188
|
+
|
|
189
|
+
window.addEventListener('resize', handleResize);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handleResize(): void {
|
|
193
|
+
if (!canvas || !ctx) return;
|
|
194
|
+
const dpr = window.devicePixelRatio || 1;
|
|
195
|
+
canvas.width = window.innerWidth * dpr;
|
|
196
|
+
canvas.height = window.innerHeight * dpr;
|
|
197
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
198
|
+
ctx.scale(dpr, dpr);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function removeCanvas(): void {
|
|
202
|
+
if (!isBrowser()) return;
|
|
203
|
+
if (canvas) {
|
|
204
|
+
canvas.remove();
|
|
205
|
+
canvas = null;
|
|
206
|
+
ctx = null;
|
|
207
|
+
window.removeEventListener('resize', handleResize);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function randomInRange(min: number, max: number): number {
|
|
212
|
+
return Math.random() * (max - min) + min;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function createParticle(options: ConfettiOptions): Particle {
|
|
216
|
+
const angle = ((options.angle ?? 90) * Math.PI) / 180;
|
|
217
|
+
const spread = ((options.spread ?? 60) * Math.PI) / 180;
|
|
218
|
+
const start_velocity = options.start_velocity ?? 30;
|
|
219
|
+
const launch_angle = angle + randomInRange(-spread / 2, spread / 2);
|
|
220
|
+
const velocity = start_velocity * randomInRange(0.4, 1);
|
|
221
|
+
|
|
222
|
+
const colors = options.colors ?? DEFAULT_COLORS;
|
|
223
|
+
|
|
224
|
+
const w = window.innerWidth;
|
|
225
|
+
const h = window.innerHeight;
|
|
226
|
+
const origin = options.origin ?? { x: 0.5, y: 1.0 };
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
x: origin.x * w,
|
|
230
|
+
y: origin.y * h,
|
|
231
|
+
vx: Math.cos(launch_angle) * velocity + (options.drift ?? 0),
|
|
232
|
+
vy: -Math.sin(launch_angle) * velocity,
|
|
233
|
+
ticks_remaining: options.ticks ?? 200,
|
|
234
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
235
|
+
shape: PARTICLE_SHAPES[Math.floor(Math.random() * PARTICLE_SHAPES.length)],
|
|
236
|
+
scalar: (options.scalar ?? 1) * randomInRange(0.6, 1.0),
|
|
237
|
+
decay: options.decay ?? 0.94,
|
|
238
|
+
gravity: options.gravity ?? 1,
|
|
239
|
+
drift: options.drift ?? 0,
|
|
240
|
+
rotation: randomInRange(0, Math.PI * 2),
|
|
241
|
+
rotation_speed: randomInRange(-0.1, 0.1),
|
|
242
|
+
opacity: 1,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function drawParticle(context: CanvasRenderingContext2D, p: Particle): void {
|
|
247
|
+
const size = 6 * p.scalar;
|
|
248
|
+
context.save();
|
|
249
|
+
context.translate(p.x, p.y);
|
|
250
|
+
context.rotate(p.rotation);
|
|
251
|
+
context.globalAlpha = p.opacity;
|
|
252
|
+
context.fillStyle = p.color;
|
|
253
|
+
|
|
254
|
+
if (p.shape === 'circle') {
|
|
255
|
+
context.beginPath();
|
|
256
|
+
context.arc(0, 0, size / 2, 0, Math.PI * 2);
|
|
257
|
+
context.fill();
|
|
258
|
+
} else {
|
|
259
|
+
context.fillRect(-size / 2, -size / 2, size, size);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
context.restore();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function updateParticle(p: Particle): boolean {
|
|
266
|
+
p.x += p.vx;
|
|
267
|
+
p.y += p.vy;
|
|
268
|
+
p.vx *= p.decay;
|
|
269
|
+
p.vy = p.vy * p.decay + p.gravity;
|
|
270
|
+
p.rotation += p.rotation_speed;
|
|
271
|
+
p.ticks_remaining--;
|
|
272
|
+
|
|
273
|
+
// Fade out in the last 20% of ticks
|
|
274
|
+
const fade_start = 40;
|
|
275
|
+
if (p.ticks_remaining < fade_start) {
|
|
276
|
+
p.opacity = Math.max(0, p.ticks_remaining / fade_start);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return p.ticks_remaining > 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function tick(): void {
|
|
283
|
+
if (!ctx || !canvas) return;
|
|
284
|
+
|
|
285
|
+
const w = window.innerWidth;
|
|
286
|
+
const h = window.innerHeight;
|
|
287
|
+
ctx.clearRect(0, 0, w, h);
|
|
288
|
+
|
|
289
|
+
particles = particles.filter((p) => {
|
|
290
|
+
const alive = updateParticle(p);
|
|
291
|
+
if (alive) {
|
|
292
|
+
drawParticle(ctx!, p);
|
|
293
|
+
}
|
|
294
|
+
return alive;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (particles.length > 0) {
|
|
298
|
+
animation_frame = requestAnimationFrame(tick);
|
|
299
|
+
} else {
|
|
300
|
+
animation_frame = null;
|
|
301
|
+
removeCanvas();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function startLoop(): void {
|
|
306
|
+
if (animation_frame !== null) return;
|
|
307
|
+
animation_frame = requestAnimationFrame(tick);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function addParticles(options: ConfettiOptions): void {
|
|
311
|
+
if (checkReducedMotion()) return;
|
|
312
|
+
|
|
313
|
+
ensureCanvas(options.z_index);
|
|
314
|
+
|
|
315
|
+
const count = options.particle_count ?? 50;
|
|
316
|
+
for (let i = 0; i < count; i++) {
|
|
317
|
+
particles.push(createParticle(options));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
startLoop();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---- Programmatic API ----
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Fire a single burst of confetti. Shorthand for `confetti.burst()`.
|
|
327
|
+
*/
|
|
328
|
+
export function confetti(options?: ConfettiOptions): void {
|
|
329
|
+
confetti.burst(options);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Single explosion. A local effect: when no `target`/`origin` is given it
|
|
334
|
+
* erupts from the last clicked element/pointer, otherwise from bottom-center.
|
|
335
|
+
*/
|
|
336
|
+
confetti.burst = function burst(options?: ConfettiOptions): void {
|
|
337
|
+
addParticles({
|
|
338
|
+
particle_count: 50,
|
|
339
|
+
spread: 60,
|
|
340
|
+
start_velocity: 30,
|
|
341
|
+
...options,
|
|
342
|
+
origin: resolveLocalOrigin(options),
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Continuous stream with interval. A local effect: anchors to the last
|
|
348
|
+
* clicked element/pointer (or a given `target`/`origin`). Returns a stop function.
|
|
349
|
+
*/
|
|
350
|
+
confetti.cannon = function cannon(options?: CannonOptions): () => void {
|
|
351
|
+
const interval_ms = options?.interval ?? 100;
|
|
352
|
+
const duration = options?.duration ?? 3000;
|
|
353
|
+
const per_burst = Math.max(1, Math.floor((options?.particle_count ?? 50) / 10));
|
|
354
|
+
|
|
355
|
+
const opts: ConfettiOptions = {
|
|
356
|
+
...options,
|
|
357
|
+
particle_count: per_burst,
|
|
358
|
+
spread: options?.spread ?? 40,
|
|
359
|
+
start_velocity: options?.start_velocity ?? 35,
|
|
360
|
+
origin: resolveLocalOrigin(options),
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const id = window.setInterval(() => {
|
|
364
|
+
addParticles(opts);
|
|
365
|
+
}, interval_ms);
|
|
366
|
+
|
|
367
|
+
active_intervals.push(id);
|
|
368
|
+
|
|
369
|
+
// Fire first batch immediately
|
|
370
|
+
addParticles(opts);
|
|
371
|
+
|
|
372
|
+
const timeout = window.setTimeout(() => {
|
|
373
|
+
stop();
|
|
374
|
+
}, duration);
|
|
375
|
+
|
|
376
|
+
function stop() {
|
|
377
|
+
window.clearInterval(id);
|
|
378
|
+
window.clearTimeout(timeout);
|
|
379
|
+
active_intervals = active_intervals.filter((i) => i !== id);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return stop;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** Multiple large bursts from random positions across the viewport. A global effect. */
|
|
386
|
+
confetti.fireworks = function fireworks(options?: ConfettiOptions): void {
|
|
387
|
+
const burst_count = 8;
|
|
388
|
+
const stagger = 220;
|
|
389
|
+
|
|
390
|
+
for (let i = 0; i < burst_count; i++) {
|
|
391
|
+
const delay = i * stagger;
|
|
392
|
+
const timeout = window.setTimeout(() => {
|
|
393
|
+
addParticles({
|
|
394
|
+
particle_count: 65,
|
|
395
|
+
spread: 130,
|
|
396
|
+
start_velocity: 50,
|
|
397
|
+
scalar: 1.1,
|
|
398
|
+
...options,
|
|
399
|
+
origin: {
|
|
400
|
+
x: randomInRange(0.1, 0.9),
|
|
401
|
+
y: randomInRange(0.2, 0.55),
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}, delay);
|
|
405
|
+
active_intervals.push(timeout);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/** Two large bursts from the left/right edges angled inward. A global effect. */
|
|
410
|
+
confetti.sides = function sides(options?: ConfettiOptions): void {
|
|
411
|
+
// Left side burst
|
|
412
|
+
addParticles({
|
|
413
|
+
particle_count: 100,
|
|
414
|
+
spread: 65,
|
|
415
|
+
start_velocity: 60,
|
|
416
|
+
scalar: 1.1,
|
|
417
|
+
...options,
|
|
418
|
+
origin: { x: 0, y: 0.65 },
|
|
419
|
+
angle: 50,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Right side burst
|
|
423
|
+
addParticles({
|
|
424
|
+
particle_count: 100,
|
|
425
|
+
spread: 65,
|
|
426
|
+
start_velocity: 60,
|
|
427
|
+
scalar: 1.1,
|
|
428
|
+
...options,
|
|
429
|
+
origin: { x: 1, y: 0.65 },
|
|
430
|
+
angle: 130,
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
/** Gentle fall from top, full width. A global effect. Returns a stop function. */
|
|
435
|
+
confetti.rain = function rain(options?: RainOptions): () => void {
|
|
436
|
+
const interval_ms = 150;
|
|
437
|
+
const duration = options?.duration ?? 5000;
|
|
438
|
+
const per_batch = 3;
|
|
439
|
+
|
|
440
|
+
const opts: ConfettiOptions = {
|
|
441
|
+
gravity: 0.4,
|
|
442
|
+
start_velocity: 5,
|
|
443
|
+
decay: 0.98,
|
|
444
|
+
ticks: 400,
|
|
445
|
+
drift: 0,
|
|
446
|
+
spread: 180,
|
|
447
|
+
...options,
|
|
448
|
+
particle_count: per_batch,
|
|
449
|
+
angle: 270,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const id = window.setInterval(() => {
|
|
453
|
+
addParticles({
|
|
454
|
+
...opts,
|
|
455
|
+
origin: { x: Math.random(), y: -0.05 },
|
|
456
|
+
});
|
|
457
|
+
}, interval_ms);
|
|
458
|
+
|
|
459
|
+
active_intervals.push(id);
|
|
460
|
+
|
|
461
|
+
const timeout = window.setTimeout(() => {
|
|
462
|
+
stop();
|
|
463
|
+
}, duration);
|
|
464
|
+
|
|
465
|
+
function stop() {
|
|
466
|
+
window.clearInterval(id);
|
|
467
|
+
window.clearTimeout(timeout);
|
|
468
|
+
active_intervals = active_intervals.filter((i) => i !== id);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return stop;
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
/** Stop all active continuous effects and clear particles. */
|
|
475
|
+
confetti.stop = function stop(): void {
|
|
476
|
+
for (const id of active_intervals) {
|
|
477
|
+
window.clearInterval(id);
|
|
478
|
+
window.clearTimeout(id);
|
|
479
|
+
}
|
|
480
|
+
active_intervals = [];
|
|
481
|
+
particles = [];
|
|
482
|
+
|
|
483
|
+
if (animation_frame !== null) {
|
|
484
|
+
cancelAnimationFrame(animation_frame);
|
|
485
|
+
animation_frame = null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
removeCanvas();
|
|
489
|
+
};
|
|
490
|
+
</script>
|
|
491
|
+
|
|
492
|
+
<script lang="ts">
|
|
493
|
+
type Preset = 'burst' | 'cannon' | 'fireworks' | 'sides' | 'rain';
|
|
494
|
+
|
|
495
|
+
const propId = $props.id();
|
|
496
|
+
let {
|
|
497
|
+
/** Whether the confetti effect is active */
|
|
498
|
+
active = false,
|
|
499
|
+
|
|
500
|
+
/** Preset animation type */
|
|
501
|
+
preset = 'burst' as Preset,
|
|
502
|
+
|
|
503
|
+
/** Number of confetti particles (falls back to the preset default when unset) */
|
|
504
|
+
particle_count = undefined as number | undefined,
|
|
505
|
+
|
|
506
|
+
/** Spread angle in degrees */
|
|
507
|
+
spread = undefined as number | undefined,
|
|
508
|
+
|
|
509
|
+
/** Initial launch velocity */
|
|
510
|
+
start_velocity = undefined as number | undefined,
|
|
511
|
+
|
|
512
|
+
/** Velocity multiplier each frame (0-1) */
|
|
513
|
+
decay = undefined as number | undefined,
|
|
514
|
+
|
|
515
|
+
/** Downward acceleration per frame */
|
|
516
|
+
gravity = undefined as number | undefined,
|
|
517
|
+
|
|
518
|
+
/** Horizontal drift per frame */
|
|
519
|
+
drift = undefined as number | undefined,
|
|
520
|
+
|
|
521
|
+
/** Particle lifetime in frames */
|
|
522
|
+
ticks = undefined as number | undefined,
|
|
523
|
+
|
|
524
|
+
/** Launch origin {x, y} as fraction of viewport (0-1) for local effects */
|
|
525
|
+
origin = undefined as { x: number; y: number } | undefined,
|
|
526
|
+
|
|
527
|
+
/** Anchor a local effect (burst/cannon) to an element, selector, or event */
|
|
528
|
+
target = undefined as ConfettiTarget | undefined,
|
|
529
|
+
|
|
530
|
+
/** Array of hex color strings */
|
|
531
|
+
colors = undefined as string[] | undefined,
|
|
532
|
+
|
|
533
|
+
/** Size multiplier */
|
|
534
|
+
scalar = undefined as number | undefined,
|
|
535
|
+
|
|
536
|
+
/** CSS z-index for the canvas overlay */
|
|
537
|
+
z_index = 1000,
|
|
538
|
+
|
|
539
|
+
/** Duration in ms for continuous presets (cannon, rain) */
|
|
540
|
+
duration = 3000,
|
|
541
|
+
|
|
542
|
+
/** Skip animation when prefers-reduced-motion is set */
|
|
543
|
+
disable_for_reduced_motion = true,
|
|
544
|
+
|
|
545
|
+
/** Element ID (unused by canvas, for component identification) */
|
|
546
|
+
id = propId,
|
|
547
|
+
|
|
548
|
+
/** Additional CSS classes (unused by canvas, for component identification) */
|
|
549
|
+
class: class_name = '',
|
|
550
|
+
|
|
551
|
+
/** Called when all particles have finished */
|
|
552
|
+
onend = undefined as (() => void) | undefined,
|
|
553
|
+
} = $props();
|
|
554
|
+
|
|
555
|
+
let stop_fn: (() => void) | null = null;
|
|
556
|
+
let was_active = false;
|
|
557
|
+
|
|
558
|
+
// Only forward props the caller actually set, so each preset keeps its own
|
|
559
|
+
// defaults (e.g. the bigger fireworks/sides bursts) when a prop is left unset.
|
|
560
|
+
function buildOptions(): ConfettiOptions {
|
|
561
|
+
const opts: ConfettiOptions = { z_index };
|
|
562
|
+
if (particle_count !== undefined) opts.particle_count = particle_count;
|
|
563
|
+
if (spread !== undefined) opts.spread = spread;
|
|
564
|
+
if (start_velocity !== undefined) opts.start_velocity = start_velocity;
|
|
565
|
+
if (decay !== undefined) opts.decay = decay;
|
|
566
|
+
if (gravity !== undefined) opts.gravity = gravity;
|
|
567
|
+
if (drift !== undefined) opts.drift = drift;
|
|
568
|
+
if (ticks !== undefined) opts.ticks = ticks;
|
|
569
|
+
if (origin !== undefined) opts.origin = origin;
|
|
570
|
+
if (target !== undefined) opts.target = target;
|
|
571
|
+
if (colors !== undefined) opts.colors = colors;
|
|
572
|
+
if (scalar !== undefined) opts.scalar = scalar;
|
|
573
|
+
return opts;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function fire(): void {
|
|
577
|
+
if (disable_for_reduced_motion && checkReducedMotion()) return;
|
|
578
|
+
|
|
579
|
+
// Clean up any previous continuous effect
|
|
580
|
+
cleanup();
|
|
581
|
+
|
|
582
|
+
const opts = buildOptions();
|
|
583
|
+
|
|
584
|
+
switch (preset) {
|
|
585
|
+
case 'burst':
|
|
586
|
+
confetti.burst(opts);
|
|
587
|
+
break;
|
|
588
|
+
case 'cannon':
|
|
589
|
+
stop_fn = confetti.cannon({ ...opts, duration });
|
|
590
|
+
break;
|
|
591
|
+
case 'fireworks':
|
|
592
|
+
confetti.fireworks(opts);
|
|
593
|
+
break;
|
|
594
|
+
case 'sides':
|
|
595
|
+
confetti.sides(opts);
|
|
596
|
+
break;
|
|
597
|
+
case 'rain':
|
|
598
|
+
stop_fn = confetti.rain({ ...opts, duration });
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Schedule onend callback
|
|
603
|
+
if (onend) {
|
|
604
|
+
const estimated_duration =
|
|
605
|
+
preset === 'cannon' || preset === 'rain' ? duration + 5000 : 5000;
|
|
606
|
+
setTimeout(() => {
|
|
607
|
+
onend?.();
|
|
608
|
+
}, estimated_duration);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function cleanup(): void {
|
|
613
|
+
if (stop_fn) {
|
|
614
|
+
stop_fn();
|
|
615
|
+
stop_fn = null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
$effect(() => {
|
|
620
|
+
if (active && !was_active) {
|
|
621
|
+
fire();
|
|
622
|
+
} else if (!active && was_active) {
|
|
623
|
+
cleanup();
|
|
624
|
+
}
|
|
625
|
+
was_active = active;
|
|
626
|
+
|
|
627
|
+
return () => {
|
|
628
|
+
cleanup();
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
</script>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a local effect (burst/cannon) should originate from. Accepts a DOM
|
|
3
|
+
* element, a CSS selector resolved against the document, or a pointer/mouse
|
|
4
|
+
* event. Resolves to a point in the viewport and overrides `origin`.
|
|
5
|
+
*/
|
|
6
|
+
export type ConfettiTarget = Element | string | MouseEvent;
|
|
7
|
+
export interface ConfettiOptions {
|
|
8
|
+
/** How many confetti particles to launch */
|
|
9
|
+
particle_count?: number;
|
|
10
|
+
/** How wide the burst cone is, in degrees */
|
|
11
|
+
spread?: number;
|
|
12
|
+
/** Initial particle speed (pixels per tick) */
|
|
13
|
+
start_velocity?: number;
|
|
14
|
+
/** Per-tick velocity decay factor (lower = particles slow down faster) */
|
|
15
|
+
decay?: number;
|
|
16
|
+
/** Downward acceleration applied to particles each tick */
|
|
17
|
+
gravity?: number;
|
|
18
|
+
/** Horizontal drift applied to particles (negative = left, positive = right) */
|
|
19
|
+
drift?: number;
|
|
20
|
+
/** How many ticks particles live before fading out */
|
|
21
|
+
ticks?: number;
|
|
22
|
+
/** Launch point as viewport fractions (0–1 for both `x` and `y`) */
|
|
23
|
+
origin?: {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Anchor a local effect (burst/cannon) to an element, selector, or event.
|
|
29
|
+
* Takes precedence over `origin`. Ignored by global effects (fireworks,
|
|
30
|
+
* sides, rain) which manage their own positions.
|
|
31
|
+
*/
|
|
32
|
+
target?: ConfettiTarget;
|
|
33
|
+
/** The palette of particle colors to pick from */
|
|
34
|
+
colors?: string[];
|
|
35
|
+
/** Particle size multiplier (1 = default size) */
|
|
36
|
+
scalar?: number;
|
|
37
|
+
/** z-index of the confetti canvas overlay */
|
|
38
|
+
z_index?: number;
|
|
39
|
+
/** Launch angle in degrees (90 = straight up) */
|
|
40
|
+
angle?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface CannonOptions extends ConfettiOptions {
|
|
43
|
+
/** Milliseconds between successive bursts */
|
|
44
|
+
interval?: number;
|
|
45
|
+
/** Total duration of the cannon effect in milliseconds */
|
|
46
|
+
duration?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface RainOptions extends ConfettiOptions {
|
|
49
|
+
/** Total duration of the rain effect in milliseconds */
|
|
50
|
+
duration?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Fire a single burst of confetti. Shorthand for `confetti.burst()`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function confetti(options?: ConfettiOptions): void;
|
|
56
|
+
export declare namespace confetti {
|
|
57
|
+
var burst: (options?: ConfettiOptions) => void;
|
|
58
|
+
var cannon: (options?: CannonOptions) => () => void;
|
|
59
|
+
var fireworks: (options?: ConfettiOptions) => void;
|
|
60
|
+
var sides: (options?: ConfettiOptions) => void;
|
|
61
|
+
var rain: (options?: RainOptions) => () => void;
|
|
62
|
+
var stop: () => void;
|
|
63
|
+
}
|
|
64
|
+
declare const Confetti: import("svelte").Component<{
|
|
65
|
+
active?: boolean;
|
|
66
|
+
preset?: "burst" | "cannon" | "fireworks" | "sides" | "rain";
|
|
67
|
+
particle_count?: number | undefined;
|
|
68
|
+
spread?: number | undefined;
|
|
69
|
+
start_velocity?: number | undefined;
|
|
70
|
+
decay?: number | undefined;
|
|
71
|
+
gravity?: number | undefined;
|
|
72
|
+
drift?: number | undefined;
|
|
73
|
+
ticks?: number | undefined;
|
|
74
|
+
origin?: {
|
|
75
|
+
x: number;
|
|
76
|
+
y: number;
|
|
77
|
+
} | undefined;
|
|
78
|
+
target?: ConfettiTarget | undefined;
|
|
79
|
+
colors?: string[] | undefined;
|
|
80
|
+
scalar?: number | undefined;
|
|
81
|
+
z_index?: number;
|
|
82
|
+
duration?: number;
|
|
83
|
+
disable_for_reduced_motion?: boolean;
|
|
84
|
+
id?: string;
|
|
85
|
+
class?: string;
|
|
86
|
+
onend?: (() => void) | undefined;
|
|
87
|
+
}, {}, "">;
|
|
88
|
+
type Confetti = ReturnType<typeof Confetti>;
|
|
89
|
+
export default Confetti;
|
|
90
|
+
//# sourceMappingURL=Confetti.svelte.d.ts.map
|