@geekity/cms 0.11.0 → 0.13.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.
Files changed (240) hide show
  1. package/README.md +177 -6
  2. package/admin/pages/account/login.njk +3 -0
  3. package/admin/pages/documents/editor.njk +7 -0
  4. package/admin/pages/indieauth/consent.njk +51 -0
  5. package/admin/pages/indieauth/refused.njk +11 -0
  6. package/admin/pages/settings/general.njk +9 -0
  7. package/admin/pages/settings/reading.njk +93 -0
  8. package/admin/pages/users/apps.njk +74 -0
  9. package/admin/pages/users/edit.njk +12 -0
  10. package/admin/static/admin.css +27 -0
  11. package/dist/admin/accounts.d.ts +3 -0
  12. package/dist/admin/accounts.d.ts.map +1 -1
  13. package/dist/admin/accounts.js +5 -0
  14. package/dist/admin/accounts.js.map +1 -1
  15. package/dist/admin/documents.d.ts +2 -0
  16. package/dist/admin/documents.d.ts.map +1 -1
  17. package/dist/admin/documents.js +17 -1
  18. package/dist/admin/documents.js.map +1 -1
  19. package/dist/admin/headers.d.ts +5 -1
  20. package/dist/admin/headers.d.ts.map +1 -1
  21. package/dist/admin/headers.js +7 -3
  22. package/dist/admin/headers.js.map +1 -1
  23. package/dist/admin/menu.d.ts.map +1 -1
  24. package/dist/admin/menu.js +2 -1
  25. package/dist/admin/menu.js.map +1 -1
  26. package/dist/admin/routes.d.ts.map +1 -1
  27. package/dist/admin/routes.js +48 -5
  28. package/dist/admin/routes.js.map +1 -1
  29. package/dist/admin/settings-general.js +1 -1
  30. package/dist/admin/settings-general.js.map +1 -1
  31. package/dist/admin/settings-reading.d.ts.map +1 -1
  32. package/dist/admin/settings-reading.js +15 -1
  33. package/dist/admin/settings-reading.js.map +1 -1
  34. package/dist/admin/settings.d.ts +79 -2
  35. package/dist/admin/settings.d.ts.map +1 -1
  36. package/dist/admin/settings.js +168 -2
  37. package/dist/admin/settings.js.map +1 -1
  38. package/dist/admin/templates.d.ts +3 -0
  39. package/dist/admin/templates.d.ts.map +1 -1
  40. package/dist/admin/templates.js +3 -0
  41. package/dist/admin/templates.js.map +1 -1
  42. package/dist/admin/users.d.ts +8 -0
  43. package/dist/admin/users.d.ts.map +1 -1
  44. package/dist/admin/users.js +9 -0
  45. package/dist/admin/users.js.map +1 -1
  46. package/dist/config.d.ts +30 -0
  47. package/dist/config.d.ts.map +1 -1
  48. package/dist/config.js +1 -0
  49. package/dist/config.js.map +1 -1
  50. package/dist/env.d.ts +14 -0
  51. package/dist/env.d.ts.map +1 -1
  52. package/dist/federation/article.d.ts.map +1 -1
  53. package/dist/federation/article.js +18 -5
  54. package/dist/federation/article.js.map +1 -1
  55. package/dist/images/icons.d.ts +44 -25
  56. package/dist/images/icons.d.ts.map +1 -1
  57. package/dist/images/icons.js +205 -60
  58. package/dist/images/icons.js.map +1 -1
  59. package/dist/images/index.d.ts +2 -2
  60. package/dist/images/index.d.ts.map +1 -1
  61. package/dist/images/index.js +1 -1
  62. package/dist/images/index.js.map +1 -1
  63. package/dist/images/markup.d.ts +14 -0
  64. package/dist/images/markup.d.ts.map +1 -1
  65. package/dist/images/markup.js +16 -11
  66. package/dist/images/markup.js.map +1 -1
  67. package/dist/images/variants.d.ts.map +1 -1
  68. package/dist/images/variants.js +3 -4
  69. package/dist/images/variants.js.map +1 -1
  70. package/dist/index.d.ts +14 -4
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +28 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/indexnow.d.ts +34 -0
  75. package/dist/indexnow.d.ts.map +1 -0
  76. package/dist/indexnow.js +141 -0
  77. package/dist/indexnow.js.map +1 -0
  78. package/dist/indieauth/bearer.d.ts +39 -0
  79. package/dist/indieauth/bearer.d.ts.map +1 -0
  80. package/dist/indieauth/bearer.js +63 -0
  81. package/dist/indieauth/bearer.js.map +1 -0
  82. package/dist/indieauth/client.d.ts +61 -0
  83. package/dist/indieauth/client.d.ts.map +1 -0
  84. package/dist/indieauth/client.js +139 -0
  85. package/dist/indieauth/client.js.map +1 -0
  86. package/dist/indieauth/connected-apps.d.ts +23 -0
  87. package/dist/indieauth/connected-apps.d.ts.map +1 -0
  88. package/dist/indieauth/connected-apps.js +73 -0
  89. package/dist/indieauth/connected-apps.js.map +1 -0
  90. package/dist/indieauth/consent.d.ts +28 -0
  91. package/dist/indieauth/consent.d.ts.map +1 -0
  92. package/dist/indieauth/consent.js +185 -0
  93. package/dist/indieauth/consent.js.map +1 -0
  94. package/dist/indieauth/discovery.d.ts +87 -0
  95. package/dist/indieauth/discovery.d.ts.map +1 -0
  96. package/dist/indieauth/discovery.js +127 -0
  97. package/dist/indieauth/discovery.js.map +1 -0
  98. package/dist/indieauth/grants.d.ts +57 -0
  99. package/dist/indieauth/grants.d.ts.map +1 -0
  100. package/dist/indieauth/grants.js +35 -0
  101. package/dist/indieauth/grants.js.map +1 -0
  102. package/dist/indieauth/identity.d.ts +36 -0
  103. package/dist/indieauth/identity.d.ts.map +1 -0
  104. package/dist/indieauth/identity.js +78 -0
  105. package/dist/indieauth/identity.js.map +1 -0
  106. package/dist/indieauth/redeem.d.ts +47 -0
  107. package/dist/indieauth/redeem.d.ts.map +1 -0
  108. package/dist/indieauth/redeem.js +69 -0
  109. package/dist/indieauth/redeem.js.map +1 -0
  110. package/dist/indieauth/request.d.ts +47 -0
  111. package/dist/indieauth/request.d.ts.map +1 -0
  112. package/dist/indieauth/request.js +104 -0
  113. package/dist/indieauth/request.js.map +1 -0
  114. package/dist/indieauth/token.d.ts +19 -0
  115. package/dist/indieauth/token.d.ts.map +1 -0
  116. package/dist/indieauth/token.js +110 -0
  117. package/dist/indieauth/token.js.map +1 -0
  118. package/dist/indieauth/tokens.d.ts +131 -0
  119. package/dist/indieauth/tokens.d.ts.map +1 -0
  120. package/dist/indieauth/tokens.js +205 -0
  121. package/dist/indieauth/tokens.js.map +1 -0
  122. package/dist/web/archive.d.ts +1 -1
  123. package/dist/web/archive.d.ts.map +1 -1
  124. package/dist/web/archive.js +3 -2
  125. package/dist/web/archive.js.map +1 -1
  126. package/dist/web/context.d.ts +25 -0
  127. package/dist/web/context.d.ts.map +1 -1
  128. package/dist/web/context.js +18 -0
  129. package/dist/web/context.js.map +1 -1
  130. package/dist/web/feed-atom.d.ts.map +1 -1
  131. package/dist/web/feed-atom.js +3 -1
  132. package/dist/web/feed-atom.js.map +1 -1
  133. package/dist/web/feed-item.d.ts +11 -2
  134. package/dist/web/feed-item.d.ts.map +1 -1
  135. package/dist/web/feed-item.js +9 -3
  136. package/dist/web/feed-item.js.map +1 -1
  137. package/dist/web/feed-json.d.ts +4 -0
  138. package/dist/web/feed-json.d.ts.map +1 -1
  139. package/dist/web/feed-json.js +4 -1
  140. package/dist/web/feed-json.js.map +1 -1
  141. package/dist/web/feed-rss.d.ts.map +1 -1
  142. package/dist/web/feed-rss.js +16 -3
  143. package/dist/web/feed-rss.js.map +1 -1
  144. package/dist/web/feed-source.d.ts +28 -0
  145. package/dist/web/feed-source.d.ts.map +1 -1
  146. package/dist/web/feed-source.js +31 -0
  147. package/dist/web/feed-source.js.map +1 -1
  148. package/dist/web/feed-xml.d.ts +2 -0
  149. package/dist/web/feed-xml.d.ts.map +1 -1
  150. package/dist/web/feed-xml.js +2 -0
  151. package/dist/web/feed-xml.js.map +1 -1
  152. package/dist/web/feeds.d.ts.map +1 -1
  153. package/dist/web/feeds.js +8 -1
  154. package/dist/web/feeds.js.map +1 -1
  155. package/dist/web/index.d.ts +3 -1
  156. package/dist/web/index.d.ts.map +1 -1
  157. package/dist/web/index.js +2 -1
  158. package/dist/web/index.js.map +1 -1
  159. package/dist/web/indexnow.d.ts +9 -0
  160. package/dist/web/indexnow.d.ts.map +1 -0
  161. package/dist/web/indexnow.js +27 -0
  162. package/dist/web/indexnow.js.map +1 -0
  163. package/dist/web/llms.d.ts +58 -0
  164. package/dist/web/llms.d.ts.map +1 -0
  165. package/dist/web/llms.js +91 -0
  166. package/dist/web/llms.js.map +1 -0
  167. package/dist/web/locale.d.ts +41 -0
  168. package/dist/web/locale.d.ts.map +1 -0
  169. package/dist/web/locale.js +56 -0
  170. package/dist/web/locale.js.map +1 -0
  171. package/dist/web/manifest.d.ts +50 -0
  172. package/dist/web/manifest.d.ts.map +1 -0
  173. package/dist/web/manifest.js +56 -0
  174. package/dist/web/manifest.js.map +1 -0
  175. package/dist/web/negotiate.d.ts.map +1 -1
  176. package/dist/web/negotiate.js +6 -0
  177. package/dist/web/negotiate.js.map +1 -1
  178. package/dist/web/render.d.ts +6 -1
  179. package/dist/web/render.d.ts.map +1 -1
  180. package/dist/web/render.js +68 -22
  181. package/dist/web/render.js.map +1 -1
  182. package/dist/web/robots.d.ts +89 -0
  183. package/dist/web/robots.d.ts.map +1 -0
  184. package/dist/web/robots.js +259 -0
  185. package/dist/web/robots.js.map +1 -0
  186. package/dist/web/routes.d.ts.map +1 -1
  187. package/dist/web/routes.js +105 -6
  188. package/dist/web/routes.js.map +1 -1
  189. package/dist/web/share-image.d.ts +51 -0
  190. package/dist/web/share-image.d.ts.map +1 -0
  191. package/dist/web/share-image.js +43 -0
  192. package/dist/web/share-image.js.map +1 -0
  193. package/dist/web/sitemap.d.ts +0 -26
  194. package/dist/web/sitemap.d.ts.map +1 -1
  195. package/dist/web/sitemap.js +0 -44
  196. package/dist/web/sitemap.js.map +1 -1
  197. package/dist/web/templates.d.ts +16 -9
  198. package/dist/web/templates.d.ts.map +1 -1
  199. package/dist/web/templates.js +83 -55
  200. package/dist/web/templates.js.map +1 -1
  201. package/dist/web/themes.d.ts +33 -0
  202. package/dist/web/themes.d.ts.map +1 -1
  203. package/dist/web/themes.js +57 -0
  204. package/dist/web/themes.js.map +1 -1
  205. package/dist/webmention/discovery.d.ts +4 -2
  206. package/dist/webmention/discovery.d.ts.map +1 -1
  207. package/dist/webmention/discovery.js +14 -8
  208. package/dist/webmention/discovery.js.map +1 -1
  209. package/dist/webmention/fetch-public.d.ts +2 -0
  210. package/dist/webmention/fetch-public.d.ts.map +1 -1
  211. package/dist/webmention/fetch-public.js +1 -1
  212. package/dist/webmention/fetch-public.js.map +1 -1
  213. package/package.json +7 -2
  214. package/themes/default/README.md +320 -139
  215. package/themes/default/layouts/404.njk +2 -0
  216. package/themes/default/layouts/500.njk +2 -0
  217. package/themes/default/layouts/503.njk +2 -0
  218. package/themes/default/layouts/author.njk +3 -1
  219. package/themes/default/layouts/base.njk +96 -17
  220. package/themes/default/layouts/category.njk +4 -2
  221. package/themes/default/layouts/front-page.njk +7 -2
  222. package/themes/default/layouts/home.njk +10 -0
  223. package/themes/default/layouts/page.njk +7 -4
  224. package/themes/default/layouts/post.njk +35 -25
  225. package/themes/default/layouts/search.njk +5 -9
  226. package/themes/default/layouts/tag.njk +7 -1
  227. package/themes/default/partials/archive.njk +3 -1
  228. package/themes/default/partials/bio.njk +9 -2
  229. package/themes/default/partials/breadcrumbs.njk +20 -0
  230. package/themes/default/partials/comment-form.njk +8 -0
  231. package/themes/default/partials/contact-form.njk +3 -0
  232. package/themes/default/partials/conversation.njk +14 -4
  233. package/themes/default/partials/jsonld.njk +19 -4
  234. package/themes/default/partials/kicker.njk +36 -0
  235. package/themes/default/partials/lang.njk +7 -0
  236. package/themes/default/partials/post-list.njk +43 -47
  237. package/themes/default/partials/reply-context.njk +14 -5
  238. package/themes/default/src/style.css +1109 -0
  239. package/themes/default/static/style.css +2124 -1160
  240. package/themes/default/theme.json +3 -1
@@ -0,0 +1,1109 @@
1
+ /*
2
+ * The default theme's stylesheet: Paper (doc-9, decision-22).
3
+ *
4
+ * The source. `pnpm build` compiles it with Tailwind v4 to `static/style.css`,
5
+ * which is what a page loads and what the package ships; that file is a build
6
+ * product and gitignored. Nothing about Tailwind reaches a reader: no script,
7
+ * no CDN, no web font, one stylesheet.
8
+ *
9
+ * One warm column at 42rem, serif throughout, sans reserved for small labels.
10
+ * Only an article has a headline; every other kind of post is its words under
11
+ * a small-caps kicker that names the kind. Dark mode follows the system.
12
+ *
13
+ * The look lives here, on the theme's semantic class names, through `@apply`:
14
+ * the templates carry no utility classes, so a site theme that has CSS for the
15
+ * class names keeps working, and one that wants to restyle a single thing
16
+ * overrides a custom property or a class.
17
+ *
18
+ * The `--color-*` custom properties below are the override contract (see the
19
+ * theme README, "Colours"). `src/web/theme-colors.test.ts` reads them out of
20
+ * the compiled file and computes the WCAG 2.2 ratio of every pair the design
21
+ * puts on screen: every run of text at 7:1, every border a reader relies on at
22
+ * 3:1, in both schemes. `--color-rule` is the one exception, a decorative
23
+ * hairline nothing a reader needs is drawn with.
24
+ */
25
+ @import 'tailwindcss' source(none);
26
+ @source '../layouts';
27
+ @source '../partials';
28
+
29
+ :root {
30
+ /* Light and dark are both drawn below, so a browser may paint its own form
31
+ controls, scrollbars and spellcheck marks to match. */
32
+ color-scheme: light dark;
33
+
34
+ /* `body` is the paper and `text` the ink. `muted` is every small line of
35
+ sans and every quote; `primary` is links, the kind a kicker names and the
36
+ citation rule; `secondary` is tags and the focus outline. `rule` is a
37
+ decorative hairline and `edge` is a border somebody relies on to find a
38
+ field or a card. `base` is the sunk surface of a field or a code block;
39
+ `base-2` and `base-3` are the cooler and the raised ones. */
40
+ --color-body: #faf7f2;
41
+ --color-text: #1c1917;
42
+ --color-muted: #534c46;
43
+ --color-primary: #8c2f1f;
44
+ --color-secondary: #2b5580;
45
+ --color-rule: #e4dccf;
46
+ --color-edge: #8a867e;
47
+ --color-base: #f3eee5;
48
+ --color-base-2: #f5f6f7;
49
+ --color-base-3: #ffffff;
50
+ --color-code-background: #f3eee5;
51
+ --color-code-text: #1c1917;
52
+ --color-error: #9c1f18;
53
+
54
+ /* Syntax highlighting: the Tomorrow palette, light member (TASK-86). The
55
+ names are what each colour marks up, and the base16 slot they come from is
56
+ in the comment; the highlighter is highlight.js, whose base16 Tomorrow
57
+ theme is the mapping these follow.
58
+
59
+ None of them is the stock colour. Tomorrow was drawn for an editor, where
60
+ a faint comment is a feature; here code is text like any other and meets
61
+ 7:1 on the block, so each colour was darkened along its own hue until it
62
+ did and no further. */
63
+ --color-code-comment: #4e4f4d; /* base03, stock #8e908c */
64
+ --color-code-tag: #4e4f4e; /* base04, stock #969896 */
65
+ --color-code-name: #991f1f; /* base08, stock #c82829 */
66
+ --color-code-literal: #77420f; /* base09, stock #f5871f */
67
+ --color-code-class: #624c00; /* base0A, stock #eab700 */
68
+ --color-code-string: #455500; /* base0B, stock #718c00 */
69
+ --color-code-support: #23565a; /* base0C, stock #3e999f */
70
+ --color-code-function: #2f507c; /* base0D, stock #4271ae */
71
+ --color-code-keyword: #63417a; /* base0E, stock #8959a8 */
72
+ --color-code-meta: #6d453c; /* base0F, stock #a3685a */
73
+ --color-code-added-background: #dcedd3;
74
+ --color-code-removed-background: #f5d8d6;
75
+ }
76
+
77
+ @media (prefers-color-scheme: dark) {
78
+ :root {
79
+ --color-body: #171412;
80
+ --color-text: #ece6de;
81
+ --color-muted: #b2aaa0;
82
+ --color-primary: #ee9370;
83
+ --color-secondary: #86b8e2;
84
+ --color-rule: #3a332e;
85
+ --color-edge: #706a67;
86
+ --color-base: #221d1a;
87
+ --color-base-2: #222124;
88
+ --color-base-3: #2d2421;
89
+ --color-code-background: #221d1a;
90
+ --color-code-text: #ece6de;
91
+ --color-error: #ff8f9f;
92
+
93
+ /* The other member of the family, Tomorrow Night, the palette highlight.js
94
+ ships under that name. Four of its colours were lightened toward white,
95
+ each until it met 7:1 on the block. */
96
+ --color-code-comment: #a9a9a9; /* base03, stock #999999 */
97
+ --color-code-tag: #b4b7b4; /* base04 */
98
+ --color-code-name: #f48b8d; /* base08, stock #f2777a */
99
+ --color-code-literal: #f99157; /* base09 */
100
+ --color-code-class: #ffcc66; /* base0A */
101
+ --color-code-string: #99cc99; /* base0B */
102
+ --color-code-support: #66cccc; /* base0C */
103
+ --color-code-function: #83add6; /* base0D, stock #6699cc */
104
+ --color-code-keyword: #cc99cc; /* base0E */
105
+ --color-code-meta: #c6a198; /* base0F, stock #a3685a */
106
+ --color-code-added-background: #203622;
107
+ --color-code-removed-background: #401e1e;
108
+ }
109
+ }
110
+
111
+ /* Tailwind's utilities, drawn from the tokens above rather than its own
112
+ palette, which is cleared so a utility can only name a colour of this
113
+ design. `inline` puts the `var()` in the utility, so the scheme the custom
114
+ properties flip under flips every utility with them. */
115
+ @theme inline reference {
116
+ --color-*: initial;
117
+ --color-paper: var(--color-body);
118
+ --color-ink: var(--color-text);
119
+ --color-muted: var(--color-muted);
120
+ --color-accent: var(--color-primary);
121
+ --color-accent-2: var(--color-secondary);
122
+ --color-rule: var(--color-rule);
123
+ --color-edge: var(--color-edge);
124
+ --color-surface: var(--color-base);
125
+ --color-code: var(--color-code-background);
126
+ --color-error: var(--color-error);
127
+ --font-serif: 'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', Georgia, serif;
128
+ --font-sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
129
+ --font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
130
+ }
131
+
132
+ /* A link or control that stands on its own, rather than inside a sentence, is
133
+ at least 24 by 24 CSS pixels (WCAG 2.5.8, TASK-145). `inline-flex` grows the
134
+ box without moving the words off the baseline they share with their line.
135
+ Pixels rather than a spacing step, because 24px is the rule's number and the
136
+ root here is 18px. */
137
+ @utility target {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ min-block-size: 24px;
141
+ min-inline-size: 24px;
142
+ }
143
+
144
+ /* A link outside prose still says it is a link at rest, with a thin underline
145
+ in its own colour; hover and focus thicken it and turn it accent, so the
146
+ change is never colour alone (WCAG 1.4.1). */
147
+ @utility quiet-link {
148
+ @apply underline decoration-current/50 decoration-1 underline-offset-4 hover:text-accent hover:decoration-accent hover:decoration-2 focus-visible:decoration-2;
149
+ }
150
+
151
+ @layer base {
152
+ /* `scrollbar-gutter` keeps the scrollbar's room whether or not the page is
153
+ long enough to scroll, so moving between a short page and a long one does
154
+ not shift the column sideways. */
155
+ html {
156
+ font-size: 18px;
157
+ -webkit-text-size-adjust: 100%;
158
+ text-size-adjust: 100%;
159
+ scrollbar-gutter: stable;
160
+ }
161
+
162
+ body {
163
+ @apply bg-paper font-serif leading-relaxed text-ink antialiased;
164
+ }
165
+
166
+ h1,
167
+ h2,
168
+ h3,
169
+ h4,
170
+ h5,
171
+ h6 {
172
+ @apply font-bold tracking-tight;
173
+ text-wrap: balance;
174
+ }
175
+
176
+ /* Prose wraps `pretty`, which keeps a paragraph from ending on one short
177
+ word; headings wrap `balance`, which evens their lines out. */
178
+ p,
179
+ li,
180
+ dd,
181
+ blockquote,
182
+ figcaption {
183
+ text-wrap: pretty;
184
+ }
185
+
186
+ a {
187
+ @apply text-accent underline decoration-accent/40 underline-offset-[3px] motion-safe:transition-colors;
188
+ }
189
+
190
+ a:hover {
191
+ @apply decoration-accent;
192
+ }
193
+
194
+ /* One focus ring for everything that takes focus: a link, a control, a code
195
+ block that scrolls. Nothing here removes it, and a forced palette redraws
196
+ an outline in its own colour. */
197
+ :focus-visible {
198
+ @apply rounded-xs outline-2 outline-offset-2 outline-accent-2;
199
+ }
200
+
201
+ hr {
202
+ @apply my-10 border-0 border-t border-rule;
203
+ }
204
+
205
+ ::selection {
206
+ @apply bg-accent/20;
207
+ }
208
+
209
+ code,
210
+ kbd,
211
+ samp,
212
+ pre {
213
+ @apply font-mono;
214
+ }
215
+
216
+ strong,
217
+ b {
218
+ @apply font-bold;
219
+ }
220
+ }
221
+
222
+ @layer components {
223
+ /* Screen reader text. The skip link is this until it is focused, and then it
224
+ is a box at the top of the page. */
225
+ .screen-reader-text {
226
+ position: absolute;
227
+ width: 1px;
228
+ height: 1px;
229
+ margin: -1px;
230
+ padding: 0;
231
+ overflow: hidden;
232
+ border: 0;
233
+ clip-path: inset(50%);
234
+ white-space: nowrap;
235
+ }
236
+
237
+ .screen-reader-text:focus {
238
+ @apply z-50 m-0 block h-auto w-auto bg-(--color-base-3) p-4 font-sans font-bold text-ink no-underline;
239
+ inset-block-start: 0.5rem;
240
+ inset-inline-start: 0.5rem;
241
+ clip-path: none;
242
+ }
243
+
244
+ /* The page shell. One wrapper at the measure; nothing is full-bleed. */
245
+ .global-wrapper {
246
+ @apply mx-auto max-w-2xl px-5 pt-8 pb-16 sm:px-6;
247
+ }
248
+
249
+ /* The header is two shapes. At the root: the site title, the tagline and
250
+ the site menu as a line of its own under a hairline. Everywhere else: the
251
+ small link home with the menu beside it on one line. */
252
+ .global-header {
253
+ @apply mb-12;
254
+ }
255
+
256
+ .global-wrapper:not([data-is-root-path='true']) .global-header {
257
+ display: flex;
258
+ @apply flex-wrap items-baseline gap-x-6 gap-y-2;
259
+ }
260
+
261
+ .global-wrapper[data-is-root-path='true'] .global-header {
262
+ @apply mb-14;
263
+ }
264
+
265
+ .main-heading {
266
+ @apply text-4xl leading-none sm:text-5xl;
267
+ }
268
+
269
+ .main-heading a,
270
+ .header-link-home {
271
+ @apply text-ink no-underline hover:text-accent hover:underline hover:decoration-2 hover:underline-offset-[6px] focus-visible:underline focus-visible:decoration-2;
272
+ }
273
+
274
+ .header-link-home {
275
+ @apply target text-lg font-bold;
276
+ }
277
+
278
+ .site-tagline {
279
+ @apply mt-3 text-lg text-muted italic;
280
+ }
281
+
282
+ .hlist {
283
+ @apply flex list-none flex-wrap p-0;
284
+ }
285
+
286
+ .site-nav ul {
287
+ @apply gap-x-5 gap-y-1 font-sans text-[0.78rem] tracking-[0.12em] uppercase;
288
+ }
289
+
290
+ .site-nav a {
291
+ @apply target quiet-link text-muted;
292
+ }
293
+
294
+ .site-nav a[aria-current='page'] {
295
+ @apply font-semibold text-ink;
296
+ }
297
+
298
+ .global-wrapper[data-is-root-path='true'] .global-header .site-nav {
299
+ @apply mt-6 border-t border-rule pt-4;
300
+ }
301
+
302
+ /* The footer: the copyright and the colophon on one side, the footer menu
303
+ on the other, wrapping on a narrow screen. */
304
+ .global-wrapper > footer {
305
+ @apply mt-20 flex flex-wrap items-center justify-between gap-4 border-t border-rule pt-6 font-sans text-sm text-muted;
306
+ }
307
+
308
+ .global-wrapper > footer .site-nav ul {
309
+ @apply gap-x-4 text-sm tracking-normal normal-case;
310
+ }
311
+
312
+ /* The kicker: what a thing is, said once, small, above it. The kind word is
313
+ the accent; the date and the categories after it are quiet links. */
314
+ .kicker {
315
+ @apply mb-2 flex flex-wrap items-center gap-x-2 font-sans text-[0.72rem] tracking-[0.14em] text-muted uppercase;
316
+ }
317
+
318
+ .kicker a {
319
+ @apply target quiet-link text-muted;
320
+ }
321
+
322
+ .kicker-kind {
323
+ @apply font-semibold text-accent;
324
+ }
325
+
326
+ /* The breadcrumb: where the page sits, on one quiet line above it. The
327
+ crumbs are links like the kicker's; the last is the page itself. */
328
+ .breadcrumbs ol {
329
+ @apply mb-6 gap-x-2 font-sans text-sm text-muted;
330
+ }
331
+
332
+ .breadcrumbs a {
333
+ @apply target quiet-link text-muted;
334
+ }
335
+
336
+ .breadcrumbs-sep {
337
+ @apply me-2;
338
+ }
339
+
340
+ /* Titles: what heads a listing, an archive, search and an error page. */
341
+ .page-title {
342
+ @apply mt-1 mb-8 text-4xl leading-[1.08] sm:text-[2.75rem];
343
+ }
344
+
345
+ .section-title {
346
+ @apply mt-14 mb-6 font-sans text-[0.72rem] font-semibold tracking-[0.14em] text-muted uppercase;
347
+ }
348
+
349
+ .empty {
350
+ @apply text-muted italic;
351
+ }
352
+
353
+ /* Prose: whatever a document's words render into. The entry's `e-content`,
354
+ a posts page's and a front page's `page-body`, a note in a feed, a
355
+ comment. Each context sets its own size; these are the shapes. */
356
+ .e-content > * + *,
357
+ .page-body > * + * {
358
+ @apply mt-5;
359
+ }
360
+
361
+ :is(.e-content, .page-body) :is(h2, h3, h4, h5, h6) {
362
+ @apply mb-3;
363
+ }
364
+
365
+ :is(.e-content, .page-body) h2 {
366
+ @apply mt-10 text-2xl;
367
+ }
368
+
369
+ :is(.e-content, .page-body) h3 {
370
+ @apply mt-8 text-xl;
371
+ }
372
+
373
+ :is(.e-content, .page-body) :is(h4, h5, h6) {
374
+ @apply mt-6 text-lg;
375
+ }
376
+
377
+ :is(.e-content, .page-body) ul {
378
+ @apply list-disc ps-6;
379
+ }
380
+
381
+ :is(.e-content, .page-body) ol {
382
+ @apply list-decimal ps-6;
383
+ }
384
+
385
+ :is(.e-content, .page-body) li + li,
386
+ :is(.e-content, .page-body) li > :is(ul, ol) {
387
+ @apply mt-2;
388
+ }
389
+
390
+ :is(.e-content, .page-body) li > p + p {
391
+ @apply mt-2;
392
+ }
393
+
394
+ :is(.e-content, .page-body) blockquote {
395
+ @apply my-8 border-s-2 border-accent ps-5 text-xl leading-snug text-muted italic;
396
+ }
397
+
398
+ :is(.e-content, .page-body) blockquote > * + * {
399
+ @apply mt-3;
400
+ }
401
+
402
+ :is(.e-content, .page-body) img,
403
+ :is(.e-content, .page-body) video {
404
+ @apply h-auto max-w-full rounded-md;
405
+ }
406
+
407
+ :is(.e-content, .page-body) figcaption {
408
+ @apply mt-2 font-sans text-sm text-muted;
409
+ }
410
+
411
+ :is(.e-content, .page-body) hr {
412
+ @apply my-10;
413
+ }
414
+
415
+ :is(.e-content, .page-body) table {
416
+ @apply w-full border-collapse font-sans text-sm;
417
+ }
418
+
419
+ :is(.e-content, .page-body) :is(th, td) {
420
+ @apply border-b border-rule px-2 py-1.5 text-start align-top;
421
+ }
422
+
423
+ :is(.e-content, .page-body) th {
424
+ @apply border-edge font-semibold;
425
+ }
426
+
427
+ :is(.e-content, .page-body) dt {
428
+ @apply font-bold;
429
+ }
430
+
431
+ :is(.e-content, .page-body) dd {
432
+ @apply ps-6;
433
+ }
434
+
435
+ :is(.e-content, .page-body) details summary {
436
+ @apply cursor-pointer;
437
+ }
438
+
439
+ :is(.e-content, .page-body) kbd {
440
+ @apply rounded-sm border border-edge px-1 text-[0.85em];
441
+ }
442
+
443
+ /* Code. A file path or an identifier is one long word, and a narrow screen
444
+ would otherwise carry it off the side of the page; a block of code
445
+ scrolls instead, and is focusable (the CMS renders it `tabindex="0"`) so
446
+ a keyboard can scroll it too. */
447
+ code {
448
+ @apply rounded-sm bg-code px-1.5 py-0.5 text-[0.85em] text-(--color-code-text);
449
+ overflow-wrap: break-word;
450
+ }
451
+
452
+ pre {
453
+ @apply overflow-x-auto rounded-md border border-edge bg-code p-4 text-[0.85rem] leading-relaxed text-(--color-code-text);
454
+ }
455
+
456
+ pre code {
457
+ @apply rounded-none bg-transparent p-0 text-[length:inherit];
458
+ overflow-wrap: normal;
459
+ }
460
+
461
+ .footnotes {
462
+ @apply mt-10 border-t border-rule pt-6 font-sans text-sm;
463
+ }
464
+
465
+ .footnotes-sep {
466
+ @apply hidden;
467
+ }
468
+
469
+ .footnote-item > p {
470
+ @apply mt-0;
471
+ }
472
+
473
+ /* Syntax highlighting (TASK-86).
474
+ *
475
+ * The CMS renders a fenced block as `pre > code.language-x` and highlights
476
+ * nothing; `static/highlight.js` marks the tokens up on the client, and
477
+ * these are its colours. The scopes and the slots they map to are
478
+ * highlight.js's own base16 Tomorrow theme, so a token it invents later
479
+ * lands somewhere sensible rather than nowhere.
480
+ *
481
+ * Every colour is a custom property, so a site that wants another palette
482
+ * redefines a dozen tokens instead of writing a syntax theme. Delimiters,
483
+ * operators and substitutions are deliberately the block's own ink: they
484
+ * are most of the characters on screen and a colour there is noise.
485
+ */
486
+ .hljs-comment {
487
+ color: var(--color-code-comment);
488
+ }
489
+
490
+ .hljs-tag {
491
+ color: var(--color-code-tag);
492
+ }
493
+
494
+ .hljs-subst,
495
+ .hljs-punctuation,
496
+ .hljs-operator {
497
+ color: var(--color-code-text);
498
+ }
499
+
500
+ .hljs-bullet,
501
+ .hljs-variable,
502
+ .hljs-template-variable,
503
+ .hljs-selector-tag,
504
+ .hljs-name {
505
+ color: var(--color-code-name);
506
+ }
507
+
508
+ .hljs-symbol,
509
+ .hljs-number,
510
+ .hljs-link,
511
+ .hljs-attr,
512
+ .hljs-variable.constant_,
513
+ .hljs-literal {
514
+ color: var(--color-code-literal);
515
+ }
516
+
517
+ .hljs-title,
518
+ .hljs-class .hljs-title,
519
+ .hljs-title.class_ {
520
+ color: var(--color-code-class);
521
+ }
522
+
523
+ .hljs-strong {
524
+ color: var(--color-code-class);
525
+ font-weight: 700;
526
+ }
527
+
528
+ .hljs-code,
529
+ .hljs-string,
530
+ .hljs-title.class_.inherited__ {
531
+ color: var(--color-code-string);
532
+ }
533
+
534
+ .hljs-built_in,
535
+ .hljs-doctag,
536
+ .hljs-quote,
537
+ .hljs-regexp,
538
+ .hljs-keyword.hljs-atrule {
539
+ color: var(--color-code-support);
540
+ }
541
+
542
+ .hljs-attribute,
543
+ .hljs-function .hljs-title,
544
+ .hljs-section,
545
+ .hljs-title.function_ {
546
+ color: var(--color-code-function);
547
+ }
548
+
549
+ .hljs-keyword,
550
+ .hljs-template-tag,
551
+ .hljs-type {
552
+ color: var(--color-code-keyword);
553
+ }
554
+
555
+ .hljs-emphasis {
556
+ color: var(--color-code-keyword);
557
+ font-style: italic;
558
+ }
559
+
560
+ .hljs-meta,
561
+ .hljs-meta .hljs-keyword,
562
+ .hljs-meta .hljs-string {
563
+ color: var(--color-code-meta);
564
+ }
565
+
566
+ /* A diff reads by the line, not by the token: the whole line takes a tint
567
+ out to the edges of the block (the negative margin cancels the `pre`
568
+ padding and the padding puts it back) and keeps the block's own ink,
569
+ which is the pair the contrast test checks. The `+` and the `-` stay in
570
+ the text, so the meaning does not rest on the colour alone. */
571
+ .hljs-addition,
572
+ .hljs-deletion {
573
+ display: block;
574
+ margin-inline: -1rem;
575
+ padding-inline: 1rem;
576
+ color: var(--color-code-text);
577
+ text-decoration: none;
578
+ }
579
+
580
+ .hljs-addition {
581
+ background-color: var(--color-code-added-background);
582
+ }
583
+
584
+ .hljs-deletion {
585
+ background-color: var(--color-code-removed-background);
586
+ }
587
+
588
+ /* A listing: `h-feed` of `h-entry`, hairlines between. An article is the
589
+ only entry with a headline; a note or a reply is its words. */
590
+ .feed {
591
+ @apply mt-2;
592
+ }
593
+
594
+ .feed-separator {
595
+ @apply my-9;
596
+ }
597
+
598
+ .feed-title {
599
+ @apply mt-1 mb-3 text-3xl leading-tight;
600
+ }
601
+
602
+ .feed-title a {
603
+ @apply text-ink no-underline hover:text-accent hover:underline hover:decoration-2 hover:underline-offset-[6px] focus-visible:underline focus-visible:decoration-2;
604
+ }
605
+
606
+ .feed-excerpt.p-summary {
607
+ @apply text-[1.05rem];
608
+ }
609
+
610
+ .feed-item > .e-content {
611
+ @apply text-[1.12rem] leading-[1.6];
612
+ }
613
+
614
+ .feed-more,
615
+ .front-links {
616
+ @apply mt-3 font-sans text-sm;
617
+ }
618
+
619
+ .feed-more a,
620
+ .front-links a {
621
+ @apply target gap-1 hover:decoration-2;
622
+ }
623
+
624
+ .front-links {
625
+ @apply mt-10 flex flex-wrap gap-x-6;
626
+ }
627
+
628
+ /* A citation: what a reply answers. A rule on the start side, a sans line
629
+ naming it, and its words as an italic quote. */
630
+ .cite {
631
+ @apply my-3 border-s-2 border-accent ps-4;
632
+ }
633
+
634
+ .cite-line {
635
+ @apply font-sans text-sm text-muted;
636
+ }
637
+
638
+ .cite-line a {
639
+ @apply target quiet-link font-medium text-ink;
640
+ }
641
+
642
+ .cite-quote {
643
+ @apply mt-1 text-[0.98rem] leading-snug text-muted italic;
644
+ }
645
+
646
+ /* One post or page: `article.blog-post.h-entry`. */
647
+ .post-header {
648
+ @apply mb-8;
649
+ }
650
+
651
+ .post-header h1 {
652
+ @apply mt-1 text-4xl leading-[1.08] sm:text-[2.75rem];
653
+ }
654
+
655
+ .post-deck {
656
+ @apply mt-4 text-xl leading-snug text-muted italic;
657
+ }
658
+
659
+ .blog-post > .e-content {
660
+ @apply text-[1.08rem] leading-[1.65];
661
+ }
662
+
663
+ /* An untitled post is its words, set larger. It is the one entry whose
664
+ heading is the hidden one TASK-144 gave it. */
665
+ .blog-post > .screen-reader-text ~ .e-content {
666
+ @apply text-[1.3rem] leading-[1.55];
667
+ }
668
+
669
+ .entry-meta,
670
+ .page-meta {
671
+ @apply mt-8 flex flex-wrap items-center gap-x-2 font-sans text-sm text-muted;
672
+ }
673
+
674
+ /* A page usually carries no date, and then this line has nothing in it. */
675
+ .page-meta:empty {
676
+ @apply hidden;
677
+ }
678
+
679
+ .entry-meta a,
680
+ .page-meta a {
681
+ @apply target quiet-link text-muted;
682
+ }
683
+
684
+ /* The tags under an entry. The `#` is drawn rather than printed, so the
685
+ `p-category` a parser reads is the tag and not the hash. */
686
+ .post-categories {
687
+ @apply mt-2 flex flex-wrap gap-x-3 font-sans text-sm;
688
+ }
689
+
690
+ .post-categories a {
691
+ @apply target quiet-link text-accent-2;
692
+ }
693
+
694
+ .post-categories a[rel='tag']::before {
695
+ content: '#';
696
+ }
697
+
698
+ .blog-post > hr {
699
+ @apply mt-10 mb-8;
700
+ }
701
+
702
+ /* The posts either side of this one, as two cards. */
703
+ .blog-post-nav {
704
+ @apply my-12 font-sans text-sm;
705
+ }
706
+
707
+ .blog-post-nav ul {
708
+ @apply grid list-none gap-4 p-0 sm:grid-cols-2;
709
+ }
710
+
711
+ .blog-post-nav li:has(> a[rel='next']) {
712
+ @apply sm:col-start-2;
713
+ }
714
+
715
+ .blog-post-nav a {
716
+ @apply target h-full w-full flex-col items-start rounded-md border border-edge p-4 text-ink no-underline hover:border-2 hover:border-accent hover:p-[calc(1rem-1px)] hover:text-accent;
717
+ }
718
+
719
+ .blog-post-nav a[rel='next'] {
720
+ @apply sm:items-end sm:text-end;
721
+ }
722
+
723
+ .blog-post-nav-label {
724
+ @apply mb-1 block text-[0.7rem] tracking-[0.14em] text-muted uppercase;
725
+ }
726
+
727
+ /* The bio: a round photo and the person beside it. */
728
+ .bio {
729
+ @apply flex items-start gap-4;
730
+ }
731
+
732
+ .bio-avatar {
733
+ @apply shrink-0;
734
+ }
735
+
736
+ .bio-avatar img {
737
+ @apply size-14 rounded-full object-cover;
738
+ }
739
+
740
+ .bio .p-name {
741
+ @apply font-bold text-ink;
742
+ }
743
+
744
+ .bio-note {
745
+ @apply mt-1 text-[0.95rem] text-muted;
746
+ }
747
+
748
+ .bio-links {
749
+ @apply mt-1 gap-x-4 font-sans text-sm;
750
+ }
751
+
752
+ .bio-links a {
753
+ @apply target;
754
+ }
755
+
756
+ .global-wrapper[data-is-root-path='true'] .bio {
757
+ @apply mt-2;
758
+ }
759
+
760
+ /* An archive's header: the kicker naming what it is, then its name. */
761
+ .archive-header .page-title {
762
+ @apply mb-4;
763
+ }
764
+
765
+ .archive-header {
766
+ @apply mb-10;
767
+ }
768
+
769
+ .category-description {
770
+ @apply text-lg text-muted italic;
771
+ }
772
+
773
+ .author-header + .bio {
774
+ @apply mb-12;
775
+ }
776
+
777
+ /* The archive page: every post there has been, month by month. */
778
+ .archive h2 {
779
+ @apply mt-10 mb-3 text-2xl;
780
+ }
781
+
782
+ .archive ol {
783
+ @apply list-none space-y-1 p-0;
784
+ }
785
+
786
+ .archive a {
787
+ @apply target;
788
+ }
789
+
790
+ /* Pagination: two words, the newer at the start and the older at the end. */
791
+ .pagination {
792
+ @apply mt-12 flex flex-wrap gap-4 font-sans text-sm;
793
+ }
794
+
795
+ .pagination a {
796
+ @apply target quiet-link gap-1;
797
+ }
798
+
799
+ .pagination-next {
800
+ @apply ms-auto;
801
+ }
802
+
803
+ /* Search: the box, what it found, and each result as a feed entry. */
804
+ .search-form {
805
+ @apply mb-8 flex gap-2;
806
+ }
807
+
808
+ .search-form label {
809
+ @apply flex-1;
810
+ }
811
+
812
+ .search-summary {
813
+ @apply mb-8 font-sans text-sm text-muted;
814
+ }
815
+
816
+ .search-result + .search-result {
817
+ @apply mt-9 border-t border-rule pt-9;
818
+ }
819
+
820
+ .search-result .feed-title {
821
+ @apply text-2xl;
822
+ }
823
+
824
+ .search-snippet mark {
825
+ @apply rounded-xs bg-accent/20 px-0.5 text-ink;
826
+ }
827
+
828
+ /* The error pages. */
829
+ :is(.not-found, .server-error, .maintenance) > p {
830
+ @apply text-[1.08rem];
831
+ }
832
+
833
+ /* Reactions: the likes, the boosts and the pages that mentioned a post, as
834
+ rows of faces under a small label. */
835
+ .reactions-section {
836
+ @apply mt-14 mb-10 flex flex-wrap gap-x-10 gap-y-4;
837
+ }
838
+
839
+ .reaction-group {
840
+ @apply min-w-0;
841
+ }
842
+
843
+ .reaction-title {
844
+ @apply mb-2 font-sans text-[0.72rem] font-normal tracking-[0.14em] text-muted uppercase;
845
+ }
846
+
847
+ .reaction-count {
848
+ @apply ms-1 font-semibold text-ink;
849
+ }
850
+
851
+ .facepile {
852
+ @apply flex flex-wrap items-center;
853
+ }
854
+
855
+ .facepile > a,
856
+ .facepile > span {
857
+ @apply relative -ms-2 rounded-full ring-2 ring-paper first:ms-0 hover:z-10 motion-safe:transition-transform motion-safe:hover:scale-110;
858
+ }
859
+
860
+ .facepile img {
861
+ @apply size-8 rounded-full;
862
+ }
863
+
864
+ /* A reaction with no avatar has nothing to recognise anybody by, so the
865
+ emoji of its kind and the name stand in, as a pill. */
866
+ .facepile > :has(.reaction-name) {
867
+ @apply target ms-1 gap-1 bg-surface px-2 font-sans text-sm text-ink no-underline ring-0 first:ms-0;
868
+ }
869
+
870
+ .reaction-icon {
871
+ @apply text-xs;
872
+ }
873
+
874
+ /* The thread: every reply from every door, each one an h-entry. */
875
+ .comments-area {
876
+ @apply mt-14;
877
+ }
878
+
879
+ .comments-title {
880
+ @apply mb-5 text-xl;
881
+ }
882
+
883
+ .comment-list {
884
+ @apply m-0 list-none space-y-8 p-0;
885
+ }
886
+
887
+ .comment-list .children {
888
+ @apply mt-6 list-none space-y-6 border-s border-rule ps-6 sm:ps-12;
889
+ }
890
+
891
+ .comment-body {
892
+ @apply flex flex-col gap-2;
893
+ }
894
+
895
+ .comment-meta {
896
+ @apply flex flex-wrap items-center gap-x-3 gap-y-1 font-sans text-sm;
897
+ }
898
+
899
+ .comment-author {
900
+ @apply flex items-center gap-2;
901
+ }
902
+
903
+ .comment-author .avatar,
904
+ .comment-avatar-empty {
905
+ @apply size-8 shrink-0 rounded-full;
906
+ }
907
+
908
+ .comment-avatar-empty {
909
+ @apply inline-flex items-center justify-center bg-surface font-semibold text-muted;
910
+ }
911
+
912
+ .comment-author a {
913
+ @apply target quiet-link text-ink;
914
+ }
915
+
916
+ .comment-metadata a {
917
+ @apply target quiet-link text-muted;
918
+ }
919
+
920
+ .comment-metadata {
921
+ @apply text-muted;
922
+ }
923
+
924
+ .comment-source {
925
+ @apply text-muted italic;
926
+ }
927
+
928
+ .comment-content {
929
+ @apply text-[1.02rem];
930
+ }
931
+
932
+ .reply {
933
+ @apply font-sans text-sm;
934
+ }
935
+
936
+ .reply a {
937
+ @apply target;
938
+ }
939
+
940
+ .no-comments {
941
+ @apply mt-8 font-sans text-sm text-muted italic;
942
+ }
943
+
944
+ /* The comment form under an open post and the contact form on a page that
945
+ asked for one, which wears the same class. */
946
+ .comment-respond {
947
+ @apply mt-12 border-t border-rule pt-10;
948
+ }
949
+
950
+ .comment-reply-title,
951
+ .contact-form-title {
952
+ @apply mb-5 text-xl;
953
+ }
954
+
955
+ .comment-help {
956
+ @apply -mt-3 mb-5 text-[0.95rem] text-muted;
957
+ }
958
+
959
+ .comment-respond form {
960
+ @apply flex flex-col gap-4;
961
+ }
962
+
963
+ .field-row {
964
+ @apply grid gap-x-4 gap-y-4 sm:grid-cols-2;
965
+ }
966
+
967
+ .comment-respond label {
968
+ @apply mb-1 block font-sans text-sm;
969
+ }
970
+
971
+ .comment-optional,
972
+ .contact-optional,
973
+ .comment-hint,
974
+ .contact-hint {
975
+ @apply text-muted;
976
+ }
977
+
978
+ .comment-hint,
979
+ .contact-hint,
980
+ .comment-field-error,
981
+ .contact-field-error {
982
+ @apply mt-1 block font-sans text-sm;
983
+ }
984
+
985
+ .comment-field-error,
986
+ .contact-field-error {
987
+ @apply text-error;
988
+ }
989
+
990
+ :is(.comment-respond, .search-form) :is(input:not([type='checkbox'], [type='hidden']), textarea) {
991
+ @apply block w-full rounded-md border border-edge bg-surface px-3 py-2 font-serif text-base text-ink;
992
+ }
993
+
994
+ /* A field the reader has left invalid, or one a refused submission marked,
995
+ says so with a heavier border in the error colour, without JavaScript. */
996
+ :is(.comment-respond, .search-form)
997
+ :is(input, textarea):is(:user-invalid, [aria-invalid='true']) {
998
+ @apply border-2 border-error px-[calc(0.75rem-1px)] py-[calc(0.5rem-1px)];
999
+ }
1000
+
1001
+ .comment-check {
1002
+ @apply flex items-center gap-2;
1003
+ }
1004
+
1005
+ .comment-check label {
1006
+ @apply mb-0;
1007
+ }
1008
+
1009
+ .comment-respond input[type='checkbox'] {
1010
+ inline-size: 24px;
1011
+ block-size: 24px;
1012
+ margin: 0;
1013
+ accent-color: var(--color-primary);
1014
+ }
1015
+
1016
+ .search-form button,
1017
+ .comment-respond .form-submit button,
1018
+ .comment-respond .form-submit input[type='submit'] {
1019
+ @apply cursor-pointer rounded-md bg-accent px-5 py-2.5 font-sans text-sm font-semibold text-paper hover:bg-ink;
1020
+ min-block-size: 24px;
1021
+ min-inline-size: 24px;
1022
+ }
1023
+
1024
+ .comment-signed-in,
1025
+ .comment-replying-to {
1026
+ @apply font-sans text-sm text-muted;
1027
+ }
1028
+
1029
+ .comment-signed-in a,
1030
+ .comment-replying-to a {
1031
+ @apply text-accent;
1032
+ }
1033
+
1034
+ .comment-notice,
1035
+ .contact-notice {
1036
+ @apply rounded-md border border-edge bg-surface p-4 font-sans text-sm text-ink;
1037
+ }
1038
+
1039
+ /* A refused form's summary: what went wrong as its heading, then a link to
1040
+ each field to put right. */
1041
+ .comment-error,
1042
+ .contact-error {
1043
+ @apply my-6 border-s-2 border-error ps-4 font-sans text-sm;
1044
+ }
1045
+
1046
+ .comment-error h3,
1047
+ .contact-error h3 {
1048
+ @apply font-sans text-base font-semibold text-error;
1049
+ }
1050
+
1051
+ .comment-error ul,
1052
+ .contact-error ul {
1053
+ @apply mt-2 list-disc ps-5;
1054
+ }
1055
+
1056
+ .comment-error a,
1057
+ .contact-error a {
1058
+ @apply target;
1059
+ }
1060
+
1061
+ /* The honeypot. Taken out of the flow rather than `display: none`, because
1062
+ some form fillers skip fields a browser would not paint at all and this
1063
+ one is meant to be filled in. */
1064
+ .comment-trap,
1065
+ .contact-trap {
1066
+ position: absolute;
1067
+ inset-inline-start: -10000px;
1068
+ width: 1px;
1069
+ height: 1px;
1070
+ overflow: hidden;
1071
+ }
1072
+ }
1073
+
1074
+ /* Forced colours: Windows High Contrast and the like, where the reader's own
1075
+ palette replaces every colour here. Borders and outlines survive that and
1076
+ backgrounds do not, so anything drawn by a background alone takes a border
1077
+ in a system colour: the buttons, a hairline, code, a matched word and the
1078
+ face that is a pill. Focus needs nothing: every ring here is an outline,
1079
+ which the forced palette redraws in its own colour. */
1080
+ @media (forced-colors: active) {
1081
+ .search-form button,
1082
+ .comment-respond .form-submit button,
1083
+ .comment-respond .form-submit input[type='submit'] {
1084
+ border: 1px solid ButtonText;
1085
+ }
1086
+
1087
+ hr {
1088
+ border-block-start: 1px solid CanvasText;
1089
+ }
1090
+
1091
+ pre {
1092
+ border: 1px solid CanvasText;
1093
+ }
1094
+
1095
+ code,
1096
+ .search-snippet mark,
1097
+ .comment-avatar-empty {
1098
+ border: 1px solid CanvasText;
1099
+ }
1100
+
1101
+ pre code {
1102
+ border: 0;
1103
+ }
1104
+
1105
+ .facepile > a,
1106
+ .facepile > span {
1107
+ outline: 1px solid CanvasText;
1108
+ }
1109
+ }