cascading-reel 0.0.12 → 0.0.13

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/dist/index.es.js CHANGED
@@ -1,66 +1,55 @@
1
- const Q = [0.04, 0, -0.04], V = 34, Z = 0.05, K = 5e3, H = 1800, j = 0.1, M = 720, b = 34, x = 20, v = 12, J = [255, 235, 110], tt = [
2
- 255,
3
- 255,
4
- 255,
5
- 0.78
6
- ], it = 190, I = 650, et = 220, nt = 200;
7
- function _(t, i, e) {
8
- return t < i ? i : t > e ? e : t;
1
+ const q = [0.04, 0, -0.04], y = 34, X = 0.05, k = 5e3, W = 1800, Y = 0.1, T = 720, G = 34, z = [255, 235, 110], $ = 190, L = 520, K = 200;
2
+ function b(e, t, i) {
3
+ return e < t ? t : e > i ? i : e;
9
4
  }
10
- function D(t) {
11
- return 1 - (1 - t) ** 3;
5
+ function Q(e) {
6
+ return Math.floor(Math.random() * e);
12
7
  }
13
- function st(t) {
14
- return Math.floor(Math.random() * t);
8
+ function C(e, t) {
9
+ return (e % t + t) % t;
15
10
  }
16
- function A(t, i) {
17
- return (t % i + i) % i;
11
+ function M(e) {
12
+ return b(Math.round(e), 0, 255);
18
13
  }
19
- function O(t) {
20
- return _(Math.round(t), 0, 255);
21
- }
22
- function lt(t) {
23
- return _(t, 0, 1);
24
- }
25
- function y(t) {
26
- const i = [];
27
- for (let e = 0; e < 3; e += 1) {
28
- const n = [];
29
- for (let l = 0; l < 3; l += 1)
30
- n.push(st(t));
31
- i.push(n);
14
+ function x(e) {
15
+ const t = [];
16
+ for (let i = 0; i < 3; i += 1) {
17
+ const s = [];
18
+ for (let r = 0; r < 3; r += 1)
19
+ s.push(Q(e));
20
+ t.push(s);
32
21
  }
33
- return i;
22
+ return t;
34
23
  }
35
- function T(t) {
36
- const i = /* @__PURE__ */ new Map();
37
- for (let s = 0; s < 3; s += 1)
38
- for (let r = 0; r < 3; r += 1) {
39
- const u = t[s][r];
40
- i.set(u, (i.get(u) ?? 0) + 1);
24
+ function F(e) {
25
+ const t = /* @__PURE__ */ new Map();
26
+ for (let n = 0; n < 3; n += 1)
27
+ for (let o = 0; o < 3; o += 1) {
28
+ const h = e[n][o];
29
+ t.set(h, (t.get(h) ?? 0) + 1);
41
30
  }
42
- let e = t[0][0], n = -1;
43
- for (const [s, r] of i.entries())
44
- r > n && (n = r, e = s);
45
- const l = [];
46
- for (let s = 0; s < 3; s += 1)
47
- for (let r = 0; r < 3; r += 1)
48
- t[s][r] === e && l.push({ col: s, row: r });
49
- return l;
50
- }
51
- function N() {
31
+ let i = e[0][0], s = -1;
32
+ for (const [n, o] of t.entries())
33
+ o > s && (s = o, i = n);
34
+ const r = [];
35
+ for (let n = 0; n < 3; n += 1)
36
+ for (let o = 0; o < 3; o += 1)
37
+ e[n][o] === i && r.push({ col: n, row: o });
38
+ return r;
39
+ }
40
+ function H() {
52
41
  return Array.from({ length: 3 }, () => Array.from({ length: 3 }, () => 0));
53
42
  }
54
- function W(t, i) {
55
- for (let e = 0; e < 3; e += 1)
56
- for (let n = 0; n < 3; n += 1)
57
- t[e][n] = i;
43
+ function I(e, t) {
44
+ for (let i = 0; i < 3; i += 1)
45
+ for (let s = 0; s < 3; s += 1)
46
+ e[i][s] = t;
58
47
  }
59
- class rt {
48
+ class V {
60
49
  rafId = null;
61
50
  step = null;
62
- start(i) {
63
- this.rafId === null && (this.step = i, this.rafId = requestAnimationFrame(this.tick));
51
+ start(t) {
52
+ this.rafId === null && (this.step = t, this.rafId = requestAnimationFrame(this.tick));
64
53
  }
65
54
  stop() {
66
55
  this.rafId !== null && (cancelAnimationFrame(this.rafId), this.rafId = null), this.step = null;
@@ -68,134 +57,133 @@ class rt {
68
57
  isRunning() {
69
58
  return this.rafId !== null;
70
59
  }
71
- tick = (i) => {
60
+ tick = (t) => {
72
61
  if (!this.step) {
73
62
  this.stop();
74
63
  return;
75
64
  }
76
- if (this.step(i) === !1) {
65
+ if (this.step(t) === !1) {
77
66
  this.stop();
78
67
  return;
79
68
  }
80
69
  this.rafId !== null && (this.rafId = requestAnimationFrame(this.tick));
81
70
  };
82
71
  }
83
- function ot(t, i, e) {
84
- const n = [0, 0, 0];
85
- let l = 0;
86
- for (let s = 2; s >= 0; s -= 1) {
87
- if (t[s] === 0) {
88
- n[s] = 0;
72
+ function D(e) {
73
+ const t = b(e, 0, 1);
74
+ return t * t * (3 - 2 * t);
75
+ }
76
+ function Z(e, t, i) {
77
+ const s = [0, 0, 0];
78
+ let r = 0;
79
+ for (let n = 2; n >= 0; n -= 1) {
80
+ if (e[n] === 0) {
81
+ s[n] = 0;
89
82
  continue;
90
83
  }
91
- n[s] = l;
92
- const r = Math.floor(i * Z);
93
- l += r + e;
84
+ s[n] = r;
85
+ const o = Math.floor(t * X);
86
+ r += o + i;
94
87
  }
95
- return n;
88
+ return s;
89
+ }
90
+ function N(e) {
91
+ const i = e.height - e.boardY + e.cellH + 2, s = [
92
+ i,
93
+ i,
94
+ i
95
+ ];
96
+ return {
97
+ outgoingDistance: i,
98
+ incomingFromOffsets: [-e.cellH, -e.cellH * 2, -e.cellH * 3],
99
+ rowStartDelays: Z(
100
+ s,
101
+ L,
102
+ y
103
+ ),
104
+ // Start incoming symbols only after outgoing ones fully complete to avoid
105
+ // perceived jitter from overlapping opposite-direction visual motion.
106
+ incomingStartShift: L
107
+ };
96
108
  }
97
- function ht(t) {
98
- let i = !0, e = !0;
99
- const l = t.height - t.boardY + t.cellH + 2, s = [
100
- l,
101
- l,
102
- l
103
- ], r = [
104
- -t.cellH,
105
- -t.cellH * 2,
106
- -t.cellH * 3
107
- ], u = ot(
108
- s,
109
- I,
110
- V
111
- ), o = I - et;
112
- for (let h = 0; h < t.scriptedOutgoingOffsets.length; h += 1) {
113
- const a = t.now - (t.scriptedOutroStartedAt + h * it);
114
- for (let c = 0; c < 3; c += 1) {
115
- const d = a - u[c];
116
- if (d <= 0) {
117
- t.scriptedOutgoingOffsets[h][c] = 0, t.scriptedIncomingOffsets[h][c] = Number.NaN, i = !1, e = !1;
109
+ function j(e) {
110
+ let t = !0, i = !0;
111
+ for (let s = 0; s < e.scriptedOutgoingOffsets.length; s += 1) {
112
+ const r = e.now - (e.scriptedOutroStartedAt + s * $);
113
+ for (let n = 0; n < 3; n += 1) {
114
+ const o = r - e.motionPlan.rowStartDelays[n];
115
+ if (o <= 0) {
116
+ e.scriptedOutgoingOffsets[s][n] = 0, e.scriptedIncomingOffsets[s][n] = Number.NaN, t = !1, i = !1;
118
117
  continue;
119
118
  }
120
- const f = _(d / I, 0, 1), g = D(f);
121
- t.scriptedOutgoingOffsets[h][c] = l * g, f < 1 && (i = !1);
122
- const p = d - o;
123
- if (p <= 0) {
124
- t.scriptedIncomingOffsets[h][c] = Number.NaN, e = !1;
119
+ const h = b(o / L, 0, 1), l = D(h);
120
+ e.scriptedOutgoingOffsets[s][n] = e.motionPlan.outgoingDistance * l, h < 1 && (t = !1);
121
+ const c = o - e.motionPlan.incomingStartShift;
122
+ if (c <= 0) {
123
+ e.scriptedIncomingOffsets[s][n] = Number.NaN, i = !1;
125
124
  continue;
126
125
  }
127
- const S = _(p / I, 0, 1), w = D(S);
128
- t.scriptedIncomingOffsets[h][c] = r[c] * (1 - w), S < 1 && (e = !1);
126
+ const a = b(c / L, 0, 1), d = D(a);
127
+ e.scriptedIncomingOffsets[s][n] = e.motionPlan.incomingFromOffsets[n] * (1 - d), a < 1 && (i = !1);
129
128
  }
130
129
  }
131
- return { allOutgoingDone: i, allIncomingDone: e };
132
- }
133
- function ct(t) {
134
- return t ? [
135
- O(t[0]),
136
- O(t[1]),
137
- O(t[2])
138
- ] : J;
130
+ return { allOutgoingDone: t, allIncomingDone: i };
139
131
  }
140
- function ut(t) {
141
- return t === "rainbow" ? "rainbow" : "solid";
132
+ function J(e) {
133
+ return e ? [
134
+ M(e[0]),
135
+ M(e[1]),
136
+ M(e[2])
137
+ ] : z;
142
138
  }
143
- function at(t) {
144
- return t === "high" || t === "balanced" || t === "low" || t === "auto" ? t : "auto";
139
+ function tt(e) {
140
+ return e === "rainbow" ? "rainbow" : "solid";
145
141
  }
146
- function dt(t) {
147
- return t ? [
148
- O(t[0]),
149
- O(t[1]),
150
- O(t[2]),
151
- lt(t[3])
152
- ] : tt;
153
- }
154
- function G(t) {
155
- if (t.length !== 3)
142
+ function P(e) {
143
+ if (e.length !== 3)
156
144
  throw new Error("rows must contain 3 rows");
157
- for (let i = 0; i < 3; i += 1)
158
- if (!Array.isArray(t[i]) || t[i].length !== 3)
159
- throw new Error(`rows[${i}] must contain 3 columns`);
145
+ for (let t = 0; t < 3; t += 1)
146
+ if (!Array.isArray(e[t]) || e[t].length !== 3)
147
+ throw new Error(`rows[${t}] must contain 3 columns`);
160
148
  return [
161
- [t[0][0], t[1][0], t[2][0]],
162
- [t[0][1], t[1][1], t[2][1]],
163
- [t[0][2], t[1][2], t[2][2]]
149
+ [e[0][0], e[1][0], e[2][0]],
150
+ [e[0][1], e[1][1], e[2][1]],
151
+ [e[0][2], e[1][2], e[2][2]]
164
152
  ];
165
153
  }
166
- function P(t, i) {
167
- if (t.length !== 3)
154
+ function v(e, t) {
155
+ if (e.length !== 3)
168
156
  throw new Error("stopGrid must contain 3 columns");
169
- const e = [];
170
- for (let n = 0; n < 3; n += 1) {
171
- const l = t[n];
172
- if (!Array.isArray(l) || l.length !== 3)
173
- throw new Error(`stopGrid[${n}] must contain 3 rows`);
174
- e[n] = [
175
- A(l[0], i),
176
- A(l[1], i),
177
- A(l[2], i)
157
+ const i = [];
158
+ for (let s = 0; s < 3; s += 1) {
159
+ const r = e[s];
160
+ if (!Array.isArray(r) || r.length !== 3)
161
+ throw new Error(`stopGrid[${s}] must contain 3 rows`);
162
+ i[s] = [
163
+ C(r[0], t),
164
+ C(r[1], t),
165
+ C(r[2], t)
178
166
  ];
179
167
  }
180
- return e;
168
+ return i;
181
169
  }
182
- function ft(t, i) {
183
- return P(G(t), i);
170
+ function it(e, t) {
171
+ return v(P(e), t);
184
172
  }
185
- function gt(t) {
173
+ function et(e) {
186
174
  return {
187
- stopGrid: t.stopGrid?.map((i) => [...i]),
188
- stopRows: t.stopRows?.map((i) => [...i]),
189
- finaleSequence: t.finaleSequence?.map((i) => i.map((e) => [...e])),
190
- finaleSequenceRows: t.finaleSequenceRows?.map((i) => i.map((e) => [...e])),
191
- highlightWin: t.highlightWin,
192
- callback: t.callback
175
+ stopGrid: e.stopGrid?.map((t) => [...t]),
176
+ stopRows: e.stopRows?.map((t) => [...t]),
177
+ finaleSequence: e.finaleSequence?.map((t) => t.map((i) => [...i])),
178
+ finaleSequenceRows: e.finaleSequenceRows?.map((t) => t.map((i) => [...i])),
179
+ highlightWin: e.highlightWin,
180
+ callback: e.callback
193
181
  };
194
182
  }
195
- class St {
183
+ class st {
196
184
  queue;
197
- constructor(i) {
198
- this.queue = (i ?? []).map((e) => gt(e));
185
+ constructor(t) {
186
+ this.queue = (t ?? []).map((i) => et(i));
199
187
  }
200
188
  hasPending() {
201
189
  return this.queue.length > 0;
@@ -204,7 +192,7 @@ class St {
204
192
  return this.queue.length === 0 ? null : this.queue.shift() ?? null;
205
193
  }
206
194
  }
207
- function pt() {
195
+ function nt() {
208
196
  return {
209
197
  isSpinning: !1,
210
198
  hasStartedFirstSpin: !1,
@@ -216,151 +204,200 @@ function pt() {
216
204
  outroStartedAt: 0,
217
205
  idleStartedAt: 0,
218
206
  preSpinStartedAt: 0,
219
- swayEnvelope: 1,
220
207
  winEffectsEnvelope: 1
221
208
  };
222
209
  }
223
- function Ct(t, i) {
224
- t.hasStartedFirstSpin = !0, t.isSpinning = !0, t.phase = "outro", t.outroStartedAt = i.startedAt, t.activeSpinState = i.activeSpinState, t.shouldHighlightCurrentSpin = i.shouldHighlightCurrentSpin;
225
- }
226
- function wt(t, i, e) {
227
- t.phase = "idle", t.idleStartedAt = e, t.isSpinning = !1, t.shouldHighlightCurrentSpin = !1, t.queueFinished = !i, t.activeSpinState = null;
228
- }
229
- function q(t, i) {
230
- t.winFlashStartedAt = i, t.phase = "winFlash";
231
- }
232
- function Ot(t) {
233
- t.isSpinning = !1, t.queueFinished = !0;
234
- }
235
- const Rt = 3, _t = 2e-3;
236
- function F(t, i) {
237
- return (Q[t] ?? 0) * i;
238
- }
239
- function Et(t) {
240
- const i = t.columnSway ?? [0, 0, 0], e = Rt * (Math.PI / 180), n = Math.max(0, Math.min(1, t.swayEnvelope)), l = t.skipWinningCells ?? !1;
241
- for (let s = 0; s < 3; s += 1) {
242
- const r = t.boardX + s * t.cellW, u = i[s] ?? 0;
243
- for (let o = 0; o < 3; o += 1) {
244
- if ((l || t.phase === "winFlash" || t.phase === "preSpin") && t.isWinningCell(s, o))
245
- continue;
246
- const h = t.offsets ? t.offsets[s][o] : 0;
247
- if (!Number.isFinite(h)) continue;
248
- const a = t.boardY + o * t.cellH + h + u + F(o, t.cellH);
249
- if (a > t.height || a + t.cellH < 0) continue;
250
- const d = Math.sin(t.now * _t + s * 1.1 + o * 1.3) * e * n, f = r + t.cellW * 0.5, g = a + t.cellH * 0.5;
251
- t.ctx.save(), t.ctx.translate(f, g), t.ctx.rotate(d), t.ctx.translate(-t.cellW * 0.5, -t.cellH * 0.5), t.drawSpriteCell(t.grid[s][o], 0, 0, t.cellW, t.cellH), t.ctx.restore();
210
+ function rt(e, t) {
211
+ e.hasStartedFirstSpin = !0, e.isSpinning = !0, e.phase = "outro", e.outroStartedAt = t.startedAt, e.activeSpinState = t.activeSpinState, e.shouldHighlightCurrentSpin = t.shouldHighlightCurrentSpin;
212
+ }
213
+ function ot(e, t, i) {
214
+ e.phase = "idle", e.idleStartedAt = i, e.isSpinning = !1, e.shouldHighlightCurrentSpin = !1, e.queueFinished = !t, e.activeSpinState = null;
215
+ }
216
+ function U(e, t) {
217
+ e.winFlashStartedAt = t, e.phase = "winFlash";
218
+ }
219
+ function ht(e) {
220
+ e.isSpinning = !1, e.queueFinished = !0;
221
+ }
222
+ const lt = `
223
+ attribute vec2 a_pos;
224
+ uniform vec2 u_resolution;
225
+ uniform vec4 u_destRect;
226
+ varying vec2 v_uv;
227
+
228
+ void main() {
229
+ vec2 local = a_pos;
230
+ vec2 pixel = vec2(
231
+ u_destRect.x + local.x * u_destRect.z,
232
+ u_destRect.y + local.y * u_destRect.w
233
+ );
234
+
235
+ vec2 zeroToOne = pixel / u_resolution;
236
+ vec2 clip = vec2(
237
+ zeroToOne.x * 2.0 - 1.0,
238
+ 1.0 - zeroToOne.y * 2.0
239
+ );
240
+
241
+ gl_Position = vec4(clip, 0.0, 1.0);
242
+ v_uv = local;
243
+ }
244
+ `, at = `
245
+ precision mediump float;
246
+
247
+ uniform sampler2D u_texture;
248
+ uniform vec4 u_srcRect;
249
+ uniform vec4 u_color;
250
+ uniform float u_useTexture;
251
+ uniform float u_shapeMode;
252
+ varying vec2 v_uv;
253
+
254
+ void main() {
255
+ if (u_shapeMode > 0.5) {
256
+ vec2 centered = v_uv - vec2(0.5, 0.5);
257
+ float dist = length(centered) * 2.0;
258
+ if (dist > 1.0) {
259
+ discard;
252
260
  }
261
+ float feather = smoothstep(1.0, 0.72, dist);
262
+ gl_FragColor = vec4(u_color.rgb, u_color.a * feather);
263
+ } else if (u_useTexture > 0.5) {
264
+ vec2 uv = vec2(
265
+ mix(u_srcRect.x, u_srcRect.z, v_uv.x),
266
+ mix(u_srcRect.y, u_srcRect.w, v_uv.y)
267
+ );
268
+ vec4 tex = texture2D(u_texture, uv);
269
+ gl_FragColor = tex * u_color;
270
+ } else {
271
+ gl_FragColor = u_color;
253
272
  }
254
273
  }
255
- function R(t, i, e, n) {
256
- const l = Math.sin(t * 127.1 + i * 311.7 + e * 74.7 + n * 19.3) * 43758.5453;
257
- return l - Math.floor(l);
258
- }
259
- const Y = /* @__PURE__ */ new Map();
260
- function It(t, i) {
261
- const e = `${t},${i}`;
262
- let n = Y.get(e);
263
- if (n) return n;
264
- n = [];
265
- for (let l = 0; l < b; l += 1)
266
- n.push({
267
- seedA: R(t, i, l, 1),
268
- seedB: R(t, i, l, 2),
269
- seedC: R(t, i, l, 3),
270
- phaseOffset: R(t, i, l, 4),
271
- twinkleSeed: R(t, i, l, 5)
272
- });
273
- return Y.set(e, n), n;
274
- }
275
- function U(t) {
276
- if (t.winningCells.length === 0 || t.phase !== "winFlash" && t.phase !== "preSpin") return;
277
- const i = Math.max(0, Math.min(1, t.winEffectsEnvelope)), e = Math.max(0, t.now - t.winFlashStartedAt), n = e % H / H, l = 1 + Math.sin(n * Math.PI * 2) * j;
278
- for (const s of t.winningCells) {
279
- const r = t.boardX + s.col * t.cellW, u = t.boardY + s.row * t.cellH + F(s.row, t.cellH);
280
- Wt(
281
- t.ctx,
282
- r,
283
- u,
284
- t.cellW,
285
- t.cellH,
286
- s.col,
287
- s.row,
288
- t.winningCellBorderRgba,
289
- e,
290
- i
274
+ `;
275
+ class ut {
276
+ canvas;
277
+ spriteImage;
278
+ spriteElementsCount;
279
+ gl;
280
+ program;
281
+ uniforms;
282
+ quadBuffer;
283
+ texture;
284
+ viewportW = 1;
285
+ viewportH = 1;
286
+ spriteWidth;
287
+ spriteHeight;
288
+ spriteSegmentHeight;
289
+ constructor(t) {
290
+ this.canvas = t.canvas, this.spriteImage = t.spriteImage, this.spriteElementsCount = Math.max(1, t.spriteElementsCount), this.spriteWidth = this.spriteImage.width, this.spriteHeight = this.spriteImage.height, this.spriteSegmentHeight = this.spriteHeight / this.spriteElementsCount;
291
+ const i = this.canvas.getContext("webgl2", {
292
+ alpha: !0,
293
+ antialias: !1
294
+ }) ?? this.canvas.getContext("webgl", { alpha: !0, antialias: !1 });
295
+ if (!i)
296
+ throw new Error("WebGL context is not available");
297
+ this.gl = i;
298
+ const s = this.createShader(this.gl.VERTEX_SHADER, lt), r = this.createShader(this.gl.FRAGMENT_SHADER, at);
299
+ this.program = this.createProgram(s, r), this.gl.deleteShader(s), this.gl.deleteShader(r);
300
+ const n = this.gl.createBuffer();
301
+ if (!n)
302
+ throw new Error("Failed to create WebGL quad buffer");
303
+ this.quadBuffer = n, this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadBuffer), this.gl.bufferData(
304
+ this.gl.ARRAY_BUFFER,
305
+ new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]),
306
+ this.gl.STATIC_DRAW
291
307
  );
292
- const o = t.getCell(s.col, s.row), h = 1 + (l - 1) * i, a = t.cellW * h, c = t.cellH * h, d = (t.cellW - a) * 0.5, f = (t.cellH - c) * 0.5;
293
- t.ctx.save(), t.drawSpriteCell(o, r + d, u + f, a, c), t.ctx.restore();
294
- }
295
- if (!(t.particlesPerCell <= 0)) {
296
- t.ctx.save(), t.ctx.beginPath(), t.ctx.rect(t.boardX, t.boardY, t.cellW * 3, t.cellH * 3), t.ctx.clip();
297
- for (const s of t.winningCells) {
298
- const r = t.boardX + s.col * t.cellW, u = t.boardY + s.row * t.cellH + F(s.row, t.cellH);
299
- bt({
300
- ctx: t.ctx,
301
- cell: s,
302
- x: r,
303
- y: u,
304
- elapsed: e,
305
- envelope: i,
306
- cellW: t.cellW,
307
- cellH: t.cellH,
308
- particlesPerCell: t.particlesPerCell,
309
- particleColorMode: t.particleColorMode,
310
- particleColorRgb: t.particleColorRgb
311
- });
308
+ const o = this.gl.createTexture();
309
+ if (!o)
310
+ throw new Error("Failed to create WebGL texture");
311
+ this.texture = o, this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture), this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, 0), this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1), this.gl.texImage2D(
312
+ this.gl.TEXTURE_2D,
313
+ 0,
314
+ this.gl.RGBA,
315
+ this.gl.RGBA,
316
+ this.gl.UNSIGNED_BYTE,
317
+ this.spriteImage
318
+ ), this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE), this.gl.useProgram(this.program);
319
+ const h = this.gl.getAttribLocation(this.program, "a_pos");
320
+ this.gl.enableVertexAttribArray(h), this.gl.vertexAttribPointer(h, 2, this.gl.FLOAT, !1, 8, 0), this.uniforms = {
321
+ resolution: this.gl.getUniformLocation(this.program, "u_resolution"),
322
+ destRect: this.gl.getUniformLocation(this.program, "u_destRect"),
323
+ srcRect: this.gl.getUniformLocation(this.program, "u_srcRect"),
324
+ color: this.gl.getUniformLocation(this.program, "u_color"),
325
+ useTexture: this.gl.getUniformLocation(this.program, "u_useTexture"),
326
+ shapeMode: this.gl.getUniformLocation(this.program, "u_shapeMode"),
327
+ texture: this.gl.getUniformLocation(this.program, "u_texture")
328
+ }, this.gl.uniform1i(this.uniforms.texture, 0), this.gl.clearColor(0, 0, 0, 0), this.gl.enable(this.gl.BLEND), this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
329
+ }
330
+ resize(t, i) {
331
+ this.viewportW = Math.max(1, Math.floor(t)), this.viewportH = Math.max(1, Math.floor(i)), this.gl.viewport(0, 0, this.viewportW, this.viewportH);
332
+ }
333
+ beginFrame() {
334
+ this.gl.useProgram(this.program), this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadBuffer), this.gl.activeTexture(this.gl.TEXTURE0), this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture), this.gl.uniform2f(this.uniforms.resolution, this.viewportW, this.viewportH), this.gl.uniform1f(this.uniforms.shapeMode, 0), this.gl.clear(this.gl.COLOR_BUFFER_BIT);
335
+ }
336
+ drawSprite(t, i, s, r, n, o = 1) {
337
+ const l = C(t, this.spriteElementsCount) * this.spriteSegmentHeight, c = l + this.spriteSegmentHeight, a = 0.5, d = a / this.spriteWidth, g = 1 - a / this.spriteWidth, p = 1 - (c - a) / this.spriteHeight, S = 1 - (l + a) / this.spriteHeight;
338
+ this.gl.uniform4f(this.uniforms.destRect, i, s, r, n), this.gl.uniform4f(this.uniforms.srcRect, d, p, g, S), this.gl.uniform4f(this.uniforms.color, 1, 1, 1, o), this.gl.uniform1f(this.uniforms.shapeMode, 0), this.gl.uniform1f(this.uniforms.useTexture, 1), this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
339
+ }
340
+ drawSolidRect(t, i, s, r, n) {
341
+ this.gl.uniform4f(this.uniforms.destRect, t, i, s, r), this.gl.uniform4f(this.uniforms.srcRect, 0, 0, 1, 1), this.gl.uniform4f(this.uniforms.color, n[0], n[1], n[2], n[3]), this.gl.uniform1f(this.uniforms.shapeMode, 0), this.gl.uniform1f(this.uniforms.useTexture, 0), this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
342
+ }
343
+ drawSoftCircle(t, i, s, r) {
344
+ const n = s * 2;
345
+ this.gl.uniform4f(
346
+ this.uniforms.destRect,
347
+ t - s,
348
+ i - s,
349
+ n,
350
+ n
351
+ ), this.gl.uniform4f(this.uniforms.srcRect, 0, 0, 1, 1), this.gl.uniform4f(this.uniforms.color, r[0], r[1], r[2], r[3]), this.gl.uniform1f(this.uniforms.useTexture, 0), this.gl.uniform1f(this.uniforms.shapeMode, 1), this.gl.drawArrays(this.gl.TRIANGLES, 0, 6), this.gl.uniform1f(this.uniforms.shapeMode, 0);
352
+ }
353
+ beginAdditiveBlend() {
354
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
355
+ }
356
+ endAdditiveBlend() {
357
+ this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
358
+ }
359
+ dispose() {
360
+ this.gl.deleteTexture(this.texture), this.gl.deleteBuffer(this.quadBuffer), this.gl.deleteProgram(this.program);
361
+ }
362
+ createShader(t, i) {
363
+ const s = this.gl.createShader(t);
364
+ if (!s)
365
+ throw new Error("Failed to create WebGL shader");
366
+ if (this.gl.shaderSource(s, i), this.gl.compileShader(s), !this.gl.getShaderParameter(s, this.gl.COMPILE_STATUS)) {
367
+ const r = this.gl.getShaderInfoLog(s) ?? "unknown error";
368
+ throw this.gl.deleteShader(s), new Error(`WebGL shader compile failed: ${r}`);
312
369
  }
313
- t.ctx.restore();
314
- }
315
- }
316
- function Wt(t, i, e, n, l, s, r, u, o, h) {
317
- const a = Math.max(1, Math.floor(Math.min(n, l) * 0.04)), c = Math.max(1, Math.floor(Math.min(n, l) * 0.03)), [, , , d] = u, f = (s + 0.5) / 3, g = (r + 0.5) / 3, p = (o * 0.2 + f * 80 + g * 40) % 360, S = Math.min(n, l), w = Math.max(4, Math.floor(S * 0.04)), m = Math.max(3, Math.floor(S * 0.03));
318
- t.save(), t.lineWidth = c, t.strokeStyle = `hsla(${p}, 90%, 70%, ${d * h})`, t.setLineDash([w, m]), t.lineDashOffset = -o * 0.03;
319
- const E = c * 0.5;
320
- t.strokeRect(
321
- i + a + E,
322
- e + a + E,
323
- n - a * 2 - c,
324
- l - a * 2 - c
325
- ), t.setLineDash([]), t.restore();
326
- }
327
- const k = 24;
328
- function bt(t) {
329
- const i = t.x + t.cellW * 0.5, e = t.y + t.cellH * 0.5, n = Math.min(t.cellW, t.cellH) * 0.72, l = Math.min(t.cellW, t.cellH) * 0.028, s = 0.96 * t.envelope, [r, u, o] = t.particleColorRgb, h = t.particleColorMode === "solid";
330
- h && (t.ctx.fillStyle = `rgb(${r},${u},${o})`);
331
- const a = It(t.cell.col, t.cell.row);
332
- for (let c = 0; c < t.particlesPerCell; c += 1) {
333
- const d = a[c], f = d.phaseOffset * M, g = t.elapsed - f;
334
- if (g < 0) continue;
335
- const p = g % M / M, S = d.seedA * Math.PI * 2, w = n * p * (0.35 + d.seedB * 0.65), m = i + Math.cos(S) * w, E = e + Math.sin(S) * w, B = 0.7 + 0.9 * Math.max(0, Math.sin((t.elapsed * 0.012 + d.twinkleSeed * 2) * Math.PI * 2)), z = Math.max(1, l * (0.55 + d.seedC * 0.6) * (1 - p * 0.5)), L = _((0.9 + B * 0.2) * s, 0.9, 1);
336
- if (!(L <= 0)) {
337
- if (h)
338
- t.ctx.globalAlpha = L;
339
- else {
340
- const X = (d.seedA * 360 + t.elapsed * 0.24 + t.cell.col * 38 + t.cell.row * 22) % 360, $ = Math.floor(X / (360 / k)) * (360 / k);
341
- t.ctx.fillStyle = `hsla(${$},98%,64%,${L})`;
342
- }
343
- t.ctx.beginPath(), t.ctx.arc(m, E, z, 0, Math.PI * 2), t.ctx.fill();
370
+ return s;
371
+ }
372
+ createProgram(t, i) {
373
+ const s = this.gl.createProgram();
374
+ if (!s)
375
+ throw new Error("Failed to create WebGL program");
376
+ if (this.gl.attachShader(s, t), this.gl.attachShader(s, i), this.gl.linkProgram(s), !this.gl.getProgramParameter(s, this.gl.LINK_STATUS)) {
377
+ const r = this.gl.getProgramInfoLog(s) ?? "unknown error";
378
+ throw this.gl.deleteProgram(s), new Error(`WebGL program link failed: ${r}`);
344
379
  }
380
+ return s;
345
381
  }
346
- t.ctx.globalAlpha = 1;
347
382
  }
348
- class C {
383
+ class u {
384
+ static RAINBOW_HUE_BUCKETS = 24;
385
+ static PARTICLE_GLOBAL_ALPHA = 0.9;
386
+ static PARTICLE_MAX_DISTANCE = 0.72;
387
+ static PARTICLE_BASE_RADIUS = 0.028;
349
388
  canvas;
350
389
  container;
351
390
  button;
352
- ctx;
353
391
  spinQueueController;
354
392
  spriteUrl;
355
393
  spriteElementsCount;
356
394
  highlightInitialWinningCells;
357
395
  particleColorRgb;
358
396
  particleColorMode;
359
- winningCellBorderRgba;
360
- quality;
361
397
  spriteImage = null;
362
- rafLoop = new rt();
363
- runtime = pt();
398
+ webglRenderer = null;
399
+ rafLoop = new V();
400
+ runtime = nt();
364
401
  dpr = 1;
365
402
  width = 0;
366
403
  height = 0;
@@ -372,33 +409,42 @@ class C {
372
409
  scriptedOutgoingGrid = null;
373
410
  scriptedPendingGrid = null;
374
411
  scriptedOutroStartedAt = 0;
375
- scriptedOutgoingOffsets = N();
376
- scriptedIncomingOffsets = N();
412
+ scriptedOutroVirtualElapsedMs = 0;
413
+ scriptedOutroLastNow = 0;
414
+ outroMotionPlan = null;
415
+ scriptedOutgoingOffsets = H();
416
+ scriptedIncomingOffsets = H();
377
417
  winningCells = [];
418
+ winningCellKeys = /* @__PURE__ */ new Set();
378
419
  grid;
420
+ particlesPerCell = G;
379
421
  lastRafTime = 0;
380
- particlesPerCell = b;
381
422
  initialHighlightRequestedAt = 0;
382
- static SWAY_ENVELOPE_TAU_MS = 220;
383
423
  static PRE_SPIN_MS = 150;
384
424
  static WIN_EFFECTS_ENVELOPE_TAU_MS = 120;
385
- constructor(i) {
386
- this.canvas = i.canvas, this.container = i.container, this.button = i.button, this.spriteUrl = i.sprite, this.spriteElementsCount = Math.max(
425
+ static OUTRO_MAX_STEP_MS = 20;
426
+ static WIN_BORDER_ALPHA = 0.72;
427
+ static WIN_BORDER_INSET_RATIO = 0.06;
428
+ static particleSeedsCache = /* @__PURE__ */ new Map();
429
+ constructor(t) {
430
+ this.canvas = t.canvas, this.container = t.container, this.button = t.button, this.spriteUrl = t.sprite, this.spriteElementsCount = Math.max(
387
431
  1,
388
- i.spriteElementsCount ?? 6
389
- ), this.highlightInitialWinningCells = i.highlightInitialWinningCells !== !1, this.spinQueueController = new St(i.queuedSpinStates), this.particleColorRgb = ct(i.particleColorRgb), this.particleColorMode = ut(i.particleColorMode), this.winningCellBorderRgba = dt(i.winningCellBorderRgba), this.quality = at(i.quality);
390
- const e = this.canvas.getContext("2d");
391
- if (!e)
392
- throw new Error("2D canvas context is not available");
393
- this.ctx = e, this.grid = i.initialSegments ? ft(i.initialSegments, this.spriteElementsCount) : y(this.spriteElementsCount);
432
+ t.spriteElementsCount ?? 6
433
+ ), this.highlightInitialWinningCells = t.highlightInitialWinningCells !== !1, this.spinQueueController = new st(t.queuedSpinStates), this.particleColorRgb = J(t.particleColorRgb), this.particleColorMode = tt(t.particleColorMode), this.grid = t.initialSegments ? it(t.initialSegments, this.spriteElementsCount) : x(this.spriteElementsCount);
394
434
  }
395
435
  async init() {
396
- this.bindEvents(), this.resize(), await this.loadSpriteIfProvided(), this.applyInitialHighlightIfNeeded(), this.prewarmWinEffects(), requestAnimationFrame((i) => {
397
- this.render(i), this.startLoop();
436
+ if (this.bindEvents(), this.resize(), await this.loadSpriteIfProvided(), !this.spriteImage)
437
+ throw new Error("sprite is required for WebGL renderer");
438
+ this.webglRenderer = new ut({
439
+ canvas: this.canvas,
440
+ spriteImage: this.spriteImage,
441
+ spriteElementsCount: this.spriteElementsCount
442
+ }), this.webglRenderer.resize(this.width, this.height), this.applyInitialHighlightIfNeeded(), requestAnimationFrame((t) => {
443
+ this.render(t), this.startLoop();
398
444
  });
399
445
  }
400
446
  destroy() {
401
- this.unbindEvents(), this.rafLoop.stop(), Ot(this.runtime), this.clearWinningCells();
447
+ this.unbindEvents(), this.rafLoop.stop(), ht(this.runtime), this.webglRenderer?.dispose(), this.webglRenderer = null, this.clearWinningCells();
402
448
  }
403
449
  spin() {
404
450
  if (this.runtime.isSpinning || this.runtime.queueFinished) return;
@@ -406,8 +452,8 @@ class C {
406
452
  this.runtime.queueFinished = !0, this.button && (this.button.disabled = !0);
407
453
  return;
408
454
  }
409
- const i = this.spinQueueController.consume(), e = i?.highlightWin === !0;
410
- this.runtime.isSpinning = !0, this.runtime.phase = "preSpin", this.runtime.preSpinStartedAt = performance.now(), this.runtime.activeSpinState = i, this.runtime.shouldHighlightCurrentSpin = e, this.runtime.hasStartedFirstSpin = !0, this.button && (this.button.disabled = !0);
455
+ const t = this.spinQueueController.consume(), i = t?.highlightWin === !0;
456
+ this.runtime.isSpinning = !0, this.runtime.phase = "preSpin", this.runtime.preSpinStartedAt = performance.now(), this.runtime.activeSpinState = t, this.runtime.shouldHighlightCurrentSpin = i, this.runtime.hasStartedFirstSpin = !0, this.button && (this.button.disabled = !0), this.startLoop();
411
457
  }
412
458
  bindEvents() {
413
459
  window.addEventListener("resize", this.resize), this.button?.addEventListener("click", this.onSpinClick);
@@ -422,212 +468,262 @@ class C {
422
468
  }
423
469
  this.runtime.isSpinning || this.spin();
424
470
  };
425
- getNextGrid(i) {
426
- return i ? P(i, this.spriteElementsCount) : y(this.spriteElementsCount);
471
+ getNextGrid(t) {
472
+ return t ? v(t, this.spriteElementsCount) : x(this.spriteElementsCount);
427
473
  }
428
- update(i) {
474
+ update(t) {
429
475
  if (this.runtime.isSpinning) {
430
476
  if (this.runtime.phase === "preSpin") {
431
- if (i - this.runtime.preSpinStartedAt < C.PRE_SPIN_MS)
477
+ if (t - this.runtime.preSpinStartedAt < u.PRE_SPIN_MS)
432
478
  return;
433
- Ct(this.runtime, {
479
+ rt(this.runtime, {
434
480
  activeSpinState: this.runtime.activeSpinState,
435
481
  shouldHighlightCurrentSpin: this.runtime.shouldHighlightCurrentSpin,
436
- startedAt: i
482
+ startedAt: t
437
483
  }), this.runtime.preSpinStartedAt = 0;
438
- const e = this.runtime.activeSpinState?.finaleSequenceRows ? this.runtime.activeSpinState.finaleSequenceRows.map((s) => G(s)) : this.runtime.activeSpinState?.finaleSequence ?? [];
439
- this.scriptedCascadeQueue = e.map((s) => s.map((r) => [...r])), this.clearWinningCells();
440
- const n = this.runtime.activeSpinState?.stopRows ? G(this.runtime.activeSpinState.stopRows) : this.runtime.activeSpinState?.stopGrid, l = this.getNextGrid(n);
441
- this.startOutroTransition(l, i);
484
+ const i = this.runtime.activeSpinState?.finaleSequenceRows ? this.runtime.activeSpinState.finaleSequenceRows.map((n) => P(n)) : this.runtime.activeSpinState?.finaleSequence ?? [];
485
+ this.scriptedCascadeQueue = i.map((n) => n.map((o) => [...o])), this.clearWinningCells();
486
+ const s = this.runtime.activeSpinState?.stopRows ? P(this.runtime.activeSpinState.stopRows) : this.runtime.activeSpinState?.stopGrid, r = this.getNextGrid(s);
487
+ this.startOutroTransition(r, t);
442
488
  }
443
489
  if (this.runtime.phase === "outro") {
444
- this.updateScriptedOutro(i);
490
+ this.updateScriptedOutro(t);
445
491
  return;
446
492
  }
447
493
  if (this.runtime.phase === "winFlash") {
448
- !this.runtime.shouldHighlightCurrentSpin && i - this.runtime.winFlashStartedAt >= K && this.finishSpinWithUi();
494
+ !this.runtime.shouldHighlightCurrentSpin && t - this.runtime.winFlashStartedAt >= k && this.finishSpinWithUi();
449
495
  return;
450
496
  }
451
497
  }
452
498
  }
453
- updateScriptedOutro(i) {
499
+ updateScriptedOutro(t) {
454
500
  if (!this.scriptedOutgoingGrid || !this.scriptedPendingGrid) {
455
501
  this.finishSpinWithUi();
456
502
  return;
457
503
  }
458
- const { allOutgoingDone: e, allIncomingDone: n } = ht({
459
- now: i,
504
+ this.outroMotionPlan || (this.outroMotionPlan = N({
460
505
  height: this.height,
461
506
  boardY: this.boardY,
462
- cellH: this.cellH,
507
+ cellH: this.cellH
508
+ }));
509
+ const i = this.getSmoothedOutroNow(t), { allOutgoingDone: s, allIncomingDone: r } = j({
510
+ now: i,
463
511
  scriptedOutroStartedAt: this.scriptedOutroStartedAt,
464
512
  scriptedOutgoingOffsets: this.scriptedOutgoingOffsets,
465
- scriptedIncomingOffsets: this.scriptedIncomingOffsets
513
+ scriptedIncomingOffsets: this.scriptedIncomingOffsets,
514
+ motionPlan: this.outroMotionPlan
466
515
  });
467
- if (!(!e || !n)) {
516
+ if (!(!s || !r)) {
468
517
  if (!this.scriptedPendingGrid) {
469
518
  this.finishSpinWithUi();
470
519
  return;
471
520
  }
472
- if (this.grid = this.scriptedPendingGrid, this.scriptedOutgoingGrid = null, this.scriptedPendingGrid = null, this.clearWinningCells(), W(this.scriptedOutgoingOffsets, 0), W(this.scriptedIncomingOffsets, 0), !this.tryStartScriptedCascade(i)) {
521
+ if (this.grid = this.scriptedPendingGrid, this.scriptedOutgoingGrid = null, this.scriptedPendingGrid = null, this.outroMotionPlan = null, this.clearWinningCells(), I(this.scriptedOutgoingOffsets, 0), I(this.scriptedIncomingOffsets, 0), !this.tryStartScriptedCascade(t)) {
473
522
  if (!this.runtime.shouldHighlightCurrentSpin) {
474
523
  this.finishSpinWithUi();
475
524
  return;
476
525
  }
477
- this.winningCells = T(this.grid), q(this.runtime, i), this.runtime.activeSpinState?.callback?.(), this.button && this.runtime.shouldHighlightCurrentSpin && this.spinQueueController.hasPending() && (this.button.disabled = !1);
526
+ this.setWinningCells(F(this.grid)), U(this.runtime, t), this.runtime.activeSpinState?.callback?.(), this.button && this.runtime.shouldHighlightCurrentSpin && this.spinQueueController.hasPending() && (this.button.disabled = !1);
478
527
  }
479
528
  }
480
529
  }
481
- tryStartScriptedCascade(i) {
530
+ tryStartScriptedCascade(t) {
482
531
  if (this.scriptedCascadeQueue.length === 0) return !1;
483
- const e = this.scriptedCascadeQueue.shift();
484
- return e ? (this.startOutroTransition(P(e, this.spriteElementsCount), i), !0) : !1;
532
+ const i = this.scriptedCascadeQueue.shift();
533
+ return i ? (this.startOutroTransition(v(i, this.spriteElementsCount), t), !0) : !1;
534
+ }
535
+ startOutroTransition(t, i) {
536
+ this.scriptedOutgoingGrid = this.grid.map((s) => [...s]), this.scriptedPendingGrid = t.map((s) => [...s]), this.outroMotionPlan = N({
537
+ height: this.height,
538
+ boardY: this.boardY,
539
+ cellH: this.cellH
540
+ }), I(this.scriptedIncomingOffsets, Number.NaN), I(this.scriptedOutgoingOffsets, 0), this.clearWinningCells(), this.runtime.phase = "outro", this.scriptedOutroStartedAt = i, this.scriptedOutroVirtualElapsedMs = 0, this.scriptedOutroLastNow = i;
485
541
  }
486
- startOutroTransition(i, e) {
487
- this.scriptedOutgoingGrid = this.grid.map((n) => [...n]), this.scriptedPendingGrid = i.map((n) => [...n]), W(this.scriptedIncomingOffsets, Number.NaN), W(this.scriptedOutgoingOffsets, 0), this.clearWinningCells(), this.runtime.phase = "outro", this.scriptedOutroStartedAt = e;
542
+ getSmoothedOutroNow(t) {
543
+ const i = Math.max(0, t - this.scriptedOutroLastNow);
544
+ this.scriptedOutroLastNow = t;
545
+ const s = Math.min(i, u.OUTRO_MAX_STEP_MS);
546
+ return this.scriptedOutroVirtualElapsedMs += s, this.scriptedOutroStartedAt + this.scriptedOutroVirtualElapsedMs;
488
547
  }
489
548
  /** @param skipCallback true при закрытии подсветки по клику (callback уже вызван по окончании прокрутки) */
490
- finishSpinWithUi(i = !1) {
491
- const e = this.runtime.activeSpinState, n = i ? void 0 : e?.callback, l = this.spinQueueController.hasPending();
492
- wt(this.runtime, l, performance.now()), this.button && (this.button.disabled = this.runtime.queueFinished), n?.();
549
+ finishSpinWithUi(t = !1) {
550
+ const i = this.runtime.activeSpinState, s = t ? void 0 : i?.callback, r = this.spinQueueController.hasPending();
551
+ ot(this.runtime, r, performance.now()), this.button && (this.button.disabled = this.runtime.queueFinished), s?.();
493
552
  }
494
553
  applyInitialHighlightIfNeeded() {
495
- this.highlightInitialWinningCells && (this.winningCells = T(this.grid), this.initialHighlightRequestedAt = performance.now());
554
+ this.highlightInitialWinningCells && (this.setWinningCells(F(this.grid)), this.initialHighlightRequestedAt = performance.now());
496
555
  }
497
- static ZERO_SWAY = [0, 0, 0];
498
- render(i) {
499
- this.initialHighlightRequestedAt > 0 && i - this.initialHighlightRequestedAt >= nt && (q(this.runtime, this.initialHighlightRequestedAt), this.runtime.winEffectsEnvelope = 1, this.initialHighlightRequestedAt = 0, this.button && (this.button.disabled = !1));
500
- const e = this.runtime.phase === "outro" || this.runtime.phase === "preSpin" ? 0 : 1, n = this.runtime.phase === "preSpin" ? 0 : this.runtime.phase === "winFlash" ? 1 : 0;
556
+ render(t) {
557
+ if (!this.webglRenderer) return;
558
+ this.initialHighlightRequestedAt > 0 && t - this.initialHighlightRequestedAt >= K && (U(this.runtime, this.initialHighlightRequestedAt), this.runtime.winEffectsEnvelope = 1, this.initialHighlightRequestedAt = 0, this.button && (this.button.disabled = !1));
559
+ const i = this.runtime.phase === "preSpin" ? 0 : this.runtime.phase === "winFlash" ? 1 : 0;
501
560
  if (this.lastRafTime > 0) {
502
- const u = Math.min(i - this.lastRafTime, 50), o = 1 - Math.exp(-u / C.SWAY_ENVELOPE_TAU_MS);
503
- this.runtime.swayEnvelope += (e - this.runtime.swayEnvelope) * o;
504
- const h = 1 - Math.exp(-u / C.WIN_EFFECTS_ENVELOPE_TAU_MS);
505
- this.runtime.winEffectsEnvelope += (n - this.runtime.winEffectsEnvelope) * h;
561
+ const h = Math.min(t - this.lastRafTime, 50), l = 1 - Math.exp(-h / u.WIN_EFFECTS_ENVELOPE_TAU_MS);
562
+ this.runtime.winEffectsEnvelope += (i - this.runtime.winEffectsEnvelope) * l;
506
563
  }
507
- this.lastRafTime = i, this.ctx.clearRect(0, 0, this.width, this.height), this.runtime.phase === "outro" && this.scriptedOutgoingGrid && this.scriptedPendingGrid ? (this.drawLayer(
508
- this.scriptedOutgoingGrid,
509
- this.scriptedOutgoingOffsets,
510
- C.ZERO_SWAY,
511
- i
512
- ), this.drawLayer(
513
- this.scriptedPendingGrid,
514
- this.scriptedIncomingOffsets,
515
- C.ZERO_SWAY,
516
- i
517
- )) : this.drawBaseGrid(i);
518
- const l = this.initialHighlightRequestedAt > 0 ? "winFlash" : this.runtime.phase, s = this.initialHighlightRequestedAt > 0 ? this.initialHighlightRequestedAt : this.runtime.winFlashStartedAt, r = this.initialHighlightRequestedAt > 0 ? 1 : this.runtime.winEffectsEnvelope;
519
- U({
520
- ctx: this.ctx,
521
- now: i,
522
- phase: l,
523
- winFlashStartedAt: s,
524
- winEffectsEnvelope: r,
525
- winningCells: this.winningCells,
526
- boardX: this.boardX,
527
- boardY: this.boardY,
528
- cellW: this.cellW,
529
- cellH: this.cellH,
530
- particlesPerCell: this.runtime.phase === "winFlash" && this.runtime.shouldHighlightCurrentSpin && this.runtime.hasStartedFirstSpin ? this.particlesPerCell : 0,
531
- particleColorMode: this.particleColorMode,
532
- particleColorRgb: this.particleColorRgb,
533
- winningCellBorderRgba: this.winningCellBorderRgba,
534
- getCell: this.getCell,
535
- drawSpriteCell: this.drawSpriteCell
564
+ this.lastRafTime = t, this.webglRenderer.beginFrame();
565
+ const s = this.runtime.phase === "winFlash" || this.runtime.phase === "preSpin" || this.initialHighlightRequestedAt > 0 && this.winningCells.length > 0;
566
+ this.runtime.phase === "outro" && this.scriptedOutgoingGrid && this.scriptedPendingGrid ? (this.drawGrid(this.scriptedOutgoingGrid, this.scriptedOutgoingOffsets, s), this.drawGrid(this.scriptedPendingGrid, this.scriptedIncomingOffsets, s)) : this.drawGrid(this.grid, null, s);
567
+ const r = this.initialHighlightRequestedAt > 0 ? "winFlash" : this.runtime.phase, n = this.initialHighlightRequestedAt > 0 ? this.initialHighlightRequestedAt : this.runtime.winFlashStartedAt, o = this.initialHighlightRequestedAt > 0 ? 1 : this.runtime.winEffectsEnvelope;
568
+ this.drawWinningEffects({
569
+ now: t,
570
+ phase: r,
571
+ winFlashStartedAt: n,
572
+ winEffectsEnvelope: o
536
573
  });
537
574
  }
538
- drawBaseGrid(i) {
539
- this.drawLayer(this.grid, null, C.ZERO_SWAY, i);
575
+ isWinningCell = (t, i) => this.winningCellKeys.has(`${t}:${i}`);
576
+ getRowCompactOffset(t) {
577
+ return (q[t] ?? 0) * this.cellH;
578
+ }
579
+ drawGrid(t, i, s) {
580
+ const r = this.webglRenderer;
581
+ if (r)
582
+ for (let n = 0; n < 3; n += 1) {
583
+ const o = this.boardX + n * this.cellW;
584
+ for (let h = 0; h < 3; h += 1) {
585
+ if (s && this.isWinningCell(n, h)) continue;
586
+ const l = i ? i[n][h] : 0;
587
+ if (!Number.isFinite(l)) continue;
588
+ const c = this.boardY + h * this.cellH + l + this.getRowCompactOffset(h);
589
+ c > this.height || c + this.cellH < 0 || r.drawSprite(t[n][h], o, c, this.cellW, this.cellH, 1);
590
+ }
591
+ }
540
592
  }
541
- drawLayer(i, e, n, l) {
542
- const s = this.runtime.phase === "winFlash" || this.runtime.phase === "preSpin" || this.initialHighlightRequestedAt > 0 && this.winningCells.length > 0;
543
- Et({
544
- ctx: this.ctx,
545
- phase: this.runtime.phase,
546
- now: l,
547
- swayEnvelope: this.runtime.swayEnvelope,
548
- grid: i,
549
- offsets: e,
550
- columnSway: n,
551
- boardX: this.boardX,
552
- boardY: this.boardY,
553
- cellW: this.cellW,
554
- cellH: this.cellH,
555
- width: this.width,
556
- height: this.height,
557
- skipWinningCells: s,
558
- isWinningCell: this.isWinningCell,
559
- drawSpriteCell: this.drawSpriteCell
560
- });
593
+ drawWinningEffects(t) {
594
+ const i = this.webglRenderer;
595
+ if (!i || this.winningCells.length === 0 || t.phase !== "winFlash" && t.phase !== "preSpin") return;
596
+ const s = Math.max(0, Math.min(1, t.winEffectsEnvelope)), r = Math.max(0, t.now - t.winFlashStartedAt), n = r % W / W, o = 1 + Math.sin(n * Math.PI * 2) * Y * s, h = Math.max(1, this.cellW * u.WIN_BORDER_INSET_RATIO), l = Math.max(1, this.cellW * 0.03), c = this.runtime.phase === "winFlash" && this.runtime.shouldHighlightCurrentSpin && this.runtime.hasStartedFirstSpin;
597
+ for (const a of this.winningCells) {
598
+ const d = this.boardX + a.col * this.cellW, g = this.boardY + a.row * this.cellH + this.getRowCompactOffset(a.row), p = this.grid[a.col][a.row], S = u.WIN_BORDER_ALPHA * s, f = this.particleColorMode === "rainbow" ? u.hslToRgb01(
599
+ (r * 0.2 + a.col * 36 + a.row * 22) % 360,
600
+ 0.96,
601
+ 0.64
602
+ ) : [
603
+ this.particleColorRgb[0] / 255,
604
+ this.particleColorRgb[1] / 255,
605
+ this.particleColorRgb[2] / 255
606
+ ], _ = this.cellW * o, m = this.cellH * o, R = (this.cellW - _) * 0.5, B = (this.cellH - m) * 0.5;
607
+ i.drawSprite(p, d + R, g + B, _, m, 1);
608
+ const E = d + h, w = g + h, O = this.cellW - h * 2, A = this.cellH - h * 2;
609
+ O <= l * 2 || A <= l * 2 || (i.drawSolidRect(E, w, O, l, [
610
+ f[0],
611
+ f[1],
612
+ f[2],
613
+ S
614
+ ]), i.drawSolidRect(E, w + A - l, O, l, [
615
+ f[0],
616
+ f[1],
617
+ f[2],
618
+ S
619
+ ]), i.drawSolidRect(E, w, l, A, [
620
+ f[0],
621
+ f[1],
622
+ f[2],
623
+ S
624
+ ]), i.drawSolidRect(E + O - l, w, l, A, [
625
+ f[0],
626
+ f[1],
627
+ f[2],
628
+ S
629
+ ]), c && this.drawCellParticleBurst({
630
+ renderer: i,
631
+ cell: a,
632
+ centerX: d + this.cellW * 0.5,
633
+ centerY: g + this.cellH * 0.5,
634
+ elapsed: r,
635
+ envelope: s
636
+ }));
637
+ }
561
638
  }
562
- getCell = (i, e) => this.grid[i][e];
563
- isWinningCell = (i, e) => this.winningCells.some((n) => n.col === i && n.row === e);
564
- drawSpriteCellToCtx(i, e, n, l, s, r) {
565
- const u = this.spriteImage;
566
- if (!u) return;
567
- const o = A(e, this.spriteElementsCount), h = u.height / this.spriteElementsCount, a = Math.floor(o * h), c = Math.floor(h);
568
- i.drawImage(u, 0, a, u.width, c, n, l, s, r);
639
+ drawCellParticleBurst(t) {
640
+ const i = Math.min(this.cellW, this.cellH) * u.PARTICLE_MAX_DISTANCE, s = Math.min(this.cellW, this.cellH) * u.PARTICLE_BASE_RADIUS, r = u.getParticleSeeds(t.cell.col, t.cell.row), n = [
641
+ this.particleColorRgb[0] / 255,
642
+ this.particleColorRgb[1] / 255,
643
+ this.particleColorRgb[2] / 255
644
+ ];
645
+ t.renderer.beginAdditiveBlend();
646
+ for (let o = 0; o < this.particlesPerCell; o += 1) {
647
+ const h = r[o], l = h.phaseOffset * T, c = t.elapsed - l;
648
+ if (c < 0) continue;
649
+ const a = c % T / T, d = h.seedA * Math.PI * 2, g = i * a * (0.35 + h.seedB * 0.65), p = t.centerX + Math.cos(d) * g, S = t.centerY + Math.sin(d) * g, f = 0.7 + 0.9 * Math.max(0, Math.sin((t.elapsed * 0.012 + h.twinkleSeed * 2) * Math.PI * 2)), _ = Math.max(1, s * (0.55 + h.seedC * 0.6) * (1 - a * 0.5)), m = Math.max(
650
+ 0,
651
+ Math.min(1, (0.9 + f * 0.2) * u.PARTICLE_GLOBAL_ALPHA * t.envelope)
652
+ );
653
+ if (m <= 0) continue;
654
+ const R = this.particleColorMode === "rainbow" ? u.getRainbowParticleColor(
655
+ t.elapsed,
656
+ t.cell.col,
657
+ t.cell.row,
658
+ h.seedA
659
+ ) : n;
660
+ t.renderer.drawSoftCircle(p, S, _, [R[0], R[1], R[2], m]);
661
+ }
662
+ t.renderer.endAdditiveBlend();
663
+ }
664
+ static hslToRgb01(t, i, s) {
665
+ const r = (t % 360 + 360) % 360, n = Math.max(0, Math.min(1, i)), o = Math.max(0, Math.min(1, s)), h = (1 - Math.abs(2 * o - 1)) * n, l = r / 60, c = h * (1 - Math.abs(l % 2 - 1));
666
+ let a = 0, d = 0, g = 0;
667
+ l >= 0 && l < 1 ? (a = h, d = c) : l < 2 ? (a = c, d = h) : l < 3 ? (d = h, g = c) : l < 4 ? (d = c, g = h) : l < 5 ? (a = c, g = h) : (a = h, g = c);
668
+ const p = o - h * 0.5;
669
+ return [a + p, d + p, g + p];
670
+ }
671
+ static getRainbowParticleColor(t, i, s, r) {
672
+ const n = (r * 360 + t * 0.24 + i * 38 + s * 22) % 360, o = 360 / u.RAINBOW_HUE_BUCKETS, h = Math.floor(n / o) * o;
673
+ return u.hslToRgb01(h, 0.98, 0.64);
674
+ }
675
+ static hash01(t, i, s, r) {
676
+ const n = Math.sin(t * 127.1 + i * 311.7 + s * 74.7 + r * 19.3) * 43758.5453;
677
+ return n - Math.floor(n);
678
+ }
679
+ static getParticleSeeds(t, i) {
680
+ const s = `${t},${i}`, r = u.particleSeedsCache.get(s);
681
+ if (r) return r;
682
+ const n = [];
683
+ for (let o = 0; o < G; o += 1)
684
+ n.push({
685
+ seedA: u.hash01(t, i, o, 1),
686
+ seedB: u.hash01(t, i, o, 2),
687
+ seedC: u.hash01(t, i, o, 3),
688
+ phaseOffset: u.hash01(t, i, o, 4),
689
+ twinkleSeed: u.hash01(t, i, o, 5)
690
+ });
691
+ return u.particleSeedsCache.set(s, n), n;
569
692
  }
570
- drawSpriteCell = (i, e, n, l, s) => {
571
- this.drawSpriteCellToCtx(this.ctx, i, e, n, l, s);
572
- };
573
693
  clearWinningCells() {
574
- this.winningCells = [];
694
+ this.winningCells = [], this.winningCellKeys.clear();
695
+ }
696
+ setWinningCells(t) {
697
+ this.winningCells = t, this.winningCellKeys.clear();
698
+ for (const i of t)
699
+ this.winningCellKeys.add(`${i.col}:${i.row}`);
575
700
  }
576
701
  resize = () => {
577
- const i = this.container.getBoundingClientRect();
702
+ const t = this.container.getBoundingClientRect();
578
703
  this.dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));
579
- const e = Math.max(300, Math.floor(i.width * this.dpr));
580
- this.width = e, this.height = e;
581
- const n = Math.floor(Math.min(this.width / 3, this.height / 3));
582
- this.cellW = n, this.cellH = n, this.boardX = Math.floor((this.width - this.cellW * 3) / 2), this.boardY = Math.floor((this.height - this.cellH * 3) / 2), this.quality === "high" ? this.particlesPerCell = b : this.quality === "balanced" ? this.particlesPerCell = x : this.quality === "low" ? this.particlesPerCell = v : this.particlesPerCell = this.dpr >= 2 ? x : b, this.canvas.width = this.width, this.canvas.height = this.height;
704
+ const i = Math.max(300, Math.floor(t.width * this.dpr));
705
+ this.width = i, this.height = i;
706
+ const s = Math.floor(Math.min(this.width / 3, this.height / 3));
707
+ this.cellW = s, this.cellH = s, this.boardX = Math.floor((this.width - this.cellW * 3) / 2), this.boardY = Math.floor((this.height - this.cellH * 3) / 2), this.canvas.width = this.width, this.canvas.height = this.height, this.webglRenderer?.resize(this.width, this.height);
583
708
  };
584
709
  async loadSpriteIfProvided() {
585
710
  if (!this.spriteUrl) return;
586
- const i = new Image();
587
- i.decoding = "async", i.src = this.spriteUrl;
711
+ const t = new Image();
712
+ t.decoding = "async", t.src = this.spriteUrl;
588
713
  try {
589
- await i.decode(), this.spriteImage = i;
714
+ await t.decode(), this.spriteImage = t;
590
715
  } catch {
591
716
  this.spriteImage = null;
592
717
  }
593
718
  }
594
- prewarmWinEffects() {
595
- if (this.winningCells.length === 0) return;
596
- const i = document.createElement("canvas");
597
- i.width = this.width, i.height = this.height;
598
- const e = i.getContext("2d");
599
- if (!e) return;
600
- const n = this.runtime.phase, l = this.runtime.winFlashStartedAt;
601
- this.runtime.phase = "winFlash", this.runtime.winFlashStartedAt = performance.now() - 300;
602
- const s = performance.now(), r = Math.min(this.particlesPerCell, v), u = (o, h, a, c, d) => {
603
- this.drawSpriteCellToCtx(e, o, h, a, c, d);
604
- };
605
- for (let o = 0; o < 2; o += 1)
606
- U({
607
- ctx: e,
608
- now: s + o * 16,
609
- phase: "winFlash",
610
- winFlashStartedAt: this.runtime.winFlashStartedAt,
611
- winEffectsEnvelope: 1,
612
- winningCells: this.winningCells,
613
- boardX: this.boardX,
614
- boardY: this.boardY,
615
- cellW: this.cellW,
616
- cellH: this.cellH,
617
- particlesPerCell: r,
618
- particleColorMode: this.particleColorMode,
619
- particleColorRgb: this.particleColorRgb,
620
- winningCellBorderRgba: this.winningCellBorderRgba,
621
- getCell: this.getCell,
622
- drawSpriteCell: u
623
- });
624
- this.runtime.phase = n, this.runtime.winFlashStartedAt = l;
625
- }
626
719
  startLoop() {
627
- this.rafLoop.isRunning() || this.rafLoop.start((i) => (this.update(i), this.render(i), !0));
720
+ this.rafLoop.isRunning() || this.rafLoop.start((t) => (this.update(t), this.render(t), this.shouldKeepAnimating()));
721
+ }
722
+ shouldKeepAnimating() {
723
+ return this.runtime.isSpinning || this.initialHighlightRequestedAt > 0 ? !0 : this.runtime.winEffectsEnvelope > 1e-3;
628
724
  }
629
725
  }
630
726
  export {
631
- C as CascadingReel
727
+ u as CascadingReel
632
728
  };
633
729
  //# sourceMappingURL=index.es.js.map