solid-drift 0.19.0 → 0.21.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/README.md CHANGED
@@ -412,6 +412,32 @@ const { x, y, status } = createDrag(() => card, {
412
412
 
413
413
  Returns `{ x, y, status }`: the drag offset in pixels and `status` (`"idle"`, `"dragging"`, `"settling"`). SSR-safe: everything rests at 0. Under reduced motion the drag still tracks the pointer (direct manipulation is not animation) but release snaps instantly to the constrained target with no glide.
414
414
 
415
+ ### `createSwipe(ref, options?)`
416
+
417
+ Touch swipe gesture recognition: swipe-to-dismiss, carousels. While `createDrag` tracks the pointer continuously, `createSwipe` makes the discrete decision: was that gesture a swipe, and which way? On pointerup it compares travel, duration, and velocity against the thresholds and fires the matching callbacks plus the `lastSwipe` signal. Pointer Events give touch parity for free: mouse, touch, and pen run through the same path. For touch, set `touch-action: pan-y` on a horizontal swipe surface (or `pan-x` for vertical) so the browser does not hijack the gesture; use `none` when recognizing both axes.
418
+
419
+ ```tsx
420
+ import { createSwipe } from "solid-drift"
421
+
422
+ let deck!: HTMLDivElement
423
+ const { lastSwipe } = createSwipe(() => deck, {
424
+ onSwipeLeft: () => dismiss(),
425
+ onSwipeRight: () => keep(),
426
+ })
427
+ <div ref={deck} style={{ "touch-action": "pan-y" }}>card</div>
428
+ ```
429
+
430
+ | Option | Default | Description |
431
+ | -------------- | ------- | ------------------------------------------------------------------------ |
432
+ | `threshold` | `48` | Minimum travel in px to count as a swipe |
433
+ | `maxDuration` | `800` | Maximum gesture duration in ms |
434
+ | `minVelocity` | `0.4` | Minimum velocity in px/ms; a fast flick below `threshold` still counts |
435
+ | `axis` | `"both"`| `"x"`, `"y"`, or `"both"` |
436
+ | `onSwipe` | none | Called for every recognized swipe with `{ direction, distance, velocity, duration, from, to }` |
437
+ | `onSwipeLeft` / `onSwipeRight` / `onSwipeUp` / `onSwipeDown` | none | Per-direction callbacks |
438
+
439
+ Returns `{ lastSwipe, reset }`. A swipe counts when travel passes `threshold` inside `maxDuration`, or velocity passes `minVelocity`; slow long drags are not swipes. Recognition is not animation, so it works identically under reduced motion; the host decides how to animate the response. SSR-safe: `lastSwipe()` stays null and callbacks never fire.
440
+
415
441
  ### `createTrail(source, options?)`
416
442
 
417
443
  A signal that replays another signal's past: it returns the value the source had `delay` milliseconds ago, interpolated between samples. Chain trails off one source for follower effects (a cursor with a comet tail, cascading highlights), or trail a scroll progress for a delayed echo of the page. The trail catches up and parks exactly on the latest value when the source rests. The follow loop runs only while the trail is behind.
@@ -1312,6 +1338,58 @@ const packet = createRedPacket({ amount: 88, coins: 14 })
1312
1338
 
1313
1339
  Options: `coins` (default `12`), `amount` (total, default `88`), `spread` (burst size in px, default `160`), `gravity` (px/s^2, default `900`), `openDuration` (ms, default `500`), `burstDuration` (ms, default `1600`), `revealDuration` (ms, default `800`), `onOpen`, `onReveal(amount)`. Returns `{ status, coins, revealed, open, reset }`. SSR-safe and reduced-motion safe: `open()` jumps straight to revealed with no burst.
1314
1340
 
1341
+ ### `createConfetti(canvas, options?)`
1342
+
1343
+ Canvas confetti bursts: celebration physics with gravity, drag, sway, and tumbling paper flutter, rendered on the shared animation clock. Give it a canvas (a fullscreen fixed overlay with `pointer-events: none` is the classic setup) and call `burst()` from party moments: mints, wins, onboarding completions. Bursts accumulate, so rapid celebrations stack instead of replacing. The canvas is fitted to its CSS size times the device pixel ratio automatically.
1344
+
1345
+ ```tsx
1346
+ import { createConfetti } from "solid-drift"
1347
+
1348
+ let cvs!: HTMLCanvasElement
1349
+ const confetti = createConfetti(() => cvs, {
1350
+ onDone: () => console.log("party over"),
1351
+ })
1352
+ <canvas ref={cvs} style={{ position: "fixed", inset: "0", "pointer-events": "none" }} />
1353
+ <button onClick={() => confetti.burst()}>Celebrate</button>
1354
+ <button onClick={() => confetti.burst({ x: 0.2, y: 0.8 })}>Side popper</button>
1355
+ ```
1356
+
1357
+ Options: `count` (particles per burst, default `120`), `colors` (default a festive palette), `spread` (launch cone in degrees around straight up, default `70`), `power` (launch speed in px/s, default `900`), `gravity` (px/s^2, default `1100`), `drag` (default `1.2`), `size` ([min, max] px, default `[6, 12]`), `shapes` (default `["rect", "circle"]`), `lifetime` (ms, default `2600`), `onDone` (called when the last particle fades). Returns `{ active, burst, clear }`: `burst(origin?)` fires from a normalized origin (default `{ x: 0.5, y: 0.6 }`), `clear()` removes every particle immediately. SSR-safe: `burst()` is a no-op on the server. Under reduced motion `burst()` skips the particles but still calls `onDone`, so chained logic (show the prize after the celebration) keeps working. Tip: pair with `useLowPowerMode` to drop the count on weak devices.
1358
+
1359
+ ### `createEmojiBurst(canvas, options?)`
1360
+
1361
+ Emoji celebration burst: the same particle physics as confetti, but the particles are emoji glyphs that rise, tumble gently, and fade. Reactions, likes, level-ups, chat celebrations. Same canvas setup, same safety rules.
1362
+
1363
+ ```tsx
1364
+ import { createEmojiBurst } from "solid-drift"
1365
+
1366
+ let cvs!: HTMLCanvasElement
1367
+ const burst = createEmojiBurst(() => cvs, { emoji: ["❤️", "🔥"] })
1368
+ <canvas ref={cvs} style={{ position: "fixed", inset: "0", "pointer-events": "none" }} />
1369
+ <button onClick={() => burst.burst()}>Send love</button>
1370
+ ```
1371
+
1372
+ Options: `emoji` (default `["🎉", "✨", "💥", "⭐", "💖", "🥳"]`), `count` (default `24`), `power` (default `650`), `gravity` (default `700`: floatier than confetti), `drag` (default `1.6`), `size` ([min, max] px, default `[24, 48]`), `spread` (default `90`), `lifetime` (default `1800`), `onDone`. Returns `{ active, burst, clear }`.
1373
+
1374
+ ### `createScratch(canvas, options?)`
1375
+
1376
+ Scratch-off cover: a lottery-ticket foil over hidden content. The canvas paints an opaque cover (silver holographic foil by default, or your own art via `paint`) and pointer drags erase through it with `destination-out`. The cleared fraction is sampled from the alpha channel on a throttled cadence, and `onComplete` fires once past `threshold`. Layer it over the prize with absolute positioning, and set `touch-action: none` on the canvas so touch scratches do not scroll the page.
1377
+
1378
+ ```tsx
1379
+ import { createScratch } from "solid-drift"
1380
+
1381
+ let foil!: HTMLCanvasElement
1382
+ const scratch = createScratch(() => foil, {
1383
+ onComplete: () => console.log("revealed!"),
1384
+ })
1385
+ <div style={{ position: "relative" }}>
1386
+ <div>YOU WON 50 STARS</div>
1387
+ <canvas ref={foil} style={{ position: "absolute", inset: "0", "touch-action": "none" }} />
1388
+ </div>
1389
+ ```
1390
+
1391
+ Options: `threshold` (fraction cleared to complete, default `0.45`), `brush` (eraser radius in px, default `26`), `paint(ctx, w, h)` (custom cover art), `onComplete`. Returns `{ cleared, done, reset }`: `cleared()` is the 0..1 fraction erased, `reset()` repaints the cover. Scratching is direct manipulation, so it works identically under reduced motion. SSR-safe: `cleared()` stays 0.
1392
+
1315
1393
  ### Easings
1316
1394
 
1317
1395
  Named easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeInOutQuart`, `easeOutExpo`, `easeOutBack`, plus the cartoon set: `easeInBack` (anticipation dip before movement), `easeInOutBack` (wind-up, overshoot, settle), `easeOutElastic` (decaying rubber-band oscillation), `easeOutBounce` (shrinking cartoon bounces). Also `cubicBezier(x1, y1, x2, y2)` for CSS-style curves. Pass a name or a custom `(t) => number` function anywhere an easing is accepted.
package/dist/fun.d.ts CHANGED
@@ -149,3 +149,170 @@ export interface RedPacketControls {
149
149
  * ```
150
150
  */
151
151
  export declare function createRedPacket(options?: RedPacketOptions): RedPacketControls;
152
+ export interface ConfettiOptions {
153
+ /** Particles per burst. Default 120. */
154
+ count?: number;
155
+ /** Confetti colors. Default a festive palette. */
156
+ colors?: string[];
157
+ /** Launch cone in degrees around straight up. Default 70. */
158
+ spread?: number;
159
+ /** Launch speed in px/s. Default 900. */
160
+ power?: number;
161
+ /** Gravity in px/s^2. Default 1100. */
162
+ gravity?: number;
163
+ /** Air drag. Default 1.2. */
164
+ drag?: number;
165
+ /** Particle size range in px. Default [6, 12]. */
166
+ size?: [min: number, max: number];
167
+ /** Particle shapes. Default ["rect", "circle"]. */
168
+ shapes?: Array<"rect" | "circle">;
169
+ /** Particle lifetime in ms. Default 2600. */
170
+ lifetime?: number;
171
+ /** Called when the last particle fades. */
172
+ onDone?: () => void;
173
+ }
174
+ export interface ConfettiControls {
175
+ /** True while any particles are alive. */
176
+ active: Accessor<boolean>;
177
+ /**
178
+ * Fire a burst from a normalized origin (0..1 across the canvas).
179
+ * Default `{ x: 0.5, y: 0.6 }`.
180
+ */
181
+ burst: (origin?: {
182
+ x: number;
183
+ y: number;
184
+ }) => void;
185
+ /** Remove every particle immediately. */
186
+ clear: () => void;
187
+ }
188
+ /**
189
+ * Canvas confetti bursts: celebration physics with gravity, drag,
190
+ * sway, and tumbling paper flutter, rendered on the shared clock.
191
+ *
192
+ * Give it a canvas (a fullscreen fixed overlay with
193
+ * `pointer-events: none` is the classic setup) and call `burst()`
194
+ * from party moments: mints, wins, onboarding completions. Bursts
195
+ * accumulate, so rapid celebrations stack instead of replacing.
196
+ *
197
+ * SSR-safe: `burst()` is a no-op on the server. Under reduced motion
198
+ * `burst()` skips the particles but still calls `onDone`, so chained
199
+ * logic (show the prize after the celebration) keeps working.
200
+ *
201
+ * ```tsx
202
+ * import { createConfetti } from "solid-drift"
203
+ *
204
+ * let cvs!: HTMLCanvasElement
205
+ * const confetti = createConfetti(() => cvs, {
206
+ * onDone: () => console.log("party over"),
207
+ * })
208
+ * <canvas ref={cvs} style={{ position: "fixed", inset: "0", "pointer-events": "none" }} />
209
+ * <button onClick={() => confetti.burst()}>Celebrate</button>
210
+ * <button onClick={() => confetti.burst({ x: 0.2, y: 0.8 })}>Side popper</button>
211
+ * ```
212
+ */
213
+ export declare function createConfetti(canvas: () => HTMLCanvasElement | null | undefined, options?: ConfettiOptions): ConfettiControls;
214
+ export interface EmojiBurstOptions {
215
+ /** Emoji pool. Default ["\u{1F389}", "\u2728", "\u{1F4A5}", "\u2B50", "\u{1F496}", "\u{1F973}"]. */
216
+ emoji?: string[];
217
+ /** Particles per burst. Default 24. */
218
+ count?: number;
219
+ /** Launch speed in px/s. Default 650. */
220
+ power?: number;
221
+ /** Gravity in px/s^2. Default 700: floatier than confetti. */
222
+ gravity?: number;
223
+ /** Air drag. Default 1.6. */
224
+ drag?: number;
225
+ /** Font size range in px. Default [24, 48]. */
226
+ size?: [min: number, max: number];
227
+ /** Launch cone in degrees around straight up. Default 90. */
228
+ spread?: number;
229
+ /** Particle lifetime in ms. Default 1800. */
230
+ lifetime?: number;
231
+ /** Called when the last particle fades. */
232
+ onDone?: () => void;
233
+ }
234
+ export interface EmojiBurstControls {
235
+ /** True while any particles are alive. */
236
+ active: Accessor<boolean>;
237
+ /**
238
+ * Fire a burst from a normalized origin (0..1 across the canvas).
239
+ * Default `{ x: 0.5, y: 0.6 }`.
240
+ */
241
+ burst: (origin?: {
242
+ x: number;
243
+ y: number;
244
+ }) => void;
245
+ /** Remove every particle immediately. */
246
+ clear: () => void;
247
+ }
248
+ /**
249
+ * Emoji celebration burst: the same particle physics as confetti,
250
+ * but the particles are emoji glyphs that rise, tumble gently, and
251
+ * fade. Reactions, likes, level-ups, chat celebrations.
252
+ *
253
+ * Same canvas setup and safety rules as `createConfetti`: SSR-safe,
254
+ * and under reduced motion `burst()` skips the particles but still
255
+ * calls `onDone`.
256
+ *
257
+ * ```tsx
258
+ * import { createEmojiBurst } from "solid-drift"
259
+ *
260
+ * let cvs!: HTMLCanvasElement
261
+ * const burst = createEmojiBurst(() => cvs, { emoji: ["\u2764\uFE0F", "\u{1F525}"] })
262
+ * <canvas ref={cvs} style={{ position: "fixed", inset: "0", "pointer-events": "none" }} />
263
+ * <button onClick={() => burst.burst()}>Send love</button>
264
+ * ```
265
+ */
266
+ export declare function createEmojiBurst(canvas: () => HTMLCanvasElement | null | undefined, options?: EmojiBurstOptions): EmojiBurstControls;
267
+ export interface ScratchOptions {
268
+ /**
269
+ * Fraction of the cover that must be cleared to complete, 0 to 1.
270
+ * Default 0.45.
271
+ */
272
+ threshold?: number;
273
+ /** Eraser brush radius in px. Default 26. */
274
+ brush?: number;
275
+ /**
276
+ * Paint the cover yourself: foil art, branding, "scratch here" copy.
277
+ * Receives the 2D context and the canvas size in px. Default is a
278
+ * silver holographic foil.
279
+ */
280
+ paint?: (ctx: CanvasRenderingContext2D, w: number, h: number) => void;
281
+ /** Called once when clearing passes the threshold. */
282
+ onComplete?: () => void;
283
+ }
284
+ export interface ScratchControls {
285
+ /** 0..1 fraction of the cover cleared. */
286
+ cleared: Accessor<number>;
287
+ /** True after clearing passes the threshold. */
288
+ done: Accessor<boolean>;
289
+ /** Repaint the cover and reset progress. */
290
+ reset: () => void;
291
+ }
292
+ /**
293
+ * Scratch-off cover: a lottery-ticket foil over hidden content.
294
+ *
295
+ * The canvas paints an opaque cover (silver holographic foil by
296
+ * default, or your own art via `paint`). Pointer drags erase through
297
+ * it with `destination-out`; the cleared fraction is sampled from the
298
+ * alpha channel on a throttled cadence, and `onComplete` fires once
299
+ * past `threshold`. Layer it over the prize with absolute positioning.
300
+ *
301
+ * Set `touch-action: none` on the canvas so touch scratches do not
302
+ * scroll the page. Scratching is direct manipulation, so it works
303
+ * identically under reduced motion. SSR-safe: `cleared()` stays 0.
304
+ *
305
+ * ```tsx
306
+ * import { createScratch } from "solid-drift"
307
+ *
308
+ * let foil!: HTMLCanvasElement
309
+ * const scratch = createScratch(() => foil, {
310
+ * onComplete: () => console.log("revealed!"),
311
+ * })
312
+ * <div style={{ position: "relative" }}>
313
+ * <div>YOU WON 50 STARS</div>
314
+ * <canvas ref={foil} style={{ position: "absolute", inset: "0", "touch-action": "none" }} />
315
+ * </div>
316
+ * ```
317
+ */
318
+ export declare function createScratch(canvas: () => HTMLCanvasElement | null | undefined, options?: ScratchOptions): ScratchControls;
package/dist/fun.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createSignal, onCleanup, } from "solid-js";
1
+ import { createEffect, createSignal, onCleanup, } from "solid-js";
2
2
  import { now, schedule } from "./engine.js";
3
3
  import { resolveEasing } from "./easing.js";
4
4
  import { createTween } from "./tween.js";
@@ -274,3 +274,450 @@ export function createRedPacket(options = {}) {
274
274
  onCleanup(() => cancel?.());
275
275
  return { status, coins: coinList, revealed, open, reset };
276
276
  }
277
+ /**
278
+ * A canvas particle layer with gravity, drag, sway, and flutter, run
279
+ * on the shared animation clock. One rAF loop per layer, only while
280
+ * particles are alive. The canvas is fitted to its CSS size times the
281
+ * device pixel ratio on every frame, so a fullscreen fixed overlay
282
+ * canvas just works.
283
+ */
284
+ function createParticleLayer(canvas, gravity, drag, onDone) {
285
+ const server = typeof window === "undefined";
286
+ const [active, setActive] = createSignal(false);
287
+ let particles = [];
288
+ let cancel = null;
289
+ let lastT = 0;
290
+ const MAX_PARTICLES = 1200;
291
+ const loop = (t) => {
292
+ const cvs = canvas();
293
+ if (!cvs) {
294
+ particles = [];
295
+ setActive(false);
296
+ cancel = null;
297
+ return false;
298
+ }
299
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
300
+ const w = cvs.clientWidth;
301
+ const h = cvs.clientHeight;
302
+ if (w === 0 || h === 0)
303
+ return true; // no layout yet; keep waiting
304
+ const pw = Math.round(w * dpr);
305
+ const ph = Math.round(h * dpr);
306
+ if (cvs.width !== pw || cvs.height !== ph) {
307
+ cvs.width = pw;
308
+ cvs.height = ph;
309
+ }
310
+ const ctx = cvs.getContext("2d");
311
+ if (!ctx) {
312
+ particles = [];
313
+ setActive(false);
314
+ cancel = null;
315
+ return false;
316
+ }
317
+ const dt = lastT === 0 ? 0.016 : Math.min(0.05, Math.max(0.001, (t - lastT) / 1000));
318
+ lastT = t;
319
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
320
+ ctx.clearRect(0, 0, w, h);
321
+ const dk = Math.max(0, 1 - drag * dt);
322
+ particles = particles.filter((p) => {
323
+ const age = t - p.born;
324
+ if (age >= p.life)
325
+ return false;
326
+ p.vx *= dk;
327
+ p.vy = p.vy * dk + gravity * dt;
328
+ p.swayPhase += p.swaySpeed * dt;
329
+ p.x += (p.vx + Math.sin(p.swayPhase) * p.sway) * dt;
330
+ p.y += p.vy * dt;
331
+ p.rot += p.vr * dt;
332
+ // Fade over the last quarter of life.
333
+ ctx.globalAlpha = Math.min(1, (p.life - age) / (p.life * 0.25));
334
+ if (p.shape === "circle") {
335
+ ctx.fillStyle = p.color;
336
+ ctx.beginPath();
337
+ ctx.arc(p.x, p.y, p.size / 2, 0, Math.PI * 2);
338
+ ctx.fill();
339
+ }
340
+ else if (p.shape === "emoji") {
341
+ ctx.font = `${p.size}px serif`;
342
+ ctx.textAlign = "center";
343
+ ctx.textBaseline = "middle";
344
+ ctx.save();
345
+ ctx.translate(p.x, p.y);
346
+ ctx.rotate(p.rot * 0.3);
347
+ ctx.fillText(p.text, 0, 0);
348
+ ctx.restore();
349
+ }
350
+ else {
351
+ // Paper flutter: the rect tumbles and its width breathes.
352
+ const squash = 0.35 + 0.65 * Math.abs(Math.sin(p.swayPhase * 2));
353
+ ctx.fillStyle = p.color;
354
+ ctx.save();
355
+ ctx.translate(p.x, p.y);
356
+ ctx.rotate(p.rot);
357
+ ctx.scale(1, squash);
358
+ ctx.fillRect(-p.size / 2, -p.size / 4, p.size, p.size / 2);
359
+ ctx.restore();
360
+ }
361
+ return true;
362
+ });
363
+ ctx.globalAlpha = 1;
364
+ if (particles.length === 0) {
365
+ setActive(false);
366
+ cancel = null;
367
+ onDone?.();
368
+ return false;
369
+ }
370
+ return true;
371
+ };
372
+ const spawn = (make) => {
373
+ if (server)
374
+ return;
375
+ particles.push(make());
376
+ if (particles.length > MAX_PARTICLES) {
377
+ particles.splice(0, particles.length - MAX_PARTICLES);
378
+ }
379
+ if (!cancel) {
380
+ lastT = 0;
381
+ setActive(true);
382
+ cancel = schedule(loop);
383
+ }
384
+ };
385
+ const clear = () => {
386
+ cancel?.();
387
+ cancel = null;
388
+ particles = [];
389
+ setActive(false);
390
+ };
391
+ if (!server) {
392
+ onCleanup(() => {
393
+ cancel?.();
394
+ cancel = null;
395
+ });
396
+ }
397
+ return { active, spawn, clear };
398
+ }
399
+ const CONFETTI_COLORS = [
400
+ "#ff4757",
401
+ "#ffa502",
402
+ "#2ed573",
403
+ "#1e90ff",
404
+ "#eccc68",
405
+ "#ff6b81",
406
+ "#7bed9f",
407
+ "#70a1ff",
408
+ "#f368e0",
409
+ "#48dbfb",
410
+ ];
411
+ /**
412
+ * Canvas confetti bursts: celebration physics with gravity, drag,
413
+ * sway, and tumbling paper flutter, rendered on the shared clock.
414
+ *
415
+ * Give it a canvas (a fullscreen fixed overlay with
416
+ * `pointer-events: none` is the classic setup) and call `burst()`
417
+ * from party moments: mints, wins, onboarding completions. Bursts
418
+ * accumulate, so rapid celebrations stack instead of replacing.
419
+ *
420
+ * SSR-safe: `burst()` is a no-op on the server. Under reduced motion
421
+ * `burst()` skips the particles but still calls `onDone`, so chained
422
+ * logic (show the prize after the celebration) keeps working.
423
+ *
424
+ * ```tsx
425
+ * import { createConfetti } from "solid-drift"
426
+ *
427
+ * let cvs!: HTMLCanvasElement
428
+ * const confetti = createConfetti(() => cvs, {
429
+ * onDone: () => console.log("party over"),
430
+ * })
431
+ * <canvas ref={cvs} style={{ position: "fixed", inset: "0", "pointer-events": "none" }} />
432
+ * <button onClick={() => confetti.burst()}>Celebrate</button>
433
+ * <button onClick={() => confetti.burst({ x: 0.2, y: 0.8 })}>Side popper</button>
434
+ * ```
435
+ */
436
+ export function createConfetti(canvas, options = {}) {
437
+ const { count = 120, colors = CONFETTI_COLORS, spread = 70, power = 900, gravity = 1100, drag = 1.2, size = [6, 12], shapes = ["rect", "circle"], lifetime = 2600, onDone, } = options;
438
+ const server = typeof window === "undefined";
439
+ const layer = createParticleLayer(canvas, gravity, drag, onDone);
440
+ const burst = (origin = { x: 0.5, y: 0.6 }) => {
441
+ if (server || prefersReducedMotion()) {
442
+ onDone?.();
443
+ return;
444
+ }
445
+ const cvs = canvas();
446
+ if (!cvs || cvs.clientWidth === 0)
447
+ return;
448
+ const ox = origin.x * cvs.clientWidth;
449
+ const oy = origin.y * cvs.clientHeight;
450
+ const [minSize, maxSize] = size;
451
+ for (let i = 0; i < count; i++) {
452
+ const angle = ((-90 + (Math.random() - 0.5) * spread) * Math.PI) / 180;
453
+ const speed = power * (0.4 + Math.random() * 0.8);
454
+ const shape = shapes[(Math.random() * shapes.length) | 0];
455
+ layer.spawn(() => ({
456
+ x: ox,
457
+ y: oy,
458
+ vx: Math.cos(angle) * speed,
459
+ vy: Math.sin(angle) * speed,
460
+ rot: Math.random() * Math.PI * 2,
461
+ vr: (Math.random() - 0.5) * 20,
462
+ sway: 40 + Math.random() * 60,
463
+ swaySpeed: 4 + Math.random() * 6,
464
+ swayPhase: Math.random() * Math.PI * 2,
465
+ size: minSize + Math.random() * (maxSize - minSize),
466
+ color: colors[(Math.random() * colors.length) | 0],
467
+ shape,
468
+ text: "",
469
+ born: now(),
470
+ life: lifetime * (0.7 + Math.random() * 0.6),
471
+ }));
472
+ }
473
+ };
474
+ return { active: layer.active, burst, clear: layer.clear };
475
+ }
476
+ const BURST_EMOJI = ["\u{1F389}", "\u2728", "\u{1F4A5}", "\u2B50", "\u{1F496}", "\u{1F973}"];
477
+ /**
478
+ * Emoji celebration burst: the same particle physics as confetti,
479
+ * but the particles are emoji glyphs that rise, tumble gently, and
480
+ * fade. Reactions, likes, level-ups, chat celebrations.
481
+ *
482
+ * Same canvas setup and safety rules as `createConfetti`: SSR-safe,
483
+ * and under reduced motion `burst()` skips the particles but still
484
+ * calls `onDone`.
485
+ *
486
+ * ```tsx
487
+ * import { createEmojiBurst } from "solid-drift"
488
+ *
489
+ * let cvs!: HTMLCanvasElement
490
+ * const burst = createEmojiBurst(() => cvs, { emoji: ["\u2764\uFE0F", "\u{1F525}"] })
491
+ * <canvas ref={cvs} style={{ position: "fixed", inset: "0", "pointer-events": "none" }} />
492
+ * <button onClick={() => burst.burst()}>Send love</button>
493
+ * ```
494
+ */
495
+ export function createEmojiBurst(canvas, options = {}) {
496
+ const { emoji = BURST_EMOJI, count = 24, power = 650, gravity = 700, drag = 1.6, size = [24, 48], spread = 90, lifetime = 1800, onDone, } = options;
497
+ const server = typeof window === "undefined";
498
+ const layer = createParticleLayer(canvas, gravity, drag, onDone);
499
+ const burst = (origin = { x: 0.5, y: 0.6 }) => {
500
+ if (server || prefersReducedMotion()) {
501
+ onDone?.();
502
+ return;
503
+ }
504
+ const cvs = canvas();
505
+ if (!cvs || cvs.clientWidth === 0)
506
+ return;
507
+ const ox = origin.x * cvs.clientWidth;
508
+ const oy = origin.y * cvs.clientHeight;
509
+ const [minSize, maxSize] = size;
510
+ for (let i = 0; i < count; i++) {
511
+ const angle = ((-90 + (Math.random() - 0.5) * spread) * Math.PI) / 180;
512
+ const speed = power * (0.5 + Math.random() * 0.7);
513
+ layer.spawn(() => ({
514
+ x: ox,
515
+ y: oy,
516
+ vx: Math.cos(angle) * speed,
517
+ vy: Math.sin(angle) * speed,
518
+ rot: (Math.random() - 0.5) * Math.PI,
519
+ vr: (Math.random() - 0.5) * 6,
520
+ sway: 30 + Math.random() * 40,
521
+ swaySpeed: 3 + Math.random() * 4,
522
+ swayPhase: Math.random() * Math.PI * 2,
523
+ size: minSize + Math.random() * (maxSize - minSize),
524
+ color: "#ffffff",
525
+ shape: "emoji",
526
+ text: emoji[(Math.random() * emoji.length) | 0],
527
+ born: now(),
528
+ life: lifetime * (0.7 + Math.random() * 0.6),
529
+ }));
530
+ }
531
+ };
532
+ return { active: layer.active, burst, clear: layer.clear };
533
+ }
534
+ /** Default cover: a silver holographic foil with sheen streaks. */
535
+ function paintFoil(ctx, w, h) {
536
+ const g = ctx.createLinearGradient(0, 0, w, h);
537
+ g.addColorStop(0, "#dde2e9");
538
+ g.addColorStop(0.45, "#a9b2bf");
539
+ g.addColorStop(0.55, "#c3ccd7");
540
+ g.addColorStop(1, "#98a2b1");
541
+ ctx.fillStyle = g;
542
+ ctx.fillRect(0, 0, w, h);
543
+ // Diagonal sheen streaks.
544
+ ctx.strokeStyle = "rgba(255, 255, 255, 0.35)";
545
+ ctx.lineWidth = Math.max(2, w * 0.02);
546
+ for (let i = -2; i < 5; i++) {
547
+ ctx.beginPath();
548
+ ctx.moveTo((i * w) / 3, -10);
549
+ ctx.lineTo((i * w) / 3 + h * 0.6, h + 10);
550
+ ctx.stroke();
551
+ }
552
+ // Speckle.
553
+ ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
554
+ const n = Math.floor((w * h) / 900);
555
+ for (let i = 0; i < n; i++) {
556
+ const x = Math.random() * w;
557
+ const y = Math.random() * h;
558
+ ctx.fillRect(x, y, 1.5, 1.5);
559
+ }
560
+ }
561
+ /**
562
+ * Scratch-off cover: a lottery-ticket foil over hidden content.
563
+ *
564
+ * The canvas paints an opaque cover (silver holographic foil by
565
+ * default, or your own art via `paint`). Pointer drags erase through
566
+ * it with `destination-out`; the cleared fraction is sampled from the
567
+ * alpha channel on a throttled cadence, and `onComplete` fires once
568
+ * past `threshold`. Layer it over the prize with absolute positioning.
569
+ *
570
+ * Set `touch-action: none` on the canvas so touch scratches do not
571
+ * scroll the page. Scratching is direct manipulation, so it works
572
+ * identically under reduced motion. SSR-safe: `cleared()` stays 0.
573
+ *
574
+ * ```tsx
575
+ * import { createScratch } from "solid-drift"
576
+ *
577
+ * let foil!: HTMLCanvasElement
578
+ * const scratch = createScratch(() => foil, {
579
+ * onComplete: () => console.log("revealed!"),
580
+ * })
581
+ * <div style={{ position: "relative" }}>
582
+ * <div>YOU WON 50 STARS</div>
583
+ * <canvas ref={foil} style={{ position: "absolute", inset: "0", "touch-action": "none" }} />
584
+ * </div>
585
+ * ```
586
+ */
587
+ export function createScratch(canvas, options = {}) {
588
+ const { threshold = 0.45, brush = 26, paint, onComplete } = options;
589
+ const server = typeof window === "undefined";
590
+ const [cleared, setCleared] = createSignal(0);
591
+ const [done, setDone] = createSignal(false);
592
+ if (server) {
593
+ return { cleared, done, reset: () => { } };
594
+ }
595
+ const paintCover = () => {
596
+ const cvs = canvas();
597
+ if (!cvs)
598
+ return;
599
+ const rect = cvs.getBoundingClientRect();
600
+ const w = Math.max(1, Math.round(rect.width));
601
+ const h = Math.max(1, Math.round(rect.height));
602
+ if (cvs.width !== w || cvs.height !== h) {
603
+ cvs.width = w;
604
+ cvs.height = h;
605
+ }
606
+ const ctx = cvs.getContext("2d");
607
+ if (!ctx)
608
+ return;
609
+ ctx.globalCompositeOperation = "source-over";
610
+ ctx.globalAlpha = 1;
611
+ if (paint)
612
+ paint(ctx, w, h);
613
+ else
614
+ paintFoil(ctx, w, h);
615
+ };
616
+ /** Fraction of sampled pixels erased, via the alpha channel. */
617
+ const sampleCleared = () => {
618
+ const cvs = canvas();
619
+ if (!cvs)
620
+ return;
621
+ const ctx = cvs.getContext("2d");
622
+ if (!ctx)
623
+ return;
624
+ const { width: w, height: h } = cvs;
625
+ if (w === 0 || h === 0)
626
+ return;
627
+ let data;
628
+ try {
629
+ data = ctx.getImageData(0, 0, w, h).data;
630
+ }
631
+ catch {
632
+ return; // tainted canvas: cannot read pixels
633
+ }
634
+ const step = 6;
635
+ let clear = 0;
636
+ let total = 0;
637
+ for (let y = 0; y < h; y += step) {
638
+ for (let x = 0; x < w; x += step) {
639
+ total++;
640
+ if (data[(y * w + x) * 4 + 3] < 128)
641
+ clear++;
642
+ }
643
+ }
644
+ const frac = total === 0 ? 0 : clear / total;
645
+ setCleared(frac);
646
+ if (!done() && frac >= threshold) {
647
+ setDone(true);
648
+ onComplete?.();
649
+ }
650
+ };
651
+ const eraseAt = (clientX, clientY) => {
652
+ const cvs = canvas();
653
+ if (!cvs)
654
+ return;
655
+ const rect = cvs.getBoundingClientRect();
656
+ if (rect.width === 0 || rect.height === 0)
657
+ return;
658
+ const ctx = cvs.getContext("2d");
659
+ if (!ctx)
660
+ return;
661
+ const sx = cvs.width / rect.width;
662
+ const sy = cvs.height / rect.height;
663
+ ctx.globalCompositeOperation = "destination-out";
664
+ ctx.beginPath();
665
+ ctx.arc((clientX - rect.left) * sx, (clientY - rect.top) * sy, brush * Math.max(sx, sy), 0, Math.PI * 2);
666
+ ctx.fill();
667
+ ctx.globalCompositeOperation = "source-over";
668
+ };
669
+ // Throttle the (relatively expensive) pixel sampling.
670
+ let lastSample = 0;
671
+ const maybeSample = () => {
672
+ const t = now();
673
+ if (t - lastSample < 120)
674
+ return;
675
+ lastSample = t;
676
+ sampleCleared();
677
+ };
678
+ const onDown = (event) => {
679
+ if (event.isPrimary === false)
680
+ return;
681
+ const cvs = canvas();
682
+ if (!cvs)
683
+ return;
684
+ eraseAt(event.clientX, event.clientY);
685
+ maybeSample();
686
+ try {
687
+ cvs.setPointerCapture(event.pointerId);
688
+ }
689
+ catch {
690
+ // setPointerCapture may throw for synthetic events; the window
691
+ // fallback below is not needed since moves stay on the canvas.
692
+ }
693
+ const move = (ev) => {
694
+ eraseAt(ev.clientX, ev.clientY);
695
+ maybeSample();
696
+ };
697
+ const up = () => {
698
+ cvs.removeEventListener("pointermove", move);
699
+ cvs.removeEventListener("pointerup", up);
700
+ cvs.removeEventListener("pointercancel", up);
701
+ sampleCleared(); // final accurate read
702
+ };
703
+ cvs.addEventListener("pointermove", move);
704
+ cvs.addEventListener("pointerup", up);
705
+ cvs.addEventListener("pointercancel", up);
706
+ };
707
+ // Late-bound refs (Solid assigns `ref` after mount) get the cover
708
+ // painted and the gesture attached as soon as they exist.
709
+ createEffect(() => {
710
+ const cvs = canvas();
711
+ if (!cvs)
712
+ return;
713
+ paintCover();
714
+ cvs.addEventListener("pointerdown", onDown);
715
+ onCleanup(() => cvs.removeEventListener("pointerdown", onDown));
716
+ });
717
+ const reset = () => {
718
+ setCleared(0);
719
+ setDone(false);
720
+ paintCover();
721
+ };
722
+ return { cleared, done, reset };
723
+ }
package/dist/gesture.d.ts CHANGED
@@ -105,4 +105,86 @@ export interface DragControls {
105
105
  * ```
106
106
  */
107
107
  export declare function createDrag(ref: MaybeElement, options?: DragOptions): DragControls;
108
+ /** Cardinal direction of a recognized swipe. */
109
+ export type SwipeDirection = "left" | "right" | "up" | "down";
110
+ /** Snapshot delivered for a recognized swipe. */
111
+ export interface SwipeDetails {
112
+ direction: SwipeDirection;
113
+ /** Primary-axis travel in px. Always positive. */
114
+ distance: number;
115
+ /** Primary-axis velocity in px/ms. Always positive. */
116
+ velocity: number;
117
+ /** Gesture duration in ms. */
118
+ duration: number;
119
+ /** Pointer path endpoints in client pixels. */
120
+ from: {
121
+ x: number;
122
+ y: number;
123
+ };
124
+ to: {
125
+ x: number;
126
+ y: number;
127
+ };
128
+ }
129
+ export interface SwipeOptions {
130
+ /** Minimum travel in px to count as a swipe. Default 48. */
131
+ threshold?: number;
132
+ /** Maximum gesture duration in ms. Default 800. */
133
+ maxDuration?: number;
134
+ /**
135
+ * Minimum primary-axis velocity in px/ms. A fast flick shorter than
136
+ * `threshold` still counts as a swipe. Default 0.4.
137
+ */
138
+ minVelocity?: number;
139
+ /** Lock recognition to an axis. Default "both". */
140
+ axis?: DragAxis;
141
+ /** Called for every recognized swipe. */
142
+ onSwipe?: (details: SwipeDetails) => void;
143
+ /** Called for a leftward swipe. */
144
+ onSwipeLeft?: (details: SwipeDetails) => void;
145
+ /** Called for a rightward swipe. */
146
+ onSwipeRight?: (details: SwipeDetails) => void;
147
+ /** Called for an upward swipe. */
148
+ onSwipeUp?: (details: SwipeDetails) => void;
149
+ /** Called for a downward swipe. */
150
+ onSwipeDown?: (details: SwipeDetails) => void;
151
+ }
152
+ export interface SwipeControls {
153
+ /** The most recent recognized swipe, or null if none yet. */
154
+ lastSwipe: Accessor<SwipeDetails | null>;
155
+ /** Clear the last swipe. */
156
+ reset: () => void;
157
+ }
158
+ /**
159
+ * Touch swipe gesture recognition: swipe-to-dismiss, carousels.
160
+ *
161
+ * While `createDrag` tracks the pointer continuously, `createSwipe`
162
+ * makes the discrete decision: was that gesture a swipe, and which
163
+ * way? On pointerup it compares travel, duration, and velocity
164
+ * against the thresholds and fires the matching callbacks plus the
165
+ * `lastSwipe` signal.
166
+ *
167
+ * Pointer Events give touch parity for free: mouse, touch, and pen
168
+ * run through the same path. For touch, set `touch-action: pan-y` on
169
+ * a horizontal swipe surface (or `pan-x` for vertical) so the browser
170
+ * does not hijack the gesture; use `none` when recognizing both axes.
171
+ *
172
+ * A swipe counts when travel passes `threshold` inside `maxDuration`,
173
+ * or when velocity passes `minVelocity` (a fast flick). Slow long
174
+ * drags are not swipes; they belong to `createDrag`.
175
+ *
176
+ * Recognition is not animation, so it works identically under
177
+ * reduced motion; the host decides how to animate the response.
178
+ * SSR-safe: `lastSwipe()` stays null and callbacks never fire.
179
+ *
180
+ * ```tsx
181
+ * let deck!: HTMLDivElement
182
+ * const { lastSwipe } = createSwipe(() => deck, {
183
+ * onSwipeLeft: () => dismiss(),
184
+ * onSwipeRight: () => keep(),
185
+ * })
186
+ * <div ref={deck} style={{ "touch-action": "pan-y" }}>card</div>
187
+ * ```
188
+ */
189
+ export declare function createSwipe(ref: MaybeElement, options?: SwipeOptions): SwipeControls;
108
190
  export {};
package/dist/gesture.js CHANGED
@@ -205,3 +205,110 @@ export function createDrag(ref, options = {}) {
205
205
  onCleanup(() => stopSettle());
206
206
  return { x, y, status };
207
207
  }
208
+ /**
209
+ * Touch swipe gesture recognition: swipe-to-dismiss, carousels.
210
+ *
211
+ * While `createDrag` tracks the pointer continuously, `createSwipe`
212
+ * makes the discrete decision: was that gesture a swipe, and which
213
+ * way? On pointerup it compares travel, duration, and velocity
214
+ * against the thresholds and fires the matching callbacks plus the
215
+ * `lastSwipe` signal.
216
+ *
217
+ * Pointer Events give touch parity for free: mouse, touch, and pen
218
+ * run through the same path. For touch, set `touch-action: pan-y` on
219
+ * a horizontal swipe surface (or `pan-x` for vertical) so the browser
220
+ * does not hijack the gesture; use `none` when recognizing both axes.
221
+ *
222
+ * A swipe counts when travel passes `threshold` inside `maxDuration`,
223
+ * or when velocity passes `minVelocity` (a fast flick). Slow long
224
+ * drags are not swipes; they belong to `createDrag`.
225
+ *
226
+ * Recognition is not animation, so it works identically under
227
+ * reduced motion; the host decides how to animate the response.
228
+ * SSR-safe: `lastSwipe()` stays null and callbacks never fire.
229
+ *
230
+ * ```tsx
231
+ * let deck!: HTMLDivElement
232
+ * const { lastSwipe } = createSwipe(() => deck, {
233
+ * onSwipeLeft: () => dismiss(),
234
+ * onSwipeRight: () => keep(),
235
+ * })
236
+ * <div ref={deck} style={{ "touch-action": "pan-y" }}>card</div>
237
+ * ```
238
+ */
239
+ export function createSwipe(ref, options = {}) {
240
+ const none = () => null;
241
+ if (typeof window === "undefined") {
242
+ return { lastSwipe: none, reset: () => { } };
243
+ }
244
+ const { threshold = 48, maxDuration = 800, minVelocity = 0.4, axis = "both", onSwipe, onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, } = options;
245
+ const [lastSwipe, setLastSwipe] = createSignal(null);
246
+ const fire = (details) => {
247
+ setLastSwipe(details);
248
+ onSwipe?.(details);
249
+ if (details.direction === "left")
250
+ onSwipeLeft?.(details);
251
+ else if (details.direction === "right")
252
+ onSwipeRight?.(details);
253
+ else if (details.direction === "up")
254
+ onSwipeUp?.(details);
255
+ else
256
+ onSwipeDown?.(details);
257
+ };
258
+ const onDown = (event) => {
259
+ if (event.isPrimary === false)
260
+ return;
261
+ const el = ref();
262
+ if (!el)
263
+ return;
264
+ const startX = event.clientX;
265
+ const startY = event.clientY;
266
+ const startT = now();
267
+ const up = (ev) => {
268
+ window.removeEventListener("pointerup", up);
269
+ window.removeEventListener("pointercancel", cancel);
270
+ const dx = ev.clientX - startX;
271
+ const dy = ev.clientY - startY;
272
+ const dt = Math.max(now() - startT, 1);
273
+ // Primary axis, honoring the axis lock.
274
+ const horizontal = axis === "x" || (axis === "both" && Math.abs(dx) >= Math.abs(dy));
275
+ const travel = horizontal ? dx : dy;
276
+ const distance = Math.abs(travel);
277
+ const velocity = distance / dt;
278
+ const isSwipe = (distance >= threshold && dt <= maxDuration) ||
279
+ velocity >= minVelocity;
280
+ if (!isSwipe || distance === 0)
281
+ return;
282
+ const direction = horizontal
283
+ ? travel > 0
284
+ ? "right"
285
+ : "left"
286
+ : travel > 0
287
+ ? "down"
288
+ : "up";
289
+ fire({
290
+ direction,
291
+ distance,
292
+ velocity,
293
+ duration: dt,
294
+ from: { x: startX, y: startY },
295
+ to: { x: ev.clientX, y: ev.clientY },
296
+ });
297
+ };
298
+ const cancel = () => {
299
+ window.removeEventListener("pointerup", up);
300
+ window.removeEventListener("pointercancel", cancel);
301
+ };
302
+ window.addEventListener("pointerup", up);
303
+ window.addEventListener("pointercancel", cancel);
304
+ };
305
+ // Late-bound refs (Solid assigns `ref` after mount) still get the grab.
306
+ createEffect(() => {
307
+ const el = ref();
308
+ if (!el)
309
+ return;
310
+ el.addEventListener("pointerdown", onDown);
311
+ onCleanup(() => el.removeEventListener("pointerdown", onDown));
312
+ });
313
+ return { lastSwipe, reset: () => setLastSwipe(null) };
314
+ }
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export { createScrollProgress, type ScrollTarget } from "./scroll.js";
12
12
  export { createInView, type InViewOptions } from "./inview.js";
13
13
  export { usePrefersReducedMotion, prefersReducedMotion, } from "./reduced-motion.js";
14
14
  export { isLowPowerMode, useLowPowerMode, type LowPowerOptions, } from "./power.js";
15
- export { createSlotMachine, type SlotMachineControls, type SlotMachineOptions, type SlotMachineStatus, createRedPacket, type RedPacketCoin, type RedPacketControls, type RedPacketOptions, type RedPacketStatus, } from "./fun.js";
15
+ export { createSlotMachine, type SlotMachineControls, type SlotMachineOptions, type SlotMachineStatus, createRedPacket, type RedPacketCoin, type RedPacketControls, type RedPacketOptions, type RedPacketStatus, createConfetti, type ConfettiControls, type ConfettiOptions, createEmojiBurst, type EmojiBurstControls, type EmojiBurstOptions, createScratch, type ScratchControls, type ScratchOptions, } from "./fun.js";
16
16
  export { createStagger } from "./stagger.js";
17
17
  export { createHorizontalScroll, type HorizontalScrollOptions, type HorizontalScrollResult, } from "./horizontal.js";
18
18
  export { createScrub, type ScrubKeyframe, type ScrubOptions } from "./scrub.js";
@@ -30,7 +30,7 @@ export { createFontSwap, type FontSwapOptions, type FontSwapResult, createTyping
30
30
  export { easings, cubicBezier, linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeOutExpo, easeOutBack, easeInBack, easeInOutBack, easeOutElastic, easeOutBounce, resolveEasing, type Easing, type EasingName, } from "./easing.js";
31
31
  export { createKineticType, createScenePlayer, createShowreel, createCamera, createColorShift, createTransition, createBeat, createBeatCuts, } from "./motion.js";
32
32
  export type { KineticTypeFrom, KineticTypeOptions, KineticTypeStatus, KineticTypeControls, MotionScene, ScenePlayerStatus, ScenePlayerControls, ShowreelScene, ShowreelSceneKind, CameraKeyframe, CameraOptions, ColorShiftOptions, ColorShiftStatus, ColorShiftControls, TransitionType, TransitionDirection, TransitionOptions, TransitionLayerStyle, TransitionStatus, TransitionControls, BeatOptions, BeatStatus, BeatControls, BeatCutOptions, } from "./motion.js";
33
- export { createDrag, type DragStatus, type DragAxis, type DragConstraints, type DragEndInfo, type DragOptions, type DragControls, } from "./gesture.js";
33
+ export { createDrag, type DragStatus, type DragAxis, type DragConstraints, type DragEndInfo, type DragOptions, type DragControls, createSwipe, type SwipeDirection, type SwipeDetails, type SwipeOptions, type SwipeControls, } from "./gesture.js";
34
34
  export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
35
35
  export type { StreamRevealStatus, StreamRevealOptions, StreamRevealControls, AgentState, AgentStateTransition, AgentStateOptions, AgentStateControls, DriftSpecPrimitive, DriftSpecStep, DriftSpec, SpecPlayerStatus, SpecPlayerHooks, SpecPlayerControls, } from "./ai.js";
36
36
  export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, createAgentTx, } from "./web3.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export { createScrollProgress } from "./scroll.js";
12
12
  export { createInView } from "./inview.js";
13
13
  export { usePrefersReducedMotion, prefersReducedMotion, } from "./reduced-motion.js";
14
14
  export { isLowPowerMode, useLowPowerMode, } from "./power.js";
15
- export { createSlotMachine, createRedPacket, } from "./fun.js";
15
+ export { createSlotMachine, createRedPacket, createConfetti, createEmojiBurst, createScratch, } from "./fun.js";
16
16
  export { createStagger } from "./stagger.js";
17
17
  export { createHorizontalScroll, } from "./horizontal.js";
18
18
  export { createScrub } from "./scrub.js";
@@ -29,7 +29,7 @@ export { createGravity, createPendulum, createFling, } from "./physics.js";
29
29
  export { createFontSwap, createTyping, createTextPhysics, createTextTunnel, createTextCutout, createTextGradient, createTextScramble, createTextWave, createCountUp, } from "./typography.js";
30
30
  export { easings, cubicBezier, linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeOutExpo, easeOutBack, easeInBack, easeInOutBack, easeOutElastic, easeOutBounce, resolveEasing, } from "./easing.js";
31
31
  export { createKineticType, createScenePlayer, createShowreel, createCamera, createColorShift, createTransition, createBeat, createBeatCuts, } from "./motion.js";
32
- export { createDrag, } from "./gesture.js";
32
+ export { createDrag, createSwipe, } from "./gesture.js";
33
33
  export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
34
34
  export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, createAgentTx, } from "./web3.js";
35
35
  export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
package/package.json CHANGED
@@ -43,5 +43,5 @@
43
43
  },
44
44
  "type": "module",
45
45
  "types": "./dist/index.d.ts",
46
- "version": "0.19.0"
46
+ "version": "0.21.0"
47
47
  }