{"version":3,"file":"index.cjs","names":[],"sources":["../src/symbols/SpriteSymbol.ts","../src/symbols/AnimatedSpriteSymbol.ts","../src/symbols/EmptySymbol.ts","../src/snapshot/SpinTextureCache.ts","../src/snapshot/StaticSpinSymbol.ts","../src/spin/anticipationRecipes.ts","../src/spin/modes/CascadeMode.ts","../src/spin/modes/ImmediateMode.ts","../src/board/BoardGrid.ts","../src/board/HwTypes.ts","../src/board/HoldAndWinState.ts","../src/board/HoldAndWinBoard.ts","../src/board/HoldAndWinBuilder.ts","../src/horizontal/HorizontalReel.ts","../src/horizontal/HorizontalReelBuilder.ts","../src/wins/Win.ts","../src/wins/WinPresenter.ts","../src/utils/gsapTicker.ts"],"sourcesContent":["import { Sprite, type Texture } from 'pixi.js';\nimport type { gsap } from 'gsap';\nimport { getGsap } from '../utils/gsapRef.js';\nimport { ReelSymbol } from './ReelSymbol.js';\n\nexport interface SpriteSymbolOptions {\n  /** Map of symbolId → Texture. */\n  textures: Record<string, Texture>;\n  /** Anchor point. Default: { x: 0.5, y: 0.5 }. */\n  anchor?: { x: number; y: number };\n}\n\n/**\n * Symbol implementation using a simple PixiJS Sprite.\n * Swaps texture on activate. Win animation is a scale pulse via GSAP.\n */\nexport class SpriteSymbol extends ReelSymbol {\n  private _sprite: Sprite;\n  private _textures: Record<string, Texture>;\n  private _winTween: gsap.core.Tween | null = null;\n\n  constructor(options: SpriteSymbolOptions) {\n    super();\n    this._textures = options.textures;\n    const anchor = options.anchor ?? { x: 0, y: 0 };\n    this._sprite = new Sprite();\n    this._sprite.anchor.set(anchor.x, anchor.y);\n    this.view.addChild(this._sprite);\n  }\n\n  protected onActivate(symbolId: string): void {\n    const texture = this._textures[symbolId];\n    if (texture) {\n      this._sprite.texture = texture;\n    }\n  }\n\n  protected onDeactivate(): void {\n    this._killWinTween();\n    this._sprite.scale.set(1, 1);\n  }\n\n  async playWin(): Promise<void> {\n    this._killWinTween();\n    return new Promise<void>((resolve) => {\n      this._winTween = getGsap().to(this._sprite.scale, {\n        x: 1.15,\n        y: 1.15,\n        duration: 0.15,\n        yoyo: true,\n        repeat: 1,\n        ease: 'power2.inOut',\n        onComplete: resolve,\n      });\n    });\n  }\n\n  stopAnimation(): void {\n    this._killWinTween();\n    this._sprite.scale.set(1, 1);\n  }\n\n  resize(width: number, height: number): void {\n    this._sprite.width = width;\n    this._sprite.height = height;\n  }\n\n  protected override onDestroy(): void {\n    this._killWinTween();\n  }\n\n  private _killWinTween(): void {\n    if (this._winTween) {\n      this._winTween.kill();\n      this._winTween = null;\n    }\n  }\n}\n","import { AnimatedSprite, type Texture } from 'pixi.js';\nimport { ReelSymbol } from './ReelSymbol.js';\n\nexport interface AnimatedSpriteSymbolOptions {\n  /** Map of symbolId → array of frame textures. */\n  frames: Record<string, Texture[]>;\n  /** Playback speed (frames per second multiplier). Default: 1. */\n  animationSpeed?: number;\n  /** Anchor point. Default: { x: 0.5, y: 0.5 }. */\n  anchor?: { x: number; y: number };\n}\n\n/**\n * Symbol implementation using PixiJS AnimatedSprite.\n * Swaps frame arrays on activate. Win animation plays the full sequence.\n */\nexport class AnimatedSpriteSymbol extends ReelSymbol {\n  private _animSprite: AnimatedSprite;\n  private _frames: Record<string, Texture[]>;\n  private _animationSpeed: number;\n  private _winResolve: (() => void) | null = null;\n\n  constructor(options: AnimatedSpriteSymbolOptions) {\n    super();\n    this._frames = options.frames;\n    this._animationSpeed = options.animationSpeed ?? 1;\n    const anchor = options.anchor ?? { x: 0, y: 0 };\n\n    // Start with the first available frame set\n    const firstFrames = Object.values(this._frames)[0] ?? [];\n    this._animSprite = new AnimatedSprite(firstFrames.length > 0 ? firstFrames : []);\n    this._animSprite.anchor.set(anchor.x, anchor.y);\n    this._animSprite.animationSpeed = this._animationSpeed;\n    this._animSprite.loop = false;\n    this.view.addChild(this._animSprite);\n  }\n\n  protected onActivate(symbolId: string): void {\n    const frames = this._frames[symbolId];\n    if (frames && frames.length > 0) {\n      this._animSprite.textures = frames;\n      this._animSprite.gotoAndStop(0);\n    }\n  }\n\n  protected onDeactivate(): void {\n    this._animSprite.stop();\n    this._winResolve = null;\n  }\n\n  async playWin(): Promise<void> {\n    return new Promise<void>((resolve) => {\n      this._winResolve = resolve;\n      this._animSprite.loop = false;\n      this._animSprite.onComplete = () => {\n        this._winResolve = null;\n        this._animSprite.onComplete = undefined;\n        // Return to frame 0 so the cell settles on its idle look instead\n        // of holding the last frame of the win sequence (which for\n        // generated pixel-art sequences often ends mid-action. muted,\n        // shifted, or otherwise not the neutral base pose).\n        this._animSprite.gotoAndStop(0);\n        resolve();\n      };\n      this._animSprite.gotoAndPlay(0);\n    });\n  }\n\n  stopAnimation(): void {\n    this._animSprite.stop();\n    this._animSprite.gotoAndStop(0);\n    if (this._winResolve) {\n      this._winResolve();\n      this._winResolve = null;\n    }\n  }\n\n  resize(width: number, height: number): void {\n    this._animSprite.width = width;\n    this._animSprite.height = height;\n    // Position the sprite to match its anchor so it fills the cell.\n    // Without this, an anchor of (0.5, 0.5) would render the sprite with\n    // its centre at the cell's top-left corner. only the bottom-right\n    // quadrant visible inside the mask.\n    this._animSprite.x = width * this._animSprite.anchor.x;\n    this._animSprite.y = height * this._animSprite.anchor.y;\n  }\n}\n","import { ReelSymbol } from './ReelSymbol.js';\n\n/**\n * A {@link ReelSymbol} that renders nothing and never animates.\n *\n * Register it for an id that needs to occupy a grid slot without producing\n * any visual — the blank rest state of a {@link HoldAndWinBoard} cell, a\n * cascade \"hole\", a dry-run symbol-set placeholder. The {@link HoldAndWinBuilder}\n * auto-registers one under its `emptyId` so callers never have to.\n */\nexport class EmptySymbol extends ReelSymbol {\n  protected onActivate(_symbolId: string): void {}\n  protected onDeactivate(): void {}\n  async playWin(): Promise<void> {}\n  stopAnimation(): void {}\n  resize(_width: number, _height: number): void {}\n}\n","import { BlurFilter, Container, Rectangle, Sprite, type Texture } from 'pixi.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\n\n/**\n * The slice of a PixiJS renderer the cache needs. `Renderer` (WebGL or\n * WebGPU) satisfies it structurally; tests can pass a stub.\n */\nexport interface SnapshotRenderer {\n  generateTexture(options: {\n    target: Container;\n    frame?: Rectangle;\n    resolution?: number;\n    antialias?: boolean;\n  }): Texture;\n}\n\n/** Tuning for the baked motion-blur variant of a snapshot. */\nexport interface MotionBlurOptions {\n  /**\n   * Smear axis — the reel's travel direction. `'y'` (default) for normal\n   * vertical reels; `'x'` for a `HorizontalReel` strip. The other axis is\n   * never blurred.\n   */\n  axis?: 'y' | 'x';\n  /**\n   * Blur strength in pixels along the axis. Default: 20% of the cell's\n   * span on that axis (`cellHeight * 0.2` for `'y'`, `cellWidth * 0.2`\n   * for `'x'`).\n   */\n  strength?: number;\n  /** BlurFilter quality (number of passes). Default: 4. */\n  quality?: number;\n  /**\n   * Extra transparent pixels added on both sides of the cell along the\n   * axis so the smear isn't clipped at the texture edge.\n   * Default: `ceil(strength)`.\n   */\n  padding?: number;\n}\n\nexport interface SpinTextureCacheOptions {\n  /** The renderer used to generate snapshot textures (`app.renderer`). */\n  renderer: SnapshotRenderer;\n  /** Resolution for generated textures. Default: the renderer's default. */\n  resolution?: number;\n  /** Default motion-blur tuning for `captureBlurred`. */\n  blur?: MotionBlurOptions;\n}\n\ninterface CacheEntry {\n  texture: Texture;\n  /** true = generated by this cache (destroyed on invalidate), false = user-provided. */\n  owned: boolean;\n  width: number;\n  height: number;\n  /** Smear axis the blurred variant was baked for (blurred entries only). */\n  axis?: 'y' | 'x';\n}\n\n/**\n * Per-symbolId cache of \"spin textures\". flat snapshots a reel shows\n * instead of the live symbol (Spine skeleton, animated sprite, ...) while\n * it spins.\n *\n * Two flavors per symbolId:\n *\n *   - **static**. the symbol rendered once into a RenderTexture at cell size.\n *   - **blurred**. the static snapshot smeared vertically through a one-time\n *     BlurFilter pass into a taller, padded RenderTexture. At spin time it's\n *     an ordinary sprite texture: zero filters per frame.\n *\n * Textures come from two sources, user-provided always winning:\n *\n *   - `setStatic(id, tex)` / `setBlurred(id, tex)`. hand-authored art from\n *     your atlas. Never destroyed by the cache.\n *   - `captureStatic` / `captureBlurred`. generated on demand from a live\n *     symbol view. Owned by the cache and destroyed on `invalidate` /\n *     `clear` / `destroy`.\n *\n * Captures are keyed by symbolId and remembered together with the cell size\n * they were taken at; capturing again at a different size regenerates.\n * Share ONE cache across all reels/symbols of a reel set. that's the point.\n */\nexport class SpinTextureCache implements Disposable {\n  private _renderer: SnapshotRenderer;\n  private _resolution: number | undefined;\n  private _blurDefaults: MotionBlurOptions;\n  private _static = new Map<string, CacheEntry>();\n  private _blurred = new Map<string, CacheEntry>();\n  private _isDestroyed = false;\n\n  constructor(options: SpinTextureCacheOptions) {\n    this._renderer = options.renderer;\n    this._resolution = options.resolution;\n    this._blurDefaults = options.blur ?? {};\n  }\n\n  // -- User-provided textures ----------------------------------------------\n\n  /** Provide a hand-authored static spin texture. Wins over captures. */\n  setStatic(symbolId: string, texture: Texture): void {\n    this._put(this._static, symbolId, { texture, owned: false, width: 0, height: 0 });\n  }\n\n  /** Provide a hand-authored motion-blur texture. Wins over captures. */\n  setBlurred(symbolId: string, texture: Texture): void {\n    this._put(this._blurred, symbolId, { texture, owned: false, width: 0, height: 0 });\n  }\n\n  // -- Lookups ---------------------------------------------------------------\n\n  getStatic(symbolId: string): Texture | null {\n    return this._static.get(symbolId)?.texture ?? null;\n  }\n\n  getBlurred(symbolId: string): Texture | null {\n    return this._blurred.get(symbolId)?.texture ?? null;\n  }\n\n  hasStatic(symbolId: string): boolean {\n    return this._static.has(symbolId);\n  }\n\n  hasBlurred(symbolId: string): boolean {\n    return this._blurred.has(symbolId);\n  }\n\n  // -- Capture ---------------------------------------------------------------\n\n  /**\n   * Snapshot `source` (a symbol's `view`, already activated and resized to\n   * the cell) into a `width`x`height` texture and cache it under `symbolId`.\n   * Returns the cached texture if one already exists at this size; a\n   * user-provided texture always short-circuits.\n   */\n  captureStatic(symbolId: string, source: Container, width: number, height: number): Texture {\n    const existing = this._static.get(symbolId);\n    if (existing && (!existing.owned || (existing.width === width && existing.height === height))) {\n      return existing.texture;\n    }\n    const texture = this._renderer.generateTexture({\n      target: source,\n      frame: new Rectangle(0, 0, width, height),\n      resolution: this._resolution,\n      antialias: true,\n    });\n    this._put(this._static, symbolId, { texture, owned: true, width, height });\n    return texture;\n  }\n\n  /**\n   * Bake a motion-blurred variant of the static snapshot for `symbolId`\n   * (which must exist. call `captureStatic` or `setStatic` first) and\n   * cache it. The smear runs along `blur.axis` — the reel's travel\n   * direction (`'y'` default; `'x'` for a `HorizontalReel`) — and the\n   * result is `2 * padding` larger than the cell on that axis only. Draw\n   * it center-anchored at the cell center and the smear extends evenly\n   * past the cell on both sides.\n   */\n  captureBlurred(\n    symbolId: string,\n    width: number,\n    height: number,\n    blur?: MotionBlurOptions,\n  ): Texture {\n    const axis = blur?.axis ?? this._blurDefaults.axis ?? 'y';\n    const existing = this._blurred.get(symbolId);\n    if (\n      existing &&\n      (!existing.owned ||\n        (existing.width === width && existing.height === height && existing.axis === axis))\n    ) {\n      return existing.texture;\n    }\n    const staticTex = this.getStatic(symbolId);\n    if (!staticTex) {\n      throw new Error(\n        `SpinTextureCache.captureBlurred('${symbolId}'): no static texture to blur. ` +\n          `Call captureStatic() or setStatic() for this symbolId first.`,\n      );\n    }\n\n    const strength =\n      blur?.strength ?? this._blurDefaults.strength ?? (axis === 'y' ? height : width) * 0.2;\n    const quality = blur?.quality ?? this._blurDefaults.quality ?? 4;\n    const padding = blur?.padding ?? this._blurDefaults.padding ?? Math.ceil(strength);\n\n    const wrap = new Container();\n    const sprite = new Sprite(staticTex);\n    // Fit the source into the cell box in case it's a user-provided static\n    // texture with different dimensions.\n    sprite.width = width;\n    sprite.height = height;\n    if (axis === 'y') {\n      sprite.y = padding;\n      sprite.filters = [new BlurFilter({ strengthX: 0, strengthY: strength, quality })];\n    } else {\n      sprite.x = padding;\n      sprite.filters = [new BlurFilter({ strengthX: strength, strengthY: 0, quality })];\n    }\n    wrap.addChild(sprite);\n\n    const texture = this._renderer.generateTexture({\n      target: wrap,\n      frame: new Rectangle(\n        0,\n        0,\n        width + (axis === 'x' ? padding * 2 : 0),\n        height + (axis === 'y' ? padding * 2 : 0),\n      ),\n      resolution: this._resolution,\n      antialias: true,\n    });\n    // Destroy the scratch scene but not the cached static texture.\n    wrap.destroy({ children: true });\n\n    this._put(this._blurred, symbolId, { texture, owned: true, width, height, axis });\n    return texture;\n  }\n\n  // -- Invalidation ------------------------------------------------------------\n\n  /** Drop (and destroy, if cache-generated) both textures for one symbolId. */\n  invalidate(symbolId: string): void {\n    this._drop(this._static, symbolId);\n    this._drop(this._blurred, symbolId);\n  }\n\n  /** Drop everything. Call when the cell size changes (responsive relayout). */\n  clear(): void {\n    for (const id of [...this._static.keys()]) this._drop(this._static, id);\n    for (const id of [...this._blurred.keys()]) this._drop(this._blurred, id);\n  }\n\n  destroy(): void {\n    if (this._isDestroyed) return;\n    this.clear();\n    this._isDestroyed = true;\n  }\n\n  get isDestroyed(): boolean {\n    return this._isDestroyed;\n  }\n\n  // -- Internals ----------------------------------------------------------------\n\n  private _put(map: Map<string, CacheEntry>, id: string, entry: CacheEntry): void {\n    this._drop(map, id);\n    map.set(id, entry);\n  }\n\n  private _drop(map: Map<string, CacheEntry>, id: string): void {\n    const existing = map.get(id);\n    if (existing?.owned) existing.texture.destroy(true);\n    map.delete(id);\n  }\n}\n\nexport interface PrewarmSpinTexturesOptions {\n  /** The shared cache to fill. */\n  cache: SpinTextureCache;\n  /** Every symbolId to bake. */\n  ids: string[];\n  /**\n   * Factory for a scratch symbol used as the render source. Typically the\n   * same factory you registered (e.g. `() => new SpineReelSymbol(opts)`).\n   * It is activated per id, snapshotted, and destroyed at the end.\n   */\n  createSymbol: () => ReelSymbol;\n  /** Cell width the reels will use. */\n  width: number;\n  /** Cell height the reels will use. */\n  height: number;\n  /** Also bake the motion-blur variants. Default: true. */\n  blurred?: boolean;\n  /** Motion-blur tuning override (defaults to the cache's). */\n  blur?: MotionBlurOptions;\n}\n\n/**\n * Bake spin textures for every symbolId up front (at load or between\n * rounds) so the first spin never pays a capture hitch. Ids that already\n * have a user-provided or captured texture are skipped by the cache.\n *\n * ```ts\n * prewarmSpinTextures({\n *   cache,\n *   ids: ['cherry', 'lemon', 'seven'],\n *   createSymbol: () => new SpineReelSymbol(spineOpts),\n *   width: 150,\n *   height: 150,\n * });\n * ```\n */\nexport function prewarmSpinTextures(options: PrewarmSpinTexturesOptions): void {\n  const { cache, ids, width, height } = options;\n  const blurred = options.blurred ?? true;\n  const symbol = options.createSymbol();\n  try {\n    for (const id of ids) {\n      if (symbol.symbolId !== id) symbol.activate(id);\n      symbol.resize(width, height);\n      cache.captureStatic(id, symbol.view, width, height);\n      if (blurred) cache.captureBlurred(id, width, height, options.blur);\n    }\n  } finally {\n    symbol.destroy();\n  }\n}\n","import { Sprite } from 'pixi.js';\nimport type { gsap } from 'gsap';\nimport { getGsap } from '../utils/gsapRef.js';\nimport { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { MotionBlurOptions, SpinTextureCache } from './SpinTextureCache.js';\n\nexport interface StaticSpinSymbolOptions {\n  /**\n   * Factory for the wrapped \"live\" symbol. any ReelSymbol subclass\n   * (SpineReelSymbol, AnimatedSpriteSymbol, a custom class). The wrapper\n   * owns the instance: it activates it while the reel is at rest and\n   * deactivates it while the reel spins.\n   */\n  createInner: () => ReelSymbol;\n  /** Shared spin-texture cache. one per reel set. */\n  cache: SpinTextureCache;\n  /**\n   * What to show while spinning. `'blurred'` (default) crossfades the\n   * static snapshot into the baked motion-blur variant; `'static'` shows\n   * the crisp snapshot only and never touches the blur pipeline.\n   */\n  spinTexture?: 'blurred' | 'static';\n  /**\n   * Crossfade duration (ms) from crisp snapshot to blurred snapshot at\n   * spin start. matches the reel's acceleration ramp. `0` = instant.\n   * Default: 120. Ignored when `spinTexture: 'static'`.\n   */\n  blurRampMs?: number;\n  /**\n   * Motion-blur tuning forwarded to `cache.captureBlurred`. On a\n   * `HorizontalReel` pass `{ axis: 'x' }` so the smear follows the strip's\n   * sideways travel (and snapshots fit the cell by height instead of width).\n   */\n  blur?: MotionBlurOptions;\n}\n\n/**\n * Wraps any `ReelSymbol` and swaps it for a cached snapshot texture while\n * the reel spins. \"spin static, not Spine.\"\n *\n * At rest the inner symbol is live (idle loops, win animations, cascade\n * destruction all delegate to it). When the reel starts, the wrapper\n * snapshots the inner symbol through the shared {@link SpinTextureCache}\n * (or reuses a cached / user-provided texture), **deactivates** the inner\n * symbol so it costs nothing, and spins a plain Sprite instead. Symbols\n * that wrap in mid-spin only retarget that sprite's texture. no inner\n * symbol is ever created or ticked for them. On land the inner symbol is\n * reactivated on the final symbolId.\n *\n * With `spinTexture: 'blurred'` the sprite crossfades from the crisp\n * snapshot to a pre-baked motion-blur variant over `blurRampMs`, then\n * spins the blurred texture. The smear follows the reel's travel axis\n * (`blur.axis` — vertical by default, `'x'` for a `HorizontalReel`). No\n * filter runs during the spin; the blur is baked once per symbolId and\n * cached.\n *\n * Register it like any other symbol:\n *\n * ```ts\n * const cache = new SpinTextureCache({ renderer: app.renderer });\n * builder.symbols((r) => {\n *   for (const id of ids) {\n *     r.register(id, StaticSpinSymbol, {\n *       createInner: () => new SpineReelSymbol(spineOpts),\n *       cache,\n *     });\n *   }\n * });\n * ```\n *\n * Prefer calling {@link prewarmSpinTextures} at load time so the first\n * spin doesn't pay the one-time capture cost.\n */\nexport class StaticSpinSymbol extends ReelSymbol {\n  private _inner: ReelSymbol;\n  private _cache: SpinTextureCache;\n  private _mode: 'blurred' | 'static';\n  private _rampMs: number;\n  private _blurOpts: MotionBlurOptions | undefined;\n\n  private _staticSprite: Sprite;\n  private _blurSprite: Sprite;\n  private _spinning = false;\n  private _anticipating = false;\n  private _cellW = 0;\n  private _cellH = 0;\n  private _rampTween: gsap.core.Tween | null = null;\n\n  constructor(options: StaticSpinSymbolOptions) {\n    super();\n    this._inner = options.createInner();\n    this._cache = options.cache;\n    this._mode = options.spinTexture ?? 'blurred';\n    this._rampMs = options.blurRampMs ?? 120;\n    this._blurOpts = options.blur;\n\n    this.view.addChild(this._inner.view);\n    this._staticSprite = new Sprite();\n    this._staticSprite.anchor.set(0.5, 0.5);\n    this._staticSprite.visible = false;\n    this._blurSprite = new Sprite();\n    this._blurSprite.anchor.set(0.5, 0.5);\n    this._blurSprite.visible = false;\n    this.view.addChild(this._staticSprite, this._blurSprite);\n  }\n\n  /** The wrapped live symbol. for type-specific access (`inner as SpineReelSymbol`). */\n  get inner(): ReelSymbol {\n    return this._inner;\n  }\n\n  /** True while the wrapper is showing a snapshot instead of the live symbol. */\n  get isShowingSnapshot(): boolean {\n    return this._spinning;\n  }\n\n  protected onActivate(symbolId: string): void {\n    if (this._spinning) {\n      // Wrapped in mid-spin: never touch the inner symbol, just retarget\n      // the snapshot sprite (instantly blurred. the ramp belongs to spin\n      // start, not to cells joining a reel already at speed).\n      this._showSnapshot(symbolId, { instant: true });\n      return;\n    }\n    if (this._inner.symbolId !== symbolId) this._inner.activate(symbolId);\n    this._hideSnapshot();\n  }\n\n  protected onDeactivate(): void {\n    this._killRamp();\n    this._spinning = false;\n    this._anticipating = false;\n    this._staticSprite.visible = false;\n    this._blurSprite.visible = false;\n    if (this._inner.symbolId !== '') this._inner.deactivate();\n  }\n\n  override onReelSpinStart(joinedMidSpin = false): void {\n    if (this._spinning) {\n      // Idempotent: a repeat notification (same-id fast path during a\n      // wrap) only refreshes the snapshot target.\n      this._showSnapshot(this.symbolId, { instant: true });\n      return;\n    }\n    this._spinning = true;\n    this._anticipating = false;\n    // Capture while the inner symbol is still live so a cache miss can\n    // snapshot it in place, then put the inner symbol to sleep for the\n    // duration of the spin.\n    this._showSnapshot(this.symbolId, { instant: joinedMidSpin || this._rampMs <= 0 });\n    if (this._inner.symbolId !== '') this._inner.deactivate();\n  }\n\n  /**\n   * The reel entered its anticipation tease: it is slow enough to read, so\n   * crossfade the baked blur back out and ride the crisp snapshot until\n   * the land. Mid-tease installs arrive blurred (`_showSnapshot` instant)\n   * and immediately relax through this same hook.\n   */\n  override onReelAnticipationStart(): void {\n    if (!this._spinning || this._anticipating) return;\n    this._anticipating = true;\n    if (this._mode === 'static') return;\n\n    this._killRamp();\n    this._staticSprite.visible = true;\n    this._staticSprite.alpha = 1;\n    if (!this._blurSprite.visible || this._rampMs <= 0) {\n      this._blurSprite.visible = false;\n      return;\n    }\n    this._rampTween = getGsap().to(this._blurSprite, {\n      alpha: 0,\n      duration: this._rampMs / 1000,\n      ease: 'power1.out',\n      onComplete: () => {\n        this._rampTween = null;\n        this._blurSprite.visible = false;\n      },\n    });\n  }\n\n  override onReelSpinEnd(): void {\n    if (!this._spinning) return;\n    this._spinning = false;\n    this._anticipating = false;\n    this._killRamp();\n    this._staticSprite.visible = false;\n    this._blurSprite.visible = false;\n    if (this._inner.symbolId !== this.symbolId) {\n      this._inner.activate(this.symbolId);\n      this._inner.resize(this._cellW, this._cellH);\n    }\n  }\n\n  override onReelLanded(): void {\n    if (!this._spinning) this._inner.onReelLanded();\n  }\n\n  async playWin(): Promise<void> {\n    return this._inner.playWin();\n  }\n\n  stopAnimation(): void {\n    if (this._inner.symbolId !== '') this._inner.stopAnimation();\n  }\n\n  override async playDestroy(opts?: { delay?: number; signal?: AbortSignal }): Promise<void> {\n    // Landed symbols delegate so art-specific destruction (Spine `out`\n    // tracks etc.) plays; a snapshot mid-spin falls back to the generic\n    // implode on the wrapper view.\n    if (!this._spinning && this._inner.symbolId !== '') return this._inner.playDestroy(opts);\n    return super.playDestroy(opts);\n  }\n\n  resize(width: number, height: number): void {\n    this._cellW = width;\n    this._cellH = height;\n    this._staticSprite.position.set(width / 2, height / 2);\n    this._blurSprite.position.set(width / 2, height / 2);\n    this._fitSprites();\n    this._inner.resize(width, height);\n  }\n\n  protected override onDestroy(): void {\n    this._killRamp();\n    // Detach before destroying so the base class's view.destroy({children})\n    // doesn't double-destroy the inner view.\n    if (this._inner.view.parent === this.view) this.view.removeChild(this._inner.view);\n    this._inner.destroy();\n  }\n\n  // -- Internals -------------------------------------------------------------\n\n  /**\n   * Point the snapshot sprites at `symbolId`'s cached textures, capturing\n   * on miss. With `instant`, the end state is applied directly (blurred\n   * fully visible); otherwise the crisp→blur crossfade ramp starts.\n   */\n  private _showSnapshot(symbolId: string, opts: { instant: boolean }): void {\n    const staticTex = this._ensureStatic(symbolId);\n    this._staticSprite.texture = staticTex;\n\n    if (this._mode === 'blurred') {\n      this._blurSprite.texture = this._cache.captureBlurred(\n        symbolId,\n        this._cellW,\n        this._cellH,\n        this._blurOpts,\n      );\n    }\n    this._fitSprites();\n\n    if (this._mode === 'static') {\n      this._staticSprite.visible = true;\n      this._staticSprite.alpha = 1;\n      return;\n    }\n\n    if (opts.instant) {\n      // Anticipating: the tease shows the crisp snapshot. never bring the\n      // blur back for re-notifications or same-id wraps mid-tease.\n      if (this._anticipating) {\n        this._killRamp();\n        this._blurSprite.visible = false;\n        this._staticSprite.visible = true;\n        this._staticSprite.alpha = 1;\n        return;\n      }\n      // Already ramping? Let the running crossfade finish on the new\n      // textures instead of snapping mid-fade.\n      if (this._rampTween) return;\n      this._staticSprite.visible = false;\n      this._blurSprite.visible = true;\n      this._blurSprite.alpha = 1;\n      return;\n    }\n\n    this._killRamp();\n    this._staticSprite.visible = true;\n    this._staticSprite.alpha = 1;\n    this._blurSprite.visible = true;\n    this._blurSprite.alpha = 0;\n    this._rampTween = getGsap().to(this._blurSprite, {\n      alpha: 1,\n      duration: this._rampMs / 1000,\n      ease: 'power1.in',\n      onComplete: () => {\n        this._rampTween = null;\n        this._staticSprite.visible = false;\n      },\n    });\n  }\n\n  /**\n   * Static texture for `symbolId`, capturing from the inner symbol on a\n   * cache miss. If the inner symbol currently holds a different identity\n   * (mid-spin wrap), it is briefly activated offscreen for the capture and\n   * deactivated again. one-time cost per symbolId; avoid it entirely with\n   * `prewarmSpinTextures`.\n   */\n  private _ensureStatic(symbolId: string) {\n    const cached = this._cache.getStatic(symbolId);\n    if (cached) return cached;\n\n    const needsTempActivation = this._inner.symbolId !== symbolId;\n    if (needsTempActivation) {\n      this._inner.activate(symbolId);\n      this._inner.resize(this._cellW, this._cellH);\n    }\n    const tex = this._cache.captureStatic(symbolId, this._inner.view, this._cellW, this._cellH);\n    if (needsTempActivation && this._spinning) this._inner.deactivate();\n    return tex;\n  }\n\n  private _hideSnapshot(): void {\n    this._killRamp();\n    this._staticSprite.visible = false;\n    this._blurSprite.visible = false;\n  }\n\n  /**\n   * Uniformly fit both sprites to the cell along the axis the blur does\n   * NOT pad (keeps aspect; the padded axis then centers out evenly past\n   * the cell). Vertical reels fit by width; horizontal (`blur.axis: 'x'`)\n   * strips fit by height.\n   */\n  private _fitSprites(): void {\n    if (this._cellW <= 0 || this._cellH <= 0) return;\n    const fitByHeight = this._blurOpts?.axis === 'x';\n    for (const sprite of [this._staticSprite, this._blurSprite]) {\n      const tw = sprite.texture.width;\n      const th = sprite.texture.height;\n      if (tw <= 0 || th <= 0) continue;\n      sprite.scale.set(fitByHeight ? this._cellH / th : this._cellW / tw);\n    }\n  }\n\n  private _killRamp(): void {\n    if (this._rampTween) {\n      this._rampTween.kill();\n      this._rampTween = null;\n    }\n  }\n}\n","import type { ColumnTarget } from '../frame/ColumnTarget.js';\n\nexport interface ScatterAnticipationOptions {\n  /** The trigger symbol id to scan for (e.g. `'SCAT'`). */\n  symbol: string;\n  /**\n   * How many of `symbol` must be showing before anticipation kicks in.\n   * Anticipation begins on the reel AFTER the one that lands the\n   * `trigger`-th symbol. Default `2` (the classic \"2 scatters and the rest\n   * of the reels start teasing\").\n   */\n  trigger?: number;\n  /**\n   * Which of the remaining reels should tease:\n   *   - `'all-remaining'` (default): every reel after the trigger reel teases,\n   *     whether or not it actually holds the symbol. Maximum tension.\n   *   - `'scatter-only'`: only reels that actually contain the symbol tease.\n   *     Use when a result can't complete the feature on some reels and you\n   *     don't want to fake tension on reels that don't matter (e.g. a grid\n   *     with only 3 scatters total shouldn't slow the empty reels between/after\n   *     them).\n   */\n  mode?: 'all-remaining' | 'scatter-only';\n}\n\n/**\n * Decide which reels should show anticipation, straight from a result grid.\n *\n * Scans reels left→right counting `symbol` occurrences; once the running total\n * reaches `trigger`, the reels AFTER that point become the anticipation set\n * (per `mode`). Feed the return value straight into\n * `reelSet.setAnticipation(...)`.\n *\n * Returns an empty array when the grid never reaches `trigger` (no tease).\n *\n * `grid` is the same `ColumnTarget[]` you pass to `reelSet.setResult(...)`, so\n * you can hand the result straight through. Only visible cells are scanned.\n * buffer symbols are off-screen and never trigger tension.\n *\n * @example\n * // Result has SCAT on reels 0 and 1 → tease reels [2,3,4].\n * const grid = [{ visible: ['A','SCAT','B'] }, { visible: ['SCAT','A','B'] }, ...];\n * const reels = anticipationForScatters(grid, { symbol: 'SCAT', trigger: 2 });\n * reelSet.setResult(grid);\n * reelSet.setAnticipation(reels, { stagger: 'sequential', slowdown: { from: 0.4, to: 0.1 } });\n *\n * @example\n * // Only 3 scatters total (reels 0,1,3): 'scatter-only' teases just reel [3],\n * // leaving reels 2 and 4 to stop normally.\n * const reels = anticipationForScatters(grid, { symbol: 'SCAT', mode: 'scatter-only' });\n */\nexport function anticipationForScatters(\n  grid: ColumnTarget[],\n  opts: ScatterAnticipationOptions,\n): number[] {\n  const trigger = opts.trigger ?? 2;\n  const mode = opts.mode ?? 'all-remaining';\n\n  // Count the trigger symbol per reel. Only visible cells are scanned; buffer\n  // symbols are off-screen and must not trigger tension.\n  const perReelCount = grid.map(\n    (col) => col.visible.filter((id) => id === opts.symbol).length,\n  );\n\n  // Find the reel at which the running total first reaches `trigger`.\n  let running = 0;\n  let triggerReel = -1;\n  for (let i = 0; i < perReelCount.length; i++) {\n    running += perReelCount[i];\n    if (running >= trigger) {\n      triggerReel = i;\n      break;\n    }\n  }\n  if (triggerReel === -1) return [];\n\n  const result: number[] = [];\n  for (let i = triggerReel + 1; i < grid.length; i++) {\n    if (mode === 'all-remaining' || perReelCount[i] > 0) result.push(i);\n  }\n  return result;\n}\n","import type { SpinningMode } from './SpinningMode.js';\n\n/**\n * Cascade/tumble spinning mode.\n * Symbols fall from above with gravity-like acceleration,\n * used for tumble/avalanche mechanics.\n */\nexport class CascadeMode implements SpinningMode {\n  readonly name = 'cascade';\n\n  private _gravity: number;\n\n  /**\n   * @param gravity - Gravity acceleration factor. Default: 1.5.\n   */\n  constructor(gravity: number = 1.5) {\n    this._gravity = gravity;\n  }\n\n  computeDeltaY(symbolHeight: number, speed: number, deltaMs: number): number {\n    const raw = (symbolHeight * speed * this._gravity * deltaMs) / 1000;\n    return Math.min(raw, symbolHeight);\n  }\n}\n","import type { SpinningMode } from './SpinningMode.js';\n\n/**\n * Immediate placement mode for super-turbo skip.\n * Symbols snap to position instantly. no actual spinning animation.\n */\nexport class ImmediateMode implements SpinningMode {\n  readonly name = 'immediate';\n\n  computeDeltaY(_symbolHeight: number, _speed: number, _deltaMs: number): number {\n    // No movement. placement is handled directly by the phase\n    return 0;\n  }\n}\n","import { Container, Graphics } from 'pixi.js';\nimport type { Ticker } from 'pixi.js';\nimport { ReelSetBuilder } from '../core/ReelSetBuilder.js';\nimport type { ReelSet } from '../core/ReelSet.js';\nimport { SharedRectMaskStrategy } from '../core/ReelViewport.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport { EmptySymbol } from '../symbols/EmptySymbol.js';\nimport { SpeedPresets } from '../config/SpeedPresets.js';\nimport type { SpeedProfile, SymbolData } from '../config/types.js';\nimport type { Disposable } from '../utils/Disposable.js';\n\n/** A cell coordinate in the grid. */\nexport interface BoardCell {\n  col: number;\n  row: number;\n}\n\n/** A landing target: spin `cell` and stop it showing `id`. */\nexport interface BoardSpinTarget {\n  cell: BoardCell;\n  id: string;\n}\n\n/** A speed profile, or a per-cell function of one (e.g. a stagger wave). */\nexport type BoardProfile = SpeedProfile | ((cell: BoardCell) => SpeedProfile);\n\nexport interface BoardGridOptions {\n  /** Grid dimensions. */\n  cols: number;\n  rows: number;\n  /** Cell edge length in pixels. */\n  cellSize: number;\n  /** Gap between cells. Default 4. */\n  gap?: number;\n  /** Id a cell shows when blank — also placed in the off-window buffers. Default `'empty'`. */\n  emptyId?: string;\n  /** Register symbol classes, exactly like `ReelSetBuilder.symbols`. Applied to every cell. */\n  symbols: (registry: SymbolRegistry) => void;\n  /** Strip weights during the spin. */\n  weights?: Record<string, number>;\n  /** Per-symbol engine overrides, exactly like `ReelSetBuilder.symbolData`. */\n  symbolData?: Record<string, Partial<SymbolData>>;\n  /** Injected RNG for the spin strips (deterministic demos / tests). */\n  rng?: () => number;\n  /** Drives every cell's reel — required. */\n  ticker: Ticker;\n  /** Per-cell background, drawn behind each reel. */\n  chrome?: (g: Graphics, size: number) => void;\n  /**\n   * Named speed profiles, each registered on every cell and selected by name\n   * via {@link BoardGrid.setProfile}. A value may be a flat profile or a\n   * per-cell function (for stagger waves). Defaults to a single `'default'`\n   * profile, which is the active one until you `setProfile` otherwise.\n   */\n  profiles?: Record<string, BoardProfile>;\n}\n\nconst key = (c: BoardCell): string => `${c.col},${c.row}`;\nconst DEFAULT_PROFILE = 'default';\n\n/**\n * A grid of cells that each spin **independently** — the generic \"board of\n * reels\" primitive. Every cell is its own 1×1 {@link ReelSet}, so it inherits\n * the engine's phases, speed modes and pooling rather than a parallel lighter\n * reel.\n *\n * Deliberately mechanism-only: it knows nothing about coins, locks, respins,\n * value or any game rule. It lays the grid out, hands back per-cell geometry\n * and live symbol instances, places symbols instantly, and spins a\n * **caller-chosen** set of cells to caller-chosen results. Build your own\n * feature on top by owning the rules in your own code; {@link HoldAndWinBoard}\n * is one such opinionated layer, built entirely on this public surface.\n *\n * ```ts\n * const grid = new BoardGrid({\n *   cols: 3, rows: 3, cellSize: 80,\n *   symbols: (r) => r.register('prize', PrizeSymbol, {}),\n *   weights: { prize: 1, empty: 4 },\n *   ticker: app.ticker,\n * });\n * app.stage.addChild(grid.container);\n *\n * await grid.spinCells(\n *   grid.cells().map((cell) => ({ cell, id: pick() })),  // you decide each result\n *   (cell, id) => console.log('landed', cell, id),       // react as each settles\n * );\n * ```\n */\nexport class BoardGrid implements Disposable {\n  readonly container: Container;\n  readonly cols: number;\n  readonly rows: number;\n  readonly cellSize: number;\n  readonly gap: number;\n  readonly emptyId: string;\n\n  private readonly _reels = new Map<string, ReelSet>();\n  private readonly _cells: BoardCell[] = [];\n  private _destroyed = false;\n\n  constructor(opts: BoardGridOptions) {\n    if (!opts.ticker) throw new Error('BoardGrid: a ticker is required.');\n    this.cols = opts.cols;\n    this.rows = opts.rows;\n    this.cellSize = opts.cellSize;\n    this.gap = opts.gap ?? 4;\n    this.emptyId = opts.emptyId ?? 'empty';\n    this.container = new Container();\n\n    const profiles: Record<string, BoardProfile> =\n      opts.profiles && Object.keys(opts.profiles).length > 0\n        ? opts.profiles\n        : { [DEFAULT_PROFILE]: { ...SpeedPresets.NORMAL, minimumSpinTime: 320 } };\n    const profileNames = Object.keys(profiles);\n    const profileFor = (p: BoardProfile, cell: BoardCell): SpeedProfile =>\n      typeof p === 'function' ? p(cell) : p;\n\n    for (let col = 0; col < opts.cols; col++) {\n      for (let row = 0; row < opts.rows; row++) {\n        const cell: BoardCell = { col, row };\n        const origin = this._origin(cell);\n        if (opts.chrome) {\n          const bg = new Graphics();\n          opts.chrome(bg, this.cellSize);\n          bg.position.set(origin.x, origin.y);\n          this.container.addChild(bg);\n        }\n\n        const builder = new ReelSetBuilder()\n          .reels(1)\n          .visibleRows(1)\n          .symbolSize(this.cellSize, this.cellSize)\n          .symbolGap(0, 0)\n          // Spine symbols overrun the default per-reel rect mask; a shared rect\n          // keeps buffer-row art from painting over neighbouring cells.\n          .maskStrategy(new SharedRectMaskStrategy())\n          .symbols((registry) => {\n            opts.symbols(registry);\n            if (!registry.has(this.emptyId)) registry.register(this.emptyId, EmptySymbol, {});\n          })\n          .initialFrame([\n            { visible: [this.emptyId], bufferAbove: [this.emptyId], bufferBelow: [this.emptyId] },\n          ])\n          .ticker(opts.ticker)\n          // The active profile defaults to the engine's 'normal'; point it at\n          // the first registered name so any profile vocabulary works.\n          .initialSpeed(profileNames[0]);\n        for (const [name, profile] of Object.entries(profiles)) {\n          builder.speed(name, profileFor(profile, cell));\n        }\n        if (opts.weights) builder.weights(opts.weights);\n        if (opts.symbolData) builder.symbolData(opts.symbolData);\n        if (opts.rng) builder.rng(opts.rng);\n\n        const reelSet = builder.build();\n        reelSet.position.set(origin.x, origin.y);\n        this.container.addChild(reelSet);\n        this._reels.set(key(cell), reelSet);\n        this._cells.push(cell);\n      }\n    }\n  }\n\n  /** Every cell coordinate, row-major. */\n  cells(): BoardCell[] {\n    return this._cells.map((c) => ({ col: c.col, row: c.row }));\n  }\n\n  /** Board-local bounds of a cell. `container.toGlobal` for stage space. */\n  cellBounds(cell: BoardCell): { x: number; y: number; width: number; height: number } {\n    const origin = this._origin(cell);\n    return { x: origin.x, y: origin.y, width: this.cellSize, height: this.cellSize };\n  }\n\n  /** Board-local center of a cell — flight / trail start and end points. */\n  cellCenter(cell: BoardCell): { x: number; y: number } {\n    const origin = this._origin(cell);\n    return { x: origin.x + this.cellSize / 2, y: origin.y + this.cellSize / 2 };\n  }\n\n  /** Live symbol instance currently shown in a cell. */\n  symbolAt(cell: BoardCell): ReelSymbol {\n    return this._reel(cell).getReel(0).getSymbolAt(0);\n  }\n\n  /** The cell's underlying 1×1 ReelSet, for driving one cell directly. */\n  reelAt(cell: BoardCell): ReelSet {\n    return this._reel(cell);\n  }\n\n  /** Select a registered speed profile by name for one cell. */\n  setProfile(cell: BoardCell, name: string): void {\n    this._reel(cell).speed.set(name);\n  }\n\n  /** Place a symbol instantly (no spin), with blank off-window buffers. */\n  place(cell: BoardCell, id: string): void {\n    // Negative-index addresses bufferAbove; index 1 the single bufferBelow.\n    // Explicit empties keep the rest state from random-filling past the mask.\n    const ids: string[] & Record<number, string> = [id];\n    ids[-1] = this.emptyId;\n    ids[1] = this.emptyId;\n    this._reel(cell).getReel(0).placeSymbols(ids);\n  }\n\n  /**\n   * Spin each target cell and stop it showing its `id`; `onLanded` fires per\n   * cell as it settles, in stagger order. The caller selects which cells spin\n   * and to what — this layer applies no lock/free policy of its own. Set\n   * profiles via {@link setProfile} first.\n   *\n   * `onLanded` may be **async**: if it returns a promise, that cell's task\n   * awaits it, so the returned promise resolves only once every cell has landed\n   * *and* its after-land work has finished. Cells still run concurrently, so an\n   * early cell's reveal overlaps with later cells still spinning.\n   */\n  async spinCells(\n    targets: BoardSpinTarget[],\n    onLanded: (cell: BoardCell, id: string) => void | Promise<void> = () => {},\n  ): Promise<void> {\n    await Promise.all(\n      targets.map(async ({ cell, id }) => {\n        const reelSet = this._reel(cell);\n        const settle = reelSet.spin();\n        // Buffers land empty too: off-window art can paint over neighbours.\n        reelSet.setResult([\n          { visible: [id], bufferAbove: [this.emptyId], bufferBelow: [this.emptyId] },\n        ]);\n        await settle;\n        await onLanded(cell, id);\n      }),\n    );\n  }\n\n  /** Slam every in-flight cell to its landed position. Returns the count. */\n  skipSpinning(): number {\n    let inFlight = 0;\n    for (const reelSet of this._reels.values()) {\n      if (reelSet.isSpinning) {\n        inFlight += 1;\n        try {\n          reelSet.skipSpin();\n        } catch {\n          /* result not provided yet — nothing to skip to; ignore */\n        }\n      }\n    }\n    return inFlight;\n  }\n\n  get isDestroyed(): boolean {\n    return this._destroyed;\n  }\n\n  destroy(): void {\n    if (this._destroyed) return;\n    this._destroyed = true;\n    for (const reelSet of this._reels.values()) reelSet.destroy();\n    this._reels.clear();\n    this._cells.length = 0;\n    this.container.destroy({ children: true });\n  }\n\n  private _origin(cell: BoardCell): { x: number; y: number } {\n    return {\n      x: cell.col * (this.cellSize + this.gap),\n      y: cell.row * (this.cellSize + this.gap),\n    };\n  }\n\n  private _reel(cell: BoardCell): ReelSet {\n    const reelSet = this._reels.get(key(cell));\n    if (!reelSet) {\n      throw new Error(`BoardGrid: cell ${key(cell)} is outside the ${this.cols}x${this.rows} grid.`);\n    }\n    return reelSet;\n  }\n}\n","/**\n * Public types for the Hold & Win board. Kept in one leaf module so the\n * reducer ({@link HoldAndWinState}), the driver ({@link HoldAndWinBoard}) and\n * the builder can share them without import cycles.\n */\n\n/** Grid coordinate of a board cell. */\nexport interface HwCell {\n  col: number;\n  row: number;\n}\n\n/**\n * A coin somewhere on the board. `id` selects the registered symbol art;\n * `data` is an opaque game-layer payload the board never interprets.\n *\n * **Ownership contract:** `cell` and `id` belong to the board — treat them as\n * read-only; the board keys its ledger by cell and mutating them is undefined.\n * `data` is yours: mutate its contents freely (a doubler does `coin.data.value\n * *= 2`), that is the intended way to carry live game state on a locked coin.\n */\nexport interface HwCoin<TData = unknown> {\n  readonly cell: HwCell;\n  readonly id: string;\n  data?: TData;\n}\n\n/** Why the respin counter changed — disambiguates a `respins:changed` event. */\nexport type HwRespinReason = 'seed' | 'hit-reset' | 'miss';\n\n/** Resolution of one {@link HoldAndWinBoard.respin} wave. */\nexport interface HwRespinResult<TData = unknown> {\n  round: number;\n  hits: HwCoin<TData>[];\n  respinsLeft: number;\n  full: boolean;\n  /** True when the feature is over (counter exhausted or board full). */\n  done: boolean;\n}\n\nexport type HoldAndWinBoardEvents<TData = unknown> = {\n  'feature:enter': [{ seed: HwCoin<TData>[]; respins: number }];\n  'respin:start': [{ round: number; respinsLeft: number; spinning: HwCell[] }];\n  /** Fires per cell, in landing (stagger) order. `coin` is null on a miss. */\n  'cell:landed': [{ cell: HwCell; coin: HwCoin<TData> | null }];\n  'coin:locked': [{ coin: HwCoin<TData>; locked: number; capacity: number }];\n  /** Fired by `release()` — the collect / fly-away moment. */\n  'coin:released': [{ coin: HwCoin<TData>; remaining: number }];\n  'respins:changed': [{ value: number; reason: HwRespinReason }];\n  'respin:end': [{ round: number; hits: HwCoin<TData>[]; respinsLeft: number }];\n  'board:full': [{ coins: HwCoin<TData>[] }];\n  /** Fired by `skip()` so the game layer can cut its own flights / collect short. */\n  'feature:skip': [{ inFlight: number }];\n  /**\n   * Fired by `reset()` — a hard clear back to idle. Distinct from `coin:released`\n   * (which means \"collect this coin\"); listeners that maintain derived state\n   * from events (HUD totals, meters) clear it here without triggering collect.\n   */\n  'feature:reset': [{ clearedCoins: number }];\n  'feature:end': [{ coins: HwCoin<TData>[]; rounds: number; full: boolean }];\n};\n\n/**\n * One state-change the reducer ({@link HoldAndWinState}) decided, ready for the\n * driver to emit. A tagged pair of `{ type, payload }` for every board event the\n * reducer owns — the driver replays them onto its emitter and keys visual side\n * effects (e.g. `playWin()` on `coin:locked`) off the type. `respin:start` and\n * `feature:skip` are driver-owned (they describe in-flight reels, not ledger\n * state) and are not produced here.\n */\nexport type HwEffect<TData = unknown> = {\n  [K in keyof HoldAndWinBoardEvents<TData>]: {\n    type: K;\n    payload: HoldAndWinBoardEvents<TData>[K][0];\n  };\n}[keyof HoldAndWinBoardEvents<TData>];\n\nexport interface HwCellSizeOptions {\n  gap?: number;\n}\n\n/** `col,row` string key for the board's cell-indexed maps. */\nexport const cellKey = (c: HwCell): string => `${c.col},${c.row}`;\n","import type { HwCell, HwCoin, HwEffect, HwRespinReason } from './HwTypes.js';\nimport { cellKey } from './HwTypes.js';\n\nexport type HwPhase = 'idle' | 'active' | 'spinning';\n\n/**\n * The pure Hold & Win state machine — the single source of truth for board\n * state, with **zero** PixiJS. It owns the locked-coin ledger, the respin\n * counter, the round number and the feature {@link HwPhase}; every derived\n * value (`freeCells`, `isFull`) is computed from the one ledger, never stored\n * in parallel, so nothing can drift out of sync.\n *\n * It is a *reducer, not a cache*: {@link HoldAndWinBoard} drives the reels and\n * reports each landing here; the methods mutate the ledger and **return the\n * ordered list of {@link HwEffect}s to emit**, which the driver replays onto its\n * event emitter (and uses to fire visual side effects like `playWin()`). Locks\n * happen progressively as cells land in stagger order, so this is an\n * *incremental* reducer ({@link beginWave} → N×{@link land} → {@link endWave}),\n * not a one-shot `reduce(state, hits)`.\n *\n * Being PixiJS-free, it is fully unit-testable: assert the returned effect\n * sequence for hit / miss / full / release / reset / error paths.\n */\nexport class HoldAndWinState<TData = unknown> {\n  private readonly _locked = new Map<string, HwCoin<TData>>();\n  private readonly _cellSet: Set<string>;\n  private readonly _allCells: HwCell[];\n  private readonly _defaultRespins: number;\n  private _respinsLeft = 0;\n  private _round = 0;\n  private _phase: HwPhase = 'idle';\n  private _waveLanded: HwCoin<TData>[] = [];\n\n  constructor(allCells: HwCell[], defaultRespins: number) {\n    this._allCells = allCells;\n    this._cellSet = new Set(allCells.map(cellKey));\n    this._defaultRespins = defaultRespins;\n  }\n\n  // ── Queries ──────────────────────────────────────────────────────────\n\n  get phase(): HwPhase {\n    return this._phase;\n  }\n  get respinsLeft(): number {\n    return this._respinsLeft;\n  }\n  get round(): number {\n    return this._round;\n  }\n  get capacity(): number {\n    return this._allCells.length;\n  }\n  get isFull(): boolean {\n    return this._locked.size === this.capacity;\n  }\n\n  lockedCoins(): HwCoin<TData>[] {\n    return [...this._locked.values()];\n  }\n\n  freeCells(): HwCell[] {\n    return this._allCells.filter((c) => !this._locked.has(cellKey(c)));\n  }\n\n  isLocked(cell: HwCell): boolean {\n    return this._locked.has(cellKey(cell));\n  }\n\n  coinAt(cell: HwCell): HwCoin<TData> | undefined {\n    return this._locked.get(cellKey(cell));\n  }\n\n  // ── Transitions (mutate + return effects to emit) ───────────────────\n\n  /** Seed the trigger coins and arm the counter. Idle → active. */\n  enter(seed: HwCoin<TData>[]): HwEffect<TData>[] {\n    if (this._phase !== 'idle') {\n      throw new Error('HoldAndWinBoard: enter() while a feature is active — call reset() first.');\n    }\n    const placed: HwCoin<TData>[] = [];\n    const seen = new Set<string>();\n    for (const coin of seed) {\n      const k = cellKey(coin.cell);\n      this._assertInGrid(coin.cell, 'enter');\n      if (seen.has(k)) throw new Error(`HoldAndWinBoard: enter() seeds cell ${k} twice.`);\n      seen.add(k);\n      const stored = this._freeze(coin.cell, coin.id, coin.data);\n      this._locked.set(k, stored);\n      placed.push(stored);\n    }\n    this._round = 0;\n    this._phase = 'active';\n    return [\n      this._setRespins(this._defaultRespins, 'seed'),\n      { type: 'feature:enter', payload: { seed: placed, respins: this._respinsLeft } },\n    ];\n  }\n\n  /**\n   * Open a wave: validate the hits, mark every free cell as spinning, bump the\n   * round. Returns the data the driver needs to emit `respin:start` and to land\n   * each cell. Active → spinning.\n   */\n  beginWave(hits: HwCoin<TData>[]): {\n    round: number;\n    spinning: HwCell[];\n    hitByKey: Map<string, HwCoin<TData>>;\n  } {\n    if (this._phase === 'idle') {\n      throw new Error('HoldAndWinBoard: respin() before enter().');\n    }\n    if (this._phase === 'spinning') {\n      throw new Error('HoldAndWinBoard: respin() while a wave is in flight.');\n    }\n    const hitByKey = new Map<string, HwCoin<TData>>();\n    for (const hit of hits) {\n      const k = cellKey(hit.cell);\n      this._assertInGrid(hit.cell, 'respin');\n      if (this._locked.has(k)) throw new Error(`HoldAndWinBoard: hit targets locked cell ${k}.`);\n      // A free cell can only land once per wave, so a duplicate hit is always a\n      // malformed result. Fail loud rather than silently dropping the first coin.\n      if (hitByKey.has(k)) throw new Error(`HoldAndWinBoard: respin() targets cell ${k} twice.`);\n      hitByKey.set(k, hit);\n    }\n    this._waveLanded = [];\n    this._phase = 'spinning';\n    this._round += 1;\n    return { round: this._round, spinning: this.freeCells(), hitByKey };\n  }\n\n  /** Record one cell's landing. `coin` is null on a miss. */\n  land(cell: HwCell, coin: HwCoin<TData> | null): HwEffect<TData>[] {\n    // Outside a wave this is a stray landing — a sibling reel settling after\n    // the wave was aborted on error, or a cell landing after reset(). Drop it\n    // so it can't re-lock a coin into a cleared ledger or resurrect a feature.\n    if (this._phase !== 'spinning') return [];\n    if (!coin) {\n      return [{ type: 'cell:landed', payload: { cell, coin: null } }];\n    }\n    const stored = this._freeze(cell, coin.id, coin.data);\n    this._locked.set(cellKey(cell), stored);\n    this._waveLanded.push(stored);\n    return [\n      { type: 'cell:landed', payload: { cell, coin: stored } },\n      {\n        type: 'coin:locked',\n        payload: { coin: stored, locked: this._locked.size, capacity: this.capacity },\n      },\n    ];\n  }\n\n  /** Close the wave: resolve the counter, detect full / feature end. */\n  endWave(): { effects: HwEffect<TData>[]; landed: HwCoin<TData>[] } {\n    // The wave was aborted or reset out from under us (see land()'s guard).\n    // Closing it would re-arm the counter and flip a finished feature back to\n    // active off a wave that no longer exists. Do nothing.\n    if (this._phase !== 'spinning') return { effects: [], landed: [] };\n    const landed = this._waveLanded;\n    const effects: HwEffect<TData>[] = [];\n    effects.push(\n      landed.length > 0\n        ? this._setRespins(this._defaultRespins, 'hit-reset')\n        : this._setRespins(this._respinsLeft - 1, 'miss'),\n    );\n    this._phase = 'active';\n    effects.push({\n      type: 'respin:end',\n      payload: { round: this._round, hits: [...landed], respinsLeft: this._respinsLeft },\n    });\n    const full = this.isFull;\n    if (full) effects.push({ type: 'board:full', payload: { coins: this.lockedCoins() } });\n    const done = full || this._respinsLeft <= 0;\n    if (done) {\n      this._phase = 'idle';\n      effects.push({\n        type: 'feature:end',\n        payload: { coins: this.lockedCoins(), rounds: this._round, full },\n      });\n    }\n    // Hand back a caller-owned copy, not the live `_waveLanded` reference, so a\n    // consumer that mutates `respin().hits` can't reach into reducer state. The\n    // `respin:end` event payload above is already copied for the same reason.\n    return { effects, landed: [...landed] };\n  }\n\n  /**\n   * Abandon an in-flight wave after a driver error: restore the phase from\n   * `spinning` back to `active` so a thrown spin doesn't strand the board (every\n   * later `beginWave` would otherwise throw \"wave in flight\"). Cells that already\n   * landed stay locked; the caller decides whether to retry or `reset`.\n   */\n  abortWave(): void {\n    if (this._phase !== 'spinning') return;\n    this._phase = 'active';\n    this._waveLanded = [];\n  }\n\n  /** Remove locked coins — the collect moment. */\n  release(cells: HwCell[]): { effects: HwEffect<TData>[]; released: HwCoin<TData>[] } {\n    if (this._phase === 'spinning') {\n      throw new Error('HoldAndWinBoard: release() while a wave is in flight — await respin() first.');\n    }\n    const effects: HwEffect<TData>[] = [];\n    const released: HwCoin<TData>[] = [];\n    for (const cell of cells) {\n      const k = cellKey(cell);\n      const coin = this._locked.get(k);\n      if (!coin) continue;\n      this._locked.delete(k);\n      released.push(coin);\n      effects.push({ type: 'coin:released', payload: { coin, remaining: this._locked.size } });\n    }\n    return { effects, released };\n  }\n\n  /**\n   * Rewrite a **locked** cell's coin identity in place (coin → jackpot, mini →\n   * major). Throws on a free cell — placing a brand-new tracked coin out of a\n   * spin is `enter`/`respin`'s job; for purely decorative art on a free cell use\n   * the cell's reel directly.\n   */\n  swap(cell: HwCell, id: string, data: TData | undefined): void {\n    if (this._phase === 'spinning') {\n      throw new Error('HoldAndWinBoard: setSymbolAt() while a wave is in flight — await respin() first.');\n    }\n    const k = cellKey(cell);\n    const prev = this._locked.get(k);\n    if (!prev) {\n      throw new Error(\n        `HoldAndWinBoard: setSymbolAt(${k}) on a non-locked cell — setSymbolAt rewrites a locked coin's identity.`,\n      );\n    }\n    this._locked.set(k, this._freeze(cell, id, data ?? prev.data));\n  }\n\n  /** Hard clear back to idle. Fires `feature:reset`, never `coin:released`. */\n  reset(): HwEffect<TData>[] {\n    const clearedCoins = this._locked.size;\n    this._locked.clear();\n    this._waveLanded = [];\n    this._round = 0;\n    this._respinsLeft = 0;\n    this._phase = 'idle';\n    return [{ type: 'feature:reset', payload: { clearedCoins } }];\n  }\n\n  // ── Internals ────────────────────────────────────────────────────────\n\n  private _setRespins(value: number, reason: HwRespinReason): HwEffect<TData> {\n    this._respinsLeft = Math.max(0, value);\n    return { type: 'respins:changed', payload: { value: this._respinsLeft, reason } };\n  }\n\n  /**\n   * Store a coin with a board-owned, frozen `cell` so the ledger key can never\n   * be corrupted by a game mutating the coin it was handed. `data` is left\n   * mutable by reference — that is the supported way to carry live value.\n   */\n  private _freeze(cell: HwCell, id: string, data: TData | undefined): HwCoin<TData> {\n    return { cell: Object.freeze({ col: cell.col, row: cell.row }), id, data };\n  }\n\n  private _assertInGrid(cell: HwCell, op: string): void {\n    if (!this._cellSet.has(cellKey(cell))) {\n      throw new Error(`HoldAndWinBoard: ${op}() targets cell ${cellKey(cell)} outside the grid.`);\n    }\n  }\n}\n","import type { Container, Graphics, Ticker } from 'pixi.js';\nimport { EventEmitter } from '../events/EventEmitter.js';\nimport type { ReelSet } from '../core/ReelSet.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport type { SpeedProfile, SymbolData } from '../config/types.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport { BoardGrid } from './BoardGrid.js';\nimport { HoldAndWinState } from './HoldAndWinState.js';\nimport type { HwPhase } from './HoldAndWinState.js';\nimport { cellKey } from './HwTypes.js';\nimport type {\n  HoldAndWinBoardEvents,\n  HwCell,\n  HwCoin,\n  HwEffect,\n  HwRespinResult,\n} from './HwTypes.js';\n\n/** Internal config produced by {@link HoldAndWinBuilder.build}. */\nexport interface HoldAndWinBoardConfig<TData> {\n  cols: number;\n  rows: number;\n  cell: number;\n  gap: number;\n  emptyId: string;\n  respins: number;\n  configurator: (registry: SymbolRegistry) => void;\n  weights: Record<string, number> | null;\n  symbolData: Record<string, Partial<SymbolData>> | null;\n  baseProfile: SpeedProfile;\n  stagger: (col: number, row: number) => number;\n  anticipateWhen:\n    | ((state: { locked: number; capacity: number; respinsLeft: number }) => boolean)\n    | null;\n  chrome: ((g: Graphics, size: number) => void) | null;\n  ticker: Ticker;\n  rng: (() => number) | null;\n}\n\n/** Extra spin time (ms) a tense wave adds on top of the normal profile. */\nconst TENSION_EXTRA_MS = 1100;\n\n/**\n * A Hold & Win board: a grid of independently spinning cells plus the round\n * choreography every H&W game repeats — spin the free cells, lock the hits,\n * reset-or-decrement the respin counter, detect the full board.\n *\n * It composes two collaborators: a `BoardGrid` (the generic \"board of reels\"\n * mechanism — geometry, instances, spinning) and a `HoldAndWinState` (the pure\n * single-source reducer — ledger, counter, phase). The board is the\n * mediator: it drives the reels, reports each landing to the reducer, and\n * replays the reducer's decided effects onto {@link events}.\n *\n * It deliberately owns nothing about *value*. Coins are opaque `{ cell, id, data }`\n * — `id` picks the registered art, `data` is the game layer's to read and mutate.\n * Adders, doublers, collectors and flights are game design, expressed through\n * three openings rather than board features: {@link events}, {@link symbolAt}\n * (the live `ReelSymbol` instance) and {@link cellBounds}/{@link cellCenter}\n * (pixel geometry for flights).\n *\n * ```ts\n * const board = new HoldAndWinBuilder<{ value: number }>()\n *   .grid(5, 3).cellSize(72, { gap: 4 })\n *   .symbols((r) => r.register('coin', CoinSymbol, COIN_TRIGGER))\n *   .weights({ coin: 1, empty: 3 }).respins(3).ticker(app.ticker)\n *   .build();\n *\n * board.events.on('coin:locked', ({ coin }) => hud.add(coin.data.value));\n * board.enter(triggerCoins);\n * while (true) {\n *   const round = await server.respin(board.lockedCoins);\n *   const result = await board.respin(round.hits);\n *   if (result.done) break;                  // game animates between rounds\n * }\n * ```\n */\nexport class HoldAndWinBoard<TData = unknown> implements Disposable {\n  readonly events = new EventEmitter<HoldAndWinBoardEvents<TData>>();\n  readonly cols: number;\n  readonly rows: number;\n\n  private readonly _grid: BoardGrid;\n  private readonly _state: HoldAndWinState<TData>;\n  private readonly _emptyId: string;\n  private readonly _anticipateWhen: HoldAndWinBoardConfig<TData>['anticipateWhen'];\n\n  constructor(cfg: HoldAndWinBoardConfig<TData>) {\n    this.cols = cfg.cols;\n    this.rows = cfg.rows;\n    this._emptyId = cfg.emptyId;\n    this._anticipateWhen = cfg.anticipateWhen;\n\n    const base = (cell: HwCell): number =>\n      (cfg.baseProfile.minimumSpinTime ?? 320) + cfg.stagger(cell.col, cell.row);\n    this._grid = new BoardGrid({\n      cols: cfg.cols,\n      rows: cfg.rows,\n      cellSize: cfg.cell,\n      gap: cfg.gap,\n      emptyId: cfg.emptyId,\n      symbols: cfg.configurator,\n      weights: cfg.weights ?? undefined,\n      symbolData: cfg.symbolData ?? undefined,\n      chrome: cfg.chrome ?? undefined,\n      ticker: cfg.ticker,\n      rng: cfg.rng ?? undefined,\n      profiles: {\n        normal: (cell) => ({ ...cfg.baseProfile, minimumSpinTime: base(cell) }),\n        tension: (cell) => ({ ...cfg.baseProfile, minimumSpinTime: base(cell) + TENSION_EXTRA_MS }),\n      },\n    });\n    this._state = new HoldAndWinState<TData>(this._grid.cells(), cfg.respins);\n  }\n\n  // ── State (delegated to the single-source reducer) ───────────────────\n\n  get container(): Container {\n    return this._grid.container;\n  }\n  get capacity(): number {\n    return this._state.capacity;\n  }\n  get respinsLeft(): number {\n    return this._state.respinsLeft;\n  }\n  get lockedCoins(): HwCoin<TData>[] {\n    return this._state.lockedCoins();\n  }\n  get isFull(): boolean {\n    return this._state.isFull;\n  }\n  get freeCells(): HwCell[] {\n    return this._state.freeCells();\n  }\n  /** Where the feature is right now: idle (no feature), active, or spinning. */\n  get phase(): HwPhase {\n    return this._state.phase;\n  }\n\n  // ── Geometry & instances (the game layer's openings) ────────────────\n\n  cellBounds(cell: HwCell): { x: number; y: number; width: number; height: number } {\n    return this._grid.cellBounds(cell);\n  }\n  cellCenter(cell: HwCell): { x: number; y: number } {\n    return this._grid.cellCenter(cell);\n  }\n  /** Live symbol instance currently shown in a cell. */\n  symbolAt(cell: HwCell): ReelSymbol {\n    return this._grid.symbolAt(cell);\n  }\n  /** The cell's underlying 1×1 ReelSet, for driving one cell directly. */\n  reelAt(cell: HwCell): ReelSet {\n    return this._grid.reelAt(cell);\n  }\n\n  /**\n   * Rewrite a **locked** cell's coin in place — coin → jackpot, mini → major,\n   * raise a tier — without disturbing any other cell. The ledger entry is\n   * rewritten so `lockedCoins` and totals stay correct. Throws on a free cell.\n   * Returns the new live symbol instance.\n   *\n   * Throws if called while a wave is in flight — `await respin()` first. To\n   * upgrade a coin in reaction to its own `coin:locked`, defer the swap until\n   * the awaited `respin()` resolves rather than swapping inside the listener.\n   */\n  setSymbolAt(cell: HwCell, id: string, data?: TData): ReelSymbol {\n    this._state.swap(cell, id, data);\n    this._grid.place(cell, id);\n    return this._grid.symbolAt(cell);\n  }\n\n  // ── Round choreography ───────────────────────────────────────────────\n\n  /** Activate the feature with the trigger coins. Seeds land locked, instantly. */\n  enter(seed: HwCoin<TData>[]): void {\n    const effects = this._state.enter(seed); // validates first; throws before any visual\n    for (const coin of seed) this._grid.place(coin.cell, coin.id);\n    this._apply(effects);\n  }\n\n  /**\n   * Spin every free cell; `hits` land (and lock) their coins, all other spinning\n   * cells land empty. Resolves once the wave has landed and the counter is\n   * resolved. The game layer drives pacing between rounds.\n   */\n  async respin(hits: HwCoin<TData>[]): Promise<HwRespinResult<TData>> {\n    const { round, spinning, hitByKey } = this._state.beginWave(hits);\n    try {\n      const tense = this._anticipating() && spinning.length > 0;\n      for (const cell of spinning) this._grid.setProfile(cell, tense ? 'tension' : 'normal');\n      this.events.emit('respin:start', { round, respinsLeft: this._state.respinsLeft, spinning });\n\n      const targets = spinning.map((cell) => ({\n        cell,\n        id: hitByKey.get(cellKey(cell))?.id ?? this._emptyId,\n      }));\n      await this._grid.spinCells(targets, (cell) => {\n        this._apply(this._state.land(cell, hitByKey.get(cellKey(cell)) ?? null));\n      });\n\n      const { effects, landed } = this._state.endWave();\n      this._apply(effects);\n      return {\n        round,\n        hits: landed,\n        respinsLeft: this._state.respinsLeft,\n        full: this._state.isFull,\n        done: this._state.phase === 'idle',\n      };\n    } catch (err) {\n      // A synchronous throw between beginWave and endWave — most plausibly a\n      // game-layer event listener (respin:start / cell:landed / coin:locked)\n      // throwing — must not strand the board. (An unregistered symbol id does\n      // NOT land here: the engine logs and slams the reel internally, so the\n      // spin resolves rather than rejecting.) abortWave() restores the reducer\n      // phase, else every later respin() throws \"wave in flight\"; skipSpinning()\n      // lands any cell still in flight, else the next respin() throws \"already\n      // spinning\" on it. Stray landings from those slams are dropped by the\n      // reducer's not-spinning guard. Rethrow so the caller still sees it.\n      this._state.abortWave();\n      this._grid.skipSpinning();\n      throw err;\n    }\n  }\n\n  /**\n   * Remove locked coins — the collect moment. Clears the cells (they become\n   * free again) and returns the released coins; the flight itself is game-layer\n   * animation, started from `cellCenter()` or the `coin:released` event.\n   */\n  release(cells: HwCell[]): HwCoin<TData>[] {\n    const { effects, released } = this._state.release(cells);\n    for (const coin of released) this._grid.place(coin.cell, this._emptyId);\n    this._apply(effects);\n    return released;\n  }\n\n  /**\n   * Fast-forward whatever is spinning: every in-flight cell is slammed to its\n   * landed position, then `feature:skip` fires so the game layer can cut its own\n   * flights short. The normal landing → `coin:locked` → `feature:end` flow still\n   * resolves; this only removes the waiting. Returns the number of cells that\n   * were in flight.\n   */\n  skip(): number {\n    const inFlight = this._grid.skipSpinning();\n    this.events.emit('feature:skip', { inFlight });\n    return inFlight;\n  }\n\n  /** Clear the board back to idle. Fires `feature:reset` (not `coin:released`). */\n  reset(): void {\n    const effects = this._state.reset();\n    for (const cell of this._grid.cells()) this._grid.place(cell, this._emptyId);\n    this._apply(effects);\n  }\n\n  get isDestroyed(): boolean {\n    return this._grid.isDestroyed;\n  }\n\n  destroy(): void {\n    if (this._grid.isDestroyed) return;\n    this.events.removeAllListeners();\n    this._grid.destroy();\n  }\n\n  // ── Internals ────────────────────────────────────────────────────────\n\n  /** Emit each reducer-decided effect and fire the visual side effects. */\n  private _apply(effects: HwEffect<TData>[]): void {\n    for (const fx of effects) {\n      // Correlated union: `fx.type` and `fx.payload` are paired by construction\n      // in the reducer, but TS can't carry that correlation through `emit`'s\n      // generic. One local cast keeps every other call site fully typed.\n      (this.events.emit as (type: string, payload: unknown) => void)(fx.type, fx.payload);\n      if (fx.type === 'coin:locked') {\n        // playWin is presentation; a hiccup must not break the feature flow, but\n        // it must not vanish silently either — log it like the rest of the engine.\n        void this.symbolAt(fx.payload.coin.cell)\n          .playWin()\n          .catch((err) => console.warn('HoldAndWinBoard: coin win animation failed.', err));\n      }\n    }\n  }\n\n  private _anticipating(): boolean {\n    if (!this._anticipateWhen) return false;\n    return this._anticipateWhen({\n      locked: this._state.lockedCoins().length,\n      capacity: this._state.capacity,\n      respinsLeft: this._state.respinsLeft,\n    });\n  }\n}\n","import type { Graphics, Ticker } from 'pixi.js';\nimport { SpeedPresets } from '../config/SpeedPresets.js';\nimport type { SpeedProfile, SymbolData } from '../config/types.js';\nimport type { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport { HoldAndWinBoard } from './HoldAndWinBoard.js';\nimport type { HwCellSizeOptions } from './HwTypes.js';\n\n/**\n * Fluent builder for {@link HoldAndWinBoard}.\n *\n * A Hold & Win board is a W×H grid of cells that spin **independently** — the\n * mechanic's atomic unit is the cell, the engine's is the column, so each cell\n * is its own 1×1 ReelSet. This builder wires that grid plus the round\n * choreography; everything value-shaped stays in the game layer (see\n * {@link HoldAndWinBoard}).\n *\n * `TData` types the opaque payload carried on each coin's `data`.\n */\nexport class HoldAndWinBuilder<TData = unknown> {\n  private _cols = 5;\n  private _rows = 3;\n  private _cell = 72;\n  private _gap = 4;\n  private _emptyId = 'empty';\n  private _respins = 3;\n  private _configurator: ((registry: SymbolRegistry) => void) | null = null;\n  private _weights: Record<string, number> | null = null;\n  private _symbolData: Record<string, Partial<SymbolData>> | null = null;\n  private _baseProfile: SpeedProfile = { ...SpeedPresets.NORMAL, minimumSpinTime: 320 };\n  private _stagger: (col: number, row: number) => number = (col, row) => (col + row) * 70;\n  private _anticipateWhen:\n    | ((state: { locked: number; capacity: number; respinsLeft: number }) => boolean)\n    | null = null;\n  private _chrome: ((g: Graphics, size: number) => void) | null = null;\n  private _ticker: Ticker | null = null;\n  private _rng: (() => number) | null = null;\n\n  grid(cols: number, rows: number): this {\n    this._cols = cols;\n    this._rows = rows;\n    return this;\n  }\n\n  cellSize(size: number, opts: HwCellSizeOptions = {}): this {\n    this._cell = size;\n    this._gap = opts.gap ?? this._gap;\n    return this;\n  }\n\n  /**\n   * Register coin symbol classes, exactly like `ReelSetBuilder.symbols`. Applied\n   * to every cell. An {@link EmptySymbol} is auto-registered under {@link emptyId}\n   * unless the configurator registers one itself.\n   */\n  symbols(configurator: (registry: SymbolRegistry) => void): this {\n    this._configurator = configurator;\n    return this;\n  }\n\n  /** Strip weights during the spin (how often coins flash past empties). */\n  weights(weights: Record<string, number>): this {\n    this._weights = weights;\n    return this;\n  }\n\n  /** Symbol id a cell shows when it holds no coin. Default `'empty'`. */\n  emptyId(id: string): this {\n    this._emptyId = id;\n    return this;\n  }\n\n  /**\n   * Per-symbol engine overrides, exactly like `ReelSetBuilder.symbolData`. The\n   * headline use is `{ unmask: true }` for coins whose lock/reveal animations\n   * expand past the cell. Safe only for server-placed ids (weight 0): unmasked\n   * strip symbols mis-track vertically while the reel spins.\n   */\n  symbolData(overrides: Record<string, Partial<SymbolData>>): this {\n    this._symbolData = { ...(this._symbolData ?? {}), ...overrides };\n    return this;\n  }\n\n  /** Respins granted on enter and restored on every hit. Default 3. */\n  respins(count: number): this {\n    this._respins = count;\n    return this;\n  }\n\n  /** Base spin feel for every cell. Default: NORMAL with a 320ms floor. */\n  speedProfile(profile: SpeedProfile): this {\n    this._baseProfile = profile;\n    return this;\n  }\n\n  /**\n   * Extra milliseconds of spin per cell on top of the base minimum spin time.\n   * Default `(col + row) * 70` — the diagonal landing wave. Return 0 for\n   * simultaneous landings.\n   */\n  stagger(fn: (col: number, row: number) => number): this {\n    this._stagger = fn;\n    return this;\n  }\n\n  /**\n   * When the predicate returns true for a wave, **every** spinning cell uses a\n   * drawn-out tension profile — the \"one cell left for Grand\" moment. Evaluated\n   * once per wave for the whole board (not per cell), against the pre-wave state.\n   */\n  anticipateWhen(\n    fn: (state: { locked: number; capacity: number; respinsLeft: number }) => boolean,\n  ): this {\n    this._anticipateWhen = fn;\n    return this;\n  }\n\n  /** Per-cell background, drawn behind each mini reel. */\n  cellChrome(draw: (g: Graphics, size: number) => void): this {\n    this._chrome = draw;\n    return this;\n  }\n\n  ticker(ticker: Ticker): this {\n    this._ticker = ticker;\n    return this;\n  }\n\n  /** Injected RNG for the spin strips (deterministic demos / tests). */\n  rng(fn: () => number): this {\n    this._rng = fn;\n    return this;\n  }\n\n  build(): HoldAndWinBoard<TData> {\n    if (!this._configurator) {\n      throw new Error('HoldAndWinBuilder: .symbols(...) is required — register at least one coin id.');\n    }\n    if (!this._ticker) {\n      throw new Error('HoldAndWinBuilder: .ticker(...) is required.');\n    }\n    return new HoldAndWinBoard<TData>({\n      cols: this._cols,\n      rows: this._rows,\n      cell: this._cell,\n      gap: this._gap,\n      emptyId: this._emptyId,\n      respins: this._respins,\n      configurator: this._configurator,\n      weights: this._weights,\n      symbolData: this._symbolData,\n      baseProfile: this._baseProfile,\n      stagger: this._stagger,\n      anticipateWhen: this._anticipateWhen,\n      chrome: this._chrome,\n      ticker: this._ticker,\n      rng: this._rng,\n    });\n  }\n}\n","import { Container, Graphics } from 'pixi.js';\nimport type { Ticker } from 'pixi.js';\nimport { EventEmitter } from '../events/EventEmitter.js';\nimport { SymbolFactory } from '../symbols/SymbolFactory.js';\nimport { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport { TickerRef } from '../utils/TickerRef.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport type { ColumnTarget } from '../frame/ColumnTarget.js';\nimport type { SpinResult } from '../events/ReelEvents.js';\nimport type {\n  HorizontalDirection,\n  HorizontalReelConfig,\n  HorizontalReelEvents,\n} from './HorizontalReelTypes.js';\n\n/** A symbol tweening from `fromX` to its resting `toX` during a cascade. */\ninterface CascadeMove {\n  inst: ReelSymbol;\n  fromX: number;\n  toX: number;\n}\n\n/**\n * A single horizontal reel — the banner reel that sits **above** the reels\n * announcing which symbols pay this round.\n *\n * It is one row, oriented sideways, and it follows the same contract as\n * {@link ReelSet}:\n *\n *   - `spin()` starts it and returns a promise.\n *   - `setResult(ids)` hands it the round's paying symbols (one per visible\n *     cell) and triggers the stop; the `spin()` promise resolves on land.\n *   - `cascade(winners, newIds?)` is the real tumble: the winning cells are\n *     **removed**, the survivors **collapse** to close the gaps, and new symbols\n *     **slide in from the feed edge**, exactly like the main reels' cascade.\n *     Pass every cell for a full \"they all drop\", or just the winning cells.\n *\n * The engine's {@link Reel} wraps on the Y axis and bakes that in throughout, so\n * this is its own small mechanism on the shared primitives (the\n * {@link SymbolFactory} pool, {@link TickerRef}, the typed {@link EventEmitter})\n * rather than a rotated reel.\n *\n * **Known trade-off / future direction.** This class deliberately *mirrors* the\n * {@link ReelSet} API rather than being one. That mirror is technical debt: it\n * re-implements the spin lifecycle and a parallel `cascade` next to\n * `ReelSet.runCascade`/`refill`, and it does not inherit anticipation, speed\n * modes, holds, pins, the spotlight or debug. The intended fix is to give\n * {@link Reel}/`ReelMotion`/`ReelViewport` an **orientation axis** so ONE reel\n * does vertical or horizontal, and this class retires in favour of a 1-reel\n * horizontal `ReelSet`. Deferred because that touches core motion and risks\n * regressing the vertical path. See the \"Horizontal reels\" row in ROADMAP.md.\n *\n * ```ts\n * const spin = strip.spin();\n * strip.setResult([{ visible: ['A','K','Q','J'] }]); // one ColumnTarget (this reel)\n * await spin;\n * // main reel reports A and Q were in a win → those cells tumble:\n * await strip.cascade([0, 2], ['WILD','K']);\n * ```\n */\nexport class HorizontalReel implements Disposable {\n  readonly container: Container;\n  readonly events = new EventEmitter<HorizontalReelEvents>();\n  readonly visibleCount: number;\n\n  private readonly _cellW: number;\n  private readonly _cellH: number;\n  private readonly _span: number;\n  private readonly _windowWidth: number;\n  private readonly _direction: HorizontalDirection;\n  private readonly _cfg: HorizontalReelConfig;\n  private readonly _factory: SymbolFactory;\n  private readonly _tickerRef: TickerRef;\n  private readonly _registeredIds: string[];\n  private readonly _rng: () => number;\n\n  /** Conveyor of `visibleCount + 2` instances: 1 buffer, the window, 1 buffer. */\n  private readonly _slots: ReelSymbol[] = [];\n  private readonly _M: number;\n  /** Scroll progress within the current cell, in `[0, span)`. */\n  private _off = 0;\n  private _state: 'idle' | 'spinning' | 'stopping' | 'landing' | 'cascading' = 'idle';\n  private _queue: string[] = [];\n  private _resolve: ((r: SpinResult) => void) | null = null;\n  private _spinStart = 0;\n  private _wasSkipped = false;\n  private _landFrom = 0;\n  private _landT = 0;\n  private _destroyed = false;\n\n  // Cascade state.\n  private _cascadeT = 0;\n  private _cascadeRemoving: { inst: ReelSymbol; baseX: number; baseY: number }[] = []; // winners imploding\n  private _cascadeMoving: CascadeMove[] = []; // survivors collapsing + new symbols refilling\n  private _cascadeFinalWindow: ReelSymbol[] = []; // the window instances after the tumble\n  private _cascadeWinners: number[] = [];\n  private _cascadeResolve: (() => void) | null = null;\n\n  constructor(cfg: HorizontalReelConfig) {\n    if (!cfg.ticker) throw new Error('HorizontalReel: a ticker is required.');\n    this._cfg = cfg;\n    this.visibleCount = cfg.visibleCount;\n    this._cellW = cfg.cellWidth;\n    this._cellH = cfg.cellHeight;\n    this._span = cfg.cellWidth + cfg.gap;\n    this._windowWidth = cfg.visibleCount * this._span - cfg.gap;\n    this._direction = cfg.direction;\n    this._M = cfg.visibleCount + 2;\n    this._rng = cfg.rng ?? Math.random;\n\n    this.container = new Container();\n\n    const registry = new SymbolRegistry();\n    cfg.configurator(registry);\n    this._registeredIds = registry.symbolIds;\n    if (this._registeredIds.length === 0) {\n      throw new Error('HorizontalReel: .symbols(...) registered no symbol ids.');\n    }\n    if (cfg.initialFrame.length !== 1) {\n      throw new Error(\n        `HorizontalReel: initialFrame takes exactly one ColumnTarget (this reel); got ${cfg.initialFrame.length}.`,\n      );\n    }\n    const initialIds = cfg.initialFrame[0].visible;\n    for (const id of initialIds) this._assertRegistered(id);\n    if (initialIds.length !== cfg.visibleCount) {\n      throw new Error(\n        `HorizontalReel: initialFrame visible must have exactly ${cfg.visibleCount} ids (got ${initialIds.length}).`,\n      );\n    }\n    this._factory = new SymbolFactory(registry);\n\n    if (cfg.chrome) {\n      const bg = new Graphics();\n      cfg.chrome(bg, this._windowWidth, this._cellH);\n      this.container.addChild(bg);\n    }\n    const mask = new Graphics().rect(0, 0, this._windowWidth, this._cellH).fill(0xffffff);\n    this.container.addChild(mask);\n    this.container.mask = mask;\n\n    this._layout(initialIds);\n    this._tickerRef = new TickerRef(cfg.ticker);\n    this._tickerRef.add((t) => this._tick(t));\n  }\n\n  get width(): number {\n    return this._windowWidth;\n  }\n  get height(): number {\n    return this._cellH;\n  }\n  get direction(): HorizontalDirection {\n    return this._direction;\n  }\n  get isSpinning(): boolean {\n    return this._state === 'spinning' || this._state === 'stopping' || this._state === 'landing';\n  }\n  get isCascading(): boolean {\n    return this._state === 'cascading';\n  }\n  get isDestroyed(): boolean {\n    return this._destroyed;\n  }\n\n  /**\n   * Start spinning. Resolves with the landed result once {@link setResult} has\n   * been called and the strip settles (or {@link skipSpin} slams it). Mirrors\n   * `ReelSet.spin()`. Throws if not idle.\n   */\n  spin(): Promise<SpinResult> {\n    if (this._state !== 'idle') {\n      throw new Error('HorizontalReel: not idle — await the previous spin()/cascade() first.');\n    }\n    this._state = 'spinning';\n    this._queue = [];\n    this._wasSkipped = false;\n    this._spinStart = performance.now();\n    // Same contract as Reel.notifySpinStart: every conveyor slot (buffers\n    // included) learns the strip is moving so spin presentations (blur,\n    // static snapshots) engage.\n    for (const slot of this._slots) slot.onReelSpinStart();\n    this.events.emit('spin:start');\n    return new Promise((resolve) => {\n      this._resolve = resolve;\n    });\n  }\n\n  /**\n   * Hand the reel the round's paying symbols and trigger the stop. Takes the\n   * same `ColumnTarget[]` as `ReelSet.setResult` — this reel is a single column,\n   * so pass exactly one entry whose `visible` holds `visibleCount` ids,\n   * left-to-right. Mirrors `ReelSet.setResult(...)`. Throws if not spinning.\n   */\n  setResult(symbols: ColumnTarget[]): void {\n    if (this._state !== 'spinning') {\n      throw new Error('HorizontalReel: call spin() before setResult().');\n    }\n    if (symbols.length !== 1) {\n      throw new Error(\n        `HorizontalReel: setResult takes exactly one ColumnTarget (this reel); got ${symbols.length}.`,\n      );\n    }\n    const target = symbols[0];\n    if (target.bufferAbove?.some((v) => v !== undefined) || target.bufferBelow?.some((v) => v !== undefined)) {\n      throw new Error('HorizontalReel: setResult does not support bufferAbove/bufferBelow (single-row reel).');\n    }\n    const ids = target.visible;\n    this._assertResultShape(ids);\n    // The last `visibleCount` symbols to feed must be the result, in the order\n    // that lands them left-to-right, plus one trailing buffer feed. rtl feeds\n    // the window in order; ltr feeds it reversed (see _doShift).\n    const windowFeeds = this._direction === 'rtl' ? [...ids] : [...ids].reverse();\n    this._queue = [...windowFeeds, this._randomId()];\n    this._state = 'stopping';\n    // The stop DECELERATES visibly (the drain ease in _advanceSpin), unlike\n    // the main reels, which hold full speed until the snap. Blur reads\n    // wrong on a slowing strip, so spin-end fires here — the outgoing\n    // symbols sharpen as the strip slows, and the result window feeds in\n    // live (crisp): what lands is never blurred. _repaint stops blur-\n    // joining new symbols once we leave 'spinning'.\n    for (const slot of this._slots) slot.onReelSpinEnd();\n  }\n\n  /**\n   * Slam to the result immediately. Mirrors `ReelSet.skipSpin()`. Requires a\n   * result to have been set.\n   */\n  skipSpin(): void {\n    if (this._state !== 'stopping' && this._state !== 'landing') {\n      throw new Error('HorizontalReel: skipSpin() needs a pending result — call setResult() first.');\n    }\n    this._wasSkipped = true;\n    while (this._queue.length > 0) this._doShift(this._queue.shift() as string);\n    this._off = 0;\n    this._render();\n    this._land();\n  }\n\n  /**\n   * Tumble the winning cells — a real cascade with removal, the same mechanic\n   * the main reels run, one row wide:\n   *\n   *   1. the `winners` symbols are **removed** (destroyed, they poof out);\n   *   2. the survivors **collapse** toward the settle edge to close the gaps,\n   *      keeping their left-to-right order;\n   *   3. `winners.length` **new symbols slide in from the feed edge** — the same\n   *      side the spin brings symbols from (`rtl` from the right, `ltr` from the\n   *      left) — to fill the freed slots.\n   *\n   * `winners` are visible indices (left-to-right from 0); pass every index for a\n   * full \"they all drop\". `newIds` are the incoming symbols (one per winner, in\n   * feed order); defaults to random. Resolves once the tumble settles and fires\n   * `cascade:complete`. Throws unless idle (a spin must have landed first).\n   */\n  cascade(winners: number[], newIds?: string[]): Promise<void> {\n    if (this._state !== 'idle') {\n      throw new Error('HorizontalReel: cascade() needs the reel idle — await spin()/cascade() first.');\n    }\n    const unique = [...new Set(winners)];\n    if (unique.length !== winners.length) {\n      throw new Error('HorizontalReel: cascade() winners must be unique.');\n    }\n    for (const w of winners) {\n      if (w < 0 || w >= this.visibleCount) {\n        throw new Error(`HorizontalReel: cascade() winner ${w} is outside 0..${this.visibleCount - 1}.`);\n      }\n    }\n    const replacements = newIds ?? winners.map(() => this._randomId());\n    if (replacements.length !== winners.length) {\n      throw new Error('HorizontalReel: cascade() newIds must match winners length.');\n    }\n    for (const id of replacements) this._assertRegistered(id);\n    if (winners.length === 0) return Promise.resolve();\n\n    const winnerSet = new Set(winners);\n    const window = this._slots.slice(1, 1 + this.visibleCount); // current visible instances\n    const removed = winners.map((w) => {\n      const inst = window[w];\n      return { inst, baseX: inst.view.x, baseY: inst.view.y }; // for center-scaled implode\n    });\n    const survivors = window.filter((_, i) => !winnerSet.has(i)); // kept, in order\n\n    // New symbols enter from the feed edge; acquire them now.\n    const news = replacements.map((id) => {\n      const inst = this._factory.acquire(id);\n      inst.resize(this._cellW, this._cellH);\n      inst.view.y = 0;\n      this.container.addChild(inst.view); // added last → draws over what it crosses\n      return inst;\n    });\n\n    // Post-tumble window: survivors collapse to the settle side, new symbols\n    // fill the feed side. rtl feeds from the right (settle left); ltr the mirror.\n    const finalWindow =\n      this._direction === 'rtl' ? [...survivors, ...news] : [...news, ...survivors];\n    this._cascadeFinalWindow = finalWindow;\n\n    // Off-window start, one full window to the feed side, so the new symbols\n    // slide in as a rigid train from off-screen without crossing survivors.\n    const offset = this.visibleCount * this._span;\n    this._cascadeMoving = finalWindow.map((inst, j) => {\n      const toX = j * this._span;\n      const isNew = news.includes(inst);\n      const fromX = isNew ? toX - this._feedSign() * offset : inst.view.x;\n      if (isNew) {\n        inst.view.x = fromX;\n        inst.view.alpha = 1;\n      }\n      return { inst, fromX, toX };\n    });\n    this._cascadeRemoving = removed;\n    this._cascadeWinners = [...winners];\n    this._cascadeT = 0;\n    this._state = 'cascading';\n    return new Promise((resolve) => {\n      this._cascadeResolve = resolve;\n    });\n  }\n\n  /** The live symbol instance in visible slot `index`, left-to-right from 0. */\n  symbolAt(index: number): ReelSymbol {\n    if (index < 0 || index >= this.visibleCount) {\n      throw new Error(`HorizontalReel: slot ${index} is outside 0..${this.visibleCount - 1}.`);\n    }\n    return this._slots[index + 1]; // slot 0 is the left buffer\n  }\n\n  destroy(): void {\n    if (this._destroyed) return;\n    this._destroyed = true;\n    this._state = 'idle';\n    this._tickerRef.destroy();\n    this.events.removeAllListeners();\n    // Destroy any cascade-in-flight instances not yet folded into _slots\n    // (removing winners + incoming new symbols). destroy() is idempotent, so\n    // survivors already in _slots are safe to hit again below.\n    for (const c of this._cascadeRemoving) if (!c.inst.isDestroyed) c.inst.destroy();\n    for (const m of this._cascadeMoving) if (!m.inst.isDestroyed) m.inst.destroy();\n    this._cascadeRemoving = [];\n    this._cascadeMoving = [];\n    this._cascadeFinalWindow = [];\n    for (const s of this._slots) s.destroy();\n    this._slots.length = 0;\n    this._factory.destroy();\n    this.container.destroy({ children: true });\n    // A spin in flight when destroyed resolves so awaiters don't hang forever.\n    this._resolve?.({ symbols: [[]], wasSkipped: true, duration: 0 });\n    this._resolve = null;\n    this._cascadeResolve?.();\n    this._cascadeResolve = null;\n  }\n\n  // ── Internals ────────────────────────────────────────────────────────\n\n  /** Seed the conveyor: [left buffer, ...window, right buffer]. */\n  private _layout(initial: string[]): void {\n    const ids = [this._randomId(), ...initial, this._randomId()]; // length M\n    for (let k = 0; k < this._M; k++) {\n      const symbol = this._factory.acquire(ids[k]);\n      symbol.resize(this._cellW, this._cellH);\n      symbol.view.y = 0;\n      this.container.addChild(symbol.view);\n      this._slots.push(symbol);\n    }\n    this._render();\n  }\n\n  private _tick(ticker: Ticker): void {\n    if (this._destroyed || this._state === 'idle') return;\n    if (this._state === 'cascading') {\n      this._advanceCascade(ticker.deltaMS);\n      return;\n    }\n    if (this._state === 'landing') {\n      this._advanceLanding(ticker.deltaMS);\n      return;\n    }\n    this._advanceSpin(ticker.deltaTime);\n  }\n\n  private _advanceSpin(dt: number): void {\n    // Ease speed down as the stop queue drains, so it decelerates into the land.\n    const drain =\n      this._state === 'stopping' ? Math.max(0.28, this._queue.length / (this.visibleCount + 1)) : 1;\n    this._off += this._cfg.speed * dt * drain;\n    while (this._off >= this._span) {\n      this._off -= this._span;\n      this._doShift(this._nextFeed());\n      if (this._state === 'landing') break; // finalized inside _nextFeed\n    }\n    this._render();\n  }\n\n  private _nextFeed(): string {\n    if (this._state === 'stopping') {\n      const id = this._queue.shift() as string;\n      if (this._queue.length === 0) this._beginLanding();\n      return id;\n    }\n    return this._randomId();\n  }\n\n  /** Move the conveyor one cell in the travel direction, feeding `id` at the tail. */\n  private _doShift(id: string): void {\n    if (this._direction === 'rtl') {\n      const leaving = this._slots.shift() as ReelSymbol; // leftmost leaves\n      this._slots.push(this._repaint(leaving, id)); // enters at the right\n    } else {\n      const leaving = this._slots.pop() as ReelSymbol; // rightmost leaves\n      this._slots.unshift(this._repaint(leaving, id)); // enters at the left\n    }\n  }\n\n  private _repaint(outgoing: ReelSymbol, id: string): ReelSymbol {\n    this.container.removeChild(outgoing.view);\n    this._factory.release(outgoing);\n    const next = this._factory.acquire(id);\n    next.resize(this._cellW, this._cellH);\n    next.view.y = 0;\n    this.container.addChild(next.view);\n    // Pool recycling wiped the symbol's state; while free-spinning it must\n    // re-join the spin presentation (mirrors Reel._replaceSymbol). During\n    // 'stopping' the feeds ARE the result window — they enter live so the\n    // landing symbols are never blurred (see setResult).\n    if (this._state === 'spinning') {\n      next.onReelSpinStart(true);\n    }\n    return next;\n  }\n\n  /** Position every conveyor slot from its index + the current sub-cell offset. */\n  private _render(): void {\n    const sign = this._direction === 'rtl' ? -1 : 1;\n    for (let k = 0; k < this._M; k++) {\n      this._slots[k].view.x = (k - 1) * this._span + sign * this._off;\n    }\n  }\n\n  private _beginLanding(): void {\n    this._state = 'landing';\n    this._landFrom = this._off; // leftover sub-cell offset after the final shift\n    this._landT = 0;\n  }\n\n  private _advanceLanding(deltaMS: number): void {\n    this._landT += deltaMS;\n    const t = Math.min(1, this._landT / 120);\n    this._off = this._landFrom * (1 - t);\n    this._render();\n    if (t >= 1) {\n      this._off = 0;\n      this._render();\n      this._land();\n    }\n  }\n\n  private _land(): void {\n    this._state = 'idle';\n    // Spin-end already fired at setResult (the strip un-blurs when the\n    // deceleration starts); here only the landing hook runs, matching the\n    // main reels' notifyLanded at the moment motion stops.\n    for (const slot of this._slots) slot.onReelLanded();\n    // Single-reel SpinResult: a one-column grid, same shape as ReelSet's.\n    const result: SpinResult = {\n      symbols: [this._windowIds()],\n      wasSkipped: this._wasSkipped,\n      duration: performance.now() - this._spinStart,\n    };\n    const resolve = this._resolve;\n    this._resolve = null;\n    this.events.emit('spin:complete', result);\n    resolve?.(result);\n  }\n\n  private _advanceCascade(deltaMS: number): void {\n    this._cascadeT += deltaMS;\n    const fall = this._cfg.cascade.fall;\n    const drop = this._cfg.cascade.drop;\n    // Phase 1: winners implode — a brief pop, then collapse to nothing, scaled\n    // about the CELL CENTRE (position-compensated so the symbol shrinks in place\n    // instead of into its top-left corner) with a fade. Mirrors the feel of the\n    // engine's ReelSymbol.playDestroy.\n    const rt = Math.min(1, this._cascadeT / fall);\n    const pop = 0.18;\n    const s = rt < pop ? 1 + 0.12 * (rt / pop) : 1.12 * (1 - ((rt - pop) / (1 - pop)) ** 2);\n    const a = rt < pop ? 1 : 1 - (rt - pop) / (1 - pop);\n    for (const rem of this._cascadeRemoving) {\n      const view = rem.inst.view;\n      view.scale.set(Math.max(s, 0));\n      view.alpha = Math.max(a, 0);\n      view.x = rem.baseX + (this._cellW / 2) * (1 - s);\n      view.y = rem.baseY + (this._cellH / 2) * (1 - s);\n    }\n    // Phase 2: survivors collapse into the gaps and new symbols slide in from\n    // the feed edge, both easing to their resting slots.\n    const mt = this._cascadeT <= fall ? 0 : Math.min(1, (this._cascadeT - fall) / drop);\n    const eased = 1 - (1 - mt) * (1 - mt);\n    for (const mv of this._cascadeMoving) {\n      mv.inst.view.x = mv.fromX + (mv.toX - mv.fromX) * eased;\n    }\n    if (this._cascadeT >= fall + drop) this._finishCascade();\n  }\n\n  private _finishCascade(): void {\n    // Winners are gone (release resets their view scale/alpha for pool reuse).\n    for (const rem of this._cascadeRemoving) {\n      this.container.removeChild(rem.inst.view);\n      this._factory.release(rem.inst);\n    }\n    // Snap the collapsed + refilled window to rest and rebuild the conveyor\n    // (buffers at either end are untouched).\n    this._cascadeFinalWindow.forEach((inst, j) => {\n      inst.view.x = j * this._span;\n      inst.view.y = 0;\n      inst.view.alpha = 1;\n      inst.view.scale.set(1);\n      this._slots[j + 1] = inst;\n    });\n    const winners = this._cascadeWinners;\n    this._cascadeRemoving = [];\n    this._cascadeMoving = [];\n    this._cascadeFinalWindow = [];\n    this._cascadeWinners = [];\n    this._state = 'idle';\n    const resolve = this._cascadeResolve;\n    this._cascadeResolve = null;\n    this.events.emit('cascade:complete', { winners, symbols: this._windowIds() });\n    resolve?.();\n  }\n\n  private _windowIds(): string[] {\n    return this._slots.slice(1, 1 + this.visibleCount).map((s) => s.symbolId);\n  }\n\n  /** Motion sign of the spin: `-1` (rtl, symbols feed from the right) or `+1` (ltr). */\n  private _feedSign(): number {\n    return this._direction === 'rtl' ? -1 : 1;\n  }\n\n  private _randomId(): string {\n    return this._registeredIds[Math.floor(this._rng() * this._registeredIds.length)];\n  }\n\n  private _assertResultShape(ids: string[]): void {\n    if (ids.length !== this.visibleCount) {\n      throw new Error(\n        `HorizontalReel: setResult needs exactly ${this.visibleCount} ids (got ${ids.length}).`,\n      );\n    }\n    for (const id of ids) this._assertRegistered(id);\n  }\n\n  private _assertRegistered(id: string): void {\n    if (!this._registeredIds.includes(id)) {\n      throw new Error(`HorizontalReel: id '${id}' is not registered by .symbols(...).`);\n    }\n  }\n}\n","import type { Graphics, Ticker } from 'pixi.js';\nimport { SymbolRegistry } from '../symbols/SymbolRegistry.js';\nimport type { ColumnTarget } from '../frame/ColumnTarget.js';\nimport { HorizontalReel } from './HorizontalReel.js';\nimport type {\n  HorizontalCascadeTiming,\n  HorizontalDirection,\n} from './HorizontalReelTypes.js';\n\n/**\n * Fluent builder for {@link HorizontalReel} — the sideways \"these symbols pay\n * this round\" banner reel above the reels.\n *\n * Its API mirrors {@link ReelSetBuilder}: register `.symbols(...)`, give it a\n * `.ticker(...)`, and `.build()`. The reel then follows the engine's spin\n * contract (`spin()` then `setResult(ids)`) plus a `cascade(...)` tumble. Only\n * `.symbols(...)` and `.ticker(...)` are required; everything else defaults\n * (a 4-wide, 72px, right-to-left reel — the `1×4` shape).\n */\nexport class HorizontalReelBuilder {\n  private _visibleCount = 4;\n  private _cellW = 72;\n  private _cellH = 72;\n  private _gap = 4;\n  private _direction: HorizontalDirection = 'rtl';\n  private _speed = 22;\n  private _cascade: Required<HorizontalCascadeTiming> = { fall: 240, drop: 260 };\n  private _initialFrame: ColumnTarget[] | null = null;\n  private _configurator: ((registry: SymbolRegistry) => void) | null = null;\n  private _chrome: ((g: Graphics, width: number, height: number) => void) | null = null;\n  private _ticker: Ticker | null = null;\n  private _rng: (() => number) | null = null;\n\n  /** How many symbol cells are visible at once. Default 4 (the `1×4` strip). */\n  visibleCount(n: number): this {\n    if (n < 1) throw new Error('HorizontalReelBuilder: visibleCount must be >= 1.');\n    this._visibleCount = n;\n    return this;\n  }\n\n  /** Cell dimensions. `height` defaults to `width` (square); `gap` defaults to 4. */\n  cellSize(width: number, height: number = width, opts: { gap?: number } = {}): this {\n    this._cellW = width;\n    this._cellH = height;\n    this._gap = opts.gap ?? this._gap;\n    return this;\n  }\n\n  /** Travel direction while spinning. `rtl` scrolls leftward (default), `ltr` rightward. */\n  direction(direction: HorizontalDirection): this {\n    this._direction = direction;\n    return this;\n  }\n\n  /** Spin speed in pixels per frame. Default 22. */\n  spinSpeed(pxPerFrame: number): this {\n    this._speed = pxPerFrame;\n    return this;\n  }\n\n  /** Drop/fall timing for `cascade(...)`. */\n  cascadeTiming(opts: HorizontalCascadeTiming): this {\n    this._cascade = {\n      fall: opts.fall ?? this._cascade.fall,\n      drop: opts.drop ?? this._cascade.drop,\n    };\n    return this;\n  }\n\n  /**\n   * Register the symbol classes shown on the reel, exactly like\n   * `ReelSetBuilder.symbols`. The spin blur feeds from these ids; every id\n   * passed to `setResult(...)` / `cascade(...)` must be registered here.\n   * Required.\n   */\n  symbols(configurator: (registry: SymbolRegistry) => void): this {\n    this._configurator = configurator;\n    return this;\n  }\n\n  /**\n   * Rest frame shown before the first spin — the same `ColumnTarget[]` as\n   * `ReelSetBuilder.initialFrame`. This reel is one column, so pass exactly one\n   * entry whose `visible` holds `visibleCount` ids. Defaults to the first\n   * `visibleCount` registered ids.\n   */\n  initialFrame(frame: ColumnTarget[]): this {\n    this._initialFrame = frame;\n    return this;\n  }\n\n  /** Optional backing drawn behind the reel, sized to the visible window. */\n  chrome(draw: (g: Graphics, width: number, height: number) => void): this {\n    this._chrome = draw;\n    return this;\n  }\n\n  /** Injected RNG for the spin blur (deterministic demos / tests). */\n  rng(fn: () => number): this {\n    this._rng = fn;\n    return this;\n  }\n\n  /** Drives the spin — required. */\n  ticker(ticker: Ticker): this {\n    this._ticker = ticker;\n    return this;\n  }\n\n  build(): HorizontalReel {\n    if (!this._configurator) {\n      throw new Error('HorizontalReelBuilder: .symbols(...) is required.');\n    }\n    if (!this._ticker) {\n      throw new Error('HorizontalReelBuilder: .ticker(...) is required.');\n    }\n    // Default the rest frame to the first registered ids.\n    let initialFrame = this._initialFrame;\n    if (!initialFrame) {\n      const reg = new SymbolRegistry();\n      this._configurator(reg);\n      const ids = reg.symbolIds;\n      const visible = Array.from({ length: this._visibleCount }, (_, i) => ids[i % ids.length]);\n      initialFrame = [{ visible }];\n    }\n    return new HorizontalReel({\n      visibleCount: this._visibleCount,\n      cellWidth: this._cellW,\n      cellHeight: this._cellH,\n      gap: this._gap,\n      direction: this._direction,\n      speed: this._speed,\n      cascade: this._cascade,\n      initialFrame,\n      configurator: this._configurator,\n      chrome: this._chrome,\n      ticker: this._ticker,\n      rng: this._rng,\n    });\n  }\n}\n","import type { SymbolPosition, Win } from '../config/types.js';\n\nexport type { Win, SymbolPosition };\n\n/** Return a new array sorted by `value` descending (non-mutating). Missing values sort as 0. */\nexport function sortByValueDesc<T extends { value?: number }>(wins: readonly T[]): T[] {\n  return [...wins].sort((a, b) => (b.value ?? 0) - (a.value ?? 0));\n}\n","import type { Win } from '../config/types.js';\nimport type { ReelSet } from '../core/ReelSet.js';\nimport type { SymbolPosition } from '../events/ReelEvents.js';\nimport type { ReelSymbol } from '../symbols/ReelSymbol.js';\nimport type { Disposable } from '../utils/Disposable.js';\nimport { sortByValueDesc } from './Win.js';\n\n/**\n * What to play on each cell being highlighted.\n *\n *   - `'win'`. default. Calls `symbol.playWin()` (your subclass's hook).\n *   - `string`. a named animation. If the symbol exposes a\n *     `playAnimation(name)` method (e.g. SpineSymbol), it's invoked; else\n *     it falls back to `playWin()`.\n *   - `(symbol, cell, win) => Promise<void>`. drive the animation\n *     yourself. Good for GSAP bounces, line-specific pulses, etc.\n */\nexport type WinSymbolAnim =\n  | 'win'\n  | string\n  | ((symbol: ReelSymbol, cell: SymbolPosition, win: Win) => Promise<void>);\n\nexport interface WinPresenterOptions {\n  /**\n   * Fade non-winning symbols to this alpha while a win is active.\n   *  - `true` (default) → alpha 0.35\n   *  - number → that alpha\n   *  - `false` → don't touch non-winners\n   *\n   * Restored on `win:end`.\n   */\n  dimLosers?: boolean | { alpha?: number };\n  /** See {@link WinSymbolAnim}. Default `'win'`. */\n  symbolAnim?: WinSymbolAnim;\n  /**\n   * Delay between cells *within* a single win (ms).\n   * - `0` (default) → all cells animate simultaneously\n   * - `> 0` → cells start one after another in array order (e.g. a\n   *   left-to-right sweep across a payline's cells)\n   */\n  stagger?: number;\n  /** Delay between successive wins in the sequence (ms). Default 400. */\n  cycleGap?: number;\n  /** Number of full cycles through the wins list. `-1` for infinite. Default 1. */\n  cycles?: number;\n  /** Sort wins by `value` descending before cycling. Default true. */\n  sortByValue?: boolean;\n}\n\ninterface ResolvedOptions {\n  dimAlpha: number | null;\n  symbolAnim: WinSymbolAnim;\n  stagger: number;\n  cycleGap: number;\n  cycles: number;\n  sortByValue: boolean;\n}\n\n/**\n * Highlights winning cells on a reel set. One job: animate the symbols.\n *\n * The presenter doesn't draw lines, outlines, or any per-win visual. it\n * emits `win:start` / `win:group` / `win:symbol` / `win:end` events so\n * your code can hook anything it wants (polylines, Spine line rigs,\n * popup numbers, sound cues) by subscribing and using\n * `reelSet.getCellBounds(col, row)` to place graphics.\n *\n * Two knobs cover the common presentation modes:\n *\n *   - `stagger: 0` → all cells in a win pulse together\n *   - `stagger: 60` → cells start one after another. a left-to-right\n *     sweep if you pass cells in reel order\n *\n * ```ts\n * const presenter = new WinPresenter(reelSet, { stagger: 80 });\n *\n * reelSet.events.on('spin:complete', async () => {\n *   const wins = await server.wins(result);  // your wins, your shape\n *   await presenter.show(wins);\n * });\n * reelSet.events.on('spin:start', () => presenter.abort());\n * ```\n *\n * Cascades: drive `presenter.show([{ cells: winners }])` from\n * `runCascade`'s `onWinnersVanish` hook. cluster pops and payline hits\n * are the same shape to the presenter.\n */\nexport class WinPresenter implements Disposable {\n  private _reelSet: ReelSet;\n  private _options: ResolvedOptions;\n  private _abort: AbortController | null = null;\n  private _isActive = false;\n  private _isDestroyed = false;\n\n  constructor(reelSet: ReelSet, options: WinPresenterOptions = {}) {\n    this._reelSet = reelSet;\n    this._options = WinPresenter._resolve(options);\n  }\n\n  get isActive(): boolean {\n    return this._isActive;\n  }\n\n  get isDestroyed(): boolean {\n    return this._isDestroyed;\n  }\n\n  /**\n   * Present the given wins. Cancels any in-flight sequence first.\n   * Resolves when all cycles complete or when `abort()` is called.\n   *\n   * Empty input resolves immediately without firing any events.\n   */\n  async show(wins: readonly Win[]): Promise<void> {\n    this.abort();\n    if (this._isDestroyed) return;\n    if (wins.length === 0) return;\n\n    const ordered = this._options.sortByValue\n      ? sortByValueDesc(wins)\n      : [...wins];\n\n    const abort = new AbortController();\n    this._abort = abort;\n    this._isActive = true;\n    this._reelSet.events.emit('win:start', ordered);\n\n    let loop = 0;\n    try {\n      while (this._options.cycles === -1 || loop < this._options.cycles) {\n        for (const win of ordered) {\n          if (abort.signal.aborted) return;\n          await this._showOne(win, abort.signal);\n          if (abort.signal.aborted) return;\n          await this._wait(this._options.cycleGap, abort.signal);\n        }\n        loop++;\n      }\n    } finally {\n      this._restoreAlpha();\n      const wasAborted = abort.signal.aborted;\n      if (this._abort === abort) this._abort = null;\n      this._isActive = false;\n      this._reelSet.events.emit('win:end', wasAborted ? 'aborted' : 'complete');\n    }\n  }\n\n  /** Abort any in-flight `show()`. */\n  abort(): void {\n    if (this._abort) this._abort.abort();\n  }\n\n  destroy(): void {\n    if (this._isDestroyed) return;\n    this._isDestroyed = true;\n    this.abort();\n  }\n\n  private async _showOne(win: Win, signal: AbortSignal): Promise<void> {\n    const cells = [...win.cells];\n    if (cells.length === 0) return;\n\n    // Apply dim before firing win:group so listeners observe the live\n    // visual state (e.g. a UI snapshot sees losers already faded).\n    this._applyDim(cells);\n    this._reelSet.events.emit('win:group', win, cells);\n\n    const stagger = this._options.stagger;\n    const animPromises: Promise<void>[] = [];\n\n    for (let i = 0; i < cells.length; i++) {\n      if (signal.aborted) break;\n      if (i > 0 && stagger > 0) await this._wait(stagger, signal);\n      if (signal.aborted) break;\n      const cell = cells[i];\n      const reel = this._reelSet.getReel(cell.reelIndex);\n      if (!reel) continue;\n      const symbol = reel.getSymbolAt(cell.rowIndex);\n      if (!symbol) continue;\n      this._reelSet.events.emit('win:symbol', symbol, cell, win);\n      animPromises.push(this._playAnim(symbol, cell, win));\n    }\n\n    await Promise.all(animPromises);\n  }\n\n  private async _playAnim(\n    symbol: ReelSymbol,\n    cell: SymbolPosition,\n    win: Win,\n  ): Promise<void> {\n    const anim = this._options.symbolAnim;\n    if (typeof anim === 'function') return anim(symbol, cell, win);\n    if (anim === 'win') return symbol.playWin();\n    const withPlay = symbol as unknown as { playAnimation?: (name: string) => Promise<void> };\n    if (typeof withPlay.playAnimation === 'function') return withPlay.playAnimation(anim);\n    return symbol.playWin();\n  }\n\n  private _applyDim(winCells: readonly SymbolPosition[]): void {\n    const alpha = this._options.dimAlpha;\n    if (alpha === null) return;\n\n    const winKeys = new Set<string>();\n    for (const c of winCells) winKeys.add(`${c.reelIndex}:${c.rowIndex}`);\n\n    const reels = this._reelSet.reels;\n    for (let r = 0; r < reels.length; r++) {\n      const reel = reels[r];\n      for (let row = 0; row < reel.visibleRows; row++) {\n        const view = reel.getSymbolAt(row).view;\n        view.alpha = winKeys.has(`${r}:${row}`) ? 1 : alpha;\n      }\n    }\n  }\n\n  private _restoreAlpha(): void {\n    const reels = this._reelSet.reels;\n    for (const reel of reels) {\n      for (let row = 0; row < reel.visibleRows; row++) {\n        reel.getSymbolAt(row).view.alpha = 1;\n      }\n    }\n  }\n\n  private _wait(ms: number, signal: AbortSignal): Promise<void> {\n    return new Promise((resolve) => {\n      if (signal.aborted) return resolve();\n      const t = setTimeout(resolve, ms);\n      signal.addEventListener(\n        'abort',\n        () => {\n          clearTimeout(t);\n          resolve();\n        },\n        { once: true },\n      );\n    });\n  }\n\n  private static _resolve(opts: WinPresenterOptions): ResolvedOptions {\n    let dimAlpha: number | null;\n    if (opts.dimLosers === false) {\n      dimAlpha = null;\n    } else if (typeof opts.dimLosers === 'object' && opts.dimLosers !== null) {\n      dimAlpha = opts.dimLosers.alpha ?? 0.35;\n    } else {\n      dimAlpha = 0.35;\n    }\n\n    return {\n      dimAlpha,\n      symbolAnim: opts.symbolAnim ?? 'win',\n      stagger: Math.max(0, opts.stagger ?? 0),\n      cycleGap: opts.cycleGap ?? 400,\n      cycles: opts.cycles ?? 1,\n      sortByValue: opts.sortByValue ?? true,\n    };\n  }\n}\n","import type { Ticker } from 'pixi.js';\nimport { getGsap } from './gsapRef.js';\n\n/**\n * Drive GSAP from a PixiJS ticker instead of its own requestAnimationFrame\n * loop, and return a disposer that restores GSAP's default loop.\n *\n * **Why this matters:** GSAP's default ticker runs on `requestAnimationFrame`,\n * which browsers throttle — and often freeze entirely — in hidden tabs and many\n * iframes. Casino lobbies and iframed clients are routinely backgrounded. When\n * that happens GSAP stalls while the PixiJS ticker keeps running, so every\n * engine animation (spin easing, the landing bounce, spotlight pulses) freezes\n * mid-flight. Pinning GSAP to the same ticker keeps animation and rendering in\n * lockstep everywhere, foreground or background.\n *\n * This is the one line every integration has to remember\n * (`gsap.ticker.remove(gsap.updateRoot); ticker.add(...)`); calling this\n * function instead removes that footgun. The bound GSAP instance is the one the\n * engine actually uses (see {@link ReelSetBuilder.gsap}), so it stays correct\n * even under the dual-instance module-resolution trap.\n *\n * Call it **once per app**, after creating the PixiJS `Application` and before\n * the first spin. Do NOT also detach `gsap.updateRoot` yourself — driving GSAP\n * from two sources advances it twice per frame (animations run at double speed).\n *\n * @param ticker - The PixiJS ticker to drive GSAP from (usually `app.ticker`).\n * @returns A disposer that detaches the driver and restores GSAP's own ticker.\n *\n * @example\n * import { Application } from 'pixi.js';\n * import { driveGsapWithTicker } from 'pixi-reels';\n *\n * const app = new Application();\n * await app.init({ ... });\n * const stopGsapSync = driveGsapWithTicker(app.ticker);\n * // ...on teardown:\n * stopGsapSync();\n */\nexport function driveGsapWithTicker(ticker: Ticker): () => void {\n  const gsap = getGsap();\n  gsap.ticker.remove(gsap.updateRoot);\n  const driver = (): void => {\n    gsap.updateRoot(ticker.lastTime / 1000);\n  };\n  ticker.add(driver);\n\n  let disposed = false;\n  return () => {\n    if (disposed) return;\n    disposed = true;\n    ticker.remove(driver);\n    gsap.ticker.add(gsap.updateRoot);\n  };\n}\n"],"mappings":"mNAgBA,IAAa,EAAb,cAAkC,EAAA,CAAW,CAC3C,QACA,UACA,UAA4C,KAE5C,YAAY,EAA8B,CACxC,OAAO,CACP,KAAK,UAAY,EAAQ,SACzB,IAAM,EAAS,EAAQ,QAAU,CAAE,EAAG,EAAG,EAAG,EAAG,CAC/C,KAAK,QAAU,IAAI,EAAA,OACnB,KAAK,QAAQ,OAAO,IAAI,EAAO,EAAG,EAAO,EAAE,CAC3C,KAAK,KAAK,SAAS,KAAK,QAAQ,CAGlC,WAAqB,EAAwB,CAC3C,IAAM,EAAU,KAAK,UAAU,GAC3B,IACF,KAAK,QAAQ,QAAU,GAI3B,cAA+B,CAC7B,KAAK,eAAe,CACpB,KAAK,QAAQ,MAAM,IAAI,EAAG,EAAE,CAG9B,MAAM,SAAyB,CAE7B,OADA,KAAK,eAAe,CACb,IAAI,QAAe,GAAY,CACpC,KAAK,UAAY,EAAA,GAAS,CAAC,GAAG,KAAK,QAAQ,MAAO,CAChD,EAAG,KACH,EAAG,KACH,SAAU,IACV,KAAM,GACN,OAAQ,EACR,KAAM,eACN,WAAY,EACb,CAAC,EACF,CAGJ,eAAsB,CACpB,KAAK,eAAe,CACpB,KAAK,QAAQ,MAAM,IAAI,EAAG,EAAE,CAG9B,OAAO,EAAe,EAAsB,CAC1C,KAAK,QAAQ,MAAQ,EACrB,KAAK,QAAQ,OAAS,EAGxB,WAAqC,CACnC,KAAK,eAAe,CAGtB,eAA8B,CAC5B,AAEE,KAAK,aADL,KAAK,UAAU,MAAM,CACJ,QC1DV,EAAb,cAA0C,EAAA,CAAW,CACnD,YACA,QACA,gBACA,YAA2C,KAE3C,YAAY,EAAsC,CAChD,OAAO,CACP,KAAK,QAAU,EAAQ,OACvB,KAAK,gBAAkB,EAAQ,gBAAkB,EACjD,IAAM,EAAS,EAAQ,QAAU,CAAE,EAAG,EAAG,EAAG,EAAG,CAGzC,EAAc,OAAO,OAAO,KAAK,QAAQ,CAAC,IAAM,EAAE,CACxD,KAAK,YAAc,IAAI,EAAA,eAAe,EAAY,OAAS,EAAI,EAAc,EAAE,CAAC,CAChF,KAAK,YAAY,OAAO,IAAI,EAAO,EAAG,EAAO,EAAE,CAC/C,KAAK,YAAY,eAAiB,KAAK,gBACvC,KAAK,YAAY,KAAO,GACxB,KAAK,KAAK,SAAS,KAAK,YAAY,CAGtC,WAAqB,EAAwB,CAC3C,IAAM,EAAS,KAAK,QAAQ,GACxB,GAAU,EAAO,OAAS,IAC5B,KAAK,YAAY,SAAW,EAC5B,KAAK,YAAY,YAAY,EAAE,EAInC,cAA+B,CAC7B,KAAK,YAAY,MAAM,CACvB,KAAK,YAAc,KAGrB,MAAM,SAAyB,CAC7B,OAAO,IAAI,QAAe,GAAY,CACpC,KAAK,YAAc,EACnB,KAAK,YAAY,KAAO,GACxB,KAAK,YAAY,eAAmB,CAClC,KAAK,YAAc,KACnB,KAAK,YAAY,WAAa,IAAA,GAK9B,KAAK,YAAY,YAAY,EAAE,CAC/B,GAAS,EAEX,KAAK,YAAY,YAAY,EAAE,EAC/B,CAGJ,eAAsB,CACpB,KAAK,YAAY,MAAM,CACvB,KAAK,YAAY,YAAY,EAAE,CAC/B,AAEE,KAAK,eADL,KAAK,aAAa,CACC,MAIvB,OAAO,EAAe,EAAsB,CAC1C,KAAK,YAAY,MAAQ,EACzB,KAAK,YAAY,OAAS,EAK1B,KAAK,YAAY,EAAI,EAAQ,KAAK,YAAY,OAAO,EACrD,KAAK,YAAY,EAAI,EAAS,KAAK,YAAY,OAAO,IC3E7C,EAAb,cAAiC,EAAA,CAAW,CAC1C,WAAqB,EAAyB,EAC9C,cAA+B,EAC/B,MAAM,SAAyB,EAC/B,eAAsB,EACtB,OAAO,EAAgB,EAAuB,ICqEnC,EAAb,KAAoD,CAClD,UACA,YACA,cACA,QAAkB,IAAI,IACtB,SAAmB,IAAI,IACvB,aAAuB,GAEvB,YAAY,EAAkC,CAC5C,KAAK,UAAY,EAAQ,SACzB,KAAK,YAAc,EAAQ,WAC3B,KAAK,cAAgB,EAAQ,MAAQ,EAAE,CAMzC,UAAU,EAAkB,EAAwB,CAClD,KAAK,KAAK,KAAK,QAAS,EAAU,CAAE,UAAS,MAAO,GAAO,MAAO,EAAG,OAAQ,EAAG,CAAC,CAInF,WAAW,EAAkB,EAAwB,CACnD,KAAK,KAAK,KAAK,SAAU,EAAU,CAAE,UAAS,MAAO,GAAO,MAAO,EAAG,OAAQ,EAAG,CAAC,CAKpF,UAAU,EAAkC,CAC1C,OAAO,KAAK,QAAQ,IAAI,EAAS,EAAE,SAAW,KAGhD,WAAW,EAAkC,CAC3C,OAAO,KAAK,SAAS,IAAI,EAAS,EAAE,SAAW,KAGjD,UAAU,EAA2B,CACnC,OAAO,KAAK,QAAQ,IAAI,EAAS,CAGnC,WAAW,EAA2B,CACpC,OAAO,KAAK,SAAS,IAAI,EAAS,CAWpC,cAAc,EAAkB,EAAmB,EAAe,EAAyB,CACzF,IAAM,EAAW,KAAK,QAAQ,IAAI,EAAS,CAC3C,GAAI,IAAa,CAAC,EAAS,OAAU,EAAS,QAAU,GAAS,EAAS,SAAW,GACnF,OAAO,EAAS,QAElB,IAAM,EAAU,KAAK,UAAU,gBAAgB,CAC7C,OAAQ,EACR,MAAO,IAAI,EAAA,UAAU,EAAG,EAAG,EAAO,EAAO,CACzC,WAAY,KAAK,YACjB,UAAW,GACZ,CAAC,CAEF,OADA,KAAK,KAAK,KAAK,QAAS,EAAU,CAAE,UAAS,MAAO,GAAM,QAAO,SAAQ,CAAC,CACnE,EAYT,eACE,EACA,EACA,EACA,EACS,CACT,IAAM,EAAO,GAAM,MAAQ,KAAK,cAAc,MAAQ,IAChD,EAAW,KAAK,SAAS,IAAI,EAAS,CAC5C,GACE,IACC,CAAC,EAAS,OACR,EAAS,QAAU,GAAS,EAAS,SAAW,GAAU,EAAS,OAAS,GAE/E,OAAO,EAAS,QAElB,IAAM,EAAY,KAAK,UAAU,EAAS,CAC1C,GAAI,CAAC,EACH,MAAU,MACR,oCAAoC,EAAS,6FAE9C,CAGH,IAAM,EACJ,GAAM,UAAY,KAAK,cAAc,WAAa,IAAS,IAAM,EAAS,GAAS,GAC/E,EAAU,GAAM,SAAW,KAAK,cAAc,SAAW,EACzD,EAAU,GAAM,SAAW,KAAK,cAAc,SAAW,KAAK,KAAK,EAAS,CAE5E,EAAO,IAAI,EAAA,UACX,EAAS,IAAI,EAAA,OAAO,EAAU,CAGpC,EAAO,MAAQ,EACf,EAAO,OAAS,EACZ,IAAS,KACX,EAAO,EAAI,EACX,EAAO,QAAU,CAAC,IAAI,EAAA,WAAW,CAAE,UAAW,EAAG,UAAW,EAAU,UAAS,CAAC,CAAC,GAEjF,EAAO,EAAI,EACX,EAAO,QAAU,CAAC,IAAI,EAAA,WAAW,CAAE,UAAW,EAAU,UAAW,EAAG,UAAS,CAAC,CAAC,EAEnF,EAAK,SAAS,EAAO,CAErB,IAAM,EAAU,KAAK,UAAU,gBAAgB,CAC7C,OAAQ,EACR,MAAO,IAAI,EAAA,UACT,EACA,EACA,GAAS,IAAS,IAAM,EAAU,EAAI,GACtC,GAAU,IAAS,IAAM,EAAU,EAAI,GACxC,CACD,WAAY,KAAK,YACjB,UAAW,GACZ,CAAC,CAKF,OAHA,EAAK,QAAQ,CAAE,SAAU,GAAM,CAAC,CAEhC,KAAK,KAAK,KAAK,SAAU,EAAU,CAAE,UAAS,MAAO,GAAM,QAAO,SAAQ,OAAM,CAAC,CAC1E,EAMT,WAAW,EAAwB,CACjC,KAAK,MAAM,KAAK,QAAS,EAAS,CAClC,KAAK,MAAM,KAAK,SAAU,EAAS,CAIrC,OAAc,CACZ,IAAK,IAAM,IAAM,CAAC,GAAG,KAAK,QAAQ,MAAM,CAAC,CAAE,KAAK,MAAM,KAAK,QAAS,EAAG,CACvE,IAAK,IAAM,IAAM,CAAC,GAAG,KAAK,SAAS,MAAM,CAAC,CAAE,KAAK,MAAM,KAAK,SAAU,EAAG,CAG3E,SAAgB,CACV,AAEJ,KAAK,gBADL,KAAK,OAAO,CACQ,IAGtB,IAAI,aAAuB,CACzB,OAAO,KAAK,aAKd,KAAa,EAA8B,EAAY,EAAyB,CAC9E,KAAK,MAAM,EAAK,EAAG,CACnB,EAAI,IAAI,EAAI,EAAM,CAGpB,MAAc,EAA8B,EAAkB,CAC5D,IAAM,EAAW,EAAI,IAAI,EAAG,CACxB,GAAU,OAAO,EAAS,QAAQ,QAAQ,GAAK,CACnD,EAAI,OAAO,EAAG,GAwClB,SAAgB,EAAoB,EAA2C,CAC7E,GAAM,CAAE,QAAO,MAAK,QAAO,UAAW,EAChC,EAAU,EAAQ,SAAW,GAC7B,EAAS,EAAQ,cAAc,CACrC,GAAI,CACF,IAAK,IAAM,KAAM,EACX,EAAO,WAAa,GAAI,EAAO,SAAS,EAAG,CAC/C,EAAO,OAAO,EAAO,EAAO,CAC5B,EAAM,cAAc,EAAI,EAAO,KAAM,EAAO,EAAO,CAC/C,GAAS,EAAM,eAAe,EAAI,EAAO,EAAQ,EAAQ,KAAK,QAE5D,CACR,EAAO,SAAS,EC1OpB,IAAa,EAAb,cAAsC,EAAA,CAAW,CAC/C,OACA,OACA,MACA,QACA,UAEA,cACA,YACA,UAAoB,GACpB,cAAwB,GACxB,OAAiB,EACjB,OAAiB,EACjB,WAA6C,KAE7C,YAAY,EAAkC,CAC5C,OAAO,CACP,KAAK,OAAS,EAAQ,aAAa,CACnC,KAAK,OAAS,EAAQ,MACtB,KAAK,MAAQ,EAAQ,aAAe,UACpC,KAAK,QAAU,EAAQ,YAAc,IACrC,KAAK,UAAY,EAAQ,KAEzB,KAAK,KAAK,SAAS,KAAK,OAAO,KAAK,CACpC,KAAK,cAAgB,IAAI,EAAA,OACzB,KAAK,cAAc,OAAO,IAAI,GAAK,GAAI,CACvC,KAAK,cAAc,QAAU,GAC7B,KAAK,YAAc,IAAI,EAAA,OACvB,KAAK,YAAY,OAAO,IAAI,GAAK,GAAI,CACrC,KAAK,YAAY,QAAU,GAC3B,KAAK,KAAK,SAAS,KAAK,cAAe,KAAK,YAAY,CAI1D,IAAI,OAAoB,CACtB,OAAO,KAAK,OAId,IAAI,mBAA6B,CAC/B,OAAO,KAAK,UAGd,WAAqB,EAAwB,CAC3C,GAAI,KAAK,UAAW,CAIlB,KAAK,cAAc,EAAU,CAAE,QAAS,GAAM,CAAC,CAC/C,OAEE,KAAK,OAAO,WAAa,GAAU,KAAK,OAAO,SAAS,EAAS,CACrE,KAAK,eAAe,CAGtB,cAA+B,CAC7B,KAAK,WAAW,CAChB,KAAK,UAAY,GACjB,KAAK,cAAgB,GACrB,KAAK,cAAc,QAAU,GAC7B,KAAK,YAAY,QAAU,GACvB,KAAK,OAAO,WAAa,IAAI,KAAK,OAAO,YAAY,CAG3D,gBAAyB,EAAgB,GAAa,CACpD,GAAI,KAAK,UAAW,CAGlB,KAAK,cAAc,KAAK,SAAU,CAAE,QAAS,GAAM,CAAC,CACpD,OAEF,KAAK,UAAY,GACjB,KAAK,cAAgB,GAIrB,KAAK,cAAc,KAAK,SAAU,CAAE,QAAS,GAAiB,KAAK,SAAW,EAAG,CAAC,CAC9E,KAAK,OAAO,WAAa,IAAI,KAAK,OAAO,YAAY,CAS3D,yBAAyC,CACnC,MAAC,KAAK,WAAa,KAAK,iBAC5B,KAAK,cAAgB,GACjB,KAAK,QAAU,UAKnB,IAHA,KAAK,WAAW,CAChB,KAAK,cAAc,QAAU,GAC7B,KAAK,cAAc,MAAQ,EACvB,CAAC,KAAK,YAAY,SAAW,KAAK,SAAW,EAAG,CAClD,KAAK,YAAY,QAAU,GAC3B,OAEF,KAAK,WAAa,EAAA,GAAS,CAAC,GAAG,KAAK,YAAa,CAC/C,MAAO,EACP,SAAU,KAAK,QAAU,IACzB,KAAM,aACN,eAAkB,CAChB,KAAK,WAAa,KAClB,KAAK,YAAY,QAAU,IAE9B,CAAC,EAGJ,eAA+B,CACxB,KAAK,YACV,KAAK,UAAY,GACjB,KAAK,cAAgB,GACrB,KAAK,WAAW,CAChB,KAAK,cAAc,QAAU,GAC7B,KAAK,YAAY,QAAU,GACvB,KAAK,OAAO,WAAa,KAAK,WAChC,KAAK,OAAO,SAAS,KAAK,SAAS,CACnC,KAAK,OAAO,OAAO,KAAK,OAAQ,KAAK,OAAO,GAIhD,cAA8B,CACvB,KAAK,WAAW,KAAK,OAAO,cAAc,CAGjD,MAAM,SAAyB,CAC7B,OAAO,KAAK,OAAO,SAAS,CAG9B,eAAsB,CAChB,KAAK,OAAO,WAAa,IAAI,KAAK,OAAO,eAAe,CAG9D,MAAe,YAAY,EAAgE,CAKzF,MADI,CAAC,KAAK,WAAa,KAAK,OAAO,WAAa,GAAW,KAAK,OAAO,YAAY,EAAK,CACjF,MAAM,YAAY,EAAK,CAGhC,OAAO,EAAe,EAAsB,CAC1C,KAAK,OAAS,EACd,KAAK,OAAS,EACd,KAAK,cAAc,SAAS,IAAI,EAAQ,EAAG,EAAS,EAAE,CACtD,KAAK,YAAY,SAAS,IAAI,EAAQ,EAAG,EAAS,EAAE,CACpD,KAAK,aAAa,CAClB,KAAK,OAAO,OAAO,EAAO,EAAO,CAGnC,WAAqC,CACnC,KAAK,WAAW,CAGZ,KAAK,OAAO,KAAK,SAAW,KAAK,MAAM,KAAK,KAAK,YAAY,KAAK,OAAO,KAAK,CAClF,KAAK,OAAO,SAAS,CAUvB,cAAsB,EAAkB,EAAkC,CACxE,IAAM,EAAY,KAAK,cAAc,EAAS,CAa9C,GAZA,KAAK,cAAc,QAAU,EAEzB,KAAK,QAAU,YACjB,KAAK,YAAY,QAAU,KAAK,OAAO,eACrC,EACA,KAAK,OACL,KAAK,OACL,KAAK,UACN,EAEH,KAAK,aAAa,CAEd,KAAK,QAAU,SAAU,CAC3B,KAAK,cAAc,QAAU,GAC7B,KAAK,cAAc,MAAQ,EAC3B,OAGF,GAAI,EAAK,QAAS,CAGhB,GAAI,KAAK,cAAe,CACtB,KAAK,WAAW,CAChB,KAAK,YAAY,QAAU,GAC3B,KAAK,cAAc,QAAU,GAC7B,KAAK,cAAc,MAAQ,EAC3B,OAIF,GAAI,KAAK,WAAY,OACrB,KAAK,cAAc,QAAU,GAC7B,KAAK,YAAY,QAAU,GAC3B,KAAK,YAAY,MAAQ,EACzB,OAGF,KAAK,WAAW,CAChB,KAAK,cAAc,QAAU,GAC7B,KAAK,cAAc,MAAQ,EAC3B,KAAK,YAAY,QAAU,GAC3B,KAAK,YAAY,MAAQ,EACzB,KAAK,WAAa,EAAA,GAAS,CAAC,GAAG,KAAK,YAAa,CAC/C,MAAO,EACP,SAAU,KAAK,QAAU,IACzB,KAAM,YACN,eAAkB,CAChB,KAAK,WAAa,KAClB,KAAK,cAAc,QAAU,IAEhC,CAAC,CAUJ,cAAsB,EAAkB,CACtC,IAAM,EAAS,KAAK,OAAO,UAAU,EAAS,CAC9C,GAAI,EAAQ,OAAO,EAEnB,IAAM,EAAsB,KAAK,OAAO,WAAa,EACjD,IACF,KAAK,OAAO,SAAS,EAAS,CAC9B,KAAK,OAAO,OAAO,KAAK,OAAQ,KAAK,OAAO,EAE9C,IAAM,EAAM,KAAK,OAAO,cAAc,EAAU,KAAK,OAAO,KAAM,KAAK,OAAQ,KAAK,OAAO,CAE3F,OADI,GAAuB,KAAK,WAAW,KAAK,OAAO,YAAY,CAC5D,EAGT,eAA8B,CAC5B,KAAK,WAAW,CAChB,KAAK,cAAc,QAAU,GAC7B,KAAK,YAAY,QAAU,GAS7B,aAA4B,CAC1B,GAAI,KAAK,QAAU,GAAK,KAAK,QAAU,EAAG,OAC1C,IAAM,EAAc,KAAK,WAAW,OAAS,IAC7C,IAAK,IAAM,IAAU,CAAC,KAAK,cAAe,KAAK,YAAY,CAAE,CAC3D,IAAM,EAAK,EAAO,QAAQ,MACpB,EAAK,EAAO,QAAQ,OACtB,GAAM,GAAK,GAAM,GACrB,EAAO,MAAM,IAAI,EAAc,KAAK,OAAS,EAAK,KAAK,OAAS,EAAG,EAIvE,WAA0B,CACxB,AAEE,KAAK,cADL,KAAK,WAAW,MAAM,CACJ,QClSxB,SAAgB,EACd,EACA,EACU,CACV,IAAM,EAAU,EAAK,SAAW,EAC1B,EAAO,EAAK,MAAQ,gBAIpB,EAAe,EAAK,IACvB,GAAQ,EAAI,QAAQ,OAAQ,GAAO,IAAO,EAAK,OAAO,CAAC,OACzD,CAGG,EAAU,EACV,EAAc,GAClB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAa,OAAQ,IAEvC,GADA,GAAW,EAAa,GACpB,GAAW,EAAS,CACtB,EAAc,EACd,MAGJ,GAAI,IAAgB,GAAI,MAAO,EAAE,CAEjC,IAAM,EAAmB,EAAE,CAC3B,IAAK,IAAI,EAAI,EAAc,EAAG,EAAI,EAAK,OAAQ,KACzC,IAAS,iBAAmB,EAAa,GAAK,IAAG,EAAO,KAAK,EAAE,CAErE,OAAO,ECzET,IAAa,EAAb,KAAiD,CAC/C,KAAgB,UAEhB,SAKA,YAAY,EAAkB,IAAK,CACjC,KAAK,SAAW,EAGlB,cAAc,EAAsB,EAAe,EAAyB,CAC1E,IAAM,EAAO,EAAe,EAAQ,KAAK,SAAW,EAAW,IAC/D,OAAO,KAAK,IAAI,EAAK,EAAa,GCfzB,EAAb,KAAmD,CACjD,KAAgB,YAEhB,cAAc,EAAuB,EAAgB,EAA0B,CAE7E,MAAO,KC+CL,EAAO,GAAyB,GAAG,EAAE,IAAI,GAAG,EAAE,MAC9C,EAAkB,UA8BX,EAAb,KAA6C,CAC3C,UACA,KACA,KACA,SACA,IACA,QAEA,OAA0B,IAAI,IAC9B,OAAuC,EAAE,CACzC,WAAqB,GAErB,YAAY,EAAwB,CAClC,GAAI,CAAC,EAAK,OAAQ,MAAU,MAAM,mCAAmC,CACrE,KAAK,KAAO,EAAK,KACjB,KAAK,KAAO,EAAK,KACjB,KAAK,SAAW,EAAK,SACrB,KAAK,IAAM,EAAK,KAAO,EACvB,KAAK,QAAU,EAAK,SAAW,QAC/B,KAAK,UAAY,IAAI,EAAA,UAErB,IAAM,EACJ,EAAK,UAAY,OAAO,KAAK,EAAK,SAAS,CAAC,OAAS,EACjD,EAAK,SACL,EAAG,GAAkB,CAAE,GAAG,EAAA,EAAa,OAAQ,gBAAiB,IAAK,CAAE,CACvE,EAAe,OAAO,KAAK,EAAS,CACpC,GAAc,EAAiB,IACnC,OAAO,GAAM,WAAa,EAAE,EAAK,CAAG,EAEtC,IAAK,IAAI,EAAM,EAAG,EAAM,EAAK,KAAM,IACjC,IAAK,IAAI,EAAM,EAAG,EAAM,EAAK,KAAM,IAAO,CACxC,IAAM,EAAkB,CAAE,MAAK,MAAK,CAC9B,EAAS,KAAK,QAAQ,EAAK,CACjC,GAAI,EAAK,OAAQ,CACf,IAAM,EAAK,IAAI,EAAA,SACf,EAAK,OAAO,EAAI,KAAK,SAAS,CAC9B,EAAG,SAAS,IAAI,EAAO,EAAG,EAAO,EAAE,CACnC,KAAK,UAAU,SAAS,EAAG,CAG7B,IAAM,EAAU,IAAI,EAAA,GAAgB,CACjC,MAAM,EAAE,CACR,YAAY,EAAE,CACd,WAAW,KAAK,SAAU,KAAK,SAAS,CACxC,UAAU,EAAG,EAAE,CAGf,aAAa,IAAI,EAAA,EAAyB,CAC1C,QAAS,GAAa,CACrB,EAAK,QAAQ,EAAS,CACjB,EAAS,IAAI,KAAK,QAAQ,EAAE,EAAS,SAAS,KAAK,QAAS,EAAa,EAAE,CAAC,EACjF,CACD,aAAa,CACZ,CAAE,QAAS,CAAC,KAAK,QAAQ,CAAE,YAAa,CAAC,KAAK,QAAQ,CAAE,YAAa,CAAC,KAAK,QAAQ,CAAE,CACtF,CAAC,CACD,OAAO,EAAK,OAAO,CAGnB,aAAa,EAAa,GAAG,CAChC,IAAK,GAAM,CAAC,EAAM,KAAY,OAAO,QAAQ,EAAS,CACpD,EAAQ,MAAM,EAAM,EAAW,EAAS,EAAK,CAAC,CAE5C,EAAK,SAAS,EAAQ,QAAQ,EAAK,QAAQ,CAC3C,EAAK,YAAY,EAAQ,WAAW,EAAK,WAAW,CACpD,EAAK,KAAK,EAAQ,IAAI,EAAK,IAAI,CAEnC,IAAM,EAAU,EAAQ,OAAO,CAC/B,EAAQ,SAAS,IAAI,EAAO,EAAG,EAAO,EAAE,CACxC,KAAK,UAAU,SAAS,EAAQ,CAChC,KAAK,OAAO,IAAI,EAAI,EAAK,CAAE,EAAQ,CACnC,KAAK,OAAO,KAAK,EAAK,EAM5B,OAAqB,CACnB,OAAO,KAAK,OAAO,IAAK,IAAO,CAAE,IAAK,EAAE,IAAK,IAAK,EAAE,IAAK,EAAE,CAI7D,WAAW,EAA0E,CACnF,IAAM,EAAS,KAAK,QAAQ,EAAK,CACjC,MAAO,CAAE,EAAG,EAAO,EAAG,EAAG,EAAO,EAAG,MAAO,KAAK,SAAU,OAAQ,KAAK,SAAU,CAIlF,WAAW,EAA2C,CACpD,IAAM,EAAS,KAAK,QAAQ,EAAK,CACjC,MAAO,CAAE,EAAG,EAAO,EAAI,KAAK,SAAW,EAAG,EAAG,EAAO,EAAI,KAAK,SAAW,EAAG,CAI7E,SAAS,EAA6B,CACpC,OAAO,KAAK,MAAM,EAAK,CAAC,QAAQ,EAAE,CAAC,YAAY,EAAE,CAInD,OAAO,EAA0B,CAC/B,OAAO,KAAK,MAAM,EAAK,CAIzB,WAAW,EAAiB,EAAoB,CAC9C,KAAK,MAAM,EAAK,CAAC,MAAM,IAAI,EAAK,CAIlC,MAAM,EAAiB,EAAkB,CAGvC,IAAM,EAAyC,CAAC,EAAG,CACnD,EAAI,IAAM,KAAK,QACf,EAAI,GAAK,KAAK,QACd,KAAK,MAAM,EAAK,CAAC,QAAQ,EAAE,CAAC,aAAa,EAAI,CAc/C,MAAM,UACJ,EACA,MAAwE,GACzD,CACf,MAAM,QAAQ,IACZ,EAAQ,IAAI,MAAO,CAAE,OAAM,QAAS,CAClC,IAAM,EAAU,KAAK,MAAM,EAAK,CAC1B,EAAS,EAAQ,MAAM,CAE7B,EAAQ,UAAU,CAChB,CAAE,QAAS,CAAC,EAAG,CAAE,YAAa,CAAC,KAAK,QAAQ,CAAE,YAAa,CAAC,KAAK,QAAQ,CAAE,CAC5E,CAAC,CACF,MAAM,EACN,MAAM,EAAS,EAAM,EAAG,EACxB,CACH,CAIH,cAAuB,CACrB,IAAI,EAAW,EACf,IAAK,IAAM,KAAW,KAAK,OAAO,QAAQ,CACxC,GAAI,EAAQ,WAAY,CACtB,GAAY,EACZ,GAAI,CACF,EAAQ,UAAU,MACZ,GAKZ,OAAO,EAGT,IAAI,aAAuB,CACzB,OAAO,KAAK,WAGd,SAAgB,CACV,SAAK,WACT,MAAK,WAAa,GAClB,IAAK,IAAM,KAAW,KAAK,OAAO,QAAQ,CAAE,EAAQ,SAAS,CAC7D,KAAK,OAAO,OAAO,CACnB,KAAK,OAAO,OAAS,EACrB,KAAK,UAAU,QAAQ,CAAE,SAAU,GAAM,CAAC,EAG5C,QAAgB,EAA2C,CACzD,MAAO,CACL,EAAG,EAAK,KAAO,KAAK,SAAW,KAAK,KACpC,EAAG,EAAK,KAAO,KAAK,SAAW,KAAK,KACrC,CAGH,MAAc,EAA0B,CACtC,IAAM,EAAU,KAAK,OAAO,IAAI,EAAI,EAAK,CAAC,CAC1C,GAAI,CAAC,EACH,MAAU,MAAM,mBAAmB,EAAI,EAAK,CAAC,kBAAkB,KAAK,KAAK,GAAG,KAAK,KAAK,QAAQ,CAEhG,OAAO,IClME,EAAW,GAAsB,GAAG,EAAE,IAAI,GAAG,EAAE,MC3D/C,EAAb,KAA8C,CAC5C,QAA2B,IAAI,IAC/B,SACA,UACA,gBACA,aAAuB,EACvB,OAAiB,EACjB,OAA0B,OAC1B,YAAuC,EAAE,CAEzC,YAAY,EAAoB,EAAwB,CACtD,KAAK,UAAY,EACjB,KAAK,SAAW,IAAI,IAAI,EAAS,IAAI,EAAQ,CAAC,CAC9C,KAAK,gBAAkB,EAKzB,IAAI,OAAiB,CACnB,OAAO,KAAK,OAEd,IAAI,aAAsB,CACxB,OAAO,KAAK,aAEd,IAAI,OAAgB,CAClB,OAAO,KAAK,OAEd,IAAI,UAAmB,CACrB,OAAO,KAAK,UAAU,OAExB,IAAI,QAAkB,CACpB,OAAO,KAAK,QAAQ,OAAS,KAAK,SAGpC,aAA+B,CAC7B,MAAO,CAAC,GAAG,KAAK,QAAQ,QAAQ,CAAC,CAGnC,WAAsB,CACpB,OAAO,KAAK,UAAU,OAAQ,GAAM,CAAC,KAAK,QAAQ,IAAI,EAAQ,EAAE,CAAC,CAAC,CAGpE,SAAS,EAAuB,CAC9B,OAAO,KAAK,QAAQ,IAAI,EAAQ,EAAK,CAAC,CAGxC,OAAO,EAAyC,CAC9C,OAAO,KAAK,QAAQ,IAAI,EAAQ,EAAK,CAAC,CAMxC,MAAM,EAA0C,CAC9C,GAAI,KAAK,SAAW,OAClB,MAAU,MAAM,2EAA2E,CAE7F,IAAM,EAA0B,EAAE,CAC5B,EAAO,IAAI,IACjB,IAAK,IAAM,KAAQ,EAAM,CACvB,IAAM,EAAI,EAAQ,EAAK,KAAK,CAE5B,GADA,KAAK,cAAc,EAAK,KAAM,QAAQ,CAClC,EAAK,IAAI,EAAE,CAAE,MAAU,MAAM,uCAAuC,EAAE,SAAS,CACnF,EAAK,IAAI,EAAE,CACX,IAAM,EAAS,KAAK,QAAQ,EAAK,KAAM,EAAK,GAAI,EAAK,KAAK,CAC1D,KAAK,QAAQ,IAAI,EAAG,EAAO,CAC3B,EAAO,KAAK,EAAO,CAIrB,MAFA,MAAK,OAAS,EACd,KAAK,OAAS,SACP,CACL,KAAK,YAAY,KAAK,gBAAiB,OAAO,CAC9C,CAAE,KAAM,gBAAiB,QAAS,CAAE,KAAM,EAAQ,QAAS,KAAK,aAAc,CAAE,CACjF,CAQH,UAAU,EAIR,CACA,GAAI,KAAK,SAAW,OAClB,MAAU,MAAM,4CAA4C,CAE9D,GAAI,KAAK,SAAW,WAClB,MAAU,MAAM,uDAAuD,CAEzE,IAAM,EAAW,IAAI,IACrB,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAI,EAAQ,EAAI,KAAK,CAE3B,GADA,KAAK,cAAc,EAAI,KAAM,SAAS,CAClC,KAAK,QAAQ,IAAI,EAAE,CAAE,MAAU,MAAM,4CAA4C,EAAE,GAAG,CAG1F,GAAI,EAAS,IAAI,EAAE,CAAE,MAAU,MAAM,0CAA0C,EAAE,SAAS,CAC1F,EAAS,IAAI,EAAG,EAAI,CAKtB,MAHA,MAAK,YAAc,EAAE,CACrB,KAAK,OAAS,WACd,KAAK,QAAU,EACR,CAAE,MAAO,KAAK,OAAQ,SAAU,KAAK,WAAW,CAAE,WAAU,CAIrE,KAAK,EAAc,EAA+C,CAIhE,GAAI,KAAK,SAAW,WAAY,MAAO,EAAE,CACzC,GAAI,CAAC,EACH,MAAO,CAAC,CAAE,KAAM,cAAe,QAAS,CAAE,OAAM,KAAM,KAAM,CAAE,CAAC,CAEjE,IAAM,EAAS,KAAK,QAAQ,EAAM,EAAK,GAAI,EAAK,KAAK,CAGrD,OAFA,KAAK,QAAQ,IAAI,EAAQ,EAAK,CAAE,EAAO,CACvC,KAAK,YAAY,KAAK,EAAO,CACtB,CACL,CAAE,KAAM,cAAe,QAAS,CAAE,OAAM,KAAM,EAAQ,CAAE,CACxD,CACE,KAAM,cACN,QAAS,CAAE,KAAM,EAAQ,OAAQ,KAAK,QAAQ,KAAM,SAAU,KAAK,SAAU,CAC9E,CACF,CAIH,SAAmE,CAIjE,GAAI,KAAK,SAAW,WAAY,MAAO,CAAE,QAAS,EAAE,CAAE,OAAQ,EAAE,CAAE,CAClE,IAAM,EAAS,KAAK,YACd,EAA6B,EAAE,CACrC,EAAQ,KACN,EAAO,OAAS,EACZ,KAAK,YAAY,KAAK,gBAAiB,YAAY,CACnD,KAAK,YAAY,KAAK,aAAe,EAAG,OAAO,CACpD,CACD,KAAK,OAAS,SACd,EAAQ,KAAK,CACX,KAAM,aACN,QAAS,CAAE,MAAO,KAAK,OAAQ,KAAM,CAAC,GAAG,EAAO,CAAE,YAAa,KAAK,aAAc,CACnF,CAAC,CACF,IAAM,EAAO,KAAK,OAalB,OAZI,GAAM,EAAQ,KAAK,CAAE,KAAM,aAAc,QAAS,CAAE,MAAO,KAAK,aAAa,CAAE,CAAE,CAAC,EACzE,GAAQ,KAAK,cAAgB,KAExC,KAAK,OAAS,OACd,EAAQ,KAAK,CACX,KAAM,cACN,QAAS,CAAE,MAAO,KAAK,aAAa,CAAE,OAAQ,KAAK,OAAQ,OAAM,CAClE,CAAC,EAKG,CAAE,UAAS,OAAQ,CAAC,GAAG,EAAO,CAAE,CASzC,WAAkB,CACZ,KAAK,SAAW,aACpB,KAAK,OAAS,SACd,KAAK,YAAc,EAAE,EAIvB,QAAQ,EAA4E,CAClF,GAAI,KAAK,SAAW,WAClB,MAAU,MAAM,+EAA+E,CAEjG,IAAM,EAA6B,EAAE,CAC/B,EAA4B,EAAE,CACpC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAI,EAAQ,EAAK,CACjB,EAAO,KAAK,QAAQ,IAAI,EAAE,CAC3B,IACL,KAAK,QAAQ,OAAO,EAAE,CACtB,EAAS,KAAK,EAAK,CACnB,EAAQ,KAAK,CAAE,KAAM,gBAAiB,QAAS,CAAE,OAAM,UAAW,KAAK,QAAQ,KAAM,CAAE,CAAC,EAE1F,MAAO,CAAE,UAAS,WAAU,CAS9B,KAAK,EAAc,EAAY,EAA+B,CAC5D,GAAI,KAAK,SAAW,WAClB,MAAU,MAAM,mFAAmF,CAErG,IAAM,EAAI,EAAQ,EAAK,CACjB,EAAO,KAAK,QAAQ,IAAI,EAAE,CAChC,GAAI,CAAC,EACH,MAAU,MACR,gCAAgC,EAAE,yEACnC,CAEH,KAAK,QAAQ,IAAI,EAAG,KAAK,QAAQ,EAAM,EAAI,GAAQ,EAAK,KAAK,CAAC,CAIhE,OAA2B,CACzB,IAAM,EAAe,KAAK,QAAQ,KAMlC,OALA,KAAK,QAAQ,OAAO,CACpB,KAAK,YAAc,EAAE,CACrB,KAAK,OAAS,EACd,KAAK,aAAe,EACpB,KAAK,OAAS,OACP,CAAC,CAAE,KAAM,gBAAiB,QAAS,CAAE,eAAc,CAAE,CAAC,CAK/D,YAAoB,EAAe,EAAyC,CAE1E,MADA,MAAK,aAAe,KAAK,IAAI,EAAG,EAAM,CAC/B,CAAE,KAAM,kBAAmB,QAAS,CAAE,MAAO,KAAK,aAAc,SAAQ,CAAE,CAQnF,QAAgB,EAAc,EAAY,EAAwC,CAChF,MAAO,CAAE,KAAM,OAAO,OAAO,CAAE,IAAK,EAAK,IAAK,IAAK,EAAK,IAAK,CAAC,CAAE,KAAI,OAAM,CAG5E,cAAsB,EAAc,EAAkB,CACpD,GAAI,CAAC,KAAK,SAAS,IAAI,EAAQ,EAAK,CAAC,CACnC,MAAU,MAAM,oBAAoB,EAAG,kBAAkB,EAAQ,EAAK,CAAC,oBAAoB,GChO3F,EAAmB,KAoCZ,EAAb,KAAoE,CAClE,OAAkB,IAAI,EAAA,EACtB,KACA,KAEA,MACA,OACA,SACA,gBAEA,YAAY,EAAmC,CAC7C,KAAK,KAAO,EAAI,KAChB,KAAK,KAAO,EAAI,KAChB,KAAK,SAAW,EAAI,QACpB,KAAK,gBAAkB,EAAI,eAE3B,IAAM,EAAQ,IACX,EAAI,YAAY,iBAAmB,KAAO,EAAI,QAAQ,EAAK,IAAK,EAAK,IAAI,CAC5E,KAAK,MAAQ,IAAI,EAAU,CACzB,KAAM,EAAI,KACV,KAAM,EAAI,KACV,SAAU,EAAI,KACd,IAAK,EAAI,IACT,QAAS,EAAI,QACb,QAAS,EAAI,aACb,QAAS,EAAI,SAAW,IAAA,GACxB,WAAY,EAAI,YAAc,IAAA,GAC9B,OAAQ,EAAI,QAAU,IAAA,GACtB,OAAQ,EAAI,OACZ,IAAK,EAAI,KAAO,IAAA,GAChB,SAAU,CACR,OAAS,IAAU,CAAE,GAAG,EAAI,YAAa,gBAAiB,EAAK,EAAK,CAAE,EACtE,QAAU,IAAU,CAAE,GAAG,EAAI,YAAa,gBAAiB,EAAK,EAAK,CAAG,EAAkB,EAC3F,CACF,CAAC,CACF,KAAK,OAAS,IAAI,EAAuB,KAAK,MAAM,OAAO,CAAE,EAAI,QAAQ,CAK3E,IAAI,WAAuB,CACzB,OAAO,KAAK,MAAM,UAEpB,IAAI,UAAmB,CACrB,OAAO,KAAK,OAAO,SAErB,IAAI,aAAsB,CACxB,OAAO,KAAK,OAAO,YAErB,IAAI,aAA+B,CACjC,OAAO,KAAK,OAAO,aAAa,CAElC,IAAI,QAAkB,CACpB,OAAO,KAAK,OAAO,OAErB,IAAI,WAAsB,CACxB,OAAO,KAAK,OAAO,WAAW,CAGhC,IAAI,OAAiB,CACnB,OAAO,KAAK,OAAO,MAKrB,WAAW,EAAuE,CAChF,OAAO,KAAK,MAAM,WAAW,EAAK,CAEpC,WAAW,EAAwC,CACjD,OAAO,KAAK,MAAM,WAAW,EAAK,CAGpC,SAAS,EAA0B,CACjC,OAAO,KAAK,MAAM,SAAS,EAAK,CAGlC,OAAO,EAAuB,CAC5B,OAAO,KAAK,MAAM,OAAO,EAAK,CAahC,YAAY,EAAc,EAAY,EAA0B,CAG9D,OAFA,KAAK,OAAO,KAAK,EAAM,EAAI,EAAK,CAChC,KAAK,MAAM,MAAM,EAAM,EAAG,CACnB,KAAK,MAAM,SAAS,EAAK,CAMlC,MAAM,EAA6B,CACjC,IAAM,EAAU,KAAK,OAAO,MAAM,EAAK,CACvC,IAAK,IAAM,KAAQ,EAAM,KAAK,MAAM,MAAM,EAAK,KAAM,EAAK,GAAG,CAC7D,KAAK,OAAO,EAAQ,CAQtB,MAAM,OAAO,EAAuD,CAClE,GAAM,CAAE,QAAO,WAAU,YAAa,KAAK,OAAO,UAAU,EAAK,CACjE,GAAI,CACF,IAAM,EAAQ,KAAK,eAAe,EAAI,EAAS,OAAS,EACxD,IAAK,IAAM,KAAQ,EAAU,KAAK,MAAM,WAAW,EAAM,EAAQ,UAAY,SAAS,CACtF,KAAK,OAAO,KAAK,eAAgB,CAAE,QAAO,YAAa,KAAK,OAAO,YAAa,WAAU,CAAC,CAE3F,IAAM,EAAU,EAAS,IAAK,IAAU,CACtC,OACA,GAAI,EAAS,IAAI,EAAQ,EAAK,CAAC,EAAE,IAAM,KAAK,SAC7C,EAAE,CACH,MAAM,KAAK,MAAM,UAAU,EAAU,GAAS,CAC5C,KAAK,OAAO,KAAK,OAAO,KAAK,EAAM,EAAS,IAAI,EAAQ,EAAK,CAAC,EAAI,KAAK,CAAC,EACxE,CAEF,GAAM,CAAE,UAAS,UAAW,KAAK,OAAO,SAAS,CAEjD,OADA,KAAK,OAAO,EAAQ,CACb,CACL,QACA,KAAM,EACN,YAAa,KAAK,OAAO,YACzB,KAAM,KAAK,OAAO,OAClB,KAAM,KAAK,OAAO,QAAU,OAC7B,OACM,EAAK,CAYZ,MAFA,KAAK,OAAO,WAAW,CACvB,KAAK,MAAM,cAAc,CACnB,GASV,QAAQ,EAAkC,CACxC,GAAM,CAAE,UAAS,YAAa,KAAK,OAAO,QAAQ,EAAM,CACxD,IAAK,IAAM,KAAQ,EAAU,KAAK,MAAM,MAAM,EAAK,KAAM,KAAK,SAAS,CAEvE,OADA,KAAK,OAAO,EAAQ,CACb,EAUT,MAAe,CACb,IAAM,EAAW,KAAK,MAAM,cAAc,CAE1C,OADA,KAAK,OAAO,KAAK,eAAgB,CAAE,WAAU,CAAC,CACvC,EAIT,OAAc,CACZ,IAAM,EAAU,KAAK,OAAO,OAAO,CACnC,IAAK,IAAM,KAAQ,KAAK,MAAM,OAAO,CAAE,KAAK,MAAM,MAAM,EAAM,KAAK,SAAS,CAC5E,KAAK,OAAO,EAAQ,CAGtB,IAAI,aAAuB,CACzB,OAAO,KAAK,MAAM,YAGpB,SAAgB,CACV,KAAK,MAAM,cACf,KAAK,OAAO,oBAAoB,CAChC,KAAK,MAAM,SAAS,EAMtB,OAAe,EAAkC,CAC/C,IAAK,IAAM,KAAM,EAId,KAAK,OAAO,KAAkD,EAAG,KAAM,EAAG,QAAQ,CAC/E,EAAG,OAAS,eAGT,KAAK,SAAS,EAAG,QAAQ,KAAK,KAAK,CACrC,SAAS,CACT,MAAO,GAAQ,QAAQ,KAAK,8CAA+C,EAAI,CAAC,CAKzF,eAAiC,CAE/B,OADK,KAAK,gBACH,KAAK,gBAAgB,CAC1B,OAAQ,KAAK,OAAO,aAAa,CAAC,OAClC,SAAU,KAAK,OAAO,SACtB,YAAa,KAAK,OAAO,YAC1B,CAAC,CALgC,KC/QzB,EAAb,KAAgD,CAC9C,MAAgB,EAChB,MAAgB,EAChB,MAAgB,GAChB,KAAe,EACf,SAAmB,QACnB,SAAmB,EACnB,cAAqE,KACrE,SAAkD,KAClD,YAAkE,KAClE,aAAqC,CAAE,GAAG,EAAA,EAAa,OAAQ,gBAAiB,IAAK,CACrF,UAA0D,EAAK,KAAS,EAAM,GAAO,GACrF,gBAEW,KACX,QAAgE,KAChE,QAAiC,KACjC,KAAsC,KAEtC,KAAK,EAAc,EAAoB,CAGrC,MAFA,MAAK,MAAQ,EACb,KAAK,MAAQ,EACN,KAGT,SAAS,EAAc,EAA0B,EAAE,CAAQ,CAGzD,MAFA,MAAK,MAAQ,EACb,KAAK,KAAO,EAAK,KAAO,KAAK,KACtB,KAQT,QAAQ,EAAwD,CAE9D,MADA,MAAK,cAAgB,EACd,KAIT,QAAQ,EAAuC,CAE7C,MADA,MAAK,SAAW,EACT,KAIT,QAAQ,EAAkB,CAExB,MADA,MAAK,SAAW,EACT,KAST,WAAW,EAAsD,CAE/D,MADA,MAAK,YAAc,CAAE,GAAI,KAAK,aAAe,EAAE,CAAG,GAAG,EAAW,CACzD,KAIT,QAAQ,EAAqB,CAE3B,MADA,MAAK,SAAW,EACT,KAIT,aAAa,EAA6B,CAExC,MADA,MAAK,aAAe,EACb,KAQT,QAAQ,EAAgD,CAEtD,MADA,MAAK,SAAW,EACT,KAQT,eACE,EACM,CAEN,MADA,MAAK,gBAAkB,EAChB,KAIT,WAAW,EAAiD,CAE1D,MADA,MAAK,QAAU,EACR,KAGT,OAAO,EAAsB,CAE3B,MADA,MAAK,QAAU,EACR,KAIT,IAAI,EAAwB,CAE1B,MADA,MAAK,KAAO,EACL,KAGT,OAAgC,CAC9B,GAAI,CAAC,KAAK,cACR,MAAU,MAAM,gFAAgF,CAElG,GAAI,CAAC,KAAK,QACR,MAAU,MAAM,+CAA+C,CAEjE,OAAO,IAAI,EAAuB,CAChC,KAAM,KAAK,MACX,KAAM,KAAK,MACX,KAAM,KAAK,MACX,IAAK,KAAK,KACV,QAAS,KAAK,SACd,QAAS,KAAK,SACd,aAAc,KAAK,cACnB,QAAS,KAAK,SACd,WAAY,KAAK,YACjB,YAAa,KAAK,aAClB,QAAS,KAAK,SACd,eAAgB,KAAK,gBACrB,OAAQ,KAAK,QACb,OAAQ,KAAK,QACb,IAAK,KAAK,KACX,CAAC,GC/FO,EAAb,KAAkD,CAChD,UACA,OAAkB,IAAI,EAAA,EACtB,aAEA,OACA,OACA,MACA,aACA,WACA,KACA,SACA,WACA,eACA,KAGA,OAAwC,EAAE,CAC1C,GAEA,KAAe,EACf,OAA6E,OAC7E,OAA2B,EAAE,CAC7B,SAAqD,KACrD,WAAqB,EACrB,YAAsB,GACtB,UAAoB,EACpB,OAAiB,EACjB,WAAqB,GAGrB,UAAoB,EACpB,iBAAiF,EAAE,CACnF,eAAwC,EAAE,CAC1C,oBAA4C,EAAE,CAC9C,gBAAoC,EAAE,CACtC,gBAA+C,KAE/C,YAAY,EAA2B,CACrC,GAAI,CAAC,EAAI,OAAQ,MAAU,MAAM,wCAAwC,CACzE,KAAK,KAAO,EACZ,KAAK,aAAe,EAAI,aACxB,KAAK,OAAS,EAAI,UAClB,KAAK,OAAS,EAAI,WAClB,KAAK,MAAQ,EAAI,UAAY,EAAI,IACjC,KAAK,aAAe,EAAI,aAAe,KAAK,MAAQ,EAAI,IACxD,KAAK,WAAa,EAAI,UACtB,KAAK,GAAK,EAAI,aAAe,EAC7B,KAAK,KAAO,EAAI,KAAO,KAAK,OAE5B,KAAK,UAAY,IAAI,EAAA,UAErB,IAAM,EAAW,IAAI,EAAA,EAGrB,GAFA,EAAI,aAAa,EAAS,CAC1B,KAAK,eAAiB,EAAS,UAC3B,KAAK,eAAe,SAAW,EACjC,MAAU,MAAM,0DAA0D,CAE5E,GAAI,EAAI,aAAa,SAAW,EAC9B,MAAU,MACR,gFAAgF,EAAI,aAAa,OAAO,GACzG,CAEH,IAAM,EAAa,EAAI,aAAa,GAAG,QACvC,IAAK,IAAM,KAAM,EAAY,KAAK,kBAAkB,EAAG,CACvD,GAAI,EAAW,SAAW,EAAI,aAC5B,MAAU,MACR,0DAA0D,EAAI,aAAa,YAAY,EAAW,OAAO,IAC1G,CAIH,GAFA,KAAK,SAAW,IAAI,EAAA,EAAc,EAAS,CAEvC,EAAI,OAAQ,CACd,IAAM,EAAK,IAAI,EAAA,SACf,EAAI,OAAO,EAAI,KAAK,aAAc,KAAK,OAAO,CAC9C,KAAK,UAAU,SAAS,EAAG,CAE7B,IAAM,EAAO,IAAI,EAAA,UAAU,CAAC,KAAK,EAAG,EAAG,KAAK,aAAc,KAAK,OAAO,CAAC,KAAK,SAAS,CACrF,KAAK,UAAU,SAAS,EAAK,CAC7B,KAAK,UAAU,KAAO,EAEtB,KAAK,QAAQ,EAAW,CACxB,KAAK,WAAa,IAAI,EAAA,EAAU,EAAI,OAAO,CAC3C,KAAK,WAAW,IAAK,GAAM,KAAK,MAAM,EAAE,CAAC,CAG3C,IAAI,OAAgB,CAClB,OAAO,KAAK,aAEd,IAAI,QAAiB,CACnB,OAAO,KAAK,OAEd,IAAI,WAAiC,CACnC,OAAO,KAAK,WAEd,IAAI,YAAsB,CACxB,OAAO,KAAK,SAAW,YAAc,KAAK,SAAW,YAAc,KAAK,SAAW,UAErF,IAAI,aAAuB,CACzB,OAAO,KAAK,SAAW,YAEzB,IAAI,aAAuB,CACzB,OAAO,KAAK,WAQd,MAA4B,CAC1B,GAAI,KAAK,SAAW,OAClB,MAAU,MAAM,wEAAwE,CAE1F,KAAK,OAAS,WACd,KAAK,OAAS,EAAE,CAChB,KAAK,YAAc,GACnB,KAAK,WAAa,YAAY,KAAK,CAInC,IAAK,IAAM,KAAQ,KAAK,OAAQ,EAAK,iBAAiB,CAEtD,OADA,KAAK,OAAO,KAAK,aAAa,CACvB,IAAI,QAAS,GAAY,CAC9B,KAAK,SAAW,GAChB,CASJ,UAAU,EAA+B,CACvC,GAAI,KAAK,SAAW,WAClB,MAAU,MAAM,kDAAkD,CAEpE,GAAI,EAAQ,SAAW,EACrB,MAAU,MACR,6EAA6E,EAAQ,OAAO,GAC7F,CAEH,IAAM,EAAS,EAAQ,GACvB,GAAI,EAAO,aAAa,KAAM,GAAM,IAAM,IAAA,GAAU,EAAI,EAAO,aAAa,KAAM,GAAM,IAAM,IAAA,GAAU,CACtG,MAAU,MAAM,wFAAwF,CAE1G,IAAM,EAAM,EAAO,QACnB,KAAK,mBAAmB,EAAI,CAI5B,IAAM,EAAc,KAAK,aAAe,MAAQ,CAAC,GAAG,EAAI,CAAG,CAAC,GAAG,EAAI,CAAC,SAAS,CAC7E,KAAK,OAAS,CAAC,GAAG,EAAa,KAAK,WAAW,CAAC,CAChD,KAAK,OAAS,WAOd,IAAK,IAAM,KAAQ,KAAK,OAAQ,EAAK,eAAe,CAOtD,UAAiB,CACf,GAAI,KAAK,SAAW,YAAc,KAAK,SAAW,UAChD,MAAU,MAAM,8EAA8E,CAGhG,IADA,KAAK,YAAc,GACZ,KAAK,OAAO,OAAS,GAAG,KAAK,SAAS,KAAK,OAAO,OAAO,CAAW,CAC3E,KAAK,KAAO,EACZ,KAAK,SAAS,CACd,KAAK,OAAO,CAmBd,QAAQ,EAAmB,EAAkC,CAC3D,GAAI,KAAK,SAAW,OAClB,MAAU,MAAM,gFAAgF,CAGlG,GADe,CAAC,GAAG,IAAI,IAAI,EAAQ,CAAC,CACzB,SAAW,EAAQ,OAC5B,MAAU,MAAM,oDAAoD,CAEtE,IAAK,IAAM,KAAK,EACd,GAAI,EAAI,GAAK,GAAK,KAAK,aACrB,MAAU,MAAM,oCAAoC,EAAE,iBAAiB,KAAK,aAAe,EAAE,GAAG,CAGpG,IAAM,EAAe,GAAU,EAAQ,QAAU,KAAK,WAAW,CAAC,CAClE,GAAI,EAAa,SAAW,EAAQ,OAClC,MAAU,MAAM,8DAA8D,CAEhF,IAAK,IAAM,KAAM,EAAc,KAAK,kBAAkB,EAAG,CACzD,GAAI,EAAQ,SAAW,EAAG,OAAO,QAAQ,SAAS,CAElD,IAAM,EAAY,IAAI,IAAI,EAAQ,CAC5B,EAAS,KAAK,OAAO,MAAM,EAAG,EAAI,KAAK,aAAa,CACpD,EAAU,EAAQ,IAAK,GAAM,CACjC,IAAM,EAAO,EAAO,GACpB,MAAO,CAAE,OAAM,MAAO,EAAK,KAAK,EAAG,MAAO,EAAK,KAAK,EAAG,EACvD,CACI,EAAY,EAAO,QAAQ,EAAG,IAAM,CAAC,EAAU,IAAI,EAAE,CAAC,CAGtD,EAAO,EAAa,IAAK,GAAO,CACpC,IAAM,EAAO,KAAK,SAAS,QAAQ,EAAG,CAItC,OAHA,EAAK,OAAO,KAAK,OAAQ,KAAK,OAAO,CACrC,EAAK,KAAK,EAAI,EACd,KAAK,UAAU,SAAS,EAAK,KAAK,CAC3B,GACP,CAII,EACJ,KAAK,aAAe,MAAQ,CAAC,GAAG,EAAW,GAAG,EAAK,CAAG,CAAC,GAAG,EAAM,GAAG,EAAU,CAC/E,KAAK,oBAAsB,EAI3B,IAAM,EAAS,KAAK,aAAe,KAAK,MAexC,MAdA,MAAK,eAAiB,EAAY,KAAK,EAAM,IAAM,CACjD,IAAM,EAAM,EAAI,KAAK,MACf,EAAQ,EAAK,SAAS,EAAK,CAC3B,EAAQ,EAAQ,EAAM,KAAK,WAAW,CAAG,EAAS,EAAK,KAAK,EAKlE,OAJI,IACF,EAAK,KAAK,EAAI,EACd,EAAK,KAAK,MAAQ,GAEb,CAAE,OAAM,QAAO,MAAK,EAC3B,CACF,KAAK,iBAAmB,EACxB,KAAK,gBAAkB,CAAC,GAAG,EAAQ,CACnC,KAAK,UAAY,EACjB,KAAK,OAAS,YACP,IAAI,QAAS,GAAY,CAC9B,KAAK,gBAAkB,GACvB,CAIJ,SAAS,EAA2B,CAClC,GAAI,EAAQ,GAAK,GAAS,KAAK,aAC7B,MAAU,MAAM,wBAAwB,EAAM,iBAAiB,KAAK,aAAe,EAAE,GAAG,CAE1F,OAAO,KAAK,OAAO,EAAQ,GAG7B,SAAgB,CACV,SAAK,WAIT,CAHA,KAAK,WAAa,GAClB,KAAK,OAAS,OACd,KAAK,WAAW,SAAS,CACzB,KAAK,OAAO,oBAAoB,CAIhC,IAAK,IAAM,KAAK,KAAK,iBAAuB,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,CAChF,IAAK,IAAM,KAAK,KAAK,eAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,CAC9E,KAAK,iBAAmB,EAAE,CAC1B,KAAK,eAAiB,EAAE,CACxB,KAAK,oBAAsB,EAAE,CAC7B,IAAK,IAAM,KAAK,KAAK,OAAQ,EAAE,SAAS,CACxC,KAAK,OAAO,OAAS,EACrB,KAAK,SAAS,SAAS,CACvB,KAAK,UAAU,QAAQ,CAAE,SAAU,GAAM,CAAC,CAE1C,KAAK,WAAW,CAAE,QAAS,CAAC,EAAE,CAAC,CAAE,WAAY,GAAM,SAAU,EAAG,CAAC,CACjE,KAAK,SAAW,KAChB,KAAK,mBAAmB,CACxB,KAAK,gBAAkB,MAMzB,QAAgB,EAAyB,CACvC,IAAM,EAAM,CAAC,KAAK,WAAW,CAAE,GAAG,EAAS,KAAK,WAAW,CAAC,CAC5D,IAAK,IAAI,EAAI,EAAG,EAAI,KAAK,GAAI,IAAK,CAChC,IAAM,EAAS,KAAK,SAAS,QAAQ,EAAI,GAAG,CAC5C,EAAO,OAAO,KAAK,OAAQ,KAAK,OAAO,CACvC,EAAO,KAAK,EAAI,EAChB,KAAK,UAAU,SAAS,EAAO,KAAK,CACpC,KAAK,OAAO,KAAK,EAAO,CAE1B,KAAK,SAAS,CAGhB,MAAc,EAAsB,CAC9B,UAAK,YAAc,KAAK,SAAW,QACvC,IAAI,KAAK,SAAW,YAAa,CAC/B,KAAK,gBAAgB,EAAO,QAAQ,CACpC,OAEF,GAAI,KAAK,SAAW,UAAW,CAC7B,KAAK,gBAAgB,EAAO,QAAQ,CACpC,OAEF,KAAK,aAAa,EAAO,UAAU,EAGrC,aAAqB,EAAkB,CAErC,IAAM,EACJ,KAAK,SAAW,WAAa,KAAK,IAAI,IAAM,KAAK,OAAO,QAAU,KAAK,aAAe,GAAG,CAAG,EAE9F,IADA,KAAK,MAAQ,KAAK,KAAK,MAAQ,EAAK,EAC7B,KAAK,MAAQ,KAAK,QACvB,KAAK,MAAQ,KAAK,MAClB,KAAK,SAAS,KAAK,WAAW,CAAC,CAC3B,KAAK,SAAW,aAEtB,KAAK,SAAS,CAGhB,WAA4B,CAC1B,GAAI,KAAK,SAAW,WAAY,CAC9B,IAAM,EAAK,KAAK,OAAO,OAAO,CAE9B,OADI,KAAK,OAAO,SAAW,GAAG,KAAK,eAAe,CAC3C,EAET,OAAO,KAAK,WAAW,CAIzB,SAAiB,EAAkB,CACjC,GAAI,KAAK,aAAe,MAAO,CAC7B,IAAM,EAAU,KAAK,OAAO,OAAO,CACnC,KAAK,OAAO,KAAK,KAAK,SAAS,EAAS,EAAG,CAAC,KACvC,CACL,IAAM,EAAU,KAAK,OAAO,KAAK,CACjC,KAAK,OAAO,QAAQ,KAAK,SAAS,EAAS,EAAG,CAAC,EAInD,SAAiB,EAAsB,EAAwB,CAC7D,KAAK,UAAU,YAAY,EAAS,KAAK,CACzC,KAAK,SAAS,QAAQ,EAAS,CAC/B,IAAM,EAAO,KAAK,SAAS,QAAQ,EAAG,CAWtC,OAVA,EAAK,OAAO,KAAK,OAAQ,KAAK,OAAO,CACrC,EAAK,KAAK,EAAI,EACd,KAAK,UAAU,SAAS,EAAK,KAAK,CAK9B,KAAK,SAAW,YAClB,EAAK,gBAAgB,GAAK,CAErB,EAIT,SAAwB,CACtB,IAAM,EAAO,KAAK,aAAe,MAAQ,GAAK,EAC9C,IAAK,IAAI,EAAI,EAAG,EAAI,KAAK,GAAI,IAC3B,KAAK,OAAO,GAAG,KAAK,GAAK,EAAI,GAAK,KAAK,MAAQ,EAAO,KAAK,KAI/D,eAA8B,CAC5B,KAAK,OAAS,UACd,KAAK,UAAY,KAAK,KACtB,KAAK,OAAS,EAGhB,gBAAwB,EAAuB,CAC7C,KAAK,QAAU,EACf,IAAM,EAAI,KAAK,IAAI,EAAG,KAAK,OAAS,IAAI,CACxC,KAAK,KAAO,KAAK,WAAa,EAAI,GAClC,KAAK,SAAS,CACV,GAAK,IACP,KAAK,KAAO,EACZ,KAAK,SAAS,CACd,KAAK,OAAO,EAIhB,OAAsB,CACpB,KAAK,OAAS,OAId,IAAK,IAAM,KAAQ,KAAK,OAAQ,EAAK,cAAc,CAEnD,IAAM,EAAqB,CACzB,QAAS,CAAC,KAAK,YAAY,CAAC,CAC5B,WAAY,KAAK,YACjB,SAAU,YAAY,KAAK,CAAG,KAAK,WACpC,CACK,EAAU,KAAK,SACrB,KAAK,SAAW,KAChB,KAAK,OAAO,KAAK,gBAAiB,EAAO,CACzC,IAAU,EAAO,CAGnB,gBAAwB,EAAuB,CAC7C,KAAK,WAAa,EAClB,IAAM,EAAO,KAAK,KAAK,QAAQ,KACzB,EAAO,KAAK,KAAK,QAAQ,KAKzB,EAAK,KAAK,IAAI,EAAG,KAAK,UAAY,EAAK,CACvC,EAAM,IACN,EAAI,EAAK,EAAM,EAAY,EAAK,EAAb,IAAoB,MAAQ,IAAM,EAAK,IAAQ,EAAI,KAAS,GAC/E,EAAI,EAAK,EAAM,EAAI,GAAK,EAAK,IAAQ,EAAI,GAC/C,IAAK,IAAM,KAAO,KAAK,iBAAkB,CACvC,IAAM,EAAO,EAAI,KAAK,KACtB,EAAK,MAAM,IAAI,KAAK,IAAI,EAAG,EAAE,CAAC,CAC9B,EAAK,MAAQ,KAAK,IAAI,EAAG,EAAE,CAC3B,EAAK,EAAI,EAAI,MAAS,KAAK,OAAS,GAAM,EAAI,GAC9C,EAAK,EAAI,EAAI,MAAS,KAAK,OAAS,GAAM,EAAI,GAIhD,IAAM,EAAK,KAAK,WAAa,EAAO,EAAI,KAAK,IAAI,GAAI,KAAK,UAAY,GAAQ,EAAK,CAC7E,EAAQ,GAAK,EAAI,IAAO,EAAI,GAClC,IAAK,IAAM,KAAM,KAAK,eACpB,EAAG,KAAK,KAAK,EAAI,EAAG,OAAS,EAAG,IAAM,EAAG,OAAS,EAEhD,KAAK,WAAa,EAAO,GAAM,KAAK,gBAAgB,CAG1D,gBAA+B,CAE7B,IAAK,IAAM,KAAO,KAAK,iBACrB,KAAK,UAAU,YAAY,EAAI,KAAK,KAAK,CACzC,KAAK,SAAS,QAAQ,EAAI,KAAK,CAIjC,KAAK,oBAAoB,SAAS,EAAM,IAAM,CAC5C,EAAK,KAAK,EAAI,EAAI,KAAK,MACvB,EAAK,KAAK,EAAI,EACd,EAAK,KAAK,MAAQ,EAClB,EAAK,KAAK,MAAM,IAAI,EAAE,CACtB,KAAK,OAAO,EAAI,GAAK,GACrB,CACF,IAAM,EAAU,KAAK,gBACrB,KAAK,iBAAmB,EAAE,CAC1B,KAAK,eAAiB,EAAE,CACxB,KAAK,oBAAsB,EAAE,CAC7B,KAAK,gBAAkB,EAAE,CACzB,KAAK,OAAS,OACd,IAAM,EAAU,KAAK,gBACrB,KAAK,gBAAkB,KACvB,KAAK,OAAO,KAAK,mBAAoB,CAAE,UAAS,QAAS,KAAK,YAAY,CAAE,CAAC,CAC7E,KAAW,CAGb,YAA+B,CAC7B,OAAO,KAAK,OAAO,MAAM,EAAG,EAAI,KAAK,aAAa,CAAC,IAAK,GAAM,EAAE,SAAS,CAI3E,WAA4B,CAC1B,OAAO,KAAK,aAAe,MAAQ,GAAK,EAG1C,WAA4B,CAC1B,OAAO,KAAK,eAAe,KAAK,MAAM,KAAK,MAAM,CAAG,KAAK,eAAe,OAAO,EAGjF,mBAA2B,EAAqB,CAC9C,GAAI,EAAI,SAAW,KAAK,aACtB,MAAU,MACR,2CAA2C,KAAK,aAAa,YAAY,EAAI,OAAO,IACrF,CAEH,IAAK,IAAM,KAAM,EAAK,KAAK,kBAAkB,EAAG,CAGlD,kBAA0B,EAAkB,CAC1C,GAAI,CAAC,KAAK,eAAe,SAAS,EAAG,CACnC,MAAU,MAAM,uBAAuB,EAAG,uCAAuC,GCzhB1E,EAAb,KAAmC,CACjC,cAAwB,EACxB,OAAiB,GACjB,OAAiB,GACjB,KAAe,EACf,WAA0C,MAC1C,OAAiB,GACjB,SAAsD,CAAE,KAAM,IAAK,KAAM,IAAK,CAC9E,cAA+C,KAC/C,cAAqE,KACrE,QAAiF,KACjF,QAAiC,KACjC,KAAsC,KAGtC,aAAa,EAAiB,CAC5B,GAAI,EAAI,EAAG,MAAU,MAAM,oDAAoD,CAE/E,MADA,MAAK,cAAgB,EACd,KAIT,SAAS,EAAe,EAAiB,EAAO,EAAyB,EAAE,CAAQ,CAIjF,MAHA,MAAK,OAAS,EACd,KAAK,OAAS,EACd,KAAK,KAAO,EAAK,KAAO,KAAK,KACtB,KAIT,UAAU,EAAsC,CAE9C,MADA,MAAK,WAAa,EACX,KAIT,UAAU,EAA0B,CAElC,MADA,MAAK,OAAS,EACP,KAIT,cAAc,EAAqC,CAKjD,MAJA,MAAK,SAAW,CACd,KAAM,EAAK,MAAQ,KAAK,SAAS,KACjC,KAAM,EAAK,MAAQ,KAAK,SAAS,KAClC,CACM,KAST,QAAQ,EAAwD,CAE9D,MADA,MAAK,cAAgB,EACd,KAST,aAAa,EAA6B,CAExC,MADA,MAAK,cAAgB,EACd,KAIT,OAAO,EAAkE,CAEvE,MADA,MAAK,QAAU,EACR,KAIT,IAAI,EAAwB,CAE1B,MADA,MAAK,KAAO,EACL,KAIT,OAAO,EAAsB,CAE3B,MADA,MAAK,QAAU,EACR,KAGT,OAAwB,CACtB,GAAI,CAAC,KAAK,cACR,MAAU,MAAM,oDAAoD,CAEtE,GAAI,CAAC,KAAK,QACR,MAAU,MAAM,mDAAmD,CAGrE,IAAI,EAAe,KAAK,cACxB,GAAI,CAAC,EAAc,CACjB,IAAM,EAAM,IAAI,EAAA,EAChB,KAAK,cAAc,EAAI,CACvB,IAAM,EAAM,EAAI,UAEhB,EAAe,CAAC,CAAE,QADF,MAAM,KAAK,CAAE,OAAQ,KAAK,cAAe,EAAG,EAAG,IAAM,EAAI,EAAI,EAAI,QAAQ,CAC9D,CAAC,CAE9B,OAAO,IAAI,EAAe,CACxB,aAAc,KAAK,cACnB,UAAW,KAAK,OAChB,WAAY,KAAK,OACjB,IAAK,KAAK,KACV,UAAW,KAAK,WAChB,MAAO,KAAK,OACZ,QAAS,KAAK,SACd,eACA,aAAc,KAAK,cACnB,OAAQ,KAAK,QACb,OAAQ,KAAK,QACb,IAAK,KAAK,KACX,CAAC,GCrIN,SAAgB,EAA8C,EAAyB,CACrF,MAAO,CAAC,GAAG,EAAK,CAAC,MAAM,EAAG,KAAO,EAAE,OAAS,IAAM,EAAE,OAAS,GAAG,CCiFlE,IAAa,EAAb,MAAa,CAAmC,CAC9C,SACA,SACA,OAAyC,KACzC,UAAoB,GACpB,aAAuB,GAEvB,YAAY,EAAkB,EAA+B,EAAE,CAAE,CAC/D,KAAK,SAAW,EAChB,KAAK,SAAW,EAAa,SAAS,EAAQ,CAGhD,IAAI,UAAoB,CACtB,OAAO,KAAK,UAGd,IAAI,aAAuB,CACzB,OAAO,KAAK,aASd,MAAM,KAAK,EAAqC,CAG9C,GAFA,KAAK,OAAO,CACR,KAAK,cACL,EAAK,SAAW,EAAG,OAEvB,IAAM,EAAU,KAAK,SAAS,YAC1B,EAAgB,EAAK,CACrB,CAAC,GAAG,EAAK,CAEP,EAAQ,IAAI,gBAClB,KAAK,OAAS,EACd,KAAK,UAAY,GACjB,KAAK,SAAS,OAAO,KAAK,YAAa,EAAQ,CAE/C,IAAI,EAAO,EACX,GAAI,CACF,KAAO,KAAK,SAAS,SAAW,IAAM,EAAO,KAAK,SAAS,QAAQ,CACjE,IAAK,IAAM,KAAO,EAAS,CAGzB,GAFI,EAAM,OAAO,UACjB,MAAM,KAAK,SAAS,EAAK,EAAM,OAAO,CAClC,EAAM,OAAO,SAAS,OAC1B,MAAM,KAAK,MAAM,KAAK,SAAS,SAAU,EAAM,OAAO,CAExD,YAEM,CACR,KAAK,eAAe,CACpB,IAAM,EAAa,EAAM,OAAO,QAC5B,KAAK,SAAW,IAAO,KAAK,OAAS,MACzC,KAAK,UAAY,GACjB,KAAK,SAAS,OAAO,KAAK,UAAW,EAAa,UAAY,WAAW,EAK7E,OAAc,CACR,KAAK,QAAQ,KAAK,OAAO,OAAO,CAGtC,SAAgB,CACV,KAAK,eACT,KAAK,aAAe,GACpB,KAAK,OAAO,EAGd,MAAc,SAAS,EAAU,EAAoC,CACnE,IAAM,EAAQ,CAAC,GAAG,EAAI,MAAM,CAC5B,GAAI,EAAM,SAAW,EAAG,OAIxB,KAAK,UAAU,EAAM,CACrB,KAAK,SAAS,OAAO,KAAK,YAAa,EAAK,EAAM,CAElD,IAAM,EAAU,KAAK,SAAS,QACxB,EAAgC,EAAE,CAExC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,QAExB,EADI,EAAO,UACP,EAAI,GAAK,EAAU,GAAG,MAAM,KAAK,MAAM,EAAS,EAAO,CACvD,EAAO,UAHqB,IAAK,CAIrC,IAAM,EAAO,EAAM,GACb,EAAO,KAAK,SAAS,QAAQ,EAAK,UAAU,CAClD,GAAI,CAAC,EAAM,SACX,IAAM,EAAS,EAAK,YAAY,EAAK,SAAS,CACzC,IACL,KAAK,SAAS,OAAO,KAAK,aAAc,EAAQ,EAAM,EAAI,CAC1D,EAAa,KAAK,KAAK,UAAU,EAAQ,EAAM,EAAI,CAAC,EAGtD,MAAM,QAAQ,IAAI,EAAa,CAGjC,MAAc,UACZ,EACA,EACA,EACe,CACf,IAAM,EAAO,KAAK,SAAS,WAC3B,GAAI,OAAO,GAAS,WAAY,OAAO,EAAK,EAAQ,EAAM,EAAI,CAC9D,GAAI,IAAS,MAAO,OAAO,EAAO,SAAS,CAC3C,IAAM,EAAW,EAEjB,OADI,OAAO,EAAS,eAAkB,WAAmB,EAAS,cAAc,EAAK,CAC9E,EAAO,SAAS,CAGzB,UAAkB,EAA2C,CAC3D,IAAM,EAAQ,KAAK,SAAS,SAC5B,GAAI,IAAU,KAAM,OAEpB,IAAM,EAAU,IAAI,IACpB,IAAK,IAAM,KAAK,EAAU,EAAQ,IAAI,GAAG,EAAE,UAAU,GAAG,EAAE,WAAW,CAErE,IAAM,EAAQ,KAAK,SAAS,MAC5B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAO,EAAM,GACnB,IAAK,IAAI,EAAM,EAAG,EAAM,EAAK,YAAa,IAAO,CAC/C,IAAM,EAAO,EAAK,YAAY,EAAI,CAAC,KACnC,EAAK,MAAQ,EAAQ,IAAI,GAAG,EAAE,GAAG,IAAM,CAAG,EAAI,IAKpD,eAA8B,CAC5B,IAAM,EAAQ,KAAK,SAAS,MAC5B,IAAK,IAAM,KAAQ,EACjB,IAAK,IAAI,EAAM,EAAG,EAAM,EAAK,YAAa,IACxC,EAAK,YAAY,EAAI,CAAC,KAAK,MAAQ,EAKzC,MAAc,EAAY,EAAoC,CAC5D,OAAO,IAAI,QAAS,GAAY,CAC9B,GAAI,EAAO,QAAS,OAAO,GAAS,CACpC,IAAM,EAAI,WAAW,EAAS,EAAG,CACjC,EAAO,iBACL,YACM,CACJ,aAAa,EAAE,CACf,GAAS,EAEX,CAAE,KAAM,GAAM,CACf,EACD,CAGJ,OAAe,SAAS,EAA4C,CAClE,IAAI,EASJ,MARA,CAKE,EALE,EAAK,YAAc,GACV,KACF,OAAO,EAAK,WAAc,UAAY,EAAK,YAAc,KACvD,EAAK,UAAU,OAAS,IAExB,IAGN,CACL,WACA,WAAY,EAAK,YAAc,MAC/B,QAAS,KAAK,IAAI,EAAG,EAAK,SAAW,EAAE,CACvC,SAAU,EAAK,UAAY,IAC3B,OAAQ,EAAK,QAAU,EACvB,YAAa,EAAK,aAAe,GAClC,GC3NL,SAAgB,EAAoB,EAA4B,CAC9D,IAAM,EAAO,EAAA,GAAS,CACtB,EAAK,OAAO,OAAO,EAAK,WAAW,CACnC,IAAM,MAAqB,CACzB,EAAK,WAAW,EAAO,SAAW,IAAK,EAEzC,EAAO,IAAI,EAAO,CAElB,IAAI,EAAW,GACf,UAAa,CACP,IACJ,EAAW,GACX,EAAO,OAAO,EAAO,CACrB,EAAK,OAAO,IAAI,EAAK,WAAW"}