pxengine 0.1.74 → 0.1.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -38864,23 +38864,26 @@ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("svg", { w
38864
38864
  ] });
38865
38865
  var ChevronLeft2 = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "15 18 9 12 15 6" }) });
38866
38866
  var ChevronRight2 = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "9 18 15 12 9 6" }) });
38867
+ var ExpandIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
38868
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "15 3 21 3 21 9" }),
38869
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "9 21 3 21 3 15" }),
38870
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
38871
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
38872
+ ] });
38873
+ var ShareIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
38874
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
38875
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
38876
+ ] });
38877
+ var CheckIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
38867
38878
  var Skel = ({ className, style }) => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: cn("animate-pulse rounded-md bg-zinc-800/60", className), style });
38868
38879
  var FORMATS = [
38869
- {
38870
- key: "pptx_url",
38871
- label: "PowerPoint",
38872
- ext: ".pptx",
38873
- emoji: "\u{1F4CA}",
38874
- desc: "Editable slides \u2014 open in Microsoft PowerPoint or Google Slides",
38875
- accent: { text: "text-orange-400", bg: "bg-orange-500/10 border-orange-500/20", pill: "bg-orange-500/15 text-orange-400 border-orange-500/25" }
38876
- },
38877
38880
  {
38878
38881
  key: "pdf_url",
38879
38882
  label: "PDF",
38880
38883
  ext: ".pdf",
38881
38884
  emoji: "\u{1F4C4}",
38882
38885
  desc: "Print-ready, fixed layout \u2014 open in any PDF viewer",
38883
- accent: { text: "text-red-400", bg: "bg-red-500/10 border-red-500/20", pill: "bg-red-500/15 text-red-400 border-red-500/25" }
38886
+ accent: { text: "text-red-400", bg: "bg-red-500/10 border-red-500/20" }
38884
38887
  },
38885
38888
  {
38886
38889
  key: "html_url",
@@ -38888,12 +38891,36 @@ var FORMATS = [
38888
38891
  ext: ".html",
38889
38892
  emoji: "\u{1F310}",
38890
38893
  desc: "Interactive web presentation \u2014 download and open in browser",
38891
- accent: { text: "text-indigo-400", bg: "bg-indigo-500/10 border-indigo-500/20", pill: "bg-indigo-500/15 text-indigo-400 border-indigo-500/25" }
38894
+ accent: { text: "text-indigo-400", bg: "bg-indigo-500/10 border-indigo-500/20" }
38892
38895
  }
38893
38896
  ];
38894
38897
  var ExportModal = ({ formats, title, onClose }) => {
38895
38898
  const available = FORMATS.filter((f) => formats[f.key]);
38896
38899
  const filename = title.replace(/[^a-z0-9]/gi, "-").toLowerCase();
38900
+ const [downloadingKey, setDownloadingKey] = (0, import_react74.useState)(null);
38901
+ const handleDownload = async (fmtKey, url, ext) => {
38902
+ if (downloadingKey) return;
38903
+ const downloadName = `${filename}${ext}`;
38904
+ const href = `/api/download?url=${encodeURIComponent(url)}&filename=${encodeURIComponent(downloadName)}`;
38905
+ try {
38906
+ setDownloadingKey(fmtKey);
38907
+ const response = await fetch(href);
38908
+ if (!response.ok) throw new Error(`status ${response.status}`);
38909
+ const blob = await response.blob();
38910
+ const objectUrl = window.URL.createObjectURL(blob);
38911
+ const anchor = document.createElement("a");
38912
+ anchor.href = objectUrl;
38913
+ anchor.download = downloadName;
38914
+ document.body.appendChild(anchor);
38915
+ anchor.click();
38916
+ anchor.remove();
38917
+ window.URL.revokeObjectURL(objectUrl);
38918
+ } catch {
38919
+ window.open(href, "_blank", "noopener,noreferrer");
38920
+ } finally {
38921
+ setDownloadingKey(null);
38922
+ }
38923
+ };
38897
38924
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4", children: [
38898
38925
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "absolute inset-0 bg-black/70 backdrop-blur-sm", onClick: onClose }),
38899
38926
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "relative z-10 bg-zinc-900 border border-zinc-800 rounded-2xl w-full max-w-sm p-6 shadow-2xl", children: [
@@ -38902,38 +38929,31 @@ var ExportModal = ({ formats, title, onClose }) => {
38902
38929
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("h3", { className: "text-sm font-semibold text-zinc-100", children: "Export Presentation" }),
38903
38930
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5 truncate", children: title })
38904
38931
  ] }),
38905
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
38906
- "button",
38907
- {
38908
- onClick: onClose,
38909
- className: "text-zinc-500 hover:text-zinc-200 transition-colors flex-shrink-0 mt-0.5",
38910
- "aria-label": "Close",
38911
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CloseIcon, {})
38912
- }
38913
- )
38932
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("button", { onClick: onClose, className: "text-zinc-500 hover:text-zinc-200 transition-colors flex-shrink-0 mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CloseIcon, {}) })
38914
38933
  ] }),
38915
38934
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "space-y-2.5", children: [
38916
38935
  available.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-sm text-zinc-500 text-center py-6", children: "No formats available yet" }),
38917
38936
  available.map((fmt) => {
38918
38937
  const url = formats[fmt.key];
38919
- const dlName = `${filename}${fmt.ext}`;
38938
+ const isDownloading = downloadingKey === fmt.key;
38920
38939
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
38921
- "a",
38940
+ "button",
38922
38941
  {
38923
- href: url,
38924
- download: dlName,
38942
+ type: "button",
38943
+ disabled: Boolean(downloadingKey),
38944
+ onClick: () => handleDownload(fmt.key, url, fmt.ext),
38925
38945
  className: cn(
38926
- "flex items-center gap-3.5 p-3.5 rounded-xl border transition-all",
38927
- "hover:brightness-110 cursor-pointer no-underline group",
38946
+ "flex w-full items-center gap-3.5 p-3.5 rounded-xl border transition-all text-left",
38947
+ "hover:brightness-110 disabled:opacity-60 disabled:cursor-not-allowed",
38928
38948
  fmt.accent.bg
38929
38949
  ),
38930
38950
  children: [
38931
38951
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: "text-xl flex-shrink-0", children: fmt.emoji }),
38932
38952
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex-1 min-w-0", children: [
38933
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: cn("text-sm font-semibold", fmt.accent.text), children: fmt.label }),
38934
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5 leading-relaxed", children: fmt.desc })
38953
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: cn("text-sm font-semibold", fmt.accent.text), children: isDownloading ? `Downloading ${fmt.label}...` : fmt.label }),
38954
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5 leading-relaxed", children: isDownloading ? "Please wait while the file is being prepared." : fmt.desc })
38935
38955
  ] }),
38936
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: cn("flex-shrink-0 transition-colors", fmt.accent.text), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}) })
38956
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: cn("flex-shrink-0", fmt.accent.text), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}) })
38937
38957
  ]
38938
38958
  },
38939
38959
  fmt.key
@@ -38944,6 +38964,87 @@ var ExportModal = ({ formats, title, onClose }) => {
38944
38964
  ] })
38945
38965
  ] });
38946
38966
  };
38967
+ var FullscreenModal = ({ url, title, slideCount, onClose }) => {
38968
+ const [currentSlide, setCurrentSlide] = (0, import_react74.useState)(1);
38969
+ const iframeRef = (0, import_react74.useRef)(null);
38970
+ (0, import_react74.useEffect)(() => {
38971
+ const onKey = (e) => {
38972
+ if (e.key === "Escape") onClose();
38973
+ };
38974
+ const onMsg = (e) => {
38975
+ if (e.data?.type === "presentationExit") onClose();
38976
+ if (e.data?.type === "slideChanged") setCurrentSlide(e.data.slide);
38977
+ };
38978
+ document.addEventListener("keydown", onKey);
38979
+ window.addEventListener("message", onMsg);
38980
+ return () => {
38981
+ document.removeEventListener("keydown", onKey);
38982
+ window.removeEventListener("message", onMsg);
38983
+ };
38984
+ }, [onClose]);
38985
+ (0, import_react74.useEffect)(() => {
38986
+ document.body.style.overflow = "hidden";
38987
+ return () => {
38988
+ document.body.style.overflow = "";
38989
+ };
38990
+ }, []);
38991
+ const send = (type) => iframeRef.current?.contentWindow?.postMessage({ type }, "*");
38992
+ return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "fixed inset-0 z-50 bg-black flex flex-col", children: [
38993
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center justify-between gap-4 px-4 h-11 bg-zinc-950/90 border-b border-zinc-800/60 flex-shrink-0", children: [
38994
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-3 min-w-0", children: [
38995
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: "text-indigo-400 flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(SlidesIcon, {}) }),
38996
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: "text-sm font-medium text-zinc-300 truncate", children: title }),
38997
+ slideCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "text-xs text-zinc-600 flex-shrink-0", children: [
38998
+ currentSlide,
38999
+ " / ",
39000
+ slideCount
39001
+ ] })
39002
+ ] }),
39003
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
39004
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39005
+ "button",
39006
+ {
39007
+ onClick: () => send("prevSlide"),
39008
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors",
39009
+ "aria-label": "Previous slide",
39010
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronLeft2, {})
39011
+ }
39012
+ ),
39013
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39014
+ "button",
39015
+ {
39016
+ onClick: () => send("nextSlide"),
39017
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors",
39018
+ "aria-label": "Next slide",
39019
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronRight2, {})
39020
+ }
39021
+ ),
39022
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39023
+ "button",
39024
+ {
39025
+ onClick: onClose,
39026
+ className: "flex items-center gap-1.5 px-3 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-400 hover:text-zinc-200 text-xs transition-colors",
39027
+ children: [
39028
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CloseIcon, {}),
39029
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { children: "ESC" })
39030
+ ]
39031
+ }
39032
+ )
39033
+ ] })
39034
+ ] }),
39035
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "flex-1 relative", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39036
+ "iframe",
39037
+ {
39038
+ ref: iframeRef,
39039
+ src: url,
39040
+ title,
39041
+ onLoad: () => iframeRef.current?.contentWindow?.postMessage({ type: "goToSlide", slide: currentSlide }, "*"),
39042
+ sandbox: "allow-same-origin allow-scripts allow-fullscreen",
39043
+ className: "absolute inset-0 w-full h-full border-0"
39044
+ }
39045
+ ) })
39046
+ ] });
39047
+ };
38947
39048
  var PresentationJobCard = ({
38948
39049
  job_id: _job_id,
38949
39050
  title: initialTitle,
@@ -38952,6 +39053,7 @@ var PresentationJobCard = ({
38952
39053
  formats: initialFormats = {},
38953
39054
  error: initialError,
38954
39055
  pollUrl,
39056
+ shareUrl,
38955
39057
  className
38956
39058
  }) => {
38957
39059
  const [status, setStatus] = (0, import_react74.useState)(initialStatus);
@@ -38959,7 +39061,9 @@ var PresentationJobCard = ({
38959
39061
  const [slideCount, setSlideCount] = (0, import_react74.useState)(initialSlideCount ?? 0);
38960
39062
  const [formats, setFormats] = (0, import_react74.useState)(initialFormats);
38961
39063
  const [error, setError] = (0, import_react74.useState)(initialError);
38962
- const [showModal, setShowModal] = (0, import_react74.useState)(false);
39064
+ const [showExport, setShowExport] = (0, import_react74.useState)(false);
39065
+ const [showFullscreen, setShowFullscreen] = (0, import_react74.useState)(false);
39066
+ const [copied, setCopied] = (0, import_react74.useState)(false);
38963
39067
  const [currentSlide, setCurrentSlide] = (0, import_react74.useState)(1);
38964
39068
  const [previewScale, setPreviewScale] = (0, import_react74.useState)(1);
38965
39069
  const [iframeReady, setIframeReady] = (0, import_react74.useState)(false);
@@ -38968,9 +39072,7 @@ var PresentationJobCard = ({
38968
39072
  const previewRef = (0, import_react74.useRef)(null);
38969
39073
  const iframeRef = (0, import_react74.useRef)(null);
38970
39074
  const updateScale = (0, import_react74.useCallback)(() => {
38971
- if (previewRef.current) {
38972
- setPreviewScale(previewRef.current.offsetWidth / 1280);
38973
- }
39075
+ if (previewRef.current) setPreviewScale(previewRef.current.offsetWidth / 1280);
38974
39076
  }, []);
38975
39077
  (0, import_react74.useEffect)(() => {
38976
39078
  updateScale();
@@ -38987,9 +39089,7 @@ var PresentationJobCard = ({
38987
39089
  const handler = (e) => {
38988
39090
  if (e.data?.type === "slideChanged") {
38989
39091
  setCurrentSlide(e.data.slide);
38990
- if (e.data.total && !slideCount) {
38991
- setSlideCount(e.data.total);
38992
- }
39092
+ if (e.data.total && !slideCount) setSlideCount(e.data.total);
38993
39093
  }
38994
39094
  };
38995
39095
  window.addEventListener("message", handler);
@@ -39000,6 +39100,10 @@ var PresentationJobCard = ({
39000
39100
  iframeRef.current?.contentWindow?.postMessage({ type: "goToSlide", slide: pendingSlide }, "*");
39001
39101
  setPendingSlide(null);
39002
39102
  }, [iframeReady, pendingSlide]);
39103
+ const sendSlideCommand = (command) => {
39104
+ if (!iframeReady) return;
39105
+ iframeRef.current?.contentWindow?.postMessage({ type: command }, "*");
39106
+ };
39003
39107
  const goToSlide = (n) => {
39004
39108
  const total2 = slideCount || 1;
39005
39109
  const target = (n - 1 + total2) % total2 + 1;
@@ -39025,9 +39129,7 @@ var PresentationJobCard = ({
39025
39129
  if (data.output.slide_count) setSlideCount(data.output.slide_count);
39026
39130
  if (data.output.formats) setFormats(data.output.formats);
39027
39131
  }
39028
- if (newStatus === "failed" && data.error) {
39029
- setError(data.error);
39030
- }
39132
+ if (newStatus === "failed" && data.error) setError(data.error);
39031
39133
  } catch {
39032
39134
  }
39033
39135
  };
@@ -39043,6 +39145,17 @@ var PresentationJobCard = ({
39043
39145
  intervalRef.current = null;
39044
39146
  }
39045
39147
  }, [isTerminal]);
39148
+ const handleShare = async () => {
39149
+ const link = shareUrl ?? formats.html_url;
39150
+ if (!link) return;
39151
+ try {
39152
+ await navigator.clipboard.writeText(link);
39153
+ setCopied(true);
39154
+ setTimeout(() => setCopied(false), 2e3);
39155
+ } catch {
39156
+ window.open(link, "_blank", "noopener,noreferrer");
39157
+ }
39158
+ };
39046
39159
  if (status === "pending" || status === "running") {
39047
39160
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl p-5", children: [
39048
39161
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-3 mb-5", children: [
@@ -39068,8 +39181,8 @@ var PresentationJobCard = ({
39068
39181
  ] })
39069
39182
  ] }) }) });
39070
39183
  }
39071
- const availablePills = FORMATS.filter((f) => formats[f.key]);
39072
39184
  const total = slideCount || 1;
39185
+ const hasHtml = Boolean(formats.html_url);
39073
39186
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(import_jsx_runtime146.Fragment, { children: [
39074
39187
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
39075
39188
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "h-[3px] bg-gradient-to-r from-indigo-600 via-violet-500 to-purple-600" }),
@@ -39083,106 +39196,127 @@ var PresentationJobCard = ({
39083
39196
  " \xB7 Ready to export"
39084
39197
  ] })
39085
39198
  ] }),
39086
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39087
- "button",
39088
- {
39089
- onClick: () => setShowModal(true),
39090
- className: "flex items-center gap-1.5 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-500 active:bg-indigo-700 text-white text-xs font-medium rounded-lg transition-colors flex-shrink-0",
39091
- children: [
39092
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}),
39093
- "Export"
39094
- ]
39095
- }
39096
- )
39199
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
39200
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39201
+ "button",
39202
+ {
39203
+ onClick: handleShare,
39204
+ disabled: !hasHtml,
39205
+ title: copied ? "Copied!" : "Copy shareable link",
39206
+ className: cn(
39207
+ "w-7 h-7 rounded-lg border flex items-center justify-center transition-all",
39208
+ copied ? "border-green-500/40 bg-green-500/10 text-green-400" : "border-zinc-700 bg-zinc-800 text-zinc-400 hover:border-zinc-500 hover:text-zinc-200",
39209
+ "disabled:opacity-30 disabled:cursor-not-allowed"
39210
+ ),
39211
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CheckIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ShareIcon, {})
39212
+ }
39213
+ ),
39214
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39215
+ "button",
39216
+ {
39217
+ onClick: () => setShowFullscreen(true),
39218
+ disabled: !hasHtml,
39219
+ title: "Open fullscreen preview",
39220
+ className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-400 hover:text-zinc-200 text-xs font-medium transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39221
+ children: [
39222
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ExpandIcon, {}),
39223
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { children: "Preview" })
39224
+ ]
39225
+ }
39226
+ ),
39227
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39228
+ "button",
39229
+ {
39230
+ onClick: () => setShowExport(true),
39231
+ className: "flex items-center gap-1.5 px-2.5 h-7 bg-indigo-600 hover:bg-indigo-500 active:bg-indigo-700 text-white text-xs font-medium rounded-lg transition-colors",
39232
+ children: [
39233
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}),
39234
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { children: "Export" })
39235
+ ]
39236
+ }
39237
+ )
39238
+ ] })
39097
39239
  ] }),
39098
- formats.html_url && /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "mt-4", children: [
39099
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39240
+ hasHtml && /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "mt-4", children: [
39241
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39100
39242
  "div",
39101
39243
  {
39102
39244
  ref: previewRef,
39103
- className: "relative overflow-hidden rounded-xl bg-zinc-950 border border-zinc-800/50",
39245
+ onClick: () => setShowFullscreen(true),
39246
+ className: "group relative overflow-hidden rounded-xl bg-zinc-950 border border-zinc-800/50 cursor-pointer",
39104
39247
  style: { height: Math.round(previewScale * 720) },
39105
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39106
- "iframe",
39107
- {
39108
- ref: iframeRef,
39109
- src: formats.html_url,
39110
- title,
39111
- onLoad: () => setIframeReady(true),
39112
- sandbox: "allow-same-origin allow-scripts",
39113
- style: {
39114
- width: 1280,
39115
- height: 720,
39116
- transform: `scale(${previewScale})`,
39117
- transformOrigin: "top left",
39118
- border: "none",
39119
- pointerEvents: "none",
39120
- display: "block"
39248
+ title: "Click to open fullscreen",
39249
+ children: [
39250
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39251
+ "iframe",
39252
+ {
39253
+ ref: iframeRef,
39254
+ src: formats.html_url,
39255
+ title,
39256
+ onLoad: () => setIframeReady(true),
39257
+ sandbox: "allow-same-origin allow-scripts",
39258
+ style: {
39259
+ width: 1280,
39260
+ height: 720,
39261
+ transform: `scale(${previewScale})`,
39262
+ transformOrigin: "top left",
39263
+ border: "none",
39264
+ pointerEvents: "none",
39265
+ display: "block"
39266
+ }
39121
39267
  }
39122
- }
39123
- )
39268
+ ),
39269
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-black/0 group-hover:bg-black/30 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-2 bg-zinc-900/80 backdrop-blur-sm border border-zinc-700 rounded-lg px-3 py-1.5 text-xs text-zinc-200 font-medium", children: [
39270
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ExpandIcon, {}),
39271
+ "Open fullscreen"
39272
+ ] }) })
39273
+ ]
39124
39274
  }
39125
39275
  ),
39126
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center justify-between mt-3", children: [
39127
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "flex items-center gap-2", children: availablePills.map((fmt) => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39128
- "span",
39276
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-2 mt-3", children: [
39277
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39278
+ "button",
39129
39279
  {
39130
- className: cn(
39131
- "text-xs px-2.5 py-0.5 rounded-md border font-medium",
39132
- fmt.accent.pill
39133
- ),
39134
- children: fmt.label
39135
- },
39136
- fmt.key
39137
- )) }),
39138
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-2", children: [
39139
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39140
- "button",
39141
- {
39142
- onClick: () => goToSlide(currentSlide - 1),
39143
- disabled: total <= 1,
39144
- className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39145
- "aria-label": "Previous slide",
39146
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronLeft2, {})
39147
- }
39148
- ),
39149
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "text-xs text-zinc-500 font-medium tabular-nums min-w-[3rem] text-center", children: [
39150
- currentSlide,
39151
- " / ",
39152
- total
39153
- ] }),
39154
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39155
- "button",
39156
- {
39157
- onClick: () => goToSlide(currentSlide + 1),
39158
- disabled: total <= 1,
39159
- className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39160
- "aria-label": "Next slide",
39161
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronRight2, {})
39162
- }
39163
- )
39164
- ] })
39165
- ] })
39166
- ] }),
39167
- !formats.html_url && availablePills.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "flex items-center gap-2 mt-4", children: availablePills.map((fmt) => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39168
- "span",
39169
- {
39170
- className: cn(
39171
- "text-xs px-2.5 py-0.5 rounded-md border font-medium",
39172
- fmt.accent.pill
39280
+ onClick: (e) => {
39281
+ e.stopPropagation();
39282
+ slideCount > 1 ? goToSlide(currentSlide - 1) : sendSlideCommand("prevSlide");
39283
+ },
39284
+ disabled: !hasHtml,
39285
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39286
+ "aria-label": "Previous slide",
39287
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronLeft2, {})
39288
+ }
39173
39289
  ),
39174
- children: fmt.label
39175
- },
39176
- fmt.key
39177
- )) })
39290
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "text-xs text-zinc-500 font-medium tabular-nums min-w-[3rem] text-center", children: [
39291
+ currentSlide,
39292
+ " / ",
39293
+ total
39294
+ ] }),
39295
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39296
+ "button",
39297
+ {
39298
+ onClick: (e) => {
39299
+ e.stopPropagation();
39300
+ slideCount > 1 ? goToSlide(currentSlide + 1) : sendSlideCommand("nextSlide");
39301
+ },
39302
+ disabled: !hasHtml,
39303
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39304
+ "aria-label": "Next slide",
39305
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronRight2, {})
39306
+ }
39307
+ )
39308
+ ] })
39309
+ ] })
39178
39310
  ] })
39179
39311
  ] }) }),
39180
- showModal && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39181
- ExportModal,
39312
+ showExport && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ExportModal, { formats, title, onClose: () => setShowExport(false) }),
39313
+ showFullscreen && hasHtml && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39314
+ FullscreenModal,
39182
39315
  {
39183
- formats,
39316
+ url: formats.html_url,
39184
39317
  title,
39185
- onClose: () => setShowModal(false)
39318
+ slideCount,
39319
+ onClose: () => setShowFullscreen(false)
39186
39320
  }
39187
39321
  )
39188
39322
  ] });