pixi-reels 2.7.0 → 2.8.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/ReelSymbol-1IDr3_Ti.cjs +2 -0
  3. package/dist/ReelSymbol-1IDr3_Ti.cjs.map +1 -0
  4. package/dist/{ReelSymbol-BvdqdXMY.js → ReelSymbol-D80YG0fh.js} +3 -3
  5. package/dist/ReelSymbol-D80YG0fh.js.map +1 -0
  6. package/dist/{SpineSymbol-Do0zw5ad.cjs → SpineSymbol-5MTpMqXG.cjs} +2 -2
  7. package/dist/{SpineSymbol-Do0zw5ad.cjs.map → SpineSymbol-5MTpMqXG.cjs.map} +1 -1
  8. package/dist/{SpineSymbol-C6FYNcwP.js → SpineSymbol-D4Qq_S6v.js} +2 -2
  9. package/dist/{SpineSymbol-C6FYNcwP.js.map → SpineSymbol-D4Qq_S6v.js.map} +1 -1
  10. package/dist/board/BoardGrid.d.ts +207 -0
  11. package/dist/board/BoardGrid.d.ts.map +1 -1
  12. package/dist/board/HoldAndWinBoard.d.ts +43 -0
  13. package/dist/board/HoldAndWinBoard.d.ts.map +1 -1
  14. package/dist/core/Reel.d.ts.map +1 -1
  15. package/dist/core/ReelSet.d.ts +33 -1
  16. package/dist/core/ReelSet.d.ts.map +1 -1
  17. package/dist/core/ReelViewport.d.ts +11 -1
  18. package/dist/core/ReelViewport.d.ts.map +1 -1
  19. package/dist/{debug-DJF_mzcM.js → debug-DPZaC56S.js} +253 -216
  20. package/dist/debug-DPZaC56S.js.map +1 -0
  21. package/dist/debug-QjdxYHVR.cjs +4 -0
  22. package/dist/debug-QjdxYHVR.cjs.map +1 -0
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +301 -116
  28. package/dist/index.js.map +1 -1
  29. package/dist/spine.cjs +1 -1
  30. package/dist/spine.js +2 -2
  31. package/dist/symbols/ReelSymbol.d.ts.map +1 -1
  32. package/dist/testing.cjs +1 -1
  33. package/dist/testing.js +2 -2
  34. package/package.json +2 -2
  35. package/dist/ReelSymbol-BvdqdXMY.js.map +0 -1
  36. package/dist/ReelSymbol-CTlZ9ihf.cjs +0 -2
  37. package/dist/ReelSymbol-CTlZ9ihf.cjs.map +0 -1
  38. package/dist/debug-BzQL0_Eq.cjs +0 -4
  39. package/dist/debug-BzQL0_Eq.cjs.map +0 -1
  40. package/dist/debug-DJF_mzcM.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # pixi-reels
2
2
 
3
+ ## 2.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#241](https://github.com/schmooky/pixi-reels/pull/241) [`0781855`](https://github.com/schmooky/pixi-reels/commit/0781855fdaa79883e7927507b72f5acd95eb3a39) Thanks [@igaming-bulochka](https://github.com/igaming-bulochka)! - Add: `BoardGrid.lift(cell)` / `HoldAndWinBoard.lift(cell)` - draw one cell's lifted art in front of every other cell's until the returned release is called, for a symbol whose one-shot must not be overlapped by a neighbour's art.
8
+
9
+ Promotion is a second render layer rather than a bigger `zIndex`, so it is a separate channel from the at-rest order: `refreshCellZIndex()` keeps ordering the lifted cell underneath the lift - and the board keeps calling it on every `place` and every landing - while the release restores the cell's place exactly, recomputing nothing. Writing `zIndex` by hand cannot express this: any landing mid-animation drops the cell back into the pack.
10
+
11
+ Lifts are reference-counted per cell, releases are idempotent, and `liftedCells` reports what is up. `reset()` and `destroy()` drop every outstanding lift; `respin()` deliberately does not, so a presentation may span one. The lift covers the art the engine hoists out of a cell for a symbol registered `unmask: true`; a masked cell is clipped to itself and cannot overlap a neighbour, so lifting one is a documented no-op.
12
+
13
+ Add: `BoardGrid.dim({ except, amount, fade })` / `HoldAndWinBoard.dim(...)` - push every cell but the named ones into the background for a beat, the partner of `lift()`. One rectangle per dimmed cell, drawn above the cells' lifted art and below anything lifted, so a lifted cell stays out in front of the dim. It fades in and back out over `fade` ms (`DEFAULT_DIM_FADE_MS`, 180) off the board's own ticker, and an interrupted fade resumes from what is on screen over the proportional share of the duration, so nothing jumps; `fade: 0` cuts. Only one dim at a time is meaningful, so a second call throws instead of silently replacing the first.
14
+
15
+ Add: `BoardGrid.dimSymbols({ except, amount, fade })` / `HoldAndWinBoard.dimSymbols(...)` - the same shape as `dim()` on a different channel: it multiplies each symbol view's `tint` towards black and leaves the cell alone, so the board's chrome, backgrounds and gaps stay as bright as they were and only the art sinks. The tint carries down the whole view, a Spine skeleton included, without touching what a symbol class tints internally. `amount` is the strength (`DEFAULT_DIM_AMOUNT`, 0.5, which is the classic `tint: 0x808080`); `0` leaves the art alone and `1` takes it to black. A dimmed cell that swaps its symbol keeps its dim: the arrival is tinted and the departure is cleaned. The two dims run on separate fade slots, so a game can sink the cells and the art at once.
16
+
17
+ Fix: `ReelSymbol.activate()` and `deactivate()` now reset `view.tint` alongside alpha, scale, rotation and filters, and so does the same-id in-place swap in `Reel._replaceSymbol` (which never touches the pool). Tint was the one mutable visual the recycle did not clear, so a symbol released while tinted - by the new board dim, or by a game tinting art of its own - carried that colour into whatever cell the pool handed it to next.
18
+
19
+ Add: `ReelSet.promote(positions)` - raise symbols above the mask and above every other symbol, with no dim, no `playWin()` and nothing to await, which previously meant asking `spotlight.show()` for a whole presentation and switching every part of it off. Views attach to the new `ReelViewport.promotedLayer` instead of being reparented, so nothing moves and nothing has to be put back. A swap under a promoted symbol ends that symbol's promotion, so the layer never holds a view the pool has taken back.
20
+
21
+ Add: `HoldAndWinBoard.refreshCellZIndex()` - already public on `BoardGrid` but unreachable from the board a game holds, which left a resolver depending on state the library does not watch with no way to say "ask me again".
22
+
23
+ A consumer that calls none of these sees no behaviour change.
24
+
3
25
  ## 2.7.0
4
26
 
5
27
  ### Minor Changes
@@ -0,0 +1,2 @@
1
+ let e=require(`pixi.js`);var t=require(`gsap`).gsap,n=class{view;_symbolId=``;_isDestroyed=!1;_gsap=t;_mainAxis=`y`;constructor(){this.view=new e.Container}get gsap(){return this._gsap}bindGsap(e){this._gsap=e}get mainAxis(){return this._mainAxis}bindMainAxis(e){this._mainAxis=e}get symbolId(){return this._symbolId}_landing=null;get landing(){return this._landing}trackLanding(e){return this._landing=e,e}resetLanding(){this._landing=null}get isDestroyed(){return this._isDestroyed}activate(e){this._symbolId=e,this.view.visible=!0,this.view.alpha=1,this.view.scale.set(1,1),this.view.rotation=0,this.view.filters=null,this.view.tint=16777215,this.view.zIndex=0,this.onActivate(e)}deactivate(){this.stopAnimation(),this.onDeactivate(),this._symbolId=``,this._landing=null,this.view.visible=!1,this.view.alpha=1,this.view.scale.set(1,1),this.view.rotation=0,this.view.filters=null,this.view.tint=16777215,this.view.zIndex=0}reset(){this.deactivate()}destroy(){this._isDestroyed||=(this.stopAnimation(),this.onDeactivate(),this.onDestroy(),this.view.destroyed||this.view.destroy({children:!0}),!0)}onDestroy(){}async playLanding(){}async playDestroy(e){let t=this.view,n=t.pivot.x,r=t.pivot.y,i=t.x,a=t.y,o=t.getLocalBounds(),s=o.x+o.width/2,c=o.y+o.height/2;t.pivot.set(s,c),t.x=i+(s-n)*t.scale.x,t.y=a+(c-r)*t.scale.y;let l=e?.delay??0,u=e?.signal,d=()=>{t.alpha=0,t.scale.set(0,0)};if(u?.aborted){d(),t.pivot.set(n,r),t.x=i,t.y=a,t.scale.set(1,1),t.alpha=0;return}await new Promise(e=>{let n=this.gsap.timeline({onComplete:()=>{u&&u.removeEventListener(`abort`,r),e()},delay:l}).to(t.scale,{x:1.1,y:1.1,duration:.06,ease:`back.out(2.5)`}).to(t.scale,{x:0,y:0,duration:.14,ease:`power3.in`},`<+=0.04`).to(t,{alpha:0,duration:.14,ease:`power3.in`},`<`),r=()=>{n.kill(),d(),e()};u&&u.addEventListener(`abort`,r,{once:!0})}),t.pivot.set(n,r),t.x=i,t.y=a,t.scale.set(1,1)}async playOut(e){await this._tweenPresence(`out`,e)}async playIn(e){await this._tweenPresence(`in`,e)}async _tweenPresence(e,t){let n=this.view,r=n.pivot.x,i=n.pivot.y,a=n.x,o=n.y,s=n.getLocalBounds(),c=s.x+s.width/2,l=s.y+s.height/2;n.pivot.set(c,l),n.x=a+(c-r)*n.scale.x,n.y=o+(l-i)*n.scale.y;let u=()=>{n.pivot.set(r,i),n.x=a,n.y=o},d=()=>{e===`out`?(n.alpha=0,n.scale.set(1,1)):(n.visible=!0,n.alpha=1,n.scale.set(1,1))},f=t?.delay??0,p=t?.signal;if(e===`in`&&(n.visible=!0,n.alpha=0,n.scale.set(.72,.72)),p?.aborted){d(),u();return}await new Promise(t=>{let r=()=>{i.kill(),d(),t()},i=this.gsap.timeline({delay:f,onComplete:()=>{p&&p.removeEventListener(`abort`,r),t()}});e===`out`?i.to(n.scale,{x:.72,y:.72,duration:.18,ease:`power2.in`}).to(n,{alpha:0,duration:.18,ease:`power2.in`},`<`):i.to(n.scale,{x:1,y:1,duration:.2,ease:`back.out(1.7)`}).to(n,{alpha:1,duration:.16,ease:`power2.out`},`<`),p&&p.addEventListener(`abort`,r,{once:!0})}),n.scale.set(1,1),u()}get cellInset(){return null}applyCellQuad(e){let t=this.view;if(e===null){t.scale.set(1,1),t.pivot.set(0,0);return}let n=(Math.hypot(e.x1-e.x0,e.y1-e.y0)+Math.hypot(e.x2-e.x3,e.y2-e.y3))/2,r=Math.hypot((e.x3+e.x2)/2-(e.x0+e.x1)/2,(e.y3+e.y2)/2-(e.y0+e.y1)/2),i=e.width>0&&e.height>0?Math.min(n/e.width,r/e.height):1,a=(e.x0+e.x1+e.x2+e.x3)/4,o=(e.y0+e.y1+e.y2+e.y3)/4;t.scale.set(i,i),t.pivot.set(e.x+e.width/2-a/i,e.y+e.height/2-o/i)}onReelSpinStart(e){}onReelSpinEnd(){}onReelAnticipationStart(){}onReelLanded(e){}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return n}});
2
+ //# sourceMappingURL=ReelSymbol-1IDr3_Ti.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-1IDr3_Ti.cjs","names":[],"sources":["../src/utils/gsap.ts","../src/symbols/ReelSymbol.ts"],"sourcesContent":["import { gsap as resolvedGsap } from 'gsap';\n\n/** The gsap namespace type, as the engine passes it around. */\nexport type Gsap = typeof resolvedGsap;\n\n/**\n * The gsap instance resolved at lib-load time. Used by any `ReelSet` whose\n * builder did not call `.gsap(...)`, and by any symbol not yet bound to a\n * set.\n *\n * **Why the engine passes gsap around at all:** under tools that resolve\n * modules through symlinked workspaces (vite + a locally-linked pixi-reels,\n * pnpm dev setups, esbuild plugin chains), the gsap import inside the lib's\n * compiled `dist/index.js` and the gsap import in the consumer's source can\n * resolve to *different module instances*. each with its own root timeline.\n * The consumer drives one, the lib's tweens live on the other, and reels\n * stall at progress 0. `ReelSetBuilder.gsap(myGsap)` hands the engine the\n * consumer's instance so both live on the same timeline.\n *\n * Held PER REEL SET, not process-wide: two sets on one stage can be driven\n * by different gsap instances, which is what a composed stage (a banner reel\n * above a main grid, a bonus board beside it) needs.\n */\nexport const DEFAULT_GSAP: Gsap = resolvedGsap;\n","import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelCellInset, ReelCellQuad, ReelLandingContext } from '../config/types.js';\nimport { DEFAULT_GSAP, type Gsap } from '../utils/gsap.js';\n\n/**\n * One visible cell on a reel. the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)`. the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()`. I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()`. the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()`. spotlight is over, return to idle.\n * - `resize(w, h)`. the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n private _gsap: Gsap = DEFAULT_GSAP;\n private _mainAxis: 'x' | 'y' = 'y';\n\n constructor() {\n this.view = new Container();\n }\n\n /**\n * The gsap instance this symbol should animate on. Use it instead of\n * importing `gsap` in a subclass: under a symlinked-workspace module\n * resolution your import and the engine's can be different instances, and\n * only this one is on the timeline the reel set actually drives.\n *\n * Bound to the owning set by `SymbolFactory`; falls back to the instance\n * resolved at lib-load time for a symbol built outside a set.\n */\n protected get gsap(): Gsap {\n return this._gsap;\n }\n\n /**\n * @internal. Called by `SymbolFactory` when the symbol is created, so a\n * pooled symbol animates on its own set's gsap rather than whichever set\n * happened to build last.\n */\n bindGsap(instance: Gsap): void {\n this._gsap = instance;\n }\n\n /**\n * The screen axis the owning set's strips travel along: `'y'` for a\n * vertical set, `'x'` for a horizontal one.\n *\n * Symbols are otherwise orientation-agnostic - `resize(width, height)` is\n * screen-space and always will be. This exists for the few effects that\n * genuinely follow travel, motion blur being the one in the box.\n */\n protected get mainAxis(): 'x' | 'y' {\n return this._mainAxis;\n }\n\n /** @internal. Bound by `SymbolFactory` from the set's orientation. */\n bindMainAxis(prop: 'x' | 'y'): void {\n this._mainAxis = prop;\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n private _landing: Promise<void> | null = null;\n\n /**\n * The landing beat this symbol started on its latest `onReelLanded()`, or\n * `null` if it started none. Stays set (resolved) until the reel moves\n * again or the symbol is pooled, so a presenter that lands cells one at a\n * time - `HoldAndWinBoard` on a lock, a win spotlight - can sequence after\n * the landing (`await symbol.landing`) instead of stomping it, and can\n * tell a symbol that already landed from one that still needs\n * `playLanding()`.\n *\n * Subclasses report a landing through {@link trackLanding}.\n */\n get landing(): Promise<void> | null {\n return this._landing;\n }\n\n /**\n * Subclass helper: record `run` as this symbol's landing beat (see\n * {@link landing}) and return it. `SpineReelSymbol` wraps its landing\n * one-shot in it; a sprite symbol wraps its settle tween.\n */\n protected trackLanding(run: Promise<void>): Promise<void> {\n this._landing = run;\n return run;\n }\n\n /** @internal Called by the reel when it leaves rest; the landing is over. */\n resetLanding(): void {\n this._landing = null;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity. Called when the symbol enters\n * the visible reel or is recycled from the pool. Resets container\n * transform / filter state for parity with deactivate().\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // Tint is a mutable visual like the rest: a symbol arriving from the pool\n // must not wear a colour some previous cell put on it. Cleared on both\n // sides of the recycle so neither half has to trust the other.\n this.view.tint = 0xffffff;\n this.view.zIndex = 0;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool. Stops\n * animations, hides the view, and resets container transform / filter\n * state so subclass decorations don't leak across recycles.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this._landing = null;\n this.view.visible = false;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // See activate(). A symbol dimmed by the board (or tinted by a game) is\n // released to the pool the moment its cell swaps, and the pool hands the\n // same instance to a different cell - which would inherit the colour.\n this.view.tint = 0xffffff;\n this.view.zIndex = 0;\n }\n\n /** Pool reset. aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.stopAnimation();\n this.onDeactivate();\n this.onDestroy();\n if (!this.view.destroyed) this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {}\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /**\n * Play the landing beat for this symbol - the settle a symbol does as its\n * reel stops, before (or instead of) any celebration. Resolves when done.\n *\n * The reel engine never calls this itself: `onReelLanded()` is the\n * engine-driven hook, fired on every landed symbol. This is the explicit\n * counterpart for a caller that lands cells one at a time - `HoldAndWinBoard`\n * calls it on each lock when built with `lockAnimation('landing')`.\n *\n * Default: resolves at once. `SpineReelSymbol` overrides it with the\n * skeleton's `landing` track; a sprite symbol can override it with a tween.\n */\n async playLanding(): Promise<void> {}\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Play the cascade-destruction animation for this symbol. Called by\n * consumers (typically via `reelSet.destroySymbols(...)`) to disintegrate\n * a winning cell before the next cascade refill drops fresh symbols in.\n *\n * Override in subclasses for art-appropriate destruction, e.g. a Spine\n * symbol can play its `disintegration` track here, or a sprite symbol can\n * swap to a shatter atlas. The promise must resolve when the symbol is no\n * longer visible.\n *\n * Default: a snappy \"poof\" centered on the symbol's bounds regardless of\n * the view's anchor. Tiny anticipation pop (~60 ms) then a fast implode to\n * `scale: 0` + `alpha: 0` (~140 ms), ~200 ms total, no rotation. Reads\n * cleanly under win-cluster pacing without competing with the win\n * presenter. The view is left at `alpha: 0` (destroyed); position / pivot\n * are restored so pool reuse via `_replaceSymbol`'s same-id fast path\n * doesn't inherit a stale pivot offset.\n *\n * `opts.delay`. seconds to wait before the animation starts. Use to\n * stagger a cluster of winners (e.g. `i * 0.015`).\n * `opts.signal`. abort signal. If aborted (now or mid-animation), the\n * tween is killed and the view is snapped to its destroyed pose\n * (`alpha: 0`, transform restored). The promise resolves normally. abort\n * means \"skip to the end,\" not \"fail\". Subclasses that override this\n * method MUST honor the signal or document why they can't (e.g. a Spine\n * `disintegration` track is uninterruptible).\n */\n async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n const view = this.view;\n // Capture original transform so pool reuse sees a clean state.\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n // Pivot to bounds-center so the scale collapses around the visual\n // centre instead of the view's (0,0) corner. and compensate position\n // so the symbol doesn't visibly jump when the pivot moves.\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n // Moving the pivot moves the view by `delta * scale`, not by `delta`: a\n // container renders a local point at `position + (point - pivot) * scale`.\n // Scale is 1 on a plain reel, which is why dropping it went unnoticed, but\n // a curved reel scales every cell and the symbol would jump on destroy.\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n const snapDestroyed = (): void => {\n view.alpha = 0;\n view.scale.set(0, 0);\n };\n\n // Pre-abort: skip the tween entirely and snap to the destroyed pose.\n if (signal?.aborted) {\n snapDestroyed();\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n view.alpha = 0;\n return;\n }\n\n await new Promise<void>((resolve) => {\n const tl = this.gsap\n .timeline({ onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, delay })\n // Brief anticipation pop. small upscale, ~60 ms, with overshoot\n // so the implode reads as a release. No rotation.\n .to(view.scale, { x: 1.1, y: 1.1, duration: 0.06, ease: 'back.out(2.5)' })\n // Snap implode. scale -> 0 + alpha -> 0 together, snappy ease-in\n // so the symbol collapses into the cell centre and is gone.\n .to(view.scale, { x: 0, y: 0, duration: 0.14, ease: 'power3.in' }, '<+=0.04')\n .to(view, { alpha: 0, duration: 0.14, ease: 'power3.in' }, '<');\n\n const onAbort = (): void => {\n tl.kill();\n snapDestroyed();\n resolve();\n };\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Restore transform. alpha stays 0 (the symbol IS destroyed). Scale\n // restored to 1 so pool reuse via `_replaceSymbol`'s same-id fast path\n // doesn't inherit a stale 0× scale; _replaceSymbol also resets scale\n // explicitly but a defensive restore here makes the destroyed cell\n // observably \"ready to be re-skinned\" between calls.\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n }\n\n /**\n * Animate this symbol AWAY, without destroying it.\n *\n * The counterpart to {@link playIn}, and the seam a mystery reveal needs:\n * the cells on screen dissolve, their identities are swapped underneath, and\n * the new ones arrive. `playDestroy` is the cascade's version of the same\n * beat and is deliberately separate - it is tuned as a \"this cell was a\n * winner and is being consumed\" poof, and a reveal is not that.\n *\n * Leaves the view hidden (`alpha: 0`) with its transform restored, so the\n * caller may swap the identity and call `playIn` next.\n *\n * Override for art-appropriate exits - a Spine symbol plays its own `out`\n * track here. Honour `opts.signal`: abort means \"snap to the end\", not\n * \"fail\", so the promise still resolves.\n *\n * Default: a ~180 ms shrink-and-fade with no overshoot, centred on the\n * symbol's bounds rather than the view origin.\n */\n async playOut(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('out', opts);\n }\n\n /**\n * Animate this symbol IN, from nothing to its resting pose.\n *\n * Owns its own start pose. A symbol that has just been re-activated is fully\n * visible (`activate()` resets alpha and scale), so an entrance that assumed\n * it started hidden would pop before it animated. This sets the hidden pose\n * first, then plays.\n *\n * Override for art-appropriate entrances - a Spine symbol plays its own `in`\n * track here. Honour `opts.signal`: abort snaps to the RESTING pose (alpha 1,\n * scale 1), because arriving is what the caller asked for.\n *\n * Default: a ~200 ms fade and scale-up with a small overshoot.\n */\n async playIn(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('in', opts);\n }\n\n /**\n * Shared body of {@link playIn} / {@link playOut}.\n *\n * One function because the two are the same tween read in opposite\n * directions, and because the fiddly part - pivoting to the visual centre so\n * the scale does not collapse toward the view's (0, 0) corner, and\n * compensating the position for the pivot move - is identical and easy to get\n * subtly wrong twice. Same correction as `playDestroy`: a container renders a\n * local point at `position + (point - pivot) * scale`, so a curved reel (which\n * scales every cell) would make the symbol jump without the `* scale` term.\n */\n private async _tweenPresence(\n direction: 'in' | 'out',\n opts?: { delay?: number; signal?: AbortSignal },\n ): Promise<void> {\n const view = this.view;\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const restorePivot = (): void => {\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n };\n const snapEnd = (): void => {\n if (direction === 'out') {\n view.alpha = 0;\n view.scale.set(1, 1);\n } else {\n view.visible = true;\n view.alpha = 1;\n view.scale.set(1, 1);\n }\n };\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n if (direction === 'in') {\n // Own the start pose: `activate()` leaves a freshly swapped symbol fully\n // visible, so without this the new art is on screen for a frame before\n // the entrance begins.\n view.visible = true;\n view.alpha = 0;\n view.scale.set(0.72, 0.72);\n }\n\n if (signal?.aborted) {\n snapEnd();\n restorePivot();\n return;\n }\n\n await new Promise<void>((resolve) => {\n const onAbort = (): void => {\n tl.kill();\n snapEnd();\n resolve();\n };\n const tl = this.gsap.timeline({\n delay,\n onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n },\n });\n if (direction === 'out') {\n tl.to(view.scale, { x: 0.72, y: 0.72, duration: 0.18, ease: 'power2.in' }).to(\n view,\n { alpha: 0, duration: 0.18, ease: 'power2.in' },\n '<',\n );\n } else {\n tl.to(view.scale, { x: 1, y: 1, duration: 0.2, ease: 'back.out(1.7)' }).to(\n view,\n { alpha: 1, duration: 0.16, ease: 'power2.out' },\n '<',\n );\n }\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Scale back to 1 either way: a hidden symbol still has to be a legal\n // resting pose, or pool reuse inherits the shrunken scale.\n view.scale.set(1, 1);\n restorePivot();\n }\n\n /**\n * The owning reel is curved: render into this projected quad instead of the\n * flat cell rectangle. `null` means the reel is flat again.\n *\n * The quad's corners are SCREEN-space and local to this view's own origin,\n * clockwise from top-left, and `width` / `height` give the flat cell box the\n * quad replaces. Called on every frame of motion and on every placement, so\n * it must be cheap and idempotent.\n *\n * **The default is an approximation.** A `Container` transform is affine, so\n * it cannot express a trapezoid; the base class fits the quad as closely as\n * an affine transform can - a UNIFORM scale about the quad's centre. Uniform\n * on purpose: symbol art is usually smaller than its cell and has a shape a\n * player recognises, and squashing one axis turns a `7` into a squashed `7`\n * rather than a `7` seen at an angle.\n *\n * Override this to render the real perspective. {@link SpriteSymbol} and\n * {@link AnimatedSpriteSymbol} do, by drawing their texture through a\n * `PerspectiveMesh`, which costs no extra render pass because the content is\n * already a texture. A symbol whose content is an arbitrary subtree (Spine, a\n * composite of sprites and text) cannot do that without rendering itself to a\n * texture every frame, so it keeps the affine fit.\n *\n * Whatever you do here, do NOT move `view.position`: the reel reads that\n * coordinate back to work out which slot this symbol is in.\n */\n /**\n * The part of its cell this symbol's art actually covers, or `null` (the\n * default) for \"all of it\".\n *\n * Slot art is usually smaller than its cell - a trimmed atlas frame is a\n * shape floating in a much bigger transparent box - and the reel needs to\n * know that to project the rectangle the art is really in. Overriding this\n * is what stops a small symbol being inflated to the cell's edges and given\n * the cell's keystone instead of its own, milder one.\n *\n * Read once per projection, so it may change with the symbol's identity.\n */\n get cellInset(): ReelCellInset | null {\n return null;\n }\n\n applyCellQuad(quad: ReelCellQuad | null): void {\n const view = this.view;\n if (quad === null) {\n view.scale.set(1, 1);\n view.pivot.set(0, 0);\n return;\n }\n // Measure the trapezoid: the mean of its two parallel edges, and the\n // distance between their midpoints.\n const nearWidth = Math.hypot(quad.x1 - quad.x0, quad.y1 - quad.y0);\n const farWidth = Math.hypot(quad.x2 - quad.x3, quad.y2 - quad.y3);\n const across = (nearWidth + farWidth) / 2;\n const along = Math.hypot(\n (quad.x3 + quad.x2) / 2 - (quad.x0 + quad.x1) / 2,\n (quad.y3 + quad.y2) / 2 - (quad.y0 + quad.y1) / 2,\n );\n // CONTAIN, not cover. A quad in the middle of the window is TALLER than\n // the flat cell (that is the drum magnifying what faces you), so a scale\n // picked to fill it would also make the symbol wider than its column and\n // overlap its neighbours - very visible on art that fills its cell\n // edge-to-edge. Taking the smaller ratio keeps every symbol inside its own\n // projected footprint at the cost of a little slack on one axis.\n const scale =\n quad.width > 0 && quad.height > 0\n ? Math.min(across / quad.width, along / quad.height)\n : 1;\n\n const cx = (quad.x0 + quad.x1 + quad.x2 + quad.x3) / 4;\n const cy = (quad.y0 + quad.y1 + quad.y2 + quad.y3) / 4;\n view.scale.set(scale, scale);\n // A container renders a local point at `position + (point - pivot) * scale`\n // and `position` must not move, so putting the flat box's centre on the\n // quad's centre has to be paid for entirely out of the pivot.\n view.pivot.set(\n quad.x + quad.width / 2 - cx / scale,\n quad.y + quad.height / 2 - cy / scale,\n );\n }\n\n /**\n * Lifecycle hook: the owning reel is spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur,\n * StaticSpinSymbol) to swap to a spin presentation automatically.\n *\n * Fired on every strip symbol (visible AND buffer cells) when the reel\n * enters the spin phase, and again with `joinedMidSpin: true` on each\n * symbol freshly installed while the reel is already spinning (pool\n * recycling wipes symbol state, so a wrapped-in symbol can't know the\n * reel is moving without this). Implementations MUST be idempotent.\n * the same instance can be notified more than once per spin.\n *\n * @param joinedMidSpin true when this symbol was installed into a reel\n * already at speed (skip start-of-spin transitions like blur ramps).\n */\n onReelSpinStart(joinedMidSpin?: boolean): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel entered its anticipation (tease) phase.\n * it is still spinning, but slowed enough that the strip is readable.\n * Spin presentations that obscure symbols (blur textures, smear\n * animations) should relax so the player can follow the tease. Also\n * fired on symbols installed while the reel is anticipating.\n * Implementations MUST be idempotent. Default: no-op.\n */\n onReelAnticipationStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n *\n * `ctx` says which reel and cell this symbol landed in, so an override can\n * play a different beat - or none - on a particular reel. The engine always\n * supplies it; it is optional only so an override written as\n * `onReelLanded()` keeps compiling. Fired before the reel's `landing` event\n * and the set's `spin:reelLanding`, by contract.\n */\n onReelLanded(ctx?: ReelLandingContext): void {}\n}\n"],"mappings":"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"}
@@ -39,10 +39,10 @@ var n = t, r = class {
39
39
  return this._isDestroyed;
40
40
  }
41
41
  activate(e) {
42
- this._symbolId = e, this.view.visible = !0, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.zIndex = 0, this.onActivate(e);
42
+ this._symbolId = e, this.view.visible = !0, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.tint = 16777215, this.view.zIndex = 0, this.onActivate(e);
43
43
  }
44
44
  deactivate() {
45
- this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this._landing = null, this.view.visible = !1, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.zIndex = 0;
45
+ this.stopAnimation(), this.onDeactivate(), this._symbolId = "", this._landing = null, this.view.visible = !1, this.view.alpha = 1, this.view.scale.set(1, 1), this.view.rotation = 0, this.view.filters = null, this.view.tint = 16777215, this.view.zIndex = 0;
46
46
  }
47
47
  reset() {
48
48
  this.deactivate();
@@ -156,4 +156,4 @@ var n = t, r = class {
156
156
  //#endregion
157
157
  export { n, r as t };
158
158
 
159
- //# sourceMappingURL=ReelSymbol-BvdqdXMY.js.map
159
+ //# sourceMappingURL=ReelSymbol-D80YG0fh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReelSymbol-D80YG0fh.js","names":[],"sources":["../src/utils/gsap.ts","../src/symbols/ReelSymbol.ts"],"sourcesContent":["import { gsap as resolvedGsap } from 'gsap';\n\n/** The gsap namespace type, as the engine passes it around. */\nexport type Gsap = typeof resolvedGsap;\n\n/**\n * The gsap instance resolved at lib-load time. Used by any `ReelSet` whose\n * builder did not call `.gsap(...)`, and by any symbol not yet bound to a\n * set.\n *\n * **Why the engine passes gsap around at all:** under tools that resolve\n * modules through symlinked workspaces (vite + a locally-linked pixi-reels,\n * pnpm dev setups, esbuild plugin chains), the gsap import inside the lib's\n * compiled `dist/index.js` and the gsap import in the consumer's source can\n * resolve to *different module instances*. each with its own root timeline.\n * The consumer drives one, the lib's tweens live on the other, and reels\n * stall at progress 0. `ReelSetBuilder.gsap(myGsap)` hands the engine the\n * consumer's instance so both live on the same timeline.\n *\n * Held PER REEL SET, not process-wide: two sets on one stage can be driven\n * by different gsap instances, which is what a composed stage (a banner reel\n * above a main grid, a bonus board beside it) needs.\n */\nexport const DEFAULT_GSAP: Gsap = resolvedGsap;\n","import { Container } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelCellInset, ReelCellQuad, ReelLandingContext } from '../config/types.js';\nimport { DEFAULT_GSAP, type Gsap } from '../utils/gsap.js';\n\n/**\n * One visible cell on a reel. the thing that actually draws.\n *\n * `ReelSymbol` is the abstract base class. Subclass it to pick a rendering\n * technology (`SpriteSymbol`, `AnimatedSpriteSymbol`, `SpineSymbol`, or a\n * custom class of your own). The reel set pools instances aggressively:\n * one instance is reused many times as it scrolls off one identity and on\n * to another, so implementations must never assume \"I was just created\".\n *\n * Required lifecycle hooks:\n *\n * - `onActivate(symbolId)`. the pool just handed me a new identity. Swap\n * texture, restart animations, bring myself out of any \"ended\" pose.\n * - `onDeactivate()`. I am about to be pooled. Pause animations, clear\n * listeners, leave myself in a clean state for the next activation.\n * - `playWin()`. the spotlight is celebrating me. Return a promise that\n * resolves when the one-shot animation is done.\n * - `stopAnimation()`. spotlight is over, return to idle.\n * - `resize(w, h)`. the reel's cell size changed (on every symbol swap).\n * Store the dimensions and reposition internal children. Forgetting\n * this is the single most common \"why do my symbols scatter\" bug.\n *\n * ```\n * create → activate(symbolId) → [playWin / stopAnimation]\n * → deactivate\n * → activate(newId) → ...\n * ```\n *\n * There's no hidden GC. Hold resources? Override `onDestroy()`.\n */\nexport abstract class ReelSymbol implements Disposable {\n /** The PixiJS container that holds this symbol's visual. */\n public readonly view: Container;\n\n private _symbolId: string = '';\n private _isDestroyed = false;\n\n private _gsap: Gsap = DEFAULT_GSAP;\n private _mainAxis: 'x' | 'y' = 'y';\n\n constructor() {\n this.view = new Container();\n }\n\n /**\n * The gsap instance this symbol should animate on. Use it instead of\n * importing `gsap` in a subclass: under a symlinked-workspace module\n * resolution your import and the engine's can be different instances, and\n * only this one is on the timeline the reel set actually drives.\n *\n * Bound to the owning set by `SymbolFactory`; falls back to the instance\n * resolved at lib-load time for a symbol built outside a set.\n */\n protected get gsap(): Gsap {\n return this._gsap;\n }\n\n /**\n * @internal. Called by `SymbolFactory` when the symbol is created, so a\n * pooled symbol animates on its own set's gsap rather than whichever set\n * happened to build last.\n */\n bindGsap(instance: Gsap): void {\n this._gsap = instance;\n }\n\n /**\n * The screen axis the owning set's strips travel along: `'y'` for a\n * vertical set, `'x'` for a horizontal one.\n *\n * Symbols are otherwise orientation-agnostic - `resize(width, height)` is\n * screen-space and always will be. This exists for the few effects that\n * genuinely follow travel, motion blur being the one in the box.\n */\n protected get mainAxis(): 'x' | 'y' {\n return this._mainAxis;\n }\n\n /** @internal. Bound by `SymbolFactory` from the set's orientation. */\n bindMainAxis(prop: 'x' | 'y'): void {\n this._mainAxis = prop;\n }\n\n get symbolId(): string {\n return this._symbolId;\n }\n\n private _landing: Promise<void> | null = null;\n\n /**\n * The landing beat this symbol started on its latest `onReelLanded()`, or\n * `null` if it started none. Stays set (resolved) until the reel moves\n * again or the symbol is pooled, so a presenter that lands cells one at a\n * time - `HoldAndWinBoard` on a lock, a win spotlight - can sequence after\n * the landing (`await symbol.landing`) instead of stomping it, and can\n * tell a symbol that already landed from one that still needs\n * `playLanding()`.\n *\n * Subclasses report a landing through {@link trackLanding}.\n */\n get landing(): Promise<void> | null {\n return this._landing;\n }\n\n /**\n * Subclass helper: record `run` as this symbol's landing beat (see\n * {@link landing}) and return it. `SpineReelSymbol` wraps its landing\n * one-shot in it; a sprite symbol wraps its settle tween.\n */\n protected trackLanding(run: Promise<void>): Promise<void> {\n this._landing = run;\n return run;\n }\n\n /** @internal Called by the reel when it leaves rest; the landing is over. */\n resetLanding(): void {\n this._landing = null;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n /**\n * Activate the symbol with a new identity. Called when the symbol enters\n * the visible reel or is recycled from the pool. Resets container\n * transform / filter state for parity with deactivate().\n */\n activate(symbolId: string): void {\n this._symbolId = symbolId;\n this.view.visible = true;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // Tint is a mutable visual like the rest: a symbol arriving from the pool\n // must not wear a colour some previous cell put on it. Cleared on both\n // sides of the recycle so neither half has to trust the other.\n this.view.tint = 0xffffff;\n this.view.zIndex = 0;\n this.onActivate(symbolId);\n }\n\n /**\n * Deactivate the symbol before returning it to the pool. Stops\n * animations, hides the view, and resets container transform / filter\n * state so subclass decorations don't leak across recycles.\n */\n deactivate(): void {\n this.stopAnimation();\n this.onDeactivate();\n this._symbolId = '';\n this._landing = null;\n this.view.visible = false;\n this.view.alpha = 1;\n this.view.scale.set(1, 1);\n this.view.rotation = 0;\n this.view.filters = null;\n // See activate(). A symbol dimmed by the board (or tinted by a game) is\n // released to the pool the moment its cell swaps, and the pool hands the\n // same instance to a different cell - which would inherit the colour.\n this.view.tint = 0xffffff;\n this.view.zIndex = 0;\n }\n\n /** Pool reset. aliases deactivate. */\n reset(): void {\n this.deactivate();\n }\n\n destroy(): void {\n if (this._isDestroyed) return;\n this.stopAnimation();\n this.onDeactivate();\n this.onDestroy();\n if (!this.view.destroyed) this.view.destroy({ children: true });\n this._isDestroyed = true;\n }\n\n /** Subclass hook: set up visuals for the given symbolId. */\n protected abstract onActivate(symbolId: string): void;\n\n /** Subclass hook: clean up visuals. */\n protected abstract onDeactivate(): void;\n\n /** Subclass hook: additional cleanup on destroy. */\n protected onDestroy(): void {}\n\n /** Play the win/highlight animation for this symbol. Resolves when complete. */\n abstract playWin(): Promise<void>;\n\n /**\n * Play the landing beat for this symbol - the settle a symbol does as its\n * reel stops, before (or instead of) any celebration. Resolves when done.\n *\n * The reel engine never calls this itself: `onReelLanded()` is the\n * engine-driven hook, fired on every landed symbol. This is the explicit\n * counterpart for a caller that lands cells one at a time - `HoldAndWinBoard`\n * calls it on each lock when built with `lockAnimation('landing')`.\n *\n * Default: resolves at once. `SpineReelSymbol` overrides it with the\n * skeleton's `landing` track; a sprite symbol can override it with a tween.\n */\n async playLanding(): Promise<void> {}\n\n /** Immediately stop any running animation and return to idle. */\n abstract stopAnimation(): void;\n\n /** Resize the symbol's visual to fit the given dimensions. */\n abstract resize(width: number, height: number): void;\n\n /**\n * Play the cascade-destruction animation for this symbol. Called by\n * consumers (typically via `reelSet.destroySymbols(...)`) to disintegrate\n * a winning cell before the next cascade refill drops fresh symbols in.\n *\n * Override in subclasses for art-appropriate destruction, e.g. a Spine\n * symbol can play its `disintegration` track here, or a sprite symbol can\n * swap to a shatter atlas. The promise must resolve when the symbol is no\n * longer visible.\n *\n * Default: a snappy \"poof\" centered on the symbol's bounds regardless of\n * the view's anchor. Tiny anticipation pop (~60 ms) then a fast implode to\n * `scale: 0` + `alpha: 0` (~140 ms), ~200 ms total, no rotation. Reads\n * cleanly under win-cluster pacing without competing with the win\n * presenter. The view is left at `alpha: 0` (destroyed); position / pivot\n * are restored so pool reuse via `_replaceSymbol`'s same-id fast path\n * doesn't inherit a stale pivot offset.\n *\n * `opts.delay`. seconds to wait before the animation starts. Use to\n * stagger a cluster of winners (e.g. `i * 0.015`).\n * `opts.signal`. abort signal. If aborted (now or mid-animation), the\n * tween is killed and the view is snapped to its destroyed pose\n * (`alpha: 0`, transform restored). The promise resolves normally. abort\n * means \"skip to the end,\" not \"fail\". Subclasses that override this\n * method MUST honor the signal or document why they can't (e.g. a Spine\n * `disintegration` track is uninterruptible).\n */\n async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n const view = this.view;\n // Capture original transform so pool reuse sees a clean state.\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n // Pivot to bounds-center so the scale collapses around the visual\n // centre instead of the view's (0,0) corner. and compensate position\n // so the symbol doesn't visibly jump when the pivot moves.\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n // Moving the pivot moves the view by `delta * scale`, not by `delta`: a\n // container renders a local point at `position + (point - pivot) * scale`.\n // Scale is 1 on a plain reel, which is why dropping it went unnoticed, but\n // a curved reel scales every cell and the symbol would jump on destroy.\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n const snapDestroyed = (): void => {\n view.alpha = 0;\n view.scale.set(0, 0);\n };\n\n // Pre-abort: skip the tween entirely and snap to the destroyed pose.\n if (signal?.aborted) {\n snapDestroyed();\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n view.alpha = 0;\n return;\n }\n\n await new Promise<void>((resolve) => {\n const tl = this.gsap\n .timeline({ onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, delay })\n // Brief anticipation pop. small upscale, ~60 ms, with overshoot\n // so the implode reads as a release. No rotation.\n .to(view.scale, { x: 1.1, y: 1.1, duration: 0.06, ease: 'back.out(2.5)' })\n // Snap implode. scale -> 0 + alpha -> 0 together, snappy ease-in\n // so the symbol collapses into the cell centre and is gone.\n .to(view.scale, { x: 0, y: 0, duration: 0.14, ease: 'power3.in' }, '<+=0.04')\n .to(view, { alpha: 0, duration: 0.14, ease: 'power3.in' }, '<');\n\n const onAbort = (): void => {\n tl.kill();\n snapDestroyed();\n resolve();\n };\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Restore transform. alpha stays 0 (the symbol IS destroyed). Scale\n // restored to 1 so pool reuse via `_replaceSymbol`'s same-id fast path\n // doesn't inherit a stale 0× scale; _replaceSymbol also resets scale\n // explicitly but a defensive restore here makes the destroyed cell\n // observably \"ready to be re-skinned\" between calls.\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n view.scale.set(1, 1);\n }\n\n /**\n * Animate this symbol AWAY, without destroying it.\n *\n * The counterpart to {@link playIn}, and the seam a mystery reveal needs:\n * the cells on screen dissolve, their identities are swapped underneath, and\n * the new ones arrive. `playDestroy` is the cascade's version of the same\n * beat and is deliberately separate - it is tuned as a \"this cell was a\n * winner and is being consumed\" poof, and a reveal is not that.\n *\n * Leaves the view hidden (`alpha: 0`) with its transform restored, so the\n * caller may swap the identity and call `playIn` next.\n *\n * Override for art-appropriate exits - a Spine symbol plays its own `out`\n * track here. Honour `opts.signal`: abort means \"snap to the end\", not\n * \"fail\", so the promise still resolves.\n *\n * Default: a ~180 ms shrink-and-fade with no overshoot, centred on the\n * symbol's bounds rather than the view origin.\n */\n async playOut(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('out', opts);\n }\n\n /**\n * Animate this symbol IN, from nothing to its resting pose.\n *\n * Owns its own start pose. A symbol that has just been re-activated is fully\n * visible (`activate()` resets alpha and scale), so an entrance that assumed\n * it started hidden would pop before it animated. This sets the hidden pose\n * first, then plays.\n *\n * Override for art-appropriate entrances - a Spine symbol plays its own `in`\n * track here. Honour `opts.signal`: abort snaps to the RESTING pose (alpha 1,\n * scale 1), because arriving is what the caller asked for.\n *\n * Default: a ~200 ms fade and scale-up with a small overshoot.\n */\n async playIn(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n await this._tweenPresence('in', opts);\n }\n\n /**\n * Shared body of {@link playIn} / {@link playOut}.\n *\n * One function because the two are the same tween read in opposite\n * directions, and because the fiddly part - pivoting to the visual centre so\n * the scale does not collapse toward the view's (0, 0) corner, and\n * compensating the position for the pivot move - is identical and easy to get\n * subtly wrong twice. Same correction as `playDestroy`: a container renders a\n * local point at `position + (point - pivot) * scale`, so a curved reel (which\n * scales every cell) would make the symbol jump without the `* scale` term.\n */\n private async _tweenPresence(\n direction: 'in' | 'out',\n opts?: { delay?: number; signal?: AbortSignal },\n ): Promise<void> {\n const view = this.view;\n const originalPivotX = view.pivot.x;\n const originalPivotY = view.pivot.y;\n const originalX = view.x;\n const originalY = view.y;\n\n const bounds = view.getLocalBounds();\n const cx = bounds.x + bounds.width / 2;\n const cy = bounds.y + bounds.height / 2;\n view.pivot.set(cx, cy);\n view.x = originalX + (cx - originalPivotX) * view.scale.x;\n view.y = originalY + (cy - originalPivotY) * view.scale.y;\n\n const restorePivot = (): void => {\n view.pivot.set(originalPivotX, originalPivotY);\n view.x = originalX;\n view.y = originalY;\n };\n const snapEnd = (): void => {\n if (direction === 'out') {\n view.alpha = 0;\n view.scale.set(1, 1);\n } else {\n view.visible = true;\n view.alpha = 1;\n view.scale.set(1, 1);\n }\n };\n\n const delay = opts?.delay ?? 0;\n const signal = opts?.signal;\n\n if (direction === 'in') {\n // Own the start pose: `activate()` leaves a freshly swapped symbol fully\n // visible, so without this the new art is on screen for a frame before\n // the entrance begins.\n view.visible = true;\n view.alpha = 0;\n view.scale.set(0.72, 0.72);\n }\n\n if (signal?.aborted) {\n snapEnd();\n restorePivot();\n return;\n }\n\n await new Promise<void>((resolve) => {\n const onAbort = (): void => {\n tl.kill();\n snapEnd();\n resolve();\n };\n const tl = this.gsap.timeline({\n delay,\n onComplete: () => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n },\n });\n if (direction === 'out') {\n tl.to(view.scale, { x: 0.72, y: 0.72, duration: 0.18, ease: 'power2.in' }).to(\n view,\n { alpha: 0, duration: 0.18, ease: 'power2.in' },\n '<',\n );\n } else {\n tl.to(view.scale, { x: 1, y: 1, duration: 0.2, ease: 'back.out(1.7)' }).to(\n view,\n { alpha: 1, duration: 0.16, ease: 'power2.out' },\n '<',\n );\n }\n if (signal) signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Scale back to 1 either way: a hidden symbol still has to be a legal\n // resting pose, or pool reuse inherits the shrunken scale.\n view.scale.set(1, 1);\n restorePivot();\n }\n\n /**\n * The owning reel is curved: render into this projected quad instead of the\n * flat cell rectangle. `null` means the reel is flat again.\n *\n * The quad's corners are SCREEN-space and local to this view's own origin,\n * clockwise from top-left, and `width` / `height` give the flat cell box the\n * quad replaces. Called on every frame of motion and on every placement, so\n * it must be cheap and idempotent.\n *\n * **The default is an approximation.** A `Container` transform is affine, so\n * it cannot express a trapezoid; the base class fits the quad as closely as\n * an affine transform can - a UNIFORM scale about the quad's centre. Uniform\n * on purpose: symbol art is usually smaller than its cell and has a shape a\n * player recognises, and squashing one axis turns a `7` into a squashed `7`\n * rather than a `7` seen at an angle.\n *\n * Override this to render the real perspective. {@link SpriteSymbol} and\n * {@link AnimatedSpriteSymbol} do, by drawing their texture through a\n * `PerspectiveMesh`, which costs no extra render pass because the content is\n * already a texture. A symbol whose content is an arbitrary subtree (Spine, a\n * composite of sprites and text) cannot do that without rendering itself to a\n * texture every frame, so it keeps the affine fit.\n *\n * Whatever you do here, do NOT move `view.position`: the reel reads that\n * coordinate back to work out which slot this symbol is in.\n */\n /**\n * The part of its cell this symbol's art actually covers, or `null` (the\n * default) for \"all of it\".\n *\n * Slot art is usually smaller than its cell - a trimmed atlas frame is a\n * shape floating in a much bigger transparent box - and the reel needs to\n * know that to project the rectangle the art is really in. Overriding this\n * is what stops a small symbol being inflated to the cell's edges and given\n * the cell's keystone instead of its own, milder one.\n *\n * Read once per projection, so it may change with the symbol's identity.\n */\n get cellInset(): ReelCellInset | null {\n return null;\n }\n\n applyCellQuad(quad: ReelCellQuad | null): void {\n const view = this.view;\n if (quad === null) {\n view.scale.set(1, 1);\n view.pivot.set(0, 0);\n return;\n }\n // Measure the trapezoid: the mean of its two parallel edges, and the\n // distance between their midpoints.\n const nearWidth = Math.hypot(quad.x1 - quad.x0, quad.y1 - quad.y0);\n const farWidth = Math.hypot(quad.x2 - quad.x3, quad.y2 - quad.y3);\n const across = (nearWidth + farWidth) / 2;\n const along = Math.hypot(\n (quad.x3 + quad.x2) / 2 - (quad.x0 + quad.x1) / 2,\n (quad.y3 + quad.y2) / 2 - (quad.y0 + quad.y1) / 2,\n );\n // CONTAIN, not cover. A quad in the middle of the window is TALLER than\n // the flat cell (that is the drum magnifying what faces you), so a scale\n // picked to fill it would also make the symbol wider than its column and\n // overlap its neighbours - very visible on art that fills its cell\n // edge-to-edge. Taking the smaller ratio keeps every symbol inside its own\n // projected footprint at the cost of a little slack on one axis.\n const scale =\n quad.width > 0 && quad.height > 0\n ? Math.min(across / quad.width, along / quad.height)\n : 1;\n\n const cx = (quad.x0 + quad.x1 + quad.x2 + quad.x3) / 4;\n const cy = (quad.y0 + quad.y1 + quad.y2 + quad.y3) / 4;\n view.scale.set(scale, scale);\n // A container renders a local point at `position + (point - pivot) * scale`\n // and `position` must not move, so putting the flat box's centre on the\n // quad's centre has to be paid for entirely out of the pivot.\n view.pivot.set(\n quad.x + quad.width / 2 - cx / scale,\n quad.y + quad.height / 2 - cy / scale,\n );\n }\n\n /**\n * Lifecycle hook: the owning reel is spinning.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayBlur,\n * StaticSpinSymbol) to swap to a spin presentation automatically.\n *\n * Fired on every strip symbol (visible AND buffer cells) when the reel\n * enters the spin phase, and again with `joinedMidSpin: true` on each\n * symbol freshly installed while the reel is already spinning (pool\n * recycling wipes symbol state, so a wrapped-in symbol can't know the\n * reel is moving without this). Implementations MUST be idempotent.\n * the same instance can be notified more than once per spin.\n *\n * @param joinedMidSpin true when this symbol was installed into a reel\n * already at speed (skip start-of-spin transitions like blur ramps).\n */\n onReelSpinStart(joinedMidSpin?: boolean): void {}\n\n /**\n * Lifecycle hook: the owning reel is about to stop (just before bounce).\n * Default: no-op.\n */\n onReelSpinEnd(): void {}\n\n /**\n * Lifecycle hook: the owning reel entered its anticipation (tease) phase.\n * it is still spinning, but slowed enough that the strip is readable.\n * Spin presentations that obscure symbols (blur textures, smear\n * animations) should relax so the player can follow the tease. Also\n * fired on symbols installed while the reel is anticipating.\n * Implementations MUST be idempotent. Default: no-op.\n */\n onReelAnticipationStart(): void {}\n\n /**\n * Lifecycle hook: the owning reel has landed on its final symbols.\n * Default: no-op. Override (e.g. SpineReelSymbol.autoPlayLanding) to fire\n * a landing animation concurrently with the bounce.\n *\n * `ctx` says which reel and cell this symbol landed in, so an override can\n * play a different beat - or none - on a particular reel. The engine always\n * supplies it; it is optional only so an override written as\n * `onReelLanded()` keeps compiling. Fired before the reel's `landing` event\n * and the set's `spin:reelLanding`, by contract.\n */\n onReelLanded(ctx?: ReelLandingContext): void {}\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`./ReelSymbol-CTlZ9ihf.cjs`);var t=null,n=(async()=>{try{t=(await import(`@esotericsoftware/spine-pixi-v8`)).Spine}catch{}})();function r(){return n}var i=class extends e.t{_spine=null;_skeletonDataMap;_idleAnimation;_winAnimation;_defaultSkin;_winResolve=null;_currentSkeletonKey=``;_strict;constructor(e){if(super(),!t)throw Error(`SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is not installed (npm install @esotericsoftware/spine-pixi-v8), or its dynamic import has not resolved yet on a cold start — await whenSpineReady() before constructing SpineSymbols.`);this._skeletonDataMap=e.skeletonDataMap,this._idleAnimation=e.idleAnimation??`idle`,this._winAnimation=e.winAnimation??`win`,this._defaultSkin=e.defaultSkin??`default`,this._strict=e.strict??!1}onActivate(e){let n=this._skeletonDataMap[e];if(n){if(this._currentSkeletonKey!==e&&(this._spine&&(this.view.removeChild(this._spine),this._spine.destroy()),this._spine=new t({skeletonData:n}),this.view.addChild(this._spine),this._currentSkeletonKey=e),this._spine.skeleton.data.findSkin(this._defaultSkin)&&(this._spine.skeleton.setSkinByName(this._defaultSkin),this._spine.skeleton.setSlotsToSetupPose()),this._spine.skeleton.data.findAnimation(this._idleAnimation))this._spine.state.setAnimation(0,this._idleAnimation,!0);else if(this._strict)throw Error(`SpineSymbol(strict): idle animation '${this._idleAnimation}' not found on skeleton '${e}'.`)}}onDeactivate(){if(this._spine&&(this._spine.state.clearListeners(),this._spine.state.clearTracks()),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}async playWin(){if(this._spine){if(!this._spine.skeleton.data.findAnimation(this._winAnimation)){if(this._strict)throw Error(`SpineSymbol(strict): win animation '${this._winAnimation}' not found on skeleton '${this._currentSkeletonKey}'.`);return}return new Promise(e=>{this._winResolve=e;let t=this._spine.state.setAnimation(0,this._winAnimation,!1);this._spine.state.addListener({complete:n=>{n===t&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve=null,e())}})})}}stopAnimation(){this._spine&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve&&=(this._winResolve(),null))}resize(e,t){if(!this._spine)return;let n=this._spine.getBounds();n.width>0&&n.height>0&&this._spine.scale.set(e/n.width,t/n.height)}onDestroy(){if(this._spine&&=(this._spine.state.clearListeners(),this._spine.destroy(),null),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});
2
- //# sourceMappingURL=SpineSymbol-Do0zw5ad.cjs.map
1
+ const e=require(`./ReelSymbol-1IDr3_Ti.cjs`);var t=null,n=(async()=>{try{t=(await import(`@esotericsoftware/spine-pixi-v8`)).Spine}catch{}})();function r(){return n}var i=class extends e.t{_spine=null;_skeletonDataMap;_idleAnimation;_winAnimation;_defaultSkin;_winResolve=null;_currentSkeletonKey=``;_strict;constructor(e){if(super(),!t)throw Error(`SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is not installed (npm install @esotericsoftware/spine-pixi-v8), or its dynamic import has not resolved yet on a cold start — await whenSpineReady() before constructing SpineSymbols.`);this._skeletonDataMap=e.skeletonDataMap,this._idleAnimation=e.idleAnimation??`idle`,this._winAnimation=e.winAnimation??`win`,this._defaultSkin=e.defaultSkin??`default`,this._strict=e.strict??!1}onActivate(e){let n=this._skeletonDataMap[e];if(n){if(this._currentSkeletonKey!==e&&(this._spine&&(this.view.removeChild(this._spine),this._spine.destroy()),this._spine=new t({skeletonData:n}),this.view.addChild(this._spine),this._currentSkeletonKey=e),this._spine.skeleton.data.findSkin(this._defaultSkin)&&(this._spine.skeleton.setSkinByName(this._defaultSkin),this._spine.skeleton.setSlotsToSetupPose()),this._spine.skeleton.data.findAnimation(this._idleAnimation))this._spine.state.setAnimation(0,this._idleAnimation,!0);else if(this._strict)throw Error(`SpineSymbol(strict): idle animation '${this._idleAnimation}' not found on skeleton '${e}'.`)}}onDeactivate(){if(this._spine&&(this._spine.state.clearListeners(),this._spine.state.clearTracks()),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}async playWin(){if(this._spine){if(!this._spine.skeleton.data.findAnimation(this._winAnimation)){if(this._strict)throw Error(`SpineSymbol(strict): win animation '${this._winAnimation}' not found on skeleton '${this._currentSkeletonKey}'.`);return}return new Promise(e=>{this._winResolve=e;let t=this._spine.state.setAnimation(0,this._winAnimation,!1);this._spine.state.addListener({complete:n=>{n===t&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve=null,e())}})})}}stopAnimation(){this._spine&&(this._spine.state.clearListeners(),this._spine.skeleton.data.findAnimation(this._idleAnimation)&&this._spine.state.setAnimation(0,this._idleAnimation,!0),this._winResolve&&=(this._winResolve(),null))}resize(e,t){if(!this._spine)return;let n=this._spine.getBounds();n.width>0&&n.height>0&&this._spine.scale.set(e/n.width,t/n.height)}onDestroy(){if(this._spine&&=(this._spine.state.clearListeners(),this._spine.destroy(),null),this._winResolve){let e=this._winResolve;this._winResolve=null,e()}}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return i}});
2
+ //# sourceMappingURL=SpineSymbol-5MTpMqXG.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-Do0zw5ad.cjs","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"6CAGA,IAAI,EAAkB,KAMhB,GAA6B,SAAY,CAC7C,GAAI,CAEF,GADoB,MAAM,OAAO,oCACR,WACnB,MAGN,CAQJ,SAAgB,GAAgC,CAC9C,OAAO,EA2BT,IAAa,EAAb,cAAiC,EAAA,CAAW,CAC1C,OAAsB,KACtB,iBACA,eACA,cACA,aACA,YAA2C,KAC3C,oBAAsC,GACtC,QAEA,YAAY,EAA6B,CAEvC,GADA,OAAO,CACH,CAAC,EACH,MAAU,MACR,6PAID,CAEH,KAAK,iBAAmB,EAAQ,gBAChC,KAAK,eAAiB,EAAQ,eAAiB,OAC/C,KAAK,cAAgB,EAAQ,cAAgB,MAC7C,KAAK,aAAe,EAAQ,aAAe,UAC3C,KAAK,QAAU,EAAQ,QAAU,GAGnC,WAAqB,EAAwB,CAC3C,IAAM,EAAe,KAAK,iBAAiB,GACtC,KAiBL,IAdI,KAAK,sBAAwB,IAC3B,KAAK,SACP,KAAK,KAAK,YAAY,KAAK,OAAO,CAClC,KAAK,OAAO,SAAS,EAEvB,KAAK,OAAS,IAAI,EAAW,CAAE,eAAc,CAAC,CAC9C,KAAK,KAAK,SAAS,KAAK,OAAO,CAC/B,KAAK,oBAAsB,GAGzB,KAAK,OAAO,SAAS,KAAK,SAAS,KAAK,aAAa,GACvD,KAAK,OAAO,SAAS,cAAc,KAAK,aAAa,CACrD,KAAK,OAAO,SAAS,qBAAqB,EAExC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,CAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,SACnD,KAAK,QACd,MAAU,MACR,wCAAwC,KAAK,eAAe,2BAC5C,EAAS,IAC1B,EAIL,cAA+B,CAO7B,GANI,KAAK,SACP,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,MAAM,aAAa,EAI7B,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG,EAIP,MAAM,SAAyB,CACxB,QAAK,OACV,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,cAAc,CAAE,CAChE,GAAI,KAAK,QACP,MAAU,MACR,uCAAuC,KAAK,cAAc,2BAC1C,KAAK,oBAAoB,IAC1C,CAEH,OAGF,OAAO,IAAI,QAAe,GAAY,CACpC,KAAK,YAAc,EACnB,IAAM,EAAQ,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,cAAe,GAAM,CAC1E,KAAK,OAAO,MAAM,YAAY,CAC5B,SAAW,GAAoB,CACzB,IAAe,IACjB,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,KAAK,YAAc,KACnB,GAAS,GAGd,CAAC,EACF,EAGJ,eAAsB,CACf,KAAK,SACV,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,AAEE,KAAK,eADL,KAAK,aAAa,CACC,OAIvB,OAAO,EAAe,EAAsB,CAC1C,GAAI,CAAC,KAAK,OAAQ,OAClB,IAAM,EAAS,KAAK,OAAO,WAAW,CAClC,EAAO,MAAQ,GAAK,EAAO,OAAS,GACtC,KAAK,OAAO,MAAM,IAChB,EAAQ,EAAO,MACf,EAAS,EAAO,OACjB,CAIL,WAAqC,CAMnC,GALA,AAGE,KAAK,UAFL,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,SAAS,CACP,MAEZ,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG"}
1
+ {"version":3,"file":"SpineSymbol-5MTpMqXG.cjs","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"6CAGA,IAAI,EAAkB,KAMhB,GAA6B,SAAY,CAC7C,GAAI,CAEF,GADoB,MAAM,OAAO,oCACR,WACnB,MAGN,CAQJ,SAAgB,GAAgC,CAC9C,OAAO,EA2BT,IAAa,EAAb,cAAiC,EAAA,CAAW,CAC1C,OAAsB,KACtB,iBACA,eACA,cACA,aACA,YAA2C,KAC3C,oBAAsC,GACtC,QAEA,YAAY,EAA6B,CAEvC,GADA,OAAO,CACH,CAAC,EACH,MAAU,MACR,6PAID,CAEH,KAAK,iBAAmB,EAAQ,gBAChC,KAAK,eAAiB,EAAQ,eAAiB,OAC/C,KAAK,cAAgB,EAAQ,cAAgB,MAC7C,KAAK,aAAe,EAAQ,aAAe,UAC3C,KAAK,QAAU,EAAQ,QAAU,GAGnC,WAAqB,EAAwB,CAC3C,IAAM,EAAe,KAAK,iBAAiB,GACtC,KAiBL,IAdI,KAAK,sBAAwB,IAC3B,KAAK,SACP,KAAK,KAAK,YAAY,KAAK,OAAO,CAClC,KAAK,OAAO,SAAS,EAEvB,KAAK,OAAS,IAAI,EAAW,CAAE,eAAc,CAAC,CAC9C,KAAK,KAAK,SAAS,KAAK,OAAO,CAC/B,KAAK,oBAAsB,GAGzB,KAAK,OAAO,SAAS,KAAK,SAAS,KAAK,aAAa,GACvD,KAAK,OAAO,SAAS,cAAc,KAAK,aAAa,CACrD,KAAK,OAAO,SAAS,qBAAqB,EAExC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,CAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,SACnD,KAAK,QACd,MAAU,MACR,wCAAwC,KAAK,eAAe,2BAC5C,EAAS,IAC1B,EAIL,cAA+B,CAO7B,GANI,KAAK,SACP,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,MAAM,aAAa,EAI7B,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG,EAIP,MAAM,SAAyB,CACxB,QAAK,OACV,IAAI,CAAC,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,cAAc,CAAE,CAChE,GAAI,KAAK,QACP,MAAU,MACR,uCAAuC,KAAK,cAAc,2BAC1C,KAAK,oBAAoB,IAC1C,CAEH,OAGF,OAAO,IAAI,QAAe,GAAY,CACpC,KAAK,YAAc,EACnB,IAAM,EAAQ,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,cAAe,GAAM,CAC1E,KAAK,OAAO,MAAM,YAAY,CAC5B,SAAW,GAAoB,CACzB,IAAe,IACjB,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,KAAK,YAAc,KACnB,GAAS,GAGd,CAAC,EACF,EAGJ,eAAsB,CACf,KAAK,SACV,KAAK,OAAO,MAAM,gBAAgB,CAC9B,KAAK,OAAO,SAAS,KAAK,cAAc,KAAK,eAAe,EAC9D,KAAK,OAAO,MAAM,aAAa,EAAG,KAAK,eAAgB,GAAK,CAE9D,AAEE,KAAK,eADL,KAAK,aAAa,CACC,OAIvB,OAAO,EAAe,EAAsB,CAC1C,GAAI,CAAC,KAAK,OAAQ,OAClB,IAAM,EAAS,KAAK,OAAO,WAAW,CAClC,EAAO,MAAQ,GAAK,EAAO,OAAS,GACtC,KAAK,OAAO,MAAM,IAChB,EAAQ,EAAO,MACf,EAAS,EAAO,OACjB,CAIL,WAAqC,CAMnC,GALA,AAGE,KAAK,UAFL,KAAK,OAAO,MAAM,gBAAgB,CAClC,KAAK,OAAO,SAAS,CACP,MAEZ,KAAK,YAAa,CACpB,IAAM,EAAI,KAAK,YACf,KAAK,YAAc,KACnB,GAAG"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./ReelSymbol-BvdqdXMY.js";
1
+ import { t as e } from "./ReelSymbol-D80YG0fh.js";
2
2
  //#region src/symbols/SpineSymbol.ts
3
3
  var t = null, n = (async () => {
4
4
  try {
@@ -67,4 +67,4 @@ var i = class extends e {
67
67
  //#endregion
68
68
  export { r as n, i as t };
69
69
 
70
- //# sourceMappingURL=SpineSymbol-C6FYNcwP.js.map
70
+ //# sourceMappingURL=SpineSymbol-D4Qq_S6v.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpineSymbol-C6FYNcwP.js","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SpineSymbol-D4Qq_S6v.js","names":[],"sources":["../src/symbols/SpineSymbol.ts"],"sourcesContent":["import { ReelSymbol } from './ReelSymbol.js';\n\n// Spine types imported dynamically. this is an optional peer dependency\nlet SpineClass: any = null;\n\n// Kick off the optional import at module init and keep the promise so callers\n// can wait for it. Without this, constructing a SpineSymbol on a cold start\n// (before the dynamic import resolves) throws the \"not installed\" error even\n// when Spine IS installed - it just hadn't finished loading yet.\nconst spineReady: Promise<void> = (async () => {\n try {\n const spineModule = await import('@esotericsoftware/spine-pixi-v8');\n SpineClass = spineModule.Spine;\n } catch {\n // Spine not available. SpineSymbol will throw on construction.\n }\n})();\n\n/**\n * Resolves once the optional `@esotericsoftware/spine-pixi-v8` import has\n * settled (whether or not it was installed). Await this before constructing\n * `SpineSymbol`s if you build the reel set immediately on app start, so a cold\n * load can't throw the misleading \"not installed\" error.\n */\nexport function whenSpineReady(): Promise<void> {\n return spineReady;\n}\n\nexport interface SpineSymbolOptions {\n /** Map of symbolId → SkeletonData. */\n skeletonDataMap: Record<string, any>;\n /** Default animation name to play in idle. Default: 'idle'. */\n idleAnimation?: string;\n /** Animation name to play on win. Default: 'win'. */\n winAnimation?: string;\n /** Default skin name. Default: 'default'. */\n defaultSkin?: string;\n /**\n * Fail loud on unmapped animations. When `true`, activating or winning with\n * a configured animation name that the skeleton doesn't define throws instead\n * of silently showing nothing (catches typos like `ide` vs `idle`). Default\n * `false` to preserve the lenient behavior. See ADR 011.\n */\n strict?: boolean;\n}\n\n/**\n * Symbol implementation using Spine 2D skeletal animation.\n *\n * Requires `@esotericsoftware/spine-pixi-v8` as an optional peer dependency.\n * If Spine is not installed, constructing a SpineSymbol will throw.\n */\nexport class SpineSymbol extends ReelSymbol {\n private _spine: any = null;\n private _skeletonDataMap: Record<string, any>;\n private _idleAnimation: string;\n private _winAnimation: string;\n private _defaultSkin: string;\n private _winResolve: (() => void) | null = null;\n private _currentSkeletonKey: string = '';\n private _strict: boolean;\n\n constructor(options: SpineSymbolOptions) {\n super();\n if (!SpineClass) {\n throw new Error(\n 'SpineSymbol could not use @esotericsoftware/spine-pixi-v8. Either it is ' +\n 'not installed (npm install @esotericsoftware/spine-pixi-v8), or its ' +\n 'dynamic import has not resolved yet on a cold start — await ' +\n 'whenSpineReady() before constructing SpineSymbols.',\n );\n }\n this._skeletonDataMap = options.skeletonDataMap;\n this._idleAnimation = options.idleAnimation ?? 'idle';\n this._winAnimation = options.winAnimation ?? 'win';\n this._defaultSkin = options.defaultSkin ?? 'default';\n this._strict = options.strict ?? false;\n }\n\n protected onActivate(symbolId: string): void {\n const skeletonData = this._skeletonDataMap[symbolId];\n if (!skeletonData) return;\n\n // Reuse existing spine if same skeleton data\n if (this._currentSkeletonKey !== symbolId) {\n if (this._spine) {\n this.view.removeChild(this._spine);\n this._spine.destroy();\n }\n this._spine = new SpineClass({ skeletonData });\n this.view.addChild(this._spine);\n this._currentSkeletonKey = symbolId;\n }\n\n if (this._spine.skeleton.data.findSkin(this._defaultSkin)) {\n this._spine.skeleton.setSkinByName(this._defaultSkin);\n this._spine.skeleton.setSlotsToSetupPose();\n }\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n } else if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): idle animation '${this._idleAnimation}' not found ` +\n `on skeleton '${symbolId}'.`,\n );\n }\n }\n\n protected onDeactivate(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.state.clearTracks();\n }\n // Settle any pending playWin so awaiters don't hang when the symbol is\n // recycled mid-animation. Mirrors the same fix in SpineReelSymbol.\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n\n async playWin(): Promise<void> {\n if (!this._spine) return;\n if (!this._spine.skeleton.data.findAnimation(this._winAnimation)) {\n if (this._strict) {\n throw new Error(\n `SpineSymbol(strict): win animation '${this._winAnimation}' not found ` +\n `on skeleton '${this._currentSkeletonKey}'.`,\n );\n }\n return;\n }\n\n return new Promise<void>((resolve) => {\n this._winResolve = resolve;\n const entry = this._spine.state.setAnimation(0, this._winAnimation, false);\n this._spine.state.addListener({\n complete: (trackEntry: any) => {\n if (trackEntry === entry) {\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n this._winResolve = null;\n resolve();\n }\n },\n });\n });\n }\n\n stopAnimation(): void {\n if (!this._spine) return;\n this._spine.state.clearListeners();\n if (this._spine.skeleton.data.findAnimation(this._idleAnimation)) {\n this._spine.state.setAnimation(0, this._idleAnimation, true);\n }\n if (this._winResolve) {\n this._winResolve();\n this._winResolve = null;\n }\n }\n\n resize(width: number, height: number): void {\n if (!this._spine) return;\n const bounds = this._spine.getBounds();\n if (bounds.width > 0 && bounds.height > 0) {\n this._spine.scale.set(\n width / bounds.width,\n height / bounds.height,\n );\n }\n }\n\n protected override onDestroy(): void {\n if (this._spine) {\n this._spine.state.clearListeners();\n this._spine.destroy();\n this._spine = null;\n }\n if (this._winResolve) {\n const r = this._winResolve;\n this._winResolve = null;\n r();\n }\n }\n}\n"],"mappings":"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"}
@@ -125,6 +125,18 @@ export interface BoardGridOptions {
125
125
  */
126
126
  cellZIndex?: BoardCellZIndexResolver;
127
127
  }
128
+ /**
129
+ * Default strength for {@link BoardGrid.dim} and {@link BoardGrid.dimSymbols}.
130
+ * Half way: dark enough to read as background, light enough that the art is
131
+ * still legible behind whatever is in front of it.
132
+ */
133
+ export declare const DEFAULT_DIM_AMOUNT = 0.5;
134
+ /**
135
+ * How long {@link BoardGrid.dim} takes to fade in, and to fade back out.
136
+ * Short enough to read as part of the beat it belongs to rather than as a
137
+ * transition of its own; `fade: 0` cuts instantly.
138
+ */
139
+ export declare const DEFAULT_DIM_FADE_MS = 180;
128
140
  /**
129
141
  * A grid of cells that each spin **independently** - the generic "board of
130
142
  * reels" primitive. Every cell is its own 1×1 {@link ReelSet}, so it inherits
@@ -173,6 +185,41 @@ export declare class BoardGrid implements Disposable {
173
185
  * the neighbour's edge.
174
186
  */
175
187
  private readonly _lifted;
188
+ /**
189
+ * The lifted art of cells a consumer has {@link lift}ed, rendered above
190
+ * `_lifted` - the transient exception to `cellZIndex`. Empty until someone
191
+ * calls `lift()`, so a board that never does renders exactly as before.
192
+ */
193
+ private readonly _promoted;
194
+ /** Outstanding {@link lift} count per cell key, in lift order. */
195
+ private readonly _liftCounts;
196
+ /**
197
+ * Holds the {@link dim} rectangles, one per dimmed cell. A child of
198
+ * `container` for its transform, drawn through `_dimLayer` for its order.
199
+ */
200
+ private readonly _dimHost;
201
+ /**
202
+ * Where the dim rectangles draw: above `_lifted`, so a dimmed cell's
203
+ * lifted art goes down with it, and below `_promoted`, so a lifted cell
204
+ * stays out in front of the dim.
205
+ */
206
+ private readonly _dimLayer;
207
+ /** Set while a {@link dim} is up - only one at a time is meaningful. */
208
+ private _dimmed;
209
+ /**
210
+ * The running fades, keyed by what they drive: `cell` moves `_dimHost.alpha`
211
+ * for {@link dim}, `symbol` moves the tint strength for {@link dimSymbols}.
212
+ * Separate slots so the two can run at once without cancelling each other.
213
+ */
214
+ private readonly _fades;
215
+ /** Cells {@link dimSymbols} currently tints, and the instance tinted in each. */
216
+ private _dimmedSymbols;
217
+ /** Strength the symbol dim is currently at, 0..1. Read by the fade. */
218
+ private _symbolDimAmount;
219
+ /** Per-cell `symbol:created` listeners keeping the symbol dim on swaps. */
220
+ private readonly _symbolDimWatch;
221
+ /** Owns the dim fade's ticker subscription, so `destroy()` cannot leak it. */
222
+ private readonly _ticker;
176
223
  private readonly _cellZIndex;
177
224
  private _destroyed;
178
225
  constructor(opts: BoardGridOptions);
@@ -183,6 +230,164 @@ export declare class BoardGrid implements Disposable {
183
230
  * supported way to order the cells themselves.
184
231
  */
185
232
  get liftedLayer(): RenderLayer;
233
+ /**
234
+ * Draw one cell's lifted art in front of every other cell's until the
235
+ * returned function is called - the transient exception to the board's
236
+ * at-rest order (`cellZIndex`). For a symbol whose one-shot must not be
237
+ * overlapped by a neighbour's art: a coin upgrading in place, a collect
238
+ * sweeping the board, a symbol firing at another cell.
239
+ *
240
+ * Promotion is a SEPARATE channel from `zIndex`, so a lift survives
241
+ * {@link refreshCellZIndex} - which keeps writing the lifted cell's
242
+ * `zIndex` throughout, ordering it for the moment it comes back - and
243
+ * releasing restores the cell's place exactly, recomputing nothing.
244
+ *
245
+ * Lifts are reference-counted per cell and the returned release is
246
+ * idempotent, so overlapping lifts of one cell cannot strand each other.
247
+ * Several lifted cells keep their relative at-rest order.
248
+ *
249
+ * Scope: this promotes the cell's LIFTED art - the views the engine hoists
250
+ * out of the cell for a symbol registered `unmask: true`, which is what the
251
+ * board renders above all cells in the first place. A cell showing a masked
252
+ * symbol has nothing in that layer, so lifting it is a no-op; masked art is
253
+ * clipped to its own cell and cannot overlap a neighbour anyway.
254
+ *
255
+ * Lifting is presentation only: no ledger, no phase, no event. A lift held
256
+ * across a respin is legal - the caller owns its own choreography - and is
257
+ * released only by its own release, {@link destroy}, or (on a
258
+ * `HoldAndWinBoard`) `reset()`.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * const release = board.lift(cell);
263
+ * await board.symbolAt(cell).playWin();
264
+ * release();
265
+ * ```
266
+ */
267
+ lift(cell: BoardCell): () => void;
268
+ /** Currently lifted cells, in lift order. Debug / assertion surface. */
269
+ get liftedCells(): BoardCell[];
270
+ /**
271
+ * Drop every outstanding lift at once, whatever its reference count.
272
+ * Called by `reset()` and `destroy()` on a `HoldAndWinBoard`, so a feature
273
+ * that ends mid-animation cannot leave a cell stuck in front. Releases
274
+ * handed out before this stay safe to call - they become no-ops.
275
+ */
276
+ releaseAllLifts(): void;
277
+ /**
278
+ * Push every cell except these into the background until the returned
279
+ * function is called - the board-level counterpart to a `ReelSet`'s
280
+ * spotlight dim, and the natural partner of {@link lift}: one cell forward,
281
+ * the rest back, for the length of one beat.
282
+ *
283
+ * One black rectangle per dimmed cell, drawn above the cells' lifted art
284
+ * and below anything {@link lift}ed - so a lifted cell stays out in front
285
+ * of the dim whether or not it is excepted, while its cell background goes
286
+ * down with the rest unless you name it in `except`.
287
+ *
288
+ * A dim covers CELLS, not symbol ids. To keep every coin of one kind bright,
289
+ * resolve the ids to cells first and pass those.
290
+ *
291
+ * It fades rather than pops: `fade` milliseconds in, the same back out, off
292
+ * the board's own ticker. Pass `fade: 0` for an instant cut. A fade
293
+ * interrupted part-way (release during the fade in, or a fresh `dim()`
294
+ * during the fade out) starts from where it is and takes the proportional
295
+ * share of `fade`, so nothing jumps.
296
+ *
297
+ * Only one dim at a time is meaningful, so a second call throws rather than
298
+ * silently replacing the first. Release is idempotent, and it ends the dim
299
+ * immediately as far as {@link dimmedCells} is concerned - the fade out is
300
+ * presentation the next `dim()` is free to interrupt.
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * const undim = board.dim({ except: [cell], amount: 0.6 });
305
+ * const drop = board.lift(cell);
306
+ * await board.symbolAt(cell).playWin();
307
+ * drop();
308
+ * undim();
309
+ * ```
310
+ */
311
+ dim(opts?: {
312
+ except?: BoardCell[];
313
+ amount?: number;
314
+ fade?: number;
315
+ }): () => void;
316
+ /**
317
+ * Drive one 0..1 value from `from` to `target` over the share of `ms` the
318
+ * remaining distance is worth, handing each step to `apply`, then run
319
+ * `onDone`. `slot` names which fade this is, so the cell dim and the symbol
320
+ * dim can run at once while each still replaces only its own.
321
+ */
322
+ private _fade;
323
+ private _cancelFade;
324
+ /**
325
+ * Push every cell's SYMBOL except these into the background until the
326
+ * returned function is called. Same shape as {@link dim}, different channel:
327
+ * this darkens the art itself and leaves the cell alone, so the board's
328
+ * chrome, backgrounds and gaps stay exactly as bright as they were and only
329
+ * the symbols sink.
330
+ *
331
+ * It works by multiplying each symbol view's `tint` towards black, which
332
+ * carries down the whole view - a Spine skeleton, its nested containers,
333
+ * anything the symbol class draws - without touching what the symbol class
334
+ * tints internally. `amount` is the strength: `0` leaves the art alone, `1`
335
+ * takes it to black, and `0.5` is the classic `tint: 0x808080`.
336
+ *
337
+ * A dimmed cell that SWAPS its symbol keeps its dim: the new occupant is
338
+ * tinted as it arrives and the one that left is cleaned before the pool can
339
+ * hand it on. That is the difference from doing this by hand - `deactivate()`
340
+ * resets alpha, scale, rotation and filters but NOT tint, so a symbol
341
+ * released while dark stays dark in whatever cell reuses it next.
342
+ *
343
+ * This owns `symbol.view.tint` for its duration; a game tinting the same
344
+ * property itself will be overwritten. Tint your own art on a child of the
345
+ * view and the two multiply cleanly.
346
+ *
347
+ * Fades like {@link dim}, on its own slot, so the two can run together: one
348
+ * to sink the cells and one to sink the art.
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * const undim = board.dimSymbols({ except: [collector], amount: 0.65 });
353
+ * await sweep();
354
+ * undim();
355
+ * ```
356
+ */
357
+ dimSymbols(opts?: {
358
+ except?: BoardCell[];
359
+ amount?: number;
360
+ fade?: number;
361
+ }): () => void;
362
+ /** Cells whose symbol {@link dimSymbols} currently tints. */
363
+ get dimmedSymbolCells(): BoardCell[];
364
+ /**
365
+ * Drop the symbol dim at once, whatever it is mid-fade, and clear every
366
+ * tint it wrote. Called by `reset()` and `destroy()` on a `HoldAndWinBoard`.
367
+ */
368
+ clearSymbolDim(): void;
369
+ /** Write the current strength onto every dimmed cell's live symbol. */
370
+ private _applySymbolDim;
371
+ /**
372
+ * Undo the tints of the dim currently tracked and stop watching its cells
373
+ * for swaps. Leaves {@link _symbolDimAmount} alone: a replacement dim wants
374
+ * to continue from the strength on screen, while `clearSymbolDim()` zeroes
375
+ * it itself.
376
+ */
377
+ private _releaseSymbolDim;
378
+ /**
379
+ * Drop the dim at once, whatever holds it and whatever it is mid-fade. A
380
+ * reset must not leave an overlay fading over a board that has already
381
+ * gone. Called by `reset()` and `destroy()` on a `HoldAndWinBoard`; a
382
+ * release handed out before this stays a safe no-op.
383
+ */
384
+ clearDim(): void;
385
+ /**
386
+ * Cells a {@link dim} currently covers; empty when nothing is dimmed. A
387
+ * released dim reports empty from the moment it is released, while its
388
+ * fade out is still on screen.
389
+ */
390
+ get dimmedCells(): BoardCell[];
186
391
  /**
187
392
  * Re-ask the `cellZIndex` resolver for every cell and write the answers
188
393
  * onto the cells' lifted containers. Called on every `place` and every
@@ -241,6 +446,8 @@ export declare class BoardGrid implements Disposable {
241
446
  get isDestroyed(): boolean;
242
447
  destroy(): void;
243
448
  private _origin;
449
+ /** Throw for a coordinate outside the grid; returns it for chaining. */
450
+ private _assertCell;
244
451
  private _reel;
245
452
  }
246
453
  //# sourceMappingURL=BoardGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BoardGrid.d.ts","sourceRoot":"","sources":["../../src/board/BoardGrid.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BoardGrid.d.ts","sourceRoot":"","sources":["../../src/board/BoardGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIzD,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,KAAK,YAAY,CAAC,CAAC;AAE9E,6DAA6D;AAC7D,MAAM,WAAW,sBAAsB;IACrC,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,kHAAkH;IAClH,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,OAAO,CAAC;IAChB,yFAAyF;IACzF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,sBAAsB,KAAK,MAAM,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,QAAQ,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6FAA6F;IAC7F,OAAO,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAC5C,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACjD,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,iBAAiB,KAAK,YAAY,CAAC;IAClE;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACxC;;;;OAIG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACtC;AA2BD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAGvC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,SAAU,YAAW,UAAU;IAC1C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,kEAAkE;IAClE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,wEAAwE;IACxE,OAAO,CAAC,OAAO,CAA4B;IAC3C;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgD;IACvE,iFAAiF;IACjF,OAAO,CAAC,cAAc,CAAwC;IAC9D,uEAAuE;IACvE,OAAO,CAAC,gBAAgB,CAAK;IAC7B,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiC;IACjE,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiC;IAC7D,OAAO,CAAC,UAAU,CAAS;gBAEf,IAAI,EAAE,gBAAgB;IA8GlC;;;;;OAKG;IACH,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,IAAI;IA4BjC,wEAAwE;IACxE,IAAI,WAAW,IAAI,SAAS,EAAE,CAK7B;IAED;;;;;OAKG;IACH,eAAe,IAAI,IAAI;IASvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,GAAG,CAAC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,MAAM,IAAI;IAiDpF;;;;;OAKG;IACH,OAAO,CAAC,KAAK;IA4Bb,OAAO,CAAC,WAAW;IAOnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,UAAU,CAAC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,MAAM,IAAI;IAwE3F,6DAA6D;IAC7D,IAAI,iBAAiB,IAAI,SAAS,EAAE,CAMnC;IAED;;;OAGG;IACH,cAAc,IAAI,IAAI;IAStB,uEAAuE;IACvE,OAAO,CAAC,eAAe;IAOvB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;;;;OAKG;IACH,QAAQ,IAAI,IAAI;IAShB;;;;OAIG;IACH,IAAI,WAAW,IAAI,SAAS,EAAE,CAE7B;IAED;;;;;OAKG;IACH,iBAAiB,IAAI,IAAI;IAezB;;;;OAIG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;;;OAIG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,uEAAuE;IACvE,KAAK,IAAI,SAAS,EAAE;IAIpB,0EAA0E;IAC1E,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAKpF,0EAA0E;IAC1E,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAKrD,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU;IAIrC,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO;IAIhC,8DAA8D;IAC9D,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/C,yEAAyE;IACzE,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAWxC;;;;;;;;;;OAUG;IACG,SAAS,CACb,OAAO,EAAE,eAAe,EAAE,EAC1B,QAAQ,GAAE,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAY,GACzE,OAAO,CAAC,IAAI,CAAC;IAkBhB,2EAA2E;IAC3E,YAAY,IAAI,MAAM;IAetB,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,OAAO,IAAI,IAAI;IAaf,OAAO,CAAC,OAAO;IAOf,wEAAwE;IACxE,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,KAAK;CAOd"}