@reslide-dev/core 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yoshikatsu Nishida
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -5,7 +5,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
5
  * Invisible click zones on the left/right edges of the slide.
6
6
  * Clicking the left ~15% goes to the previous slide,
7
7
  * clicking the right ~15% goes to the next slide.
8
- * Shows a subtle arrow indicator on hover.
8
+ * Shows a gradient overlay and arrow indicator on hover (docswell style).
9
9
  */
10
10
  function ClickNavigation({ onPrev, onNext, disabled }) {
11
11
  if (disabled) return null;
@@ -20,12 +20,15 @@ function ClickNavigation({ onPrev, onNext, disabled }) {
20
20
  function NavZone({ direction, onClick }) {
21
21
  const [hovered, setHovered] = useState(false);
22
22
  const isPrev = direction === "prev";
23
+ const handleClick = useCallback((e) => {
24
+ e.stopPropagation();
25
+ e.currentTarget.blur();
26
+ onClick();
27
+ }, [onClick]);
28
+ const gradient = isPrev ? "linear-gradient(to right, rgba(0,0,0,0.08), transparent)" : "linear-gradient(to left, rgba(0,0,0,0.08), transparent)";
23
29
  return /* @__PURE__ */ jsx("button", {
24
30
  type: "button",
25
- onClick: useCallback((e) => {
26
- e.stopPropagation();
27
- onClick();
28
- }, [onClick]),
31
+ onClick: handleClick,
29
32
  onMouseEnter: () => setHovered(true),
30
33
  onMouseLeave: () => setHovered(false),
31
34
  "aria-label": isPrev ? "Previous slide" : "Next slide",
@@ -35,16 +38,16 @@ function NavZone({ direction, onClick }) {
35
38
  bottom: 0,
36
39
  [isPrev ? "left" : "right"]: 0,
37
40
  width: "15%",
38
- background: "none",
41
+ background: hovered ? gradient : "none",
39
42
  border: "none",
43
+ outline: "none",
40
44
  cursor: "pointer",
41
45
  zIndex: 20,
42
46
  display: "flex",
43
47
  alignItems: "center",
44
48
  justifyContent: isPrev ? "flex-start" : "flex-end",
45
49
  padding: "0 1.5rem",
46
- opacity: hovered ? 1 : 0,
47
- transition: "opacity 0.2s ease"
50
+ transition: "background 0.25s ease"
48
51
  },
49
52
  children: /* @__PURE__ */ jsx("svg", {
50
53
  width: "32",
@@ -57,9 +60,10 @@ function NavZone({ direction, onClick }) {
57
60
  strokeLinejoin: "round",
58
61
  style: {
59
62
  color: "var(--slide-text, #1a1a1a)",
60
- opacity: .4,
61
- filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.2))",
62
- transform: isPrev ? "none" : "rotate(180deg)"
63
+ opacity: hovered ? .6 : 0,
64
+ filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.15))",
65
+ transform: isPrev ? "none" : "rotate(180deg)",
66
+ transition: "opacity 0.25s ease"
63
67
  },
64
68
  children: /* @__PURE__ */ jsx("polyline", { points: "15 18 9 12 15 6" })
65
69
  })
@@ -321,6 +325,11 @@ function isPresenterView() {
321
325
  */
322
326
  function NavigationBar({ isDrawing, onToggleDrawing }) {
323
327
  const { currentSlide, totalSlides, clickStep, totalClickSteps, isOverview, isFullscreen, next, prev, toggleOverview, toggleFullscreen } = useDeck();
328
+ const handlePrint = useCallback(() => {
329
+ const url = new URL(window.location.href);
330
+ url.searchParams.set("mode", "print");
331
+ window.open(url.toString(), "_blank");
332
+ }, []);
324
333
  const [visible, setVisible] = useState(false);
325
334
  const timerRef = useRef(null);
326
335
  const barRef = useRef(null);
@@ -431,6 +440,12 @@ function NavigationBar({ isDrawing, onToggleDrawing }) {
431
440
  title: "Drawing (d)",
432
441
  active: isDrawing,
433
442
  children: /* @__PURE__ */ jsx(PenIcon, {})
443
+ }),
444
+ /* @__PURE__ */ jsx(Divider, {}),
445
+ /* @__PURE__ */ jsx(NavButton, {
446
+ onClick: handlePrint,
447
+ title: "Print / PDF (Cmd+P)",
448
+ children: /* @__PURE__ */ jsx(PrintIcon, {})
434
449
  })
435
450
  ]
436
451
  });
@@ -622,6 +637,124 @@ function PenIcon() {
622
637
  ]
623
638
  });
624
639
  }
640
+ function PrintIcon() {
641
+ return /* @__PURE__ */ jsxs("svg", {
642
+ width: "16",
643
+ height: "16",
644
+ viewBox: "0 0 24 24",
645
+ fill: "none",
646
+ stroke: "currentColor",
647
+ strokeWidth: "2",
648
+ strokeLinecap: "round",
649
+ strokeLinejoin: "round",
650
+ children: [
651
+ /* @__PURE__ */ jsx("polyline", { points: "6 9 6 2 18 2 18 9" }),
652
+ /* @__PURE__ */ jsx("path", { d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" }),
653
+ /* @__PURE__ */ jsx("rect", {
654
+ x: "6",
655
+ y: "14",
656
+ width: "12",
657
+ height: "8"
658
+ })
659
+ ]
660
+ });
661
+ }
662
+ //#endregion
663
+ //#region src/Pointer.tsx
664
+ let rippleId = 0;
665
+ /**
666
+ * Presentation pointer — a large colored circle that follows the cursor.
667
+ * Clicking creates an expanding ripple effect to highlight a point.
668
+ * Toggle with `q` key (handled in Deck).
669
+ */
670
+ function Pointer({ active, color, size = 24 }) {
671
+ const [pos, setPos] = useState(null);
672
+ const [ripples, setRipples] = useState([]);
673
+ const containerRef = useRef(null);
674
+ const getPos = useCallback((e) => {
675
+ const container = containerRef.current?.parentElement;
676
+ if (!container) return null;
677
+ const rect = container.getBoundingClientRect();
678
+ return {
679
+ x: e.clientX - rect.left,
680
+ y: e.clientY - rect.top
681
+ };
682
+ }, []);
683
+ const handleMouseMove = useCallback((e) => {
684
+ if (!active) return;
685
+ setPos(getPos(e));
686
+ }, [active, getPos]);
687
+ const handleClick = useCallback((e) => {
688
+ if (!active) return;
689
+ const p = getPos(e);
690
+ if (!p) return;
691
+ const id = ++rippleId;
692
+ setRipples((prev) => [...prev, {
693
+ id,
694
+ x: p.x,
695
+ y: p.y
696
+ }]);
697
+ setTimeout(() => setRipples((prev) => prev.filter((r) => r.id !== id)), 500);
698
+ }, [active, getPos]);
699
+ const handleMouseLeave = useCallback(() => setPos(null), []);
700
+ useEffect(() => {
701
+ if (!active) {
702
+ setPos(null);
703
+ setRipples([]);
704
+ return;
705
+ }
706
+ window.addEventListener("mousemove", handleMouseMove);
707
+ window.addEventListener("click", handleClick, true);
708
+ window.addEventListener("mouseleave", handleMouseLeave);
709
+ return () => {
710
+ window.removeEventListener("mousemove", handleMouseMove);
711
+ window.removeEventListener("click", handleClick, true);
712
+ window.removeEventListener("mouseleave", handleMouseLeave);
713
+ };
714
+ }, [
715
+ active,
716
+ handleMouseMove,
717
+ handleClick,
718
+ handleMouseLeave
719
+ ]);
720
+ const resolvedColor = color ?? "var(--slide-accent, #16a34a)";
721
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("style", { children: `
722
+ @keyframes reslide-pointer-ripple {
723
+ 0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0.5; }
724
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
725
+ }
726
+ ` }), /* @__PURE__ */ jsxs("div", {
727
+ ref: containerRef,
728
+ style: {
729
+ position: "absolute",
730
+ inset: 0,
731
+ pointerEvents: "none",
732
+ zIndex: 45,
733
+ overflow: "hidden"
734
+ },
735
+ children: [active && pos && /* @__PURE__ */ jsx("div", { style: {
736
+ position: "absolute",
737
+ left: pos.x,
738
+ top: pos.y,
739
+ width: size,
740
+ height: size,
741
+ borderRadius: "50%",
742
+ backgroundColor: resolvedColor,
743
+ opacity: .7,
744
+ transform: "translate(-50%, -50%)",
745
+ willChange: "left, top"
746
+ } }), ripples.map((r) => /* @__PURE__ */ jsx("div", { style: {
747
+ position: "absolute",
748
+ left: r.x,
749
+ top: r.y,
750
+ width: size * 3,
751
+ height: size * 3,
752
+ borderRadius: "50%",
753
+ border: `2px solid ${resolvedColor}`,
754
+ animation: "reslide-pointer-ripple 0.5s ease-out forwards"
755
+ } }, r.id))]
756
+ })] });
757
+ }
625
758
  //#endregion
626
759
  //#region src/slide-context.ts
627
760
  const SlideIndexContext = createContext(null);
@@ -632,17 +765,84 @@ function useSlideIndex() {
632
765
  }
633
766
  //#endregion
634
767
  //#region src/PrintView.tsx
768
+ const DESIGN_WIDTH$2 = 960;
769
+ const DESIGN_HEIGHT$2 = 540;
770
+ function ScaledSlideCard({ children, index }) {
771
+ const frameRef = useRef(null);
772
+ const [scale, setScale] = useState(.3);
773
+ useEffect(() => {
774
+ const el = frameRef.current;
775
+ if (!el) return;
776
+ const observer = new ResizeObserver((entries) => {
777
+ for (const entry of entries) {
778
+ const { width } = entry.contentRect;
779
+ if (width > 0) setScale(width / DESIGN_WIDTH$2);
780
+ }
781
+ });
782
+ observer.observe(el);
783
+ return () => observer.disconnect();
784
+ }, []);
785
+ return /* @__PURE__ */ jsxs("div", {
786
+ className: "reslide-print-slide-card",
787
+ children: [/* @__PURE__ */ jsx("div", {
788
+ ref: frameRef,
789
+ className: "reslide-print-slide-frame",
790
+ children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
791
+ value: index,
792
+ children: /* @__PURE__ */ jsx("div", {
793
+ className: "reslide-print-slide-content",
794
+ style: {
795
+ width: DESIGN_WIDTH$2,
796
+ height: DESIGN_HEIGHT$2,
797
+ transform: `scale(${scale})`,
798
+ transformOrigin: "top left"
799
+ },
800
+ children
801
+ })
802
+ })
803
+ }), /* @__PURE__ */ jsx("div", {
804
+ className: "reslide-print-slide-label",
805
+ children: index + 1
806
+ })]
807
+ });
808
+ }
635
809
  /**
636
- * Renders all slides vertically for print/PDF export.
637
- * Use with @media print CSS to generate PDFs via browser print.
810
+ * Renders all slides in a printable handout layout.
811
+ * 6 slides per page (2 columns × 3 rows), each maintaining 16:9 ratio.
638
812
  */
639
813
  function PrintView({ children }) {
640
- return /* @__PURE__ */ jsx("div", {
641
- className: "reslide-print-view",
642
- children: Children.toArray(children).map((slide, i) => /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
643
- value: i,
644
- children: slide
645
- }, i))
814
+ const slides = Children.toArray(children);
815
+ const noop = useCallback(() => {}, []);
816
+ const noopNum = useCallback((_n) => {}, []);
817
+ const noopReg = useCallback((_i, _c) => {}, []);
818
+ const contextValue = useMemo(() => ({
819
+ currentSlide: 0,
820
+ totalSlides: slides.length,
821
+ clickStep: 999,
822
+ totalClickSteps: 0,
823
+ isOverview: false,
824
+ isFullscreen: false,
825
+ next: noop,
826
+ prev: noop,
827
+ goTo: noopNum,
828
+ toggleOverview: noop,
829
+ toggleFullscreen: noop,
830
+ registerClickSteps: noopReg
831
+ }), [
832
+ slides.length,
833
+ noop,
834
+ noopNum,
835
+ noopReg
836
+ ]);
837
+ return /* @__PURE__ */ jsx(DeckContext.Provider, {
838
+ value: contextValue,
839
+ children: /* @__PURE__ */ jsx("div", {
840
+ className: "reslide-print-view",
841
+ children: slides.map((slide, i) => /* @__PURE__ */ jsx(ScaledSlideCard, {
842
+ index: i,
843
+ children: slide
844
+ }, i))
845
+ })
646
846
  });
647
847
  }
648
848
  //#endregion
@@ -655,7 +855,7 @@ function ProgressBar() {
655
855
  className: "reslide-progress-bar",
656
856
  style: {
657
857
  position: "absolute",
658
- top: 0,
858
+ bottom: 0,
659
859
  left: 0,
660
860
  width: "100%",
661
861
  height: "3px",
@@ -664,7 +864,7 @@ function ProgressBar() {
664
864
  children: /* @__PURE__ */ jsx("div", { style: {
665
865
  height: "100%",
666
866
  width: `${Math.min(slideProgress + clickFraction, 1) * 100}%`,
667
- backgroundColor: "var(--slide-accent, #3b82f6)",
867
+ backgroundColor: "var(--slide-progress, #16a34a)",
668
868
  transition: "width 0.3s ease"
669
869
  } })
670
870
  });
@@ -785,14 +985,20 @@ function useFullscreen(ref) {
785
985
  }
786
986
  //#endregion
787
987
  //#region src/Deck.tsx
788
- function Deck({ children, transition = "none" }) {
988
+ /** Design resolution slides are authored at this size and scaled to fit */
989
+ const DESIGN_WIDTH$1 = 960;
990
+ const DESIGN_HEIGHT$1 = 540;
991
+ function Deck({ children, transition = "none", aspectRatio = 16 / 9 }) {
789
992
  const containerRef = useRef(null);
993
+ const deckRef = useRef(null);
790
994
  const [currentSlide, setCurrentSlide] = useState(0);
791
995
  const [clickStep, setClickStep] = useState(0);
792
996
  const [isOverview, setIsOverview] = useState(false);
793
997
  const [isDrawing, setIsDrawing] = useState(false);
998
+ const [isPointer, setIsPointer] = useState(false);
794
999
  const [isPrinting, setIsPrinting] = useState(false);
795
1000
  const [clickStepsMap, setClickStepsMap] = useState({});
1001
+ const [scale, setScale] = useState(1);
796
1002
  const { isFullscreen, toggleFullscreen } = useFullscreen(containerRef);
797
1003
  const totalSlides = Children.count(children);
798
1004
  const totalClickSteps = clickStepsMap[currentSlide] ?? 0;
@@ -855,6 +1061,7 @@ function Deck({ children, transition = "none" }) {
855
1061
  useEffect(() => {
856
1062
  function handleKeyDown(e) {
857
1063
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
1064
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
858
1065
  switch (e.key) {
859
1066
  case "ArrowRight":
860
1067
  case " ":
@@ -882,6 +1089,12 @@ function Deck({ children, transition = "none" }) {
882
1089
  case "d":
883
1090
  e.preventDefault();
884
1091
  setIsDrawing((v) => !v);
1092
+ setIsPointer(false);
1093
+ break;
1094
+ case "q":
1095
+ e.preventDefault();
1096
+ setIsPointer((v) => !v);
1097
+ setIsDrawing(false);
885
1098
  break;
886
1099
  }
887
1100
  }
@@ -893,6 +1106,36 @@ function Deck({ children, transition = "none" }) {
893
1106
  toggleOverview,
894
1107
  toggleFullscreen
895
1108
  ]);
1109
+ useEffect(() => {
1110
+ const el = deckRef.current;
1111
+ if (!el) return;
1112
+ let startX = 0;
1113
+ let startY = 0;
1114
+ function handleTouchStart(e) {
1115
+ const touch = e.touches[0];
1116
+ startX = touch.clientX;
1117
+ startY = touch.clientY;
1118
+ }
1119
+ function handleTouchEnd(e) {
1120
+ if (isOverview || isDrawing) return;
1121
+ const touch = e.changedTouches[0];
1122
+ const dx = touch.clientX - startX;
1123
+ const dy = touch.clientY - startY;
1124
+ if (Math.abs(dx) > 50 && Math.abs(dx) > Math.abs(dy) * 1.5) if (dx < 0) next();
1125
+ else prev();
1126
+ }
1127
+ el.addEventListener("touchstart", handleTouchStart, { passive: true });
1128
+ el.addEventListener("touchend", handleTouchEnd, { passive: true });
1129
+ return () => {
1130
+ el.removeEventListener("touchstart", handleTouchStart);
1131
+ el.removeEventListener("touchend", handleTouchEnd);
1132
+ };
1133
+ }, [
1134
+ next,
1135
+ prev,
1136
+ isOverview,
1137
+ isDrawing
1138
+ ]);
896
1139
  useEffect(() => {
897
1140
  function onBeforePrint() {
898
1141
  setIsPrinting(true);
@@ -907,6 +1150,18 @@ function Deck({ children, transition = "none" }) {
907
1150
  window.removeEventListener("afterprint", onAfterPrint);
908
1151
  };
909
1152
  }, []);
1153
+ useEffect(() => {
1154
+ const el = deckRef.current;
1155
+ if (!el) return;
1156
+ const observer = new ResizeObserver((entries) => {
1157
+ for (const entry of entries) {
1158
+ const { width, height } = entry.contentRect;
1159
+ if (width > 0 && height > 0) setScale(Math.min(width / DESIGN_WIDTH$1, height / DESIGN_HEIGHT$1));
1160
+ }
1161
+ });
1162
+ observer.observe(el);
1163
+ return () => observer.disconnect();
1164
+ }, []);
910
1165
  const contextValue = useMemo(() => ({
911
1166
  currentSlide,
912
1167
  totalSlides,
@@ -934,21 +1189,32 @@ function Deck({ children, transition = "none" }) {
934
1189
  toggleFullscreen,
935
1190
  registerClickSteps
936
1191
  ]);
937
- return /* @__PURE__ */ jsx(DeckContext.Provider, {
938
- value: contextValue,
939
- children: /* @__PURE__ */ jsxs("div", {
940
- ref: containerRef,
941
- className: "reslide-deck",
942
- style: {
943
- position: "relative",
944
- width: "100%",
945
- height: "100%",
946
- overflow: "hidden",
947
- backgroundColor: "var(--slide-bg, #fff)",
948
- color: "var(--slide-text, #1a1a1a)"
949
- },
950
- children: [
951
- isPrinting ? /* @__PURE__ */ jsx(PrintView, { children }) : isOverview ? /* @__PURE__ */ jsx(OverviewGrid, {
1192
+ const useLetterbox = aspectRatio > 0;
1193
+ const slideArea = /* @__PURE__ */ jsxs("div", {
1194
+ ref: deckRef,
1195
+ className: "reslide-deck",
1196
+ style: {
1197
+ position: "relative",
1198
+ width: "100%",
1199
+ height: "100%",
1200
+ overflow: "hidden",
1201
+ backgroundColor: "var(--slide-bg, #fff)",
1202
+ color: "var(--slide-text, #1a1a1a)",
1203
+ cursor: isPointer ? "none" : void 0
1204
+ },
1205
+ children: [
1206
+ isPrinting ? /* @__PURE__ */ jsx(PrintView, { children }) : /* @__PURE__ */ jsx("div", {
1207
+ className: "reslide-scale-wrapper",
1208
+ style: {
1209
+ position: "absolute",
1210
+ top: "50%",
1211
+ left: "50%",
1212
+ width: DESIGN_WIDTH$1,
1213
+ height: DESIGN_HEIGHT$1,
1214
+ transform: `translate(-50%, -50%) scale(${scale})`,
1215
+ transformOrigin: "center center"
1216
+ },
1217
+ children: isOverview ? /* @__PURE__ */ jsx(OverviewGrid, {
952
1218
  totalSlides,
953
1219
  goTo,
954
1220
  children
@@ -956,23 +1222,61 @@ function Deck({ children, transition = "none" }) {
956
1222
  currentSlide,
957
1223
  transition,
958
1224
  children
959
- }),
960
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ClickNavigation, {
961
- onPrev: prev,
962
- onNext: next,
963
- disabled: isDrawing
964
- }),
965
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ProgressBar, {}),
966
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(SlideNumber, {}),
967
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(DrawingLayer, {
968
- active: isDrawing,
969
- currentSlide
970
- }),
971
- !isPrinting && /* @__PURE__ */ jsx(NavigationBar, {
972
- isDrawing,
973
- onToggleDrawing: () => setIsDrawing((v) => !v)
974
1225
  })
975
- ]
1226
+ }),
1227
+ !isOverview && !isPrinting && /* @__PURE__ */ jsx(ClickNavigation, {
1228
+ onPrev: prev,
1229
+ onNext: next,
1230
+ disabled: isDrawing
1231
+ }),
1232
+ !isOverview && !isPrinting && /* @__PURE__ */ jsx(ProgressBar, {}),
1233
+ !isOverview && !isPrinting && /* @__PURE__ */ jsx(SlideNumber, {}),
1234
+ !isOverview && !isPrinting && /* @__PURE__ */ jsx(DrawingLayer, {
1235
+ active: isDrawing,
1236
+ currentSlide
1237
+ }),
1238
+ !isOverview && !isPrinting && /* @__PURE__ */ jsx(Pointer, { active: isPointer }),
1239
+ !isPrinting && /* @__PURE__ */ jsx(NavigationBar, {
1240
+ isDrawing,
1241
+ onToggleDrawing: () => setIsDrawing((v) => !v)
1242
+ })
1243
+ ]
1244
+ });
1245
+ if (!useLetterbox) return /* @__PURE__ */ jsx(DeckContext.Provider, {
1246
+ value: contextValue,
1247
+ children: /* @__PURE__ */ jsx("div", {
1248
+ ref: containerRef,
1249
+ style: {
1250
+ width: "100%",
1251
+ height: "100%"
1252
+ },
1253
+ children: slideArea
1254
+ })
1255
+ });
1256
+ return /* @__PURE__ */ jsx(DeckContext.Provider, {
1257
+ value: contextValue,
1258
+ children: /* @__PURE__ */ jsx("div", {
1259
+ ref: containerRef,
1260
+ className: "reslide-letterbox",
1261
+ style: {
1262
+ width: "100%",
1263
+ height: "100%",
1264
+ display: "flex",
1265
+ alignItems: "center",
1266
+ justifyContent: "center",
1267
+ backgroundColor: "var(--reslide-letterbox-bg, #000)",
1268
+ overflow: "hidden"
1269
+ },
1270
+ children: /* @__PURE__ */ jsx("div", {
1271
+ className: "reslide-letterbox-inner",
1272
+ style: {
1273
+ height: "100%",
1274
+ maxWidth: "100%",
1275
+ aspectRatio: String(aspectRatio),
1276
+ overflow: "hidden"
1277
+ },
1278
+ children: slideArea
1279
+ })
976
1280
  })
977
1281
  });
978
1282
  }
@@ -993,7 +1297,7 @@ function OverviewGrid({ children, totalSlides, goTo }) {
993
1297
  type: "button",
994
1298
  onClick: () => goTo(i),
995
1299
  style: {
996
- border: "1px solid var(--slide-accent, #3b82f6)",
1300
+ border: "1px solid var(--slide-accent, #16a34a)",
997
1301
  borderRadius: "0.5rem",
998
1302
  overflow: "hidden",
999
1303
  cursor: "pointer",
@@ -1140,7 +1444,7 @@ function Slide({ children, layout = "default", image, className, style }) {
1140
1444
  alignItems: "center",
1141
1445
  textAlign: "center",
1142
1446
  padding: "3rem 4rem",
1143
- backgroundColor: "var(--slide-accent, #3b82f6)",
1447
+ backgroundColor: "var(--slide-accent, #16a34a)",
1144
1448
  color: "var(--slide-section-text, #fff)",
1145
1449
  ...style
1146
1450
  },
@@ -1159,7 +1463,7 @@ function Slide({ children, layout = "default", image, className, style }) {
1159
1463
  style: {
1160
1464
  fontSize: "1.5em",
1161
1465
  fontStyle: "italic",
1162
- borderLeft: "4px solid var(--slide-accent, #3b82f6)",
1466
+ borderLeft: "4px solid var(--slide-accent, #16a34a)",
1163
1467
  paddingLeft: "1.5rem",
1164
1468
  margin: 0
1165
1469
  },
@@ -1284,6 +1588,51 @@ SlotRight.displayName = "SlotRight";
1284
1588
  SlotRight.__reslideSlot = "right";
1285
1589
  //#endregion
1286
1590
  //#region src/PresenterView.tsx
1591
+ const DESIGN_WIDTH = 960;
1592
+ const DESIGN_HEIGHT = 540;
1593
+ /**
1594
+ * Scaled slide preview — renders at design size and scales to fit container.
1595
+ * Maintains 16:9 aspect ratio.
1596
+ */
1597
+ function ScaledSlide({ children }) {
1598
+ const containerRef = useRef(null);
1599
+ const [scale, setScale] = useState(1);
1600
+ useEffect(() => {
1601
+ const el = containerRef.current;
1602
+ if (!el) return;
1603
+ const observer = new ResizeObserver((entries) => {
1604
+ for (const entry of entries) {
1605
+ const { width, height } = entry.contentRect;
1606
+ if (width > 0 && height > 0) setScale(Math.min(width / DESIGN_WIDTH, height / DESIGN_HEIGHT));
1607
+ }
1608
+ });
1609
+ observer.observe(el);
1610
+ return () => observer.disconnect();
1611
+ }, []);
1612
+ return /* @__PURE__ */ jsx("div", {
1613
+ ref: containerRef,
1614
+ style: {
1615
+ width: "100%",
1616
+ aspectRatio: "16 / 9",
1617
+ position: "relative",
1618
+ overflow: "hidden",
1619
+ backgroundColor: "var(--slide-bg, #fff)",
1620
+ color: "var(--slide-text, #1a1a1a)"
1621
+ },
1622
+ children: /* @__PURE__ */ jsx("div", {
1623
+ style: {
1624
+ position: "absolute",
1625
+ top: "50%",
1626
+ left: "50%",
1627
+ width: DESIGN_WIDTH,
1628
+ height: DESIGN_HEIGHT,
1629
+ transform: `translate(-50%, -50%) scale(${scale})`,
1630
+ transformOrigin: "center center"
1631
+ },
1632
+ children
1633
+ })
1634
+ });
1635
+ }
1287
1636
  /**
1288
1637
  * Presenter view that syncs with the main presentation window.
1289
1638
  * Shows: current slide, next slide preview, notes, and timer.
@@ -1374,16 +1723,17 @@ function PresenterView({ children, notes }) {
1374
1723
  children: [
1375
1724
  /* @__PURE__ */ jsx("div", {
1376
1725
  style: {
1377
- border: "2px solid #3b82f6",
1726
+ border: "2px solid #16a34a",
1378
1727
  borderRadius: "0.5rem",
1379
1728
  overflow: "hidden",
1380
- position: "relative",
1381
- backgroundColor: "var(--slide-bg, #fff)",
1382
- color: "var(--slide-text, #1a1a1a)"
1729
+ display: "flex",
1730
+ alignItems: "center",
1731
+ justifyContent: "center",
1732
+ backgroundColor: "#000"
1383
1733
  },
1384
1734
  children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1385
1735
  value: currentSlide,
1386
- children: slides[currentSlide]
1736
+ children: /* @__PURE__ */ jsx(ScaledSlide, { children: slides[currentSlide] })
1387
1737
  })
1388
1738
  }),
1389
1739
  /* @__PURE__ */ jsxs("div", {
@@ -1395,25 +1745,29 @@ function PresenterView({ children, notes }) {
1395
1745
  },
1396
1746
  children: [/* @__PURE__ */ jsx("div", {
1397
1747
  style: {
1398
- flex: "0 0 40%",
1399
1748
  border: "1px solid #334155",
1400
1749
  borderRadius: "0.5rem",
1401
1750
  overflow: "hidden",
1402
1751
  opacity: .8,
1403
- backgroundColor: "var(--slide-bg, #fff)",
1404
- color: "var(--slide-text, #1a1a1a)"
1752
+ display: "flex",
1753
+ alignItems: "center",
1754
+ justifyContent: "center",
1755
+ backgroundColor: "#000"
1405
1756
  },
1406
- children: currentSlide < totalSlides - 1 && /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1757
+ children: currentSlide < totalSlides - 1 ? /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1407
1758
  value: currentSlide + 1,
1408
- children: /* @__PURE__ */ jsx("div", {
1409
- style: {
1410
- transform: "scale(0.5)",
1411
- transformOrigin: "top left",
1412
- width: "200%",
1413
- height: "200%"
1414
- },
1415
- children: slides[currentSlide + 1]
1416
- })
1759
+ children: /* @__PURE__ */ jsx(ScaledSlide, { children: slides[currentSlide + 1] })
1760
+ }) : /* @__PURE__ */ jsx("div", {
1761
+ style: {
1762
+ width: "100%",
1763
+ aspectRatio: "16 / 9",
1764
+ display: "flex",
1765
+ alignItems: "center",
1766
+ justifyContent: "center",
1767
+ color: "#64748b",
1768
+ fontSize: "0.875rem"
1769
+ },
1770
+ children: "End of slides"
1417
1771
  })
1418
1772
  }), /* @__PURE__ */ jsxs("div", {
1419
1773
  style: {
@@ -1626,7 +1980,7 @@ function extractHeading(node) {
1626
1980
  if (!isValidElement(node)) return null;
1627
1981
  const el = node;
1628
1982
  const type = el.type;
1629
- if (type === "h1" || type === "h2") return extractText(el.props.children);
1983
+ if (type === "h1" || type === "h2") return extractText$1(el.props.children);
1630
1984
  const children = el.props.children;
1631
1985
  if (children == null) return null;
1632
1986
  let result = null;
@@ -1638,12 +1992,12 @@ function extractHeading(node) {
1638
1992
  return result;
1639
1993
  }
1640
1994
  /** Extract plain text from a React node tree */
1641
- function extractText(node) {
1995
+ function extractText$1(node) {
1642
1996
  if (node == null) return "";
1643
1997
  if (typeof node === "string") return node;
1644
1998
  if (typeof node === "number") return String(node);
1645
- if (Array.isArray(node)) return node.map(extractText).join("");
1646
- if (isValidElement(node)) return extractText(node.props.children);
1999
+ if (Array.isArray(node)) return node.map(extractText$1).join("");
2000
+ if (isValidElement(node)) return extractText$1(node.props.children);
1647
2001
  return "";
1648
2002
  }
1649
2003
  /**
@@ -1693,7 +2047,7 @@ function Toc({ children, className, style }) {
1693
2047
  fontSize: "0.875rem",
1694
2048
  lineHeight: 1.4,
1695
2049
  fontFamily: "inherit",
1696
- color: isActive ? "var(--toc-active-text, var(--slide-accent, #3b82f6))" : "var(--toc-text, var(--slide-text, #1a1a1a))",
2050
+ color: isActive ? "var(--toc-active-text, var(--slide-accent, #16a34a))" : "var(--toc-text, var(--slide-text, #1a1a1a))",
1697
2051
  backgroundColor: isActive ? "var(--toc-active-bg, rgba(59, 130, 246, 0.1))" : "transparent",
1698
2052
  fontWeight: isActive ? 600 : 400,
1699
2053
  transition: "background-color 0.15s, color 0.15s"
@@ -1721,6 +2075,25 @@ function Toc({ children, className, style }) {
1721
2075
  //#endregion
1722
2076
  //#region src/Mermaid.tsx
1723
2077
  /**
2078
+ * Recursively extract text content from React children.
2079
+ * MDX wraps raw text in paragraphs and other elements,
2080
+ * so we need to traverse the tree to get the original text.
2081
+ */
2082
+ function extractText(node) {
2083
+ if (node == null || typeof node === "boolean") return "";
2084
+ if (typeof node === "string") return node;
2085
+ if (typeof node === "number") return String(node);
2086
+ if (Array.isArray(node)) return node.map(extractText).join("");
2087
+ if (isValidElement(node)) {
2088
+ const { children } = node.props;
2089
+ const text = extractText(children);
2090
+ const tag = typeof node.type === "string" ? node.type : "";
2091
+ if (tag === "p" || tag === "br") return `${text}\n`;
2092
+ return text;
2093
+ }
2094
+ return "";
2095
+ }
2096
+ /**
1724
2097
  * Renders a Mermaid diagram.
1725
2098
  *
1726
2099
  * Usage in MDX:
@@ -1739,7 +2112,9 @@ function Mermaid({ children }) {
1739
2112
  const [svg, setSvg] = useState("");
1740
2113
  const [error, setError] = useState("");
1741
2114
  const id = useId().replace(/:/g, "_");
2115
+ const code = extractText(children).trim();
1742
2116
  useEffect(() => {
2117
+ if (!code) return;
1743
2118
  let cancelled = false;
1744
2119
  async function render() {
1745
2120
  try {
@@ -1749,8 +2124,6 @@ function Mermaid({ children }) {
1749
2124
  theme: "default",
1750
2125
  securityLevel: "loose"
1751
2126
  });
1752
- const code = typeof children === "string" ? children.trim() : "";
1753
- if (!code) return;
1754
2127
  const { svg: rendered } = await mermaid.default.render(`mermaid-${id}`, code);
1755
2128
  if (!cancelled) {
1756
2129
  setSvg(rendered);
@@ -1764,7 +2137,7 @@ function Mermaid({ children }) {
1764
2137
  return () => {
1765
2138
  cancelled = true;
1766
2139
  };
1767
- }, [children, id]);
2140
+ }, [code, id]);
1768
2141
  if (error) return /* @__PURE__ */ jsx("div", {
1769
2142
  className: "reslide-mermaid reslide-mermaid--error",
1770
2143
  children: /* @__PURE__ */ jsx("pre", { children: error })
@@ -1776,4 +2149,4 @@ function Mermaid({ children }) {
1776
2149
  });
1777
2150
  }
1778
2151
  //#endregion
1779
- export { useDeck as C, DeckContext as S, useSlideIndex as _, PresenterView as a, openPresenterWindow as b, Mark as c, Slide as d, Deck as f, SlideIndexContext as g, PrintView as h, GlobalLayer as i, Click as l, ProgressBar as m, Toc as n, SlotRight as o, SlideNumber as p, Draggable as r, Notes as s, Mermaid as t, ClickSteps as u, NavigationBar as v, ClickNavigation as w, DrawingLayer as x, isPresenterView as y };
2152
+ export { DeckContext as C, DrawingLayer as S, ClickNavigation as T, useSlideIndex as _, PresenterView as a, isPresenterView as b, Mark as c, Slide as d, Deck as f, SlideIndexContext as g, PrintView as h, GlobalLayer as i, Click as l, ProgressBar as m, Toc as n, SlotRight as o, SlideNumber as p, Draggable as r, Notes as s, Mermaid as t, ClickSteps as u, Pointer as v, useDeck as w, openPresenterWindow as x, NavigationBar as y };
@@ -9,10 +9,13 @@ interface DeckProps {
9
9
  children: ReactNode;
10
10
  /** Slide transition type */
11
11
  transition?: TransitionType;
12
+ /** Aspect ratio (default: 16/9). Set to 0 to disable and fill parent. */
13
+ aspectRatio?: number;
12
14
  }
13
15
  declare function Deck({
14
16
  children,
15
- transition
17
+ transition,
18
+ aspectRatio
16
19
  }: DeckProps): react_jsx_runtime0.JSX.Element;
17
20
  //#endregion
18
21
  //#region src/Slide.d.ts
@@ -102,6 +105,17 @@ declare namespace SlotRight {
102
105
  var __reslideSlot: "right";
103
106
  }
104
107
  //#endregion
108
+ //#region src/PrintView.d.ts
109
+ /**
110
+ * Renders all slides in a printable handout layout.
111
+ * 6 slides per page (2 columns × 3 rows), each maintaining 16:9 ratio.
112
+ */
113
+ declare function PrintView({
114
+ children
115
+ }: {
116
+ children: ReactNode;
117
+ }): react_jsx_runtime0.JSX.Element;
118
+ //#endregion
105
119
  //#region src/PresenterView.d.ts
106
120
  interface PresenterViewProps {
107
121
  children: ReactNode;
@@ -216,8 +230,8 @@ declare function Toc({
216
230
  //#endregion
217
231
  //#region src/Mermaid.d.ts
218
232
  interface MermaidProps {
219
- /** Mermaid diagram code */
220
- children: string;
233
+ /** Mermaid diagram code (string or MDX children) */
234
+ children: ReactNode;
221
235
  }
222
236
  /**
223
237
  * Renders a Mermaid diagram.
@@ -237,4 +251,4 @@ declare function Mermaid({
237
251
  children
238
252
  }: MermaidProps): react_jsx_runtime0.JSX.Element;
239
253
  //#endregion
240
- export { Deck as C, SlideProps as S, TransitionType as T, MarkProps as _, Draggable as a, ClickSteps as b, GlobalLayerProps as c, PresenterView as d, PresenterViewProps as f, Mark as g, NotesProps as h, TocProps as i, isPresenterView as l, Notes as m, MermaidProps as n, DraggableProps as o, SlotRight as p, Toc as r, GlobalLayer as s, Mermaid as t, openPresenterWindow as u, Click as v, DeckProps as w, Slide as x, ClickProps as y };
254
+ export { SlideProps as C, TransitionType as E, Slide as S, DeckProps as T, Mark as _, Draggable as a, ClickProps as b, GlobalLayerProps as c, PresenterView as d, PresenterViewProps as f, NotesProps as g, Notes as h, TocProps as i, isPresenterView as l, SlotRight as m, MermaidProps as n, DraggableProps as o, PrintView as p, Toc as r, GlobalLayer as s, Mermaid as t, openPresenterWindow as u, MarkProps as v, Deck as w, ClickSteps as x, Click as y };
package/dist/index.d.mts CHANGED
@@ -1,19 +1,8 @@
1
- import { C as Deck, S as SlideProps, T as TransitionType, _ as MarkProps, a as Draggable, b as ClickSteps, c as GlobalLayerProps, d as PresenterView, f as PresenterViewProps, g as Mark, h as NotesProps, i as TocProps, l as isPresenterView, m as Notes, n as MermaidProps, o as DraggableProps, p as SlotRight, r as Toc, s as GlobalLayer, t as Mermaid, u as openPresenterWindow, v as Click, w as DeckProps, x as Slide, y as ClickProps } from "./Mermaid-CUQUPHdK.mjs";
1
+ import { C as SlideProps, E as TransitionType, S as Slide, T as DeckProps, _ as Mark, a as Draggable, b as ClickProps, c as GlobalLayerProps, d as PresenterView, f as PresenterViewProps, g as NotesProps, h as Notes, i as TocProps, l as isPresenterView, m as SlotRight, n as MermaidProps, o as DraggableProps, p as PrintView, r as Toc, s as GlobalLayer, t as Mermaid, u as openPresenterWindow, v as MarkProps, w as Deck, x as ClickSteps, y as Click } from "./Mermaid-DWHv8vPf.mjs";
2
2
  import * as react from "react";
3
- import { CSSProperties, ElementType, ReactNode } from "react";
3
+ import { CSSProperties, ElementType } from "react";
4
4
  import * as react_jsx_runtime0 from "react/jsx-runtime";
5
5
 
6
- //#region src/PrintView.d.ts
7
- /**
8
- * Renders all slides vertically for print/PDF export.
9
- * Use with @media print CSS to generate PDFs via browser print.
10
- */
11
- declare function PrintView({
12
- children
13
- }: {
14
- children: ReactNode;
15
- }): react_jsx_runtime0.JSX.Element;
16
- //#endregion
17
6
  //#region src/DrawingLayer.d.ts
18
7
  interface DrawingLayerProps {
19
8
  /** Whether drawing mode is active */
@@ -77,7 +66,7 @@ interface ClickNavigationProps {
77
66
  * Invisible click zones on the left/right edges of the slide.
78
67
  * Clicking the left ~15% goes to the previous slide,
79
68
  * clicking the right ~15% goes to the next slide.
80
- * Shows a subtle arrow indicator on hover.
69
+ * Shows a gradient overlay and arrow indicator on hover (docswell style).
81
70
  */
82
71
  declare function ClickNavigation({
83
72
  onPrev,
@@ -98,6 +87,26 @@ declare function NavigationBar({
98
87
  onToggleDrawing: () => void;
99
88
  }): react_jsx_runtime0.JSX.Element;
100
89
  //#endregion
90
+ //#region src/Pointer.d.ts
91
+ interface PointerProps {
92
+ /** Whether pointer mode is active */
93
+ active: boolean;
94
+ /** Pointer color (default: uses --slide-accent or #16a34a) */
95
+ color?: string;
96
+ /** Pointer diameter in pixels */
97
+ size?: number;
98
+ }
99
+ /**
100
+ * Presentation pointer — a large colored circle that follows the cursor.
101
+ * Clicking creates an expanding ripple effect to highlight a point.
102
+ * Toggle with `q` key (handled in Deck).
103
+ */
104
+ declare function Pointer({
105
+ active,
106
+ color,
107
+ size
108
+ }: PointerProps): react_jsx_runtime0.JSX.Element;
109
+ //#endregion
101
110
  //#region src/ProgressBar.d.ts
102
111
  declare function ProgressBar(): react_jsx_runtime0.JSX.Element;
103
112
  //#endregion
@@ -182,4 +191,4 @@ declare function useDeck(): DeckContextValue;
182
191
  declare const SlideIndexContext: react.Context<number | null>;
183
192
  declare function useSlideIndex(): number;
184
193
  //#endregion
185
- export { Click, ClickNavigation, type ClickProps, ClickSteps, CodeEditor, type CodeEditorProps, Deck, type DeckActions, DeckContext, type DeckContextValue, type DeckProps, type DeckState, Draggable, type DraggableProps, DrawingLayer, type DrawingLayerProps, GlobalLayer, type GlobalLayerProps, Mark, type MarkProps, Mermaid, type MermaidProps, NavigationBar, Notes, type NotesProps, PresenterView, type PresenterViewProps, PrintView, ProgressBar, ReslideEmbed, type ReslideEmbedProps, Slide, SlideIndexContext, SlideNumber, type SlideProps, SlotRight, Toc, type TocProps, type TransitionType, isPresenterView, openPresenterWindow, useDeck, useSlideIndex };
194
+ export { Click, ClickNavigation, type ClickProps, ClickSteps, CodeEditor, type CodeEditorProps, Deck, type DeckActions, DeckContext, type DeckContextValue, type DeckProps, type DeckState, Draggable, type DraggableProps, DrawingLayer, type DrawingLayerProps, GlobalLayer, type GlobalLayerProps, Mark, type MarkProps, Mermaid, type MermaidProps, NavigationBar, Notes, type NotesProps, Pointer, type PointerProps, PresenterView, type PresenterViewProps, PrintView, ProgressBar, ReslideEmbed, type ReslideEmbedProps, Slide, SlideIndexContext, SlideNumber, type SlideProps, SlotRight, Toc, type TocProps, type TransitionType, isPresenterView, openPresenterWindow, useDeck, useSlideIndex };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as useDeck, S as DeckContext, _ as useSlideIndex, a as PresenterView, b as openPresenterWindow, c as Mark, d as Slide, f as Deck, g as SlideIndexContext, h as PrintView, i as GlobalLayer, l as Click, m as ProgressBar, n as Toc, o as SlotRight, p as SlideNumber, r as Draggable, s as Notes, t as Mermaid, u as ClickSteps, v as NavigationBar, w as ClickNavigation, x as DrawingLayer, y as isPresenterView } from "./Mermaid-CUCMRIkQ.mjs";
1
+ import { C as DeckContext, S as DrawingLayer, T as ClickNavigation, _ as useSlideIndex, a as PresenterView, b as isPresenterView, c as Mark, d as Slide, f as Deck, g as SlideIndexContext, h as PrintView, i as GlobalLayer, l as Click, m as ProgressBar, n as Toc, o as SlotRight, p as SlideNumber, r as Draggable, s as Notes, t as Mermaid, u as ClickSteps, v as Pointer, w as useDeck, x as openPresenterWindow, y as NavigationBar } from "./Mermaid-BfetSkya.mjs";
2
2
  import { Fragment, useCallback, useEffect, useRef, useState } from "react";
3
3
  import * as runtime from "react/jsx-runtime";
4
4
  import { jsx } from "react/jsx-runtime";
@@ -180,4 +180,4 @@ function ReslideEmbed({ code, transition, components: userComponents, className,
180
180
  });
181
181
  }
182
182
  //#endregion
183
- export { Click, ClickNavigation, ClickSteps, CodeEditor, Deck, DeckContext, Draggable, DrawingLayer, GlobalLayer, Mark, Mermaid, NavigationBar, Notes, PresenterView, PrintView, ProgressBar, ReslideEmbed, Slide, SlideIndexContext, SlideNumber, SlotRight, Toc, isPresenterView, openPresenterWindow, useDeck, useSlideIndex };
183
+ export { Click, ClickNavigation, ClickSteps, CodeEditor, Deck, DeckContext, Draggable, DrawingLayer, GlobalLayer, Mark, Mermaid, NavigationBar, Notes, Pointer, PresenterView, PrintView, ProgressBar, ReslideEmbed, Slide, SlideIndexContext, SlideNumber, SlotRight, Toc, isPresenterView, openPresenterWindow, useDeck, useSlideIndex };
@@ -1,4 +1,4 @@
1
- import { C as Deck, a as Draggable, b as ClickSteps, d as PresenterView, f as PresenterViewProps, g as Mark, l as isPresenterView, m as Notes, p as SlotRight, r as Toc, s as GlobalLayer, t as Mermaid, v as Click, x as Slide } from "./Mermaid-CUQUPHdK.mjs";
1
+ import { S as Slide, _ as Mark, a as Draggable, d as PresenterView, f as PresenterViewProps, h as Notes, l as isPresenterView, m as SlotRight, p as PrintView, r as Toc, s as GlobalLayer, t as Mermaid, w as Deck, x as ClickSteps, y as Click } from "./Mermaid-DWHv8vPf.mjs";
2
2
 
3
3
  //#region src/mdx-components.d.ts
4
4
  declare const reslideComponents: {
@@ -15,4 +15,4 @@ declare const reslideComponents: {
15
15
  Mermaid: typeof Mermaid;
16
16
  };
17
17
  //#endregion
18
- export { PresenterView, type PresenterViewProps, isPresenterView, reslideComponents };
18
+ export { PresenterView, type PresenterViewProps, PrintView, isPresenterView, reslideComponents };
@@ -1,4 +1,4 @@
1
- import { a as PresenterView, c as Mark, d as Slide, f as Deck, i as GlobalLayer, l as Click, n as Toc, o as SlotRight, r as Draggable, s as Notes, t as Mermaid, u as ClickSteps, y as isPresenterView } from "./Mermaid-CUCMRIkQ.mjs";
1
+ import { a as PresenterView, b as isPresenterView, c as Mark, d as Slide, f as Deck, h as PrintView, i as GlobalLayer, l as Click, n as Toc, o as SlotRight, r as Draggable, s as Notes, t as Mermaid, u as ClickSteps } from "./Mermaid-BfetSkya.mjs";
2
2
  //#region src/mdx-components.ts
3
3
  const reslideComponents = {
4
4
  Deck,
@@ -14,4 +14,4 @@ const reslideComponents = {
14
14
  Mermaid
15
15
  };
16
16
  //#endregion
17
- export { PresenterView, isPresenterView, reslideComponents };
17
+ export { PresenterView, PrintView, isPresenterView, reslideComponents };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reslide-dev/core",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Framework-agnostic React presentation components",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -1,7 +1,7 @@
1
1
  :root {
2
2
  --slide-bg: #ffffff;
3
3
  --slide-text: #1a1a2e;
4
- --slide-accent: #3b82f6;
4
+ --slide-accent: #16a34a;
5
5
  --slide-section-text: #ffffff;
6
6
 
7
7
  --mark-yellow: #fef08a;
@@ -1,58 +1,87 @@
1
1
  /**
2
2
  * Print stylesheet for reslide presentations.
3
- * Import this to enable Ctrl+P / Cmd+P PDF export.
3
+ * Handout layout: 6 slides per page (2 columns × 3 rows).
4
4
  *
5
- * Usage: import '@reslide/core/themes/print.css'
6
- *
7
- * This renders all slides vertically for printing,
8
- * each slide on its own page at 16:9 aspect ratio.
5
+ * Usage: import '@reslide-dev/core/print.css'
9
6
  */
10
- @media print {
11
- /* Reset the deck for print layout */
12
- .reslide-deck {
13
- width: auto !important;
14
- height: auto !important;
15
- overflow: visible !important;
16
- position: static !important;
17
- }
18
7
 
19
- /* Show all slides stacked vertically */
20
- .reslide-slide {
21
- page-break-after: always;
22
- page-break-inside: avoid;
23
- width: 100vw !important;
24
- height: 56.25vw !important; /* 16:9 aspect ratio */
25
- max-height: 100vh;
26
- position: relative !important;
27
- overflow: hidden !important;
28
- }
8
+ /* ── Screen preview ── */
9
+ .reslide-print-page {
10
+ background: #f0f0f0;
11
+ min-height: 100vh;
12
+ padding: 2rem;
13
+ -webkit-print-color-adjust: exact;
14
+ print-color-adjust: exact;
15
+ }
16
+
17
+ .reslide-print-page .reslide-print-view {
18
+ max-width: 800px;
19
+ margin: 0 auto;
20
+ display: grid;
21
+ grid-template-columns: 1fr 1fr;
22
+ gap: 1.5rem;
23
+ }
24
+
25
+ .reslide-print-page .reslide-print-slide-card {
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: 0.25rem;
29
+ }
29
30
 
30
- /* Remove the last page break */
31
- .reslide-slide:last-child {
32
- page-break-after: auto;
31
+ .reslide-print-page .reslide-print-slide-frame {
32
+ aspect-ratio: 16 / 9;
33
+ border: 1px solid #ccc;
34
+ border-radius: 4px;
35
+ overflow: hidden;
36
+ background: var(--slide-bg, white);
37
+ position: relative;
38
+ }
39
+
40
+ .reslide-print-page .reslide-print-slide-content {
41
+ overflow: hidden;
42
+ }
43
+
44
+ .reslide-print-page .reslide-print-slide-label {
45
+ font-size: 0.75rem;
46
+ color: #888;
47
+ text-align: center;
48
+ font-family: system-ui, sans-serif;
49
+ }
50
+
51
+ /* Show all click content */
52
+ .reslide-print-page .reslide-click {
53
+ opacity: 1 !important;
54
+ visibility: visible !important;
55
+ }
56
+
57
+ /* ── Print layout ── */
58
+ @media print {
59
+ .reslide-print-page {
60
+ background: white !important;
61
+ padding: 0 !important;
33
62
  }
34
63
 
35
- /* Hide UI elements */
36
- .reslide-slide-number,
37
- .reslide-overview,
38
- .reslide-notes,
39
- canvas {
40
- display: none !important;
64
+ .reslide-print-page .reslide-print-view {
65
+ max-width: none !important;
66
+ display: grid !important;
67
+ grid-template-columns: 1fr 1fr !important;
68
+ gap: 8mm !important;
69
+ padding: 10mm !important;
41
70
  }
42
71
 
43
- /* Show all click content */
44
- .reslide-click {
45
- opacity: 1 !important;
46
- visibility: visible !important;
72
+ .reslide-print-page .reslide-print-slide-frame {
73
+ border: 0.5pt solid #ccc !important;
74
+ border-radius: 0 !important;
47
75
  }
48
76
 
49
- /* Ensure transition container shows content */
50
- .reslide-transition-container {
51
- position: static !important;
77
+ .reslide-print-page .reslide-print-slide-label {
78
+ font-size: 7pt !important;
79
+ color: #666 !important;
80
+ margin-top: 1mm !important;
52
81
  }
53
82
 
54
- .reslide-transition-slide {
55
- position: static !important;
56
- animation: none !important;
83
+ /* 6 slides per page (2×3) — force page break after every 6th card */
84
+ .reslide-print-page .reslide-print-slide-card:nth-child(6n) {
85
+ page-break-after: always;
57
86
  }
58
87
  }