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/dist/index.umd.js
CHANGED
|
@@ -1,81 +1,135 @@
|
|
|
1
|
-
(function(m,g){typeof exports=="object"&&typeof module<"u"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(m=typeof globalThis<"u"?globalThis:m||self,g(m.ReelDeal={}))})(this,(function(m){"use strict";const g={minSpins:2,durationMs:4e3,speedPxS:4e3,staggerMs:300},w={maxPx:4,speedAtMax:2200},x={amplitudePx:9,speedHz:.25,phaseOffsetRad:.9,rampMs:800},H=.1,A=.6,B=(a,t,s)=>Math.max(t,Math.min(s,Math.floor(a))),I=(a,t)=>(a%t+t)%t,v=a=>Math.max(0,Math.min(1,a)),G=a=>{const t=v(a);return t**2*(2-t)},Y=a=>{const t=v(a);return t+t**2-t**3},q=a=>{const t=v(a);if(t<=H){const i=t/H;return G(i)*H}if(t<A)return t;const s=(t-A)/(1-A);return A+Y(s)*(1-A)},L=(a,t)=>typeof a=="number"&&Number.isFinite(a)?a:t,_=(a,t)=>Math.max(0,L(a,t)),R=(a,t)=>Math.max(0,Math.floor(L(a,t))),$=2,X=a=>{const t={...g,...a??{}};return{minSpins:R(t.minSpins,g.minSpins),durationMs:R(t.durationMs,g.durationMs??0),speedPxS:R(t.speedPxS,g.speedPxS??0),staggerMs:R(t.staggerMs,g.staggerMs??0)}},Q=a=>{const t={...w,...a??{}};return{maxPx:_(t.maxPx,w.maxPx),speedAtMax:_(t.speedAtMax,w.speedAtMax)}},K=a=>{if(a===!1)return null;const t={...x,...a??{}};return{amplitudePx:_(t.amplitudePx,x.amplitudePx),speedHz:_(t.speedHz,x.speedHz),phaseOffsetRad:L(t.phaseOffsetRad,x.phaseOffsetRad),rampMs:R(t.rampMs,x.rampMs)}},j=a=>Math.max(1,L(a,$)),Z=(a,t)=>{const s=document.getElementById(a);if(!s)throw new Error(`${t} not found: ${a}`);return s},C=(a,t)=>typeof a=="string"?Z(a,t):a,J=5,z=.72,tt=.28,et=7,N=.85,st=1-N,it=30,rt=500,nt=1.25,at=.25,F={warpAngleDeg:360,edgePower:.5},ot=a=>({warpAngleDeg:_(a?.warpAngleDeg,F.warpAngleDeg),edgePower:_(a?.edgePower,F.edgePower)});class lt{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=B(t.reels??1,1,J),this.spinConfig=X(t.spinConfig),this.spinBlur=Q(t.spinBlur),this.webglShading=ot(t.webglShading),this.idleBob=K(t.idleBob),this.maxDpr=j(t.maxDpr),this.canvas=C(t.canvas,"Canvas"),this.container=C(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=B(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,rt),n=c>0&&f>0,h=new Array(this.reels),d=new Array(this.reels),S=new Array(this.reels),p=new Array(this.reels),b=new Array(this.reels);for(let o=0;o<this.reels;o+=1){const ht=B(t[o]??0,0,this.opts.slotCount-1)*this.frameHeight-this.centerIndex*this.frameHeight,ut=I(ht,this.spriteHeight),ft=I(this.offsets[o],this.spriteHeight),U=I(ft-ut,this.spriteHeight);let E=-(i*this.spriteHeight+U);if(u&&l>0){const D=r*l/1e3,M=Math.max(i,Math.ceil(Math.max(0,D-U)/this.spriteHeight));E=-(U+M*this.spriteHeight)}if(e>0&&u){const D=r*e*o/1e3,M=Math.ceil(D/this.spriteHeight);E-=M*this.spriteHeight}h[o]=this.offsets[o],b[o]=this.offsets[o]+E;let W=E,k=0;if(n&&E!==0&&f>0){const M=Math.sign(E)*f;W+=M,k=c}d[o]=W,p[o]=k,u?S[o]=Math.max(0,Math.round(Math.abs(W)/r*1e3)):S[o]=Math.max(0,l+e*o)}const P=d.reduce((o,y)=>o+Math.abs(y),0),T=S.every(o=>o<=0),V=p.every(o=>o<=0);if(P===0||T&&V){for(let o=0;o<this.reels;o+=1)this.offsets[o]=b[o];return this.anim=null,this.resolvePendingSpins(),this.render(),Promise.resolve()}const O=performance.now();return this.anim={startTime:O,startOffsets:h,deltas:d,durationMs:S,settleDurationMs:p,targets:b},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*at,s=Math.max(0,it);return Math.min(s,t)}getStopSpringOffset(t,s){if(t===0)return 0;const i=Math.exp(-4*s),e=Math.PI*2*nt*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?C(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,S=u[n]??e[n]+r[n];if(h>0&&f<h){const p=Math.min(1,f/h),b=q(p);p<1&&(c=!1);const P=e[n]+r[n]*b;let T=0;if(p>z&&this.frameHeight>0){const O=(1-v((p-z)/tt))**2,o=P/this.frameHeight*Math.PI*2,y=Math.min(et,this.frameHeight*.06);T=Math.sin(o)*O*y}this.offsets[n]=P+T}else if(d>0){const p=h<=0?f/d:(f-h)/d,b=v(p),P=e[n]+r[n]-S,T=this.getStopSpringOffset(P,b);b<1&&(c=!1),this.offsets[n]=S+T}else this.offsets[n]=S}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 v(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]*N+e*st,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=G(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
|
+
(function(d,P){typeof exports=="object"&&typeof module<"u"?P(exports):typeof define=="function"&&define.amd?define(["exports"],P):(d=typeof globalThis<"u"?globalThis:d||self,P(d.ReelDeal={}))})(this,(function(d){"use strict";const P=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})}),k=async(r,e)=>{const t=typeof r=="string"?new Image:r;typeof r=="string"&&(t.src=r),(!t.complete||t.naturalWidth<=0)&&await P(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},C={maxPx:4,speedAtMax:2200},E={amplitudePx:9,speedHz:.25,phaseOffsetRad:.9,rampMs:800},N={warpAngleDeg:60,edgePower:4.8},V={thickness:6,intensity:1},U=.1,R=.6,T=(r,e,t)=>Math.max(e,Math.min(t,Math.floor(r))),D=(r,e)=>(r%e+e)%e,_=r=>Math.max(0,Math.min(1,r)),te=r=>{const e=_(r);return e**2*(2-e)},ie=r=>{const e=_(r);return e+e**2-e**3},se=r=>{const e=_(r);if(e<=U){const i=e/U;return te(i)*U}if(e<R)return e;const t=(e-R)/(1-R);return R+ie(t)*(1-R)},H=(r,e)=>typeof r=="number"&&Number.isFinite(r)?r:e,x=(r,e)=>Math.max(0,H(r,e)),L=(r,e)=>Math.max(0,Math.floor(H(r,e))),re=2,F=r=>{const e={...w,...r??{}};return{minSpins:L(e.minSpins,w.minSpins??0),durationMs:L(e.durationMs,w.durationMs??0),speedPxS:L(e.speedPxS,w.speedPxS??0),staggerMs:L(e.staggerMs,w.staggerMs??0)}},Y=r=>{const e={...C,...r??{}};return{maxPx:x(e.maxPx,C.maxPx),speedAtMax:x(e.speedAtMax,C.speedAtMax)}},q=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:L(e.rampMs,E.rampMs)}},$=r=>Math.max(1,H(r,re)),Q=r=>({warpAngleDeg:x(r?.warpAngleDeg,N.warpAngleDeg),edgePower:x(r?.edgePower,N.edgePower)}),j=r=>r===!1||!r?null:{thickness:x(r.thickness,V.thickness),intensity:_(r.intensity??V.intensity)},ne=r=>{const{stopAtSegments:e,currentOffsets:t,reels:i,frameHeight:s,spriteHeight:n,centerIndex:a,slotCount:u,spinConfig:l,stopSpringOvershootPx:p,stopSpringSettleMs:o,useSpeed:c}=r,S=new Array(i),m=new Array(i),h=new Array(i),g=new Array(i),b=new Array(i),{minSpins:v,staggerMs:O,speedPxS:M,durationMs:I}=l,z=o>0&&p>0;for(let f=0;f<i;f+=1){const Le=T(e[f]??0,0,u-1)*s-a*s,Me=D(Le,n),Ie=D(t[f],n),G=D(Ie-Me,n);let A=-(v*n+G);if(c&&I>0){const B=M*I/1e3,y=Math.max(v,Math.ceil(Math.max(0,B-G)/n));A=-(G+y*n)}if(O>0&&c){const B=M*O*f/1e3,y=Math.ceil(B/n);A-=y*n}S[f]=t[f],b[f]=t[f]+A;let X=A,ee=0;if(z&&A!==0&&p>0){const y=Math.sign(A)*p;X+=y,ee=o}m[f]=X,g[f]=ee,c?h[f]=Math.max(0,Math.round(Math.abs(X)/M*1e3)):h[f]=Math.max(0,I+O*f)}const Pe=m.reduce((f,J)=>f+Math.abs(J),0),Re=h.every(f=>f<=0),Te=g.every(f=>f<=0);return{startOffsets:S,deltas:m,durations:h,settleDurationMs:g,targets:b,totalDelta:Pe,allSpinZero:Re,allSettleZero:Te}},ae=(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)},oe=(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},le=(r,e,t,i,s)=>{e.idleStartTime===null&&(e.idleStartTime=r);const{amplitudePx:n,speedHz:a,phaseOffsetRad:u,rampMs:l}=t,p=r-e.idleStartTime,o=p/1e3,c=Math.max(0,Math.min(1,p/Math.max(1,l))),S=Math.min(1,Math.max(0,c*c*(2-c))),m=a*Math.PI*2;for(let h=0;h<s;h+=1){const g=o*m+h*u;i[h]=e.idleBaseOffsets[h]+Math.sin(g)*(n*S),e.velocities[h]=0}return e.idleStartTime};class he{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 ue{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(T(Math.floor(Math.random()*e),0,e-1));return t}}class fe{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 ce=`
|
|
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
|
+
`,de=`
|
|
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
|
+
`,pe=`
|
|
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
|
+
`,me=`
|
|
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 ge{program=null;buffer=null;attribs=null;uniforms=null;startTime=null;show=!1;init(e,t){const i=this.createProgram(e,pe,me);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 K{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 ge: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"),p=typeof WebGLRenderingContext<"u",o=l.getContext("webgl2")??l.getContext("webgl");this.webglInitError=`context is null (browser WebGL=${p?"yes":"no"}, fallback canvas=${o?"yes":"no"})`,console.warn("ReelDeal WebGL:",this.webglInitError);return}const t=this.createWebGLProgram(e,ce,de);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 _(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 be=(r,e)=>{const t=document.getElementById(r);if(!t)throw new Error(`${e} not found: ${r}`);return t},W=(r,e)=>typeof r=="string"?be(r,e):r,Se=5,Z=.72,ve=.28,_e=7,xe=55,we=300,Ee=.35;class Ae{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 he;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:T(e.slotCount,1,Number.MAX_SAFE_INTEGER)};this.opts=t,this.reels=T(e.reels??1,1,Se),this.spinConfig=F(e.spinConfig),this.spinBlur=Y(e.spinBlur),this.webglShading=Q(e.webglShading),this.idleBob=q(e.idleBob),this.maxDpr=$(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 ue({slotCount:t.slotCount,reels:this.reels,initialQueue:t.queuedSpinStates})}initRenderer(){if(!this.spriteImg)return;const e=new K;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 k(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 fe(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=T(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,we),l=ne({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 p=performance.now();this.anim={startTime:p,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)},S=()=>{o.abort(i.signal?.reason??new DOMException("Aborted","AbortError"))};i.signal&&i.signal.addEventListener("abort",S,{once:!0});let m=null;return i.timeoutMs&&i.timeoutMs>0&&(m=window.setTimeout(()=>{o.abort(new DOMException("Spin timed out","TimeoutError"))},i.timeoutMs)),new Promise((h,g)=>{const b=()=>{m!==null&&window.clearTimeout(m),o.signal.removeEventListener("abort",c),i.signal&&i.signal.removeEventListener("abort",S)};if(o.signal.aborted){b(),g(o.signal.reason??new DOMException("Aborted","AbortError"));return}o.signal.addEventListener("abort",c,{once:!0}),this.pendingSpinResolvers.push({resolve:()=>{b(),h()},reject:v=>{b(),g(v)},cleanup:b})})}getSpinConfig(e){return e?F({...this.spinConfig,...e}):this.spinConfig}getStopSpringOvershootPx(){const e=this.frameHeight*Ee,t=Math.max(0,xe);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 p=!0;for(let o=0;o<this.reels;o+=1){const c=i[o]??0,S=a[o]??0,m=u[o]??s[o]+n[o];if(c>0&&l<c){const h=Math.min(1,l/c),g=se(h);h<1&&(p=!1);const b=s[o]+n[o]*g;let v=0;if(h>Z&&this.frameHeight>0){const M=(1-_((h-Z)/ve))**2,I=b/this.frameHeight*Math.PI*2,z=Math.min(_e,this.frameHeight*.06);v=Math.sin(I)*M*z}this.offsets[o]=b+v}else if(S>0){const h=c<=0?l/S:(l-c)/S,g=_(h),b=s[o]+n[o]-m,v=ae(b,g);g<1&&(p=!1),this.offsets[o]=m+v}else this.offsets[o]=m}if(this.updateVelocity(e),this.render(),p){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=oe(e,{lastVelT:this.lastVelT,lastVelOffsets:this.lastVelOffsets,velocities:this.velocities},this.offsets)}updateIdle(e){this.idleBob&&(this.idleStartTime=le(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 p=Math.max(1,Math.floor(u)),o=Math.max(1,Math.floor(l));this.applyResize(p,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())}}d.ReelDeal=Ae,d.WebGLRenderer=K,d.defaultIdleBob=E,d.defaultSpinBlur=C,d.defaultSpinConfig=w,d.loadSprite=k,d.normalizeFinalSpinLine=j,d.normalizeIdleBob=q,d.normalizeMaxDpr=$,d.normalizeSpinBlur=Y,d.normalizeSpinConfig=F,d.normalizeWebGLShading=Q,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
81
135
|
//# sourceMappingURL=index.umd.js.map
|