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.
- package/dist/buildtime/changelog.d.ts +20 -0
- package/dist/buildtime/changelog.js +74 -0
- package/dist/buildtime/fence-meta.d.ts +20 -0
- package/dist/buildtime/fence-meta.js +48 -0
- package/dist/buildtime/markdown-layout.svelte.d.ts +3 -0
- package/dist/buildtime/preprocess.d.ts +13 -0
- package/dist/buildtime/preprocess.js +77 -17
- package/dist/buildtime/twoslash.d.ts +26 -0
- package/dist/buildtime/twoslash.js +40 -0
- package/dist/buildtime/vite/git.d.ts +7 -0
- package/dist/buildtime/vite/git.js +32 -0
- package/dist/buildtime/vite/index.d.ts +13 -0
- package/dist/buildtime/vite/index.js +16 -0
- package/dist/buildtime/vite/releases.d.ts +12 -0
- package/dist/buildtime/vite/releases.js +57 -0
- package/dist/components/changelog/changelog-entry.svelte +90 -0
- package/dist/components/changelog/changelog-entry.svelte.d.ts +7 -0
- package/dist/components/changelog/changelog.svelte +56 -0
- package/dist/components/changelog/changelog.svelte.d.ts +14 -0
- package/dist/components/docs/mermaid.svelte +213 -0
- package/dist/components/docs/mermaid.svelte.d.ts +6 -0
- package/dist/components/landing/action.svelte +50 -0
- package/dist/components/landing/action.svelte.d.ts +13 -0
- package/dist/components/landing/cta.svelte +52 -0
- package/dist/components/landing/cta.svelte.d.ts +13 -0
- package/dist/components/landing/feature-grid.svelte +51 -0
- package/dist/components/landing/feature-grid.svelte.d.ts +19 -0
- package/dist/components/landing/feature.svelte +33 -0
- package/dist/components/landing/feature.svelte.d.ts +11 -0
- package/dist/components/landing/hero.svelte +70 -0
- package/dist/components/landing/hero.svelte.d.ts +19 -0
- package/dist/components/layouts/docs-header.svelte +36 -3
- package/dist/components/markdown/pre.svelte +238 -8
- package/dist/components/markdown/pre.svelte.d.ts +6 -0
- package/dist/core/changelog.d.ts +31 -0
- package/dist/core/changelog.js +7 -0
- package/dist/fallbacks/changelog.d.ts +10 -0
- package/dist/fallbacks/changelog.js +3 -0
- package/dist/generate/feed.d.ts +35 -0
- package/dist/generate/feed.js +80 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/theme.css +32 -0
- 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,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;
|