veslx 0.1.14 → 0.1.16

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 (82) hide show
  1. package/README.md +262 -55
  2. package/bin/lib/build.ts +65 -13
  3. package/bin/lib/import-config.ts +10 -9
  4. package/bin/lib/init.ts +21 -22
  5. package/bin/lib/serve.ts +66 -12
  6. package/bin/veslx.ts +2 -2
  7. package/dist/client/App.js +3 -9
  8. package/dist/client/App.js.map +1 -1
  9. package/dist/client/components/front-matter.js +11 -25
  10. package/dist/client/components/front-matter.js.map +1 -1
  11. package/dist/client/components/gallery/components/figure-caption.js +6 -4
  12. package/dist/client/components/gallery/components/figure-caption.js.map +1 -1
  13. package/dist/client/components/gallery/components/figure-header.js +3 -3
  14. package/dist/client/components/gallery/components/figure-header.js.map +1 -1
  15. package/dist/client/components/gallery/components/lightbox.js +13 -13
  16. package/dist/client/components/gallery/components/lightbox.js.map +1 -1
  17. package/dist/client/components/gallery/components/loading-image.js +11 -10
  18. package/dist/client/components/gallery/components/loading-image.js.map +1 -1
  19. package/dist/client/components/gallery/hooks/use-gallery-images.js +31 -7
  20. package/dist/client/components/gallery/hooks/use-gallery-images.js.map +1 -1
  21. package/dist/client/components/gallery/index.js +22 -15
  22. package/dist/client/components/gallery/index.js.map +1 -1
  23. package/dist/client/components/header.js +5 -3
  24. package/dist/client/components/header.js.map +1 -1
  25. package/dist/client/components/mdx-components.js +42 -8
  26. package/dist/client/components/mdx-components.js.map +1 -1
  27. package/dist/client/components/post-list.js +97 -90
  28. package/dist/client/components/post-list.js.map +1 -1
  29. package/dist/client/components/running-bar.js +1 -1
  30. package/dist/client/components/running-bar.js.map +1 -1
  31. package/dist/client/components/slide.js +18 -0
  32. package/dist/client/components/slide.js.map +1 -0
  33. package/dist/client/components/slides-renderer.js +7 -71
  34. package/dist/client/components/slides-renderer.js.map +1 -1
  35. package/dist/client/hooks/use-mdx-content.js +55 -9
  36. package/dist/client/hooks/use-mdx-content.js.map +1 -1
  37. package/dist/client/main.js +1 -0
  38. package/dist/client/main.js.map +1 -1
  39. package/dist/client/pages/content-router.js +19 -0
  40. package/dist/client/pages/content-router.js.map +1 -0
  41. package/dist/client/pages/home.js +11 -7
  42. package/dist/client/pages/home.js.map +1 -1
  43. package/dist/client/pages/post.js +8 -20
  44. package/dist/client/pages/post.js.map +1 -1
  45. package/dist/client/pages/slides.js +62 -86
  46. package/dist/client/pages/slides.js.map +1 -1
  47. package/dist/client/plugin/src/client.js +58 -96
  48. package/dist/client/plugin/src/client.js.map +1 -1
  49. package/dist/client/plugin/src/directory-tree.js +111 -0
  50. package/dist/client/plugin/src/directory-tree.js.map +1 -0
  51. package/index.html +1 -1
  52. package/package.json +27 -15
  53. package/plugin/src/client.tsx +64 -116
  54. package/plugin/src/directory-tree.ts +171 -0
  55. package/plugin/src/lib.ts +6 -249
  56. package/plugin/src/plugin.ts +93 -50
  57. package/plugin/src/remark-slides.ts +100 -0
  58. package/plugin/src/types.ts +22 -0
  59. package/src/App.tsx +3 -6
  60. package/src/components/front-matter.tsx +14 -29
  61. package/src/components/gallery/components/figure-caption.tsx +15 -7
  62. package/src/components/gallery/components/figure-header.tsx +3 -3
  63. package/src/components/gallery/components/lightbox.tsx +15 -13
  64. package/src/components/gallery/components/loading-image.tsx +15 -12
  65. package/src/components/gallery/hooks/use-gallery-images.ts +51 -10
  66. package/src/components/gallery/index.tsx +32 -26
  67. package/src/components/header.tsx +14 -9
  68. package/src/components/mdx-components.tsx +61 -8
  69. package/src/components/post-list.tsx +149 -115
  70. package/src/components/running-bar.tsx +1 -1
  71. package/src/components/slide.tsx +22 -5
  72. package/src/components/slides-renderer.tsx +7 -115
  73. package/src/components/welcome.tsx +11 -14
  74. package/src/hooks/use-mdx-content.ts +94 -9
  75. package/src/index.css +159 -0
  76. package/src/main.tsx +1 -0
  77. package/src/pages/content-router.tsx +27 -0
  78. package/src/pages/home.tsx +16 -2
  79. package/src/pages/post.tsx +10 -13
  80. package/src/pages/slides.tsx +75 -88
  81. package/src/vite-env.d.ts +7 -17
  82. package/vite.config.ts +31 -6
package/bin/veslx.ts CHANGED
@@ -21,7 +21,7 @@ cli
21
21
  .action(init)
22
22
 
23
23
  cli
24
- .command("serve", "Start the veslx server")
24
+ .command("serve [dir]", "Start the veslx server")
25
25
  .action(serve);
26
26
 
27
27
  cli
@@ -33,7 +33,7 @@ cli
33
33
  .action(stop);
34
34
 
35
35
  cli
36
- .command("build", "Build the veslx app")
36
+ .command("build [dir]", "Build the veslx app")
37
37
  .action(build)
38
38
 
39
39
  cli.help();
@@ -1,15 +1,9 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { jsx } from "react/jsx-runtime";
2
2
  import { BrowserRouter, Routes, Route } from "react-router-dom";
3
3
  import { ThemeProvider } from "./components/theme-provider.js";
4
- import { Home } from "./pages/home.js";
5
- import { Post } from "./pages/post.js";
6
- import { SlidesPage } from "./pages/slides.js";
4
+ import { ContentRouter } from "./pages/content-router.js";
7
5
  function App() {
8
- return /* @__PURE__ */ jsx(ThemeProvider, { attribute: "class", defaultTheme: "system", enableSystem: true, children: /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsxs(Routes, { children: [
9
- /* @__PURE__ */ jsx(Route, { path: ":path/SLIDES.mdx", element: /* @__PURE__ */ jsx(SlidesPage, {}) }),
10
- /* @__PURE__ */ jsx(Route, { path: ":path/README.mdx", element: /* @__PURE__ */ jsx(Post, {}) }),
11
- /* @__PURE__ */ jsx(Route, { path: "/*", element: /* @__PURE__ */ jsx(Home, {}) })
12
- ] }) }) });
6
+ return /* @__PURE__ */ jsx(ThemeProvider, { attribute: "class", defaultTheme: "system", enableSystem: true, children: /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(Routes, { children: /* @__PURE__ */ jsx(Route, { path: "/*", element: /* @__PURE__ */ jsx(ContentRouter, {}) }) }) }) });
13
7
  }
14
8
  export {
15
9
  App as default
@@ -1 +1 @@
1
- {"version":3,"file":"App.js","sources":["../../src/App.tsx"],"sourcesContent":["import { BrowserRouter, Routes, Route } from \"react-router-dom\"\nimport { ThemeProvider } from \"./components/theme-provider\"\nimport { Home } from \"./pages/home\"\nimport { Post } from \"./pages/post\"\nimport { SlidesPage } from \"./pages/slides\"\n\nfunction App() {\n return (\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n <BrowserRouter>\n <Routes>\n <Route path=\":path/SLIDES.mdx\" element={<SlidesPage />} />\n <Route path=\":path/README.mdx\" element={<Post />} />\n <Route path=\"/*\" element={<Home />} />\n </Routes>\n </BrowserRouter>\n </ThemeProvider>\n )\n}\n\nexport default App\n"],"names":[],"mappings":";;;;;;AAMA,SAAS,MAAM;AACb,SACE,oBAAC,eAAA,EAAc,WAAU,SAAQ,cAAa,UAAS,cAAY,MACjE,UAAA,oBAAC,eAAA,EACG,UAAA,qBAAC,QAAA,EACC,UAAA;AAAA,IAAA,oBAAC,SAAM,MAAK,oBAAmB,SAAS,oBAAC,cAAW,GAAI;AAAA,wBACvD,OAAA,EAAM,MAAK,oBAAmB,SAAS,oBAAC,QAAK,GAAI;AAAA,wBACjD,OAAA,EAAM,MAAK,MAAK,SAAS,oBAAC,QAAK,EAAA,CAAI;AAAA,EAAA,EAAA,CACtC,GACJ,GACF;AAEJ;"}
1
+ {"version":3,"file":"App.js","sources":["../../src/App.tsx"],"sourcesContent":["import { BrowserRouter, Routes, Route } from \"react-router-dom\"\nimport { ThemeProvider } from \"./components/theme-provider\"\nimport { ContentRouter } from \"./pages/content-router\"\n\nfunction App() {\n return (\n <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n <BrowserRouter>\n <Routes>\n {/* Single catch-all route - ContentRouter determines page type */}\n <Route path=\"/*\" element={<ContentRouter />} />\n </Routes>\n </BrowserRouter>\n </ThemeProvider>\n )\n}\n\nexport default App\n"],"names":[],"mappings":";;;;AAIA,SAAS,MAAM;AACb,SACE,oBAAC,iBAAc,WAAU,SAAQ,cAAa,UAAS,cAAY,MACjE,UAAA,oBAAC,eAAA,EACG,UAAA,oBAAC,UAEC,UAAA,oBAAC,OAAA,EAAM,MAAK,MAAK,6BAAU,eAAA,CAAA,CAAc,EAAA,CAAI,EAAA,CAC/C,EAAA,CACJ,EAAA,CACF;AAEJ;"}
@@ -1,30 +1,16 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMDXContent, useMDXSlides } from "../hooks/use-mdx-content.js";
2
3
  import { formatDate } from "../lib/format-date.js";
3
- import { Presentation } from "lucide-react";
4
- import { Link } from "react-router-dom";
5
- function FrontMatter({
6
- title,
7
- date,
8
- description,
9
- slides
10
- }) {
11
- return /* @__PURE__ */ jsx("div", { children: title && /* @__PURE__ */ jsxs("header", { className: "not-prose flex flex-col gap-2 mb-8 pt-4", children: [
12
- /* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3", children: title }),
13
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: [
14
- date && /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(date)) }),
15
- slides && /* @__PURE__ */ jsxs(
16
- Link,
17
- {
18
- to: `/${slides.path}`,
19
- className: "font-mono text-xs px-2 py-0.5 rounded flex items-center gap-1",
20
- children: [
21
- /* @__PURE__ */ jsx(Presentation, { className: "h-3.5 w-3.5" }),
22
- /* @__PURE__ */ jsx("span", { children: "slides" })
23
- ]
24
- }
25
- )
26
- ] }),
27
- description && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap text-sm items-center gap-3 text-muted-foreground", children: description })
4
+ import { useParams } from "react-router-dom";
5
+ function FrontMatter() {
6
+ const { "path": path = "." } = useParams();
7
+ const { frontmatter: readmeFm } = useMDXContent(path);
8
+ const { frontmatter: slidesFm } = useMDXSlides(path);
9
+ let frontmatter = readmeFm || slidesFm;
10
+ 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
+ /* @__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
+ /* @__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)) }) }),
13
+ (frontmatter == null ? void 0 : frontmatter.description) && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap text-sm items-center gap-3 text-muted-foreground", children: frontmatter == null ? void 0 : frontmatter.description })
28
14
  ] }) });
29
15
  }
30
16
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { formatDate } from \"@/lib/format-date\"\nimport { Presentation } from \"lucide-react\"\nimport { FileEntry } from \"plugin/src/lib\"\nimport { Link } from \"react-router-dom\"\n\nexport function FrontMatter({\n title,\n date,\n description,\n slides,\n}: {\n title?: string\n date?: string\n description?: string\n slides?: FileEntry | null\n}){\n\n return (\n <div>\n {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 {title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(date as string))}\n </time>\n )}\n {slides && (\n <Link\n to={`/${slides.path}`}\n className=\"font-mono text-xs px-2 py-0.5 rounded flex items-center gap-1\"\n >\n <Presentation className=\"h-3.5 w-3.5\" />\n <span>slides</span>\n </Link>\n )}\n </div>\n\n {description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}"],"names":[],"mappings":";;;;AAKO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKE;AAEA,6BACG,OAAA,EACE,UAAA,SACC,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,OACH;AAAA,IAGA,qBAAC,OAAA,EAAI,WAAU,2DACZ,UAAA;AAAA,MAAA,QACC,oBAAC,UAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,IAAc,CAAC,EAAA,CACtC;AAAA,MAED,UACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI,IAAI,OAAO,IAAI;AAAA,UACnB,WAAU;AAAA,UAEV,UAAA;AAAA,YAAA,oBAAC,cAAA,EAAa,WAAU,cAAA,CAAc;AAAA,YACtC,oBAAC,UAAK,UAAA,SAAA,CAAM;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACd,GAEJ;AAAA,IAEC,eACC,oBAAC,OAAA,EAAI,WAAU,mEACZ,UAAA,YAAA,CACH;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
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;"}
@@ -2,10 +2,12 @@ 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("div", { className: "mx-auto max-w-md", children: /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground leading-relaxed text-left", children: [
6
- label && /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: label }),
7
- label && caption && /* @__PURE__ */ jsx("span", { className: "mx-1", children: "—" }),
8
- caption && renderMathInText(caption)
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: [
6
+ label && /* @__PURE__ */ jsxs("span", { className: "font-semibold text-foreground tracking-tight", children: [
7
+ label,
8
+ caption && /* @__PURE__ */ jsx("span", { className: "font-normal mx-1.5", children: "·" })
9
+ ] }),
10
+ caption && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/90", children: renderMathInText(caption) })
9
11
  ] }) });
10
12
  }
11
13
  export {
@@ -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 <div className=\"mx-auto max-w-md\">\n <div className=\"text-sm text-muted-foreground leading-relaxed text-left\">\n {label && <span className=\"font-medium text-foreground\">{label}</span>}\n {label && caption && <span className=\"mx-1\">—</span>}\n {caption && renderMathInText(caption)}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;AAEO,SAAS,cAAc,EAAE,SAAS,SAA+C;AACtF,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAE/B,6BACG,OAAA,EAAI,WAAU,oBACb,UAAA,qBAAC,OAAA,EAAI,WAAU,2DACZ,UAAA;AAAA,IAAA,SAAS,oBAAC,QAAA,EAAK,WAAU,+BAA+B,UAAA,OAAM;AAAA,IAC9D,SAAS,WAAW,oBAAC,QAAA,EAAK,WAAU,QAAO,UAAA,KAAC;AAAA,IAC5C,WAAW,iBAAiB,OAAO;AAAA,EAAA,EAAA,CACtC,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=\"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;"}
@@ -2,9 +2,9 @@ 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: "mx-auto max-w-md", children: [
6
- title && /* @__PURE__ */ jsx("h3", { className: "text-sm md:text-base font-medium tracking-tight text-foreground text-left", children: renderMathInText(title) }),
7
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground leading-relaxed text-left mt-1", children: renderMathInText(subtitle) })
5
+ return /* @__PURE__ */ jsxs("div", { className: "px-[calc((var(--gallery-width)-var(--content-width))/2)] mb-4", children: [
6
+ title && /* @__PURE__ */ jsx("h3", { className: "text-[15px] font-medium tracking-[-0.01em] text-foreground", children: renderMathInText(title) }),
7
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground/80 leading-relaxed mt-1", children: renderMathInText(subtitle) })
8
8
  ] });
9
9
  }
10
10
  export {
@@ -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=\"mx-auto max-w-md\">\n {title && (\n <h3 className=\"text-sm md:text-base font-medium tracking-tight text-foreground text-left\">\n {renderMathInText(title)}\n </h3>\n )}\n {subtitle && (\n <p className=\"text-sm text-muted-foreground leading-relaxed text-left 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,oBACZ,UAAA;AAAA,IAAA,6BACE,MAAA,EAAG,WAAU,6EACX,UAAA,iBAAiB,KAAK,GACzB;AAAA,IAED,YACC,oBAAC,KAAA,EAAE,WAAU,gEACV,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=\"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;"}
@@ -15,7 +15,7 @@ function Lightbox({
15
15
  /* @__PURE__ */ jsxs(
16
16
  "div",
17
17
  {
18
- className: "fixed inset-0 z-[9999] bg-background",
18
+ className: "fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-fade-in-slow",
19
19
  onClick: onClose,
20
20
  ...{ [FULLSCREEN_DATA_ATTR]: "true" },
21
21
  style: { top: 0, left: 0, right: 0, bottom: 0 },
@@ -23,21 +23,21 @@ function Lightbox({
23
23
  /* @__PURE__ */ jsxs(
24
24
  "div",
25
25
  {
26
- className: "fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-4 py-3 bg-background/80 backdrop-blur-sm",
26
+ className: "fixed top-0 left-0 right-0 z-10 flex items-center justify-between px-6 py-4",
27
27
  onClick: (e) => e.stopPropagation(),
28
28
  children: [
29
- /* @__PURE__ */ jsxs("div", { className: "font-mono text-xs text-muted-foreground tabular-nums", children: [
29
+ /* @__PURE__ */ jsxs("div", { className: "font-mono text-[11px] text-muted-foreground/60 tabular-nums tracking-wider uppercase", children: [
30
30
  String(selectedIndex + 1).padStart(2, "0"),
31
- " / ",
31
+ /* @__PURE__ */ jsx("span", { className: "mx-1.5 text-muted-foreground/30", children: "/" }),
32
32
  String(images.length).padStart(2, "0")
33
33
  ] }),
34
34
  /* @__PURE__ */ jsx(
35
35
  "button",
36
36
  {
37
37
  onClick: onClose,
38
- className: "p-2 text-muted-foreground hover:text-foreground transition-colors",
38
+ className: "p-2 -m-2 text-muted-foreground/50 hover:text-foreground transition-colors duration-200",
39
39
  "aria-label": "Close",
40
- children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
40
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", strokeWidth: 1.5 })
41
41
  }
42
42
  )
43
43
  ]
@@ -50,9 +50,9 @@ function Lightbox({
50
50
  e.stopPropagation();
51
51
  onPrevious();
52
52
  },
53
- className: "fixed left-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors",
53
+ 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",
54
54
  "aria-label": "Previous image",
55
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-8 w-8" })
55
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-6 w-6", strokeWidth: 1.5 })
56
56
  }
57
57
  ),
58
58
  showNavigation && selectedIndex < images.length - 1 && /* @__PURE__ */ jsx(
@@ -62,9 +62,9 @@ function Lightbox({
62
62
  e.stopPropagation();
63
63
  onNext();
64
64
  },
65
- className: "fixed right-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors",
65
+ 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",
66
66
  "aria-label": "Next image",
67
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-8 w-8" })
67
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-6 w-6", strokeWidth: 1.5 })
68
68
  }
69
69
  ),
70
70
  /* @__PURE__ */ jsx("div", { className: "fixed inset-0 flex items-center justify-center p-16", children: /* @__PURE__ */ jsx(
@@ -72,16 +72,16 @@ function Lightbox({
72
72
  {
73
73
  src: current.src,
74
74
  alt: current.label,
75
- className: "max-w-full max-h-full object-contain",
75
+ className: "max-w-full max-h-full object-contain rounded-sm shadow-2xl",
76
76
  onClick: (e) => e.stopPropagation()
77
77
  }
78
78
  ) }),
79
79
  /* @__PURE__ */ jsx(
80
80
  "div",
81
81
  {
82
- className: "fixed bottom-0 left-0 right-0 z-10 p-4 text-center bg-background/80 backdrop-blur-sm",
82
+ className: "fixed bottom-0 left-0 right-0 z-10 px-6 py-5 text-center",
83
83
  onClick: (e) => e.stopPropagation(),
84
- children: /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: current.label })
84
+ children: /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-muted-foreground/50 tracking-wide", children: current.label })
85
85
  }
86
86
  )
87
87
  ]
@@ -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\"\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-4 py-3 bg-background/80 backdrop-blur-sm\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"font-mono text-xs text-muted-foreground tabular-nums\">\n {String(selectedIndex + 1).padStart(2, '0')} / {String(images.length).padStart(2, '0')}\n </div>\n <button\n onClick={onClose}\n className=\"p-2 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Close\"\n >\n <X className=\"h-5 w-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-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Previous image\"\n >\n <ChevronLeft className=\"h-8 w-8\" />\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-4 top-1/2 -translate-y-1/2 z-10 p-2 text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Next image\"\n >\n <ChevronRight className=\"h-8 w-8\" />\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\"\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n\n {/* Caption */}\n <div\n className=\"fixed bottom-0 left-0 right-0 z-10 p-4 text-center bg-background/80 backdrop-blur-sm\"\n onClick={(e) => e.stopPropagation()}\n >\n <span className=\"font-mono text-xs text-muted-foreground\">\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,wDACZ,UAAA;AAAA,kBAAA,OAAO,gBAAgB,CAAC,EAAE,SAAS,GAAG,GAAG;AAAA,kBAAE;AAAA,kBAAI,OAAO,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG;AAAA,gBAAA,GACvF;AAAA,gBACA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,SAAS;AAAA,oBACT,WAAU;AAAA,oBACV,cAAW;AAAA,oBAEX,UAAA,oBAAC,GAAA,EAAE,WAAU,UAAA,CAAU;AAAA,kBAAA;AAAA,gBAAA;AAAA,cACzB;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,UAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAKpC,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,UAAA,CAAU;AAAA,YAAA;AAAA,UAAA;AAAA,UAKtC,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,2CACb,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-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,6 +1,6 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { Image } from "lucide-react";
3
+ import { ImageOff } from "lucide-react";
4
4
  import { cn } from "../../../lib/utils.js";
5
5
  function LoadingImage({
6
6
  className,
@@ -9,21 +9,22 @@ function LoadingImage({
9
9
  }) {
10
10
  const [isLoading, setIsLoading] = useState(true);
11
11
  const [hasError, setHasError] = useState(false);
12
- return /* @__PURE__ */ jsxs("div", { className: cn("relative", wrapperClassName), children: [
13
- isLoading && !hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-muted/30 animate-pulse flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-8 h-8 border border-border/50 rounded-sm" }) }),
14
- hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-muted/20 flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
15
- /* @__PURE__ */ jsx(Image, { className: "h-5 w-5 text-muted-foreground/40 mx-auto" }),
16
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground/40 mt-1.5 block font-mono", children: "failed" })
12
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative overflow-hidden rounded-sm bg-muted/20", wrapperClassName), children: [
13
+ isLoading && !hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-r from-transparent via-muted/40 to-transparent animate-shimmer" }) }),
14
+ hasError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-muted/10 flex items-center justify-center backdrop-blur-sm", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-1", children: [
15
+ /* @__PURE__ */ jsx(ImageOff, { className: "h-4 w-4 text-muted-foreground/30 mx-auto", strokeWidth: 1.5 }),
16
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/30 block font-mono uppercase tracking-wider", children: "unavailable" })
17
17
  ] }) }),
18
18
  /* @__PURE__ */ jsx(
19
19
  "img",
20
20
  {
21
21
  ...props,
22
22
  className: cn(
23
- className,
24
- "transition-opacity duration-500 ease-out-expo",
25
- isLoading && "opacity-0",
26
- hasError && "opacity-0"
23
+ "w-full h-full",
24
+ "transition-all duration-500 ease-out",
25
+ isLoading ? "opacity-0 scale-[1.02]" : "opacity-100 scale-100",
26
+ hasError && "opacity-0",
27
+ className
27
28
  ),
28
29
  onLoad: (e) => {
29
30
  var _a;
@@ -1 +1 @@
1
- {"version":3,"file":"loading-image.js","sources":["../../../../../src/components/gallery/components/loading-image.tsx"],"sourcesContent":["import { useState, ImgHTMLAttributes } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport function LoadingImage({\n className,\n wrapperClassName,\n ...props\n}: ImgHTMLAttributes<HTMLImageElement> & { wrapperClassName?: string }) {\n const [isLoading, setIsLoading] = useState(true);\n const [hasError, setHasError] = useState(false);\n\n return (\n <div className={cn(\"relative\", wrapperClassName)}>\n {isLoading && !hasError && (\n <div className=\"absolute inset-0 bg-muted/30 animate-pulse flex items-center justify-center\">\n <div className=\"w-8 h-8 border border-border/50 rounded-sm\" />\n </div>\n )}\n {hasError && (\n <div className=\"absolute inset-0 bg-muted/20 flex items-center justify-center\">\n <div className=\"text-center\">\n <Image className=\"h-5 w-5 text-muted-foreground/40 mx-auto\" />\n <span className=\"text-xs text-muted-foreground/40 mt-1.5 block font-mono\">failed</span>\n </div>\n </div>\n )}\n <img\n {...props}\n className={cn(\n className,\n \"transition-opacity duration-500 ease-out-expo\",\n isLoading && \"opacity-0\",\n hasError && \"opacity-0\"\n )}\n onLoad={(e) => {\n setIsLoading(false);\n props.onLoad?.(e);\n }}\n onError={(e) => {\n setIsLoading(false);\n setHasError(true);\n props.onError?.(e);\n }}\n />\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAIO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAwE;AACtE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,8BACG,OAAA,EAAI,WAAW,GAAG,YAAY,gBAAgB,GAC5C,UAAA;AAAA,IAAA,aAAa,CAAC,YACb,oBAAC,OAAA,EAAI,WAAU,+EACb,UAAA,oBAAC,OAAA,EAAI,WAAU,6CAAA,CAA6C,EAAA,CAC9D;AAAA,IAED,gCACE,OAAA,EAAI,WAAU,iEACb,UAAA,qBAAC,OAAA,EAAI,WAAU,eACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAM,WAAU,2CAAA,CAA2C;AAAA,MAC5D,oBAAC,QAAA,EAAK,WAAU,2DAA0D,UAAA,SAAA,CAAM;AAAA,IAAA,EAAA,CAClF,EAAA,CACF;AAAA,IAEF;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,YAAY;AAAA,QAAA;AAAA,QAEd,QAAQ,CAAC,MAAM;;AACb,uBAAa,KAAK;AAClB,sBAAM,WAAN,+BAAe;AAAA,QACjB;AAAA,QACA,SAAS,CAAC,MAAM;;AACd,uBAAa,KAAK;AAClB,sBAAY,IAAI;AAChB,sBAAM,YAAN,+BAAgB;AAAA,QAClB;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
1
+ {"version":3,"file":"loading-image.js","sources":["../../../../../src/components/gallery/components/loading-image.tsx"],"sourcesContent":["import { useState, ImgHTMLAttributes } from \"react\";\nimport { ImageOff } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nexport function LoadingImage({\n className,\n wrapperClassName,\n ...props\n}: ImgHTMLAttributes<HTMLImageElement> & { wrapperClassName?: string }) {\n const [isLoading, setIsLoading] = useState(true);\n const [hasError, setHasError] = useState(false);\n\n return (\n <div className={cn(\"relative overflow-hidden rounded-sm bg-muted/20\", wrapperClassName)}>\n {isLoading && !hasError && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/40 to-transparent animate-shimmer\" />\n </div>\n )}\n {hasError && (\n <div className=\"absolute inset-0 bg-muted/10 flex items-center justify-center backdrop-blur-sm\">\n <div className=\"text-center space-y-1\">\n <ImageOff className=\"h-4 w-4 text-muted-foreground/30 mx-auto\" strokeWidth={1.5} />\n <span className=\"text-[10px] text-muted-foreground/30 block font-mono uppercase tracking-wider\">\n unavailable\n </span>\n </div>\n </div>\n )}\n <img\n {...props}\n className={cn(\n \"w-full h-full\",\n \"transition-all duration-500 ease-out\",\n isLoading ? \"opacity-0 scale-[1.02]\" : \"opacity-100 scale-100\",\n hasError && \"opacity-0\",\n className\n )}\n onLoad={(e) => {\n setIsLoading(false);\n props.onLoad?.(e);\n }}\n onError={(e) => {\n setIsLoading(false);\n setHasError(true);\n props.onError?.(e);\n }}\n />\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAIO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAwE;AACtE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,8BACG,OAAA,EAAI,WAAW,GAAG,mDAAmD,gBAAgB,GACnF,UAAA;AAAA,IAAA,aAAa,CAAC,YACb,oBAAC,OAAA,EAAI,WAAU,qDACb,UAAA,oBAAC,OAAA,EAAI,WAAU,iGAAA,CAAiG,EAAA,CAClH;AAAA,IAED,gCACE,OAAA,EAAI,WAAU,kFACb,UAAA,qBAAC,OAAA,EAAI,WAAU,yBACb,UAAA;AAAA,MAAA,oBAAC,UAAA,EAAS,WAAU,4CAA2C,aAAa,KAAK;AAAA,MACjF,oBAAC,QAAA,EAAK,WAAU,iFAAgF,UAAA,cAAA,CAEhG;AAAA,IAAA,EAAA,CACF,EAAA,CACF;AAAA,IAEF;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,YAAY,2BAA2B;AAAA,UACvC,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAEF,QAAQ,CAAC,MAAM;;AACb,uBAAa,KAAK;AAClB,sBAAM,WAAN,+BAAe;AAAA,QACjB;AAAA,QACA,SAAS,CAAC,MAAM;;AACd,uBAAa,KAAK;AAClB,sBAAY,IAAI;AAChB,sBAAM,YAAN,+BAAgB;AAAA,QAClB;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
@@ -1,7 +1,21 @@
1
1
  import { useMemo } from "react";
2
2
  import { useTheme } from "next-themes";
3
+ import { useParams } from "react-router-dom";
3
4
  import { useDirectory } from "../../../plugin/src/client.js";
4
5
  import { minimatch } from "minimatch";
6
+ function collectAllImages(entry) {
7
+ if (entry.type === "file") {
8
+ if (entry.name.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)) {
9
+ return [entry];
10
+ }
11
+ return [];
12
+ }
13
+ const images = [];
14
+ for (const child of entry.children || []) {
15
+ images.push(...collectAllImages(child));
16
+ }
17
+ return images;
18
+ }
5
19
  function sortPathsNumerically(paths) {
6
20
  paths.sort((a, b) => {
7
21
  const nums = (s) => (s.match(/\d+/g) || []).map(Number);
@@ -52,18 +66,28 @@ function useGalleryImages({
52
66
  page = 0
53
67
  }) {
54
68
  const { resolvedTheme } = useTheme();
69
+ const { "*": routePath = "" } = useParams();
70
+ const currentDir = routePath.replace(/\/[^/]+\.mdx$/i, "").replace(/\/$/, "") || ".";
55
71
  let resolvedPath = path;
56
- const { directory } = useDirectory(resolvedPath);
72
+ if (path == null ? void 0 : path.startsWith("./")) {
73
+ const relativePart = path.slice(2);
74
+ resolvedPath = currentDir === "." ? relativePart : `${currentDir}/${relativePart}`;
75
+ } else if (path && !path.startsWith("/") && !path.includes("/")) {
76
+ resolvedPath = currentDir === "." ? path : `${currentDir}/${path}`;
77
+ }
78
+ const directoryPath = resolvedPath || ".";
79
+ const { directory } = useDirectory(directoryPath);
57
80
  const paths = useMemo(() => {
58
81
  if (!directory) return [];
59
- const imageChildren = directory.children.filter((child) => {
60
- return !!child.name.match(/\.(png|jpeg|gif|svg|webp)$/i) && child.type === "file";
61
- });
62
- let imagePaths = imageChildren.map((child) => child.path);
82
+ let imagePaths;
63
83
  if (globs && globs.length > 0) {
64
- imagePaths = imagePaths.filter((p) => {
65
- return globs.some((glob) => minimatch(p.split("/").pop() || "", glob));
84
+ const allImages = collectAllImages(directory);
85
+ imagePaths = allImages.map((img) => img.path).filter((p) => globs.some((glob) => minimatch(p, glob, { matchBase: true })));
86
+ } else {
87
+ const imageChildren = directory.children.filter((child) => {
88
+ return !!child.name.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i) && child.type === "file";
66
89
  });
90
+ imagePaths = imageChildren.map((child) => child.path);
67
91
  }
68
92
  sortPathsNumerically(imagePaths);
69
93
  let filtered = filterPathsByTheme(imagePaths, resolvedTheme);
@@ -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 { useDirectory } from \"../../../../plugin/src/client\";\nimport { FileEntry } from \"../../../../plugin/src/lib\";\nimport { minimatch } from \"minimatch\";\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\n let resolvedPath = path;\n\n const { directory } = useDirectory(resolvedPath);\n\n const paths = useMemo(() => {\n if (!directory) return [];\n\n const imageChildren = directory.children.filter((child): child is FileEntry => {\n return !!child.name.match(/\\.(png|jpeg|gif|svg|webp)$/i) && child.type === \"file\";\n });\n\n let imagePaths = imageChildren.map(child => child.path);\n\n if (globs && globs.length > 0) {\n imagePaths = imagePaths.filter(p => {\n return globs.some(glob => minimatch(p.split('/').pop() || '', glob));\n });\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":";;;;AAMA,SAAS,qBAAqB,OAAuB;AACnD,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,UAAM,OAAO,CAAC,OAAe,EAAE,MAAM,MAAM,KAAK,CAAA,GAAI,IAAI,MAAM;AAC9D,UAAM,KAAK,KAAK,CAAC;AACjB,UAAM,KAAK,KAAK,CAAC;AACjB,UAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,GAAG,MAAM;AACzC,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,YAAM,QAAQ,GAAG,CAAC,KAAK,MAAM,GAAG,CAAC,KAAK;AACtC,UAAI,SAAS,EAAG,QAAO;AAAA,IACzB;AACA,WAAO,EAAE,cAAc,CAAC;AAAA,EAC1B,CAAC;AACH;AAEA,SAAS,mBAAmB,OAAiB,OAAqC;AAChF,QAAM,iCAAiB,IAAA;AAEvB,QAAM,QAAQ,CAAA,SAAQ;AACpB,QAAI,KAAK,SAAS,YAAY,GAAG;AAC/B,YAAM,WAAW,KAAK,QAAQ,cAAc,EAAE;AAC9C,YAAM,QAAQ,WAAW,IAAI,QAAQ,KAAK,CAAA;AAC1C,YAAM,QAAQ;AACd,iBAAW,IAAI,UAAU,KAAK;AAAA,IAChC,WAAW,KAAK,SAAS,WAAW,GAAG;AACrC,YAAM,WAAW,KAAK,QAAQ,aAAa,EAAE;AAC7C,YAAM,QAAQ,WAAW,IAAI,QAAQ,KAAK,CAAA;AAC1C,YAAM,OAAO;AACb,iBAAW,IAAI,UAAU,KAAK;AAAA,IAChC,OAAO;AACL,iBAAW,IAAI,MAAM,EAAE,UAAU,MAAM;AAAA,IACzC;AAAA,EACF,CAAC;AAED,QAAM,WAAqB,CAAA;AAC3B,aAAW,QAAQ,CAAC,OAAO,aAAa;AACtC,QAAI,MAAM,UAAU;AAClB,eAAS,KAAK,MAAM,QAAQ;AAAA,IAC9B,OAAO;AACL,YAAM,SAAS,UAAU;AACzB,YAAM,gBAAgB,SAAS,MAAM,OAAO,MAAM;AAClD,YAAM,eAAe,SAAS,MAAM,QAAQ,MAAM;AAClD,eAAS,KAAK,iBAAiB,gBAAgB,QAAQ;AAAA,IACzD;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAGO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,OAAO;AACT,GAKG;AACD,QAAM,EAAE,cAAA,IAAkB,SAAA;AAE1B,MAAI,eAAe;AAEnB,QAAM,EAAE,UAAA,IAAc,aAAa,YAAY;AAE/C,QAAM,QAAQ,QAAQ,MAAM;AAC1B,QAAI,CAAC,UAAW,QAAO,CAAA;AAEvB,UAAM,gBAAgB,UAAU,SAAS,OAAO,CAAC,UAA8B;AAC7E,aAAO,CAAC,CAAC,MAAM,KAAK,MAAM,6BAA6B,KAAK,MAAM,SAAS;AAAA,IAC7E,CAAC;AAED,QAAI,aAAa,cAAc,IAAI,CAAA,UAAS,MAAM,IAAI;AAEtD,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,mBAAa,WAAW,OAAO,CAAA,MAAK;AAClC,eAAO,MAAM,KAAK,CAAA,SAAQ,UAAU,EAAE,MAAM,GAAG,EAAE,IAAA,KAAS,IAAI,IAAI,CAAC;AAAA,MACrE,CAAC;AAAA,IACH;AAEA,yBAAqB,UAAU;AAC/B,QAAI,WAAW,mBAAmB,YAAY,aAAa;AAE3D,QAAI,OAAO;AACT,iBAAW,SAAS,MAAM,OAAO,QAAQ,OAAO,KAAK,KAAK;AAAA,IAC5D;AAEA,WAAO;AAAA,EACT,GAAG,CAAC,WAAW,OAAO,eAAe,OAAO,IAAI,CAAC;AAEjD,SAAO;AAAA,IACL;AAAA,IACA,WAAW,CAAC;AAAA,IACZ,SAAS,cAAc,UAAa,MAAM,WAAW;AAAA,EAAA;AAEzD;"}
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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"}
@@ -37,43 +37,50 @@ function Gallery({
37
37
  [paths]
38
38
  );
39
39
  if (isLoading) {
40
- return /* @__PURE__ */ jsx("div", { className: "not-prose py-4 md:py-6", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2 sm:gap-4 max-w-[var(--gallery-width)] mx-auto", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx(
40
+ return /* @__PURE__ */ jsx("figure", { className: "not-prose py-6 md:py-8", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx(
41
41
  "div",
42
42
  {
43
- className: "aspect-[4/3] bg-muted/30 animate-pulse",
44
- style: { animationDelay: `${i * 100}ms` }
43
+ className: "aspect-square rounded-sm bg-muted/20 relative overflow-hidden",
44
+ children: /* @__PURE__ */ jsx(
45
+ "div",
46
+ {
47
+ className: "absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer",
48
+ style: { animationDelay: `${i * 150}ms` }
49
+ }
50
+ )
45
51
  },
46
52
  i
47
53
  )) }) });
48
54
  }
49
55
  if (isEmpty) {
50
- return /* @__PURE__ */ jsx("div", { className: "not-prose py-16 text-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-3 text-muted-foreground/60", children: [
51
- /* @__PURE__ */ jsx(Image, { className: "h-4 w-4" }),
52
- /* @__PURE__ */ jsx("span", { className: "font-mono text-sm tracking-wide", children: "no image(s) found" })
56
+ return /* @__PURE__ */ jsx("figure", { className: "not-prose py-12 text-center", children: /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2.5 text-muted-foreground/40", children: [
57
+ /* @__PURE__ */ jsx(Image, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }),
58
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs uppercase tracking-widest", children: "No images" })
53
59
  ] }) });
54
60
  }
55
61
  return /* @__PURE__ */ jsxs(Fragment, { children: [
56
- /* @__PURE__ */ jsxs("div", { className: "rounded-lg not-prose flex flex-col gap-0 relative p-4 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]", 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: [
57
63
  /* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
58
- /* @__PURE__ */ jsxs(Carousel, { children: [
59
- /* @__PURE__ */ jsx(CarouselContent, { children: images.map((img, index) => /* @__PURE__ */ jsx(
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(
60
66
  CarouselItem,
61
67
  {
62
- className: "mx-auto md:basis-1/2 lg:basis-1/3 cursor-pointer transition-transform duration-700 ease-out-expo hover:scale-[1.02]",
68
+ className: `pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? "flex-none" : ""}`,
63
69
  onClick: () => lightbox.open(index),
64
- children: /* @__PURE__ */ jsx(
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(
65
71
  LoadingImage,
66
72
  {
67
73
  src: img.src,
68
- alt: img.label
74
+ alt: img.label,
75
+ className: "object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
69
76
  }
70
- )
77
+ ) })
71
78
  },
72
79
  index
73
80
  )) }),
74
81
  images.length > 3 && /* @__PURE__ */ jsxs(Fragment, { children: [
75
- /* @__PURE__ */ jsx(CarouselPrevious, {}),
76
- /* @__PURE__ */ jsx(CarouselNext, {})
82
+ /* @__PURE__ */ jsx(CarouselPrevious, { className: "left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" }),
83
+ /* @__PURE__ */ jsx(CarouselNext, { className: "right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" })
77
84
  ] })
78
85
  ] }),
79
86
  /* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
@@ -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 <div className=\"not-prose py-4 md:py-6\">\n <div className=\"grid grid-cols-3 gap-2 sm:gap-4 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-[4/3] bg-muted/30 animate-pulse\"\n style={{ animationDelay: `${i * 100}ms` }}\n />\n ))}\n </div>\n </div>\n );\n }\n\n if (isEmpty) {\n return (\n <div className=\"not-prose py-16 text-center\">\n <div className=\"inline-flex items-center gap-3 text-muted-foreground/60\">\n <Image className=\"h-4 w-4\" />\n <span className=\"font-mono text-sm tracking-wide\">no image(s) found</span>\n </div>\n </div>\n );\n }\n\n return (\n <>\n <div className=\"rounded-lg not-prose flex flex-col gap-0 relative p-4 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]\">\n <FigureHeader title={title} subtitle={subtitle} />\n\n <Carousel>\n <CarouselContent>\n {images.map((img, index) => (\n <CarouselItem \n key={index} \n className=\"mx-auto md:basis-1/2 lg:basis-1/3 cursor-pointer transition-transform duration-700 ease-out-expo hover:scale-[1.02]\"\n // wrapperClassName=\"h-full\"\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n />\n </CarouselItem>\n ))}\n </CarouselContent>\n\n {images.length > 3 && (\n <>\n <CarouselPrevious />\n <CarouselNext />\n </>\n )} \n </Carousel>\n\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\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":";;;;;;;;;;AAgBA,SAAS,cAAc,MAAsB;AAC3C,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,SAAS;AAC1C,SAAO,SACJ,QAAQ,mCAAmC,EAAE,EAC7C,QAAQ,SAAS,GAAG,EACpB,QAAQ,QAAQ,GAAG,EACnB,KAAA;AACL;AAEA,SAAS,YAAY,MAAsB;AACzC,SAAO,QAAQ,IAAI;AACrB;AAEA,SAAwB,QAAQ;AAAA,EAC9B;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,OAAO;AACT,GASG;AACD,QAAM,EAAE,OAAO,WAAW,QAAA,IAAY,iBAAiB;AAAA,IACrD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AAED,QAAM,WAAW,YAAY,MAAM,MAAM;AAEzC,QAAM,SAA0B;AAAA,IAAQ,MACtC,MAAM,IAAI,CAAA,OAAM,EAAE,KAAK,YAAY,CAAC,GAAG,OAAO,cAAc,CAAC,IAAI;AAAA,IACjE,CAAC,KAAK;AAAA,EAAA;AAGR,MAAI,WAAW;AACb,+BACG,OAAA,EAAI,WAAU,0BACb,UAAA,oBAAC,SAAI,WAAU,wEACZ,UAAA,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,MACrB;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QACV,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,KAAA;AAAA,MAAK;AAAA,MAFnC;AAAA,IAAA,CAIR,GACH,EAAA,CACF;AAAA,EAEJ;AAEA,MAAI,SAAS;AACX,+BACG,OAAA,EAAI,WAAU,+BACb,UAAA,qBAAC,OAAA,EAAI,WAAU,2DACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAM,WAAU,UAAA,CAAU;AAAA,MAC3B,oBAAC,QAAA,EAAK,WAAU,mCAAkC,UAAA,oBAAA,CAAiB;AAAA,IAAA,EAAA,CACrE,EAAA,CACF;AAAA,EAEJ;AAEA,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,uIACb,UAAA;AAAA,MAAA,oBAAC,cAAA,EAAa,OAAc,SAAA,CAAoB;AAAA,2BAE/C,UAAA,EACC,UAAA;AAAA,QAAA,oBAAC,iBAAA,EACE,UAAA,OAAO,IAAI,CAAC,KAAK,UAChB;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,WAAU;AAAA,YAEV,SAAS,MAAM,SAAS,KAAK,KAAK;AAAA,YAElC,UAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,KAAK,IAAI;AAAA,gBACT,KAAK,IAAI;AAAA,cAAA;AAAA,YAAA;AAAA,UACX;AAAA,UARK;AAAA,QAAA,CAUR,GACH;AAAA,QAEC,OAAO,SAAS,KACf,qBAAA,UAAA,EACE,UAAA;AAAA,UAAA,oBAAC,kBAAA,EAAiB;AAAA,8BACjB,cAAA,CAAA,CAAa;AAAA,QAAA,EAAA,CAChB;AAAA,MAAA,GAEJ;AAAA,MAEA,oBAAC,eAAA,EAAc,SAAkB,OAAO,aAAA,CAAc;AAAA,IAAA,GACxD;AAAA,IAEC,SAAS,UAAU,SAAS,kBAAkB,QAC7C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,eAAe,SAAS;AAAA,QACxB,SAAS,SAAS;AAAA,QAClB,YAAY,SAAS;AAAA,QACrB,QAAQ,SAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EACnB,GAEJ;AAEJ;"}
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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"}
@@ -3,14 +3,16 @@ import { 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
+ import siteConfig from "virtual:veslx-config";
6
7
  function Header({ slideControls } = {}) {
8
+ const config = siteConfig;
7
9
  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: [
8
10
  /* @__PURE__ */ jsx("nav", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
9
11
  Link,
10
12
  {
11
13
  to: "/",
12
14
  className: "rounded-lg font-mono py-1.5 text-sm font-medium text-muted-foreground hover:underline",
13
- children: "pl"
15
+ children: config.name
14
16
  }
15
17
  ) }),
16
18
  /* @__PURE__ */ jsx("div", { className: "flex-1" }),
@@ -40,10 +42,10 @@ function Header({ slideControls } = {}) {
40
42
  )
41
43
  ] }),
42
44
  /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-2", children: [
43
- /* @__PURE__ */ jsx(
45
+ config.github && /* @__PURE__ */ jsx(
44
46
  Link,
45
47
  {
46
- to: "https://github.com/eoinmurray/pinglab",
48
+ to: `https://github.com/${config.github}`,
47
49
  target: "_blank",
48
50
  className: "text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
49
51
  "aria-label": "GitHub",