@casoon/pages-theme 0.0.0-stage → 0.12.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +279 -2
- package/assets/casoon-logo-dark.svg +70 -0
- package/assets/casoon-logo.svg +71 -0
- package/assets/favicon.svg +1 -0
- package/components/AnchorGrid.astro +135 -0
- package/components/ApiEntry.astro +136 -0
- package/components/Badge.astro +31 -0
- package/components/Badges.astro +10 -0
- package/components/BuildOutput.astro +84 -0
- package/components/Button.astro +55 -0
- package/components/CallToAction.astro +64 -0
- package/components/Callout.astro +66 -0
- package/components/CodeBlock.astro +84 -0
- package/components/Demo.astro +80 -0
- package/components/ExamplePanel.astro +153 -0
- package/components/Facts.astro +112 -0
- package/components/Faq.astro +123 -0
- package/components/Features.astro +139 -0
- package/components/FileSet.astro +258 -0
- package/components/Footer.astro +160 -0
- package/components/Header.astro +278 -0
- package/components/Hero.astro +318 -0
- package/components/Install.astro +154 -0
- package/components/Integrations.astro +125 -0
- package/components/Keymap.astro +102 -0
- package/components/Quickstart.astro +95 -0
- package/components/ReleaseHighlights.astro +115 -0
- package/components/Spotlight.astro +177 -0
- package/components/Swatch.astro +45 -0
- package/components/Tabs.astro +90 -0
- package/components/Terminal.astro +79 -0
- package/components/index.ts +25 -0
- package/content.ts +23 -0
- package/fonts/OFL.txt +95 -0
- package/fonts/inter-latin-400-normal.woff2 +0 -0
- package/fonts/inter-latin-500-normal.woff2 +0 -0
- package/fonts/inter-latin-600-normal.woff2 +0 -0
- package/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/fonts/onest-latin-wght-normal.woff2 +0 -0
- package/index.ts +204 -0
- package/layouts/Base.astro +131 -0
- package/layouts/Docs.astro +286 -0
- package/lib/ansi.ts +168 -0
- package/lib/changelog.ts +85 -0
- package/lib/docs.ts +54 -0
- package/lib/i18n.ts +136 -0
- package/lib/og.ts +91 -0
- package/lib/showcase.ts +48 -0
- package/lib/url.ts +16 -0
- package/lib/version.ts +50 -0
- package/package.json +71 -4
- package/routes/404.astro +49 -0
- package/routes/changelog.astro +129 -0
- package/routes/docs/[...slug].astro +39 -0
- package/routes/showcase/[slug].astro +108 -0
- package/routes/showcase/index.astro +195 -0
- package/styles/base.css +307 -0
- package/styles/fonts.css +52 -0
- package/styles/tokens.css +136 -0
- package/virtual.d.ts +12 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The files a run produced, in three views:
|
|
4
|
+
* tabs — CSS-only tabs, one pane per file (hero layout "stack")
|
|
5
|
+
* grid — one card per file, the first lines of each
|
|
6
|
+
* rail — file names with a one-line note, no content
|
|
7
|
+
* One named slot per file id holds the content; `rail` needs none.
|
|
8
|
+
*/
|
|
9
|
+
import config from 'virtual:casoon-pages/config';
|
|
10
|
+
import { t } from '../lib/i18n';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
files: { id: string; name: string; path?: string; size?: string; note?: string }[];
|
|
14
|
+
view?: 'tabs' | 'grid' | 'rail';
|
|
15
|
+
label?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { files, view = 'tabs', label = t(config.lang).filesLabel } = Astro.props;
|
|
19
|
+
const uid = `files-${files.map((f) => f.id).join('-')}`;
|
|
20
|
+
// Narrow screens show the name without its suffix, so five tabs still fit on one line.
|
|
21
|
+
// Only when the stems stay distinct — `data.txt` next to `data.xml` would read as two tabs alike.
|
|
22
|
+
const stems = files.map((f) => f.name.replace(/\.[^.]+$/, ''));
|
|
23
|
+
const abbreviate = new Set(stems).size === files.length;
|
|
24
|
+
const panels =
|
|
25
|
+
view === 'rail'
|
|
26
|
+
? []
|
|
27
|
+
: await Promise.all(files.map((f) => (Astro.slots.has(f.id) ? Astro.slots.render(f.id) : '')));
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
view === 'tabs' && (
|
|
32
|
+
<div class="files">
|
|
33
|
+
<div class:list={['list', abbreviate && 'abbrev']} role="radiogroup" aria-label={label}>
|
|
34
|
+
{files.map((f, i) => (
|
|
35
|
+
<>
|
|
36
|
+
<input
|
|
37
|
+
class="visually-hidden"
|
|
38
|
+
type="radio"
|
|
39
|
+
name={uid}
|
|
40
|
+
id={`${uid}-${i}`}
|
|
41
|
+
value={i}
|
|
42
|
+
checked={i === 0}
|
|
43
|
+
/>
|
|
44
|
+
<label for={`${uid}-${i}`}>
|
|
45
|
+
<span class="full">{f.name}</span>
|
|
46
|
+
{abbreviate && <span class="stem">{stems[i]}</span>}
|
|
47
|
+
</label>
|
|
48
|
+
</>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
{files.map((f, i) => (
|
|
52
|
+
<div class="panel" data-i={i}>
|
|
53
|
+
{/* Long lines scroll horizontally; tabindex keeps the region keyboard-reachable. */}
|
|
54
|
+
<pre tabindex="0" set:html={panels[i]} />
|
|
55
|
+
{(f.path || f.size) && (
|
|
56
|
+
<p class="meta">
|
|
57
|
+
{f.path && <span class="path">{f.path}</span>}
|
|
58
|
+
{f.size && <span>{f.size}</span>}
|
|
59
|
+
</p>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
view === 'grid' && (
|
|
69
|
+
<ul class="grid" aria-label={label}>
|
|
70
|
+
{files.map((f, i) => (
|
|
71
|
+
<li>
|
|
72
|
+
<p class="name">{f.name}</p>
|
|
73
|
+
<pre set:html={panels[i]} />
|
|
74
|
+
{f.note && <p class="note">{f.note}</p>}
|
|
75
|
+
</li>
|
|
76
|
+
))}
|
|
77
|
+
</ul>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
view === 'rail' && (
|
|
83
|
+
<ul class="rail" aria-label={label}>
|
|
84
|
+
{files.map((f) => (
|
|
85
|
+
<li>
|
|
86
|
+
<span class="dot" aria-hidden="true" />
|
|
87
|
+
<p class="name">{f.name}</p>
|
|
88
|
+
{f.note && <p class="note">{f.note}</p>}
|
|
89
|
+
</li>
|
|
90
|
+
))}
|
|
91
|
+
</ul>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
<style>
|
|
96
|
+
.files {
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
border: 1px solid var(--border);
|
|
99
|
+
border-radius: 14px;
|
|
100
|
+
background: var(--surface);
|
|
101
|
+
box-shadow: var(--shadow);
|
|
102
|
+
}
|
|
103
|
+
/* One row at every width: the names shorten first, and only then does the strip scroll. */
|
|
104
|
+
.list {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-wrap: nowrap;
|
|
107
|
+
gap: 2px;
|
|
108
|
+
overflow-x: auto;
|
|
109
|
+
border-bottom: 1px solid var(--border);
|
|
110
|
+
background: var(--bg-deep);
|
|
111
|
+
scrollbar-width: none;
|
|
112
|
+
}
|
|
113
|
+
.list::-webkit-scrollbar {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
label {
|
|
117
|
+
flex: 1 0 auto;
|
|
118
|
+
padding: 13px 10px;
|
|
119
|
+
border-top: 2px solid transparent;
|
|
120
|
+
color: var(--muted);
|
|
121
|
+
font-family: var(--font-mono);
|
|
122
|
+
font-size: 12.5px;
|
|
123
|
+
text-align: center;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
}
|
|
126
|
+
input:checked + label {
|
|
127
|
+
border-top-color: var(--accent);
|
|
128
|
+
background: var(--surface);
|
|
129
|
+
color: var(--heading);
|
|
130
|
+
}
|
|
131
|
+
input:focus-visible + label {
|
|
132
|
+
outline: 2px solid var(--accent);
|
|
133
|
+
outline-offset: -2px;
|
|
134
|
+
}
|
|
135
|
+
.stem {
|
|
136
|
+
display: none;
|
|
137
|
+
}
|
|
138
|
+
@media (max-width: 640px) {
|
|
139
|
+
.abbrev .full {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
142
|
+
.abbrev .stem {
|
|
143
|
+
display: inline;
|
|
144
|
+
}
|
|
145
|
+
label {
|
|
146
|
+
padding: 13px 6px;
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
.panel {
|
|
151
|
+
display: none;
|
|
152
|
+
}
|
|
153
|
+
/* First panel is the no-:has() fallback. */
|
|
154
|
+
.panel[data-i='0'],
|
|
155
|
+
.files:has(input[value='1']:checked) .panel[data-i='1'],
|
|
156
|
+
.files:has(input[value='2']:checked) .panel[data-i='2'],
|
|
157
|
+
.files:has(input[value='3']:checked) .panel[data-i='3'],
|
|
158
|
+
.files:has(input[value='4']:checked) .panel[data-i='4'],
|
|
159
|
+
.files:has(input[value='5']:checked) .panel[data-i='5'] {
|
|
160
|
+
display: block;
|
|
161
|
+
}
|
|
162
|
+
.files:has(input:not([value='0']):checked) .panel[data-i='0'] {
|
|
163
|
+
display: none;
|
|
164
|
+
}
|
|
165
|
+
.panel pre {
|
|
166
|
+
min-height: 210px;
|
|
167
|
+
max-height: 300px;
|
|
168
|
+
margin: 0;
|
|
169
|
+
padding: 16px 20px;
|
|
170
|
+
overflow: auto;
|
|
171
|
+
color: var(--text);
|
|
172
|
+
font-size: 12.5px;
|
|
173
|
+
line-height: 1.75;
|
|
174
|
+
}
|
|
175
|
+
.meta {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-wrap: wrap;
|
|
178
|
+
gap: 6px 10px;
|
|
179
|
+
margin: 0;
|
|
180
|
+
padding: 10px 20px;
|
|
181
|
+
border-top: 1px solid var(--border);
|
|
182
|
+
color: var(--muted);
|
|
183
|
+
font-family: var(--font-mono);
|
|
184
|
+
font-size: 11.5px;
|
|
185
|
+
}
|
|
186
|
+
.path {
|
|
187
|
+
color: var(--accent);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.grid {
|
|
191
|
+
display: grid;
|
|
192
|
+
grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
|
|
193
|
+
gap: 12px;
|
|
194
|
+
margin: 0;
|
|
195
|
+
padding: 0;
|
|
196
|
+
list-style: none;
|
|
197
|
+
}
|
|
198
|
+
.grid li {
|
|
199
|
+
padding: 16px 18px;
|
|
200
|
+
border: 1px solid var(--border);
|
|
201
|
+
border-top: 3px solid var(--accent);
|
|
202
|
+
border-radius: 12px;
|
|
203
|
+
background: var(--surface);
|
|
204
|
+
}
|
|
205
|
+
.grid .name {
|
|
206
|
+
margin: 0 0 8px;
|
|
207
|
+
color: var(--heading);
|
|
208
|
+
font-family: var(--font-mono);
|
|
209
|
+
font-size: 13.5px;
|
|
210
|
+
font-weight: 500;
|
|
211
|
+
}
|
|
212
|
+
.grid pre {
|
|
213
|
+
max-height: 72px;
|
|
214
|
+
margin: 0;
|
|
215
|
+
overflow: hidden;
|
|
216
|
+
color: var(--muted);
|
|
217
|
+
font-size: 11.5px;
|
|
218
|
+
line-height: 1.7;
|
|
219
|
+
/* The excerpt ends in a fade, so the cut reads as deliberate rather than broken. */
|
|
220
|
+
mask-image: linear-gradient(to bottom, #000 58%, transparent);
|
|
221
|
+
}
|
|
222
|
+
.grid .note {
|
|
223
|
+
margin: 8px 0 0;
|
|
224
|
+
font-size: 12.5px;
|
|
225
|
+
line-height: 1.5;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.rail {
|
|
229
|
+
display: grid;
|
|
230
|
+
grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
|
|
231
|
+
gap: 24px;
|
|
232
|
+
margin: 0;
|
|
233
|
+
padding: 22px 0 0;
|
|
234
|
+
border-top: 1px solid var(--border-strong);
|
|
235
|
+
list-style: none;
|
|
236
|
+
}
|
|
237
|
+
.rail .dot {
|
|
238
|
+
display: block;
|
|
239
|
+
width: 7px;
|
|
240
|
+
height: 7px;
|
|
241
|
+
margin-bottom: 12px;
|
|
242
|
+
border-radius: 50%;
|
|
243
|
+
background: var(--accent);
|
|
244
|
+
}
|
|
245
|
+
.rail .name {
|
|
246
|
+
margin: 0 0 5px;
|
|
247
|
+
color: var(--heading);
|
|
248
|
+
font-family: var(--font-mono);
|
|
249
|
+
font-size: 14px;
|
|
250
|
+
font-weight: 500;
|
|
251
|
+
}
|
|
252
|
+
.rail .note {
|
|
253
|
+
margin: 0;
|
|
254
|
+
color: var(--muted);
|
|
255
|
+
font-size: 12.5px;
|
|
256
|
+
line-height: 1.5;
|
|
257
|
+
}
|
|
258
|
+
</style>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:casoon-pages/config';
|
|
3
|
+
import logo from '../assets/casoon-logo.svg';
|
|
4
|
+
import logoDark from '../assets/casoon-logo-dark.svg';
|
|
5
|
+
import { t } from '../lib/i18n';
|
|
6
|
+
import { CASOON_URL, catalogUrl, url } from '../lib/url';
|
|
7
|
+
|
|
8
|
+
const s = t(config.lang);
|
|
9
|
+
const IMPRESSUM = `${CASOON_URL}impressum/`;
|
|
10
|
+
const DATENSCHUTZ = `${CASOON_URL}datenschutz/`;
|
|
11
|
+
|
|
12
|
+
const columns = [
|
|
13
|
+
{
|
|
14
|
+
title: s.footerProject,
|
|
15
|
+
links: [
|
|
16
|
+
...(config.website ? [{ label: s.website(config.name), href: config.website }] : []),
|
|
17
|
+
{ label: s.documentation, href: url('docs/') },
|
|
18
|
+
...(config.hasShowcase ? [{ label: s.showcase, href: url('showcase/') }] : []),
|
|
19
|
+
...(config.changelog ? [{ label: s.changelog, href: url('changelog/') }] : []),
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: s.footerPackages,
|
|
24
|
+
links: [
|
|
25
|
+
...(config.repo ? [{ label: 'GitHub', href: `https://github.com/${config.repo}` }] : []),
|
|
26
|
+
...config.packages,
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
title: 'CASOON',
|
|
31
|
+
links: [
|
|
32
|
+
{ label: 'casoon.de', href: CASOON_URL },
|
|
33
|
+
...(config.catalog
|
|
34
|
+
? [{ label: s.catalogEntry(config.name), href: catalogUrl(config.lang, config.catalog) }]
|
|
35
|
+
: []),
|
|
36
|
+
// Only a public GitHub project is actually part of the open-source catalog.
|
|
37
|
+
...(config.repo ? [{ label: s.allProjects, href: catalogUrl(config.lang) }] : []),
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<footer class="footer">
|
|
44
|
+
<div class="cols">
|
|
45
|
+
<div>
|
|
46
|
+
<a class="logo" href={CASOON_URL}>
|
|
47
|
+
<img class="logo-light" src={logo.src} width="150" height="41" alt="CASOON" />
|
|
48
|
+
<img class="logo-dark" src={logoDark.src} width="150" height="41" alt="CASOON" />
|
|
49
|
+
</a>
|
|
50
|
+
<p>{config.description}</p>
|
|
51
|
+
<p class="by">{s.footerBy} · Jörn Seidel</p>
|
|
52
|
+
</div>
|
|
53
|
+
{
|
|
54
|
+
columns.filter((col) => col.links.length > 0).map((col) => (
|
|
55
|
+
<nav aria-label={col.title}>
|
|
56
|
+
<p class="title">{col.title}</p>
|
|
57
|
+
<ul>
|
|
58
|
+
{col.links.map((link) => (
|
|
59
|
+
<li>
|
|
60
|
+
<a href={link.href}>{link.label}</a>
|
|
61
|
+
</li>
|
|
62
|
+
))}
|
|
63
|
+
</ul>
|
|
64
|
+
</nav>
|
|
65
|
+
))
|
|
66
|
+
}
|
|
67
|
+
</div>
|
|
68
|
+
<div class="bottom">
|
|
69
|
+
<span>{config.license && `${s.license(config.license)} · `}© {new Date().getFullYear()} Jörn Seidel</span>
|
|
70
|
+
<span class="legal">
|
|
71
|
+
<a href={IMPRESSUM}>{s.imprint}</a>
|
|
72
|
+
<a href={DATENSCHUTZ}>{s.privacyPolicy}</a>
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
</footer>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
/* Black footer in both themes. */
|
|
79
|
+
.footer {
|
|
80
|
+
--text: #b4b1ac;
|
|
81
|
+
--muted: #b4b1ac;
|
|
82
|
+
--heading: #fff;
|
|
83
|
+
--border: rgb(255 255 255 / 0.12);
|
|
84
|
+
|
|
85
|
+
background: #11100e;
|
|
86
|
+
color: var(--text);
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
}
|
|
89
|
+
.cols {
|
|
90
|
+
display: grid;
|
|
91
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
92
|
+
gap: 40px;
|
|
93
|
+
max-width: var(--max);
|
|
94
|
+
margin: 0 auto;
|
|
95
|
+
padding: 80px 24px 48px;
|
|
96
|
+
}
|
|
97
|
+
.logo {
|
|
98
|
+
display: inline-block;
|
|
99
|
+
margin-bottom: 14px;
|
|
100
|
+
}
|
|
101
|
+
.logo img {
|
|
102
|
+
display: block;
|
|
103
|
+
width: 120px;
|
|
104
|
+
height: auto;
|
|
105
|
+
}
|
|
106
|
+
.logo-light {
|
|
107
|
+
display: none !important;
|
|
108
|
+
}
|
|
109
|
+
p {
|
|
110
|
+
margin: 0 0 6px;
|
|
111
|
+
max-width: 32ch;
|
|
112
|
+
}
|
|
113
|
+
.by {
|
|
114
|
+
color: var(--muted);
|
|
115
|
+
font-size: 12.5px;
|
|
116
|
+
}
|
|
117
|
+
.title {
|
|
118
|
+
margin-bottom: 16px;
|
|
119
|
+
color: var(--heading);
|
|
120
|
+
font-size: 18px;
|
|
121
|
+
font-weight: 500;
|
|
122
|
+
}
|
|
123
|
+
ul {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: 12px;
|
|
127
|
+
margin: 0;
|
|
128
|
+
padding: 0;
|
|
129
|
+
list-style: none;
|
|
130
|
+
}
|
|
131
|
+
ul a {
|
|
132
|
+
color: var(--text);
|
|
133
|
+
}
|
|
134
|
+
ul a:hover {
|
|
135
|
+
color: var(--heading);
|
|
136
|
+
}
|
|
137
|
+
.bottom {
|
|
138
|
+
display: flex;
|
|
139
|
+
flex-wrap: wrap;
|
|
140
|
+
justify-content: space-between;
|
|
141
|
+
gap: 16px;
|
|
142
|
+
max-width: var(--max);
|
|
143
|
+
margin: 0 auto;
|
|
144
|
+
padding: 16px 24px 40px;
|
|
145
|
+
border-top: 1px solid var(--border);
|
|
146
|
+
color: var(--muted);
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
}
|
|
149
|
+
.legal {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-wrap: wrap;
|
|
152
|
+
gap: 18px;
|
|
153
|
+
}
|
|
154
|
+
.legal a {
|
|
155
|
+
color: var(--muted);
|
|
156
|
+
}
|
|
157
|
+
.legal a:hover {
|
|
158
|
+
color: var(--heading);
|
|
159
|
+
}
|
|
160
|
+
</style>
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:casoon-pages/config';
|
|
3
|
+
import { t } from '../lib/i18n';
|
|
4
|
+
import { url } from '../lib/url';
|
|
5
|
+
|
|
6
|
+
const s = t(config.lang);
|
|
7
|
+
const path = Astro.url.pathname;
|
|
8
|
+
const websiteHost = config.website && new URL(config.website).host.replace(/^www\./, '');
|
|
9
|
+
const nav = [
|
|
10
|
+
{ label: s.docs, href: url('docs/') },
|
|
11
|
+
...(config.demo ? [{ label: config.demo, href: url('demo/') }] : []),
|
|
12
|
+
...(config.hasShowcase ? [{ label: s.showcase, href: url('showcase/') }] : []),
|
|
13
|
+
...(config.changelog ? [{ label: s.changelog, href: url('changelog/') }] : []),
|
|
14
|
+
];
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<header class="header" data-wide={websiteHost || config.demo ? '' : undefined}>
|
|
18
|
+
<div class="inner">
|
|
19
|
+
<a class="home" href={url()}>
|
|
20
|
+
<span class="name">{config.name}</span>
|
|
21
|
+
{config.version && <span class="version">v{config.version}</span>}
|
|
22
|
+
</a>
|
|
23
|
+
<div class="links">
|
|
24
|
+
{
|
|
25
|
+
websiteHost && (
|
|
26
|
+
<a class="website" href={config.website}>
|
|
27
|
+
{websiteHost}
|
|
28
|
+
</a>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
<nav aria-label={s.mainNav}>
|
|
32
|
+
<ul>
|
|
33
|
+
{
|
|
34
|
+
nav.map((item) => (
|
|
35
|
+
<li>
|
|
36
|
+
<a href={item.href} aria-current={path.startsWith(item.href) ? 'page' : undefined}>
|
|
37
|
+
{item.label}
|
|
38
|
+
</a>
|
|
39
|
+
</li>
|
|
40
|
+
))
|
|
41
|
+
}
|
|
42
|
+
</ul>
|
|
43
|
+
<span class="sep" aria-hidden="true"></span>
|
|
44
|
+
{config.repo && <a class="cta" href={`https://github.com/${config.repo}`}>GitHub</a>}
|
|
45
|
+
</nav>
|
|
46
|
+
</div>
|
|
47
|
+
<button class="theme" type="button" data-theme-toggle aria-label={s.darkTheme} aria-pressed="false" hidden>
|
|
48
|
+
<!-- Shows the theme a click switches to: moon while light, sun while dark. -->
|
|
49
|
+
<svg class="icon-moon" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
|
50
|
+
<path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5Z" />
|
|
51
|
+
</svg>
|
|
52
|
+
<svg class="icon-sun" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
|
53
|
+
<circle cx="12" cy="12" r="4" />
|
|
54
|
+
<path d="M12 2.5v2M12 19.5v2M2.5 12h2M19.5 12h2M5.3 5.3l1.4 1.4M17.3 17.3l1.4 1.4M5.3 18.7l1.4-1.4M17.3 6.7l1.4-1.4" />
|
|
55
|
+
</svg>
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</header>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
/* Floating white pill over the page. */
|
|
62
|
+
.header {
|
|
63
|
+
position: sticky;
|
|
64
|
+
top: 0;
|
|
65
|
+
z-index: 50;
|
|
66
|
+
padding: 14px 24px 0;
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
}
|
|
69
|
+
.inner {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
gap: 20px;
|
|
73
|
+
max-width: 1000px;
|
|
74
|
+
height: var(--header-h);
|
|
75
|
+
margin: 0 auto;
|
|
76
|
+
padding: 0 10px 0 24px;
|
|
77
|
+
border-radius: 40px;
|
|
78
|
+
background: var(--surface);
|
|
79
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
|
|
80
|
+
pointer-events: auto;
|
|
81
|
+
}
|
|
82
|
+
.home {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 10px;
|
|
86
|
+
color: var(--heading);
|
|
87
|
+
}
|
|
88
|
+
.home:hover {
|
|
89
|
+
color: var(--heading);
|
|
90
|
+
}
|
|
91
|
+
.name {
|
|
92
|
+
font-size: 19px;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
letter-spacing: -0.01em;
|
|
95
|
+
}
|
|
96
|
+
.version {
|
|
97
|
+
padding: 1px 7px;
|
|
98
|
+
border: 1px solid var(--border);
|
|
99
|
+
border-radius: 999px;
|
|
100
|
+
color: var(--muted);
|
|
101
|
+
font-family: var(--font-mono);
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
}
|
|
104
|
+
.website {
|
|
105
|
+
padding: 3px 10px;
|
|
106
|
+
border: 1px solid var(--border-strong);
|
|
107
|
+
border-radius: 999px;
|
|
108
|
+
color: var(--text);
|
|
109
|
+
font-size: 13.5px;
|
|
110
|
+
white-space: nowrap;
|
|
111
|
+
}
|
|
112
|
+
.website:hover {
|
|
113
|
+
border-color: var(--accent);
|
|
114
|
+
color: var(--heading);
|
|
115
|
+
}
|
|
116
|
+
.links {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex: 1;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: 20px;
|
|
121
|
+
min-width: 0;
|
|
122
|
+
}
|
|
123
|
+
nav {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: 2px;
|
|
127
|
+
margin-left: auto;
|
|
128
|
+
}
|
|
129
|
+
ul {
|
|
130
|
+
display: flex;
|
|
131
|
+
gap: 2px;
|
|
132
|
+
margin: 0;
|
|
133
|
+
padding: 0;
|
|
134
|
+
list-style: none;
|
|
135
|
+
}
|
|
136
|
+
nav a {
|
|
137
|
+
display: block;
|
|
138
|
+
padding: 7px 14px;
|
|
139
|
+
border-radius: 999px;
|
|
140
|
+
color: var(--heading);
|
|
141
|
+
font-size: 15px;
|
|
142
|
+
font-weight: 500;
|
|
143
|
+
white-space: nowrap;
|
|
144
|
+
}
|
|
145
|
+
nav a:hover {
|
|
146
|
+
color: var(--accent);
|
|
147
|
+
}
|
|
148
|
+
nav a[aria-current='page'] {
|
|
149
|
+
color: var(--heading);
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
}
|
|
152
|
+
nav a.cta {
|
|
153
|
+
padding: 8px 18px;
|
|
154
|
+
background: linear-gradient(180deg, var(--accent-top) 0%, var(--accent-fill) 25%, var(--accent-fill) 100%);
|
|
155
|
+
color: #fff;
|
|
156
|
+
}
|
|
157
|
+
.sep {
|
|
158
|
+
width: 1px;
|
|
159
|
+
height: 20px;
|
|
160
|
+
margin: 0 8px;
|
|
161
|
+
background: var(--border);
|
|
162
|
+
}
|
|
163
|
+
.theme {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
justify-content: center;
|
|
167
|
+
width: 32px;
|
|
168
|
+
height: 32px;
|
|
169
|
+
/* Close to the GitHub link, as if it were the last nav entry: 20px gap - 14px. */
|
|
170
|
+
margin-left: -14px;
|
|
171
|
+
border: 0;
|
|
172
|
+
border-radius: 999px;
|
|
173
|
+
background: var(--bg);
|
|
174
|
+
color: var(--heading);
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
}
|
|
177
|
+
.theme:hover {
|
|
178
|
+
border-color: var(--border-strong);
|
|
179
|
+
color: var(--heading);
|
|
180
|
+
}
|
|
181
|
+
.theme[hidden] {
|
|
182
|
+
display: none;
|
|
183
|
+
}
|
|
184
|
+
.theme svg {
|
|
185
|
+
fill: none;
|
|
186
|
+
stroke: currentColor;
|
|
187
|
+
stroke-width: 1.75;
|
|
188
|
+
stroke-linecap: round;
|
|
189
|
+
stroke-linejoin: round;
|
|
190
|
+
}
|
|
191
|
+
:global([data-theme='light']) .icon-sun,
|
|
192
|
+
:global([data-theme='dark']) .icon-moon {
|
|
193
|
+
display: none;
|
|
194
|
+
}
|
|
195
|
+
/* Narrow screens: name and theme toggle on the first row, the links on a second row that
|
|
196
|
+
stays one line and scrolls sideways when it does not fit — no wrapped labels, nothing cut
|
|
197
|
+
off. A header with a website pill or a demo entry needs the second row earlier. The website
|
|
198
|
+
pill is dropped from that row: it repeats in the footer's "Project" column, and it was the
|
|
199
|
+
single widest entry, pushing GitHub out of view. */
|
|
200
|
+
@media (max-width: 640px) {
|
|
201
|
+
:global(:root) {
|
|
202
|
+
--header-h: 92px;
|
|
203
|
+
}
|
|
204
|
+
.header {
|
|
205
|
+
padding: 8px 8px 0;
|
|
206
|
+
}
|
|
207
|
+
.inner {
|
|
208
|
+
flex-wrap: wrap;
|
|
209
|
+
align-content: center;
|
|
210
|
+
gap: 2px 8px;
|
|
211
|
+
height: var(--header-h);
|
|
212
|
+
padding: 0 16px;
|
|
213
|
+
border-radius: 24px;
|
|
214
|
+
}
|
|
215
|
+
.links {
|
|
216
|
+
flex: 0 0 calc(100% + 32px);
|
|
217
|
+
order: 3;
|
|
218
|
+
gap: 8px;
|
|
219
|
+
margin: 0 -16px;
|
|
220
|
+
/* Room for the focus outline, which the scroll container would clip. */
|
|
221
|
+
padding: 3px 8px;
|
|
222
|
+
overflow-x: auto;
|
|
223
|
+
scrollbar-width: none;
|
|
224
|
+
}
|
|
225
|
+
.website {
|
|
226
|
+
display: none;
|
|
227
|
+
}
|
|
228
|
+
.theme {
|
|
229
|
+
margin-left: auto;
|
|
230
|
+
}
|
|
231
|
+
nav {
|
|
232
|
+
margin-left: 0;
|
|
233
|
+
}
|
|
234
|
+
.sep {
|
|
235
|
+
display: none;
|
|
236
|
+
}
|
|
237
|
+
nav a {
|
|
238
|
+
padding: 7px 8px;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
@media (max-width: 900px) {
|
|
242
|
+
:global(:root:has(.header[data-wide])) {
|
|
243
|
+
--header-h: 92px;
|
|
244
|
+
}
|
|
245
|
+
[data-wide] .inner {
|
|
246
|
+
flex-wrap: wrap;
|
|
247
|
+
align-content: center;
|
|
248
|
+
gap: 2px 8px;
|
|
249
|
+
height: var(--header-h);
|
|
250
|
+
padding: 0 16px;
|
|
251
|
+
}
|
|
252
|
+
[data-wide] .links {
|
|
253
|
+
flex: 0 0 calc(100% + 32px);
|
|
254
|
+
order: 3;
|
|
255
|
+
gap: 8px;
|
|
256
|
+
margin: 0 -16px;
|
|
257
|
+
/* Room for the focus outline, which the scroll container would clip. */
|
|
258
|
+
padding: 3px 8px;
|
|
259
|
+
overflow-x: auto;
|
|
260
|
+
scrollbar-width: none;
|
|
261
|
+
}
|
|
262
|
+
[data-wide] .website {
|
|
263
|
+
display: none;
|
|
264
|
+
}
|
|
265
|
+
[data-wide] .theme {
|
|
266
|
+
margin-left: auto;
|
|
267
|
+
}
|
|
268
|
+
[data-wide] nav {
|
|
269
|
+
margin-left: 0;
|
|
270
|
+
}
|
|
271
|
+
[data-wide] .sep {
|
|
272
|
+
display: none;
|
|
273
|
+
}
|
|
274
|
+
[data-wide] nav a {
|
|
275
|
+
padding: 7px 8px;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
</style>
|