cascading-reel 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const V=6,g=3,f=3,Z=34,j=.05,y=1800,K=.1,x=720,_=34,H=20,T=12,J=[255,235,110],tt=[255,255,255,.78],it=190,R=650,et=220,nt=200;function m(t,i,e){return t<i?i:t>e?e:t}function q(t){return 1-(1-t)**3}function st(t){return Math.floor(Math.random()*t)}function I(t,i){return(t%i+i)%i}function b(t){return m(Math.round(t),0,255)}function lt(t){return m(t,0,1)}function G(t){const i=[];for(let e=0;e<g;e+=1){const n=[];for(let l=0;l<f;l+=1)n.push(st(t));i.push(n)}return i}function N(t){const i=new Map;for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1){const u=t[s][r];i.set(u,(i.get(u)??0)+1)}let e=t[0][0],n=-1;for(const[s,r]of i.entries())r>n&&(n=r,e=s);const l=[];for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1)t[s][r]===e&&l.push({col:s,row:r});return l}function Y(){return Array.from({length:g},()=>Array.from({length:f},()=>0))}function W(t,i){for(let e=0;e<g;e+=1)for(let n=0;n<f;n+=1)t[e][n]=i}class rt{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 ot(t,i,e){const n=[0,0,0];let l=0;for(let s=f-1;s>=0;s-=1){if(t[s]===0){n[s]=0;continue}n[s]=l;const r=Math.floor(i*j);l+=r+e}return n}function ht(t){let i=!0,e=!0;const l=t.height-t.boardY+t.cellH+2,s=[l,l,l],r=[-t.cellH,-t.cellH*2,-t.cellH*3],u=ot(s,R,Z),o=R-et;for(let h=0;h<t.scriptedOutgoingOffsets.length;h+=1){const a=t.now-(t.scriptedOutroStartedAt+h*it);for(let c=0;c<f;c+=1){const d=a-u[c];if(d<=0){t.scriptedOutgoingOffsets[h][c]=0,t.scriptedIncomingOffsets[h][c]=Number.NaN,i=!1,e=!1;continue}const p=m(d/R,0,1),S=q(p);t.scriptedOutgoingOffsets[h][c]=l*S,p<1&&(i=!1);const E=d-o;if(E<=0){t.scriptedIncomingOffsets[h][c]=Number.NaN,e=!1;continue}const C=m(E/R,0,1),O=q(C);t.scriptedIncomingOffsets[h][c]=r[c]*(1-O),C<1&&(e=!1)}}return{allOutgoingDone:i,allIncomingDone:e}}function ct(t){return t?[b(t[0]),b(t[1]),b(t[2])]:J}function ut(t){return t==="rainbow"?"rainbow":"solid"}function at(t){return t==="high"||t==="balanced"||t==="low"||t==="auto"?t:"auto"}function dt(t){return t?[b(t[0]),b(t[1]),b(t[2]),lt(t[3])]:tt}function F(t){if(t.length!==f)throw new Error(`rows must contain ${f} rows`);for(let i=0;i<f;i+=1)if(!Array.isArray(t[i])||t[i].length!==g)throw new Error(`rows[${i}] must contain ${g} 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 v(t,i){if(t.length!==g)throw new Error(`stopGrid must contain ${g} columns`);const e=[];for(let n=0;n<g;n+=1){const l=t[n];if(!Array.isArray(l)||l.length!==f)throw new Error(`stopGrid[${n}] must contain ${f} rows`);e[n]=[I(l[0],i),I(l[1],i),I(l[2],i)]}return e}function ft(t,i){return v(F(t),i)}function gt(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 pt{queue;constructor(i){this.queue=(i??[]).map(e=>gt(e))}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?null:this.queue.shift()??null}}function St(){return{isSpinning:!1,hasStartedFirstSpin:!1,queueFinished:!1,shouldHighlightCurrentSpin:!1,activeSpinState:null,phase:"idle",winFlashStartedAt:0,outroStartedAt:0,idleStartedAt:0,preSpinStartedAt:0,swayEnvelope:1,winEffectsEnvelope:1}}function Ct(t,i){t.hasStartedFirstSpin=!0,t.isSpinning=!0,t.phase="outro",t.outroStartedAt=i.startedAt,t.activeSpinState=i.activeSpinState,t.shouldHighlightCurrentSpin=i.shouldHighlightCurrentSpin}function wt(t,i,e){t.phase="idle",t.idleStartedAt=e,t.isSpinning=!1,t.shouldHighlightCurrentSpin=!1,t.queueFinished=!i,t.activeSpinState=null}function D(t,i){t.winFlashStartedAt=i,t.phase="winFlash"}function Et(t){t.isSpinning=!1,t.queueFinished=!0}const Ot=3,bt=.002;function At(t){const i=t.columnSway??[0,0,0],e=Ot*(Math.PI/180),n=Math.max(0,Math.min(1,t.swayEnvelope)),l=t.skipWinningCells??!1;for(let s=0;s<g;s+=1){const r=t.boardX+s*t.cellW,u=i[s]??0;for(let o=0;o<f;o+=1){if((l||t.phase==="winFlash"||t.phase==="preSpin")&&t.isWinningCell(s,o))continue;const h=t.offsets?t.offsets[s][o]:0;if(!Number.isFinite(h))continue;const a=t.boardY+o*t.cellH+h+u;if(a>t.height||a+t.cellH<0)continue;const d=Math.sin(t.now*bt+s*1.1+o*1.3)*e*n,p=r+t.cellW*.5,S=a+t.cellH*.5;t.ctx.save(),t.ctx.translate(p,S),t.ctx.rotate(d),t.ctx.translate(-t.cellW*.5,-t.cellH*.5),t.drawSpriteCell(t.grid[s][o],0,0,t.cellW,t.cellH),t.ctx.restore()}}}function A(t,i,e,n){const l=Math.sin(t*127.1+i*311.7+e*74.7+n*19.3)*43758.5453;return l-Math.floor(l)}const U=new Map;function mt(t,i){const e=`${t},${i}`;let n=U.get(e);if(n)return n;n=[];for(let l=0;l<_;l+=1)n.push({seedA:A(t,i,l,1),seedB:A(t,i,l,2),seedC:A(t,i,l,3),phaseOffset:A(t,i,l,4),twinkleSeed:A(t,i,l,5)});return U.set(e,n),n}function k(t){if(t.winningCells.length===0||t.phase!=="winFlash"&&t.phase!=="preSpin")return;const i=Math.max(0,Math.min(1,t.winEffectsEnvelope)),e=Math.max(0,t.now-t.winFlashStartedAt),n=e%y/y,l=1+Math.sin(n*Math.PI*2)*K;for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Mt(t.ctx,r,u,t.cellW,t.cellH,s.col,s.row,t.winningCellBorderRgba,e,i);const o=t.getCell(s.col,s.row),h=1+(l-1)*i,a=t.cellW*h,c=t.cellH*h,d=(t.cellW-a)*.5,p=(t.cellH-c)*.5;t.ctx.save(),t.drawSpriteCell(o,r+d,u+p,a,c),t.ctx.restore()}if(!(t.particlesPerCell<=0)){t.ctx.save(),t.ctx.beginPath(),t.ctx.rect(t.boardX,t.boardY,t.cellW*g,t.cellH*f),t.ctx.clip();for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Rt({ctx:t.ctx,cell:s,x:r,y:u,elapsed:e,envelope:i,cellW:t.cellW,cellH:t.cellH,particlesPerCell:t.particlesPerCell,particleColorMode:t.particleColorMode,particleColorRgb:t.particleColorRgb})}t.ctx.restore()}}function Mt(t,i,e,n,l,s,r,u,o,h){const a=Math.max(1,Math.floor(Math.min(n,l)*.04)),c=Math.max(1,Math.floor(Math.min(n,l)*.025)),[,,,d]=u,p=(s+.5)/g,S=(r+.5)/f,E=(o*.15+p*80+S*45)%360,C=Math.min(n,l),O=Math.max(4,Math.floor(C*.04)),L=Math.max(3,Math.floor(C*.03));t.save(),t.lineWidth=c,t.strokeStyle=`hsla(${E}, 90%, 70%, ${d*h})`,t.setLineDash([O,L]),t.lineDashOffset=-o*.03;const M=c*.5;t.strokeRect(i+a+M,e+a+M,n-a*2-c,l-a*2-c),t.setLineDash([]),t.restore()}const B=24;function Rt(t){const i=t.x+t.cellW*.5,e=t.y+t.cellH*.5,n=Math.min(t.cellW,t.cellH)*.72,l=Math.min(t.cellW,t.cellH)*.028,s=.96*t.envelope,[r,u,o]=t.particleColorRgb,h=t.particleColorMode==="solid";h&&(t.ctx.fillStyle=`rgb(${r},${u},${o})`);const a=mt(t.cell.col,t.cell.row);for(let c=0;c<t.particlesPerCell;c+=1){const d=a[c],p=d.phaseOffset*x,S=t.elapsed-p;if(S<0)continue;const E=S%x/x,C=d.seedA*Math.PI*2,O=n*E*(.35+d.seedB*.65),L=i+Math.cos(C)*O,M=e+Math.sin(C)*O,z=.7+.9*Math.max(0,Math.sin((t.elapsed*.012+d.twinkleSeed*2)*Math.PI*2)),X=Math.max(1,l*(.55+d.seedC*.6)*(1-E*.5)),P=m((.9+z*.2)*s,.9,1);if(!(P<=0)){if(h)t.ctx.globalAlpha=P;else{const $=(d.seedA*360+t.elapsed*.24+t.cell.col*38+t.cell.row*22)%360,Q=Math.floor($/(360/B))*(360/B);t.ctx.fillStyle=`hsla(${Q},98%,64%,${P})`}t.ctx.beginPath(),t.ctx.arc(L,M,X,0,Math.PI*2),t.ctx.fill()}}t.ctx.globalAlpha=1}class w{canvas;container;button;ctx;spinQueueController;spriteUrl;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;winningCellBorderRgba;quality;spriteImage=null;rafLoop=new rt;runtime=St();dpr=1;width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutgoingOffsets=Y();scriptedIncomingOffsets=Y();winningCells=[];grid;lastRafTime=0;particlesPerCell=_;initialHighlightRequestedAt=0;static SWAY_ENVELOPE_TAU_MS=220;static PRE_SPIN_MS=150;static WIN_EFFECTS_ENVELOPE_TAU_MS=120;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??V),this.highlightInitialWinningCells=i.highlightInitialWinningCells!==!1,this.spinQueueController=new pt(i.queuedSpinStates),this.particleColorRgb=ct(i.particleColorRgb),this.particleColorMode=ut(i.particleColorMode),this.winningCellBorderRgba=dt(i.winningCellBorderRgba),this.quality=at(i.quality);const e=this.canvas.getContext("2d");if(!e)throw new Error("2D canvas context is not available");this.ctx=e,this.grid=i.initialSegments?ft(i.initialSegments,this.spriteElementsCount):G(this.spriteElementsCount)}async init(){this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),this.applyInitialHighlightIfNeeded(),this.prewarmWinEffects(),requestAnimationFrame(i=>{this.render(i),this.startLoop()})}destroy(){this.unbindEvents(),this.rafLoop.stop(),Et(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();this.runtime.isSpinning=!0,this.runtime.phase="preSpin",this.runtime.preSpinStartedAt=performance.now(),this.runtime.activeSpinState=i,this.runtime.shouldHighlightCurrentSpin=e,this.runtime.hasStartedFirstSpin=!0,this.button&&(this.button.disabled=!0)}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?v(i,this.spriteElementsCount):G(this.spriteElementsCount)}update(i){if(this.runtime.isSpinning){if(this.runtime.phase==="preSpin"){if(i-this.runtime.preSpinStartedAt<w.PRE_SPIN_MS)return;Ct(this.runtime,{activeSpinState:this.runtime.activeSpinState,shouldHighlightCurrentSpin:this.runtime.shouldHighlightCurrentSpin,startedAt:i}),this.runtime.preSpinStartedAt=0;const e=this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(s=>F(s)):this.runtime.activeSpinState?.finaleSequence??[];this.scriptedCascadeQueue=e.map(s=>s.map(r=>[...r])),this.clearWinningCells();const n=this.runtime.activeSpinState?.stopRows?F(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,l=this.getNextGrid(n);this.startOutroTransition(l,i)}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}=ht({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(),W(this.scriptedOutgoingOffsets,0),W(this.scriptedIncomingOffsets,0),!this.tryStartScriptedCascade(i)){if(!this.runtime.shouldHighlightCurrentSpin){this.finishSpinWithUi();return}this.winningCells=N(this.grid),D(this.runtime,i)}}}tryStartScriptedCascade(i){if(this.scriptedCascadeQueue.length===0)return!1;const e=this.scriptedCascadeQueue.shift();return e?(this.startOutroTransition(v(e,this.spriteElementsCount),i),!0):!1}startOutroTransition(i,e){this.scriptedOutgoingGrid=this.grid.map(n=>[...n]),this.scriptedPendingGrid=i.map(n=>[...n]),W(this.scriptedIncomingOffsets,Number.NaN),W(this.scriptedOutgoingOffsets,0),this.clearWinningCells(),this.runtime.phase="outro",this.scriptedOutroStartedAt=e}finishSpinWithUi(){const i=this.runtime.activeSpinState?.callback;wt(this.runtime,this.spinQueueController.hasPending(),performance.now()),this.button&&(this.button.disabled=this.runtime.queueFinished),i?.()}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.winningCells=N(this.grid),this.initialHighlightRequestedAt=performance.now())}static ZERO_SWAY=[0,0,0];render(i){this.initialHighlightRequestedAt>0&&i-this.initialHighlightRequestedAt>=nt&&(D(this.runtime,this.initialHighlightRequestedAt),this.runtime.winEffectsEnvelope=1,this.initialHighlightRequestedAt=0);const e=this.runtime.phase==="outro"||this.runtime.phase==="preSpin"?0:1,n=this.runtime.phase==="preSpin"?0:this.runtime.phase==="winFlash"?1:0;if(this.lastRafTime>0){const u=Math.min(i-this.lastRafTime,50),o=1-Math.exp(-u/w.SWAY_ENVELOPE_TAU_MS);this.runtime.swayEnvelope+=(e-this.runtime.swayEnvelope)*o;const h=1-Math.exp(-u/w.WIN_EFFECTS_ENVELOPE_TAU_MS);this.runtime.winEffectsEnvelope+=(n-this.runtime.winEffectsEnvelope)*h}this.lastRafTime=i,this.ctx.clearRect(0,0,this.width,this.height),this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawLayer(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsets,w.ZERO_SWAY,i),this.drawLayer(this.scriptedPendingGrid,this.scriptedIncomingOffsets,w.ZERO_SWAY,i)):this.drawBaseGrid(i);const l=this.initialHighlightRequestedAt>0?"winFlash":this.runtime.phase,s=this.initialHighlightRequestedAt>0?this.initialHighlightRequestedAt:this.runtime.winFlashStartedAt,r=this.initialHighlightRequestedAt>0?1:this.runtime.winEffectsEnvelope;k({ctx:this.ctx,now:i,phase:l,winFlashStartedAt:s,winEffectsEnvelope:r,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:this.runtime.phase==="winFlash"&&this.runtime.shouldHighlightCurrentSpin&&this.runtime.hasStartedFirstSpin?this.particlesPerCell:0,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:this.drawSpriteCell})}drawBaseGrid(i){this.drawLayer(this.grid,null,w.ZERO_SWAY,i)}drawLayer(i,e,n,l){const s=this.runtime.phase==="winFlash"||this.runtime.phase==="preSpin"||this.initialHighlightRequestedAt>0&&this.winningCells.length>0;At({ctx:this.ctx,phase:this.runtime.phase,now:l,swayEnvelope:this.runtime.swayEnvelope,grid:i,offsets:e,columnSway:n,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,width:this.width,height:this.height,skipWinningCells:s,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);drawSpriteCellToCtx(i,e,n,l,s,r){const u=this.spriteImage;if(!u)return;const o=I(e,this.spriteElementsCount),h=u.height/this.spriteElementsCount,a=Math.floor(o*h),c=Math.floor(h);i.drawImage(u,0,a,u.width,c,n,l,s,r)}drawSpriteCell=(i,e,n,l,s)=>{this.drawSpriteCellToCtx(this.ctx,i,e,n,l,s)};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/g,this.height/f));this.cellW=n,this.cellH=n,this.boardX=Math.floor((this.width-this.cellW*g)/2),this.boardY=Math.floor((this.height-this.cellH*f)/2),this.quality==="high"?this.particlesPerCell=_:this.quality==="balanced"?this.particlesPerCell=H:this.quality==="low"?this.particlesPerCell=T:this.particlesPerCell=this.dpr>=2?H:_,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}}prewarmWinEffects(){if(this.winningCells.length===0)return;const i=document.createElement("canvas");i.width=this.width,i.height=this.height;const e=i.getContext("2d");if(!e)return;const n=this.runtime.phase,l=this.runtime.winFlashStartedAt;this.runtime.phase="winFlash",this.runtime.winFlashStartedAt=performance.now()-300;const s=performance.now(),r=Math.min(this.particlesPerCell,T),u=(o,h,a,c,d)=>{this.drawSpriteCellToCtx(e,o,h,a,c,d)};for(let o=0;o<2;o+=1)k({ctx:e,now:s+o*16,phase:"winFlash",winFlashStartedAt:this.runtime.winFlashStartedAt,winEffectsEnvelope:1,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:r,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:u});this.runtime.phase=n,this.runtime.winFlashStartedAt=l}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(i=>(this.update(i),this.render(i),!0))}}exports.CascadingReel=w;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const V=6,g=3,f=3,Z=34,j=.05,K=5e3,v=1800,J=.1,F=720,_=34,y=20,T=12,tt=[255,235,110],it=[255,255,255,.78],et=190,M=650,nt=220,st=200;function m(t,i,e){return t<i?i:t>e?e:t}function q(t){return 1-(1-t)**3}function lt(t){return Math.floor(Math.random()*t)}function I(t,i){return(t%i+i)%i}function O(t){return m(Math.round(t),0,255)}function rt(t){return m(t,0,1)}function N(t){const i=[];for(let e=0;e<g;e+=1){const n=[];for(let l=0;l<f;l+=1)n.push(lt(t));i.push(n)}return i}function G(t){const i=new Map;for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1){const u=t[s][r];i.set(u,(i.get(u)??0)+1)}let e=t[0][0],n=-1;for(const[s,r]of i.entries())r>n&&(n=r,e=s);const l=[];for(let s=0;s<g;s+=1)for(let r=0;r<f;r+=1)t[s][r]===e&&l.push({col:s,row:r});return l}function Y(){return Array.from({length:g},()=>Array.from({length:f},()=>0))}function R(t,i){for(let e=0;e<g;e+=1)for(let n=0;n<f;n+=1)t[e][n]=i}class ot{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 ht(t,i,e){const n=[0,0,0];let l=0;for(let s=f-1;s>=0;s-=1){if(t[s]===0){n[s]=0;continue}n[s]=l;const r=Math.floor(i*j);l+=r+e}return n}function ct(t){let i=!0,e=!0;const l=t.height-t.boardY+t.cellH+2,s=[l,l,l],r=[-t.cellH,-t.cellH*2,-t.cellH*3],u=ht(s,M,Z),o=M-nt;for(let h=0;h<t.scriptedOutgoingOffsets.length;h+=1){const a=t.now-(t.scriptedOutroStartedAt+h*et);for(let c=0;c<f;c+=1){const d=a-u[c];if(d<=0){t.scriptedOutgoingOffsets[h][c]=0,t.scriptedIncomingOffsets[h][c]=Number.NaN,i=!1,e=!1;continue}const p=m(d/M,0,1),S=q(p);t.scriptedOutgoingOffsets[h][c]=l*S,p<1&&(i=!1);const E=d-o;if(E<=0){t.scriptedIncomingOffsets[h][c]=Number.NaN,e=!1;continue}const C=m(E/M,0,1),b=q(C);t.scriptedIncomingOffsets[h][c]=r[c]*(1-b),C<1&&(e=!1)}}return{allOutgoingDone:i,allIncomingDone:e}}function ut(t){return t?[O(t[0]),O(t[1]),O(t[2])]:tt}function at(t){return t==="rainbow"?"rainbow":"solid"}function dt(t){return t==="high"||t==="balanced"||t==="low"||t==="auto"?t:"auto"}function ft(t){return t?[O(t[0]),O(t[1]),O(t[2]),rt(t[3])]:it}function x(t){if(t.length!==f)throw new Error(`rows must contain ${f} rows`);for(let i=0;i<f;i+=1)if(!Array.isArray(t[i])||t[i].length!==g)throw new Error(`rows[${i}] must contain ${g} 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 H(t,i){if(t.length!==g)throw new Error(`stopGrid must contain ${g} columns`);const e=[];for(let n=0;n<g;n+=1){const l=t[n];if(!Array.isArray(l)||l.length!==f)throw new Error(`stopGrid[${n}] must contain ${f} rows`);e[n]=[I(l[0],i),I(l[1],i),I(l[2],i)]}return e}function gt(t,i){return H(x(t),i)}function pt(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])),highlightWin:t.highlightWin,callback:t.callback}}class St{queue;constructor(i){this.queue=(i??[]).map(e=>pt(e))}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?null:this.queue.shift()??null}}function Ct(){return{isSpinning:!1,hasStartedFirstSpin:!1,queueFinished:!1,shouldHighlightCurrentSpin:!1,activeSpinState:null,phase:"idle",winFlashStartedAt:0,outroStartedAt:0,idleStartedAt:0,preSpinStartedAt:0,swayEnvelope:1,winEffectsEnvelope:1}}function wt(t,i){t.hasStartedFirstSpin=!0,t.isSpinning=!0,t.phase="outro",t.outroStartedAt=i.startedAt,t.activeSpinState=i.activeSpinState,t.shouldHighlightCurrentSpin=i.shouldHighlightCurrentSpin}function Et(t,i,e){t.phase="idle",t.idleStartedAt=e,t.isSpinning=!1,t.shouldHighlightCurrentSpin=!1,t.queueFinished=!i,t.activeSpinState=null}function U(t,i){t.winFlashStartedAt=i,t.phase="winFlash"}function bt(t){t.isSpinning=!1,t.queueFinished=!0}const Ot=3,At=.002;function mt(t){const i=t.columnSway??[0,0,0],e=Ot*(Math.PI/180),n=Math.max(0,Math.min(1,t.swayEnvelope)),l=t.skipWinningCells??!1;for(let s=0;s<g;s+=1){const r=t.boardX+s*t.cellW,u=i[s]??0;for(let o=0;o<f;o+=1){if((l||t.phase==="winFlash"||t.phase==="preSpin")&&t.isWinningCell(s,o))continue;const h=t.offsets?t.offsets[s][o]:0;if(!Number.isFinite(h))continue;const a=t.boardY+o*t.cellH+h+u;if(a>t.height||a+t.cellH<0)continue;const d=Math.sin(t.now*At+s*1.1+o*1.3)*e*n,p=r+t.cellW*.5,S=a+t.cellH*.5;t.ctx.save(),t.ctx.translate(p,S),t.ctx.rotate(d),t.ctx.translate(-t.cellW*.5,-t.cellH*.5),t.drawSpriteCell(t.grid[s][o],0,0,t.cellW,t.cellH),t.ctx.restore()}}}function A(t,i,e,n){const l=Math.sin(t*127.1+i*311.7+e*74.7+n*19.3)*43758.5453;return l-Math.floor(l)}const D=new Map;function Wt(t,i){const e=`${t},${i}`;let n=D.get(e);if(n)return n;n=[];for(let l=0;l<_;l+=1)n.push({seedA:A(t,i,l,1),seedB:A(t,i,l,2),seedC:A(t,i,l,3),phaseOffset:A(t,i,l,4),twinkleSeed:A(t,i,l,5)});return D.set(e,n),n}function k(t){if(t.winningCells.length===0||t.phase!=="winFlash"&&t.phase!=="preSpin")return;const i=Math.max(0,Math.min(1,t.winEffectsEnvelope)),e=Math.max(0,t.now-t.winFlashStartedAt),n=e%v/v,l=1+Math.sin(n*Math.PI*2)*J;for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Mt(t.ctx,r,u,t.cellW,t.cellH,s.col,s.row,t.winningCellBorderRgba,e,i);const o=t.getCell(s.col,s.row),h=1+(l-1)*i,a=t.cellW*h,c=t.cellH*h,d=(t.cellW-a)*.5,p=(t.cellH-c)*.5;t.ctx.save(),t.drawSpriteCell(o,r+d,u+p,a,c),t.ctx.restore()}if(!(t.particlesPerCell<=0)){t.ctx.save(),t.ctx.beginPath(),t.ctx.rect(t.boardX,t.boardY,t.cellW*g,t.cellH*f),t.ctx.clip();for(const s of t.winningCells){const r=t.boardX+s.col*t.cellW,u=t.boardY+s.row*t.cellH;Rt({ctx:t.ctx,cell:s,x:r,y:u,elapsed:e,envelope:i,cellW:t.cellW,cellH:t.cellH,particlesPerCell:t.particlesPerCell,particleColorMode:t.particleColorMode,particleColorRgb:t.particleColorRgb})}t.ctx.restore()}}function Mt(t,i,e,n,l,s,r,u,o,h){const a=Math.max(1,Math.floor(Math.min(n,l)*.04)),c=Math.max(1,Math.floor(Math.min(n,l)*.03)),[,,,d]=u,p=(s+.5)/g,S=(r+.5)/f,E=(o*.2+p*80+S*40)%360,C=Math.min(n,l),b=Math.max(4,Math.floor(C*.04)),L=Math.max(3,Math.floor(C*.03));t.save(),t.lineWidth=c,t.strokeStyle=`hsla(${E}, 90%, 70%, ${d*h})`,t.setLineDash([b,L]),t.lineDashOffset=-o*.03;const W=c*.5;t.strokeRect(i+a+W,e+a+W,n-a*2-c,l-a*2-c),t.setLineDash([]),t.restore()}const B=24;function Rt(t){const i=t.x+t.cellW*.5,e=t.y+t.cellH*.5,n=Math.min(t.cellW,t.cellH)*.72,l=Math.min(t.cellW,t.cellH)*.028,s=.96*t.envelope,[r,u,o]=t.particleColorRgb,h=t.particleColorMode==="solid";h&&(t.ctx.fillStyle=`rgb(${r},${u},${o})`);const a=Wt(t.cell.col,t.cell.row);for(let c=0;c<t.particlesPerCell;c+=1){const d=a[c],p=d.phaseOffset*F,S=t.elapsed-p;if(S<0)continue;const E=S%F/F,C=d.seedA*Math.PI*2,b=n*E*(.35+d.seedB*.65),L=i+Math.cos(C)*b,W=e+Math.sin(C)*b,z=.7+.9*Math.max(0,Math.sin((t.elapsed*.012+d.twinkleSeed*2)*Math.PI*2)),X=Math.max(1,l*(.55+d.seedC*.6)*(1-E*.5)),P=m((.9+z*.2)*s,.9,1);if(!(P<=0)){if(h)t.ctx.globalAlpha=P;else{const $=(d.seedA*360+t.elapsed*.24+t.cell.col*38+t.cell.row*22)%360,Q=Math.floor($/(360/B))*(360/B);t.ctx.fillStyle=`hsla(${Q},98%,64%,${P})`}t.ctx.beginPath(),t.ctx.arc(L,W,X,0,Math.PI*2),t.ctx.fill()}}t.ctx.globalAlpha=1}class w{canvas;container;button;ctx;spinQueueController;spriteUrl;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;winningCellBorderRgba;quality;spriteImage=null;rafLoop=new ot;runtime=Ct();dpr=1;width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutgoingOffsets=Y();scriptedIncomingOffsets=Y();winningCells=[];grid;lastRafTime=0;particlesPerCell=_;initialHighlightRequestedAt=0;static SWAY_ENVELOPE_TAU_MS=220;static PRE_SPIN_MS=150;static WIN_EFFECTS_ENVELOPE_TAU_MS=120;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??V),this.highlightInitialWinningCells=i.highlightInitialWinningCells!==!1,this.spinQueueController=new St(i.queuedSpinStates),this.particleColorRgb=ut(i.particleColorRgb),this.particleColorMode=at(i.particleColorMode),this.winningCellBorderRgba=ft(i.winningCellBorderRgba),this.quality=dt(i.quality);const e=this.canvas.getContext("2d");if(!e)throw new Error("2D canvas context is not available");this.ctx=e,this.grid=i.initialSegments?gt(i.initialSegments,this.spriteElementsCount):N(this.spriteElementsCount)}async init(){this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),this.applyInitialHighlightIfNeeded(),this.prewarmWinEffects(),requestAnimationFrame(i=>{this.render(i),this.startLoop()})}destroy(){this.unbindEvents(),this.rafLoop.stop(),bt(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=i?.highlightWin===!0;this.runtime.isSpinning=!0,this.runtime.phase="preSpin",this.runtime.preSpinStartedAt=performance.now(),this.runtime.activeSpinState=i,this.runtime.shouldHighlightCurrentSpin=e,this.runtime.hasStartedFirstSpin=!0,this.button&&(this.button.disabled=!0)}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=()=>{if(this.runtime.phase==="winFlash"&&(this.runtime.shouldHighlightCurrentSpin||!this.runtime.hasStartedFirstSpin)){this.finishSpinWithUi(),this.spinQueueController.hasPending()&&this.spin();return}this.runtime.isSpinning||this.spin()};getNextGrid(i){return i?H(i,this.spriteElementsCount):N(this.spriteElementsCount)}update(i){if(this.runtime.isSpinning){if(this.runtime.phase==="preSpin"){if(i-this.runtime.preSpinStartedAt<w.PRE_SPIN_MS)return;wt(this.runtime,{activeSpinState:this.runtime.activeSpinState,shouldHighlightCurrentSpin:this.runtime.shouldHighlightCurrentSpin,startedAt:i}),this.runtime.preSpinStartedAt=0;const e=this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(s=>x(s)):this.runtime.activeSpinState?.finaleSequence??[];this.scriptedCascadeQueue=e.map(s=>s.map(r=>[...r])),this.clearWinningCells();const n=this.runtime.activeSpinState?.stopRows?x(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,l=this.getNextGrid(n);this.startOutroTransition(l,i)}if(this.runtime.phase==="outro"){this.updateScriptedOutro(i);return}if(this.runtime.phase==="winFlash"){!this.runtime.shouldHighlightCurrentSpin&&i-this.runtime.winFlashStartedAt>=K&&this.finishSpinWithUi();return}}}updateScriptedOutro(i){if(!this.scriptedOutgoingGrid||!this.scriptedPendingGrid){this.finishSpinWithUi();return}const{allOutgoingDone:e,allIncomingDone:n}=ct({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=G(this.grid),U(this.runtime,i),this.button&&this.runtime.shouldHighlightCurrentSpin&&this.spinQueueController.hasPending()&&(this.button.disabled=!1)}}}tryStartScriptedCascade(i){if(this.scriptedCascadeQueue.length===0)return!1;const e=this.scriptedCascadeQueue.shift();return e?(this.startOutroTransition(H(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;Et(this.runtime,this.spinQueueController.hasPending(),performance.now()),this.button&&(this.button.disabled=this.runtime.queueFinished),i?.()}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.winningCells=G(this.grid),this.initialHighlightRequestedAt=performance.now())}static ZERO_SWAY=[0,0,0];render(i){this.initialHighlightRequestedAt>0&&i-this.initialHighlightRequestedAt>=st&&(U(this.runtime,this.initialHighlightRequestedAt),this.runtime.winEffectsEnvelope=1,this.initialHighlightRequestedAt=0,this.button&&(this.button.disabled=!1));const e=this.runtime.phase==="outro"||this.runtime.phase==="preSpin"?0:1,n=this.runtime.phase==="preSpin"?0:this.runtime.phase==="winFlash"?1:0;if(this.lastRafTime>0){const u=Math.min(i-this.lastRafTime,50),o=1-Math.exp(-u/w.SWAY_ENVELOPE_TAU_MS);this.runtime.swayEnvelope+=(e-this.runtime.swayEnvelope)*o;const h=1-Math.exp(-u/w.WIN_EFFECTS_ENVELOPE_TAU_MS);this.runtime.winEffectsEnvelope+=(n-this.runtime.winEffectsEnvelope)*h}this.lastRafTime=i,this.ctx.clearRect(0,0,this.width,this.height),this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawLayer(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsets,w.ZERO_SWAY,i),this.drawLayer(this.scriptedPendingGrid,this.scriptedIncomingOffsets,w.ZERO_SWAY,i)):this.drawBaseGrid(i);const l=this.initialHighlightRequestedAt>0?"winFlash":this.runtime.phase,s=this.initialHighlightRequestedAt>0?this.initialHighlightRequestedAt:this.runtime.winFlashStartedAt,r=this.initialHighlightRequestedAt>0?1:this.runtime.winEffectsEnvelope;k({ctx:this.ctx,now:i,phase:l,winFlashStartedAt:s,winEffectsEnvelope:r,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:this.runtime.phase==="winFlash"&&this.runtime.shouldHighlightCurrentSpin&&this.runtime.hasStartedFirstSpin?this.particlesPerCell:0,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:this.drawSpriteCell})}drawBaseGrid(i){this.drawLayer(this.grid,null,w.ZERO_SWAY,i)}drawLayer(i,e,n,l){const s=this.runtime.phase==="winFlash"||this.runtime.phase==="preSpin"||this.initialHighlightRequestedAt>0&&this.winningCells.length>0;mt({ctx:this.ctx,phase:this.runtime.phase,now:l,swayEnvelope:this.runtime.swayEnvelope,grid:i,offsets:e,columnSway:n,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,width:this.width,height:this.height,skipWinningCells:s,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);drawSpriteCellToCtx(i,e,n,l,s,r){const u=this.spriteImage;if(!u)return;const o=I(e,this.spriteElementsCount),h=u.height/this.spriteElementsCount,a=Math.floor(o*h),c=Math.floor(h);i.drawImage(u,0,a,u.width,c,n,l,s,r)}drawSpriteCell=(i,e,n,l,s)=>{this.drawSpriteCellToCtx(this.ctx,i,e,n,l,s)};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/g,this.height/f));this.cellW=n,this.cellH=n,this.boardX=Math.floor((this.width-this.cellW*g)/2),this.boardY=Math.floor((this.height-this.cellH*f)/2),this.quality==="high"?this.particlesPerCell=_:this.quality==="balanced"?this.particlesPerCell=y:this.quality==="low"?this.particlesPerCell=T:this.particlesPerCell=this.dpr>=2?y:_,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}}prewarmWinEffects(){if(this.winningCells.length===0)return;const i=document.createElement("canvas");i.width=this.width,i.height=this.height;const e=i.getContext("2d");if(!e)return;const n=this.runtime.phase,l=this.runtime.winFlashStartedAt;this.runtime.phase="winFlash",this.runtime.winFlashStartedAt=performance.now()-300;const s=performance.now(),r=Math.min(this.particlesPerCell,T),u=(o,h,a,c,d)=>{this.drawSpriteCellToCtx(e,o,h,a,c,d)};for(let o=0;o<2;o+=1)k({ctx:e,now:s+o*16,phase:"winFlash",winFlashStartedAt:this.runtime.winFlashStartedAt,winEffectsEnvelope:1,winningCells:this.winningCells,boardX:this.boardX,boardY:this.boardY,cellW:this.cellW,cellH:this.cellH,particlesPerCell:r,particleColorMode:this.particleColorMode,particleColorRgb:this.particleColorRgb,winningCellBorderRgba:this.winningCellBorderRgba,getCell:this.getCell,drawSpriteCell:u});this.runtime.phase=n,this.runtime.winFlashStartedAt=l}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(i=>(this.update(i),this.render(i),!0))}}exports.CascadingReel=w;
2
2
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +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.1;\n/** Duration for one particle to fly from center to edge (continuous spray). */\nexport const PARTICLE_FLY_DURATION_MS = 720;\nexport const FLOW_WIN_PARTICLES_PER_CELL_HIGH = 34;\nexport const FLOW_WIN_PARTICLES_PER_CELL_BALANCED = 20;\nexport const FLOW_WIN_PARTICLES_PER_CELL_LOW = 12;\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/** Delay before starting initial win-effect so canvas/JIT can warm up. */\nexport const INITIAL_WIN_FLASH_DELAY_MS = 200;\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 { QualityPreset, 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 normalizeQuality(preset?: QualityPreset): QualityPreset {\n if (preset === 'high' || preset === 'balanced' || preset === 'low' || preset === 'auto') {\n return preset;\n }\n return 'auto';\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 outroStartedAt: number;\n idleStartedAt: number;\n preSpinStartedAt: number;\n swayEnvelope: number;\n winEffectsEnvelope: 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 outroStartedAt: 0,\n idleStartedAt: 0,\n preSpinStartedAt: 0,\n swayEnvelope: 1,\n winEffectsEnvelope: 1,\n };\n}\n\nexport function beginSpin(\n state: RuntimeState,\n params: {\n activeSpinState: SpinState | null;\n shouldHighlightCurrentSpin: boolean;\n startedAt: number;\n },\n): void {\n state.hasStartedFirstSpin = true;\n state.isSpinning = true;\n state.phase = 'outro';\n state.outroStartedAt = params.startedAt;\n state.activeSpinState = params.activeSpinState;\n state.shouldHighlightCurrentSpin = params.shouldHighlightCurrentSpin;\n}\n\nexport function finishSpin(state: RuntimeState, hasPendingInQueue: boolean, now: number): void {\n state.phase = 'idle';\n state.idleStartedAt = now;\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_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PULSE_AMPLITUDE,\n FLOW_WIN_PULSE_PERIOD_MS,\n GRID_COLS,\n GRID_ROWS,\n PARTICLE_FLY_DURATION_MS,\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\nconst SWAY_AMPLITUDE_DEG = 3;\nconst SWAY_OMEGA = 0.002;\n\nexport function drawGridLayer(params: {\n ctx: CanvasRenderingContext2D;\n phase: SpinPhase;\n now: number;\n swayEnvelope: number;\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 /** When true, winning cells are not drawn here (they are drawn by win effects). */\n skipWinningCells?: boolean;\n isWinningCell: (col: number, row: number) => boolean;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n const sway = params.columnSway ?? [0, 0, 0];\n const swayAmplitudeRad = SWAY_AMPLITUDE_DEG * (Math.PI / 180);\n const envelope = Math.max(0, Math.min(1, params.swayEnvelope));\n const skipWinning = params.skipWinningCells ?? false;\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 (\n (skipWinning || params.phase === 'winFlash' || params.phase === 'preSpin') &&\n params.isWinningCell(col, row)\n )\n 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 const rawAngle = Math.sin(params.now * SWAY_OMEGA + col * 1.1 + row * 1.3) * swayAmplitudeRad;\n const angle = rawAngle * envelope;\n\n const centerX = x + params.cellW * 0.5;\n const centerY = y + params.cellH * 0.5;\n\n params.ctx.save();\n params.ctx.translate(centerX, centerY);\n params.ctx.rotate(angle);\n params.ctx.translate(-params.cellW * 0.5, -params.cellH * 0.5);\n params.drawSpriteCell(params.grid[col][row], 0, 0, params.cellW, params.cellH);\n params.ctx.restore();\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\ntype ParticleSeeds = {\n seedA: number;\n seedB: number;\n seedC: number;\n phaseOffset: number;\n twinkleSeed: number;\n};\n\nconst particleSeedsCache = new Map<string, ParticleSeeds[]>();\n\nfunction getParticleSeeds(col: number, row: number): ParticleSeeds[] {\n const key = `${col},${row}`;\n let seeds = particleSeedsCache.get(key);\n if (seeds) return seeds;\n\n seeds = [];\n for (let i = 0; i < FLOW_WIN_PARTICLES_PER_CELL_HIGH; i += 1) {\n seeds.push({\n seedA: hash01(col, row, i, 1),\n seedB: hash01(col, row, i, 2),\n seedC: hash01(col, row, i, 3),\n phaseOffset: hash01(col, row, i, 4),\n twinkleSeed: hash01(col, row, i, 5),\n });\n }\n particleSeedsCache.set(key, seeds);\n return seeds;\n}\n\nexport function drawWinningEffects(params: {\n ctx: CanvasRenderingContext2D;\n now: number;\n phase: SpinPhase;\n winFlashStartedAt: number;\n winEffectsEnvelope: number;\n winningCells: CellPosition[];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particlesPerCell: 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' && params.phase !== 'preSpin') return;\n\n const envelope = Math.max(0, Math.min(1, params.winEffectsEnvelope));\n const elapsed = Math.max(0, params.now - params.winFlashStartedAt);\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\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 cell.col,\n cell.row,\n params.winningCellBorderRgba,\n elapsed,\n envelope,\n );\n\n const symbol = params.getCell(cell.col, cell.row);\n const scale = 1 + (pulse - 1) * envelope;\n const scaledW = params.cellW * scale;\n const scaledH = params.cellH * scale;\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\n if (params.particlesPerCell <= 0) return;\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 (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawCellParticleBurst({\n ctx: params.ctx,\n cell,\n x,\n y,\n elapsed,\n envelope,\n cellW: params.cellW,\n cellH: params.cellH,\n particlesPerCell: params.particlesPerCell,\n particleColorMode: params.particleColorMode,\n particleColorRgb: params.particleColorRgb,\n });\n }\n\n params.ctx.restore();\n}\n\nfunction drawWinningCellBorder(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n cellW: number,\n cellH: number,\n cellCol: number,\n cellRow: number,\n color: [number, number, number, number],\n elapsed: number,\n envelope: 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\n const localX = (cellCol + 0.5) / GRID_COLS;\n const localY = (cellRow + 0.5) / GRID_ROWS;\n const hue = (elapsed * 0.15 + localX * 80 + localY * 45) % 360;\n\n const minSide = Math.min(cellW, cellH);\n const dashLength = Math.max(4, Math.floor(minSide * 0.04));\n const gapLength = Math.max(3, Math.floor(minSide * 0.03));\n\n ctx.save();\n ctx.lineWidth = lineWidth;\n ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${a * envelope})`;\n ctx.setLineDash([dashLength, gapLength]);\n ctx.lineDashOffset = -elapsed * 0.03;\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.setLineDash([]);\n ctx.restore();\n}\n\nconst RAINBOW_HUE_BUCKETS = 24;\n\nfunction drawCellParticleBurst(params: {\n ctx: CanvasRenderingContext2D;\n cell: CellPosition;\n x: number;\n y: number;\n elapsed: number;\n envelope: number;\n cellW: number;\n cellH: number;\n particlesPerCell: 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.96 * params.envelope;\n\n const [r, g, b] = params.particleColorRgb;\n const isSolid = params.particleColorMode === 'solid';\n if (isSolid) {\n params.ctx.fillStyle = `rgb(${r},${g},${b})`;\n }\n\n const seedsList = getParticleSeeds(params.cell.col, params.cell.row);\n\n for (let i = 0; i < params.particlesPerCell; i += 1) {\n const s = seedsList[i];\n const startTime = s.phaseOffset * PARTICLE_FLY_DURATION_MS;\n const age = params.elapsed - startTime;\n if (age < 0) continue;\n const particleT = (age % PARTICLE_FLY_DURATION_MS) / PARTICLE_FLY_DURATION_MS;\n\n const direction = s.seedA * Math.PI * 2;\n const distance = maxDistance * particleT * (0.35 + s.seedB * 0.65);\n const px = centerX + Math.cos(direction) * distance;\n const py = centerY + Math.sin(direction) * distance;\n const twinkle =\n 0.7 + 0.9 * Math.max(0, Math.sin((params.elapsed * 0.012 + s.twinkleSeed * 2) * Math.PI * 2));\n const radius = Math.max(1, baseRadius * (0.55 + s.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 (isSolid) {\n params.ctx.globalAlpha = alpha;\n } else {\n const hueRaw =\n (s.seedA * 360 + params.elapsed * 0.24 + params.cell.col * 38 + params.cell.row * 22) % 360;\n const hue = Math.floor(hueRaw / (360 / RAINBOW_HUE_BUCKETS)) * (360 / RAINBOW_HUE_BUCKETS);\n params.ctx.fillStyle = `hsla(${hue},98%,64%,${alpha})`;\n }\n\n params.ctx.beginPath();\n params.ctx.arc(px, py, radius, 0, Math.PI * 2);\n params.ctx.fill();\n }\n\n params.ctx.globalAlpha = 1;\n}\n","import {\n DEFAULT_SPRITE_ELEMENTS_COUNT,\n FLOW_WIN_PARTICLES_PER_CELL_BALANCED,\n FLOW_WIN_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PARTICLES_PER_CELL_LOW,\n GRID_COLS,\n GRID_ROWS,\n INITIAL_WIN_FLASH_DELAY_MS,\n} 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 normalizeQuality,\n normalizeStopGrid,\n normalizeWinningCellBorderColor,\n rowsToStopGrid,\n} from './normalize';\nimport { drawGridLayer, drawWinningEffects } from './render/canvasRenderer';\nimport type { CascadingReelConfig, CellPosition, QualityPreset, 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 private readonly quality: QualityPreset;\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 private lastRafTime = 0;\n private particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n private initialHighlightRequestedAt = 0;\n private static readonly SWAY_ENVELOPE_TAU_MS = 220;\n private static readonly PRE_SPIN_MS = 150;\n private static readonly WIN_EFFECTS_ENVELOPE_TAU_MS = 120;\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 this.quality = normalizeQuality(config.quality);\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.prewarmWinEffects();\n requestAnimationFrame((warmNow) => {\n this.render(warmNow);\n this.startLoop();\n });\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 this.runtime.isSpinning = true;\n this.runtime.phase = 'preSpin';\n this.runtime.preSpinStartedAt = performance.now();\n this.runtime.activeSpinState = activeSpinState;\n this.runtime.shouldHighlightCurrentSpin = shouldHighlightCurrentSpin;\n this.runtime.hasStartedFirstSpin = true;\n\n if (this.button) this.button.disabled = true;\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\n if (this.runtime.phase === 'preSpin') {\n if (now - this.runtime.preSpinStartedAt < CascadingReel.PRE_SPIN_MS) {\n return;\n }\n beginSpin(this.runtime, {\n activeSpinState: this.runtime.activeSpinState,\n shouldHighlightCurrentSpin: this.runtime.shouldHighlightCurrentSpin,\n startedAt: now,\n });\n this.runtime.preSpinStartedAt = 0;\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, now);\n }\n\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(), performance.now());\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 this.initialHighlightRequestedAt = performance.now();\n }\n\n private static readonly ZERO_SWAY: [number, number, number] = [0, 0, 0];\n\n private render(now: number): void {\n if (\n this.initialHighlightRequestedAt > 0 &&\n now - this.initialHighlightRequestedAt >= INITIAL_WIN_FLASH_DELAY_MS\n ) {\n startWinFlash(this.runtime, this.initialHighlightRequestedAt);\n this.runtime.winEffectsEnvelope = 1;\n this.initialHighlightRequestedAt = 0;\n }\n\n const swayTarget = this.runtime.phase === 'outro' || this.runtime.phase === 'preSpin' ? 0 : 1;\n const winEffectsTarget =\n this.runtime.phase === 'preSpin' ? 0 : this.runtime.phase === 'winFlash' ? 1 : 0;\n if (this.lastRafTime > 0) {\n const dt = Math.min(now - this.lastRafTime, 50);\n const kSway = 1 - Math.exp(-dt / CascadingReel.SWAY_ENVELOPE_TAU_MS);\n this.runtime.swayEnvelope += (swayTarget - this.runtime.swayEnvelope) * kSway;\n const kWin = 1 - Math.exp(-dt / CascadingReel.WIN_EFFECTS_ENVELOPE_TAU_MS);\n this.runtime.winEffectsEnvelope +=\n (winEffectsTarget - this.runtime.winEffectsEnvelope) * kWin;\n }\n this.lastRafTime = now;\n\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 now,\n );\n this.drawLayer(\n this.scriptedPendingGrid,\n this.scriptedIncomingOffsets,\n CascadingReel.ZERO_SWAY,\n now,\n );\n } else {\n this.drawBaseGrid(now);\n }\n\n const winPhase = this.initialHighlightRequestedAt > 0 ? 'winFlash' : this.runtime.phase;\n const winFlashStartedAt =\n this.initialHighlightRequestedAt > 0\n ? this.initialHighlightRequestedAt\n : this.runtime.winFlashStartedAt;\n const winEffectsEnvelope =\n this.initialHighlightRequestedAt > 0 ? 1 : this.runtime.winEffectsEnvelope;\n\n drawWinningEffects({\n ctx: this.ctx,\n now,\n phase: winPhase,\n winFlashStartedAt,\n winEffectsEnvelope,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell:\n this.runtime.phase === 'winFlash' &&\n this.runtime.shouldHighlightCurrentSpin &&\n this.runtime.hasStartedFirstSpin\n ? this.particlesPerCell\n : 0,\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(now: number): void {\n this.drawLayer(this.grid, null, CascadingReel.ZERO_SWAY, now);\n }\n\n private drawLayer(\n grid: SymbolId[][],\n offsets: number[][] | null,\n columnSway: [number, number, number],\n now: number,\n ): void {\n const skipWinningCells =\n this.runtime.phase === 'winFlash' ||\n this.runtime.phase === 'preSpin' ||\n (this.initialHighlightRequestedAt > 0 && this.winningCells.length > 0);\n\n drawGridLayer({\n ctx: this.ctx,\n phase: this.runtime.phase,\n now,\n swayEnvelope: this.runtime.swayEnvelope,\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 skipWinningCells,\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 drawSpriteCellToCtx(\n ctx: CanvasRenderingContext2D,\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 ctx.drawImage(image, 0, sourceY, image.width, sourceHeight, x, y, width, height);\n }\n\n private readonly drawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void => {\n this.drawSpriteCellToCtx(this.ctx, symbolId, 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 if (this.quality === 'high') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n else if (this.quality === 'balanced')\n this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_BALANCED;\n else if (this.quality === 'low') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_LOW;\n else\n this.particlesPerCell =\n this.dpr >= 2 ? FLOW_WIN_PARTICLES_PER_CELL_BALANCED : FLOW_WIN_PARTICLES_PER_CELL_HIGH;\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 prewarmWinEffects(): void {\n if (this.winningCells.length === 0) return;\n\n const offscreen = document.createElement('canvas');\n offscreen.width = this.width;\n offscreen.height = this.height;\n const offscreenCtx = offscreen.getContext('2d');\n if (!offscreenCtx) return;\n\n const savedPhase = this.runtime.phase;\n const savedWinFlashStartedAt = this.runtime.winFlashStartedAt;\n\n this.runtime.phase = 'winFlash';\n this.runtime.winFlashStartedAt = performance.now() - 300;\n\n const prewarmNow = performance.now();\n const particlesPrewarm = Math.min(this.particlesPerCell, FLOW_WIN_PARTICLES_PER_CELL_LOW);\n\n const drawSpriteToOffscreen = (\n symbolId: SymbolId,\n x: number,\n y: number,\n w: number,\n h: number,\n ): void => {\n this.drawSpriteCellToCtx(offscreenCtx, symbolId, x, y, w, h);\n };\n\n for (let frame = 0; frame < 2; frame += 1) {\n drawWinningEffects({\n ctx: offscreenCtx,\n now: prewarmNow + frame * 16,\n phase: 'winFlash',\n winFlashStartedAt: this.runtime.winFlashStartedAt,\n winEffectsEnvelope: 1,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell: particlesPrewarm,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: drawSpriteToOffscreen,\n });\n }\n\n this.runtime.phase = savedPhase;\n this.runtime.winFlashStartedAt = savedWinFlashStartedAt;\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_PULSE_PERIOD_MS","FLOW_WIN_PULSE_AMPLITUDE","PARTICLE_FLY_DURATION_MS","FLOW_WIN_PARTICLES_PER_CELL_HIGH","FLOW_WIN_PARTICLES_PER_CELL_BALANCED","FLOW_WIN_PARTICLES_PER_CELL_LOW","DEFAULT_PARTICLE_COLOR_RGB","DEFAULT_WINNING_CELL_BORDER_RGBA","FLOW_COLUMN_STAGGER_MS","FLOW_FALL_MS","FLOW_OUTRO_OVERLAP_MS","INITIAL_WIN_FLASH_DELAY_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","normalizeQuality","preset","normalizeWinningCellBorderColor","rowsToStopGrid","rows","normalizeStopGrid","stopGrid","next","normalizeInitialSegments","initialSegments","cloneSpinState","state","SpinQueueController","initialQueue","entry","createRuntimeState","beginSpin","finishSpin","hasPendingInQueue","startWinFlash","startedAt","destroyState","SWAY_AMPLITUDE_DEG","SWAY_OMEGA","drawGridLayer","sway","swayAmplitudeRad","envelope","skipWinning","x","swayY","offsetY","y","angle","centerX","centerY","hash01","a","b","c","d","particleSeedsCache","getParticleSeeds","key","seeds","i","drawWinningEffects","elapsed","pulseProgress","pulse","cell","drawWinningCellBorder","scale","scaledW","scaledH","offsetX","drawCellParticleBurst","ctx","cellW","cellH","cellCol","cellRow","inset","lineWidth","localX","localY","hue","minSide","dashLength","gapLength","halfLine","RAINBOW_HUE_BUCKETS","maxDistance","baseRadius","globalFade","g","isSolid","seedsList","s","startTime","age","particleT","direction","distance","px","py","twinkle","radius","alpha","hueRaw","CascadingReel","config","context","warmNow","activeSpinState","shouldHighlightCurrentSpin","scriptedSource","stopGridSource","nextGrid","callback","swayTarget","winEffectsTarget","dt","kSway","kWin","winPhase","winFlashStartedAt","winEffectsEnvelope","columnSway","skipWinningCells","symbolId","width","height","image","segmentIndex","segmentHeight","sourceY","sourceHeight","bounds","side","squareSize","offscreen","offscreenCtx","savedPhase","savedWinFlashStartedAt","prewarmNow","particlesPrewarm","drawSpriteToOffscreen","w","h","frame","time"],"mappings":"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"}
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;\n/** Длительность фазы подсветки выигрыша (мс). Подсветка отключается по таймауту. */\nexport const FLOW_WIN_FLASH_MS = 5000;\nexport const FLOW_WIN_PULSE_PERIOD_MS = 1800;\nexport const FLOW_WIN_PULSE_AMPLITUDE = 0.1;\n/** Duration for one particle to fly from center to edge (continuous spray). */\nexport const PARTICLE_FLY_DURATION_MS = 720;\nexport const FLOW_WIN_PARTICLES_PER_CELL_HIGH = 34;\nexport const FLOW_WIN_PARTICLES_PER_CELL_BALANCED = 20;\nexport const FLOW_WIN_PARTICLES_PER_CELL_LOW = 12;\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/** Delay before starting initial win-effect so canvas/JIT can warm up. */\nexport const INITIAL_WIN_FLASH_DELAY_MS = 200;\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 { QualityPreset, 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 normalizeQuality(preset?: QualityPreset): QualityPreset {\n if (preset === 'high' || preset === 'balanced' || preset === 'low' || preset === 'auto') {\n return preset;\n }\n return 'auto';\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 highlightWin: state.highlightWin,\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 outroStartedAt: number;\n idleStartedAt: number;\n preSpinStartedAt: number;\n swayEnvelope: number;\n winEffectsEnvelope: 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 outroStartedAt: 0,\n idleStartedAt: 0,\n preSpinStartedAt: 0,\n swayEnvelope: 1,\n winEffectsEnvelope: 1,\n };\n}\n\nexport function beginSpin(\n state: RuntimeState,\n params: {\n activeSpinState: SpinState | null;\n shouldHighlightCurrentSpin: boolean;\n startedAt: number;\n },\n): void {\n state.hasStartedFirstSpin = true;\n state.isSpinning = true;\n state.phase = 'outro';\n state.outroStartedAt = params.startedAt;\n state.activeSpinState = params.activeSpinState;\n state.shouldHighlightCurrentSpin = params.shouldHighlightCurrentSpin;\n}\n\nexport function finishSpin(state: RuntimeState, hasPendingInQueue: boolean, now: number): void {\n state.phase = 'idle';\n state.idleStartedAt = now;\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_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PULSE_AMPLITUDE,\n FLOW_WIN_PULSE_PERIOD_MS,\n GRID_COLS,\n GRID_ROWS,\n PARTICLE_FLY_DURATION_MS,\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\nconst SWAY_AMPLITUDE_DEG = 3;\nconst SWAY_OMEGA = 0.002;\n\nexport function drawGridLayer(params: {\n ctx: CanvasRenderingContext2D;\n phase: SpinPhase;\n now: number;\n swayEnvelope: number;\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 /** When true, winning cells are not drawn here (they are drawn by win effects). */\n skipWinningCells?: boolean;\n isWinningCell: (col: number, row: number) => boolean;\n drawSpriteCell: DrawSpriteCell;\n}): void {\n const sway = params.columnSway ?? [0, 0, 0];\n const swayAmplitudeRad = SWAY_AMPLITUDE_DEG * (Math.PI / 180);\n const envelope = Math.max(0, Math.min(1, params.swayEnvelope));\n const skipWinning = params.skipWinningCells ?? false;\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 (\n (skipWinning || params.phase === 'winFlash' || params.phase === 'preSpin') &&\n params.isWinningCell(col, row)\n )\n 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 const rawAngle = Math.sin(params.now * SWAY_OMEGA + col * 1.1 + row * 1.3) * swayAmplitudeRad;\n const angle = rawAngle * envelope;\n\n const centerX = x + params.cellW * 0.5;\n const centerY = y + params.cellH * 0.5;\n\n params.ctx.save();\n params.ctx.translate(centerX, centerY);\n params.ctx.rotate(angle);\n params.ctx.translate(-params.cellW * 0.5, -params.cellH * 0.5);\n params.drawSpriteCell(params.grid[col][row], 0, 0, params.cellW, params.cellH);\n params.ctx.restore();\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\ntype ParticleSeeds = {\n seedA: number;\n seedB: number;\n seedC: number;\n phaseOffset: number;\n twinkleSeed: number;\n};\n\nconst particleSeedsCache = new Map<string, ParticleSeeds[]>();\n\nfunction getParticleSeeds(col: number, row: number): ParticleSeeds[] {\n const key = `${col},${row}`;\n let seeds = particleSeedsCache.get(key);\n if (seeds) return seeds;\n\n seeds = [];\n for (let i = 0; i < FLOW_WIN_PARTICLES_PER_CELL_HIGH; i += 1) {\n seeds.push({\n seedA: hash01(col, row, i, 1),\n seedB: hash01(col, row, i, 2),\n seedC: hash01(col, row, i, 3),\n phaseOffset: hash01(col, row, i, 4),\n twinkleSeed: hash01(col, row, i, 5),\n });\n }\n particleSeedsCache.set(key, seeds);\n return seeds;\n}\n\nexport function drawWinningEffects(params: {\n ctx: CanvasRenderingContext2D;\n now: number;\n phase: SpinPhase;\n winFlashStartedAt: number;\n winEffectsEnvelope: number;\n winningCells: CellPosition[];\n boardX: number;\n boardY: number;\n cellW: number;\n cellH: number;\n particlesPerCell: 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' && params.phase !== 'preSpin') return;\n\n const envelope = Math.max(0, Math.min(1, params.winEffectsEnvelope));\n const elapsed = Math.max(0, params.now - params.winFlashStartedAt);\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\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 cell.col,\n cell.row,\n params.winningCellBorderRgba,\n elapsed,\n envelope,\n );\n\n const symbol = params.getCell(cell.col, cell.row);\n const scale = 1 + (pulse - 1) * envelope;\n const scaledW = params.cellW * scale;\n const scaledH = params.cellH * scale;\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\n if (params.particlesPerCell <= 0) return;\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 (const cell of params.winningCells) {\n const x = params.boardX + cell.col * params.cellW;\n const y = params.boardY + cell.row * params.cellH;\n drawCellParticleBurst({\n ctx: params.ctx,\n cell,\n x,\n y,\n elapsed,\n envelope,\n cellW: params.cellW,\n cellH: params.cellH,\n particlesPerCell: params.particlesPerCell,\n particleColorMode: params.particleColorMode,\n particleColorRgb: params.particleColorRgb,\n });\n }\n\n params.ctx.restore();\n}\n\nfunction drawWinningCellBorder(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n cellW: number,\n cellH: number,\n cellCol: number,\n cellRow: number,\n color: [number, number, number, number],\n elapsed: number,\n envelope: 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.03));\n const [, , , a] = color;\n\n const localX = (cellCol + 0.5) / GRID_COLS;\n const localY = (cellRow + 0.5) / GRID_ROWS;\n const hue = (elapsed * 0.2 + localX * 80 + localY * 40) % 360;\n\n const minSide = Math.min(cellW, cellH);\n const dashLength = Math.max(4, Math.floor(minSide * 0.04));\n const gapLength = Math.max(3, Math.floor(minSide * 0.03));\n\n ctx.save();\n ctx.lineWidth = lineWidth;\n ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${a * envelope})`;\n ctx.setLineDash([dashLength, gapLength]);\n ctx.lineDashOffset = -elapsed * 0.03;\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.setLineDash([]);\n ctx.restore();\n}\n\nconst RAINBOW_HUE_BUCKETS = 24;\n\nfunction drawCellParticleBurst(params: {\n ctx: CanvasRenderingContext2D;\n cell: CellPosition;\n x: number;\n y: number;\n elapsed: number;\n envelope: number;\n cellW: number;\n cellH: number;\n particlesPerCell: 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.96 * params.envelope;\n\n const [r, g, b] = params.particleColorRgb;\n const isSolid = params.particleColorMode === 'solid';\n if (isSolid) {\n params.ctx.fillStyle = `rgb(${r},${g},${b})`;\n }\n\n const seedsList = getParticleSeeds(params.cell.col, params.cell.row);\n\n for (let i = 0; i < params.particlesPerCell; i += 1) {\n const s = seedsList[i];\n const startTime = s.phaseOffset * PARTICLE_FLY_DURATION_MS;\n const age = params.elapsed - startTime;\n if (age < 0) continue;\n const particleT = (age % PARTICLE_FLY_DURATION_MS) / PARTICLE_FLY_DURATION_MS;\n\n const direction = s.seedA * Math.PI * 2;\n const distance = maxDistance * particleT * (0.35 + s.seedB * 0.65);\n const px = centerX + Math.cos(direction) * distance;\n const py = centerY + Math.sin(direction) * distance;\n const twinkle =\n 0.7 + 0.9 * Math.max(0, Math.sin((params.elapsed * 0.012 + s.twinkleSeed * 2) * Math.PI * 2));\n const radius = Math.max(1, baseRadius * (0.55 + s.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 (isSolid) {\n params.ctx.globalAlpha = alpha;\n } else {\n const hueRaw =\n (s.seedA * 360 + params.elapsed * 0.24 + params.cell.col * 38 + params.cell.row * 22) % 360;\n const hue = Math.floor(hueRaw / (360 / RAINBOW_HUE_BUCKETS)) * (360 / RAINBOW_HUE_BUCKETS);\n params.ctx.fillStyle = `hsla(${hue},98%,64%,${alpha})`;\n }\n\n params.ctx.beginPath();\n params.ctx.arc(px, py, radius, 0, Math.PI * 2);\n params.ctx.fill();\n }\n\n params.ctx.globalAlpha = 1;\n}\n","import {\n DEFAULT_SPRITE_ELEMENTS_COUNT,\n FLOW_WIN_FLASH_MS,\n FLOW_WIN_PARTICLES_PER_CELL_BALANCED,\n FLOW_WIN_PARTICLES_PER_CELL_HIGH,\n FLOW_WIN_PARTICLES_PER_CELL_LOW,\n GRID_COLS,\n GRID_ROWS,\n INITIAL_WIN_FLASH_DELAY_MS,\n} 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 normalizeQuality,\n normalizeStopGrid,\n normalizeWinningCellBorderColor,\n rowsToStopGrid,\n} from './normalize';\nimport { drawGridLayer, drawWinningEffects } from './render/canvasRenderer';\nimport type { CascadingReelConfig, CellPosition, QualityPreset, 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 private readonly quality: QualityPreset;\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 private lastRafTime = 0;\n private particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n private initialHighlightRequestedAt = 0;\n private static readonly SWAY_ENVELOPE_TAU_MS = 220;\n private static readonly PRE_SPIN_MS = 150;\n private static readonly WIN_EFFECTS_ENVELOPE_TAU_MS = 120;\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 this.quality = normalizeQuality(config.quality);\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.prewarmWinEffects();\n requestAnimationFrame((warmNow) => {\n this.render(warmNow);\n this.startLoop();\n });\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 = activeSpinState?.highlightWin === true;\n this.runtime.isSpinning = true;\n this.runtime.phase = 'preSpin';\n this.runtime.preSpinStartedAt = performance.now();\n this.runtime.activeSpinState = activeSpinState;\n this.runtime.shouldHighlightCurrentSpin = shouldHighlightCurrentSpin;\n this.runtime.hasStartedFirstSpin = true;\n\n if (this.button) this.button.disabled = true;\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 // Подсветка до клика: в winFlash с подсветкой кнопка включена, по клику завершаем и при наличии очереди запускаем следующий спин\n if (\n this.runtime.phase === 'winFlash' &&\n (this.runtime.shouldHighlightCurrentSpin || !this.runtime.hasStartedFirstSpin)\n ) {\n this.finishSpinWithUi();\n if (this.spinQueueController.hasPending()) this.spin();\n return;\n }\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\n if (this.runtime.phase === 'preSpin') {\n if (now - this.runtime.preSpinStartedAt < CascadingReel.PRE_SPIN_MS) {\n return;\n }\n beginSpin(this.runtime, {\n activeSpinState: this.runtime.activeSpinState,\n shouldHighlightCurrentSpin: this.runtime.shouldHighlightCurrentSpin,\n startedAt: now,\n });\n this.runtime.preSpinStartedAt = 0;\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, now);\n }\n\n if (this.runtime.phase === 'outro') {\n this.updateScriptedOutro(now);\n return;\n }\n if (this.runtime.phase === 'winFlash') {\n // Подсветка без ограничения по времени: выход только по клику «Spin»\n if (\n !this.runtime.shouldHighlightCurrentSpin &&\n now - this.runtime.winFlashStartedAt >= FLOW_WIN_FLASH_MS\n ) {\n this.finishSpinWithUi();\n }\n return;\n }\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 // Включаем кнопку только если в очереди есть следующий спин; после последнего — оставляем disabled\n if (\n this.button &&\n this.runtime.shouldHighlightCurrentSpin &&\n this.spinQueueController.hasPending()\n )\n this.button.disabled = false;\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(), performance.now());\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 this.initialHighlightRequestedAt = performance.now();\n }\n\n private static readonly ZERO_SWAY: [number, number, number] = [0, 0, 0];\n\n private render(now: number): void {\n if (\n this.initialHighlightRequestedAt > 0 &&\n now - this.initialHighlightRequestedAt >= INITIAL_WIN_FLASH_DELAY_MS\n ) {\n startWinFlash(this.runtime, this.initialHighlightRequestedAt);\n this.runtime.winEffectsEnvelope = 1;\n this.initialHighlightRequestedAt = 0;\n if (this.button) this.button.disabled = false;\n }\n\n const swayTarget = this.runtime.phase === 'outro' || this.runtime.phase === 'preSpin' ? 0 : 1;\n const winEffectsTarget =\n this.runtime.phase === 'preSpin' ? 0 : this.runtime.phase === 'winFlash' ? 1 : 0;\n if (this.lastRafTime > 0) {\n const dt = Math.min(now - this.lastRafTime, 50);\n const kSway = 1 - Math.exp(-dt / CascadingReel.SWAY_ENVELOPE_TAU_MS);\n this.runtime.swayEnvelope += (swayTarget - this.runtime.swayEnvelope) * kSway;\n const kWin = 1 - Math.exp(-dt / CascadingReel.WIN_EFFECTS_ENVELOPE_TAU_MS);\n this.runtime.winEffectsEnvelope +=\n (winEffectsTarget - this.runtime.winEffectsEnvelope) * kWin;\n }\n this.lastRafTime = now;\n\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 now,\n );\n this.drawLayer(\n this.scriptedPendingGrid,\n this.scriptedIncomingOffsets,\n CascadingReel.ZERO_SWAY,\n now,\n );\n } else {\n this.drawBaseGrid(now);\n }\n\n const winPhase = this.initialHighlightRequestedAt > 0 ? 'winFlash' : this.runtime.phase;\n const winFlashStartedAt =\n this.initialHighlightRequestedAt > 0\n ? this.initialHighlightRequestedAt\n : this.runtime.winFlashStartedAt;\n const winEffectsEnvelope =\n this.initialHighlightRequestedAt > 0 ? 1 : this.runtime.winEffectsEnvelope;\n\n drawWinningEffects({\n ctx: this.ctx,\n now,\n phase: winPhase,\n winFlashStartedAt,\n winEffectsEnvelope,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell:\n this.runtime.phase === 'winFlash' &&\n this.runtime.shouldHighlightCurrentSpin &&\n this.runtime.hasStartedFirstSpin\n ? this.particlesPerCell\n : 0,\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(now: number): void {\n this.drawLayer(this.grid, null, CascadingReel.ZERO_SWAY, now);\n }\n\n private drawLayer(\n grid: SymbolId[][],\n offsets: number[][] | null,\n columnSway: [number, number, number],\n now: number,\n ): void {\n const skipWinningCells =\n this.runtime.phase === 'winFlash' ||\n this.runtime.phase === 'preSpin' ||\n (this.initialHighlightRequestedAt > 0 && this.winningCells.length > 0);\n\n drawGridLayer({\n ctx: this.ctx,\n phase: this.runtime.phase,\n now,\n swayEnvelope: this.runtime.swayEnvelope,\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 skipWinningCells,\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 drawSpriteCellToCtx(\n ctx: CanvasRenderingContext2D,\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 ctx.drawImage(image, 0, sourceY, image.width, sourceHeight, x, y, width, height);\n }\n\n private readonly drawSpriteCell = (\n symbolId: SymbolId,\n x: number,\n y: number,\n width: number,\n height: number,\n ): void => {\n this.drawSpriteCellToCtx(this.ctx, symbolId, 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 if (this.quality === 'high') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_HIGH;\n else if (this.quality === 'balanced')\n this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_BALANCED;\n else if (this.quality === 'low') this.particlesPerCell = FLOW_WIN_PARTICLES_PER_CELL_LOW;\n else\n this.particlesPerCell =\n this.dpr >= 2 ? FLOW_WIN_PARTICLES_PER_CELL_BALANCED : FLOW_WIN_PARTICLES_PER_CELL_HIGH;\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 prewarmWinEffects(): void {\n if (this.winningCells.length === 0) return;\n\n const offscreen = document.createElement('canvas');\n offscreen.width = this.width;\n offscreen.height = this.height;\n const offscreenCtx = offscreen.getContext('2d');\n if (!offscreenCtx) return;\n\n const savedPhase = this.runtime.phase;\n const savedWinFlashStartedAt = this.runtime.winFlashStartedAt;\n\n this.runtime.phase = 'winFlash';\n this.runtime.winFlashStartedAt = performance.now() - 300;\n\n const prewarmNow = performance.now();\n const particlesPrewarm = Math.min(this.particlesPerCell, FLOW_WIN_PARTICLES_PER_CELL_LOW);\n\n const drawSpriteToOffscreen = (\n symbolId: SymbolId,\n x: number,\n y: number,\n w: number,\n h: number,\n ): void => {\n this.drawSpriteCellToCtx(offscreenCtx, symbolId, x, y, w, h);\n };\n\n for (let frame = 0; frame < 2; frame += 1) {\n drawWinningEffects({\n ctx: offscreenCtx,\n now: prewarmNow + frame * 16,\n phase: 'winFlash',\n winFlashStartedAt: this.runtime.winFlashStartedAt,\n winEffectsEnvelope: 1,\n winningCells: this.winningCells,\n boardX: this.boardX,\n boardY: this.boardY,\n cellW: this.cellW,\n cellH: this.cellH,\n particlesPerCell: particlesPrewarm,\n particleColorMode: this.particleColorMode,\n particleColorRgb: this.particleColorRgb,\n winningCellBorderRgba: this.winningCellBorderRgba,\n getCell: this.getCell,\n drawSpriteCell: drawSpriteToOffscreen,\n });\n }\n\n this.runtime.phase = savedPhase;\n this.runtime.winFlashStartedAt = savedWinFlashStartedAt;\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","PARTICLE_FLY_DURATION_MS","FLOW_WIN_PARTICLES_PER_CELL_HIGH","FLOW_WIN_PARTICLES_PER_CELL_BALANCED","FLOW_WIN_PARTICLES_PER_CELL_LOW","DEFAULT_PARTICLE_COLOR_RGB","DEFAULT_WINNING_CELL_BORDER_RGBA","FLOW_COLUMN_STAGGER_MS","FLOW_FALL_MS","FLOW_OUTRO_OVERLAP_MS","INITIAL_WIN_FLASH_DELAY_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","normalizeQuality","preset","normalizeWinningCellBorderColor","rowsToStopGrid","rows","normalizeStopGrid","stopGrid","next","normalizeInitialSegments","initialSegments","cloneSpinState","state","SpinQueueController","initialQueue","entry","createRuntimeState","beginSpin","finishSpin","hasPendingInQueue","startWinFlash","startedAt","destroyState","SWAY_AMPLITUDE_DEG","SWAY_OMEGA","drawGridLayer","sway","swayAmplitudeRad","envelope","skipWinning","x","swayY","offsetY","y","angle","centerX","centerY","hash01","a","b","c","d","particleSeedsCache","getParticleSeeds","key","seeds","i","drawWinningEffects","elapsed","pulseProgress","pulse","cell","drawWinningCellBorder","scale","scaledW","scaledH","offsetX","drawCellParticleBurst","ctx","cellW","cellH","cellCol","cellRow","inset","lineWidth","localX","localY","hue","minSide","dashLength","gapLength","halfLine","RAINBOW_HUE_BUCKETS","maxDistance","baseRadius","globalFade","g","isSolid","seedsList","s","startTime","age","particleT","direction","distance","px","py","twinkle","radius","alpha","hueRaw","CascadingReel","config","context","warmNow","activeSpinState","shouldHighlightCurrentSpin","scriptedSource","stopGridSource","nextGrid","callback","swayTarget","winEffectsTarget","dt","kSway","kWin","winPhase","winFlashStartedAt","winEffectsEnvelope","columnSway","skipWinningCells","symbolId","width","height","image","segmentIndex","segmentHeight","sourceY","sourceHeight","bounds","side","squareSize","offscreen","offscreenCtx","savedPhase","savedWinFlashStartedAt","prewarmNow","particlesPrewarm","drawSpriteToOffscreen","w","h","frame","time"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -88,6 +88,8 @@ export declare type SpinState = {
88
88
  stopRows?: number[][];
89
89
  finaleSequence?: number[][][];
90
90
  finaleSequenceRows?: number[][][];
91
+ /** When true, show win highlight after this spin. When false, never. When undefined, show only if this is the last spin in the queue. */
92
+ highlightWin?: boolean;
91
93
  callback?: () => void;
92
94
  };
93
95
 
package/dist/index.es.js CHANGED
@@ -4,7 +4,7 @@ const Y = [255, 235, 110], B = [
4
4
  255,
5
5
  0.78
6
6
  ], k = 190, I = 650, z = 220, X = 200;
7
- function E(t, i, e) {
7
+ function w(t, i, e) {
8
8
  return t < i ? i : t > e ? e : t;
9
9
  }
10
10
  function m(t) {
@@ -17,10 +17,10 @@ function W(t, i) {
17
17
  return (t % i + i) % i;
18
18
  }
19
19
  function R(t) {
20
- return E(Math.round(t), 0, 255);
20
+ return w(Math.round(t), 0, 255);
21
21
  }
22
22
  function Q(t) {
23
- return E(t, 0, 1);
23
+ return w(t, 0, 1);
24
24
  }
25
25
  function F(t) {
26
26
  const i = [];
@@ -48,7 +48,7 @@ function G(t) {
48
48
  t[s][r] === e && l.push({ col: s, row: r });
49
49
  return l;
50
50
  }
51
- function D() {
51
+ function H() {
52
52
  return Array.from({ length: 3 }, () => Array.from({ length: 3 }, () => 0));
53
53
  }
54
54
  function L(t, i) {
@@ -117,14 +117,14 @@ function K(t) {
117
117
  t.scriptedOutgoingOffsets[h][c] = 0, t.scriptedIncomingOffsets[h][c] = Number.NaN, i = !1, e = !1;
118
118
  continue;
119
119
  }
120
- const f = E(d / I, 0, 1), g = m(f);
120
+ const f = w(d / I, 0, 1), g = m(f);
121
121
  t.scriptedOutgoingOffsets[h][c] = l * g, f < 1 && (i = !1);
122
122
  const p = d - o;
123
123
  if (p <= 0) {
124
124
  t.scriptedIncomingOffsets[h][c] = Number.NaN, e = !1;
125
125
  continue;
126
126
  }
127
- const S = E(p / I, 0, 1), _ = m(S);
127
+ const S = w(p / I, 0, 1), _ = m(S);
128
128
  t.scriptedIncomingOffsets[h][c] = r[c] * (1 - _), S < 1 && (e = !1);
129
129
  }
130
130
  }
@@ -151,7 +151,7 @@ function it(t) {
151
151
  Q(t[3])
152
152
  ] : B;
153
153
  }
154
- function b(t) {
154
+ function P(t) {
155
155
  if (t.length !== 3)
156
156
  throw new Error("rows must contain 3 rows");
157
157
  for (let i = 0; i < 3; i += 1)
@@ -180,7 +180,7 @@ function M(t, i) {
180
180
  return e;
181
181
  }
182
182
  function et(t, i) {
183
- return M(b(t), i);
183
+ return M(P(t), i);
184
184
  }
185
185
  function nt(t) {
186
186
  return {
@@ -188,6 +188,7 @@ function nt(t) {
188
188
  stopRows: t.stopRows?.map((i) => [...i]),
189
189
  finaleSequence: t.finaleSequence?.map((i) => i.map((e) => [...e])),
190
190
  finaleSequenceRows: t.finaleSequenceRows?.map((i) => i.map((e) => [...e])),
191
+ highlightWin: t.highlightWin,
191
192
  callback: t.callback
192
193
  };
193
194
  }
@@ -225,7 +226,7 @@ function rt(t, i) {
225
226
  function ot(t, i, e) {
226
227
  t.phase = "idle", t.idleStartedAt = e, t.isSpinning = !1, t.shouldHighlightCurrentSpin = !1, t.queueFinished = !i, t.activeSpinState = null;
227
228
  }
228
- function H(t, i) {
229
+ function D(t, i) {
229
230
  t.winFlashStartedAt = i, t.phase = "winFlash";
230
231
  }
231
232
  function ht(t) {
@@ -310,17 +311,17 @@ function T(t) {
310
311
  }
311
312
  }
312
313
  function ft(t, i, e, n, l, s, r, u, o, h) {
313
- const a = Math.max(1, Math.floor(Math.min(n, l) * 0.04)), c = Math.max(1, Math.floor(Math.min(n, l) * 0.025)), [, , , d] = u, f = (s + 0.5) / 3, g = (r + 0.5) / 3, p = (o * 0.15 + f * 80 + g * 45) % 360, S = Math.min(n, l), _ = Math.max(4, Math.floor(S * 0.04)), A = Math.max(3, Math.floor(S * 0.03));
314
+ const a = Math.max(1, Math.floor(Math.min(n, l) * 0.04)), c = Math.max(1, Math.floor(Math.min(n, l) * 0.03)), [, , , d] = u, f = (s + 0.5) / 3, g = (r + 0.5) / 3, p = (o * 0.2 + f * 80 + g * 40) % 360, S = Math.min(n, l), _ = Math.max(4, Math.floor(S * 0.04)), A = Math.max(3, Math.floor(S * 0.03));
314
315
  t.save(), t.lineWidth = c, t.strokeStyle = `hsla(${p}, 90%, 70%, ${d * h})`, t.setLineDash([_, A]), t.lineDashOffset = -o * 0.03;
315
- const w = c * 0.5;
316
+ const E = c * 0.5;
316
317
  t.strokeRect(
317
- i + a + w,
318
- e + a + w,
318
+ i + a + E,
319
+ e + a + E,
319
320
  n - a * 2 - c,
320
321
  l - a * 2 - c
321
322
  ), t.setLineDash([]), t.restore();
322
323
  }
323
- const v = 24;
324
+ const N = 24;
324
325
  function gt(t) {
325
326
  const i = t.x + t.cellW * 0.5, e = t.y + t.cellH * 0.5, n = Math.min(t.cellW, t.cellH) * 0.72, l = Math.min(t.cellW, t.cellH) * 0.028, s = 0.96 * t.envelope, [r, u, o] = t.particleColorRgb, h = t.particleColorMode === "solid";
326
327
  h && (t.ctx.fillStyle = `rgb(${r},${u},${o})`);
@@ -328,15 +329,15 @@ function gt(t) {
328
329
  for (let c = 0; c < t.particlesPerCell; c += 1) {
329
330
  const d = a[c], f = d.phaseOffset * 720, g = t.elapsed - f;
330
331
  if (g < 0) continue;
331
- const p = g % 720 / 720, S = d.seedA * Math.PI * 2, _ = n * p * (0.35 + d.seedB * 0.65), A = i + Math.cos(S) * _, w = e + Math.sin(S) * _, N = 0.7 + 0.9 * Math.max(0, Math.sin((t.elapsed * 0.012 + d.twinkleSeed * 2) * Math.PI * 2)), y = Math.max(1, l * (0.55 + d.seedC * 0.6) * (1 - p * 0.5)), P = E((0.9 + N * 0.2) * s, 0.9, 1);
332
- if (!(P <= 0)) {
332
+ const p = g % 720 / 720, S = d.seedA * Math.PI * 2, _ = n * p * (0.35 + d.seedB * 0.65), A = i + Math.cos(S) * _, E = e + Math.sin(S) * _, v = 0.7 + 0.9 * Math.max(0, Math.sin((t.elapsed * 0.012 + d.twinkleSeed * 2) * Math.PI * 2)), y = Math.max(1, l * (0.55 + d.seedC * 0.6) * (1 - p * 0.5)), b = w((0.9 + v * 0.2) * s, 0.9, 1);
333
+ if (!(b <= 0)) {
333
334
  if (h)
334
- t.ctx.globalAlpha = P;
335
+ t.ctx.globalAlpha = b;
335
336
  else {
336
- const q = (d.seedA * 360 + t.elapsed * 0.24 + t.cell.col * 38 + t.cell.row * 22) % 360, U = Math.floor(q / (360 / v)) * (360 / v);
337
- t.ctx.fillStyle = `hsla(${U},98%,64%,${P})`;
337
+ const q = (d.seedA * 360 + t.elapsed * 0.24 + t.cell.col * 38 + t.cell.row * 22) % 360, U = Math.floor(q / (360 / N)) * (360 / N);
338
+ t.ctx.fillStyle = `hsla(${U},98%,64%,${b})`;
338
339
  }
339
- t.ctx.beginPath(), t.ctx.arc(A, w, y, 0, Math.PI * 2), t.ctx.fill();
340
+ t.ctx.beginPath(), t.ctx.arc(A, E, y, 0, Math.PI * 2), t.ctx.fill();
340
341
  }
341
342
  }
342
343
  t.ctx.globalAlpha = 1;
@@ -368,8 +369,8 @@ class C {
368
369
  scriptedOutgoingGrid = null;
369
370
  scriptedPendingGrid = null;
370
371
  scriptedOutroStartedAt = 0;
371
- scriptedOutgoingOffsets = D();
372
- scriptedIncomingOffsets = D();
372
+ scriptedOutgoingOffsets = H();
373
+ scriptedIncomingOffsets = H();
373
374
  winningCells = [];
374
375
  grid;
375
376
  lastRafTime = 0;
@@ -402,7 +403,7 @@ class C {
402
403
  this.runtime.queueFinished = !0, this.button && (this.button.disabled = !0);
403
404
  return;
404
405
  }
405
- const i = this.spinQueueController.consume(), e = !this.spinQueueController.hasPending();
406
+ const i = this.spinQueueController.consume(), e = i?.highlightWin === !0;
406
407
  this.runtime.isSpinning = !0, this.runtime.phase = "preSpin", this.runtime.preSpinStartedAt = performance.now(), this.runtime.activeSpinState = i, this.runtime.shouldHighlightCurrentSpin = e, this.runtime.hasStartedFirstSpin = !0, this.button && (this.button.disabled = !0);
407
408
  }
408
409
  bindEvents() {
@@ -412,6 +413,10 @@ class C {
412
413
  window.removeEventListener("resize", this.resize), this.button?.removeEventListener("click", this.onSpinClick);
413
414
  }
414
415
  onSpinClick = () => {
416
+ if (this.runtime.phase === "winFlash" && (this.runtime.shouldHighlightCurrentSpin || !this.runtime.hasStartedFirstSpin)) {
417
+ this.finishSpinWithUi(), this.spinQueueController.hasPending() && this.spin();
418
+ return;
419
+ }
415
420
  this.runtime.isSpinning || this.spin();
416
421
  };
417
422
  getNextGrid(i) {
@@ -427,16 +432,19 @@ class C {
427
432
  shouldHighlightCurrentSpin: this.runtime.shouldHighlightCurrentSpin,
428
433
  startedAt: i
429
434
  }), this.runtime.preSpinStartedAt = 0;
430
- const e = this.runtime.activeSpinState?.finaleSequenceRows ? this.runtime.activeSpinState.finaleSequenceRows.map((s) => b(s)) : this.runtime.activeSpinState?.finaleSequence ?? [];
435
+ const e = this.runtime.activeSpinState?.finaleSequenceRows ? this.runtime.activeSpinState.finaleSequenceRows.map((s) => P(s)) : this.runtime.activeSpinState?.finaleSequence ?? [];
431
436
  this.scriptedCascadeQueue = e.map((s) => s.map((r) => [...r])), this.clearWinningCells();
432
- const n = this.runtime.activeSpinState?.stopRows ? b(this.runtime.activeSpinState.stopRows) : this.runtime.activeSpinState?.stopGrid, l = this.getNextGrid(n);
437
+ const n = this.runtime.activeSpinState?.stopRows ? P(this.runtime.activeSpinState.stopRows) : this.runtime.activeSpinState?.stopGrid, l = this.getNextGrid(n);
433
438
  this.startOutroTransition(l, i);
434
439
  }
435
440
  if (this.runtime.phase === "outro") {
436
441
  this.updateScriptedOutro(i);
437
442
  return;
438
443
  }
439
- this.runtime.phase;
444
+ if (this.runtime.phase === "winFlash") {
445
+ !this.runtime.shouldHighlightCurrentSpin && i - this.runtime.winFlashStartedAt >= 5e3 && this.finishSpinWithUi();
446
+ return;
447
+ }
440
448
  }
441
449
  }
442
450
  updateScriptedOutro(i) {
@@ -463,7 +471,7 @@ class C {
463
471
  this.finishSpinWithUi();
464
472
  return;
465
473
  }
466
- this.winningCells = G(this.grid), H(this.runtime, i);
474
+ this.winningCells = G(this.grid), D(this.runtime, i), this.button && this.runtime.shouldHighlightCurrentSpin && this.spinQueueController.hasPending() && (this.button.disabled = !1);
467
475
  }
468
476
  }
469
477
  }
@@ -484,7 +492,7 @@ class C {
484
492
  }
485
493
  static ZERO_SWAY = [0, 0, 0];
486
494
  render(i) {
487
- this.initialHighlightRequestedAt > 0 && i - this.initialHighlightRequestedAt >= X && (H(this.runtime, this.initialHighlightRequestedAt), this.runtime.winEffectsEnvelope = 1, this.initialHighlightRequestedAt = 0);
495
+ this.initialHighlightRequestedAt > 0 && i - this.initialHighlightRequestedAt >= X && (D(this.runtime, this.initialHighlightRequestedAt), this.runtime.winEffectsEnvelope = 1, this.initialHighlightRequestedAt = 0, this.button && (this.button.disabled = !1));
488
496
  const e = this.runtime.phase === "outro" || this.runtime.phase === "preSpin" ? 0 : 1, n = this.runtime.phase === "preSpin" ? 0 : this.runtime.phase === "winFlash" ? 1 : 0;
489
497
  if (this.lastRafTime > 0) {
490
498
  const u = Math.min(i - this.lastRafTime, 50), o = 1 - Math.exp(-u / C.SWAY_ENVELOPE_TAU_MS);