@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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/dist/template-routes.test.d.ts +2 -0
  3. package/dist/template-routes.test.d.ts.map +1 -0
  4. package/dist/template-routes.test.js +63 -0
  5. package/dist/template-routes.test.js.map +1 -0
  6. package/dist/theme.test.js +19 -0
  7. package/dist/theme.test.js.map +1 -1
  8. package/package.json +4 -4
  9. package/src/components/CategoryGrid.astro +2 -11
  10. package/src/components/ContributorsGrid.astro +2 -11
  11. package/src/components/CurationGrid.astro +1 -1
  12. package/src/components/DecisionRow.astro +1 -1
  13. package/src/components/ExploreByCategory.astro +3 -3
  14. package/src/components/ExploreByStack.astro +3 -3
  15. package/src/components/FilterGroupMenu.astro +5 -5
  16. package/src/components/Hero.astro +8 -8
  17. package/src/components/Icon.astro +56 -53
  18. package/src/components/IndexRow.astro +5 -5
  19. package/src/components/ItemCard.astro +5 -16
  20. package/src/components/MinimalAbout.astro +2 -2
  21. package/src/components/OriginalCollection.astro +4 -4
  22. package/src/components/Pagination.astro +2 -2
  23. package/src/components/RecordSection.astro +1 -1
  24. package/src/components/RefinePanel.astro +1 -1
  25. package/src/components/SmartLensTabs.astro +3 -3
  26. package/src/components/StackGrid.astro +6 -15
  27. package/src/components/WhyThisExists.astro +3 -3
  28. package/src/layouts/Container.astro +4 -4
  29. package/src/layouts/Footer.astro +3 -3
  30. package/src/layouts/Header.astro +5 -5
  31. package/src/layouts/ThemeToggle.astro +1 -1
  32. package/src/styles.css +33 -339
  33. package/src/template-routes.test.ts +103 -0
  34. package/src/theme.test.ts +24 -0
  35. package/templates/default/.github/workflows/build.yml +1 -3
  36. package/templates/default/.github/workflows/cleanup-stale-records.yml +1 -3
  37. package/templates/default/.github/workflows/daily-refresh.yml +1 -3
  38. package/templates/default/.github/workflows/sync-contributors.yml +2 -4
  39. package/templates/default/.github/workflows/sync-github-metadata.yml +1 -3
  40. package/templates/default/.github/workflows/validate-data.yml +1 -3
  41. package/templates/default/grove.config.ts +1 -1
  42. package/templates/default/package.json +9 -4
  43. package/templates/default/public/icons/stacks/rag.svg +7 -0
  44. package/templates/default/public/llms-full.txt +63 -186
  45. package/templates/default/public/llms.txt +6 -107
  46. package/templates/default/public/sitemap.xml +10 -10
  47. package/templates/default/src/data/records.ts +80 -2
  48. package/templates/default/src/pages/404.astro +5 -5
  49. package/templates/default/src/pages/[slug]/[recordSlug].astro +36 -38
  50. package/templates/default/src/pages/[slug]/index.astro +223 -19
  51. package/templates/default/src/pages/about.astro +17 -10
  52. package/templates/default/src/pages/apps/[recordSlug].astro +2 -1
  53. package/templates/default/src/pages/contributors.astro +8 -17
  54. package/templates/default/src/pages/index.astro +11 -3
  55. package/templates/default/src/pages/sitemap.xml.ts +3 -3
  56. package/templates/default/src/pages/submit.astro +28 -36
  57. package/templates/default/tsconfig.json +9 -0
  58. package/templates/default/scripts/build-llms.mjs +0 -123
  59. package/templates/default/scripts/build-records-json.mjs +0 -27
  60. package/templates/default/scripts/build-sitemap.mjs +0 -20
  61. package/templates/default/scripts/cleanup-stale-records.mjs +0 -20
  62. package/templates/default/scripts/enrich-github-metadata.mjs +0 -99
  63. package/templates/default/scripts/fetch-icons.mjs +0 -137
  64. package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +0 -86
  65. package/templates/default/scripts/parse-legacy-readme.mjs +0 -59
  66. package/templates/default/scripts/refresh-records-activity.mjs +0 -24
  67. package/templates/default/scripts/repair-license-format.mjs +0 -72
  68. package/templates/default/scripts/report-cleanup-candidates.mjs +0 -21
  69. package/templates/default/scripts/seed-from-github.mjs +0 -62
  70. package/templates/default/scripts/sync-contributors.mjs +0 -145
  71. package/templates/default/scripts/sync-github-metadata.mjs +0 -28
  72. package/templates/default/scripts/validate-records.mjs +0 -27
@@ -23,7 +23,7 @@ function pageList(active: number, count: number): Array<number | "ellipsis"> {
23
23
 
24
24
  const pages = pageList(current, total);
25
25
  const linkClass =
26
- "grove-btn grove-btn-outline min-w-9 px-2 py-1.5 text-2xs";
26
+ "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 min-w-9 px-2 py-1.5 text-2xs";
27
27
  ---
28
28
 
29
29
  {total > 1 && (
@@ -45,7 +45,7 @@ const linkClass =
45
45
  href={hrefFor(page)}
46
46
  class:list={[
47
47
  linkClass,
48
- page === current && "grove-btn-primary",
48
+ page === current && "bg-primary text-primary-foreground hover:bg-primary/90",
49
49
  ]}
50
50
  aria-current={page === current ? "page" : undefined}
51
51
  >
@@ -68,7 +68,7 @@ const visible = items.slice(0, 6);
68
68
 
69
69
  <section
70
70
  id={id}
71
- class={`grove-section scroll-mt-20 ${className}`}
71
+ class={`py-12 sm:py-16 scroll-mt-20 ${className}`}
72
72
  >
73
73
  <Container>
74
74
  <div class="mb-6 flex flex-wrap items-end justify-between gap-3">
@@ -104,7 +104,7 @@ const groups: RenderedFilterGroup[] = baseGroups
104
104
  {!hide.includes("sort") && (
105
105
  <label class="relative">
106
106
  <span class="sr-only">Sort items</span>
107
- <select name="sort" class="h-9 cursor-pointer appearance-none rounded-md border border-ink-200 bg-white py-1.5 pl-3 pr-8 text-[0.8125rem] font-medium text-ink-700 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300">
107
+ <select name="sort" class="h-9 cursor-pointer appearance-none border py-1.5 pl-3 pr-8 text-[0.8125rem] font-medium">
108
108
  {SORT_OPTIONS.map((option) => <option value={option.value} selected={(initial.sort ?? "recently-updated") === option.value}>{option.label}</option>)}
109
109
  </select>
110
110
  <svg viewBox="0 0 16 16" width="11" height="11" aria-hidden="true" class="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-ink-400" fill="currentColor">
@@ -55,10 +55,10 @@ const lenses =
55
55
  <a
56
56
  href={hrefForLens(lens.id, currentParams, pathPrefix)}
57
57
  class:list={[
58
- "grove-btn rounded-md px-3 py-1.5 text-2xs font-medium transition-colors",
58
+ "inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-2xs font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35",
59
59
  active
60
- ? "grove-btn-primary"
61
- : "grove-btn-ghost border border-ink-200 dark:border-ink-800",
60
+ ? "bg-primary text-primary-foreground hover:bg-primary/90"
61
+ : "border-border bg-secondary text-foreground hover:bg-accent",
62
62
  ]}
63
63
  aria-current={active ? "page" : undefined}
64
64
  data-event="lens_tab_click"
@@ -64,10 +64,10 @@ function statusLabel(s: StackEntry["status"]): string | null {
64
64
 
65
65
  <section
66
66
  id="stacks"
67
- class:list={["grove-section scroll-mt-20 border-b border-ink-200 dark:border-ink-800", className]}
67
+ class:list={["py-12 sm:py-16 scroll-mt-20 border-b border-ink-200 dark:border-ink-800", className]}
68
68
  aria-labelledby={bare ? undefined : "grove-stackgrid-heading"}
69
69
  >
70
- <div class="grove-container mx-auto px-5 sm:px-7">
70
+ <div class="mx-auto w-full max-w-container px-5 sm:px-7">
71
71
  {!bare && (
72
72
  <header class="mb-6">
73
73
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
@@ -89,7 +89,7 @@ function statusLabel(s: StackEntry["status"]): string | null {
89
89
  {stacks.map((s) => (
90
90
  <a
91
91
  href={buildHref(s.slug)}
92
- class="card card-hover group flex h-full items-center gap-3 p-3.5 no-underline"
92
+ class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors hover:bg-secondary group flex h-full items-center gap-3 p-3.5 no-underline"
93
93
  data-event="stack_grid_click"
94
94
  data-event-source="stack_grid"
95
95
  >
@@ -115,10 +115,10 @@ function statusLabel(s: StackEntry["status"]): string | null {
115
115
  class:list={[
116
116
  "shrink-0 inline-flex items-center rounded-md border px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider",
117
117
  s.status === "live"
118
- ? "grove-badge-new"
118
+ ? "!bg-emerald-50 !text-emerald-700 dark:!bg-emerald-950/80 dark:!text-emerald-300"
119
119
  : s.status === "expanding"
120
- ? "grove-badge-mature"
121
- : "grove-badge-stale",
120
+ ? "!bg-blue-50 !text-blue-700 dark:!bg-blue-950/80 dark:!text-blue-300"
121
+ : "!bg-amber-50 !text-amber-800 dark:!bg-amber-950/80 dark:!text-amber-300",
122
122
  ]}
123
123
  >
124
124
  {statusLabel(s.status)}
@@ -129,12 +129,3 @@ function statusLabel(s: StackEntry["status"]): string | null {
129
129
  </div>
130
130
  </div>
131
131
  </section>
132
-
133
- <style>
134
- .line-clamp-2 {
135
- display: -webkit-box;
136
- -webkit-line-clamp: 2;
137
- -webkit-box-orient: vertical;
138
- overflow: hidden;
139
- }
140
- </style>
@@ -56,10 +56,10 @@ const items = points && points.length > 0 ? points : defaultPoints;
56
56
 
57
57
  <section
58
58
  id="about"
59
- class:list={["grove-section scroll-mt-20 border-b border-ink-200 dark:border-ink-800", className]}
59
+ class:list={["py-12 sm:py-16 scroll-mt-20 border-b border-ink-200 dark:border-ink-800", className]}
60
60
  aria-labelledby="grove-why-heading"
61
61
  >
62
- <div class="grove-container mx-auto px-5 sm:px-7">
62
+ <div class="mx-auto w-full max-w-container px-5 sm:px-7">
63
63
  <header class="flex flex-col gap-2">
64
64
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
65
65
  {eyebrow}
@@ -82,7 +82,7 @@ const items = points && points.length > 0 ? points : defaultPoints;
82
82
  aria-label="Why this directory"
83
83
  >
84
84
  {items.map((p) => (
85
- <li class="grove-card p-4">
85
+ <li class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-4">
86
86
  <div
87
87
  aria-hidden="true"
88
88
  class="mb-3 flex h-8 w-8 items-center justify-center rounded-md border border-ink-200 bg-white text-ink-700 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300"
@@ -7,12 +7,12 @@ interface Props {
7
7
  const { size = "default", class: className = "" } = Astro.props;
8
8
  const sizeClass =
9
9
  size === "wide"
10
- ? "grove-container-wide max-w-wide"
10
+ ? "mx-auto w-full max-w-wide"
11
11
  : size === "narrow"
12
- ? "grove-container-narrow max-w-narrow"
13
- : "grove-container max-w-container";
12
+ ? "mx-auto w-full max-w-narrow"
13
+ : "mx-auto w-full max-w-container";
14
14
  ---
15
15
 
16
- <div class={`${sizeClass} mx-auto px-5 sm:px-7 ${className}`}>
16
+ <div class={`${sizeClass} px-5 sm:px-7 ${className}`}>
17
17
  <slot />
18
18
  </div>
@@ -104,12 +104,12 @@ const projectItems = project.items.length ? project.items : defaultProject;
104
104
 
105
105
  <footer
106
106
  class:list={[
107
- "mt-16 border-t border-ink-200 bg-white text-ink-500 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-400",
107
+ "border-border bg-background mt-16 border-t text-ink-500 dark:text-ink-400",
108
108
  className,
109
109
  ]}
110
110
  >
111
111
  <div
112
- class="grove-container-wide mx-auto grid max-w-wide grid-cols-2 gap-8 px-5 py-12 md:grid-cols-4 sm:px-7"
112
+ class="mx-auto grid w-full max-w-wide grid-cols-2 gap-8 px-5 py-12 md:grid-cols-4 sm:px-7"
113
113
  >
114
114
  <div class="col-span-2 md:col-span-1">
115
115
  <div
@@ -225,7 +225,7 @@ const projectItems = project.items.length ? project.items : defaultProject;
225
225
 
226
226
  <div class="border-t border-ink-200 dark:border-ink-800">
227
227
  <div
228
- class="grove-container-wide mx-auto flex max-w-wide flex-wrap items-center gap-3 px-5 py-4 text-2xs text-ink-500 sm:px-7 dark:text-ink-400"
228
+ class="mx-auto flex w-full max-w-wide flex-wrap items-center gap-3 px-5 py-4 text-2xs text-ink-500 sm:px-7 dark:text-ink-400"
229
229
  >
230
230
  <span>© {year} {holder}{licenseLine ? `. ${licenseLine}` : "."}</span>
231
231
  {gh && (
@@ -73,12 +73,12 @@ const starsLabel =
73
73
 
74
74
  <header
75
75
  class:list={[
76
- "sticky top-0 z-50 border-b border-ink-200 bg-white/85 backdrop-blur dark:border-ink-800 dark:bg-ink-950/85",
76
+ "border-border bg-background/85 sticky top-0 z-50 border-b backdrop-blur",
77
77
  className,
78
78
  ]}
79
79
  >
80
80
  <div
81
- class="grove-container-wide mx-auto flex max-w-wide items-center gap-6 px-5 py-3 sm:px-7"
81
+ class="mx-auto flex w-full max-w-wide items-center gap-6 px-5 py-3 sm:px-7"
82
82
  >
83
83
  <a
84
84
  href="/"
@@ -110,7 +110,7 @@ const starsLabel =
110
110
  {submitHref && (
111
111
  <a
112
112
  href={submitHref}
113
- class="hidden h-9 items-center gap-1.5 rounded-md border border-ink-200 bg-white px-3 text-[0.875rem] font-medium text-ink-700 transition-colors hover:border-ink-300 hover:bg-ink-50 sm:inline-flex dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300 dark:hover:border-ink-700 dark:hover:bg-ink-900/40"
113
+ 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 hidden sm:inline-flex"
114
114
  >
115
115
  <svg
116
116
  viewBox="0 0 16 16"
@@ -132,7 +132,7 @@ const starsLabel =
132
132
  rel="noopener noreferrer"
133
133
  aria-label={`${repoLabel} on GitHub — ${stars} stars`}
134
134
  title={`${repoLabel} on GitHub`}
135
- class="inline-flex h-9 items-center divide-x divide-ink-200 overflow-hidden rounded-md border border-ink-200 text-[0.875rem] font-medium transition-colors hover:border-ink-300 hover:bg-ink-50 dark:divide-ink-800 dark:border-ink-800 dark:hover:border-ink-700 dark:hover:bg-ink-900/40"
135
+ class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 overflow-hidden rounded-[var(--radius)] border border-border bg-secondary p-0 text-sm font-medium leading-none text-foreground no-underline outline-none transition-colors hover:bg-accent focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35"
136
136
  >
137
137
  <span class="flex items-center gap-1.5 px-2.5 py-1.5 text-ink-900 dark:text-ink-100">
138
138
  <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="currentColor">
@@ -140,7 +140,7 @@ const starsLabel =
140
140
  </svg>
141
141
  <span>{repoLabel}</span>
142
142
  </span>
143
- <span class="flex items-center gap-1 bg-ink-50 px-2 py-1.5 text-ink-700 dark:bg-ink-900/60 dark:text-ink-300">
143
+ <span class="flex items-center gap-1 border-s border-border bg-secondary px-2 py-1.5 text-secondary-foreground">
144
144
  <svg viewBox="0 0 16 16" width="11" height="11" aria-hidden="true" fill="currentColor" class="text-amber-500">
145
145
  <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path>
146
146
  </svg>
@@ -24,7 +24,7 @@ const { class: className = "" } = Astro.props;
24
24
  data-theme-toggle
25
25
  aria-label="Toggle theme"
26
26
  title="Toggle theme"
27
- class={`relative inline-flex h-8 w-8 items-center justify-center rounded-md border border-ink-200 bg-white text-ink-700 transition-colors hover:border-ink-300 hover:bg-ink-50 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-200 dark:hover:border-ink-700 dark:hover:bg-ink-900/40 ${className}`}
27
+ 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 relative h-8 w-8 p-0 ${className}`}
28
28
  >
29
29
  {/* Sun — shown in dark mode (click to go light) */}
30
30
  <svg
package/src/styles.css CHANGED
@@ -6,14 +6,9 @@
6
6
  *
7
7
  * import "@grove-dev/astro/styles.css";
8
8
  *
9
- * The `grove-` prefix below is the stable contract for the showcase
10
- * component classes (`grove-section`, `grove-card`, `grove-btn`, etc.).
11
- * Tailwind v4's `@theme` block declares the same tokens as CSS custom
12
- * properties and Tailwind utility names — components can use either
13
- * bare `grove-*` classes or Tailwind utilities like `text-ink-900`.
14
- *
15
- * Namespace history: earlier V0 drafts used a different (now
16
- * removed) prefix. Only `grove-*` remains.
9
+ * This file owns only Tailwind theme tokens, runtime light/dark values,
10
+ * base element defaults, and rendered Markdown styles. Astro components
11
+ * keep their presentation in HTML with Tailwind utilities.
17
12
  */
18
13
 
19
14
  @import "tailwindcss";
@@ -127,7 +122,7 @@
127
122
  --container-narrow: 720px;
128
123
 
129
124
  /* ── Border radius scale ─────────────────────────────────────── */
130
- --radius: 0.375rem;
125
+ --radius: 0.625rem;
131
126
  --radius-lg: 0.5rem;
132
127
  --radius-xl: 0.75rem;
133
128
 
@@ -287,6 +282,35 @@
287
282
  button {
288
283
  font: inherit;
289
284
  }
285
+
286
+ :where(
287
+ input[type="text"],
288
+ input[type="search"],
289
+ input[type="url"],
290
+ input[type="email"],
291
+ input:not([type]),
292
+ select,
293
+ textarea
294
+ ) {
295
+ border-color: var(--grove-input) !important;
296
+ border-radius: var(--radius) !important;
297
+ background-color: var(--grove-background) !important;
298
+ color: var(--grove-foreground) !important;
299
+ }
300
+
301
+ :where(
302
+ input[type="text"],
303
+ input[type="search"],
304
+ input[type="url"],
305
+ input[type="email"],
306
+ input:not([type]),
307
+ select,
308
+ textarea
309
+ ):focus-visible {
310
+ border-color: var(--grove-ring) !important;
311
+ outline: none;
312
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--grove-ring) 35%, transparent);
313
+ }
290
314
  }
291
315
 
292
316
  /* ──────────────────────────────────────────────────────────────────
@@ -394,333 +418,3 @@
394
418
  padding-left: 1rem;
395
419
  color: var(--color-ink-500);
396
420
  }
397
-
398
- /* ──────────────────────────────────────────────────────────────────
399
- * Component classes — shared by the showcase component library.
400
- * Use these directly; you can also use the equivalent Tailwind
401
- * utilities (text-ink-900, bg-white, …) and mix freely.
402
- * ────────────────────────────────────────────────────────────────── */
403
-
404
- @layer components {
405
- /*
406
- * Stable container classes for consumers of the packaged stylesheet.
407
- * Tailwind does not emit utilities referenced only inside a dependency,
408
- * so relying on `max-w-container` alone can leave template content
409
- * unconstrained.
410
- */
411
- .grove-container,
412
- .grove-container-wide,
413
- .grove-container-narrow {
414
- width: 100%;
415
- margin-inline: auto;
416
- }
417
-
418
- .grove-container {
419
- max-width: var(--container-container);
420
- }
421
-
422
- .grove-container-wide {
423
- max-width: var(--container-wide);
424
- }
425
-
426
- .grove-container-narrow {
427
- max-width: var(--container-narrow);
428
- }
429
-
430
- /* Section spacing — consistent vertical rhythm */
431
- .grove-section {
432
- padding-top: 3rem;
433
- padding-bottom: 3rem;
434
- }
435
- @media (min-width: 640px) {
436
- .grove-section {
437
- padding-top: 4rem;
438
- padding-bottom: 4rem;
439
- }
440
- }
441
-
442
- .grove-section-tight {
443
- padding-top: 2.5rem;
444
- padding-bottom: 2.5rem;
445
- }
446
- @media (min-width: 640px) {
447
- .grove-section-tight {
448
- padding-top: 3rem;
449
- padding-bottom: 3rem;
450
- }
451
- }
452
-
453
- /* Subtle card border + hover lift, used everywhere */
454
- .grove-card {
455
- border: 1px solid var(--color-ink-200);
456
- background: #ffffff;
457
- border-radius: var(--radius-lg);
458
- transition:
459
- border-color 150ms ease,
460
- box-shadow 150ms ease,
461
- background-color 150ms ease;
462
- }
463
- .dark .grove-card {
464
- border-color: var(--color-ink-800);
465
- background: var(--color-ink-950);
466
- }
467
-
468
- .grove-card-hover:hover {
469
- border-color: var(--color-ink-300);
470
- background: rgba(244, 244, 245, 0.6);
471
- box-shadow:
472
- 0 1px 2px rgba(0, 0, 0, 0.04),
473
- 0 4px 12px -2px rgba(0, 0, 0, 0.06);
474
- }
475
- .dark .grove-card-hover:hover {
476
- border-color: var(--color-ink-700);
477
- background: rgba(24, 24, 27, 0.4);
478
- box-shadow:
479
- 0 1px 2px rgba(0, 0, 0, 0.4),
480
- 0 4px 12px -2px rgba(0, 0, 0, 0.3);
481
- }
482
-
483
- /* Pill badge — small, neutral */
484
- .grove-badge {
485
- display: inline-flex;
486
- align-items: center;
487
- gap: 0.25rem;
488
- border-radius: 0.25rem;
489
- border: 1px solid var(--color-ink-200);
490
- background: var(--color-ink-50);
491
- padding: 0.125rem 0.375rem;
492
- font-size: var(--text-2xs);
493
- line-height: var(--text-2xs--line-height);
494
- font-weight: 500;
495
- color: var(--color-ink-700);
496
- }
497
- .dark .grove-badge {
498
- border-color: var(--color-ink-800);
499
- background: rgba(24, 24, 27, 0.6);
500
- color: var(--color-ink-300);
501
- }
502
-
503
- .grove-badge-accent {
504
- border-color: var(--color-ink-900);
505
- background: var(--color-ink-900);
506
- color: #ffffff;
507
- }
508
- .dark .grove-badge-accent {
509
- border-color: var(--color-ink-100);
510
- background: var(--color-ink-100);
511
- color: var(--color-ink-900);
512
- }
513
-
514
- .grove-badge-new {
515
- border-color: rgba(110, 231, 183, 0.6);
516
- background: var(--color-emerald-50);
517
- color: var(--color-emerald-800);
518
- }
519
- .dark .grove-badge-new {
520
- border-color: rgba(6, 95, 70, 0.6);
521
- background: rgba(2, 44, 34, 0.4);
522
- color: var(--color-emerald-300);
523
- }
524
-
525
- .grove-badge-hot {
526
- border-color: rgba(253, 186, 116, 0.6);
527
- background: var(--color-orange-50);
528
- color: var(--color-orange-800);
529
- }
530
- .dark .grove-badge-hot {
531
- border-color: rgba(154, 52, 18, 0.6);
532
- background: rgba(67, 20, 7, 0.4);
533
- color: var(--color-orange-300);
534
- }
535
-
536
- .grove-badge-mature {
537
- border-color: rgba(147, 197, 253, 0.6);
538
- background: var(--color-blue-50);
539
- color: var(--color-blue-800);
540
- }
541
- .dark .grove-badge-mature {
542
- border-color: rgba(30, 64, 175, 0.6);
543
- background: rgba(23, 37, 84, 0.4);
544
- color: var(--color-blue-300);
545
- }
546
-
547
- .grove-badge-stale {
548
- border-color: rgba(252, 211, 77, 0.6);
549
- background: var(--color-amber-50);
550
- color: var(--color-amber-800);
551
- }
552
- .dark .grove-badge-stale {
553
- border-color: rgba(146, 64, 14, 0.6);
554
- background: rgba(69, 26, 3, 0.4);
555
- color: var(--color-amber-300);
556
- }
557
-
558
- /* Buttons — outline / ghost / primary */
559
- .grove-btn {
560
- display: inline-flex;
561
- align-items: center;
562
- justify-content: center;
563
- gap: 0.375rem;
564
- border-radius: var(--radius);
565
- border: 1px solid transparent;
566
- padding: 0.375rem 0.75rem;
567
- font-size: 0.875rem;
568
- line-height: 1.25rem;
569
- font-weight: 500;
570
- transition: color 150ms ease, background-color 150ms ease,
571
- border-color 150ms ease;
572
- text-decoration: none;
573
- cursor: pointer;
574
- }
575
-
576
- .grove-btn-ghost {
577
- color: var(--color-ink-700);
578
- }
579
- .grove-btn-ghost:hover {
580
- background: var(--color-ink-100);
581
- }
582
- .dark .grove-btn-ghost {
583
- color: var(--color-ink-300);
584
- }
585
- .dark .grove-btn-ghost:hover {
586
- background: var(--color-ink-900);
587
- }
588
-
589
- .grove-btn-outline {
590
- border-color: var(--color-ink-200);
591
- background: #ffffff;
592
- color: var(--color-ink-900);
593
- }
594
- .grove-btn-outline:hover {
595
- border-color: var(--color-ink-300);
596
- background: var(--color-ink-50);
597
- }
598
- .dark .grove-btn-outline {
599
- border-color: var(--color-ink-800);
600
- background: var(--color-ink-950);
601
- color: var(--color-ink-100);
602
- }
603
- .dark .grove-btn-outline:hover {
604
- border-color: var(--color-ink-700);
605
- background: rgba(24, 24, 27, 0.4);
606
- }
607
-
608
- .grove-btn-primary {
609
- border-color: var(--color-ink-900);
610
- background: var(--color-ink-900);
611
- color: #ffffff;
612
- }
613
- .grove-btn-primary:hover {
614
- background: var(--color-ink-800);
615
- }
616
- .dark .grove-btn-primary {
617
- border-color: var(--color-ink-100);
618
- background: var(--color-ink-100);
619
- color: var(--color-ink-900);
620
- }
621
- .dark .grove-btn-primary:hover {
622
- background: #ffffff;
623
- }
624
- }
625
-
626
- /* ────────────────────────────────────────────────────────────────────
627
- * Backwards-compat aliases — openapps used the unprefixed utility
628
- * names (`card`, `btn-primary`, `btn-outline`, `btn-ghost`). New
629
- * components should use the `grove-` prefix. These rules mirror
630
- * the `grove-*` definitions above so the openapps-derived
631
- * components (ItemCard, AppCard, etc. — kept as `ItemCard` for V1
632
- * published API stability) work without per-component
633
- * edits.
634
- * ────────────────────────────────────────────────────────────────────*/
635
- .card {
636
- border: 1px solid var(--color-ink-200);
637
- background: #ffffff;
638
- border-radius: 0.5rem;
639
- transition: border-color 150ms ease, background-color 150ms ease, box-shadow 150ms ease;
640
- }
641
- .dark .card {
642
- border-color: var(--color-ink-800);
643
- background: var(--color-ink-950);
644
- }
645
- .card-hover:hover {
646
- border-color: var(--color-ink-300);
647
- background: rgba(244, 244, 245, 0.6);
648
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 4px 12px -2px rgba(0, 0, 0, 0.06);
649
- }
650
- .dark .card-hover:hover {
651
- border-color: var(--color-ink-700);
652
- background: rgba(24, 24, 27, 0.4);
653
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px -2px rgba(0, 0, 0, 0.3);
654
- }
655
-
656
- /* Chip — small inline label used for platforms/stacks/etc. on
657
- * ItemCard. Mirrors openapps' `.chip` from the ItemCard.astro
658
- * (component name retained for V1 published API stability).
659
- * `<style>` block so the @apply in the component still works. */
660
- .chip {
661
- display: inline-flex;
662
- align-items: center;
663
- gap: 0.25rem;
664
- border-radius: 0.375rem;
665
- border: 1px solid var(--color-ink-200);
666
- background: #ffffff;
667
- padding: 0.125rem 0.375rem;
668
- font-size: 0.6875rem;
669
- font-weight: 500;
670
- color: var(--color-ink-700);
671
- transition: color 150ms ease, background-color 150ms ease, border-color 150ms ease;
672
- }
673
- .dark .chip {
674
- border-color: var(--color-ink-200);
675
- background: var(--color-ink-950);
676
- color: var(--color-ink-300);
677
- }
678
- .chip-stack {
679
- background: var(--color-ink-50);
680
- }
681
- .dark .chip-stack {
682
- background: rgba(24, 24, 27, 0.6);
683
- }
684
- .btn-primary,
685
- .btn-outline,
686
- .btn-ghost {
687
- display: inline-flex;
688
- align-items: center;
689
- justify-content: center;
690
- gap: 0.375rem;
691
- border-radius: 0.5rem;
692
- padding: 0.5rem 0.875rem;
693
- font-size: 0.875rem;
694
- font-weight: 500;
695
- white-space: nowrap;
696
- transition: color 0.15s, background-color 0.15s, border-color 0.15s;
697
- cursor: pointer;
698
- border: 1px solid transparent;
699
- text-decoration: none;
700
- }
701
- .btn-primary {
702
- border-color: var(--color-ink-900);
703
- background: var(--color-ink-900);
704
- color: var(--color-ink-50);
705
- }
706
- .btn-primary:hover {
707
- background: var(--color-ink-700);
708
- border-color: var(--color-ink-700);
709
- }
710
- .btn-outline {
711
- border-color: var(--color-ink-200);
712
- background: var(--color-ink-50);
713
- color: var(--color-ink-700);
714
- }
715
- .btn-outline:hover {
716
- border-color: var(--color-ink-300);
717
- background: var(--color-ink-100);
718
- }
719
- .btn-ghost {
720
- background: transparent;
721
- color: var(--color-ink-500);
722
- }
723
- .btn-ghost:hover {
724
- color: var(--color-ink-900);
725
- background: var(--color-ink-100);
726
- }