@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
@@ -1,416 +1,127 @@
1
1
  ---
2
- import { siteConfig } from "../data/config";
3
- import BaseLayout from "../components/layout/BaseLayout.astro";
4
- import Container from "../components/layout/Container.astro";
5
- import { stacks } from "../data/stacks";
6
- import { categories } from "../data/categories";
7
-
8
- const platformOptions = [
9
- { id: "ios", name: "iOS" },
10
- { id: "android", name: "Android" },
11
- { id: "web", name: "Web" },
12
- { id: "macos", name: "macOS" },
13
- { id: "windows", name: "Windows" },
14
- { id: "linux", name: "Linux" },
15
- ];
16
-
17
- const distributionOptions = [
18
- { type: "app-store", label: "App Store", platform: "ios", placeholder: "https://apps.apple.com/..." },
19
- { type: "play-store", label: "Play Store", platform: "android", placeholder: "https://play.google.com/store/apps/details?id=..." },
20
- { type: "fdroid", label: "F-Droid", platform: "android", placeholder: "https://f-droid.org/packages/..." },
21
- { type: "github-releases", label: "GitHub Releases", platform: "android", placeholder: "https://github.com/owner/repo/releases" },
22
- { type: "apk", label: "APK download", platform: "android", placeholder: "https://example.com/app.apk" },
23
- { type: "testflight", label: "TestFlight", platform: "ios", placeholder: "https://testflight.apple.com/join/..." },
24
- { type: "website", label: "Website download", platform: "web", placeholder: "https://example.com/download" },
25
- ];
26
-
27
- const title = "Submit App - ${siteConfig.name}";
28
- const description = "Submit an open-source app to ${siteConfig.name}.";
29
- // The submit page is a thin wrapper around a GitHub Issues form —
30
- // no content of its own. Keep it out of search results and out of
31
- // the sitemap.
2
+ /**
3
+ * Submit page — text-based instructions for contributing a new
4
+ * record. No form: the openapps 19K form preview is overkill for
5
+ * the default template, and a static "open a PR / open an issue"
6
+ * workflow matches how the showcase actually accepts submissions.
7
+ */
8
+ import siteConfig from "../../data/generated/site-config.json";
9
+ import { indexSlug, itemLabel } from "../data/records";
10
+ import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
11
+ import Container from "@grove-dev/astro/layouts/Container.astro";
12
+
13
+ const title = `Submit a ${itemLabel()} — ${siteConfig.name}`;
14
+ const description = `Submit a new ${itemLabel()} to ${siteConfig.name}.`;
32
15
  const noindex = true;
33
- ---
34
-
35
- <BaseLayout title={title} description={description} noindex={noindex}>
36
- <Container>
37
- <section class="py-10 sm:py-14">
38
- <div class="max-w-3xl">
39
- <p class="m-0 text-2xs font-semibold uppercase tracking-wider text-ink-500 dark:text-ink-400">
40
- App submission
41
- </p>
42
- <h1 class="mt-2 text-[2rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
43
- Add an app from GitHub
44
- </h1>
45
- </div>
46
-
47
- <div class="mt-8 grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(360px,0.8fr)]">
48
- <form id="submit-form" class="card p-5">
49
- <div class="grid gap-5">
50
- <label class="grid gap-1.5">
51
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">GitHub URL</span>
52
- <div class="flex flex-col gap-2 sm:flex-row">
53
- <input
54
- id="repo-url"
55
- name="repoUrl"
56
- type="url"
57
- required
58
- placeholder="https://github.com/owner/repo"
59
- class="min-h-10 flex-1 rounded-md border border-ink-200 bg-white px-3 text-sm text-ink-900 outline-none focus:border-ink-500 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-100"
60
- />
61
- <button id="fetch-repo" type="button" class="btn-primary min-h-10 justify-center">
62
- Fetch draft
63
- </button>
64
- </div>
65
- </label>
66
-
67
- <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
68
- <label class="grid gap-1.5">
69
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Name</span>
70
- <input id="app-name" type="text" class="field" />
71
- </label>
72
- <label class="grid gap-1.5">
73
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Slug</span>
74
- <input id="app-slug" type="text" class="field font-mono" />
75
- </label>
76
- </div>
77
-
78
- <label class="grid gap-1.5">
79
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Description</span>
80
- <textarea id="app-description" rows="3" class="field resize-y"></textarea>
81
- </label>
82
-
83
- <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
84
- <label class="grid gap-1.5">
85
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Category</span>
86
- <select id="app-category" class="field">
87
- {categories.map((category) => (
88
- <option value={category.slug}>{category.name}</option>
89
- ))}
90
- </select>
91
- </label>
92
- <label class="grid gap-1.5">
93
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Primary stack</span>
94
- <select id="primary-stack" class="field">
95
- {stacks.map((stack) => (
96
- <option value={stack.slug}>{stack.name}</option>
97
- ))}
98
- </select>
99
- </label>
100
- </div>
101
-
102
- <fieldset class="grid gap-2">
103
- <legend class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
104
- Platforms
105
- </legend>
106
- <div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
107
- {platformOptions.map((platform) => (
108
- <label class="flex min-h-9 items-center gap-2 rounded-md border border-ink-200 px-2 text-sm dark:border-ink-800">
109
- <input type="checkbox" name="platform" value={platform.id} class="h-4 w-4" />
110
- <span>{platform.name}</span>
111
- </label>
112
- ))}
113
- </div>
114
- </fieldset>
115
-
116
- <label class="grid gap-1.5">
117
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Tags</span>
118
- <input id="app-tags" type="text" placeholder="self-hosted, photos, offline-first" class="field" />
119
- </label>
120
-
121
- <fieldset class="grid gap-2">
122
- <legend class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
123
- Download links
124
- </legend>
125
- <div class="grid gap-2">
126
- {distributionOptions.map((option) => (
127
- <label class="grid gap-1 rounded-md border border-ink-200 p-2 dark:border-ink-800">
128
- <span class="text-xs font-medium text-ink-700 dark:text-ink-200">{option.label}</span>
129
- <input
130
- type="url"
131
- data-distribution-type={option.type}
132
- data-distribution-label={option.label}
133
- data-distribution-platform={option.platform}
134
- placeholder={option.placeholder}
135
- class="field min-h-9 text-xs"
136
- />
137
- </label>
138
- ))}
139
- </div>
140
- </fieldset>
141
-
142
- <label class="grid gap-1.5">
143
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">Best for</span>
144
- <textarea id="best-for" rows="3" class="field resize-y"></textarea>
145
- </label>
146
- </div>
147
- </form>
148
-
149
- <aside class="card flex min-h-[520px] flex-col p-5">
150
- <div class="flex items-center justify-between gap-3">
151
- <h2 class="m-0 text-base font-semibold text-ink-900 dark:text-ink-100">YAML draft</h2>
152
- <a id="open-pr-link" href="#" target="_blank" rel="noopener noreferrer" class="btn-outline pointer-events-none opacity-50">
153
- Open PR draft
154
- </a>
155
- </div>
156
- <pre class="mt-4 min-h-0 flex-1 overflow-auto rounded-md border border-ink-200 bg-ink-50 p-3 text-xs leading-relaxed text-ink-800 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-200"><code id="yaml-preview">Paste a GitHub URL to generate a draft.</code></pre>
157
- <p id="submit-status" class="mt-3 min-h-5 text-2xs text-ink-500 dark:text-ink-400" aria-live="polite" aria-atomic="true"></p>
158
- </aside>
159
- </div>
160
- </section>
161
- </Container>
162
-
163
- <style>
164
- @reference "../styles/global.css";
165
-
166
- .field {
167
- @apply min-h-10 rounded-md border border-ink-200 bg-white px-3 py-2 text-sm text-ink-900 outline-none focus:border-ink-500 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-100;
168
- }
169
- </style>
16
+ const slug = indexSlug();
170
17
 
171
- <script>
172
- // @ts-nocheck
173
- const repoUrl = document.querySelector("#repo-url");
174
- const fetchButton = document.querySelector("#fetch-repo");
175
- const status = document.querySelector("#submit-status");
176
- const preview = document.querySelector("#yaml-preview");
177
- const openPrLink = document.querySelector("#open-pr-link");
178
- const fields = {
179
- name: document.querySelector("#app-name"),
180
- slug: document.querySelector("#app-slug"),
181
- description: document.querySelector("#app-description"),
182
- category: document.querySelector("#app-category"),
183
- primaryStack: document.querySelector("#primary-stack"),
184
- tags: document.querySelector("#app-tags"),
185
- bestFor: document.querySelector("#best-for"),
186
- };
187
-
188
- let repository = null;
189
-
190
- function slugify(value) {
191
- return String(value || "")
192
- .toLowerCase()
193
- .replace(/[^a-z0-9]+/g, "-")
194
- .replace(/^-+|-+$/g, "");
195
- }
196
-
197
- function parseGithubUrl(value) {
198
- const match = String(value).match(/github\.com\/([^/]+)\/([^/?#]+)/);
199
- if (!match) return null;
200
- return { owner: match[1], repo: match[2].replace(/\.git$/, "") };
201
- }
202
-
203
- function inferStack(repo) {
204
- const language = String(repo.language || "").toLowerCase();
205
- const topics = (repo.topics || []).map((topic) => String(topic).toLowerCase());
206
- if (topics.includes("flutter") || language === "dart") return "flutter";
207
- if (topics.includes("react-native")) return "react-native";
208
- if (topics.includes("capacitor") || topics.includes("ionic")) return "capacitor";
209
- if (language === "swift" || language === "objective-c") return "ios";
210
- if (language === "kotlin" || language === "java") return "android";
211
- // Unknown — return empty so the user has to pick. The previous
212
- // default of "flutter" mis-tagged Java/Kotlin/Swift-only apps.
213
- return "";
214
- }
215
-
216
- function inferPlatforms(repo, stack) {
217
- const topics = (repo.topics || []).map((topic) => String(topic).toLowerCase());
218
- const values = new Set();
219
- for (const topic of topics) {
220
- if (["ios", "android", "web", "macos", "windows", "linux"].includes(topic)) values.add(topic);
221
- }
222
- if (stack === "ios") values.add("ios");
223
- if (stack === "android") values.add("android");
224
- if (stack === "flutter" || stack === "react-native" || stack === "capacitor") {
225
- values.add("ios");
226
- values.add("android");
227
- }
228
- return [...values];
229
- }
230
-
231
- function setPlatforms(values) {
232
- const selected = new Set(values);
233
- document.querySelectorAll("input[name='platform']").forEach((input) => {
234
- input.checked = selected.has(input.value);
235
- });
236
- }
237
-
238
- function selectedPlatforms() {
239
- return [...document.querySelectorAll("input[name='platform']:checked")].map((input) => input.value);
240
- }
241
-
242
- function distributionChannels() {
243
- return [...document.querySelectorAll("[data-distribution-type]")]
244
- .map((input) => ({
245
- type: input.dataset.distributionType,
246
- label: input.dataset.distributionLabel,
247
- platform: input.dataset.distributionPlatform,
248
- url: input.value.trim(),
249
- }))
250
- .filter((channel) => channel.url);
251
- }
252
-
253
- function listLines(values, indent) {
254
- return values.map((value) => `${" ".repeat(indent)}- ${value}`).join("\n");
255
- }
256
-
257
- function yamlQuote(value) {
258
- const safe = String(value ?? "").replace(/"/g, '\\"');
259
- return `"${safe}"`;
260
- }
261
-
262
- function generateYaml() {
263
- if (!repository) return "";
264
- const parsed = parseGithubUrl(repoUrl.value);
265
- if (!parsed) return "";
266
- const slug = slugify(fields.slug.value || repository.name);
267
- const tags = fields.tags.value.split(",").map((tag) => tag.trim()).filter(Boolean);
268
- const bestFor = fields.bestFor.value.split("\n").map((line) => line.trim()).filter(Boolean);
269
- const platforms = selectedPlatforms();
270
- const distribution = distributionChannels();
271
- const stack = fields.primaryStack.value;
272
- const topics = repository.topics || [];
273
- const technologies = [...new Set([stack, String(repository.language || "").toLowerCase(), ...topics])].filter(Boolean);
274
- const now = new Date().toISOString();
275
-
276
- return [
277
- "schemaVersion: 1",
278
- "",
279
- `id: github:${repository.full_name}`,
280
- `slug: ${slug}`,
281
- "",
282
- "source:",
283
- " provider: github",
284
- ` owner: ${parsed.owner}`,
285
- ` repo: ${parsed.repo}`,
286
- ` url: ${repository.html_url}`,
287
- "",
288
- "app:",
289
- ` name: ${yamlQuote(fields.name.value)}`,
290
- ` description: ${yamlQuote(fields.description.value)}`,
291
- ` category: ${fields.category.value}`,
292
- " projectType: production",
293
- " platforms:",
294
- platforms.length ? listLines(platforms, 4) : " - ios",
295
- " tags:",
296
- tags.length ? listLines(tags, 4) : listLines(topics.slice(0, 5), 4),
297
- ...(distribution.length
298
- ? [
299
- " distribution:",
300
- " channels:",
301
- ...distribution.map((channel) => [
302
- ` - type: ${channel.type}`,
303
- ` platform: ${channel.platform}`,
304
- ` label: ${yamlQuote(channel.label)}`,
305
- ` url: ${channel.url}`,
306
- " verified: false",
307
- ].join("\n")),
308
- ]
309
- : []),
310
- "",
311
- "stack:",
312
- ` primary: ${stack}`,
313
- " technologies:",
314
- ...technologies.slice(0, 10).map((id) => ` - id: ${id}\n role: technology`),
315
- "",
316
- "github:",
317
- " repository:",
318
- ` id: ${repository.id}`,
319
- ` node_id: ${repository.node_id}`,
320
- ` name: ${repository.name}`,
321
- ` full_name: ${repository.full_name}`,
322
- ` html_url: ${repository.html_url}`,
323
- ` clone_url: ${repository.clone_url}`,
324
- ` ssh_url: ${repository.ssh_url}`,
325
- ` homepage: ${repository.homepage ? yamlQuote(repository.homepage) : "null"}`,
326
- ` description: ${repository.description ? yamlQuote(repository.description) : "null"}`,
327
- ` fork: ${Boolean(repository.fork)}`,
328
- ` archived: ${Boolean(repository.archived)}`,
329
- ` disabled: ${Boolean(repository.disabled)}`,
330
- ` private: ${Boolean(repository.private)}`,
331
- ` is_template: ${Boolean(repository.is_template)}`,
332
- ` visibility: ${repository.visibility || "public"}`,
333
- ` default_branch: ${repository.default_branch}`,
334
- ` language: ${repository.language ? yamlQuote(repository.language) : "null"}`,
335
- " topics:",
336
- topics.length ? listLines(topics, 6) : " []",
337
- " license:",
338
- ` key: ${repository.license?.key || "unknown"}`,
339
- ` name: ${repository.license?.name ? yamlQuote(repository.license.name) : "null"}`,
340
- ` spdx_id: ${repository.license?.spdx_id || "NOASSERTION"}`,
341
- ` stargazers_count: ${repository.stargazers_count || 0}`,
342
- ` watchers_count: ${repository.watchers_count || 0}`,
343
- ` forks_count: ${repository.forks_count || 0}`,
344
- ` open_issues_count: ${repository.open_issues_count || 0}`,
345
- ` subscribers_count: ${repository.subscribers_count || 0}`,
346
- ` size: ${repository.size || 0}`,
347
- ` created_at: ${repository.created_at}`,
348
- ` updated_at: ${repository.updated_at}`,
349
- ` pushed_at: ${repository.pushed_at}`,
350
- " sync:",
351
- ` syncedAt: ${now}`,
352
- " apiVersion: rest-v3",
353
- " source: submit-page",
354
- "",
355
- "curation:",
356
- " reviewed: false",
357
- " reviewedBy: null",
358
- " reviewedAt: null",
359
- " bestFor:",
360
- bestFor.length ? listLines(bestFor, 4) : " []",
361
- " caveats:",
362
- " []",
363
- "",
364
- ].join("\n");
365
- }
366
-
367
- function refreshYaml() {
368
- const yaml = generateYaml();
369
- preview.textContent = yaml || "Paste a GitHub URL to generate a draft.";
370
- if (!yaml || !repository) {
371
- openPrLink.href = "#";
372
- openPrLink.classList.add("pointer-events-none", "opacity-50");
373
- return;
374
- }
375
- const slug = slugify(fields.slug.value || repository.name);
376
- const params = new URLSearchParams({
377
- filename: `data/apps/${slug}.yml`,
378
- value: yaml,
379
- });
380
- openPrLink.href = `https://github.com/tortuvshin/open-apps/new/main?${params.toString()}`;
381
- openPrLink.classList.remove("pointer-events-none", "opacity-50");
382
- }
383
-
384
- fetchButton.addEventListener("click", async () => {
385
- const parsed = parseGithubUrl(repoUrl.value);
386
- if (!parsed) {
387
- status.textContent = "Enter a valid GitHub repository URL.";
388
- return;
389
- }
390
- fetchButton.disabled = true;
391
- status.textContent = "Fetching GitHub metadata...";
392
- try {
393
- const response = await fetch(`https://api.github.com/repos/${parsed.owner}/${parsed.repo}`, {
394
- headers: { Accept: "application/vnd.github+json" },
395
- });
396
- if (!response.ok) throw new Error(`GitHub returned ${response.status}`);
397
- repository = await response.json();
398
- const stack = inferStack(repository);
399
- fields.name.value = repository.name || parsed.repo;
400
- fields.slug.value = slugify(repository.name || parsed.repo);
401
- fields.description.value = repository.description || "";
402
- fields.primaryStack.value = stack;
403
- fields.tags.value = (repository.topics || []).slice(0, 8).join(", ");
404
- setPlatforms(inferPlatforms(repository, stack));
405
- status.textContent = "Draft generated.";
406
- refreshYaml();
407
- } catch (error) {
408
- status.textContent = error.message;
409
- } finally {
410
- fetchButton.disabled = false;
411
- }
412
- });
18
+ const repoPath = (siteConfig.repoUrl ?? "").replace(/^https?:\/\/github\.com\//, "");
19
+ const issueUrl = repoPath
20
+ ? `https://github.com/${repoPath}/issues/new?template=record_submission.md`
21
+ : "https://github.com/tortuvshin/grove/issues/new";
22
+ ---
413
23
 
414
- document.querySelector("#submit-form").addEventListener("input", refreshYaml);
415
- </script>
24
+ <BaseLayout
25
+ title={title}
26
+ description={description}
27
+ site={siteConfig}
28
+ noindex={noindex}
29
+ >
30
+ <Container>
31
+ <article class="py-12 sm:py-16">
32
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
33
+ Submit
34
+ </span>
35
+ <h1 class="mt-1 text-[1.75rem] sm:text-[2rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100 m-0">
36
+ Submit a {itemLabel()}
37
+ </h1>
38
+ <p class="mt-4 max-w-[62ch] text-sm text-ink-700 dark:text-ink-200 m-0">
39
+ {siteConfig.name} is a community knowledge site. Adding a new{" "}
40
+ {itemLabel()} is a two-step process — open a submission issue,
41
+ then open a pull request that adds a new YAML file under{" "}
42
+ <code class="font-mono text-ink-900 dark:text-ink-100">data/records/</code>.
43
+ </p>
44
+
45
+ <div class="mt-6 flex flex-wrap gap-2">
46
+ <a
47
+ href={issueUrl}
48
+ target="_blank"
49
+ rel="noopener noreferrer"
50
+ class="grove-btn grove-btn-primary"
51
+ >
52
+ Open a submission issue ↗
53
+ </a>
54
+ {repoPath && (
55
+ <a
56
+ href={`https://github.com/${repoPath}/blob/main/.github/ISSUE_TEMPLATE/record_submission.md`}
57
+ target="_blank"
58
+ rel="noopener noreferrer"
59
+ class="grove-btn grove-btn-outline"
60
+ >
61
+ See the issue template
62
+ </a>
63
+ )}
64
+ </div>
65
+
66
+ <section class="mt-12 max-w-[68ch">
67
+ <h2 class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
68
+ Manual record (alternative to the issue form)
69
+ </h2>
70
+ <p class="mt-2 text-sm text-ink-700 dark:text-ink-200 m-0">
71
+ Prefer to write a record by hand? Here's the minimum shape for a{" "}
72
+ <code class="font-mono">{siteConfig.blueprint}</code> record.
73
+ The schema is validated automatically on every PR.
74
+ </p>
75
+ {siteConfig.blueprint === "project-directory" && (
76
+ <pre class="mt-4 overflow-x-auto rounded-md border border-ink-200 bg-ink-50 p-4 text-xs leading-relaxed text-ink-800 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-200"><code>{`kind: project
77
+ slug: my-project
78
+ name: My Project
79
+ description: One-line description of what this project does
80
+ category: tools
81
+ tags:
82
+ - open-source
83
+ links:
84
+ github: https://github.com/owner/repo
85
+ website: https://example.com`}</code></pre>
86
+ )}
87
+ {siteConfig.blueprint === "resource-hub" && (
88
+ <pre class="mt-4 overflow-x-auto rounded-md border border-ink-200 bg-ink-50 p-4 text-xs leading-relaxed text-ink-800 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-200"><code>{`kind: resource
89
+ slug: my-resource
90
+ title: My Resource
91
+ description: One-line description of what this resource is
92
+ category: guides
93
+ type: guide
94
+ topic: coding-agents
95
+ tags:
96
+ - ai
97
+ links:
98
+ source: https://example.com`}</code></pre>
99
+ )}
100
+ {siteConfig.blueprint === "ecosystem-map" && (
101
+ <pre class="mt-4 overflow-x-auto rounded-md border border-ink-200 bg-ink-50 p-4 text-xs leading-relaxed text-ink-800 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-200"><code>{`kind: entity
102
+ slug: my-entity
103
+ name: My Organization
104
+ description: One-line description of what this organization does
105
+ category: education
106
+ type: company
107
+ tags:
108
+ - technology
109
+ links:
110
+ website: https://example.com`}</code></pre>
111
+ )}
112
+ </section>
113
+
114
+ <section class="mt-12 max-w-[68ch">
115
+ <h2 class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
116
+ What happens after you submit
117
+ </h2>
118
+ <ol class="mt-3 m-0 flex list-decimal flex-col gap-2 pl-5 text-sm text-ink-700 dark:text-ink-200">
119
+ <li>A maintainer reviews the issue and tags it as <code class="font-mono">accepted</code> or <code class="font-mono">needs-info</code>.</li>
120
+ <li>You open a PR with the new <code class="font-mono">data/records/&lt;slug&gt;.yml</code> file.</li>
121
+ <li>CI runs <code class="font-mono">grove validate</code> and previews the site with your record.</li>
122
+ <li>A maintainer merges. The next <code class="font-mono">grove build</code> deploys it.</li>
123
+ </ol>
124
+ </section>
125
+ </article>
126
+ </Container>
416
127
  </BaseLayout>
@@ -1,84 +1,58 @@
1
1
  @import "tailwindcss";
2
- @config "../../tailwind.config.mjs";
2
+
3
+ /*
4
+ * Grove V1 design tokens. Theme is overridable from `grove.config.ts`
5
+ * at scaffold time; we expose the same names as CSS variables so
6
+ * consumers can layer their own brand colors in without forking.
7
+ *
8
+ * The `ink-*` color scale is preserved from the V0 template because
9
+ * the page-level classes (`text-ink-900`, `bg-ink-50`, ...) still
10
+ * reference it. New pages should prefer the default Tailwind
11
+ * `gray-*` / `slate-*` palettes; the ink scale exists for
12
+ * back-compat with the V1 minimal template.
13
+ */
14
+ @theme {
15
+ --color-ink-50: #fafafa;
16
+ --color-ink-100: #f4f4f5;
17
+ --color-ink-200: #e4e4e7;
18
+ --color-ink-300: #d4d4d8;
19
+ --color-ink-400: #a1a1aa;
20
+ --color-ink-500: #71717a;
21
+ --color-ink-600: #52525b;
22
+ --color-ink-700: #3f3f46;
23
+ --color-ink-800: #27272a;
24
+ --color-ink-900: #18181b;
25
+ --color-ink-950: #09090b;
26
+
27
+ --color-grove-primary: #16a34a;
28
+ }
3
29
 
4
30
  @layer base {
5
31
  :root {
32
+ --grove-color-primary: var(--color-grove-primary);
33
+ --grove-radius: 0.5rem;
34
+ --grove-container-width: 72rem;
35
+
6
36
  color-scheme: light dark;
7
37
  }
38
+
8
39
  html {
9
40
  -webkit-text-size-adjust: 100%;
10
41
  -webkit-font-smoothing: antialiased;
11
42
  -moz-osx-font-smoothing: grayscale;
12
43
  }
44
+
13
45
  body {
14
- @apply bg-white text-ink-900 dark:bg-ink-950 dark:text-ink-100 font-sans text-[15px] leading-relaxed;
15
- }
16
- a {
17
- @apply text-ink-900 dark:text-ink-100;
46
+ background: #ffffff;
47
+ color: var(--color-ink-900);
48
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
49
+ "Helvetica Neue", Arial, sans-serif;
50
+ font-size: 15px;
51
+ line-height: 1.55;
18
52
  }
19
- /* Subtle selection */
20
- ::selection {
21
- @apply bg-ink-200 text-ink-900 dark:bg-ink-800 dark:text-ink-100;
22
- }
23
- /* Slim, neutral scrollbar */
24
- * {
25
- scrollbar-width: thin;
26
- scrollbar-color: theme('colors.ink.300') transparent;
27
- }
28
- *::-webkit-scrollbar {
29
- width: 8px;
30
- height: 8px;
31
- }
32
- *::-webkit-scrollbar-thumb {
33
- @apply bg-ink-200 dark:bg-ink-800 rounded;
34
- }
35
- }
36
53
 
37
- @layer components {
38
- /* Section spacing — consistent vertical rhythm */
39
- .section {
40
- @apply py-12 sm:py-16;
41
- }
42
- .section-tight {
43
- @apply py-10 sm:py-12;
44
- }
45
- /* Subtle card border + hover lift, used everywhere */
46
- .card {
47
- @apply rounded-lg border border-ink-200 dark:border-ink-800 bg-white dark:bg-ink-950 transition-[border-color,box-shadow,background-color] duration-150;
48
- }
49
- .card-hover {
50
- @apply hover:border-ink-300 dark:hover:border-ink-700 hover:bg-ink-50/60 dark:hover:bg-ink-900/40 hover:shadow-[0_1px_2px_rgba(0,0,0,0.04),0_4px_12px_-2px_rgba(0,0,0,0.06)] dark:hover:shadow-[0_1px_2px_rgba(0,0,0,0.4),0_4px_12px_-2px_rgba(0,0,0,0.3)];
51
- }
52
- /* Pill badge — small, neutral */
53
- .badge {
54
- @apply inline-flex items-center gap-1 rounded border border-ink-200 dark:border-ink-800 bg-ink-50 dark:bg-ink-900/60 px-1.5 py-0.5 text-2xs font-medium text-ink-700 dark:text-ink-300;
55
- }
56
- .badge-accent {
57
- @apply border-ink-900 dark:border-ink-100 bg-ink-900 dark:bg-ink-100 text-white dark:text-ink-900;
58
- }
59
- .badge-new {
60
- @apply border-emerald-300/60 dark:border-emerald-800/60 bg-emerald-50 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-300;
61
- }
62
- .badge-hot {
63
- @apply border-orange-300/60 dark:border-orange-800/60 bg-orange-50 dark:bg-orange-950/40 text-orange-800 dark:text-orange-300;
64
- }
65
- .badge-mature {
66
- @apply border-blue-300/60 dark:border-blue-800/60 bg-blue-50 dark:bg-blue-950/40 text-blue-800 dark:text-blue-300;
67
- }
68
- .badge-stale {
69
- @apply border-amber-300/60 dark:border-amber-800/60 bg-amber-50 dark:bg-amber-950/40 text-amber-800 dark:text-amber-300;
70
- }
71
- /* Outline button */
72
- .btn {
73
- @apply inline-flex items-center justify-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors;
74
- }
75
- .btn-ghost {
76
- @apply inline-flex items-center justify-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors border-transparent text-ink-700 dark:text-ink-300 hover:bg-ink-100 dark:hover:bg-ink-900;
77
- }
78
- .btn-outline {
79
- @apply inline-flex items-center justify-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors border-ink-200 dark:border-ink-800 bg-white dark:bg-ink-950 text-ink-900 dark:text-ink-100 hover:border-ink-300 dark:hover:border-ink-700 hover:bg-ink-50 dark:hover:bg-ink-900/40;
80
- }
81
- .btn-primary {
82
- @apply inline-flex items-center justify-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors border-ink-900 dark:border-ink-100 bg-ink-900 dark:bg-ink-100 text-white dark:text-ink-900 hover:bg-ink-800 dark:hover:bg-white;
54
+ body.dark {
55
+ background: var(--color-ink-950);
56
+ color: var(--color-ink-100);
83
57
  }
84
58
  }