foss-design 0.3.6 → 0.4.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.
@@ -2585,14 +2585,14 @@ function readPref() {
2585
2585
  return "system";
2586
2586
  }
2587
2587
  var pref = new Store(readPref());
2588
- var media = window.matchMedia("(prefers-color-scheme: dark)");
2588
+ var media$1 = window.matchMedia("(prefers-color-scheme: dark)");
2589
2589
  function resolve(value) {
2590
- return value === "system" ? media.matches ? "dark" : "light" : value;
2590
+ return value === "system" ? media$1.matches ? "dark" : "light" : value;
2591
2591
  }
2592
2592
  function apply() {
2593
2593
  document.documentElement.dataset.theme = resolve(pref.get());
2594
2594
  }
2595
- media.addEventListener("change", apply);
2595
+ media$1.addEventListener("change", apply);
2596
2596
  pref.subscribe(apply);
2597
2597
  apply();
2598
2598
  function setThemePref(value) {
@@ -2608,9 +2608,9 @@ function useThemePref() {
2608
2608
  function useTheme() {
2609
2609
  const value = useStore(pref);
2610
2610
  const systemDark = useSyncExternalStore((listener) => {
2611
- media.addEventListener("change", listener);
2612
- return () => media.removeEventListener("change", listener);
2613
- }, () => media.matches);
2611
+ media$1.addEventListener("change", listener);
2612
+ return () => media$1.removeEventListener("change", listener);
2613
+ }, () => media$1.matches);
2614
2614
  return value === "system" ? systemDark ? "dark" : "light" : value;
2615
2615
  }
2616
2616
  //#endregion
@@ -3094,7 +3094,8 @@ function SearchButton({ width = 240, label = "Search and jump" }) {
3094
3094
  type: "button",
3095
3095
  onClick: openPalette,
3096
3096
  style: { width },
3097
- className: "flex h-ctrl min-w-0 shrink cursor-pointer items-center gap-2 rounded-[6px] border border-rule bg-soft pr-1.5 pl-3 text-ctrl text-muted transition-colors hover:border-rule-strong max-lg:w-auto!",
3097
+ "aria-label": label,
3098
+ className: "flex h-ctrl min-w-0 shrink cursor-pointer items-center gap-2 rounded-[6px] border border-rule bg-soft pr-1.5 pl-3 text-ctrl text-muted transition-colors hover:border-rule-strong max-lg:w-auto! max-md:w-ctrl! max-md:justify-center max-md:border-0 max-md:bg-transparent max-md:p-0",
3098
3099
  children: [
3099
3100
  /* @__PURE__ */ jsx(Icon, {
3100
3101
  name: "search",
@@ -3104,7 +3105,10 @@ function SearchButton({ width = 240, label = "Search and jump" }) {
3104
3105
  className: "min-w-0 grow truncate text-left max-lg:hidden",
3105
3106
  children: label
3106
3107
  }),
3107
- /* @__PURE__ */ jsx(Kbd, { children: MOD_KEY === "⌘" ? "⌘K" : "Ctrl K" })
3108
+ /* @__PURE__ */ jsx("span", {
3109
+ className: "flex max-md:hidden",
3110
+ children: /* @__PURE__ */ jsx(Kbd, { children: MOD_KEY === "⌘" ? "⌘K" : "Ctrl K" })
3111
+ })
3108
3112
  ]
3109
3113
  });
3110
3114
  }
@@ -5637,12 +5641,12 @@ function useIsHydrated() {
5637
5641
  }
5638
5642
  __name$6(useIsHydrated, "useIsHydrated");
5639
5643
  var useReactSyncExternalStore = React[" useSyncExternalStore ".trim().toString()];
5640
- function subscribe() {
5644
+ function subscribe$1() {
5641
5645
  return () => {};
5642
5646
  }
5643
- __name$6(subscribe, "subscribe");
5647
+ __name$6(subscribe$1, "subscribe");
5644
5648
  function useIsHydratedModern() {
5645
- return useReactSyncExternalStore(subscribe, () => true, () => false);
5649
+ return useReactSyncExternalStore(subscribe$1, () => true, () => false);
5646
5650
  }
5647
5651
  __name$6(useIsHydratedModern, "useIsHydratedModern");
5648
5652
  var useIsHydrated2 = typeof useReactSyncExternalStore === "function" ? useIsHydratedModern : useIsHydrated;
@@ -6717,24 +6721,57 @@ function AppTopBar() {
6717
6721
  const project = useProject().data;
6718
6722
  const { slots } = useViewer();
6719
6723
  const [location] = useLocation();
6720
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Bar, { children: [
6721
- slots.barStart,
6722
- /* @__PURE__ */ jsxs("div", {
6723
- className: "flex min-w-0 items-center gap-6",
6724
- children: [slots.brand ?? /* @__PURE__ */ jsx(Wordmark, {
6725
- name: project?.name,
6726
- version: project?.version
6727
- }), /* @__PURE__ */ jsx(Tabs, { location })]
6728
- }),
6729
- /* @__PURE__ */ jsxs("div", {
6730
- className: "ml-auto flex items-center gap-2",
6731
- children: [
6732
- /* @__PURE__ */ jsx(SearchButton, {}),
6733
- /* @__PURE__ */ jsx(ThemeMenu, {}),
6734
- slots.barEnd
6735
- ]
6724
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6725
+ /* @__PURE__ */ jsxs(Bar, { children: [
6726
+ slots.barStart,
6727
+ /* @__PURE__ */ jsxs("div", {
6728
+ className: "flex min-w-0 items-center gap-6",
6729
+ children: [slots.brand ?? /* @__PURE__ */ jsx(Wordmark, {
6730
+ name: project?.name,
6731
+ version: project?.version
6732
+ }), /* @__PURE__ */ jsx(Tabs, { location })]
6733
+ }),
6734
+ /* @__PURE__ */ jsxs("div", {
6735
+ className: "ml-auto flex items-center gap-2",
6736
+ children: [
6737
+ /* @__PURE__ */ jsx(SearchButton, {}),
6738
+ /* @__PURE__ */ jsx(ThemeMenu, {}),
6739
+ slots.barEnd
6740
+ ]
6741
+ })
6742
+ ] }),
6743
+ /* @__PURE__ */ jsx(PhoneTabs, { location }),
6744
+ slots.belowBar
6745
+ ] });
6746
+ }
6747
+ /** The section tabs on a phone, where the bar has no room for them: a row under it. */
6748
+ function PhoneTabs({ location }) {
6749
+ return /* @__PURE__ */ jsx("nav", {
6750
+ className: "sticky top-[52px] z-30 hidden h-10 shrink-0 items-stretch border-b border-rule bg-surface px-2 max-md:flex",
6751
+ children: TABS.map((tab) => {
6752
+ const on = tab.match(location);
6753
+ return /* @__PURE__ */ jsxs(Link, {
6754
+ href: tab.to,
6755
+ "aria-current": on ? "page" : void 0,
6756
+ className: cn("relative flex grow items-center justify-center text-[13.5px] no-underline", on ? "font-medium text-ink" : "text-ink2"),
6757
+ children: [tab.label, on ? /* @__PURE__ */ jsx("span", {
6758
+ "aria-hidden": true,
6759
+ className: "absolute inset-x-6 bottom-0 h-[2px] rounded-full bg-action"
6760
+ }) : null]
6761
+ }, tab.to);
6736
6762
  })
6737
- ] }), slots.belowBar] });
6763
+ });
6764
+ }
6765
+ //#endregion
6766
+ //#region viewer/src/lib/phone.ts
6767
+ /** Phones: under Tailwind's `md`. The canvas gives way to one screen at a time there. */
6768
+ var media = window.matchMedia("(max-width: 767px)");
6769
+ var subscribe = (onChange) => {
6770
+ media.addEventListener("change", onChange);
6771
+ return () => media.removeEventListener("change", onChange);
6772
+ };
6773
+ function useIsPhone() {
6774
+ return useSyncExternalStore(subscribe, () => media.matches);
6738
6775
  }
6739
6776
  //#endregion
6740
6777
  //#region ../../node_modules/.pnpm/@radix-ui+react-popover@1.1.23_@types+react-dom@19.3.0_@types+react@19.3.0__@types+reac_bdb8fe50570f35efe0254f5915b263fd/node_modules/@radix-ui/react-popover/dist/index.mjs
@@ -7836,7 +7873,7 @@ function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
7836
7873
  return /* @__PURE__ */ jsxs("div", {
7837
7874
  className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
7838
7875
  style: {
7839
- gridTemplateColumns: `${labelWidth}px minmax(0, 1fr)`,
7876
+ gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
7840
7877
  minHeight: minH
7841
7878
  },
7842
7879
  children: [/* @__PURE__ */ jsx("span", {
@@ -7996,7 +8033,7 @@ function PageHead({ title, sub, right, badge, crumbs }) {
7996
8033
  }), badge]
7997
8034
  }),
7998
8035
  sub ? /* @__PURE__ */ jsx("div", {
7999
- className: "text-[13px] text-muted",
8036
+ className: "text-[13px] [overflow-wrap:anywhere] text-muted",
8000
8037
  children: sub
8001
8038
  }) : null
8002
8039
  ]
@@ -11949,7 +11986,7 @@ function isTyping(target) {
11949
11986
  //#endregion
11950
11987
  //#region viewer/src/pages/canvas/canvas-page.tsx
11951
11988
  var query = new URLSearchParams(window.location.search);
11952
- var CAPTURE = query.has("capture");
11989
+ var CAPTURE$1 = query.has("capture");
11953
11990
  var cameraKey = (canvas, page) => `foss-design.camera.${canvas}.${page}`;
11954
11991
  function loadCamera(key) {
11955
11992
  try {
@@ -11985,7 +12022,7 @@ function CanvasView({ canvas, page }) {
11985
12022
  const camera = useMemo(() => new CameraStore(), []);
11986
12023
  const apiRef = useRef(null);
11987
12024
  const [theme, setTheme] = useScreenTheme(canvas);
11988
- const [sidebar, setSidebar] = useState(() => !CAPTURE && window.innerWidth > 900);
12025
+ const [sidebar, setSidebar] = useState(() => !CAPTURE$1 && window.innerWidth > 900);
11989
12026
  const [inspectOn, setInspect] = useState(false);
11990
12027
  const sizes = useStore(store, (state) => state.sizes);
11991
12028
  const layout = useMemo(() => page ? layoutPage(page, sizes) : {
@@ -12029,7 +12066,7 @@ function CanvasView({ canvas, page }) {
12029
12066
  if (placed.current || !layout.bounds.w) return;
12030
12067
  placed.current = true;
12031
12068
  const focus = takeFocus(canvas.id);
12032
- const saved = CAPTURE ? null : loadCamera(key);
12069
+ const saved = CAPTURE$1 ? null : loadCamera(key);
12033
12070
  if (focus) {
12034
12071
  store.set((state) => ({
12035
12072
  ...state,
@@ -12037,7 +12074,7 @@ function CanvasView({ canvas, page }) {
12037
12074
  }));
12038
12075
  requestAnimationFrame(() => apiRef.current?.fitItem(focus, false));
12039
12076
  } else if (saved && saved.z >= camera.minZoom) camera.set(saved);
12040
- else requestAnimationFrame(() => CAPTURE ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
12077
+ else requestAnimationFrame(() => CAPTURE$1 ? apiRef.current?.fitAll(false) : apiRef.current?.openView());
12041
12078
  }, [
12042
12079
  layout,
12043
12080
  camera,
@@ -12046,7 +12083,7 @@ function CanvasView({ canvas, page }) {
12046
12083
  store
12047
12084
  ]);
12048
12085
  useEffect(() => {
12049
- if (CAPTURE) return;
12086
+ if (CAPTURE$1) return;
12050
12087
  let timer;
12051
12088
  const off = camera.subscribe((c) => {
12052
12089
  clearTimeout(timer);
@@ -12139,7 +12176,7 @@ function CanvasView({ canvas, page }) {
12139
12176
  navigate
12140
12177
  ]);
12141
12178
  useEffect(() => {
12142
- if (!CAPTURE) return;
12179
+ if (!CAPTURE$1) return;
12143
12180
  const started = Date.now();
12144
12181
  const timer = setInterval(() => {
12145
12182
  const frames = store.get().frames;
@@ -12222,7 +12259,7 @@ function CanvasView({ canvas, page }) {
12222
12259
  inspectOn,
12223
12260
  inspection
12224
12261
  ]);
12225
- if (CAPTURE) return /* @__PURE__ */ jsx("div", {
12262
+ if (CAPTURE$1) return /* @__PURE__ */ jsx("div", {
12226
12263
  className: "flex h-dvh",
12227
12264
  children: /* @__PURE__ */ jsx(Viewport, {
12228
12265
  layout,
@@ -12438,6 +12475,79 @@ function useScreenFrames(frameEls, layout) {
12438
12475
  }, [frameEls, layout]);
12439
12476
  }
12440
12477
  //#endregion
12478
+ //#region viewer/src/ui/sheet.tsx
12479
+ /**
12480
+ * A panel sliding up from the bottom edge, for phones: lists to pick from and actions. Like
12481
+ * Dialog, focus stays inside, and Escape, a tap on the dimmed page or the close button close it.
12482
+ */
12483
+ function Sheet({ open, onOpenChange, title, children }) {
12484
+ return /* @__PURE__ */ jsx(Dialog$1, {
12485
+ open,
12486
+ onOpenChange,
12487
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsxs(DialogPortal, {
12488
+ forceMount: true,
12489
+ children: [/* @__PURE__ */ jsx(DialogOverlay, {
12490
+ asChild: true,
12491
+ forceMount: true,
12492
+ children: /* @__PURE__ */ jsx(motion.div, {
12493
+ initial: { opacity: 0 },
12494
+ animate: { opacity: 1 },
12495
+ exit: { opacity: 0 },
12496
+ transition: { duration: .16 },
12497
+ className: "fixed inset-0 z-50 bg-overlay"
12498
+ })
12499
+ }), /* @__PURE__ */ jsx(DialogContent, {
12500
+ asChild: true,
12501
+ forceMount: true,
12502
+ "aria-describedby": void 0,
12503
+ children: /* @__PURE__ */ jsxs(motion.div, {
12504
+ initial: { y: "100%" },
12505
+ animate: { y: 0 },
12506
+ exit: {
12507
+ y: "100%",
12508
+ transition: {
12509
+ duration: .18,
12510
+ ease: "easeIn"
12511
+ }
12512
+ },
12513
+ transition: {
12514
+ type: "spring",
12515
+ stiffness: 420,
12516
+ damping: 40
12517
+ },
12518
+ className: "fixed inset-x-0 bottom-0 z-50 flex max-h-[82dvh] flex-col rounded-t-[14px] border-t border-rule bg-surface pb-[env(safe-area-inset-bottom)] shadow-pop outline-none",
12519
+ children: [
12520
+ /* @__PURE__ */ jsx("div", {
12521
+ className: "flex justify-center pt-2",
12522
+ children: /* @__PURE__ */ jsx("span", {
12523
+ "aria-hidden": true,
12524
+ className: "h-1 w-9 rounded-full bg-rule-strong"
12525
+ })
12526
+ }),
12527
+ /* @__PURE__ */ jsxs("div", {
12528
+ className: "flex min-h-[48px] items-center justify-between gap-3 pr-2 pl-4",
12529
+ children: [/* @__PURE__ */ jsx(DialogTitle, {
12530
+ className: "m-0 truncate text-[15px] font-semibold",
12531
+ children: title
12532
+ }), /* @__PURE__ */ jsx(DialogClose, {
12533
+ asChild: true,
12534
+ children: /* @__PURE__ */ jsx(IconButton, {
12535
+ icon: "x",
12536
+ label: "Close"
12537
+ })
12538
+ })]
12539
+ }),
12540
+ /* @__PURE__ */ jsx("div", {
12541
+ className: "min-h-0 overflow-y-auto overscroll-contain border-t border-rule",
12542
+ children
12543
+ })
12544
+ ]
12545
+ })
12546
+ })]
12547
+ }) : null })
12548
+ });
12549
+ }
12550
+ //#endregion
12441
12551
  //#region viewer/src/pages/canvas/play-page.tsx
12442
12552
  function framesOf(canvas, pageId) {
12443
12553
  return (pageId ? canvas.pages.filter((p) => p.id === pageId) : canvas.pages).flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url").map((item) => ({
@@ -12743,6 +12853,441 @@ function Player({ canvas, itemId }) {
12743
12853
  });
12744
12854
  }
12745
12855
  //#endregion
12856
+ //#region viewer/src/pages/canvas/phone-view.tsx
12857
+ /** Room around the screen on the stage, per side. */
12858
+ var GUTTER = 12;
12859
+ /**
12860
+ * A canvas on a phone: no board to pan, one screen at a time as in play mode. The screen fills
12861
+ * the width and scrolls; the bottom bar steps through every screen of the canvas and opens the
12862
+ * list of them. Serves `/c/:canvas`, its pages and `/c/:canvas/play/:item` under 768px.
12863
+ */
12864
+ function PhoneCanvas({ canvasId, pageId, itemId }) {
12865
+ const { data: canvas, error, isLoading } = useCanvas(canvasId);
12866
+ if (error) return /* @__PURE__ */ jsx(NotFound, {
12867
+ title: "Canvas not found",
12868
+ text: error.message
12869
+ });
12870
+ if (isLoading || !canvas) return /* @__PURE__ */ jsx("div", { className: "canvas-dots h-dvh" });
12871
+ return /* @__PURE__ */ jsx(PhoneViewer, {
12872
+ canvas,
12873
+ pageId,
12874
+ itemId
12875
+ });
12876
+ }
12877
+ function PhoneViewer({ canvas, pageId, itemId }) {
12878
+ const [, navigate] = useLocation();
12879
+ const { frameSandbox, slots, scope } = useViewer();
12880
+ const appTheme = useTheme();
12881
+ const all = useMemo(() => framesOf(canvas, null), [canvas]);
12882
+ const index = Math.max(0, itemId ? all.findIndex((entry) => entry.item.id === itemId) : pageId ? all.findIndex((entry) => entry.pageId === pageId) : 0);
12883
+ const entry = all[index];
12884
+ const item = entry?.item;
12885
+ const [theme, setTheme] = useState(canvas.theme ?? appTheme);
12886
+ const [listOpen, setListOpen] = useState(false);
12887
+ const [errors, setErrors] = useState([]);
12888
+ const [stageWidth, setStageWidth] = useState(0);
12889
+ const [autoHeight, setAutoHeight] = useState(null);
12890
+ const [direction, setDirection] = useState(0);
12891
+ const [actual, setActual] = useState(false);
12892
+ const stage = useRef(null);
12893
+ const frame = useRef(null);
12894
+ const [frameEl, setFrameEl] = useState(null);
12895
+ const setFrame = useCallback((el) => {
12896
+ if (!el) return;
12897
+ frame.current = el;
12898
+ setFrameEl(el);
12899
+ }, []);
12900
+ const show = useCallback((target, step = 0) => {
12901
+ setDirection(step);
12902
+ navigate(playHref(canvas.id, target.item.id, target.pageId), { replace: true });
12903
+ }, [canvas.id, navigate]);
12904
+ const move = (step) => {
12905
+ if (all.length < 2) return;
12906
+ show(all[(index + step + all.length) % all.length], step);
12907
+ };
12908
+ const goTo = (id) => {
12909
+ const target = all.find((e) => e.item.id === id);
12910
+ if (target) show(target, all.indexOf(target) > index ? 1 : -1);
12911
+ };
12912
+ useLayoutEffect(() => {
12913
+ const el = stage.current;
12914
+ if (!el) return;
12915
+ const observer = new ResizeObserver(() => setStageWidth(el.clientWidth));
12916
+ observer.observe(el);
12917
+ return () => observer.disconnect();
12918
+ }, []);
12919
+ const frameTheme = item?.kind === "screen" ? item.theme ?? theme : theme;
12920
+ const [openTheme] = useState(frameTheme);
12921
+ const src = item ? item.kind === "screen" ? frameSrc(item.url, openTheme) : item.url : "";
12922
+ const openUrl = useOpenUrl(canvas.id, item, frameTheme);
12923
+ useEffect(() => {
12924
+ setErrors([]);
12925
+ setAutoHeight(item?.kind === "screen" ? item.measuredHeight ?? null : null);
12926
+ stage.current?.scrollTo({ top: 0 });
12927
+ const el = frame.current;
12928
+ if (!el || item?.kind !== "screen") return;
12929
+ return listenToFrame(el, (message) => {
12930
+ if (message.type === "size") setAutoHeight(message.height);
12931
+ else if (message.type === "error") setErrors((list) => list.includes(message.message) ? list : [...list, message.message]);
12932
+ else if (message.type === "updated") setErrors([]);
12933
+ else if (message.type === "go") goTo(message.target.includes("/") ? message.target.split("/")[1] : message.target);
12934
+ else if (message.type === "link") followLink(message, canvas, goTo);
12935
+ });
12936
+ }, [frameEl, src]);
12937
+ useEffect(() => {
12938
+ if (item?.kind === "screen") sendTheme(frame.current, frameTheme);
12939
+ }, [frameTheme, item]);
12940
+ useEffect(() => {
12941
+ const onKey = (event) => {
12942
+ if (document.querySelector("[role=\"dialog\"]")) return;
12943
+ if (event.key === "ArrowRight") move(1);
12944
+ else if (event.key === "ArrowLeft") move(-1);
12945
+ };
12946
+ window.addEventListener("keydown", onKey);
12947
+ return () => window.removeEventListener("keydown", onKey);
12948
+ });
12949
+ const swipe = useRef(null);
12950
+ const onTouchStart = (event) => {
12951
+ const touch = event.touches[0];
12952
+ swipe.current = touch ? {
12953
+ x: touch.clientX,
12954
+ y: touch.clientY
12955
+ } : null;
12956
+ };
12957
+ const onTouchEnd = (event) => {
12958
+ const start = swipe.current;
12959
+ const touch = event.changedTouches[0];
12960
+ swipe.current = null;
12961
+ if (!start || !touch) return;
12962
+ const dx = touch.clientX - start.x;
12963
+ const dy = touch.clientY - start.y;
12964
+ if (Math.abs(dx) > 56 && Math.abs(dx) > Math.abs(dy) * 1.5) move(dx < 0 ? 1 : -1);
12965
+ };
12966
+ if (!item || !entry) return /* @__PURE__ */ jsxs("div", {
12967
+ className: "flex h-dvh flex-col",
12968
+ children: [/* @__PURE__ */ jsx(PhoneBar, {
12969
+ canvas,
12970
+ title: canvas.title,
12971
+ sub: null
12972
+ }), /* @__PURE__ */ jsx(NotFound, {
12973
+ title: "No screens here",
12974
+ text: "This canvas has no screens to show yet."
12975
+ })]
12976
+ });
12977
+ const width = item.frame.width;
12978
+ const fitScale = stageWidth ? Math.min(1, (stageWidth - 24) / width) : 0;
12979
+ const wide = fitScale > 0 && fitScale < .8;
12980
+ const scale = wide && actual ? 1 : fitScale;
12981
+ const height = item.frame.height === "auto" ? Math.max(autoHeight ?? 0, 480) : item.frame.height;
12982
+ const canTheme = item.kind === "screen" && !item.theme;
12983
+ return /* @__PURE__ */ jsxs("div", {
12984
+ className: "flex h-dvh flex-col",
12985
+ children: [
12986
+ /* @__PURE__ */ jsx(PhoneBar, {
12987
+ canvas,
12988
+ title: item.title || item.id,
12989
+ sub: [canvas.title, entry.where].filter(Boolean).join(" · "),
12990
+ back: scope ? void 0 : "/",
12991
+ end: /* @__PURE__ */ jsxs(Fragment, { children: [
12992
+ wide ? /* @__PURE__ */ jsx(IconButton, {
12993
+ icon: actual ? "zoom-out" : "zoom-in",
12994
+ label: actual ? "Fit to width" : "Actual size",
12995
+ onClick: () => setActual((value) => !value)
12996
+ }) : null,
12997
+ /* @__PURE__ */ jsxs(Menu, {
12998
+ align: "end",
12999
+ width: 220,
13000
+ trigger: /* @__PURE__ */ jsx(IconButton, {
13001
+ icon: "more",
13002
+ label: "Screen options"
13003
+ }),
13004
+ children: [
13005
+ canTheme ? /* @__PURE__ */ jsxs(Fragment, { children: [
13006
+ /* @__PURE__ */ jsx(MenuLabel, { children: "Screen theme" }),
13007
+ /* @__PURE__ */ jsx(ThemeItem, {
13008
+ value: "light",
13009
+ current: theme,
13010
+ onPick: setTheme
13011
+ }),
13012
+ /* @__PURE__ */ jsx(ThemeItem, {
13013
+ value: "dark",
13014
+ current: theme,
13015
+ onPick: setTheme
13016
+ }),
13017
+ /* @__PURE__ */ jsx(MenuSeparator, {})
13018
+ ] }) : null,
13019
+ /* @__PURE__ */ jsxs(MenuItem, {
13020
+ onSelect: () => reloadFrame(frame.current),
13021
+ children: [/* @__PURE__ */ jsx(Icon, {
13022
+ name: "refresh",
13023
+ size: 15,
13024
+ className: "text-ink2"
13025
+ }), "Reload screen"]
13026
+ }),
13027
+ openUrl ? /* @__PURE__ */ jsxs(MenuItem, {
13028
+ onSelect: () => window.open(openUrl, "_blank", "noopener"),
13029
+ children: [/* @__PURE__ */ jsx(Icon, {
13030
+ name: "external",
13031
+ size: 15,
13032
+ className: "text-ink2"
13033
+ }), "Open in a new tab"]
13034
+ }) : null,
13035
+ /* @__PURE__ */ jsxs(MenuItem, {
13036
+ onSelect: () => setThemePref(appTheme === "dark" ? "light" : "dark"),
13037
+ children: [/* @__PURE__ */ jsx(Icon, {
13038
+ name: appTheme === "dark" ? "sun" : "moon",
13039
+ size: 15,
13040
+ className: "text-ink2"
13041
+ }), appTheme === "dark" ? "Light appearance" : "Dark appearance"]
13042
+ })
13043
+ ]
13044
+ }),
13045
+ slots.barEnd
13046
+ ] })
13047
+ }),
13048
+ slots.belowBar,
13049
+ /* @__PURE__ */ jsxs("div", {
13050
+ ref: stage,
13051
+ onTouchStart,
13052
+ onTouchEnd,
13053
+ className: cn("canvas-dots relative min-h-0 grow overflow-y-auto overscroll-contain", wide && actual ? "overflow-x-auto" : "overflow-x-hidden"),
13054
+ children: [errors.length ? /* @__PURE__ */ jsxs("div", {
13055
+ className: "sticky top-0 z-10 flex items-start gap-2 border-b border-rule bg-danger-soft px-4 py-2 font-mono text-[12px] whitespace-pre-wrap text-danger-text",
13056
+ children: [/* @__PURE__ */ jsx(Icon, {
13057
+ name: "alert",
13058
+ size: 14,
13059
+ className: "mt-px shrink-0"
13060
+ }), /* @__PURE__ */ jsx("span", {
13061
+ className: "min-w-0 break-words",
13062
+ children: errors.join("\n")
13063
+ })]
13064
+ }) : null, /* @__PURE__ */ jsx(AnimatePresence, {
13065
+ initial: false,
13066
+ mode: "popLayout",
13067
+ custom: direction,
13068
+ children: /* @__PURE__ */ jsx(motion.div, {
13069
+ custom: direction,
13070
+ initial: {
13071
+ opacity: 0,
13072
+ x: direction * 40
13073
+ },
13074
+ animate: {
13075
+ opacity: 1,
13076
+ x: 0
13077
+ },
13078
+ exit: {
13079
+ opacity: 0,
13080
+ x: direction * -40,
13081
+ transition: { duration: .12 }
13082
+ },
13083
+ transition: {
13084
+ type: "spring",
13085
+ stiffness: 420,
13086
+ damping: 38
13087
+ },
13088
+ className: cn("flex", wide && actual ? "w-max" : "justify-center"),
13089
+ style: { padding: GUTTER },
13090
+ children: scale ? /* @__PURE__ */ jsx("div", {
13091
+ className: "shrink-0 overflow-hidden rounded-[6px] bg-surface",
13092
+ style: {
13093
+ width: width * scale,
13094
+ height: height * scale,
13095
+ boxShadow: "var(--shadow-frame), 0 0 0 1px var(--rule)"
13096
+ },
13097
+ children: /* @__PURE__ */ jsx("iframe", {
13098
+ ref: setFrame,
13099
+ src,
13100
+ title: item.title,
13101
+ allow: "clipboard-read; clipboard-write; fullscreen",
13102
+ sandbox: frameSandbox,
13103
+ className: "block border-0",
13104
+ style: {
13105
+ width,
13106
+ height,
13107
+ transform: `scale(${scale})`,
13108
+ transformOrigin: "0 0",
13109
+ colorScheme: frameTheme
13110
+ },
13111
+ onLoad: () => item.kind === "screen" && sendTheme(frame.current, frameTheme)
13112
+ }, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`)
13113
+ }) : null
13114
+ }, item.id)
13115
+ })]
13116
+ }),
13117
+ /* @__PURE__ */ jsxs("nav", {
13118
+ "aria-label": "Screens",
13119
+ className: "flex shrink-0 items-center gap-1 border-t border-rule bg-surface px-2 pt-1.5 pb-[max(6px,env(safe-area-inset-bottom))]",
13120
+ children: [
13121
+ /* @__PURE__ */ jsx(IconButton, {
13122
+ icon: "chevron-left",
13123
+ label: "Previous screen",
13124
+ onClick: () => move(-1),
13125
+ disabled: all.length < 2
13126
+ }),
13127
+ /* @__PURE__ */ jsxs("button", {
13128
+ type: "button",
13129
+ onClick: () => setListOpen(true),
13130
+ className: "flex h-10 min-w-0 grow cursor-pointer items-center justify-center gap-2 rounded-[8px] border-0 bg-transparent px-3 text-[13.5px] text-ink active:bg-soft2",
13131
+ children: [
13132
+ /* @__PURE__ */ jsx(Icon, {
13133
+ name: "list",
13134
+ size: 15,
13135
+ className: "shrink-0 text-muted"
13136
+ }),
13137
+ /* @__PURE__ */ jsxs("span", {
13138
+ className: "tabular shrink-0 text-muted",
13139
+ children: [
13140
+ index + 1,
13141
+ " / ",
13142
+ all.length
13143
+ ]
13144
+ }),
13145
+ /* @__PURE__ */ jsx("span", {
13146
+ className: "min-w-0 truncate font-medium",
13147
+ children: item.title || item.id
13148
+ })
13149
+ ]
13150
+ }),
13151
+ /* @__PURE__ */ jsx(IconButton, {
13152
+ icon: "chevron-right",
13153
+ label: "Next screen",
13154
+ onClick: () => move(1),
13155
+ disabled: all.length < 2
13156
+ })
13157
+ ]
13158
+ }),
13159
+ /* @__PURE__ */ jsx(Sheet, {
13160
+ open: listOpen,
13161
+ onOpenChange: setListOpen,
13162
+ title: canvas.title,
13163
+ children: /* @__PURE__ */ jsx(ScreenList, {
13164
+ canvas,
13165
+ current: item.id,
13166
+ onPick: (id) => {
13167
+ setListOpen(false);
13168
+ goTo(id);
13169
+ }
13170
+ })
13171
+ })
13172
+ ]
13173
+ });
13174
+ }
13175
+ function ThemeItem({ value, current, onPick }) {
13176
+ return /* @__PURE__ */ jsxs(MenuItem, {
13177
+ active: current === value,
13178
+ onSelect: () => onPick(value),
13179
+ children: [
13180
+ /* @__PURE__ */ jsx(Icon, {
13181
+ name: value === "dark" ? "moon" : "sun",
13182
+ size: 15,
13183
+ className: "text-ink2"
13184
+ }),
13185
+ value === "dark" ? "Dark" : "Light",
13186
+ current === value ? /* @__PURE__ */ jsx(Icon, {
13187
+ name: "check",
13188
+ size: 15,
13189
+ className: "ml-auto text-ink2"
13190
+ }) : null
13191
+ ]
13192
+ });
13193
+ }
13194
+ /** The phone's top bar: a way back, the screen and where it sits, and the screen's actions. */
13195
+ function PhoneBar({ canvas, title, sub, back, end }) {
13196
+ const { slots } = useViewer();
13197
+ return /* @__PURE__ */ jsxs("header", {
13198
+ className: "flex h-[52px] shrink-0 items-center gap-2 border-b border-rule bg-surface pr-2 pl-2 pt-[env(safe-area-inset-top)]",
13199
+ children: [
13200
+ slots.barStart,
13201
+ back ? /* @__PURE__ */ jsx(Link, {
13202
+ href: back,
13203
+ "aria-label": `All canvases, leaving ${canvas.title}`,
13204
+ className: "inline-flex size-10 shrink-0 items-center justify-center rounded-[8px] text-ink2 active:bg-soft2",
13205
+ children: /* @__PURE__ */ jsx(Icon, {
13206
+ name: "arrow-left",
13207
+ size: 18
13208
+ })
13209
+ }) : /* @__PURE__ */ jsx("span", {
13210
+ className: "flex size-10 shrink-0 items-center justify-center",
13211
+ children: /* @__PURE__ */ jsx(Mark, {})
13212
+ }),
13213
+ /* @__PURE__ */ jsxs("div", {
13214
+ className: "flex min-w-0 grow flex-col leading-tight",
13215
+ children: [/* @__PURE__ */ jsx("span", {
13216
+ className: "truncate text-[14.5px] font-semibold",
13217
+ children: title
13218
+ }), sub ? /* @__PURE__ */ jsx("span", {
13219
+ className: "truncate text-[12px] text-muted",
13220
+ children: sub
13221
+ }) : null]
13222
+ }),
13223
+ /* @__PURE__ */ jsx("div", {
13224
+ className: "flex shrink-0 items-center gap-1",
13225
+ children: end
13226
+ })
13227
+ ]
13228
+ });
13229
+ }
13230
+ var KIND_ICON = (entry) => entry.item.kind === "url" ? "globe" : entry.item.frame.device === "phone" ? "smartphone" : entry.item.frame.device === "tablet" ? "tablet" : "monitor";
13231
+ /** Every screen of the canvas by page and section, the current one marked. */
13232
+ function ScreenList({ canvas, current, onPick }) {
13233
+ const activeRef = useRef(null);
13234
+ useEffect(() => {
13235
+ activeRef.current?.scrollIntoView({ block: "center" });
13236
+ }, []);
13237
+ const multi = canvas.pages.length > 1;
13238
+ return /* @__PURE__ */ jsx("div", {
13239
+ className: "flex flex-col pb-2",
13240
+ children: canvas.pages.map((page) => {
13241
+ const entries = framesOf(canvas, page.id);
13242
+ if (!entries.length) return null;
13243
+ return /* @__PURE__ */ jsxs("section", {
13244
+ className: "flex flex-col",
13245
+ children: [multi ? /* @__PURE__ */ jsx("h3", {
13246
+ className: "sticky top-0 z-[1] m-0 border-b border-rule bg-soft px-4 py-2 text-[12px] font-semibold text-ink2",
13247
+ children: page.title
13248
+ }) : null, page.sections.map((section) => {
13249
+ const items = entries.filter((entry) => section.items.includes(entry.item));
13250
+ if (!items.length) return null;
13251
+ return /* @__PURE__ */ jsxs("div", {
13252
+ className: "flex flex-col",
13253
+ children: [section.title ? /* @__PURE__ */ jsx("div", {
13254
+ className: "px-4 pt-3 pb-1 text-[12px] text-muted",
13255
+ children: section.title
13256
+ }) : null, items.map((entry) => {
13257
+ const on = entry.item.id === current;
13258
+ return /* @__PURE__ */ jsxs("button", {
13259
+ ref: on ? activeRef : void 0,
13260
+ type: "button",
13261
+ "aria-current": on ? "true" : void 0,
13262
+ onClick: () => onPick(entry.item.id),
13263
+ className: cn("flex min-h-[48px] cursor-pointer items-center gap-3 border-0 bg-transparent px-4 text-left text-[14px] active:bg-soft2", on ? "font-medium text-ink" : "text-ink2"),
13264
+ children: [
13265
+ /* @__PURE__ */ jsx("span", {
13266
+ className: cn("flex size-8 shrink-0 items-center justify-center rounded-[8px]", on ? "bg-action-soft text-action" : "bg-soft2 text-muted"),
13267
+ children: /* @__PURE__ */ jsx(Icon, {
13268
+ name: KIND_ICON(entry),
13269
+ size: 15
13270
+ })
13271
+ }),
13272
+ /* @__PURE__ */ jsx("span", {
13273
+ className: "min-w-0 grow truncate",
13274
+ children: entry.item.title || entry.item.id
13275
+ }),
13276
+ on ? /* @__PURE__ */ jsx(Icon, {
13277
+ name: "check",
13278
+ size: 16,
13279
+ className: "shrink-0 text-action"
13280
+ }) : null
13281
+ ]
13282
+ }, entry.item.id);
13283
+ })]
13284
+ }, section.id);
13285
+ })]
13286
+ }, page.id);
13287
+ })
13288
+ });
13289
+ }
13290
+ //#endregion
12746
13291
  //#region viewer/src/lib/format.ts
12747
13292
  var units = [
12748
13293
  ["year", 31536e3],
@@ -12843,6 +13388,7 @@ function Foot({ children }) {
12843
13388
  }
12844
13389
  function CanvasList({ project }) {
12845
13390
  const { canvasRowActions: rowActions, canvasesFoot } = useViewer().slots;
13391
+ const phone = useIsPhone();
12846
13392
  if (!project.canvases.length) return /* @__PURE__ */ jsxs(Panel, { children: [
12847
13393
  /* @__PURE__ */ jsx(PanelHead, {
12848
13394
  title: "Canvases",
@@ -12873,7 +13419,31 @@ function CanvasList({ project }) {
12873
13419
  const actions = rowActions?.(canvas.id);
12874
13420
  return /* @__PURE__ */ jsx(WithActions, {
12875
13421
  actions,
12876
- children: /* @__PURE__ */ jsxs(RowLink, {
13422
+ children: phone ? /* @__PURE__ */ jsxs(RowLink, {
13423
+ to: `/c/${encodeURIComponent(canvas.id)}`,
13424
+ cols: "86px minmax(0,1fr)",
13425
+ align: "ll",
13426
+ pad: "10px 16px",
13427
+ minH: 74,
13428
+ first: !!actions,
13429
+ children: [/* @__PURE__ */ jsx(Cover, { canvas }), /* @__PURE__ */ jsx(Two, {
13430
+ top: canvas.title,
13431
+ bottom: /* @__PURE__ */ jsxs("span", {
13432
+ className: "flex min-w-0 items-center gap-2",
13433
+ children: [canvas.issues ? /* @__PURE__ */ jsx(Badge, {
13434
+ tone: "danger",
13435
+ children: plural(canvas.issues, "problem")
13436
+ }) : null, /* @__PURE__ */ jsxs("span", {
13437
+ className: "truncate",
13438
+ children: [
13439
+ plural(canvas.screens, "screen"),
13440
+ " · ",
13441
+ relativeTime(canvas.updatedAt)
13442
+ ]
13443
+ })]
13444
+ })
13445
+ })]
13446
+ }) : /* @__PURE__ */ jsxs(RowLink, {
12877
13447
  to: `/c/${encodeURIComponent(canvas.id)}`,
12878
13448
  cols: "86px minmax(0,1fr) auto auto",
12879
13449
  align: "llrr",
@@ -13003,9 +13573,11 @@ function HomePage() {
13003
13573
  sub: slots.homeSub ? slots.homeSub : project.static ? `Snapshot built with foss-design ${project.version}` : /* @__PURE__ */ jsxs(Fragment, { children: [
13004
13574
  /* @__PURE__ */ jsxs(Mono, {
13005
13575
  size: 12.5,
13576
+ wrap: true,
13006
13577
  children: [project.root, "/.design"]
13007
13578
  }),
13008
- " · foss-design ",
13579
+ " ",
13580
+ "· foss-design ",
13009
13581
  project.version
13010
13582
  ] })
13011
13583
  }),
@@ -16666,6 +17238,7 @@ function MotionPage({ system }) {
16666
17238
  //#region viewer/src/pages/system/nav.tsx
16667
17239
  /** Grouped links of the system section; a wrapping row above the content on narrow screens. */
16668
17240
  function SystemNav({ system }) {
17241
+ const phone = useIsPhone();
16669
17242
  const kinds = new Set(system.tokens.map((token) => token.kind));
16670
17243
  const foundations = [
16671
17244
  {
@@ -16707,6 +17280,33 @@ function SystemNav({ system }) {
16707
17280
  groups.set(component.group, list);
16708
17281
  }
16709
17282
  const guidelines = system.guidelines.filter((guide) => guide.slug !== "overview");
17283
+ if (phone) {
17284
+ const sections = [
17285
+ {
17286
+ label: "",
17287
+ items: [{
17288
+ to: "/system",
17289
+ label: "Overview"
17290
+ }]
17291
+ },
17292
+ {
17293
+ label: "Foundations",
17294
+ items: foundations
17295
+ },
17296
+ {
17297
+ label: "Guidelines",
17298
+ items: guidelines.map((guide) => ({
17299
+ to: `/system/guidelines/${encodeURIComponent(guide.slug)}`,
17300
+ label: guide.title
17301
+ }))
17302
+ },
17303
+ ...[...groups].map(([label, items]) => ({
17304
+ label,
17305
+ items
17306
+ }))
17307
+ ];
17308
+ return /* @__PURE__ */ jsx(PhoneSystemNav, { sections: sections.filter((section) => section.items.length) });
17309
+ }
16710
17310
  return /* @__PURE__ */ jsxs("div", {
16711
17311
  className: "flex flex-col max-lg:gap-1 max-lg:[&_nav]:flex-row max-lg:[&_nav]:flex-wrap",
16712
17312
  children: [
@@ -16742,6 +17342,70 @@ function SystemNav({ system }) {
16742
17342
  function count(system, kind) {
16743
17343
  return system.tokens.filter((token) => token.kind === kind).length || void 0;
16744
17344
  }
17345
+ /** On a phone: the current section on a full-width button that lists them all in a sheet. */
17346
+ function PhoneSystemNav({ sections }) {
17347
+ const [location] = useLocation();
17348
+ const [open, setOpen] = useState(false);
17349
+ const current = sections.flatMap((section) => section.items).find((item) => item.to === location);
17350
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
17351
+ type: "button",
17352
+ onClick: () => setOpen(true),
17353
+ className: "flex h-11 w-full cursor-pointer items-center gap-2 rounded-[8px] border border-rule bg-surface px-3.5 text-left text-[14px] text-ink",
17354
+ children: [
17355
+ /* @__PURE__ */ jsx("span", {
17356
+ className: "text-[12.5px] text-muted",
17357
+ children: "Section"
17358
+ }),
17359
+ /* @__PURE__ */ jsx("span", {
17360
+ className: "min-w-0 grow truncate font-medium",
17361
+ children: current?.label ?? "Overview"
17362
+ }),
17363
+ /* @__PURE__ */ jsx(Icon, {
17364
+ name: "chevron-down",
17365
+ size: 16,
17366
+ className: "shrink-0 text-muted"
17367
+ })
17368
+ ]
17369
+ }), /* @__PURE__ */ jsx(Sheet, {
17370
+ open,
17371
+ onOpenChange: setOpen,
17372
+ title: "Design system",
17373
+ children: /* @__PURE__ */ jsx("nav", {
17374
+ "aria-label": "Design system",
17375
+ className: "flex flex-col pb-2",
17376
+ children: sections.map((section) => /* @__PURE__ */ jsxs("div", {
17377
+ className: "flex flex-col",
17378
+ children: [section.label ? /* @__PURE__ */ jsx("div", {
17379
+ className: "px-4 pt-3 pb-1 text-[12px] text-muted",
17380
+ children: section.label
17381
+ }) : null, section.items.map((item) => {
17382
+ const on = item.to === location;
17383
+ return /* @__PURE__ */ jsxs(Link, {
17384
+ href: item.to,
17385
+ onClick: () => setOpen(false),
17386
+ "aria-current": on ? "page" : void 0,
17387
+ className: cn("flex min-h-[46px] items-center gap-2 px-4 text-[14px] no-underline active:bg-soft2", on ? "font-medium text-ink" : "text-ink2"),
17388
+ children: [
17389
+ /* @__PURE__ */ jsx("span", {
17390
+ className: "min-w-0 grow truncate",
17391
+ children: item.label
17392
+ }),
17393
+ item.hint ? /* @__PURE__ */ jsx("span", {
17394
+ className: "text-[12.5px] text-muted",
17395
+ children: item.hint
17396
+ }) : null,
17397
+ on ? /* @__PURE__ */ jsx(Icon, {
17398
+ name: "check",
17399
+ size: 16,
17400
+ className: "shrink-0 text-action"
17401
+ }) : null
17402
+ ]
17403
+ }, item.to);
17404
+ })]
17405
+ }, section.label || "overview"))
17406
+ })
17407
+ })] });
17408
+ }
16745
17409
  //#endregion
16746
17410
  //#region viewer/src/pages/system/other-tokens.tsx
16747
17411
  function OtherTokensPage({ system }) {
@@ -17437,7 +18101,30 @@ function Title() {
17437
18101
  }, [name]);
17438
18102
  return null;
17439
18103
  }
18104
+ /** Screenshots (`?capture`) always get the canvas, whatever the window. */
18105
+ var CAPTURE = new URLSearchParams(window.location.search).has("capture");
18106
+ /** The canvas routes' pages; on a phone the canvas is one screen at a time. */
18107
+ function useCanvasPages() {
18108
+ const phone = useIsPhone() && !CAPTURE;
18109
+ return {
18110
+ play: (canvas, item) => phone ? /* @__PURE__ */ jsx(PhoneCanvas, {
18111
+ canvasId: canvas,
18112
+ itemId: item
18113
+ }) : /* @__PURE__ */ jsx(PlayPage, {
18114
+ canvasId: canvas,
18115
+ itemId: item
18116
+ }),
18117
+ canvas: (canvas, page) => phone ? /* @__PURE__ */ jsx(PhoneCanvas, {
18118
+ canvasId: canvas,
18119
+ pageId: page
18120
+ }) : /* @__PURE__ */ jsx(CanvasPage, {
18121
+ canvasId: canvas,
18122
+ pageId: page
18123
+ })
18124
+ };
18125
+ }
17440
18126
  function Routes() {
18127
+ const pages = useCanvasPages();
17441
18128
  return /* @__PURE__ */ jsxs(Switch$1, { children: [
17442
18129
  /* @__PURE__ */ jsx(Route, {
17443
18130
  path: "/c/:canvas/full/:item",
@@ -17448,21 +18135,15 @@ function Routes() {
17448
18135
  }),
17449
18136
  /* @__PURE__ */ jsx(Route, {
17450
18137
  path: "/c/:canvas/play/:item",
17451
- children: (params) => /* @__PURE__ */ jsx(PlayPage, {
17452
- canvasId: params.canvas,
17453
- itemId: params.item
17454
- })
18138
+ children: (params) => pages.play(params.canvas, params.item)
17455
18139
  }),
17456
18140
  /* @__PURE__ */ jsx(Route, {
17457
18141
  path: "/c/:canvas/p/:page",
17458
- children: (params) => /* @__PURE__ */ jsx(CanvasPage, {
17459
- canvasId: params.canvas,
17460
- pageId: params.page
17461
- })
18142
+ children: (params) => pages.canvas(params.canvas, params.page)
17462
18143
  }),
17463
18144
  /* @__PURE__ */ jsx(Route, {
17464
18145
  path: "/c/:canvas",
17465
- children: (params) => /* @__PURE__ */ jsx(CanvasPage, { canvasId: params.canvas })
18146
+ children: (params) => pages.canvas(params.canvas)
17466
18147
  }),
17467
18148
  /* @__PURE__ */ jsxs(Route, {
17468
18149
  path: "/system/*?",
@@ -17482,6 +18163,7 @@ function ScopedRoutes({ canvas }) {
17482
18163
  to: home,
17483
18164
  replace: true
17484
18165
  });
18166
+ const pages = useCanvasPages();
17485
18167
  return /* @__PURE__ */ jsxs(Switch$1, { children: [
17486
18168
  /* @__PURE__ */ jsx(Route, {
17487
18169
  path: "/c/:canvas/full/:item",
@@ -17492,21 +18174,15 @@ function ScopedRoutes({ canvas }) {
17492
18174
  }),
17493
18175
  /* @__PURE__ */ jsx(Route, {
17494
18176
  path: "/c/:canvas/play/:item",
17495
- children: (params) => guard(params.canvas, /* @__PURE__ */ jsx(PlayPage, {
17496
- canvasId: canvas,
17497
- itemId: params.item
17498
- }))
18177
+ children: (params) => guard(params.canvas, pages.play(canvas, params.item))
17499
18178
  }),
17500
18179
  /* @__PURE__ */ jsx(Route, {
17501
18180
  path: "/c/:canvas/p/:page",
17502
- children: (params) => guard(params.canvas, /* @__PURE__ */ jsx(CanvasPage, {
17503
- canvasId: canvas,
17504
- pageId: params.page
17505
- }))
18181
+ children: (params) => guard(params.canvas, pages.canvas(canvas, params.page))
17506
18182
  }),
17507
18183
  /* @__PURE__ */ jsx(Route, {
17508
18184
  path: "/c/:canvas",
17509
- children: (params) => guard(params.canvas, /* @__PURE__ */ jsx(CanvasPage, { canvasId: canvas }))
18185
+ children: (params) => guard(params.canvas, pages.canvas(canvas))
17510
18186
  }),
17511
18187
  /* @__PURE__ */ jsx(Route, { children: /* @__PURE__ */ jsx(Redirect, {
17512
18188
  to: home,
@@ -17808,4 +18484,4 @@ function TextArea({ label, hint, error, className, id, disabled, rows = 4, ...re
17808
18484
  });
17809
18485
  }
17810
18486
  //#endregion
17811
- export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, iconButtonClass, initials, localSource, menuItemClass, menuSurface, resolveUrl, setThemePref, staticSource, toast, useTheme, useThemePref };
18487
+ export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Sheet, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, iconButtonClass, initials, localSource, menuItemClass, menuSurface, resolveUrl, setThemePref, staticSource, toast, useIsPhone, useTheme, useThemePref };