@zinejs/core 0.3.0 → 0.4.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.
- package/README.md +3 -1
- package/dist/geometry/curls/silk.d.ts +27 -0
- package/dist/geometry/curls/types.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +5 -5
- package/dist/index.umd.cjs.map +1 -1
- package/dist/{types-D-rEO00p.js → types-CcPVyhOJ.js} +2 -1
- package/dist/{types-D-rEO00p.js.map → types-CcPVyhOJ.js.map} +1 -1
- package/dist/{webglRenderer-Bg6VF4CW.js → webglRenderer-BK-ArG1G.js} +63 -29
- package/dist/webglRenderer-BK-ArG1G.js.map +1 -0
- package/dist/zine.d.ts +1 -1
- package/options.schema.json +2 -2
- package/package.json +1 -1
- package/dist/webglRenderer-Bg6VF4CW.js.map +0 -1
package/dist/index.umd.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.cjs","names":["#radius","#mounted","#listeners","#state","#handlers","#activeId","#start","#prev","#last","#end","#reset","#pinching","#points","#pair","#startDistance","#remove","#container","#clip","#viewport","#book","#pageLeft","#makeCanvas","#pageRight","#leaf","#leafFront","#leafBack","#leafShadow","#paint","#fill","#contentShift","#trackAspect","#layoutBook","#applyShift","#fromShiftUnit","#toShiftUnit","#bookW","#pageAspect","#bookBox","#contentBox","#container","#canvas","#onContextLost","#onContextRestored","#onVisibilityChange","#gl","#buildGlResources","#clearRestoreTimer","#texCache","#blankTex","#quadVao","#curlVao","#flat","#curl","#cacheBytes","#content","#flip","#fatalHandler","#fill","#trackAspect","#render","#pageAspect","#flipT","#curlType","#anchor","#fromShiftUnit","#toShiftUnit","#view","#bookBox","#contentBox","#contextLost","#restoreTimer","#fatal","#fatalFired","#flatU","#curlU","#makeQuadVao","#buildCurlMesh","#makeBlankTexture","#idxCount","#posBuf","#normBuf","#mesh","#resize","#fitBook","#drawFlip","#drawStatic","#bookW","#bookH","#shiftX","#useFlat","#drawQuad","#bindFace","#dpr","#fillFade","#textureFor","#evictTextures","#base","#front","#back","#rawPages","#resolvePages","#image","#resolved","#cache","#renderer","#container","#source","#direction","#spreadMode","#curl","#clickToFlip","#clickZoneSize","#singlePageThreshold","#startPageOption","#buildSpreadModel","#flipDuration","#zoomEnabled","#wheelZoom","#doubleClickLevels","#maxZoom","#honorDoubleClickInFlipZone","#clickFlipDelayValue","#ready","#init","#currentPage","#requestFlip","#startFlip","#current","#spreadIndexForPage","#machine","#activeAnim","#finishActiveAnim","#queuedFlip","#raf","#scale","#tx","#ty","#clampPan","#emitter","#destroyed","#clearPendingClickFlip","#resizeObserver","#a11yCleanup","#unbindWheel","#unbindDblClick","#unbindInput","#spreads","#leadPage","#runFlip","#resolveContent","#currentContent","#singlePage","#anchorY","#animateProgress","#commit","#effectiveDuration","#paintSpread","#prefetchWindow","#announce","#drainQueuedFlip","#pinching","#pan","#contentRect","#press","#pendingGrab","#drag","#stageDrag","#promoteGrab","#maybeClickFlip","#cancelFlip","#clickFlipDirection","#pendingClickTimer","#pinchBaseScale","#mountWithFallback","#onPageUpdate","#applySinglePage","#onDragStart","#onDragMove","#onDragEnd","#onPinchStart","#onPinchMove","#onPinchEnd","#bindWheelZoom","#bindDoubleClickZoom","#setupReducedMotion","#setupA11y","#renderCurrent","#observeResize","#fallbackToCss","#fellBack","#reducedMotion","#onKeyDown","#liveRegion","#narrow","#effectiveMode","#applyContainerAspect","#lastAspect","#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/geometry/curls/types.ts","../src/geometry/flipProgressToPose.ts","../src/renderer/cssRenderer.ts","../src/geometry/curls/mesh.ts","../src/geometry/curls/roll.ts","../src/geometry/curls/cone.ts","../src/geometry/curls/leaf.ts","../src/geometry/curls/flick.ts","../src/geometry/curls/simple.ts","../src/geometry/curls/index.ts","../src/renderer/gl/context.ts","../src/renderer/gl/program.ts","../src/renderer/gl/texture.ts","../src/renderer/webglRenderer.ts","../src/renderer/select.ts","../src/source/compose.ts","../src/zine.ts","../src/source/imageSource.ts","../src/index.ts"],"sourcesContent":["export type Direction = 'ltr' | 'rtl';\n\n/**\n * How pages group into spreads:\n * - `double`: paired from the start — `[0|1] [2|3] …`\n * - `single`: one page per spread — `[0] [1] …`\n * - `cover`: first page alone, then paired — `[·|0] [1|2] …`\n * - `book`: first AND last alone (front + back cover) — `[·|0] [1|2] … [N-1|·]`\n */\nexport type SpreadMode = 'double' | 'single' | 'cover' | 'book';\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 mode?: SpreadMode;\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 / a lone cover).\n */\nexport function buildSpreads(pageCount: number, options: SpreadOptions = {}): Spread[] {\n const { direction = 'ltr', mode = 'cover' } = 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 (mode === 'single') {\n for (let i = 0; i < pageCount; i++) spreads.push({ left: null, right: i });\n } else if (mode === 'double') {\n for (let i = 0; i < pageCount; i += 2) spreads.push({ left: page(i), right: page(i + 1) });\n } else if (mode === 'cover') {\n spreads.push({ left: null, right: 0 });\n for (let i = 1; i < pageCount; i += 2) spreads.push({ left: page(i), right: page(i + 1) });\n } else {\n // book: front cover alone, interior paired, back cover alone\n spreads.push({ left: null, right: 0 });\n if (pageCount > 1) {\n for (let i = 1; i <= pageCount - 2; i += 2) {\n spreads.push({ left: page(i), right: i + 1 <= pageCount - 2 ? page(i + 1) : null });\n }\n spreads.push({ left: pageCount - 1, right: null });\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","/**\n * Curl type identifiers and the deform contract. Kept free of the deform\n * implementations so the engine (and CSS-only builds) can reference the union and the\n * validation list without pulling the WebGL-only curl math into the eager bundle.\n */\nimport type { PageMesh } from './mesh';\n\n/** Where a corner-anchored curl folds from: y in [0,1], 0 = top edge, 1 = bottom edge. */\nexport interface CurlAnchor {\n y: number;\n /**\n * Full-width lone page (single-page mode). The leaf hinges at the container edge and\n * has no facing half to land on — deforms may soften and delay the flop so the curl\n * reads naturally before the renderer dissolves the sheet.\n */\n fill?: boolean;\n}\n\nexport type CurlDeform = (mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor) => void;\n\nexport interface CurlModel {\n readonly deform: CurlDeform;\n /** True when the fold originates at a corner (`anchor.y`) rather than the whole edge. */\n readonly anchored: boolean;\n}\n\nexport type CurlType = 'roll' | 'cone' | 'leaf' | 'flick' | 'simple';\n\nexport const CURL_TYPES: CurlType[] = ['roll', 'cone', 'leaf', 'flick', 'simple'];\n\nexport const DEFAULT_CURL: CurlType = 'cone';\n","export interface FlipPose {\n /** Turning-page rotation about the spine, radians: 0 (flat/open) → π (fully turned). */\n angle: number;\n /** Mesh cylinder-wrap curl, 0..1 — flat at the ends, peaks edge-on at t=0.5. CSS renderer ignores it. */\n curl: number;\n /** Fold-shadow intensity, 0..1 — peaks edge-on at t=0.5. */\n shadowAlpha: number;\n}\n\n/**\n * Map a 0→1 flip progress to a renderer-neutral fold pose.\n *\n * Linear in `t` on purpose — easing belongs to the animator that produces `t`,\n * so seeking to a fixed progress (e.g. the 0/.25/.5/.75/1 visual-regression stops)\n * is deterministic. The CSS renderer consumes `angle` as a spine `rotateY`; the\n * WebGL2 renderer additionally bends its mesh by `curl`.\n */\nexport function flipProgressToPose(t: number): FlipPose {\n const clamped = t < 0 ? 0 : t > 1 ? 1 : t;\n const arc = Math.sin(clamped * Math.PI);\n return {\n angle: clamped * Math.PI,\n curl: arc,\n shadowAlpha: arc,\n };\n}\n","import { flipProgressToPose } from '../geometry/flipProgressToPose';\nimport type { Spread } from '../engine/spread';\nimport type {\n FlipDirection,\n LayoutMetrics,\n PageContent,\n Renderer,\n RenderOptions,\n SpreadContent,\n} from './types';\n\nconst RAD_TO_DEG = 180 / Math.PI;\n\n/** How far to shift a spread to centre a lone page, in units of a quarter container width:\n * -1 = only a right page (cover), +1 = only a left page (back), 0 = a full spread. */\nfunction shiftUnit(content: SpreadContent): number {\n const lone = (content.left === null) !== (content.right === null);\n return lone ? (content.right !== null ? -1 : 1) : 0;\n}\n\n/**\n * Layered gradients painted over the turning leaf to fake paper depth on the flat\n * fold: a crease shadow at the spine, a soft curve sheen, and a free-edge shade.\n * The spine is the leaf's pivot edge — left for a forward flip, right for backward.\n */\nfunction foldOverlay(direction: FlipDirection): string {\n const dir = direction === 'forward' ? 'to right' : 'to left';\n return [\n `linear-gradient(${dir}, rgba(0,0,0,0.6), rgba(0,0,0,0.15) 40%, rgba(0,0,0,0) 60%)`,\n `linear-gradient(${dir}, transparent 40%, rgba(255,255,255,0.32) 66%, transparent 90%)`,\n `linear-gradient(${dir}, transparent 66%, rgba(0,0,0,0.35))`,\n ].join(',');\n}\n\n/**\n * Baseline renderer: a 2-panel CSS-3D fold. Runs on every browser, no GPU. Pages\n * are <canvas> elements; the turning leaf (front + back faces, backface-hidden)\n * rotates about the spine driven by flipProgressToPose(), with a gradient shadow\n * overlay for depth. `beginFlip` stages the surfaces; `setFlipProgress` animates.\n */\nexport class CssRenderer implements Renderer {\n #container: HTMLElement | null = null;\n #clip!: HTMLDivElement;\n #viewport!: HTMLDivElement;\n #book!: HTMLDivElement;\n #pageLeft!: HTMLCanvasElement;\n #pageRight!: HTMLCanvasElement;\n #leaf!: HTMLDivElement;\n #leafFront!: HTMLCanvasElement;\n #leafBack!: HTMLCanvasElement;\n #leafShadow!: HTMLDivElement;\n // Lone-page centering, interpolated across a flip (see the WebGL renderer).\n #fromShiftUnit = 0;\n #toShiftUnit = 0;\n // Book aspect fit: the spread is letterboxed to the page aspect (see the WebGL renderer).\n #pageAspect = 0;\n #bookW = 0;\n #fill = false;\n #contentShift = 0; // shiftUnit of the current static spread (nonzero = lone page, centered)\n\n mount(container: HTMLElement): Promise<void> {\n const doc = container.ownerDocument;\n this.#container = container;\n\n // Fixed-size clip box; NOT transformed, so zoom can't scale the crop region.\n this.#clip = doc.createElement('div');\n this.#clip.className = 'zine-clip';\n this.#clip.style.cssText = 'position:relative;width:100%;height:100%;overflow:hidden;';\n\n // Transform target for zoom/pan; lives inside the clip.\n this.#viewport = doc.createElement('div');\n this.#viewport.className = 'zine-viewport';\n this.#viewport.style.cssText = 'position:absolute;inset:0;transform-origin:0 0;';\n\n this.#book = doc.createElement('div');\n this.#book.className = 'zine-book';\n this.#book.style.cssText =\n 'position:absolute;inset:0;perspective:2000px;transform-style:preserve-3d;';\n\n this.#pageLeft = this.#makeCanvas(doc, 'zine-page-left', 'left:0;');\n this.#pageRight = this.#makeCanvas(doc, 'zine-page-right', 'right:0;');\n\n this.#leaf = doc.createElement('div');\n this.#leaf.className = 'zine-leaf';\n this.#leaf.style.cssText =\n 'position:absolute;top:0;width:50%;height:100%;transform-style:preserve-3d;display:none;';\n\n this.#leafFront = doc.createElement('canvas');\n this.#leafFront.className = 'zine-leaf-front';\n this.#leafFront.style.cssText =\n 'position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;';\n\n this.#leafBack = doc.createElement('canvas');\n this.#leafBack.className = 'zine-leaf-back';\n this.#leafBack.style.cssText =\n 'position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;transform:rotateY(180deg);';\n\n this.#leafShadow = doc.createElement('div');\n this.#leafShadow.className = 'zine-leaf-shadow';\n this.#leafShadow.style.cssText = 'position:absolute;inset:0;opacity:0;pointer-events:none;';\n this.#leafShadow.style.background = foldOverlay('forward'); // default; beginFlip sets per direction\n\n this.#leaf.append(this.#leafFront, this.#leafBack, this.#leafShadow);\n this.#book.append(this.#pageLeft, this.#pageRight, this.#leaf);\n this.#viewport.append(this.#book);\n this.#clip.append(this.#viewport);\n container.append(this.#clip);\n\n return Promise.resolve();\n }\n\n destroy(): void {\n this.#clip?.remove();\n this.#container = null;\n }\n\n renderSpread(_spread: Spread, content: SpreadContent, options?: RenderOptions): void {\n if (options?.fill) {\n // Single-page mode: the lone page fills the container; hide the other panel.\n this.#pageLeft.style.width = '100%';\n this.#pageRight.style.display = 'none';\n this.#paint(this.#pageLeft, content.right ?? content.left);\n } else {\n this.#pageLeft.style.width = '50%';\n this.#pageRight.style.display = '';\n this.#paint(this.#pageLeft, content.left);\n this.#paint(this.#pageRight, content.right);\n }\n this.#fill = options?.fill ?? false;\n this.#contentShift = this.#fill ? 0 : shiftUnit(content);\n this.#trackAspect(content);\n this.#layoutBook();\n this.#applyShift(this.#contentShift);\n // A fresh spread cancels any in-progress flip.\n this.#leaf.style.display = 'none';\n this.#leaf.style.transform = '';\n this.#leafShadow.style.opacity = '0';\n }\n\n beginFlip(\n from: SpreadContent,\n to: SpreadContent,\n direction: FlipDirection,\n options?: RenderOptions,\n ): void {\n if (options?.fill) {\n // Single-page: a full-width leaf turns about one edge, revealing `to` underneath.\n this.#pageLeft.style.width = '100%';\n this.#pageRight.style.display = 'none';\n this.#leaf.style.width = '100%';\n this.#leaf.style.left = '0';\n this.#leaf.style.transformOrigin = direction === 'forward' ? 'left center' : 'right center';\n this.#paint(this.#pageLeft, to.right ?? to.left);\n this.#paint(this.#leafFront, from.right ?? from.left);\n this.#paint(this.#leafBack, to.right ?? to.left);\n } else {\n this.#pageLeft.style.width = '50%';\n this.#pageRight.style.display = '';\n this.#leaf.style.width = '50%';\n if (direction === 'forward') {\n // Right page lifts and swings left about the spine. Left stays; right reveals `to`.\n this.#paint(this.#pageLeft, from.left);\n this.#paint(this.#pageRight, to.right);\n this.#paint(this.#leafFront, from.right);\n this.#paint(this.#leafBack, to.left);\n this.#leaf.style.left = '50%';\n this.#leaf.style.transformOrigin = 'left center';\n } else {\n // Left page swings right about its right edge. Right stays; left reveals `to`.\n this.#paint(this.#pageRight, from.right);\n this.#paint(this.#pageLeft, to.left);\n this.#paint(this.#leafFront, from.left);\n this.#paint(this.#leafBack, to.right);\n this.#leaf.style.left = '0';\n this.#leaf.style.transformOrigin = 'right center';\n }\n }\n this.#fromShiftUnit = options?.fill ? 0 : shiftUnit(from);\n this.#toShiftUnit = options?.fill ? 0 : shiftUnit(to);\n this.#fill = options?.fill ?? false;\n this.#trackAspect(to);\n this.#layoutBook();\n this.#applyShift(this.#fromShiftUnit);\n this.#leaf.style.display = 'block';\n this.#leaf.style.transform = 'rotateY(0deg)';\n this.#leafShadow.style.background = foldOverlay(direction);\n this.#leafShadow.style.opacity = '0';\n }\n\n setFlipProgress(t: number, direction: FlipDirection): void {\n const pose = flipProgressToPose(t);\n const deg = pose.angle * RAD_TO_DEG;\n this.#applyShift(this.#fromShiftUnit + (this.#toShiftUnit - this.#fromShiftUnit) * t);\n this.#leaf.style.display = 'block';\n this.#leaf.style.transform = `rotateY(${direction === 'forward' ? -deg : deg}deg)`;\n this.#leafShadow.style.opacity = String(pose.shadowAlpha);\n }\n\n #applyShift(unit: number): void {\n const px = unit * (this.#bookW / 4);\n this.#book.style.transform = px ? `translateX(${px}px)` : '';\n }\n\n /** Remember the page aspect (width/height) so the book can be letterboxed to it. */\n #trackAspect(content: SpreadContent): void {\n const p = content.left ?? content.right;\n if (p && p.height) this.#pageAspect = p.width / p.height;\n }\n\n /** Letterbox the 2-page book to the page aspect, centered in the container, so pages\n * fill their halves without stretching; the surround is the container background. */\n #layoutBook(): void {\n const b = this.#bookBox();\n this.#bookW = b.width;\n const s = this.#book.style;\n s.inset = 'auto';\n s.left = `${b.x}px`;\n s.top = `${b.y}px`;\n s.width = `${b.width}px`;\n s.height = `${b.height}px`;\n }\n\n /** The fitted book rect in container px (letterbox aware) — a stable 2-page area. */\n #bookBox(): { x: number; y: number; width: number; height: number } {\n const cw = this.#container?.clientWidth ?? 0;\n const ch = this.#container?.clientHeight ?? 0;\n if (this.#pageAspect <= 0 || cw <= 0 || ch <= 0) return { x: 0, y: 0, width: cw, height: ch };\n const ba = (this.#fill ? 1 : 2) * this.#pageAspect;\n let bw = cw;\n let bh = ch;\n if (ba > cw / ch) bh = cw / ba;\n else bw = ch * ba;\n return { x: (cw - bw) / 2, y: (ch - bh) / 2, width: bw, height: bh };\n }\n\n /** Where the current spread is actually painted: the book, or its centered half for a lone\n * page (mirrors #applyShift). This is what the engine hit-tests against. */\n #contentBox(): { x: number; y: number; width: number; height: number } {\n const b = this.#bookBox();\n return this.#contentShift !== 0 ? { x: b.x + b.width / 4, y: b.y, width: b.width / 2, height: b.height } : b;\n }\n\n setViewTransform(scale: number, x: number, y: number): void {\n this.#viewport.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;\n }\n\n measure(): LayoutMetrics {\n const el = this.#container;\n const w = el?.clientWidth ?? 0;\n const h = el?.clientHeight ?? 0;\n const known = this.#pageAspect > 0;\n const book = known ? this.#bookBox() : undefined;\n const content = known ? this.#contentBox() : undefined;\n return { containerWidth: w, containerHeight: h, pageWidth: w / 2, pageHeight: h, book, content };\n }\n\n #makeCanvas(doc: Document, className: string, extra: string): HTMLCanvasElement {\n const c = doc.createElement('canvas');\n c.className = className;\n c.style.cssText = `position:absolute;top:0;width:50%;height:100%;${extra}`;\n return c;\n }\n\n #paint(canvas: HTMLCanvasElement, content: PageContent | null): void {\n const ctx = canvas.getContext('2d');\n if (content === null) {\n ctx?.clearRect(0, 0, canvas.width, canvas.height);\n return;\n }\n canvas.width = content.width;\n canvas.height = content.height;\n ctx?.drawImage(content, 0, 0);\n }\n}\n","/**\n * Shared grid mesh + normal utilities for the page-curl models.\n *\n * A curl model fills `positions`/`normals` in place each frame; UVs are static grid\n * coordinates (no texture swimming). All models bend the same (cols+1)×(rows+1) grid.\n */\n\nexport interface PageMesh {\n readonly cols: number;\n readonly rows: number;\n /** ((cols+1)*(rows+1))*2 — static grid UVs (u across width, v down height). */\n readonly uvs: Float32Array;\n /** *3 — deformed positions, updated by the curl model. */\n readonly positions: Float32Array;\n /** *3 — per-vertex normals, updated by the curl model. */\n readonly normals: Float32Array;\n}\n\n/** Allocate a (cols+1)×(rows+1) grid mesh with static UVs (positions/normals filled by a curl). */\nexport function createPageMesh(cols: number, rows: number): PageMesh {\n const nx = cols + 1;\n const ny = rows + 1;\n const count = nx * ny;\n const uvs = new Float32Array(count * 2);\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n const k = j * nx + i;\n uvs[k * 2] = i / cols;\n uvs[k * 2 + 1] = j / rows;\n }\n }\n return {\n cols,\n rows,\n uvs,\n positions: new Float32Array(count * 3),\n normals: new Float32Array(count * 3),\n };\n}\n\n/** Per-vertex normals from central differences of the deformed grid (front = +z when flat). */\nexport function computeNormals(mesh: PageMesh): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n const nrm = mesh.normals;\n\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n const iPrev = i > 0 ? i - 1 : i;\n const iNext = i < nx - 1 ? i + 1 : i;\n const jPrev = j > 0 ? j - 1 : j;\n const jNext = j < ny - 1 ? j + 1 : j;\n\n const a = (j * nx + iNext) * 3;\n const b = (j * nx + iPrev) * 3;\n const c = (jNext * nx + i) * 3;\n const e = (jPrev * nx + i) * 3;\n\n const tuX = pos[a]! - pos[b]!;\n const tuY = pos[a + 1]! - pos[b + 1]!;\n const tuZ = pos[a + 2]! - pos[b + 2]!;\n const tvX = pos[c]! - pos[e]!;\n const tvY = pos[c + 1]! - pos[e + 1]!;\n const tvZ = pos[c + 2]! - pos[e + 2]!;\n\n let nx0 = tuY * tvZ - tuZ * tvY;\n let ny0 = tuZ * tvX - tuX * tvZ;\n let nz0 = tuX * tvY - tuY * tvX;\n const l = Math.hypot(nx0, ny0, nz0) || 1;\n nx0 /= l;\n ny0 /= l;\n nz0 /= l;\n\n const k = (j * nx + i) * 3;\n nrm[k] = nx0;\n nrm[k + 1] = ny0;\n nrm[k + 2] = nz0;\n }\n }\n}\n","import { computeNormals, type PageMesh } from './mesh';\n\n/**\n * Roll-and-flop. The leaf wraps a cylinder whose wrap angle grows 0→π over the first\n * half while its radius (= W/theta) shrinks, so the sheet rolls up off the surface in\n * place; over the second half it unwraps π→0 while rigidly rotating around the spine\n * 0→-π down onto the far side. Arc length across the width is exactly preserved.\n */\nexport function deformRoll(mesh: PageMesh, W: number, H: number, t: number): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n const theta = Math.max(1e-6, t < 0.5 ? t * 2 * Math.PI : (1 - (t - 0.5) * 2) * Math.PI);\n const radius = W / theta;\n const rot = t < 0.5 ? 0 : -(t - 0.5) * 2 * Math.PI;\n const cosR = Math.cos(rot);\n const sinR = Math.sin(rot);\n\n for (let i = 0; i < nx; i++) {\n const rad = (i / mesh.cols) * theta;\n const cx = Math.sin(rad) * radius;\n const cz = (1 - Math.cos(rad)) * radius;\n const X = cx * cosR + cz * sinR;\n const Z = -cx * sinR + cz * cosR;\n for (let j = 0; j < ny; j++) {\n const k = (j * nx + i) * 3;\n pos[k] = X;\n pos[k + 1] = (j / mesh.rows) * H;\n pos[k + 2] = Z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/**\n * Cone angle at peak curl. π/2 is flat; smaller values wind the sheet tighter.\n * ~30° gives a soft, paper-like ridge without self-intersection on typical page\n * aspect ratios (Hong / Nuon / Project Austin all land in the 18–35° band).\n */\nconst THETA_MIN = (30 * Math.PI) / 180;\n/** Gentler opening for full-width lone pages — a tight cone reads as a funnel when\n * the leaf owns the whole viewport and hinges at the edge. */\nconst THETA_MIN_FILL = (44 * Math.PI) / 180;\n\n/**\n * How far past the page the cone apex sits, as a fraction of height, at rest.\n * Closer mid-turn sharpens the diagonal; farther reads more cylindrical.\n * Kept moderately distant so the sheet stays page-like without cloth-y sliding.\n */\nconst APEX_REST = 1.35;\nconst APEX_PEAK = 0.7;\n/** Farther apex on fill: softer diagonal, less cloth-y twist across the full leaf. */\nconst APEX_REST_FILL = 1.85;\nconst APEX_PEAK_FILL = 1.15;\n\n/**\n * Above this |anchor.y − 0.5| the deform is a pure cone; at the mid-edge it\n * blends fully to a cylinder (Project Austin’s conicContribution).\n */\nconst CORNER_PURE = 0.42;\n\n/**\n * Power on fill flop progress. Slightly >1 delays spine rotation so the cone develops\n * over the page before the leaf swings past the edge hinge — kept close to 1 so the\n * flop isn't packed into a late whip (lone pages dissolve rather than land).\n */\nconst FILL_RHO_POWER = 1.08;\n\n/**\n * Natural conical page curl — the Xerox PARC / iBooks model.\n *\n * The leaf wraps an imaginary right circular cone whose apex rides the spine and\n * whose opening angle θ breathes over the turn (Hong, Card & Chen, 2004). A corner\n * grab yields a diagonal cone; a mid-edge grab blends toward a cylinder so the\n * free edge lifts as one. The curled sheet then rotates about the spine and settles\n * flat on the far side. Arc length from the apex is preserved, so the paper never\n * stretches — the motion reads as stiff, premium stock rather than cloth.\n *\n * On a full-width lone page (`anchor.fill`) the hinge is the container edge with no\n * facing half to land on: the cone softens, the flop is delayed so the peel stays\n * over the page longer, and the renderer dissolves the sheet as it finishes.\n */\nexport function deformCone(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n const fill = !!anchor.fill;\n\n // Curl envelope: 0 at the ends (flat), 1 at mid-turn. Soft power keeps the\n // ridge full through the middle third — paper holds its bend rather than\n // peaking and collapsing.\n const curl = Math.pow(Math.sin(Math.PI * t), 0.85);\n\n // θ: π/2 (identity) → THETA_MIN (pronounced curl) → π/2.\n const thetaMin = fill ? THETA_MIN_FILL : THETA_MIN;\n const theta = Math.PI / 2 - curl * (Math.PI / 2 - thetaMin);\n const sinTheta = Math.sin(theta);\n const cosTheta = Math.cos(theta);\n const invSinTheta = 1 / Math.max(sinTheta, 1e-6);\n\n // Apex distance: pulled in as the curl develops for a tighter diagonal.\n const apexRest = fill ? APEX_REST_FILL : APEX_REST;\n const apexPeak = fill ? APEX_PEAK_FILL : APEX_PEAK;\n const apexDist = H * (apexRest + (apexPeak - apexRest) * curl);\n\n // Corner bias: −1 = top edge, +1 = bottom. Near 0 → cylinder blend.\n const bias = (anchor.y - 0.5) * 2;\n const fromBottom = bias >= 0;\n const absBias = Math.abs(bias);\n let conic = Math.min(1, absBias / CORNER_PURE);\n const cylWeight = 1 - conic;\n\n // Cylinder radius matched to the cone’s free-edge cross-section (Austin), so\n // the blend is continuous as the grab slides from corner to mid-edge.\n // Apex sits past the *leading* edge (below for a bottom grab, above for top).\n const AyRef = fromBottom ? H + apexDist : -apexDist;\n const cylR = Math.hypot(W, 0.5 * H - AyRef);\n const cylRadius = Math.max(cylR * sinTheta, W / Math.PI);\n\n // Spine rotation: concurrent with the cone so the page flops while it curls.\n // Same x–z convention as `roll` (ρ: 0 → −π). On fill, delay the flop so the\n // peel develops over the page before the leaf swings past the edge hinge.\n const rhoT = fill ? Math.pow(t, FILL_RHO_POWER) : t;\n const rho = -Math.PI * rhoT;\n const cosR = Math.cos(rho);\n const sinR = Math.sin(rho);\n\n // Near the rests the cone is the identity plane. A finite-radius cylinder blend\n // still carries a tiny residual arc — skip it and rotate the flat sheet.\n if (curl < 1e-4) {\n for (let j = 0; j < ny; j++) {\n const y = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n const k = (j * nx + i) * 3;\n pos[k] = x0 * cosR;\n pos[k + 1] = y;\n pos[k + 2] = -x0 * sinR;\n }\n }\n computeNormals(mesh);\n return;\n }\n\n for (let j = 0; j < ny; j++) {\n const y0 = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n\n let cx = x0;\n let cy = y0;\n let cz = 0;\n\n if (conic > 1e-5) {\n // Canonical cone: apex above the page (A < 0) makes the *near* edge lead.\n // In our y-down leaf, that means the top. For a bottom grab we flip into\n // that space, deform, then flip back — one code path for both corners.\n const yIn = fromBottom ? H - y0 : y0;\n const A = -apexDist;\n const R = Math.hypot(x0, yIn - A);\n if (R > 1e-8) {\n const r = R * sinTheta;\n const beta = Math.asin(Math.min(1, Math.max(0, x0 / R))) * invSinTheta;\n const oneCos = 1 - Math.cos(beta);\n const yCone = R + A - r * oneCos * sinTheta;\n cx = r * Math.sin(beta);\n cy = fromBottom ? H - yCone : yCone;\n cz = r * oneCos * cosTheta;\n }\n }\n\n let bx = cx;\n let by = cy;\n let bz = cz;\n\n if (cylWeight > 1e-5) {\n const beta = x0 / cylRadius;\n const sx = cylRadius * Math.sin(beta);\n const sz = cylRadius * (1 - Math.cos(beta));\n bx = conic * cx + cylWeight * sx;\n by = conic * cy + cylWeight * y0;\n bz = conic * cz + cylWeight * sz;\n }\n\n const k = (j * nx + i) * 3;\n pos[k] = bx * cosR + bz * sinR;\n pos[k + 1] = by;\n pos[k + 2] = -bx * sinR + bz * cosR;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/** Peak material width occupied by the bending wave. */\nconst MAX_BEND_LENGTH = 0.34;\n\n/** Maximum tilt of the rolling axis for a corner-led turn. */\nconst MAX_TILT = (11 * Math.PI) / 180;\n\nconst PROFILE_STEPS = 128;\n\n/**\n * Unit-length raised-cosine curvature profile.\n *\n * κ(s) = π(1 - cos(2πs)), so its integrated tangent angle is\n * θ(s) = πs - ½sin(2πs). Curvature is exactly zero at both ends and integrates\n * to π: the incoming and outgoing planes are flat, opposite-facing, and meet\n * the bend without a crease. X/Z are arc-length integrals of cos(θ)/sin(θ).\n */\nfunction makeProfile(): { x: Float64Array; z: Float64Array } {\n const x = new Float64Array(PROFILE_STEPS + 1);\n const z = new Float64Array(PROFILE_STEPS + 1);\n const ds = 1 / PROFILE_STEPS;\n const theta = (s: number): number => Math.PI * s - 0.5 * Math.sin(2 * Math.PI * s);\n for (let i = 1; i <= PROFILE_STEPS; i++) {\n const a = theta((i - 1) * ds);\n const b = theta(i * ds);\n x[i] = x[i - 1]! + 0.5 * (Math.cos(a) + Math.cos(b)) * ds;\n z[i] = z[i - 1]! + 0.5 * (Math.sin(a) + Math.sin(b)) * ds;\n }\n // Symmetry makes this analytically zero; remove accumulated floating-point drift.\n x[PROFILE_STEPS] = 0;\n return { x, z };\n}\n\nconst PROFILE = makeProfile();\nconst PROFILE_HEIGHT = PROFILE.z[PROFILE_STEPS]!;\n\nfunction sampleProfile(s: number): [x: number, z: number] {\n const p = Math.max(0, Math.min(1, s)) * PROFILE_STEPS;\n const i = Math.min(PROFILE_STEPS - 1, Math.floor(p));\n const f = p - i;\n return [\n PROFILE.x[i]! + (PROFILE.x[i + 1]! - PROFILE.x[i]!) * f,\n PROFILE.z[i]! + (PROFILE.z[i + 1]! - PROFILE.z[i]!) * f,\n ];\n}\n\n/**\n * Traveling smooth-curvature leaf curl.\n *\n * Inspired by rolling-cylinder page curls and modern developable-surface book\n * simulations, but with a compact raised-cosine curvature wave instead of a\n * constant-radius cylinder. The sheet has three regions:\n *\n * 1. untouched paper before the wave,\n * 2. an isometric bend whose curvature smoothly rises and falls,\n * 3. a flat, reflected flap after the wave.\n *\n * The wave travels from the free edge to just beyond the spine while its width grows\n * and collapses. Integrating a unit tangent preserves material length exactly; straight\n * ruling lines preserve distance along the curl axis. Curvature reaches zero at both\n * seams, eliminating the hard crease shared by circular/fold models.\n */\nexport function deformLeaf(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n // Exact rest poses avoid the tiny numerical ridge left by a vanishing wave.\n if (t <= 1e-7 || t >= 1 - 1e-7) {\n const side = t < 0.5 ? 1 : -1;\n for (let j = 0; j < ny; j++) {\n const y = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const k = (j * nx + i) * 3;\n pos[k] = side * (i / mesh.cols) * W;\n pos[k + 1] = y;\n pos[k + 2] = 0;\n }\n }\n computeNormals(mesh);\n return;\n }\n\n // The wave broadens through the middle, then tightens as the leaf settles.\n const envelope = Math.pow(Math.sin(Math.PI * t), 0.72);\n const bendLength = Math.max(1e-6, W * MAX_BEND_LENGTH * envelope);\n\n // Bottom grabs tilt the ruling clockwise in page coordinates; top grabs mirror it.\n // The tilt fades at both rest states so the page lands exactly square.\n const anchorBias = Math.max(-1, Math.min(1, (anchor.y - 0.5) * 2));\n const tilt = anchorBias * MAX_TILT * Math.pow(Math.sin(Math.PI * t), 1.15);\n const nX = Math.cos(tilt);\n const nY = Math.sin(tilt);\n const fX = -nY;\n const fY = nX;\n\n // Place the axis outside the whole rectangle at t=0 and beyond the spine at t=1.\n // Accounting for tilt keeps every row in the correct physical region at the ends.\n const diagonalReach = Math.abs(nY) * H * 0.5;\n const startX = W + diagonalReach / nX;\n const endX = -(bendLength + diagonalReach) / nX;\n const axisX = startX + (endX - startX) * t;\n const axisY = H * 0.5;\n\n for (let j = 0; j < ny; j++) {\n const y0 = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n const relX = x0 - axisX;\n const relY = y0 - axisY;\n const d = relX * nX + relY * nY;\n const e = relX * fX + relY * fY;\n const k = (j * nx + i) * 3;\n\n if (d <= 0) {\n // The wave has not reached this point: untouched front-facing paper.\n pos[k] = x0;\n pos[k + 1] = y0;\n pos[k + 2] = 0;\n continue;\n }\n\n let normalDistance: number;\n let z: number;\n if (d < bendLength) {\n // Scale the unit arc-length profile by the material width of this wave.\n const [profileX, profileZ] = sampleProfile(d / bendLength);\n normalDistance = bendLength * profileX;\n z = bendLength * profileZ;\n } else {\n // The tangent has rotated by π; paper leaves the wave flat and reversed.\n normalDistance = -(d - bendLength);\n z = bendLength * PROFILE_HEIGHT;\n }\n\n pos[k] = axisX + fX * e + nX * normalDistance;\n pos[k + 1] = axisY + fY * e + nY * normalDistance;\n pos[k + 2] = z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/**\n * Peak inertial tip lag in radians (~30°). How far the free edge trails the\n * spine rotation at the height of the acceleration phase, and symmetrically how\n * far it follows through while the turn decelerates.\n */\nconst SWAY = 0.52;\n\n/**\n * Extra lag for rows far from the grabbed corner (0 = whole edge moves as one).\n * The driven corner is held; the diagonal remainder of the sheet trails it.\n */\nconst LAG = 0.45;\n\n/**\n * Inertial follow-through page flick.\n *\n * The leaf is a cantilever clamped at the spine and driven through a rotation\n * ρ(t) = πt. Paper inertia (the Euler force, ∝ −ρ̈) bends the sheet opposite the\n * angular acceleration with a first-mode-like profile — zero curvature at the\n * clamped spine, maximal at the free edge. Under an eased turn the acceleration\n * changes sign at mid-flight, so the free edge trails while the turn winds up,\n * swings through vertical, then overtakes and settles as the turn slows: the\n * follow-through/overlapping-action principle, grounded in base-rotated\n * cantilever dynamics. Tangent angles are integrated along the width, so the\n * sheet never stretches, and the curvature direction reversing over the turn is\n * what none of the wrap-style curls do.\n *\n * Rows far from the grabbed corner lag slightly more than the driven row, so a\n * corner grab pulls a soft living diagonal across the sheet.\n */\nexport function deformFlick(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const cols = mesh.cols;\n const pos = mesh.positions;\n const ds = W / cols;\n\n // Base rotation about the spine, 0 → π (same landing convention as `simple`).\n const rho = Math.PI * t;\n\n // Inertial amplitude ∝ −ρ̈ of an eased unit turn: the tip trails (negative)\n // while accelerating, leads (positive) while braking, and is zero at both\n // rests and at peak speed mid-turn.\n const amplitude = -SWAY * Math.sin(2 * Math.PI * t);\n\n for (let j = 0; j < ny; j++) {\n const v = j / mesh.rows;\n // The grabbed row is driven directly; distance from it adds trailing inertia.\n const rowAmplitude = amplitude * (1 + LAG * Math.abs(v - anchor.y));\n const y = v * H;\n\n // Integrate the unit tangent of θ(u) = ρ + A·u² across the width (trapezoid\n // per grid cell). u² is the first-mode tangent shape: clamped flat at the\n // spine, curving ever more freely toward the edge. Exact arc length.\n let x = 0;\n let z = 0;\n let cosPrev = Math.cos(rho);\n let sinPrev = Math.sin(rho);\n pos[(j * nx) * 3] = 0;\n pos[(j * nx) * 3 + 1] = y;\n pos[(j * nx) * 3 + 2] = 0;\n for (let i = 1; i < nx; i++) {\n const u = i / cols;\n const theta = rho + rowAmplitude * u * u;\n const cosCur = Math.cos(theta);\n const sinCur = Math.sin(theta);\n x += 0.5 * (cosPrev + cosCur) * ds;\n z += 0.5 * (sinPrev + sinCur) * ds;\n cosPrev = cosCur;\n sinPrev = sinCur;\n\n const k = (j * nx + i) * 3;\n pos[k] = x;\n pos[k + 1] = y;\n pos[k + 2] = z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\n\n/**\n * Flat page turn — the plainest flip, matching the CSS renderer. The leaf stays a rigid\n * flat sheet and rotates around the spine (angle 0→π), going edge-on at the midpoint. No\n * cylinder bend; GPU-rendered here with the shared perspective so it reads like the CSS fold.\n */\nexport function deformSimple(mesh: PageMesh, W: number, H: number, t: number): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n const angle = t * Math.PI;\n const cosA = Math.cos(angle);\n const sinA = Math.sin(angle);\n\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W; // distance from the spine\n const X = x0 * cosA;\n const Z = x0 * sinA;\n for (let j = 0; j < ny; j++) {\n const k = (j * nx + i) * 3;\n pos[k] = X;\n pos[k + 1] = (j / mesh.rows) * H;\n pos[k + 2] = Z;\n }\n }\n\n computeNormals(mesh);\n}\n","/**\n * Page-curl registry. Each model turns a 0→1 flip progress into a bent page mesh; the\n * WebGL2 renderer picks one by the `curl` option. `anchored` models fold from a corner\n * given by `anchor.y` (0 = top, 1 = bottom — where the reader tapped); the others roll\n * the whole free edge and ignore it.\n *\n * Importing this module pulls in the deform math, so only the WebGL2 renderer does.\n * The engine references the lightweight `./types` (union + validation list) instead.\n */\nimport { deformRoll } from './roll';\nimport { deformCone } from './cone';\nimport { deformLeaf } from './leaf';\nimport { deformFlick } from './flick';\nimport { deformSimple } from './simple';\nimport type { CurlModel, CurlType } from './types';\n\nexport { createPageMesh, computeNormals, type PageMesh } from './mesh';\nexport { CURL_TYPES, DEFAULT_CURL } from './types';\nexport type { CurlAnchor, CurlDeform, CurlModel, CurlType } from './types';\n\nexport const CURLS: Record<CurlType, CurlModel> = {\n roll: { deform: deformRoll, anchored: false },\n cone: { deform: deformCone, anchored: true },\n leaf: { deform: deformLeaf, anchored: true },\n flick: { deform: deformFlick, anchored: true },\n simple: { deform: deformSimple, anchored: false },\n};\n","/** Create a WebGL2 context, or throw so the caller can fall back to CSS (§8.4). */\nexport function createGlContext(canvas: HTMLCanvasElement): WebGL2RenderingContext {\n const gl = canvas.getContext('webgl2', {\n alpha: true,\n premultipliedAlpha: true,\n antialias: true,\n depth: false,\n stencil: false,\n // We render on demand (not every frame), so the buffer must persist between\n // composites — otherwise the settled page flickers once the flip animation\n // stops and nothing is redrawing each frame.\n preserveDrawingBuffer: true,\n });\n if (gl === null) {\n throw new Error('WebglRenderer: could not create a WebGL2 context.');\n }\n return gl;\n}\n","/** Compile + link a shader program; throws with the driver log on failure. */\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vertexSrc: string,\n fragmentSrc: string,\n): WebGLProgram {\n const program = gl.createProgram();\n if (program === null) throw new Error('WebglRenderer: could not create a program.');\n\n const vs = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);\n const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n\n // Shaders can be detached/deleted once linked.\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program);\n gl.deleteProgram(program);\n throw new Error(`WebglRenderer: program link failed: ${log ?? 'unknown error'}`);\n }\n return program;\n}\n\nfunction compileShader(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {\n const shader = gl.createShader(type);\n if (shader === null) throw new Error('WebglRenderer: could not create a shader.');\n gl.shaderSource(shader, src);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`WebglRenderer: shader compile failed: ${log ?? 'unknown error'}`);\n }\n return shader;\n}\n","/** Create a page texture with clamp + linear filtering (no mipmaps — §8.3). */\nexport function createTexture(gl: WebGL2RenderingContext): WebGLTexture {\n const tex = gl.createTexture();\n if (tex === null) throw new Error('WebglRenderer: could not create a texture.');\n gl.bindTexture(gl.TEXTURE_2D, tex);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n return tex;\n}\n\n/** Upload a decoded page raster (ImageBitmap or canvas) into `tex`. */\nexport function uploadTexture(\n gl: WebGL2RenderingContext,\n tex: WebGLTexture,\n source: TexImageSource,\n): void {\n gl.bindTexture(gl.TEXTURE_2D, tex);\n // No Y flip: the vertex shader maps aUnit.y=0 to the page top and samples the\n // source's top row there. UNPACK_FLIP_Y is honored for <canvas> sources but\n // IGNORED for ImageBitmap, so flipping would invert PDF (canvas) pages while\n // leaving image (ImageBitmap) pages upright. Off keeps both consistent.\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n}\n","import {\n CURLS,\n createPageMesh,\n DEFAULT_CURL,\n type CurlAnchor,\n type CurlType,\n type PageMesh,\n} from '../geometry/curls';\nimport type { Spread } from '../engine/spread';\nimport type {\n FlipDirection,\n LayoutMetrics,\n Renderer,\n RenderOptions,\n SpreadContent,\n} from './types';\nimport { createGlContext } from './gl/context';\nimport { createProgram } from './gl/program';\nimport { createTexture, uploadTexture } from './gl/texture';\n\n// Flat program: a page rect (device px, top-left origin) with zoom/pan and a soft\n// permanent gutter shadow toward the spine so the turning leaf's shadow lands into it.\nconst FLAT_VERT = `#version 300 es\nin vec2 aUnit;\nuniform vec2 uViewport;\nuniform vec4 uRect;\nuniform vec3 uView;\nout vec2 vUv;\nvoid main() {\n vec2 px = uRect.xy + aUnit * uRect.zw;\n px = px * uView.x + uView.yz;\n vec2 clip = px / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUnit;\n}`;\n\nconst FLAT_FRAG = `#version 300 es\nprecision mediump float;\nin vec2 vUv;\nuniform sampler2D uTex;\nuniform float uGutterSide; // +1 = spine at right edge, -1 = at left edge, 0 = none\nout vec4 outColor;\nvoid main() {\n vec4 c = texture(uTex, vUv);\n if (uGutterSide != 0.0) {\n float d = uGutterSide > 0.0 ? (1.0 - vUv.x) : vUv.x;\n c.rgb *= mix(0.72, 1.0, smoothstep(0.0, 0.10, d));\n }\n outColor = c;\n}`;\n\n// Curl program: a thin pass-through for the CPU-deformed page mesh (positions and\n// normals come from the selected curl model). It places the leaf, applies a top-safe depth\n// perspective, and lights from the true surface normal.\nconst CURL_VERT = `#version 300 es\nin vec3 aPos; // leaf-local device px: x = bent offset from spine, y 0..H, z depth\nin vec3 aNormal;\nin vec2 aUv;\nuniform vec2 uViewport;\nuniform vec3 uView; // scale, tx, ty\nuniform float uOriginX; // spine x in device px\nuniform float uDir; // +1 leaf to the right of spine, -1 to the left\nuniform float uLeafW; // leaf width in device px (perspective scale)\nout vec2 vUv;\nout float vFacing;\nout float vU;\nvoid main() {\n float bookX = uOriginX + uDir * aPos.x;\n float bookY = aPos.y;\n float Z = aPos.z;\n // Eye distance, in leaf widths. Keeps the perspective resolution-independent. 5.3 puts the\n // roll's deepest point ~14% larger than flat; stronger than that (a nearer eye) balloons the\n // turning sheet and reads more like a fisheye than a page lifting.\n float D = uLeafW * 5.3;\n float persp = D / max(D - Z, 1.0);\n float cx = uViewport.x * 0.5;\n float cy = uViewport.y * 0.5;\n float px = cx + (bookX - cx) * persp;\n float py = cy + (bookY - cy) * min(persp, 1.0); // Y only shrinks (top-safe)\n vec2 p = vec2(px, py) * uView.x + uView.yz;\n vec2 clip = p / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUv;\n vFacing = aNormal.z;\n vU = aUv.x;\n}`;\n\nconst CURL_FRAG = `#version 300 es\nprecision mediump float;\nin vec2 vUv;\nin float vFacing;\nin float vU;\nuniform sampler2D uFront;\nuniform sampler2D uBack;\nuniform highp float uDir;\nuniform float uGloss; // 0 disables the specular highlight (e.g. the flat 'simple' curl)\nuniform float uAlpha; // leaf opacity; a lone page dissolves into the page landing beneath it\nout vec4 outColor;\nvoid main() {\n // The turn mesh carries its own facing in the normal; the front points at the viewer\n // (normal.z > 0) until the leaf passes vertical, for both flip directions. u->x still\n // flips with uDir so a left-hand leaf reads spine-inward.\n bool showFront = vFacing > 0.0;\n float fx = uDir > 0.0 ? vUv.x : 1.0 - vUv.x;\n vec4 c = showFront ? texture(uFront, vec2(fx, vUv.y)) : texture(uBack, vec2(1.0 - fx, vUv.y));\n\n // Fold shading: the sheet darkens sharply where it curves away from the viewer, so the\n // rolled edge reads as a deep crease with a soft highlight riding the ridge.\n float diff = clamp(abs(vFacing), 0.0, 1.0);\n float lit = mix(0.42, 1.0, diff);\n lit *= mix(0.7, 1.0, smoothstep(0.0, 0.12, vU)); // spine crease matches the gutter shadow\n\n // Glossy specular: brightest where the surface tilts ~30 deg from facing the viewer,\n // so a thin glossy highlight rides across the sheet as it rolls.\n float spec = pow(max(cos(acos(diff) - 0.52), 0.0), 220.0) * 0.22 * uGloss;\n\n c.rgb = clamp(c.rgb * lit + spec, 0.0, 1.0);\n // Premultiplied alpha (blendFunc ONE, ONE_MINUS_SRC_ALPHA): scale colour as well as alpha,\n // or fading would brighten the sheet additively instead of dissolving it.\n outColor = vec4(c.rgb * uAlpha, c.a * uAlpha);\n}`;\n\nconst GRID_COLS = 28;\nconst GRID_ROWS = 36;\n/** Progress at which a lone page starts dissolving. A full-width leaf has no facing half to\n * flop onto, so instead of landing on empty space it fades into the page arriving beneath it\n * over the rest of the turn. Roll is already flat by mid-turn; curling models stay bent\n * longer, so they dissolve later. The fade itself eases so the leaf settles rather than\n * vanishing while still swinging. */\nconst FILL_FADE_START_ROLL = 0.7;\nconst FILL_FADE_START_CURL = 0.8;\nconst RESTORE_TIMEOUT_MS = 4000;\nconst MAX_TEXTURE_CACHE_BYTES = 256 * 1024 * 1024;\n\ninterface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\ninterface FlipState {\n underLeft: TexImageSource | null;\n underRight: TexImageSource | null;\n underFull: TexImageSource | null; // fill mode\n front: TexImageSource | null;\n back: TexImageSource | null;\n dir: number; // +1 forward (leaf right of spine), -1 backward (left)\n fill: boolean;\n}\n\n/** How far to shift a spread to centre a lone page, in units of a quarter container width:\n * -1 = only a right page (cover), +1 = only a left page (back), 0 = a full spread. */\nfunction shiftUnit(content: SpreadContent): number {\n const lone = (content.left === null) !== (content.right === null);\n return lone ? (content.right !== null ? -1 : 1) : 0;\n}\n\n/**\n * GPU renderer (WebGL2, hand-written). The turning leaf is a dense grid deformed on\n * the CPU by the selected curl model (see geometry/curls) and\n * uploaded each frame, lit from real normals; the static pages are flat quads with a\n * gutter shadow. Restores\n * GL state on context loss; signals onFatal (→ CSS fallback) when unrecoverable.\n */\nexport class WebglRenderer implements Renderer {\n #container: HTMLElement | null = null;\n #canvas: HTMLCanvasElement | null = null;\n #gl: WebGL2RenderingContext | null = null;\n\n #flat: WebGLProgram | null = null;\n #curl: WebGLProgram | null = null;\n #quadVao: WebGLVertexArrayObject | null = null;\n #curlVao: WebGLVertexArrayObject | null = null;\n #posBuf: WebGLBuffer | null = null;\n #normBuf: WebGLBuffer | null = null;\n #idxCount = 0;\n #mesh: PageMesh = createPageMesh(GRID_COLS, GRID_ROWS);\n\n #texCache = new Map<TexImageSource, { tex: WebGLTexture; bytes: number }>();\n #cacheBytes = 0;\n #blankTex: WebGLTexture | null = null;\n\n #flatU: Record<string, WebGLUniformLocation | null> = {};\n #curlU: Record<string, WebGLUniformLocation | null> = {};\n\n #view = { scale: 1, tx: 0, ty: 0 };\n #dpr = 1;\n #content: SpreadContent | null = null;\n #fill = false;\n #flip: FlipState | null = null;\n #flipT = 0;\n #curlType: CurlType = DEFAULT_CURL;\n #anchor: CurlAnchor = { y: 0.5 };\n // A lone page (cover / book front-back) sits on one half; these center it by shifting\n // the view a quarter-width, interpolated across a flip so the open/close doesn't jump.\n #shiftX = 0;\n #fromShiftUnit = 0;\n #toShiftUnit = 0;\n // Book aspect fit: the 2-page spread is letterboxed to the pages' aspect, centered\n // in the container, so pages fill their halves without stretching. 0 = aspect unknown.\n #pageAspect = 0;\n #bookW = 0;\n #bookH = 0;\n\n #contextLost = false;\n #fatalFired = false;\n #restoreTimer: ReturnType<typeof setTimeout> | null = null;\n #fatalHandler: (() => void) | null = null;\n\n mount(container: HTMLElement): Promise<void> {\n this.#container = container;\n const doc = container.ownerDocument;\n const canvas = doc.createElement('canvas');\n canvas.style.cssText = 'display:block;width:100%;height:100%;';\n container.append(canvas);\n this.#canvas = canvas;\n\n canvas.addEventListener('webglcontextlost', this.#onContextLost as EventListener);\n canvas.addEventListener('webglcontextrestored', this.#onContextRestored as EventListener);\n doc.addEventListener('visibilitychange', this.#onVisibilityChange);\n\n this.#gl = createGlContext(canvas); // throws if unavailable -> engine falls back (§8.4)\n this.#buildGlResources();\n return Promise.resolve();\n }\n\n destroy(): void {\n this.#clearRestoreTimer();\n const canvas = this.#canvas;\n if (canvas !== null) {\n canvas.removeEventListener('webglcontextlost', this.#onContextLost as EventListener);\n canvas.removeEventListener('webglcontextrestored', this.#onContextRestored as EventListener);\n canvas.ownerDocument.removeEventListener('visibilitychange', this.#onVisibilityChange);\n }\n const gl = this.#gl;\n if (gl !== null) {\n for (const cached of this.#texCache.values()) gl.deleteTexture(cached.tex);\n if (this.#blankTex !== null) gl.deleteTexture(this.#blankTex);\n if (this.#quadVao !== null) gl.deleteVertexArray(this.#quadVao);\n if (this.#curlVao !== null) gl.deleteVertexArray(this.#curlVao);\n if (this.#flat !== null) gl.deleteProgram(this.#flat);\n if (this.#curl !== null) gl.deleteProgram(this.#curl);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n canvas?.remove();\n this.#texCache.clear();\n this.#cacheBytes = 0;\n this.#blankTex = null;\n this.#container = null;\n this.#canvas = null;\n this.#gl = null;\n this.#content = null;\n this.#flip = null;\n }\n\n onFatal(handler: () => void): void {\n this.#fatalHandler = handler;\n }\n\n renderSpread(_spread: Spread, content: SpreadContent, options?: RenderOptions): void {\n this.#content = content;\n this.#fill = options?.fill ?? false;\n this.#flip = null;\n this.#trackAspect(content);\n this.#render();\n }\n\n /** Remember the page aspect (width/height) so the book can be letterboxed to it. */\n #trackAspect(content: SpreadContent): void {\n const p = content.left ?? content.right;\n if (p && p.height) this.#pageAspect = p.width / p.height;\n }\n\n beginFlip(\n from: SpreadContent,\n to: SpreadContent,\n direction: FlipDirection,\n options?: RenderOptions,\n ): void {\n const fill = options?.fill ?? false;\n this.#flipT = 0;\n if (options?.curl) this.#curlType = options.curl;\n if (options?.anchor) this.#anchor = options.anchor;\n this.#fromShiftUnit = fill ? 0 : shiftUnit(from);\n this.#toShiftUnit = fill ? 0 : shiftUnit(to);\n this.#trackAspect(to);\n if (fill) {\n this.#flip = {\n underLeft: null,\n underRight: null,\n underFull: to.right ?? to.left,\n front: from.right ?? from.left,\n // A lone page has no facing \"next\" leaf on its back; show the same page mirrored\n // (the shader flips the back UV) so it reads as the sheet's own reverse side.\n back: from.right ?? from.left,\n dir: direction === 'forward' ? 1 : -1,\n fill: true,\n };\n } else if (direction === 'forward') {\n this.#flip = {\n underLeft: from.left,\n underRight: to.right,\n underFull: null,\n front: from.right,\n back: to.left,\n dir: 1,\n fill: false,\n };\n } else {\n this.#flip = {\n underLeft: to.left,\n underRight: from.right,\n underFull: null,\n front: from.left,\n back: to.right,\n dir: -1,\n fill: false,\n };\n }\n this.#render();\n }\n\n setFlipProgress(t: number, _direction: FlipDirection): void {\n this.#flipT = t;\n this.#render();\n }\n\n setViewTransform(scale: number, x: number, y: number): void {\n this.#view = { scale, tx: x, ty: y };\n this.#render();\n }\n\n measure(): LayoutMetrics {\n const el = this.#container;\n const w = el?.clientWidth ?? 0;\n const h = el?.clientHeight ?? 0;\n const known = this.#pageAspect > 0;\n const book = known ? this.#bookBox() : undefined;\n const content = known ? this.#contentBox() : undefined;\n return { containerWidth: w, containerHeight: h, pageWidth: w / 2, pageHeight: h, book, content };\n }\n\n /** The fitted book rect in container CSS px (letterbox aware) — a stable 2-page area. */\n #bookBox(): { x: number; y: number; width: number; height: number } {\n const el = this.#container;\n const cw = el?.clientWidth ?? 0;\n const ch = el?.clientHeight ?? 0;\n if (this.#pageAspect <= 0 || cw <= 0 || ch <= 0) return { x: 0, y: 0, width: cw, height: ch };\n const ba = (this.#fill ? 1 : 2) * this.#pageAspect;\n let bw = cw;\n let bh = ch;\n if (ba > cw / ch) bh = cw / ba;\n else bw = ch * ba;\n return { x: (cw - bw) / 2, y: (ch - bh) / 2, width: bw, height: bh };\n }\n\n /** Where the current spread is actually painted: the book, or its centered half for a lone\n * page (mirrors the draw-time #shiftX centering). This is what the engine hit-tests against. */\n #contentBox(): { x: number; y: number; width: number; height: number } {\n const b = this.#bookBox();\n const lone = !this.#fill && this.#content !== null && shiftUnit(this.#content) !== 0;\n return lone ? { x: b.x + b.width / 4, y: b.y, width: b.width / 2, height: b.height } : b;\n }\n\n // --- context loss / restore ---------------------------------------------------\n\n #onContextLost = (event: Event): void => {\n event.preventDefault();\n this.#contextLost = true;\n if (this.#restoreTimer === null) {\n this.#restoreTimer = setTimeout(() => this.#fatal(), RESTORE_TIMEOUT_MS);\n }\n };\n\n #onContextRestored = (): void => {\n this.#clearRestoreTimer();\n this.#contextLost = false;\n this.#buildGlResources();\n this.#render();\n };\n\n #onVisibilityChange = (): void => {\n if (this.#canvas?.ownerDocument.visibilityState === 'visible') this.#render();\n };\n\n #fatal(): void {\n if (this.#fatalFired) return;\n this.#fatalFired = true;\n this.#fatalHandler?.();\n }\n\n #clearRestoreTimer(): void {\n if (this.#restoreTimer !== null) {\n clearTimeout(this.#restoreTimer);\n this.#restoreTimer = null;\n }\n }\n\n #buildGlResources(): void {\n const gl = this.#gl;\n if (gl === null) return;\n\n this.#flat = createProgram(gl, FLAT_VERT, FLAT_FRAG);\n this.#curl = createProgram(gl, CURL_VERT, CURL_FRAG);\n for (const name of ['uViewport', 'uRect', 'uView', 'uTex', 'uGutterSide']) {\n this.#flatU[name] = gl.getUniformLocation(this.#flat, name);\n }\n for (const name of ['uViewport', 'uView', 'uOriginX', 'uDir', 'uLeafW', 'uFront', 'uBack', 'uGloss', 'uAlpha']) {\n this.#curlU[name] = gl.getUniformLocation(this.#curl, name);\n }\n\n this.#quadVao = this.#makeQuadVao(gl, this.#flat);\n this.#buildCurlMesh(gl, this.#curl);\n\n this.#texCache.clear();\n this.#cacheBytes = 0;\n this.#blankTex = this.#makeBlankTexture(gl);\n gl.useProgram(this.#flat);\n gl.uniform1i(this.#flatU.uTex ?? null, 0);\n gl.useProgram(this.#curl);\n gl.uniform1i(this.#curlU.uFront ?? null, 0);\n gl.uniform1i(this.#curlU.uBack ?? null, 1);\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // premultiplied alpha\n }\n\n #makeQuadVao(gl: WebGL2RenderingContext, program: WebGLProgram): WebGLVertexArrayObject {\n const vao = gl.createVertexArray();\n const buffer = gl.createBuffer();\n if (vao === null || buffer === null) throw new Error('WebglRenderer: could not create buffers.');\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW);\n const aUnit = gl.getAttribLocation(program, 'aUnit');\n gl.enableVertexAttribArray(aUnit);\n gl.vertexAttribPointer(aUnit, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n return vao;\n }\n\n #buildCurlMesh(gl: WebGL2RenderingContext, program: WebGLProgram): void {\n const nx = GRID_COLS + 1;\n const indices: number[] = [];\n for (let j = 0; j < GRID_ROWS; j++) {\n for (let i = 0; i < GRID_COLS; i++) {\n const a = j * nx + i;\n const b = a + 1;\n const c = a + nx;\n const d = c + 1;\n indices.push(a, c, b, b, c, d);\n }\n }\n this.#idxCount = indices.length;\n\n const vao = gl.createVertexArray();\n this.#posBuf = gl.createBuffer();\n this.#normBuf = gl.createBuffer();\n const uvBuf = gl.createBuffer();\n const idxBuf = gl.createBuffer();\n if (vao === null || this.#posBuf === null || this.#normBuf === null || uvBuf === null || idxBuf === null) {\n throw new Error('WebglRenderer: could not create mesh buffers.');\n }\n gl.bindVertexArray(vao);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#posBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.positions, gl.DYNAMIC_DRAW);\n const aPos = gl.getAttribLocation(program, 'aPos');\n gl.enableVertexAttribArray(aPos);\n gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#normBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.normals, gl.DYNAMIC_DRAW);\n const aNormal = gl.getAttribLocation(program, 'aNormal');\n gl.enableVertexAttribArray(aNormal);\n gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.uvs, gl.STATIC_DRAW);\n const aUv = gl.getAttribLocation(program, 'aUv');\n gl.enableVertexAttribArray(aUv);\n gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxBuf);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);\n\n gl.bindVertexArray(null);\n this.#curlVao = vao;\n }\n\n // --- drawing ------------------------------------------------------------------\n\n #render(): void {\n const gl = this.#gl;\n const canvas = this.#canvas;\n if (gl === null || canvas === null || this.#contextLost || gl.isContextLost()) return;\n this.#resize();\n gl.clear(gl.COLOR_BUFFER_BIT);\n this.#fitBook();\n if (this.#flip !== null) this.#drawFlip();\n else if (this.#content !== null) this.#drawStatic();\n }\n\n /** Letterbox the 2-page book to the page aspect, centered; restricts the GL viewport\n * so pages fill their halves without stretching (the surround stays cleared). */\n #fitBook(): void {\n const gl = this.#gl!;\n const canvas = this.#canvas!;\n const cw = canvas.width;\n const ch = canvas.height;\n if (this.#pageAspect > 0) {\n const bookAspect = (this.#fill ? 1 : 2) * this.#pageAspect;\n if (bookAspect > cw / ch) {\n this.#bookW = cw;\n this.#bookH = Math.round(cw / bookAspect);\n } else {\n this.#bookH = ch;\n this.#bookW = Math.round(ch * bookAspect);\n }\n } else {\n this.#bookW = cw;\n this.#bookH = ch;\n }\n const bx = Math.round((cw - this.#bookW) / 2);\n const by = Math.round((ch - this.#bookH) / 2); // centered, so GL's bottom offset == by\n gl.viewport(bx, by, this.#bookW, this.#bookH);\n }\n\n #drawStatic(): void {\n const w = this.#bookW;\n const h = this.#bookH;\n const content = this.#content!;\n this.#shiftX = this.#fill ? 0 : shiftUnit(content) * (w / 4);\n this.#useFlat();\n if (this.#fill) {\n this.#drawQuad({ x: 0, y: 0, w, h }, content.right ?? content.left);\n } else {\n // A lone page (one side null) gets no gutter shadow — it's a standalone, centered page.\n this.#drawQuad({ x: 0, y: 0, w: w / 2, h }, content.left, content.right ? 1 : 0);\n this.#drawQuad({ x: w / 2, y: 0, w: w / 2, h }, content.right, content.left ? -1 : 0);\n }\n }\n\n #drawFlip(): void {\n const gl = this.#gl!;\n const w = this.#bookW;\n const h = this.#bookH;\n const flip = this.#flip!;\n\n // Interpolate the lone-page centering across the flip so an opening cover (or a\n // closing back page) slides between centered and spread instead of jumping.\n this.#shiftX = flip.fill\n ? 0\n : (this.#fromShiftUnit + (this.#toShiftUnit - this.#fromShiftUnit) * this.#flipT) * (w / 4);\n\n // Static pages underneath (with gutter shadow).\n this.#useFlat();\n if (flip.fill) {\n this.#drawQuad({ x: 0, y: 0, w, h }, flip.underFull);\n } else {\n this.#drawQuad({ x: 0, y: 0, w: w / 2, h }, flip.underLeft, 1);\n this.#drawQuad({ x: w / 2, y: 0, w: w / 2, h }, flip.underRight, -1);\n }\n\n if (flip.front === null && flip.back === null) return;\n\n // Deform the leaf mesh with the selected curl model on the CPU, upload it, then draw.\n const leafW = flip.fill ? w : w / 2;\n const originX = flip.fill ? (flip.dir > 0 ? 0 : w) : w / 2;\n CURLS[this.#curlType].deform(this.#mesh, leafW, h, this.#flipT, {\n y: this.#anchor.y,\n fill: flip.fill,\n });\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#posBuf);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.#mesh.positions);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#normBuf);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.#mesh.normals);\n\n this.#bindFace(gl.TEXTURE0, flip.front);\n this.#bindFace(gl.TEXTURE1, flip.back);\n gl.activeTexture(gl.TEXTURE0);\n\n gl.useProgram(this.#curl);\n gl.bindVertexArray(this.#curlVao);\n gl.uniform2f(this.#curlU.uViewport ?? null, w, h);\n gl.uniform3f(\n this.#curlU.uView ?? null,\n this.#view.scale,\n this.#view.tx * this.#dpr + this.#shiftX,\n this.#view.ty * this.#dpr,\n );\n gl.uniform1f(this.#curlU.uOriginX ?? null, originX);\n gl.uniform1f(this.#curlU.uDir ?? null, flip.dir);\n gl.uniform1f(this.#curlU.uLeafW ?? null, leafW);\n gl.uniform1f(this.#curlU.uGloss ?? null, this.#curlType === 'simple' ? 0 : 1); // flat curl = no shine\n gl.uniform1f(this.#curlU.uAlpha ?? null, flip.fill ? this.#fillFade() : 1);\n gl.drawElements(gl.TRIANGLES, this.#idxCount, gl.UNSIGNED_SHORT, 0);\n }\n\n /** Leaf opacity for a lone page: solid until the curl-appropriate fade start, then\n * dissolving to 0 as it lands, so the turn resolves into the destination page rather\n * than onto blank space. The fade eases so opacity hangs longer then finishes cleanly. */\n #fillFade(): number {\n const start =\n this.#curlType === 'roll' || this.#curlType === 'simple'\n ? FILL_FADE_START_ROLL\n : FILL_FADE_START_CURL;\n const over = (this.#flipT - start) / (1 - start);\n if (over <= 0) return 1;\n if (over >= 1) return 0;\n // Ease-out the remaining opacity: stay readable through most of the window, then settle.\n return Math.pow(1 - over, 1.5);\n }\n\n #useFlat(): void {\n const gl = this.#gl!;\n gl.useProgram(this.#flat);\n gl.bindVertexArray(this.#quadVao);\n gl.activeTexture(gl.TEXTURE0);\n gl.uniform2f(this.#flatU.uViewport ?? null, this.#bookW, this.#bookH);\n gl.uniform3f(\n this.#flatU.uView ?? null,\n this.#view.scale,\n this.#view.tx * this.#dpr + this.#shiftX,\n this.#view.ty * this.#dpr,\n );\n }\n\n #drawQuad(rect: Rect, source: TexImageSource | null, gutterSide = 0): void {\n const gl = this.#gl!;\n if (source === null) return;\n this.#bindFace(gl.TEXTURE0, source);\n gl.uniform4f(this.#flatU.uRect ?? null, rect.x, rect.y, rect.w, rect.h);\n gl.uniform1f(this.#flatU.uGutterSide ?? null, gutterSide);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n\n #bindFace(unit: number, source: TexImageSource | null): void {\n const gl = this.#gl!;\n gl.activeTexture(unit);\n gl.bindTexture(gl.TEXTURE_2D, source !== null ? this.#textureFor(source) : this.#blankTex);\n }\n\n #textureFor(source: TexImageSource): WebGLTexture {\n const gl = this.#gl!;\n const existing = this.#texCache.get(source);\n if (existing !== undefined) {\n this.#texCache.delete(source);\n this.#texCache.set(source, existing);\n return existing.tex;\n }\n const tex = createTexture(gl);\n uploadTexture(gl, tex, source);\n const dims = source as unknown as { width: number; height: number };\n const bytes = dims.width * dims.height * 4;\n this.#texCache.set(source, { tex, bytes });\n this.#cacheBytes += bytes;\n this.#evictTextures();\n return tex;\n }\n\n #evictTextures(): void {\n const gl = this.#gl!;\n for (const [src, cached] of this.#texCache) {\n if (this.#cacheBytes <= MAX_TEXTURE_CACHE_BYTES || this.#texCache.size <= 4) break;\n this.#texCache.delete(src);\n this.#cacheBytes -= cached.bytes;\n gl.deleteTexture(cached.tex);\n }\n }\n\n #makeBlankTexture(gl: WebGL2RenderingContext): WebGLTexture {\n const tex = createTexture(gl);\n gl.bindTexture(gl.TEXTURE_2D, tex);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));\n return tex;\n }\n\n #resize(): void {\n const gl = this.#gl;\n const canvas = this.#canvas;\n const el = this.#container;\n if (gl === null || canvas === null || el === null) return;\n this.#dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const w = Math.max(1, Math.round(el.clientWidth * this.#dpr));\n const h = Math.max(1, Math.round(el.clientHeight * this.#dpr));\n if (canvas.width !== w || canvas.height !== h) {\n canvas.width = w;\n canvas.height = h;\n gl.viewport(0, 0, w, h);\n }\n }\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 type { PageContent } from '../renderer/types';\nimport type { Source } from './types';\n\nexport interface CompositionOptions {\n /** Image URL prepended as a lone front cover (adds a page). */\n frontCover?: string | null;\n /** Image URL appended as a lone back cover (adds a page). */\n backCover?: string | null;\n /** Replace source pages with image URLs, keyed by 0-based source index (negative = from the end). */\n pages?: Record<number, string> | null;\n}\n\n/**\n * Wrap `base` so image covers/replacements sit alongside it, or return `base`\n * unchanged when nothing is requested. Covers ADD pages (front prepended, back\n * appended); `pages` REPLACE existing source pages. `pages` indexes the base's own\n * pages, so it is unaffected by an added cover.\n */\nexport function composeSource(base: Source, options: CompositionOptions): Source {\n const hasFront = typeof options.frontCover === 'string';\n const hasBack = typeof options.backCover === 'string';\n const hasPages = options.pages != null && Object.keys(options.pages).length > 0;\n if (!hasFront && !hasBack && !hasPages) return base;\n return new CompositeSource(base, options);\n}\n\nfunction loadImage(url: string): Promise<ImageBitmap> {\n return fetch(url)\n .then((res) => {\n if (!res.ok) throw new Error(`zine: could not load image \"${url}\" (HTTP ${res.status}).`);\n return res.blob();\n })\n .then((blob) => createImageBitmap(blob));\n}\n\nclass CompositeSource implements Source {\n #base: Source;\n #front: string | null;\n #back: string | null;\n #rawPages: Record<number, string>;\n #resolved = new Map<number, string>(); // base-index → url (negatives resolved)\n #cache = new Map<string, Promise<ImageBitmap>>();\n open?: () => Promise<void>;\n onPageUpdate?: (handler: (index: number) => void) => void;\n\n constructor(base: Source, options: CompositionOptions) {\n this.#base = base;\n this.#front = typeof options.frontCover === 'string' ? options.frontCover : null;\n this.#back = typeof options.backCover === 'string' ? options.backCover : null;\n this.#rawPages = options.pages ?? {};\n\n if (typeof base.open === 'function') {\n // Async base: page count (and negative indices) known only after open().\n this.open = async (): Promise<void> => {\n await base.open!();\n this.#resolvePages();\n };\n } else {\n this.#resolvePages(); // sync base: count is known now\n }\n\n if (typeof base.onPageUpdate === 'function') {\n // Forward base page updates, shifting to book indices (past the front cover).\n this.onPageUpdate = (handler) => {\n base.onPageUpdate!((src) => handler(src + (this.#front !== null ? 1 : 0)));\n };\n }\n }\n\n get pageCount(): number {\n return this.#base.pageCount + (this.#front !== null ? 1 : 0) + (this.#back !== null ? 1 : 0);\n }\n\n get(index: number): Promise<PageContent> {\n const frontOffset = this.#front !== null ? 1 : 0;\n if (this.#front !== null && index === 0) return this.#image(this.#front);\n if (this.#back !== null && index === this.pageCount - 1) return this.#image(this.#back);\n const src = index - frontOffset;\n const override = this.#resolved.get(src);\n return override !== undefined ? this.#image(override) : this.#base.get(src);\n }\n\n prefetch(indices: number[]): void {\n const frontOffset = this.#front !== null ? 1 : 0;\n const baseIndices: number[] = [];\n for (const index of indices) {\n if (this.#front !== null && index === 0) {\n void this.#image(this.#front).catch(() => {});\n } else if (this.#back !== null && index === this.pageCount - 1) {\n void this.#image(this.#back).catch(() => {});\n } else {\n const src = index - frontOffset;\n const override = this.#resolved.get(src);\n if (override !== undefined) void this.#image(override).catch(() => {});\n else baseIndices.push(src);\n }\n }\n this.#base.prefetch(baseIndices);\n }\n\n destroy(): void {\n this.#base.destroy();\n for (const promise of this.#cache.values()) {\n void promise.then((bmp) => bmp.close()).catch(() => {});\n }\n this.#cache.clear();\n }\n\n #resolvePages(): void {\n const n = this.#base.pageCount;\n this.#resolved.clear();\n for (const [key, url] of Object.entries(this.#rawPages)) {\n const raw = Number(key);\n const src = raw < 0 ? n + raw : raw;\n if (Number.isInteger(src) && src >= 0 && src < n) this.#resolved.set(src, url);\n }\n }\n\n #image(url: string): Promise<ImageBitmap> {\n let promise = this.#cache.get(url);\n if (promise === undefined) {\n promise = loadImage(url).catch((error: unknown) => {\n this.#cache.delete(url); // let a later get() retry\n throw error;\n });\n this.#cache.set(url, promise);\n }\n return promise;\n }\n}\n","import {\n buildSpreads,\n shouldSinglePage,\n type Direction,\n type Spread,\n type SpreadMode,\n} 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 { CURL_TYPES, DEFAULT_CURL, type CurlType } from './geometry/curls/types';\nimport { selectRenderer, type RendererOption } from './renderer/select';\nimport type { FlipDirection, PageContent, Renderer, SpreadContent } from './renderer/types';\nimport type { Source } from './source/types';\nimport { composeSource } from './source/compose';\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\n/** A lone page fills the container, so it sweeps the full width where a spread leaf only covers\n * its half — and it dissolves instead of landing on a facing page. Stretch duration so the\n * peel and fade read at a comparable pace to a spread turn rather than whipping away. */\nconst LONE_PAGE_FLIP_SCALE = 1.55;\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 /** Page-curl model for the WebGL2 renderer: 'roll' | 'cone' | 'leaf' | 'flick' | 'simple'. Default 'cone'. */\n curl?: CurlType;\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 /** How pages group into spreads: 'double' | 'single' | 'cover' | 'book'. Default 'cover'. */\n spreadMode?: SpreadMode;\n /** Image URL prepended as a lone front cover (adds a page). */\n frontCover?: string;\n /** Image URL appended as a lone back cover (adds a page). */\n backCover?: string;\n /** Replace source pages with image URLs, keyed by 0-based source index (negative = from the end). */\n pages?: Record<number, string>;\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 #spreadMode: SpreadMode;\n #curl: CurlType;\n #anchorY = 1; // where the last tap/drag grabbed (0=top, 1=bottom); drives anchored curls\n #clickToFlip: 'edge' | 'half' | 'off';\n #clickZoneSize: number;\n #honorDoubleClickInFlipZone: boolean;\n #clickFlipDelayValue: number;\n #singlePageThreshold: number;\n #singlePage = false;\n #narrow = false; // responsive: container currently below singlePageThreshold\n #lastAspect = ''; // last container aspect-ratio written (avoids redundant style writes)\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 // Latest flip intent requested while one was already animating; replayed on settle so\n // clicks/keys during a turn aren't dropped and continuous flipping keeps advancing.\n #queuedFlip: (() => void) | null = null;\n // The fold animation currently on screen, so a new flip request can land it instantly\n // (snap to its end pose + commit) and start immediately instead of waiting it out.\n #activeAnim: { toT: number; direction: FlipDirection; onDone: () => void } | 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 = composeSource(options.source, {\n frontCover: options.frontCover,\n backCover: options.backCover,\n pages: options.pages,\n });\n const direction = options.direction ?? 'ltr';\n this.#direction = direction;\n this.#spreadMode = options.spreadMode ?? 'cover';\n this.#curl = options.curl ?? DEFAULT_CURL;\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.#requestFlip(() => this.#startFlip(this.#current + 1));\n }\n\n flipPrev(): void {\n this.#requestFlip(() => 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.#requestFlip(() => this.#startFlip(this.#spreadIndexForPage(target)));\n }\n\n /** Run a flip now. If a fold is animating, land it instantly first so the new turn starts\n * immediately on this click (thunks read `#current` lazily, so it steps on from where the\n * interrupted turn landed). A drag in flight can't be interrupted this way, so its request\n * is queued and replayed on settle instead. */\n #requestFlip(run: () => void): void {\n if (this.#machine.state === 'idle') {\n run();\n } else if (this.#activeAnim) {\n this.#finishActiveAnim(); // lands the current turn → machine back to idle\n run();\n } else {\n this.#queuedFlip = run; // dragging: no animation to cut short, so wait for release\n }\n }\n\n /** Snap the in-flight fold to its end pose and commit it now, cancelling its RAF loop. */\n #finishActiveAnim(): void {\n const anim = this.#activeAnim;\n if (!anim) return;\n this.#activeAnim = null;\n if (this.#raf !== null) {\n cancelAnimationFrame(this.#raf);\n this.#raf = null;\n }\n this.#renderer?.setFlipProgress(anim.toT, anim.direction);\n anim.onDone();\n }\n\n /** After an animation settles, fire whatever flip was requested mid-turn. */\n #drainQueuedFlip(): void {\n const queued = this.#queuedFlip;\n if (!queued) return;\n this.#queuedFlip = null;\n queued();\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.#activeAnim = null;\n this.#queuedFlip = null;\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 curl: this.#curl,\n anchor: { y: this.#anchorY },\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 // Remembered so a mid-turn flip request can #finishActiveAnim() to land it at once.\n // Object identity also tags this run: if #finishActiveAnim (or a new flip) replaces it,\n // a stale frame that still fires bails instead of re-committing or rescheduling itself.\n const anim = { toT, direction, onDone };\n this.#activeAnim = anim;\n const start = performance.now();\n const step = (now: number): void => {\n if (this.#activeAnim !== anim) return; // superseded/interrupted → this frame is void\n // Easing lives here; flipProgressToPose stays linear so seeks are deterministic.\n // Lone pages ease out harder — no facing half to land on, only a dissolve.\n const raw = Math.min(1, (now - start) / duration);\n const eased = this.#singlePage ? easeInOutLone(raw) : easeInOutQuad(raw);\n this.#renderer?.setFlipProgress(fromT + (toT - fromT) * eased, direction);\n if (raw < 1) {\n this.#raf = requestAnimationFrame(step);\n } else {\n this.#raf = null;\n this.#activeAnim = 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 this.#drainQueuedFlip();\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 b = this.#contentRect();\n // Points are relative to the letterboxed book, so zones/corners track the page, not the bars.\n const point = { x: clientX - rect.left - b.x, y: clientY - rect.top - b.y };\n this.#press = point; // remembered for tap classification / click-to-flip zone\n const cornerSize = Math.min(b.width, b.height) * CORNER_FRACTION;\n if (hitTest(point, { width: b.width, height: b.height }, cornerSize) !== 'corner') {\n return; // not a corner — no drag; a tap here may still click-to-flip on release\n }\n this.#anchorY = clamp(point.y / b.height, 0, 1); // fold anchors at the grabbed corner\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 > b.width / 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: b.width,\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 curl: this.#curl,\n anchor: { y: this.#anchorY },\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 step = direction === 'forward' ? 1 : -1;\n if (this.#current + step < 0 || this.#current + step >= this.#spreads.length) return;\n // Anchor the fold at the tapped height (cone/leaf/flick curl from where you tap).\n this.#anchorY = clamp(this.#press.y / this.#contentRect().height, 0, 1);\n // Read `#current` lazily so a tap queued mid-turn steps on from wherever the page lands.\n const flip = (): void => this.#startFlip(this.#current + step);\n this.#clearPendingClickFlip();\n if (this.#clickFlipDelayValue <= 0) {\n // No double-click competing here → flip right away (or queue if mid-turn).\n this.#requestFlip(flip);\n return;\n }\n // Wait out the window; a double-click (zoom) cancels this.\n this.#pendingClickTimer = setTimeout(() => {\n this.#pendingClickTimer = null;\n this.#requestFlip(flip);\n }, this.#clickFlipDelayValue);\n }\n\n /** Which way a tap at `point` (container-local) turns the page, or null for a dead zone. */\n /** Where the current spread is actually drawn (lone pages centered), as the renderer reports\n * it — the region taps/drags map into. Falls back to book, then the full container. */\n #contentRect(): { x: number; y: number; width: number; height: number } {\n const m = this.#renderer!.measure();\n return m.content ?? m.book ?? { x: 0, y: 0, width: m.containerWidth, height: m.containerHeight };\n }\n\n #clickFlipDirection(point: { x: number; y: number }): FlipDirection | null {\n if (!this.#renderer) return null;\n const containerWidth = this.#contentRect().width;\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 this.#drainQueuedFlip();\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 // Re-render the current spread when a source upgrades one of its pages in place.\n this.#source.onPageUpdate?.((index) => this.#onPageUpdate(index));\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 if (this.#reducedMotion) return 0;\n return this.#flipDuration * (this.#singlePage ? LONE_PAGE_FLIP_SCALE : 1);\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 /** The layout to actually use: the responsive narrow fallback forces 'single'. */\n #effectiveMode(): SpreadMode {\n return this.#narrow || this.#spreadMode === 'single' ? 'single' : this.#spreadMode;\n }\n\n #applySinglePage(containerWidth: number): void {\n const narrow = shouldSinglePage(containerWidth, this.#singlePageThreshold);\n if (narrow === this.#narrow) return;\n this.#narrow = narrow;\n const mode = this.#effectiveMode();\n this.#singlePage = mode === 'single';\n this.#spreads = buildSpreads(this.#source.pageCount, { direction: this.#direction, mode });\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 this.#applyContainerAspect();\n }\n\n /** Match the container's aspect-ratio to the book so it fits with no letterbox bars.\n * Non-destructive: `aspect-ratio` only drives whichever dimension the consumer leaves\n * auto (fit-width when they set a width), and is ignored if both are fixed. */\n #applyContainerAspect(): void {\n const b = this.#renderer?.measure().book;\n if (!b || b.width <= 0 || b.height <= 0) return;\n const ratio = (b.width / b.height).toFixed(4);\n if (ratio === this.#lastAspect) return;\n this.#lastAspect = ratio;\n this.#container.style.aspectRatio = ratio;\n }\n\n /** A source upgraded a page's content (e.g. progressive PDF); repaint if it's on screen and idle. */\n #onPageUpdate(index: number): void {\n if (this.#machine.state !== 'idle') return; // don't disturb an in-flight flip\n const spread = this.#spreads[this.#current];\n if (spread && (spread.left === index || spread.right === index)) {\n void this.#renderCurrent();\n }\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 const mode = this.#effectiveMode();\n this.#singlePage = mode === 'single';\n this.#spreads = buildSpreads(pageCount, { direction: this.#direction, mode });\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\n/** Quadratic ease-in-out. Deliberately gentler than a cubic: a cubic leaves the page nearly\n * motionless for the first quarter of the turn and then spikes through the middle at roughly\n * double the peak speed, which reads as a lurch rather than a sheet of paper being turned. */\nfunction easeInOutQuad(t: number): number {\n return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;\n}\n\n/** Lone-page turn: same gentle start as the spread ease, then a longer decelerating finish.\n * Without a facing half the landing is a dissolve — front-loading a bit of progress and\n * stretching the tail keeps that fade from reading as a whip. */\nfunction easeInOutLone(t: number): number {\n const x = easeInOutQuad(t);\n return 1 - Math.pow(1 - x, 1.3);\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 const curl = o.curl;\n if (curl !== undefined && !CURL_TYPES.includes(curl as CurlType)) {\n throw new Error(`Zine: curl must be one of ${CURL_TYPES.join(', ')}; got ${JSON.stringify(curl)}.`);\n }\n\n const spreadMode = o.spreadMode;\n if (spreadMode !== undefined && !['double', 'single', 'cover', 'book'].includes(spreadMode as string)) {\n throw new Error(\n `Zine: spreadMode must be 'double', 'single', 'cover', or 'book'; got ${JSON.stringify(spreadMode)}.`,\n );\n }\n\n for (const key of ['frontCover', 'backCover'] as const) {\n const value = o[key];\n if (value !== undefined && typeof value !== 'string') {\n throw new Error(`Zine: ${key} must be an image URL string; got ${JSON.stringify(value)}.`);\n }\n }\n const pages = o.pages;\n if (pages !== undefined && (typeof pages !== 'object' || pages === null || Array.isArray(pages))) {\n throw new Error('Zine: pages must be an object mapping page indices to image URLs.');\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","export { Zine } from './zine';\nexport type { ZineOptions, ZoomOptions } from './zine';\n\nexport { ImageSource } from './source/imageSource';\nexport type { ImageSourceOptions, ImageFit } from './source/imageSource';\nexport type { Source } from './source/types';\n\nexport type { RendererOption, RendererKind } from './renderer/select';\nexport type { Renderer, SpreadContent, PageContent, FlipDirection, LayoutMetrics, RenderOptions } from './renderer/types';\n\nexport type { ZineEventMap } from './engine/emitter';\nexport type { Direction, Spread } from './engine/spread';\nexport { CURL_TYPES } from './geometry/curls/types';\nexport type { CurlType, CurlAnchor } from './geometry/curls/types';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.umd.cjs","names":["#radius","#mounted","#listeners","#state","#handlers","#activeId","#start","#prev","#last","#end","#reset","#pinching","#points","#pair","#startDistance","#remove","#container","#clip","#viewport","#book","#pageLeft","#makeCanvas","#pageRight","#leaf","#leafFront","#leafBack","#leafShadow","#paint","#fill","#contentShift","#trackAspect","#layoutBook","#applyShift","#fromShiftUnit","#toShiftUnit","#bookW","#pageAspect","#bookBox","#contentBox","#container","#canvas","#onContextLost","#onContextRestored","#onVisibilityChange","#gl","#buildGlResources","#clearRestoreTimer","#texCache","#blankTex","#quadVao","#curlVao","#flat","#curl","#cacheBytes","#content","#flip","#fatalHandler","#fill","#trackAspect","#render","#pageAspect","#flipT","#curlType","#anchor","#fromShiftUnit","#toShiftUnit","#view","#bookBox","#contentBox","#contextLost","#restoreTimer","#fatal","#fatalFired","#flatU","#curlU","#makeQuadVao","#buildCurlMesh","#makeBlankTexture","#idxCount","#posBuf","#normBuf","#mesh","#resize","#fitBook","#drawFlip","#drawStatic","#bookW","#bookH","#shiftX","#useFlat","#drawQuad","#bindFace","#dpr","#fillFade","#textureFor","#evictTextures","#base","#front","#back","#rawPages","#resolvePages","#image","#resolved","#cache","#renderer","#container","#source","#direction","#spreadMode","#curl","#clickToFlip","#clickZoneSize","#singlePageThreshold","#startPageOption","#buildSpreadModel","#flipDuration","#zoomEnabled","#wheelZoom","#doubleClickLevels","#maxZoom","#honorDoubleClickInFlipZone","#clickFlipDelayValue","#ready","#init","#currentPage","#requestFlip","#startFlip","#current","#spreadIndexForPage","#machine","#activeAnim","#finishActiveAnim","#queuedFlip","#raf","#scale","#tx","#ty","#clampPan","#emitter","#destroyed","#clearPendingClickFlip","#resizeObserver","#a11yCleanup","#unbindWheel","#unbindDblClick","#unbindInput","#spreads","#leadPage","#runFlip","#resolveContent","#currentContent","#singlePage","#anchorY","#animateProgress","#commit","#effectiveDuration","#paintSpread","#prefetchWindow","#announce","#drainQueuedFlip","#pinching","#pan","#contentRect","#press","#pendingGrab","#drag","#stageDrag","#promoteGrab","#maybeClickFlip","#cancelFlip","#clickFlipDirection","#pendingClickTimer","#pinchBaseScale","#mountWithFallback","#onPageUpdate","#applySinglePage","#onDragStart","#onDragMove","#onDragEnd","#onPinchStart","#onPinchMove","#onPinchEnd","#bindWheelZoom","#bindDoubleClickZoom","#setupReducedMotion","#setupA11y","#renderCurrent","#observeResize","#fallbackToCss","#fellBack","#reducedMotion","#onKeyDown","#liveRegion","#narrow","#effectiveMode","#applyContainerAspect","#lastAspect","#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/geometry/curls/types.ts","../src/geometry/flipProgressToPose.ts","../src/renderer/cssRenderer.ts","../src/geometry/curls/mesh.ts","../src/geometry/curls/roll.ts","../src/geometry/curls/cone.ts","../src/geometry/curls/leaf.ts","../src/geometry/curls/flick.ts","../src/geometry/curls/silk.ts","../src/geometry/curls/simple.ts","../src/geometry/curls/index.ts","../src/renderer/gl/context.ts","../src/renderer/gl/program.ts","../src/renderer/gl/texture.ts","../src/renderer/webglRenderer.ts","../src/renderer/select.ts","../src/source/compose.ts","../src/zine.ts","../src/source/imageSource.ts","../src/index.ts"],"sourcesContent":["export type Direction = 'ltr' | 'rtl';\n\n/**\n * How pages group into spreads:\n * - `double`: paired from the start — `[0|1] [2|3] …`\n * - `single`: one page per spread — `[0] [1] …`\n * - `cover`: first page alone, then paired — `[·|0] [1|2] …`\n * - `book`: first AND last alone (front + back cover) — `[·|0] [1|2] … [N-1|·]`\n */\nexport type SpreadMode = 'double' | 'single' | 'cover' | 'book';\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 mode?: SpreadMode;\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 / a lone cover).\n */\nexport function buildSpreads(pageCount: number, options: SpreadOptions = {}): Spread[] {\n const { direction = 'ltr', mode = 'cover' } = 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 (mode === 'single') {\n for (let i = 0; i < pageCount; i++) spreads.push({ left: null, right: i });\n } else if (mode === 'double') {\n for (let i = 0; i < pageCount; i += 2) spreads.push({ left: page(i), right: page(i + 1) });\n } else if (mode === 'cover') {\n spreads.push({ left: null, right: 0 });\n for (let i = 1; i < pageCount; i += 2) spreads.push({ left: page(i), right: page(i + 1) });\n } else {\n // book: front cover alone, interior paired, back cover alone\n spreads.push({ left: null, right: 0 });\n if (pageCount > 1) {\n for (let i = 1; i <= pageCount - 2; i += 2) {\n spreads.push({ left: page(i), right: i + 1 <= pageCount - 2 ? page(i + 1) : null });\n }\n spreads.push({ left: pageCount - 1, right: null });\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","/**\n * Curl type identifiers and the deform contract. Kept free of the deform\n * implementations so the engine (and CSS-only builds) can reference the union and the\n * validation list without pulling the WebGL-only curl math into the eager bundle.\n */\nimport type { PageMesh } from './mesh';\n\n/** Where a corner-anchored curl folds from: y in [0,1], 0 = top edge, 1 = bottom edge. */\nexport interface CurlAnchor {\n y: number;\n /**\n * Full-width lone page (single-page mode). The leaf hinges at the container edge and\n * has no facing half to land on — deforms may soften and delay the flop so the curl\n * reads naturally before the renderer dissolves the sheet.\n */\n fill?: boolean;\n}\n\nexport type CurlDeform = (mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor) => void;\n\nexport interface CurlModel {\n readonly deform: CurlDeform;\n /** True when the fold originates at a corner (`anchor.y`) rather than the whole edge. */\n readonly anchored: boolean;\n}\n\nexport type CurlType = 'roll' | 'cone' | 'leaf' | 'flick' | 'silk' | 'simple';\n\nexport const CURL_TYPES: CurlType[] = ['roll', 'cone', 'leaf', 'flick', 'silk', 'simple'];\n\nexport const DEFAULT_CURL: CurlType = 'cone';\n","export interface FlipPose {\n /** Turning-page rotation about the spine, radians: 0 (flat/open) → π (fully turned). */\n angle: number;\n /** Mesh cylinder-wrap curl, 0..1 — flat at the ends, peaks edge-on at t=0.5. CSS renderer ignores it. */\n curl: number;\n /** Fold-shadow intensity, 0..1 — peaks edge-on at t=0.5. */\n shadowAlpha: number;\n}\n\n/**\n * Map a 0→1 flip progress to a renderer-neutral fold pose.\n *\n * Linear in `t` on purpose — easing belongs to the animator that produces `t`,\n * so seeking to a fixed progress (e.g. the 0/.25/.5/.75/1 visual-regression stops)\n * is deterministic. The CSS renderer consumes `angle` as a spine `rotateY`; the\n * WebGL2 renderer additionally bends its mesh by `curl`.\n */\nexport function flipProgressToPose(t: number): FlipPose {\n const clamped = t < 0 ? 0 : t > 1 ? 1 : t;\n const arc = Math.sin(clamped * Math.PI);\n return {\n angle: clamped * Math.PI,\n curl: arc,\n shadowAlpha: arc,\n };\n}\n","import { flipProgressToPose } from '../geometry/flipProgressToPose';\nimport type { Spread } from '../engine/spread';\nimport type {\n FlipDirection,\n LayoutMetrics,\n PageContent,\n Renderer,\n RenderOptions,\n SpreadContent,\n} from './types';\n\nconst RAD_TO_DEG = 180 / Math.PI;\n\n/** How far to shift a spread to centre a lone page, in units of a quarter container width:\n * -1 = only a right page (cover), +1 = only a left page (back), 0 = a full spread. */\nfunction shiftUnit(content: SpreadContent): number {\n const lone = (content.left === null) !== (content.right === null);\n return lone ? (content.right !== null ? -1 : 1) : 0;\n}\n\n/**\n * Layered gradients painted over the turning leaf to fake paper depth on the flat\n * fold: a crease shadow at the spine, a soft curve sheen, and a free-edge shade.\n * The spine is the leaf's pivot edge — left for a forward flip, right for backward.\n */\nfunction foldOverlay(direction: FlipDirection): string {\n const dir = direction === 'forward' ? 'to right' : 'to left';\n return [\n `linear-gradient(${dir}, rgba(0,0,0,0.6), rgba(0,0,0,0.15) 40%, rgba(0,0,0,0) 60%)`,\n `linear-gradient(${dir}, transparent 40%, rgba(255,255,255,0.32) 66%, transparent 90%)`,\n `linear-gradient(${dir}, transparent 66%, rgba(0,0,0,0.35))`,\n ].join(',');\n}\n\n/**\n * Baseline renderer: a 2-panel CSS-3D fold. Runs on every browser, no GPU. Pages\n * are <canvas> elements; the turning leaf (front + back faces, backface-hidden)\n * rotates about the spine driven by flipProgressToPose(), with a gradient shadow\n * overlay for depth. `beginFlip` stages the surfaces; `setFlipProgress` animates.\n */\nexport class CssRenderer implements Renderer {\n #container: HTMLElement | null = null;\n #clip!: HTMLDivElement;\n #viewport!: HTMLDivElement;\n #book!: HTMLDivElement;\n #pageLeft!: HTMLCanvasElement;\n #pageRight!: HTMLCanvasElement;\n #leaf!: HTMLDivElement;\n #leafFront!: HTMLCanvasElement;\n #leafBack!: HTMLCanvasElement;\n #leafShadow!: HTMLDivElement;\n // Lone-page centering, interpolated across a flip (see the WebGL renderer).\n #fromShiftUnit = 0;\n #toShiftUnit = 0;\n // Book aspect fit: the spread is letterboxed to the page aspect (see the WebGL renderer).\n #pageAspect = 0;\n #bookW = 0;\n #fill = false;\n #contentShift = 0; // shiftUnit of the current static spread (nonzero = lone page, centered)\n\n mount(container: HTMLElement): Promise<void> {\n const doc = container.ownerDocument;\n this.#container = container;\n\n // Fixed-size clip box; NOT transformed, so zoom can't scale the crop region.\n this.#clip = doc.createElement('div');\n this.#clip.className = 'zine-clip';\n this.#clip.style.cssText = 'position:relative;width:100%;height:100%;overflow:hidden;';\n\n // Transform target for zoom/pan; lives inside the clip.\n this.#viewport = doc.createElement('div');\n this.#viewport.className = 'zine-viewport';\n this.#viewport.style.cssText = 'position:absolute;inset:0;transform-origin:0 0;';\n\n this.#book = doc.createElement('div');\n this.#book.className = 'zine-book';\n this.#book.style.cssText =\n 'position:absolute;inset:0;perspective:2000px;transform-style:preserve-3d;';\n\n this.#pageLeft = this.#makeCanvas(doc, 'zine-page-left', 'left:0;');\n this.#pageRight = this.#makeCanvas(doc, 'zine-page-right', 'right:0;');\n\n this.#leaf = doc.createElement('div');\n this.#leaf.className = 'zine-leaf';\n this.#leaf.style.cssText =\n 'position:absolute;top:0;width:50%;height:100%;transform-style:preserve-3d;display:none;';\n\n this.#leafFront = doc.createElement('canvas');\n this.#leafFront.className = 'zine-leaf-front';\n this.#leafFront.style.cssText =\n 'position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;';\n\n this.#leafBack = doc.createElement('canvas');\n this.#leafBack.className = 'zine-leaf-back';\n this.#leafBack.style.cssText =\n 'position:absolute;inset:0;width:100%;height:100%;backface-visibility:hidden;transform:rotateY(180deg);';\n\n this.#leafShadow = doc.createElement('div');\n this.#leafShadow.className = 'zine-leaf-shadow';\n this.#leafShadow.style.cssText = 'position:absolute;inset:0;opacity:0;pointer-events:none;';\n this.#leafShadow.style.background = foldOverlay('forward'); // default; beginFlip sets per direction\n\n this.#leaf.append(this.#leafFront, this.#leafBack, this.#leafShadow);\n this.#book.append(this.#pageLeft, this.#pageRight, this.#leaf);\n this.#viewport.append(this.#book);\n this.#clip.append(this.#viewport);\n container.append(this.#clip);\n\n return Promise.resolve();\n }\n\n destroy(): void {\n this.#clip?.remove();\n this.#container = null;\n }\n\n renderSpread(_spread: Spread, content: SpreadContent, options?: RenderOptions): void {\n if (options?.fill) {\n // Single-page mode: the lone page fills the container; hide the other panel.\n this.#pageLeft.style.width = '100%';\n this.#pageRight.style.display = 'none';\n this.#paint(this.#pageLeft, content.right ?? content.left);\n } else {\n this.#pageLeft.style.width = '50%';\n this.#pageRight.style.display = '';\n this.#paint(this.#pageLeft, content.left);\n this.#paint(this.#pageRight, content.right);\n }\n this.#fill = options?.fill ?? false;\n this.#contentShift = this.#fill ? 0 : shiftUnit(content);\n this.#trackAspect(content);\n this.#layoutBook();\n this.#applyShift(this.#contentShift);\n // A fresh spread cancels any in-progress flip.\n this.#leaf.style.display = 'none';\n this.#leaf.style.transform = '';\n this.#leafShadow.style.opacity = '0';\n }\n\n beginFlip(\n from: SpreadContent,\n to: SpreadContent,\n direction: FlipDirection,\n options?: RenderOptions,\n ): void {\n if (options?.fill) {\n // Single-page: a full-width leaf turns about one edge, revealing `to` underneath.\n this.#pageLeft.style.width = '100%';\n this.#pageRight.style.display = 'none';\n this.#leaf.style.width = '100%';\n this.#leaf.style.left = '0';\n this.#leaf.style.transformOrigin = direction === 'forward' ? 'left center' : 'right center';\n this.#paint(this.#pageLeft, to.right ?? to.left);\n this.#paint(this.#leafFront, from.right ?? from.left);\n this.#paint(this.#leafBack, to.right ?? to.left);\n } else {\n this.#pageLeft.style.width = '50%';\n this.#pageRight.style.display = '';\n this.#leaf.style.width = '50%';\n if (direction === 'forward') {\n // Right page lifts and swings left about the spine. Left stays; right reveals `to`.\n this.#paint(this.#pageLeft, from.left);\n this.#paint(this.#pageRight, to.right);\n this.#paint(this.#leafFront, from.right);\n this.#paint(this.#leafBack, to.left);\n this.#leaf.style.left = '50%';\n this.#leaf.style.transformOrigin = 'left center';\n } else {\n // Left page swings right about its right edge. Right stays; left reveals `to`.\n this.#paint(this.#pageRight, from.right);\n this.#paint(this.#pageLeft, to.left);\n this.#paint(this.#leafFront, from.left);\n this.#paint(this.#leafBack, to.right);\n this.#leaf.style.left = '0';\n this.#leaf.style.transformOrigin = 'right center';\n }\n }\n this.#fromShiftUnit = options?.fill ? 0 : shiftUnit(from);\n this.#toShiftUnit = options?.fill ? 0 : shiftUnit(to);\n this.#fill = options?.fill ?? false;\n this.#trackAspect(to);\n this.#layoutBook();\n this.#applyShift(this.#fromShiftUnit);\n this.#leaf.style.display = 'block';\n this.#leaf.style.transform = 'rotateY(0deg)';\n this.#leafShadow.style.background = foldOverlay(direction);\n this.#leafShadow.style.opacity = '0';\n }\n\n setFlipProgress(t: number, direction: FlipDirection): void {\n const pose = flipProgressToPose(t);\n const deg = pose.angle * RAD_TO_DEG;\n this.#applyShift(this.#fromShiftUnit + (this.#toShiftUnit - this.#fromShiftUnit) * t);\n this.#leaf.style.display = 'block';\n this.#leaf.style.transform = `rotateY(${direction === 'forward' ? -deg : deg}deg)`;\n this.#leafShadow.style.opacity = String(pose.shadowAlpha);\n }\n\n #applyShift(unit: number): void {\n const px = unit * (this.#bookW / 4);\n this.#book.style.transform = px ? `translateX(${px}px)` : '';\n }\n\n /** Remember the page aspect (width/height) so the book can be letterboxed to it. */\n #trackAspect(content: SpreadContent): void {\n const p = content.left ?? content.right;\n if (p && p.height) this.#pageAspect = p.width / p.height;\n }\n\n /** Letterbox the 2-page book to the page aspect, centered in the container, so pages\n * fill their halves without stretching; the surround is the container background. */\n #layoutBook(): void {\n const b = this.#bookBox();\n this.#bookW = b.width;\n const s = this.#book.style;\n s.inset = 'auto';\n s.left = `${b.x}px`;\n s.top = `${b.y}px`;\n s.width = `${b.width}px`;\n s.height = `${b.height}px`;\n }\n\n /** The fitted book rect in container px (letterbox aware) — a stable 2-page area. */\n #bookBox(): { x: number; y: number; width: number; height: number } {\n const cw = this.#container?.clientWidth ?? 0;\n const ch = this.#container?.clientHeight ?? 0;\n if (this.#pageAspect <= 0 || cw <= 0 || ch <= 0) return { x: 0, y: 0, width: cw, height: ch };\n const ba = (this.#fill ? 1 : 2) * this.#pageAspect;\n let bw = cw;\n let bh = ch;\n if (ba > cw / ch) bh = cw / ba;\n else bw = ch * ba;\n return { x: (cw - bw) / 2, y: (ch - bh) / 2, width: bw, height: bh };\n }\n\n /** Where the current spread is actually painted: the book, or its centered half for a lone\n * page (mirrors #applyShift). This is what the engine hit-tests against. */\n #contentBox(): { x: number; y: number; width: number; height: number } {\n const b = this.#bookBox();\n return this.#contentShift !== 0 ? { x: b.x + b.width / 4, y: b.y, width: b.width / 2, height: b.height } : b;\n }\n\n setViewTransform(scale: number, x: number, y: number): void {\n this.#viewport.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;\n }\n\n measure(): LayoutMetrics {\n const el = this.#container;\n const w = el?.clientWidth ?? 0;\n const h = el?.clientHeight ?? 0;\n const known = this.#pageAspect > 0;\n const book = known ? this.#bookBox() : undefined;\n const content = known ? this.#contentBox() : undefined;\n return { containerWidth: w, containerHeight: h, pageWidth: w / 2, pageHeight: h, book, content };\n }\n\n #makeCanvas(doc: Document, className: string, extra: string): HTMLCanvasElement {\n const c = doc.createElement('canvas');\n c.className = className;\n c.style.cssText = `position:absolute;top:0;width:50%;height:100%;${extra}`;\n return c;\n }\n\n #paint(canvas: HTMLCanvasElement, content: PageContent | null): void {\n const ctx = canvas.getContext('2d');\n if (content === null) {\n ctx?.clearRect(0, 0, canvas.width, canvas.height);\n return;\n }\n canvas.width = content.width;\n canvas.height = content.height;\n ctx?.drawImage(content, 0, 0);\n }\n}\n","/**\n * Shared grid mesh + normal utilities for the page-curl models.\n *\n * A curl model fills `positions`/`normals` in place each frame; UVs are static grid\n * coordinates (no texture swimming). All models bend the same (cols+1)×(rows+1) grid.\n */\n\nexport interface PageMesh {\n readonly cols: number;\n readonly rows: number;\n /** ((cols+1)*(rows+1))*2 — static grid UVs (u across width, v down height). */\n readonly uvs: Float32Array;\n /** *3 — deformed positions, updated by the curl model. */\n readonly positions: Float32Array;\n /** *3 — per-vertex normals, updated by the curl model. */\n readonly normals: Float32Array;\n}\n\n/** Allocate a (cols+1)×(rows+1) grid mesh with static UVs (positions/normals filled by a curl). */\nexport function createPageMesh(cols: number, rows: number): PageMesh {\n const nx = cols + 1;\n const ny = rows + 1;\n const count = nx * ny;\n const uvs = new Float32Array(count * 2);\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n const k = j * nx + i;\n uvs[k * 2] = i / cols;\n uvs[k * 2 + 1] = j / rows;\n }\n }\n return {\n cols,\n rows,\n uvs,\n positions: new Float32Array(count * 3),\n normals: new Float32Array(count * 3),\n };\n}\n\n/** Per-vertex normals from central differences of the deformed grid (front = +z when flat). */\nexport function computeNormals(mesh: PageMesh): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n const nrm = mesh.normals;\n\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n const iPrev = i > 0 ? i - 1 : i;\n const iNext = i < nx - 1 ? i + 1 : i;\n const jPrev = j > 0 ? j - 1 : j;\n const jNext = j < ny - 1 ? j + 1 : j;\n\n const a = (j * nx + iNext) * 3;\n const b = (j * nx + iPrev) * 3;\n const c = (jNext * nx + i) * 3;\n const e = (jPrev * nx + i) * 3;\n\n const tuX = pos[a]! - pos[b]!;\n const tuY = pos[a + 1]! - pos[b + 1]!;\n const tuZ = pos[a + 2]! - pos[b + 2]!;\n const tvX = pos[c]! - pos[e]!;\n const tvY = pos[c + 1]! - pos[e + 1]!;\n const tvZ = pos[c + 2]! - pos[e + 2]!;\n\n let nx0 = tuY * tvZ - tuZ * tvY;\n let ny0 = tuZ * tvX - tuX * tvZ;\n let nz0 = tuX * tvY - tuY * tvX;\n const l = Math.hypot(nx0, ny0, nz0) || 1;\n nx0 /= l;\n ny0 /= l;\n nz0 /= l;\n\n const k = (j * nx + i) * 3;\n nrm[k] = nx0;\n nrm[k + 1] = ny0;\n nrm[k + 2] = nz0;\n }\n }\n}\n","import { computeNormals, type PageMesh } from './mesh';\n\n/**\n * Roll-and-flop. The leaf wraps a cylinder whose wrap angle grows 0→π over the first\n * half while its radius (= W/theta) shrinks, so the sheet rolls up off the surface in\n * place; over the second half it unwraps π→0 while rigidly rotating around the spine\n * 0→-π down onto the far side. Arc length across the width is exactly preserved.\n */\nexport function deformRoll(mesh: PageMesh, W: number, H: number, t: number): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n const theta = Math.max(1e-6, t < 0.5 ? t * 2 * Math.PI : (1 - (t - 0.5) * 2) * Math.PI);\n const radius = W / theta;\n const rot = t < 0.5 ? 0 : -(t - 0.5) * 2 * Math.PI;\n const cosR = Math.cos(rot);\n const sinR = Math.sin(rot);\n\n for (let i = 0; i < nx; i++) {\n const rad = (i / mesh.cols) * theta;\n const cx = Math.sin(rad) * radius;\n const cz = (1 - Math.cos(rad)) * radius;\n const X = cx * cosR + cz * sinR;\n const Z = -cx * sinR + cz * cosR;\n for (let j = 0; j < ny; j++) {\n const k = (j * nx + i) * 3;\n pos[k] = X;\n pos[k + 1] = (j / mesh.rows) * H;\n pos[k + 2] = Z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/**\n * Cone angle at peak curl. π/2 is flat; smaller values wind the sheet tighter.\n * ~30° gives a soft, paper-like ridge without self-intersection on typical page\n * aspect ratios (Hong / Nuon / Project Austin all land in the 18–35° band).\n */\nconst THETA_MIN = (30 * Math.PI) / 180;\n/** Gentler opening for full-width lone pages — a tight cone reads as a funnel when\n * the leaf owns the whole viewport and hinges at the edge. */\nconst THETA_MIN_FILL = (44 * Math.PI) / 180;\n\n/**\n * How far past the page the cone apex sits, as a fraction of height, at rest.\n * Closer mid-turn sharpens the diagonal; farther reads more cylindrical.\n * Kept moderately distant so the sheet stays page-like without cloth-y sliding.\n */\nconst APEX_REST = 1.35;\nconst APEX_PEAK = 0.7;\n/** Farther apex on fill: softer diagonal, less cloth-y twist across the full leaf. */\nconst APEX_REST_FILL = 1.85;\nconst APEX_PEAK_FILL = 1.15;\n\n/**\n * Above this |anchor.y − 0.5| the deform is a pure cone; at the mid-edge it\n * blends fully to a cylinder (Project Austin’s conicContribution).\n */\nconst CORNER_PURE = 0.42;\n\n/**\n * Power on fill flop progress. Slightly >1 delays spine rotation so the cone develops\n * over the page before the leaf swings past the edge hinge — kept close to 1 so the\n * flop isn't packed into a late whip (lone pages dissolve rather than land).\n */\nconst FILL_RHO_POWER = 1.08;\n\n/**\n * Natural conical page curl — the Xerox PARC / iBooks model.\n *\n * The leaf wraps an imaginary right circular cone whose apex rides the spine and\n * whose opening angle θ breathes over the turn (Hong, Card & Chen, 2004). A corner\n * grab yields a diagonal cone; a mid-edge grab blends toward a cylinder so the\n * free edge lifts as one. The curled sheet then rotates about the spine and settles\n * flat on the far side. Arc length from the apex is preserved, so the paper never\n * stretches — the motion reads as stiff, premium stock rather than cloth.\n *\n * On a full-width lone page (`anchor.fill`) the hinge is the container edge with no\n * facing half to land on: the cone softens, the flop is delayed so the peel stays\n * over the page longer, and the renderer dissolves the sheet as it finishes.\n */\nexport function deformCone(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n const fill = !!anchor.fill;\n\n // Curl envelope: 0 at the ends (flat), 1 at mid-turn. Soft power keeps the\n // ridge full through the middle third — paper holds its bend rather than\n // peaking and collapsing.\n const curl = Math.pow(Math.sin(Math.PI * t), 0.85);\n\n // θ: π/2 (identity) → THETA_MIN (pronounced curl) → π/2.\n const thetaMin = fill ? THETA_MIN_FILL : THETA_MIN;\n const theta = Math.PI / 2 - curl * (Math.PI / 2 - thetaMin);\n const sinTheta = Math.sin(theta);\n const cosTheta = Math.cos(theta);\n const invSinTheta = 1 / Math.max(sinTheta, 1e-6);\n\n // Apex distance: pulled in as the curl develops for a tighter diagonal.\n const apexRest = fill ? APEX_REST_FILL : APEX_REST;\n const apexPeak = fill ? APEX_PEAK_FILL : APEX_PEAK;\n const apexDist = H * (apexRest + (apexPeak - apexRest) * curl);\n\n // Corner bias: −1 = top edge, +1 = bottom. Near 0 → cylinder blend.\n const bias = (anchor.y - 0.5) * 2;\n const fromBottom = bias >= 0;\n const absBias = Math.abs(bias);\n let conic = Math.min(1, absBias / CORNER_PURE);\n const cylWeight = 1 - conic;\n\n // Cylinder radius matched to the cone’s free-edge cross-section (Austin), so\n // the blend is continuous as the grab slides from corner to mid-edge.\n // Apex sits past the *leading* edge (below for a bottom grab, above for top).\n const AyRef = fromBottom ? H + apexDist : -apexDist;\n const cylR = Math.hypot(W, 0.5 * H - AyRef);\n const cylRadius = Math.max(cylR * sinTheta, W / Math.PI);\n\n // Spine rotation: concurrent with the cone so the page flops while it curls.\n // Same x–z convention as `roll` (ρ: 0 → −π). On fill, delay the flop so the\n // peel develops over the page before the leaf swings past the edge hinge.\n const rhoT = fill ? Math.pow(t, FILL_RHO_POWER) : t;\n const rho = -Math.PI * rhoT;\n const cosR = Math.cos(rho);\n const sinR = Math.sin(rho);\n\n // Near the rests the cone is the identity plane. A finite-radius cylinder blend\n // still carries a tiny residual arc — skip it and rotate the flat sheet.\n if (curl < 1e-4) {\n for (let j = 0; j < ny; j++) {\n const y = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n const k = (j * nx + i) * 3;\n pos[k] = x0 * cosR;\n pos[k + 1] = y;\n pos[k + 2] = -x0 * sinR;\n }\n }\n computeNormals(mesh);\n return;\n }\n\n for (let j = 0; j < ny; j++) {\n const y0 = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n\n let cx = x0;\n let cy = y0;\n let cz = 0;\n\n if (conic > 1e-5) {\n // Canonical cone: apex above the page (A < 0) makes the *near* edge lead.\n // In our y-down leaf, that means the top. For a bottom grab we flip into\n // that space, deform, then flip back — one code path for both corners.\n const yIn = fromBottom ? H - y0 : y0;\n const A = -apexDist;\n const R = Math.hypot(x0, yIn - A);\n if (R > 1e-8) {\n const r = R * sinTheta;\n const beta = Math.asin(Math.min(1, Math.max(0, x0 / R))) * invSinTheta;\n const oneCos = 1 - Math.cos(beta);\n const yCone = R + A - r * oneCos * sinTheta;\n cx = r * Math.sin(beta);\n cy = fromBottom ? H - yCone : yCone;\n cz = r * oneCos * cosTheta;\n }\n }\n\n let bx = cx;\n let by = cy;\n let bz = cz;\n\n if (cylWeight > 1e-5) {\n const beta = x0 / cylRadius;\n const sx = cylRadius * Math.sin(beta);\n const sz = cylRadius * (1 - Math.cos(beta));\n bx = conic * cx + cylWeight * sx;\n by = conic * cy + cylWeight * y0;\n bz = conic * cz + cylWeight * sz;\n }\n\n const k = (j * nx + i) * 3;\n pos[k] = bx * cosR + bz * sinR;\n pos[k + 1] = by;\n pos[k + 2] = -bx * sinR + bz * cosR;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/** Peak material width occupied by the bending wave. */\nconst MAX_BEND_LENGTH = 0.34;\n\n/** Maximum tilt of the rolling axis for a corner-led turn. */\nconst MAX_TILT = (11 * Math.PI) / 180;\n\nconst PROFILE_STEPS = 128;\n\n/**\n * Unit-length raised-cosine curvature profile.\n *\n * κ(s) = π(1 - cos(2πs)), so its integrated tangent angle is\n * θ(s) = πs - ½sin(2πs). Curvature is exactly zero at both ends and integrates\n * to π: the incoming and outgoing planes are flat, opposite-facing, and meet\n * the bend without a crease. X/Z are arc-length integrals of cos(θ)/sin(θ).\n */\nfunction makeProfile(): { x: Float64Array; z: Float64Array } {\n const x = new Float64Array(PROFILE_STEPS + 1);\n const z = new Float64Array(PROFILE_STEPS + 1);\n const ds = 1 / PROFILE_STEPS;\n const theta = (s: number): number => Math.PI * s - 0.5 * Math.sin(2 * Math.PI * s);\n for (let i = 1; i <= PROFILE_STEPS; i++) {\n const a = theta((i - 1) * ds);\n const b = theta(i * ds);\n x[i] = x[i - 1]! + 0.5 * (Math.cos(a) + Math.cos(b)) * ds;\n z[i] = z[i - 1]! + 0.5 * (Math.sin(a) + Math.sin(b)) * ds;\n }\n // Symmetry makes this analytically zero; remove accumulated floating-point drift.\n x[PROFILE_STEPS] = 0;\n return { x, z };\n}\n\nconst PROFILE = makeProfile();\nconst PROFILE_HEIGHT = PROFILE.z[PROFILE_STEPS]!;\n\nfunction sampleProfile(s: number): [x: number, z: number] {\n const p = Math.max(0, Math.min(1, s)) * PROFILE_STEPS;\n const i = Math.min(PROFILE_STEPS - 1, Math.floor(p));\n const f = p - i;\n return [\n PROFILE.x[i]! + (PROFILE.x[i + 1]! - PROFILE.x[i]!) * f,\n PROFILE.z[i]! + (PROFILE.z[i + 1]! - PROFILE.z[i]!) * f,\n ];\n}\n\n/**\n * Traveling smooth-curvature leaf curl.\n *\n * Inspired by rolling-cylinder page curls and modern developable-surface book\n * simulations, but with a compact raised-cosine curvature wave instead of a\n * constant-radius cylinder. The sheet has three regions:\n *\n * 1. untouched paper before the wave,\n * 2. an isometric bend whose curvature smoothly rises and falls,\n * 3. a flat, reflected flap after the wave.\n *\n * The wave travels from the free edge to just beyond the spine while its width grows\n * and collapses. Integrating a unit tangent preserves material length exactly; straight\n * ruling lines preserve distance along the curl axis. Curvature reaches zero at both\n * seams, eliminating the hard crease shared by circular/fold models.\n */\nexport function deformLeaf(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n // Exact rest poses avoid the tiny numerical ridge left by a vanishing wave.\n if (t <= 1e-7 || t >= 1 - 1e-7) {\n const side = t < 0.5 ? 1 : -1;\n for (let j = 0; j < ny; j++) {\n const y = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const k = (j * nx + i) * 3;\n pos[k] = side * (i / mesh.cols) * W;\n pos[k + 1] = y;\n pos[k + 2] = 0;\n }\n }\n computeNormals(mesh);\n return;\n }\n\n // The wave broadens through the middle, then tightens as the leaf settles.\n const envelope = Math.pow(Math.sin(Math.PI * t), 0.72);\n const bendLength = Math.max(1e-6, W * MAX_BEND_LENGTH * envelope);\n\n // Bottom grabs tilt the ruling clockwise in page coordinates; top grabs mirror it.\n // The tilt fades at both rest states so the page lands exactly square.\n const anchorBias = Math.max(-1, Math.min(1, (anchor.y - 0.5) * 2));\n const tilt = anchorBias * MAX_TILT * Math.pow(Math.sin(Math.PI * t), 1.15);\n const nX = Math.cos(tilt);\n const nY = Math.sin(tilt);\n const fX = -nY;\n const fY = nX;\n\n // Place the axis outside the whole rectangle at t=0 and beyond the spine at t=1.\n // Accounting for tilt keeps every row in the correct physical region at the ends.\n const diagonalReach = Math.abs(nY) * H * 0.5;\n const startX = W + diagonalReach / nX;\n const endX = -(bendLength + diagonalReach) / nX;\n const axisX = startX + (endX - startX) * t;\n const axisY = H * 0.5;\n\n for (let j = 0; j < ny; j++) {\n const y0 = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W;\n const relX = x0 - axisX;\n const relY = y0 - axisY;\n const d = relX * nX + relY * nY;\n const e = relX * fX + relY * fY;\n const k = (j * nx + i) * 3;\n\n if (d <= 0) {\n // The wave has not reached this point: untouched front-facing paper.\n pos[k] = x0;\n pos[k + 1] = y0;\n pos[k + 2] = 0;\n continue;\n }\n\n let normalDistance: number;\n let z: number;\n if (d < bendLength) {\n // Scale the unit arc-length profile by the material width of this wave.\n const [profileX, profileZ] = sampleProfile(d / bendLength);\n normalDistance = bendLength * profileX;\n z = bendLength * profileZ;\n } else {\n // The tangent has rotated by π; paper leaves the wave flat and reversed.\n normalDistance = -(d - bendLength);\n z = bendLength * PROFILE_HEIGHT;\n }\n\n pos[k] = axisX + fX * e + nX * normalDistance;\n pos[k + 1] = axisY + fY * e + nY * normalDistance;\n pos[k + 2] = z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/**\n * Peak inertial tip lag in radians (~30°). How far the free edge trails the\n * spine rotation at the height of the acceleration phase, and symmetrically how\n * far it follows through while the turn decelerates.\n */\nconst SWAY = 0.52;\n\n/**\n * Extra lag for rows far from the grabbed corner (0 = whole edge moves as one).\n * The driven corner is held; the diagonal remainder of the sheet trails it.\n */\nconst LAG = 0.45;\n\n/**\n * Inertial follow-through page flick.\n *\n * The leaf is a cantilever clamped at the spine and driven through a rotation\n * ρ(t) = πt. Paper inertia (the Euler force, ∝ −ρ̈) bends the sheet opposite the\n * angular acceleration with a first-mode-like profile — zero curvature at the\n * clamped spine, maximal at the free edge. Under an eased turn the acceleration\n * changes sign at mid-flight, so the free edge trails while the turn winds up,\n * swings through vertical, then overtakes and settles as the turn slows: the\n * follow-through/overlapping-action principle, grounded in base-rotated\n * cantilever dynamics. Tangent angles are integrated along the width, so the\n * sheet never stretches, and the curvature direction reversing over the turn is\n * what none of the wrap-style curls do.\n *\n * Rows far from the grabbed corner lag slightly more than the driven row, so a\n * corner grab pulls a soft living diagonal across the sheet.\n */\nexport function deformFlick(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const cols = mesh.cols;\n const pos = mesh.positions;\n const ds = W / cols;\n\n // Base rotation about the spine, 0 → π (same landing convention as `simple`).\n const rho = Math.PI * t;\n\n // Inertial amplitude ∝ −ρ̈ of an eased unit turn: the tip trails (negative)\n // while accelerating, leads (positive) while braking, and is zero at both\n // rests and at peak speed mid-turn.\n const amplitude = -SWAY * Math.sin(2 * Math.PI * t);\n\n for (let j = 0; j < ny; j++) {\n const v = j / mesh.rows;\n // The grabbed row is driven directly; distance from it adds trailing inertia.\n const rowAmplitude = amplitude * (1 + LAG * Math.abs(v - anchor.y));\n const y = v * H;\n\n // Integrate the unit tangent of θ(u) = ρ + A·u² across the width (trapezoid\n // per grid cell). u² is the first-mode tangent shape: clamped flat at the\n // spine, curving ever more freely toward the edge. Exact arc length.\n let x = 0;\n let z = 0;\n let cosPrev = Math.cos(rho);\n let sinPrev = Math.sin(rho);\n pos[(j * nx) * 3] = 0;\n pos[(j * nx) * 3 + 1] = y;\n pos[(j * nx) * 3 + 2] = 0;\n for (let i = 1; i < nx; i++) {\n const u = i / cols;\n const theta = rho + rowAmplitude * u * u;\n const cosCur = Math.cos(theta);\n const sinCur = Math.sin(theta);\n x += 0.5 * (cosPrev + cosCur) * ds;\n z += 0.5 * (sinPrev + sinCur) * ds;\n cosPrev = cosCur;\n sinPrev = sinCur;\n\n const k = (j * nx + i) * 3;\n pos[k] = x;\n pos[k + 1] = y;\n pos[k + 2] = z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\nimport type { CurlAnchor } from './types';\n\n/**\n * Peak S-curve amplitude in radians. Mid-turn the tangent oscillates about the\n * spine angle so the cross-section forms a true inflection (two opposite bends).\n * ~30° reads as thick stock turned by hand, not a knife crease.\n */\nconst S_AMP = 0.52;\n\n/**\n * Early free-edge peel lead in radians. The grabbed tip lifts ahead of the body\n * — first motion of a careful hand turn. Strongest near t≈0.35, gone by rest.\n */\nconst PEEL_LEAD = 0.62;\n\n/**\n * How much rows far from the grab trail the driven corner (0 = rigid across height).\n * Builds the soft diagonal of a corner-led hand turn.\n */\nconst ROW_LAG = 0.5;\n\n/**\n * Power on spine progress for full-width lone pages. >1 delays the flop so the\n * S develops over the sheet before the leaf dissolves off the edge hinge.\n */\nconst FILL_RHO_POWER = 1.12;\n\n/**\n * Silk — hand-turned S-curve page.\n *\n * This is not a traveling contact wave (`leaf`), not a cone wrap (`cone`), not a\n * cylinder roll (`roll`), and not a single-mode inertial cantilever (`flick`).\n *\n * Real paper turned by hand takes an S-shaped cross-section: a main bend through\n * the near half and a reverse bend through the far half (the flap over the\n * fingers). The sheet is a developable strip clamped at the spine and driven\n * through a base rotation ρ(t) = πt. Along the width the tangent is:\n *\n * θ(u) = ρ + env·S·sin(2πu) + peel(t)·u²\n *\n * where:\n * - sin(2πu) is a full-period S (zero net at spine and free edge, opposite\n * bends in the two halves) — a true inflection that none of the other\n * models produce mid-turn,\n * - u² is an early peel lead that lifts the free edge first, then releases.\n *\n * env = sin(πt) holds the S through the middle and forces exact flat rests.\n * Rows far from the tap lag so a corner grab draws a living diagonal.\n * Arc length is integrated with the unit tangent (trapezoid per grid cell), so\n * the paper never stretches.\n */\nexport function deformSilk(mesh: PageMesh, W: number, H: number, t: number, anchor: CurlAnchor): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const cols = mesh.cols;\n const pos = mesh.positions;\n const ds = W / cols;\n const fill = !!anchor.fill;\n\n // Exact rests: shaping terms vanish with env, but pin the mesh explicitly.\n if (t <= 1e-7 || t >= 1 - 1e-7) {\n const side = t < 0.5 ? 1 : -1;\n for (let j = 0; j < ny; j++) {\n const y = (j / mesh.rows) * H;\n for (let i = 0; i < nx; i++) {\n const k = (j * nx + i) * 3;\n pos[k] = side * (i / mesh.cols) * W;\n pos[k + 1] = y;\n pos[k + 2] = 0;\n }\n }\n computeNormals(mesh);\n return;\n }\n\n // Spine rotation. On a fill leaf, delay slightly so the S develops on-page.\n const rhoT = fill ? Math.pow(t, FILL_RHO_POWER) : t;\n const rho = Math.PI * rhoT;\n\n // S envelope: 0 at rests, full through the middle third.\n const env = Math.pow(Math.sin(Math.PI * t), 0.8);\n const sAmp = S_AMP * env;\n\n // Early peel lead on the free edge: peaks before mid-turn, zero at both rests.\n // sin(πt)·(1−t) peaks near t≈0.35 — corner lifts while the body catches up.\n const peelAmp = PEEL_LEAD * Math.sin(Math.PI * t) * (1 - t);\n\n for (let j = 0; j < ny; j++) {\n const v = j / mesh.rows;\n const y = v * H;\n // Grabbed row is driven hardest; far rows trail (soft diagonal).\n const rowLead = 1 - ROW_LAG * Math.abs(v - anchor.y);\n const rowS = sAmp * (0.75 + 0.25 * rowLead);\n const rowPeel = peelAmp * rowLead;\n\n let x = 0;\n let z = 0;\n let cosPrev = Math.cos(rho);\n let sinPrev = Math.sin(rho);\n pos[j * nx * 3] = 0;\n pos[j * nx * 3 + 1] = y;\n pos[j * nx * 3 + 2] = 0;\n\n for (let i = 1; i < nx; i++) {\n const u = i / cols;\n // S inflection + early free-edge peel. Spine (u=0) is exactly ρ.\n const theta = rho + rowS * Math.sin(2 * Math.PI * u) + rowPeel * u * u;\n const cosCur = Math.cos(theta);\n const sinCur = Math.sin(theta);\n x += 0.5 * (cosPrev + cosCur) * ds;\n z += 0.5 * (sinPrev + sinCur) * ds;\n cosPrev = cosCur;\n sinPrev = sinCur;\n\n const k = (j * nx + i) * 3;\n pos[k] = x;\n pos[k + 1] = y;\n pos[k + 2] = z;\n }\n }\n\n computeNormals(mesh);\n}\n","import { computeNormals, type PageMesh } from './mesh';\n\n/**\n * Flat page turn — the plainest flip, matching the CSS renderer. The leaf stays a rigid\n * flat sheet and rotates around the spine (angle 0→π), going edge-on at the midpoint. No\n * cylinder bend; GPU-rendered here with the shared perspective so it reads like the CSS fold.\n */\nexport function deformSimple(mesh: PageMesh, W: number, H: number, t: number): void {\n const nx = mesh.cols + 1;\n const ny = mesh.rows + 1;\n const pos = mesh.positions;\n\n const angle = t * Math.PI;\n const cosA = Math.cos(angle);\n const sinA = Math.sin(angle);\n\n for (let i = 0; i < nx; i++) {\n const x0 = (i / mesh.cols) * W; // distance from the spine\n const X = x0 * cosA;\n const Z = x0 * sinA;\n for (let j = 0; j < ny; j++) {\n const k = (j * nx + i) * 3;\n pos[k] = X;\n pos[k + 1] = (j / mesh.rows) * H;\n pos[k + 2] = Z;\n }\n }\n\n computeNormals(mesh);\n}\n","/**\n * Page-curl registry. Each model turns a 0→1 flip progress into a bent page mesh; the\n * WebGL2 renderer picks one by the `curl` option. `anchored` models fold from a corner\n * given by `anchor.y` (0 = top, 1 = bottom — where the reader tapped); the others roll\n * or rotate the whole free edge and ignore it.\n *\n * Importing this module pulls in the deform math, so only the WebGL2 renderer does.\n * The engine references the lightweight `./types` (union + validation list) instead.\n */\nimport { deformRoll } from './roll';\nimport { deformCone } from './cone';\nimport { deformLeaf } from './leaf';\nimport { deformFlick } from './flick';\nimport { deformSilk } from './silk';\nimport { deformSimple } from './simple';\nimport type { CurlModel, CurlType } from './types';\n\nexport { createPageMesh, computeNormals, type PageMesh } from './mesh';\nexport { CURL_TYPES, DEFAULT_CURL } from './types';\nexport type { CurlAnchor, CurlDeform, CurlModel, CurlType } from './types';\n\nexport const CURLS: Record<CurlType, CurlModel> = {\n roll: { deform: deformRoll, anchored: false },\n cone: { deform: deformCone, anchored: true },\n leaf: { deform: deformLeaf, anchored: true },\n flick: { deform: deformFlick, anchored: true },\n silk: { deform: deformSilk, anchored: true },\n simple: { deform: deformSimple, anchored: false },\n};\n","/** Create a WebGL2 context, or throw so the caller can fall back to CSS (§8.4). */\nexport function createGlContext(canvas: HTMLCanvasElement): WebGL2RenderingContext {\n const gl = canvas.getContext('webgl2', {\n alpha: true,\n premultipliedAlpha: true,\n antialias: true,\n depth: false,\n stencil: false,\n // We render on demand (not every frame), so the buffer must persist between\n // composites — otherwise the settled page flickers once the flip animation\n // stops and nothing is redrawing each frame.\n preserveDrawingBuffer: true,\n });\n if (gl === null) {\n throw new Error('WebglRenderer: could not create a WebGL2 context.');\n }\n return gl;\n}\n","/** Compile + link a shader program; throws with the driver log on failure. */\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vertexSrc: string,\n fragmentSrc: string,\n): WebGLProgram {\n const program = gl.createProgram();\n if (program === null) throw new Error('WebglRenderer: could not create a program.');\n\n const vs = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);\n const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n\n // Shaders can be detached/deleted once linked.\n gl.deleteShader(vs);\n gl.deleteShader(fs);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program);\n gl.deleteProgram(program);\n throw new Error(`WebglRenderer: program link failed: ${log ?? 'unknown error'}`);\n }\n return program;\n}\n\nfunction compileShader(gl: WebGL2RenderingContext, type: number, src: string): WebGLShader {\n const shader = gl.createShader(type);\n if (shader === null) throw new Error('WebglRenderer: could not create a shader.');\n gl.shaderSource(shader, src);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader);\n gl.deleteShader(shader);\n throw new Error(`WebglRenderer: shader compile failed: ${log ?? 'unknown error'}`);\n }\n return shader;\n}\n","/** Create a page texture with clamp + linear filtering (no mipmaps — §8.3). */\nexport function createTexture(gl: WebGL2RenderingContext): WebGLTexture {\n const tex = gl.createTexture();\n if (tex === null) throw new Error('WebglRenderer: could not create a texture.');\n gl.bindTexture(gl.TEXTURE_2D, tex);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n return tex;\n}\n\n/** Upload a decoded page raster (ImageBitmap or canvas) into `tex`. */\nexport function uploadTexture(\n gl: WebGL2RenderingContext,\n tex: WebGLTexture,\n source: TexImageSource,\n): void {\n gl.bindTexture(gl.TEXTURE_2D, tex);\n // No Y flip: the vertex shader maps aUnit.y=0 to the page top and samples the\n // source's top row there. UNPACK_FLIP_Y is honored for <canvas> sources but\n // IGNORED for ImageBitmap, so flipping would invert PDF (canvas) pages while\n // leaving image (ImageBitmap) pages upright. Off keeps both consistent.\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);\n}\n","import {\n CURLS,\n createPageMesh,\n DEFAULT_CURL,\n type CurlAnchor,\n type CurlType,\n type PageMesh,\n} from '../geometry/curls';\nimport type { Spread } from '../engine/spread';\nimport type {\n FlipDirection,\n LayoutMetrics,\n Renderer,\n RenderOptions,\n SpreadContent,\n} from './types';\nimport { createGlContext } from './gl/context';\nimport { createProgram } from './gl/program';\nimport { createTexture, uploadTexture } from './gl/texture';\n\n// Flat program: a page rect (device px, top-left origin) with zoom/pan and a soft\n// permanent gutter shadow toward the spine so the turning leaf's shadow lands into it.\nconst FLAT_VERT = `#version 300 es\nin vec2 aUnit;\nuniform vec2 uViewport;\nuniform vec4 uRect;\nuniform vec3 uView;\nout vec2 vUv;\nvoid main() {\n vec2 px = uRect.xy + aUnit * uRect.zw;\n px = px * uView.x + uView.yz;\n vec2 clip = px / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUnit;\n}`;\n\nconst FLAT_FRAG = `#version 300 es\nprecision mediump float;\nin vec2 vUv;\nuniform sampler2D uTex;\nuniform float uGutterSide; // +1 = spine at right edge, -1 = at left edge, 0 = none\nout vec4 outColor;\nvoid main() {\n vec4 c = texture(uTex, vUv);\n if (uGutterSide != 0.0) {\n float d = uGutterSide > 0.0 ? (1.0 - vUv.x) : vUv.x;\n c.rgb *= mix(0.72, 1.0, smoothstep(0.0, 0.10, d));\n }\n outColor = c;\n}`;\n\n// Curl program: a thin pass-through for the CPU-deformed page mesh (positions and\n// normals come from the selected curl model). It places the leaf, applies a top-safe depth\n// perspective, and lights from the true surface normal.\nconst CURL_VERT = `#version 300 es\nin vec3 aPos; // leaf-local device px: x = bent offset from spine, y 0..H, z depth\nin vec3 aNormal;\nin vec2 aUv;\nuniform vec2 uViewport;\nuniform vec3 uView; // scale, tx, ty\nuniform float uOriginX; // spine x in device px\nuniform float uDir; // +1 leaf to the right of spine, -1 to the left\nuniform float uLeafW; // leaf width in device px (perspective scale)\nout vec2 vUv;\nout float vFacing;\nout float vU;\nvoid main() {\n float bookX = uOriginX + uDir * aPos.x;\n float bookY = aPos.y;\n float Z = aPos.z;\n // Eye distance, in leaf widths. Keeps the perspective resolution-independent. 5.3 puts the\n // roll's deepest point ~14% larger than flat; stronger than that (a nearer eye) balloons the\n // turning sheet and reads more like a fisheye than a page lifting.\n float D = uLeafW * 5.3;\n float persp = D / max(D - Z, 1.0);\n float cx = uViewport.x * 0.5;\n float cy = uViewport.y * 0.5;\n float px = cx + (bookX - cx) * persp;\n float py = cy + (bookY - cy) * min(persp, 1.0); // Y only shrinks (top-safe)\n vec2 p = vec2(px, py) * uView.x + uView.yz;\n vec2 clip = p / uViewport * 2.0 - 1.0;\n gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);\n vUv = aUv;\n vFacing = aNormal.z;\n vU = aUv.x;\n}`;\n\nconst CURL_FRAG = `#version 300 es\nprecision mediump float;\nin vec2 vUv;\nin float vFacing;\nin float vU;\nuniform sampler2D uFront;\nuniform sampler2D uBack;\nuniform highp float uDir;\nuniform float uGloss; // 0 disables the specular highlight (e.g. the flat 'simple' curl)\nuniform float uAlpha; // leaf opacity; a lone page dissolves into the page landing beneath it\nout vec4 outColor;\nvoid main() {\n // The turn mesh carries its own facing in the normal; the front points at the viewer\n // (normal.z > 0) until the leaf passes vertical, for both flip directions. u->x still\n // flips with uDir so a left-hand leaf reads spine-inward.\n bool showFront = vFacing > 0.0;\n float fx = uDir > 0.0 ? vUv.x : 1.0 - vUv.x;\n vec4 c = showFront ? texture(uFront, vec2(fx, vUv.y)) : texture(uBack, vec2(1.0 - fx, vUv.y));\n\n // Fold shading: the sheet darkens sharply where it curves away from the viewer, so the\n // rolled edge reads as a deep crease with a soft highlight riding the ridge.\n float diff = clamp(abs(vFacing), 0.0, 1.0);\n float lit = mix(0.42, 1.0, diff);\n lit *= mix(0.7, 1.0, smoothstep(0.0, 0.12, vU)); // spine crease matches the gutter shadow\n\n // Glossy specular: brightest where the surface tilts ~30 deg from facing the viewer,\n // so a thin glossy highlight rides across the sheet as it rolls.\n float spec = pow(max(cos(acos(diff) - 0.52), 0.0), 220.0) * 0.22 * uGloss;\n\n c.rgb = clamp(c.rgb * lit + spec, 0.0, 1.0);\n // Premultiplied alpha (blendFunc ONE, ONE_MINUS_SRC_ALPHA): scale colour as well as alpha,\n // or fading would brighten the sheet additively instead of dissolving it.\n outColor = vec4(c.rgb * uAlpha, c.a * uAlpha);\n}`;\n\nconst GRID_COLS = 28;\nconst GRID_ROWS = 36;\n/** Progress at which a lone page starts dissolving. A full-width leaf has no facing half to\n * flop onto, so instead of landing on empty space it fades into the page arriving beneath it\n * over the rest of the turn. Roll is already flat by mid-turn; curling models stay bent\n * longer, so they dissolve later. The fade itself eases so the leaf settles rather than\n * vanishing while still swinging. */\nconst FILL_FADE_START_ROLL = 0.7;\nconst FILL_FADE_START_CURL = 0.8;\nconst RESTORE_TIMEOUT_MS = 4000;\nconst MAX_TEXTURE_CACHE_BYTES = 256 * 1024 * 1024;\n\ninterface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\ninterface FlipState {\n underLeft: TexImageSource | null;\n underRight: TexImageSource | null;\n underFull: TexImageSource | null; // fill mode\n front: TexImageSource | null;\n back: TexImageSource | null;\n dir: number; // +1 forward (leaf right of spine), -1 backward (left)\n fill: boolean;\n}\n\n/** How far to shift a spread to centre a lone page, in units of a quarter container width:\n * -1 = only a right page (cover), +1 = only a left page (back), 0 = a full spread. */\nfunction shiftUnit(content: SpreadContent): number {\n const lone = (content.left === null) !== (content.right === null);\n return lone ? (content.right !== null ? -1 : 1) : 0;\n}\n\n/**\n * GPU renderer (WebGL2, hand-written). The turning leaf is a dense grid deformed on\n * the CPU by the selected curl model (see geometry/curls) and\n * uploaded each frame, lit from real normals; the static pages are flat quads with a\n * gutter shadow. Restores\n * GL state on context loss; signals onFatal (→ CSS fallback) when unrecoverable.\n */\nexport class WebglRenderer implements Renderer {\n #container: HTMLElement | null = null;\n #canvas: HTMLCanvasElement | null = null;\n #gl: WebGL2RenderingContext | null = null;\n\n #flat: WebGLProgram | null = null;\n #curl: WebGLProgram | null = null;\n #quadVao: WebGLVertexArrayObject | null = null;\n #curlVao: WebGLVertexArrayObject | null = null;\n #posBuf: WebGLBuffer | null = null;\n #normBuf: WebGLBuffer | null = null;\n #idxCount = 0;\n #mesh: PageMesh = createPageMesh(GRID_COLS, GRID_ROWS);\n\n #texCache = new Map<TexImageSource, { tex: WebGLTexture; bytes: number }>();\n #cacheBytes = 0;\n #blankTex: WebGLTexture | null = null;\n\n #flatU: Record<string, WebGLUniformLocation | null> = {};\n #curlU: Record<string, WebGLUniformLocation | null> = {};\n\n #view = { scale: 1, tx: 0, ty: 0 };\n #dpr = 1;\n #content: SpreadContent | null = null;\n #fill = false;\n #flip: FlipState | null = null;\n #flipT = 0;\n #curlType: CurlType = DEFAULT_CURL;\n #anchor: CurlAnchor = { y: 0.5 };\n // A lone page (cover / book front-back) sits on one half; these center it by shifting\n // the view a quarter-width, interpolated across a flip so the open/close doesn't jump.\n #shiftX = 0;\n #fromShiftUnit = 0;\n #toShiftUnit = 0;\n // Book aspect fit: the 2-page spread is letterboxed to the pages' aspect, centered\n // in the container, so pages fill their halves without stretching. 0 = aspect unknown.\n #pageAspect = 0;\n #bookW = 0;\n #bookH = 0;\n\n #contextLost = false;\n #fatalFired = false;\n #restoreTimer: ReturnType<typeof setTimeout> | null = null;\n #fatalHandler: (() => void) | null = null;\n\n mount(container: HTMLElement): Promise<void> {\n this.#container = container;\n const doc = container.ownerDocument;\n const canvas = doc.createElement('canvas');\n canvas.style.cssText = 'display:block;width:100%;height:100%;';\n container.append(canvas);\n this.#canvas = canvas;\n\n canvas.addEventListener('webglcontextlost', this.#onContextLost as EventListener);\n canvas.addEventListener('webglcontextrestored', this.#onContextRestored as EventListener);\n doc.addEventListener('visibilitychange', this.#onVisibilityChange);\n\n this.#gl = createGlContext(canvas); // throws if unavailable -> engine falls back (§8.4)\n this.#buildGlResources();\n return Promise.resolve();\n }\n\n destroy(): void {\n this.#clearRestoreTimer();\n const canvas = this.#canvas;\n if (canvas !== null) {\n canvas.removeEventListener('webglcontextlost', this.#onContextLost as EventListener);\n canvas.removeEventListener('webglcontextrestored', this.#onContextRestored as EventListener);\n canvas.ownerDocument.removeEventListener('visibilitychange', this.#onVisibilityChange);\n }\n const gl = this.#gl;\n if (gl !== null) {\n for (const cached of this.#texCache.values()) gl.deleteTexture(cached.tex);\n if (this.#blankTex !== null) gl.deleteTexture(this.#blankTex);\n if (this.#quadVao !== null) gl.deleteVertexArray(this.#quadVao);\n if (this.#curlVao !== null) gl.deleteVertexArray(this.#curlVao);\n if (this.#flat !== null) gl.deleteProgram(this.#flat);\n if (this.#curl !== null) gl.deleteProgram(this.#curl);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n canvas?.remove();\n this.#texCache.clear();\n this.#cacheBytes = 0;\n this.#blankTex = null;\n this.#container = null;\n this.#canvas = null;\n this.#gl = null;\n this.#content = null;\n this.#flip = null;\n }\n\n onFatal(handler: () => void): void {\n this.#fatalHandler = handler;\n }\n\n renderSpread(_spread: Spread, content: SpreadContent, options?: RenderOptions): void {\n this.#content = content;\n this.#fill = options?.fill ?? false;\n this.#flip = null;\n this.#trackAspect(content);\n this.#render();\n }\n\n /** Remember the page aspect (width/height) so the book can be letterboxed to it. */\n #trackAspect(content: SpreadContent): void {\n const p = content.left ?? content.right;\n if (p && p.height) this.#pageAspect = p.width / p.height;\n }\n\n beginFlip(\n from: SpreadContent,\n to: SpreadContent,\n direction: FlipDirection,\n options?: RenderOptions,\n ): void {\n const fill = options?.fill ?? false;\n this.#flipT = 0;\n if (options?.curl) this.#curlType = options.curl;\n if (options?.anchor) this.#anchor = options.anchor;\n this.#fromShiftUnit = fill ? 0 : shiftUnit(from);\n this.#toShiftUnit = fill ? 0 : shiftUnit(to);\n this.#trackAspect(to);\n if (fill) {\n this.#flip = {\n underLeft: null,\n underRight: null,\n underFull: to.right ?? to.left,\n front: from.right ?? from.left,\n // A lone page has no facing \"next\" leaf on its back; show the same page mirrored\n // (the shader flips the back UV) so it reads as the sheet's own reverse side.\n back: from.right ?? from.left,\n dir: direction === 'forward' ? 1 : -1,\n fill: true,\n };\n } else if (direction === 'forward') {\n this.#flip = {\n underLeft: from.left,\n underRight: to.right,\n underFull: null,\n front: from.right,\n back: to.left,\n dir: 1,\n fill: false,\n };\n } else {\n this.#flip = {\n underLeft: to.left,\n underRight: from.right,\n underFull: null,\n front: from.left,\n back: to.right,\n dir: -1,\n fill: false,\n };\n }\n this.#render();\n }\n\n setFlipProgress(t: number, _direction: FlipDirection): void {\n this.#flipT = t;\n this.#render();\n }\n\n setViewTransform(scale: number, x: number, y: number): void {\n this.#view = { scale, tx: x, ty: y };\n this.#render();\n }\n\n measure(): LayoutMetrics {\n const el = this.#container;\n const w = el?.clientWidth ?? 0;\n const h = el?.clientHeight ?? 0;\n const known = this.#pageAspect > 0;\n const book = known ? this.#bookBox() : undefined;\n const content = known ? this.#contentBox() : undefined;\n return { containerWidth: w, containerHeight: h, pageWidth: w / 2, pageHeight: h, book, content };\n }\n\n /** The fitted book rect in container CSS px (letterbox aware) — a stable 2-page area. */\n #bookBox(): { x: number; y: number; width: number; height: number } {\n const el = this.#container;\n const cw = el?.clientWidth ?? 0;\n const ch = el?.clientHeight ?? 0;\n if (this.#pageAspect <= 0 || cw <= 0 || ch <= 0) return { x: 0, y: 0, width: cw, height: ch };\n const ba = (this.#fill ? 1 : 2) * this.#pageAspect;\n let bw = cw;\n let bh = ch;\n if (ba > cw / ch) bh = cw / ba;\n else bw = ch * ba;\n return { x: (cw - bw) / 2, y: (ch - bh) / 2, width: bw, height: bh };\n }\n\n /** Where the current spread is actually painted: the book, or its centered half for a lone\n * page (mirrors the draw-time #shiftX centering). This is what the engine hit-tests against. */\n #contentBox(): { x: number; y: number; width: number; height: number } {\n const b = this.#bookBox();\n const lone = !this.#fill && this.#content !== null && shiftUnit(this.#content) !== 0;\n return lone ? { x: b.x + b.width / 4, y: b.y, width: b.width / 2, height: b.height } : b;\n }\n\n // --- context loss / restore ---------------------------------------------------\n\n #onContextLost = (event: Event): void => {\n event.preventDefault();\n this.#contextLost = true;\n if (this.#restoreTimer === null) {\n this.#restoreTimer = setTimeout(() => this.#fatal(), RESTORE_TIMEOUT_MS);\n }\n };\n\n #onContextRestored = (): void => {\n this.#clearRestoreTimer();\n this.#contextLost = false;\n this.#buildGlResources();\n this.#render();\n };\n\n #onVisibilityChange = (): void => {\n if (this.#canvas?.ownerDocument.visibilityState === 'visible') this.#render();\n };\n\n #fatal(): void {\n if (this.#fatalFired) return;\n this.#fatalFired = true;\n this.#fatalHandler?.();\n }\n\n #clearRestoreTimer(): void {\n if (this.#restoreTimer !== null) {\n clearTimeout(this.#restoreTimer);\n this.#restoreTimer = null;\n }\n }\n\n #buildGlResources(): void {\n const gl = this.#gl;\n if (gl === null) return;\n\n this.#flat = createProgram(gl, FLAT_VERT, FLAT_FRAG);\n this.#curl = createProgram(gl, CURL_VERT, CURL_FRAG);\n for (const name of ['uViewport', 'uRect', 'uView', 'uTex', 'uGutterSide']) {\n this.#flatU[name] = gl.getUniformLocation(this.#flat, name);\n }\n for (const name of ['uViewport', 'uView', 'uOriginX', 'uDir', 'uLeafW', 'uFront', 'uBack', 'uGloss', 'uAlpha']) {\n this.#curlU[name] = gl.getUniformLocation(this.#curl, name);\n }\n\n this.#quadVao = this.#makeQuadVao(gl, this.#flat);\n this.#buildCurlMesh(gl, this.#curl);\n\n this.#texCache.clear();\n this.#cacheBytes = 0;\n this.#blankTex = this.#makeBlankTexture(gl);\n gl.useProgram(this.#flat);\n gl.uniform1i(this.#flatU.uTex ?? null, 0);\n gl.useProgram(this.#curl);\n gl.uniform1i(this.#curlU.uFront ?? null, 0);\n gl.uniform1i(this.#curlU.uBack ?? null, 1);\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); // premultiplied alpha\n }\n\n #makeQuadVao(gl: WebGL2RenderingContext, program: WebGLProgram): WebGLVertexArrayObject {\n const vao = gl.createVertexArray();\n const buffer = gl.createBuffer();\n if (vao === null || buffer === null) throw new Error('WebglRenderer: could not create buffers.');\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW);\n const aUnit = gl.getAttribLocation(program, 'aUnit');\n gl.enableVertexAttribArray(aUnit);\n gl.vertexAttribPointer(aUnit, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n return vao;\n }\n\n #buildCurlMesh(gl: WebGL2RenderingContext, program: WebGLProgram): void {\n const nx = GRID_COLS + 1;\n const indices: number[] = [];\n for (let j = 0; j < GRID_ROWS; j++) {\n for (let i = 0; i < GRID_COLS; i++) {\n const a = j * nx + i;\n const b = a + 1;\n const c = a + nx;\n const d = c + 1;\n indices.push(a, c, b, b, c, d);\n }\n }\n this.#idxCount = indices.length;\n\n const vao = gl.createVertexArray();\n this.#posBuf = gl.createBuffer();\n this.#normBuf = gl.createBuffer();\n const uvBuf = gl.createBuffer();\n const idxBuf = gl.createBuffer();\n if (vao === null || this.#posBuf === null || this.#normBuf === null || uvBuf === null || idxBuf === null) {\n throw new Error('WebglRenderer: could not create mesh buffers.');\n }\n gl.bindVertexArray(vao);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#posBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.positions, gl.DYNAMIC_DRAW);\n const aPos = gl.getAttribLocation(program, 'aPos');\n gl.enableVertexAttribArray(aPos);\n gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#normBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.normals, gl.DYNAMIC_DRAW);\n const aNormal = gl.getAttribLocation(program, 'aNormal');\n gl.enableVertexAttribArray(aNormal);\n gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf);\n gl.bufferData(gl.ARRAY_BUFFER, this.#mesh.uvs, gl.STATIC_DRAW);\n const aUv = gl.getAttribLocation(program, 'aUv');\n gl.enableVertexAttribArray(aUv);\n gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 0, 0);\n\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxBuf);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);\n\n gl.bindVertexArray(null);\n this.#curlVao = vao;\n }\n\n // --- drawing ------------------------------------------------------------------\n\n #render(): void {\n const gl = this.#gl;\n const canvas = this.#canvas;\n if (gl === null || canvas === null || this.#contextLost || gl.isContextLost()) return;\n this.#resize();\n gl.clear(gl.COLOR_BUFFER_BIT);\n this.#fitBook();\n if (this.#flip !== null) this.#drawFlip();\n else if (this.#content !== null) this.#drawStatic();\n }\n\n /** Letterbox the 2-page book to the page aspect, centered; restricts the GL viewport\n * so pages fill their halves without stretching (the surround stays cleared). */\n #fitBook(): void {\n const gl = this.#gl!;\n const canvas = this.#canvas!;\n const cw = canvas.width;\n const ch = canvas.height;\n if (this.#pageAspect > 0) {\n const bookAspect = (this.#fill ? 1 : 2) * this.#pageAspect;\n if (bookAspect > cw / ch) {\n this.#bookW = cw;\n this.#bookH = Math.round(cw / bookAspect);\n } else {\n this.#bookH = ch;\n this.#bookW = Math.round(ch * bookAspect);\n }\n } else {\n this.#bookW = cw;\n this.#bookH = ch;\n }\n const bx = Math.round((cw - this.#bookW) / 2);\n const by = Math.round((ch - this.#bookH) / 2); // centered, so GL's bottom offset == by\n gl.viewport(bx, by, this.#bookW, this.#bookH);\n }\n\n #drawStatic(): void {\n const w = this.#bookW;\n const h = this.#bookH;\n const content = this.#content!;\n this.#shiftX = this.#fill ? 0 : shiftUnit(content) * (w / 4);\n this.#useFlat();\n if (this.#fill) {\n this.#drawQuad({ x: 0, y: 0, w, h }, content.right ?? content.left);\n } else {\n // A lone page (one side null) gets no gutter shadow — it's a standalone, centered page.\n this.#drawQuad({ x: 0, y: 0, w: w / 2, h }, content.left, content.right ? 1 : 0);\n this.#drawQuad({ x: w / 2, y: 0, w: w / 2, h }, content.right, content.left ? -1 : 0);\n }\n }\n\n #drawFlip(): void {\n const gl = this.#gl!;\n const w = this.#bookW;\n const h = this.#bookH;\n const flip = this.#flip!;\n\n // Interpolate the lone-page centering across the flip so an opening cover (or a\n // closing back page) slides between centered and spread instead of jumping.\n this.#shiftX = flip.fill\n ? 0\n : (this.#fromShiftUnit + (this.#toShiftUnit - this.#fromShiftUnit) * this.#flipT) * (w / 4);\n\n // Static pages underneath (with gutter shadow).\n this.#useFlat();\n if (flip.fill) {\n this.#drawQuad({ x: 0, y: 0, w, h }, flip.underFull);\n } else {\n this.#drawQuad({ x: 0, y: 0, w: w / 2, h }, flip.underLeft, 1);\n this.#drawQuad({ x: w / 2, y: 0, w: w / 2, h }, flip.underRight, -1);\n }\n\n if (flip.front === null && flip.back === null) return;\n\n // Deform the leaf mesh with the selected curl model on the CPU, upload it, then draw.\n const leafW = flip.fill ? w : w / 2;\n const originX = flip.fill ? (flip.dir > 0 ? 0 : w) : w / 2;\n CURLS[this.#curlType].deform(this.#mesh, leafW, h, this.#flipT, {\n y: this.#anchor.y,\n fill: flip.fill,\n });\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#posBuf);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.#mesh.positions);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#normBuf);\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.#mesh.normals);\n\n this.#bindFace(gl.TEXTURE0, flip.front);\n this.#bindFace(gl.TEXTURE1, flip.back);\n gl.activeTexture(gl.TEXTURE0);\n\n gl.useProgram(this.#curl);\n gl.bindVertexArray(this.#curlVao);\n gl.uniform2f(this.#curlU.uViewport ?? null, w, h);\n gl.uniform3f(\n this.#curlU.uView ?? null,\n this.#view.scale,\n this.#view.tx * this.#dpr + this.#shiftX,\n this.#view.ty * this.#dpr,\n );\n gl.uniform1f(this.#curlU.uOriginX ?? null, originX);\n gl.uniform1f(this.#curlU.uDir ?? null, flip.dir);\n gl.uniform1f(this.#curlU.uLeafW ?? null, leafW);\n gl.uniform1f(this.#curlU.uGloss ?? null, this.#curlType === 'simple' ? 0 : 1); // flat curl = no shine\n gl.uniform1f(this.#curlU.uAlpha ?? null, flip.fill ? this.#fillFade() : 1);\n gl.drawElements(gl.TRIANGLES, this.#idxCount, gl.UNSIGNED_SHORT, 0);\n }\n\n /** Leaf opacity for a lone page: solid until the curl-appropriate fade start, then\n * dissolving to 0 as it lands, so the turn resolves into the destination page rather\n * than onto blank space. The fade eases so opacity hangs longer then finishes cleanly. */\n #fillFade(): number {\n const start =\n this.#curlType === 'roll' || this.#curlType === 'simple'\n ? FILL_FADE_START_ROLL\n : FILL_FADE_START_CURL;\n const over = (this.#flipT - start) / (1 - start);\n if (over <= 0) return 1;\n if (over >= 1) return 0;\n // Ease-out the remaining opacity: stay readable through most of the window, then settle.\n return Math.pow(1 - over, 1.5);\n }\n\n #useFlat(): void {\n const gl = this.#gl!;\n gl.useProgram(this.#flat);\n gl.bindVertexArray(this.#quadVao);\n gl.activeTexture(gl.TEXTURE0);\n gl.uniform2f(this.#flatU.uViewport ?? null, this.#bookW, this.#bookH);\n gl.uniform3f(\n this.#flatU.uView ?? null,\n this.#view.scale,\n this.#view.tx * this.#dpr + this.#shiftX,\n this.#view.ty * this.#dpr,\n );\n }\n\n #drawQuad(rect: Rect, source: TexImageSource | null, gutterSide = 0): void {\n const gl = this.#gl!;\n if (source === null) return;\n this.#bindFace(gl.TEXTURE0, source);\n gl.uniform4f(this.#flatU.uRect ?? null, rect.x, rect.y, rect.w, rect.h);\n gl.uniform1f(this.#flatU.uGutterSide ?? null, gutterSide);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n\n #bindFace(unit: number, source: TexImageSource | null): void {\n const gl = this.#gl!;\n gl.activeTexture(unit);\n gl.bindTexture(gl.TEXTURE_2D, source !== null ? this.#textureFor(source) : this.#blankTex);\n }\n\n #textureFor(source: TexImageSource): WebGLTexture {\n const gl = this.#gl!;\n const existing = this.#texCache.get(source);\n if (existing !== undefined) {\n this.#texCache.delete(source);\n this.#texCache.set(source, existing);\n return existing.tex;\n }\n const tex = createTexture(gl);\n uploadTexture(gl, tex, source);\n const dims = source as unknown as { width: number; height: number };\n const bytes = dims.width * dims.height * 4;\n this.#texCache.set(source, { tex, bytes });\n this.#cacheBytes += bytes;\n this.#evictTextures();\n return tex;\n }\n\n #evictTextures(): void {\n const gl = this.#gl!;\n for (const [src, cached] of this.#texCache) {\n if (this.#cacheBytes <= MAX_TEXTURE_CACHE_BYTES || this.#texCache.size <= 4) break;\n this.#texCache.delete(src);\n this.#cacheBytes -= cached.bytes;\n gl.deleteTexture(cached.tex);\n }\n }\n\n #makeBlankTexture(gl: WebGL2RenderingContext): WebGLTexture {\n const tex = createTexture(gl);\n gl.bindTexture(gl.TEXTURE_2D, tex);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));\n return tex;\n }\n\n #resize(): void {\n const gl = this.#gl;\n const canvas = this.#canvas;\n const el = this.#container;\n if (gl === null || canvas === null || el === null) return;\n this.#dpr = typeof devicePixelRatio === 'number' ? devicePixelRatio : 1;\n const w = Math.max(1, Math.round(el.clientWidth * this.#dpr));\n const h = Math.max(1, Math.round(el.clientHeight * this.#dpr));\n if (canvas.width !== w || canvas.height !== h) {\n canvas.width = w;\n canvas.height = h;\n gl.viewport(0, 0, w, h);\n }\n }\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 type { PageContent } from '../renderer/types';\nimport type { Source } from './types';\n\nexport interface CompositionOptions {\n /** Image URL prepended as a lone front cover (adds a page). */\n frontCover?: string | null;\n /** Image URL appended as a lone back cover (adds a page). */\n backCover?: string | null;\n /** Replace source pages with image URLs, keyed by 0-based source index (negative = from the end). */\n pages?: Record<number, string> | null;\n}\n\n/**\n * Wrap `base` so image covers/replacements sit alongside it, or return `base`\n * unchanged when nothing is requested. Covers ADD pages (front prepended, back\n * appended); `pages` REPLACE existing source pages. `pages` indexes the base's own\n * pages, so it is unaffected by an added cover.\n */\nexport function composeSource(base: Source, options: CompositionOptions): Source {\n const hasFront = typeof options.frontCover === 'string';\n const hasBack = typeof options.backCover === 'string';\n const hasPages = options.pages != null && Object.keys(options.pages).length > 0;\n if (!hasFront && !hasBack && !hasPages) return base;\n return new CompositeSource(base, options);\n}\n\nfunction loadImage(url: string): Promise<ImageBitmap> {\n return fetch(url)\n .then((res) => {\n if (!res.ok) throw new Error(`zine: could not load image \"${url}\" (HTTP ${res.status}).`);\n return res.blob();\n })\n .then((blob) => createImageBitmap(blob));\n}\n\nclass CompositeSource implements Source {\n #base: Source;\n #front: string | null;\n #back: string | null;\n #rawPages: Record<number, string>;\n #resolved = new Map<number, string>(); // base-index → url (negatives resolved)\n #cache = new Map<string, Promise<ImageBitmap>>();\n open?: () => Promise<void>;\n onPageUpdate?: (handler: (index: number) => void) => void;\n\n constructor(base: Source, options: CompositionOptions) {\n this.#base = base;\n this.#front = typeof options.frontCover === 'string' ? options.frontCover : null;\n this.#back = typeof options.backCover === 'string' ? options.backCover : null;\n this.#rawPages = options.pages ?? {};\n\n if (typeof base.open === 'function') {\n // Async base: page count (and negative indices) known only after open().\n this.open = async (): Promise<void> => {\n await base.open!();\n this.#resolvePages();\n };\n } else {\n this.#resolvePages(); // sync base: count is known now\n }\n\n if (typeof base.onPageUpdate === 'function') {\n // Forward base page updates, shifting to book indices (past the front cover).\n this.onPageUpdate = (handler) => {\n base.onPageUpdate!((src) => handler(src + (this.#front !== null ? 1 : 0)));\n };\n }\n }\n\n get pageCount(): number {\n return this.#base.pageCount + (this.#front !== null ? 1 : 0) + (this.#back !== null ? 1 : 0);\n }\n\n get(index: number): Promise<PageContent> {\n const frontOffset = this.#front !== null ? 1 : 0;\n if (this.#front !== null && index === 0) return this.#image(this.#front);\n if (this.#back !== null && index === this.pageCount - 1) return this.#image(this.#back);\n const src = index - frontOffset;\n const override = this.#resolved.get(src);\n return override !== undefined ? this.#image(override) : this.#base.get(src);\n }\n\n prefetch(indices: number[]): void {\n const frontOffset = this.#front !== null ? 1 : 0;\n const baseIndices: number[] = [];\n for (const index of indices) {\n if (this.#front !== null && index === 0) {\n void this.#image(this.#front).catch(() => {});\n } else if (this.#back !== null && index === this.pageCount - 1) {\n void this.#image(this.#back).catch(() => {});\n } else {\n const src = index - frontOffset;\n const override = this.#resolved.get(src);\n if (override !== undefined) void this.#image(override).catch(() => {});\n else baseIndices.push(src);\n }\n }\n this.#base.prefetch(baseIndices);\n }\n\n destroy(): void {\n this.#base.destroy();\n for (const promise of this.#cache.values()) {\n void promise.then((bmp) => bmp.close()).catch(() => {});\n }\n this.#cache.clear();\n }\n\n #resolvePages(): void {\n const n = this.#base.pageCount;\n this.#resolved.clear();\n for (const [key, url] of Object.entries(this.#rawPages)) {\n const raw = Number(key);\n const src = raw < 0 ? n + raw : raw;\n if (Number.isInteger(src) && src >= 0 && src < n) this.#resolved.set(src, url);\n }\n }\n\n #image(url: string): Promise<ImageBitmap> {\n let promise = this.#cache.get(url);\n if (promise === undefined) {\n promise = loadImage(url).catch((error: unknown) => {\n this.#cache.delete(url); // let a later get() retry\n throw error;\n });\n this.#cache.set(url, promise);\n }\n return promise;\n }\n}\n","import {\n buildSpreads,\n shouldSinglePage,\n type Direction,\n type Spread,\n type SpreadMode,\n} 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 { CURL_TYPES, DEFAULT_CURL, type CurlType } from './geometry/curls/types';\nimport { selectRenderer, type RendererOption } from './renderer/select';\nimport type { FlipDirection, PageContent, Renderer, SpreadContent } from './renderer/types';\nimport type { Source } from './source/types';\nimport { composeSource } from './source/compose';\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\n/** A lone page fills the container, so it sweeps the full width where a spread leaf only covers\n * its half — and it dissolves instead of landing on a facing page. Stretch duration so the\n * peel and fade read at a comparable pace to a spread turn rather than whipping away. */\nconst LONE_PAGE_FLIP_SCALE = 1.55;\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 /** Page-curl model for the WebGL2 renderer: 'roll' | 'cone' | 'leaf' | 'flick' | 'silk' | 'simple'. Default 'cone'. */\n curl?: CurlType;\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 /** How pages group into spreads: 'double' | 'single' | 'cover' | 'book'. Default 'cover'. */\n spreadMode?: SpreadMode;\n /** Image URL prepended as a lone front cover (adds a page). */\n frontCover?: string;\n /** Image URL appended as a lone back cover (adds a page). */\n backCover?: string;\n /** Replace source pages with image URLs, keyed by 0-based source index (negative = from the end). */\n pages?: Record<number, string>;\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 #spreadMode: SpreadMode;\n #curl: CurlType;\n #anchorY = 1; // where the last tap/drag grabbed (0=top, 1=bottom); drives anchored curls\n #clickToFlip: 'edge' | 'half' | 'off';\n #clickZoneSize: number;\n #honorDoubleClickInFlipZone: boolean;\n #clickFlipDelayValue: number;\n #singlePageThreshold: number;\n #singlePage = false;\n #narrow = false; // responsive: container currently below singlePageThreshold\n #lastAspect = ''; // last container aspect-ratio written (avoids redundant style writes)\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 // Latest flip intent requested while one was already animating; replayed on settle so\n // clicks/keys during a turn aren't dropped and continuous flipping keeps advancing.\n #queuedFlip: (() => void) | null = null;\n // The fold animation currently on screen, so a new flip request can land it instantly\n // (snap to its end pose + commit) and start immediately instead of waiting it out.\n #activeAnim: { toT: number; direction: FlipDirection; onDone: () => void } | 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 = composeSource(options.source, {\n frontCover: options.frontCover,\n backCover: options.backCover,\n pages: options.pages,\n });\n const direction = options.direction ?? 'ltr';\n this.#direction = direction;\n this.#spreadMode = options.spreadMode ?? 'cover';\n this.#curl = options.curl ?? DEFAULT_CURL;\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.#requestFlip(() => this.#startFlip(this.#current + 1));\n }\n\n flipPrev(): void {\n this.#requestFlip(() => 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.#requestFlip(() => this.#startFlip(this.#spreadIndexForPage(target)));\n }\n\n /** Run a flip now. If a fold is animating, land it instantly first so the new turn starts\n * immediately on this click (thunks read `#current` lazily, so it steps on from where the\n * interrupted turn landed). A drag in flight can't be interrupted this way, so its request\n * is queued and replayed on settle instead. */\n #requestFlip(run: () => void): void {\n if (this.#machine.state === 'idle') {\n run();\n } else if (this.#activeAnim) {\n this.#finishActiveAnim(); // lands the current turn → machine back to idle\n run();\n } else {\n this.#queuedFlip = run; // dragging: no animation to cut short, so wait for release\n }\n }\n\n /** Snap the in-flight fold to its end pose and commit it now, cancelling its RAF loop. */\n #finishActiveAnim(): void {\n const anim = this.#activeAnim;\n if (!anim) return;\n this.#activeAnim = null;\n if (this.#raf !== null) {\n cancelAnimationFrame(this.#raf);\n this.#raf = null;\n }\n this.#renderer?.setFlipProgress(anim.toT, anim.direction);\n anim.onDone();\n }\n\n /** After an animation settles, fire whatever flip was requested mid-turn. */\n #drainQueuedFlip(): void {\n const queued = this.#queuedFlip;\n if (!queued) return;\n this.#queuedFlip = null;\n queued();\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.#activeAnim = null;\n this.#queuedFlip = null;\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 curl: this.#curl,\n anchor: { y: this.#anchorY },\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 // Remembered so a mid-turn flip request can #finishActiveAnim() to land it at once.\n // Object identity also tags this run: if #finishActiveAnim (or a new flip) replaces it,\n // a stale frame that still fires bails instead of re-committing or rescheduling itself.\n const anim = { toT, direction, onDone };\n this.#activeAnim = anim;\n const start = performance.now();\n const step = (now: number): void => {\n if (this.#activeAnim !== anim) return; // superseded/interrupted → this frame is void\n // Easing lives here; flipProgressToPose stays linear so seeks are deterministic.\n // Lone pages ease out harder — no facing half to land on, only a dissolve.\n const raw = Math.min(1, (now - start) / duration);\n const eased = this.#singlePage ? easeInOutLone(raw) : easeInOutQuad(raw);\n this.#renderer?.setFlipProgress(fromT + (toT - fromT) * eased, direction);\n if (raw < 1) {\n this.#raf = requestAnimationFrame(step);\n } else {\n this.#raf = null;\n this.#activeAnim = 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 this.#drainQueuedFlip();\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 b = this.#contentRect();\n // Points are relative to the letterboxed book, so zones/corners track the page, not the bars.\n const point = { x: clientX - rect.left - b.x, y: clientY - rect.top - b.y };\n this.#press = point; // remembered for tap classification / click-to-flip zone\n const cornerSize = Math.min(b.width, b.height) * CORNER_FRACTION;\n if (hitTest(point, { width: b.width, height: b.height }, cornerSize) !== 'corner') {\n return; // not a corner — no drag; a tap here may still click-to-flip on release\n }\n this.#anchorY = clamp(point.y / b.height, 0, 1); // fold anchors at the grabbed corner\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 > b.width / 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: b.width,\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 curl: this.#curl,\n anchor: { y: this.#anchorY },\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 step = direction === 'forward' ? 1 : -1;\n if (this.#current + step < 0 || this.#current + step >= this.#spreads.length) return;\n // Anchor the fold at the tapped height (cone/leaf/flick curl from where you tap).\n this.#anchorY = clamp(this.#press.y / this.#contentRect().height, 0, 1);\n // Read `#current` lazily so a tap queued mid-turn steps on from wherever the page lands.\n const flip = (): void => this.#startFlip(this.#current + step);\n this.#clearPendingClickFlip();\n if (this.#clickFlipDelayValue <= 0) {\n // No double-click competing here → flip right away (or queue if mid-turn).\n this.#requestFlip(flip);\n return;\n }\n // Wait out the window; a double-click (zoom) cancels this.\n this.#pendingClickTimer = setTimeout(() => {\n this.#pendingClickTimer = null;\n this.#requestFlip(flip);\n }, this.#clickFlipDelayValue);\n }\n\n /** Which way a tap at `point` (container-local) turns the page, or null for a dead zone. */\n /** Where the current spread is actually drawn (lone pages centered), as the renderer reports\n * it — the region taps/drags map into. Falls back to book, then the full container. */\n #contentRect(): { x: number; y: number; width: number; height: number } {\n const m = this.#renderer!.measure();\n return m.content ?? m.book ?? { x: 0, y: 0, width: m.containerWidth, height: m.containerHeight };\n }\n\n #clickFlipDirection(point: { x: number; y: number }): FlipDirection | null {\n if (!this.#renderer) return null;\n const containerWidth = this.#contentRect().width;\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 this.#drainQueuedFlip();\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 // Re-render the current spread when a source upgrades one of its pages in place.\n this.#source.onPageUpdate?.((index) => this.#onPageUpdate(index));\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 if (this.#reducedMotion) return 0;\n return this.#flipDuration * (this.#singlePage ? LONE_PAGE_FLIP_SCALE : 1);\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 /** The layout to actually use: the responsive narrow fallback forces 'single'. */\n #effectiveMode(): SpreadMode {\n return this.#narrow || this.#spreadMode === 'single' ? 'single' : this.#spreadMode;\n }\n\n #applySinglePage(containerWidth: number): void {\n const narrow = shouldSinglePage(containerWidth, this.#singlePageThreshold);\n if (narrow === this.#narrow) return;\n this.#narrow = narrow;\n const mode = this.#effectiveMode();\n this.#singlePage = mode === 'single';\n this.#spreads = buildSpreads(this.#source.pageCount, { direction: this.#direction, mode });\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 this.#applyContainerAspect();\n }\n\n /** Match the container's aspect-ratio to the book so it fits with no letterbox bars.\n * Non-destructive: `aspect-ratio` only drives whichever dimension the consumer leaves\n * auto (fit-width when they set a width), and is ignored if both are fixed. */\n #applyContainerAspect(): void {\n const b = this.#renderer?.measure().book;\n if (!b || b.width <= 0 || b.height <= 0) return;\n const ratio = (b.width / b.height).toFixed(4);\n if (ratio === this.#lastAspect) return;\n this.#lastAspect = ratio;\n this.#container.style.aspectRatio = ratio;\n }\n\n /** A source upgraded a page's content (e.g. progressive PDF); repaint if it's on screen and idle. */\n #onPageUpdate(index: number): void {\n if (this.#machine.state !== 'idle') return; // don't disturb an in-flight flip\n const spread = this.#spreads[this.#current];\n if (spread && (spread.left === index || spread.right === index)) {\n void this.#renderCurrent();\n }\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 const mode = this.#effectiveMode();\n this.#singlePage = mode === 'single';\n this.#spreads = buildSpreads(pageCount, { direction: this.#direction, mode });\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\n/** Quadratic ease-in-out. Deliberately gentler than a cubic: a cubic leaves the page nearly\n * motionless for the first quarter of the turn and then spikes through the middle at roughly\n * double the peak speed, which reads as a lurch rather than a sheet of paper being turned. */\nfunction easeInOutQuad(t: number): number {\n return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;\n}\n\n/** Lone-page turn: same gentle start as the spread ease, then a longer decelerating finish.\n * Without a facing half the landing is a dissolve — front-loading a bit of progress and\n * stretching the tail keeps that fade from reading as a whip. */\nfunction easeInOutLone(t: number): number {\n const x = easeInOutQuad(t);\n return 1 - Math.pow(1 - x, 1.3);\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 const curl = o.curl;\n if (curl !== undefined && !CURL_TYPES.includes(curl as CurlType)) {\n throw new Error(`Zine: curl must be one of ${CURL_TYPES.join(', ')}; got ${JSON.stringify(curl)}.`);\n }\n\n const spreadMode = o.spreadMode;\n if (spreadMode !== undefined && !['double', 'single', 'cover', 'book'].includes(spreadMode as string)) {\n throw new Error(\n `Zine: spreadMode must be 'double', 'single', 'cover', or 'book'; got ${JSON.stringify(spreadMode)}.`,\n );\n }\n\n for (const key of ['frontCover', 'backCover'] as const) {\n const value = o[key];\n if (value !== undefined && typeof value !== 'string') {\n throw new Error(`Zine: ${key} must be an image URL string; got ${JSON.stringify(value)}.`);\n }\n }\n const pages = o.pages;\n if (pages !== undefined && (typeof pages !== 'object' || pages === null || Array.isArray(pages))) {\n throw new Error('Zine: pages must be an object mapping page indices to image URLs.');\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","export { Zine } from './zine';\nexport type { ZineOptions, ZoomOptions } from './zine';\n\nexport { ImageSource } from './source/imageSource';\nexport type { ImageSourceOptions, ImageFit } from './source/imageSource';\nexport type { Source } from './source/types';\n\nexport type { RendererOption, RendererKind } from './renderer/select';\nexport type { Renderer, SpreadContent, PageContent, FlipDirection, LayoutMetrics, RenderOptions } from './renderer/types';\n\nexport type { ZineEventMap } from './engine/emitter';\nexport type { Direction, Spread } from './engine/spread';\nexport { CURL_TYPES } from './geometry/curls/types';\nexport type { CurlType, CurlAnchor } from './geometry/curls/types';\n"],"mappings":"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"}
|