svelte-docsmith 0.8.0 → 0.9.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 (44) hide show
  1. package/dist/buildtime/changelog.d.ts +20 -0
  2. package/dist/buildtime/changelog.js +74 -0
  3. package/dist/buildtime/fence-meta.d.ts +20 -0
  4. package/dist/buildtime/fence-meta.js +48 -0
  5. package/dist/buildtime/markdown-layout.svelte.d.ts +3 -0
  6. package/dist/buildtime/preprocess.d.ts +13 -0
  7. package/dist/buildtime/preprocess.js +77 -17
  8. package/dist/buildtime/twoslash.d.ts +26 -0
  9. package/dist/buildtime/twoslash.js +40 -0
  10. package/dist/buildtime/vite/git.d.ts +7 -0
  11. package/dist/buildtime/vite/git.js +32 -0
  12. package/dist/buildtime/vite/index.d.ts +13 -0
  13. package/dist/buildtime/vite/index.js +16 -0
  14. package/dist/buildtime/vite/releases.d.ts +12 -0
  15. package/dist/buildtime/vite/releases.js +57 -0
  16. package/dist/components/changelog/changelog-entry.svelte +90 -0
  17. package/dist/components/changelog/changelog-entry.svelte.d.ts +7 -0
  18. package/dist/components/changelog/changelog.svelte +56 -0
  19. package/dist/components/changelog/changelog.svelte.d.ts +14 -0
  20. package/dist/components/docs/mermaid.svelte +213 -0
  21. package/dist/components/docs/mermaid.svelte.d.ts +6 -0
  22. package/dist/components/landing/action.svelte +50 -0
  23. package/dist/components/landing/action.svelte.d.ts +13 -0
  24. package/dist/components/landing/cta.svelte +52 -0
  25. package/dist/components/landing/cta.svelte.d.ts +13 -0
  26. package/dist/components/landing/feature-grid.svelte +51 -0
  27. package/dist/components/landing/feature-grid.svelte.d.ts +19 -0
  28. package/dist/components/landing/feature.svelte +33 -0
  29. package/dist/components/landing/feature.svelte.d.ts +11 -0
  30. package/dist/components/landing/hero.svelte +70 -0
  31. package/dist/components/landing/hero.svelte.d.ts +19 -0
  32. package/dist/components/layouts/docs-header.svelte +36 -3
  33. package/dist/components/markdown/pre.svelte +238 -8
  34. package/dist/components/markdown/pre.svelte.d.ts +6 -0
  35. package/dist/core/changelog.d.ts +31 -0
  36. package/dist/core/changelog.js +7 -0
  37. package/dist/fallbacks/changelog.d.ts +10 -0
  38. package/dist/fallbacks/changelog.js +3 -0
  39. package/dist/generate/feed.d.ts +35 -0
  40. package/dist/generate/feed.js +80 -0
  41. package/dist/index.d.ts +8 -0
  42. package/dist/index.js +9 -0
  43. package/dist/theme.css +32 -0
  44. package/package.json +21 -2
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ import type { ChangelogRelease } from '../../core/changelog.js';
3
+
4
+ const { release }: { release: ChangelogRelease } = $props();
5
+
6
+ const formatted = $derived(
7
+ release.date
8
+ ? new Date(release.date).toLocaleDateString('en-US', {
9
+ year: 'numeric',
10
+ month: 'long',
11
+ day: 'numeric'
12
+ })
13
+ : undefined
14
+ );
15
+ </script>
16
+
17
+ <!-- The version is the anchor the feed links to, so it carries the id. -->
18
+ <section id={release.version} class="scroll-mt-24">
19
+ <div class="flex flex-wrap items-baseline gap-x-3 gap-y-1">
20
+ <h2 class="text-2xl font-semibold tracking-tight">
21
+ {#if release.path}
22
+ <a href={release.path} class="hover:text-primary transition-colors">{release.version}</a>
23
+ {:else}
24
+ {release.version}
25
+ {/if}
26
+ </h2>
27
+ {#if formatted}
28
+ <time datetime={release.date} class="text-muted-foreground text-sm">{formatted}</time>
29
+ {/if}
30
+ </div>
31
+
32
+ {#each release.groups as group (group.kind)}
33
+ <h3 class="text-muted-foreground mt-6 text-xs font-semibold tracking-wide uppercase">
34
+ {group.kind}
35
+ </h3>
36
+ <ul class="mt-3 space-y-4">
37
+ {#each group.items as item (item)}
38
+ <li class="border-border/70 border-l-2 pl-4">
39
+ <!-- Rendered at build time from your own repository's changelog. -->
40
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
41
+ <div class="changelog-prose">{@html item}</div>
42
+ </li>
43
+ {/each}
44
+ </ul>
45
+ {/each}
46
+ </section>
47
+
48
+ <style>
49
+ /* The entries arrive as HTML fragments rather than flowing through the
50
+ markdown layout's prose styles, so they need their own small set. */
51
+ .changelog-prose :global(p) {
52
+ margin: 0 0 0.75rem;
53
+ line-height: 1.65;
54
+ }
55
+ .changelog-prose :global(p:last-child) {
56
+ margin-bottom: 0;
57
+ }
58
+ .changelog-prose :global(code) {
59
+ font-family: var(--font-mono, ui-monospace, monospace);
60
+ font-size: 0.85em;
61
+ padding: 0.05em 0.3em;
62
+ border-radius: 0.3rem;
63
+ background: color-mix(in oklch, var(--muted) 60%, transparent);
64
+ }
65
+ .changelog-prose :global(a) {
66
+ color: var(--primary);
67
+ text-decoration: underline;
68
+ text-underline-offset: 2px;
69
+ }
70
+ .changelog-prose :global(ul) {
71
+ margin: 0.5rem 0 0.75rem;
72
+ padding-left: 1.1rem;
73
+ list-style: disc;
74
+ }
75
+ .changelog-prose :global(li) {
76
+ margin: 0.25rem 0;
77
+ }
78
+ .changelog-prose :global(pre) {
79
+ margin: 0.75rem 0;
80
+ padding: 0.75rem 1rem;
81
+ border-radius: var(--radius, 0.5rem);
82
+ background: var(--muted);
83
+ overflow-x: auto;
84
+ font-size: 0.85em;
85
+ }
86
+ .changelog-prose :global(pre code) {
87
+ background: none;
88
+ padding: 0;
89
+ }
90
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { ChangelogRelease } from '../../core/changelog.js';
2
+ type $$ComponentProps = {
3
+ release: ChangelogRelease;
4
+ };
5
+ declare const ChangelogEntry: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type ChangelogEntry = ReturnType<typeof ChangelogEntry>;
7
+ export default ChangelogEntry;
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import type { ChangelogRelease } from '../../core/changelog.js';
3
+ import ChangelogEntry from './changelog-entry.svelte';
4
+
5
+ const {
6
+ releases,
7
+ title = 'Changelog',
8
+ description,
9
+ feed = '/changelog.xml'
10
+ }: {
11
+ /** Releases from the generated `svelte-docsmith/changelog` index. */
12
+ releases: ChangelogRelease[];
13
+ /** Page heading. */
14
+ title?: string;
15
+ /** Line below the heading. */
16
+ description?: string;
17
+ /** Path to the Atom feed, or `false` to hide the subscribe link. */
18
+ feed?: string | false;
19
+ } = $props();
20
+ </script>
21
+
22
+ <div class="mx-auto max-w-3xl px-4 py-16 md:px-6 lg:py-20">
23
+ <header class="mb-12">
24
+ <h1 class="text-4xl font-semibold tracking-tight text-balance sm:text-5xl">{title}</h1>
25
+ {#if description}
26
+ <p class="text-muted-foreground mt-4 text-lg leading-relaxed text-pretty">{description}</p>
27
+ {/if}
28
+ {#if feed}
29
+ <a
30
+ href={feed}
31
+ class="text-muted-foreground hover:text-foreground mt-4 inline-flex items-center gap-2 text-sm transition-colors"
32
+ >
33
+ <svg viewBox="0 0 24 24" fill="currentColor" class="size-3.5" aria-hidden="true">
34
+ <path
35
+ d="M4 11a9 9 0 0 1 9 9M4 4a16 16 0 0 1 16 16M6 19a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"
36
+ fill="none"
37
+ stroke="currentColor"
38
+ stroke-width="2"
39
+ stroke-linecap="round"
40
+ />
41
+ </svg>
42
+ Subscribe via Atom
43
+ </a>
44
+ {/if}
45
+ </header>
46
+
47
+ {#if releases.length === 0}
48
+ <p class="text-muted-foreground">No releases yet.</p>
49
+ {:else}
50
+ <div class="space-y-14">
51
+ {#each releases as release (release.version)}
52
+ <ChangelogEntry {release} />
53
+ {/each}
54
+ </div>
55
+ {/if}
56
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { ChangelogRelease } from '../../core/changelog.js';
2
+ type $$ComponentProps = {
3
+ /** Releases from the generated `svelte-docsmith/changelog` index. */
4
+ releases: ChangelogRelease[];
5
+ /** Page heading. */
6
+ title?: string;
7
+ /** Line below the heading. */
8
+ description?: string;
9
+ /** Path to the Atom feed, or `false` to hide the subscribe link. */
10
+ feed?: string | false;
11
+ };
12
+ declare const Changelog: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type Changelog = ReturnType<typeof Changelog>;
14
+ export default Changelog;
@@ -0,0 +1,213 @@
1
+ <script module lang="ts">
2
+ type Mermaid = (typeof import('mermaid'))['default'];
3
+
4
+ // One shared import; the bundler dedupes the chunk, this dedupes the promise.
5
+ let loaded: Promise<Mermaid> | undefined;
6
+ const loadMermaid = () => (loaded ??= import('mermaid').then((m) => m.default));
7
+
8
+ // `mermaid.initialize()` writes global config and `render()` is async, so two
9
+ // diagrams rendering concurrently can apply each other's theme — most visibly
10
+ // when one falls back to a built-in theme and reconfigures for everyone. Every
11
+ // initialize/render pair runs through this queue so they never interleave.
12
+ let chain: Promise<unknown> = Promise.resolve();
13
+
14
+ function serialize<T>(job: () => Promise<T>): Promise<T> {
15
+ const result = chain.then(job, job);
16
+ // Swallow rejections on the chain itself so one bad diagram doesn't stall
17
+ // the queue for the rest of the page.
18
+ chain = result.catch(() => {});
19
+ return result;
20
+ }
21
+ </script>
22
+
23
+ <script lang="ts">
24
+ import { onMount } from 'svelte';
25
+
26
+ // Rendered from a ```mermaid code fence: the preprocessor emits a lazy
27
+ // `import('svelte-docsmith/mermaid')` so `mermaid` (an optional peer dep) is
28
+ // only pulled into pages that actually contain a diagram. Rendering is
29
+ // client-side — mermaid needs a DOM — with a `<pre>` source fallback.
30
+ const { code }: { code: string } = $props();
31
+
32
+ // Graph ids must be unique among elements currently in the document: mermaid
33
+ // clears whatever already carries the id it's given, so a shared id silently
34
+ // wipes another diagram that's already on the page. `$props.id()` is unique per
35
+ // instance and stays unique even if two copies of this module end up loaded.
36
+ const uid = $props.id();
37
+ // Bumped per render, because the theme toggle re-renders while this instance's
38
+ // previous SVG is still mounted — reusing the id would delete it mid-swap.
39
+ let renders = 0;
40
+
41
+ let svg = $state('');
42
+ let failed = $state(false);
43
+ // Whether the last render targeted dark mode, so we only re-render when the
44
+ // site theme actually flips (not on every unrelated <html> class change).
45
+ let lastDark: boolean | undefined;
46
+
47
+ // Reused canvas for normalizing color tokens (see resolveColor); `null` once
48
+ // we've determined this browser gives us no 2d context to work with.
49
+ let colorCanvas: CanvasRenderingContext2D | null | undefined;
50
+
51
+ // Assigning an unparseable colour to `fillStyle` is silently ignored rather
52
+ // than throwing, so we set this first and check whether it survived. Any
53
+ // colour works as long as it's an unlikely design token, since an unchanged
54
+ // `fillStyle` is what tells us the assignment was rejected.
55
+ const SENTINEL = '#010203';
56
+
57
+ // Resolve a CSS color to a concrete `#rrggbb` string mermaid's color math
58
+ // (khroma) can parse. Our design tokens are `oklch(...)`, which khroma can't
59
+ // read. Reading `fillStyle` back is not enough to convert them: a browser
60
+ // serializes a wide-gamut colour unchanged, so Chrome hands back the same
61
+ // `oklch(...)` string it was given. Painting a pixel and reading its bytes
62
+ // forces the conversion to sRGB. Returns null when the value can't be
63
+ // resolved, which also covers an engine that doesn't understand oklch at all.
64
+ function resolveColor(value: string): string | null {
65
+ colorCanvas ??= document.createElement('canvas').getContext('2d', { willReadFrequently: true });
66
+ const ctx = colorCanvas;
67
+ const input = value.trim();
68
+ if (!ctx || !input) return null;
69
+ ctx.fillStyle = SENTINEL;
70
+ ctx.fillStyle = input;
71
+ if (ctx.fillStyle === SENTINEL && input.toLowerCase() !== SENTINEL) return null;
72
+ ctx.clearRect(0, 0, 1, 1);
73
+ ctx.fillRect(0, 0, 1, 1);
74
+ const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
75
+ return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;
76
+ }
77
+
78
+ // Map the site's design tokens onto mermaid's documented theme variables, so a
79
+ // diagram reads as part of the site (its palette, borders, text) instead of
80
+ // mermaid's stock look. Read fresh each render so it follows the light/dark
81
+ // toggle. Returns null when the tokens can't be resolved — the caller then
82
+ // falls back to a built-in theme rather than shipping broken colors.
83
+ function themeVariables(dark: boolean): Record<string, string | boolean> | null {
84
+ const root = getComputedStyle(document.documentElement);
85
+ const background = resolveColor(root.getPropertyValue('--background'));
86
+ const foreground = resolveColor(root.getPropertyValue('--foreground'));
87
+ const muted = resolveColor(root.getPropertyValue('--muted'));
88
+ const mutedForeground = resolveColor(root.getPropertyValue('--muted-foreground'));
89
+ const border = resolveColor(root.getPropertyValue('--border'));
90
+ const accent = resolveColor(root.getPropertyValue('--accent'));
91
+ const card = resolveColor(root.getPropertyValue('--card'));
92
+ if (!background || !foreground || !muted || !mutedForeground || !border || !accent || !card) {
93
+ return null;
94
+ }
95
+ // Only mermaid's documented base variables — it derives node/cluster/edge
96
+ // colors from these, which keeps us off its more fragile internals.
97
+ return {
98
+ darkMode: dark,
99
+ background,
100
+ primaryColor: muted, // node fill
101
+ primaryTextColor: foreground,
102
+ primaryBorderColor: border,
103
+ lineColor: mutedForeground, // edges/arrows
104
+ secondaryColor: accent,
105
+ tertiaryColor: card
106
+ };
107
+ }
108
+
109
+ async function render() {
110
+ let mermaid: Mermaid;
111
+ try {
112
+ mermaid = await loadMermaid();
113
+ } catch {
114
+ // `mermaid` isn't installed — fall back to the source.
115
+ failed = true;
116
+ return;
117
+ }
118
+ const dark = document.documentElement.classList.contains('dark');
119
+ lastDark = dark;
120
+ const vars = themeVariables(dark);
121
+ // Try the token-themed `base` render first; if mermaid's color math rejects
122
+ // anything, retry with a built-in theme so a valid diagram never silently
123
+ // disappears. Only a genuine syntax error reaches the `<pre>` fallback.
124
+ const attempts = [
125
+ vars && { theme: 'base' as const, themeVariables: vars },
126
+ { theme: dark ? ('dark' as const) : ('default' as const) }
127
+ ];
128
+ for (const attempt of attempts) {
129
+ if (!attempt) continue;
130
+ try {
131
+ // Configure and render as one unit so a concurrent diagram can't
132
+ // re-initialize mermaid between the two calls.
133
+ const result = await serialize(() => {
134
+ mermaid.initialize({
135
+ startOnLoad: false,
136
+ securityLevel: 'strict',
137
+ fontFamily: 'inherit',
138
+ ...attempt
139
+ });
140
+ return mermaid.render(`${uid}-${(renders += 1)}`, code);
141
+ });
142
+ svg = result.svg;
143
+ failed = false;
144
+ return;
145
+ } catch {
146
+ // Try the next (safer) config.
147
+ }
148
+ }
149
+ // Bad diagram syntax — fall back to source.
150
+ failed = true;
151
+ }
152
+
153
+ onMount(() => {
154
+ render();
155
+ // Re-render when the theme toggles so the diagram follows light/dark.
156
+ const observer = new MutationObserver(() => {
157
+ if (document.documentElement.classList.contains('dark') !== lastDark) render();
158
+ });
159
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
160
+ return () => observer.disconnect();
161
+ });
162
+ </script>
163
+
164
+ {#if failed}
165
+ <pre class="docsmith-mermaid-fallback not-prose"><code>{code}</code></pre>
166
+ {:else if svg}
167
+ <!-- mermaid sanitizes its own output (securityLevel: strict) -->
168
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
169
+ <figure class="docsmith-mermaid not-prose">{@html svg}</figure>
170
+ {:else}
171
+ <!-- Shares the global .docsmith-mermaid-skeleton with the server-rendered
172
+ placeholder, so swapping to the diagram doesn't change the height. -->
173
+ <div class="docsmith-mermaid-skeleton not-prose" role="status" aria-label="Loading diagram"></div>
174
+ {/if}
175
+
176
+ <style>
177
+ /* The loading skeleton is global (theme.css), shared with the server-rendered
178
+ placeholder. Here we only style the resolved diagram and the fallback. */
179
+ .docsmith-mermaid {
180
+ margin: 1.5rem 0;
181
+ display: flex;
182
+ justify-content: center;
183
+ animation: docsmith-mermaid-in 0.25s ease-out;
184
+ }
185
+ .docsmith-mermaid :global(svg) {
186
+ max-width: 100%;
187
+ height: auto;
188
+ }
189
+ @keyframes docsmith-mermaid-in {
190
+ from {
191
+ opacity: 0;
192
+ }
193
+ to {
194
+ opacity: 1;
195
+ }
196
+ }
197
+ @media (prefers-reduced-motion: reduce) {
198
+ .docsmith-mermaid {
199
+ animation: none;
200
+ }
201
+ }
202
+
203
+ .docsmith-mermaid-fallback {
204
+ margin: 1.5rem 0;
205
+ padding: 1rem;
206
+ border-radius: var(--radius, 0.75rem);
207
+ border: 1px solid var(--border);
208
+ background: var(--muted);
209
+ overflow-x: auto;
210
+ font-size: 0.85rem;
211
+ line-height: 1.5;
212
+ }
213
+ </style>
@@ -0,0 +1,6 @@
1
+ type Mermaid = (typeof import('mermaid'))['default'];
2
+ type $$ComponentProps = {
3
+ code: string;
4
+ };
5
+ declare const Mermaid: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ export default Mermaid;
@@ -0,0 +1,50 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ const {
5
+ href,
6
+ variant = 'primary',
7
+ external = false,
8
+ arrow = variant === 'primary',
9
+ children
10
+ }: {
11
+ href: string;
12
+ /** `primary` is the filled button, `secondary` the outlined one beside it. */
13
+ variant?: 'primary' | 'secondary';
14
+ external?: boolean;
15
+ /** Trailing arrow that nudges on hover. Defaults on for `primary`. */
16
+ arrow?: boolean;
17
+ children: Snippet;
18
+ } = $props();
19
+
20
+ const rel = $derived(external ? 'noopener noreferrer' : undefined);
21
+ const target = $derived(external ? '_blank' : undefined);
22
+ </script>
23
+
24
+ <!-- A call-to-action link for <Hero> and <CTA>. Deliberately small: anything more
25
+ elaborate is better written as a plain anchor in your own page. -->
26
+ <a
27
+ {href}
28
+ {rel}
29
+ {target}
30
+ class="group inline-flex h-11 items-center gap-2 rounded-lg px-6 font-medium transition-all {variant ===
31
+ 'primary'
32
+ ? 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm hover:shadow-md'
33
+ : 'border-border bg-background hover:bg-accent border'}"
34
+ >
35
+ {@render children()}
36
+ {#if arrow}
37
+ <svg
38
+ viewBox="0 0 24 24"
39
+ fill="none"
40
+ stroke="currentColor"
41
+ stroke-width="2"
42
+ stroke-linecap="round"
43
+ stroke-linejoin="round"
44
+ class="size-4 transition-transform group-hover:translate-x-0.5"
45
+ aria-hidden="true"
46
+ >
47
+ <path d="M5 12h14M12 5l7 7-7 7" />
48
+ </svg>
49
+ {/if}
50
+ </a>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ href: string;
4
+ /** `primary` is the filled button, `secondary` the outlined one beside it. */
5
+ variant?: 'primary' | 'secondary';
6
+ external?: boolean;
7
+ /** Trailing arrow that nudges on hover. Defaults on for `primary`. */
8
+ arrow?: boolean;
9
+ children: Snippet;
10
+ };
11
+ declare const Action: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type Action = ReturnType<typeof Action>;
13
+ export default Action;
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ const {
5
+ title,
6
+ description,
7
+ actions,
8
+ children
9
+ }: {
10
+ title: string;
11
+ /** Supporting line below the heading. */
12
+ description?: string;
13
+ /** Call-to-action buttons. */
14
+ actions?: Snippet;
15
+ /** Anything to sit below the actions, e.g. a note or a callout. */
16
+ children?: Snippet;
17
+ } = $props();
18
+ </script>
19
+
20
+ <!-- The closing panel of a landing page: a bordered card with a soft glow behind
21
+ the heading, so the page ends on the primary colour rather than trailing off. -->
22
+ <section class="mx-auto max-w-7xl px-4 py-20 md:px-6 lg:px-8 lg:py-28">
23
+ <div
24
+ class="border-border bg-card relative isolate overflow-hidden rounded-2xl border px-6 py-16 text-center shadow-sm md:px-12"
25
+ >
26
+ <div class="pointer-events-none absolute inset-0 -z-10" aria-hidden="true">
27
+ <div
28
+ class="bg-primary/15 absolute top-0 left-1/2 h-75 w-150 -translate-x-1/2 rounded-full opacity-50 blur-[100px]"
29
+ ></div>
30
+ </div>
31
+
32
+ <h2 class="mx-auto max-w-2xl text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
33
+ {title}
34
+ </h2>
35
+
36
+ {#if description}
37
+ <p class="text-muted-foreground mx-auto mt-4 max-w-xl text-lg leading-relaxed text-pretty">
38
+ {description}
39
+ </p>
40
+ {/if}
41
+
42
+ {#if actions}
43
+ <div class="mt-8 flex flex-wrap items-center justify-center gap-3">
44
+ {@render actions()}
45
+ </div>
46
+ {/if}
47
+
48
+ {#if children}
49
+ <div class="mx-auto mt-10 max-w-xl text-left">{@render children()}</div>
50
+ {/if}
51
+ </div>
52
+ </section>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ title: string;
4
+ /** Supporting line below the heading. */
5
+ description?: string;
6
+ /** Call-to-action buttons. */
7
+ actions?: Snippet;
8
+ /** Anything to sit below the actions, e.g. a note or a callout. */
9
+ children?: Snippet;
10
+ };
11
+ declare const Cta: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type Cta = ReturnType<typeof Cta>;
13
+ export default Cta;
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ const {
5
+ title,
6
+ description,
7
+ columns = 3,
8
+ background = 'default',
9
+ children
10
+ }: {
11
+ /** Section heading above the grid. */
12
+ title?: string;
13
+ /** Supporting line below the heading. */
14
+ description?: string;
15
+ /** Columns at the widest breakpoint; the grid steps down on smaller screens. */
16
+ columns?: 2 | 3;
17
+ /**
18
+ * `muted` gives the section a tinted band with rules above and below, for
19
+ * alternating against neighbouring sections.
20
+ */
21
+ background?: 'default' | 'muted';
22
+ /** The `<Feature>` cells. */
23
+ children: Snippet;
24
+ } = $props();
25
+ </script>
26
+
27
+ <section class={background === 'muted' ? 'border-border/50 bg-muted/20 border-y' : ''}>
28
+ <div class="mx-auto max-w-7xl px-4 py-16 md:px-6 lg:px-8 lg:py-24">
29
+ {#if title || description}
30
+ <div class="mx-auto max-w-2xl text-center">
31
+ {#if title}
32
+ <h2 class="text-3xl font-semibold tracking-tight text-balance sm:text-4xl">{title}</h2>
33
+ {/if}
34
+ {#if description}
35
+ <p class="text-muted-foreground mt-4 text-lg leading-relaxed text-pretty">
36
+ {description}
37
+ </p>
38
+ {/if}
39
+ </div>
40
+ {/if}
41
+
42
+ <div
43
+ class="grid gap-x-10 gap-y-10 sm:grid-cols-2 {columns === 3 ? 'lg:grid-cols-3' : ''} {title ||
44
+ description
45
+ ? 'mt-14'
46
+ : ''}"
47
+ >
48
+ {@render children()}
49
+ </div>
50
+ </div>
51
+ </section>
@@ -0,0 +1,19 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ /** Section heading above the grid. */
4
+ title?: string;
5
+ /** Supporting line below the heading. */
6
+ description?: string;
7
+ /** Columns at the widest breakpoint; the grid steps down on smaller screens. */
8
+ columns?: 2 | 3;
9
+ /**
10
+ * `muted` gives the section a tinted band with rules above and below, for
11
+ * alternating against neighbouring sections.
12
+ */
13
+ background?: 'default' | 'muted';
14
+ /** The `<Feature>` cells. */
15
+ children: Snippet;
16
+ };
17
+ declare const FeatureGrid: import("svelte").Component<$$ComponentProps, {}, "">;
18
+ type FeatureGrid = ReturnType<typeof FeatureGrid>;
19
+ export default FeatureGrid;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ const {
5
+ title,
6
+ icon,
7
+ children
8
+ }: {
9
+ title: string;
10
+ /** Optional leading icon, rendered in a tinted square. */
11
+ icon?: Snippet;
12
+ /** The description. */
13
+ children?: Snippet;
14
+ } = $props();
15
+ </script>
16
+
17
+ <!-- One cell of a <FeatureGrid>. Icon beside the text rather than above it, so a
18
+ row of features stays compact as the column count grows. -->
19
+ <div class="flex gap-4">
20
+ {#if icon}
21
+ <div
22
+ class="bg-primary/10 text-primary flex size-10 shrink-0 items-center justify-center rounded-lg"
23
+ >
24
+ {@render icon()}
25
+ </div>
26
+ {/if}
27
+ <div class="min-w-0">
28
+ <h3 class="font-semibold">{title}</h3>
29
+ {#if children}
30
+ <p class="text-muted-foreground mt-1 text-sm leading-relaxed">{@render children()}</p>
31
+ {/if}
32
+ </div>
33
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ title: string;
4
+ /** Optional leading icon, rendered in a tinted square. */
5
+ icon?: Snippet;
6
+ /** The description. */
7
+ children?: Snippet;
8
+ };
9
+ declare const Feature: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type Feature = ReturnType<typeof Feature>;
11
+ export default Feature;