veslx 0.1.24 → 0.1.26

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.
@@ -67,7 +67,7 @@ function useGalleryImages({
67
67
  }) {
68
68
  const { resolvedTheme } = useTheme();
69
69
  const { "*": routePath = "" } = useParams();
70
- const currentDir = routePath.replace(/\/[^/]+\.mdx$/i, "").replace(/\/$/, "") || ".";
70
+ const currentDir = routePath.replace(/\/?[^/]+\.mdx$/i, "").replace(/\/$/, "") || ".";
71
71
  let resolvedPath = path;
72
72
  if (path == null ? void 0 : path.startsWith("./")) {
73
73
  const relativePart = path.slice(2);
@@ -1 +1 @@
1
- {"version":3,"file":"use-gallery-images.js","sources":["../../../../../src/components/gallery/hooks/use-gallery-images.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport { useParams } from \"react-router-dom\";\nimport { useDirectory } from \"../../../../plugin/src/client\";\nimport { FileEntry, DirectoryEntry } from \"../../../../plugin/src/lib\";\nimport { minimatch } from \"minimatch\";\n\n// Recursively collect all image files from a directory tree\nfunction collectAllImages(entry: DirectoryEntry | FileEntry): FileEntry[] {\n if (entry.type === \"file\") {\n if (entry.name.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i)) {\n return [entry];\n }\n return [];\n }\n // It's a directory - recurse into children\n const images: FileEntry[] = [];\n for (const child of entry.children || []) {\n images.push(...collectAllImages(child));\n }\n return images;\n}\n\nfunction sortPathsNumerically(paths: string[]): void {\n paths.sort((a, b) => {\n const nums = (s: string) => (s.match(/\\d+/g) || []).map(Number);\n const na = nums(a);\n const nb = nums(b);\n const len = Math.max(na.length, nb.length);\n for (let i = 0; i < len; i++) {\n const diff = (na[i] ?? 0) - (nb[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return a.localeCompare(b);\n });\n}\n\nfunction filterPathsByTheme(paths: string[], theme: string | undefined): string[] {\n const pathGroups = new Map<string, { light?: string; dark?: string; original?: string }>();\n\n paths.forEach(path => {\n if (path.endsWith('_light.png')) {\n const baseName = path.replace('_light.png', '');\n const group = pathGroups.get(baseName) || {};\n group.light = path;\n pathGroups.set(baseName, group);\n } else if (path.endsWith('_dark.png')) {\n const baseName = path.replace('_dark.png', '');\n const group = pathGroups.get(baseName) || {};\n group.dark = path;\n pathGroups.set(baseName, group);\n } else {\n pathGroups.set(path, { original: path });\n }\n });\n\n const filtered: string[] = [];\n pathGroups.forEach((group, baseName) => {\n if (group.original) {\n filtered.push(group.original);\n } else {\n const isDark = theme === 'dark';\n const preferredPath = isDark ? group.dark : group.light;\n const fallbackPath = isDark ? group.light : group.dark;\n filtered.push(preferredPath || fallbackPath || baseName);\n }\n });\n\n return filtered;\n}\n\n\nexport function useGalleryImages({\n path,\n globs = null,\n limit,\n page = 0,\n}: {\n path?: string;\n globs?: string[] | null;\n limit?: number | null;\n page?: number;\n}) {\n const { resolvedTheme } = useTheme();\n const { \"*\": routePath = \"\" } = useParams();\n\n // Get the current post's directory from the route\n // Route is like \"04-components/README.mdx\" -> \"04-components\"\n // Or \"gallery-examples\" -> \"gallery-examples\"\n const currentDir = routePath\n .replace(/\\/[^/]+\\.mdx$/i, \"\") // Remove /filename.mdx\n .replace(/\\/$/, \"\") // Remove trailing slash\n || \".\";\n\n // Resolve the path relative to current directory\n let resolvedPath = path;\n if (path?.startsWith(\"./\")) {\n // Relative path like \"./images\" -> \"gallery-examples/images\"\n const relativePart = path.slice(2);\n resolvedPath = currentDir === \".\" ? relativePart : `${currentDir}/${relativePart}`;\n } else if (path && !path.startsWith(\"/\") && !path.includes(\"/\")) {\n // Simple name like \"images\" -> \"gallery-examples/images\"\n resolvedPath = currentDir === \".\" ? path : `${currentDir}/${path}`;\n }\n\n // If only globs provided (no path), use root directory\n const directoryPath = resolvedPath || \".\";\n const { directory } = useDirectory(directoryPath);\n\n const paths = useMemo(() => {\n if (!directory) return [];\n\n let imagePaths: string[];\n\n if (globs && globs.length > 0) {\n // When globs provided, collect all images recursively and match against filename\n const allImages = collectAllImages(directory);\n imagePaths = allImages\n .map(img => img.path)\n .filter(p => globs.some(glob => minimatch(p, glob, { matchBase: true })));\n } else {\n // No globs - just get images from the specified directory\n const imageChildren = directory.children.filter((child): child is FileEntry => {\n return !!child.name.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i) && child.type === \"file\";\n });\n imagePaths = imageChildren.map(child => child.path);\n }\n\n sortPathsNumerically(imagePaths);\n let filtered = filterPathsByTheme(imagePaths, resolvedTheme);\n\n if (limit) {\n filtered = filtered.slice(page * limit, (page + 1) * limit);\n }\n\n return filtered;\n }, [directory, globs, resolvedTheme, limit, page]);\n\n return {\n paths,\n isLoading: !directory,\n isEmpty: directory !== undefined && paths.length === 0,\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-gallery-images.js","sources":["../../../../../src/components/gallery/hooks/use-gallery-images.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport { useParams } from \"react-router-dom\";\nimport { useDirectory } from \"../../../../plugin/src/client\";\nimport { FileEntry, DirectoryEntry } from \"../../../../plugin/src/lib\";\nimport { minimatch } from \"minimatch\";\n\n// Recursively collect all image files from a directory tree\nfunction collectAllImages(entry: DirectoryEntry | FileEntry): FileEntry[] {\n if (entry.type === \"file\") {\n if (entry.name.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i)) {\n return [entry];\n }\n return [];\n }\n // It's a directory - recurse into children\n const images: FileEntry[] = [];\n for (const child of entry.children || []) {\n images.push(...collectAllImages(child));\n }\n return images;\n}\n\nfunction sortPathsNumerically(paths: string[]): void {\n paths.sort((a, b) => {\n const nums = (s: string) => (s.match(/\\d+/g) || []).map(Number);\n const na = nums(a);\n const nb = nums(b);\n const len = Math.max(na.length, nb.length);\n for (let i = 0; i < len; i++) {\n const diff = (na[i] ?? 0) - (nb[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return a.localeCompare(b);\n });\n}\n\nfunction filterPathsByTheme(paths: string[], theme: string | undefined): string[] {\n const pathGroups = new Map<string, { light?: string; dark?: string; original?: string }>();\n\n paths.forEach(path => {\n if (path.endsWith('_light.png')) {\n const baseName = path.replace('_light.png', '');\n const group = pathGroups.get(baseName) || {};\n group.light = path;\n pathGroups.set(baseName, group);\n } else if (path.endsWith('_dark.png')) {\n const baseName = path.replace('_dark.png', '');\n const group = pathGroups.get(baseName) || {};\n group.dark = path;\n pathGroups.set(baseName, group);\n } else {\n pathGroups.set(path, { original: path });\n }\n });\n\n const filtered: string[] = [];\n pathGroups.forEach((group, baseName) => {\n if (group.original) {\n filtered.push(group.original);\n } else {\n const isDark = theme === 'dark';\n const preferredPath = isDark ? group.dark : group.light;\n const fallbackPath = isDark ? group.light : group.dark;\n filtered.push(preferredPath || fallbackPath || baseName);\n }\n });\n\n return filtered;\n}\n\n\nexport function useGalleryImages({\n path,\n globs = null,\n limit,\n page = 0,\n}: {\n path?: string;\n globs?: string[] | null;\n limit?: number | null;\n page?: number;\n}) {\n const { resolvedTheme } = useTheme();\n const { \"*\": routePath = \"\" } = useParams();\n\n // Get the current post's directory from the route\n // Route is like \"04-components/README.mdx\" -> \"04-components\"\n // Or \"14-gallery.mdx\" -> \".\" (root level file)\n const currentDir = routePath\n .replace(/\\/?[^/]+\\.mdx$/i, \"\") // Remove [/]filename.mdx (slash optional for root files)\n .replace(/\\/$/, \"\") // Remove trailing slash\n || \".\";\n\n // Resolve the path relative to current directory\n let resolvedPath = path;\n if (path?.startsWith(\"./\")) {\n // Relative path like \"./images\" -> \"gallery-examples/images\"\n const relativePart = path.slice(2);\n resolvedPath = currentDir === \".\" ? relativePart : `${currentDir}/${relativePart}`;\n } else if (path && !path.startsWith(\"/\") && !path.includes(\"/\")) {\n // Simple name like \"images\" -> \"gallery-examples/images\"\n resolvedPath = currentDir === \".\" ? path : `${currentDir}/${path}`;\n }\n\n // If only globs provided (no path), use root directory\n const directoryPath = resolvedPath || \".\";\n const { directory } = useDirectory(directoryPath);\n\n const paths = useMemo(() => {\n if (!directory) return [];\n\n let imagePaths: string[];\n\n if (globs && globs.length > 0) {\n // When globs provided, collect all images recursively and match against filename\n const allImages = collectAllImages(directory);\n imagePaths = allImages\n .map(img => img.path)\n .filter(p => globs.some(glob => minimatch(p, glob, { matchBase: true })));\n } else {\n // No globs - just get images from the specified directory\n const imageChildren = directory.children.filter((child): child is FileEntry => {\n return !!child.name.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i) && child.type === \"file\";\n });\n imagePaths = imageChildren.map(child => child.path);\n }\n\n sortPathsNumerically(imagePaths);\n let filtered = filterPathsByTheme(imagePaths, resolvedTheme);\n\n if (limit) {\n filtered = filtered.slice(page * limit, (page + 1) * limit);\n }\n\n return filtered;\n }, [directory, globs, resolvedTheme, limit, page]);\n\n return {\n paths,\n isLoading: !directory,\n isEmpty: directory !== undefined && paths.length === 0,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -7,7 +7,6 @@ import { useLightbox } from "./hooks/use-lightbox.js";
7
7
  import { LoadingImage } from "./components/loading-image.js";
8
8
  import { FigureHeader } from "./components/figure-header.js";
9
9
  import { FigureCaption } from "./components/figure-caption.js";
10
- import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from "../ui/carousel.js";
11
10
  function getImageLabel(path) {
12
11
  const filename = path.split("/").pop() || path;
13
12
  return filename.replace(/\.(png|jpg|jpeg|gif|svg|webp)$/i, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim();
@@ -60,8 +59,11 @@ function Gallery({
60
59
  /* @__PURE__ */ jsx("span", { className: "font-mono text-xs uppercase tracking-widest", children: "No images" })
61
60
  ] }) });
62
61
  }
62
+ const isSingle = images.length === 1;
63
+ const isTwo = images.length === 2;
63
64
  const isCompact = images.length <= 3;
64
65
  const isSingleWithChildren = images.length === 1 && children;
66
+ const shouldBreakOut = images.length >= 2;
65
67
  const imageElement = (index, img, className) => /* @__PURE__ */ jsx(
66
68
  "div",
67
69
  {
@@ -79,8 +81,8 @@ function Gallery({
79
81
  index
80
82
  );
81
83
  return /* @__PURE__ */ jsxs(Fragment, { children: [
82
- /* @__PURE__ */ jsxs("figure", { className: `not-prose relative py-6 md:py-8 ${isCompact ? "" : "-mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))] px-[calc((var(--gallery-width)-var(--content-width))/2)]"}`, children: [
83
- !isSingleWithChildren && /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
84
+ /* @__PURE__ */ jsxs("figure", { className: `not-prose relative py-6 md:py-8 ${shouldBreakOut ? isTwo ? "w-[75vw] ml-[calc(-37.5vw+50%)]" : isCompact ? "w-[96vw] ml-[calc(-48vw+50%)]" : "w-[var(--gallery-width)] ml-[calc(-45vw+50%)]" : ""}`, children: [
85
+ !isSingleWithChildren && !isSingle && /* @__PURE__ */ jsx("div", { className: "max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]", children: /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }) }),
84
86
  isSingleWithChildren ? /* @__PURE__ */ jsxs("div", { className: `flex gap-6 ${childAlign === "left" ? "" : "flex-row-reverse"}`, children: [
85
87
  /* @__PURE__ */ jsxs("div", { className: "flex-1 text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5 flex flex-col", children: [
86
88
  (title || subtitle) && /* @__PURE__ */ jsx("div", { className: "invisible", children: /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }) }),
@@ -91,29 +93,27 @@ function Gallery({
91
93
  imageElement(0, images[0]),
92
94
  /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
93
95
  ] })
94
- ] }) : isCompact ? /* @__PURE__ */ jsx("div", { className: "flex gap-3", children: images.map((img, index) => imageElement(index, img, "flex-1")) }) : /* @__PURE__ */ jsxs(Carousel, { className: "w-full", children: [
95
- /* @__PURE__ */ jsx(CarouselContent, { className: "-ml-2 md:-ml-3", children: images.map((img, index) => /* @__PURE__ */ jsx(
96
- CarouselItem,
97
- {
98
- className: "pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group",
99
- onClick: () => lightbox.open(index),
100
- children: /* @__PURE__ */ jsx("div", { className: "aspect-square overflow-hidden rounded-sm bg-muted/10", children: /* @__PURE__ */ jsx(
101
- LoadingImage,
102
- {
103
- src: img.src,
104
- alt: img.label,
105
- className: "object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
106
- }
107
- ) })
108
- },
109
- index
110
- )) }),
111
- images.length > 3 && /* @__PURE__ */ jsxs(Fragment, { children: [
112
- /* @__PURE__ */ jsx(CarouselPrevious, { className: "left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" }),
113
- /* @__PURE__ */ jsx(CarouselNext, { className: "right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" })
114
- ] })
115
- ] }),
116
- !isSingleWithChildren && /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
96
+ ] }) : isSingle ? /* @__PURE__ */ jsxs("div", { className: "max-w-[70%] mx-auto", children: [
97
+ /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
98
+ imageElement(0, images[0]),
99
+ /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
100
+ ] }) : isCompact ? /* @__PURE__ */ jsx("div", { className: "flex gap-3", children: images.map((img, index) => imageElement(index, img, "flex-1")) }) : /* @__PURE__ */ jsx("div", { className: "flex gap-3 overflow-x-auto pb-4", children: images.map((img, index) => /* @__PURE__ */ jsx(
101
+ "div",
102
+ {
103
+ className: "flex-none w-[30%] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group",
104
+ onClick: () => lightbox.open(index),
105
+ children: /* @__PURE__ */ jsx(
106
+ LoadingImage,
107
+ {
108
+ src: img.src,
109
+ alt: img.label,
110
+ className: "object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
111
+ }
112
+ )
113
+ },
114
+ index
115
+ )) }),
116
+ !isSingleWithChildren && !isSingle && /* @__PURE__ */ jsx("div", { className: "max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]", children: /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel }) })
117
117
  ] }),
118
118
  lightbox.isOpen && lightbox.selectedIndex !== null && /* @__PURE__ */ jsx(
119
119
  Lightbox,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/components/ui/carousel\"\n\nfunction getImageLabel(path: string): string {\n const filename = path.split('/').pop() || path;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction getImageUrl(path: string): string {\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null,\n caption,\n captionLabel,\n title,\n subtitle,\n limit = null,\n page = 0,\n children,\n childAlign = \"right\",\n}: {\n path?: string;\n globs?: string[] | null;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n limit?: number | null;\n page?: number;\n children?: React.ReactNode;\n childAlign?: \"left\" | \"right\";\n}) {\n const { paths, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n\n const images: LightboxImage[] = useMemo(() =>\n paths.map(p => ({ src: getImageUrl(p), label: getImageLabel(p) })),\n [paths]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\">\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\">\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n const isCompact = images.length <= 3;\n const isSingleWithChildren = images.length === 1 && children;\n\n const imageElement = (index: number, img: LightboxImage, className?: string) => (\n <div\n key={index}\n className={`aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n );\n\n return (\n <>\n <figure className={`not-prose relative py-6 md:py-8 ${isCompact ? '' : '-mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))] px-[calc((var(--gallery-width)-var(--content-width))/2)]'}`}>\n {!isSingleWithChildren && <FigureHeader title={title} subtitle={subtitle} />}\n\n {isSingleWithChildren ? (\n <div className={`flex gap-6 ${childAlign === 'left' ? '' : 'flex-row-reverse'}`}>\n <div className=\"flex-1 text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5 flex flex-col\">\n {(title || subtitle) && <div className=\"invisible\"><FigureHeader title={title} subtitle={subtitle} /></div>}\n <div>{children}</div>\n </div>\n <div className=\"w-3/5 flex-shrink-0\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n </div>\n ) : isCompact ? (\n <div className=\"flex gap-3\">\n {images.map((img, index) => imageElement(index, img, 'flex-1'))}\n </div>\n ) : (\n <Carousel className=\"w-full\">\n <CarouselContent className=\"-ml-2 md:-ml-3\">\n {images.map((img, index) => (\n <CarouselItem\n key={index}\n className=\"pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group\"\n onClick={() => lightbox.open(index)}\n >\n <div className=\"aspect-square overflow-hidden rounded-sm bg-muted/10\">\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n\n {images.length > 3 && (\n <>\n <CarouselPrevious className=\"left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n <CarouselNext className=\"right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n </>\n )}\n </Carousel>\n )}\n\n {!isSingleWithChildren && <FigureCaption caption={caption} label={captionLabel} />}\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\n\nfunction getImageLabel(path: string): string {\n const filename = path.split('/').pop() || path;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction getImageUrl(path: string): string {\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null,\n caption,\n captionLabel,\n title,\n subtitle,\n limit = null,\n page = 0,\n children,\n childAlign = \"right\",\n}: {\n path?: string;\n globs?: string[] | null;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n limit?: number | null;\n page?: number;\n children?: React.ReactNode;\n childAlign?: \"left\" | \"right\";\n}) {\n const { paths, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n\n const images: LightboxImage[] = useMemo(() =>\n paths.map(p => ({ src: getImageUrl(p), label: getImageLabel(p) })),\n [paths]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\">\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\">\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n const isSingle = images.length === 1;\n const isTwo = images.length === 2;\n const isCompact = images.length <= 3;\n const isSingleWithChildren = images.length === 1 && children;\n // 2-3 images should break out of the content width\n const shouldBreakOut = images.length >= 2;\n\n const imageElement = (index: number, img: LightboxImage, className?: string) => (\n <div\n key={index}\n className={`aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n );\n\n return (\n <>\n <figure className={`not-prose relative py-6 md:py-8 ${shouldBreakOut ? (isTwo ? 'w-[75vw] ml-[calc(-37.5vw+50%)]' : isCompact ? 'w-[96vw] ml-[calc(-48vw+50%)]' : 'w-[var(--gallery-width)] ml-[calc(-45vw+50%)]') : ''}`}>\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureHeader title={title} subtitle={subtitle} />\n </div>\n )}\n\n {isSingleWithChildren ? (\n <div className={`flex gap-6 ${childAlign === 'left' ? '' : 'flex-row-reverse'}`}>\n <div className=\"flex-1 text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5 flex flex-col\">\n {(title || subtitle) && <div className=\"invisible\"><FigureHeader title={title} subtitle={subtitle} /></div>}\n <div>{children}</div>\n </div>\n <div className=\"w-3/5 flex-shrink-0\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n </div>\n ) : isSingle ? (\n <div className=\"max-w-[70%] mx-auto\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n ) : isCompact ? (\n <div className=\"flex gap-3\">\n {images.map((img, index) => imageElement(index, img, 'flex-1'))}\n </div>\n ) : (\n <div className=\"flex gap-3 overflow-x-auto pb-4\">\n {images.map((img, index) => (\n <div\n key={index}\n className=\"flex-none w-[30%] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group\"\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n ))}\n </div>\n )}\n\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n )}\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -8,7 +8,13 @@ import { cn } from "../lib/utils.js";
8
8
  function Header({ slideControls } = {}) {
9
9
  const config = siteConfig;
10
10
  const { "*": path } = useParams();
11
- return /* @__PURE__ */ jsx("header", { className: "print:hidden fixed top-0 left-0 right-0 z-40", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4", children: [
11
+ return /* @__PURE__ */ jsx("header", { className: cn(
12
+ "print:hidden",
13
+ slideControls && "fixed top-0 left-0 right-0 z-40"
14
+ ), children: /* @__PURE__ */ jsxs("div", { className: cn(
15
+ "mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4",
16
+ !slideControls && "max-w-[var(--content-width)]"
17
+ ), children: [
12
18
  /* @__PURE__ */ jsx("nav", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
13
19
  Link,
14
20
  {
@@ -1 +1 @@
1
- {"version":3,"file":"header.js","sources":["../../../src/components/header.tsx"],"sourcesContent":["import { Link, useParams } from \"react-router-dom\";\nimport { ModeToggle } from \"./mode-toggle\";\nimport { SiGithub } from \"@icons-pack/react-simple-icons\";\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport siteConfig from \"virtual:veslx-config\";\nimport { cn } from \"@/lib/utils\";\n\ninterface HeaderProps {\n slideControls?: {\n current: number;\n total: number;\n onPrevious: () => void;\n onNext: () => void;\n };\n}\n\nexport function Header({ slideControls }: HeaderProps = {}) {\n const config = siteConfig;\n\n const { \"*\": path } = useParams()\n\n return (\n <header className=\"print:hidden fixed top-0 left-0 right-0 z-40\">\n <div className=\"mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4\">\n <nav className=\"flex items-center gap-1\">\n <Link\n to=\"/\"\n className=\"rounded-lg font-mono py-1.5 text-sm font-medium text-muted-foreground hover:underline\"\n >\n {config.name}\n </Link>\n </nav>\n\n <div className=\"flex-1\" />\n\n {/* Navigation */}\n <nav className=\"flex items-center gap-4\">\n {slideControls && (\n <>\n <button\n onClick={slideControls.onPrevious}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Previous slide (↑)\"\n >\n <ChevronUp className=\"h-4 w-4\" />\n </button>\n <span className=\"font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center\">\n {slideControls.current + 1}/{slideControls.total}\n </span>\n <button\n onClick={slideControls.onNext}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Next slide (↓)\"\n >\n <ChevronDown className=\"h-4 w-4\" />\n </button>\n </>\n )}\n <Link\n to={`/posts`}\n className={cn(\n \"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300\",\n path?.startsWith(\"posts\") && \"font-semibold text-foreground\",\n )}\n >\n Posts\n </Link>\n <Link\n to={`/docs`}\n className={cn(\n \"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300\",\n path?.startsWith(\"docs\") && \"font-semibold text-foreground\",\n )}\n >\n Docs\n </Link>\n {config.github && (\n <Link\n to={`https://github.com/${config.github}`}\n target=\"_blank\"\n className=\"text-muted-foreground/70 hover:text-foreground transition-colors duration-300\"\n aria-label=\"GitHub\"\n >\n <SiGithub className=\"h-4 w-4\" />\n </Link>\n )}\n <ModeToggle />\n </nav>\n </div>\n </header>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAgBO,SAAS,OAAO,EAAE,cAAA,IAA+B,IAAI;AAC1D,QAAM,SAAS;AAEf,QAAM,EAAE,KAAK,KAAA,IAAS,UAAA;AAEtB,6BACG,UAAA,EAAO,WAAU,gDAChB,UAAA,qBAAC,OAAA,EAAI,WAAU,wEACb,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAG;AAAA,QACH,WAAU;AAAA,QAET,UAAA,OAAO;AAAA,MAAA;AAAA,IAAA,GAEZ;AAAA,IAEA,oBAAC,OAAA,EAAI,WAAU,SAAA,CAAS;AAAA,IAGxB,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,iBACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEjC,qBAAC,QAAA,EAAK,WAAU,mFACb,UAAA;AAAA,UAAA,cAAc,UAAU;AAAA,UAAE;AAAA,UAAE,cAAc;AAAA,QAAA,GAC7C;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,aAAA,EAAY,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACnC,GACF;AAAA,MAEF;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,aACA,6BAAM,WAAW,aAAY;AAAA,UAAA;AAAA,UAEhC,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,aACA,6BAAM,WAAW,YAAW;AAAA,UAAA;AAAA,UAE/B,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGA,OAAO,UACN;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI,sBAAsB,OAAO,MAAM;AAAA,UACvC,QAAO;AAAA,UACP,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA,oBAAC,UAAA,EAAS,WAAU,UAAA,CAAU;AAAA,QAAA;AAAA,MAAA;AAAA,0BAGjC,YAAA,CAAA,CAAW;AAAA,IAAA,EAAA,CACd;AAAA,EAAA,EAAA,CACF,EAAA,CACF;AAEJ;"}
1
+ {"version":3,"file":"header.js","sources":["../../../src/components/header.tsx"],"sourcesContent":["import { Link, useParams } from \"react-router-dom\";\nimport { ModeToggle } from \"./mode-toggle\";\nimport { SiGithub } from \"@icons-pack/react-simple-icons\";\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport siteConfig from \"virtual:veslx-config\";\nimport { cn } from \"@/lib/utils\";\n\ninterface HeaderProps {\n slideControls?: {\n current: number;\n total: number;\n onPrevious: () => void;\n onNext: () => void;\n };\n}\n\nexport function Header({ slideControls }: HeaderProps = {}) {\n const config = siteConfig;\n\n const { \"*\": path } = useParams()\n\n return (\n <header className={cn(\n \"print:hidden\",\n slideControls && \"fixed top-0 left-0 right-0 z-40\"\n )}>\n <div className={cn(\n \"mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4\",\n !slideControls && \"max-w-[var(--content-width)]\"\n )}>\n <nav className=\"flex items-center gap-1\">\n <Link\n to=\"/\"\n className=\"rounded-lg font-mono py-1.5 text-sm font-medium text-muted-foreground hover:underline\"\n >\n {config.name}\n </Link>\n </nav>\n\n <div className=\"flex-1\" />\n\n {/* Navigation */}\n <nav className=\"flex items-center gap-4\">\n {slideControls && (\n <>\n <button\n onClick={slideControls.onPrevious}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Previous slide (↑)\"\n >\n <ChevronUp className=\"h-4 w-4\" />\n </button>\n <span className=\"font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center\">\n {slideControls.current + 1}/{slideControls.total}\n </span>\n <button\n onClick={slideControls.onNext}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Next slide (↓)\"\n >\n <ChevronDown className=\"h-4 w-4\" />\n </button>\n </>\n )}\n <Link\n to={`/posts`}\n className={cn(\n \"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300\",\n path?.startsWith(\"posts\") && \"font-semibold text-foreground\",\n )}\n >\n Posts\n </Link>\n <Link\n to={`/docs`}\n className={cn(\n \"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300\",\n path?.startsWith(\"docs\") && \"font-semibold text-foreground\",\n )}\n >\n Docs\n </Link>\n {config.github && (\n <Link\n to={`https://github.com/${config.github}`}\n target=\"_blank\"\n className=\"text-muted-foreground/70 hover:text-foreground transition-colors duration-300\"\n aria-label=\"GitHub\"\n >\n <SiGithub className=\"h-4 w-4\" />\n </Link>\n )}\n <ModeToggle />\n </nav>\n </div>\n </header>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAgBO,SAAS,OAAO,EAAE,cAAA,IAA+B,IAAI;AAC1D,QAAM,SAAS;AAEf,QAAM,EAAE,KAAK,KAAA,IAAS,UAAA;AAEtB,SACE,oBAAC,YAAO,WAAW;AAAA,IACjB;AAAA,IACA,iBAAiB;AAAA,EAAA,GAEjB,UAAA,qBAAC,OAAA,EAAI,WAAW;AAAA,IACd;AAAA,IACA,CAAC,iBAAiB;AAAA,EAAA,GAElB,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAG;AAAA,QACH,WAAU;AAAA,QAET,UAAA,OAAO;AAAA,MAAA;AAAA,IAAA,GAEZ;AAAA,IAEA,oBAAC,OAAA,EAAI,WAAU,SAAA,CAAS;AAAA,IAGxB,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,iBACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEjC,qBAAC,QAAA,EAAK,WAAU,mFACb,UAAA;AAAA,UAAA,cAAc,UAAU;AAAA,UAAE;AAAA,UAAE,cAAc;AAAA,QAAA,GAC7C;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,aAAA,EAAY,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACnC,GACF;AAAA,MAEF;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,aACA,6BAAM,WAAW,aAAY;AAAA,UAAA;AAAA,UAEhC,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,aACA,6BAAM,WAAW,YAAW;AAAA,UAAA;AAAA,UAE/B,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGA,OAAO,UACN;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI,sBAAsB,OAAO,MAAM;AAAA,UACvC,QAAO;AAAA,UACP,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA,oBAAC,UAAA,EAAS,WAAU,UAAA,CAAU;AAAA,QAAA;AAAA,MAAA;AAAA,0BAGjC,YAAA,CAAA,CAAW;AAAA,IAAA,EAAA,CACd;AAAA,EAAA,EAAA,CACF,EAAA,CACF;AAEJ;"}
@@ -60,7 +60,7 @@ function PostList({ directory, view = "all" }) {
60
60
  } else {
61
61
  linkPath = `/${post.path}`;
62
62
  }
63
- const isSlides = linkPath.endsWith("SLIDES.mdx");
63
+ const isSlides = linkPath.endsWith("SLIDES.mdx") || linkPath.endsWith(".slides.mdx");
64
64
  return /* @__PURE__ */ jsx(
65
65
  Link,
66
66
  {
@@ -1 +1 @@
1
- {"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { Link } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport type { DirectoryEntry } from \"../../plugin/src/lib\";\nimport { formatDate } from \"@/lib/format-date\";\nimport { ArrowRight, Presentation } from \"lucide-react\";\nimport {\n type ContentView,\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n filterByView,\n getFrontmatter,\n} from \"@/lib/content-classification\";\n\n// Helper to extract numeric prefix from filename (e.g., \"01-intro\" → 1)\nfunction extractOrder(name: string): number | null {\n const match = name.match(/^(\\d+)-/);\n return match ? parseInt(match[1], 10) : null;\n}\n\n// Helper to strip numeric prefix for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction stripNumericPrefix(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\ninterface PostListProps {\n directory: DirectoryEntry;\n view?: ContentView;\n}\n\nexport default function PostList({ directory, view = 'all' }: PostListProps) {\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n // Apply view filter\n posts = filterByView(posts, view);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Helper to get date from post\n const getPostDate = (post: PostEntry): Date | null => {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.date ? new Date(frontmatter.date as string) : null;\n };\n\n // Smart sorting: numeric prefix → date → alphabetical\n posts = posts.sort((a, b) => {\n const aOrder = extractOrder(a.name);\n const bOrder = extractOrder(b.name);\n const aDate = getPostDate(a);\n const bDate = getPostDate(b);\n\n // Both have numeric prefix → sort by number\n if (aOrder !== null && bOrder !== null) {\n return aOrder - bOrder;\n }\n // One has prefix, one doesn't → prefixed comes first\n if (aOrder !== null) return -1;\n if (bOrder !== null) return 1;\n\n // Both have dates → sort by date (newest first)\n if (aDate && bDate) {\n return bDate.getTime() - aDate.getTime();\n }\n // One has date → dated comes first\n if (aDate) return -1;\n if (bDate) return 1;\n\n // Neither → alphabetical by title\n const aTitle = (getFrontmatter(a)?.title as string) || a.name;\n const bTitle = (getFrontmatter(b)?.title as string) || b.name;\n return aTitle.localeCompare(bTitle);\n });\n\n return (\n <div className=\"space-y-1\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n\n // Title: explicit frontmatter > stripped numeric prefix > raw name\n const title = (frontmatter?.title as string) || stripNumericPrefix(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : null;\n\n // Determine the link path\n let linkPath: string;\n if (post.file) {\n // Standalone MDX file\n linkPath = `/${post.file.path}`;\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n linkPath = `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n linkPath = `/${post.readme.path}`;\n } else {\n // Fallback to folder path\n linkPath = `/${post.path}`;\n }\n\n const isSlides = linkPath.endsWith('SLIDES.mdx');\n\n return (\n <Link\n key={post.path}\n to={linkPath}\n className={cn(\n \"group block py-3 px-3 -mx-3 rounded-md\",\n \"transition-colors duration-150\",\n )}\n >\n <article className=\"flex items-center gap-4\">\n {/* Main content */}\n <div className=\"flex-1 min-w-0\">\n <h3 className={cn(\n \"text-sm font-medium text-foreground\",\n \"group-hover:underline\",\n \"flex items-center gap-2\"\n )}>\n <span>{title}</span>\n <ArrowRight className=\"h-3 w-3 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200 text-primary\" />\n </h3>\n\n {description && (\n <p className=\"text-sm text-muted-foreground line-clamp-1 mt-0.5\">\n {description}\n </p>\n )}\n </div>\n\n {isSlides && (\n <Presentation className=\"h-3 w-3 text-muted-foreground\" />\n )}\n <time\n dateTime={date?.toISOString()}\n className=\"font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0\"\n >\n {date && formatDate(date)}\n </time>\n </article>\n </Link>\n );\n })}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { Link } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport type { DirectoryEntry } from \"../../plugin/src/lib\";\nimport { formatDate } from \"@/lib/format-date\";\nimport { ArrowRight, Presentation } from \"lucide-react\";\nimport {\n type ContentView,\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n filterByView,\n getFrontmatter,\n} from \"@/lib/content-classification\";\n\n// Helper to extract numeric prefix from filename (e.g., \"01-intro\" → 1)\nfunction extractOrder(name: string): number | null {\n const match = name.match(/^(\\d+)-/);\n return match ? parseInt(match[1], 10) : null;\n}\n\n// Helper to strip numeric prefix for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction stripNumericPrefix(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\ninterface PostListProps {\n directory: DirectoryEntry;\n view?: ContentView;\n}\n\nexport default function PostList({ directory, view = 'all' }: PostListProps) {\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n // Apply view filter\n posts = filterByView(posts, view);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Helper to get date from post\n const getPostDate = (post: PostEntry): Date | null => {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.date ? new Date(frontmatter.date as string) : null;\n };\n\n // Smart sorting: numeric prefix → date → alphabetical\n posts = posts.sort((a, b) => {\n const aOrder = extractOrder(a.name);\n const bOrder = extractOrder(b.name);\n const aDate = getPostDate(a);\n const bDate = getPostDate(b);\n\n // Both have numeric prefix → sort by number\n if (aOrder !== null && bOrder !== null) {\n return aOrder - bOrder;\n }\n // One has prefix, one doesn't → prefixed comes first\n if (aOrder !== null) return -1;\n if (bOrder !== null) return 1;\n\n // Both have dates → sort by date (newest first)\n if (aDate && bDate) {\n return bDate.getTime() - aDate.getTime();\n }\n // One has date → dated comes first\n if (aDate) return -1;\n if (bDate) return 1;\n\n // Neither → alphabetical by title\n const aTitle = (getFrontmatter(a)?.title as string) || a.name;\n const bTitle = (getFrontmatter(b)?.title as string) || b.name;\n return aTitle.localeCompare(bTitle);\n });\n\n return (\n <div className=\"space-y-1\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n\n // Title: explicit frontmatter > stripped numeric prefix > raw name\n const title = (frontmatter?.title as string) || stripNumericPrefix(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : null;\n\n // Determine the link path\n let linkPath: string;\n if (post.file) {\n // Standalone MDX file\n linkPath = `/${post.file.path}`;\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n linkPath = `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n linkPath = `/${post.readme.path}`;\n } else {\n // Fallback to folder path\n linkPath = `/${post.path}`;\n }\n\n const isSlides = linkPath.endsWith('SLIDES.mdx') || linkPath.endsWith('.slides.mdx');\n\n return (\n <Link\n key={post.path}\n to={linkPath}\n className={cn(\n \"group block py-3 px-3 -mx-3 rounded-md\",\n \"transition-colors duration-150\",\n )}\n >\n <article className=\"flex items-center gap-4\">\n {/* Main content */}\n <div className=\"flex-1 min-w-0\">\n <h3 className={cn(\n \"text-sm font-medium text-foreground\",\n \"group-hover:underline\",\n \"flex items-center gap-2\"\n )}>\n <span>{title}</span>\n <ArrowRight className=\"h-3 w-3 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200 text-primary\" />\n </h3>\n\n {description && (\n <p className=\"text-sm text-muted-foreground line-clamp-1 mt-0.5\">\n {description}\n </p>\n )}\n </div>\n\n {isSlides && (\n <Presentation className=\"h-3 w-3 text-muted-foreground\" />\n )}\n <time\n dateTime={date?.toISOString()}\n className=\"font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0\"\n >\n {date && formatDate(date)}\n </time>\n </article>\n </Link>\n );\n })}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@ function Home({ view }) {
24
24
  /* @__PURE__ */ jsx(Header, {}),
25
25
  /* @__PURE__ */ jsxs("main", { className: "flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]", children: [
26
26
  /* @__PURE__ */ jsx("title", { children: isRoot ? config.name : `${config.name} - ${path}` }),
27
- /* @__PURE__ */ jsxs("main", { className: "flex flex-col gap-8 mb-32 mt-32", children: [
27
+ /* @__PURE__ */ jsxs("main", { className: "flex flex-col gap-8 mb-32 mt-12", children: [
28
28
  isRoot && /* @__PURE__ */ jsxs("div", { className: "animate-fade-in", children: [
29
29
  /* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground", children: config.name }),
30
30
  config.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: config.description })
@@ -1 +1 @@
1
- {"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport PostList from \"@/components/post-list\";\nimport { ErrorDisplay } from \"@/components/page-error\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { ContentTabs } from \"@/components/content-tabs\";\nimport {\n type ContentView,\n directoryToPostEntries,\n filterVisiblePosts,\n getViewCounts,\n} from \"@/lib/content-classification\";\nimport siteConfig from \"virtual:veslx-config\";\n\ninterface HomeProps {\n view?: ContentView;\n}\n\nexport function Home({ view }: HomeProps) {\n const { \"*\": path = \".\" } = useParams();\n const config = siteConfig;\n\n // Normalize path - \"posts\", \"docs\", and \"all\" are view routes, not directories\n const isViewRoute = path === \"posts\" || path === \"docs\" || path === \"all\";\n const directoryPath = isViewRoute ? \".\" : path;\n\n const { directory, loading, error } = useDirectory(directoryPath)\n\n // Use prop view, fallback to config default\n const activeView = view ?? config.defaultView;\n\n const isRoot = path === \".\" || path === \"\" || isViewRoute;\n\n // Calculate counts for tabs (only meaningful on root)\n const counts = directory\n ? getViewCounts(filterVisiblePosts(directoryToPostEntries(directory)))\n : { posts: 0, docs: 0, all: 0 };\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{isRoot ? config.name : `${config.name} - ${path}`}</title>\n <main className=\"flex flex-col gap-8 mb-32 mt-32\">\n {isRoot && (\n <div className=\"animate-fade-in\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground\">\n {config.name}\n </h1>\n {config.description && (\n <p className=\"mt-2 text-muted-foreground\">\n {config.description}\n </p>\n )}\n </div>\n )}\n\n <div className=\"flex flex-col gap-2\">\n {/* {isRoot && directory && (\n <div className=\"animate-fade-in\">\n <ContentTabs value={activeView} counts={counts} />\n </div>\n )} */}\n {directory && (\n <div className=\"animate-fade-in\">\n <PostList directory={directory} view={isRoot ? activeView : 'all'} />\n </div>\n )}\n </div>\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAoBO,SAAS,KAAK,EAAE,QAAmB;AACxC,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,SAAS;AAGf,QAAM,cAAc,SAAS,WAAW,SAAS,UAAU,SAAS;AACpE,QAAM,gBAAgB,cAAc,MAAM;AAE1C,QAAM,EAAE,WAAoB,UAAU,aAAa,aAAa;AAGhE,QAAM,aAAa,QAAQ,OAAO;AAElC,QAAM,SAAS,SAAS,OAAO,SAAS,MAAM;AAG/B,cACX,cAAc,mBAAmB,uBAAuB,SAAS,CAAC,CAAC,IACnE,CAA4B;AAEhC,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAQA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAO,mBAAS,OAAO,OAAO,GAAG,OAAO,IAAI,MAAM,IAAI,GAAA,CAAG;AAAA,MAC1D,qBAAC,QAAA,EAAK,WAAU,mCACb,UAAA;AAAA,QAAA,UACC,qBAAC,OAAA,EAAI,WAAU,mBACb,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,qEACX,UAAA,OAAO,MACV;AAAA,UACC,OAAO,eACN,oBAAC,OAAE,WAAU,8BACV,iBAAO,YAAA,CACV;AAAA,QAAA,GAEJ;AAAA,4BAGD,OAAA,EAAI,WAAU,uBAMZ,UAAA,iCACE,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,YAAS,WAAsB,MAAM,SAAS,aAAa,OAAO,GACrE,EAAA,CAEJ;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport PostList from \"@/components/post-list\";\nimport { ErrorDisplay } from \"@/components/page-error\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { ContentTabs } from \"@/components/content-tabs\";\nimport {\n type ContentView,\n directoryToPostEntries,\n filterVisiblePosts,\n getViewCounts,\n} from \"@/lib/content-classification\";\nimport siteConfig from \"virtual:veslx-config\";\n\ninterface HomeProps {\n view?: ContentView;\n}\n\nexport function Home({ view }: HomeProps) {\n const { \"*\": path = \".\" } = useParams();\n const config = siteConfig;\n\n // Normalize path - \"posts\", \"docs\", and \"all\" are view routes, not directories\n const isViewRoute = path === \"posts\" || path === \"docs\" || path === \"all\";\n const directoryPath = isViewRoute ? \".\" : path;\n\n const { directory, loading, error } = useDirectory(directoryPath)\n\n // Use prop view, fallback to config default\n const activeView = view ?? config.defaultView;\n\n const isRoot = path === \".\" || path === \"\" || isViewRoute;\n\n // Calculate counts for tabs (only meaningful on root)\n const counts = directory\n ? getViewCounts(filterVisiblePosts(directoryToPostEntries(directory)))\n : { posts: 0, docs: 0, all: 0 };\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{isRoot ? config.name : `${config.name} - ${path}`}</title>\n <main className=\"flex flex-col gap-8 mb-32 mt-12\">\n {isRoot && (\n <div className=\"animate-fade-in\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground\">\n {config.name}\n </h1>\n {config.description && (\n <p className=\"mt-2 text-muted-foreground\">\n {config.description}\n </p>\n )}\n </div>\n )}\n\n <div className=\"flex flex-col gap-2\">\n {directory && (\n <div className=\"animate-fade-in\">\n <PostList directory={directory} view={isRoot ? activeView : 'all'} />\n </div>\n )}\n </div>\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAoBO,SAAS,KAAK,EAAE,QAAmB;AACxC,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,SAAS;AAGf,QAAM,cAAc,SAAS,WAAW,SAAS,UAAU,SAAS;AACpE,QAAM,gBAAgB,cAAc,MAAM;AAE1C,QAAM,EAAE,WAAoB,UAAU,aAAa,aAAa;AAGhE,QAAM,aAAa,QAAQ,OAAO;AAElC,QAAM,SAAS,SAAS,OAAO,SAAS,MAAM;AAG/B,cACX,cAAc,mBAAmB,uBAAuB,SAAS,CAAC,CAAC,IACnE,CAA4B;AAEhC,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAQA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAO,mBAAS,OAAO,OAAO,GAAG,OAAO,IAAI,MAAM,IAAI,GAAA,CAAG;AAAA,MAC1D,qBAAC,QAAA,EAAK,WAAU,mCACb,UAAA;AAAA,QAAA,UACC,qBAAC,OAAA,EAAI,WAAU,mBACb,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,qEACX,UAAA,OAAO,MACV;AAAA,UACC,OAAO,eACN,oBAAC,OAAE,WAAU,8BACV,iBAAO,YAAA,CACV;AAAA,QAAA,GAEJ;AAAA,4BAGD,OAAA,EAAI,WAAU,uBACZ,UAAA,iCACE,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,YAAS,WAAsB,MAAM,SAAS,aAAa,OAAO,GACrE,EAAA,CAEJ;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
@@ -26,13 +26,13 @@ function Post() {
26
26
  /* @__PURE__ */ jsx("title", { children: frontmatter == null ? void 0 : frontmatter.title }),
27
27
  /* @__PURE__ */ jsx(RunningBar, {}),
28
28
  /* @__PURE__ */ jsx(Header, {}),
29
- /* @__PURE__ */ jsxs("main", { className: "flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]", children: [
29
+ /* @__PURE__ */ jsxs("main", { className: "flex-1 w-full overflow-x-clip", children: [
30
30
  isRunning && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-3", children: [
31
31
  /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-current animate-pulse" }),
32
32
  /* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
33
33
  /* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
34
34
  ] }) }),
35
- Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "my-24 prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--prose-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) })
35
+ Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "my-12 mx-auto px-[var(--page-padding)] prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--content-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) })
36
36
  ] })
37
37
  ] });
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\nexport function Post() {\n const { \"*\": rawPath = \".\" } = useParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Extract directory path for finding sibling files (slides, etc.)\n const dirPath = mdxPath.replace(/\\/[^/]+\\.mdx$/, '') || '.';\n\n const { directory, loading: dirLoading } = useDirectory(dirPath)\n const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(mdxPath);\n const isRunning = isSimulationRunning();\n\n let slides: FileEntry | null = null;\n if (directory) {\n slides = findSlides(directory);\n }\n\n const loading = dirLoading || mdxLoading;\n\n if (loading) return <Loading />\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n {isRunning && (\n <div className=\"sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <FrontmatterProvider frontmatter={frontmatter}>\n <article className=\"my-24 prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--prose-width)] animate-fade-in\">\n <Content components={mdxComponents} />\n </article>\n </FrontmatterProvider>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAUO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,UAAU,IAAA,IAAQ,UAAA;AAG/B,QAAM,UAAU;AAGhB,QAAM,UAAU,QAAQ,QAAQ,iBAAiB,EAAE,KAAK;AAExD,QAAM,EAAE,UAA+B,IAAI,aAAa,OAAO;AAC/D,QAAM,EAAE,SAAS,aAAa,SAAS,YAAY,MAAA,IAAU,cAAc,OAAO;AAClF,QAAM,YAAY,oBAAA;AAGlB,MAAI,WAAW;AACJ,eAAW,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAwB;AAE9B,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAE7B,MAAI,OAAO;AACT,WACE,oBAAC,QAAA,EAAK,WAAU,gEACd,UAAA,oBAAC,OAAE,WAAU,4BAA4B,UAAA,MAAM,QAAA,CAAQ,GACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACb,UAAA;AAAA,MAAA,iCACE,OAAA,EAAI,WAAU,kIACb,UAAA,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,oDAAA,CAAoD;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA,sBAAkB;AAAA,QAC9D,oBAAC,QAAA,EAAK,WAAU,8BAA6B,UAAA,uCAAA,CAAoC;AAAA,MAAA,EAAA,CACnF,EAAA,CACF;AAAA,MAGD,WACC,oBAAC,qBAAA,EAAoB,aACnB,UAAA,oBAAC,WAAA,EAAQ,WAAU,oMACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\nexport function Post() {\n const { \"*\": rawPath = \".\" } = useParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Extract directory path for finding sibling files (slides, etc.)\n const dirPath = mdxPath.replace(/\\/[^/]+\\.mdx$/, '') || '.';\n\n const { directory, loading: dirLoading } = useDirectory(dirPath)\n const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(mdxPath);\n const isRunning = isSimulationRunning();\n\n let slides: FileEntry | null = null;\n if (directory) {\n slides = findSlides(directory);\n }\n\n const loading = dirLoading || mdxLoading;\n\n if (loading) return <Loading />\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header />\n <main className=\"flex-1 w-full overflow-x-clip\">\n {isRunning && (\n <div className=\"sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <FrontmatterProvider frontmatter={frontmatter}>\n <article className=\"my-12 mx-auto px-[var(--page-padding)] prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--content-width)] animate-fade-in\">\n <Content components={mdxComponents} />\n </article>\n </FrontmatterProvider>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAUO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,UAAU,IAAA,IAAQ,UAAA;AAG/B,QAAM,UAAU;AAGhB,QAAM,UAAU,QAAQ,QAAQ,iBAAiB,EAAE,KAAK;AAExD,QAAM,EAAE,UAA+B,IAAI,aAAa,OAAO;AAC/D,QAAM,EAAE,SAAS,aAAa,SAAS,YAAY,MAAA,IAAU,cAAc,OAAO;AAClF,QAAM,YAAY,oBAAA;AAGlB,MAAI,WAAW;AACJ,eAAW,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAwB;AAE9B,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAE7B,MAAI,OAAO;AACT,WACE,oBAAC,QAAA,EAAK,WAAU,gEACd,UAAA,oBAAC,OAAE,WAAU,4BAA4B,UAAA,MAAM,QAAA,CAAQ,GACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,iCACb,UAAA;AAAA,MAAA,iCACE,OAAA,EAAI,WAAU,kIACb,UAAA,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,oDAAA,CAAoD;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA,sBAAkB;AAAA,QAC9D,oBAAC,QAAA,EAAK,WAAU,8BAA6B,UAAA,uCAAA,CAAoC;AAAA,MAAA,EAAA,CACnF,EAAA,CACF;AAAA,MAGD,WACC,oBAAC,qBAAA,EAAoB,aACnB,UAAA,oBAAC,WAAA,EAAQ,WAAU,uOACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
@@ -67,10 +67,10 @@ function SlidesPage() {
67
67
  }, [currentSlide, totalSlides]);
68
68
  useEffect(() => {
69
69
  const handleKeyDown = (e) => {
70
- if (e.key === "ArrowUp" || e.key === "ArrowLeft" || e.key === "k") {
70
+ if (e.key === "ArrowUp") {
71
71
  e.preventDefault();
72
72
  goToPrevious();
73
- } else if (e.key === "ArrowDown" || e.key === "ArrowRight" || e.key === "j") {
73
+ } else if (e.key === "ArrowDown") {
74
74
  e.preventDefault();
75
75
  goToNext();
76
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"slides.js","sources":["../../../src/pages/slides.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\nimport { useParams, useSearchParams } from \"react-router-dom\"\nimport Loading from \"@/components/loading\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { slidesMdxComponents } from \"@/components/slides-renderer\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\n\nexport function SlidesPage() {\n const { \"*\": rawPath = \".\" } = useParams();\n const [searchParams, setSearchParams] = useSearchParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Load the compiled MDX module (now includes slideCount export)\n const { Content, frontmatter, slideCount, loading, error } = useMDXSlides(mdxPath);\n\n const totalSlides = slideCount || 0;\n\n const [currentSlide, setCurrentSlide] = useState(0);\n const titleSlideRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n\n // Scroll to slide on initial load if query param is set\n useEffect(() => {\n const slideParam = parseInt(searchParams.get(\"slide\") || \"0\", 10);\n if (slideParam > 0 && contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${slideParam}\"]`);\n if (slideEl) {\n slideEl.scrollIntoView({ behavior: \"auto\" });\n }\n }\n }, [searchParams, Content]);\n\n // Track current slide based on scroll position\n useEffect(() => {\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const index = entry.target.getAttribute(\"data-slide-index\");\n if (index !== null) {\n const slideNum = index === \"title\" ? 0 : parseInt(index, 10);\n setCurrentSlide(slideNum);\n setSearchParams(slideNum > 0 ? { slide: String(slideNum) } : {}, { replace: true });\n }\n }\n }\n },\n { threshold: 0.5 }\n );\n\n // Observe title slide\n if (titleSlideRef.current) {\n observer.observe(titleSlideRef.current);\n }\n\n // Observe content slides\n if (contentRef.current) {\n const slides = contentRef.current.querySelectorAll(\"[data-slide-index]\");\n slides.forEach((slide) => observer.observe(slide));\n }\n\n return () => observer.disconnect();\n }, [Content, setSearchParams]);\n\n // Keyboard/scroll navigation helpers\n const goToPrevious = useCallback(() => {\n const prev = Math.max(0, currentSlide - 1);\n if (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${prev}\"]`);\n slideEl?.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [currentSlide]);\n\n const goToNext = useCallback(() => {\n const next = Math.min(totalSlides - 1, currentSlide + 1);\n if (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${next}\"]`);\n slideEl?.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [currentSlide, totalSlides]);\n\n // Keyboard navigation\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowUp\" || e.key === \"ArrowLeft\" || e.key === \"k\") {\n e.preventDefault();\n goToPrevious();\n } else if (e.key === \"ArrowDown\" || e.key === \"ArrowRight\" || e.key === \"j\") {\n e.preventDefault();\n goToNext();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [goToPrevious, goToNext]);\n\n if (loading) {\n return <Loading />\n }\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n if (!Content) {\n return (\n <div className=\"flex items-center justify-center p-12 text-muted-foreground font-mono text-sm\">\n no slides found — use \"---\" to separate slides\n </div>\n );\n }\n\n return (\n <main className=\"slides-container\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header\n slideControls={{\n current: currentSlide,\n total: totalSlides,\n onPrevious: goToPrevious,\n onNext: goToNext,\n }}\n />\n <FrontmatterProvider frontmatter={frontmatter}>\n <div {...{[FULLSCREEN_DATA_ATTR]: \"true\"}}>\n <div ref={contentRef}>\n <Content components={slidesMdxComponents} />\n </div>\n </div>\n </FrontmatterProvider>\n </main>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slides.js","sources":["../../../src/pages/slides.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\nimport { useParams, useSearchParams } from \"react-router-dom\"\nimport Loading from \"@/components/loading\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { slidesMdxComponents } from \"@/components/slides-renderer\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\n\nexport function SlidesPage() {\n const { \"*\": rawPath = \".\" } = useParams();\n const [searchParams, setSearchParams] = useSearchParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Load the compiled MDX module (now includes slideCount export)\n const { Content, frontmatter, slideCount, loading, error } = useMDXSlides(mdxPath);\n\n const totalSlides = slideCount || 0;\n\n const [currentSlide, setCurrentSlide] = useState(0);\n const titleSlideRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n\n // Scroll to slide on initial load if query param is set\n useEffect(() => {\n const slideParam = parseInt(searchParams.get(\"slide\") || \"0\", 10);\n if (slideParam > 0 && contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${slideParam}\"]`);\n if (slideEl) {\n slideEl.scrollIntoView({ behavior: \"auto\" });\n }\n }\n }, [searchParams, Content]);\n\n // Track current slide based on scroll position\n useEffect(() => {\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const index = entry.target.getAttribute(\"data-slide-index\");\n if (index !== null) {\n const slideNum = index === \"title\" ? 0 : parseInt(index, 10);\n setCurrentSlide(slideNum);\n setSearchParams(slideNum > 0 ? { slide: String(slideNum) } : {}, { replace: true });\n }\n }\n }\n },\n { threshold: 0.5 }\n );\n\n // Observe title slide\n if (titleSlideRef.current) {\n observer.observe(titleSlideRef.current);\n }\n\n // Observe content slides\n if (contentRef.current) {\n const slides = contentRef.current.querySelectorAll(\"[data-slide-index]\");\n slides.forEach((slide) => observer.observe(slide));\n }\n\n return () => observer.disconnect();\n }, [Content, setSearchParams]);\n\n // Keyboard/scroll navigation helpers\n const goToPrevious = useCallback(() => {\n const prev = Math.max(0, currentSlide - 1);\n if (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${prev}\"]`);\n slideEl?.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [currentSlide]);\n\n const goToNext = useCallback(() => {\n const next = Math.min(totalSlides - 1, currentSlide + 1);\n if (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${next}\"]`);\n slideEl?.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [currentSlide, totalSlides]);\n\n // Keyboard navigation (up/down only - left/right reserved for horizontal scrolling)\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n goToPrevious();\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n goToNext();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [goToPrevious, goToNext]);\n\n if (loading) {\n return <Loading />\n }\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n if (!Content) {\n return (\n <div className=\"flex items-center justify-center p-12 text-muted-foreground font-mono text-sm\">\n no slides found — use \"---\" to separate slides\n </div>\n );\n }\n\n return (\n <main className=\"slides-container\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header\n slideControls={{\n current: currentSlide,\n total: totalSlides,\n onPrevious: goToPrevious,\n onNext: goToNext,\n }}\n />\n <FrontmatterProvider frontmatter={frontmatter}>\n <div {...{[FULLSCREEN_DATA_ATTR]: \"true\"}}>\n <div ref={contentRef}>\n <Content components={slidesMdxComponents} />\n </div>\n </div>\n </FrontmatterProvider>\n </main>\n )\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veslx",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,16 +49,6 @@
49
49
  "@tailwindcss/vite": "^4.1.17",
50
50
  "@types/bun": "^1.3.4",
51
51
  "@types/js-yaml": "^4.0.9",
52
- "@visx/axis": "^3.12.0",
53
- "@visx/curve": "^3.12.0",
54
- "@visx/gradient": "^3.12.0",
55
- "@visx/grid": "^3.12.0",
56
- "@visx/group": "^3.12.0",
57
- "@visx/responsive": "^3.12.0",
58
- "@visx/scale": "^3.12.0",
59
- "@visx/shape": "^3.12.0",
60
- "@visx/text": "^3.12.0",
61
- "@visx/tooltip": "^3.12.0",
62
52
  "@vitejs/plugin-react": "^5.1.0",
63
53
  "acorn": "^8.15.0",
64
54
  "acorn-jsx": "^5.3.2",
@@ -100,6 +90,11 @@
100
90
  "@types/node": "^25.0.0",
101
91
  "@types/react": "^19.2.2",
102
92
  "@types/react-dom": "^19.2.2",
93
+ "@visx/axis": "^3.12.0",
94
+ "@visx/gradient": "^3.12.0",
95
+ "@visx/grid": "^3.12.0",
96
+ "@visx/group": "^3.12.0",
97
+ "@visx/shape": "^3.12.0",
103
98
  "chokidar": "^4.0.3",
104
99
  "eslint": "^9.39.1",
105
100
  "eslint-plugin-react-hooks": "^7.0.1",
@@ -86,9 +86,9 @@ export function useGalleryImages({
86
86
 
87
87
  // Get the current post's directory from the route
88
88
  // Route is like "04-components/README.mdx" -> "04-components"
89
- // Or "gallery-examples" -> "gallery-examples"
89
+ // Or "14-gallery.mdx" -> "." (root level file)
90
90
  const currentDir = routePath
91
- .replace(/\/[^/]+\.mdx$/i, "") // Remove /filename.mdx
91
+ .replace(/\/?[^/]+\.mdx$/i, "") // Remove [/]filename.mdx (slash optional for root files)
92
92
  .replace(/\/$/, "") // Remove trailing slash
93
93
  || ".";
94
94
 
@@ -6,13 +6,6 @@ import { useLightbox } from "./hooks/use-lightbox";
6
6
  import { LoadingImage } from "./components/loading-image";
7
7
  import { FigureHeader } from "./components/figure-header";
8
8
  import { FigureCaption } from "./components/figure-caption";
9
- import {
10
- Carousel,
11
- CarouselContent,
12
- CarouselItem,
13
- CarouselNext,
14
- CarouselPrevious,
15
- } from "@/components/ui/carousel"
16
9
 
17
10
  function getImageLabel(path: string): string {
18
11
  const filename = path.split('/').pop() || path;
@@ -95,8 +88,12 @@ export default function Gallery({
95
88
  );
96
89
  }
97
90
 
91
+ const isSingle = images.length === 1;
92
+ const isTwo = images.length === 2;
98
93
  const isCompact = images.length <= 3;
99
94
  const isSingleWithChildren = images.length === 1 && children;
95
+ // 2-3 images should break out of the content width
96
+ const shouldBreakOut = images.length >= 2;
100
97
 
101
98
  const imageElement = (index: number, img: LightboxImage, className?: string) => (
102
99
  <div
@@ -114,8 +111,12 @@ export default function Gallery({
114
111
 
115
112
  return (
116
113
  <>
117
- <figure className={`not-prose relative py-6 md:py-8 ${isCompact ? '' : '-mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))] px-[calc((var(--gallery-width)-var(--content-width))/2)]'}`}>
118
- {!isSingleWithChildren && <FigureHeader title={title} subtitle={subtitle} />}
114
+ <figure className={`not-prose relative py-6 md:py-8 ${shouldBreakOut ? (isTwo ? 'w-[75vw] ml-[calc(-37.5vw+50%)]' : isCompact ? 'w-[96vw] ml-[calc(-48vw+50%)]' : 'w-[var(--gallery-width)] ml-[calc(-45vw+50%)]') : ''}`}>
115
+ {!isSingleWithChildren && !isSingle && (
116
+ <div className="max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]">
117
+ <FigureHeader title={title} subtitle={subtitle} />
118
+ </div>
119
+ )}
119
120
 
120
121
  {isSingleWithChildren ? (
121
122
  <div className={`flex gap-6 ${childAlign === 'left' ? '' : 'flex-row-reverse'}`}>
@@ -129,40 +130,39 @@ export default function Gallery({
129
130
  <FigureCaption caption={caption} label={captionLabel} />
130
131
  </div>
131
132
  </div>
133
+ ) : isSingle ? (
134
+ <div className="max-w-[70%] mx-auto">
135
+ <FigureHeader title={title} subtitle={subtitle} />
136
+ {imageElement(0, images[0])}
137
+ <FigureCaption caption={caption} label={captionLabel} />
138
+ </div>
132
139
  ) : isCompact ? (
133
140
  <div className="flex gap-3">
134
141
  {images.map((img, index) => imageElement(index, img, 'flex-1'))}
135
142
  </div>
136
143
  ) : (
137
- <Carousel className="w-full">
138
- <CarouselContent className="-ml-2 md:-ml-3">
139
- {images.map((img, index) => (
140
- <CarouselItem
141
- key={index}
142
- className="pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group"
143
- onClick={() => lightbox.open(index)}
144
- >
145
- <div className="aspect-square overflow-hidden rounded-sm bg-muted/10">
146
- <LoadingImage
147
- src={img.src}
148
- alt={img.label}
149
- className="object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
150
- />
151
- </div>
152
- </CarouselItem>
153
- ))}
154
- </CarouselContent>
155
-
156
- {images.length > 3 && (
157
- <>
158
- <CarouselPrevious className="left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" />
159
- <CarouselNext className="right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" />
160
- </>
161
- )}
162
- </Carousel>
144
+ <div className="flex gap-3 overflow-x-auto pb-4">
145
+ {images.map((img, index) => (
146
+ <div
147
+ key={index}
148
+ className="flex-none w-[30%] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group"
149
+ onClick={() => lightbox.open(index)}
150
+ >
151
+ <LoadingImage
152
+ src={img.src}
153
+ alt={img.label}
154
+ className="object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
155
+ />
156
+ </div>
157
+ ))}
158
+ </div>
163
159
  )}
164
160
 
165
- {!isSingleWithChildren && <FigureCaption caption={caption} label={captionLabel} />}
161
+ {!isSingleWithChildren && !isSingle && (
162
+ <div className="max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]">
163
+ <FigureCaption caption={caption} label={captionLabel} />
164
+ </div>
165
+ )}
166
166
  </figure>
167
167
 
168
168
  {lightbox.isOpen && lightbox.selectedIndex !== null && (
@@ -20,8 +20,14 @@ export function Header({ slideControls }: HeaderProps = {}) {
20
20
  const { "*": path } = useParams()
21
21
 
22
22
  return (
23
- <header className="print:hidden fixed top-0 left-0 right-0 z-40">
24
- <div className="mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4">
23
+ <header className={cn(
24
+ "print:hidden",
25
+ slideControls && "fixed top-0 left-0 right-0 z-40"
26
+ )}>
27
+ <div className={cn(
28
+ "mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4",
29
+ !slideControls && "max-w-[var(--content-width)]"
30
+ )}>
25
31
  <nav className="flex items-center gap-1">
26
32
  <Link
27
33
  to="/"
@@ -117,7 +117,7 @@ export default function PostList({ directory, view = 'all' }: PostListProps) {
117
117
  linkPath = `/${post.path}`;
118
118
  }
119
119
 
120
- const isSlides = linkPath.endsWith('SLIDES.mdx');
120
+ const isSlides = linkPath.endsWith('SLIDES.mdx') || linkPath.endsWith('.slides.mdx');
121
121
 
122
122
  return (
123
123
  <Link
@@ -54,7 +54,7 @@ export function Home({ view }: HomeProps) {
54
54
  <Header />
55
55
  <main className="flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]">
56
56
  <title>{isRoot ? config.name : `${config.name} - ${path}`}</title>
57
- <main className="flex flex-col gap-8 mb-32 mt-32">
57
+ <main className="flex flex-col gap-8 mb-32 mt-12">
58
58
  {isRoot && (
59
59
  <div className="animate-fade-in">
60
60
  <h1 className="text-2xl md:text-3xl font-semibold tracking-tight text-foreground">
@@ -69,11 +69,6 @@ export function Home({ view }: HomeProps) {
69
69
  )}
70
70
 
71
71
  <div className="flex flex-col gap-2">
72
- {/* {isRoot && directory && (
73
- <div className="animate-fade-in">
74
- <ContentTabs value={activeView} counts={counts} />
75
- </div>
76
- )} */}
77
72
  {directory && (
78
73
  <div className="animate-fade-in">
79
74
  <PostList directory={directory} view={isRoot ? activeView : 'all'} />
@@ -43,7 +43,7 @@ export function Post() {
43
43
  <title>{frontmatter?.title}</title>
44
44
  <RunningBar />
45
45
  <Header />
46
- <main className="flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]">
46
+ <main className="flex-1 w-full overflow-x-clip">
47
47
  {isRunning && (
48
48
  <div className="sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide">
49
49
  <span className="inline-flex items-center gap-3">
@@ -56,7 +56,7 @@ export function Post() {
56
56
 
57
57
  {Content && (
58
58
  <FrontmatterProvider frontmatter={frontmatter}>
59
- <article className="my-24 prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--prose-width)] animate-fade-in">
59
+ <article className="my-12 mx-auto px-[var(--page-padding)] prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--content-width)] animate-fade-in">
60
60
  <Content components={mdxComponents} />
61
61
  </article>
62
62
  </FrontmatterProvider>
@@ -85,13 +85,13 @@ export function SlidesPage() {
85
85
  }
86
86
  }, [currentSlide, totalSlides]);
87
87
 
88
- // Keyboard navigation
88
+ // Keyboard navigation (up/down only - left/right reserved for horizontal scrolling)
89
89
  useEffect(() => {
90
90
  const handleKeyDown = (e: KeyboardEvent) => {
91
- if (e.key === "ArrowUp" || e.key === "ArrowLeft" || e.key === "k") {
91
+ if (e.key === "ArrowUp") {
92
92
  e.preventDefault();
93
93
  goToPrevious();
94
- } else if (e.key === "ArrowDown" || e.key === "ArrowRight" || e.key === "j") {
94
+ } else if (e.key === "ArrowDown") {
95
95
  e.preventDefault();
96
96
  goToNext();
97
97
  }
@@ -1,49 +0,0 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import * as React from "react";
3
- import { Slot } from "@radix-ui/react-slot";
4
- import { cva } from "class-variance-authority";
5
- import { cn } from "../../lib/utils.js";
6
- const buttonVariants = cva(
7
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
8
- {
9
- variants: {
10
- variant: {
11
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
13
- outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
14
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
- ghost: "hover:bg-accent hover:text-accent-foreground",
16
- link: "text-primary underline-offset-4 hover:underline"
17
- },
18
- size: {
19
- default: "h-10 px-4 py-2",
20
- sm: "h-9 rounded-md px-3",
21
- lg: "h-11 rounded-md px-8",
22
- icon: "h-10 w-10"
23
- }
24
- },
25
- defaultVariants: {
26
- variant: "default",
27
- size: "default"
28
- }
29
- }
30
- );
31
- const Button = React.forwardRef(
32
- ({ className, variant, size, asChild = false, ...props }, ref) => {
33
- const Comp = asChild ? Slot : "button";
34
- return /* @__PURE__ */ jsx(
35
- Comp,
36
- {
37
- className: cn(buttonVariants({ variant, size, className })),
38
- ref,
39
- ...props
40
- }
41
- );
42
- }
43
- );
44
- Button.displayName = "Button";
45
- export {
46
- Button,
47
- buttonVariants
48
- };
49
- //# sourceMappingURL=button.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"button.js","sources":["../../../../src/components/ui/button.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\"\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n )\n }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"],"names":[],"mappings":";;;;;AAMA,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,aACE;AAAA,QACF,SACE;AAAA,QACF,WACE;AAAA,QACF,OAAO;AAAA,QACP,MAAM;AAAA,MAAA;AAAA,MAER,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAQA,MAAM,SAAS,MAAM;AAAA,EACnB,CAAC,EAAE,WAAW,SAAS,MAAM,UAAU,OAAO,GAAG,MAAA,GAAS,QAAQ;AAChE,UAAM,OAAO,UAAU,OAAO;AAC9B,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,GAAG,eAAe,EAAE,SAAS,MAAM,UAAA,CAAW,CAAC;AAAA,QAC1D;AAAA,QACC,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AACF;AACA,OAAO,cAAc;"}
@@ -1,195 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import * as React from "react";
3
- import useEmblaCarousel from "embla-carousel-react";
4
- import { ArrowLeft, ArrowRight } from "lucide-react";
5
- import { cn } from "../../lib/utils.js";
6
- import { Button } from "./button.js";
7
- const CarouselContext = React.createContext(null);
8
- function useCarousel() {
9
- const context = React.useContext(CarouselContext);
10
- if (!context) {
11
- throw new Error("useCarousel must be used within a <Carousel />");
12
- }
13
- return context;
14
- }
15
- const Carousel = React.forwardRef(
16
- ({
17
- orientation = "horizontal",
18
- opts,
19
- setApi,
20
- plugins,
21
- className,
22
- children,
23
- ...props
24
- }, ref) => {
25
- const [carouselRef, api] = useEmblaCarousel(
26
- {
27
- ...opts,
28
- axis: orientation === "horizontal" ? "x" : "y"
29
- },
30
- plugins
31
- );
32
- const [canScrollPrev, setCanScrollPrev] = React.useState(false);
33
- const [canScrollNext, setCanScrollNext] = React.useState(false);
34
- const onSelect = React.useCallback((api2) => {
35
- if (!api2) {
36
- return;
37
- }
38
- setCanScrollPrev(api2.canScrollPrev());
39
- setCanScrollNext(api2.canScrollNext());
40
- }, []);
41
- const scrollPrev = React.useCallback(() => {
42
- api == null ? void 0 : api.scrollPrev();
43
- }, [api]);
44
- const scrollNext = React.useCallback(() => {
45
- api == null ? void 0 : api.scrollNext();
46
- }, [api]);
47
- const handleKeyDown = React.useCallback(
48
- (event) => {
49
- if (event.key === "ArrowLeft") {
50
- event.preventDefault();
51
- scrollPrev();
52
- } else if (event.key === "ArrowRight") {
53
- event.preventDefault();
54
- scrollNext();
55
- }
56
- },
57
- [scrollPrev, scrollNext]
58
- );
59
- React.useEffect(() => {
60
- if (!api || !setApi) {
61
- return;
62
- }
63
- setApi(api);
64
- }, [api, setApi]);
65
- React.useEffect(() => {
66
- if (!api) {
67
- return;
68
- }
69
- onSelect(api);
70
- api.on("reInit", onSelect);
71
- api.on("select", onSelect);
72
- return () => {
73
- api == null ? void 0 : api.off("select", onSelect);
74
- };
75
- }, [api, onSelect]);
76
- return /* @__PURE__ */ jsx(
77
- CarouselContext.Provider,
78
- {
79
- value: {
80
- carouselRef,
81
- api,
82
- opts,
83
- orientation: orientation || ((opts == null ? void 0 : opts.axis) === "y" ? "vertical" : "horizontal"),
84
- scrollPrev,
85
- scrollNext,
86
- canScrollPrev,
87
- canScrollNext
88
- },
89
- children: /* @__PURE__ */ jsx(
90
- "div",
91
- {
92
- ref,
93
- onKeyDownCapture: handleKeyDown,
94
- className: cn("relative", className),
95
- role: "region",
96
- "aria-roledescription": "carousel",
97
- ...props,
98
- children
99
- }
100
- )
101
- }
102
- );
103
- }
104
- );
105
- Carousel.displayName = "Carousel";
106
- const CarouselContent = React.forwardRef(({ className, ...props }, ref) => {
107
- const { carouselRef, orientation } = useCarousel();
108
- return /* @__PURE__ */ jsx("div", { ref: carouselRef, className: "overflow-hidden", children: /* @__PURE__ */ jsx(
109
- "div",
110
- {
111
- ref,
112
- className: cn(
113
- "flex",
114
- orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
115
- className
116
- ),
117
- ...props
118
- }
119
- ) });
120
- });
121
- CarouselContent.displayName = "CarouselContent";
122
- const CarouselItem = React.forwardRef(({ className, ...props }, ref) => {
123
- const { orientation } = useCarousel();
124
- return /* @__PURE__ */ jsx(
125
- "div",
126
- {
127
- ref,
128
- role: "group",
129
- "aria-roledescription": "slide",
130
- className: cn(
131
- "min-w-0 shrink-0 grow-0 basis-full",
132
- orientation === "horizontal" ? "pl-4" : "pt-4",
133
- className
134
- ),
135
- ...props
136
- }
137
- );
138
- });
139
- CarouselItem.displayName = "CarouselItem";
140
- const CarouselPrevious = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
141
- const { orientation, scrollPrev, canScrollPrev } = useCarousel();
142
- return /* @__PURE__ */ jsxs(
143
- Button,
144
- {
145
- ref,
146
- variant,
147
- size,
148
- className: cn(
149
- "absolute h-8 w-8 rounded-full",
150
- orientation === "horizontal" ? "-left-12 top-1/2 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
151
- className
152
- ),
153
- disabled: !canScrollPrev,
154
- onClick: scrollPrev,
155
- ...props,
156
- children: [
157
- /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }),
158
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Previous slide" })
159
- ]
160
- }
161
- );
162
- });
163
- CarouselPrevious.displayName = "CarouselPrevious";
164
- const CarouselNext = React.forwardRef(({ className, variant = "outline", size = "icon", ...props }, ref) => {
165
- const { orientation, scrollNext, canScrollNext } = useCarousel();
166
- return /* @__PURE__ */ jsxs(
167
- Button,
168
- {
169
- ref,
170
- variant,
171
- size,
172
- className: cn(
173
- "absolute h-8 w-8 rounded-full",
174
- orientation === "horizontal" ? "-right-12 top-1/2 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
175
- className
176
- ),
177
- disabled: !canScrollNext,
178
- onClick: scrollNext,
179
- ...props,
180
- children: [
181
- /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
182
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Next slide" })
183
- ]
184
- }
185
- );
186
- });
187
- CarouselNext.displayName = "CarouselNext";
188
- export {
189
- Carousel,
190
- CarouselContent,
191
- CarouselItem,
192
- CarouselNext,
193
- CarouselPrevious
194
- };
195
- //# sourceMappingURL=carousel.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"carousel.js","sources":["../../../../src/components/ui/carousel.tsx"],"sourcesContent":["import * as React from \"react\"\nimport useEmblaCarousel, {\n type UseEmblaCarouselType,\n} from \"embla-carousel-react\"\nimport { ArrowLeft, ArrowRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ntype CarouselProps = {\n opts?: CarouselOptions\n plugins?: CarouselPlugin\n orientation?: \"horizontal\" | \"vertical\"\n setApi?: (api: CarouselApi) => void\n}\n\ntype CarouselContextProps = {\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n api: ReturnType<typeof useEmblaCarousel>[1]\n scrollPrev: () => void\n scrollNext: () => void\n canScrollPrev: boolean\n canScrollNext: boolean\n} & CarouselProps\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n const context = React.useContext(CarouselContext)\n\n if (!context) {\n throw new Error(\"useCarousel must be used within a <Carousel />\")\n }\n\n return context\n}\n\nconst Carousel = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n (\n {\n orientation = \"horizontal\",\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n },\n ref\n ) => {\n const [carouselRef, api] = useEmblaCarousel(\n {\n ...opts,\n axis: orientation === \"horizontal\" ? \"x\" : \"y\",\n },\n plugins\n )\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n const [canScrollNext, setCanScrollNext] = React.useState(false)\n\n const onSelect = React.useCallback((api: CarouselApi) => {\n if (!api) {\n return\n }\n\n setCanScrollPrev(api.canScrollPrev())\n setCanScrollNext(api.canScrollNext())\n }, [])\n\n const scrollPrev = React.useCallback(() => {\n api?.scrollPrev()\n }, [api])\n\n const scrollNext = React.useCallback(() => {\n api?.scrollNext()\n }, [api])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"ArrowLeft\") {\n event.preventDefault()\n scrollPrev()\n } else if (event.key === \"ArrowRight\") {\n event.preventDefault()\n scrollNext()\n }\n },\n [scrollPrev, scrollNext]\n )\n\n React.useEffect(() => {\n if (!api || !setApi) {\n return\n }\n\n setApi(api)\n }, [api, setApi])\n\n React.useEffect(() => {\n if (!api) {\n return\n }\n\n onSelect(api)\n api.on(\"reInit\", onSelect)\n api.on(\"select\", onSelect)\n\n return () => {\n api?.off(\"select\", onSelect)\n }\n }, [api, onSelect])\n\n return (\n <CarouselContext.Provider\n value={{\n carouselRef,\n api: api,\n opts,\n orientation:\n orientation || (opts?.axis === \"y\" ? \"vertical\" : \"horizontal\"),\n scrollPrev,\n scrollNext,\n canScrollPrev,\n canScrollNext,\n }}\n >\n <div\n ref={ref}\n onKeyDownCapture={handleKeyDown}\n className={cn(\"relative\", className)}\n role=\"region\"\n aria-roledescription=\"carousel\"\n {...props}\n >\n {children}\n </div>\n </CarouselContext.Provider>\n )\n }\n)\nCarousel.displayName = \"Carousel\"\n\nconst CarouselContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { carouselRef, orientation } = useCarousel()\n\n return (\n <div ref={carouselRef} className=\"overflow-hidden\">\n <div\n ref={ref}\n className={cn(\n \"flex\",\n orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\",\n className\n )}\n {...props}\n />\n </div>\n )\n})\nCarouselContent.displayName = \"CarouselContent\"\n\nconst CarouselItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { orientation } = useCarousel()\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-roledescription=\"slide\"\n className={cn(\n \"min-w-0 shrink-0 grow-0 basis-full\",\n orientation === \"horizontal\" ? \"pl-4\" : \"pt-4\",\n className\n )}\n {...props}\n />\n )\n})\nCarouselItem.displayName = \"CarouselItem\"\n\nconst CarouselPrevious = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<typeof Button>\n>(({ className, variant = \"outline\", size = \"icon\", ...props }, ref) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n\n return (\n <Button\n ref={ref}\n variant={variant}\n size={size}\n className={cn(\n \"absolute h-8 w-8 rounded-full\",\n orientation === \"horizontal\"\n ? \"-left-12 top-1/2 -translate-y-1/2\"\n : \"-top-12 left-1/2 -translate-x-1/2 rotate-90\",\n className\n )}\n disabled={!canScrollPrev}\n onClick={scrollPrev}\n {...props}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n <span className=\"sr-only\">Previous slide</span>\n </Button>\n )\n})\nCarouselPrevious.displayName = \"CarouselPrevious\"\n\nconst CarouselNext = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<typeof Button>\n>(({ className, variant = \"outline\", size = \"icon\", ...props }, ref) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel()\n\n return (\n <Button\n ref={ref}\n variant={variant}\n size={size}\n className={cn(\n \"absolute h-8 w-8 rounded-full\",\n orientation === \"horizontal\"\n ? \"-right-12 top-1/2 -translate-y-1/2\"\n : \"-bottom-12 left-1/2 -translate-x-1/2 rotate-90\",\n className\n )}\n disabled={!canScrollNext}\n onClick={scrollNext}\n {...props}\n >\n <ArrowRight className=\"h-4 w-4\" />\n <span className=\"sr-only\">Next slide</span>\n </Button>\n )\n})\nCarouselNext.displayName = \"CarouselNext\"\n\nexport {\n type CarouselApi,\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n}\n"],"names":["api"],"mappings":";;;;;;AA8BA,MAAM,kBAAkB,MAAM,cAA2C,IAAI;AAE7E,SAAS,cAAc;AACrB,QAAM,UAAU,MAAM,WAAW,eAAe;AAEhD,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AAEA,SAAO;AACT;AAEA,MAAM,WAAW,MAAM;AAAA,EAIrB,CACE;AAAA,IACE,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,CAAC,aAAa,GAAG,IAAI;AAAA,MACzB;AAAA,QACE,GAAG;AAAA,QACH,MAAM,gBAAgB,eAAe,MAAM;AAAA,MAAA;AAAA,MAE7C;AAAA,IAAA;AAEF,UAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,KAAK;AAC9D,UAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,KAAK;AAE9D,UAAM,WAAW,MAAM,YAAY,CAACA,SAAqB;AACvD,UAAI,CAACA,MAAK;AACR;AAAA,MACF;AAEA,uBAAiBA,KAAI,eAAe;AACpC,uBAAiBA,KAAI,eAAe;AAAA,IACtC,GAAG,CAAA,CAAE;AAEL,UAAM,aAAa,MAAM,YAAY,MAAM;AACzC,iCAAK;AAAA,IACP,GAAG,CAAC,GAAG,CAAC;AAER,UAAM,aAAa,MAAM,YAAY,MAAM;AACzC,iCAAK;AAAA,IACP,GAAG,CAAC,GAAG,CAAC;AAER,UAAM,gBAAgB,MAAM;AAAA,MAC1B,CAAC,UAA+C;AAC9C,YAAI,MAAM,QAAQ,aAAa;AAC7B,gBAAM,eAAA;AACN,qBAAA;AAAA,QACF,WAAW,MAAM,QAAQ,cAAc;AACrC,gBAAM,eAAA;AACN,qBAAA;AAAA,QACF;AAAA,MACF;AAAA,MACA,CAAC,YAAY,UAAU;AAAA,IAAA;AAGzB,UAAM,UAAU,MAAM;AACpB,UAAI,CAAC,OAAO,CAAC,QAAQ;AACnB;AAAA,MACF;AAEA,aAAO,GAAG;AAAA,IACZ,GAAG,CAAC,KAAK,MAAM,CAAC;AAEhB,UAAM,UAAU,MAAM;AACpB,UAAI,CAAC,KAAK;AACR;AAAA,MACF;AAEA,eAAS,GAAG;AACZ,UAAI,GAAG,UAAU,QAAQ;AACzB,UAAI,GAAG,UAAU,QAAQ;AAEzB,aAAO,MAAM;AACX,mCAAK,IAAI,UAAU;AAAA,MACrB;AAAA,IACF,GAAG,CAAC,KAAK,QAAQ,CAAC;AAElB,WACE;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,OAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA,aACE,iBAAgB,6BAAM,UAAS,MAAM,aAAa;AAAA,UACpD;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC;AAAA,YACA,kBAAkB;AAAA,YAClB,WAAW,GAAG,YAAY,SAAS;AAAA,YACnC,MAAK;AAAA,YACL,wBAAqB;AAAA,YACpB,GAAG;AAAA,YAEH;AAAA,UAAA;AAAA,QAAA;AAAA,MACH;AAAA,IAAA;AAAA,EAGN;AACF;AACA,SAAS,cAAc;AAEvB,MAAM,kBAAkB,MAAM,WAG5B,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAAQ;AAClC,QAAM,EAAE,aAAa,YAAA,IAAgB,YAAA;AAErC,SACE,oBAAC,OAAA,EAAI,KAAK,aAAa,WAAU,mBAC/B,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,eAAe,UAAU;AAAA,QACzC;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA,GAER;AAEJ,CAAC;AACD,gBAAgB,cAAc;AAE9B,MAAM,eAAe,MAAM,WAGzB,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAAQ;AAClC,QAAM,EAAE,YAAA,IAAgB,YAAA;AAExB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,wBAAqB;AAAA,MACrB,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,eAAe,SAAS;AAAA,QACxC;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV,CAAC;AACD,aAAa,cAAc;AAE3B,MAAM,mBAAmB,MAAM,WAG7B,CAAC,EAAE,WAAW,UAAU,WAAW,OAAO,QAAQ,GAAG,MAAA,GAAS,QAAQ;AACtE,QAAM,EAAE,aAAa,YAAY,cAAA,IAAkB,YAAA;AAEnD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,eACZ,sCACA;AAAA,QACJ;AAAA,MAAA;AAAA,MAEF,UAAU,CAAC;AAAA,MACX,SAAS;AAAA,MACR,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAAC,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,QAC/B,oBAAC,QAAA,EAAK,WAAU,WAAU,UAAA,iBAAA,CAAc;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG9C,CAAC;AACD,iBAAiB,cAAc;AAE/B,MAAM,eAAe,MAAM,WAGzB,CAAC,EAAE,WAAW,UAAU,WAAW,OAAO,QAAQ,GAAG,MAAA,GAAS,QAAQ;AACtE,QAAM,EAAE,aAAa,YAAY,cAAA,IAAkB,YAAA;AAEnD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB,eACZ,uCACA;AAAA,QACJ;AAAA,MAAA;AAAA,MAEF,UAAU,CAAC;AAAA,MACX,SAAS;AAAA,MACR,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAAC,YAAA,EAAW,WAAU,UAAA,CAAU;AAAA,QAChC,oBAAC,QAAA,EAAK,WAAU,WAAU,UAAA,aAAA,CAAU;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG1C,CAAC;AACD,aAAa,cAAc;"}