pdfjs-reader-core 0.2.17 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -140,8 +140,8 @@ async function loadDocument(options) {
140
140
  signal.addEventListener("abort", abortHandler);
141
141
  }
142
142
  if (onProgress) {
143
- loadingTask.onProgress = ({ loaded, total }) => {
144
- onProgress({ loaded, total });
143
+ loadingTask.onProgress = ({ loaded: loaded2, total }) => {
144
+ onProgress({ loaded: loaded2, total });
145
145
  };
146
146
  }
147
147
  let document2;
@@ -271,9 +271,9 @@ function loadDocumentWithCallbacks(options) {
271
271
  };
272
272
  abortController.signal.addEventListener("abort", abortHandler);
273
273
  if (onProgress) {
274
- loadingTask.onProgress = ({ loaded, total }) => {
274
+ loadingTask.onProgress = ({ loaded: loaded2, total }) => {
275
275
  if (!abortController.signal.aborted) {
276
- onProgress({ loaded, total });
276
+ onProgress({ loaded: loaded2, total });
277
277
  }
278
278
  };
279
279
  }
@@ -627,13 +627,13 @@ function createViewerStore(initialOverrides = {}) {
627
627
  },
628
628
  zoomIn: () => {
629
629
  const { scale } = get();
630
- const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= scale);
630
+ const currentIndex = ZOOM_LEVELS.findIndex((z2) => z2 >= scale);
631
631
  const nextIndex = Math.min(currentIndex + 1, ZOOM_LEVELS.length - 1);
632
632
  set({ scale: ZOOM_LEVELS[nextIndex] ?? MAX_SCALE });
633
633
  },
634
634
  zoomOut: () => {
635
635
  const { scale } = get();
636
- const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= scale);
636
+ const currentIndex = ZOOM_LEVELS.findIndex((z2) => z2 >= scale);
637
637
  const prevIndex = Math.max(currentIndex - 1, 0);
638
638
  set({ scale: ZOOM_LEVELS[prevIndex] ?? MIN_SCALE });
639
639
  },
@@ -1784,6 +1784,113 @@ var init_coordinates = __esm({
1784
1784
  }
1785
1785
  });
1786
1786
 
1787
+ // src/utils/page-turn-sound.ts
1788
+ function getAudioContext() {
1789
+ if (typeof window === "undefined") return null;
1790
+ if (!audioContext) {
1791
+ try {
1792
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
1793
+ } catch {
1794
+ return null;
1795
+ }
1796
+ }
1797
+ return audioContext;
1798
+ }
1799
+ function playPageTurnSound(volume = 0.3) {
1800
+ const ctx = getAudioContext();
1801
+ if (!ctx) return;
1802
+ if (ctx.state === "suspended") {
1803
+ ctx.resume();
1804
+ }
1805
+ const now = ctx.currentTime;
1806
+ const duration = 0.35;
1807
+ const bufferSize = Math.floor(ctx.sampleRate * duration);
1808
+ const noiseBuffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
1809
+ const data = noiseBuffer.getChannelData(0);
1810
+ for (let i = 0; i < bufferSize; i++) {
1811
+ data[i] = Math.random() * 2 - 1;
1812
+ }
1813
+ const noiseSource = ctx.createBufferSource();
1814
+ noiseSource.buffer = noiseBuffer;
1815
+ const bandpass = ctx.createBiquadFilter();
1816
+ bandpass.type = "bandpass";
1817
+ bandpass.frequency.setValueAtTime(3e3, now);
1818
+ bandpass.frequency.exponentialRampToValueAtTime(800, now + duration * 0.6);
1819
+ bandpass.Q.setValueAtTime(0.8, now);
1820
+ const highpass = ctx.createBiquadFilter();
1821
+ highpass.type = "highpass";
1822
+ highpass.frequency.setValueAtTime(400, now);
1823
+ highpass.frequency.linearRampToValueAtTime(200, now + duration);
1824
+ const envelope = ctx.createGain();
1825
+ envelope.gain.setValueAtTime(0, now);
1826
+ envelope.gain.linearRampToValueAtTime(volume * 0.6, now + 0.02);
1827
+ envelope.gain.setValueAtTime(volume * 0.6, now + 0.05);
1828
+ envelope.gain.linearRampToValueAtTime(volume, now + duration * 0.3);
1829
+ envelope.gain.exponentialRampToValueAtTime(1e-3, now + duration);
1830
+ const snapBuffer = ctx.createBuffer(1, Math.floor(ctx.sampleRate * 0.08), ctx.sampleRate);
1831
+ const snapData = snapBuffer.getChannelData(0);
1832
+ for (let i = 0; i < snapData.length; i++) {
1833
+ snapData[i] = Math.random() * 2 - 1;
1834
+ }
1835
+ const snapSource = ctx.createBufferSource();
1836
+ snapSource.buffer = snapBuffer;
1837
+ const snapFilter = ctx.createBiquadFilter();
1838
+ snapFilter.type = "bandpass";
1839
+ snapFilter.frequency.setValueAtTime(2e3, now);
1840
+ snapFilter.Q.setValueAtTime(1.5, now);
1841
+ const snapEnvelope = ctx.createGain();
1842
+ snapEnvelope.gain.setValueAtTime(0, now);
1843
+ snapEnvelope.gain.setValueAtTime(0, now + duration * 0.7);
1844
+ snapEnvelope.gain.linearRampToValueAtTime(volume * 0.8, now + duration * 0.75);
1845
+ snapEnvelope.gain.exponentialRampToValueAtTime(1e-3, now + duration);
1846
+ noiseSource.connect(bandpass);
1847
+ bandpass.connect(highpass);
1848
+ highpass.connect(envelope);
1849
+ envelope.connect(ctx.destination);
1850
+ snapSource.connect(snapFilter);
1851
+ snapFilter.connect(snapEnvelope);
1852
+ snapEnvelope.connect(ctx.destination);
1853
+ noiseSource.start(now);
1854
+ noiseSource.stop(now + duration);
1855
+ snapSource.start(now);
1856
+ snapSource.stop(now + duration);
1857
+ }
1858
+ var audioContext;
1859
+ var init_page_turn_sound = __esm({
1860
+ "src/utils/page-turn-sound.ts"() {
1861
+ "use strict";
1862
+ audioContext = null;
1863
+ }
1864
+ });
1865
+
1866
+ // src/utils/camera-math.ts
1867
+ function fitPageScale(page, viewport) {
1868
+ const sx = viewport.width / page.width;
1869
+ const sy = viewport.height / page.height;
1870
+ return Math.min(sx, sy);
1871
+ }
1872
+ function computeCameraForBlock(bbox, page, viewport, opts = {}) {
1873
+ const targetScale = opts.targetScale ?? 1.5;
1874
+ const paddingPdf = opts.paddingPdf ?? 80;
1875
+ const [x1, y1, x2, y2] = bbox;
1876
+ const blockW = Math.max(1, x2 - x1 + paddingPdf * 2);
1877
+ const blockH = Math.max(1, y2 - y1 + paddingPdf * 2);
1878
+ const blockCX = (x1 + x2) / 2;
1879
+ const blockCY = (y1 + y2) / 2;
1880
+ const fitBlock = Math.min(viewport.width / blockW, viewport.height / blockH);
1881
+ const scale = fitBlock * targetScale;
1882
+ const pageCX = page.width / 2;
1883
+ const pageCY = page.height / 2;
1884
+ const x = (pageCX - blockCX) * scale;
1885
+ const y = (pageCY - blockCY) * scale;
1886
+ return { scale, x, y };
1887
+ }
1888
+ var init_camera_math = __esm({
1889
+ "src/utils/camera-math.ts"() {
1890
+ "use strict";
1891
+ }
1892
+ });
1893
+
1787
1894
  // src/utils/index.ts
1788
1895
  var init_utils = __esm({
1789
1896
  "src/utils/index.ts"() {
@@ -1798,6 +1905,7 @@ var init_utils = __esm({
1798
1905
  init_agent_api();
1799
1906
  init_text_search();
1800
1907
  init_coordinates();
1908
+ init_page_turn_sound();
1801
1909
  }
1802
1910
  });
1803
1911
 
@@ -2201,6 +2309,78 @@ var init_student_store = __esm({
2201
2309
  }
2202
2310
  });
2203
2311
 
2312
+ // src/store/narration-store.ts
2313
+ function createNarrationStore(overrides = {}) {
2314
+ return (0, import_vanilla6.createStore)()((set) => ({
2315
+ ...initialState6,
2316
+ ...overrides,
2317
+ setCurrentChunk: (chunk) => set({ currentChunk: chunk }),
2318
+ setCurrentPage: (page) => set({ currentPage: page }),
2319
+ pushChunkHistory: (entry) => set((state) => ({
2320
+ chunkHistory: [
2321
+ ...state.chunkHistory.slice(-(MAX_HISTORY - 1)),
2322
+ entry
2323
+ ]
2324
+ })),
2325
+ setCamera: (camera) => set((state) => ({ camera: { ...state.camera, ...camera } })),
2326
+ addOverlay: (overlay) => set((state) => ({ activeOverlays: [...state.activeOverlays, overlay] })),
2327
+ removeOverlay: (id) => set((state) => ({
2328
+ activeOverlays: state.activeOverlays.filter((o) => o.id !== id)
2329
+ })),
2330
+ clearOverlays: (predicate) => set((state) => ({
2331
+ activeOverlays: predicate ? state.activeOverlays.filter((o) => !predicate(o)) : []
2332
+ })),
2333
+ setEngineStatus: (s) => set({ engineStatus: s }),
2334
+ setLlmStatus: (s, error = null) => set({ llmStatus: s, lastError: error }),
2335
+ setLastStoryboard: (sb) => set({ lastStoryboard: sb }),
2336
+ setPaused: (paused) => set({ isPaused: paused }),
2337
+ appendDebugEvent: (event) => set((state) => {
2338
+ debugEventCounter += 1;
2339
+ const next = {
2340
+ ...event,
2341
+ id: `dbg-${debugEventCounter}`,
2342
+ timestamp: Date.now()
2343
+ };
2344
+ return {
2345
+ debugEvents: [
2346
+ ...state.debugEvents.slice(-(MAX_DEBUG_EVENTS - 1)),
2347
+ next
2348
+ ]
2349
+ };
2350
+ }),
2351
+ clearDebugEvents: () => set({ debugEvents: [] }),
2352
+ reset: () => set(initialState6)
2353
+ }));
2354
+ }
2355
+ function makeOverlayId(action) {
2356
+ overlayIdCounter += 1;
2357
+ return `ov-${action.type}-${overlayIdCounter}-${Date.now()}`;
2358
+ }
2359
+ var import_vanilla6, MAX_HISTORY, initialState6, MAX_DEBUG_EVENTS, debugEventCounter, overlayIdCounter;
2360
+ var init_narration_store = __esm({
2361
+ "src/store/narration-store.ts"() {
2362
+ "use strict";
2363
+ import_vanilla6 = require("zustand/vanilla");
2364
+ MAX_HISTORY = 5;
2365
+ initialState6 = {
2366
+ currentChunk: null,
2367
+ currentPage: 1,
2368
+ chunkHistory: [],
2369
+ camera: { scale: 1, x: 0, y: 0, easing: "ease-in-out" },
2370
+ activeOverlays: [],
2371
+ engineStatus: "idle",
2372
+ llmStatus: "idle",
2373
+ lastStoryboard: null,
2374
+ lastError: null,
2375
+ isPaused: false,
2376
+ debugEvents: []
2377
+ };
2378
+ MAX_DEBUG_EVENTS = 50;
2379
+ debugEventCounter = 0;
2380
+ overlayIdCounter = 0;
2381
+ }
2382
+ });
2383
+
2204
2384
  // src/store/index.ts
2205
2385
  var init_store = __esm({
2206
2386
  "src/store/index.ts"() {
@@ -2210,13 +2390,14 @@ var init_store = __esm({
2210
2390
  init_search_store();
2211
2391
  init_agent_store();
2212
2392
  init_student_store();
2393
+ init_narration_store();
2213
2394
  }
2214
2395
  });
2215
2396
 
2216
2397
  // src/hooks/PDFViewerContext.tsx
2217
2398
  function PDFViewerProvider({
2218
2399
  children,
2219
- initialState: initialState6,
2400
+ initialState: initialState7,
2220
2401
  theme = "light",
2221
2402
  defaultSidebarPanel = "thumbnails",
2222
2403
  studentMode: _studentMode = false
@@ -2228,22 +2409,22 @@ function PDFViewerProvider({
2228
2409
  const studentStoreRef = (0, import_react2.useRef)(null);
2229
2410
  if (!viewerStoreRef.current) {
2230
2411
  viewerStoreRef.current = createViewerStore({
2231
- ...initialState6?.viewer,
2412
+ ...initialState7?.viewer,
2232
2413
  theme,
2233
2414
  sidebarPanel: defaultSidebarPanel
2234
2415
  });
2235
2416
  }
2236
2417
  if (!annotationStoreRef.current) {
2237
- annotationStoreRef.current = createAnnotationStore(initialState6?.annotation);
2418
+ annotationStoreRef.current = createAnnotationStore(initialState7?.annotation);
2238
2419
  }
2239
2420
  if (!searchStoreRef.current) {
2240
- searchStoreRef.current = createSearchStore(initialState6?.search);
2421
+ searchStoreRef.current = createSearchStore(initialState7?.search);
2241
2422
  }
2242
2423
  if (!agentStoreRef.current) {
2243
- agentStoreRef.current = createAgentStore(initialState6?.agent);
2424
+ agentStoreRef.current = createAgentStore(initialState7?.agent);
2244
2425
  }
2245
2426
  if (!studentStoreRef.current) {
2246
- studentStoreRef.current = createStudentStore(initialState6?.student);
2427
+ studentStoreRef.current = createStudentStore(initialState7?.student);
2247
2428
  }
2248
2429
  (0, import_react2.useEffect)(() => {
2249
2430
  return () => {
@@ -3567,8 +3748,8 @@ var init_PluginManager = __esm({
3567
3748
  /**
3568
3749
  * Get toolbar items by position
3569
3750
  */
3570
- getToolbarItemsByPosition(position) {
3571
- return this.getToolbarItems().filter((item) => item.position === position);
3751
+ getToolbarItemsByPosition(position2) {
3752
+ return this.getToolbarItems().filter((item) => item.position === position2);
3572
3753
  }
3573
3754
  /**
3574
3755
  * Get all sidebar panels from all plugins
@@ -4681,7 +4862,7 @@ var init_MobileToolbar = __esm({
4681
4862
  sidebarOpen,
4682
4863
  theme,
4683
4864
  onThemeChange,
4684
- position = "bottom",
4865
+ position: position2 = "bottom",
4685
4866
  className
4686
4867
  }) {
4687
4868
  const [showMoreMenu, setShowMoreMenu] = (0, import_react17.useState)(false);
@@ -4715,8 +4896,8 @@ var init_MobileToolbar = __esm({
4715
4896
  "bg-white dark:bg-gray-800",
4716
4897
  "border-gray-200 dark:border-gray-700",
4717
4898
  "px-2 py-1 safe-area-inset",
4718
- position === "top" && "top-0 border-b",
4719
- position === "bottom" && "bottom-0 border-t",
4899
+ position2 === "top" && "top-0 border-b",
4900
+ position2 === "bottom" && "bottom-0 border-t",
4720
4901
  className
4721
4902
  ),
4722
4903
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between gap-1", children: [
@@ -4842,7 +5023,7 @@ var init_MobileToolbar = __esm({
4842
5023
  "bg-white dark:bg-gray-800",
4843
5024
  "rounded-lg shadow-lg",
4844
5025
  "border border-gray-200 dark:border-gray-700",
4845
- position === "bottom" ? "bottom-full mb-2" : "top-full mt-2"
5026
+ position2 === "bottom" ? "bottom-full mb-2" : "top-full mt-2"
4846
5027
  ),
4847
5028
  children: [
4848
5029
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-2 py-1 text-xs text-gray-500 dark:text-gray-400 font-medium", children: "Theme" }),
@@ -6668,7 +6849,7 @@ var init_AnnotationToolbar = __esm({
6668
6849
  onShapeTypeChange: onShapeTypeChangeProp,
6669
6850
  onColorChange: onColorChangeProp,
6670
6851
  onStrokeWidthChange: onStrokeWidthChangeProp,
6671
- position = "top",
6852
+ position: position2 = "top",
6672
6853
  className
6673
6854
  }) {
6674
6855
  const storeActiveTool = useAnnotationStore((s) => s.activeAnnotationTool);
@@ -6718,9 +6899,9 @@ var init_AnnotationToolbar = __esm({
6718
6899
  {
6719
6900
  className: cn(
6720
6901
  "annotation-toolbar flex items-center gap-1 p-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700",
6721
- position === "floating" && "fixed bottom-20 left-1/2 -translate-x-1/2 z-50",
6722
- position === "top" && "sticky top-0 z-40",
6723
- position === "bottom" && "sticky bottom-0 z-40",
6902
+ position2 === "floating" && "fixed bottom-20 left-1/2 -translate-x-1/2 z-50",
6903
+ position2 === "top" && "sticky top-0 z-40",
6904
+ position2 === "bottom" && "sticky bottom-0 z-40",
6724
6905
  !isActive && "opacity-90",
6725
6906
  className
6726
6907
  ),
@@ -8369,7 +8550,7 @@ var init_SelectionToolbar = __esm({
8369
8550
  activeColor = "yellow",
8370
8551
  className
8371
8552
  }) {
8372
- const [position, setPosition] = (0, import_react35.useState)({ top: 0, left: 0, visible: false });
8553
+ const [position2, setPosition] = (0, import_react35.useState)({ top: 0, left: 0, visible: false });
8373
8554
  const toolbarRef = (0, import_react35.useRef)(null);
8374
8555
  (0, import_react35.useEffect)(() => {
8375
8556
  if (selection && selection.text && selection.rects.length > 0) {
@@ -8408,7 +8589,7 @@ var init_SelectionToolbar = __esm({
8408
8589
  const handleCopy = (0, import_react35.useCallback)(() => {
8409
8590
  onCopy?.();
8410
8591
  }, [onCopy]);
8411
- if (!position.visible || !selection?.text) {
8592
+ if (!position2.visible || !selection?.text) {
8412
8593
  return null;
8413
8594
  }
8414
8595
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
@@ -8426,8 +8607,8 @@ var init_SelectionToolbar = __esm({
8426
8607
  className
8427
8608
  ),
8428
8609
  style: {
8429
- top: position.top,
8430
- left: position.left,
8610
+ top: position2.top,
8611
+ left: position2.left,
8431
8612
  transform: "translateX(-50%)"
8432
8613
  },
8433
8614
  onMouseDown: (e) => {
@@ -8544,7 +8725,7 @@ var init_HighlightPopover = __esm({
8544
8725
  }) {
8545
8726
  const [isEditingComment, setIsEditingComment] = (0, import_react36.useState)(false);
8546
8727
  const [comment, setComment] = (0, import_react36.useState)(highlight?.comment ?? "");
8547
- const [position, setPosition] = (0, import_react36.useState)({ top: 0, left: 0, visible: false });
8728
+ const [position2, setPosition] = (0, import_react36.useState)({ top: 0, left: 0, visible: false });
8548
8729
  const popoverRef = (0, import_react36.useRef)(null);
8549
8730
  const textareaRef = (0, import_react36.useRef)(null);
8550
8731
  (0, import_react36.useEffect)(() => {
@@ -8592,11 +8773,11 @@ var init_HighlightPopover = __esm({
8592
8773
  onClose();
8593
8774
  }
8594
8775
  }
8595
- if (position.visible) {
8776
+ if (position2.visible) {
8596
8777
  document.addEventListener("mousedown", handleClickOutside);
8597
8778
  return () => document.removeEventListener("mousedown", handleClickOutside);
8598
8779
  }
8599
- }, [position.visible, onClose]);
8780
+ }, [position2.visible, onClose]);
8600
8781
  (0, import_react36.useEffect)(() => {
8601
8782
  function handleKeyDown(event) {
8602
8783
  if (event.key === "Escape") {
@@ -8608,11 +8789,11 @@ var init_HighlightPopover = __esm({
8608
8789
  }
8609
8790
  }
8610
8791
  }
8611
- if (position.visible) {
8792
+ if (position2.visible) {
8612
8793
  document.addEventListener("keydown", handleKeyDown);
8613
8794
  return () => document.removeEventListener("keydown", handleKeyDown);
8614
8795
  }
8615
- }, [position.visible, isEditingComment, highlight?.comment, onClose]);
8796
+ }, [position2.visible, isEditingComment, highlight?.comment, onClose]);
8616
8797
  const handleColorClick = (0, import_react36.useCallback)(
8617
8798
  (color) => {
8618
8799
  if (highlight) {
@@ -8639,7 +8820,7 @@ var init_HighlightPopover = __esm({
8639
8820
  onCopy?.(highlight.text);
8640
8821
  }
8641
8822
  }, [highlight, onCopy]);
8642
- if (!highlight || !position.visible) {
8823
+ if (!highlight || !position2.visible) {
8643
8824
  return null;
8644
8825
  }
8645
8826
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
@@ -8656,8 +8837,8 @@ var init_HighlightPopover = __esm({
8656
8837
  className
8657
8838
  ),
8658
8839
  style: {
8659
- top: position.top,
8660
- left: position.left,
8840
+ top: position2.top,
8841
+ left: position2.left,
8661
8842
  transform: "translate(-50%, -100%)",
8662
8843
  width: 280
8663
8844
  },
@@ -9839,17 +10020,237 @@ var init_DualPageContainer = __esm({
9839
10020
  }
9840
10021
  });
9841
10022
 
10023
+ // src/components/PDFViewer/BookModeContainer.tsx
10024
+ var import_react41, import_react_pageflip, import_jsx_runtime27, BookPage, BookModeContainer;
10025
+ var init_BookModeContainer = __esm({
10026
+ "src/components/PDFViewer/BookModeContainer.tsx"() {
10027
+ "use strict";
10028
+ import_react41 = __toESM(require("react"), 1);
10029
+ import_react_pageflip = __toESM(require("react-pageflip"), 1);
10030
+ init_PDFPage2();
10031
+ init_PDFLoadingScreen2();
10032
+ init_hooks();
10033
+ init_utils();
10034
+ import_jsx_runtime27 = require("react/jsx-runtime");
10035
+ BookPage = import_react41.default.forwardRef(function BookPage2({ pageNumber, page, scale, rotation, width, height }, ref) {
10036
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { ref, className: "book-page", "data-page-number": pageNumber, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { style: { width, height, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10037
+ PDFPage,
10038
+ {
10039
+ pageNumber,
10040
+ page,
10041
+ scale,
10042
+ rotation,
10043
+ showTextLayer: false,
10044
+ showAnnotationLayer: false,
10045
+ showHighlightLayer: false
10046
+ }
10047
+ ) }) });
10048
+ });
10049
+ BookModeContainer = (0, import_react41.memo)(function BookModeContainer2({
10050
+ className,
10051
+ flippingTime = 800,
10052
+ drawShadow = true,
10053
+ maxShadowOpacity = 0.7
10054
+ }) {
10055
+ const {
10056
+ document: document2,
10057
+ currentPage,
10058
+ numPages,
10059
+ scale,
10060
+ rotation,
10061
+ theme,
10062
+ isLoading,
10063
+ goToPage
10064
+ } = usePDFViewer();
10065
+ const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
10066
+ const { viewerStore } = usePDFViewerStores();
10067
+ const [pages, setPages] = (0, import_react41.useState)([]);
10068
+ const [rawPageDims, setRawPageDims] = (0, import_react41.useState)({ width: 612, height: 792 });
10069
+ const [isLoadingPages, setIsLoadingPages] = (0, import_react41.useState)(false);
10070
+ const containerRef = (0, import_react41.useRef)(null);
10071
+ const [containerSize, setContainerSize] = (0, import_react41.useState)({ width: 0, height: 0 });
10072
+ const flipBookRef = (0, import_react41.useRef)(null);
10073
+ const isSyncingRef = (0, import_react41.useRef)(false);
10074
+ (0, import_react41.useEffect)(() => {
10075
+ const el = containerRef.current;
10076
+ if (!el) return;
10077
+ const measure = () => {
10078
+ setContainerSize({ width: el.clientWidth, height: el.clientHeight });
10079
+ };
10080
+ measure();
10081
+ const ro = new ResizeObserver(measure);
10082
+ ro.observe(el);
10083
+ return () => ro.disconnect();
10084
+ }, []);
10085
+ (0, import_react41.useEffect)(() => {
10086
+ if (!document2) {
10087
+ setPages([]);
10088
+ return;
10089
+ }
10090
+ let cancelled = false;
10091
+ const loadAllPages = async () => {
10092
+ setIsLoadingPages(true);
10093
+ try {
10094
+ const pagePromises = [];
10095
+ for (let i = 1; i <= numPages; i++) {
10096
+ pagePromises.push(document2.getPage(i));
10097
+ }
10098
+ const results = await Promise.allSettled(pagePromises);
10099
+ if (!cancelled) {
10100
+ const loaded2 = results.map((r) => r.status === "fulfilled" ? r.value : null);
10101
+ setPages(loaded2);
10102
+ const firstPage = loaded2[0];
10103
+ if (firstPage) {
10104
+ const vp = firstPage.getViewport({ scale: 1, rotation });
10105
+ setRawPageDims({ width: vp.width, height: vp.height });
10106
+ }
10107
+ }
10108
+ } catch {
10109
+ } finally {
10110
+ if (!cancelled) setIsLoadingPages(false);
10111
+ }
10112
+ };
10113
+ loadAllPages();
10114
+ return () => {
10115
+ cancelled = true;
10116
+ };
10117
+ }, [document2, numPages, rotation]);
10118
+ (0, import_react41.useEffect)(() => {
10119
+ if (pages[0]) {
10120
+ const vp = pages[0].getViewport({ scale: 1, rotation });
10121
+ setRawPageDims({ width: vp.width, height: vp.height });
10122
+ }
10123
+ }, [pages, rotation]);
10124
+ const padding = 8;
10125
+ const fitWidth = Math.max(containerSize.width - padding * 2, 200);
10126
+ const fitHeight = Math.max(containerSize.height - padding * 2, 300);
10127
+ const pageAspect = rawPageDims.width / rawPageDims.height;
10128
+ let displayWidth;
10129
+ let displayHeight;
10130
+ if (fitWidth / fitHeight > pageAspect) {
10131
+ displayHeight = fitHeight;
10132
+ displayWidth = Math.floor(fitHeight * pageAspect);
10133
+ } else {
10134
+ displayWidth = fitWidth;
10135
+ displayHeight = Math.floor(fitWidth / pageAspect);
10136
+ }
10137
+ const renderScale = displayWidth / rawPageDims.width;
10138
+ (0, import_react41.useEffect)(() => {
10139
+ const pageFlip = flipBookRef.current?.pageFlip();
10140
+ if (!pageFlip) return;
10141
+ const flipBookPage = pageFlip.getCurrentPageIndex();
10142
+ const targetIndex = currentPage - 1;
10143
+ if (flipBookPage !== targetIndex) {
10144
+ isSyncingRef.current = true;
10145
+ pageFlip.turnToPage(targetIndex);
10146
+ setTimeout(() => {
10147
+ isSyncingRef.current = false;
10148
+ }, 100);
10149
+ }
10150
+ }, [currentPage]);
10151
+ (0, import_react41.useEffect)(() => {
10152
+ if (scrollToPageRequest) {
10153
+ requestAnimationFrame(() => {
10154
+ viewerStore.getState().completeScrollRequest(scrollToPageRequest.requestId);
10155
+ });
10156
+ }
10157
+ }, [scrollToPageRequest, viewerStore]);
10158
+ const handleFlip = (0, import_react41.useCallback)((e) => {
10159
+ if (isSyncingRef.current) return;
10160
+ const newPage = e.data + 1;
10161
+ if (newPage !== currentPage && newPage >= 1 && newPage <= numPages) {
10162
+ goToPage(newPage);
10163
+ }
10164
+ }, [currentPage, numPages, goToPage]);
10165
+ const themeStyles = {
10166
+ light: "bg-gray-100",
10167
+ dark: "bg-gray-900",
10168
+ sepia: "bg-amber-50"
10169
+ };
10170
+ const themeClass = theme === "dark" ? "dark" : theme === "sepia" ? "sepia" : "";
10171
+ const ready = !!document2 && !isLoadingPages && pages.length > 0;
10172
+ const hasContainer = containerSize.width > 0 && containerSize.height > 0;
10173
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10174
+ "div",
10175
+ {
10176
+ ref: containerRef,
10177
+ className: cn(
10178
+ "book-mode-container",
10179
+ "flex-1 h-full w-full overflow-hidden",
10180
+ "flex items-center justify-center",
10181
+ themeStyles[theme],
10182
+ themeClass,
10183
+ className
10184
+ ),
10185
+ style: { userSelect: "none", WebkitUserSelect: "none" },
10186
+ children: [
10187
+ !ready && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10188
+ PDFLoadingScreen,
10189
+ {
10190
+ phase: !document2 ? isLoading ? "fetching" : "initializing" : "rendering"
10191
+ }
10192
+ ),
10193
+ ready && hasContainer && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10194
+ import_react_pageflip.default,
10195
+ {
10196
+ ref: flipBookRef,
10197
+ width: displayWidth,
10198
+ height: displayHeight,
10199
+ size: "fixed",
10200
+ minWidth: displayWidth,
10201
+ maxWidth: displayWidth,
10202
+ minHeight: displayHeight,
10203
+ maxHeight: displayHeight,
10204
+ drawShadow,
10205
+ maxShadowOpacity,
10206
+ flippingTime,
10207
+ usePortrait: true,
10208
+ startPage: currentPage - 1,
10209
+ showCover: false,
10210
+ mobileScrollSupport: true,
10211
+ swipeDistance: 30,
10212
+ showPageCorners: true,
10213
+ useMouseEvents: true,
10214
+ clickEventForward: false,
10215
+ onFlip: handleFlip,
10216
+ className: "book-flipbook",
10217
+ style: {},
10218
+ startZIndex: 0,
10219
+ autoSize: false,
10220
+ renderOnlyPageLengthChange: false,
10221
+ disableFlipByClick: false,
10222
+ children: pages.map((page, index) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10223
+ BookPage,
10224
+ {
10225
+ pageNumber: index + 1,
10226
+ page,
10227
+ scale: renderScale,
10228
+ rotation,
10229
+ width: displayWidth,
10230
+ height: displayHeight
10231
+ },
10232
+ index
10233
+ ))
10234
+ }
10235
+ )
10236
+ ]
10237
+ }
10238
+ );
10239
+ });
10240
+ }
10241
+ });
10242
+
9842
10243
  // src/components/FloatingZoomControls/FloatingZoomControls.tsx
9843
- var import_react41, import_jsx_runtime27, FloatingZoomControls;
10244
+ var import_react42, import_jsx_runtime28, FloatingZoomControls;
9844
10245
  var init_FloatingZoomControls = __esm({
9845
10246
  "src/components/FloatingZoomControls/FloatingZoomControls.tsx"() {
9846
10247
  "use strict";
9847
- import_react41 = require("react");
10248
+ import_react42 = require("react");
9848
10249
  init_hooks();
9849
10250
  init_utils();
9850
- import_jsx_runtime27 = require("react/jsx-runtime");
9851
- FloatingZoomControls = (0, import_react41.memo)(function FloatingZoomControls2({
9852
- position = "bottom-right",
10251
+ import_jsx_runtime28 = require("react/jsx-runtime");
10252
+ FloatingZoomControls = (0, import_react42.memo)(function FloatingZoomControls2({
10253
+ position: position2 = "bottom-right",
9853
10254
  className,
9854
10255
  showFitToWidth = true,
9855
10256
  showFitToPage = false,
@@ -9858,20 +10259,20 @@ var init_FloatingZoomControls = __esm({
9858
10259
  const { viewerStore } = usePDFViewerStores();
9859
10260
  const scale = useViewerStore((s) => s.scale);
9860
10261
  const document2 = useViewerStore((s) => s.document);
9861
- const handleZoomIn = (0, import_react41.useCallback)(() => {
10262
+ const handleZoomIn = (0, import_react42.useCallback)(() => {
9862
10263
  const currentScale = viewerStore.getState().scale;
9863
10264
  const newScale = Math.min(4, currentScale + 0.05);
9864
10265
  viewerStore.getState().setScale(newScale);
9865
10266
  }, [viewerStore]);
9866
- const handleZoomOut = (0, import_react41.useCallback)(() => {
10267
+ const handleZoomOut = (0, import_react42.useCallback)(() => {
9867
10268
  const currentScale = viewerStore.getState().scale;
9868
10269
  const newScale = Math.max(0.1, currentScale - 0.05);
9869
10270
  viewerStore.getState().setScale(newScale);
9870
10271
  }, [viewerStore]);
9871
- const handleFitToWidth = (0, import_react41.useCallback)(() => {
10272
+ const handleFitToWidth = (0, import_react42.useCallback)(() => {
9872
10273
  viewerStore.getState().setScale(1);
9873
10274
  }, [viewerStore]);
9874
- const handleFitToPage = (0, import_react41.useCallback)(() => {
10275
+ const handleFitToPage = (0, import_react42.useCallback)(() => {
9875
10276
  viewerStore.getState().setScale(0.75);
9876
10277
  }, [viewerStore]);
9877
10278
  if (!document2) return null;
@@ -9882,7 +10283,7 @@ var init_FloatingZoomControls = __esm({
9882
10283
  "top-left": "top-4 left-4"
9883
10284
  };
9884
10285
  const zoomPercentage = Math.round(scale * 100);
9885
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10286
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
9886
10287
  "div",
9887
10288
  {
9888
10289
  className: cn(
@@ -9890,11 +10291,11 @@ var init_FloatingZoomControls = __esm({
9890
10291
  "bg-white dark:bg-gray-800 rounded-lg shadow-lg",
9891
10292
  "border border-gray-200 dark:border-gray-700",
9892
10293
  "p-1",
9893
- positionClasses[position],
10294
+ positionClasses[position2],
9894
10295
  className
9895
10296
  ),
9896
10297
  children: [
9897
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10298
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
9898
10299
  "button",
9899
10300
  {
9900
10301
  onClick: handleZoomOut,
@@ -9908,14 +10309,14 @@ var init_FloatingZoomControls = __esm({
9908
10309
  disabled: scale <= 0.25,
9909
10310
  title: "Zoom Out",
9910
10311
  "aria-label": "Zoom Out",
9911
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) })
10312
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M20 12H4" }) })
9912
10313
  }
9913
10314
  ),
9914
- showZoomLevel && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "min-w-[48px] text-center text-sm font-medium text-gray-700 dark:text-gray-300", children: [
10315
+ showZoomLevel && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "min-w-[48px] text-center text-sm font-medium text-gray-700 dark:text-gray-300", children: [
9915
10316
  zoomPercentage,
9916
10317
  "%"
9917
10318
  ] }),
9918
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10319
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
9919
10320
  "button",
9920
10321
  {
9921
10322
  onClick: handleZoomIn,
@@ -9929,11 +10330,11 @@ var init_FloatingZoomControls = __esm({
9929
10330
  disabled: scale >= 4,
9930
10331
  title: "Zoom In",
9931
10332
  "aria-label": "Zoom In",
9932
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 4v16m8-8H4" }) })
10333
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 4v16m8-8H4" }) })
9933
10334
  }
9934
10335
  ),
9935
- (showFitToWidth || showFitToPage) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1" }),
9936
- showFitToWidth && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10336
+ (showFitToWidth || showFitToPage) && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1" }),
10337
+ showFitToWidth && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
9937
10338
  "button",
9938
10339
  {
9939
10340
  onClick: handleFitToWidth,
@@ -9945,10 +10346,10 @@ var init_FloatingZoomControls = __esm({
9945
10346
  ),
9946
10347
  title: "Fit to Width",
9947
10348
  "aria-label": "Fit to Width",
9948
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" }) })
10349
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" }) })
9949
10350
  }
9950
10351
  ),
9951
- showFitToPage && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10352
+ showFitToPage && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
9952
10353
  "button",
9953
10354
  {
9954
10355
  onClick: handleFitToPage,
@@ -9960,7 +10361,7 @@ var init_FloatingZoomControls = __esm({
9960
10361
  ),
9961
10362
  title: "Fit to Page",
9962
10363
  "aria-label": "Fit to Page",
9963
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) })
10364
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) })
9964
10365
  }
9965
10366
  )
9966
10367
  ]
@@ -10022,11 +10423,11 @@ function calculateMatchRects3(textItems, startOffset, length, viewport) {
10022
10423
  }
10023
10424
  return rects;
10024
10425
  }
10025
- var import_react42, import_jsx_runtime28, PDFViewerInner, PDFViewerInnerWithRef, PDFViewerClient;
10426
+ var import_react43, import_jsx_runtime29, PDFViewerInner, PDFViewerInnerWithRef, PDFViewerClient;
10026
10427
  var init_PDFViewerClient = __esm({
10027
10428
  "src/components/PDFViewer/PDFViewerClient.tsx"() {
10028
10429
  "use strict";
10029
- import_react42 = require("react");
10430
+ import_react43 = require("react");
10030
10431
  init_hooks();
10031
10432
  init_utils();
10032
10433
  init_Toolbar2();
@@ -10035,11 +10436,12 @@ var init_PDFViewerClient = __esm({
10035
10436
  init_DocumentContainer();
10036
10437
  init_ContinuousScrollContainer();
10037
10438
  init_DualPageContainer();
10439
+ init_BookModeContainer();
10038
10440
  init_FloatingZoomControls2();
10039
10441
  init_PDFLoadingScreen2();
10040
10442
  init_utils();
10041
- import_jsx_runtime28 = require("react/jsx-runtime");
10042
- PDFViewerInner = (0, import_react42.memo)(function PDFViewerInner2({
10443
+ import_jsx_runtime29 = require("react/jsx-runtime");
10444
+ PDFViewerInner = (0, import_react43.memo)(function PDFViewerInner2({
10043
10445
  src,
10044
10446
  initialPage = 1,
10045
10447
  page: controlledPage,
@@ -10066,19 +10468,19 @@ var init_PDFViewerClient = __esm({
10066
10468
  onReady
10067
10469
  }) {
10068
10470
  const { viewerStore, annotationStore, searchStore } = usePDFViewerStores();
10069
- const mountedRef = (0, import_react42.useRef)(true);
10070
- const [, setLoadState] = (0, import_react42.useState)("idle");
10071
- const onDocumentLoadRef = (0, import_react42.useRef)(onDocumentLoad);
10072
- const onErrorRef = (0, import_react42.useRef)(onError);
10073
- const onPageChangeRef = (0, import_react42.useRef)(onPageChange);
10074
- const onScaleChangeRef = (0, import_react42.useRef)(onScaleChange);
10075
- const onZoomChangeRef = (0, import_react42.useRef)(onZoomChange);
10076
- const onPageRenderStartRef = (0, import_react42.useRef)(onPageRenderStart);
10077
- const onPageRenderCompleteRef = (0, import_react42.useRef)(onPageRenderComplete);
10078
- const onHighlightAddedRef = (0, import_react42.useRef)(onHighlightAdded);
10079
- const onHighlightRemovedRef = (0, import_react42.useRef)(onHighlightRemoved);
10080
- const onAnnotationAddedRef = (0, import_react42.useRef)(onAnnotationAdded);
10081
- const onReadyRef = (0, import_react42.useRef)(onReady);
10471
+ const mountedRef = (0, import_react43.useRef)(true);
10472
+ const [, setLoadState] = (0, import_react43.useState)("idle");
10473
+ const onDocumentLoadRef = (0, import_react43.useRef)(onDocumentLoad);
10474
+ const onErrorRef = (0, import_react43.useRef)(onError);
10475
+ const onPageChangeRef = (0, import_react43.useRef)(onPageChange);
10476
+ const onScaleChangeRef = (0, import_react43.useRef)(onScaleChange);
10477
+ const onZoomChangeRef = (0, import_react43.useRef)(onZoomChange);
10478
+ const onPageRenderStartRef = (0, import_react43.useRef)(onPageRenderStart);
10479
+ const onPageRenderCompleteRef = (0, import_react43.useRef)(onPageRenderComplete);
10480
+ const onHighlightAddedRef = (0, import_react43.useRef)(onHighlightAdded);
10481
+ const onHighlightRemovedRef = (0, import_react43.useRef)(onHighlightRemoved);
10482
+ const onAnnotationAddedRef = (0, import_react43.useRef)(onAnnotationAdded);
10483
+ const onReadyRef = (0, import_react43.useRef)(onReady);
10082
10484
  onDocumentLoadRef.current = onDocumentLoad;
10083
10485
  onErrorRef.current = onError;
10084
10486
  onPageChangeRef.current = onPageChange;
@@ -10091,8 +10493,8 @@ var init_PDFViewerClient = __esm({
10091
10493
  onAnnotationAddedRef.current = onAnnotationAdded;
10092
10494
  onReadyRef.current = onReady;
10093
10495
  const isControlled = controlledPage !== void 0;
10094
- const prevControlledPageRef = (0, import_react42.useRef)(controlledPage);
10095
- const srcIdRef = (0, import_react42.useRef)(null);
10496
+ const prevControlledPageRef = (0, import_react43.useRef)(controlledPage);
10497
+ const srcIdRef = (0, import_react43.useRef)(null);
10096
10498
  const currentPage = useViewerStore((s) => s.currentPage);
10097
10499
  const scale = useViewerStore((s) => s.scale);
10098
10500
  const theme = useViewerStore((s) => s.theme);
@@ -10102,8 +10504,8 @@ var init_PDFViewerClient = __esm({
10102
10504
  const sidebarOpen = useViewerStore((s) => s.sidebarOpen);
10103
10505
  const streamingProgress = useViewerStore((s) => s.streamingProgress);
10104
10506
  const srcId = getSrcIdentifier(src);
10105
- const handleRef = (0, import_react42.useRef)(null);
10106
- (0, import_react42.useEffect)(() => {
10507
+ const handleRef = (0, import_react43.useRef)(null);
10508
+ (0, import_react43.useEffect)(() => {
10107
10509
  const handle = {
10108
10510
  // ==================== Text Highlighting ====================
10109
10511
  highlightText: async (text, options) => {
@@ -10521,14 +10923,14 @@ var init_PDFViewerClient = __esm({
10521
10923
  handleRef.current = handle;
10522
10924
  onReadyRef.current?.(handle);
10523
10925
  }, [viewerStore, annotationStore, searchStore]);
10524
- const handleRetry = (0, import_react42.useCallback)(() => {
10926
+ const handleRetry = (0, import_react43.useCallback)(() => {
10525
10927
  srcIdRef.current = null;
10526
10928
  viewerStore.getState().setError(null);
10527
10929
  setLoadState("idle");
10528
10930
  }, [viewerStore]);
10529
- const abortControllerRef = (0, import_react42.useRef)(null);
10530
- const currentSrcRef = (0, import_react42.useRef)(null);
10531
- (0, import_react42.useEffect)(() => {
10931
+ const abortControllerRef = (0, import_react43.useRef)(null);
10932
+ const currentSrcRef = (0, import_react43.useRef)(null);
10933
+ (0, import_react43.useEffect)(() => {
10532
10934
  mountedRef.current = true;
10533
10935
  return () => {
10534
10936
  mountedRef.current = false;
@@ -10553,8 +10955,8 @@ var init_PDFViewerClient = __esm({
10553
10955
  viewerStore.getState().setError(null);
10554
10956
  };
10555
10957
  }, [viewerStore]);
10556
- const cancelLoaderRef = (0, import_react42.useRef)(null);
10557
- (0, import_react42.useEffect)(() => {
10958
+ const cancelLoaderRef = (0, import_react43.useRef)(null);
10959
+ (0, import_react43.useEffect)(() => {
10558
10960
  if (srcIdRef.current === srcId && viewerStore.getState().document) {
10559
10961
  return;
10560
10962
  }
@@ -10595,12 +10997,12 @@ var init_PDFViewerClient = __esm({
10595
10997
  src,
10596
10998
  workerSrc,
10597
10999
  signal: abortController.signal,
10598
- onProgress: ({ loaded, total }) => {
11000
+ onProgress: ({ loaded: loaded2, total }) => {
10599
11001
  if (!mountedRef.current || srcIdRef.current !== loadId || abortController.signal.aborted) {
10600
11002
  return;
10601
11003
  }
10602
11004
  const now = Date.now();
10603
- const percent = total > 0 ? Math.round(loaded / total * 100) : 0;
11005
+ const percent = total > 0 ? Math.round(loaded2 / total * 100) : 0;
10604
11006
  const timePassed = now - lastProgressUpdate >= PROGRESS_THROTTLE_MS;
10605
11007
  const percentChanged = Math.abs(percent - lastPercent) >= PROGRESS_MIN_CHANGE;
10606
11008
  const isComplete = percent >= 100;
@@ -10611,10 +11013,10 @@ var init_PDFViewerClient = __esm({
10611
11013
  loadingProgress: {
10612
11014
  phase: "fetching",
10613
11015
  percent,
10614
- bytesLoaded: loaded,
11016
+ bytesLoaded: loaded2,
10615
11017
  totalBytes: total
10616
11018
  },
10617
- streamingProgress: { loaded, total },
11019
+ streamingProgress: { loaded: loaded2, total },
10618
11020
  documentLoadingState: "loading"
10619
11021
  });
10620
11022
  }
@@ -10688,22 +11090,22 @@ var init_PDFViewerClient = __esm({
10688
11090
  }
10689
11091
  };
10690
11092
  }, [srcId, src, workerSrc, initialPage, initialScale, viewerStore]);
10691
- const prevPageRef = (0, import_react42.useRef)(currentPage);
10692
- (0, import_react42.useEffect)(() => {
11093
+ const prevPageRef = (0, import_react43.useRef)(currentPage);
11094
+ (0, import_react43.useEffect)(() => {
10693
11095
  if (prevPageRef.current !== currentPage) {
10694
11096
  prevPageRef.current = currentPage;
10695
11097
  onPageChangeRef.current?.(currentPage);
10696
11098
  }
10697
11099
  }, [currentPage]);
10698
- const prevScaleRef = (0, import_react42.useRef)(scale);
10699
- (0, import_react42.useEffect)(() => {
11100
+ const prevScaleRef = (0, import_react43.useRef)(scale);
11101
+ (0, import_react43.useEffect)(() => {
10700
11102
  if (prevScaleRef.current !== scale) {
10701
11103
  prevScaleRef.current = scale;
10702
11104
  onScaleChangeRef.current?.(scale);
10703
11105
  onZoomChangeRef.current?.(scale);
10704
11106
  }
10705
11107
  }, [scale]);
10706
- (0, import_react42.useEffect)(() => {
11108
+ (0, import_react43.useEffect)(() => {
10707
11109
  if (!isControlled || controlledPage === void 0) return;
10708
11110
  if (prevControlledPageRef.current === controlledPage) return;
10709
11111
  prevControlledPageRef.current = controlledPage;
@@ -10716,7 +11118,7 @@ var init_PDFViewerClient = __esm({
10716
11118
  if (error) {
10717
11119
  if (errorComponent) {
10718
11120
  const errorContent = typeof errorComponent === "function" ? errorComponent(error, handleRetry) : errorComponent;
10719
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11121
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
10720
11122
  "div",
10721
11123
  {
10722
11124
  className: cn(
@@ -10730,7 +11132,7 @@ var init_PDFViewerClient = __esm({
10730
11132
  }
10731
11133
  );
10732
11134
  }
10733
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11135
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
10734
11136
  "div",
10735
11137
  {
10736
11138
  className: cn(
@@ -10740,10 +11142,10 @@ var init_PDFViewerClient = __esm({
10740
11142
  themeClass,
10741
11143
  className
10742
11144
  ),
10743
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-center p-8", children: [
10744
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-red-500 text-lg font-semibold mb-2", children: "Failed to load PDF" }),
10745
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-gray-500 text-sm", children: error.message }),
10746
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11145
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "text-center p-8", children: [
11146
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "text-red-500 text-lg font-semibold mb-2", children: "Failed to load PDF" }),
11147
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "text-gray-500 text-sm", children: error.message }),
11148
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
10747
11149
  "button",
10748
11150
  {
10749
11151
  onClick: handleRetry,
@@ -10758,15 +11160,17 @@ var init_PDFViewerClient = __esm({
10758
11160
  const renderContainer = () => {
10759
11161
  switch (viewMode) {
10760
11162
  case "continuous":
10761
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ContinuousScrollContainer, {});
11163
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(ContinuousScrollContainer, {});
10762
11164
  case "dual":
10763
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(DualPageContainer, {});
11165
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(DualPageContainer, {});
11166
+ case "book":
11167
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(BookModeContainer, {});
10764
11168
  case "single":
10765
11169
  default:
10766
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(DocumentContainer, {});
11170
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(DocumentContainer, {});
10767
11171
  }
10768
11172
  };
10769
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
11173
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
10770
11174
  "div",
10771
11175
  {
10772
11176
  className: cn(
@@ -10778,14 +11182,14 @@ var init_PDFViewerClient = __esm({
10778
11182
  className
10779
11183
  ),
10780
11184
  children: [
10781
- showToolbar && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Toolbar, {}),
10782
- showAnnotationToolbar && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AnnotationToolbar, {}),
10783
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-1 overflow-hidden", children: [
10784
- showSidebar && sidebarOpen && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Sidebar, {}),
11185
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Toolbar, {}),
11186
+ showAnnotationToolbar && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(AnnotationToolbar, {}),
11187
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-1 overflow-hidden", children: [
11188
+ showSidebar && sidebarOpen && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Sidebar, {}),
10785
11189
  renderContainer()
10786
11190
  ] }),
10787
- showFloatingZoom && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FloatingZoomControls, { position: "bottom-right" }),
10788
- isLoading && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "absolute inset-0 z-50", children: loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11191
+ showFloatingZoom && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(FloatingZoomControls, { position: "bottom-right" }),
11192
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "absolute inset-0 z-50", children: loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
10789
11193
  PDFLoadingScreen,
10790
11194
  {
10791
11195
  phase: loadingProgress?.phase ?? "fetching",
@@ -10794,10 +11198,10 @@ var init_PDFViewerClient = __esm({
10794
11198
  totalBytes: loadingProgress?.totalBytes
10795
11199
  }
10796
11200
  ) }),
10797
- !isLoading && streamingProgress && streamingProgress.total > 0 && streamingProgress.loaded < streamingProgress.total && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "absolute bottom-20 right-4 z-40 px-3 py-2 bg-gray-900/80 text-white text-xs rounded-lg shadow-lg flex items-center gap-2", children: [
10798
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "w-3 h-3 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
10799
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { children: "Loading pages..." }),
10800
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "text-white/60", children: [
11201
+ !isLoading && streamingProgress && streamingProgress.total > 0 && streamingProgress.loaded < streamingProgress.total && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "absolute bottom-20 right-4 z-40 px-3 py-2 bg-gray-900/80 text-white text-xs rounded-lg shadow-lg flex items-center gap-2", children: [
11202
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "w-3 h-3 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
11203
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { children: "Loading pages..." }),
11204
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "text-white/60", children: [
10801
11205
  Math.round(streamingProgress.loaded / streamingProgress.total * 100),
10802
11206
  "%"
10803
11207
  ] })
@@ -10806,10 +11210,10 @@ var init_PDFViewerClient = __esm({
10806
11210
  }
10807
11211
  );
10808
11212
  });
10809
- PDFViewerInnerWithRef = (0, import_react42.forwardRef)(
11213
+ PDFViewerInnerWithRef = (0, import_react43.forwardRef)(
10810
11214
  function PDFViewerInnerWithRef2(props, ref) {
10811
- const handleRef = (0, import_react42.useRef)(null);
10812
- const handleReady = (0, import_react42.useCallback)((handle) => {
11215
+ const handleRef = (0, import_react43.useRef)(null);
11216
+ const handleReady = (0, import_react43.useCallback)((handle) => {
10813
11217
  handleRef.current = handle;
10814
11218
  if (typeof ref === "function") {
10815
11219
  ref(handle);
@@ -10817,17 +11221,17 @@ var init_PDFViewerClient = __esm({
10817
11221
  ref.current = handle;
10818
11222
  }
10819
11223
  }, [ref]);
10820
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(PDFViewerInner, { ...props, onReady: handleReady });
11224
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(PDFViewerInner, { ...props, onReady: handleReady });
10821
11225
  }
10822
11226
  );
10823
- PDFViewerClient = (0, import_react42.memo)(
10824
- (0, import_react42.forwardRef)(function PDFViewerClient2(props, ref) {
10825
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
11227
+ PDFViewerClient = (0, import_react43.memo)(
11228
+ (0, import_react43.forwardRef)(function PDFViewerClient2(props, ref) {
11229
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
10826
11230
  PDFViewerProvider,
10827
11231
  {
10828
11232
  theme: props.theme,
10829
11233
  defaultSidebarPanel: props.defaultSidebarPanel,
10830
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(PDFViewerInnerWithRef, { ref, ...props })
11234
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(PDFViewerInnerWithRef, { ref, ...props })
10831
11235
  }
10832
11236
  );
10833
11237
  })
@@ -10836,20 +11240,20 @@ var init_PDFViewerClient = __esm({
10836
11240
  });
10837
11241
 
10838
11242
  // src/components/PDFViewer/PDFViewer.tsx
10839
- var import_react43, import_jsx_runtime29, PDFViewerClient3, PDFViewerLoading, PDFViewer;
11243
+ var import_react44, import_jsx_runtime30, PDFViewerClient3, PDFViewerLoading, PDFViewer;
10840
11244
  var init_PDFViewer = __esm({
10841
11245
  "src/components/PDFViewer/PDFViewer.tsx"() {
10842
11246
  "use strict";
10843
- import_react43 = require("react");
11247
+ import_react44 = require("react");
10844
11248
  init_utils();
10845
- import_jsx_runtime29 = require("react/jsx-runtime");
10846
- PDFViewerClient3 = (0, import_react43.lazy)(
11249
+ import_jsx_runtime30 = require("react/jsx-runtime");
11250
+ PDFViewerClient3 = (0, import_react44.lazy)(
10847
11251
  () => Promise.resolve().then(() => (init_PDFViewerClient(), PDFViewerClient_exports)).then((mod) => ({ default: mod.PDFViewerClient }))
10848
11252
  );
10849
- PDFViewerLoading = (0, import_react43.memo)(function PDFViewerLoading2({
11253
+ PDFViewerLoading = (0, import_react44.memo)(function PDFViewerLoading2({
10850
11254
  className
10851
11255
  }) {
10852
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
11256
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
10853
11257
  "div",
10854
11258
  {
10855
11259
  className: cn(
@@ -10858,18 +11262,18 @@ var init_PDFViewer = __esm({
10858
11262
  "bg-white dark:bg-gray-900",
10859
11263
  className
10860
11264
  ),
10861
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col items-center", children: [
10862
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" }),
10863
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "mt-2 text-sm text-gray-500", children: "Loading PDF viewer..." })
11265
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-col items-center", children: [
11266
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" }),
11267
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "mt-2 text-sm text-gray-500", children: "Loading PDF viewer..." })
10864
11268
  ] }) })
10865
11269
  }
10866
11270
  );
10867
11271
  });
10868
- PDFViewer = (0, import_react43.memo)(function PDFViewer2(props) {
11272
+ PDFViewer = (0, import_react44.memo)(function PDFViewer2(props) {
10869
11273
  if (typeof window === "undefined") {
10870
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(PDFViewerLoading, { className: props.className });
11274
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(PDFViewerLoading, { className: props.className });
10871
11275
  }
10872
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react43.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(PDFViewerLoading, { className: props.className }), children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(PDFViewerClient3, { ...props }) });
11276
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react44.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(PDFViewerLoading, { className: props.className }), children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(PDFViewerClient3, { ...props }) });
10873
11277
  });
10874
11278
  }
10875
11279
  });
@@ -10884,24 +11288,33 @@ var init_PDFViewer2 = __esm({
10884
11288
  init_VirtualizedDocumentContainer();
10885
11289
  init_ContinuousScrollContainer();
10886
11290
  init_DualPageContainer();
11291
+ init_BookModeContainer();
10887
11292
  }
10888
11293
  });
10889
11294
 
10890
11295
  // src/index.ts
10891
11296
  var index_exports = {};
10892
11297
  __export(index_exports, {
11298
+ AnimatedHighlight: () => AnimatedHighlight,
11299
+ AnimatedUnderline: () => AnimatedUnderline,
10893
11300
  AnnotationLayer: () => AnnotationLayer,
10894
11301
  AnnotationToolbar: () => AnnotationToolbar,
10895
11302
  AskAboutOverlay: () => AskAboutOverlay,
10896
11303
  AskAboutTrigger: () => AskAboutTrigger,
11304
+ BookModeContainer: () => BookModeContainer,
10897
11305
  BookmarksPanel: () => BookmarksPanel,
11306
+ BoxOverlay: () => BoxOverlay,
11307
+ CalloutArrow: () => CalloutArrow,
11308
+ CameraView: () => CameraView,
10898
11309
  CanvasLayer: () => CanvasLayer,
11310
+ CinemaLayer: () => CinemaLayer,
10899
11311
  ContinuousScrollContainer: () => ContinuousScrollContainer,
10900
11312
  DocumentContainer: () => DocumentContainer,
10901
11313
  DrawingCanvas: () => DrawingCanvas,
10902
11314
  DualPageContainer: () => DualPageContainer,
10903
11315
  FloatingZoomControls: () => FloatingZoomControls,
10904
11316
  FocusRegionLayer: () => FocusRegionLayer,
11317
+ GhostReference: () => GhostReference,
10905
11318
  HighlightLayer: () => HighlightLayer,
10906
11319
  HighlightPopover: () => HighlightPopover,
10907
11320
  HighlightsPanel: () => HighlightsPanel,
@@ -10918,32 +11331,46 @@ __export(index_exports, {
10918
11331
  PDFViewerContext: () => PDFViewerContext,
10919
11332
  PDFViewerProvider: () => PDFViewerProvider,
10920
11333
  PluginManager: () => PluginManager,
11334
+ PulseOverlay: () => PulseOverlay,
10921
11335
  QuickNoteButton: () => QuickNoteButton,
10922
11336
  QuickNotePopover: () => QuickNotePopover,
11337
+ SYSTEM_PROMPT: () => SYSTEM_PROMPT,
10923
11338
  SearchPanel: () => SearchPanel,
10924
11339
  SelectionToolbar: () => SelectionToolbar,
10925
11340
  ShapePreview: () => ShapePreview,
10926
11341
  ShapeRenderer: () => ShapeRenderer,
10927
11342
  Sidebar: () => Sidebar,
11343
+ SpotlightMask: () => SpotlightMask,
11344
+ StickyLabel: () => StickyLabel,
10928
11345
  StickyNote: () => StickyNote,
11346
+ StoryboardActionSchema: () => StoryboardActionSchema,
11347
+ StoryboardEngine: () => StoryboardEngine,
11348
+ StoryboardSchema: () => StoryboardSchema,
11349
+ SubtitleBar: () => SubtitleBar,
10929
11350
  TakeawaysPanel: () => TakeawaysPanel,
10930
11351
  TextLayer: () => TextLayer,
10931
11352
  ThumbnailPanel: () => ThumbnailPanel,
10932
11353
  Toolbar: () => Toolbar,
11354
+ TutorModeContainer: () => TutorModeContainer,
10933
11355
  VirtualizedDocumentContainer: () => VirtualizedDocumentContainer,
10934
11356
  applyRotation: () => applyRotation,
11357
+ buildBBoxIndex: () => buildBBoxIndex,
11358
+ buildUserPrompt: () => buildUserPrompt,
10935
11359
  clearHighlights: () => clearHighlights,
10936
11360
  clearStudentData: () => clearStudentData,
10937
11361
  cn: () => cn,
11362
+ cosineSimilarity: () => cosineSimilarity,
10938
11363
  countTextOnPage: () => countTextOnPage,
10939
11364
  createAgentAPI: () => createAgentAPI,
10940
11365
  createAgentStore: () => createAgentStore,
10941
11366
  createAnnotationStore: () => createAnnotationStore,
11367
+ createNarrationStore: () => createNarrationStore,
10942
11368
  createPDFViewer: () => createPDFViewer,
10943
11369
  createPluginManager: () => createPluginManager,
10944
11370
  createSearchStore: () => createSearchStore,
10945
11371
  createStudentStore: () => createStudentStore,
10946
11372
  createViewerStore: () => createViewerStore,
11373
+ directStoryboard: () => directStoryboard,
10947
11374
  doRectsIntersect: () => doRectsIntersect,
10948
11375
  downloadAnnotationsAsJSON: () => downloadAnnotationsAsJSON,
10949
11376
  downloadAnnotationsAsMarkdown: () => downloadAnnotationsAsMarkdown,
@@ -10958,6 +11385,7 @@ __export(index_exports, {
10958
11385
  generateDocumentId: () => generateDocumentId,
10959
11386
  getAllDocumentIds: () => getAllDocumentIds,
10960
11387
  getAllStudentDataDocumentIds: () => getAllStudentDataDocumentIds,
11388
+ getLocalMiniLM: () => getLocalMiniLM,
10961
11389
  getMetadata: () => getMetadata,
10962
11390
  getOutline: () => getOutline,
10963
11391
  getPage: () => getPage,
@@ -10975,17 +11403,23 @@ __export(index_exports, {
10975
11403
  loadDocumentWithCallbacks: () => loadDocumentWithCallbacks,
10976
11404
  loadHighlights: () => loadHighlights,
10977
11405
  loadStudentData: () => loadStudentData,
11406
+ makeOverlayId: () => makeOverlayId,
11407
+ matchChunkToBlock: () => matchChunkToBlock,
10978
11408
  mergeAdjacentRects: () => mergeAdjacentRects,
10979
11409
  pdfToPercent: () => pdfToPercent,
10980
11410
  pdfToViewport: () => pdfToViewport,
10981
11411
  pdfjsLib: () => pdfjsLib,
10982
11412
  percentToPDF: () => percentToPDF,
10983
11413
  percentToViewport: () => percentToViewport,
11414
+ playPageTurnSound: () => playPageTurnSound,
10984
11415
  quickViewer: () => quickViewer,
10985
11416
  removeRotation: () => removeRotation,
10986
11417
  saveHighlights: () => saveHighlights,
10987
11418
  saveStudentData: () => saveStudentData,
10988
11419
  scaleRect: () => scaleRect,
11420
+ storyboardFromMatch: () => storyboardFromMatch,
11421
+ storyboardJsonSchema: () => storyboardJsonSchema,
11422
+ truncate: () => truncate,
10989
11423
  useAgentContext: () => useAgentContext,
10990
11424
  useAgentStore: () => useAgentStore,
10991
11425
  useAnnotationStore: () => useAnnotationStore,
@@ -11023,9 +11457,9 @@ init_HighlightPopover2();
11023
11457
  init_AnnotationToolbar2();
11024
11458
 
11025
11459
  // src/components/Annotations/StickyNote.tsx
11026
- var import_react44 = require("react");
11460
+ var import_react45 = require("react");
11027
11461
  init_utils();
11028
- var import_jsx_runtime30 = require("react/jsx-runtime");
11462
+ var import_jsx_runtime31 = require("react/jsx-runtime");
11029
11463
  var NOTE_COLORS = [
11030
11464
  "#fef08a",
11031
11465
  // yellow
@@ -11038,7 +11472,7 @@ var NOTE_COLORS = [
11038
11472
  "#fed7aa"
11039
11473
  // orange
11040
11474
  ];
11041
- var StickyNote = (0, import_react44.memo)(function StickyNote2({
11475
+ var StickyNote = (0, import_react45.memo)(function StickyNote2({
11042
11476
  note,
11043
11477
  scale,
11044
11478
  isSelected,
@@ -11051,37 +11485,37 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11051
11485
  onDragStart,
11052
11486
  className
11053
11487
  }) {
11054
- const [isExpanded, setIsExpanded] = (0, import_react44.useState)(false);
11055
- const [localContent, setLocalContent] = (0, import_react44.useState)(note.content);
11056
- const textareaRef = (0, import_react44.useRef)(null);
11057
- const noteRef = (0, import_react44.useRef)(null);
11058
- (0, import_react44.useEffect)(() => {
11488
+ const [isExpanded, setIsExpanded] = (0, import_react45.useState)(false);
11489
+ const [localContent, setLocalContent] = (0, import_react45.useState)(note.content);
11490
+ const textareaRef = (0, import_react45.useRef)(null);
11491
+ const noteRef = (0, import_react45.useRef)(null);
11492
+ (0, import_react45.useEffect)(() => {
11059
11493
  setLocalContent(note.content);
11060
11494
  }, [note.content]);
11061
- (0, import_react44.useEffect)(() => {
11495
+ (0, import_react45.useEffect)(() => {
11062
11496
  if (isEditing && textareaRef.current) {
11063
11497
  textareaRef.current.focus();
11064
11498
  textareaRef.current.select();
11065
11499
  }
11066
11500
  }, [isEditing]);
11067
- const handleClick = (0, import_react44.useCallback)((e) => {
11501
+ const handleClick = (0, import_react45.useCallback)((e) => {
11068
11502
  e.stopPropagation();
11069
11503
  onSelect?.();
11070
11504
  if (!isExpanded) {
11071
11505
  setIsExpanded(true);
11072
11506
  }
11073
11507
  }, [isExpanded, onSelect]);
11074
- const handleDoubleClick = (0, import_react44.useCallback)((e) => {
11508
+ const handleDoubleClick = (0, import_react45.useCallback)((e) => {
11075
11509
  e.stopPropagation();
11076
11510
  onStartEdit?.();
11077
11511
  }, [onStartEdit]);
11078
- const handleBlur = (0, import_react44.useCallback)(() => {
11512
+ const handleBlur = (0, import_react45.useCallback)(() => {
11079
11513
  if (isEditing && localContent !== note.content) {
11080
11514
  onUpdate?.({ content: localContent });
11081
11515
  }
11082
11516
  onEndEdit?.();
11083
11517
  }, [isEditing, localContent, note.content, onUpdate, onEndEdit]);
11084
- const handleKeyDown = (0, import_react44.useCallback)((e) => {
11518
+ const handleKeyDown = (0, import_react45.useCallback)((e) => {
11085
11519
  if (e.key === "Escape") {
11086
11520
  setLocalContent(note.content);
11087
11521
  onEndEdit?.();
@@ -11089,16 +11523,16 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11089
11523
  handleBlur();
11090
11524
  }
11091
11525
  }, [note.content, onEndEdit, handleBlur]);
11092
- const handleColorChange = (0, import_react44.useCallback)((color) => {
11526
+ const handleColorChange = (0, import_react45.useCallback)((color) => {
11093
11527
  onUpdate?.({ color });
11094
11528
  }, [onUpdate]);
11095
- const handleCollapse = (0, import_react44.useCallback)((e) => {
11529
+ const handleCollapse = (0, import_react45.useCallback)((e) => {
11096
11530
  e.stopPropagation();
11097
11531
  setIsExpanded(false);
11098
11532
  onEndEdit?.();
11099
11533
  }, [onEndEdit]);
11100
11534
  if (!isExpanded) {
11101
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11535
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11102
11536
  "div",
11103
11537
  {
11104
11538
  ref: noteRef,
@@ -11119,14 +11553,14 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11119
11553
  onMouseDown: onDragStart,
11120
11554
  onTouchStart: onDragStart,
11121
11555
  title: note.content || "Empty note",
11122
- children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11556
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11123
11557
  "svg",
11124
11558
  {
11125
11559
  className: "w-4 h-4 opacity-70",
11126
11560
  fill: "currentColor",
11127
11561
  viewBox: "0 0 20 20",
11128
11562
  style: { color: "#333" },
11129
- children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11563
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11130
11564
  "path",
11131
11565
  {
11132
11566
  fillRule: "evenodd",
@@ -11139,7 +11573,7 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11139
11573
  }
11140
11574
  );
11141
11575
  }
11142
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
11576
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
11143
11577
  "div",
11144
11578
  {
11145
11579
  ref: noteRef,
@@ -11157,14 +11591,14 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11157
11591
  },
11158
11592
  onClick: handleClick,
11159
11593
  children: [
11160
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
11594
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
11161
11595
  "div",
11162
11596
  {
11163
11597
  className: "flex items-center justify-between px-2 py-1 border-b border-black/10 cursor-move",
11164
11598
  onMouseDown: onDragStart,
11165
11599
  onTouchStart: onDragStart,
11166
11600
  children: [
11167
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex gap-1", children: NOTE_COLORS.map((color) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11601
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex gap-1", children: NOTE_COLORS.map((color) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11168
11602
  "button",
11169
11603
  {
11170
11604
  className: cn(
@@ -11181,8 +11615,8 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11181
11615
  },
11182
11616
  color
11183
11617
  )) }),
11184
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex gap-1", children: [
11185
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11618
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "flex gap-1", children: [
11619
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11186
11620
  "button",
11187
11621
  {
11188
11622
  className: "p-0.5 hover:bg-black/10 rounded",
@@ -11191,23 +11625,23 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11191
11625
  onDelete?.();
11192
11626
  },
11193
11627
  title: "Delete note",
11194
- children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("svg", { className: "w-3.5 h-3.5 text-gray-600", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) })
11628
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("svg", { className: "w-3.5 h-3.5 text-gray-600", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) })
11195
11629
  }
11196
11630
  ),
11197
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11631
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11198
11632
  "button",
11199
11633
  {
11200
11634
  className: "p-0.5 hover:bg-black/10 rounded",
11201
11635
  onClick: handleCollapse,
11202
11636
  title: "Collapse note",
11203
- children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("svg", { className: "w-3.5 h-3.5 text-gray-600", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
11637
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("svg", { className: "w-3.5 h-3.5 text-gray-600", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
11204
11638
  }
11205
11639
  )
11206
11640
  ] })
11207
11641
  ]
11208
11642
  }
11209
11643
  ),
11210
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "p-2", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11644
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "p-2", children: isEditing ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11211
11645
  "textarea",
11212
11646
  {
11213
11647
  ref: textareaRef,
@@ -11222,7 +11656,7 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11222
11656
  onKeyDown: handleKeyDown,
11223
11657
  placeholder: "Enter note..."
11224
11658
  }
11225
- ) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
11659
+ ) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11226
11660
  "div",
11227
11661
  {
11228
11662
  className: cn(
@@ -11233,16 +11667,16 @@ var StickyNote = (0, import_react44.memo)(function StickyNote2({
11233
11667
  children: note.content || "Double-click to edit..."
11234
11668
  }
11235
11669
  ) }),
11236
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "px-2 pb-1 text-[10px] text-gray-500", children: new Date(note.updatedAt).toLocaleDateString() })
11670
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "px-2 pb-1 text-[10px] text-gray-500", children: new Date(note.updatedAt).toLocaleDateString() })
11237
11671
  ]
11238
11672
  }
11239
11673
  );
11240
11674
  });
11241
11675
 
11242
11676
  // src/components/Annotations/DrawingCanvas.tsx
11243
- var import_react45 = require("react");
11677
+ var import_react46 = require("react");
11244
11678
  init_utils();
11245
- var import_jsx_runtime31 = require("react/jsx-runtime");
11679
+ var import_jsx_runtime32 = require("react/jsx-runtime");
11246
11680
  function pointsToSvgPath(points) {
11247
11681
  if (points.length === 0) return "";
11248
11682
  if (points.length === 1) {
@@ -11280,7 +11714,7 @@ function simplifyPath(points, tolerance = 1) {
11280
11714
  result.push(points[points.length - 1]);
11281
11715
  return result;
11282
11716
  }
11283
- var DrawingCanvas = (0, import_react45.memo)(function DrawingCanvas2({
11717
+ var DrawingCanvas = (0, import_react46.memo)(function DrawingCanvas2({
11284
11718
  width,
11285
11719
  height,
11286
11720
  scale,
@@ -11290,10 +11724,10 @@ var DrawingCanvas = (0, import_react45.memo)(function DrawingCanvas2({
11290
11724
  onDrawingComplete,
11291
11725
  className
11292
11726
  }) {
11293
- const svgRef = (0, import_react45.useRef)(null);
11294
- const [isDrawing, setIsDrawing] = (0, import_react45.useState)(false);
11295
- const [currentPath, setCurrentPath] = (0, import_react45.useState)([]);
11296
- const getPoint = (0, import_react45.useCallback)((e) => {
11727
+ const svgRef = (0, import_react46.useRef)(null);
11728
+ const [isDrawing, setIsDrawing] = (0, import_react46.useState)(false);
11729
+ const [currentPath, setCurrentPath] = (0, import_react46.useState)([]);
11730
+ const getPoint = (0, import_react46.useCallback)((e) => {
11297
11731
  if (!svgRef.current) return null;
11298
11732
  const svg = svgRef.current;
11299
11733
  const rect = svg.getBoundingClientRect();
@@ -11313,7 +11747,7 @@ var DrawingCanvas = (0, import_react45.memo)(function DrawingCanvas2({
11313
11747
  y: (clientY - rect.top) / scale
11314
11748
  };
11315
11749
  }, [scale]);
11316
- const handleStart = (0, import_react45.useCallback)((e) => {
11750
+ const handleStart = (0, import_react46.useCallback)((e) => {
11317
11751
  if (!isActive) return;
11318
11752
  const point = getPoint(e);
11319
11753
  if (point) {
@@ -11321,14 +11755,14 @@ var DrawingCanvas = (0, import_react45.memo)(function DrawingCanvas2({
11321
11755
  setCurrentPath([point]);
11322
11756
  }
11323
11757
  }, [isActive, getPoint]);
11324
- const handleMove = (0, import_react45.useCallback)((e) => {
11758
+ const handleMove = (0, import_react46.useCallback)((e) => {
11325
11759
  if (!isDrawing || !isActive) return;
11326
11760
  const point = getPoint(e);
11327
11761
  if (point) {
11328
11762
  setCurrentPath((prev) => [...prev, point]);
11329
11763
  }
11330
11764
  }, [isDrawing, isActive, getPoint]);
11331
- const handleEnd = (0, import_react45.useCallback)(() => {
11765
+ const handleEnd = (0, import_react46.useCallback)(() => {
11332
11766
  if (!isDrawing) return;
11333
11767
  setIsDrawing(false);
11334
11768
  if (currentPath.length >= 2) {
@@ -11337,7 +11771,7 @@ var DrawingCanvas = (0, import_react45.memo)(function DrawingCanvas2({
11337
11771
  }
11338
11772
  setCurrentPath([]);
11339
11773
  }, [isDrawing, currentPath, onDrawingComplete]);
11340
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11774
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
11341
11775
  "svg",
11342
11776
  {
11343
11777
  ref: svgRef,
@@ -11357,7 +11791,7 @@ var DrawingCanvas = (0, import_react45.memo)(function DrawingCanvas2({
11357
11791
  onTouchStart: handleStart,
11358
11792
  onTouchMove: handleMove,
11359
11793
  onTouchEnd: handleEnd,
11360
- children: isDrawing && currentPath.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11794
+ children: isDrawing && currentPath.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
11361
11795
  "path",
11362
11796
  {
11363
11797
  d: pointsToSvgPath(currentPath),
@@ -11374,10 +11808,10 @@ var DrawingCanvas = (0, import_react45.memo)(function DrawingCanvas2({
11374
11808
  });
11375
11809
 
11376
11810
  // src/components/Annotations/ShapeRenderer.tsx
11377
- var import_react46 = require("react");
11811
+ var import_react47 = require("react");
11378
11812
  init_utils();
11379
- var import_jsx_runtime32 = require("react/jsx-runtime");
11380
- var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11813
+ var import_jsx_runtime33 = require("react/jsx-runtime");
11814
+ var ShapeRenderer = (0, import_react47.memo)(function ShapeRenderer2({
11381
11815
  shape,
11382
11816
  scale,
11383
11817
  isSelected,
@@ -11387,18 +11821,18 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11387
11821
  onDelete: _onDelete,
11388
11822
  className
11389
11823
  }) {
11390
- const [_isDragging, setIsDragging] = (0, import_react46.useState)(false);
11391
- const [_isResizing, setIsResizing] = (0, import_react46.useState)(false);
11392
- const [activeHandle, setActiveHandle] = (0, import_react46.useState)(null);
11393
- const startPosRef = (0, import_react46.useRef)({ x: 0, y: 0 });
11394
- const startShapeRef = (0, import_react46.useRef)({ x: 0, y: 0, width: 0, height: 0 });
11824
+ const [_isDragging, setIsDragging] = (0, import_react47.useState)(false);
11825
+ const [_isResizing, setIsResizing] = (0, import_react47.useState)(false);
11826
+ const [activeHandle, setActiveHandle] = (0, import_react47.useState)(null);
11827
+ const startPosRef = (0, import_react47.useRef)({ x: 0, y: 0 });
11828
+ const startShapeRef = (0, import_react47.useRef)({ x: 0, y: 0, width: 0, height: 0 });
11395
11829
  const { shapeType, x, y, width, height, color, strokeWidth, id: _id } = shape;
11396
11830
  const scaledX = x * scale;
11397
11831
  const scaledY = y * scale;
11398
11832
  const scaledWidth = width * scale;
11399
11833
  const scaledHeight = height * scale;
11400
11834
  const scaledStroke = strokeWidth * scale;
11401
- const getResizeHandles = (0, import_react46.useCallback)(() => {
11835
+ const getResizeHandles = (0, import_react47.useCallback)(() => {
11402
11836
  const handleSize = 8;
11403
11837
  const half = handleSize / 2;
11404
11838
  return [
@@ -11412,7 +11846,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11412
11846
  { position: "w", cursor: "ew-resize", x: scaledX - half, y: scaledY + scaledHeight / 2 - half }
11413
11847
  ];
11414
11848
  }, [scaledX, scaledY, scaledWidth, scaledHeight]);
11415
- const handleMouseDown = (0, import_react46.useCallback)((e, handle) => {
11849
+ const handleMouseDown = (0, import_react47.useCallback)((e, handle) => {
11416
11850
  e.stopPropagation();
11417
11851
  onSelect?.();
11418
11852
  if (!isEditing) return;
@@ -11488,7 +11922,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11488
11922
  document.addEventListener("mousemove", handleMouseMove);
11489
11923
  document.addEventListener("mouseup", handleMouseUp);
11490
11924
  }, [isEditing, x, y, width, height, scale, onSelect, onUpdate]);
11491
- const renderShape2 = (0, import_react46.useCallback)(() => {
11925
+ const renderShape2 = (0, import_react47.useCallback)(() => {
11492
11926
  const commonProps = {
11493
11927
  stroke: color,
11494
11928
  strokeWidth: scaledStroke,
@@ -11500,7 +11934,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11500
11934
  };
11501
11935
  switch (shapeType) {
11502
11936
  case "rect":
11503
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
11937
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11504
11938
  "rect",
11505
11939
  {
11506
11940
  x: scaledX,
@@ -11511,7 +11945,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11511
11945
  }
11512
11946
  );
11513
11947
  case "circle":
11514
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
11948
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11515
11949
  "ellipse",
11516
11950
  {
11517
11951
  cx: scaledX + scaledWidth / 2,
@@ -11522,7 +11956,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11522
11956
  }
11523
11957
  );
11524
11958
  case "line":
11525
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
11959
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11526
11960
  "line",
11527
11961
  {
11528
11962
  x1: scaledX,
@@ -11542,22 +11976,22 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11542
11976
  const arrow1Y = endY - arrowLength * Math.sin(angle - arrowAngle);
11543
11977
  const arrow2X = endX - arrowLength * Math.cos(angle + arrowAngle);
11544
11978
  const arrow2Y = endY - arrowLength * Math.sin(angle + arrowAngle);
11545
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("g", { children: [
11546
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("line", { x1: scaledX, y1: scaledY, x2: endX, y2: endY, ...commonProps }),
11547
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("line", { x1: endX, y1: endY, x2: arrow1X, y2: arrow1Y, ...commonProps }),
11548
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("line", { x1: endX, y1: endY, x2: arrow2X, y2: arrow2Y, ...commonProps })
11979
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("g", { children: [
11980
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("line", { x1: scaledX, y1: scaledY, x2: endX, y2: endY, ...commonProps }),
11981
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("line", { x1: endX, y1: endY, x2: arrow1X, y2: arrow1Y, ...commonProps }),
11982
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("line", { x1: endX, y1: endY, x2: arrow2X, y2: arrow2Y, ...commonProps })
11549
11983
  ] });
11550
11984
  default:
11551
11985
  return null;
11552
11986
  }
11553
11987
  }, [shapeType, scaledX, scaledY, scaledWidth, scaledHeight, color, scaledStroke, isSelected]);
11554
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
11988
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
11555
11989
  "g",
11556
11990
  {
11557
11991
  className: cn("shape-renderer", className),
11558
11992
  onMouseDown: (e) => handleMouseDown(e),
11559
11993
  children: [
11560
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
11994
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11561
11995
  "rect",
11562
11996
  {
11563
11997
  x: scaledX - 5,
@@ -11570,7 +12004,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11570
12004
  }
11571
12005
  ),
11572
12006
  renderShape2(),
11573
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12007
+ isSelected && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11574
12008
  "rect",
11575
12009
  {
11576
12010
  x: scaledX - 2,
@@ -11583,7 +12017,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11583
12017
  strokeDasharray: "4 2"
11584
12018
  }
11585
12019
  ),
11586
- isSelected && isEditing && getResizeHandles().map((handle) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12020
+ isSelected && isEditing && getResizeHandles().map((handle) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11587
12021
  "rect",
11588
12022
  {
11589
12023
  x: handle.x,
@@ -11603,7 +12037,7 @@ var ShapeRenderer = (0, import_react46.memo)(function ShapeRenderer2({
11603
12037
  }
11604
12038
  );
11605
12039
  });
11606
- var ShapePreview = (0, import_react46.memo)(function ShapePreview2({
12040
+ var ShapePreview = (0, import_react47.memo)(function ShapePreview2({
11607
12041
  shapeType,
11608
12042
  startPoint,
11609
12043
  endPoint,
@@ -11624,9 +12058,9 @@ var ShapePreview = (0, import_react46.memo)(function ShapePreview2({
11624
12058
  };
11625
12059
  switch (shapeType) {
11626
12060
  case "rect":
11627
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("rect", { x, y, width, height, ...commonProps });
12061
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("rect", { x, y, width, height, ...commonProps });
11628
12062
  case "circle":
11629
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12063
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11630
12064
  "ellipse",
11631
12065
  {
11632
12066
  cx: x + width / 2,
@@ -11637,7 +12071,7 @@ var ShapePreview = (0, import_react46.memo)(function ShapePreview2({
11637
12071
  }
11638
12072
  );
11639
12073
  case "line":
11640
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12074
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11641
12075
  "line",
11642
12076
  {
11643
12077
  x1: startPoint.x * scale,
@@ -11659,8 +12093,8 @@ var ShapePreview = (0, import_react46.memo)(function ShapePreview2({
11659
12093
  const arrow1Y = endY - arrowLength * Math.sin(angle - arrowAngle);
11660
12094
  const arrow2X = endX - arrowLength * Math.cos(angle + arrowAngle);
11661
12095
  const arrow2Y = endY - arrowLength * Math.sin(angle + arrowAngle);
11662
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("g", { children: [
11663
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
12096
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("g", { children: [
12097
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
11664
12098
  "line",
11665
12099
  {
11666
12100
  x1: startPoint.x * scale,
@@ -11670,8 +12104,8 @@ var ShapePreview = (0, import_react46.memo)(function ShapePreview2({
11670
12104
  ...commonProps
11671
12105
  }
11672
12106
  ),
11673
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("line", { x1: endX, y1: endY, x2: arrow1X, y2: arrow1Y, ...commonProps }),
11674
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("line", { x1: endX, y1: endY, x2: arrow2X, y2: arrow2Y, ...commonProps })
12107
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("line", { x1: endX, y1: endY, x2: arrow1X, y2: arrow1Y, ...commonProps }),
12108
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("line", { x1: endX, y1: endY, x2: arrow2X, y2: arrow2Y, ...commonProps })
11675
12109
  ] });
11676
12110
  default:
11677
12111
  return null;
@@ -11679,31 +12113,31 @@ var ShapePreview = (0, import_react46.memo)(function ShapePreview2({
11679
12113
  });
11680
12114
 
11681
12115
  // src/components/Annotations/QuickNoteButton.tsx
11682
- var import_react47 = require("react");
12116
+ var import_react48 = require("react");
11683
12117
  init_utils();
11684
- var import_jsx_runtime33 = require("react/jsx-runtime");
11685
- var QuickNoteButton = (0, import_react47.memo)(function QuickNoteButton2({
12118
+ var import_jsx_runtime34 = require("react/jsx-runtime");
12119
+ var QuickNoteButton = (0, import_react48.memo)(function QuickNoteButton2({
11686
12120
  pageNumber,
11687
12121
  scale,
11688
- position = "top-right",
12122
+ position: position2 = "top-right",
11689
12123
  onClick,
11690
12124
  className,
11691
12125
  visible = true
11692
12126
  }) {
11693
- const [isHovered, setIsHovered] = (0, import_react47.useState)(false);
11694
- const handleClick = (0, import_react47.useCallback)(
12127
+ const [isHovered, setIsHovered] = (0, import_react48.useState)(false);
12128
+ const handleClick = (0, import_react48.useCallback)(
11695
12129
  (e) => {
11696
12130
  e.stopPropagation();
11697
- const x = position === "top-right" ? 80 : 80;
11698
- const y = position === "top-right" ? 20 : 80;
12131
+ const x = position2 === "top-right" ? 80 : 80;
12132
+ const y = position2 === "top-right" ? 20 : 80;
11699
12133
  onClick(pageNumber, x / scale, y / scale);
11700
12134
  },
11701
- [pageNumber, onClick, position, scale]
12135
+ [pageNumber, onClick, position2, scale]
11702
12136
  );
11703
12137
  if (!visible) {
11704
12138
  return null;
11705
12139
  }
11706
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
12140
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
11707
12141
  "button",
11708
12142
  {
11709
12143
  onClick: handleClick,
@@ -11719,13 +12153,13 @@ var QuickNoteButton = (0, import_react47.memo)(function QuickNoteButton2({
11719
12153
  "transition-all duration-200",
11720
12154
  "focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2",
11721
12155
  isHovered && "scale-110",
11722
- position === "top-right" && "top-3 right-3",
11723
- position === "bottom-right" && "bottom-3 right-3",
12156
+ position2 === "top-right" && "top-3 right-3",
12157
+ position2 === "bottom-right" && "bottom-3 right-3",
11724
12158
  className
11725
12159
  ),
11726
12160
  title: "Add quick note",
11727
12161
  "aria-label": "Add quick note",
11728
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
12162
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
11729
12163
  "svg",
11730
12164
  {
11731
12165
  className: "w-4 h-4 text-yellow-900",
@@ -11733,7 +12167,7 @@ var QuickNoteButton = (0, import_react47.memo)(function QuickNoteButton2({
11733
12167
  viewBox: "0 0 24 24",
11734
12168
  stroke: "currentColor",
11735
12169
  strokeWidth: 2,
11736
- children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" })
12170
+ children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" })
11737
12171
  }
11738
12172
  )
11739
12173
  }
@@ -11741,37 +12175,37 @@ var QuickNoteButton = (0, import_react47.memo)(function QuickNoteButton2({
11741
12175
  });
11742
12176
 
11743
12177
  // src/components/Annotations/QuickNotePopover.tsx
11744
- var import_react48 = require("react");
12178
+ var import_react49 = require("react");
11745
12179
  init_utils();
11746
- var import_jsx_runtime34 = require("react/jsx-runtime");
11747
- var QuickNotePopover = (0, import_react48.memo)(function QuickNotePopover2({
12180
+ var import_jsx_runtime35 = require("react/jsx-runtime");
12181
+ var QuickNotePopover = (0, import_react49.memo)(function QuickNotePopover2({
11748
12182
  visible,
11749
- position,
12183
+ position: position2,
11750
12184
  initialContent = "",
11751
12185
  agentLastStatement,
11752
12186
  onSave,
11753
12187
  onCancel,
11754
12188
  className
11755
12189
  }) {
11756
- const [content, setContent] = (0, import_react48.useState)(initialContent);
11757
- const textareaRef = (0, import_react48.useRef)(null);
11758
- const popoverRef = (0, import_react48.useRef)(null);
11759
- const [adjustedPosition, setAdjustedPosition] = (0, import_react48.useState)(position);
11760
- (0, import_react48.useEffect)(() => {
12190
+ const [content, setContent] = (0, import_react49.useState)(initialContent);
12191
+ const textareaRef = (0, import_react49.useRef)(null);
12192
+ const popoverRef = (0, import_react49.useRef)(null);
12193
+ const [adjustedPosition, setAdjustedPosition] = (0, import_react49.useState)(position2);
12194
+ (0, import_react49.useEffect)(() => {
11761
12195
  if (visible && textareaRef.current) {
11762
12196
  textareaRef.current.focus();
11763
12197
  }
11764
12198
  }, [visible]);
11765
- (0, import_react48.useEffect)(() => {
12199
+ (0, import_react49.useEffect)(() => {
11766
12200
  if (visible) {
11767
12201
  setContent(initialContent);
11768
12202
  }
11769
12203
  }, [visible, initialContent]);
11770
- (0, import_react48.useEffect)(() => {
12204
+ (0, import_react49.useEffect)(() => {
11771
12205
  if (!visible || !popoverRef.current) return;
11772
12206
  const rect = popoverRef.current.getBoundingClientRect();
11773
12207
  const padding = 10;
11774
- let { x, y } = position;
12208
+ let { x, y } = position2;
11775
12209
  if (x + rect.width > window.innerWidth - padding) {
11776
12210
  x = window.innerWidth - rect.width - padding;
11777
12211
  }
@@ -11785,15 +12219,15 @@ var QuickNotePopover = (0, import_react48.memo)(function QuickNotePopover2({
11785
12219
  y = padding;
11786
12220
  }
11787
12221
  setAdjustedPosition({ x, y });
11788
- }, [position, visible]);
11789
- const handleSave = (0, import_react48.useCallback)(() => {
12222
+ }, [position2, visible]);
12223
+ const handleSave = (0, import_react49.useCallback)(() => {
11790
12224
  if (content.trim()) {
11791
12225
  onSave(content.trim());
11792
12226
  } else {
11793
12227
  onCancel();
11794
12228
  }
11795
12229
  }, [content, onSave, onCancel]);
11796
- const handleKeyDown = (0, import_react48.useCallback)(
12230
+ const handleKeyDown = (0, import_react49.useCallback)(
11797
12231
  (e) => {
11798
12232
  if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
11799
12233
  e.preventDefault();
@@ -11808,7 +12242,7 @@ var QuickNotePopover = (0, import_react48.memo)(function QuickNotePopover2({
11808
12242
  if (!visible) {
11809
12243
  return null;
11810
12244
  }
11811
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
12245
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
11812
12246
  "div",
11813
12247
  {
11814
12248
  ref: popoverRef,
@@ -11827,15 +12261,15 @@ var QuickNotePopover = (0, import_react48.memo)(function QuickNotePopover2({
11827
12261
  top: adjustedPosition.y
11828
12262
  },
11829
12263
  children: [
11830
- agentLastStatement && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "mb-2 p-2 bg-blue-50 dark:bg-blue-900/50 rounded text-xs text-blue-600 dark:text-blue-300 border border-blue-100 dark:border-blue-800", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-start gap-1", children: [
11831
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("svg", { className: "w-3 h-3 mt-0.5 flex-shrink-0", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }),
11832
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "line-clamp-2", children: [
12264
+ agentLastStatement && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "mb-2 p-2 bg-blue-50 dark:bg-blue-900/50 rounded text-xs text-blue-600 dark:text-blue-300 border border-blue-100 dark:border-blue-800", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-start gap-1", children: [
12265
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("svg", { className: "w-3 h-3 mt-0.5 flex-shrink-0", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }),
12266
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "line-clamp-2", children: [
11833
12267
  "AI discussed: \u201C",
11834
12268
  agentLastStatement,
11835
12269
  "\u201D"
11836
12270
  ] })
11837
12271
  ] }) }),
11838
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
12272
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
11839
12273
  "textarea",
11840
12274
  {
11841
12275
  ref: textareaRef,
@@ -11854,13 +12288,13 @@ var QuickNotePopover = (0, import_react48.memo)(function QuickNotePopover2({
11854
12288
  )
11855
12289
  }
11856
12290
  ),
11857
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center justify-between mt-2", children: [
11858
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: [
12291
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center justify-between mt-2", children: [
12292
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: [
11859
12293
  navigator.platform.includes("Mac") ? "\u2318" : "Ctrl",
11860
12294
  "+Enter to save"
11861
12295
  ] }),
11862
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex gap-2", children: [
11863
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
12296
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex gap-2", children: [
12297
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
11864
12298
  "button",
11865
12299
  {
11866
12300
  onClick: onCancel,
@@ -11873,7 +12307,7 @@ var QuickNotePopover = (0, import_react48.memo)(function QuickNotePopover2({
11873
12307
  children: "Cancel"
11874
12308
  }
11875
12309
  ),
11876
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
12310
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
11877
12311
  "button",
11878
12312
  {
11879
12313
  onClick: handleSave,
@@ -11896,24 +12330,24 @@ var QuickNotePopover = (0, import_react48.memo)(function QuickNotePopover2({
11896
12330
  });
11897
12331
 
11898
12332
  // src/components/AskAbout/AskAboutOverlay.tsx
11899
- var import_react49 = require("react");
12333
+ var import_react50 = require("react");
11900
12334
  init_utils();
11901
- var import_jsx_runtime35 = require("react/jsx-runtime");
11902
- var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
12335
+ var import_jsx_runtime36 = require("react/jsx-runtime");
12336
+ var AskAboutOverlay = (0, import_react50.memo)(function AskAboutOverlay2({
11903
12337
  visible,
11904
12338
  progress,
11905
- position,
12339
+ position: position2,
11906
12340
  size = 60,
11907
12341
  className
11908
12342
  }) {
11909
- if (!visible || !position) {
12343
+ if (!visible || !position2) {
11910
12344
  return null;
11911
12345
  }
11912
12346
  const strokeWidth = 4;
11913
12347
  const radius = (size - strokeWidth) / 2;
11914
12348
  const circumference = 2 * Math.PI * radius;
11915
12349
  const strokeDashoffset = circumference * (1 - progress);
11916
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
12350
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
11917
12351
  "div",
11918
12352
  {
11919
12353
  className: cn(
@@ -11922,11 +12356,11 @@ var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
11922
12356
  className
11923
12357
  ),
11924
12358
  style: {
11925
- left: position.x - size / 2,
11926
- top: position.y - size / 2
12359
+ left: position2.x - size / 2,
12360
+ top: position2.y - size / 2
11927
12361
  },
11928
12362
  children: [
11929
- /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
12363
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
11930
12364
  "svg",
11931
12365
  {
11932
12366
  width: size,
@@ -11934,7 +12368,7 @@ var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
11934
12368
  viewBox: `0 0 ${size} ${size}`,
11935
12369
  className: "transform -rotate-90",
11936
12370
  children: [
11937
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
12371
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11938
12372
  "circle",
11939
12373
  {
11940
12374
  cx: size / 2,
@@ -11945,7 +12379,7 @@ var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
11945
12379
  strokeWidth
11946
12380
  }
11947
12381
  ),
11948
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
12382
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11949
12383
  "circle",
11950
12384
  {
11951
12385
  cx: size / 2,
@@ -11963,12 +12397,12 @@ var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
11963
12397
  ]
11964
12398
  }
11965
12399
  ),
11966
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
12400
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11967
12401
  "div",
11968
12402
  {
11969
12403
  className: "absolute inset-0 flex items-center justify-center",
11970
12404
  style: { color: progress >= 1 ? "#22c55e" : "white" },
11971
- children: progress >= 1 ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
12405
+ children: progress >= 1 ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11972
12406
  "svg",
11973
12407
  {
11974
12408
  width: "24",
@@ -11979,9 +12413,9 @@ var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
11979
12413
  strokeWidth: "2",
11980
12414
  strokeLinecap: "round",
11981
12415
  strokeLinejoin: "round",
11982
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("polyline", { points: "20 6 9 17 4 12" })
12416
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("polyline", { points: "20 6 9 17 4 12" })
11983
12417
  }
11984
- ) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
12418
+ ) : /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
11985
12419
  "svg",
11986
12420
  {
11987
12421
  width: "20",
@@ -11993,9 +12427,9 @@ var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
11993
12427
  strokeLinecap: "round",
11994
12428
  strokeLinejoin: "round",
11995
12429
  children: [
11996
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
11997
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
11998
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
12430
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
12431
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
12432
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
11999
12433
  ]
12000
12434
  }
12001
12435
  )
@@ -12007,24 +12441,24 @@ var AskAboutOverlay = (0, import_react49.memo)(function AskAboutOverlay2({
12007
12441
  });
12008
12442
 
12009
12443
  // src/components/AskAbout/AskAboutTrigger.tsx
12010
- var import_react50 = require("react");
12444
+ var import_react51 = require("react");
12011
12445
  init_utils();
12012
- var import_jsx_runtime36 = require("react/jsx-runtime");
12013
- var AskAboutTrigger = (0, import_react50.memo)(function AskAboutTrigger2({
12014
- position,
12446
+ var import_jsx_runtime37 = require("react/jsx-runtime");
12447
+ var AskAboutTrigger = (0, import_react51.memo)(function AskAboutTrigger2({
12448
+ position: position2,
12015
12449
  onConfirm,
12016
12450
  onCancel,
12017
12451
  visible,
12018
12452
  autoHideDelay = 5e3,
12019
12453
  className
12020
12454
  }) {
12021
- const [adjustedPosition, setAdjustedPosition] = (0, import_react50.useState)(position);
12022
- const triggerRef = (0, import_react50.useRef)(null);
12023
- (0, import_react50.useEffect)(() => {
12455
+ const [adjustedPosition, setAdjustedPosition] = (0, import_react51.useState)(position2);
12456
+ const triggerRef = (0, import_react51.useRef)(null);
12457
+ (0, import_react51.useEffect)(() => {
12024
12458
  if (!visible || !triggerRef.current) return;
12025
12459
  const rect = triggerRef.current.getBoundingClientRect();
12026
12460
  const padding = 10;
12027
- let { x, y } = position;
12461
+ let { x, y } = position2;
12028
12462
  if (x + rect.width / 2 > window.innerWidth - padding) {
12029
12463
  x = window.innerWidth - rect.width / 2 - padding;
12030
12464
  }
@@ -12032,23 +12466,23 @@ var AskAboutTrigger = (0, import_react50.memo)(function AskAboutTrigger2({
12032
12466
  x = rect.width / 2 + padding;
12033
12467
  }
12034
12468
  if (y + rect.height > window.innerHeight - padding) {
12035
- y = position.y - rect.height - 20;
12469
+ y = position2.y - rect.height - 20;
12036
12470
  }
12037
12471
  setAdjustedPosition({ x, y });
12038
- }, [position, visible]);
12039
- (0, import_react50.useEffect)(() => {
12472
+ }, [position2, visible]);
12473
+ (0, import_react51.useEffect)(() => {
12040
12474
  if (!visible || autoHideDelay === 0) return;
12041
12475
  const timer = setTimeout(onCancel, autoHideDelay);
12042
12476
  return () => clearTimeout(timer);
12043
12477
  }, [visible, autoHideDelay, onCancel]);
12044
- const handleConfirm = (0, import_react50.useCallback)(
12478
+ const handleConfirm = (0, import_react51.useCallback)(
12045
12479
  (e) => {
12046
12480
  e.stopPropagation();
12047
12481
  onConfirm();
12048
12482
  },
12049
12483
  [onConfirm]
12050
12484
  );
12051
- const handleCancel = (0, import_react50.useCallback)(
12485
+ const handleCancel = (0, import_react51.useCallback)(
12052
12486
  (e) => {
12053
12487
  e.stopPropagation();
12054
12488
  onCancel();
@@ -12058,7 +12492,7 @@ var AskAboutTrigger = (0, import_react50.memo)(function AskAboutTrigger2({
12058
12492
  if (!visible) {
12059
12493
  return null;
12060
12494
  }
12061
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
12495
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
12062
12496
  "div",
12063
12497
  {
12064
12498
  ref: triggerRef,
@@ -12077,8 +12511,8 @@ var AskAboutTrigger = (0, import_react50.memo)(function AskAboutTrigger2({
12077
12511
  transform: "translate(-50%, 0)"
12078
12512
  },
12079
12513
  children: [
12080
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-sm text-gray-600 dark:text-gray-300 px-2", children: "Ask about this?" }),
12081
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
12514
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "text-sm text-gray-600 dark:text-gray-300 px-2", children: "Ask about this?" }),
12515
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
12082
12516
  "button",
12083
12517
  {
12084
12518
  onClick: handleConfirm,
@@ -12091,7 +12525,7 @@ var AskAboutTrigger = (0, import_react50.memo)(function AskAboutTrigger2({
12091
12525
  children: "Ask"
12092
12526
  }
12093
12527
  ),
12094
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
12528
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
12095
12529
  "button",
12096
12530
  {
12097
12531
  onClick: handleCancel,
@@ -12111,11 +12545,11 @@ var AskAboutTrigger = (0, import_react50.memo)(function AskAboutTrigger2({
12111
12545
  });
12112
12546
 
12113
12547
  // src/components/Minimap/Minimap.tsx
12114
- var import_react51 = require("react");
12548
+ var import_react52 = require("react");
12115
12549
  init_hooks();
12116
12550
  init_utils();
12117
- var import_jsx_runtime37 = require("react/jsx-runtime");
12118
- var PageIndicator = (0, import_react51.memo)(function PageIndicator2({
12551
+ var import_jsx_runtime38 = require("react/jsx-runtime");
12552
+ var PageIndicator = (0, import_react52.memo)(function PageIndicator2({
12119
12553
  pageNumber,
12120
12554
  status,
12121
12555
  isBookmarked,
@@ -12129,7 +12563,7 @@ var PageIndicator = (0, import_react51.memo)(function PageIndicator2({
12129
12563
  if (status === "visited") return "bg-green-400";
12130
12564
  return "bg-gray-200 dark:bg-gray-700";
12131
12565
  };
12132
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
12566
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
12133
12567
  "button",
12134
12568
  {
12135
12569
  onClick,
@@ -12145,13 +12579,13 @@ var PageIndicator = (0, import_react51.memo)(function PageIndicator2({
12145
12579
  title: `Page ${pageNumber}${isBookmarked ? " (bookmarked)" : ""}`,
12146
12580
  "aria-label": `Go to page ${pageNumber}`,
12147
12581
  children: [
12148
- isBookmarked && !compact && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "absolute -top-1 -right-1 w-2 h-2 bg-yellow-500 rounded-full border border-white" }),
12149
- showNumber && !compact && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "absolute inset-0 flex items-center justify-center text-[8px] font-medium text-white", children: pageNumber })
12582
+ isBookmarked && !compact && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "absolute -top-1 -right-1 w-2 h-2 bg-yellow-500 rounded-full border border-white" }),
12583
+ showNumber && !compact && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "absolute inset-0 flex items-center justify-center text-[8px] font-medium text-white", children: pageNumber })
12150
12584
  ]
12151
12585
  }
12152
12586
  );
12153
12587
  });
12154
- var Minimap = (0, import_react51.memo)(function Minimap2({
12588
+ var Minimap = (0, import_react52.memo)(function Minimap2({
12155
12589
  variant = "sidebar",
12156
12590
  floatingPosition = "right",
12157
12591
  maxHeight = 300,
@@ -12164,18 +12598,18 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12164
12598
  const currentPage = useViewerStore((s) => s.currentPage);
12165
12599
  const numPages = useViewerStore((s) => s.numPages);
12166
12600
  const goToPage = useViewerStore((s) => s.goToPage);
12167
- const bookmarkedPages = (0, import_react51.useMemo)(() => {
12601
+ const bookmarkedPages = (0, import_react52.useMemo)(() => {
12168
12602
  return new Set(bookmarks.map((b) => b.pageNumber));
12169
12603
  }, [bookmarks]);
12170
12604
  const compact = numPages > 50;
12171
- const handlePageClick = (0, import_react51.useCallback)(
12605
+ const handlePageClick = (0, import_react52.useCallback)(
12172
12606
  (pageNumber) => {
12173
12607
  goToPage(pageNumber);
12174
12608
  onPageClick?.(pageNumber);
12175
12609
  },
12176
12610
  [goToPage, onPageClick]
12177
12611
  );
12178
- const getPageStatus = (0, import_react51.useCallback)(
12612
+ const getPageStatus = (0, import_react52.useCallback)(
12179
12613
  (pageNumber) => {
12180
12614
  if (pageNumber === currentPage) return "current";
12181
12615
  if (bookmarkedPages.has(pageNumber)) return "bookmarked";
@@ -12184,11 +12618,11 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12184
12618
  },
12185
12619
  [currentPage, visitedPages, bookmarkedPages]
12186
12620
  );
12187
- const pageIndicators = (0, import_react51.useMemo)(() => {
12621
+ const pageIndicators = (0, import_react52.useMemo)(() => {
12188
12622
  const pages = [];
12189
12623
  for (let i = 1; i <= numPages; i++) {
12190
12624
  pages.push(
12191
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
12625
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12192
12626
  PageIndicator,
12193
12627
  {
12194
12628
  pageNumber: i,
@@ -12209,16 +12643,16 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12209
12643
  if (numPages === 0) {
12210
12644
  return null;
12211
12645
  }
12212
- const content = /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
12213
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "mb-3", children: [
12214
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1", children: [
12215
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: "Progress" }),
12216
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
12646
+ const content = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_jsx_runtime38.Fragment, { children: [
12647
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "mb-3", children: [
12648
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1", children: [
12649
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { children: "Progress" }),
12650
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { children: [
12217
12651
  progressPercentage,
12218
12652
  "%"
12219
12653
  ] })
12220
12654
  ] }),
12221
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
12655
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12222
12656
  "div",
12223
12657
  {
12224
12658
  className: "h-full bg-green-500 rounded-full transition-all duration-300",
@@ -12226,7 +12660,7 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12226
12660
  }
12227
12661
  ) })
12228
12662
  ] }),
12229
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
12663
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12230
12664
  "div",
12231
12665
  {
12232
12666
  className: cn(
@@ -12237,21 +12671,21 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12237
12671
  children: pageIndicators
12238
12672
  }
12239
12673
  ),
12240
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "mt-3 pt-2 border-t border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-wrap gap-3 text-xs text-gray-500 dark:text-gray-400", children: [
12241
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex items-center gap-1", children: [
12242
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "w-2 h-2 rounded-sm bg-blue-500" }),
12243
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: "Current" })
12674
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "mt-3 pt-2 border-t border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex flex-wrap gap-3 text-xs text-gray-500 dark:text-gray-400", children: [
12675
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-1", children: [
12676
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "w-2 h-2 rounded-sm bg-blue-500" }),
12677
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { children: "Current" })
12244
12678
  ] }),
12245
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex items-center gap-1", children: [
12246
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "w-2 h-2 rounded-sm bg-green-400" }),
12247
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: "Visited" })
12679
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-1", children: [
12680
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "w-2 h-2 rounded-sm bg-green-400" }),
12681
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { children: "Visited" })
12248
12682
  ] }),
12249
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex items-center gap-1", children: [
12250
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "w-2 h-2 rounded-sm bg-yellow-400" }),
12251
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: "Bookmarked" })
12683
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex items-center gap-1", children: [
12684
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "w-2 h-2 rounded-sm bg-yellow-400" }),
12685
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { children: "Bookmarked" })
12252
12686
  ] })
12253
12687
  ] }) }),
12254
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "mt-2 text-xs text-gray-500 dark:text-gray-400", children: [
12688
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "mt-2 text-xs text-gray-500 dark:text-gray-400", children: [
12255
12689
  visitedCount,
12256
12690
  " of ",
12257
12691
  numPages,
@@ -12259,7 +12693,7 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12259
12693
  ] })
12260
12694
  ] });
12261
12695
  if (variant === "floating") {
12262
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
12696
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
12263
12697
  "div",
12264
12698
  {
12265
12699
  className: cn(
@@ -12275,13 +12709,13 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12275
12709
  ),
12276
12710
  style: { maxHeight },
12277
12711
  children: [
12278
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("h3", { className: "text-sm font-semibold text-gray-700 dark:text-gray-200 mb-2", children: "Reading Progress" }),
12712
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("h3", { className: "text-sm font-semibold text-gray-700 dark:text-gray-200 mb-2", children: "Reading Progress" }),
12279
12713
  content
12280
12714
  ]
12281
12715
  }
12282
12716
  );
12283
12717
  }
12284
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
12718
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12285
12719
  "div",
12286
12720
  {
12287
12721
  className: cn(
@@ -12299,13 +12733,13 @@ var Minimap = (0, import_react51.memo)(function Minimap2({
12299
12733
  init_FloatingZoomControls2();
12300
12734
 
12301
12735
  // src/components/PDFThumbnailNav/PDFThumbnailNav.tsx
12302
- var import_react52 = require("react");
12736
+ var import_react53 = require("react");
12303
12737
  init_hooks();
12304
12738
  init_utils();
12305
- var import_jsx_runtime38 = require("react/jsx-runtime");
12739
+ var import_jsx_runtime39 = require("react/jsx-runtime");
12306
12740
  var DEFAULT_WIDTH = 612;
12307
12741
  var DEFAULT_HEIGHT = 792;
12308
- var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12742
+ var PDFThumbnailNav = (0, import_react53.memo)(function PDFThumbnailNav2({
12309
12743
  thumbnailScale = 0.15,
12310
12744
  orientation = "vertical",
12311
12745
  maxVisible = 10,
@@ -12316,14 +12750,14 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12316
12750
  }) {
12317
12751
  const { document: document2, numPages, currentPage } = usePDFViewer();
12318
12752
  const { viewerStore } = usePDFViewerStores();
12319
- const containerRef = (0, import_react52.useRef)(null);
12320
- const [thumbnails, setThumbnails] = (0, import_react52.useState)(/* @__PURE__ */ new Map());
12321
- const [visibleRange, setVisibleRange] = (0, import_react52.useState)({ start: 1, end: maxVisible });
12322
- const renderQueueRef = (0, import_react52.useRef)(/* @__PURE__ */ new Set());
12323
- const pageCache = (0, import_react52.useRef)(/* @__PURE__ */ new Map());
12753
+ const containerRef = (0, import_react53.useRef)(null);
12754
+ const [thumbnails, setThumbnails] = (0, import_react53.useState)(/* @__PURE__ */ new Map());
12755
+ const [visibleRange, setVisibleRange] = (0, import_react53.useState)({ start: 1, end: maxVisible });
12756
+ const renderQueueRef = (0, import_react53.useRef)(/* @__PURE__ */ new Set());
12757
+ const pageCache = (0, import_react53.useRef)(/* @__PURE__ */ new Map());
12324
12758
  const thumbnailWidth = Math.floor(DEFAULT_WIDTH * thumbnailScale);
12325
12759
  const thumbnailHeight = Math.floor(DEFAULT_HEIGHT * thumbnailScale);
12326
- const updateVisibleRange = (0, import_react52.useCallback)(() => {
12760
+ const updateVisibleRange = (0, import_react53.useCallback)(() => {
12327
12761
  if (!containerRef.current || numPages === 0) return;
12328
12762
  const container = containerRef.current;
12329
12763
  const isHorizontal2 = orientation === "horizontal";
@@ -12335,7 +12769,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12335
12769
  const lastVisible = Math.min(numPages, firstVisible + visibleCount);
12336
12770
  setVisibleRange({ start: firstVisible, end: lastVisible });
12337
12771
  }, [numPages, orientation, thumbnailWidth, thumbnailHeight, gap]);
12338
- (0, import_react52.useEffect)(() => {
12772
+ (0, import_react53.useEffect)(() => {
12339
12773
  const container = containerRef.current;
12340
12774
  if (!container) return;
12341
12775
  const handleScroll = () => {
@@ -12345,7 +12779,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12345
12779
  updateVisibleRange();
12346
12780
  return () => container.removeEventListener("scroll", handleScroll);
12347
12781
  }, [updateVisibleRange]);
12348
- (0, import_react52.useEffect)(() => {
12782
+ (0, import_react53.useEffect)(() => {
12349
12783
  if (!document2) {
12350
12784
  setThumbnails(/* @__PURE__ */ new Map());
12351
12785
  pageCache.current.clear();
@@ -12400,7 +12834,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12400
12834
  };
12401
12835
  renderThumbnails();
12402
12836
  }, [document2, visibleRange, thumbnailScale, thumbnails]);
12403
- (0, import_react52.useEffect)(() => {
12837
+ (0, import_react53.useEffect)(() => {
12404
12838
  if (!containerRef.current || numPages === 0) return;
12405
12839
  const container = containerRef.current;
12406
12840
  const isHorizontal2 = orientation === "horizontal";
@@ -12416,12 +12850,12 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12416
12850
  });
12417
12851
  }
12418
12852
  }, [currentPage, numPages, orientation, thumbnailWidth, thumbnailHeight, gap]);
12419
- const handleThumbnailClick = (0, import_react52.useCallback)((pageNum) => {
12853
+ const handleThumbnailClick = (0, import_react53.useCallback)((pageNum) => {
12420
12854
  onThumbnailClick?.(pageNum);
12421
12855
  viewerStore.getState().requestScrollToPage(pageNum, "smooth");
12422
12856
  }, [onThumbnailClick, viewerStore]);
12423
12857
  if (!document2 || numPages === 0) {
12424
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12858
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
12425
12859
  "div",
12426
12860
  {
12427
12861
  className: cn(
@@ -12442,7 +12876,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12442
12876
  }
12443
12877
  const isHorizontal = orientation === "horizontal";
12444
12878
  const totalSize = numPages * ((isHorizontal ? thumbnailWidth : thumbnailHeight) + gap) - gap;
12445
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12879
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
12446
12880
  "div",
12447
12881
  {
12448
12882
  ref: containerRef,
@@ -12456,7 +12890,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12456
12890
  style: {
12457
12891
  ...isHorizontal ? { overflowX: "auto", overflowY: "hidden" } : { overflowX: "hidden", overflowY: "auto" }
12458
12892
  },
12459
- children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12893
+ children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
12460
12894
  "div",
12461
12895
  {
12462
12896
  className: cn(
@@ -12473,7 +12907,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12473
12907
  const thumbnail = thumbnails.get(pageNum);
12474
12908
  const isActive = pageNum === currentPage;
12475
12909
  const isVisible = pageNum >= visibleRange.start && pageNum <= visibleRange.end;
12476
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
12910
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
12477
12911
  "div",
12478
12912
  {
12479
12913
  className: cn(
@@ -12498,7 +12932,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12498
12932
  }
12499
12933
  },
12500
12934
  children: [
12501
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12935
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
12502
12936
  "div",
12503
12937
  {
12504
12938
  className: "relative bg-white dark:bg-gray-700",
@@ -12506,7 +12940,7 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12506
12940
  width: thumbnailWidth,
12507
12941
  height: thumbnailHeight
12508
12942
  },
12509
- children: isVisible && thumbnail?.canvas ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12943
+ children: isVisible && thumbnail?.canvas ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
12510
12944
  "img",
12511
12945
  {
12512
12946
  src: thumbnail.canvas.toDataURL(),
@@ -12514,10 +12948,10 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12514
12948
  className: "w-full h-full object-contain",
12515
12949
  loading: "lazy"
12516
12950
  }
12517
- ) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "absolute inset-0 flex items-center justify-center text-gray-400 dark:text-gray-500 text-xs", children: pageNum })
12951
+ ) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: "absolute inset-0 flex items-center justify-center text-gray-400 dark:text-gray-500 text-xs", children: pageNum })
12518
12952
  }
12519
12953
  ),
12520
- showPageNumbers && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
12954
+ showPageNumbers && /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
12521
12955
  "div",
12522
12956
  {
12523
12957
  className: cn(
@@ -12540,10 +12974,10 @@ var PDFThumbnailNav = (0, import_react52.memo)(function PDFThumbnailNav2({
12540
12974
  });
12541
12975
 
12542
12976
  // src/components/ErrorBoundary/PDFErrorBoundary.tsx
12543
- var import_react53 = require("react");
12977
+ var import_react54 = require("react");
12544
12978
  init_utils();
12545
- var import_jsx_runtime39 = require("react/jsx-runtime");
12546
- var PDFErrorBoundary = class extends import_react53.Component {
12979
+ var import_jsx_runtime40 = require("react/jsx-runtime");
12980
+ var PDFErrorBoundary = class extends import_react54.Component {
12547
12981
  constructor(props) {
12548
12982
  super(props);
12549
12983
  this.handleReset = () => {
@@ -12570,7 +13004,7 @@ var PDFErrorBoundary = class extends import_react53.Component {
12570
13004
  return fallback;
12571
13005
  }
12572
13006
  if (showDefaultUI) {
12573
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
13007
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12574
13008
  DefaultErrorUI,
12575
13009
  {
12576
13010
  error,
@@ -12589,7 +13023,7 @@ function DefaultErrorUI({ error, onReset, className }) {
12589
13023
  const isNetworkError = error.message.includes("fetch") || error.message.includes("network") || error.message.includes("Failed to load");
12590
13024
  let title = "Something went wrong";
12591
13025
  let description = error.message;
12592
- let icon = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
13026
+ let icon = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12593
13027
  "path",
12594
13028
  {
12595
13029
  strokeLinecap: "round",
@@ -12601,7 +13035,7 @@ function DefaultErrorUI({ error, onReset, className }) {
12601
13035
  if (isPDFError) {
12602
13036
  title = "Unable to load PDF";
12603
13037
  description = "The PDF file could not be loaded. It may be corrupted or in an unsupported format.";
12604
- icon = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
13038
+ icon = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12605
13039
  "path",
12606
13040
  {
12607
13041
  strokeLinecap: "round",
@@ -12613,7 +13047,7 @@ function DefaultErrorUI({ error, onReset, className }) {
12613
13047
  } else if (isNetworkError) {
12614
13048
  title = "Network error";
12615
13049
  description = "Unable to fetch the PDF file. Please check your internet connection and try again.";
12616
- icon = /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
13050
+ icon = /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12617
13051
  "path",
12618
13052
  {
12619
13053
  strokeLinecap: "round",
@@ -12623,7 +13057,7 @@ function DefaultErrorUI({ error, onReset, className }) {
12623
13057
  }
12624
13058
  ) });
12625
13059
  }
12626
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
13060
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
12627
13061
  "div",
12628
13062
  {
12629
13063
  className: cn(
@@ -12636,14 +13070,14 @@ function DefaultErrorUI({ error, onReset, className }) {
12636
13070
  ),
12637
13071
  children: [
12638
13072
  icon,
12639
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("h2", { className: "mt-4 text-xl font-semibold text-gray-900 dark:text-gray-100", children: title }),
12640
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 max-w-md", children: description }),
12641
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("details", { className: "mt-4 text-left max-w-md w-full", children: [
12642
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("summary", { className: "cursor-pointer text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", children: "Technical details" }),
12643
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("pre", { className: "mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto", children: error.stack || error.message })
13073
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("h2", { className: "mt-4 text-xl font-semibold text-gray-900 dark:text-gray-100", children: title }),
13074
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 max-w-md", children: description }),
13075
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("details", { className: "mt-4 text-left max-w-md w-full", children: [
13076
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("summary", { className: "cursor-pointer text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", children: "Technical details" }),
13077
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("pre", { className: "mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto", children: error.stack || error.message })
12644
13078
  ] }),
12645
- /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "mt-6 flex gap-3", children: [
12646
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
13079
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "mt-6 flex gap-3", children: [
13080
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12647
13081
  "button",
12648
13082
  {
12649
13083
  onClick: onReset,
@@ -12657,7 +13091,7 @@ function DefaultErrorUI({ error, onReset, className }) {
12657
13091
  children: "Try again"
12658
13092
  }
12659
13093
  ),
12660
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
13094
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
12661
13095
  "button",
12662
13096
  {
12663
13097
  onClick: () => window.location.reload(),
@@ -12677,12 +13111,2173 @@ function DefaultErrorUI({ error, onReset, className }) {
12677
13111
  );
12678
13112
  }
12679
13113
  function withErrorBoundary({ component, ...props }) {
12680
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(PDFErrorBoundary, { ...props, children: component });
13114
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(PDFErrorBoundary, { ...props, children: component });
12681
13115
  }
12682
13116
 
12683
13117
  // src/components/index.ts
12684
13118
  init_PDFLoadingScreen2();
12685
13119
 
13120
+ // src/components/TutorMode/TutorModeContainer.tsx
13121
+ var import_react56 = require("react");
13122
+ var import_zustand2 = require("zustand");
13123
+ init_PDFPage2();
13124
+ init_hooks();
13125
+
13126
+ // src/components/TutorMode/CameraView.tsx
13127
+ var import_framer_motion = require("framer-motion");
13128
+ var import_jsx_runtime41 = require("react/jsx-runtime");
13129
+ function CameraView({
13130
+ camera,
13131
+ children,
13132
+ durationMs = 700,
13133
+ className
13134
+ }) {
13135
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
13136
+ import_framer_motion.motion.div,
13137
+ {
13138
+ className,
13139
+ style: {
13140
+ transformOrigin: "50% 50%",
13141
+ willChange: "transform",
13142
+ width: "100%",
13143
+ height: "100%",
13144
+ position: "relative"
13145
+ },
13146
+ animate: {
13147
+ scale: camera.scale,
13148
+ x: camera.x,
13149
+ y: camera.y
13150
+ },
13151
+ transition: {
13152
+ duration: durationMs / 1e3,
13153
+ ease: camera.easing === "linear" ? "linear" : camera.easing === "ease-in" ? [0.42, 0, 1, 1] : camera.easing === "ease-out" ? [0, 0, 0.58, 1] : [0.42, 0, 0.58, 1]
13154
+ },
13155
+ children
13156
+ }
13157
+ );
13158
+ }
13159
+
13160
+ // src/components/TutorMode/CinemaLayer.tsx
13161
+ var import_framer_motion10 = require("framer-motion");
13162
+
13163
+ // src/components/TutorMode/SpotlightMask.tsx
13164
+ var import_react55 = require("react");
13165
+ var import_framer_motion2 = require("framer-motion");
13166
+ var import_jsx_runtime42 = require("react/jsx-runtime");
13167
+ function SpotlightMask({
13168
+ page,
13169
+ bbox,
13170
+ action,
13171
+ durationMs = 400
13172
+ }) {
13173
+ const maskId = (0, import_react55.useId)();
13174
+ const filterId = `${maskId}-blur`;
13175
+ const [x1, y1, x2, y2] = bbox;
13176
+ const w = Math.max(0, x2 - x1);
13177
+ const h = Math.max(0, y2 - y1);
13178
+ const rx = action.shape === "rounded" ? 12 : action.shape === "ellipse" ? w / 2 : 0;
13179
+ const ry = action.shape === "rounded" ? 12 : action.shape === "ellipse" ? h / 2 : 0;
13180
+ const feather = action.feather_px;
13181
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
13182
+ "svg",
13183
+ {
13184
+ viewBox: `0 0 ${page.width} ${page.height}`,
13185
+ width: page.width,
13186
+ height: page.height,
13187
+ preserveAspectRatio: "none",
13188
+ style: {
13189
+ position: "absolute",
13190
+ inset: 0,
13191
+ pointerEvents: "none",
13192
+ width: page.width,
13193
+ height: page.height
13194
+ },
13195
+ "data-role": "spotlight-mask",
13196
+ children: [
13197
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("defs", { children: [
13198
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("filter", { id: filterId, x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("feGaussianBlur", { in: "SourceGraphic", stdDeviation: feather / 4 }) }),
13199
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("mask", { id: maskId, children: [
13200
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("rect", { x: 0, y: 0, width: page.width, height: page.height, fill: "white" }),
13201
+ action.shape === "ellipse" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
13202
+ "ellipse",
13203
+ {
13204
+ cx: (x1 + x2) / 2,
13205
+ cy: (y1 + y2) / 2,
13206
+ rx: w / 2,
13207
+ ry: h / 2,
13208
+ fill: "black",
13209
+ filter: `url(#${filterId})`
13210
+ }
13211
+ ) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
13212
+ "rect",
13213
+ {
13214
+ x: x1,
13215
+ y: y1,
13216
+ width: w,
13217
+ height: h,
13218
+ rx,
13219
+ ry,
13220
+ fill: "black",
13221
+ filter: `url(#${filterId})`
13222
+ }
13223
+ )
13224
+ ] })
13225
+ ] }),
13226
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
13227
+ import_framer_motion2.motion.rect,
13228
+ {
13229
+ x: 0,
13230
+ y: 0,
13231
+ width: page.width,
13232
+ height: page.height,
13233
+ fill: "black",
13234
+ mask: `url(#${maskId})`,
13235
+ initial: { fillOpacity: 0 },
13236
+ animate: { fillOpacity: action.dim_opacity },
13237
+ exit: { fillOpacity: 0 },
13238
+ transition: { duration: durationMs / 1e3, ease: "easeOut" }
13239
+ }
13240
+ )
13241
+ ]
13242
+ }
13243
+ );
13244
+ }
13245
+
13246
+ // src/components/TutorMode/AnimatedUnderline.tsx
13247
+ var import_framer_motion3 = require("framer-motion");
13248
+ var import_jsx_runtime43 = require("react/jsx-runtime");
13249
+ function pathForStyle(x1, x2, y, style) {
13250
+ if (style === "straight") return `M ${x1} ${y} L ${x2} ${y}`;
13251
+ if (style === "double")
13252
+ return `M ${x1} ${y - 3} L ${x2} ${y - 3} M ${x1} ${y + 3} L ${x2} ${y + 3}`;
13253
+ if (style === "wavy") {
13254
+ const steps = Math.max(8, Math.floor((x2 - x1) / 18));
13255
+ let d2 = `M ${x1} ${y}`;
13256
+ for (let i = 1; i <= steps; i++) {
13257
+ const px = x1 + (x2 - x1) * i / steps;
13258
+ const dy = i % 2 === 0 ? 4 : -4;
13259
+ d2 += ` Q ${px - (x2 - x1) / (2 * steps)} ${y + dy} ${px} ${y}`;
13260
+ }
13261
+ return d2;
13262
+ }
13263
+ const segs = 6;
13264
+ let d = `M ${x1} ${y}`;
13265
+ for (let i = 1; i <= segs; i++) {
13266
+ const px = x1 + (x2 - x1) * i / segs;
13267
+ const jitter = (Math.random() - 0.5) * 4;
13268
+ d += ` L ${px} ${y + jitter}`;
13269
+ }
13270
+ return d;
13271
+ }
13272
+ function AnimatedUnderline({ bbox, action }) {
13273
+ const [x1, , x2, y2] = bbox;
13274
+ const y = y2 + 6;
13275
+ const d = pathForStyle(x1, x2, y, action.style);
13276
+ const duration = action.draw_duration_ms / 1e3;
13277
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
13278
+ "svg",
13279
+ {
13280
+ style: {
13281
+ position: "absolute",
13282
+ inset: 0,
13283
+ pointerEvents: "none",
13284
+ overflow: "visible"
13285
+ },
13286
+ "data-role": "underline",
13287
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
13288
+ import_framer_motion3.motion.path,
13289
+ {
13290
+ d,
13291
+ fill: "none",
13292
+ stroke: action.color,
13293
+ strokeWidth: 4,
13294
+ strokeLinecap: "round",
13295
+ initial: { pathLength: 0, opacity: 0 },
13296
+ animate: { pathLength: 1, opacity: 1 },
13297
+ exit: { opacity: 0 },
13298
+ transition: { duration, ease: "easeOut" }
13299
+ }
13300
+ )
13301
+ }
13302
+ );
13303
+ }
13304
+
13305
+ // src/components/TutorMode/AnimatedHighlight.tsx
13306
+ var import_framer_motion4 = require("framer-motion");
13307
+ var import_jsx_runtime44 = require("react/jsx-runtime");
13308
+ function AnimatedHighlight({ bbox, action }) {
13309
+ const [x1, y1, x2, y2] = bbox;
13310
+ const w = x2 - x1;
13311
+ const h = y2 - y1;
13312
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
13313
+ import_framer_motion4.motion.div,
13314
+ {
13315
+ style: {
13316
+ position: "absolute",
13317
+ left: x1,
13318
+ top: y1,
13319
+ height: h,
13320
+ background: action.color,
13321
+ borderRadius: 4,
13322
+ mixBlendMode: "multiply",
13323
+ transformOrigin: "0% 50%",
13324
+ pointerEvents: "none"
13325
+ },
13326
+ initial: { width: 0, opacity: 0.9 },
13327
+ animate: { width: w, opacity: 0.9 },
13328
+ exit: { opacity: 0 },
13329
+ transition: { duration: action.draw_duration_ms / 1e3, ease: "easeOut" },
13330
+ "data-role": "highlight"
13331
+ }
13332
+ );
13333
+ }
13334
+
13335
+ // src/components/TutorMode/PulseOverlay.tsx
13336
+ var import_framer_motion5 = require("framer-motion");
13337
+ var import_jsx_runtime45 = require("react/jsx-runtime");
13338
+ var INTENSITY = {
13339
+ subtle: { scale: 1.02, border: "2px solid rgba(59,130,246,0.6)" },
13340
+ normal: { scale: 1.05, border: "3px solid rgba(59,130,246,0.8)" },
13341
+ strong: { scale: 1.1, border: "4px solid rgba(59,130,246,1.0)" }
13342
+ };
13343
+ function PulseOverlay({ bbox, action }) {
13344
+ const [x1, y1, x2, y2] = bbox;
13345
+ const { scale, border } = INTENSITY[action.intensity];
13346
+ const repeat = action.count === 1 ? 0 : action.count - 1;
13347
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
13348
+ import_framer_motion5.motion.div,
13349
+ {
13350
+ style: {
13351
+ position: "absolute",
13352
+ left: x1,
13353
+ top: y1,
13354
+ width: x2 - x1,
13355
+ height: y2 - y1,
13356
+ border,
13357
+ borderRadius: 8,
13358
+ pointerEvents: "none",
13359
+ boxSizing: "border-box"
13360
+ },
13361
+ animate: { scale: [1, scale, 1] },
13362
+ transition: {
13363
+ duration: 1.2,
13364
+ times: [0, 0.5, 1],
13365
+ ease: "easeInOut",
13366
+ repeat,
13367
+ repeatType: "loop"
13368
+ },
13369
+ exit: { opacity: 0 },
13370
+ "data-role": "pulse"
13371
+ }
13372
+ );
13373
+ }
13374
+
13375
+ // src/components/TutorMode/CalloutArrow.tsx
13376
+ var import_framer_motion6 = require("framer-motion");
13377
+ var import_jsx_runtime46 = require("react/jsx-runtime");
13378
+ function centerOf(b) {
13379
+ return { x: (b[0] + b[2]) / 2, y: (b[1] + b[3]) / 2 };
13380
+ }
13381
+ function arrowPath(fromBbox, toBbox, curve) {
13382
+ const a = centerOf(fromBbox);
13383
+ const b = centerOf(toBbox);
13384
+ if (curve === "straight") return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
13385
+ if (curve === "zigzag") {
13386
+ const mx = (a.x + b.x) / 2;
13387
+ return `M ${a.x} ${a.y} L ${mx} ${a.y} L ${mx} ${b.y} L ${b.x} ${b.y}`;
13388
+ }
13389
+ const dx = b.x - a.x;
13390
+ const dy = b.y - a.y;
13391
+ const cx = (a.x + b.x) / 2 - dy * 0.25;
13392
+ const cy = (a.y + b.y) / 2 + dx * 0.25;
13393
+ return `M ${a.x} ${a.y} Q ${cx} ${cy} ${b.x} ${b.y}`;
13394
+ }
13395
+ function CalloutArrow({ fromBbox, toBbox, action }) {
13396
+ const d = arrowPath(fromBbox, toBbox, action.curve);
13397
+ const label = action.label;
13398
+ const target = centerOf(toBbox);
13399
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
13400
+ "svg",
13401
+ {
13402
+ style: {
13403
+ position: "absolute",
13404
+ inset: 0,
13405
+ pointerEvents: "none",
13406
+ overflow: "visible"
13407
+ },
13408
+ "data-role": "callout",
13409
+ children: [
13410
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
13411
+ "marker",
13412
+ {
13413
+ id: "arrowhead",
13414
+ viewBox: "0 0 10 10",
13415
+ refX: "8",
13416
+ refY: "5",
13417
+ markerWidth: "8",
13418
+ markerHeight: "8",
13419
+ orient: "auto",
13420
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: "#3B82F6" })
13421
+ }
13422
+ ) }),
13423
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
13424
+ import_framer_motion6.motion.path,
13425
+ {
13426
+ d,
13427
+ fill: "none",
13428
+ stroke: "#3B82F6",
13429
+ strokeWidth: 3,
13430
+ strokeLinecap: "round",
13431
+ markerEnd: "url(#arrowhead)",
13432
+ initial: { pathLength: 0, opacity: 0 },
13433
+ animate: { pathLength: 1, opacity: 1 },
13434
+ exit: { opacity: 0 },
13435
+ transition: { duration: 0.6, ease: "easeOut" }
13436
+ }
13437
+ ),
13438
+ label ? /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
13439
+ import_framer_motion6.motion.g,
13440
+ {
13441
+ initial: { opacity: 0 },
13442
+ animate: { opacity: 1 },
13443
+ exit: { opacity: 0 },
13444
+ transition: { delay: 0.3, duration: 0.3 },
13445
+ children: [
13446
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
13447
+ "rect",
13448
+ {
13449
+ x: target.x - 4,
13450
+ y: target.y - 28,
13451
+ width: label.length * 9 + 12,
13452
+ height: 22,
13453
+ rx: 4,
13454
+ fill: "#1F2937"
13455
+ }
13456
+ ),
13457
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
13458
+ "text",
13459
+ {
13460
+ x: target.x + 2,
13461
+ y: target.y - 12,
13462
+ fill: "white",
13463
+ fontSize: 14,
13464
+ fontFamily: "system-ui, sans-serif",
13465
+ children: label
13466
+ }
13467
+ )
13468
+ ]
13469
+ }
13470
+ ) : null
13471
+ ]
13472
+ }
13473
+ );
13474
+ }
13475
+
13476
+ // src/components/TutorMode/GhostReference.tsx
13477
+ var import_framer_motion7 = require("framer-motion");
13478
+ var import_jsx_runtime47 = require("react/jsx-runtime");
13479
+ var POSITIONS = {
13480
+ "top-right": { top: 40, right: 40 },
13481
+ "top-left": { top: 40, left: 40 },
13482
+ "bottom-right": { bottom: 40, right: 40 },
13483
+ "bottom-left": { bottom: 40, left: 40 }
13484
+ };
13485
+ function GhostReference({
13486
+ page,
13487
+ sourceBbox,
13488
+ sourceBlockText,
13489
+ sourcePageNumber,
13490
+ action
13491
+ }) {
13492
+ const width = 360;
13493
+ const [x1, y1, x2, y2] = sourceBbox;
13494
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
13495
+ import_framer_motion7.motion.div,
13496
+ {
13497
+ initial: { opacity: 0, y: 20, scale: 0.95 },
13498
+ animate: { opacity: 1, y: 0, scale: 1 },
13499
+ exit: { opacity: 0, y: 20, scale: 0.95 },
13500
+ transition: { duration: 0.4, ease: "easeOut" },
13501
+ style: {
13502
+ position: "absolute",
13503
+ width,
13504
+ background: "#111",
13505
+ color: "white",
13506
+ borderRadius: 12,
13507
+ padding: 12,
13508
+ boxShadow: "0 10px 40px rgba(0,0,0,0.5)",
13509
+ pointerEvents: "none",
13510
+ fontFamily: "system-ui, sans-serif",
13511
+ fontSize: 13,
13512
+ ...POSITIONS[action.position]
13513
+ },
13514
+ "data-role": "ghost-reference",
13515
+ children: [
13516
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { style: { opacity: 0.7, fontSize: 11, marginBottom: 6 }, children: [
13517
+ "Page ",
13518
+ sourcePageNumber,
13519
+ " \u2014 ",
13520
+ action.target_block
13521
+ ] }),
13522
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
13523
+ "svg",
13524
+ {
13525
+ width: width - 24,
13526
+ height: 160,
13527
+ viewBox: `0 0 ${page.width} ${page.height}`,
13528
+ style: { background: "#1F2937", borderRadius: 6, display: "block" },
13529
+ preserveAspectRatio: "xMidYMid meet",
13530
+ children: [
13531
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
13532
+ "rect",
13533
+ {
13534
+ x: 0,
13535
+ y: 0,
13536
+ width: page.width,
13537
+ height: page.height,
13538
+ fill: "#1F2937"
13539
+ }
13540
+ ),
13541
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
13542
+ "rect",
13543
+ {
13544
+ x: x1,
13545
+ y: y1,
13546
+ width: x2 - x1,
13547
+ height: y2 - y1,
13548
+ fill: "rgba(250,204,21,0.45)",
13549
+ stroke: "#FBBF24",
13550
+ strokeWidth: 8
13551
+ }
13552
+ )
13553
+ ]
13554
+ }
13555
+ ),
13556
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
13557
+ "div",
13558
+ {
13559
+ style: {
13560
+ marginTop: 8,
13561
+ fontSize: 12,
13562
+ lineHeight: 1.4,
13563
+ opacity: 0.9
13564
+ },
13565
+ children: sourceBlockText ?? "(figure)"
13566
+ }
13567
+ )
13568
+ ]
13569
+ }
13570
+ );
13571
+ }
13572
+
13573
+ // src/components/TutorMode/BoxOverlay.tsx
13574
+ var import_framer_motion8 = require("framer-motion");
13575
+ var import_jsx_runtime48 = require("react/jsx-runtime");
13576
+ function BoxOverlay({ bbox, action }) {
13577
+ const [x1, y1, x2, y2] = bbox;
13578
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
13579
+ import_framer_motion8.motion.div,
13580
+ {
13581
+ initial: { opacity: 0, scale: 0.97 },
13582
+ animate: { opacity: 1, scale: 1 },
13583
+ exit: { opacity: 0 },
13584
+ transition: { duration: 0.35, ease: "easeOut" },
13585
+ style: {
13586
+ position: "absolute",
13587
+ left: x1,
13588
+ top: y1,
13589
+ width: x2 - x1,
13590
+ height: y2 - y1,
13591
+ border: `${action.style === "dashed" ? "3px dashed" : "3px solid"} ${action.color}`,
13592
+ borderRadius: 6,
13593
+ pointerEvents: "none",
13594
+ boxSizing: "border-box"
13595
+ },
13596
+ "data-role": "box"
13597
+ }
13598
+ );
13599
+ }
13600
+
13601
+ // src/components/TutorMode/StickyLabel.tsx
13602
+ var import_framer_motion9 = require("framer-motion");
13603
+ var import_jsx_runtime49 = require("react/jsx-runtime");
13604
+ function position(bbox, where) {
13605
+ const [x1, y1, x2, y2] = bbox;
13606
+ const cx = (x1 + x2) / 2;
13607
+ const cy = (y1 + y2) / 2;
13608
+ const PAD = 16;
13609
+ switch (where) {
13610
+ case "top":
13611
+ return { left: cx, top: y1 - PAD, transform: "translate(-50%, -100%)" };
13612
+ case "bottom":
13613
+ return { left: cx, top: y2 + PAD, transform: "translate(-50%, 0)" };
13614
+ case "left":
13615
+ return { left: x1 - PAD, top: cy, transform: "translate(-100%, -50%)" };
13616
+ case "right":
13617
+ return { left: x2 + PAD, top: cy, transform: "translate(0, -50%)" };
13618
+ default:
13619
+ return { left: cx, top: y1, transform: "translate(-50%, -100%)" };
13620
+ }
13621
+ }
13622
+ function StickyLabel({ bbox, action }) {
13623
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
13624
+ import_framer_motion9.motion.div,
13625
+ {
13626
+ initial: { opacity: 0, scale: 0.9 },
13627
+ animate: { opacity: 1, scale: 1 },
13628
+ exit: { opacity: 0 },
13629
+ transition: { duration: 0.35, ease: "easeOut" },
13630
+ style: {
13631
+ position: "absolute",
13632
+ padding: "6px 10px",
13633
+ background: "#FEF3C7",
13634
+ color: "#78350F",
13635
+ borderRadius: 6,
13636
+ boxShadow: "0 3px 10px rgba(0,0,0,0.2)",
13637
+ fontSize: 14,
13638
+ fontFamily: "system-ui, sans-serif",
13639
+ maxWidth: 280,
13640
+ pointerEvents: "none",
13641
+ ...position(bbox, action.position)
13642
+ },
13643
+ "data-role": "label",
13644
+ children: action.text
13645
+ }
13646
+ );
13647
+ }
13648
+
13649
+ // src/components/TutorMode/CinemaLayer.tsx
13650
+ var import_jsx_runtime50 = require("react/jsx-runtime");
13651
+ function blockBbox(index, block_id) {
13652
+ return index.blockById.get(block_id)?.block.bbox;
13653
+ }
13654
+ function CinemaLayer({
13655
+ page,
13656
+ index,
13657
+ overlays,
13658
+ scale
13659
+ }) {
13660
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
13661
+ "div",
13662
+ {
13663
+ "data-role": "cinema-layer",
13664
+ style: {
13665
+ position: "absolute",
13666
+ inset: 0,
13667
+ transformOrigin: "0 0",
13668
+ transform: `scale(${scale})`,
13669
+ width: page.page_dimensions.width,
13670
+ height: page.page_dimensions.height,
13671
+ pointerEvents: "none",
13672
+ // PDFPage renders internal layers at z-index 10/20/40/45/50
13673
+ // (canvas / text / highlight / focus / annotation). Without an
13674
+ // explicit z-index here, every tutor overlay stacks UNDER the
13675
+ // AnnotationLayer and becomes invisible. 100 puts us above all of
13676
+ // them while still letting the Exit button (z-index 60) remain
13677
+ // reachable because it sits OUTSIDE this stacking context.
13678
+ zIndex: 100
13679
+ },
13680
+ children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_framer_motion10.AnimatePresence, { children: overlays.map((overlay) => {
13681
+ switch (overlay.kind) {
13682
+ case "spotlight": {
13683
+ const a = overlay.action;
13684
+ const b = blockBbox(index, a.target_block);
13685
+ if (!b) return null;
13686
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
13687
+ SpotlightMask,
13688
+ {
13689
+ page: page.page_dimensions,
13690
+ bbox: b,
13691
+ action: a
13692
+ },
13693
+ overlay.id
13694
+ );
13695
+ }
13696
+ case "underline": {
13697
+ const a = overlay.action;
13698
+ const b = blockBbox(index, a.target_block);
13699
+ if (!b) return null;
13700
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(AnimatedUnderline, { bbox: b, action: a }, overlay.id);
13701
+ }
13702
+ case "highlight": {
13703
+ const a = overlay.action;
13704
+ const b = blockBbox(index, a.target_block);
13705
+ if (!b) return null;
13706
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(AnimatedHighlight, { bbox: b, action: a }, overlay.id);
13707
+ }
13708
+ case "pulse": {
13709
+ const a = overlay.action;
13710
+ const b = blockBbox(index, a.target_block);
13711
+ if (!b) return null;
13712
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(PulseOverlay, { bbox: b, action: a }, overlay.id);
13713
+ }
13714
+ case "callout": {
13715
+ const a = overlay.action;
13716
+ const from = blockBbox(index, a.from_block);
13717
+ const to = blockBbox(index, a.to_block);
13718
+ if (!from || !to) return null;
13719
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
13720
+ CalloutArrow,
13721
+ {
13722
+ fromBbox: from,
13723
+ toBbox: to,
13724
+ action: a
13725
+ },
13726
+ overlay.id
13727
+ );
13728
+ }
13729
+ case "ghost_reference": {
13730
+ const a = overlay.action;
13731
+ const hit = index.blockById.get(a.target_block);
13732
+ if (!hit) return null;
13733
+ const targetPage = index.byPage.get(a.target_page);
13734
+ if (!targetPage) return null;
13735
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
13736
+ GhostReference,
13737
+ {
13738
+ page: targetPage.page_dimensions,
13739
+ sourceBbox: hit.block.bbox,
13740
+ sourceBlockText: hit.block.text,
13741
+ sourcePageNumber: hit.pageNumber,
13742
+ action: a
13743
+ },
13744
+ overlay.id
13745
+ );
13746
+ }
13747
+ case "box": {
13748
+ const a = overlay.action;
13749
+ const b = blockBbox(index, a.target_block);
13750
+ if (!b) return null;
13751
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(BoxOverlay, { bbox: b, action: a }, overlay.id);
13752
+ }
13753
+ case "label": {
13754
+ const a = overlay.action;
13755
+ const b = blockBbox(index, a.target_block);
13756
+ if (!b) return null;
13757
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(StickyLabel, { bbox: b, action: a }, overlay.id);
13758
+ }
13759
+ case "clear":
13760
+ case "camera":
13761
+ return null;
13762
+ }
13763
+ }) })
13764
+ }
13765
+ );
13766
+ }
13767
+
13768
+ // src/components/TutorMode/SubtitleBar.tsx
13769
+ var import_framer_motion11 = require("framer-motion");
13770
+ var import_jsx_runtime51 = require("react/jsx-runtime");
13771
+ function SubtitleBar({ text }) {
13772
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_framer_motion11.AnimatePresence, { children: text ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
13773
+ import_framer_motion11.motion.div,
13774
+ {
13775
+ initial: { opacity: 0, y: 20 },
13776
+ animate: { opacity: 1, y: 0 },
13777
+ exit: { opacity: 0, y: 20 },
13778
+ transition: { duration: 0.3 },
13779
+ style: {
13780
+ position: "absolute",
13781
+ left: "50%",
13782
+ bottom: 32,
13783
+ transform: "translateX(-50%)",
13784
+ background: "rgba(0,0,0,0.75)",
13785
+ color: "white",
13786
+ padding: "10px 18px",
13787
+ borderRadius: 8,
13788
+ maxWidth: "80%",
13789
+ fontSize: 16,
13790
+ lineHeight: 1.4,
13791
+ fontFamily: "system-ui, sans-serif",
13792
+ pointerEvents: "none",
13793
+ zIndex: 50,
13794
+ textAlign: "center"
13795
+ },
13796
+ "data-role": "subtitle-bar",
13797
+ children: text
13798
+ },
13799
+ text
13800
+ ) : null });
13801
+ }
13802
+
13803
+ // src/director/storyboard-engine.ts
13804
+ init_narration_store();
13805
+ init_camera_math();
13806
+ var DEFAULT_MIN_OVERLAY_MS = 3500;
13807
+ var StoryboardEngine = class {
13808
+ constructor(deps) {
13809
+ this.pendingTimers = /* @__PURE__ */ new Set();
13810
+ this.currentStoryboardId = 0;
13811
+ this.deps = deps;
13812
+ }
13813
+ /**
13814
+ * Execute a new storyboard. Cancels in-flight steps from the previous storyboard
13815
+ * and smoothly transitions the camera/overlays from the current state.
13816
+ */
13817
+ execute(storyboard) {
13818
+ this.cancelPending();
13819
+ this.currentStoryboardId += 1;
13820
+ const storyboardId = this.currentStoryboardId;
13821
+ const { narrationStore } = this.deps;
13822
+ narrationStore.getState().setEngineStatus("transitioning");
13823
+ narrationStore.getState().setLastStoryboard(storyboard);
13824
+ let steps = [...storyboard.steps].sort((a, b) => a.at_ms - b.at_ms);
13825
+ const hasCamera = steps.some((s) => s.action.type === "camera");
13826
+ if (!hasCamera) {
13827
+ const focus = steps.find(
13828
+ (s) => s.action.type !== "clear" && "target_block" in s.action && s.action.target_block
13829
+ );
13830
+ if (focus && focus.action.type !== "clear" && "target_block" in focus.action) {
13831
+ steps = [
13832
+ {
13833
+ at_ms: 0,
13834
+ duration_ms: 700,
13835
+ action: {
13836
+ type: "camera",
13837
+ target_block: focus.action.target_block,
13838
+ scale: 1,
13839
+ padding: 60,
13840
+ easing: "ease-out"
13841
+ }
13842
+ },
13843
+ ...steps
13844
+ ];
13845
+ }
13846
+ }
13847
+ for (const step of steps) {
13848
+ const timer = setTimeout(() => {
13849
+ if (storyboardId !== this.currentStoryboardId) return;
13850
+ this.runStep(step);
13851
+ }, step.at_ms);
13852
+ this.pendingTimers.add(timer);
13853
+ }
13854
+ const markExecuting = setTimeout(() => {
13855
+ if (storyboardId !== this.currentStoryboardId) return;
13856
+ narrationStore.getState().setEngineStatus("executing");
13857
+ }, 0);
13858
+ this.pendingTimers.add(markExecuting);
13859
+ const last = steps[steps.length - 1];
13860
+ if (last) {
13861
+ const totalMs = last.at_ms + last.duration_ms;
13862
+ const markIdle = setTimeout(() => {
13863
+ if (storyboardId !== this.currentStoryboardId) return;
13864
+ narrationStore.getState().setEngineStatus("idle");
13865
+ }, totalMs + 50);
13866
+ this.pendingTimers.add(markIdle);
13867
+ }
13868
+ }
13869
+ /** Abort all pending steps and set engine status to idle. */
13870
+ cancelPending() {
13871
+ for (const t of this.pendingTimers) clearTimeout(t);
13872
+ this.pendingTimers.clear();
13873
+ this.deps.narrationStore.getState().setEngineStatus("idle");
13874
+ }
13875
+ /** Reset visuals: clear overlays, fit camera back to page. */
13876
+ resetVisuals() {
13877
+ this.cancelPending();
13878
+ const { narrationStore, bboxIndex, getViewport } = this.deps;
13879
+ narrationStore.getState().clearOverlays();
13880
+ const viewport = getViewport();
13881
+ const currentPage = narrationStore.getState().currentPage;
13882
+ const pageDims = bboxIndex.byPage.get(currentPage);
13883
+ const fit = pageDims && viewport.width > 0 && viewport.height > 0 ? fitPageScale(pageDims.page_dimensions, viewport) * 0.95 : 1;
13884
+ narrationStore.getState().setCamera({ scale: fit, x: 0, y: 0, easing: "ease-in-out" });
13885
+ }
13886
+ /** Execute one step — dispatch to narrationStore. Returns true if applied. */
13887
+ runStep(step) {
13888
+ const action = step.action;
13889
+ const { narrationStore, bboxIndex } = this.deps;
13890
+ if ("target_block" in action && action.target_block) {
13891
+ if (!bboxIndex.blockById.has(action.target_block)) {
13892
+ narrationStore.getState().appendDebugEvent({
13893
+ kind: "llm-error",
13894
+ summary: `dropped ${action.type} step \u2192 unknown target_block "${action.target_block}"`,
13895
+ payload: { action, validIds: [...bboxIndex.blockById.keys()] }
13896
+ });
13897
+ return false;
13898
+ }
13899
+ }
13900
+ if ("from_block" in action && action.from_block) {
13901
+ if (!bboxIndex.blockById.has(action.from_block)) {
13902
+ narrationStore.getState().appendDebugEvent({
13903
+ kind: "llm-error",
13904
+ summary: `dropped ${action.type} step \u2192 unknown from_block "${action.from_block}"`,
13905
+ payload: { action }
13906
+ });
13907
+ return false;
13908
+ }
13909
+ }
13910
+ if ("to_block" in action && action.to_block) {
13911
+ if (!bboxIndex.blockById.has(action.to_block)) {
13912
+ narrationStore.getState().appendDebugEvent({
13913
+ kind: "llm-error",
13914
+ summary: `dropped ${action.type} step \u2192 unknown to_block "${action.to_block}"`,
13915
+ payload: { action }
13916
+ });
13917
+ return false;
13918
+ }
13919
+ }
13920
+ if (action.type === "camera") {
13921
+ this.applyCamera(action, step.duration_ms);
13922
+ return true;
13923
+ }
13924
+ if (action.type === "clear") {
13925
+ const targets = action.targets;
13926
+ if (targets === "all" || targets === "overlays") {
13927
+ narrationStore.getState().clearOverlays();
13928
+ } else if (targets === "spotlights") {
13929
+ narrationStore.getState().clearOverlays((o) => o.kind === "spotlight");
13930
+ } else if (Array.isArray(targets)) {
13931
+ const ids = new Set(targets);
13932
+ narrationStore.getState().clearOverlays((o) => ids.has(o.id));
13933
+ }
13934
+ return true;
13935
+ }
13936
+ const minMs = this.deps.minOverlayDurationMs ?? DEFAULT_MIN_OVERLAY_MS;
13937
+ const visibleMs = Math.max(step.duration_ms, minMs);
13938
+ const overlay = {
13939
+ id: makeOverlayId(action),
13940
+ kind: action.type,
13941
+ action,
13942
+ createdAt: Date.now(),
13943
+ expiresAt: Date.now() + visibleMs
13944
+ };
13945
+ narrationStore.getState().addOverlay(overlay);
13946
+ const timer = setTimeout(() => {
13947
+ narrationStore.getState().removeOverlay(overlay.id);
13948
+ }, visibleMs);
13949
+ this.pendingTimers.add(timer);
13950
+ return true;
13951
+ }
13952
+ applyCamera(action, durationMs) {
13953
+ const { narrationStore, bboxIndex, getViewport } = this.deps;
13954
+ const viewport = getViewport();
13955
+ let bbox = action.target_bbox;
13956
+ let pageDims = void 0;
13957
+ if (!bbox && action.target_block) {
13958
+ const hit = bboxIndex.blockById.get(action.target_block);
13959
+ if (!hit) return;
13960
+ bbox = hit.block.bbox;
13961
+ pageDims = bboxIndex.byPage.get(hit.pageNumber);
13962
+ } else if (bbox) {
13963
+ pageDims = bboxIndex.byPage.get(narrationStore.getState().currentPage);
13964
+ }
13965
+ if (!bbox || !pageDims) return;
13966
+ const fit = fitPageScale(pageDims.page_dimensions, viewport);
13967
+ const requested = Math.max(0.5, Math.min(3, action.scale ?? 1));
13968
+ const finalScale = fit * requested;
13969
+ const [x1, y1, x2, y2] = bbox;
13970
+ const blockCX = (x1 + x2) / 2;
13971
+ const blockCY = (y1 + y2) / 2;
13972
+ const pageCX = pageDims.page_dimensions.width / 2;
13973
+ const pageCY = pageDims.page_dimensions.height / 2;
13974
+ const x = (pageCX - blockCX) * finalScale;
13975
+ const y = (pageCY - blockCY) * finalScale;
13976
+ const camera = {
13977
+ scale: finalScale,
13978
+ x,
13979
+ y,
13980
+ easing: action.easing
13981
+ };
13982
+ narrationStore.getState().setCamera(camera);
13983
+ void durationMs;
13984
+ void computeCameraForBlock;
13985
+ }
13986
+ };
13987
+
13988
+ // src/director/storyboard-schema.ts
13989
+ var import_zod = require("zod");
13990
+ var BBoxCoordsSchema = import_zod.z.tuple([import_zod.z.number(), import_zod.z.number(), import_zod.z.number(), import_zod.z.number()]);
13991
+ var CameraSchema = import_zod.z.object({
13992
+ type: import_zod.z.literal("camera"),
13993
+ target_block: import_zod.z.string().optional(),
13994
+ target_bbox: BBoxCoordsSchema.optional(),
13995
+ scale: import_zod.z.number().min(0.5).max(4).default(1),
13996
+ padding: import_zod.z.number().min(0).max(400).default(80),
13997
+ easing: import_zod.z.enum(["linear", "ease-in", "ease-out", "ease-in-out"]).default("ease-in-out")
13998
+ }).refine((a) => !!a.target_block || !!a.target_bbox, {
13999
+ message: "camera requires target_block or target_bbox"
14000
+ });
14001
+ var SpotlightSchema = import_zod.z.object({
14002
+ type: import_zod.z.literal("spotlight"),
14003
+ target_block: import_zod.z.string(),
14004
+ dim_opacity: import_zod.z.number().min(0).max(1).default(0.65),
14005
+ feather_px: import_zod.z.number().min(0).max(200).default(40),
14006
+ shape: import_zod.z.enum(["rect", "rounded", "ellipse"]).default("rounded")
14007
+ });
14008
+ var UnderlineSchema = import_zod.z.object({
14009
+ type: import_zod.z.literal("underline"),
14010
+ target_block: import_zod.z.string(),
14011
+ color: import_zod.z.string().default("#FBBF24"),
14012
+ style: import_zod.z.enum(["straight", "sketch", "double", "wavy"]).default("sketch"),
14013
+ draw_duration_ms: import_zod.z.number().min(100).max(3e3).default(600)
14014
+ });
14015
+ var HighlightSchema = import_zod.z.object({
14016
+ type: import_zod.z.literal("highlight"),
14017
+ target_block: import_zod.z.string(),
14018
+ color: import_zod.z.string().default("rgba(250, 204, 21, 0.35)"),
14019
+ draw_duration_ms: import_zod.z.number().min(100).max(3e3).default(500)
14020
+ });
14021
+ var PulseSchema = import_zod.z.object({
14022
+ type: import_zod.z.literal("pulse"),
14023
+ target_block: import_zod.z.string(),
14024
+ count: import_zod.z.number().int().min(1).max(5).default(2),
14025
+ intensity: import_zod.z.enum(["subtle", "normal", "strong"]).default("normal")
14026
+ });
14027
+ var CalloutSchema = import_zod.z.object({
14028
+ type: import_zod.z.literal("callout"),
14029
+ from_block: import_zod.z.string(),
14030
+ to_block: import_zod.z.string(),
14031
+ label: import_zod.z.string().max(120).optional(),
14032
+ curve: import_zod.z.enum(["straight", "curved", "zigzag"]).default("curved")
14033
+ });
14034
+ var GhostReferenceSchema = import_zod.z.object({
14035
+ type: import_zod.z.literal("ghost_reference"),
14036
+ target_page: import_zod.z.number().int().min(1),
14037
+ target_block: import_zod.z.string(),
14038
+ position: import_zod.z.enum(["top-right", "top-left", "bottom-right", "bottom-left"]).default("top-right")
14039
+ });
14040
+ var BoxSchema = import_zod.z.object({
14041
+ type: import_zod.z.literal("box"),
14042
+ target_block: import_zod.z.string(),
14043
+ color: import_zod.z.string().default("#3B82F6"),
14044
+ style: import_zod.z.enum(["solid", "dashed"]).default("solid")
14045
+ });
14046
+ var LabelSchema = import_zod.z.object({
14047
+ type: import_zod.z.literal("label"),
14048
+ target_block: import_zod.z.string(),
14049
+ text: import_zod.z.string().min(1).max(120),
14050
+ position: import_zod.z.enum(["top", "bottom", "left", "right"]).default("top")
14051
+ });
14052
+ var ClearSchema = import_zod.z.object({
14053
+ type: import_zod.z.literal("clear"),
14054
+ targets: import_zod.z.union([import_zod.z.enum(["all", "spotlights", "overlays"]), import_zod.z.array(import_zod.z.string())]).default("overlays")
14055
+ });
14056
+ var StoryboardActionSchema = import_zod.z.union([
14057
+ CameraSchema,
14058
+ SpotlightSchema,
14059
+ UnderlineSchema,
14060
+ HighlightSchema,
14061
+ PulseSchema,
14062
+ CalloutSchema,
14063
+ GhostReferenceSchema,
14064
+ BoxSchema,
14065
+ LabelSchema,
14066
+ ClearSchema
14067
+ ]);
14068
+ var StoryboardStepSchema = import_zod.z.object({
14069
+ at_ms: import_zod.z.number().min(0).max(5e3).default(0),
14070
+ duration_ms: import_zod.z.number().min(100).max(5e3).default(800),
14071
+ action: StoryboardActionSchema
14072
+ });
14073
+ var StoryboardSchema = import_zod.z.object({
14074
+ version: import_zod.z.literal(1),
14075
+ reasoning: import_zod.z.string().max(500).default(""),
14076
+ steps: import_zod.z.array(StoryboardStepSchema).min(1).max(4)
14077
+ });
14078
+ function storyboardJsonSchema(opts = {}) {
14079
+ const { validBlockIds, validCrossPageBlockIds } = opts;
14080
+ const blockIdSchema = validBlockIds && validBlockIds.length > 0 ? { type: ["string", "null"], enum: [...validBlockIds, null] } : { type: ["string", "null"] };
14081
+ const crossPageBlockIdSchema = validCrossPageBlockIds && validCrossPageBlockIds.length > 0 ? {
14082
+ type: ["string", "null"],
14083
+ enum: [...validCrossPageBlockIds, ...validBlockIds ?? [], null]
14084
+ } : blockIdSchema;
14085
+ const actionSchema = {
14086
+ type: "object",
14087
+ additionalProperties: false,
14088
+ required: [
14089
+ "type",
14090
+ "target_block",
14091
+ "target_bbox",
14092
+ "scale",
14093
+ "padding",
14094
+ "easing",
14095
+ "dim_opacity",
14096
+ "feather_px",
14097
+ "shape",
14098
+ "color",
14099
+ "style",
14100
+ "draw_duration_ms",
14101
+ "count",
14102
+ "intensity",
14103
+ "from_block",
14104
+ "to_block",
14105
+ "label",
14106
+ "curve",
14107
+ "target_page",
14108
+ "position",
14109
+ "text",
14110
+ "targets"
14111
+ ],
14112
+ properties: {
14113
+ type: {
14114
+ type: "string",
14115
+ enum: [
14116
+ "camera",
14117
+ "spotlight",
14118
+ "underline",
14119
+ "highlight",
14120
+ "pulse",
14121
+ "callout",
14122
+ "ghost_reference",
14123
+ "box",
14124
+ "label",
14125
+ "clear"
14126
+ ]
14127
+ },
14128
+ target_block: blockIdSchema,
14129
+ target_bbox: {
14130
+ type: ["array", "null"],
14131
+ items: { type: "number" },
14132
+ minItems: 4,
14133
+ maxItems: 4
14134
+ },
14135
+ scale: { type: ["number", "null"] },
14136
+ padding: { type: ["number", "null"] },
14137
+ easing: {
14138
+ type: ["string", "null"],
14139
+ enum: ["linear", "ease-in", "ease-out", "ease-in-out", null]
14140
+ },
14141
+ dim_opacity: { type: ["number", "null"] },
14142
+ feather_px: { type: ["number", "null"] },
14143
+ shape: {
14144
+ type: ["string", "null"],
14145
+ enum: ["rect", "rounded", "ellipse", null]
14146
+ },
14147
+ color: { type: ["string", "null"] },
14148
+ style: {
14149
+ type: ["string", "null"],
14150
+ enum: ["straight", "sketch", "double", "wavy", "solid", "dashed", null]
14151
+ },
14152
+ draw_duration_ms: { type: ["number", "null"] },
14153
+ count: { type: ["integer", "null"] },
14154
+ intensity: {
14155
+ type: ["string", "null"],
14156
+ enum: ["subtle", "normal", "strong", null]
14157
+ },
14158
+ from_block: blockIdSchema,
14159
+ to_block: crossPageBlockIdSchema,
14160
+ label: { type: ["string", "null"] },
14161
+ curve: {
14162
+ type: ["string", "null"],
14163
+ enum: ["straight", "curved", "zigzag", null]
14164
+ },
14165
+ target_page: { type: ["integer", "null"] },
14166
+ position: {
14167
+ type: ["string", "null"],
14168
+ enum: [
14169
+ "top",
14170
+ "bottom",
14171
+ "left",
14172
+ "right",
14173
+ "top-right",
14174
+ "top-left",
14175
+ "bottom-right",
14176
+ "bottom-left",
14177
+ null
14178
+ ]
14179
+ },
14180
+ text: { type: ["string", "null"] },
14181
+ targets: {
14182
+ type: ["string", "null"],
14183
+ enum: ["all", "spotlights", "overlays", null]
14184
+ }
14185
+ }
14186
+ };
14187
+ return {
14188
+ type: "object",
14189
+ additionalProperties: false,
14190
+ required: ["version", "reasoning", "steps"],
14191
+ properties: {
14192
+ version: { type: "integer", enum: [1] },
14193
+ reasoning: { type: "string" },
14194
+ steps: {
14195
+ type: "array",
14196
+ minItems: 1,
14197
+ maxItems: 4,
14198
+ items: {
14199
+ type: "object",
14200
+ additionalProperties: false,
14201
+ required: ["at_ms", "duration_ms", "action"],
14202
+ properties: {
14203
+ at_ms: { type: "number" },
14204
+ duration_ms: { type: "number" },
14205
+ action: actionSchema
14206
+ }
14207
+ }
14208
+ }
14209
+ }
14210
+ };
14211
+ }
14212
+
14213
+ // src/director/prompts.ts
14214
+ var SYSTEM_PROMPT = `You are the cinematic director of an AI tutor's PDF visualization. The tutor speaks one "chunk" at a time; for each chunk you anchor the visuals onto the EXACT blocks on the page the tutor is talking about, so the reader sees the page react like a produced teaching video. Think of yourself as a motion designer layering effects on top of a document \u2014 zoom is only one tool in the kit, and often not the right one.
14215
+
14216
+ # Your primary task
14217
+ You are given a list of blocks for the current page under "Page blocks", each with a \`block_id\`, \`text\`, \`type\`, \`bbox\`, and \`default_action\`. Your #1 job is to decide WHICH block(s) the current chunk is referring to, and then pick the right visual action(s) to anchor there.
14218
+
14219
+ Anchoring rules:
14220
+ - EVERY action that references a block MUST set \`target_block\` (or \`from_block\`/\`to_block\` for callouts) to an EXISTING \`block_id\` from "Page blocks" (or "Cross-page figures index" for cross-page refs). Never invent an id. Never emit a step whose target can't be found in the provided lists.
14221
+ - Match the chunk to blocks by semantic overlap with the block's \`text\`: quoted phrases, named entities, keywords, figure references ("Fig 3.2", "the suture"), list enumerations.
14222
+ - If no block clearly matches, emit a single \`camera\` step that fits the page (no overlays) and explain in \`reasoning\`. Do NOT spray overlays onto random blocks.
14223
+ - If multiple blocks match, pick the most specific one, or use a \`callout\` from one to the other.
14224
+
14225
+ # Output shape
14226
+ Output ONLY this JSON, nothing else:
14227
+ {
14228
+ "version": 1,
14229
+ "reasoning": "<which block(s) you picked, which intent you used, and why \u2014 name the block_id>",
14230
+ "steps": [ { "at_ms": <int>, "duration_ms": <int>, "action": <action> }, ... ]
14231
+ }
14232
+
14233
+ # Action shapes \u2014 ALL fields shown are REQUIRED per action type
14234
+ - camera: { "type":"camera", "target_block":"<id>", "scale":1.1, "padding":80, "easing":"ease-out" }
14235
+ - spotlight: { "type":"spotlight", "target_block":"<id>", "dim_opacity":0.65, "feather_px":40, "shape":"rounded" }
14236
+ - underline: { "type":"underline", "target_block":"<id>", "color":"#FBBF24", "style":"sketch", "draw_duration_ms":600 }
14237
+ - highlight: { "type":"highlight", "target_block":"<id>", "color":"rgba(250,204,21,0.35)", "draw_duration_ms":500 }
14238
+ - pulse: { "type":"pulse", "target_block":"<id>", "count":2, "intensity":"normal" }
14239
+ - callout: { "type":"callout", "from_block":"<id>", "to_block":"<id>", "label":"<text>", "curve":"curved" }
14240
+ - ghost_reference: { "type":"ghost_reference", "target_page":<int>, "target_block":"<id>", "position":"top-right" }
14241
+ - box: { "type":"box", "target_block":"<id>", "color":"#3B82F6", "style":"solid" }
14242
+ - label: { "type":"label", "target_block":"<id>", "text":"<text>", "position":"top" }
14243
+ - clear: { "type":"clear", "targets":"overlays" }
14244
+
14245
+ # When to use each action (match the effect to the narration's intent, not just the block type)
14246
+ - camera \u2014 when focus SHIFTS to a new region. If the primary block is already on-screen and roughly centered, you may skip camera entirely and start with an overlay. When you do use camera, prefer scale 1.1\u20131.4 for gentle re-centering; reserve 1.5+ for dense figures you need to inspect closely.
14247
+ - spotlight \u2014 when narration ISOLATES one idea, term, or sentence. Great for definitions, principles, and "the key insight is\u2026" moments.
14248
+ - underline \u2014 when narration QUOTES a phrase or reads a line word-by-word. Use style "sketch" for handwritten feel, "straight" for formal, "wavy" for emphasis. Pairs well with spotlight.
14249
+ - highlight \u2014 when narration FLAGS a keyword inline without full focus. Cheap, fast, great for list items, definitions-in-context, callback references.
14250
+ - pulse \u2014 when narration says "notice this" / "see here" / "look at the diagram". Use with figures, icons, and anchor blocks that should catch the eye without blocking surrounding content.
14251
+ - callout \u2014 when narration CONNECTS two things on the page: a caption to its figure, a label to a region, one list item to another. Arrow implies directional meaning.
14252
+ - ghost_reference \u2014 when narration REFERS to a block on a DIFFERENT page ("as we saw on page 2\u2026"). Never use for same-page references.
14253
+ - box \u2014 when narration FRAMES a structural region: a table, a sidebar, a group of related items, a diagram subpart. Use dashed style for "in-progress" / "under discussion" regions.
14254
+ - label \u2014 when narration attaches a NAMED TAG to a block: "this is the definition", "this is an example", "step 3". Keep text \u226440 chars for readability.
14255
+ - clear \u2014 rarely needed; the engine auto-expires overlays. Use only when you explicitly want to wipe prior state before a new beat.
14256
+
14257
+ Respect each block's \`default_action\` as a soft hint, but override it freely when narration intent calls for a different effect. A paragraph labeled \`default_action: spotlight\` can absolutely take a highlight or underline if that's what the narration asks for.
14258
+
14259
+ # Intent Taxonomy \u2014 canonical "recipes" you should use as your default vocabulary
14260
+ When narration fits one of these patterns, emit the corresponding storyboard shape (fill in real block_ids from the page). You may also COMPOSE freely beyond these \u2014 they are a floor, not a ceiling.
14261
+
14262
+ ## define \u2014 the narration introduces or defines a term
14263
+ Shape: spotlight the term + underline it + drop a label tag. No camera move if the block is already on-screen.
14264
+ {
14265
+ "version": 1,
14266
+ "reasoning": "define recipe: spotlighting and underlining the term, labeling as 'definition'",
14267
+ "steps": [
14268
+ { "at_ms":0, "duration_ms":700, "action": { "type":"spotlight", "target_block":"p1_para0", "dim_opacity":0.6, "feather_px":40, "shape":"rounded" } },
14269
+ { "at_ms":200, "duration_ms":800, "action": { "type":"underline", "target_block":"p1_para0", "color":"#FBBF24", "style":"sketch", "draw_duration_ms":700 } },
14270
+ { "at_ms":900, "duration_ms":1200, "action": { "type":"label", "target_block":"p1_para0", "text":"definition", "position":"top" } }
14271
+ ]
14272
+ }
14273
+
14274
+ ## point_out \u2014 the narration directs the viewer's eye to a figure, diagram, or specific region
14275
+ Shape: gentle camera move + callout arrow from caption to figure + pulse the figure.
14276
+ {
14277
+ "version": 1,
14278
+ "reasoning": "point_out recipe: drawing attention from caption p1_cap1 to figure p1_fig0",
14279
+ "steps": [
14280
+ { "at_ms":0, "duration_ms":600, "action": { "type":"camera", "target_block":"p1_fig0", "scale":1.3, "padding":80, "easing":"ease-out" } },
14281
+ { "at_ms":400, "duration_ms":900, "action": { "type":"callout", "from_block":"p1_cap1", "to_block":"p1_fig0", "label":"see here", "curve":"curved" } },
14282
+ { "at_ms":900, "duration_ms":1200, "action": { "type":"pulse", "target_block":"p1_fig0", "count":2, "intensity":"normal" } }
14283
+ ]
14284
+ }
14285
+
14286
+ ## compare \u2014 the narration contrasts two things on the page
14287
+ Shape: box A + box B + callout between them with a relational label.
14288
+ {
14289
+ "version": 1,
14290
+ "reasoning": "compare recipe: framing fibrous vs synovial joints",
14291
+ "steps": [
14292
+ { "at_ms":0, "duration_ms":600, "action": { "type":"box", "target_block":"p1_list5", "color":"#3B82F6", "style":"solid" } },
14293
+ { "at_ms":300, "duration_ms":600, "action": { "type":"box", "target_block":"p1_list12", "color":"#F472B6", "style":"solid" } },
14294
+ { "at_ms":800, "duration_ms":1000, "action": { "type":"callout", "from_block":"p1_list5", "to_block":"p1_list12", "label":"vs", "curve":"curved" } }
14295
+ ]
14296
+ }
14297
+
14298
+ ## emphasize \u2014 the narration stresses a keyword, warning, or takeaway
14299
+ Shape: highlight + pulse. Fast, punchy, no camera.
14300
+ {
14301
+ "version": 1,
14302
+ "reasoning": "emphasize recipe: highlighting key keyword and pulsing for stress",
14303
+ "steps": [
14304
+ { "at_ms":0, "duration_ms":500, "action": { "type":"highlight", "target_block":"p1_list0", "color":"rgba(250,204,21,0.35)", "draw_duration_ms":450 } },
14305
+ { "at_ms":350, "duration_ms":800, "action": { "type":"pulse", "target_block":"p1_list0", "count":2, "intensity":"strong" } }
14306
+ ]
14307
+ }
14308
+
14309
+ # Choreography rules
14310
+ - HARD REQUIREMENT: every storyboard MUST contain at least ONE non-camera step. A lone camera step is NEVER a valid output \u2014 the viewer needs an overlay to know WHY the camera moved. If you cannot find a good overlay target, emit a \`highlight\` or \`pulse\` on your primary block as the second step.
14311
+ - Favor VARIETY that matches narration texture \u2014 a definition earns different visuals than a comparison. Don't send every chunk through the same zoom+box motion.
14312
+ - Include a camera step only when focus genuinely shifts to a new region. If the primary target is already on-screen and roughly centred, SKIP the camera entirely and start directly with an overlay.
14313
+ - Camera scale: default to **1.1** (gentle re-centre). Use **1.2\u20131.3** for normal reading distance. Use **1.4\u20131.6** ONLY for dense figures or small inline details. NEVER use a scale below 0.5 or above 4.0 \u2014 the engine rejects those. When in doubt, use 1.1.
14314
+ - Prefer overlays that OVERLAP the camera move. A camera that takes 700ms to finish while overlays fire at 200ms feels cinematic; overlays waiting until after the camera settles feel sluggish. Stagger \`at_ms\`: camera at 0, first overlay at 150\u2013300ms, second overlay at 600\u2013900ms.
14315
+ - 2\u20134 steps is typical; single-step overlays (no camera) are PREFERRED when the target is already visible.
14316
+ - When narration compares two things, USE the compare recipe (box + box + callout), not a single camera step. When narration says "key takeaway", USE emphasize (highlight + pulse).
14317
+ - Never target a block_id not present in the provided lists. If no block matches, emit a single camera step at scale 1.0 + a subtle pulse on the nearest heading; explain in \`reasoning\`.
14318
+ - Output ONLY valid JSON. No markdown, no code fences, no commentary, no trailing whitespace inside property values.
14319
+
14320
+ # Forbidden outputs \u2014 these will be rejected:
14321
+ - A storyboard with only a camera step.
14322
+ - A camera step with scale < 0.5 or > 4.0.
14323
+ - target_block values not listed in "Page blocks" or "Cross-page figures index".
14324
+ - Tab characters, newlines, or explanatory text inside JSON string values.`;
14325
+ function truncate(text, max = 200) {
14326
+ if (!text) return "";
14327
+ if (text.length <= max) return text;
14328
+ const slice = text.slice(0, max);
14329
+ const last = slice.lastIndexOf(" ");
14330
+ return (last > 40 ? slice.slice(0, last) : slice) + "\u2026";
14331
+ }
14332
+ function buildUserPrompt(input) {
14333
+ const {
14334
+ chunk,
14335
+ pageNumber,
14336
+ page,
14337
+ index,
14338
+ history,
14339
+ camera,
14340
+ activeOverlays,
14341
+ maxSteps = 4
14342
+ } = input;
14343
+ const pageBlocks = page.blocks.map((b) => ({
14344
+ block_id: b.block_id,
14345
+ type: b.type,
14346
+ text: truncate(b.text, 200),
14347
+ bbox: b.bbox,
14348
+ default_action: b.default_action
14349
+ }));
14350
+ const xPageFigures = index.crossPageFigures.filter((f) => f.page !== pageNumber).slice(0, 20).map((f) => ({
14351
+ block_id: f.block_id,
14352
+ page: f.page,
14353
+ type: f.type,
14354
+ text: truncate(f.text, 200)
14355
+ }));
14356
+ const recent = history.slice(-3).map((h) => h.text);
14357
+ const overlaySummary = activeOverlays.map((o) => ({ id: o.id, kind: o.kind }));
14358
+ const blockIdList = pageBlocks.map((b) => b.block_id);
14359
+ return [
14360
+ `Current page: ${pageNumber}`,
14361
+ `Page blocks (${pageBlocks.length}) \u2014 you MUST pick target_block from this list:`,
14362
+ JSON.stringify(pageBlocks),
14363
+ "",
14364
+ `Valid block_ids for this page: ${JSON.stringify(blockIdList)}`,
14365
+ "",
14366
+ `Cross-page figures index: ${JSON.stringify(xPageFigures)}`,
14367
+ "",
14368
+ `Current chunk (what the tutor just said): ${JSON.stringify(chunk)}`,
14369
+ `Recent chunks: ${JSON.stringify(recent)}`,
14370
+ `Current camera: ${JSON.stringify(camera)}`,
14371
+ `Active overlays: ${JSON.stringify(overlaySummary)}`,
14372
+ "",
14373
+ `Max steps: ${maxSteps}`,
14374
+ `Output JSON storyboard. Every target_block MUST be one of the ids above.`
14375
+ ].join("\n");
14376
+ }
14377
+
14378
+ // src/director/sse-parser.ts
14379
+ async function* parseSse(body) {
14380
+ const reader = body.getReader();
14381
+ const decoder = new TextDecoder();
14382
+ let buffer = "";
14383
+ try {
14384
+ while (true) {
14385
+ const { value, done } = await reader.read();
14386
+ if (done) break;
14387
+ buffer += decoder.decode(value, { stream: true });
14388
+ let idx;
14389
+ while ((idx = buffer.indexOf("\n")) !== -1) {
14390
+ const rawLine = buffer.slice(0, idx).trim();
14391
+ buffer = buffer.slice(idx + 1);
14392
+ if (!rawLine.startsWith("data:")) continue;
14393
+ const payload = rawLine.slice(5).trim();
14394
+ if (!payload || payload === "[DONE]") continue;
14395
+ try {
14396
+ yield JSON.parse(payload);
14397
+ } catch {
14398
+ }
14399
+ }
14400
+ }
14401
+ } finally {
14402
+ reader.releaseLock();
14403
+ }
14404
+ }
14405
+ function extractDelta(chunk) {
14406
+ if (!chunk || typeof chunk !== "object") return null;
14407
+ const choices = chunk.choices;
14408
+ if (!choices || !choices.length) return null;
14409
+ return choices[0].delta?.content ?? null;
14410
+ }
14411
+
14412
+ // src/director/llm-director.ts
14413
+ async function directStoryboard(config, input) {
14414
+ const {
14415
+ endpointUrl,
14416
+ model,
14417
+ authToken,
14418
+ extraBody,
14419
+ maxTokens = 1024,
14420
+ temperature = 0.3,
14421
+ useJsonSchema = true,
14422
+ stream = false
14423
+ } = config;
14424
+ const userContent = buildUserPrompt(input);
14425
+ const body = {
14426
+ model,
14427
+ stream,
14428
+ temperature,
14429
+ max_tokens: maxTokens,
14430
+ messages: [
14431
+ { role: "system", content: SYSTEM_PROMPT },
14432
+ { role: "user", content: userContent }
14433
+ ],
14434
+ ...extraBody ?? {}
14435
+ };
14436
+ if (useJsonSchema) {
14437
+ const validBlockIds = input.page.blocks.map((b) => b.block_id);
14438
+ const validCrossPageBlockIds = input.index.crossPageFigures.filter((f) => f.page !== input.pageNumber).map((f) => f.block_id);
14439
+ body.response_format = {
14440
+ type: "json_schema",
14441
+ json_schema: {
14442
+ name: "storyboard",
14443
+ strict: true,
14444
+ schema: storyboardJsonSchema({
14445
+ validBlockIds,
14446
+ validCrossPageBlockIds
14447
+ })
14448
+ }
14449
+ };
14450
+ }
14451
+ const headers = {
14452
+ "Content-Type": "application/json",
14453
+ Accept: stream ? "text/event-stream" : "application/json"
14454
+ };
14455
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
14456
+ const timeoutController = new AbortController();
14457
+ const timer = setTimeout(
14458
+ () => timeoutController.abort(),
14459
+ input.timeoutMs ?? 2500
14460
+ );
14461
+ const signal = mergeSignals(input.signal, timeoutController.signal);
14462
+ try {
14463
+ const response = await fetch(endpointUrl, {
14464
+ method: "POST",
14465
+ headers,
14466
+ body: JSON.stringify(body),
14467
+ signal
14468
+ });
14469
+ if (!response.ok || !response.body) {
14470
+ return {
14471
+ storyboard: null,
14472
+ raw: "",
14473
+ error: `HTTP ${response.status}`
14474
+ };
14475
+ }
14476
+ let raw = "";
14477
+ if (stream && response.body) {
14478
+ for await (const chunk of parseSse(response.body)) {
14479
+ const delta = extractDelta(chunk);
14480
+ if (delta) raw += delta;
14481
+ }
14482
+ } else {
14483
+ const json = await response.json();
14484
+ raw = json.choices?.[0]?.message?.content ?? "";
14485
+ }
14486
+ const stripped = collapseWhitespaceRuns(stripCodeFences(raw).trim());
14487
+ let parsed;
14488
+ try {
14489
+ parsed = JSON.parse(stripped);
14490
+ } catch (e) {
14491
+ return {
14492
+ storyboard: null,
14493
+ raw,
14494
+ error: `parse error: ${e.message}`
14495
+ };
14496
+ }
14497
+ const cleaned = clampNumericRanges(stripNullsDeep(parsed));
14498
+ const validation = StoryboardSchema.safeParse(cleaned);
14499
+ if (validation.success) {
14500
+ return {
14501
+ storyboard: enforceOverlayPresence(validation.data),
14502
+ raw
14503
+ };
14504
+ }
14505
+ const salvaged = salvageStoryboard(cleaned);
14506
+ if (salvaged) {
14507
+ return { storyboard: enforceOverlayPresence(salvaged), raw };
14508
+ }
14509
+ return {
14510
+ storyboard: null,
14511
+ raw,
14512
+ error: `validation failed: ${validation.error.message}`
14513
+ };
14514
+ } catch (e) {
14515
+ const name = e.name;
14516
+ const msg = name === "AbortError" ? "aborted" : e.message;
14517
+ return { storyboard: null, raw: "", error: msg };
14518
+ } finally {
14519
+ clearTimeout(timer);
14520
+ }
14521
+ }
14522
+ function stripCodeFences(s) {
14523
+ const m = s.match(/```(?:json)?\s*([\s\S]*?)```/);
14524
+ return m ? m[1] : s;
14525
+ }
14526
+ function collapseWhitespaceRuns(src) {
14527
+ let out = "";
14528
+ let inString = false;
14529
+ let escape = false;
14530
+ let run = 0;
14531
+ for (let i = 0; i < src.length; i++) {
14532
+ const c = src[i];
14533
+ if (inString) {
14534
+ out += c;
14535
+ if (escape) {
14536
+ escape = false;
14537
+ } else if (c === "\\") {
14538
+ escape = true;
14539
+ } else if (c === '"') {
14540
+ inString = false;
14541
+ }
14542
+ continue;
14543
+ }
14544
+ if (c === '"') {
14545
+ out += c;
14546
+ inString = true;
14547
+ run = 0;
14548
+ continue;
14549
+ }
14550
+ if (c === " " || c === " " || c === "\n" || c === "\r") {
14551
+ run++;
14552
+ if (run <= 1) out += " ";
14553
+ continue;
14554
+ }
14555
+ run = 0;
14556
+ out += c;
14557
+ }
14558
+ return out;
14559
+ }
14560
+ function clampNumericRanges(input) {
14561
+ if (input === null || input === void 0) return input;
14562
+ if (Array.isArray(input)) return input.map(clampNumericRanges);
14563
+ if (typeof input !== "object") return input;
14564
+ const obj = input;
14565
+ const out = {};
14566
+ for (const [k, v] of Object.entries(obj)) {
14567
+ out[k] = clampNumericRanges(v);
14568
+ }
14569
+ const type = typeof out.type === "string" ? out.type : void 0;
14570
+ if (type === "camera") {
14571
+ if (typeof out.scale === "number") out.scale = clamp(out.scale, 0.5, 4);
14572
+ if (typeof out.padding === "number") {
14573
+ out.padding = clamp(out.padding, 0, 400);
14574
+ }
14575
+ }
14576
+ if (typeof out.dim_opacity === "number") {
14577
+ out.dim_opacity = clamp(out.dim_opacity, 0, 1);
14578
+ }
14579
+ if (typeof out.feather_px === "number") {
14580
+ out.feather_px = clamp(out.feather_px, 0, 200);
14581
+ }
14582
+ if (typeof out.draw_duration_ms === "number") {
14583
+ out.draw_duration_ms = clamp(out.draw_duration_ms, 100, 3e3);
14584
+ }
14585
+ if (typeof out.count === "number") {
14586
+ out.count = Math.round(clamp(out.count, 1, 5));
14587
+ }
14588
+ if (typeof out.at_ms === "number") {
14589
+ out.at_ms = clamp(out.at_ms, 0, 5e3);
14590
+ }
14591
+ if (typeof out.duration_ms === "number" && type === void 0) {
14592
+ out.duration_ms = clamp(out.duration_ms, 100, 5e3);
14593
+ }
14594
+ return out;
14595
+ }
14596
+ function clamp(v, lo, hi) {
14597
+ return Math.min(hi, Math.max(lo, v));
14598
+ }
14599
+ function enforceOverlayPresence(sb) {
14600
+ if (sb.steps.length === 0) return sb;
14601
+ const hasOverlay = sb.steps.some(
14602
+ (s) => s.action.type !== "camera" && s.action.type !== "clear"
14603
+ );
14604
+ if (hasOverlay) return sb;
14605
+ const cameraStep = sb.steps.find((s) => s.action.type === "camera");
14606
+ if (!cameraStep || cameraStep.action.type !== "camera") return sb;
14607
+ const target = cameraStep.action.target_block;
14608
+ if (!target) return sb;
14609
+ return {
14610
+ ...sb,
14611
+ reasoning: `${sb.reasoning} [auto-appended pulse: camera-only storyboards are forbidden]`,
14612
+ steps: [
14613
+ ...sb.steps,
14614
+ {
14615
+ at_ms: Math.min(4800, (cameraStep.at_ms ?? 0) + 200),
14616
+ duration_ms: 900,
14617
+ action: {
14618
+ type: "pulse",
14619
+ target_block: target,
14620
+ count: 2,
14621
+ intensity: "normal"
14622
+ }
14623
+ }
14624
+ ]
14625
+ };
14626
+ }
14627
+ function stripNullsDeep(input) {
14628
+ if (input === null) return void 0;
14629
+ if (Array.isArray(input)) {
14630
+ return input.map(stripNullsDeep).filter((v) => v !== void 0);
14631
+ }
14632
+ if (input && typeof input === "object") {
14633
+ const out = {};
14634
+ for (const [k, v] of Object.entries(input)) {
14635
+ const cleaned = stripNullsDeep(v);
14636
+ if (cleaned !== void 0) out[k] = cleaned;
14637
+ }
14638
+ return out;
14639
+ }
14640
+ return input;
14641
+ }
14642
+ function salvageStoryboard(parsed) {
14643
+ if (!parsed || typeof parsed !== "object") return null;
14644
+ const obj = parsed;
14645
+ if (!Array.isArray(obj.steps)) return null;
14646
+ const goodSteps = [];
14647
+ for (const step of obj.steps) {
14648
+ const r = StoryboardStepSchema.safeParse(step);
14649
+ if (r.success) goodSteps.push(r.data);
14650
+ if (goodSteps.length >= 4) break;
14651
+ }
14652
+ if (goodSteps.length === 0) return null;
14653
+ return {
14654
+ version: 1,
14655
+ reasoning: typeof obj.reasoning === "string" ? obj.reasoning + " (salvaged)" : "salvaged",
14656
+ steps: goodSteps
14657
+ };
14658
+ }
14659
+ function mergeSignals(a, b) {
14660
+ if (!a) return b;
14661
+ if (!b) return a;
14662
+ const ctrl = new AbortController();
14663
+ const onAbort = () => ctrl.abort();
14664
+ a.addEventListener("abort", onAbort);
14665
+ b.addEventListener("abort", onAbort);
14666
+ return ctrl.signal;
14667
+ }
14668
+
14669
+ // src/director/embedding-fallback.ts
14670
+ function cosineSimilarity(a, b) {
14671
+ let dot = 0;
14672
+ let na = 0;
14673
+ let nb = 0;
14674
+ const n = Math.min(a.length, b.length);
14675
+ for (let i = 0; i < n; i++) {
14676
+ dot += a[i] * b[i];
14677
+ na += a[i] * a[i];
14678
+ nb += b[i] * b[i];
14679
+ }
14680
+ const denom = Math.sqrt(na) * Math.sqrt(nb);
14681
+ return denom === 0 ? 0 : dot / denom;
14682
+ }
14683
+ async function matchChunkToBlock(chunk, page, provider) {
14684
+ const textBlocks = page.blocks.filter(
14685
+ (b) => typeof b.text === "string" && b.text.trim().length > 0
14686
+ );
14687
+ if (textBlocks.length === 0) return null;
14688
+ const inputs = [chunk, ...textBlocks.map((b) => b.text)];
14689
+ const embeds = await provider.embed(inputs);
14690
+ if (embeds.length < 2) return null;
14691
+ const chunkEmbed = embeds[0];
14692
+ let best = null;
14693
+ for (let i = 0; i < textBlocks.length; i++) {
14694
+ const score = cosineSimilarity(chunkEmbed, embeds[i + 1]);
14695
+ if (!best || score > best.score) best = { block: textBlocks[i], score };
14696
+ }
14697
+ return best;
14698
+ }
14699
+ function nearestFigureOnPage(caption, page) {
14700
+ if (!page) return null;
14701
+ const [cx1, cy1, cx2, cy2] = caption.bbox;
14702
+ const ccx = (cx1 + cx2) / 2;
14703
+ const ccy = (cy1 + cy2) / 2;
14704
+ let best = null;
14705
+ for (const b of page.blocks) {
14706
+ if (b.block_id === caption.block_id) continue;
14707
+ if (b.type !== "figure" && b.type !== "figure_region") continue;
14708
+ const [x1, y1, x2, y2] = b.bbox;
14709
+ const fx = (x1 + x2) / 2;
14710
+ const fy = (y1 + y2) / 2;
14711
+ const dist = Math.hypot(fx - ccx, fy - ccy);
14712
+ if (!best || dist < best.dist) best = { block: b, dist };
14713
+ }
14714
+ return best?.block ?? null;
14715
+ }
14716
+ function truncateLabel(text, max) {
14717
+ if (!text) return "";
14718
+ const clean = text.replace(/\s+/g, " ").trim();
14719
+ if (clean.length <= max) return clean;
14720
+ return clean.slice(0, max - 1) + "\u2026";
14721
+ }
14722
+ function storyboardFromMatch(match, page) {
14723
+ if (!match) {
14724
+ return {
14725
+ version: 1,
14726
+ reasoning: "fallback: no match \u2014 clearing overlays",
14727
+ steps: [
14728
+ {
14729
+ at_ms: 0,
14730
+ duration_ms: 800,
14731
+ action: { type: "clear", targets: "overlays" }
14732
+ }
14733
+ ]
14734
+ };
14735
+ }
14736
+ const { block } = match;
14737
+ const id = block.block_id;
14738
+ const reason = `fallback (block.type=${block.type}): matched ${id} (${match.score.toFixed(2)})`;
14739
+ switch (block.type) {
14740
+ case "heading": {
14741
+ return {
14742
+ version: 1,
14743
+ reasoning: reason,
14744
+ steps: [
14745
+ {
14746
+ at_ms: 0,
14747
+ duration_ms: 700,
14748
+ action: {
14749
+ type: "spotlight",
14750
+ target_block: id,
14751
+ dim_opacity: 0.6,
14752
+ feather_px: 40,
14753
+ shape: "rounded"
14754
+ }
14755
+ },
14756
+ {
14757
+ at_ms: 300,
14758
+ duration_ms: 1200,
14759
+ action: {
14760
+ type: "label",
14761
+ target_block: id,
14762
+ text: truncateLabel(block.text, 32) || "section",
14763
+ position: "top"
14764
+ }
14765
+ }
14766
+ ]
14767
+ };
14768
+ }
14769
+ case "paragraph": {
14770
+ return {
14771
+ version: 1,
14772
+ reasoning: reason,
14773
+ steps: [
14774
+ {
14775
+ at_ms: 0,
14776
+ duration_ms: 600,
14777
+ action: {
14778
+ type: "camera",
14779
+ target_block: id,
14780
+ scale: 1.1,
14781
+ padding: 80,
14782
+ easing: "ease-out"
14783
+ }
14784
+ },
14785
+ {
14786
+ at_ms: 300,
14787
+ duration_ms: 900,
14788
+ action: {
14789
+ type: "underline",
14790
+ target_block: id,
14791
+ color: "#FBBF24",
14792
+ style: "sketch",
14793
+ draw_duration_ms: 800
14794
+ }
14795
+ }
14796
+ ]
14797
+ };
14798
+ }
14799
+ case "list_item":
14800
+ case "mcq_option": {
14801
+ return {
14802
+ version: 1,
14803
+ reasoning: reason,
14804
+ steps: [
14805
+ {
14806
+ at_ms: 0,
14807
+ duration_ms: 500,
14808
+ action: {
14809
+ type: "highlight",
14810
+ target_block: id,
14811
+ color: "rgba(250, 204, 21, 0.35)",
14812
+ draw_duration_ms: 450
14813
+ }
14814
+ }
14815
+ ]
14816
+ };
14817
+ }
14818
+ case "caption": {
14819
+ const figure = nearestFigureOnPage(block, page);
14820
+ if (figure) {
14821
+ return {
14822
+ version: 1,
14823
+ reasoning: `${reason}; caption \u2192 figure ${figure.block_id}`,
14824
+ steps: [
14825
+ {
14826
+ at_ms: 0,
14827
+ duration_ms: 900,
14828
+ action: {
14829
+ type: "callout",
14830
+ from_block: id,
14831
+ to_block: figure.block_id,
14832
+ label: "see",
14833
+ curve: "curved"
14834
+ }
14835
+ },
14836
+ {
14837
+ at_ms: 600,
14838
+ duration_ms: 1e3,
14839
+ action: {
14840
+ type: "pulse",
14841
+ target_block: figure.block_id,
14842
+ count: 2,
14843
+ intensity: "normal"
14844
+ }
14845
+ }
14846
+ ]
14847
+ };
14848
+ }
14849
+ return {
14850
+ version: 1,
14851
+ reasoning: `${reason}; no figure on page, underlining caption`,
14852
+ steps: [
14853
+ {
14854
+ at_ms: 0,
14855
+ duration_ms: 800,
14856
+ action: {
14857
+ type: "underline",
14858
+ target_block: id,
14859
+ color: "#FBBF24",
14860
+ style: "sketch",
14861
+ draw_duration_ms: 700
14862
+ }
14863
+ }
14864
+ ]
14865
+ };
14866
+ }
14867
+ case "figure": {
14868
+ return {
14869
+ version: 1,
14870
+ reasoning: reason,
14871
+ steps: [
14872
+ {
14873
+ at_ms: 0,
14874
+ duration_ms: 900,
14875
+ action: {
14876
+ type: "pulse",
14877
+ target_block: id,
14878
+ count: 2,
14879
+ intensity: "strong"
14880
+ }
14881
+ },
14882
+ {
14883
+ at_ms: 400,
14884
+ duration_ms: 1200,
14885
+ action: {
14886
+ type: "box",
14887
+ target_block: id,
14888
+ color: "#3B82F6",
14889
+ style: "solid"
14890
+ }
14891
+ }
14892
+ ]
14893
+ };
14894
+ }
14895
+ case "figure_region": {
14896
+ return {
14897
+ version: 1,
14898
+ reasoning: reason,
14899
+ steps: [
14900
+ {
14901
+ at_ms: 0,
14902
+ duration_ms: 900,
14903
+ action: {
14904
+ type: "pulse",
14905
+ target_block: id,
14906
+ count: 2,
14907
+ intensity: "normal"
14908
+ }
14909
+ }
14910
+ ]
14911
+ };
14912
+ }
14913
+ case "table": {
14914
+ return {
14915
+ version: 1,
14916
+ reasoning: reason,
14917
+ steps: [
14918
+ {
14919
+ at_ms: 0,
14920
+ duration_ms: 700,
14921
+ action: {
14922
+ type: "camera",
14923
+ target_block: id,
14924
+ scale: 1.2,
14925
+ padding: 60,
14926
+ easing: "ease-out"
14927
+ }
14928
+ },
14929
+ {
14930
+ at_ms: 300,
14931
+ duration_ms: 1e3,
14932
+ action: {
14933
+ type: "box",
14934
+ target_block: id,
14935
+ color: "#3B82F6",
14936
+ style: "dashed"
14937
+ }
14938
+ }
14939
+ ]
14940
+ };
14941
+ }
14942
+ default: {
14943
+ return {
14944
+ version: 1,
14945
+ reasoning: `${reason}; unknown block.type, using highlight`,
14946
+ steps: [
14947
+ {
14948
+ at_ms: 0,
14949
+ duration_ms: 600,
14950
+ action: {
14951
+ type: "highlight",
14952
+ target_block: id,
14953
+ color: "rgba(250, 204, 21, 0.35)",
14954
+ draw_duration_ms: 500
14955
+ }
14956
+ }
14957
+ ]
14958
+ };
14959
+ }
14960
+ }
14961
+ }
14962
+
14963
+ // src/components/TutorMode/TutorModeContainer.tsx
14964
+ var import_jsx_runtime52 = require("react/jsx-runtime");
14965
+ function buildBBoxIndex(bboxData) {
14966
+ const byPage = /* @__PURE__ */ new Map();
14967
+ const blockById = /* @__PURE__ */ new Map();
14968
+ const crossPageFigures = [];
14969
+ for (const page of bboxData) {
14970
+ byPage.set(page.page_number, page);
14971
+ for (const block of page.blocks) {
14972
+ blockById.set(block.block_id, { block, pageNumber: page.page_number });
14973
+ if ((block.type === "figure" || block.type === "figure_region" || block.type === "caption") && typeof block.text === "string" && block.text.length > 0) {
14974
+ crossPageFigures.push({
14975
+ block_id: block.block_id,
14976
+ page: page.page_number,
14977
+ type: block.type,
14978
+ text: block.text
14979
+ });
14980
+ }
14981
+ }
14982
+ }
14983
+ return { byPage, blockById, crossPageFigures };
14984
+ }
14985
+ function TutorModeContainer({
14986
+ pageNumber,
14987
+ bboxData,
14988
+ narrationStore,
14989
+ scale,
14990
+ rotation = 0,
14991
+ currentChunk,
14992
+ llm,
14993
+ idleTimeoutMs = 5e3,
14994
+ llmTimeoutMs = 3e4,
14995
+ embeddingProvider,
14996
+ showSubtitles = false,
14997
+ showExitButton = true,
14998
+ onExitTutorMode,
14999
+ minOverlayDurationMs,
15000
+ className
15001
+ }) {
15002
+ const containerRef = (0, import_react56.useRef)(null);
15003
+ const index = (0, import_react56.useMemo)(() => buildBBoxIndex(bboxData), [bboxData]);
15004
+ const { document: document2 } = usePDFViewer();
15005
+ const [pageProxy, setPageProxy] = (0, import_react56.useState)(null);
15006
+ const [viewport, setViewport] = (0, import_react56.useState)({ width: 800, height: 1e3 });
15007
+ const camera = (0, import_zustand2.useStore)(narrationStore, (s) => s.camera);
15008
+ const activeOverlays = (0, import_zustand2.useStore)(narrationStore, (s) => s.activeOverlays);
15009
+ (0, import_react56.useEffect)(() => {
15010
+ if (!containerRef.current) return;
15011
+ const el = containerRef.current;
15012
+ const update = () => setViewport({ width: el.clientWidth, height: el.clientHeight });
15013
+ update();
15014
+ const ro = new ResizeObserver(update);
15015
+ ro.observe(el);
15016
+ return () => ro.disconnect();
15017
+ }, []);
15018
+ (0, import_react56.useEffect)(() => {
15019
+ if (!document2) {
15020
+ setPageProxy(null);
15021
+ return;
15022
+ }
15023
+ let cancelled = false;
15024
+ document2.getPage(pageNumber).then((p) => {
15025
+ if (!cancelled) setPageProxy(p);
15026
+ }).catch(() => {
15027
+ if (!cancelled) setPageProxy(null);
15028
+ });
15029
+ return () => {
15030
+ cancelled = true;
15031
+ };
15032
+ }, [document2, pageNumber]);
15033
+ (0, import_react56.useEffect)(() => {
15034
+ narrationStore.getState().setCurrentPage(pageNumber);
15035
+ }, [pageNumber, narrationStore]);
15036
+ (0, import_react56.useEffect)(() => {
15037
+ const page2 = index.byPage.get(pageNumber);
15038
+ if (!page2) return;
15039
+ if (viewport.width === 0 || viewport.height === 0) return;
15040
+ if (narrationStore.getState().activeOverlays.length > 0) return;
15041
+ const fit = Math.min(
15042
+ viewport.width / page2.page_dimensions.width,
15043
+ viewport.height / page2.page_dimensions.height
15044
+ ) * 0.95;
15045
+ narrationStore.getState().setCamera({ scale: fit, x: 0, y: 0 });
15046
+ }, [pageNumber, viewport, index, narrationStore]);
15047
+ const engineRef = (0, import_react56.useRef)(null);
15048
+ (0, import_react56.useEffect)(() => {
15049
+ engineRef.current = new StoryboardEngine({
15050
+ narrationStore,
15051
+ bboxIndex: index,
15052
+ getViewport: () => viewport,
15053
+ minOverlayDurationMs
15054
+ });
15055
+ return () => engineRef.current?.cancelPending();
15056
+ }, [narrationStore, index, viewport, minOverlayDurationMs]);
15057
+ const abortRef = (0, import_react56.useRef)(null);
15058
+ const debounceRef = (0, import_react56.useRef)(null);
15059
+ const lastChunkRef = (0, import_react56.useRef)(null);
15060
+ (0, import_react56.useEffect)(() => {
15061
+ if (!llm) return;
15062
+ if (!currentChunk || currentChunk === lastChunkRef.current) return;
15063
+ if (debounceRef.current) clearTimeout(debounceRef.current);
15064
+ debounceRef.current = setTimeout(async () => {
15065
+ const chunk = currentChunk;
15066
+ if (chunk === lastChunkRef.current) return;
15067
+ lastChunkRef.current = chunk;
15068
+ const page2 = index.byPage.get(pageNumber);
15069
+ if (!page2) return;
15070
+ narrationStore.getState().pushChunkHistory({
15071
+ text: chunk,
15072
+ pageNumber,
15073
+ timestamp: Date.now()
15074
+ });
15075
+ narrationStore.getState().appendDebugEvent({
15076
+ kind: "chunk",
15077
+ summary: `chunk \u2192 ${chunk.slice(0, 80)}${chunk.length > 80 ? "\u2026" : ""}`,
15078
+ payload: { chunk, pageNumber }
15079
+ });
15080
+ abortRef.current?.abort();
15081
+ abortRef.current = new AbortController();
15082
+ narrationStore.getState().setLlmStatus("in-flight");
15083
+ narrationStore.getState().appendDebugEvent({
15084
+ kind: "llm-request",
15085
+ summary: `LLM ${llm.model} (page ${pageNumber}, ${page2.blocks.length} blocks)`,
15086
+ payload: { model: llm.model, pageNumber, blockCount: page2.blocks.length }
15087
+ });
15088
+ const result = await directStoryboard(llm, {
15089
+ chunk,
15090
+ pageNumber,
15091
+ page: page2,
15092
+ index,
15093
+ history: narrationStore.getState().chunkHistory,
15094
+ camera: narrationStore.getState().camera,
15095
+ activeOverlays: narrationStore.getState().activeOverlays,
15096
+ signal: abortRef.current.signal,
15097
+ timeoutMs: llmTimeoutMs
15098
+ });
15099
+ if (result.storyboard) {
15100
+ narrationStore.getState().setLlmStatus("idle");
15101
+ narrationStore.getState().appendDebugEvent({
15102
+ kind: "llm-response",
15103
+ summary: `storyboard \u2713 ${result.storyboard.steps.length} steps \u2014 ${result.storyboard.reasoning.slice(0, 60)}`,
15104
+ payload: { raw: result.raw, storyboard: result.storyboard }
15105
+ });
15106
+ engineRef.current?.execute(result.storyboard);
15107
+ narrationStore.getState().appendDebugEvent({
15108
+ kind: "storyboard-execute",
15109
+ summary: `engine executing ${result.storyboard.steps.length} steps`,
15110
+ payload: result.storyboard.steps.map((s) => ({
15111
+ at_ms: s.at_ms,
15112
+ type: s.action.type,
15113
+ target: "target_block" in s.action ? s.action.target_block : "target" in s.action ? s.action.target : void 0
15114
+ }))
15115
+ });
15116
+ } else {
15117
+ narrationStore.getState().setLlmStatus("failed", result.error ?? "unknown");
15118
+ narrationStore.getState().appendDebugEvent({
15119
+ kind: "llm-error",
15120
+ summary: `LLM failed: ${(result.error ?? "unknown").slice(0, 80)}`,
15121
+ payload: { error: result.error, raw: result.raw }
15122
+ });
15123
+ if (embeddingProvider) {
15124
+ try {
15125
+ const match = await matchChunkToBlock(chunk, page2, embeddingProvider);
15126
+ const fallbackSb = storyboardFromMatch(match, page2);
15127
+ narrationStore.getState().appendDebugEvent({
15128
+ kind: "fallback-fired",
15129
+ summary: `embedding fallback \u2192 ${match?.block.block_id ?? "no match"}`,
15130
+ payload: { match, storyboard: fallbackSb }
15131
+ });
15132
+ engineRef.current?.execute(fallbackSb);
15133
+ } catch (e) {
15134
+ narrationStore.getState().appendDebugEvent({
15135
+ kind: "llm-error",
15136
+ summary: `fallback also failed: ${e.message}`,
15137
+ payload: e
15138
+ });
15139
+ }
15140
+ }
15141
+ }
15142
+ }, 200);
15143
+ return () => {
15144
+ if (debounceRef.current) clearTimeout(debounceRef.current);
15145
+ };
15146
+ }, [currentChunk, llm, index, pageNumber, narrationStore, embeddingProvider, llmTimeoutMs]);
15147
+ (0, import_react56.useEffect)(() => {
15148
+ if (!currentChunk) return;
15149
+ const t = setTimeout(() => {
15150
+ if (!engineRef.current) return;
15151
+ const hist = narrationStore.getState().chunkHistory;
15152
+ const latest = hist.length > 0 ? hist[hist.length - 1] : null;
15153
+ if (!latest) return;
15154
+ if (Date.now() - latest.timestamp < idleTimeoutMs) return;
15155
+ engineRef.current.resetVisuals();
15156
+ }, idleTimeoutMs + 100);
15157
+ return () => clearTimeout(t);
15158
+ }, [currentChunk, idleTimeoutMs, narrationStore]);
15159
+ const page = index.byPage.get(pageNumber);
15160
+ const dpiScale = page ? page.page_dimensions.dpi / 72 : 1;
15161
+ const rasterScale = dpiScale * (scale || 1);
15162
+ const baseW = page ? page.page_dimensions.width * (scale || 1) : 0;
15163
+ const baseH = page ? page.page_dimensions.height * (scale || 1) : 0;
15164
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
15165
+ "div",
15166
+ {
15167
+ ref: containerRef,
15168
+ className,
15169
+ style: {
15170
+ position: "relative",
15171
+ width: "100%",
15172
+ height: "100%",
15173
+ overflow: "hidden",
15174
+ background: "#111"
15175
+ },
15176
+ "data-role": "tutor-mode-container",
15177
+ "data-page-loaded": page ? "true" : "false",
15178
+ children: [
15179
+ showExitButton ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
15180
+ "button",
15181
+ {
15182
+ onClick: () => {
15183
+ engineRef.current?.resetVisuals();
15184
+ onExitTutorMode?.();
15185
+ },
15186
+ style: {
15187
+ position: "absolute",
15188
+ top: 12,
15189
+ right: 12,
15190
+ zIndex: 60,
15191
+ minHeight: 40,
15192
+ minWidth: 40,
15193
+ padding: "8px 14px",
15194
+ border: "none",
15195
+ borderRadius: 8,
15196
+ background: "rgba(255,255,255,0.12)",
15197
+ color: "white",
15198
+ cursor: "pointer",
15199
+ fontFamily: "system-ui, sans-serif",
15200
+ fontSize: 14,
15201
+ touchAction: "manipulation"
15202
+ },
15203
+ "aria-label": "Reset view \u2014 clear overlays and fit the page",
15204
+ "data-role": "exit-tutor",
15205
+ children: "Reset view"
15206
+ }
15207
+ ) : null,
15208
+ page ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(CameraView, { camera, children: /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
15209
+ "div",
15210
+ {
15211
+ style: {
15212
+ position: "absolute",
15213
+ top: "50%",
15214
+ left: "50%",
15215
+ width: baseW,
15216
+ height: baseH,
15217
+ transform: "translate(-50%, -50%)"
15218
+ },
15219
+ children: [
15220
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
15221
+ PDFPage,
15222
+ {
15223
+ pageNumber,
15224
+ page: pageProxy,
15225
+ scale: rasterScale,
15226
+ rotation,
15227
+ showTextLayer: false,
15228
+ showHighlightLayer: false,
15229
+ showAnnotationLayer: false
15230
+ }
15231
+ ),
15232
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
15233
+ CinemaLayer,
15234
+ {
15235
+ page,
15236
+ index,
15237
+ overlays: activeOverlays,
15238
+ scale: scale || 1
15239
+ }
15240
+ )
15241
+ ]
15242
+ }
15243
+ ) }) : null,
15244
+ showSubtitles ? /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(SubtitleBar, { text: currentChunk ?? null }) : null
15245
+ ]
15246
+ }
15247
+ );
15248
+ }
15249
+
15250
+ // src/director/transformers-embedding.ts
15251
+ var loaded = null;
15252
+ function getLocalMiniLM() {
15253
+ if (loaded) return loaded;
15254
+ loaded = (async () => {
15255
+ const mod = await import(
15256
+ /* webpackIgnore: true */
15257
+ "@xenova/transformers"
15258
+ );
15259
+ const { pipeline } = mod;
15260
+ const extractor = await pipeline(
15261
+ "feature-extraction",
15262
+ "Xenova/all-MiniLM-L6-v2"
15263
+ );
15264
+ return {
15265
+ async embed(texts) {
15266
+ const out = [];
15267
+ for (const t of texts) {
15268
+ const result = await extractor(t, {
15269
+ pooling: "mean",
15270
+ normalize: true
15271
+ });
15272
+ out.push(new Float32Array(result.data.slice()));
15273
+ }
15274
+ return out;
15275
+ }
15276
+ };
15277
+ })();
15278
+ return loaded;
15279
+ }
15280
+
12686
15281
  // src/index.ts
12687
15282
  init_hooks();
12688
15283
  init_store();
@@ -12694,18 +15289,26 @@ init_PluginManager();
12694
15289
  init_utils();
12695
15290
  // Annotate the CommonJS export names for ESM import in node:
12696
15291
  0 && (module.exports = {
15292
+ AnimatedHighlight,
15293
+ AnimatedUnderline,
12697
15294
  AnnotationLayer,
12698
15295
  AnnotationToolbar,
12699
15296
  AskAboutOverlay,
12700
15297
  AskAboutTrigger,
15298
+ BookModeContainer,
12701
15299
  BookmarksPanel,
15300
+ BoxOverlay,
15301
+ CalloutArrow,
15302
+ CameraView,
12702
15303
  CanvasLayer,
15304
+ CinemaLayer,
12703
15305
  ContinuousScrollContainer,
12704
15306
  DocumentContainer,
12705
15307
  DrawingCanvas,
12706
15308
  DualPageContainer,
12707
15309
  FloatingZoomControls,
12708
15310
  FocusRegionLayer,
15311
+ GhostReference,
12709
15312
  HighlightLayer,
12710
15313
  HighlightPopover,
12711
15314
  HighlightsPanel,
@@ -12722,32 +15325,46 @@ init_utils();
12722
15325
  PDFViewerContext,
12723
15326
  PDFViewerProvider,
12724
15327
  PluginManager,
15328
+ PulseOverlay,
12725
15329
  QuickNoteButton,
12726
15330
  QuickNotePopover,
15331
+ SYSTEM_PROMPT,
12727
15332
  SearchPanel,
12728
15333
  SelectionToolbar,
12729
15334
  ShapePreview,
12730
15335
  ShapeRenderer,
12731
15336
  Sidebar,
15337
+ SpotlightMask,
15338
+ StickyLabel,
12732
15339
  StickyNote,
15340
+ StoryboardActionSchema,
15341
+ StoryboardEngine,
15342
+ StoryboardSchema,
15343
+ SubtitleBar,
12733
15344
  TakeawaysPanel,
12734
15345
  TextLayer,
12735
15346
  ThumbnailPanel,
12736
15347
  Toolbar,
15348
+ TutorModeContainer,
12737
15349
  VirtualizedDocumentContainer,
12738
15350
  applyRotation,
15351
+ buildBBoxIndex,
15352
+ buildUserPrompt,
12739
15353
  clearHighlights,
12740
15354
  clearStudentData,
12741
15355
  cn,
15356
+ cosineSimilarity,
12742
15357
  countTextOnPage,
12743
15358
  createAgentAPI,
12744
15359
  createAgentStore,
12745
15360
  createAnnotationStore,
15361
+ createNarrationStore,
12746
15362
  createPDFViewer,
12747
15363
  createPluginManager,
12748
15364
  createSearchStore,
12749
15365
  createStudentStore,
12750
15366
  createViewerStore,
15367
+ directStoryboard,
12751
15368
  doRectsIntersect,
12752
15369
  downloadAnnotationsAsJSON,
12753
15370
  downloadAnnotationsAsMarkdown,
@@ -12762,6 +15379,7 @@ init_utils();
12762
15379
  generateDocumentId,
12763
15380
  getAllDocumentIds,
12764
15381
  getAllStudentDataDocumentIds,
15382
+ getLocalMiniLM,
12765
15383
  getMetadata,
12766
15384
  getOutline,
12767
15385
  getPage,
@@ -12779,17 +15397,23 @@ init_utils();
12779
15397
  loadDocumentWithCallbacks,
12780
15398
  loadHighlights,
12781
15399
  loadStudentData,
15400
+ makeOverlayId,
15401
+ matchChunkToBlock,
12782
15402
  mergeAdjacentRects,
12783
15403
  pdfToPercent,
12784
15404
  pdfToViewport,
12785
15405
  pdfjsLib,
12786
15406
  percentToPDF,
12787
15407
  percentToViewport,
15408
+ playPageTurnSound,
12788
15409
  quickViewer,
12789
15410
  removeRotation,
12790
15411
  saveHighlights,
12791
15412
  saveStudentData,
12792
15413
  scaleRect,
15414
+ storyboardFromMatch,
15415
+ storyboardJsonSchema,
15416
+ truncate,
12793
15417
  useAgentContext,
12794
15418
  useAgentStore,
12795
15419
  useAnnotationStore,