pdfjs-reader-core 0.4.1 → 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 +5 -0
- package/dist/index.cjs +1504 -450
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -23
- package/dist/index.d.ts +115 -23
- package/dist/index.js +1493 -439
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1885,6 +1885,17 @@ function computeCameraForBlock(bbox, page, viewport, opts = {}) {
|
|
|
1885
1885
|
const y = (pageCY - blockCY) * scale;
|
|
1886
1886
|
return { scale, x, y };
|
|
1887
1887
|
}
|
|
1888
|
+
function clampCamera(target, page, viewport) {
|
|
1889
|
+
const pageWScreen = page.width * target.scale;
|
|
1890
|
+
const pageHScreen = page.height * target.scale;
|
|
1891
|
+
const maxOffsetX = Math.max(0, (pageWScreen - viewport.width) / 2);
|
|
1892
|
+
const maxOffsetY = Math.max(0, (pageHScreen - viewport.height) / 2);
|
|
1893
|
+
return {
|
|
1894
|
+
scale: target.scale,
|
|
1895
|
+
x: Math.max(-maxOffsetX, Math.min(maxOffsetX, target.x)),
|
|
1896
|
+
y: Math.max(-maxOffsetY, Math.min(maxOffsetY, target.y))
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1888
1899
|
var init_camera_math = __esm({
|
|
1889
1900
|
"src/utils/camera-math.ts"() {
|
|
1890
1901
|
"use strict";
|
|
@@ -3748,8 +3759,8 @@ var init_PluginManager = __esm({
|
|
|
3748
3759
|
/**
|
|
3749
3760
|
* Get toolbar items by position
|
|
3750
3761
|
*/
|
|
3751
|
-
getToolbarItemsByPosition(
|
|
3752
|
-
return this.getToolbarItems().filter((item) => item.position ===
|
|
3762
|
+
getToolbarItemsByPosition(position) {
|
|
3763
|
+
return this.getToolbarItems().filter((item) => item.position === position);
|
|
3753
3764
|
}
|
|
3754
3765
|
/**
|
|
3755
3766
|
* Get all sidebar panels from all plugins
|
|
@@ -4862,7 +4873,7 @@ var init_MobileToolbar = __esm({
|
|
|
4862
4873
|
sidebarOpen,
|
|
4863
4874
|
theme,
|
|
4864
4875
|
onThemeChange,
|
|
4865
|
-
position
|
|
4876
|
+
position = "bottom",
|
|
4866
4877
|
className
|
|
4867
4878
|
}) {
|
|
4868
4879
|
const [showMoreMenu, setShowMoreMenu] = (0, import_react17.useState)(false);
|
|
@@ -4896,8 +4907,8 @@ var init_MobileToolbar = __esm({
|
|
|
4896
4907
|
"bg-white dark:bg-gray-800",
|
|
4897
4908
|
"border-gray-200 dark:border-gray-700",
|
|
4898
4909
|
"px-2 py-1 safe-area-inset",
|
|
4899
|
-
|
|
4900
|
-
|
|
4910
|
+
position === "top" && "top-0 border-b",
|
|
4911
|
+
position === "bottom" && "bottom-0 border-t",
|
|
4901
4912
|
className
|
|
4902
4913
|
),
|
|
4903
4914
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between gap-1", children: [
|
|
@@ -5023,7 +5034,7 @@ var init_MobileToolbar = __esm({
|
|
|
5023
5034
|
"bg-white dark:bg-gray-800",
|
|
5024
5035
|
"rounded-lg shadow-lg",
|
|
5025
5036
|
"border border-gray-200 dark:border-gray-700",
|
|
5026
|
-
|
|
5037
|
+
position === "bottom" ? "bottom-full mb-2" : "top-full mt-2"
|
|
5027
5038
|
),
|
|
5028
5039
|
children: [
|
|
5029
5040
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-2 py-1 text-xs text-gray-500 dark:text-gray-400 font-medium", children: "Theme" }),
|
|
@@ -6849,7 +6860,7 @@ var init_AnnotationToolbar = __esm({
|
|
|
6849
6860
|
onShapeTypeChange: onShapeTypeChangeProp,
|
|
6850
6861
|
onColorChange: onColorChangeProp,
|
|
6851
6862
|
onStrokeWidthChange: onStrokeWidthChangeProp,
|
|
6852
|
-
position
|
|
6863
|
+
position = "top",
|
|
6853
6864
|
className
|
|
6854
6865
|
}) {
|
|
6855
6866
|
const storeActiveTool = useAnnotationStore((s) => s.activeAnnotationTool);
|
|
@@ -6899,9 +6910,9 @@ var init_AnnotationToolbar = __esm({
|
|
|
6899
6910
|
{
|
|
6900
6911
|
className: cn(
|
|
6901
6912
|
"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",
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6913
|
+
position === "floating" && "fixed bottom-20 left-1/2 -translate-x-1/2 z-50",
|
|
6914
|
+
position === "top" && "sticky top-0 z-40",
|
|
6915
|
+
position === "bottom" && "sticky bottom-0 z-40",
|
|
6905
6916
|
!isActive && "opacity-90",
|
|
6906
6917
|
className
|
|
6907
6918
|
),
|
|
@@ -8550,7 +8561,7 @@ var init_SelectionToolbar = __esm({
|
|
|
8550
8561
|
activeColor = "yellow",
|
|
8551
8562
|
className
|
|
8552
8563
|
}) {
|
|
8553
|
-
const [
|
|
8564
|
+
const [position, setPosition] = (0, import_react35.useState)({ top: 0, left: 0, visible: false });
|
|
8554
8565
|
const toolbarRef = (0, import_react35.useRef)(null);
|
|
8555
8566
|
(0, import_react35.useEffect)(() => {
|
|
8556
8567
|
if (selection && selection.text && selection.rects.length > 0) {
|
|
@@ -8589,7 +8600,7 @@ var init_SelectionToolbar = __esm({
|
|
|
8589
8600
|
const handleCopy = (0, import_react35.useCallback)(() => {
|
|
8590
8601
|
onCopy?.();
|
|
8591
8602
|
}, [onCopy]);
|
|
8592
|
-
if (!
|
|
8603
|
+
if (!position.visible || !selection?.text) {
|
|
8593
8604
|
return null;
|
|
8594
8605
|
}
|
|
8595
8606
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
@@ -8607,8 +8618,8 @@ var init_SelectionToolbar = __esm({
|
|
|
8607
8618
|
className
|
|
8608
8619
|
),
|
|
8609
8620
|
style: {
|
|
8610
|
-
top:
|
|
8611
|
-
left:
|
|
8621
|
+
top: position.top,
|
|
8622
|
+
left: position.left,
|
|
8612
8623
|
transform: "translateX(-50%)"
|
|
8613
8624
|
},
|
|
8614
8625
|
onMouseDown: (e) => {
|
|
@@ -8725,7 +8736,7 @@ var init_HighlightPopover = __esm({
|
|
|
8725
8736
|
}) {
|
|
8726
8737
|
const [isEditingComment, setIsEditingComment] = (0, import_react36.useState)(false);
|
|
8727
8738
|
const [comment, setComment] = (0, import_react36.useState)(highlight?.comment ?? "");
|
|
8728
|
-
const [
|
|
8739
|
+
const [position, setPosition] = (0, import_react36.useState)({ top: 0, left: 0, visible: false });
|
|
8729
8740
|
const popoverRef = (0, import_react36.useRef)(null);
|
|
8730
8741
|
const textareaRef = (0, import_react36.useRef)(null);
|
|
8731
8742
|
(0, import_react36.useEffect)(() => {
|
|
@@ -8773,11 +8784,11 @@ var init_HighlightPopover = __esm({
|
|
|
8773
8784
|
onClose();
|
|
8774
8785
|
}
|
|
8775
8786
|
}
|
|
8776
|
-
if (
|
|
8787
|
+
if (position.visible) {
|
|
8777
8788
|
document.addEventListener("mousedown", handleClickOutside);
|
|
8778
8789
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
8779
8790
|
}
|
|
8780
|
-
}, [
|
|
8791
|
+
}, [position.visible, onClose]);
|
|
8781
8792
|
(0, import_react36.useEffect)(() => {
|
|
8782
8793
|
function handleKeyDown(event) {
|
|
8783
8794
|
if (event.key === "Escape") {
|
|
@@ -8789,11 +8800,11 @@ var init_HighlightPopover = __esm({
|
|
|
8789
8800
|
}
|
|
8790
8801
|
}
|
|
8791
8802
|
}
|
|
8792
|
-
if (
|
|
8803
|
+
if (position.visible) {
|
|
8793
8804
|
document.addEventListener("keydown", handleKeyDown);
|
|
8794
8805
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
8795
8806
|
}
|
|
8796
|
-
}, [
|
|
8807
|
+
}, [position.visible, isEditingComment, highlight?.comment, onClose]);
|
|
8797
8808
|
const handleColorClick = (0, import_react36.useCallback)(
|
|
8798
8809
|
(color) => {
|
|
8799
8810
|
if (highlight) {
|
|
@@ -8820,7 +8831,7 @@ var init_HighlightPopover = __esm({
|
|
|
8820
8831
|
onCopy?.(highlight.text);
|
|
8821
8832
|
}
|
|
8822
8833
|
}, [highlight, onCopy]);
|
|
8823
|
-
if (!highlight || !
|
|
8834
|
+
if (!highlight || !position.visible) {
|
|
8824
8835
|
return null;
|
|
8825
8836
|
}
|
|
8826
8837
|
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
@@ -8837,8 +8848,8 @@ var init_HighlightPopover = __esm({
|
|
|
8837
8848
|
className
|
|
8838
8849
|
),
|
|
8839
8850
|
style: {
|
|
8840
|
-
top:
|
|
8841
|
-
left:
|
|
8851
|
+
top: position.top,
|
|
8852
|
+
left: position.left,
|
|
8842
8853
|
transform: "translate(-50%, -100%)",
|
|
8843
8854
|
width: 280
|
|
8844
8855
|
},
|
|
@@ -10250,7 +10261,7 @@ var init_FloatingZoomControls = __esm({
|
|
|
10250
10261
|
init_utils();
|
|
10251
10262
|
import_jsx_runtime28 = require("react/jsx-runtime");
|
|
10252
10263
|
FloatingZoomControls = (0, import_react42.memo)(function FloatingZoomControls2({
|
|
10253
|
-
position
|
|
10264
|
+
position = "bottom-right",
|
|
10254
10265
|
className,
|
|
10255
10266
|
showFitToWidth = true,
|
|
10256
10267
|
showFitToPage = false,
|
|
@@ -10291,7 +10302,7 @@ var init_FloatingZoomControls = __esm({
|
|
|
10291
10302
|
"bg-white dark:bg-gray-800 rounded-lg shadow-lg",
|
|
10292
10303
|
"border border-gray-200 dark:border-gray-700",
|
|
10293
10304
|
"p-1",
|
|
10294
|
-
positionClasses[
|
|
10305
|
+
positionClasses[position],
|
|
10295
10306
|
className
|
|
10296
10307
|
),
|
|
10297
10308
|
children: [
|
|
@@ -12119,7 +12130,7 @@ var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
|
12119
12130
|
var QuickNoteButton = (0, import_react48.memo)(function QuickNoteButton2({
|
|
12120
12131
|
pageNumber,
|
|
12121
12132
|
scale,
|
|
12122
|
-
position
|
|
12133
|
+
position = "top-right",
|
|
12123
12134
|
onClick,
|
|
12124
12135
|
className,
|
|
12125
12136
|
visible = true
|
|
@@ -12128,11 +12139,11 @@ var QuickNoteButton = (0, import_react48.memo)(function QuickNoteButton2({
|
|
|
12128
12139
|
const handleClick = (0, import_react48.useCallback)(
|
|
12129
12140
|
(e) => {
|
|
12130
12141
|
e.stopPropagation();
|
|
12131
|
-
const x =
|
|
12132
|
-
const y =
|
|
12142
|
+
const x = position === "top-right" ? 80 : 80;
|
|
12143
|
+
const y = position === "top-right" ? 20 : 80;
|
|
12133
12144
|
onClick(pageNumber, x / scale, y / scale);
|
|
12134
12145
|
},
|
|
12135
|
-
[pageNumber, onClick,
|
|
12146
|
+
[pageNumber, onClick, position, scale]
|
|
12136
12147
|
);
|
|
12137
12148
|
if (!visible) {
|
|
12138
12149
|
return null;
|
|
@@ -12153,8 +12164,8 @@ var QuickNoteButton = (0, import_react48.memo)(function QuickNoteButton2({
|
|
|
12153
12164
|
"transition-all duration-200",
|
|
12154
12165
|
"focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2",
|
|
12155
12166
|
isHovered && "scale-110",
|
|
12156
|
-
|
|
12157
|
-
|
|
12167
|
+
position === "top-right" && "top-3 right-3",
|
|
12168
|
+
position === "bottom-right" && "bottom-3 right-3",
|
|
12158
12169
|
className
|
|
12159
12170
|
),
|
|
12160
12171
|
title: "Add quick note",
|
|
@@ -12180,7 +12191,7 @@ init_utils();
|
|
|
12180
12191
|
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
12181
12192
|
var QuickNotePopover = (0, import_react49.memo)(function QuickNotePopover2({
|
|
12182
12193
|
visible,
|
|
12183
|
-
position
|
|
12194
|
+
position,
|
|
12184
12195
|
initialContent = "",
|
|
12185
12196
|
agentLastStatement,
|
|
12186
12197
|
onSave,
|
|
@@ -12190,7 +12201,7 @@ var QuickNotePopover = (0, import_react49.memo)(function QuickNotePopover2({
|
|
|
12190
12201
|
const [content, setContent] = (0, import_react49.useState)(initialContent);
|
|
12191
12202
|
const textareaRef = (0, import_react49.useRef)(null);
|
|
12192
12203
|
const popoverRef = (0, import_react49.useRef)(null);
|
|
12193
|
-
const [adjustedPosition, setAdjustedPosition] = (0, import_react49.useState)(
|
|
12204
|
+
const [adjustedPosition, setAdjustedPosition] = (0, import_react49.useState)(position);
|
|
12194
12205
|
(0, import_react49.useEffect)(() => {
|
|
12195
12206
|
if (visible && textareaRef.current) {
|
|
12196
12207
|
textareaRef.current.focus();
|
|
@@ -12205,7 +12216,7 @@ var QuickNotePopover = (0, import_react49.memo)(function QuickNotePopover2({
|
|
|
12205
12216
|
if (!visible || !popoverRef.current) return;
|
|
12206
12217
|
const rect = popoverRef.current.getBoundingClientRect();
|
|
12207
12218
|
const padding = 10;
|
|
12208
|
-
let { x, y } =
|
|
12219
|
+
let { x, y } = position;
|
|
12209
12220
|
if (x + rect.width > window.innerWidth - padding) {
|
|
12210
12221
|
x = window.innerWidth - rect.width - padding;
|
|
12211
12222
|
}
|
|
@@ -12219,7 +12230,7 @@ var QuickNotePopover = (0, import_react49.memo)(function QuickNotePopover2({
|
|
|
12219
12230
|
y = padding;
|
|
12220
12231
|
}
|
|
12221
12232
|
setAdjustedPosition({ x, y });
|
|
12222
|
-
}, [
|
|
12233
|
+
}, [position, visible]);
|
|
12223
12234
|
const handleSave = (0, import_react49.useCallback)(() => {
|
|
12224
12235
|
if (content.trim()) {
|
|
12225
12236
|
onSave(content.trim());
|
|
@@ -12336,11 +12347,11 @@ var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
|
12336
12347
|
var AskAboutOverlay = (0, import_react50.memo)(function AskAboutOverlay2({
|
|
12337
12348
|
visible,
|
|
12338
12349
|
progress,
|
|
12339
|
-
position
|
|
12350
|
+
position,
|
|
12340
12351
|
size = 60,
|
|
12341
12352
|
className
|
|
12342
12353
|
}) {
|
|
12343
|
-
if (!visible || !
|
|
12354
|
+
if (!visible || !position) {
|
|
12344
12355
|
return null;
|
|
12345
12356
|
}
|
|
12346
12357
|
const strokeWidth = 4;
|
|
@@ -12356,8 +12367,8 @@ var AskAboutOverlay = (0, import_react50.memo)(function AskAboutOverlay2({
|
|
|
12356
12367
|
className
|
|
12357
12368
|
),
|
|
12358
12369
|
style: {
|
|
12359
|
-
left:
|
|
12360
|
-
top:
|
|
12370
|
+
left: position.x - size / 2,
|
|
12371
|
+
top: position.y - size / 2
|
|
12361
12372
|
},
|
|
12362
12373
|
children: [
|
|
12363
12374
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
@@ -12445,20 +12456,20 @@ var import_react51 = require("react");
|
|
|
12445
12456
|
init_utils();
|
|
12446
12457
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
12447
12458
|
var AskAboutTrigger = (0, import_react51.memo)(function AskAboutTrigger2({
|
|
12448
|
-
position
|
|
12459
|
+
position,
|
|
12449
12460
|
onConfirm,
|
|
12450
12461
|
onCancel,
|
|
12451
12462
|
visible,
|
|
12452
12463
|
autoHideDelay = 5e3,
|
|
12453
12464
|
className
|
|
12454
12465
|
}) {
|
|
12455
|
-
const [adjustedPosition, setAdjustedPosition] = (0, import_react51.useState)(
|
|
12466
|
+
const [adjustedPosition, setAdjustedPosition] = (0, import_react51.useState)(position);
|
|
12456
12467
|
const triggerRef = (0, import_react51.useRef)(null);
|
|
12457
12468
|
(0, import_react51.useEffect)(() => {
|
|
12458
12469
|
if (!visible || !triggerRef.current) return;
|
|
12459
12470
|
const rect = triggerRef.current.getBoundingClientRect();
|
|
12460
12471
|
const padding = 10;
|
|
12461
|
-
let { x, y } =
|
|
12472
|
+
let { x, y } = position;
|
|
12462
12473
|
if (x + rect.width / 2 > window.innerWidth - padding) {
|
|
12463
12474
|
x = window.innerWidth - rect.width / 2 - padding;
|
|
12464
12475
|
}
|
|
@@ -12466,10 +12477,10 @@ var AskAboutTrigger = (0, import_react51.memo)(function AskAboutTrigger2({
|
|
|
12466
12477
|
x = rect.width / 2 + padding;
|
|
12467
12478
|
}
|
|
12468
12479
|
if (y + rect.height > window.innerHeight - padding) {
|
|
12469
|
-
y =
|
|
12480
|
+
y = position.y - rect.height - 20;
|
|
12470
12481
|
}
|
|
12471
12482
|
setAdjustedPosition({ x, y });
|
|
12472
|
-
}, [
|
|
12483
|
+
}, [position, visible]);
|
|
12473
12484
|
(0, import_react51.useEffect)(() => {
|
|
12474
12485
|
if (!visible || autoHideDelay === 0) return;
|
|
12475
12486
|
const timer = setTimeout(onCancel, autoHideDelay);
|
|
@@ -13118,7 +13129,7 @@ function withErrorBoundary({ component, ...props }) {
|
|
|
13118
13129
|
init_PDFLoadingScreen2();
|
|
13119
13130
|
|
|
13120
13131
|
// src/components/TutorMode/TutorModeContainer.tsx
|
|
13121
|
-
var
|
|
13132
|
+
var import_react58 = require("react");
|
|
13122
13133
|
var import_zustand2 = require("zustand");
|
|
13123
13134
|
init_PDFPage2();
|
|
13124
13135
|
init_hooks();
|
|
@@ -13158,26 +13169,48 @@ function CameraView({
|
|
|
13158
13169
|
}
|
|
13159
13170
|
|
|
13160
13171
|
// src/components/TutorMode/CinemaLayer.tsx
|
|
13161
|
-
var
|
|
13172
|
+
var import_framer_motion8 = require("framer-motion");
|
|
13162
13173
|
|
|
13163
13174
|
// src/components/TutorMode/SpotlightMask.tsx
|
|
13164
13175
|
var import_react55 = require("react");
|
|
13165
13176
|
var import_framer_motion2 = require("framer-motion");
|
|
13177
|
+
|
|
13178
|
+
// src/components/TutorMode/tokens.ts
|
|
13179
|
+
var INK = "#2a2420";
|
|
13180
|
+
var PAPER = "#faf6ec";
|
|
13181
|
+
var ACCENT = "#b04a1a";
|
|
13182
|
+
var ACCENT_SOFT = "rgba(176, 74, 26, 0.18)";
|
|
13183
|
+
var ACCENT_GLOW = "rgba(176, 74, 26, 0.35)";
|
|
13184
|
+
var MARKER = "#e6b422";
|
|
13185
|
+
var MARKER_SOFT = "rgba(230, 180, 34, 0.38)";
|
|
13186
|
+
var SERIF = "'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', 'EB Garamond', 'Hoefler Text', Georgia, serif";
|
|
13187
|
+
var EASE_OUT_EXPO = [0.22, 1, 0.36, 1];
|
|
13188
|
+
|
|
13189
|
+
// src/components/TutorMode/SpotlightMask.tsx
|
|
13166
13190
|
var import_jsx_runtime42 = require("react/jsx-runtime");
|
|
13167
13191
|
function SpotlightMask({
|
|
13168
13192
|
page,
|
|
13169
13193
|
bbox,
|
|
13170
13194
|
action,
|
|
13171
|
-
durationMs =
|
|
13195
|
+
durationMs = 500
|
|
13172
13196
|
}) {
|
|
13173
13197
|
const maskId = (0, import_react55.useId)();
|
|
13174
13198
|
const filterId = `${maskId}-blur`;
|
|
13175
|
-
const [
|
|
13176
|
-
const
|
|
13177
|
-
const
|
|
13178
|
-
const
|
|
13179
|
-
const
|
|
13180
|
-
const
|
|
13199
|
+
const [rawX1, rawY1, rawX2, rawY2] = bbox;
|
|
13200
|
+
const rawW = Math.max(0, rawX2 - rawX1);
|
|
13201
|
+
const rawH = Math.max(0, rawY2 - rawY1);
|
|
13202
|
+
const pad = Math.min(28, Math.max(10, Math.min(rawW, rawH) * 0.06));
|
|
13203
|
+
const x1 = rawX1 - pad;
|
|
13204
|
+
const y1 = rawY1 - pad;
|
|
13205
|
+
const x2 = rawX2 + pad;
|
|
13206
|
+
const y2 = rawY2 + pad;
|
|
13207
|
+
const w = x2 - x1;
|
|
13208
|
+
const h = y2 - y1;
|
|
13209
|
+
const rx = action.shape === "rounded" ? 14 : action.shape === "ellipse" ? w / 2 : 0;
|
|
13210
|
+
const ry = action.shape === "rounded" ? 14 : action.shape === "ellipse" ? h / 2 : 0;
|
|
13211
|
+
const feather = Math.max(16, action.feather_px);
|
|
13212
|
+
const cx = (x1 + x2) / 2;
|
|
13213
|
+
const cy = (y1 + y2) / 2;
|
|
13181
13214
|
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
13182
13215
|
"svg",
|
|
13183
13216
|
{
|
|
@@ -13190,7 +13223,8 @@ function SpotlightMask({
|
|
|
13190
13223
|
inset: 0,
|
|
13191
13224
|
pointerEvents: "none",
|
|
13192
13225
|
width: page.width,
|
|
13193
|
-
height: page.height
|
|
13226
|
+
height: page.height,
|
|
13227
|
+
overflow: "visible"
|
|
13194
13228
|
},
|
|
13195
13229
|
"data-role": "spotlight-mask",
|
|
13196
13230
|
children: [
|
|
@@ -13201,8 +13235,8 @@ function SpotlightMask({
|
|
|
13201
13235
|
action.shape === "ellipse" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
13202
13236
|
"ellipse",
|
|
13203
13237
|
{
|
|
13204
|
-
cx
|
|
13205
|
-
cy
|
|
13238
|
+
cx,
|
|
13239
|
+
cy,
|
|
13206
13240
|
rx: w / 2,
|
|
13207
13241
|
ry: h / 2,
|
|
13208
13242
|
fill: "black",
|
|
@@ -13230,14 +13264,82 @@ function SpotlightMask({
|
|
|
13230
13264
|
y: 0,
|
|
13231
13265
|
width: page.width,
|
|
13232
13266
|
height: page.height,
|
|
13233
|
-
fill:
|
|
13267
|
+
fill: INK,
|
|
13234
13268
|
mask: `url(#${maskId})`,
|
|
13235
13269
|
initial: { fillOpacity: 0 },
|
|
13236
13270
|
animate: { fillOpacity: action.dim_opacity },
|
|
13237
13271
|
exit: { fillOpacity: 0 },
|
|
13238
|
-
transition: { duration: durationMs / 1e3, ease:
|
|
13272
|
+
transition: { duration: durationMs / 1e3, ease: EASE_OUT_EXPO }
|
|
13239
13273
|
}
|
|
13240
|
-
)
|
|
13274
|
+
),
|
|
13275
|
+
action.shape === "ellipse" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
13276
|
+
import_framer_motion2.motion.ellipse,
|
|
13277
|
+
{
|
|
13278
|
+
cx,
|
|
13279
|
+
cy,
|
|
13280
|
+
rx: w / 2,
|
|
13281
|
+
ry: h / 2,
|
|
13282
|
+
fill: "none",
|
|
13283
|
+
stroke: ACCENT,
|
|
13284
|
+
strokeWidth: 3,
|
|
13285
|
+
initial: { opacity: 0, scale: 1.08 },
|
|
13286
|
+
animate: { opacity: 0.9, scale: 1 },
|
|
13287
|
+
exit: { opacity: 0 },
|
|
13288
|
+
style: { transformOrigin: `${cx}px ${cy}px`, transformBox: "fill-box" },
|
|
13289
|
+
transition: {
|
|
13290
|
+
duration: durationMs / 1e3,
|
|
13291
|
+
delay: 0.15,
|
|
13292
|
+
ease: EASE_OUT_EXPO
|
|
13293
|
+
}
|
|
13294
|
+
}
|
|
13295
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
13296
|
+
import_framer_motion2.motion.rect,
|
|
13297
|
+
{
|
|
13298
|
+
x: x1,
|
|
13299
|
+
y: y1,
|
|
13300
|
+
width: w,
|
|
13301
|
+
height: h,
|
|
13302
|
+
rx,
|
|
13303
|
+
ry,
|
|
13304
|
+
fill: "none",
|
|
13305
|
+
stroke: ACCENT,
|
|
13306
|
+
strokeWidth: 3,
|
|
13307
|
+
initial: { opacity: 0, scale: 1.04 },
|
|
13308
|
+
animate: { opacity: 0.9, scale: 1 },
|
|
13309
|
+
exit: { opacity: 0 },
|
|
13310
|
+
style: {
|
|
13311
|
+
transformOrigin: `${cx}px ${cy}px`,
|
|
13312
|
+
transformBox: "fill-box"
|
|
13313
|
+
},
|
|
13314
|
+
transition: {
|
|
13315
|
+
duration: durationMs / 1e3,
|
|
13316
|
+
delay: 0.15,
|
|
13317
|
+
ease: EASE_OUT_EXPO
|
|
13318
|
+
}
|
|
13319
|
+
}
|
|
13320
|
+
),
|
|
13321
|
+
action.shape !== "ellipse" ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
13322
|
+
import_framer_motion2.motion.rect,
|
|
13323
|
+
{
|
|
13324
|
+
x: x1 - 2,
|
|
13325
|
+
y: y1 - 2,
|
|
13326
|
+
width: w + 4,
|
|
13327
|
+
height: h + 4,
|
|
13328
|
+
rx: rx + 2,
|
|
13329
|
+
ry: ry + 2,
|
|
13330
|
+
fill: "none",
|
|
13331
|
+
stroke: ACCENT_GLOW,
|
|
13332
|
+
strokeWidth: 8,
|
|
13333
|
+
initial: { opacity: 0 },
|
|
13334
|
+
animate: { opacity: 0.6 },
|
|
13335
|
+
exit: { opacity: 0 },
|
|
13336
|
+
transition: {
|
|
13337
|
+
duration: durationMs / 1e3,
|
|
13338
|
+
delay: 0.2,
|
|
13339
|
+
ease: EASE_OUT_EXPO
|
|
13340
|
+
}
|
|
13341
|
+
}
|
|
13342
|
+
) : null
|
|
13241
13343
|
]
|
|
13242
13344
|
}
|
|
13243
13345
|
);
|
|
@@ -13246,35 +13348,58 @@ function SpotlightMask({
|
|
|
13246
13348
|
// src/components/TutorMode/AnimatedUnderline.tsx
|
|
13247
13349
|
var import_framer_motion3 = require("framer-motion");
|
|
13248
13350
|
var import_jsx_runtime43 = require("react/jsx-runtime");
|
|
13351
|
+
function jitterAt(i) {
|
|
13352
|
+
const h = Math.sin(i * 12.9898) * 43758.5453;
|
|
13353
|
+
return (h - Math.floor(h) - 0.5) * 4;
|
|
13354
|
+
}
|
|
13249
13355
|
function pathForStyle(x1, x2, y, style) {
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13356
|
+
const x1e = x1 - 4;
|
|
13357
|
+
const x2e = x2 + 4;
|
|
13358
|
+
if (style === "straight") {
|
|
13359
|
+
return { primary: `M ${x1e} ${y} L ${x2e} ${y}` };
|
|
13360
|
+
}
|
|
13361
|
+
if (style === "double") {
|
|
13362
|
+
return {
|
|
13363
|
+
primary: `M ${x1e} ${y - 3} L ${x2e} ${y - 3}`,
|
|
13364
|
+
ghost: `M ${x1e} ${y + 3} L ${x2e} ${y + 3}`
|
|
13365
|
+
};
|
|
13366
|
+
}
|
|
13253
13367
|
if (style === "wavy") {
|
|
13254
|
-
const
|
|
13255
|
-
|
|
13368
|
+
const len = x2e - x1e;
|
|
13369
|
+
const steps = Math.max(12, Math.floor(len / 10));
|
|
13370
|
+
const amp = 3.2;
|
|
13371
|
+
let d = `M ${x1e} ${y}`;
|
|
13256
13372
|
for (let i = 1; i <= steps; i++) {
|
|
13257
|
-
const
|
|
13258
|
-
const
|
|
13259
|
-
|
|
13373
|
+
const t = i / steps;
|
|
13374
|
+
const px = x1e + len * t;
|
|
13375
|
+
const py = y + Math.sin(t * Math.PI * 4) * amp;
|
|
13376
|
+
const prevT = (i - 1) / steps;
|
|
13377
|
+
const cpx = x1e + len * (prevT + (t - prevT) / 2);
|
|
13378
|
+
const cpy = y + Math.sin((prevT + (t - prevT) / 2) * Math.PI * 4) * amp;
|
|
13379
|
+
d += ` Q ${cpx} ${cpy} ${px} ${py}`;
|
|
13260
13380
|
}
|
|
13261
|
-
return
|
|
13381
|
+
return { primary: d };
|
|
13262
13382
|
}
|
|
13263
|
-
const segs =
|
|
13264
|
-
let
|
|
13383
|
+
const segs = 8;
|
|
13384
|
+
let primary = `M ${x1e} ${y + jitterAt(0)}`;
|
|
13385
|
+
let ghost = `M ${x1e} ${y + jitterAt(100) + 1.5}`;
|
|
13265
13386
|
for (let i = 1; i <= segs; i++) {
|
|
13266
|
-
const px =
|
|
13267
|
-
|
|
13268
|
-
|
|
13387
|
+
const px = x1e + (x2e - x1e) * i / segs;
|
|
13388
|
+
primary += ` L ${px} ${y + jitterAt(i)}`;
|
|
13389
|
+
ghost += ` L ${px} ${y + jitterAt(i + 100) + 1.5}`;
|
|
13269
13390
|
}
|
|
13270
|
-
return
|
|
13391
|
+
return { primary, ghost };
|
|
13271
13392
|
}
|
|
13272
13393
|
function AnimatedUnderline({ bbox, action }) {
|
|
13273
13394
|
const [x1, , x2, y2] = bbox;
|
|
13274
13395
|
const y = y2 + 6;
|
|
13275
|
-
const
|
|
13396
|
+
const { primary, ghost } = pathForStyle(x1, x2, y, action.style);
|
|
13276
13397
|
const duration = action.draw_duration_ms / 1e3;
|
|
13277
|
-
|
|
13398
|
+
const stroke = action.color && action.color !== "#FBBF24" ? action.color : ACCENT;
|
|
13399
|
+
const blotX = x2 + 4;
|
|
13400
|
+
const blotY = y;
|
|
13401
|
+
const strokeWeight = action.style === "wavy" ? 3 : 4;
|
|
13402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
|
|
13278
13403
|
"svg",
|
|
13279
13404
|
{
|
|
13280
13405
|
style: {
|
|
@@ -13284,50 +13409,148 @@ function AnimatedUnderline({ bbox, action }) {
|
|
|
13284
13409
|
overflow: "visible"
|
|
13285
13410
|
},
|
|
13286
13411
|
"data-role": "underline",
|
|
13287
|
-
children:
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13412
|
+
children: [
|
|
13413
|
+
ghost ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
13414
|
+
import_framer_motion3.motion.path,
|
|
13415
|
+
{
|
|
13416
|
+
d: ghost,
|
|
13417
|
+
fill: "none",
|
|
13418
|
+
stroke,
|
|
13419
|
+
strokeWidth: strokeWeight - 1.5,
|
|
13420
|
+
strokeLinecap: "round",
|
|
13421
|
+
strokeOpacity: 0.35,
|
|
13422
|
+
initial: { pathLength: 0, opacity: 0 },
|
|
13423
|
+
animate: { pathLength: 1, opacity: 0.55 },
|
|
13424
|
+
exit: { opacity: 0 },
|
|
13425
|
+
transition: { duration, ease: EASE_OUT_EXPO }
|
|
13426
|
+
}
|
|
13427
|
+
) : null,
|
|
13428
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
13429
|
+
import_framer_motion3.motion.path,
|
|
13430
|
+
{
|
|
13431
|
+
d: primary,
|
|
13432
|
+
fill: "none",
|
|
13433
|
+
stroke,
|
|
13434
|
+
strokeWidth: strokeWeight,
|
|
13435
|
+
strokeLinecap: "round",
|
|
13436
|
+
strokeLinejoin: "round",
|
|
13437
|
+
initial: { pathLength: 0, opacity: 0 },
|
|
13438
|
+
animate: { pathLength: 1, opacity: 1 },
|
|
13439
|
+
exit: { opacity: 0 },
|
|
13440
|
+
transition: { duration, ease: EASE_OUT_EXPO }
|
|
13441
|
+
}
|
|
13442
|
+
),
|
|
13443
|
+
/* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
|
|
13444
|
+
import_framer_motion3.motion.circle,
|
|
13445
|
+
{
|
|
13446
|
+
cx: blotX,
|
|
13447
|
+
cy: blotY,
|
|
13448
|
+
r: strokeWeight / 2 + 0.5,
|
|
13449
|
+
fill: stroke,
|
|
13450
|
+
initial: { scale: 0, opacity: 0 },
|
|
13451
|
+
animate: { scale: 1, opacity: 0.9 },
|
|
13452
|
+
exit: { opacity: 0 },
|
|
13453
|
+
style: {
|
|
13454
|
+
transformOrigin: `${blotX}px ${blotY}px`,
|
|
13455
|
+
transformBox: "fill-box"
|
|
13456
|
+
},
|
|
13457
|
+
transition: {
|
|
13458
|
+
duration: 0.25,
|
|
13459
|
+
delay: duration - 0.1,
|
|
13460
|
+
ease: EASE_OUT_EXPO
|
|
13461
|
+
}
|
|
13462
|
+
}
|
|
13463
|
+
)
|
|
13464
|
+
]
|
|
13301
13465
|
}
|
|
13302
13466
|
);
|
|
13303
13467
|
}
|
|
13304
13468
|
|
|
13305
13469
|
// src/components/TutorMode/AnimatedHighlight.tsx
|
|
13470
|
+
var import_react56 = require("react");
|
|
13306
13471
|
var import_framer_motion4 = require("framer-motion");
|
|
13307
13472
|
var import_jsx_runtime44 = require("react/jsx-runtime");
|
|
13308
13473
|
function AnimatedHighlight({ bbox, action }) {
|
|
13309
13474
|
const [x1, y1, x2, y2] = bbox;
|
|
13310
|
-
const
|
|
13311
|
-
const
|
|
13312
|
-
|
|
13313
|
-
|
|
13475
|
+
const h = Math.max(1, y2 - y1);
|
|
13476
|
+
const bleed = Math.min(4, h * 0.12);
|
|
13477
|
+
const yTop = y1 - bleed;
|
|
13478
|
+
const yBot = y2 + bleed;
|
|
13479
|
+
const height = yBot - yTop;
|
|
13480
|
+
const duration = action.draw_duration_ms / 1e3;
|
|
13481
|
+
const filterId = (0, import_react56.useId)();
|
|
13482
|
+
const fill = action.color && action.color !== "rgba(250, 204, 21, 0.35)" && action.color !== "rgba(250,204,21,0.35)" ? action.color : MARKER_SOFT;
|
|
13483
|
+
const inner = action.color && action.color !== "rgba(250, 204, 21, 0.35)" && action.color !== "rgba(250,204,21,0.35)" ? action.color : MARKER;
|
|
13484
|
+
const taper = Math.min(6, h * 0.2);
|
|
13485
|
+
const pathD = `
|
|
13486
|
+
M ${x1 - 2} ${yTop + taper}
|
|
13487
|
+
L ${x1 + 2} ${yTop}
|
|
13488
|
+
L ${x2 - 2} ${yTop}
|
|
13489
|
+
L ${x2 + 2} ${yTop + taper}
|
|
13490
|
+
L ${x2 + 2} ${yBot - taper}
|
|
13491
|
+
L ${x2 - 2} ${yBot}
|
|
13492
|
+
L ${x1 + 2} ${yBot}
|
|
13493
|
+
L ${x1 - 2} ${yBot - taper}
|
|
13494
|
+
Z
|
|
13495
|
+
`;
|
|
13496
|
+
return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
|
|
13497
|
+
"svg",
|
|
13314
13498
|
{
|
|
13315
13499
|
style: {
|
|
13316
13500
|
position: "absolute",
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
borderRadius: 4,
|
|
13322
|
-
mixBlendMode: "multiply",
|
|
13323
|
-
transformOrigin: "0% 50%",
|
|
13324
|
-
pointerEvents: "none"
|
|
13501
|
+
inset: 0,
|
|
13502
|
+
pointerEvents: "none",
|
|
13503
|
+
overflow: "visible",
|
|
13504
|
+
mixBlendMode: "multiply"
|
|
13325
13505
|
},
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13506
|
+
"data-role": "highlight",
|
|
13507
|
+
children: [
|
|
13508
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("filter", { id: filterId, children: [
|
|
13509
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
13510
|
+
"feTurbulence",
|
|
13511
|
+
{
|
|
13512
|
+
type: "fractalNoise",
|
|
13513
|
+
baseFrequency: "1.6",
|
|
13514
|
+
numOctaves: "1",
|
|
13515
|
+
seed: 3,
|
|
13516
|
+
result: "noise"
|
|
13517
|
+
}
|
|
13518
|
+
),
|
|
13519
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("feDisplacementMap", { in: "SourceGraphic", in2: "noise", scale: 1.4 })
|
|
13520
|
+
] }) }),
|
|
13521
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
|
|
13522
|
+
import_framer_motion4.motion.g,
|
|
13523
|
+
{
|
|
13524
|
+
initial: { clipPath: `inset(0 100% 0 0)` },
|
|
13525
|
+
animate: { clipPath: `inset(0 0% 0 0)` },
|
|
13526
|
+
exit: { opacity: 0 },
|
|
13527
|
+
transition: { duration, ease: EASE_OUT_EXPO },
|
|
13528
|
+
children: [
|
|
13529
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
13530
|
+
"path",
|
|
13531
|
+
{
|
|
13532
|
+
d: pathD,
|
|
13533
|
+
fill,
|
|
13534
|
+
opacity: 0.85,
|
|
13535
|
+
filter: `url(#${filterId})`
|
|
13536
|
+
}
|
|
13537
|
+
),
|
|
13538
|
+
/* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
|
|
13539
|
+
"rect",
|
|
13540
|
+
{
|
|
13541
|
+
x: x1 - 1,
|
|
13542
|
+
y: y1 - bleed * 0.4,
|
|
13543
|
+
width: x2 - x1 + 2,
|
|
13544
|
+
height: height - bleed * 0.8,
|
|
13545
|
+
fill: inner,
|
|
13546
|
+
opacity: 0.5,
|
|
13547
|
+
filter: `url(#${filterId})`
|
|
13548
|
+
}
|
|
13549
|
+
)
|
|
13550
|
+
]
|
|
13551
|
+
}
|
|
13552
|
+
)
|
|
13553
|
+
]
|
|
13331
13554
|
}
|
|
13332
13555
|
);
|
|
13333
13556
|
}
|
|
@@ -13336,66 +13559,214 @@ function AnimatedHighlight({ bbox, action }) {
|
|
|
13336
13559
|
var import_framer_motion5 = require("framer-motion");
|
|
13337
13560
|
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
13338
13561
|
var INTENSITY = {
|
|
13339
|
-
subtle: {
|
|
13340
|
-
normal: {
|
|
13341
|
-
strong: {
|
|
13562
|
+
subtle: { bracketLen: 14, strokeWeight: 2, coreOpacity: 0.5, ringScale: 1.08 },
|
|
13563
|
+
normal: { bracketLen: 20, strokeWeight: 2.5, coreOpacity: 0.75, ringScale: 1.14 },
|
|
13564
|
+
strong: { bracketLen: 26, strokeWeight: 3, coreOpacity: 1, ringScale: 1.22 }
|
|
13342
13565
|
};
|
|
13343
13566
|
function PulseOverlay({ bbox, action }) {
|
|
13344
13567
|
const [x1, y1, x2, y2] = bbox;
|
|
13345
|
-
const
|
|
13346
|
-
const
|
|
13347
|
-
|
|
13348
|
-
|
|
13568
|
+
const w = Math.max(1, x2 - x1);
|
|
13569
|
+
const h = Math.max(1, y2 - y1);
|
|
13570
|
+
const cx = (x1 + x2) / 2;
|
|
13571
|
+
const cy = (y1 + y2) / 2;
|
|
13572
|
+
const spec = INTENSITY[action.intensity] ?? INTENSITY.normal;
|
|
13573
|
+
const L = Math.min(spec.bracketLen, Math.min(w, h) / 2.5);
|
|
13574
|
+
const PAD = 6;
|
|
13575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(
|
|
13576
|
+
"svg",
|
|
13349
13577
|
{
|
|
13350
13578
|
style: {
|
|
13351
13579
|
position: "absolute",
|
|
13352
|
-
|
|
13353
|
-
top: y1,
|
|
13354
|
-
width: x2 - x1,
|
|
13355
|
-
height: y2 - y1,
|
|
13356
|
-
border,
|
|
13357
|
-
borderRadius: 8,
|
|
13580
|
+
inset: 0,
|
|
13358
13581
|
pointerEvents: "none",
|
|
13359
|
-
|
|
13360
|
-
},
|
|
13361
|
-
animate: { scale: [1, scale, 1] },
|
|
13362
|
-
transition: {
|
|
13363
|
-
duration: 1.2,
|
|
13364
|
-
times: [0, 0.5, 1],
|
|
13365
|
-
ease: "easeInOut",
|
|
13366
|
-
repeat,
|
|
13367
|
-
repeatType: "loop"
|
|
13582
|
+
overflow: "visible"
|
|
13368
13583
|
},
|
|
13584
|
+
"data-role": "pulse",
|
|
13585
|
+
children: [
|
|
13586
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13587
|
+
import_framer_motion5.motion.ellipse,
|
|
13588
|
+
{
|
|
13589
|
+
cx,
|
|
13590
|
+
cy,
|
|
13591
|
+
rx: w / 2 + 10,
|
|
13592
|
+
ry: h / 2 + 10,
|
|
13593
|
+
fill: ACCENT_GLOW,
|
|
13594
|
+
style: {
|
|
13595
|
+
transformOrigin: `${cx}px ${cy}px`,
|
|
13596
|
+
transformBox: "fill-box",
|
|
13597
|
+
filter: "blur(16px)"
|
|
13598
|
+
},
|
|
13599
|
+
initial: { opacity: 0, scale: 0.95 },
|
|
13600
|
+
animate: {
|
|
13601
|
+
opacity: [0, spec.coreOpacity * 0.45, 0.15],
|
|
13602
|
+
scale: [0.95, spec.ringScale, 1]
|
|
13603
|
+
},
|
|
13604
|
+
exit: { opacity: 0 },
|
|
13605
|
+
transition: {
|
|
13606
|
+
duration: 1.3,
|
|
13607
|
+
times: [0, 0.5, 1],
|
|
13608
|
+
ease: EASE_OUT_EXPO
|
|
13609
|
+
}
|
|
13610
|
+
}
|
|
13611
|
+
),
|
|
13612
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13613
|
+
Bracket,
|
|
13614
|
+
{
|
|
13615
|
+
originX: x1 - PAD,
|
|
13616
|
+
originY: y1 - PAD,
|
|
13617
|
+
direction: "tl",
|
|
13618
|
+
length: L,
|
|
13619
|
+
weight: spec.strokeWeight,
|
|
13620
|
+
delay: 0
|
|
13621
|
+
}
|
|
13622
|
+
),
|
|
13623
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13624
|
+
Bracket,
|
|
13625
|
+
{
|
|
13626
|
+
originX: x2 + PAD,
|
|
13627
|
+
originY: y1 - PAD,
|
|
13628
|
+
direction: "tr",
|
|
13629
|
+
length: L,
|
|
13630
|
+
weight: spec.strokeWeight,
|
|
13631
|
+
delay: 0.04
|
|
13632
|
+
}
|
|
13633
|
+
),
|
|
13634
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13635
|
+
Bracket,
|
|
13636
|
+
{
|
|
13637
|
+
originX: x1 - PAD,
|
|
13638
|
+
originY: y2 + PAD,
|
|
13639
|
+
direction: "bl",
|
|
13640
|
+
length: L,
|
|
13641
|
+
weight: spec.strokeWeight,
|
|
13642
|
+
delay: 0.08
|
|
13643
|
+
}
|
|
13644
|
+
),
|
|
13645
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13646
|
+
Bracket,
|
|
13647
|
+
{
|
|
13648
|
+
originX: x2 + PAD,
|
|
13649
|
+
originY: y2 + PAD,
|
|
13650
|
+
direction: "br",
|
|
13651
|
+
length: L,
|
|
13652
|
+
weight: spec.strokeWeight,
|
|
13653
|
+
delay: 0.12
|
|
13654
|
+
}
|
|
13655
|
+
),
|
|
13656
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13657
|
+
import_framer_motion5.motion.rect,
|
|
13658
|
+
{
|
|
13659
|
+
x: x1 - PAD,
|
|
13660
|
+
y: y1 - PAD,
|
|
13661
|
+
width: w + PAD * 2,
|
|
13662
|
+
height: h + PAD * 2,
|
|
13663
|
+
fill: "none",
|
|
13664
|
+
stroke: ACCENT,
|
|
13665
|
+
strokeWidth: 1,
|
|
13666
|
+
rx: 4,
|
|
13667
|
+
style: {
|
|
13668
|
+
transformOrigin: `${cx}px ${cy}px`,
|
|
13669
|
+
transformBox: "fill-box"
|
|
13670
|
+
},
|
|
13671
|
+
initial: { scale: 1, opacity: 0 },
|
|
13672
|
+
animate: {
|
|
13673
|
+
scale: [1, spec.ringScale, 1],
|
|
13674
|
+
opacity: [0, spec.coreOpacity * 0.5, 0]
|
|
13675
|
+
},
|
|
13676
|
+
exit: { opacity: 0 },
|
|
13677
|
+
transition: {
|
|
13678
|
+
duration: 1.3,
|
|
13679
|
+
times: [0, 0.5, 1],
|
|
13680
|
+
ease: EASE_OUT_EXPO,
|
|
13681
|
+
delay: 0.2
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
)
|
|
13685
|
+
]
|
|
13686
|
+
}
|
|
13687
|
+
);
|
|
13688
|
+
}
|
|
13689
|
+
function Bracket({
|
|
13690
|
+
originX,
|
|
13691
|
+
originY,
|
|
13692
|
+
direction,
|
|
13693
|
+
length,
|
|
13694
|
+
weight,
|
|
13695
|
+
delay
|
|
13696
|
+
}) {
|
|
13697
|
+
const xSign = direction === "tl" || direction === "bl" ? 1 : -1;
|
|
13698
|
+
const ySign = direction === "tl" || direction === "tr" ? 1 : -1;
|
|
13699
|
+
const d = `
|
|
13700
|
+
M ${originX + xSign * length} ${originY}
|
|
13701
|
+
L ${originX} ${originY}
|
|
13702
|
+
L ${originX} ${originY + ySign * length}
|
|
13703
|
+
`;
|
|
13704
|
+
const slideX = -xSign * 8;
|
|
13705
|
+
const slideY = -ySign * 8;
|
|
13706
|
+
return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13707
|
+
import_framer_motion5.motion.path,
|
|
13708
|
+
{
|
|
13709
|
+
d,
|
|
13710
|
+
fill: "none",
|
|
13711
|
+
stroke: ACCENT,
|
|
13712
|
+
strokeWidth: weight,
|
|
13713
|
+
strokeLinecap: "round",
|
|
13714
|
+
strokeLinejoin: "round",
|
|
13715
|
+
initial: { opacity: 0, x: slideX, y: slideY },
|
|
13716
|
+
animate: { opacity: 1, x: 0, y: 0 },
|
|
13369
13717
|
exit: { opacity: 0 },
|
|
13370
|
-
|
|
13718
|
+
transition: {
|
|
13719
|
+
duration: 0.4,
|
|
13720
|
+
delay,
|
|
13721
|
+
ease: EASE_OUT_EXPO
|
|
13722
|
+
}
|
|
13371
13723
|
}
|
|
13372
13724
|
);
|
|
13373
13725
|
}
|
|
13374
13726
|
|
|
13375
13727
|
// src/components/TutorMode/CalloutArrow.tsx
|
|
13728
|
+
var import_react57 = require("react");
|
|
13376
13729
|
var import_framer_motion6 = require("framer-motion");
|
|
13377
13730
|
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
13378
13731
|
function centerOf(b) {
|
|
13379
13732
|
return { x: (b[0] + b[2]) / 2, y: (b[1] + b[3]) / 2 };
|
|
13380
13733
|
}
|
|
13381
|
-
function
|
|
13734
|
+
function edgePoints(fromBbox, toBbox) {
|
|
13382
13735
|
const a = centerOf(fromBbox);
|
|
13383
13736
|
const b = centerOf(toBbox);
|
|
13384
|
-
if (curve === "straight") return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
|
|
13385
|
-
if (curve === "zigzag") {
|
|
13386
|
-
const mx = (a.x + b.x) / 2;
|
|
13387
|
-
return `M ${a.x} ${a.y} L ${mx} ${a.y} L ${mx} ${b.y} L ${b.x} ${b.y}`;
|
|
13388
|
-
}
|
|
13389
13737
|
const dx = b.x - a.x;
|
|
13390
13738
|
const dy = b.y - a.y;
|
|
13391
|
-
const
|
|
13392
|
-
const
|
|
13393
|
-
|
|
13739
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
13740
|
+
const ux = dx / len;
|
|
13741
|
+
const uy = dy / len;
|
|
13742
|
+
const aHalfW = (fromBbox[2] - fromBbox[0]) / 2;
|
|
13743
|
+
const aHalfH = (fromBbox[3] - fromBbox[1]) / 2;
|
|
13744
|
+
const bHalfW = (toBbox[2] - toBbox[0]) / 2;
|
|
13745
|
+
const bHalfH = (toBbox[3] - toBbox[1]) / 2;
|
|
13746
|
+
const aOff = Math.min(Math.max(aHalfW, aHalfH), 60);
|
|
13747
|
+
const bOff = Math.min(Math.max(bHalfW, bHalfH), 60);
|
|
13748
|
+
return {
|
|
13749
|
+
from: { x: a.x + ux * aOff, y: a.y + uy * aOff },
|
|
13750
|
+
to: { x: b.x - ux * bOff, y: b.y - uy * bOff }
|
|
13751
|
+
};
|
|
13752
|
+
}
|
|
13753
|
+
function arrowPath(from, to, curve) {
|
|
13754
|
+
if (curve === "straight") return `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
|
|
13755
|
+
if (curve === "zigzag") {
|
|
13756
|
+
const mx = (from.x + to.x) / 2;
|
|
13757
|
+
return `M ${from.x} ${from.y} L ${mx} ${from.y} L ${mx} ${to.y} L ${to.x} ${to.y}`;
|
|
13758
|
+
}
|
|
13759
|
+
const dx = to.x - from.x;
|
|
13760
|
+
const dy = to.y - from.y;
|
|
13761
|
+
const cx = (from.x + to.x) / 2 - dy * 0.22;
|
|
13762
|
+
const cy = (from.y + to.y) / 2 + dx * 0.22;
|
|
13763
|
+
return `M ${from.x} ${from.y} Q ${cx} ${cy} ${to.x} ${to.y}`;
|
|
13394
13764
|
}
|
|
13395
13765
|
function CalloutArrow({ fromBbox, toBbox, action }) {
|
|
13396
|
-
const
|
|
13397
|
-
const
|
|
13398
|
-
const
|
|
13766
|
+
const markerId = (0, import_react57.useId)();
|
|
13767
|
+
const glowId = `${markerId}-glow`;
|
|
13768
|
+
const { from, to } = edgePoints(fromBbox, toBbox);
|
|
13769
|
+
const d = arrowPath(from, to, action.curve);
|
|
13399
13770
|
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
|
|
13400
13771
|
"svg",
|
|
13401
13772
|
{
|
|
@@ -13407,247 +13778,142 @@ function CalloutArrow({ fromBbox, toBbox, action }) {
|
|
|
13407
13778
|
},
|
|
13408
13779
|
"data-role": "callout",
|
|
13409
13780
|
children: [
|
|
13410
|
-
/* @__PURE__ */ (0, import_jsx_runtime46.
|
|
13411
|
-
|
|
13781
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("defs", { children: [
|
|
13782
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13783
|
+
"marker",
|
|
13784
|
+
{
|
|
13785
|
+
id: markerId,
|
|
13786
|
+
viewBox: "0 0 12 10",
|
|
13787
|
+
refX: 10,
|
|
13788
|
+
refY: 5,
|
|
13789
|
+
markerWidth: 10,
|
|
13790
|
+
markerHeight: 10,
|
|
13791
|
+
orient: "auto",
|
|
13792
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13793
|
+
"path",
|
|
13794
|
+
{
|
|
13795
|
+
d: "M 1 1 L 10 5 L 1 9",
|
|
13796
|
+
fill: "none",
|
|
13797
|
+
stroke: ACCENT,
|
|
13798
|
+
strokeWidth: 1.8,
|
|
13799
|
+
strokeLinecap: "round",
|
|
13800
|
+
strokeLinejoin: "round"
|
|
13801
|
+
}
|
|
13802
|
+
)
|
|
13803
|
+
}
|
|
13804
|
+
),
|
|
13805
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("filter", { id: glowId, x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("feGaussianBlur", { stdDeviation: "2.5" }) })
|
|
13806
|
+
] }),
|
|
13807
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13808
|
+
import_framer_motion6.motion.circle,
|
|
13809
|
+
{
|
|
13810
|
+
cx: from.x,
|
|
13811
|
+
cy: from.y,
|
|
13812
|
+
r: 4,
|
|
13813
|
+
fill: ACCENT,
|
|
13814
|
+
initial: { scale: 0, opacity: 0 },
|
|
13815
|
+
animate: { scale: 1, opacity: 1 },
|
|
13816
|
+
exit: { opacity: 0 },
|
|
13817
|
+
style: {
|
|
13818
|
+
transformOrigin: `${from.x}px ${from.y}px`,
|
|
13819
|
+
transformBox: "fill-box"
|
|
13820
|
+
},
|
|
13821
|
+
transition: { duration: 0.3, ease: EASE_OUT_EXPO }
|
|
13822
|
+
}
|
|
13823
|
+
),
|
|
13824
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13825
|
+
import_framer_motion6.motion.circle,
|
|
13412
13826
|
{
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13827
|
+
cx: from.x,
|
|
13828
|
+
cy: from.y,
|
|
13829
|
+
r: 7,
|
|
13830
|
+
fill: "none",
|
|
13831
|
+
stroke: ACCENT,
|
|
13832
|
+
strokeWidth: 1.2,
|
|
13833
|
+
strokeOpacity: 0.4,
|
|
13834
|
+
initial: { scale: 0, opacity: 0 },
|
|
13835
|
+
animate: { scale: 1, opacity: 0.5 },
|
|
13836
|
+
exit: { opacity: 0 },
|
|
13837
|
+
style: {
|
|
13838
|
+
transformOrigin: `${from.x}px ${from.y}px`,
|
|
13839
|
+
transformBox: "fill-box"
|
|
13840
|
+
},
|
|
13841
|
+
transition: { duration: 0.4, delay: 0.08, ease: EASE_OUT_EXPO }
|
|
13421
13842
|
}
|
|
13422
|
-
)
|
|
13843
|
+
),
|
|
13423
13844
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13424
13845
|
import_framer_motion6.motion.path,
|
|
13425
13846
|
{
|
|
13426
13847
|
d,
|
|
13427
13848
|
fill: "none",
|
|
13428
|
-
stroke:
|
|
13429
|
-
strokeWidth:
|
|
13849
|
+
stroke: ACCENT,
|
|
13850
|
+
strokeWidth: 6,
|
|
13851
|
+
strokeOpacity: 0.18,
|
|
13430
13852
|
strokeLinecap: "round",
|
|
13431
|
-
|
|
13853
|
+
filter: `url(#${glowId})`,
|
|
13432
13854
|
initial: { pathLength: 0, opacity: 0 },
|
|
13433
|
-
animate: { pathLength: 1, opacity:
|
|
13855
|
+
animate: { pathLength: 1, opacity: 0.8 },
|
|
13434
13856
|
exit: { opacity: 0 },
|
|
13435
|
-
transition: { duration: 0.
|
|
13857
|
+
transition: { duration: 0.7, delay: 0.12, ease: EASE_OUT_EXPO }
|
|
13436
13858
|
}
|
|
13437
13859
|
),
|
|
13438
|
-
|
|
13439
|
-
import_framer_motion6.motion.
|
|
13860
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13861
|
+
import_framer_motion6.motion.path,
|
|
13440
13862
|
{
|
|
13441
|
-
|
|
13442
|
-
|
|
13863
|
+
d,
|
|
13864
|
+
fill: "none",
|
|
13865
|
+
stroke: ACCENT,
|
|
13866
|
+
strokeWidth: 2.4,
|
|
13867
|
+
strokeLinecap: "round",
|
|
13868
|
+
strokeLinejoin: "round",
|
|
13869
|
+
markerEnd: `url(#${markerId})`,
|
|
13870
|
+
initial: { pathLength: 0, opacity: 0 },
|
|
13871
|
+
animate: { pathLength: 1, opacity: 1 },
|
|
13443
13872
|
exit: { opacity: 0 },
|
|
13444
|
-
transition: {
|
|
13445
|
-
children: [
|
|
13446
|
-
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13447
|
-
"rect",
|
|
13448
|
-
{
|
|
13449
|
-
x: target.x - 4,
|
|
13450
|
-
y: target.y - 28,
|
|
13451
|
-
width: label.length * 9 + 12,
|
|
13452
|
-
height: 22,
|
|
13453
|
-
rx: 4,
|
|
13454
|
-
fill: "#1F2937"
|
|
13455
|
-
}
|
|
13456
|
-
),
|
|
13457
|
-
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
13458
|
-
"text",
|
|
13459
|
-
{
|
|
13460
|
-
x: target.x + 2,
|
|
13461
|
-
y: target.y - 12,
|
|
13462
|
-
fill: "white",
|
|
13463
|
-
fontSize: 14,
|
|
13464
|
-
fontFamily: "system-ui, sans-serif",
|
|
13465
|
-
children: label
|
|
13466
|
-
}
|
|
13467
|
-
)
|
|
13468
|
-
]
|
|
13873
|
+
transition: { duration: 0.7, delay: 0.15, ease: EASE_OUT_EXPO }
|
|
13469
13874
|
}
|
|
13470
|
-
)
|
|
13875
|
+
)
|
|
13471
13876
|
]
|
|
13472
13877
|
}
|
|
13473
13878
|
);
|
|
13474
13879
|
}
|
|
13475
13880
|
|
|
13476
|
-
// src/components/TutorMode/
|
|
13881
|
+
// src/components/TutorMode/BoxOverlay.tsx
|
|
13477
13882
|
var import_framer_motion7 = require("framer-motion");
|
|
13478
13883
|
var import_jsx_runtime47 = require("react/jsx-runtime");
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
"
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
function GhostReference({
|
|
13486
|
-
page,
|
|
13487
|
-
sourceBbox,
|
|
13488
|
-
sourceBlockText,
|
|
13489
|
-
sourcePageNumber,
|
|
13490
|
-
action
|
|
13491
|
-
}) {
|
|
13492
|
-
const width = 360;
|
|
13493
|
-
const [x1, y1, x2, y2] = sourceBbox;
|
|
13494
|
-
return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
|
|
13884
|
+
function BoxOverlay({ bbox, action }) {
|
|
13885
|
+
const [x1, y1, x2, y2] = bbox;
|
|
13886
|
+
const useSystem = !action.color || action.color === "#3B82F6" || action.color === "#3b82f6";
|
|
13887
|
+
const borderColor = useSystem ? ACCENT : action.color;
|
|
13888
|
+
const washColor = useSystem ? ACCENT_SOFT : void 0;
|
|
13889
|
+
return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
|
|
13495
13890
|
import_framer_motion7.motion.div,
|
|
13496
13891
|
{
|
|
13497
|
-
initial: { opacity: 0,
|
|
13498
|
-
animate: { opacity: 1,
|
|
13499
|
-
exit: { opacity: 0
|
|
13500
|
-
transition: { duration: 0.
|
|
13892
|
+
initial: { opacity: 0, scale: 0.98 },
|
|
13893
|
+
animate: { opacity: 1, scale: 1 },
|
|
13894
|
+
exit: { opacity: 0 },
|
|
13895
|
+
transition: { duration: 0.45, ease: EASE_OUT_EXPO },
|
|
13501
13896
|
style: {
|
|
13502
13897
|
position: "absolute",
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13898
|
+
left: x1 - 3,
|
|
13899
|
+
top: y1 - 3,
|
|
13900
|
+
width: x2 - x1 + 6,
|
|
13901
|
+
height: y2 - y1 + 6,
|
|
13902
|
+
border: `${action.style === "dashed" ? "2px dashed" : "2px solid"} ${borderColor}`,
|
|
13903
|
+
borderRadius: 4,
|
|
13904
|
+
background: washColor,
|
|
13509
13905
|
pointerEvents: "none",
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13906
|
+
boxSizing: "border-box",
|
|
13907
|
+
// Subtle outer glow for the accent version, tinted to match.
|
|
13908
|
+
boxShadow: useSystem ? `0 0 0 1px rgba(176, 74, 26, 0.10), 0 8px 24px -10px rgba(176, 74, 26, 0.35)` : void 0
|
|
13513
13909
|
},
|
|
13514
|
-
"data-role": "
|
|
13515
|
-
children: [
|
|
13516
|
-
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { style: { opacity: 0.7, fontSize: 11, marginBottom: 6 }, children: [
|
|
13517
|
-
"Page ",
|
|
13518
|
-
sourcePageNumber,
|
|
13519
|
-
" \u2014 ",
|
|
13520
|
-
action.target_block
|
|
13521
|
-
] }),
|
|
13522
|
-
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(
|
|
13523
|
-
"svg",
|
|
13524
|
-
{
|
|
13525
|
-
width: width - 24,
|
|
13526
|
-
height: 160,
|
|
13527
|
-
viewBox: `0 0 ${page.width} ${page.height}`,
|
|
13528
|
-
style: { background: "#1F2937", borderRadius: 6, display: "block" },
|
|
13529
|
-
preserveAspectRatio: "xMidYMid meet",
|
|
13530
|
-
children: [
|
|
13531
|
-
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
|
|
13532
|
-
"rect",
|
|
13533
|
-
{
|
|
13534
|
-
x: 0,
|
|
13535
|
-
y: 0,
|
|
13536
|
-
width: page.width,
|
|
13537
|
-
height: page.height,
|
|
13538
|
-
fill: "#1F2937"
|
|
13539
|
-
}
|
|
13540
|
-
),
|
|
13541
|
-
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
|
|
13542
|
-
"rect",
|
|
13543
|
-
{
|
|
13544
|
-
x: x1,
|
|
13545
|
-
y: y1,
|
|
13546
|
-
width: x2 - x1,
|
|
13547
|
-
height: y2 - y1,
|
|
13548
|
-
fill: "rgba(250,204,21,0.45)",
|
|
13549
|
-
stroke: "#FBBF24",
|
|
13550
|
-
strokeWidth: 8
|
|
13551
|
-
}
|
|
13552
|
-
)
|
|
13553
|
-
]
|
|
13554
|
-
}
|
|
13555
|
-
),
|
|
13556
|
-
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
|
|
13557
|
-
"div",
|
|
13558
|
-
{
|
|
13559
|
-
style: {
|
|
13560
|
-
marginTop: 8,
|
|
13561
|
-
fontSize: 12,
|
|
13562
|
-
lineHeight: 1.4,
|
|
13563
|
-
opacity: 0.9
|
|
13564
|
-
},
|
|
13565
|
-
children: sourceBlockText ?? "(figure)"
|
|
13566
|
-
}
|
|
13567
|
-
)
|
|
13568
|
-
]
|
|
13569
|
-
}
|
|
13570
|
-
);
|
|
13571
|
-
}
|
|
13572
|
-
|
|
13573
|
-
// src/components/TutorMode/BoxOverlay.tsx
|
|
13574
|
-
var import_framer_motion8 = require("framer-motion");
|
|
13575
|
-
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
13576
|
-
function BoxOverlay({ bbox, action }) {
|
|
13577
|
-
const [x1, y1, x2, y2] = bbox;
|
|
13578
|
-
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
13579
|
-
import_framer_motion8.motion.div,
|
|
13580
|
-
{
|
|
13581
|
-
initial: { opacity: 0, scale: 0.97 },
|
|
13582
|
-
animate: { opacity: 1, scale: 1 },
|
|
13583
|
-
exit: { opacity: 0 },
|
|
13584
|
-
transition: { duration: 0.35, ease: "easeOut" },
|
|
13585
|
-
style: {
|
|
13586
|
-
position: "absolute",
|
|
13587
|
-
left: x1,
|
|
13588
|
-
top: y1,
|
|
13589
|
-
width: x2 - x1,
|
|
13590
|
-
height: y2 - y1,
|
|
13591
|
-
border: `${action.style === "dashed" ? "3px dashed" : "3px solid"} ${action.color}`,
|
|
13592
|
-
borderRadius: 6,
|
|
13593
|
-
pointerEvents: "none",
|
|
13594
|
-
boxSizing: "border-box"
|
|
13595
|
-
},
|
|
13596
|
-
"data-role": "box"
|
|
13597
|
-
}
|
|
13598
|
-
);
|
|
13599
|
-
}
|
|
13600
|
-
|
|
13601
|
-
// src/components/TutorMode/StickyLabel.tsx
|
|
13602
|
-
var import_framer_motion9 = require("framer-motion");
|
|
13603
|
-
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
13604
|
-
function position(bbox, where) {
|
|
13605
|
-
const [x1, y1, x2, y2] = bbox;
|
|
13606
|
-
const cx = (x1 + x2) / 2;
|
|
13607
|
-
const cy = (y1 + y2) / 2;
|
|
13608
|
-
const PAD = 16;
|
|
13609
|
-
switch (where) {
|
|
13610
|
-
case "top":
|
|
13611
|
-
return { left: cx, top: y1 - PAD, transform: "translate(-50%, -100%)" };
|
|
13612
|
-
case "bottom":
|
|
13613
|
-
return { left: cx, top: y2 + PAD, transform: "translate(-50%, 0)" };
|
|
13614
|
-
case "left":
|
|
13615
|
-
return { left: x1 - PAD, top: cy, transform: "translate(-100%, -50%)" };
|
|
13616
|
-
case "right":
|
|
13617
|
-
return { left: x2 + PAD, top: cy, transform: "translate(0, -50%)" };
|
|
13618
|
-
default:
|
|
13619
|
-
return { left: cx, top: y1, transform: "translate(-50%, -100%)" };
|
|
13620
|
-
}
|
|
13621
|
-
}
|
|
13622
|
-
function StickyLabel({ bbox, action }) {
|
|
13623
|
-
return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
13624
|
-
import_framer_motion9.motion.div,
|
|
13625
|
-
{
|
|
13626
|
-
initial: { opacity: 0, scale: 0.9 },
|
|
13627
|
-
animate: { opacity: 1, scale: 1 },
|
|
13628
|
-
exit: { opacity: 0 },
|
|
13629
|
-
transition: { duration: 0.35, ease: "easeOut" },
|
|
13630
|
-
style: {
|
|
13631
|
-
position: "absolute",
|
|
13632
|
-
padding: "6px 10px",
|
|
13633
|
-
background: "#FEF3C7",
|
|
13634
|
-
color: "#78350F",
|
|
13635
|
-
borderRadius: 6,
|
|
13636
|
-
boxShadow: "0 3px 10px rgba(0,0,0,0.2)",
|
|
13637
|
-
fontSize: 14,
|
|
13638
|
-
fontFamily: "system-ui, sans-serif",
|
|
13639
|
-
maxWidth: 280,
|
|
13640
|
-
pointerEvents: "none",
|
|
13641
|
-
...position(bbox, action.position)
|
|
13642
|
-
},
|
|
13643
|
-
"data-role": "label",
|
|
13644
|
-
children: action.text
|
|
13910
|
+
"data-role": "box"
|
|
13645
13911
|
}
|
|
13646
13912
|
);
|
|
13647
13913
|
}
|
|
13648
13914
|
|
|
13649
13915
|
// src/components/TutorMode/CinemaLayer.tsx
|
|
13650
|
-
var
|
|
13916
|
+
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
13651
13917
|
function blockBbox(index, block_id) {
|
|
13652
13918
|
return index.blockById.get(block_id)?.block.bbox;
|
|
13653
13919
|
}
|
|
@@ -13657,7 +13923,7 @@ function CinemaLayer({
|
|
|
13657
13923
|
overlays,
|
|
13658
13924
|
scale
|
|
13659
13925
|
}) {
|
|
13660
|
-
return /* @__PURE__ */ (0,
|
|
13926
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
13661
13927
|
"div",
|
|
13662
13928
|
{
|
|
13663
13929
|
"data-role": "cinema-layer",
|
|
@@ -13677,13 +13943,13 @@ function CinemaLayer({
|
|
|
13677
13943
|
// reachable because it sits OUTSIDE this stacking context.
|
|
13678
13944
|
zIndex: 100
|
|
13679
13945
|
},
|
|
13680
|
-
children: /* @__PURE__ */ (0,
|
|
13946
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_framer_motion8.AnimatePresence, { children: overlays.map((overlay) => {
|
|
13681
13947
|
switch (overlay.kind) {
|
|
13682
13948
|
case "spotlight": {
|
|
13683
13949
|
const a = overlay.action;
|
|
13684
13950
|
const b = blockBbox(index, a.target_block);
|
|
13685
13951
|
if (!b) return null;
|
|
13686
|
-
return /* @__PURE__ */ (0,
|
|
13952
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
13687
13953
|
SpotlightMask,
|
|
13688
13954
|
{
|
|
13689
13955
|
page: page.page_dimensions,
|
|
@@ -13697,26 +13963,26 @@ function CinemaLayer({
|
|
|
13697
13963
|
const a = overlay.action;
|
|
13698
13964
|
const b = blockBbox(index, a.target_block);
|
|
13699
13965
|
if (!b) return null;
|
|
13700
|
-
return /* @__PURE__ */ (0,
|
|
13966
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(AnimatedUnderline, { bbox: b, action: a }, overlay.id);
|
|
13701
13967
|
}
|
|
13702
13968
|
case "highlight": {
|
|
13703
13969
|
const a = overlay.action;
|
|
13704
13970
|
const b = blockBbox(index, a.target_block);
|
|
13705
13971
|
if (!b) return null;
|
|
13706
|
-
return /* @__PURE__ */ (0,
|
|
13972
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(AnimatedHighlight, { bbox: b, action: a }, overlay.id);
|
|
13707
13973
|
}
|
|
13708
13974
|
case "pulse": {
|
|
13709
13975
|
const a = overlay.action;
|
|
13710
13976
|
const b = blockBbox(index, a.target_block);
|
|
13711
13977
|
if (!b) return null;
|
|
13712
|
-
return /* @__PURE__ */ (0,
|
|
13978
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(PulseOverlay, { bbox: b, action: a }, overlay.id);
|
|
13713
13979
|
}
|
|
13714
13980
|
case "callout": {
|
|
13715
13981
|
const a = overlay.action;
|
|
13716
13982
|
const from = blockBbox(index, a.from_block);
|
|
13717
13983
|
const to = blockBbox(index, a.to_block);
|
|
13718
13984
|
if (!from || !to) return null;
|
|
13719
|
-
return /* @__PURE__ */ (0,
|
|
13985
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
13720
13986
|
CalloutArrow,
|
|
13721
13987
|
{
|
|
13722
13988
|
fromBbox: from,
|
|
@@ -13726,36 +13992,16 @@ function CinemaLayer({
|
|
|
13726
13992
|
overlay.id
|
|
13727
13993
|
);
|
|
13728
13994
|
}
|
|
13729
|
-
case "ghost_reference":
|
|
13730
|
-
|
|
13731
|
-
const hit = index.blockById.get(a.target_block);
|
|
13732
|
-
if (!hit) return null;
|
|
13733
|
-
const targetPage = index.byPage.get(a.target_page);
|
|
13734
|
-
if (!targetPage) return null;
|
|
13735
|
-
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
13736
|
-
GhostReference,
|
|
13737
|
-
{
|
|
13738
|
-
page: targetPage.page_dimensions,
|
|
13739
|
-
sourceBbox: hit.block.bbox,
|
|
13740
|
-
sourceBlockText: hit.block.text,
|
|
13741
|
-
sourcePageNumber: hit.pageNumber,
|
|
13742
|
-
action: a
|
|
13743
|
-
},
|
|
13744
|
-
overlay.id
|
|
13745
|
-
);
|
|
13746
|
-
}
|
|
13995
|
+
case "ghost_reference":
|
|
13996
|
+
return null;
|
|
13747
13997
|
case "box": {
|
|
13748
13998
|
const a = overlay.action;
|
|
13749
13999
|
const b = blockBbox(index, a.target_block);
|
|
13750
14000
|
if (!b) return null;
|
|
13751
|
-
return /* @__PURE__ */ (0,
|
|
13752
|
-
}
|
|
13753
|
-
case "label": {
|
|
13754
|
-
const a = overlay.action;
|
|
13755
|
-
const b = blockBbox(index, a.target_block);
|
|
13756
|
-
if (!b) return null;
|
|
13757
|
-
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(StickyLabel, { bbox: b, action: a }, overlay.id);
|
|
14001
|
+
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(BoxOverlay, { bbox: b, action: a }, overlay.id);
|
|
13758
14002
|
}
|
|
14003
|
+
case "label":
|
|
14004
|
+
return null;
|
|
13759
14005
|
case "clear":
|
|
13760
14006
|
case "camera":
|
|
13761
14007
|
return null;
|
|
@@ -13765,12 +14011,774 @@ function CinemaLayer({
|
|
|
13765
14011
|
);
|
|
13766
14012
|
}
|
|
13767
14013
|
|
|
13768
|
-
// src/components/TutorMode/
|
|
14014
|
+
// src/components/TutorMode/GhostReferenceOverlay.tsx
|
|
14015
|
+
var import_framer_motion10 = require("framer-motion");
|
|
14016
|
+
|
|
14017
|
+
// src/components/TutorMode/GhostReference.tsx
|
|
14018
|
+
var import_framer_motion9 = require("framer-motion");
|
|
14019
|
+
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
14020
|
+
var POSITIONS = {
|
|
14021
|
+
"top-right": {
|
|
14022
|
+
top: "clamp(12px, 4vw, 40px)",
|
|
14023
|
+
right: "clamp(12px, 4vw, 40px)"
|
|
14024
|
+
},
|
|
14025
|
+
"top-left": {
|
|
14026
|
+
top: "clamp(12px, 4vw, 40px)",
|
|
14027
|
+
left: "clamp(12px, 4vw, 40px)"
|
|
14028
|
+
},
|
|
14029
|
+
"bottom-right": {
|
|
14030
|
+
bottom: "clamp(12px, 4vw, 40px)",
|
|
14031
|
+
right: "clamp(12px, 4vw, 40px)"
|
|
14032
|
+
},
|
|
14033
|
+
"bottom-left": {
|
|
14034
|
+
bottom: "clamp(12px, 4vw, 40px)",
|
|
14035
|
+
left: "clamp(12px, 4vw, 40px)"
|
|
14036
|
+
}
|
|
14037
|
+
};
|
|
14038
|
+
var INK2 = "#2a2420";
|
|
14039
|
+
var PAPER2 = "#faf6ec";
|
|
14040
|
+
var PAPER_DEEP = "#f3ece0";
|
|
14041
|
+
var ACCENT2 = "#b04a1a";
|
|
14042
|
+
var RULE = "rgba(42, 36, 32, 0.10)";
|
|
14043
|
+
var SERIF2 = "'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', 'EB Garamond', 'Hoefler Text', Georgia, serif";
|
|
14044
|
+
function GhostReference({
|
|
14045
|
+
page,
|
|
14046
|
+
sourceBbox,
|
|
14047
|
+
sourceBlockText,
|
|
14048
|
+
action
|
|
14049
|
+
}) {
|
|
14050
|
+
const [x1, y1, x2, y2] = sourceBbox;
|
|
14051
|
+
const text = sourceBlockText ?? "(figure)";
|
|
14052
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
14053
|
+
import_framer_motion9.motion.div,
|
|
14054
|
+
{
|
|
14055
|
+
initial: { opacity: 0, y: 24, scale: 0.97 },
|
|
14056
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
14057
|
+
exit: { opacity: 0, y: 20, scale: 0.97 },
|
|
14058
|
+
transition: {
|
|
14059
|
+
duration: 0.55,
|
|
14060
|
+
ease: [0.22, 1, 0.36, 1]
|
|
14061
|
+
// custom out-expo for an unhurried settle
|
|
14062
|
+
},
|
|
14063
|
+
style: {
|
|
14064
|
+
position: "absolute",
|
|
14065
|
+
width: "min(420px, calc(100vw - clamp(24px, 8vw, 80px)))",
|
|
14066
|
+
background: PAPER2,
|
|
14067
|
+
color: INK2,
|
|
14068
|
+
border: `1px solid ${RULE}`,
|
|
14069
|
+
// Barely-there corner radius — square-ish corners feel editorial,
|
|
14070
|
+
// 12px+ radius feels SaaS-notification. 3px is the sweet spot.
|
|
14071
|
+
borderRadius: 3,
|
|
14072
|
+
overflow: "hidden",
|
|
14073
|
+
// Warm, two-layer shadow: a tight contact shadow for definition,
|
|
14074
|
+
// a wider diffuse one tinted toward ink rather than pure grey.
|
|
14075
|
+
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)",
|
|
14076
|
+
pointerEvents: "none",
|
|
14077
|
+
...POSITIONS[action.position]
|
|
14078
|
+
},
|
|
14079
|
+
"data-role": "ghost-reference",
|
|
14080
|
+
children: [
|
|
14081
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14082
|
+
import_framer_motion9.motion.span,
|
|
14083
|
+
{
|
|
14084
|
+
"aria-hidden": true,
|
|
14085
|
+
initial: { scaleY: 0 },
|
|
14086
|
+
animate: { scaleY: 1 },
|
|
14087
|
+
exit: { scaleY: 0 },
|
|
14088
|
+
transition: { duration: 0.55, delay: 0.06, ease: [0.22, 1, 0.36, 1] },
|
|
14089
|
+
style: {
|
|
14090
|
+
position: "absolute",
|
|
14091
|
+
left: 0,
|
|
14092
|
+
top: 0,
|
|
14093
|
+
bottom: 0,
|
|
14094
|
+
width: 3,
|
|
14095
|
+
background: ACCENT2,
|
|
14096
|
+
transformOrigin: "top"
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
14099
|
+
),
|
|
14100
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14101
|
+
"span",
|
|
14102
|
+
{
|
|
14103
|
+
"aria-hidden": true,
|
|
14104
|
+
style: {
|
|
14105
|
+
position: "absolute",
|
|
14106
|
+
inset: 0,
|
|
14107
|
+
pointerEvents: "none",
|
|
14108
|
+
background: `
|
|
14109
|
+
radial-gradient(120% 80% at 0% 0%, rgba(255, 244, 220, 0.5) 0%, transparent 55%),
|
|
14110
|
+
radial-gradient(100% 80% at 100% 100%, rgba(243, 229, 200, 0.35) 0%, transparent 60%)
|
|
14111
|
+
`
|
|
14112
|
+
}
|
|
14113
|
+
}
|
|
14114
|
+
),
|
|
14115
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
14116
|
+
"div",
|
|
14117
|
+
{
|
|
14118
|
+
style: {
|
|
14119
|
+
position: "relative",
|
|
14120
|
+
padding: "20px 22px 20px 26px",
|
|
14121
|
+
display: "flex",
|
|
14122
|
+
gap: 16,
|
|
14123
|
+
alignItems: "flex-start"
|
|
14124
|
+
},
|
|
14125
|
+
children: [
|
|
14126
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14127
|
+
import_framer_motion9.motion.div,
|
|
14128
|
+
{
|
|
14129
|
+
"aria-hidden": true,
|
|
14130
|
+
initial: { opacity: 0, scale: 0.92 },
|
|
14131
|
+
animate: { opacity: 1, scale: 1 },
|
|
14132
|
+
exit: { opacity: 0, scale: 0.92 },
|
|
14133
|
+
transition: { duration: 0.45, delay: 0.18, ease: [0.22, 1, 0.36, 1] },
|
|
14134
|
+
style: {
|
|
14135
|
+
flexShrink: 0,
|
|
14136
|
+
width: 46,
|
|
14137
|
+
aspectRatio: `${page.width} / ${page.height}`,
|
|
14138
|
+
background: PAPER_DEEP,
|
|
14139
|
+
borderRadius: 2,
|
|
14140
|
+
overflow: "hidden",
|
|
14141
|
+
boxShadow: "inset 0 0 0 1px rgba(42, 36, 32, 0.12), 0 1px 3px rgba(42, 36, 32, 0.10)"
|
|
14142
|
+
},
|
|
14143
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
14144
|
+
"svg",
|
|
14145
|
+
{
|
|
14146
|
+
width: "100%",
|
|
14147
|
+
height: "100%",
|
|
14148
|
+
viewBox: `0 0 ${page.width} ${page.height}`,
|
|
14149
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
14150
|
+
style: { display: "block" },
|
|
14151
|
+
children: [
|
|
14152
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14153
|
+
"rect",
|
|
14154
|
+
{
|
|
14155
|
+
x: 0,
|
|
14156
|
+
y: 0,
|
|
14157
|
+
width: page.width,
|
|
14158
|
+
height: page.height,
|
|
14159
|
+
fill: PAPER_DEEP
|
|
14160
|
+
}
|
|
14161
|
+
),
|
|
14162
|
+
TEXT_LINES.map((ln, i) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14163
|
+
"rect",
|
|
14164
|
+
{
|
|
14165
|
+
x: page.width * ln.x,
|
|
14166
|
+
y: page.height * ln.y,
|
|
14167
|
+
width: page.width * ln.w,
|
|
14168
|
+
height: page.height * 0.012,
|
|
14169
|
+
fill: "rgba(42, 36, 32, 0.18)"
|
|
14170
|
+
},
|
|
14171
|
+
i
|
|
14172
|
+
)),
|
|
14173
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14174
|
+
"rect",
|
|
14175
|
+
{
|
|
14176
|
+
x: x1,
|
|
14177
|
+
y: y1,
|
|
14178
|
+
width: x2 - x1,
|
|
14179
|
+
height: y2 - y1,
|
|
14180
|
+
fill: "rgba(176, 74, 26, 0.28)",
|
|
14181
|
+
stroke: ACCENT2,
|
|
14182
|
+
strokeWidth: 18
|
|
14183
|
+
}
|
|
14184
|
+
)
|
|
14185
|
+
]
|
|
14186
|
+
}
|
|
14187
|
+
)
|
|
14188
|
+
}
|
|
14189
|
+
),
|
|
14190
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
|
|
14191
|
+
import_framer_motion9.motion.div,
|
|
14192
|
+
{
|
|
14193
|
+
initial: { opacity: 0, y: 6 },
|
|
14194
|
+
animate: { opacity: 1, y: 0 },
|
|
14195
|
+
exit: { opacity: 0 },
|
|
14196
|
+
transition: { duration: 0.5, delay: 0.26, ease: [0.22, 1, 0.36, 1] },
|
|
14197
|
+
style: {
|
|
14198
|
+
flex: 1,
|
|
14199
|
+
minWidth: 0,
|
|
14200
|
+
fontFamily: SERIF2,
|
|
14201
|
+
fontSize: 15.5,
|
|
14202
|
+
lineHeight: 1.55,
|
|
14203
|
+
color: INK2,
|
|
14204
|
+
fontFeatureSettings: "'liga' 1, 'kern' 1, 'onum' 1",
|
|
14205
|
+
textRendering: "optimizeLegibility",
|
|
14206
|
+
letterSpacing: 0.05,
|
|
14207
|
+
// Hang an ornamental opening glyph outside the text column
|
|
14208
|
+
// so the reader's eye falls into the paragraph as if into a
|
|
14209
|
+
// well-set pull quote.
|
|
14210
|
+
position: "relative",
|
|
14211
|
+
paddingLeft: 2
|
|
14212
|
+
},
|
|
14213
|
+
children: [
|
|
14214
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14215
|
+
"span",
|
|
14216
|
+
{
|
|
14217
|
+
"aria-hidden": true,
|
|
14218
|
+
style: {
|
|
14219
|
+
position: "absolute",
|
|
14220
|
+
left: -14,
|
|
14221
|
+
top: -2,
|
|
14222
|
+
color: ACCENT2,
|
|
14223
|
+
fontSize: 22,
|
|
14224
|
+
lineHeight: 1,
|
|
14225
|
+
fontWeight: 500
|
|
14226
|
+
// ornamental flourish anchoring the paragraph
|
|
14227
|
+
},
|
|
14228
|
+
children: "\u2767"
|
|
14229
|
+
}
|
|
14230
|
+
),
|
|
14231
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14232
|
+
"span",
|
|
14233
|
+
{
|
|
14234
|
+
style: {
|
|
14235
|
+
display: "-webkit-box",
|
|
14236
|
+
WebkitLineClamp: 8,
|
|
14237
|
+
WebkitBoxOrient: "vertical",
|
|
14238
|
+
overflow: "hidden"
|
|
14239
|
+
},
|
|
14240
|
+
children: text
|
|
14241
|
+
}
|
|
14242
|
+
)
|
|
14243
|
+
]
|
|
14244
|
+
}
|
|
14245
|
+
)
|
|
14246
|
+
]
|
|
14247
|
+
}
|
|
14248
|
+
),
|
|
14249
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
|
|
14250
|
+
import_framer_motion9.motion.span,
|
|
14251
|
+
{
|
|
14252
|
+
"aria-hidden": true,
|
|
14253
|
+
initial: { scaleX: 0, opacity: 0 },
|
|
14254
|
+
animate: { scaleX: 1, opacity: 0.6 },
|
|
14255
|
+
exit: { opacity: 0 },
|
|
14256
|
+
transition: { duration: 0.5, delay: 0.42, ease: [0.22, 1, 0.36, 1] },
|
|
14257
|
+
style: {
|
|
14258
|
+
position: "absolute",
|
|
14259
|
+
right: 22,
|
|
14260
|
+
bottom: 10,
|
|
14261
|
+
height: 1,
|
|
14262
|
+
width: 28,
|
|
14263
|
+
background: ACCENT2,
|
|
14264
|
+
transformOrigin: "right"
|
|
14265
|
+
}
|
|
14266
|
+
}
|
|
14267
|
+
)
|
|
14268
|
+
]
|
|
14269
|
+
}
|
|
14270
|
+
);
|
|
14271
|
+
}
|
|
14272
|
+
var TEXT_LINES = [
|
|
14273
|
+
{ x: 0.1, y: 0.12, w: 0.54 },
|
|
14274
|
+
{ x: 0.1, y: 0.18, w: 0.68 },
|
|
14275
|
+
{ x: 0.1, y: 0.24, w: 0.48 },
|
|
14276
|
+
{ x: 0.1, y: 0.34, w: 0.72 },
|
|
14277
|
+
{ x: 0.1, y: 0.4, w: 0.62 },
|
|
14278
|
+
{ x: 0.1, y: 0.46, w: 0.66 },
|
|
14279
|
+
{ x: 0.1, y: 0.56, w: 0.56 },
|
|
14280
|
+
{ x: 0.1, y: 0.62, w: 0.7 },
|
|
14281
|
+
{ x: 0.1, y: 0.68, w: 0.44 },
|
|
14282
|
+
{ x: 0.1, y: 0.78, w: 0.64 },
|
|
14283
|
+
{ x: 0.1, y: 0.84, w: 0.58 }
|
|
14284
|
+
];
|
|
14285
|
+
|
|
14286
|
+
// src/components/TutorMode/GhostReferenceOverlay.tsx
|
|
14287
|
+
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
14288
|
+
function GhostReferenceOverlay({
|
|
14289
|
+
overlays,
|
|
14290
|
+
index
|
|
14291
|
+
}) {
|
|
14292
|
+
const ghosts = overlays.filter((o) => o.kind === "ghost_reference");
|
|
14293
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
14294
|
+
"div",
|
|
14295
|
+
{
|
|
14296
|
+
"data-role": "ghost-reference-overlay",
|
|
14297
|
+
style: {
|
|
14298
|
+
position: "absolute",
|
|
14299
|
+
inset: 0,
|
|
14300
|
+
pointerEvents: "none",
|
|
14301
|
+
// Sits above the Reset view button (z-60) so a ghost card doesn't
|
|
14302
|
+
// disappear behind it if the host also renders the Reset button.
|
|
14303
|
+
zIndex: 70
|
|
14304
|
+
},
|
|
14305
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_framer_motion10.AnimatePresence, { children: ghosts.map((overlay) => {
|
|
14306
|
+
const a = overlay.action;
|
|
14307
|
+
const hit = index.blockById.get(a.target_block);
|
|
14308
|
+
if (!hit) return null;
|
|
14309
|
+
const targetPage = index.byPage.get(a.target_page);
|
|
14310
|
+
if (!targetPage) return null;
|
|
14311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
14312
|
+
GhostReference,
|
|
14313
|
+
{
|
|
14314
|
+
page: targetPage.page_dimensions,
|
|
14315
|
+
sourceBbox: hit.block.bbox,
|
|
14316
|
+
sourceBlockText: hit.block.text,
|
|
14317
|
+
sourcePageNumber: hit.pageNumber,
|
|
14318
|
+
action: a
|
|
14319
|
+
},
|
|
14320
|
+
overlay.id
|
|
14321
|
+
);
|
|
14322
|
+
}) })
|
|
14323
|
+
}
|
|
14324
|
+
);
|
|
14325
|
+
}
|
|
14326
|
+
|
|
14327
|
+
// src/components/TutorMode/LabelOverlay.tsx
|
|
14328
|
+
var import_framer_motion12 = require("framer-motion");
|
|
14329
|
+
|
|
14330
|
+
// src/components/TutorMode/StickyLabel.tsx
|
|
13769
14331
|
var import_framer_motion11 = require("framer-motion");
|
|
13770
14332
|
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
13771
|
-
|
|
13772
|
-
|
|
14333
|
+
var INK3 = "#2a2420";
|
|
14334
|
+
var PAPER3 = "#faf6ec";
|
|
14335
|
+
var ACCENT3 = "#b04a1a";
|
|
14336
|
+
var SERIF3 = "'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', 'EB Garamond', 'Hoefler Text', Georgia, serif";
|
|
14337
|
+
var STEM = 18;
|
|
14338
|
+
function StickyLabel({ screenAnchor, action }) {
|
|
14339
|
+
const { x, y } = screenAnchor;
|
|
14340
|
+
const layout = LAYOUTS[action.position] ?? LAYOUTS.top;
|
|
14341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
|
|
13773
14342
|
import_framer_motion11.motion.div,
|
|
14343
|
+
{
|
|
14344
|
+
initial: { opacity: 0, scale: 0.88 },
|
|
14345
|
+
animate: { opacity: 1, scale: 1 },
|
|
14346
|
+
exit: { opacity: 0, scale: 0.92 },
|
|
14347
|
+
transition: {
|
|
14348
|
+
duration: 0.4,
|
|
14349
|
+
ease: [0.22, 1, 0.36, 1]
|
|
14350
|
+
},
|
|
14351
|
+
style: {
|
|
14352
|
+
position: "absolute",
|
|
14353
|
+
left: x,
|
|
14354
|
+
top: y,
|
|
14355
|
+
// Wrapper positions at the anchor; the body's transform places
|
|
14356
|
+
// it on the correct side via `layout.containerTransform`.
|
|
14357
|
+
transform: layout.containerTransform,
|
|
14358
|
+
pointerEvents: "none",
|
|
14359
|
+
// Compose transform-origin toward the anchor so scale-in feels
|
|
14360
|
+
// tethered to the block rather than free-floating.
|
|
14361
|
+
transformOrigin: layout.origin
|
|
14362
|
+
},
|
|
14363
|
+
"data-role": "label",
|
|
14364
|
+
children: [
|
|
14365
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
14366
|
+
import_framer_motion11.motion.span,
|
|
14367
|
+
{
|
|
14368
|
+
"aria-hidden": true,
|
|
14369
|
+
initial: { scaleX: 0, scaleY: 0, opacity: 0 },
|
|
14370
|
+
animate: { scaleX: 1, scaleY: 1, opacity: 1 },
|
|
14371
|
+
exit: { opacity: 0 },
|
|
14372
|
+
transition: { duration: 0.35, ease: [0.22, 1, 0.36, 1] },
|
|
14373
|
+
style: {
|
|
14374
|
+
position: "absolute",
|
|
14375
|
+
background: ACCENT3,
|
|
14376
|
+
transformOrigin: layout.stemOrigin,
|
|
14377
|
+
...layout.stem
|
|
14378
|
+
}
|
|
14379
|
+
}
|
|
14380
|
+
),
|
|
14381
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
14382
|
+
import_framer_motion11.motion.span,
|
|
14383
|
+
{
|
|
14384
|
+
"aria-hidden": true,
|
|
14385
|
+
initial: { scale: 0 },
|
|
14386
|
+
animate: { scale: 1 },
|
|
14387
|
+
exit: { scale: 0 },
|
|
14388
|
+
transition: { duration: 0.3, delay: 0.15, ease: [0.22, 1, 0.36, 1] },
|
|
14389
|
+
style: {
|
|
14390
|
+
position: "absolute",
|
|
14391
|
+
width: 6,
|
|
14392
|
+
height: 6,
|
|
14393
|
+
borderRadius: "50%",
|
|
14394
|
+
background: ACCENT3,
|
|
14395
|
+
boxShadow: `0 0 0 2px ${PAPER3}, 0 0 0 3px rgba(176, 74, 26, 0.25)`,
|
|
14396
|
+
...layout.dot
|
|
14397
|
+
}
|
|
14398
|
+
}
|
|
14399
|
+
),
|
|
14400
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
14401
|
+
import_framer_motion11.motion.div,
|
|
14402
|
+
{
|
|
14403
|
+
initial: { y: layout.bodyIn.y, x: layout.bodyIn.x, opacity: 0 },
|
|
14404
|
+
animate: { y: 0, x: 0, opacity: 1 },
|
|
14405
|
+
exit: { opacity: 0 },
|
|
14406
|
+
transition: { duration: 0.4, delay: 0.08, ease: [0.22, 1, 0.36, 1] },
|
|
14407
|
+
style: {
|
|
14408
|
+
position: "absolute",
|
|
14409
|
+
...layout.bodyAnchor,
|
|
14410
|
+
background: PAPER3,
|
|
14411
|
+
color: INK3,
|
|
14412
|
+
border: "1px solid rgba(42, 36, 32, 0.10)",
|
|
14413
|
+
borderRadius: 3,
|
|
14414
|
+
padding: "6px 12px 6px 14px",
|
|
14415
|
+
fontFamily: SERIF3,
|
|
14416
|
+
fontSize: 12.5,
|
|
14417
|
+
lineHeight: 1.25,
|
|
14418
|
+
letterSpacing: 0.6,
|
|
14419
|
+
textTransform: "uppercase",
|
|
14420
|
+
fontWeight: 500,
|
|
14421
|
+
whiteSpace: "nowrap",
|
|
14422
|
+
maxWidth: 240,
|
|
14423
|
+
overflow: "hidden",
|
|
14424
|
+
textOverflow: "ellipsis",
|
|
14425
|
+
// Warm two-layer shadow (matches GhostReference's palette).
|
|
14426
|
+
boxShadow: "0 1px 2px rgba(42, 36, 32, 0.12), 0 8px 18px -6px rgba(42, 36, 32, 0.22)",
|
|
14427
|
+
// Internal left accent rule — a 2px terracotta stripe.
|
|
14428
|
+
backgroundImage: `linear-gradient(to right, ${ACCENT3} 0, ${ACCENT3} 2px, transparent 2px)`,
|
|
14429
|
+
backgroundRepeat: "no-repeat",
|
|
14430
|
+
backgroundSize: "2px 100%",
|
|
14431
|
+
backgroundPosition: "left top"
|
|
14432
|
+
},
|
|
14433
|
+
children: action.text
|
|
14434
|
+
}
|
|
14435
|
+
)
|
|
14436
|
+
]
|
|
14437
|
+
}
|
|
14438
|
+
);
|
|
14439
|
+
}
|
|
14440
|
+
var LAYOUTS = {
|
|
14441
|
+
top: {
|
|
14442
|
+
containerTransform: "translate(-50%, -100%)",
|
|
14443
|
+
origin: "50% 100%",
|
|
14444
|
+
stem: {
|
|
14445
|
+
left: "50%",
|
|
14446
|
+
bottom: -STEM,
|
|
14447
|
+
width: 1,
|
|
14448
|
+
height: STEM - 4,
|
|
14449
|
+
transform: "translateX(-50%)"
|
|
14450
|
+
},
|
|
14451
|
+
stemOrigin: "bottom",
|
|
14452
|
+
dot: {
|
|
14453
|
+
left: "50%",
|
|
14454
|
+
bottom: -STEM + 1,
|
|
14455
|
+
transform: "translate(-50%, 100%)"
|
|
14456
|
+
},
|
|
14457
|
+
bodyAnchor: { bottom: 0, left: "50%", transform: "translateX(-50%)" },
|
|
14458
|
+
bodyIn: { x: 0, y: -4 }
|
|
14459
|
+
},
|
|
14460
|
+
bottom: {
|
|
14461
|
+
containerTransform: "translate(-50%, 0%)",
|
|
14462
|
+
origin: "50% 0%",
|
|
14463
|
+
stem: {
|
|
14464
|
+
left: "50%",
|
|
14465
|
+
top: STEM - (STEM - 4),
|
|
14466
|
+
width: 1,
|
|
14467
|
+
height: STEM - 4,
|
|
14468
|
+
transform: "translateX(-50%)"
|
|
14469
|
+
},
|
|
14470
|
+
stemOrigin: "top",
|
|
14471
|
+
dot: {
|
|
14472
|
+
left: "50%",
|
|
14473
|
+
top: -1,
|
|
14474
|
+
transform: "translate(-50%, -100%)"
|
|
14475
|
+
},
|
|
14476
|
+
bodyAnchor: { top: STEM, left: "50%", transform: "translateX(-50%)" },
|
|
14477
|
+
bodyIn: { x: 0, y: 4 }
|
|
14478
|
+
},
|
|
14479
|
+
left: {
|
|
14480
|
+
containerTransform: "translate(-100%, -50%)",
|
|
14481
|
+
origin: "100% 50%",
|
|
14482
|
+
stem: {
|
|
14483
|
+
top: "50%",
|
|
14484
|
+
right: -STEM,
|
|
14485
|
+
width: STEM - 4,
|
|
14486
|
+
height: 1,
|
|
14487
|
+
transform: "translateY(-50%)"
|
|
14488
|
+
},
|
|
14489
|
+
stemOrigin: "right",
|
|
14490
|
+
dot: {
|
|
14491
|
+
right: -STEM + 1,
|
|
14492
|
+
top: "50%",
|
|
14493
|
+
transform: "translate(100%, -50%)"
|
|
14494
|
+
},
|
|
14495
|
+
bodyAnchor: { right: 0, top: "50%", transform: "translateY(-50%)" },
|
|
14496
|
+
bodyIn: { x: -4, y: 0 }
|
|
14497
|
+
},
|
|
14498
|
+
right: {
|
|
14499
|
+
containerTransform: "translate(0%, -50%)",
|
|
14500
|
+
origin: "0% 50%",
|
|
14501
|
+
stem: {
|
|
14502
|
+
top: "50%",
|
|
14503
|
+
left: STEM - (STEM - 4),
|
|
14504
|
+
width: STEM - 4,
|
|
14505
|
+
height: 1,
|
|
14506
|
+
transform: "translateY(-50%)"
|
|
14507
|
+
},
|
|
14508
|
+
stemOrigin: "left",
|
|
14509
|
+
dot: {
|
|
14510
|
+
left: -1,
|
|
14511
|
+
top: "50%",
|
|
14512
|
+
transform: "translate(-100%, -50%)"
|
|
14513
|
+
},
|
|
14514
|
+
bodyAnchor: { left: STEM, top: "50%", transform: "translateY(-50%)" },
|
|
14515
|
+
bodyIn: { x: 4, y: 0 }
|
|
14516
|
+
}
|
|
14517
|
+
};
|
|
14518
|
+
|
|
14519
|
+
// src/components/TutorMode/LabelOverlay.tsx
|
|
14520
|
+
var import_jsx_runtime52 = require("react/jsx-runtime");
|
|
14521
|
+
function LabelOverlay({
|
|
14522
|
+
overlays,
|
|
14523
|
+
index,
|
|
14524
|
+
currentPage,
|
|
14525
|
+
camera,
|
|
14526
|
+
viewport
|
|
14527
|
+
}) {
|
|
14528
|
+
const labels = overlays.filter((o) => o.kind === "label");
|
|
14529
|
+
const page = index.byPage.get(currentPage);
|
|
14530
|
+
return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
14531
|
+
"div",
|
|
14532
|
+
{
|
|
14533
|
+
"data-role": "label-overlay",
|
|
14534
|
+
style: {
|
|
14535
|
+
position: "absolute",
|
|
14536
|
+
inset: 0,
|
|
14537
|
+
pointerEvents: "none",
|
|
14538
|
+
overflow: "hidden",
|
|
14539
|
+
// Above CameraView and the Reset button but not above
|
|
14540
|
+
// GhostReferenceOverlay (z:70) — labels are block-attached and
|
|
14541
|
+
// should not cover a cross-page reference card.
|
|
14542
|
+
zIndex: 65
|
|
14543
|
+
},
|
|
14544
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(import_framer_motion12.AnimatePresence, { children: page ? labels.map((overlay) => {
|
|
14545
|
+
const a = overlay.action;
|
|
14546
|
+
const hit = index.blockById.get(a.target_block);
|
|
14547
|
+
if (!hit) return null;
|
|
14548
|
+
const anchor = computeScreenAnchor(
|
|
14549
|
+
hit.block.bbox,
|
|
14550
|
+
a.position,
|
|
14551
|
+
page,
|
|
14552
|
+
camera,
|
|
14553
|
+
viewport
|
|
14554
|
+
);
|
|
14555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
14556
|
+
StickyLabel,
|
|
14557
|
+
{
|
|
14558
|
+
screenAnchor: anchor,
|
|
14559
|
+
action: a
|
|
14560
|
+
},
|
|
14561
|
+
overlay.id
|
|
14562
|
+
);
|
|
14563
|
+
}) : null })
|
|
14564
|
+
}
|
|
14565
|
+
);
|
|
14566
|
+
}
|
|
14567
|
+
function computeScreenAnchor(bbox, where, page, camera, viewport) {
|
|
14568
|
+
const [x1, y1, x2, y2] = bbox;
|
|
14569
|
+
const pageCX = page.page_dimensions.width / 2;
|
|
14570
|
+
const pageCY = page.page_dimensions.height / 2;
|
|
14571
|
+
let px, py;
|
|
14572
|
+
switch (where) {
|
|
14573
|
+
case "top":
|
|
14574
|
+
px = (x1 + x2) / 2;
|
|
14575
|
+
py = y1;
|
|
14576
|
+
break;
|
|
14577
|
+
case "bottom":
|
|
14578
|
+
px = (x1 + x2) / 2;
|
|
14579
|
+
py = y2;
|
|
14580
|
+
break;
|
|
14581
|
+
case "left":
|
|
14582
|
+
px = x1;
|
|
14583
|
+
py = (y1 + y2) / 2;
|
|
14584
|
+
break;
|
|
14585
|
+
case "right":
|
|
14586
|
+
px = x2;
|
|
14587
|
+
py = (y1 + y2) / 2;
|
|
14588
|
+
break;
|
|
14589
|
+
default:
|
|
14590
|
+
px = (x1 + x2) / 2;
|
|
14591
|
+
py = y1;
|
|
14592
|
+
}
|
|
14593
|
+
const screenX = viewport.width / 2 + camera.x + (px - pageCX) * camera.scale;
|
|
14594
|
+
const screenY = viewport.height / 2 + camera.y + (py - pageCY) * camera.scale;
|
|
14595
|
+
return { x: screenX, y: screenY };
|
|
14596
|
+
}
|
|
14597
|
+
|
|
14598
|
+
// src/components/TutorMode/CalloutLabelOverlay.tsx
|
|
14599
|
+
var import_framer_motion13 = require("framer-motion");
|
|
14600
|
+
var import_jsx_runtime53 = require("react/jsx-runtime");
|
|
14601
|
+
function CalloutLabelOverlay({
|
|
14602
|
+
overlays,
|
|
14603
|
+
index,
|
|
14604
|
+
currentPage,
|
|
14605
|
+
camera,
|
|
14606
|
+
viewport
|
|
14607
|
+
}) {
|
|
14608
|
+
const callouts = overlays.filter(
|
|
14609
|
+
(o) => o.kind === "callout" && o.action.label
|
|
14610
|
+
);
|
|
14611
|
+
const page = index.byPage.get(currentPage);
|
|
14612
|
+
return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
14613
|
+
"div",
|
|
14614
|
+
{
|
|
14615
|
+
"data-role": "callout-label-overlay",
|
|
14616
|
+
style: {
|
|
14617
|
+
position: "absolute",
|
|
14618
|
+
inset: 0,
|
|
14619
|
+
pointerEvents: "none",
|
|
14620
|
+
overflow: "hidden",
|
|
14621
|
+
// Above the arrow stroke (which is inside CameraView) and the
|
|
14622
|
+
// reset button, below the ghost card.
|
|
14623
|
+
zIndex: 68
|
|
14624
|
+
},
|
|
14625
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(import_framer_motion13.AnimatePresence, { children: page ? callouts.map((overlay) => {
|
|
14626
|
+
const a = overlay.action;
|
|
14627
|
+
const fromHit = index.blockById.get(a.from_block);
|
|
14628
|
+
const toHit = index.blockById.get(a.to_block);
|
|
14629
|
+
if (!fromHit || !toHit || !a.label) return null;
|
|
14630
|
+
const pos = computePillAnchor(
|
|
14631
|
+
fromHit.block.bbox,
|
|
14632
|
+
toHit.block.bbox,
|
|
14633
|
+
page,
|
|
14634
|
+
camera,
|
|
14635
|
+
viewport
|
|
14636
|
+
);
|
|
14637
|
+
return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
14638
|
+
CalloutLabelPill,
|
|
14639
|
+
{
|
|
14640
|
+
label: a.label,
|
|
14641
|
+
anchor: pos,
|
|
14642
|
+
side: pos.side
|
|
14643
|
+
},
|
|
14644
|
+
overlay.id
|
|
14645
|
+
);
|
|
14646
|
+
}) : null })
|
|
14647
|
+
}
|
|
14648
|
+
);
|
|
14649
|
+
}
|
|
14650
|
+
function computePillAnchor(fromBbox, toBbox, page, camera, viewport) {
|
|
14651
|
+
const aCX = (fromBbox[0] + fromBbox[2]) / 2;
|
|
14652
|
+
const aCY = (fromBbox[1] + fromBbox[3]) / 2;
|
|
14653
|
+
const bCX = (toBbox[0] + toBbox[2]) / 2;
|
|
14654
|
+
const bCY = (toBbox[1] + toBbox[3]) / 2;
|
|
14655
|
+
const dx = bCX - aCX;
|
|
14656
|
+
const dy = bCY - aCY;
|
|
14657
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
14658
|
+
const ux = dx / len;
|
|
14659
|
+
const uy = dy / len;
|
|
14660
|
+
const bHalfW = (toBbox[2] - toBbox[0]) / 2;
|
|
14661
|
+
const bHalfH = (toBbox[3] - toBbox[1]) / 2;
|
|
14662
|
+
const bOff = Math.min(Math.max(bHalfW, bHalfH), 60);
|
|
14663
|
+
const toX = bCX - ux * bOff;
|
|
14664
|
+
const toY = bCY - uy * bOff;
|
|
14665
|
+
const pageCX = page.page_dimensions.width / 2;
|
|
14666
|
+
const pageCY = page.page_dimensions.height / 2;
|
|
14667
|
+
const tipScreenX = viewport.width / 2 + camera.x + (toX - pageCX) * camera.scale;
|
|
14668
|
+
const tipScreenY = viewport.height / 2 + camera.y + (toY - pageCY) * camera.scale;
|
|
14669
|
+
const isVertical = Math.abs(dy) >= Math.abs(dx);
|
|
14670
|
+
const OFFSET = 32;
|
|
14671
|
+
const MAX_PILL_W = 220;
|
|
14672
|
+
const MAX_PILL_H = 30;
|
|
14673
|
+
const SAFE = 16;
|
|
14674
|
+
if (isVertical) {
|
|
14675
|
+
const canFitRight = tipScreenX + OFFSET + MAX_PILL_W < viewport.width - SAFE;
|
|
14676
|
+
const side2 = canFitRight ? "right" : "left";
|
|
14677
|
+
return {
|
|
14678
|
+
x: tipScreenX + (side2 === "right" ? OFFSET : -OFFSET),
|
|
14679
|
+
y: tipScreenY,
|
|
14680
|
+
side: side2
|
|
14681
|
+
};
|
|
14682
|
+
}
|
|
14683
|
+
const canFitBelow = tipScreenY + OFFSET + MAX_PILL_H < viewport.height - SAFE;
|
|
14684
|
+
const side = canFitBelow ? "below" : "above";
|
|
14685
|
+
return {
|
|
14686
|
+
x: tipScreenX,
|
|
14687
|
+
y: tipScreenY + (side === "below" ? OFFSET : -OFFSET),
|
|
14688
|
+
side
|
|
14689
|
+
};
|
|
14690
|
+
}
|
|
14691
|
+
function CalloutLabelPill({
|
|
14692
|
+
label,
|
|
14693
|
+
anchor,
|
|
14694
|
+
side
|
|
14695
|
+
}) {
|
|
14696
|
+
const spec = PILL_SIDE_SPECS[side];
|
|
14697
|
+
return /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
|
|
14698
|
+
import_framer_motion13.motion.div,
|
|
14699
|
+
{
|
|
14700
|
+
initial: { opacity: 0, scale: 0.92, ...spec.slideIn },
|
|
14701
|
+
animate: { opacity: 1, scale: 1, x: 0, y: 0 },
|
|
14702
|
+
exit: { opacity: 0, scale: 0.94 },
|
|
14703
|
+
transition: { duration: 0.45, delay: 0.5, ease: EASE_OUT_EXPO },
|
|
14704
|
+
style: {
|
|
14705
|
+
position: "absolute",
|
|
14706
|
+
left: anchor.x,
|
|
14707
|
+
top: anchor.y,
|
|
14708
|
+
transform: spec.transform,
|
|
14709
|
+
pointerEvents: "none",
|
|
14710
|
+
background: PAPER,
|
|
14711
|
+
color: INK,
|
|
14712
|
+
border: "1px solid rgba(42, 36, 32, 0.10)",
|
|
14713
|
+
borderRadius: 3,
|
|
14714
|
+
padding: spec.padding,
|
|
14715
|
+
fontFamily: SERIF,
|
|
14716
|
+
fontSize: 11.5,
|
|
14717
|
+
lineHeight: 1.2,
|
|
14718
|
+
letterSpacing: 0.6,
|
|
14719
|
+
textTransform: "uppercase",
|
|
14720
|
+
fontWeight: 500,
|
|
14721
|
+
whiteSpace: "nowrap",
|
|
14722
|
+
maxWidth: 220,
|
|
14723
|
+
overflow: "hidden",
|
|
14724
|
+
textOverflow: "ellipsis",
|
|
14725
|
+
boxShadow: "0 1px 2px rgba(42, 36, 32, 0.12), 0 8px 18px -6px rgba(42, 36, 32, 0.22)",
|
|
14726
|
+
// Accent rule on the "inward" edge (the one closest to the arrow).
|
|
14727
|
+
backgroundImage: spec.accentGradient,
|
|
14728
|
+
backgroundRepeat: "no-repeat",
|
|
14729
|
+
backgroundSize: spec.accentSize,
|
|
14730
|
+
backgroundPosition: spec.accentPosition
|
|
14731
|
+
},
|
|
14732
|
+
"data-role": "callout-label",
|
|
14733
|
+
children: label
|
|
14734
|
+
}
|
|
14735
|
+
);
|
|
14736
|
+
}
|
|
14737
|
+
var PILL_SIDE_SPECS = {
|
|
14738
|
+
// Pill sits to the RIGHT of a vertical arrow → left edge anchors at
|
|
14739
|
+
// offset point, accent rule on the left (pointing back toward arrow).
|
|
14740
|
+
right: {
|
|
14741
|
+
transform: "translate(0, -50%)",
|
|
14742
|
+
slideIn: { x: -6 },
|
|
14743
|
+
padding: "5px 12px 5px 14px",
|
|
14744
|
+
accentGradient: `linear-gradient(to right, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14745
|
+
accentSize: "2px 100%",
|
|
14746
|
+
accentPosition: "left top"
|
|
14747
|
+
},
|
|
14748
|
+
left: {
|
|
14749
|
+
transform: "translate(-100%, -50%)",
|
|
14750
|
+
slideIn: { x: 6 },
|
|
14751
|
+
padding: "5px 14px 5px 12px",
|
|
14752
|
+
accentGradient: `linear-gradient(to left, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14753
|
+
accentSize: "2px 100%",
|
|
14754
|
+
accentPosition: "right top"
|
|
14755
|
+
},
|
|
14756
|
+
// Pill sits BELOW a horizontal arrow → top edge anchors at offset
|
|
14757
|
+
// point, accent rule on the top (pointing back up toward arrow).
|
|
14758
|
+
below: {
|
|
14759
|
+
transform: "translate(-50%, 0)",
|
|
14760
|
+
slideIn: { y: -6 },
|
|
14761
|
+
padding: "7px 12px 5px 12px",
|
|
14762
|
+
accentGradient: `linear-gradient(to bottom, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14763
|
+
accentSize: "100% 2px",
|
|
14764
|
+
accentPosition: "left top"
|
|
14765
|
+
},
|
|
14766
|
+
above: {
|
|
14767
|
+
transform: "translate(-50%, -100%)",
|
|
14768
|
+
slideIn: { y: 6 },
|
|
14769
|
+
padding: "5px 12px 7px 12px",
|
|
14770
|
+
accentGradient: `linear-gradient(to top, ${ACCENT} 0, ${ACCENT} 2px, transparent 2px)`,
|
|
14771
|
+
accentSize: "100% 2px",
|
|
14772
|
+
accentPosition: "left bottom"
|
|
14773
|
+
}
|
|
14774
|
+
};
|
|
14775
|
+
|
|
14776
|
+
// src/components/TutorMode/SubtitleBar.tsx
|
|
14777
|
+
var import_framer_motion14 = require("framer-motion");
|
|
14778
|
+
var import_jsx_runtime54 = require("react/jsx-runtime");
|
|
14779
|
+
function SubtitleBar({ text }) {
|
|
14780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_framer_motion14.AnimatePresence, { children: text ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
14781
|
+
import_framer_motion14.motion.div,
|
|
13774
14782
|
{
|
|
13775
14783
|
initial: { opacity: 0, y: 20 },
|
|
13776
14784
|
animate: { opacity: 1, y: 0 },
|
|
@@ -13996,12 +15004,17 @@ var StoryboardEngine = class {
|
|
|
13996
15004
|
const blockCY = (y1 + y2) / 2;
|
|
13997
15005
|
const pageCX = pageDims.page_dimensions.width / 2;
|
|
13998
15006
|
const pageCY = pageDims.page_dimensions.height / 2;
|
|
13999
|
-
const
|
|
14000
|
-
const
|
|
15007
|
+
const rawX = (pageCX - blockCX) * finalScale;
|
|
15008
|
+
const rawY = (pageCY - blockCY) * finalScale;
|
|
15009
|
+
const clamped = clampCamera(
|
|
15010
|
+
{ scale: finalScale, x: rawX, y: rawY },
|
|
15011
|
+
pageDims.page_dimensions,
|
|
15012
|
+
viewport
|
|
15013
|
+
);
|
|
14001
15014
|
const camera = {
|
|
14002
|
-
scale:
|
|
14003
|
-
x,
|
|
14004
|
-
y,
|
|
15015
|
+
scale: clamped.scale,
|
|
15016
|
+
x: clamped.x,
|
|
15017
|
+
y: clamped.y,
|
|
14005
15018
|
easing: action.easing
|
|
14006
15019
|
};
|
|
14007
15020
|
narrationStore.getState().setCamera(camera);
|
|
@@ -14986,7 +15999,7 @@ function storyboardFromMatch(match, page) {
|
|
|
14986
15999
|
}
|
|
14987
16000
|
|
|
14988
16001
|
// src/components/TutorMode/TutorModeContainer.tsx
|
|
14989
|
-
var
|
|
16002
|
+
var import_jsx_runtime55 = require("react/jsx-runtime");
|
|
14990
16003
|
function buildBBoxIndex(bboxData) {
|
|
14991
16004
|
const byPage = /* @__PURE__ */ new Map();
|
|
14992
16005
|
const blockById = /* @__PURE__ */ new Map();
|
|
@@ -15024,16 +16037,34 @@ function TutorModeContainer({
|
|
|
15024
16037
|
minOverlayDurationMs,
|
|
15025
16038
|
backgroundColor = "#ffffff",
|
|
15026
16039
|
loadingComponent,
|
|
16040
|
+
onPageChange,
|
|
15027
16041
|
className
|
|
15028
16042
|
}) {
|
|
15029
|
-
const containerRef = (0,
|
|
15030
|
-
const index = (0,
|
|
15031
|
-
const {
|
|
15032
|
-
|
|
15033
|
-
|
|
16043
|
+
const containerRef = (0, import_react58.useRef)(null);
|
|
16044
|
+
const index = (0, import_react58.useMemo)(() => buildBBoxIndex(bboxData), [bboxData]);
|
|
16045
|
+
const {
|
|
16046
|
+
document: document2,
|
|
16047
|
+
currentPage: viewerCurrentPage,
|
|
16048
|
+
numPages,
|
|
16049
|
+
goToPage: viewerGoToPage
|
|
16050
|
+
} = usePDFViewer();
|
|
16051
|
+
const [pageProxy, setPageProxy] = (0, import_react58.useState)(null);
|
|
16052
|
+
const [viewport, setViewport] = (0, import_react58.useState)({ width: 800, height: 1e3 });
|
|
15034
16053
|
const camera = (0, import_zustand2.useStore)(narrationStore, (s) => s.camera);
|
|
15035
16054
|
const activeOverlays = (0, import_zustand2.useStore)(narrationStore, (s) => s.activeOverlays);
|
|
15036
|
-
(0,
|
|
16055
|
+
(0, import_react58.useEffect)(() => {
|
|
16056
|
+
if (numPages <= 0) return;
|
|
16057
|
+
if (pageNumber < 1 || pageNumber > numPages) return;
|
|
16058
|
+
if (viewerCurrentPage === pageNumber) return;
|
|
16059
|
+
viewerGoToPage(pageNumber);
|
|
16060
|
+
}, [pageNumber, numPages, viewerCurrentPage, viewerGoToPage]);
|
|
16061
|
+
(0, import_react58.useEffect)(() => {
|
|
16062
|
+
if (!onPageChange) return;
|
|
16063
|
+
if (viewerCurrentPage === pageNumber) return;
|
|
16064
|
+
if (viewerCurrentPage < 1) return;
|
|
16065
|
+
onPageChange(viewerCurrentPage);
|
|
16066
|
+
}, [viewerCurrentPage, pageNumber, onPageChange]);
|
|
16067
|
+
(0, import_react58.useEffect)(() => {
|
|
15037
16068
|
if (!containerRef.current) return;
|
|
15038
16069
|
const el = containerRef.current;
|
|
15039
16070
|
const update = () => setViewport({ width: el.clientWidth, height: el.clientHeight });
|
|
@@ -15042,7 +16073,7 @@ function TutorModeContainer({
|
|
|
15042
16073
|
ro.observe(el);
|
|
15043
16074
|
return () => ro.disconnect();
|
|
15044
16075
|
}, []);
|
|
15045
|
-
(0,
|
|
16076
|
+
(0, import_react58.useEffect)(() => {
|
|
15046
16077
|
if (!document2) {
|
|
15047
16078
|
setPageProxy(null);
|
|
15048
16079
|
return;
|
|
@@ -15057,10 +16088,10 @@ function TutorModeContainer({
|
|
|
15057
16088
|
cancelled = true;
|
|
15058
16089
|
};
|
|
15059
16090
|
}, [document2, pageNumber]);
|
|
15060
|
-
(0,
|
|
16091
|
+
(0, import_react58.useEffect)(() => {
|
|
15061
16092
|
narrationStore.getState().setCurrentPage(pageNumber);
|
|
15062
16093
|
}, [pageNumber, narrationStore]);
|
|
15063
|
-
(0,
|
|
16094
|
+
(0, import_react58.useEffect)(() => {
|
|
15064
16095
|
const page2 = index.byPage.get(pageNumber);
|
|
15065
16096
|
if (!page2) return;
|
|
15066
16097
|
if (viewport.width === 0 || viewport.height === 0) return;
|
|
@@ -15071,8 +16102,8 @@ function TutorModeContainer({
|
|
|
15071
16102
|
) * 0.95;
|
|
15072
16103
|
narrationStore.getState().setCamera({ scale: fit, x: 0, y: 0 });
|
|
15073
16104
|
}, [pageNumber, viewport, index, narrationStore]);
|
|
15074
|
-
const engineRef = (0,
|
|
15075
|
-
(0,
|
|
16105
|
+
const engineRef = (0, import_react58.useRef)(null);
|
|
16106
|
+
(0, import_react58.useEffect)(() => {
|
|
15076
16107
|
engineRef.current = new StoryboardEngine({
|
|
15077
16108
|
narrationStore,
|
|
15078
16109
|
bboxIndex: index,
|
|
@@ -15081,10 +16112,10 @@ function TutorModeContainer({
|
|
|
15081
16112
|
});
|
|
15082
16113
|
return () => engineRef.current?.cancelPending();
|
|
15083
16114
|
}, [narrationStore, index, viewport, minOverlayDurationMs]);
|
|
15084
|
-
const abortRef = (0,
|
|
15085
|
-
const debounceRef = (0,
|
|
15086
|
-
const lastChunkRef = (0,
|
|
15087
|
-
(0,
|
|
16115
|
+
const abortRef = (0, import_react58.useRef)(null);
|
|
16116
|
+
const debounceRef = (0, import_react58.useRef)(null);
|
|
16117
|
+
const lastChunkRef = (0, import_react58.useRef)(null);
|
|
16118
|
+
(0, import_react58.useEffect)(() => {
|
|
15088
16119
|
if (!llm) return;
|
|
15089
16120
|
if (!currentChunk || currentChunk === lastChunkRef.current) return;
|
|
15090
16121
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
@@ -15171,7 +16202,7 @@ function TutorModeContainer({
|
|
|
15171
16202
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
15172
16203
|
};
|
|
15173
16204
|
}, [currentChunk, llm, index, pageNumber, narrationStore, embeddingProvider, llmTimeoutMs]);
|
|
15174
|
-
(0,
|
|
16205
|
+
(0, import_react58.useEffect)(() => {
|
|
15175
16206
|
if (!currentChunk) return;
|
|
15176
16207
|
const t = setTimeout(() => {
|
|
15177
16208
|
if (!engineRef.current) return;
|
|
@@ -15189,7 +16220,7 @@ function TutorModeContainer({
|
|
|
15189
16220
|
const baseW = page ? page.page_dimensions.width * (scale || 1) : 0;
|
|
15190
16221
|
const baseH = page ? page.page_dimensions.height * (scale || 1) : 0;
|
|
15191
16222
|
const isReady = !!page && !!pageProxy;
|
|
15192
|
-
return /* @__PURE__ */ (0,
|
|
16223
|
+
return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
|
|
15193
16224
|
"div",
|
|
15194
16225
|
{
|
|
15195
16226
|
ref: containerRef,
|
|
@@ -15204,7 +16235,7 @@ function TutorModeContainer({
|
|
|
15204
16235
|
"data-role": "tutor-mode-container",
|
|
15205
16236
|
"data-page-loaded": isReady ? "true" : "false",
|
|
15206
16237
|
children: [
|
|
15207
|
-
showExitButton && isReady ? /* @__PURE__ */ (0,
|
|
16238
|
+
showExitButton && isReady ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
15208
16239
|
"button",
|
|
15209
16240
|
{
|
|
15210
16241
|
onClick: () => {
|
|
@@ -15235,43 +16266,66 @@ function TutorModeContainer({
|
|
|
15235
16266
|
children: "Reset view"
|
|
15236
16267
|
}
|
|
15237
16268
|
) : null,
|
|
15238
|
-
isReady ? /* @__PURE__ */ (0,
|
|
15239
|
-
|
|
15240
|
-
|
|
15241
|
-
|
|
15242
|
-
|
|
15243
|
-
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
16269
|
+
isReady ? /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(import_jsx_runtime55.Fragment, { children: [
|
|
16270
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(CameraView, { camera, children: /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
|
|
16271
|
+
"div",
|
|
16272
|
+
{
|
|
16273
|
+
style: {
|
|
16274
|
+
position: "absolute",
|
|
16275
|
+
top: "50%",
|
|
16276
|
+
left: "50%",
|
|
16277
|
+
width: baseW,
|
|
16278
|
+
height: baseH,
|
|
16279
|
+
transform: "translate(-50%, -50%)"
|
|
16280
|
+
},
|
|
16281
|
+
children: [
|
|
16282
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
16283
|
+
PDFPage,
|
|
16284
|
+
{
|
|
16285
|
+
pageNumber,
|
|
16286
|
+
page: pageProxy,
|
|
16287
|
+
scale: rasterScale,
|
|
16288
|
+
rotation,
|
|
16289
|
+
showTextLayer: false,
|
|
16290
|
+
showHighlightLayer: false,
|
|
16291
|
+
showAnnotationLayer: false
|
|
16292
|
+
}
|
|
16293
|
+
),
|
|
16294
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
16295
|
+
CinemaLayer,
|
|
16296
|
+
{
|
|
16297
|
+
page,
|
|
16298
|
+
index,
|
|
16299
|
+
overlays: activeOverlays,
|
|
16300
|
+
scale: scale || 1
|
|
16301
|
+
}
|
|
16302
|
+
)
|
|
16303
|
+
]
|
|
16304
|
+
}
|
|
16305
|
+
) }),
|
|
16306
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
16307
|
+
LabelOverlay,
|
|
16308
|
+
{
|
|
16309
|
+
overlays: activeOverlays,
|
|
16310
|
+
index,
|
|
16311
|
+
currentPage: pageNumber,
|
|
16312
|
+
camera,
|
|
16313
|
+
viewport
|
|
16314
|
+
}
|
|
16315
|
+
),
|
|
16316
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
16317
|
+
CalloutLabelOverlay,
|
|
16318
|
+
{
|
|
16319
|
+
overlays: activeOverlays,
|
|
16320
|
+
index,
|
|
16321
|
+
currentPage: pageNumber,
|
|
16322
|
+
camera,
|
|
16323
|
+
viewport
|
|
16324
|
+
}
|
|
16325
|
+
),
|
|
16326
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(GhostReferenceOverlay, { overlays: activeOverlays, index })
|
|
16327
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TutorLoadingState, { custom: loadingComponent }),
|
|
16328
|
+
showSubtitles ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(SubtitleBar, { text: currentChunk ?? null }) : null
|
|
15275
16329
|
]
|
|
15276
16330
|
}
|
|
15277
16331
|
);
|
|
@@ -15280,7 +16334,7 @@ function TutorLoadingState({
|
|
|
15280
16334
|
custom
|
|
15281
16335
|
}) {
|
|
15282
16336
|
if (custom) {
|
|
15283
|
-
return /* @__PURE__ */ (0,
|
|
16337
|
+
return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
15284
16338
|
"div",
|
|
15285
16339
|
{
|
|
15286
16340
|
style: {
|
|
@@ -15295,7 +16349,7 @@ function TutorLoadingState({
|
|
|
15295
16349
|
}
|
|
15296
16350
|
);
|
|
15297
16351
|
}
|
|
15298
|
-
return /* @__PURE__ */ (0,
|
|
16352
|
+
return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
|
|
15299
16353
|
"div",
|
|
15300
16354
|
{
|
|
15301
16355
|
style: {
|
|
@@ -15312,7 +16366,7 @@ function TutorLoadingState({
|
|
|
15312
16366
|
},
|
|
15313
16367
|
"data-role": "tutor-loading",
|
|
15314
16368
|
children: [
|
|
15315
|
-
/* @__PURE__ */ (0,
|
|
16369
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
15316
16370
|
"div",
|
|
15317
16371
|
{
|
|
15318
16372
|
"aria-hidden": true,
|
|
@@ -15326,8 +16380,8 @@ function TutorLoadingState({
|
|
|
15326
16380
|
}
|
|
15327
16381
|
}
|
|
15328
16382
|
),
|
|
15329
|
-
/* @__PURE__ */ (0,
|
|
15330
|
-
/* @__PURE__ */ (0,
|
|
16383
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { children: "Loading document\u2026" }),
|
|
16384
|
+
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)("style", { children: `
|
|
15331
16385
|
@keyframes pdf-tutor-spin {
|
|
15332
16386
|
from { transform: rotate(0deg); }
|
|
15333
16387
|
to { transform: rotate(360deg); }
|