@geekity/cms 0.11.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.
@@ -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 plain CSS. There is no build step and no dependency: a site
47
- that wants Tailwind or anything else brings its own.
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 andrewshell.org design's shell (decision-16): the
149
- skip link, one `.global-wrapper` at the 42rem measure, `.global-header`,
150
- `<main id="main">` and the footer, in that order.
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
- paragraph under it; on every other page it is `a.header-link-home`, the site
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 source
264
- design's entry, and it is a microformats2 `h-entry`:
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,38 +315,47 @@ design's entry, and it is a microformats2 `h-entry`:
279
315
  >Published 2 September 2026</time
280
316
  ></a
281
317
  >
282
- <br /><time class="small dt-updated" datetime="2026-09-05T09:00:00.000Z"
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">…the posts either side…</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 — or a fediverse peer reading the
298
- `e-content` — takes them with the post. The permalink wraps the `dt-published`
299
- time as a `u-url`, and a `dt-updated` line follows it only when the update
300
- happened on a different day in the site's own timezone; a typo fixed an hour
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
350
  **Every post's page has one `h1`.** A post with a name of its own is headed by
307
351
  it, as above. A note, or a reply without a title, opens on its words instead,
308
- so it has no `header`; in its place is an `h1` a screen reader and a crawler
309
- navigate by and a sighted reader never sees, saying what the post is, who wrote
310
- it and when:
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:
311
355
 
312
356
  ```html
313
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>
314
359
  ```
315
360
 
316
361
  It says `Reply` for a reply and leaves out the name or the date the post does
@@ -319,16 +364,24 @@ for an article. The headings under it step down one level at a time: the
319
364
  conversation and the comment form are `h2`, and a refused form's error summary
320
365
  is an `h3` inside the form's section.
321
366
 
322
- What it is filed under is printed under the words and inside the article, so
323
- that each link is a `p-category` of this entry: the categories first, then the
324
- tags, each as one `p.post-categories` from `partials/tags.njk`.
325
-
326
- After the entry a post prints `nav.blog-post-nav`, `rel="prev"` and
327
- `rel="next"` links to `previous` and `next` with an arrow either side, and
328
- nothing at all at the ends of the archive; then the conversation and the
329
- comment form. A page prints the contact form when its front matter asked for
330
- one. A page has no neighbours, no taxonomy and no IndieNews link, because none
331
- of those are things a page has.
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.
332
385
 
333
386
  ### The bio
334
387
 
@@ -387,49 +440,66 @@ byline under the title rather than a bio in the footer, so it writes one.
387
440
 
388
441
  ### A listing
389
442
 
390
- `partials/post-list.njk` is the feed every listing is made of — the home page,
391
- the posts page, a tag, category or author archive — and it is a microformats2
392
- `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:
393
447
 
394
448
  ```html
395
449
  <div class="feed h-feed">
396
450
  <article class="feed-item h-entry">
397
- <div class="feed-content">
398
- <h2 class="feed-title p-name">
399
- <a href="/2026/09/hello/" class="u-url">Hello</a>
400
- </h2>
401
- <div class="feed-excerpt p-summary">
402
- <p>What the post is about, in about 280 characters...</p>
403
- </div>
404
- <p class="feed-more">
405
- <a href="/hello/" aria-label="Continue reading: Hello">
406
- Continue reading<span aria-hidden="true"> &rarr;</span>
407
- </a>
408
- </p>
409
- <div class="feed-meta">
410
- <p>
411
- <time class="feed-date dt-published" datetime="…"
412
- >2 September 2026</time
413
- >
414
- </p>
415
- <p class="post-categories">
416
- <a href="/category/notes/" class="p-category" rel="category">notes</a>
417
- </p>
418
- </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>
419
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"> &rarr;</span>
467
+ </a>
468
+ </p>
420
469
  </article>
421
470
  <hr class="feed-separator" />
422
- <article class="feed-item h-entry">…</article>
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>
423
483
  </div>
424
484
  ```
425
485
 
426
- The excerpt is the entry's `summary` — the very line the feeds publish, so a
427
- reader and a feed cannot be told two different things — through Nunjucks'
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'
428
494
  `truncate(280)`, which cuts at the last space before 280 characters and adds an
429
495
  ellipsis. A `description` shorter than that is printed whole. It is plain text
430
496
  rather than markup, deliberately: an excerpt with half a code block in it is
431
497
  not an excerpt.
432
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
+
433
503
  An entry's tags are not in a feed item. They belong under the entry, where
434
504
  there is one post's worth of them rather than twenty.
435
505
 
@@ -445,21 +515,24 @@ before including it, so the outline never skips a level:
445
515
 
446
516
  A listing with nothing on it says `No posts found.` in `p.empty`.
447
517
 
448
- `partials/pagination.njk` prints two arrows in `nav.pagination` — `← Previous`
518
+ `partials/pagination.njk` prints two links in `nav.pagination`, `← Previous`
449
519
  to the page before this one and `Next →` to the page after it, with `rel="prev"`
450
- and `rel="next"` — and nothing at all on a listing of one page. The arrows
520
+ and `rel="next"`, and nothing at all on a listing of one page. The arrows
451
521
  themselves are `aria-hidden`, because a screen reader that announced them would
452
522
  read the decoration and then the word.
453
523
 
454
524
  Each listing layout heads its own page: `layouts/home.njk` with the listing's
455
- title, `layouts/tag.njk` and `layouts/category.njk` with the term, and
456
- `layouts/author.njk` with the person as an `h-card`. The category archive puts
457
- its heading in a `header.category-header` and prints a `div.category-description`
458
- under it when the context carries a `categoryDescription`; the CMS has no store
459
- of term descriptions, so nothing writes one today and the header is the heading
460
- alone.
461
-
462
- `layouts/404.njk` says `Content not found.` and links home and to the search.
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.
463
536
 
464
537
  `layouts/500.njk` is the page a request gets when the server fails while
465
538
  answering it (TASK-129). It says `Something went wrong.` in an
@@ -682,38 +755,48 @@ to its own stylesheet.
682
755
 
683
756
  ### Colours
684
757
 
685
- `static/style.css` is the source design: a serif body and sans headings at an
686
- 18px root on a 1.2 minor-third scale, warm paper with near-black text, a rust
687
- primary and a blue secondary, one column, links that invert to the primary
688
- colour on hover, and a rule in the primary colour. Everything is a custom
689
- property on `:root`, so a site that only wants different colours overrides the
690
- half-dozen `--color-*` tokens rather than the stylesheet.
691
-
692
- The source is light only. The theme adds a second scheme under
693
- `@media (prefers-color-scheme: dark)` that redefines the same colour tokens on
694
- dark paper, with the rust and the blue lifted until they read on it, and
695
- `color-scheme: light dark` so a browser paints its own form controls and
696
- scrollbars to match. There is no toggle: the reader's system setting is the
697
- setting.
698
-
699
- Both schemes meet WCAG 2.2 AA — 4.5:1 for body text, 3:1 for large text, rules
700
- and focus outlines — and that is a test rather than a claim.
701
- `src/web/theme-colors.test.ts` reads the custom properties out of this
702
- stylesheet and computes the ratios for every pair the design puts on screen, so
703
- a colour changed here that breaks one fails the build.
704
-
705
- | Token | What it colours |
706
- | ------------------------- | -------------------------------------------------- |
707
- | `--color-body` | The paper, and the text of an inverted link. |
708
- | `--color-text` | The ink. |
709
- | `--color-primary` | Links, the rule, the focus outline, table headers. |
710
- | `--color-secondary` | Blockquote text and its border. |
711
- | `--color-base` | A warm sunk surface: rules between entries. |
712
- | `--color-base-2` | A cooler sunk surface. |
713
- | `--color-base-3` | The raised surface: the focused skip link, inputs. |
714
- | `--color-code-background` | Behind `code` and a fenced block. |
715
- | `--color-code-text` | Code with no highlighting on it. |
716
- | `--color-error` | A form field that will not do. |
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.
717
800
 
718
801
  ### Code highlighting
719
802
 
@@ -750,11 +833,12 @@ a different version of highlight.js than the one installed.
750
833
  **The palette is Tomorrow**, the one both of the sites this design comes from
751
834
  used: Chris Kempson's Tomorrow on light paper and Tomorrow Night on dark, from
752
835
  highlight.js's own base16 themes, which is also what decides which `hljs-`
753
- scope takes which slot. Five of the light colours are not the stock ones — a
754
- syntax theme is drawn for an editor, where a faint comment is a feature, and on
755
- this paper the stock greys, orange, yellow, green and teal read between 1.7:1
756
- and 3.5:1. Each was darkened along its own hue until it made 4.5:1 and no
757
- further; on the dark scheme only the `base0F` brown needed lifting. Every one
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
758
842
  of them is in the contrast test beside the rest of the palette.
759
843
 
760
844
  | Token | base16 | What it colours |
@@ -1275,8 +1359,11 @@ The packaged partial draws each entry as the source design does: an
1275
1359
  `li.comment.h-entry` carrying `id="comment-{{ reply.id }}"` and a
1276
1360
  `comment-{{ reply.source }}` class, holding an `article.comment-body` whose
1277
1361
  `footer.comment-meta` is the author as a `.comment-author.vcard.p-author.h-card`
1278
- and the permalink as a `.comment-metadata` link around a `time.dt-published`,
1279
- then the words in `div.comment-content.e-content`. Answers nest in an
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
1280
1367
  `ol.children` inside what they answer. A Reply link in a `div.reply` goes on the
1281
1368
  ones written here — `source == "comment"` — when the post is still open; it
1282
1369
  carries the comment's id to the form as `?reply_to=`, so threading needs no
@@ -1285,7 +1372,8 @@ webmention on the page that sent it, so neither gets one.
1285
1372
 
1286
1373
  Above the thread, `group()` draws one `div.reaction-group` per kind that has
1287
1374
  anything — `p-like`, `p-repost` and `p-mention`, the source theme's classes — as
1288
- an `h2.reaction-title` of the label and the count beside a `div.facepile` of
1375
+ an `h2.reaction-title` of the label and the count, in a `span.reaction-count`,
1376
+ beside a `div.facepile` of
1289
1377
  `a.u-url` faces. A face is a round `u-photo` avatar; a reaction whose author the
1290
1378
  site knows no picture of is the emoji badge of its kind and a `.reaction-name`
1291
1379
  instead. Likes and boosts link to the person, and a mention to the page it came
@@ -1352,7 +1440,10 @@ The packaged partial is a `section#respond.comment-respond` with an
1352
1440
  `h2.comment-reply-title`, one paragraph per field and the button in a
1353
1441
  `p.form-submit` — the source design's names, so the stylesheet styles every
1354
1442
  label, field and button of both forms through `.comment-respond` and a site
1355
- 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.
1356
1447
 
1357
1448
  | Key | What it holds |
1358
1449
  | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -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
- <h1 class="page-title">Filed under &ldquo;{{ category }}&rdquo;</h1>
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 %}<br>{% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date }}</time>
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>