vowel 0.1.13 → 0.1.15

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/README.md CHANGED
@@ -1,136 +1,31 @@
1
- ## MVP
1
+ # Vowel
2
2
 
3
- - [x] Add all element types supported in Obsidian to markdown
4
- - [x] Add rich link previews
5
- - [x] Add RSS
6
- - [x] Add robots.txt
7
- - [x] AI
8
- - [x] Search
9
- - [x] Image
10
- - [x] Typing
11
- - [x] Add sitemap
12
- - [x] Make taxonomies linkable in frontmatter (if a corresponding folder exists)
13
- - [x] Add header metadata (meta tags)
14
- - [x] CSS handling
15
- - [x] Favicon handling
16
- - [x] Docs
17
- - [x] Build action
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
18
5
 
19
- ## V2
6
+ The easiest way to code your own website.
20
7
 
21
- - [x] Fallback homepages
22
- - [ ] Create post list element (`#tags/blue`, `@tags/blue`, `~tags/blue`)
23
- - [x] Better favicon and styles handling
24
- - [ ] Ignore README files
25
- - [ ] Add more header metadata
26
- - [ ] Define folder settings
27
- - [ ] Title
28
- - [ ] Breadrumb
29
- - [x] Define special page properties
30
- - [x] Title
31
- - [x] Description
32
- - [x] Meta image
33
- - [x] Date
34
- - [ ] Modified date
35
- - [ ] Tags
36
- - [ ] Author/Authors
37
- - [ ] Make file names kebab-cased for URLs and links
38
- - [ ] Add hidden routes (`$`)
39
- - [ ] Image alt text
40
- - [ ] Code highlighter
41
- - [x] Deploy action (Vercel)
42
- - [ ] Add anchor links for headings
43
- - [ ] Post TOC
44
- - [ ] GitHub-style notes
45
- - [ ] Add in-browser search
46
- - [x] Figure out how to stop components from remounting on every change
47
- - [ ] Make RSS optional
48
- - [ ] Pagination
49
- - [ ] Demote headings global option
8
+ [Try it on StackBlitz](https://stackblitz.com/~/github.com/samlfair/vowel-site?file=home.md).
50
9
 
51
- ## Wish list
52
-
53
- - [ ] Automatic file creation
54
- - [ ] Image serving and optimization
55
- - [ ] GUI
56
- - [ ] More deploy actions
57
- - [ ] Optional JSON interactivity (ratings, comments, SubPubHub)
58
- - [ ] Redirects
59
- - [ ] Advanced markdown
60
- - [ ] Highlight
61
- - [ ] Sanitize
62
- - [ ] Tables
63
- - [ ] Strike
64
- - [ ] Task lists
65
- - [ ] Footnotes
66
- - [ ] Mermaid
67
- - [ ] Math
68
- - [ ] Wikilinks
69
-
70
- ## Ideas
71
-
72
- https://github.com/arobase-che/remark-attr
73
- https://github.com/wataru-chocola/remark-definition-list
74
- https://github.com/FinnRG/remark-mentions
75
- https://github.com/remarkjs/remark-toc
76
-
77
- - Docusaurus
78
- - MkDocs
79
- - Coolify
80
- - NodeGui
81
-
82
- https://www.nngroup.com/articles/breadcrumb-navigation-useful/
83
- https://www.nngroup.com/articles/url-as-ui/
84
-
85
- CSS Frameworks:
86
-
87
- - Open Props
88
- - Pico
89
- - Milligram
90
- - Spectre
91
-
92
- ## Docs
93
-
94
- Welcome to Vowel! This project is under development.
95
-
96
- Vowel is a Really Simple Static Site Generator. Write Markdown files, and Vowel will generate a website for you. Eventually, you will also be able to write a `style.css` file to style your website. When complete, Vowel will generate:
97
-
98
- - An RSS feed
99
- - A sitemap
100
- - Rich link previews (internal and external)
101
- - Post lists
102
- - Navigation
103
- - Optimized images
104
- - Metadata
105
- - And more
106
-
107
- Each file in Vowel is a page. Index pages are called `home.md`.
10
+ Turn a folder of Markdown files into a website by running one command:
108
11
 
109
12
  ```
110
- .
111
- ├── home.md
112
- ├── settings.md
113
- ├── about.md
114
- └── posts/
115
- ├── home.md
116
- ├── red-car.md
117
- ├── blue-true.md
118
- └── green-van.md
13
+ npx -y -p svelte@next -p vowel@latest npx vowel
119
14
  ```
120
15
 
121
- In the above project, `/home.md` is the website homepage. `about.md` is `/about`, `/posts/home.md` is the blog homepage, `/blog`, and `/posts/red-car.md` is a blog post, `/posts/red-car`.
16
+ No installs. No config.
122
17
 
123
- Every folder must have an index page. That means that there are two ways to represent a page:
18
+ Create a blog, a documentation site, a landing page, a wiki (whatever you want), using just Markdown and CSS.
124
19
 
125
- - `/example-page.md`
126
- - `/example-page/home.md`
127
- Both of the above will generate the `/example-page` URL. You cannot have both files.
20
+ No HTML.
128
21
 
129
- At the root of a project, a `settings.md` file defines global configurations, like your site title. It can have the following properties:
22
+ No JavaScript.
130
23
 
131
- - `title`: Website title
132
- - `breadcrumb`: The root breadcrumb
24
+ [Read the docs](https://vowel.cc/docs).
133
25
 
134
- Each folder can also have a `settings.md` file, with the following properties:
26
+ For comments, questions, bugs, and contributions, please reach out to me on Twitter: [@samlfair](https://twitter.com/samlfair).
135
27
 
136
- - `breadcrumb`: The breadcrumb for the folder
28
+ [npm-version-src]: https://img.shields.io/npm/v/vowel/latest.svg
29
+ [npm-version-href]: https://npmjs.com/package/vowel
30
+ [npm-downloads-src]: https://img.shields.io/npm/dm/vowel.svg
31
+ [npm-downloads-href]: https://npmjs.com/package/vowel
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vowel",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "bin": "./bin.js",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
package/server.js CHANGED
@@ -46,21 +46,13 @@ async function buildProject() {
46
46
  let projectData = null;
47
47
 
48
48
  try {
49
- console.log({ config });
50
49
  if (existsSync(projectJsonPath)) {
51
- console.log('inside');
52
50
  const data = await readFile(projectJsonPath, 'utf-8');
53
- console.log({ data });
54
51
  projectData = JSON.parse(data);
55
52
  }
56
53
 
57
- console.log({ projectData });
58
- console.log('Building...');
59
-
60
54
  await build(config);
61
55
 
62
- console.log('Build completed.');
63
-
64
56
  if (projectData !== null) {
65
57
  await mkdir(vercelDirPath, { recursive: true });
66
58
  await writeFile(projectJsonPath, JSON.stringify(projectData, null, 2), 'utf-8');
package/src/app.html CHANGED
@@ -3,6 +3,26 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <style>
7
+ html {
8
+ -webkit-text-size-adjust: 100%;
9
+ margin: 0;
10
+ font-size: 62.5%;
11
+ }
12
+
13
+ body {
14
+ font-size: 1.6rem;
15
+ line-height: 1.5;
16
+ -webkit-font-smoothing: antialiased;
17
+ font-weight: 400;
18
+ margin: 0;
19
+ }
20
+
21
+ .container {
22
+ height: 100vh;
23
+ overflow-y: scroll;
24
+ }
25
+ </style>
6
26
  %sveltekit.head%
7
27
  </head>
8
28
 
@@ -4,7 +4,7 @@
4
4
  Barebones, Basic.css, Pico, Typeplate, Simple.css, and more
5
5
  */
6
6
 
7
- :global {
7
+ .theme-default {
8
8
  /* Variables */
9
9
 
10
10
  :root {
@@ -48,19 +48,6 @@
48
48
  margin: 0;
49
49
  }
50
50
 
51
- html {
52
- line-height: 1.15;
53
- -webkit-text-size-adjust: 100%;
54
- font-size: 62.5%;
55
- }
56
-
57
- body {
58
- font-size: 1.6rem;
59
- line-height: 1.5;
60
- -webkit-font-smoothing: antialiased;
61
- font-weight: 400;
62
- }
63
-
64
51
  main,
65
52
  img,
66
53
  details,
@@ -138,13 +125,8 @@
138
125
 
139
126
  /* Layout */
140
127
 
141
- .container {
142
- height: 100vh;
143
- overflow-y: scroll;
144
- font-family: var(--font-sans);
145
- }
146
-
147
128
  .page {
129
+ font-family: var(--font-sans);
148
130
  max-width: 60ch;
149
131
  margin: auto;
150
132
  min-height: 100vh;
@@ -452,7 +434,7 @@
452
434
  /* Sidebar */
453
435
 
454
436
  .sidebar {
455
- margin: auto;
437
+ margin-inline: auto;
456
438
  }
457
439
 
458
440
  /* Footer */
@@ -13,10 +13,6 @@
13
13
 
14
14
  let { ast, level, website, format, identifier } = $derived(props);
15
15
 
16
- if (identifier) {
17
- console.log(identifier);
18
- }
19
-
20
16
  function getElement(node) {
21
17
  if (node.type === 'heading') {
22
18
  return ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'div'][node.depth - 1];
@@ -44,7 +40,6 @@
44
40
  // Handle `footnoteDefinition` and `footnoteReference`
45
41
 
46
42
  if (elements[node.type]) return elements[node.type];
47
- console.log(node);
48
43
  }
49
44
 
50
45
  const noteTypes = {
@@ -6,4 +6,4 @@ export { default as Page } from './Page.svelte';
6
6
  export { default as Sitemap } from './Sitemap.svelte';
7
7
  export { default as FrontmatterProperty } from './FrontmatterProperty.svelte';
8
8
  export { default as FrontmatterTaxonomy } from './FrontMatterTaxonomy.svelte';
9
- export { default as DefaultStyles } from './DefaultStyles.svelte';
9
+ // export { default as DefaultStyles } from './DefaultStyles.svelte';
@@ -1,5 +1,6 @@
1
- export default function createPageClass(url, type = 'page') {
1
+ export default function createPageClass(url, type = 'page', theme) {
2
2
  if (!url) return 'item';
3
- if (url === '/') return 'page home';
3
+ if (url === '/') return `page home${theme ? ` theme-${theme}` : ''}`;
4
+ if (type === 'page') return `page ${url.split('/').join(' ')}${theme ? ` theme-${theme}` : ''}`;
4
5
  return type + url.split('/').join(' ');
5
6
  }
@@ -3,6 +3,10 @@ import { access } from 'fs/promises';
3
3
  import { constants } from 'fs';
4
4
  import { normalize, join, basename } from 'path';
5
5
  import { dev } from '$app/environment';
6
+ import mri from 'mri';
7
+
8
+ const args = mri(process.argv);
9
+ const isBuild = args._.includes('build');
6
10
 
7
11
  export const prerender = true;
8
12
  export const csr = dev;
@@ -24,7 +28,7 @@ async function checkFileExists(filePath, homeDir) {
24
28
  export async function load() {
25
29
  // const startLoad = performance.now();
26
30
  const now = Date.now();
27
- const contentCacheDuration = 60000; // Cache for 1 minute
31
+ const contentCacheDuration = isBuild ? 60000 : 0; // Cache for 1 second
28
32
  const initialURLCache = await loadCache($home[0]);
29
33
 
30
34
  const cssExists = await checkFileExists(normalize('/assets/styles.css'), $home[0]);
@@ -1,12 +1,12 @@
1
1
  <script>
2
- import { Nav, Page, Breadcrumbs, Sitemap, DefaultStyles } from '$lib/components/index.js';
2
+ import { Nav, Page, Breadcrumbs, Sitemap } from '$lib/components/index.js';
3
3
  import { getPage, createPageClass } from '$lib/utilities/index.js';
4
4
  import LinkPreview from '$lib/components/Markdown/LinkPreview.svelte';
5
5
  import getFileLabel from '../../lib/utilities/getFileLabel';
6
6
  import { error } from '@sveltejs/kit';
7
7
  import { getFolder, getFolderLabel } from '../../lib/utilities';
8
8
  import { page as pageStore } from '$app/stores';
9
- import ConditionalWrapper from '../../lib/components/ConditionalWrapper.svelte';
9
+ import { goto, invalidate, invalidateAll } from '$app/navigation';
10
10
 
11
11
  let { data } = $props();
12
12
 
@@ -56,23 +56,34 @@
56
56
 
57
57
  const favicon = getFavicon();
58
58
 
59
+ let key = $state(Date.now());
60
+
59
61
  $effect(() => {
60
62
  // Update website on file change
61
63
  if (import.meta.hot) {
62
64
  import.meta.hot.on('vowel:update', ({ path, file }) => {
63
65
  let updatedPage = getPage(website, path);
66
+ if (!updatedPage) {
67
+ invalidateAll().then(() => {
68
+ window.location.reload();
69
+ });
70
+ }
64
71
 
65
72
  for (const key in file) {
73
+ if (!updatedPage[key]) updatedPage[key] = {};
66
74
  updatedPage[key] = file[key];
67
75
  }
68
76
  });
77
+ import.meta.hot.on('vowel:refresh', () => {
78
+ console.log('Refresh');
79
+ invalidateAll().then(() => {
80
+ window.location.reload();
81
+ });
82
+ });
69
83
  }
70
84
  });
71
85
  </script>
72
86
 
73
- <!-- svelte-ignore state_referenced_locally -->
74
- <!-- svelte-ignore state_referenced_locally -->
75
-
76
87
  <svelte:head>
77
88
  <title>{makePageMetaTitle()}</title>
78
89
  <meta property="”og:url”" content={page.url} />
@@ -91,9 +102,10 @@
91
102
  {/if}
92
103
  </svelte:head>
93
104
 
94
- <DefaultStyles />
95
-
96
- <div data-sveltekit-preload-data="hover" class={createPageClass(page.url)}>
105
+ <div
106
+ data-sveltekit-preload-data="hover"
107
+ class={createPageClass(page.url, null, website._.theme === 'default' ? 'default' : false)}
108
+ >
97
109
  <header>
98
110
  {#if website._.logo}
99
111
  <a href="/" class="site-logo">
@@ -106,7 +118,7 @@
106
118
  {#if website._.slogan}
107
119
  <p class="slogan">{website._.slogan}</p>
108
120
  {/if}
109
- <Nav folder={data.website} segments={data.path.split('/')} />
121
+ <Nav folder={website} segments={data.path.split('/')} />
110
122
  <nav class="breadcrumbs" aria-label="Breadcrumb">
111
123
  <Breadcrumbs level={0} />
112
124
  </nav>
@@ -115,10 +127,558 @@
115
127
  <Page level={0} {page} {website} path={data.path} />
116
128
  </main>
117
129
  <nav class="sidebar">
118
- <Sitemap section={data.website} segments={data.path.split('/')} root />
130
+ <Sitemap section={website} segments={data.path.split('/')} root />
119
131
  </nav>
120
132
  <footer>
121
133
  © {website._.author ? website._.author + ' ' : ''}
122
134
  {new Date().getFullYear()}
123
135
  </footer>
124
136
  </div>
137
+
138
+ <style>
139
+ /*
140
+ Inspiration for this stylesheet comes from Normalize.css, Josh Comeau,
141
+ Barebones, Basic.css, Pico, Typeplate, Simple.css, and more
142
+ */
143
+
144
+ :global {
145
+ body:has(> div > .theme-default) {
146
+ --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue,
147
+ helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
148
+ --font-serif: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif,
149
+ Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
150
+ --font-mono: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
151
+ --text-color: hsl(0, 0%, 13%);
152
+ --accent-color: hsl(215, 100%, 35%);
153
+ --accent-color-hover: hsl(215, 76%, 49%);
154
+ --border-color: hsl(0, 0%, 73%);
155
+ --main-background: hsl(0, 0%, 100%);
156
+ --code-background: hsl(0, 0%, 95%);
157
+ --soft-background: hsl(0, 0%, 95%);
158
+
159
+ @media (prefers-color-scheme: dark) {
160
+ --text-color: hsl(0, 0%, 80%);
161
+ --accent-color: hsl(194, 76%, 49%);
162
+ --accent-color-hover: hsl(194, 86%, 57%);
163
+ --border-color: hsl(0, 0%, 27%);
164
+ --main-background: hsl(0, 0%, 12%);
165
+ --code-background: hsl(0, 0%, 5%);
166
+ --soft-background: hsl(0, 0%, 14%);
167
+ }
168
+
169
+ color: var(--text-color);
170
+ background: var(--main-background);
171
+ }
172
+
173
+ .theme-default {
174
+ &.page {
175
+ font-family: var(--font-sans);
176
+ max-width: 60ch;
177
+ margin: auto;
178
+ min-height: 100vh;
179
+ display: flex;
180
+ flex-direction: column;
181
+ padding: 1rem;
182
+ overflow-x: hidden;
183
+ }
184
+
185
+ /*
186
+ Reset
187
+ */
188
+
189
+ *,
190
+ *::before,
191
+ *::after {
192
+ box-sizing: border-box;
193
+ }
194
+
195
+ * {
196
+ margin: 0;
197
+ }
198
+
199
+ main,
200
+ img,
201
+ details,
202
+ picture,
203
+ video,
204
+ canvas,
205
+ svg {
206
+ display: block;
207
+ max-width: 100%;
208
+ }
209
+
210
+ summary {
211
+ display: list-item;
212
+ }
213
+
214
+ hr {
215
+ box-sizing: content-box;
216
+ height: 0;
217
+ overflow: visible;
218
+ }
219
+
220
+ pre {
221
+ font-family: monospace, monospace;
222
+ font-size: 1em;
223
+ }
224
+
225
+ a {
226
+ background-color: transparent;
227
+ }
228
+
229
+ abbr[title] {
230
+ border-bottom: none;
231
+ text-decoration: underline;
232
+ text-decoration: underline dotted;
233
+ }
234
+
235
+ b,
236
+ strong {
237
+ font-weight: bolder;
238
+ }
239
+
240
+ code,
241
+ kbd,
242
+ samp {
243
+ font-family: monospace, monospace;
244
+ font-size: 1em;
245
+ }
246
+
247
+ small {
248
+ font-size: 80%;
249
+ }
250
+
251
+ sub,
252
+ sup {
253
+ font-size: 75%;
254
+ line-height: 0;
255
+ position: relative;
256
+ vertical-align: baseline;
257
+ }
258
+
259
+ sub {
260
+ bottom: -0.25em;
261
+ }
262
+
263
+ sup {
264
+ top: -0.5em;
265
+ }
266
+
267
+ img {
268
+ border-style: none;
269
+ height: auto;
270
+ border-radius: 5px;
271
+ overflow: hidden;
272
+ }
273
+
274
+ /* Layout */
275
+
276
+ main {
277
+ margin-bottom: auto;
278
+ padding-bottom: 4rem;
279
+ }
280
+
281
+ /* Colors */
282
+
283
+ a {
284
+ color: var(--accent-color);
285
+ }
286
+
287
+ a:hover {
288
+ color: var(--accent-color-hover);
289
+ }
290
+
291
+ /* Typography */
292
+
293
+ p,
294
+ h1,
295
+ h2,
296
+ h3,
297
+ h4,
298
+ h5,
299
+ h6 {
300
+ overflow-wrap: break-word;
301
+ }
302
+
303
+ h1,
304
+ h2,
305
+ h3,
306
+ h4,
307
+ h5,
308
+ h6 {
309
+ margin-top: 0.5rem;
310
+ margin-bottom: 1rem;
311
+ text-rendering: optimizeLegibility;
312
+ font-weight: 600;
313
+ line-height: 1.1;
314
+ }
315
+ h1 {
316
+ font-size: 3rem;
317
+ line-height: 1.2;
318
+ font-weight: 800;
319
+ }
320
+
321
+ h2 {
322
+ font-size: 2.6rem;
323
+ margin-top: 3rem;
324
+ }
325
+
326
+ h3 {
327
+ font-size: 2rem;
328
+ margin-top: 3rem;
329
+ }
330
+
331
+ h4 {
332
+ font-size: 1.44rem;
333
+ }
334
+
335
+ h5 {
336
+ font-size: 1.15rem;
337
+ }
338
+
339
+ h6 {
340
+ font-size: 0.96rem;
341
+ }
342
+
343
+ article h1 {
344
+ font-weight: 600;
345
+ font-size: 2rem;
346
+ }
347
+
348
+ @media only screen and (max-width: 720px) {
349
+ h1 {
350
+ font-size: 2.5rem;
351
+ }
352
+
353
+ h2 {
354
+ font-size: 2.1rem;
355
+ }
356
+
357
+ h3 {
358
+ font-size: 1.75rem;
359
+ }
360
+
361
+ h4 {
362
+ font-size: 1.25rem;
363
+ }
364
+ }
365
+
366
+ p {
367
+ margin-top: 0;
368
+ }
369
+
370
+ ol,
371
+ ul {
372
+ /* padding-left: 0; */
373
+ margin-top: 0;
374
+ }
375
+
376
+ ul ul,
377
+ ul ol,
378
+ ol ol,
379
+ ol ul {
380
+ font-size: 100%;
381
+ /* margin: 1rem 0 1rem 3rem; */
382
+ }
383
+
384
+ li {
385
+ margin-bottom: 0.5rem;
386
+ }
387
+
388
+ /* Extra selector for specificity, see below */
389
+ li > p:not(:last-child) {
390
+ margin: 0;
391
+ padding: 0;
392
+ }
393
+
394
+ code {
395
+ background: var(--code-background);
396
+ font-size: 0.9em;
397
+ white-space: nowrap;
398
+ margin-inline: 0px;
399
+ padding: 3px 4px;
400
+ border-radius: 5px;
401
+ }
402
+
403
+ code,
404
+ mark {
405
+ -webkit-box-decoration-break: clone;
406
+ box-decoration-break: clone;
407
+ }
408
+
409
+ pre {
410
+ background: var(--code-background);
411
+ padding: 1rem 1.5rem;
412
+ border-radius: 5px;
413
+ overflow-x: auto;
414
+ }
415
+
416
+ dt {
417
+ float: left;
418
+ margin: 0 20px 0 0;
419
+ width: 120px;
420
+ color: #daf2fd;
421
+ font-weight: 500;
422
+ text-align: right;
423
+ }
424
+
425
+ dd {
426
+ margin: 0 0 5px 0;
427
+ text-align: left;
428
+ }
429
+
430
+ blockquote {
431
+ margin: 1.5em 0;
432
+ padding: 0 1.6em 0 1.6em;
433
+ border-left: 5px solid var(--border-color);
434
+ }
435
+
436
+ a {
437
+ text-decoration: none;
438
+ }
439
+
440
+ figcaption {
441
+ margin-top: 0.5rem;
442
+ font-style: italic;
443
+ }
444
+
445
+ article {
446
+ border: 1px solid var(--border-color);
447
+ background: var(--soft-background);
448
+ padding: 1rem 2rem 1.5rem;
449
+ border-radius: 0.5rem;
450
+ }
451
+
452
+ article + article {
453
+ margin-top: 1rem;
454
+ }
455
+
456
+ th,
457
+ td {
458
+ padding: 12px 15px;
459
+ text-align: left;
460
+ border-bottom: 1px solid var(--border-color-softer);
461
+ }
462
+ th:first-child,
463
+ td:first-child {
464
+ padding-left: 0;
465
+ }
466
+ th:last-child,
467
+ td:last-child {
468
+ padding-right: 0;
469
+ }
470
+
471
+ :where(pre, blockquote, dl, figure, table, p, ul, ol, form, article, section, aside):not(
472
+ :last-child
473
+ ) {
474
+ margin-bottom: 2.5rem;
475
+ }
476
+
477
+ hr {
478
+ margin-top: 3rem;
479
+ margin-bottom: 3.5rem;
480
+ border-width: 0px;
481
+ border-top: 1px solid var(--text-color);
482
+ }
483
+
484
+ /* Header */
485
+
486
+ header {
487
+ margin-top: 4rem;
488
+ }
489
+
490
+ nav.top-bar {
491
+ border: 1px solid var(--border-color);
492
+ padding: 6px 10px;
493
+ margin-inline: -3px;
494
+ border-radius: 5px;
495
+ display: flex;
496
+ column-gap: 2rem;
497
+ row-gap: 0.5rem;
498
+ flex-wrap: wrap;
499
+ justify-content: flex-start;
500
+ background: var(--soft-background);
501
+ }
502
+
503
+ nav.top-bar:has(+ nav.top-bar) {
504
+ border-bottom: none;
505
+ border-bottom-left-radius: 0;
506
+ border-bottom-right-radius: 0;
507
+ }
508
+
509
+ nav.top-bar:has(+ nav.top-bar):after {
510
+ display: block;
511
+ content: '';
512
+ border-bottom: 1px solid var(--border-color);
513
+ flex-basis: 100%;
514
+ margin-top: 0.7rem;
515
+ }
516
+
517
+ nav.top-bar + nav.top-bar {
518
+ border-top-left-radius: 0;
519
+ border-top-right-radius: 0;
520
+ border-top: none;
521
+ margin-top: 0;
522
+ }
523
+
524
+ nav > a {
525
+ text-wrap: nowrap;
526
+ }
527
+
528
+ header a.site-title {
529
+ color: unset;
530
+ text-decoration: none;
531
+ font-weight: 900;
532
+ font-size: 2.8em;
533
+ }
534
+
535
+ header .slogan {
536
+ /* font-family: var(--font-serif); */
537
+ font-weight: 500;
538
+ }
539
+
540
+ nav :where([aria-current='page'], [aria-current='true']),
541
+ nav :where([aria-current='page'], [aria-current='true']):hover {
542
+ text-decoration: none;
543
+ color: unset;
544
+ cursor: default;
545
+ }
546
+
547
+ header .breadcrumbs > a:only-child {
548
+ display: none;
549
+ }
550
+
551
+ header .breadcrumbs {
552
+ margin-block: 4rem 1rem;
553
+ }
554
+
555
+ header .breadcrumbs a {
556
+ font-size: 0.9em;
557
+ text-decoration: none;
558
+ color: unset;
559
+ }
560
+
561
+ header .breadcrumbs .separator:after {
562
+ margin-inline: 0.5rem;
563
+ content: '|';
564
+ }
565
+
566
+ /* Sidebar */
567
+
568
+ .sidebar {
569
+ margin-inline: auto;
570
+ }
571
+
572
+ /* Footer */
573
+
574
+ footer {
575
+ text-align: center;
576
+ margin-block: 6rem 8rem;
577
+ }
578
+
579
+ /* Pages */
580
+
581
+ .page.home > main > h1 {
582
+ display: none;
583
+ }
584
+
585
+ .page.home .content p:first-child {
586
+ font-size: 1.2em;
587
+ }
588
+
589
+ section[class*='$'] {
590
+ display: grid;
591
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
592
+ grid-auto-rows: auto;
593
+ grid-gap: 2rem;
594
+
595
+ & article {
596
+ margin: 0;
597
+ }
598
+ }
599
+
600
+ article.item {
601
+ display: grid;
602
+ flex-direction: column;
603
+ grid-template-areas:
604
+ 'title icon'
605
+ 'description description';
606
+ align-content: start;
607
+ }
608
+
609
+ article.item h1 {
610
+ grid-area: title;
611
+ font-size: 1.6rem;
612
+ }
613
+
614
+ article.item dl.icon {
615
+ grid-area: icon;
616
+ font-size: 1.5em;
617
+ }
618
+
619
+ article.item dl.icon dd {
620
+ text-align: right;
621
+ }
622
+
623
+ article.item .description {
624
+ grid-area: description;
625
+ }
626
+
627
+ article.item dt {
628
+ display: none;
629
+ }
630
+
631
+ article.item dl.icon {
632
+ order: -1;
633
+ }
634
+
635
+ aside.note {
636
+ &.note {
637
+ --icon: 'ℹ️';
638
+ --rgb: 71, 139, 230;
639
+ }
640
+ &.tip {
641
+ --icon: '💡';
642
+ --rgb: 87, 171, 90;
643
+ }
644
+ &.important {
645
+ --icon: '💬';
646
+ --rgb: 152, 110, 226;
647
+ }
648
+ &.warning {
649
+ --icon: '⚠️';
650
+ --rgb: 198, 144, 38;
651
+ }
652
+ &.caution {
653
+ --icon: '⛔️';
654
+ --rgb: 229, 83, 75;
655
+ }
656
+
657
+ border-left: 3px solid rgb(var(--rgb));
658
+ padding: 1.5rem 2rem;
659
+ background: rgba(var(--rgb), 0.04);
660
+
661
+ h2 {
662
+ margin: 0 0 0.5em 0;
663
+ font-size: 1.1em;
664
+ color: rgb(var(--rgb));
665
+ }
666
+
667
+ h2:before {
668
+ font-size: 0.8em;
669
+ margin-right: 0.7rem;
670
+ opacity: 0.9;
671
+ }
672
+
673
+ h2:before {
674
+ content: var(--icon);
675
+ }
676
+ }
677
+
678
+ li.checked,
679
+ li.unchecked {
680
+ padding-left: 0.8rem;
681
+ }
682
+ }
683
+ }
684
+ </style>
package/vite.config.js CHANGED
@@ -23,6 +23,12 @@ export default defineConfig({
23
23
  name: 'markdown:watch',
24
24
  configureServer(server) {
25
25
  server.watcher.add(homeDir);
26
+ server.watcher.on('add', () => {
27
+ server.ws.send('vowel:refresh');
28
+ });
29
+ server.watcher.on('unlink', () => {
30
+ server.ws.send('vowel:refresh');
31
+ });
26
32
  server.watcher.on('change', async (homePath, stats) => {
27
33
  if (homePath.endsWith('.md')) {
28
34
  // Remove `.md`