@geekity/cms 0.10.0 → 0.12.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/README.md +22 -5
- package/admin/components/fields.njk +55 -12
- package/admin/layouts/base.njk +5 -1
- package/admin/layouts/settings-page.njk +7 -2
- package/admin/layouts/shell.njk +1 -1
- package/admin/pages/account/forgot.njk +1 -1
- package/admin/pages/account/login.njk +1 -1
- package/admin/pages/account/reset.njk +4 -2
- package/admin/pages/account/setup.njk +1 -1
- package/admin/pages/dashboard/home.njk +1 -0
- package/admin/pages/documents/list.njk +1 -0
- package/admin/pages/documents/taxonomy.njk +1 -0
- package/admin/pages/federation/followers.njk +3 -0
- package/admin/pages/federation/settings.njk +5 -0
- package/admin/pages/media/library.njk +39 -0
- package/admin/pages/navigation/menus.njk +6 -1
- package/admin/pages/settings/discussion.njk +7 -0
- package/admin/pages/settings/email.njk +10 -0
- package/admin/pages/settings/general.njk +7 -0
- package/admin/pages/settings/permalinks.njk +6 -0
- package/admin/pages/settings/reading.njk +7 -0
- package/admin/pages/tools/content-index.njk +1 -0
- package/admin/pages/tools/personal-data.njk +5 -0
- package/admin/pages/users/edit.njk +7 -5
- package/admin/pages/users/list.njk +1 -0
- package/admin/pages/users/new.njk +5 -1
- package/admin/static/admin-bar.css +16 -0
- package/admin/static/admin.css +103 -3
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +18 -0
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/media.d.ts +13 -2
- package/dist/admin/media.d.ts.map +1 -1
- package/dist/admin/media.js +58 -3
- package/dist/admin/media.js.map +1 -1
- package/dist/admin/store.d.ts +1 -1
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +1 -1
- package/dist/admin/store.js.map +1 -1
- package/dist/admin/uploads.d.ts +5 -2
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +6 -2
- package/dist/admin/uploads.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +37 -1
- package/dist/federation/article.js.map +1 -1
- package/dist/images/alt-text.d.ts +51 -0
- package/dist/images/alt-text.d.ts.map +1 -0
- package/dist/images/alt-text.js +113 -0
- package/dist/images/alt-text.js.map +1 -0
- package/dist/images/markup.d.ts +11 -0
- package/dist/images/markup.d.ts.map +1 -1
- package/dist/images/markup.js +12 -0
- package/dist/images/markup.js.map +1 -1
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +27 -12
- package/dist/web/render.js.map +1 -1
- package/package.json +14 -9
- package/themes/default/README.md +230 -106
- package/themes/default/layouts/404.njk +2 -0
- package/themes/default/layouts/500.njk +2 -0
- package/themes/default/layouts/503.njk +2 -0
- package/themes/default/layouts/author.njk +3 -1
- package/themes/default/layouts/base.njk +1 -1
- package/themes/default/layouts/category.njk +4 -2
- package/themes/default/layouts/front-page.njk +1 -1
- package/themes/default/layouts/page.njk +4 -2
- package/themes/default/layouts/post.njk +41 -23
- package/themes/default/layouts/search.njk +2 -5
- package/themes/default/layouts/tag.njk +7 -1
- package/themes/default/partials/comment-form.njk +41 -14
- package/themes/default/partials/contact-form.njk +35 -14
- package/themes/default/partials/conversation.njk +11 -4
- package/themes/default/partials/kicker.njk +34 -0
- package/themes/default/partials/post-list.njk +40 -46
- package/themes/default/partials/reply-context.njk +12 -5
- package/themes/default/src/style.css +1095 -0
- package/themes/default/static/style.css +2079 -1083
- package/themes/default/theme.json +1 -1
package/themes/default/README.md
CHANGED
|
@@ -23,6 +23,8 @@ themes/default/
|
|
|
23
23
|
post-list.njk the h-feed a listing is made of
|
|
24
24
|
pagination.njk previous/next pager
|
|
25
25
|
tags.njk macros for tag and category links
|
|
26
|
+
kicker.njk the line above an entry naming its kind and date
|
|
27
|
+
reply-context.njk the citation of what a reply answers
|
|
26
28
|
bio.njk who an entry is by, as an h-card
|
|
27
29
|
menu.njk one named menu, as a nav of links
|
|
28
30
|
feeds.njk macros for the feed links in <head>
|
|
@@ -39,12 +41,39 @@ themes/default/
|
|
|
39
41
|
comment-digest.*.njk the hourly or daily digest of what is waiting
|
|
40
42
|
comment-reply.*.njk the notice a commenter gets about a reply
|
|
41
43
|
contact-message.*.njk a message from a page's contact form
|
|
44
|
+
src/
|
|
45
|
+
style.css the stylesheet's source, compiled with Tailwind
|
|
42
46
|
static/
|
|
43
|
-
style.css served at /theme/style.css
|
|
47
|
+
style.css the compiled stylesheet, served at /theme/style.css
|
|
48
|
+
highlight.js the code highlighter
|
|
44
49
|
```
|
|
45
50
|
|
|
46
|
-
Plain Nunjucks and
|
|
47
|
-
|
|
51
|
+
Plain Nunjucks templates and one stylesheet. The stylesheet is written in
|
|
52
|
+
Tailwind v4 (`src/style.css`) and compiled when the package is built, so
|
|
53
|
+
`static/style.css` is a build product: gitignored in this repository, written
|
|
54
|
+
by `pnpm build` (which the root `prepare` runs after an install and before a
|
|
55
|
+
publish), and shipped in the package like every other file here. The compile
|
|
56
|
+
happens once, on the machine that builds the package. A reader gets one plain
|
|
57
|
+
CSS file and no script, web font or CDN request because of it.
|
|
58
|
+
|
|
59
|
+
The design is Paper (doc-9, decision-22): one warm column, serif throughout,
|
|
60
|
+
sans for small labels, and a kicker above every entry naming its kind.
|
|
61
|
+
|
|
62
|
+
**Rebuilding the stylesheet.** After editing `src/style.css`:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
pnpm --filter @geekity/cms build:theme
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Tailwind scans only this theme's `layouts/` and `partials/`, so a utility class
|
|
69
|
+
written in a template here is compiled in and nothing outside the theme is
|
|
70
|
+
read. The templates themselves use the semantic class names below and the look
|
|
71
|
+
lives in `src/style.css`, through `@apply`.
|
|
72
|
+
|
|
73
|
+
**A site theme owes Tailwind nothing.** Its own `static/style.css` replaces this
|
|
74
|
+
one wholesale, and any CSS that knows the class names in this README will do.
|
|
75
|
+
The demo theme's is plain CSS. A site that only wants different colours
|
|
76
|
+
overrides the `--color-*` custom properties instead; see [Colours](#colours).
|
|
48
77
|
|
|
49
78
|
## The manifest
|
|
50
79
|
|
|
@@ -145,9 +174,10 @@ words above the posts; `layouts/home.njk` already does.
|
|
|
145
174
|
|
|
146
175
|
## The page shell
|
|
147
176
|
|
|
148
|
-
`layouts/base.njk` is the
|
|
149
|
-
|
|
150
|
-
|
|
177
|
+
`layouts/base.njk` is the page shell: the skip link, one `.global-wrapper` at the
|
|
178
|
+
42rem measure, `.global-header`, `<main id="main">` and the footer, in that
|
|
179
|
+
order. The shape is the andrewshell.org design's (decision-16) and the look is
|
|
180
|
+
Paper's (doc-9).
|
|
151
181
|
|
|
152
182
|
**One home for each kind of link.** The header carries the site menu, on every
|
|
153
183
|
page. [The bio](#the-bio) carries the person whose page or post it is. The
|
|
@@ -156,7 +186,7 @@ nowhere else.
|
|
|
156
186
|
|
|
157
187
|
**The header has one rule, and the menu under it.** On the front page it is the
|
|
158
188
|
site title as `h1.main-heading`, linked home, with `site.tagline` in a
|
|
159
|
-
|
|
189
|
+
`p.site-tagline` under it; on every other page it is `a.header-link-home`, the site
|
|
160
190
|
title small and linked home, and no tagline. The wrapper carries
|
|
161
191
|
`data-is-root-path="true"` at `/` and nothing anywhere else, which is how the
|
|
162
192
|
stylesheet tells the two apart. `menus.primary` is printed inside the
|
|
@@ -260,13 +290,19 @@ html.geekity-admin-bar {
|
|
|
260
290
|
|
|
261
291
|
### An entry
|
|
262
292
|
|
|
263
|
-
`layouts/post.njk` and `layouts/page.njk` draw the same thing, the
|
|
264
|
-
|
|
293
|
+
`layouts/post.njk` and `layouts/page.njk` draw the same thing, the entry, and it
|
|
294
|
+
is a microformats2 `h-entry`. An article, and a page:
|
|
265
295
|
|
|
266
296
|
```html
|
|
267
297
|
<article class="blog-post h-entry">
|
|
268
|
-
<header>
|
|
298
|
+
<header class="post-header">
|
|
299
|
+
<p class="kicker">
|
|
300
|
+
<span class="kicker-kind">Article</span>
|
|
301
|
+
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
302
|
+
<a href="/category/notes/" class="p-category" rel="category">notes</a>
|
|
303
|
+
</p>
|
|
269
304
|
<h1 class="p-name">Hello</h1>
|
|
305
|
+
<p class="post-deck p-summary">The description the author wrote.</p>
|
|
270
306
|
</header>
|
|
271
307
|
<section class="e-content">
|
|
272
308
|
<p>The rendered body.</p>
|
|
@@ -279,40 +315,73 @@ design's entry, and it is a microformats2 `h-entry`:
|
|
|
279
315
|
>Published 2 September 2026</time
|
|
280
316
|
></a
|
|
281
317
|
>
|
|
282
|
-
<
|
|
318
|
+
<span aria-hidden="true">·</span>
|
|
319
|
+
<time class="small dt-updated" datetime="2026-09-05T09:00:00.000Z"
|
|
283
320
|
>Updated 5 September 2026</time
|
|
284
321
|
>
|
|
285
322
|
</p>
|
|
286
323
|
</section>
|
|
287
|
-
<p class="post-categories">…categories…</p>
|
|
288
324
|
<p class="post-categories">…tags…</p>
|
|
289
325
|
<hr />
|
|
290
326
|
<footer>…the bio…</footer>
|
|
291
327
|
</article>
|
|
292
|
-
<nav class="blog-post-nav"
|
|
328
|
+
<nav class="blog-post-nav" aria-label="Neighbouring posts">…</nav>
|
|
293
329
|
```
|
|
294
330
|
|
|
331
|
+
**The kicker names the kind.** `p.kicker`, from `partials/kicker.njk`, opens
|
|
332
|
+
every entry: the kind word in `span.kicker-kind` (Article, Note or Reply for a
|
|
333
|
+
post, from its `postType`; Page for a page), then the categories it is filed
|
|
334
|
+
under as `p-category` links. It is the only place the kind is said, small and
|
|
335
|
+
once, and an article is additionally the only kind with a visible headline.
|
|
336
|
+
The deck is the front matter's `description` as a `p.post-deck.p-summary`,
|
|
337
|
+
printed only when the author wrote one.
|
|
338
|
+
|
|
295
339
|
**The Published line is inside the `e-content`**, which is the one thing about
|
|
296
340
|
this shape worth knowing. It is what the source theme does: the date and the
|
|
297
|
-
permalink are part of the words, so a reader
|
|
298
|
-
`e-content
|
|
299
|
-
time as a `u-url`, and a `dt-updated`
|
|
300
|
-
happened on a different day in the site's own timezone; a typo fixed
|
|
301
|
-
later is not news. A post tagged `indienews` opens the line with a
|
|
341
|
+
permalink are part of the words, so a reader, or a fediverse peer reading the
|
|
342
|
+
`e-content`, takes them with the post. The permalink wraps the `dt-published`
|
|
343
|
+
time as a `u-url`, and a `dt-updated` follows it after a middle dot only when
|
|
344
|
+
the update happened on a different day in the site's own timezone; a typo fixed
|
|
345
|
+
an hour later is not news. A post tagged `indienews` opens the line with a
|
|
302
346
|
`u-category` link to <https://news.indieweb.org/en>, which is how IndieNews is
|
|
303
347
|
told the post is for it; a page never prints one, and its line is a
|
|
304
348
|
`p.page-meta` rather than a `p.entry-meta`.
|
|
305
349
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
350
|
+
**Every post's page has one `h1`.** A post with a name of its own is headed by
|
|
351
|
+
it, as above. A note, or a reply without a title, opens on its words instead,
|
|
352
|
+
set larger, so it has no `header`; in its place is an `h1` a screen reader and
|
|
353
|
+
a crawler navigate by and a sighted reader never sees, saying what the post is,
|
|
354
|
+
who wrote it and when, and then the kicker:
|
|
355
|
+
|
|
356
|
+
```html
|
|
357
|
+
<h1 class="screen-reader-text">Note by Ada Lovelace, 11 September 2026</h1>
|
|
358
|
+
<p class="kicker"><span class="kicker-kind">Note</span></p>
|
|
359
|
+
```
|
|
309
360
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
comment form
|
|
314
|
-
|
|
315
|
-
|
|
361
|
+
It says `Reply` for a reply and leaves out the name or the date the post does
|
|
362
|
+
not have. It is not a `p-name`: a parser that found one would take the post
|
|
363
|
+
for an article. The headings under it step down one level at a time: the
|
|
364
|
+
conversation and the comment form are `h2`, and a refused form's error summary
|
|
365
|
+
is an `h3` inside the form's section.
|
|
366
|
+
|
|
367
|
+
**A reply cites what it answers** with `partials/reply-context.njk`, a
|
|
368
|
+
`div.reply-context.cite.u-in-reply-to.h-cite`: a rule on the start side, a
|
|
369
|
+
`p.cite-line` saying "In reply to" and the target's name, author and date as
|
|
370
|
+
far as they are known, and its excerpt as a `blockquote.cite-quote.p-content`.
|
|
371
|
+
An untitled reply prints it under the kicker; a titled reply prints it above
|
|
372
|
+
its header. The same partial cites a reply in a feed.
|
|
373
|
+
|
|
374
|
+
The tags are printed under the words and inside the article, so that each link
|
|
375
|
+
is a `p-category` of this entry, as one `p.post-categories` from
|
|
376
|
+
`partials/tags.njk`. The stylesheet draws the `#` in front of each, so the
|
|
377
|
+
category a parser reads is the tag and not the hash.
|
|
378
|
+
|
|
379
|
+
After the entry a post prints `nav.blog-post-nav`: the `previous` and `next`
|
|
380
|
+
posts as two cards, `rel="prev"` and `rel="next"`, each opening on a
|
|
381
|
+
`span.blog-post-nav-label`, and nothing at all at the ends of the archive; then
|
|
382
|
+
the conversation and the comment form. A page prints the contact form when its
|
|
383
|
+
front matter asked for one. A page has no neighbours, no tags and no IndieNews
|
|
384
|
+
link, because none of those are things a page has.
|
|
316
385
|
|
|
317
386
|
### The bio
|
|
318
387
|
|
|
@@ -371,49 +440,66 @@ byline under the title rather than a bio in the footer, so it writes one.
|
|
|
371
440
|
|
|
372
441
|
### A listing
|
|
373
442
|
|
|
374
|
-
`partials/post-list.njk` is the feed every listing is made of
|
|
375
|
-
the posts page, a tag, category or author archive
|
|
376
|
-
`h-feed
|
|
443
|
+
`partials/post-list.njk` is the feed every listing is made of (the home page,
|
|
444
|
+
the posts page, a tag, category or author archive, and the front page's recent
|
|
445
|
+
posts) and it is a microformats2 `h-feed` of `h-entry` items, each opening on
|
|
446
|
+
its kicker:
|
|
377
447
|
|
|
378
448
|
```html
|
|
379
449
|
<div class="feed h-feed">
|
|
380
450
|
<article class="feed-item h-entry">
|
|
381
|
-
<
|
|
382
|
-
<
|
|
383
|
-
|
|
384
|
-
</
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
<div class="feed-meta">
|
|
394
|
-
<p>
|
|
395
|
-
<time class="feed-date dt-published" datetime="…"
|
|
396
|
-
>2 September 2026</time
|
|
397
|
-
>
|
|
398
|
-
</p>
|
|
399
|
-
<p class="post-categories">
|
|
400
|
-
<a href="/category/notes/" class="p-category" rel="category">notes</a>
|
|
401
|
-
</p>
|
|
402
|
-
</div>
|
|
451
|
+
<p class="kicker">
|
|
452
|
+
<span class="kicker-kind">Article</span>
|
|
453
|
+
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
454
|
+
<time class="feed-date dt-published" datetime="…">2 September 2026</time>
|
|
455
|
+
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
456
|
+
<a href="/category/notes/" class="p-category" rel="category">notes</a>
|
|
457
|
+
</p>
|
|
458
|
+
<h2 class="feed-title p-name">
|
|
459
|
+
<a href="/2026/09/hello/" class="u-url">Hello</a>
|
|
460
|
+
</h2>
|
|
461
|
+
<div class="feed-excerpt p-summary">
|
|
462
|
+
<p>What the post is about, in about 280 characters...</p>
|
|
403
463
|
</div>
|
|
464
|
+
<p class="feed-more">
|
|
465
|
+
<a href="/2026/09/hello/" aria-label="Continue reading: Hello">
|
|
466
|
+
Continue reading<span aria-hidden="true"> →</span>
|
|
467
|
+
</a>
|
|
468
|
+
</p>
|
|
404
469
|
</article>
|
|
405
470
|
<hr class="feed-separator" />
|
|
406
|
-
<article class="feed-item h-entry"
|
|
471
|
+
<article class="feed-item h-entry">
|
|
472
|
+
<p class="kicker">
|
|
473
|
+
<span class="kicker-kind">Note</span>
|
|
474
|
+
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
475
|
+
<a href="/2026/09/coffee/" class="u-url"
|
|
476
|
+
><time class="feed-date dt-published" datetime="…"
|
|
477
|
+
>3 September 2026</time
|
|
478
|
+
></a
|
|
479
|
+
>
|
|
480
|
+
</p>
|
|
481
|
+
<div class="feed-excerpt e-content"><p>The whole note.</p></div>
|
|
482
|
+
</article>
|
|
407
483
|
</div>
|
|
408
484
|
```
|
|
409
485
|
|
|
410
|
-
|
|
411
|
-
|
|
486
|
+
**Only an article has a headline.** A note, which has no title, is printed
|
|
487
|
+
whole as its `e-content`, and the date in its kicker is its `u-url`. A reply is
|
|
488
|
+
a note that first cites what it answers with `partials/reply-context.njk`, and
|
|
489
|
+
a reply with a title is an article that does. An article's categories are in
|
|
490
|
+
its kicker; a note's are on its own page.
|
|
491
|
+
|
|
492
|
+
The excerpt is the entry's `summary`, the very line the feeds publish, so a
|
|
493
|
+
reader and a feed cannot be told two different things, through Nunjucks'
|
|
412
494
|
`truncate(280)`, which cuts at the last space before 280 characters and adds an
|
|
413
495
|
ellipsis. A `description` shorter than that is printed whole. It is plain text
|
|
414
496
|
rather than markup, deliberately: an excerpt with half a code block in it is
|
|
415
497
|
not an excerpt.
|
|
416
498
|
|
|
499
|
+
Each entry of a listing carries `replyContext` beside `inReplyTo` when it is a
|
|
500
|
+
reply whose target has been fetched, the same object a reply's own page gets,
|
|
501
|
+
so the feed can cite it in full.
|
|
502
|
+
|
|
417
503
|
An entry's tags are not in a feed item. They belong under the entry, where
|
|
418
504
|
there is one post's worth of them rather than twenty.
|
|
419
505
|
|
|
@@ -429,21 +515,24 @@ before including it, so the outline never skips a level:
|
|
|
429
515
|
|
|
430
516
|
A listing with nothing on it says `No posts found.` in `p.empty`.
|
|
431
517
|
|
|
432
|
-
`partials/pagination.njk` prints two
|
|
518
|
+
`partials/pagination.njk` prints two links in `nav.pagination`, `← Previous`
|
|
433
519
|
to the page before this one and `Next →` to the page after it, with `rel="prev"`
|
|
434
|
-
and `rel="next"
|
|
520
|
+
and `rel="next"`, and nothing at all on a listing of one page. The arrows
|
|
435
521
|
themselves are `aria-hidden`, because a screen reader that announced them would
|
|
436
522
|
read the decoration and then the word.
|
|
437
523
|
|
|
438
524
|
Each listing layout heads its own page: `layouts/home.njk` with the listing's
|
|
439
|
-
title,
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
525
|
+
title, and the three archives with a `header.archive-header` whose kicker says
|
|
526
|
+
what kind of archive it is (Tag, Category, Author) over an `h1.page-title`
|
|
527
|
+
naming whose: the term, or the person, followed by their `h-card`. The
|
|
528
|
+
category archive's header is also a `header.category-header` and prints a
|
|
529
|
+
`div.category-description` under the name when the context carries a
|
|
530
|
+
`categoryDescription`; the CMS has no store of term descriptions, so nothing
|
|
531
|
+
writes one today and the header is the heading alone.
|
|
532
|
+
|
|
533
|
+
`layouts/404.njk` says `Content not found.` under a `Not found` kicker and
|
|
534
|
+
links home and to the search. The 500 and 503 pages below open on a kicker the
|
|
535
|
+
same way.
|
|
447
536
|
|
|
448
537
|
`layouts/500.njk` is the page a request gets when the server fails while
|
|
449
538
|
answering it (TASK-129). It says `Something went wrong.` in an
|
|
@@ -666,38 +755,48 @@ to its own stylesheet.
|
|
|
666
755
|
|
|
667
756
|
### Colours
|
|
668
757
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
a
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
|
693
|
-
|
|
|
694
|
-
| `--color-
|
|
695
|
-
| `--color-
|
|
696
|
-
| `--color-
|
|
697
|
-
| `--color-
|
|
698
|
-
| `--color-
|
|
699
|
-
| `--color-
|
|
700
|
-
| `--color-
|
|
758
|
+
Every colour is a custom property on `:root`, and these tokens are the
|
|
759
|
+
override contract: a site that only wants different colours redefines them in
|
|
760
|
+
its own stylesheet rather than replacing this one. Tailwind's own palette is
|
|
761
|
+
cleared in `src/style.css`, and its utilities (`text-ink`, `bg-paper`,
|
|
762
|
+
`border-edge` and the rest) are mapped onto these properties with
|
|
763
|
+
`@theme inline`, so redefining a token recolours every rule that uses it.
|
|
764
|
+
|
|
765
|
+
A second scheme under `@media (prefers-color-scheme: dark)` redefines the same
|
|
766
|
+
tokens on dark paper, and `color-scheme: light dark` lets a browser paint its
|
|
767
|
+
own form controls and scrollbars to match. There is no toggle: the reader's
|
|
768
|
+
system setting is the setting.
|
|
769
|
+
|
|
770
|
+
Both schemes meet the Colour contrast rule of the
|
|
771
|
+
[specification.website checklist](https://specification.website/checklist.md):
|
|
772
|
+
every run of text, the small kickers, dates and meta lines included, at 7:1
|
|
773
|
+
(WCAG 1.4.6, AAA) on whatever it sits on, and every border a reader relies on
|
|
774
|
+
(a field, a card, a code block, the focus outline, the citation and blockquote
|
|
775
|
+
rules) at 3:1 (1.4.11). Nothing a reader needs is drawn at reduced opacity.
|
|
776
|
+
That is a test rather than a claim: `src/web/theme-colors.test.ts` reads the
|
|
777
|
+
tokens out of the compiled stylesheet and computes the ratio of every pair the
|
|
778
|
+
design puts on screen, so a colour changed in `src/style.css` that breaks one
|
|
779
|
+
fails the build.
|
|
780
|
+
|
|
781
|
+
| Token | Utility | What it colours |
|
|
782
|
+
| ------------------------- | ---------- | ---------------------------------------------------------------------- |
|
|
783
|
+
| `--color-body` | `paper` | The paper, and the words on a submit button. |
|
|
784
|
+
| `--color-text` | `ink` | Body text and headlines. |
|
|
785
|
+
| `--color-muted` | `muted` | Kickers, dates, meta lines, decks, quotes, the bio note, the footer. |
|
|
786
|
+
| `--color-primary` | `accent` | Links, a kicker's kind word, the citation rule, the submit button. |
|
|
787
|
+
| `--color-secondary` | `accent-2` | Tag links and the focus outline. |
|
|
788
|
+
| `--color-rule` | `rule` | Decorative hairlines only: between entries, above the footer. |
|
|
789
|
+
| `--color-edge` | `edge` | Borders a reader relies on: fields, the previous and next cards, code. |
|
|
790
|
+
| `--color-base` | `surface` | A sunk surface: fields, notices, a face with no avatar. |
|
|
791
|
+
| `--color-base-2` | — | A cooler sunk surface. |
|
|
792
|
+
| `--color-base-3` | — | The raised surface: the focused skip link. |
|
|
793
|
+
| `--color-code-background` | `code` | Behind `code` and a fenced block. |
|
|
794
|
+
| `--color-code-text` | — | Code with no highlighting on it. |
|
|
795
|
+
| `--color-error` | `error` | A field that will not do, and what the form says about it. |
|
|
796
|
+
|
|
797
|
+
`--color-rule` is the one token in no contrast pair, because it is decoration:
|
|
798
|
+
a site that recolours it may make it as faint as it likes. Anything drawn with
|
|
799
|
+
`--color-edge` has to stay at 3:1 against the paper and the surface.
|
|
701
800
|
|
|
702
801
|
### Code highlighting
|
|
703
802
|
|
|
@@ -734,11 +833,12 @@ a different version of highlight.js than the one installed.
|
|
|
734
833
|
**The palette is Tomorrow**, the one both of the sites this design comes from
|
|
735
834
|
used: Chris Kempson's Tomorrow on light paper and Tomorrow Night on dark, from
|
|
736
835
|
highlight.js's own base16 themes, which is also what decides which `hljs-`
|
|
737
|
-
scope takes which slot.
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
further
|
|
836
|
+
scope takes which slot. None of the light colours is the stock one: a syntax
|
|
837
|
+
theme is drawn for an editor, where a faint comment is a feature, and here code
|
|
838
|
+
is text like any other and meets 7:1 on its block, the bar every run of text
|
|
839
|
+
on the page meets. Each light colour was darkened along its own hue until it
|
|
840
|
+
did and no further, and four of the dark ones (the comment grey, the red, the
|
|
841
|
+
blue and the `base0F` brown) were lightened toward white the same way. Every one
|
|
742
842
|
of them is in the contrast test beside the rest of the palette.
|
|
743
843
|
|
|
744
844
|
| Token | base16 | What it colours |
|
|
@@ -1259,8 +1359,11 @@ The packaged partial draws each entry as the source design does: an
|
|
|
1259
1359
|
`li.comment.h-entry` carrying `id="comment-{{ reply.id }}"` and a
|
|
1260
1360
|
`comment-{{ reply.source }}` class, holding an `article.comment-body` whose
|
|
1261
1361
|
`footer.comment-meta` is the author as a `.comment-author.vcard.p-author.h-card`
|
|
1262
|
-
|
|
1263
|
-
|
|
1362
|
+
(their avatar, or the first letter of their name in a `span.comment-avatar-empty`
|
|
1363
|
+
when there is none) and the permalink as a `.comment-metadata` link around a
|
|
1364
|
+
`time.dt-published`, with a `span.comment-source` saying "via webmention" or
|
|
1365
|
+
"via the fediverse" when it came that way, then the words in
|
|
1366
|
+
`div.comment-content.e-content`. Answers nest in an
|
|
1264
1367
|
`ol.children` inside what they answer. A Reply link in a `div.reply` goes on the
|
|
1265
1368
|
ones written here — `source == "comment"` — when the post is still open; it
|
|
1266
1369
|
carries the comment's id to the form as `?reply_to=`, so threading needs no
|
|
@@ -1269,7 +1372,8 @@ webmention on the page that sent it, so neither gets one.
|
|
|
1269
1372
|
|
|
1270
1373
|
Above the thread, `group()` draws one `div.reaction-group` per kind that has
|
|
1271
1374
|
anything — `p-like`, `p-repost` and `p-mention`, the source theme's classes — as
|
|
1272
|
-
an `h2.reaction-title` of the label and the count
|
|
1375
|
+
an `h2.reaction-title` of the label and the count, in a `span.reaction-count`,
|
|
1376
|
+
beside a `div.facepile` of
|
|
1273
1377
|
`a.u-url` faces. A face is a round `u-photo` avatar; a reaction whose author the
|
|
1274
1378
|
site knows no picture of is the emoji badge of its kind and a `.reaction-name`
|
|
1275
1379
|
instead. Likes and boosts link to the person, and a mention to the page it came
|
|
@@ -1336,7 +1440,10 @@ The packaged partial is a `section#respond.comment-respond` with an
|
|
|
1336
1440
|
`h2.comment-reply-title`, one paragraph per field and the button in a
|
|
1337
1441
|
`p.form-submit` — the source design's names, so the stylesheet styles every
|
|
1338
1442
|
label, field and button of both forms through `.comment-respond` and a site
|
|
1339
|
-
that has CSS for the WordPress theme can bring it.
|
|
1443
|
+
that has CSS for the WordPress theme can bring it. The name and email fields sit
|
|
1444
|
+
in a `div.field-row`, side by side on a wide screen, in both forms. On a site
|
|
1445
|
+
that takes webmentions a `p.comment-help` under the heading says a reader can
|
|
1446
|
+
reply from their own site instead.
|
|
1340
1447
|
|
|
1341
1448
|
| Key | What it holds |
|
|
1342
1449
|
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -1355,6 +1462,23 @@ technology, and give it `tabindex="-1"` and `autocomplete="off"`. A submission
|
|
|
1355
1462
|
that filled it is dropped. **Do not** remove it from a replacement partial —
|
|
1356
1463
|
it is one of three things standing between the site and a spam queue.
|
|
1357
1464
|
|
|
1465
|
+
### When a submission is refused
|
|
1466
|
+
|
|
1467
|
+
The CMS answers a refused comment with the post again, `problems` and `error`
|
|
1468
|
+
filled in. The packaged partial tells assistive technology about it in two
|
|
1469
|
+
places, and a replacement partial should do the same:
|
|
1470
|
+
|
|
1471
|
+
- **A summary before the form.** One element with `role="alert"`,
|
|
1472
|
+
`tabindex="-1"` and `autofocus`, labelled by a heading that is `error`, or a
|
|
1473
|
+
sentence of your own when there is none, and holding a link to each field in
|
|
1474
|
+
`problems` (`<a href="#comment-name">…</a>`). `autofocus` moves focus to it on
|
|
1475
|
+
load with no script, so it has to be the first `autofocus` on the page.
|
|
1476
|
+
- **On each field in `problems`.** `aria-invalid="true"` and
|
|
1477
|
+
`aria-describedby` naming the element that holds its message, so a screen
|
|
1478
|
+
reader reads the message with the field.
|
|
1479
|
+
|
|
1480
|
+
The contact form below keeps the same contract.
|
|
1481
|
+
|
|
1358
1482
|
### When somebody is signed in
|
|
1359
1483
|
|
|
1360
1484
|
Two more keys are on `commentForm` when — and only when — the request carries a
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{% extends "layouts/base.njk" %}
|
|
2
|
+
{% import "partials/kicker.njk" as kicker with context %}
|
|
2
3
|
|
|
3
4
|
{% block content %}
|
|
4
5
|
<article class="not-found">
|
|
6
|
+
{{ kicker.line("Not found") }}
|
|
5
7
|
<h1 class="page-title">Content not found.</h1>
|
|
6
8
|
<p>Go <a href="{{ "/" | url }}">home</a>, or <a href="{{ "/search/" | url }}">search the site</a>.</p>
|
|
7
9
|
</article>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{% extends "layouts/base.njk" %}
|
|
2
|
+
{% import "partials/kicker.njk" as kicker with context %}
|
|
2
3
|
|
|
3
4
|
{% block content %}
|
|
4
5
|
<article class="server-error">
|
|
6
|
+
{{ kicker.line("Server error") }}
|
|
5
7
|
<h1 class="page-title">Something went wrong.</h1>
|
|
6
8
|
<p>This page could not be shown because of a problem on the server. Try again in a moment, or go <a href="{{ "/" | url }}">home</a>.</p>
|
|
7
9
|
</article>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{% extends "layouts/base.njk" %}
|
|
2
|
+
{% import "partials/kicker.njk" as kicker with context %}
|
|
2
3
|
|
|
3
4
|
{% block content %}
|
|
4
5
|
<article class="maintenance">
|
|
6
|
+
{{ kicker.line("Maintenance") }}
|
|
5
7
|
<h1 class="page-title">Down for maintenance.</h1>
|
|
6
8
|
<p>This site is down for maintenance and will be back shortly.{% if until %} It should be back by <time datetime="{{ until }}">{{ untilText }}</time>.{% endif %} Please try again then.</p>
|
|
7
9
|
</article>
|
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
{{ feeds.feedLinks(author.url, site.title + ": " + author.name) }}
|
|
27
27
|
{% endblock %}
|
|
28
28
|
|
|
29
|
+
{% import "partials/kicker.njk" as kicker with context %}
|
|
29
30
|
{% block content %}
|
|
30
|
-
<header class="author-header">
|
|
31
|
+
<header class="author-header archive-header">
|
|
32
|
+
{{ kicker.line("Author") }}
|
|
31
33
|
<h1 class="page-title">{{ author.name }}</h1>
|
|
32
34
|
</header>
|
|
33
35
|
{% include "partials/bio.njk" %}
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
<a href="{{ "/" | url }}">{{ site.title }}</a>
|
|
168
168
|
</h1>
|
|
169
169
|
{% if site.tagline %}
|
|
170
|
-
<p>{{ site.tagline }}</p>
|
|
170
|
+
<p class="site-tagline">{{ site.tagline }}</p>
|
|
171
171
|
{% endif %}
|
|
172
172
|
{% else %}
|
|
173
173
|
<a class="header-link-home" href="{{ "/" | url }}">{{ site.title }}</a>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
{{ feeds.feedLinks(categoryRoot, site.title + ": " + category) }}
|
|
10
10
|
{% endblock %}
|
|
11
11
|
|
|
12
|
+
{% import "partials/kicker.njk" as kicker with context %}
|
|
12
13
|
{% block content %}
|
|
13
14
|
{#
|
|
14
15
|
The archive's header. `categoryDescription` is what the design prints under
|
|
@@ -16,8 +17,9 @@
|
|
|
16
17
|
alone until something does — a site whose own context carries one gets it
|
|
17
18
|
printed without overriding this layout.
|
|
18
19
|
#}
|
|
19
|
-
<header class="category-header">
|
|
20
|
-
|
|
20
|
+
<header class="category-header archive-header">
|
|
21
|
+
{{ kicker.line("Category") }}
|
|
22
|
+
<h1 class="page-title">{{ category }}</h1>
|
|
21
23
|
{% if categoryDescription %}
|
|
22
24
|
<div class="category-description">{{ categoryDescription }}</div>
|
|
23
25
|
{% endif %}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
{{ content | safe }}
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
|
-
<h2>Recent Posts</h2>
|
|
26
|
+
<h2 class="section-title">Recent Posts</h2>
|
|
27
27
|
{#- The feed a listing prints, over the posts the CMS decided were recent:
|
|
28
28
|
this month's writing when there is enough of it and the five newest
|
|
29
29
|
otherwise. `feedHeading` is 3 because the `h2` above is what they sit
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
whoever the site is by, not by the person a front matter line names.
|
|
14
14
|
#}
|
|
15
15
|
{% extends "layouts/base.njk" %}
|
|
16
|
+
{% import "partials/kicker.njk" as kicker with context %}
|
|
16
17
|
|
|
17
18
|
{#- Who the bio credits, and what tells `layouts/base.njk` the site menu is in
|
|
18
19
|
the bio rather than in the page footer. -#}
|
|
@@ -21,7 +22,8 @@
|
|
|
21
22
|
{% block content %}
|
|
22
23
|
{% set entryUrl = page.url or url %}
|
|
23
24
|
<article class="blog-post h-entry">
|
|
24
|
-
<header>
|
|
25
|
+
<header class="post-header">
|
|
26
|
+
{{ kicker.line("Page") }}
|
|
25
27
|
<h1 class="p-name">{{ title }}</h1>
|
|
26
28
|
</header>
|
|
27
29
|
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
<a href="{{ entryUrl | url }}" class="u-url"><time class="small dt-published" datetime="{{ date | date("iso") }}">Published {{ date | date }}</time></a>
|
|
37
39
|
{%- endif %}
|
|
38
40
|
{%- if updated and (not date or (updated | date("html")) != (date | date("html"))) %}
|
|
39
|
-
{% if date %}<
|
|
41
|
+
{% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date }}</time>
|
|
40
42
|
{%- endif -%}
|
|
41
43
|
</p>
|
|
42
44
|
</section>
|