@grove-dev/astro 0.2.19 → 0.3.1
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.
- package/LICENSE +21 -0
- package/dist/template-routes.test.d.ts +2 -0
- package/dist/template-routes.test.d.ts.map +1 -0
- package/dist/template-routes.test.js +63 -0
- package/dist/template-routes.test.js.map +1 -0
- package/dist/theme.test.js +19 -0
- package/dist/theme.test.js.map +1 -1
- package/package.json +4 -4
- package/src/components/CategoryGrid.astro +2 -11
- package/src/components/ContributorsGrid.astro +2 -11
- package/src/components/CurationGrid.astro +1 -1
- package/src/components/DecisionRow.astro +1 -1
- package/src/components/ExploreByCategory.astro +3 -3
- package/src/components/ExploreByStack.astro +3 -3
- package/src/components/FilterGroupMenu.astro +5 -5
- package/src/components/Hero.astro +8 -8
- package/src/components/Icon.astro +56 -53
- package/src/components/IndexRow.astro +5 -5
- package/src/components/ItemCard.astro +5 -16
- package/src/components/MinimalAbout.astro +2 -2
- package/src/components/OriginalCollection.astro +4 -4
- package/src/components/Pagination.astro +2 -2
- package/src/components/RecordSection.astro +1 -1
- package/src/components/RefinePanel.astro +1 -1
- package/src/components/SmartLensTabs.astro +3 -3
- package/src/components/StackGrid.astro +6 -15
- package/src/components/WhyThisExists.astro +3 -3
- package/src/layouts/Container.astro +4 -4
- package/src/layouts/Footer.astro +3 -3
- package/src/layouts/Header.astro +5 -5
- package/src/layouts/ThemeToggle.astro +1 -1
- package/src/styles.css +33 -339
- package/src/template-routes.test.ts +103 -0
- package/src/theme.test.ts +24 -0
- package/templates/default/.github/workflows/build.yml +1 -3
- package/templates/default/.github/workflows/cleanup-stale-records.yml +1 -3
- package/templates/default/.github/workflows/daily-refresh.yml +1 -3
- package/templates/default/.github/workflows/sync-contributors.yml +2 -4
- package/templates/default/.github/workflows/sync-github-metadata.yml +1 -3
- package/templates/default/.github/workflows/validate-data.yml +1 -3
- package/templates/default/grove.config.ts +1 -1
- package/templates/default/package.json +9 -4
- package/templates/default/public/icons/stacks/rag.svg +7 -0
- package/templates/default/public/llms-full.txt +63 -186
- package/templates/default/public/llms.txt +6 -107
- package/templates/default/public/sitemap.xml +10 -10
- package/templates/default/src/data/records.ts +80 -2
- package/templates/default/src/pages/404.astro +5 -5
- package/templates/default/src/pages/[slug]/[recordSlug].astro +36 -38
- package/templates/default/src/pages/[slug]/index.astro +223 -19
- package/templates/default/src/pages/about.astro +17 -10
- package/templates/default/src/pages/apps/[recordSlug].astro +2 -1
- package/templates/default/src/pages/contributors.astro +8 -17
- package/templates/default/src/pages/index.astro +11 -3
- package/templates/default/src/pages/sitemap.xml.ts +3 -3
- package/templates/default/src/pages/submit.astro +28 -36
- package/templates/default/tsconfig.json +9 -0
- package/templates/default/scripts/build-llms.mjs +0 -123
- package/templates/default/scripts/build-records-json.mjs +0 -27
- package/templates/default/scripts/build-sitemap.mjs +0 -20
- package/templates/default/scripts/cleanup-stale-records.mjs +0 -20
- package/templates/default/scripts/enrich-github-metadata.mjs +0 -99
- package/templates/default/scripts/fetch-icons.mjs +0 -137
- package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +0 -86
- package/templates/default/scripts/parse-legacy-readme.mjs +0 -59
- package/templates/default/scripts/refresh-records-activity.mjs +0 -24
- package/templates/default/scripts/repair-license-format.mjs +0 -72
- package/templates/default/scripts/report-cleanup-candidates.mjs +0 -21
- package/templates/default/scripts/seed-from-github.mjs +0 -62
- package/templates/default/scripts/sync-contributors.mjs +0 -145
- package/templates/default/scripts/sync-github-metadata.mjs +0 -28
- package/templates/default/scripts/validate-records.mjs +0 -27
|
@@ -2,61 +2,61 @@
|
|
|
2
2
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
3
|
<url>
|
|
4
4
|
<loc>https://example.com/</loc>
|
|
5
|
-
<lastmod>2026-
|
|
5
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
6
6
|
<changefreq>daily</changefreq>
|
|
7
7
|
<priority>1.0</priority>
|
|
8
8
|
</url>
|
|
9
9
|
<url>
|
|
10
10
|
<loc>https://example.com/projects</loc>
|
|
11
|
-
<lastmod>2026-
|
|
11
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
12
12
|
<changefreq>daily</changefreq>
|
|
13
13
|
<priority>0.9</priority>
|
|
14
14
|
</url>
|
|
15
15
|
<url>
|
|
16
16
|
<loc>https://example.com/projects/coolify</loc>
|
|
17
|
-
<lastmod>2026-
|
|
17
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
18
18
|
<changefreq>weekly</changefreq>
|
|
19
19
|
<priority>0.7</priority>
|
|
20
20
|
</url>
|
|
21
21
|
<url>
|
|
22
22
|
<loc>https://example.com/projects/gitea</loc>
|
|
23
|
-
<lastmod>2026-
|
|
23
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
24
24
|
<changefreq>weekly</changefreq>
|
|
25
25
|
<priority>0.7</priority>
|
|
26
26
|
</url>
|
|
27
27
|
<url>
|
|
28
28
|
<loc>https://example.com/projects/hoppscotch</loc>
|
|
29
|
-
<lastmod>2026-
|
|
29
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
30
30
|
<changefreq>weekly</changefreq>
|
|
31
31
|
<priority>0.7</priority>
|
|
32
32
|
</url>
|
|
33
33
|
<url>
|
|
34
34
|
<loc>https://example.com/projects/inventree</loc>
|
|
35
|
-
<lastmod>2026-
|
|
35
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
36
36
|
<changefreq>weekly</changefreq>
|
|
37
37
|
<priority>0.7</priority>
|
|
38
38
|
</url>
|
|
39
39
|
<url>
|
|
40
40
|
<loc>https://example.com/projects/label-studio</loc>
|
|
41
|
-
<lastmod>2026-
|
|
41
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
42
42
|
<changefreq>weekly</changefreq>
|
|
43
43
|
<priority>0.7</priority>
|
|
44
44
|
</url>
|
|
45
45
|
<url>
|
|
46
46
|
<loc>https://example.com/projects/logseq</loc>
|
|
47
|
-
<lastmod>2026-
|
|
47
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
48
48
|
<changefreq>weekly</changefreq>
|
|
49
49
|
<priority>0.7</priority>
|
|
50
50
|
</url>
|
|
51
51
|
<url>
|
|
52
52
|
<loc>https://example.com/projects/private-gpt</loc>
|
|
53
|
-
<lastmod>2026-
|
|
53
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
54
54
|
<changefreq>weekly</changefreq>
|
|
55
55
|
<priority>0.7</priority>
|
|
56
56
|
</url>
|
|
57
57
|
<url>
|
|
58
58
|
<loc>https://example.com/projects/wakapi</loc>
|
|
59
|
-
<lastmod>2026-
|
|
59
|
+
<lastmod>2026-07-01T16:50:32.236Z</lastmod>
|
|
60
60
|
<changefreq>weekly</changefreq>
|
|
61
61
|
<priority>0.7</priority>
|
|
62
62
|
</url>
|
|
@@ -38,6 +38,7 @@ import { fileURLToPath } from "node:url";
|
|
|
38
38
|
import { dirname, resolve } from "node:path";
|
|
39
39
|
import { marked } from "marked";
|
|
40
40
|
import sanitizeHtml from "sanitize-html";
|
|
41
|
+
import { prettySlug } from "@grove-dev/astro";
|
|
41
42
|
import type {
|
|
42
43
|
ProjectRecord,
|
|
43
44
|
ResourceRecord,
|
|
@@ -79,12 +80,39 @@ interface SiteConfigPayload {
|
|
|
79
80
|
labelSingular?: string;
|
|
80
81
|
labelPlural?: string;
|
|
81
82
|
};
|
|
83
|
+
taxonomy?: Partial<
|
|
84
|
+
Record<
|
|
85
|
+
"categories" | "stacks" | "platforms" | "distributionChannels",
|
|
86
|
+
Array<{ id: string; name: string }>
|
|
87
|
+
>
|
|
88
|
+
>;
|
|
82
89
|
name?: string;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
const fullRecordsRaw: Resource[] =
|
|
86
|
-
|
|
92
|
+
const fullRecordsRaw: Resource[] =
|
|
93
|
+
(fullPayload as unknown as FullPayload).records ?? [];
|
|
94
|
+
const indexRecordsRaw: IndexRecord[] =
|
|
95
|
+
(indexPayload as unknown as IndexPayload).records ?? [];
|
|
87
96
|
const siteConfigRaw: SiteConfigPayload = siteConfigPayload as SiteConfigPayload;
|
|
97
|
+
const taxonomyMaps = Object.fromEntries(
|
|
98
|
+
Object.entries(siteConfigRaw.taxonomy ?? {}).map(([kind, entries]) => [
|
|
99
|
+
kind,
|
|
100
|
+
new Map((entries ?? []).map((entry) => [entry.id, entry.name])),
|
|
101
|
+
]),
|
|
102
|
+
) as Partial<
|
|
103
|
+
Record<
|
|
104
|
+
"categories" | "stacks" | "platforms" | "distributionChannels",
|
|
105
|
+
Map<string, string>
|
|
106
|
+
>
|
|
107
|
+
>;
|
|
108
|
+
export const taxonomy = siteConfigRaw.taxonomy ?? {};
|
|
109
|
+
|
|
110
|
+
export function taxonomyLabel(
|
|
111
|
+
kind: "categories" | "stacks" | "platforms" | "distributionChannels",
|
|
112
|
+
id: string,
|
|
113
|
+
): string {
|
|
114
|
+
return taxonomyMaps[kind]?.get(id) ?? prettySlug(id);
|
|
115
|
+
}
|
|
88
116
|
|
|
89
117
|
/** Full records (every record, all visibility). Use this for the
|
|
90
118
|
* detail page (where you need `content`, `bestFor`, `whyListed`,
|
|
@@ -224,6 +252,10 @@ export const fullItems: Resource[] = (() => {
|
|
|
224
252
|
return fullRecords.filter((r) => r.kind === blueprintKind);
|
|
225
253
|
})();
|
|
226
254
|
|
|
255
|
+
export const fullProjects: ProjectRecord[] = fullRecords.filter(
|
|
256
|
+
(record): record is ProjectRecord => record.kind === "project",
|
|
257
|
+
);
|
|
258
|
+
|
|
227
259
|
// ──────────────────────────────────────────────────────────────────────
|
|
228
260
|
// Markdown content rendering (sanitized)
|
|
229
261
|
// ──────────────────────────────────────────────────────────────────────
|
|
@@ -316,3 +348,49 @@ for (const r of fullRecords) {
|
|
|
316
348
|
export function getContentHtml(slug: string): string | null {
|
|
317
349
|
return contentHtmlBySlug.get(slug) ?? null;
|
|
318
350
|
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Sanitized Markdown for a consumer-authored page under
|
|
354
|
+
* `content/pages/<page>.md`. Default template pages use this to
|
|
355
|
+
* accept custom copy while keeping Grove's layout and components.
|
|
356
|
+
*/
|
|
357
|
+
export function getPageContentHtml(page: string): string | null {
|
|
358
|
+
const candidates = [
|
|
359
|
+
resolve(here, "..", "..", "content", "pages", `${page}.md`),
|
|
360
|
+
resolve(here, "..", "..", "..", "content", "pages", `${page}.md`),
|
|
361
|
+
resolve(process.cwd(), "content", "pages", `${page}.md`),
|
|
362
|
+
];
|
|
363
|
+
const path = candidates.find((candidate) => existsSync(candidate));
|
|
364
|
+
if (!path) return null;
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const rawHtml = marked.parse(readFileSync(path, "utf8"), { async: false }) as string;
|
|
368
|
+
return sanitizeHtml(rawHtml, {
|
|
369
|
+
allowedTags: [
|
|
370
|
+
"h1", "h2", "h3", "h4",
|
|
371
|
+
"p", "br", "hr",
|
|
372
|
+
"ul", "ol", "li",
|
|
373
|
+
"strong", "em", "b", "i", "u", "s", "del",
|
|
374
|
+
"a", "code", "pre", "blockquote", "img",
|
|
375
|
+
],
|
|
376
|
+
allowedAttributes: {
|
|
377
|
+
a: ["href", "title", "rel", "target"],
|
|
378
|
+
img: ["src", "alt", "title", "width", "height", "loading"],
|
|
379
|
+
},
|
|
380
|
+
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
381
|
+
allowedSchemesByTag: {
|
|
382
|
+
a: ["http", "https", "mailto", "tel"],
|
|
383
|
+
img: ["http", "https"],
|
|
384
|
+
},
|
|
385
|
+
transformTags: {
|
|
386
|
+
a: sanitizeHtml.simpleTransform("a", {
|
|
387
|
+
rel: "noopener noreferrer",
|
|
388
|
+
target: "_blank",
|
|
389
|
+
}, true),
|
|
390
|
+
},
|
|
391
|
+
disallowedTagsMode: "discard",
|
|
392
|
+
});
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -21,8 +21,8 @@ const description = `That page doesn't exist on ${siteConfig.name}.`;
|
|
|
21
21
|
site={siteConfig}
|
|
22
22
|
noindex
|
|
23
23
|
>
|
|
24
|
-
<section class="
|
|
25
|
-
<div class="
|
|
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
26
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
27
27
|
404
|
|
28
28
|
</span>
|
|
@@ -50,14 +50,14 @@ const description = `That page doesn't exist on ${siteConfig.name}.`;
|
|
|
50
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
51
|
/>
|
|
52
52
|
</label>
|
|
53
|
-
<button type="submit" class="
|
|
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
54
|
Search
|
|
55
55
|
</button>
|
|
56
56
|
</form>
|
|
57
57
|
|
|
58
58
|
<div class="mt-6 flex flex-wrap gap-3">
|
|
59
|
-
<a href="/" class="
|
|
60
|
-
<a href={`/${slug}`} class="
|
|
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
61
|
</div>
|
|
62
62
|
</div>
|
|
63
63
|
</section>
|
|
@@ -64,7 +64,7 @@ if (!record) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const isProject = record.kind === "project";
|
|
67
|
-
const proj: ProjectRecord | null = project;
|
|
67
|
+
const proj: ProjectRecord | null = project ?? null;
|
|
68
68
|
|
|
69
69
|
const name =
|
|
70
70
|
record.kind === "resource" ? (record as { title: string }).title : (record as { name: string }).name;
|
|
@@ -101,18 +101,14 @@ const tags = record.tags ?? [];
|
|
|
101
101
|
const healthStatus = proj?.health?.status;
|
|
102
102
|
const healthLabel = healthStatus ? statusDisplay(healthStatus) : null;
|
|
103
103
|
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
// name — every badge became `grove-badge-{l}` regardless of value.
|
|
108
|
-
// Map each known id to its stable CSS class in the package
|
|
109
|
-
// stylesheet. Unknown ids (e.g. legacy "featured") fall back to the
|
|
110
|
-
// neutral `.grove-badge` look instead of producing broken classes.
|
|
104
|
+
// Map each known label to a static Tailwind class string so every
|
|
105
|
+
// utility is visible to Tailwind's source scanner. Unknown/legacy
|
|
106
|
+
// labels use the neutral secondary badge treatment.
|
|
111
107
|
const labelClassMap: Record<LabelId, string> = {
|
|
112
|
-
new: "
|
|
113
|
-
hot: "
|
|
114
|
-
mature: "
|
|
115
|
-
featured: "
|
|
108
|
+
new: "!bg-emerald-50 !text-emerald-700 dark:!bg-emerald-950/80 dark:!text-emerald-300",
|
|
109
|
+
hot: "!bg-orange-50 !text-orange-800 dark:!bg-orange-950/80 dark:!text-orange-300",
|
|
110
|
+
mature: "!bg-blue-50 !text-blue-700 dark:!bg-blue-950/80 dark:!text-blue-300",
|
|
111
|
+
featured: "inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground",
|
|
116
112
|
};
|
|
117
113
|
|
|
118
114
|
// ── Optional content markdown (record.content is a path under
|
|
@@ -224,18 +220,20 @@ if (isProject && proj) {
|
|
|
224
220
|
keywords: (record.tags ?? []).length > 0 ? (record.tags ?? []).join(", ") : undefined,
|
|
225
221
|
isAccessibleForFree: true,
|
|
226
222
|
};
|
|
227
|
-
} else {
|
|
223
|
+
} else if (entity) {
|
|
228
224
|
jsonLd = {
|
|
229
225
|
"@context": "https://schema.org",
|
|
230
|
-
"@type":
|
|
226
|
+
"@type": entity.type === "person" ? "Person" : "Organization",
|
|
231
227
|
name,
|
|
232
228
|
headline: name,
|
|
233
|
-
description:
|
|
229
|
+
description: entity.description || undefined,
|
|
234
230
|
url: homepageUrl || undefined,
|
|
235
|
-
foundingDate:
|
|
236
|
-
location:
|
|
237
|
-
keywords: (
|
|
231
|
+
foundingDate: entity.founded || undefined,
|
|
232
|
+
location: entity.location || undefined,
|
|
233
|
+
keywords: (entity.tags ?? []).length > 0 ? (entity.tags ?? []).join(", ") : undefined,
|
|
238
234
|
};
|
|
235
|
+
} else {
|
|
236
|
+
jsonLd = {};
|
|
239
237
|
}
|
|
240
238
|
---
|
|
241
239
|
|
|
@@ -248,8 +246,8 @@ if (isProject && proj) {
|
|
|
248
246
|
submitHref="/submit"
|
|
249
247
|
submitLabel={`Submit ${name}`}
|
|
250
248
|
>
|
|
251
|
-
<article class="
|
|
252
|
-
<div class="
|
|
249
|
+
<article class="py-12 sm:py-16">
|
|
250
|
+
<div class="mx-auto w-full max-w-wide px-5 sm:px-7">
|
|
253
251
|
<nav class="mb-6 text-2xs text-ink-500 dark:text-ink-400" aria-label="Breadcrumb">
|
|
254
252
|
<a href={`/${slug}`} class="hover:text-ink-900 dark:hover:text-ink-100 capitalize">
|
|
255
253
|
{slug}
|
|
@@ -259,7 +257,7 @@ if (isProject && proj) {
|
|
|
259
257
|
</nav>
|
|
260
258
|
|
|
261
259
|
{/* ── AppCard-style header ──────────────────────────────── */}
|
|
262
|
-
<header class="
|
|
260
|
+
<header class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-col gap-5 p-6 sm:flex-row sm:items-start">
|
|
263
261
|
<div class="flex-shrink-0">
|
|
264
262
|
{avatarSrc ? (
|
|
265
263
|
<img
|
|
@@ -292,7 +290,7 @@ if (isProject && proj) {
|
|
|
292
290
|
</span>
|
|
293
291
|
)}
|
|
294
292
|
{curationLabels.map((l) => (
|
|
295
|
-
<span class={`
|
|
293
|
+
<span class={`inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground ${labelClassMap[l as LabelId] ?? ""}`} title={`Curated: ${labelDisplay(l) ?? l}`}>
|
|
296
294
|
{labelDisplay(l) ?? prettySlug(l)}
|
|
297
295
|
</span>
|
|
298
296
|
))}
|
|
@@ -308,7 +306,7 @@ if (isProject && proj) {
|
|
|
308
306
|
</span>
|
|
309
307
|
)}
|
|
310
308
|
{healthLabel && (
|
|
311
|
-
<span class="
|
|
309
|
+
<span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground !bg-amber-50 !text-amber-800 dark:!bg-amber-950/80 dark:!text-amber-300">{healthLabel}</span>
|
|
312
310
|
)}
|
|
313
311
|
</div>
|
|
314
312
|
|
|
@@ -350,7 +348,7 @@ if (isProject && proj) {
|
|
|
350
348
|
href={repoUrl}
|
|
351
349
|
target="_blank"
|
|
352
350
|
rel="noopener noreferrer"
|
|
353
|
-
class="
|
|
351
|
+
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"
|
|
354
352
|
>
|
|
355
353
|
View on GitHub →
|
|
356
354
|
</a>
|
|
@@ -360,7 +358,7 @@ if (isProject && proj) {
|
|
|
360
358
|
href={homepageUrl}
|
|
361
359
|
target="_blank"
|
|
362
360
|
rel="noopener noreferrer"
|
|
363
|
-
class="
|
|
361
|
+
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"
|
|
364
362
|
>
|
|
365
363
|
Homepage
|
|
366
364
|
</a>
|
|
@@ -371,7 +369,7 @@ if (isProject && proj) {
|
|
|
371
369
|
|
|
372
370
|
{/* ── Score bars ───────────────────────────────────────── */}
|
|
373
371
|
{proj && (
|
|
374
|
-
<section class="mt-8
|
|
372
|
+
<section class="mt-8 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-6" aria-labelledby="scores-heading">
|
|
375
373
|
<h2 id="scores-heading" class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
376
374
|
Scores
|
|
377
375
|
</h2>
|
|
@@ -396,7 +394,7 @@ if (isProject && proj) {
|
|
|
396
394
|
|
|
397
395
|
{/* ── GitHub signals dl ────────────────────────────────── */}
|
|
398
396
|
{isProject && github && (
|
|
399
|
-
<section class="mt-8
|
|
397
|
+
<section class="mt-8 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-6" aria-labelledby="signals-heading">
|
|
400
398
|
<h2 id="signals-heading" class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
401
399
|
GitHub signals
|
|
402
400
|
</h2>
|
|
@@ -461,8 +459,8 @@ if (isProject && proj) {
|
|
|
461
459
|
{/* ── Decision signals (3-col) ──────────────────────────── */}
|
|
462
460
|
{isProject && github && (
|
|
463
461
|
<section class="mt-8 grid grid-cols-1 gap-4 lg:grid-cols-3" aria-label="Decision signals">
|
|
464
|
-
{/* Repository health
|
|
465
|
-
<div class="
|
|
462
|
+
{/* Repository health card */}
|
|
463
|
+
<div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
466
464
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
467
465
|
Repository health
|
|
468
466
|
</h3>
|
|
@@ -501,7 +499,7 @@ if (isProject && proj) {
|
|
|
501
499
|
</div>
|
|
502
500
|
|
|
503
501
|
{/* Activity bar chart */}
|
|
504
|
-
<div class="
|
|
502
|
+
<div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
505
503
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
506
504
|
Activity
|
|
507
505
|
</h3>
|
|
@@ -537,7 +535,7 @@ if (isProject && proj) {
|
|
|
537
535
|
</div>
|
|
538
536
|
|
|
539
537
|
{/* Contribution readiness chips */}
|
|
540
|
-
<div class="
|
|
538
|
+
<div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
541
539
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
542
540
|
Contribution readiness
|
|
543
541
|
</h3>
|
|
@@ -563,7 +561,7 @@ if (isProject && proj) {
|
|
|
563
561
|
|
|
564
562
|
{/* ── Language mix ─────────────────────────────────────── */}
|
|
565
563
|
{isProject && languages.length > 0 && (
|
|
566
|
-
<section class="mt-4
|
|
564
|
+
<section class="mt-4 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
567
565
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
568
566
|
Language mix
|
|
569
567
|
</h3>
|
|
@@ -597,7 +595,7 @@ if (isProject && proj) {
|
|
|
597
595
|
Stack
|
|
598
596
|
</span>
|
|
599
597
|
{stacks.map((s) => (
|
|
600
|
-
<span class="
|
|
598
|
+
<span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5">
|
|
601
599
|
<Icon name={s} category="stack" size={14} />
|
|
602
600
|
{s}
|
|
603
601
|
</span>
|
|
@@ -610,7 +608,7 @@ if (isProject && proj) {
|
|
|
610
608
|
Platform
|
|
611
609
|
</span>
|
|
612
610
|
{platforms.map((p) => (
|
|
613
|
-
<span class="
|
|
611
|
+
<span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5">
|
|
614
612
|
<Icon name={p} category="platform" size={14} />
|
|
615
613
|
{p}
|
|
616
614
|
</span>
|
|
@@ -632,7 +630,7 @@ if (isProject && proj) {
|
|
|
632
630
|
<li>
|
|
633
631
|
<a
|
|
634
632
|
href={`/${slug}?q=${encodeURIComponent(t)}`}
|
|
635
|
-
class="
|
|
633
|
+
class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5"
|
|
636
634
|
>
|
|
637
635
|
{t}
|
|
638
636
|
</a>
|
|
@@ -650,12 +648,12 @@ if (isProject && proj) {
|
|
|
650
648
|
</h2>
|
|
651
649
|
<ul class="mt-4 m-0 flex list-none flex-col gap-2 p-0">
|
|
652
650
|
{distribution.map((c) => (
|
|
653
|
-
<li class="
|
|
651
|
+
<li class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-wrap items-baseline justify-between gap-2 p-3">
|
|
654
652
|
<div class="flex flex-col">
|
|
655
653
|
<span class="text-sm font-semibold text-ink-900 dark:text-ink-100">
|
|
656
654
|
{c.label ?? c.type}
|
|
657
655
|
{c.verified && (
|
|
658
|
-
<span class="ml-2
|
|
656
|
+
<span class="ml-2 inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground !bg-emerald-50 !text-emerald-700 dark:!bg-emerald-950/80 dark:!text-emerald-300">Verified</span>
|
|
659
657
|
)}
|
|
660
658
|
</span>
|
|
661
659
|
{c.notes && (
|
|
@@ -666,7 +664,7 @@ if (isProject && proj) {
|
|
|
666
664
|
href={c.url}
|
|
667
665
|
target="_blank"
|
|
668
666
|
rel="noopener noreferrer"
|
|
669
|
-
class="
|
|
667
|
+
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"
|
|
670
668
|
>
|
|
671
669
|
{c.platform ?? c.type} →
|
|
672
670
|
</a>
|