reel-deal 0.1.0 → 0.1.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 +8 -0
- package/dist/index.cjs.js +134 -80
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +124 -35
- package/dist/index.es.js +807 -421
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +134 -80
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -114,10 +114,18 @@ onBeforeUnmount(() => {
|
|
|
114
114
|
| `queuedSpinStates` | `ReelDealSpinState[]` | — | Optional queue of predefined spins. |
|
|
115
115
|
| `spinConfig` | `Partial<ReelDealSpinConfig>` | `defaultSpinConfig` | Default spin config (minSpins, durationMs, speedPxS, staggerMs). |
|
|
116
116
|
| `spinBlur` | `Partial<ReelDealSpinBlur>` | `defaultSpinBlur` | Motion blur settings. |
|
|
117
|
+
| `webglShading` | `Partial<ReelDealWebGLShading>` | — | WebGL shading settings (warpAngleDeg, edgePower). |
|
|
117
118
|
| `idleBob` | `Partial<ReelDealIdleBob> \| false` | `defaultIdleBob` | Idle bobbing animation (set `false` to disable). |
|
|
119
|
+
| `finalSpinLine` | `boolean \| ReelDealFinalSpinLine` | `false` | Flash a horizontal liquid-fire line after the final spin. |
|
|
118
120
|
| `maxDpr` | `number` | `2` | Max device pixel ratio. |
|
|
119
121
|
<br>
|
|
120
122
|
|
|
123
|
+
Final spin line config:
|
|
124
|
+
```ts
|
|
125
|
+
finalSpinLine: { style: 'tint', intensity: 1 }
|
|
126
|
+
// styles: 'additive' | 'tint' | 'burn'
|
|
127
|
+
```
|
|
128
|
+
|
|
121
129
|
# Methods
|
|
122
130
|
```ts
|
|
123
131
|
await reel.init();
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,81 +1,135 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v={minSpins:2,durationMs:4e3,speedPxS:4e3,staggerMs:300},L={maxPx:4,speedAtMax:2200},_={amplitudePx:9,speedHz:.25,phaseOffsetRad:.9,rampMs:800},C=.1,A=.6,O=(a,t,s)=>Math.max(t,Math.min(s,Math.floor(a))),I=(a,t)=>(a%t+t)%t,P=a=>Math.max(0,Math.min(1,a)),F=a=>{const t=P(a);return t**2*(2-t)},$=a=>{const t=P(a);return t+t**2-t**3},Q=a=>{const t=P(a);if(t<=C){const i=t/C;return F(i)*C}if(t<A)return t;const s=(t-A)/(1-A);return A+$(s)*(1-A)},y=(a,t)=>typeof a=="number"&&Number.isFinite(a)?a:t,T=(a,t)=>Math.max(0,y(a,t)),R=(a,t)=>Math.max(0,Math.floor(y(a,t))),K=2,X=a=>{const t={...v,...a??{}};return{minSpins:R(t.minSpins,v.minSpins),durationMs:R(t.durationMs,v.durationMs??0),speedPxS:R(t.speedPxS,v.speedPxS??0),staggerMs:R(t.staggerMs,v.staggerMs??0)}},Z=a=>{const t={...L,...a??{}};return{maxPx:T(t.maxPx,L.maxPx),speedAtMax:T(t.speedAtMax,L.speedAtMax)}},j=a=>{if(a===!1)return null;const t={..._,...a??{}};return{amplitudePx:T(t.amplitudePx,_.amplitudePx),speedHz:T(t.speedHz,_.speedHz),phaseOffsetRad:y(t.phaseOffsetRad,_.phaseOffsetRad),rampMs:R(t.rampMs,_.rampMs)}},J=a=>Math.max(1,y(a,K)),tt=(a,t)=>{const s=document.getElementById(a);if(!s)throw new Error(`${t} not found: ${a}`);return s},U=(a,t)=>typeof a=="string"?tt(a,t):a,et=5,z=.72,st=.28,it=7,V=.85,rt=1-V,nt=30,at=500,ot=1.25,lt=.25,N={warpAngleDeg:360,edgePower:.5},ht=a=>({warpAngleDeg:T(a?.warpAngleDeg,N.warpAngleDeg),edgePower:T(a?.edgePower,N.edgePower)});class ut{canvas;container;opts;visibleSlots=3;heightScale=2/3;centerIndex=1;reels;spinConfig;spinBlur;webglShading;idleBob;maxDpr;spriteImg=null;spriteWidth=0;spriteHeight=0;frameHeight=0;width=0;height=0;dpr=1;offsets;velocities;lastVelT=null;lastVelOffsets;idleStartTime=null;idleBaseOffsets;gl=null;glProgram=null;glBuffer=null;glTexture=null;glAttribs=null;webglInitError=null;glUniforms=null;anim=null;rafId=null;pendingSpinResolvers=[];resizeObserver=null;resizeQueued=!1;button=null;buttonHandlerAttached=!1;spinning=!1;queuedSpins=null;constructor(t){this.opts=t,this.reels=O(t.reels??1,1,et),this.spinConfig=X(t.spinConfig),this.spinBlur=Z(t.spinBlur),this.webglShading=ht(t.webglShading),this.idleBob=j(t.idleBob),this.maxDpr=J(t.maxDpr),this.canvas=U(t.canvas,"Canvas"),this.container=U(t.container,"Container"),this.offsets=new Array(this.reels).fill(0),this.velocities=new Array(this.reels).fill(0),this.lastVelOffsets=new Array(this.reels).fill(0),this.idleBaseOffsets=new Array(this.reels).fill(0)}async init(){if(await this.loadSprite(),this.tryInitWebGL(),!this.gl)throw new Error(`WebGL is not supported or failed to initialize. ${this.webglInitError??""}`.trim());this.syncContainerLayoutVars(),this.ensureCanvasCoversContainer(),this.setupResizeWatcher(),this.resizeToContainer(),this.setSegments(this.opts.initialSegments),this.button=this.resolveButton(),this.queuedSpins=this.resolveSpinQueue(),this.attachButtonHandler(),this.render(),this.startLoop()}destroy(){this.stop(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.button&&this.buttonHandlerAttached&&(this.button.removeEventListener("click",this.handleButtonClick),this.buttonHandlerAttached=!1),this.releaseWebGLResources()}ensureCanvasCoversContainer(){this.canvas!==this.container&&this.container.contains(this.canvas)&&(this.canvas.style.width||(this.canvas.style.width="100%"),this.canvas.style.height||(this.canvas.style.height="100%"),this.canvas.style.display||(this.canvas.style.display="block"))}syncContainerLayoutVars(){const t=this.canvas===this.container?this.canvas:this.container;t.style.setProperty("--reels",String(this.reels)),t.style.setProperty("--visible-slots",String(this.visibleSlots)),t.style.aspectRatio=`${this.reels} / ${this.visibleSlots*this.heightScale}`,this.canvas!==this.container&&(this.container.style.overflow="hidden")}setSegments(t){this.ensureReady();const s=t??[];for(let i=0;i<this.reels;i+=1){const e=s[i]??0,r=O(e,0,this.opts.slotCount-1);this.offsets[i]=r*this.frameHeight-this.centerIndex*this.frameHeight,this.lastVelOffsets[i]=this.offsets[i],this.velocities[i]=0,this.idleBaseOffsets[i]=this.offsets[i]}this.lastVelT=null,this.idleStartTime=null,this.anim=null,this.stop(),this.render(),this.startLoop()}setSegment(t){this.setSegments(new Array(this.reels).fill(t))}spinOnce(t,s){this.ensureReady();const{minSpins:i,staggerMs:e,speedPxS:r,durationMs:l}=this.getSpinConfig(s),u=r>0,f=this.getStopSpringOvershootPx(),c=Math.max(0,at),n=c>0&&f>0,h=new Array(this.reels),d=new Array(this.reels),p=new Array(this.reels),g=new Array(this.reels),m=new Array(this.reels);for(let o=0;o<this.reels;o+=1){const k=O(t[o]??0,0,this.opts.slotCount-1)*this.frameHeight-this.centerIndex*this.frameHeight,Y=I(k,this.spriteHeight),q=I(this.offsets[o],this.spriteHeight),H=I(q-Y,this.spriteHeight);let x=-(i*this.spriteHeight+H);if(u&&l>0){const w=r*l/1e3,E=Math.max(i,Math.ceil(Math.max(0,w-H)/this.spriteHeight));x=-(H+E*this.spriteHeight)}if(e>0&&u){const w=r*e*o/1e3,E=Math.ceil(w/this.spriteHeight);x-=E*this.spriteHeight}h[o]=this.offsets[o],m[o]=this.offsets[o]+x;let B=x,G=0;if(n&&x!==0&&f>0){const E=Math.sign(x)*f;B+=E,G=c}d[o]=B,g[o]=G,u?p[o]=Math.max(0,Math.round(Math.abs(B)/r*1e3)):p[o]=Math.max(0,l+e*o)}const S=d.reduce((o,M)=>o+Math.abs(M),0),b=p.every(o=>o<=0),W=g.every(o=>o<=0);if(S===0||b&&W){for(let o=0;o<this.reels;o+=1)this.offsets[o]=m[o];return this.anim=null,this.resolvePendingSpins(),this.render(),Promise.resolve()}const D=performance.now();return this.anim={startTime:D,startOffsets:h,deltas:d,durationMs:p,settleDurationMs:g,targets:m},this.idleStartTime=null,this.startLoop(),new Promise(o=>{this.pendingSpinResolvers.push(o)})}getSpinConfig(t){return t?X({...this.spinConfig,...t}):this.spinConfig}getStopSpringOvershootPx(){const t=this.frameHeight*lt,s=Math.max(0,nt);return Math.min(s,t)}getStopSpringOffset(t,s){if(t===0)return 0;const i=Math.exp(-4*s),e=Math.PI*2*ot*s;return t*i*Math.cos(e)}async spinQueue(t){for(let s=0;s<t.length;s+=1){const i=t[s];await this.spinOnce(i.stopAtSegments),i.callback?.(s,i.stopAtSegments)}}stop(){this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=null,this.anim=null,this.resolvePendingSpins(),this.lastVelT=null;for(let t=0;t<this.reels;t+=1)this.velocities[t]=0}resolvePendingSpins(){if(this.pendingSpinResolvers.length===0)return;const t=this.pendingSpinResolvers;this.pendingSpinResolvers=[];for(const s of t)s()}requestResize(){this.queueResize()}ensureReady(){if(!this.spriteImg||this.spriteWidth<=0||this.spriteHeight<=0||this.frameHeight<=0)throw new Error("Sprite is not ready. Call init() and await it first.")}attachButtonHandler(){!this.button||this.buttonHandlerAttached||(this.button.addEventListener("click",this.handleButtonClick,{passive:!0}),this.buttonHandlerAttached=!0)}handleButtonClick=async()=>{if(this.spinning)return;const t=this.consumeNextSpin()??{stopAtSegments:this.buildRandomSegments()};this.spinning=!0,this.setButtonDisabled(!0);try{await this.spinOnce(t.stopAtSegments)}finally{this.queuedSpins&&this.queuedSpins.length===0?this.setButtonDisabled(!0):this.setButtonDisabled(!1),this.spinning=!1}};resolveButton(){return this.opts.button?U(this.opts.button,"Button"):null}resolveSpinQueue(){return this.opts.queuedSpinStates??null}consumeNextSpin(){return this.queuedSpins||(this.queuedSpins=this.resolveSpinQueue()),!this.queuedSpins||this.queuedSpins.length===0?null:this.queuedSpins.shift()??null}buildRandomSegments(){const t=Math.max(1,Math.floor(this.opts.slotCount)),s=[];for(let i=0;i<this.reels;i+=1)s.push(Math.floor(Math.random()*t));return s}setButtonDisabled(t){this.button&&(this.button.disabled=t,t?this.button.setAttribute("aria-busy","true"):this.button.removeAttribute("aria-busy"))}shouldAnimate(){return this.anim!==null||this.idleBob!==null}startLoop(){this.rafId===null&&this.shouldAnimate()&&(this.rafId=requestAnimationFrame(this.loop))}loop=t=>{if(this.anim){const{startTime:s,durationMs:i,startOffsets:e,deltas:r,settleDurationMs:l,targets:u}=this.anim,f=t-s;let c=!0;for(let n=0;n<this.reels;n+=1){const h=i[n]??0,d=l[n]??0,p=u[n]??e[n]+r[n];if(h>0&&f<h){const g=Math.min(1,f/h),m=Q(g);g<1&&(c=!1);const S=e[n]+r[n]*m;let b=0;if(g>z&&this.frameHeight>0){const D=(1-P((g-z)/st))**2,o=S/this.frameHeight*Math.PI*2,M=Math.min(it,this.frameHeight*.06);b=Math.sin(o)*D*M}this.offsets[n]=S+b}else if(d>0){const g=h<=0?f/d:(f-h)/d,m=P(g),S=e[n]+r[n]-p,b=this.getStopSpringOffset(S,m);m<1&&(c=!1),this.offsets[n]=p+b}else this.offsets[n]=p}if(this.updateVelocity(t),this.render(),c){for(let n=0;n<this.reels;n+=1){const h=this.anim.targets[n]??this.anim.startOffsets[n]+this.anim.deltas[n];this.offsets[n]=h,this.idleBaseOffsets[n]=this.offsets[n]}this.updateVelocity(t);for(let n=0;n<this.reels;n+=1)this.velocities[n]=0;this.anim=null,this.idleStartTime=null,this.render(),this.resolvePendingSpins()}}else this.idleBob&&(this.updateIdle(t),this.render());if(!this.shouldAnimate()){this.rafId=null;return}this.rafId!==null&&(this.rafId=requestAnimationFrame(this.loop))};render(){this.spriteImg&&(this.width<=0||this.height<=0||this.gl&&this.renderWebGL())}getSpinBlurPxForReel(t){if(!this.anim)return 0;const s=Math.abs(this.velocities[t]??0),i=Math.max(1,this.spinBlur.speedAtMax);return P(s/i)*Math.max(0,this.spinBlur.maxPx)}renderWebGL(){if(!this.gl||!this.glProgram||!this.glAttribs||!this.glUniforms||!this.glTexture||!this.glBuffer||!this.spriteImg)return;const t=this.gl;t.useProgram(this.glProgram),t.bindBuffer(t.ARRAY_BUFFER,this.glBuffer),t.enableVertexAttribArray(this.glAttribs.pos),t.vertexAttribPointer(this.glAttribs.pos,2,t.FLOAT,!1,16,0),t.enableVertexAttribArray(this.glAttribs.uv),t.vertexAttribPointer(this.glAttribs.uv,2,t.FLOAT,!1,16,8),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.glTexture),t.uniform1i(this.glUniforms.texture,0),t.uniform1f(this.glUniforms.frameHeight,this.frameHeight),t.uniform1f(this.glUniforms.spriteHeight,this.spriteHeight),t.uniform1f(this.glUniforms.visibleSlots,this.visibleSlots),t.uniform1f(this.glUniforms.heightScale,this.heightScale),t.uniform1f(this.glUniforms.edgePower,Math.max(.5,this.webglShading.edgePower));const s=this.webglShading,i=Math.max(0,s.warpAngleDeg)*Math.PI/180;t.uniform1f(this.glUniforms.warpAngle,i),t.clear(t.COLOR_BUFFER_BIT);const e=1/this.reels;for(let r=0;r<this.reels;r+=1){t.uniform1f(this.glUniforms.reelX,r*e),t.uniform1f(this.glUniforms.reelW,e);const l=I(this.offsets[r],this.spriteHeight);t.uniform1f(this.glUniforms.offsetPx,l),t.uniform1f(this.glUniforms.blurPx,this.getSpinBlurPxForReel(r)),t.drawArrays(t.TRIANGLE_STRIP,0,4)}}updateVelocity(t){if(this.lastVelT===null){this.lastVelT=t;for(let i=0;i<this.reels;i+=1)this.lastVelOffsets[i]=this.offsets[i],this.velocities[i]=0;return}const s=(t-this.lastVelT)/1e3;if(!(s<=0)){for(let i=0;i<this.reels;i+=1){const e=(this.offsets[i]-this.lastVelOffsets[i])/s;this.velocities[i]=this.velocities[i]*V+e*rt,this.lastVelOffsets[i]=this.offsets[i]}this.lastVelT=t}}updateIdle(t){if(!this.idleBob)return;this.idleStartTime===null&&(this.idleStartTime=t);const{amplitudePx:s,speedHz:i,phaseOffsetRad:e,rampMs:r}=this.idleBob,l=t-this.idleStartTime,u=l/1e3,f=Math.max(0,Math.min(1,l/Math.max(1,r))),c=F(f),n=i*Math.PI*2;for(let h=0;h<this.reels;h+=1){const d=u*n+h*e;this.offsets[h]=this.idleBaseOffsets[h]+Math.sin(d)*(s*c),this.velocities[h]=0}}async loadSprite(){const{sprite:t}=this.opts,s=typeof t=="string"?new Image:t;typeof t=="string"&&(s.src=t),(!s.complete||s.naturalWidth<=0)&&await new Promise((c,n)=>{const h=()=>c(),d=()=>n(new Error("Failed to load sprite image"));s.addEventListener("load",h,{once:!0}),s.addEventListener("error",d,{once:!0})});const i=Math.max(1,Math.floor(this.opts.slotCount)),e=s.naturalWidth,r=e,l=r*i,u=s.naturalHeight;if(Math.abs(u-l)>2)throw new Error(`Sprite size mismatch. Expected height ~${Math.round(l)}px (slotCount=${i}, slot aspect 1/1), got ${u}px.`);this.spriteImg=s,this.spriteWidth=e,this.frameHeight=Math.max(1,Math.round(r)),this.spriteHeight=Math.max(1,Math.round(this.frameHeight*i))}setupResizeWatcher(){typeof ResizeObserver>"u"||this.resizeObserver||(this.resizeObserver=new ResizeObserver(()=>this.queueResize()),this.resizeObserver.observe(this.container))}queueResize=()=>{this.resizeQueued||(this.resizeQueued=!0,requestAnimationFrame(()=>{this.resizeQueued=!1,this.resizeToContainer()}))};resizeToContainer(){const t=this.container.clientWidth,s=this.container.clientHeight,i=this.visibleSlots*this.heightScale;if(t<=0||s<=0){this.applyResize(1,1);return}const e=i,r=this.reels,l=t/r*e;let u=t,f=l;f>s&&(f=s,u=s/e*r);const c=Math.max(1,Math.floor(u)),n=Math.max(1,Math.floor(f));this.applyResize(c,n),this.render()}applyViewportCrop(){this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.style.transform=""}applyResize(t,s){const i=Math.max(1,Math.floor(t)),e=Math.max(1,Math.floor(s)),r=this.getTargetDpr();this.width=i,this.height=e,this.dpr=r,this.canvas.width=Math.floor(this.width*this.dpr),this.canvas.height=Math.floor(this.height*this.dpr),this.gl&&this.gl.viewport(0,0,this.canvas.width,this.canvas.height),this.applyViewportCrop()}getTargetDpr(){const t=window.devicePixelRatio||1,s=Math.max(1,t);return Math.min(this.maxDpr,s)}tryInitWebGL(){if(!this.spriteImg)return;this.releaseWebGLResources(),this.webglInitError=null;const t=this.canvas.getContext("webgl2",{antialias:!0,alpha:!0})??this.canvas.getContext("webgl",{antialias:!0,alpha:!0})??this.canvas.getContext("experimental-webgl",{antialias:!0,alpha:!0});if(!t){const n=document.createElement("canvas"),h=typeof WebGLRenderingContext<"u",d=n.getContext("webgl2")??n.getContext("webgl");this.webglInitError=`context is null (browser WebGL=${h?"yes":"no"}, fallback canvas=${d?"yes":"no"})`,console.warn("ReelDeal WebGL:",this.webglInitError);return}const e=this.createWebGLProgram(t,`
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ne=r=>new Promise((e,t)=>{const i=()=>e(),s=()=>t(new Error("Failed to load sprite image"));r.addEventListener("load",i,{once:!0}),r.addEventListener("error",s,{once:!0})}),Y=async(r,e)=>{const t=typeof r=="string"?new Image:r;typeof r=="string"&&(t.src=r),(!t.complete||t.naturalWidth<=0)&&await ne(t);const i=Math.max(1,Math.floor(e)),s=t.naturalWidth,n=s,a=n*i,u=t.naturalHeight;if(Math.abs(u-a)>2)throw new Error(`Sprite size mismatch. Expected height ~${Math.round(a)}px (slotCount=${i}, slot aspect 1/1), got ${u}px.`);return{image:t,spriteWidth:s,frameHeight:Math.max(1,Math.round(n)),spriteHeight:Math.max(1,Math.round(n*i)),slotCount:i}},w={minSpins:2,durationMs:4e3,speedPxS:4e3,staggerMs:300},y={maxPx:4,speedAtMax:2200},E={amplitudePx:9,speedHz:.25,phaseOffsetRad:.9,rampMs:800},k={warpAngleDeg:60,edgePower:4.8},N={thickness:6,intensity:1},F=.1,L=.6,M=(r,e,t)=>Math.max(e,Math.min(t,Math.floor(r))),D=(r,e)=>(r%e+e)%e,v=r=>Math.max(0,Math.min(1,r)),ae=r=>{const e=v(r);return e**2*(2-e)},oe=r=>{const e=v(r);return e+e**2-e**3},le=r=>{const e=v(r);if(e<=F){const i=e/F;return ae(i)*F}if(e<L)return e;const t=(e-L)/(1-L);return L+oe(t)*(1-L)},H=(r,e)=>typeof r=="number"&&Number.isFinite(r)?r:e,x=(r,e)=>Math.max(0,H(r,e)),T=(r,e)=>Math.max(0,Math.floor(H(r,e))),he=2,z=r=>{const e={...w,...r??{}};return{minSpins:T(e.minSpins,w.minSpins??0),durationMs:T(e.durationMs,w.durationMs??0),speedPxS:T(e.speedPxS,w.speedPxS??0),staggerMs:T(e.staggerMs,w.staggerMs??0)}},q=r=>{const e={...y,...r??{}};return{maxPx:x(e.maxPx,y.maxPx),speedAtMax:x(e.speedAtMax,y.speedAtMax)}},$=r=>{if(r===!1)return null;const e={...E,...r??{}};return{amplitudePx:x(e.amplitudePx,E.amplitudePx),speedHz:x(e.speedHz,E.speedHz),phaseOffsetRad:H(e.phaseOffsetRad,E.phaseOffsetRad),rampMs:T(e.rampMs,E.rampMs)}},Q=r=>Math.max(1,H(r,he)),K=r=>({warpAngleDeg:x(r?.warpAngleDeg,k.warpAngleDeg),edgePower:x(r?.edgePower,k.edgePower)}),j=r=>r===!1||!r?null:{thickness:x(r.thickness,N.thickness),intensity:v(r.intensity??N.intensity)},ue=r=>{const{stopAtSegments:e,currentOffsets:t,reels:i,frameHeight:s,spriteHeight:n,centerIndex:a,slotCount:u,spinConfig:l,stopSpringOvershootPx:d,stopSpringSettleMs:o,useSpeed:c}=r,b=new Array(i),p=new Array(i),h=new Array(i),g=new Array(i),m=new Array(i),{minSpins:S,staggerMs:I,speedPxS:A,durationMs:P}=l,O=o>0&&d>0;for(let f=0;f<i;f+=1){const ie=M(e[f]??0,0,u-1)*s-a*s,se=D(ie,n),re=D(t[f],n),B=D(re-se,n);let _=-(S*n+B);if(c&&P>0){const C=A*P/1e3,R=Math.max(S,Math.ceil(Math.max(0,C-B)/n));_=-(B+R*n)}if(I>0&&c){const C=A*I*f/1e3,R=Math.ceil(C/n);_-=R*n}b[f]=t[f],m[f]=t[f]+_;let U=_,X=0;if(O&&_!==0&&d>0){const R=Math.sign(_)*d;U+=R,X=o}p[f]=U,g[f]=X,c?h[f]=Math.max(0,Math.round(Math.abs(U)/A*1e3)):h[f]=Math.max(0,P+I*f)}const J=p.reduce((f,G)=>f+Math.abs(G),0),ee=h.every(f=>f<=0),te=g.every(f=>f<=0);return{startOffsets:b,deltas:p,durations:h,settleDurationMs:g,targets:m,totalDelta:J,allSpinZero:ee,allSettleZero:te}},fe=(r,e)=>{if(r===0)return 0;const t=Math.exp(-5*e),i=Math.PI*2*1.25*e;return r*t*Math.cos(i)},ce=(r,e,t)=>{if(e.lastVelT===null){e.lastVelT=r;for(let a=0;a<t.length;a+=1)e.lastVelOffsets[a]=t[a],e.velocities[a]=0;return e.lastVelT}const i=(r-e.lastVelT)/1e3;if(i<=0)return e.lastVelT;const s=.85,n=1-s;for(let a=0;a<t.length;a+=1){const u=(t[a]-e.lastVelOffsets[a])/i;e.velocities[a]=e.velocities[a]*s+u*n,e.lastVelOffsets[a]=t[a]}return e.lastVelT=r,e.lastVelT},de=(r,e,t,i,s)=>{e.idleStartTime===null&&(e.idleStartTime=r);const{amplitudePx:n,speedHz:a,phaseOffsetRad:u,rampMs:l}=t,d=r-e.idleStartTime,o=d/1e3,c=Math.max(0,Math.min(1,d/Math.max(1,l))),b=Math.min(1,Math.max(0,c*c*(2-c))),p=a*Math.PI*2;for(let h=0;h<s;h+=1){const g=o*p+h*u;i[h]=e.idleBaseOffsets[h]+Math.sin(g)*(n*b),e.velocities[h]=0}return e.idleStartTime};class pe{rafId=null;step=null;abortListener=null;start(e,t){this.rafId===null&&(this.step=e,this.attachAbort(t),this.rafId=requestAnimationFrame(this.tick))}stop(){this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.step=null,this.detachAbort()}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))};attachAbort(e){if(!e)return;if(e.aborted){this.stop();return}const t=()=>{this.stop()};e.addEventListener("abort",t,{once:!0}),this.abortListener=()=>e.removeEventListener("abort",t)}detachAbort(){this.abortListener&&this.abortListener(),this.abortListener=null}}class ge{slotCount;reels;queue;constructor(e){this.slotCount=Math.max(1,Math.floor(e.slotCount)),this.reels=Math.max(1,Math.floor(e.reels)),this.queue=[...e.initialQueue??[]]}reset(e){this.queue=[...e??[]]}hasPending(){return this.queue.length>0}consume(){return this.queue.length===0?{state:{stopAtSegments:this.buildRandomSegments()},remaining:0,isLast:!0}:{state:this.queue.shift()??{stopAtSegments:this.buildRandomSegments()},remaining:this.queue.length,isLast:this.queue.length===0}}buildRandomSegments(){const e=this.slotCount,t=[];for(let i=0;i<this.reels;i+=1)t.push(M(Math.floor(Math.random()*e),0,e-1));return t}}class me{button;handler;attached=!1;spinning=!1;constructor(e,t){this.button=e,this.handler=t,this.attach()}updateAvailability(e){this.button&&this.setDisabled(!e)}dispose(){this.button&&this.attached&&(this.button.removeEventListener("click",this.onClick),this.attached=!1),this.spinning=!1}attach(){!this.button||this.attached||(this.button.addEventListener("click",this.onClick,{passive:!0}),this.attached=!0)}setDisabled(e){this.button&&(this.button.disabled=e,e?this.button.setAttribute("aria-busy","true"):this.button.removeAttribute("aria-busy"))}onClick=async()=>{if(!this.spinning){this.spinning=!0,this.setDisabled(!0);try{await this.handler()}finally{this.spinning=!1,this.setDisabled(!1)}}}}const be=`
|
|
2
|
+
attribute vec2 a_pos;
|
|
3
|
+
attribute vec2 a_uv;
|
|
4
|
+
varying vec2 v_uv;
|
|
5
|
+
void main() {
|
|
6
|
+
v_uv = a_uv;
|
|
7
|
+
gl_Position = vec4(a_pos, 0.0, 1.0);
|
|
8
|
+
}
|
|
9
|
+
`,Se=`
|
|
10
|
+
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
11
|
+
precision highp float;
|
|
12
|
+
#else
|
|
13
|
+
precision mediump float;
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
varying vec2 v_uv;
|
|
17
|
+
uniform sampler2D u_tex;
|
|
18
|
+
uniform float u_reelX;
|
|
19
|
+
uniform float u_reelW;
|
|
20
|
+
uniform float u_offsetPx;
|
|
21
|
+
uniform float u_frameHeight;
|
|
22
|
+
uniform float u_spriteHeight;
|
|
23
|
+
uniform float u_visibleSlots;
|
|
24
|
+
uniform float u_heightScale;
|
|
25
|
+
uniform float u_edgePower;
|
|
26
|
+
uniform float u_warpAngle;
|
|
27
|
+
uniform float u_blurPx;
|
|
28
|
+
|
|
29
|
+
const float HALF_PI = 1.5707963;
|
|
30
|
+
|
|
31
|
+
float saturate(float x) { return clamp(x, 0.0, 1.0); }
|
|
32
|
+
|
|
33
|
+
void main() {
|
|
34
|
+
if (v_uv.x < u_reelX || v_uv.x > (u_reelX + u_reelW)) {
|
|
35
|
+
discard;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
float localX = (v_uv.x - u_reelX) / u_reelW;
|
|
39
|
+
float localY = v_uv.y;
|
|
40
|
+
float hs = max(0.0001, u_heightScale);
|
|
41
|
+
float fullY = localY * hs + (1.0 - hs) * 0.5;
|
|
42
|
+
|
|
43
|
+
float warp = max(0.0, u_warpAngle);
|
|
44
|
+
float warpStrength = saturate(warp / HALF_PI);
|
|
45
|
+
float distNorm = abs(fullY - 0.5) * 2.0;
|
|
46
|
+
float edgePow = max(0.5, u_edgePower);
|
|
47
|
+
float edgeWeight = pow(saturate(distNorm), edgePow) * warpStrength;
|
|
48
|
+
|
|
49
|
+
float stretchMax = 6.0;
|
|
50
|
+
float stretch = mix(1.0, stretchMax, edgeWeight);
|
|
51
|
+
float warpedY = 0.5 + (fullY - 0.5) * stretch;
|
|
52
|
+
|
|
53
|
+
float arcY = 0.5 + 0.5 * sin((warpedY - 0.5) * HALF_PI);
|
|
54
|
+
warpedY = mix(warpedY, arcY, 0.2);
|
|
55
|
+
|
|
56
|
+
float yPx = u_offsetPx + warpedY * (u_frameHeight * u_visibleSlots);
|
|
57
|
+
float v = fract((u_offsetPx / u_spriteHeight) +
|
|
58
|
+
warpedY * ((u_frameHeight * u_visibleSlots) / u_spriteHeight));
|
|
59
|
+
|
|
60
|
+
vec4 base = texture2D(u_tex, vec2(localX, v));
|
|
61
|
+
|
|
62
|
+
if (u_blurPx > 0.0) {
|
|
63
|
+
float maxStepPx = 1.0 + 3.0 * saturate(u_blurPx / 4.0);
|
|
64
|
+
float blurPx = min(u_blurPx, maxStepPx);
|
|
65
|
+
float blurStep = blurPx / max(1.0, u_spriteHeight);
|
|
66
|
+
vec4 sum = base * 0.227027;
|
|
67
|
+
sum += texture2D(u_tex, vec2(localX, fract(v + blurStep))) * 0.1945946;
|
|
68
|
+
sum += texture2D(u_tex, vec2(localX, fract(v - blurStep))) * 0.1945946;
|
|
69
|
+
sum += texture2D(u_tex, vec2(localX, fract(v + 2.0 * blurStep))) * 0.1216216;
|
|
70
|
+
sum += texture2D(u_tex, vec2(localX, fract(v - 2.0 * blurStep))) * 0.1216216;
|
|
71
|
+
sum += texture2D(u_tex, vec2(localX, fract(v + 3.0 * blurStep))) * 0.054054;
|
|
72
|
+
sum += texture2D(u_tex, vec2(localX, fract(v - 3.0 * blurStep))) * 0.054054;
|
|
73
|
+
sum += texture2D(u_tex, vec2(localX, fract(v + 4.0 * blurStep))) * 0.016216;
|
|
74
|
+
sum += texture2D(u_tex, vec2(localX, fract(v - 4.0 * blurStep))) * 0.016216;
|
|
75
|
+
base = sum;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
gl_FragColor = base;
|
|
79
|
+
}
|
|
80
|
+
`,ve=`
|
|
81
|
+
attribute vec2 a_pos;
|
|
82
|
+
varying vec2 v_pos;
|
|
83
|
+
void main() {
|
|
84
|
+
v_pos = a_pos;
|
|
85
|
+
gl_Position = vec4(a_pos, 0.0, 1.0);
|
|
86
|
+
}
|
|
87
|
+
`,xe=`
|
|
88
|
+
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
89
|
+
precision highp float;
|
|
90
|
+
#else
|
|
91
|
+
precision mediump float;
|
|
92
|
+
#endif
|
|
93
|
+
|
|
94
|
+
varying vec2 v_pos;
|
|
95
|
+
uniform float u_time;
|
|
96
|
+
uniform vec2 u_resolution;
|
|
97
|
+
uniform float u_thickness;
|
|
98
|
+
uniform float u_intensity;
|
|
99
|
+
|
|
100
|
+
void main() {
|
|
101
|
+
vec2 uv = v_pos;
|
|
102
|
+
float centerY = 0.0;
|
|
103
|
+
float distY = abs(uv.y - centerY);
|
|
104
|
+
float thicknessPx = u_thickness / u_resolution.y * 2.0;
|
|
105
|
+
|
|
106
|
+
float lineMask = 1.0 - smoothstep(0.0, thicknessPx * 2.0, distY);
|
|
107
|
+
|
|
108
|
+
if (lineMask <= 0.001) {
|
|
109
|
+
discard;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
vec3 color;
|
|
113
|
+
float alpha = lineMask * u_intensity;
|
|
114
|
+
|
|
115
|
+
float warm = 1.0 - exp(-u_time * 2.6);
|
|
116
|
+
float flicker = (sin(u_time * 37.0) * sin(u_time * 13.0)) * 0.28;
|
|
117
|
+
flicker *= exp(-u_time * 2.2);
|
|
118
|
+
warm = clamp(warm + flicker, 0.0, 1.0);
|
|
119
|
+
float warmColor = mix(0.65, 1.0, warm);
|
|
120
|
+
alpha *= warm;
|
|
121
|
+
|
|
122
|
+
float pulse = sin(u_time * 4.0) * 0.3 + 0.7;
|
|
123
|
+
float x = (uv.x + 1.0) * 0.5;
|
|
124
|
+
float wave = sin(x * 30.0 - u_time * 8.0) * 0.2;
|
|
125
|
+
|
|
126
|
+
color = vec3(1.0, 0.8, 0.2) * (pulse + wave);
|
|
127
|
+
|
|
128
|
+
float core = exp(-distY * 120.0 / thicknessPx);
|
|
129
|
+
color = mix(color, vec3(1.0), core * 0.6);
|
|
130
|
+
|
|
131
|
+
color *= warmColor;
|
|
132
|
+
gl_FragColor = vec4(color, alpha);
|
|
133
|
+
}
|
|
134
|
+
`;class _e{program=null;buffer=null;attribs=null;uniforms=null;startTime=null;show=!1;init(e,t){const i=this.createProgram(e,ve,xe);if(!i)return;const s=e.createBuffer();if(!s){e.deleteProgram(i);return}e.bindBuffer(e.ARRAY_BUFFER,s);const n=new Float32Array([-1,-1,1,-1,-1,1,1,1]);e.bufferData(e.ARRAY_BUFFER,n,e.STATIC_DRAW),this.program=i,this.buffer=s,this.attribs={pos:e.getAttribLocation(i,"a_pos")},this.uniforms={time:e.getUniformLocation(i,"u_time"),resolution:e.getUniformLocation(i,"u_resolution"),thickness:e.getUniformLocation(i,"u_thickness"),intensity:e.getUniformLocation(i,"u_intensity")},this.startTime=null,this.show=!1}showLine(){this.show=!0,this.startTime=null}hideLine(){this.show=!1,this.startTime=null}dispose(e){this.program&&e.deleteProgram(this.program),this.buffer&&e.deleteBuffer(this.buffer),this.program=null,this.buffer=null,this.attribs=null,this.uniforms=null,this.startTime=null,this.show=!1}render(e,t,i,s){if(!this.show||!this.program||!this.buffer||!this.attribs||!this.uniforms)return;const n=performance.now();this.startTime===null&&(this.startTime=n);const a=(n-this.startTime)/1e3;e.useProgram(this.program),e.bindBuffer(e.ARRAY_BUFFER,this.buffer),e.enableVertexAttribArray(this.attribs.pos),e.vertexAttribPointer(this.attribs.pos,2,e.FLOAT,!1,0,0),e.uniform1f(this.uniforms.time,a),e.uniform2f(this.uniforms.resolution,t.width,t.height),e.uniform1f(this.uniforms.thickness,i.thickness*s),e.uniform1f(this.uniforms.intensity,i.intensity),e.drawArrays(e.TRIANGLE_STRIP,0,4)}createProgram(e,t,i){const s=this.createShader(e,e.VERTEX_SHADER,t),n=this.createShader(e,e.FRAGMENT_SHADER,i);if(!s||!n)return s&&e.deleteShader(s),n&&e.deleteShader(n),null;const a=e.createProgram();return a?(e.attachShader(a,s),e.attachShader(a,n),e.linkProgram(a),e.getProgramParameter(a,e.LINK_STATUS)?(e.detachShader(a,s),e.detachShader(a,n),e.deleteShader(s),e.deleteShader(n),a):(e.deleteProgram(a),e.deleteShader(s),e.deleteShader(n),null)):null}createShader(e,t,i){const s=e.createShader(t);return s?(e.shaderSource(s,i),e.compileShader(s),e.getShaderParameter(s,e.COMPILE_STATUS)?s:(e.deleteShader(s),null)):null}}class Z{gl=null;glProgram=null;glBuffer=null;glTexture=null;glAttribs=null;glUniforms=null;config=null;canvas=null;spriteImg=null;webglInitError=null;lineRenderer=null;showLine=!1;init(e){if(this.canvas=e.canvas,this.spriteImg=e.spriteImg,this.config={spriteWidth:e.spriteWidth,spriteHeight:e.spriteHeight,frameHeight:e.frameHeight,reels:e.reels,visibleSlots:e.visibleSlots,heightScale:e.heightScale,spinBlur:e.spinBlur,webglShading:e.webglShading,finalSpinLine:e.finalSpinLine},this.lineRenderer=e.finalSpinLine?new _e:null,this.tryInitWebGL(),!this.gl)throw new Error(`WebGL is not supported or failed to initialize. ${this.webglInitError??""}`.trim())}getError(){return this.webglInitError}render(e){!this.gl||!this.config||!this.spriteImg||!this.glProgram||!this.glAttribs||!this.glUniforms||!this.glBuffer||(this.renderWebGL(e),this.renderFinalLine(e))}onResize(e,t){!this.gl||!this.canvas||this.gl.viewport(0,0,e,t)}showFinalLine(){this.config?.finalSpinLine&&(this.showLine=!0,this.lineRenderer?.showLine())}hideFinalLine(){this.showLine=!1,this.lineRenderer?.hideLine()}dispose(){this.gl&&(this.glTexture&&this.gl.deleteTexture(this.glTexture),this.glBuffer&&this.gl.deleteBuffer(this.glBuffer),this.glProgram&&this.gl.deleteProgram(this.glProgram),this.lineRenderer&&this.lineRenderer.dispose(this.gl),this.glTexture=null,this.glBuffer=null,this.glProgram=null,this.glAttribs=null,this.glUniforms=null,this.gl=null,this.webglInitError=null)}tryInitWebGL(){if(!this.canvas||!this.spriteImg||!this.config)return;this.dispose(),this.webglInitError=null;const e=this.canvas.getContext("webgl2",{antialias:!0,alpha:!0})??this.canvas.getContext("webgl",{antialias:!0,alpha:!0})??this.canvas.getContext("experimental-webgl",{antialias:!0,alpha:!0});if(!e){const l=document.createElement("canvas"),d=typeof WebGLRenderingContext<"u",o=l.getContext("webgl2")??l.getContext("webgl");this.webglInitError=`context is null (browser WebGL=${d?"yes":"no"}, fallback canvas=${o?"yes":"no"})`,console.warn("ReelDeal WebGL:",this.webglInitError);return}const t=this.createWebGLProgram(e,be,Se);if(!t){this.webglInitError="shader program failed to compile/link (see console)",console.warn("ReelDeal WebGL:",this.webglInitError);return}const i=e.createBuffer();if(!i){this.webglInitError="failed to create vertex buffer",console.warn("ReelDeal WebGL:",this.webglInitError),e.deleteProgram(t);return}e.bindBuffer(e.ARRAY_BUFFER,i);const s=new Float32Array([-1,-1,0,1,1,-1,1,1,-1,1,0,0,1,1,1,0]);e.bufferData(e.ARRAY_BUFFER,s,e.STATIC_DRAW);const n=e.createTexture();if(!n){this.webglInitError="failed to create texture",console.warn("ReelDeal WebGL:",this.webglInitError),e.deleteBuffer(i),e.deleteProgram(t);return}e.bindTexture(e.TEXTURE_2D,n),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,this.spriteImg);const a=l=>l>0&&(l&l-1)===0;a(this.config.spriteWidth)&&a(this.config.spriteHeight)?(e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.REPEAT),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.REPEAT),e.generateMipmap(e.TEXTURE_2D),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR_MIPMAP_LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR)):(e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE)),e.useProgram(t),e.clearColor(0,0,0,0),e.enable(e.BLEND),e.blendFunc(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA),this.gl=e,this.glProgram=t,this.glBuffer=i,this.glTexture=n,this.glAttribs={pos:e.getAttribLocation(t,"a_pos"),uv:e.getAttribLocation(t,"a_uv")},this.glUniforms={texture:e.getUniformLocation(t,"u_tex"),reelX:e.getUniformLocation(t,"u_reelX"),reelW:e.getUniformLocation(t,"u_reelW"),offsetPx:e.getUniformLocation(t,"u_offsetPx"),frameHeight:e.getUniformLocation(t,"u_frameHeight"),spriteHeight:e.getUniformLocation(t,"u_spriteHeight"),visibleSlots:e.getUniformLocation(t,"u_visibleSlots"),heightScale:e.getUniformLocation(t,"u_heightScale"),edgePower:e.getUniformLocation(t,"u_edgePower"),warpAngle:e.getUniformLocation(t,"u_warpAngle"),blurPx:e.getUniformLocation(t,"u_blurPx")},this.config.finalSpinLine&&this.lineRenderer&&this.lineRenderer.init(e,this.config.finalSpinLine)}createWebGLProgram(e,t,i){const s=this.createWebGLShader(e,e.VERTEX_SHADER,t),n=this.createWebGLShader(e,e.FRAGMENT_SHADER,i);if(!s||!n)return s&&e.deleteShader(s),n&&e.deleteShader(n),null;const a=e.createProgram();if(!a)return null;if(e.attachShader(a,s),e.attachShader(a,n),e.linkProgram(a),!e.getProgramParameter(a,e.LINK_STATUS)){const u=e.getProgramInfoLog(a)??"unknown link error";return this.webglInitError=`program link failed: ${u}`,console.warn("ReelDeal WebGL: program link failed",u),e.deleteProgram(a),e.deleteShader(s),e.deleteShader(n),null}return e.detachShader(a,s),e.detachShader(a,n),e.deleteShader(s),e.deleteShader(n),a}createWebGLShader(e,t,i){const s=e.createShader(t);if(!s)return null;if(e.shaderSource(s,i),e.compileShader(s),!e.getShaderParameter(s,e.COMPILE_STATUS)){const n=e.getShaderInfoLog(s)??"unknown compile error";return this.webglInitError=`shader compile failed: ${n}`,console.warn("ReelDeal WebGL: shader compile failed",n),e.deleteShader(s),null}return s}getSpinBlurPxForReel(e,t){if(!this.config)return 0;const i=Math.abs(t[e]??0),s=Math.max(1,this.config.spinBlur.speedAtMax);return v(i/s)*Math.max(0,this.config.spinBlur.maxPx)}renderWebGL(e){if(!this.gl||!this.glProgram||!this.glAttribs||!this.glUniforms||!this.glTexture||!this.glBuffer||!this.config||!this.spriteImg||!this.canvas)return;const t=this.gl;t.useProgram(this.glProgram),t.bindBuffer(t.ARRAY_BUFFER,this.glBuffer),t.enableVertexAttribArray(this.glAttribs.pos),t.vertexAttribPointer(this.glAttribs.pos,2,t.FLOAT,!1,16,0),t.enableVertexAttribArray(this.glAttribs.uv),t.vertexAttribPointer(this.glAttribs.uv,2,t.FLOAT,!1,16,8),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.glTexture),t.uniform1i(this.glUniforms.texture,0),t.uniform1f(this.glUniforms.frameHeight,this.config.frameHeight),t.uniform1f(this.glUniforms.spriteHeight,this.config.spriteHeight),t.uniform1f(this.glUniforms.visibleSlots,this.config.visibleSlots),t.uniform1f(this.glUniforms.heightScale,this.config.heightScale),t.uniform1f(this.glUniforms.edgePower,Math.max(.5,this.config.webglShading.edgePower));const i=this.config.webglShading,s=Math.max(0,i.warpAngleDeg)*Math.PI/180;t.uniform1f(this.glUniforms.warpAngle,s),t.clear(t.COLOR_BUFFER_BIT);const n=1/this.config.reels;for(let a=0;a<this.config.reels;a+=1){t.uniform1f(this.glUniforms.reelX,a*n),t.uniform1f(this.glUniforms.reelW,n);const u=D(e.offsets[a],this.config.spriteHeight);t.uniform1f(this.glUniforms.offsetPx,u),t.uniform1f(this.glUniforms.blurPx,this.getSpinBlurPxForReel(a,e.velocities)),t.drawArrays(t.TRIANGLE_STRIP,0,4)}}renderFinalLine(e){!this.showLine||!this.config?.finalSpinLine||!this.gl||!this.canvas||!this.lineRenderer||this.lineRenderer.render(this.gl,this.canvas,this.config.finalSpinLine,e.dpr)}}const we=(r,e)=>{const t=document.getElementById(r);if(!t)throw new Error(`${e} not found: ${r}`);return t},W=(r,e)=>typeof r=="string"?we(r,e):r,Ee=5,V=.72,Ae=.28,Pe=7,Re=55,Le=300,Te=.35;class Me{canvas;container;opts;visibleSlots=3;heightScale=2/3;centerIndex=1;reels;spinConfig;spinBlur;webglShading;idleBob;maxDpr;finalSpinLine;spriteImg=null;spriteWidth=0;spriteHeight=0;frameHeight=0;width=0;height=0;dpr=1;offsets;velocities;lastVelT=null;lastVelOffsets;idleStartTime=null;idleBaseOffsets;renderer=null;webglInitError=null;anim=null;loop=new pe;pendingSpinResolvers=[];showFinalLine=!1;isLastSpin=!1;resizeObserver=null;resizeQueued=!1;button=null;uiController=null;spinQueueController;constructor(e){if(!e.canvas)throw new Error("ReelDeal: canvas is required");if(!e.container)throw new Error("ReelDeal: container is required");if(!e.sprite)throw new Error("ReelDeal: sprite is required");if(e.slotCount===void 0)throw new Error("ReelDeal: slotCount is required");const t={...e,canvas:e.canvas,container:e.container,sprite:e.sprite,slotCount:M(e.slotCount,1,Number.MAX_SAFE_INTEGER)};this.opts=t,this.reels=M(e.reels??1,1,Ee),this.spinConfig=z(e.spinConfig),this.spinBlur=q(e.spinBlur),this.webglShading=K(e.webglShading),this.idleBob=$(e.idleBob),this.maxDpr=Q(e.maxDpr),this.finalSpinLine=j(e.finalSpinLine),this.canvas=W(t.canvas,"Canvas"),this.container=W(t.container,"Container"),this.offsets=new Float32Array(this.reels),this.velocities=new Float32Array(this.reels),this.lastVelOffsets=new Float32Array(this.reels),this.idleBaseOffsets=new Float32Array(this.reels),this.spinQueueController=new ge({slotCount:t.slotCount,reels:this.reels,initialQueue:t.queuedSpinStates})}initRenderer(){if(!this.spriteImg)return;const e=new Z;try{e.init({canvas:this.canvas,spriteImg:this.spriteImg,spriteWidth:this.spriteWidth,spriteHeight:this.spriteHeight,frameHeight:this.frameHeight,reels:this.reels,visibleSlots:this.visibleSlots,heightScale:this.heightScale,spinBlur:this.spinBlur,webglShading:this.webglShading,finalSpinLine:this.finalSpinLine}),this.webglInitError=e.getError(),this.renderer=e}catch(t){throw this.webglInitError=e.getError()??(t instanceof Error?t.message:String(t)),t}}async init(){const e=await Y(this.opts.sprite,this.opts.slotCount);if(this.spriteImg=e.image,this.spriteWidth=e.spriteWidth,this.frameHeight=e.frameHeight,this.spriteHeight=e.spriteHeight,this.initRenderer(),!this.renderer)throw new Error(`WebGL is not supported or failed to initialize. ${this.webglInitError??""}`.trim());this.syncContainerLayoutVars(),this.ensureCanvasCoversContainer(),this.setupResizeWatcher(),this.resizeToContainer(),this.setSegments(this.opts.initialSegments),this.button=this.resolveButton(),this.uiController=new me(this.button,this.handleButtonClick),this.uiController.updateAvailability(!0),this.render(),this.startLoop()}destroy(){this.stop(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.uiController?.dispose(),this.uiController=null,this.renderer&&(this.renderer.dispose(),this.renderer=null)}ensureCanvasCoversContainer(){this.canvas!==this.container&&this.container.contains(this.canvas)&&(this.canvas.style.width||(this.canvas.style.width="100%"),this.canvas.style.height||(this.canvas.style.height="100%"),this.canvas.style.display||(this.canvas.style.display="block"))}syncContainerLayoutVars(){const e=this.canvas===this.container?this.canvas:this.container;e.style.setProperty("--reels",String(this.reels)),e.style.setProperty("--visible-slots",String(this.visibleSlots)),e.style.aspectRatio=`${this.reels} / ${this.visibleSlots*this.heightScale}`,this.canvas!==this.container&&(this.container.style.overflow="hidden")}setSegments(e){this.ensureReady();const t=e??[];for(let i=0;i<this.reels;i+=1){const s=t[i]??0,n=M(s,0,this.opts.slotCount-1);this.offsets[i]=n*this.frameHeight-this.centerIndex*this.frameHeight,this.lastVelOffsets[i]=this.offsets[i],this.velocities[i]=0,this.idleBaseOffsets[i]=this.offsets[i]}this.lastVelT=null,this.idleStartTime=null,this.anim=null,this.stop(),this.render(),this.startLoop()}setSegment(e){this.setSegments(new Array(this.reels).fill(e))}spinOnce(e,t){this.ensureReady();const i=Array.isArray(e)?{stopAtSegments:e,config:t}:e;if(i.signal?.aborted)return Promise.reject(i.signal.reason??new DOMException("Aborted","AbortError"));const s=this.getSpinConfig(i.config??t),n=s.speedPxS>0,a=this.getStopSpringOvershootPx(),u=Math.max(0,Le),l=ue({stopAtSegments:i.stopAtSegments,currentOffsets:this.offsets,reels:this.reels,frameHeight:this.frameHeight,spriteHeight:this.spriteHeight,centerIndex:this.centerIndex,slotCount:this.opts.slotCount,spinConfig:s,stopSpringOvershootPx:a,stopSpringSettleMs:u,useSpeed:n});if(l.totalDelta===0||l.allSpinZero&&l.allSettleZero){for(let h=0;h<this.reels;h+=1)this.offsets[h]=l.targets[h];return this.anim=null,this.resolvePendingSpins(),this.render(),Promise.resolve()}const d=performance.now();this.anim={startTime:d,startOffsets:l.startOffsets,deltas:l.deltas,durationMs:l.durations,settleDurationMs:l.settleDurationMs,targets:l.targets},this.idleStartTime=null,this.startLoop();const o=new AbortController,c=()=>{const h=o.signal.reason??i.signal?.reason??"Spin aborted";this.stop(h)},b=()=>{o.abort(i.signal?.reason??new DOMException("Aborted","AbortError"))};i.signal&&i.signal.addEventListener("abort",b,{once:!0});let p=null;return i.timeoutMs&&i.timeoutMs>0&&(p=window.setTimeout(()=>{o.abort(new DOMException("Spin timed out","TimeoutError"))},i.timeoutMs)),new Promise((h,g)=>{const m=()=>{p!==null&&window.clearTimeout(p),o.signal.removeEventListener("abort",c),i.signal&&i.signal.removeEventListener("abort",b)};if(o.signal.aborted){m(),g(o.signal.reason??new DOMException("Aborted","AbortError"));return}o.signal.addEventListener("abort",c,{once:!0}),this.pendingSpinResolvers.push({resolve:()=>{m(),h()},reject:S=>{m(),g(S)},cleanup:m})})}getSpinConfig(e){return e?z({...this.spinConfig,...e}):this.spinConfig}getStopSpringOvershootPx(){const e=this.frameHeight*Te,t=Math.max(0,Re);return Math.min(t,e)}async spinQueue(e){this.spinQueueController.reset(e),this.uiController?.updateAvailability(!0);for(let t=0;t<e.length;t+=1){const i=e[t];await this.spinOnce(i.stopAtSegments),i.callback?.(t,i.stopAtSegments)}this.uiController?.updateAvailability(!0)}stop(e){this.loop.stop(),this.anim=null,e!==void 0?this.rejectPendingSpins(e):this.resolvePendingSpins(),this.lastVelT=null,this.velocities.fill(0)}resolvePendingSpins(){if(this.pendingSpinResolvers.length===0)return;const e=this.pendingSpinResolvers;this.pendingSpinResolvers=[];for(const{resolve:t,cleanup:i}of e)i(),t()}rejectPendingSpins(e){if(this.pendingSpinResolvers.length===0)return;const t=this.pendingSpinResolvers;this.pendingSpinResolvers=[];for(const{reject:i,cleanup:s}of t)s(),i(e)}requestResize(){this.queueResize()}ensureReady(){if(!this.spriteImg||this.spriteWidth<=0||this.spriteHeight<=0||this.frameHeight<=0)throw new Error("Sprite is not ready. Call init() and await it first.")}handleButtonClick=async()=>{this.hideFinalLine();const e=this.spinQueueController.consume();this.isLastSpin=e.isLast,this.uiController?.updateAvailability(!0),await this.spinOnce(e.state.stopAtSegments),this.isLastSpin&&this.finalSpinLine&&this.showFinalLineEffect()};resolveButton(){return this.opts.button?W(this.opts.button,"Button"):null}shouldAnimate(){return this.anim!==null||this.idleBob!==null||this.showFinalLine}startLoop(){this.loop.isRunning()||this.shouldAnimate()&&this.loop.start(this.tick)}tick=e=>{if(this.anim){const{startTime:t,durationMs:i,startOffsets:s,deltas:n,settleDurationMs:a,targets:u}=this.anim,l=e-t;let d=!0;for(let o=0;o<this.reels;o+=1){const c=i[o]??0,b=a[o]??0,p=u[o]??s[o]+n[o];if(c>0&&l<c){const h=Math.min(1,l/c),g=le(h);h<1&&(d=!1);const m=s[o]+n[o]*g;let S=0;if(h>V&&this.frameHeight>0){const A=(1-v((h-V)/Ae))**2,P=m/this.frameHeight*Math.PI*2,O=Math.min(Pe,this.frameHeight*.06);S=Math.sin(P)*A*O}this.offsets[o]=m+S}else if(b>0){const h=c<=0?l/b:(l-c)/b,g=v(h),m=s[o]+n[o]-p,S=fe(m,g);g<1&&(d=!1),this.offsets[o]=p+S}else this.offsets[o]=p}if(this.updateVelocity(e),this.render(),d){for(let o=0;o<this.reels;o+=1){const c=this.anim.targets[o]??this.anim.startOffsets[o]+this.anim.deltas[o];this.offsets[o]=c,this.idleBaseOffsets[o]=this.offsets[o]}this.updateVelocity(e);for(let o=0;o<this.reels;o+=1)this.velocities[o]=0;this.anim=null,this.idleStartTime=null,this.render(),this.resolvePendingSpins()}}else this.idleBob&&(this.updateIdle(e),this.render());return this.shouldAnimate()};render(){this.spriteImg&&(this.width<=0||this.height<=0||this.renderer&&this.renderer.render({offsets:this.offsets,velocities:this.velocities,dpr:this.dpr}))}updateVelocity(e){this.lastVelT=ce(e,{lastVelT:this.lastVelT,lastVelOffsets:this.lastVelOffsets,velocities:this.velocities},this.offsets)}updateIdle(e){this.idleBob&&(this.idleStartTime=de(e,{idleStartTime:this.idleStartTime,idleBaseOffsets:this.idleBaseOffsets,velocities:this.velocities},this.idleBob,this.offsets,this.reels))}setupResizeWatcher(){typeof ResizeObserver>"u"||this.resizeObserver||(this.resizeObserver=new ResizeObserver(()=>this.queueResize()),this.resizeObserver.observe(this.container))}queueResize=()=>{this.resizeQueued||(this.resizeQueued=!0,requestAnimationFrame(()=>{this.resizeQueued=!1,this.resizeToContainer()}))};resizeToContainer(){const e=this.container.clientWidth,t=this.container.clientHeight,i=this.visibleSlots*this.heightScale;if(e<=0||t<=0){this.applyResize(1,1);return}const s=i,n=this.reels,a=e/n*s;let u=e,l=a;l>t&&(l=t,u=t/s*n);const d=Math.max(1,Math.floor(u)),o=Math.max(1,Math.floor(l));this.applyResize(d,o),this.render()}applyViewportCrop(){this.ensureCanvasCoversContainer(),this.canvas.style.transform=""}applyResize(e,t){const i=Math.max(1,Math.floor(e)),s=Math.max(1,Math.floor(t)),n=this.getTargetDpr();this.width=i,this.height=s,this.dpr=n,this.canvas.width=Math.floor(this.width*this.dpr),this.canvas.height=Math.floor(this.height*this.dpr),this.renderer&&this.renderer.onResize(this.canvas.width,this.canvas.height),this.applyViewportCrop()}getTargetDpr(){const e=window.devicePixelRatio||1,t=Math.max(1,e);return Math.min(this.maxDpr,t)}hideFinalLine(){this.showFinalLine=!1,this.renderer?.hideFinalLine(),this.render()}showFinalLineEffect(){this.finalSpinLine&&(this.showFinalLine=!0,this.renderer?.showFinalLine(),this.startLoop())}}exports.ReelDeal=Me;exports.WebGLRenderer=Z;exports.defaultIdleBob=E;exports.defaultSpinBlur=y;exports.defaultSpinConfig=w;exports.loadSprite=Y;exports.normalizeFinalSpinLine=j;exports.normalizeIdleBob=$;exports.normalizeMaxDpr=Q;exports.normalizeSpinBlur=q;exports.normalizeSpinConfig=z;exports.normalizeWebGLShading=K;
|
|
81
135
|
//# sourceMappingURL=index.cjs.js.map
|