solid-drift 0.13.0 → 0.14.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 +19 -0
- package/dist/fun.d.ts +64 -0
- package/dist/fun.js +133 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1145,6 +1145,25 @@ const confettiCount = () => (lowPower() ? 20 : 150)
|
|
|
1145
1145
|
|
|
1146
1146
|
Options: `maxDeviceMemory` (GB, default `4`), `maxHardwareConcurrency` (default `4`): a device at or below either threshold counts as low-end. Where the device signals are unsupported they degrade to "not low-end". SSR-safe: always `false` on the server.
|
|
1147
1147
|
|
|
1148
|
+
### `createSlotMachine(options)`
|
|
1149
|
+
|
|
1150
|
+
Gacha slot machine: reels launch fast, decelerate with momentum, and stop left to right. Spin-to-mint theater for reveals, loot boxes, and prize draws. Pass `landing` to `spin()` when the outcome is already decided (the minted NFT, the prize): the reels still spin with full drama and land exactly on your symbols. Omit it for a fair random spin.
|
|
1151
|
+
|
|
1152
|
+
```tsx
|
|
1153
|
+
import { createSlotMachine } from "solid-drift"
|
|
1154
|
+
|
|
1155
|
+
const machine = createSlotMachine({
|
|
1156
|
+
symbols: ["🍒", "⭐", "💎", "🚀"],
|
|
1157
|
+
onTick: (reel) => navigator.vibrate?.(10), // haptic tick per symbol
|
|
1158
|
+
onDone: (result) => console.log("minted:", result),
|
|
1159
|
+
})
|
|
1160
|
+
|
|
1161
|
+
<button onClick={() => machine.spin()}>SPIN</button>
|
|
1162
|
+
<div>{machine.values().join(" ")}</div>
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
Options: `symbols` (required, at least 2), `reels` (default `3`), `duration` (ms for the first reel, default `1400`), `stagger` (extra ms per subsequent reel, default `500`), `minSpins` (full rotations before stopping, default `3`), `easing` (default `"easeOutQuart"`), `onTick(reel, symbol)`, `onDone(result)`. Returns `{ values, result, status, spin, stop, reset }`: `values()` is the visible symbol per reel, `result()` the final symbols of the last spin, `status()` is `"idle"`, `"spinning"`, or `"done"`. `stop()` halts at the current symbols; `reset()` returns to idle. SSR-safe and reduced-motion safe: `spin()` jumps straight to the result.
|
|
1166
|
+
|
|
1148
1167
|
### Easings
|
|
1149
1168
|
|
|
1150
1169
|
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
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Accessor } from "solid-js";
|
|
2
|
+
import { type Easing, type EasingName } from "./easing.js";
|
|
3
|
+
export type SlotMachineStatus = "idle" | "spinning" | "done";
|
|
4
|
+
export interface SlotMachineOptions<T = string> {
|
|
5
|
+
/** Symbol strip every reel cycles through. Required, at least 2 entries. */
|
|
6
|
+
symbols: T[];
|
|
7
|
+
/** Number of reels. Default 3. */
|
|
8
|
+
reels?: number;
|
|
9
|
+
/** Spin duration in ms for the first reel. Default 1400. */
|
|
10
|
+
duration?: number;
|
|
11
|
+
/** Extra ms per subsequent reel, so reels stop left to right. Default 500. */
|
|
12
|
+
stagger?: number;
|
|
13
|
+
/** Full rotations before a reel may stop. Default 3. */
|
|
14
|
+
minSpins?: number;
|
|
15
|
+
/** Deceleration curve: fast launch, long settle. Default "easeOutQuart". */
|
|
16
|
+
easing?: Easing | EasingName;
|
|
17
|
+
/** Called each time a reel passes a symbol. Pair with a haptic tick. */
|
|
18
|
+
onTick?: (reel: number, symbol: T) => void;
|
|
19
|
+
/** Called with the final symbols when all reels stop. */
|
|
20
|
+
onDone?: (result: T[]) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface SlotMachineControls<T = string> {
|
|
23
|
+
/** Currently visible symbol per reel. */
|
|
24
|
+
values: Accessor<T[]>;
|
|
25
|
+
/** Final symbols of the last completed spin. */
|
|
26
|
+
result: Accessor<T[]>;
|
|
27
|
+
status: Accessor<SlotMachineStatus>;
|
|
28
|
+
/**
|
|
29
|
+
* Spin the reels. Pass landing symbols to rig the outcome: the spin
|
|
30
|
+
* becomes theater for a predetermined mint result. A wrong-length
|
|
31
|
+
* landing falls back to a random one.
|
|
32
|
+
*/
|
|
33
|
+
spin: (landing?: T[]) => void;
|
|
34
|
+
/** Halt immediately at the current symbols. */
|
|
35
|
+
stop: () => void;
|
|
36
|
+
/** Back to idle with the initial symbols. */
|
|
37
|
+
reset: () => void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gacha slot machine: reels launch fast, decelerate with momentum, and
|
|
41
|
+
* stop left to right. Spin-to-mint theater for reveals, loot boxes, and
|
|
42
|
+
* prize draws.
|
|
43
|
+
*
|
|
44
|
+
* Pass `landing` to `spin()` when the outcome is already decided (the
|
|
45
|
+
* minted NFT, the prize): the reels still spin with full drama and land
|
|
46
|
+
* exactly on your symbols. Omit it for a fair random spin.
|
|
47
|
+
*
|
|
48
|
+
* SSR-safe: on the server `spin()` jumps straight to the result.
|
|
49
|
+
* Under reduced motion the reels jump straight to the result with no spin.
|
|
50
|
+
*
|
|
51
|
+
* ```tsx
|
|
52
|
+
* import { createSlotMachine } from "solid-drift"
|
|
53
|
+
*
|
|
54
|
+
* const machine = createSlotMachine({
|
|
55
|
+
* symbols: ["🍒", "⭐", "💎", "🚀"],
|
|
56
|
+
* onTick: (reel) => navigator.vibrate?.(10), // haptic tick per symbol
|
|
57
|
+
* onDone: (result) => console.log("minted:", result),
|
|
58
|
+
* })
|
|
59
|
+
*
|
|
60
|
+
* <button onClick={() => machine.spin()}>SPIN</button>
|
|
61
|
+
* <div>{machine.values().join(" ")}</div>
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function createSlotMachine<T = string>(options: SlotMachineOptions<T>): SlotMachineControls<T>;
|
package/dist/fun.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createSignal, onCleanup, } from "solid-js";
|
|
2
|
+
import { now, schedule } from "./engine.js";
|
|
3
|
+
import { resolveEasing } from "./easing.js";
|
|
4
|
+
import { prefersReducedMotion } from "./reduced-motion.js";
|
|
5
|
+
/**
|
|
6
|
+
* Gacha slot machine: reels launch fast, decelerate with momentum, and
|
|
7
|
+
* stop left to right. Spin-to-mint theater for reveals, loot boxes, and
|
|
8
|
+
* prize draws.
|
|
9
|
+
*
|
|
10
|
+
* Pass `landing` to `spin()` when the outcome is already decided (the
|
|
11
|
+
* minted NFT, the prize): the reels still spin with full drama and land
|
|
12
|
+
* exactly on your symbols. Omit it for a fair random spin.
|
|
13
|
+
*
|
|
14
|
+
* SSR-safe: on the server `spin()` jumps straight to the result.
|
|
15
|
+
* Under reduced motion the reels jump straight to the result with no spin.
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* import { createSlotMachine } from "solid-drift"
|
|
19
|
+
*
|
|
20
|
+
* const machine = createSlotMachine({
|
|
21
|
+
* symbols: ["🍒", "⭐", "💎", "🚀"],
|
|
22
|
+
* onTick: (reel) => navigator.vibrate?.(10), // haptic tick per symbol
|
|
23
|
+
* onDone: (result) => console.log("minted:", result),
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* <button onClick={() => machine.spin()}>SPIN</button>
|
|
27
|
+
* <div>{machine.values().join(" ")}</div>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function createSlotMachine(options) {
|
|
31
|
+
const { symbols, reels = 3, duration = 1400, stagger = 500, minSpins = 3, onTick, onDone, } = options;
|
|
32
|
+
const easing = resolveEasing(options.easing ?? "easeOutQuart");
|
|
33
|
+
if (symbols.length < 2) {
|
|
34
|
+
throw new Error("createSlotMachine: symbols needs at least 2 entries.");
|
|
35
|
+
}
|
|
36
|
+
const len = symbols.length;
|
|
37
|
+
const wrap = (index) => ((index % len) + len) % len;
|
|
38
|
+
const symbolAt = (offset) => symbols[wrap(Math.floor(offset))];
|
|
39
|
+
const targetIndex = (symbol) => {
|
|
40
|
+
const i = symbols.indexOf(symbol);
|
|
41
|
+
return i === -1 ? 0 : i;
|
|
42
|
+
};
|
|
43
|
+
const initialOffsets = () => Array.from({ length: reels }, (_, i) => i % len);
|
|
44
|
+
const initialValues = () => initialOffsets().map(symbolAt);
|
|
45
|
+
const [values, setValues] = createSignal(initialValues());
|
|
46
|
+
const [result, setResult] = createSignal(initialValues());
|
|
47
|
+
const [status, setStatus] = createSignal("idle");
|
|
48
|
+
let offsets = initialOffsets();
|
|
49
|
+
let anims = [];
|
|
50
|
+
let cancel = null;
|
|
51
|
+
const settle = (finalSymbols) => {
|
|
52
|
+
cancel = null;
|
|
53
|
+
anims = [];
|
|
54
|
+
setValues(finalSymbols);
|
|
55
|
+
setResult(finalSymbols);
|
|
56
|
+
setStatus("done");
|
|
57
|
+
onDone?.(finalSymbols);
|
|
58
|
+
};
|
|
59
|
+
const spin = (landing) => {
|
|
60
|
+
cancel?.();
|
|
61
|
+
cancel = null;
|
|
62
|
+
const finalSymbols = landing && landing.length === reels
|
|
63
|
+
? landing.map((s) => symbols[targetIndex(s)])
|
|
64
|
+
: Array.from({ length: reels }, () => symbols[(Math.random() * len) | 0]);
|
|
65
|
+
if (typeof window === "undefined" || prefersReducedMotion()) {
|
|
66
|
+
// Accessibility / SSR: no theater, straight to the result.
|
|
67
|
+
offsets = finalSymbols.map((s) => targetIndex(s));
|
|
68
|
+
settle(finalSymbols);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setStatus("spinning");
|
|
72
|
+
const t0 = now();
|
|
73
|
+
anims = finalSymbols.map((symbol, i) => {
|
|
74
|
+
const from = offsets[i];
|
|
75
|
+
const currentIdx = wrap(Math.floor(from));
|
|
76
|
+
const delta = wrap(targetIndex(symbol) - currentIdx);
|
|
77
|
+
// minSpins full turns plus the forward distance to the target index.
|
|
78
|
+
// len divides minSpins * len, so floor(to) lands exactly on target.
|
|
79
|
+
const to = from + minSpins * len + delta;
|
|
80
|
+
return {
|
|
81
|
+
from,
|
|
82
|
+
to,
|
|
83
|
+
startAt: t0,
|
|
84
|
+
duration: duration + i * stagger,
|
|
85
|
+
lastIndex: currentIdx,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
cancel = schedule((t) => {
|
|
89
|
+
let allDone = true;
|
|
90
|
+
const next = offsets.slice();
|
|
91
|
+
for (let i = 0; i < reels; i++) {
|
|
92
|
+
const a = anims[i];
|
|
93
|
+
const p = Math.min(Math.max((t - a.startAt) / a.duration, 0), 1);
|
|
94
|
+
const offset = a.from + (a.to - a.from) * easing(p);
|
|
95
|
+
next[i] = offset;
|
|
96
|
+
const index = wrap(Math.floor(offset));
|
|
97
|
+
if (index !== a.lastIndex) {
|
|
98
|
+
a.lastIndex = index;
|
|
99
|
+
onTick?.(i, symbols[index]);
|
|
100
|
+
}
|
|
101
|
+
if (p < 1)
|
|
102
|
+
allDone = false;
|
|
103
|
+
}
|
|
104
|
+
offsets = next;
|
|
105
|
+
setValues(next.map(symbolAt));
|
|
106
|
+
if (allDone) {
|
|
107
|
+
settle(finalSymbols);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const stop = () => {
|
|
114
|
+
if (!cancel)
|
|
115
|
+
return;
|
|
116
|
+
cancel();
|
|
117
|
+
const current = offsets.map(symbolAt);
|
|
118
|
+
offsets = current.map((s) => targetIndex(s));
|
|
119
|
+
settle(current);
|
|
120
|
+
};
|
|
121
|
+
const reset = () => {
|
|
122
|
+
cancel?.();
|
|
123
|
+
cancel = null;
|
|
124
|
+
anims = [];
|
|
125
|
+
offsets = initialOffsets();
|
|
126
|
+
const initial = initialValues();
|
|
127
|
+
setValues(initial);
|
|
128
|
+
setResult(initial);
|
|
129
|
+
setStatus("idle");
|
|
130
|
+
};
|
|
131
|
+
onCleanup(() => cancel?.());
|
|
132
|
+
return { values, result, status, spin, stop, reset };
|
|
133
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +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, } from "./fun.js";
|
|
15
16
|
export { createStagger } from "./stagger.js";
|
|
16
17
|
export { createHorizontalScroll, type HorizontalScrollOptions, type HorizontalScrollResult, } from "./horizontal.js";
|
|
17
18
|
export { createScrub, type ScrubKeyframe, type ScrubOptions } from "./scrub.js";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +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, } from "./fun.js";
|
|
15
16
|
export { createStagger } from "./stagger.js";
|
|
16
17
|
export { createHorizontalScroll, } from "./horizontal.js";
|
|
17
18
|
export { createScrub } from "./scrub.js";
|
package/package.json
CHANGED