@reslide-dev/core 0.2.1 → 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,12 +864,41 @@ 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
  });
671
871
  }
672
872
  //#endregion
873
+ //#region src/SlideNumber.tsx
874
+ /**
875
+ * Displays the current slide number in the bottom-right corner.
876
+ * Automatically reads state from DeckContext.
877
+ */
878
+ function SlideNumber() {
879
+ const { currentSlide, totalSlides } = useDeck();
880
+ return /* @__PURE__ */ jsxs("div", {
881
+ className: "reslide-slide-number",
882
+ style: {
883
+ position: "absolute",
884
+ bottom: "0.75rem",
885
+ right: "1rem",
886
+ fontSize: "0.75rem",
887
+ fontFamily: "system-ui, sans-serif",
888
+ fontVariantNumeric: "tabular-nums",
889
+ color: "var(--slide-text, #1a1a1a)",
890
+ opacity: .4,
891
+ pointerEvents: "none",
892
+ zIndex: 10
893
+ },
894
+ children: [
895
+ currentSlide + 1,
896
+ " / ",
897
+ totalSlides
898
+ ]
899
+ });
900
+ }
901
+ //#endregion
673
902
  //#region src/SlideTransition.tsx
674
903
  const DURATION = 300;
675
904
  function SlideTransition({ children, currentSlide, transition }) {
@@ -756,14 +985,20 @@ function useFullscreen(ref) {
756
985
  }
757
986
  //#endregion
758
987
  //#region src/Deck.tsx
759
- 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 }) {
760
992
  const containerRef = useRef(null);
993
+ const deckRef = useRef(null);
761
994
  const [currentSlide, setCurrentSlide] = useState(0);
762
995
  const [clickStep, setClickStep] = useState(0);
763
996
  const [isOverview, setIsOverview] = useState(false);
764
997
  const [isDrawing, setIsDrawing] = useState(false);
998
+ const [isPointer, setIsPointer] = useState(false);
765
999
  const [isPrinting, setIsPrinting] = useState(false);
766
1000
  const [clickStepsMap, setClickStepsMap] = useState({});
1001
+ const [scale, setScale] = useState(1);
767
1002
  const { isFullscreen, toggleFullscreen } = useFullscreen(containerRef);
768
1003
  const totalSlides = Children.count(children);
769
1004
  const totalClickSteps = clickStepsMap[currentSlide] ?? 0;
@@ -826,6 +1061,7 @@ function Deck({ children, transition = "none" }) {
826
1061
  useEffect(() => {
827
1062
  function handleKeyDown(e) {
828
1063
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
1064
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
829
1065
  switch (e.key) {
830
1066
  case "ArrowRight":
831
1067
  case " ":
@@ -853,6 +1089,12 @@ function Deck({ children, transition = "none" }) {
853
1089
  case "d":
854
1090
  e.preventDefault();
855
1091
  setIsDrawing((v) => !v);
1092
+ setIsPointer(false);
1093
+ break;
1094
+ case "q":
1095
+ e.preventDefault();
1096
+ setIsPointer((v) => !v);
1097
+ setIsDrawing(false);
856
1098
  break;
857
1099
  }
858
1100
  }
@@ -864,6 +1106,36 @@ function Deck({ children, transition = "none" }) {
864
1106
  toggleOverview,
865
1107
  toggleFullscreen
866
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
+ ]);
867
1139
  useEffect(() => {
868
1140
  function onBeforePrint() {
869
1141
  setIsPrinting(true);
@@ -878,6 +1150,18 @@ function Deck({ children, transition = "none" }) {
878
1150
  window.removeEventListener("afterprint", onAfterPrint);
879
1151
  };
880
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
+ }, []);
881
1165
  const contextValue = useMemo(() => ({
882
1166
  currentSlide,
883
1167
  totalSlides,
@@ -905,21 +1189,32 @@ function Deck({ children, transition = "none" }) {
905
1189
  toggleFullscreen,
906
1190
  registerClickSteps
907
1191
  ]);
908
- return /* @__PURE__ */ jsx(DeckContext.Provider, {
909
- value: contextValue,
910
- children: /* @__PURE__ */ jsxs("div", {
911
- ref: containerRef,
912
- className: "reslide-deck",
913
- style: {
914
- position: "relative",
915
- width: "100%",
916
- height: "100%",
917
- overflow: "hidden",
918
- backgroundColor: "var(--slide-bg, #fff)",
919
- color: "var(--slide-text, #1a1a1a)"
920
- },
921
- children: [
922
- 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, {
923
1218
  totalSlides,
924
1219
  goTo,
925
1220
  children
@@ -927,22 +1222,61 @@ function Deck({ children, transition = "none" }) {
927
1222
  currentSlide,
928
1223
  transition,
929
1224
  children
930
- }),
931
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ClickNavigation, {
932
- onPrev: prev,
933
- onNext: next,
934
- disabled: isDrawing
935
- }),
936
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(ProgressBar, {}),
937
- !isOverview && !isPrinting && /* @__PURE__ */ jsx(DrawingLayer, {
938
- active: isDrawing,
939
- currentSlide
940
- }),
941
- !isPrinting && /* @__PURE__ */ jsx(NavigationBar, {
942
- isDrawing,
943
- onToggleDrawing: () => setIsDrawing((v) => !v)
944
1225
  })
945
- ]
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
+ })
946
1280
  })
947
1281
  });
948
1282
  }
@@ -963,7 +1297,7 @@ function OverviewGrid({ children, totalSlides, goTo }) {
963
1297
  type: "button",
964
1298
  onClick: () => goTo(i),
965
1299
  style: {
966
- border: "1px solid var(--slide-accent, #3b82f6)",
1300
+ border: "1px solid var(--slide-accent, #16a34a)",
967
1301
  borderRadius: "0.5rem",
968
1302
  overflow: "hidden",
969
1303
  cursor: "pointer",
@@ -1110,7 +1444,7 @@ function Slide({ children, layout = "default", image, className, style }) {
1110
1444
  alignItems: "center",
1111
1445
  textAlign: "center",
1112
1446
  padding: "3rem 4rem",
1113
- backgroundColor: "var(--slide-accent, #3b82f6)",
1447
+ backgroundColor: "var(--slide-accent, #16a34a)",
1114
1448
  color: "var(--slide-section-text, #fff)",
1115
1449
  ...style
1116
1450
  },
@@ -1129,7 +1463,7 @@ function Slide({ children, layout = "default", image, className, style }) {
1129
1463
  style: {
1130
1464
  fontSize: "1.5em",
1131
1465
  fontStyle: "italic",
1132
- borderLeft: "4px solid var(--slide-accent, #3b82f6)",
1466
+ borderLeft: "4px solid var(--slide-accent, #16a34a)",
1133
1467
  paddingLeft: "1.5rem",
1134
1468
  margin: 0
1135
1469
  },
@@ -1254,6 +1588,51 @@ SlotRight.displayName = "SlotRight";
1254
1588
  SlotRight.__reslideSlot = "right";
1255
1589
  //#endregion
1256
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
+ }
1257
1636
  /**
1258
1637
  * Presenter view that syncs with the main presentation window.
1259
1638
  * Shows: current slide, next slide preview, notes, and timer.
@@ -1344,16 +1723,17 @@ function PresenterView({ children, notes }) {
1344
1723
  children: [
1345
1724
  /* @__PURE__ */ jsx("div", {
1346
1725
  style: {
1347
- border: "2px solid #3b82f6",
1726
+ border: "2px solid #16a34a",
1348
1727
  borderRadius: "0.5rem",
1349
1728
  overflow: "hidden",
1350
- position: "relative",
1351
- backgroundColor: "var(--slide-bg, #fff)",
1352
- color: "var(--slide-text, #1a1a1a)"
1729
+ display: "flex",
1730
+ alignItems: "center",
1731
+ justifyContent: "center",
1732
+ backgroundColor: "#000"
1353
1733
  },
1354
1734
  children: /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1355
1735
  value: currentSlide,
1356
- children: slides[currentSlide]
1736
+ children: /* @__PURE__ */ jsx(ScaledSlide, { children: slides[currentSlide] })
1357
1737
  })
1358
1738
  }),
1359
1739
  /* @__PURE__ */ jsxs("div", {
@@ -1365,25 +1745,29 @@ function PresenterView({ children, notes }) {
1365
1745
  },
1366
1746
  children: [/* @__PURE__ */ jsx("div", {
1367
1747
  style: {
1368
- flex: "0 0 40%",
1369
1748
  border: "1px solid #334155",
1370
1749
  borderRadius: "0.5rem",
1371
1750
  overflow: "hidden",
1372
1751
  opacity: .8,
1373
- backgroundColor: "var(--slide-bg, #fff)",
1374
- color: "var(--slide-text, #1a1a1a)"
1752
+ display: "flex",
1753
+ alignItems: "center",
1754
+ justifyContent: "center",
1755
+ backgroundColor: "#000"
1375
1756
  },
1376
- children: currentSlide < totalSlides - 1 && /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1757
+ children: currentSlide < totalSlides - 1 ? /* @__PURE__ */ jsx(SlideIndexContext.Provider, {
1377
1758
  value: currentSlide + 1,
1378
- children: /* @__PURE__ */ jsx("div", {
1379
- style: {
1380
- transform: "scale(0.5)",
1381
- transformOrigin: "top left",
1382
- width: "200%",
1383
- height: "200%"
1384
- },
1385
- children: slides[currentSlide + 1]
1386
- })
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"
1387
1771
  })
1388
1772
  }), /* @__PURE__ */ jsxs("div", {
1389
1773
  style: {
@@ -1596,7 +1980,7 @@ function extractHeading(node) {
1596
1980
  if (!isValidElement(node)) return null;
1597
1981
  const el = node;
1598
1982
  const type = el.type;
1599
- if (type === "h1" || type === "h2") return extractText(el.props.children);
1983
+ if (type === "h1" || type === "h2") return extractText$1(el.props.children);
1600
1984
  const children = el.props.children;
1601
1985
  if (children == null) return null;
1602
1986
  let result = null;
@@ -1608,12 +1992,12 @@ function extractHeading(node) {
1608
1992
  return result;
1609
1993
  }
1610
1994
  /** Extract plain text from a React node tree */
1611
- function extractText(node) {
1995
+ function extractText$1(node) {
1612
1996
  if (node == null) return "";
1613
1997
  if (typeof node === "string") return node;
1614
1998
  if (typeof node === "number") return String(node);
1615
- if (Array.isArray(node)) return node.map(extractText).join("");
1616
- 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);
1617
2001
  return "";
1618
2002
  }
1619
2003
  /**
@@ -1663,7 +2047,7 @@ function Toc({ children, className, style }) {
1663
2047
  fontSize: "0.875rem",
1664
2048
  lineHeight: 1.4,
1665
2049
  fontFamily: "inherit",
1666
- 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))",
1667
2051
  backgroundColor: isActive ? "var(--toc-active-bg, rgba(59, 130, 246, 0.1))" : "transparent",
1668
2052
  fontWeight: isActive ? 600 : 400,
1669
2053
  transition: "background-color 0.15s, color 0.15s"
@@ -1691,6 +2075,25 @@ function Toc({ children, className, style }) {
1691
2075
  //#endregion
1692
2076
  //#region src/Mermaid.tsx
1693
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
+ /**
1694
2097
  * Renders a Mermaid diagram.
1695
2098
  *
1696
2099
  * Usage in MDX:
@@ -1709,7 +2112,9 @@ function Mermaid({ children }) {
1709
2112
  const [svg, setSvg] = useState("");
1710
2113
  const [error, setError] = useState("");
1711
2114
  const id = useId().replace(/:/g, "_");
2115
+ const code = extractText(children).trim();
1712
2116
  useEffect(() => {
2117
+ if (!code) return;
1713
2118
  let cancelled = false;
1714
2119
  async function render() {
1715
2120
  try {
@@ -1719,8 +2124,6 @@ function Mermaid({ children }) {
1719
2124
  theme: "default",
1720
2125
  securityLevel: "loose"
1721
2126
  });
1722
- const code = typeof children === "string" ? children.trim() : "";
1723
- if (!code) return;
1724
2127
  const { svg: rendered } = await mermaid.default.render(`mermaid-${id}`, code);
1725
2128
  if (!cancelled) {
1726
2129
  setSvg(rendered);
@@ -1734,7 +2137,7 @@ function Mermaid({ children }) {
1734
2137
  return () => {
1735
2138
  cancelled = true;
1736
2139
  };
1737
- }, [children, id]);
2140
+ }, [code, id]);
1738
2141
  if (error) return /* @__PURE__ */ jsx("div", {
1739
2142
  className: "reslide-mermaid reslide-mermaid--error",
1740
2143
  children: /* @__PURE__ */ jsx("pre", { children: error })
@@ -1746,4 +2149,4 @@ function Mermaid({ children }) {
1746
2149
  });
1747
2150
  }
1748
2151
  //#endregion
1749
- export { ClickNavigation as C, useDeck as S, NavigationBar as _, PresenterView as a, DrawingLayer as b, Mark as c, Slide as d, Deck as f, useSlideIndex as g, SlideIndexContext as h, GlobalLayer as i, Click as l, PrintView as m, Toc as n, SlotRight as o, ProgressBar as p, Draggable as r, Notes as s, Mermaid as t, ClickSteps as u, isPresenterView as v, DeckContext as x, openPresenterWindow 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,9 +87,36 @@ 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
113
+ //#region src/SlideNumber.d.ts
114
+ /**
115
+ * Displays the current slide number in the bottom-right corner.
116
+ * Automatically reads state from DeckContext.
117
+ */
118
+ declare function SlideNumber(): react_jsx_runtime0.JSX.Element;
119
+ //#endregion
104
120
  //#region src/ReslideEmbed.d.ts
105
121
  interface ReslideEmbedProps {
106
122
  /** Compiled MDX code from compileMdxSlides() */
@@ -175,4 +191,4 @@ declare function useDeck(): DeckContextValue;
175
191
  declare const SlideIndexContext: react.Context<number | null>;
176
192
  declare function useSlideIndex(): number;
177
193
  //#endregion
178
- 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, 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 ClickNavigation, S as useDeck, _ as NavigationBar, a as PresenterView, b as DrawingLayer, c as Mark, d as Slide, f as Deck, g as useSlideIndex, h as SlideIndexContext, i as GlobalLayer, l as Click, m as PrintView, n as Toc, o as SlotRight, p as ProgressBar, r as Draggable, s as Notes, t as Mermaid, u as ClickSteps, v as isPresenterView, x as DeckContext, y as openPresenterWindow } from "./Mermaid-BtXG2Ea2.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";
@@ -117,7 +117,10 @@ const builtinComponents = {
117
117
  Notes,
118
118
  SlotRight,
119
119
  GlobalLayer,
120
- Draggable
120
+ Draggable,
121
+ Mermaid,
122
+ Toc,
123
+ CodeEditor
121
124
  };
122
125
  /**
123
126
  * Renders compiled MDX slides as a full reslide presentation.
@@ -177,4 +180,4 @@ function ReslideEmbed({ code, transition, components: userComponents, className,
177
180
  });
178
181
  }
179
182
  //#endregion
180
- export { Click, ClickNavigation, ClickSteps, CodeEditor, Deck, DeckContext, Draggable, DrawingLayer, GlobalLayer, Mark, Mermaid, NavigationBar, Notes, PresenterView, PrintView, ProgressBar, ReslideEmbed, Slide, SlideIndexContext, 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, v as isPresenterView } from "./Mermaid-BtXG2Ea2.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.1",
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
  }