@jant/core 0.6.14 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/bin/commands/import-site.js +81 -13
  2. package/bin/commands/migrate.js +55 -0
  3. package/bin/commands/posts/rebuild-html.js +184 -0
  4. package/bin/commands/site/snapshot/import.js +14 -10
  5. package/bin/lib/d1-query.js +23 -5
  6. package/bin/lib/migration-runner.js +29 -7
  7. package/bin/lib/node-database.js +32 -0
  8. package/bin/lib/site-snapshot.js +347 -46
  9. package/bin/lib/sql-export.js +1 -1
  10. package/bin/lib/thread-collection-migration.js +326 -0
  11. package/dist/app-BxCOR3Uc.js +6 -0
  12. package/dist/{app-2i7-WQHg.js → app-C-oi_t8W.js} +7408 -6343
  13. package/dist/client/.vite/manifest.json +3 -3
  14. package/dist/client/_assets/client-B_eGKN5p.css +2 -0
  15. package/dist/client/_assets/{client-BvID5ucz.js → client-DxY5BFe8.js} +58 -37
  16. package/dist/client/_assets/client-auth-CakPESv9.js +5533 -0
  17. package/dist/{export-DWn149b7.js → export-CsFx6F_M.js} +1259 -125
  18. package/dist/{github-sync-Ckk0sJxK.js → github-sync-Cq1pzzOI.js} +1 -1
  19. package/dist/{github-sync-C0zyF6XS.js → github-sync-ppWXN3jb.js} +2 -2
  20. package/dist/index.js +3 -3
  21. package/dist/node.js +5 -5
  22. package/package.json +11 -10
  23. package/src/__tests__/bin/posts-rebuild-html.test.ts +137 -0
  24. package/src/__tests__/dev-navigation-defaults.test.ts +22 -0
  25. package/src/__tests__/export-hugo-build.test.ts +63 -4
  26. package/src/__tests__/export-import-roundtrip.test.ts +6 -4
  27. package/src/__tests__/export-service.test.ts +142 -9
  28. package/src/__tests__/helpers/app.ts +3 -0
  29. package/src/__tests__/helpers/export-fixtures.ts +2 -0
  30. package/src/__tests__/import-site-command.test.ts +65 -0
  31. package/src/__tests__/vite-config.test.ts +45 -0
  32. package/src/app.tsx +16 -3
  33. package/src/client/__tests__/collection-created-notice.test.ts +36 -0
  34. package/src/client/__tests__/collection-form-bridge.test.ts +10 -1
  35. package/src/client/__tests__/collection-page-actions.test.ts +71 -2
  36. package/src/client/__tests__/compose-bridge.test.ts +64 -0
  37. package/src/client/__tests__/feed-video-player.test.ts +16 -1
  38. package/src/client/__tests__/footnote-rail.test.ts +472 -0
  39. package/src/client/__tests__/hydrate-partial.test.ts +3 -0
  40. package/src/client/__tests__/toast.test.ts +18 -1
  41. package/src/client/collection-created-notice.ts +66 -0
  42. package/src/client/collection-form-bridge.ts +17 -0
  43. package/src/client/collection-navigation.ts +44 -0
  44. package/src/client/collection-page-actions.ts +57 -1
  45. package/src/client/components/__tests__/jant-collection-directory.test.ts +155 -6
  46. package/src/client/components/__tests__/jant-compose-dialog.test.ts +370 -5
  47. package/src/client/components/__tests__/jant-compose-editor.test.ts +97 -0
  48. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +104 -0
  49. package/src/client/components/__tests__/jant-config-editor.test.ts +405 -0
  50. package/src/client/components/__tests__/jant-media-lightbox.test.ts +121 -0
  51. package/src/client/components/__tests__/jant-nav-manager.test.ts +538 -2
  52. package/src/client/components/__tests__/jant-post-form.test.ts +2 -2
  53. package/src/client/components/__tests__/jant-post-menu.test.ts +47 -5
  54. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -2
  55. package/src/client/components/__tests__/jant-settings-general.test.ts +3 -3
  56. package/src/client/components/collection-manager-types.ts +9 -3
  57. package/src/client/components/compose-types.ts +8 -0
  58. package/src/client/components/jant-collection-directory.ts +165 -30
  59. package/src/client/components/jant-command-palette.ts +2 -1
  60. package/src/client/components/jant-compose-dialog.ts +85 -21
  61. package/src/client/components/jant-compose-editor.ts +13 -7
  62. package/src/client/components/jant-compose-fullscreen.ts +24 -4
  63. package/src/client/components/jant-config-editor.ts +722 -0
  64. package/src/client/components/jant-media-lightbox.ts +100 -18
  65. package/src/client/components/jant-nav-manager.ts +1283 -54
  66. package/src/client/components/jant-post-form.ts +3 -3
  67. package/src/client/components/jant-post-menu.ts +86 -74
  68. package/src/client/components/nav-manager-types.ts +51 -2
  69. package/src/client/components/post-form-types.ts +1 -1
  70. package/src/client/compose-bridge.ts +4 -1
  71. package/src/client/feed-video-player.ts +62 -2
  72. package/src/client/footnote-rail.ts +478 -0
  73. package/src/client/hydrate-partial.ts +2 -0
  74. package/src/client/media-lightbox-events.ts +63 -0
  75. package/src/client/site-header-fragment.ts +38 -0
  76. package/src/client/tiptap/__tests__/blockquote-editing.test.ts +201 -0
  77. package/src/client/tiptap/__tests__/clear-formatting.test.ts +168 -0
  78. package/src/client/tiptap/__tests__/list-editing.test.ts +447 -0
  79. package/src/client/tiptap/__tests__/markdown-clipboard.test.ts +727 -4
  80. package/src/client/tiptap/__tests__/table-controls.test.ts +263 -0
  81. package/src/client/tiptap/__tests__/table-size-picker.test.ts +179 -0
  82. package/src/client/tiptap/bubble-menu.ts +88 -10
  83. package/src/client/tiptap/continuous-lists.ts +102 -0
  84. package/src/client/tiptap/create-editor.ts +3 -0
  85. package/src/client/tiptap/extensions.ts +14 -5
  86. package/src/client/tiptap/markdown-clipboard.ts +608 -11
  87. package/src/client/tiptap/slash-commands.ts +8 -1
  88. package/src/client/tiptap/structural-keymap.ts +211 -0
  89. package/src/client/tiptap/tab-indent.ts +8 -22
  90. package/src/client/tiptap/table-control-labels.ts +30 -0
  91. package/src/client/tiptap/table-controls.ts +537 -0
  92. package/src/client/tiptap/table-size-picker.ts +254 -0
  93. package/src/client/toast.ts +8 -6
  94. package/src/client-auth.ts +1 -0
  95. package/src/client.ts +1 -0
  96. package/src/db/__tests__/d1-query.test.ts +43 -1
  97. package/src/db/__tests__/migration-runner.test.ts +63 -0
  98. package/src/db/__tests__/migrations.test.ts +469 -0
  99. package/src/db/__tests__/thread-collection-migration.test.ts +364 -0
  100. package/src/db/migrations/0027_old_blue_blade.sql +39 -0
  101. package/src/db/migrations/0028_superb_post.sql +2 -0
  102. package/src/db/migrations/0029_boring_magma.sql +1 -0
  103. package/src/db/migrations/0030_typical_vivisector.sql +50 -0
  104. package/src/db/migrations/meta/0027_snapshot.json +2511 -0
  105. package/src/db/migrations/meta/0028_snapshot.json +2511 -0
  106. package/src/db/migrations/meta/0029_snapshot.json +2519 -0
  107. package/src/db/migrations/meta/0030_snapshot.json +2541 -0
  108. package/src/db/migrations/meta/_journal.json +28 -0
  109. package/src/db/migrations/pg/0025_moaning_epoch.sql +39 -0
  110. package/src/db/migrations/pg/0026_talented_killer_shrike.sql +2 -0
  111. package/src/db/migrations/pg/0027_short_cannonball.sql +1 -0
  112. package/src/db/migrations/pg/0028_perfect_sharon_carter.sql +27 -0
  113. package/src/db/migrations/pg/meta/0025_snapshot.json +3204 -0
  114. package/src/db/migrations/pg/meta/0026_snapshot.json +3204 -0
  115. package/src/db/migrations/pg/meta/0027_snapshot.json +3211 -0
  116. package/src/db/migrations/pg/meta/0028_snapshot.json +3248 -0
  117. package/src/db/migrations/pg/meta/_journal.json +28 -0
  118. package/src/db/pg/schema.ts +28 -20
  119. package/src/db/rehearsal-fixtures/demo-current.json +39 -4
  120. package/src/db/rehearsal-fixtures/demo-current.sql +121 -6
  121. package/src/db/schema.ts +28 -20
  122. package/src/i18n/__tests__/detect.test.ts +8 -2
  123. package/src/i18n/detect.ts +14 -5
  124. package/src/i18n/locales/public/en.po +133 -30
  125. package/src/i18n/locales/public/en.ts +1 -1
  126. package/src/i18n/locales/public/zh-Hans.po +128 -25
  127. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  128. package/src/i18n/locales/public/zh-Hant.po +128 -25
  129. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  130. package/src/i18n/locales/settings/en.po +513 -10
  131. package/src/i18n/locales/settings/en.ts +1 -1
  132. package/src/i18n/locales/settings/zh-Hans.po +515 -12
  133. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  134. package/src/i18n/locales/settings/zh-Hant.po +515 -12
  135. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  136. package/src/lib/__tests__/api-settings.test.ts +299 -0
  137. package/src/lib/__tests__/constants.test.ts +5 -0
  138. package/src/lib/__tests__/feed-policy.test.ts +25 -0
  139. package/src/lib/__tests__/footnotes-upgrade.test.ts +181 -0
  140. package/src/lib/__tests__/markdown-roundtrip-embed.test.ts +61 -0
  141. package/src/lib/__tests__/markdown-to-tiptap.test.ts +32 -4
  142. package/src/lib/__tests__/markdown.test.ts +8 -6
  143. package/src/lib/__tests__/navigation.test.ts +59 -2
  144. package/src/lib/__tests__/post-body-html.test.ts +176 -0
  145. package/src/lib/__tests__/post-display.test.ts +64 -0
  146. package/src/lib/__tests__/resolve-config.test.ts +113 -1
  147. package/src/lib/__tests__/summary.test.ts +82 -0
  148. package/src/lib/__tests__/timeline.test.ts +109 -70
  149. package/src/lib/__tests__/timezones.test.ts +31 -3
  150. package/src/lib/__tests__/tiptap-render.test.ts +268 -9
  151. package/src/lib/__tests__/tiptap-to-markdown.test.ts +6 -0
  152. package/src/lib/__tests__/view.test.ts +53 -0
  153. package/src/lib/__tests__/worker-response-cache.test.ts +18 -1
  154. package/src/lib/api-settings.ts +239 -12
  155. package/src/lib/collection-sort.ts +7 -5
  156. package/src/lib/constants.ts +2 -0
  157. package/src/lib/feed-path.ts +25 -0
  158. package/src/lib/feed-policy.ts +64 -0
  159. package/src/lib/footnotes.ts +264 -0
  160. package/src/lib/github-sync-site-config.ts +2 -0
  161. package/src/lib/hugo-markdown.ts +10 -3
  162. package/src/lib/jant-branding.ts +1 -0
  163. package/src/lib/markdown-manager.ts +188 -5
  164. package/src/lib/markdown.ts +10 -3
  165. package/src/lib/navigation.ts +16 -3
  166. package/src/lib/post-body-html.ts +131 -0
  167. package/src/lib/post-display.ts +7 -1
  168. package/src/lib/render.tsx +9 -2
  169. package/src/lib/resolve-config.ts +100 -16
  170. package/src/lib/schemas.ts +133 -1
  171. package/src/lib/settings-paths.ts +5 -0
  172. package/src/lib/site-skill.ts +78 -0
  173. package/src/lib/summary.ts +16 -5
  174. package/src/lib/timeline.ts +98 -122
  175. package/src/lib/timezones.ts +54 -14
  176. package/src/lib/tiptap-render.ts +355 -81
  177. package/src/lib/view.ts +30 -20
  178. package/src/lib/worker-response-cache.ts +11 -18
  179. package/src/middleware/__tests__/public-content-access.test.ts +91 -0
  180. package/src/middleware/config.ts +2 -2
  181. package/src/middleware/public-content-access.ts +87 -0
  182. package/src/node/__tests__/cli-export.test.ts +14 -0
  183. package/src/node/__tests__/cli-migrate.test.ts +6 -0
  184. package/src/node/__tests__/cli-site-snapshot.test.ts +205 -2
  185. package/src/node/__tests__/cli-snapshot-meta.test.ts +100 -0
  186. package/src/node/index.ts +2 -1
  187. package/src/preset.css +16 -2
  188. package/src/routes/__tests__/compose.test.ts +51 -0
  189. package/src/routes/__tests__/site-skill.test.ts +112 -0
  190. package/src/routes/api/__tests__/collections.test.ts +91 -19
  191. package/src/routes/api/__tests__/mcp.test.ts +65 -1
  192. package/src/routes/api/__tests__/nav-items.test.ts +91 -0
  193. package/src/routes/api/__tests__/public-access.test.ts +81 -0
  194. package/src/routes/api/__tests__/settings.test.ts +217 -0
  195. package/src/routes/api/collections.ts +22 -26
  196. package/src/routes/api/export.ts +2 -0
  197. package/src/routes/api/internal/__tests__/post-body-html.test.ts +163 -0
  198. package/src/routes/api/internal/__tests__/sites.test.ts +35 -2
  199. package/src/routes/api/internal/post-body-html.ts +43 -0
  200. package/src/routes/api/internal/sites.ts +24 -0
  201. package/src/routes/api/nav-items.ts +24 -1
  202. package/src/routes/api/posts.ts +2 -2
  203. package/src/routes/api/public/__tests__/archive.test.ts +35 -0
  204. package/src/routes/api/public/__tests__/posts.test.ts +208 -0
  205. package/src/routes/api/public/archive.ts +4 -0
  206. package/src/routes/api/public/posts.ts +41 -24
  207. package/src/routes/api/search.ts +3 -0
  208. package/src/routes/api/settings.ts +33 -0
  209. package/src/routes/auth/__tests__/setup.test.ts +7 -4
  210. package/src/routes/compose.tsx +2 -1
  211. package/src/routes/dash/settings.tsx +45 -2
  212. package/src/routes/feed/__tests__/feed.test.ts +210 -6
  213. package/src/routes/feed/feed.ts +23 -14
  214. package/src/routes/pages/__tests__/archive-params.test.ts +92 -3
  215. package/src/routes/pages/__tests__/collection-routing.test.ts +63 -5
  216. package/src/routes/pages/__tests__/collections.test.ts +9 -9
  217. package/src/routes/pages/__tests__/preview.test.ts +174 -0
  218. package/src/routes/pages/__tests__/search.test.ts +69 -0
  219. package/src/routes/pages/archive.tsx +27 -7
  220. package/src/routes/pages/collection.tsx +32 -19
  221. package/src/routes/pages/collections.tsx +6 -1
  222. package/src/routes/pages/page.tsx +83 -11
  223. package/src/routes/site-skill.ts +18 -0
  224. package/src/services/__tests__/auth.test.ts +7 -5
  225. package/src/services/__tests__/collection.test.ts +159 -44
  226. package/src/services/__tests__/navigation.test.ts +229 -21
  227. package/src/services/__tests__/post-timeline.test.ts +109 -30
  228. package/src/services/__tests__/post.test.ts +567 -5
  229. package/src/services/__tests__/search.test.ts +48 -1
  230. package/src/services/__tests__/settings.test.ts +8 -0
  231. package/src/services/__tests__/site-admin.test.ts +121 -1
  232. package/src/services/auth.ts +2 -2
  233. package/src/services/bootstrap.ts +3 -2
  234. package/src/services/collection.ts +226 -167
  235. package/src/services/export-theme/assets/client-site.css +1 -1
  236. package/src/services/export-theme/assets/client-site.js +46 -25
  237. package/src/services/export-theme/layouts/_default/list.html +18 -4
  238. package/src/services/export-theme/layouts/_default/rss.xml +8 -12
  239. package/src/services/export-theme/layouts/featured/list.html +5 -4
  240. package/src/services/export-theme/layouts/partials/featured-thread.html +56 -0
  241. package/src/services/export-theme/layouts/partials/footer.html +1 -1
  242. package/src/services/export-theme/layouts/partials/media-gallery.html +1 -0
  243. package/src/services/export-theme/styles/main.css +30 -2
  244. package/src/services/export.ts +91 -80
  245. package/src/services/mcp.ts +37 -27
  246. package/src/services/media.ts +1 -1
  247. package/src/services/navigation.ts +185 -46
  248. package/src/services/post.ts +874 -271
  249. package/src/services/search.ts +8 -2
  250. package/src/services/settings.ts +2 -10
  251. package/src/services/site-admin.ts +63 -43
  252. package/src/style-cjk-jp.css +1 -1
  253. package/src/style-cjk-kr.css +1 -1
  254. package/src/style-cjk-tc.css +1 -1
  255. package/src/style-cjk.css +1 -1
  256. package/src/styles/components.css +540 -0
  257. package/src/styles/site-media.css +111 -19
  258. package/src/styles/tokens.css +14 -9
  259. package/src/styles/ui.css +829 -29
  260. package/src/types/bindings.ts +3 -0
  261. package/src/types/config.ts +243 -12
  262. package/src/types/constants.ts +22 -1
  263. package/src/types/entities.ts +9 -8
  264. package/src/types/operations.ts +18 -10
  265. package/src/types/props.ts +8 -2
  266. package/src/types/raw-assets.d.ts +5 -0
  267. package/src/types/views.ts +16 -4
  268. package/src/ui/__tests__/font-themes.test.ts +42 -30
  269. package/src/ui/compose/ComposeDialog.tsx +92 -2
  270. package/src/ui/dash/appearance/NavigationContent.tsx +332 -4
  271. package/src/ui/dash/appearance/__tests__/NavigationContent.test.tsx +23 -0
  272. package/src/ui/dash/settings/ConfigEditorContent.tsx +578 -0
  273. package/src/ui/dash/settings/GeneralContent.tsx +15 -5
  274. package/src/ui/dash/settings/SettingsRootContent.tsx +21 -1
  275. package/src/ui/dash/settings/__tests__/ConfigEditorContent.test.tsx +98 -0
  276. package/src/ui/dash/settings/__tests__/SettingsRootContent.test.tsx +15 -0
  277. package/src/ui/feed/CuratedThreadPreview.tsx +16 -4
  278. package/src/ui/feed/LinkCard.tsx +2 -12
  279. package/src/ui/feed/NoteCard.tsx +10 -20
  280. package/src/ui/feed/QuoteCard.tsx +2 -12
  281. package/src/ui/feed/ThreadPreview.tsx +26 -34
  282. package/src/ui/feed/TimelineFeed.tsx +2 -2
  283. package/src/ui/feed/__tests__/thread-preview.test.ts +197 -56
  284. package/src/ui/feed/__tests__/timeline-cards.test.ts +178 -3
  285. package/src/ui/feed/thread-preview-state.ts +5 -7
  286. package/src/ui/font-themes.ts +74 -26
  287. package/src/ui/layouts/BaseLayout.tsx +22 -8
  288. package/src/ui/layouts/__tests__/BaseLayout.test.tsx +64 -1
  289. package/src/ui/pages/CollectionPage.tsx +101 -22
  290. package/src/ui/pages/CollectionsPage.tsx +3 -1
  291. package/src/ui/pages/PostPage.tsx +39 -12
  292. package/src/ui/pages/SearchPage.tsx +6 -14
  293. package/src/ui/pages/__tests__/ArchivePage.test.tsx +7 -0
  294. package/src/ui/pages/__tests__/PostPage.test.tsx +88 -0
  295. package/src/ui/shared/CollectionDirectory.tsx +13 -9
  296. package/src/ui/shared/CollectionsManager.tsx +17 -7
  297. package/src/ui/shared/DraftPreviewBar.tsx +33 -0
  298. package/src/ui/shared/HomePageBranding.tsx +2 -2
  299. package/src/ui/shared/MediaGallery.tsx +1 -0
  300. package/src/ui/shared/PostFooter.tsx +3 -1
  301. package/src/ui/shared/__tests__/home-page-branding.test.tsx +1 -1
  302. package/src/ui/shared/__tests__/media-gallery.test.ts +1 -0
  303. package/src/ui/shared/__tests__/navigation-labels.test.ts +2 -2
  304. package/src/ui/shared/__tests__/post-footer.test.ts +15 -0
  305. package/src/ui/shared/collection-management-labels.ts +33 -2
  306. package/src/ui/shared/navigation-labels.ts +2 -4
  307. package/src/ui/shared/post-article-attributes.ts +46 -0
  308. package/dist/app-DZDlmh7C.js +0 -6
  309. package/dist/client/_assets/client-Cevfpm8H.css +0 -2
  310. package/dist/client/_assets/client-auth-v3yvhbgZ.js +0 -4736
  311. package/src/client/tiptap/wrapping-input-rules.ts +0 -102
package/src/styles/ui.css CHANGED
@@ -30,6 +30,73 @@
30
30
  counter-reset: sidenote-counter;
31
31
  }
32
32
 
33
+ /* Authenticated utility chrome lives outside .site-page so the previewed
34
+ theme layout remains the same subtree visitors receive after publishing. */
35
+ .draft-preview-bar {
36
+ position: sticky;
37
+ top: 0;
38
+ z-index: 40;
39
+ width: 100%;
40
+ border-bottom: 1px solid
41
+ color-mix(in srgb, var(--site-divider) 78%, transparent);
42
+ background: color-mix(
43
+ in srgb,
44
+ var(--site-elevated-bg) 94%,
45
+ var(--site-nav-hover-bg)
46
+ );
47
+ color: var(--site-text-secondary);
48
+ font-family: var(--font-ui);
49
+ box-shadow: 0 1px 0
50
+ color-mix(in srgb, var(--site-elevated-bg) 70%, transparent);
51
+ backdrop-filter: blur(14px);
52
+ -webkit-backdrop-filter: blur(14px);
53
+ }
54
+
55
+ .draft-preview-bar-inner {
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ gap: 0.7rem;
60
+ min-height: 2.5rem;
61
+ max-width: var(--layout-body-max-width);
62
+ margin: 0 auto;
63
+ padding: 0.45rem var(--site-padding);
64
+ font-size: var(--type-ui-caption);
65
+ line-height: 1.35;
66
+ text-align: center;
67
+ }
68
+
69
+ .draft-preview-bar-label {
70
+ display: inline-flex;
71
+ align-items: center;
72
+ gap: 0.42rem;
73
+ flex: none;
74
+ color: var(--site-text-primary);
75
+ font-weight: var(--fw-semibold);
76
+ letter-spacing: var(--type-label-tracking);
77
+ text-transform: uppercase;
78
+ }
79
+
80
+ .draft-preview-bar-dot {
81
+ width: 0.42rem;
82
+ height: 0.42rem;
83
+ border-radius: 999px;
84
+ background: var(--site-accent);
85
+ box-shadow: 0 0 0 3px
86
+ color-mix(in srgb, var(--site-accent) 12%, transparent);
87
+ }
88
+
89
+ .draft-preview-bar-description {
90
+ color: var(--site-text-secondary);
91
+ }
92
+
93
+ @media (max-width: 480px) {
94
+ .draft-preview-bar-inner {
95
+ flex-direction: column;
96
+ gap: 0.18rem;
97
+ }
98
+ }
99
+
33
100
  /*
34
101
  * Tufte horizontal frame — single source of truth for left/right positioning.
35
102
  * Every page-level section gets the same asymmetric padding.
@@ -1127,6 +1194,148 @@
1127
1194
  align-items: center;
1128
1195
  }
1129
1196
 
1197
+ .collection-created-notice {
1198
+ display: flex;
1199
+ align-items: center;
1200
+ flex-wrap: wrap;
1201
+ gap: 0.4rem 0.7rem;
1202
+ width: fit-content;
1203
+ max-width: 100%;
1204
+ min-height: 2rem;
1205
+ margin-bottom: 0.7rem;
1206
+ padding: 0.35rem 0.45rem 0.35rem 0.65rem;
1207
+ border-radius: 0.55rem;
1208
+ background: color-mix(in srgb, var(--site-accent) 5%, var(--site-page-bg));
1209
+ box-shadow: inset 2px 0 0
1210
+ color-mix(in srgb, var(--site-accent) 30%, transparent);
1211
+ }
1212
+
1213
+ .collection-created-notice-message {
1214
+ margin: 0;
1215
+ color: color-mix(
1216
+ in srgb,
1217
+ var(--site-text-secondary) 82%,
1218
+ var(--site-text-primary)
1219
+ );
1220
+ font-family: var(--font-ui);
1221
+ font-size: var(--type-sm);
1222
+ line-height: 1.35;
1223
+ }
1224
+
1225
+ @keyframes collection-created-notice-enter {
1226
+ from {
1227
+ opacity: 0;
1228
+ transform: translateY(0.25rem);
1229
+ }
1230
+
1231
+ to {
1232
+ opacity: 1;
1233
+ transform: translateY(0);
1234
+ }
1235
+ }
1236
+
1237
+ @keyframes collection-created-notice-attention {
1238
+ from {
1239
+ background: color-mix(
1240
+ in srgb,
1241
+ var(--site-accent) 11%,
1242
+ var(--site-page-bg)
1243
+ );
1244
+ box-shadow: inset 2px 0 0
1245
+ color-mix(in srgb, var(--site-accent) 52%, transparent);
1246
+ }
1247
+
1248
+ to {
1249
+ background: color-mix(
1250
+ in srgb,
1251
+ var(--site-accent) 5%,
1252
+ var(--site-page-bg)
1253
+ );
1254
+ box-shadow: inset 2px 0 0
1255
+ color-mix(in srgb, var(--site-accent) 30%, transparent);
1256
+ }
1257
+ }
1258
+
1259
+ @media (prefers-reduced-motion: no-preference) {
1260
+ .collection-created-notice {
1261
+ animation:
1262
+ collection-created-notice-enter 360ms cubic-bezier(0.22, 1, 0.36, 1)
1263
+ both,
1264
+ collection-created-notice-attention 1.8s 120ms ease-out both;
1265
+ }
1266
+ }
1267
+
1268
+ .collection-created-notice-actions {
1269
+ display: flex;
1270
+ align-items: center;
1271
+ flex: 0 0 auto;
1272
+ gap: 0.35rem;
1273
+ }
1274
+
1275
+ .collection-created-notice-action {
1276
+ appearance: none;
1277
+ display: inline-flex;
1278
+ align-items: center;
1279
+ min-height: 2rem;
1280
+ padding: 0.2rem 0;
1281
+ border: 0;
1282
+ background: transparent;
1283
+ font-family: var(--font-ui);
1284
+ font-size: var(--type-sm);
1285
+ line-height: 1.35;
1286
+ text-decoration: none;
1287
+ text-underline-offset: 0.22em;
1288
+ cursor: pointer;
1289
+ }
1290
+
1291
+ .collection-created-notice-action-primary {
1292
+ color: var(--site-text-primary);
1293
+ font-weight: var(--fw-medium);
1294
+ }
1295
+
1296
+ .collection-created-notice-action-primary:hover {
1297
+ color: var(--site-text-primary);
1298
+ text-decoration: underline;
1299
+ }
1300
+
1301
+ .collection-created-notice-action:focus-visible {
1302
+ outline: 2px solid color-mix(in srgb, var(--site-accent) 28%, transparent);
1303
+ outline-offset: 3px;
1304
+ border-radius: var(--radius-sm, 0.25rem);
1305
+ }
1306
+
1307
+ .collection-created-notice-action:disabled {
1308
+ opacity: 0.5;
1309
+ cursor: wait;
1310
+ }
1311
+
1312
+ .collection-created-notice-dismiss {
1313
+ appearance: none;
1314
+ display: inline-flex;
1315
+ align-items: center;
1316
+ justify-content: center;
1317
+ width: 1.75rem;
1318
+ height: 1.75rem;
1319
+ padding: 0;
1320
+ border: 0;
1321
+ border-radius: 999px;
1322
+ background: transparent;
1323
+ color: var(--site-text-secondary);
1324
+ opacity: 0.65;
1325
+ cursor: pointer;
1326
+ }
1327
+
1328
+ .collection-created-notice-dismiss:hover {
1329
+ background: color-mix(in srgb, var(--site-accent) 6%, transparent);
1330
+ color: var(--site-text-primary);
1331
+ opacity: 1;
1332
+ }
1333
+
1334
+ .collection-created-notice-dismiss:focus-visible {
1335
+ outline: 2px solid color-mix(in srgb, var(--site-accent) 28%, transparent);
1336
+ outline-offset: 2px;
1337
+ }
1338
+
1130
1339
  .collections-page-actions {
1131
1340
  display: flex;
1132
1341
  align-items: center;
@@ -1440,6 +1649,15 @@
1440
1649
  position: absolute;
1441
1650
  top: 0.7rem;
1442
1651
  right: 0;
1652
+ opacity: 0;
1653
+ pointer-events: none;
1654
+ }
1655
+
1656
+ .collection-directory-managed-row:hover .collection-directory-item-menu,
1657
+ .collection-directory-managed-row:focus-within
1658
+ .collection-directory-item-menu {
1659
+ opacity: 1;
1660
+ pointer-events: auto;
1443
1661
  }
1444
1662
 
1445
1663
  .collections-link-create,
@@ -2080,6 +2298,13 @@
2080
2298
  }
2081
2299
  }
2082
2300
 
2301
+ @media (hover: none) {
2302
+ .collection-directory-item-menu {
2303
+ opacity: 1;
2304
+ pointer-events: auto;
2305
+ }
2306
+ }
2307
+
2083
2308
  /* =========================================================================
2084
2309
  * Timeline & Post Components
2085
2310
  * ========================================================================= */
@@ -2285,7 +2510,7 @@
2285
2510
  .post-header-meta-link {
2286
2511
  color: var(--site-text-secondary);
2287
2512
  font-family: var(--font-ui);
2288
- font-size: var(--type-ui-meta);
2513
+ font-size: var(--type-ui-hint);
2289
2514
  text-decoration: none;
2290
2515
  white-space: nowrap;
2291
2516
  }
@@ -2332,19 +2557,13 @@
2332
2557
  [data-page="post"] .feed-quote-attribution,
2333
2558
  [data-page="post"] .post-header-meta-row,
2334
2559
  [data-page="post"] .post-header-meta-link,
2335
- [data-page="post"] .post-header-meta-link time,
2336
- [data-page="post"] [data-post-meta],
2337
- [data-page="post"] [data-post-meta] .post-footer-meta,
2338
- [data-page="post"] [data-post-meta] a,
2339
- [data-page="post"] [data-post-meta] time {
2560
+ [data-page="post"] .post-header-meta-link time {
2340
2561
  color: var(--site-reading-meta);
2341
2562
  }
2342
2563
 
2343
2564
  [data-page="post"] .feed-link-domain:hover,
2344
2565
  [data-page="post"] .feed-quote-source:hover,
2345
- [data-page="post"] .post-header-meta-link:hover,
2346
- [data-page="post"] [data-post-meta] a:hover,
2347
- [data-page="post"] [data-post-meta] .post-footer-external-link:hover {
2566
+ [data-page="post"] .post-header-meta-link:hover {
2348
2567
  color: var(--site-reading-body);
2349
2568
  }
2350
2569
 
@@ -2356,9 +2575,128 @@
2356
2575
  );
2357
2576
  }
2358
2577
 
2359
- /* --- Sidenotes (Tufte-style margin notes) ------------------------------ */
2578
+ /* --- Semantic footnotes with Tufte presentation ------------------------ */
2579
+
2580
+ /* The published DOM stays semantic and readable without CSS: body blocks
2581
+ come first, followed by doc-endnotes containing one native ordered list.
2582
+ Detail and timeline posts use their real container width to opt into the
2583
+ Tufte rail; narrow containers, print, feeds, and readers retain endnote
2584
+ order. */
2585
+ [data-post-view],
2586
+ [data-timeline-item] {
2587
+ container: post-view / inline-size;
2588
+ }
2589
+
2590
+ .footnote-endnotes {
2591
+ margin-block: 1.5rem 0;
2592
+ padding-block-start: 0.85rem;
2593
+ border-block-start: 1px solid var(--site-divider);
2594
+ }
2595
+
2596
+ .footnote-list {
2597
+ margin-block: 0;
2598
+ padding-inline-start: 1.5rem;
2599
+ }
2360
2600
 
2361
- .sidenote,
2601
+ .footnote {
2602
+ scroll-margin-block-start: 2rem;
2603
+ padding-inline-start: 0.15rem;
2604
+ overflow-wrap: anywhere;
2605
+ font-family: var(--font-body);
2606
+ font-size: var(--type-sm);
2607
+ font-style: normal;
2608
+ font-weight: 400;
2609
+ font-variant: normal;
2610
+ line-height: 1.3;
2611
+ letter-spacing: var(--type-body-tracking, normal);
2612
+ text-align: left;
2613
+ color: var(--site-footnote-text);
2614
+ }
2615
+
2616
+ .footnote::marker {
2617
+ color: inherit;
2618
+ font-size: var(--type-xs);
2619
+ font-variant-numeric: tabular-nums;
2620
+ }
2621
+
2622
+ .footnote:target {
2623
+ border-radius: var(--radius-sm, 0.25rem);
2624
+ background: color-mix(in srgb, var(--site-reading-meta) 8%, transparent);
2625
+ }
2626
+
2627
+ .footnote > :first-child {
2628
+ margin-block-start: 0;
2629
+ }
2630
+
2631
+ .footnote > :last-child {
2632
+ margin-block-end: 0;
2633
+ }
2634
+
2635
+ .footnote code {
2636
+ font-size: var(--type-code);
2637
+ }
2638
+
2639
+ .footnote-backlinks {
2640
+ display: inline-flex;
2641
+ flex-wrap: wrap;
2642
+ align-items: baseline;
2643
+ gap: 0.35rem;
2644
+ font-size: var(--type-xs);
2645
+ }
2646
+
2647
+ span.footnote-backlinks {
2648
+ margin-inline-start: 0.35rem;
2649
+ vertical-align: baseline;
2650
+ }
2651
+
2652
+ p.footnote-backlinks {
2653
+ display: flex;
2654
+ margin-block: 0.45rem 0;
2655
+ }
2656
+
2657
+ .footnote + .footnote {
2658
+ margin-block-start: 0.75rem;
2659
+ }
2660
+
2661
+ .footnote-backref,
2662
+ .footnote-ref > a {
2663
+ color: inherit;
2664
+ text-decoration: none;
2665
+ }
2666
+
2667
+ .footnote-backref:hover,
2668
+ .footnote-ref > a:hover {
2669
+ text-decoration: underline;
2670
+ }
2671
+
2672
+ .footnote-backref:focus-visible,
2673
+ .footnote-ref > a:focus-visible {
2674
+ border-radius: var(--radius-sm, 0.25rem);
2675
+ outline: 1px solid currentColor;
2676
+ outline-offset: 2px;
2677
+ }
2678
+
2679
+ .footnote-ref {
2680
+ --site-prose-link-color: var(--site-footnote-marker);
2681
+ --site-prose-link-hover: var(--site-footnote-marker);
2682
+ --site-prose-link-underline: transparent;
2683
+
2684
+ margin-inline: 0.25rem;
2685
+ vertical-align: super;
2686
+ line-height: 0;
2687
+ font-size: var(--type-footnote-ref);
2688
+ font-variant-numeric: tabular-nums;
2689
+ white-space: nowrap;
2690
+ color: var(--site-footnote-marker);
2691
+ }
2692
+
2693
+ /* --- Legacy v1 sidenotes ----------------------------------------------- */
2694
+
2695
+ /* Kept for one compatibility window so imported snapshots or cached HTML
2696
+ from an older runtime remain readable during rollout. Product reads render
2697
+ stale post projections as v3 before the database rebuild runs. */
2698
+
2699
+ .sidenote:not([role="doc-footnote"]),
2362
2700
  .marginnote {
2363
2701
  /* Layout: float into the right margin. A floated note anchors to its containing
2364
2702
  block's right *content* edge, so if a theme gives that block inline-end padding
@@ -2401,7 +2739,7 @@
2401
2739
  }
2402
2740
 
2403
2741
  .sidenote-number::after,
2404
- .sidenote::before {
2742
+ .sidenote:not([role="doc-footnote"])::before {
2405
2743
  position: relative;
2406
2744
  vertical-align: baseline;
2407
2745
  }
@@ -2417,13 +2755,13 @@
2417
2755
  margin-right: 0.25rem;
2418
2756
  }
2419
2757
 
2420
- .sidenote::before {
2758
+ .sidenote:not([role="doc-footnote"])::before {
2421
2759
  content: counter(sidenote-counter) " ";
2422
2760
  font-size: var(--type-xs);
2423
2761
  top: -0.5rem;
2424
2762
  }
2425
2763
 
2426
- .sidenote > code,
2764
+ .sidenote:not([role="doc-footnote"]) > code,
2427
2765
  .marginnote > code {
2428
2766
  font-size: var(--type-code);
2429
2767
  }
@@ -2452,12 +2790,12 @@
2452
2790
  cursor: pointer;
2453
2791
  }
2454
2792
 
2455
- .sidenote,
2793
+ .sidenote:not([role="doc-footnote"]),
2456
2794
  .marginnote {
2457
2795
  display: none;
2458
2796
  }
2459
2797
 
2460
- .margin-toggle:checked + .sidenote,
2798
+ .margin-toggle:checked + .sidenote:not([role="doc-footnote"]),
2461
2799
  .margin-toggle:checked + .marginnote {
2462
2800
  display: block;
2463
2801
  float: left;
@@ -3005,6 +3343,10 @@
3005
3343
  line-height: 1.3;
3006
3344
  }
3007
3345
 
3346
+ .feed-quote-attribution:has(+ .post-menu-footer) {
3347
+ margin-bottom: 0.45rem;
3348
+ }
3349
+
3008
3350
  .feed-quote-attribution::before {
3009
3351
  content: "";
3010
3352
  width: 0.9rem;
@@ -3183,10 +3525,17 @@
3183
3525
 
3184
3526
  .post-footer-detail {
3185
3527
  margin-top: 24px;
3186
- font-size: var(--type-ui-meta);
3528
+ font-size: var(--type-ui-hint);
3187
3529
  color: var(--site-text-secondary);
3188
3530
  }
3189
3531
 
3532
+ /* A standalone detail footer needs extra breathing room to close the
3533
+ reading surface. Thread entries keep a smaller content gap so their
3534
+ metadata stays connected without crowding the post body. */
3535
+ .thread-group-detail .post-footer-detail {
3536
+ margin-top: var(--content-gap);
3537
+ }
3538
+
3190
3539
  .post-footer-meta {
3191
3540
  display: flex;
3192
3541
  flex: 1 1 auto;
@@ -5621,6 +5970,10 @@
5621
5970
  z-index: 6;
5622
5971
  }
5623
5972
 
5973
+ .compose-action-row-without-collection {
5974
+ justify-content: flex-end;
5975
+ }
5976
+
5624
5977
  @media (min-width: 700px) {
5625
5978
  .compose-action-row {
5626
5979
  padding: 12px 18px 16px;
@@ -6712,6 +7065,7 @@
6712
7065
  text-align: left;
6713
7066
  font-size: var(--compose-panel-control-size);
6714
7067
  color: var(--site-text-primary);
7068
+ text-decoration: none;
6715
7069
  transition: background-color 0.12s ease;
6716
7070
  }
6717
7071
 
@@ -6719,6 +7073,12 @@
6719
7073
  background-color: var(--compose-control-bg);
6720
7074
  }
6721
7075
 
7076
+ .compose-dropdown-separator {
7077
+ height: 1px;
7078
+ margin: 4px 8px;
7079
+ background-color: var(--compose-control-border);
7080
+ }
7081
+
6722
7082
  .compose-slug-suggestion {
6723
7083
  display: grid;
6724
7084
  grid-template-columns: minmax(0, 1fr) auto;
@@ -8936,6 +9296,9 @@
8936
9296
  .compose-tiptap-body .tiptap {
8937
9297
  outline: none;
8938
9298
  min-height: 120px;
9299
+ min-width: 0;
9300
+ max-width: 100%;
9301
+ overflow-x: auto;
8939
9302
  font-size: var(--type-content-body);
8940
9303
  line-height: var(--type-body-leading);
8941
9304
  letter-spacing: var(--type-body-tracking);
@@ -9027,26 +9390,66 @@
9027
9390
 
9028
9391
  .compose-tiptap-body .tiptap ul {
9029
9392
  list-style-type: disc;
9030
- padding-left: 1.5em;
9031
- margin: 0.25em 0;
9393
+ padding-left: 1.4em;
9394
+ margin: 1.25em 0;
9032
9395
  }
9033
9396
 
9034
9397
  .compose-tiptap-body .tiptap ol {
9035
9398
  list-style-type: decimal;
9399
+ /* Keep multi-digit markers inside the editor's scrollable bounds. */
9400
+ padding-left: 2.25em;
9401
+ margin: 1.25em 0;
9402
+ }
9403
+
9404
+ .compose-tiptap-body .tiptap :is(ul, ol) :is(ul, ol) {
9036
9405
  padding-left: 1.5em;
9037
- margin: 0.25em 0;
9406
+ margin: 0.4em 0;
9407
+ }
9408
+
9409
+ .compose-tiptap-body .tiptap ul ul {
9410
+ list-style-type: circle;
9411
+ }
9412
+
9413
+ .compose-tiptap-body .tiptap ul ul ul {
9414
+ list-style-type: square;
9415
+ }
9416
+
9417
+ .compose-tiptap-body .tiptap ol ol {
9418
+ list-style-type: lower-alpha;
9419
+ }
9420
+
9421
+ .compose-tiptap-body .tiptap ol ol ol {
9422
+ list-style-type: lower-roman;
9038
9423
  }
9039
9424
 
9040
9425
  .compose-tiptap-body .tiptap li {
9041
- margin: 0.15em 0;
9426
+ margin: 0.5em 0;
9427
+ }
9428
+
9429
+ .compose-tiptap-body .tiptap li > p:first-child {
9430
+ margin-top: 0;
9431
+ }
9432
+
9433
+ .compose-tiptap-body .tiptap li > p:last-child {
9434
+ margin-bottom: 0;
9435
+ }
9436
+
9437
+ /* A nested list makes its leading paragraph no longer the last child, so
9438
+ clear the body-paragraph margin that would otherwise separate the levels. */
9439
+ .compose-tiptap-body .tiptap li > p:has(+ :is(ul, ol)),
9440
+ .compose-tiptap-body
9441
+ .tiptap
9442
+ li
9443
+ > p:has(+ .ProseMirror-gapcursor + :is(ul, ol)) {
9444
+ margin-bottom: 0;
9042
9445
  }
9043
9446
 
9044
9447
  .compose-tiptap-body .tiptap blockquote {
9045
9448
  --_blockquote-bg: var(--compose-blockquote-bg);
9046
9449
  --_blockquote-text: var(--compose-blockquote-text);
9047
9450
  position: relative;
9048
- margin: 0.72em 0;
9049
- padding: 1.3rem 0.9em 0.76em 1em;
9451
+ margin: 1.4rem 0;
9452
+ padding: 1.4rem 1rem 0.75rem;
9050
9453
  border: none;
9051
9454
  border-radius: 6px;
9052
9455
  background: var(--_blockquote-bg);
@@ -9061,6 +9464,35 @@
9061
9464
  color 0.18s ease;
9062
9465
  }
9063
9466
 
9467
+ /* Reply compose deliberately stays denser than ordinary authored prose. */
9468
+ .compose-reply-compose-layout .compose-tiptap-body .tiptap ul {
9469
+ padding-left: 1.5em;
9470
+ margin: 0.25em 0;
9471
+ }
9472
+
9473
+ .compose-reply-compose-layout .compose-tiptap-body .tiptap ol {
9474
+ padding-left: 2.25em;
9475
+ margin: 0.25em 0;
9476
+ }
9477
+
9478
+ .compose-reply-compose-layout
9479
+ .compose-tiptap-body
9480
+ .tiptap
9481
+ :is(ul, ol)
9482
+ :is(ul, ol) {
9483
+ padding-left: 1.5em;
9484
+ margin: 0.2em 0;
9485
+ }
9486
+
9487
+ .compose-reply-compose-layout .compose-tiptap-body .tiptap li {
9488
+ margin: 0.15em 0;
9489
+ }
9490
+
9491
+ .compose-reply-compose-layout .compose-tiptap-body .tiptap blockquote {
9492
+ margin: 0.72em 0;
9493
+ padding: 1.3rem 0.9em 0.76em 1em;
9494
+ }
9495
+
9064
9496
  /* CJK: no italic for blockquotes (no true italic glyph) */
9065
9497
  :lang(zh) .compose-tiptap-body .tiptap blockquote,
9066
9498
  :lang(ja) .compose-tiptap-body .tiptap blockquote,
@@ -9141,9 +9573,9 @@
9141
9573
 
9142
9574
  .compose-tiptap-body .tiptap table {
9143
9575
  border-collapse: collapse;
9144
- width: 100%;
9576
+ width: max-content;
9577
+ min-width: 100%;
9145
9578
  margin: 0.75em 0;
9146
- overflow: hidden;
9147
9579
  }
9148
9580
 
9149
9581
  .compose-tiptap-body .tiptap td,
@@ -9151,7 +9583,7 @@
9151
9583
  border: 1px solid var(--site-divider);
9152
9584
  padding: 0.4em 0.6em;
9153
9585
  vertical-align: top;
9154
- min-width: 1em;
9586
+ min-width: 6rem;
9155
9587
  position: relative;
9156
9588
  }
9157
9589
 
@@ -9164,6 +9596,211 @@
9164
9596
  background: oklch(from var(--site-accent) l c h / 0.12);
9165
9597
  }
9166
9598
 
9599
+ /* Contextual table toolbar — deliberately quieter than the text bubble menu. */
9600
+ .tiptap-table-controls {
9601
+ position: fixed;
9602
+ z-index: 10001;
9603
+ align-items: center;
9604
+ gap: 2px;
9605
+ padding: 3px;
9606
+ border: 1px solid color-mix(in srgb, var(--site-divider) 58%, transparent);
9607
+ border-radius: 11px;
9608
+ background: color-mix(
9609
+ in srgb,
9610
+ var(--site-elevated-bg) 96%,
9611
+ var(--site-nav-hover-bg) 4%
9612
+ );
9613
+ box-shadow:
9614
+ 0 10px 24px oklch(from var(--site-text-primary) l c h / 0.07),
9615
+ 0 2px 5px oklch(from var(--site-text-primary) l c h / 0.035);
9616
+ }
9617
+
9618
+ .tiptap-table-control-btn {
9619
+ display: inline-flex;
9620
+ align-items: center;
9621
+ justify-content: center;
9622
+ width: 2rem;
9623
+ height: 2rem;
9624
+ border-radius: 8px;
9625
+ color: var(--site-text-secondary);
9626
+ cursor: pointer;
9627
+ transition:
9628
+ color 0.15s ease,
9629
+ background-color 0.15s ease;
9630
+ }
9631
+
9632
+ .tiptap-table-control-btn:hover,
9633
+ .tiptap-table-control-btn:focus-visible,
9634
+ .tiptap-table-control-btn[aria-expanded="true"] {
9635
+ color: var(--site-text-primary);
9636
+ background: color-mix(
9637
+ in srgb,
9638
+ var(--site-nav-hover-bg) 78%,
9639
+ var(--site-elevated-bg) 22%
9640
+ );
9641
+ }
9642
+
9643
+ .tiptap-table-control-btn:focus-visible,
9644
+ .tiptap-table-menu-item:focus-visible,
9645
+ .tiptap-table-size-cell:focus-visible {
9646
+ outline: 2px solid color-mix(in srgb, var(--site-accent) 72%, transparent);
9647
+ outline-offset: 1px;
9648
+ }
9649
+
9650
+ .tiptap-table-control-btn:disabled,
9651
+ .tiptap-table-menu-item:disabled {
9652
+ opacity: 0.35;
9653
+ cursor: not-allowed;
9654
+ }
9655
+
9656
+ .tiptap-table-options {
9657
+ position: absolute;
9658
+ top: calc(100% + 6px);
9659
+ left: 50%;
9660
+ width: min(13.5rem, calc(100vw - 20px));
9661
+ padding: 5px;
9662
+ border: 1px solid color-mix(in srgb, var(--site-divider) 58%, transparent);
9663
+ border-radius: 12px;
9664
+ background: var(--site-elevated-bg);
9665
+ box-shadow:
9666
+ 0 14px 28px oklch(from var(--site-text-primary) l c h / 0.08),
9667
+ 0 2px 6px oklch(from var(--site-text-primary) l c h / 0.04);
9668
+ transform: translateX(-50%);
9669
+ }
9670
+
9671
+ .tiptap-table-options[hidden] {
9672
+ display: none;
9673
+ }
9674
+
9675
+ .tiptap-table-menu-item {
9676
+ display: flex;
9677
+ align-items: center;
9678
+ width: 100%;
9679
+ min-height: 2.25rem;
9680
+ padding: 0.45rem 0.65rem;
9681
+ border-radius: 8px;
9682
+ color: var(--site-text-primary);
9683
+ font-size: var(--type-sm);
9684
+ line-height: 1.25;
9685
+ text-align: left;
9686
+ cursor: pointer;
9687
+ }
9688
+
9689
+ .tiptap-table-menu-item:hover,
9690
+ .tiptap-table-menu-item:focus-visible {
9691
+ background: var(--site-nav-hover-bg);
9692
+ }
9693
+
9694
+ .tiptap-table-menu-item.is-destructive {
9695
+ color: var(--destructive);
9696
+ }
9697
+
9698
+ .tiptap-table-options-separator {
9699
+ height: 1px;
9700
+ margin: 4px 5px;
9701
+ background: color-mix(in srgb, var(--site-divider) 58%, transparent);
9702
+ }
9703
+
9704
+ /* Slash-command table size picker. */
9705
+ .tiptap-table-size-picker {
9706
+ position: fixed;
9707
+ z-index: 10002;
9708
+ width: min(17rem, calc(100vw - 20px));
9709
+ padding: 10px;
9710
+ border: 1px solid color-mix(in srgb, var(--site-divider) 58%, transparent);
9711
+ border-radius: 13px;
9712
+ background: linear-gradient(
9713
+ 180deg,
9714
+ color-mix(
9715
+ in srgb,
9716
+ var(--site-elevated-bg) 96%,
9717
+ var(--site-nav-hover-bg) 4%
9718
+ )
9719
+ 0%,
9720
+ var(--site-elevated-bg) 100%
9721
+ );
9722
+ box-shadow:
9723
+ 0 14px 30px oklch(from var(--site-text-primary) l c h / 0.08),
9724
+ 0 2px 6px oklch(from var(--site-text-primary) l c h / 0.035);
9725
+ }
9726
+
9727
+ .tiptap-table-size-picker-header {
9728
+ display: flex;
9729
+ align-items: baseline;
9730
+ justify-content: space-between;
9731
+ gap: 1rem;
9732
+ padding: 0 2px 8px;
9733
+ color: var(--site-text-secondary);
9734
+ font-size: var(--type-xs);
9735
+ font-weight: var(--fw-medium);
9736
+ }
9737
+
9738
+ .tiptap-table-size-picker-dimensions {
9739
+ color: var(--site-text-primary);
9740
+ font-variant-numeric: tabular-nums;
9741
+ }
9742
+
9743
+ .tiptap-table-size-grid {
9744
+ display: grid;
9745
+ gap: 3px;
9746
+ }
9747
+
9748
+ .tiptap-table-size-grid-row {
9749
+ display: grid;
9750
+ grid-template-columns: repeat(8, minmax(0, 1fr));
9751
+ gap: 3px;
9752
+ }
9753
+
9754
+ .tiptap-table-size-cell {
9755
+ aspect-ratio: 1;
9756
+ min-width: 0;
9757
+ border: 1px solid color-mix(in srgb, var(--site-divider) 68%, transparent);
9758
+ border-radius: 4px;
9759
+ background: color-mix(
9760
+ in srgb,
9761
+ var(--site-elevated-bg) 90%,
9762
+ var(--site-nav-hover-bg) 10%
9763
+ );
9764
+ cursor: pointer;
9765
+ transition:
9766
+ border-color 0.1s ease,
9767
+ background-color 0.1s ease,
9768
+ transform 0.1s ease;
9769
+ }
9770
+
9771
+ .tiptap-table-size-cell.is-selected {
9772
+ border-color: color-mix(
9773
+ in srgb,
9774
+ var(--site-accent) 38%,
9775
+ var(--site-divider)
9776
+ );
9777
+ background: color-mix(
9778
+ in srgb,
9779
+ var(--site-accent) 14%,
9780
+ var(--site-elevated-bg)
9781
+ );
9782
+ }
9783
+
9784
+ .tiptap-table-size-cell.is-current {
9785
+ border-color: color-mix(
9786
+ in srgb,
9787
+ var(--site-accent) 72%,
9788
+ var(--site-divider)
9789
+ );
9790
+ transform: scale(0.9);
9791
+ }
9792
+
9793
+ @media (pointer: coarse) {
9794
+ .tiptap-table-control-btn {
9795
+ width: 2.75rem;
9796
+ height: 2.75rem;
9797
+ }
9798
+
9799
+ .tiptap-table-menu-item {
9800
+ min-height: 2.75rem;
9801
+ }
9802
+ }
9803
+
9167
9804
  /* --- Image figure in editor ------------------------------------------- */
9168
9805
 
9169
9806
  .tiptap-image-figure {
@@ -9847,10 +10484,10 @@
9847
10484
  }
9848
10485
 
9849
10486
  .compose-tiptap-body .tiptap .tiptap-footnote-reference {
9850
- font-size: 0.75em;
10487
+ font-size: var(--type-footnote-ref);
9851
10488
  line-height: 1;
9852
10489
  vertical-align: super;
9853
- color: var(--site-accent);
10490
+ color: var(--site-footnote-marker);
9854
10491
  white-space: nowrap;
9855
10492
  cursor: pointer;
9856
10493
  }
@@ -9858,6 +10495,7 @@
9858
10495
  .compose-tiptap-body
9859
10496
  .tiptap
9860
10497
  .tiptap-footnote-reference.ProseMirror-selectednode {
10498
+ color: var(--site-accent);
9861
10499
  outline: 1px solid var(--site-accent);
9862
10500
  outline-offset: 2px;
9863
10501
  border-radius: 4px;
@@ -9872,7 +10510,7 @@
9872
10510
  }
9873
10511
 
9874
10512
  .compose-tiptap-body .tiptap .tiptap-footnote-definition::before {
9875
- content: attr(data-footnote-label);
10513
+ content: "[^" attr(data-footnote-label) "]:";
9876
10514
  grid-column: 1;
9877
10515
  align-self: start;
9878
10516
  color: var(--site-text-secondary);
@@ -12157,6 +12795,168 @@
12157
12795
  }
12158
12796
  }
12159
12797
 
12798
+ /* ---------------------------------------------------------------------------
12799
+ * Wide public-post footnote rail.
12800
+ *
12801
+ * Kept OUTSIDE @layer components so the runtime layout can override the
12802
+ * unlayered `.prose` list rules. The source HTML remains one natural endnote
12803
+ * list; these selectors only apply after the client has measured a wide detail
12804
+ * or timeline container and confirmed that the rail fits safely.
12805
+ * ------------------------------------------------------------------------- */
12806
+
12807
+ :is(
12808
+ [data-post-view] [data-page="post"] [data-post-body].post-detail-body,
12809
+ [data-timeline-item] article[data-post] > [data-post-body].prose
12810
+ ) {
12811
+ --jant-footnote-rail-enabled: 0;
12812
+ }
12813
+
12814
+ @media screen {
12815
+ @container post-view (min-width: 56rem) {
12816
+ :is(
12817
+ [data-post-view] [data-page="post"] [data-post-body].post-detail-body,
12818
+ [data-timeline-item] article[data-post] > [data-post-body].prose
12819
+ ) {
12820
+ --jant-footnote-rail-enabled: 1;
12821
+ }
12822
+
12823
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready) {
12824
+ position: relative;
12825
+ }
12826
+
12827
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready)
12828
+ > .footnote-endnotes {
12829
+ position: absolute;
12830
+ inset-block-start: 0;
12831
+ inset-inline-start: calc(100% + var(--layout-sidenote-gap));
12832
+ width: var(--layout-sidenote-width);
12833
+ margin: 0;
12834
+ padding: 0;
12835
+ border: 0;
12836
+ }
12837
+
12838
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready)
12839
+ > .footnote-endnotes
12840
+ > .footnote-list {
12841
+ margin: 0;
12842
+ padding: 0;
12843
+ list-style: none;
12844
+ row-gap: 0.75rem;
12845
+ }
12846
+
12847
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready)
12848
+ > .footnote-endnotes
12849
+ > .footnote-list
12850
+ > .footnote {
12851
+ display: grid;
12852
+ grid-template-columns: max-content minmax(0, 1fr);
12853
+ column-gap: 1ch;
12854
+ align-items: baseline;
12855
+ min-width: 0;
12856
+ margin: 0;
12857
+ padding: 0;
12858
+ list-style: none;
12859
+ }
12860
+
12861
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready)
12862
+ > .footnote-endnotes
12863
+ > .footnote-list
12864
+ > .footnote::before {
12865
+ content: var(--footnote-rail-number);
12866
+ grid-column: 1;
12867
+ grid-row: 1;
12868
+ font-size: inherit;
12869
+ line-height: inherit;
12870
+ font-variant-numeric: tabular-nums;
12871
+ color: var(--site-footnote-marker);
12872
+ }
12873
+
12874
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready)
12875
+ > .footnote-endnotes
12876
+ > .footnote-list
12877
+ > .footnote
12878
+ > * {
12879
+ grid-column: 2;
12880
+ min-width: 0;
12881
+ }
12882
+
12883
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready)
12884
+ > .footnote-endnotes
12885
+ > .footnote-list
12886
+ > .footnote
12887
+ > :first-child {
12888
+ grid-row: 1;
12889
+ }
12890
+
12891
+ [data-post-body].prose.footnote-rail-measuring > .footnote-endnotes {
12892
+ visibility: hidden;
12893
+ }
12894
+
12895
+ [data-post-body].prose.footnote-rail-measuring
12896
+ > .footnote-endnotes
12897
+ > .footnote-list {
12898
+ display: grid;
12899
+ }
12900
+
12901
+ [data-post-body].prose.footnote-rail-ready
12902
+ > .footnote-endnotes
12903
+ > .footnote-list {
12904
+ position: relative;
12905
+ display: block;
12906
+ block-size: var(--footnote-rail-block-size);
12907
+ }
12908
+
12909
+ [data-post-body].prose.footnote-rail-ready
12910
+ > .footnote-endnotes
12911
+ > .footnote-list
12912
+ > .footnote {
12913
+ position: absolute;
12914
+ inset-block-start: var(--footnote-rail-y);
12915
+ inset-inline: 0;
12916
+ width: 100%;
12917
+ }
12918
+
12919
+ /* The note is already beside its first reference. Keep reciprocal links in
12920
+ the DOM for keyboard and assistive-technology users, revealing them when
12921
+ focus enters rather than adding a persistent arrow to every rail item. */
12922
+ [data-post-body].prose.footnote-rail-ready .footnote-backlinks {
12923
+ position: absolute;
12924
+ width: 1px;
12925
+ height: 1px;
12926
+ padding: 0;
12927
+ margin: -1px;
12928
+ overflow: hidden;
12929
+ clip: rect(0, 0, 0, 0);
12930
+ clip-path: inset(50%);
12931
+ white-space: nowrap;
12932
+ border: 0;
12933
+ }
12934
+
12935
+ [data-post-body].prose.footnote-rail-ready
12936
+ .footnote-backlinks:focus-within {
12937
+ position: static;
12938
+ grid-column: 2;
12939
+ width: auto;
12940
+ height: auto;
12941
+ margin: 0;
12942
+ overflow: visible;
12943
+ clip: auto;
12944
+ clip-path: none;
12945
+ white-space: normal;
12946
+ }
12947
+
12948
+ [data-post-body].prose.footnote-rail-ready .footnote:target {
12949
+ background: transparent;
12950
+ }
12951
+ }
12952
+ }
12953
+
12954
+ @media print {
12955
+ [data-post-body].prose:is(.footnote-rail-measuring, .footnote-rail-ready) {
12956
+ min-block-size: auto !important;
12957
+ }
12958
+ }
12959
+
12160
12960
  /* ---------------------------------------------------------------------------
12161
12961
  * Home description — editorial "lede" paragraph.
12162
12962
  *