reel-deal 0.1.0-dev.1 → 0.1.1
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 +1 -1
- package/dist/index.cjs.js +14 -22
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.es.js +260 -251
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +14 -22
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -1,73 +1,68 @@
|
|
|
1
|
-
const
|
|
1
|
+
const T = {
|
|
2
2
|
minSpins: 2,
|
|
3
3
|
durationMs: 4e3,
|
|
4
4
|
speedPxS: 4e3,
|
|
5
5
|
staggerMs: 300
|
|
6
|
-
},
|
|
7
|
-
warpAngleDeg: 90,
|
|
8
|
-
edgePower: 0.5
|
|
9
|
-
}, I = {
|
|
6
|
+
}, C = {
|
|
10
7
|
maxPx: 4,
|
|
11
8
|
speedAtMax: 2200
|
|
12
|
-
},
|
|
13
|
-
amplitudePx:
|
|
9
|
+
}, E = {
|
|
10
|
+
amplitudePx: 9,
|
|
14
11
|
speedHz: 0.25,
|
|
15
12
|
phaseOffsetRad: 0.9,
|
|
16
|
-
delayMs: 500,
|
|
17
13
|
rampMs: 800
|
|
18
|
-
},
|
|
19
|
-
const
|
|
20
|
-
return
|
|
21
|
-
},
|
|
22
|
-
const
|
|
23
|
-
return
|
|
24
|
-
},
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
const
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
const
|
|
33
|
-
return
|
|
34
|
-
},
|
|
35
|
-
const
|
|
36
|
-
return {
|
|
37
|
-
minSpins: p(e.minSpins, b.minSpins),
|
|
38
|
-
durationMs: p(e.durationMs, b.durationMs ?? 0),
|
|
39
|
-
speedPxS: p(e.speedPxS, b.speedPxS ?? 0),
|
|
40
|
-
staggerMs: p(e.staggerMs, b.staggerMs ?? 0)
|
|
41
|
-
};
|
|
42
|
-
}, N = (a) => {
|
|
43
|
-
const e = { ...I, ...a ?? {} };
|
|
14
|
+
}, B = 0.1, A = 0.6, O = (a, t, s) => Math.max(t, Math.min(s, Math.floor(a))), I = (a, t) => (a % t + t) % t, v = (a) => Math.max(0, Math.min(1, a)), F = (a) => {
|
|
15
|
+
const t = v(a);
|
|
16
|
+
return t ** 2 * (2 - t);
|
|
17
|
+
}, $ = (a) => {
|
|
18
|
+
const t = v(a);
|
|
19
|
+
return t + t ** 2 - t ** 3;
|
|
20
|
+
}, Q = (a) => {
|
|
21
|
+
const t = v(a);
|
|
22
|
+
if (t <= B) {
|
|
23
|
+
const i = t / B;
|
|
24
|
+
return F(i) * B;
|
|
25
|
+
}
|
|
26
|
+
if (t < A)
|
|
27
|
+
return t;
|
|
28
|
+
const s = (t - A) / (1 - A);
|
|
29
|
+
return A + $(s) * (1 - A);
|
|
30
|
+
}, L = (a, t) => typeof a == "number" && Number.isFinite(a) ? a : t, _ = (a, t) => Math.max(0, L(a, t)), w = (a, t) => Math.max(0, Math.floor(L(a, t))), K = 2, X = (a) => {
|
|
31
|
+
const t = { ...T, ...a ?? {} };
|
|
44
32
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
minSpins: w(t.minSpins, T.minSpins),
|
|
34
|
+
durationMs: w(t.durationMs, T.durationMs ?? 0),
|
|
35
|
+
speedPxS: w(t.speedPxS, T.speedPxS ?? 0),
|
|
36
|
+
staggerMs: w(t.staggerMs, T.staggerMs ?? 0)
|
|
47
37
|
};
|
|
48
|
-
},
|
|
49
|
-
const
|
|
38
|
+
}, Z = (a) => {
|
|
39
|
+
const t = { ...C, ...a ?? {} };
|
|
50
40
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
maxPx: _(t.maxPx, C.maxPx),
|
|
42
|
+
speedAtMax: _(t.speedAtMax, C.speedAtMax)
|
|
53
43
|
};
|
|
54
|
-
},
|
|
44
|
+
}, j = (a) => {
|
|
55
45
|
if (a === !1) return null;
|
|
56
|
-
const
|
|
46
|
+
const t = { ...E, ...a ?? {} };
|
|
57
47
|
return {
|
|
58
|
-
amplitudePx:
|
|
59
|
-
speedHz:
|
|
60
|
-
phaseOffsetRad:
|
|
61
|
-
|
|
62
|
-
rampMs: p(e.rampMs, g.rampMs)
|
|
48
|
+
amplitudePx: _(t.amplitudePx, E.amplitudePx),
|
|
49
|
+
speedHz: _(t.speedHz, E.speedHz),
|
|
50
|
+
phaseOffsetRad: L(t.phaseOffsetRad, E.phaseOffsetRad),
|
|
51
|
+
rampMs: w(t.rampMs, E.rampMs)
|
|
63
52
|
};
|
|
64
|
-
},
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
67
|
-
throw new Error(`${
|
|
68
|
-
return
|
|
69
|
-
},
|
|
70
|
-
|
|
53
|
+
}, J = (a) => Math.max(1, L(a, K)), tt = (a, t) => {
|
|
54
|
+
const s = document.getElementById(a);
|
|
55
|
+
if (!s)
|
|
56
|
+
throw new Error(`${t} not found: ${a}`);
|
|
57
|
+
return s;
|
|
58
|
+
}, U = (a, t) => typeof a == "string" ? tt(a, t) : a, et = 5, z = 0.72, st = 0.28, it = 7, V = 0.85, rt = 1 - V, nt = 55, at = 300, ot = 1.25, lt = 0.35, N = {
|
|
59
|
+
warpAngleDeg: 60,
|
|
60
|
+
edgePower: 4.8
|
|
61
|
+
}, ht = (a) => ({
|
|
62
|
+
warpAngleDeg: _(a?.warpAngleDeg, N.warpAngleDeg),
|
|
63
|
+
edgePower: _(a?.edgePower, N.edgePower)
|
|
64
|
+
});
|
|
65
|
+
class ut {
|
|
71
66
|
canvas;
|
|
72
67
|
container;
|
|
73
68
|
opts;
|
|
@@ -109,8 +104,8 @@ class j {
|
|
|
109
104
|
buttonHandlerAttached = !1;
|
|
110
105
|
spinning = !1;
|
|
111
106
|
queuedSpins = null;
|
|
112
|
-
constructor(
|
|
113
|
-
this.opts =
|
|
107
|
+
constructor(t) {
|
|
108
|
+
this.opts = t, this.reels = O(t.reels ?? 1, 1, et), this.spinConfig = X(t.spinConfig), this.spinBlur = Z(t.spinBlur), this.webglShading = ht(t.webglShading), this.idleBob = j(t.idleBob), this.maxDpr = J(t.maxDpr), this.canvas = U(t.canvas, "Canvas"), this.container = U(t.container, "Container"), this.offsets = new Array(this.reels).fill(0), this.velocities = new Array(this.reels).fill(0), this.lastVelOffsets = new Array(this.reels).fill(0), this.idleBaseOffsets = new Array(this.reels).fill(0);
|
|
114
109
|
}
|
|
115
110
|
async init() {
|
|
116
111
|
if (await this.loadSprite(), this.tryInitWebGL(), !this.gl)
|
|
@@ -126,70 +121,93 @@ class j {
|
|
|
126
121
|
this.canvas !== this.container && this.container.contains(this.canvas) && (this.canvas.style.width || (this.canvas.style.width = "100%"), this.canvas.style.height || (this.canvas.style.height = "100%"), this.canvas.style.display || (this.canvas.style.display = "block"));
|
|
127
122
|
}
|
|
128
123
|
syncContainerLayoutVars() {
|
|
129
|
-
const
|
|
130
|
-
|
|
124
|
+
const t = this.canvas === this.container ? this.canvas : this.container;
|
|
125
|
+
t.style.setProperty("--reels", String(this.reels)), t.style.setProperty("--visible-slots", String(this.visibleSlots)), t.style.aspectRatio = `${this.reels} / ${this.visibleSlots * this.heightScale}`, this.canvas !== this.container && (this.container.style.overflow = "hidden");
|
|
131
126
|
}
|
|
132
|
-
setSegments(
|
|
127
|
+
setSegments(t) {
|
|
133
128
|
this.ensureReady();
|
|
134
|
-
const
|
|
135
|
-
for (let
|
|
136
|
-
const
|
|
137
|
-
this.offsets[
|
|
129
|
+
const s = t ?? [];
|
|
130
|
+
for (let i = 0; i < this.reels; i += 1) {
|
|
131
|
+
const e = s[i] ?? 0, r = O(e, 0, this.opts.slotCount - 1);
|
|
132
|
+
this.offsets[i] = r * this.frameHeight - this.centerIndex * this.frameHeight, this.lastVelOffsets[i] = this.offsets[i], this.velocities[i] = 0, this.idleBaseOffsets[i] = this.offsets[i];
|
|
138
133
|
}
|
|
139
134
|
this.lastVelT = null, this.idleStartTime = null, this.anim = null, this.stop(), this.render(), this.startLoop();
|
|
140
135
|
}
|
|
141
|
-
setSegment(
|
|
142
|
-
this.setSegments(new Array(this.reels).fill(
|
|
136
|
+
setSegment(t) {
|
|
137
|
+
this.setSegments(new Array(this.reels).fill(t));
|
|
143
138
|
}
|
|
144
|
-
spinOnce(
|
|
139
|
+
spinOnce(t, s) {
|
|
145
140
|
this.ensureReady();
|
|
146
|
-
const { minSpins:
|
|
147
|
-
for (let
|
|
148
|
-
const
|
|
149
|
-
let
|
|
150
|
-
if (
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
Math.ceil(Math.max(0,
|
|
141
|
+
const { minSpins: i, staggerMs: e, speedPxS: r, durationMs: l } = this.getSpinConfig(s), u = r > 0, f = this.getStopSpringOvershootPx(), c = Math.max(0, at), n = c > 0 && f > 0, h = new Array(this.reels), g = new Array(this.reels), p = new Array(this.reels), d = new Array(this.reels), m = new Array(this.reels);
|
|
142
|
+
for (let o = 0; o < this.reels; o += 1) {
|
|
143
|
+
const Y = O(t[o] ?? 0, 0, this.opts.slotCount - 1) * this.frameHeight - this.centerIndex * this.frameHeight, k = I(Y, this.spriteHeight), q = I(this.offsets[o], this.spriteHeight), H = I(q - k, this.spriteHeight);
|
|
144
|
+
let b = -(i * this.spriteHeight + H);
|
|
145
|
+
if (u && l > 0) {
|
|
146
|
+
const R = r * l / 1e3, P = Math.max(
|
|
147
|
+
i,
|
|
148
|
+
Math.ceil(Math.max(0, R - H) / this.spriteHeight)
|
|
154
149
|
);
|
|
155
|
-
|
|
150
|
+
b = -(H + P * this.spriteHeight);
|
|
151
|
+
}
|
|
152
|
+
if (e > 0 && u) {
|
|
153
|
+
const R = r * e * o / 1e3, P = Math.ceil(R / this.spriteHeight);
|
|
154
|
+
b -= P * this.spriteHeight;
|
|
156
155
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
h[o] = this.offsets[o], m[o] = this.offsets[o] + b;
|
|
157
|
+
let D = b, G = 0;
|
|
158
|
+
if (n && b !== 0 && f > 0) {
|
|
159
|
+
const P = Math.sign(b) * f;
|
|
160
|
+
D += P, G = c;
|
|
160
161
|
}
|
|
161
|
-
|
|
162
|
+
g[o] = D, d[o] = G, u ? p[o] = Math.max(0, Math.round(Math.abs(D) / r * 1e3)) : p[o] = Math.max(0, l + e * o);
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
const S = g.reduce((o, M) => o + Math.abs(M), 0), x = p.every((o) => o <= 0), W = d.every((o) => o <= 0);
|
|
165
|
+
if (S === 0 || x && W) {
|
|
166
|
+
for (let o = 0; o < this.reels; o += 1)
|
|
167
|
+
this.offsets[o] = m[o];
|
|
166
168
|
return this.anim = null, this.resolvePendingSpins(), this.render(), Promise.resolve();
|
|
167
169
|
}
|
|
168
|
-
const
|
|
169
|
-
return this.anim = {
|
|
170
|
-
|
|
170
|
+
const y = performance.now();
|
|
171
|
+
return this.anim = {
|
|
172
|
+
startTime: y,
|
|
173
|
+
startOffsets: h,
|
|
174
|
+
deltas: g,
|
|
175
|
+
durationMs: p,
|
|
176
|
+
settleDurationMs: d,
|
|
177
|
+
targets: m
|
|
178
|
+
}, this.idleStartTime = null, this.startLoop(), new Promise((o) => {
|
|
179
|
+
this.pendingSpinResolvers.push(o);
|
|
171
180
|
});
|
|
172
181
|
}
|
|
173
|
-
getSpinConfig(
|
|
174
|
-
return
|
|
182
|
+
getSpinConfig(t) {
|
|
183
|
+
return t ? X({ ...this.spinConfig, ...t }) : this.spinConfig;
|
|
184
|
+
}
|
|
185
|
+
getStopSpringOvershootPx() {
|
|
186
|
+
const t = this.frameHeight * lt, s = Math.max(0, nt);
|
|
187
|
+
return Math.min(s, t);
|
|
175
188
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
189
|
+
getStopSpringOffset(t, s) {
|
|
190
|
+
if (t === 0) return 0;
|
|
191
|
+
const i = Math.exp(-5 * s), e = Math.PI * 2 * ot * s;
|
|
192
|
+
return t * i * Math.cos(e);
|
|
193
|
+
}
|
|
194
|
+
async spinQueue(t) {
|
|
195
|
+
for (let s = 0; s < t.length; s += 1) {
|
|
196
|
+
const i = t[s];
|
|
197
|
+
await this.spinOnce(i.stopAtSegments), i.callback?.(s, i.stopAtSegments);
|
|
180
198
|
}
|
|
181
199
|
}
|
|
182
200
|
stop() {
|
|
183
201
|
this.rafId !== null && cancelAnimationFrame(this.rafId), this.rafId = null, this.anim = null, this.resolvePendingSpins(), this.lastVelT = null;
|
|
184
|
-
for (let
|
|
185
|
-
this.velocities[
|
|
202
|
+
for (let t = 0; t < this.reels; t += 1)
|
|
203
|
+
this.velocities[t] = 0;
|
|
186
204
|
}
|
|
187
205
|
resolvePendingSpins() {
|
|
188
206
|
if (this.pendingSpinResolvers.length === 0) return;
|
|
189
|
-
const
|
|
207
|
+
const t = this.pendingSpinResolvers;
|
|
190
208
|
this.pendingSpinResolvers = [];
|
|
191
|
-
for (const
|
|
192
|
-
|
|
209
|
+
for (const s of t)
|
|
210
|
+
s();
|
|
193
211
|
}
|
|
194
212
|
requestResize() {
|
|
195
213
|
this.queueResize();
|
|
@@ -203,18 +221,18 @@ class j {
|
|
|
203
221
|
}
|
|
204
222
|
handleButtonClick = async () => {
|
|
205
223
|
if (this.spinning) return;
|
|
206
|
-
const
|
|
224
|
+
const t = this.consumeNextSpin() ?? {
|
|
207
225
|
stopAtSegments: this.buildRandomSegments()
|
|
208
226
|
};
|
|
209
227
|
this.spinning = !0, this.setButtonDisabled(!0);
|
|
210
228
|
try {
|
|
211
|
-
await this.spinOnce(
|
|
229
|
+
await this.spinOnce(t.stopAtSegments);
|
|
212
230
|
} finally {
|
|
213
231
|
this.queuedSpins && this.queuedSpins.length === 0 ? this.setButtonDisabled(!0) : this.setButtonDisabled(!1), this.spinning = !1;
|
|
214
232
|
}
|
|
215
233
|
};
|
|
216
234
|
resolveButton() {
|
|
217
|
-
return this.opts.button ?
|
|
235
|
+
return this.opts.button ? U(this.opts.button, "Button") : null;
|
|
218
236
|
}
|
|
219
237
|
resolveSpinQueue() {
|
|
220
238
|
return this.opts.queuedSpinStates ?? null;
|
|
@@ -223,13 +241,13 @@ class j {
|
|
|
223
241
|
return this.queuedSpins || (this.queuedSpins = this.resolveSpinQueue()), !this.queuedSpins || this.queuedSpins.length === 0 ? null : this.queuedSpins.shift() ?? null;
|
|
224
242
|
}
|
|
225
243
|
buildRandomSegments() {
|
|
226
|
-
const
|
|
227
|
-
for (let
|
|
228
|
-
|
|
229
|
-
return
|
|
244
|
+
const t = Math.max(1, Math.floor(this.opts.slotCount)), s = [];
|
|
245
|
+
for (let i = 0; i < this.reels; i += 1)
|
|
246
|
+
s.push(Math.floor(Math.random() * t));
|
|
247
|
+
return s;
|
|
230
248
|
}
|
|
231
|
-
setButtonDisabled(
|
|
232
|
-
this.button && (this.button.disabled =
|
|
249
|
+
setButtonDisabled(t) {
|
|
250
|
+
this.button && (this.button.disabled = t, t ? this.button.setAttribute("aria-busy", "true") : this.button.removeAttribute("aria-busy"));
|
|
233
251
|
}
|
|
234
252
|
shouldAnimate() {
|
|
235
253
|
return this.anim !== null || this.idleBob !== null;
|
|
@@ -237,30 +255,39 @@ class j {
|
|
|
237
255
|
startLoop() {
|
|
238
256
|
this.rafId === null && this.shouldAnimate() && (this.rafId = requestAnimationFrame(this.loop));
|
|
239
257
|
}
|
|
240
|
-
loop = (
|
|
258
|
+
loop = (t) => {
|
|
241
259
|
if (this.anim) {
|
|
242
|
-
const { startTime:
|
|
243
|
-
let
|
|
260
|
+
const { startTime: s, durationMs: i, startOffsets: e, deltas: r, settleDurationMs: l, targets: u } = this.anim, f = t - s;
|
|
261
|
+
let c = !0;
|
|
244
262
|
for (let n = 0; n < this.reels; n += 1) {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
263
|
+
const h = i[n] ?? 0, g = l[n] ?? 0, p = u[n] ?? e[n] + r[n];
|
|
264
|
+
if (h > 0 && f < h) {
|
|
265
|
+
const d = Math.min(1, f / h), m = Q(d);
|
|
266
|
+
d < 1 && (c = !1);
|
|
267
|
+
const S = e[n] + r[n] * m;
|
|
268
|
+
let x = 0;
|
|
269
|
+
if (d > z && this.frameHeight > 0) {
|
|
270
|
+
const y = (1 - v((d - z) / st)) ** 2, o = S / this.frameHeight * Math.PI * 2, M = Math.min(it, this.frameHeight * 0.06);
|
|
271
|
+
x = Math.sin(o) * y * M;
|
|
272
|
+
}
|
|
273
|
+
this.offsets[n] = S + x;
|
|
274
|
+
} else if (g > 0) {
|
|
275
|
+
const d = h <= 0 ? f / g : (f - h) / g, m = v(d), S = e[n] + r[n] - p, x = this.getStopSpringOffset(S, m);
|
|
276
|
+
m < 1 && (c = !1), this.offsets[n] = p + x;
|
|
277
|
+
} else
|
|
278
|
+
this.offsets[n] = p;
|
|
254
279
|
}
|
|
255
|
-
if (this.updateVelocity(
|
|
256
|
-
for (let n = 0; n < this.reels; n += 1)
|
|
257
|
-
this.
|
|
258
|
-
|
|
280
|
+
if (this.updateVelocity(t), this.render(), c) {
|
|
281
|
+
for (let n = 0; n < this.reels; n += 1) {
|
|
282
|
+
const h = this.anim.targets[n] ?? this.anim.startOffsets[n] + this.anim.deltas[n];
|
|
283
|
+
this.offsets[n] = h, this.idleBaseOffsets[n] = this.offsets[n];
|
|
284
|
+
}
|
|
285
|
+
this.updateVelocity(t);
|
|
259
286
|
for (let n = 0; n < this.reels; n += 1)
|
|
260
287
|
this.velocities[n] = 0;
|
|
261
288
|
this.anim = null, this.idleStartTime = null, this.render(), this.resolvePendingSpins();
|
|
262
289
|
}
|
|
263
|
-
} else this.idleBob && (this.updateIdle(
|
|
290
|
+
} else this.idleBob && (this.updateIdle(t), this.render());
|
|
264
291
|
if (!this.shouldAnimate()) {
|
|
265
292
|
this.rafId = null;
|
|
266
293
|
return;
|
|
@@ -270,70 +297,61 @@ class j {
|
|
|
270
297
|
render() {
|
|
271
298
|
this.spriteImg && (this.width <= 0 || this.height <= 0 || this.gl && this.renderWebGL());
|
|
272
299
|
}
|
|
273
|
-
getSpinBlurPxForReel(
|
|
300
|
+
getSpinBlurPxForReel(t) {
|
|
274
301
|
if (!this.anim) return 0;
|
|
275
|
-
const
|
|
276
|
-
return v(
|
|
302
|
+
const s = Math.abs(this.velocities[t] ?? 0), i = Math.max(1, this.spinBlur.speedAtMax);
|
|
303
|
+
return v(s / i) * Math.max(0, this.spinBlur.maxPx);
|
|
277
304
|
}
|
|
278
305
|
renderWebGL() {
|
|
279
306
|
if (!this.gl || !this.glProgram || !this.glAttribs || !this.glUniforms || !this.glTexture || !this.glBuffer || !this.spriteImg) return;
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
const
|
|
307
|
+
const t = this.gl;
|
|
308
|
+
t.useProgram(this.glProgram), t.bindBuffer(t.ARRAY_BUFFER, this.glBuffer), t.enableVertexAttribArray(this.glAttribs.pos), t.vertexAttribPointer(this.glAttribs.pos, 2, t.FLOAT, !1, 16, 0), t.enableVertexAttribArray(this.glAttribs.uv), t.vertexAttribPointer(this.glAttribs.uv, 2, t.FLOAT, !1, 16, 8), t.activeTexture(t.TEXTURE0), t.bindTexture(t.TEXTURE_2D, this.glTexture), t.uniform1i(this.glUniforms.texture, 0), t.uniform1f(this.glUniforms.frameHeight, this.frameHeight), t.uniform1f(this.glUniforms.spriteHeight, this.spriteHeight), t.uniform1f(this.glUniforms.visibleSlots, this.visibleSlots), t.uniform1f(this.glUniforms.heightScale, this.heightScale), t.uniform1f(this.glUniforms.edgePower, Math.max(0.5, this.webglShading.edgePower));
|
|
309
|
+
const s = this.webglShading, i = Math.max(0, s.warpAngleDeg) * Math.PI / 180;
|
|
310
|
+
t.uniform1f(this.glUniforms.warpAngle, i), t.clear(t.COLOR_BUFFER_BIT);
|
|
311
|
+
const e = 1 / this.reels;
|
|
285
312
|
for (let r = 0; r < this.reels; r += 1) {
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
313
|
+
t.uniform1f(this.glUniforms.reelX, r * e), t.uniform1f(this.glUniforms.reelW, e);
|
|
314
|
+
const l = I(this.offsets[r], this.spriteHeight);
|
|
315
|
+
t.uniform1f(this.glUniforms.offsetPx, l), t.uniform1f(this.glUniforms.blurPx, this.getSpinBlurPxForReel(r)), t.drawArrays(t.TRIANGLE_STRIP, 0, 4);
|
|
289
316
|
}
|
|
290
317
|
}
|
|
291
|
-
updateVelocity(
|
|
318
|
+
updateVelocity(t) {
|
|
292
319
|
if (this.lastVelT === null) {
|
|
293
|
-
this.lastVelT =
|
|
294
|
-
for (let
|
|
295
|
-
this.lastVelOffsets[
|
|
320
|
+
this.lastVelT = t;
|
|
321
|
+
for (let i = 0; i < this.reels; i += 1)
|
|
322
|
+
this.lastVelOffsets[i] = this.offsets[i], this.velocities[i] = 0;
|
|
296
323
|
return;
|
|
297
324
|
}
|
|
298
|
-
const
|
|
299
|
-
if (!(
|
|
300
|
-
for (let
|
|
301
|
-
const
|
|
302
|
-
this.velocities[
|
|
325
|
+
const s = (t - this.lastVelT) / 1e3;
|
|
326
|
+
if (!(s <= 0)) {
|
|
327
|
+
for (let i = 0; i < this.reels; i += 1) {
|
|
328
|
+
const e = (this.offsets[i] - this.lastVelOffsets[i]) / s;
|
|
329
|
+
this.velocities[i] = this.velocities[i] * V + e * rt, this.lastVelOffsets[i] = this.offsets[i];
|
|
303
330
|
}
|
|
304
|
-
this.lastVelT =
|
|
331
|
+
this.lastVelT = t;
|
|
305
332
|
}
|
|
306
333
|
}
|
|
307
|
-
updateIdle(
|
|
334
|
+
updateIdle(t) {
|
|
308
335
|
if (!this.idleBob) return;
|
|
309
|
-
this.idleStartTime === null && (this.idleStartTime =
|
|
310
|
-
const { amplitudePx:
|
|
311
|
-
if (n < Math.max(0, r)) {
|
|
312
|
-
for (let h = 0; h < this.reels; h += 1)
|
|
313
|
-
this.offsets[h] = this.idleBaseOffsets[h], this.velocities[h] = 0;
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const c = (n - Math.max(0, r)) / 1e3, f = Math.max(
|
|
317
|
-
0,
|
|
318
|
-
Math.min(1, (n - Math.max(0, r)) / Math.max(1, o))
|
|
319
|
-
), u = U(f), d = s * Math.PI * 2;
|
|
336
|
+
this.idleStartTime === null && (this.idleStartTime = t);
|
|
337
|
+
const { amplitudePx: s, speedHz: i, phaseOffsetRad: e, rampMs: r } = this.idleBob, l = t - this.idleStartTime, u = l / 1e3, f = Math.max(0, Math.min(1, l / Math.max(1, r))), c = F(f), n = i * Math.PI * 2;
|
|
320
338
|
for (let h = 0; h < this.reels; h += 1) {
|
|
321
|
-
const
|
|
322
|
-
this.offsets[h] = this.idleBaseOffsets[h] + Math.sin(
|
|
339
|
+
const g = u * n + h * e;
|
|
340
|
+
this.offsets[h] = this.idleBaseOffsets[h] + Math.sin(g) * (s * c), this.velocities[h] = 0;
|
|
323
341
|
}
|
|
324
342
|
}
|
|
325
343
|
async loadSprite() {
|
|
326
|
-
const { sprite:
|
|
327
|
-
typeof
|
|
328
|
-
const
|
|
329
|
-
|
|
344
|
+
const { sprite: t } = this.opts, s = typeof t == "string" ? new Image() : t;
|
|
345
|
+
typeof t == "string" && (s.src = t), (!s.complete || s.naturalWidth <= 0) && await new Promise((c, n) => {
|
|
346
|
+
const h = () => c(), g = () => n(new Error("Failed to load sprite image"));
|
|
347
|
+
s.addEventListener("load", h, { once: !0 }), s.addEventListener("error", g, { once: !0 });
|
|
330
348
|
});
|
|
331
|
-
const
|
|
332
|
-
if (Math.abs(
|
|
349
|
+
const i = Math.max(1, Math.floor(this.opts.slotCount)), e = s.naturalWidth, r = e, l = r * i, u = s.naturalHeight;
|
|
350
|
+
if (Math.abs(u - l) > 2)
|
|
333
351
|
throw new Error(
|
|
334
|
-
`Sprite size mismatch. Expected height ~${Math.round(
|
|
352
|
+
`Sprite size mismatch. Expected height ~${Math.round(l)}px (slotCount=${i}, slot aspect 1/1), got ${u}px.`
|
|
335
353
|
);
|
|
336
|
-
this.spriteImg =
|
|
354
|
+
this.spriteImg = s, this.spriteWidth = e, this.frameHeight = Math.max(1, Math.round(r)), this.spriteHeight = Math.max(1, Math.round(this.frameHeight * i));
|
|
337
355
|
}
|
|
338
356
|
setupResizeWatcher() {
|
|
339
357
|
typeof ResizeObserver > "u" || this.resizeObserver || (this.resizeObserver = new ResizeObserver(() => this.queueResize()), this.resizeObserver.observe(this.container));
|
|
@@ -344,32 +362,32 @@ class j {
|
|
|
344
362
|
}));
|
|
345
363
|
};
|
|
346
364
|
resizeToContainer() {
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
365
|
+
const t = this.container.clientWidth, s = this.container.clientHeight, i = this.visibleSlots * this.heightScale;
|
|
366
|
+
if (t <= 0 || s <= 0) {
|
|
349
367
|
this.applyResize(1, 1);
|
|
350
368
|
return;
|
|
351
369
|
}
|
|
352
|
-
const
|
|
353
|
-
let
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
this.applyResize(
|
|
370
|
+
const e = i, r = this.reels, l = t / r * e;
|
|
371
|
+
let u = t, f = l;
|
|
372
|
+
f > s && (f = s, u = s / e * r);
|
|
373
|
+
const c = Math.max(1, Math.floor(u)), n = Math.max(1, Math.floor(f));
|
|
374
|
+
this.applyResize(c, n), this.render();
|
|
357
375
|
}
|
|
358
376
|
applyViewportCrop() {
|
|
359
|
-
this.
|
|
377
|
+
this.ensureCanvasCoversContainer(), this.canvas.style.transform = "";
|
|
360
378
|
}
|
|
361
|
-
applyResize(
|
|
362
|
-
const
|
|
363
|
-
this.width =
|
|
379
|
+
applyResize(t, s) {
|
|
380
|
+
const i = Math.max(1, Math.floor(t)), e = Math.max(1, Math.floor(s)), r = this.getTargetDpr();
|
|
381
|
+
this.width = i, this.height = e, this.dpr = r, this.canvas.width = Math.floor(this.width * this.dpr), this.canvas.height = Math.floor(this.height * this.dpr), this.gl && this.gl.viewport(0, 0, this.canvas.width, this.canvas.height), this.applyViewportCrop();
|
|
364
382
|
}
|
|
365
383
|
getTargetDpr() {
|
|
366
|
-
const
|
|
367
|
-
return Math.min(this.maxDpr,
|
|
384
|
+
const t = window.devicePixelRatio || 1, s = Math.max(1, t);
|
|
385
|
+
return Math.min(this.maxDpr, s);
|
|
368
386
|
}
|
|
369
387
|
tryInitWebGL() {
|
|
370
388
|
if (!this.spriteImg) return;
|
|
371
389
|
this.releaseWebGLResources(), this.webglInitError = null;
|
|
372
|
-
const
|
|
390
|
+
const t = this.canvas.getContext("webgl2", {
|
|
373
391
|
antialias: !0,
|
|
374
392
|
alpha: !0
|
|
375
393
|
}) ?? this.canvas.getContext("webgl", {
|
|
@@ -379,12 +397,12 @@ class j {
|
|
|
379
397
|
antialias: !0,
|
|
380
398
|
alpha: !0
|
|
381
399
|
});
|
|
382
|
-
if (!
|
|
383
|
-
const
|
|
384
|
-
this.webglInitError = `context is null (browser WebGL=${
|
|
400
|
+
if (!t) {
|
|
401
|
+
const n = document.createElement("canvas"), h = typeof WebGLRenderingContext < "u", g = n.getContext("webgl2") ?? n.getContext("webgl");
|
|
402
|
+
this.webglInitError = `context is null (browser WebGL=${h ? "yes" : "no"}, fallback canvas=${g ? "yes" : "no"})`, console.warn("ReelDeal WebGL:", this.webglInitError);
|
|
385
403
|
return;
|
|
386
404
|
}
|
|
387
|
-
const
|
|
405
|
+
const e = this.createWebGLProgram(t, `
|
|
388
406
|
attribute vec2 a_pos;
|
|
389
407
|
attribute vec2 a_uv;
|
|
390
408
|
varying vec2 v_uv;
|
|
@@ -412,6 +430,8 @@ class j {
|
|
|
412
430
|
uniform float u_warpAngle;
|
|
413
431
|
uniform float u_blurPx;
|
|
414
432
|
|
|
433
|
+
const float HALF_PI = 1.5707963;
|
|
434
|
+
|
|
415
435
|
float saturate(float x) { return clamp(x, 0.0, 1.0); }
|
|
416
436
|
|
|
417
437
|
void main() {
|
|
@@ -420,43 +440,33 @@ class j {
|
|
|
420
440
|
}
|
|
421
441
|
|
|
422
442
|
float localX = (v_uv.x - u_reelX) / u_reelW;
|
|
423
|
-
// v_uv.y is authored as 0 at TOP, 1 at BOTTOM (Canvas-like).
|
|
424
443
|
float localY = v_uv.y;
|
|
425
444
|
float hs = max(0.0001, u_heightScale);
|
|
426
445
|
float fullY = localY * hs + (1.0 - hs) * 0.5;
|
|
427
446
|
|
|
428
447
|
float warp = max(0.0, u_warpAngle);
|
|
429
|
-
float warpStrength = saturate(warp /
|
|
430
|
-
float
|
|
431
|
-
float centerBand = 1.0 / 3.0;
|
|
432
|
-
float halfBand = centerBand * 0.5;
|
|
433
|
-
float dist = abs(fullY - center);
|
|
434
|
-
// Smoother transition: avoid a hard boundary at halfBand.
|
|
435
|
-
float edgeWeight = smoothstep(halfBand, 0.5, dist);
|
|
448
|
+
float warpStrength = saturate(warp / HALF_PI);
|
|
449
|
+
float distNorm = abs(fullY - 0.5) * 2.0;
|
|
436
450
|
float edgePow = max(0.5, u_edgePower);
|
|
437
|
-
edgeWeight = pow(
|
|
438
|
-
|
|
439
|
-
float
|
|
440
|
-
float
|
|
441
|
-
float
|
|
442
|
-
|
|
451
|
+
float edgeWeight = pow(saturate(distNorm), edgePow) * warpStrength;
|
|
452
|
+
|
|
453
|
+
float stretchMax = 6.0;
|
|
454
|
+
float stretch = mix(1.0, stretchMax, edgeWeight);
|
|
455
|
+
float warpedY = 0.5 + (fullY - 0.5) * stretch;
|
|
456
|
+
|
|
457
|
+
float arcY = 0.5 + 0.5 * sin((warpedY - 0.5) * HALF_PI);
|
|
458
|
+
warpedY = mix(warpedY, arcY, 0.2);
|
|
443
459
|
|
|
444
460
|
float yPx = u_offsetPx + warpedY * (u_frameHeight * u_visibleSlots);
|
|
445
|
-
// With UNPACK_FLIP_Y_WEBGL=0 and Canvas-like UVs (v=0 at TOP),
|
|
446
|
-
// v=0 samples the TOP of the original image, so pixel Y-from-top maps directly to v.
|
|
447
|
-
// Normalize early to improve precision on mobile GPUs.
|
|
448
461
|
float v = fract((u_offsetPx / u_spriteHeight) +
|
|
449
462
|
warpedY * ((u_frameHeight * u_visibleSlots) / u_spriteHeight));
|
|
450
463
|
|
|
451
464
|
vec4 base = texture2D(u_tex, vec2(localX, v));
|
|
452
465
|
|
|
453
466
|
if (u_blurPx > 0.0) {
|
|
454
|
-
// Clamp blur step to reduce banding on mobile GPUs.
|
|
455
|
-
// Scale max step with speed proxy (u_blurPx).
|
|
456
467
|
float maxStepPx = 1.0 + 3.0 * saturate(u_blurPx / 4.0);
|
|
457
468
|
float blurPx = min(u_blurPx, maxStepPx);
|
|
458
469
|
float blurStep = blurPx / max(1.0, u_spriteHeight);
|
|
459
|
-
// 9-tap gaussian-ish blur to reduce banding on mobile.
|
|
460
470
|
vec4 sum = base * 0.227027;
|
|
461
471
|
sum += texture2D(u_tex, vec2(localX, fract(v + blurStep))) * 0.1945946;
|
|
462
472
|
sum += texture2D(u_tex, vec2(localX, fract(v - blurStep))) * 0.1945946;
|
|
@@ -472,72 +482,71 @@ class j {
|
|
|
472
482
|
gl_FragColor = base;
|
|
473
483
|
}
|
|
474
484
|
`);
|
|
475
|
-
if (!
|
|
485
|
+
if (!e) {
|
|
476
486
|
this.webglInitError = "shader program failed to compile/link (see console)", console.warn("ReelDeal WebGL:", this.webglInitError);
|
|
477
487
|
return;
|
|
478
488
|
}
|
|
479
|
-
const r =
|
|
489
|
+
const r = t.createBuffer();
|
|
480
490
|
if (!r) {
|
|
481
|
-
this.webglInitError = "failed to create vertex buffer", console.warn("ReelDeal WebGL:", this.webglInitError),
|
|
491
|
+
this.webglInitError = "failed to create vertex buffer", console.warn("ReelDeal WebGL:", this.webglInitError), t.deleteProgram(e);
|
|
482
492
|
return;
|
|
483
493
|
}
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
if (!
|
|
489
|
-
this.webglInitError = "failed to create texture", console.warn("ReelDeal WebGL:", this.webglInitError),
|
|
494
|
+
t.bindBuffer(t.ARRAY_BUFFER, r);
|
|
495
|
+
const l = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, 1, 1, 1, 0]);
|
|
496
|
+
t.bufferData(t.ARRAY_BUFFER, l, t.STATIC_DRAW);
|
|
497
|
+
const u = t.createTexture();
|
|
498
|
+
if (!u) {
|
|
499
|
+
this.webglInitError = "failed to create texture", console.warn("ReelDeal WebGL:", this.webglInitError), t.deleteBuffer(r), t.deleteProgram(e);
|
|
490
500
|
return;
|
|
491
501
|
}
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
pos:
|
|
496
|
-
uv:
|
|
502
|
+
t.bindTexture(t.TEXTURE_2D, u), t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL, 0), t.texImage2D(t.TEXTURE_2D, 0, t.RGBA, t.RGBA, t.UNSIGNED_BYTE, this.spriteImg);
|
|
503
|
+
const f = (n) => n > 0 && (n & n - 1) === 0;
|
|
504
|
+
f(this.spriteWidth) && f(this.spriteHeight) ? (t.texParameteri(t.TEXTURE_2D, t.TEXTURE_WRAP_S, t.REPEAT), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_WRAP_T, t.REPEAT), t.generateMipmap(t.TEXTURE_2D), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_MIN_FILTER, t.LINEAR_MIPMAP_LINEAR), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_MAG_FILTER, t.LINEAR)) : (t.texParameteri(t.TEXTURE_2D, t.TEXTURE_MIN_FILTER, t.LINEAR), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_MAG_FILTER, t.LINEAR), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_WRAP_S, t.CLAMP_TO_EDGE), t.texParameteri(t.TEXTURE_2D, t.TEXTURE_WRAP_T, t.CLAMP_TO_EDGE)), t.useProgram(e), t.clearColor(0, 0, 0, 0), t.enable(t.BLEND), t.blendFunc(t.SRC_ALPHA, t.ONE_MINUS_SRC_ALPHA), this.gl = t, this.glProgram = e, this.glBuffer = r, this.glTexture = u, this.glAttribs = {
|
|
505
|
+
pos: t.getAttribLocation(e, "a_pos"),
|
|
506
|
+
uv: t.getAttribLocation(e, "a_uv")
|
|
497
507
|
}, this.glUniforms = {
|
|
498
|
-
texture:
|
|
499
|
-
reelX:
|
|
500
|
-
reelW:
|
|
501
|
-
offsetPx:
|
|
502
|
-
frameHeight:
|
|
503
|
-
spriteHeight:
|
|
504
|
-
visibleSlots:
|
|
505
|
-
heightScale:
|
|
506
|
-
edgePower:
|
|
507
|
-
warpAngle:
|
|
508
|
-
blurPx:
|
|
508
|
+
texture: t.getUniformLocation(e, "u_tex"),
|
|
509
|
+
reelX: t.getUniformLocation(e, "u_reelX"),
|
|
510
|
+
reelW: t.getUniformLocation(e, "u_reelW"),
|
|
511
|
+
offsetPx: t.getUniformLocation(e, "u_offsetPx"),
|
|
512
|
+
frameHeight: t.getUniformLocation(e, "u_frameHeight"),
|
|
513
|
+
spriteHeight: t.getUniformLocation(e, "u_spriteHeight"),
|
|
514
|
+
visibleSlots: t.getUniformLocation(e, "u_visibleSlots"),
|
|
515
|
+
heightScale: t.getUniformLocation(e, "u_heightScale"),
|
|
516
|
+
edgePower: t.getUniformLocation(e, "u_edgePower"),
|
|
517
|
+
warpAngle: t.getUniformLocation(e, "u_warpAngle"),
|
|
518
|
+
blurPx: t.getUniformLocation(e, "u_blurPx")
|
|
509
519
|
};
|
|
510
520
|
}
|
|
511
|
-
createWebGLProgram(
|
|
512
|
-
const
|
|
513
|
-
if (!
|
|
514
|
-
return
|
|
515
|
-
const
|
|
516
|
-
if (!
|
|
517
|
-
if (
|
|
518
|
-
const
|
|
519
|
-
return this.webglInitError = `program link failed: ${
|
|
521
|
+
createWebGLProgram(t, s, i) {
|
|
522
|
+
const e = this.createWebGLShader(t, t.VERTEX_SHADER, s), r = this.createWebGLShader(t, t.FRAGMENT_SHADER, i);
|
|
523
|
+
if (!e || !r)
|
|
524
|
+
return e && t.deleteShader(e), r && t.deleteShader(r), null;
|
|
525
|
+
const l = t.createProgram();
|
|
526
|
+
if (!l) return null;
|
|
527
|
+
if (t.attachShader(l, e), t.attachShader(l, r), t.linkProgram(l), !t.getProgramParameter(l, t.LINK_STATUS)) {
|
|
528
|
+
const u = t.getProgramInfoLog(l) ?? "unknown link error";
|
|
529
|
+
return this.webglInitError = `program link failed: ${u}`, console.warn("ReelDeal WebGL: program link failed", u), t.deleteProgram(l), t.deleteShader(e), t.deleteShader(r), null;
|
|
520
530
|
}
|
|
521
|
-
return
|
|
522
|
-
}
|
|
523
|
-
createWebGLShader(
|
|
524
|
-
const
|
|
525
|
-
if (!
|
|
526
|
-
if (
|
|
527
|
-
const r =
|
|
528
|
-
return this.webglInitError = `shader compile failed: ${r}`, console.warn("ReelDeal WebGL: shader compile failed", r),
|
|
531
|
+
return t.detachShader(l, e), t.detachShader(l, r), t.deleteShader(e), t.deleteShader(r), l;
|
|
532
|
+
}
|
|
533
|
+
createWebGLShader(t, s, i) {
|
|
534
|
+
const e = t.createShader(s);
|
|
535
|
+
if (!e) return null;
|
|
536
|
+
if (t.shaderSource(e, i), t.compileShader(e), !t.getShaderParameter(e, t.COMPILE_STATUS)) {
|
|
537
|
+
const r = t.getShaderInfoLog(e) ?? "unknown compile error";
|
|
538
|
+
return this.webglInitError = `shader compile failed: ${r}`, console.warn("ReelDeal WebGL: shader compile failed", r), t.deleteShader(e), null;
|
|
529
539
|
}
|
|
530
|
-
return
|
|
540
|
+
return e;
|
|
531
541
|
}
|
|
532
542
|
releaseWebGLResources() {
|
|
533
543
|
this.gl && (this.glTexture && this.gl.deleteTexture(this.glTexture), this.glBuffer && this.gl.deleteBuffer(this.glBuffer), this.glProgram && this.gl.deleteProgram(this.glProgram), this.glTexture = null, this.glBuffer = null, this.glProgram = null, this.glAttribs = null, this.glUniforms = null, this.gl = null);
|
|
534
544
|
}
|
|
535
545
|
}
|
|
536
546
|
export {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
y as defaultWebGLShading
|
|
547
|
+
ut as ReelDeal,
|
|
548
|
+
E as defaultIdleBob,
|
|
549
|
+
C as defaultSpinBlur,
|
|
550
|
+
T as defaultSpinConfig
|
|
542
551
|
};
|
|
543
552
|
//# sourceMappingURL=index.es.js.map
|