reel-deal 0.1.0 → 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 -0
- package/dist/index.cjs.js +11 -11
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.es.js +50 -52
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +11 -11
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -3,7 +3,7 @@ const T = {
|
|
|
3
3
|
durationMs: 4e3,
|
|
4
4
|
speedPxS: 4e3,
|
|
5
5
|
staggerMs: 300
|
|
6
|
-
},
|
|
6
|
+
}, C = {
|
|
7
7
|
maxPx: 4,
|
|
8
8
|
speedAtMax: 2200
|
|
9
9
|
}, E = {
|
|
@@ -11,7 +11,7 @@ const T = {
|
|
|
11
11
|
speedHz: 0.25,
|
|
12
12
|
phaseOffsetRad: 0.9,
|
|
13
13
|
rampMs: 800
|
|
14
|
-
},
|
|
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
15
|
const t = v(a);
|
|
16
16
|
return t ** 2 * (2 - t);
|
|
17
17
|
}, $ = (a) => {
|
|
@@ -19,27 +19,27 @@ const T = {
|
|
|
19
19
|
return t + t ** 2 - t ** 3;
|
|
20
20
|
}, Q = (a) => {
|
|
21
21
|
const t = v(a);
|
|
22
|
-
if (t <=
|
|
23
|
-
const i = t /
|
|
24
|
-
return F(i) *
|
|
22
|
+
if (t <= B) {
|
|
23
|
+
const i = t / B;
|
|
24
|
+
return F(i) * B;
|
|
25
25
|
}
|
|
26
26
|
if (t < A)
|
|
27
27
|
return t;
|
|
28
28
|
const s = (t - A) / (1 - A);
|
|
29
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)),
|
|
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
31
|
const t = { ...T, ...a ?? {} };
|
|
32
32
|
return {
|
|
33
|
-
minSpins:
|
|
34
|
-
durationMs:
|
|
35
|
-
speedPxS:
|
|
36
|
-
staggerMs:
|
|
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)
|
|
37
37
|
};
|
|
38
38
|
}, Z = (a) => {
|
|
39
|
-
const t = { ...
|
|
39
|
+
const t = { ...C, ...a ?? {} };
|
|
40
40
|
return {
|
|
41
|
-
maxPx: _(t.maxPx,
|
|
42
|
-
speedAtMax: _(t.speedAtMax,
|
|
41
|
+
maxPx: _(t.maxPx, C.maxPx),
|
|
42
|
+
speedAtMax: _(t.speedAtMax, C.speedAtMax)
|
|
43
43
|
};
|
|
44
44
|
}, j = (a) => {
|
|
45
45
|
if (a === !1) return null;
|
|
@@ -48,16 +48,16 @@ const T = {
|
|
|
48
48
|
amplitudePx: _(t.amplitudePx, E.amplitudePx),
|
|
49
49
|
speedHz: _(t.speedHz, E.speedHz),
|
|
50
50
|
phaseOffsetRad: L(t.phaseOffsetRad, E.phaseOffsetRad),
|
|
51
|
-
rampMs:
|
|
51
|
+
rampMs: w(t.rampMs, E.rampMs)
|
|
52
52
|
};
|
|
53
53
|
}, J = (a) => Math.max(1, L(a, K)), tt = (a, t) => {
|
|
54
54
|
const s = document.getElementById(a);
|
|
55
55
|
if (!s)
|
|
56
56
|
throw new Error(`${t} not found: ${a}`);
|
|
57
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 =
|
|
59
|
-
warpAngleDeg:
|
|
60
|
-
edgePower:
|
|
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
61
|
}, ht = (a) => ({
|
|
62
62
|
warpAngleDeg: _(a?.warpAngleDeg, N.warpAngleDeg),
|
|
63
63
|
edgePower: _(a?.edgePower, N.edgePower)
|
|
@@ -105,9 +105,7 @@ class ut {
|
|
|
105
105
|
spinning = !1;
|
|
106
106
|
queuedSpins = null;
|
|
107
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);
|
|
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);
|
|
111
109
|
}
|
|
112
110
|
async init() {
|
|
113
111
|
if (await this.loadSprite(), this.tryInitWebGL(), !this.gl)
|
|
@@ -142,29 +140,29 @@ class ut {
|
|
|
142
140
|
this.ensureReady();
|
|
143
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);
|
|
144
142
|
for (let o = 0; o < this.reels; o += 1) {
|
|
145
|
-
const
|
|
146
|
-
let
|
|
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);
|
|
147
145
|
if (u && l > 0) {
|
|
148
|
-
const
|
|
146
|
+
const R = r * l / 1e3, P = Math.max(
|
|
149
147
|
i,
|
|
150
|
-
Math.ceil(Math.max(0,
|
|
148
|
+
Math.ceil(Math.max(0, R - H) / this.spriteHeight)
|
|
151
149
|
);
|
|
152
|
-
|
|
150
|
+
b = -(H + P * this.spriteHeight);
|
|
153
151
|
}
|
|
154
152
|
if (e > 0 && u) {
|
|
155
|
-
const
|
|
156
|
-
|
|
153
|
+
const R = r * e * o / 1e3, P = Math.ceil(R / this.spriteHeight);
|
|
154
|
+
b -= P * this.spriteHeight;
|
|
157
155
|
}
|
|
158
|
-
h[o] = this.offsets[o], m[o] = this.offsets[o] +
|
|
159
|
-
let D =
|
|
160
|
-
if (n &&
|
|
161
|
-
const P = Math.sign(
|
|
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;
|
|
162
160
|
D += P, G = c;
|
|
163
161
|
}
|
|
164
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);
|
|
165
163
|
}
|
|
166
|
-
const S = g.reduce((o,
|
|
167
|
-
if (S === 0 ||
|
|
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) {
|
|
168
166
|
for (let o = 0; o < this.reels; o += 1)
|
|
169
167
|
this.offsets[o] = m[o];
|
|
170
168
|
return this.anim = null, this.resolvePendingSpins(), this.render(), Promise.resolve();
|
|
@@ -190,7 +188,7 @@ class ut {
|
|
|
190
188
|
}
|
|
191
189
|
getStopSpringOffset(t, s) {
|
|
192
190
|
if (t === 0) return 0;
|
|
193
|
-
const i = Math.exp(-
|
|
191
|
+
const i = Math.exp(-5 * s), e = Math.PI * 2 * ot * s;
|
|
194
192
|
return t * i * Math.cos(e);
|
|
195
193
|
}
|
|
196
194
|
async spinQueue(t) {
|
|
@@ -267,15 +265,15 @@ class ut {
|
|
|
267
265
|
const d = Math.min(1, f / h), m = Q(d);
|
|
268
266
|
d < 1 && (c = !1);
|
|
269
267
|
const S = e[n] + r[n] * m;
|
|
270
|
-
let
|
|
268
|
+
let x = 0;
|
|
271
269
|
if (d > z && this.frameHeight > 0) {
|
|
272
|
-
const y = (1 - v((d - z) / st)) ** 2, o = S / this.frameHeight * Math.PI * 2,
|
|
273
|
-
|
|
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;
|
|
274
272
|
}
|
|
275
|
-
this.offsets[n] = S +
|
|
273
|
+
this.offsets[n] = S + x;
|
|
276
274
|
} else if (g > 0) {
|
|
277
|
-
const d = h <= 0 ? f / g : (f - h) / g, m = v(d), S = e[n] + r[n] - p,
|
|
278
|
-
m < 1 && (c = !1), this.offsets[n] = p +
|
|
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;
|
|
279
277
|
} else
|
|
280
278
|
this.offsets[n] = p;
|
|
281
279
|
}
|
|
@@ -376,7 +374,7 @@ class ut {
|
|
|
376
374
|
this.applyResize(c, n), this.render();
|
|
377
375
|
}
|
|
378
376
|
applyViewportCrop() {
|
|
379
|
-
this.
|
|
377
|
+
this.ensureCanvasCoversContainer(), this.canvas.style.transform = "";
|
|
380
378
|
}
|
|
381
379
|
applyResize(t, s) {
|
|
382
380
|
const i = Math.max(1, Math.floor(t)), e = Math.max(1, Math.floor(s)), r = this.getTargetDpr();
|
|
@@ -447,17 +445,17 @@ class ut {
|
|
|
447
445
|
float fullY = localY * hs + (1.0 - hs) * 0.5;
|
|
448
446
|
|
|
449
447
|
float warp = max(0.0, u_warpAngle);
|
|
450
|
-
float warpStrength = saturate(warp /
|
|
451
|
-
float
|
|
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);
|
|
448
|
+
float warpStrength = saturate(warp / HALF_PI);
|
|
449
|
+
float distNorm = abs(fullY - 0.5) * 2.0;
|
|
456
450
|
float edgePow = max(0.5, u_edgePower);
|
|
457
|
-
edgeWeight = pow(
|
|
458
|
-
|
|
459
|
-
float
|
|
460
|
-
float
|
|
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);
|
|
461
459
|
|
|
462
460
|
float yPx = u_offsetPx + warpedY * (u_frameHeight * u_visibleSlots);
|
|
463
461
|
float v = fract((u_offsetPx / u_spriteHeight) +
|
|
@@ -548,7 +546,7 @@ class ut {
|
|
|
548
546
|
export {
|
|
549
547
|
ut as ReelDeal,
|
|
550
548
|
E as defaultIdleBob,
|
|
551
|
-
|
|
549
|
+
C as defaultSpinBlur,
|
|
552
550
|
T as defaultSpinConfig
|
|
553
551
|
};
|
|
554
552
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/defaults.ts","../src/utils/math.ts","../src/normalize.ts","../src/utils/dom.ts","../src/ReelDeal.ts"],"sourcesContent":["import type { ReelDealIdleBob, ReelDealSpinBlur, ReelDealSpinConfig } from './types';\n\nexport const defaultSpinConfig: ReelDealSpinConfig = {\n minSpins: 2,\n durationMs: 4000,\n speedPxS: 4000,\n staggerMs: 300,\n};\n\nexport const defaultSpinBlur: ReelDealSpinBlur = {\n maxPx: 4,\n speedAtMax: 2200,\n};\n\nexport const defaultIdleBob: ReelDealIdleBob = {\n amplitudePx: 9,\n speedHz: 0.25,\n phaseOffsetRad: 0.9,\n rampMs: 800,\n};\n","const SPIN_ACCEL_END = 0.1;\nconst SPIN_DECEL_START = 0.6;\n\nexport const clampInt = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, Math.floor(value)));\n\nexport const mod = (n: number, m: number): number => ((n % m) + m) % m;\n\nexport const clamp01 = (t: number): number => Math.max(0, Math.min(1, t));\n\nexport const rampHermite = (u: number): number => {\n const x = clamp01(u);\n\n return x ** 2 * (2 - x);\n};\n\nexport const brakeHermite = (u: number): number => {\n const x = clamp01(u);\n\n return x + x ** 2 - x ** 3;\n};\n\nexport const reelProgress = (t: number): number => {\n const tt = clamp01(t);\n\n if (tt <= SPIN_ACCEL_END) {\n const u = tt / SPIN_ACCEL_END;\n\n return rampHermite(u) * SPIN_ACCEL_END;\n }\n\n if (tt < SPIN_DECEL_START) {\n return tt;\n }\n\n const u = (tt - SPIN_DECEL_START) / (1 - SPIN_DECEL_START);\n\n return SPIN_DECEL_START + brakeHermite(u) * (1 - SPIN_DECEL_START);\n};\n\nexport const finiteOr = (value: number | undefined, fallback: number): number =>\n typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n\nexport const nonNegative = (value: number | undefined, fallback: number): number =>\n Math.max(0, finiteOr(value, fallback));\n\nexport const nonNegativeInt = (value: number | undefined, fallback: number): number =>\n Math.max(0, Math.floor(finiteOr(value, fallback)));\n","import { defaultIdleBob, defaultSpinBlur, defaultSpinConfig } from './defaults';\nimport type { ReelDealIdleBob, ReelDealSpinBlur, ReelDealSpinConfig } from './types';\nimport { finiteOr, nonNegative, nonNegativeInt } from './utils/math';\n\nconst DEFAULT_MAX_DPR = 2;\n\nexport type NormalizedSpinConfig = Required<ReelDealSpinConfig>;\n\nexport const normalizeSpinConfig = (\n overrides?: Partial<ReelDealSpinConfig>,\n): NormalizedSpinConfig => {\n const base = { ...defaultSpinConfig, ...(overrides ?? {}) };\n\n return {\n minSpins: nonNegativeInt(base.minSpins, defaultSpinConfig.minSpins),\n durationMs: nonNegativeInt(base.durationMs, defaultSpinConfig.durationMs ?? 0),\n speedPxS: nonNegativeInt(base.speedPxS, defaultSpinConfig.speedPxS ?? 0),\n staggerMs: nonNegativeInt(base.staggerMs, defaultSpinConfig.staggerMs ?? 0),\n };\n};\n\nexport const normalizeSpinBlur = (overrides?: Partial<ReelDealSpinBlur>): ReelDealSpinBlur => {\n const base = { ...defaultSpinBlur, ...(overrides ?? {}) };\n\n return {\n maxPx: nonNegative(base.maxPx, defaultSpinBlur.maxPx),\n speedAtMax: nonNegative(base.speedAtMax, defaultSpinBlur.speedAtMax),\n };\n};\n\nexport const normalizeIdleBob = (\n overrides?: Partial<ReelDealIdleBob> | false,\n): ReelDealIdleBob | null => {\n if (overrides === false) return null;\n\n const base = { ...defaultIdleBob, ...(overrides ?? {}) };\n\n return {\n amplitudePx: nonNegative(base.amplitudePx, defaultIdleBob.amplitudePx),\n speedHz: nonNegative(base.speedHz, defaultIdleBob.speedHz),\n phaseOffsetRad: finiteOr(base.phaseOffsetRad, defaultIdleBob.phaseOffsetRad),\n rampMs: nonNegativeInt(base.rampMs, defaultIdleBob.rampMs),\n };\n};\n\nexport const normalizeMaxDpr = (value?: number): number =>\n Math.max(1, finiteOr(value, DEFAULT_MAX_DPR));\n","export const resolveElementById = <T extends HTMLElement>(id: string, kind: string): T => {\n const el = document.getElementById(id);\n\n if (!el) {\n throw new Error(`${kind} not found: ${id}`);\n }\n\n return el as T;\n};\n\nexport const resolveElement = <T extends HTMLElement>(elOrId: T | string, kind: string): T => {\n if (typeof elOrId === 'string') return resolveElementById<T>(elOrId, kind);\n\n return elOrId;\n};\n","import {\n type NormalizedSpinConfig,\n normalizeIdleBob,\n normalizeMaxDpr,\n normalizeSpinBlur,\n normalizeSpinConfig,\n} from './normalize';\nimport type {\n ReelDealIdleBob,\n ReelDealOptions,\n ReelDealSpinBlur,\n ReelDealSpinConfig,\n ReelDealSpinState,\n} from './types';\nimport { resolveElement } from './utils/dom';\nimport { clamp01, clampInt, mod, nonNegative, rampHermite, reelProgress } from './utils/math';\n\nconst MAX_REELS = 5;\nconst SPIN_TICK_START = 0.72;\nconst SPIN_TICK_RANGE = 0.28;\nconst SPIN_TICK_MAX_PX = 7;\nconst VELOCITY_DECAY = 0.85;\nconst VELOCITY_GAIN = 1 - VELOCITY_DECAY;\nconst STOP_SPRING_OVERSHOOT_PX = 30;\nconst STOP_SPRING_SETTLE_MS = 500;\nconst STOP_SPRING_OSCILLATIONS = 1.25;\nconst STOP_SPRING_DAMPING = 4;\nconst STOP_SPRING_MAX_RATIO = 0.25;\n\ntype WebGLShadingConfig = {\n warpAngleDeg: number;\n edgePower: number;\n};\n\nconst DEFAULT_WEBGL_SHADING: WebGLShadingConfig = {\n warpAngleDeg: 360,\n edgePower: 0.5,\n};\n\nconst normalizeWebGLShading = (overrides?: Partial<WebGLShadingConfig>): WebGLShadingConfig => ({\n warpAngleDeg: nonNegative(overrides?.warpAngleDeg, DEFAULT_WEBGL_SHADING.warpAngleDeg),\n edgePower: nonNegative(overrides?.edgePower, DEFAULT_WEBGL_SHADING.edgePower),\n});\n\ntype SpinAnimation = {\n startTime: number;\n startOffsets: number[];\n deltas: number[];\n durationMs: number[];\n settleDurationMs: number[];\n targets: number[];\n};\n\nexport class ReelDeal {\n private readonly canvas: HTMLCanvasElement;\n private readonly container: HTMLElement;\n\n private readonly opts: ReelDealOptions;\n private readonly visibleSlots = 3;\n private readonly heightScale = 2 / 3;\n private readonly centerIndex = 1;\n private readonly reels: number;\n private readonly spinConfig: NormalizedSpinConfig;\n private readonly spinBlur: ReelDealSpinBlur;\n private readonly webglShading: WebGLShadingConfig;\n private readonly idleBob: ReelDealIdleBob | null;\n private readonly maxDpr: number;\n\n private spriteImg: HTMLImageElement | null = null;\n private spriteWidth = 0;\n private spriteHeight = 0;\n private frameHeight = 0;\n\n private width = 0;\n private height = 0;\n private dpr = 1;\n\n private offsets: number[];\n private velocities: number[];\n private lastVelT: number | null = null;\n private lastVelOffsets: number[];\n private idleStartTime: number | null = null;\n private idleBaseOffsets: number[];\n\n private gl: WebGLRenderingContext | WebGL2RenderingContext | null = null;\n private glProgram: WebGLProgram | null = null;\n private glBuffer: WebGLBuffer | null = null;\n private glTexture: WebGLTexture | null = null;\n private glAttribs: { pos: number; uv: number } | null = null;\n private webglInitError: string | null = null;\n private glUniforms: {\n texture: WebGLUniformLocation | null;\n reelX: WebGLUniformLocation | null;\n reelW: WebGLUniformLocation | null;\n offsetPx: WebGLUniformLocation | null;\n frameHeight: WebGLUniformLocation | null;\n spriteHeight: WebGLUniformLocation | null;\n visibleSlots: WebGLUniformLocation | null;\n heightScale: WebGLUniformLocation | null;\n edgePower: WebGLUniformLocation | null;\n warpAngle: WebGLUniformLocation | null;\n blurPx: WebGLUniformLocation | null;\n } | null = null;\n\n private anim: SpinAnimation | null = null;\n private rafId: number | null = null;\n private pendingSpinResolvers: Array<() => void> = [];\n\n private resizeObserver: ResizeObserver | null = null;\n private resizeQueued = false;\n\n private button: HTMLButtonElement | null = null;\n private buttonHandlerAttached = false;\n private spinning = false;\n private queuedSpins: ReelDealSpinState[] | null = null;\n\n constructor(options: ReelDealOptions) {\n this.opts = options;\n this.reels = clampInt(options.reels ?? 1, 1, MAX_REELS);\n this.spinConfig = normalizeSpinConfig(options.spinConfig);\n this.spinBlur = normalizeSpinBlur(options.spinBlur);\n this.webglShading = normalizeWebGLShading(\n (options as { webglShading?: Partial<WebGLShadingConfig> }).webglShading,\n );\n this.idleBob = normalizeIdleBob(options.idleBob);\n this.maxDpr = normalizeMaxDpr(options.maxDpr);\n\n this.canvas = resolveElement<HTMLCanvasElement>(options.canvas, 'Canvas');\n this.container = resolveElement<HTMLElement>(options.container, 'Container');\n\n this.offsets = new Array(this.reels).fill(0);\n this.velocities = new Array(this.reels).fill(0);\n this.lastVelOffsets = new Array(this.reels).fill(0);\n this.idleBaseOffsets = new Array(this.reels).fill(0);\n }\n\n async init(): Promise<void> {\n await this.loadSprite();\n\n this.tryInitWebGL();\n\n if (!this.gl) {\n throw new Error(\n `WebGL is not supported or failed to initialize. ${this.webglInitError ?? ''}`.trim(),\n );\n }\n\n this.syncContainerLayoutVars();\n this.ensureCanvasCoversContainer();\n this.setupResizeWatcher();\n this.resizeToContainer();\n\n this.setSegments(this.opts.initialSegments);\n\n this.button = this.resolveButton();\n this.queuedSpins = this.resolveSpinQueue();\n this.attachButtonHandler();\n\n this.render();\n this.startLoop();\n }\n\n destroy(): void {\n this.stop();\n\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = null;\n }\n\n if (this.button && this.buttonHandlerAttached) {\n this.button.removeEventListener('click', this.handleButtonClick);\n this.buttonHandlerAttached = false;\n }\n\n this.releaseWebGLResources();\n }\n\n private ensureCanvasCoversContainer(): void {\n if (this.canvas === this.container) return;\n if (!this.container.contains(this.canvas)) return;\n\n if (!this.canvas.style.width) this.canvas.style.width = '100%';\n if (!this.canvas.style.height) this.canvas.style.height = '100%';\n if (!this.canvas.style.display) this.canvas.style.display = 'block';\n }\n\n private syncContainerLayoutVars(): void {\n const target = this.canvas === this.container ? this.canvas : this.container;\n\n target.style.setProperty('--reels', String(this.reels));\n target.style.setProperty('--visible-slots', String(this.visibleSlots));\n target.style.aspectRatio = `${this.reels} / ${this.visibleSlots * this.heightScale}`;\n\n if (this.canvas !== this.container) {\n this.container.style.overflow = 'hidden';\n }\n }\n\n setSegments(initialSegments?: number[]): void {\n this.ensureReady();\n\n const segments = initialSegments ?? [];\n\n for (let r = 0; r < this.reels; r += 1) {\n const segIndex = segments[r] ?? 0;\n const idx = clampInt(segIndex, 0, this.opts.slotCount - 1);\n\n this.offsets[r] = idx * this.frameHeight - this.centerIndex * this.frameHeight;\n this.lastVelOffsets[r] = this.offsets[r];\n this.velocities[r] = 0;\n this.idleBaseOffsets[r] = this.offsets[r];\n }\n\n this.lastVelT = null;\n this.idleStartTime = null;\n this.anim = null;\n\n this.stop();\n this.render();\n this.startLoop();\n }\n\n setSegment(segmentIndex: number): void {\n this.setSegments(new Array(this.reels).fill(segmentIndex));\n }\n\n spinOnce(stopAtSegments: number[], config?: Partial<ReelDealSpinConfig>): Promise<void> {\n this.ensureReady();\n\n const { minSpins, staggerMs, speedPxS, durationMs } = this.getSpinConfig(config);\n const useSpeed = speedPxS > 0;\n const stopSpringOvershootPx = this.getStopSpringOvershootPx();\n const stopSpringSettleMs = Math.max(0, STOP_SPRING_SETTLE_MS);\n const stopSpringEnabled = stopSpringSettleMs > 0 && stopSpringOvershootPx > 0;\n\n const startOffsets = new Array(this.reels);\n const deltas = new Array(this.reels);\n const durations = new Array(this.reels);\n const settleDurationMs = new Array(this.reels);\n const targets = new Array(this.reels);\n\n for (let r = 0; r < this.reels; r += 1) {\n const idx = clampInt(stopAtSegments[r] ?? 0, 0, this.opts.slotCount - 1);\n const desiredTop = idx * this.frameHeight - this.centerIndex * this.frameHeight;\n const desired = mod(desiredTop, this.spriteHeight);\n const current = mod(this.offsets[r], this.spriteHeight);\n\n const alignDelta = mod(current - desired, this.spriteHeight);\n\n let delta = -(minSpins * this.spriteHeight + alignDelta);\n\n if (useSpeed && durationMs > 0) {\n const desiredDistance = (speedPxS * durationMs) / 1000;\n const loops = Math.max(\n minSpins,\n Math.ceil(Math.max(0, desiredDistance - alignDelta) / this.spriteHeight),\n );\n\n delta = -(alignDelta + loops * this.spriteHeight);\n }\n\n if (staggerMs > 0 && useSpeed) {\n const extraDelta = (speedPxS * staggerMs * r) / 1000;\n const extraLoops = Math.ceil(extraDelta / this.spriteHeight);\n\n delta -= extraLoops * this.spriteHeight;\n }\n\n startOffsets[r] = this.offsets[r];\n targets[r] = this.offsets[r] + delta;\n\n let finalDelta = delta;\n let settleDuration = 0;\n\n if (stopSpringEnabled && delta !== 0 && stopSpringOvershootPx > 0) {\n const direction = Math.sign(delta);\n const overshoot = direction * stopSpringOvershootPx;\n\n finalDelta += overshoot;\n settleDuration = stopSpringSettleMs;\n }\n\n deltas[r] = finalDelta;\n settleDurationMs[r] = settleDuration;\n\n if (useSpeed) {\n durations[r] = Math.max(0, Math.round((Math.abs(finalDelta) / speedPxS) * 1000));\n } else {\n durations[r] = Math.max(0, durationMs + staggerMs * r);\n }\n }\n\n const totalDelta = deltas.reduce((acc, v) => acc + Math.abs(v), 0);\n const allSpinZero = durations.every((entry) => entry <= 0);\n const allSettleZero = settleDurationMs.every((entry) => entry <= 0);\n\n if (totalDelta === 0 || (allSpinZero && allSettleZero)) {\n for (let r = 0; r < this.reels; r += 1) {\n this.offsets[r] = targets[r];\n }\n\n this.anim = null;\n this.resolvePendingSpins();\n this.render();\n\n return Promise.resolve();\n }\n\n const startTime = performance.now();\n\n this.anim = {\n startTime,\n startOffsets,\n deltas,\n durationMs: durations,\n settleDurationMs,\n targets,\n };\n this.idleStartTime = null;\n this.startLoop();\n\n return new Promise((resolve) => {\n this.pendingSpinResolvers.push(resolve);\n });\n }\n\n private getSpinConfig(overrides?: Partial<ReelDealSpinConfig>): NormalizedSpinConfig {\n if (!overrides) return this.spinConfig;\n\n return normalizeSpinConfig({ ...this.spinConfig, ...overrides });\n }\n\n private getStopSpringOvershootPx(): number {\n const cap = this.frameHeight * STOP_SPRING_MAX_RATIO;\n const overshoot = Math.max(0, STOP_SPRING_OVERSHOOT_PX);\n\n return Math.min(overshoot, cap);\n }\n\n private getStopSpringOffset(overshoot: number, t: number): number {\n if (overshoot === 0) return 0;\n\n const decay = Math.exp(-STOP_SPRING_DAMPING * t);\n const angle = Math.PI * 2 * STOP_SPRING_OSCILLATIONS * t;\n\n return overshoot * decay * Math.cos(angle);\n }\n\n async spinQueue(spins: ReelDealSpinState[]): Promise<void> {\n for (let i = 0; i < spins.length; i += 1) {\n const spin = spins[i];\n\n await this.spinOnce(spin.stopAtSegments);\n\n spin.callback?.(i, spin.stopAtSegments);\n }\n }\n\n stop(): void {\n if (this.rafId !== null) cancelAnimationFrame(this.rafId);\n\n this.rafId = null;\n this.anim = null;\n this.resolvePendingSpins();\n this.lastVelT = null;\n\n for (let r = 0; r < this.reels; r += 1) {\n this.velocities[r] = 0;\n }\n }\n\n private resolvePendingSpins(): void {\n if (this.pendingSpinResolvers.length === 0) return;\n\n const resolvers = this.pendingSpinResolvers;\n\n this.pendingSpinResolvers = [];\n\n for (const resolve of resolvers) {\n resolve();\n }\n }\n\n requestResize(): void {\n this.queueResize();\n }\n\n private ensureReady(): void {\n if (\n !this.spriteImg ||\n this.spriteWidth <= 0 ||\n this.spriteHeight <= 0 ||\n this.frameHeight <= 0\n ) {\n throw new Error('Sprite is not ready. Call init() and await it first.');\n }\n }\n\n private attachButtonHandler(): void {\n if (!this.button || this.buttonHandlerAttached) return;\n\n this.button.addEventListener('click', this.handleButtonClick, { passive: true });\n this.buttonHandlerAttached = true;\n }\n\n private handleButtonClick = async (): Promise<void> => {\n if (this.spinning) return;\n\n const spin = this.consumeNextSpin() ?? {\n stopAtSegments: this.buildRandomSegments(),\n };\n\n this.spinning = true;\n this.setButtonDisabled(true);\n\n try {\n await this.spinOnce(spin.stopAtSegments);\n } finally {\n if (this.queuedSpins && this.queuedSpins.length === 0) {\n this.setButtonDisabled(true);\n } else {\n this.setButtonDisabled(false);\n }\n\n this.spinning = false;\n }\n };\n\n private resolveButton(): HTMLButtonElement | null {\n if (!this.opts.button) return null;\n\n return resolveElement<HTMLButtonElement>(this.opts.button, 'Button');\n }\n\n private resolveSpinQueue(): ReelDealSpinState[] | null {\n return this.opts.queuedSpinStates ?? null;\n }\n\n private consumeNextSpin(): ReelDealSpinState | null {\n if (!this.queuedSpins) {\n this.queuedSpins = this.resolveSpinQueue();\n }\n\n if (!this.queuedSpins || this.queuedSpins.length === 0) return null;\n\n return this.queuedSpins.shift() ?? null;\n }\n\n private buildRandomSegments(): number[] {\n const max = Math.max(1, Math.floor(this.opts.slotCount));\n const segments: number[] = [];\n\n for (let r = 0; r < this.reels; r += 1) {\n segments.push(Math.floor(Math.random() * max));\n }\n\n return segments;\n }\n\n private setButtonDisabled(disabled: boolean): void {\n if (!this.button) return;\n\n this.button.disabled = disabled;\n\n if (disabled) this.button.setAttribute('aria-busy', 'true');\n else this.button.removeAttribute('aria-busy');\n }\n\n private shouldAnimate(): boolean {\n return this.anim !== null || this.idleBob !== null;\n }\n\n private startLoop(): void {\n if (this.rafId !== null) return;\n if (!this.shouldAnimate()) return;\n\n this.rafId = requestAnimationFrame(this.loop);\n }\n\n private loop = (now: number): void => {\n if (this.anim) {\n const { startTime, durationMs, startOffsets, deltas, settleDurationMs, targets } = this.anim;\n const elapsed = now - startTime;\n\n let allDone = true;\n\n for (let r = 0; r < this.reels; r += 1) {\n const duration = durationMs[r] ?? 0;\n const settleDuration = settleDurationMs[r] ?? 0;\n const targetOffset = targets[r] ?? startOffsets[r] + deltas[r];\n\n if (duration > 0 && elapsed < duration) {\n const t = Math.min(1, elapsed / duration);\n const eased = reelProgress(t);\n\n if (t < 1) allDone = false;\n\n const baseOffset = startOffsets[r] + deltas[r] * eased;\n\n let tick = 0;\n\n if (t > SPIN_TICK_START && this.frameHeight > 0) {\n const u = clamp01((t - SPIN_TICK_START) / SPIN_TICK_RANGE);\n const env = (1 - u) ** 2;\n const phase = (baseOffset / this.frameHeight) * Math.PI * 2;\n const amp = Math.min(SPIN_TICK_MAX_PX, this.frameHeight * 0.06);\n\n tick = Math.sin(phase) * env * amp;\n }\n\n this.offsets[r] = baseOffset + tick;\n } else if (settleDuration > 0) {\n const t =\n duration <= 0 ? elapsed / settleDuration : (elapsed - duration) / settleDuration;\n const u = clamp01(t);\n const overshoot = startOffsets[r] + deltas[r] - targetOffset;\n const spring = this.getStopSpringOffset(overshoot, u);\n\n if (u < 1) allDone = false;\n\n this.offsets[r] = targetOffset + spring;\n } else {\n this.offsets[r] = targetOffset;\n }\n }\n\n this.updateVelocity(now);\n this.render();\n\n if (allDone) {\n for (let r = 0; r < this.reels; r += 1) {\n const targetOffset =\n this.anim.targets[r] ?? this.anim.startOffsets[r] + this.anim.deltas[r];\n\n this.offsets[r] = targetOffset;\n this.idleBaseOffsets[r] = this.offsets[r];\n }\n\n this.updateVelocity(now);\n\n for (let r = 0; r < this.reels; r += 1) {\n this.velocities[r] = 0;\n }\n\n this.anim = null;\n this.idleStartTime = null;\n\n this.render();\n this.resolvePendingSpins();\n }\n } else if (this.idleBob) {\n this.updateIdle(now);\n this.render();\n }\n\n if (!this.shouldAnimate()) {\n this.rafId = null;\n\n return;\n }\n\n if (this.rafId === null) return;\n\n this.rafId = requestAnimationFrame(this.loop);\n };\n\n private render(): void {\n if (!this.spriteImg) return;\n if (this.width <= 0 || this.height <= 0) return;\n if (!this.gl) return;\n\n this.renderWebGL();\n }\n\n private getSpinBlurPxForReel(reel: number): number {\n if (!this.anim) return 0;\n\n const speed = Math.abs(this.velocities[reel] ?? 0);\n const speedAtMax = Math.max(1, this.spinBlur.speedAtMax);\n const t = clamp01(speed / speedAtMax);\n\n return t * Math.max(0, this.spinBlur.maxPx);\n }\n\n private renderWebGL(): void {\n if (\n !this.gl ||\n !this.glProgram ||\n !this.glAttribs ||\n !this.glUniforms ||\n !this.glTexture ||\n !this.glBuffer\n ) {\n return;\n }\n\n if (!this.spriteImg) return;\n\n const gl = this.gl;\n\n gl.useProgram(this.glProgram);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.glBuffer);\n\n gl.enableVertexAttribArray(this.glAttribs.pos);\n gl.vertexAttribPointer(this.glAttribs.pos, 2, gl.FLOAT, false, 16, 0);\n gl.enableVertexAttribArray(this.glAttribs.uv);\n gl.vertexAttribPointer(this.glAttribs.uv, 2, gl.FLOAT, false, 16, 8);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.glTexture);\n gl.uniform1i(this.glUniforms.texture, 0);\n\n gl.uniform1f(this.glUniforms.frameHeight, this.frameHeight);\n gl.uniform1f(this.glUniforms.spriteHeight, this.spriteHeight);\n gl.uniform1f(this.glUniforms.visibleSlots, this.visibleSlots);\n gl.uniform1f(this.glUniforms.heightScale, this.heightScale);\n gl.uniform1f(this.glUniforms.edgePower, Math.max(0.5, this.webglShading.edgePower));\n\n const shading = this.webglShading;\n const warpAngle = (Math.max(0, shading.warpAngleDeg) * Math.PI) / 180;\n\n gl.uniform1f(this.glUniforms.warpAngle, warpAngle);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n const reelW = 1 / this.reels;\n\n for (let r = 0; r < this.reels; r += 1) {\n gl.uniform1f(this.glUniforms.reelX, r * reelW);\n gl.uniform1f(this.glUniforms.reelW, reelW);\n\n const offsetPx = mod(this.offsets[r], this.spriteHeight);\n\n gl.uniform1f(this.glUniforms.offsetPx, offsetPx);\n gl.uniform1f(this.glUniforms.blurPx, this.getSpinBlurPxForReel(r));\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n }\n\n private updateVelocity(now: number): void {\n if (this.lastVelT === null) {\n this.lastVelT = now;\n\n for (let r = 0; r < this.reels; r += 1) {\n this.lastVelOffsets[r] = this.offsets[r];\n this.velocities[r] = 0;\n }\n\n return;\n }\n\n const dt = (now - this.lastVelT) / 1000;\n\n if (dt <= 0) return;\n\n for (let r = 0; r < this.reels; r += 1) {\n const v = (this.offsets[r] - this.lastVelOffsets[r]) / dt;\n\n this.velocities[r] = this.velocities[r] * VELOCITY_DECAY + v * VELOCITY_GAIN;\n this.lastVelOffsets[r] = this.offsets[r];\n }\n\n this.lastVelT = now;\n }\n\n private updateIdle(now: number): void {\n if (!this.idleBob) return;\n if (this.idleStartTime === null) this.idleStartTime = now;\n\n const { amplitudePx, speedHz, phaseOffsetRad, rampMs } = this.idleBob;\n const elapsedMs = now - this.idleStartTime;\n\n const tSec = elapsedMs / 1000;\n const rampT = Math.max(0, Math.min(1, elapsedMs / Math.max(1, rampMs)));\n const ramp = rampHermite(rampT);\n const omega = speedHz * Math.PI * 2;\n\n for (let r = 0; r < this.reels; r += 1) {\n const phase = tSec * omega + r * phaseOffsetRad;\n\n this.offsets[r] = this.idleBaseOffsets[r] + Math.sin(phase) * (amplitudePx * ramp);\n this.velocities[r] = 0;\n }\n }\n\n private async loadSprite(): Promise<void> {\n const { sprite } = this.opts;\n const img = typeof sprite === 'string' ? new Image() : sprite;\n\n if (typeof sprite === 'string') {\n img.src = sprite;\n }\n\n if (!img.complete || img.naturalWidth <= 0) {\n await new Promise<void>((resolve, reject) => {\n const onLoad = () => resolve();\n const onError = () => reject(new Error('Failed to load sprite image'));\n\n img.addEventListener('load', onLoad, { once: true });\n img.addEventListener('error', onError, { once: true });\n });\n }\n\n const slotCount = Math.max(1, Math.floor(this.opts.slotCount));\n const spriteWidth = img.naturalWidth;\n const frameHeight = spriteWidth;\n const expectedSpriteHeight = frameHeight * slotCount;\n\n const naturalHeight = img.naturalHeight;\n const diff = Math.abs(naturalHeight - expectedSpriteHeight);\n\n if (diff > 2) {\n throw new Error(\n `Sprite size mismatch. Expected height ~${Math.round(expectedSpriteHeight)}px (slotCount=${slotCount}, slot aspect 1/1), got ${naturalHeight}px.`,\n );\n }\n\n this.spriteImg = img;\n this.spriteWidth = spriteWidth;\n this.frameHeight = Math.max(1, Math.round(frameHeight));\n this.spriteHeight = Math.max(1, Math.round(this.frameHeight * slotCount));\n }\n\n private setupResizeWatcher(): void {\n if (typeof ResizeObserver === 'undefined') return;\n if (this.resizeObserver) return;\n\n this.resizeObserver = new ResizeObserver(() => this.queueResize());\n this.resizeObserver.observe(this.container);\n }\n\n private queueResize = (): void => {\n if (this.resizeQueued) return;\n\n this.resizeQueued = true;\n\n requestAnimationFrame(() => {\n this.resizeQueued = false;\n this.resizeToContainer();\n });\n };\n\n private resizeToContainer(): void {\n const cssWidth = this.container.clientWidth;\n const cssHeight = this.container.clientHeight;\n const slotsViewport = this.visibleSlots * this.heightScale;\n\n if (cssWidth <= 0 || cssHeight <= 0) {\n this.applyResize(1, 1);\n\n return;\n }\n\n const slots = slotsViewport;\n const reels = this.reels;\n const fitByWidthH = (cssWidth / reels) * slots;\n\n let w = cssWidth;\n let h = fitByWidthH;\n\n if (h > cssHeight) {\n h = cssHeight;\n w = (cssHeight / slots) * reels;\n }\n\n const viewportWidth = Math.max(1, Math.floor(w));\n const viewportHeight = Math.max(1, Math.floor(h));\n\n this.applyResize(viewportWidth, viewportHeight);\n this.render();\n }\n\n private applyViewportCrop(): void {\n this.canvas.style.width = '100%';\n this.canvas.style.height = '100%';\n this.canvas.style.transform = '';\n }\n\n private applyResize(width: number, height: number): void {\n const normalizedWidth = Math.max(1, Math.floor(width));\n const normalizedHeight = Math.max(1, Math.floor(height));\n\n const dpr = this.getTargetDpr();\n\n this.width = normalizedWidth;\n this.height = normalizedHeight;\n this.dpr = dpr;\n\n this.canvas.width = Math.floor(this.width * this.dpr);\n this.canvas.height = Math.floor(this.height * this.dpr);\n\n if (this.gl) {\n this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);\n }\n\n this.applyViewportCrop();\n }\n\n private getTargetDpr(): number {\n const rawDpr = window.devicePixelRatio || 1;\n const base = Math.max(1, rawDpr);\n\n return Math.min(this.maxDpr, base);\n }\n\n private tryInitWebGL(): void {\n if (!this.spriteImg) return;\n\n this.releaseWebGLResources();\n this.webglInitError = null;\n\n const gl =\n (this.canvas.getContext('webgl2', {\n antialias: true,\n alpha: true,\n }) as WebGL2RenderingContext | null) ??\n (this.canvas.getContext('webgl', {\n antialias: true,\n alpha: true,\n }) as WebGLRenderingContext | null) ??\n (this.canvas.getContext('experimental-webgl', {\n antialias: true,\n alpha: true,\n }) as WebGLRenderingContext | null);\n\n if (!gl) {\n const fallback = document.createElement('canvas');\n const canWebGL = typeof WebGLRenderingContext !== 'undefined';\n const fallbackGl =\n (fallback.getContext('webgl2') as WebGL2RenderingContext | null) ??\n (fallback.getContext('webgl') as WebGLRenderingContext | null);\n\n this.webglInitError = `context is null (browser WebGL=${canWebGL ? 'yes' : 'no'}, fallback canvas=${fallbackGl ? 'yes' : 'no'})`;\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n return;\n }\n\n const vsSource = `\n attribute vec2 a_pos;\n attribute vec2 a_uv;\n varying vec2 v_uv;\n void main() {\n v_uv = a_uv;\n gl_Position = vec4(a_pos, 0.0, 1.0);\n }\n `;\n\n const fsSource = `\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n varying vec2 v_uv;\n uniform sampler2D u_tex;\n uniform float u_reelX;\n uniform float u_reelW;\n uniform float u_offsetPx;\n uniform float u_frameHeight;\n uniform float u_spriteHeight;\n uniform float u_visibleSlots;\n uniform float u_heightScale;\n uniform float u_edgePower;\n uniform float u_warpAngle;\n uniform float u_blurPx;\n\n const float HALF_PI = 1.5707963;\n\n float saturate(float x) { return clamp(x, 0.0, 1.0); }\n\n void main() {\n if (v_uv.x < u_reelX || v_uv.x > (u_reelX + u_reelW)) {\n discard;\n }\n\n float localX = (v_uv.x - u_reelX) / u_reelW;\n float localY = v_uv.y;\n float hs = max(0.0001, u_heightScale);\n float fullY = localY * hs + (1.0 - hs) * 0.5;\n\n float warp = max(0.0, u_warpAngle);\n float warpStrength = saturate(warp / 1.2);\n float center = 0.5;\n float centerBand = 1.0 / 3.0;\n float halfBand = centerBand * 0.5;\n float dist = abs(fullY - center);\n float edgeWeight = smoothstep(halfBand, 0.5, dist);\n float edgePow = max(0.5, u_edgePower);\n edgeWeight = pow(edgeWeight, edgePow) * warpStrength;\n float yCentered = (fullY - 0.5) * 2.0;\n float sinY = 0.5 + 0.5 * sin(yCentered * HALF_PI);\n float warpedY = mix(fullY, sinY, edgeWeight);\n\n float yPx = u_offsetPx + warpedY * (u_frameHeight * u_visibleSlots);\n float v = fract((u_offsetPx / u_spriteHeight) +\n warpedY * ((u_frameHeight * u_visibleSlots) / u_spriteHeight));\n\n vec4 base = texture2D(u_tex, vec2(localX, v));\n\n if (u_blurPx > 0.0) {\n float maxStepPx = 1.0 + 3.0 * saturate(u_blurPx / 4.0);\n float blurPx = min(u_blurPx, maxStepPx);\n float blurStep = blurPx / max(1.0, u_spriteHeight);\n vec4 sum = base * 0.227027;\n sum += texture2D(u_tex, vec2(localX, fract(v + blurStep))) * 0.1945946;\n sum += texture2D(u_tex, vec2(localX, fract(v - blurStep))) * 0.1945946;\n sum += texture2D(u_tex, vec2(localX, fract(v + 2.0 * blurStep))) * 0.1216216;\n sum += texture2D(u_tex, vec2(localX, fract(v - 2.0 * blurStep))) * 0.1216216;\n sum += texture2D(u_tex, vec2(localX, fract(v + 3.0 * blurStep))) * 0.054054;\n sum += texture2D(u_tex, vec2(localX, fract(v - 3.0 * blurStep))) * 0.054054;\n sum += texture2D(u_tex, vec2(localX, fract(v + 4.0 * blurStep))) * 0.016216;\n sum += texture2D(u_tex, vec2(localX, fract(v - 4.0 * blurStep))) * 0.016216;\n base = sum;\n }\n\n gl_FragColor = base;\n }\n `;\n\n const program = this.createWebGLProgram(gl, vsSource, fsSource);\n\n if (!program) {\n this.webglInitError = 'shader program failed to compile/link (see console)';\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n return;\n }\n\n const buffer = gl.createBuffer();\n\n if (!buffer) {\n this.webglInitError = 'failed to create vertex buffer';\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n gl.deleteProgram(program);\n\n return;\n }\n\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n\n const quad = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, 1, 1, 1, 0]);\n\n gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);\n\n const texture = gl.createTexture();\n\n if (!texture) {\n this.webglInitError = 'failed to create texture';\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n gl.deleteBuffer(buffer);\n gl.deleteProgram(program);\n\n return;\n }\n\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.spriteImg);\n\n const isPowerOfTwo = (n: number): boolean => n > 0 && (n & (n - 1)) === 0;\n const pot = isPowerOfTwo(this.spriteWidth) && isPowerOfTwo(this.spriteHeight);\n\n if (pot) {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);\n gl.generateMipmap(gl.TEXTURE_2D);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n } else {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n }\n\n gl.useProgram(program);\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\n this.gl = gl;\n this.glProgram = program;\n this.glBuffer = buffer;\n this.glTexture = texture;\n this.glAttribs = {\n pos: gl.getAttribLocation(program, 'a_pos'),\n uv: gl.getAttribLocation(program, 'a_uv'),\n };\n this.glUniforms = {\n texture: gl.getUniformLocation(program, 'u_tex'),\n reelX: gl.getUniformLocation(program, 'u_reelX'),\n reelW: gl.getUniformLocation(program, 'u_reelW'),\n offsetPx: gl.getUniformLocation(program, 'u_offsetPx'),\n frameHeight: gl.getUniformLocation(program, 'u_frameHeight'),\n spriteHeight: gl.getUniformLocation(program, 'u_spriteHeight'),\n visibleSlots: gl.getUniformLocation(program, 'u_visibleSlots'),\n heightScale: gl.getUniformLocation(program, 'u_heightScale'),\n edgePower: gl.getUniformLocation(program, 'u_edgePower'),\n warpAngle: gl.getUniformLocation(program, 'u_warpAngle'),\n blurPx: gl.getUniformLocation(program, 'u_blurPx'),\n };\n }\n\n private createWebGLProgram(\n gl: WebGLRenderingContext | WebGL2RenderingContext,\n vsSource: string,\n fsSource: string,\n ): WebGLProgram | null {\n const vs = this.createWebGLShader(gl, gl.VERTEX_SHADER, vsSource);\n const fs = this.createWebGLShader(gl, gl.FRAGMENT_SHADER, fsSource);\n\n if (!vs || !fs) {\n if (vs) gl.deleteShader(vs);\n if (fs) gl.deleteShader(fs);\n\n return null;\n }\n\n const program = gl.createProgram();\n\n if (!program) return null;\n\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const info = gl.getProgramInfoLog(program) ?? 'unknown link error';\n\n this.webglInitError = `program link failed: ${info}`;\n\n console.warn('ReelDeal WebGL: program link failed', info);\n\n gl.deleteProgram(program);\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n\n return null;\n }\n\n gl.detachShader(program, vs);\n gl.detachShader(program, fs);\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n\n return program;\n }\n\n private createWebGLShader(\n gl: WebGLRenderingContext | WebGL2RenderingContext,\n type: number,\n source: string,\n ): WebGLShader | null {\n const shader = gl.createShader(type);\n\n if (!shader) return null;\n\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const info = gl.getShaderInfoLog(shader) ?? 'unknown compile error';\n\n this.webglInitError = `shader compile failed: ${info}`;\n\n console.warn('ReelDeal WebGL: shader compile failed', info);\n\n gl.deleteShader(shader);\n\n return null;\n }\n\n return shader;\n }\n\n private releaseWebGLResources(): void {\n if (!this.gl) return;\n if (this.glTexture) this.gl.deleteTexture(this.glTexture);\n if (this.glBuffer) this.gl.deleteBuffer(this.glBuffer);\n if (this.glProgram) this.gl.deleteProgram(this.glProgram);\n\n this.glTexture = null;\n this.glBuffer = null;\n this.glProgram = null;\n this.glAttribs = null;\n this.glUniforms = null;\n this.gl = null;\n }\n}\n"],"names":["defaultSpinConfig","defaultSpinBlur","defaultIdleBob","SPIN_ACCEL_END","SPIN_DECEL_START","clampInt","value","min","max","mod","n","m","clamp01","t","rampHermite","u","x","brakeHermite","reelProgress","tt","finiteOr","fallback","nonNegative","nonNegativeInt","DEFAULT_MAX_DPR","normalizeSpinConfig","overrides","base","normalizeSpinBlur","normalizeIdleBob","normalizeMaxDpr","resolveElementById","id","kind","el","resolveElement","elOrId","MAX_REELS","SPIN_TICK_START","SPIN_TICK_RANGE","SPIN_TICK_MAX_PX","VELOCITY_DECAY","VELOCITY_GAIN","STOP_SPRING_OVERSHOOT_PX","STOP_SPRING_SETTLE_MS","STOP_SPRING_OSCILLATIONS","STOP_SPRING_MAX_RATIO","DEFAULT_WEBGL_SHADING","normalizeWebGLShading","ReelDeal","options","target","initialSegments","segments","r","segIndex","idx","segmentIndex","stopAtSegments","config","minSpins","staggerMs","speedPxS","durationMs","useSpeed","stopSpringOvershootPx","stopSpringSettleMs","stopSpringEnabled","startOffsets","deltas","durations","settleDurationMs","targets","desiredTop","desired","current","alignDelta","delta","desiredDistance","loops","extraDelta","extraLoops","finalDelta","settleDuration","overshoot","totalDelta","acc","v","allSpinZero","entry","allSettleZero","startTime","resolve","cap","decay","angle","spins","i","spin","resolvers","disabled","now","elapsed","allDone","duration","targetOffset","eased","baseOffset","tick","env","phase","amp","spring","reel","speed","speedAtMax","gl","shading","warpAngle","reelW","offsetPx","dt","amplitudePx","speedHz","phaseOffsetRad","rampMs","elapsedMs","tSec","rampT","ramp","omega","sprite","img","reject","onLoad","onError","slotCount","spriteWidth","frameHeight","expectedSpriteHeight","naturalHeight","cssWidth","cssHeight","slotsViewport","slots","reels","fitByWidthH","w","h","viewportWidth","viewportHeight","width","height","normalizedWidth","normalizedHeight","dpr","rawDpr","canWebGL","fallbackGl","program","buffer","quad","texture","isPowerOfTwo","vsSource","fsSource","vs","fs","info","type","source","shader"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/defaults.ts","../src/utils/math.ts","../src/normalize.ts","../src/utils/dom.ts","../src/ReelDeal.ts"],"sourcesContent":["import type { ReelDealIdleBob, ReelDealSpinBlur, ReelDealSpinConfig } from './types';\n\nexport const defaultSpinConfig: ReelDealSpinConfig = {\n minSpins: 2,\n durationMs: 4000,\n speedPxS: 4000,\n staggerMs: 300,\n};\n\nexport const defaultSpinBlur: ReelDealSpinBlur = {\n maxPx: 4,\n speedAtMax: 2200,\n};\n\nexport const defaultIdleBob: ReelDealIdleBob = {\n amplitudePx: 9,\n speedHz: 0.25,\n phaseOffsetRad: 0.9,\n rampMs: 800,\n};\n","const SPIN_ACCEL_END = 0.1;\nconst SPIN_DECEL_START = 0.6;\n\nexport const clampInt = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, Math.floor(value)));\n\nexport const mod = (n: number, m: number): number => ((n % m) + m) % m;\n\nexport const clamp01 = (t: number): number => Math.max(0, Math.min(1, t));\n\nexport const rampHermite = (u: number): number => {\n const x = clamp01(u);\n\n return x ** 2 * (2 - x);\n};\n\nexport const brakeHermite = (u: number): number => {\n const x = clamp01(u);\n\n return x + x ** 2 - x ** 3;\n};\n\nexport const reelProgress = (t: number): number => {\n const tt = clamp01(t);\n\n if (tt <= SPIN_ACCEL_END) {\n const u = tt / SPIN_ACCEL_END;\n\n return rampHermite(u) * SPIN_ACCEL_END;\n }\n\n if (tt < SPIN_DECEL_START) {\n return tt;\n }\n\n const u = (tt - SPIN_DECEL_START) / (1 - SPIN_DECEL_START);\n\n return SPIN_DECEL_START + brakeHermite(u) * (1 - SPIN_DECEL_START);\n};\n\nexport const finiteOr = (value: number | undefined, fallback: number): number =>\n typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n\nexport const nonNegative = (value: number | undefined, fallback: number): number =>\n Math.max(0, finiteOr(value, fallback));\n\nexport const nonNegativeInt = (value: number | undefined, fallback: number): number =>\n Math.max(0, Math.floor(finiteOr(value, fallback)));\n","import { defaultIdleBob, defaultSpinBlur, defaultSpinConfig } from './defaults';\nimport type { ReelDealIdleBob, ReelDealSpinBlur, ReelDealSpinConfig } from './types';\nimport { finiteOr, nonNegative, nonNegativeInt } from './utils/math';\n\nconst DEFAULT_MAX_DPR = 2;\n\ntype NormalizedSpinConfig = Required<ReelDealSpinConfig>;\n\nexport const normalizeSpinConfig = (\n overrides?: Partial<ReelDealSpinConfig>,\n): NormalizedSpinConfig => {\n const base = { ...defaultSpinConfig, ...(overrides ?? {}) };\n\n return {\n minSpins: nonNegativeInt(base.minSpins, defaultSpinConfig.minSpins),\n durationMs: nonNegativeInt(base.durationMs, defaultSpinConfig.durationMs ?? 0),\n speedPxS: nonNegativeInt(base.speedPxS, defaultSpinConfig.speedPxS ?? 0),\n staggerMs: nonNegativeInt(base.staggerMs, defaultSpinConfig.staggerMs ?? 0),\n };\n};\n\nexport const normalizeSpinBlur = (overrides?: Partial<ReelDealSpinBlur>): ReelDealSpinBlur => {\n const base = { ...defaultSpinBlur, ...(overrides ?? {}) };\n\n return {\n maxPx: nonNegative(base.maxPx, defaultSpinBlur.maxPx),\n speedAtMax: nonNegative(base.speedAtMax, defaultSpinBlur.speedAtMax),\n };\n};\n\nexport const normalizeIdleBob = (\n overrides?: Partial<ReelDealIdleBob> | false,\n): ReelDealIdleBob | null => {\n if (overrides === false) return null;\n\n const base = { ...defaultIdleBob, ...(overrides ?? {}) };\n\n return {\n amplitudePx: nonNegative(base.amplitudePx, defaultIdleBob.amplitudePx),\n speedHz: nonNegative(base.speedHz, defaultIdleBob.speedHz),\n phaseOffsetRad: finiteOr(base.phaseOffsetRad, defaultIdleBob.phaseOffsetRad),\n rampMs: nonNegativeInt(base.rampMs, defaultIdleBob.rampMs),\n };\n};\n\nexport const normalizeMaxDpr = (value?: number): number =>\n Math.max(1, finiteOr(value, DEFAULT_MAX_DPR));\n","const resolveElementById = <T extends HTMLElement>(id: string, kind: string): T => {\n const el = document.getElementById(id);\n\n if (!el) {\n throw new Error(`${kind} not found: ${id}`);\n }\n\n return el as T;\n};\n\nexport const resolveElement = <T extends HTMLElement>(elOrId: T | string, kind: string): T => {\n if (typeof elOrId === 'string') return resolveElementById<T>(elOrId, kind);\n\n return elOrId;\n};\n","import {\n normalizeIdleBob,\n normalizeMaxDpr,\n normalizeSpinBlur,\n normalizeSpinConfig,\n} from './normalize';\nimport type {\n ReelDealIdleBob,\n ReelDealOptions,\n ReelDealSpinBlur,\n ReelDealSpinConfig,\n ReelDealSpinState,\n ReelDealWebGLShading,\n} from './types';\nimport { resolveElement } from './utils/dom';\nimport { clamp01, clampInt, mod, nonNegative, rampHermite, reelProgress } from './utils/math';\n\nconst MAX_REELS = 5;\nconst SPIN_TICK_START = 0.72;\nconst SPIN_TICK_RANGE = 0.28;\nconst SPIN_TICK_MAX_PX = 7;\nconst VELOCITY_DECAY = 0.85;\nconst VELOCITY_GAIN = 1 - VELOCITY_DECAY;\nconst STOP_SPRING_OVERSHOOT_PX = 55;\nconst STOP_SPRING_SETTLE_MS = 300;\nconst STOP_SPRING_OSCILLATIONS = 1.25;\nconst STOP_SPRING_DAMPING = 5;\nconst STOP_SPRING_MAX_RATIO = 0.35;\n\ntype WebGLShadingConfig = ReelDealWebGLShading;\n\nconst DEFAULT_WEBGL_SHADING: WebGLShadingConfig = {\n warpAngleDeg: 60,\n edgePower: 4.8,\n};\n\nconst normalizeWebGLShading = (overrides?: Partial<WebGLShadingConfig>): WebGLShadingConfig => ({\n warpAngleDeg: nonNegative(overrides?.warpAngleDeg, DEFAULT_WEBGL_SHADING.warpAngleDeg),\n edgePower: nonNegative(overrides?.edgePower, DEFAULT_WEBGL_SHADING.edgePower),\n});\n\ntype SpinAnimation = {\n startTime: number;\n startOffsets: number[];\n deltas: number[];\n durationMs: number[];\n settleDurationMs: number[];\n targets: number[];\n};\n\nexport class ReelDeal {\n private readonly canvas: HTMLCanvasElement;\n private readonly container: HTMLElement;\n\n private readonly opts: ReelDealOptions;\n private readonly visibleSlots = 3;\n private readonly heightScale = 2 / 3;\n private readonly centerIndex = 1;\n private readonly reels: number;\n private readonly spinConfig: ReturnType<typeof normalizeSpinConfig>;\n private readonly spinBlur: ReelDealSpinBlur;\n private readonly webglShading: WebGLShadingConfig;\n private readonly idleBob: ReelDealIdleBob | null;\n private readonly maxDpr: number;\n\n private spriteImg: HTMLImageElement | null = null;\n private spriteWidth = 0;\n private spriteHeight = 0;\n private frameHeight = 0;\n\n private width = 0;\n private height = 0;\n private dpr = 1;\n\n private readonly offsets: number[];\n private readonly velocities: number[];\n private lastVelT: number | null = null;\n private readonly lastVelOffsets: number[];\n private idleStartTime: number | null = null;\n private readonly idleBaseOffsets: number[];\n\n private gl: WebGLRenderingContext | WebGL2RenderingContext | null = null;\n private glProgram: WebGLProgram | null = null;\n private glBuffer: WebGLBuffer | null = null;\n private glTexture: WebGLTexture | null = null;\n private glAttribs: { pos: number; uv: number } | null = null;\n private webglInitError: string | null = null;\n private glUniforms: {\n texture: WebGLUniformLocation | null;\n reelX: WebGLUniformLocation | null;\n reelW: WebGLUniformLocation | null;\n offsetPx: WebGLUniformLocation | null;\n frameHeight: WebGLUniformLocation | null;\n spriteHeight: WebGLUniformLocation | null;\n visibleSlots: WebGLUniformLocation | null;\n heightScale: WebGLUniformLocation | null;\n edgePower: WebGLUniformLocation | null;\n warpAngle: WebGLUniformLocation | null;\n blurPx: WebGLUniformLocation | null;\n } | null = null;\n\n private anim: SpinAnimation | null = null;\n private rafId: number | null = null;\n private pendingSpinResolvers: Array<() => void> = [];\n\n private resizeObserver: ResizeObserver | null = null;\n private resizeQueued = false;\n\n private button: HTMLButtonElement | null = null;\n private buttonHandlerAttached = false;\n private spinning = false;\n private queuedSpins: ReelDealSpinState[] | null = null;\n\n constructor(options: ReelDealOptions) {\n this.opts = options;\n this.reels = clampInt(options.reels ?? 1, 1, MAX_REELS);\n this.spinConfig = normalizeSpinConfig(options.spinConfig);\n this.spinBlur = normalizeSpinBlur(options.spinBlur);\n this.webglShading = normalizeWebGLShading(options.webglShading);\n this.idleBob = normalizeIdleBob(options.idleBob);\n this.maxDpr = normalizeMaxDpr(options.maxDpr);\n\n this.canvas = resolveElement<HTMLCanvasElement>(options.canvas, 'Canvas');\n this.container = resolveElement<HTMLElement>(options.container, 'Container');\n\n this.offsets = new Array(this.reels).fill(0);\n this.velocities = new Array(this.reels).fill(0);\n this.lastVelOffsets = new Array(this.reels).fill(0);\n this.idleBaseOffsets = new Array(this.reels).fill(0);\n }\n\n async init(): Promise<void> {\n await this.loadSprite();\n\n this.tryInitWebGL();\n\n if (!this.gl) {\n throw new Error(\n `WebGL is not supported or failed to initialize. ${this.webglInitError ?? ''}`.trim(),\n );\n }\n\n this.syncContainerLayoutVars();\n this.ensureCanvasCoversContainer();\n this.setupResizeWatcher();\n this.resizeToContainer();\n\n this.setSegments(this.opts.initialSegments);\n\n this.button = this.resolveButton();\n this.queuedSpins = this.resolveSpinQueue();\n\n this.attachButtonHandler();\n\n this.render();\n this.startLoop();\n }\n\n destroy(): void {\n this.stop();\n\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = null;\n }\n\n if (this.button && this.buttonHandlerAttached) {\n this.button.removeEventListener('click', this.handleButtonClick);\n this.buttonHandlerAttached = false;\n }\n\n this.releaseWebGLResources();\n }\n\n private ensureCanvasCoversContainer(): void {\n if (this.canvas === this.container) return;\n if (!this.container.contains(this.canvas)) return;\n\n if (!this.canvas.style.width) this.canvas.style.width = '100%';\n if (!this.canvas.style.height) this.canvas.style.height = '100%';\n if (!this.canvas.style.display) this.canvas.style.display = 'block';\n }\n\n private syncContainerLayoutVars(): void {\n const target = this.canvas === this.container ? this.canvas : this.container;\n\n target.style.setProperty('--reels', String(this.reels));\n target.style.setProperty('--visible-slots', String(this.visibleSlots));\n target.style.aspectRatio = `${this.reels} / ${this.visibleSlots * this.heightScale}`;\n\n if (this.canvas !== this.container) {\n this.container.style.overflow = 'hidden';\n }\n }\n\n setSegments(initialSegments?: number[]): void {\n this.ensureReady();\n\n const segments = initialSegments ?? [];\n\n for (let r = 0; r < this.reels; r += 1) {\n const segIndex = segments[r] ?? 0;\n const idx = clampInt(segIndex, 0, this.opts.slotCount - 1);\n\n this.offsets[r] = idx * this.frameHeight - this.centerIndex * this.frameHeight;\n this.lastVelOffsets[r] = this.offsets[r];\n this.velocities[r] = 0;\n this.idleBaseOffsets[r] = this.offsets[r];\n }\n\n this.lastVelT = null;\n this.idleStartTime = null;\n this.anim = null;\n\n this.stop();\n this.render();\n this.startLoop();\n }\n\n setSegment(segmentIndex: number): void {\n this.setSegments(new Array(this.reels).fill(segmentIndex));\n }\n\n spinOnce(stopAtSegments: number[], config?: Partial<ReelDealSpinConfig>): Promise<void> {\n this.ensureReady();\n\n const { minSpins, staggerMs, speedPxS, durationMs } = this.getSpinConfig(config);\n const useSpeed = speedPxS > 0;\n const stopSpringOvershootPx = this.getStopSpringOvershootPx();\n const stopSpringSettleMs = Math.max(0, STOP_SPRING_SETTLE_MS);\n const stopSpringEnabled = stopSpringSettleMs > 0 && stopSpringOvershootPx > 0;\n\n const startOffsets = new Array(this.reels);\n const deltas = new Array(this.reels);\n const durations = new Array(this.reels);\n const settleDurationMs = new Array(this.reels);\n const targets = new Array(this.reels);\n\n for (let r = 0; r < this.reels; r += 1) {\n const idx = clampInt(stopAtSegments[r] ?? 0, 0, this.opts.slotCount - 1);\n const desiredTop = idx * this.frameHeight - this.centerIndex * this.frameHeight;\n const desired = mod(desiredTop, this.spriteHeight);\n const current = mod(this.offsets[r], this.spriteHeight);\n\n const alignDelta = mod(current - desired, this.spriteHeight);\n\n let delta = -(minSpins * this.spriteHeight + alignDelta);\n\n if (useSpeed && durationMs > 0) {\n const desiredDistance = (speedPxS * durationMs) / 1000;\n const loops = Math.max(\n minSpins,\n Math.ceil(Math.max(0, desiredDistance - alignDelta) / this.spriteHeight),\n );\n\n delta = -(alignDelta + loops * this.spriteHeight);\n }\n\n if (staggerMs > 0 && useSpeed) {\n const extraDelta = (speedPxS * staggerMs * r) / 1000;\n const extraLoops = Math.ceil(extraDelta / this.spriteHeight);\n\n delta -= extraLoops * this.spriteHeight;\n }\n\n startOffsets[r] = this.offsets[r];\n targets[r] = this.offsets[r] + delta;\n\n let finalDelta = delta;\n let settleDuration = 0;\n\n if (stopSpringEnabled && delta !== 0 && stopSpringOvershootPx > 0) {\n const direction = Math.sign(delta);\n const overshoot = direction * stopSpringOvershootPx;\n\n finalDelta += overshoot;\n settleDuration = stopSpringSettleMs;\n }\n\n deltas[r] = finalDelta;\n settleDurationMs[r] = settleDuration;\n\n if (useSpeed) {\n durations[r] = Math.max(0, Math.round((Math.abs(finalDelta) / speedPxS) * 1000));\n } else {\n durations[r] = Math.max(0, durationMs + staggerMs * r);\n }\n }\n\n const totalDelta = deltas.reduce((acc, v) => acc + Math.abs(v), 0);\n const allSpinZero = durations.every((entry) => entry <= 0);\n const allSettleZero = settleDurationMs.every((entry) => entry <= 0);\n\n if (totalDelta === 0 || (allSpinZero && allSettleZero)) {\n for (let r = 0; r < this.reels; r += 1) {\n this.offsets[r] = targets[r];\n }\n\n this.anim = null;\n this.resolvePendingSpins();\n this.render();\n\n return Promise.resolve();\n }\n\n const startTime = performance.now();\n\n this.anim = {\n startTime,\n startOffsets,\n deltas,\n durationMs: durations,\n settleDurationMs,\n targets,\n };\n this.idleStartTime = null;\n\n this.startLoop();\n\n return new Promise((resolve) => {\n this.pendingSpinResolvers.push(resolve);\n });\n }\n\n private getSpinConfig(\n overrides?: Partial<ReelDealSpinConfig>,\n ): ReturnType<typeof normalizeSpinConfig> {\n if (!overrides) return this.spinConfig;\n\n return normalizeSpinConfig({ ...this.spinConfig, ...overrides });\n }\n\n private getStopSpringOvershootPx(): number {\n const cap = this.frameHeight * STOP_SPRING_MAX_RATIO;\n const overshoot = Math.max(0, STOP_SPRING_OVERSHOOT_PX);\n\n return Math.min(overshoot, cap);\n }\n\n private getStopSpringOffset(overshoot: number, t: number): number {\n if (overshoot === 0) return 0;\n\n const decay = Math.exp(-STOP_SPRING_DAMPING * t);\n const angle = Math.PI * 2 * STOP_SPRING_OSCILLATIONS * t;\n\n return overshoot * decay * Math.cos(angle);\n }\n\n async spinQueue(spins: ReelDealSpinState[]): Promise<void> {\n for (let i = 0; i < spins.length; i += 1) {\n const spin = spins[i];\n\n await this.spinOnce(spin.stopAtSegments);\n\n spin.callback?.(i, spin.stopAtSegments);\n }\n }\n\n stop(): void {\n if (this.rafId !== null) cancelAnimationFrame(this.rafId);\n\n this.rafId = null;\n this.anim = null;\n this.resolvePendingSpins();\n this.lastVelT = null;\n\n for (let r = 0; r < this.reels; r += 1) {\n this.velocities[r] = 0;\n }\n }\n\n private resolvePendingSpins(): void {\n if (this.pendingSpinResolvers.length === 0) return;\n\n const resolvers = this.pendingSpinResolvers;\n\n this.pendingSpinResolvers = [];\n\n for (const resolve of resolvers) {\n resolve();\n }\n }\n\n requestResize(): void {\n this.queueResize();\n }\n\n private ensureReady(): void {\n if (\n !this.spriteImg ||\n this.spriteWidth <= 0 ||\n this.spriteHeight <= 0 ||\n this.frameHeight <= 0\n ) {\n throw new Error('Sprite is not ready. Call init() and await it first.');\n }\n }\n\n private attachButtonHandler(): void {\n if (!this.button || this.buttonHandlerAttached) return;\n\n this.button.addEventListener('click', this.handleButtonClick, { passive: true });\n this.buttonHandlerAttached = true;\n }\n\n private handleButtonClick = async (): Promise<void> => {\n if (this.spinning) return;\n\n const spin = this.consumeNextSpin() ?? {\n stopAtSegments: this.buildRandomSegments(),\n };\n\n this.spinning = true;\n this.setButtonDisabled(true);\n\n try {\n await this.spinOnce(spin.stopAtSegments);\n } finally {\n if (this.queuedSpins && this.queuedSpins.length === 0) {\n this.setButtonDisabled(true);\n } else {\n this.setButtonDisabled(false);\n }\n\n this.spinning = false;\n }\n };\n\n private resolveButton(): HTMLButtonElement | null {\n if (!this.opts.button) return null;\n\n return resolveElement<HTMLButtonElement>(this.opts.button, 'Button');\n }\n\n private resolveSpinQueue(): ReelDealSpinState[] | null {\n return this.opts.queuedSpinStates ?? null;\n }\n\n private consumeNextSpin(): ReelDealSpinState | null {\n if (!this.queuedSpins) {\n this.queuedSpins = this.resolveSpinQueue();\n }\n\n if (!this.queuedSpins || this.queuedSpins.length === 0) return null;\n\n return this.queuedSpins.shift() ?? null;\n }\n\n private buildRandomSegments(): number[] {\n const max = Math.max(1, Math.floor(this.opts.slotCount));\n const segments: number[] = [];\n\n for (let r = 0; r < this.reels; r += 1) {\n segments.push(Math.floor(Math.random() * max));\n }\n\n return segments;\n }\n\n private setButtonDisabled(disabled: boolean): void {\n if (!this.button) return;\n\n this.button.disabled = disabled;\n\n if (disabled) this.button.setAttribute('aria-busy', 'true');\n else this.button.removeAttribute('aria-busy');\n }\n\n private shouldAnimate(): boolean {\n return this.anim !== null || this.idleBob !== null;\n }\n\n private startLoop(): void {\n if (this.rafId !== null) return;\n if (!this.shouldAnimate()) return;\n\n this.rafId = requestAnimationFrame(this.loop);\n }\n\n private loop = (now: number): void => {\n if (this.anim) {\n const { startTime, durationMs, startOffsets, deltas, settleDurationMs, targets } = this.anim;\n const elapsed = now - startTime;\n\n let allDone = true;\n\n for (let r = 0; r < this.reels; r += 1) {\n const duration = durationMs[r] ?? 0;\n const settleDuration = settleDurationMs[r] ?? 0;\n const targetOffset = targets[r] ?? startOffsets[r] + deltas[r];\n\n if (duration > 0 && elapsed < duration) {\n const t = Math.min(1, elapsed / duration);\n const eased = reelProgress(t);\n\n if (t < 1) allDone = false;\n\n const baseOffset = startOffsets[r] + deltas[r] * eased;\n\n let tick = 0;\n\n if (t > SPIN_TICK_START && this.frameHeight > 0) {\n const u = clamp01((t - SPIN_TICK_START) / SPIN_TICK_RANGE);\n const env = (1 - u) ** 2;\n const phase = (baseOffset / this.frameHeight) * Math.PI * 2;\n const amp = Math.min(SPIN_TICK_MAX_PX, this.frameHeight * 0.06);\n\n tick = Math.sin(phase) * env * amp;\n }\n\n this.offsets[r] = baseOffset + tick;\n } else if (settleDuration > 0) {\n const t =\n duration <= 0 ? elapsed / settleDuration : (elapsed - duration) / settleDuration;\n const u = clamp01(t);\n const overshoot = startOffsets[r] + deltas[r] - targetOffset;\n const spring = this.getStopSpringOffset(overshoot, u);\n\n if (u < 1) allDone = false;\n\n this.offsets[r] = targetOffset + spring;\n } else {\n this.offsets[r] = targetOffset;\n }\n }\n\n this.updateVelocity(now);\n this.render();\n\n if (allDone) {\n for (let r = 0; r < this.reels; r += 1) {\n const targetOffset =\n this.anim.targets[r] ?? this.anim.startOffsets[r] + this.anim.deltas[r];\n\n this.offsets[r] = targetOffset;\n this.idleBaseOffsets[r] = this.offsets[r];\n }\n\n this.updateVelocity(now);\n\n for (let r = 0; r < this.reels; r += 1) {\n this.velocities[r] = 0;\n }\n\n this.anim = null;\n this.idleStartTime = null;\n\n this.render();\n this.resolvePendingSpins();\n }\n } else if (this.idleBob) {\n this.updateIdle(now);\n this.render();\n }\n\n if (!this.shouldAnimate()) {\n this.rafId = null;\n\n return;\n }\n\n if (this.rafId === null) return;\n\n this.rafId = requestAnimationFrame(this.loop);\n };\n\n private render(): void {\n if (!this.spriteImg) return;\n if (this.width <= 0 || this.height <= 0) return;\n if (!this.gl) return;\n\n this.renderWebGL();\n }\n\n private getSpinBlurPxForReel(reel: number): number {\n if (!this.anim) return 0;\n\n const speed = Math.abs(this.velocities[reel] ?? 0);\n const speedAtMax = Math.max(1, this.spinBlur.speedAtMax);\n const t = clamp01(speed / speedAtMax);\n\n return t * Math.max(0, this.spinBlur.maxPx);\n }\n\n private renderWebGL(): void {\n if (\n !this.gl ||\n !this.glProgram ||\n !this.glAttribs ||\n !this.glUniforms ||\n !this.glTexture ||\n !this.glBuffer\n ) {\n return;\n }\n\n if (!this.spriteImg) return;\n\n const gl = this.gl;\n\n gl.useProgram(this.glProgram);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.glBuffer);\n\n gl.enableVertexAttribArray(this.glAttribs.pos);\n gl.vertexAttribPointer(this.glAttribs.pos, 2, gl.FLOAT, false, 16, 0);\n gl.enableVertexAttribArray(this.glAttribs.uv);\n gl.vertexAttribPointer(this.glAttribs.uv, 2, gl.FLOAT, false, 16, 8);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.glTexture);\n gl.uniform1i(this.glUniforms.texture, 0);\n\n gl.uniform1f(this.glUniforms.frameHeight, this.frameHeight);\n gl.uniform1f(this.glUniforms.spriteHeight, this.spriteHeight);\n gl.uniform1f(this.glUniforms.visibleSlots, this.visibleSlots);\n gl.uniform1f(this.glUniforms.heightScale, this.heightScale);\n gl.uniform1f(this.glUniforms.edgePower, Math.max(0.5, this.webglShading.edgePower));\n\n const shading = this.webglShading;\n const warpAngle = (Math.max(0, shading.warpAngleDeg) * Math.PI) / 180;\n\n gl.uniform1f(this.glUniforms.warpAngle, warpAngle);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n const reelW = 1 / this.reels;\n\n for (let r = 0; r < this.reels; r += 1) {\n gl.uniform1f(this.glUniforms.reelX, r * reelW);\n gl.uniform1f(this.glUniforms.reelW, reelW);\n\n const offsetPx = mod(this.offsets[r], this.spriteHeight);\n\n gl.uniform1f(this.glUniforms.offsetPx, offsetPx);\n gl.uniform1f(this.glUniforms.blurPx, this.getSpinBlurPxForReel(r));\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n }\n\n private updateVelocity(now: number): void {\n if (this.lastVelT === null) {\n this.lastVelT = now;\n\n for (let r = 0; r < this.reels; r += 1) {\n this.lastVelOffsets[r] = this.offsets[r];\n this.velocities[r] = 0;\n }\n\n return;\n }\n\n const dt = (now - this.lastVelT) / 1000;\n\n if (dt <= 0) return;\n\n for (let r = 0; r < this.reels; r += 1) {\n const v = (this.offsets[r] - this.lastVelOffsets[r]) / dt;\n\n this.velocities[r] = this.velocities[r] * VELOCITY_DECAY + v * VELOCITY_GAIN;\n this.lastVelOffsets[r] = this.offsets[r];\n }\n\n this.lastVelT = now;\n }\n\n private updateIdle(now: number): void {\n if (!this.idleBob) return;\n if (this.idleStartTime === null) this.idleStartTime = now;\n\n const { amplitudePx, speedHz, phaseOffsetRad, rampMs } = this.idleBob;\n const elapsedMs = now - this.idleStartTime;\n\n const tSec = elapsedMs / 1000;\n const rampT = Math.max(0, Math.min(1, elapsedMs / Math.max(1, rampMs)));\n const ramp = rampHermite(rampT);\n const omega = speedHz * Math.PI * 2;\n\n for (let r = 0; r < this.reels; r += 1) {\n const phase = tSec * omega + r * phaseOffsetRad;\n\n this.offsets[r] = this.idleBaseOffsets[r] + Math.sin(phase) * (amplitudePx * ramp);\n this.velocities[r] = 0;\n }\n }\n\n private async loadSprite(): Promise<void> {\n const { sprite } = this.opts;\n const img = typeof sprite === 'string' ? new Image() : sprite;\n\n if (typeof sprite === 'string') {\n img.src = sprite;\n }\n\n if (!img.complete || img.naturalWidth <= 0) {\n await new Promise<void>((resolve, reject) => {\n const onLoad = () => resolve();\n const onError = () => reject(new Error('Failed to load sprite image'));\n\n img.addEventListener('load', onLoad, { once: true });\n img.addEventListener('error', onError, { once: true });\n });\n }\n\n const slotCount = Math.max(1, Math.floor(this.opts.slotCount));\n const spriteWidth = img.naturalWidth;\n const frameHeight = spriteWidth;\n const expectedSpriteHeight = frameHeight * slotCount;\n\n const naturalHeight = img.naturalHeight;\n const diff = Math.abs(naturalHeight - expectedSpriteHeight);\n\n if (diff > 2) {\n throw new Error(\n `Sprite size mismatch. Expected height ~${Math.round(expectedSpriteHeight)}px (slotCount=${slotCount}, slot aspect 1/1), got ${naturalHeight}px.`,\n );\n }\n\n this.spriteImg = img;\n this.spriteWidth = spriteWidth;\n this.frameHeight = Math.max(1, Math.round(frameHeight));\n this.spriteHeight = Math.max(1, Math.round(this.frameHeight * slotCount));\n }\n\n private setupResizeWatcher(): void {\n if (typeof ResizeObserver === 'undefined') return;\n if (this.resizeObserver) return;\n\n this.resizeObserver = new ResizeObserver(() => this.queueResize());\n this.resizeObserver.observe(this.container);\n }\n\n private queueResize = (): void => {\n if (this.resizeQueued) return;\n\n this.resizeQueued = true;\n\n requestAnimationFrame(() => {\n this.resizeQueued = false;\n this.resizeToContainer();\n });\n };\n\n private resizeToContainer(): void {\n const cssWidth = this.container.clientWidth;\n const cssHeight = this.container.clientHeight;\n const slotsViewport = this.visibleSlots * this.heightScale;\n\n if (cssWidth <= 0 || cssHeight <= 0) {\n this.applyResize(1, 1);\n\n return;\n }\n\n const slots = slotsViewport;\n const reels = this.reels;\n const fitByWidthH = (cssWidth / reels) * slots;\n\n let w = cssWidth;\n let h = fitByWidthH;\n\n if (h > cssHeight) {\n h = cssHeight;\n w = (cssHeight / slots) * reels;\n }\n\n const viewportWidth = Math.max(1, Math.floor(w));\n const viewportHeight = Math.max(1, Math.floor(h));\n\n this.applyResize(viewportWidth, viewportHeight);\n this.render();\n }\n\n private applyViewportCrop(): void {\n this.ensureCanvasCoversContainer();\n this.canvas.style.transform = '';\n }\n\n private applyResize(width: number, height: number): void {\n const normalizedWidth = Math.max(1, Math.floor(width));\n const normalizedHeight = Math.max(1, Math.floor(height));\n\n const dpr = this.getTargetDpr();\n\n this.width = normalizedWidth;\n this.height = normalizedHeight;\n this.dpr = dpr;\n\n this.canvas.width = Math.floor(this.width * this.dpr);\n this.canvas.height = Math.floor(this.height * this.dpr);\n\n if (this.gl) {\n this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);\n }\n\n this.applyViewportCrop();\n }\n\n private getTargetDpr(): number {\n const rawDpr = window.devicePixelRatio || 1;\n const base = Math.max(1, rawDpr);\n\n return Math.min(this.maxDpr, base);\n }\n\n private tryInitWebGL(): void {\n if (!this.spriteImg) return;\n\n this.releaseWebGLResources();\n this.webglInitError = null;\n\n const gl =\n (this.canvas.getContext('webgl2', {\n antialias: true,\n alpha: true,\n }) as WebGL2RenderingContext | null) ??\n (this.canvas.getContext('webgl', {\n antialias: true,\n alpha: true,\n }) as WebGLRenderingContext | null) ??\n (this.canvas.getContext('experimental-webgl', {\n antialias: true,\n alpha: true,\n }) as WebGLRenderingContext | null);\n\n if (!gl) {\n const fallback = document.createElement('canvas');\n const canWebGL = typeof WebGLRenderingContext !== 'undefined';\n const fallbackGl =\n (fallback.getContext('webgl2') as WebGL2RenderingContext | null) ??\n (fallback.getContext('webgl') as WebGLRenderingContext | null);\n\n this.webglInitError = `context is null (browser WebGL=${canWebGL ? 'yes' : 'no'}, fallback canvas=${fallbackGl ? 'yes' : 'no'})`;\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n return;\n }\n\n const vsSource = `\n attribute vec2 a_pos;\n attribute vec2 a_uv;\n varying vec2 v_uv;\n void main() {\n v_uv = a_uv;\n gl_Position = vec4(a_pos, 0.0, 1.0);\n }\n `;\n\n const fsSource = `\n #ifdef GL_FRAGMENT_PRECISION_HIGH\n precision highp float;\n #else\n precision mediump float;\n #endif\n\n varying vec2 v_uv;\n uniform sampler2D u_tex;\n uniform float u_reelX;\n uniform float u_reelW;\n uniform float u_offsetPx;\n uniform float u_frameHeight;\n uniform float u_spriteHeight;\n uniform float u_visibleSlots;\n uniform float u_heightScale;\n uniform float u_edgePower;\n uniform float u_warpAngle;\n uniform float u_blurPx;\n\n const float HALF_PI = 1.5707963;\n\n float saturate(float x) { return clamp(x, 0.0, 1.0); }\n\n void main() {\n if (v_uv.x < u_reelX || v_uv.x > (u_reelX + u_reelW)) {\n discard;\n }\n\n float localX = (v_uv.x - u_reelX) / u_reelW;\n float localY = v_uv.y;\n float hs = max(0.0001, u_heightScale);\n float fullY = localY * hs + (1.0 - hs) * 0.5;\n\n float warp = max(0.0, u_warpAngle);\n float warpStrength = saturate(warp / HALF_PI);\n float distNorm = abs(fullY - 0.5) * 2.0;\n float edgePow = max(0.5, u_edgePower);\n float edgeWeight = pow(saturate(distNorm), edgePow) * warpStrength;\n\n float stretchMax = 6.0;\n float stretch = mix(1.0, stretchMax, edgeWeight);\n float warpedY = 0.5 + (fullY - 0.5) * stretch;\n\n float arcY = 0.5 + 0.5 * sin((warpedY - 0.5) * HALF_PI);\n warpedY = mix(warpedY, arcY, 0.2);\n\n float yPx = u_offsetPx + warpedY * (u_frameHeight * u_visibleSlots);\n float v = fract((u_offsetPx / u_spriteHeight) +\n warpedY * ((u_frameHeight * u_visibleSlots) / u_spriteHeight));\n\n vec4 base = texture2D(u_tex, vec2(localX, v));\n\n if (u_blurPx > 0.0) {\n float maxStepPx = 1.0 + 3.0 * saturate(u_blurPx / 4.0);\n float blurPx = min(u_blurPx, maxStepPx);\n float blurStep = blurPx / max(1.0, u_spriteHeight);\n vec4 sum = base * 0.227027;\n sum += texture2D(u_tex, vec2(localX, fract(v + blurStep))) * 0.1945946;\n sum += texture2D(u_tex, vec2(localX, fract(v - blurStep))) * 0.1945946;\n sum += texture2D(u_tex, vec2(localX, fract(v + 2.0 * blurStep))) * 0.1216216;\n sum += texture2D(u_tex, vec2(localX, fract(v - 2.0 * blurStep))) * 0.1216216;\n sum += texture2D(u_tex, vec2(localX, fract(v + 3.0 * blurStep))) * 0.054054;\n sum += texture2D(u_tex, vec2(localX, fract(v - 3.0 * blurStep))) * 0.054054;\n sum += texture2D(u_tex, vec2(localX, fract(v + 4.0 * blurStep))) * 0.016216;\n sum += texture2D(u_tex, vec2(localX, fract(v - 4.0 * blurStep))) * 0.016216;\n base = sum;\n }\n\n gl_FragColor = base;\n }\n `;\n\n const program = this.createWebGLProgram(gl, vsSource, fsSource);\n\n if (!program) {\n this.webglInitError = 'shader program failed to compile/link (see console)';\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n return;\n }\n\n const buffer = gl.createBuffer();\n\n if (!buffer) {\n this.webglInitError = 'failed to create vertex buffer';\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n gl.deleteProgram(program);\n\n return;\n }\n\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n\n const quad = new Float32Array([-1, -1, 0, 1, 1, -1, 1, 1, -1, 1, 0, 0, 1, 1, 1, 0]);\n\n gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);\n\n const texture = gl.createTexture();\n\n if (!texture) {\n this.webglInitError = 'failed to create texture';\n\n console.warn('ReelDeal WebGL:', this.webglInitError);\n\n gl.deleteBuffer(buffer);\n gl.deleteProgram(program);\n\n return;\n }\n\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.spriteImg);\n\n const isPowerOfTwo = (n: number): boolean => n > 0 && (n & (n - 1)) === 0;\n const pot = isPowerOfTwo(this.spriteWidth) && isPowerOfTwo(this.spriteHeight);\n\n if (pot) {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);\n gl.generateMipmap(gl.TEXTURE_2D);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n } else {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n }\n\n gl.useProgram(program);\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\n this.gl = gl;\n this.glProgram = program;\n this.glBuffer = buffer;\n this.glTexture = texture;\n this.glAttribs = {\n pos: gl.getAttribLocation(program, 'a_pos'),\n uv: gl.getAttribLocation(program, 'a_uv'),\n };\n this.glUniforms = {\n texture: gl.getUniformLocation(program, 'u_tex'),\n reelX: gl.getUniformLocation(program, 'u_reelX'),\n reelW: gl.getUniformLocation(program, 'u_reelW'),\n offsetPx: gl.getUniformLocation(program, 'u_offsetPx'),\n frameHeight: gl.getUniformLocation(program, 'u_frameHeight'),\n spriteHeight: gl.getUniformLocation(program, 'u_spriteHeight'),\n visibleSlots: gl.getUniformLocation(program, 'u_visibleSlots'),\n heightScale: gl.getUniformLocation(program, 'u_heightScale'),\n edgePower: gl.getUniformLocation(program, 'u_edgePower'),\n warpAngle: gl.getUniformLocation(program, 'u_warpAngle'),\n blurPx: gl.getUniformLocation(program, 'u_blurPx'),\n };\n }\n\n private createWebGLProgram(\n gl: WebGLRenderingContext | WebGL2RenderingContext,\n vsSource: string,\n fsSource: string,\n ): WebGLProgram | null {\n const vs = this.createWebGLShader(gl, gl.VERTEX_SHADER, vsSource);\n const fs = this.createWebGLShader(gl, gl.FRAGMENT_SHADER, fsSource);\n\n if (!vs || !fs) {\n if (vs) gl.deleteShader(vs);\n if (fs) gl.deleteShader(fs);\n\n return null;\n }\n\n const program = gl.createProgram();\n\n if (!program) return null;\n\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const info = gl.getProgramInfoLog(program) ?? 'unknown link error';\n\n this.webglInitError = `program link failed: ${info}`;\n\n console.warn('ReelDeal WebGL: program link failed', info);\n\n gl.deleteProgram(program);\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n\n return null;\n }\n\n gl.detachShader(program, vs);\n gl.detachShader(program, fs);\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n\n return program;\n }\n\n private createWebGLShader(\n gl: WebGLRenderingContext | WebGL2RenderingContext,\n type: number,\n source: string,\n ): WebGLShader | null {\n const shader = gl.createShader(type);\n\n if (!shader) return null;\n\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const info = gl.getShaderInfoLog(shader) ?? 'unknown compile error';\n\n this.webglInitError = `shader compile failed: ${info}`;\n\n console.warn('ReelDeal WebGL: shader compile failed', info);\n\n gl.deleteShader(shader);\n\n return null;\n }\n\n return shader;\n }\n\n private releaseWebGLResources(): void {\n if (!this.gl) return;\n if (this.glTexture) this.gl.deleteTexture(this.glTexture);\n if (this.glBuffer) this.gl.deleteBuffer(this.glBuffer);\n if (this.glProgram) this.gl.deleteProgram(this.glProgram);\n\n this.glTexture = null;\n this.glBuffer = null;\n this.glProgram = null;\n this.glAttribs = null;\n this.glUniforms = null;\n this.gl = null;\n }\n}\n"],"names":["defaultSpinConfig","defaultSpinBlur","defaultIdleBob","SPIN_ACCEL_END","SPIN_DECEL_START","clampInt","value","min","max","mod","n","m","clamp01","t","rampHermite","u","x","brakeHermite","reelProgress","tt","finiteOr","fallback","nonNegative","nonNegativeInt","DEFAULT_MAX_DPR","normalizeSpinConfig","overrides","base","normalizeSpinBlur","normalizeIdleBob","normalizeMaxDpr","resolveElementById","id","kind","el","resolveElement","elOrId","MAX_REELS","SPIN_TICK_START","SPIN_TICK_RANGE","SPIN_TICK_MAX_PX","VELOCITY_DECAY","VELOCITY_GAIN","STOP_SPRING_OVERSHOOT_PX","STOP_SPRING_SETTLE_MS","STOP_SPRING_OSCILLATIONS","STOP_SPRING_MAX_RATIO","DEFAULT_WEBGL_SHADING","normalizeWebGLShading","ReelDeal","options","target","initialSegments","segments","r","segIndex","idx","segmentIndex","stopAtSegments","config","minSpins","staggerMs","speedPxS","durationMs","useSpeed","stopSpringOvershootPx","stopSpringSettleMs","stopSpringEnabled","startOffsets","deltas","durations","settleDurationMs","targets","desiredTop","desired","current","alignDelta","delta","desiredDistance","loops","extraDelta","extraLoops","finalDelta","settleDuration","overshoot","totalDelta","acc","v","allSpinZero","entry","allSettleZero","startTime","resolve","cap","decay","angle","spins","i","spin","resolvers","disabled","now","elapsed","allDone","duration","targetOffset","eased","baseOffset","tick","env","phase","amp","spring","reel","speed","speedAtMax","gl","shading","warpAngle","reelW","offsetPx","dt","amplitudePx","speedHz","phaseOffsetRad","rampMs","elapsedMs","tSec","rampT","ramp","omega","sprite","img","reject","onLoad","onError","slotCount","spriteWidth","frameHeight","expectedSpriteHeight","naturalHeight","cssWidth","cssHeight","slotsViewport","slots","reels","fitByWidthH","w","h","viewportWidth","viewportHeight","width","height","normalizedWidth","normalizedHeight","dpr","rawDpr","canWebGL","fallbackGl","program","buffer","quad","texture","isPowerOfTwo","vsSource","fsSource","vs","fs","info","type","source","shader"],"mappings":"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"}
|
package/dist/index.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(m,g){typeof exports=="object"&&typeof module<"u"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(m=typeof globalThis<"u"?globalThis:m||self,g(m.ReelDeal={}))})(this,(function(m){"use strict";const g={minSpins:2,durationMs:4e3,speedPxS:4e3,staggerMs:300},w={maxPx:4,speedAtMax:2200},x={amplitudePx:9,speedHz:.25,phaseOffsetRad:.9,rampMs:800},H=.1,A=.6,B=(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)),G=a=>{const t=v(a);return t**2*(2-t)},Y=a=>{const t=v(a);return t+t**2-t**3},q=a=>{const t=v(a);if(t<=H){const i=t/H;return G(i)*H}if(t<A)return t;const s=(t-A)/(1-A);return A+Y(s)*(1-A)},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))),$=2,X=a=>{const t={...g,...a??{}};return{minSpins:R(t.minSpins,g.minSpins),durationMs:R(t.durationMs,g.durationMs??0),speedPxS:R(t.speedPxS,g.speedPxS??0),staggerMs:R(t.staggerMs,g.staggerMs??0)}},Q=a=>{const t={...w,...a??{}};return{maxPx:_(t.maxPx,w.maxPx),speedAtMax:_(t.speedAtMax,w.speedAtMax)}},K=a=>{if(a===!1)return null;const t={...x,...a??{}};return{amplitudePx:_(t.amplitudePx,x.amplitudePx),speedHz:_(t.speedHz,x.speedHz),phaseOffsetRad:L(t.phaseOffsetRad,x.phaseOffsetRad),rampMs:R(t.rampMs,x.rampMs)}},j=a=>Math.max(1,L(a,$)),Z=(a,t)=>{const s=document.getElementById(a);if(!s)throw new Error(`${t} not found: ${a}`);return s},C=(a,t)=>typeof a=="string"?Z(a,t):a,J=5,z=.72,tt=.28,et=7,N=.85,st=1-N,it=30,rt=500,nt=1.25,at=.25,F={warpAngleDeg:360,edgePower:.5},ot=a=>({warpAngleDeg:_(a?.warpAngleDeg,F.warpAngleDeg),edgePower:_(a?.edgePower,F.edgePower)});class lt{canvas;container;opts;visibleSlots=3;heightScale=2/3;centerIndex=1;reels;spinConfig;spinBlur;webglShading;idleBob;maxDpr;spriteImg=null;spriteWidth=0;spriteHeight=0;frameHeight=0;width=0;height=0;dpr=1;offsets;velocities;lastVelT=null;lastVelOffsets;idleStartTime=null;idleBaseOffsets;gl=null;glProgram=null;glBuffer=null;glTexture=null;glAttribs=null;webglInitError=null;glUniforms=null;anim=null;rafId=null;pendingSpinResolvers=[];resizeObserver=null;resizeQueued=!1;button=null;buttonHandlerAttached=!1;spinning=!1;queuedSpins=null;constructor(t){this.opts=t,this.reels=B(t.reels??1,1,J),this.spinConfig=X(t.spinConfig),this.spinBlur=Q(t.spinBlur),this.webglShading=ot(t.webglShading),this.idleBob=K(t.idleBob),this.maxDpr=j(t.maxDpr),this.canvas=C(t.canvas,"Canvas"),this.container=C(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)}async init(){if(await this.loadSprite(),this.tryInitWebGL(),!this.gl)throw new Error(`WebGL is not supported or failed to initialize. ${this.webglInitError??""}`.trim());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()}destroy(){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()}ensureCanvasCoversContainer(){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"))}syncContainerLayoutVars(){const t=this.canvas===this.container?this.canvas:this.container;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")}setSegments(t){this.ensureReady();const s=t??[];for(let i=0;i<this.reels;i+=1){const e=s[i]??0,r=B(e,0,this.opts.slotCount-1);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]}this.lastVelT=null,this.idleStartTime=null,this.anim=null,this.stop(),this.render(),this.startLoop()}setSegment(t){this.setSegments(new Array(this.reels).fill(t))}spinOnce(t,s){this.ensureReady();const{minSpins:i,staggerMs:e,speedPxS:r,durationMs:l}=this.getSpinConfig(s),u=r>0,f=this.getStopSpringOvershootPx(),c=Math.max(0,rt),n=c>0&&f>0,h=new Array(this.reels),d=new Array(this.reels),S=new Array(this.reels),p=new Array(this.reels),b=new Array(this.reels);for(let o=0;o<this.reels;o+=1){const ht=B(t[o]??0,0,this.opts.slotCount-1)*this.frameHeight-this.centerIndex*this.frameHeight,ut=I(ht,this.spriteHeight),ft=I(this.offsets[o],this.spriteHeight),U=I(ft-ut,this.spriteHeight);let E=-(i*this.spriteHeight+U);if(u&&l>0){const D=r*l/1e3,M=Math.max(i,Math.ceil(Math.max(0,D-U)/this.spriteHeight));E=-(U+M*this.spriteHeight)}if(e>0&&u){const D=r*e*o/1e3,M=Math.ceil(D/this.spriteHeight);E-=M*this.spriteHeight}h[o]=this.offsets[o],b[o]=this.offsets[o]+E;let W=E,k=0;if(n&&E!==0&&f>0){const M=Math.sign(E)*f;W+=M,k=c}d[o]=W,p[o]=k,u?S[o]=Math.max(0,Math.round(Math.abs(W)/r*1e3)):S[o]=Math.max(0,l+e*o)}const P=d.reduce((o,y)=>o+Math.abs(y),0),T=S.every(o=>o<=0),V=p.every(o=>o<=0);if(P===0||T&&V){for(let o=0;o<this.reels;o+=1)this.offsets[o]=b[o];return this.anim=null,this.resolvePendingSpins(),this.render(),Promise.resolve()}const O=performance.now();return this.anim={startTime:O,startOffsets:h,deltas:d,durationMs:S,settleDurationMs:p,targets:b},this.idleStartTime=null,this.startLoop(),new Promise(o=>{this.pendingSpinResolvers.push(o)})}getSpinConfig(t){return t?X({...this.spinConfig,...t}):this.spinConfig}getStopSpringOvershootPx(){const t=this.frameHeight*at,s=Math.max(0,it);return Math.min(s,t)}getStopSpringOffset(t,s){if(t===0)return 0;const i=Math.exp(-4*s),e=Math.PI*2*nt*s;return t*i*Math.cos(e)}async spinQueue(t){for(let s=0;s<t.length;s+=1){const i=t[s];await this.spinOnce(i.stopAtSegments),i.callback?.(s,i.stopAtSegments)}}stop(){this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=null,this.anim=null,this.resolvePendingSpins(),this.lastVelT=null;for(let t=0;t<this.reels;t+=1)this.velocities[t]=0}resolvePendingSpins(){if(this.pendingSpinResolvers.length===0)return;const t=this.pendingSpinResolvers;this.pendingSpinResolvers=[];for(const s of t)s()}requestResize(){this.queueResize()}ensureReady(){if(!this.spriteImg||this.spriteWidth<=0||this.spriteHeight<=0||this.frameHeight<=0)throw new Error("Sprite is not ready. Call init() and await it first.")}attachButtonHandler(){!this.button||this.buttonHandlerAttached||(this.button.addEventListener("click",this.handleButtonClick,{passive:!0}),this.buttonHandlerAttached=!0)}handleButtonClick=async()=>{if(this.spinning)return;const t=this.consumeNextSpin()??{stopAtSegments:this.buildRandomSegments()};this.spinning=!0,this.setButtonDisabled(!0);try{await this.spinOnce(t.stopAtSegments)}finally{this.queuedSpins&&this.queuedSpins.length===0?this.setButtonDisabled(!0):this.setButtonDisabled(!1),this.spinning=!1}};resolveButton(){return this.opts.button?C(this.opts.button,"Button"):null}resolveSpinQueue(){return this.opts.queuedSpinStates??null}consumeNextSpin(){return this.queuedSpins||(this.queuedSpins=this.resolveSpinQueue()),!this.queuedSpins||this.queuedSpins.length===0?null:this.queuedSpins.shift()??null}buildRandomSegments(){const t=Math.max(1,Math.floor(this.opts.slotCount)),s=[];for(let i=0;i<this.reels;i+=1)s.push(Math.floor(Math.random()*t));return s}setButtonDisabled(t){this.button&&(this.button.disabled=t,t?this.button.setAttribute("aria-busy","true"):this.button.removeAttribute("aria-busy"))}shouldAnimate(){return this.anim!==null||this.idleBob!==null}startLoop(){this.rafId===null&&this.shouldAnimate()&&(this.rafId=requestAnimationFrame(this.loop))}loop=t=>{if(this.anim){const{startTime:s,durationMs:i,startOffsets:e,deltas:r,settleDurationMs:l,targets:u}=this.anim,f=t-s;let c=!0;for(let n=0;n<this.reels;n+=1){const h=i[n]??0,d=l[n]??0,S=u[n]??e[n]+r[n];if(h>0&&f<h){const p=Math.min(1,f/h),b=q(p);p<1&&(c=!1);const P=e[n]+r[n]*b;let T=0;if(p>z&&this.frameHeight>0){const O=(1-v((p-z)/tt))**2,o=P/this.frameHeight*Math.PI*2,y=Math.min(et,this.frameHeight*.06);T=Math.sin(o)*O*y}this.offsets[n]=P+T}else if(d>0){const p=h<=0?f/d:(f-h)/d,b=v(p),P=e[n]+r[n]-S,T=this.getStopSpringOffset(P,b);b<1&&(c=!1),this.offsets[n]=S+T}else this.offsets[n]=S}if(this.updateVelocity(t),this.render(),c){for(let n=0;n<this.reels;n+=1){const h=this.anim.targets[n]??this.anim.startOffsets[n]+this.anim.deltas[n];this.offsets[n]=h,this.idleBaseOffsets[n]=this.offsets[n]}this.updateVelocity(t);for(let n=0;n<this.reels;n+=1)this.velocities[n]=0;this.anim=null,this.idleStartTime=null,this.render(),this.resolvePendingSpins()}}else this.idleBob&&(this.updateIdle(t),this.render());if(!this.shouldAnimate()){this.rafId=null;return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.loop))};render(){this.spriteImg&&(this.width<=0||this.height<=0||this.gl&&this.renderWebGL())}getSpinBlurPxForReel(t){if(!this.anim)return 0;const s=Math.abs(this.velocities[t]??0),i=Math.max(1,this.spinBlur.speedAtMax);return v(s/i)*Math.max(0,this.spinBlur.maxPx)}renderWebGL(){if(!this.gl||!this.glProgram||!this.glAttribs||!this.glUniforms||!this.glTexture||!this.glBuffer||!this.spriteImg)return;const t=this.gl;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(.5,this.webglShading.edgePower));const s=this.webglShading,i=Math.max(0,s.warpAngleDeg)*Math.PI/180;t.uniform1f(this.glUniforms.warpAngle,i),t.clear(t.COLOR_BUFFER_BIT);const e=1/this.reels;for(let r=0;r<this.reels;r+=1){t.uniform1f(this.glUniforms.reelX,r*e),t.uniform1f(this.glUniforms.reelW,e);const l=I(this.offsets[r],this.spriteHeight);t.uniform1f(this.glUniforms.offsetPx,l),t.uniform1f(this.glUniforms.blurPx,this.getSpinBlurPxForReel(r)),t.drawArrays(t.TRIANGLE_STRIP,0,4)}}updateVelocity(t){if(this.lastVelT===null){this.lastVelT=t;for(let i=0;i<this.reels;i+=1)this.lastVelOffsets[i]=this.offsets[i],this.velocities[i]=0;return}const s=(t-this.lastVelT)/1e3;if(!(s<=0)){for(let i=0;i<this.reels;i+=1){const e=(this.offsets[i]-this.lastVelOffsets[i])/s;this.velocities[i]=this.velocities[i]*N+e*st,this.lastVelOffsets[i]=this.offsets[i]}this.lastVelT=t}}updateIdle(t){if(!this.idleBob)return;this.idleStartTime===null&&(this.idleStartTime=t);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=G(f),n=i*Math.PI*2;for(let h=0;h<this.reels;h+=1){const d=u*n+h*e;this.offsets[h]=this.idleBaseOffsets[h]+Math.sin(d)*(s*c),this.velocities[h]=0}}async loadSprite(){const{sprite:t}=this.opts,s=typeof t=="string"?new Image:t;typeof t=="string"&&(s.src=t),(!s.complete||s.naturalWidth<=0)&&await new Promise((c,n)=>{const h=()=>c(),d=()=>n(new Error("Failed to load sprite image"));s.addEventListener("load",h,{once:!0}),s.addEventListener("error",d,{once:!0})});const i=Math.max(1,Math.floor(this.opts.slotCount)),e=s.naturalWidth,r=e,l=r*i,u=s.naturalHeight;if(Math.abs(u-l)>2)throw new Error(`Sprite size mismatch. Expected height ~${Math.round(l)}px (slotCount=${i}, slot aspect 1/1), got ${u}px.`);this.spriteImg=s,this.spriteWidth=e,this.frameHeight=Math.max(1,Math.round(r)),this.spriteHeight=Math.max(1,Math.round(this.frameHeight*i))}setupResizeWatcher(){typeof ResizeObserver>"u"||this.resizeObserver||(this.resizeObserver=new ResizeObserver(()=>this.queueResize()),this.resizeObserver.observe(this.container))}queueResize=()=>{this.resizeQueued||(this.resizeQueued=!0,requestAnimationFrame(()=>{this.resizeQueued=!1,this.resizeToContainer()}))};resizeToContainer(){const t=this.container.clientWidth,s=this.container.clientHeight,i=this.visibleSlots*this.heightScale;if(t<=0||s<=0){this.applyResize(1,1);return}const e=i,r=this.reels,l=t/r*e;let u=t,f=l;f>s&&(f=s,u=s/e*r);const c=Math.max(1,Math.floor(u)),n=Math.max(1,Math.floor(f));this.applyResize(c,n),this.render()}applyViewportCrop(){this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.style.transform=""}applyResize(t,s){const i=Math.max(1,Math.floor(t)),e=Math.max(1,Math.floor(s)),r=this.getTargetDpr();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()}getTargetDpr(){const t=window.devicePixelRatio||1,s=Math.max(1,t);return Math.min(this.maxDpr,s)}tryInitWebGL(){if(!this.spriteImg)return;this.releaseWebGLResources(),this.webglInitError=null;const t=this.canvas.getContext("webgl2",{antialias:!0,alpha:!0})??this.canvas.getContext("webgl",{antialias:!0,alpha:!0})??this.canvas.getContext("experimental-webgl",{antialias:!0,alpha:!0});if(!t){const n=document.createElement("canvas"),h=typeof WebGLRenderingContext<"u",d=n.getContext("webgl2")??n.getContext("webgl");this.webglInitError=`context is null (browser WebGL=${h?"yes":"no"}, fallback canvas=${d?"yes":"no"})`,console.warn("ReelDeal WebGL:",this.webglInitError);return}const e=this.createWebGLProgram(t,`
|
|
1
|
+
(function(m,g){typeof exports=="object"&&typeof module<"u"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(m=typeof globalThis<"u"?globalThis:m||self,g(m.ReelDeal={}))})(this,(function(m){"use strict";const g={minSpins:2,durationMs:4e3,speedPxS:4e3,staggerMs:300},w={maxPx:4,speedAtMax:2200},x={amplitudePx:9,speedHz:.25,phaseOffsetRad:.9,rampMs:800},H=.1,A=.6,C=(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)),G=a=>{const t=v(a);return t**2*(2-t)},k=a=>{const t=v(a);return t+t**2-t**3},q=a=>{const t=v(a);if(t<=H){const i=t/H;return G(i)*H}if(t<A)return t;const s=(t-A)/(1-A);return A+k(s)*(1-A)},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))),$=2,X=a=>{const t={...g,...a??{}};return{minSpins:R(t.minSpins,g.minSpins),durationMs:R(t.durationMs,g.durationMs??0),speedPxS:R(t.speedPxS,g.speedPxS??0),staggerMs:R(t.staggerMs,g.staggerMs??0)}},Q=a=>{const t={...w,...a??{}};return{maxPx:_(t.maxPx,w.maxPx),speedAtMax:_(t.speedAtMax,w.speedAtMax)}},K=a=>{if(a===!1)return null;const t={...x,...a??{}};return{amplitudePx:_(t.amplitudePx,x.amplitudePx),speedHz:_(t.speedHz,x.speedHz),phaseOffsetRad:L(t.phaseOffsetRad,x.phaseOffsetRad),rampMs:R(t.rampMs,x.rampMs)}},j=a=>Math.max(1,L(a,$)),Z=(a,t)=>{const s=document.getElementById(a);if(!s)throw new Error(`${t} not found: ${a}`);return s},B=(a,t)=>typeof a=="string"?Z(a,t):a,J=5,z=.72,tt=.28,et=7,N=.85,st=1-N,it=55,rt=300,nt=1.25,at=.35,F={warpAngleDeg:60,edgePower:4.8},ot=a=>({warpAngleDeg:_(a?.warpAngleDeg,F.warpAngleDeg),edgePower:_(a?.edgePower,F.edgePower)});class lt{canvas;container;opts;visibleSlots=3;heightScale=2/3;centerIndex=1;reels;spinConfig;spinBlur;webglShading;idleBob;maxDpr;spriteImg=null;spriteWidth=0;spriteHeight=0;frameHeight=0;width=0;height=0;dpr=1;offsets;velocities;lastVelT=null;lastVelOffsets;idleStartTime=null;idleBaseOffsets;gl=null;glProgram=null;glBuffer=null;glTexture=null;glAttribs=null;webglInitError=null;glUniforms=null;anim=null;rafId=null;pendingSpinResolvers=[];resizeObserver=null;resizeQueued=!1;button=null;buttonHandlerAttached=!1;spinning=!1;queuedSpins=null;constructor(t){this.opts=t,this.reels=C(t.reels??1,1,J),this.spinConfig=X(t.spinConfig),this.spinBlur=Q(t.spinBlur),this.webglShading=ot(t.webglShading),this.idleBob=K(t.idleBob),this.maxDpr=j(t.maxDpr),this.canvas=B(t.canvas,"Canvas"),this.container=B(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)}async init(){if(await this.loadSprite(),this.tryInitWebGL(),!this.gl)throw new Error(`WebGL is not supported or failed to initialize. ${this.webglInitError??""}`.trim());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()}destroy(){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()}ensureCanvasCoversContainer(){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"))}syncContainerLayoutVars(){const t=this.canvas===this.container?this.canvas:this.container;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")}setSegments(t){this.ensureReady();const s=t??[];for(let i=0;i<this.reels;i+=1){const e=s[i]??0,r=C(e,0,this.opts.slotCount-1);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]}this.lastVelT=null,this.idleStartTime=null,this.anim=null,this.stop(),this.render(),this.startLoop()}setSegment(t){this.setSegments(new Array(this.reels).fill(t))}spinOnce(t,s){this.ensureReady();const{minSpins:i,staggerMs:e,speedPxS:r,durationMs:l}=this.getSpinConfig(s),u=r>0,f=this.getStopSpringOvershootPx(),c=Math.max(0,rt),n=c>0&&f>0,h=new Array(this.reels),d=new Array(this.reels),S=new Array(this.reels),p=new Array(this.reels),b=new Array(this.reels);for(let o=0;o<this.reels;o+=1){const ht=C(t[o]??0,0,this.opts.slotCount-1)*this.frameHeight-this.centerIndex*this.frameHeight,ut=I(ht,this.spriteHeight),ft=I(this.offsets[o],this.spriteHeight),U=I(ft-ut,this.spriteHeight);let E=-(i*this.spriteHeight+U);if(u&&l>0){const D=r*l/1e3,M=Math.max(i,Math.ceil(Math.max(0,D-U)/this.spriteHeight));E=-(U+M*this.spriteHeight)}if(e>0&&u){const D=r*e*o/1e3,M=Math.ceil(D/this.spriteHeight);E-=M*this.spriteHeight}h[o]=this.offsets[o],b[o]=this.offsets[o]+E;let W=E,Y=0;if(n&&E!==0&&f>0){const M=Math.sign(E)*f;W+=M,Y=c}d[o]=W,p[o]=Y,u?S[o]=Math.max(0,Math.round(Math.abs(W)/r*1e3)):S[o]=Math.max(0,l+e*o)}const P=d.reduce((o,y)=>o+Math.abs(y),0),T=S.every(o=>o<=0),V=p.every(o=>o<=0);if(P===0||T&&V){for(let o=0;o<this.reels;o+=1)this.offsets[o]=b[o];return this.anim=null,this.resolvePendingSpins(),this.render(),Promise.resolve()}const O=performance.now();return this.anim={startTime:O,startOffsets:h,deltas:d,durationMs:S,settleDurationMs:p,targets:b},this.idleStartTime=null,this.startLoop(),new Promise(o=>{this.pendingSpinResolvers.push(o)})}getSpinConfig(t){return t?X({...this.spinConfig,...t}):this.spinConfig}getStopSpringOvershootPx(){const t=this.frameHeight*at,s=Math.max(0,it);return Math.min(s,t)}getStopSpringOffset(t,s){if(t===0)return 0;const i=Math.exp(-5*s),e=Math.PI*2*nt*s;return t*i*Math.cos(e)}async spinQueue(t){for(let s=0;s<t.length;s+=1){const i=t[s];await this.spinOnce(i.stopAtSegments),i.callback?.(s,i.stopAtSegments)}}stop(){this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=null,this.anim=null,this.resolvePendingSpins(),this.lastVelT=null;for(let t=0;t<this.reels;t+=1)this.velocities[t]=0}resolvePendingSpins(){if(this.pendingSpinResolvers.length===0)return;const t=this.pendingSpinResolvers;this.pendingSpinResolvers=[];for(const s of t)s()}requestResize(){this.queueResize()}ensureReady(){if(!this.spriteImg||this.spriteWidth<=0||this.spriteHeight<=0||this.frameHeight<=0)throw new Error("Sprite is not ready. Call init() and await it first.")}attachButtonHandler(){!this.button||this.buttonHandlerAttached||(this.button.addEventListener("click",this.handleButtonClick,{passive:!0}),this.buttonHandlerAttached=!0)}handleButtonClick=async()=>{if(this.spinning)return;const t=this.consumeNextSpin()??{stopAtSegments:this.buildRandomSegments()};this.spinning=!0,this.setButtonDisabled(!0);try{await this.spinOnce(t.stopAtSegments)}finally{this.queuedSpins&&this.queuedSpins.length===0?this.setButtonDisabled(!0):this.setButtonDisabled(!1),this.spinning=!1}};resolveButton(){return this.opts.button?B(this.opts.button,"Button"):null}resolveSpinQueue(){return this.opts.queuedSpinStates??null}consumeNextSpin(){return this.queuedSpins||(this.queuedSpins=this.resolveSpinQueue()),!this.queuedSpins||this.queuedSpins.length===0?null:this.queuedSpins.shift()??null}buildRandomSegments(){const t=Math.max(1,Math.floor(this.opts.slotCount)),s=[];for(let i=0;i<this.reels;i+=1)s.push(Math.floor(Math.random()*t));return s}setButtonDisabled(t){this.button&&(this.button.disabled=t,t?this.button.setAttribute("aria-busy","true"):this.button.removeAttribute("aria-busy"))}shouldAnimate(){return this.anim!==null||this.idleBob!==null}startLoop(){this.rafId===null&&this.shouldAnimate()&&(this.rafId=requestAnimationFrame(this.loop))}loop=t=>{if(this.anim){const{startTime:s,durationMs:i,startOffsets:e,deltas:r,settleDurationMs:l,targets:u}=this.anim,f=t-s;let c=!0;for(let n=0;n<this.reels;n+=1){const h=i[n]??0,d=l[n]??0,S=u[n]??e[n]+r[n];if(h>0&&f<h){const p=Math.min(1,f/h),b=q(p);p<1&&(c=!1);const P=e[n]+r[n]*b;let T=0;if(p>z&&this.frameHeight>0){const O=(1-v((p-z)/tt))**2,o=P/this.frameHeight*Math.PI*2,y=Math.min(et,this.frameHeight*.06);T=Math.sin(o)*O*y}this.offsets[n]=P+T}else if(d>0){const p=h<=0?f/d:(f-h)/d,b=v(p),P=e[n]+r[n]-S,T=this.getStopSpringOffset(P,b);b<1&&(c=!1),this.offsets[n]=S+T}else this.offsets[n]=S}if(this.updateVelocity(t),this.render(),c){for(let n=0;n<this.reels;n+=1){const h=this.anim.targets[n]??this.anim.startOffsets[n]+this.anim.deltas[n];this.offsets[n]=h,this.idleBaseOffsets[n]=this.offsets[n]}this.updateVelocity(t);for(let n=0;n<this.reels;n+=1)this.velocities[n]=0;this.anim=null,this.idleStartTime=null,this.render(),this.resolvePendingSpins()}}else this.idleBob&&(this.updateIdle(t),this.render());if(!this.shouldAnimate()){this.rafId=null;return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.loop))};render(){this.spriteImg&&(this.width<=0||this.height<=0||this.gl&&this.renderWebGL())}getSpinBlurPxForReel(t){if(!this.anim)return 0;const s=Math.abs(this.velocities[t]??0),i=Math.max(1,this.spinBlur.speedAtMax);return v(s/i)*Math.max(0,this.spinBlur.maxPx)}renderWebGL(){if(!this.gl||!this.glProgram||!this.glAttribs||!this.glUniforms||!this.glTexture||!this.glBuffer||!this.spriteImg)return;const t=this.gl;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(.5,this.webglShading.edgePower));const s=this.webglShading,i=Math.max(0,s.warpAngleDeg)*Math.PI/180;t.uniform1f(this.glUniforms.warpAngle,i),t.clear(t.COLOR_BUFFER_BIT);const e=1/this.reels;for(let r=0;r<this.reels;r+=1){t.uniform1f(this.glUniforms.reelX,r*e),t.uniform1f(this.glUniforms.reelW,e);const l=I(this.offsets[r],this.spriteHeight);t.uniform1f(this.glUniforms.offsetPx,l),t.uniform1f(this.glUniforms.blurPx,this.getSpinBlurPxForReel(r)),t.drawArrays(t.TRIANGLE_STRIP,0,4)}}updateVelocity(t){if(this.lastVelT===null){this.lastVelT=t;for(let i=0;i<this.reels;i+=1)this.lastVelOffsets[i]=this.offsets[i],this.velocities[i]=0;return}const s=(t-this.lastVelT)/1e3;if(!(s<=0)){for(let i=0;i<this.reels;i+=1){const e=(this.offsets[i]-this.lastVelOffsets[i])/s;this.velocities[i]=this.velocities[i]*N+e*st,this.lastVelOffsets[i]=this.offsets[i]}this.lastVelT=t}}updateIdle(t){if(!this.idleBob)return;this.idleStartTime===null&&(this.idleStartTime=t);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=G(f),n=i*Math.PI*2;for(let h=0;h<this.reels;h+=1){const d=u*n+h*e;this.offsets[h]=this.idleBaseOffsets[h]+Math.sin(d)*(s*c),this.velocities[h]=0}}async loadSprite(){const{sprite:t}=this.opts,s=typeof t=="string"?new Image:t;typeof t=="string"&&(s.src=t),(!s.complete||s.naturalWidth<=0)&&await new Promise((c,n)=>{const h=()=>c(),d=()=>n(new Error("Failed to load sprite image"));s.addEventListener("load",h,{once:!0}),s.addEventListener("error",d,{once:!0})});const i=Math.max(1,Math.floor(this.opts.slotCount)),e=s.naturalWidth,r=e,l=r*i,u=s.naturalHeight;if(Math.abs(u-l)>2)throw new Error(`Sprite size mismatch. Expected height ~${Math.round(l)}px (slotCount=${i}, slot aspect 1/1), got ${u}px.`);this.spriteImg=s,this.spriteWidth=e,this.frameHeight=Math.max(1,Math.round(r)),this.spriteHeight=Math.max(1,Math.round(this.frameHeight*i))}setupResizeWatcher(){typeof ResizeObserver>"u"||this.resizeObserver||(this.resizeObserver=new ResizeObserver(()=>this.queueResize()),this.resizeObserver.observe(this.container))}queueResize=()=>{this.resizeQueued||(this.resizeQueued=!0,requestAnimationFrame(()=>{this.resizeQueued=!1,this.resizeToContainer()}))};resizeToContainer(){const t=this.container.clientWidth,s=this.container.clientHeight,i=this.visibleSlots*this.heightScale;if(t<=0||s<=0){this.applyResize(1,1);return}const e=i,r=this.reels,l=t/r*e;let u=t,f=l;f>s&&(f=s,u=s/e*r);const c=Math.max(1,Math.floor(u)),n=Math.max(1,Math.floor(f));this.applyResize(c,n),this.render()}applyViewportCrop(){this.ensureCanvasCoversContainer(),this.canvas.style.transform=""}applyResize(t,s){const i=Math.max(1,Math.floor(t)),e=Math.max(1,Math.floor(s)),r=this.getTargetDpr();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()}getTargetDpr(){const t=window.devicePixelRatio||1,s=Math.max(1,t);return Math.min(this.maxDpr,s)}tryInitWebGL(){if(!this.spriteImg)return;this.releaseWebGLResources(),this.webglInitError=null;const t=this.canvas.getContext("webgl2",{antialias:!0,alpha:!0})??this.canvas.getContext("webgl",{antialias:!0,alpha:!0})??this.canvas.getContext("experimental-webgl",{antialias:!0,alpha:!0});if(!t){const n=document.createElement("canvas"),h=typeof WebGLRenderingContext<"u",d=n.getContext("webgl2")??n.getContext("webgl");this.webglInitError=`context is null (browser WebGL=${h?"yes":"no"}, fallback canvas=${d?"yes":"no"})`,console.warn("ReelDeal WebGL:",this.webglInitError);return}const e=this.createWebGLProgram(t,`
|
|
2
2
|
attribute vec2 a_pos;
|
|
3
3
|
attribute vec2 a_uv;
|
|
4
4
|
varying vec2 v_uv;
|
|
@@ -41,17 +41,17 @@
|
|
|
41
41
|
float fullY = localY * hs + (1.0 - hs) * 0.5;
|
|
42
42
|
|
|
43
43
|
float warp = max(0.0, u_warpAngle);
|
|
44
|
-
float warpStrength = saturate(warp /
|
|
45
|
-
float
|
|
46
|
-
float centerBand = 1.0 / 3.0;
|
|
47
|
-
float halfBand = centerBand * 0.5;
|
|
48
|
-
float dist = abs(fullY - center);
|
|
49
|
-
float edgeWeight = smoothstep(halfBand, 0.5, dist);
|
|
44
|
+
float warpStrength = saturate(warp / HALF_PI);
|
|
45
|
+
float distNorm = abs(fullY - 0.5) * 2.0;
|
|
50
46
|
float edgePow = max(0.5, u_edgePower);
|
|
51
|
-
edgeWeight = pow(
|
|
52
|
-
|
|
53
|
-
float
|
|
54
|
-
float
|
|
47
|
+
float edgeWeight = pow(saturate(distNorm), edgePow) * warpStrength;
|
|
48
|
+
|
|
49
|
+
float stretchMax = 6.0;
|
|
50
|
+
float stretch = mix(1.0, stretchMax, edgeWeight);
|
|
51
|
+
float warpedY = 0.5 + (fullY - 0.5) * stretch;
|
|
52
|
+
|
|
53
|
+
float arcY = 0.5 + 0.5 * sin((warpedY - 0.5) * HALF_PI);
|
|
54
|
+
warpedY = mix(warpedY, arcY, 0.2);
|
|
55
55
|
|
|
56
56
|
float yPx = u_offsetPx + warpedY * (u_frameHeight * u_visibleSlots);
|
|
57
57
|
float v = fract((u_offsetPx / u_spriteHeight) +
|