promptslide 0.3.16 → 0.3.17

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") {
@@ -668,19 +752,30 @@ function SlideEmbed({ slides, transition, directionalTransition }) {
668
752
 
669
753
  // src/core/slide-deck.tsx
670
754
  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";
755
+ import {
756
+ ChevronLeft,
757
+ ChevronRight,
758
+ Download,
759
+ Grid3X3,
760
+ List,
761
+ MessageCircle,
762
+ Monitor,
763
+ PanelLeftClose,
764
+ PanelLeftOpen,
765
+ Play
766
+ } from "lucide-react";
767
+ import { useCallback as useCallback5, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo4, useRef as useRef6, useState as useState7 } from "react";
673
768
 
674
769
  // src/core/annotations/annotation-overlay.tsx
675
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
770
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
676
771
 
677
772
  // src/core/annotations/annotation-form.tsx
678
- import { useEffect as useEffect3, useRef, useState as useState3 } from "react";
773
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
679
774
  import { ArrowUp, X } from "lucide-react";
680
775
  import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
681
776
  function AnnotationForm({ xPercent, yPercent, onSubmit, onCancel }) {
682
777
  const [text, setText] = useState3("");
683
- const textareaRef = useRef(null);
778
+ const textareaRef = useRef2(null);
684
779
  useEffect3(() => {
685
780
  textareaRef.current?.focus();
686
781
  }, []);
@@ -829,15 +924,30 @@ function findByTextContent(root, text) {
829
924
 
830
925
  // src/core/annotations/annotation-overlay.tsx
831
926
  import { Fragment, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
832
- function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId, onSelectId, onShowPanel, slideAnnotations, addAnnotation }) {
927
+ function AnnotationOverlay({
928
+ slides,
929
+ currentSlide,
930
+ slideContainerRef,
931
+ selectedId,
932
+ hoveredId,
933
+ onSelectId,
934
+ onShowPanel,
935
+ slideAnnotations,
936
+ addAnnotation
937
+ }) {
833
938
  const [pending, setPending] = useState4(null);
834
939
  const [hoveredElement, setHoveredElement] = useState4(null);
835
- const overlayRef = useRef2(null);
940
+ const overlayRef = useRef3(null);
836
941
  const slideTitle = slides[currentSlide]?.title || `Slide ${currentSlide + 1}`;
837
942
  const resolvedAnnotations = slideAnnotations.map((a, i) => {
838
943
  const container = slideContainerRef.current;
839
944
  if (!container) {
840
- return { annotation: a, xPercent: a.target.position.xPercent, yPercent: a.target.position.yPercent, number: i + 1 };
945
+ return {
946
+ annotation: a,
947
+ xPercent: a.target.position.xPercent,
948
+ yPercent: a.target.position.yPercent,
949
+ number: i + 1
950
+ };
841
951
  }
842
952
  const { element } = resolveTarget(a.target, container);
843
953
  if (element) {
@@ -847,7 +957,12 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
847
957
  const yPercent = (rect.top + rect.height / 2 - containerRect.top) / containerRect.height * 100;
848
958
  return { annotation: a, xPercent, yPercent, number: i + 1 };
849
959
  }
850
- return { annotation: a, xPercent: a.target.position.xPercent, yPercent: a.target.position.yPercent, number: i + 1 };
960
+ return {
961
+ annotation: a,
962
+ xPercent: a.target.position.xPercent,
963
+ yPercent: a.target.position.yPercent,
964
+ number: i + 1
965
+ };
851
966
  });
852
967
  const handleOverlayClick = useCallback3(
853
968
  (e) => {
@@ -881,11 +996,11 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
881
996
  const handleSubmit = useCallback3(
882
997
  (text) => {
883
998
  if (!pending) return;
884
- addAnnotation(currentSlide, slideTitle, pending.target, text);
999
+ addAnnotation(currentSlide, slideTitle, pending.target, text, slides[currentSlide]?.id);
885
1000
  setPending(null);
886
1001
  onShowPanel();
887
1002
  },
888
- [pending, currentSlide, slideTitle, addAnnotation, onShowPanel]
1003
+ [pending, currentSlide, slideTitle, slides, addAnnotation, onShowPanel]
889
1004
  );
890
1005
  const handleMouseMove = useCallback3(
891
1006
  (e) => {
@@ -902,12 +1017,14 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
902
1017
  if (elementUnder && container.contains(elementUnder) && elementUnder !== container) {
903
1018
  const containerRect = container.getBoundingClientRect();
904
1019
  const elRect = elementUnder.getBoundingClientRect();
1020
+ const scaleX = containerRect.width / container.offsetWidth;
1021
+ const scaleY = containerRect.height / container.offsetHeight;
905
1022
  setHoveredElement(
906
1023
  new DOMRect(
907
- elRect.left - containerRect.left,
908
- elRect.top - containerRect.top,
909
- elRect.width,
910
- elRect.height
1024
+ (elRect.left - containerRect.left) / scaleX,
1025
+ (elRect.top - containerRect.top) / scaleY,
1026
+ elRect.width / scaleX,
1027
+ elRect.height / scaleY
911
1028
  )
912
1029
  );
913
1030
  } else {
@@ -941,7 +1058,9 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
941
1058
  role: "button",
942
1059
  tabIndex: 0,
943
1060
  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` },
1061
+ style: {
1062
+ 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`
1063
+ },
945
1064
  onClick: handleOverlayClick,
946
1065
  onKeyDown: (e) => {
947
1066
  if (e.key === "Escape") {
@@ -953,7 +1072,7 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
953
1072
  onMouseLeave: () => setHoveredElement(null)
954
1073
  }
955
1074
  ),
956
- resolvedAnnotations.map(({ annotation, xPercent, yPercent, number }) => /* @__PURE__ */ jsx10(
1075
+ resolvedAnnotations.filter(({ annotation }) => annotation.status === "open" || annotation.id === hoveredId).map(({ annotation, xPercent, yPercent, number }) => /* @__PURE__ */ jsx10(
957
1076
  AnnotationPin,
958
1077
  {
959
1078
  number,
@@ -982,61 +1101,96 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
982
1101
  }
983
1102
 
984
1103
  // src/core/annotations/annotation-panel.tsx
985
- import { MessageCircle, Trash2, X as X2 } from "lucide-react";
1104
+ import { Check, Pencil, PanelRightClose, RotateCcw, Trash2 } from "lucide-react";
1105
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
986
1106
  import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
987
- function AnnotationPanel({ annotations, selectedId, onSelect, onDelete, onClose }) {
1107
+ function AnnotationPanel({
1108
+ annotations,
1109
+ unlinkedAnnotations = [],
1110
+ selectedId,
1111
+ onSelect,
1112
+ onHover,
1113
+ onDelete,
1114
+ onUpdate,
1115
+ onClose
1116
+ }) {
988
1117
  const open = annotations.filter((a) => a.status === "open");
989
1118
  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
- ] }),
1119
+ const total = annotations.length + unlinkedAnnotations.length;
1120
+ const numberFor = (annotation) => annotations.findIndex((a) => a.id === annotation.id) + 1;
1121
+ 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: [
1122
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
997
1123
  /* @__PURE__ */ jsx11(
998
1124
  "button",
999
1125
  {
1000
1126
  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" })
1127
+ className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
1128
+ "aria-label": "Hide comments",
1129
+ title: "Hide comments (C)",
1130
+ children: /* @__PURE__ */ jsx11(PanelRightClose, { className: "h-4 w-4" })
1003
1131
  }
1004
- )
1132
+ ),
1133
+ /* @__PURE__ */ jsxs5("span", { className: "text-right text-sm font-semibold", children: [
1134
+ "Comments (",
1135
+ total,
1136
+ ")"
1137
+ ] })
1005
1138
  ] }),
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, {}) }),
1139
+ /* @__PURE__ */ jsxs5("div", { className: "min-h-0 flex-1 overflow-y-auto p-3", children: [
1140
+ annotations.length === 0 && unlinkedAnnotations.length === 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4 px-3 py-6", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) }),
1009
1141
  open.length > 0 && /* @__PURE__ */ jsxs5("div", { children: [
1010
1142
  /* @__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(
1143
+ open.map((a) => /* @__PURE__ */ jsx11(
1012
1144
  AnnotationItem,
1013
1145
  {
1014
1146
  annotation: a,
1015
- number: i + 1,
1147
+ number: numberFor(a),
1016
1148
  isSelected: a.id === selectedId,
1017
1149
  onSelect: () => onSelect(a.id),
1018
- onDelete: () => onDelete(a.id)
1150
+ onHover: (hovered) => onHover(hovered ? a.id : null),
1151
+ onDelete: () => onDelete(a.id),
1152
+ onUpdate: (patch) => onUpdate(a.id, patch)
1019
1153
  },
1020
1154
  a.id
1021
1155
  ))
1022
1156
  ] }),
1023
1157
  resolved.length > 0 && /* @__PURE__ */ jsxs5("div", { className: open.length > 0 ? "mt-3" : "", children: [
1024
1158
  /* @__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(
1159
+ resolved.map((a) => /* @__PURE__ */ jsx11(
1026
1160
  AnnotationItem,
1027
1161
  {
1028
1162
  annotation: a,
1029
- number: open.length + i + 1,
1163
+ number: numberFor(a),
1030
1164
  isSelected: a.id === selectedId,
1031
1165
  onSelect: () => onSelect(a.id),
1032
- onDelete: () => onDelete(a.id)
1166
+ onHover: (hovered) => onHover(hovered ? a.id : null),
1167
+ onDelete: () => onDelete(a.id),
1168
+ onUpdate: (patch) => onUpdate(a.id, patch)
1033
1169
  },
1034
1170
  a.id
1035
1171
  ))
1172
+ ] }),
1173
+ unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "mt-3", children: [
1174
+ /* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Removed slides" }),
1175
+ unlinkedAnnotations.map((a, i) => /* @__PURE__ */ jsxs5("div", { children: [
1176
+ /* @__PURE__ */ jsx11("p", { className: "px-2 text-[11px] text-neutral-500", children: a.slideTitle || a.slideId }),
1177
+ /* @__PURE__ */ jsx11(
1178
+ AnnotationItem,
1179
+ {
1180
+ annotation: a,
1181
+ number: i + 1,
1182
+ isSelected: a.id === selectedId,
1183
+ onSelect: () => onSelect(a.id),
1184
+ onHover: (hovered) => onHover(hovered ? a.id : null),
1185
+ onDelete: () => onDelete(a.id),
1186
+ onUpdate: (patch) => onUpdate(a.id, patch)
1187
+ }
1188
+ )
1189
+ ] }, a.id))
1036
1190
  ] })
1037
1191
  ] }),
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" }),
1192
+ annotations.length + unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1193
+ /* @__PURE__ */ jsx11("div", { className: "border-t border-neutral-800" }),
1040
1194
  /* @__PURE__ */ jsx11("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) })
1041
1195
  ] })
1042
1196
  ] });
@@ -1046,7 +1200,7 @@ function WorkflowHint() {
1046
1200
  /* @__PURE__ */ jsx11("p", { className: "text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Workflow" }),
1047
1201
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1048
1202
  /* @__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" })
1203
+ /* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: "Comment on everything you want to fix" })
1050
1204
  ] }),
1051
1205
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1052
1206
  /* @__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 +1208,7 @@ function WorkflowHint() {
1054
1208
  ] }),
1055
1209
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1056
1210
  /* @__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"' })
1211
+ /* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: '"Fix open comments"' })
1058
1212
  ] })
1059
1213
  ] });
1060
1214
  }
@@ -1063,30 +1217,81 @@ function AnnotationItem({
1063
1217
  number,
1064
1218
  isSelected,
1065
1219
  onSelect,
1066
- onDelete
1220
+ onHover,
1221
+ onDelete,
1222
+ onUpdate
1067
1223
  }) {
1224
+ const [isEditing, setIsEditing] = useState5(false);
1225
+ const [draft, setDraft] = useState5(annotation.body);
1226
+ const editRef = useRef4(null);
1227
+ useEffect5(() => {
1228
+ if (isEditing) editRef.current?.focus();
1229
+ }, [isEditing]);
1230
+ const saveEdit = () => {
1231
+ const body = draft.trim();
1232
+ if (!body) return;
1233
+ if (body !== annotation.body) onUpdate({ body });
1234
+ setIsEditing(false);
1235
+ };
1068
1236
  return /* @__PURE__ */ jsxs5(
1069
1237
  "div",
1070
1238
  {
1071
1239
  role: "button",
1072
1240
  tabIndex: 0,
1073
1241
  onClick: onSelect,
1242
+ onMouseEnter: () => onHover(true),
1243
+ onMouseLeave: () => onHover(false),
1074
1244
  onKeyDown: (e) => {
1075
- if (e.key === "Enter" || e.key === " ") onSelect();
1245
+ if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) onSelect();
1076
1246
  },
1077
1247
  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
1248
  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
- ),
1249
+ /* @__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: [
1250
+ /* @__PURE__ */ jsx11(
1251
+ "button",
1252
+ {
1253
+ type: "button",
1254
+ title: "Edit comment",
1255
+ "aria-label": "Edit comment",
1256
+ onClick: (e) => {
1257
+ e.stopPropagation();
1258
+ setDraft(annotation.body);
1259
+ setIsEditing(true);
1260
+ },
1261
+ className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
1262
+ children: /* @__PURE__ */ jsx11(Pencil, { className: "h-3.5 w-3.5" })
1263
+ }
1264
+ ),
1265
+ /* @__PURE__ */ jsx11(
1266
+ "button",
1267
+ {
1268
+ type: "button",
1269
+ title: annotation.status === "open" ? "Resolve comment" : "Reopen comment",
1270
+ "aria-label": annotation.status === "open" ? "Resolve comment" : "Reopen comment",
1271
+ onClick: (e) => {
1272
+ e.stopPropagation();
1273
+ onHover(false);
1274
+ onUpdate({ status: annotation.status === "open" ? "resolved" : "open" });
1275
+ },
1276
+ className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
1277
+ children: annotation.status === "open" ? /* @__PURE__ */ jsx11(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx11(RotateCcw, { className: "h-3.5 w-3.5" })
1278
+ }
1279
+ ),
1280
+ /* @__PURE__ */ jsx11(
1281
+ "button",
1282
+ {
1283
+ type: "button",
1284
+ title: "Delete comment",
1285
+ "aria-label": "Delete comment",
1286
+ onClick: (e) => {
1287
+ e.stopPropagation();
1288
+ onDelete();
1289
+ },
1290
+ className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
1291
+ children: /* @__PURE__ */ jsx11(Trash2, { className: "h-3.5 w-3.5" })
1292
+ }
1293
+ )
1294
+ ] }),
1090
1295
  /* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
1091
1296
  /* @__PURE__ */ jsx11(
1092
1297
  "div",
@@ -1095,8 +1300,52 @@ function AnnotationItem({
1095
1300
  children: number
1096
1301
  }
1097
1302
  ),
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 }),
1303
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1 pr-4", children: [
1304
+ isEditing ? /* @__PURE__ */ jsxs5("div", { children: [
1305
+ /* @__PURE__ */ jsx11(
1306
+ "textarea",
1307
+ {
1308
+ ref: editRef,
1309
+ value: draft,
1310
+ onClick: (e) => e.stopPropagation(),
1311
+ onChange: (e) => setDraft(e.target.value),
1312
+ onKeyDown: (e) => {
1313
+ if (e.key === "Escape") setIsEditing(false);
1314
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") saveEdit();
1315
+ },
1316
+ className: "w-full resize-y rounded border border-neutral-700 bg-neutral-900 p-2 text-[13px] text-white outline-none focus:border-[#FF6B35]",
1317
+ rows: 3,
1318
+ maxLength: 4096
1319
+ }
1320
+ ),
1321
+ /* @__PURE__ */ jsxs5("div", { className: "mt-1 flex gap-2 text-xs", children: [
1322
+ /* @__PURE__ */ jsx11(
1323
+ "button",
1324
+ {
1325
+ type: "button",
1326
+ onClick: (e) => {
1327
+ e.stopPropagation();
1328
+ saveEdit();
1329
+ },
1330
+ disabled: !draft.trim(),
1331
+ className: "text-[#FF6B35] disabled:opacity-40",
1332
+ children: "Save"
1333
+ }
1334
+ ),
1335
+ /* @__PURE__ */ jsx11(
1336
+ "button",
1337
+ {
1338
+ type: "button",
1339
+ onClick: (e) => {
1340
+ e.stopPropagation();
1341
+ setIsEditing(false);
1342
+ },
1343
+ className: "text-neutral-400",
1344
+ children: "Cancel"
1345
+ }
1346
+ )
1347
+ ] })
1348
+ ] }) : /* @__PURE__ */ jsx11("p", { className: "text-[13px] leading-relaxed text-neutral-200", children: annotation.body }),
1100
1349
  annotation.target.contentNearPin && /* @__PURE__ */ jsx11("p", { className: "mt-1 truncate text-[11px] text-neutral-600", children: annotation.target.contentNearPin }),
1101
1350
  annotation.resolution && /* @__PURE__ */ jsx11("p", { className: "mt-1.5 text-[11px] text-emerald-400/80 italic", children: annotation.resolution })
1102
1351
  ] })
@@ -1107,7 +1356,7 @@ function AnnotationItem({
1107
1356
  }
1108
1357
 
1109
1358
  // 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";
1359
+ import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
1111
1360
 
1112
1361
  // src/core/annotations/adapters/http.ts
1113
1362
  var ENDPOINT = "/__promptslide_annotations";
@@ -1143,23 +1392,47 @@ function createHttpAdapter() {
1143
1392
  async remove(id) {
1144
1393
  cache = cache.filter((a) => a.id !== id);
1145
1394
  await saveAll(cache);
1395
+ },
1396
+ async update(id, patch) {
1397
+ cache = cache.map((a) => a.id === id ? { ...a, ...patch } : a);
1398
+ await saveAll(cache);
1399
+ },
1400
+ async replaceAll(annotations) {
1401
+ cache = annotations;
1402
+ await saveAll(cache);
1146
1403
  }
1147
1404
  };
1148
1405
  }
1149
1406
 
1150
1407
  // 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);
1408
+ function useAnnotations(slidesOrAdapter, adapter) {
1409
+ const slides = Array.isArray(slidesOrAdapter) ? slidesOrAdapter : void 0;
1410
+ const storage = Array.isArray(slidesOrAdapter) ? adapter : slidesOrAdapter ?? adapter;
1411
+ const adapterRef = useRef5(storage ?? createHttpAdapter());
1412
+ const slidesRef = useRef5(slides);
1413
+ slidesRef.current = slides;
1414
+ const [annotations, setAnnotations] = useState6([]);
1415
+ useEffect6(() => {
1416
+ adapterRef.current.load().then((loaded) => {
1417
+ const configuredSlides = slidesRef.current;
1418
+ const migrated = loaded.map((annotation) => {
1419
+ if (annotation.slideId || !configuredSlides) return annotation;
1420
+ const id = configuredSlides[annotation.slideIndex]?.id;
1421
+ return id ? { ...annotation, slideId: id } : annotation;
1422
+ });
1423
+ setAnnotations(migrated);
1424
+ if (migrated.some((annotation, index) => annotation !== loaded[index])) {
1425
+ void adapterRef.current.replaceAll?.(migrated);
1426
+ }
1427
+ });
1156
1428
  return adapterRef.current.subscribe?.(setAnnotations);
1157
1429
  }, []);
1158
1430
  const addAnnotation = useCallback4(
1159
- (slideIndex, slideTitle, target, body) => {
1431
+ (slideIndex, slideTitle, target, body, slideId) => {
1160
1432
  const annotation = {
1161
1433
  id: crypto.randomUUID(),
1162
1434
  slideIndex,
1435
+ ...slideId && { slideId },
1163
1436
  slideTitle,
1164
1437
  target,
1165
1438
  body,
@@ -1175,15 +1448,36 @@ function useAnnotations(adapter) {
1175
1448
  setAnnotations((prev) => prev.filter((a) => a.id !== id));
1176
1449
  adapterRef.current.remove(id);
1177
1450
  }, []);
1451
+ const updateAnnotation = useCallback4(
1452
+ (id, patch) => {
1453
+ const current = annotations.find((a) => a.id === id);
1454
+ if (!current) return;
1455
+ const updated = { body: patch.body ?? current.body, status: patch.status ?? current.status };
1456
+ setAnnotations((prev) => prev.map((a) => a.id === id ? { ...a, ...updated } : a));
1457
+ void adapterRef.current.update?.(id, updated);
1458
+ },
1459
+ [annotations]
1460
+ );
1178
1461
  const getSlideAnnotations = useCallback4(
1179
1462
  (slideIndex) => annotations.filter((a) => a.slideIndex === slideIndex),
1180
1463
  [annotations]
1181
1464
  );
1182
- const openCount = useMemo3(() => annotations.filter((a) => a.status === "open").length, [annotations]);
1465
+ const openCount = useMemo3(
1466
+ () => annotations.filter((a) => a.status === "open").length,
1467
+ [annotations]
1468
+ );
1183
1469
  const updateAnnotations = useCallback4((updated) => {
1184
1470
  setAnnotations(updated);
1185
1471
  }, []);
1186
- return { annotations, addAnnotation, deleteAnnotation, getSlideAnnotations, openCount, updateAnnotations };
1472
+ return {
1473
+ annotations,
1474
+ addAnnotation,
1475
+ deleteAnnotation,
1476
+ updateAnnotation,
1477
+ getSlideAnnotations,
1478
+ openCount,
1479
+ updateAnnotations
1480
+ };
1187
1481
  }
1188
1482
 
1189
1483
  // src/core/utils.ts
@@ -1194,14 +1488,96 @@ function cn(...inputs) {
1194
1488
  }
1195
1489
 
1196
1490
  // src/core/slide-deck.tsx
1197
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1491
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1492
+ var NARROW_VIEWPORT_WIDTH = 768;
1493
+ var DEFAULT_THUMBNAIL_WIDTH = 240;
1494
+ var MIN_THUMBNAIL_WIDTH = 176;
1495
+ var MAX_THUMBNAIL_WIDTH = 480;
1496
+ var THUMBNAIL_WIDTH_STORAGE_KEY = "promptslide:thumbnail-width";
1497
+ function GridThumbnail({
1498
+ slide,
1499
+ index,
1500
+ total
1501
+ }) {
1502
+ const viewportRef = useRef6(null);
1503
+ const canvasRef = useRef6(null);
1504
+ const SlideComponent = slide.component;
1505
+ useLayoutEffect(() => {
1506
+ const viewport = viewportRef.current;
1507
+ const canvas = canvasRef.current;
1508
+ if (!viewport || !canvas) return;
1509
+ const resize = () => {
1510
+ canvas.style.transform = `scale(${viewport.clientWidth / SLIDE_DIMENSIONS.width})`;
1511
+ };
1512
+ resize();
1513
+ const observer = new ResizeObserver(resize);
1514
+ observer.observe(viewport);
1515
+ return () => observer.disconnect();
1516
+ }, []);
1517
+ return /* @__PURE__ */ jsx12("div", { ref: viewportRef, className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx12(
1518
+ "div",
1519
+ {
1520
+ ref: canvasRef,
1521
+ className: "absolute top-0 left-0 origin-top-left",
1522
+ style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
1523
+ 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 }) }) })
1524
+ }
1525
+ ) });
1526
+ }
1527
+ function ListSlide({ slide, index, total }) {
1528
+ const viewportRef = useRef6(null);
1529
+ const canvasRef = useRef6(null);
1530
+ const SlideComponent = slide.component;
1531
+ useLayoutEffect(() => {
1532
+ const viewport = viewportRef.current;
1533
+ const canvas = canvasRef.current;
1534
+ if (!viewport || !canvas) return;
1535
+ const resize = () => {
1536
+ canvas.style.transform = `scale(${viewport.clientWidth / SLIDE_DIMENSIONS.width})`;
1537
+ };
1538
+ resize();
1539
+ const observer = new ResizeObserver(resize);
1540
+ observer.observe(viewport);
1541
+ return () => observer.disconnect();
1542
+ }, []);
1543
+ return /* @__PURE__ */ jsx12(
1544
+ "div",
1545
+ {
1546
+ ref: viewportRef,
1547
+ 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",
1548
+ children: /* @__PURE__ */ jsx12(
1549
+ "div",
1550
+ {
1551
+ ref: canvasRef,
1552
+ className: "list-slide-canvas absolute top-0 left-0 origin-top-left",
1553
+ style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
1554
+ 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 }) }) })
1555
+ }
1556
+ )
1557
+ }
1558
+ );
1559
+ }
1198
1560
  function SlideExportView({ slides, slideIndex }) {
1199
- const [ready, setReady] = useState6(false);
1561
+ const [ready, setReady] = useState7(false);
1200
1562
  const clampedIndex = Math.max(0, Math.min(slideIndex, slides.length - 1));
1201
1563
  const slideConfig = slides[clampedIndex];
1202
1564
  const SlideComponent = slideConfig.component;
1203
- useEffect6(() => {
1204
- setReady(true);
1565
+ useEffect7(() => {
1566
+ let cancelled = false;
1567
+ const markReady = async () => {
1568
+ await document.fonts.ready;
1569
+ await Promise.all(Array.from(document.images, (image) => image.decode().catch(() => {
1570
+ })));
1571
+ requestAnimationFrame(
1572
+ () => requestAnimationFrame(() => {
1573
+ if (!cancelled) setReady(true);
1574
+ })
1575
+ );
1576
+ };
1577
+ void markReady();
1578
+ return () => {
1579
+ cancelled = true;
1580
+ };
1205
1581
  }, []);
1206
1582
  return /* @__PURE__ */ jsx12(
1207
1583
  "div",
@@ -1226,8 +1602,16 @@ function SlideExportView({ slides, slideIndex }) {
1226
1602
  }
1227
1603
  );
1228
1604
  }
1229
- function SlideDeck({ slides, transition, directionalTransition, annotations, onAnnotationAdd, onAnnotationDelete }) {
1230
- const [exportParams] = useState6(() => {
1605
+ function SlideDeck({
1606
+ slides,
1607
+ transition,
1608
+ directionalTransition,
1609
+ annotations,
1610
+ onAnnotationAdd,
1611
+ onAnnotationDelete,
1612
+ onAnnotationUpdate
1613
+ }) {
1614
+ const [exportParams] = useState7(() => {
1231
1615
  if (typeof window === "undefined") return null;
1232
1616
  const params = new URLSearchParams(window.location.search);
1233
1617
  if (params.get("export") !== "true") return null;
@@ -1236,24 +1620,158 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1236
1620
  if (exportParams) {
1237
1621
  return /* @__PURE__ */ jsx12(SlideExportView, { slides, slideIndex: exportParams.slideIndex });
1238
1622
  }
1239
- const internal = useAnnotations();
1623
+ const internal = useAnnotations(slides);
1240
1624
  const isExternallyManaged = annotations !== void 0;
1241
1625
  const effectiveAnnotations = isExternallyManaged ? annotations : internal.annotations;
1242
1626
  const effectiveAdd = isExternallyManaged ? onAnnotationAdd : internal.addAnnotation;
1243
1627
  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),
1628
+ const effectiveUpdate = isExternallyManaged ? onAnnotationUpdate : internal.updateAnnotation;
1629
+ const openCount = useMemo4(
1630
+ () => effectiveAnnotations.filter((a) => a.status === "open").length,
1247
1631
  [effectiveAnnotations]
1248
1632
  );
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);
1633
+ const getSlideAnnotations = useCallback5(
1634
+ (slideIndex) => effectiveAnnotations.filter(
1635
+ (a) => a.slideId ? a.slideId === slides[slideIndex]?.id : a.slideIndex === slideIndex
1636
+ ),
1637
+ [effectiveAnnotations, slides]
1638
+ );
1639
+ const unlinkedAnnotations = useMemo4(
1640
+ () => effectiveAnnotations.filter((a) => a.slideId && !slides.some((slide) => slide.id === a.slideId)),
1641
+ [effectiveAnnotations, slides]
1642
+ );
1643
+ const [viewMode, setViewMode] = useState7("slide");
1644
+ const [isPresentationMode, setIsPresentationMode] = useState7(false);
1645
+ const [viewportWidth, setViewportWidth] = useState7(
1646
+ () => typeof window === "undefined" ? 1280 : window.innerWidth
1647
+ );
1648
+ const [showThumbnailRail, setShowThumbnailRail] = useState7(
1649
+ () => typeof window === "undefined" ? true : window.innerWidth >= NARROW_VIEWPORT_WIDTH
1650
+ );
1651
+ const [thumbnailWidth, setThumbnailWidth] = useState7(() => {
1652
+ if (typeof window === "undefined") return DEFAULT_THUMBNAIL_WIDTH;
1653
+ const savedWidth = Number(window.localStorage.getItem(THUMBNAIL_WIDTH_STORAGE_KEY));
1654
+ return Number.isFinite(savedWidth) && savedWidth >= MIN_THUMBNAIL_WIDTH ? Math.min(savedWidth, MAX_THUMBNAIL_WIDTH) : DEFAULT_THUMBNAIL_WIDTH;
1655
+ });
1656
+ const [isAnnotationMode, setIsAnnotationMode] = useState7(false);
1657
+ const [showAnnotationPanel, setShowAnnotationPanel] = useState7(false);
1658
+ const [selectedAnnotationId, setSelectedAnnotationId] = useState7(null);
1659
+ const [hoveredAnnotationId, setHoveredAnnotationId] = useState7(null);
1660
+ const [isResizingThumbnails, setIsResizingThumbnails] = useState7(false);
1661
+ const [isDragCollapsed, setIsDragCollapsed] = useState7(false);
1662
+ const [isAnimatingDragBoundary, setIsAnimatingDragBoundary] = useState7(false);
1663
+ const [scale, setScale] = useState7(1);
1664
+ const containerRef = useRef6(null);
1665
+ const slideContainerRef = useRef6(null);
1666
+ const previewViewportRef = useRef6(null);
1667
+ const activeThumbnailRef = useRef6(null);
1668
+ const preferredRailOpenRef = useRef6(true);
1669
+ const wasNarrowViewportRef = useRef6(viewportWidth < NARROW_VIEWPORT_WIDTH);
1670
+ const resizeStartRef = useRef6(null);
1671
+ const previousUserSelectRef = useRef6("");
1672
+ const previousCursorRef = useRef6("");
1673
+ const dragCollapsedRef = useRef6(false);
1674
+ const dragAnimationTimerRef = useRef6(null);
1675
+ const isNarrowViewport = viewportWidth < NARROW_VIEWPORT_WIDTH;
1676
+ const maxThumbnailWidth = isNarrowViewport ? Math.max(120, Math.floor(viewportWidth * 0.8)) : Math.min(MAX_THUMBNAIL_WIDTH, viewportWidth - 360);
1677
+ const minThumbnailWidth = Math.min(MIN_THUMBNAIL_WIDTH, maxThumbnailWidth);
1678
+ const visibleThumbnailWidth = Math.min(thumbnailWidth, maxThumbnailWidth);
1679
+ const isThumbnailRailVisible = showThumbnailRail && !isDragCollapsed;
1680
+ const setThumbnailRailOpen = useCallback5((open) => {
1681
+ preferredRailOpenRef.current = open;
1682
+ setShowThumbnailRail(open);
1683
+ }, []);
1684
+ useEffect7(() => {
1685
+ const handleResize = () => setViewportWidth(window.innerWidth);
1686
+ window.addEventListener("resize", handleResize);
1687
+ return () => window.removeEventListener("resize", handleResize);
1688
+ }, []);
1689
+ useEffect7(() => {
1690
+ if (isNarrowViewport !== wasNarrowViewportRef.current) {
1691
+ setShowThumbnailRail(isNarrowViewport ? false : preferredRailOpenRef.current);
1692
+ if (isNarrowViewport) {
1693
+ setIsAnnotationMode(false);
1694
+ setShowAnnotationPanel(false);
1695
+ setSelectedAnnotationId(null);
1696
+ }
1697
+ wasNarrowViewportRef.current = isNarrowViewport;
1698
+ }
1699
+ }, [isNarrowViewport]);
1700
+ useEffect7(() => {
1701
+ window.localStorage.setItem(THUMBNAIL_WIDTH_STORAGE_KEY, String(thumbnailWidth));
1702
+ }, [thumbnailWidth]);
1703
+ useEffect7(
1704
+ () => () => {
1705
+ if (resizeStartRef.current) {
1706
+ document.body.style.userSelect = previousUserSelectRef.current;
1707
+ document.body.style.cursor = previousCursorRef.current;
1708
+ }
1709
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
1710
+ },
1711
+ []
1712
+ );
1713
+ useEffect7(() => {
1714
+ if ((!showThumbnailRail || isPresentationMode || viewMode !== "slide") && resizeStartRef.current) {
1715
+ resizeStartRef.current = null;
1716
+ dragCollapsedRef.current = false;
1717
+ setIsResizingThumbnails(false);
1718
+ setIsDragCollapsed(false);
1719
+ setIsAnimatingDragBoundary(false);
1720
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
1721
+ document.body.style.userSelect = previousUserSelectRef.current;
1722
+ document.body.style.cursor = previousCursorRef.current;
1723
+ }
1724
+ }, [showThumbnailRail, isPresentationMode, viewMode]);
1725
+ useEffect7(() => {
1726
+ if (!isResizingThumbnails) return;
1727
+ const getDraggedWidth = (clientX) => {
1728
+ const start = resizeStartRef.current;
1729
+ return start ? start.width + clientX - start.pointerX : null;
1730
+ };
1731
+ const handlePointerMove = (event) => {
1732
+ if (event.pointerId !== resizeStartRef.current?.pointerId) return;
1733
+ const nextWidth = getDraggedWidth(event.clientX);
1734
+ if (nextWidth === null) return;
1735
+ const collapsed = nextWidth < minThumbnailWidth;
1736
+ if (collapsed !== dragCollapsedRef.current) {
1737
+ dragCollapsedRef.current = collapsed;
1738
+ setIsDragCollapsed(collapsed);
1739
+ setIsAnimatingDragBoundary(true);
1740
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
1741
+ dragAnimationTimerRef.current = setTimeout(() => {
1742
+ setIsAnimatingDragBoundary(false);
1743
+ dragAnimationTimerRef.current = null;
1744
+ }, 200);
1745
+ }
1746
+ if (!collapsed) setThumbnailWidth(Math.min(maxThumbnailWidth, nextWidth));
1747
+ };
1748
+ const stopResize = (event) => {
1749
+ if (event && event.pointerId !== resizeStartRef.current?.pointerId) return;
1750
+ const nextWidth = event ? getDraggedWidth(event.clientX) : null;
1751
+ const collapsed = event?.type === "pointerup" && nextWidth !== null && nextWidth < minThumbnailWidth;
1752
+ resizeStartRef.current = null;
1753
+ dragCollapsedRef.current = false;
1754
+ document.body.style.userSelect = previousUserSelectRef.current;
1755
+ document.body.style.cursor = previousCursorRef.current;
1756
+ setIsResizingThumbnails(false);
1757
+ setIsDragCollapsed(false);
1758
+ if (collapsed) setThumbnailRailOpen(false);
1759
+ else if (nextWidth !== null) {
1760
+ setThumbnailWidth(Math.max(minThumbnailWidth, Math.min(maxThumbnailWidth, nextWidth)));
1761
+ }
1762
+ };
1763
+ const handleBlur = () => stopResize();
1764
+ window.addEventListener("pointermove", handlePointerMove);
1765
+ window.addEventListener("pointerup", stopResize);
1766
+ window.addEventListener("pointercancel", stopResize);
1767
+ window.addEventListener("blur", handleBlur);
1768
+ return () => {
1769
+ window.removeEventListener("pointermove", handlePointerMove);
1770
+ window.removeEventListener("pointerup", stopResize);
1771
+ window.removeEventListener("pointercancel", stopResize);
1772
+ window.removeEventListener("blur", handleBlur);
1773
+ };
1774
+ }, [isResizingThumbnails, minThumbnailWidth, maxThumbnailWidth, setThumbnailRailOpen]);
1257
1775
  const {
1258
1776
  currentSlide,
1259
1777
  animationStep,
@@ -1274,22 +1792,27 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1274
1792
  await document.exitFullscreen();
1275
1793
  }
1276
1794
  }, []);
1277
- useEffect6(() => {
1795
+ useEffect7(() => {
1278
1796
  const handleFullscreenChange = () => {
1279
1797
  setIsPresentationMode(!!document.fullscreenElement);
1280
1798
  };
1281
1799
  document.addEventListener("fullscreenchange", handleFullscreenChange);
1282
1800
  return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
1283
1801
  }, []);
1284
- useEffect6(() => {
1802
+ useEffect7(() => {
1803
+ if (viewMode === "slide" && !isPresentationMode && showThumbnailRail) {
1804
+ activeThumbnailRef.current?.scrollIntoView({ block: "nearest" });
1805
+ }
1806
+ }, [currentSlide, isPresentationMode, showThumbnailRail, viewMode]);
1807
+ useEffect7(() => {
1285
1808
  const calculateScale = () => {
1286
1809
  if (!isPresentationMode) {
1287
1810
  setScale(1);
1288
1811
  return;
1289
1812
  }
1290
- const viewportWidth = window.innerWidth;
1813
+ const availableWidth = window.innerWidth;
1291
1814
  const viewportHeight = window.innerHeight;
1292
- const scaleX = viewportWidth / SLIDE_DIMENSIONS.width;
1815
+ const scaleX = availableWidth / SLIDE_DIMENSIONS.width;
1293
1816
  const scaleY = viewportHeight / SLIDE_DIMENSIONS.height;
1294
1817
  setScale(Math.min(scaleX, scaleY));
1295
1818
  };
@@ -1297,7 +1820,46 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1297
1820
  window.addEventListener("resize", calculateScale);
1298
1821
  return () => window.removeEventListener("resize", calculateScale);
1299
1822
  }, [isPresentationMode]);
1300
- const handleExportPdf = () => {
1823
+ useLayoutEffect(() => {
1824
+ const viewport = previewViewportRef.current;
1825
+ const canvas = slideContainerRef.current;
1826
+ if (!viewport || !canvas) return;
1827
+ const resize = (width, height) => {
1828
+ const fitScale = Math.min(width / SLIDE_DIMENSIONS.width, height / SLIDE_DIMENSIONS.height);
1829
+ canvas.style.transform = `scale(${fitScale})`;
1830
+ };
1831
+ resize(viewport.clientWidth, viewport.clientHeight);
1832
+ const observer = new ResizeObserver(([entry]) => {
1833
+ if (!entry) return;
1834
+ resize(entry.contentRect.width, entry.contentRect.height);
1835
+ });
1836
+ observer.observe(viewport);
1837
+ return () => observer.disconnect();
1838
+ }, [viewMode, isPresentationMode]);
1839
+ const toggleCommentMode = useCallback5(() => {
1840
+ const next = !isAnnotationMode;
1841
+ setIsAnnotationMode(next);
1842
+ setShowAnnotationPanel(next);
1843
+ if (!next) {
1844
+ setSelectedAnnotationId(null);
1845
+ setHoveredAnnotationId(null);
1846
+ }
1847
+ }, [isAnnotationMode]);
1848
+ const handleExportPdf = useCallback5(async () => {
1849
+ try {
1850
+ const response = await fetch("/__promptslide_pdf");
1851
+ if (response.ok && response.headers.get("content-type")?.includes("application/pdf")) {
1852
+ const blob = await response.blob();
1853
+ const url = URL.createObjectURL(blob);
1854
+ const link = document.createElement("a");
1855
+ link.href = url;
1856
+ link.download = "slides.pdf";
1857
+ link.click();
1858
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
1859
+ return;
1860
+ }
1861
+ } catch {
1862
+ }
1301
1863
  const previousMode = viewMode;
1302
1864
  setViewMode("list");
1303
1865
  setTimeout(() => {
@@ -1308,17 +1870,49 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1308
1870
  window.addEventListener("afterprint", handleAfterPrint);
1309
1871
  window.print();
1310
1872
  }, 100);
1311
- };
1312
- useEffect6(() => {
1873
+ }, [viewMode]);
1874
+ useEffect7(() => {
1313
1875
  const handleKeyDown = (e) => {
1314
- if (e.key === "f" || e.key === "F") {
1315
- togglePresentationMode();
1876
+ const target = e.target;
1877
+ if (target?.closest("input, textarea, [contenteditable='true']")) return;
1878
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
1879
+ const key = e.key.toLowerCase();
1880
+ if (e.repeat && "fvglcdt".includes(key)) return;
1881
+ if (key === "f") {
1882
+ e.preventDefault();
1883
+ void togglePresentationMode();
1884
+ return;
1885
+ }
1886
+ if (key === "v" && !isPresentationMode) {
1887
+ e.preventDefault();
1888
+ setViewMode("slide");
1316
1889
  return;
1317
1890
  }
1318
- if (e.key === "g" || e.key === "G") {
1891
+ if (key === "g") {
1892
+ e.preventDefault();
1319
1893
  setViewMode((prev) => prev === "grid" ? "slide" : "grid");
1320
1894
  return;
1321
1895
  }
1896
+ if (key === "l") {
1897
+ e.preventDefault();
1898
+ setViewMode((prev) => prev === "list" ? "slide" : "list");
1899
+ return;
1900
+ }
1901
+ if (key === "c" && !isPresentationMode) {
1902
+ e.preventDefault();
1903
+ toggleCommentMode();
1904
+ return;
1905
+ }
1906
+ if (key === "d" && !isPresentationMode) {
1907
+ e.preventDefault();
1908
+ void handleExportPdf();
1909
+ return;
1910
+ }
1911
+ if (viewMode === "slide" && !isPresentationMode && key === "t") {
1912
+ e.preventDefault();
1913
+ setThumbnailRailOpen(!showThumbnailRail);
1914
+ return;
1915
+ }
1322
1916
  if (viewMode !== "slide" || isAnnotationMode && !isPresentationMode) return;
1323
1917
  if (e.key === "ArrowRight" || e.key === " ") {
1324
1918
  e.preventDefault();
@@ -1330,10 +1924,24 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1330
1924
  };
1331
1925
  window.addEventListener("keydown", handleKeyDown);
1332
1926
  return () => window.removeEventListener("keydown", handleKeyDown);
1333
- }, [advance, goBack, viewMode, togglePresentationMode, isAnnotationMode, isPresentationMode]);
1927
+ }, [
1928
+ advance,
1929
+ goBack,
1930
+ viewMode,
1931
+ togglePresentationMode,
1932
+ isAnnotationMode,
1933
+ isPresentationMode,
1934
+ setThumbnailRailOpen,
1935
+ showThumbnailRail,
1936
+ toggleCommentMode,
1937
+ handleExportPdf
1938
+ ]);
1334
1939
  return /* @__PURE__ */ jsxs6("div", { className: "min-h-screen w-full bg-neutral-950 text-foreground", children: [
1335
1940
  /* @__PURE__ */ jsx12("style", { children: `
1336
1941
  @media print {
1942
+ .list-slide-canvas {
1943
+ transform: scale(1.5) !important;
1944
+ }
1337
1945
  @page {
1338
1946
  size: 1920px 1080px;
1339
1947
  margin: 0;
@@ -1357,9 +1965,9 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1357
1965
  "div",
1358
1966
  {
1359
1967
  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"
1968
+ "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",
1969
+ (isPresentationMode || isNarrowViewport && isAnnotationMode && showAnnotationPanel) && "hidden",
1970
+ isAnnotationMode && showAnnotationPanel ? "right-[21rem]" : "right-4"
1363
1971
  ),
1364
1972
  children: [
1365
1973
  /* @__PURE__ */ jsx12(
@@ -1370,7 +1978,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1370
1978
  "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1371
1979
  viewMode === "slide" && "bg-neutral-800 text-white"
1372
1980
  ),
1373
- title: "Presentation View",
1981
+ title: "Presentation View (V)",
1374
1982
  children: /* @__PURE__ */ jsx12(Monitor, { className: "h-4 w-4" })
1375
1983
  }
1376
1984
  ),
@@ -1382,7 +1990,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1382
1990
  "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1383
1991
  viewMode === "list" && "bg-neutral-800 text-white"
1384
1992
  ),
1385
- title: "List View",
1993
+ title: "List View (L)",
1386
1994
  children: /* @__PURE__ */ jsx12(List, { className: "h-4 w-4" })
1387
1995
  }
1388
1996
  ),
@@ -1394,7 +2002,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1394
2002
  "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1395
2003
  viewMode === "grid" && "bg-neutral-800 text-white"
1396
2004
  ),
1397
- title: "Grid View",
2005
+ title: "Grid View (G)",
1398
2006
  children: /* @__PURE__ */ jsx12(Grid3X3, { className: "h-4 w-4" })
1399
2007
  }
1400
2008
  ),
@@ -1402,21 +2010,14 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1402
2010
  /* @__PURE__ */ jsxs6(
1403
2011
  "button",
1404
2012
  {
1405
- onClick: () => {
1406
- setIsAnnotationMode((prev) => {
1407
- const next = !prev;
1408
- setShowAnnotationPanel(next);
1409
- if (!next) setSelectedAnnotationId(null);
1410
- return next;
1411
- });
1412
- },
2013
+ onClick: toggleCommentMode,
1413
2014
  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]"
2015
+ "relative inline-flex h-8 items-center justify-center rounded-md transition-colors",
2016
+ isAnnotationMode ? "border border-[#FF6B35]/50 bg-[#FF6B35]/15 p-[7px] text-[#FF6B35] hover:border-[#FF6B35] hover:bg-[#FF6B35]/25" : "p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white"
1416
2017
  ),
1417
- title: "Annotate slides",
2018
+ title: "Comment (C)",
1418
2019
  children: [
1419
- /* @__PURE__ */ jsx12(MessageCircle2, { className: "h-4 w-4" }),
2020
+ /* @__PURE__ */ jsx12(MessageCircle, { className: "h-4 w-4" }),
1420
2021
  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
2022
  ]
1422
2023
  }
@@ -1427,17 +2028,20 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1427
2028
  {
1428
2029
  onClick: handleExportPdf,
1429
2030
  className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
1430
- title: "Download PDF",
2031
+ title: "Download PDF (D)",
1431
2032
  children: /* @__PURE__ */ jsx12(Download, { className: "h-4 w-4" })
1432
2033
  }
1433
2034
  ),
1434
- /* @__PURE__ */ jsx12(
2035
+ /* @__PURE__ */ jsxs6(
1435
2036
  "button",
1436
2037
  {
1437
2038
  onClick: togglePresentationMode,
1438
- className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
2039
+ 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",
1439
2040
  title: "Present (F)",
1440
- children: /* @__PURE__ */ jsx12(Maximize, { className: "h-4 w-4" })
2041
+ children: [
2042
+ /* @__PURE__ */ jsx12(Play, { className: "h-3.5 w-3.5 fill-current" }),
2043
+ /* @__PURE__ */ jsx12("span", { children: "Present" })
2044
+ ]
1441
2045
  }
1442
2046
  )
1443
2047
  ]
@@ -1446,11 +2050,157 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1446
2050
  viewMode === "slide" && /* @__PURE__ */ jsxs6(
1447
2051
  "div",
1448
2052
  {
1449
- className: cn(
1450
- "flex h-screen w-full print:hidden",
1451
- isPresentationMode ? "bg-black" : ""
1452
- ),
2053
+ className: cn("flex h-screen w-full print:hidden", isPresentationMode ? "bg-black" : ""),
1453
2054
  children: [
2055
+ !isPresentationMode && /* @__PURE__ */ jsxs6(Fragment3, { children: [
2056
+ /* @__PURE__ */ jsxs6(
2057
+ "div",
2058
+ {
2059
+ className: cn(
2060
+ "relative z-10 h-screen shrink-0",
2061
+ isNarrowViewport && "fixed inset-y-0 left-0",
2062
+ isResizingThumbnails && !isAnimatingDragBoundary ? "transition-none" : "transition-[width] duration-200 ease-out motion-reduce:transition-none"
2063
+ ),
2064
+ style: { width: isThumbnailRailVisible ? visibleThumbnailWidth : 0 },
2065
+ children: [
2066
+ /* @__PURE__ */ jsx12("div", { className: "h-full w-full overflow-hidden", children: /* @__PURE__ */ jsxs6(
2067
+ "aside",
2068
+ {
2069
+ id: "presentation-thumbnails",
2070
+ "aria-label": "Slide thumbnails",
2071
+ "aria-hidden": !isThumbnailRailVisible,
2072
+ inert: !isThumbnailRailVisible,
2073
+ className: cn(
2074
+ "flex h-screen shrink-0 flex-col border-r border-neutral-800 bg-neutral-950 text-white transition-opacity motion-reduce:transition-none",
2075
+ isThumbnailRailVisible ? "opacity-100 delay-100 duration-100" : "opacity-0 duration-75"
2076
+ ),
2077
+ style: { width: visibleThumbnailWidth },
2078
+ children: [
2079
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
2080
+ /* @__PURE__ */ jsxs6("span", { className: "text-sm font-semibold", children: [
2081
+ "Slides (",
2082
+ slides.length,
2083
+ ")"
2084
+ ] }),
2085
+ /* @__PURE__ */ jsx12(
2086
+ "button",
2087
+ {
2088
+ type: "button",
2089
+ onClick: () => setThumbnailRailOpen(false),
2090
+ className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
2091
+ "aria-label": "Hide slide thumbnails",
2092
+ title: "Hide thumbnails (T)",
2093
+ children: /* @__PURE__ */ jsx12(PanelLeftClose, { className: "h-4 w-4" })
2094
+ }
2095
+ )
2096
+ ] }),
2097
+ /* @__PURE__ */ jsx12("div", { className: "min-h-0 flex-1 space-y-3 overflow-y-auto p-3", children: slides.map((slideConfig, index) => /* @__PURE__ */ jsxs6(
2098
+ "button",
2099
+ {
2100
+ ref: index === currentSlide ? activeThumbnailRef : void 0,
2101
+ type: "button",
2102
+ onClick: () => goToSlide(index),
2103
+ "aria-label": `Go to slide ${index + 1}${slideConfig.title ? `: ${slideConfig.title}` : ""}`,
2104
+ "aria-current": index === currentSlide ? "page" : void 0,
2105
+ className: cn(
2106
+ "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]",
2107
+ index === currentSlide ? "border-[#FF6B35] bg-[#FF6B35]/15 ring-1 ring-[#FF6B35]" : "border-transparent hover:border-neutral-600 hover:bg-neutral-800"
2108
+ ),
2109
+ children: [
2110
+ /* @__PURE__ */ jsx12("div", { className: "relative aspect-video w-full overflow-hidden rounded bg-black", children: /* @__PURE__ */ jsx12(
2111
+ GridThumbnail,
2112
+ {
2113
+ slide: slideConfig,
2114
+ index,
2115
+ total: slides.length
2116
+ }
2117
+ ) }),
2118
+ /* @__PURE__ */ jsx12("div", { className: "mt-1.5 flex items-baseline gap-2 px-0.5 text-xs", children: slideConfig.title ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2119
+ /* @__PURE__ */ jsx12("span", { className: "font-mono text-neutral-400", children: index + 1 }),
2120
+ /* @__PURE__ */ jsx12("span", { className: "truncate", children: slideConfig.title })
2121
+ ] }) : /* @__PURE__ */ jsxs6("span", { children: [
2122
+ "Slide ",
2123
+ index + 1
2124
+ ] }) })
2125
+ ]
2126
+ },
2127
+ slideConfig.id ?? index
2128
+ )) })
2129
+ ]
2130
+ }
2131
+ ) }),
2132
+ isThumbnailRailVisible && /* @__PURE__ */ jsx12(
2133
+ "div",
2134
+ {
2135
+ role: "separator",
2136
+ "aria-label": "Resize slide thumbnails",
2137
+ "aria-orientation": "vertical",
2138
+ "aria-valuemin": minThumbnailWidth,
2139
+ "aria-valuemax": maxThumbnailWidth,
2140
+ "aria-valuenow": visibleThumbnailWidth,
2141
+ tabIndex: 0,
2142
+ className: cn(
2143
+ "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",
2144
+ isResizingThumbnails && "after:bg-[#FF6B35]/60"
2145
+ ),
2146
+ onPointerDown: (event) => {
2147
+ if (event.button !== 0) return;
2148
+ event.preventDefault();
2149
+ resizeStartRef.current = {
2150
+ pointerId: event.pointerId,
2151
+ pointerX: event.clientX,
2152
+ width: visibleThumbnailWidth
2153
+ };
2154
+ dragCollapsedRef.current = false;
2155
+ setIsAnimatingDragBoundary(false);
2156
+ if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
2157
+ setIsResizingThumbnails(true);
2158
+ previousUserSelectRef.current = document.body.style.userSelect;
2159
+ previousCursorRef.current = document.body.style.cursor;
2160
+ document.body.style.userSelect = "none";
2161
+ document.body.style.cursor = "col-resize";
2162
+ },
2163
+ onKeyDown: (event) => {
2164
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
2165
+ event.preventDefault();
2166
+ event.stopPropagation();
2167
+ const amount = event.key === "ArrowRight" ? 16 : -16;
2168
+ setThumbnailWidth(
2169
+ Math.max(
2170
+ minThumbnailWidth,
2171
+ Math.min(maxThumbnailWidth, visibleThumbnailWidth + amount)
2172
+ )
2173
+ );
2174
+ }
2175
+ }
2176
+ }
2177
+ )
2178
+ ]
2179
+ }
2180
+ ),
2181
+ /* @__PURE__ */ jsx12(
2182
+ "div",
2183
+ {
2184
+ "aria-hidden": isThumbnailRailVisible || isResizingThumbnails,
2185
+ inert: isThumbnailRailVisible || isResizingThumbnails,
2186
+ className: cn(
2187
+ "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",
2188
+ isThumbnailRailVisible || isResizingThumbnails ? "pointer-events-none opacity-0" : "opacity-100 delay-200"
2189
+ ),
2190
+ children: /* @__PURE__ */ jsx12(
2191
+ "button",
2192
+ {
2193
+ type: "button",
2194
+ onClick: () => setThumbnailRailOpen(true),
2195
+ className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
2196
+ "aria-label": "Show slide thumbnails",
2197
+ title: "Show thumbnails (T)",
2198
+ children: /* @__PURE__ */ jsx12(PanelLeftOpen, { className: "h-4 w-4" })
2199
+ }
2200
+ )
2201
+ }
2202
+ )
2203
+ ] }),
1454
2204
  /* @__PURE__ */ jsxs6(
1455
2205
  "div",
1456
2206
  {
@@ -1458,7 +2208,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1458
2208
  role: "presentation",
1459
2209
  tabIndex: isPresentationMode ? 0 : void 0,
1460
2210
  className: cn(
1461
- "flex flex-1 flex-col items-center justify-center overflow-hidden",
2211
+ "flex min-w-0 flex-1 flex-col items-center justify-center overflow-hidden",
1462
2212
  isPresentationMode ? "bg-black p-0" : "p-4 md:p-8"
1463
2213
  ),
1464
2214
  onClick: isPresentationMode ? advance : void 0,
@@ -1491,42 +2241,63 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1491
2241
  }
1492
2242
  )
1493
2243
  }
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
- ] }) }),
2244
+ ) : /* @__PURE__ */ jsx12(
2245
+ "div",
2246
+ {
2247
+ ref: previewViewportRef,
2248
+ className: "relative aspect-video w-full max-w-7xl overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-2xl",
2249
+ children: /* @__PURE__ */ jsxs6(
2250
+ "div",
2251
+ {
2252
+ ref: slideContainerRef,
2253
+ className: "absolute top-0 left-0 overflow-hidden",
2254
+ style: {
2255
+ width: SLIDE_DIMENSIONS.width,
2256
+ height: SLIDE_DIMENSIONS.height,
2257
+ transformOrigin: "top left"
2258
+ },
2259
+ children: [
2260
+ /* @__PURE__ */ jsx12(
2261
+ SlideRenderer,
2262
+ {
2263
+ slides,
2264
+ currentSlide,
2265
+ animationStep,
2266
+ totalSteps,
2267
+ direction,
2268
+ showAllAnimations,
2269
+ transition,
2270
+ directionalTransition,
2271
+ onTransitionComplete
2272
+ }
2273
+ ),
2274
+ isAnnotationMode && /* @__PURE__ */ jsx12(
2275
+ AnnotationOverlay,
2276
+ {
2277
+ slides,
2278
+ currentSlide,
2279
+ slideContainerRef,
2280
+ selectedId: selectedAnnotationId,
2281
+ hoveredId: hoveredAnnotationId,
2282
+ onSelectId: setSelectedAnnotationId,
2283
+ onShowPanel: () => setShowAnnotationPanel(true),
2284
+ slideAnnotations: getSlideAnnotations(currentSlide),
2285
+ addAnnotation: effectiveAdd ?? (() => {
2286
+ })
2287
+ }
2288
+ )
2289
+ ]
2290
+ }
2291
+ )
2292
+ }
2293
+ ) }),
1524
2294
  !isPresentationMode && /* @__PURE__ */ jsxs6("div", { className: "mt-6 flex items-center gap-4", children: [
1525
2295
  /* @__PURE__ */ jsx12(
1526
2296
  "button",
1527
2297
  {
1528
2298
  onClick: goBack,
1529
2299
  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",
2300
+ title: "Previous step or slide (\u2190)",
1530
2301
  children: /* @__PURE__ */ jsx12(ChevronLeft, { className: "h-5 w-5" })
1531
2302
  }
1532
2303
  ),
@@ -1540,6 +2311,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1540
2311
  {
1541
2312
  onClick: advance,
1542
2313
  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",
2314
+ title: "Next step or slide (\u2192 / Space)",
1543
2315
  children: /* @__PURE__ */ jsx12(ChevronRight, { className: "h-5 w-5" })
1544
2316
  }
1545
2317
  )
@@ -1547,29 +2319,58 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1547
2319
  ]
1548
2320
  }
1549
2321
  ),
1550
- isAnnotationMode && showAnnotationPanel && !isPresentationMode && /* @__PURE__ */ jsx12(
1551
- AnnotationPanel,
2322
+ !isPresentationMode && /* @__PURE__ */ jsx12(
2323
+ "div",
1552
2324
  {
1553
- annotations: getSlideAnnotations(currentSlide),
1554
- selectedId: selectedAnnotationId,
1555
- onSelect: setSelectedAnnotationId,
1556
- onDelete: effectiveDelete ?? (() => {
1557
- }),
1558
- onClose: () => {
1559
- setShowAnnotationPanel(false);
1560
- setSelectedAnnotationId(null);
1561
- }
2325
+ "aria-hidden": !isAnnotationMode || !showAnnotationPanel,
2326
+ inert: !isAnnotationMode || !showAnnotationPanel,
2327
+ className: cn(
2328
+ "h-full shrink-0 overflow-hidden transition-[width,transform] duration-200 ease-out motion-reduce:transition-none",
2329
+ isNarrowViewport && "fixed inset-y-0 right-0 z-50 shadow-xl",
2330
+ isNarrowViewport && (!isAnnotationMode || !showAnnotationPanel) && "translate-x-full"
2331
+ ),
2332
+ style: {
2333
+ width: isAnnotationMode && showAnnotationPanel ? isNarrowViewport ? "min(20rem, 90vw)" : "20rem" : 0
2334
+ },
2335
+ children: /* @__PURE__ */ jsx12(
2336
+ "div",
2337
+ {
2338
+ className: cn(
2339
+ "h-full transition-opacity motion-reduce:transition-none",
2340
+ isAnnotationMode && showAnnotationPanel ? "opacity-100 delay-100 duration-100" : "opacity-0 duration-75"
2341
+ ),
2342
+ children: /* @__PURE__ */ jsx12(
2343
+ AnnotationPanel,
2344
+ {
2345
+ annotations: getSlideAnnotations(currentSlide),
2346
+ unlinkedAnnotations,
2347
+ selectedId: selectedAnnotationId,
2348
+ onSelect: setSelectedAnnotationId,
2349
+ onHover: setHoveredAnnotationId,
2350
+ onDelete: effectiveDelete ?? (() => {
2351
+ }),
2352
+ onUpdate: effectiveUpdate ?? (() => {
2353
+ }),
2354
+ onClose: () => {
2355
+ setIsAnnotationMode(false);
2356
+ setShowAnnotationPanel(false);
2357
+ setSelectedAnnotationId(null);
2358
+ setHoveredAnnotationId(null);
2359
+ }
2360
+ }
2361
+ )
2362
+ }
2363
+ )
1562
2364
  }
1563
2365
  )
1564
2366
  ]
1565
2367
  }
1566
2368
  ),
1567
2369
  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
2370
  const prevSection = index > 0 ? slides[index - 1]?.section : void 0;
1570
2371
  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 }),
2372
+ return /* @__PURE__ */ jsxs6("div", { className: "contents", children: [
2373
+ 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
2374
  /* @__PURE__ */ jsxs6(
1574
2375
  "button",
1575
2376
  {
@@ -1577,30 +2378,15 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1577
2378
  goToSlide(index);
1578
2379
  setViewMode("slide");
1579
2380
  },
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",
2381
+ 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
2382
  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
- ),
2383
+ /* @__PURE__ */ jsx12(GridThumbnail, { slide: slideConfig, index, total: slides.length }),
1598
2384
  /* @__PURE__ */ jsx12("div", { className: "absolute inset-0 bg-black/0 transition-colors group-hover:bg-black/20" }),
1599
2385
  /* @__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
2386
  ]
1601
2387
  }
1602
2388
  )
1603
- ] }, index);
2389
+ ] }, slideConfig.id ?? index);
1604
2390
  }) }) }),
1605
2391
  /* @__PURE__ */ jsx12(
1606
2392
  "div",
@@ -1610,25 +2396,15 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
1610
2396
  "print:m-0 print:block print:max-w-none print:p-0",
1611
2397
  viewMode === "list" ? "block" : "hidden print:block"
1612
2398
  ),
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
- }) })
2399
+ children: /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-1 gap-8 print:block", children: slides.map((slideConfig, index) => /* @__PURE__ */ jsx12(
2400
+ ListSlide,
2401
+ {
2402
+ slide: slideConfig,
2403
+ index,
2404
+ total: slides.length
2405
+ },
2406
+ slideConfig.id ?? index
2407
+ )) })
1632
2408
  }
1633
2409
  )
1634
2410
  ] });