pdfjs-reader-core 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/index.cjs +1598 -453
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -26
- package/dist/index.d.ts +151 -26
- package/dist/index.js +1587 -442
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1861,6 +1861,17 @@ function computeCameraForBlock(bbox, page, viewport, opts = {}) {
|
|
|
1861
1861
|
const y = (pageCY - blockCY) * scale;
|
|
1862
1862
|
return { scale, x, y };
|
|
1863
1863
|
}
|
|
1864
|
+
function clampCamera(target, page, viewport) {
|
|
1865
|
+
const pageWScreen = page.width * target.scale;
|
|
1866
|
+
const pageHScreen = page.height * target.scale;
|
|
1867
|
+
const maxOffsetX = Math.max(0, (pageWScreen - viewport.width) / 2);
|
|
1868
|
+
const maxOffsetY = Math.max(0, (pageHScreen - viewport.height) / 2);
|
|
1869
|
+
return {
|
|
1870
|
+
scale: target.scale,
|
|
1871
|
+
x: Math.max(-maxOffsetX, Math.min(maxOffsetX, target.x)),
|
|
1872
|
+
y: Math.max(-maxOffsetY, Math.min(maxOffsetY, target.y))
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1864
1875
|
var init_camera_math = __esm({
|
|
1865
1876
|
"src/utils/camera-math.ts"() {
|
|
1866
1877
|
"use strict";
|
|
@@ -3717,8 +3728,8 @@ var init_PluginManager = __esm({
|
|
|
3717
3728
|
/**
|
|
3718
3729
|
* Get toolbar items by position
|
|
3719
3730
|
*/
|
|
3720
|
-
getToolbarItemsByPosition(
|
|
3721
|
-
return this.getToolbarItems().filter((item) => item.position ===
|
|
3731
|
+
getToolbarItemsByPosition(position) {
|
|
3732
|
+
return this.getToolbarItems().filter((item) => item.position === position);
|
|
3722
3733
|
}
|
|
3723
3734
|
/**
|
|
3724
3735
|
* Get all sidebar panels from all plugins
|
|
@@ -4825,7 +4836,7 @@ var init_MobileToolbar = __esm({
|
|
|
4825
4836
|
sidebarOpen,
|
|
4826
4837
|
theme,
|
|
4827
4838
|
onThemeChange,
|
|
4828
|
-
position
|
|
4839
|
+
position = "bottom",
|
|
4829
4840
|
className
|
|
4830
4841
|
}) {
|
|
4831
4842
|
const [showMoreMenu, setShowMoreMenu] = useState5(false);
|
|
@@ -4859,8 +4870,8 @@ var init_MobileToolbar = __esm({
|
|
|
4859
4870
|
"bg-white dark:bg-gray-800",
|
|
4860
4871
|
"border-gray-200 dark:border-gray-700",
|
|
4861
4872
|
"px-2 py-1 safe-area-inset",
|
|
4862
|
-
|
|
4863
|
-
|
|
4873
|
+
position === "top" && "top-0 border-b",
|
|
4874
|
+
position === "bottom" && "bottom-0 border-t",
|
|
4864
4875
|
className
|
|
4865
4876
|
),
|
|
4866
4877
|
children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-1", children: [
|
|
@@ -4986,7 +4997,7 @@ var init_MobileToolbar = __esm({
|
|
|
4986
4997
|
"bg-white dark:bg-gray-800",
|
|
4987
4998
|
"rounded-lg shadow-lg",
|
|
4988
4999
|
"border border-gray-200 dark:border-gray-700",
|
|
4989
|
-
|
|
5000
|
+
position === "bottom" ? "bottom-full mb-2" : "top-full mt-2"
|
|
4990
5001
|
),
|
|
4991
5002
|
children: [
|
|
4992
5003
|
/* @__PURE__ */ jsx3("div", { className: "px-2 py-1 text-xs text-gray-500 dark:text-gray-400 font-medium", children: "Theme" }),
|
|
@@ -6812,7 +6823,7 @@ var init_AnnotationToolbar = __esm({
|
|
|
6812
6823
|
onShapeTypeChange: onShapeTypeChangeProp,
|
|
6813
6824
|
onColorChange: onColorChangeProp,
|
|
6814
6825
|
onStrokeWidthChange: onStrokeWidthChangeProp,
|
|
6815
|
-
position
|
|
6826
|
+
position = "top",
|
|
6816
6827
|
className
|
|
6817
6828
|
}) {
|
|
6818
6829
|
const storeActiveTool = useAnnotationStore((s) => s.activeAnnotationTool);
|
|
@@ -6862,9 +6873,9 @@ var init_AnnotationToolbar = __esm({
|
|
|
6862
6873
|
{
|
|
6863
6874
|
className: cn(
|
|
6864
6875
|
"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",
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6876
|
+
position === "floating" && "fixed bottom-20 left-1/2 -translate-x-1/2 z-50",
|
|
6877
|
+
position === "top" && "sticky top-0 z-40",
|
|
6878
|
+
position === "bottom" && "sticky bottom-0 z-40",
|
|
6868
6879
|
!isActive && "opacity-90",
|
|
6869
6880
|
className
|
|
6870
6881
|
),
|
|
@@ -8513,7 +8524,7 @@ var init_SelectionToolbar = __esm({
|
|
|
8513
8524
|
activeColor = "yellow",
|
|
8514
8525
|
className
|
|
8515
8526
|
}) {
|
|
8516
|
-
const [
|
|
8527
|
+
const [position, setPosition] = useState16({ top: 0, left: 0, visible: false });
|
|
8517
8528
|
const toolbarRef = useRef14(null);
|
|
8518
8529
|
useEffect17(() => {
|
|
8519
8530
|
if (selection && selection.text && selection.rects.length > 0) {
|
|
@@ -8552,7 +8563,7 @@ var init_SelectionToolbar = __esm({
|
|
|
8552
8563
|
const handleCopy = useCallback27(() => {
|
|
8553
8564
|
onCopy?.();
|
|
8554
8565
|
}, [onCopy]);
|
|
8555
|
-
if (!
|
|
8566
|
+
if (!position.visible || !selection?.text) {
|
|
8556
8567
|
return null;
|
|
8557
8568
|
}
|
|
8558
8569
|
return /* @__PURE__ */ jsxs18(
|
|
@@ -8570,8 +8581,8 @@ var init_SelectionToolbar = __esm({
|
|
|
8570
8581
|
className
|
|
8571
8582
|
),
|
|
8572
8583
|
style: {
|
|
8573
|
-
top:
|
|
8574
|
-
left:
|
|
8584
|
+
top: position.top,
|
|
8585
|
+
left: position.left,
|
|
8575
8586
|
transform: "translateX(-50%)"
|
|
8576
8587
|
},
|
|
8577
8588
|
onMouseDown: (e) => {
|
|
@@ -8688,7 +8699,7 @@ var init_HighlightPopover = __esm({
|
|
|
8688
8699
|
}) {
|
|
8689
8700
|
const [isEditingComment, setIsEditingComment] = useState17(false);
|
|
8690
8701
|
const [comment, setComment] = useState17(highlight?.comment ?? "");
|
|
8691
|
-
const [
|
|
8702
|
+
const [position, setPosition] = useState17({ top: 0, left: 0, visible: false });
|
|
8692
8703
|
const popoverRef = useRef15(null);
|
|
8693
8704
|
const textareaRef = useRef15(null);
|
|
8694
8705
|
useEffect18(() => {
|
|
@@ -8736,11 +8747,11 @@ var init_HighlightPopover = __esm({
|
|
|
8736
8747
|
onClose();
|
|
8737
8748
|
}
|
|
8738
8749
|
}
|
|
8739
|
-
if (
|
|
8750
|
+
if (position.visible) {
|
|
8740
8751
|
document.addEventListener("mousedown", handleClickOutside);
|
|
8741
8752
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
8742
8753
|
}
|
|
8743
|
-
}, [
|
|
8754
|
+
}, [position.visible, onClose]);
|
|
8744
8755
|
useEffect18(() => {
|
|
8745
8756
|
function handleKeyDown(event) {
|
|
8746
8757
|
if (event.key === "Escape") {
|
|
@@ -8752,11 +8763,11 @@ var init_HighlightPopover = __esm({
|
|
|
8752
8763
|
}
|
|
8753
8764
|
}
|
|
8754
8765
|
}
|
|
8755
|
-
if (
|
|
8766
|
+
if (position.visible) {
|
|
8756
8767
|
document.addEventListener("keydown", handleKeyDown);
|
|
8757
8768
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
8758
8769
|
}
|
|
8759
|
-
}, [
|
|
8770
|
+
}, [position.visible, isEditingComment, highlight?.comment, onClose]);
|
|
8760
8771
|
const handleColorClick = useCallback28(
|
|
8761
8772
|
(color) => {
|
|
8762
8773
|
if (highlight) {
|
|
@@ -8783,7 +8794,7 @@ var init_HighlightPopover = __esm({
|
|
|
8783
8794
|
onCopy?.(highlight.text);
|
|
8784
8795
|
}
|
|
8785
8796
|
}, [highlight, onCopy]);
|
|
8786
|
-
if (!highlight || !
|
|
8797
|
+
if (!highlight || !position.visible) {
|
|
8787
8798
|
return null;
|
|
8788
8799
|
}
|
|
8789
8800
|
return /* @__PURE__ */ jsxs19(
|
|
@@ -8800,8 +8811,8 @@ var init_HighlightPopover = __esm({
|
|
|
8800
8811
|
className
|
|
8801
8812
|
),
|
|
8802
8813
|
style: {
|
|
8803
|
-
top:
|
|
8804
|
-
left:
|
|
8814
|
+
top: position.top,
|
|
8815
|
+
left: position.left,
|
|
8805
8816
|
transform: "translate(-50%, -100%)",
|
|
8806
8817
|
width: 280
|
|
8807
8818
|
},
|
|
@@ -10213,7 +10224,7 @@ var init_FloatingZoomControls = __esm({
|
|
|
10213
10224
|
init_hooks();
|
|
10214
10225
|
init_utils();
|
|
10215
10226
|
FloatingZoomControls = memo27(function FloatingZoomControls2({
|
|
10216
|
-
position
|
|
10227
|
+
position = "bottom-right",
|
|
10217
10228
|
className,
|
|
10218
10229
|
showFitToWidth = true,
|
|
10219
10230
|
showFitToPage = false,
|
|
@@ -10254,7 +10265,7 @@ var init_FloatingZoomControls = __esm({
|
|
|
10254
10265
|
"bg-white dark:bg-gray-800 rounded-lg shadow-lg",
|
|
10255
10266
|
"border border-gray-200 dark:border-gray-700",
|
|
10256
10267
|
"p-1",
|
|
10257
|
-
positionClasses[
|
|
10268
|
+
positionClasses[position],
|
|
10258
10269
|
className
|
|
10259
10270
|
),
|
|
10260
10271
|
children: [
|
|
@@ -11934,7 +11945,7 @@ import { jsx as jsx34 } from "react/jsx-runtime";
|
|
|
11934
11945
|
var QuickNoteButton = memo33(function QuickNoteButton2({
|
|
11935
11946
|
pageNumber,
|
|
11936
11947
|
scale,
|
|
11937
|
-
position
|
|
11948
|
+
position = "top-right",
|
|
11938
11949
|
onClick,
|
|
11939
11950
|
className,
|
|
11940
11951
|
visible = true
|
|
@@ -11943,11 +11954,11 @@ var QuickNoteButton = memo33(function QuickNoteButton2({
|
|
|
11943
11954
|
const handleClick = useCallback38(
|
|
11944
11955
|
(e) => {
|
|
11945
11956
|
e.stopPropagation();
|
|
11946
|
-
const x =
|
|
11947
|
-
const y =
|
|
11957
|
+
const x = position === "top-right" ? 80 : 80;
|
|
11958
|
+
const y = position === "top-right" ? 20 : 80;
|
|
11948
11959
|
onClick(pageNumber, x / scale, y / scale);
|
|
11949
11960
|
},
|
|
11950
|
-
[pageNumber, onClick,
|
|
11961
|
+
[pageNumber, onClick, position, scale]
|
|
11951
11962
|
);
|
|
11952
11963
|
if (!visible) {
|
|
11953
11964
|
return null;
|
|
@@ -11968,8 +11979,8 @@ var QuickNoteButton = memo33(function QuickNoteButton2({
|
|
|
11968
11979
|
"transition-all duration-200",
|
|
11969
11980
|
"focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2",
|
|
11970
11981
|
isHovered && "scale-110",
|
|
11971
|
-
|
|
11972
|
-
|
|
11982
|
+
position === "top-right" && "top-3 right-3",
|
|
11983
|
+
position === "bottom-right" && "bottom-3 right-3",
|
|
11973
11984
|
className
|
|
11974
11985
|
),
|
|
11975
11986
|
title: "Add quick note",
|
|
@@ -11995,7 +12006,7 @@ import { memo as memo34, useCallback as useCallback39, useState as useState27, u
|
|
|
11995
12006
|
import { jsx as jsx35, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
11996
12007
|
var QuickNotePopover = memo34(function QuickNotePopover2({
|
|
11997
12008
|
visible,
|
|
11998
|
-
position
|
|
12009
|
+
position,
|
|
11999
12010
|
initialContent = "",
|
|
12000
12011
|
agentLastStatement,
|
|
12001
12012
|
onSave,
|
|
@@ -12005,7 +12016,7 @@ var QuickNotePopover = memo34(function QuickNotePopover2({
|
|
|
12005
12016
|
const [content, setContent] = useState27(initialContent);
|
|
12006
12017
|
const textareaRef = useRef24(null);
|
|
12007
12018
|
const popoverRef = useRef24(null);
|
|
12008
|
-
const [adjustedPosition, setAdjustedPosition] = useState27(
|
|
12019
|
+
const [adjustedPosition, setAdjustedPosition] = useState27(position);
|
|
12009
12020
|
useEffect25(() => {
|
|
12010
12021
|
if (visible && textareaRef.current) {
|
|
12011
12022
|
textareaRef.current.focus();
|
|
@@ -12020,7 +12031,7 @@ var QuickNotePopover = memo34(function QuickNotePopover2({
|
|
|
12020
12031
|
if (!visible || !popoverRef.current) return;
|
|
12021
12032
|
const rect = popoverRef.current.getBoundingClientRect();
|
|
12022
12033
|
const padding = 10;
|
|
12023
|
-
let { x, y } =
|
|
12034
|
+
let { x, y } = position;
|
|
12024
12035
|
if (x + rect.width > window.innerWidth - padding) {
|
|
12025
12036
|
x = window.innerWidth - rect.width - padding;
|
|
12026
12037
|
}
|
|
@@ -12034,7 +12045,7 @@ var QuickNotePopover = memo34(function QuickNotePopover2({
|
|
|
12034
12045
|
y = padding;
|
|
12035
12046
|
}
|
|
12036
12047
|
setAdjustedPosition({ x, y });
|
|
12037
|
-
}, [
|
|
12048
|
+
}, [position, visible]);
|
|
12038
12049
|
const handleSave = useCallback39(() => {
|
|
12039
12050
|
if (content.trim()) {
|
|
12040
12051
|
onSave(content.trim());
|
|
@@ -12151,11 +12162,11 @@ import { jsx as jsx36, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
|
12151
12162
|
var AskAboutOverlay = memo35(function AskAboutOverlay2({
|
|
12152
12163
|
visible,
|
|
12153
12164
|
progress,
|
|
12154
|
-
position
|
|
12165
|
+
position,
|
|
12155
12166
|
size = 60,
|
|
12156
12167
|
className
|
|
12157
12168
|
}) {
|
|
12158
|
-
if (!visible || !
|
|
12169
|
+
if (!visible || !position) {
|
|
12159
12170
|
return null;
|
|
12160
12171
|
}
|
|
12161
12172
|
const strokeWidth = 4;
|
|
@@ -12171,8 +12182,8 @@ var AskAboutOverlay = memo35(function AskAboutOverlay2({
|
|
|
12171
12182
|
className
|
|
12172
12183
|
),
|
|
12173
12184
|
style: {
|
|
12174
|
-
left:
|
|
12175
|
-
top:
|
|
12185
|
+
left: position.x - size / 2,
|
|
12186
|
+
top: position.y - size / 2
|
|
12176
12187
|
},
|
|
12177
12188
|
children: [
|
|
12178
12189
|
/* @__PURE__ */ jsxs30(
|
|
@@ -12260,20 +12271,20 @@ init_utils();
|
|
|
12260
12271
|
import { memo as memo36, useCallback as useCallback40, useState as useState28, useRef as useRef25, useEffect as useEffect26 } from "react";
|
|
12261
12272
|
import { jsx as jsx37, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
12262
12273
|
var AskAboutTrigger = memo36(function AskAboutTrigger2({
|
|
12263
|
-
position
|
|
12274
|
+
position,
|
|
12264
12275
|
onConfirm,
|
|
12265
12276
|
onCancel,
|
|
12266
12277
|
visible,
|
|
12267
12278
|
autoHideDelay = 5e3,
|
|
12268
12279
|
className
|
|
12269
12280
|
}) {
|
|
12270
|
-
const [adjustedPosition, setAdjustedPosition] = useState28(
|
|
12281
|
+
const [adjustedPosition, setAdjustedPosition] = useState28(position);
|
|
12271
12282
|
const triggerRef = useRef25(null);
|
|
12272
12283
|
useEffect26(() => {
|
|
12273
12284
|
if (!visible || !triggerRef.current) return;
|
|
12274
12285
|
const rect = triggerRef.current.getBoundingClientRect();
|
|
12275
12286
|
const padding = 10;
|
|
12276
|
-
let { x, y } =
|
|
12287
|
+
let { x, y } = position;
|
|
12277
12288
|
if (x + rect.width / 2 > window.innerWidth - padding) {
|
|
12278
12289
|
x = window.innerWidth - rect.width / 2 - padding;
|
|
12279
12290
|
}
|
|
@@ -12281,10 +12292,10 @@ var AskAboutTrigger = memo36(function AskAboutTrigger2({
|
|
|
12281
12292
|
x = rect.width / 2 + padding;
|
|
12282
12293
|
}
|
|
12283
12294
|
if (y + rect.height > window.innerHeight - padding) {
|
|
12284
|
-
y =
|
|
12295
|
+
y = position.y - rect.height - 20;
|
|
12285
12296
|
}
|
|
12286
12297
|
setAdjustedPosition({ x, y });
|
|
12287
|
-
}, [
|
|
12298
|
+
}, [position, visible]);
|
|
12288
12299
|
useEffect26(() => {
|
|
12289
12300
|
if (!visible || autoHideDelay === 0) return;
|
|
12290
12301
|
const timer = setTimeout(onCancel, autoHideDelay);
|
|
@@ -12978,21 +12989,43 @@ import { AnimatePresence } from "framer-motion";
|
|
|
12978
12989
|
// src/components/TutorMode/SpotlightMask.tsx
|
|
12979
12990
|
import { useId } from "react";
|
|
12980
12991
|
import { motion as motion2 } from "framer-motion";
|
|
12992
|
+
|
|
12993
|
+
// src/components/TutorMode/tokens.ts
|
|
12994
|
+
var INK = "#2a2420";
|
|
12995
|
+
var PAPER = "#faf6ec";
|
|
12996
|
+
var ACCENT = "#b04a1a";
|
|
12997
|
+
var ACCENT_SOFT = "rgba(176, 74, 26, 0.18)";
|
|
12998
|
+
var ACCENT_GLOW = "rgba(176, 74, 26, 0.35)";
|
|
12999
|
+
var MARKER = "#e6b422";
|
|
13000
|
+
var MARKER_SOFT = "rgba(230, 180, 34, 0.38)";
|
|
13001
|
+
var SERIF = "'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', 'EB Garamond', 'Hoefler Text', Georgia, serif";
|
|
13002
|
+
var EASE_OUT_EXPO = [0.22, 1, 0.36, 1];
|
|
13003
|
+
|
|
13004
|
+
// src/components/TutorMode/SpotlightMask.tsx
|
|
12981
13005
|
import { jsx as jsx42, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
12982
13006
|
function SpotlightMask({
|
|
12983
13007
|
page,
|
|
12984
13008
|
bbox,
|
|
12985
13009
|
action,
|
|
12986
|
-
durationMs =
|
|
13010
|
+
durationMs = 500
|
|
12987
13011
|
}) {
|
|
12988
13012
|
const maskId = useId();
|
|
12989
13013
|
const filterId = `${maskId}-blur`;
|
|
12990
|
-
const [
|
|
12991
|
-
const
|
|
12992
|
-
const
|
|
12993
|
-
const
|
|
12994
|
-
const
|
|
12995
|
-
const
|
|
13014
|
+
const [rawX1, rawY1, rawX2, rawY2] = bbox;
|
|
13015
|
+
const rawW = Math.max(0, rawX2 - rawX1);
|
|
13016
|
+
const rawH = Math.max(0, rawY2 - rawY1);
|
|
13017
|
+
const pad = Math.min(28, Math.max(10, Math.min(rawW, rawH) * 0.06));
|
|
13018
|
+
const x1 = rawX1 - pad;
|
|
13019
|
+
const y1 = rawY1 - pad;
|
|
13020
|
+
const x2 = rawX2 + pad;
|
|
13021
|
+
const y2 = rawY2 + pad;
|
|
13022
|
+
const w = x2 - x1;
|
|
13023
|
+
const h = y2 - y1;
|
|
13024
|
+
const rx = action.shape === "rounded" ? 14 : action.shape === "ellipse" ? w / 2 : 0;
|
|
13025
|
+
const ry = action.shape === "rounded" ? 14 : action.shape === "ellipse" ? h / 2 : 0;
|
|
13026
|
+
const feather = Math.max(16, action.feather_px);
|
|
13027
|
+
const cx = (x1 + x2) / 2;
|
|
13028
|
+
const cy = (y1 + y2) / 2;
|
|
12996
13029
|
return /* @__PURE__ */ jsxs35(
|
|
12997
13030
|
"svg",
|
|
12998
13031
|
{
|
|
@@ -13005,7 +13038,8 @@ function SpotlightMask({
|
|
|
13005
13038
|
inset: 0,
|
|
13006
13039
|
pointerEvents: "none",
|
|
13007
13040
|
width: page.width,
|
|
13008
|
-
height: page.height
|
|
13041
|
+
height: page.height,
|
|
13042
|
+
overflow: "visible"
|
|
13009
13043
|
},
|
|
13010
13044
|
"data-role": "spotlight-mask",
|
|
13011
13045
|
children: [
|
|
@@ -13016,8 +13050,8 @@ function SpotlightMask({
|
|
|
13016
13050
|
action.shape === "ellipse" ? /* @__PURE__ */ jsx42(
|
|
13017
13051
|
"ellipse",
|
|
13018
13052
|
{
|
|
13019
|
-
cx
|
|
13020
|
-
cy
|
|
13053
|
+
cx,
|
|
13054
|
+
cy,
|
|
13021
13055
|
rx: w / 2,
|
|
13022
13056
|
ry: h / 2,
|
|
13023
13057
|
fill: "black",
|
|
@@ -13045,14 +13079,82 @@ function SpotlightMask({
|
|
|
13045
13079
|
y: 0,
|
|
13046
13080
|
width: page.width,
|
|
13047
13081
|
height: page.height,
|
|
13048
|
-
fill:
|
|
13082
|
+
fill: INK,
|
|
13049
13083
|
mask: `url(#${maskId})`,
|
|
13050
13084
|
initial: { fillOpacity: 0 },
|
|
13051
13085
|
animate: { fillOpacity: action.dim_opacity },
|
|
13052
13086
|
exit: { fillOpacity: 0 },
|
|
13053
|
-
transition: { duration: durationMs / 1e3, ease:
|
|
13087
|
+
transition: { duration: durationMs / 1e3, ease: EASE_OUT_EXPO }
|
|
13054
13088
|
}
|
|
13055
|
-
)
|
|
13089
|
+
),
|
|
13090
|
+
action.shape === "ellipse" ? /* @__PURE__ */ jsx42(
|
|
13091
|
+
motion2.ellipse,
|
|
13092
|
+
{
|
|
13093
|
+
cx,
|
|
13094
|
+
cy,
|
|
13095
|
+
rx: w / 2,
|
|
13096
|
+
ry: h / 2,
|
|
13097
|
+
fill: "none",
|
|
13098
|
+
stroke: ACCENT,
|
|
13099
|
+
strokeWidth: 3,
|
|
13100
|
+
initial: { opacity: 0, scale: 1.08 },
|
|
13101
|
+
animate: { opacity: 0.9, scale: 1 },
|
|
13102
|
+
exit: { opacity: 0 },
|
|
13103
|
+
style: { transformOrigin: `${cx}px ${cy}px`, transformBox: "fill-box" },
|
|
13104
|
+
transition: {
|
|
13105
|
+
duration: durationMs / 1e3,
|
|
13106
|
+
delay: 0.15,
|
|
13107
|
+
ease: EASE_OUT_EXPO
|
|
13108
|
+
}
|
|
13109
|
+
}
|
|
13110
|
+
) : /* @__PURE__ */ jsx42(
|
|
13111
|
+
motion2.rect,
|
|
13112
|
+
{
|
|
13113
|
+
x: x1,
|
|
13114
|
+
y: y1,
|
|
13115
|
+
width: w,
|
|
13116
|
+
height: h,
|
|
13117
|
+
rx,
|
|
13118
|
+
ry,
|
|
13119
|
+
fill: "none",
|
|
13120
|
+
stroke: ACCENT,
|
|
13121
|
+
strokeWidth: 3,
|
|
13122
|
+
initial: { opacity: 0, scale: 1.04 },
|
|
13123
|
+
animate: { opacity: 0.9, scale: 1 },
|
|
13124
|
+
exit: { opacity: 0 },
|
|
13125
|
+
style: {
|
|
13126
|
+
transformOrigin: `${cx}px ${cy}px`,
|
|
13127
|
+
transformBox: "fill-box"
|
|
13128
|
+
},
|
|
13129
|
+
transition: {
|
|
13130
|
+
duration: durationMs / 1e3,
|
|
13131
|
+
delay: 0.15,
|
|
13132
|
+
ease: EASE_OUT_EXPO
|
|
13133
|
+
}
|
|
13134
|
+
}
|
|
13135
|
+
),
|
|
13136
|
+
action.shape !== "ellipse" ? /* @__PURE__ */ jsx42(
|
|
13137
|
+
motion2.rect,
|
|
13138
|
+
{
|
|
13139
|
+
x: x1 - 2,
|
|
13140
|
+
y: y1 - 2,
|
|
13141
|
+
width: w + 4,
|
|
13142
|
+
height: h + 4,
|
|
13143
|
+
rx: rx + 2,
|
|
13144
|
+
ry: ry + 2,
|
|
13145
|
+
fill: "none",
|
|
13146
|
+
stroke: ACCENT_GLOW,
|
|
13147
|
+
strokeWidth: 8,
|
|
13148
|
+
initial: { opacity: 0 },
|
|
13149
|
+
animate: { opacity: 0.6 },
|
|
13150
|
+
exit: { opacity: 0 },
|
|
13151
|
+
transition: {
|
|
13152
|
+
duration: durationMs / 1e3,
|
|
13153
|
+
delay: 0.2,
|
|
13154
|
+
ease: EASE_OUT_EXPO
|
|
13155
|
+
}
|
|
13156
|
+
}
|
|
13157
|
+
) : null
|
|
13056
13158
|
]
|
|
13057
13159
|
}
|
|
13058
13160
|
);
|
|
@@ -13060,36 +13162,59 @@ function SpotlightMask({
|
|
|
13060
13162
|
|
|
13061
13163
|
// src/components/TutorMode/AnimatedUnderline.tsx
|
|
13062
13164
|
import { motion as motion3 } from "framer-motion";
|
|
13063
|
-
import { jsx as jsx43 } from "react/jsx-runtime";
|
|
13165
|
+
import { jsx as jsx43, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
13166
|
+
function jitterAt(i) {
|
|
13167
|
+
const h = Math.sin(i * 12.9898) * 43758.5453;
|
|
13168
|
+
return (h - Math.floor(h) - 0.5) * 4;
|
|
13169
|
+
}
|
|
13064
13170
|
function pathForStyle(x1, x2, y, style) {
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13171
|
+
const x1e = x1 - 4;
|
|
13172
|
+
const x2e = x2 + 4;
|
|
13173
|
+
if (style === "straight") {
|
|
13174
|
+
return { primary: `M ${x1e} ${y} L ${x2e} ${y}` };
|
|
13175
|
+
}
|
|
13176
|
+
if (style === "double") {
|
|
13177
|
+
return {
|
|
13178
|
+
primary: `M ${x1e} ${y - 3} L ${x2e} ${y - 3}`,
|
|
13179
|
+
ghost: `M ${x1e} ${y + 3} L ${x2e} ${y + 3}`
|
|
13180
|
+
};
|
|
13181
|
+
}
|
|
13068
13182
|
if (style === "wavy") {
|
|
13069
|
-
const
|
|
13070
|
-
|
|
13183
|
+
const len = x2e - x1e;
|
|
13184
|
+
const steps = Math.max(12, Math.floor(len / 10));
|
|
13185
|
+
const amp = 3.2;
|
|
13186
|
+
let d = `M ${x1e} ${y}`;
|
|
13071
13187
|
for (let i = 1; i <= steps; i++) {
|
|
13072
|
-
const
|
|
13073
|
-
const
|
|
13074
|
-
|
|
13188
|
+
const t = i / steps;
|
|
13189
|
+
const px = x1e + len * t;
|
|
13190
|
+
const py = y + Math.sin(t * Math.PI * 4) * amp;
|
|
13191
|
+
const prevT = (i - 1) / steps;
|
|
13192
|
+
const cpx = x1e + len * (prevT + (t - prevT) / 2);
|
|
13193
|
+
const cpy = y + Math.sin((prevT + (t - prevT) / 2) * Math.PI * 4) * amp;
|
|
13194
|
+
d += ` Q ${cpx} ${cpy} ${px} ${py}`;
|
|
13075
13195
|
}
|
|
13076
|
-
return
|
|
13196
|
+
return { primary: d };
|
|
13077
13197
|
}
|
|
13078
|
-
const segs =
|
|
13079
|
-
let
|
|
13198
|
+
const segs = 8;
|
|
13199
|
+
let primary = `M ${x1e} ${y + jitterAt(0)}`;
|
|
13200
|
+
let ghost = `M ${x1e} ${y + jitterAt(100) + 1.5}`;
|
|
13080
13201
|
for (let i = 1; i <= segs; i++) {
|
|
13081
|
-
const px =
|
|
13082
|
-
|
|
13083
|
-
|
|
13202
|
+
const px = x1e + (x2e - x1e) * i / segs;
|
|
13203
|
+
primary += ` L ${px} ${y + jitterAt(i)}`;
|
|
13204
|
+
ghost += ` L ${px} ${y + jitterAt(i + 100) + 1.5}`;
|
|
13084
13205
|
}
|
|
13085
|
-
return
|
|
13206
|
+
return { primary, ghost };
|
|
13086
13207
|
}
|
|
13087
13208
|
function AnimatedUnderline({ bbox, action }) {
|
|
13088
13209
|
const [x1, , x2, y2] = bbox;
|
|
13089
13210
|
const y = y2 + 6;
|
|
13090
|
-
const
|
|
13211
|
+
const { primary, ghost } = pathForStyle(x1, x2, y, action.style);
|
|
13091
13212
|
const duration = action.draw_duration_ms / 1e3;
|
|
13092
|
-
|
|
13213
|
+
const stroke = action.color && action.color !== "#FBBF24" ? action.color : ACCENT;
|
|
13214
|
+
const blotX = x2 + 4;
|
|
13215
|
+
const blotY = y;
|
|
13216
|
+
const strokeWeight = action.style === "wavy" ? 3 : 4;
|
|
13217
|
+
return /* @__PURE__ */ jsxs36(
|
|
13093
13218
|
"svg",
|
|
13094
13219
|
{
|
|
13095
13220
|
style: {
|
|
@@ -13099,119 +13224,365 @@ function AnimatedUnderline({ bbox, action }) {
|
|
|
13099
13224
|
overflow: "visible"
|
|
13100
13225
|
},
|
|
13101
13226
|
"data-role": "underline",
|
|
13102
|
-
children:
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13227
|
+
children: [
|
|
13228
|
+
ghost ? /* @__PURE__ */ jsx43(
|
|
13229
|
+
motion3.path,
|
|
13230
|
+
{
|
|
13231
|
+
d: ghost,
|
|
13232
|
+
fill: "none",
|
|
13233
|
+
stroke,
|
|
13234
|
+
strokeWidth: strokeWeight - 1.5,
|
|
13235
|
+
strokeLinecap: "round",
|
|
13236
|
+
strokeOpacity: 0.35,
|
|
13237
|
+
initial: { pathLength: 0, opacity: 0 },
|
|
13238
|
+
animate: { pathLength: 1, opacity: 0.55 },
|
|
13239
|
+
exit: { opacity: 0 },
|
|
13240
|
+
transition: { duration, ease: EASE_OUT_EXPO }
|
|
13241
|
+
}
|
|
13242
|
+
) : null,
|
|
13243
|
+
/* @__PURE__ */ jsx43(
|
|
13244
|
+
motion3.path,
|
|
13245
|
+
{
|
|
13246
|
+
d: primary,
|
|
13247
|
+
fill: "none",
|
|
13248
|
+
stroke,
|
|
13249
|
+
strokeWidth: strokeWeight,
|
|
13250
|
+
strokeLinecap: "round",
|
|
13251
|
+
strokeLinejoin: "round",
|
|
13252
|
+
initial: { pathLength: 0, opacity: 0 },
|
|
13253
|
+
animate: { pathLength: 1, opacity: 1 },
|
|
13254
|
+
exit: { opacity: 0 },
|
|
13255
|
+
transition: { duration, ease: EASE_OUT_EXPO }
|
|
13256
|
+
}
|
|
13257
|
+
),
|
|
13258
|
+
/* @__PURE__ */ jsx43(
|
|
13259
|
+
motion3.circle,
|
|
13260
|
+
{
|
|
13261
|
+
cx: blotX,
|
|
13262
|
+
cy: blotY,
|
|
13263
|
+
r: strokeWeight / 2 + 0.5,
|
|
13264
|
+
fill: stroke,
|
|
13265
|
+
initial: { scale: 0, opacity: 0 },
|
|
13266
|
+
animate: { scale: 1, opacity: 0.9 },
|
|
13267
|
+
exit: { opacity: 0 },
|
|
13268
|
+
style: {
|
|
13269
|
+
transformOrigin: `${blotX}px ${blotY}px`,
|
|
13270
|
+
transformBox: "fill-box"
|
|
13271
|
+
},
|
|
13272
|
+
transition: {
|
|
13273
|
+
duration: 0.25,
|
|
13274
|
+
delay: duration - 0.1,
|
|
13275
|
+
ease: EASE_OUT_EXPO
|
|
13276
|
+
}
|
|
13277
|
+
}
|
|
13278
|
+
)
|
|
13279
|
+
]
|
|
13116
13280
|
}
|
|
13117
13281
|
);
|
|
13118
13282
|
}
|
|
13119
13283
|
|
|
13120
13284
|
// src/components/TutorMode/AnimatedHighlight.tsx
|
|
13285
|
+
import { useId as useId2 } from "react";
|
|
13121
13286
|
import { motion as motion4 } from "framer-motion";
|
|
13122
|
-
import { jsx as jsx44 } from "react/jsx-runtime";
|
|
13287
|
+
import { jsx as jsx44, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
13123
13288
|
function AnimatedHighlight({ bbox, action }) {
|
|
13124
13289
|
const [x1, y1, x2, y2] = bbox;
|
|
13125
|
-
const
|
|
13126
|
-
const
|
|
13127
|
-
|
|
13128
|
-
|
|
13290
|
+
const h = Math.max(1, y2 - y1);
|
|
13291
|
+
const bleed = Math.min(4, h * 0.12);
|
|
13292
|
+
const yTop = y1 - bleed;
|
|
13293
|
+
const yBot = y2 + bleed;
|
|
13294
|
+
const height = yBot - yTop;
|
|
13295
|
+
const duration = action.draw_duration_ms / 1e3;
|
|
13296
|
+
const filterId = useId2();
|
|
13297
|
+
const fill = action.color && action.color !== "rgba(250, 204, 21, 0.35)" && action.color !== "rgba(250,204,21,0.35)" ? action.color : MARKER_SOFT;
|
|
13298
|
+
const inner = action.color && action.color !== "rgba(250, 204, 21, 0.35)" && action.color !== "rgba(250,204,21,0.35)" ? action.color : MARKER;
|
|
13299
|
+
const taper = Math.min(6, h * 0.2);
|
|
13300
|
+
const pathD = `
|
|
13301
|
+
M ${x1 - 2} ${yTop + taper}
|
|
13302
|
+
L ${x1 + 2} ${yTop}
|
|
13303
|
+
L ${x2 - 2} ${yTop}
|
|
13304
|
+
L ${x2 + 2} ${yTop + taper}
|
|
13305
|
+
L ${x2 + 2} ${yBot - taper}
|
|
13306
|
+
L ${x2 - 2} ${yBot}
|
|
13307
|
+
L ${x1 + 2} ${yBot}
|
|
13308
|
+
L ${x1 - 2} ${yBot - taper}
|
|
13309
|
+
Z
|
|
13310
|
+
`;
|
|
13311
|
+
return /* @__PURE__ */ jsxs37(
|
|
13312
|
+
"svg",
|
|
13129
13313
|
{
|
|
13130
13314
|
style: {
|
|
13131
13315
|
position: "absolute",
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
borderRadius: 4,
|
|
13137
|
-
mixBlendMode: "multiply",
|
|
13138
|
-
transformOrigin: "0% 50%",
|
|
13139
|
-
pointerEvents: "none"
|
|
13316
|
+
inset: 0,
|
|
13317
|
+
pointerEvents: "none",
|
|
13318
|
+
overflow: "visible",
|
|
13319
|
+
mixBlendMode: "multiply"
|
|
13140
13320
|
},
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13321
|
+
"data-role": "highlight",
|
|
13322
|
+
children: [
|
|
13323
|
+
/* @__PURE__ */ jsx44("defs", { children: /* @__PURE__ */ jsxs37("filter", { id: filterId, children: [
|
|
13324
|
+
/* @__PURE__ */ jsx44(
|
|
13325
|
+
"feTurbulence",
|
|
13326
|
+
{
|
|
13327
|
+
type: "fractalNoise",
|
|
13328
|
+
baseFrequency: "1.6",
|
|
13329
|
+
numOctaves: "1",
|
|
13330
|
+
seed: 3,
|
|
13331
|
+
result: "noise"
|
|
13332
|
+
}
|
|
13333
|
+
),
|
|
13334
|
+
/* @__PURE__ */ jsx44("feDisplacementMap", { in: "SourceGraphic", in2: "noise", scale: 1.4 })
|
|
13335
|
+
] }) }),
|
|
13336
|
+
/* @__PURE__ */ jsxs37(
|
|
13337
|
+
motion4.g,
|
|
13338
|
+
{
|
|
13339
|
+
initial: { clipPath: `inset(0 100% 0 0)` },
|
|
13340
|
+
animate: { clipPath: `inset(0 0% 0 0)` },
|
|
13341
|
+
exit: { opacity: 0 },
|
|
13342
|
+
transition: { duration, ease: EASE_OUT_EXPO },
|
|
13343
|
+
children: [
|
|
13344
|
+
/* @__PURE__ */ jsx44(
|
|
13345
|
+
"path",
|
|
13346
|
+
{
|
|
13347
|
+
d: pathD,
|
|
13348
|
+
fill,
|
|
13349
|
+
opacity: 0.85,
|
|
13350
|
+
filter: `url(#${filterId})`
|
|
13351
|
+
}
|
|
13352
|
+
),
|
|
13353
|
+
/* @__PURE__ */ jsx44(
|
|
13354
|
+
"rect",
|
|
13355
|
+
{
|
|
13356
|
+
x: x1 - 1,
|
|
13357
|
+
y: y1 - bleed * 0.4,
|
|
13358
|
+
width: x2 - x1 + 2,
|
|
13359
|
+
height: height - bleed * 0.8,
|
|
13360
|
+
fill: inner,
|
|
13361
|
+
opacity: 0.5,
|
|
13362
|
+
filter: `url(#${filterId})`
|
|
13363
|
+
}
|
|
13364
|
+
)
|
|
13365
|
+
]
|
|
13366
|
+
}
|
|
13367
|
+
)
|
|
13368
|
+
]
|
|
13146
13369
|
}
|
|
13147
13370
|
);
|
|
13148
13371
|
}
|
|
13149
13372
|
|
|
13150
13373
|
// src/components/TutorMode/PulseOverlay.tsx
|
|
13151
13374
|
import { motion as motion5 } from "framer-motion";
|
|
13152
|
-
import { jsx as jsx45 } from "react/jsx-runtime";
|
|
13375
|
+
import { jsx as jsx45, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
13153
13376
|
var INTENSITY = {
|
|
13154
|
-
subtle: {
|
|
13155
|
-
normal: {
|
|
13156
|
-
strong: {
|
|
13377
|
+
subtle: { bracketLen: 14, strokeWeight: 2, coreOpacity: 0.5, ringScale: 1.08 },
|
|
13378
|
+
normal: { bracketLen: 20, strokeWeight: 2.5, coreOpacity: 0.75, ringScale: 1.14 },
|
|
13379
|
+
strong: { bracketLen: 26, strokeWeight: 3, coreOpacity: 1, ringScale: 1.22 }
|
|
13157
13380
|
};
|
|
13158
13381
|
function PulseOverlay({ bbox, action }) {
|
|
13159
13382
|
const [x1, y1, x2, y2] = bbox;
|
|
13160
|
-
const
|
|
13161
|
-
const
|
|
13162
|
-
|
|
13163
|
-
|
|
13383
|
+
const w = Math.max(1, x2 - x1);
|
|
13384
|
+
const h = Math.max(1, y2 - y1);
|
|
13385
|
+
const cx = (x1 + x2) / 2;
|
|
13386
|
+
const cy = (y1 + y2) / 2;
|
|
13387
|
+
const spec = INTENSITY[action.intensity] ?? INTENSITY.normal;
|
|
13388
|
+
const L = Math.min(spec.bracketLen, Math.min(w, h) / 2.5);
|
|
13389
|
+
const PAD = 6;
|
|
13390
|
+
return /* @__PURE__ */ jsxs38(
|
|
13391
|
+
"svg",
|
|
13164
13392
|
{
|
|
13165
13393
|
style: {
|
|
13166
13394
|
position: "absolute",
|
|
13167
|
-
|
|
13168
|
-
top: y1,
|
|
13169
|
-
width: x2 - x1,
|
|
13170
|
-
height: y2 - y1,
|
|
13171
|
-
border,
|
|
13172
|
-
borderRadius: 8,
|
|
13395
|
+
inset: 0,
|
|
13173
13396
|
pointerEvents: "none",
|
|
13174
|
-
|
|
13175
|
-
},
|
|
13176
|
-
animate: { scale: [1, scale, 1] },
|
|
13177
|
-
transition: {
|
|
13178
|
-
duration: 1.2,
|
|
13179
|
-
times: [0, 0.5, 1],
|
|
13180
|
-
ease: "easeInOut",
|
|
13181
|
-
repeat,
|
|
13182
|
-
repeatType: "loop"
|
|
13397
|
+
overflow: "visible"
|
|
13183
13398
|
},
|
|
13399
|
+
"data-role": "pulse",
|
|
13400
|
+
children: [
|
|
13401
|
+
/* @__PURE__ */ jsx45(
|
|
13402
|
+
motion5.ellipse,
|
|
13403
|
+
{
|
|
13404
|
+
cx,
|
|
13405
|
+
cy,
|
|
13406
|
+
rx: w / 2 + 10,
|
|
13407
|
+
ry: h / 2 + 10,
|
|
13408
|
+
fill: ACCENT_GLOW,
|
|
13409
|
+
style: {
|
|
13410
|
+
transformOrigin: `${cx}px ${cy}px`,
|
|
13411
|
+
transformBox: "fill-box",
|
|
13412
|
+
filter: "blur(16px)"
|
|
13413
|
+
},
|
|
13414
|
+
initial: { opacity: 0, scale: 0.95 },
|
|
13415
|
+
animate: {
|
|
13416
|
+
opacity: [0, spec.coreOpacity * 0.45, 0.15],
|
|
13417
|
+
scale: [0.95, spec.ringScale, 1]
|
|
13418
|
+
},
|
|
13419
|
+
exit: { opacity: 0 },
|
|
13420
|
+
transition: {
|
|
13421
|
+
duration: 1.3,
|
|
13422
|
+
times: [0, 0.5, 1],
|
|
13423
|
+
ease: EASE_OUT_EXPO
|
|
13424
|
+
}
|
|
13425
|
+
}
|
|
13426
|
+
),
|
|
13427
|
+
/* @__PURE__ */ jsx45(
|
|
13428
|
+
Bracket,
|
|
13429
|
+
{
|
|
13430
|
+
originX: x1 - PAD,
|
|
13431
|
+
originY: y1 - PAD,
|
|
13432
|
+
direction: "tl",
|
|
13433
|
+
length: L,
|
|
13434
|
+
weight: spec.strokeWeight,
|
|
13435
|
+
delay: 0
|
|
13436
|
+
}
|
|
13437
|
+
),
|
|
13438
|
+
/* @__PURE__ */ jsx45(
|
|
13439
|
+
Bracket,
|
|
13440
|
+
{
|
|
13441
|
+
originX: x2 + PAD,
|
|
13442
|
+
originY: y1 - PAD,
|
|
13443
|
+
direction: "tr",
|
|
13444
|
+
length: L,
|
|
13445
|
+
weight: spec.strokeWeight,
|
|
13446
|
+
delay: 0.04
|
|
13447
|
+
}
|
|
13448
|
+
),
|
|
13449
|
+
/* @__PURE__ */ jsx45(
|
|
13450
|
+
Bracket,
|
|
13451
|
+
{
|
|
13452
|
+
originX: x1 - PAD,
|
|
13453
|
+
originY: y2 + PAD,
|
|
13454
|
+
direction: "bl",
|
|
13455
|
+
length: L,
|
|
13456
|
+
weight: spec.strokeWeight,
|
|
13457
|
+
delay: 0.08
|
|
13458
|
+
}
|
|
13459
|
+
),
|
|
13460
|
+
/* @__PURE__ */ jsx45(
|
|
13461
|
+
Bracket,
|
|
13462
|
+
{
|
|
13463
|
+
originX: x2 + PAD,
|
|
13464
|
+
originY: y2 + PAD,
|
|
13465
|
+
direction: "br",
|
|
13466
|
+
length: L,
|
|
13467
|
+
weight: spec.strokeWeight,
|
|
13468
|
+
delay: 0.12
|
|
13469
|
+
}
|
|
13470
|
+
),
|
|
13471
|
+
/* @__PURE__ */ jsx45(
|
|
13472
|
+
motion5.rect,
|
|
13473
|
+
{
|
|
13474
|
+
x: x1 - PAD,
|
|
13475
|
+
y: y1 - PAD,
|
|
13476
|
+
width: w + PAD * 2,
|
|
13477
|
+
height: h + PAD * 2,
|
|
13478
|
+
fill: "none",
|
|
13479
|
+
stroke: ACCENT,
|
|
13480
|
+
strokeWidth: 1,
|
|
13481
|
+
rx: 4,
|
|
13482
|
+
style: {
|
|
13483
|
+
transformOrigin: `${cx}px ${cy}px`,
|
|
13484
|
+
transformBox: "fill-box"
|
|
13485
|
+
},
|
|
13486
|
+
initial: { scale: 1, opacity: 0 },
|
|
13487
|
+
animate: {
|
|
13488
|
+
scale: [1, spec.ringScale, 1],
|
|
13489
|
+
opacity: [0, spec.coreOpacity * 0.5, 0]
|
|
13490
|
+
},
|
|
13491
|
+
exit: { opacity: 0 },
|
|
13492
|
+
transition: {
|
|
13493
|
+
duration: 1.3,
|
|
13494
|
+
times: [0, 0.5, 1],
|
|
13495
|
+
ease: EASE_OUT_EXPO,
|
|
13496
|
+
delay: 0.2
|
|
13497
|
+
}
|
|
13498
|
+
}
|
|
13499
|
+
)
|
|
13500
|
+
]
|
|
13501
|
+
}
|
|
13502
|
+
);
|
|
13503
|
+
}
|
|
13504
|
+
function Bracket({
|
|
13505
|
+
originX,
|
|
13506
|
+
originY,
|
|
13507
|
+
direction,
|
|
13508
|
+
length,
|
|
13509
|
+
weight,
|
|
13510
|
+
delay
|
|
13511
|
+
}) {
|
|
13512
|
+
const xSign = direction === "tl" || direction === "bl" ? 1 : -1;
|
|
13513
|
+
const ySign = direction === "tl" || direction === "tr" ? 1 : -1;
|
|
13514
|
+
const d = `
|
|
13515
|
+
M ${originX + xSign * length} ${originY}
|
|
13516
|
+
L ${originX} ${originY}
|
|
13517
|
+
L ${originX} ${originY + ySign * length}
|
|
13518
|
+
`;
|
|
13519
|
+
const slideX = -xSign * 8;
|
|
13520
|
+
const slideY = -ySign * 8;
|
|
13521
|
+
return /* @__PURE__ */ jsx45(
|
|
13522
|
+
motion5.path,
|
|
13523
|
+
{
|
|
13524
|
+
d,
|
|
13525
|
+
fill: "none",
|
|
13526
|
+
stroke: ACCENT,
|
|
13527
|
+
strokeWidth: weight,
|
|
13528
|
+
strokeLinecap: "round",
|
|
13529
|
+
strokeLinejoin: "round",
|
|
13530
|
+
initial: { opacity: 0, x: slideX, y: slideY },
|
|
13531
|
+
animate: { opacity: 1, x: 0, y: 0 },
|
|
13184
13532
|
exit: { opacity: 0 },
|
|
13185
|
-
|
|
13533
|
+
transition: {
|
|
13534
|
+
duration: 0.4,
|
|
13535
|
+
delay,
|
|
13536
|
+
ease: EASE_OUT_EXPO
|
|
13537
|
+
}
|
|
13186
13538
|
}
|
|
13187
13539
|
);
|
|
13188
13540
|
}
|
|
13189
13541
|
|
|
13190
13542
|
// src/components/TutorMode/CalloutArrow.tsx
|
|
13543
|
+
import { useId as useId3 } from "react";
|
|
13191
13544
|
import { motion as motion6 } from "framer-motion";
|
|
13192
|
-
import { jsx as jsx46, jsxs as
|
|
13545
|
+
import { jsx as jsx46, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
13193
13546
|
function centerOf(b) {
|
|
13194
13547
|
return { x: (b[0] + b[2]) / 2, y: (b[1] + b[3]) / 2 };
|
|
13195
13548
|
}
|
|
13196
|
-
function
|
|
13549
|
+
function edgePoints(fromBbox, toBbox) {
|
|
13197
13550
|
const a = centerOf(fromBbox);
|
|
13198
13551
|
const b = centerOf(toBbox);
|
|
13199
|
-
if (curve === "straight") return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
|
|
13200
|
-
if (curve === "zigzag") {
|
|
13201
|
-
const mx = (a.x + b.x) / 2;
|
|
13202
|
-
return `M ${a.x} ${a.y} L ${mx} ${a.y} L ${mx} ${b.y} L ${b.x} ${b.y}`;
|
|
13203
|
-
}
|
|
13204
13552
|
const dx = b.x - a.x;
|
|
13205
13553
|
const dy = b.y - a.y;
|
|
13206
|
-
const
|
|
13207
|
-
const
|
|
13208
|
-
|
|
13554
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
13555
|
+
const ux = dx / len;
|
|
13556
|
+
const uy = dy / len;
|
|
13557
|
+
const aHalfW = (fromBbox[2] - fromBbox[0]) / 2;
|
|
13558
|
+
const aHalfH = (fromBbox[3] - fromBbox[1]) / 2;
|
|
13559
|
+
const bHalfW = (toBbox[2] - toBbox[0]) / 2;
|
|
13560
|
+
const bHalfH = (toBbox[3] - toBbox[1]) / 2;
|
|
13561
|
+
const aOff = Math.min(Math.max(aHalfW, aHalfH), 60);
|
|
13562
|
+
const bOff = Math.min(Math.max(bHalfW, bHalfH), 60);
|
|
13563
|
+
return {
|
|
13564
|
+
from: { x: a.x + ux * aOff, y: a.y + uy * aOff },
|
|
13565
|
+
to: { x: b.x - ux * bOff, y: b.y - uy * bOff }
|
|
13566
|
+
};
|
|
13567
|
+
}
|
|
13568
|
+
function arrowPath(from, to, curve) {
|
|
13569
|
+
if (curve === "straight") return `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
|
|
13570
|
+
if (curve === "zigzag") {
|
|
13571
|
+
const mx = (from.x + to.x) / 2;
|
|
13572
|
+
return `M ${from.x} ${from.y} L ${mx} ${from.y} L ${mx} ${to.y} L ${to.x} ${to.y}`;
|
|
13573
|
+
}
|
|
13574
|
+
const dx = to.x - from.x;
|
|
13575
|
+
const dy = to.y - from.y;
|
|
13576
|
+
const cx = (from.x + to.x) / 2 - dy * 0.22;
|
|
13577
|
+
const cy = (from.y + to.y) / 2 + dx * 0.22;
|
|
13578
|
+
return `M ${from.x} ${from.y} Q ${cx} ${cy} ${to.x} ${to.y}`;
|
|
13209
13579
|
}
|
|
13210
13580
|
function CalloutArrow({ fromBbox, toBbox, action }) {
|
|
13211
|
-
const
|
|
13212
|
-
const
|
|
13213
|
-
const
|
|
13214
|
-
|
|
13581
|
+
const markerId = useId3();
|
|
13582
|
+
const glowId = `${markerId}-glow`;
|
|
13583
|
+
const { from, to } = edgePoints(fromBbox, toBbox);
|
|
13584
|
+
const d = arrowPath(from, to, action.curve);
|
|
13585
|
+
return /* @__PURE__ */ jsxs39(
|
|
13215
13586
|
"svg",
|
|
13216
13587
|
{
|
|
13217
13588
|
style: {
|
|
@@ -13222,247 +13593,142 @@ function CalloutArrow({ fromBbox, toBbox, action }) {
|
|
|
13222
13593
|
},
|
|
13223
13594
|
"data-role": "callout",
|
|
13224
13595
|
children: [
|
|
13225
|
-
/* @__PURE__ */
|
|
13226
|
-
|
|
13596
|
+
/* @__PURE__ */ jsxs39("defs", { children: [
|
|
13597
|
+
/* @__PURE__ */ jsx46(
|
|
13598
|
+
"marker",
|
|
13599
|
+
{
|
|
13600
|
+
id: markerId,
|
|
13601
|
+
viewBox: "0 0 12 10",
|
|
13602
|
+
refX: 10,
|
|
13603
|
+
refY: 5,
|
|
13604
|
+
markerWidth: 10,
|
|
13605
|
+
markerHeight: 10,
|
|
13606
|
+
orient: "auto",
|
|
13607
|
+
children: /* @__PURE__ */ jsx46(
|
|
13608
|
+
"path",
|
|
13609
|
+
{
|
|
13610
|
+
d: "M 1 1 L 10 5 L 1 9",
|
|
13611
|
+
fill: "none",
|
|
13612
|
+
stroke: ACCENT,
|
|
13613
|
+
strokeWidth: 1.8,
|
|
13614
|
+
strokeLinecap: "round",
|
|
13615
|
+
strokeLinejoin: "round"
|
|
13616
|
+
}
|
|
13617
|
+
)
|
|
13618
|
+
}
|
|
13619
|
+
),
|
|
13620
|
+
/* @__PURE__ */ jsx46("filter", { id: glowId, x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ jsx46("feGaussianBlur", { stdDeviation: "2.5" }) })
|
|
13621
|
+
] }),
|
|
13622
|
+
/* @__PURE__ */ jsx46(
|
|
13623
|
+
motion6.circle,
|
|
13624
|
+
{
|
|
13625
|
+
cx: from.x,
|
|
13626
|
+
cy: from.y,
|
|
13627
|
+
r: 4,
|
|
13628
|
+
fill: ACCENT,
|
|
13629
|
+
initial: { scale: 0, opacity: 0 },
|
|
13630
|
+
animate: { scale: 1, opacity: 1 },
|
|
13631
|
+
exit: { opacity: 0 },
|
|
13632
|
+
style: {
|
|
13633
|
+
transformOrigin: `${from.x}px ${from.y}px`,
|
|
13634
|
+
transformBox: "fill-box"
|
|
13635
|
+
},
|
|
13636
|
+
transition: { duration: 0.3, ease: EASE_OUT_EXPO }
|
|
13637
|
+
}
|
|
13638
|
+
),
|
|
13639
|
+
/* @__PURE__ */ jsx46(
|
|
13640
|
+
motion6.circle,
|
|
13227
13641
|
{
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13642
|
+
cx: from.x,
|
|
13643
|
+
cy: from.y,
|
|
13644
|
+
r: 7,
|
|
13645
|
+
fill: "none",
|
|
13646
|
+
stroke: ACCENT,
|
|
13647
|
+
strokeWidth: 1.2,
|
|
13648
|
+
strokeOpacity: 0.4,
|
|
13649
|
+
initial: { scale: 0, opacity: 0 },
|
|
13650
|
+
animate: { scale: 1, opacity: 0.5 },
|
|
13651
|
+
exit: { opacity: 0 },
|
|
13652
|
+
style: {
|
|
13653
|
+
transformOrigin: `${from.x}px ${from.y}px`,
|
|
13654
|
+
transformBox: "fill-box"
|
|
13655
|
+
},
|
|
13656
|
+
transition: { duration: 0.4, delay: 0.08, ease: EASE_OUT_EXPO }
|
|
13236
13657
|
}
|
|
13237
|
-
)
|
|
13658
|
+
),
|
|
13238
13659
|
/* @__PURE__ */ jsx46(
|
|
13239
13660
|
motion6.path,
|
|
13240
13661
|
{
|
|
13241
13662
|
d,
|
|
13242
13663
|
fill: "none",
|
|
13243
|
-
stroke:
|
|
13244
|
-
strokeWidth:
|
|
13664
|
+
stroke: ACCENT,
|
|
13665
|
+
strokeWidth: 6,
|
|
13666
|
+
strokeOpacity: 0.18,
|
|
13245
13667
|
strokeLinecap: "round",
|
|
13246
|
-
|
|
13668
|
+
filter: `url(#${glowId})`,
|
|
13247
13669
|
initial: { pathLength: 0, opacity: 0 },
|
|
13248
|
-
animate: { pathLength: 1, opacity:
|
|
13670
|
+
animate: { pathLength: 1, opacity: 0.8 },
|
|
13249
13671
|
exit: { opacity: 0 },
|
|
13250
|
-
transition: { duration: 0.
|
|
13672
|
+
transition: { duration: 0.7, delay: 0.12, ease: EASE_OUT_EXPO }
|
|
13251
13673
|
}
|
|
13252
13674
|
),
|
|
13253
|
-
|
|
13254
|
-
motion6.
|
|
13675
|
+
/* @__PURE__ */ jsx46(
|
|
13676
|
+
motion6.path,
|
|
13255
13677
|
{
|
|
13256
|
-
|
|
13257
|
-
|
|
13678
|
+
d,
|
|
13679
|
+
fill: "none",
|
|
13680
|
+
stroke: ACCENT,
|
|
13681
|
+
strokeWidth: 2.4,
|
|
13682
|
+
strokeLinecap: "round",
|
|
13683
|
+
strokeLinejoin: "round",
|
|
13684
|
+
markerEnd: `url(#${markerId})`,
|
|
13685
|
+
initial: { pathLength: 0, opacity: 0 },
|
|
13686
|
+
animate: { pathLength: 1, opacity: 1 },
|
|
13258
13687
|
exit: { opacity: 0 },
|
|
13259
|
-
transition: {
|
|
13260
|
-
children: [
|
|
13261
|
-
/* @__PURE__ */ jsx46(
|
|
13262
|
-
"rect",
|
|
13263
|
-
{
|
|
13264
|
-
x: target.x - 4,
|
|
13265
|
-
y: target.y - 28,
|
|
13266
|
-
width: label.length * 9 + 12,
|
|
13267
|
-
height: 22,
|
|
13268
|
-
rx: 4,
|
|
13269
|
-
fill: "#1F2937"
|
|
13270
|
-
}
|
|
13271
|
-
),
|
|
13272
|
-
/* @__PURE__ */ jsx46(
|
|
13273
|
-
"text",
|
|
13274
|
-
{
|
|
13275
|
-
x: target.x + 2,
|
|
13276
|
-
y: target.y - 12,
|
|
13277
|
-
fill: "white",
|
|
13278
|
-
fontSize: 14,
|
|
13279
|
-
fontFamily: "system-ui, sans-serif",
|
|
13280
|
-
children: label
|
|
13281
|
-
}
|
|
13282
|
-
)
|
|
13283
|
-
]
|
|
13688
|
+
transition: { duration: 0.7, delay: 0.15, ease: EASE_OUT_EXPO }
|
|
13284
13689
|
}
|
|
13285
|
-
)
|
|
13690
|
+
)
|
|
13286
13691
|
]
|
|
13287
13692
|
}
|
|
13288
13693
|
);
|
|
13289
13694
|
}
|
|
13290
13695
|
|
|
13291
|
-
// src/components/TutorMode/
|
|
13696
|
+
// src/components/TutorMode/BoxOverlay.tsx
|
|
13292
13697
|
import { motion as motion7 } from "framer-motion";
|
|
13293
|
-
import { jsx as jsx47
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
"
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
function GhostReference({
|
|
13301
|
-
page,
|
|
13302
|
-
sourceBbox,
|
|
13303
|
-
sourceBlockText,
|
|
13304
|
-
sourcePageNumber,
|
|
13305
|
-
action
|
|
13306
|
-
}) {
|
|
13307
|
-
const width = 360;
|
|
13308
|
-
const [x1, y1, x2, y2] = sourceBbox;
|
|
13309
|
-
return /* @__PURE__ */ jsxs37(
|
|
13698
|
+
import { jsx as jsx47 } from "react/jsx-runtime";
|
|
13699
|
+
function BoxOverlay({ bbox, action }) {
|
|
13700
|
+
const [x1, y1, x2, y2] = bbox;
|
|
13701
|
+
const useSystem = !action.color || action.color === "#3B82F6" || action.color === "#3b82f6";
|
|
13702
|
+
const borderColor = useSystem ? ACCENT : action.color;
|
|
13703
|
+
const washColor = useSystem ? ACCENT_SOFT : void 0;
|
|
13704
|
+
return /* @__PURE__ */ jsx47(
|
|
13310
13705
|
motion7.div,
|
|
13311
13706
|
{
|
|
13312
|
-
initial: { opacity: 0,
|
|
13313
|
-
animate: { opacity: 1,
|
|
13314
|
-
exit: { opacity: 0
|
|
13315
|
-
transition: { duration: 0.
|
|
13707
|
+
initial: { opacity: 0, scale: 0.98 },
|
|
13708
|
+
animate: { opacity: 1, scale: 1 },
|
|
13709
|
+
exit: { opacity: 0 },
|
|
13710
|
+
transition: { duration: 0.45, ease: EASE_OUT_EXPO },
|
|
13316
13711
|
style: {
|
|
13317
13712
|
position: "absolute",
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13713
|
+
left: x1 - 3,
|
|
13714
|
+
top: y1 - 3,
|
|
13715
|
+
width: x2 - x1 + 6,
|
|
13716
|
+
height: y2 - y1 + 6,
|
|
13717
|
+
border: `${action.style === "dashed" ? "2px dashed" : "2px solid"} ${borderColor}`,
|
|
13718
|
+
borderRadius: 4,
|
|
13719
|
+
background: washColor,
|
|
13324
13720
|
pointerEvents: "none",
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
},
|
|
13329
|
-
"data-role": "ghost-reference",
|
|
13330
|
-
children: [
|
|
13331
|
-
/* @__PURE__ */ jsxs37("div", { style: { opacity: 0.7, fontSize: 11, marginBottom: 6 }, children: [
|
|
13332
|
-
"Page ",
|
|
13333
|
-
sourcePageNumber,
|
|
13334
|
-
" \u2014 ",
|
|
13335
|
-
action.target_block
|
|
13336
|
-
] }),
|
|
13337
|
-
/* @__PURE__ */ jsxs37(
|
|
13338
|
-
"svg",
|
|
13339
|
-
{
|
|
13340
|
-
width: width - 24,
|
|
13341
|
-
height: 160,
|
|
13342
|
-
viewBox: `0 0 ${page.width} ${page.height}`,
|
|
13343
|
-
style: { background: "#1F2937", borderRadius: 6, display: "block" },
|
|
13344
|
-
preserveAspectRatio: "xMidYMid meet",
|
|
13345
|
-
children: [
|
|
13346
|
-
/* @__PURE__ */ jsx47(
|
|
13347
|
-
"rect",
|
|
13348
|
-
{
|
|
13349
|
-
x: 0,
|
|
13350
|
-
y: 0,
|
|
13351
|
-
width: page.width,
|
|
13352
|
-
height: page.height,
|
|
13353
|
-
fill: "#1F2937"
|
|
13354
|
-
}
|
|
13355
|
-
),
|
|
13356
|
-
/* @__PURE__ */ jsx47(
|
|
13357
|
-
"rect",
|
|
13358
|
-
{
|
|
13359
|
-
x: x1,
|
|
13360
|
-
y: y1,
|
|
13361
|
-
width: x2 - x1,
|
|
13362
|
-
height: y2 - y1,
|
|
13363
|
-
fill: "rgba(250,204,21,0.45)",
|
|
13364
|
-
stroke: "#FBBF24",
|
|
13365
|
-
strokeWidth: 8
|
|
13366
|
-
}
|
|
13367
|
-
)
|
|
13368
|
-
]
|
|
13369
|
-
}
|
|
13370
|
-
),
|
|
13371
|
-
/* @__PURE__ */ jsx47(
|
|
13372
|
-
"div",
|
|
13373
|
-
{
|
|
13374
|
-
style: {
|
|
13375
|
-
marginTop: 8,
|
|
13376
|
-
fontSize: 12,
|
|
13377
|
-
lineHeight: 1.4,
|
|
13378
|
-
opacity: 0.9
|
|
13379
|
-
},
|
|
13380
|
-
children: sourceBlockText ?? "(figure)"
|
|
13381
|
-
}
|
|
13382
|
-
)
|
|
13383
|
-
]
|
|
13384
|
-
}
|
|
13385
|
-
);
|
|
13386
|
-
}
|
|
13387
|
-
|
|
13388
|
-
// src/components/TutorMode/BoxOverlay.tsx
|
|
13389
|
-
import { motion as motion8 } from "framer-motion";
|
|
13390
|
-
import { jsx as jsx48 } from "react/jsx-runtime";
|
|
13391
|
-
function BoxOverlay({ bbox, action }) {
|
|
13392
|
-
const [x1, y1, x2, y2] = bbox;
|
|
13393
|
-
return /* @__PURE__ */ jsx48(
|
|
13394
|
-
motion8.div,
|
|
13395
|
-
{
|
|
13396
|
-
initial: { opacity: 0, scale: 0.97 },
|
|
13397
|
-
animate: { opacity: 1, scale: 1 },
|
|
13398
|
-
exit: { opacity: 0 },
|
|
13399
|
-
transition: { duration: 0.35, ease: "easeOut" },
|
|
13400
|
-
style: {
|
|
13401
|
-
position: "absolute",
|
|
13402
|
-
left: x1,
|
|
13403
|
-
top: y1,
|
|
13404
|
-
width: x2 - x1,
|
|
13405
|
-
height: y2 - y1,
|
|
13406
|
-
border: `${action.style === "dashed" ? "3px dashed" : "3px solid"} ${action.color}`,
|
|
13407
|
-
borderRadius: 6,
|
|
13408
|
-
pointerEvents: "none",
|
|
13409
|
-
boxSizing: "border-box"
|
|
13721
|
+
boxSizing: "border-box",
|
|
13722
|
+
// Subtle outer glow for the accent version, tinted to match.
|
|
13723
|
+
boxShadow: useSystem ? `0 0 0 1px rgba(176, 74, 26, 0.10), 0 8px 24px -10px rgba(176, 74, 26, 0.35)` : void 0
|
|
13410
13724
|
},
|
|
13411
13725
|
"data-role": "box"
|
|
13412
13726
|
}
|
|
13413
13727
|
);
|
|
13414
13728
|
}
|
|
13415
13729
|
|
|
13416
|
-
// src/components/TutorMode/StickyLabel.tsx
|
|
13417
|
-
import { motion as motion9 } from "framer-motion";
|
|
13418
|
-
import { jsx as jsx49 } from "react/jsx-runtime";
|
|
13419
|
-
function position(bbox, where) {
|
|
13420
|
-
const [x1, y1, x2, y2] = bbox;
|
|
13421
|
-
const cx = (x1 + x2) / 2;
|
|
13422
|
-
const cy = (y1 + y2) / 2;
|
|
13423
|
-
const PAD = 16;
|
|
13424
|
-
switch (where) {
|
|
13425
|
-
case "top":
|
|
13426
|
-
return { left: cx, top: y1 - PAD, transform: "translate(-50%, -100%)" };
|
|
13427
|
-
case "bottom":
|
|
13428
|
-
return { left: cx, top: y2 + PAD, transform: "translate(-50%, 0)" };
|
|
13429
|
-
case "left":
|
|
13430
|
-
return { left: x1 - PAD, top: cy, transform: "translate(-100%, -50%)" };
|
|
13431
|
-
case "right":
|
|
13432
|
-
return { left: x2 + PAD, top: cy, transform: "translate(0, -50%)" };
|
|
13433
|
-
default:
|
|
13434
|
-
return { left: cx, top: y1, transform: "translate(-50%, -100%)" };
|
|
13435
|
-
}
|
|
13436
|
-
}
|
|
13437
|
-
function StickyLabel({ bbox, action }) {
|
|
13438
|
-
return /* @__PURE__ */ jsx49(
|
|
13439
|
-
motion9.div,
|
|
13440
|
-
{
|
|
13441
|
-
initial: { opacity: 0, scale: 0.9 },
|
|
13442
|
-
animate: { opacity: 1, scale: 1 },
|
|
13443
|
-
exit: { opacity: 0 },
|
|
13444
|
-
transition: { duration: 0.35, ease: "easeOut" },
|
|
13445
|
-
style: {
|
|
13446
|
-
position: "absolute",
|
|
13447
|
-
padding: "6px 10px",
|
|
13448
|
-
background: "#FEF3C7",
|
|
13449
|
-
color: "#78350F",
|
|
13450
|
-
borderRadius: 6,
|
|
13451
|
-
boxShadow: "0 3px 10px rgba(0,0,0,0.2)",
|
|
13452
|
-
fontSize: 14,
|
|
13453
|
-
fontFamily: "system-ui, sans-serif",
|
|
13454
|
-
maxWidth: 280,
|
|
13455
|
-
pointerEvents: "none",
|
|
13456
|
-
...position(bbox, action.position)
|
|
13457
|
-
},
|
|
13458
|
-
"data-role": "label",
|
|
13459
|
-
children: action.text
|
|
13460
|
-
}
|
|
13461
|
-
);
|
|
13462
|
-
}
|
|
13463
|
-
|
|
13464
13730
|
// src/components/TutorMode/CinemaLayer.tsx
|
|
13465
|
-
import { jsx as
|
|
13731
|
+
import { jsx as jsx48 } from "react/jsx-runtime";
|
|
13466
13732
|
function blockBbox(index, block_id) {
|
|
13467
13733
|
return index.blockById.get(block_id)?.block.bbox;
|
|
13468
13734
|
}
|
|
@@ -13472,7 +13738,7 @@ function CinemaLayer({
|
|
|
13472
13738
|
overlays,
|
|
13473
13739
|
scale
|
|
13474
13740
|
}) {
|
|
13475
|
-
return /* @__PURE__ */
|
|
13741
|
+
return /* @__PURE__ */ jsx48(
|
|
13476
13742
|
"div",
|
|
13477
13743
|
{
|
|
13478
13744
|
"data-role": "cinema-layer",
|
|
@@ -13492,13 +13758,13 @@ function CinemaLayer({
|
|
|
13492
13758
|
// reachable because it sits OUTSIDE this stacking context.
|
|
13493
13759
|
zIndex: 100
|
|
13494
13760
|
},
|
|
13495
|
-
children: /* @__PURE__ */
|
|
13761
|
+
children: /* @__PURE__ */ jsx48(AnimatePresence, { children: overlays.map((overlay) => {
|
|
13496
13762
|
switch (overlay.kind) {
|
|
13497
13763
|
case "spotlight": {
|
|
13498
13764
|
const a = overlay.action;
|
|
13499
13765
|
const b = blockBbox(index, a.target_block);
|
|
13500
13766
|
if (!b) return null;
|
|
13501
|
-
return /* @__PURE__ */
|
|
13767
|
+
return /* @__PURE__ */ jsx48(
|
|
13502
13768
|
SpotlightMask,
|
|
13503
13769
|
{
|
|
13504
13770
|
page: page.page_dimensions,
|
|
@@ -13512,26 +13778,26 @@ function CinemaLayer({
|
|
|
13512
13778
|
const a = overlay.action;
|
|
13513
13779
|
const b = blockBbox(index, a.target_block);
|
|
13514
13780
|
if (!b) return null;
|
|
13515
|
-
return /* @__PURE__ */
|
|
13781
|
+
return /* @__PURE__ */ jsx48(AnimatedUnderline, { bbox: b, action: a }, overlay.id);
|
|
13516
13782
|
}
|
|
13517
13783
|
case "highlight": {
|
|
13518
13784
|
const a = overlay.action;
|
|
13519
13785
|
const b = blockBbox(index, a.target_block);
|
|
13520
13786
|
if (!b) return null;
|
|
13521
|
-
return /* @__PURE__ */
|
|
13787
|
+
return /* @__PURE__ */ jsx48(AnimatedHighlight, { bbox: b, action: a }, overlay.id);
|
|
13522
13788
|
}
|
|
13523
13789
|
case "pulse": {
|
|
13524
13790
|
const a = overlay.action;
|
|
13525
13791
|
const b = blockBbox(index, a.target_block);
|
|
13526
13792
|
if (!b) return null;
|
|
13527
|
-
return /* @__PURE__ */
|
|
13793
|
+
return /* @__PURE__ */ jsx48(PulseOverlay, { bbox: b, action: a }, overlay.id);
|
|
13528
13794
|
}
|
|
13529
13795
|
case "callout": {
|
|
13530
13796
|
const a = overlay.action;
|
|
13531
13797
|
const from = blockBbox(index, a.from_block);
|
|
13532
13798
|
const to = blockBbox(index, a.to_block);
|
|
13533
13799
|
if (!from || !to) return null;
|
|
13534
|
-
return /* @__PURE__ */
|
|
13800
|
+
return /* @__PURE__ */ jsx48(
|
|
13535
13801
|
CalloutArrow,
|
|
13536
13802
|
{
|
|
13537
13803
|
fromBbox: from,
|
|
@@ -13541,36 +13807,16 @@ function CinemaLayer({
|
|
|
13541
13807
|
overlay.id
|
|
13542
13808
|
);
|
|
13543
13809
|
}
|
|
13544
|
-
case "ghost_reference":
|
|
13545
|
-
|
|
13546
|
-
const hit = index.blockById.get(a.target_block);
|
|
13547
|
-
if (!hit) return null;
|
|
13548
|
-
const targetPage = index.byPage.get(a.target_page);
|
|
13549
|
-
if (!targetPage) return null;
|
|
13550
|
-
return /* @__PURE__ */ jsx50(
|
|
13551
|
-
GhostReference,
|
|
13552
|
-
{
|
|
13553
|
-
page: targetPage.page_dimensions,
|
|
13554
|
-
sourceBbox: hit.block.bbox,
|
|
13555
|
-
sourceBlockText: hit.block.text,
|
|
13556
|
-
sourcePageNumber: hit.pageNumber,
|
|
13557
|
-
action: a
|
|
13558
|
-
},
|
|
13559
|
-
overlay.id
|
|
13560
|
-
);
|
|
13561
|
-
}
|
|
13810
|
+
case "ghost_reference":
|
|
13811
|
+
return null;
|
|
13562
13812
|
case "box": {
|
|
13563
13813
|
const a = overlay.action;
|
|
13564
13814
|
const b = blockBbox(index, a.target_block);
|
|
13565
13815
|
if (!b) return null;
|
|
13566
|
-
return /* @__PURE__ */
|
|
13567
|
-
}
|
|
13568
|
-
case "label": {
|
|
13569
|
-
const a = overlay.action;
|
|
13570
|
-
const b = blockBbox(index, a.target_block);
|
|
13571
|
-
if (!b) return null;
|
|
13572
|
-
return /* @__PURE__ */ jsx50(StickyLabel, { bbox: b, action: a }, overlay.id);
|
|
13816
|
+
return /* @__PURE__ */ jsx48(BoxOverlay, { bbox: b, action: a }, overlay.id);
|
|
13573
13817
|
}
|
|
13818
|
+
case "label":
|
|
13819
|
+
return null;
|
|
13574
13820
|
case "clear":
|
|
13575
13821
|
case "camera":
|
|
13576
13822
|
return null;
|
|
@@ -13580,12 +13826,774 @@ function CinemaLayer({
|
|
|
13580
13826
|
);
|
|
13581
13827
|
}
|
|
13582
13828
|
|
|
13829
|
+
// src/components/TutorMode/GhostReferenceOverlay.tsx
|
|
13830
|
+
import { AnimatePresence as AnimatePresence2 } from "framer-motion";
|
|
13831
|
+
|
|
13832
|
+
// src/components/TutorMode/GhostReference.tsx
|
|
13833
|
+
import { motion as motion8 } from "framer-motion";
|
|
13834
|
+
import { jsx as jsx49, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
13835
|
+
var POSITIONS = {
|
|
13836
|
+
"top-right": {
|
|
13837
|
+
top: "clamp(12px, 4vw, 40px)",
|
|
13838
|
+
right: "clamp(12px, 4vw, 40px)"
|
|
13839
|
+
},
|
|
13840
|
+
"top-left": {
|
|
13841
|
+
top: "clamp(12px, 4vw, 40px)",
|
|
13842
|
+
left: "clamp(12px, 4vw, 40px)"
|
|
13843
|
+
},
|
|
13844
|
+
"bottom-right": {
|
|
13845
|
+
bottom: "clamp(12px, 4vw, 40px)",
|
|
13846
|
+
right: "clamp(12px, 4vw, 40px)"
|
|
13847
|
+
},
|
|
13848
|
+
"bottom-left": {
|
|
13849
|
+
bottom: "clamp(12px, 4vw, 40px)",
|
|
13850
|
+
left: "clamp(12px, 4vw, 40px)"
|
|
13851
|
+
}
|
|
13852
|
+
};
|
|
13853
|
+
var INK2 = "#2a2420";
|
|
13854
|
+
var PAPER2 = "#faf6ec";
|
|
13855
|
+
var PAPER_DEEP = "#f3ece0";
|
|
13856
|
+
var ACCENT2 = "#b04a1a";
|
|
13857
|
+
var RULE = "rgba(42, 36, 32, 0.10)";
|
|
13858
|
+
var SERIF2 = "'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', 'EB Garamond', 'Hoefler Text', Georgia, serif";
|
|
13859
|
+
function GhostReference({
|
|
13860
|
+
page,
|
|
13861
|
+
sourceBbox,
|
|
13862
|
+
sourceBlockText,
|
|
13863
|
+
action
|
|
13864
|
+
}) {
|
|
13865
|
+
const [x1, y1, x2, y2] = sourceBbox;
|
|
13866
|
+
const text = sourceBlockText ?? "(figure)";
|
|
13867
|
+
return /* @__PURE__ */ jsxs40(
|
|
13868
|
+
motion8.div,
|
|
13869
|
+
{
|
|
13870
|
+
initial: { opacity: 0, y: 24, scale: 0.97 },
|
|
13871
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
13872
|
+
exit: { opacity: 0, y: 20, scale: 0.97 },
|
|
13873
|
+
transition: {
|
|
13874
|
+
duration: 0.55,
|
|
13875
|
+
ease: [0.22, 1, 0.36, 1]
|
|
13876
|
+
// custom out-expo for an unhurried settle
|
|
13877
|
+
},
|
|
13878
|
+
style: {
|
|
13879
|
+
position: "absolute",
|
|
13880
|
+
width: "min(420px, calc(100vw - clamp(24px, 8vw, 80px)))",
|
|
13881
|
+
background: PAPER2,
|
|
13882
|
+
color: INK2,
|
|
13883
|
+
border: `1px solid ${RULE}`,
|
|
13884
|
+
// Barely-there corner radius — square-ish corners feel editorial,
|
|
13885
|
+
// 12px+ radius feels SaaS-notification. 3px is the sweet spot.
|
|
13886
|
+
borderRadius: 3,
|
|
13887
|
+
overflow: "hidden",
|
|
13888
|
+
// Warm, two-layer shadow: a tight contact shadow for definition,
|
|
13889
|
+
// a wider diffuse one tinted toward ink rather than pure grey.
|
|
13890
|
+
boxShadow: "0 1px 2px rgba(42, 36, 32, 0.10), 0 20px 44px -14px rgba(42, 36, 32, 0.22), 0 8px 20px -10px rgba(42, 36, 32, 0.14)",
|
|
13891
|
+
pointerEvents: "none",
|
|
13892
|
+
...POSITIONS[action.position]
|
|
13893
|
+
},
|
|
13894
|
+
"data-role": "ghost-reference",
|
|
13895
|
+
children: [
|
|
13896
|
+
/* @__PURE__ */ jsx49(
|
|
13897
|
+
motion8.span,
|
|
13898
|
+
{
|
|
13899
|
+
"aria-hidden": true,
|
|
13900
|
+
initial: { scaleY: 0 },
|
|
13901
|
+
animate: { scaleY: 1 },
|
|
13902
|
+
exit: { scaleY: 0 },
|
|
13903
|
+
transition: { duration: 0.55, delay: 0.06, ease: [0.22, 1, 0.36, 1] },
|
|
13904
|
+
style: {
|
|
13905
|
+
position: "absolute",
|
|
13906
|
+
left: 0,
|
|
13907
|
+
top: 0,
|
|
13908
|
+
bottom: 0,
|
|
13909
|
+
width: 3,
|
|
13910
|
+
background: ACCENT2,
|
|
13911
|
+
transformOrigin: "top"
|
|
13912
|
+
}
|
|
13913
|
+
}
|
|
13914
|
+
),
|
|
13915
|
+
/* @__PURE__ */ jsx49(
|
|
13916
|
+
"span",
|
|
13917
|
+
{
|
|
13918
|
+
"aria-hidden": true,
|
|
13919
|
+
style: {
|
|
13920
|
+
position: "absolute",
|
|
13921
|
+
inset: 0,
|
|
13922
|
+
pointerEvents: "none",
|
|
13923
|
+
background: `
|
|
13924
|
+
radial-gradient(120% 80% at 0% 0%, rgba(255, 244, 220, 0.5) 0%, transparent 55%),
|
|
13925
|
+
radial-gradient(100% 80% at 100% 100%, rgba(243, 229, 200, 0.35) 0%, transparent 60%)
|
|
13926
|
+
`
|
|
13927
|
+
}
|
|
13928
|
+
}
|
|
13929
|
+
),
|
|
13930
|
+
/* @__PURE__ */ jsxs40(
|
|
13931
|
+
"div",
|
|
13932
|
+
{
|
|
13933
|
+
style: {
|
|
13934
|
+
position: "relative",
|
|
13935
|
+
padding: "20px 22px 20px 26px",
|
|
13936
|
+
display: "flex",
|
|
13937
|
+
gap: 16,
|
|
13938
|
+
alignItems: "flex-start"
|
|
13939
|
+
},
|
|
13940
|
+
children: [
|
|
13941
|
+
/* @__PURE__ */ jsx49(
|
|
13942
|
+
motion8.div,
|
|
13943
|
+
{
|
|
13944
|
+
"aria-hidden": true,
|
|
13945
|
+
initial: { opacity: 0, scale: 0.92 },
|
|
13946
|
+
animate: { opacity: 1, scale: 1 },
|
|
13947
|
+
exit: { opacity: 0, scale: 0.92 },
|
|
13948
|
+
transition: { duration: 0.45, delay: 0.18, ease: [0.22, 1, 0.36, 1] },
|
|
13949
|
+
style: {
|
|
13950
|
+
flexShrink: 0,
|
|
13951
|
+
width: 46,
|
|
13952
|
+
aspectRatio: `${page.width} / ${page.height}`,
|
|
13953
|
+
background: PAPER_DEEP,
|
|
13954
|
+
borderRadius: 2,
|
|
13955
|
+
overflow: "hidden",
|
|
13956
|
+
boxShadow: "inset 0 0 0 1px rgba(42, 36, 32, 0.12), 0 1px 3px rgba(42, 36, 32, 0.10)"
|
|
13957
|
+
},
|
|
13958
|
+
children: /* @__PURE__ */ jsxs40(
|
|
13959
|
+
"svg",
|
|
13960
|
+
{
|
|
13961
|
+
width: "100%",
|
|
13962
|
+
height: "100%",
|
|
13963
|
+
viewBox: `0 0 ${page.width} ${page.height}`,
|
|
13964
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
13965
|
+
style: { display: "block" },
|
|
13966
|
+
children: [
|
|
13967
|
+
/* @__PURE__ */ jsx49(
|
|
13968
|
+
"rect",
|
|
13969
|
+
{
|
|
13970
|
+
x: 0,
|
|
13971
|
+
y: 0,
|
|
13972
|
+
width: page.width,
|
|
13973
|
+
height: page.height,
|
|
13974
|
+
fill: PAPER_DEEP
|
|
13975
|
+
}
|
|
13976
|
+
),
|
|
13977
|
+
TEXT_LINES.map((ln, i) => /* @__PURE__ */ jsx49(
|
|
13978
|
+
"rect",
|
|
13979
|
+
{
|
|
13980
|
+
x: page.width * ln.x,
|
|
13981
|
+
y: page.height * ln.y,
|
|
13982
|
+
width: page.width * ln.w,
|
|
13983
|
+
height: page.height * 0.012,
|
|
13984
|
+
fill: "rgba(42, 36, 32, 0.18)"
|
|
13985
|
+
},
|
|
13986
|
+
i
|
|
13987
|
+
)),
|
|
13988
|
+
/* @__PURE__ */ jsx49(
|
|
13989
|
+
"rect",
|
|
13990
|
+
{
|
|
13991
|
+
x: x1,
|
|
13992
|
+
y: y1,
|
|
13993
|
+
width: x2 - x1,
|
|
13994
|
+
height: y2 - y1,
|
|
13995
|
+
fill: "rgba(176, 74, 26, 0.28)",
|
|
13996
|
+
stroke: ACCENT2,
|
|
13997
|
+
strokeWidth: 18
|
|
13998
|
+
}
|
|
13999
|
+
)
|
|
14000
|
+
]
|
|
14001
|
+
}
|
|
14002
|
+
)
|
|
14003
|
+
}
|
|
14004
|
+
),
|
|
14005
|
+
/* @__PURE__ */ jsxs40(
|
|
14006
|
+
motion8.div,
|
|
14007
|
+
{
|
|
14008
|
+
initial: { opacity: 0, y: 6 },
|
|
14009
|
+
animate: { opacity: 1, y: 0 },
|
|
14010
|
+
exit: { opacity: 0 },
|
|
14011
|
+
transition: { duration: 0.5, delay: 0.26, ease: [0.22, 1, 0.36, 1] },
|
|
14012
|
+
style: {
|
|
14013
|
+
flex: 1,
|
|
14014
|
+
minWidth: 0,
|
|
14015
|
+
fontFamily: SERIF2,
|
|
14016
|
+
fontSize: 15.5,
|
|
14017
|
+
lineHeight: 1.55,
|
|
14018
|
+
color: INK2,
|
|
14019
|
+
fontFeatureSettings: "'liga' 1, 'kern' 1, 'onum' 1",
|
|
14020
|
+
textRendering: "optimizeLegibility",
|
|
14021
|
+
letterSpacing: 0.05,
|
|
14022
|
+
// Hang an ornamental opening glyph outside the text column
|
|
14023
|
+
// so the reader's eye falls into the paragraph as if into a
|
|
14024
|
+
// well-set pull quote.
|
|
14025
|
+
position: "relative",
|
|
14026
|
+
paddingLeft: 2
|
|
14027
|
+
},
|
|
14028
|
+
children: [
|
|
14029
|
+
/* @__PURE__ */ jsx49(
|
|
14030
|
+
"span",
|
|
14031
|
+
{
|
|
14032
|
+
"aria-hidden": true,
|
|
14033
|
+
style: {
|
|
14034
|
+
position: "absolute",
|
|
14035
|
+
left: -14,
|
|
14036
|
+
top: -2,
|
|
14037
|
+
color: ACCENT2,
|
|
14038
|
+
fontSize: 22,
|
|
14039
|
+
lineHeight: 1,
|
|
14040
|
+
fontWeight: 500
|
|
14041
|
+
// ornamental flourish anchoring the paragraph
|
|
14042
|
+
},
|
|
14043
|
+
children: "\u2767"
|
|
14044
|
+
}
|
|
14045
|
+
),
|
|
14046
|
+
/* @__PURE__ */ jsx49(
|
|
14047
|
+
"span",
|
|
14048
|
+
{
|
|
14049
|
+
style: {
|
|
14050
|
+
display: "-webkit-box",
|
|
14051
|
+
WebkitLineClamp: 8,
|
|
14052
|
+
WebkitBoxOrient: "vertical",
|
|
14053
|
+
overflow: "hidden"
|
|
14054
|
+
},
|
|
14055
|
+
children: text
|
|
14056
|
+
}
|
|
14057
|
+
)
|
|
14058
|
+
]
|
|
14059
|
+
}
|
|
14060
|
+
)
|
|
14061
|
+
]
|
|
14062
|
+
}
|
|
14063
|
+
),
|
|
14064
|
+
/* @__PURE__ */ jsx49(
|
|
14065
|
+
motion8.span,
|
|
14066
|
+
{
|
|
14067
|
+
"aria-hidden": true,
|
|
14068
|
+
initial: { scaleX: 0, opacity: 0 },
|
|
14069
|
+
animate: { scaleX: 1, opacity: 0.6 },
|
|
14070
|
+
exit: { opacity: 0 },
|
|
14071
|
+
transition: { duration: 0.5, delay: 0.42, ease: [0.22, 1, 0.36, 1] },
|
|
14072
|
+
style: {
|
|
14073
|
+
position: "absolute",
|
|
14074
|
+
right: 22,
|
|
14075
|
+
bottom: 10,
|
|
14076
|
+
height: 1,
|
|
14077
|
+
width: 28,
|
|
14078
|
+
background: ACCENT2,
|
|
14079
|
+
transformOrigin: "right"
|
|
14080
|
+
}
|
|
14081
|
+
}
|
|
14082
|
+
)
|
|
14083
|
+
]
|
|
14084
|
+
}
|
|
14085
|
+
);
|
|
14086
|
+
}
|
|
14087
|
+
var TEXT_LINES = [
|
|
14088
|
+
{ x: 0.1, y: 0.12, w: 0.54 },
|
|
14089
|
+
{ x: 0.1, y: 0.18, w: 0.68 },
|
|
14090
|
+
{ x: 0.1, y: 0.24, w: 0.48 },
|
|
14091
|
+
{ x: 0.1, y: 0.34, w: 0.72 },
|
|
14092
|
+
{ x: 0.1, y: 0.4, w: 0.62 },
|
|
14093
|
+
{ x: 0.1, y: 0.46, w: 0.66 },
|
|
14094
|
+
{ x: 0.1, y: 0.56, w: 0.56 },
|
|
14095
|
+
{ x: 0.1, y: 0.62, w: 0.7 },
|
|
14096
|
+
{ x: 0.1, y: 0.68, w: 0.44 },
|
|
14097
|
+
{ x: 0.1, y: 0.78, w: 0.64 },
|
|
14098
|
+
{ x: 0.1, y: 0.84, w: 0.58 }
|
|
14099
|
+
];
|
|
14100
|
+
|
|
14101
|
+
// src/components/TutorMode/GhostReferenceOverlay.tsx
|
|
14102
|
+
import { jsx as jsx50 } from "react/jsx-runtime";
|
|
14103
|
+
function GhostReferenceOverlay({
|
|
14104
|
+
overlays,
|
|
14105
|
+
index
|
|
14106
|
+
}) {
|
|
14107
|
+
const ghosts = overlays.filter((o) => o.kind === "ghost_reference");
|
|
14108
|
+
return /* @__PURE__ */ jsx50(
|
|
14109
|
+
"div",
|
|
14110
|
+
{
|
|
14111
|
+
"data-role": "ghost-reference-overlay",
|
|
14112
|
+
style: {
|
|
14113
|
+
position: "absolute",
|
|
14114
|
+
inset: 0,
|
|
14115
|
+
pointerEvents: "none",
|
|
14116
|
+
// Sits above the Reset view button (z-60) so a ghost card doesn't
|
|
14117
|
+
// disappear behind it if the host also renders the Reset button.
|
|
14118
|
+
zIndex: 70
|
|
14119
|
+
},
|
|
14120
|
+
children: /* @__PURE__ */ jsx50(AnimatePresence2, { children: ghosts.map((overlay) => {
|
|
14121
|
+
const a = overlay.action;
|
|
14122
|
+
const hit = index.blockById.get(a.target_block);
|
|
14123
|
+
if (!hit) return null;
|
|
14124
|
+
const targetPage = index.byPage.get(a.target_page);
|
|
14125
|
+
if (!targetPage) return null;
|
|
14126
|
+
return /* @__PURE__ */ jsx50(
|
|
14127
|
+
GhostReference,
|
|
14128
|
+
{
|
|
14129
|
+
page: targetPage.page_dimensions,
|
|
14130
|
+
sourceBbox: hit.block.bbox,
|
|
14131
|
+
sourceBlockText: hit.block.text,
|
|
14132
|
+
sourcePageNumber: hit.pageNumber,
|
|
14133
|
+
action: a
|
|
14134
|
+
},
|
|
14135
|
+
overlay.id
|
|
14136
|
+
);
|
|
14137
|
+
}) })
|
|
14138
|
+
}
|
|
14139
|
+
);
|
|
14140
|
+
}
|
|
14141
|
+
|
|
14142
|
+
// src/components/TutorMode/LabelOverlay.tsx
|
|
14143
|
+
import { AnimatePresence as AnimatePresence3 } from "framer-motion";
|
|
14144
|
+
|
|
14145
|
+
// src/components/TutorMode/StickyLabel.tsx
|
|
14146
|
+
import { motion as motion9 } from "framer-motion";
|
|
14147
|
+
import { jsx as jsx51, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
14148
|
+
var INK3 = "#2a2420";
|
|
14149
|
+
var PAPER3 = "#faf6ec";
|
|
14150
|
+
var ACCENT3 = "#b04a1a";
|
|
14151
|
+
var SERIF3 = "'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', 'EB Garamond', 'Hoefler Text', Georgia, serif";
|
|
14152
|
+
var STEM = 18;
|
|
14153
|
+
function StickyLabel({ screenAnchor, action }) {
|
|
14154
|
+
const { x, y } = screenAnchor;
|
|
14155
|
+
const layout = LAYOUTS[action.position] ?? LAYOUTS.top;
|
|
14156
|
+
return /* @__PURE__ */ jsxs41(
|
|
14157
|
+
motion9.div,
|
|
14158
|
+
{
|
|
14159
|
+
initial: { opacity: 0, scale: 0.88 },
|
|
14160
|
+
animate: { opacity: 1, scale: 1 },
|
|
14161
|
+
exit: { opacity: 0, scale: 0.92 },
|
|
14162
|
+
transition: {
|
|
14163
|
+
duration: 0.4,
|
|
14164
|
+
ease: [0.22, 1, 0.36, 1]
|
|
14165
|
+
},
|
|
14166
|
+
style: {
|
|
14167
|
+
position: "absolute",
|
|
14168
|
+
left: x,
|
|
14169
|
+
top: y,
|
|
14170
|
+
// Wrapper positions at the anchor; the body's transform places
|
|
14171
|
+
// it on the correct side via `layout.containerTransform`.
|
|
14172
|
+
transform: layout.containerTransform,
|
|
14173
|
+
pointerEvents: "none",
|
|
14174
|
+
// Compose transform-origin toward the anchor so scale-in feels
|
|
14175
|
+
// tethered to the block rather than free-floating.
|
|
14176
|
+
transformOrigin: layout.origin
|
|
14177
|
+
},
|
|
14178
|
+
"data-role": "label",
|
|
14179
|
+
children: [
|
|
14180
|
+
/* @__PURE__ */ jsx51(
|
|
14181
|
+
motion9.span,
|
|
14182
|
+
{
|
|
14183
|
+
"aria-hidden": true,
|
|
14184
|
+
initial: { scaleX: 0, scaleY: 0, opacity: 0 },
|
|
14185
|
+
animate: { scaleX: 1, scaleY: 1, opacity: 1 },
|
|
14186
|
+
exit: { opacity: 0 },
|
|
14187
|
+
transition: { duration: 0.35, ease: [0.22, 1, 0.36, 1] },
|
|
14188
|
+
style: {
|
|
14189
|
+
position: "absolute",
|
|
14190
|
+
background: ACCENT3,
|
|
14191
|
+
transformOrigin: layout.stemOrigin,
|
|
14192
|
+
...layout.stem
|
|
14193
|
+
}
|
|
14194
|
+
}
|
|
14195
|
+
),
|
|
14196
|
+
/* @__PURE__ */ jsx51(
|
|
14197
|
+
motion9.span,
|
|
14198
|
+
{
|
|
14199
|
+
"aria-hidden": true,
|
|
14200
|
+
initial: { scale: 0 },
|
|
14201
|
+
animate: { scale: 1 },
|
|
14202
|
+
exit: { scale: 0 },
|
|
14203
|
+
transition: { duration: 0.3, delay: 0.15, ease: [0.22, 1, 0.36, 1] },
|
|
14204
|
+
style: {
|
|
14205
|
+
position: "absolute",
|
|
14206
|
+
width: 6,
|
|
14207
|
+
height: 6,
|
|
14208
|
+
borderRadius: "50%",
|
|
14209
|
+
background: ACCENT3,
|
|
14210
|
+
boxShadow: `0 0 0 2px ${PAPER3}, 0 0 0 3px rgba(176, 74, 26, 0.25)`,
|
|
14211
|
+
...layout.dot
|
|
14212
|
+
}
|
|
14213
|
+
}
|
|
14214
|
+
),
|
|
14215
|
+
/* @__PURE__ */ jsx51(
|
|
14216
|
+
motion9.div,
|
|
14217
|
+
{
|
|
14218
|
+
initial: { y: layout.bodyIn.y, x: layout.bodyIn.x, opacity: 0 },
|
|
14219
|
+
animate: { y: 0, x: 0, opacity: 1 },
|
|
14220
|
+
exit: { opacity: 0 },
|
|
14221
|
+
transition: { duration: 0.4, delay: 0.08, ease: [0.22, 1, 0.36, 1] },
|
|
14222
|
+
style: {
|
|
14223
|
+
position: "absolute",
|
|
14224
|
+
...layout.bodyAnchor,
|
|
14225
|
+
background: PAPER3,
|
|
14226
|
+
color: INK3,
|
|
14227
|
+
border: "1px solid rgba(42, 36, 32, 0.10)",
|
|
14228
|
+
borderRadius: 3,
|
|
14229
|
+
padding: "6px 12px 6px 14px",
|
|
14230
|
+
fontFamily: SERIF3,
|
|
14231
|
+
fontSize: 12.5,
|
|
14232
|
+
lineHeight: 1.25,
|
|
14233
|
+
letterSpacing: 0.6,
|
|
14234
|
+
textTransform: "uppercase",
|
|
14235
|
+
fontWeight: 500,
|
|
14236
|
+
whiteSpace: "nowrap",
|
|
14237
|
+
maxWidth: 240,
|
|
14238
|
+
overflow: "hidden",
|
|
14239
|
+
textOverflow: "ellipsis",
|
|
14240
|
+
// Warm two-layer shadow (matches GhostReference's palette).
|
|
14241
|
+
boxShadow: "0 1px 2px rgba(42, 36, 32, 0.12), 0 8px 18px -6px rgba(42, 36, 32, 0.22)",
|
|
14242
|
+
// Internal left accent rule — a 2px terracotta stripe.
|
|
14243
|
+
backgroundImage: `linear-gradient(to right, ${ACCENT3} 0, ${ACCENT3} 2px, transparent 2px)`,
|
|
14244
|
+
backgroundRepeat: "no-repeat",
|
|
14245
|
+
backgroundSize: "2px 100%",
|
|
14246
|
+
backgroundPosition: "left top"
|
|
14247
|
+
},
|
|
14248
|
+
children: action.text
|
|
14249
|
+
}
|
|
14250
|
+
)
|
|
14251
|
+
]
|
|
14252
|
+
}
|
|
14253
|
+
);
|
|
14254
|
+
}
|
|
14255
|
+
var LAYOUTS = {
|
|
14256
|
+
top: {
|
|
14257
|
+
containerTransform: "translate(-50%, -100%)",
|
|
14258
|
+
origin: "50% 100%",
|
|
14259
|
+
stem: {
|
|
14260
|
+
left: "50%",
|
|
14261
|
+
bottom: -STEM,
|
|
14262
|
+
width: 1,
|
|
14263
|
+
height: STEM - 4,
|
|
14264
|
+
transform: "translateX(-50%)"
|
|
14265
|
+
},
|
|
14266
|
+
stemOrigin: "bottom",
|
|
14267
|
+
dot: {
|
|
14268
|
+
left: "50%",
|
|
14269
|
+
bottom: -STEM + 1,
|
|
14270
|
+
transform: "translate(-50%, 100%)"
|
|
14271
|
+
},
|
|
14272
|
+
bodyAnchor: { bottom: 0, left: "50%", transform: "translateX(-50%)" },
|
|
14273
|
+
bodyIn: { x: 0, y: -4 }
|
|
14274
|
+
},
|
|
14275
|
+
bottom: {
|
|
14276
|
+
containerTransform: "translate(-50%, 0%)",
|
|
14277
|
+
origin: "50% 0%",
|
|
14278
|
+
stem: {
|
|
14279
|
+
left: "50%",
|
|
14280
|
+
top: STEM - (STEM - 4),
|
|
14281
|
+
width: 1,
|
|
14282
|
+
height: STEM - 4,
|
|
14283
|
+
transform: "translateX(-50%)"
|
|
14284
|
+
},
|
|
14285
|
+
stemOrigin: "top",
|
|
14286
|
+
dot: {
|
|
14287
|
+
left: "50%",
|
|
14288
|
+
top: -1,
|
|
14289
|
+
transform: "translate(-50%, -100%)"
|
|
14290
|
+
},
|
|
14291
|
+
bodyAnchor: { top: STEM, left: "50%", transform: "translateX(-50%)" },
|
|
14292
|
+
bodyIn: { x: 0, y: 4 }
|
|
14293
|
+
},
|
|
14294
|
+
left: {
|
|
14295
|
+
containerTransform: "translate(-100%, -50%)",
|
|
14296
|
+
origin: "100% 50%",
|
|
14297
|
+
stem: {
|
|
14298
|
+
top: "50%",
|
|
14299
|
+
right: -STEM,
|
|
14300
|
+
width: STEM - 4,
|
|
14301
|
+
height: 1,
|
|
14302
|
+
transform: "translateY(-50%)"
|
|
14303
|
+
},
|
|
14304
|
+
stemOrigin: "right",
|
|
14305
|
+
dot: {
|
|
14306
|
+
right: -STEM + 1,
|
|
14307
|
+
top: "50%",
|
|
14308
|
+
transform: "translate(100%, -50%)"
|
|
14309
|
+
},
|
|
14310
|
+
bodyAnchor: { right: 0, top: "50%", transform: "translateY(-50%)" },
|
|
14311
|
+
bodyIn: { x: -4, y: 0 }
|
|
14312
|
+
},
|
|
14313
|
+
right: {
|
|
14314
|
+
containerTransform: "translate(0%, -50%)",
|
|
14315
|
+
origin: "0% 50%",
|
|
14316
|
+
stem: {
|
|
14317
|
+
top: "50%",
|
|
14318
|
+
left: STEM - (STEM - 4),
|
|
14319
|
+
width: STEM - 4,
|
|
14320
|
+
height: 1,
|
|
14321
|
+
transform: "translateY(-50%)"
|
|
14322
|
+
},
|
|
14323
|
+
stemOrigin: "left",
|
|
14324
|
+
dot: {
|
|
14325
|
+
left: -1,
|
|
14326
|
+
top: "50%",
|
|
14327
|
+
transform: "translate(-100%, -50%)"
|
|
14328
|
+
},
|
|
14329
|
+
bodyAnchor: { left: STEM, top: "50%", transform: "translateY(-50%)" },
|
|
14330
|
+
bodyIn: { x: 4, y: 0 }
|
|
14331
|
+
}
|
|
14332
|
+
};
|
|
14333
|
+
|
|
14334
|
+
// src/components/TutorMode/LabelOverlay.tsx
|
|
14335
|
+
import { jsx as jsx52 } from "react/jsx-runtime";
|
|
14336
|
+
function LabelOverlay({
|
|
14337
|
+
overlays,
|
|
14338
|
+
index,
|
|
14339
|
+
currentPage,
|
|
14340
|
+
camera,
|
|
14341
|
+
viewport
|
|
14342
|
+
}) {
|
|
14343
|
+
const labels = overlays.filter((o) => o.kind === "label");
|
|
14344
|
+
const page = index.byPage.get(currentPage);
|
|
14345
|
+
return /* @__PURE__ */ jsx52(
|
|
14346
|
+
"div",
|
|
14347
|
+
{
|
|
14348
|
+
"data-role": "label-overlay",
|
|
14349
|
+
style: {
|
|
14350
|
+
position: "absolute",
|
|
14351
|
+
inset: 0,
|
|
14352
|
+
pointerEvents: "none",
|
|
14353
|
+
overflow: "hidden",
|
|
14354
|
+
// Above CameraView and the Reset button but not above
|
|
14355
|
+
// GhostReferenceOverlay (z:70) — labels are block-attached and
|
|
14356
|
+
// should not cover a cross-page reference card.
|
|
14357
|
+
zIndex: 65
|
|
14358
|
+
},
|
|
14359
|
+
children: /* @__PURE__ */ jsx52(AnimatePresence3, { children: page ? labels.map((overlay) => {
|
|
14360
|
+
const a = overlay.action;
|
|
14361
|
+
const hit = index.blockById.get(a.target_block);
|
|
14362
|
+
if (!hit) return null;
|
|
14363
|
+
const anchor = computeScreenAnchor(
|
|
14364
|
+
hit.block.bbox,
|
|
14365
|
+
a.position,
|
|
14366
|
+
page,
|
|
14367
|
+
camera,
|
|
14368
|
+
viewport
|
|
14369
|
+
);
|
|
14370
|
+
return /* @__PURE__ */ jsx52(
|
|
14371
|
+
StickyLabel,
|
|
14372
|
+
{
|
|
14373
|
+
screenAnchor: anchor,
|
|
14374
|
+
action: a
|
|
14375
|
+
},
|
|
14376
|
+
overlay.id
|
|
14377
|
+
);
|
|
14378
|
+
}) : null })
|
|
14379
|
+
}
|
|
14380
|
+
);
|
|
14381
|
+
}
|
|
14382
|
+
function computeScreenAnchor(bbox, where, page, camera, viewport) {
|
|
14383
|
+
const [x1, y1, x2, y2] = bbox;
|
|
14384
|
+
const pageCX = page.page_dimensions.width / 2;
|
|
14385
|
+
const pageCY = page.page_dimensions.height / 2;
|
|
14386
|
+
let px, py;
|
|
14387
|
+
switch (where) {
|
|
14388
|
+
case "top":
|
|
14389
|
+
px = (x1 + x2) / 2;
|
|
14390
|
+
py = y1;
|
|
14391
|
+
break;
|
|
14392
|
+
case "bottom":
|
|
14393
|
+
px = (x1 + x2) / 2;
|
|
14394
|
+
py = y2;
|
|
14395
|
+
break;
|
|
14396
|
+
case "left":
|
|
14397
|
+
px = x1;
|
|
14398
|
+
py = (y1 + y2) / 2;
|
|
14399
|
+
break;
|
|
14400
|
+
case "right":
|
|
14401
|
+
px = x2;
|
|
14402
|
+
py = (y1 + y2) / 2;
|
|
14403
|
+
break;
|
|
14404
|
+
default:
|
|
14405
|
+
px = (x1 + x2) / 2;
|
|
14406
|
+
py = y1;
|
|
14407
|
+
}
|
|
14408
|
+
const screenX = viewport.width / 2 + camera.x + (px - pageCX) * camera.scale;
|
|
14409
|
+
const screenY = viewport.height / 2 + camera.y + (py - pageCY) * camera.scale;
|
|
14410
|
+
return { x: screenX, y: screenY };
|
|
14411
|
+
}
|
|
14412
|
+
|
|
14413
|
+
// src/components/TutorMode/CalloutLabelOverlay.tsx
|
|
14414
|
+
import { AnimatePresence as AnimatePresence4, motion as motion10 } from "framer-motion";
|
|
14415
|
+
import { jsx as jsx53 } from "react/jsx-runtime";
|
|
14416
|
+
function CalloutLabelOverlay({
|
|
14417
|
+
overlays,
|
|
14418
|
+
index,
|
|
14419
|
+
currentPage,
|
|
14420
|
+
camera,
|
|
14421
|
+
viewport
|
|
14422
|
+
}) {
|
|
14423
|
+
const callouts = overlays.filter(
|
|
14424
|
+
(o) => o.kind === "callout" && o.action.label
|
|
14425
|
+
);
|
|
14426
|
+
const page = index.byPage.get(currentPage);
|
|
14427
|
+
return /* @__PURE__ */ jsx53(
|
|
14428
|
+
"div",
|
|
14429
|
+
{
|
|
14430
|
+
"data-role": "callout-label-overlay",
|
|
14431
|
+
style: {
|
|
14432
|
+
position: "absolute",
|
|
14433
|
+
inset: 0,
|
|
14434
|
+
pointerEvents: "none",
|
|
14435
|
+
overflow: "hidden",
|
|
14436
|
+
// Above the arrow stroke (which is inside CameraView) and the
|
|
14437
|
+
// reset button, below the ghost card.
|
|
14438
|
+
zIndex: 68
|
|
14439
|
+
},
|
|
14440
|
+
children: /* @__PURE__ */ jsx53(AnimatePresence4, { children: page ? callouts.map((overlay) => {
|
|
14441
|
+
const a = overlay.action;
|
|
14442
|
+
const fromHit = index.blockById.get(a.from_block);
|
|
14443
|
+
const toHit = index.blockById.get(a.to_block);
|
|
14444
|
+
if (!fromHit || !toHit || !a.label) return null;
|
|
14445
|
+
const pos = computePillAnchor(
|
|
14446
|
+
fromHit.block.bbox,
|
|
14447
|
+
toHit.block.bbox,
|
|
14448
|
+
page,
|
|
14449
|
+
camera,
|
|
14450
|
+
viewport
|
|
14451
|
+
);
|
|
14452
|
+
return /* @__PURE__ */ jsx53(
|
|
14453
|
+
CalloutLabelPill,
|
|
14454
|
+
{
|
|
14455
|
+
label: a.label,
|
|
14456
|
+
anchor: pos,
|
|
14457
|
+
side: pos.side
|
|
14458
|
+
},
|
|
14459
|
+
overlay.id
|
|
14460
|
+
);
|
|
14461
|
+
}) : null })
|
|
14462
|
+
}
|
|
14463
|
+
);
|
|
14464
|
+
}
|
|
14465
|
+
function computePillAnchor(fromBbox, toBbox, page, camera, viewport) {
|
|
14466
|
+
const aCX = (fromBbox[0] + fromBbox[2]) / 2;
|
|
14467
|
+
const aCY = (fromBbox[1] + fromBbox[3]) / 2;
|
|
14468
|
+
const bCX = (toBbox[0] + toBbox[2]) / 2;
|
|
14469
|
+
const bCY = (toBbox[1] + toBbox[3]) / 2;
|
|
14470
|
+
const dx = bCX - aCX;
|
|
14471
|
+
const dy = bCY - aCY;
|
|
14472
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
14473
|
+
const ux = dx / len;
|
|
14474
|
+
const uy = dy / len;
|
|
14475
|
+
const bHalfW = (toBbox[2] - toBbox[0]) / 2;
|
|
14476
|
+
const bHalfH = (toBbox[3] - toBbox[1]) / 2;
|
|
14477
|
+
const bOff = Math.min(Math.max(bHalfW, bHalfH), 60);
|
|
14478
|
+
const toX = bCX - ux * bOff;
|
|
14479
|
+
const toY = bCY - uy * bOff;
|
|
14480
|
+
const pageCX = page.page_dimensions.width / 2;
|
|
14481
|
+
const pageCY = page.page_dimensions.height / 2;
|
|
14482
|
+
const tipScreenX = viewport.width / 2 + camera.x + (toX - pageCX) * camera.scale;
|
|
14483
|
+
const tipScreenY = viewport.height / 2 + camera.y + (toY - pageCY) * camera.scale;
|
|
14484
|
+
const isVertical = Math.abs(dy) >= Math.abs(dx);
|
|
14485
|
+
const OFFSET = 32;
|
|
14486
|
+
const MAX_PILL_W = 220;
|
|
14487
|
+
const MAX_PILL_H = 30;
|
|
14488
|
+
const SAFE = 16;
|
|
14489
|
+
if (isVertical) {
|
|
14490
|
+
const canFitRight = tipScreenX + OFFSET + MAX_PILL_W < viewport.width - SAFE;
|
|
14491
|
+
const side2 = canFitRight ? "right" : "left";
|
|
14492
|
+
return {
|
|
14493
|
+
x: tipScreenX + (side2 === "right" ? OFFSET : -OFFSET),
|
|
14494
|
+
y: tipScreenY,
|
|
14495
|
+
side: side2
|
|
14496
|
+
};
|
|
14497
|
+
}
|
|
14498
|
+
const canFitBelow = tipScreenY + OFFSET + MAX_PILL_H < viewport.height - SAFE;
|
|
14499
|
+
const side = canFitBelow ? "below" : "above";
|
|
14500
|
+
return {
|
|
14501
|
+
x: tipScreenX,
|
|
14502
|
+
y: tipScreenY + (side === "below" ? OFFSET : -OFFSET),
|
|
14503
|
+
side
|
|
14504
|
+
};
|
|
14505
|
+
}
|
|
14506
|
+
function CalloutLabelPill({
|
|
14507
|
+
label,
|
|
14508
|
+
anchor,
|
|
14509
|
+
side
|
|
14510
|
+
}) {
|
|
14511
|
+
const spec = PILL_SIDE_SPECS[side];
|
|
14512
|
+
return /* @__PURE__ */ jsx53(
|
|
14513
|
+
motion10.div,
|
|
14514
|
+
{
|
|
14515
|
+
initial: { opacity: 0, scale: 0.92, ...spec.slideIn },
|
|
14516
|
+
animate: { opacity: 1, scale: 1, x: 0, y: 0 },
|
|
14517
|
+
exit: { opacity: 0, scale: 0.94 },
|
|
14518
|
+
transition: { duration: 0.45, delay: 0.5, ease: EASE_OUT_EXPO },
|
|
14519
|
+
style: {
|
|
14520
|
+
position: "absolute",
|
|
14521
|
+
left: anchor.x,
|
|
14522
|
+
top: anchor.y,
|
|
14523
|
+
transform: spec.transform,
|
|
14524
|
+
pointerEvents: "none",
|
|
14525
|
+
background: PAPER,
|
|
14526
|
+
color: INK,
|
|
14527
|
+
border: "1px solid rgba(42, 36, 32, 0.10)",
|
|
14528
|
+
borderRadius: 3,
|
|
14529
|
+
padding: spec.padding,
|
|
14530
|
+
fontFamily: SERIF,
|
|
14531
|
+
fontSize: 11.5,
|
|
14532
|
+
lineHeight: 1.2,
|
|
14533
|
+
letterSpacing: 0.6,
|
|
14534
|
+
textTransform: "uppercase",
|
|
14535
|
+
fontWeight: 500,
|
|
14536
|
+
whiteSpace: "nowrap",
|
|
14537
|
+
maxWidth: 220,
|
|
14538
|
+
overflow: "hidden",
|
|
14539
|
+
textOverflow: "ellipsis",
|
|
14540
|
+
boxShadow: "0 1px 2px rgba(42, 36, 32, 0.12), 0 8px 18px -6px rgba(42, 36, 32, 0.22)",
|
|
14541
|
+
// Accent rule on the "inward" edge (the one closest to the arrow).
|
|
14542
|
+
backgroundImage: spec.accentGradient,
|
|
14543
|
+
backgroundRepeat: "no-repeat",
|
|
14544
|
+
backgroundSize: spec.accentSize,
|
|
14545
|
+
backgroundPosition: spec.accentPosition
|
|
14546
|
+
},
|
|
14547
|
+
"data-role": "callout-label",
|
|
14548
|
+
children: label
|
|
14549
|
+
}
|
|
14550
|
+
);
|
|
14551
|
+
}
|
|
14552
|
+
var PILL_SIDE_SPECS = {
|
|
14553
|
+
// Pill sits to the RIGHT of a vertical arrow → left edge anchors at
|
|
14554
|
+
// offset point, accent rule on the left (pointing back toward arrow).
|
|
14555
|
+
right: {
|
|
14556
|
+
transform: "translate(0, -50%)",
|
|
14557
|
+
slideIn: { x: -6 },
|
|
14558
|
+
padding: "5px 12px 5px 14px",
|
|
14559
|
+
accentGradient: `linear-gradient(to right, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14560
|
+
accentSize: "2px 100%",
|
|
14561
|
+
accentPosition: "left top"
|
|
14562
|
+
},
|
|
14563
|
+
left: {
|
|
14564
|
+
transform: "translate(-100%, -50%)",
|
|
14565
|
+
slideIn: { x: 6 },
|
|
14566
|
+
padding: "5px 14px 5px 12px",
|
|
14567
|
+
accentGradient: `linear-gradient(to left, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14568
|
+
accentSize: "2px 100%",
|
|
14569
|
+
accentPosition: "right top"
|
|
14570
|
+
},
|
|
14571
|
+
// Pill sits BELOW a horizontal arrow → top edge anchors at offset
|
|
14572
|
+
// point, accent rule on the top (pointing back up toward arrow).
|
|
14573
|
+
below: {
|
|
14574
|
+
transform: "translate(-50%, 0)",
|
|
14575
|
+
slideIn: { y: -6 },
|
|
14576
|
+
padding: "7px 12px 5px 12px",
|
|
14577
|
+
accentGradient: `linear-gradient(to bottom, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14578
|
+
accentSize: "100% 2px",
|
|
14579
|
+
accentPosition: "left top"
|
|
14580
|
+
},
|
|
14581
|
+
above: {
|
|
14582
|
+
transform: "translate(-50%, -100%)",
|
|
14583
|
+
slideIn: { y: 6 },
|
|
14584
|
+
padding: "5px 12px 7px 12px",
|
|
14585
|
+
accentGradient: `linear-gradient(to top, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14586
|
+
accentSize: "100% 2px",
|
|
14587
|
+
accentPosition: "left bottom"
|
|
14588
|
+
}
|
|
14589
|
+
};
|
|
14590
|
+
|
|
13583
14591
|
// src/components/TutorMode/SubtitleBar.tsx
|
|
13584
|
-
import { AnimatePresence as
|
|
13585
|
-
import { jsx as
|
|
14592
|
+
import { AnimatePresence as AnimatePresence5, motion as motion11 } from "framer-motion";
|
|
14593
|
+
import { jsx as jsx54 } from "react/jsx-runtime";
|
|
13586
14594
|
function SubtitleBar({ text }) {
|
|
13587
|
-
return /* @__PURE__ */
|
|
13588
|
-
|
|
14595
|
+
return /* @__PURE__ */ jsx54(AnimatePresence5, { children: text ? /* @__PURE__ */ jsx54(
|
|
14596
|
+
motion11.div,
|
|
13589
14597
|
{
|
|
13590
14598
|
initial: { opacity: 0, y: 20 },
|
|
13591
14599
|
animate: { opacity: 1, y: 0 },
|
|
@@ -13621,7 +14629,20 @@ init_camera_math();
|
|
|
13621
14629
|
var DEFAULT_MIN_OVERLAY_MS = 3500;
|
|
13622
14630
|
var StoryboardEngine = class {
|
|
13623
14631
|
constructor(deps) {
|
|
13624
|
-
|
|
14632
|
+
/**
|
|
14633
|
+
* Timers that schedule the START of a step (via `setTimeout(runStep, at_ms)`).
|
|
14634
|
+
* These are storyboard-scoped: when a new storyboard arrives, anything still
|
|
14635
|
+
* pending should be abandoned.
|
|
14636
|
+
*/
|
|
14637
|
+
this.pendingStepTimers = /* @__PURE__ */ new Set();
|
|
14638
|
+
/**
|
|
14639
|
+
* Timers that auto-REMOVE an already-placed overlay after its visible
|
|
14640
|
+
* duration. Keyed by overlay id so we can cancel one specifically. These are
|
|
14641
|
+
* NOT cancelled when a new storyboard starts — otherwise the still-visible
|
|
14642
|
+
* overlay from the previous beat would get stranded in the store forever
|
|
14643
|
+
* (the "stuck spotlight" bug).
|
|
14644
|
+
*/
|
|
14645
|
+
this.overlayRemovalTimers = /* @__PURE__ */ new Map();
|
|
13625
14646
|
this.currentStoryboardId = 0;
|
|
13626
14647
|
this.deps = deps;
|
|
13627
14648
|
}
|
|
@@ -13664,13 +14685,13 @@ var StoryboardEngine = class {
|
|
|
13664
14685
|
if (storyboardId !== this.currentStoryboardId) return;
|
|
13665
14686
|
this.runStep(step);
|
|
13666
14687
|
}, step.at_ms);
|
|
13667
|
-
this.
|
|
14688
|
+
this.pendingStepTimers.add(timer);
|
|
13668
14689
|
}
|
|
13669
14690
|
const markExecuting = setTimeout(() => {
|
|
13670
14691
|
if (storyboardId !== this.currentStoryboardId) return;
|
|
13671
14692
|
narrationStore.getState().setEngineStatus("executing");
|
|
13672
14693
|
}, 0);
|
|
13673
|
-
this.
|
|
14694
|
+
this.pendingStepTimers.add(markExecuting);
|
|
13674
14695
|
const last = steps[steps.length - 1];
|
|
13675
14696
|
if (last) {
|
|
13676
14697
|
const totalMs = last.at_ms + last.duration_ms;
|
|
@@ -13678,18 +14699,29 @@ var StoryboardEngine = class {
|
|
|
13678
14699
|
if (storyboardId !== this.currentStoryboardId) return;
|
|
13679
14700
|
narrationStore.getState().setEngineStatus("idle");
|
|
13680
14701
|
}, totalMs + 50);
|
|
13681
|
-
this.
|
|
14702
|
+
this.pendingStepTimers.add(markIdle);
|
|
13682
14703
|
}
|
|
13683
14704
|
}
|
|
13684
|
-
/**
|
|
14705
|
+
/**
|
|
14706
|
+
* Abort pending STEP dispatches from the current storyboard. Overlay
|
|
14707
|
+
* removal timers are left alone so already-visible overlays still auto-
|
|
14708
|
+
* expire on their own schedule. To force-clear every overlay, call
|
|
14709
|
+
* `resetVisuals()` instead.
|
|
14710
|
+
*/
|
|
13685
14711
|
cancelPending() {
|
|
13686
|
-
for (const t of this.
|
|
13687
|
-
this.
|
|
14712
|
+
for (const t of this.pendingStepTimers) clearTimeout(t);
|
|
14713
|
+
this.pendingStepTimers.clear();
|
|
13688
14714
|
this.deps.narrationStore.getState().setEngineStatus("idle");
|
|
13689
14715
|
}
|
|
13690
|
-
/**
|
|
14716
|
+
/** Cancel every removal timer (used by resetVisuals only). */
|
|
14717
|
+
cancelAllRemovalTimers() {
|
|
14718
|
+
for (const t of this.overlayRemovalTimers.values()) clearTimeout(t);
|
|
14719
|
+
this.overlayRemovalTimers.clear();
|
|
14720
|
+
}
|
|
14721
|
+
/** Reset visuals: clear overlays, cancel every removal timer, fit camera. */
|
|
13691
14722
|
resetVisuals() {
|
|
13692
14723
|
this.cancelPending();
|
|
14724
|
+
this.cancelAllRemovalTimers();
|
|
13693
14725
|
const { narrationStore, bboxIndex, getViewport } = this.deps;
|
|
13694
14726
|
narrationStore.getState().clearOverlays();
|
|
13695
14727
|
const viewport = getViewport();
|
|
@@ -13760,8 +14792,9 @@ var StoryboardEngine = class {
|
|
|
13760
14792
|
narrationStore.getState().addOverlay(overlay);
|
|
13761
14793
|
const timer = setTimeout(() => {
|
|
13762
14794
|
narrationStore.getState().removeOverlay(overlay.id);
|
|
14795
|
+
this.overlayRemovalTimers.delete(overlay.id);
|
|
13763
14796
|
}, visibleMs);
|
|
13764
|
-
this.
|
|
14797
|
+
this.overlayRemovalTimers.set(overlay.id, timer);
|
|
13765
14798
|
return true;
|
|
13766
14799
|
}
|
|
13767
14800
|
applyCamera(action, durationMs) {
|
|
@@ -13786,12 +14819,17 @@ var StoryboardEngine = class {
|
|
|
13786
14819
|
const blockCY = (y1 + y2) / 2;
|
|
13787
14820
|
const pageCX = pageDims.page_dimensions.width / 2;
|
|
13788
14821
|
const pageCY = pageDims.page_dimensions.height / 2;
|
|
13789
|
-
const
|
|
13790
|
-
const
|
|
14822
|
+
const rawX = (pageCX - blockCX) * finalScale;
|
|
14823
|
+
const rawY = (pageCY - blockCY) * finalScale;
|
|
14824
|
+
const clamped = clampCamera(
|
|
14825
|
+
{ scale: finalScale, x: rawX, y: rawY },
|
|
14826
|
+
pageDims.page_dimensions,
|
|
14827
|
+
viewport
|
|
14828
|
+
);
|
|
13791
14829
|
const camera = {
|
|
13792
|
-
scale:
|
|
13793
|
-
x,
|
|
13794
|
-
y,
|
|
14830
|
+
scale: clamped.scale,
|
|
14831
|
+
x: clamped.x,
|
|
14832
|
+
y: clamped.y,
|
|
13795
14833
|
easing: action.easing
|
|
13796
14834
|
};
|
|
13797
14835
|
narrationStore.getState().setCamera(camera);
|
|
@@ -14776,7 +15814,7 @@ function storyboardFromMatch(match, page) {
|
|
|
14776
15814
|
}
|
|
14777
15815
|
|
|
14778
15816
|
// src/components/TutorMode/TutorModeContainer.tsx
|
|
14779
|
-
import { jsx as
|
|
15817
|
+
import { Fragment as Fragment4, jsx as jsx55, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
14780
15818
|
function buildBBoxIndex(bboxData) {
|
|
14781
15819
|
const byPage = /* @__PURE__ */ new Map();
|
|
14782
15820
|
const blockById = /* @__PURE__ */ new Map();
|
|
@@ -14812,15 +15850,35 @@ function TutorModeContainer({
|
|
|
14812
15850
|
showExitButton = true,
|
|
14813
15851
|
onExitTutorMode,
|
|
14814
15852
|
minOverlayDurationMs,
|
|
15853
|
+
backgroundColor = "#ffffff",
|
|
15854
|
+
loadingComponent,
|
|
15855
|
+
onPageChange,
|
|
14815
15856
|
className
|
|
14816
15857
|
}) {
|
|
14817
15858
|
const containerRef = useRef27(null);
|
|
14818
15859
|
const index = useMemo15(() => buildBBoxIndex(bboxData), [bboxData]);
|
|
14819
|
-
const {
|
|
15860
|
+
const {
|
|
15861
|
+
document: document2,
|
|
15862
|
+
currentPage: viewerCurrentPage,
|
|
15863
|
+
numPages,
|
|
15864
|
+
goToPage: viewerGoToPage
|
|
15865
|
+
} = usePDFViewer();
|
|
14820
15866
|
const [pageProxy, setPageProxy] = useState30(null);
|
|
14821
15867
|
const [viewport, setViewport] = useState30({ width: 800, height: 1e3 });
|
|
14822
15868
|
const camera = useStore2(narrationStore, (s) => s.camera);
|
|
14823
15869
|
const activeOverlays = useStore2(narrationStore, (s) => s.activeOverlays);
|
|
15870
|
+
useEffect28(() => {
|
|
15871
|
+
if (numPages <= 0) return;
|
|
15872
|
+
if (pageNumber < 1 || pageNumber > numPages) return;
|
|
15873
|
+
if (viewerCurrentPage === pageNumber) return;
|
|
15874
|
+
viewerGoToPage(pageNumber);
|
|
15875
|
+
}, [pageNumber, numPages, viewerCurrentPage, viewerGoToPage]);
|
|
15876
|
+
useEffect28(() => {
|
|
15877
|
+
if (!onPageChange) return;
|
|
15878
|
+
if (viewerCurrentPage === pageNumber) return;
|
|
15879
|
+
if (viewerCurrentPage < 1) return;
|
|
15880
|
+
onPageChange(viewerCurrentPage);
|
|
15881
|
+
}, [viewerCurrentPage, pageNumber, onPageChange]);
|
|
14824
15882
|
useEffect28(() => {
|
|
14825
15883
|
if (!containerRef.current) return;
|
|
14826
15884
|
const el = containerRef.current;
|
|
@@ -14976,7 +16034,8 @@ function TutorModeContainer({
|
|
|
14976
16034
|
const rasterScale = dpiScale * (scale || 1);
|
|
14977
16035
|
const baseW = page ? page.page_dimensions.width * (scale || 1) : 0;
|
|
14978
16036
|
const baseH = page ? page.page_dimensions.height * (scale || 1) : 0;
|
|
14979
|
-
|
|
16037
|
+
const isReady = !!page && !!pageProxy;
|
|
16038
|
+
return /* @__PURE__ */ jsxs42(
|
|
14980
16039
|
"div",
|
|
14981
16040
|
{
|
|
14982
16041
|
ref: containerRef,
|
|
@@ -14986,12 +16045,12 @@ function TutorModeContainer({
|
|
|
14986
16045
|
width: "100%",
|
|
14987
16046
|
height: "100%",
|
|
14988
16047
|
overflow: "hidden",
|
|
14989
|
-
background:
|
|
16048
|
+
background: backgroundColor
|
|
14990
16049
|
},
|
|
14991
16050
|
"data-role": "tutor-mode-container",
|
|
14992
|
-
"data-page-loaded":
|
|
16051
|
+
"data-page-loaded": isReady ? "true" : "false",
|
|
14993
16052
|
children: [
|
|
14994
|
-
showExitButton ? /* @__PURE__ */
|
|
16053
|
+
showExitButton && isReady ? /* @__PURE__ */ jsx55(
|
|
14995
16054
|
"button",
|
|
14996
16055
|
{
|
|
14997
16056
|
onClick: () => {
|
|
@@ -15008,7 +16067,9 @@ function TutorModeContainer({
|
|
|
15008
16067
|
padding: "8px 14px",
|
|
15009
16068
|
border: "none",
|
|
15010
16069
|
borderRadius: 8,
|
|
15011
|
-
|
|
16070
|
+
// Dark translucent pill with white text reads cleanly on both
|
|
16071
|
+
// light and dark container backgrounds.
|
|
16072
|
+
background: "rgba(17,24,39,0.72)",
|
|
15012
16073
|
color: "white",
|
|
15013
16074
|
cursor: "pointer",
|
|
15014
16075
|
fontFamily: "system-ui, sans-serif",
|
|
@@ -15020,43 +16081,127 @@ function TutorModeContainer({
|
|
|
15020
16081
|
children: "Reset view"
|
|
15021
16082
|
}
|
|
15022
16083
|
) : null,
|
|
15023
|
-
|
|
16084
|
+
isReady ? /* @__PURE__ */ jsxs42(Fragment4, { children: [
|
|
16085
|
+
/* @__PURE__ */ jsx55(CameraView, { camera, children: /* @__PURE__ */ jsxs42(
|
|
16086
|
+
"div",
|
|
16087
|
+
{
|
|
16088
|
+
style: {
|
|
16089
|
+
position: "absolute",
|
|
16090
|
+
top: "50%",
|
|
16091
|
+
left: "50%",
|
|
16092
|
+
width: baseW,
|
|
16093
|
+
height: baseH,
|
|
16094
|
+
transform: "translate(-50%, -50%)"
|
|
16095
|
+
},
|
|
16096
|
+
children: [
|
|
16097
|
+
/* @__PURE__ */ jsx55(
|
|
16098
|
+
PDFPage,
|
|
16099
|
+
{
|
|
16100
|
+
pageNumber,
|
|
16101
|
+
page: pageProxy,
|
|
16102
|
+
scale: rasterScale,
|
|
16103
|
+
rotation,
|
|
16104
|
+
showTextLayer: false,
|
|
16105
|
+
showHighlightLayer: false,
|
|
16106
|
+
showAnnotationLayer: false
|
|
16107
|
+
}
|
|
16108
|
+
),
|
|
16109
|
+
/* @__PURE__ */ jsx55(
|
|
16110
|
+
CinemaLayer,
|
|
16111
|
+
{
|
|
16112
|
+
page,
|
|
16113
|
+
index,
|
|
16114
|
+
overlays: activeOverlays,
|
|
16115
|
+
scale: scale || 1
|
|
16116
|
+
}
|
|
16117
|
+
)
|
|
16118
|
+
]
|
|
16119
|
+
}
|
|
16120
|
+
) }),
|
|
16121
|
+
/* @__PURE__ */ jsx55(
|
|
16122
|
+
LabelOverlay,
|
|
16123
|
+
{
|
|
16124
|
+
overlays: activeOverlays,
|
|
16125
|
+
index,
|
|
16126
|
+
currentPage: pageNumber,
|
|
16127
|
+
camera,
|
|
16128
|
+
viewport
|
|
16129
|
+
}
|
|
16130
|
+
),
|
|
16131
|
+
/* @__PURE__ */ jsx55(
|
|
16132
|
+
CalloutLabelOverlay,
|
|
16133
|
+
{
|
|
16134
|
+
overlays: activeOverlays,
|
|
16135
|
+
index,
|
|
16136
|
+
currentPage: pageNumber,
|
|
16137
|
+
camera,
|
|
16138
|
+
viewport
|
|
16139
|
+
}
|
|
16140
|
+
),
|
|
16141
|
+
/* @__PURE__ */ jsx55(GhostReferenceOverlay, { overlays: activeOverlays, index })
|
|
16142
|
+
] }) : /* @__PURE__ */ jsx55(TutorLoadingState, { custom: loadingComponent }),
|
|
16143
|
+
showSubtitles ? /* @__PURE__ */ jsx55(SubtitleBar, { text: currentChunk ?? null }) : null
|
|
16144
|
+
]
|
|
16145
|
+
}
|
|
16146
|
+
);
|
|
16147
|
+
}
|
|
16148
|
+
function TutorLoadingState({
|
|
16149
|
+
custom
|
|
16150
|
+
}) {
|
|
16151
|
+
if (custom) {
|
|
16152
|
+
return /* @__PURE__ */ jsx55(
|
|
16153
|
+
"div",
|
|
16154
|
+
{
|
|
16155
|
+
style: {
|
|
16156
|
+
position: "absolute",
|
|
16157
|
+
inset: 0,
|
|
16158
|
+
display: "flex",
|
|
16159
|
+
alignItems: "center",
|
|
16160
|
+
justifyContent: "center"
|
|
16161
|
+
},
|
|
16162
|
+
"data-role": "tutor-loading",
|
|
16163
|
+
children: custom
|
|
16164
|
+
}
|
|
16165
|
+
);
|
|
16166
|
+
}
|
|
16167
|
+
return /* @__PURE__ */ jsxs42(
|
|
16168
|
+
"div",
|
|
16169
|
+
{
|
|
16170
|
+
style: {
|
|
16171
|
+
position: "absolute",
|
|
16172
|
+
inset: 0,
|
|
16173
|
+
display: "flex",
|
|
16174
|
+
flexDirection: "column",
|
|
16175
|
+
alignItems: "center",
|
|
16176
|
+
justifyContent: "center",
|
|
16177
|
+
gap: 12,
|
|
16178
|
+
color: "rgba(0,0,0,0.55)",
|
|
16179
|
+
fontFamily: "system-ui, sans-serif",
|
|
16180
|
+
fontSize: 13
|
|
16181
|
+
},
|
|
16182
|
+
"data-role": "tutor-loading",
|
|
16183
|
+
children: [
|
|
16184
|
+
/* @__PURE__ */ jsx55(
|
|
15024
16185
|
"div",
|
|
15025
16186
|
{
|
|
16187
|
+
"aria-hidden": true,
|
|
15026
16188
|
style: {
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
}
|
|
15034
|
-
children: [
|
|
15035
|
-
/* @__PURE__ */ jsx52(
|
|
15036
|
-
PDFPage,
|
|
15037
|
-
{
|
|
15038
|
-
pageNumber,
|
|
15039
|
-
page: pageProxy,
|
|
15040
|
-
scale: rasterScale,
|
|
15041
|
-
rotation,
|
|
15042
|
-
showTextLayer: false,
|
|
15043
|
-
showHighlightLayer: false,
|
|
15044
|
-
showAnnotationLayer: false
|
|
15045
|
-
}
|
|
15046
|
-
),
|
|
15047
|
-
/* @__PURE__ */ jsx52(
|
|
15048
|
-
CinemaLayer,
|
|
15049
|
-
{
|
|
15050
|
-
page,
|
|
15051
|
-
index,
|
|
15052
|
-
overlays: activeOverlays,
|
|
15053
|
-
scale: scale || 1
|
|
15054
|
-
}
|
|
15055
|
-
)
|
|
15056
|
-
]
|
|
16189
|
+
width: 36,
|
|
16190
|
+
height: 36,
|
|
16191
|
+
borderRadius: "50%",
|
|
16192
|
+
border: "3px solid rgba(0,0,0,0.1)",
|
|
16193
|
+
borderTopColor: "rgba(0,0,0,0.45)",
|
|
16194
|
+
animation: "pdf-tutor-spin 0.9s linear infinite"
|
|
16195
|
+
}
|
|
15057
16196
|
}
|
|
15058
|
-
)
|
|
15059
|
-
|
|
16197
|
+
),
|
|
16198
|
+
/* @__PURE__ */ jsx55("span", { children: "Loading document\u2026" }),
|
|
16199
|
+
/* @__PURE__ */ jsx55("style", { children: `
|
|
16200
|
+
@keyframes pdf-tutor-spin {
|
|
16201
|
+
from { transform: rotate(0deg); }
|
|
16202
|
+
to { transform: rotate(360deg); }
|
|
16203
|
+
}
|
|
16204
|
+
` })
|
|
15060
16205
|
]
|
|
15061
16206
|
}
|
|
15062
16207
|
);
|