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