@zinejs/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["#radius","#mounted","#listeners","#state","#handlers","#activeId","#start","#prev","#last","#end","#reset","#pinching","#points","#pair","#startDistance","#remove","#renderer","#container","#source","#direction","#cover","#clickToFlip","#clickZoneSize","#singlePageThreshold","#startPageOption","#buildSpreadModel","#flipDuration","#zoomEnabled","#wheelZoom","#doubleClickLevels","#maxZoom","#honorDoubleClickInFlipZone","#clickFlipDelayValue","#ready","#init","#currentPage","#startFlip","#current","#spreadIndexForPage","#scale","#tx","#ty","#clampPan","#emitter","#destroyed","#raf","#clearPendingClickFlip","#resizeObserver","#a11yCleanup","#unbindWheel","#unbindDblClick","#unbindInput","#spreads","#machine","#leadPage","#runFlip","#resolveContent","#currentContent","#singlePage","#animateProgress","#commit","#effectiveDuration","#paintSpread","#prefetchWindow","#announce","#pinching","#pan","#press","#pendingGrab","#drag","#stageDrag","#promoteGrab","#maybeClickFlip","#cancelFlip","#clickFlipDirection","#pendingClickTimer","#pinchBaseScale","#mountWithFallback","#applySinglePage","#onDragStart","#onDragMove","#onDragEnd","#onPinchStart","#onPinchMove","#onPinchEnd","#bindWheelZoom","#bindDoubleClickZoom","#setupReducedMotion","#setupA11y","#renderCurrent","#observeResize","#fallbackToCss","#fellBack","#reducedMotion","#onKeyDown","#liveRegion","#scheduleUpdate","#updateScheduled","#getPage","#virtualizer","#urls","#preload","#decode","#cache"],"sources":["../src/engine/spread.ts","../src/engine/virtualizer.ts","../src/engine/emitter.ts","../src/engine/stateMachine.ts","../src/engine/input.ts","../src/geometry/hitTest.ts","../src/renderer/select.ts","../src/zine.ts","../src/source/imageSource.ts"],"sourcesContent":["export type Direction = 'ltr' | 'rtl';\n\nexport interface Spread {\n /** Page index shown on the left, or null for a blank side. */\n left: number | null;\n /** Page index shown on the right, or null for a blank side. */\n right: number | null;\n}\n\nexport interface SpreadOptions {\n direction?: Direction;\n /** First page is a lone cover on the leading side; interior pages pair up after it. */\n cover?: boolean;\n /** One page per spread (narrow/portrait viewports). Takes precedence over `cover`. */\n singlePage?: boolean;\n}\n\n/**\n * Map a flat page list (indices 0..pageCount-1) into display spreads.\n *\n * Reading order is always preserved; `direction` only mirrors each spread's\n * left/right positions, so RTL is just an LTR layout with the sides swapped.\n * Out-of-range slots are `null` (a blank half on odd counts / the back cover).\n */\nexport function buildSpreads(pageCount: number, options: SpreadOptions = {}): Spread[] {\n const { direction = 'ltr', cover = false, singlePage = false } = options;\n if (pageCount <= 0) return [];\n\n const page = (i: number): number | null => (i >= 0 && i < pageCount ? i : null);\n const spreads: Spread[] = [];\n\n if (singlePage) {\n for (let i = 0; i < pageCount; i++) {\n spreads.push({ left: null, right: i });\n }\n } else if (cover) {\n spreads.push({ left: null, right: 0 });\n for (let i = 1; i < pageCount; i += 2) {\n spreads.push({ left: page(i), right: page(i + 1) });\n }\n } else {\n for (let i = 0; i < pageCount; i += 2) {\n spreads.push({ left: page(i), right: page(i + 1) });\n }\n }\n\n return direction === 'rtl'\n ? spreads.map((s) => ({ left: s.right, right: s.left }))\n : spreads;\n}\n\n/** Whether a container this wide should fall back to one page per spread. */\nexport function shouldSinglePage(containerWidth: number, threshold: number): boolean {\n return containerWidth < threshold;\n}\n","/** Spread indices that should be mounted around `current`, clamped to the book. */\nexport function mountWindow(current: number, spreadCount: number, radius: number): number[] {\n const window: number[] = [];\n const from = Math.max(0, current - radius);\n const to = Math.min(spreadCount - 1, current + radius);\n for (let i = from; i <= to; i++) {\n window.push(i);\n }\n return window;\n}\n\nexport interface VirtualizerDelta {\n toMount: number[];\n toEvict: number[];\n}\n\n/**\n * Tracks which spreads are mounted and, on each move, reports the difference:\n * spreads entering the ±radius window (`toMount`) and those leaving it\n * (`toEvict`). Default radius 2 = current spread ±2.\n */\nexport class Virtualizer {\n #radius: number;\n #mounted = new Set<number>();\n\n constructor(radius = 2) {\n this.#radius = radius;\n }\n\n get mounted(): number[] {\n return [...this.#mounted].sort((a, b) => a - b);\n }\n\n /** Recenter on `current`; mounts/evicts to match the window and returns the delta. */\n update(current: number, spreadCount: number): VirtualizerDelta {\n const window = new Set(mountWindow(current, spreadCount, this.#radius));\n const toMount = [...window].filter((i) => !this.#mounted.has(i)).sort((a, b) => a - b);\n const toEvict = [...this.#mounted].filter((i) => !window.has(i)).sort((a, b) => a - b);\n for (const i of toEvict) this.#mounted.delete(i);\n for (const i of toMount) this.#mounted.add(i);\n return { toMount, toEvict };\n }\n}\n","export type Listener<T> = (payload: T) => void;\n\n/** Events typed as `void` are emitted with no payload argument. */\ntype PayloadArgs<T> = [T] extends [void] ? [] : [payload: T];\n\n/** Minimal typed event emitter keyed by an event map. */\nexport class Emitter<Events> {\n #listeners = new Map<keyof Events, Set<Listener<unknown>>>();\n\n /** Subscribe; returns an unsubscribe function. */\n on<K extends keyof Events>(type: K, listener: Listener<Events[K]>): () => void {\n let set = this.#listeners.get(type);\n if (!set) {\n set = new Set();\n this.#listeners.set(type, set);\n }\n set.add(listener as Listener<unknown>);\n return () => this.off(type, listener);\n }\n\n off<K extends keyof Events>(type: K, listener: Listener<Events[K]>): void {\n this.#listeners.get(type)?.delete(listener as Listener<unknown>);\n }\n\n emit<K extends keyof Events>(type: K, ...args: PayloadArgs<Events[K]>): void {\n const payload = args[0] as Events[K];\n // Copy so a listener that unsubscribes mid-emit doesn't disturb iteration.\n const set = this.#listeners.get(type);\n if (!set) return;\n for (const listener of [...set]) {\n (listener as Listener<Events[K]>)(payload);\n }\n }\n\n clear(): void {\n this.#listeners.clear();\n }\n}\n\n/**\n * Public event surface of a Zine instance. Payloads are provisional and will\n * firm up as the engine wires the emitters that fire them.\n */\nexport interface ZineEventMap {\n ready: void;\n flipStart: { from: number; to: number };\n flipEnd: { page: number };\n pageChanged: { page: number };\n zoomChanged: { scale: number };\n sourceError: { index: number; error: unknown };\n rendererFallback: { from: string; to: string };\n}\n\nexport type ZineEmitter = Emitter<ZineEventMap>;\n","export type FlipState = 'idle' | 'dragging' | 'animating' | 'zoomed-panning';\n\nexport type FlipEvent = 'grab' | 'release' | 'settle' | 'panStart' | 'panEnd' | 'flip';\n\n/**\n * Legal transitions. A flip drag runs idle → dragging → animating → idle; a\n * programmatic flip jumps idle → animating directly via `flip`; zoom panning\n * (only reachable when zoomed in) is a separate branch idle → zoomed-panning →\n * idle. Any (state, event) pair absent here is illegal.\n */\nconst TRANSITIONS: Record<FlipState, Partial<Record<FlipEvent, FlipState>>> = {\n idle: { grab: 'dragging', panStart: 'zoomed-panning', flip: 'animating' },\n dragging: { release: 'animating' },\n animating: { settle: 'idle' },\n 'zoomed-panning': { panEnd: 'idle' },\n};\n\n/** Pure transition: the next state, or null if `event` is illegal from `state`. */\nexport function nextState(state: FlipState, event: FlipEvent): FlipState | null {\n return TRANSITIONS[state][event] ?? null;\n}\n\n/** Stateful holder around `nextState`; illegal events are ignored. */\nexport class FlipMachine {\n #state: FlipState;\n\n constructor(initial: FlipState = 'idle') {\n this.#state = initial;\n }\n\n get state(): FlipState {\n return this.#state;\n }\n\n /** Apply `event`; returns the new state, or null (no-op) if the event was illegal. */\n send(event: FlipEvent): FlipState | null {\n const next = nextState(this.#state, event);\n if (next !== null) {\n this.#state = next;\n }\n return next;\n }\n}\n","export interface GestureStart {\n x: number;\n y: number;\n t: number;\n}\n\nexport interface GestureMove {\n x: number;\n y: number;\n /** Displacement from the start point. */\n dx: number;\n dy: number;\n}\n\nexport interface GestureEnd {\n x: number;\n y: number;\n dx: number;\n dy: number;\n /** Elapsed time from start to end (ms). */\n dt: number;\n /** Velocity of the final move segment (px/ms). */\n vx: number;\n vy: number;\n /** A fast flick: final speed exceeded the swipe threshold and the gesture was not canceled. */\n swipe: boolean;\n /** The pointer was canceled (e.g. pointercancel) rather than lifted. */\n canceled: boolean;\n}\n\nexport interface PointerHandlers {\n onStart?: (g: GestureStart) => void;\n onMove?: (g: GestureMove) => void;\n onEnd?: (g: GestureEnd) => void;\n}\n\n/** Flick threshold in px/ms; a release faster than this is a swipe. */\nconst SWIPE_VELOCITY = 0.3;\n\ninterface Sample {\n x: number;\n y: number;\n t: number;\n}\n\n/**\n * Recognizes a single-pointer drag from raw pointer samples and reports normalized\n * gestures. DOM-agnostic on purpose: `bindPointerInput` maps real PointerEvents onto\n * it, but the kinematics stay pure and unit-testable. Extra pointers are ignored\n * (multi-touch pinch is a separate recognizer).\n */\nexport class PointerRecognizer {\n #handlers: PointerHandlers;\n #activeId: number | null = null;\n #start: Sample | null = null;\n #prev: Sample | null = null;\n #last: Sample | null = null;\n\n constructor(handlers: PointerHandlers = {}) {\n this.#handlers = handlers;\n }\n\n get active(): boolean {\n return this.#activeId !== null;\n }\n\n down(id: number, x: number, y: number, t: number): void {\n if (this.#activeId !== null) return; // single-pointer; ignore extra pointers\n this.#activeId = id;\n const s: Sample = { x, y, t };\n this.#start = s;\n this.#prev = s;\n this.#last = s;\n this.#handlers.onStart?.({ x, y, t });\n }\n\n move(id: number, x: number, y: number, t: number): void {\n if (id !== this.#activeId || this.#start === null) return;\n this.#prev = this.#last;\n this.#last = { x, y, t };\n this.#handlers.onMove?.({ x, y, dx: x - this.#start.x, dy: y - this.#start.y });\n }\n\n up(id: number, x: number, y: number, t: number): void {\n this.#end(id, x, y, t, false);\n }\n\n cancel(id: number, x: number, y: number, t: number): void {\n this.#end(id, x, y, t, true);\n }\n\n #end(id: number, x: number, y: number, t: number, canceled: boolean): void {\n if (id !== this.#activeId || this.#start === null) return;\n const start = this.#start;\n // Velocity comes from the last move segment, not the release point — on lift the\n // pointer usually stops, so release-relative velocity would kill every flick.\n const last = this.#last ?? start;\n const prev = this.#prev ?? start;\n const segDt = last.t - prev.t;\n const vx = segDt > 0 ? (last.x - prev.x) / segDt : 0;\n const vy = segDt > 0 ? (last.y - prev.y) / segDt : 0;\n const speed = Math.hypot(vx, vy);\n this.#reset();\n this.#handlers.onEnd?.({\n x,\n y,\n dx: x - start.x,\n dy: y - start.y,\n dt: t - start.t,\n vx,\n vy,\n swipe: !canceled && speed > SWIPE_VELOCITY,\n canceled,\n });\n }\n\n #reset(): void {\n this.#activeId = null;\n this.#start = null;\n this.#prev = null;\n this.#last = null;\n }\n}\n\n/**\n * Wire real PointerEvents from `target` into a recognizer. Returns an unbind\n * function that removes every listener. Coordinates are passed through as\n * client-space; mapping to page-local space is the engine's job.\n */\nexport function bindPointerInput(target: EventTarget, recognizer: PointerRecognizer): () => void {\n const down = (e: Event): void => {\n const p = e as PointerEvent;\n recognizer.down(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n };\n const move = (e: Event): void => {\n const p = e as PointerEvent;\n recognizer.move(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n };\n const up = (e: Event): void => {\n const p = e as PointerEvent;\n recognizer.up(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n };\n const cancel = (e: Event): void => {\n const p = e as PointerEvent;\n recognizer.cancel(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n };\n target.addEventListener('pointerdown', down);\n target.addEventListener('pointermove', move);\n target.addEventListener('pointerup', up);\n target.addEventListener('pointercancel', cancel);\n return () => {\n target.removeEventListener('pointerdown', down);\n target.removeEventListener('pointermove', move);\n target.removeEventListener('pointerup', up);\n target.removeEventListener('pointercancel', cancel);\n };\n}\n\n/**\n * Wire PointerEvents from `target` into a drag recognizer and/or a pinch\n * recognizer at once, so a single pointer stream feeds both. Returns an unbind.\n */\nexport function bindGestures(\n target: EventTarget,\n recognizers: { pointer?: PointerRecognizer; pinch?: PinchRecognizer },\n): () => void {\n const { pointer, pinch } = recognizers;\n const down = (e: Event): void => {\n const p = e as PointerEvent;\n pointer?.down(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n pinch?.down(p.pointerId, p.clientX, p.clientY);\n };\n const move = (e: Event): void => {\n const p = e as PointerEvent;\n pointer?.move(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n pinch?.move(p.pointerId, p.clientX, p.clientY);\n };\n const up = (e: Event): void => {\n const p = e as PointerEvent;\n pointer?.up(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n pinch?.up(p.pointerId);\n };\n const cancel = (e: Event): void => {\n const p = e as PointerEvent;\n pointer?.cancel(p.pointerId, p.clientX, p.clientY, p.timeStamp);\n pinch?.cancel(p.pointerId);\n };\n target.addEventListener('pointerdown', down);\n target.addEventListener('pointermove', move);\n target.addEventListener('pointerup', up);\n target.addEventListener('pointercancel', cancel);\n return () => {\n target.removeEventListener('pointerdown', down);\n target.removeEventListener('pointermove', move);\n target.removeEventListener('pointerup', up);\n target.removeEventListener('pointercancel', cancel);\n };\n}\n\nexport interface PinchStart {\n centerX: number;\n centerY: number;\n /** Distance between the two pointers at pinch start. */\n distance: number;\n}\n\nexport interface PinchMove {\n centerX: number;\n centerY: number;\n /** Current distance / start distance. */\n scale: number;\n}\n\nexport interface PinchHandlers {\n onPinchStart?: (g: PinchStart) => void;\n onPinchMove?: (g: PinchMove) => void;\n onPinchEnd?: () => void;\n}\n\ninterface Pt {\n x: number;\n y: number;\n}\n\nfunction distance(a: Pt, b: Pt): number {\n return Math.hypot(a.x - b.x, a.y - b.y);\n}\n\n/**\n * Recognizes a two-pointer pinch and reports zoom scale (relative to the start\n * distance) and the live center point. No timestamps: pinch drives zoom, not a\n * flick. A third pointer is ignored; lifting either tracked pointer ends the pinch.\n * Coordinating this with the drag recognizer (a second finger should suspend a\n * single-finger drag) is the engine's job.\n */\nexport class PinchRecognizer {\n #handlers: PinchHandlers;\n #points = new Map<number, Pt>();\n #startDistance = 0;\n #pinching = false;\n\n constructor(handlers: PinchHandlers = {}) {\n this.#handlers = handlers;\n }\n\n get pinching(): boolean {\n return this.#pinching;\n }\n\n down(id: number, x: number, y: number): void {\n if (this.#points.has(id) || this.#points.size >= 2) return; // track only two\n this.#points.set(id, { x, y });\n if (this.#points.size === 2) {\n const { a, b } = this.#pair();\n this.#startDistance = distance(a, b);\n this.#pinching = true;\n this.#handlers.onPinchStart?.({\n centerX: (a.x + b.x) / 2,\n centerY: (a.y + b.y) / 2,\n distance: this.#startDistance,\n });\n }\n }\n\n move(id: number, x: number, y: number): void {\n const p = this.#points.get(id);\n if (!p) return;\n p.x = x;\n p.y = y;\n if (!this.#pinching) return;\n const { a, b } = this.#pair();\n this.#handlers.onPinchMove?.({\n centerX: (a.x + b.x) / 2,\n centerY: (a.y + b.y) / 2,\n scale: this.#startDistance > 0 ? distance(a, b) / this.#startDistance : 1,\n });\n }\n\n up(id: number): void {\n this.#remove(id);\n }\n\n cancel(id: number): void {\n this.#remove(id);\n }\n\n #remove(id: number): void {\n if (!this.#points.delete(id)) return;\n if (this.#pinching && this.#points.size < 2) {\n this.#pinching = false;\n this.#startDistance = 0;\n this.#handlers.onPinchEnd?.();\n }\n }\n\n #pair(): { a: Pt; b: Pt } {\n const values = [...this.#points.values()];\n const a = values[0];\n const b = values[1];\n if (!a || !b) throw new Error('pinch requires two active pointers');\n return { a, b };\n }\n}\n","import type { PageRect, Vec2 } from './foldFromPointer';\n\nexport type HitRegion = 'corner' | 'page' | 'outside';\n\n/**\n * Classify a page-local point into a hit region.\n *\n * Coordinates are page-local (see `foldFromPointer`): (0,0) top-left,\n * page spanning (0,0)..(width,height). `cornerSize` is the width/height of each\n * grabbable corner box. All four corners report `corner`; the engine decides\n * which are actually grabbable for the current spread layout (LTR/RTL, cover).\n */\nexport function hitTest(point: Vec2, pageRect: PageRect, cornerSize: number): HitRegion {\n const { width, height } = pageRect;\n if (point.x < 0 || point.x > width || point.y < 0 || point.y > height) {\n return 'outside';\n }\n const nearX = point.x <= cornerSize || point.x >= width - cornerSize;\n const nearY = point.y <= cornerSize || point.y >= height - cornerSize;\n return nearX && nearY ? 'corner' : 'page';\n}\n","import type { Renderer } from './types';\n\nexport type RendererKind = 'css' | 'webgl2';\n\n/**\n * How the consumer chooses a renderer:\n * - `'auto'` (default): prefer the GPU path (WebGL2), else CSS.\n * - a single kind: force it (`'webgl2'` throws if the device has no WebGL2).\n * - an ordered array: try each in turn, first viable wins.\n * - a `Renderer` instance: use it as-is (custom implementation, §6.1/§11).\n */\nexport type RendererOption = 'auto' | RendererKind | RendererKind[] | Renderer;\n\nexport interface Capabilities {\n /** WebGL2 available — the GPU renderer's only backend. */\n gpu: boolean;\n}\n\ntype RendererLoader = () => Promise<Renderer>;\n\n/**\n * Lazy loaders, one per kind. Each is a dynamic `import()` so the bundler\n * code-splits it into its own chunk and a device fetches only the one selected.\n */\nconst LOADERS: Partial<Record<RendererKind, RendererLoader>> = {\n css: async () => new (await import('./cssRenderer')).CssRenderer(),\n webgl2: async () => new (await import('./webglRenderer')).WebglRenderer(),\n};\n\n// The WebGL2 renderer is auto-selected on GPU-capable devices; CSS is the baseline\n// for non-GPU devices and the fallback for unrecoverable GPU failures (§8.4).\nconst WEBGL2_AUTO = true;\n\nfunction isRenderer(option: RendererOption): option is Renderer {\n return (\n typeof option === 'object' &&\n option !== null &&\n !Array.isArray(option) &&\n typeof (option as Renderer).mount === 'function'\n );\n}\n\nfunction autoOrder(caps: Capabilities): RendererKind[] {\n return caps.gpu && WEBGL2_AUTO ? ['webgl2', 'css'] : ['css'];\n}\n\n/** Detect WebGL2 support — the GPU renderer's only backend (no WebGL1, no WebGPU). */\nexport function detectCapabilities(): Capabilities {\n return { gpu: hasWebGl2() };\n}\n\nfunction hasWebGl2(): boolean {\n try {\n return Boolean(document.createElement('canvas').getContext('webgl2'));\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the `renderer` option to a mounted renderer instance, lazy-loading its\n * chunk. Throws an agent-actionable error if nothing viable can be loaded.\n */\nexport async function selectRenderer(\n option: RendererOption = 'auto',\n caps: Capabilities = detectCapabilities(),\n): Promise<Renderer> {\n if (isRenderer(option)) return option;\n\n const order = Array.isArray(option) ? option : option === 'auto' ? autoOrder(caps) : [option];\n\n for (const kind of order) {\n if (kind === 'webgl2' && !caps.gpu) continue; // skip the GPU path without WebGL2\n const loader = LOADERS[kind];\n if (loader) return loader();\n }\n\n if (order.length === 1 && order[0] === 'webgl2' && !caps.gpu) {\n throw new Error(\n \"renderer: 'webgl2' was forced but this device has no WebGL2. Use 'css' or 'auto'.\",\n );\n }\n throw new Error(\n `renderer: none of [${order.join(', ')}] could be loaded — use 'css' or 'auto'.`,\n );\n}\n","import { buildSpreads, shouldSinglePage, type Direction, type Spread } from './engine/spread';\nimport { Virtualizer } from './engine/virtualizer';\nimport { Emitter, type ZineEventMap } from './engine/emitter';\nimport { FlipMachine } from './engine/stateMachine';\nimport { PointerRecognizer, PinchRecognizer, bindGestures, type GestureEnd } from './engine/input';\nimport { hitTest } from './geometry/hitTest';\nimport { selectRenderer, type RendererOption } from './renderer/select';\nimport type { FlipDirection, PageContent, Renderer, SpreadContent } from './renderer/types';\nimport type { Source } from './source/types';\n\n/** Grab-zone size as a fraction of the smaller container dimension. */\nconst CORNER_FRACTION = 0.25;\n\n/** Wheel-zoom sensitivity: scale multiplies by exp(-deltaY * this) per wheel event. */\nconst WHEEL_ZOOM_SENSITIVITY = 0.0015;\n\n/** Pointer movement (px) beyond which a press becomes a drag rather than a tap. */\nconst DRAG_THRESHOLD = 6;\n\n/** Window (ms) a single click waits to rule out a double-click before flipping. */\nconst DOUBLE_CLICK_MS = 250;\n\ninterface DragState {\n direction: FlipDirection;\n targetIndex: number;\n toPage: number;\n toContent: SpreadContent;\n width: number;\n t: number;\n}\n\nexport interface ZoomOptions {\n /** Whether zooming is allowed at all; default true. */\n enabled?: boolean;\n /** Maximum zoom scale; default 4. */\n max?: number;\n /** Zoom on Ctrl/⌘ + wheel (also how trackpad pinch arrives on desktop); default true. */\n wheel?: boolean;\n /** Zoom levels cycled by double-click (wraps to the first); `false` disables. Default [1, 2, 4]. */\n doubleClick?: number[] | false;\n /**\n * Listen for double-click zoom inside the click-to-flip zones. Default: off in 'edge' mode\n * (edges flip instantly, no delay), on in 'half' mode. Turning it on in 'edge' mode makes\n * clicks wait out `clickFlipDelay` so a double-click can preempt them with a zoom.\n */\n doubleClickInFlipZone?: boolean;\n}\n\nexport interface ZineOptions {\n /** Content source (e.g. an ImageSource). Required. */\n source: Source;\n /** Renderer selection; default 'auto'. */\n renderer?: RendererOption;\n /** Fixed container width in px; omit to let the container/CSS drive the size. */\n width?: number;\n /** Fixed container height in px; omit to let the container/CSS drive the size. */\n height?: number;\n /** Reading direction; default 'ltr'. */\n direction?: Direction;\n /** First page is a lone cover; default false. */\n cover?: boolean;\n /** Page to open on; default 0. */\n startPage?: number;\n /** Flip animation duration in ms; default 500. */\n flipDuration?: number;\n /** How a tap/click turns pages: near an edge ('edge'), by page half ('half'), or 'off'. Default 'edge'. */\n clickToFlip?: 'edge' | 'half' | 'off';\n /** Edge-zone size in px per side, used when clickToFlip is 'edge'. Default 64. */\n clickZoneSize?: number;\n /**\n * Delay (ms) a click waits before flipping, so a double-click can preempt it with a zoom.\n * Omit for auto: 0 when double-click zoom is inactive, 250 when it's active.\n */\n clickFlipDelay?: number;\n /** Zoom behavior. */\n zoom?: ZoomOptions;\n /** Container widths below this (px) switch to one page per spread; default 640. */\n singlePageThreshold?: number;\n}\n\n/**\n * The public flipbook. Composes the engine (spreads, virtualizer, events), a\n * lazily-selected renderer, and a content source. Construction is synchronous\n * but setup is async — await `ready` (or listen for the `ready` event) before\n * driving it.\n */\nexport class Zine {\n #container: HTMLElement;\n #source: Source;\n #emitter = new Emitter<ZineEventMap>();\n #virtualizer = new Virtualizer(2);\n #machine = new FlipMachine();\n #direction: Direction;\n #cover: boolean;\n #clickToFlip: 'edge' | 'half' | 'off';\n #clickZoneSize: number;\n #honorDoubleClickInFlipZone: boolean;\n #clickFlipDelayValue: number;\n #singlePageThreshold: number;\n #singlePage = false;\n #startPageOption: number | undefined;\n #spreads: Spread[] = [];\n #current = 0;\n #currentPage = 0;\n #currentContent: SpreadContent = { left: null, right: null };\n #flipDuration: number;\n #zoomEnabled: boolean;\n #wheelZoom: boolean;\n #doubleClickLevels: number[] | null;\n #maxZoom: number;\n #resizeObserver: ResizeObserver | null = null;\n #updateScheduled = false;\n #scale = 1;\n #tx = 0;\n #ty = 0;\n #renderer: Renderer | null = null;\n #fellBack = false;\n #destroyed = false;\n #raf: number | null = null;\n #drag: DragState | null = null;\n #pendingGrab: { direction: FlipDirection; targetIndex: number; toPage: number; width: number } | null = null;\n #press: { x: number; y: number } | null = null;\n #pendingClickTimer: ReturnType<typeof setTimeout> | null = null;\n #pan: { baseTx: number; baseTy: number } | null = null;\n #pinching = false;\n #pinchBaseScale = 1;\n #reducedMotion = false;\n #liveRegion: HTMLElement | null = null;\n #unbindInput: (() => void) | null = null;\n #unbindWheel: (() => void) | null = null;\n #unbindDblClick: (() => void) | null = null;\n #a11yCleanup: (() => void) | null = null;\n #ready: Promise<void>;\n\n /** Stable seek API: drive the fold to a fixed progress without animating (visual regression). */\n readonly debug = {\n setFlipProgress: (t: number, direction: FlipDirection): void => {\n this.#renderer?.setFlipProgress(t, direction);\n },\n };\n\n constructor(container: HTMLElement, options: ZineOptions) {\n validateOptions(container, options);\n this.#container = container;\n if (options.width !== undefined) container.style.width = `${options.width}px`;\n if (options.height !== undefined) container.style.height = `${options.height}px`;\n this.#source = options.source;\n const direction = options.direction ?? 'ltr';\n const cover = options.cover ?? false;\n this.#direction = direction;\n this.#cover = cover;\n this.#clickToFlip = options.clickToFlip ?? 'edge';\n this.#clickZoneSize = options.clickZoneSize ?? 64;\n this.#singlePageThreshold = options.singlePageThreshold ?? 640;\n this.#startPageOption = options.startPage;\n // Sync sources (a known page count) build spreads now — so bad pageCount/startPage\n // throw immediately from `new Zine`. Async sources (an `open()`) defer to #init.\n if (typeof this.#source.open !== 'function') {\n this.#buildSpreadModel();\n }\n this.#flipDuration = options.flipDuration ?? 800;\n this.#zoomEnabled = options.zoom?.enabled ?? true;\n this.#wheelZoom = options.zoom?.wheel ?? true;\n const dbl = options.zoom?.doubleClick;\n const levels = dbl === false ? [] : [...(dbl ?? [1, 2, 4])].sort((a, b) => a - b);\n this.#doubleClickLevels = levels.length > 0 ? levels : null;\n this.#maxZoom = options.zoom?.max ?? 4;\n\n // Arbitrate click-to-flip vs double-click zoom inside the flip zones.\n // Listen for double-click zoom in flip zones: default off in 'edge', on in 'half'.\n // clickFlipDelay === 0 hard-disables it. When honored, a delay lets a click wait\n // out a possible double-click; auto default 250 ms, overridable.\n const doubleClickZoomActive = this.#zoomEnabled && this.#doubleClickLevels !== null;\n const listenInFlipZone = options.zoom?.doubleClickInFlipZone ?? (this.#clickToFlip === 'half');\n this.#honorDoubleClickInFlipZone =\n this.#clickToFlip !== 'off' &&\n doubleClickZoomActive &&\n listenInFlipZone &&\n options.clickFlipDelay !== 0;\n this.#clickFlipDelayValue = this.#honorDoubleClickInFlipZone\n ? (options.clickFlipDelay ?? DOUBLE_CLICK_MS)\n : 0;\n\n this.#ready = this.#init(options.renderer ?? 'auto');\n }\n\n /** Resolves once the renderer is mounted and the first spread is painted. */\n get ready(): Promise<void> {\n return this.#ready;\n }\n\n getPageCount(): number {\n return this.#source.pageCount;\n }\n\n getPage(): number {\n return this.#currentPage;\n }\n\n flipNext(): void {\n this.#startFlip(this.#current + 1);\n }\n\n flipPrev(): void {\n this.#startFlip(this.#current - 1);\n }\n\n flipTo(page: number): void {\n const target = clamp(page, 0, Math.max(0, this.#source.pageCount - 1));\n this.#startFlip(this.#spreadIndexForPage(target));\n }\n\n getZoom(): number {\n return this.#scale;\n }\n\n /** Zoom to `scale` (clamped to [1, zoom.max]), keeping `center` (container-local) fixed. */\n setZoom(scale: number, center?: { x: number; y: number }): void {\n if (!this.#renderer || !this.#zoomEnabled) return;\n const s2 = clamp(scale, 1, this.#maxZoom);\n const m = this.#renderer.measure();\n const focal = center ?? { x: m.containerWidth / 2, y: m.containerHeight / 2 };\n const s1 = this.#scale;\n // Solve for the translate that keeps the focal screen point fixed as s1→s2.\n let tx = s2 === 1 ? 0 : focal.x - (s2 / s1) * (focal.x - this.#tx);\n let ty = s2 === 1 ? 0 : focal.y - (s2 / s1) * (focal.y - this.#ty);\n [tx, ty] = this.#clampPan(tx, ty, s2, m.containerWidth, m.containerHeight);\n this.#scale = s2;\n this.#tx = tx;\n this.#ty = ty;\n this.#renderer.setViewTransform(s2, tx, ty);\n this.#emitter.emit('zoomChanged', { scale: s2 });\n }\n\n resetZoom(): void {\n this.setZoom(1);\n }\n\n #clampPan(tx: number, ty: number, scale: number, w: number, h: number): [number, number] {\n // Keep the scaled content covering the viewport (transform-origin is 0,0).\n return [clamp(tx, w * (1 - scale), 0), clamp(ty, h * (1 - scale), 0)];\n }\n\n on<K extends keyof ZineEventMap>(\n event: K,\n listener: (payload: ZineEventMap[K]) => void,\n ): () => void {\n return this.#emitter.on(event, listener);\n }\n\n destroy(): void {\n this.#destroyed = true;\n if (this.#raf !== null) cancelAnimationFrame(this.#raf);\n this.#clearPendingClickFlip();\n this.#resizeObserver?.disconnect();\n this.#a11yCleanup?.();\n this.#unbindWheel?.();\n this.#unbindDblClick?.();\n this.#unbindInput?.();\n this.#renderer?.destroy();\n this.#renderer = null;\n this.#source.destroy();\n this.#emitter.clear();\n }\n\n #startFlip(targetIndex: number): void {\n if (targetIndex < 0 || targetIndex >= this.#spreads.length || targetIndex === this.#current) {\n return;\n }\n // `flip` is legal only from idle; otherwise a flip/drag is already in flight.\n if (this.#machine.send('flip') === null) return;\n\n const direction: FlipDirection = targetIndex > this.#current ? 'forward' : 'backward';\n const toPage = this.#leadPage(targetIndex);\n // State is already locked (send('flip') above), so re-entrant flips are rejected\n // even though staging the destination content below is async.\n this.#emitter.emit('flipStart', { from: this.#currentPage, to: toPage });\n void this.#runFlip(targetIndex, direction, toPage);\n }\n\n async #runFlip(targetIndex: number, direction: FlipDirection, toPage: number): Promise<void> {\n const toSpread = this.#spreads[targetIndex];\n const toContent: SpreadContent = toSpread\n ? await this.#resolveContent(toSpread)\n : { left: null, right: null };\n this.#renderer?.beginFlip(this.#currentContent, toContent, direction, {\n fill: this.#singlePage,\n });\n this.#animateProgress(0, 1, direction, () => this.#commit(targetIndex, toPage, toContent));\n }\n\n /** Animate the fold from `fromT` to `toT`, easing per-frame, then run `onDone`. */\n #animateProgress(\n fromT: number,\n toT: number,\n direction: FlipDirection,\n onDone: () => void,\n ): void {\n const duration = this.#effectiveDuration() * Math.abs(toT - fromT);\n if (duration <= 0) {\n // Reduced motion (or zero-duration): swap without the curl sweep.\n this.#renderer?.setFlipProgress(toT, direction);\n onDone();\n return;\n }\n const start = performance.now();\n const step = (now: number): void => {\n // Easing lives here; flipProgressToPose stays linear so seeks are deterministic.\n const raw = Math.min(1, (now - start) / duration);\n this.#renderer?.setFlipProgress(fromT + (toT - fromT) * easeInOutCubic(raw), direction);\n if (raw < 1) {\n this.#raf = requestAnimationFrame(step);\n } else {\n this.#raf = null;\n onDone();\n }\n };\n this.#raf = requestAnimationFrame(step);\n }\n\n #commit(targetIndex: number, toPage: number, toContent: SpreadContent): void {\n const spread = this.#spreads[targetIndex];\n this.#current = targetIndex;\n this.#currentPage = toPage;\n this.#currentContent = toContent;\n // renderSpread paints the landed spread and clears the turning leaf.\n if (spread) this.#paintSpread(spread, toContent);\n this.#prefetchWindow();\n this.#machine.send('settle');\n this.#announce();\n this.#emitter.emit('pageChanged', { page: toPage });\n this.#emitter.emit('flipEnd', { page: toPage });\n }\n\n #leadPage(spreadIndex: number): number {\n const spread = this.#spreads[spreadIndex];\n if (!spread) return this.#currentPage;\n const pages = [spread.left, spread.right].filter((p): p is number => p !== null);\n return pages.length > 0 ? Math.min(...pages) : this.#currentPage;\n }\n\n #onDragStart(clientX: number, clientY: number): void {\n if (this.#pinching || !this.#renderer || this.#machine.state !== 'idle') return;\n this.#clearPendingClickFlip(); // a new press cancels a click-flip still waiting out its window\n // Zoomed in → a drag pans; at scale 1 → a corner drag flips (§9 mode switch).\n if (this.#scale > 1) {\n if (this.#machine.send('panStart') === null) return;\n this.#pan = { baseTx: this.#tx, baseTy: this.#ty };\n return;\n }\n const rect = this.#container.getBoundingClientRect();\n const point = { x: clientX - rect.left, y: clientY - rect.top };\n this.#press = point; // remembered for tap classification / click-to-flip zone\n const { containerWidth, containerHeight } = this.#renderer.measure();\n const cornerSize = Math.min(containerWidth, containerHeight) * CORNER_FRACTION;\n if (hitTest(point, { width: containerWidth, height: containerHeight }, cornerSize) !== 'corner') {\n return; // not a corner — no drag; a tap here may still click-to-flip on release\n }\n // Arm a potential drag; it only becomes a real flip once the pointer moves\n // (so a corner *tap* never fires a spurious flipStart). Right-side corner\n // turns the page forward in LTR; RTL mirrors it.\n const rightSide = point.x > containerWidth / 2;\n const forward = this.#direction === 'rtl' ? !rightSide : rightSide;\n const targetIndex = this.#current + (forward ? 1 : -1);\n if (targetIndex < 0 || targetIndex >= this.#spreads.length) return;\n this.#pendingGrab = {\n direction: forward ? 'forward' : 'backward',\n targetIndex,\n toPage: this.#leadPage(targetIndex),\n width: containerWidth,\n };\n }\n\n /** Turn an armed corner press into a live drag flip once the pointer has moved. */\n #promoteGrab(): void {\n const grab = this.#pendingGrab;\n if (!grab) return;\n this.#pendingGrab = null;\n if (this.#machine.send('grab') === null) return;\n this.#drag = {\n direction: grab.direction,\n targetIndex: grab.targetIndex,\n toPage: grab.toPage,\n toContent: { left: null, right: null },\n width: grab.width,\n t: 0,\n };\n this.#emitter.emit('flipStart', { from: this.#currentPage, to: grab.toPage });\n void this.#stageDrag(grab.targetIndex, grab.direction);\n }\n\n async #stageDrag(targetIndex: number, direction: FlipDirection): Promise<void> {\n const toSpread = this.#spreads[targetIndex];\n const toContent: SpreadContent = toSpread\n ? await this.#resolveContent(toSpread)\n : { left: null, right: null };\n // The drag may have ended (or retargeted) while content was resolving.\n if (this.#drag?.targetIndex !== targetIndex) return;\n this.#drag.toContent = toContent;\n this.#renderer?.beginFlip(this.#currentContent, toContent, direction, {\n fill: this.#singlePage,\n });\n this.#renderer?.setFlipProgress(this.#drag.t, direction);\n }\n\n #onDragMove(dx: number, dy: number): void {\n if (this.#pinching) return;\n if (this.#pan) {\n if (!this.#renderer) return;\n const m = this.#renderer.measure();\n const [tx, ty] = this.#clampPan(\n this.#pan.baseTx + dx,\n this.#pan.baseTy + dy,\n this.#scale,\n m.containerWidth,\n m.containerHeight,\n );\n this.#tx = tx;\n this.#ty = ty;\n this.#renderer.setViewTransform(this.#scale, tx, ty);\n return;\n }\n // An armed corner press becomes a real flip once it moves past the threshold.\n if (this.#pendingGrab && Math.hypot(dx, dy) > DRAG_THRESHOLD) {\n this.#promoteGrab();\n }\n const drag = this.#drag;\n if (!drag) return;\n const signed = drag.direction === 'forward' ? -dx : dx;\n drag.t = clamp(signed / drag.width, 0, 1);\n this.#renderer?.setFlipProgress(drag.t, drag.direction);\n }\n\n #onDragEnd(gesture: GestureEnd): void {\n if (this.#pinching) return;\n if (this.#pan) {\n this.#pan = null;\n this.#machine.send('panEnd');\n return;\n }\n const drag = this.#drag;\n if (!drag) {\n // Never became a drag → it was a tap; maybe flip via clickToFlip.\n this.#pendingGrab = null;\n this.#maybeClickFlip(gesture);\n return;\n }\n this.#drag = null;\n this.#machine.send('release');\n\n const swiped =\n gesture.swipe &&\n (drag.direction === 'forward' ? gesture.dx < 0 : gesture.dx > 0);\n if (drag.t >= 0.5 || swiped) {\n this.#animateProgress(drag.t, 1, drag.direction, () =>\n this.#commit(drag.targetIndex, drag.toPage, drag.toContent),\n );\n } else {\n this.#animateProgress(drag.t, 0, drag.direction, () => this.#cancelFlip());\n }\n }\n\n #maybeClickFlip(gesture: GestureEnd): void {\n if (this.#clickToFlip === 'off' || this.#scale > 1 || !this.#press) return;\n // A tap barely moves; anything more was a drag we already ignored.\n if (Math.abs(gesture.dx) > DRAG_THRESHOLD || Math.abs(gesture.dy) > DRAG_THRESHOLD) return;\n const direction = this.#clickFlipDirection(this.#press);\n if (!direction) return;\n const targetIndex = this.#current + (direction === 'forward' ? 1 : -1);\n if (targetIndex < 0 || targetIndex >= this.#spreads.length) return;\n this.#clearPendingClickFlip();\n if (this.#clickFlipDelayValue <= 0) {\n // No double-click competing here → flip right away.\n this.#startFlip(targetIndex);\n return;\n }\n // Wait out the window; a double-click (zoom) cancels this.\n this.#pendingClickTimer = setTimeout(() => {\n this.#pendingClickTimer = null;\n this.#startFlip(targetIndex);\n }, this.#clickFlipDelayValue);\n }\n\n /** Which way a tap at `point` (container-local) turns the page, or null for a dead zone. */\n #clickFlipDirection(point: { x: number; y: number }): FlipDirection | null {\n if (!this.#renderer) return null;\n const { containerWidth } = this.#renderer.measure();\n let side: 'left' | 'right' | null;\n if (this.#clickToFlip === 'half') {\n side = point.x > containerWidth / 2 ? 'right' : 'left';\n } else if (point.x <= this.#clickZoneSize) {\n side = 'left';\n } else if (point.x >= containerWidth - this.#clickZoneSize) {\n side = 'right';\n } else {\n side = null; // center dead zone in 'edge' mode\n }\n if (!side) return null;\n const forwardSide = this.#direction === 'rtl' ? 'left' : 'right';\n return side === forwardSide ? 'forward' : 'backward';\n }\n\n #clearPendingClickFlip(): void {\n if (this.#pendingClickTimer !== null) {\n clearTimeout(this.#pendingClickTimer);\n this.#pendingClickTimer = null;\n }\n }\n\n #cancelFlip(): void {\n const spread = this.#spreads[this.#current];\n if (spread) this.#paintSpread(spread, this.#currentContent);\n this.#machine.send('settle');\n this.#emitter.emit('flipEnd', { page: this.#currentPage });\n }\n\n #onPinchStart(): void {\n // A second finger abandons any single-pointer gesture in flight.\n this.#pendingGrab = null;\n this.#clearPendingClickFlip();\n if (this.#drag) {\n this.#drag = null;\n this.#machine.send('release');\n this.#machine.send('settle');\n const spread = this.#spreads[this.#current];\n if (spread) this.#paintSpread(spread, this.#currentContent);\n } else if (this.#pan) {\n this.#pan = null;\n this.#machine.send('panEnd');\n }\n this.#pinching = true;\n this.#pinchBaseScale = this.#scale;\n }\n\n #onPinchMove(centerX: number, centerY: number, scale: number): void {\n if (!this.#pinching) return;\n const rect = this.#container.getBoundingClientRect();\n this.setZoom(this.#pinchBaseScale * scale, { x: centerX - rect.left, y: centerY - rect.top });\n }\n\n #onPinchEnd(): void {\n this.#pinching = false;\n }\n\n #bindWheelZoom(): void {\n const onWheel = (event: WheelEvent): void => {\n // Ctrl/⌘ + wheel only — this is also what a desktop trackpad pinch emits.\n if (!this.#wheelZoom || !this.#zoomEnabled || !(event.ctrlKey || event.metaKey)) return;\n // Override the browser's native page zoom.\n event.preventDefault();\n const rect = this.#container.getBoundingClientRect();\n const factor = Math.exp(-event.deltaY * WHEEL_ZOOM_SENSITIVITY);\n this.setZoom(this.#scale * factor, {\n x: event.clientX - rect.left,\n y: event.clientY - rect.top,\n });\n };\n this.#container.addEventListener('wheel', onWheel as EventListener, { passive: false });\n this.#unbindWheel = () =>\n this.#container.removeEventListener('wheel', onWheel as EventListener);\n }\n\n #bindDoubleClickZoom(): void {\n const onDoubleClick = (event: MouseEvent): void => {\n const levels = this.#doubleClickLevels;\n if (!this.#zoomEnabled || levels === null) return;\n // At scale 1 a double-click inside a flip zone belongs to click-to-flip, not zoom,\n // unless we're honoring double-click zoom there.\n if (this.#scale <= 1 && this.#clickToFlip !== 'off' && !this.#honorDoubleClickInFlipZone) {\n const rect = this.#container.getBoundingClientRect();\n const local = { x: event.clientX - rect.left, y: event.clientY - rect.top };\n if (this.#clickFlipDirection(local) !== null) return; // in a flip zone → leave it to click-to-flip\n }\n // A double-click means the single-click flip we may have queued was really a zoom.\n this.#clearPendingClickFlip();\n event.preventDefault();\n // Next configured level above the current scale, else wrap to the first.\n const next = levels.find((l) => l > this.#scale + 1e-6) ?? levels[0] ?? this.#scale;\n const rect = this.#container.getBoundingClientRect();\n this.setZoom(next, { x: event.clientX - rect.left, y: event.clientY - rect.top });\n };\n this.#container.addEventListener('dblclick', onDoubleClick as EventListener);\n this.#unbindDblClick = () =>\n this.#container.removeEventListener('dblclick', onDoubleClick as EventListener);\n }\n\n async #init(rendererOption: RendererOption): Promise<void> {\n // Load the renderer chunk while opening an async source (e.g. a PDF) in parallel.\n const rendererPromise = selectRenderer(rendererOption);\n if (typeof this.#source.open === 'function') {\n await this.#source.open();\n this.#buildSpreadModel(); // page count known now; validates + builds spreads\n }\n this.#source.prefetch([this.#currentPage]);\n\n const selected = await rendererPromise;\n const renderer = await this.#mountWithFallback(selected);\n this.#renderer = renderer;\n\n // Now that we can measure, apply single-page mode if the container is narrow.\n this.#applySinglePage(renderer.measure().containerWidth);\n\n const pointer = new PointerRecognizer({\n onStart: (g) => this.#onDragStart(g.x, g.y),\n onMove: (g) => this.#onDragMove(g.dx, g.dy),\n onEnd: (g) => this.#onDragEnd(g),\n });\n const pinch = new PinchRecognizer({\n onPinchStart: () => this.#onPinchStart(),\n onPinchMove: (g) => this.#onPinchMove(g.centerX, g.centerY, g.scale),\n onPinchEnd: () => this.#onPinchEnd(),\n });\n this.#unbindInput = bindGestures(this.#container, { pointer, pinch });\n this.#bindWheelZoom();\n this.#bindDoubleClickZoom();\n this.#setupReducedMotion();\n this.#a11yCleanup = this.#setupA11y();\n\n await this.#renderCurrent();\n this.#observeResize();\n this.#prefetchWindow();\n this.#announce();\n this.#emitter.emit('ready');\n }\n\n /** Mount `renderer`; if it can't initialize (e.g. no WebGL2 context) fall back to CSS. */\n async #mountWithFallback(renderer: Renderer): Promise<Renderer> {\n try {\n await renderer.mount(this.#container);\n renderer.onFatal?.(() => {\n void this.#fallbackToCss();\n });\n return renderer;\n } catch {\n renderer.destroy();\n const css = await selectRenderer('css');\n await css.mount(this.#container);\n this.#fellBack = true;\n this.#emitter.emit('rendererFallback', { from: 'webgl2', to: 'css' });\n return css;\n }\n }\n\n /** Runtime fallback: a live renderer signaled an unrecoverable failure (§8.4). */\n async #fallbackToCss(): Promise<void> {\n if (this.#fellBack || this.#destroyed) return;\n this.#fellBack = true;\n this.#renderer?.destroy();\n this.#renderer = null;\n const css = await selectRenderer('css');\n if (this.#destroyed) {\n css.destroy();\n return;\n }\n await css.mount(this.#container);\n this.#renderer = css;\n this.#applySinglePage(css.measure().containerWidth);\n await this.#renderCurrent();\n this.#emitter.emit('rendererFallback', { from: 'webgl2', to: 'css' });\n }\n\n #setupReducedMotion(): void {\n if (typeof matchMedia !== 'function') return;\n const query = matchMedia('(prefers-reduced-motion: reduce)');\n this.#reducedMotion = query.matches;\n query.addEventListener?.('change', (e) => {\n this.#reducedMotion = e.matches;\n });\n }\n\n /** Wire keyboard nav + an aria-live page announcer. Skips non-DOM containers. */\n #setupA11y(): (() => void) | null {\n const container = this.#container;\n const doc = container.ownerDocument;\n if (!doc || typeof container.setAttribute !== 'function') return null;\n\n if (!container.hasAttribute('tabindex')) container.tabIndex = 0;\n container.setAttribute('aria-roledescription', 'flipbook');\n const onKey = (e: KeyboardEvent): void => this.#onKeyDown(e);\n container.addEventListener('keydown', onKey);\n\n const live = doc.createElement('div');\n live.setAttribute('aria-live', 'polite');\n live.setAttribute('aria-atomic', 'true');\n live.style.cssText =\n 'position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;';\n container.appendChild(live);\n this.#liveRegion = live;\n\n return () => {\n container.removeEventListener('keydown', onKey);\n live.remove();\n this.#liveRegion = null;\n };\n }\n\n #onKeyDown(event: KeyboardEvent): void {\n const forwardKey = this.#direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight';\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowLeft':\n if (event.key === forwardKey) this.flipNext();\n else this.flipPrev();\n event.preventDefault();\n break;\n case 'Home':\n this.flipTo(0);\n event.preventDefault();\n break;\n case 'End':\n this.flipTo(this.#source.pageCount - 1);\n event.preventDefault();\n break;\n default:\n break;\n }\n }\n\n #announce(): void {\n if (this.#liveRegion) {\n this.#liveRegion.textContent = `Page ${this.#currentPage + 1} of ${this.#source.pageCount}`;\n }\n }\n\n #effectiveDuration(): number {\n return this.#reducedMotion ? 0 : this.#flipDuration;\n }\n\n /** Re-measure and re-render; call after the container resizes. */\n update(): void {\n if (!this.#renderer || this.#machine.state !== 'idle') return;\n this.#applySinglePage(this.#renderer.measure().containerWidth);\n void this.#renderCurrent();\n }\n\n #applySinglePage(containerWidth: number): void {\n const single = shouldSinglePage(containerWidth, this.#singlePageThreshold);\n if (single === this.#singlePage) return;\n this.#singlePage = single;\n this.#spreads = buildSpreads(this.#source.pageCount, {\n direction: this.#direction,\n cover: this.#cover,\n singlePage: single,\n });\n this.#current = this.#spreadIndexForPage(this.#currentPage);\n }\n\n async #renderCurrent(): Promise<void> {\n const spread = this.#spreads[this.#current];\n if (!spread || !this.#renderer) return;\n this.#currentContent = await this.#resolveContent(spread);\n this.#paintSpread(spread, this.#currentContent);\n }\n\n #paintSpread(spread: Spread, content: SpreadContent): void {\n this.#renderer?.renderSpread(spread, content, { fill: this.#singlePage });\n }\n\n #observeResize(): void {\n if (typeof ResizeObserver === 'undefined') return;\n this.#resizeObserver = new ResizeObserver(() => this.#scheduleUpdate());\n this.#resizeObserver.observe(this.#container);\n }\n\n #scheduleUpdate(): void {\n if (this.#updateScheduled) return;\n this.#updateScheduled = true;\n requestAnimationFrame(() => {\n this.#updateScheduled = false;\n this.update();\n });\n }\n\n async #resolveContent(spread: Spread): Promise<SpreadContent> {\n const [left, right] = await Promise.all([this.#getPage(spread.left), this.#getPage(spread.right)]);\n return { left, right };\n }\n\n /** Decode one page; on failure emit `sourceError` and degrade to a blank (null) page. */\n async #getPage(index: number | null): Promise<PageContent | null> {\n if (index === null) return null;\n try {\n return await this.#source.get(index);\n } catch (error) {\n this.#emitter.emit('sourceError', { index, error });\n return null;\n }\n }\n\n #prefetchWindow(): void {\n const { toMount } = this.#virtualizer.update(this.#current, this.#spreads.length);\n const pages: number[] = [];\n for (const index of toMount) {\n const spread = this.#spreads[index];\n if (spread?.left != null) pages.push(spread.left);\n if (spread?.right != null) pages.push(spread.right);\n }\n this.#source.prefetch(pages);\n }\n\n #spreadIndexForPage(page: number): number {\n const index = this.#spreads.findIndex((s) => s.left === page || s.right === page);\n return index === -1 ? 0 : index;\n }\n\n /** Validate the (now-known) page count + startPage and build the spread model. */\n #buildSpreadModel(): void {\n const pageCount = this.#source.pageCount;\n if (!Number.isInteger(pageCount) || pageCount < 1) {\n throw new Error(`Zine: source has ${pageCount} pages; a Source must have at least 1 page.`);\n }\n const startPage = this.#startPageOption;\n if (\n startPage !== undefined &&\n (!Number.isInteger(startPage) || startPage < 0 || startPage >= pageCount)\n ) {\n throw new Error(\n `Zine: startPage ${JSON.stringify(startPage)} is out of range for a ${pageCount}-page book (valid 0..${pageCount - 1}).`,\n );\n }\n this.#spreads = buildSpreads(pageCount, { direction: this.#direction, cover: this.#cover });\n this.#currentPage = clamp(startPage ?? 0, 0, pageCount - 1);\n this.#current = this.#spreadIndexForPage(this.#currentPage);\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return value < min ? min : value > max ? max : value;\n}\n\nfunction easeInOutCubic(t: number): number {\n return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n}\n\nfunction typeName(v: unknown): string {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'array';\n return typeof v;\n}\n\n/** Validate constructor inputs up front, throwing agent-actionable errors. */\nfunction validateOptions(container: unknown, options: unknown): void {\n if (\n typeof container !== 'object' ||\n container === null ||\n typeof (container as { appendChild?: unknown }).appendChild !== 'function' ||\n typeof (container as { addEventListener?: unknown }).addEventListener !== 'function'\n ) {\n throw new Error(`Zine: container must be a DOM element; received ${typeName(container)}.`);\n }\n if (typeof options !== 'object' || options === null) {\n throw new Error('Zine: an options object with a `source` is required.');\n }\n const o = options as Record<string, unknown>;\n\n const source = o.source as { get?: unknown; pageCount?: unknown } | undefined;\n if (\n typeof source !== 'object' ||\n source === null ||\n typeof source.get !== 'function' ||\n typeof source.pageCount !== 'number'\n ) {\n throw new Error('Zine: `source` is required and must be a Source, e.g. new ImageSource(urls).');\n }\n // Note: pageCount >= 1 and startPage-in-range are validated in #buildSpreadModel —\n // synchronously for a sync source, or after open() for an async one (e.g. PdfSource).\n\n const startPage = o.startPage;\n if (startPage !== undefined && (typeof startPage !== 'number' || !Number.isInteger(startPage) || startPage < 0)) {\n throw new Error(`Zine: startPage must be a non-negative integer; got ${JSON.stringify(startPage)}.`);\n }\n\n const direction = o.direction;\n if (direction !== undefined && direction !== 'ltr' && direction !== 'rtl') {\n throw new Error(`Zine: direction must be 'ltr' or 'rtl'; got ${JSON.stringify(direction)}.`);\n }\n\n const clickToFlip = o.clickToFlip;\n if (clickToFlip !== undefined && !['edge', 'half', 'off'].includes(clickToFlip as string)) {\n throw new Error(`Zine: clickToFlip must be 'edge', 'half', or 'off'; got ${JSON.stringify(clickToFlip)}.`);\n }\n\n assertMin(o.width, 'width', 1);\n assertMin(o.height, 'height', 1);\n assertMin(o.flipDuration, 'flipDuration', 0);\n assertMin(o.clickZoneSize, 'clickZoneSize', 0);\n assertMin(o.clickFlipDelay, 'clickFlipDelay', 0);\n assertMin(o.singlePageThreshold, 'singlePageThreshold', 0);\n validateZoomOption(o.zoom);\n validateRendererOption(o.renderer);\n}\n\nfunction assertMin(value: unknown, name: string, min: number): void {\n if (value === undefined) return;\n if (typeof value !== 'number' || !Number.isFinite(value) || value < min) {\n throw new Error(`Zine: ${name} must be a number >= ${min}; got ${JSON.stringify(value)}.`);\n }\n}\n\nfunction validateZoomOption(zoom: unknown): void {\n if (zoom === undefined) return;\n if (typeof zoom !== 'object' || zoom === null || Array.isArray(zoom)) {\n throw new Error('Zine: zoom must be an object, e.g. { max: 4 }.');\n }\n const z = zoom as Record<string, unknown>;\n if (z.enabled !== undefined && typeof z.enabled !== 'boolean') {\n throw new Error(`Zine: zoom.enabled must be a boolean; got ${JSON.stringify(z.enabled)}.`);\n }\n if (z.wheel !== undefined && typeof z.wheel !== 'boolean') {\n throw new Error(`Zine: zoom.wheel must be a boolean; got ${JSON.stringify(z.wheel)}.`);\n }\n if (z.doubleClickInFlipZone !== undefined && typeof z.doubleClickInFlipZone !== 'boolean') {\n throw new Error(\n `Zine: zoom.doubleClickInFlipZone must be a boolean; got ${JSON.stringify(z.doubleClickInFlipZone)}.`,\n );\n }\n if (z.doubleClick !== undefined && z.doubleClick !== false) {\n const levels = z.doubleClick;\n if (\n !Array.isArray(levels) ||\n levels.some((n) => typeof n !== 'number' || !Number.isFinite(n) || n < 1)\n ) {\n throw new Error(\n 'Zine: zoom.doubleClick must be false or an array of zoom levels >= 1, e.g. [1, 2, 4].',\n );\n }\n }\n assertMin(z.max, 'zoom.max', 1);\n}\n\nfunction validateRendererOption(renderer: unknown): void {\n if (renderer === undefined) return;\n if (typeof renderer === 'string') {\n if (renderer !== 'auto' && renderer !== 'css' && renderer !== 'webgl2') {\n throw new Error(\n `Zine: renderer '${renderer}' is not recognized; use 'auto', 'css', 'webgl2', an array of those, or a custom Renderer.`,\n );\n }\n return;\n }\n if (Array.isArray(renderer)) {\n for (const kind of renderer) {\n if (kind !== 'css' && kind !== 'webgl2') {\n throw new Error(\n `Zine: renderer order array may only contain 'css' or 'webgl2'; got ${JSON.stringify(kind)}.`,\n );\n }\n }\n return;\n }\n if (\n typeof renderer === 'object' &&\n renderer !== null &&\n typeof (renderer as { mount?: unknown }).mount === 'function'\n ) {\n return;\n }\n throw new Error(\n \"Zine: renderer must be 'auto', 'css', 'webgl2', an array of those, or a custom Renderer instance.\",\n );\n}\n","import type { PageContent } from '../renderer/types';\nimport type { Source } from './types';\n\nexport type ImageFit = 'contain' | 'cover';\n\nexport interface ImageSourceOptions {\n /** How many adjacent pages to prefetch around a requested page. Default 1. */\n preload?: number;\n /** How each image fits its page; a hint consumed by the render layer. Default 'contain'. */\n fit?: ImageFit;\n}\n\n/**\n * Default content source for image books: decodes a list of image URLs to\n * ImageBitmaps, memoizes them, and prefetches neighbors on access.\n */\nexport class ImageSource implements Source {\n readonly pageCount: number;\n readonly fit: ImageFit;\n #urls: string[];\n #preload: number;\n #cache = new Map<number, Promise<ImageBitmap>>();\n\n constructor(urls: string[], options: ImageSourceOptions = {}) {\n this.#urls = urls;\n this.pageCount = urls.length;\n this.#preload = options.preload ?? 1;\n this.fit = options.fit ?? 'contain';\n }\n\n get(index: number): Promise<PageContent> {\n const decoded = this.#decode(index);\n for (let d = 1; d <= this.#preload; d++) {\n this.prefetch([index - d, index + d]);\n }\n return decoded;\n }\n\n prefetch(indices: number[]): void {\n for (const i of indices) {\n if (i >= 0 && i < this.pageCount) {\n void this.#decode(i).catch(() => {}); // swallow: prefetch failures surface on real get()\n }\n }\n }\n\n destroy(): void {\n for (const pending of this.#cache.values()) {\n void pending.then((bitmap) => bitmap.close()).catch(() => {});\n }\n this.#cache.clear();\n }\n\n #decode(index: number): Promise<ImageBitmap> {\n const cached = this.#cache.get(index);\n if (cached) return cached;\n\n const url = this.#urls[index];\n if (url === undefined) {\n return Promise.reject(\n new RangeError(`ImageSource: page ${index} is out of range (0..${this.pageCount - 1}).`),\n );\n }\n\n const promise = (async () => {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`ImageSource: failed to fetch page ${index} (${url}) — HTTP ${res.status}.`);\n }\n return createImageBitmap(await res.blob());\n })().catch((err: unknown) => {\n this.#cache.delete(index); // drop the failed entry so a later get() can retry\n throw err;\n });\n\n this.#cache.set(index, promise);\n return promise;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.ZineJS={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=Object.defineProperty,n=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(e){throw n=[e],e}},r=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r};function i(e,t={}){let{direction:n=`ltr`,cover:r=!1,singlePage:i=!1}=t;if(e<=0)return[];let a=t=>t>=0&&t<e?t:null,o=[];if(i)for(let t=0;t<e;t++)o.push({left:null,right:t});else if(r){o.push({left:null,right:0});for(let t=1;t<e;t+=2)o.push({left:a(t),right:a(t+1)})}else for(let t=0;t<e;t+=2)o.push({left:a(t),right:a(t+1)});return n===`rtl`?o.map(e=>({left:e.right,right:e.left})):o}function a(e,t){return e<t}function o(e,t,n){let r=[],i=Math.max(0,e-n),a=Math.min(t-1,e+n);for(let e=i;e<=a;e++)r.push(e);return r}var s=class{#e;#t=new Set;constructor(e=2){this.#e=e}get mounted(){return[...this.#t].sort((e,t)=>e-t)}update(e,t){let n=new Set(o(e,t,this.#e)),r=[...n].filter(e=>!this.#t.has(e)).sort((e,t)=>e-t),i=[...this.#t].filter(e=>!n.has(e)).sort((e,t)=>e-t);for(let e of i)this.#t.delete(e);for(let e of r)this.#t.add(e);return{toMount:r,toEvict:i}}},c=class{#e=new Map;on(e,t){let n=this.#e.get(e);return n||(n=new Set,this.#e.set(e,n)),n.add(t),()=>this.off(e,t)}off(e,t){this.#e.get(e)?.delete(t)}emit(e,...t){let n=t[0],r=this.#e.get(e);if(r)for(let e of[...r])e(n)}clear(){this.#e.clear()}},l={idle:{grab:`dragging`,panStart:`zoomed-panning`,flip:`animating`},dragging:{release:`animating`},animating:{settle:`idle`},"zoomed-panning":{panEnd:`idle`}};function u(e,t){return l[e][t]??null}var d=class{#e;constructor(e=`idle`){this.#e=e}get state(){return this.#e}send(e){let t=u(this.#e,e);return t!==null&&(this.#e=t),t}},ee=.3,f=class{#e;#t=null;#n=null;#r=null;#i=null;constructor(e={}){this.#e=e}get active(){return this.#t!==null}down(e,t,n,r){if(this.#t!==null)return;this.#t=e;let i={x:t,y:n,t:r};this.#n=i,this.#r=i,this.#i=i,this.#e.onStart?.({x:t,y:n,t:r})}move(e,t,n,r){e!==this.#t||this.#n===null||(this.#r=this.#i,this.#i={x:t,y:n,t:r},this.#e.onMove?.({x:t,y:n,dx:t-this.#n.x,dy:n-this.#n.y}))}up(e,t,n,r){this.#a(e,t,n,r,!1)}cancel(e,t,n,r){this.#a(e,t,n,r,!0)}#a(e,t,n,r,i){if(e!==this.#t||this.#n===null)return;let a=this.#n,o=this.#i??a,s=this.#r??a,c=o.t-s.t,l=c>0?(o.x-s.x)/c:0,u=c>0?(o.y-s.y)/c:0,d=Math.hypot(l,u);this.#o(),this.#e.onEnd?.({x:t,y:n,dx:t-a.x,dy:n-a.y,dt:r-a.t,vx:l,vy:u,swipe:!i&&d>ee,canceled:i})}#o(){this.#t=null,this.#n=null,this.#r=null,this.#i=null}};function p(e,t){let{pointer:n,pinch:r}=t,i=e=>{let t=e;n?.down(t.pointerId,t.clientX,t.clientY,t.timeStamp),r?.down(t.pointerId,t.clientX,t.clientY)},a=e=>{let t=e;n?.move(t.pointerId,t.clientX,t.clientY,t.timeStamp),r?.move(t.pointerId,t.clientX,t.clientY)},o=e=>{let t=e;n?.up(t.pointerId,t.clientX,t.clientY,t.timeStamp),r?.up(t.pointerId)},s=e=>{let t=e;n?.cancel(t.pointerId,t.clientX,t.clientY,t.timeStamp),r?.cancel(t.pointerId)};return e.addEventListener(`pointerdown`,i),e.addEventListener(`pointermove`,a),e.addEventListener(`pointerup`,o),e.addEventListener(`pointercancel`,s),()=>{e.removeEventListener(`pointerdown`,i),e.removeEventListener(`pointermove`,a),e.removeEventListener(`pointerup`,o),e.removeEventListener(`pointercancel`,s)}}function m(e,t){return Math.hypot(e.x-t.x,e.y-t.y)}var h=class{#e;#t=new Map;#n=0;#r=!1;constructor(e={}){this.#e=e}get pinching(){return this.#r}down(e,t,n){if(!(this.#t.has(e)||this.#t.size>=2)&&(this.#t.set(e,{x:t,y:n}),this.#t.size===2)){let{a:e,b:t}=this.#a();this.#n=m(e,t),this.#r=!0,this.#e.onPinchStart?.({centerX:(e.x+t.x)/2,centerY:(e.y+t.y)/2,distance:this.#n})}}move(e,t,n){let r=this.#t.get(e);if(!r||(r.x=t,r.y=n,!this.#r))return;let{a:i,b:a}=this.#a();this.#e.onPinchMove?.({centerX:(i.x+a.x)/2,centerY:(i.y+a.y)/2,scale:this.#n>0?m(i,a)/this.#n:1})}up(e){this.#i(e)}cancel(e){this.#i(e)}#i(e){this.#t.delete(e)&&this.#r&&this.#t.size<2&&(this.#r=!1,this.#n=0,this.#e.onPinchEnd?.())}#a(){let e=[...this.#t.values()],t=e[0],n=e[1];if(!t||!n)throw Error(`pinch requires two active pointers`);return{a:t,b:n}}};function g(e,t,n){let{width:r,height:i}=t;if(e.x<0||e.x>r||e.y<0||e.y>i)return`outside`;let a=e.x<=n||e.x>=r-n,o=e.y<=n||e.y>=i-n;return a&&o?`corner`:`page`}function _(e){let t=e<0?0:e>1?1:e,n=Math.sin(t*Math.PI);return{angle:t*Math.PI,curl:n,shadowAlpha:n}}var v=n((()=>{})),y=r({CssRenderer:()=>S});function b(e){let t=e===`forward`?`to right`:`to left`;return[`linear-gradient(${t}, rgba(0,0,0,0.6), rgba(0,0,0,0.15) 40%, rgba(0,0,0,0) 60%)`,`linear-gradient(${t}, transparent 40%, rgba(255,255,255,0.32) 66%, transparent 90%)`,`linear-gradient(${t}, transparent 66%, rgba(0,0,0,0.35))`].join(`,`)}var x,S,C=n((()=>{v(),x=180/Math.PI,S=class{#e=null;#t;#n;#r;#i;#a;#o;#s;#c;#l;mount(e){let t=e.ownerDocument;return this.#e=e,this.#t=t.createElement(`div`),this.#t.className=`zine-clip`,this.#t.style.cssText=`position:relative;width:100%;height:100%;overflow:hidden;`,this.#n=t.createElement(`div`),this.#n.className=`zine-viewport`,this.#n.style.cssText=`position:absolute;inset:0;transform-origin:0 0;`,this.#r=t.createElement(`div`),this.#r.className=`zine-book`,this.#r.style.cssText=`position:absolute;inset:0;perspective:2000px;transform-style:preserve-3d;`,this.#i=this.#u(t,`zine-page-left`,`left:0;`),this.#a=this.#u(t,`zine-page-right`,`right:0;`),this.#o=t.createElement(`div`),this.#o.className=`zine-leaf`,this.#o.style.cssText=`position:absolute;top:0;width:50%;height:100%;transform-style:preserve-3d;display:none;`,this.#s=t.createElement(`canvas`),this.#s.className=`zine-leaf-front`,this.#s.style.cssText=`position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;`,this.#c=t.createElement(`canvas`),this.#c.className=`zine-leaf-back`,this.#c.style.cssText=`position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;transform:rotateY(180deg);`,this.#l=t.createElement(`div`),this.#l.className=`zine-leaf-shadow`,this.#l.style.cssText=`position:absolute;inset:0;opacity:0;pointer-events:none;`,this.#l.style.background=b(`forward`),this.#o.append(this.#s,this.#c,this.#l),this.#r.append(this.#i,this.#a,this.#o),this.#n.append(this.#r),this.#t.append(this.#n),e.append(this.#t),Promise.resolve()}destroy(){this.#t?.remove(),this.#e=null}renderSpread(e,t,n){n?.fill?(this.#i.style.width=`100%`,this.#a.style.display=`none`,this.#d(this.#i,t.right??t.left)):(this.#i.style.width=`50%`,this.#a.style.display=``,this.#d(this.#i,t.left),this.#d(this.#a,t.right)),this.#o.style.display=`none`,this.#o.style.transform=``,this.#l.style.opacity=`0`}beginFlip(e,t,n,r){r?.fill?(this.#i.style.width=`100%`,this.#a.style.display=`none`,this.#o.style.width=`100%`,this.#o.style.left=`0`,this.#o.style.transformOrigin=n===`forward`?`left center`:`right center`,this.#d(this.#i,t.right??t.left),this.#d(this.#s,e.right??e.left),this.#d(this.#c,t.right??t.left)):(this.#i.style.width=`50%`,this.#a.style.display=``,this.#o.style.width=`50%`,n===`forward`?(this.#d(this.#i,e.left),this.#d(this.#a,t.right),this.#d(this.#s,e.right),this.#d(this.#c,t.left),this.#o.style.left=`50%`,this.#o.style.transformOrigin=`left center`):(this.#d(this.#a,e.right),this.#d(this.#i,t.left),this.#d(this.#s,e.left),this.#d(this.#c,t.right),this.#o.style.left=`0`,this.#o.style.transformOrigin=`right center`)),this.#o.style.display=`block`,this.#o.style.transform=`rotateY(0deg)`,this.#l.style.background=b(n),this.#l.style.opacity=`0`}setFlipProgress(e,t){let n=_(e),r=n.angle*x;this.#o.style.display=`block`,this.#o.style.transform=`rotateY(${t===`forward`?-r:r}deg)`,this.#l.style.opacity=String(n.shadowAlpha)}setViewTransform(e,t,n){this.#n.style.transform=`translate(${t}px, ${n}px) scale(${e})`}measure(){let e=this.#e,t=e?.clientWidth??0,n=e?.clientHeight??0;return{containerWidth:t,containerHeight:n,pageWidth:t/2,pageHeight:n}}#u(e,t,n){let r=e.createElement(`canvas`);return r.className=t,r.style.cssText=`position:absolute;top:0;width:50%;height:100%;${n}`,r}#d(e,t){let n=e.getContext(`2d`);if(t===null){n?.clearRect(0,0,e.width,e.height);return}e.width=t.width,e.height=t.height,n?.drawImage(t,0,0)}}}));function w(e){let t=e.getContext(`webgl2`,{alpha:!0,premultipliedAlpha:!0,antialias:!0,depth:!1,stencil:!1,preserveDrawingBuffer:!0});if(t===null)throw Error(`WebglRenderer: could not create a WebGL2 context.`);return t}var T=n((()=>{}));function E(e,t,n){let r=e.createProgram();if(r===null)throw Error(`WebglRenderer: could not create a program.`);let i=D(e,e.VERTEX_SHADER,t),a=D(e,e.FRAGMENT_SHADER,n);if(e.attachShader(r,i),e.attachShader(r,a),e.linkProgram(r),e.deleteShader(i),e.deleteShader(a),!e.getProgramParameter(r,e.LINK_STATUS)){let t=e.getProgramInfoLog(r);throw e.deleteProgram(r),Error(`WebglRenderer: program link failed: ${t??`unknown error`}`)}return r}function D(e,t,n){let r=e.createShader(t);if(r===null)throw Error(`WebglRenderer: could not create a shader.`);if(e.shaderSource(r,n),e.compileShader(r),!e.getShaderParameter(r,e.COMPILE_STATUS)){let t=e.getShaderInfoLog(r);throw e.deleteShader(r),Error(`WebglRenderer: shader compile failed: ${t??`unknown error`}`)}return r}var O=n((()=>{}));function k(e){let t=e.createTexture();if(t===null)throw Error(`WebglRenderer: could not create a texture.`);return e.bindTexture(e.TEXTURE_2D,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),t}function A(e,t,n){e.bindTexture(e.TEXTURE_2D,t),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!1),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,n)}var j=n((()=>{})),te=r({WebglRenderer:()=>z}),M,N,P,F,I,L,R,z,B=n((()=>{v(),T(),O(),j(),M=`#version 300 es
2
+ in vec2 aUnit;
3
+ uniform vec2 uViewport;
4
+ uniform vec4 uRect;
5
+ uniform vec3 uView;
6
+ out vec2 vUv;
7
+ void main() {
8
+ vec2 px = uRect.xy + aUnit * uRect.zw;
9
+ px = px * uView.x + uView.yz;
10
+ vec2 clip = px / uViewport * 2.0 - 1.0;
11
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
12
+ vUv = aUnit;
13
+ }`,N=`#version 300 es
14
+ precision mediump float;
15
+ in vec2 vUv;
16
+ uniform sampler2D uTex;
17
+ uniform float uGutterSide; // +1 = spine at the right edge, -1 = at the left edge, 0 = none
18
+ out vec4 outColor;
19
+ void main() {
20
+ vec4 c = texture(uTex, vUv);
21
+ if (uGutterSide != 0.0) {
22
+ // Soft, permanent gutter shadow: darken toward the spine edge where the pages
23
+ // meet, so the turning leaf's spine shadow lands into it instead of vanishing.
24
+ float d = uGutterSide > 0.0 ? (1.0 - vUv.x) : vUv.x;
25
+ c.rgb *= mix(0.72, 1.0, smoothstep(0.0, 0.10, d));
26
+ }
27
+ outColor = c;
28
+ }`,P=`#version 300 es
29
+ in vec2 aUnit;
30
+ uniform vec2 uViewport;
31
+ uniform vec4 uLeaf; // spineX, y, width, height (device px)
32
+ uniform vec3 uView; // scale, tx, ty
33
+ uniform float uAngle; // spine rotation 0..PI
34
+ uniform float uCurl; // 0..1 bend amount
35
+ uniform float uDir; // +1 leaf sweeps from +x side, -1 from -x side
36
+ out vec2 vUv;
37
+ out float vU;
38
+ out float vLight; // surface facing: +1 toward viewer, 0 edge-on, -1 away
39
+ const float CAM = 3.0; // perspective camera distance, in page widths (bigger = subtler)
40
+ void main() {
41
+ float u = aUnit.x;
42
+ float W = uLeaf.z;
43
+ // Cylinder wrap of the turning sheet, rotated about the spine (vertical y axis).
44
+ float bend = uCurl * 1.5;
45
+ float phi, xw, zw;
46
+ if (bend > 0.001) {
47
+ float R = W / bend;
48
+ phi = u * bend;
49
+ xw = R * sin(phi);
50
+ zw = R * (1.0 - cos(phi));
51
+ } else {
52
+ phi = 0.0;
53
+ xw = u * W;
54
+ zw = 0.0;
55
+ }
56
+ float X = xw * cos(uAngle) - zw * sin(uAngle); // in-plane
57
+ float Z = xw * sin(uAngle) + zw * cos(uAngle); // depth (+ toward viewer)
58
+ float px = uLeaf.x + uDir * X;
59
+ float py = uLeaf.y + aUnit.y * uLeaf.w;
60
+ // Perspective by depth. X may bulge toward the viewer for 3D pop; Y only ever
61
+ // SHRINKS (persp clamped <= 1) about the page's own vertical center, so the top
62
+ // is never pushed past the frame (that was the hidden-top bug).
63
+ float D = W * CAM;
64
+ float persp = D / max(D - Z, 1.0);
65
+ float cx = uViewport.x * 0.5;
66
+ float cy = uLeaf.y + 0.5 * uLeaf.w;
67
+ px = cx + (px - cx) * persp;
68
+ py = cy + (py - cy) * min(persp, 1.0);
69
+ vec2 p = vec2(px, py) * uView.x + uView.yz;
70
+ vec2 clip = p / uViewport * 2.0 - 1.0;
71
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
72
+ vUv = aUnit;
73
+ vU = u;
74
+ vLight = cos(phi + uAngle);
75
+ }`,F=`#version 300 es
76
+ precision mediump float;
77
+ in vec2 vUv;
78
+ in float vU;
79
+ in float vLight;
80
+ uniform sampler2D uFront;
81
+ uniform sampler2D uBack;
82
+ uniform highp float uDir; // must match the vertex stage's default highp
83
+ out vec4 outColor;
84
+ void main() {
85
+ // A backward flip (uDir < 0) mirrors the mesh horizontally, which flips both the
86
+ // projected winding (gl_FrontFacing) and the page's u->x mapping; undo both here.
87
+ float fx = uDir > 0.0 ? vUv.x : 1.0 - vUv.x;
88
+ // Front/back from the sheet's smooth facing (vLight), NOT gl_FrontFacing: the
89
+ // winding-based test flickers per-pixel at grazing angles as the curl overlaps.
90
+ bool showFront = vLight > 0.0;
91
+ vec4 c = showFront ? texture(uFront, vec2(fx, vUv.y)) : texture(uBack, vec2(1.0 - fx, vUv.y));
92
+
93
+ // Diffuse curl shading from the (smooth) facing magnitude: face-on bright, edge-on dark.
94
+ float diff = clamp(abs(vLight), 0.0, 1.0);
95
+ float lit = mix(0.72, 1.0, diff); // ambient floor, never crushed
96
+ float sheen = smoothstep(0.6, 0.95, diff) * (1.0 - diff) * 0.5; // soft crest highlight
97
+ // Spine crease uses the SAME curve as the static gutter shadow, so when the leaf
98
+ // lands the shadow is continuous instead of popping away.
99
+ lit *= mix(0.72, 1.0, smoothstep(0.0, 0.10, vU));
100
+
101
+ c.rgb = clamp(c.rgb * lit + sheen, 0.0, 1.0);
102
+ outColor = c;
103
+ }`,I=24,L=4e3,R=256*1024*1024,z=class{#e=null;#t=null;#n=null;#r=null;#i=null;#a=null;#o=null;#s=0;#c=new Map;#l=0;#u=null;#d={};#f={};#p={scale:1,tx:0,ty:0};#m=1;#h=null;#g=!1;#_=null;#v=0;#y=`forward`;#b=!1;#x=!1;#S=null;#C=null;mount(e){this.#e=e;let t=e.ownerDocument,n=t.createElement(`canvas`);return n.style.cssText=`display:block;width:100%;height:100%;`,e.append(n),this.#t=n,n.addEventListener(`webglcontextlost`,this.#w),n.addEventListener(`webglcontextrestored`,this.#T),t.addEventListener(`visibilitychange`,this.#E),this.#n=w(n),this.#k(),Promise.resolve()}destroy(){this.#O();let e=this.#t;e!==null&&(e.removeEventListener(`webglcontextlost`,this.#w),e.removeEventListener(`webglcontextrestored`,this.#T),e.ownerDocument.removeEventListener(`visibilitychange`,this.#E));let t=this.#n;if(t!==null){for(let e of this.#c.values())t.deleteTexture(e.tex);this.#u!==null&&t.deleteTexture(this.#u),this.#a!==null&&t.deleteVertexArray(this.#a),this.#o!==null&&t.deleteVertexArray(this.#o),this.#r!==null&&t.deleteProgram(this.#r),this.#i!==null&&t.deleteProgram(this.#i),t.getExtension(`WEBGL_lose_context`)?.loseContext()}e?.remove(),this.#c.clear(),this.#l=0,this.#u=null,this.#e=null,this.#t=null,this.#n=null,this.#h=null,this.#_=null}onFatal(e){this.#C=e}renderSpread(e,t,n){this.#h=t,this.#g=n?.fill??!1,this.#_=null,this.#A()}beginFlip(e,t,n,r){let i=r?.fill??!1;this.#y=n,this.#v=0,i?this.#_={underLeft:null,underRight:null,underFull:t.right??t.left,front:e.right??e.left,back:t.right??t.left,dir:n===`forward`?1:-1,spineAtStart:n===`forward`?0:1,fill:!0}:n===`forward`?this.#_={underLeft:e.left,underRight:t.right,underFull:null,front:e.right,back:t.left,dir:1,spineAtStart:.5,fill:!1}:this.#_={underLeft:t.left,underRight:e.right,underFull:null,front:e.left,back:t.right,dir:-1,spineAtStart:.5,fill:!1},this.#A()}setFlipProgress(e,t){this.#v=e,this.#y=t,this.#A()}setViewTransform(e,t,n){this.#p={scale:e,tx:t,ty:n},this.#A()}measure(){let e=this.#e,t=e?.clientWidth??0,n=e?.clientHeight??0;return{containerWidth:t,containerHeight:n,pageWidth:t/2,pageHeight:n}}#w=e=>{e.preventDefault(),this.#b=!0,this.#S===null&&(this.#S=setTimeout(()=>this.#D(),L))};#T=()=>{this.#O(),this.#b=!1,this.#k(),this.#A()};#E=()=>{this.#t?.ownerDocument.visibilityState===`visible`&&this.#A()};#D(){this.#x||(this.#x=!0,this.#C?.())}#O(){this.#S!==null&&(clearTimeout(this.#S),this.#S=null)}#k(){let e=this.#n;if(e===null)return;this.#r=E(e,M,N),this.#i=E(e,P,F);for(let t of[`uViewport`,`uRect`,`uView`,`uTex`,`uGutterSide`])this.#d[t]=e.getUniformLocation(this.#r,t);for(let t of[`uViewport`,`uLeaf`,`uView`,`uAngle`,`uCurl`,`uDir`,`uFront`,`uBack`])this.#f[t]=e.getUniformLocation(this.#i,t);this.#a=this.#z(e,this.#r,new Float32Array([0,0,1,0,0,1,1,1]));let t=[];for(let e=0;e<=I;e++){let n=e/I;t.push(n,0,n,1)}this.#s=50,this.#o=this.#z(e,this.#i,new Float32Array(t)),this.#c.clear(),this.#l=0,this.#u=this.#R(e),e.useProgram(this.#r),e.uniform1i(this.#d.uTex??null,0),e.useProgram(this.#i),e.uniform1i(this.#f.uFront??null,0),e.uniform1i(this.#f.uBack??null,1),e.clearColor(0,0,0,0),e.enable(e.BLEND),e.blendFunc(e.ONE,e.ONE_MINUS_SRC_ALPHA)}#A(){let e=this.#n,t=this.#t;e===null||t===null||this.#b||e.isContextLost()||(this.#B(),e.clear(e.COLOR_BUFFER_BIT),this.#_===null?this.#h!==null&&this.#j():this.#M())}#j(){let e=this.#t,t=e.width,n=e.height;this.#N();let r=this.#h;this.#g?this.#P({x:0,y:0,w:t,h:n},r.right??r.left):(this.#P({x:0,y:0,w:t/2,h:n},r.left,1),this.#P({x:t/2,y:0,w:t/2,h:n},r.right,-1))}#M(){let e=this.#n,t=this.#t,n=t.width,r=t.height,i=this.#_,a=_(this.#v);if(this.#N(),i.fill?this.#P({x:0,y:0,w:n,h:r},i.underFull):(this.#P({x:0,y:0,w:n/2,h:r},i.underLeft,1),this.#P({x:n/2,y:0,w:n/2,h:r},i.underRight,-1)),i.front===null&&i.back===null)return;this.#F(e.TEXTURE0,i.front),this.#F(e.TEXTURE1,i.back),e.activeTexture(e.TEXTURE0);let o=i.fill?n:n/2,s=i.spineAtStart*n;e.useProgram(this.#i),e.bindVertexArray(this.#o),e.uniform2f(this.#f.uViewport??null,n,r),e.uniform3f(this.#f.uView??null,this.#p.scale,this.#p.tx*this.#m,this.#p.ty*this.#m),e.uniform4f(this.#f.uLeaf??null,s,0,o,r),e.uniform1f(this.#f.uAngle??null,a.angle),e.uniform1f(this.#f.uCurl??null,a.curl),e.uniform1f(this.#f.uDir??null,i.dir),e.drawArrays(e.TRIANGLE_STRIP,0,this.#s)}#N(){let e=this.#n,t=this.#t;e.useProgram(this.#r),e.bindVertexArray(this.#a),e.activeTexture(e.TEXTURE0),e.uniform2f(this.#d.uViewport??null,t.width,t.height),e.uniform3f(this.#d.uView??null,this.#p.scale,this.#p.tx*this.#m,this.#p.ty*this.#m)}#P(e,t,n=0){let r=this.#n;t!==null&&(this.#F(r.TEXTURE0,t),r.uniform4f(this.#d.uRect??null,e.x,e.y,e.w,e.h),r.uniform1f(this.#d.uGutterSide??null,n),r.drawArrays(r.TRIANGLE_STRIP,0,4))}#F(e,t){let n=this.#n;n.activeTexture(e),n.bindTexture(n.TEXTURE_2D,t===null?this.#u:this.#I(t))}#I(e){let t=this.#n,n=this.#c.get(e);if(n!==void 0)return this.#c.delete(e),this.#c.set(e,n),n.tex;let r=k(t);A(t,r,e);let i=e,a=i.width*i.height*4;return this.#c.set(e,{tex:r,bytes:a}),this.#l+=a,this.#L(),r}#L(){let e=this.#n;for(let[t,n]of this.#c){if(this.#l<=R||this.#c.size<=4)break;this.#c.delete(t),this.#l-=n.bytes,e.deleteTexture(n.tex)}}#R(e){let t=k(e);return e.bindTexture(e.TEXTURE_2D,t),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,1,1,0,e.RGBA,e.UNSIGNED_BYTE,new Uint8Array([0,0,0,0])),t}#z(e,t,n){let r=e.createVertexArray(),i=e.createBuffer();if(r===null||i===null)throw Error(`WebglRenderer: could not create buffers.`);e.bindVertexArray(r),e.bindBuffer(e.ARRAY_BUFFER,i),e.bufferData(e.ARRAY_BUFFER,n,e.STATIC_DRAW);let a=e.getAttribLocation(t,`aUnit`);return e.enableVertexAttribArray(a),e.vertexAttribPointer(a,2,e.FLOAT,!1,0,0),e.bindVertexArray(null),r}#B(){let e=this.#n,t=this.#t,n=this.#e;if(e===null||t===null||n===null)return;this.#m=typeof devicePixelRatio==`number`?devicePixelRatio:1;let r=Math.max(1,Math.round(n.clientWidth*this.#m)),i=Math.max(1,Math.round(n.clientHeight*this.#m));(t.width!==r||t.height!==i)&&(t.width=r,t.height=i,e.viewport(0,0,r,i))}}})),V={css:async()=>new(await(Promise.resolve().then(()=>(C(),y)))).CssRenderer,webgl2:async()=>new(await(Promise.resolve().then(()=>(B(),te)))).WebglRenderer},H=!0;function U(e){return typeof e==`object`&&!!e&&!Array.isArray(e)&&typeof e.mount==`function`}function W(e){return e.gpu&&H?[`webgl2`,`css`]:[`css`]}function G(){return{gpu:K()}}function K(){try{return!!document.createElement(`canvas`).getContext(`webgl2`)}catch{return!1}}async function q(e=`auto`,t=G()){if(U(e))return e;let n=Array.isArray(e)?e:e===`auto`?W(t):[e];for(let e of n){if(e===`webgl2`&&!t.gpu)continue;let n=V[e];if(n)return n()}throw n.length===1&&n[0]===`webgl2`&&!t.gpu?Error(`renderer: 'webgl2' was forced but this device has no WebGL2. Use 'css' or 'auto'.`):Error(`renderer: none of [${n.join(`, `)}] could be loaded — use 'css' or 'auto'.`)}var J=.25,Y=.0015,X=6,Z=250,ne=class{#e;#t;#n=new c;#r=new s(2);#i=new d;#a;#o;#s;#c;#l;#u;#d;#f=!1;#p;#m=[];#h=0;#g=0;#_={left:null,right:null};#v;#y;#b;#x;#S;#C=null;#w=!1;#T=1;#E=0;#D=0;#O=null;#k=!1;#A=!1;#j=null;#M=null;#N=null;#P=null;#F=null;#I=null;#L=!1;#R=1;#z=!1;#B=null;#V=null;#H=null;#U=null;#W=null;#G;debug={setFlipProgress:(e,t)=>{this.#O?.setFlipProgress(e,t)}};constructor(e,t){ae(e,t),this.#e=e,t.width!==void 0&&(e.style.width=`${t.width}px`),t.height!==void 0&&(e.style.height=`${t.height}px`),this.#t=t.source;let n=t.direction??`ltr`,r=t.cover??!1;this.#a=n,this.#o=r,this.#s=t.clickToFlip??`edge`,this.#c=t.clickZoneSize??64,this.#d=t.singlePageThreshold??640,this.#p=t.startPage,typeof this.#t.open!=`function`&&this.#ke(),this.#v=t.flipDuration??800,this.#y=t.zoom?.enabled??!0,this.#b=t.zoom?.wheel??!0;let i=t.zoom?.doubleClick,a=i===!1?[]:[...i??[1,2,4]].sort((e,t)=>e-t);this.#x=a.length>0?a:null,this.#S=t.zoom?.max??4;let o=this.#y&&this.#x!==null,s=t.zoom?.doubleClickInFlipZone??this.#s===`half`;this.#l=this.#s!==`off`&&o&&s&&t.clickFlipDelay!==0,this.#u=this.#l?t.clickFlipDelay??Z:0,this.#G=this.#fe(t.renderer??`auto`)}get ready(){return this.#G}getPageCount(){return this.#t.pageCount}getPage(){return this.#g}flipNext(){this.#q(this.#h+1)}flipPrev(){this.#q(this.#h-1)}flipTo(e){let t=Q(e,0,Math.max(0,this.#t.pageCount-1));this.#q(this.#Oe(t))}getZoom(){return this.#T}setZoom(e,t){if(!this.#O||!this.#y)return;let n=Q(e,1,this.#S),r=this.#O.measure(),i=t??{x:r.containerWidth/2,y:r.containerHeight/2},a=this.#T,o=n===1?0:i.x-n/a*(i.x-this.#E),s=n===1?0:i.y-n/a*(i.y-this.#D);[o,s]=this.#K(o,s,n,r.containerWidth,r.containerHeight),this.#T=n,this.#E=o,this.#D=s,this.#O.setViewTransform(n,o,s),this.#n.emit(`zoomChanged`,{scale:n})}resetZoom(){this.setZoom(1)}#K(e,t,n,r,i){return[Q(e,r*(1-n),0),Q(t,i*(1-n),0)]}on(e,t){return this.#n.on(e,t)}destroy(){this.#A=!0,this.#j!==null&&cancelAnimationFrame(this.#j),this.#ae(),this.#C?.disconnect(),this.#W?.(),this.#H?.(),this.#U?.(),this.#V?.(),this.#O?.destroy(),this.#O=null,this.#t.destroy(),this.#n.clear()}#q(e){if(e<0||e>=this.#m.length||e===this.#h||this.#i.send(`flip`)===null)return;let t=e>this.#h?`forward`:`backward`,n=this.#Z(e);this.#n.emit(`flipStart`,{from:this.#g,to:n}),this.#J(e,t,n)}async#J(e,t,n){let r=this.#m[e],i=r?await this.#Te(r):{left:null,right:null};this.#O?.beginFlip(this.#_,i,t,{fill:this.#f}),this.#Y(0,1,t,()=>this.#X(e,n,i))}#Y(e,t,n,r){let i=this.#ye()*Math.abs(t-e);if(i<=0){this.#O?.setFlipProgress(t,n),r();return}let a=performance.now(),o=s=>{let c=Math.min(1,(s-a)/i);this.#O?.setFlipProgress(e+(t-e)*re(c),n),c<1?this.#j=requestAnimationFrame(o):(this.#j=null,r())};this.#j=requestAnimationFrame(o)}#X(e,t,n){let r=this.#m[e];this.#h=e,this.#g=t,this.#_=n,r&&this.#Se(r,n),this.#De(),this.#i.send(`settle`),this.#ve(),this.#n.emit(`pageChanged`,{page:t}),this.#n.emit(`flipEnd`,{page:t})}#Z(e){let t=this.#m[e];if(!t)return this.#g;let n=[t.left,t.right].filter(e=>e!==null);return n.length>0?Math.min(...n):this.#g}#Q(e,t){if(this.#L||!this.#O||this.#i.state!==`idle`)return;if(this.#ae(),this.#T>1){if(this.#i.send(`panStart`)===null)return;this.#I={baseTx:this.#E,baseTy:this.#D};return}let n=this.#e.getBoundingClientRect(),r={x:e-n.left,y:t-n.top};this.#P=r;let{containerWidth:i,containerHeight:a}=this.#O.measure(),o=Math.min(i,a)*J;if(g(r,{width:i,height:a},o)!==`corner`)return;let s=r.x>i/2,c=this.#a===`rtl`?!s:s,l=this.#h+(c?1:-1);l<0||l>=this.#m.length||(this.#N={direction:c?`forward`:`backward`,targetIndex:l,toPage:this.#Z(l),width:i})}#$(){let e=this.#N;e&&(this.#N=null,this.#i.send(`grab`)!==null&&(this.#M={direction:e.direction,targetIndex:e.targetIndex,toPage:e.toPage,toContent:{left:null,right:null},width:e.width,t:0},this.#n.emit(`flipStart`,{from:this.#g,to:e.toPage}),this.#ee(e.targetIndex,e.direction)))}async#ee(e,t){let n=this.#m[e],r=n?await this.#Te(n):{left:null,right:null};this.#M?.targetIndex===e&&(this.#M.toContent=r,this.#O?.beginFlip(this.#_,r,t,{fill:this.#f}),this.#O?.setFlipProgress(this.#M.t,t))}#te(e,t){if(this.#L)return;if(this.#I){if(!this.#O)return;let n=this.#O.measure(),[r,i]=this.#K(this.#I.baseTx+e,this.#I.baseTy+t,this.#T,n.containerWidth,n.containerHeight);this.#E=r,this.#D=i,this.#O.setViewTransform(this.#T,r,i);return}this.#N&&Math.hypot(e,t)>X&&this.#$();let n=this.#M;n&&(n.t=Q((n.direction===`forward`?-e:e)/n.width,0,1),this.#O?.setFlipProgress(n.t,n.direction))}#ne(e){if(this.#L)return;if(this.#I){this.#I=null,this.#i.send(`panEnd`);return}let t=this.#M;if(!t){this.#N=null,this.#re(e);return}this.#M=null,this.#i.send(`release`);let n=e.swipe&&(t.direction===`forward`?e.dx<0:e.dx>0);t.t>=.5||n?this.#Y(t.t,1,t.direction,()=>this.#X(t.targetIndex,t.toPage,t.toContent)):this.#Y(t.t,0,t.direction,()=>this.#oe())}#re(e){if(this.#s===`off`||this.#T>1||!this.#P||Math.abs(e.dx)>X||Math.abs(e.dy)>X)return;let t=this.#ie(this.#P);if(!t)return;let n=this.#h+(t===`forward`?1:-1);if(!(n<0||n>=this.#m.length)){if(this.#ae(),this.#u<=0){this.#q(n);return}this.#F=setTimeout(()=>{this.#F=null,this.#q(n)},this.#u)}}#ie(e){if(!this.#O)return null;let{containerWidth:t}=this.#O.measure(),n;if(n=this.#s===`half`?e.x>t/2?`right`:`left`:e.x<=this.#c?`left`:e.x>=t-this.#c?`right`:null,!n)return null;let r=this.#a===`rtl`?`left`:`right`;return n===r?`forward`:`backward`}#ae(){this.#F!==null&&(clearTimeout(this.#F),this.#F=null)}#oe(){let e=this.#m[this.#h];e&&this.#Se(e,this.#_),this.#i.send(`settle`),this.#n.emit(`flipEnd`,{page:this.#g})}#se(){if(this.#N=null,this.#ae(),this.#M){this.#M=null,this.#i.send(`release`),this.#i.send(`settle`);let e=this.#m[this.#h];e&&this.#Se(e,this.#_)}else this.#I&&(this.#I=null,this.#i.send(`panEnd`));this.#L=!0,this.#R=this.#T}#ce(e,t,n){if(!this.#L)return;let r=this.#e.getBoundingClientRect();this.setZoom(this.#R*n,{x:e-r.left,y:t-r.top})}#le(){this.#L=!1}#ue(){let e=e=>{if(!this.#b||!this.#y||!(e.ctrlKey||e.metaKey))return;e.preventDefault();let t=this.#e.getBoundingClientRect(),n=Math.exp(-e.deltaY*Y);this.setZoom(this.#T*n,{x:e.clientX-t.left,y:e.clientY-t.top})};this.#e.addEventListener(`wheel`,e,{passive:!1}),this.#H=()=>this.#e.removeEventListener(`wheel`,e)}#de(){let e=e=>{let t=this.#x;if(!this.#y||t===null)return;if(this.#T<=1&&this.#s!==`off`&&!this.#l){let t=this.#e.getBoundingClientRect(),n={x:e.clientX-t.left,y:e.clientY-t.top};if(this.#ie(n)!==null)return}this.#ae(),e.preventDefault();let n=t.find(e=>e>this.#T+1e-6)??t[0]??this.#T,r=this.#e.getBoundingClientRect();this.setZoom(n,{x:e.clientX-r.left,y:e.clientY-r.top})};this.#e.addEventListener(`dblclick`,e),this.#U=()=>this.#e.removeEventListener(`dblclick`,e)}async#fe(e){let t=q(e);typeof this.#t.open==`function`&&(await this.#t.open(),this.#ke()),this.#t.prefetch([this.#g]);let n=await t,r=await this.#pe(n);this.#O=r,this.#be(r.measure().containerWidth);let i=new f({onStart:e=>this.#Q(e.x,e.y),onMove:e=>this.#te(e.dx,e.dy),onEnd:e=>this.#ne(e)}),a=new h({onPinchStart:()=>this.#se(),onPinchMove:e=>this.#ce(e.centerX,e.centerY,e.scale),onPinchEnd:()=>this.#le()});this.#V=p(this.#e,{pointer:i,pinch:a}),this.#ue(),this.#de(),this.#he(),this.#W=this.#ge(),await this.#xe(),this.#Ce(),this.#De(),this.#ve(),this.#n.emit(`ready`)}async#pe(e){try{return await e.mount(this.#e),e.onFatal?.(()=>{this.#me()}),e}catch{e.destroy();let t=await q(`css`);return await t.mount(this.#e),this.#k=!0,this.#n.emit(`rendererFallback`,{from:`webgl2`,to:`css`}),t}}async#me(){if(this.#k||this.#A)return;this.#k=!0,this.#O?.destroy(),this.#O=null;let e=await q(`css`);if(this.#A){e.destroy();return}await e.mount(this.#e),this.#O=e,this.#be(e.measure().containerWidth),await this.#xe(),this.#n.emit(`rendererFallback`,{from:`webgl2`,to:`css`})}#he(){if(typeof matchMedia!=`function`)return;let e=matchMedia(`(prefers-reduced-motion: reduce)`);this.#z=e.matches,e.addEventListener?.(`change`,e=>{this.#z=e.matches})}#ge(){let e=this.#e,t=e.ownerDocument;if(!t||typeof e.setAttribute!=`function`)return null;e.hasAttribute(`tabindex`)||(e.tabIndex=0),e.setAttribute(`aria-roledescription`,`flipbook`);let n=e=>this.#_e(e);e.addEventListener(`keydown`,n);let r=t.createElement(`div`);return r.setAttribute(`aria-live`,`polite`),r.setAttribute(`aria-atomic`,`true`),r.style.cssText=`position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;`,e.appendChild(r),this.#B=r,()=>{e.removeEventListener(`keydown`,n),r.remove(),this.#B=null}}#_e(e){let t=this.#a===`rtl`?`ArrowLeft`:`ArrowRight`;switch(e.key){case`ArrowRight`:case`ArrowLeft`:e.key===t?this.flipNext():this.flipPrev(),e.preventDefault();break;case`Home`:this.flipTo(0),e.preventDefault();break;case`End`:this.flipTo(this.#t.pageCount-1),e.preventDefault();break;default:break}}#ve(){this.#B&&(this.#B.textContent=`Page ${this.#g+1} of ${this.#t.pageCount}`)}#ye(){return this.#z?0:this.#v}update(){!this.#O||this.#i.state!==`idle`||(this.#be(this.#O.measure().containerWidth),this.#xe())}#be(e){let t=a(e,this.#d);t!==this.#f&&(this.#f=t,this.#m=i(this.#t.pageCount,{direction:this.#a,cover:this.#o,singlePage:t}),this.#h=this.#Oe(this.#g))}async#xe(){let e=this.#m[this.#h];!e||!this.#O||(this.#_=await this.#Te(e),this.#Se(e,this.#_))}#Se(e,t){this.#O?.renderSpread(e,t,{fill:this.#f})}#Ce(){typeof ResizeObserver>`u`||(this.#C=new ResizeObserver(()=>this.#we()),this.#C.observe(this.#e))}#we(){this.#w||(this.#w=!0,requestAnimationFrame(()=>{this.#w=!1,this.update()}))}async#Te(e){let[t,n]=await Promise.all([this.#Ee(e.left),this.#Ee(e.right)]);return{left:t,right:n}}async#Ee(e){if(e===null)return null;try{return await this.#t.get(e)}catch(t){return this.#n.emit(`sourceError`,{index:e,error:t}),null}}#De(){let{toMount:e}=this.#r.update(this.#h,this.#m.length),t=[];for(let n of e){let e=this.#m[n];e?.left!=null&&t.push(e.left),e?.right!=null&&t.push(e.right)}this.#t.prefetch(t)}#Oe(e){let t=this.#m.findIndex(t=>t.left===e||t.right===e);return t===-1?0:t}#ke(){let e=this.#t.pageCount;if(!Number.isInteger(e)||e<1)throw Error(`Zine: source has ${e} pages; a Source must have at least 1 page.`);let t=this.#p;if(t!==void 0&&(!Number.isInteger(t)||t<0||t>=e))throw Error(`Zine: startPage ${JSON.stringify(t)} is out of range for a ${e}-page book (valid 0..${e-1}).`);this.#m=i(e,{direction:this.#a,cover:this.#o}),this.#g=Q(t??0,0,e-1),this.#h=this.#Oe(this.#g)}};function Q(e,t,n){return e<t?t:e>n?n:e}function re(e){return e<.5?4*e*e*e:1-(-2*e+2)**3/2}function ie(e){return e===null?`null`:Array.isArray(e)?`array`:typeof e}function ae(e,t){if(typeof e!=`object`||!e||typeof e.appendChild!=`function`||typeof e.addEventListener!=`function`)throw Error(`Zine: container must be a DOM element; received ${ie(e)}.`);if(typeof t!=`object`||!t)throw Error("Zine: an options object with a `source` is required.");let n=t,r=n.source;if(typeof r!=`object`||!r||typeof r.get!=`function`||typeof r.pageCount!=`number`)throw Error("Zine: `source` is required and must be a Source, e.g. new ImageSource(urls).");let i=n.startPage;if(i!==void 0&&(typeof i!=`number`||!Number.isInteger(i)||i<0))throw Error(`Zine: startPage must be a non-negative integer; got ${JSON.stringify(i)}.`);let a=n.direction;if(a!==void 0&&a!==`ltr`&&a!==`rtl`)throw Error(`Zine: direction must be 'ltr' or 'rtl'; got ${JSON.stringify(a)}.`);let o=n.clickToFlip;if(o!==void 0&&![`edge`,`half`,`off`].includes(o))throw Error(`Zine: clickToFlip must be 'edge', 'half', or 'off'; got ${JSON.stringify(o)}.`);$(n.width,`width`,1),$(n.height,`height`,1),$(n.flipDuration,`flipDuration`,0),$(n.clickZoneSize,`clickZoneSize`,0),$(n.clickFlipDelay,`clickFlipDelay`,0),$(n.singlePageThreshold,`singlePageThreshold`,0),oe(n.zoom),se(n.renderer)}function $(e,t,n){if(e!==void 0&&(typeof e!=`number`||!Number.isFinite(e)||e<n))throw Error(`Zine: ${t} must be a number >= ${n}; got ${JSON.stringify(e)}.`)}function oe(e){if(e===void 0)return;if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Zine: zoom must be an object, e.g. { max: 4 }.`);let t=e;if(t.enabled!==void 0&&typeof t.enabled!=`boolean`)throw Error(`Zine: zoom.enabled must be a boolean; got ${JSON.stringify(t.enabled)}.`);if(t.wheel!==void 0&&typeof t.wheel!=`boolean`)throw Error(`Zine: zoom.wheel must be a boolean; got ${JSON.stringify(t.wheel)}.`);if(t.doubleClickInFlipZone!==void 0&&typeof t.doubleClickInFlipZone!=`boolean`)throw Error(`Zine: zoom.doubleClickInFlipZone must be a boolean; got ${JSON.stringify(t.doubleClickInFlipZone)}.`);if(t.doubleClick!==void 0&&t.doubleClick!==!1){let e=t.doubleClick;if(!Array.isArray(e)||e.some(e=>typeof e!=`number`||!Number.isFinite(e)||e<1))throw Error(`Zine: zoom.doubleClick must be false or an array of zoom levels >= 1, e.g. [1, 2, 4].`)}$(t.max,`zoom.max`,1)}function se(e){if(e!==void 0){if(typeof e==`string`){if(e!==`auto`&&e!==`css`&&e!==`webgl2`)throw Error(`Zine: renderer '${e}' is not recognized; use 'auto', 'css', 'webgl2', an array of those, or a custom Renderer.`);return}if(Array.isArray(e)){for(let t of e)if(t!==`css`&&t!==`webgl2`)throw Error(`Zine: renderer order array may only contain 'css' or 'webgl2'; got ${JSON.stringify(t)}.`);return}if(!(typeof e==`object`&&e&&typeof e.mount==`function`))throw Error(`Zine: renderer must be 'auto', 'css', 'webgl2', an array of those, or a custom Renderer instance.`)}}e.ImageSource=class{pageCount;fit;#e;#t;#n=new Map;constructor(e,t={}){this.#e=e,this.pageCount=e.length,this.#t=t.preload??1,this.fit=t.fit??`contain`}get(e){let t=this.#r(e);for(let t=1;t<=this.#t;t++)this.prefetch([e-t,e+t]);return t}prefetch(e){for(let t of e)t>=0&&t<this.pageCount&&this.#r(t).catch(()=>{})}destroy(){for(let e of this.#n.values())e.then(e=>e.close()).catch(()=>{});this.#n.clear()}#r(e){let t=this.#n.get(e);if(t)return t;let n=this.#e[e];if(n===void 0)return Promise.reject(RangeError(`ImageSource: page ${e} is out of range (0..${this.pageCount-1}).`));let r=(async()=>{let t=await fetch(n);if(!t.ok)throw Error(`ImageSource: failed to fetch page ${e} (${n}) — HTTP ${t.status}.`);return createImageBitmap(await t.blob())})().catch(t=>{throw this.#n.delete(e),t});return this.#n.set(e,r),r}},e.Zine=ne});
104
+ //# sourceMappingURL=index.umd.cjs.map