veslx 0.1.21 → 0.1.23

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.
Files changed (73) hide show
  1. package/bin/lib/serve.ts +16 -2
  2. package/bin/lib/start.ts +12 -10
  3. package/bin/veslx.ts +1 -1
  4. package/dist/client/components/front-matter.js +2 -6
  5. package/dist/client/components/front-matter.js.map +1 -1
  6. package/dist/client/components/gallery/components/figure-caption.js +1 -1
  7. package/dist/client/components/gallery/components/figure-caption.js.map +1 -1
  8. package/dist/client/components/gallery/components/figure-header.js +1 -1
  9. package/dist/client/components/gallery/components/figure-header.js.map +1 -1
  10. package/dist/client/components/gallery/components/lightbox.js +1 -1
  11. package/dist/client/components/gallery/components/lightbox.js.map +1 -1
  12. package/dist/client/components/gallery/index.js +38 -8
  13. package/dist/client/components/gallery/index.js.map +1 -1
  14. package/dist/client/components/header.js +45 -21
  15. package/dist/client/components/header.js.map +1 -1
  16. package/dist/client/components/mdx-components.js +8 -0
  17. package/dist/client/components/mdx-components.js.map +1 -1
  18. package/dist/client/components/post-list.js +13 -11
  19. package/dist/client/components/post-list.js.map +1 -1
  20. package/dist/client/components/slides/figure-slide.js +14 -0
  21. package/dist/client/components/slides/figure-slide.js.map +1 -0
  22. package/dist/client/components/slides/hero-slide.js +21 -0
  23. package/dist/client/components/slides/hero-slide.js.map +1 -0
  24. package/dist/client/components/slides/slide-outline.js +28 -0
  25. package/dist/client/components/slides/slide-outline.js.map +1 -0
  26. package/dist/client/components/slides/text-slide.js +18 -0
  27. package/dist/client/components/slides/text-slide.js.map +1 -0
  28. package/dist/client/components/slides-renderer.js.map +1 -1
  29. package/dist/client/hooks/use-mdx-content.js +19 -6
  30. package/dist/client/hooks/use-mdx-content.js.map +1 -1
  31. package/dist/client/lib/content-classification.js +11 -2
  32. package/dist/client/lib/content-classification.js.map +1 -1
  33. package/dist/client/lib/frontmatter-context.js +17 -0
  34. package/dist/client/lib/frontmatter-context.js.map +1 -0
  35. package/dist/client/pages/content-router.js +4 -1
  36. package/dist/client/pages/content-router.js.map +1 -1
  37. package/dist/client/pages/home.js +2 -6
  38. package/dist/client/pages/home.js.map +1 -1
  39. package/dist/client/pages/post.js +2 -9
  40. package/dist/client/pages/post.js.map +1 -1
  41. package/dist/client/pages/slides.js +9 -12
  42. package/dist/client/pages/slides.js.map +1 -1
  43. package/dist/client/plugin/src/client.js +20 -2
  44. package/dist/client/plugin/src/client.js.map +1 -1
  45. package/index.html +13 -0
  46. package/package.json +1 -1
  47. package/plugin/src/client.tsx +28 -2
  48. package/plugin/src/plugin.ts +49 -4
  49. package/src/components/content-tabs.tsx +4 -4
  50. package/src/components/front-matter.tsx +3 -8
  51. package/src/components/gallery/components/figure-caption.tsx +1 -1
  52. package/src/components/gallery/components/figure-header.tsx +1 -1
  53. package/src/components/gallery/components/lightbox.tsx +1 -1
  54. package/src/components/gallery/index.tsx +68 -29
  55. package/src/components/header.tsx +44 -25
  56. package/src/components/mdx-components.tsx +12 -0
  57. package/src/components/post-list.tsx +14 -10
  58. package/src/components/slides/figure-slide.tsx +16 -0
  59. package/src/components/slides/hero-slide.tsx +34 -0
  60. package/src/components/slides/slide-outline.tsx +38 -0
  61. package/src/components/slides/text-slide.tsx +35 -0
  62. package/src/components/slides-renderer.tsx +1 -1
  63. package/src/hooks/use-mdx-content.ts +27 -6
  64. package/src/index.css +1 -2
  65. package/src/lib/content-classification.ts +13 -2
  66. package/src/lib/frontmatter-context.tsx +29 -0
  67. package/src/pages/content-router.tsx +7 -1
  68. package/src/pages/home.tsx +3 -3
  69. package/src/pages/post.tsx +6 -24
  70. package/src/pages/slides.tsx +14 -16
  71. package/vite.config.ts +4 -3
  72. package/dist/client/components/content-tabs.js +0 -50
  73. package/dist/client/components/content-tabs.js.map +0 -1
package/bin/lib/serve.ts CHANGED
@@ -57,7 +57,21 @@ export default async function serve(dir?: string) {
57
57
  // Get defaults first, then merge with config file if it exists
58
58
  // Look for config in content directory first, then fall back to cwd
59
59
  const defaults = await getDefaultConfig(contentDir);
60
- const fileConfig = await importConfig(contentDir) || await importConfig(cwd);
60
+
61
+ // Track which config file was found for hot reload
62
+ let configPath: string | undefined;
63
+ const contentConfigPath = path.join(contentDir, 'veslx.yaml');
64
+ const cwdConfigPath = path.join(cwd, 'veslx.yaml');
65
+
66
+ let fileConfig = await importConfig(contentDir);
67
+ if (fileConfig) {
68
+ configPath = contentConfigPath;
69
+ } else {
70
+ fileConfig = await importConfig(cwd);
71
+ if (fileConfig) {
72
+ configPath = cwdConfigPath;
73
+ }
74
+ }
61
75
 
62
76
  // CLI argument takes precedence over config file
63
77
  const config = {
@@ -82,7 +96,7 @@ export default async function serve(dir?: string) {
82
96
  // Cache in user's project so it persists across bunx runs
83
97
  cacheDir: path.join(cwd, 'node_modules/.vite'),
84
98
  plugins: [
85
- veslxPlugin(finalContentDir, config)
99
+ veslxPlugin(finalContentDir, config, { configPath })
86
100
  ],
87
101
  })
88
102
 
package/bin/lib/start.ts CHANGED
@@ -1,17 +1,19 @@
1
- import importConfig from "./import-config"
2
1
  import pm2 from "pm2";
2
+ import path from "path";
3
3
  import { log } from './log'
4
4
 
5
- export default async function start() {
6
- const config = await importConfig(process.cwd());
5
+ export default async function start(dir?: string) {
6
+ const cwd = process.cwd();
7
7
 
8
- if (!config) {
9
- log.error("veslx.yaml not found");
10
- return
11
- }
8
+ // Resolve content directory the same way as serve
9
+ const contentDir = dir
10
+ ? (path.isAbsolute(dir) ? dir : path.resolve(cwd, dir))
11
+ : cwd;
12
12
 
13
- const cwd = process.cwd();
14
- const name = `veslx-${cwd.replace(/\//g, '-').replace(/^-/, '')}`.toLowerCase();
13
+ const name = `veslx-${contentDir.replace(/\//g, '-').replace(/^-/, '')}`.toLowerCase();
14
+
15
+ // Build args for veslx serve
16
+ const args = dir ? ['veslx', 'serve', dir] : ['veslx', 'serve'];
15
17
 
16
18
  pm2.connect((err) => {
17
19
  if (err) {
@@ -22,7 +24,7 @@ export default async function start() {
22
24
  pm2.start({
23
25
  name: name,
24
26
  script: 'bunx',
25
- args: ['veslx', 'serve'],
27
+ args: args,
26
28
  cwd: cwd,
27
29
  autorestart: true,
28
30
  watch: false,
package/bin/veslx.ts CHANGED
@@ -22,7 +22,7 @@ cli
22
22
  .action(serve);
23
23
 
24
24
  cli
25
- .command("start", "Start the veslx server as a deamon")
25
+ .command("start [dir]", "Start the veslx server as a daemon")
26
26
  .action(start);
27
27
 
28
28
  cli
@@ -1,12 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useMDXContent, useMDXSlides } from "../hooks/use-mdx-content.js";
2
+ import { useFrontmatter } from "../lib/frontmatter-context.js";
3
3
  import { formatDate } from "../lib/format-date.js";
4
- import { useParams } from "react-router-dom";
5
4
  function FrontMatter() {
6
- const { "path": path = "." } = useParams();
7
- const { frontmatter: readmeFm } = useMDXContent(path);
8
- const { frontmatter: slidesFm } = useMDXSlides(path);
9
- let frontmatter = readmeFm || slidesFm;
5
+ const frontmatter = useFrontmatter();
10
6
  return /* @__PURE__ */ jsx("div", { children: (frontmatter == null ? void 0 : frontmatter.title) && /* @__PURE__ */ jsxs("header", { className: "not-prose flex flex-col gap-2 mb-8 pt-4", children: [
11
7
  /* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3", children: frontmatter == null ? void 0 : frontmatter.title }),
12
8
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: (frontmatter == null ? void 0 : frontmatter.date) && /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(frontmatter.date)) }) }),
@@ -1 +1 @@
1
- {"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { useMDXContent, useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { formatDate } from \"@/lib/format-date\"\nimport { useParams } from \"react-router-dom\"\n\nexport function FrontMatter(){\n const { \"path\": path = \".\" } = useParams();\n const { frontmatter: readmeFm } = useMDXContent(path);\n const { frontmatter: slidesFm } = useMDXSlides(path);\n\n let frontmatter = readmeFm || slidesFm;\n\n return (\n <div>\n {frontmatter?.title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {frontmatter?.title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {frontmatter?.date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n )}\n </div>\n\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter?.description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}"],"names":[],"mappings":";;;;AAIO,SAAS,cAAa;AAC3B,QAAM,EAAE,QAAQ,OAAO,IAAA,IAAQ,UAAA;AAC/B,QAAM,EAAE,aAAa,aAAa,cAAc,IAAI;AACpD,QAAM,EAAE,aAAa,aAAa,aAAa,IAAI;AAEnD,MAAI,cAAc,YAAY;AAE9B,6BACG,OAAA,EACE,WAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,2CAAa,OAChB;AAAA,wBAGC,OAAA,EAAI,WAAU,2DACZ,WAAA,2CAAa,SACZ,oBAAC,QAAA,EAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,YAAY,IAAc,CAAC,GAClD,GAEJ;AAAA,KAEC,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,qDAAa,YAAA,CAChB;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
1
+ {"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { useFrontmatter } from \"@/lib/frontmatter-context\";\nimport { formatDate } from \"@/lib/format-date\"\n\nexport function FrontMatter(){\n const frontmatter = useFrontmatter();\n\n return (\n <div>\n {frontmatter?.title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {frontmatter?.title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {frontmatter?.date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n )}\n </div>\n\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter?.description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}\n"],"names":[],"mappings":";;;AAGO,SAAS,cAAa;AAC3B,QAAM,cAAc,eAAA;AAEpB,6BACG,OAAA,EACE,WAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,2CAAa,OAChB;AAAA,wBAGC,OAAA,EAAI,WAAU,2DACZ,WAAA,2CAAa,SACZ,oBAAC,QAAA,EAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,YAAY,IAAc,CAAC,GAClD,GAEJ;AAAA,KAEC,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,qDAAa,YAAA,CAChB;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
@@ -2,7 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { renderMathInText } from "../lib/render-math-in-text.js";
3
3
  function FigureCaption({ caption, label }) {
4
4
  if (!caption && !label) return null;
5
- return /* @__PURE__ */ jsx("figcaption", { className: "px-[calc((var(--gallery-width)-var(--content-width))/2)] mt-4", children: /* @__PURE__ */ jsxs("p", { className: "text-[13px] leading-[1.6] text-muted-foreground", children: [
5
+ return /* @__PURE__ */ jsx("figcaption", { className: "mt-4", children: /* @__PURE__ */ jsxs("p", { className: "text-[13px] leading-[1.6] text-muted-foreground", children: [
6
6
  label && /* @__PURE__ */ jsxs("span", { className: "font-semibold text-foreground tracking-tight", children: [
7
7
  label,
8
8
  caption && /* @__PURE__ */ jsx("span", { className: "font-normal mx-1.5", children: "·" })
@@ -1 +1 @@
1
- {"version":3,"file":"figure-caption.js","sources":["../../../../../src/components/gallery/components/figure-caption.tsx"],"sourcesContent":["import { renderMathInText } from \"../lib/render-math-in-text\";\n\nexport function FigureCaption({ caption, label }: { caption?: string; label?: string }) {\n if (!caption && !label) return null;\n\n return (\n <figcaption className=\"px-[calc((var(--gallery-width)-var(--content-width))/2)] mt-4\">\n <p className=\"text-[13px] leading-[1.6] text-muted-foreground\">\n {label && (\n <span className=\"font-semibold text-foreground tracking-tight\">\n {label}\n {caption && <span className=\"font-normal mx-1.5\">·</span>}\n </span>\n )}\n {caption && (\n <span className=\"text-muted-foreground/90\">\n {renderMathInText(caption)}\n </span>\n )}\n </p>\n </figcaption>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,cAAc,EAAE,SAAS,SAA+C;AACtF,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAE/B,6BACG,cAAA,EAAW,WAAU,iEACpB,UAAA,qBAAC,KAAA,EAAE,WAAU,mDACV,UAAA;AAAA,IAAA,SACC,qBAAC,QAAA,EAAK,WAAU,gDACb,UAAA;AAAA,MAAA;AAAA,MACA,WAAW,oBAAC,QAAA,EAAK,WAAU,sBAAqB,UAAA,IAAA,CAAC;AAAA,IAAA,GACpD;AAAA,IAED,WACC,oBAAC,QAAA,EAAK,WAAU,4BACb,UAAA,iBAAiB,OAAO,EAAA,CAC3B;AAAA,EAAA,EAAA,CAEJ,EAAA,CACF;AAEJ;"}
1
+ {"version":3,"file":"figure-caption.js","sources":["../../../../../src/components/gallery/components/figure-caption.tsx"],"sourcesContent":["import { renderMathInText } from \"../lib/render-math-in-text\";\n\nexport function FigureCaption({ caption, label }: { caption?: string; label?: string }) {\n if (!caption && !label) return null;\n\n return (\n <figcaption className=\"mt-4\">\n <p className=\"text-[13px] leading-[1.6] text-muted-foreground\">\n {label && (\n <span className=\"font-semibold text-foreground tracking-tight\">\n {label}\n {caption && <span className=\"font-normal mx-1.5\">·</span>}\n </span>\n )}\n {caption && (\n <span className=\"text-muted-foreground/90\">\n {renderMathInText(caption)}\n </span>\n )}\n </p>\n </figcaption>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,cAAc,EAAE,SAAS,SAA+C;AACtF,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAE/B,6BACG,cAAA,EAAW,WAAU,QACpB,UAAA,qBAAC,KAAA,EAAE,WAAU,mDACV,UAAA;AAAA,IAAA,SACC,qBAAC,QAAA,EAAK,WAAU,gDACb,UAAA;AAAA,MAAA;AAAA,MACA,WAAW,oBAAC,QAAA,EAAK,WAAU,sBAAqB,UAAA,IAAA,CAAC;AAAA,IAAA,GACpD;AAAA,IAED,WACC,oBAAC,QAAA,EAAK,WAAU,4BACb,UAAA,iBAAiB,OAAO,EAAA,CAC3B;AAAA,EAAA,EAAA,CAEJ,EAAA,CACF;AAEJ;"}
@@ -2,7 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { renderMathInText } from "../lib/render-math-in-text.js";
3
3
  function FigureHeader({ title, subtitle }) {
4
4
  if (!title && !subtitle) return null;
5
- return /* @__PURE__ */ jsxs("div", { className: "px-[calc((var(--gallery-width)-var(--content-width))/2)] mb-4", children: [
5
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
6
6
  title && /* @__PURE__ */ jsx("h3", { className: "text-[15px] font-medium tracking-[-0.01em] text-foreground", children: renderMathInText(title) }),
7
7
  subtitle && /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground/80 leading-relaxed mt-1", children: renderMathInText(subtitle) })
8
8
  ] });
@@ -1 +1 @@
1
- {"version":3,"file":"figure-header.js","sources":["../../../../../src/components/gallery/components/figure-header.tsx"],"sourcesContent":["import { renderMathInText } from \"../lib/render-math-in-text\";\n\nexport function FigureHeader({ title, subtitle }: { title?: string; subtitle?: string }) {\n if (!title && !subtitle) return null;\n\n return (\n <div className=\"px-[calc((var(--gallery-width)-var(--content-width))/2)] mb-4\">\n {title && (\n <h3 className=\"text-[15px] font-medium tracking-[-0.01em] text-foreground\">\n {renderMathInText(title)}\n </h3>\n )}\n {subtitle && (\n <p className=\"text-[13px] text-muted-foreground/80 leading-relaxed mt-1\">\n {renderMathInText(subtitle)}\n </p>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,aAAa,EAAE,OAAO,YAAmD;AACvF,MAAI,CAAC,SAAS,CAAC,SAAU,QAAO;AAEhC,SACE,qBAAC,OAAA,EAAI,WAAU,iEACZ,UAAA;AAAA,IAAA,6BACE,MAAA,EAAG,WAAU,8DACX,UAAA,iBAAiB,KAAK,GACzB;AAAA,IAED,YACC,oBAAC,KAAA,EAAE,WAAU,6DACV,UAAA,iBAAiB,QAAQ,EAAA,CAC5B;AAAA,EAAA,GAEJ;AAEJ;"}
1
+ {"version":3,"file":"figure-header.js","sources":["../../../../../src/components/gallery/components/figure-header.tsx"],"sourcesContent":["import { renderMathInText } from \"../lib/render-math-in-text\";\n\nexport function FigureHeader({ title, subtitle }: { title?: string; subtitle?: string }) {\n if (!title && !subtitle) return null;\n\n return (\n <div className=\"mb-4\">\n {title && (\n <h3 className=\"text-[15px] font-medium tracking-[-0.01em] text-foreground\">\n {renderMathInText(title)}\n </h3>\n )}\n {subtitle && (\n <p className=\"text-[13px] text-muted-foreground/80 leading-relaxed mt-1\">\n {renderMathInText(subtitle)}\n </p>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,aAAa,EAAE,OAAO,YAAmD;AACvF,MAAI,CAAC,SAAS,CAAC,SAAU,QAAO;AAEhC,SACE,qBAAC,OAAA,EAAI,WAAU,QACZ,UAAA;AAAA,IAAA,6BACE,MAAA,EAAG,WAAU,8DACX,UAAA,iBAAiB,KAAK,GACzB;AAAA,IAED,YACC,oBAAC,KAAA,EAAE,WAAU,6DACV,UAAA,iBAAiB,QAAQ,EAAA,CAC5B;AAAA,EAAA,GAEJ;AAEJ;"}
@@ -15,7 +15,7 @@ function Lightbox({
15
15
  /* @__PURE__ */ jsxs(
16
16
  "div",
17
17
  {
18
- className: "fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-fade-in-slow",
18
+ className: "fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-[fade-in_150ms_ease-out]",
19
19
  onClick: onClose,
20
20
  ...{ [FULLSCREEN_DATA_ATTR]: "true" },
21
21
  style: { top: 0, left: 0, right: 0, bottom: 0 },
@@ -1 +1 @@
1
- {"version":3,"file":"lightbox.js","sources":["../../../../../src/components/gallery/components/lightbox.tsx"],"sourcesContent":["import { createPortal } from \"react-dom\";\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\n\nexport interface LightboxImage {\n src: string;\n label: string;\n}\n\nexport interface LightboxProps {\n images: LightboxImage[];\n selectedIndex: number;\n onClose: () => void;\n onPrevious: () => void;\n onNext: () => void;\n showNavigation?: boolean;\n}\n\n/**\n * Fullscreen lightbox component for viewing images\n */\nexport function Lightbox({\n images,\n selectedIndex,\n onClose,\n onPrevious,\n onNext,\n showNavigation = true,\n}: LightboxProps) {\n const current = images[selectedIndex];\n\n return createPortal(\n <div\n className=\"fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-fade-in-slow\"\n onClick={onClose}\n {...{ [FULLSCREEN_DATA_ATTR]: \"true\" }}\n style={{ top: 0, left: 0, right: 0, bottom: 0 }}\n >\n {/* Top bar */}\n <div\n className=\"fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-6 py-4\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"font-mono text-[11px] text-muted-foreground/60 tabular-nums tracking-wider uppercase\">\n {String(selectedIndex + 1).padStart(2, '0')}\n <span className=\"mx-1.5 text-muted-foreground/30\">/</span>\n {String(images.length).padStart(2, '0')}\n </div>\n <button\n onClick={onClose}\n className=\"p-2 -m-2 text-muted-foreground/50 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4\" strokeWidth={1.5} />\n </button>\n </div>\n\n {/* Navigation: Previous */}\n {showNavigation && selectedIndex > 0 && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onPrevious();\n }}\n className=\"fixed left-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Previous image\"\n >\n <ChevronLeft className=\"h-6 w-6\" strokeWidth={1.5} />\n </button>\n )}\n\n {/* Navigation: Next */}\n {showNavigation && selectedIndex < images.length - 1 && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onNext();\n }}\n className=\"fixed right-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Next image\"\n >\n <ChevronRight className=\"h-6 w-6\" strokeWidth={1.5} />\n </button>\n )}\n\n {/* Main image */}\n <div className=\"fixed inset-0 flex items-center justify-center p-16\">\n <img\n src={current.src}\n alt={current.label}\n className=\"max-w-full max-h-full object-contain rounded-sm shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n\n {/* Caption */}\n <div\n className=\"fixed bottom-0 left-0 right-0 z-10 px-6 py-5 text-center\"\n onClick={(e) => e.stopPropagation()}\n >\n <span className=\"font-mono text-[11px] text-muted-foreground/50 tracking-wide\">\n {current.label}\n </span>\n </div>\n </div>,\n document.body\n );\n}\n"],"names":[],"mappings":";;;;AAqBO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AACnB,GAAkB;AAChB,QAAM,UAAU,OAAO,aAAa;AAEpC,SAAO;AAAA,IACL;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,SAAS;AAAA,QACR,GAAG,EAAE,CAAC,oBAAoB,GAAG,OAAA;AAAA,QAC9B,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,EAAA;AAAA,QAG5C,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,cAElB,UAAA;AAAA,gBAAA,qBAAC,OAAA,EAAI,WAAU,wFACZ,UAAA;AAAA,kBAAA,OAAO,gBAAgB,CAAC,EAAE,SAAS,GAAG,GAAG;AAAA,kBAC1C,oBAAC,QAAA,EAAK,WAAU,mCAAkC,UAAA,KAAC;AAAA,kBAClD,OAAO,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG;AAAA,gBAAA,GACxC;AAAA,gBACA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,SAAS;AAAA,oBACT,WAAU;AAAA,oBACV,cAAW;AAAA,oBAEX,UAAA,oBAAC,GAAA,EAAE,WAAU,WAAU,aAAa,IAAA,CAAK;AAAA,kBAAA;AAAA,gBAAA;AAAA,cAC3C;AAAA,YAAA;AAAA,UAAA;AAAA,UAID,kBAAkB,gBAAgB,KACjC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAA;AACF,2BAAA;AAAA,cACF;AAAA,cACA,WAAU;AAAA,cACV,cAAW;AAAA,cAEX,UAAA,oBAAC,aAAA,EAAY,WAAU,WAAU,aAAa,IAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,UAKtD,kBAAkB,gBAAgB,OAAO,SAAS,KACjD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAA;AACF,uBAAA;AAAA,cACF;AAAA,cACA,WAAU;AAAA,cACV,cAAW;AAAA,cAEX,UAAA,oBAAC,cAAA,EAAa,WAAU,WAAU,aAAa,IAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,UAKxD,oBAAC,OAAA,EAAI,WAAU,uDACb,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAK,QAAQ;AAAA,cACb,KAAK,QAAQ;AAAA,cACb,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,YAAgB;AAAA,UAAA,GAEtC;AAAA,UAGA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,cAElB,UAAA,oBAAC,QAAA,EAAK,WAAU,gEACb,kBAAQ,MAAA,CACX;AAAA,YAAA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,SAAS;AAAA,EAAA;AAEb;"}
1
+ {"version":3,"file":"lightbox.js","sources":["../../../../../src/components/gallery/components/lightbox.tsx"],"sourcesContent":["import { createPortal } from \"react-dom\";\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\n\nexport interface LightboxImage {\n src: string;\n label: string;\n}\n\nexport interface LightboxProps {\n images: LightboxImage[];\n selectedIndex: number;\n onClose: () => void;\n onPrevious: () => void;\n onNext: () => void;\n showNavigation?: boolean;\n}\n\n/**\n * Fullscreen lightbox component for viewing images\n */\nexport function Lightbox({\n images,\n selectedIndex,\n onClose,\n onPrevious,\n onNext,\n showNavigation = true,\n}: LightboxProps) {\n const current = images[selectedIndex];\n\n return createPortal(\n <div\n className=\"fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-[fade-in_150ms_ease-out]\"\n onClick={onClose}\n {...{ [FULLSCREEN_DATA_ATTR]: \"true\" }}\n style={{ top: 0, left: 0, right: 0, bottom: 0 }}\n >\n {/* Top bar */}\n <div\n className=\"fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-6 py-4\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"font-mono text-[11px] text-muted-foreground/60 tabular-nums tracking-wider uppercase\">\n {String(selectedIndex + 1).padStart(2, '0')}\n <span className=\"mx-1.5 text-muted-foreground/30\">/</span>\n {String(images.length).padStart(2, '0')}\n </div>\n <button\n onClick={onClose}\n className=\"p-2 -m-2 text-muted-foreground/50 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4\" strokeWidth={1.5} />\n </button>\n </div>\n\n {/* Navigation: Previous */}\n {showNavigation && selectedIndex > 0 && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onPrevious();\n }}\n className=\"fixed left-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Previous image\"\n >\n <ChevronLeft className=\"h-6 w-6\" strokeWidth={1.5} />\n </button>\n )}\n\n {/* Navigation: Next */}\n {showNavigation && selectedIndex < images.length - 1 && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onNext();\n }}\n className=\"fixed right-6 top-1/2 -translate-y-1/2 z-10 p-3 -m-3 text-muted-foreground/40 hover:text-foreground transition-colors duration-200\"\n aria-label=\"Next image\"\n >\n <ChevronRight className=\"h-6 w-6\" strokeWidth={1.5} />\n </button>\n )}\n\n {/* Main image */}\n <div className=\"fixed inset-0 flex items-center justify-center p-16\">\n <img\n src={current.src}\n alt={current.label}\n className=\"max-w-full max-h-full object-contain rounded-sm shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n\n {/* Caption */}\n <div\n className=\"fixed bottom-0 left-0 right-0 z-10 px-6 py-5 text-center\"\n onClick={(e) => e.stopPropagation()}\n >\n <span className=\"font-mono text-[11px] text-muted-foreground/50 tracking-wide\">\n {current.label}\n </span>\n </div>\n </div>,\n document.body\n );\n}\n"],"names":[],"mappings":";;;;AAqBO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AACnB,GAAkB;AAChB,QAAM,UAAU,OAAO,aAAa;AAEpC,SAAO;AAAA,IACL;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,SAAS;AAAA,QACR,GAAG,EAAE,CAAC,oBAAoB,GAAG,OAAA;AAAA,QAC9B,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,EAAA;AAAA,QAG5C,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,cAElB,UAAA;AAAA,gBAAA,qBAAC,OAAA,EAAI,WAAU,wFACZ,UAAA;AAAA,kBAAA,OAAO,gBAAgB,CAAC,EAAE,SAAS,GAAG,GAAG;AAAA,kBAC1C,oBAAC,QAAA,EAAK,WAAU,mCAAkC,UAAA,KAAC;AAAA,kBAClD,OAAO,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG;AAAA,gBAAA,GACxC;AAAA,gBACA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,SAAS;AAAA,oBACT,WAAU;AAAA,oBACV,cAAW;AAAA,oBAEX,UAAA,oBAAC,GAAA,EAAE,WAAU,WAAU,aAAa,IAAA,CAAK;AAAA,kBAAA;AAAA,gBAAA;AAAA,cAC3C;AAAA,YAAA;AAAA,UAAA;AAAA,UAID,kBAAkB,gBAAgB,KACjC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAA;AACF,2BAAA;AAAA,cACF;AAAA,cACA,WAAU;AAAA,cACV,cAAW;AAAA,cAEX,UAAA,oBAAC,aAAA,EAAY,WAAU,WAAU,aAAa,IAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,UAKtD,kBAAkB,gBAAgB,OAAO,SAAS,KACjD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS,CAAC,MAAM;AACd,kBAAE,gBAAA;AACF,uBAAA;AAAA,cACF;AAAA,cACA,WAAU;AAAA,cACV,cAAW;AAAA,cAEX,UAAA,oBAAC,cAAA,EAAa,WAAU,WAAU,aAAa,IAAA,CAAK;AAAA,YAAA;AAAA,UAAA;AAAA,UAKxD,oBAAC,OAAA,EAAI,WAAU,uDACb,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAK,QAAQ;AAAA,cACb,KAAK,QAAQ;AAAA,cACb,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,YAAgB;AAAA,UAAA,GAEtC;AAAA,UAGA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,cAElB,UAAA,oBAAC,QAAA,EAAK,WAAU,gEACb,kBAAQ,MAAA,CACX;AAAA,YAAA;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,SAAS;AAAA,EAAA;AAEb;"}
@@ -23,7 +23,9 @@ function Gallery({
23
23
  title,
24
24
  subtitle,
25
25
  limit = null,
26
- page = 0
26
+ page = 0,
27
+ children,
28
+ childAlign = "right"
27
29
  }) {
28
30
  const { paths, isLoading, isEmpty } = useGalleryImages({
29
31
  path,
@@ -58,16 +60,44 @@ function Gallery({
58
60
  /* @__PURE__ */ jsx("span", { className: "font-mono text-xs uppercase tracking-widest", children: "No images" })
59
61
  ] }) });
60
62
  }
63
+ const isCompact = images.length <= 3;
64
+ const isSingleWithChildren = images.length === 1 && children;
65
+ const imageElement = (index, img, className) => /* @__PURE__ */ jsx(
66
+ "div",
67
+ {
68
+ className: `aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ""}`,
69
+ onClick: () => lightbox.open(index),
70
+ children: /* @__PURE__ */ jsx(
71
+ LoadingImage,
72
+ {
73
+ src: img.src,
74
+ alt: img.label,
75
+ className: "object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
76
+ }
77
+ )
78
+ },
79
+ index
80
+ );
61
81
  return /* @__PURE__ */ jsxs(Fragment, { children: [
62
- /* @__PURE__ */ jsxs("figure", { className: "not-prose relative py-6 md:py-8 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]", children: [
63
- /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
64
- /* @__PURE__ */ jsxs(Carousel, { className: "w-full", children: [
65
- /* @__PURE__ */ jsx(CarouselContent, { className: `-ml-2 md:-ml-3 ${images.length < 3 ? "justify-center" : ""}`, children: images.map((img, index) => /* @__PURE__ */ jsx(
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
+ isSingleWithChildren ? /* @__PURE__ */ jsxs("div", { className: `flex gap-6 ${childAlign === "left" ? "" : "flex-row-reverse"}`, children: [
85
+ /* @__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
+ (title || subtitle) && /* @__PURE__ */ jsx("div", { className: "invisible", children: /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }) }),
87
+ /* @__PURE__ */ jsx("div", { children })
88
+ ] }),
89
+ /* @__PURE__ */ jsxs("div", { className: "w-3/5 flex-shrink-0", children: [
90
+ /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
91
+ imageElement(0, images[0]),
92
+ /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
93
+ ] })
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(
66
96
  CarouselItem,
67
97
  {
68
- className: `pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? "flex-none" : ""}`,
98
+ className: "pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group",
69
99
  onClick: () => lightbox.open(index),
70
- children: /* @__PURE__ */ jsx("div", { className: "aspect-square overflow-hidden rounded-sm ring-1 ring-border/50 transition-all duration-300 group-hover:ring-border group-hover:shadow-lg bg-muted/10", children: /* @__PURE__ */ jsx(
100
+ children: /* @__PURE__ */ jsx("div", { className: "aspect-square overflow-hidden rounded-sm bg-muted/10", children: /* @__PURE__ */ jsx(
71
101
  LoadingImage,
72
102
  {
73
103
  src: img.src,
@@ -83,7 +113,7 @@ function Gallery({
83
113
  /* @__PURE__ */ jsx(CarouselNext, { className: "right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" })
84
114
  ] })
85
115
  ] }),
86
- /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
116
+ !isSingleWithChildren && /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
87
117
  ] }),
88
118
  lightbox.isOpen && lightbox.selectedIndex !== null && /* @__PURE__ */ jsx(
89
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}: {\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}) {\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 return (\n <>\n <figure className=\"not-prose relative py-6 md:py-8 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]\">\n <FigureHeader title={title} subtitle={subtitle} />\n\n <Carousel className=\"w-full\">\n <CarouselContent className={`-ml-2 md:-ml-3 ${images.length < 3 ? 'justify-center' : ''}`}>\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 ${images.length < 3 ? 'flex-none' : ''}`}\n onClick={() => lightbox.open(index)}\n >\n <div className=\"aspect-square overflow-hidden rounded-sm ring-1 ring-border/50 transition-all duration-300 group-hover:ring-border group-hover:shadow-lg 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 <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\";\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,11 +1,13 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Link } from "react-router-dom";
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useParams, Link } from "react-router-dom";
3
3
  import { ModeToggle } from "./mode-toggle.js";
4
4
  import { SiGithub } from "@icons-pack/react-simple-icons";
5
5
  import { ChevronUp, ChevronDown } from "lucide-react";
6
6
  import siteConfig from "virtual:veslx-config";
7
+ import { cn } from "../lib/utils.js";
7
8
  function Header({ slideControls } = {}) {
8
9
  const config = siteConfig;
10
+ const { "*": path } = useParams();
9
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: [
10
12
  /* @__PURE__ */ jsx("nav", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
11
13
  Link,
@@ -16,32 +18,54 @@ function Header({ slideControls } = {}) {
16
18
  }
17
19
  ) }),
18
20
  /* @__PURE__ */ jsx("div", { className: "flex-1" }),
19
- slideControls && /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-1", children: [
21
+ /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-4", children: [
22
+ slideControls && /* @__PURE__ */ jsxs(Fragment, { children: [
23
+ /* @__PURE__ */ jsx(
24
+ "button",
25
+ {
26
+ onClick: slideControls.onPrevious,
27
+ className: "p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200",
28
+ title: "Previous slide (↑)",
29
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" })
30
+ }
31
+ ),
32
+ /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center", children: [
33
+ slideControls.current + 1,
34
+ "/",
35
+ slideControls.total
36
+ ] }),
37
+ /* @__PURE__ */ jsx(
38
+ "button",
39
+ {
40
+ onClick: slideControls.onNext,
41
+ className: "p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200",
42
+ title: "Next slide (↓)",
43
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
44
+ }
45
+ )
46
+ ] }),
20
47
  /* @__PURE__ */ jsx(
21
- "button",
48
+ Link,
22
49
  {
23
- onClick: slideControls.onPrevious,
24
- className: "p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200",
25
- title: "Previous slide (↑)",
26
- children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" })
50
+ to: `/posts`,
51
+ className: cn(
52
+ "font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
53
+ (path == null ? void 0 : path.startsWith("posts")) && "font-semibold text-foreground"
54
+ ),
55
+ children: "Posts"
27
56
  }
28
57
  ),
29
- /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center", children: [
30
- slideControls.current + 1,
31
- "/",
32
- slideControls.total
33
- ] }),
34
58
  /* @__PURE__ */ jsx(
35
- "button",
59
+ Link,
36
60
  {
37
- onClick: slideControls.onNext,
38
- className: "p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200",
39
- title: "Next slide (↓)",
40
- children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
61
+ to: `/docs`,
62
+ className: cn(
63
+ "font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
64
+ (path == null ? void 0 : path.startsWith("docs")) && "font-semibold text-foreground"
65
+ ),
66
+ children: "Docs"
41
67
  }
42
- )
43
- ] }),
44
- /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-2", children: [
68
+ ),
45
69
  config.github && /* @__PURE__ */ jsx(
46
70
  Link,
47
71
  {
@@ -1 +1 @@
1
- {"version":3,"file":"header.js","sources":["../../../src/components/header.tsx"],"sourcesContent":["import { Link } 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\";\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 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 {/* Slide navigation controls */}\n {slideControls && (\n <nav className=\"flex items-center gap-1\">\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 </nav>\n )}\n\n {/* Navigation */}\n <nav className=\"flex items-center gap-2\">\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":";;;;;;AAeO,SAAS,OAAO,EAAE,cAAA,IAA+B,IAAI;AAC1D,QAAM,SAAS;AAEf,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,IAGvB,iBACC,qBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAS,cAAc;AAAA,UACvB,WAAU;AAAA,UACV,OAAM;AAAA,UAEN,UAAA,oBAAC,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,QAAA;AAAA,MAAA;AAAA,MAEjC,qBAAC,QAAA,EAAK,WAAU,mFACb,UAAA;AAAA,QAAA,cAAc,UAAU;AAAA,QAAE;AAAA,QAAE,cAAc;AAAA,MAAA,GAC7C;AAAA,MACA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAS,cAAc;AAAA,UACvB,WAAU;AAAA,UACV,OAAM;AAAA,UAEN,UAAA,oBAAC,aAAA,EAAY,WAAU,UAAA,CAAU;AAAA,QAAA;AAAA,MAAA;AAAA,IACnC,GACF;AAAA,IAIF,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,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=\"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;"}
@@ -4,6 +4,10 @@ import Gallery from "./gallery/index.js";
4
4
  import { ParameterTable } from "./parameter-table.js";
5
5
  import { ParameterBadge } from "./parameter-badge.js";
6
6
  import { FrontMatter } from "./front-matter.js";
7
+ import { HeroSlide } from "./slides/hero-slide.js";
8
+ import { FigureSlide } from "./slides/figure-slide.js";
9
+ import { TextSlide } from "./slides/text-slide.js";
10
+ import { SlideOutline } from "./slides/slide-outline.js";
7
11
  function SmartLink({ href, children, ...props }) {
8
12
  const location = useLocation();
9
13
  const isExternal = (href == null ? void 0 : href.startsWith("http")) || (href == null ? void 0 : href.startsWith("mailto:")) || (href == null ? void 0 : href.startsWith("tel:"));
@@ -49,6 +53,10 @@ const mdxComponents = {
49
53
  Gallery,
50
54
  ParameterTable,
51
55
  ParameterBadge,
56
+ HeroSlide,
57
+ FigureSlide,
58
+ TextSlide,
59
+ SlideOutline,
52
60
  // Headings - clean sans-serif
53
61
  h1: (props) => {
54
62
  const id = generateId(props.children);
@@ -1 +1 @@
1
- {"version":3,"file":"mdx-components.js","sources":["../../../src/components/mdx-components.tsx"],"sourcesContent":["import { Link, useLocation } from 'react-router-dom'\nimport Gallery from '@/components/gallery'\nimport { ParameterTable } from '@/components/parameter-table'\nimport { ParameterBadge } from '@/components/parameter-badge'\nimport { FrontMatter } from './front-matter'\n\n/**\n * Smart link component that uses React Router for internal links\n * and regular anchor tags for external links.\n */\nfunction SmartLink({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const location = useLocation()\n\n // External links: absolute URLs, mailto, tel, etc.\n const isExternal = href?.startsWith('http') || href?.startsWith('mailto:') || href?.startsWith('tel:')\n\n // Hash-only links stay as anchors for in-page navigation\n const isHashOnly = href?.startsWith('#')\n\n if (isExternal || isHashOnly || !href) {\n return (\n <a\n href={href}\n className=\"text-primary hover:underline underline-offset-2\"\n {...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...props}\n >\n {children}\n </a>\n )\n }\n\n // Resolve relative paths (./foo.mdx, ../bar.mdx) against current location\n let resolvedHref = href\n if (href.startsWith('./') || href.startsWith('../')) {\n // Get current directory from pathname\n const currentPath = location.pathname\n const currentDir = currentPath.replace(/\\/[^/]+\\.mdx$/, '') || currentPath.replace(/\\/[^/]*$/, '') || '/'\n\n // Simple relative path resolution\n if (href.startsWith('./')) {\n resolvedHref = `${currentDir}/${href.slice(2)}`.replace(/\\/+/g, '/')\n } else if (href.startsWith('../')) {\n const parentDir = currentDir.replace(/\\/[^/]+$/, '') || '/'\n resolvedHref = `${parentDir}/${href.slice(3)}`.replace(/\\/+/g, '/')\n }\n }\n\n // Internal link - use React Router Link\n return (\n <Link\n to={resolvedHref}\n className=\"text-primary hover:underline underline-offset-2\"\n {...props}\n >\n {children}\n </Link>\n )\n}\n\nfunction generateId(children: unknown): string {\n return children\n ?.toString()\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-') ?? ''\n}\n\n// Shared MDX components - lab notebook / coder aesthetic\nexport const mdxComponents = {\n\n FrontMatter,\n\n Gallery,\n\n ParameterTable,\n\n ParameterBadge,\n\n // Headings - clean sans-serif\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h1\n id={id}\n className=\"text-2xl font-semibold tracking-tight mt-12 mb-4 first:mt-0\"\n {...props}\n />\n )\n },\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h2\n id={id}\n className=\"text-xl font-semibold tracking-tight mt-10 mb-3 pb-2 border-b border-border\"\n {...props}\n />\n )\n },\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h3\n id={id}\n className=\"text-lg font-medium tracking-tight mt-8 mb-2\"\n {...props}\n />\n )\n },\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h4\n id={id}\n className=\"text-base font-medium mt-6 mb-2\"\n {...props}\n />\n )\n },\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h5\n id={id}\n className=\"text-sm font-medium mt-4 mb-1\"\n {...props}\n />\n )\n },\n\n // Code blocks - IDE/terminal style\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => (\n <pre\n className=\"not-prose w-full overflow-x-auto p-4 text-sm bg-muted/50 border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: (props: React.HTMLAttributes<HTMLElement> & { className?: string }) => {\n const isInline = !props.className?.includes('language-')\n if (isInline) {\n return (\n <code\n className=\"font-mono text-[0.85em] bg-muted px-1.5 py-0.5 rounded text-primary\"\n {...props}\n />\n )\n }\n return <code {...props} />\n },\n\n // Blockquote\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) => (\n <blockquote\n className=\"border-l-2 border-primary pl-4 my-6 text-muted-foreground\"\n {...props}\n />\n ),\n\n // Lists\n ul: (props: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"my-4 ml-6 list-disc marker:text-muted-foreground\" {...props} />\n ),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => (\n <ol className=\"my-4 ml-6 list-decimal marker:text-muted-foreground\" {...props} />\n ),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"mt-1.5\" {...props} />\n ),\n\n // Links - uses React Router for internal navigation\n a: SmartLink,\n\n // Tables\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className=\"w-full text-sm border-collapse\" {...props} />\n </div>\n ),\n thead: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <thead className=\"bg-muted/50\" {...props} />\n ),\n tbody: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <tbody {...props} />\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) => (\n <tr className=\"border-b border-border last:border-b-0\" {...props} />\n ),\n th: (props: React.ThHTMLAttributes<HTMLTableCellElement>) => (\n <th\n className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\"\n {...props}\n />\n ),\n td: (props: React.TdHTMLAttributes<HTMLTableCellElement>) => (\n <td className=\"px-4 py-3 align-top\" {...props} />\n ),\n\n // Horizontal rule\n hr: (props: React.HTMLAttributes<HTMLHRElement>) => (\n <hr className=\"my-8 border-t border-border\" {...props} />\n ),\n\n // Paragraph\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"leading-relaxed mb-4 last:mb-0\" {...props} />\n ),\n\n // Strong/emphasis\n strong: (props: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"font-semibold\" {...props} />\n ),\n em: (props: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"italic\" {...props} />\n ),\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mdx-components.js","sources":["../../../src/components/mdx-components.tsx"],"sourcesContent":["import { Link, useLocation } from 'react-router-dom'\nimport Gallery from '@/components/gallery'\nimport { ParameterTable } from '@/components/parameter-table'\nimport { ParameterBadge } from '@/components/parameter-badge'\nimport { FrontMatter } from './front-matter'\nimport { HeroSlide } from './slides/hero-slide'\nimport { FigureSlide } from './slides/figure-slide'\nimport { TextSlide } from './slides/text-slide'\nimport { SlideOutline } from './slides/slide-outline'\n\n/**\n * Smart link component that uses React Router for internal links\n * and regular anchor tags for external links.\n */\nfunction SmartLink({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const location = useLocation()\n\n // External links: absolute URLs, mailto, tel, etc.\n const isExternal = href?.startsWith('http') || href?.startsWith('mailto:') || href?.startsWith('tel:')\n\n // Hash-only links stay as anchors for in-page navigation\n const isHashOnly = href?.startsWith('#')\n\n if (isExternal || isHashOnly || !href) {\n return (\n <a\n href={href}\n className=\"text-primary hover:underline underline-offset-2\"\n {...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...props}\n >\n {children}\n </a>\n )\n }\n\n // Resolve relative paths (./foo.mdx, ../bar.mdx) against current location\n let resolvedHref = href\n if (href.startsWith('./') || href.startsWith('../')) {\n // Get current directory from pathname\n const currentPath = location.pathname\n const currentDir = currentPath.replace(/\\/[^/]+\\.mdx$/, '') || currentPath.replace(/\\/[^/]*$/, '') || '/'\n\n // Simple relative path resolution\n if (href.startsWith('./')) {\n resolvedHref = `${currentDir}/${href.slice(2)}`.replace(/\\/+/g, '/')\n } else if (href.startsWith('../')) {\n const parentDir = currentDir.replace(/\\/[^/]+$/, '') || '/'\n resolvedHref = `${parentDir}/${href.slice(3)}`.replace(/\\/+/g, '/')\n }\n }\n\n // Internal link - use React Router Link\n return (\n <Link\n to={resolvedHref}\n className=\"text-primary hover:underline underline-offset-2\"\n {...props}\n >\n {children}\n </Link>\n )\n}\n\nfunction generateId(children: unknown): string {\n return children\n ?.toString()\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-') ?? ''\n}\n\n// Shared MDX components - lab notebook / coder aesthetic\nexport const mdxComponents = {\n\n FrontMatter,\n\n Gallery,\n\n ParameterTable,\n\n ParameterBadge,\n\n HeroSlide,\n\n FigureSlide,\n\n TextSlide,\n\n SlideOutline,\n\n // Headings - clean sans-serif\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h1\n id={id}\n className=\"text-2xl font-semibold tracking-tight mt-12 mb-4 first:mt-0\"\n {...props}\n />\n )\n },\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h2\n id={id}\n className=\"text-xl font-semibold tracking-tight mt-10 mb-3 pb-2 border-b border-border\"\n {...props}\n />\n )\n },\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h3\n id={id}\n className=\"text-lg font-medium tracking-tight mt-8 mb-2\"\n {...props}\n />\n )\n },\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h4\n id={id}\n className=\"text-base font-medium mt-6 mb-2\"\n {...props}\n />\n )\n },\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h5\n id={id}\n className=\"text-sm font-medium mt-4 mb-1\"\n {...props}\n />\n )\n },\n\n // Code blocks - IDE/terminal style\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => (\n <pre\n className=\"not-prose w-full overflow-x-auto p-4 text-sm bg-muted/50 border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: (props: React.HTMLAttributes<HTMLElement> & { className?: string }) => {\n const isInline = !props.className?.includes('language-')\n if (isInline) {\n return (\n <code\n className=\"font-mono text-[0.85em] bg-muted px-1.5 py-0.5 rounded text-primary\"\n {...props}\n />\n )\n }\n return <code {...props} />\n },\n\n // Blockquote\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) => (\n <blockquote\n className=\"border-l-2 border-primary pl-4 my-6 text-muted-foreground\"\n {...props}\n />\n ),\n\n // Lists\n ul: (props: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"my-4 ml-6 list-disc marker:text-muted-foreground\" {...props} />\n ),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => (\n <ol className=\"my-4 ml-6 list-decimal marker:text-muted-foreground\" {...props} />\n ),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"mt-1.5\" {...props} />\n ),\n\n // Links - uses React Router for internal navigation\n a: SmartLink,\n\n // Tables\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className=\"w-full text-sm border-collapse\" {...props} />\n </div>\n ),\n thead: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <thead className=\"bg-muted/50\" {...props} />\n ),\n tbody: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <tbody {...props} />\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) => (\n <tr className=\"border-b border-border last:border-b-0\" {...props} />\n ),\n th: (props: React.ThHTMLAttributes<HTMLTableCellElement>) => (\n <th\n className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\"\n {...props}\n />\n ),\n td: (props: React.TdHTMLAttributes<HTMLTableCellElement>) => (\n <td className=\"px-4 py-3 align-top\" {...props} />\n ),\n\n // Horizontal rule\n hr: (props: React.HTMLAttributes<HTMLHRElement>) => (\n <hr className=\"my-8 border-t border-border\" {...props} />\n ),\n\n // Paragraph\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"leading-relaxed mb-4 last:mb-0\" {...props} />\n ),\n\n // Strong/emphasis\n strong: (props: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"font-semibold\" {...props} />\n ),\n em: (props: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"italic\" {...props} />\n ),\n}\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Link } from "react-router-dom";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { formatDate } from "../lib/format-date.js";
5
- import { ArrowRight } from "lucide-react";
5
+ import { ArrowRight, Presentation } from "lucide-react";
6
6
  import { directoryToPostEntries, filterVisiblePosts, filterByView, getFrontmatter } from "../lib/content-classification.js";
7
7
  function extractOrder(name) {
8
8
  const match = name.match(/^(\d+)-/);
@@ -60,6 +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
64
  return /* @__PURE__ */ jsx(
64
65
  Link,
65
66
  {
@@ -68,15 +69,7 @@ function PostList({ directory, view = "all" }) {
68
69
  "group block py-3 px-3 -mx-3 rounded-md",
69
70
  "transition-colors duration-150"
70
71
  ),
71
- children: /* @__PURE__ */ jsxs("article", { className: "flex items-start gap-4", children: [
72
- /* @__PURE__ */ jsx(
73
- "time",
74
- {
75
- dateTime: date == null ? void 0 : date.toISOString(),
76
- className: "font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0 pt-0.5",
77
- children: date ? formatDate(date) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/30", children: "—" })
78
- }
79
- ),
72
+ children: /* @__PURE__ */ jsxs("article", { className: "flex items-center gap-4", children: [
80
73
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
81
74
  /* @__PURE__ */ jsxs("h3", { className: cn(
82
75
  "text-sm font-medium text-foreground",
@@ -87,7 +80,16 @@ function PostList({ directory, view = "all" }) {
87
80
  /* @__PURE__ */ jsx(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" })
88
81
  ] }),
89
82
  description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground line-clamp-1 mt-0.5", children: description })
90
- ] })
83
+ ] }),
84
+ isSlides && /* @__PURE__ */ jsx(Presentation, { className: "h-3 w-3 text-muted-foreground" }),
85
+ /* @__PURE__ */ jsx(
86
+ "time",
87
+ {
88
+ dateTime: date == null ? void 0 : date.toISOString(),
89
+ className: "font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0",
90
+ children: date && formatDate(date)
91
+ }
92
+ )
91
93
  ] })
92
94
  },
93
95
  post.path
@@ -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 } 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 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-start gap-4\">\n {/* Date - left side, fixed width */}\n <time\n dateTime={date?.toISOString()}\n className=\"font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0 pt-0.5\"\n >\n {date ? formatDate(date) : <span className=\"text-muted-foreground/30\">—</span>}\n </time>\n\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 </article>\n </Link>\n );\n })}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AAeA,SAAS,aAAa,MAA6B;AACjD,QAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,SAAO,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC1C;AAGA,SAAS,mBAAmB,MAAsB;AAChD,SAAO,KACJ,QAAQ,SAAS,EAAE,EACnB,QAAQ,MAAM,GAAG,EACjB,QAAQ,SAAS,CAAA,MAAK,EAAE,aAAa;AAC1C;AAOA,SAAwB,SAAS,EAAE,WAAW,OAAO,SAAwB;AAC3E,MAAI,QAAQ,uBAAuB,SAAS;AAE5C,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,UAAQ,mBAAmB,KAAK;AAGhC,UAAQ,aAAa,OAAO,IAAI;AAEhC,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,QAAM,cAAc,CAAC,SAAiC;AACpD,UAAM,cAAc,eAAe,IAAI;AACvC,YAAO,2CAAa,QAAO,IAAI,KAAK,YAAY,IAAc,IAAI;AAAA,EACpE;AAGA,UAAQ,MAAM,KAAK,CAAC,GAAG,MAAM;;AAC3B,UAAM,SAAS,aAAa,EAAE,IAAI;AAClC,UAAM,SAAS,aAAa,EAAE,IAAI;AAClC,UAAM,QAAQ,YAAY,CAAC;AAC3B,UAAM,QAAQ,YAAY,CAAC;AAG3B,QAAI,WAAW,QAAQ,WAAW,MAAM;AACtC,aAAO,SAAS;AAAA,IAClB;AAEA,QAAI,WAAW,KAAM,QAAO;AAC5B,QAAI,WAAW,KAAM,QAAO;AAG5B,QAAI,SAAS,OAAO;AAClB,aAAO,MAAM,YAAY,MAAM,QAAA;AAAA,IACjC;AAEA,QAAI,MAAO,QAAO;AAClB,QAAI,MAAO,QAAO;AAGlB,UAAM,WAAU,oBAAe,CAAC,MAAhB,mBAAmB,UAAoB,EAAE;AACzD,UAAM,WAAU,oBAAe,CAAC,MAAhB,mBAAmB,UAAoB,EAAE;AACzD,WAAO,OAAO,cAAc,MAAM;AAAA,EACpC,CAAC;AAED,6BACG,OAAA,EAAI,WAAU,aACZ,UAAA,MAAM,IAAI,CAAC,SAAS;AACnB,UAAM,cAAc,eAAe,IAAI;AAGvC,UAAM,SAAS,2CAAa,UAAoB,mBAAmB,KAAK,IAAI;AAC5E,UAAM,cAAc,2CAAa;AACjC,UAAM,QAAO,2CAAa,QAAO,IAAI,KAAK,YAAY,IAAc,IAAI;AAGxE,QAAI;AACJ,QAAI,KAAK,MAAM;AAEb,iBAAW,IAAI,KAAK,KAAK,IAAI;AAAA,IAC/B,WAAW,KAAK,UAAU,CAAC,KAAK,QAAQ;AAEtC,iBAAW,IAAI,KAAK,OAAO,IAAI;AAAA,IACjC,WAAW,KAAK,QAAQ;AAEtB,iBAAW,IAAI,KAAK,OAAO,IAAI;AAAA,IACjC,OAAO;AAEL,iBAAW,IAAI,KAAK,IAAI;AAAA,IAC1B;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,IAAI;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA,qBAAC,WAAA,EAAQ,WAAU,0BAEjB,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,UAAU,6BAAM;AAAA,cAChB,WAAU;AAAA,cAET,UAAA,OAAO,WAAW,IAAI,wBAAK,QAAA,EAAK,WAAU,4BAA2B,UAAA,IAAA,CAAC;AAAA,YAAA;AAAA,UAAA;AAAA,UAIzE,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,YAAA,qBAAC,QAAG,WAAW;AAAA,cACb;AAAA,cACA;AAAA,cACA;AAAA,YAAA,GAEA,UAAA;AAAA,cAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,cACb,oBAAC,YAAA,EAAW,WAAU,8HAAA,CAA8H;AAAA,YAAA,GACtJ;AAAA,YAEC,eACC,oBAAC,KAAA,EAAE,WAAU,qDACV,UAAA,YAAA,CACH;AAAA,UAAA,EAAA,CAEJ;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,MAjCK,KAAK;AAAA,IAAA;AAAA,EAoChB,CAAC,EAAA,CACH;AAEJ;"}
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":";;;;;;AAeA,SAAS,aAAa,MAA6B;AACjD,QAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,SAAO,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAC1C;AAGA,SAAS,mBAAmB,MAAsB;AAChD,SAAO,KACJ,QAAQ,SAAS,EAAE,EACnB,QAAQ,MAAM,GAAG,EACjB,QAAQ,SAAS,CAAA,MAAK,EAAE,aAAa;AAC1C;AAOA,SAAwB,SAAS,EAAE,WAAW,OAAO,SAAwB;AAC3E,MAAI,QAAQ,uBAAuB,SAAS;AAE5C,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,UAAQ,mBAAmB,KAAK;AAGhC,UAAQ,aAAa,OAAO,IAAI;AAEhC,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,oBAAC,SAAI,WAAU,qBACb,8BAAC,KAAA,EAAE,WAAU,yDAAwD,UAAA,aAAA,CAAU,EAAA,CACjF;AAAA,EAEJ;AAGA,QAAM,cAAc,CAAC,SAAiC;AACpD,UAAM,cAAc,eAAe,IAAI;AACvC,YAAO,2CAAa,QAAO,IAAI,KAAK,YAAY,IAAc,IAAI;AAAA,EACpE;AAGA,UAAQ,MAAM,KAAK,CAAC,GAAG,MAAM;;AAC3B,UAAM,SAAS,aAAa,EAAE,IAAI;AAClC,UAAM,SAAS,aAAa,EAAE,IAAI;AAClC,UAAM,QAAQ,YAAY,CAAC;AAC3B,UAAM,QAAQ,YAAY,CAAC;AAG3B,QAAI,WAAW,QAAQ,WAAW,MAAM;AACtC,aAAO,SAAS;AAAA,IAClB;AAEA,QAAI,WAAW,KAAM,QAAO;AAC5B,QAAI,WAAW,KAAM,QAAO;AAG5B,QAAI,SAAS,OAAO;AAClB,aAAO,MAAM,YAAY,MAAM,QAAA;AAAA,IACjC;AAEA,QAAI,MAAO,QAAO;AAClB,QAAI,MAAO,QAAO;AAGlB,UAAM,WAAU,oBAAe,CAAC,MAAhB,mBAAmB,UAAoB,EAAE;AACzD,UAAM,WAAU,oBAAe,CAAC,MAAhB,mBAAmB,UAAoB,EAAE;AACzD,WAAO,OAAO,cAAc,MAAM;AAAA,EACpC,CAAC;AAED,6BACG,OAAA,EAAI,WAAU,aACZ,UAAA,MAAM,IAAI,CAAC,SAAS;AACnB,UAAM,cAAc,eAAe,IAAI;AAGvC,UAAM,SAAS,2CAAa,UAAoB,mBAAmB,KAAK,IAAI;AAC5E,UAAM,cAAc,2CAAa;AACjC,UAAM,QAAO,2CAAa,QAAO,IAAI,KAAK,YAAY,IAAc,IAAI;AAGxE,QAAI;AACJ,QAAI,KAAK,MAAM;AAEb,iBAAW,IAAI,KAAK,KAAK,IAAI;AAAA,IAC/B,WAAW,KAAK,UAAU,CAAC,KAAK,QAAQ;AAEtC,iBAAW,IAAI,KAAK,OAAO,IAAI;AAAA,IACjC,WAAW,KAAK,QAAQ;AAEtB,iBAAW,IAAI,KAAK,OAAO,IAAI;AAAA,IACjC,OAAO;AAEL,iBAAW,IAAI,KAAK,IAAI;AAAA,IAC1B;AAEA,UAAM,WAAW,SAAS,SAAS,YAAY;AAE/C,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,IAAI;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAGF,UAAA,qBAAC,WAAA,EAAQ,WAAU,2BAEjB,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,YAAA,qBAAC,QAAG,WAAW;AAAA,cACb;AAAA,cACA;AAAA,cACA;AAAA,YAAA,GAEA,UAAA;AAAA,cAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,cACb,oBAAC,YAAA,EAAW,WAAU,8HAAA,CAA8H;AAAA,YAAA,GACtJ;AAAA,YAEC,eACC,oBAAC,KAAA,EAAE,WAAU,qDACV,UAAA,YAAA,CACH;AAAA,UAAA,GAEJ;AAAA,UAEC,YACC,oBAAC,cAAA,EAAa,WAAU,gCAAA,CAAgC;AAAA,UAE1D;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,UAAU,6BAAM;AAAA,cAChB,WAAU;AAAA,cAET,UAAA,QAAQ,WAAW,IAAI;AAAA,YAAA;AAAA,UAAA;AAAA,QAC1B,EAAA,CACF;AAAA,MAAA;AAAA,MAnCK,KAAK;AAAA,IAAA;AAAA,EAsChB,CAAC,EAAA,CACH;AAEJ;"}