@grove-dev/astro 0.1.4 → 0.2.2

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 (246) hide show
  1. package/dist/index.d.ts +4 -25
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +56 -22
  4. package/dist/index.js.map +1 -1
  5. package/dist/layouts/index.d.ts +25 -0
  6. package/dist/layouts/index.d.ts.map +1 -0
  7. package/dist/layouts/index.js +25 -0
  8. package/dist/layouts/index.js.map +1 -0
  9. package/dist/lib/display.d.ts +33 -0
  10. package/dist/lib/display.d.ts.map +1 -0
  11. package/dist/lib/display.js +104 -0
  12. package/dist/lib/display.js.map +1 -0
  13. package/dist/lib/format.d.ts +30 -0
  14. package/dist/lib/format.d.ts.map +1 -0
  15. package/dist/lib/format.js +73 -0
  16. package/dist/lib/format.js.map +1 -0
  17. package/dist/lib/index.d.ts +26 -0
  18. package/dist/lib/index.d.ts.map +1 -0
  19. package/dist/lib/index.js +26 -0
  20. package/dist/lib/index.js.map +1 -0
  21. package/dist/lib/lenses.d.ts +96 -0
  22. package/dist/lib/lenses.d.ts.map +1 -0
  23. package/dist/lib/lenses.js +181 -0
  24. package/dist/lib/lenses.js.map +1 -0
  25. package/dist/lib/repo.d.ts +30 -0
  26. package/dist/lib/repo.d.ts.map +1 -0
  27. package/dist/lib/repo.js +44 -0
  28. package/dist/lib/repo.js.map +1 -0
  29. package/dist/lib/scores.d.ts +38 -0
  30. package/dist/lib/scores.d.ts.map +1 -0
  31. package/dist/lib/scores.js +83 -0
  32. package/dist/lib/scores.js.map +1 -0
  33. package/dist/lib/search.d.ts +199 -0
  34. package/dist/lib/search.d.ts.map +1 -0
  35. package/dist/lib/search.js +357 -0
  36. package/dist/lib/search.js.map +1 -0
  37. package/dist/lib/taxonomy-counts.d.ts +25 -0
  38. package/dist/lib/taxonomy-counts.d.ts.map +1 -0
  39. package/dist/lib/taxonomy-counts.js +41 -0
  40. package/dist/lib/taxonomy-counts.js.map +1 -0
  41. package/package.json +7 -7
  42. package/src/components/AppSection.astro +83 -0
  43. package/src/components/AppsIndexRow.astro +204 -0
  44. package/src/components/AppsPagination.astro +126 -0
  45. package/src/components/CategoryGrid.astro +86 -17
  46. package/src/components/ContributorsGrid.astro +149 -0
  47. package/src/components/DecisionRow.astro +89 -0
  48. package/src/components/ExploreByCategory.astro +67 -0
  49. package/src/components/ExploreByStack.astro +72 -0
  50. package/src/components/Hero.astro +246 -0
  51. package/src/components/Icon.astro +223 -0
  52. package/src/components/ItemCard.astro +289 -30
  53. package/src/components/MinimalAbout.astro +82 -0
  54. package/src/components/OriginalCollection.astro +116 -0
  55. package/src/components/RefinePanel.astro +197 -0
  56. package/src/components/ScoreBars.astro +92 -22
  57. package/src/components/SmartLensTabs.astro +71 -0
  58. package/src/components/StackGrid.astro +132 -0
  59. package/src/components/WhyThisExists.astro +116 -0
  60. package/src/index.ts +61 -14
  61. package/src/layouts/BaseLayout.astro +227 -15
  62. package/src/layouts/Container.astro +57 -0
  63. package/src/layouts/Footer.astro +235 -0
  64. package/src/layouts/Header.astro +159 -0
  65. package/src/layouts/SectionHeader.astro +58 -0
  66. package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
  67. package/src/layouts/ThemeToggle.astro +110 -0
  68. package/src/layouts/astro-shim.d.ts +29 -0
  69. package/src/layouts/index.ts +24 -0
  70. package/src/lib/display.ts +118 -0
  71. package/src/lib/format.ts +65 -0
  72. package/src/lib/index.ts +26 -0
  73. package/src/lib/lenses.ts +241 -0
  74. package/src/lib/repo.ts +44 -0
  75. package/src/lib/scores.ts +94 -0
  76. package/src/lib/search.ts +481 -0
  77. package/src/lib/taxonomy-counts.ts +60 -0
  78. package/src/styles.css +366 -638
  79. package/templates/default/.astro/content.d.ts +155 -0
  80. package/templates/default/.astro/settings.json +5 -0
  81. package/templates/default/.astro/types.d.ts +2 -0
  82. package/templates/default/astro.config.mjs +8 -6
  83. package/templates/default/content/pages/about.md +66 -0
  84. package/templates/default/content/pages/methodology.md +103 -0
  85. package/templates/default/data/generated/records.full.json +8 -0
  86. package/templates/default/data/generated/records.index.json +7 -0
  87. package/templates/default/data/generated/records.json +8 -0
  88. package/templates/default/data/generated/site-config.json +31 -0
  89. package/templates/default/data/records/.gitkeep +0 -0
  90. package/templates/default/dist/_astro/BaseLayout.DfpwR3jc.css +1 -0
  91. package/templates/default/dist/about/index.html +165 -0
  92. package/templates/default/dist/icons/platforms/android.svg +1 -0
  93. package/templates/default/dist/icons/platforms/apple-dark.svg +1 -0
  94. package/templates/default/dist/icons/platforms/apple-light.svg +1 -0
  95. package/templates/default/dist/icons/platforms/chrome.svg +1 -0
  96. package/templates/default/dist/icons/platforms/chromeos.svg +5 -0
  97. package/templates/default/dist/icons/platforms/desktop.svg +4 -0
  98. package/templates/default/dist/icons/platforms/embedded.svg +5 -0
  99. package/templates/default/dist/icons/platforms/ios.svg +1 -0
  100. package/templates/default/dist/icons/platforms/linux.svg +1 -0
  101. package/templates/default/dist/icons/platforms/macos.svg +1 -0
  102. package/templates/default/dist/icons/platforms/ubuntu.svg +1 -0
  103. package/templates/default/dist/icons/platforms/web.svg +5 -0
  104. package/templates/default/dist/icons/platforms/windows.svg +6 -0
  105. package/templates/default/dist/icons/stacks/android.svg +1 -0
  106. package/templates/default/dist/icons/stacks/apple-dark.svg +1 -0
  107. package/templates/default/dist/icons/stacks/apple-light.svg +1 -0
  108. package/templates/default/dist/icons/stacks/capacitor.svg +14 -0
  109. package/templates/default/dist/icons/stacks/dart.svg +1 -0
  110. package/templates/default/dist/icons/stacks/firebase.svg +1 -0
  111. package/templates/default/dist/icons/stacks/flutter.svg +1 -0
  112. package/templates/default/dist/icons/stacks/graphql.svg +1 -0
  113. package/templates/default/dist/icons/stacks/ionic.svg +4 -0
  114. package/templates/default/dist/icons/stacks/java.svg +1 -0
  115. package/templates/default/dist/icons/stacks/javascript.svg +1 -0
  116. package/templates/default/dist/icons/stacks/kotlin.svg +1 -0
  117. package/templates/default/dist/icons/stacks/mongodb.svg +1 -0
  118. package/templates/default/dist/icons/stacks/nodejs.svg +1 -0
  119. package/templates/default/dist/icons/stacks/python.svg +1 -0
  120. package/templates/default/dist/icons/stacks/react-native.svg +1 -0
  121. package/templates/default/dist/icons/stacks/react.svg +1 -0
  122. package/templates/default/dist/icons/stacks/rust.svg +1 -0
  123. package/templates/default/dist/icons/stacks/solidity.svg +1 -0
  124. package/templates/default/dist/icons/stacks/swift.svg +1 -0
  125. package/templates/default/dist/icons/stacks/tensorflow.svg +1 -0
  126. package/templates/default/dist/icons/stacks/typescript.svg +1 -0
  127. package/templates/default/dist/index.html +55 -0
  128. package/templates/default/dist/llms-full.txt +14 -0
  129. package/templates/default/dist/llms.txt +11 -0
  130. package/templates/default/dist/og-image.svg +48 -0
  131. package/templates/default/dist/projects/index.html +61 -0
  132. package/templates/default/dist/robots.txt +53 -0
  133. package/templates/default/dist/sitemap.xml +21 -0
  134. package/templates/default/dist/submit/index.html +69 -0
  135. package/templates/default/grove.config.ts +36 -0
  136. package/templates/default/node_modules/.bin/astro +21 -0
  137. package/templates/default/node_modules/.bin/astro-check +21 -0
  138. package/templates/default/node_modules/.bin/grove +21 -0
  139. package/templates/default/node_modules/.bin/jiti +21 -0
  140. package/templates/default/node_modules/.bin/prettier +21 -0
  141. package/templates/default/node_modules/.bin/rollup +21 -0
  142. package/templates/default/node_modules/.bin/tsc +21 -0
  143. package/templates/default/node_modules/.bin/tsserver +21 -0
  144. package/templates/default/node_modules/.bin/tsx +21 -0
  145. package/templates/default/node_modules/.bin/vite +21 -0
  146. package/templates/default/node_modules/.bin/yaml +21 -0
  147. package/templates/default/node_modules/.vite/deps/_metadata.json +79 -0
  148. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +433 -0
  149. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +7 -0
  150. package/templates/default/node_modules/.vite/deps/astro___aria-query.js +5 -0
  151. package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
  152. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +5 -0
  153. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
  154. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +9 -0
  155. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
  156. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +241 -0
  157. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +7 -0
  158. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +1592 -0
  159. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +7 -0
  160. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  161. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  162. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +186 -0
  163. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +7 -0
  164. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +48 -0
  165. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +7 -0
  166. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +6779 -0
  167. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +7 -0
  168. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +33 -0
  169. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +7 -0
  170. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +88 -0
  171. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +7 -0
  172. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +72 -0
  173. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +7 -0
  174. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +3757 -0
  175. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +7 -0
  176. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +54 -0
  177. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +7 -0
  178. package/templates/default/node_modules/.vite/deps/package.json +3 -0
  179. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +213 -0
  180. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +7 -0
  181. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +525 -0
  182. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +7 -0
  183. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +1020 -0
  184. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +7 -0
  185. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +147 -0
  186. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +7 -0
  187. package/templates/default/package.json +6 -5
  188. package/templates/default/public/llms-full.txt +14 -0
  189. package/templates/default/public/llms.txt +11 -0
  190. package/templates/default/src/components/layout/BaseLayout.astro +10 -57
  191. package/templates/default/src/components/layout/Footer.astro +16 -134
  192. package/templates/default/src/components/layout/Header.astro +25 -126
  193. package/templates/default/src/data/records.ts +161 -0
  194. package/templates/default/src/lib/markdown.ts +171 -0
  195. package/templates/default/src/pages/about.astro +95 -429
  196. package/templates/default/src/pages/index.astro +258 -61
  197. package/templates/default/src/pages/projects/[slug].astro +480 -0
  198. package/templates/default/src/pages/projects/index.astro +193 -0
  199. package/templates/default/src/pages/sitemap.xml.ts +73 -61
  200. package/templates/default/src/pages/submit.astro +122 -411
  201. package/templates/default/src/styles/global.css +42 -68
  202. package/src/components/ActiveFilterChips.astro +0 -20
  203. package/src/components/DirectoryFilters.astro +0 -100
  204. package/src/components/DirectoryHero.astro +0 -32
  205. package/src/components/HealthBadge.astro +0 -12
  206. package/src/components/LensTabs.astro +0 -26
  207. package/src/components/MethodologyPanel.astro +0 -20
  208. package/src/components/Pagination.astro +0 -24
  209. package/src/components/ProjectDetail.astro +0 -166
  210. package/src/components/SubmitDraft.astro +0 -137
  211. package/templates/default/src/components/AppCard.astro +0 -248
  212. package/templates/default/src/components/AppSection.astro +0 -58
  213. package/templates/default/src/components/AppsIndexRow.astro +0 -230
  214. package/templates/default/src/components/AppsPagination.astro +0 -147
  215. package/templates/default/src/components/CategoryGrid.astro +0 -80
  216. package/templates/default/src/components/ContributorsGrid.astro +0 -105
  217. package/templates/default/src/components/DecisionRow.astro +0 -219
  218. package/templates/default/src/components/ExploreByCategory.astro +0 -74
  219. package/templates/default/src/components/ExploreByStack.astro +0 -102
  220. package/templates/default/src/components/Hero.astro +0 -176
  221. package/templates/default/src/components/MinimalAbout.astro +0 -57
  222. package/templates/default/src/components/OriginalCollection.astro +0 -144
  223. package/templates/default/src/components/RefinePanel.astro +0 -272
  224. package/templates/default/src/components/ScoreBars.astro +0 -72
  225. package/templates/default/src/components/SmartLensTabs.astro +0 -65
  226. package/templates/default/src/components/StackGrid.astro +0 -95
  227. package/templates/default/src/components/WhyThisExists.astro +0 -81
  228. package/templates/default/src/components/icons/Icon.astro +0 -148
  229. package/templates/default/src/components/layout/SectionHeader.astro +0 -42
  230. package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
  231. package/templates/default/src/data/apps.ts +0 -149
  232. package/templates/default/src/data/categories.ts +0 -27
  233. package/templates/default/src/data/config.ts +0 -28
  234. package/templates/default/src/data/contributors.ts +0 -96
  235. package/templates/default/src/data/stacks.ts +0 -47
  236. package/templates/default/src/data/stats.ts +0 -65
  237. package/templates/default/src/data/types.ts +0 -248
  238. package/templates/default/src/lib/apps-search.ts +0 -228
  239. package/templates/default/src/lib/format.ts +0 -36
  240. package/templates/default/src/lib/lenses.ts +0 -98
  241. package/templates/default/src/lib/repo.ts +0 -12
  242. package/templates/default/src/lib/scores.ts +0 -51
  243. package/templates/default/src/lib/taxonomy-counts.ts +0 -31
  244. package/templates/default/src/pages/apps/[slug].astro +0 -709
  245. package/templates/default/src/pages/apps/index.astro +0 -669
  246. package/templates/default/src/pages/contributors.astro +0 -142
package/src/index.ts CHANGED
@@ -1,17 +1,64 @@
1
1
  /**
2
- * @grove-dev/astro — Astro UI primitives for Grove.
2
+ * @grove-dev/astro — Astro framework adapter for Grove.
3
3
  *
4
- * This package only owns Astro components, layouts, styles, and
5
- * Astro-specific integrations. Generic filter/sort/stats/score
6
- * helpers live in `@grove-dev/ui` and are re-exported from here for
7
- * convenience.
8
- *
9
- * Astro components are not exported from this barrel — consumers
10
- * import them by path, e.g.:
11
- * import ItemCard from "@grove-dev/astro/components/ItemCard.astro";
12
- * import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
13
- *
14
- * That keeps the barrel type-only and lets `astro check` validate
15
- * the .astro files in their own context.
4
+ * Provides:
5
+ * - `default` export: an Astro integration that wires the
6
+ * `@grove-dev/astro/components/*` and `@grove-dev/astro/layouts/*`
7
+ * subpath imports into Vite so consumer projects can write:
8
+ * import ItemCard from "@grove-dev/astro/components/ItemCard.astro";
9
+ * and have Vite resolve the source `.astro` file (the package's
10
+ * `dist/` only contains TypeScript helpers, not the components
11
+ * themselves).
12
+ * - Re-exports the framework-agnostic `Resource` types from
13
+ * `@grove-dev/core` so the component prop signatures stay
14
+ * type-safe without forcing a dependency on the core package
15
+ * in every consumer file.
16
+ * - Re-exports the generic `lib/` helpers (search, lenses, scores,
17
+ * repo, format, display, taxonomy-counts) under a single import
18
+ * path so consumers can `import { ... } from "@grove-dev/astro"`.
16
19
  */
17
- export * from "@grove-dev/ui";
20
+ import { fileURLToPath } from "node:url";
21
+ import { dirname, resolve } from "node:path";
22
+ import type { AstroIntegration } from "astro";
23
+
24
+ export * from "@grove-dev/core";
25
+
26
+ // Generic lib helpers — repo URL parsing, formatting, search /
27
+ // facet / sort / paginate, lens application, score tiers, taxonomy
28
+ // counts, and pretty-print display maps. All dependency-free and
29
+ // typed against `@grove-dev/core`.
30
+ export * from "./lib/index.js";
31
+
32
+ const here = dirname(fileURLToPath(import.meta.url));
33
+ // `here` is the compiled `dist/` directory. The components and
34
+ // layouts live in the source tree (the `dist/` is for the
35
+ // integration helper only). Walk up one level to reach `src/`
36
+ // before resolving the subpath roots.
37
+ const srcRoot = resolve(here, "..", "src");
38
+ const componentsDir = resolve(srcRoot, "components");
39
+ const layoutsDir = resolve(srcRoot, "layouts");
40
+
41
+ export default function groveAstro(): AstroIntegration {
42
+ return {
43
+ name: "@grove-dev/astro",
44
+ hooks: {
45
+ "astro:config:setup": ({ updateConfig }) => {
46
+ // Alias the components/layouts source directories so
47
+ // consumer builds can import from
48
+ // @grove-dev/astro/components/ItemCard.astro
49
+ // without us shipping a glob-shaped `exports` map that
50
+ // Vite/Rollup does not expand reliably.
51
+ updateConfig({
52
+ vite: {
53
+ resolve: {
54
+ alias: {
55
+ "@grove-dev/astro/components": componentsDir,
56
+ "@grove-dev/astro/layouts": layoutsDir,
57
+ },
58
+ },
59
+ },
60
+ });
61
+ },
62
+ },
63
+ };
64
+ }
@@ -1,32 +1,244 @@
1
1
  ---
2
+ /**
3
+ * BaseLayout — wraps every page. Owns the theme-detection inline
4
+ * script and the global shell (Header, Footer). Per-page SEO
5
+ * (title, description, OG, JSON-LD) is delegated to <Seo /> so each
6
+ * page can declare what it needs without polluting the layout.
7
+ *
8
+ * Generic: takes a `site` prop with brand + nav, no siteConfig import.
9
+ * The header / footer / SEO components are imported from sibling
10
+ * files; consumers can override the Header/Footer via Astro's slot
11
+ * mechanism (`<Header slot="header" />`) if they need custom branding.
12
+ *
13
+ * The THEME_INIT inline script persists user preference in
14
+ * `localStorage` under the key "grove-theme" — matching the key
15
+ * `ThemeToggle.astro` uses.
16
+ */
17
+
2
18
  import "../styles.css";
19
+ import Header from "./Header.astro";
20
+ import Footer from "./Footer.astro";
21
+ import Seo from "./Seo.astro";
22
+
23
+ interface NavItem {
24
+ label: string;
25
+ href: string;
26
+ }
27
+
28
+ interface Site {
29
+ name: string;
30
+ tagline?: string;
31
+ description?: string;
32
+ nav: NavItem[];
33
+ repoUrl?: string;
34
+ url?: string;
35
+ }
3
36
 
4
37
  interface Props {
5
38
  title: string;
6
- description?: string;
39
+ description: string;
40
+ site: Site;
41
+ type?: "website" | "article" | "profile";
42
+ noindex?: boolean;
43
+ jsonLd?: Record<string, unknown> | Record<string, unknown>[];
44
+ /** Optional Google Analytics 4 Measurement ID (e.g. "G-XXXXXX"). */
45
+ gaId?: string;
46
+ /** Path used for JSON-LD `SearchAction` target. Defaults to "/projects". */
47
+ searchPath?: string;
48
+ /** Submit button label rendered in the header. Omit to hide. */
49
+ submitLabel?: string;
50
+ /** Submit button href rendered in the header. */
51
+ submitHref?: string;
7
52
  }
8
53
 
9
- const { title, description = "A living, health-aware developer directory." } = Astro.props;
54
+ const {
55
+ title,
56
+ description,
57
+ site,
58
+ type,
59
+ noindex,
60
+ jsonLd,
61
+ gaId,
62
+ searchPath,
63
+ submitLabel,
64
+ submitHref,
65
+ } = Astro.props;
66
+
67
+ const favicon =
68
+ "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%2318181b'/%3E%3C/svg%3E";
10
69
  ---
11
70
 
12
71
  <!doctype html>
13
72
  <html lang="en">
14
73
  <head>
15
- <meta charset="utf-8" />
16
- <meta name="viewport" content="width=device-width, initial-scale=1" />
17
- <meta name="description" content={description} />
18
- <title>{title}</title>
74
+ <meta charset="UTF-8" />
75
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
76
+ <meta name="generator" content={Astro.generator} />
77
+ <meta name="theme-color" content="#0a0a0c" media="(prefers-color-scheme: dark)" />
78
+ <meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" />
79
+ <link rel="icon" href={favicon} />
80
+ <link rel="sitemap" href="/sitemap.xml" />
81
+ <link
82
+ rel="alternate"
83
+ type="text/plain"
84
+ href="/llms.txt"
85
+ title="LLM usage guidance"
86
+ />
87
+
88
+ {gaId && (
89
+ <>
90
+ {/* GA4 — gtag.js loader. We send the initial page_view with
91
+ the current path + query string so the dashboard
92
+ distinguishes /projects?stack=flutter from
93
+ /projects?stack=react (otherwise they'd all collapse to
94
+ /projects). */}
95
+ <script
96
+ is:inline
97
+ async
98
+ src={`https://www.googletagmanager.com/gtag/js?id=${gaId}`}
99
+ />
100
+ <script is:inline define:vars={{ gaId }}>
101
+ window.dataLayer = window.dataLayer || [];
102
+ function gtag() {
103
+ dataLayer.push(arguments);
104
+ }
105
+ gtag("js", new Date());
106
+ gtag("config", gaId, {
107
+ page_path: window.location.pathname + window.location.search,
108
+ page_title: document.title,
109
+ });
110
+ </script>
111
+ </>
112
+ )}
113
+
114
+ <Seo
115
+ title={title}
116
+ description={description}
117
+ type={type}
118
+ noindex={noindex}
119
+ jsonLd={jsonLd}
120
+ site={site}
121
+ searchPath={searchPath}
122
+ />
123
+
124
+ <slot name="head" />
125
+
126
+ <script is:inline>
127
+ // THEME_INIT — runs before paint so the user never sees a
128
+ // flash of the wrong theme. State is persisted in localStorage
129
+ // under the key "grove-theme" (matching ThemeToggle.astro).
130
+ (function () {
131
+ var STORAGE_KEY = "grove-theme";
132
+ function getMode() {
133
+ try {
134
+ var stored = localStorage.getItem(STORAGE_KEY);
135
+ if (stored === "light" || stored === "dark" || stored === "system")
136
+ return stored;
137
+ } catch (e) {
138
+ // localStorage may be blocked (private mode, etc.) —
139
+ // fall through to the system default.
140
+ }
141
+ return "system";
142
+ }
143
+ function apply(mode) {
144
+ var resolved =
145
+ mode === "system"
146
+ ? (window.matchMedia("(prefers-color-scheme: dark)").matches
147
+ ? "dark"
148
+ : "light")
149
+ : mode;
150
+ var root = document.documentElement;
151
+ if (resolved === "dark") root.classList.add("dark");
152
+ else root.classList.remove("dark");
153
+ root.dataset.theme = mode;
154
+ root.dataset.resolvedTheme = resolved;
155
+ }
156
+ apply(getMode());
157
+ window.addEventListener("theme:set", function (e) {
158
+ apply(e.detail);
159
+ });
160
+ var mq = window.matchMedia("(prefers-color-scheme: dark)");
161
+ mq.addEventListener("change", function () {
162
+ if (getMode() === "system") apply("system");
163
+ });
164
+ })();
165
+ </script>
19
166
  </head>
20
- <body>
21
- <header class="oc-topbar">
22
- <a href="/" class="oc-brand">Grove</a>
23
- <nav aria-label="Primary">
24
- <a href="/projects">Directory</a>
25
- <a href="/methodology">Methodology</a>
26
- </nav>
27
- </header>
28
- <main class="oc-shell">
167
+ <body class="min-h-screen bg-white text-ink-900 dark:bg-ink-950 dark:text-ink-100">
168
+ <slot name="header">
169
+ <Header
170
+ site={site}
171
+ submitLabel={submitLabel}
172
+ submitHref={submitHref}
173
+ />
174
+ </slot>
175
+
176
+ <main>
29
177
  <slot />
30
178
  </main>
179
+
180
+ <slot name="footer">
181
+ <Footer site={site} />
182
+ </slot>
183
+
184
+ <slot name="body-end" />
185
+
186
+ {gaId && (
187
+ <script is:inline define:vars={{ gaId }}>
188
+ // Mirror in-page SPA navigation (history.pushState /
189
+ // replaceState) into GA4 page_view events so filter changes
190
+ // on /projects show up as separate page views in the
191
+ // dashboard instead of one inflated session.
192
+ (function () {
193
+ var last = location.pathname + location.search;
194
+ function report() {
195
+ var next = location.pathname + location.search;
196
+ if (next === last) return;
197
+ last = next;
198
+ window.gtag("event", "page_view", {
199
+ page_path: next,
200
+ page_title: document.title,
201
+ page_location: location.href,
202
+ });
203
+ }
204
+ window.addEventListener("popstate", report);
205
+ var origPush = history.pushState;
206
+ var origReplace = history.replaceState;
207
+ history.pushState = function () {
208
+ origPush.apply(this, arguments);
209
+ report();
210
+ };
211
+ history.replaceState = function () {
212
+ origReplace.apply(this, arguments);
213
+ report();
214
+ };
215
+ })();
216
+ </script>
217
+ )}
218
+
219
+ {gaId && (
220
+ <script is:inline>
221
+ // Delegated click handler for `data-event` elements. We do
222
+ // this in one place so individual components just emit data
223
+ // attributes and stay declarative.
224
+ (function () {
225
+ document.addEventListener("click", function (e) {
226
+ var el = e.target instanceof Element
227
+ ? e.target.closest("[data-event]")
228
+ : null;
229
+ if (!el) return;
230
+ var name = el.getAttribute("data-event");
231
+ if (!name) return;
232
+ var params = {
233
+ source: el.getAttribute("data-event-source") || undefined,
234
+ app_slug: el.getAttribute("data-event-app") || undefined,
235
+ channel: el.getAttribute("data-event-channel") || undefined,
236
+ link_url: el.getAttribute("href") || undefined,
237
+ };
238
+ window.gtag("event", name, params);
239
+ });
240
+ })();
241
+ </script>
242
+ )}
31
243
  </body>
32
244
  </html>
@@ -0,0 +1,57 @@
1
+ ---
2
+ /**
3
+ * Container — page-width wrapper.
4
+ *
5
+ * Centralizes the three layout widths the showcase uses:
6
+ *
7
+ * - "default" → 1140px (the standard reading width)
8
+ * - "wide" → 1400px (home, full-bleed grid sections)
9
+ * - "narrow" → 720px (about, methodology, content-heavy pages)
10
+ *
11
+ * Pure layout, no styling dependencies beyond Tailwind's `max-w-*`
12
+ * tokens defined in `@grove-dev/astro/styles.css`.
13
+ */
14
+
15
+ interface Props {
16
+ size?: "default" | "wide" | "narrow";
17
+ class?: string;
18
+ /** Padding preset. Defaults to a comfortable 1.25rem / 1.75rem. */
19
+ padding?: "default" | "none" | "tight";
20
+ }
21
+
22
+ const {
23
+ size = "default",
24
+ class: className = "",
25
+ padding = "default",
26
+ } = Astro.props;
27
+
28
+ const sizeClass =
29
+ size === "wide"
30
+ ? "grove-container-wide"
31
+ : size === "narrow"
32
+ ? "grove-container-narrow"
33
+ : "grove-container";
34
+
35
+ const paddingClass =
36
+ padding === "none"
37
+ ? ""
38
+ : padding === "tight"
39
+ ? "px-4 sm:px-5"
40
+ : "px-5 sm:px-7";
41
+ ---
42
+
43
+ <div class:list={["mx-auto", sizeClass, paddingClass, className]}>
44
+ <slot />
45
+ </div>
46
+
47
+ <style>
48
+ .grove-container {
49
+ max-width: var(--container-container, 1140px);
50
+ }
51
+ .grove-container-wide {
52
+ max-width: var(--container-wide, 1400px);
53
+ }
54
+ .grove-container-narrow {
55
+ max-width: var(--container-narrow, 720px);
56
+ }
57
+ </style>
@@ -0,0 +1,235 @@
1
+ ---
2
+ /**
3
+ * Footer — 4-column grid (brand + Discover + Contribute + Project) plus
4
+ * a thin bar at the bottom with copyright + repo link.
5
+ *
6
+ * Generic: takes a `site` prop with brand + repoUrl. No siteConfig
7
+ * import — consumers compose props from their own config.
8
+ *
9
+ * Each column's links are passed in as a `{ heading, items[] }` shape
10
+ * so consumers can customize the order and copy. Defaults are provided
11
+ * for the brand and bar so the footer still renders if no extra
12
+ * link data is supplied.
13
+ */
14
+
15
+ interface NavItem {
16
+ label: string;
17
+ href: string;
18
+ /** When true, opens in a new tab (external). */
19
+ external?: boolean;
20
+ }
21
+
22
+ interface FooterColumn {
23
+ heading: string;
24
+ items: NavItem[];
25
+ }
26
+
27
+ interface Site {
28
+ name: string;
29
+ tagline?: string;
30
+ description?: string;
31
+ repoUrl?: string;
32
+ }
33
+
34
+ interface Props {
35
+ site: Site;
36
+ /** Discover links — first content column. */
37
+ discover?: FooterColumn;
38
+ /** Contribute links — second content column. */
39
+ contribute?: FooterColumn;
40
+ /** Project links — third content column. */
41
+ project?: FooterColumn;
42
+ /** Stats line under brand blurb (e.g. "79 apps · 12 categories"). */
43
+ stats?: { apps?: number; categories?: number; contributors?: number };
44
+ /** Copyright suffix. Defaults to site.name. */
45
+ copyrightHolder?: string;
46
+ /** License line under the bar (e.g. "Content under CC BY-SA 4.0."). */
47
+ licenseLine?: string;
48
+ /** Extra class for the outer footer element. */
49
+ class?: string;
50
+ }
51
+
52
+ const {
53
+ site,
54
+ discover = { heading: "Discover", items: [] },
55
+ contribute = { heading: "Contribute", items: [] },
56
+ project = { heading: "Project", items: [] },
57
+ stats,
58
+ copyrightHolder,
59
+ licenseLine,
60
+ class: className = "",
61
+ } = Astro.props;
62
+
63
+ const year = new Date().getFullYear();
64
+ const holder = copyrightHolder ?? site.name;
65
+ const gh = site.repoUrl;
66
+
67
+ const defaultDiscover: NavItem[] = gh
68
+ ? [
69
+ { label: "Source on GitHub", href: gh, external: true },
70
+ { label: "Latest release", href: `${gh}/releases`, external: true },
71
+ ]
72
+ : [];
73
+ const defaultContribute: NavItem[] = gh
74
+ ? [
75
+ { label: "Submit via GitHub", href: `${gh}/blob/main/CONTRIBUTING.md`, external: true },
76
+ { label: "Report an issue", href: `${gh}/issues`, external: true },
77
+ { label: "Contributing guide", href: `${gh}/blob/main/CONTRIBUTING.md`, external: true },
78
+ ]
79
+ : [];
80
+ const defaultProject: NavItem[] = gh
81
+ ? [
82
+ { label: "README", href: `${gh}/blob/main/README.md`, external: true },
83
+ { label: "Changelog", href: `${gh}/blob/main/CHANGELOG.md`, external: true },
84
+ ]
85
+ : [];
86
+
87
+ const discoverItems = discover.items.length ? discover.items : defaultDiscover;
88
+ const contributeItems = contribute.items.length ? contribute.items : defaultContribute;
89
+ const projectItems = project.items.length ? project.items : defaultProject;
90
+ ---
91
+
92
+ <footer
93
+ class:list={[
94
+ "mt-16 border-t border-ink-200 bg-white text-ink-500 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-400",
95
+ className,
96
+ ]}
97
+ >
98
+ <div
99
+ class="mx-auto grid max-w-wide grid-cols-2 gap-8 px-5 py-12 md:grid-cols-4 sm:px-7"
100
+ style="max-width: var(--container-wide, 1400px);"
101
+ >
102
+ <div class="col-span-2 md:col-span-1">
103
+ <div
104
+ class="mb-2 inline-flex items-center gap-2 text-[0.95rem] font-semibold text-ink-900 dark:text-ink-100"
105
+ >
106
+ <span
107
+ aria-hidden="true"
108
+ class="inline-block h-3.5 w-3.5 rounded bg-ink-900 dark:bg-ink-100"
109
+ ></span>
110
+ <span>{site.name}</span>
111
+ </div>
112
+ {site.tagline && (
113
+ <p class="m-0 max-w-[36ch] text-[0.8125rem] leading-relaxed text-ink-500 dark:text-ink-400">
114
+ {site.tagline}
115
+ </p>
116
+ )}
117
+ {stats && (
118
+ <p class="mt-3 m-0 text-2xs text-ink-500 dark:text-ink-400">
119
+ {stats.apps !== undefined && (
120
+ <>
121
+ <span class="font-mono">{stats.apps}</span> apps
122
+ </>
123
+ )}
124
+ {stats.apps !== undefined && (stats.categories !== undefined || stats.contributors !== undefined) && (
125
+ <span class="mx-1 text-ink-300 dark:text-ink-700">·</span>
126
+ )}
127
+ {stats.categories !== undefined && (
128
+ <>
129
+ <span class="font-mono">{stats.categories}</span> categories
130
+ </>
131
+ )}
132
+ {stats.categories !== undefined && stats.contributors !== undefined && (
133
+ <span class="mx-1 text-ink-300 dark:text-ink-700">·</span>
134
+ )}
135
+ {stats.contributors !== undefined && (
136
+ <>
137
+ <span class="font-mono">{stats.contributors}</span> contributors
138
+ </>
139
+ )}
140
+ </p>
141
+ )}
142
+ </div>
143
+
144
+ {discoverItems.length > 0 && (
145
+ <div>
146
+ <h4 class="mb-3 text-2xs font-semibold uppercase tracking-wider text-ink-900 dark:text-ink-100">
147
+ {discover.heading}
148
+ </h4>
149
+ <ul class="m-0 flex list-none flex-col gap-2 p-0">
150
+ {discoverItems.map((item) => (
151
+ <li>
152
+ <a
153
+ href={item.href}
154
+ class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100"
155
+ {...(item.external
156
+ ? { target: "_blank", rel: "noopener noreferrer" }
157
+ : {})}
158
+ >
159
+ {item.label}
160
+ </a>
161
+ </li>
162
+ ))}
163
+ </ul>
164
+ </div>
165
+ )}
166
+
167
+ {contributeItems.length > 0 && (
168
+ <div>
169
+ <h4 class="mb-3 text-2xs font-semibold uppercase tracking-wider text-ink-900 dark:text-ink-100">
170
+ {contribute.heading}
171
+ </h4>
172
+ <ul class="m-0 flex list-none flex-col gap-2 p-0">
173
+ {contributeItems.map((item) => (
174
+ <li>
175
+ <a
176
+ href={item.href}
177
+ class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100"
178
+ {...(item.external
179
+ ? { target: "_blank", rel: "noopener noreferrer" }
180
+ : {})}
181
+ >
182
+ {item.label}
183
+ </a>
184
+ </li>
185
+ ))}
186
+ </ul>
187
+ </div>
188
+ )}
189
+
190
+ {projectItems.length > 0 && (
191
+ <div>
192
+ <h4 class="mb-3 text-2xs font-semibold uppercase tracking-wider text-ink-900 dark:text-ink-100">
193
+ {project.heading}
194
+ </h4>
195
+ <ul class="m-0 flex list-none flex-col gap-2 p-0">
196
+ {projectItems.map((item) => (
197
+ <li>
198
+ <a
199
+ href={item.href}
200
+ class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100"
201
+ {...(item.external
202
+ ? { target: "_blank", rel: "noopener noreferrer" }
203
+ : {})}
204
+ >
205
+ {item.label}
206
+ </a>
207
+ </li>
208
+ ))}
209
+ </ul>
210
+ </div>
211
+ )}
212
+ </div>
213
+
214
+ <div class="border-t border-ink-200 dark:border-ink-800">
215
+ <div
216
+ class="mx-auto flex max-w-wide flex-wrap items-center gap-3 px-5 py-4 text-2xs text-ink-500 sm:px-7 dark:text-ink-400"
217
+ style="max-width: var(--container-wide, 1400px);"
218
+ >
219
+ <span>© {year} {holder}.{licenseLine ? ` ${licenseLine}` : ""}</span>
220
+ {gh && (
221
+ <>
222
+ <span class="text-ink-300 dark:text-ink-700">·</span>
223
+ <a
224
+ href={gh}
225
+ target="_blank"
226
+ rel="noopener noreferrer"
227
+ class="transition-colors hover:text-ink-900 dark:hover:text-ink-100"
228
+ >
229
+ View source on GitHub
230
+ </a>
231
+ </>
232
+ )}
233
+ </div>
234
+ </div>
235
+ </footer>