@grove-dev/astro 0.7.0 → 0.8.0

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 (110) hide show
  1. package/dist/index.d.ts +3 -3
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +22 -33
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/display.d.ts +1 -1
  6. package/dist/lib/display.js +1 -1
  7. package/dist/lib/format.d.ts +1 -1
  8. package/dist/lib/format.js +1 -1
  9. package/dist/lib/icon-kinds.d.ts +1 -1
  10. package/dist/lib/icon-kinds.js +43 -43
  11. package/dist/lib/icon-registry.d.ts +2 -2
  12. package/dist/lib/icon-registry.js +23 -23
  13. package/dist/lib/index.d.ts +9 -9
  14. package/dist/lib/index.d.ts.map +1 -1
  15. package/dist/lib/index.js +9 -9
  16. package/dist/lib/index.js.map +1 -1
  17. package/dist/lib/lenses.d.ts +1 -1
  18. package/dist/lib/lenses.js +1 -1
  19. package/dist/lib/packaged-icons.d.ts +1 -1
  20. package/dist/lib/packaged-icons.js +4 -4
  21. package/dist/lib/repo.d.ts +1 -1
  22. package/dist/lib/repo.js +1 -1
  23. package/dist/lib/search.d.ts +1 -1
  24. package/dist/lib/search.js +1 -1
  25. package/dist/lib/taxonomy-counts.d.ts +1 -1
  26. package/dist/lib/taxonomy-counts.js +1 -1
  27. package/package.json +7 -17
  28. package/src/index.ts +23 -38
  29. package/src/lib/display.ts +1 -1
  30. package/src/lib/format.ts +1 -1
  31. package/src/lib/icon-kinds.ts +44 -44
  32. package/src/lib/icon-registry.ts +25 -25
  33. package/src/lib/index.ts +9 -9
  34. package/src/lib/lenses.ts +1 -1
  35. package/src/lib/packaged-icons.ts +8 -8
  36. package/src/lib/repo.ts +1 -1
  37. package/src/lib/search.ts +1 -1
  38. package/src/lib/taxonomy-counts.ts +1 -1
  39. package/src/server/collections.ts +53 -42
  40. package/src/server/contrast.ts +14 -9
  41. package/src/server/directory.ts +22 -17
  42. package/src/server/index.ts +3 -2
  43. package/src/server/models.ts +404 -213
  44. package/src/server/seo.ts +34 -31
  45. package/src/styles.css +235 -91
  46. package/dist/layouts/index.d.ts +0 -25
  47. package/dist/layouts/index.d.ts.map +0 -1
  48. package/dist/layouts/index.js +0 -25
  49. package/dist/layouts/index.js.map +0 -1
  50. package/dist/ui/button.d.ts +0 -53
  51. package/dist/ui/button.d.ts.map +0 -1
  52. package/dist/ui/button.js +0 -90
  53. package/dist/ui/button.js.map +0 -1
  54. package/src/components/CardGrid.astro +0 -41
  55. package/src/components/CardIcon.astro +0 -67
  56. package/src/components/CategoryGrid.astro +0 -114
  57. package/src/components/CollectionCard.astro +0 -56
  58. package/src/components/CollectionIndex.astro +0 -102
  59. package/src/components/CollectionPage.astro +0 -128
  60. package/src/components/CollectionRow.astro +0 -81
  61. package/src/components/CollectionTeaser.astro +0 -47
  62. package/src/components/ContributorsGrid.astro +0 -143
  63. package/src/components/DirectoryIndexClient.astro +0 -510
  64. package/src/components/EditorialSummary.astro +0 -58
  65. package/src/components/FilterGroupMenu.astro +0 -69
  66. package/src/components/FilterOptions.astro +0 -49
  67. package/src/components/FinalCta.astro +0 -43
  68. package/src/components/Hero.astro +0 -243
  69. package/src/components/Icon.astro +0 -191
  70. package/src/components/IndexRow.astro +0 -37
  71. package/src/components/LanguageBreakdown.astro +0 -67
  72. package/src/components/MarkdownBody.astro +0 -37
  73. package/src/components/OriginalCollection.astro +0 -117
  74. package/src/components/Pagination.astro +0 -50
  75. package/src/components/PoweredBy.astro +0 -60
  76. package/src/components/ProjectCard.astro +0 -321
  77. package/src/components/RecordHeader.astro +0 -221
  78. package/src/components/RecordSection.astro +0 -128
  79. package/src/components/RecordSidebar.astro +0 -387
  80. package/src/components/RefinePanel.astro +0 -168
  81. package/src/components/SmartLensTabs.astro +0 -81
  82. package/src/components/StackGrid.astro +0 -151
  83. package/src/components/StackPlatformChips.astro +0 -73
  84. package/src/components/SubmissionClient.astro +0 -254
  85. package/src/components/TableOfContents.astro +0 -140
  86. package/src/components/WhyThisExists.astro +0 -116
  87. package/src/layouts/BaseLayout.astro +0 -415
  88. package/src/layouts/Container.astro +0 -18
  89. package/src/layouts/Footer.astro +0 -231
  90. package/src/layouts/Header.astro +0 -214
  91. package/src/layouts/SectionHeader.astro +0 -43
  92. package/src/layouts/Seo.astro +0 -171
  93. package/src/layouts/ThemeToggle.astro +0 -118
  94. package/src/layouts/astro-shim.d.ts +0 -29
  95. package/src/layouts/index.ts +0 -24
  96. package/src/server/collections.test.ts +0 -212
  97. package/src/server/contrast.test.ts +0 -82
  98. package/src/server/directory.test.ts +0 -177
  99. package/src/server/github-repo.test.ts +0 -88
  100. package/src/server/models-home.test.ts +0 -101
  101. package/src/server/models.test.ts +0 -135
  102. package/src/server/seo.test.ts +0 -123
  103. package/src/ui/Badge.astro +0 -38
  104. package/src/ui/Button.astro +0 -42
  105. package/src/ui/EmptyState.astro +0 -38
  106. package/src/ui/FilterDrawer.astro +0 -124
  107. package/src/ui/PageHeader.astro +0 -39
  108. package/src/ui/SearchField.astro +0 -106
  109. package/src/ui/button.test.ts +0 -54
  110. package/src/ui/button.ts +0 -107
@@ -1,116 +0,0 @@
1
- ---
2
- /**
3
- * WhyThisExists — the "what is this site for?" section.
4
- *
5
- * Renders 3 short, opinionated points (icon + title + body). The
6
- * icon set is small and generic: "item" (window/grid glyph),
7
- * "code" (angle brackets), "search" (magnifier). Defaults to
8
- * the canonical "directory, source, search" trio.
9
- *
10
- * Generic: every point comes through props. No blueprint-specific
11
- * copy. Consumers can pass `points` to swap the order or copy.
12
- */
13
-
14
- interface WhyPoint {
15
- title: string;
16
- body: string;
17
- icon: "item" | "code" | "search";
18
- }
19
-
20
- interface Props {
21
- points?: WhyPoint[];
22
- eyebrow?: string;
23
- title?: string;
24
- description?: string;
25
- class?: string;
26
- }
27
-
28
- const {
29
- points,
30
- eyebrow = "Why this exists",
31
- title = "Most directory discovery is fragmented.",
32
- description = "Lists are useful, but they often become long, static pages without enough context to compare what is real, maintained, and worth exploring.",
33
- class: className = "",
34
- } = Astro.props;
35
-
36
- const defaultPoints: WhyPoint[] = [
37
- {
38
- icon: "item",
39
- title: "Real projects",
40
- body: "Not SDKs, templates, or tiny demos. Focused on complete projects you can actually run.",
41
- },
42
- {
43
- icon: "code",
44
- title: "Codebase context",
45
- body: "Stack, platform, license, activity, maturity, and contribution signals in one place.",
46
- },
47
- {
48
- icon: "search",
49
- title: "Searchable discovery",
50
- body: "Browse by category, stack, platform, stars, recency, and status instead of scanning a flat list.",
51
- },
52
- ];
53
-
54
- const items = points && points.length > 0 ? points : defaultPoints;
55
- ---
56
-
57
- <section
58
- id="about"
59
- class:list={["py-12 sm:py-16 scroll-mt-20 border-b border-ink-200 dark:border-ink-800", className]}
60
- aria-labelledby="grove-why-heading"
61
- >
62
- <div class="mx-auto w-full max-w-container px-5 sm:px-7">
63
- <header class="flex flex-col gap-2">
64
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
65
- {eyebrow}
66
- </span>
67
- <h2
68
- id="grove-why-heading"
69
- class="m-0 max-w-[40ch] text-[1.5rem] sm:text-[1.75rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
70
- >
71
- {title}
72
- </h2>
73
- {description && (
74
- <p class="m-0 max-w-[64ch] text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400">
75
- {description}
76
- </p>
77
- )}
78
- </header>
79
-
80
- <ul
81
- class="m-0 mt-7 grid list-none grid-cols-1 gap-3 p-0 sm:grid-cols-3"
82
- aria-label="Why this directory"
83
- >
84
- {items.map((p) => (
85
- <li class="rounded-[calc(var(--radius)+0.25rem)] border border-border bg-card shadow-none transition-colors p-4">
86
- <div
87
- aria-hidden="true"
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"
89
- >
90
- {p.icon === "item" && (
91
- <svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
92
- <path d="M1.75 3.5a.25.25 0 0 1 .25-.25h12a.25.25 0 0 1 .25.25v8a.25.25 0 0 1-.25.25h-12a.25.25 0 0 1-.25-.25v-8ZM2 4.75v6h12v-6H2ZM2.5 6.5h4v2.5h-4V6.5Z"></path>
93
- </svg>
94
- )}
95
- {p.icon === "code" && (
96
- <svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
97
- <path d="M4.72 3.22a.75.75 0 0 1 1.06 1.06L2.06 8l3.72 3.72a.75.75 0 1 1-1.06 1.06L.47 8.53a.75.75 0 0 1 0-1.06l4.25-4.25Zm6.56 0a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L13.94 8l-3.72-3.72a.75.75 0 0 1 0-1.06Z"></path>
98
- </svg>
99
- )}
100
- {p.icon === "search" && (
101
- <svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
102
- <path d="M10.68 11.74a6 6 0 1 1 1.06-1.06l3.04 3.04a.75.75 0 1 1-1.06 1.06l-3.04-3.04ZM11.5 7a4.5 4.5 0 1 0-9 0 4.5 4.5 0 0 0 9 0Z"></path>
103
- </svg>
104
- )}
105
- </div>
106
- <h3 class="m-0 text-[0.9375rem] font-semibold text-ink-900 dark:text-ink-100">
107
- {p.title}
108
- </h3>
109
- <p class="m-0 mt-1.5 text-[0.8125rem] leading-relaxed text-ink-500 dark:text-ink-400">
110
- {p.body}
111
- </p>
112
- </li>
113
- ))}
114
- </ul>
115
- </div>
116
- </section>
@@ -1,415 +0,0 @@
1
- ---
2
- /**
3
- * BaseLayout — wraps every page. Owns the theme-detection inline
4
- * script and the global shell (Header, Footer). Per-page SEO
5
- * (title, description, OG, JSON-LD) is delegated to `<Seo />` so
6
- * each page can declare what it needs without polluting the layout
7
- * file.
8
- *
9
- * Generic: takes a `site` prop with brand + nav + stats. No
10
- * `siteConfig` import — consumers compose the props from their
11
- * own config (typically `data/generated/site-config.json` for the
12
- * showcase).
13
- *
14
- * GA4 can be configured once through `site.analytics.googleAnalyticsId`
15
- * or overridden per page with `gaId`. Omit both for privacy-first deployments.
16
- */
17
- import '../styles.css';
18
- import Header from './Header.astro';
19
- import Footer from './Footer.astro';
20
- import Seo from './Seo.astro';
21
- import { derivePrimaryPalette } from '../server/contrast';
22
-
23
- interface NavItem {
24
- label: string;
25
- href: string;
26
- }
27
-
28
- interface SiteStats {
29
- totalApps?: number;
30
- totalCategories?: number;
31
- totalStacks?: number;
32
- totalOwners?: number;
33
- totalStars?: number;
34
- repositoryStars?: number;
35
- repositoryForks?: number;
36
- repositoryContributors?: number;
37
- originalRepo?: string;
38
- originalStars?: number;
39
- originalForks?: number;
40
- originalContributors?: number;
41
- }
42
-
43
- interface Site {
44
- name: string;
45
- tagline?: string;
46
- description?: string;
47
- nav: NavItem[];
48
- footer?: {
49
- columns?: Array<{
50
- heading: string;
51
- items: Array<{ label: string; href: string; external?: boolean }>;
52
- }>;
53
- copyright?: string;
54
- license?: string;
55
- };
56
- submission?: {
57
- eyebrow?: string;
58
- title?: string;
59
- description?: string;
60
- good?: string[];
61
- avoid?: string[];
62
- };
63
- repoUrl?: string;
64
- url?: string;
65
- /** Header logo path under `public/` (e.g. `/logo.svg`). */
66
- logo?: string;
67
- /** Favicon path under `public/` (e.g. `/favicon.svg`). */
68
- favicon?: string;
69
- /** BCP-47 language tag (e.g. "en"). Drives `<html lang>` and og:locale. */
70
- locale?: string;
71
- /** Twitter/X handle (e.g. "@grove") — emitted as twitter:site. */
72
- twitter?: string;
73
- analytics?: {
74
- googleAnalyticsId?: string;
75
- };
76
- /** Optional directory-level stats — populated from site-config.json
77
- * at generate time. Components use it to render hero counts,
78
- * origin card stats, and footer summary lines. */
79
- stats?: SiteStats;
80
- /** Theme knobs from grove.config.ts — resolved into CSS custom
81
- * properties on `<html>` so the package stylesheet picks them up.
82
- * Values are plain strings because the prop usually arrives from a
83
- * JSON import; unknown values fall back to the package defaults. */
84
- theme?: {
85
- primaryColor?: string;
86
- /** "none" | "soft" | "round" */
87
- radius?: string;
88
- /** "compact" | "comfortable" | "spacious" */
89
- density?: string;
90
- containerWidth?: string;
91
- };
92
- blueprintConfig?: {
93
- routeSlug?: string;
94
- labelSingular?: string;
95
- labelPlural?: string;
96
- };
97
- }
98
-
99
- interface Props {
100
- title: string;
101
- description: string;
102
- site: Site;
103
- type?: 'website' | 'article' | 'profile';
104
- noindex?: boolean;
105
- jsonLd?: Record<string, unknown> | Record<string, unknown>[];
106
- /** Open Graph / Twitter image path (e.g. "/og/records/foo.png").
107
- * Defaults to the site-wide /og-image.svg inside <Seo />. */
108
- image?: string;
109
- /** Alt text for the OG/Twitter image. */
110
- imageAlt?: string;
111
- /** Optional Google Analytics 4 Measurement ID (e.g. "G-XXXXXX"). */
112
- gaId?: string;
113
- /** Path used for JSON-LD `SearchAction` target. Defaults to "/items". */
114
- searchPath?: string;
115
- /** Submit button label rendered in the header. Omit to hide. */
116
- submitLabel?: string;
117
- /** Submit button href rendered in the header. */
118
- submitHref?: string;
119
- }
120
-
121
- const {
122
- title,
123
- description,
124
- site,
125
- type,
126
- noindex,
127
- jsonLd,
128
- image,
129
- imageAlt,
130
- gaId,
131
- searchPath,
132
- submitLabel,
133
- submitHref,
134
- } = Astro.props;
135
- const directoryPath = searchPath ?? `/${site.blueprintConfig?.routeSlug ?? 'items'}`;
136
- const effectiveGaId = gaId ?? site.analytics?.googleAnalyticsId;
137
-
138
- // ── Theme wiring ─────────────────────────────────────────────────
139
- // `theme.*` from grove.config.ts resolves to CSS custom properties
140
- // on the <html> style attribute. An attribute (not an inline
141
- // <style>) so the override wins over the bundled stylesheet
142
- // regardless of how Vite orders the CSS output.
143
- const RADIUS_PRESETS: Record<string, [string, string, string]> = {
144
- none: ["0rem", "0rem", "0rem"],
145
- soft: ["0.625rem", "0.5rem", "0.75rem"],
146
- round: ["0.875rem", "0.75rem", "1rem"],
147
- };
148
- const DENSITY_SPACING: Record<string, string> = {
149
- compact: "0.225rem",
150
- comfortable: "0.25rem",
151
- spacious: "0.28rem",
152
- };
153
-
154
- // Brand fill and text derive from ONE WCAG computation (contrast.ts)
155
- // so background and foreground can never drift apart across themes.
156
-
157
- const themeConfig = site.theme;
158
- const themeStyleParts: string[] = [];
159
- if (themeConfig) {
160
- const radius = RADIUS_PRESETS[themeConfig.radius ?? "soft"];
161
- if (radius && themeConfig.radius && themeConfig.radius !== "soft") {
162
- themeStyleParts.push(
163
- `--grove-radius: ${radius[0]}`,
164
- `--grove-radius-lg: ${radius[1]}`,
165
- `--grove-radius-xl: ${radius[2]}`,
166
- );
167
- }
168
- const spacing = DENSITY_SPACING[themeConfig.density ?? "comfortable"];
169
- if (spacing && themeConfig.density && themeConfig.density !== "comfortable") {
170
- themeStyleParts.push(`--spacing: ${spacing}`);
171
- }
172
- if (themeConfig.containerWidth) {
173
- themeStyleParts.push(`--grove-container: ${themeConfig.containerWidth}`);
174
- }
175
- const primary = themeConfig.primaryColor;
176
- if (typeof primary === "string" && /^#[0-9a-fA-F]{3,8}$/.test(primary)) {
177
- const palette = derivePrimaryPalette(primary);
178
- if (palette) {
179
- themeStyleParts.push(
180
- `--grove-theme-primary: ${palette.solid}`,
181
- `--grove-theme-primary-foreground: ${palette.solidForeground}`,
182
- `--grove-theme-primary-dark: ${palette.dark}`,
183
- `--grove-theme-primary-dark-foreground: ${palette.darkForeground}`,
184
- );
185
- }
186
- }
187
- }
188
- const themeStyle = themeStyleParts.length > 0 ? `${themeStyleParts.join("; ")};` : undefined;
189
-
190
- // `site.favicon` points at a file the consumer owns under `public/`.
191
- // The fallback is a generated square — tinted with the configured
192
- // brand color rather than a hardcoded near-black, so an unconfigured
193
- // site still gets a tab mark that is recognisably its own.
194
- const faviconFill = (site.theme?.primaryColor ?? "#18181b").trim();
195
- const favicon =
196
- site.favicon ??
197
- `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='${encodeURIComponent(faviconFill)}'/%3E%3C/svg%3E`;
198
- ---
199
-
200
- <!doctype html>
201
- <html lang={site.locale ?? 'en'} style={themeStyle}>
202
- <head>
203
- <meta charset="UTF-8" />
204
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
205
- <meta name="generator" content={Astro.generator} />
206
- <meta name="theme-color" content="#0a0a0c" media="(prefers-color-scheme: dark)" />
207
- <meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" />
208
- <link rel="icon" href={favicon} />
209
- <link rel="sitemap" href="/sitemap.xml" />
210
- {/* Preconnect to GitHub's avatar CDN — most pages render at least
211
- one avatar (record list cards, detail page logo) and the
212
- connection is reusable across cards. Cross-origin only, no
213
- document fetch, so the cost is one TLS handshake per page. */}
214
- <link rel="preconnect" href="https://avatars.githubusercontent.com" crossorigin="anonymous" />
215
- <link
216
- rel="alternate"
217
- type="text/plain"
218
- href="/llms.txt"
219
- title="LLM usage guidance"
220
- />
221
-
222
- {effectiveGaId && (
223
- <>
224
- <script
225
- is:inline
226
- async
227
- src={`https://www.googletagmanager.com/gtag/js?id=${effectiveGaId}`}
228
- />
229
- <script is:inline define:vars={{ gaId: effectiveGaId }}>
230
- window.dataLayer = window.dataLayer || [];
231
- function gtag() {
232
- dataLayer.push(arguments);
233
- }
234
- gtag("js", new Date());
235
- gtag("config", gaId, {
236
- page_path: window.location.pathname + window.location.search,
237
- page_title: document.title,
238
- });
239
- </script>
240
- </>
241
- )}
242
-
243
- <Seo
244
- title={title}
245
- description={description}
246
- type={type}
247
- noindex={noindex}
248
- jsonLd={jsonLd}
249
- {...(image ? { image } : {})}
250
- imageAlt={imageAlt}
251
- site={site}
252
- searchPath={directoryPath}
253
- />
254
-
255
- <slot name="head" />
256
-
257
- <script is:inline>
258
- // THEME_INIT — runs before paint so the user never sees a
259
- // flash of the wrong theme. State is persisted in localStorage
260
- // under the key "grove-theme" (matching ThemeToggle.astro).
261
- (function () {
262
- var STORAGE_KEY = "grove-theme";
263
- function getMode() {
264
- try {
265
- var stored = localStorage.getItem(STORAGE_KEY);
266
- if (stored === "light" || stored === "dark" || stored === "system")
267
- return stored;
268
- } catch (e) {
269
- // localStorage may be blocked (private mode, etc.) —
270
- // fall through to the system default.
271
- }
272
- return "system";
273
- }
274
- function apply(mode) {
275
- var resolved =
276
- mode === "system"
277
- ? (window.matchMedia("(prefers-color-scheme: dark)").matches
278
- ? "dark"
279
- : "light")
280
- : mode;
281
- var root = document.documentElement;
282
- if (resolved === "dark") root.classList.add("dark");
283
- else root.classList.remove("dark");
284
- root.dataset.theme = mode;
285
- root.dataset.resolvedTheme = resolved;
286
- }
287
- apply(getMode());
288
- window.addEventListener("theme:set", function (e) {
289
- apply(e.detail);
290
- });
291
- var mq = window.matchMedia("(prefers-color-scheme: dark)");
292
- mq.addEventListener("change", function () {
293
- if (getMode() === "system") apply("system");
294
- });
295
- })();
296
- </script>
297
- </head>
298
- <body class="min-h-screen bg-background text-foreground">
299
- <a
300
- href="#main"
301
- class="sr-only z-50 rounded-md bg-background px-3 py-2 text-sm font-medium text-foreground outline-none ring-2 ring-ring focus:not-sr-only focus:fixed focus:left-3 focus:top-3"
302
- >
303
- Skip to content
304
- </a>
305
- <slot name="header">
306
- <Header
307
- site={site}
308
- submitLabel={submitLabel ?? (site.blueprintConfig ? `Submit ${site.blueprintConfig.labelSingular ?? "item"}` : undefined)}
309
- submitHref={submitHref ?? (site.blueprintConfig ? "/submit" : undefined)}
310
- />
311
- </slot>
312
-
313
- <main id="main">
314
- <slot />
315
- </main>
316
-
317
- <slot name="footer">
318
- <Footer
319
- site={site}
320
- stats={{
321
- totalApps: site.stats?.totalApps,
322
- totalCategories: site.stats?.totalCategories,
323
- totalStacks: site.stats?.totalStacks,
324
- totalOwners: site.stats?.totalOwners,
325
- }}
326
- itemsLabel={site.blueprintConfig?.labelPlural ?? "items"}
327
- discover={{
328
- heading: "Discover",
329
- items: [
330
- ...(site.stats?.totalApps
331
- ? [{ label: "Browse all", href: directoryPath }]
332
- : []),
333
- { label: "Community", href: "/contributors" },
334
- ...(site.stats?.originalRepo
335
- ? [{ label: "View on GitHub", href: site.stats.originalRepo, external: true }]
336
- : []),
337
- ],
338
- }}
339
- contribute={{
340
- heading: "Contribute",
341
- items: [
342
- ...(site.repoUrl
343
- ? [
344
- {
345
- label: "Submit via GitHub",
346
- href: `${site.repoUrl}/blob/main/CONTRIBUTING.md`,
347
- external: true,
348
- },
349
- ]
350
- : []),
351
- ...(site.repoUrl
352
- ? [
353
- {
354
- label: "Report an issue",
355
- href: `${site.repoUrl}/issues`,
356
- external: true,
357
- },
358
- ]
359
- : []),
360
- ],
361
- }}
362
- project={{
363
- heading: "Project",
364
- items: [
365
- ...(site.repoUrl
366
- ? [
367
- {
368
- label: "Source on GitHub",
369
- href: site.repoUrl,
370
- external: true,
371
- },
372
- ]
373
- : []),
374
- ...(site.repoUrl
375
- ? [
376
- {
377
- label: "Changelog",
378
- href: `${site.repoUrl}/blob/main/CHANGELOG.md`,
379
- external: true,
380
- },
381
- ]
382
- : []),
383
- ],
384
- }}
385
- />
386
- </slot>
387
-
388
- <slot name="body-end" />
389
-
390
- {effectiveGaId && (
391
- <script is:inline define:vars={{ gaId: effectiveGaId }}>
392
- (function () {
393
- // GA4's `gtag('config', id)` call in the <head> already auto-pings
394
- // `page_view` on full navigations, so this block only needs to
395
- // catch history changes (back/forward). No history.pushState /
396
- // replaceState wrap: the package does not install
397
- // @astrojs/transitions' ClientRouter, and those wraps
398
- // double-fired page_view events on every navigation.
399
- var last = location.pathname + location.search;
400
- function report() {
401
- var next = location.pathname + location.search;
402
- if (next === last) return;
403
- last = next;
404
- window.gtag("event", "page_view", {
405
- page_path: next,
406
- page_title: document.title,
407
- page_location: location.href,
408
- });
409
- }
410
- window.addEventListener("popstate", report);
411
- })();
412
- </script>
413
- )}
414
- </body>
415
- </html>
@@ -1,18 +0,0 @@
1
- ---
2
- interface Props {
3
- size?: "default" | "wide" | "narrow";
4
- class?: string;
5
- }
6
-
7
- const { size = "default", class: className = "" } = Astro.props;
8
- const sizeClass =
9
- size === "wide"
10
- ? "mx-auto w-full max-w-wide"
11
- : size === "narrow"
12
- ? "mx-auto w-full max-w-narrow"
13
- : "mx-auto w-full max-w-container";
14
- ---
15
-
16
- <div class={`${sizeClass} px-5 sm:px-7 ${className}`}>
17
- <slot />
18
- </div>