@glw907/cairn-cms 0.60.0 → 0.62.1

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 (281) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/components/AdminLayout.svelte +152 -229
  3. package/dist/components/CairnAdmin.svelte +13 -42
  4. package/dist/components/CairnLogo.svelte +1 -6
  5. package/dist/components/CairnMediaLibrary.svelte +821 -1210
  6. package/dist/components/CairnTidySettings.svelte +194 -261
  7. package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
  8. package/dist/components/ComponentForm.svelte +110 -185
  9. package/dist/components/ComponentInsertDialog.svelte +163 -283
  10. package/dist/components/ConceptList.svelte +111 -191
  11. package/dist/components/ConfirmPage.svelte +5 -12
  12. package/dist/components/CsrfField.svelte +5 -11
  13. package/dist/components/DeleteDialog.svelte +15 -42
  14. package/dist/components/EditPage.svelte +781 -1205
  15. package/dist/components/EditorToolbar.svelte +108 -170
  16. package/dist/components/HelpHome.svelte +824 -0
  17. package/dist/components/HelpHome.svelte.d.ts +22 -0
  18. package/dist/components/IconPicker.svelte +23 -53
  19. package/dist/components/LinkPicker.svelte +34 -58
  20. package/dist/components/LoginPage.svelte +14 -27
  21. package/dist/components/ManageEditors.svelte +3 -15
  22. package/dist/components/MarkdownEditor.svelte +689 -957
  23. package/dist/components/MarkdownHelpDialog.svelte +12 -27
  24. package/dist/components/MediaCaptureCard.svelte +18 -57
  25. package/dist/components/MediaFigureControl.svelte +32 -71
  26. package/dist/components/MediaHeroField.svelte +210 -329
  27. package/dist/components/MediaInsertPopover.svelte +156 -283
  28. package/dist/components/MediaPicker.svelte +67 -131
  29. package/dist/components/NavTree.svelte +46 -78
  30. package/dist/components/RenameDialog.svelte +16 -43
  31. package/dist/components/ShortcutsDialog.svelte +9 -13
  32. package/dist/components/ShortcutsGrid.svelte +1 -2
  33. package/dist/components/TidyReview.svelte +140 -248
  34. package/dist/components/WebLinkDialog.svelte +19 -40
  35. package/dist/components/cairn-admin.css +4 -0
  36. package/dist/components/client-ingest.d.ts +16 -8
  37. package/dist/components/client-ingest.js +12 -6
  38. package/dist/components/editor-media.js +16 -8
  39. package/dist/components/editor-placeholder.d.ts +4 -2
  40. package/dist/components/editor-tidy.d.ts +24 -12
  41. package/dist/components/editor-tidy.js +8 -4
  42. package/dist/components/index.d.ts +1 -0
  43. package/dist/components/index.js +1 -0
  44. package/dist/components/link-completion.d.ts +12 -6
  45. package/dist/components/link-completion.js +12 -6
  46. package/dist/components/markdown-directives.d.ts +9 -6
  47. package/dist/components/markdown-directives.js +9 -6
  48. package/dist/components/markdown-format.d.ts +7 -2
  49. package/dist/components/markdown-format.js +59 -28
  50. package/dist/components/markdown-reference.d.ts +8 -0
  51. package/dist/components/markdown-reference.js +22 -0
  52. package/dist/components/media-upload-outcome.d.ts +12 -6
  53. package/dist/components/objective-errors.d.ts +8 -4
  54. package/dist/components/objective-errors.js +8 -4
  55. package/dist/components/preview-doc.d.ts +4 -2
  56. package/dist/components/preview-doc.js +4 -2
  57. package/dist/components/spellcheck.d.ts +57 -29
  58. package/dist/components/spellcheck.js +50 -20
  59. package/dist/components/tidy-categorize.d.ts +20 -10
  60. package/dist/components/tidy-categorize.js +16 -8
  61. package/dist/components/tidy-validate.d.ts +12 -6
  62. package/dist/components/tidy-validate.js +20 -10
  63. package/dist/components/topbar-context.d.ts +4 -2
  64. package/dist/content/advisories.d.ts +51 -0
  65. package/dist/content/advisories.js +79 -0
  66. package/dist/content/compose.d.ts +4 -2
  67. package/dist/content/compose.js +1 -0
  68. package/dist/content/excerpt.js +4 -2
  69. package/dist/content/getting-started.d.ts +18 -0
  70. package/dist/content/getting-started.js +12 -0
  71. package/dist/content/links.d.ts +16 -8
  72. package/dist/content/links.js +12 -6
  73. package/dist/content/manifest.d.ts +36 -18
  74. package/dist/content/manifest.js +32 -16
  75. package/dist/content/media-refs.d.ts +4 -2
  76. package/dist/content/media-refs.js +4 -2
  77. package/dist/content/media-rewrite.d.ts +8 -4
  78. package/dist/content/media-rewrite.js +76 -38
  79. package/dist/content/schema.d.ts +20 -10
  80. package/dist/content/site-dictionary.d.ts +4 -2
  81. package/dist/content/site-dictionary.js +8 -4
  82. package/dist/content/types.d.ts +97 -42
  83. package/dist/delivery/CairnHead.svelte +8 -11
  84. package/dist/delivery/content-index.d.ts +16 -8
  85. package/dist/delivery/feeds.js +4 -2
  86. package/dist/delivery/json-ld.d.ts +3 -0
  87. package/dist/delivery/json-ld.js +3 -0
  88. package/dist/delivery/manifest.d.ts +4 -2
  89. package/dist/delivery/manifest.js +4 -2
  90. package/dist/delivery/public-routes.d.ts +12 -6
  91. package/dist/delivery/public-routes.js +4 -2
  92. package/dist/delivery/seo-fields.d.ts +12 -6
  93. package/dist/delivery/seo-fields.js +8 -4
  94. package/dist/delivery/site-indexes.d.ts +4 -2
  95. package/dist/delivery/site-resolver.d.ts +4 -2
  96. package/dist/delivery/site-resolver.js +4 -2
  97. package/dist/doctor/cloudflare-api.d.ts +6 -0
  98. package/dist/doctor/cloudflare-api.js +6 -0
  99. package/dist/doctor/index.d.ts +12 -6
  100. package/dist/doctor/report.d.ts +3 -0
  101. package/dist/doctor/report.js +3 -0
  102. package/dist/doctor/run.d.ts +3 -0
  103. package/dist/doctor/run.js +3 -0
  104. package/dist/doctor/types.d.ts +10 -2
  105. package/dist/doctor/types.js +6 -0
  106. package/dist/doctor/wrangler-config.d.ts +7 -2
  107. package/dist/doctor/wrangler-config.js +3 -0
  108. package/dist/email.d.ts +4 -2
  109. package/dist/env.d.ts +0 -3
  110. package/dist/env.js +0 -3
  111. package/dist/github/branches.d.ts +4 -2
  112. package/dist/github/branches.js +4 -2
  113. package/dist/github/signing.d.ts +1 -1
  114. package/dist/github/signing.js +2 -2
  115. package/dist/log/events.d.ts +1 -1
  116. package/dist/media/bulk-delete-plan.d.ts +8 -4
  117. package/dist/media/config.d.ts +12 -6
  118. package/dist/media/config.js +16 -8
  119. package/dist/media/delivery-bucket.d.ts +4 -2
  120. package/dist/media/library-entry.d.ts +4 -2
  121. package/dist/media/library-entry.js +4 -2
  122. package/dist/media/manifest.d.ts +29 -15
  123. package/dist/media/manifest.js +29 -16
  124. package/dist/media/naming.d.ts +12 -6
  125. package/dist/media/naming.js +24 -12
  126. package/dist/media/orphan-scan.d.ts +4 -2
  127. package/dist/media/reconcile.d.ts +21 -11
  128. package/dist/media/reconcile.js +12 -6
  129. package/dist/media/reference.d.ts +8 -4
  130. package/dist/media/reference.js +12 -6
  131. package/dist/media/rewrite-plan.d.ts +12 -6
  132. package/dist/media/sniff.d.ts +4 -2
  133. package/dist/media/sniff.js +28 -14
  134. package/dist/media/store.d.ts +16 -8
  135. package/dist/media/store.js +4 -2
  136. package/dist/media/transform-url.d.ts +12 -6
  137. package/dist/media/transform-url.js +8 -4
  138. package/dist/media/usage.d.ts +8 -4
  139. package/dist/nav/site-config.d.ts +16 -8
  140. package/dist/render/component-grammar.d.ts +23 -10
  141. package/dist/render/component-grammar.js +19 -8
  142. package/dist/render/component-insert.d.ts +8 -4
  143. package/dist/render/component-insert.js +4 -2
  144. package/dist/render/component-reference.d.ts +4 -2
  145. package/dist/render/component-reference.js +4 -2
  146. package/dist/render/component-validate.d.ts +3 -0
  147. package/dist/render/component-validate.js +3 -0
  148. package/dist/render/glyph.d.ts +4 -2
  149. package/dist/render/glyph.js +4 -2
  150. package/dist/render/pipeline.d.ts +20 -10
  151. package/dist/render/pipeline.js +4 -2
  152. package/dist/render/registry.d.ts +40 -20
  153. package/dist/render/registry.js +16 -8
  154. package/dist/render/rehype-dispatch.d.ts +22 -8
  155. package/dist/render/rehype-dispatch.js +22 -8
  156. package/dist/render/remark-directives.d.ts +3 -0
  157. package/dist/render/remark-directives.js +3 -0
  158. package/dist/render/remark-figure.d.ts +4 -2
  159. package/dist/render/remark-figure.js +4 -2
  160. package/dist/render/resolve-links.d.ts +4 -2
  161. package/dist/render/resolve-links.js +4 -2
  162. package/dist/render/resolve-media.d.ts +16 -8
  163. package/dist/render/resolve-media.js +12 -6
  164. package/dist/sveltekit/admin-dispatch.d.ts +2 -0
  165. package/dist/sveltekit/admin-dispatch.js +9 -3
  166. package/dist/sveltekit/auth-routes.d.ts +3 -0
  167. package/dist/sveltekit/auth-routes.js +3 -0
  168. package/dist/sveltekit/cairn-admin.d.ts +16 -5
  169. package/dist/sveltekit/cairn-admin.js +26 -10
  170. package/dist/sveltekit/content-routes.d.ts +191 -86
  171. package/dist/sveltekit/content-routes.js +295 -107
  172. package/dist/sveltekit/editors-routes.d.ts +3 -0
  173. package/dist/sveltekit/editors-routes.js +3 -0
  174. package/dist/sveltekit/guard.d.ts +4 -2
  175. package/dist/sveltekit/guard.js +4 -2
  176. package/dist/sveltekit/https-required-page.d.ts +1 -1
  177. package/dist/sveltekit/https-required-page.js +1 -1
  178. package/dist/sveltekit/index.d.ts +1 -1
  179. package/dist/sveltekit/media-route.d.ts +1 -2
  180. package/dist/sveltekit/media-route.js +13 -8
  181. package/dist/sveltekit/nav-routes.d.ts +7 -2
  182. package/dist/sveltekit/nav-routes.js +3 -0
  183. package/dist/sveltekit/types.d.ts +4 -2
  184. package/dist/vite/index.d.ts +32 -16
  185. package/dist/vite/index.js +52 -26
  186. package/dist/vite/resolve-root.d.ts +8 -4
  187. package/dist/vite/resolve-root.js +4 -2
  188. package/package.json +8 -2
  189. package/src/lib/components/AdminLayout.svelte +22 -0
  190. package/src/lib/components/CairnAdmin.svelte +3 -0
  191. package/src/lib/components/CairnTidySettings.svelte +2 -2
  192. package/src/lib/components/ComponentForm.svelte +0 -1
  193. package/src/lib/components/EditPage.svelte +133 -41
  194. package/src/lib/components/HelpHome.svelte +850 -0
  195. package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
  196. package/src/lib/components/client-ingest.ts +20 -10
  197. package/src/lib/components/editor-media.ts +20 -10
  198. package/src/lib/components/editor-placeholder.ts +12 -6
  199. package/src/lib/components/editor-tidy.ts +28 -14
  200. package/src/lib/components/index.ts +1 -0
  201. package/src/lib/components/link-completion.ts +12 -6
  202. package/src/lib/components/markdown-directives.ts +13 -8
  203. package/src/lib/components/markdown-format.ts +63 -30
  204. package/src/lib/components/markdown-reference.ts +30 -0
  205. package/src/lib/components/media-upload-outcome.ts +12 -6
  206. package/src/lib/components/objective-errors.ts +16 -8
  207. package/src/lib/components/preview-doc.ts +4 -2
  208. package/src/lib/components/spellcheck.ts +92 -40
  209. package/src/lib/components/tidy-categorize.ts +28 -14
  210. package/src/lib/components/tidy-validate.ts +28 -14
  211. package/src/lib/components/topbar-context.ts +4 -2
  212. package/src/lib/content/advisories.ts +141 -0
  213. package/src/lib/content/compose.ts +5 -2
  214. package/src/lib/content/excerpt.ts +4 -2
  215. package/src/lib/content/getting-started.ts +31 -0
  216. package/src/lib/content/links.ts +16 -8
  217. package/src/lib/content/manifest.ts +36 -18
  218. package/src/lib/content/media-refs.ts +4 -2
  219. package/src/lib/content/media-rewrite.ts +100 -50
  220. package/src/lib/content/schema.ts +20 -10
  221. package/src/lib/content/site-dictionary.ts +8 -4
  222. package/src/lib/content/types.ts +97 -42
  223. package/src/lib/delivery/content-index.ts +16 -8
  224. package/src/lib/delivery/feeds.ts +4 -2
  225. package/src/lib/delivery/json-ld.ts +3 -0
  226. package/src/lib/delivery/manifest.ts +4 -2
  227. package/src/lib/delivery/public-routes.ts +16 -8
  228. package/src/lib/delivery/seo-fields.ts +12 -6
  229. package/src/lib/delivery/site-indexes.ts +4 -2
  230. package/src/lib/delivery/site-resolver.ts +4 -2
  231. package/src/lib/doctor/cloudflare-api.ts +6 -0
  232. package/src/lib/doctor/index.ts +12 -6
  233. package/src/lib/doctor/report.ts +3 -0
  234. package/src/lib/doctor/run.ts +3 -0
  235. package/src/lib/doctor/types.ts +10 -2
  236. package/src/lib/doctor/wrangler-config.ts +7 -2
  237. package/src/lib/email.ts +4 -2
  238. package/src/lib/env.ts +0 -3
  239. package/src/lib/github/branches.ts +4 -2
  240. package/src/lib/github/signing.ts +2 -2
  241. package/src/lib/log/events.ts +1 -0
  242. package/src/lib/media/bulk-delete-plan.ts +8 -4
  243. package/src/lib/media/config.ts +24 -12
  244. package/src/lib/media/delivery-bucket.ts +4 -2
  245. package/src/lib/media/library-entry.ts +4 -2
  246. package/src/lib/media/manifest.ts +33 -18
  247. package/src/lib/media/naming.ts +24 -12
  248. package/src/lib/media/orphan-scan.ts +4 -2
  249. package/src/lib/media/reconcile.ts +21 -11
  250. package/src/lib/media/reference.ts +12 -6
  251. package/src/lib/media/rewrite-plan.ts +12 -6
  252. package/src/lib/media/sniff.ts +28 -14
  253. package/src/lib/media/store.ts +16 -8
  254. package/src/lib/media/transform-url.ts +12 -6
  255. package/src/lib/media/usage.ts +8 -4
  256. package/src/lib/nav/site-config.ts +16 -8
  257. package/src/lib/render/component-grammar.ts +23 -10
  258. package/src/lib/render/component-insert.ts +8 -4
  259. package/src/lib/render/component-reference.ts +4 -2
  260. package/src/lib/render/component-validate.ts +3 -0
  261. package/src/lib/render/glyph.ts +4 -2
  262. package/src/lib/render/pipeline.ts +20 -10
  263. package/src/lib/render/registry.ts +44 -22
  264. package/src/lib/render/rehype-dispatch.ts +22 -8
  265. package/src/lib/render/remark-directives.ts +3 -0
  266. package/src/lib/render/remark-figure.ts +4 -2
  267. package/src/lib/render/resolve-links.ts +4 -2
  268. package/src/lib/render/resolve-media.ts +16 -8
  269. package/src/lib/sveltekit/admin-dispatch.ts +10 -4
  270. package/src/lib/sveltekit/auth-routes.ts +3 -0
  271. package/src/lib/sveltekit/cairn-admin.ts +37 -15
  272. package/src/lib/sveltekit/content-routes.ts +492 -197
  273. package/src/lib/sveltekit/editors-routes.ts +3 -0
  274. package/src/lib/sveltekit/guard.ts +4 -2
  275. package/src/lib/sveltekit/https-required-page.ts +1 -1
  276. package/src/lib/sveltekit/index.ts +3 -0
  277. package/src/lib/sveltekit/media-route.ts +13 -8
  278. package/src/lib/sveltekit/nav-routes.ts +7 -2
  279. package/src/lib/sveltekit/types.ts +4 -2
  280. package/src/lib/vite/index.ts +60 -30
  281. package/src/lib/vite/resolve-root.ts +8 -4
@@ -0,0 +1,850 @@
1
+ <!--
2
+ @component
3
+ The Help home admin screen, the standing place an author comes to get their bearings and look up
4
+ how things work. It renders inside `AdminLayout` (the office shell), so it carries no `data-theme`
5
+ wrapper and imports no CSS: the layout owns the theme and `cairn-admin.css`, and this component
6
+ consumes the Warm Stone tokens through its scoped `<style>` block.
7
+
8
+ The content is one calm column: a masthead, then three co-equal eyebrow-plus-display sections.
9
+
10
+ - Getting started reads `data.gettingStarted`, the count derived from the committed manifest and
11
+ the open edit branches (never a stored count). At 3 of 3 the whole section is omitted, never
12
+ shown as a done checklist. A per-device localStorage flag hides it on request.
13
+ - Formatting renders `data.reference` (the everyday text and links rows) as a real semantic table.
14
+ - Get help reads the optional `data.supportContact`. Unset is the canonical self-serve default,
15
+ with no control; set renders the hand-off shaped by the contact (email, URL, or a note).
16
+ -->
17
+ <script lang="ts">
18
+ import type { MarkdownReferenceRow } from './markdown-reference.js';
19
+ import type { HelpData } from '../sveltekit/content-routes.js';
20
+
21
+ let { data }: { data: HelpData } = $props();
22
+
23
+ // The everyday reference rows, split into the two reading columns. The blocks group stays in the
24
+ // editor's full Ctrl+/ sheet; the Help home shows only the nine text and links rows.
25
+ const textRows = $derived(data.reference.filter((r) => r.group === 'text'));
26
+ const linkRows = $derived(data.reference.filter((r) => r.group === 'links'));
27
+
28
+ // The three getting-started steps, mapped from the derived booleans in fixed order. The done and
29
+ // todo copy is the owned voice; a not-done step routes to where it completes, a done step keeps a
30
+ // quiet "Open it" so every row carries an affordance.
31
+ const steps = $derived([
32
+ {
33
+ title: 'Write your first post',
34
+ done: data.gettingStarted.wrotePost,
35
+ doneDesc: 'You have a post started. Open it any time to keep going.',
36
+ todoDesc: 'A post is for news and updates. Start your first one whenever you are ready.',
37
+ actionLabel: 'Write a post',
38
+ href: '/admin/posts',
39
+ },
40
+ {
41
+ title: 'Publish it',
42
+ done: data.gettingStarted.publishedPost,
43
+ doneDesc: 'Your post is on the live site. Edit it and publish again any time.',
44
+ todoDesc:
45
+ 'Publishing puts your post on the live site. You can change it and publish again any time.',
46
+ actionLabel: 'Publish a post',
47
+ href: '/admin/posts',
48
+ },
49
+ {
50
+ title: 'Create a page',
51
+ done: data.gettingStarted.createdPage,
52
+ doneDesc: 'You have a page. Open it any time to keep going.',
53
+ todoDesc: 'A page is for the parts that stay put, like About or Contact.',
54
+ actionLabel: 'Add a page',
55
+ href: '/admin/pages',
56
+ },
57
+ ]);
58
+
59
+ // The decorative progress-bar width tracks the count. At 0 it drops to a faint seed so the rail
60
+ // reads as a track, never as fake progress.
61
+ const doneCount = $derived(data.gettingStarted.doneCount);
62
+ const barWidth = $derived(doneCount === 0 ? '3%' : `${(doneCount / 3) * 100}%`);
63
+
64
+ /**
65
+ * Classify a freeform support contact into the hand-off shape it should render: a mailto for an
66
+ * email, an external link for a URL, or a plain note for anything else.
67
+ */
68
+ function supportLink(
69
+ contact: string,
70
+ ): { kind: 'email'; href: string } | { kind: 'url'; href: string } | { kind: 'text' } {
71
+ const c = contact.trim();
72
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(c)) return { kind: 'email', href: `mailto:${c}` };
73
+ if (/^https?:\/\/\S+$/.test(c)) return { kind: 'url', href: c };
74
+ return { kind: 'text' };
75
+ }
76
+
77
+ const contact = $derived(data.supportContact?.trim() ?? '');
78
+ const support = $derived(contact ? supportLink(contact) : null);
79
+
80
+ // The per-device dismiss for the getting-started section. The admin is client-rendered, but read
81
+ // the flag inside an effect so SSR never touches localStorage; the setter writes it on click.
82
+ const HIDDEN_KEY = 'cairn-help-getting-started-hidden';
83
+ let hidden = $state(false);
84
+ $effect(() => {
85
+ hidden = localStorage.getItem(HIDDEN_KEY) === '1';
86
+ });
87
+ function hideSteps() {
88
+ hidden = true;
89
+ localStorage.setItem(HIDDEN_KEY, '1');
90
+ }
91
+ function showSteps() {
92
+ hidden = false;
93
+ localStorage.removeItem(HIDDEN_KEY);
94
+ }
95
+ </script>
96
+
97
+ <div class="cairn-help-content">
98
+ <div class="help-col">
99
+ <!-- masthead: a real-sentence h1, the page's single display beat -->
100
+ <div class="page-head">
101
+ <span class="eyebrow">Help</span>
102
+ <h1 class="page-h1">Find your way around</h1>
103
+ <p class="page-lede">The place to get your bearings and look up how things work.</p>
104
+ </div>
105
+
106
+ <!-- SECTION 1: getting started (omitted at 3 of 3) -->
107
+ {#if doneCount < 3}
108
+ {#if hidden}
109
+ <div class="start-restore">
110
+ <button type="button" onclick={showSteps}>Show getting started</button>
111
+ </div>
112
+ {:else}
113
+ <section class="section" aria-labelledby="cairn-help-start-eye">
114
+ <div class="section-head">
115
+ <div class="section-titles">
116
+ <span class="eyebrow" id="cairn-help-start-eye">Getting started</span>
117
+ <h2 class="section-h">Your first steps</h2>
118
+ </div>
119
+ </div>
120
+
121
+ <div class="card start" class:start-empty={doneCount === 0}>
122
+ <div class="start-top">
123
+ {#if doneCount === 0}
124
+ <span class="start-mark" aria-hidden="true">
125
+ <svg viewBox="0 0 24 24" fill="currentColor"
126
+ ><ellipse cx="12" cy="18.2" rx="7" ry="2.5" /><ellipse
127
+ cx="12"
128
+ cy="13"
129
+ rx="5.2"
130
+ ry="2.1"
131
+ /><ellipse cx="12" cy="8.6" rx="3.7" ry="1.7" /><ellipse
132
+ cx="12"
133
+ cy="5"
134
+ rx="2"
135
+ ry="1.2"
136
+ /></svg
137
+ >
138
+ </span>
139
+ {/if}
140
+ <div class="start-lead">
141
+ <p class="start-lead-sub">Three small steps to get going.</p>
142
+ </div>
143
+ <!-- the count is the source of truth; the bar is decorative only -->
144
+ <div class="prog">
145
+ <div class="prog-count"><b>{doneCount}</b> of 3 done</div>
146
+ <div class="prog-bar" class:is-seed={doneCount === 0} role="presentation">
147
+ <i style="width:{barWidth}"></i>
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ <ol class="steps">
153
+ {#each steps as step (step.title)}
154
+ <li class="step" class:is-done={step.done}>
155
+ <span class="step-box" aria-hidden="true">
156
+ {#if step.done}
157
+ <svg
158
+ fill="none"
159
+ viewBox="0 0 24 24"
160
+ stroke="currentColor"
161
+ stroke-width="3"
162
+ stroke-linecap="round"
163
+ stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg
164
+ >
165
+ {/if}
166
+ </span>
167
+ <span class="step-body">
168
+ <span class="step-title">{step.title}</span>
169
+ <span class="step-desc">{step.done ? step.doneDesc : step.todoDesc}</span>
170
+ </span>
171
+ {#if step.done}
172
+ <span class="step-done-tag">
173
+ <svg
174
+ fill="none"
175
+ viewBox="0 0 24 24"
176
+ stroke="currentColor"
177
+ stroke-width="2.5"
178
+ stroke-linecap="round"
179
+ stroke-linejoin="round"
180
+ aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg
181
+ >
182
+ Done
183
+ </span>
184
+ <a class="step-open" href={step.href}>Open it</a>
185
+ {:else}
186
+ <span class="sr-only">not done</span>
187
+ <a class="step-act" href={step.href}>
188
+ {step.actionLabel}
189
+ <svg
190
+ fill="none"
191
+ viewBox="0 0 24 24"
192
+ stroke="currentColor"
193
+ stroke-width="2.5"
194
+ stroke-linecap="round"
195
+ stroke-linejoin="round"
196
+ aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg
197
+ >
198
+ </a>
199
+ {/if}
200
+ </li>
201
+ {/each}
202
+ </ol>
203
+
204
+ <div class="start-foot">
205
+ <span>These steps follow what is really on your site.</span>
206
+ <button type="button" onclick={hideSteps}>Hide these steps</button>
207
+ </div>
208
+ </div>
209
+ </section>
210
+ {/if}
211
+ {/if}
212
+
213
+ <!-- SECTION 2: formatting reference -->
214
+ <section class="section" aria-labelledby="cairn-help-ref-eye">
215
+ <div class="section-head">
216
+ <div class="section-titles">
217
+ <span class="eyebrow" id="cairn-help-ref-eye">Formatting</span>
218
+ <h2 class="section-h">How to format text</h2>
219
+ </div>
220
+ <span class="section-meta">Type the characters on the left</span>
221
+ </div>
222
+
223
+ {#snippet refTable(caption: string, rows: MarkdownReferenceRow[])}
224
+ <table class="ref-table">
225
+ <caption>{caption}</caption>
226
+ <thead>
227
+ <tr><th scope="col">Type this</th><th scope="col">What it makes</th></tr>
228
+ </thead>
229
+ <tbody>
230
+ {#each rows as row (row.syntax)}
231
+ <tr>
232
+ <th scope="row">{row.syntax}</th>
233
+ <td>{row.makes}</td>
234
+ </tr>
235
+ {/each}
236
+ </tbody>
237
+ </table>
238
+ {/snippet}
239
+
240
+ <div class="card ref-card">
241
+ <div class="ref-cols">
242
+ {@render refTable('Text', textRows)}
243
+ {@render refTable('Links and lists', linkRows)}
244
+ </div>
245
+
246
+ <p class="ref-foot">
247
+ <svg
248
+ fill="none"
249
+ viewBox="0 0 24 24"
250
+ stroke="currentColor"
251
+ stroke-width="2"
252
+ stroke-linecap="round"
253
+ stroke-linejoin="round"
254
+ aria-hidden="true"
255
+ ><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg
256
+ >
257
+ <span
258
+ >This same sheet opens beside the editor while you write. Press <span class="kbd"
259
+ >Ctrl</span
260
+ >
261
+ <span class="kbd">/</span>.</span
262
+ >
263
+ </p>
264
+ </div>
265
+ </section>
266
+
267
+ <!-- SECTION 3: get help -->
268
+ <section class="section" aria-labelledby="cairn-help-eye">
269
+ <div class="section-head">
270
+ <div class="section-titles">
271
+ <span class="eyebrow" id="cairn-help-eye">Get help</span>
272
+ <h2 class="section-h">
273
+ {support ? 'Ask the person who set up your site' : 'Stuck on something?'}
274
+ </h2>
275
+ </div>
276
+ </div>
277
+
278
+ <div class="card help-card">
279
+ {#if !support}
280
+ <div class="help-card-body">
281
+ <p class="help-card-sub">
282
+ Check the formatting guide above, or ask whoever set up your site.
283
+ </p>
284
+ </div>
285
+ {:else if support.kind === 'text'}
286
+ <div class="help-card-body">
287
+ <p class="help-card-sub">Whoever set up your site left this note: {contact}</p>
288
+ </div>
289
+ {:else}
290
+ <div class="help-card-body">
291
+ <p class="help-card-sub">
292
+ Whoever set up your site can change things you cannot change here, like the menu and
293
+ who can sign in.
294
+ {#if support.kind === 'email'}Reach them at <b>{contact}</b>.{:else}Find help at
295
+ <b>{contact}</b>.{/if}
296
+ </p>
297
+ </div>
298
+ {#if support.kind === 'email'}
299
+ <a class="btn-quiet" href={support.href}>
300
+ <svg
301
+ fill="none"
302
+ viewBox="0 0 24 24"
303
+ stroke="currentColor"
304
+ stroke-width="2"
305
+ stroke-linecap="round"
306
+ stroke-linejoin="round"
307
+ aria-hidden="true"
308
+ ><rect x="2" y="4" width="20" height="16" rx="2" /><path
309
+ d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"
310
+ /></svg
311
+ >
312
+ Email support
313
+ </a>
314
+ {:else}
315
+ <a class="btn-quiet" href={support.href} target="_blank" rel="noopener">
316
+ <svg
317
+ fill="none"
318
+ viewBox="0 0 24 24"
319
+ stroke="currentColor"
320
+ stroke-width="2"
321
+ stroke-linecap="round"
322
+ stroke-linejoin="round"
323
+ aria-hidden="true"
324
+ ><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path
325
+ d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
326
+ /></svg
327
+ >
328
+ Get help
329
+ </a>
330
+ {/if}
331
+ {/if}
332
+ </div>
333
+ </section>
334
+ </div>
335
+ </div>
336
+
337
+ <style>
338
+ /* The Help home content column. The styles consume the Warm Stone tokens the AdminLayout theme
339
+ root owns; this block never redefines :root or [data-theme]. Ported from the rev.2 polished
340
+ mockup (docs/internal/design/2026-06-23-help-shell-mockup-rev2-polished.html), adapted to the
341
+ three co-equal eyebrow-plus-display sections and the derived getting-started state. */
342
+
343
+ .sr-only {
344
+ position: absolute;
345
+ width: 1px;
346
+ height: 1px;
347
+ padding: 0;
348
+ margin: -1px;
349
+ overflow: hidden;
350
+ clip: rect(0 0 0 0);
351
+ white-space: nowrap;
352
+ border: 0;
353
+ }
354
+
355
+ .cairn-help-content {
356
+ flex: 1 1 auto;
357
+ padding: 2.75rem 2.5rem 3.5rem;
358
+ overflow: auto;
359
+ }
360
+ .help-col {
361
+ max-width: 58rem;
362
+ margin: 0 auto;
363
+ width: 100%;
364
+ }
365
+
366
+ .eyebrow {
367
+ font-size: 0.6875rem;
368
+ font-weight: 600;
369
+ letter-spacing: 0.08em;
370
+ text-transform: uppercase;
371
+ color: var(--color-muted);
372
+ }
373
+
374
+ /* the masthead: a plain eyebrow over a real-sentence h1, the page's single display beat */
375
+ .page-head {
376
+ margin-bottom: 30px;
377
+ }
378
+ .page-h1 {
379
+ font-family: var(--font-display);
380
+ font-size: 1.875rem;
381
+ font-weight: 700;
382
+ letter-spacing: -0.025em;
383
+ margin: 5px 0 0;
384
+ }
385
+ .page-lede {
386
+ font-size: 0.9375rem;
387
+ color: var(--color-muted);
388
+ margin: 8px 0 0;
389
+ max-width: 54ch;
390
+ line-height: 1.55;
391
+ }
392
+
393
+ /* the shared section-head rhythm: an eyebrow over a display title, with an optional trailing meta */
394
+ .section {
395
+ margin-top: 36px;
396
+ }
397
+ .section-head {
398
+ display: flex;
399
+ align-items: baseline;
400
+ gap: 14px;
401
+ margin-bottom: 16px;
402
+ }
403
+ .section-titles {
404
+ display: flex;
405
+ flex-direction: column;
406
+ gap: 2px;
407
+ }
408
+ .section-h {
409
+ font-family: var(--font-display);
410
+ font-size: 1.25rem;
411
+ font-weight: 700;
412
+ letter-spacing: -0.02em;
413
+ line-height: 1.12;
414
+ margin: 0;
415
+ }
416
+ .section-meta {
417
+ margin-left: auto;
418
+ align-self: center;
419
+ font-size: 0.75rem;
420
+ color: var(--color-muted);
421
+ }
422
+
423
+ /* the floating-card recipe: rounded-box, hairline plus soft shadow, on base-100 */
424
+ .card {
425
+ border: 1px solid var(--cairn-card-border);
426
+ border-radius: var(--radius-box);
427
+ background: var(--color-base-100);
428
+ box-shadow: var(--cairn-shadow);
429
+ overflow: hidden;
430
+ }
431
+
432
+ /* SECTION 1: getting started */
433
+ .start {
434
+ padding: 22px 24px 18px;
435
+ }
436
+ /* the 0-of-3 empty state composition: the cairn mark presides above a warmer frame */
437
+ .start-empty {
438
+ background: color-mix(in oklab, var(--color-primary) 2.5%, var(--color-base-100));
439
+ }
440
+ .start-mark {
441
+ flex: none;
442
+ height: 40px;
443
+ width: 40px;
444
+ border-radius: 0.75rem;
445
+ background: color-mix(in oklab, var(--color-primary) 12%, transparent);
446
+ color: var(--color-primary);
447
+ display: inline-flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ }
451
+ .start-mark svg {
452
+ width: 22px;
453
+ height: 22px;
454
+ }
455
+ .start-top {
456
+ display: flex;
457
+ align-items: flex-start;
458
+ gap: 18px;
459
+ flex-wrap: wrap;
460
+ }
461
+ .start-lead {
462
+ flex: 1 1 20rem;
463
+ min-width: 0;
464
+ }
465
+ .start-lead-sub {
466
+ font-size: 0.8125rem;
467
+ color: var(--color-muted);
468
+ margin: 0;
469
+ max-width: 46ch;
470
+ line-height: 1.5;
471
+ }
472
+
473
+ /* the progress readout: the count is the source of truth, the bar is decorative only */
474
+ .prog {
475
+ flex: 0 0 auto;
476
+ min-width: 11rem;
477
+ text-align: right;
478
+ }
479
+ .prog-count {
480
+ font-size: 0.8125rem;
481
+ font-weight: 600;
482
+ color: var(--color-base-content);
483
+ }
484
+ .prog-count b {
485
+ color: var(--color-positive-ink);
486
+ font-weight: 700;
487
+ }
488
+ .prog-bar {
489
+ height: 6px;
490
+ border-radius: 999px;
491
+ margin-top: 8px;
492
+ background: color-mix(in oklab, var(--color-base-content) 8%, transparent);
493
+ overflow: hidden;
494
+ }
495
+ .prog-bar > i {
496
+ display: block;
497
+ height: 100%;
498
+ min-width: 6px;
499
+ border-radius: 999px;
500
+ background: var(--color-primary);
501
+ }
502
+ /* the 0-of-3 seed: a faint desaturated fill, so the rail reads as a track, never as progress */
503
+ .prog-bar.is-seed > i {
504
+ background: color-mix(in oklab, var(--color-base-content) 22%, transparent);
505
+ }
506
+
507
+ /* the steps: quiet rows inside the single card, never nested cards */
508
+ .steps {
509
+ list-style: none;
510
+ margin: 18px 0 0;
511
+ padding: 0;
512
+ display: grid;
513
+ gap: 8px;
514
+ }
515
+ .step {
516
+ display: flex;
517
+ align-items: center;
518
+ gap: 13px;
519
+ padding: 12px 14px;
520
+ border: 1px solid var(--cairn-card-border);
521
+ border-radius: 0.75rem;
522
+ background: var(--color-base-100);
523
+ }
524
+ /* the unchecked ring is content-55% (about 3:1 on base-100), a control state perceivable on its
525
+ own (WCAG 1.4.11), and kept a thin ring rather than a filled box so it never reads as checked */
526
+ .step-box {
527
+ flex: none;
528
+ height: 24px;
529
+ width: 24px;
530
+ border-radius: 7px;
531
+ border: 2px solid color-mix(in oklab, var(--color-base-content) 55%, transparent);
532
+ background: var(--color-base-100);
533
+ display: inline-flex;
534
+ align-items: center;
535
+ justify-content: center;
536
+ color: transparent;
537
+ }
538
+ .step-box svg {
539
+ width: 13px;
540
+ height: 13px;
541
+ }
542
+ .step-body {
543
+ flex: 1 1 auto;
544
+ min-width: 0;
545
+ display: flex;
546
+ flex-direction: column;
547
+ }
548
+ .step-title {
549
+ font-size: 0.9375rem;
550
+ font-weight: 600;
551
+ color: var(--color-base-content);
552
+ }
553
+ .step-desc {
554
+ font-size: 0.8125rem;
555
+ color: var(--color-muted);
556
+ margin-top: 2px;
557
+ line-height: 1.45;
558
+ }
559
+
560
+ /* a not-done step routes to where it completes through its own short action link */
561
+ .step-act {
562
+ flex: none;
563
+ margin-left: auto;
564
+ display: inline-flex;
565
+ align-items: center;
566
+ gap: 5px;
567
+ height: 30px;
568
+ padding: 0 12px;
569
+ border: 1px solid var(--cairn-card-border);
570
+ border-radius: var(--radius-field);
571
+ background: var(--color-base-100);
572
+ color: var(--color-primary);
573
+ font: 600 0.78125rem/1 var(--font-body);
574
+ cursor: pointer;
575
+ text-decoration: none;
576
+ transition: border-color 120ms ease, background-color 120ms ease;
577
+ }
578
+ .step-act:hover {
579
+ border-color: color-mix(in oklab, var(--color-primary) 40%, var(--cairn-card-border));
580
+ background: color-mix(in oklab, var(--color-primary) 6%, transparent);
581
+ }
582
+ .step-act svg {
583
+ width: 13px;
584
+ height: 13px;
585
+ }
586
+
587
+ /* the done state. The cue reaches three ways and never by color alone (WCAG 1.4.1): the filled
588
+ glyph box, a visible "Done" tag, and the open step's sr-only "not done". A done step keeps a
589
+ quiet "Open it" so every row carries an affordance. */
590
+ .step.is-done {
591
+ background: color-mix(in oklab, var(--color-positive-ink) 4%, var(--color-base-100));
592
+ }
593
+ .step.is-done .step-box {
594
+ background: var(--color-positive-ink);
595
+ border-color: var(--color-positive-ink);
596
+ color: var(--color-primary-content);
597
+ }
598
+ .step.is-done .step-title {
599
+ color: var(--color-muted);
600
+ }
601
+ .step-done-tag {
602
+ flex: none;
603
+ margin-left: auto;
604
+ display: inline-flex;
605
+ align-items: center;
606
+ gap: 5px;
607
+ font-size: 0.75rem;
608
+ font-weight: 600;
609
+ color: var(--color-positive-ink);
610
+ }
611
+ .step-done-tag svg {
612
+ width: 13px;
613
+ height: 13px;
614
+ }
615
+ .step-open {
616
+ flex: none;
617
+ display: inline-flex;
618
+ align-items: center;
619
+ gap: 5px;
620
+ font: 600 0.78125rem/1 var(--font-body);
621
+ color: var(--color-muted);
622
+ padding: 4px 6px;
623
+ text-decoration: underline;
624
+ text-underline-offset: 2px;
625
+ }
626
+ .step-open:hover {
627
+ color: var(--color-base-content);
628
+ }
629
+
630
+ .start-foot {
631
+ margin-top: 16px;
632
+ display: flex;
633
+ align-items: center;
634
+ gap: 10px;
635
+ flex-wrap: wrap;
636
+ font-size: 0.75rem;
637
+ color: var(--color-muted);
638
+ }
639
+ .start-foot button {
640
+ color: var(--color-muted);
641
+ background: none;
642
+ border: 0;
643
+ padding: 0;
644
+ cursor: pointer;
645
+ font: inherit;
646
+ text-decoration: underline;
647
+ text-underline-offset: 2px;
648
+ }
649
+ .start-foot button:hover {
650
+ color: var(--color-base-content);
651
+ }
652
+
653
+ /* the restore affordance shown once the section is hidden */
654
+ .start-restore {
655
+ margin-top: 36px;
656
+ }
657
+ .start-restore button {
658
+ color: var(--color-muted);
659
+ background: none;
660
+ border: 0;
661
+ padding: 0;
662
+ cursor: pointer;
663
+ font: 500 0.8125rem/1 var(--font-body);
664
+ text-decoration: underline;
665
+ text-underline-offset: 2px;
666
+ }
667
+ .start-restore button:hover {
668
+ color: var(--color-base-content);
669
+ }
670
+
671
+ /* SECTION 2: formatting reference */
672
+ .ref-cols {
673
+ display: grid;
674
+ grid-template-columns: 1fr 1fr;
675
+ }
676
+ .ref-cols > table:first-child {
677
+ border-right: 1px solid var(--cairn-card-border);
678
+ }
679
+ .ref-table {
680
+ width: 100%;
681
+ border-collapse: collapse;
682
+ table-layout: fixed;
683
+ }
684
+ .ref-table caption {
685
+ text-align: left;
686
+ font-size: 0.625rem;
687
+ font-weight: 600;
688
+ letter-spacing: 0.09em;
689
+ text-transform: uppercase;
690
+ color: var(--color-muted);
691
+ padding: 16px 22px 6px;
692
+ }
693
+ .ref-table thead th {
694
+ text-align: left;
695
+ width: 50%;
696
+ font-size: 0.625rem;
697
+ font-weight: 600;
698
+ letter-spacing: 0.07em;
699
+ text-transform: uppercase;
700
+ color: var(--color-muted);
701
+ font-family: var(--font-body);
702
+ padding: 6px 22px 10px;
703
+ border-bottom: 1px solid color-mix(in oklab, var(--cairn-card-border) 70%, transparent);
704
+ }
705
+ .ref-table tbody tr + tr td,
706
+ .ref-table tbody tr + tr th {
707
+ border-top: 1px solid color-mix(in oklab, var(--cairn-card-border) 65%, transparent);
708
+ }
709
+ /* the "type this" cell renders the literal syntax in the iA Writer Mono editor face */
710
+ .ref-table th[scope='row'] {
711
+ font-weight: 400;
712
+ text-align: left;
713
+ padding: 10px 22px;
714
+ font-family: var(--font-editor);
715
+ font-size: 0.8125rem;
716
+ line-height: 1.5;
717
+ color: var(--color-base-content);
718
+ white-space: nowrap;
719
+ }
720
+ /* the "what it makes" cell: the plain gloss in the body face */
721
+ .ref-table td {
722
+ padding: 10px 22px;
723
+ font-size: 0.875rem;
724
+ line-height: 1.5;
725
+ color: var(--color-base-content);
726
+ }
727
+
728
+ /* the reference foot points at the present, working help beside the editor */
729
+ .ref-foot {
730
+ display: flex;
731
+ align-items: center;
732
+ gap: 8px;
733
+ margin: 0;
734
+ padding: 12px 22px;
735
+ border-top: 1px solid var(--cairn-card-border);
736
+ background: color-mix(in oklab, var(--color-base-content) 1.5%, transparent);
737
+ font-size: 0.8125rem;
738
+ color: var(--color-muted);
739
+ }
740
+ .ref-foot svg {
741
+ width: 15px;
742
+ height: 15px;
743
+ flex: none;
744
+ color: var(--color-muted);
745
+ }
746
+ .kbd {
747
+ font-family: var(--font-editor);
748
+ font-size: 0.6875rem;
749
+ border: 1px solid var(--cairn-card-border);
750
+ border-radius: 0.25rem;
751
+ padding: 1px 5px;
752
+ background: var(--color-base-100);
753
+ color: var(--color-base-content);
754
+ }
755
+
756
+ /* SECTION 3: get help. A calm text-plus-action row, no decorative icon tile. */
757
+ .help-card {
758
+ display: flex;
759
+ align-items: center;
760
+ gap: 16px;
761
+ padding: 18px 20px;
762
+ }
763
+ .help-card-body {
764
+ flex: 1 1 auto;
765
+ min-width: 0;
766
+ }
767
+ .help-card-sub {
768
+ font-size: 0.8125rem;
769
+ color: var(--color-muted);
770
+ margin: 0;
771
+ line-height: 1.45;
772
+ max-width: 48ch;
773
+ }
774
+ .help-card-sub b {
775
+ color: var(--color-base-content);
776
+ font-weight: 600;
777
+ }
778
+
779
+ /* a quiet bordered control (the get-help hand-off) */
780
+ .btn-quiet {
781
+ display: inline-flex;
782
+ align-items: center;
783
+ justify-content: center;
784
+ gap: 6px;
785
+ height: 36px;
786
+ padding: 0 14px;
787
+ border: 1px solid var(--cairn-card-border);
788
+ border-radius: var(--radius-field);
789
+ background: var(--color-base-100);
790
+ color: var(--color-base-content);
791
+ font: 600 0.8125rem/1 var(--font-body);
792
+ cursor: pointer;
793
+ text-decoration: none;
794
+ white-space: nowrap;
795
+ transition: border-color 120ms ease, color 120ms ease;
796
+ }
797
+ .btn-quiet:hover {
798
+ border-color: color-mix(in oklab, var(--color-primary) 38%, var(--cairn-card-border));
799
+ color: var(--color-primary);
800
+ }
801
+ .btn-quiet svg {
802
+ width: 15px;
803
+ height: 15px;
804
+ }
805
+
806
+ @media (max-width: 900px) {
807
+ .ref-cols {
808
+ grid-template-columns: 1fr;
809
+ }
810
+ .ref-cols > table:first-child {
811
+ border-right: 0;
812
+ border-bottom: 1px solid var(--cairn-card-border);
813
+ }
814
+ }
815
+ @media (max-width: 760px) {
816
+ .cairn-help-content {
817
+ padding: 1.75rem 1.25rem 2.5rem;
818
+ }
819
+ .start-top {
820
+ flex-direction: column;
821
+ }
822
+ .prog {
823
+ text-align: left;
824
+ min-width: 0;
825
+ width: 100%;
826
+ }
827
+ .step {
828
+ flex-wrap: wrap;
829
+ }
830
+ .step-act,
831
+ .step-done-tag,
832
+ .step-open {
833
+ margin-left: 37px;
834
+ }
835
+ .ref-table th[scope='row'] {
836
+ white-space: normal;
837
+ }
838
+ .help-card {
839
+ flex-direction: column;
840
+ align-items: flex-start;
841
+ }
842
+ }
843
+
844
+ @media (prefers-reduced-motion: reduce) {
845
+ .btn-quiet,
846
+ .step-act {
847
+ transition: none;
848
+ }
849
+ }
850
+ </style>