promptslide 0.3.16 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -115,7 +115,7 @@ function Animated({
115
115
  return /* @__PURE__ */ jsx2(
116
116
  motion.div,
117
117
  {
118
- initial: "hidden",
118
+ initial: isVisible ? false : "hidden",
119
119
  animate: isVisible ? "visible" : "hidden",
120
120
  variants: animationVariants[animation],
121
121
  transition: {
@@ -149,7 +149,7 @@ function AnimatedGroup({
149
149
  return /* @__PURE__ */ jsx2(
150
150
  motion.div,
151
151
  {
152
- initial: "hidden",
152
+ initial: isVisible ? false : "hidden",
153
153
  animate: isVisible ? "visible" : "hidden",
154
154
  variants: containerVariants,
155
155
  className,
@@ -160,9 +160,11 @@ function AnimatedGroup({
160
160
 
161
161
  // src/core/transitions.ts
162
162
  var fadeVariants = {
163
- enter: { opacity: 0 },
164
- center: { opacity: 1 },
165
- exit: { opacity: 0 }
163
+ // Render the next slide fully beneath the current one, then fade the
164
+ // outgoing slide away. This keeps the frame opaque throughout the change.
165
+ enter: { opacity: 1, zIndex: 0 },
166
+ center: { opacity: 1, zIndex: 0 },
167
+ exit: { opacity: 0, zIndex: 1 }
166
168
  };
167
169
  var slideLeftVariants = {
168
170
  enter: { x: SLIDE_DISTANCE, opacity: 0 },
@@ -352,20 +354,102 @@ function MorphText({
352
354
  }
353
355
 
354
356
  // src/core/use-slide-navigation.ts
355
- import { useCallback, useEffect, useState } from "react";
357
+ import { useCallback, useEffect, useRef, useState } from "react";
358
+ var POSITION_KEY = "promptslide:position";
359
+ function slideKey(slides, index) {
360
+ return slides[index]?.id || String(index + 1);
361
+ }
362
+ function slideFromHash(slides) {
363
+ if (typeof window === "undefined" || !window.location.hash) return null;
364
+ let hash;
365
+ try {
366
+ hash = decodeURIComponent(window.location.hash.slice(1));
367
+ } catch {
368
+ return null;
369
+ }
370
+ const byId = slides.findIndex((slide) => slide.id === hash);
371
+ if (byId >= 0) return byId;
372
+ if (/^[1-9]\d*$/.test(hash)) {
373
+ const index = Number(hash) - 1;
374
+ if (index < slides.length) return index;
375
+ }
376
+ return null;
377
+ }
378
+ function savedStep(slides, index) {
379
+ if (typeof window === "undefined") return 0;
380
+ try {
381
+ const saved = JSON.parse(window.sessionStorage.getItem(POSITION_KEY) || "null");
382
+ if (saved?.slide === slideKey(slides, index) && Number.isInteger(saved.step)) {
383
+ return Math.max(0, Math.min(saved.step, slides[index]?.steps ?? 0));
384
+ }
385
+ } catch {
386
+ }
387
+ return 0;
388
+ }
356
389
  function useSlideNavigation({
357
390
  slides,
358
391
  initialSlide = 0,
359
392
  onSlideChange
360
393
  }) {
361
- const [currentSlide, setCurrentSlide] = useState(initialSlide);
362
- const [animationStep, setAnimationStep] = useState(0);
394
+ const [currentSlide, setCurrentSlide] = useState(() => slideFromHash(slides) ?? initialSlide);
395
+ const [animationStep, setAnimationStep] = useState(
396
+ () => savedStep(slides, slideFromHash(slides) ?? initialSlide)
397
+ );
398
+ const slidesRef = useRef(slides);
399
+ const activeKeyRef = useRef(slideKey(slides, slideFromHash(slides) ?? initialSlide));
363
400
  const [navState, setNavState] = useState({
364
401
  status: "idle",
365
402
  direction: 0
366
403
  });
367
404
  const [queuedAction, setQueuedAction] = useState(null);
368
405
  const totalSteps = slides[currentSlide]?.steps ?? 0;
406
+ useEffect(() => {
407
+ if (typeof window === "undefined" || slides.length === 0) return;
408
+ if (slidesRef.current !== slides) {
409
+ slidesRef.current = slides;
410
+ const previousKey = activeKeyRef.current;
411
+ const movedIndex = slides.findIndex((slide) => slide.id && slide.id === previousKey);
412
+ if (movedIndex >= 0 && movedIndex !== currentSlide) {
413
+ setCurrentSlide(movedIndex);
414
+ return;
415
+ }
416
+ if (movedIndex < 0 && !/^[1-9]\d*$/.test(previousKey) && currentSlide !== 0) {
417
+ setAnimationStep(0);
418
+ setCurrentSlide(0);
419
+ return;
420
+ }
421
+ }
422
+ if (!slides[currentSlide]) {
423
+ setCurrentSlide(0);
424
+ setAnimationStep(0);
425
+ return;
426
+ }
427
+ const key = slideKey(slides, currentSlide);
428
+ activeKeyRef.current = key;
429
+ const hash = `#${encodeURIComponent(key)}`;
430
+ if (window.location.hash !== hash) {
431
+ window.history.replaceState(window.history.state, "", hash);
432
+ }
433
+ try {
434
+ window.sessionStorage.setItem(
435
+ POSITION_KEY,
436
+ JSON.stringify({ slide: key, step: animationStep })
437
+ );
438
+ } catch {
439
+ }
440
+ }, [slides, currentSlide, animationStep]);
441
+ useEffect(() => {
442
+ const handleHashChange = () => {
443
+ const index = slideFromHash(slides) ?? 0;
444
+ if (index === currentSlide) return;
445
+ setNavState({ status: "transitioning", direction: index > currentSlide ? 1 : -1 });
446
+ setAnimationStep(0);
447
+ setCurrentSlide(index);
448
+ onSlideChange?.(index);
449
+ };
450
+ window.addEventListener("hashchange", handleHashChange);
451
+ return () => window.removeEventListener("hashchange", handleHashChange);
452
+ }, [slides, currentSlide, onSlideChange]);
369
453
  const onTransitionComplete = useCallback(() => {
370
454
  setNavState((prev) => {
371
455
  if (prev.status === "transitioning") {
@@ -532,6 +616,7 @@ function SlideRenderer({
532
616
  return /* @__PURE__ */ jsx6(AnimatePresence, { initial: false, children: /* @__PURE__ */ jsx6(
533
617
  motion3.div,
534
618
  {
619
+ "data-slide-render-index": currentSlide,
535
620
  variants: slideVariants,
536
621
  initial: "enter",
537
622
  animate: "center",
@@ -549,20 +634,7 @@ function SlideRenderer({
549
634
  currentStep: animationStep,
550
635
  totalSteps,
551
636
  showAllAnimations,
552
- children: /* @__PURE__ */ jsx6(
553
- SlideErrorBoundary,
554
- {
555
- slideIndex: currentSlide,
556
- slideTitle: slides[currentSlide]?.title,
557
- children: /* @__PURE__ */ jsx6(
558
- CurrentSlideComponent,
559
- {
560
- slideNumber: currentSlide + 1,
561
- totalSlides: slides.length
562
- }
563
- )
564
- }
565
- )
637
+ children: /* @__PURE__ */ jsx6(SlideErrorBoundary, { slideIndex: currentSlide, slideTitle: slides[currentSlide]?.title, children: /* @__PURE__ */ jsx6(CurrentSlideComponent, { slideNumber: currentSlide + 1, totalSlides: slides.length }) })
566
638
  }
567
639
  )
568
640
  },
@@ -668,19 +740,30 @@ function SlideEmbed({ slides, transition, directionalTransition }) {
668
740
 
669
741
  // src/core/slide-deck.tsx
670
742
  import { LayoutGroup } from "framer-motion";
671
- import { ChevronLeft, ChevronRight, Download, Grid3X3, List, Maximize, MessageCircle as MessageCircle2, Monitor } from "lucide-react";
672
- import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef4, useState as useState6 } from "react";
743
+ import {
744
+ ChevronLeft,
745
+ ChevronRight,
746
+ Download,
747
+ Grid3X3,
748
+ List,
749
+ MessageCircle,
750
+ Monitor,
751
+ PanelLeftClose,
752
+ PanelLeftOpen,
753
+ Play
754
+ } from "lucide-react";
755
+ import { useCallback as useCallback5, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo4, useRef as useRef6, useState as useState7 } from "react";
673
756
 
674
757
  // src/core/annotations/annotation-overlay.tsx
675
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
758
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
676
759
 
677
760
  // src/core/annotations/annotation-form.tsx
678
- import { useEffect as useEffect3, useRef, useState as useState3 } from "react";
761
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
679
762
  import { ArrowUp, X } from "lucide-react";
680
763
  import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
681
764
  function AnnotationForm({ xPercent, yPercent, onSubmit, onCancel }) {
682
765
  const [text, setText] = useState3("");
683
- const textareaRef = useRef(null);
766
+ const textareaRef = useRef2(null);
684
767
  useEffect3(() => {
685
768
  textareaRef.current?.focus();
686
769
  }, []);
@@ -829,15 +912,30 @@ function findByTextContent(root, text) {
829
912
 
830
913
  // src/core/annotations/annotation-overlay.tsx
831
914
  import { Fragment, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
832
- function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId, onSelectId, onShowPanel, slideAnnotations, addAnnotation }) {
915
+ function AnnotationOverlay({
916
+ slides,
917
+ currentSlide,
918
+ slideContainerRef,
919
+ selectedId,
920
+ hoveredId,
921
+ onSelectId,
922
+ onShowPanel,
923
+ slideAnnotations,
924
+ addAnnotation
925
+ }) {
833
926
  const [pending, setPending] = useState4(null);
834
927
  const [hoveredElement, setHoveredElement] = useState4(null);
835
- const overlayRef = useRef2(null);
928
+ const overlayRef = useRef3(null);
836
929
  const slideTitle = slides[currentSlide]?.title || `Slide ${currentSlide + 1}`;
837
930
  const resolvedAnnotations = slideAnnotations.map((a, i) => {
838
931
  const container = slideContainerRef.current;
839
932
  if (!container) {
840
- return { annotation: a, xPercent: a.target.position.xPercent, yPercent: a.target.position.yPercent, number: i + 1 };
933
+ return {
934
+ annotation: a,
935
+ xPercent: a.target.position.xPercent,
936
+ yPercent: a.target.position.yPercent,
937
+ number: i + 1
938
+ };
841
939
  }
842
940
  const { element } = resolveTarget(a.target, container);
843
941
  if (element) {
@@ -847,7 +945,12 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
847
945
  const yPercent = (rect.top + rect.height / 2 - containerRect.top) / containerRect.height * 100;
848
946
  return { annotation: a, xPercent, yPercent, number: i + 1 };
849
947
  }
850
- return { annotation: a, xPercent: a.target.position.xPercent, yPercent: a.target.position.yPercent, number: i + 1 };
948
+ return {
949
+ annotation: a,
950
+ xPercent: a.target.position.xPercent,
951
+ yPercent: a.target.position.yPercent,
952
+ number: i + 1
953
+ };
851
954
  });
852
955
  const handleOverlayClick = useCallback3(
853
956
  (e) => {
@@ -881,11 +984,11 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
881
984
  const handleSubmit = useCallback3(
882
985
  (text) => {
883
986
  if (!pending) return;
884
- addAnnotation(currentSlide, slideTitle, pending.target, text);
987
+ addAnnotation(currentSlide, slideTitle, pending.target, text, slides[currentSlide]?.id);
885
988
  setPending(null);
886
989
  onShowPanel();
887
990
  },
888
- [pending, currentSlide, slideTitle, addAnnotation, onShowPanel]
991
+ [pending, currentSlide, slideTitle, slides, addAnnotation, onShowPanel]
889
992
  );
890
993
  const handleMouseMove = useCallback3(
891
994
  (e) => {
@@ -902,12 +1005,14 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
902
1005
  if (elementUnder && container.contains(elementUnder) && elementUnder !== container) {
903
1006
  const containerRect = container.getBoundingClientRect();
904
1007
  const elRect = elementUnder.getBoundingClientRect();
1008
+ const scaleX = containerRect.width / container.offsetWidth;
1009
+ const scaleY = containerRect.height / container.offsetHeight;
905
1010
  setHoveredElement(
906
1011
  new DOMRect(
907
- elRect.left - containerRect.left,
908
- elRect.top - containerRect.top,
909
- elRect.width,
910
- elRect.height
1012
+ (elRect.left - containerRect.left) / scaleX,
1013
+ (elRect.top - containerRect.top) / scaleY,
1014
+ elRect.width / scaleX,
1015
+ elRect.height / scaleY
911
1016
  )
912
1017
  );
913
1018
  } else {
@@ -941,7 +1046,9 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
941
1046
  role: "button",
942
1047
  tabIndex: 0,
943
1048
  className: "absolute inset-0 z-20",
944
- style: { cursor: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none'%3E%3Ccircle cx='12' cy='12' r='8' stroke='%23FF6B35' stroke-width='2' opacity='0.8'/%3E%3Ccircle cx='12' cy='12' r='2' fill='%23FF6B35'/%3E%3C/svg%3E") 12 12, crosshair` },
1049
+ style: {
1050
+ cursor: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none'%3E%3Ccircle cx='12' cy='12' r='8' stroke='%23FF6B35' stroke-width='2' opacity='0.8'/%3E%3Ccircle cx='12' cy='12' r='2' fill='%23FF6B35'/%3E%3C/svg%3E") 12 12, crosshair`
1051
+ },
945
1052
  onClick: handleOverlayClick,
946
1053
  onKeyDown: (e) => {
947
1054
  if (e.key === "Escape") {
@@ -953,7 +1060,7 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
953
1060
  onMouseLeave: () => setHoveredElement(null)
954
1061
  }
955
1062
  ),
956
- resolvedAnnotations.map(({ annotation, xPercent, yPercent, number }) => /* @__PURE__ */ jsx10(
1063
+ resolvedAnnotations.filter(({ annotation }) => annotation.status === "open" || annotation.id === hoveredId).map(({ annotation, xPercent, yPercent, number }) => /* @__PURE__ */ jsx10(
957
1064
  AnnotationPin,
958
1065
  {
959
1066
  number,
@@ -982,61 +1089,96 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
982
1089
  }
983
1090
 
984
1091
  // src/core/annotations/annotation-panel.tsx
985
- import { MessageCircle, Trash2, X as X2 } from "lucide-react";
1092
+ import { Check, Pencil, PanelRightClose, RotateCcw, Trash2 } from "lucide-react";
1093
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
986
1094
  import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
987
- function AnnotationPanel({ annotations, selectedId, onSelect, onDelete, onClose }) {
1095
+ function AnnotationPanel({
1096
+ annotations,
1097
+ unlinkedAnnotations = [],
1098
+ selectedId,
1099
+ onSelect,
1100
+ onHover,
1101
+ onDelete,
1102
+ onUpdate,
1103
+ onClose
1104
+ }) {
988
1105
  const open = annotations.filter((a) => a.status === "open");
989
1106
  const resolved = annotations.filter((a) => a.status === "resolved");
990
- return /* @__PURE__ */ jsxs5("div", { className: "flex h-full w-80 flex-shrink-0 flex-col border-l border-white/[0.06] bg-neutral-950/95 backdrop-blur-2xl", children: [
991
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between px-4 py-3.5", children: [
992
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2.5", children: [
993
- /* @__PURE__ */ jsx11("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-[#FF6B35]/15", children: /* @__PURE__ */ jsx11(MessageCircle, { className: "h-3.5 w-3.5 text-[#FF6B35]" }) }),
994
- /* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-white", children: "Annotations" }),
995
- open.length > 0 && /* @__PURE__ */ jsx11("span", { className: "flex h-5 min-w-5 items-center justify-center rounded-full bg-[#FF6B35]/15 px-1.5 text-[11px] font-semibold text-[#FF6B35]", children: open.length })
996
- ] }),
1107
+ const total = annotations.length + unlinkedAnnotations.length;
1108
+ const numberFor = (annotation) => annotations.findIndex((a) => a.id === annotation.id) + 1;
1109
+ return /* @__PURE__ */ jsxs5("div", { className: "flex h-full w-80 max-w-[90vw] flex-shrink-0 flex-col border-l border-neutral-800 bg-neutral-950 text-white", children: [
1110
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
997
1111
  /* @__PURE__ */ jsx11(
998
1112
  "button",
999
1113
  {
1000
1114
  onClick: onClose,
1001
- className: "rounded-lg p-1.5 text-neutral-500 transition-colors hover:bg-white/[0.06] hover:text-neutral-300",
1002
- children: /* @__PURE__ */ jsx11(X2, { className: "h-4 w-4" })
1115
+ className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
1116
+ "aria-label": "Hide comments",
1117
+ title: "Hide comments (C)",
1118
+ children: /* @__PURE__ */ jsx11(PanelRightClose, { className: "h-4 w-4" })
1003
1119
  }
1004
- )
1120
+ ),
1121
+ /* @__PURE__ */ jsxs5("span", { className: "text-right text-sm font-semibold", children: [
1122
+ "Comments (",
1123
+ total,
1124
+ ")"
1125
+ ] })
1005
1126
  ] }),
1006
- /* @__PURE__ */ jsx11("div", { className: "h-px bg-gradient-to-r from-transparent via-white/[0.06] to-transparent" }),
1007
- /* @__PURE__ */ jsxs5("div", { className: "flex-1 overflow-y-auto p-2", children: [
1008
- annotations.length === 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4 px-3 py-6", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) }),
1127
+ /* @__PURE__ */ jsxs5("div", { className: "min-h-0 flex-1 overflow-y-auto p-3", children: [
1128
+ annotations.length === 0 && unlinkedAnnotations.length === 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4 px-3 py-6", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) }),
1009
1129
  open.length > 0 && /* @__PURE__ */ jsxs5("div", { children: [
1010
1130
  /* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Open" }),
1011
- open.map((a, i) => /* @__PURE__ */ jsx11(
1131
+ open.map((a) => /* @__PURE__ */ jsx11(
1012
1132
  AnnotationItem,
1013
1133
  {
1014
1134
  annotation: a,
1015
- number: i + 1,
1135
+ number: numberFor(a),
1016
1136
  isSelected: a.id === selectedId,
1017
1137
  onSelect: () => onSelect(a.id),
1018
- onDelete: () => onDelete(a.id)
1138
+ onHover: (hovered) => onHover(hovered ? a.id : null),
1139
+ onDelete: () => onDelete(a.id),
1140
+ onUpdate: (patch) => onUpdate(a.id, patch)
1019
1141
  },
1020
1142
  a.id
1021
1143
  ))
1022
1144
  ] }),
1023
1145
  resolved.length > 0 && /* @__PURE__ */ jsxs5("div", { className: open.length > 0 ? "mt-3" : "", children: [
1024
1146
  /* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Resolved" }),
1025
- resolved.map((a, i) => /* @__PURE__ */ jsx11(
1147
+ resolved.map((a) => /* @__PURE__ */ jsx11(
1026
1148
  AnnotationItem,
1027
1149
  {
1028
1150
  annotation: a,
1029
- number: open.length + i + 1,
1151
+ number: numberFor(a),
1030
1152
  isSelected: a.id === selectedId,
1031
1153
  onSelect: () => onSelect(a.id),
1032
- onDelete: () => onDelete(a.id)
1154
+ onHover: (hovered) => onHover(hovered ? a.id : null),
1155
+ onDelete: () => onDelete(a.id),
1156
+ onUpdate: (patch) => onUpdate(a.id, patch)
1033
1157
  },
1034
1158
  a.id
1035
1159
  ))
1160
+ ] }),
1161
+ unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "mt-3", children: [
1162
+ /* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Removed slides" }),
1163
+ unlinkedAnnotations.map((a, i) => /* @__PURE__ */ jsxs5("div", { children: [
1164
+ /* @__PURE__ */ jsx11("p", { className: "px-2 text-[11px] text-neutral-500", children: a.slideTitle || a.slideId }),
1165
+ /* @__PURE__ */ jsx11(
1166
+ AnnotationItem,
1167
+ {
1168
+ annotation: a,
1169
+ number: i + 1,
1170
+ isSelected: a.id === selectedId,
1171
+ onSelect: () => onSelect(a.id),
1172
+ onHover: (hovered) => onHover(hovered ? a.id : null),
1173
+ onDelete: () => onDelete(a.id),
1174
+ onUpdate: (patch) => onUpdate(a.id, patch)
1175
+ }
1176
+ )
1177
+ ] }, a.id))
1036
1178
  ] })
1037
1179
  ] }),
1038
- annotations.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1039
- /* @__PURE__ */ jsx11("div", { className: "h-px bg-gradient-to-r from-transparent via-white/[0.06] to-transparent" }),
1180
+ annotations.length + unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1181
+ /* @__PURE__ */ jsx11("div", { className: "border-t border-neutral-800" }),
1040
1182
  /* @__PURE__ */ jsx11("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) })
1041
1183
  ] })
1042
1184
  ] });
@@ -1046,7 +1188,7 @@ function WorkflowHint() {
1046
1188
  /* @__PURE__ */ jsx11("p", { className: "text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Workflow" }),
1047
1189
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1048
1190
  /* @__PURE__ */ jsx11("div", { className: "mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-[#FF6B35]/15 text-[10px] font-semibold text-[#FF6B35]", children: "1" }),
1049
- /* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: "Annotate everything you want to fix" })
1191
+ /* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: "Comment on everything you want to fix" })
1050
1192
  ] }),
1051
1193
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1052
1194
  /* @__PURE__ */ jsx11("div", { className: "mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-[#FF6B35]/15 text-[10px] font-semibold text-[#FF6B35]", children: "2" }),
@@ -1054,7 +1196,7 @@ function WorkflowHint() {
1054
1196
  ] }),
1055
1197
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1056
1198
  /* @__PURE__ */ jsx11("div", { className: "mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-[#FF6B35]/15 text-[10px] font-semibold text-[#FF6B35]", children: "3" }),
1057
- /* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: '"Fix open annotations"' })
1199
+ /* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: '"Fix open comments"' })
1058
1200
  ] })
1059
1201
  ] });
1060
1202
  }
@@ -1063,30 +1205,81 @@ function AnnotationItem({
1063
1205
  number,
1064
1206
  isSelected,
1065
1207
  onSelect,
1066
- onDelete
1208
+ onHover,
1209
+ onDelete,
1210
+ onUpdate
1067
1211
  }) {
1212
+ const [isEditing, setIsEditing] = useState5(false);
1213
+ const [draft, setDraft] = useState5(annotation.body);
1214
+ const editRef = useRef4(null);
1215
+ useEffect5(() => {
1216
+ if (isEditing) editRef.current?.focus();
1217
+ }, [isEditing]);
1218
+ const saveEdit = () => {
1219
+ const body = draft.trim();
1220
+ if (!body) return;
1221
+ if (body !== annotation.body) onUpdate({ body });
1222
+ setIsEditing(false);
1223
+ };
1068
1224
  return /* @__PURE__ */ jsxs5(
1069
1225
  "div",
1070
1226
  {
1071
1227
  role: "button",
1072
1228
  tabIndex: 0,
1073
1229
  onClick: onSelect,
1230
+ onMouseEnter: () => onHover(true),
1231
+ onMouseLeave: () => onHover(false),
1074
1232
  onKeyDown: (e) => {
1075
- if (e.key === "Enter" || e.key === " ") onSelect();
1233
+ if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) onSelect();
1076
1234
  },
1077
1235
  className: `group relative mb-0.5 cursor-pointer rounded-xl p-2.5 transition-all duration-150 ${isSelected ? "bg-[#FF6B35]/10 ring-1 ring-[#FF6B35]/20" : "hover:bg-white/[0.04]"}`,
1078
1236
  children: [
1079
- /* @__PURE__ */ jsx11(
1080
- "button",
1081
- {
1082
- onClick: (e) => {
1083
- e.stopPropagation();
1084
- onDelete();
1085
- },
1086
- className: "absolute top-2 right-2 rounded-lg p-1 text-neutral-600 opacity-0 transition-all hover:bg-white/[0.08] hover:text-neutral-300 group-hover:opacity-100",
1087
- children: /* @__PURE__ */ jsx11(Trash2, { className: "h-3 w-3" })
1088
- }
1089
- ),
1237
+ /* @__PURE__ */ jsxs5("div", { className: "absolute top-2 right-2 z-10 flex gap-0.5 rounded-full border border-neutral-700 bg-neutral-900/70 p-0.5 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100 group-focus-within:opacity-100", children: [
1238
+ /* @__PURE__ */ jsx11(
1239
+ "button",
1240
+ {
1241
+ type: "button",
1242
+ title: "Edit comment",
1243
+ "aria-label": "Edit comment",
1244
+ onClick: (e) => {
1245
+ e.stopPropagation();
1246
+ setDraft(annotation.body);
1247
+ setIsEditing(true);
1248
+ },
1249
+ className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
1250
+ children: /* @__PURE__ */ jsx11(Pencil, { className: "h-3.5 w-3.5" })
1251
+ }
1252
+ ),
1253
+ /* @__PURE__ */ jsx11(
1254
+ "button",
1255
+ {
1256
+ type: "button",
1257
+ title: annotation.status === "open" ? "Resolve comment" : "Reopen comment",
1258
+ "aria-label": annotation.status === "open" ? "Resolve comment" : "Reopen comment",
1259
+ onClick: (e) => {
1260
+ e.stopPropagation();
1261
+ onHover(false);
1262
+ onUpdate({ status: annotation.status === "open" ? "resolved" : "open" });
1263
+ },
1264
+ className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
1265
+ children: annotation.status === "open" ? /* @__PURE__ */ jsx11(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx11(RotateCcw, { className: "h-3.5 w-3.5" })
1266
+ }
1267
+ ),
1268
+ /* @__PURE__ */ jsx11(
1269
+ "button",
1270
+ {
1271
+ type: "button",
1272
+ title: "Delete comment",
1273
+ "aria-label": "Delete comment",
1274
+ onClick: (e) => {
1275
+ e.stopPropagation();
1276
+ onDelete();
1277
+ },
1278
+ className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
1279
+ children: /* @__PURE__ */ jsx11(Trash2, { className: "h-3.5 w-3.5" })
1280
+ }
1281
+ )
1282
+ ] }),
1090
1283
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1091
1284
  /* @__PURE__ */ jsx11(
1092
1285
  "div",
@@ -1095,8 +1288,52 @@ function AnnotationItem({
1095
1288
  children: number
1096
1289
  }
1097
1290
  ),
1098
- /* @__PURE__ */ jsxs5("div", { className: "min-w-0 pr-4", children: [
1099
- /* @__PURE__ */ jsx11("p", { className: "text-[13px] leading-relaxed text-neutral-200", children: annotation.body }),
1291
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1 pr-4", children: [
1292
+ isEditing ? /* @__PURE__ */ jsxs5("div", { children: [
1293
+ /* @__PURE__ */ jsx11(
1294
+ "textarea",
1295
+ {
1296
+ ref: editRef,
1297
+ value: draft,
1298
+ onClick: (e) => e.stopPropagation(),
1299
+ onChange: (e) => setDraft(e.target.value),
1300
+ onKeyDown: (e) => {
1301
+ if (e.key === "Escape") setIsEditing(false);
1302
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") saveEdit();
1303
+ },
1304
+ className: "w-full resize-y rounded border border-neutral-700 bg-neutral-900 p-2 text-[13px] text-white outline-none focus:border-[#FF6B35]",
1305
+ rows: 3,
1306
+ maxLength: 4096
1307
+ }
1308
+ ),
1309
+ /* @__PURE__ */ jsxs5("div", { className: "mt-1 flex gap-2 text-xs", children: [
1310
+ /* @__PURE__ */ jsx11(
1311
+ "button",
1312
+ {
1313
+ type: "button",
1314
+ onClick: (e) => {
1315
+ e.stopPropagation();
1316
+ saveEdit();
1317
+ },
1318
+ disabled: !draft.trim(),
1319
+ className: "text-[#FF6B35] disabled:opacity-40",
1320
+ children: "Save"
1321
+ }
1322
+ ),
1323
+ /* @__PURE__ */ jsx11(
1324
+ "button",
1325
+ {
1326
+ type: "button",
1327
+ onClick: (e) => {
1328
+ e.stopPropagation();
1329
+ setIsEditing(false);
1330
+ },
1331
+ className: "text-neutral-400",
1332
+ children: "Cancel"
1333
+ }
1334
+ )
1335
+ ] })
1336
+ ] }) : /* @__PURE__ */ jsx11("p", { className: "text-[13px] leading-relaxed text-neutral-200", children: annotation.body }),
1100
1337
  annotation.target.contentNearPin && /* @__PURE__ */ jsx11("p", { className: "mt-1 truncate text-[11px] text-neutral-600", children: annotation.target.contentNearPin }),
1101
1338
  annotation.resolution && /* @__PURE__ */ jsx11("p", { className: "mt-1.5 text-[11px] text-emerald-400/80 italic", children: annotation.resolution })
1102
1339
  ] })
@@ -1107,7 +1344,7 @@ function AnnotationItem({
1107
1344
  }
1108
1345
 
1109
1346
  // src/core/annotations/use-annotations.ts
1110
- import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef3, useState as useState5 } from "react";
1347
+ import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
1111
1348
 
1112
1349
  // src/core/annotations/adapters/http.ts
1113
1350
  var ENDPOINT = "/__promptslide_annotations";
@@ -1143,23 +1380,47 @@ function createHttpAdapter() {
1143
1380
  async remove(id) {
1144
1381
  cache = cache.filter((a) => a.id !== id);
1145
1382
  await saveAll(cache);
1383
+ },
1384
+ async update(id, patch) {
1385
+ cache = cache.map((a) => a.id === id ? { ...a, ...patch } : a);
1386
+ await saveAll(cache);
1387
+ },
1388
+ async replaceAll(annotations) {
1389
+ cache = annotations;
1390
+ await saveAll(cache);
1146
1391
  }
1147
1392
  };
1148
1393
  }
1149
1394
 
1150
1395
  // src/core/annotations/use-annotations.ts
1151
- function useAnnotations(adapter) {
1152
- const adapterRef = useRef3(adapter ?? createHttpAdapter());
1153
- const [annotations, setAnnotations] = useState5([]);
1154
- useEffect5(() => {
1155
- adapterRef.current.load().then(setAnnotations);
1396
+ function useAnnotations(slidesOrAdapter, adapter) {
1397
+ const slides = Array.isArray(slidesOrAdapter) ? slidesOrAdapter : void 0;
1398
+ const storage = Array.isArray(slidesOrAdapter) ? adapter : slidesOrAdapter ?? adapter;
1399
+ const adapterRef = useRef5(storage ?? createHttpAdapter());
1400
+ const slidesRef = useRef5(slides);
1401
+ slidesRef.current = slides;
1402
+ const [annotations, setAnnotations] = useState6([]);
1403
+ useEffect6(() => {
1404
+ adapterRef.current.load().then((loaded) => {
1405
+ const configuredSlides = slidesRef.current;
1406
+ const migrated = loaded.map((annotation) => {
1407
+ if (annotation.slideId || !configuredSlides) return annotation;
1408
+ const id = configuredSlides[annotation.slideIndex]?.id;
1409
+ return id ? { ...annotation, slideId: id } : annotation;
1410
+ });
1411
+ setAnnotations(migrated);
1412
+ if (migrated.some((annotation, index) => annotation !== loaded[index])) {
1413
+ void adapterRef.current.replaceAll?.(migrated);
1414
+ }
1415
+ });
1156
1416
  return adapterRef.current.subscribe?.(setAnnotations);
1157
1417
  }, []);
1158
1418
  const addAnnotation = useCallback4(
1159
- (slideIndex, slideTitle, target, body) => {
1419
+ (slideIndex, slideTitle, target, body, slideId) => {
1160
1420
  const annotation = {
1161
1421
  id: crypto.randomUUID(),
1162
1422
  slideIndex,
1423
+ ...slideId && { slideId },
1163
1424
  slideTitle,
1164
1425
  target,
1165
1426
  body,
@@ -1175,15 +1436,36 @@ function useAnnotations(adapter) {
1175
1436
  setAnnotations((prev) => prev.filter((a) => a.id !== id));
1176
1437
  adapterRef.current.remove(id);
1177
1438
  }, []);
1439
+ const updateAnnotation = useCallback4(
1440
+ (id, patch) => {
1441
+ const current = annotations.find((a) => a.id === id);
1442
+ if (!current) return;
1443
+ const updated = { body: patch.body ?? current.body, status: patch.status ?? current.status };
1444
+ setAnnotations((prev) => prev.map((a) => a.id === id ? { ...a, ...updated } : a));
1445
+ void adapterRef.current.update?.(id, updated);
1446
+ },
1447
+ [annotations]
1448
+ );
1178
1449
  const getSlideAnnotations = useCallback4(
1179
1450
  (slideIndex) => annotations.filter((a) => a.slideIndex === slideIndex),
1180
1451
  [annotations]
1181
1452
  );
1182
- const openCount = useMemo3(() => annotations.filter((a) => a.status === "open").length, [annotations]);
1453
+ const openCount = useMemo3(
1454
+ () => annotations.filter((a) => a.status === "open").length,
1455
+ [annotations]
1456
+ );
1183
1457
  const updateAnnotations = useCallback4((updated) => {
1184
1458
  setAnnotations(updated);
1185
1459
  }, []);
1186
- return { annotations, addAnnotation, deleteAnnotation, getSlideAnnotations, openCount, updateAnnotations };
1460
+ return {
1461
+ annotations,
1462
+ addAnnotation,
1463
+ deleteAnnotation,
1464
+ updateAnnotation,
1465
+ getSlideAnnotations,
1466
+ openCount,
1467
+ updateAnnotations
1468
+ };
1187
1469
  }
1188
1470
 
1189
1471
  // src/core/utils.ts
@@ -1194,14 +1476,96 @@ function cn(...inputs) {
1194
1476
  }
1195
1477
 
1196
1478
  // src/core/slide-deck.tsx
1197
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1479
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1480
+ var NARROW_VIEWPORT_WIDTH = 768;
1481
+ var DEFAULT_THUMBNAIL_WIDTH = 240;
1482
+ var MIN_THUMBNAIL_WIDTH = 176;
1483
+ var MAX_THUMBNAIL_WIDTH = 480;
1484
+ var THUMBNAIL_WIDTH_STORAGE_KEY = "promptslide:thumbnail-width";
1485
+ function GridThumbnail({
1486
+ slide,
1487
+ index,
1488
+ total
1489
+ }) {
1490
+ const viewportRef = useRef6(null);
1491
+ const canvasRef = useRef6(null);
1492
+ const SlideComponent = slide.component;
1493
+ useLayoutEffect(() => {
1494
+ const viewport = viewportRef.current;
1495
+ const canvas = canvasRef.current;
1496
+ if (!viewport || !canvas) return;
1497
+ const resize = () => {
1498
+ canvas.style.transform = `scale(${viewport.clientWidth / SLIDE_DIMENSIONS.width})`;
1499
+ };
1500
+ resize();
1501
+ const observer = new ResizeObserver(resize);
1502
+ observer.observe(viewport);
1503
+ return () => observer.disconnect();
1504
+ }, []);
1505
+ return /* @__PURE__ */ jsx12("div", { ref: viewportRef, className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx12(
1506
+ "div",
1507
+ {
1508
+ ref: canvasRef,
1509
+ className: "absolute top-0 left-0 origin-top-left",
1510
+ style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
1511
+ children: /* @__PURE__ */ jsx12(AnimationProvider, { currentStep: slide.steps, totalSteps: slide.steps, showAllAnimations: true, children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slide.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: total }) }) })
1512
+ }
1513
+ ) });
1514
+ }
1515
+ function ListSlide({ slide, index, total }) {
1516
+ const viewportRef = useRef6(null);
1517
+ const canvasRef = useRef6(null);
1518
+ const SlideComponent = slide.component;
1519
+ useLayoutEffect(() => {
1520
+ const viewport = viewportRef.current;
1521
+ const canvas = canvasRef.current;
1522
+ if (!viewport || !canvas) return;
1523
+ const resize = () => {
1524
+ canvas.style.transform = `scale(${viewport.clientWidth / SLIDE_DIMENSIONS.width})`;
1525
+ };
1526
+ resize();
1527
+ const observer = new ResizeObserver(resize);
1528
+ observer.observe(viewport);
1529
+ return () => observer.disconnect();
1530
+ }, []);
1531
+ return /* @__PURE__ */ jsx12(
1532
+ "div",
1533
+ {
1534
+ ref: viewportRef,
1535
+ className: "relative aspect-video w-full overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-sm print:m-0 print:h-[1080px] print:w-[1920px] print:break-after-page print:rounded-none print:border-0 print:shadow-none",
1536
+ children: /* @__PURE__ */ jsx12(
1537
+ "div",
1538
+ {
1539
+ ref: canvasRef,
1540
+ className: "list-slide-canvas absolute top-0 left-0 origin-top-left",
1541
+ style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
1542
+ children: /* @__PURE__ */ jsx12(AnimationProvider, { currentStep: slide.steps, totalSteps: slide.steps, showAllAnimations: true, children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slide.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: total }) }) })
1543
+ }
1544
+ )
1545
+ }
1546
+ );
1547
+ }
1198
1548
  function SlideExportView({ slides, slideIndex }) {
1199
- const [ready, setReady] = useState6(false);
1549
+ const [ready, setReady] = useState7(false);
1200
1550
  const clampedIndex = Math.max(0, Math.min(slideIndex, slides.length - 1));
1201
1551
  const slideConfig = slides[clampedIndex];
1202
1552
  const SlideComponent = slideConfig.component;
1203
- useEffect6(() => {
1204
- setReady(true);
1553
+ useEffect7(() => {
1554
+ let cancelled = false;
1555
+ const markReady = async () => {
1556
+ await document.fonts.ready;
1557
+ await Promise.all(Array.from(document.images, (image) => image.decode().catch(() => {
1558
+ })));
1559
+ requestAnimationFrame(
1560
+ () => requestAnimationFrame(() => {
1561
+ if (!cancelled) setReady(true);
1562
+ })
1563
+ );
1564
+ };
1565
+ void markReady();
1566
+ return () => {
1567
+ cancelled = true;
1568
+ };
1205
1569
  }, []);
1206
1570
  return /* @__PURE__ */ jsx12(
1207
1571
  "div",
@@ -1226,8 +1590,16 @@ function SlideExportView({ slides, slideIndex }) {
1226
1590
  }
1227
1591
  );
1228
1592
  }
1229
- function SlideDeck({ slides, transition, directionalTransition, annotations, onAnnotationAdd, onAnnotationDelete }) {
1230
- const [exportParams] = useState6(() => {
1593
+ function SlideDeck({
1594
+ slides,
1595
+ transition,
1596
+ directionalTransition,
1597
+ annotations,
1598
+ onAnnotationAdd,
1599
+ onAnnotationDelete,
1600
+ onAnnotationUpdate
1601
+ }) {
1602
+ const [exportParams] = useState7(() => {
1231
1603
  if (typeof window === "undefined") return null;
1232
1604
  const params = new URLSearchParams(window.location.search);
1233
1605
  if (params.get("export") !== "true") return null;
@@ -1236,24 +1608,178 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1236
1608
  if (exportParams) {
1237
1609
  return /* @__PURE__ */ jsx12(SlideExportView, { slides, slideIndex: exportParams.slideIndex });
1238
1610
  }
1239
- const internal = useAnnotations();
1611
+ const internal = useAnnotations(slides);
1240
1612
  const isExternallyManaged = annotations !== void 0;
1241
1613
  const effectiveAnnotations = isExternallyManaged ? annotations : internal.annotations;
1242
1614
  const effectiveAdd = isExternallyManaged ? onAnnotationAdd : internal.addAnnotation;
1243
1615
  const effectiveDelete = isExternallyManaged ? onAnnotationDelete : internal.deleteAnnotation;
1244
- const openCount = useMemo4(() => effectiveAnnotations.filter((a) => a.status === "open").length, [effectiveAnnotations]);
1245
- const getSlideAnnotations = useCallback5(
1246
- (slideIndex) => effectiveAnnotations.filter((a) => a.slideIndex === slideIndex),
1616
+ const effectiveUpdate = isExternallyManaged ? onAnnotationUpdate : internal.updateAnnotation;
1617
+ const openCount = useMemo4(
1618
+ () => effectiveAnnotations.filter((a) => a.status === "open").length,
1247
1619
  [effectiveAnnotations]
1248
1620
  );
1249
- const [viewMode, setViewMode] = useState6("slide");
1250
- const [isPresentationMode, setIsPresentationMode] = useState6(false);
1251
- const [isAnnotationMode, setIsAnnotationMode] = useState6(false);
1252
- const [showAnnotationPanel, setShowAnnotationPanel] = useState6(false);
1253
- const [selectedAnnotationId, setSelectedAnnotationId] = useState6(null);
1254
- const [scale, setScale] = useState6(1);
1255
- const containerRef = useRef4(null);
1256
- const slideContainerRef = useRef4(null);
1621
+ const getSlideAnnotations = useCallback5(
1622
+ (slideIndex) => effectiveAnnotations.filter(
1623
+ (a) => a.slideId ? a.slideId === slides[slideIndex]?.id : a.slideIndex === slideIndex
1624
+ ),
1625
+ [effectiveAnnotations, slides]
1626
+ );
1627
+ const unlinkedAnnotations = useMemo4(
1628
+ () => effectiveAnnotations.filter((a) => a.slideId && !slides.some((slide) => slide.id === a.slideId)),
1629
+ [effectiveAnnotations, slides]
1630
+ );
1631
+ const [viewMode, setViewMode] = useState7("slide");
1632
+ const [isEditorOpen, setIsEditorOpen] = useState7(false);
1633
+ const [editorRevision, setEditorRevision] = useState7(0);
1634
+ useEffect7(() => {
1635
+ const onEditorState = (event) => {
1636
+ const open = Boolean(event.detail?.open);
1637
+ setIsEditorOpen(open);
1638
+ if (open) {
1639
+ setViewMode("slide");
1640
+ setIsAnnotationMode(false);
1641
+ setShowAnnotationPanel(false);
1642
+ }
1643
+ };
1644
+ const refreshEditor = () => setEditorRevision((value) => value + 1);
1645
+ window.addEventListener("promptslide:editor-state", onEditorState);
1646
+ window.addEventListener("promptslide:editor-refresh", refreshEditor);
1647
+ return () => {
1648
+ window.removeEventListener("promptslide:editor-state", onEditorState);
1649
+ window.removeEventListener("promptslide:editor-refresh", refreshEditor);
1650
+ };
1651
+ }, []);
1652
+ const [isPresentationMode, setIsPresentationMode] = useState7(false);
1653
+ const [viewportWidth, setViewportWidth] = useState7(
1654
+ () => typeof window === "undefined" ? 1280 : window.innerWidth
1655
+ );
1656
+ const [showThumbnailRail, setShowThumbnailRail] = useState7(
1657
+ () => typeof window === "undefined" ? true : window.innerWidth >= NARROW_VIEWPORT_WIDTH
1658
+ );
1659
+ const [thumbnailWidth, setThumbnailWidth] = useState7(() => {
1660
+ if (typeof window === "undefined") return DEFAULT_THUMBNAIL_WIDTH;
1661
+ const savedWidth = Number(window.localStorage.getItem(THUMBNAIL_WIDTH_STORAGE_KEY));
1662
+ return Number.isFinite(savedWidth) && savedWidth >= MIN_THUMBNAIL_WIDTH ? Math.min(savedWidth, MAX_THUMBNAIL_WIDTH) : DEFAULT_THUMBNAIL_WIDTH;
1663
+ });
1664
+ const [isAnnotationMode, setIsAnnotationMode] = useState7(false);
1665
+ const [showAnnotationPanel, setShowAnnotationPanel] = useState7(false);
1666
+ const [selectedAnnotationId, setSelectedAnnotationId] = useState7(null);
1667
+ const [hoveredAnnotationId, setHoveredAnnotationId] = useState7(null);
1668
+ const [isResizingThumbnails, setIsResizingThumbnails] = useState7(false);
1669
+ const [isDragCollapsed, setIsDragCollapsed] = useState7(false);
1670
+ const [isAnimatingDragBoundary, setIsAnimatingDragBoundary] = useState7(false);
1671
+ const [scale, setScale] = useState7(1);
1672
+ const containerRef = useRef6(null);
1673
+ const slideContainerRef = useRef6(null);
1674
+ const previewViewportRef = useRef6(null);
1675
+ const activeThumbnailRef = useRef6(null);
1676
+ const preferredRailOpenRef = useRef6(true);
1677
+ const wasNarrowViewportRef = useRef6(viewportWidth < NARROW_VIEWPORT_WIDTH);
1678
+ const resizeStartRef = useRef6(null);
1679
+ const previousUserSelectRef = useRef6("");
1680
+ const previousCursorRef = useRef6("");
1681
+ const dragCollapsedRef = useRef6(false);
1682
+ const dragAnimationTimerRef = useRef6(null);
1683
+ const isNarrowViewport = viewportWidth < NARROW_VIEWPORT_WIDTH;
1684
+ const maxThumbnailWidth = isNarrowViewport ? Math.max(120, Math.floor(viewportWidth * 0.8)) : Math.min(MAX_THUMBNAIL_WIDTH, viewportWidth - 360);
1685
+ const minThumbnailWidth = Math.min(MIN_THUMBNAIL_WIDTH, maxThumbnailWidth);
1686
+ const visibleThumbnailWidth = Math.min(thumbnailWidth, maxThumbnailWidth);
1687
+ const isThumbnailRailVisible = showThumbnailRail && !isDragCollapsed;
1688
+ const setThumbnailRailOpen = useCallback5((open) => {
1689
+ preferredRailOpenRef.current = open;
1690
+ setShowThumbnailRail(open);
1691
+ }, []);
1692
+ useEffect7(() => {
1693
+ const handleResize = () => setViewportWidth(window.innerWidth);
1694
+ window.addEventListener("resize", handleResize);
1695
+ return () => window.removeEventListener("resize", handleResize);
1696
+ }, []);
1697
+ useEffect7(() => {
1698
+ if (isNarrowViewport !== wasNarrowViewportRef.current) {
1699
+ setShowThumbnailRail(isNarrowViewport ? false : preferredRailOpenRef.current);
1700
+ if (isNarrowViewport) {
1701
+ setIsAnnotationMode(false);
1702
+ setShowAnnotationPanel(false);
1703
+ setSelectedAnnotationId(null);
1704
+ }
1705
+ wasNarrowViewportRef.current = isNarrowViewport;
1706
+ }
1707
+ }, [isNarrowViewport]);
1708
+ useEffect7(() => {
1709
+ window.localStorage.setItem(THUMBNAIL_WIDTH_STORAGE_KEY, String(thumbnailWidth));
1710
+ }, [thumbnailWidth]);
1711
+ useEffect7(
1712
+ () => () => {
1713
+ if (resizeStartRef.current) {
1714
+ document.body.style.userSelect = previousUserSelectRef.current;
1715
+ document.body.style.cursor = previousCursorRef.current;
1716
+ }
1717
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
1718
+ },
1719
+ []
1720
+ );
1721
+ useEffect7(() => {
1722
+ if ((!showThumbnailRail || isPresentationMode || viewMode !== "slide") && resizeStartRef.current) {
1723
+ resizeStartRef.current = null;
1724
+ dragCollapsedRef.current = false;
1725
+ setIsResizingThumbnails(false);
1726
+ setIsDragCollapsed(false);
1727
+ setIsAnimatingDragBoundary(false);
1728
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
1729
+ document.body.style.userSelect = previousUserSelectRef.current;
1730
+ document.body.style.cursor = previousCursorRef.current;
1731
+ }
1732
+ }, [showThumbnailRail, isPresentationMode, viewMode]);
1733
+ useEffect7(() => {
1734
+ if (!isResizingThumbnails) return;
1735
+ const getDraggedWidth = (clientX) => {
1736
+ const start = resizeStartRef.current;
1737
+ return start ? start.width + clientX - start.pointerX : null;
1738
+ };
1739
+ const handlePointerMove = (event) => {
1740
+ if (event.pointerId !== resizeStartRef.current?.pointerId) return;
1741
+ const nextWidth = getDraggedWidth(event.clientX);
1742
+ if (nextWidth === null) return;
1743
+ const collapsed = nextWidth < minThumbnailWidth;
1744
+ if (collapsed !== dragCollapsedRef.current) {
1745
+ dragCollapsedRef.current = collapsed;
1746
+ setIsDragCollapsed(collapsed);
1747
+ setIsAnimatingDragBoundary(true);
1748
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
1749
+ dragAnimationTimerRef.current = setTimeout(() => {
1750
+ setIsAnimatingDragBoundary(false);
1751
+ dragAnimationTimerRef.current = null;
1752
+ }, 200);
1753
+ }
1754
+ if (!collapsed) setThumbnailWidth(Math.min(maxThumbnailWidth, nextWidth));
1755
+ };
1756
+ const stopResize = (event) => {
1757
+ if (event && event.pointerId !== resizeStartRef.current?.pointerId) return;
1758
+ const nextWidth = event ? getDraggedWidth(event.clientX) : null;
1759
+ const collapsed = event?.type === "pointerup" && nextWidth !== null && nextWidth < minThumbnailWidth;
1760
+ resizeStartRef.current = null;
1761
+ dragCollapsedRef.current = false;
1762
+ document.body.style.userSelect = previousUserSelectRef.current;
1763
+ document.body.style.cursor = previousCursorRef.current;
1764
+ setIsResizingThumbnails(false);
1765
+ setIsDragCollapsed(false);
1766
+ if (collapsed) setThumbnailRailOpen(false);
1767
+ else if (nextWidth !== null) {
1768
+ setThumbnailWidth(Math.max(minThumbnailWidth, Math.min(maxThumbnailWidth, nextWidth)));
1769
+ }
1770
+ };
1771
+ const handleBlur = () => stopResize();
1772
+ window.addEventListener("pointermove", handlePointerMove);
1773
+ window.addEventListener("pointerup", stopResize);
1774
+ window.addEventListener("pointercancel", stopResize);
1775
+ window.addEventListener("blur", handleBlur);
1776
+ return () => {
1777
+ window.removeEventListener("pointermove", handlePointerMove);
1778
+ window.removeEventListener("pointerup", stopResize);
1779
+ window.removeEventListener("pointercancel", stopResize);
1780
+ window.removeEventListener("blur", handleBlur);
1781
+ };
1782
+ }, [isResizingThumbnails, minThumbnailWidth, maxThumbnailWidth, setThumbnailRailOpen]);
1257
1783
  const {
1258
1784
  currentSlide,
1259
1785
  animationStep,
@@ -1274,22 +1800,27 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1274
1800
  await document.exitFullscreen();
1275
1801
  }
1276
1802
  }, []);
1277
- useEffect6(() => {
1803
+ useEffect7(() => {
1278
1804
  const handleFullscreenChange = () => {
1279
1805
  setIsPresentationMode(!!document.fullscreenElement);
1280
1806
  };
1281
1807
  document.addEventListener("fullscreenchange", handleFullscreenChange);
1282
1808
  return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
1283
1809
  }, []);
1284
- useEffect6(() => {
1810
+ useEffect7(() => {
1811
+ if (viewMode === "slide" && !isPresentationMode && showThumbnailRail) {
1812
+ activeThumbnailRef.current?.scrollIntoView({ block: "nearest" });
1813
+ }
1814
+ }, [currentSlide, isPresentationMode, showThumbnailRail, viewMode]);
1815
+ useEffect7(() => {
1285
1816
  const calculateScale = () => {
1286
1817
  if (!isPresentationMode) {
1287
1818
  setScale(1);
1288
1819
  return;
1289
1820
  }
1290
- const viewportWidth = window.innerWidth;
1821
+ const availableWidth = window.innerWidth;
1291
1822
  const viewportHeight = window.innerHeight;
1292
- const scaleX = viewportWidth / SLIDE_DIMENSIONS.width;
1823
+ const scaleX = availableWidth / SLIDE_DIMENSIONS.width;
1293
1824
  const scaleY = viewportHeight / SLIDE_DIMENSIONS.height;
1294
1825
  setScale(Math.min(scaleX, scaleY));
1295
1826
  };
@@ -1297,7 +1828,46 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1297
1828
  window.addEventListener("resize", calculateScale);
1298
1829
  return () => window.removeEventListener("resize", calculateScale);
1299
1830
  }, [isPresentationMode]);
1300
- const handleExportPdf = () => {
1831
+ useLayoutEffect(() => {
1832
+ const viewport = previewViewportRef.current;
1833
+ const canvas = slideContainerRef.current;
1834
+ if (!viewport || !canvas) return;
1835
+ const resize = (width, height) => {
1836
+ const fitScale = Math.min(width / SLIDE_DIMENSIONS.width, height / SLIDE_DIMENSIONS.height);
1837
+ canvas.style.transform = `scale(${fitScale})`;
1838
+ };
1839
+ resize(viewport.clientWidth, viewport.clientHeight);
1840
+ const observer = new ResizeObserver(([entry]) => {
1841
+ if (!entry) return;
1842
+ resize(entry.contentRect.width, entry.contentRect.height);
1843
+ });
1844
+ observer.observe(viewport);
1845
+ return () => observer.disconnect();
1846
+ }, [viewMode, isPresentationMode]);
1847
+ const toggleCommentMode = useCallback5(() => {
1848
+ const next = !isAnnotationMode;
1849
+ setIsAnnotationMode(next);
1850
+ setShowAnnotationPanel(next);
1851
+ if (!next) {
1852
+ setSelectedAnnotationId(null);
1853
+ setHoveredAnnotationId(null);
1854
+ }
1855
+ }, [isAnnotationMode]);
1856
+ const handleExportPdf = useCallback5(async () => {
1857
+ try {
1858
+ const response = await fetch("/__promptslide_pdf");
1859
+ if (response.ok && response.headers.get("content-type")?.includes("application/pdf")) {
1860
+ const blob = await response.blob();
1861
+ const url = URL.createObjectURL(blob);
1862
+ const link = document.createElement("a");
1863
+ link.href = url;
1864
+ link.download = "slides.pdf";
1865
+ link.click();
1866
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
1867
+ return;
1868
+ }
1869
+ } catch {
1870
+ }
1301
1871
  const previousMode = viewMode;
1302
1872
  setViewMode("list");
1303
1873
  setTimeout(() => {
@@ -1308,17 +1878,50 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1308
1878
  window.addEventListener("afterprint", handleAfterPrint);
1309
1879
  window.print();
1310
1880
  }, 100);
1311
- };
1312
- useEffect6(() => {
1881
+ }, [viewMode]);
1882
+ useEffect7(() => {
1313
1883
  const handleKeyDown = (e) => {
1314
- if (e.key === "f" || e.key === "F") {
1315
- togglePresentationMode();
1884
+ const target = e.target;
1885
+ if (target?.closest("input, textarea, [contenteditable='true']")) return;
1886
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
1887
+ const key = e.key.toLowerCase();
1888
+ if (e.repeat && "fvglcdt".includes(key)) return;
1889
+ if (isEditorOpen && ["f", "v", "g", "l", "c", "d"].includes(key)) return;
1890
+ if (key === "f") {
1891
+ e.preventDefault();
1892
+ void togglePresentationMode();
1316
1893
  return;
1317
1894
  }
1318
- if (e.key === "g" || e.key === "G") {
1895
+ if (key === "v" && !isPresentationMode) {
1896
+ e.preventDefault();
1897
+ setViewMode("slide");
1898
+ return;
1899
+ }
1900
+ if (key === "g") {
1901
+ e.preventDefault();
1319
1902
  setViewMode((prev) => prev === "grid" ? "slide" : "grid");
1320
1903
  return;
1321
1904
  }
1905
+ if (key === "l") {
1906
+ e.preventDefault();
1907
+ setViewMode((prev) => prev === "list" ? "slide" : "list");
1908
+ return;
1909
+ }
1910
+ if (key === "c" && !isPresentationMode) {
1911
+ e.preventDefault();
1912
+ toggleCommentMode();
1913
+ return;
1914
+ }
1915
+ if (key === "d" && !isPresentationMode) {
1916
+ e.preventDefault();
1917
+ void handleExportPdf();
1918
+ return;
1919
+ }
1920
+ if (viewMode === "slide" && !isPresentationMode && key === "t") {
1921
+ e.preventDefault();
1922
+ setThumbnailRailOpen(!showThumbnailRail);
1923
+ return;
1924
+ }
1322
1925
  if (viewMode !== "slide" || isAnnotationMode && !isPresentationMode) return;
1323
1926
  if (e.key === "ArrowRight" || e.key === " ") {
1324
1927
  e.preventDefault();
@@ -1330,10 +1933,48 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1330
1933
  };
1331
1934
  window.addEventListener("keydown", handleKeyDown);
1332
1935
  return () => window.removeEventListener("keydown", handleKeyDown);
1333
- }, [advance, goBack, viewMode, togglePresentationMode, isAnnotationMode, isPresentationMode]);
1936
+ }, [
1937
+ advance,
1938
+ goBack,
1939
+ viewMode,
1940
+ togglePresentationMode,
1941
+ isAnnotationMode,
1942
+ isEditorOpen,
1943
+ isPresentationMode,
1944
+ setThumbnailRailOpen,
1945
+ showThumbnailRail,
1946
+ toggleCommentMode,
1947
+ handleExportPdf
1948
+ ]);
1949
+ const editorPanelWidth = viewportWidth <= 850 ? 280 : 320;
1334
1950
  return /* @__PURE__ */ jsxs6("div", { className: "min-h-screen w-full bg-neutral-950 text-foreground", children: [
1335
1951
  /* @__PURE__ */ jsx12("style", { children: `
1952
+ .slide-thumbnail-scroll-area {
1953
+ scrollbar-width: thin;
1954
+ scrollbar-color: #404040 transparent;
1955
+ }
1956
+ .slide-thumbnail-scroll-area::-webkit-scrollbar {
1957
+ width: 10px;
1958
+ }
1959
+ .slide-thumbnail-scroll-area::-webkit-scrollbar-track {
1960
+ background: transparent;
1961
+ }
1962
+ .slide-thumbnail-scroll-area::-webkit-scrollbar-thumb {
1963
+ background-color: #404040;
1964
+ border: 3px solid transparent;
1965
+ border-radius: 9999px;
1966
+ background-clip: padding-box;
1967
+ }
1968
+ .slide-thumbnail-scroll-area::-webkit-scrollbar-thumb:hover {
1969
+ background-color: #525252;
1970
+ }
1971
+ .slide-thumbnail-scroll-area::-webkit-scrollbar-corner {
1972
+ background: transparent;
1973
+ }
1336
1974
  @media print {
1975
+ .list-slide-canvas {
1976
+ transform: scale(1.5) !important;
1977
+ }
1337
1978
  @page {
1338
1979
  size: 1920px 1080px;
1339
1980
  margin: 0;
@@ -1356,10 +1997,14 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1356
1997
  /* @__PURE__ */ jsxs6(
1357
1998
  "div",
1358
1999
  {
2000
+ "data-ps-viewer-toolbar": true,
2001
+ style: isEditorOpen ? { right: editorPanelWidth + 16, maxWidth: `calc(100vw - ${editorPanelWidth + 32}px)` } : void 0,
1359
2002
  className: cn(
1360
- "fixed top-4 z-50 flex gap-1 rounded-lg border border-neutral-800 bg-neutral-950/90 p-1 backdrop-blur-sm transition-[right] print:hidden",
1361
- isPresentationMode && "hidden",
1362
- isAnnotationMode && showAnnotationPanel ? "right-[19.5rem]" : "right-4"
2003
+ "fixed top-4 z-50 flex gap-1 rounded-lg border border-neutral-800 bg-neutral-950/90 p-1 backdrop-blur-sm transition-[right] duration-200 ease-out print:hidden",
2004
+ (isPresentationMode || isNarrowViewport && isAnnotationMode && showAnnotationPanel) && "hidden",
2005
+ isEditorOpen && "flex-wrap",
2006
+ isEditorOpen && viewportWidth <= 850 && "[&_button:disabled]:hidden [&>div]:hidden",
2007
+ !isEditorOpen && (isAnnotationMode && showAnnotationPanel ? "right-[21rem]" : "right-4")
1363
2008
  ),
1364
2009
  children: [
1365
2010
  /* @__PURE__ */ jsx12(
@@ -1370,7 +2015,8 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1370
2015
  "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1371
2016
  viewMode === "slide" && "bg-neutral-800 text-white"
1372
2017
  ),
1373
- title: "Presentation View",
2018
+ disabled: isEditorOpen,
2019
+ title: "Presentation View (V)",
1374
2020
  children: /* @__PURE__ */ jsx12(Monitor, { className: "h-4 w-4" })
1375
2021
  }
1376
2022
  ),
@@ -1382,7 +2028,8 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1382
2028
  "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1383
2029
  viewMode === "list" && "bg-neutral-800 text-white"
1384
2030
  ),
1385
- title: "List View",
2031
+ disabled: isEditorOpen,
2032
+ title: "List View (L)",
1386
2033
  children: /* @__PURE__ */ jsx12(List, { className: "h-4 w-4" })
1387
2034
  }
1388
2035
  ),
@@ -1394,7 +2041,8 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1394
2041
  "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1395
2042
  viewMode === "grid" && "bg-neutral-800 text-white"
1396
2043
  ),
1397
- title: "Grid View",
2044
+ disabled: isEditorOpen,
2045
+ title: "Grid View (G)",
1398
2046
  children: /* @__PURE__ */ jsx12(Grid3X3, { className: "h-4 w-4" })
1399
2047
  }
1400
2048
  ),
@@ -1402,42 +2050,45 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1402
2050
  /* @__PURE__ */ jsxs6(
1403
2051
  "button",
1404
2052
  {
1405
- onClick: () => {
1406
- setIsAnnotationMode((prev) => {
1407
- const next = !prev;
1408
- setShowAnnotationPanel(next);
1409
- if (!next) setSelectedAnnotationId(null);
1410
- return next;
1411
- });
1412
- },
2053
+ onClick: toggleCommentMode,
1413
2054
  className: cn(
1414
- "relative rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1415
- isAnnotationMode && "bg-[#FF6B35] text-white hover:bg-[#FF7A4A]"
2055
+ "relative inline-flex h-8 items-center justify-center gap-1.5 rounded-md font-[family-name:system-ui] text-[13px] leading-none font-medium transition-colors",
2056
+ isAnnotationMode ? "border border-[#FF6B35]/50 bg-[#FF6B35]/15 p-[7px] text-[#FF6B35] hover:border-[#FF6B35] hover:bg-[#FF6B35]/25" : "px-2.5 text-neutral-400 hover:bg-neutral-800 hover:text-white"
1416
2057
  ),
1417
- title: "Annotate slides",
2058
+ disabled: isEditorOpen,
2059
+ "aria-label": "Comment",
2060
+ "aria-pressed": isAnnotationMode,
2061
+ title: "Comment (C)",
1418
2062
  children: [
1419
- /* @__PURE__ */ jsx12(MessageCircle2, { className: "h-4 w-4" }),
2063
+ /* @__PURE__ */ jsx12(MessageCircle, { className: "h-[15px] w-[15px] shrink-0" }),
2064
+ !isAnnotationMode && /* @__PURE__ */ jsx12("span", { children: "Comment" }),
1420
2065
  openCount > 0 && /* @__PURE__ */ jsx12("span", { className: "absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-[#FF6B35] text-[10px] font-bold text-white", children: openCount })
1421
2066
  ]
1422
2067
  }
1423
2068
  ),
2069
+ /* @__PURE__ */ jsx12("span", { "data-ps-editor-toolbar": true, className: "contents" }),
1424
2070
  /* @__PURE__ */ jsx12("div", { className: "mx-1 w-px bg-neutral-800" }),
1425
2071
  /* @__PURE__ */ jsx12(
1426
2072
  "button",
1427
2073
  {
1428
2074
  onClick: handleExportPdf,
1429
2075
  className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1430
- title: "Download PDF",
2076
+ disabled: isEditorOpen,
2077
+ title: "Download PDF (D)",
1431
2078
  children: /* @__PURE__ */ jsx12(Download, { className: "h-4 w-4" })
1432
2079
  }
1433
2080
  ),
1434
- /* @__PURE__ */ jsx12(
2081
+ /* @__PURE__ */ jsxs6(
1435
2082
  "button",
1436
2083
  {
1437
2084
  onClick: togglePresentationMode,
1438
- className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
2085
+ className: "inline-flex h-8 items-center justify-center gap-1.5 rounded-md border border-[#FF6B35]/50 bg-[#FF6B35]/15 px-3 text-sm font-semibold text-[#FF6B35] transition-colors hover:border-[#FF6B35] hover:bg-[#FF6B35]/25",
2086
+ disabled: isEditorOpen,
1439
2087
  title: "Present (F)",
1440
- children: /* @__PURE__ */ jsx12(Maximize, { className: "h-4 w-4" })
2088
+ children: [
2089
+ /* @__PURE__ */ jsx12(Play, { className: "h-3.5 w-3.5 fill-current" }),
2090
+ /* @__PURE__ */ jsx12("span", { children: "Present" })
2091
+ ]
1441
2092
  }
1442
2093
  )
1443
2094
  ]
@@ -1446,11 +2097,157 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1446
2097
  viewMode === "slide" && /* @__PURE__ */ jsxs6(
1447
2098
  "div",
1448
2099
  {
1449
- className: cn(
1450
- "flex h-screen w-full print:hidden",
1451
- isPresentationMode ? "bg-black" : ""
1452
- ),
2100
+ className: cn("flex h-screen w-full print:hidden", isPresentationMode ? "bg-black" : ""),
1453
2101
  children: [
2102
+ !isPresentationMode && /* @__PURE__ */ jsxs6(Fragment3, { children: [
2103
+ /* @__PURE__ */ jsxs6(
2104
+ "div",
2105
+ {
2106
+ className: cn(
2107
+ "relative z-10 h-screen shrink-0",
2108
+ isNarrowViewport && "fixed inset-y-0 left-0",
2109
+ isResizingThumbnails && !isAnimatingDragBoundary ? "transition-none" : "transition-[width] duration-200 ease-out motion-reduce:transition-none"
2110
+ ),
2111
+ style: { width: isThumbnailRailVisible ? visibleThumbnailWidth : 0 },
2112
+ children: [
2113
+ /* @__PURE__ */ jsx12("div", { className: "h-full w-full overflow-hidden", children: /* @__PURE__ */ jsxs6(
2114
+ "aside",
2115
+ {
2116
+ id: "presentation-thumbnails",
2117
+ "aria-label": "Slide thumbnails",
2118
+ "aria-hidden": !isThumbnailRailVisible,
2119
+ inert: !isThumbnailRailVisible,
2120
+ className: cn(
2121
+ "flex h-screen shrink-0 flex-col border-r border-neutral-800 bg-neutral-950 text-white transition-opacity motion-reduce:transition-none",
2122
+ isThumbnailRailVisible ? "opacity-100 delay-100 duration-100" : "opacity-0 duration-75"
2123
+ ),
2124
+ style: { width: visibleThumbnailWidth },
2125
+ children: [
2126
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
2127
+ /* @__PURE__ */ jsxs6("span", { className: "text-sm font-semibold", children: [
2128
+ "Slides (",
2129
+ slides.length,
2130
+ ")"
2131
+ ] }),
2132
+ /* @__PURE__ */ jsx12(
2133
+ "button",
2134
+ {
2135
+ type: "button",
2136
+ onClick: () => setThumbnailRailOpen(false),
2137
+ className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
2138
+ "aria-label": "Hide slide thumbnails",
2139
+ title: "Hide thumbnails (T)",
2140
+ children: /* @__PURE__ */ jsx12(PanelLeftClose, { className: "h-4 w-4" })
2141
+ }
2142
+ )
2143
+ ] }),
2144
+ /* @__PURE__ */ jsx12("div", { className: "slide-thumbnail-scroll-area min-h-0 flex-1 space-y-3 overflow-y-auto p-3", children: slides.map((slideConfig, index) => /* @__PURE__ */ jsxs6(
2145
+ "button",
2146
+ {
2147
+ ref: index === currentSlide ? activeThumbnailRef : void 0,
2148
+ type: "button",
2149
+ onClick: () => goToSlide(index),
2150
+ "aria-label": `Go to slide ${index + 1}${slideConfig.title ? `: ${slideConfig.title}` : ""}`,
2151
+ "aria-current": index === currentSlide ? "page" : void 0,
2152
+ className: cn(
2153
+ "block w-full rounded-lg border p-1.5 text-left transition-colors focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#FF6B35]",
2154
+ index === currentSlide ? "border-[#FF6B35] bg-[#FF6B35]/15 ring-1 ring-[#FF6B35]" : "border-transparent hover:border-neutral-600 hover:bg-neutral-800"
2155
+ ),
2156
+ children: [
2157
+ /* @__PURE__ */ jsx12("div", { className: "relative aspect-video w-full overflow-hidden rounded bg-black", children: /* @__PURE__ */ jsx12(
2158
+ GridThumbnail,
2159
+ {
2160
+ slide: slideConfig,
2161
+ index,
2162
+ total: slides.length
2163
+ }
2164
+ ) }),
2165
+ /* @__PURE__ */ jsx12("div", { className: "mt-1.5 flex items-baseline gap-2 px-0.5 text-xs", children: slideConfig.title ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2166
+ /* @__PURE__ */ jsx12("span", { className: "font-mono text-neutral-400", children: index + 1 }),
2167
+ /* @__PURE__ */ jsx12("span", { className: "truncate", children: slideConfig.title })
2168
+ ] }) : /* @__PURE__ */ jsxs6("span", { children: [
2169
+ "Slide ",
2170
+ index + 1
2171
+ ] }) })
2172
+ ]
2173
+ },
2174
+ slideConfig.id ?? index
2175
+ )) })
2176
+ ]
2177
+ }
2178
+ ) }),
2179
+ isThumbnailRailVisible && /* @__PURE__ */ jsx12(
2180
+ "div",
2181
+ {
2182
+ role: "separator",
2183
+ "aria-label": "Resize slide thumbnails",
2184
+ "aria-orientation": "vertical",
2185
+ "aria-valuemin": minThumbnailWidth,
2186
+ "aria-valuemax": maxThumbnailWidth,
2187
+ "aria-valuenow": visibleThumbnailWidth,
2188
+ tabIndex: 0,
2189
+ className: cn(
2190
+ "absolute inset-y-0 -right-1 w-2 cursor-col-resize touch-none focus-visible:outline-none after:pointer-events-none after:absolute after:inset-y-0 after:right-1 after:w-0.5 after:bg-transparent after:content-[''] after:transition-colors hover:after:bg-[#FF6B35]/50 focus-visible:after:bg-[#FF6B35]/50",
2191
+ isResizingThumbnails && "after:bg-[#FF6B35]/60"
2192
+ ),
2193
+ onPointerDown: (event) => {
2194
+ if (event.button !== 0) return;
2195
+ event.preventDefault();
2196
+ resizeStartRef.current = {
2197
+ pointerId: event.pointerId,
2198
+ pointerX: event.clientX,
2199
+ width: visibleThumbnailWidth
2200
+ };
2201
+ dragCollapsedRef.current = false;
2202
+ setIsAnimatingDragBoundary(false);
2203
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
2204
+ setIsResizingThumbnails(true);
2205
+ previousUserSelectRef.current = document.body.style.userSelect;
2206
+ previousCursorRef.current = document.body.style.cursor;
2207
+ document.body.style.userSelect = "none";
2208
+ document.body.style.cursor = "col-resize";
2209
+ },
2210
+ onKeyDown: (event) => {
2211
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
2212
+ event.preventDefault();
2213
+ event.stopPropagation();
2214
+ const amount = event.key === "ArrowRight" ? 16 : -16;
2215
+ setThumbnailWidth(
2216
+ Math.max(
2217
+ minThumbnailWidth,
2218
+ Math.min(maxThumbnailWidth, visibleThumbnailWidth + amount)
2219
+ )
2220
+ );
2221
+ }
2222
+ }
2223
+ }
2224
+ )
2225
+ ]
2226
+ }
2227
+ ),
2228
+ /* @__PURE__ */ jsx12(
2229
+ "div",
2230
+ {
2231
+ "aria-hidden": isThumbnailRailVisible || isResizingThumbnails,
2232
+ inert: isThumbnailRailVisible || isResizingThumbnails,
2233
+ className: cn(
2234
+ "fixed top-4 left-4 z-50 rounded-lg border border-neutral-800 bg-neutral-950/90 backdrop-blur-sm transition-opacity duration-150 motion-reduce:transition-none",
2235
+ isThumbnailRailVisible || isResizingThumbnails ? "pointer-events-none opacity-0" : "opacity-100 delay-200"
2236
+ ),
2237
+ children: /* @__PURE__ */ jsx12(
2238
+ "button",
2239
+ {
2240
+ type: "button",
2241
+ onClick: () => setThumbnailRailOpen(true),
2242
+ className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
2243
+ "aria-label": "Show slide thumbnails",
2244
+ title: "Show thumbnails (T)",
2245
+ children: /* @__PURE__ */ jsx12(PanelLeftOpen, { className: "h-4 w-4" })
2246
+ }
2247
+ )
2248
+ }
2249
+ )
2250
+ ] }),
1454
2251
  /* @__PURE__ */ jsxs6(
1455
2252
  "div",
1456
2253
  {
@@ -1458,7 +2255,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1458
2255
  role: "presentation",
1459
2256
  tabIndex: isPresentationMode ? 0 : void 0,
1460
2257
  className: cn(
1461
- "flex flex-1 flex-col items-center justify-center overflow-hidden",
2258
+ "flex min-w-0 flex-1 flex-col items-center justify-center overflow-hidden",
1462
2259
  isPresentationMode ? "bg-black p-0" : "p-4 md:p-8"
1463
2260
  ),
1464
2261
  onClick: isPresentationMode ? advance : void 0,
@@ -1491,42 +2288,76 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1491
2288
  }
1492
2289
  )
1493
2290
  }
1494
- ) : /* @__PURE__ */ jsxs6("div", { ref: slideContainerRef, className: "relative aspect-video w-full max-w-7xl overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-2xl", children: [
1495
- /* @__PURE__ */ jsx12(
1496
- SlideRenderer,
1497
- {
1498
- slides,
1499
- currentSlide,
1500
- animationStep,
1501
- totalSteps,
1502
- direction,
1503
- showAllAnimations,
1504
- transition,
1505
- directionalTransition,
1506
- onTransitionComplete
1507
- }
1508
- ),
1509
- isAnnotationMode && /* @__PURE__ */ jsx12(
1510
- AnnotationOverlay,
1511
- {
1512
- slides,
1513
- currentSlide,
1514
- slideContainerRef,
1515
- selectedId: selectedAnnotationId,
1516
- onSelectId: setSelectedAnnotationId,
1517
- onShowPanel: () => setShowAnnotationPanel(true),
1518
- slideAnnotations: getSlideAnnotations(currentSlide),
1519
- addAnnotation: effectiveAdd ?? (() => {
1520
- })
1521
- }
1522
- )
1523
- ] }) }),
2291
+ ) : /* @__PURE__ */ jsxs6(
2292
+ "div",
2293
+ {
2294
+ ref: previewViewportRef,
2295
+ className: "relative aspect-video w-full max-w-7xl overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-2xl",
2296
+ children: [
2297
+ /* @__PURE__ */ jsxs6(
2298
+ "div",
2299
+ {
2300
+ ref: slideContainerRef,
2301
+ "data-ps-editor-canvas": true,
2302
+ tabIndex: -1,
2303
+ "data-slide-index": currentSlide,
2304
+ className: "absolute top-0 left-0 overflow-hidden outline-none",
2305
+ style: {
2306
+ width: SLIDE_DIMENSIONS.width,
2307
+ height: SLIDE_DIMENSIONS.height,
2308
+ transformOrigin: "top left"
2309
+ },
2310
+ children: [
2311
+ /* @__PURE__ */ jsx12(
2312
+ SlideRenderer,
2313
+ {
2314
+ slides,
2315
+ currentSlide,
2316
+ animationStep,
2317
+ totalSteps,
2318
+ direction,
2319
+ showAllAnimations,
2320
+ transition,
2321
+ directionalTransition,
2322
+ onTransitionComplete
2323
+ },
2324
+ editorRevision
2325
+ ),
2326
+ isAnnotationMode && /* @__PURE__ */ jsx12(
2327
+ AnnotationOverlay,
2328
+ {
2329
+ slides,
2330
+ currentSlide,
2331
+ slideContainerRef,
2332
+ selectedId: selectedAnnotationId,
2333
+ hoveredId: hoveredAnnotationId,
2334
+ onSelectId: setSelectedAnnotationId,
2335
+ onShowPanel: () => setShowAnnotationPanel(true),
2336
+ slideAnnotations: getSlideAnnotations(currentSlide),
2337
+ addAnnotation: effectiveAdd ?? (() => {
2338
+ })
2339
+ }
2340
+ )
2341
+ ]
2342
+ }
2343
+ ),
2344
+ /* @__PURE__ */ jsx12(
2345
+ "div",
2346
+ {
2347
+ "data-ps-editor-overlay": true,
2348
+ className: "pointer-events-none absolute inset-0 z-20"
2349
+ }
2350
+ )
2351
+ ]
2352
+ }
2353
+ ) }),
1524
2354
  !isPresentationMode && /* @__PURE__ */ jsxs6("div", { className: "mt-6 flex items-center gap-4", children: [
1525
2355
  /* @__PURE__ */ jsx12(
1526
2356
  "button",
1527
2357
  {
1528
2358
  onClick: goBack,
1529
2359
  className: "rounded-full border border-neutral-800 bg-black/50 p-2 text-neutral-400 backdrop-blur-sm transition-colors hover:bg-neutral-900 hover:text-white",
2360
+ title: "Previous step or slide (\u2190)",
1530
2361
  children: /* @__PURE__ */ jsx12(ChevronLeft, { className: "h-5 w-5" })
1531
2362
  }
1532
2363
  ),
@@ -1540,6 +2371,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1540
2371
  {
1541
2372
  onClick: advance,
1542
2373
  className: "rounded-full border border-neutral-800 bg-black/50 p-2 text-neutral-400 backdrop-blur-sm transition-colors hover:bg-neutral-900 hover:text-white",
2374
+ title: "Next step or slide (\u2192 / Space)",
1543
2375
  children: /* @__PURE__ */ jsx12(ChevronRight, { className: "h-5 w-5" })
1544
2376
  }
1545
2377
  )
@@ -1547,29 +2379,69 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1547
2379
  ]
1548
2380
  }
1549
2381
  ),
1550
- isAnnotationMode && showAnnotationPanel && !isPresentationMode && /* @__PURE__ */ jsx12(
1551
- AnnotationPanel,
2382
+ /* @__PURE__ */ jsx12(
2383
+ "div",
1552
2384
  {
1553
- annotations: getSlideAnnotations(currentSlide),
1554
- selectedId: selectedAnnotationId,
1555
- onSelect: setSelectedAnnotationId,
1556
- onDelete: effectiveDelete ?? (() => {
1557
- }),
1558
- onClose: () => {
1559
- setShowAnnotationPanel(false);
1560
- setSelectedAnnotationId(null);
1561
- }
2385
+ "data-ps-editor-panel": true,
2386
+ "data-open": isEditorOpen,
2387
+ "aria-hidden": !isEditorOpen,
2388
+ inert: !isEditorOpen,
2389
+ className: "h-full shrink-0 overflow-hidden transition-[width] duration-200 ease-out motion-reduce:transition-none",
2390
+ style: { width: isEditorOpen ? editorPanelWidth : 0 }
2391
+ }
2392
+ ),
2393
+ !isPresentationMode && /* @__PURE__ */ jsx12(
2394
+ "div",
2395
+ {
2396
+ "aria-hidden": !isAnnotationMode || !showAnnotationPanel,
2397
+ inert: !isAnnotationMode || !showAnnotationPanel,
2398
+ className: cn(
2399
+ "h-full shrink-0 overflow-hidden transition-[width,transform] duration-200 ease-out motion-reduce:transition-none",
2400
+ isNarrowViewport && "fixed inset-y-0 right-0 z-50 shadow-xl",
2401
+ isNarrowViewport && (!isAnnotationMode || !showAnnotationPanel) && "translate-x-full"
2402
+ ),
2403
+ style: {
2404
+ width: isAnnotationMode && showAnnotationPanel ? isNarrowViewport ? "min(20rem, 90vw)" : "20rem" : 0
2405
+ },
2406
+ children: /* @__PURE__ */ jsx12(
2407
+ "div",
2408
+ {
2409
+ className: cn(
2410
+ "h-full transition-opacity motion-reduce:transition-none",
2411
+ isAnnotationMode && showAnnotationPanel ? "opacity-100 delay-100 duration-100" : "opacity-0 duration-75"
2412
+ ),
2413
+ children: /* @__PURE__ */ jsx12(
2414
+ AnnotationPanel,
2415
+ {
2416
+ annotations: getSlideAnnotations(currentSlide),
2417
+ unlinkedAnnotations,
2418
+ selectedId: selectedAnnotationId,
2419
+ onSelect: setSelectedAnnotationId,
2420
+ onHover: setHoveredAnnotationId,
2421
+ onDelete: effectiveDelete ?? (() => {
2422
+ }),
2423
+ onUpdate: effectiveUpdate ?? (() => {
2424
+ }),
2425
+ onClose: () => {
2426
+ setIsAnnotationMode(false);
2427
+ setShowAnnotationPanel(false);
2428
+ setSelectedAnnotationId(null);
2429
+ setHoveredAnnotationId(null);
2430
+ }
2431
+ }
2432
+ )
2433
+ }
2434
+ )
1562
2435
  }
1563
2436
  )
1564
2437
  ]
1565
2438
  }
1566
2439
  ),
1567
2440
  viewMode === "grid" && /* @__PURE__ */ jsx12("div", { className: "mx-auto max-w-7xl p-8 pt-16 print:hidden", children: /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4", children: slides.map((slideConfig, index) => {
1568
- const SlideComponent = slideConfig.component;
1569
2441
  const prevSection = index > 0 ? slides[index - 1]?.section : void 0;
1570
2442
  const showSectionHeader = slideConfig.section && slideConfig.section !== prevSection;
1571
- return /* @__PURE__ */ jsxs6("div", { className: showSectionHeader ? "col-span-full" : void 0, children: [
1572
- showSectionHeader && /* @__PURE__ */ jsx12("h3", { className: "mt-4 mb-3 text-xs font-bold tracking-[0.2em] text-neutral-500 uppercase first:mt-0", children: slideConfig.section }),
2443
+ return /* @__PURE__ */ jsxs6("div", { className: "contents", children: [
2444
+ showSectionHeader && /* @__PURE__ */ jsx12("h3", { className: "col-span-full mt-4 mb-0 text-xs font-bold tracking-[0.2em] text-neutral-500 uppercase first:mt-0", children: slideConfig.section }),
1573
2445
  /* @__PURE__ */ jsxs6(
1574
2446
  "button",
1575
2447
  {
@@ -1577,30 +2449,15 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1577
2449
  goToSlide(index);
1578
2450
  setViewMode("slide");
1579
2451
  },
1580
- className: "group relative aspect-video w-full overflow-hidden rounded-lg border border-neutral-800 bg-black shadow-sm transition-all hover:border-primary hover:shadow-lg hover:shadow-primary/10",
2452
+ className: "group relative aspect-video w-full overflow-hidden rounded-lg border border-neutral-800 bg-black shadow-sm transition-all hover:border-[#FF6B35] hover:shadow-lg hover:shadow-[#FF6B35]/10",
1581
2453
  children: [
1582
- /* @__PURE__ */ jsx12(
1583
- "div",
1584
- {
1585
- className: "h-full w-full origin-top-left scale-[0.25]",
1586
- style: { width: "400%", height: "400%" },
1587
- children: /* @__PURE__ */ jsx12(
1588
- AnimationProvider,
1589
- {
1590
- currentStep: slideConfig.steps,
1591
- totalSteps: slideConfig.steps,
1592
- showAllAnimations: true,
1593
- children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slideConfig.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: slides.length }) })
1594
- }
1595
- )
1596
- }
1597
- ),
2454
+ /* @__PURE__ */ jsx12(GridThumbnail, { slide: slideConfig, index, total: slides.length }),
1598
2455
  /* @__PURE__ */ jsx12("div", { className: "absolute inset-0 bg-black/0 transition-colors group-hover:bg-black/20" }),
1599
2456
  /* @__PURE__ */ jsx12("div", { className: "absolute bottom-2 left-2 rounded bg-black/70 px-2 py-1 text-xs font-medium text-white", children: slideConfig.title ? `${index + 1}. ${slideConfig.title}` : index + 1 })
1600
2457
  ]
1601
2458
  }
1602
2459
  )
1603
- ] }, index);
2460
+ ] }, slideConfig.id ?? index);
1604
2461
  }) }) }),
1605
2462
  /* @__PURE__ */ jsx12(
1606
2463
  "div",
@@ -1610,25 +2467,15 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1610
2467
  "print:m-0 print:block print:max-w-none print:p-0",
1611
2468
  viewMode === "list" ? "block" : "hidden print:block"
1612
2469
  ),
1613
- children: /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-1 gap-8 print:block", children: slides.map((slideConfig, index) => {
1614
- const SlideComponent = slideConfig.component;
1615
- return /* @__PURE__ */ jsx12(
1616
- "div",
1617
- {
1618
- className: "aspect-video w-full overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-sm print:relative print:m-0 print:h-[1080px] print:w-[1920px] print:break-after-page print:overflow-hidden print:rounded-none print:border-0 print:shadow-none",
1619
- children: /* @__PURE__ */ jsx12("div", { className: "h-full w-full print:h-[720px] print:w-[1280px] print:origin-top-left print:scale-[1.5]", children: /* @__PURE__ */ jsx12(
1620
- AnimationProvider,
1621
- {
1622
- currentStep: slideConfig.steps,
1623
- totalSteps: slideConfig.steps,
1624
- showAllAnimations: true,
1625
- children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slideConfig.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: slides.length }) })
1626
- }
1627
- ) })
1628
- },
1629
- index
1630
- );
1631
- }) })
2470
+ children: /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-1 gap-8 print:block", children: slides.map((slideConfig, index) => /* @__PURE__ */ jsx12(
2471
+ ListSlide,
2472
+ {
2473
+ slide: slideConfig,
2474
+ index,
2475
+ total: slides.length
2476
+ },
2477
+ slideConfig.id ?? index
2478
+ )) })
1632
2479
  }
1633
2480
  )
1634
2481
  ] });