reel-deal 0.1.0-dev.0 → 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.
@@ -0,0 +1,554 @@
1
+ const T = {
2
+ minSpins: 2,
3
+ durationMs: 4e3,
4
+ speedPxS: 4e3,
5
+ staggerMs: 300
6
+ }, B = {
7
+ maxPx: 4,
8
+ speedAtMax: 2200
9
+ }, E = {
10
+ amplitudePx: 9,
11
+ speedHz: 0.25,
12
+ phaseOffsetRad: 0.9,
13
+ rampMs: 800
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 ?? {} };
32
+ return {
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)
37
+ };
38
+ }, Z = (a) => {
39
+ const t = { ...B, ...a ?? {} };
40
+ return {
41
+ maxPx: _(t.maxPx, B.maxPx),
42
+ speedAtMax: _(t.speedAtMax, B.speedAtMax)
43
+ };
44
+ }, j = (a) => {
45
+ if (a === !1) return null;
46
+ const t = { ...E, ...a ?? {} };
47
+ return {
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)
52
+ };
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 {
66
+ canvas;
67
+ container;
68
+ opts;
69
+ visibleSlots = 3;
70
+ heightScale = 2 / 3;
71
+ centerIndex = 1;
72
+ reels;
73
+ spinConfig;
74
+ spinBlur;
75
+ webglShading;
76
+ idleBob;
77
+ maxDpr;
78
+ spriteImg = null;
79
+ spriteWidth = 0;
80
+ spriteHeight = 0;
81
+ frameHeight = 0;
82
+ width = 0;
83
+ height = 0;
84
+ dpr = 1;
85
+ offsets;
86
+ velocities;
87
+ lastVelT = null;
88
+ lastVelOffsets;
89
+ idleStartTime = null;
90
+ idleBaseOffsets;
91
+ gl = null;
92
+ glProgram = null;
93
+ glBuffer = null;
94
+ glTexture = null;
95
+ glAttribs = null;
96
+ webglInitError = null;
97
+ glUniforms = null;
98
+ anim = null;
99
+ rafId = null;
100
+ pendingSpinResolvers = [];
101
+ resizeObserver = null;
102
+ resizeQueued = !1;
103
+ button = null;
104
+ buttonHandlerAttached = !1;
105
+ spinning = !1;
106
+ queuedSpins = null;
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);
111
+ }
112
+ async init() {
113
+ if (await this.loadSprite(), this.tryInitWebGL(), !this.gl)
114
+ throw new Error(
115
+ `WebGL is not supported or failed to initialize. ${this.webglInitError ?? ""}`.trim()
116
+ );
117
+ this.syncContainerLayoutVars(), this.ensureCanvasCoversContainer(), this.setupResizeWatcher(), this.resizeToContainer(), this.setSegments(this.opts.initialSegments), this.button = this.resolveButton(), this.queuedSpins = this.resolveSpinQueue(), this.attachButtonHandler(), this.render(), this.startLoop();
118
+ }
119
+ destroy() {
120
+ this.stop(), this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.button && this.buttonHandlerAttached && (this.button.removeEventListener("click", this.handleButtonClick), this.buttonHandlerAttached = !1), this.releaseWebGLResources();
121
+ }
122
+ ensureCanvasCoversContainer() {
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"));
124
+ }
125
+ syncContainerLayoutVars() {
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");
128
+ }
129
+ setSegments(t) {
130
+ this.ensureReady();
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];
135
+ }
136
+ this.lastVelT = null, this.idleStartTime = null, this.anim = null, this.stop(), this.render(), this.startLoop();
137
+ }
138
+ setSegment(t) {
139
+ this.setSegments(new Array(this.reels).fill(t));
140
+ }
141
+ spinOnce(t, s) {
142
+ this.ensureReady();
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)
151
+ );
152
+ x = -(H + P * this.spriteHeight);
153
+ }
154
+ if (e > 0 && u) {
155
+ const M = r * e * o / 1e3, P = Math.ceil(M / this.spriteHeight);
156
+ x -= P * this.spriteHeight;
157
+ }
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);
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];
170
+ return this.anim = null, this.resolvePendingSpins(), this.render(), Promise.resolve();
171
+ }
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);
182
+ });
183
+ }
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);
195
+ }
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);
200
+ }
201
+ }
202
+ stop() {
203
+ this.rafId !== null && cancelAnimationFrame(this.rafId), this.rafId = null, this.anim = null, this.resolvePendingSpins(), this.lastVelT = null;
204
+ for (let t = 0; t < this.reels; t += 1)
205
+ this.velocities[t] = 0;
206
+ }
207
+ resolvePendingSpins() {
208
+ if (this.pendingSpinResolvers.length === 0) return;
209
+ const t = this.pendingSpinResolvers;
210
+ this.pendingSpinResolvers = [];
211
+ for (const s of t)
212
+ s();
213
+ }
214
+ requestResize() {
215
+ this.queueResize();
216
+ }
217
+ ensureReady() {
218
+ if (!this.spriteImg || this.spriteWidth <= 0 || this.spriteHeight <= 0 || this.frameHeight <= 0)
219
+ throw new Error("Sprite is not ready. Call init() and await it first.");
220
+ }
221
+ attachButtonHandler() {
222
+ !this.button || this.buttonHandlerAttached || (this.button.addEventListener("click", this.handleButtonClick, { passive: !0 }), this.buttonHandlerAttached = !0);
223
+ }
224
+ handleButtonClick = async () => {
225
+ if (this.spinning) return;
226
+ const t = this.consumeNextSpin() ?? {
227
+ stopAtSegments: this.buildRandomSegments()
228
+ };
229
+ this.spinning = !0, this.setButtonDisabled(!0);
230
+ try {
231
+ await this.spinOnce(t.stopAtSegments);
232
+ } finally {
233
+ this.queuedSpins && this.queuedSpins.length === 0 ? this.setButtonDisabled(!0) : this.setButtonDisabled(!1), this.spinning = !1;
234
+ }
235
+ };
236
+ resolveButton() {
237
+ return this.opts.button ? U(this.opts.button, "Button") : null;
238
+ }
239
+ resolveSpinQueue() {
240
+ return this.opts.queuedSpinStates ?? null;
241
+ }
242
+ consumeNextSpin() {
243
+ return this.queuedSpins || (this.queuedSpins = this.resolveSpinQueue()), !this.queuedSpins || this.queuedSpins.length === 0 ? null : this.queuedSpins.shift() ?? null;
244
+ }
245
+ buildRandomSegments() {
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;
250
+ }
251
+ setButtonDisabled(t) {
252
+ this.button && (this.button.disabled = t, t ? this.button.setAttribute("aria-busy", "true") : this.button.removeAttribute("aria-busy"));
253
+ }
254
+ shouldAnimate() {
255
+ return this.anim !== null || this.idleBob !== null;
256
+ }
257
+ startLoop() {
258
+ this.rafId === null && this.shouldAnimate() && (this.rafId = requestAnimationFrame(this.loop));
259
+ }
260
+ loop = (t) => {
261
+ if (this.anim) {
262
+ const { startTime: s, durationMs: i, startOffsets: e, deltas: r, settleDurationMs: l, targets: u } = this.anim, f = t - s;
263
+ let c = !0;
264
+ for (let n = 0; n < this.reels; n += 1) {
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;
281
+ }
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);
288
+ for (let n = 0; n < this.reels; n += 1)
289
+ this.velocities[n] = 0;
290
+ this.anim = null, this.idleStartTime = null, this.render(), this.resolvePendingSpins();
291
+ }
292
+ } else this.idleBob && (this.updateIdle(t), this.render());
293
+ if (!this.shouldAnimate()) {
294
+ this.rafId = null;
295
+ return;
296
+ }
297
+ this.rafId !== null && (this.rafId = requestAnimationFrame(this.loop));
298
+ };
299
+ render() {
300
+ this.spriteImg && (this.width <= 0 || this.height <= 0 || this.gl && this.renderWebGL());
301
+ }
302
+ getSpinBlurPxForReel(t) {
303
+ if (!this.anim) return 0;
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);
306
+ }
307
+ renderWebGL() {
308
+ if (!this.gl || !this.glProgram || !this.glAttribs || !this.glUniforms || !this.glTexture || !this.glBuffer || !this.spriteImg) return;
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;
314
+ for (let r = 0; r < this.reels; r += 1) {
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);
318
+ }
319
+ }
320
+ updateVelocity(t) {
321
+ if (this.lastVelT === null) {
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;
325
+ return;
326
+ }
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];
332
+ }
333
+ this.lastVelT = t;
334
+ }
335
+ }
336
+ updateIdle(t) {
337
+ if (!this.idleBob) return;
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;
340
+ for (let h = 0; h < this.reels; h += 1) {
341
+ const g = u * n + h * e;
342
+ this.offsets[h] = this.idleBaseOffsets[h] + Math.sin(g) * (s * c), this.velocities[h] = 0;
343
+ }
344
+ }
345
+ async loadSprite() {
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 });
350
+ });
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)
353
+ throw new Error(
354
+ `Sprite size mismatch. Expected height ~${Math.round(l)}px (slotCount=${i}, slot aspect 1/1), got ${u}px.`
355
+ );
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));
357
+ }
358
+ setupResizeWatcher() {
359
+ typeof ResizeObserver > "u" || this.resizeObserver || (this.resizeObserver = new ResizeObserver(() => this.queueResize()), this.resizeObserver.observe(this.container));
360
+ }
361
+ queueResize = () => {
362
+ this.resizeQueued || (this.resizeQueued = !0, requestAnimationFrame(() => {
363
+ this.resizeQueued = !1, this.resizeToContainer();
364
+ }));
365
+ };
366
+ resizeToContainer() {
367
+ const t = this.container.clientWidth, s = this.container.clientHeight, i = this.visibleSlots * this.heightScale;
368
+ if (t <= 0 || s <= 0) {
369
+ this.applyResize(1, 1);
370
+ return;
371
+ }
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();
377
+ }
378
+ applyViewportCrop() {
379
+ this.canvas.style.width = "100%", this.canvas.style.height = "100%", this.canvas.style.transform = "";
380
+ }
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();
384
+ }
385
+ getTargetDpr() {
386
+ const t = window.devicePixelRatio || 1, s = Math.max(1, t);
387
+ return Math.min(this.maxDpr, s);
388
+ }
389
+ tryInitWebGL() {
390
+ if (!this.spriteImg) return;
391
+ this.releaseWebGLResources(), this.webglInitError = null;
392
+ const t = this.canvas.getContext("webgl2", {
393
+ antialias: !0,
394
+ alpha: !0
395
+ }) ?? this.canvas.getContext("webgl", {
396
+ antialias: !0,
397
+ alpha: !0
398
+ }) ?? this.canvas.getContext("experimental-webgl", {
399
+ antialias: !0,
400
+ alpha: !0
401
+ });
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);
405
+ return;
406
+ }
407
+ const e = this.createWebGLProgram(t, `
408
+ attribute vec2 a_pos;
409
+ attribute vec2 a_uv;
410
+ varying vec2 v_uv;
411
+ void main() {
412
+ v_uv = a_uv;
413
+ gl_Position = vec4(a_pos, 0.0, 1.0);
414
+ }
415
+ `, `
416
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
417
+ precision highp float;
418
+ #else
419
+ precision mediump float;
420
+ #endif
421
+
422
+ varying vec2 v_uv;
423
+ uniform sampler2D u_tex;
424
+ uniform float u_reelX;
425
+ uniform float u_reelW;
426
+ uniform float u_offsetPx;
427
+ uniform float u_frameHeight;
428
+ uniform float u_spriteHeight;
429
+ uniform float u_visibleSlots;
430
+ uniform float u_heightScale;
431
+ uniform float u_edgePower;
432
+ uniform float u_warpAngle;
433
+ uniform float u_blurPx;
434
+
435
+ const float HALF_PI = 1.5707963;
436
+
437
+ float saturate(float x) { return clamp(x, 0.0, 1.0); }
438
+
439
+ void main() {
440
+ if (v_uv.x < u_reelX || v_uv.x > (u_reelX + u_reelW)) {
441
+ discard;
442
+ }
443
+
444
+ float localX = (v_uv.x - u_reelX) / u_reelW;
445
+ float localY = v_uv.y;
446
+ float hs = max(0.0001, u_heightScale);
447
+ float fullY = localY * hs + (1.0 - hs) * 0.5;
448
+
449
+ float warp = max(0.0, u_warpAngle);
450
+ float warpStrength = saturate(warp / 1.2);
451
+ float center = 0.5;
452
+ float centerBand = 1.0 / 3.0;
453
+ float halfBand = centerBand * 0.5;
454
+ float dist = abs(fullY - center);
455
+ float edgeWeight = smoothstep(halfBand, 0.5, dist);
456
+ float edgePow = max(0.5, u_edgePower);
457
+ edgeWeight = pow(edgeWeight, edgePow) * warpStrength;
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);
461
+
462
+ float yPx = u_offsetPx + warpedY * (u_frameHeight * u_visibleSlots);
463
+ float v = fract((u_offsetPx / u_spriteHeight) +
464
+ warpedY * ((u_frameHeight * u_visibleSlots) / u_spriteHeight));
465
+
466
+ vec4 base = texture2D(u_tex, vec2(localX, v));
467
+
468
+ if (u_blurPx > 0.0) {
469
+ float maxStepPx = 1.0 + 3.0 * saturate(u_blurPx / 4.0);
470
+ float blurPx = min(u_blurPx, maxStepPx);
471
+ float blurStep = blurPx / max(1.0, u_spriteHeight);
472
+ vec4 sum = base * 0.227027;
473
+ sum += texture2D(u_tex, vec2(localX, fract(v + blurStep))) * 0.1945946;
474
+ sum += texture2D(u_tex, vec2(localX, fract(v - blurStep))) * 0.1945946;
475
+ sum += texture2D(u_tex, vec2(localX, fract(v + 2.0 * blurStep))) * 0.1216216;
476
+ sum += texture2D(u_tex, vec2(localX, fract(v - 2.0 * blurStep))) * 0.1216216;
477
+ sum += texture2D(u_tex, vec2(localX, fract(v + 3.0 * blurStep))) * 0.054054;
478
+ sum += texture2D(u_tex, vec2(localX, fract(v - 3.0 * blurStep))) * 0.054054;
479
+ sum += texture2D(u_tex, vec2(localX, fract(v + 4.0 * blurStep))) * 0.016216;
480
+ sum += texture2D(u_tex, vec2(localX, fract(v - 4.0 * blurStep))) * 0.016216;
481
+ base = sum;
482
+ }
483
+
484
+ gl_FragColor = base;
485
+ }
486
+ `);
487
+ if (!e) {
488
+ this.webglInitError = "shader program failed to compile/link (see console)", console.warn("ReelDeal WebGL:", this.webglInitError);
489
+ return;
490
+ }
491
+ const r = t.createBuffer();
492
+ if (!r) {
493
+ this.webglInitError = "failed to create vertex buffer", console.warn("ReelDeal WebGL:", this.webglInitError), t.deleteProgram(e);
494
+ return;
495
+ }
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);
502
+ return;
503
+ }
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")
509
+ }, this.glUniforms = {
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")
521
+ };
522
+ }
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;
532
+ }
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;
541
+ }
542
+ return e;
543
+ }
544
+ releaseWebGLResources() {
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);
546
+ }
547
+ }
548
+ export {
549
+ ut as ReelDeal,
550
+ E as defaultIdleBob,
551
+ B as defaultSpinBlur,
552
+ T as defaultSpinConfig
553
+ };
554
+ //# sourceMappingURL=index.es.js.map