cascading-reel 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,7 +31,9 @@ const reel = new CascadingReel({
31
31
  canvas: canvas as HTMLCanvasElement,
32
32
  button: button as HTMLButtonElement,
33
33
  sprite: new URL('./assets/reel.webp', import.meta.url).href,
34
+ spriteCrossOrigin: 'anonymous',
34
35
  spriteElementsCount: 6,
36
+ symbolScale: 0.85,
35
37
  initialSegments: [
36
38
  [0, 1, 2],
37
39
  [3, 0, 5],
@@ -80,7 +82,9 @@ onMounted(async () => {
80
82
  canvas: canvasRef.value,
81
83
  button: buttonRef.value ?? undefined,
82
84
  sprite: new URL('./assets/reel.webp', import.meta.url).href,
85
+ spriteCrossOrigin: 'anonymous',
83
86
  spriteElementsCount: 6,
87
+ symbolScale: 0.85,
84
88
  particleColor: 'rainbow',
85
89
  });
86
90
  await reel.value.init();
@@ -130,17 +134,19 @@ type SpinState = {
130
134
 
131
135
  ## Options
132
136
 
133
- | Option | Type | Default | Description |
134
- |:--|:--|:--:|:--|
135
- | `canvas` | `HTMLCanvasElement` | | Canvas for rendering. |
136
- | `container` | `HTMLElement` | | Element used for responsive sizing. |
137
- | `button` | `HTMLButtonElement` | | Optional spin button. |
138
- | `sprite` | `string` | | Sprite sheet URL. |
139
- | `spriteElementsCount` | `number` | `6` | Number of symbols in the sprite sheet. |
140
- | `initialSegments` | `number[][]` | randomized | Initial 3x3 state in rows format. |
141
- | `highlightInitialWinningCells` | `boolean` | `true` | Show initial highlight before first spin. |
142
- | `queuedSpinStates` | `SpinState[]` | `[]` | Predefined queue consumed by `spin()`. |
143
- | `particleColor` | `'rainbow' \| [number, number, number]` | `[255, 235, 110]` | Win particle color mode or solid RGB color. |
137
+ | Option | Type | Default | Description |
138
+ |:-------------------------------|:-----------------------------------------|:-----------------:|:-------------------------------------------------------|
139
+ | `canvas` | `HTMLCanvasElement` | | Canvas for rendering. |
140
+ | `container` | `HTMLElement` | | Element used for responsive sizing. |
141
+ | `button` | `HTMLButtonElement` | | Optional spin button. |
142
+ | `sprite` | `string \| HTMLImageElement` | | Sprite sheet URL or preloaded image element. |
143
+ | `spriteCrossOrigin` | `'' \| 'anonymous' \| 'use-credentials'` | `'anonymous'` | `crossOrigin` mode used when `sprite` is a URL string. |
144
+ | `spriteElementsCount` | `number` | `6` | Number of symbols in the sprite sheet. |
145
+ | `symbolScale` | `number` | `0.9` | Symbol scale inside the cell. Clamped to `0.5..1.2`. |
146
+ | `initialSegments` | `number[][]` | randomized | Initial 3x3 state in rows format. |
147
+ | `highlightInitialWinningCells` | `boolean` | `true` | Show initial highlight before first spin. |
148
+ | `queuedSpinStates` | `SpinState[]` | `[]` | Predefined queue consumed by `spin()`. |
149
+ | `particleColor` | `'rainbow' \| [number, number, number]` | `[255, 235, 110]` | Win particle color mode or solid RGB color. |
144
150
 
145
151
  ## Methods
146
152
 
@@ -156,6 +162,7 @@ reel.destroy();
156
162
  - Every symbol frame is square.
157
163
  - Total texture height equals `spriteElementsCount * frameWidth` (square-frame assumption).
158
164
  - `PNG` and `WebP` with transparency are supported.
165
+ - When using a cross-origin sprite URL (CDN/sandbox), keep `spriteCrossOrigin: 'anonymous'` and ensure the host returns CORS headers.
159
166
 
160
167
  ## License
161
168
 
package/dist/index.cjs.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const k=6,f=3,d=3,X=[.04,0,-.04],A={columnStaggerMs:76,fallMs:800,outroOverlapMs:88,outroRowGapMs:14,rowBaseSpacingRatio:.05,incomingAlphaRampMs:34,fixedStepMs:1e3/120,maxCatchUpStepsPerFrame:6};A.outroRowGapMs;A.rowBaseSpacingRatio;const L=1800,V=.15,I=720,D=34,y=[255,235,110];A.columnStaggerMs;A.fallMs;A.outroOverlapMs;const Y=200;function x(s,t,i){return s<t?t:s>i?i:s}function $(s){return Math.floor(Math.random()*s)}function E(s,t){return(s%t+t)%t}function O(s){return x(Math.round(s),0,255)}function F(s){const t=[];for(let i=0;i<f;i+=1){const e=[];for(let n=0;n<d;n+=1)e.push($(s));t.push(e)}return t}function B(s){const t=new Map;for(let r=0;r<f;r+=1)for(let o=0;o<d;o+=1){const h=s[r][o];t.set(h,(t.get(h)??0)+1)}let i=s[0][0],e=-1;for(const[r,o]of t.entries())o>e&&(e=o,i=r);const n=[];for(let r=0;r<f;r+=1)for(let o=0;o<d;o+=1)s[r][o]===i&&n.push({col:r,row:o});return n}function S(){return Array.from({length:f},()=>Array.from({length:d},()=>0))}function P(s,t){for(let i=0;i<f;i+=1)for(let e=0;e<d;e+=1)s[i][e]=t}class K{rafId=null;step=null;start(t){this.rafId===null&&(this.step=t,this.rafId=requestAnimationFrame(this.tick))}stop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.step=null}isRunning(){return this.rafId!==null}tick=t=>{if(!this.step){this.stop();return}if(this.step(t)===!1){this.stop();return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.tick))}}function Q(s){const t=x(s,0,1);return t*t*t*(t*(t*6-15)+10)}function G(s,t){if(s.endMs<=s.startMs)return{value:s.to,t:1,done:!0};const i=x((t-s.startMs)/(s.endMs-s.startMs),0,1),e=Q(i);return{value:s.from+(s.to-s.from)*e,t:i,done:i>=1}}function j(s,t,i,e){const n=[0,0,0];let r=0;for(let o=d-1;o>=0;o-=1){if(s[o]===0){n[o]=0;continue}n[o]=r;const h=Math.floor(t*e);r+=h+i}return n}function Z(s){const i=s.height-s.boardY+s.cellH+2,e=[i,i,i];return{columnStaggerMs:s.motionProfile.columnStaggerMs,fallMs:s.motionProfile.fallMs,incomingAlphaRampMs:s.motionProfile.incomingAlphaRampMs,outgoingDistance:i,incomingFromOffsets:[-s.cellH,-s.cellH*2,-s.cellH*3],rowStartDelays:j(e,s.motionProfile.fallMs,s.motionProfile.outroRowGapMs,s.motionProfile.rowBaseSpacingRatio),incomingStartShift:Math.max(0,s.motionProfile.fallMs-s.motionProfile.outroOverlapMs)}}function H(s){let t=!0,i=!0;for(let e=0;e<s.scriptedOutgoingOffsets.length;e+=1){const n=s.elapsedMs-e*s.motionPlan.columnStaggerMs;for(let r=0;r<d;r+=1){const o=n-s.motionPlan.rowStartDelays[r];if(o<=0){s.scriptedOutgoingOffsets[e][r]=0,s.scriptedIncomingOffsets[e][r]=s.motionPlan.incomingFromOffsets[r],s.scriptedIncomingAlpha[e][r]=0,s.scriptedIncomingVisibility[e][r]="hidden",t=!1,i=!1;continue}const h=G({startMs:0,endMs:s.motionPlan.fallMs,from:0,to:s.motionPlan.outgoingDistance},o);s.scriptedOutgoingOffsets[e][r]=h.value,s.scriptedIncomingVisibility[e][r]=h.done?"entering":"exiting",h.done||(t=!1);const u=o-s.motionPlan.incomingStartShift;if(u<=0){s.scriptedIncomingOffsets[e][r]=s.motionPlan.incomingFromOffsets[r],s.scriptedIncomingAlpha[e][r]=0,s.scriptedIncomingVisibility[e][r]="hidden",i=!1;continue}const c=G({startMs:0,endMs:s.motionPlan.fallMs,from:s.motionPlan.incomingFromOffsets[r],to:0},u);s.scriptedIncomingOffsets[e][r]=c.value,s.scriptedIncomingAlpha[e][r]=x(u/Math.max(1,s.motionPlan.incomingAlphaRampMs),0,1),s.scriptedIncomingVisibility[e][r]=c.done?"active":"entering",c.done||(i=!1)}}return{allOutgoingDone:t,allIncomingDone:i}}function J(s){if(s==="rainbow")return{mode:"rainbow",rgb:y};const t=s??y;return{mode:"solid",rgb:[O(t[0]),O(t[1]),O(t[2])]}}function C(s){if(s.length!==d)throw new Error(`rows must contain ${d} rows`);for(let t=0;t<d;t+=1)if(!Array.isArray(s[t])||s[t].length!==f)throw new Error(`rows[${t}] must contain ${f} columns`);return[[s[0][0],s[1][0],s[2][0]],[s[0][1],s[1][1],s[2][1]],[s[0][2],s[1][2],s[2][2]]]}function v(s,t){if(s.length!==f)throw new Error(`stopGrid must contain ${f} columns`);const i=[];for(let e=0;e<f;e+=1){const n=s[e];if(!Array.isArray(n)||n.length!==d)throw new Error(`stopGrid[${e}] must contain ${d} rows`);i[e]=[E(n[0],t),E(n[1],t),E(n[2],t)]}return i}function tt(s,t){return v(C(s),t)}function it(s){return{stopGrid:s.stopGrid?.map(t=>[...t]),stopRows:s.stopRows?.map(t=>[...t]),finaleSequence:s.finaleSequence?.map(t=>t.map(i=>[...i])),finaleSequenceRows:s.finaleSequenceRows?.map(t=>t.map(i=>[...i])),highlightWin:s.highlightWin,callback:s.callback}}class et{queue;constructor(t){this.queue=(t??[]).map(i=>it(i))}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,winEffectsEnvelope:1}}function rt(s,t){s.hasStartedFirstSpin=!0,s.isSpinning=!0,s.phase="outro",s.outroStartedAt=t.startedAt,s.activeSpinState=t.activeSpinState,s.shouldHighlightCurrentSpin=t.shouldHighlightCurrentSpin}function nt(s,t,i){s.phase="idle",s.idleStartedAt=i,s.isSpinning=!1,s.shouldHighlightCurrentSpin=!1,s.queueFinished=!t,s.activeSpinState=null}function U(s,t){s.winFlashStartedAt=t,s.phase="winFlash"}function ot(s){s.isSpinning=!1,s.queueFinished=!0}const ht=`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});var U=[.04,0,-.04],S={columnStaggerMs:76,fallMs:800,outroOverlapMs:88,outroRowGapMs:14,rowBaseSpacingRatio:.05,incomingAlphaRampMs:34,fixedStepMs:1e3/120,maxCatchUpStepsPerFrame:6},ot=S.outroRowGapMs,lt=S.rowBaseSpacingRatio,v=1800,N=.15,T=[255,235,110],ht=S.columnStaggerMs,at=S.fallMs,ct=S.outroOverlapMs;function M(e,t,i){return e<t?t:e>i?i:e}function q(e){return Math.floor(Math.random()*e)}function b(e,t){return(e%t+t)%t}function R(e){return M(Math.round(e),0,255)}function L(e){const t=[];for(let i=0;i<3;i+=1){const s=[];for(let r=0;r<3;r+=1)s.push(q(e));t.push(s)}return t}function W(e){const t=new Map;for(let n=0;n<3;n+=1)for(let o=0;o<3;o+=1){const l=e[n][o];t.set(l,(t.get(l)??0)+1)}let i=e[0][0],s=-1;for(const[n,o]of t.entries())o>s&&(s=o,i=n);const r=[];for(let n=0;n<3;n+=1)for(let o=0;o<3;o+=1)e[n][o]===i&&r.push({col:n,row:o});return r}function g(){return Array.from({length:3},()=>Array.from({length:3},()=>0))}function m(e,t){for(let i=0;i<3;i+=1)for(let s=0;s<3;s+=1)e[i][s]=t}var z=class{rafId=null;step=null;start(e){this.rafId===null&&(this.step=e,this.rafId=requestAnimationFrame(this.tick))}stop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.step=null}isRunning(){return this.rafId!==null}tick=e=>{if(!this.step){this.stop();return}if(this.step(e)===!1){this.stop();return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.tick))}};function X(e){const t=M(e,0,1);return t*t*t*(t*(t*6-15)+10)}function y(e,t){if(e.endMs<=e.startMs)return{value:e.to,t:1,done:!0};const i=M((t-e.startMs)/(e.endMs-e.startMs),0,1),s=X(i);return{value:e.from+(e.to-e.from)*s,t:i,done:i>=1}}function k(e,t,i,s){const r=[0,0,0];let n=0;for(let o=2;o>=0;o-=1){if(e[o]===0){r[o]=0;continue}r[o]=n;const l=Math.floor(t*s);n+=l+i}return r}function V(e){const t=e.height-e.boardY+e.cellH+2,i=[t,t,t];return{columnStaggerMs:e.motionProfile.columnStaggerMs,fallMs:e.motionProfile.fallMs,incomingAlphaRampMs:e.motionProfile.incomingAlphaRampMs,outgoingDistance:t,incomingFromOffsets:[-e.cellH,-e.cellH*2,-e.cellH*3],rowStartDelays:k(i,e.motionProfile.fallMs,e.motionProfile.outroRowGapMs,e.motionProfile.rowBaseSpacingRatio),incomingStartShift:Math.max(0,e.motionProfile.fallMs-e.motionProfile.outroOverlapMs)}}function D(e){let t=!0,i=!0;for(let s=0;s<e.scriptedOutgoingOffsets.length;s+=1){const r=e.elapsedMs-s*e.motionPlan.columnStaggerMs;for(let n=0;n<3;n+=1){const o=r-e.motionPlan.rowStartDelays[n];if(o<=0){e.scriptedOutgoingOffsets[s][n]=0,e.scriptedIncomingOffsets[s][n]=e.motionPlan.incomingFromOffsets[n],e.scriptedIncomingAlpha[s][n]=0,e.scriptedIncomingVisibility[s][n]="hidden",t=!1,i=!1;continue}const l=y({startMs:0,endMs:e.motionPlan.fallMs,from:0,to:e.motionPlan.outgoingDistance},o);e.scriptedOutgoingOffsets[s][n]=l.value,e.scriptedIncomingVisibility[s][n]=l.done?"entering":"exiting",l.done||(t=!1);const u=o-e.motionPlan.incomingStartShift;if(u<=0){e.scriptedIncomingOffsets[s][n]=e.motionPlan.incomingFromOffsets[n],e.scriptedIncomingAlpha[s][n]=0,e.scriptedIncomingVisibility[s][n]="hidden",i=!1;continue}const c=y({startMs:0,endMs:e.motionPlan.fallMs,from:e.motionPlan.incomingFromOffsets[n],to:0},u);e.scriptedIncomingOffsets[s][n]=c.value,e.scriptedIncomingAlpha[s][n]=M(u/Math.max(1,e.motionPlan.incomingAlphaRampMs),0,1),e.scriptedIncomingVisibility[s][n]=c.done?"active":"entering",c.done||(i=!1)}}return{allOutgoingDone:t,allIncomingDone:i}}function Y(e){if(e==="rainbow")return{mode:"rainbow",rgb:T};const t=e??T;return{mode:"solid",rgb:[R(t[0]),R(t[1]),R(t[2])]}}function $(e,t){return typeof e!="number"||!Number.isFinite(e)?t:M(e,.5,1.2)}function C(e){if(e.length!==3)throw new Error("rows must contain 3 rows");for(let t=0;t<3;t+=1)if(!Array.isArray(e[t])||e[t].length!==3)throw new Error(`rows[${t}] must contain 3 columns`);return[[e[0][0],e[1][0],e[2][0]],[e[0][1],e[1][1],e[2][1]],[e[0][2],e[1][2],e[2][2]]]}function x(e,t){if(e.length!==3)throw new Error("stopGrid must contain 3 columns");const i=[];for(let s=0;s<3;s+=1){const r=e[s];if(!Array.isArray(r)||r.length!==3)throw new Error(`stopGrid[${s}] must contain 3 rows`);i[s]=[b(r[0],t),b(r[1],t),b(r[2],t)]}return i}function K(e,t){return x(C(e),t)}function Q(e){return{stopGrid:e.stopGrid?.map(t=>[...t]),stopRows:e.stopRows?.map(t=>[...t]),finaleSequence:e.finaleSequence?.map(t=>t.map(i=>[...i])),finaleSequenceRows:e.finaleSequenceRows?.map(t=>t.map(i=>[...i])),highlightWin:e.highlightWin,callback:e.callback}}var j=class{queue;constructor(e){this.queue=(e??[]).map(t=>Q(t))}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?null:this.queue.shift()??null}};function Z(){return{isSpinning:!1,hasStartedFirstSpin:!1,queueFinished:!1,shouldHighlightCurrentSpin:!1,activeSpinState:null,phase:"idle",winFlashStartedAt:0,outroStartedAt:0,idleStartedAt:0,preSpinStartedAt:0,winEffectsEnvelope:1}}function J(e,t){e.hasStartedFirstSpin=!0,e.isSpinning=!0,e.phase="outro",e.outroStartedAt=t.startedAt,e.activeSpinState=t.activeSpinState,e.shouldHighlightCurrentSpin=t.shouldHighlightCurrentSpin}function tt(e,t,i){e.phase="idle",e.idleStartedAt=i,e.isSpinning=!1,e.shouldHighlightCurrentSpin=!1,e.queueFinished=!t,e.activeSpinState=null}function F(e,t){e.winFlashStartedAt=t,e.phase="winFlash"}function it(e){e.isSpinning=!1,e.queueFinished=!0}var et=`
2
2
  attribute vec2 a_pos;
3
3
  uniform vec2 u_resolution;
4
4
  uniform mediump vec4 u_destRect;
@@ -20,7 +20,7 @@ void main() {
20
20
  gl_Position = vec4(clip, 0.0, 1.0);
21
21
  v_uv = local;
22
22
  }
23
- `,lt=`
23
+ `,st=`
24
24
  precision mediump float;
25
25
 
26
26
  uniform sampler2D u_texture;
@@ -126,5 +126,6 @@ void main() {
126
126
  gl_FragColor = u_color;
127
127
  }
128
128
  }
129
- `;class at{canvas;spriteImage;spriteElementsCount;gl;program;uniforms;quadBuffer;texture;viewportW=1;viewportH=1;spriteWidth;spriteHeight;spriteSegmentHeight;constructor(t){this.canvas=t.canvas,this.spriteImage=t.spriteImage,this.spriteElementsCount=Math.max(1,t.spriteElementsCount),this.spriteWidth=this.spriteImage.width,this.spriteHeight=this.spriteImage.height,this.spriteSegmentHeight=this.spriteHeight/this.spriteElementsCount;const i=this.canvas.getContext("webgl2",{alpha:!0,antialias:!1})??this.canvas.getContext("webgl",{alpha:!0,antialias:!1});if(!i)throw new Error("WebGL context is not available");this.gl=i;const e=this.createShader(this.gl.VERTEX_SHADER,ht),n=this.createShader(this.gl.FRAGMENT_SHADER,lt);this.program=this.createProgram(e,n),this.gl.deleteShader(e),this.gl.deleteShader(n);const r=this.gl.createBuffer();if(!r)throw new Error("Failed to create WebGL quad buffer");this.quadBuffer=r,this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.quadBuffer),this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]),this.gl.STATIC_DRAW);const o=this.gl.createTexture();if(!o)throw new Error("Failed to create WebGL texture");this.texture=o,this.gl.bindTexture(this.gl.TEXTURE_2D,this.texture),this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,0),this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.gl.RGBA,this.gl.UNSIGNED_BYTE,this.spriteImage),this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,0),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.useProgram(this.program);const h=this.gl.getAttribLocation(this.program,"a_pos");this.gl.enableVertexAttribArray(h),this.gl.vertexAttribPointer(h,2,this.gl.FLOAT,!1,8,0),this.uniforms={resolution:this.gl.getUniformLocation(this.program,"u_resolution"),destRect:this.gl.getUniformLocation(this.program,"u_destRect"),srcRect:this.gl.getUniformLocation(this.program,"u_srcRect"),color:this.gl.getUniformLocation(this.program,"u_color"),useTexture:this.gl.getUniformLocation(this.program,"u_useTexture"),shapeMode:this.gl.getUniformLocation(this.program,"u_shapeMode"),texture:this.gl.getUniformLocation(this.program,"u_texture"),time:this.gl.getUniformLocation(this.program,"u_time"),borderPx:this.gl.getUniformLocation(this.program,"u_borderPx"),borderInsetPx:this.gl.getUniformLocation(this.program,"u_borderInsetPx"),cornerRadiusPx:this.gl.getUniformLocation(this.program,"u_cornerRadiusPx"),noiseAmp:this.gl.getUniformLocation(this.program,"u_noiseAmp"),pulseStrength:this.gl.getUniformLocation(this.program,"u_pulseStrength")},this.gl.uniform1i(this.uniforms.texture,0),this.gl.uniform1f(this.uniforms.time,0),this.gl.uniform1f(this.uniforms.borderPx,1),this.gl.uniform1f(this.uniforms.borderInsetPx,0),this.gl.uniform1f(this.uniforms.cornerRadiusPx,0),this.gl.uniform1f(this.uniforms.noiseAmp,0),this.gl.uniform1f(this.uniforms.pulseStrength,0),this.gl.clearColor(0,0,0,0),this.gl.enable(this.gl.BLEND),this.gl.blendFunc(this.gl.ONE,this.gl.ONE_MINUS_SRC_ALPHA)}resize(t,i){this.viewportW=Math.max(1,Math.floor(t)),this.viewportH=Math.max(1,Math.floor(i)),this.gl.viewport(0,0,this.viewportW,this.viewportH)}beginFrame(){this.gl.useProgram(this.program),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.quadBuffer),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,this.texture),this.gl.uniform2f(this.uniforms.resolution,this.viewportW,this.viewportH),this.gl.uniform1f(this.uniforms.shapeMode,0),this.gl.clear(this.gl.COLOR_BUFFER_BIT)}drawSprite(t,i,e,n,r,o=1){const u=E(t,this.spriteElementsCount)*this.spriteSegmentHeight,c=u+this.spriteSegmentHeight,a=.5,p=a/this.spriteWidth,g=1-a/this.spriteWidth,m=1-(c-a)/this.spriteHeight,_=1-(u+a)/this.spriteHeight;this.gl.uniform4f(this.uniforms.destRect,i,e,n,r),this.gl.uniform4f(this.uniforms.srcRect,p,m,g,_),this.gl.uniform4f(this.uniforms.color,1,1,1,o),this.gl.uniform1f(this.uniforms.shapeMode,0),this.gl.uniform1f(this.uniforms.useTexture,1),this.gl.drawArrays(this.gl.TRIANGLES,0,6)}drawSolidRect(t,i,e,n,r){this.gl.uniform4f(this.uniforms.destRect,t,i,e,n),this.gl.uniform4f(this.uniforms.srcRect,0,0,1,1),this.gl.uniform4f(this.uniforms.color,r[0],r[1],r[2],r[3]),this.gl.uniform1f(this.uniforms.shapeMode,0),this.gl.uniform1f(this.uniforms.useTexture,0),this.gl.drawArrays(this.gl.TRIANGLES,0,6)}drawSoftCircle(t,i,e,n){const r=e*2;this.gl.uniform4f(this.uniforms.destRect,t-e,i-e,r,r),this.gl.uniform4f(this.uniforms.srcRect,0,0,1,1),this.gl.uniform4f(this.uniforms.color,n[0],n[1],n[2],n[3]),this.gl.uniform1f(this.uniforms.useTexture,0),this.gl.uniform1f(this.uniforms.shapeMode,1),this.gl.drawArrays(this.gl.TRIANGLES,0,6),this.gl.uniform1f(this.uniforms.shapeMode,0)}drawElectricBorder(t){this.gl.uniform4f(this.uniforms.destRect,t.x,t.y,t.width,t.height),this.gl.uniform4f(this.uniforms.srcRect,0,0,1,1),this.gl.uniform4f(this.uniforms.color,t.rgba[0],t.rgba[1],t.rgba[2],t.rgba[3]),this.gl.uniform1f(this.uniforms.useTexture,0),this.gl.uniform1f(this.uniforms.shapeMode,2),this.gl.uniform1f(this.uniforms.time,t.timeMs*.001),this.gl.uniform1f(this.uniforms.borderPx,Math.max(.5,t.borderThicknessPx)),this.gl.uniform1f(this.uniforms.borderInsetPx,Math.max(0,t.borderInsetPx)),this.gl.uniform1f(this.uniforms.cornerRadiusPx,Math.max(0,t.cornerRadiusPx)),this.gl.uniform1f(this.uniforms.noiseAmp,Math.max(0,t.noiseAmplitudePx)),this.gl.uniform1f(this.uniforms.pulseStrength,Math.max(0,t.pulseStrength)),this.gl.drawArrays(this.gl.TRIANGLES,0,6),this.gl.uniform1f(this.uniforms.shapeMode,0)}beginAdditiveBlend(){this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE)}endAdditiveBlend(){this.gl.blendFunc(this.gl.ONE,this.gl.ONE_MINUS_SRC_ALPHA)}dispose(){this.gl.deleteTexture(this.texture),this.gl.deleteBuffer(this.quadBuffer),this.gl.deleteProgram(this.program)}createShader(t,i){const e=this.gl.createShader(t);if(!e)throw new Error("Failed to create WebGL shader");if(this.gl.shaderSource(e,i),this.gl.compileShader(e),!this.gl.getShaderParameter(e,this.gl.COMPILE_STATUS)){const n=this.gl.getShaderInfoLog(e)??"unknown error";throw this.gl.deleteShader(e),new Error(`WebGL shader compile failed: ${n}`)}return e}createProgram(t,i){const e=this.gl.createProgram();if(!e)throw new Error("Failed to create WebGL program");if(this.gl.attachShader(e,t),this.gl.attachShader(e,i),this.gl.linkProgram(e),!this.gl.getProgramParameter(e,this.gl.LINK_STATUS)){const n=this.gl.getProgramInfoLog(e)??"unknown error";throw this.gl.deleteProgram(e),new Error(`WebGL program link failed: ${n}`)}return e}}class l{static RAINBOW_HUE_BUCKETS=24;static PARTICLE_GLOBAL_ALPHA=.9;static PARTICLE_MAX_DISTANCE=.72;static PARTICLE_BASE_RADIUS=.028;canvas;container;button;spinQueueController;spriteUrl;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;motionProfile;isCoarsePointerDevice;spriteImage=null;webglRenderer=null;rafLoop=new K;runtime=st();width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutroElapsedMs=0;outroMotionPlan=null;scriptedOutgoingOffsets=S();scriptedOutgoingOffsetsPrev=S();scriptedIncomingOffsets=S();scriptedIncomingOffsetsPrev=S();scriptedIncomingAlpha=S();scriptedIncomingAlphaPrev=S();scriptedIncomingVisibility=l.createVisibilityGrid("hidden");scriptedIncomingVisibilityPrev=l.createVisibilityGrid("hidden");winningCells=[];winningCellKeys=new Set;grid;particlesPerCell=D;lastRafTime=0;initialHighlightRequestedAt=0;simulationLastNow=0;simulationAccumulatorMs=0;outroInterpolationAlpha=1;isOutroPipelineWarmedUp=!1;isGpuPipelineWarmedUp=!1;perfWindowStartedAt=0;perfFrameCount=0;perfOver20MsCount=0;perfDtSamples=[];mobileDprCap=2;mobilePerfGoodWindows=0;mobilePerfBadWindows=0;static PERF_WINDOW_MS=1500;static PERF_BAD_P95_DT_MS=19;static PERF_GOOD_P95_DT_MS=17.5;static PERF_BAD_SLOW_RATIO=.03;static MOBILE_BAD_WINDOWS_TO_DECREASE_DPR=2;static MOBILE_GOOD_WINDOWS_TO_INCREASE_DPR=4;static MOBILE_DPR_CAP_MIN=1.25;static MOBILE_DPR_CAP_MAX=1.5;static MOBILE_DPR_STEP_DOWN=.1;static MOBILE_DPR_STEP_UP=.05;static DPR_QUANT_STEP=.25;static MAX_CANVAS_AREA_PX_COARSE=900*900;static MAX_CANVAS_AREA_PX_FINE=1400*1400;static PRE_SPIN_MS=150;static WIN_EFFECTS_ENVELOPE_TAU_MS=120;static MAX_FRAME_DELTA_MS=100;static WIN_BORDER_ALPHA=.72;static WIN_BORDER_INSET_RATIO=.08;static particleSeedsCache=new Map;constructor(t){this.canvas=t.canvas,this.container=t.container,this.button=t.button,this.spriteUrl=t.sprite,this.spriteElementsCount=Math.max(1,t.spriteElementsCount??k),this.highlightInitialWinningCells=t.highlightInitialWinningCells!==!1,this.spinQueueController=new et(t.queuedSpinStates);const i=J(t.particleColor);this.particleColorRgb=i.rgb,this.particleColorMode=i.mode,this.motionProfile=A,this.isCoarsePointerDevice=l.detectCoarsePointerDevice(),this.mobileDprCap=this.isCoarsePointerDevice?l.MOBILE_DPR_CAP_MAX:2,this.grid=t.initialSegments?tt(t.initialSegments,this.spriteElementsCount):F(this.spriteElementsCount)}async init(){if(this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),!this.spriteImage)throw new Error("sprite is required for WebGL renderer");this.webglRenderer=new at({canvas:this.canvas,spriteImage:this.spriteImage,spriteElementsCount:this.spriteElementsCount}),this.webglRenderer.resize(this.width,this.height),this.warmUpOutroPipeline(),this.warmUpGpuPipeline(),this.applyInitialHighlightIfNeeded(),requestAnimationFrame(t=>{this.render(t),this.startLoop()})}destroy(){this.unbindEvents(),this.rafLoop.stop(),this.simulationLastNow=0,this.simulationAccumulatorMs=0,ot(this.runtime),this.webglRenderer?.dispose(),this.webglRenderer=null,this.clearWinningCells()}spin(){if(this.dismissHighlightIfActive(),this.runtime.isSpinning||this.runtime.queueFinished)return;if(!this.spinQueueController.hasPending()){this.runtime.queueFinished=!0,this.button&&(this.button.disabled=!0);return}const t=this.spinQueueController.consume(),i=t?.highlightWin===!0;this.runtime.isSpinning=!0,this.runtime.phase="preSpin",this.runtime.preSpinStartedAt=performance.now(),this.runtime.activeSpinState=t,this.runtime.shouldHighlightCurrentSpin=i,this.runtime.hasStartedFirstSpin=!0,this.simulationLastNow=0,this.simulationAccumulatorMs=0,this.button&&(this.button.disabled=!0),this.startLoop()}bindEvents(){window.addEventListener("resize",this.resize),document.addEventListener("visibilitychange",this.onVisibilityChange),this.button?.addEventListener("click",this.onSpinClick)}unbindEvents(){window.removeEventListener("resize",this.resize),document.removeEventListener("visibilitychange",this.onVisibilityChange),this.button?.removeEventListener("click",this.onSpinClick)}onVisibilityChange=()=>{this.lastRafTime=0,this.simulationLastNow=0,this.simulationAccumulatorMs=0,this.resetPerfWindow()};onSpinClick=()=>{this.runtime.isSpinning&&this.runtime.phase!=="winFlash"||this.spin()};getNextGrid(t){return t?v(t,this.spriteElementsCount):F(this.spriteElementsCount)}update(t){if(this.runtime.isSpinning){if(this.runtime.phase==="preSpin"){if(t-this.runtime.preSpinStartedAt<l.PRE_SPIN_MS)return;rt(this.runtime,{activeSpinState:this.runtime.activeSpinState,shouldHighlightCurrentSpin:this.runtime.shouldHighlightCurrentSpin,startedAt:t}),this.runtime.preSpinStartedAt=0;const i=this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(r=>C(r)):this.runtime.activeSpinState?.finaleSequence??[];this.scriptedCascadeQueue=i.map(r=>r.map(o=>[...o])),this.clearWinningCells();const e=this.runtime.activeSpinState?.stopRows?C(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,n=this.getNextGrid(e);this.startOutroTransition(n,t)}this.runtime.phase}}stepScriptedOutro(t){if(!this.scriptedOutgoingGrid||!this.scriptedPendingGrid){this.finishSpinWithUi();return}this.outroMotionPlan||(this.outroMotionPlan=this.createOutroMotionPlan()),this.scriptedOutroElapsedMs+=t;const{allOutgoingDone:i,allIncomingDone:e}=H({elapsedMs:this.scriptedOutroElapsedMs,scriptedOutgoingOffsets:this.scriptedOutgoingOffsets,scriptedIncomingOffsets:this.scriptedIncomingOffsets,scriptedIncomingAlpha:this.scriptedIncomingAlpha,scriptedIncomingVisibility:this.scriptedIncomingVisibility,motionPlan:this.outroMotionPlan});if(!(!i||!e)&&(this.grid=this.scriptedPendingGrid,this.scriptedOutgoingGrid=null,this.scriptedPendingGrid=null,this.outroMotionPlan=null,this.clearWinningCells(),this.resetOutroBuffers(1,"active"),!this.tryStartScriptedCascade(this.scriptedOutroStartedAt+this.scriptedOutroElapsedMs))){if(!this.runtime.shouldHighlightCurrentSpin){this.finishSpinWithUi();return}this.setWinningCells(B(this.grid)),U(this.runtime,this.scriptedOutroStartedAt+this.scriptedOutroElapsedMs),this.runtime.activeSpinState?.callback?.(),this.button&&this.runtime.shouldHighlightCurrentSpin&&this.spinQueueController.hasPending()&&(this.button.disabled=!1)}}tryStartScriptedCascade(t){if(this.scriptedCascadeQueue.length===0)return!1;const i=this.scriptedCascadeQueue.shift();return i?(this.startOutroTransition(v(i,this.spriteElementsCount),t),!0):!1}startOutroTransition(t,i){this.scriptedOutgoingGrid=this.grid.map(e=>[...e]),this.scriptedPendingGrid=t.map(e=>[...e]),this.outroMotionPlan=this.createOutroMotionPlan(),this.resetOutroBuffers(0,"hidden"),this.clearWinningCells(),this.runtime.phase="outro",this.scriptedOutroStartedAt=i,this.scriptedOutroElapsedMs=0,this.outroInterpolationAlpha=1}finishSpinWithUi(t=!1){const i=this.runtime.activeSpinState,e=t?void 0:i?.callback,n=this.spinQueueController.hasPending();nt(this.runtime,n,performance.now()),this.button&&(this.button.disabled=this.runtime.queueFinished),e?.()}dismissHighlightIfActive(){this.runtime.phase==="winFlash"&&this.finishSpinWithUi(!0)}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.setWinningCells(B(this.grid)),this.initialHighlightRequestedAt=performance.now())}warmUpOutroPipeline(){if(this.isOutroPipelineWarmedUp)return;const t=this.createOutroMotionPlan(),i=S(),e=S(),n=S(),r=l.createVisibilityGrid("hidden"),o=Math.max(...t.rowStartDelays),h=(f-1)*t.columnStaggerMs,u=[0,this.motionProfile.fixedStepMs,t.incomingStartShift+this.motionProfile.fixedStepMs,t.fallMs+o+h+this.motionProfile.fixedStepMs];for(const c of u)H({elapsedMs:c,scriptedOutgoingOffsets:i,scriptedIncomingOffsets:e,scriptedIncomingAlpha:n,scriptedIncomingVisibility:r,motionPlan:t});this.isOutroPipelineWarmedUp=!0}createOutroMotionPlan(){const t=Z({height:this.height,boardY:this.boardY,cellH:this.cellH,motionProfile:this.motionProfile});if(!this.isCoarsePointerDevice)return t;const i=1e3/60;return{...t,columnStaggerMs:this.quantizeMs(t.columnStaggerMs,i),incomingStartShift:this.quantizeMs(t.incomingStartShift,i),rowStartDelays:[this.quantizeMs(t.rowStartDelays[0],i),this.quantizeMs(t.rowStartDelays[1],i),this.quantizeMs(t.rowStartDelays[2],i)]}}quantizeMs(t,i){return t<=0?0:Math.max(1,Math.round(t/i))*i}warmUpGpuPipeline(){if(this.isGpuPipelineWarmedUp)return;const t=this.webglRenderer;if(!t)return;const i=Math.max(8,this.cellW*.2),e=Math.max(8,this.cellH*.2),n=this.boardX+2,r=this.boardY+2;t.beginFrame(),t.drawSprite(0,n,r,i,e,1),t.drawSolidRect(n+i+2,r,i,e,[1,1,1,.35]),t.beginAdditiveBlend(),t.drawSoftCircle(n+i*.5,r+e*.5,Math.max(2,i*.25),[1,.9,.4,.4]),t.endAdditiveBlend(),this.isGpuPipelineWarmedUp=!0}trackOutroPerf(t,i){if(this.runtime.phase!=="outro"){this.resetPerfWindow();return}this.perfWindowStartedAt<=0&&(this.perfWindowStartedAt=i),this.perfFrameCount+=1,t>20&&(this.perfOver20MsCount+=1),this.perfDtSamples.push(t),!(i-this.perfWindowStartedAt<l.PERF_WINDOW_MS)&&(this.flushPerfWindow(),this.perfWindowStartedAt=i)}resetPerfWindow(){this.perfWindowStartedAt=0,this.perfFrameCount=0,this.perfOver20MsCount=0,this.perfDtSamples.length=0}flushPerfWindow(){if(this.perfFrameCount===0)return;const t=[...this.perfDtSamples].sort((n,r)=>n-r),i=Math.max(0,Math.min(t.length-1,Math.floor(t.length*.95))),e=t[i];this.adjustMobileDprCap(e,this.perfOver20MsCount,this.perfFrameCount),this.perfFrameCount=0,this.perfOver20MsCount=0,this.perfDtSamples.length=0}adjustMobileDprCap(t,i,e){if(!this.isCoarsePointerDevice||e<=0)return;const n=i/e,r=t>l.PERF_BAD_P95_DT_MS||n>l.PERF_BAD_SLOW_RATIO,o=t<=l.PERF_GOOD_P95_DT_MS&&i===0;let h=this.mobileDprCap;r?(this.mobilePerfBadWindows+=1,this.mobilePerfGoodWindows=0,this.mobilePerfBadWindows>=l.MOBILE_BAD_WINDOWS_TO_DECREASE_DPR&&(h=Math.max(l.MOBILE_DPR_CAP_MIN,this.mobileDprCap-l.MOBILE_DPR_STEP_DOWN),this.mobilePerfBadWindows=0)):o?(this.mobilePerfGoodWindows+=1,this.mobilePerfBadWindows=0,this.mobilePerfGoodWindows>=l.MOBILE_GOOD_WINDOWS_TO_INCREASE_DPR&&(h=Math.min(l.MOBILE_DPR_CAP_MAX,this.mobileDprCap+l.MOBILE_DPR_STEP_UP),this.mobilePerfGoodWindows=0)):(this.mobilePerfGoodWindows=0,this.mobilePerfBadWindows=0),!(Math.abs(h-this.mobileDprCap)<.001)&&(this.mobileDprCap=h,this.resize())}render(t){if(!this.webglRenderer)return;this.initialHighlightRequestedAt>0&&t-this.initialHighlightRequestedAt>=Y&&(U(this.runtime,this.initialHighlightRequestedAt),this.runtime.winEffectsEnvelope=1,this.initialHighlightRequestedAt=0,this.button&&(this.button.disabled=!1));const i=this.runtime.phase==="preSpin"?0:this.runtime.phase==="winFlash"?1:0;if(this.lastRafTime>0){const h=Math.min(t-this.lastRafTime,50);this.trackOutroPerf(h,t);const u=1-Math.exp(-h/l.WIN_EFFECTS_ENVELOPE_TAU_MS);this.runtime.winEffectsEnvelope+=(i-this.runtime.winEffectsEnvelope)*u}else this.resetPerfWindow();this.lastRafTime=t,this.webglRenderer.beginFrame();const e=this.runtime.phase==="winFlash"||this.runtime.phase==="preSpin"||this.initialHighlightRequestedAt>0&&this.winningCells.length>0;this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawGridInterpolated(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsetsPrev,this.scriptedOutgoingOffsets,this.outroInterpolationAlpha,e),this.drawGridInterpolated(this.scriptedPendingGrid,this.scriptedIncomingOffsetsPrev,this.scriptedIncomingOffsets,this.outroInterpolationAlpha,e,this.scriptedIncomingAlphaPrev,this.scriptedIncomingAlpha,this.scriptedIncomingVisibility)):this.drawGrid(this.grid,null,e);const n=this.initialHighlightRequestedAt>0?"winFlash":this.runtime.phase,r=this.initialHighlightRequestedAt>0?this.initialHighlightRequestedAt:this.runtime.winFlashStartedAt,o=this.initialHighlightRequestedAt>0?1:this.runtime.winEffectsEnvelope;this.drawWinningEffects({now:t,phase:n,winFlashStartedAt:r,winEffectsEnvelope:o})}isWinningCell=(t,i)=>this.winningCellKeys.has(`${t}:${i}`);getRowCompactOffset(t){return(X[t]??0)*this.cellH}applyPixelSnapY(t){return this.isCoarsePointerDevice?Math.round(t*2)/2:t}drawGrid(t,i,e){this.drawGridWithSampler({grid:t,skipWinningCells:e,sampleOffsetY:(n,r)=>i?i[n][r]:0,sampleAlpha:()=>1,isVisible:()=>!0})}drawGridInterpolated(t,i,e,n,r,o,h,u){this.drawGridWithSampler({grid:t,skipWinningCells:r,sampleOffsetY:(c,a)=>i[c][a]+(e[c][a]-i[c][a])*n,sampleAlpha:(c,a)=>o&&h?o[c][a]+(h[c][a]-o[c][a])*n:1,isVisible:(c,a)=>!u||u[c][a]!=="hidden"})}drawGridWithSampler(t){const i=this.webglRenderer;if(i)for(let e=0;e<f;e+=1){const n=this.boardX+e*this.cellW;for(let r=0;r<d;r+=1){if(t.skipWinningCells&&this.isWinningCell(e,r)||!t.isVisible(e,r))continue;const o=t.sampleOffsetY(e,r),h=this.applyPixelSnapY(this.boardY+r*this.cellH+o+this.getRowCompactOffset(r));if(h>this.height||h+this.cellH<0)continue;const u=t.sampleAlpha(e,r);u<=0||i.drawSprite(t.grid[e][r],n,h,this.cellW,this.cellH,u)}}}drawWinningEffects(t){const i=this.webglRenderer;if(!i||this.winningCells.length===0||t.phase!=="winFlash"&&t.phase!=="preSpin")return;const e=Math.max(0,Math.min(1,t.winEffectsEnvelope)),n=Math.max(0,t.now-t.winFlashStartedAt),r=n%L/L,o=1+Math.sin(r*Math.PI*2)*V*e,h=Math.max(1,this.cellW*l.WIN_BORDER_INSET_RATIO),u=Math.max(1,this.cellW*.022),c=this.runtime.phase==="winFlash"&&this.runtime.shouldHighlightCurrentSpin&&this.runtime.hasStartedFirstSpin;for(const a of this.winningCells){const p=this.boardX+a.col*this.cellW,g=this.boardY+a.row*this.cellH+this.getRowCompactOffset(a.row),m=this.grid[a.col][a.row],_=l.WIN_BORDER_ALPHA*e,R=this.particleColorMode==="rainbow"?l.hslToRgb01((n*.2+a.col*36+a.row*22)%360,.96,.64):[this.particleColorRgb[0]/255,this.particleColorRgb[1]/255,this.particleColorRgb[2]/255],w=this.cellW*o,M=this.cellH*o,b=(this.cellW-w)*.5,N=(this.cellH-M)*.5;i.drawSprite(m,p+b,g+N,w,M,1);const q=p+h,z=g+h,T=this.cellW-h*2,W=this.cellH-h*2;T<=u*2||W<=u*2||(this.drawElectricBorder({renderer:i,cell:a,x:q,y:z,w:T,h:W,borderThickness:u,borderColor:R,alpha:_,elapsed:n,envelope:e}),c&&this.drawCellParticleBurst({renderer:i,cell:a,centerX:p+this.cellW*.5,centerY:g+this.cellH*.5,elapsed:n,envelope:e}))}}drawCellParticleBurst(t){const i=Math.min(this.cellW,this.cellH)*l.PARTICLE_MAX_DISTANCE,e=Math.min(this.cellW,this.cellH)*l.PARTICLE_BASE_RADIUS,n=l.getParticleSeeds(t.cell.col,t.cell.row),r=[this.particleColorRgb[0]/255,this.particleColorRgb[1]/255,this.particleColorRgb[2]/255];t.renderer.beginAdditiveBlend();for(let o=0;o<this.particlesPerCell;o+=1){const h=n[o],u=h.phaseOffset*I,c=t.elapsed-u;if(c<0)continue;const a=c%I/I,p=h.seedA*Math.PI*2,g=i*a*(.35+h.seedB*.65),m=t.centerX+Math.cos(p)*g,_=t.centerY+Math.sin(p)*g,R=.7+.9*Math.max(0,Math.sin((t.elapsed*.012+h.twinkleSeed*2)*Math.PI*2)),w=Math.max(1,e*(.55+h.seedC*.6)*(1-a*.5)),M=Math.max(0,Math.min(1,(.9+R*.2)*l.PARTICLE_GLOBAL_ALPHA*t.envelope));if(M<=0)continue;const b=this.particleColorMode==="rainbow"?l.getRainbowParticleColor(t.elapsed,t.cell.col,t.cell.row,h.seedA):r;t.renderer.drawSoftCircle(m,_,w,[b[0],b[1],b[2],M])}t.renderer.endAdditiveBlend()}drawElectricBorder(t){const i=Math.max(0,Math.min(1,t.alpha*t.envelope)),e=t.borderThickness*1.8,n=t.elapsed+t.cell.col*170+t.cell.row*290;t.renderer.beginAdditiveBlend(),t.renderer.drawElectricBorder({x:t.x-e,y:t.y-e,width:t.w+e*2,height:t.h+e*2,rgba:[t.borderColor[0],t.borderColor[1],t.borderColor[2],i*.55],timeMs:n,borderThicknessPx:Math.max(.8,t.borderThickness*1.1),borderInsetPx:e*.85,cornerRadiusPx:Math.max(2,t.borderThickness*2.5),noiseAmplitudePx:Math.max(.15,t.borderThickness*.6),pulseStrength:.9}),t.renderer.drawElectricBorder({x:t.x-e*.5,y:t.y-e*.5,width:t.w+e,height:t.h+e,rgba:[t.borderColor[0],t.borderColor[1],t.borderColor[2],i*.9],timeMs:n*1.03,borderThicknessPx:Math.max(.7,t.borderThickness*.8),borderInsetPx:e*.5,cornerRadiusPx:Math.max(1.5,t.borderThickness*1.7),noiseAmplitudePx:Math.max(.12,t.borderThickness*.42),pulseStrength:1.25}),t.renderer.endAdditiveBlend()}static hslToRgb01(t,i,e){const n=(t%360+360)%360,r=Math.max(0,Math.min(1,i)),o=Math.max(0,Math.min(1,e)),h=(1-Math.abs(2*o-1))*r,u=n/60,c=h*(1-Math.abs(u%2-1));let a=0,p=0,g=0;u>=0&&u<1?(a=h,p=c):u<2?(a=c,p=h):u<3?(p=h,g=c):u<4?(p=c,g=h):u<5?(a=c,g=h):(a=h,g=c);const m=o-h*.5;return[a+m,p+m,g+m]}static getRainbowParticleColor(t,i,e,n){const r=(n*360+t*.24+i*38+e*22)%360,o=360/l.RAINBOW_HUE_BUCKETS,h=Math.floor(r/o)*o;return l.hslToRgb01(h,.98,.64)}static detectCoarsePointerDevice(){if(typeof window>"u")return!1;const t=typeof window.matchMedia=="function"&&window.matchMedia("(pointer: coarse)").matches,i=typeof navigator<"u"&&typeof navigator.maxTouchPoints=="number"?navigator.maxTouchPoints:0;return t||i>0}quantizeDprCap(t){const i=l.DPR_QUANT_STEP;return Math.max(1,Math.round(t/i)*i)}static hash01(t,i,e,n){const r=Math.sin(t*127.1+i*311.7+e*74.7+n*19.3)*43758.5453;return r-Math.floor(r)}static getParticleSeeds(t,i){const e=`${t},${i}`,n=l.particleSeedsCache.get(e);if(n)return n;const r=[];for(let o=0;o<D;o+=1)r.push({seedA:l.hash01(t,i,o,1),seedB:l.hash01(t,i,o,2),seedC:l.hash01(t,i,o,3),phaseOffset:l.hash01(t,i,o,4),twinkleSeed:l.hash01(t,i,o,5)});return l.particleSeedsCache.set(e,r),r}clearWinningCells(){this.winningCells=[],this.winningCellKeys.clear()}setWinningCells(t){this.winningCells=t,this.winningCellKeys.clear();for(const i of t)this.winningCellKeys.add(`${i.col}:${i.row}`)}resize=()=>{const t=this.container.getBoundingClientRect(),i=Math.max(300,Math.floor(t.width)),e=Math.max(1,i*i),n=this.isCoarsePointerDevice?l.MAX_CANVAS_AREA_PX_COARSE:l.MAX_CANVAS_AREA_PX_FINE,r=Math.max(1,Math.sqrt(n/e)),o=this.isCoarsePointerDevice?this.mobileDprCap:2,h=this.quantizeDprCap(Math.min(o,r)),u=Math.max(1,Math.min(window.devicePixelRatio||1,h)),c=Math.max(300,Math.floor(i*u));this.width=c,this.height=c;const a=Math.floor(Math.min(this.width/f,this.height/d));this.cellW=a,this.cellH=a,this.boardX=Math.floor((this.width-this.cellW*f)/2),this.boardY=Math.floor((this.height-this.cellH*d)/2),this.canvas.width=this.width,this.canvas.height=this.height,this.webglRenderer?.resize(this.width,this.height)};async loadSpriteIfProvided(){if(!this.spriteUrl)return;const t=new Image;t.decoding="async",t.src=this.spriteUrl;try{await t.decode(),this.spriteImage=t}catch{this.spriteImage=null}}advanceSimulation(t){if(this.simulationLastNow<=0){this.simulationLastNow=t,this.update(t);return}const i=Math.max(0,Math.min(t-this.simulationLastNow,l.MAX_FRAME_DELTA_MS));if(this.simulationLastNow=t,this.update(t),this.runtime.phase!=="outro"){this.outroInterpolationAlpha=1;return}this.advanceOutroFixedStep(i)}advanceOutroFixedStep(t){this.simulationAccumulatorMs+=t;const i=this.motionProfile.fixedStepMs;let e=0;for(;this.simulationAccumulatorMs>=i&&e<this.motionProfile.maxCatchUpStepsPerFrame&&this.runtime.phase==="outro";)this.snapshotOutroState(),this.stepScriptedOutro(i),this.simulationAccumulatorMs-=i,e+=1;if(this.runtime.phase!=="outro"){this.outroInterpolationAlpha=1;return}this.outroInterpolationAlpha=Math.max(0,Math.min(1,this.simulationAccumulatorMs/i))}snapshotOutroState(){this.copyOffsets(this.scriptedOutgoingOffsetsPrev,this.scriptedOutgoingOffsets),this.copyOffsets(this.scriptedIncomingOffsetsPrev,this.scriptedIncomingOffsets),this.copyOffsets(this.scriptedIncomingAlphaPrev,this.scriptedIncomingAlpha),this.copyVisibility(this.scriptedIncomingVisibilityPrev,this.scriptedIncomingVisibility)}copyOffsets(t,i){for(let e=0;e<f;e+=1)for(let n=0;n<d;n+=1)t[e][n]=i[e][n]}copyVisibility(t,i){for(let e=0;e<f;e+=1)for(let n=0;n<d;n+=1)t[e][n]=i[e][n]}resetOutroBuffers(t,i){P(this.scriptedOutgoingOffsets,0),P(this.scriptedIncomingOffsets,0),P(this.scriptedOutgoingOffsetsPrev,0),P(this.scriptedIncomingOffsetsPrev,0),P(this.scriptedIncomingAlpha,t),P(this.scriptedIncomingAlphaPrev,t),this.fillVisibilityGrid(this.scriptedIncomingVisibility,i),this.fillVisibilityGrid(this.scriptedIncomingVisibilityPrev,i)}static createVisibilityGrid(t){return Array.from({length:f},()=>Array.from({length:d},()=>t))}fillVisibilityGrid(t,i){for(let e=0;e<f;e+=1)for(let n=0;n<d;n+=1)t[e][n]=i}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(t=>(this.advanceSimulation(t),this.render(t),this.shouldKeepAnimating()))}shouldKeepAnimating(){return this.runtime.isSpinning||this.initialHighlightRequestedAt>0?!0:this.runtime.winEffectsEnvelope>.001}}exports.CascadingReel=l;
130
- //# sourceMappingURL=index.cjs.js.map
129
+ `,rt=class{canvas;spriteImage;spriteElementsCount;gl;program;uniforms;quadBuffer;texture;viewportW=1;viewportH=1;spriteWidth;spriteHeight;spriteSegmentHeight;constructor(e){this.canvas=e.canvas,this.spriteImage=e.spriteImage,this.spriteElementsCount=Math.max(1,e.spriteElementsCount),this.spriteWidth=this.spriteImage.width,this.spriteHeight=this.spriteImage.height,this.spriteSegmentHeight=this.spriteHeight/this.spriteElementsCount;const t=this.canvas.getContext("webgl2",{alpha:!0,antialias:!1})??this.canvas.getContext("webgl",{alpha:!0,antialias:!1});if(!t)throw new Error("WebGL context is not available");this.gl=t;const i=this.createShader(this.gl.VERTEX_SHADER,et),s=this.createShader(this.gl.FRAGMENT_SHADER,st);this.program=this.createProgram(i,s),this.gl.deleteShader(i),this.gl.deleteShader(s);const r=this.gl.createBuffer();if(!r)throw new Error("Failed to create WebGL quad buffer");this.quadBuffer=r,this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.quadBuffer),this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]),this.gl.STATIC_DRAW);const n=this.gl.createTexture();if(!n)throw new Error("Failed to create WebGL texture");this.texture=n,this.gl.bindTexture(this.gl.TEXTURE_2D,this.texture),this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL,0),this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,1),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,this.gl.RGBA,this.gl.UNSIGNED_BYTE,this.spriteImage),this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,0),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.LINEAR),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE),this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE),this.gl.useProgram(this.program);const o=this.gl.getAttribLocation(this.program,"a_pos");this.gl.enableVertexAttribArray(o),this.gl.vertexAttribPointer(o,2,this.gl.FLOAT,!1,8,0),this.uniforms={resolution:this.gl.getUniformLocation(this.program,"u_resolution"),destRect:this.gl.getUniformLocation(this.program,"u_destRect"),srcRect:this.gl.getUniformLocation(this.program,"u_srcRect"),color:this.gl.getUniformLocation(this.program,"u_color"),useTexture:this.gl.getUniformLocation(this.program,"u_useTexture"),shapeMode:this.gl.getUniformLocation(this.program,"u_shapeMode"),texture:this.gl.getUniformLocation(this.program,"u_texture"),time:this.gl.getUniformLocation(this.program,"u_time"),borderPx:this.gl.getUniformLocation(this.program,"u_borderPx"),borderInsetPx:this.gl.getUniformLocation(this.program,"u_borderInsetPx"),cornerRadiusPx:this.gl.getUniformLocation(this.program,"u_cornerRadiusPx"),noiseAmp:this.gl.getUniformLocation(this.program,"u_noiseAmp"),pulseStrength:this.gl.getUniformLocation(this.program,"u_pulseStrength")},this.gl.uniform1i(this.uniforms.texture,0),this.gl.uniform1f(this.uniforms.time,0),this.gl.uniform1f(this.uniforms.borderPx,1),this.gl.uniform1f(this.uniforms.borderInsetPx,0),this.gl.uniform1f(this.uniforms.cornerRadiusPx,0),this.gl.uniform1f(this.uniforms.noiseAmp,0),this.gl.uniform1f(this.uniforms.pulseStrength,0),this.gl.clearColor(0,0,0,0),this.gl.enable(this.gl.BLEND),this.gl.blendFunc(this.gl.ONE,this.gl.ONE_MINUS_SRC_ALPHA)}resize(e,t){this.viewportW=Math.max(1,Math.floor(e)),this.viewportH=Math.max(1,Math.floor(t)),this.gl.viewport(0,0,this.viewportW,this.viewportH)}beginFrame(){this.gl.useProgram(this.program),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.quadBuffer),this.gl.activeTexture(this.gl.TEXTURE0),this.gl.bindTexture(this.gl.TEXTURE_2D,this.texture),this.gl.uniform2f(this.uniforms.resolution,this.viewportW,this.viewportH),this.gl.uniform1f(this.uniforms.shapeMode,0),this.gl.clear(this.gl.COLOR_BUFFER_BIT)}drawSprite(e,t,i,s,r,n=1){const o=b(e,this.spriteElementsCount)*this.spriteSegmentHeight,l=o+this.spriteSegmentHeight,u=.5,c=u/this.spriteWidth,a=1-u/this.spriteWidth,d=1-(l-u)/this.spriteHeight,f=1-(o+u)/this.spriteHeight;this.gl.uniform4f(this.uniforms.destRect,t,i,s,r),this.gl.uniform4f(this.uniforms.srcRect,c,d,a,f),this.gl.uniform4f(this.uniforms.color,1,1,1,n),this.gl.uniform1f(this.uniforms.shapeMode,0),this.gl.uniform1f(this.uniforms.useTexture,1),this.gl.drawArrays(this.gl.TRIANGLES,0,6)}drawSolidRect(e,t,i,s,r){this.gl.uniform4f(this.uniforms.destRect,e,t,i,s),this.gl.uniform4f(this.uniforms.srcRect,0,0,1,1),this.gl.uniform4f(this.uniforms.color,r[0],r[1],r[2],r[3]),this.gl.uniform1f(this.uniforms.shapeMode,0),this.gl.uniform1f(this.uniforms.useTexture,0),this.gl.drawArrays(this.gl.TRIANGLES,0,6)}drawSoftCircle(e,t,i,s){const r=i*2;this.gl.uniform4f(this.uniforms.destRect,e-i,t-i,r,r),this.gl.uniform4f(this.uniforms.srcRect,0,0,1,1),this.gl.uniform4f(this.uniforms.color,s[0],s[1],s[2],s[3]),this.gl.uniform1f(this.uniforms.useTexture,0),this.gl.uniform1f(this.uniforms.shapeMode,1),this.gl.drawArrays(this.gl.TRIANGLES,0,6),this.gl.uniform1f(this.uniforms.shapeMode,0)}drawElectricBorder(e){this.gl.uniform4f(this.uniforms.destRect,e.x,e.y,e.width,e.height),this.gl.uniform4f(this.uniforms.srcRect,0,0,1,1),this.gl.uniform4f(this.uniforms.color,e.rgba[0],e.rgba[1],e.rgba[2],e.rgba[3]),this.gl.uniform1f(this.uniforms.useTexture,0),this.gl.uniform1f(this.uniforms.shapeMode,2),this.gl.uniform1f(this.uniforms.time,e.timeMs*.001),this.gl.uniform1f(this.uniforms.borderPx,Math.max(.5,e.borderThicknessPx)),this.gl.uniform1f(this.uniforms.borderInsetPx,Math.max(0,e.borderInsetPx)),this.gl.uniform1f(this.uniforms.cornerRadiusPx,Math.max(0,e.cornerRadiusPx)),this.gl.uniform1f(this.uniforms.noiseAmp,Math.max(0,e.noiseAmplitudePx)),this.gl.uniform1f(this.uniforms.pulseStrength,Math.max(0,e.pulseStrength)),this.gl.drawArrays(this.gl.TRIANGLES,0,6),this.gl.uniform1f(this.uniforms.shapeMode,0)}beginAdditiveBlend(){this.gl.blendFunc(this.gl.SRC_ALPHA,this.gl.ONE)}endAdditiveBlend(){this.gl.blendFunc(this.gl.ONE,this.gl.ONE_MINUS_SRC_ALPHA)}dispose(){this.gl.deleteTexture(this.texture),this.gl.deleteBuffer(this.quadBuffer),this.gl.deleteProgram(this.program)}createShader(e,t){const i=this.gl.createShader(e);if(!i)throw new Error("Failed to create WebGL shader");if(this.gl.shaderSource(i,t),this.gl.compileShader(i),!this.gl.getShaderParameter(i,this.gl.COMPILE_STATUS)){const s=this.gl.getShaderInfoLog(i)??"unknown error";throw this.gl.deleteShader(i),new Error(`WebGL shader compile failed: ${s}`)}return i}createProgram(e,t){const i=this.gl.createProgram();if(!i)throw new Error("Failed to create WebGL program");if(this.gl.attachShader(i,e),this.gl.attachShader(i,t),this.gl.linkProgram(i),!this.gl.getProgramParameter(i,this.gl.LINK_STATUS)){const s=this.gl.getProgramInfoLog(i)??"unknown error";throw this.gl.deleteProgram(i),new Error(`WebGL program link failed: ${s}`)}return i}},nt=class h{static RAINBOW_HUE_BUCKETS=24;static PARTICLE_GLOBAL_ALPHA=.9;static PARTICLE_MAX_DISTANCE=.72;static PARTICLE_BASE_RADIUS=.028;static DEFAULT_SYMBOL_SCALE=.9;canvas;container;button;spinQueueController;spriteSource;spriteCrossOrigin;spriteElementsCount;highlightInitialWinningCells;particleColorRgb;particleColorMode;symbolScale;motionProfile;isCoarsePointerDevice;spriteImage=null;spriteLoadError=null;webglRenderer=null;rafLoop=new z;runtime=Z();width=0;height=0;cellW=0;cellH=0;boardX=0;boardY=0;scriptedCascadeQueue=[];scriptedOutgoingGrid=null;scriptedPendingGrid=null;scriptedOutroStartedAt=0;scriptedOutroElapsedMs=0;outroMotionPlan=null;scriptedOutgoingOffsets=g();scriptedOutgoingOffsetsPrev=g();scriptedIncomingOffsets=g();scriptedIncomingOffsetsPrev=g();scriptedIncomingAlpha=g();scriptedIncomingAlphaPrev=g();scriptedIncomingVisibility=h.createVisibilityGrid("hidden");scriptedIncomingVisibilityPrev=h.createVisibilityGrid("hidden");winningCells=[];winningCellKeys=new Set;grid;particlesPerCell=34;lastRafTime=0;initialHighlightRequestedAt=0;simulationLastNow=0;simulationAccumulatorMs=0;outroInterpolationAlpha=1;isOutroPipelineWarmedUp=!1;isGpuPipelineWarmedUp=!1;perfWindowStartedAt=0;perfFrameCount=0;perfOver20MsCount=0;perfDtSamples=[];mobileDprCap=2;mobilePerfGoodWindows=0;mobilePerfBadWindows=0;static PERF_WINDOW_MS=1500;static PERF_BAD_P95_DT_MS=19;static PERF_GOOD_P95_DT_MS=17.5;static PERF_BAD_SLOW_RATIO=.03;static MOBILE_BAD_WINDOWS_TO_DECREASE_DPR=2;static MOBILE_GOOD_WINDOWS_TO_INCREASE_DPR=4;static MOBILE_DPR_CAP_MIN=1.25;static MOBILE_DPR_CAP_MAX=1.5;static MOBILE_DPR_STEP_DOWN=.1;static MOBILE_DPR_STEP_UP=.05;static DPR_QUANT_STEP=.25;static MAX_CANVAS_AREA_PX_COARSE=900*900;static MAX_CANVAS_AREA_PX_FINE=1400*1400;static PRE_SPIN_MS=150;static WIN_EFFECTS_ENVELOPE_TAU_MS=120;static MAX_FRAME_DELTA_MS=100;static WIN_BORDER_ALPHA=.72;static WIN_BORDER_INSET_RATIO=.08;static particleSeedsCache=new Map;constructor(t){this.canvas=t.canvas,this.container=t.container,this.button=t.button,this.spriteSource=t.sprite,this.spriteCrossOrigin=t.spriteCrossOrigin??"anonymous",this.spriteElementsCount=Math.max(1,t.spriteElementsCount??6),this.highlightInitialWinningCells=t.highlightInitialWinningCells!==!1,this.spinQueueController=new j(t.queuedSpinStates);const i=Y(t.particleColor);this.particleColorRgb=i.rgb,this.particleColorMode=i.mode,this.symbolScale=$(t.symbolScale,h.DEFAULT_SYMBOL_SCALE),this.motionProfile=S,this.isCoarsePointerDevice=h.detectCoarsePointerDevice(),this.mobileDprCap=this.isCoarsePointerDevice?h.MOBILE_DPR_CAP_MAX:2,this.grid=t.initialSegments?K(t.initialSegments,this.spriteElementsCount):L(this.spriteElementsCount)}async init(){if(this.bindEvents(),this.resize(),await this.loadSpriteIfProvided(),!this.spriteImage){const t=this.spriteLoadError?` (${this.spriteLoadError})`:"";throw new Error(`sprite is required for WebGL renderer${t}`)}this.webglRenderer=new rt({canvas:this.canvas,spriteImage:this.spriteImage,spriteElementsCount:this.spriteElementsCount}),this.webglRenderer.resize(this.width,this.height),this.warmUpOutroPipeline(),this.warmUpGpuPipeline(),this.applyInitialHighlightIfNeeded(),requestAnimationFrame(t=>{this.render(t),this.startLoop()})}destroy(){this.unbindEvents(),this.rafLoop.stop(),this.simulationLastNow=0,this.simulationAccumulatorMs=0,it(this.runtime),this.webglRenderer?.dispose(),this.webglRenderer=null,this.clearWinningCells()}spin(){if(this.dismissHighlightIfActive(),this.runtime.isSpinning||this.runtime.queueFinished)return;if(!this.spinQueueController.hasPending()){this.runtime.queueFinished=!0,this.button&&(this.button.disabled=!0);return}const t=this.spinQueueController.consume(),i=t?.highlightWin===!0;this.runtime.isSpinning=!0,this.runtime.phase="preSpin",this.runtime.preSpinStartedAt=performance.now(),this.runtime.activeSpinState=t,this.runtime.shouldHighlightCurrentSpin=i,this.runtime.hasStartedFirstSpin=!0,this.simulationLastNow=0,this.simulationAccumulatorMs=0,this.button&&(this.button.disabled=!0),this.startLoop()}bindEvents(){window.addEventListener("resize",this.resize),document.addEventListener("visibilitychange",this.onVisibilityChange),this.button?.addEventListener("click",this.onSpinClick)}unbindEvents(){window.removeEventListener("resize",this.resize),document.removeEventListener("visibilitychange",this.onVisibilityChange),this.button?.removeEventListener("click",this.onSpinClick)}onVisibilityChange=()=>{this.lastRafTime=0,this.simulationLastNow=0,this.simulationAccumulatorMs=0,this.resetPerfWindow()};onSpinClick=()=>{this.runtime.isSpinning&&this.runtime.phase!=="winFlash"||this.spin()};getNextGrid(t){return t?x(t,this.spriteElementsCount):L(this.spriteElementsCount)}update(t){if(this.runtime.isSpinning){if(this.runtime.phase==="preSpin"){if(t-this.runtime.preSpinStartedAt<h.PRE_SPIN_MS)return;J(this.runtime,{activeSpinState:this.runtime.activeSpinState,shouldHighlightCurrentSpin:this.runtime.shouldHighlightCurrentSpin,startedAt:t}),this.runtime.preSpinStartedAt=0,this.scriptedCascadeQueue=(this.runtime.activeSpinState?.finaleSequenceRows?this.runtime.activeSpinState.finaleSequenceRows.map(r=>C(r)):this.runtime.activeSpinState?.finaleSequence??[]).map(r=>r.map(n=>[...n])),this.clearWinningCells();const i=this.runtime.activeSpinState?.stopRows?C(this.runtime.activeSpinState.stopRows):this.runtime.activeSpinState?.stopGrid,s=this.getNextGrid(i);this.startOutroTransition(s,t)}this.runtime.phase}}stepScriptedOutro(t){if(!this.scriptedOutgoingGrid||!this.scriptedPendingGrid){this.finishSpinWithUi();return}this.outroMotionPlan||(this.outroMotionPlan=this.createOutroMotionPlan()),this.scriptedOutroElapsedMs+=t;const{allOutgoingDone:i,allIncomingDone:s}=D({elapsedMs:this.scriptedOutroElapsedMs,scriptedOutgoingOffsets:this.scriptedOutgoingOffsets,scriptedIncomingOffsets:this.scriptedIncomingOffsets,scriptedIncomingAlpha:this.scriptedIncomingAlpha,scriptedIncomingVisibility:this.scriptedIncomingVisibility,motionPlan:this.outroMotionPlan});if(!(!i||!s)&&(this.grid=this.scriptedPendingGrid,this.scriptedOutgoingGrid=null,this.scriptedPendingGrid=null,this.outroMotionPlan=null,this.clearWinningCells(),this.resetOutroBuffers(1,"active"),!this.tryStartScriptedCascade(this.scriptedOutroStartedAt+this.scriptedOutroElapsedMs))){if(!this.runtime.shouldHighlightCurrentSpin){this.finishSpinWithUi();return}this.setWinningCells(W(this.grid)),F(this.runtime,this.scriptedOutroStartedAt+this.scriptedOutroElapsedMs),this.runtime.activeSpinState?.callback?.(),this.button&&this.runtime.shouldHighlightCurrentSpin&&this.spinQueueController.hasPending()&&(this.button.disabled=!1)}}tryStartScriptedCascade(t){if(this.scriptedCascadeQueue.length===0)return!1;const i=this.scriptedCascadeQueue.shift();return i?(this.startOutroTransition(x(i,this.spriteElementsCount),t),!0):!1}startOutroTransition(t,i){this.scriptedOutgoingGrid=this.grid.map(s=>[...s]),this.scriptedPendingGrid=t.map(s=>[...s]),this.outroMotionPlan=this.createOutroMotionPlan(),this.resetOutroBuffers(0,"hidden"),this.clearWinningCells(),this.runtime.phase="outro",this.scriptedOutroStartedAt=i,this.scriptedOutroElapsedMs=0,this.outroInterpolationAlpha=1}finishSpinWithUi(t=!1){const i=this.runtime.activeSpinState,s=t?void 0:i?.callback,r=this.spinQueueController.hasPending();tt(this.runtime,r,performance.now()),this.button&&(this.button.disabled=this.runtime.queueFinished),s?.()}dismissHighlightIfActive(){this.runtime.phase==="winFlash"&&this.finishSpinWithUi(!0)}applyInitialHighlightIfNeeded(){this.highlightInitialWinningCells&&(this.setWinningCells(W(this.grid)),this.initialHighlightRequestedAt=performance.now())}warmUpOutroPipeline(){if(this.isOutroPipelineWarmedUp)return;const t=this.createOutroMotionPlan(),i=g(),s=g(),r=g(),n=h.createVisibilityGrid("hidden"),o=Math.max(...t.rowStartDelays),l=2*t.columnStaggerMs,u=[0,this.motionProfile.fixedStepMs,t.incomingStartShift+this.motionProfile.fixedStepMs,t.fallMs+o+l+this.motionProfile.fixedStepMs];for(const c of u)D({elapsedMs:c,scriptedOutgoingOffsets:i,scriptedIncomingOffsets:s,scriptedIncomingAlpha:r,scriptedIncomingVisibility:n,motionPlan:t});this.isOutroPipelineWarmedUp=!0}createOutroMotionPlan(){const t=V({height:this.height,boardY:this.boardY,cellH:this.cellH,motionProfile:this.motionProfile});if(!this.isCoarsePointerDevice)return t;const i=1e3/60;return{...t,columnStaggerMs:this.quantizeMs(t.columnStaggerMs,i),incomingStartShift:this.quantizeMs(t.incomingStartShift,i),rowStartDelays:[this.quantizeMs(t.rowStartDelays[0],i),this.quantizeMs(t.rowStartDelays[1],i),this.quantizeMs(t.rowStartDelays[2],i)]}}quantizeMs(t,i){return t<=0?0:Math.max(1,Math.round(t/i))*i}warmUpGpuPipeline(){if(this.isGpuPipelineWarmedUp)return;const t=this.webglRenderer;if(!t)return;const i=Math.max(8,this.cellW*.2),s=Math.max(8,this.cellH*.2),r=this.boardX+2,n=this.boardY+2;t.beginFrame(),t.drawSprite(0,r,n,i,s,1),t.drawSolidRect(r+i+2,n,i,s,[1,1,1,.35]),t.beginAdditiveBlend(),t.drawSoftCircle(r+i*.5,n+s*.5,Math.max(2,i*.25),[1,.9,.4,.4]),t.endAdditiveBlend(),this.isGpuPipelineWarmedUp=!0}trackOutroPerf(t,i){if(this.runtime.phase!=="outro"){this.resetPerfWindow();return}this.perfWindowStartedAt<=0&&(this.perfWindowStartedAt=i),this.perfFrameCount+=1,t>20&&(this.perfOver20MsCount+=1),this.perfDtSamples.push(t),!(i-this.perfWindowStartedAt<h.PERF_WINDOW_MS)&&(this.flushPerfWindow(),this.perfWindowStartedAt=i)}resetPerfWindow(){this.perfWindowStartedAt=0,this.perfFrameCount=0,this.perfOver20MsCount=0,this.perfDtSamples.length=0}flushPerfWindow(){if(this.perfFrameCount===0)return;const t=[...this.perfDtSamples].sort((s,r)=>s-r),i=t[Math.max(0,Math.min(t.length-1,Math.floor(t.length*.95)))];this.adjustMobileDprCap(i,this.perfOver20MsCount,this.perfFrameCount),this.perfFrameCount=0,this.perfOver20MsCount=0,this.perfDtSamples.length=0}adjustMobileDprCap(t,i,s){if(!this.isCoarsePointerDevice||s<=0)return;const r=i/s,n=t>h.PERF_BAD_P95_DT_MS||r>h.PERF_BAD_SLOW_RATIO,o=t<=h.PERF_GOOD_P95_DT_MS&&i===0;let l=this.mobileDprCap;n?(this.mobilePerfBadWindows+=1,this.mobilePerfGoodWindows=0,this.mobilePerfBadWindows>=h.MOBILE_BAD_WINDOWS_TO_DECREASE_DPR&&(l=Math.max(h.MOBILE_DPR_CAP_MIN,this.mobileDprCap-h.MOBILE_DPR_STEP_DOWN),this.mobilePerfBadWindows=0)):o?(this.mobilePerfGoodWindows+=1,this.mobilePerfBadWindows=0,this.mobilePerfGoodWindows>=h.MOBILE_GOOD_WINDOWS_TO_INCREASE_DPR&&(l=Math.min(h.MOBILE_DPR_CAP_MAX,this.mobileDprCap+h.MOBILE_DPR_STEP_UP),this.mobilePerfGoodWindows=0)):(this.mobilePerfGoodWindows=0,this.mobilePerfBadWindows=0),!(Math.abs(l-this.mobileDprCap)<.001)&&(this.mobileDprCap=l,this.resize())}render(t){if(!this.webglRenderer)return;this.initialHighlightRequestedAt>0&&t-this.initialHighlightRequestedAt>=200&&(F(this.runtime,this.initialHighlightRequestedAt),this.runtime.winEffectsEnvelope=1,this.initialHighlightRequestedAt=0,this.button&&(this.button.disabled=!1));const i=this.runtime.phase==="preSpin"?0:this.runtime.phase==="winFlash"?1:0;if(this.lastRafTime>0){const l=Math.min(t-this.lastRafTime,50);this.trackOutroPerf(l,t);const u=1-Math.exp(-l/h.WIN_EFFECTS_ENVELOPE_TAU_MS);this.runtime.winEffectsEnvelope+=(i-this.runtime.winEffectsEnvelope)*u}else this.resetPerfWindow();this.lastRafTime=t,this.webglRenderer.beginFrame();const s=this.runtime.phase==="winFlash"||this.runtime.phase==="preSpin"||this.initialHighlightRequestedAt>0&&this.winningCells.length>0;this.runtime.phase==="outro"&&this.scriptedOutgoingGrid&&this.scriptedPendingGrid?(this.drawGridInterpolated(this.scriptedOutgoingGrid,this.scriptedOutgoingOffsetsPrev,this.scriptedOutgoingOffsets,this.outroInterpolationAlpha,s),this.drawGridInterpolated(this.scriptedPendingGrid,this.scriptedIncomingOffsetsPrev,this.scriptedIncomingOffsets,this.outroInterpolationAlpha,s,this.scriptedIncomingAlphaPrev,this.scriptedIncomingAlpha,this.scriptedIncomingVisibility)):this.drawGrid(this.grid,null,s);const r=this.initialHighlightRequestedAt>0?"winFlash":this.runtime.phase,n=this.initialHighlightRequestedAt>0?this.initialHighlightRequestedAt:this.runtime.winFlashStartedAt,o=this.initialHighlightRequestedAt>0?1:this.runtime.winEffectsEnvelope;this.drawWinningEffects({now:t,phase:r,winFlashStartedAt:n,winEffectsEnvelope:o})}isWinningCell=(t,i)=>this.winningCellKeys.has(`${t}:${i}`);getRowCompactOffset(t){return(U[t]??0)*this.cellH}applyPixelSnapY(t){return this.isCoarsePointerDevice?Math.round(t*2)/2:t}drawGrid(t,i,s){this.drawGridWithSampler({grid:t,skipWinningCells:s,sampleOffsetY:(r,n)=>i?i[r][n]:0,sampleAlpha:()=>1,isVisible:()=>!0})}drawGridInterpolated(t,i,s,r,n,o,l,u){this.drawGridWithSampler({grid:t,skipWinningCells:n,sampleOffsetY:(c,a)=>i[c][a]+(s[c][a]-i[c][a])*r,sampleAlpha:(c,a)=>o&&l?o[c][a]+(l[c][a]-o[c][a])*r:1,isVisible:(c,a)=>!u||u[c][a]!=="hidden"})}drawGridWithSampler(t){const i=this.webglRenderer;if(i)for(let s=0;s<3;s+=1){const r=this.boardX+s*this.cellW;for(let n=0;n<3;n+=1){if(t.skipWinningCells&&this.isWinningCell(s,n)||!t.isVisible(s,n))continue;const o=t.sampleOffsetY(s,n),l=this.applyPixelSnapY(this.boardY+n*this.cellH+o+this.getRowCompactOffset(n));if(l>this.height||l+this.cellH<0)continue;const u=t.sampleAlpha(s,n);if(u<=0)continue;const c=this.cellW*this.symbolScale,a=this.cellH*this.symbolScale,d=(this.cellW-c)*.5,f=(this.cellH-a)*.5;i.drawSprite(t.grid[s][n],r+d,l+f,c,a,u)}}}drawWinningEffects(t){const i=this.webglRenderer;if(!i||this.winningCells.length===0||t.phase!=="winFlash"&&t.phase!=="preSpin")return;const s=Math.max(0,Math.min(1,t.winEffectsEnvelope)),r=Math.max(0,t.now-t.winFlashStartedAt),n=r%v/v,o=1+Math.sin(n*Math.PI*2)*N*s,l=Math.max(1,this.cellW*h.WIN_BORDER_INSET_RATIO),u=Math.max(1,this.cellW*.022),c=this.runtime.phase==="winFlash"&&this.runtime.shouldHighlightCurrentSpin&&this.runtime.hasStartedFirstSpin;for(const a of this.winningCells){const d=this.boardX+a.col*this.cellW,f=this.boardY+a.row*this.cellH+this.getRowCompactOffset(a.row),p=this.grid[a.col][a.row],E=h.WIN_BORDER_ALPHA*s,w=this.particleColorMode==="rainbow"?h.hslToRgb01((r*.2+a.col*36+a.row*22)%360,.96,.64):[this.particleColorRgb[0]/255,this.particleColorRgb[1]/255,this.particleColorRgb[2]/255],_=this.cellW*this.symbolScale*o,P=this.cellH*this.symbolScale*o,A=(this.cellW-_)*.5,B=(this.cellH-P)*.5;i.drawSprite(p,d+A,f+B,_,P,1);const G=d+l,H=f+l,O=this.cellW-l*2,I=this.cellH-l*2;O<=u*2||I<=u*2||(this.drawElectricBorder({renderer:i,cell:a,x:G,y:H,w:O,h:I,borderThickness:u,borderColor:w,alpha:E,elapsed:r,envelope:s}),c&&this.drawCellParticleBurst({renderer:i,cell:a,centerX:d+this.cellW*.5,centerY:f+this.cellH*.5,elapsed:r,envelope:s}))}}drawCellParticleBurst(t){const i=Math.min(this.cellW,this.cellH)*h.PARTICLE_MAX_DISTANCE,s=Math.min(this.cellW,this.cellH)*h.PARTICLE_BASE_RADIUS,r=h.getParticleSeeds(t.cell.col,t.cell.row),n=[this.particleColorRgb[0]/255,this.particleColorRgb[1]/255,this.particleColorRgb[2]/255];t.renderer.beginAdditiveBlend();for(let o=0;o<this.particlesPerCell;o+=1){const l=r[o],u=l.phaseOffset*720,c=t.elapsed-u;if(c<0)continue;const a=c%720/720,d=l.seedA*Math.PI*2,f=i*a*(.35+l.seedB*.65),p=t.centerX+Math.cos(d)*f,E=t.centerY+Math.sin(d)*f,w=.7+.9*Math.max(0,Math.sin((t.elapsed*.012+l.twinkleSeed*2)*Math.PI*2)),_=Math.max(1,s*(.55+l.seedC*.6)*(1-a*.5)),P=Math.max(0,Math.min(1,(.9+w*.2)*h.PARTICLE_GLOBAL_ALPHA*t.envelope));if(P<=0)continue;const A=this.particleColorMode==="rainbow"?h.getRainbowParticleColor(t.elapsed,t.cell.col,t.cell.row,l.seedA):n;t.renderer.drawSoftCircle(p,E,_,[A[0],A[1],A[2],P])}t.renderer.endAdditiveBlend()}drawElectricBorder(t){const i=Math.max(0,Math.min(1,t.alpha*t.envelope)),s=t.borderThickness*1.8,r=t.elapsed+t.cell.col*170+t.cell.row*290;t.renderer.beginAdditiveBlend(),t.renderer.drawElectricBorder({x:t.x-s,y:t.y-s,width:t.w+s*2,height:t.h+s*2,rgba:[t.borderColor[0],t.borderColor[1],t.borderColor[2],i*.55],timeMs:r,borderThicknessPx:Math.max(.8,t.borderThickness*1.1),borderInsetPx:s*.85,cornerRadiusPx:Math.max(2,t.borderThickness*2.5),noiseAmplitudePx:Math.max(.15,t.borderThickness*.6),pulseStrength:.9}),t.renderer.drawElectricBorder({x:t.x-s*.5,y:t.y-s*.5,width:t.w+s,height:t.h+s,rgba:[t.borderColor[0],t.borderColor[1],t.borderColor[2],i*.9],timeMs:r*1.03,borderThicknessPx:Math.max(.7,t.borderThickness*.8),borderInsetPx:s*.5,cornerRadiusPx:Math.max(1.5,t.borderThickness*1.7),noiseAmplitudePx:Math.max(.12,t.borderThickness*.42),pulseStrength:1.25}),t.renderer.endAdditiveBlend()}static hslToRgb01(t,i,s){const r=(t%360+360)%360,n=Math.max(0,Math.min(1,i)),o=Math.max(0,Math.min(1,s)),l=(1-Math.abs(2*o-1))*n,u=r/60,c=l*(1-Math.abs(u%2-1));let a=0,d=0,f=0;u>=0&&u<1?(a=l,d=c):u<2?(a=c,d=l):u<3?(d=l,f=c):u<4?(d=c,f=l):u<5?(a=c,f=l):(a=l,f=c);const p=o-l*.5;return[a+p,d+p,f+p]}static getRainbowParticleColor(t,i,s,r){const n=(r*360+t*.24+i*38+s*22)%360,o=360/h.RAINBOW_HUE_BUCKETS,l=Math.floor(n/o)*o;return h.hslToRgb01(l,.98,.64)}static detectCoarsePointerDevice(){if(typeof window>"u")return!1;const t=typeof window.matchMedia=="function"&&window.matchMedia("(pointer: coarse)").matches,i=typeof navigator<"u"&&typeof navigator.maxTouchPoints=="number"?navigator.maxTouchPoints:0;return t||i>0}quantizeDprCap(t){const i=h.DPR_QUANT_STEP;return Math.max(1,Math.round(t/i)*i)}static hash01(t,i,s,r){const n=Math.sin(t*127.1+i*311.7+s*74.7+r*19.3)*43758.5453;return n-Math.floor(n)}static getParticleSeeds(t,i){const s=`${t},${i}`,r=h.particleSeedsCache.get(s);if(r)return r;const n=[];for(let o=0;o<34;o+=1)n.push({seedA:h.hash01(t,i,o,1),seedB:h.hash01(t,i,o,2),seedC:h.hash01(t,i,o,3),phaseOffset:h.hash01(t,i,o,4),twinkleSeed:h.hash01(t,i,o,5)});return h.particleSeedsCache.set(s,n),n}clearWinningCells(){this.winningCells=[],this.winningCellKeys.clear()}setWinningCells(t){this.winningCells=t,this.winningCellKeys.clear();for(const i of t)this.winningCellKeys.add(`${i.col}:${i.row}`)}resize=()=>{const t=this.container.getBoundingClientRect(),i=Math.max(300,Math.floor(t.width)),s=Math.max(1,i*i),r=this.isCoarsePointerDevice?h.MAX_CANVAS_AREA_PX_COARSE:h.MAX_CANVAS_AREA_PX_FINE,n=Math.max(1,Math.sqrt(r/s)),o=this.isCoarsePointerDevice?this.mobileDprCap:2,l=this.quantizeDprCap(Math.min(o,n)),u=Math.max(1,Math.min(window.devicePixelRatio||1,l)),c=Math.max(300,Math.floor(i*u));this.width=c,this.height=c;const a=Math.floor(Math.min(this.width/3,this.height/3));this.cellW=a,this.cellH=a,this.boardX=Math.floor((this.width-this.cellW*3)/2),this.boardY=Math.floor((this.height-this.cellH*3)/2),this.canvas.width=this.width,this.canvas.height=this.height,this.webglRenderer?.resize(this.width,this.height)};async loadSpriteIfProvided(){if(!this.spriteSource){this.spriteImage=null,this.spriteLoadError=null;return}if(typeof this.spriteSource!="string"){try{await this.decodeSpriteImage(this.spriteSource),this.spriteImage=this.spriteSource,this.spriteLoadError=null}catch(i){this.spriteImage=null,this.spriteLoadError=this.toSpriteLoadErrorMessage(i,"[HTMLImageElement]")}return}const t=new Image;t.decoding="async",t.crossOrigin=this.spriteCrossOrigin,t.src=this.spriteSource;try{await this.decodeSpriteImage(t),this.spriteImage=t,this.spriteLoadError=null}catch(i){this.spriteImage=null,this.spriteLoadError=this.toSpriteLoadErrorMessage(i,this.spriteSource)}}async decodeSpriteImage(t){t.complete&&t.naturalWidth>0&&t.naturalHeight>0||await t.decode()}toSpriteLoadErrorMessage(t,i){return`failed to load sprite "${i}" - ${t instanceof Error?`${t.name}: ${t.message}`:String(t)}`}advanceSimulation(t){if(this.simulationLastNow<=0){this.simulationLastNow=t,this.update(t);return}const i=Math.max(0,Math.min(t-this.simulationLastNow,h.MAX_FRAME_DELTA_MS));if(this.simulationLastNow=t,this.update(t),this.runtime.phase!=="outro"){this.outroInterpolationAlpha=1;return}this.advanceOutroFixedStep(i)}advanceOutroFixedStep(t){this.simulationAccumulatorMs+=t;const i=this.motionProfile.fixedStepMs;let s=0;for(;this.simulationAccumulatorMs>=i&&s<this.motionProfile.maxCatchUpStepsPerFrame&&this.runtime.phase==="outro";)this.snapshotOutroState(),this.stepScriptedOutro(i),this.simulationAccumulatorMs-=i,s+=1;if(this.runtime.phase!=="outro"){this.outroInterpolationAlpha=1;return}this.outroInterpolationAlpha=Math.max(0,Math.min(1,this.simulationAccumulatorMs/i))}snapshotOutroState(){this.copyOffsets(this.scriptedOutgoingOffsetsPrev,this.scriptedOutgoingOffsets),this.copyOffsets(this.scriptedIncomingOffsetsPrev,this.scriptedIncomingOffsets),this.copyOffsets(this.scriptedIncomingAlphaPrev,this.scriptedIncomingAlpha),this.copyVisibility(this.scriptedIncomingVisibilityPrev,this.scriptedIncomingVisibility)}copyOffsets(t,i){for(let s=0;s<3;s+=1)for(let r=0;r<3;r+=1)t[s][r]=i[s][r]}copyVisibility(t,i){for(let s=0;s<3;s+=1)for(let r=0;r<3;r+=1)t[s][r]=i[s][r]}resetOutroBuffers(t,i){m(this.scriptedOutgoingOffsets,0),m(this.scriptedIncomingOffsets,0),m(this.scriptedOutgoingOffsetsPrev,0),m(this.scriptedIncomingOffsetsPrev,0),m(this.scriptedIncomingAlpha,t),m(this.scriptedIncomingAlphaPrev,t),this.fillVisibilityGrid(this.scriptedIncomingVisibility,i),this.fillVisibilityGrid(this.scriptedIncomingVisibilityPrev,i)}static createVisibilityGrid(t){return Array.from({length:3},()=>Array.from({length:3},()=>t))}fillVisibilityGrid(t,i){for(let s=0;s<3;s+=1)for(let r=0;r<3;r+=1)t[s][r]=i}startLoop(){this.rafLoop.isRunning()||this.rafLoop.start(t=>(this.advanceSimulation(t),this.render(t),this.shouldKeepAnimating()))}shouldKeepAnimating(){return this.runtime.isSpinning||this.initialHighlightRequestedAt>0?!0:this.runtime.winEffectsEnvelope>.001}};exports.CascadingReel=nt;
130
+
131
+ //# sourceMappingURL=index.cjs.js.map