cascading-reel 0.0.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/LICENSE ADDED
@@ -0,0 +1,42 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ux-ui.pro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ MIT License
23
+
24
+ Copyright (c) 2024 ux-ui.pro
25
+
26
+ Permission is hereby granted, free of charge, to any person obtaining a copy
27
+ of this software and associated documentation files (the "Software"), to deal
28
+ in the Software without restriction, including without limitation the rights
29
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30
+ copies of the Software, and to permit persons to whom the Software is
31
+ furnished to do so, subject to the following conditions:
32
+
33
+ The above copyright notice and this permission notice shall be included in all
34
+ copies or substantial portions of the Software.
35
+
36
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
39
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
40
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
42
+ SOFTWARE.
package/README.md ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const D=6,a=3,u=3,U=34,B=.05,A=1200,F=1800,k=.075,z=34,$=[255,235,110],X=[255,255,255,.78],Q=190,M=650,Z=220;function w(t,i,e){return t<i?i:t>e?e:t}function m(t){return 1-(1-t)**3}function j(t){return Math.floor(Math.random()*t)}function E(t,i){return(t%i+i)%i}function p(t){return w(Math.round(t),0,255)}function V(t){return w(t,0,1)}function G(t){const i=[];for(let e=0;e<a;e+=1){const n=[];for(let r=0;r<u;r+=1)n.push(j(t));i.push(n)}return i}function v(t){const i=new Map;for(let l=0;l<a;l+=1)for(let s=0;s<u;s+=1){const o=t[l][s];i.set(o,(i.get(o)??0)+1)}let e=t[0][0],n=-1;for(const[l,s]of i.entries())s>n&&(n=s,e=l);const r=[];for(let l=0;l<a;l+=1)for(let s=0;s<u;s+=1)t[l][s]===e&&r.push({col:l,row:s});return r}function H(){return Array.from({length:a},()=>Array.from({length:u},()=>0))}function R(t,i){for(let e=0;e<a;e+=1)for(let n=0;n<u;n+=1)t[e][n]=i}class J{rafId=null;step=null;start(i){this.rafId===null&&(this.step=i,this.rafId=requestAnimationFrame(this.tick))}stop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.step=null}isRunning(){return this.rafId!==null}tick=i=>{if(!this.step){this.stop();return}if(this.step(i)===!1){this.stop();return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.tick))}}function K(t,i,e){const n=[0,0,0];let r=0;for(let l=u-1;l>=0;l-=1){if(t[l]===0){n[l]=0;continue}n[l]=r;const s=Math.floor(i*B);r+=s+e}return n}function tt(t){let i=!0,e=!0;const r=t.height-t.boardY+t.cellH+2,l=[r,r,r],s=[-t.cellH,-t.cellH*2,-t.cellH*3],o=K(l,M,U),d=M-Z;for(let h=0;h<t.scriptedOutgoingOffsets.length;h+=1){const f=t.now-(t.scriptedOutroStartedAt+h*Q);for(let c=0;c<u;c+=1){const g=f-o[c];if(g<=0){t.scriptedOutgoingOffsets[h][c]=0,t.scriptedIncomingOffsets[h][c]=Number.NaN,i=!1,e=!1;continue}const S=w(g/M,0,1),b=m(S);t.scriptedOutgoingOffsets[h][c]=r*b,S<1&&(i=!1);const W=g-d;if(W<=0){t.scriptedIncomingOffsets[h][c]=Number.NaN,e=!1;continue}const I=w(W/M,0,1),y=m(I);t.scriptedIncomingOffsets[h][c]=s[c]*(1-y),I<1&&(e=!1)}}return{allOutgoingDone:i,allIncomingDone:e}}function it(t){return t?[p(t[0]),p(t[1]),p(t[2])]:$}function et(t){return t==="rainbow"?"rainbow":"solid"}function nt(t){return t?[p(t[0]),p(t[1]),p(t[2]),V(t[3])]:X}function _(t){if(t.length!==u)throw new Error(`rows must contain ${u} rows`);for(let i=0;i<u;i+=1)if(!Array.isArray(t[i])||t[i].length!==a)throw new Error(`rows[${i}] must contain ${a} columns`);return[[t[0][0],t[1][0],t[2][0]],[t[0][1],t[1][1],t[2][1]],[t[0][2],t[1][2],t[2][2]]]}function P(t,i){if(t.length!==a)throw new Error(`stopGrid must contain ${a} columns`);const e=[];for(let n=0;n<a;n+=1){const r=t[n];if(!Array.isArray(r)||r.length!==u)throw new Error(`stopGrid[${n}] must contain ${u} rows`);e[n]=[E(r[0],i),E(r[1],i),E(r[2],i)]}return e}function st(t,i){return P(_(t),i)}function rt(t){return{stopGrid:t.stopGrid?.map(i=>[...i]),stopRows:t.stopRows?.map(i=>[...i]),finaleSequence:t.finaleSequence?.map(i=>i.map(e=>[...e])),finaleSequenceRows:t.finaleSequenceRows?.map(i=>i.map(e=>[...e])),callback:t.callback}}class lt{queue;constructor(i){this.queue=(i??[]).map(e=>rt(e))}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?null:this.queue.shift()??null}}function ot(){return{isSpinning:!1,hasStartedFirstSpin:!1,queueFinished:!1,shouldHighlightCurrentSpin:!1,activeSpinState:null,phase:"idle",winFlashStartedAt:0}}function ct(t,i){t.hasStartedFirstSpin=!0,t.isSpinning=!0,t.phase="outro",t.activeSpinState=i.activeSpinState,t.shouldHighlightCurrentSpin=i.shouldHighlightCurrentSpin}function ht(t,i){t.phase="idle",t.isSpinning=!1,t.shouldHighlightCurrentSpin=!1,t.queueFinished=!i,t.activeSpinState=null}function N(t,i){t.winFlashStartedAt=i,t.phase="winFlash"}function ut(t){t.isSpinning=!1,t.queueFinished=!0}function dt(t){const i=t.columnSway??[0,0,0];for(let e=0;e<a;e+=1){const n=t.boardX+e*t.cellW,r=i[e]??0;for(let l=0;l<u;l+=1){if(t.phase==="winFlash"&&t.isWinningCell(e,l))continue;const s=t.offsets?t.offsets[e][l]:0;if(!Number.isFinite(s))continue;const o=t.boardY+l*t.cellH+s+r;o>t.height||o+t.cellH<0||t.drawSpriteCell(t.grid[e][l],n,o,t.cellW,t.cellH)}}}function C(t,i,e,n){const r=Math.sin(t*127.1+i*311.7+e*74.7+n*19.3)*43758.5453;return r-Math.floor(r)}function at(t){if(t.winningCells.length===0||t.phase!=="winFlash")return;const i=Math.max(0,t.now-t.winFlashStartedAt),e=i%A/A,n=i%F/F,r=1+Math.sin(n*Math.PI*2)*k,l=1-(1-e)**2;for(const s of t.winningCells){const o=t.boardX+s.col*t.cellW,d=t.boardY+s.row*t.cellH;ft(t.ctx,o,d,t.cellW,t.cellH,t.winningCellBorderRgba,i);const h=t.getCell(s.col,s.row),f=t.cellW*r,c=t.cellH*r,g=(t.cellW-f)*.5,S=(t.cellH-c)*.5;t.ctx.save(),t.drawSpriteCell(h,o+g,d+S,f,c),t.ctx.restore(),gt({ctx:t.ctx,cell:s,x:o,y:d,cycleProgress:e,easeOut:l,boardX:t.boardX,boardY:t.boardY,cellW:t.cellW,cellH:t.cellH,particleColorMode:t.particleColorMode,particleColorRgb:t.particleColorRgb})}}function ft(t,i,e,n,r,l,s){const o=Math.max(1,Math.floor(Math.min(n,r)*.04)),d=Math.max(1,Math.floor(Math.min(n,r)*.025)),[,,,h]=l,f=s*.15%360;t.save(),t.lineWidth=d,t.strokeStyle=`hsla(${f}, 90%, 70%, ${h})`;const c=d*.5;t.strokeRect(i+o+c,e+o+c,n-o*2-d,r-o*2-d),t.restore()}function gt(t){const i=t.x+t.cellW*.5,e=t.y+t.cellH*.5,n=Math.min(t.cellW,t.cellH)*.72,r=Math.min(t.cellW,t.cellH)*.028,l=.92+(1-t.cycleProgress)*.08;t.ctx.save(),t.ctx.beginPath(),t.ctx.rect(t.boardX,t.boardY,t.cellW*a,t.cellH*u),t.ctx.clip();for(let s=0;s<z;s+=1){const o=C(t.cell.col,t.cell.row,s,1),d=C(t.cell.col,t.cell.row,s,2),h=C(t.cell.col,t.cell.row,s,3),f=C(t.cell.col,t.cell.row,s,4)*.28,c=C(t.cell.col,t.cell.row,s,5),g=w((t.cycleProgress-f)/(1-f),0,1);if(g<=0)continue;const S=o*Math.PI*2,b=n*g*(.35+d*.65)*(.85+t.easeOut*.15),W=i+Math.cos(S)*b,I=e+Math.sin(S)*b,y=.7+.9*Math.max(0,Math.sin((t.cycleProgress*11+c*2)*Math.PI*2)),q=Math.max(1,r*(.55+h*.6)*(1-g*.5)),L=w((.9+y*.2)*l,.9,1);if(!(L<=0)){if(t.particleColorMode==="rainbow"){const x=(o*360+t.cycleProgress*240+t.cell.col*38+t.cell.row*22)%360;t.ctx.fillStyle=`hsla(${x}, 98%, 64%, ${L})`}else{const[x,Y,T]=t.particleColorRgb;t.ctx.fillStyle=`rgba(${x}, ${Y}, ${T}, ${L})`}t.ctx.beginPath(),t.ctx.arc(W,I,q,0,Math.PI*2),t.ctx.fill()}}t.ctx.restore()}class O{canvas;container;button;ctx;spinQueueController;spriteUrl;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;winningCellBorderRgba;spriteImage=null;rafLoop=new J;runtime=ot();dpr=1;width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutgoingOffsets=H();scriptedIncomingOffsets=H();winningCells=[];grid;constructor(i){this.canvas=i.canvas,this.container=i.container,this.button=i.button,this.spriteUrl=i.sprite,this.spriteElementsCount=Math.max(1,i.spriteElementsCount??D),this.highlightInitialWinningCells=i.highlightInitialWinningCells!==!1,this.spinQueueController=new lt(i.queuedSpinStates),this.particleColorRgb=it(i.particleColorRgb),this.particleColorMode=et(i.particleColorMode),this.winningCellBorderRgba=nt(i.winningCellBorderRgba);const e=this.canvas.getContext("2d");if(!e)throw new Error("2D canvas context is not available");this.ctx=e,this.grid=i.initialSegments?st(i.initialSegments,this.spriteElementsCount):G(this.spriteElementsCount)}async init(){this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),this.applyInitialHighlightIfNeeded(),this.startLoop()}destroy(){this.unbindEvents(),this.rafLoop.stop(),ut(this.runtime),this.clearWinningCells()}spin(){if(this.runtime.isSpinning||this.runtime.queueFinished)return;if(!this.spinQueueController.hasPending()){this.runtime.queueFinished=!0,this.button&&(this.button.disabled=!0);return}const i=this.spinQueueController.consume(),e=!this.spinQueueController.hasPending();ct(this.runtime,{activeSpinState:i,shouldHighlightCurrentSpin:e}),this.button&&(this.button.disabled=!0);const n=this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(s=>_(s)):this.runtime.activeSpinState?.finaleSequence??[];this.scriptedCascadeQueue=n.map(s=>s.map(o=>[...o])),this.clearWinningCells();const r=this.runtime.activeSpinState?.stopRows?_(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,l=this.getNextGrid(r);this.startOutroTransition(l,performance.now())}bindEvents(){window.addEventListener("resize",this.resize),this.button?.addEventListener("click",this.onSpinClick)}unbindEvents(){window.removeEventListener("resize",this.resize),this.button?.removeEventListener("click",this.onSpinClick)}onSpinClick=()=>{this.runtime.isSpinning||this.spin()};getNextGrid(i){return i?P(i,this.spriteElementsCount):G(this.spriteElementsCount)}update(i){if(this.runtime.isSpinning){if(this.runtime.phase==="outro"){this.updateScriptedOutro(i);return}this.runtime.phase}}updateScriptedOutro(i){if(!this.scriptedOutgoingGrid||!this.scriptedPendingGrid){this.finishSpinWithUi();return}const{allOutgoingDone:e,allIncomingDone:n}=tt({now:i,height:this.height,boardY:this.boardY,cellH:this.cellH,scriptedOutroStartedAt:this.scriptedOutroStartedAt,scriptedOutgoingOffsets:this.scriptedOutgoingOffsets,scriptedIncomingOffsets:this.scriptedIncomingOffsets});if(!(!e||!n)){if(!this.scriptedPendingGrid){this.finishSpinWithUi();return}if(this.grid=this.scriptedPendingGrid,this.scriptedOutgoingGrid=null,this.scriptedPendingGrid=null,this.clearWinningCells(),R(this.scriptedOutgoingOffsets,0),R(this.scriptedIncomingOffsets,0),!this.tryStartScriptedCascade(i)){if(!this.runtime.shouldHighlightCurrentSpin){this.finishSpinWithUi();return}this.winningCells=v(this.grid),N(this.runtime,i)}}}tryStartScriptedCascade(i){if(this.scriptedCascadeQueue.length===0)return!1;const e=this.scriptedCascadeQueue.shift();return e?(this.startOutroTransition(P(e,this.spriteElementsCount),i),!0):!1}startOutroTransition(i,e){this.scriptedOutgoingGrid=this.grid.map(n=>[...n]),this.scriptedPendingGrid=i.map(n=>[...n]),R(this.scriptedIncomingOffsets,Number.NaN),R(this.scriptedOutgoingOffsets,0),this.clearWinningCells(),this.runtime.phase="outro",this.scriptedOutroStartedAt=e}finishSpinWithUi(){const i=this.runtime.activeSpinState?.callback;ht(this.runtime,this.spinQueueController.hasPending()),this.button&&(this.button.disabled=this.runtime.queueFinished),i?.()}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.winningCells=v(this.grid),N(this.runtime,performance.now()))}static ZERO_SWAY=[0,0,0];render(i){this.ctx.clearRect(0,0,this.width,this.height),this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawLayer(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsets,O.ZERO_SWAY),this.drawLayer(this.scriptedPendingGrid,this.scriptedIncomingOffsets,O.ZERO_SWAY)):this.drawBaseGrid(),at({ctx:this.ctx,now:i,phase:this.runtime.phase,winFlashStartedAt:this.runtime.winFlashStartedAt,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:this.drawSpriteCell})}drawBaseGrid(){this.drawLayer(this.grid,null,O.ZERO_SWAY)}drawLayer(i,e,n){dt({ctx:this.ctx,phase:this.runtime.phase,grid:i,offsets:e,columnSway:n,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,width:this.width,height:this.height,isWinningCell:this.isWinningCell,drawSpriteCell:this.drawSpriteCell})}getCell=(i,e)=>this.grid[i][e];isWinningCell=(i,e)=>this.winningCells.some(n=>n.col===i&&n.row===e);drawSpriteCell=(i,e,n,r,l)=>{const s=this.spriteImage;if(!s)return;const o=E(i,this.spriteElementsCount),d=s.height/this.spriteElementsCount,h=Math.floor(o*d),f=Math.floor(d);this.ctx.drawImage(s,0,h,s.width,f,e,n,r,l)};clearWinningCells(){this.winningCells=[]}resize=()=>{const i=this.container.getBoundingClientRect();this.dpr=Math.max(1,Math.min(window.devicePixelRatio||1,2));const e=Math.max(300,Math.floor(i.width*this.dpr));this.width=e,this.height=e;const n=Math.floor(Math.min(this.width/a,this.height/u));this.cellW=n,this.cellH=n,this.boardX=Math.floor((this.width-this.cellW*a)/2),this.boardY=Math.floor((this.height-this.cellH*u)/2),this.canvas.width=this.width,this.canvas.height=this.height};async loadSpriteIfProvided(){if(!this.spriteUrl)return;const i=new Image;i.decoding="async",i.src=this.spriteUrl;try{await i.decode(),this.spriteImage=i}catch{this.spriteImage=null}}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(i=>(this.update(i),this.render(i),!0))}}exports.CascadingReel=O;
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/constants.ts","../src/utils/math.ts","../src/core/grid.ts","../src/core/loop.ts","../src/core/outro.ts","../src/normalize.ts","../src/core/spinQueue.ts","../src/core/state.ts","../src/render/canvasRenderer.ts","../src/CascadingReel.ts"],"sourcesContent":["export const DEFAULT_SPRITE_ELEMENTS_COUNT = 6;\nexport const GRID_COLS = 3;\nexport const GRID_ROWS = 3;\nexport const FLOW_OUTRO_ROW_GAP_MS = 34;\nexport const FLOW_ROW_BASE_SPACING_RATIO = 0.05;\nexport const FLOW_WIN_FLASH_MS = 1200;\nexport const FLOW_WIN_PULSE_PERIOD_MS = 1800;\nexport const FLOW_WIN_PULSE_AMPLITUDE = 0.075;\nexport const FLOW_WIN_PARTICLES_PER_CELL = 34;\nexport const DEFAULT_PARTICLE_COLOR_RGB: [number, number, number] = [255, 235, 110];\nexport const DEFAULT_WINNING_CELL_BORDER_RGBA: [number, number, number, number] = [\n 255, 255, 255, 0.78,\n];\nexport const FLOW_COLUMN_STAGGER_MS = 190;\nexport const FLOW_FALL_MS = 650;\nexport const FLOW_OUTRO_OVERLAP_MS = 220;\n","export function clamp(value: number, min: number, max: number): number {\n if (value < min) return min;\n if (value > max) return max;\n return value;\n}\n\nexport function easeOutCubic(t: number): number {\n return 1 - (1 - t) ** 3;\n}\n\nexport function randomInt(maxExclusive: number): number {\n return Math.floor(Math.random() * maxExclusive);\n}\n\nexport function normalizeSegment(segment: number, elementsCount: number): number {\n return ((segment % elementsCount) + elementsCount) % elementsCount;\n}\n\nexport function normalizeRgbChannel(value: number): number {\n return clamp(Math.round(value), 0, 255);\n}\n\nexport function normalizeAlphaChannel(value: number): number {\n return clamp(value, 0, 1);\n}\n","import { GRID_COLS, GRID_ROWS } from '../constants';\nimport type { CellPosition, SymbolId } from '../types';\nimport { randomInt } from '../utils/math';\n\nexport function createRandomGrid(spriteElementsCount: number): SymbolId[][] {\n const grid: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column: SymbolId[] = [];\n for (let row = 0; row < GRID_ROWS; row += 1) {\n column.push(randomInt(spriteElementsCount));\n }\n grid.push(column);\n }\n return grid;\n}\n\nexport function findMostFrequentCells(grid: SymbolId[][]): CellPosition[] {\n const counts = new Map<SymbolId, number>();\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const symbol = grid[col][row];\n counts.set(symbol, (counts.get(symbol) ?? 0) + 1);\n }\n }\n\n let selectedSymbol: SymbolId = grid[0][0];\n let maxCount = -1;\n for (const [symbol, count] of counts.entries()) {\n if (count > maxCount) {\n maxCount = count;\n selectedSymbol = symbol;\n }\n }\n\n const cells: CellPosition[] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (grid[col][row] === selectedSymbol) {\n cells.push({ col, row });\n }\n }\n }\n return cells;\n}\n\nexport function createZeroOffsets(): number[][] {\n return Array.from({ length: GRID_COLS }, () => Array.from({ length: GRID_ROWS }, () => 0));\n}\n\nexport function fillOffsets(offsets: number[][], value: number): void {\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n offsets[col][row] = value;\n }\n }\n}\n","export type RafStep = (now: number) => boolean;\n\nexport class RafLoop {\n private rafId: number | null = null;\n private step: RafStep | null = null;\n\n public start(step: RafStep): void {\n if (this.rafId !== null) return;\n this.step = step;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n public stop(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.step = null;\n }\n\n public isRunning(): boolean {\n return this.rafId !== null;\n }\n\n private readonly tick = (now: number): void => {\n if (!this.step) {\n this.stop();\n return;\n }\n\n const shouldContinue = this.step(now);\n if (shouldContinue === false) {\n this.stop();\n return;\n }\n\n if (this.rafId === null) return;\n this.rafId = requestAnimationFrame(this.tick);\n };\n}\n","import {\n FLOW_COLUMN_STAGGER_MS,\n FLOW_FALL_MS,\n FLOW_OUTRO_OVERLAP_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n FLOW_ROW_BASE_SPACING_RATIO,\n GRID_ROWS,\n} from '../constants';\nimport { clamp, easeOutCubic } from '../utils/math';\n\nexport function buildSequentialRowStartDelays(\n fromRowOffsets: [number, number, number],\n duration: number,\n gapMs: number,\n): [number, number, number] {\n const delays: [number, number, number] = [0, 0, 0];\n let nextDelay = 0;\n for (let row = GRID_ROWS - 1; row >= 0; row -= 1) {\n if (fromRowOffsets[row] === 0) {\n delays[row] = 0;\n continue;\n }\n delays[row] = nextDelay;\n const baseSpacing = Math.floor(duration * FLOW_ROW_BASE_SPACING_RATIO);\n nextDelay += baseSpacing + gapMs;\n }\n return delays;\n}\n\nexport function updateOutroOffsets(params: {\n now: number;\n height: number;\n boardY: number;\n cellH: number;\n scriptedOutroStartedAt: number;\n scriptedOutgoingOffsets: number[][];\n scriptedIncomingOffsets: number[][];\n}): { allOutgoingDone: boolean; allIncomingDone: boolean } {\n let allOutgoingDone = true;\n let allIncomingDone = true;\n\n const exitEpsilon = 2;\n const outgoingDistance = params.height - params.boardY + params.cellH + exitEpsilon;\n const outgoingOffsetsForOrder: [number, number, number] = [\n outgoingDistance,\n outgoingDistance,\n outgoingDistance,\n ];\n const incomingFromOffsets: [number, number, number] = [\n -params.cellH,\n -params.cellH * 2,\n -params.cellH * 3,\n ];\n const rowStartDelays = buildSequentialRowStartDelays(\n outgoingOffsetsForOrder,\n FLOW_FALL_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n );\n const incomingStartShift = FLOW_FALL_MS - FLOW_OUTRO_OVERLAP_MS;\n\n for (let col = 0; col < params.scriptedOutgoingOffsets.length; col += 1) {\n const columnElapsed =\n params.now - (params.scriptedOutroStartedAt + col * FLOW_COLUMN_STAGGER_MS);\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const rowElapsed = columnElapsed - rowStartDelays[row];\n\n if (rowElapsed <= 0) {\n params.scriptedOutgoingOffsets[col][row] = 0;\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allOutgoingDone = false;\n allIncomingDone = false;\n continue;\n }\n\n const tOut = clamp(rowElapsed / FLOW_FALL_MS, 0, 1);\n const easedOut = easeOutCubic(tOut);\n params.scriptedOutgoingOffsets[col][row] = outgoingDistance * easedOut;\n if (tOut < 1) allOutgoingDone = false;\n\n const incomingElapsed = rowElapsed - incomingStartShift;\n if (incomingElapsed <= 0) {\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allIncomingDone = false;\n continue;\n }\n\n const tIn = clamp(incomingElapsed / FLOW_FALL_MS, 0, 1);\n const easedIn = easeOutCubic(tIn);\n params.scriptedIncomingOffsets[col][row] = incomingFromOffsets[row] * (1 - easedIn);\n if (tIn < 1) allIncomingDone = false;\n }\n }\n\n return { allOutgoingDone, allIncomingDone };\n}\n","import {\n DEFAULT_PARTICLE_COLOR_RGB,\n DEFAULT_WINNING_CELL_BORDER_RGBA,\n GRID_COLS,\n GRID_ROWS,\n} from './constants';\nimport type { SpinState, SymbolId } from './types';\nimport { normalizeAlphaChannel, normalizeRgbChannel, normalizeSegment } from './utils/math';\n\nexport function normalizeParticleColor(color?: [number, number, number]): [number, number, number] {\n if (!color) return DEFAULT_PARTICLE_COLOR_RGB;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n ];\n}\n\nexport function normalizeParticleColorMode(mode?: 'solid' | 'rainbow'): 'solid' | 'rainbow' {\n if (mode === 'rainbow') return 'rainbow';\n return 'solid';\n}\n\nexport function normalizeWinningCellBorderColor(\n color?: [number, number, number, number],\n): [number, number, number, number] {\n if (!color) return DEFAULT_WINNING_CELL_BORDER_RGBA;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n normalizeAlphaChannel(color[3]),\n ];\n}\n\nexport function rowsToStopGrid(rows: number[][]): number[][] {\n if (rows.length !== GRID_ROWS) {\n throw new Error(`rows must contain ${GRID_ROWS} rows`);\n }\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (!Array.isArray(rows[row]) || rows[row].length !== GRID_COLS) {\n throw new Error(`rows[${row}] must contain ${GRID_COLS} columns`);\n }\n }\n\n return [\n [rows[0][0], rows[1][0], rows[2][0]],\n [rows[0][1], rows[1][1], rows[2][1]],\n [rows[0][2], rows[1][2], rows[2][2]],\n ];\n}\n\nexport function normalizeStopGrid(stopGrid: number[][], elementsCount: number): SymbolId[][] {\n if (stopGrid.length !== GRID_COLS) {\n throw new Error(`stopGrid must contain ${GRID_COLS} columns`);\n }\n\n const next: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column = stopGrid[col];\n if (!Array.isArray(column) || column.length !== GRID_ROWS) {\n throw new Error(`stopGrid[${col}] must contain ${GRID_ROWS} rows`);\n }\n next[col] = [\n normalizeSegment(column[0], elementsCount),\n normalizeSegment(column[1], elementsCount),\n normalizeSegment(column[2], elementsCount),\n ];\n }\n return next;\n}\n\nexport function normalizeInitialSegments(\n initialSegments: number[][],\n elementsCount: number,\n): SymbolId[][] {\n return normalizeStopGrid(rowsToStopGrid(initialSegments), elementsCount);\n}\n\nexport function cloneSpinState(state: SpinState): SpinState {\n return {\n stopGrid: state.stopGrid?.map((column) => [...column]),\n stopRows: state.stopRows?.map((row) => [...row]),\n finaleSequence: state.finaleSequence?.map((grid) => grid.map((column) => [...column])),\n finaleSequenceRows: state.finaleSequenceRows?.map((grid) => grid.map((row) => [...row])),\n callback: state.callback,\n };\n}\n","import { cloneSpinState } from '../normalize';\nimport type { SpinState } from '../types';\n\nexport class SpinQueueController {\n private queue: SpinState[];\n\n public constructor(initialQueue?: SpinState[]) {\n this.queue = (initialQueue ?? []).map((entry) => cloneSpinState(entry));\n }\n\n public hasPending(): boolean {\n return this.queue.length > 0;\n }\n\n public consume(): SpinState | null {\n if (this.queue.length === 0) return null;\n return this.queue.shift() ?? null;\n }\n}\n","import type { SpinPhase, SpinState } from '../types';\n\nexport type RuntimeState = {\n isSpinning: boolean;\n hasStartedFirstSpin: boolean;\n queueFinished: boolean;\n shouldHighlightCurrentSpin: boolean;\n activeSpinState: SpinState | null;\n phase: SpinPhase;\n winFlashStartedAt: number;\n};\n\nexport function createRuntimeState(): RuntimeState {\n return {\n isSpinning: false,\n hasStartedFirstSpin: false,\n queueFinished: false,\n shouldHighlightCurrentSpin: false,\n activeSpinState: null,\n phase: 'idle',\n winFlashStartedAt: 0,\n };\n}\n\nexport function beginSpin(\n state: RuntimeState,\n params: {\n activeSpinState: SpinState | null;\n shouldHighlightCurrentSpin: boolean;\n },\n): void {\n state.hasStartedFirstSpin = true;\n state.isSpinning = true;\n state.phase = 'outro';\n state.activeSpinState = params.activeSpinState;\n state.shouldHighlightCurrentSpin = params.shouldHighlightCurrentSpin;\n}\n\nexport function finishSpin(state: RuntimeState, hasPendingInQueue: boolean): void {\n state.phase = 'idle';\n state.isSpinning = false;\n state.shouldHighlightCurrentSpin = false;\n state.queueFinished = !hasPendingInQueue;\n state.activeSpinState = null;\n}\n\nexport function startWinFlash(state: RuntimeState, startedAt: number): void {\n state.winFlashStartedAt = startedAt;\n state.phase = 'winFlash';\n}\n\nexport function destroyState(state: RuntimeState): void {\n state.isSpinning = false;\n state.queueFinished = true;\n}\n","import {\n FLOW_WIN_FLASH_MS,\n FLOW_WIN_PARTICLES_PER_CELL,\n FLOW_WIN_PULSE_AMPLITUDE,\n FLOW_WIN_PULSE_PERIOD_MS,\n GRID_COLS,\n GRID_ROWS,\n} from '../constants';\nimport type { CellPosition, SpinPhase, SymbolId } from '../types';\nimport { clamp } from '../utils/math';\n\ntype DrawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n) => void;\n\nexport function drawGridLayer(params: {\n ctx: CanvasRenderingContext2D;\n phase: SpinPhase;\n grid: SymbolId[][];\n offsets: number[][] | null;\n columnSway?: [number, number, number];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n width: number;\n height: number;\n isWinningCell: (col: number, row: number) => boolean;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n const sway = params.columnSway ?? [0, 0, 0];\n\n for (let col = 0; col < GRID_COLS; col += 1) {\n const x = params.boardX + col * params.cellW;\n const swayY = sway[col] ?? 0;\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (params.phase === 'winFlash' && params.isWinningCell(col, row)) continue;\n const offsetY = params.offsets ? params.offsets[col][row] : 0;\n if (!Number.isFinite(offsetY)) continue;\n\n const y = params.boardY + row * params.cellH + offsetY + swayY;\n if (y > params.height || y + params.cellH < 0) continue;\n\n params.drawSpriteCell(params.grid[col][row], x, y, params.cellW, params.cellH);\n }\n }\n}\n\nfunction hash01(a: number, b: number, c: number, d: number): number {\n const value = Math.sin(a * 127.1 + b * 311.7 + c * 74.7 + d * 19.3) * 43758.5453;\n return value - Math.floor(value);\n}\n\nexport function drawWinningEffects(params: {\n ctx: CanvasRenderingContext2D;\n now: number;\n phase: SpinPhase;\n winFlashStartedAt: number;\n winningCells: CellPosition[];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n winningCellBorderRgba: [number, number, number, number];\n getCell: (col: number, row: number) => SymbolId;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n if (params.winningCells.length === 0) return;\n if (params.phase !== 'winFlash') return;\n\n const elapsed = Math.max(0, params.now - params.winFlashStartedAt);\n const cycleProgress = (elapsed % FLOW_WIN_FLASH_MS) / FLOW_WIN_FLASH_MS;\n const pulseProgress = (elapsed % FLOW_WIN_PULSE_PERIOD_MS) / FLOW_WIN_PULSE_PERIOD_MS;\n const pulse = 1 + Math.sin(pulseProgress * Math.PI * 2) * FLOW_WIN_PULSE_AMPLITUDE;\n const easeOut = 1 - (1 - cycleProgress) ** 2;\n\n for (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawWinningCellBorder(\n params.ctx,\n x,\n y,\n params.cellW,\n params.cellH,\n params.winningCellBorderRgba,\n elapsed,\n );\n\n const symbol = params.getCell(cell.col, cell.row);\n const scaledW = params.cellW * pulse;\n const scaledH = params.cellH * pulse;\n const offsetX = (params.cellW - scaledW) * 0.5;\n const offsetY = (params.cellH - scaledH) * 0.5;\n\n params.ctx.save();\n params.drawSpriteCell(symbol, x + offsetX, y + offsetY, scaledW, scaledH);\n params.ctx.restore();\n\n drawCellParticleBurst({\n ctx: params.ctx,\n cell,\n x,\n y,\n cycleProgress,\n easeOut,\n boardX: params.boardX,\n boardY: params.boardY,\n cellW: params.cellW,\n cellH: params.cellH,\n particleColorMode: params.particleColorMode,\n particleColorRgb: params.particleColorRgb,\n });\n }\n}\n\nfunction drawWinningCellBorder(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n cellW: number,\n cellH: number,\n color: [number, number, number, number],\n elapsed: number,\n): void {\n const inset = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.04));\n const lineWidth = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.025));\n const [, , , a] = color;\n const hue = (elapsed * 0.15) % 360;\n\n ctx.save();\n ctx.lineWidth = lineWidth;\n ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${a})`;\n const halfLine = lineWidth * 0.5;\n ctx.strokeRect(\n x + inset + halfLine,\n y + inset + halfLine,\n cellW - inset * 2 - lineWidth,\n cellH - inset * 2 - lineWidth,\n );\n ctx.restore();\n}\n\nfunction drawCellParticleBurst(params: {\n ctx: CanvasRenderingContext2D;\n cell: CellPosition;\n x: number;\n y: number;\n cycleProgress: number;\n easeOut: number;\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n}): void {\n const centerX = params.x + params.cellW * 0.5;\n const centerY = params.y + params.cellH * 0.5;\n const maxDistance = Math.min(params.cellW, params.cellH) * 0.72;\n const baseRadius = Math.min(params.cellW, params.cellH) * 0.028;\n const globalFade = 0.92 + (1 - params.cycleProgress) * 0.08;\n\n params.ctx.save();\n params.ctx.beginPath();\n params.ctx.rect(params.boardX, params.boardY, params.cellW * GRID_COLS, params.cellH * GRID_ROWS);\n params.ctx.clip();\n\n for (let i = 0; i < FLOW_WIN_PARTICLES_PER_CELL; i += 1) {\n const seedA = hash01(params.cell.col, params.cell.row, i, 1);\n const seedB = hash01(params.cell.col, params.cell.row, i, 2);\n const seedC = hash01(params.cell.col, params.cell.row, i, 3);\n const phaseOffset = hash01(params.cell.col, params.cell.row, i, 4) * 0.28;\n const twinkleSeed = hash01(params.cell.col, params.cell.row, i, 5);\n const particleT = clamp((params.cycleProgress - phaseOffset) / (1 - phaseOffset), 0, 1);\n if (particleT <= 0) continue;\n\n const direction = seedA * Math.PI * 2;\n const distance =\n maxDistance * particleT * (0.35 + seedB * 0.65) * (0.85 + params.easeOut * 0.15);\n const px = centerX + Math.cos(direction) * distance;\n const py = centerY + Math.sin(direction) * distance;\n const twinkle =\n 0.7 +\n 0.9 * Math.max(0, Math.sin((params.cycleProgress * 11 + twinkleSeed * 2) * Math.PI * 2));\n const radius = Math.max(1, baseRadius * (0.55 + seedC * 0.6) * (1 - particleT * 0.5));\n const alpha = clamp((0.9 + twinkle * 0.2) * globalFade, 0.9, 1);\n if (alpha <= 0) continue;\n\n if (params.particleColorMode === 'rainbow') {\n const hue =\n (seedA * 360 + params.cycleProgress * 240 + params.cell.col * 38 + params.cell.row * 22) %\n 360;\n params.ctx.fillStyle = `hsla(${hue}, 98%, 64%, ${alpha})`;\n } else {\n const [r, g, b] = params.particleColorRgb;\n params.ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n params.ctx.beginPath();\n params.ctx.arc(px, py, radius, 0, Math.PI * 2);\n params.ctx.fill();\n }\n params.ctx.restore();\n}\n","import { DEFAULT_SPRITE_ELEMENTS_COUNT, GRID_COLS, GRID_ROWS } from './constants';\nimport {\n createRandomGrid,\n createZeroOffsets,\n fillOffsets,\n findMostFrequentCells,\n} from './core/grid';\nimport { RafLoop } from './core/loop';\nimport { updateOutroOffsets } from './core/outro';\nimport { SpinQueueController } from './core/spinQueue';\nimport {\n beginSpin,\n createRuntimeState,\n destroyState,\n finishSpin,\n startWinFlash,\n} from './core/state';\nimport {\n normalizeInitialSegments,\n normalizeParticleColor,\n normalizeParticleColorMode,\n normalizeStopGrid,\n normalizeWinningCellBorderColor,\n rowsToStopGrid,\n} from './normalize';\nimport { drawGridLayer, drawWinningEffects } from './render/canvasRenderer';\nimport type { CascadingReelConfig, CellPosition, SymbolId } from './types';\nimport { normalizeSegment } from './utils/math';\n\nexport class CascadingReel {\n private readonly canvas: HTMLCanvasElement;\n private readonly container: HTMLElement;\n private readonly button?: HTMLButtonElement;\n private readonly ctx: CanvasRenderingContext2D;\n private readonly spinQueueController: SpinQueueController;\n private readonly spriteUrl?: string;\n private readonly spriteElementsCount: number;\n private readonly highlightInitialWinningCells: boolean;\n private readonly particleColorRgb: [number, number, number];\n private readonly particleColorMode: 'solid' | 'rainbow';\n private readonly winningCellBorderRgba: [number, number, number, number];\n\n private spriteImage: HTMLImageElement | null = null;\n private readonly rafLoop = new RafLoop();\n private readonly runtime = createRuntimeState();\n private dpr = 1;\n private width = 0;\n private height = 0;\n private cellW = 0;\n private cellH = 0;\n private boardX = 0;\n private boardY = 0;\n private scriptedCascadeQueue: number[][][] = [];\n private scriptedOutgoingGrid: SymbolId[][] | null = null;\n private scriptedPendingGrid: SymbolId[][] | null = null;\n private scriptedOutroStartedAt = 0;\n private scriptedOutgoingOffsets: number[][] = createZeroOffsets();\n private scriptedIncomingOffsets: number[][] = createZeroOffsets();\n private winningCells: CellPosition[] = [];\n private grid: SymbolId[][];\n\n public constructor(config: CascadingReelConfig) {\n this.canvas = config.canvas;\n this.container = config.container;\n this.button = config.button;\n this.spriteUrl = config.sprite;\n this.spriteElementsCount = Math.max(\n 1,\n config.spriteElementsCount ?? DEFAULT_SPRITE_ELEMENTS_COUNT,\n );\n this.highlightInitialWinningCells = config.highlightInitialWinningCells !== false;\n this.spinQueueController = new SpinQueueController(config.queuedSpinStates);\n this.particleColorRgb = normalizeParticleColor(config.particleColorRgb);\n this.particleColorMode = normalizeParticleColorMode(config.particleColorMode);\n this.winningCellBorderRgba = normalizeWinningCellBorderColor(config.winningCellBorderRgba);\n\n const context = this.canvas.getContext('2d');\n if (!context) {\n throw new Error('2D canvas context is not available');\n }\n this.ctx = context;\n this.grid = config.initialSegments\n ? normalizeInitialSegments(config.initialSegments, this.spriteElementsCount)\n : createRandomGrid(this.spriteElementsCount);\n }\n\n public async init(): Promise<void> {\n this.bindEvents();\n this.resize();\n await this.loadSpriteIfProvided();\n this.applyInitialHighlightIfNeeded();\n this.startLoop();\n }\n\n public destroy(): void {\n this.unbindEvents();\n this.rafLoop.stop();\n destroyState(this.runtime);\n this.clearWinningCells();\n }\n\n public spin(): void {\n if (this.runtime.isSpinning) return;\n if (this.runtime.queueFinished) return;\n if (!this.spinQueueController.hasPending()) {\n this.runtime.queueFinished = true;\n if (this.button) this.button.disabled = true;\n return;\n }\n\n const activeSpinState = this.spinQueueController.consume();\n const shouldHighlightCurrentSpin = !this.spinQueueController.hasPending();\n beginSpin(this.runtime, {\n activeSpinState,\n shouldHighlightCurrentSpin,\n });\n\n if (this.button) this.button.disabled = true;\n\n const scriptedSource = this.runtime.activeSpinState?.finaleSequenceRows\n ? this.runtime.activeSpinState.finaleSequenceRows.map((rows) => rowsToStopGrid(rows))\n : (this.runtime.activeSpinState?.finaleSequence ?? []);\n this.scriptedCascadeQueue = scriptedSource.map((grid) => grid.map((column) => [...column]));\n this.clearWinningCells();\n\n const stopGridSource = this.runtime.activeSpinState?.stopRows\n ? rowsToStopGrid(this.runtime.activeSpinState.stopRows)\n : this.runtime.activeSpinState?.stopGrid;\n const nextGrid = this.getNextGrid(stopGridSource);\n this.startOutroTransition(nextGrid, performance.now());\n }\n\n private bindEvents(): void {\n window.addEventListener('resize', this.resize);\n this.button?.addEventListener('click', this.onSpinClick);\n }\n\n private unbindEvents(): void {\n window.removeEventListener('resize', this.resize);\n this.button?.removeEventListener('click', this.onSpinClick);\n }\n\n private readonly onSpinClick = (): void => {\n if (this.runtime.isSpinning) return;\n this.spin();\n };\n\n private getNextGrid(stopGrid?: number[][]): SymbolId[][] {\n if (!stopGrid) return createRandomGrid(this.spriteElementsCount);\n return normalizeStopGrid(stopGrid, this.spriteElementsCount);\n }\n\n private update(now: number): void {\n if (!this.runtime.isSpinning) return;\n if (this.runtime.phase === 'outro') {\n this.updateScriptedOutro(now);\n return;\n }\n if (this.runtime.phase === 'winFlash') return;\n }\n\n private updateScriptedOutro(now: number): void {\n if (!this.scriptedOutgoingGrid || !this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n const { allOutgoingDone, allIncomingDone } = updateOutroOffsets({\n now,\n height: this.height,\n boardY: this.boardY,\n cellH: this.cellH,\n scriptedOutroStartedAt: this.scriptedOutroStartedAt,\n scriptedOutgoingOffsets: this.scriptedOutgoingOffsets,\n scriptedIncomingOffsets: this.scriptedIncomingOffsets,\n });\n\n if (!allOutgoingDone || !allIncomingDone) return;\n if (!this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n this.grid = this.scriptedPendingGrid;\n this.scriptedOutgoingGrid = null;\n this.scriptedPendingGrid = null;\n this.clearWinningCells();\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n fillOffsets(this.scriptedIncomingOffsets, 0);\n\n if (this.tryStartScriptedCascade(now)) return;\n if (!this.runtime.shouldHighlightCurrentSpin) {\n this.finishSpinWithUi();\n return;\n }\n\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, now);\n }\n\n private tryStartScriptedCascade(now: number): boolean {\n if (this.scriptedCascadeQueue.length === 0) return false;\n const nextGrid = this.scriptedCascadeQueue.shift();\n if (!nextGrid) return false;\n this.startOutroTransition(normalizeStopGrid(nextGrid, this.spriteElementsCount), now);\n return true;\n }\n\n private startOutroTransition(nextGrid: SymbolId[][], now: number): void {\n this.scriptedOutgoingGrid = this.grid.map((column) => [...column]);\n this.scriptedPendingGrid = nextGrid.map((column) => [...column]);\n fillOffsets(this.scriptedIncomingOffsets, Number.NaN);\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n this.clearWinningCells();\n this.runtime.phase = 'outro';\n this.scriptedOutroStartedAt = now;\n }\n\n private finishSpinWithUi(): void {\n const callback = this.runtime.activeSpinState?.callback;\n finishSpin(this.runtime, this.spinQueueController.hasPending());\n if (this.button) this.button.disabled = this.runtime.queueFinished;\n callback?.();\n }\n\n private applyInitialHighlightIfNeeded(): void {\n if (!this.highlightInitialWinningCells) return;\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, performance.now());\n }\n\n private static readonly ZERO_SWAY: [number, number, number] = [0, 0, 0];\n\n private render(now: number): void {\n this.ctx.clearRect(0, 0, this.width, this.height);\n if (this.runtime.phase === 'outro' && this.scriptedOutgoingGrid && this.scriptedPendingGrid) {\n this.drawLayer(\n this.scriptedOutgoingGrid,\n this.scriptedOutgoingOffsets,\n CascadingReel.ZERO_SWAY,\n );\n this.drawLayer(\n this.scriptedPendingGrid,\n this.scriptedIncomingOffsets,\n CascadingReel.ZERO_SWAY,\n );\n } else {\n this.drawBaseGrid();\n }\n\n drawWinningEffects({\n ctx: this.ctx,\n now,\n phase: this.runtime.phase,\n winFlashStartedAt: this.runtime.winFlashStartedAt,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private drawBaseGrid(): void {\n this.drawLayer(this.grid, null, CascadingReel.ZERO_SWAY);\n }\n\n private drawLayer(\n grid: SymbolId[][],\n offsets: number[][] | null,\n columnSway: [number, number, number],\n ): void {\n drawGridLayer({\n ctx: this.ctx,\n phase: this.runtime.phase,\n grid,\n offsets,\n columnSway,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n width: this.width,\n height: this.height,\n isWinningCell: this.isWinningCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private readonly getCell = (col: number, row: number): SymbolId => {\n return this.grid[col][row];\n };\n\n private readonly isWinningCell = (col: number, row: number): boolean => {\n return this.winningCells.some((cell) => cell.col === col && cell.row === row);\n };\n\n private readonly drawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void => {\n const image = this.spriteImage;\n if (!image) return;\n\n const segmentIndex = normalizeSegment(symbolId, this.spriteElementsCount);\n const segmentHeight = image.height / this.spriteElementsCount;\n const sourceY = Math.floor(segmentIndex * segmentHeight);\n const sourceHeight = Math.floor(segmentHeight);\n\n this.ctx.drawImage(image, 0, sourceY, image.width, sourceHeight, x, y, width, height);\n };\n\n private clearWinningCells(): void {\n this.winningCells = [];\n }\n\n private readonly resize = (): void => {\n const bounds = this.container.getBoundingClientRect();\n this.dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n const side = Math.max(300, Math.floor(bounds.width * this.dpr));\n this.width = side;\n this.height = side;\n const squareSize = Math.floor(Math.min(this.width / GRID_COLS, this.height / GRID_ROWS));\n this.cellW = squareSize;\n this.cellH = squareSize;\n this.boardX = Math.floor((this.width - this.cellW * GRID_COLS) / 2);\n this.boardY = Math.floor((this.height - this.cellH * GRID_ROWS) / 2);\n\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n };\n\n private async loadSpriteIfProvided(): Promise<void> {\n if (!this.spriteUrl) return;\n const image = new Image();\n image.decoding = 'async';\n image.src = this.spriteUrl;\n try {\n await image.decode();\n this.spriteImage = image;\n } catch {\n this.spriteImage = null;\n }\n }\n\n private startLoop(): void {\n if (this.rafLoop.isRunning()) return;\n this.rafLoop.start((time: number): boolean => {\n this.update(time);\n this.render(time);\n return true;\n });\n }\n}\n"],"names":["DEFAULT_SPRITE_ELEMENTS_COUNT","GRID_COLS","GRID_ROWS","FLOW_OUTRO_ROW_GAP_MS","FLOW_ROW_BASE_SPACING_RATIO","FLOW_WIN_FLASH_MS","FLOW_WIN_PULSE_PERIOD_MS","FLOW_WIN_PULSE_AMPLITUDE","FLOW_WIN_PARTICLES_PER_CELL","DEFAULT_PARTICLE_COLOR_RGB","DEFAULT_WINNING_CELL_BORDER_RGBA","FLOW_COLUMN_STAGGER_MS","FLOW_FALL_MS","FLOW_OUTRO_OVERLAP_MS","clamp","value","min","max","easeOutCubic","randomInt","maxExclusive","normalizeSegment","segment","elementsCount","normalizeRgbChannel","normalizeAlphaChannel","createRandomGrid","spriteElementsCount","grid","col","column","row","findMostFrequentCells","counts","symbol","selectedSymbol","maxCount","count","cells","createZeroOffsets","fillOffsets","offsets","RafLoop","step","now","buildSequentialRowStartDelays","fromRowOffsets","duration","gapMs","delays","nextDelay","baseSpacing","updateOutroOffsets","params","allOutgoingDone","allIncomingDone","outgoingDistance","outgoingOffsetsForOrder","incomingFromOffsets","rowStartDelays","incomingStartShift","columnElapsed","rowElapsed","tOut","easedOut","incomingElapsed","tIn","easedIn","normalizeParticleColor","color","normalizeParticleColorMode","mode","normalizeWinningCellBorderColor","rowsToStopGrid","rows","normalizeStopGrid","stopGrid","next","normalizeInitialSegments","initialSegments","cloneSpinState","state","SpinQueueController","initialQueue","entry","createRuntimeState","beginSpin","finishSpin","hasPendingInQueue","startWinFlash","startedAt","destroyState","drawGridLayer","sway","x","swayY","offsetY","y","hash01","a","b","c","d","drawWinningEffects","elapsed","cycleProgress","pulseProgress","pulse","easeOut","cell","drawWinningCellBorder","scaledW","scaledH","offsetX","drawCellParticleBurst","ctx","cellW","cellH","inset","lineWidth","hue","halfLine","centerX","centerY","maxDistance","baseRadius","globalFade","i","seedA","seedB","seedC","phaseOffset","twinkleSeed","particleT","direction","distance","px","py","twinkle","radius","alpha","r","g","CascadingReel","config","context","activeSpinState","shouldHighlightCurrentSpin","scriptedSource","stopGridSource","nextGrid","callback","columnSway","symbolId","width","height","image","segmentIndex","segmentHeight","sourceY","sourceHeight","bounds","side","squareSize","time"],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ export declare class CascadingReel {
2
+ private readonly canvas;
3
+ private readonly container;
4
+ private readonly button?;
5
+ private readonly ctx;
6
+ private readonly spinQueueController;
7
+ private readonly spriteUrl?;
8
+ private readonly spriteElementsCount;
9
+ private readonly highlightInitialWinningCells;
10
+ private readonly particleColorRgb;
11
+ private readonly particleColorMode;
12
+ private readonly winningCellBorderRgba;
13
+ private spriteImage;
14
+ private readonly rafLoop;
15
+ private readonly runtime;
16
+ private dpr;
17
+ private width;
18
+ private height;
19
+ private cellW;
20
+ private cellH;
21
+ private boardX;
22
+ private boardY;
23
+ private scriptedCascadeQueue;
24
+ private scriptedOutgoingGrid;
25
+ private scriptedPendingGrid;
26
+ private scriptedOutroStartedAt;
27
+ private scriptedOutgoingOffsets;
28
+ private scriptedIncomingOffsets;
29
+ private winningCells;
30
+ private grid;
31
+ constructor(config: CascadingReelConfig);
32
+ init(): Promise<void>;
33
+ destroy(): void;
34
+ spin(): void;
35
+ private bindEvents;
36
+ private unbindEvents;
37
+ private readonly onSpinClick;
38
+ private getNextGrid;
39
+ private update;
40
+ private updateScriptedOutro;
41
+ private tryStartScriptedCascade;
42
+ private startOutroTransition;
43
+ private finishSpinWithUi;
44
+ private applyInitialHighlightIfNeeded;
45
+ private static readonly ZERO_SWAY;
46
+ private render;
47
+ private drawBaseGrid;
48
+ private drawLayer;
49
+ private readonly getCell;
50
+ private readonly isWinningCell;
51
+ private readonly drawSpriteCell;
52
+ private clearWinningCells;
53
+ private readonly resize;
54
+ private loadSpriteIfProvided;
55
+ private startLoop;
56
+ }
57
+
58
+ export declare type CascadingReelConfig = {
59
+ canvas: HTMLCanvasElement;
60
+ container: HTMLElement;
61
+ button?: HTMLButtonElement;
62
+ sprite?: string;
63
+ spriteElementsCount?: number;
64
+ initialSegments?: number[][];
65
+ highlightInitialWinningCells?: boolean;
66
+ queuedSpinStates?: SpinState[];
67
+ particleColorRgb?: [number, number, number];
68
+ particleColorMode?: 'solid' | 'rainbow';
69
+ winningCellBorderRgba?: [number, number, number, number];
70
+ };
71
+
72
+ export declare type SpinState = {
73
+ stopGrid?: number[][];
74
+ stopRows?: number[][];
75
+ finaleSequence?: number[][][];
76
+ finaleSequenceRows?: number[][][];
77
+ callback?: () => void;
78
+ };
79
+
80
+ export { }
@@ -0,0 +1,514 @@
1
+ const v = [255, 235, 110], N = [
2
+ 255,
3
+ 255,
4
+ 255,
5
+ 0.78
6
+ ], T = 190, w = 650, U = 220;
7
+ function S(t, i, e) {
8
+ return t < i ? i : t > e ? e : t;
9
+ }
10
+ function D(t) {
11
+ return 1 - (1 - t) ** 3;
12
+ }
13
+ function q(t) {
14
+ return Math.floor(Math.random() * t);
15
+ }
16
+ function I(t, i) {
17
+ return (t % i + i) % i;
18
+ }
19
+ function g(t) {
20
+ return S(Math.round(t), 0, 255);
21
+ }
22
+ function Y(t) {
23
+ return S(t, 0, 1);
24
+ }
25
+ function P(t) {
26
+ const i = [];
27
+ for (let e = 0; e < 3; e += 1) {
28
+ const n = [];
29
+ for (let r = 0; r < 3; r += 1)
30
+ n.push(q(t));
31
+ i.push(n);
32
+ }
33
+ return i;
34
+ }
35
+ function F(t) {
36
+ const i = /* @__PURE__ */ new Map();
37
+ for (let l = 0; l < 3; l += 1)
38
+ for (let s = 0; s < 3; s += 1) {
39
+ const o = t[l][s];
40
+ i.set(o, (i.get(o) ?? 0) + 1);
41
+ }
42
+ let e = t[0][0], n = -1;
43
+ for (const [l, s] of i.entries())
44
+ s > n && (n = s, e = l);
45
+ const r = [];
46
+ for (let l = 0; l < 3; l += 1)
47
+ for (let s = 0; s < 3; s += 1)
48
+ t[l][s] === e && r.push({ col: l, row: s });
49
+ return r;
50
+ }
51
+ function A() {
52
+ return Array.from({ length: 3 }, () => Array.from({ length: 3 }, () => 0));
53
+ }
54
+ function _(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;
58
+ }
59
+ class B {
60
+ rafId = null;
61
+ step = null;
62
+ start(i) {
63
+ this.rafId === null && (this.step = i, this.rafId = requestAnimationFrame(this.tick));
64
+ }
65
+ stop() {
66
+ this.rafId !== null && (cancelAnimationFrame(this.rafId), this.rafId = null), this.step = null;
67
+ }
68
+ isRunning() {
69
+ return this.rafId !== null;
70
+ }
71
+ tick = (i) => {
72
+ if (!this.step) {
73
+ this.stop();
74
+ return;
75
+ }
76
+ if (this.step(i) === !1) {
77
+ this.stop();
78
+ return;
79
+ }
80
+ this.rafId !== null && (this.rafId = requestAnimationFrame(this.tick));
81
+ };
82
+ }
83
+ function k(t, i, e) {
84
+ const n = [0, 0, 0];
85
+ let r = 0;
86
+ for (let l = 2; l >= 0; l -= 1) {
87
+ if (t[l] === 0) {
88
+ n[l] = 0;
89
+ continue;
90
+ }
91
+ n[l] = r;
92
+ const s = Math.floor(i * 0.05);
93
+ r += s + e;
94
+ }
95
+ return n;
96
+ }
97
+ function z(t) {
98
+ let i = !0, e = !0;
99
+ const r = t.height - t.boardY + t.cellH + 2, l = [
100
+ r,
101
+ r,
102
+ r
103
+ ], s = [
104
+ -t.cellH,
105
+ -t.cellH * 2,
106
+ -t.cellH * 3
107
+ ], o = k(
108
+ l,
109
+ w,
110
+ 34
111
+ ), u = w - U;
112
+ for (let h = 0; h < t.scriptedOutgoingOffsets.length; h += 1) {
113
+ const d = t.now - (t.scriptedOutroStartedAt + h * T);
114
+ for (let c = 0; c < 3; c += 1) {
115
+ const a = d - o[c];
116
+ if (a <= 0) {
117
+ t.scriptedOutgoingOffsets[h][c] = 0, t.scriptedIncomingOffsets[h][c] = Number.NaN, i = !1, e = !1;
118
+ continue;
119
+ }
120
+ const f = S(a / w, 0, 1), p = D(f);
121
+ t.scriptedOutgoingOffsets[h][c] = r * p, f < 1 && (i = !1);
122
+ const C = a - u;
123
+ if (C <= 0) {
124
+ t.scriptedIncomingOffsets[h][c] = Number.NaN, e = !1;
125
+ continue;
126
+ }
127
+ const R = S(C / w, 0, 1), b = D(R);
128
+ t.scriptedIncomingOffsets[h][c] = s[c] * (1 - b), R < 1 && (e = !1);
129
+ }
130
+ }
131
+ return { allOutgoingDone: i, allIncomingDone: e };
132
+ }
133
+ function $(t) {
134
+ return t ? [
135
+ g(t[0]),
136
+ g(t[1]),
137
+ g(t[2])
138
+ ] : v;
139
+ }
140
+ function X(t) {
141
+ return t === "rainbow" ? "rainbow" : "solid";
142
+ }
143
+ function Q(t) {
144
+ return t ? [
145
+ g(t[0]),
146
+ g(t[1]),
147
+ g(t[2]),
148
+ Y(t[3])
149
+ ] : N;
150
+ }
151
+ function E(t) {
152
+ if (t.length !== 3)
153
+ throw new Error("rows must contain 3 rows");
154
+ for (let i = 0; i < 3; i += 1)
155
+ if (!Array.isArray(t[i]) || t[i].length !== 3)
156
+ throw new Error(`rows[${i}] must contain 3 columns`);
157
+ return [
158
+ [t[0][0], t[1][0], t[2][0]],
159
+ [t[0][1], t[1][1], t[2][1]],
160
+ [t[0][2], t[1][2], t[2][2]]
161
+ ];
162
+ }
163
+ function M(t, i) {
164
+ if (t.length !== 3)
165
+ throw new Error("stopGrid must contain 3 columns");
166
+ const e = [];
167
+ for (let n = 0; n < 3; n += 1) {
168
+ const r = t[n];
169
+ if (!Array.isArray(r) || r.length !== 3)
170
+ throw new Error(`stopGrid[${n}] must contain 3 rows`);
171
+ e[n] = [
172
+ I(r[0], i),
173
+ I(r[1], i),
174
+ I(r[2], i)
175
+ ];
176
+ }
177
+ return e;
178
+ }
179
+ function Z(t, i) {
180
+ return M(E(t), i);
181
+ }
182
+ function V(t) {
183
+ return {
184
+ stopGrid: t.stopGrid?.map((i) => [...i]),
185
+ stopRows: t.stopRows?.map((i) => [...i]),
186
+ finaleSequence: t.finaleSequence?.map((i) => i.map((e) => [...e])),
187
+ finaleSequenceRows: t.finaleSequenceRows?.map((i) => i.map((e) => [...e])),
188
+ callback: t.callback
189
+ };
190
+ }
191
+ class j {
192
+ queue;
193
+ constructor(i) {
194
+ this.queue = (i ?? []).map((e) => V(e));
195
+ }
196
+ hasPending() {
197
+ return this.queue.length > 0;
198
+ }
199
+ consume() {
200
+ return this.queue.length === 0 ? null : this.queue.shift() ?? null;
201
+ }
202
+ }
203
+ function J() {
204
+ return {
205
+ isSpinning: !1,
206
+ hasStartedFirstSpin: !1,
207
+ queueFinished: !1,
208
+ shouldHighlightCurrentSpin: !1,
209
+ activeSpinState: null,
210
+ phase: "idle",
211
+ winFlashStartedAt: 0
212
+ };
213
+ }
214
+ function K(t, i) {
215
+ t.hasStartedFirstSpin = !0, t.isSpinning = !0, t.phase = "outro", t.activeSpinState = i.activeSpinState, t.shouldHighlightCurrentSpin = i.shouldHighlightCurrentSpin;
216
+ }
217
+ function tt(t, i) {
218
+ t.phase = "idle", t.isSpinning = !1, t.shouldHighlightCurrentSpin = !1, t.queueFinished = !i, t.activeSpinState = null;
219
+ }
220
+ function x(t, i) {
221
+ t.winFlashStartedAt = i, t.phase = "winFlash";
222
+ }
223
+ function it(t) {
224
+ t.isSpinning = !1, t.queueFinished = !0;
225
+ }
226
+ function et(t) {
227
+ const i = t.columnSway ?? [0, 0, 0];
228
+ for (let e = 0; e < 3; e += 1) {
229
+ const n = t.boardX + e * t.cellW, r = i[e] ?? 0;
230
+ for (let l = 0; l < 3; l += 1) {
231
+ if (t.phase === "winFlash" && t.isWinningCell(e, l)) continue;
232
+ const s = t.offsets ? t.offsets[e][l] : 0;
233
+ if (!Number.isFinite(s)) continue;
234
+ const o = t.boardY + l * t.cellH + s + r;
235
+ o > t.height || o + t.cellH < 0 || t.drawSpriteCell(t.grid[e][l], n, o, t.cellW, t.cellH);
236
+ }
237
+ }
238
+ }
239
+ function O(t, i, e, n) {
240
+ const r = Math.sin(t * 127.1 + i * 311.7 + e * 74.7 + n * 19.3) * 43758.5453;
241
+ return r - Math.floor(r);
242
+ }
243
+ function nt(t) {
244
+ if (t.winningCells.length === 0 || t.phase !== "winFlash") return;
245
+ const i = Math.max(0, t.now - t.winFlashStartedAt), e = i % 1200 / 1200, n = i % 1800 / 1800, r = 1 + Math.sin(n * Math.PI * 2) * 0.075, l = 1 - (1 - e) ** 2;
246
+ for (const s of t.winningCells) {
247
+ const o = t.boardX + s.col * t.cellW, u = t.boardY + s.row * t.cellH;
248
+ st(
249
+ t.ctx,
250
+ o,
251
+ u,
252
+ t.cellW,
253
+ t.cellH,
254
+ t.winningCellBorderRgba,
255
+ i
256
+ );
257
+ const h = t.getCell(s.col, s.row), d = t.cellW * r, c = t.cellH * r, a = (t.cellW - d) * 0.5, f = (t.cellH - c) * 0.5;
258
+ t.ctx.save(), t.drawSpriteCell(h, o + a, u + f, d, c), t.ctx.restore(), rt({
259
+ ctx: t.ctx,
260
+ cell: s,
261
+ x: o,
262
+ y: u,
263
+ cycleProgress: e,
264
+ easeOut: l,
265
+ boardX: t.boardX,
266
+ boardY: t.boardY,
267
+ cellW: t.cellW,
268
+ cellH: t.cellH,
269
+ particleColorMode: t.particleColorMode,
270
+ particleColorRgb: t.particleColorRgb
271
+ });
272
+ }
273
+ }
274
+ function st(t, i, e, n, r, l, s) {
275
+ const o = Math.max(1, Math.floor(Math.min(n, r) * 0.04)), u = Math.max(1, Math.floor(Math.min(n, r) * 0.025)), [, , , h] = l, d = s * 0.15 % 360;
276
+ t.save(), t.lineWidth = u, t.strokeStyle = `hsla(${d}, 90%, 70%, ${h})`;
277
+ const c = u * 0.5;
278
+ t.strokeRect(
279
+ i + o + c,
280
+ e + o + c,
281
+ n - o * 2 - u,
282
+ r - o * 2 - u
283
+ ), t.restore();
284
+ }
285
+ function rt(t) {
286
+ const i = t.x + t.cellW * 0.5, e = t.y + t.cellH * 0.5, n = Math.min(t.cellW, t.cellH) * 0.72, r = Math.min(t.cellW, t.cellH) * 0.028, l = 0.92 + (1 - t.cycleProgress) * 0.08;
287
+ t.ctx.save(), t.ctx.beginPath(), t.ctx.rect(t.boardX, t.boardY, t.cellW * 3, t.cellH * 3), t.ctx.clip();
288
+ for (let s = 0; s < 34; s += 1) {
289
+ const o = O(t.cell.col, t.cell.row, s, 1), u = O(t.cell.col, t.cell.row, s, 2), h = O(t.cell.col, t.cell.row, s, 3), d = O(t.cell.col, t.cell.row, s, 4) * 0.28, c = O(t.cell.col, t.cell.row, s, 5), a = S((t.cycleProgress - d) / (1 - d), 0, 1);
290
+ if (a <= 0) continue;
291
+ const f = o * Math.PI * 2, p = n * a * (0.35 + u * 0.65) * (0.85 + t.easeOut * 0.15), C = i + Math.cos(f) * p, R = e + Math.sin(f) * p, b = 0.7 + 0.9 * Math.max(0, Math.sin((t.cycleProgress * 11 + c * 2) * Math.PI * 2)), y = Math.max(1, r * (0.55 + h * 0.6) * (1 - a * 0.5)), L = S((0.9 + b * 0.2) * l, 0.9, 1);
292
+ if (!(L <= 0)) {
293
+ if (t.particleColorMode === "rainbow") {
294
+ const G = (o * 360 + t.cycleProgress * 240 + t.cell.col * 38 + t.cell.row * 22) % 360;
295
+ t.ctx.fillStyle = `hsla(${G}, 98%, 64%, ${L})`;
296
+ } else {
297
+ const [G, m, H] = t.particleColorRgb;
298
+ t.ctx.fillStyle = `rgba(${G}, ${m}, ${H}, ${L})`;
299
+ }
300
+ t.ctx.beginPath(), t.ctx.arc(C, R, y, 0, Math.PI * 2), t.ctx.fill();
301
+ }
302
+ }
303
+ t.ctx.restore();
304
+ }
305
+ class W {
306
+ canvas;
307
+ container;
308
+ button;
309
+ ctx;
310
+ spinQueueController;
311
+ spriteUrl;
312
+ spriteElementsCount;
313
+ highlightInitialWinningCells;
314
+ particleColorRgb;
315
+ particleColorMode;
316
+ winningCellBorderRgba;
317
+ spriteImage = null;
318
+ rafLoop = new B();
319
+ runtime = J();
320
+ dpr = 1;
321
+ width = 0;
322
+ height = 0;
323
+ cellW = 0;
324
+ cellH = 0;
325
+ boardX = 0;
326
+ boardY = 0;
327
+ scriptedCascadeQueue = [];
328
+ scriptedOutgoingGrid = null;
329
+ scriptedPendingGrid = null;
330
+ scriptedOutroStartedAt = 0;
331
+ scriptedOutgoingOffsets = A();
332
+ scriptedIncomingOffsets = A();
333
+ winningCells = [];
334
+ grid;
335
+ constructor(i) {
336
+ this.canvas = i.canvas, this.container = i.container, this.button = i.button, this.spriteUrl = i.sprite, this.spriteElementsCount = Math.max(
337
+ 1,
338
+ i.spriteElementsCount ?? 6
339
+ ), this.highlightInitialWinningCells = i.highlightInitialWinningCells !== !1, this.spinQueueController = new j(i.queuedSpinStates), this.particleColorRgb = $(i.particleColorRgb), this.particleColorMode = X(i.particleColorMode), this.winningCellBorderRgba = Q(i.winningCellBorderRgba);
340
+ const e = this.canvas.getContext("2d");
341
+ if (!e)
342
+ throw new Error("2D canvas context is not available");
343
+ this.ctx = e, this.grid = i.initialSegments ? Z(i.initialSegments, this.spriteElementsCount) : P(this.spriteElementsCount);
344
+ }
345
+ async init() {
346
+ this.bindEvents(), this.resize(), await this.loadSpriteIfProvided(), this.applyInitialHighlightIfNeeded(), this.startLoop();
347
+ }
348
+ destroy() {
349
+ this.unbindEvents(), this.rafLoop.stop(), it(this.runtime), this.clearWinningCells();
350
+ }
351
+ spin() {
352
+ if (this.runtime.isSpinning || this.runtime.queueFinished) return;
353
+ if (!this.spinQueueController.hasPending()) {
354
+ this.runtime.queueFinished = !0, this.button && (this.button.disabled = !0);
355
+ return;
356
+ }
357
+ const i = this.spinQueueController.consume(), e = !this.spinQueueController.hasPending();
358
+ K(this.runtime, {
359
+ activeSpinState: i,
360
+ shouldHighlightCurrentSpin: e
361
+ }), this.button && (this.button.disabled = !0);
362
+ const n = this.runtime.activeSpinState?.finaleSequenceRows ? this.runtime.activeSpinState.finaleSequenceRows.map((s) => E(s)) : this.runtime.activeSpinState?.finaleSequence ?? [];
363
+ this.scriptedCascadeQueue = n.map((s) => s.map((o) => [...o])), this.clearWinningCells();
364
+ const r = this.runtime.activeSpinState?.stopRows ? E(this.runtime.activeSpinState.stopRows) : this.runtime.activeSpinState?.stopGrid, l = this.getNextGrid(r);
365
+ this.startOutroTransition(l, performance.now());
366
+ }
367
+ bindEvents() {
368
+ window.addEventListener("resize", this.resize), this.button?.addEventListener("click", this.onSpinClick);
369
+ }
370
+ unbindEvents() {
371
+ window.removeEventListener("resize", this.resize), this.button?.removeEventListener("click", this.onSpinClick);
372
+ }
373
+ onSpinClick = () => {
374
+ this.runtime.isSpinning || this.spin();
375
+ };
376
+ getNextGrid(i) {
377
+ return i ? M(i, this.spriteElementsCount) : P(this.spriteElementsCount);
378
+ }
379
+ update(i) {
380
+ if (this.runtime.isSpinning) {
381
+ if (this.runtime.phase === "outro") {
382
+ this.updateScriptedOutro(i);
383
+ return;
384
+ }
385
+ this.runtime.phase;
386
+ }
387
+ }
388
+ updateScriptedOutro(i) {
389
+ if (!this.scriptedOutgoingGrid || !this.scriptedPendingGrid) {
390
+ this.finishSpinWithUi();
391
+ return;
392
+ }
393
+ const { allOutgoingDone: e, allIncomingDone: n } = z({
394
+ now: i,
395
+ height: this.height,
396
+ boardY: this.boardY,
397
+ cellH: this.cellH,
398
+ scriptedOutroStartedAt: this.scriptedOutroStartedAt,
399
+ scriptedOutgoingOffsets: this.scriptedOutgoingOffsets,
400
+ scriptedIncomingOffsets: this.scriptedIncomingOffsets
401
+ });
402
+ if (!(!e || !n)) {
403
+ if (!this.scriptedPendingGrid) {
404
+ this.finishSpinWithUi();
405
+ return;
406
+ }
407
+ if (this.grid = this.scriptedPendingGrid, this.scriptedOutgoingGrid = null, this.scriptedPendingGrid = null, this.clearWinningCells(), _(this.scriptedOutgoingOffsets, 0), _(this.scriptedIncomingOffsets, 0), !this.tryStartScriptedCascade(i)) {
408
+ if (!this.runtime.shouldHighlightCurrentSpin) {
409
+ this.finishSpinWithUi();
410
+ return;
411
+ }
412
+ this.winningCells = F(this.grid), x(this.runtime, i);
413
+ }
414
+ }
415
+ }
416
+ tryStartScriptedCascade(i) {
417
+ if (this.scriptedCascadeQueue.length === 0) return !1;
418
+ const e = this.scriptedCascadeQueue.shift();
419
+ return e ? (this.startOutroTransition(M(e, this.spriteElementsCount), i), !0) : !1;
420
+ }
421
+ startOutroTransition(i, e) {
422
+ this.scriptedOutgoingGrid = this.grid.map((n) => [...n]), this.scriptedPendingGrid = i.map((n) => [...n]), _(this.scriptedIncomingOffsets, Number.NaN), _(this.scriptedOutgoingOffsets, 0), this.clearWinningCells(), this.runtime.phase = "outro", this.scriptedOutroStartedAt = e;
423
+ }
424
+ finishSpinWithUi() {
425
+ const i = this.runtime.activeSpinState?.callback;
426
+ tt(this.runtime, this.spinQueueController.hasPending()), this.button && (this.button.disabled = this.runtime.queueFinished), i?.();
427
+ }
428
+ applyInitialHighlightIfNeeded() {
429
+ this.highlightInitialWinningCells && (this.winningCells = F(this.grid), x(this.runtime, performance.now()));
430
+ }
431
+ static ZERO_SWAY = [0, 0, 0];
432
+ render(i) {
433
+ this.ctx.clearRect(0, 0, this.width, this.height), this.runtime.phase === "outro" && this.scriptedOutgoingGrid && this.scriptedPendingGrid ? (this.drawLayer(
434
+ this.scriptedOutgoingGrid,
435
+ this.scriptedOutgoingOffsets,
436
+ W.ZERO_SWAY
437
+ ), this.drawLayer(
438
+ this.scriptedPendingGrid,
439
+ this.scriptedIncomingOffsets,
440
+ W.ZERO_SWAY
441
+ )) : this.drawBaseGrid(), nt({
442
+ ctx: this.ctx,
443
+ now: i,
444
+ phase: this.runtime.phase,
445
+ winFlashStartedAt: this.runtime.winFlashStartedAt,
446
+ winningCells: this.winningCells,
447
+ boardX: this.boardX,
448
+ boardY: this.boardY,
449
+ cellW: this.cellW,
450
+ cellH: this.cellH,
451
+ particleColorMode: this.particleColorMode,
452
+ particleColorRgb: this.particleColorRgb,
453
+ winningCellBorderRgba: this.winningCellBorderRgba,
454
+ getCell: this.getCell,
455
+ drawSpriteCell: this.drawSpriteCell
456
+ });
457
+ }
458
+ drawBaseGrid() {
459
+ this.drawLayer(this.grid, null, W.ZERO_SWAY);
460
+ }
461
+ drawLayer(i, e, n) {
462
+ et({
463
+ ctx: this.ctx,
464
+ phase: this.runtime.phase,
465
+ grid: i,
466
+ offsets: e,
467
+ columnSway: n,
468
+ boardX: this.boardX,
469
+ boardY: this.boardY,
470
+ cellW: this.cellW,
471
+ cellH: this.cellH,
472
+ width: this.width,
473
+ height: this.height,
474
+ isWinningCell: this.isWinningCell,
475
+ drawSpriteCell: this.drawSpriteCell
476
+ });
477
+ }
478
+ getCell = (i, e) => this.grid[i][e];
479
+ isWinningCell = (i, e) => this.winningCells.some((n) => n.col === i && n.row === e);
480
+ drawSpriteCell = (i, e, n, r, l) => {
481
+ const s = this.spriteImage;
482
+ if (!s) return;
483
+ const o = I(i, this.spriteElementsCount), u = s.height / this.spriteElementsCount, h = Math.floor(o * u), d = Math.floor(u);
484
+ this.ctx.drawImage(s, 0, h, s.width, d, e, n, r, l);
485
+ };
486
+ clearWinningCells() {
487
+ this.winningCells = [];
488
+ }
489
+ resize = () => {
490
+ const i = this.container.getBoundingClientRect();
491
+ this.dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));
492
+ const e = Math.max(300, Math.floor(i.width * this.dpr));
493
+ this.width = e, this.height = e;
494
+ const n = Math.floor(Math.min(this.width / 3, this.height / 3));
495
+ 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.canvas.width = this.width, this.canvas.height = this.height;
496
+ };
497
+ async loadSpriteIfProvided() {
498
+ if (!this.spriteUrl) return;
499
+ const i = new Image();
500
+ i.decoding = "async", i.src = this.spriteUrl;
501
+ try {
502
+ await i.decode(), this.spriteImage = i;
503
+ } catch {
504
+ this.spriteImage = null;
505
+ }
506
+ }
507
+ startLoop() {
508
+ this.rafLoop.isRunning() || this.rafLoop.start((i) => (this.update(i), this.render(i), !0));
509
+ }
510
+ }
511
+ export {
512
+ W as CascadingReel
513
+ };
514
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/constants.ts","../src/utils/math.ts","../src/core/grid.ts","../src/core/loop.ts","../src/core/outro.ts","../src/normalize.ts","../src/core/spinQueue.ts","../src/core/state.ts","../src/render/canvasRenderer.ts","../src/CascadingReel.ts"],"sourcesContent":["export const DEFAULT_SPRITE_ELEMENTS_COUNT = 6;\nexport const GRID_COLS = 3;\nexport const GRID_ROWS = 3;\nexport const FLOW_OUTRO_ROW_GAP_MS = 34;\nexport const FLOW_ROW_BASE_SPACING_RATIO = 0.05;\nexport const FLOW_WIN_FLASH_MS = 1200;\nexport const FLOW_WIN_PULSE_PERIOD_MS = 1800;\nexport const FLOW_WIN_PULSE_AMPLITUDE = 0.075;\nexport const FLOW_WIN_PARTICLES_PER_CELL = 34;\nexport const DEFAULT_PARTICLE_COLOR_RGB: [number, number, number] = [255, 235, 110];\nexport const DEFAULT_WINNING_CELL_BORDER_RGBA: [number, number, number, number] = [\n 255, 255, 255, 0.78,\n];\nexport const FLOW_COLUMN_STAGGER_MS = 190;\nexport const FLOW_FALL_MS = 650;\nexport const FLOW_OUTRO_OVERLAP_MS = 220;\n","export function clamp(value: number, min: number, max: number): number {\n if (value < min) return min;\n if (value > max) return max;\n return value;\n}\n\nexport function easeOutCubic(t: number): number {\n return 1 - (1 - t) ** 3;\n}\n\nexport function randomInt(maxExclusive: number): number {\n return Math.floor(Math.random() * maxExclusive);\n}\n\nexport function normalizeSegment(segment: number, elementsCount: number): number {\n return ((segment % elementsCount) + elementsCount) % elementsCount;\n}\n\nexport function normalizeRgbChannel(value: number): number {\n return clamp(Math.round(value), 0, 255);\n}\n\nexport function normalizeAlphaChannel(value: number): number {\n return clamp(value, 0, 1);\n}\n","import { GRID_COLS, GRID_ROWS } from '../constants';\nimport type { CellPosition, SymbolId } from '../types';\nimport { randomInt } from '../utils/math';\n\nexport function createRandomGrid(spriteElementsCount: number): SymbolId[][] {\n const grid: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column: SymbolId[] = [];\n for (let row = 0; row < GRID_ROWS; row += 1) {\n column.push(randomInt(spriteElementsCount));\n }\n grid.push(column);\n }\n return grid;\n}\n\nexport function findMostFrequentCells(grid: SymbolId[][]): CellPosition[] {\n const counts = new Map<SymbolId, number>();\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const symbol = grid[col][row];\n counts.set(symbol, (counts.get(symbol) ?? 0) + 1);\n }\n }\n\n let selectedSymbol: SymbolId = grid[0][0];\n let maxCount = -1;\n for (const [symbol, count] of counts.entries()) {\n if (count > maxCount) {\n maxCount = count;\n selectedSymbol = symbol;\n }\n }\n\n const cells: CellPosition[] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (grid[col][row] === selectedSymbol) {\n cells.push({ col, row });\n }\n }\n }\n return cells;\n}\n\nexport function createZeroOffsets(): number[][] {\n return Array.from({ length: GRID_COLS }, () => Array.from({ length: GRID_ROWS }, () => 0));\n}\n\nexport function fillOffsets(offsets: number[][], value: number): void {\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n offsets[col][row] = value;\n }\n }\n}\n","export type RafStep = (now: number) => boolean;\n\nexport class RafLoop {\n private rafId: number | null = null;\n private step: RafStep | null = null;\n\n public start(step: RafStep): void {\n if (this.rafId !== null) return;\n this.step = step;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n public stop(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.step = null;\n }\n\n public isRunning(): boolean {\n return this.rafId !== null;\n }\n\n private readonly tick = (now: number): void => {\n if (!this.step) {\n this.stop();\n return;\n }\n\n const shouldContinue = this.step(now);\n if (shouldContinue === false) {\n this.stop();\n return;\n }\n\n if (this.rafId === null) return;\n this.rafId = requestAnimationFrame(this.tick);\n };\n}\n","import {\n FLOW_COLUMN_STAGGER_MS,\n FLOW_FALL_MS,\n FLOW_OUTRO_OVERLAP_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n FLOW_ROW_BASE_SPACING_RATIO,\n GRID_ROWS,\n} from '../constants';\nimport { clamp, easeOutCubic } from '../utils/math';\n\nexport function buildSequentialRowStartDelays(\n fromRowOffsets: [number, number, number],\n duration: number,\n gapMs: number,\n): [number, number, number] {\n const delays: [number, number, number] = [0, 0, 0];\n let nextDelay = 0;\n for (let row = GRID_ROWS - 1; row >= 0; row -= 1) {\n if (fromRowOffsets[row] === 0) {\n delays[row] = 0;\n continue;\n }\n delays[row] = nextDelay;\n const baseSpacing = Math.floor(duration * FLOW_ROW_BASE_SPACING_RATIO);\n nextDelay += baseSpacing + gapMs;\n }\n return delays;\n}\n\nexport function updateOutroOffsets(params: {\n now: number;\n height: number;\n boardY: number;\n cellH: number;\n scriptedOutroStartedAt: number;\n scriptedOutgoingOffsets: number[][];\n scriptedIncomingOffsets: number[][];\n}): { allOutgoingDone: boolean; allIncomingDone: boolean } {\n let allOutgoingDone = true;\n let allIncomingDone = true;\n\n const exitEpsilon = 2;\n const outgoingDistance = params.height - params.boardY + params.cellH + exitEpsilon;\n const outgoingOffsetsForOrder: [number, number, number] = [\n outgoingDistance,\n outgoingDistance,\n outgoingDistance,\n ];\n const incomingFromOffsets: [number, number, number] = [\n -params.cellH,\n -params.cellH * 2,\n -params.cellH * 3,\n ];\n const rowStartDelays = buildSequentialRowStartDelays(\n outgoingOffsetsForOrder,\n FLOW_FALL_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n );\n const incomingStartShift = FLOW_FALL_MS - FLOW_OUTRO_OVERLAP_MS;\n\n for (let col = 0; col < params.scriptedOutgoingOffsets.length; col += 1) {\n const columnElapsed =\n params.now - (params.scriptedOutroStartedAt + col * FLOW_COLUMN_STAGGER_MS);\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const rowElapsed = columnElapsed - rowStartDelays[row];\n\n if (rowElapsed <= 0) {\n params.scriptedOutgoingOffsets[col][row] = 0;\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allOutgoingDone = false;\n allIncomingDone = false;\n continue;\n }\n\n const tOut = clamp(rowElapsed / FLOW_FALL_MS, 0, 1);\n const easedOut = easeOutCubic(tOut);\n params.scriptedOutgoingOffsets[col][row] = outgoingDistance * easedOut;\n if (tOut < 1) allOutgoingDone = false;\n\n const incomingElapsed = rowElapsed - incomingStartShift;\n if (incomingElapsed <= 0) {\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allIncomingDone = false;\n continue;\n }\n\n const tIn = clamp(incomingElapsed / FLOW_FALL_MS, 0, 1);\n const easedIn = easeOutCubic(tIn);\n params.scriptedIncomingOffsets[col][row] = incomingFromOffsets[row] * (1 - easedIn);\n if (tIn < 1) allIncomingDone = false;\n }\n }\n\n return { allOutgoingDone, allIncomingDone };\n}\n","import {\n DEFAULT_PARTICLE_COLOR_RGB,\n DEFAULT_WINNING_CELL_BORDER_RGBA,\n GRID_COLS,\n GRID_ROWS,\n} from './constants';\nimport type { SpinState, SymbolId } from './types';\nimport { normalizeAlphaChannel, normalizeRgbChannel, normalizeSegment } from './utils/math';\n\nexport function normalizeParticleColor(color?: [number, number, number]): [number, number, number] {\n if (!color) return DEFAULT_PARTICLE_COLOR_RGB;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n ];\n}\n\nexport function normalizeParticleColorMode(mode?: 'solid' | 'rainbow'): 'solid' | 'rainbow' {\n if (mode === 'rainbow') return 'rainbow';\n return 'solid';\n}\n\nexport function normalizeWinningCellBorderColor(\n color?: [number, number, number, number],\n): [number, number, number, number] {\n if (!color) return DEFAULT_WINNING_CELL_BORDER_RGBA;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n normalizeAlphaChannel(color[3]),\n ];\n}\n\nexport function rowsToStopGrid(rows: number[][]): number[][] {\n if (rows.length !== GRID_ROWS) {\n throw new Error(`rows must contain ${GRID_ROWS} rows`);\n }\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (!Array.isArray(rows[row]) || rows[row].length !== GRID_COLS) {\n throw new Error(`rows[${row}] must contain ${GRID_COLS} columns`);\n }\n }\n\n return [\n [rows[0][0], rows[1][0], rows[2][0]],\n [rows[0][1], rows[1][1], rows[2][1]],\n [rows[0][2], rows[1][2], rows[2][2]],\n ];\n}\n\nexport function normalizeStopGrid(stopGrid: number[][], elementsCount: number): SymbolId[][] {\n if (stopGrid.length !== GRID_COLS) {\n throw new Error(`stopGrid must contain ${GRID_COLS} columns`);\n }\n\n const next: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column = stopGrid[col];\n if (!Array.isArray(column) || column.length !== GRID_ROWS) {\n throw new Error(`stopGrid[${col}] must contain ${GRID_ROWS} rows`);\n }\n next[col] = [\n normalizeSegment(column[0], elementsCount),\n normalizeSegment(column[1], elementsCount),\n normalizeSegment(column[2], elementsCount),\n ];\n }\n return next;\n}\n\nexport function normalizeInitialSegments(\n initialSegments: number[][],\n elementsCount: number,\n): SymbolId[][] {\n return normalizeStopGrid(rowsToStopGrid(initialSegments), elementsCount);\n}\n\nexport function cloneSpinState(state: SpinState): SpinState {\n return {\n stopGrid: state.stopGrid?.map((column) => [...column]),\n stopRows: state.stopRows?.map((row) => [...row]),\n finaleSequence: state.finaleSequence?.map((grid) => grid.map((column) => [...column])),\n finaleSequenceRows: state.finaleSequenceRows?.map((grid) => grid.map((row) => [...row])),\n callback: state.callback,\n };\n}\n","import { cloneSpinState } from '../normalize';\nimport type { SpinState } from '../types';\n\nexport class SpinQueueController {\n private queue: SpinState[];\n\n public constructor(initialQueue?: SpinState[]) {\n this.queue = (initialQueue ?? []).map((entry) => cloneSpinState(entry));\n }\n\n public hasPending(): boolean {\n return this.queue.length > 0;\n }\n\n public consume(): SpinState | null {\n if (this.queue.length === 0) return null;\n return this.queue.shift() ?? null;\n }\n}\n","import type { SpinPhase, SpinState } from '../types';\n\nexport type RuntimeState = {\n isSpinning: boolean;\n hasStartedFirstSpin: boolean;\n queueFinished: boolean;\n shouldHighlightCurrentSpin: boolean;\n activeSpinState: SpinState | null;\n phase: SpinPhase;\n winFlashStartedAt: number;\n};\n\nexport function createRuntimeState(): RuntimeState {\n return {\n isSpinning: false,\n hasStartedFirstSpin: false,\n queueFinished: false,\n shouldHighlightCurrentSpin: false,\n activeSpinState: null,\n phase: 'idle',\n winFlashStartedAt: 0,\n };\n}\n\nexport function beginSpin(\n state: RuntimeState,\n params: {\n activeSpinState: SpinState | null;\n shouldHighlightCurrentSpin: boolean;\n },\n): void {\n state.hasStartedFirstSpin = true;\n state.isSpinning = true;\n state.phase = 'outro';\n state.activeSpinState = params.activeSpinState;\n state.shouldHighlightCurrentSpin = params.shouldHighlightCurrentSpin;\n}\n\nexport function finishSpin(state: RuntimeState, hasPendingInQueue: boolean): void {\n state.phase = 'idle';\n state.isSpinning = false;\n state.shouldHighlightCurrentSpin = false;\n state.queueFinished = !hasPendingInQueue;\n state.activeSpinState = null;\n}\n\nexport function startWinFlash(state: RuntimeState, startedAt: number): void {\n state.winFlashStartedAt = startedAt;\n state.phase = 'winFlash';\n}\n\nexport function destroyState(state: RuntimeState): void {\n state.isSpinning = false;\n state.queueFinished = true;\n}\n","import {\n FLOW_WIN_FLASH_MS,\n FLOW_WIN_PARTICLES_PER_CELL,\n FLOW_WIN_PULSE_AMPLITUDE,\n FLOW_WIN_PULSE_PERIOD_MS,\n GRID_COLS,\n GRID_ROWS,\n} from '../constants';\nimport type { CellPosition, SpinPhase, SymbolId } from '../types';\nimport { clamp } from '../utils/math';\n\ntype DrawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n) => void;\n\nexport function drawGridLayer(params: {\n ctx: CanvasRenderingContext2D;\n phase: SpinPhase;\n grid: SymbolId[][];\n offsets: number[][] | null;\n columnSway?: [number, number, number];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n width: number;\n height: number;\n isWinningCell: (col: number, row: number) => boolean;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n const sway = params.columnSway ?? [0, 0, 0];\n\n for (let col = 0; col < GRID_COLS; col += 1) {\n const x = params.boardX + col * params.cellW;\n const swayY = sway[col] ?? 0;\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (params.phase === 'winFlash' && params.isWinningCell(col, row)) continue;\n const offsetY = params.offsets ? params.offsets[col][row] : 0;\n if (!Number.isFinite(offsetY)) continue;\n\n const y = params.boardY + row * params.cellH + offsetY + swayY;\n if (y > params.height || y + params.cellH < 0) continue;\n\n params.drawSpriteCell(params.grid[col][row], x, y, params.cellW, params.cellH);\n }\n }\n}\n\nfunction hash01(a: number, b: number, c: number, d: number): number {\n const value = Math.sin(a * 127.1 + b * 311.7 + c * 74.7 + d * 19.3) * 43758.5453;\n return value - Math.floor(value);\n}\n\nexport function drawWinningEffects(params: {\n ctx: CanvasRenderingContext2D;\n now: number;\n phase: SpinPhase;\n winFlashStartedAt: number;\n winningCells: CellPosition[];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n winningCellBorderRgba: [number, number, number, number];\n getCell: (col: number, row: number) => SymbolId;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n if (params.winningCells.length === 0) return;\n if (params.phase !== 'winFlash') return;\n\n const elapsed = Math.max(0, params.now - params.winFlashStartedAt);\n const cycleProgress = (elapsed % FLOW_WIN_FLASH_MS) / FLOW_WIN_FLASH_MS;\n const pulseProgress = (elapsed % FLOW_WIN_PULSE_PERIOD_MS) / FLOW_WIN_PULSE_PERIOD_MS;\n const pulse = 1 + Math.sin(pulseProgress * Math.PI * 2) * FLOW_WIN_PULSE_AMPLITUDE;\n const easeOut = 1 - (1 - cycleProgress) ** 2;\n\n for (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawWinningCellBorder(\n params.ctx,\n x,\n y,\n params.cellW,\n params.cellH,\n params.winningCellBorderRgba,\n elapsed,\n );\n\n const symbol = params.getCell(cell.col, cell.row);\n const scaledW = params.cellW * pulse;\n const scaledH = params.cellH * pulse;\n const offsetX = (params.cellW - scaledW) * 0.5;\n const offsetY = (params.cellH - scaledH) * 0.5;\n\n params.ctx.save();\n params.drawSpriteCell(symbol, x + offsetX, y + offsetY, scaledW, scaledH);\n params.ctx.restore();\n\n drawCellParticleBurst({\n ctx: params.ctx,\n cell,\n x,\n y,\n cycleProgress,\n easeOut,\n boardX: params.boardX,\n boardY: params.boardY,\n cellW: params.cellW,\n cellH: params.cellH,\n particleColorMode: params.particleColorMode,\n particleColorRgb: params.particleColorRgb,\n });\n }\n}\n\nfunction drawWinningCellBorder(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n cellW: number,\n cellH: number,\n color: [number, number, number, number],\n elapsed: number,\n): void {\n const inset = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.04));\n const lineWidth = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.025));\n const [, , , a] = color;\n const hue = (elapsed * 0.15) % 360;\n\n ctx.save();\n ctx.lineWidth = lineWidth;\n ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${a})`;\n const halfLine = lineWidth * 0.5;\n ctx.strokeRect(\n x + inset + halfLine,\n y + inset + halfLine,\n cellW - inset * 2 - lineWidth,\n cellH - inset * 2 - lineWidth,\n );\n ctx.restore();\n}\n\nfunction drawCellParticleBurst(params: {\n ctx: CanvasRenderingContext2D;\n cell: CellPosition;\n x: number;\n y: number;\n cycleProgress: number;\n easeOut: number;\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n}): void {\n const centerX = params.x + params.cellW * 0.5;\n const centerY = params.y + params.cellH * 0.5;\n const maxDistance = Math.min(params.cellW, params.cellH) * 0.72;\n const baseRadius = Math.min(params.cellW, params.cellH) * 0.028;\n const globalFade = 0.92 + (1 - params.cycleProgress) * 0.08;\n\n params.ctx.save();\n params.ctx.beginPath();\n params.ctx.rect(params.boardX, params.boardY, params.cellW * GRID_COLS, params.cellH * GRID_ROWS);\n params.ctx.clip();\n\n for (let i = 0; i < FLOW_WIN_PARTICLES_PER_CELL; i += 1) {\n const seedA = hash01(params.cell.col, params.cell.row, i, 1);\n const seedB = hash01(params.cell.col, params.cell.row, i, 2);\n const seedC = hash01(params.cell.col, params.cell.row, i, 3);\n const phaseOffset = hash01(params.cell.col, params.cell.row, i, 4) * 0.28;\n const twinkleSeed = hash01(params.cell.col, params.cell.row, i, 5);\n const particleT = clamp((params.cycleProgress - phaseOffset) / (1 - phaseOffset), 0, 1);\n if (particleT <= 0) continue;\n\n const direction = seedA * Math.PI * 2;\n const distance =\n maxDistance * particleT * (0.35 + seedB * 0.65) * (0.85 + params.easeOut * 0.15);\n const px = centerX + Math.cos(direction) * distance;\n const py = centerY + Math.sin(direction) * distance;\n const twinkle =\n 0.7 +\n 0.9 * Math.max(0, Math.sin((params.cycleProgress * 11 + twinkleSeed * 2) * Math.PI * 2));\n const radius = Math.max(1, baseRadius * (0.55 + seedC * 0.6) * (1 - particleT * 0.5));\n const alpha = clamp((0.9 + twinkle * 0.2) * globalFade, 0.9, 1);\n if (alpha <= 0) continue;\n\n if (params.particleColorMode === 'rainbow') {\n const hue =\n (seedA * 360 + params.cycleProgress * 240 + params.cell.col * 38 + params.cell.row * 22) %\n 360;\n params.ctx.fillStyle = `hsla(${hue}, 98%, 64%, ${alpha})`;\n } else {\n const [r, g, b] = params.particleColorRgb;\n params.ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n params.ctx.beginPath();\n params.ctx.arc(px, py, radius, 0, Math.PI * 2);\n params.ctx.fill();\n }\n params.ctx.restore();\n}\n","import { DEFAULT_SPRITE_ELEMENTS_COUNT, GRID_COLS, GRID_ROWS } from './constants';\nimport {\n createRandomGrid,\n createZeroOffsets,\n fillOffsets,\n findMostFrequentCells,\n} from './core/grid';\nimport { RafLoop } from './core/loop';\nimport { updateOutroOffsets } from './core/outro';\nimport { SpinQueueController } from './core/spinQueue';\nimport {\n beginSpin,\n createRuntimeState,\n destroyState,\n finishSpin,\n startWinFlash,\n} from './core/state';\nimport {\n normalizeInitialSegments,\n normalizeParticleColor,\n normalizeParticleColorMode,\n normalizeStopGrid,\n normalizeWinningCellBorderColor,\n rowsToStopGrid,\n} from './normalize';\nimport { drawGridLayer, drawWinningEffects } from './render/canvasRenderer';\nimport type { CascadingReelConfig, CellPosition, SymbolId } from './types';\nimport { normalizeSegment } from './utils/math';\n\nexport class CascadingReel {\n private readonly canvas: HTMLCanvasElement;\n private readonly container: HTMLElement;\n private readonly button?: HTMLButtonElement;\n private readonly ctx: CanvasRenderingContext2D;\n private readonly spinQueueController: SpinQueueController;\n private readonly spriteUrl?: string;\n private readonly spriteElementsCount: number;\n private readonly highlightInitialWinningCells: boolean;\n private readonly particleColorRgb: [number, number, number];\n private readonly particleColorMode: 'solid' | 'rainbow';\n private readonly winningCellBorderRgba: [number, number, number, number];\n\n private spriteImage: HTMLImageElement | null = null;\n private readonly rafLoop = new RafLoop();\n private readonly runtime = createRuntimeState();\n private dpr = 1;\n private width = 0;\n private height = 0;\n private cellW = 0;\n private cellH = 0;\n private boardX = 0;\n private boardY = 0;\n private scriptedCascadeQueue: number[][][] = [];\n private scriptedOutgoingGrid: SymbolId[][] | null = null;\n private scriptedPendingGrid: SymbolId[][] | null = null;\n private scriptedOutroStartedAt = 0;\n private scriptedOutgoingOffsets: number[][] = createZeroOffsets();\n private scriptedIncomingOffsets: number[][] = createZeroOffsets();\n private winningCells: CellPosition[] = [];\n private grid: SymbolId[][];\n\n public constructor(config: CascadingReelConfig) {\n this.canvas = config.canvas;\n this.container = config.container;\n this.button = config.button;\n this.spriteUrl = config.sprite;\n this.spriteElementsCount = Math.max(\n 1,\n config.spriteElementsCount ?? DEFAULT_SPRITE_ELEMENTS_COUNT,\n );\n this.highlightInitialWinningCells = config.highlightInitialWinningCells !== false;\n this.spinQueueController = new SpinQueueController(config.queuedSpinStates);\n this.particleColorRgb = normalizeParticleColor(config.particleColorRgb);\n this.particleColorMode = normalizeParticleColorMode(config.particleColorMode);\n this.winningCellBorderRgba = normalizeWinningCellBorderColor(config.winningCellBorderRgba);\n\n const context = this.canvas.getContext('2d');\n if (!context) {\n throw new Error('2D canvas context is not available');\n }\n this.ctx = context;\n this.grid = config.initialSegments\n ? normalizeInitialSegments(config.initialSegments, this.spriteElementsCount)\n : createRandomGrid(this.spriteElementsCount);\n }\n\n public async init(): Promise<void> {\n this.bindEvents();\n this.resize();\n await this.loadSpriteIfProvided();\n this.applyInitialHighlightIfNeeded();\n this.startLoop();\n }\n\n public destroy(): void {\n this.unbindEvents();\n this.rafLoop.stop();\n destroyState(this.runtime);\n this.clearWinningCells();\n }\n\n public spin(): void {\n if (this.runtime.isSpinning) return;\n if (this.runtime.queueFinished) return;\n if (!this.spinQueueController.hasPending()) {\n this.runtime.queueFinished = true;\n if (this.button) this.button.disabled = true;\n return;\n }\n\n const activeSpinState = this.spinQueueController.consume();\n const shouldHighlightCurrentSpin = !this.spinQueueController.hasPending();\n beginSpin(this.runtime, {\n activeSpinState,\n shouldHighlightCurrentSpin,\n });\n\n if (this.button) this.button.disabled = true;\n\n const scriptedSource = this.runtime.activeSpinState?.finaleSequenceRows\n ? this.runtime.activeSpinState.finaleSequenceRows.map((rows) => rowsToStopGrid(rows))\n : (this.runtime.activeSpinState?.finaleSequence ?? []);\n this.scriptedCascadeQueue = scriptedSource.map((grid) => grid.map((column) => [...column]));\n this.clearWinningCells();\n\n const stopGridSource = this.runtime.activeSpinState?.stopRows\n ? rowsToStopGrid(this.runtime.activeSpinState.stopRows)\n : this.runtime.activeSpinState?.stopGrid;\n const nextGrid = this.getNextGrid(stopGridSource);\n this.startOutroTransition(nextGrid, performance.now());\n }\n\n private bindEvents(): void {\n window.addEventListener('resize', this.resize);\n this.button?.addEventListener('click', this.onSpinClick);\n }\n\n private unbindEvents(): void {\n window.removeEventListener('resize', this.resize);\n this.button?.removeEventListener('click', this.onSpinClick);\n }\n\n private readonly onSpinClick = (): void => {\n if (this.runtime.isSpinning) return;\n this.spin();\n };\n\n private getNextGrid(stopGrid?: number[][]): SymbolId[][] {\n if (!stopGrid) return createRandomGrid(this.spriteElementsCount);\n return normalizeStopGrid(stopGrid, this.spriteElementsCount);\n }\n\n private update(now: number): void {\n if (!this.runtime.isSpinning) return;\n if (this.runtime.phase === 'outro') {\n this.updateScriptedOutro(now);\n return;\n }\n if (this.runtime.phase === 'winFlash') return;\n }\n\n private updateScriptedOutro(now: number): void {\n if (!this.scriptedOutgoingGrid || !this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n const { allOutgoingDone, allIncomingDone } = updateOutroOffsets({\n now,\n height: this.height,\n boardY: this.boardY,\n cellH: this.cellH,\n scriptedOutroStartedAt: this.scriptedOutroStartedAt,\n scriptedOutgoingOffsets: this.scriptedOutgoingOffsets,\n scriptedIncomingOffsets: this.scriptedIncomingOffsets,\n });\n\n if (!allOutgoingDone || !allIncomingDone) return;\n if (!this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n this.grid = this.scriptedPendingGrid;\n this.scriptedOutgoingGrid = null;\n this.scriptedPendingGrid = null;\n this.clearWinningCells();\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n fillOffsets(this.scriptedIncomingOffsets, 0);\n\n if (this.tryStartScriptedCascade(now)) return;\n if (!this.runtime.shouldHighlightCurrentSpin) {\n this.finishSpinWithUi();\n return;\n }\n\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, now);\n }\n\n private tryStartScriptedCascade(now: number): boolean {\n if (this.scriptedCascadeQueue.length === 0) return false;\n const nextGrid = this.scriptedCascadeQueue.shift();\n if (!nextGrid) return false;\n this.startOutroTransition(normalizeStopGrid(nextGrid, this.spriteElementsCount), now);\n return true;\n }\n\n private startOutroTransition(nextGrid: SymbolId[][], now: number): void {\n this.scriptedOutgoingGrid = this.grid.map((column) => [...column]);\n this.scriptedPendingGrid = nextGrid.map((column) => [...column]);\n fillOffsets(this.scriptedIncomingOffsets, Number.NaN);\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n this.clearWinningCells();\n this.runtime.phase = 'outro';\n this.scriptedOutroStartedAt = now;\n }\n\n private finishSpinWithUi(): void {\n const callback = this.runtime.activeSpinState?.callback;\n finishSpin(this.runtime, this.spinQueueController.hasPending());\n if (this.button) this.button.disabled = this.runtime.queueFinished;\n callback?.();\n }\n\n private applyInitialHighlightIfNeeded(): void {\n if (!this.highlightInitialWinningCells) return;\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, performance.now());\n }\n\n private static readonly ZERO_SWAY: [number, number, number] = [0, 0, 0];\n\n private render(now: number): void {\n this.ctx.clearRect(0, 0, this.width, this.height);\n if (this.runtime.phase === 'outro' && this.scriptedOutgoingGrid && this.scriptedPendingGrid) {\n this.drawLayer(\n this.scriptedOutgoingGrid,\n this.scriptedOutgoingOffsets,\n CascadingReel.ZERO_SWAY,\n );\n this.drawLayer(\n this.scriptedPendingGrid,\n this.scriptedIncomingOffsets,\n CascadingReel.ZERO_SWAY,\n );\n } else {\n this.drawBaseGrid();\n }\n\n drawWinningEffects({\n ctx: this.ctx,\n now,\n phase: this.runtime.phase,\n winFlashStartedAt: this.runtime.winFlashStartedAt,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private drawBaseGrid(): void {\n this.drawLayer(this.grid, null, CascadingReel.ZERO_SWAY);\n }\n\n private drawLayer(\n grid: SymbolId[][],\n offsets: number[][] | null,\n columnSway: [number, number, number],\n ): void {\n drawGridLayer({\n ctx: this.ctx,\n phase: this.runtime.phase,\n grid,\n offsets,\n columnSway,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n width: this.width,\n height: this.height,\n isWinningCell: this.isWinningCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private readonly getCell = (col: number, row: number): SymbolId => {\n return this.grid[col][row];\n };\n\n private readonly isWinningCell = (col: number, row: number): boolean => {\n return this.winningCells.some((cell) => cell.col === col && cell.row === row);\n };\n\n private readonly drawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void => {\n const image = this.spriteImage;\n if (!image) return;\n\n const segmentIndex = normalizeSegment(symbolId, this.spriteElementsCount);\n const segmentHeight = image.height / this.spriteElementsCount;\n const sourceY = Math.floor(segmentIndex * segmentHeight);\n const sourceHeight = Math.floor(segmentHeight);\n\n this.ctx.drawImage(image, 0, sourceY, image.width, sourceHeight, x, y, width, height);\n };\n\n private clearWinningCells(): void {\n this.winningCells = [];\n }\n\n private readonly resize = (): void => {\n const bounds = this.container.getBoundingClientRect();\n this.dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n const side = Math.max(300, Math.floor(bounds.width * this.dpr));\n this.width = side;\n this.height = side;\n const squareSize = Math.floor(Math.min(this.width / GRID_COLS, this.height / GRID_ROWS));\n this.cellW = squareSize;\n this.cellH = squareSize;\n this.boardX = Math.floor((this.width - this.cellW * GRID_COLS) / 2);\n this.boardY = Math.floor((this.height - this.cellH * GRID_ROWS) / 2);\n\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n };\n\n private async loadSpriteIfProvided(): Promise<void> {\n if (!this.spriteUrl) return;\n const image = new Image();\n image.decoding = 'async';\n image.src = this.spriteUrl;\n try {\n await image.decode();\n this.spriteImage = image;\n } catch {\n this.spriteImage = null;\n }\n }\n\n private startLoop(): void {\n if (this.rafLoop.isRunning()) return;\n this.rafLoop.start((time: number): boolean => {\n this.update(time);\n this.render(time);\n return true;\n });\n }\n}\n"],"names":["DEFAULT_PARTICLE_COLOR_RGB","DEFAULT_WINNING_CELL_BORDER_RGBA","FLOW_COLUMN_STAGGER_MS","FLOW_FALL_MS","FLOW_OUTRO_OVERLAP_MS","clamp","value","min","max","easeOutCubic","randomInt","maxExclusive","normalizeSegment","segment","elementsCount","normalizeRgbChannel","normalizeAlphaChannel","createRandomGrid","spriteElementsCount","grid","col","column","row","findMostFrequentCells","counts","symbol","selectedSymbol","maxCount","count","cells","createZeroOffsets","fillOffsets","offsets","RafLoop","step","now","buildSequentialRowStartDelays","fromRowOffsets","duration","gapMs","delays","nextDelay","baseSpacing","updateOutroOffsets","params","allOutgoingDone","allIncomingDone","outgoingDistance","outgoingOffsetsForOrder","incomingFromOffsets","rowStartDelays","incomingStartShift","columnElapsed","rowElapsed","tOut","easedOut","incomingElapsed","tIn","easedIn","normalizeParticleColor","color","normalizeParticleColorMode","mode","normalizeWinningCellBorderColor","rowsToStopGrid","rows","normalizeStopGrid","stopGrid","next","normalizeInitialSegments","initialSegments","cloneSpinState","state","SpinQueueController","initialQueue","entry","createRuntimeState","beginSpin","finishSpin","hasPendingInQueue","startWinFlash","startedAt","destroyState","drawGridLayer","sway","x","swayY","offsetY","y","hash01","a","b","c","d","drawWinningEffects","elapsed","cycleProgress","pulseProgress","pulse","easeOut","cell","drawWinningCellBorder","scaledW","scaledH","offsetX","drawCellParticleBurst","ctx","cellW","cellH","inset","lineWidth","hue","halfLine","centerX","centerY","maxDistance","baseRadius","globalFade","i","seedA","seedB","seedC","phaseOffset","twinkleSeed","particleT","direction","distance","px","py","twinkle","radius","alpha","r","g","CascadingReel","config","context","activeSpinState","shouldHighlightCurrentSpin","scriptedSource","stopGridSource","nextGrid","callback","columnSway","symbolId","width","height","image","segmentIndex","segmentHeight","sourceY","sourceHeight","bounds","side","squareSize","time"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(g,R){typeof exports=="object"&&typeof module<"u"?R(exports):typeof define=="function"&&define.amd?define(["exports"],R):(g=typeof globalThis<"u"?globalThis:g||self,R(g.CascadingReel={}))})(this,(function(g){"use strict";const H=[255,235,110],v=[255,255,255,.78],N=190,w=650,T=220;function S(t,i,e){return t<i?i:t>e?e:t}function F(t){return 1-(1-t)**3}function U(t){return Math.floor(Math.random()*t)}function _(t,i){return(t%i+i)%i}function p(t){return S(Math.round(t),0,255)}function q(t){return S(t,0,1)}function A(t){const i=[];for(let e=0;e<3;e+=1){const n=[];for(let r=0;r<3;r+=1)n.push(U(t));i.push(n)}return i}function y(t){const i=new Map;for(let o=0;o<3;o+=1)for(let s=0;s<3;s+=1){const l=t[o][s];i.set(l,(i.get(l)??0)+1)}let e=t[0][0],n=-1;for(const[o,s]of i.entries())s>n&&(n=s,e=o);const r=[];for(let o=0;o<3;o+=1)for(let s=0;s<3;s+=1)t[o][s]===e&&r.push({col:o,row:s});return r}function x(){return Array.from({length:3},()=>Array.from({length:3},()=>0))}function I(t,i){for(let e=0;e<3;e+=1)for(let n=0;n<3;n+=1)t[e][n]=i}class Y{rafId=null;step=null;start(i){this.rafId===null&&(this.step=i,this.rafId=requestAnimationFrame(this.tick))}stop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.step=null}isRunning(){return this.rafId!==null}tick=i=>{if(!this.step){this.stop();return}if(this.step(i)===!1){this.stop();return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.tick))}}function B(t,i,e){const n=[0,0,0];let r=0;for(let o=2;o>=0;o-=1){if(t[o]===0){n[o]=0;continue}n[o]=r;const s=Math.floor(i*.05);r+=s+e}return n}function k(t){let i=!0,e=!0;const r=t.height-t.boardY+t.cellH+2,o=[r,r,r],s=[-t.cellH,-t.cellH*2,-t.cellH*3],l=B(o,w,34),u=w-T;for(let h=0;h<t.scriptedOutgoingOffsets.length;h+=1){const d=t.now-(t.scriptedOutroStartedAt+h*N);for(let c=0;c<3;c+=1){const a=d-l[c];if(a<=0){t.scriptedOutgoingOffsets[h][c]=0,t.scriptedIncomingOffsets[h][c]=Number.NaN,i=!1,e=!1;continue}const f=S(a/w,0,1),W=F(f);t.scriptedOutgoingOffsets[h][c]=r*W,f<1&&(i=!1);const b=a-u;if(b<=0){t.scriptedIncomingOffsets[h][c]=Number.NaN,e=!1;continue}const L=S(b/w,0,1),M=F(L);t.scriptedIncomingOffsets[h][c]=s[c]*(1-M),L<1&&(e=!1)}}return{allOutgoingDone:i,allIncomingDone:e}}function z(t){return t?[p(t[0]),p(t[1]),p(t[2])]:H}function $(t){return t==="rainbow"?"rainbow":"solid"}function X(t){return t?[p(t[0]),p(t[1]),p(t[2]),q(t[3])]:v}function G(t){if(t.length!==3)throw new Error("rows must contain 3 rows");for(let i=0;i<3;i+=1)if(!Array.isArray(t[i])||t[i].length!==3)throw new Error(`rows[${i}] must contain 3 columns`);return[[t[0][0],t[1][0],t[2][0]],[t[0][1],t[1][1],t[2][1]],[t[0][2],t[1][2],t[2][2]]]}function E(t,i){if(t.length!==3)throw new Error("stopGrid must contain 3 columns");const e=[];for(let n=0;n<3;n+=1){const r=t[n];if(!Array.isArray(r)||r.length!==3)throw new Error(`stopGrid[${n}] must contain 3 rows`);e[n]=[_(r[0],i),_(r[1],i),_(r[2],i)]}return e}function Q(t,i){return E(G(t),i)}function Z(t){return{stopGrid:t.stopGrid?.map(i=>[...i]),stopRows:t.stopRows?.map(i=>[...i]),finaleSequence:t.finaleSequence?.map(i=>i.map(e=>[...e])),finaleSequenceRows:t.finaleSequenceRows?.map(i=>i.map(e=>[...e])),callback:t.callback}}class j{queue;constructor(i){this.queue=(i??[]).map(e=>Z(e))}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?null:this.queue.shift()??null}}function V(){return{isSpinning:!1,hasStartedFirstSpin:!1,queueFinished:!1,shouldHighlightCurrentSpin:!1,activeSpinState:null,phase:"idle",winFlashStartedAt:0}}function J(t,i){t.hasStartedFirstSpin=!0,t.isSpinning=!0,t.phase="outro",t.activeSpinState=i.activeSpinState,t.shouldHighlightCurrentSpin=i.shouldHighlightCurrentSpin}function K(t,i){t.phase="idle",t.isSpinning=!1,t.shouldHighlightCurrentSpin=!1,t.queueFinished=!i,t.activeSpinState=null}function m(t,i){t.winFlashStartedAt=i,t.phase="winFlash"}function tt(t){t.isSpinning=!1,t.queueFinished=!0}function it(t){const i=t.columnSway??[0,0,0];for(let e=0;e<3;e+=1){const n=t.boardX+e*t.cellW,r=i[e]??0;for(let o=0;o<3;o+=1){if(t.phase==="winFlash"&&t.isWinningCell(e,o))continue;const s=t.offsets?t.offsets[e][o]:0;if(!Number.isFinite(s))continue;const l=t.boardY+o*t.cellH+s+r;l>t.height||l+t.cellH<0||t.drawSpriteCell(t.grid[e][o],n,l,t.cellW,t.cellH)}}}function O(t,i,e,n){const r=Math.sin(t*127.1+i*311.7+e*74.7+n*19.3)*43758.5453;return r-Math.floor(r)}function et(t){if(t.winningCells.length===0||t.phase!=="winFlash")return;const i=Math.max(0,t.now-t.winFlashStartedAt),e=i%1200/1200,n=i%1800/1800,r=1+Math.sin(n*Math.PI*2)*.075,o=1-(1-e)**2;for(const s of t.winningCells){const l=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;nt(t.ctx,l,u,t.cellW,t.cellH,t.winningCellBorderRgba,i);const h=t.getCell(s.col,s.row),d=t.cellW*r,c=t.cellH*r,a=(t.cellW-d)*.5,f=(t.cellH-c)*.5;t.ctx.save(),t.drawSpriteCell(h,l+a,u+f,d,c),t.ctx.restore(),st({ctx:t.ctx,cell:s,x:l,y:u,cycleProgress:e,easeOut:o,boardX:t.boardX,boardY:t.boardY,cellW:t.cellW,cellH:t.cellH,particleColorMode:t.particleColorMode,particleColorRgb:t.particleColorRgb})}}function nt(t,i,e,n,r,o,s){const l=Math.max(1,Math.floor(Math.min(n,r)*.04)),u=Math.max(1,Math.floor(Math.min(n,r)*.025)),[,,,h]=o,d=s*.15%360;t.save(),t.lineWidth=u,t.strokeStyle=`hsla(${d}, 90%, 70%, ${h})`;const c=u*.5;t.strokeRect(i+l+c,e+l+c,n-l*2-u,r-l*2-u),t.restore()}function st(t){const i=t.x+t.cellW*.5,e=t.y+t.cellH*.5,n=Math.min(t.cellW,t.cellH)*.72,r=Math.min(t.cellW,t.cellH)*.028,o=.92+(1-t.cycleProgress)*.08;t.ctx.save(),t.ctx.beginPath(),t.ctx.rect(t.boardX,t.boardY,t.cellW*3,t.cellH*3),t.ctx.clip();for(let s=0;s<34;s+=1){const l=O(t.cell.col,t.cell.row,s,1),u=O(t.cell.col,t.cell.row,s,2),h=O(t.cell.col,t.cell.row,s,3),d=O(t.cell.col,t.cell.row,s,4)*.28,c=O(t.cell.col,t.cell.row,s,5),a=S((t.cycleProgress-d)/(1-d),0,1);if(a<=0)continue;const f=l*Math.PI*2,W=n*a*(.35+u*.65)*(.85+t.easeOut*.15),b=i+Math.cos(f)*W,L=e+Math.sin(f)*W,M=.7+.9*Math.max(0,Math.sin((t.cycleProgress*11+c*2)*Math.PI*2)),rt=Math.max(1,r*(.55+h*.6)*(1-a*.5)),P=S((.9+M*.2)*o,.9,1);if(!(P<=0)){if(t.particleColorMode==="rainbow"){const D=(l*360+t.cycleProgress*240+t.cell.col*38+t.cell.row*22)%360;t.ctx.fillStyle=`hsla(${D}, 98%, 64%, ${P})`}else{const[D,ot,lt]=t.particleColorRgb;t.ctx.fillStyle=`rgba(${D}, ${ot}, ${lt}, ${P})`}t.ctx.beginPath(),t.ctx.arc(b,L,rt,0,Math.PI*2),t.ctx.fill()}}t.ctx.restore()}class C{canvas;container;button;ctx;spinQueueController;spriteUrl;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;winningCellBorderRgba;spriteImage=null;rafLoop=new Y;runtime=V();dpr=1;width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutgoingOffsets=x();scriptedIncomingOffsets=x();winningCells=[];grid;constructor(i){this.canvas=i.canvas,this.container=i.container,this.button=i.button,this.spriteUrl=i.sprite,this.spriteElementsCount=Math.max(1,i.spriteElementsCount??6),this.highlightInitialWinningCells=i.highlightInitialWinningCells!==!1,this.spinQueueController=new j(i.queuedSpinStates),this.particleColorRgb=z(i.particleColorRgb),this.particleColorMode=$(i.particleColorMode),this.winningCellBorderRgba=X(i.winningCellBorderRgba);const e=this.canvas.getContext("2d");if(!e)throw new Error("2D canvas context is not available");this.ctx=e,this.grid=i.initialSegments?Q(i.initialSegments,this.spriteElementsCount):A(this.spriteElementsCount)}async init(){this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),this.applyInitialHighlightIfNeeded(),this.startLoop()}destroy(){this.unbindEvents(),this.rafLoop.stop(),tt(this.runtime),this.clearWinningCells()}spin(){if(this.runtime.isSpinning||this.runtime.queueFinished)return;if(!this.spinQueueController.hasPending()){this.runtime.queueFinished=!0,this.button&&(this.button.disabled=!0);return}const i=this.spinQueueController.consume(),e=!this.spinQueueController.hasPending();J(this.runtime,{activeSpinState:i,shouldHighlightCurrentSpin:e}),this.button&&(this.button.disabled=!0);const n=this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(s=>G(s)):this.runtime.activeSpinState?.finaleSequence??[];this.scriptedCascadeQueue=n.map(s=>s.map(l=>[...l])),this.clearWinningCells();const r=this.runtime.activeSpinState?.stopRows?G(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,o=this.getNextGrid(r);this.startOutroTransition(o,performance.now())}bindEvents(){window.addEventListener("resize",this.resize),this.button?.addEventListener("click",this.onSpinClick)}unbindEvents(){window.removeEventListener("resize",this.resize),this.button?.removeEventListener("click",this.onSpinClick)}onSpinClick=()=>{this.runtime.isSpinning||this.spin()};getNextGrid(i){return i?E(i,this.spriteElementsCount):A(this.spriteElementsCount)}update(i){if(this.runtime.isSpinning){if(this.runtime.phase==="outro"){this.updateScriptedOutro(i);return}this.runtime.phase}}updateScriptedOutro(i){if(!this.scriptedOutgoingGrid||!this.scriptedPendingGrid){this.finishSpinWithUi();return}const{allOutgoingDone:e,allIncomingDone:n}=k({now:i,height:this.height,boardY:this.boardY,cellH:this.cellH,scriptedOutroStartedAt:this.scriptedOutroStartedAt,scriptedOutgoingOffsets:this.scriptedOutgoingOffsets,scriptedIncomingOffsets:this.scriptedIncomingOffsets});if(!(!e||!n)){if(!this.scriptedPendingGrid){this.finishSpinWithUi();return}if(this.grid=this.scriptedPendingGrid,this.scriptedOutgoingGrid=null,this.scriptedPendingGrid=null,this.clearWinningCells(),I(this.scriptedOutgoingOffsets,0),I(this.scriptedIncomingOffsets,0),!this.tryStartScriptedCascade(i)){if(!this.runtime.shouldHighlightCurrentSpin){this.finishSpinWithUi();return}this.winningCells=y(this.grid),m(this.runtime,i)}}}tryStartScriptedCascade(i){if(this.scriptedCascadeQueue.length===0)return!1;const e=this.scriptedCascadeQueue.shift();return e?(this.startOutroTransition(E(e,this.spriteElementsCount),i),!0):!1}startOutroTransition(i,e){this.scriptedOutgoingGrid=this.grid.map(n=>[...n]),this.scriptedPendingGrid=i.map(n=>[...n]),I(this.scriptedIncomingOffsets,Number.NaN),I(this.scriptedOutgoingOffsets,0),this.clearWinningCells(),this.runtime.phase="outro",this.scriptedOutroStartedAt=e}finishSpinWithUi(){const i=this.runtime.activeSpinState?.callback;K(this.runtime,this.spinQueueController.hasPending()),this.button&&(this.button.disabled=this.runtime.queueFinished),i?.()}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.winningCells=y(this.grid),m(this.runtime,performance.now()))}static ZERO_SWAY=[0,0,0];render(i){this.ctx.clearRect(0,0,this.width,this.height),this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawLayer(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsets,C.ZERO_SWAY),this.drawLayer(this.scriptedPendingGrid,this.scriptedIncomingOffsets,C.ZERO_SWAY)):this.drawBaseGrid(),et({ctx:this.ctx,now:i,phase:this.runtime.phase,winFlashStartedAt:this.runtime.winFlashStartedAt,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:this.drawSpriteCell})}drawBaseGrid(){this.drawLayer(this.grid,null,C.ZERO_SWAY)}drawLayer(i,e,n){it({ctx:this.ctx,phase:this.runtime.phase,grid:i,offsets:e,columnSway:n,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,width:this.width,height:this.height,isWinningCell:this.isWinningCell,drawSpriteCell:this.drawSpriteCell})}getCell=(i,e)=>this.grid[i][e];isWinningCell=(i,e)=>this.winningCells.some(n=>n.col===i&&n.row===e);drawSpriteCell=(i,e,n,r,o)=>{const s=this.spriteImage;if(!s)return;const l=_(i,this.spriteElementsCount),u=s.height/this.spriteElementsCount,h=Math.floor(l*u),d=Math.floor(u);this.ctx.drawImage(s,0,h,s.width,d,e,n,r,o)};clearWinningCells(){this.winningCells=[]}resize=()=>{const i=this.container.getBoundingClientRect();this.dpr=Math.max(1,Math.min(window.devicePixelRatio||1,2));const e=Math.max(300,Math.floor(i.width*this.dpr));this.width=e,this.height=e;const n=Math.floor(Math.min(this.width/3,this.height/3));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.canvas.width=this.width,this.canvas.height=this.height};async loadSpriteIfProvided(){if(!this.spriteUrl)return;const i=new Image;i.decoding="async",i.src=this.spriteUrl;try{await i.decode(),this.spriteImage=i}catch{this.spriteImage=null}}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(i=>(this.update(i),this.render(i),!0))}}g.CascadingReel=C,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/constants.ts","../src/utils/math.ts","../src/core/grid.ts","../src/core/loop.ts","../src/core/outro.ts","../src/normalize.ts","../src/core/spinQueue.ts","../src/core/state.ts","../src/render/canvasRenderer.ts","../src/CascadingReel.ts"],"sourcesContent":["export const DEFAULT_SPRITE_ELEMENTS_COUNT = 6;\nexport const GRID_COLS = 3;\nexport const GRID_ROWS = 3;\nexport const FLOW_OUTRO_ROW_GAP_MS = 34;\nexport const FLOW_ROW_BASE_SPACING_RATIO = 0.05;\nexport const FLOW_WIN_FLASH_MS = 1200;\nexport const FLOW_WIN_PULSE_PERIOD_MS = 1800;\nexport const FLOW_WIN_PULSE_AMPLITUDE = 0.075;\nexport const FLOW_WIN_PARTICLES_PER_CELL = 34;\nexport const DEFAULT_PARTICLE_COLOR_RGB: [number, number, number] = [255, 235, 110];\nexport const DEFAULT_WINNING_CELL_BORDER_RGBA: [number, number, number, number] = [\n 255, 255, 255, 0.78,\n];\nexport const FLOW_COLUMN_STAGGER_MS = 190;\nexport const FLOW_FALL_MS = 650;\nexport const FLOW_OUTRO_OVERLAP_MS = 220;\n","export function clamp(value: number, min: number, max: number): number {\n if (value < min) return min;\n if (value > max) return max;\n return value;\n}\n\nexport function easeOutCubic(t: number): number {\n return 1 - (1 - t) ** 3;\n}\n\nexport function randomInt(maxExclusive: number): number {\n return Math.floor(Math.random() * maxExclusive);\n}\n\nexport function normalizeSegment(segment: number, elementsCount: number): number {\n return ((segment % elementsCount) + elementsCount) % elementsCount;\n}\n\nexport function normalizeRgbChannel(value: number): number {\n return clamp(Math.round(value), 0, 255);\n}\n\nexport function normalizeAlphaChannel(value: number): number {\n return clamp(value, 0, 1);\n}\n","import { GRID_COLS, GRID_ROWS } from '../constants';\nimport type { CellPosition, SymbolId } from '../types';\nimport { randomInt } from '../utils/math';\n\nexport function createRandomGrid(spriteElementsCount: number): SymbolId[][] {\n const grid: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column: SymbolId[] = [];\n for (let row = 0; row < GRID_ROWS; row += 1) {\n column.push(randomInt(spriteElementsCount));\n }\n grid.push(column);\n }\n return grid;\n}\n\nexport function findMostFrequentCells(grid: SymbolId[][]): CellPosition[] {\n const counts = new Map<SymbolId, number>();\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const symbol = grid[col][row];\n counts.set(symbol, (counts.get(symbol) ?? 0) + 1);\n }\n }\n\n let selectedSymbol: SymbolId = grid[0][0];\n let maxCount = -1;\n for (const [symbol, count] of counts.entries()) {\n if (count > maxCount) {\n maxCount = count;\n selectedSymbol = symbol;\n }\n }\n\n const cells: CellPosition[] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (grid[col][row] === selectedSymbol) {\n cells.push({ col, row });\n }\n }\n }\n return cells;\n}\n\nexport function createZeroOffsets(): number[][] {\n return Array.from({ length: GRID_COLS }, () => Array.from({ length: GRID_ROWS }, () => 0));\n}\n\nexport function fillOffsets(offsets: number[][], value: number): void {\n for (let col = 0; col < GRID_COLS; col += 1) {\n for (let row = 0; row < GRID_ROWS; row += 1) {\n offsets[col][row] = value;\n }\n }\n}\n","export type RafStep = (now: number) => boolean;\n\nexport class RafLoop {\n private rafId: number | null = null;\n private step: RafStep | null = null;\n\n public start(step: RafStep): void {\n if (this.rafId !== null) return;\n this.step = step;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n public stop(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.step = null;\n }\n\n public isRunning(): boolean {\n return this.rafId !== null;\n }\n\n private readonly tick = (now: number): void => {\n if (!this.step) {\n this.stop();\n return;\n }\n\n const shouldContinue = this.step(now);\n if (shouldContinue === false) {\n this.stop();\n return;\n }\n\n if (this.rafId === null) return;\n this.rafId = requestAnimationFrame(this.tick);\n };\n}\n","import {\n FLOW_COLUMN_STAGGER_MS,\n FLOW_FALL_MS,\n FLOW_OUTRO_OVERLAP_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n FLOW_ROW_BASE_SPACING_RATIO,\n GRID_ROWS,\n} from '../constants';\nimport { clamp, easeOutCubic } from '../utils/math';\n\nexport function buildSequentialRowStartDelays(\n fromRowOffsets: [number, number, number],\n duration: number,\n gapMs: number,\n): [number, number, number] {\n const delays: [number, number, number] = [0, 0, 0];\n let nextDelay = 0;\n for (let row = GRID_ROWS - 1; row >= 0; row -= 1) {\n if (fromRowOffsets[row] === 0) {\n delays[row] = 0;\n continue;\n }\n delays[row] = nextDelay;\n const baseSpacing = Math.floor(duration * FLOW_ROW_BASE_SPACING_RATIO);\n nextDelay += baseSpacing + gapMs;\n }\n return delays;\n}\n\nexport function updateOutroOffsets(params: {\n now: number;\n height: number;\n boardY: number;\n cellH: number;\n scriptedOutroStartedAt: number;\n scriptedOutgoingOffsets: number[][];\n scriptedIncomingOffsets: number[][];\n}): { allOutgoingDone: boolean; allIncomingDone: boolean } {\n let allOutgoingDone = true;\n let allIncomingDone = true;\n\n const exitEpsilon = 2;\n const outgoingDistance = params.height - params.boardY + params.cellH + exitEpsilon;\n const outgoingOffsetsForOrder: [number, number, number] = [\n outgoingDistance,\n outgoingDistance,\n outgoingDistance,\n ];\n const incomingFromOffsets: [number, number, number] = [\n -params.cellH,\n -params.cellH * 2,\n -params.cellH * 3,\n ];\n const rowStartDelays = buildSequentialRowStartDelays(\n outgoingOffsetsForOrder,\n FLOW_FALL_MS,\n FLOW_OUTRO_ROW_GAP_MS,\n );\n const incomingStartShift = FLOW_FALL_MS - FLOW_OUTRO_OVERLAP_MS;\n\n for (let col = 0; col < params.scriptedOutgoingOffsets.length; col += 1) {\n const columnElapsed =\n params.now - (params.scriptedOutroStartedAt + col * FLOW_COLUMN_STAGGER_MS);\n for (let row = 0; row < GRID_ROWS; row += 1) {\n const rowElapsed = columnElapsed - rowStartDelays[row];\n\n if (rowElapsed <= 0) {\n params.scriptedOutgoingOffsets[col][row] = 0;\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allOutgoingDone = false;\n allIncomingDone = false;\n continue;\n }\n\n const tOut = clamp(rowElapsed / FLOW_FALL_MS, 0, 1);\n const easedOut = easeOutCubic(tOut);\n params.scriptedOutgoingOffsets[col][row] = outgoingDistance * easedOut;\n if (tOut < 1) allOutgoingDone = false;\n\n const incomingElapsed = rowElapsed - incomingStartShift;\n if (incomingElapsed <= 0) {\n params.scriptedIncomingOffsets[col][row] = Number.NaN;\n allIncomingDone = false;\n continue;\n }\n\n const tIn = clamp(incomingElapsed / FLOW_FALL_MS, 0, 1);\n const easedIn = easeOutCubic(tIn);\n params.scriptedIncomingOffsets[col][row] = incomingFromOffsets[row] * (1 - easedIn);\n if (tIn < 1) allIncomingDone = false;\n }\n }\n\n return { allOutgoingDone, allIncomingDone };\n}\n","import {\n DEFAULT_PARTICLE_COLOR_RGB,\n DEFAULT_WINNING_CELL_BORDER_RGBA,\n GRID_COLS,\n GRID_ROWS,\n} from './constants';\nimport type { SpinState, SymbolId } from './types';\nimport { normalizeAlphaChannel, normalizeRgbChannel, normalizeSegment } from './utils/math';\n\nexport function normalizeParticleColor(color?: [number, number, number]): [number, number, number] {\n if (!color) return DEFAULT_PARTICLE_COLOR_RGB;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n ];\n}\n\nexport function normalizeParticleColorMode(mode?: 'solid' | 'rainbow'): 'solid' | 'rainbow' {\n if (mode === 'rainbow') return 'rainbow';\n return 'solid';\n}\n\nexport function normalizeWinningCellBorderColor(\n color?: [number, number, number, number],\n): [number, number, number, number] {\n if (!color) return DEFAULT_WINNING_CELL_BORDER_RGBA;\n return [\n normalizeRgbChannel(color[0]),\n normalizeRgbChannel(color[1]),\n normalizeRgbChannel(color[2]),\n normalizeAlphaChannel(color[3]),\n ];\n}\n\nexport function rowsToStopGrid(rows: number[][]): number[][] {\n if (rows.length !== GRID_ROWS) {\n throw new Error(`rows must contain ${GRID_ROWS} rows`);\n }\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (!Array.isArray(rows[row]) || rows[row].length !== GRID_COLS) {\n throw new Error(`rows[${row}] must contain ${GRID_COLS} columns`);\n }\n }\n\n return [\n [rows[0][0], rows[1][0], rows[2][0]],\n [rows[0][1], rows[1][1], rows[2][1]],\n [rows[0][2], rows[1][2], rows[2][2]],\n ];\n}\n\nexport function normalizeStopGrid(stopGrid: number[][], elementsCount: number): SymbolId[][] {\n if (stopGrid.length !== GRID_COLS) {\n throw new Error(`stopGrid must contain ${GRID_COLS} columns`);\n }\n\n const next: SymbolId[][] = [];\n for (let col = 0; col < GRID_COLS; col += 1) {\n const column = stopGrid[col];\n if (!Array.isArray(column) || column.length !== GRID_ROWS) {\n throw new Error(`stopGrid[${col}] must contain ${GRID_ROWS} rows`);\n }\n next[col] = [\n normalizeSegment(column[0], elementsCount),\n normalizeSegment(column[1], elementsCount),\n normalizeSegment(column[2], elementsCount),\n ];\n }\n return next;\n}\n\nexport function normalizeInitialSegments(\n initialSegments: number[][],\n elementsCount: number,\n): SymbolId[][] {\n return normalizeStopGrid(rowsToStopGrid(initialSegments), elementsCount);\n}\n\nexport function cloneSpinState(state: SpinState): SpinState {\n return {\n stopGrid: state.stopGrid?.map((column) => [...column]),\n stopRows: state.stopRows?.map((row) => [...row]),\n finaleSequence: state.finaleSequence?.map((grid) => grid.map((column) => [...column])),\n finaleSequenceRows: state.finaleSequenceRows?.map((grid) => grid.map((row) => [...row])),\n callback: state.callback,\n };\n}\n","import { cloneSpinState } from '../normalize';\nimport type { SpinState } from '../types';\n\nexport class SpinQueueController {\n private queue: SpinState[];\n\n public constructor(initialQueue?: SpinState[]) {\n this.queue = (initialQueue ?? []).map((entry) => cloneSpinState(entry));\n }\n\n public hasPending(): boolean {\n return this.queue.length > 0;\n }\n\n public consume(): SpinState | null {\n if (this.queue.length === 0) return null;\n return this.queue.shift() ?? null;\n }\n}\n","import type { SpinPhase, SpinState } from '../types';\n\nexport type RuntimeState = {\n isSpinning: boolean;\n hasStartedFirstSpin: boolean;\n queueFinished: boolean;\n shouldHighlightCurrentSpin: boolean;\n activeSpinState: SpinState | null;\n phase: SpinPhase;\n winFlashStartedAt: number;\n};\n\nexport function createRuntimeState(): RuntimeState {\n return {\n isSpinning: false,\n hasStartedFirstSpin: false,\n queueFinished: false,\n shouldHighlightCurrentSpin: false,\n activeSpinState: null,\n phase: 'idle',\n winFlashStartedAt: 0,\n };\n}\n\nexport function beginSpin(\n state: RuntimeState,\n params: {\n activeSpinState: SpinState | null;\n shouldHighlightCurrentSpin: boolean;\n },\n): void {\n state.hasStartedFirstSpin = true;\n state.isSpinning = true;\n state.phase = 'outro';\n state.activeSpinState = params.activeSpinState;\n state.shouldHighlightCurrentSpin = params.shouldHighlightCurrentSpin;\n}\n\nexport function finishSpin(state: RuntimeState, hasPendingInQueue: boolean): void {\n state.phase = 'idle';\n state.isSpinning = false;\n state.shouldHighlightCurrentSpin = false;\n state.queueFinished = !hasPendingInQueue;\n state.activeSpinState = null;\n}\n\nexport function startWinFlash(state: RuntimeState, startedAt: number): void {\n state.winFlashStartedAt = startedAt;\n state.phase = 'winFlash';\n}\n\nexport function destroyState(state: RuntimeState): void {\n state.isSpinning = false;\n state.queueFinished = true;\n}\n","import {\n FLOW_WIN_FLASH_MS,\n FLOW_WIN_PARTICLES_PER_CELL,\n FLOW_WIN_PULSE_AMPLITUDE,\n FLOW_WIN_PULSE_PERIOD_MS,\n GRID_COLS,\n GRID_ROWS,\n} from '../constants';\nimport type { CellPosition, SpinPhase, SymbolId } from '../types';\nimport { clamp } from '../utils/math';\n\ntype DrawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n) => void;\n\nexport function drawGridLayer(params: {\n ctx: CanvasRenderingContext2D;\n phase: SpinPhase;\n grid: SymbolId[][];\n offsets: number[][] | null;\n columnSway?: [number, number, number];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n width: number;\n height: number;\n isWinningCell: (col: number, row: number) => boolean;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n const sway = params.columnSway ?? [0, 0, 0];\n\n for (let col = 0; col < GRID_COLS; col += 1) {\n const x = params.boardX + col * params.cellW;\n const swayY = sway[col] ?? 0;\n for (let row = 0; row < GRID_ROWS; row += 1) {\n if (params.phase === 'winFlash' && params.isWinningCell(col, row)) continue;\n const offsetY = params.offsets ? params.offsets[col][row] : 0;\n if (!Number.isFinite(offsetY)) continue;\n\n const y = params.boardY + row * params.cellH + offsetY + swayY;\n if (y > params.height || y + params.cellH < 0) continue;\n\n params.drawSpriteCell(params.grid[col][row], x, y, params.cellW, params.cellH);\n }\n }\n}\n\nfunction hash01(a: number, b: number, c: number, d: number): number {\n const value = Math.sin(a * 127.1 + b * 311.7 + c * 74.7 + d * 19.3) * 43758.5453;\n return value - Math.floor(value);\n}\n\nexport function drawWinningEffects(params: {\n ctx: CanvasRenderingContext2D;\n now: number;\n phase: SpinPhase;\n winFlashStartedAt: number;\n winningCells: CellPosition[];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n winningCellBorderRgba: [number, number, number, number];\n getCell: (col: number, row: number) => SymbolId;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n if (params.winningCells.length === 0) return;\n if (params.phase !== 'winFlash') return;\n\n const elapsed = Math.max(0, params.now - params.winFlashStartedAt);\n const cycleProgress = (elapsed % FLOW_WIN_FLASH_MS) / FLOW_WIN_FLASH_MS;\n const pulseProgress = (elapsed % FLOW_WIN_PULSE_PERIOD_MS) / FLOW_WIN_PULSE_PERIOD_MS;\n const pulse = 1 + Math.sin(pulseProgress * Math.PI * 2) * FLOW_WIN_PULSE_AMPLITUDE;\n const easeOut = 1 - (1 - cycleProgress) ** 2;\n\n for (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawWinningCellBorder(\n params.ctx,\n x,\n y,\n params.cellW,\n params.cellH,\n params.winningCellBorderRgba,\n elapsed,\n );\n\n const symbol = params.getCell(cell.col, cell.row);\n const scaledW = params.cellW * pulse;\n const scaledH = params.cellH * pulse;\n const offsetX = (params.cellW - scaledW) * 0.5;\n const offsetY = (params.cellH - scaledH) * 0.5;\n\n params.ctx.save();\n params.drawSpriteCell(symbol, x + offsetX, y + offsetY, scaledW, scaledH);\n params.ctx.restore();\n\n drawCellParticleBurst({\n ctx: params.ctx,\n cell,\n x,\n y,\n cycleProgress,\n easeOut,\n boardX: params.boardX,\n boardY: params.boardY,\n cellW: params.cellW,\n cellH: params.cellH,\n particleColorMode: params.particleColorMode,\n particleColorRgb: params.particleColorRgb,\n });\n }\n}\n\nfunction drawWinningCellBorder(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n cellW: number,\n cellH: number,\n color: [number, number, number, number],\n elapsed: number,\n): void {\n const inset = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.04));\n const lineWidth = Math.max(1, Math.floor(Math.min(cellW, cellH) * 0.025));\n const [, , , a] = color;\n const hue = (elapsed * 0.15) % 360;\n\n ctx.save();\n ctx.lineWidth = lineWidth;\n ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${a})`;\n const halfLine = lineWidth * 0.5;\n ctx.strokeRect(\n x + inset + halfLine,\n y + inset + halfLine,\n cellW - inset * 2 - lineWidth,\n cellH - inset * 2 - lineWidth,\n );\n ctx.restore();\n}\n\nfunction drawCellParticleBurst(params: {\n ctx: CanvasRenderingContext2D;\n cell: CellPosition;\n x: number;\n y: number;\n cycleProgress: number;\n easeOut: number;\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particleColorMode: 'solid' | 'rainbow';\n particleColorRgb: [number, number, number];\n}): void {\n const centerX = params.x + params.cellW * 0.5;\n const centerY = params.y + params.cellH * 0.5;\n const maxDistance = Math.min(params.cellW, params.cellH) * 0.72;\n const baseRadius = Math.min(params.cellW, params.cellH) * 0.028;\n const globalFade = 0.92 + (1 - params.cycleProgress) * 0.08;\n\n params.ctx.save();\n params.ctx.beginPath();\n params.ctx.rect(params.boardX, params.boardY, params.cellW * GRID_COLS, params.cellH * GRID_ROWS);\n params.ctx.clip();\n\n for (let i = 0; i < FLOW_WIN_PARTICLES_PER_CELL; i += 1) {\n const seedA = hash01(params.cell.col, params.cell.row, i, 1);\n const seedB = hash01(params.cell.col, params.cell.row, i, 2);\n const seedC = hash01(params.cell.col, params.cell.row, i, 3);\n const phaseOffset = hash01(params.cell.col, params.cell.row, i, 4) * 0.28;\n const twinkleSeed = hash01(params.cell.col, params.cell.row, i, 5);\n const particleT = clamp((params.cycleProgress - phaseOffset) / (1 - phaseOffset), 0, 1);\n if (particleT <= 0) continue;\n\n const direction = seedA * Math.PI * 2;\n const distance =\n maxDistance * particleT * (0.35 + seedB * 0.65) * (0.85 + params.easeOut * 0.15);\n const px = centerX + Math.cos(direction) * distance;\n const py = centerY + Math.sin(direction) * distance;\n const twinkle =\n 0.7 +\n 0.9 * Math.max(0, Math.sin((params.cycleProgress * 11 + twinkleSeed * 2) * Math.PI * 2));\n const radius = Math.max(1, baseRadius * (0.55 + seedC * 0.6) * (1 - particleT * 0.5));\n const alpha = clamp((0.9 + twinkle * 0.2) * globalFade, 0.9, 1);\n if (alpha <= 0) continue;\n\n if (params.particleColorMode === 'rainbow') {\n const hue =\n (seedA * 360 + params.cycleProgress * 240 + params.cell.col * 38 + params.cell.row * 22) %\n 360;\n params.ctx.fillStyle = `hsla(${hue}, 98%, 64%, ${alpha})`;\n } else {\n const [r, g, b] = params.particleColorRgb;\n params.ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n params.ctx.beginPath();\n params.ctx.arc(px, py, radius, 0, Math.PI * 2);\n params.ctx.fill();\n }\n params.ctx.restore();\n}\n","import { DEFAULT_SPRITE_ELEMENTS_COUNT, GRID_COLS, GRID_ROWS } from './constants';\nimport {\n createRandomGrid,\n createZeroOffsets,\n fillOffsets,\n findMostFrequentCells,\n} from './core/grid';\nimport { RafLoop } from './core/loop';\nimport { updateOutroOffsets } from './core/outro';\nimport { SpinQueueController } from './core/spinQueue';\nimport {\n beginSpin,\n createRuntimeState,\n destroyState,\n finishSpin,\n startWinFlash,\n} from './core/state';\nimport {\n normalizeInitialSegments,\n normalizeParticleColor,\n normalizeParticleColorMode,\n normalizeStopGrid,\n normalizeWinningCellBorderColor,\n rowsToStopGrid,\n} from './normalize';\nimport { drawGridLayer, drawWinningEffects } from './render/canvasRenderer';\nimport type { CascadingReelConfig, CellPosition, SymbolId } from './types';\nimport { normalizeSegment } from './utils/math';\n\nexport class CascadingReel {\n private readonly canvas: HTMLCanvasElement;\n private readonly container: HTMLElement;\n private readonly button?: HTMLButtonElement;\n private readonly ctx: CanvasRenderingContext2D;\n private readonly spinQueueController: SpinQueueController;\n private readonly spriteUrl?: string;\n private readonly spriteElementsCount: number;\n private readonly highlightInitialWinningCells: boolean;\n private readonly particleColorRgb: [number, number, number];\n private readonly particleColorMode: 'solid' | 'rainbow';\n private readonly winningCellBorderRgba: [number, number, number, number];\n\n private spriteImage: HTMLImageElement | null = null;\n private readonly rafLoop = new RafLoop();\n private readonly runtime = createRuntimeState();\n private dpr = 1;\n private width = 0;\n private height = 0;\n private cellW = 0;\n private cellH = 0;\n private boardX = 0;\n private boardY = 0;\n private scriptedCascadeQueue: number[][][] = [];\n private scriptedOutgoingGrid: SymbolId[][] | null = null;\n private scriptedPendingGrid: SymbolId[][] | null = null;\n private scriptedOutroStartedAt = 0;\n private scriptedOutgoingOffsets: number[][] = createZeroOffsets();\n private scriptedIncomingOffsets: number[][] = createZeroOffsets();\n private winningCells: CellPosition[] = [];\n private grid: SymbolId[][];\n\n public constructor(config: CascadingReelConfig) {\n this.canvas = config.canvas;\n this.container = config.container;\n this.button = config.button;\n this.spriteUrl = config.sprite;\n this.spriteElementsCount = Math.max(\n 1,\n config.spriteElementsCount ?? DEFAULT_SPRITE_ELEMENTS_COUNT,\n );\n this.highlightInitialWinningCells = config.highlightInitialWinningCells !== false;\n this.spinQueueController = new SpinQueueController(config.queuedSpinStates);\n this.particleColorRgb = normalizeParticleColor(config.particleColorRgb);\n this.particleColorMode = normalizeParticleColorMode(config.particleColorMode);\n this.winningCellBorderRgba = normalizeWinningCellBorderColor(config.winningCellBorderRgba);\n\n const context = this.canvas.getContext('2d');\n if (!context) {\n throw new Error('2D canvas context is not available');\n }\n this.ctx = context;\n this.grid = config.initialSegments\n ? normalizeInitialSegments(config.initialSegments, this.spriteElementsCount)\n : createRandomGrid(this.spriteElementsCount);\n }\n\n public async init(): Promise<void> {\n this.bindEvents();\n this.resize();\n await this.loadSpriteIfProvided();\n this.applyInitialHighlightIfNeeded();\n this.startLoop();\n }\n\n public destroy(): void {\n this.unbindEvents();\n this.rafLoop.stop();\n destroyState(this.runtime);\n this.clearWinningCells();\n }\n\n public spin(): void {\n if (this.runtime.isSpinning) return;\n if (this.runtime.queueFinished) return;\n if (!this.spinQueueController.hasPending()) {\n this.runtime.queueFinished = true;\n if (this.button) this.button.disabled = true;\n return;\n }\n\n const activeSpinState = this.spinQueueController.consume();\n const shouldHighlightCurrentSpin = !this.spinQueueController.hasPending();\n beginSpin(this.runtime, {\n activeSpinState,\n shouldHighlightCurrentSpin,\n });\n\n if (this.button) this.button.disabled = true;\n\n const scriptedSource = this.runtime.activeSpinState?.finaleSequenceRows\n ? this.runtime.activeSpinState.finaleSequenceRows.map((rows) => rowsToStopGrid(rows))\n : (this.runtime.activeSpinState?.finaleSequence ?? []);\n this.scriptedCascadeQueue = scriptedSource.map((grid) => grid.map((column) => [...column]));\n this.clearWinningCells();\n\n const stopGridSource = this.runtime.activeSpinState?.stopRows\n ? rowsToStopGrid(this.runtime.activeSpinState.stopRows)\n : this.runtime.activeSpinState?.stopGrid;\n const nextGrid = this.getNextGrid(stopGridSource);\n this.startOutroTransition(nextGrid, performance.now());\n }\n\n private bindEvents(): void {\n window.addEventListener('resize', this.resize);\n this.button?.addEventListener('click', this.onSpinClick);\n }\n\n private unbindEvents(): void {\n window.removeEventListener('resize', this.resize);\n this.button?.removeEventListener('click', this.onSpinClick);\n }\n\n private readonly onSpinClick = (): void => {\n if (this.runtime.isSpinning) return;\n this.spin();\n };\n\n private getNextGrid(stopGrid?: number[][]): SymbolId[][] {\n if (!stopGrid) return createRandomGrid(this.spriteElementsCount);\n return normalizeStopGrid(stopGrid, this.spriteElementsCount);\n }\n\n private update(now: number): void {\n if (!this.runtime.isSpinning) return;\n if (this.runtime.phase === 'outro') {\n this.updateScriptedOutro(now);\n return;\n }\n if (this.runtime.phase === 'winFlash') return;\n }\n\n private updateScriptedOutro(now: number): void {\n if (!this.scriptedOutgoingGrid || !this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n const { allOutgoingDone, allIncomingDone } = updateOutroOffsets({\n now,\n height: this.height,\n boardY: this.boardY,\n cellH: this.cellH,\n scriptedOutroStartedAt: this.scriptedOutroStartedAt,\n scriptedOutgoingOffsets: this.scriptedOutgoingOffsets,\n scriptedIncomingOffsets: this.scriptedIncomingOffsets,\n });\n\n if (!allOutgoingDone || !allIncomingDone) return;\n if (!this.scriptedPendingGrid) {\n this.finishSpinWithUi();\n return;\n }\n\n this.grid = this.scriptedPendingGrid;\n this.scriptedOutgoingGrid = null;\n this.scriptedPendingGrid = null;\n this.clearWinningCells();\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n fillOffsets(this.scriptedIncomingOffsets, 0);\n\n if (this.tryStartScriptedCascade(now)) return;\n if (!this.runtime.shouldHighlightCurrentSpin) {\n this.finishSpinWithUi();\n return;\n }\n\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, now);\n }\n\n private tryStartScriptedCascade(now: number): boolean {\n if (this.scriptedCascadeQueue.length === 0) return false;\n const nextGrid = this.scriptedCascadeQueue.shift();\n if (!nextGrid) return false;\n this.startOutroTransition(normalizeStopGrid(nextGrid, this.spriteElementsCount), now);\n return true;\n }\n\n private startOutroTransition(nextGrid: SymbolId[][], now: number): void {\n this.scriptedOutgoingGrid = this.grid.map((column) => [...column]);\n this.scriptedPendingGrid = nextGrid.map((column) => [...column]);\n fillOffsets(this.scriptedIncomingOffsets, Number.NaN);\n fillOffsets(this.scriptedOutgoingOffsets, 0);\n this.clearWinningCells();\n this.runtime.phase = 'outro';\n this.scriptedOutroStartedAt = now;\n }\n\n private finishSpinWithUi(): void {\n const callback = this.runtime.activeSpinState?.callback;\n finishSpin(this.runtime, this.spinQueueController.hasPending());\n if (this.button) this.button.disabled = this.runtime.queueFinished;\n callback?.();\n }\n\n private applyInitialHighlightIfNeeded(): void {\n if (!this.highlightInitialWinningCells) return;\n this.winningCells = findMostFrequentCells(this.grid);\n startWinFlash(this.runtime, performance.now());\n }\n\n private static readonly ZERO_SWAY: [number, number, number] = [0, 0, 0];\n\n private render(now: number): void {\n this.ctx.clearRect(0, 0, this.width, this.height);\n if (this.runtime.phase === 'outro' && this.scriptedOutgoingGrid && this.scriptedPendingGrid) {\n this.drawLayer(\n this.scriptedOutgoingGrid,\n this.scriptedOutgoingOffsets,\n CascadingReel.ZERO_SWAY,\n );\n this.drawLayer(\n this.scriptedPendingGrid,\n this.scriptedIncomingOffsets,\n CascadingReel.ZERO_SWAY,\n );\n } else {\n this.drawBaseGrid();\n }\n\n drawWinningEffects({\n ctx: this.ctx,\n now,\n phase: this.runtime.phase,\n winFlashStartedAt: this.runtime.winFlashStartedAt,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private drawBaseGrid(): void {\n this.drawLayer(this.grid, null, CascadingReel.ZERO_SWAY);\n }\n\n private drawLayer(\n grid: SymbolId[][],\n offsets: number[][] | null,\n columnSway: [number, number, number],\n ): void {\n drawGridLayer({\n ctx: this.ctx,\n phase: this.runtime.phase,\n grid,\n offsets,\n columnSway,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n width: this.width,\n height: this.height,\n isWinningCell: this.isWinningCell,\n drawSpriteCell: this.drawSpriteCell,\n });\n }\n\n private readonly getCell = (col: number, row: number): SymbolId => {\n return this.grid[col][row];\n };\n\n private readonly isWinningCell = (col: number, row: number): boolean => {\n return this.winningCells.some((cell) => cell.col === col && cell.row === row);\n };\n\n private readonly drawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void => {\n const image = this.spriteImage;\n if (!image) return;\n\n const segmentIndex = normalizeSegment(symbolId, this.spriteElementsCount);\n const segmentHeight = image.height / this.spriteElementsCount;\n const sourceY = Math.floor(segmentIndex * segmentHeight);\n const sourceHeight = Math.floor(segmentHeight);\n\n this.ctx.drawImage(image, 0, sourceY, image.width, sourceHeight, x, y, width, height);\n };\n\n private clearWinningCells(): void {\n this.winningCells = [];\n }\n\n private readonly resize = (): void => {\n const bounds = this.container.getBoundingClientRect();\n this.dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n const side = Math.max(300, Math.floor(bounds.width * this.dpr));\n this.width = side;\n this.height = side;\n const squareSize = Math.floor(Math.min(this.width / GRID_COLS, this.height / GRID_ROWS));\n this.cellW = squareSize;\n this.cellH = squareSize;\n this.boardX = Math.floor((this.width - this.cellW * GRID_COLS) / 2);\n this.boardY = Math.floor((this.height - this.cellH * GRID_ROWS) / 2);\n\n this.canvas.width = this.width;\n this.canvas.height = this.height;\n };\n\n private async loadSpriteIfProvided(): Promise<void> {\n if (!this.spriteUrl) return;\n const image = new Image();\n image.decoding = 'async';\n image.src = this.spriteUrl;\n try {\n await image.decode();\n this.spriteImage = image;\n } catch {\n this.spriteImage = null;\n }\n }\n\n private startLoop(): void {\n if (this.rafLoop.isRunning()) return;\n this.rafLoop.start((time: number): boolean => {\n this.update(time);\n this.render(time);\n return true;\n });\n }\n}\n"],"names":["DEFAULT_PARTICLE_COLOR_RGB","DEFAULT_WINNING_CELL_BORDER_RGBA","FLOW_COLUMN_STAGGER_MS","FLOW_FALL_MS","FLOW_OUTRO_OVERLAP_MS","clamp","value","min","max","easeOutCubic","randomInt","maxExclusive","normalizeSegment","segment","elementsCount","normalizeRgbChannel","normalizeAlphaChannel","createRandomGrid","spriteElementsCount","grid","col","column","row","findMostFrequentCells","counts","symbol","selectedSymbol","maxCount","count","cells","createZeroOffsets","fillOffsets","offsets","RafLoop","step","now","buildSequentialRowStartDelays","fromRowOffsets","duration","gapMs","delays","nextDelay","baseSpacing","updateOutroOffsets","params","allOutgoingDone","allIncomingDone","outgoingDistance","outgoingOffsetsForOrder","incomingFromOffsets","rowStartDelays","incomingStartShift","columnElapsed","rowElapsed","tOut","easedOut","incomingElapsed","tIn","easedIn","normalizeParticleColor","color","normalizeParticleColorMode","mode","normalizeWinningCellBorderColor","rowsToStopGrid","rows","normalizeStopGrid","stopGrid","next","normalizeInitialSegments","initialSegments","cloneSpinState","state","SpinQueueController","initialQueue","entry","createRuntimeState","beginSpin","finishSpin","hasPendingInQueue","startWinFlash","startedAt","destroyState","drawGridLayer","sway","x","swayY","offsetY","y","hash01","a","b","c","d","drawWinningEffects","elapsed","cycleProgress","pulseProgress","pulse","easeOut","cell","drawWinningCellBorder","scaledW","scaledH","offsetX","drawCellParticleBurst","ctx","cellW","cellH","inset","lineWidth","hue","halfLine","centerX","centerY","maxDistance","baseRadius","globalFade","i","seedA","seedB","seedC","phaseOffset","twinkleSeed","particleT","direction","distance","px","py","twinkle","radius","alpha","r","g","CascadingReel","config","context","activeSpinState","shouldHighlightCurrentSpin","scriptedSource","stopGridSource","nextGrid","callback","columnSway","symbolId","width","height","image","segmentIndex","segmentHeight","sourceY","sourceHeight","bounds","side","squareSize","time"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "cascading-reel",
3
+ "version": "0.0.1",
4
+ "description": "Canvas-based cascading reel animation for slot-style UIs.",
5
+ "author": "ux-ui.pro",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ux-ui-pro/cascading-reel.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/ux-ui-pro/cascading-reel/issues"
13
+ },
14
+ "homepage": "https://github.com/ux-ui-pro/cascading-reel",
15
+ "sideEffects": false,
16
+ "type": "module",
17
+ "packageManager": "yarn@1.22.22",
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ },
21
+ "scripts": {
22
+ "clean": "rimraf dist",
23
+ "build": "vite build",
24
+ "dev": "vite",
25
+ "verify": "yarn build && yarn lint && yarn typecheck && yarn test:smoke",
26
+ "lint": "biome check src",
27
+ "lint:fix": "biome check --write src",
28
+ "format": "biome format --write src",
29
+ "typecheck": "tsc -p tsconfig.json --noEmit",
30
+ "test:smoke": "node --test tests/*.js",
31
+ "prepublishOnly": "yarn clean && yarn verify"
32
+ },
33
+ "source": "src/index.ts",
34
+ "main": "dist/index.cjs.js",
35
+ "module": "dist/index.es.js",
36
+ "browser": "./dist/index.umd.js",
37
+ "types": "dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/index.es.js",
42
+ "require": "./dist/index.cjs.js",
43
+ "default": "./dist/index.umd.js"
44
+ },
45
+ "./dist/*": "./dist/*"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "README.md",
50
+ "LICENSE"
51
+ ],
52
+ "devDependencies": {
53
+ "@biomejs/biome": "2.3.12",
54
+ "@types/node": "25.0.10",
55
+ "rimraf": "6.1.2",
56
+ "typescript": "5.9.3",
57
+ "vite": "7.3.1",
58
+ "vite-plugin-dts": "4.5.4"
59
+ },
60
+ "keywords": [
61
+ "typescript",
62
+ "javascript",
63
+ "canvas",
64
+ "html5-canvas",
65
+ "webgl",
66
+ "animation",
67
+ "slot",
68
+ "slots",
69
+ "slot-machine",
70
+ "reel",
71
+ "reels",
72
+ "cascading-reel",
73
+ "casino",
74
+ "game",
75
+ "sprite",
76
+ "spritesheet",
77
+ "motion-blur",
78
+ "performance",
79
+ "dpr",
80
+ "browser"
81
+ ]
82
+ }