@grove-dev/astro 0.2.20 → 0.3.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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -75
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +11 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/display.d.ts +1 -26
  7. package/dist/lib/display.d.ts.map +1 -1
  8. package/dist/lib/display.js +1 -103
  9. package/dist/lib/display.js.map +1 -1
  10. package/dist/lib/format.d.ts +1 -28
  11. package/dist/lib/format.d.ts.map +1 -1
  12. package/dist/lib/format.js +1 -71
  13. package/dist/lib/format.js.map +1 -1
  14. package/dist/lib/lenses.d.ts +1 -57
  15. package/dist/lib/lenses.d.ts.map +1 -1
  16. package/dist/lib/lenses.js +1 -198
  17. package/dist/lib/lenses.js.map +1 -1
  18. package/dist/lib/repo.d.ts +1 -19
  19. package/dist/lib/repo.d.ts.map +1 -1
  20. package/dist/lib/repo.js +1 -30
  21. package/dist/lib/repo.js.map +1 -1
  22. package/dist/lib/scores.d.ts +1 -36
  23. package/dist/lib/scores.d.ts.map +1 -1
  24. package/dist/lib/scores.js +1 -75
  25. package/dist/lib/scores.js.map +1 -1
  26. package/dist/lib/search.d.ts +1 -113
  27. package/dist/lib/search.d.ts.map +1 -1
  28. package/dist/lib/search.js +1 -379
  29. package/dist/lib/search.js.map +1 -1
  30. package/dist/lib/taxonomy-counts.d.ts +1 -10
  31. package/dist/lib/taxonomy-counts.d.ts.map +1 -1
  32. package/dist/lib/taxonomy-counts.js +1 -21
  33. package/dist/lib/taxonomy-counts.js.map +1 -1
  34. package/package.json +22 -10
  35. package/src/components/ContributorsGrid.astro +3 -3
  36. package/src/components/CurationGrid.astro +2 -2
  37. package/src/components/DirectoryIndexClient.astro +240 -0
  38. package/src/components/FilterGroupMenu.astro +8 -4
  39. package/src/components/FinalCta.astro +41 -0
  40. package/src/components/Hero.astro +15 -18
  41. package/src/components/Icon.astro +99 -27
  42. package/src/components/RefinePanel.astro +7 -7
  43. package/src/components/SmartLensTabs.astro +5 -4
  44. package/src/components/StackGrid.astro +1 -1
  45. package/src/components/SubmissionClient.astro +133 -0
  46. package/src/index.ts +11 -1
  47. package/src/layouts/BaseLayout.astro +19 -1
  48. package/src/layouts/Footer.astro +25 -56
  49. package/src/layouts/Header.astro +33 -15
  50. package/src/layouts/ThemeToggle.astro +7 -3
  51. package/src/lib/display.ts +1 -111
  52. package/src/lib/format.ts +1 -64
  53. package/src/lib/lenses.ts +1 -220
  54. package/src/lib/repo.ts +1 -32
  55. package/src/lib/scores.ts +1 -90
  56. package/src/lib/search.ts +1 -418
  57. package/src/lib/taxonomy-counts.ts +1 -26
  58. package/{templates/default/src/data/records.ts → src/server/directory.ts} +90 -8
  59. package/src/server/index.ts +2 -0
  60. package/src/server/models.ts +432 -0
  61. package/dist/theme.test.d.ts +0 -2
  62. package/dist/theme.test.d.ts.map +0 -1
  63. package/dist/theme.test.js +0 -55
  64. package/dist/theme.test.js.map +0 -1
  65. package/src/theme.test.ts +0 -67
  66. package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +0 -33
  67. package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  68. package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +0 -48
  69. package/templates/default/.github/pull_request_template.md +0 -27
  70. package/templates/default/.github/workflows/build.yml +0 -55
  71. package/templates/default/.github/workflows/cleanup-stale-records.yml +0 -48
  72. package/templates/default/.github/workflows/daily-refresh.yml +0 -53
  73. package/templates/default/.github/workflows/sync-contributors.yml +0 -53
  74. package/templates/default/.github/workflows/sync-github-metadata.yml +0 -58
  75. package/templates/default/.github/workflows/validate-data.yml +0 -35
  76. package/templates/default/astro.config.mjs +0 -25
  77. package/templates/default/content/pages/about.md +0 -66
  78. package/templates/default/content/pages/methodology.md +0 -103
  79. package/templates/default/data/records/.gitkeep +0 -0
  80. package/templates/default/data/records/coolify.yml +0 -46
  81. package/templates/default/data/records/gitea.yml +0 -46
  82. package/templates/default/data/records/hoppscotch.yml +0 -47
  83. package/templates/default/data/records/inventree.yml +0 -47
  84. package/templates/default/data/records/label-studio.yml +0 -46
  85. package/templates/default/data/records/logseq.yml +0 -48
  86. package/templates/default/data/records/private-gpt.yml +0 -47
  87. package/templates/default/data/records/wakapi.yml +0 -44
  88. package/templates/default/data/taxonomy/categories.yml +0 -47
  89. package/templates/default/data/taxonomy/distribution-channels.yml +0 -43
  90. package/templates/default/data/taxonomy/platforms.yml +0 -27
  91. package/templates/default/data/taxonomy/stacks.yml +0 -41
  92. package/templates/default/grove.config.ts +0 -36
  93. package/templates/default/package.json +0 -38
  94. package/templates/default/public/icons/platforms/android.svg +0 -1
  95. package/templates/default/public/icons/platforms/apple-dark.svg +0 -1
  96. package/templates/default/public/icons/platforms/apple-light.svg +0 -1
  97. package/templates/default/public/icons/platforms/chrome.svg +0 -1
  98. package/templates/default/public/icons/platforms/chromeos.svg +0 -5
  99. package/templates/default/public/icons/platforms/desktop.svg +0 -4
  100. package/templates/default/public/icons/platforms/embedded.svg +0 -5
  101. package/templates/default/public/icons/platforms/ios.svg +0 -1
  102. package/templates/default/public/icons/platforms/linux.svg +0 -1
  103. package/templates/default/public/icons/platforms/macos.svg +0 -1
  104. package/templates/default/public/icons/platforms/ubuntu.svg +0 -1
  105. package/templates/default/public/icons/platforms/web.svg +0 -5
  106. package/templates/default/public/icons/platforms/windows.svg +0 -6
  107. package/templates/default/public/icons/stacks/android.svg +0 -1
  108. package/templates/default/public/icons/stacks/apple-dark.svg +0 -1
  109. package/templates/default/public/icons/stacks/apple-light.svg +0 -1
  110. package/templates/default/public/icons/stacks/bun.svg +0 -1
  111. package/templates/default/public/icons/stacks/capacitor.svg +0 -14
  112. package/templates/default/public/icons/stacks/clojure.svg +0 -1
  113. package/templates/default/public/icons/stacks/clojurescript.svg +0 -1
  114. package/templates/default/public/icons/stacks/dart.svg +0 -1
  115. package/templates/default/public/icons/stacks/deno.svg +0 -1
  116. package/templates/default/public/icons/stacks/django.svg +0 -1
  117. package/templates/default/public/icons/stacks/docker.svg +0 -1
  118. package/templates/default/public/icons/stacks/firebase.svg +0 -1
  119. package/templates/default/public/icons/stacks/flutter.svg +0 -1
  120. package/templates/default/public/icons/stacks/go.svg +0 -1
  121. package/templates/default/public/icons/stacks/graphql.svg +0 -1
  122. package/templates/default/public/icons/stacks/ionic.svg +0 -4
  123. package/templates/default/public/icons/stacks/java.svg +0 -1
  124. package/templates/default/public/icons/stacks/javascript.svg +0 -1
  125. package/templates/default/public/icons/stacks/kotlin.svg +0 -1
  126. package/templates/default/public/icons/stacks/llm.svg +0 -1
  127. package/templates/default/public/icons/stacks/mongodb.svg +0 -1
  128. package/templates/default/public/icons/stacks/node.js.svg +0 -1
  129. package/templates/default/public/icons/stacks/nodejs.svg +0 -1
  130. package/templates/default/public/icons/stacks/python.svg +0 -1
  131. package/templates/default/public/icons/stacks/rag.svg +0 -7
  132. package/templates/default/public/icons/stacks/react-native.svg +0 -1
  133. package/templates/default/public/icons/stacks/react.svg +0 -1
  134. package/templates/default/public/icons/stacks/rust.svg +0 -1
  135. package/templates/default/public/icons/stacks/solidity.svg +0 -1
  136. package/templates/default/public/icons/stacks/sveltekit.svg +0 -1
  137. package/templates/default/public/icons/stacks/swift.svg +0 -1
  138. package/templates/default/public/icons/stacks/tensorflow.svg +0 -1
  139. package/templates/default/public/icons/stacks/typescript.svg +0 -1
  140. package/templates/default/public/icons/stacks/vue.svg +0 -1
  141. package/templates/default/public/llms-full.txt +0 -206
  142. package/templates/default/public/llms.txt +0 -112
  143. package/templates/default/public/og-image.svg +0 -48
  144. package/templates/default/public/robots.txt +0 -53
  145. package/templates/default/public/sitemap.xml +0 -63
  146. package/templates/default/scripts/build-llms.mjs +0 -123
  147. package/templates/default/scripts/build-records-json.mjs +0 -27
  148. package/templates/default/scripts/build-sitemap.mjs +0 -20
  149. package/templates/default/scripts/cleanup-stale-records.mjs +0 -20
  150. package/templates/default/scripts/enrich-github-metadata.mjs +0 -99
  151. package/templates/default/scripts/fetch-icons.mjs +0 -137
  152. package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +0 -86
  153. package/templates/default/scripts/parse-legacy-readme.mjs +0 -59
  154. package/templates/default/scripts/refresh-records-activity.mjs +0 -24
  155. package/templates/default/scripts/repair-license-format.mjs +0 -72
  156. package/templates/default/scripts/report-cleanup-candidates.mjs +0 -21
  157. package/templates/default/scripts/seed-from-github.mjs +0 -62
  158. package/templates/default/scripts/sync-contributors.mjs +0 -145
  159. package/templates/default/scripts/sync-github-metadata.mjs +0 -28
  160. package/templates/default/scripts/validate-records.mjs +0 -27
  161. package/templates/default/src/pages/404.astro +0 -64
  162. package/templates/default/src/pages/[slug]/[recordSlug].astro +0 -689
  163. package/templates/default/src/pages/[slug]/index.astro +0 -278
  164. package/templates/default/src/pages/about.astro +0 -182
  165. package/templates/default/src/pages/apps/[recordSlug].astro +0 -33
  166. package/templates/default/src/pages/contributors.astro +0 -175
  167. package/templates/default/src/pages/index.astro +0 -225
  168. package/templates/default/src/pages/sitemap.xml.ts +0 -89
  169. package/templates/default/src/pages/submit.astro +0 -281
  170. package/templates/default/src/styles/global.css +0 -33
  171. package/templates/default/wrangler.jsonc +0 -28
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: MIT
3
-
4
- /**
5
- * report-cleanup-candidates.mjs — flag records that look like
6
- * they should be archived. Wrapper around `grove cleanup stale`,
7
- * which writes a markdown report under `data/generated/`.
8
- */
9
- import { spawnSync } from "node:child_process";
10
-
11
- const result = spawnSync("grove", ["cleanup", "stale"], {
12
- stdio: "inherit",
13
- env: process.env,
14
- });
15
-
16
- if (result.error) {
17
- console.error("Failed to spawn `grove cleanup stale`:", result.error.message);
18
- process.exit(1);
19
- }
20
-
21
- process.exit(result.status ?? 0);
@@ -1,62 +0,0 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: MIT
3
-
4
- /**
5
- * seed-from-github.mjs — interactive seeder for the first
6
- * record. Given a GitHub URL, fetch the metadata and write a
7
- * starter yml to `data/records/<slug>.yml`. Useful for the
8
- * "add my repo" onboarding path.
9
- */
10
- import { mkdir, writeFile } from "node:fs/promises";
11
- import { parse, stringify } from "yaml";
12
- import { dirname, join } from "node:path";
13
- import { fileURLToPath } from "node:url";
14
-
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
- const ROOT = join(__dirname, "..");
17
- const RECORDS_DIR = join(ROOT, "data", "records");
18
-
19
- const url = process.argv[2];
20
- if (!url) {
21
- console.error("Usage: node seed-from-github.mjs <github-url>");
22
- process.exit(1);
23
- }
24
-
25
- const match = String(url).match(/github\.com\/([^/]+)\/([^/?#]+)/);
26
- if (!match) {
27
- console.error("Not a GitHub URL:", url);
28
- process.exit(1);
29
- }
30
- const owner = match[1];
31
- const repo = match[2].replace(/\.git$/, "");
32
-
33
- const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
34
- headers: { Accept: "application/vnd.github+json", "User-Agent": "grove-seeder" },
35
- });
36
- if (!res.ok) {
37
- console.error("GitHub returned", res.status);
38
- process.exit(1);
39
- }
40
- const r = await res.json();
41
- const slug = repo.toLowerCase();
42
- const rec = {
43
- kind: "project",
44
- slug,
45
- name: r.name,
46
- description: r.description ?? "",
47
- repoUrl: r.html_url,
48
- homepageUrl: r.homepage ?? "",
49
- category: "tools",
50
- stack: r.language ?? "",
51
- stacks: [],
52
- platforms: [],
53
- tags: r.topics ?? [],
54
- stars: r.stargazers_count,
55
- license: r.license?.spdx_id ?? "",
56
- status: r.archived ? "archived" : "active",
57
- curation: { reviewed: false, labels: [] },
58
- };
59
-
60
- await mkdir(RECORDS_DIR, { recursive: true });
61
- await writeFile(join(RECORDS_DIR, `${slug}.yml`), stringify(rec), "utf8");
62
- console.log(`Wrote ${slug}.yml`);
@@ -1,145 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * sync-contributors — fetch contributors for every record's repoUrl
4
- * and write `data/generated/contributors.json` (consumed by the home
5
- * page and the /contributors page).
6
- *
7
- * Generic: walks every record in data/generated/records.index.json,
8
- * looks at record.github.fullName or links.github, and calls the
9
- * GitHub /repos/{owner}/{repo}/contributors endpoint. Aggregates by
10
- * owner (across all of an owner's projects in the directory).
11
- *
12
- * Anonymous contributors are excluded — the home grid renders <a>
13
- * avatars, and a name-less contributor is more confusing than
14
- * missing.
15
- *
16
- * Usage: node scripts/sync-contributors.mjs
17
- * Env: GH_TOKEN — required for >60 req/h. Falls back to unauth.
18
- *
19
- * V1 intentionally small: no retries, no rate-limit backoff beyond
20
- * the basic 403-detection. The weekly schedule in
21
- * .github/workflows/sync-contributors.yml is loose enough that
22
- * re-running the workflow is a fine recovery path.
23
- */
24
- import { readFile, writeFile, mkdir } from "node:fs/promises";
25
- import { dirname, resolve } from "node:path";
26
-
27
- const HERE = new URL(".", import.meta.url).pathname;
28
- const ROOT = resolve(HERE, "..");
29
- const GENERATED = resolve(ROOT, "data", "generated");
30
- const INDEX_PATH = resolve(GENERATED, "records.index.json");
31
- const OUT_PATH = resolve(GENERATED, "contributors.json");
32
-
33
- const GH_TOKEN = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
34
- const HEADERS = {
35
- Accept: "application/vnd.github+json",
36
- "User-Agent": "grove-sync-contributors",
37
- "X-GitHub-Api-Version": "2022-11-28",
38
- ...(GH_TOKEN ? { Authorization: `Bearer ${GH_TOKEN}` } : {}),
39
- };
40
-
41
- function ownerRepoFromFullName(fullName) {
42
- if (!fullName || !fullName.includes("/")) return null;
43
- const [owner, repo] = fullName.split("/", 2);
44
- return { owner, repo };
45
- }
46
-
47
- async function ghJson(url) {
48
- const res = await fetch(url, { headers: HEADERS });
49
- if (res.status === 204) return null;
50
- if (res.status === 403) {
51
- const remaining = res.headers.get("x-ratelimit-remaining");
52
- if (remaining === "0") {
53
- throw new Error(`rate-limited (403) on ${url}`);
54
- }
55
- return null;
56
- }
57
- if (res.status === 404) return null;
58
- if (!res.ok) throw new Error(`HTTP ${res.status} on ${url}`);
59
- return res.json();
60
- }
61
-
62
- async function fetchContributorsForRepo(owner, repo) {
63
- // GitHub caps /contributors at 500; for our purposes (fanning into
64
- // a home grid) we only need the first page. Anonymous entries
65
- // (no `login`) are dropped at parse time.
66
- const url = `https://api.github.com/repos/${owner}/${repo}/contributors?per_page=100&anon=false`;
67
- const data = await ghJson(url);
68
- if (!Array.isArray(data)) return [];
69
- return data
70
- .filter((c) => c && c.login)
71
- .map((c) => ({
72
- username: c.login,
73
- avatarUrl: c.avatar_url,
74
- profileUrl: c.html_url,
75
- contributions: c.contributions ?? 0,
76
- }));
77
- }
78
-
79
- async function main() {
80
- let index;
81
- try {
82
- index = JSON.parse(await readFile(INDEX_PATH, "utf8"));
83
- } catch (err) {
84
- console.error(`Could not read ${INDEX_PATH}: ${err.message}`);
85
- console.error("Run `pnpm run build:data` first.");
86
- process.exit(1);
87
- }
88
-
89
- const records = Array.isArray(index.records) ? index.records : [];
90
- // Group by owner — a single person with three projects in the
91
- // directory shows up once with the summed contribution count.
92
- const byOwner = new Map();
93
- const seenRepos = new Set();
94
-
95
- for (const r of records) {
96
- const gh = r.github || {};
97
- const ref = ownerRepoFromFullName(gh.fullName) || (() => {
98
- const url = r.repoUrl || (r.links && r.links.github) || "";
99
- const m = /github\.com\/([^/]+)\/([^/?#]+)/.exec(url);
100
- return m ? { owner: m[1], repo: m[2].replace(/\.git$/, "") } : null;
101
- })();
102
- if (!ref) continue;
103
- if (seenRepos.has(`${ref.owner}/${ref.repo}`)) continue;
104
- seenRepos.add(`${ref.owner}/${ref.repo}`);
105
-
106
- try {
107
- const list = await fetchContributorsForRepo(ref.owner, ref.repo);
108
- for (const c of list) {
109
- const existing = byOwner.get(c.username);
110
- if (existing) {
111
- existing.contributions = (existing.contributions ?? 0) + c.contributions;
112
- } else {
113
- byOwner.set(c.username, c);
114
- }
115
- }
116
- console.log(` ${ref.owner}/${ref.repo}: ${list.length} contributors`);
117
- } catch (err) {
118
- console.warn(` ${ref.owner}/${ref.repo}: ${err.message}`);
119
- }
120
- }
121
-
122
- const contributors = [...byOwner.values()].sort(
123
- (a, b) => (b.contributions ?? 0) - (a.contributions ?? 0),
124
- );
125
-
126
- await mkdir(dirname(OUT_PATH), { recursive: true });
127
- await writeFile(
128
- OUT_PATH,
129
- JSON.stringify(
130
- {
131
- generatedAt: new Date().toISOString(),
132
- contributors,
133
- },
134
- null,
135
- 2,
136
- ),
137
- "utf8",
138
- );
139
- console.log(`\nWrote ${contributors.length} contributors → ${OUT_PATH}`);
140
- }
141
-
142
- main().catch((err) => {
143
- console.error(err.stack || err.message);
144
- process.exit(1);
145
- });
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: MIT
3
-
4
- /**
5
- * sync-github-metadata.mjs — full metadata sync from GitHub's
6
- * REST API. Wrapper around `@grove-dev/cli sync github`.
7
- *
8
- * Refreshes stargazers_count, forks_count, license, default branch,
9
- * pushed_at, language, topics, and the contributor list for every
10
- * record whose `repoUrl` is a GitHub repository.
11
- *
12
- * Writes back into the corresponding `data/records/<slug>.yml` (the
13
- * CLI is conservative — only fills in fields that are currently
14
- * empty).
15
- */
16
- import { spawnSync } from "node:child_process";
17
-
18
- const result = spawnSync("grove", ["sync", "github"], {
19
- stdio: "inherit",
20
- env: process.env,
21
- });
22
-
23
- if (result.error) {
24
- console.error("Failed to spawn `grove sync github`:", result.error.message);
25
- process.exit(1);
26
- }
27
-
28
- process.exit(result.status ?? 0);
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
- // SPDX-License-Identifier: MIT
3
-
4
- /**
5
- * validate-records.mjs — Zod-schema validation for every record yml.
6
- *
7
- * Thin wrapper around `@grove-dev/cli`'s `validate` command so the
8
- * `pnpm run validate:data` script works without V0 command names.
9
- *
10
- * For schema authoring see `packages/core/src/schema.ts`. The CLI
11
- * handles every blueprint (project-directory, resource-hub,
12
- * ecosystem-map) and every record kind (project, resource, entity).
13
- */
14
- import { spawnSync } from "node:child_process";
15
-
16
- const result = spawnSync("grove", ["validate"], {
17
- stdio: "inherit",
18
- env: process.env,
19
- });
20
-
21
- if (result.error) {
22
- console.error("Failed to spawn `grove validate`:", result.error.message);
23
- console.error("Make sure `@grove-dev/cli` is installed and on PATH.");
24
- process.exit(1);
25
- }
26
-
27
- process.exit(result.status ?? 0);
@@ -1,64 +0,0 @@
1
- ---
2
- /**
3
- * 404 — generic fallback page.
4
- *
5
- * Renders the normal shell (header / footer / theme toggle) with a
6
- * short message and a search form pointing at the list page. Stays
7
- * on-brand regardless of blueprint.
8
- */
9
- import siteConfig from "../../data/generated/site-config.json";
10
- import { indexSlug } from "../data/records";
11
- import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
12
-
13
- const slug = indexSlug();
14
- const title = `Not found — ${siteConfig.name}`;
15
- const description = `That page doesn't exist on ${siteConfig.name}.`;
16
- ---
17
-
18
- <BaseLayout
19
- title={title}
20
- description={description}
21
- site={siteConfig}
22
- noindex
23
- >
24
- <section class="py-12 sm:py-16">
25
- <div class="mx-auto w-full max-w-container px-5 py-20 sm:px-7 sm:py-28">
26
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
27
- 404
28
- </span>
29
- <h1 class="m-0 mt-2 text-[2.25rem] sm:text-[3rem] font-semibold leading-[1.05] tracking-tight text-ink-900 dark:text-ink-100">
30
- Page not found
31
- </h1>
32
- <p class="m-0 mt-4 max-w-xl text-lg leading-relaxed text-ink-700 dark:text-ink-200">
33
- The page you're looking for doesn't exist on {siteConfig.name}.
34
- It may have been moved, renamed, or never existed.
35
- </p>
36
-
37
- <form
38
- class="mt-8 flex max-w-xl flex-col gap-2 sm:flex-row sm:items-center"
39
- role="search"
40
- action={`/${slug}`}
41
- method="get"
42
- >
43
- <label class="relative flex-1">
44
- <span class="sr-only">Search</span>
45
- <input
46
- type="search"
47
- name="q"
48
- placeholder={`Search ${slug}...`}
49
- autocomplete="off"
50
- class="w-full rounded-md border border-ink-200 bg-white py-2.5 px-3 text-sm placeholder:text-ink-400 focus:border-ink-900 focus:outline-none focus:ring-2 focus:ring-ink-900/10 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-100"
51
- />
52
- </label>
53
- <button type="submit" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">
54
- Search
55
- </button>
56
- </form>
57
-
58
- <div class="mt-6 flex flex-wrap gap-3">
59
- <a href="/" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">Back to home</a>
60
- <a href={`/${slug}`} class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent">Browse {slug}</a>
61
- </div>
62
- </div>
63
- </section>
64
- </BaseLayout>