@zinejs/core 0.4.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +11 -0
  2. package/dist/bundled-Cp43MIt0.js +100 -0
  3. package/dist/bundled-Cp43MIt0.js.map +1 -0
  4. package/dist/controls/arrows.d.ts +17 -0
  5. package/dist/controls/builtins.d.ts +8 -0
  6. package/dist/controls/controls.d.ts +14 -0
  7. package/dist/controls/icons.d.ts +51 -0
  8. package/dist/controls/outline.d.ts +14 -0
  9. package/dist/controls/qr.d.ts +16 -0
  10. package/dist/controls/registry.d.ts +21 -0
  11. package/dist/controls/search.d.ts +13 -0
  12. package/dist/controls/share.d.ts +13 -0
  13. package/dist/controls/sidebar.d.ts +24 -0
  14. package/dist/controls/socials.d.ts +16 -0
  15. package/dist/controls/styles.d.ts +3 -0
  16. package/dist/controls/thumbnails.d.ts +17 -0
  17. package/dist/controls/toolbar.d.ts +45 -0
  18. package/dist/controls/types.d.ts +81 -0
  19. package/dist/controls-BrPo8pKL.js +649 -0
  20. package/dist/controls-BrPo8pKL.js.map +1 -0
  21. package/dist/curls.js +133 -0
  22. package/dist/curls.js.map +1 -0
  23. package/dist/engine/deeplink.d.ts +18 -0
  24. package/dist/engine/emitter.d.ts +6 -0
  25. package/dist/engine/zoomTile.d.ts +65 -0
  26. package/dist/geometry/curls/bundled.d.ts +16 -0
  27. package/dist/geometry/curls/index.d.ts +8 -4
  28. package/dist/geometry/curls/types.d.ts +23 -1
  29. package/dist/icons-D87p7JMP.js +29 -0
  30. package/dist/icons-D87p7JMP.js.map +1 -0
  31. package/dist/index.d.ts +7 -4
  32. package/dist/index.js +788 -363
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.umd.js +447 -5
  35. package/dist/index.umd.js.map +1 -1
  36. package/dist/registry-5nuYFMd4.js +36 -0
  37. package/dist/registry-5nuYFMd4.js.map +1 -0
  38. package/dist/renderer/types.d.ts +2 -2
  39. package/dist/share-CyrZiFqf.js +283 -0
  40. package/dist/share-CyrZiFqf.js.map +1 -0
  41. package/dist/source/types.d.ts +61 -2
  42. package/dist/types-DEz3mRLI.js +11 -0
  43. package/dist/types-DEz3mRLI.js.map +1 -0
  44. package/dist/{webglRenderer-BK-ArG1G.js → webglRenderer-BcNhTqG8.js} +33 -248
  45. package/dist/webglRenderer-BcNhTqG8.js.map +1 -0
  46. package/dist/zine.d.ts +176 -6
  47. package/options.schema.json +55 -2
  48. package/package.json +24 -2
  49. package/dist/types-CcPVyhOJ.js +0 -13
  50. package/dist/types-CcPVyhOJ.js.map +0 -1
  51. package/dist/webglRenderer-BK-ArG1G.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controls-BrPo8pKL.js","names":["#holder","#place","#wrap","#zine","#doc","#bar","#build","#markCurrent","#unsubscribe","#rows","#observeRows","#paint","#observer","#zine","#doc","#bar","#note","#build","#unsubscribe","#markCurrent","#addLevel","#rows","#zine","#doc","#bar","#input","#timer","#search","#hits","#run","#note","#zine","#prev","#button","#next","#place","#refresh","#unsubscribe","#wrap","#zine","#container","#doc","#root","#bar","#onKeyDown","#isolate","#place","#refresh","#unsubscribe","#popover","#closePopover","#wrap","#build","#arrows","#probeOutline","#panel","#panelKind","#context","#buttons","#button","#titleOf","#iconOf","#activate","#openMenu","#showPopover","#placePopover","#hasVisibleChildren","#formFields","#pageInput","#hasOutline"],"sources":["../src/controls/builtins.ts","../src/controls/styles.ts","../src/controls/sidebar.ts","../src/controls/thumbnails.ts","../src/controls/outline.ts","../src/controls/search.ts","../src/controls/arrows.ts","../src/controls/toolbar.ts","../src/controls/controls.ts"],"sourcesContent":["import { ICONS } from './icons';\nimport { defineControl } from './registry';\nimport type { ControlContext } from './types';\n\n\n/** Zoom step per click: two clicks double the scale. */\nconst ZOOM_STEP = Math.SQRT2;\n\nconst isFullscreen = (ctx: ControlContext): boolean => {\n const doc = ctx.zine.container.ownerDocument;\n return doc?.fullscreenElement === ctx.zine.container;\n};\n\n/**\n * Register the stock controls. Called once as the toolbar chunk loads, so the icon set and these\n * definitions stay out of the base bundle.\n *\n * `pageInput` and `search` are not here: both need the toolbar instance itself (one builds an\n * input, the other opens an anchored panel), so the toolbar registers them.\n */\nexport function registerBuiltins(): void {\n defineControl({\n id: 'prev',\n title: 'Previous page',\n icon: ICONS.prev,\n isDisabled: (ctx) => !ctx.zine.canFlipPrev(),\n action: (ctx) => ctx.zine.flipPrev(),\n });\n\n defineControl({\n id: 'next',\n title: 'Next page',\n icon: ICONS.next,\n isDisabled: (ctx) => !ctx.zine.canFlipNext(),\n action: (ctx) => ctx.zine.flipNext(),\n });\n\n // The chevrons point the way the book travels, which reverses in RTL — same as the page\n // arrows flanking the book.\n const rtl = (ctx: ControlContext): boolean => ctx.zine.getDirection() === 'rtl';\n\n defineControl({\n id: 'first',\n title: 'First page',\n icon: (ctx) => (rtl(ctx) ? ICONS.last : ICONS.first),\n isDisabled: (ctx) => !ctx.zine.canFlipPrev(),\n action: (ctx) => {\n ctx.zine.flipTo(0);\n ctx.close();\n },\n });\n\n defineControl({\n id: 'last',\n title: 'Last page',\n icon: (ctx) => (rtl(ctx) ? ICONS.first : ICONS.last),\n isDisabled: (ctx) => !ctx.zine.canFlipNext(),\n action: (ctx) => {\n ctx.zine.flipTo(ctx.zine.getPageCount() - 1);\n ctx.close();\n },\n });\n\n defineControl({\n id: 'zoomIn',\n title: 'Zoom in',\n icon: ICONS.zoomIn,\n isDisabled: (ctx) => ctx.zine.getZoom() >= ctx.zine.getMaxZoom() - 1e-6,\n action: (ctx) => ctx.zine.setZoom(ctx.zine.getZoom() * ZOOM_STEP),\n });\n\n defineControl({\n id: 'zoomOut',\n title: 'Zoom out',\n icon: ICONS.zoomOut,\n isDisabled: (ctx) => ctx.zine.getZoom() <= 1 + 1e-6,\n action: (ctx) => ctx.zine.setZoom(ctx.zine.getZoom() / ZOOM_STEP),\n });\n\n defineControl({\n id: 'fullscreen',\n title: 'Fullscreen',\n icon: ICONS.fullscreen,\n isVisible: (ctx) => typeof ctx.zine.container.requestFullscreen === 'function',\n isActive: isFullscreen,\n action: (ctx) => {\n const doc = ctx.zine.container.ownerDocument;\n if (isFullscreen(ctx)) void doc?.exitFullscreen?.();\n else void ctx.zine.container.requestFullscreen?.();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'share',\n title: 'Share',\n icon: ICONS.share,\n action: (ctx) => {\n ctx.close();\n // The dialog, its brand marks and the QR encoder are a chunk of their own: a book nobody\n // shares never downloads any of it.\n void import('./share')\n .then(({ ShareDialog }) => new ShareDialog(ctx.zine, ctx.zine.container))\n .catch(() => {\n // No dialog, so fall back to the plain behaviour rather than doing nothing.\n void navigator.clipboard?.writeText(ctx.zine.pageLink()).catch(() => {});\n });\n },\n });\n\n defineControl({\n id: 'download',\n title: 'Download PDF',\n icon: ICONS.download,\n // Hidden for image books and for PDFs opened from a caller-owned pdf.js document, which\n // have no file of their own to save.\n isVisible: (ctx) => ctx.zine.canDownload(),\n action: (ctx) => {\n void ctx.zine.download();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'spread',\n // Says what pressing it will do, not what the book is doing now, and in the reader's terms —\n // \"spread mode\" is the library's word for it, not theirs.\n title: (ctx) => (ctx.zine.isSinglePage() ? 'Show two pages' : 'Show one page'),\n icon: (ctx) => (ctx.zine.isSinglePage() ? ICONS.twoPages : ICONS.onePage),\n // Hidden while a narrow container is forcing one page: pressing it could not honour two.\n isVisible: (ctx) => !ctx.zine.isResponsiveSingle(),\n action: (ctx) => {\n ctx.zine.toggleSpreadMode();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'print',\n title: 'Print',\n icon: ICONS.print,\n // Same reach as download: there has to be an original document to print.\n isVisible: (ctx) => ctx.zine.canPrint(),\n action: (ctx) => {\n void ctx.zine.print();\n ctx.close();\n },\n });\n\n defineControl({\n id: 'menu',\n title: 'More',\n icon: ICONS.menu,\n // Share and fullscreen sit on the bar itself by default, so the overflow holds what is left.\n children: ['first', 'last', 'spread', 'thumbnails', 'outline', 'print', 'download'],\n });\n}\n","/**\n * The toolbar's stylesheet, injected once per document.\n *\n * The rest of the library styles elements inline, but a toolbar needs `:hover`, `:focus-visible`\n * and `:disabled` — states inline styles cannot express — so this is the one place a `<style>`\n * element is warranted. Everything is scoped under `.zine-controls`, and the colours come from\n * custom properties so a consumer can retheme without overriding rules.\n */\nconst STYLE_ID = 'zine-controls-style';\n\nexport const CSS = `\n/* Docked mode wraps the book so the bar can sit beside it without shrinking the container,\n whose measured box the renderer and hit-testing both depend on.\n The wrapper deliberately does not stretch or grow its children: the container keeps whatever\n width, aspect-ratio and auto-margins the consumer's own CSS gave it. */\n.zine-controls-wrap { display: flex; }\n/* Stacked: children keep their own width (so a percentage or auto-margin still resolves against\n the wrapper) and take only the height they ask for. */\n.zine-controls-wrap-top,\n.zine-controls-wrap-bottom { flex-direction: column; align-items: stretch; }\n/* Side by side: the bar is only as wide as its buttons, and both are vertically centred. */\n.zine-controls-wrap-left,\n.zine-controls-wrap-right { flex-direction: row; align-items: center; }\n/* min-height:0 matters as much as min-width here. A flex item's automatic minimum size floors it\n at its content, so once the canvas has grown for a taller layout the book cannot shrink back —\n its aspect-ratio is restored but ignored, leaving the extra height behind. */\n.zine-controls-wrap > * { flex: 0 0 auto; min-width: 0; min-height: 0; }\n\n.zine-controls {\n position: relative;\n display: flex;\n gap: 4px;\n padding: 5px;\n z-index: 2;\n box-sizing: border-box;\n font: 500 12px/1.2 system-ui, sans-serif;\n color: var(--zine-controls-fg, #f4f4f5);\n touch-action: auto;\n -webkit-user-select: none;\n user-select: none;\n}\n.zine-controls-docked { flex: 0 0 auto; justify-content: center; align-items: center; }\n.zine-controls-floating {\n position: absolute;\n /* Transparent to the book except on the buttons themselves, so gaps stay draggable. */\n pointer-events: none;\n}\n.zine-controls-bar {\n display: flex;\n align-items: center;\n gap: 1px;\n padding: 3px;\n border-radius: 8px;\n pointer-events: auto;\n background: var(--zine-controls-bg, rgba(24, 24, 27, 0.82));\n backdrop-filter: blur(8px);\n}\n/* Floating: lifted off the page it covers. Docked: flat, with an outline so it reads as a\n control strip rather than a shadow hanging in empty space. */\n.zine-controls-floating .zine-controls-bar { box-shadow: 0 2px 12px rgba(0, 0, 0, 0.28); }\n.zine-controls-docked .zine-controls-bar {\n border: 1px solid var(--zine-controls-hover, rgba(255, 255, 255, 0.14));\n}\n.zine-controls-floating.zine-controls-bottom { inset: auto 0 0 0; justify-content: center; }\n.zine-controls-floating.zine-controls-top { inset: 0 0 auto 0; justify-content: center; }\n.zine-controls-floating.zine-controls-left { inset: 0 auto 0 0; align-items: center; }\n.zine-controls-floating.zine-controls-right { inset: 0 0 0 auto; align-items: center; }\n/* A bar on a vertical edge stacks its buttons, docked or floating. */\n.zine-controls-left .zine-controls-bar,\n.zine-controls-right .zine-controls-bar { flex-direction: column; }\n\n.zine-controls-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n font: inherit;\n}\n.zine-controls-btn svg { width: 15px; height: 15px; }\n.zine-controls-btn:hover:not(:disabled) { background: var(--zine-controls-hover, rgba(255,255,255,0.14)); }\n.zine-controls-btn:focus-visible {\n outline: 2px solid var(--zine-controls-accent, #7dd3fc);\n outline-offset: 1px;\n}\n.zine-controls-btn:disabled { opacity: 0.38; cursor: default; }\n/* A custom widget cannot always be disabled itself, so it is marked instead and reads the same. */\n.zine-controls-off { opacity: 0.38; }\n.zine-controls-off[aria-disabled='true'] { pointer-events: none; }\n.zine-controls-btn[aria-pressed=\"true\"],\n.zine-controls-btn[aria-expanded=\"true\"] { background: var(--zine-controls-hover, rgba(255,255,255,0.14)); }\n\n.zine-controls-sep {\n width: 1px;\n align-self: stretch;\n margin: 3px 3px;\n background: currentColor;\n opacity: 0.22;\n}\n.zine-controls-left .zine-controls-sep,\n.zine-controls-right .zine-controls-sep { width: auto; height: 1px; margin: 3px 3px; }\n\n.zine-controls-page {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 0 3px;\n pointer-events: auto;\n white-space: nowrap;\n}\n.zine-controls-page input {\n width: 2.2em;\n padding: 2px 3px;\n border: 1px solid var(--zine-controls-hover, rgba(255,255,255,0.2));\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.25);\n color: inherit;\n font: inherit;\n text-align: center;\n -moz-appearance: textfield;\n}\n.zine-controls-page input::-webkit-outer-spin-button,\n.zine-controls-page input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }\n.zine-controls-page input:focus-visible {\n outline: 2px solid var(--zine-controls-accent, #7dd3fc);\n outline-offset: 0;\n}\n\n.zine-controls-menu {\n position: absolute;\n display: flex;\n flex-direction: column;\n gap: 1px;\n min-width: 152px;\n padding: 4px;\n border-radius: 8px;\n pointer-events: auto;\n background: var(--zine-controls-bg, rgba(24, 24, 27, 0.94));\n box-shadow: 0 6px 22px rgba(0, 0, 0, 0.36);\n backdrop-filter: blur(8px);\n}\n.zine-controls-menu .zine-controls-btn {\n width: 100%;\n height: auto;\n gap: 8px;\n padding: 6px 8px;\n justify-content: flex-start;\n}\n\n/* Search shares the rail: a query field pinned at the top over a scrolling list of hits. */\n.zine-search { gap: 0; overflow: hidden; }\n.zine-search-input {\n flex: 0 0 auto;\n width: 100%;\n padding: 6px 8px;\n border: 1px solid var(--zine-controls-hover, rgba(255,255,255,0.2));\n border-radius: 6px;\n background: rgba(0, 0, 0, 0.25);\n color: inherit;\n font: inherit;\n box-sizing: border-box;\n}\n.zine-search-hits {\n flex: 1 1 auto;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: 5px;\n scrollbar-width: thin;\n}\n.zine-search-hit {\n display: block;\n width: 100%;\n padding: 6px 8px;\n border: 0;\n border-radius: 5px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n box-sizing: border-box;\n}\n.zine-search-hit:hover { background: var(--zine-controls-hover, rgba(255,255,255,0.14)); }\n.zine-search-hit:focus-visible {\n outline: 2px solid var(--zine-controls-accent, #7dd3fc);\n outline-offset: -2px;\n}\n.zine-search-page { display: block; font-size: 0.9em; opacity: 0.85; }\n.zine-search-hit small {\n display: block;\n margin-top: 2px;\n opacity: 0.62;\n line-height: 1.35;\n overflow-wrap: anywhere;\n}\n\n/* Page-turn arrows flanking the book. Like the docked toolbar and the side panels, they sit\n outside the container so they never shrink the book or intercept its gestures. */\n.zine-arrows-wrap {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n}\n.zine-arrows-wrap > *:not(.zine-arrow) { flex: 1 1 auto; min-width: 0; min-height: 0; }\n.zine-arrow {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n padding: 0;\n border: 0;\n border-radius: 50%;\n background: var(--zine-controls-bg, rgba(24, 24, 27, 0.82));\n color: var(--zine-controls-fg, #f4f4f5);\n cursor: pointer;\n backdrop-filter: blur(8px);\n -webkit-user-select: none;\n user-select: none;\n}\n.zine-arrow svg { width: 22px; height: 22px; }\n.zine-arrow:hover:not(:disabled) { background: var(--zine-controls-hover, rgba(255,255,255,0.22)); }\n.zine-arrow:focus-visible {\n outline: 2px solid var(--zine-controls-accent, #7dd3fc);\n outline-offset: 2px;\n}\n/* Nothing to turn to: invisible, but still occupying its slot so the book does not slide across\n as the reader reaches a cover. */\n.zine-arrow-hidden { visibility: hidden; }\n/* No room to flank on a narrow screen; the toolbar's own prev/next still work. */\n@media (max-width: 640px) {\n .zine-arrow { display: none; }\n}\n@media (prefers-reduced-motion: no-preference) {\n .zine-arrow { transition: background 120ms ease; }\n}\n\n/* Side panels (thumbnails, outline). The rail wraps the book and its docked toolbar so it sits\n beside them without shrinking the container the renderer measures. */\n.zine-panel-wrap { display: flex; align-items: stretch; gap: 8px; }\n\n/* The holder stretches to the book's height; the rail is absolutely positioned inside it so a\n long list scrolls rather than growing the row and running past the bottom of the book. */\n.zine-panel-holder {\n position: relative;\n flex: 0 0 auto;\n align-self: stretch;\n min-height: 0;\n}\n.zine-panel {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: 6px;\n box-sizing: border-box;\n border-radius: 8px;\n background: var(--zine-controls-bg, rgba(24, 24, 27, 0.82));\n color: var(--zine-controls-fg, #f4f4f5);\n font: 500 11px/1.2 system-ui, sans-serif;\n scrollbar-width: thin;\n touch-action: auto;\n -webkit-user-select: none;\n user-select: none;\n}\n/* Overlay fallback, used when the container has no parent to wrap. */\n.zine-panel-overlay { position: absolute; inset: 0 auto 0 0; z-index: 3; }\n.zine-panel-active { background: var(--zine-controls-hover, rgba(255,255,255,0.18)); }\n.zine-panel-note { padding: 8px; opacity: 0.66; line-height: 1.4; }\n\n.zine-thumbs { align-items: center; }\n\n.zine-thumbs-row {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n gap: 2px;\n width: 100%;\n padding: 4px;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font: inherit;\n cursor: pointer;\n box-sizing: border-box;\n}\n.zine-thumbs-row:hover { background: var(--zine-controls-hover, rgba(255,255,255,0.14)); }\n.zine-thumbs-row:focus-visible {\n outline: 2px solid var(--zine-controls-accent, #7dd3fc);\n outline-offset: -2px;\n}\n.zine-thumbs-row.zine-panel-active .zine-thumbs-cell {\n outline: 1px solid var(--zine-controls-accent, #7dd3fc);\n}\n\n/* Outline: a single column of headings, indented by depth. */\n.zine-outline { gap: 1px; }\n.zine-outline-row {\n display: block;\n width: 100%;\n padding: 6px 8px;\n border: 0;\n border-radius: 5px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n line-height: 1.35;\n cursor: pointer;\n box-sizing: border-box;\n overflow-wrap: anywhere;\n}\n.zine-outline-row:hover:not(:disabled) {\n background: var(--zine-controls-hover, rgba(255,255,255,0.14));\n}\n.zine-outline-row:focus-visible {\n outline: 2px solid var(--zine-controls-accent, #7dd3fc);\n outline-offset: -2px;\n}\n/* A heading whose destination could not be resolved: shown, but nothing to click through to. */\n.zine-outline-row:disabled { opacity: 0.5; cursor: default; }\n\n.zine-thumbs-cell {\n flex: 1 1 0;\n min-width: 0;\n min-height: 24px;\n display: flex;\n background: rgba(0, 0, 0, 0.3);\n border-radius: 2px;\n overflow: hidden;\n}\n/* A lone page in a book that pairs elsewhere (a cover, a back page) keeps one page's width and\n centres, instead of stretching across both columns. */\n.zine-thumbs-row-lone .zine-thumbs-cell { flex: 0 0 calc(50% - 1px); }\n.zine-thumbs-img { display: block; width: 100%; height: auto; }\n.zine-thumbs-caption { flex: 1 0 100%; text-align: center; opacity: 0.7; }\n\n/* Share dialog. Centred over the page rather than in the side rail: a QR and six destinations\n need more room than the rail gives, and sharing is a brief interruption, not a browsing mode. */\n.zine-share-backdrop {\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n background: rgba(0, 0, 0, 0.55);\n font: 500 13px/1.4 system-ui, sans-serif;\n color: var(--zine-controls-fg, #f4f4f5);\n}\n.zine-share {\n width: min(320px, 100%);\n max-height: 100%;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-radius: 12px;\n background: var(--zine-share-bg, #1c1c20);\n box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);\n box-sizing: border-box;\n}\n.zine-share-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n.zine-share-head strong { font-size: 15px; }\n.zine-share-close {\n display: inline-flex;\n width: 26px;\n height: 26px;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n.zine-share-close svg { width: 16px; height: 16px; }\n.zine-share-close:hover { background: var(--zine-controls-hover, rgba(255,255,255,0.14)); }\n\n.zine-share-qr {\n align-self: center;\n line-height: 0;\n padding: 8px;\n border-radius: 8px;\n background: #fff;\n}\n.zine-share-qr svg { display: block; }\n\n.zine-share-link { display: flex; gap: 6px; }\n.zine-share-link input {\n flex: 1 1 auto;\n min-width: 0;\n padding: 7px 9px;\n border: 1px solid var(--zine-controls-hover, rgba(255,255,255,0.2));\n border-radius: 7px;\n background: rgba(0, 0, 0, 0.3);\n color: inherit;\n font: inherit;\n}\n.zine-share-copy {\n flex: 0 0 auto;\n min-width: 74px;\n padding: 7px 12px;\n border: 0;\n border-radius: 7px;\n background: var(--zine-controls-accent, #7dd3fc);\n color: #06202c;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n}\n\n.zine-share-socials { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }\n.zine-share-social {\n display: inline-flex;\n width: 38px;\n height: 38px;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: inherit;\n text-decoration: none;\n}\n.zine-share-social svg { width: 19px; height: 19px; }\n.zine-share-social:hover { background: var(--zine-controls-hover, rgba(255,255,255,0.18)); }\n.zine-share-close:focus-visible,\n.zine-share-copy:focus-visible,\n.zine-share-social:focus-visible,\n.zine-share-link input:focus-visible {\n outline: 2px solid var(--zine-controls-accent, #7dd3fc);\n outline-offset: 2px;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .zine-controls-btn { transition: background 120ms ease; }\n}\n`;\n\n/** Add the stylesheet to `doc` if it is not already there. */\nexport function ensureStyles(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const style = doc.createElement('style');\n style.id = STYLE_ID;\n style.textContent = CSS;\n (doc.head ?? doc.documentElement)?.appendChild(style);\n}\n","/**\n * The rail beside the book that the thumbnail and outline lists both sit in.\n *\n * It exists to solve one problem shared by every such panel: the container's measured box is what\n * the renderer sizes the book from and what pointer hit-testing maps into, so a panel must not\n * live inside it. Instead the book (and its docked toolbar, if any) is wrapped in a flex row with\n * the panel as its sibling, and `destroy` unwinds that again.\n *\n * The rail is absolutely positioned inside a holder that stretches to the book's height, so a\n * long list scrolls internally instead of running past the bottom of the book.\n */\nexport class Sidebar {\n /** The scrolling list; panels append their rows here. */\n readonly root: HTMLElement;\n #holder: HTMLElement;\n #wrap: HTMLElement | null = null;\n\n constructor(\n doc: Document,\n container: HTMLElement,\n options: { className: string; label: string; width: number },\n ) {\n this.#holder = doc.createElement('div');\n this.#holder.className = 'zine-panel-holder';\n this.#holder.style.width = `${options.width}px`;\n\n this.root = doc.createElement('div');\n this.root.className = `zine-panel ${options.className}`;\n this.root.setAttribute('role', 'listbox');\n this.root.setAttribute('aria-label', options.label);\n this.#holder.appendChild(this.root);\n\n // In the overlay fallback the rail sits inside the book, where the book's own gesture\n // handlers would otherwise read a click on a row as a page tap.\n for (const type of ['pointerdown', 'pointerup', 'pointermove', 'click', 'dblclick', 'wheel']) {\n this.root.addEventListener(type, (e) => e.stopPropagation());\n }\n\n this.#place(doc, container);\n }\n\n /** Resize the rail, e.g. once a thumbnail list knows whether it needs one column or two. */\n setWidth(px: number): void {\n this.#holder.style.width = `${px}px`;\n }\n\n #place(doc: Document, container: HTMLElement): void {\n const parent = container.parentNode;\n if (!parent) {\n // Not in a document yet: overlay rather than lose the panel.\n this.#holder.classList.add('zine-panel-overlay');\n container.appendChild(this.#holder);\n return;\n }\n // A docked toolbar already wrapped the book; wrap that whole assembly so the rail sits\n // beside book *and* toolbar rather than between them.\n const existing = container.closest('.zine-controls-wrap');\n const target = (existing ?? container) as HTMLElement;\n const wrap = doc.createElement('div');\n wrap.className = 'zine-panel-wrap';\n target.parentNode!.insertBefore(wrap, target);\n wrap.append(this.#holder, target);\n this.#wrap = wrap;\n }\n\n destroy(): void {\n this.#holder.remove();\n const wrap = this.#wrap;\n if (wrap?.parentNode) {\n // The rail is gone, so what is left is the book; put it back where the wrapper was.\n while (wrap.firstChild) wrap.parentNode.insertBefore(wrap.firstChild, wrap);\n wrap.remove();\n }\n this.#wrap = null;\n }\n}\n","import { Sidebar } from './sidebar';\nimport type { Zine } from '../zine';\n\n/** Width of one page thumbnail, in px. A paired rail fits two of these side by side. */\nconst THUMB_WIDTH = 78;\n/** Padding inside the rail, per side. */\nconst RAIL_PAD = 6;\n/** Gap between the two pages of a spread. */\nconst CELL_GAP = 2;\n\n/**\n * A rail of page thumbnails beside the book.\n *\n * Rows mirror the book's own spread model, so a `double` book shows two pages per row and a\n * `single` book one — including the responsive fallback that forces single-page on a narrow\n * container. A book that mixes the two (`cover`, `book`) keeps two columns so pairs line up, and\n * centres its lone first/last page between them. Clicking a row turns to that spread.\n *\n * Pages are drawn only as they scroll into view: a long PDF would otherwise decode every page at\n * once the moment the rail opened.\n */\nexport class Thumbnails {\n #zine: Zine;\n #doc: Document;\n #bar: Sidebar;\n #rows: { el: HTMLElement; spread: number }[] = [];\n #observer: IntersectionObserver | null = null;\n #unsubscribe: (() => void)[] = [];\n\n constructor(zine: Zine, container: HTMLElement) {\n this.#zine = zine;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n this.#bar = new Sidebar(doc, container, {\n className: 'zine-thumbs',\n label: 'Pages',\n width: THUMB_WIDTH + RAIL_PAD * 2,\n });\n\n this.#build();\n\n const onPage = (): void => this.#markCurrent();\n this.#unsubscribe.push(zine.on('pageChanged', onPage));\n this.#markCurrent();\n }\n\n #build(): void {\n const spreads = this.#zine.getSpreads();\n const rtl = this.#zine.getDirection() === 'rtl';\n // Two columns only if the book actually pairs pages somewhere. A wholly single-page book\n // gets a one-page-wide rail rather than a column of half-empty rows.\n const paired = spreads.some((s) => s.left !== null && s.right !== null);\n this.#bar.root.classList.toggle('zine-thumbs-solo', !paired);\n if (paired) this.#bar.setWidth(THUMB_WIDTH * 2 + CELL_GAP + RAIL_PAD * 2);\n\n for (const [index, spread] of spreads.entries()) {\n const row = this.#doc.createElement('button');\n row.type = 'button';\n row.className = 'zine-thumbs-row';\n row.setAttribute('role', 'option');\n if (rtl) row.style.flexDirection = 'row-reverse';\n\n const pages = [spread.left, spread.right].filter((p): p is number => p !== null);\n // A lone page in a book that pairs elsewhere keeps a single page's width and sits in the\n // middle, rather than stretching across both columns or hugging one side.\n if (paired && pages.length === 1) row.classList.add('zine-thumbs-row-lone');\n\n for (const page of pages) {\n const cell = this.#doc.createElement('span');\n cell.className = 'zine-thumbs-cell';\n cell.dataset.page = String(page);\n row.appendChild(cell);\n }\n\n const labels = [...pages].sort((a, b) => a - b).map((p) => p + 1);\n row.setAttribute(\n 'aria-label',\n labels.length > 1 ? `Pages ${labels[0]}–${labels.at(-1)}` : `Page ${labels[0] ?? ''}`,\n );\n\n const caption = this.#doc.createElement('span');\n caption.className = 'zine-thumbs-caption';\n caption.textContent = labels.join('–');\n row.appendChild(caption);\n\n row.addEventListener('click', () => {\n if (pages.length > 0) this.#zine.flipTo(Math.min(...pages));\n });\n this.#bar.root.appendChild(row);\n this.#rows.push({ el: row, spread: index });\n }\n this.#observeRows();\n }\n\n /** Decode a row's pages the first time it comes into view. */\n #observeRows(): void {\n const cells = [...this.#bar.root.querySelectorAll<HTMLElement>('.zine-thumbs-cell[data-page]')];\n if (typeof IntersectionObserver === 'undefined') {\n for (const cell of cells) void this.#paint(cell);\n return;\n }\n this.#observer = new IntersectionObserver(\n (entries, obs) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n obs.unobserve(entry.target);\n void this.#paint(entry.target as HTMLElement);\n }\n },\n { root: this.#bar.root, rootMargin: '200px' },\n );\n for (const cell of cells) this.#observer.observe(cell);\n }\n\n async #paint(cell: HTMLElement): Promise<void> {\n const page = Number(cell.dataset.page);\n const content = await this.#zine.getPageImage(page);\n if (!content || !cell.isConnected) return;\n const w = (content as { width: number }).width;\n const h = (content as { height: number }).height;\n if (!w || !h) return;\n // One page's worth of pixels, whatever the rail's column count, so thumbs stay a consistent\n // size between modes. Scaled by dpr so they are not soft on a retina screen.\n const dpr = Math.min(2, globalThis.devicePixelRatio || 1);\n const canvas = this.#doc.createElement('canvas');\n canvas.width = Math.max(1, Math.round(THUMB_WIDTH * dpr));\n canvas.height = Math.max(1, Math.round(((THUMB_WIDTH * h) / w) * dpr));\n canvas.className = 'zine-thumbs-img';\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n try {\n ctx.drawImage(content as CanvasImageSource, 0, 0, canvas.width, canvas.height);\n } catch {\n return; // an unusable raster is not worth breaking the rail over\n }\n cell.replaceChildren(canvas);\n }\n\n /** Highlight the spread on screen and keep it scrolled into view. */\n #markCurrent(): void {\n const current = this.#zine.getSpreadIndex();\n for (const { el, spread } of this.#rows) {\n const active = spread === current;\n el.classList.toggle('zine-panel-active', active);\n el.setAttribute('aria-selected', String(active));\n if (active) el.scrollIntoView?.({ block: 'nearest' });\n }\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#observer?.disconnect();\n this.#observer = null;\n this.#bar.destroy();\n }\n}\n","import { Sidebar } from './sidebar';\nimport type { OutlineItem } from '../source/types';\nimport type { Zine } from '../zine';\n\n/** Rail width in px. Wider than the thumbnail rail because it holds headings, not pictures. */\nconst RAIL_WIDTH = 208;\n/** Indent per nesting level, in px. */\nconst INDENT = 12;\n\n/**\n * The document's table of contents beside the book.\n *\n * One row per heading, nested entries indented under their parent. Clicking a row turns to the\n * page it points at; a heading whose destination could not be resolved is still listed, just not\n * clickable. Unlike the thumbnail rail this is a single column — headings are text, and a second\n * column would only make them narrower.\n */\nexport class Outline {\n #zine: Zine;\n #doc: Document;\n #bar: Sidebar;\n #rows: { el: HTMLElement; page: number }[] = [];\n #unsubscribe: (() => void)[] = [];\n\n constructor(zine: Zine, container: HTMLElement) {\n this.#zine = zine;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n this.#bar = new Sidebar(doc, container, {\n className: 'zine-outline',\n label: 'Outline',\n width: RAIL_WIDTH,\n });\n\n this.#note('Loading…');\n void this.#build();\n\n this.#unsubscribe.push(zine.on('pageChanged', () => this.#markCurrent()));\n }\n\n #note(text: string): void {\n const note = this.#doc.createElement('div');\n note.className = 'zine-panel-note';\n note.textContent = text;\n this.#bar.root.replaceChildren(note);\n }\n\n async #build(): Promise<void> {\n const items = await this.#zine.getOutline();\n if (!this.#bar.root.isConnected) return; // closed while the outline was loading\n if (items.length === 0) {\n // The toolbar hides the control when there is nothing to list, so this is only reachable\n // through a hand-written layout that names 'outline' regardless.\n this.#note('This document has no outline.');\n return;\n }\n this.#bar.root.replaceChildren();\n this.#addLevel(items, 0);\n this.#markCurrent();\n }\n\n #addLevel(items: OutlineItem[], depth: number): void {\n for (const item of items) {\n const row = this.#doc.createElement('button');\n row.type = 'button';\n row.className = 'zine-outline-row';\n row.setAttribute('role', 'option');\n row.style.paddingLeft = `${8 + depth * INDENT}px`;\n row.textContent = item.title;\n row.title = item.title;\n if (item.page === null) {\n // Keep the heading visible but inert: it still tells the reader what is in the document.\n row.disabled = true;\n } else {\n const page = item.page;\n row.setAttribute('aria-label', `${item.title}, page ${page + 1}`);\n row.addEventListener('click', () => this.#zine.flipTo(page));\n this.#rows.push({ el: row, page });\n }\n this.#bar.root.appendChild(row);\n if (item.children.length > 0) this.#addLevel(item.children, depth + 1);\n }\n }\n\n /**\n * Mark the entry the reader is inside — the last one at or before the current page, rather\n * than an exact match, since a section spans many pages and rarely starts on the one shown.\n */\n #markCurrent(): void {\n const current = this.#zine.getPage();\n let best: HTMLElement | null = null;\n let bestPage = -1;\n for (const { el, page } of this.#rows) {\n el.classList.remove('zine-panel-active');\n el.removeAttribute('aria-selected');\n if (page <= current && page >= bestPage) {\n best = el;\n bestPage = page;\n }\n }\n if (!best) return;\n best.classList.add('zine-panel-active');\n best.setAttribute('aria-selected', 'true');\n best.scrollIntoView?.({ block: 'nearest' });\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#bar.destroy();\n }\n}\n","import { Sidebar } from './sidebar';\nimport type { Zine } from '../zine';\n\n/** Rail width in px. Matches the outline: both hold text, not pictures. */\nconst RAIL_WIDTH = 208;\n/** Wait after the last keystroke before searching, so typing does not scan per character. */\nconst DEBOUNCE_MS = 180;\n/** Shorter queries match too much to be useful. */\nconst MIN_QUERY = 2;\n\n/**\n * Full-text search beside the book: a query field over its results.\n *\n * It lives in the same rail as the thumbnails and outline rather than a popover, because results\n * are an open-ended list — a floating panel over the book would either cover the page being\n * searched or clip the very results it found.\n */\nexport class Search {\n #zine: Zine;\n #doc: Document;\n #bar: Sidebar;\n #input: HTMLInputElement;\n #hits: HTMLElement;\n /** Increments per query so a slow search that resolves late cannot overwrite a newer one. */\n #run = 0;\n #timer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(zine: Zine, container: HTMLElement) {\n this.#zine = zine;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n this.#bar = new Sidebar(doc, container, {\n className: 'zine-search',\n label: 'Search results',\n width: RAIL_WIDTH,\n });\n\n this.#input = doc.createElement('input');\n this.#input.type = 'search';\n this.#input.className = 'zine-search-input';\n this.#input.placeholder = 'Search…';\n this.#input.setAttribute('aria-label', 'Search the document');\n this.#input.addEventListener('input', () => {\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = setTimeout(() => void this.#search(), DEBOUNCE_MS);\n });\n // Enter searches at once rather than waiting out the debounce.\n this.#input.addEventListener('keydown', (e) => {\n if ((e as KeyboardEvent).key !== 'Enter') return;\n if (this.#timer) clearTimeout(this.#timer);\n void this.#search();\n });\n\n this.#hits = doc.createElement('div');\n this.#hits.className = 'zine-search-hits';\n\n this.#bar.root.append(this.#input, this.#hits);\n this.#input.focus();\n }\n\n #note(text: string): void {\n const note = this.#doc.createElement('div');\n note.className = 'zine-panel-note';\n note.textContent = text;\n this.#hits.replaceChildren(note);\n }\n\n async #search(): Promise<void> {\n const query = this.#input.value.trim();\n const mine = ++this.#run;\n if (query.length < MIN_QUERY) {\n this.#hits.replaceChildren();\n return;\n }\n this.#note('Searching…');\n const results = await this.#zine.search(query);\n if (mine !== this.#run || !this.#bar.root.isConnected) return; // superseded, or closed\n if (results.length === 0) {\n this.#note(`No matches for “${query}”`);\n return;\n }\n this.#hits.replaceChildren(\n ...results.map((hit) => {\n const row = this.#doc.createElement('button');\n row.type = 'button';\n row.className = 'zine-search-hit';\n row.setAttribute('role', 'option');\n row.setAttribute('aria-label', `Page ${hit.page + 1}: ${hit.excerpt}`);\n const label = this.#doc.createElement('span');\n label.className = 'zine-search-page';\n label.textContent = `Page ${hit.page + 1}`;\n const excerpt = this.#doc.createElement('small');\n excerpt.textContent = hit.excerpt;\n row.append(label, excerpt);\n row.addEventListener('click', () => this.#zine.flipTo(hit.page));\n return row;\n }),\n );\n }\n\n destroy(): void {\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = null;\n this.#run++; // any search still in flight is now stale\n this.#bar.destroy();\n }\n}\n","import { createIcon, ICONS } from './icons';\nimport type { Zine } from '../zine';\n\n/**\n * Big page-turn arrows flanking the book.\n *\n * They sit *beside* the book rather than over it, for the same reason the docked toolbar does:\n * the container's measured box is what the renderer sizes pages from and what pointer\n * hit-testing maps into, so anything inside it would shrink the book and skew every tap. The\n * book (with whatever the toolbar already wrapped around it) goes in a flex row between them.\n *\n * An arrow disables itself at the end of the book rather than disappearing, so the layout does\n * not jump as the reader reaches the covers.\n */\nexport class Arrows {\n #zine: Zine;\n #prev: HTMLButtonElement;\n #next: HTMLButtonElement;\n #wrap: HTMLElement | null = null;\n #unsubscribe: (() => void)[] = [];\n\n constructor(zine: Zine, container: HTMLElement) {\n this.#zine = zine;\n const doc = container.ownerDocument!;\n\n const rtl = zine.getDirection() === 'rtl';\n // The arrows point where the page goes, so in RTL they swap: \"back\" is to the right.\n this.#prev = this.#button(doc, rtl ? ICONS.next : ICONS.prev, 'Previous page', () =>\n zine.flipPrev(),\n );\n this.#next = this.#button(doc, rtl ? ICONS.prev : ICONS.next, 'Next page', () =>\n zine.flipNext(),\n );\n\n this.#place(doc, container);\n\n const refresh = (): void => this.#refresh();\n this.#unsubscribe.push(zine.on('pageChanged', refresh));\n this.#unsubscribe.push(zine.on('flipEnd', refresh));\n this.#refresh();\n }\n\n #button(\n doc: Document,\n icon: string,\n label: string,\n onClick: () => void,\n ): HTMLButtonElement {\n const btn = doc.createElement('button');\n btn.type = 'button';\n btn.className = 'zine-arrow';\n btn.title = label;\n btn.setAttribute('aria-label', label);\n btn.appendChild(createIcon(doc, icon));\n btn.addEventListener('click', onClick);\n // The book's gesture handlers sit on the container; an arrow press is not a page tap.\n for (const type of ['pointerdown', 'pointerup', 'click']) {\n btn.addEventListener(type, (e) => e.stopPropagation());\n }\n return btn;\n }\n\n #place(doc: Document, container: HTMLElement): void {\n // Wrap the outermost thing the library has already built around the book — a docked toolbar\n // or a side panel — so the arrows flank the whole assembly, not just the pages.\n const outer =\n container.closest('.zine-panel-wrap') ??\n container.closest('.zine-controls-wrap') ??\n container;\n const parent = outer.parentNode;\n if (!parent) return; // not in a document; nothing sensible to flank\n const wrap = doc.createElement('div');\n wrap.className = 'zine-arrows-wrap';\n parent.insertBefore(wrap, outer);\n wrap.append(this.#prev, outer, this.#next);\n this.#wrap = wrap;\n }\n\n /**\n * Hide an arrow that has nowhere to go — no back arrow on the first page.\n *\n * Hidden rather than removed so its slot stays reserved: dropping it from the flow would slide\n * the book sideways every time the reader reached a cover. `disabled` comes along for the ride\n * so the button is inert even if a consumer's own CSS makes it visible again.\n */\n #refresh(): void {\n for (const [btn, canTurn] of [\n [this.#prev, this.#zine.canFlipPrev()],\n [this.#next, this.#zine.canFlipNext()],\n ] as const) {\n btn.disabled = !canTurn;\n btn.classList.toggle('zine-arrow-hidden', !canTurn);\n }\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#prev.remove();\n this.#next.remove();\n const wrap = this.#wrap;\n if (wrap?.parentNode) {\n // The arrows are gone, so what is left is the book; put it back where the wrapper was.\n while (wrap.firstChild) wrap.parentNode.insertBefore(wrap.firstChild, wrap);\n wrap.remove();\n }\n this.#wrap = null;\n }\n}\n","import { createIcon, ICONS } from './icons';\nimport { DEFAULT_ITEMS, defineControl, resolveControl } from './registry';\nimport { registerBuiltins } from './builtins';\nimport { ensureStyles } from './styles';\nimport { Thumbnails } from './thumbnails';\nimport { Outline } from './outline';\nimport { Search } from './search';\nimport { Arrows } from './arrows';\n\n/** The side panels, all of which share the one rail beside the book. */\nconst PANELS = { thumbnails: Thumbnails, outline: Outline, search: Search };\ntype PanelKind = keyof typeof PANELS;\n/** What every panel has in common: build itself on construction, and clean up after itself. */\ntype Panel = InstanceType<(typeof PANELS)[PanelKind]>;\nimport type {\n ControlContext,\n ControlDef,\n ControlItem,\n ControlsOptions,\n ControlsPosition,\n} from './types';\nimport type { Zine } from '../zine';\n\n/**\n * Events the container listens to for flipping, zooming and dragging. The gesture layer binds\n * these on the container and never checks `event.target`, so anything the toolbar handles has to\n * be stopped here or a button press would also register as a page tap.\n */\nconst SWALLOWED = ['pointerdown', 'pointerup', 'pointermove', 'click', 'dblclick', 'wheel', 'keydown'];\n\ninterface Popover {\n el: HTMLElement;\n trigger: HTMLElement;\n}\n\nexport class Toolbar {\n #zine: Zine;\n #doc: Document;\n #root: HTMLElement;\n #bar: HTMLElement;\n /** Every control on screen, buttons and `render` widgets alike, so #refresh can reach them. */\n #buttons: { el: HTMLElement; def: ControlDef }[] = [];\n #popover: Popover | null = null;\n #unsubscribe: (() => void)[] = [];\n #pageInput: HTMLInputElement | null = null;\n /** The flex wrapper docked mode inserts around the container; unwound on destroy. */\n #wrap: HTMLElement | null = null;\n #container: HTMLElement;\n /** The side panel currently in the rail. Only one at a time — they share the space. */\n #panel: Panel | null = null;\n #panelKind: PanelKind | null = null;\n /** Whether the document turned out to have any outline entries. Null until known: the answer\n * is async, and the outline control stays hidden rather than flash in and out. */\n #hasOutline: boolean | null = null;\n #arrows: Arrows | null = null;\n\n constructor(zine: Zine, container: HTMLElement, options: ControlsOptions) {\n this.#zine = zine;\n this.#container = container;\n const doc = container.ownerDocument!;\n this.#doc = doc;\n ensureStyles(doc);\n\n const position = options.position ?? 'bottom';\n const docked = options.docked ?? true;\n this.#root = doc.createElement('div');\n this.#root.className = [\n 'zine-controls',\n `zine-controls-${position}`,\n docked ? 'zine-controls-docked' : 'zine-controls-floating',\n options.className,\n ]\n .filter(Boolean)\n .join(' ');\n this.#root.setAttribute('role', 'toolbar');\n this.#root.setAttribute('aria-label', 'Flipbook controls');\n\n this.#bar = doc.createElement('div');\n this.#bar.className = 'zine-controls-bar';\n this.#root.appendChild(this.#bar);\n\n // Toolbar keys are handled first, then every listed event is stopped so none of it reaches\n // the book's gesture, zoom or keyboard handlers on the container.\n this.#root.addEventListener('keydown', (e) => this.#onKeyDown(e as KeyboardEvent));\n for (const event of SWALLOWED) {\n this.#root.addEventListener(event, (e) => this.#isolate(e));\n }\n this.#place(container, position, docked);\n\n const refresh = (): void => this.#refresh();\n this.#unsubscribe.push(zine.on('pageChanged', refresh));\n this.#unsubscribe.push(zine.on('zoomChanged', refresh));\n this.#unsubscribe.push(zine.on('flipEnd', refresh));\n const onDocPointer = (e: Event): void => {\n if (this.#popover && !this.#popover.el.contains(e.target as Node)) this.#closePopover();\n };\n doc.addEventListener('pointerdown', onDocPointer, true);\n this.#unsubscribe.push(() => doc.removeEventListener('pointerdown', onDocPointer, true));\n }\n\n /**\n * Put the toolbar in the page.\n *\n * Floating is simple: a child of the container, absolutely positioned over the book.\n *\n * Docked has to sit *outside* the container, because the container's measured box is what the\n * renderer sizes the book from and what pointer hit-testing maps into — a bar inside it would\n * shrink the book and skew every tap. So the container is wrapped in a flex column (or row),\n * with the toolbar as its sibling. The wrapper inherits the container's own layout box so the\n * arrangement the consumer's CSS set up is preserved.\n */\n #place(container: HTMLElement, position: ControlsPosition, docked: boolean): void {\n if (!docked) {\n container.appendChild(this.#root);\n return;\n }\n const parent = container.parentNode;\n if (!parent) {\n // Not in a document yet: fall back to overlaying rather than losing the toolbar.\n this.#root.classList.replace('zine-controls-docked', 'zine-controls-floating');\n container.appendChild(this.#root);\n return;\n }\n const wrap = this.#doc.createElement('div');\n wrap.className = `zine-controls-wrap zine-controls-wrap-${position}`;\n parent.insertBefore(wrap, container);\n wrap.appendChild(container);\n // 'top'/'left' put the bar first; flex-direction (set in CSS) handles the axis.\n if (position === 'top' || position === 'left') wrap.insertBefore(this.#root, container);\n else wrap.appendChild(this.#root);\n this.#wrap = wrap;\n }\n\n /** Build the layout. Separate from construction so instance-bound widgets (the page field, the\n * search panel) can be registered against this toolbar first. */\n mount(options: ControlsOptions): void {\n this.#build(options.items ?? DEFAULT_ITEMS);\n // After #place, so the arrows can wrap whatever it built around the book.\n if (options.arrows ?? true) this.#arrows = new Arrows(this.#zine, this.#container);\n this.#refresh();\n this.#probeOutline();\n }\n\n destroy(): void {\n for (const off of this.#unsubscribe) off();\n this.#unsubscribe = [];\n this.#closePopover();\n this.#panel?.destroy();\n this.#panel = null;\n this.#panelKind = null;\n this.#root.remove();\n // Unwrap from the inside out: the arrows enclose the toolbar's wrapper, so removing theirs\n // first would leave this one orphaned outside the document.\n const wrap = this.#wrap;\n if (wrap?.parentNode) {\n wrap.parentNode.insertBefore(wrap.firstChild!, wrap);\n wrap.remove();\n }\n this.#wrap = null;\n this.#arrows?.destroy();\n this.#arrows = null;\n }\n\n /** Keep toolbar interaction from reaching the book's own gesture/zoom/keyboard handlers. */\n #isolate(event: Event): void {\n event.stopPropagation();\n }\n\n #context(close: () => void = () => this.#closePopover()): ControlContext {\n return { zine: this.#zine, close };\n }\n\n #build(items: readonly ControlItem[]): void {\n for (const item of items) {\n if (item === '|') {\n const sep = this.#doc.createElement('div');\n sep.className = 'zine-controls-sep';\n this.#bar.appendChild(sep);\n continue;\n }\n const def = resolveControl(item);\n let el: HTMLElement;\n if (def.render) {\n el = def.render(this.#context());\n // Registered like any button so its predicates are re-read on every change; #button\n // does this for the controls it builds.\n this.#buttons.push({ el, def });\n } else {\n el = this.#button(def, 'zine-controls-btn');\n }\n this.#bar.appendChild(el);\n }\n }\n\n /** A control button. Bar buttons are icon-only with the title as tooltip; menu items and any\n * control without an icon also carry a visible label. */\n /** A control's label, which may be a function of the current state. */\n #titleOf(def: ControlDef): string {\n return typeof def.title === 'function' ? def.title(this.#context()) : def.title;\n }\n\n /** A control's glyph, which may likewise depend on state (an RTL mirror, say). */\n #iconOf(def: ControlDef): string | undefined {\n return typeof def.icon === 'function' ? def.icon(this.#context()) : def.icon;\n }\n\n #button(def: ControlDef, className: string, withLabel = false): HTMLButtonElement {\n const btn = this.#doc.createElement('button');\n const title = this.#titleOf(def);\n const icon = this.#iconOf(def);\n btn.type = 'button';\n btn.className = className;\n btn.title = title;\n btn.setAttribute('aria-label', title);\n if (icon) btn.appendChild(createIcon(this.#doc, icon));\n if (withLabel || !icon) {\n const label = this.#doc.createElement('span');\n label.className = 'zine-controls-label';\n label.textContent = title;\n btn.appendChild(label);\n }\n btn.addEventListener('click', () => this.#activate(def, btn));\n this.#buttons.push({ el: btn, def });\n return btn;\n }\n\n #activate(def: ControlDef, trigger: HTMLButtonElement): void {\n if (def.isDisabled?.(this.#context())) return;\n // A submenu toggles its popover; a second press on the same trigger closes it.\n if (def.children) {\n if (this.#popover?.trigger === trigger) this.#closePopover();\n else this.#openMenu(def, trigger);\n return;\n }\n def.action?.(this.#context());\n this.#refresh();\n }\n\n #openMenu(def: ControlDef, trigger: HTMLButtonElement): void {\n this.#closePopover();\n const menu = this.#doc.createElement('div');\n menu.className = 'zine-controls-menu';\n menu.setAttribute('role', 'menu');\n const ctx = this.#context();\n for (const child of def.children ?? []) {\n if (child === '|') continue;\n const childDef = resolveControl(child);\n if (childDef.isVisible && !childDef.isVisible(ctx)) continue;\n let item: HTMLElement;\n if (childDef.render) {\n item = childDef.render(this.#context());\n this.#buttons.push({ el: item, def: childDef });\n } else {\n item = this.#button(childDef, 'zine-controls-btn', true);\n }\n item.setAttribute('role', 'menuitem');\n menu.appendChild(item);\n }\n this.#showPopover(menu, trigger);\n // The entries were only just built, so nothing has evaluated their state yet: without this\n // a menu opens with every entry enabled until the next page turn happens to refresh it.\n this.#refresh();\n menu.querySelector('button')?.focus();\n }\n\n #showPopover(el: HTMLElement, trigger: HTMLElement): void {\n this.#root.appendChild(el);\n this.#popover = { el, trigger };\n trigger.setAttribute('aria-expanded', 'true');\n this.#placePopover(el, trigger);\n }\n\n /** Anchor a popover to its trigger, flipped to stay inside the book. */\n #placePopover(el: HTMLElement, trigger: HTMLElement): void {\n const root = this.#root.getBoundingClientRect();\n const anchor = trigger.getBoundingClientRect();\n const box = el.getBoundingClientRect();\n if (!root.width || !box.width) return; // not laid out (e.g. jsdom): leave to CSS\n let left = anchor.left - root.left + anchor.width / 2 - box.width / 2;\n left = Math.max(4, Math.min(left, root.width - box.width - 4));\n el.style.left = `${left}px`;\n // Prefer opening away from the edge the toolbar sits on.\n const below = anchor.bottom - root.top + 6;\n const above = anchor.top - root.top - box.height - 6;\n el.style.top = `${above >= 0 ? above : below}px`;\n }\n\n #closePopover(): void {\n if (!this.#popover) return;\n const { el, trigger } = this.#popover;\n this.#popover = null;\n trigger.removeAttribute('aria-expanded');\n // Drop buttons that belonged to the popover so #refresh stops touching detached nodes.\n this.#buttons = this.#buttons.filter((b) => !el.contains(b.el));\n el.remove();\n if (this.#doc.activeElement && el.contains(this.#doc.activeElement)) trigger.focus();\n }\n\n /** Arrow keys move between controls; Escape dismisses an open popover. */\n #onKeyDown(event: KeyboardEvent): void {\n if (event.key === 'Escape' && this.#popover) {\n const { trigger } = this.#popover;\n this.#closePopover();\n trigger.focus();\n event.preventDefault();\n return;\n }\n const focusables = [...this.#root.querySelectorAll<HTMLElement>('button, input')];\n const at = focusables.indexOf(this.#doc.activeElement as HTMLElement);\n if (at < 0) return;\n // Let the page-number field use its own arrows for text editing.\n if (this.#doc.activeElement instanceof HTMLInputElement) return;\n const step = event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 0;\n if (step === 0) return;\n const next = focusables[(at + step + focusables.length) % focusables.length];\n next?.focus();\n event.preventDefault();\n }\n\n /**\n * Re-read every control's state after the book changes.\n *\n * Applies to custom widgets built with `render` as well as to plain buttons: the predicates\n * are on `ControlDef`, so honouring them only for button-shaped controls would make the same\n * field mean different things depending on how a control chose to draw itself. A widget may be\n * any element, so each piece of state is applied in whatever way that element supports.\n */\n #refresh(): void {\n const ctx = this.#context();\n for (const { el, def } of this.#buttons) {\n // Does not apply to this book at all: take the space back, it is not coming again.\n const applies = def.children ? this.#hasVisibleChildren(def, ctx) : def.isVisible?.(ctx);\n if (applies !== undefined) el.style.display = applies ? '' : 'none';\n\n const disabled = def.isDisabled?.(ctx) ?? false;\n // A wrapper element cannot be disabled, so mark it and disable the fields inside.\n for (const field of this.#formFields(el)) field.disabled = disabled;\n if (!this.#formFields(el).length) el.setAttribute('aria-disabled', String(disabled));\n el.classList.toggle('zine-controls-off', disabled);\n\n if (def.isActive) el.setAttribute('aria-pressed', String(def.isActive(ctx)));\n if (typeof def.title === 'function') {\n const title = def.title(ctx);\n el.title = title;\n el.setAttribute('aria-label', title);\n const label = el.querySelector('.zine-controls-label');\n if (label) label.textContent = title;\n }\n if (typeof def.icon === 'function') {\n el.querySelector('svg')?.replaceWith(createIcon(this.#doc, def.icon(ctx)));\n }\n }\n if (this.#pageInput && this.#doc.activeElement !== this.#pageInput) {\n this.#pageInput.value = String(this.#zine.getPage() + 1);\n }\n }\n\n /** The element itself if it can be disabled, else any inputs/buttons it wraps. */\n #formFields(el: HTMLElement): (HTMLButtonElement | HTMLInputElement)[] {\n if (el instanceof HTMLButtonElement || el instanceof HTMLInputElement) return [el];\n return [...el.querySelectorAll<HTMLButtonElement | HTMLInputElement>('button, input')];\n }\n\n /** A submenu is only worth showing if something inside it is. Keeps the overflow button from\n * opening onto nothing when every entry has hidden itself. */\n #hasVisibleChildren(def: ControlDef, ctx: ControlContext): boolean {\n if (def.isVisible && !def.isVisible(ctx)) return false;\n return (def.children ?? []).some((child) => {\n if (child === '|') return false;\n const childDef = resolveControl(child);\n return childDef.isVisible ? childDef.isVisible(ctx) : true;\n });\n }\n\n /** Registered lazily by {@link registerWidgets} so the widget can reach this instance. */\n makePageInput(): HTMLElement {\n const wrap = this.#doc.createElement('div');\n wrap.className = 'zine-controls-page';\n const input = this.#doc.createElement('input');\n input.type = 'number';\n input.min = '1';\n input.max = String(this.#zine.getPageCount());\n input.value = String(this.#zine.getPage() + 1);\n input.setAttribute('aria-label', 'Page number');\n const total = this.#doc.createElement('span');\n total.textContent = `/ ${this.#zine.getPageCount()}`;\n\n const commit = (): void => {\n const n = Number(input.value);\n if (Number.isFinite(n)) this.#zine.flipTo(Math.round(n) - 1);\n input.value = String(this.#zine.getPage() + 1);\n };\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n commit();\n input.blur();\n }\n });\n input.addEventListener('blur', commit);\n wrap.append(input, total);\n this.#pageInput = input;\n return wrap;\n }\n\n /** Which side panel is showing, if any. */\n openPanel(): PanelKind | null {\n return this.#panelKind;\n }\n\n /**\n * Whether this document has a table of contents worth offering.\n *\n * Many PDFs have none, and a control that can only report its own emptiness is a dead end, so\n * the outline button stays hidden until the outline is known to be non-empty. The first call\n * kicks off the lookup and refreshes the toolbar when it lands; `Zine.getOutline` memoizes, so\n * the panel does not pay for it twice.\n */\n hasOutline(): boolean {\n return this.#hasOutline === true;\n }\n\n /** Ask the document whether it has an outline, and refresh once the answer lands. Started as\n * the toolbar mounts so the button settles into place immediately rather than on first use. */\n #probeOutline(): void {\n if (this.#hasOutline !== null || !this.#zine.canOutline()) return;\n this.#hasOutline = false; // assume not, until we hear otherwise\n void this.#zine.getOutline().then((items) => {\n if (items.length === 0) return;\n this.#hasOutline = true;\n this.#refresh();\n // The menu may already be open with the entry missing; rebuild it so it appears.\n const popover = this.#popover;\n if (popover) {\n const def = this.#buttons.find((b) => b.el === popover.trigger)?.def;\n this.#closePopover();\n if (def) this.#openMenu(def, popover.trigger as HTMLButtonElement);\n }\n });\n }\n\n /**\n * Show one of the side panels, or close it if it is already up. They share the rail beside the\n * book, so opening one closes the others. Rebuilt each time, so the thumbnail rail always\n * matches the current spread grouping (which `spreadMode` and the responsive fallback change).\n */\n togglePanel(kind: PanelKind): void {\n const wasOpen = this.#panelKind;\n this.#panel?.destroy();\n this.#panel = null;\n this.#panelKind = null;\n if (wasOpen !== kind) {\n this.#panel = new PANELS[kind](this.#zine, this.#container);\n this.#panelKind = kind;\n }\n this.#refresh();\n }\n\n}\n\n/**\n * Controls that need the toolbar itself (a DOM widget, or a panel to open) are registered per\n * instance, since the registry is module-level and shared.\n */\nexport function registerWidgets(toolbar: Toolbar): void {\n registerBuiltins();\n defineControl({\n id: 'thumbnails',\n title: () => (toolbar.openPanel() === 'thumbnails' ? 'Hide thumbnails' : 'Show thumbnails'),\n icon: ICONS.thumbnails,\n // Documents only, for now: an image book is already a short, flat list of pictures.\n isVisible: (ctx) => ctx.zine.isDocument(),\n isActive: () => toolbar.openPanel() === 'thumbnails',\n action: (ctx) => {\n toolbar.togglePanel('thumbnails');\n ctx.close();\n },\n });\n defineControl({\n id: 'outline',\n title: () => (toolbar.openPanel() === 'outline' ? 'Hide outline' : 'Show outline'),\n icon: ICONS.outline,\n // Only when the document actually has entries to show.\n isVisible: () => toolbar.hasOutline(),\n isActive: () => toolbar.openPanel() === 'outline',\n action: (ctx) => {\n toolbar.togglePanel('outline');\n ctx.close();\n },\n });\n defineControl({\n id: 'pageInput',\n title: 'Page',\n render: () => toolbar.makePageInput(),\n });\n defineControl({\n id: 'search',\n title: () => (toolbar.openPanel() === 'search' ? 'Hide search' : 'Search'),\n icon: ICONS.search,\n isVisible: (ctx) => ctx.zine.canSearch(),\n isActive: () => toolbar.openPanel() === 'search',\n action: () => toolbar.togglePanel('search'),\n });\n}\n","import { registerWidgets, Toolbar } from './toolbar';\nimport type { ControlsOptions } from './types';\nimport type { Zine } from '../zine';\n\nexport { defineControl, getControl, DEFAULT_ITEMS } from './registry';\nexport { ICONS, createIcon } from './icons';\nexport type {\n ControlContext,\n ControlDef,\n ControlItem,\n ControlsOptions,\n ControlsPosition,\n} from './types';\n\n/**\n * Build the toolbar inside `container` and return its teardown.\n *\n * This module is the lazy chunk `zine.ts` reaches through a dynamic `import()`, so a book built\n * with `controls: false` never downloads the toolbar, its icons or its stylesheet. The file is\n * named `controls.ts` rather than `index.ts` so the emitted chunk is `controls-<hash>.js`, which\n * is what the size-limit budget watches.\n */\nexport function mountControls(\n zine: Zine,\n container: HTMLElement,\n options: ControlsOptions = {},\n): () => void {\n const toolbar = new Toolbar(zine, container, options);\n // Built-ins and the instance-bound widgets have to exist before the layout resolves ids.\n registerWidgets(toolbar);\n toolbar.mount(options);\n return () => toolbar.destroy();\n}\n"],"mappings":"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"}
package/dist/curls.js ADDED
@@ -0,0 +1,133 @@
1
+ import { n as e, r as t, t as n } from "./types-DEz3mRLI.js";
2
+ import { a as r, i, n as a, o, t as s } from "./bundled-Cp43MIt0.js";
3
+ //#region src/geometry/curls/roll.ts
4
+ function c(e, t, n, i) {
5
+ let a = e.cols + 1, o = e.rows + 1, s = e.positions, c = Math.max(1e-6, i < .5 ? i * 2 * Math.PI : (1 - (i - .5) * 2) * Math.PI), l = t / c, u = i < .5 ? 0 : -(i - .5) * 2 * Math.PI, d = Math.cos(u), f = Math.sin(u);
6
+ for (let t = 0; t < a; t++) {
7
+ let r = t / e.cols * c, i = Math.sin(r) * l, u = (1 - Math.cos(r)) * l, p = i * d + u * f, m = -i * f + u * d;
8
+ for (let r = 0; r < o; r++) {
9
+ let i = (r * a + t) * 3;
10
+ s[i] = p, s[i + 1] = r / e.rows * n, s[i + 2] = m;
11
+ }
12
+ }
13
+ r(e);
14
+ }
15
+ //#endregion
16
+ //#region src/geometry/curls/leaf.ts
17
+ var l = .34, u = 11 * Math.PI / 180, d = 128;
18
+ function f() {
19
+ let e = /* @__PURE__ */ new Float64Array(129), t = /* @__PURE__ */ new Float64Array(129), n = 1 / d, r = (e) => Math.PI * e - .5 * Math.sin(2 * Math.PI * e);
20
+ for (let i = 1; i <= d; i++) {
21
+ let a = r((i - 1) * n), o = r(i * n);
22
+ e[i] = e[i - 1] + .5 * (Math.cos(a) + Math.cos(o)) * n, t[i] = t[i - 1] + .5 * (Math.sin(a) + Math.sin(o)) * n;
23
+ }
24
+ return e[d] = 0, {
25
+ x: e,
26
+ z: t
27
+ };
28
+ }
29
+ var p = f(), m = p.z[d];
30
+ function h(e) {
31
+ let t = Math.max(0, Math.min(1, e)) * d, n = Math.min(d - 1, Math.floor(t)), r = t - n;
32
+ return [p.x[n] + (p.x[n + 1] - p.x[n]) * r, p.z[n] + (p.z[n + 1] - p.z[n]) * r];
33
+ }
34
+ function g(e, t, n, i, a) {
35
+ let o = e.cols + 1, s = e.rows + 1, c = e.positions;
36
+ if (i <= 1e-7 || i >= .9999999) {
37
+ let a = i < .5 ? 1 : -1;
38
+ for (let r = 0; r < s; r++) {
39
+ let i = r / e.rows * n;
40
+ for (let n = 0; n < o; n++) {
41
+ let s = (r * o + n) * 3;
42
+ c[s] = a * (n / e.cols) * t, c[s + 1] = i, c[s + 2] = 0;
43
+ }
44
+ }
45
+ r(e);
46
+ return;
47
+ }
48
+ let d = Math.sin(Math.PI * i) ** .72, f = Math.max(1e-6, t * l * d), p = Math.max(-1, Math.min(1, (a.y - .5) * 2)) * u * Math.sin(Math.PI * i) ** 1.15, g = Math.cos(p), _ = Math.sin(p), v = -_, y = g, b = Math.abs(_) * n * .5, x = t + b / g, S = x + (-(f + b) / g - x) * i, C = n * .5;
49
+ for (let r = 0; r < s; r++) {
50
+ let i = r / e.rows * n;
51
+ for (let n = 0; n < o; n++) {
52
+ let a = n / e.cols * t, s = a - S, l = i - C, u = s * g + l * _, d = s * v + l * y, p = (r * o + n) * 3;
53
+ if (u <= 0) {
54
+ c[p] = a, c[p + 1] = i, c[p + 2] = 0;
55
+ continue;
56
+ }
57
+ let b, x;
58
+ if (u < f) {
59
+ let [e, t] = h(u / f);
60
+ b = f * e, x = f * t;
61
+ } else b = -(u - f), x = f * m;
62
+ c[p] = S + v * d + g * b, c[p + 1] = C + y * d + _ * b, c[p + 2] = x;
63
+ }
64
+ }
65
+ r(e);
66
+ }
67
+ //#endregion
68
+ //#region src/geometry/curls/flick.ts
69
+ var _ = .45;
70
+ function v(e, t, n, i, a) {
71
+ let o = e.cols + 1, s = e.rows + 1, c = e.cols, l = e.positions, u = t / c, d = Math.PI * i, f = -.52 * Math.sin(2 * Math.PI * i);
72
+ for (let t = 0; t < s; t++) {
73
+ let r = t / e.rows, i = f * (1 + _ * Math.abs(r - a.y)), s = r * n, p = 0, m = 0, h = Math.cos(d), g = Math.sin(d);
74
+ l[t * o * 3] = 0, l[t * o * 3 + 1] = s, l[t * o * 3 + 2] = 0;
75
+ for (let e = 1; e < o; e++) {
76
+ let n = e / c, r = d + i * n * n, a = Math.cos(r), f = Math.sin(r);
77
+ p += .5 * (h + a) * u, m += .5 * (g + f) * u, h = a, g = f;
78
+ let _ = (t * o + e) * 3;
79
+ l[_] = p, l[_ + 1] = s, l[_ + 2] = m;
80
+ }
81
+ }
82
+ r(e);
83
+ }
84
+ //#endregion
85
+ //#region src/geometry/curls/silk.ts
86
+ var y = .52, b = .62, x = .5, S = 1.12;
87
+ function C(e, t, n, i, a) {
88
+ let o = e.cols + 1, s = e.rows + 1, c = e.cols, l = e.positions, u = t / c, d = !!a.fill;
89
+ if (i <= 1e-7 || i >= .9999999) {
90
+ let a = i < .5 ? 1 : -1;
91
+ for (let r = 0; r < s; r++) {
92
+ let i = r / e.rows * n;
93
+ for (let n = 0; n < o; n++) {
94
+ let s = (r * o + n) * 3;
95
+ l[s] = a * (n / e.cols) * t, l[s + 1] = i, l[s + 2] = 0;
96
+ }
97
+ }
98
+ r(e);
99
+ return;
100
+ }
101
+ let f = Math.PI * (d ? i ** +S : i), p = y * Math.sin(Math.PI * i) ** .8, m = b * Math.sin(Math.PI * i) * (1 - i);
102
+ for (let t = 0; t < s; t++) {
103
+ let r = t / e.rows, i = r * n, s = 1 - x * Math.abs(r - a.y), d = p * (.75 + .25 * s), h = m * s, g = 0, _ = 0, v = Math.cos(f), y = Math.sin(f);
104
+ l[t * o * 3] = 0, l[t * o * 3 + 1] = i, l[t * o * 3 + 2] = 0;
105
+ for (let e = 1; e < o; e++) {
106
+ let n = e / c, r = f + d * Math.sin(2 * Math.PI * n) + h * n * n, a = Math.cos(r), s = Math.sin(r);
107
+ g += .5 * (v + a) * u, _ += .5 * (y + s) * u, v = a, y = s;
108
+ let p = (t * o + e) * 3;
109
+ l[p] = g, l[p + 1] = i, l[p + 2] = _;
110
+ }
111
+ }
112
+ r(e);
113
+ }
114
+ //#endregion
115
+ //#region src/geometry/curls/index.ts
116
+ var w = {
117
+ deform: c,
118
+ anchored: !1,
119
+ flat: !0
120
+ }, T = {
121
+ deform: g,
122
+ anchored: !0
123
+ }, E = {
124
+ deform: v,
125
+ anchored: !0
126
+ }, D = {
127
+ deform: C,
128
+ anchored: !0
129
+ };
130
+ //#endregion
131
+ export { s as BUNDLED_CURLS, n as CURL_TYPES, e as DEFAULT_CURL, t as IMPORTABLE_CURLS, r as computeNormals, a as cone, o as createPageMesh, E as flick, T as leaf, w as roll, D as silk, i as simple };
132
+
133
+ //# sourceMappingURL=curls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curls.js","names":[],"sources":["../src/geometry/curls/roll.ts","../src/geometry/curls/leaf.ts","../src/geometry/curls/flick.ts","../src/geometry/curls/silk.ts","../src/geometry/curls/index.ts"],"sourcesContent":["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/** 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","/**\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 * Only `cone` (the default) and `simple` (used for reduced motion) are reachable from the\n * engine, so only those two are bundled. The rest are exported here as individual models for\n * callers to import and pass to `curl` — see `@zinejs/core/curls`. Each is a separate binding\n * rather than a member of one object, which is what lets a bundler drop the ones nobody names.\n */\nimport { deformRoll } from './roll';\nimport { deformLeaf } from './leaf';\nimport { deformFlick } from './flick';\nimport { deformSilk } from './silk';\nimport type { CurlModel } from './types';\n\nexport { createPageMesh, computeNormals, type PageMesh } from './mesh';\nexport { CURL_TYPES, DEFAULT_CURL, IMPORTABLE_CURLS } from './types';\nexport type { CurlAnchor, CurlDeform, CurlModel, CurlType, CurlSpec } from './types';\nexport { cone, simple, BUNDLED_CURLS } from './bundled';\n\n// `flat` says the sheet is already flat by mid-turn, so a lone page may dissolve on the earlier\n// schedule. Roll qualifies despite bending: it rolls up and flops over well before it lands.\nexport const roll: CurlModel = { deform: deformRoll, anchored: false, flat: true };\nexport const leaf: CurlModel = { deform: deformLeaf, anchored: true };\nexport const flick: CurlModel = { deform: deformFlick, anchored: true };\nexport const silk: CurlModel = { deform: deformSilk, anchored: true };\n"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /** Take the hash if it is going, and report whether this caller got it. */
2
+ export declare function claimHash(): boolean;
3
+ /** Give the hash back, so a book that replaces this one can own it. */
4
+ export declare function releaseHash(): void;
5
+ /** Read the 1-based page from a hash, as a 0-based index. Null when it carries no page. */
6
+ export declare function pageFromHash(hash: string): number | null;
7
+ /** The hash that names `page` (0-based), leaving any other keys in `hash` untouched. */
8
+ export declare function hashWithPage(hash: string, page: number): string;
9
+ export interface DeepLinkHandle {
10
+ /** Write the current page into the address bar. */
11
+ push(page: number): void;
12
+ stop(): void;
13
+ }
14
+ /**
15
+ * Mirror the reader's page in the URL, and follow it when the URL changes (back/forward, or a
16
+ * pasted link). `onNavigate` fires only for a page the book is not already on.
17
+ */
18
+ export declare function bindDeepLink(win: Window, onNavigate: (page: number) => void): DeepLinkHandle;
@@ -1,3 +1,4 @@
1
+ import type { SpreadMode } from './spread.js';
1
2
  export type Listener<T> = (payload: T) => void;
2
3
  /** Events typed as `void` are emitted with no payload argument. */
3
4
  type PayloadArgs<T> = [T] extends [void] ? [] : [payload: T];
@@ -26,6 +27,11 @@ export interface ZineEventMap {
26
27
  pageChanged: {
27
28
  page: number;
28
29
  };
30
+ /** Pages regrouped: the spread mode changed, or a narrow container forced one page. */
31
+ spreadChanged: {
32
+ mode: SpreadMode;
33
+ singlePage: boolean;
34
+ };
29
35
  zoomChanged: {
30
36
  scale: number;
31
37
  };
@@ -0,0 +1,65 @@
1
+ import type { PageContent } from '../renderer/types';
2
+ import type { PageRequest } from '../source/types';
3
+ /** A rectangle in container pixels. */
4
+ export interface Rect {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ }
10
+ /** The current view: the zoom scale and the translate applied about the container origin. */
11
+ export interface View {
12
+ scale: number;
13
+ tx: number;
14
+ ty: number;
15
+ }
16
+ /** One page of the spread, and where it is painted when unzoomed. */
17
+ export interface TilePage {
18
+ index: number;
19
+ rect: Rect;
20
+ }
21
+ /** What to draw: a page's raster region, and the container-space box it belongs in. */
22
+ export interface TilePlan {
23
+ index: number;
24
+ request: PageRequest;
25
+ /** Where the tile lands on screen, in *untransformed* container pixels. */
26
+ dest: Rect;
27
+ }
28
+ /**
29
+ * Work out which part of each page is visible, and at what magnification.
30
+ *
31
+ * The renderer paints a page into `rect` and then scales the whole viewport about the container
32
+ * origin, so a page occupies `rect * scale + t` on screen. Inverting that gives the part of the
33
+ * page inside the viewport, which is the only part worth rasterizing sharply.
34
+ *
35
+ * Returns the plan in untransformed container coordinates: the caller draws it under the same
36
+ * transform the renderer uses, so the tile tracks the page without any extra bookkeeping.
37
+ */
38
+ export declare function planTiles(pages: TilePage[], view: View, viewport: Rect): TilePlan[];
39
+ /** Whether two plans ask for the same pixels, so a re-render can be skipped while panning. */
40
+ export declare function sameTile(a: TilePlan, b: TilePlan): boolean;
41
+ /**
42
+ * A canvas laid over the book that paints crisp tiles on top of the magnified page.
43
+ *
44
+ * It sits outside the renderer on purpose: both renderers stretch a page raster to a fixed panel,
45
+ * so neither can place a partial-page tile, and teaching them to would mean the same geometry
46
+ * twice. The overlay applies the same transform the renderer does, so the tiles register exactly
47
+ * over the pixels they replace and simply disappear when the reader zooms out.
48
+ */
49
+ export declare class ZoomOverlay {
50
+ #private;
51
+ constructor(doc: Document, container: HTMLElement);
52
+ /** Drop every tile and hide the overlay, revealing the renderer's own pixels again. */
53
+ clear(): void;
54
+ /** The plans currently painted, so the caller can skip re-rendering an unchanged view. */
55
+ get shown(): readonly TilePlan[];
56
+ /**
57
+ * Paint `tiles` under `view`. Sizes the backing store to the device pixel grid so the tiles are
58
+ * not resampled a second time on the way to the screen.
59
+ */
60
+ draw(tiles: {
61
+ plan: TilePlan;
62
+ content: PageContent;
63
+ }[], view: View, viewport: Rect): void;
64
+ destroy(): void;
65
+ }
@@ -0,0 +1,16 @@
1
+ import { type CurlModel, type CurlSpec, type CurlType } from './types';
2
+ /**
3
+ * The curl models carried in the bundle, and the only ones nameable by string.
4
+ *
5
+ * Kept apart from `./index` so importing them does not drag in the other four: the barrel names
6
+ * every model, which is right for a caller picking one but wrong for the renderer, which must
7
+ * only ever pull these two.
8
+ */
9
+ export declare const cone: CurlModel;
10
+ export declare const simple: CurlModel;
11
+ export declare const BUNDLED_CURLS: Record<CurlType, CurlModel>;
12
+ /**
13
+ * Turn a `curl` option into a model. Strings name a bundled curl; anything else is already a
14
+ * model, imported from `@zinejs/core/curls` or written by the caller.
15
+ */
16
+ export declare function resolveCurl(spec: CurlSpec): CurlModel;
@@ -1,5 +1,9 @@
1
- import type { CurlModel, CurlType } from './types';
1
+ import type { CurlModel } from './types';
2
2
  export { createPageMesh, computeNormals, type PageMesh } from './mesh';
3
- export { CURL_TYPES, DEFAULT_CURL } from './types';
4
- export type { CurlAnchor, CurlDeform, CurlModel, CurlType } from './types';
5
- export declare const CURLS: Record<CurlType, CurlModel>;
3
+ export { CURL_TYPES, DEFAULT_CURL, IMPORTABLE_CURLS } from './types';
4
+ export type { CurlAnchor, CurlDeform, CurlModel, CurlType, CurlSpec } from './types';
5
+ export { cone, simple, BUNDLED_CURLS } from './bundled';
6
+ export declare const roll: CurlModel;
7
+ export declare const leaf: CurlModel;
8
+ export declare const flick: CurlModel;
9
+ export declare const silk: CurlModel;
@@ -19,7 +19,29 @@ export interface CurlModel {
19
19
  readonly deform: CurlDeform;
20
20
  /** True when the fold originates at a corner (`anchor.y`) rather than the whole edge. */
21
21
  readonly anchored: boolean;
22
+ /**
23
+ * True when the sheet is flat by mid-turn, so a lone page can start dissolving earlier: there
24
+ * is no curl left to read. Defaults to false, which suits a model still bent as it lands.
25
+ */
26
+ readonly flat?: boolean;
27
+ /**
28
+ * False to drop the specular highlight, for a sheet that never bends and so has no curve to
29
+ * catch the light. Defaults to true.
30
+ */
31
+ readonly gloss?: boolean;
22
32
  }
23
- export type CurlType = 'roll' | 'cone' | 'leaf' | 'flick' | 'silk' | 'simple';
33
+ /**
34
+ * The curl models carried in the bundle: the default, and the flat turn reduced motion falls
35
+ * back to. Everything else is imported from `@zinejs/core/curls` and passed as a model, so a
36
+ * book that never uses it never pays for it.
37
+ */
38
+ export type CurlType = 'cone' | 'simple';
24
39
  export declare const CURL_TYPES: CurlType[];
40
+ /**
41
+ * Curl models that exist but are not bundled. Named only so passing one as a string can say what
42
+ * to do about it instead of failing as an unknown value.
43
+ */
44
+ export declare const IMPORTABLE_CURLS: string[];
25
45
  export declare const DEFAULT_CURL: CurlType;
46
+ /** What the `curl` option accepts: a bundled name, or a model imported (or written) by the caller. */
47
+ export type CurlSpec = CurlType | CurlModel;
@@ -0,0 +1,29 @@
1
+ //#region src/controls/icons.ts
2
+ var e = {
3
+ prev: "<path d=\"m15 18-6-6 6-6\"/>",
4
+ next: "<path d=\"m9 18 6-6-6-6\"/>",
5
+ first: "<path d=\"m11 17-5-5 5-5\"/><path d=\"m18 17-5-5 5-5\"/>",
6
+ last: "<path d=\"m6 17 5-5-5-5\"/><path d=\"m13 17 5-5-5-5\"/>",
7
+ zoomIn: "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/><path d=\"M11 8v6\"/><path d=\"M8 11h6\"/>",
8
+ zoomOut: "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/><path d=\"M8 11h6\"/>",
9
+ search: "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>",
10
+ share: "<circle cx=\"18\" cy=\"5\" r=\"3\"/><circle cx=\"6\" cy=\"12\" r=\"3\"/><circle cx=\"18\" cy=\"19\" r=\"3\"/><path d=\"m8.6 13.5 6.8 4\"/><path d=\"m15.4 6.5-6.8 4\"/>",
11
+ menu: "<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/>",
12
+ fullscreen: "<path d=\"M8 3H5a2 2 0 0 0-2 2v3\"/><path d=\"M21 8V5a2 2 0 0 0-2-2h-3\"/><path d=\"M3 16v3a2 2 0 0 0 2 2h3\"/><path d=\"M16 21h3a2 2 0 0 0 2-2v-3\"/>",
13
+ exitFullscreen: "<path d=\"M8 3v3a2 2 0 0 1-2 2H3\"/><path d=\"M21 8h-3a2 2 0 0 1-2-2V3\"/><path d=\"M3 16h3a2 2 0 0 1 2 2v3\"/><path d=\"M16 21v-3a2 2 0 0 1 2-2h3\"/>",
14
+ thumbnails: "<path d=\"M7 2h10\"/><path d=\"M5 6h14\"/><rect width=\"18\" height=\"12\" x=\"3\" y=\"10\" rx=\"2\"/>",
15
+ outline: "<path d=\"M21 12h-8\"/><path d=\"M21 6h-8\"/><path d=\"M21 18h-8\"/><path d=\"M3 6v4c0 1.1.9 2 2 2h3\"/><path d=\"M3 10v6c0 1.1.9 2 2 2h3\"/>",
16
+ twoPages: "<path d=\"M12 7v14\"/><path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/>",
17
+ onePage: "<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v5h6\"/>",
18
+ print: "<path d=\"M6 9V2h12v7\"/><path d=\"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2\"/><rect width=\"12\" height=\"8\" x=\"6\" y=\"14\" rx=\"1\"/>",
19
+ download: "<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"M7 10l5 5 5-5\"/><path d=\"M12 15V3\"/>",
20
+ close: "<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>"
21
+ };
22
+ function t(e, t) {
23
+ let n = e.createElementNS("http://www.w3.org/2000/svg", "svg");
24
+ return n.setAttribute("viewBox", "0 0 24 24"), n.setAttribute("fill", "none"), n.setAttribute("stroke", "currentColor"), n.setAttribute("stroke-width", "1.75"), n.setAttribute("stroke-linecap", "round"), n.setAttribute("stroke-linejoin", "round"), n.setAttribute("aria-hidden", "true"), n.setAttribute("focusable", "false"), n.innerHTML = t, n;
25
+ }
26
+ //#endregion
27
+ export { t as n, e as t };
28
+
29
+ //# sourceMappingURL=icons-D87p7JMP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons-D87p7JMP.js","names":[],"sources":["../src/controls/icons.ts"],"sourcesContent":["/**\n * Toolbar glyphs, inlined so the bundle carries no icon-font or sprite dependency.\n *\n * Paths are from Lucide (https://lucide.dev), ISC licensed:\n * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather\n * (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.\n *\n * Each value is the inner markup of a 24x24 `viewBox` drawn with `stroke=\"currentColor\"`,\n * `fill=\"none\"` and round caps/joins — {@link createIcon} supplies those, so the strings stay to\n * the geometry.\n */\n// Not annotated `Record<string, string>`: that would make every ICONS.foo possibly-undefined\n// under noUncheckedIndexedAccess, even for names written right here. `satisfies` keeps the\n// literal's exact keys while still checking the values are markup strings.\nexport const ICONS = {\n /** chevron-left */\n prev: '<path d=\"m15 18-6-6 6-6\"/>',\n /** chevron-right */\n next: '<path d=\"m9 18 6-6-6-6\"/>',\n /** chevrons-left */\n first: '<path d=\"m11 17-5-5 5-5\"/><path d=\"m18 17-5-5 5-5\"/>',\n /** chevrons-right */\n last: '<path d=\"m6 17 5-5-5-5\"/><path d=\"m13 17 5-5-5-5\"/>',\n /** zoom-in */\n zoomIn:\n '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/><path d=\"M11 8v6\"/><path d=\"M8 11h6\"/>',\n /** zoom-out */\n zoomOut: '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/><path d=\"M8 11h6\"/>',\n /** search */\n search: '<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>',\n /** share-2 */\n share:\n '<circle cx=\"18\" cy=\"5\" r=\"3\"/><circle cx=\"6\" cy=\"12\" r=\"3\"/><circle cx=\"18\" cy=\"19\" r=\"3\"/><path d=\"m8.6 13.5 6.8 4\"/><path d=\"m15.4 6.5-6.8 4\"/>',\n /** ellipsis-vertical */\n menu: '<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/>',\n /** maximize */\n fullscreen:\n '<path d=\"M8 3H5a2 2 0 0 0-2 2v3\"/><path d=\"M21 8V5a2 2 0 0 0-2-2h-3\"/><path d=\"M3 16v3a2 2 0 0 0 2 2h3\"/><path d=\"M16 21h3a2 2 0 0 0 2-2v-3\"/>',\n /** minimize */\n exitFullscreen:\n '<path d=\"M8 3v3a2 2 0 0 1-2 2H3\"/><path d=\"M21 8h-3a2 2 0 0 1-2-2V3\"/><path d=\"M3 16h3a2 2 0 0 1 2 2v3\"/><path d=\"M16 21v-3a2 2 0 0 1 2-2h3\"/>',\n /** gallery-vertical-end */\n thumbnails:\n '<path d=\"M7 2h10\"/><path d=\"M5 6h14\"/><rect width=\"18\" height=\"12\" x=\"3\" y=\"10\" rx=\"2\"/>',\n /** list-tree */\n outline:\n '<path d=\"M21 12h-8\"/><path d=\"M21 6h-8\"/><path d=\"M21 18h-8\"/><path d=\"M3 6v4c0 1.1.9 2 2 2h3\"/><path d=\"M3 10v6c0 1.1.9 2 2 2h3\"/>',\n /** book-open: two facing pages */\n twoPages:\n '<path d=\"M12 7v14\"/><path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/>',\n /** file: a single page */\n onePage: '<path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v5h6\"/>',\n /** printer */\n print:\n '<path d=\"M6 9V2h12v7\"/><path d=\"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2\"/><rect width=\"12\" height=\"8\" x=\"6\" y=\"14\" rx=\"1\"/>',\n /** download */\n download: '<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"M7 10l5 5 5-5\"/><path d=\"M12 15V3\"/>',\n /** x */\n close: '<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>',\n} satisfies Record<string, string>;\n\n/** Build an `<svg>` for one of the {@link ICONS}, or null if the name is unknown. */\nexport function createIcon(doc: Document, markup: string): SVGSVGElement {\n const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n // Slightly under Lucide's default 2: at the toolbar's icon size a full-weight stroke reads\n // heavy and closes up the tighter shapes.\n svg.setAttribute('stroke-width', '1.75');\n svg.setAttribute('stroke-linecap', 'round');\n svg.setAttribute('stroke-linejoin', 'round');\n svg.setAttribute('aria-hidden', 'true');\n svg.setAttribute('focusable', 'false');\n // Icon markup is library-authored, never consumer input.\n svg.innerHTML = markup;\n return svg;\n}\n"],"mappings":";AAcA,IAAa,IAAQ;CAEnB,MAAM;CAEN,MAAM;CAEN,OAAO;CAEP,MAAM;CAEN,QACE;CAEF,SAAS;CAET,QAAQ;CAER,OACE;CAEF,MAAM;CAEN,YACE;CAEF,gBACE;CAEF,YACE;CAEF,SACE;CAEF,UACE;CAEF,SAAS;CAET,OACE;CAEF,UAAU;CAEV,OAAO;AACT;AAGA,SAAgB,EAAW,GAAe,GAA+B;CACvE,IAAM,IAAM,EAAI,gBAAgB,8BAA8B,KAAK;CAanE,OAZA,EAAI,aAAa,WAAW,WAAW,GACvC,EAAI,aAAa,QAAQ,MAAM,GAC/B,EAAI,aAAa,UAAU,cAAc,GAGzC,EAAI,aAAa,gBAAgB,MAAM,GACvC,EAAI,aAAa,kBAAkB,OAAO,GAC1C,EAAI,aAAa,mBAAmB,OAAO,GAC3C,EAAI,aAAa,eAAe,MAAM,GACtC,EAAI,aAAa,aAAa,OAAO,GAErC,EAAI,YAAY,GACT;AACT"}