pandorga 1.2.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 (376) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +77 -0
  3. data/DESIGN.md +1434 -0
  4. data/LICENSE +21 -0
  5. data/README.md +81 -0
  6. data/_data/themes/dark.yml +97 -0
  7. data/_data/themes/light.yml +120 -0
  8. data/_data/themes/shared.yml +37 -0
  9. data/_data/translations.yml +85 -0
  10. data/_includes/chord-tab.html +38 -0
  11. data/_includes/content-runtime/00-config.html +22 -0
  12. data/_includes/content-runtime/10-utils.html +176 -0
  13. data/_includes/content-runtime/20-markdown.html +264 -0
  14. data/_includes/content-runtime/25-code-blocks.html +315 -0
  15. data/_includes/content-runtime/30-includes-bib.html +190 -0
  16. data/_includes/content-runtime/30-includes-xcite.html +122 -0
  17. data/_includes/content-runtime/30-includes.html +365 -0
  18. data/_includes/content-runtime/40-listing.html +225 -0
  19. data/_includes/content-runtime/41-listing-helpers.html +71 -0
  20. data/_includes/content-runtime/42-listing-filter.html +208 -0
  21. data/_includes/content-runtime/43-blocks.html +379 -0
  22. data/_includes/content-runtime/50-detail.html +861 -0
  23. data/_includes/content-runtime/55-toc.html +176 -0
  24. data/_includes/content-runtime/60-fragments.html +567 -0
  25. data/_includes/content-runtime/90-bootstrap.html +76 -0
  26. data/_includes/cut-in.html +10 -0
  27. data/_includes/cv/cv-box.html +18 -0
  28. data/_includes/dict-card.html +24 -0
  29. data/_includes/figure.html +36 -0
  30. data/_includes/home/band-index.html +43 -0
  31. data/_includes/home/band-listing.html +49 -0
  32. data/_includes/mathjax.html +10 -0
  33. data/_includes/ongoing-product.html +1 -0
  34. data/_includes/page/content-runtime.html +20 -0
  35. data/_includes/page/date_evolution.html +19 -0
  36. data/_includes/page/figure-lightbox.html +102 -0
  37. data/_includes/page/formatted_date.html +15 -0
  38. data/_includes/page/hero-default.html +18 -0
  39. data/_includes/page/listing-runtime-attrs.html +8 -0
  40. data/_includes/page/mermaid.html +101 -0
  41. data/_includes/page/network-graph.html +1253 -0
  42. data/_includes/page/page-header.html +41 -0
  43. data/_includes/page/professional-connections.html +2 -0
  44. data/_includes/page/site-footer.html +101 -0
  45. data/_includes/page/timeline-section.html +868 -0
  46. data/_includes/plotly.html +22 -0
  47. data/_includes/theme/dev-reexport.html +100 -0
  48. data/_includes/theme/favicons.html +4 -0
  49. data/_includes/theme/font-loader.html +55 -0
  50. data/_includes/theme/shell-theme-boot.html +115 -0
  51. data/_includes/theme/site-logo.html +6 -0
  52. data/_includes/theme/tailwind-theme-colors.json.liquid +65 -0
  53. data/_includes/theme/tailwind-theme-fonts.json.liquid +6 -0
  54. data/_includes/theme/theme-context.liquid +20 -0
  55. data/_includes/theme/theme-toggle.html +94 -0
  56. data/_includes/theme/theme-vars-block.liquid +161 -0
  57. data/_includes/theme/theme-vars.html +16 -0
  58. data/_includes/transcription.html +80 -0
  59. data/_includes/tweet.html +22 -0
  60. data/_includes/video.html +11 -0
  61. data/_includes/xcite.html +3 -0
  62. data/_includes/youtube-short.html +26 -0
  63. data/_includes/youtube.html +12 -0
  64. data/_layouts/article.html +27 -0
  65. data/_layouts/articles.html +534 -0
  66. data/_layouts/bibliography.html +417 -0
  67. data/_layouts/blog.html +544 -0
  68. data/_layouts/cv.html +393 -0
  69. data/_layouts/home.html +319 -0
  70. data/_layouts/network.html +9 -0
  71. data/_layouts/pandorga/articles.html +4 -0
  72. data/_layouts/pandorga/bibliography.html +4 -0
  73. data/_layouts/pandorga/blog.html +4 -0
  74. data/_layouts/pandorga/cv.html +4 -0
  75. data/_layouts/pandorga/media.html +4 -0
  76. data/_layouts/pandorga/network.html +4 -0
  77. data/_layouts/pandorga/portfolio.html +4 -0
  78. data/_layouts/post.html +7 -0
  79. data/_layouts/product.html +5 -0
  80. data/_layouts/project.html +27 -0
  81. data/_layouts/projects.html +534 -0
  82. data/_layouts/resources.html +387 -0
  83. data/_layouts/shell.html +203 -0
  84. data/_layouts/text.html +246 -0
  85. data/_plugins/bibliography_artifact.rb +57 -0
  86. data/_plugins/content_backend.rb +37 -0
  87. data/_plugins/content_collections.rb +51 -0
  88. data/_plugins/content_detail_paths.rb +58 -0
  89. data/_plugins/content_headers.rb +73 -0
  90. data/_plugins/content_keys.rb +310 -0
  91. data/_plugins/content_local_serve.rb +168 -0
  92. data/_plugins/content_site_adapter.rb +159 -0
  93. data/_plugins/content_validators_hook.rb +20 -0
  94. data/_plugins/content_writing_index.rb +52 -0
  95. data/_plugins/content_writing_paths.rb +55 -0
  96. data/_plugins/force_utf8_encoding.rb +13 -0
  97. data/_plugins/git_chronology.rb +211 -0
  98. data/_plugins/git_metadata.rb +43 -0
  99. data/_plugins/job_sections.rb +25 -0
  100. data/_plugins/lib/content_validators.rb +27 -0
  101. data/_plugins/lib/resource_validator.rb +56 -0
  102. data/_plugins/lib/tag_validator.rb +153 -0
  103. data/_plugins/lib/text_excerpt.rb +56 -0
  104. data/_plugins/lib/writing_entry.rb +107 -0
  105. data/_plugins/local_detail_rewrites.rb +194 -0
  106. data/_plugins/timeline.rb +507 -0
  107. data/_plugins/writing_slug_limits.rb +70 -0
  108. data/assets/css/article.css +1330 -0
  109. data/assets/css/base.css +1432 -0
  110. data/assets/css/cv.css +13 -0
  111. data/assets/css/global-link.css +94 -0
  112. data/assets/css/ledger/bibliography.css +307 -0
  113. data/assets/css/ledger/chrome.css +118 -0
  114. data/assets/css/ledger/cv.css +836 -0
  115. data/assets/css/ledger/detail.css +1270 -0
  116. data/assets/css/ledger/footer.css +298 -0
  117. data/assets/css/ledger/home.css +1490 -0
  118. data/assets/css/ledger/network.css +106 -0
  119. data/assets/css/ledger/projects.css +716 -0
  120. data/assets/css/ledger/rascunhos.css +379 -0
  121. data/assets/css/ledger/sources.css +327 -0
  122. data/assets/css/ledger/writing.css +754 -0
  123. data/assets/tailwind-play.js +15 -0
  124. data/examples/full/Gemfile +7 -0
  125. data/examples/full/_config.yml +92 -0
  126. data/examples/full/_redirects +3 -0
  127. data/examples/full/content/collections/articles/2026-01-01-welcome.md +9 -0
  128. data/examples/full/content/collections/data/contacts.yml +3 -0
  129. data/examples/full/content/collections/data/education.yml +4 -0
  130. data/examples/full/content/collections/jobs/research-fellow.md +12 -0
  131. data/examples/full/content/collections/posts/2026-01-02-note.md +9 -0
  132. data/examples/full/content/collections/products/ledger-tool.md +6 -0
  133. data/examples/full/content/collections/projects/demo-kit.md +10 -0
  134. data/examples/full/content/collections/resources/style-guide.md +7 -0
  135. data/examples/full/content/pages/cv/summary.md +1 -0
  136. data/examples/full/content/pages/headers.yml +20 -0
  137. data/examples/full/index.html +4 -0
  138. data/examples/minimal/Gemfile +7 -0
  139. data/examples/minimal/_config.yml +47 -0
  140. data/examples/minimal/_redirects +3 -0
  141. data/examples/minimal/content/collections/articles/2026-01-01-hello.md +10 -0
  142. data/examples/minimal/content/collections/articles/2026-01-02-second.md +9 -0
  143. data/examples/minimal/content/pages/headers.yml +6 -0
  144. data/examples/minimal/index.html +4 -0
  145. data/exe/pandorga +7 -0
  146. data/functions/api/studio/[[path]].js +562 -0
  147. data/functions/api/studio/_lib/auth.js +131 -0
  148. data/functions/api/studio/_lib/blocks.js +156 -0
  149. data/functions/api/studio/_lib/gemini.js +62 -0
  150. data/functions/api/studio/_lib/github.js +278 -0
  151. data/functions/api/studio/_lib/guidelines.js +143 -0
  152. data/functions/api/studio/_lib/paths.js +76 -0
  153. data/functions/api/studio/_lib/prompts.js +165 -0
  154. data/functions/api/studio/_lib/rate-limit.js +40 -0
  155. data/functions/api/studio/_lib/rewrite.js +136 -0
  156. data/functions/api/studio/_lib/sanitize.js +143 -0
  157. data/lib/pandorga/cli.rb +66 -0
  158. data/lib/pandorga/commands/doctor.rb +66 -0
  159. data/lib/pandorga/commands/export.rb +30 -0
  160. data/lib/pandorga/commands/install_functions.rb +41 -0
  161. data/lib/pandorga/commands/new.rb +113 -0
  162. data/lib/pandorga/commands/publish.rb +53 -0
  163. data/lib/pandorga/commands/serve.rb +34 -0
  164. data/lib/pandorga/commands/studio_schema.rb +309 -0
  165. data/lib/pandorga/jekyll/generator.rb +74 -0
  166. data/lib/pandorga/jekyll/plugins/bibliography_artifact.rb +57 -0
  167. data/lib/pandorga/jekyll/plugins/content_backend.rb +37 -0
  168. data/lib/pandorga/jekyll/plugins/content_collections.rb +51 -0
  169. data/lib/pandorga/jekyll/plugins/content_detail_paths.rb +58 -0
  170. data/lib/pandorga/jekyll/plugins/content_headers.rb +73 -0
  171. data/lib/pandorga/jekyll/plugins/content_keys.rb +310 -0
  172. data/lib/pandorga/jekyll/plugins/content_local_serve.rb +168 -0
  173. data/lib/pandorga/jekyll/plugins/content_site_adapter.rb +159 -0
  174. data/lib/pandorga/jekyll/plugins/content_validators_hook.rb +20 -0
  175. data/lib/pandorga/jekyll/plugins/content_writing_index.rb +52 -0
  176. data/lib/pandorga/jekyll/plugins/content_writing_paths.rb +55 -0
  177. data/lib/pandorga/jekyll/plugins/force_utf8_encoding.rb +13 -0
  178. data/lib/pandorga/jekyll/plugins/git_chronology.rb +211 -0
  179. data/lib/pandorga/jekyll/plugins/git_metadata.rb +43 -0
  180. data/lib/pandorga/jekyll/plugins/job_sections.rb +25 -0
  181. data/lib/pandorga/jekyll/plugins/lib/content_validators.rb +27 -0
  182. data/lib/pandorga/jekyll/plugins/lib/resource_validator.rb +56 -0
  183. data/lib/pandorga/jekyll/plugins/lib/tag_validator.rb +153 -0
  184. data/lib/pandorga/jekyll/plugins/lib/text_excerpt.rb +56 -0
  185. data/lib/pandorga/jekyll/plugins/lib/writing_entry.rb +107 -0
  186. data/lib/pandorga/jekyll/plugins/local_detail_rewrites.rb +194 -0
  187. data/lib/pandorga/jekyll/plugins/timeline.rb +507 -0
  188. data/lib/pandorga/jekyll/plugins/writing_slug_limits.rb +70 -0
  189. data/lib/pandorga/jekyll.rb +15 -0
  190. data/lib/pandorga/object_store_env.rb +51 -0
  191. data/lib/pandorga/registry/schema.yml +115 -0
  192. data/lib/pandorga/registry.rb +279 -0
  193. data/lib/pandorga/version.rb +5 -0
  194. data/lib/pandorga.rb +19 -0
  195. data/scripts/cdn-allowlist.json +19 -0
  196. data/scripts/content/backfill-content-keys.rb +99 -0
  197. data/scripts/content/band_art.rb +362 -0
  198. data/scripts/content/export-content-json.rb +741 -0
  199. data/scripts/content/flatten-ansi-capture.mjs +153 -0
  200. data/scripts/content/hero_portraits.rb +28 -0
  201. data/scripts/content/publish-content-to-object-store.sh +78 -0
  202. data/scripts/content/publish-content-to-r2.sh +5 -0
  203. data/scripts/content/sync-content-json-to-r2.rb +167 -0
  204. data/scripts/content/sync-content-redirects.rb +12 -0
  205. data/scripts/generate-configuration-doc.rb +127 -0
  206. data/scripts/lib/content_license.rb +50 -0
  207. data/scripts/lib/content_storage_config.rb +70 -0
  208. data/scripts/lib/normalize-display-math.mjs +52 -0
  209. data/scripts/lib/r2_json_sync.rb +76 -0
  210. data/scripts/lib/withdrawn_public_objects.rb +23 -0
  211. data/scripts/lib/writing_index_contract.rb +61 -0
  212. data/scripts/pdf/md-to-pdf.mjs +1400 -0
  213. data/scripts/pdf/md-to-pdf.sh +27 -0
  214. data/scripts/test/fixtures/dual-template/_config.yml +32 -0
  215. data/scripts/test/fixtures/dual-template/index.html +4 -0
  216. data/scripts/test/render-parity/article-note.json +5 -0
  217. data/scripts/test/render-parity/blockquote.json +5 -0
  218. data/scripts/test/render-parity/blocks/ansi-basic.json +7 -0
  219. data/scripts/test/render-parity/blocks/ansi-bold-is-bright.json +7 -0
  220. data/scripts/test/render-parity/blocks/ansi-drops-cursor-moves.json +7 -0
  221. data/scripts/test/render-parity/blocks/ansi-escapes-markup.json +7 -0
  222. data/scripts/test/render-parity/blocks/ansi-extended-colour.json +7 -0
  223. data/scripts/test/render-parity/blocks/ansi-real-control-byte.json +7 -0
  224. data/scripts/test/render-parity/blocks/archive-card-date-once.json +19 -0
  225. data/scripts/test/render-parity/blocks/archive-card-empty-foot.json +7 -0
  226. data/scripts/test/render-parity/blocks/archive-card-footer-dates.json +22 -0
  227. data/scripts/test/render-parity/blocks/archive-card-meta-parts.json +26 -0
  228. data/scripts/test/render-parity/blocks/archive-card.json +32 -0
  229. data/scripts/test/render-parity/blocks/cta-link.json +13 -0
  230. data/scripts/test/render-parity/blocks/lang-pill-pt.json +8 -0
  231. data/scripts/test/render-parity/blocks/lang-pill.json +8 -0
  232. data/scripts/test/render-parity/blocks/ledger-row-meta-parts.json +29 -0
  233. data/scripts/test/render-parity/blocks/ledger-row.json +35 -0
  234. data/scripts/test/render-parity/blocks/meta-strip.json +27 -0
  235. data/scripts/test/render-parity/blocks/poster-media-image.json +8 -0
  236. data/scripts/test/render-parity/blocks/poster-media-video.json +9 -0
  237. data/scripts/test/render-parity/blocks/section-banner.json +13 -0
  238. data/scripts/test/render-parity/blocks/system-card-custom-meta.json +22 -0
  239. data/scripts/test/render-parity/blocks/system-card.json +48 -0
  240. data/scripts/test/render-parity/blocks/tag-chips-links.json +12 -0
  241. data/scripts/test/render-parity/blocks/tag-chips.json +9 -0
  242. data/scripts/test/render-parity/cut-in-blank-line.json +9 -0
  243. data/scripts/test/render-parity/cut-in-right.json +9 -0
  244. data/scripts/test/render-parity/cut-in.json +8 -0
  245. data/scripts/test/render-parity/figure.json +5 -0
  246. data/scripts/test/render-parity/load-runtime.mjs +337 -0
  247. data/scripts/test/render-parity/ongoing-product.json +5 -0
  248. data/scripts/test/render-parity/plotly.json +5 -0
  249. data/scripts/test/render-parity/poem.json +5 -0
  250. data/scripts/test/render-parity/run-absent-field-checks.mjs +232 -0
  251. data/scripts/test/render-parity/run-block-fixture.mjs +86 -0
  252. data/scripts/test/render-parity/run-detail-parity-checks.mjs +123 -0
  253. data/scripts/test/render-parity/run-field-flow-checks.mjs +227 -0
  254. data/scripts/test/render-parity/run-fixture.mjs +159 -0
  255. data/scripts/test/render-parity/selectors.mjs +43 -0
  256. data/scripts/test/render-parity/video.json +5 -0
  257. data/scripts/test/render-parity/youtube-short.json +10 -0
  258. data/scripts/test/render-parity/youtube.json +8 -0
  259. data/scripts/test/test-absent-field-absent-element.rb +43 -0
  260. data/scripts/test/test-cdn-pins.rb +44 -0
  261. data/scripts/test/test-cloudflare-watch-paths-doc.rb +31 -0
  262. data/scripts/test/test-configuration-doc.rb +36 -0
  263. data/scripts/test/test-content-detail-urls.rb +346 -0
  264. data/scripts/test/test-content-keys.rb +110 -0
  265. data/scripts/test/test-content-license.rb +45 -0
  266. data/scripts/test/test-content-render-parity.rb +38 -0
  267. data/scripts/test/test-content-validators.rb +12 -0
  268. data/scripts/test/test-css-structure.rb +72 -0
  269. data/scripts/test/test-cv-export-contract.rb +158 -0
  270. data/scripts/test/test-detail-render-parity.rb +40 -0
  271. data/scripts/test/test-dev-reexport.rb +122 -0
  272. data/scripts/test/test-doctor.rb +39 -0
  273. data/scripts/test/test-dual-template-instances.rb +87 -0
  274. data/scripts/test/test-dual-template-runtime.rb +167 -0
  275. data/scripts/test/test-examples-build.rb +60 -0
  276. data/scripts/test/test-export-content-json-incremental.rb +138 -0
  277. data/scripts/test/test-export.rb +53 -0
  278. data/scripts/test/test-exported-fields-reach-the-view.rb +60 -0
  279. data/scripts/test/test-form-control-theming.rb +143 -0
  280. data/scripts/test/test-git-chronology.rb +414 -0
  281. data/scripts/test/test-hero-portraits.mjs +97 -0
  282. data/scripts/test/test-hero-portraits.rb +162 -0
  283. data/scripts/test/test-home-band-art.rb +135 -0
  284. data/scripts/test/test-install-functions.rb +39 -0
  285. data/scripts/test/test-language-export.rb +79 -0
  286. data/scripts/test/test-listing-filter-contract.rb +119 -0
  287. data/scripts/test/test-math-row-breaks.mjs +79 -0
  288. data/scripts/test/test-math-row-breaks.rb +18 -0
  289. data/scripts/test/test-new.rb +113 -0
  290. data/scripts/test/test-no-personal-data.rb +85 -0
  291. data/scripts/test/test-public-cv-export.rb +164 -0
  292. data/scripts/test/test-r2-config-consistency.rb +57 -0
  293. data/scripts/test/test-r2-json-sync.rb +193 -0
  294. data/scripts/test/test-registry-home.rb +34 -0
  295. data/scripts/test/test-runtime-js-syntax.rb +64 -0
  296. data/scripts/test/test-slim-writing-index.rb +126 -0
  297. data/scripts/test/test-studio-api-contract.rb +99 -0
  298. data/scripts/test/test-studio-content-files.rb +79 -0
  299. data/scripts/test/test-studio-liquid-roundtrip.mjs +179 -0
  300. data/scripts/test/test-studio-rate-limit.mjs +70 -0
  301. data/scripts/test/test-studio-rewrite-api-contract.rb +59 -0
  302. data/scripts/test/test-studio-rewrite-sanitize.mjs +210 -0
  303. data/scripts/test/test-studio-schema-composition.rb +126 -0
  304. data/scripts/test/test-studio-slug-config.rb +167 -0
  305. data/scripts/test/test-template-packages.rb +51 -0
  306. data/scripts/test/test-text-excerpt.rb +51 -0
  307. data/scripts/test/test-unwrap-media-src.mjs +39 -0
  308. data/scripts/test/test-unwrap-media-src.rb +19 -0
  309. data/scripts/test/test-withdrawn-content-export.rb +69 -0
  310. data/scripts/test/test-writing-index-export-slugs.rb +84 -0
  311. data/scripts/test/test-writing-slug-limits.rb +61 -0
  312. data/scripts/test/test-xcite-chronology.rb +106 -0
  313. data/scripts/validate.sh +74 -0
  314. data/scripts/visual/build-cv-pages-json.js +33 -0
  315. data/scripts/visual/build-site-pages-json.js +94 -0
  316. data/scripts/visual/publish-visual-inspection-to-r2.sh +42 -0
  317. data/scripts/visual/take-screenshots.js +88 -0
  318. data/scripts/visual/visual-inspection-pages.json +113 -0
  319. data/studio/assets/index-B_j0pMD5.css +1 -0
  320. data/studio/assets/index-Ch4YEv9g.js +285 -0
  321. data/studio/index.html +27 -0
  322. data/studio/schema.yml +869 -0
  323. data/studio-app/README.md +27 -0
  324. data/studio-app/index.html +26 -0
  325. data/studio-app/package-lock.json +2499 -0
  326. data/studio-app/package.json +26 -0
  327. data/studio-app/skills-lock.json +47 -0
  328. data/studio-app/src/api/client.js +71 -0
  329. data/studio-app/src/editor/body-editor.js +70 -0
  330. data/studio-app/src/editor/liquid-core.js +382 -0
  331. data/studio-app/src/editor/liquid.js +77 -0
  332. data/studio-app/src/lib/document.js +207 -0
  333. data/studio-app/src/lib/list-controls.js +317 -0
  334. data/studio-app/src/lib/schema.js +66 -0
  335. data/studio-app/src/lib/yaml-doc.js +100 -0
  336. data/studio-app/src/main.js +3693 -0
  337. data/studio-app/src/media/browser.js +578 -0
  338. data/studio-app/src/preview/render.js +430 -0
  339. data/studio-app/src/styles/studio.css +2826 -0
  340. data/studio-app/vite.config.js +25 -0
  341. data/templates/articles/README.md +40 -0
  342. data/templates/articles/fixtures/article.md +9 -0
  343. data/templates/articles/screenshots/README.md +6 -0
  344. data/templates/articles/studio.yml +73 -0
  345. data/templates/articles/template.yml +21 -0
  346. data/templates/bibliography/README.md +41 -0
  347. data/templates/bibliography/fixtures/references.json +9 -0
  348. data/templates/bibliography/screenshots/README.md +6 -0
  349. data/templates/bibliography/studio.yml +66 -0
  350. data/templates/bibliography/template.yml +18 -0
  351. data/templates/blog/README.md +38 -0
  352. data/templates/blog/fixtures/post.md +9 -0
  353. data/templates/blog/screenshots/README.md +6 -0
  354. data/templates/blog/studio.yml +69 -0
  355. data/templates/blog/template.yml +20 -0
  356. data/templates/cv/README.md +34 -0
  357. data/templates/cv/fixtures/job.md +12 -0
  358. data/templates/cv/screenshots/README.md +6 -0
  359. data/templates/cv/studio.yml +200 -0
  360. data/templates/cv/template.yml +25 -0
  361. data/templates/media/README.md +38 -0
  362. data/templates/media/fixtures/resource.md +7 -0
  363. data/templates/media/screenshots/README.md +6 -0
  364. data/templates/media/studio.yml +58 -0
  365. data/templates/media/template.yml +17 -0
  366. data/templates/network/README.md +34 -0
  367. data/templates/network/fixtures/sources.json +3 -0
  368. data/templates/network/screenshots/README.md +6 -0
  369. data/templates/network/studio.yml +28 -0
  370. data/templates/network/template.yml +10 -0
  371. data/templates/portfolio/README.md +39 -0
  372. data/templates/portfolio/fixtures/project.md +10 -0
  373. data/templates/portfolio/screenshots/README.md +6 -0
  374. data/templates/portfolio/studio.yml +89 -0
  375. data/templates/portfolio/template.yml +18 -0
  376. metadata +505 -0
@@ -0,0 +1,1330 @@
1
+ /* ── Article content ── */
2
+ :root {
3
+ --reading-measure: 45rem;
4
+ }
5
+
6
+ .text-reading-column {
7
+ width: 100%;
8
+ max-width: var(--reading-measure);
9
+ }
10
+
11
+ /* Sidebar title/description/meta share the same measure as the body column */
12
+ .text-detail-sidebar {
13
+ width: 100%;
14
+ max-width: var(--reading-measure);
15
+ }
16
+
17
+ /* Detail pages: title in left third; body starts aligned with the title */
18
+ .text-page-main {
19
+ align-self: start;
20
+ background-color: var(--color-background);
21
+ border: 1.5px solid var(--border-ink);
22
+ border-radius: var(--radius-card);
23
+ padding: var(--space-md);
24
+ width: 100%;
25
+ }
26
+
27
+ @media (min-width: 768px) {
28
+ .text-page-main {
29
+ padding: var(--space-2xl);
30
+ }
31
+ }
32
+
33
+ /* Size and leading are not set here: ledger/detail.css loads after this sheet
34
+ and owns both (1.1rem at var(--leading-body)). They used to be declared in
35
+ both files, 1em/1.8 here against 1.1rem/1.7 there, and the pair that lost
36
+ was still being used to derive line-box measurements further down. */
37
+ /* Body / prose ink is full on-surface (black in light, white in dark).
38
+ Meta, captions, and chrome stay on on-surface-variant. */
39
+ .article-content {
40
+ width: 100%;
41
+ max-width: var(--reading-measure);
42
+ color: var(--color-on-surface);
43
+ }
44
+
45
+ /* Section headings — content-title voice (Regular 400, never bold);
46
+ detail.css owns final size/leading. */
47
+ .article-content h1,
48
+ .article-content h2,
49
+ .article-content h3 {
50
+ font-family: var(--font-content-title);
51
+ font-weight: var(--weight-title);
52
+ font-synthesis: none;
53
+ margin-top: 2rem;
54
+ margin-bottom: 0.75rem;
55
+ color: var(--color-on-surface);
56
+ border-left: none;
57
+ padding-left: 0;
58
+ }
59
+
60
+ .article-content h4 {
61
+ font-family: var(--font-body);
62
+ font-weight: var(--weight-title);
63
+ font-synthesis: none;
64
+ margin-top: 2rem;
65
+ margin-bottom: 0.75rem;
66
+ color: var(--color-on-surface);
67
+ border-left: none;
68
+ padding-left: 0;
69
+ }
70
+
71
+ .article-content h1 {
72
+ font-size: var(--type-title);
73
+ }
74
+
75
+ .article-content h2 {
76
+ font-size: var(--type-title);
77
+ }
78
+
79
+ .article-content h3 {
80
+ font-size: var(--type-card-lg);
81
+ }
82
+
83
+ .article-content h4 {
84
+ font-size: var(--type-body);
85
+ }
86
+
87
+ /* The paragraph gap belongs to ledger/detail.css, which sets it at 1.15em;
88
+ only the reset survives here. */
89
+ .article-content p {
90
+ margin-top: 0;
91
+ }
92
+
93
+ /* MathJax: keep inline math in the text flow; display math on its own line */
94
+ .article-content mjx-container:not([display="true"]) {
95
+ display: inline;
96
+ margin: 0;
97
+ vertical-align: baseline;
98
+ }
99
+
100
+ /* A display equation is a block in the prose flow, so it takes the paragraph
101
+ gap and nothing more. Two things used to inflate it, to a measured 52px
102
+ above the equation and 39px below against a 22px paragraph gap.
103
+
104
+ First, the runtime wraps a standalone `$$…$$` in a `.math-display` div
105
+ (20-markdown.html) and MathJax then renders its own container inside that
106
+ wrapper, so a selector list naming both spaced one equation twice.
107
+
108
+ Second, the container half of that pair never applied: MathJax injects
109
+ `mjx-container[jax="CHTML"][display="true"] { margin: 1em 0 }` at runtime,
110
+ which outranks `.article-content mjx-container[display="true"]` on
111
+ specificity. The 0.5rem this file asked for was never what the page drew.
112
+
113
+ So the wrapper owns the gap, the scroll and the padding; the container
114
+ inside it owns none of them. */
115
+ .article-content .math-display {
116
+ display: block;
117
+ /* The bottom gap is the paragraph gap `.article-content p` sets in
118
+ ledger/detail.css; the top gap is left to the previous block, exactly as
119
+ it is between two paragraphs. The negative top margin hands back the
120
+ padding below, which a margin collapse cannot absorb — the two are one
121
+ number, so change them together or the rhythm moves. */
122
+ margin: -1.25rem 0 calc(1.15em - 1.25rem);
123
+ /* overflow-x lets an over-wide equation scroll sideways. CSS forces
124
+ overflow-y to auto alongside it, so a bracket, radical or matrix rule that
125
+ MathJax draws past its own line box gets clipped, or grows a vertical
126
+ scrollbar it has no use for. The padding is the allowance for that
127
+ overshoot: 1.25rem clears the worst case on the object-centric article
128
+ (a 3-row bmatrix, measured at 12px past a 0.5rem allowance). It costs
129
+ nothing visually, because the margin above gives it straight back. */
130
+ padding: 1.25rem 0;
131
+ overflow-x: auto;
132
+ text-align: center;
133
+ }
134
+
135
+ /* `[jax]` is redundant as a selector — every MathJax container carries it —
136
+ but it lifts this rule past MathJax's own injected margin, which is the
137
+ whole point of the rule. */
138
+ .article-content .math-display mjx-container[jax][display="true"] {
139
+ margin: 0;
140
+ padding: 0;
141
+ overflow: visible;
142
+ }
143
+
144
+ /* Display math the wrapper regex cannot reach, because the `$$` sits inside a
145
+ list item or a table cell rather than alone in a paragraph. It still has to
146
+ break the line; its vertical margin stays MathJax's own 1em, which lands
147
+ within a pixel of the paragraph gap. */
148
+ .article-content mjx-container[display="true"] {
149
+ display: block;
150
+ overflow-x: auto;
151
+ text-align: center;
152
+ }
153
+
154
+ /* End-of-section brand strip: centered raw HTML row of logos (not figure.html).
155
+ Default height is shared; override per <img style="height:…"> when a mark
156
+ needs a different optical weight. Pairs sit ~2ch apart (inline-block, not flex).
157
+ Vertical margin is one body line box before and after, read off the leading
158
+ token rather than copied as a number; 1lh is unusable while line-height is
159
+ 0, and the literal this used to carry (1.8em) outlived the leading it was
160
+ measured against. */
161
+ .article-content .article-brand-strip {
162
+ display: block;
163
+ text-align: center;
164
+ margin: calc(var(--leading-body) * 1em) 0;
165
+ line-height: 0;
166
+ }
167
+
168
+ .article-content .article-brand-strip img {
169
+ display: inline-block;
170
+ vertical-align: middle;
171
+ width: auto;
172
+ height: 2.75rem;
173
+ max-width: min(100%, 14rem);
174
+ object-fit: contain;
175
+ }
176
+
177
+ .article-content .article-brand-strip img+img {
178
+ margin-left: 2ch;
179
+ }
180
+
181
+ .article-content .article-brand-strip-dark {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ box-sizing: border-box;
186
+ padding: 0.35rem 0.55rem;
187
+ /* A fixed dark plate beNewsreader light-on-transparent marks. It must stay dark in
188
+ both themes, so no surface token fits: every one of them flips. */
189
+ background: #111;
190
+ }
191
+
192
+ .article-content .article-brand-strip-dark img {
193
+ height: 2.25rem;
194
+ }
195
+
196
+ /* Poem blocks preserve line breaks typed in Markdown without trailing spaces.
197
+ Same face and size as the reading column — not a smaller apparatus step. */
198
+ .article-content .poem {
199
+ white-space: normal;
200
+ font-family: var(--font-body);
201
+ font-size: var(--type-prose);
202
+ font-weight: var(--weight-body);
203
+ font-style: normal;
204
+ line-height: var(--leading-prose);
205
+ color: var(--color-on-surface);
206
+ margin: 0;
207
+ }
208
+
209
+ .article-content .poem p {
210
+ white-space: pre-line;
211
+ font-size: inherit;
212
+ font-weight: inherit;
213
+ margin: 0;
214
+ }
215
+
216
+ .article-content .poem p+p {
217
+ /* Blank line in source = new stanza or refrão. */
218
+ margin-top: 1em;
219
+ }
220
+
221
+ .article-content ul {
222
+ margin-left: 1.5rem;
223
+ margin-bottom: 1rem;
224
+ list-style-type: disc;
225
+ list-style-position: outside;
226
+ }
227
+
228
+ .article-content ol {
229
+ margin-left: 1.5rem;
230
+ margin-bottom: 1rem;
231
+ list-style-type: decimal;
232
+ list-style-position: outside;
233
+ }
234
+
235
+ .article-content li {
236
+ margin-bottom: 0.3rem;
237
+ }
238
+
239
+ .article-content code {
240
+ background: var(--color-surface-container);
241
+ padding: 0.1rem 0.3rem;
242
+ font-size: var(--type-meta);
243
+ font-family: var(--font-code);
244
+ }
245
+
246
+ :is(.article-content, .article-cite-block) pre {
247
+ background: var(--color-surface-container);
248
+ padding: 1rem;
249
+ overflow-x: auto;
250
+ margin-bottom: 1rem;
251
+ font-family: var(--font-code);
252
+ }
253
+
254
+ :is(.article-content, .article-cite-block) pre code {
255
+ background: none;
256
+ padding: 0;
257
+ }
258
+
259
+ .article-content a {
260
+ color: var(--color-link);
261
+ }
262
+
263
+ .article-content img {
264
+ display: block;
265
+ margin-left: auto;
266
+ margin-right: auto;
267
+ max-width: 100%;
268
+ }
269
+
270
+ @media (max-width: 767px) {
271
+
272
+ /* Override inline figure widths to allow full width on mobile */
273
+ .article-content figure[style] {
274
+ max-width: 100% !important;
275
+ width: 100% !important;
276
+ }
277
+
278
+ .article-content img:not(.article-cut-in) {
279
+ width: 100%;
280
+ max-height: 100vw;
281
+ object-fit: contain;
282
+ }
283
+
284
+ .article-content img.article-cut-in {
285
+ max-height: calc(var(--leading-body) * 6em);
286
+ }
287
+ }
288
+
289
+ /* Typographic cut-in: 3×4 portrait floated into the column so type wraps
290
+ around it. Height is six body line boxes and the width is the 3:4 of that,
291
+ both derived from the leading token so the crop keeps landing on the text
292
+ it wraps. The pair was frozen at 8.1em by 10.8em, which was six line boxes
293
+ of a leading the sheet no longer sets.
294
+
295
+ The image carries the geometry; the plate around it carries the float. */
296
+ .article-content img.article-cut-in {
297
+ display: block;
298
+ width: calc(var(--leading-body) * 4.5em);
299
+ height: calc(var(--leading-body) * 6em);
300
+ max-width: 100%;
301
+ object-fit: cover;
302
+ object-position: center 18%;
303
+ margin: 0;
304
+ }
305
+
306
+ /* A cut-in sits on the same plate as every other picture in an article, which
307
+ it did not before: the include emitted a bare `<img>`, so a portrait dropped
308
+ into a paragraph was the one piece of media on the site arriving without a
309
+ ground under it. `.article-content figure` is written for a full-width
310
+ block, so the three properties that make this one a float have to be stated
311
+ back: the width shrinks to its portrait, the float moves here off the image,
312
+ and the margin becomes the gutter the wrapping text reads against. The
313
+ padding is the small step, not the figure's own: a plate as thick as a
314
+ full-width figure's would swallow a 3x4 portrait set into running text. */
315
+ .article-content figure.article-cut-in-plate {
316
+ float: left;
317
+ display: block;
318
+ width: auto;
319
+ max-width: 40%;
320
+ margin: 0.15em 0.9em 0.45em 0;
321
+ padding: var(--space-sm);
322
+ }
323
+
324
+ .article-content figure.article-cut-in-plate.article-cut-in--right {
325
+ float: right;
326
+ margin: 0.15em 0 0.45em 0.9em;
327
+ }
328
+
329
+ /* Short wrapping grafs beside a cut-in. The default 1.1em paragraph gap is
330
+ meant for long reading-column blocks; next to a 3×4 float and a *** rule
331
+ it reads as an extra blank line. Only pages that float a cut-in as a
332
+ direct child (the bulletin layout) pick this up. */
333
+ .article-content:has(> figure.article-cut-in-plate)>p {
334
+ margin-bottom: 0.8em;
335
+ }
336
+
337
+ .article-content:has(> figure.article-cut-in-plate)>hr {
338
+ margin: 1.5rem 0;
339
+ }
340
+
341
+ /* ── Media plates ──
342
+ Every figure sits on a plate. This is not decoration: a light-background
343
+ Matplotlib plot and a dark simulation video otherwise arrive on the page as
344
+ two different surfaces, and a transparent PNG on the dark palette reads as a
345
+ rendering fault rather than as a picture. The plate gives all of them one
346
+ ground in both themes.
347
+
348
+ The projects page had this to itself until now; these rules are the ones it
349
+ proved, moved up so the treatment is the site's rather than one page's. What
350
+ stayed beNewsreader in ledger/projects.css is the *other* thing that page does —
351
+ letting media out of the reading measure — which is a separate decision.
352
+
353
+ `.plotly-figure` and the videos both carry the `figure` element, so one
354
+ selector covers the include library, the browser runtime and the two legacy
355
+ WordPress `wp-block-image` figures in the Portuguese posts. */
356
+ .article-content figure {
357
+ margin: 1.5rem auto;
358
+ text-align: center;
359
+ width: 100%;
360
+ display: block;
361
+ background: var(--color-surface-container-low);
362
+ padding: var(--space-md);
363
+ }
364
+
365
+ .article-content figure img {
366
+ margin-inline: auto;
367
+ }
368
+
369
+ /* Video is the one medium that brings its own black, so it takes the full
370
+ plate width over the dim step rather than the container step under it. */
371
+ .article-content figure video {
372
+ display: block;
373
+ width: 100%;
374
+ background: var(--color-surface-dim);
375
+ }
376
+
377
+ /* Landscape YouTube / local video: always the full reading measure.
378
+ Shorts stay narrow (45%); a paired short block uses the full measure with
379
+ each frame at 45%. */
380
+ .article-content figure.article-youtube-landscape,
381
+ .article-content figure.article-video-landscape {
382
+ max-width: 100%;
383
+ width: 100%;
384
+ }
385
+
386
+ .article-content figure.article-youtube-short-wrap {
387
+ max-width: 45%;
388
+ }
389
+
390
+ .article-content figure.article-youtube-short-wrap--pair {
391
+ max-width: 100%;
392
+ }
393
+
394
+ .article-content .article-youtube-short iframe {
395
+ display: block;
396
+ }
397
+
398
+ /* The Plotly iframe paints its own themed background and is already inside the
399
+ plate; a second fill here would box a box. */
400
+ .article-content .plotly-embed-frame {
401
+ background: transparent;
402
+ }
403
+
404
+ /* ── Figure lightbox (docs/features/figure-lightbox.md) ── */
405
+ img.figure-lightbox-trigger {
406
+ cursor: zoom-in;
407
+ }
408
+
409
+ img.figure-lightbox-trigger:focus-visible {
410
+ outline: 2px solid var(--color-primary);
411
+ outline-offset: 3px;
412
+ }
413
+
414
+ html.figure-lightbox-open,
415
+ body.figure-lightbox-open {
416
+ overflow: hidden;
417
+ }
418
+
419
+ .figure-lightbox {
420
+ position: fixed;
421
+ inset: 0;
422
+ z-index: 80;
423
+ display: flex;
424
+ align-items: center;
425
+ justify-content: center;
426
+ padding: 1.25rem;
427
+ box-sizing: border-box;
428
+ }
429
+
430
+ .figure-lightbox[hidden] {
431
+ display: none;
432
+ }
433
+
434
+ .figure-lightbox-backdrop {
435
+ position: absolute;
436
+ inset: 0;
437
+ /* Near-opaque black so the article beNewsreader never reads as “another page”. */
438
+ background: rgba(0, 0, 0, 0.85);
439
+ cursor: zoom-out;
440
+ animation: figure-lightbox-fade-in var(--motion-duration-fast) var(--motion-ease-out);
441
+ }
442
+
443
+ .figure-lightbox-panel {
444
+ position: relative;
445
+ z-index: 1;
446
+ display: flex;
447
+ flex-direction: column;
448
+ align-items: center;
449
+ max-width: min(92vw, 72rem);
450
+ max-height: 92vh;
451
+ animation: figure-lightbox-scale-in var(--motion-duration-fast) var(--motion-ease-out);
452
+ }
453
+
454
+ .figure-lightbox-img {
455
+ display: block;
456
+ max-width: 92vw;
457
+ max-height: 85vh;
458
+ width: auto;
459
+ height: auto;
460
+ object-fit: contain;
461
+ background: var(--color-surface);
462
+ border: 1px solid color-mix(in srgb, var(--color-outline-variant) 40%, transparent);
463
+ border-radius: 0;
464
+ }
465
+
466
+ /* Moves with the caption under the plate — the lightbox shows the same
467
+ sentence about the same figure. */
468
+ .figure-lightbox-caption {
469
+ margin: 0.75rem 0 0;
470
+ max-width: min(40rem, 90vw);
471
+ font-family: var(--font-body);
472
+ font-size: var(--type-label-lg);
473
+ line-height: 1.45;
474
+ text-align: center;
475
+ /* Light caption on the black scrim (readable in light and dark themes). */
476
+ color: rgba(255, 255, 255, 0.92);
477
+ }
478
+
479
+ .figure-lightbox-close {
480
+ position: fixed;
481
+ top: 1rem;
482
+ right: 1rem;
483
+ z-index: 2;
484
+ display: inline-flex;
485
+ align-items: center;
486
+ justify-content: center;
487
+ width: 2.75rem;
488
+ height: 2.75rem;
489
+ padding: 0;
490
+ border: 1px solid color-mix(in srgb, var(--color-outline-variant) 50%, transparent);
491
+ border-radius: 0;
492
+ background: var(--color-surface);
493
+ color: var(--color-on-surface);
494
+ cursor: pointer;
495
+ transition: color var(--motion-duration-fast) var(--motion-ease-default), background-color var(--motion-duration-fast) var(--motion-ease-default);
496
+ }
497
+
498
+ .figure-lightbox-close:hover,
499
+ .figure-lightbox-close:focus-visible {
500
+ color: var(--color-primary);
501
+ background: var(--color-surface-container-low);
502
+ outline: none;
503
+ }
504
+
505
+ .figure-lightbox-close:focus-visible {
506
+ outline: 2px solid var(--color-primary);
507
+ outline-offset: 2px;
508
+ }
509
+
510
+ .figure-lightbox-close .material-symbols-outlined {
511
+ font-size: var(--type-section);
512
+ line-height: 1;
513
+ }
514
+
515
+ @keyframes figure-lightbox-fade-in {
516
+ from {
517
+ opacity: 0;
518
+ }
519
+
520
+ to {
521
+ opacity: 1;
522
+ }
523
+ }
524
+
525
+ @keyframes figure-lightbox-scale-in {
526
+ from {
527
+ opacity: 0;
528
+ transform: scale(0.97);
529
+ }
530
+
531
+ to {
532
+ opacity: 1;
533
+ transform: scale(1);
534
+ }
535
+ }
536
+
537
+ @media (prefers-reduced-motion: reduce) {
538
+
539
+ .figure-lightbox-backdrop,
540
+ .figure-lightbox-panel {
541
+ animation: none;
542
+ }
543
+ }
544
+
545
+ /* Mermaid takes the container step, not the panel step. `mermaidConfig()`
546
+ paints cluster backgrounds with --color-panel-contrast and node fills with
547
+ --color-surface-card, so a panel plate would swallow every subgraph box the
548
+ diagram draws. The container step sits clear of both in each theme — do not
549
+ "simplify" this to panel-contrast. */
550
+ .article-content .mermaid {
551
+ margin: var(--space-2xl) 0;
552
+ padding: var(--space-xl) var(--space-lg);
553
+ background: var(--color-surface-container);
554
+ text-align: center;
555
+ overflow-x: auto;
556
+ -webkit-overflow-scrolling: touch;
557
+ }
558
+
559
+ .article-content .mermaid svg {
560
+ display: block;
561
+ margin-left: auto;
562
+ margin-right: auto;
563
+ max-width: none;
564
+ width: auto;
565
+ height: auto;
566
+ }
567
+
568
+ /* Captions are sentences. The include centres the whole figure, and a centred
569
+ paragraph of provenance is harder to read than a left-set one; the rule on
570
+ the element itself beats the `text-center` utility the runtime emits. The
571
+ hairline separates the caption from the picture now that both share a
572
+ plate.
573
+
574
+ Scoped to `figure figcaption`, not to any figcaption, so the treatment and
575
+ the plate always travel together. That used to exclude the YouTube caption,
576
+ because youtube.html emitted a bare `<figcaption>` inside a `<div>` with no
577
+ `<figure>` around it and the rule would have drawn a hairline under an
578
+ unplated iframe. The include emits a `<figure>` now, so the embed takes the
579
+ plate and its caption takes this treatment with it. */
580
+ /* Captions match the detail standfirst: body − 1pt, upright, not fine print.
581
+ Ink is a soft step toward variant so legends sit quieter than body copy. */
582
+ .article-content figure figcaption,
583
+ .article-content figcaption {
584
+ text-align: left;
585
+ font-family: var(--font-body);
586
+ font-size: var(--type-subtitle);
587
+ font-style: normal;
588
+ font-weight: var(--weight-body);
589
+ line-height: 1.55;
590
+ letter-spacing: normal;
591
+ color: var(--color-legend);
592
+ margin-top: var(--space-sm);
593
+ padding-top: var(--space-sm);
594
+ border-top: 1px solid var(--border-gentle-faint);
595
+ }
596
+
597
+ .article-content figcaption p {
598
+ display: inline;
599
+ margin: 0;
600
+ }
601
+
602
+ .article-content blockquote {
603
+ background: var(--color-surface-container-low);
604
+ padding: 1rem 1.5rem;
605
+ margin: 1.5em 0;
606
+ color: var(--color-on-surface);
607
+ font-size: var(--type-prose);
608
+ font-style: normal;
609
+ font-weight: var(--weight-body);
610
+ border-left: none;
611
+ }
612
+
613
+ .article-content blockquote a {
614
+ color: var(--color-link);
615
+ text-decoration: underline;
616
+ }
617
+
618
+ .article-content blockquote p {
619
+ margin-bottom: 0.5rem;
620
+ }
621
+
622
+ .article-content blockquote p:last-child {
623
+ margin-bottom: 0;
624
+ }
625
+
626
+ /* ── Tables ── */
627
+ .article-content table {
628
+ width: 100%;
629
+ border-collapse: collapse;
630
+ margin-bottom: 1.5rem;
631
+ font-size: var(--type-dek);
632
+ }
633
+
634
+ .article-content th,
635
+ .article-content td {
636
+ border: 1px solid var(--border-gentle-subtle);
637
+ padding: 0.5rem 0.75rem;
638
+ text-align: left;
639
+ vertical-align: top;
640
+ }
641
+
642
+ .article-content thead th {
643
+ background: var(--color-surface-container-low);
644
+ font-family: var(--font-body);
645
+ font-weight: var(--weight-emphasis);
646
+ color: var(--color-legend);
647
+ }
648
+
649
+ .article-content tbody tr:nth-child(even) {
650
+ background: var(--color-surface-container-low);
651
+ }
652
+
653
+ .article-content tbody tr:nth-child(even) td {
654
+ color: var(--color-on-surface);
655
+ }
656
+
657
+ .article-content hr {
658
+ border: 0;
659
+ border-top: 1px solid var(--border-gentle-divider);
660
+ margin: 2rem 0;
661
+ clear: both;
662
+ }
663
+
664
+ /* ── Article sidebar heading (title + description live in the left column) ── */
665
+ .article-sidebar-heading {
666
+ border-bottom: 1px solid var(--border-gentle-divider);
667
+ padding-bottom: 1.5rem;
668
+ }
669
+
670
+ .article-sidebar-heading .text-title {
671
+ margin: 0 0 0.875rem;
672
+ }
673
+
674
+ @media (min-width: 768px) {
675
+ .article-sidebar-heading .text-title {
676
+ margin-bottom: 1rem;
677
+ }
678
+ }
679
+
680
+ .article-sidebar-description {
681
+ font-family: var(--font-body);
682
+ font-size: var(--type-body);
683
+ font-weight: var(--weight-body);
684
+ line-height: var(--leading-body);
685
+ letter-spacing: normal;
686
+ color: var(--color-legend);
687
+ margin: 0;
688
+ text-transform: none;
689
+ }
690
+
691
+ /* Speaker cue in a transcript — title-cased name, accent colour, no shouty
692
+ caps and no chip chrome. Colour alone marks who is speaking; the dialogue
693
+ stays the main measure. */
694
+ .transcription-speaker {
695
+ flex: 0 0 20%;
696
+ max-width: 20%;
697
+ font-family: var(--font-body);
698
+ font-size: var(--type-body);
699
+ font-weight: var(--weight-emphasis);
700
+ line-height: var(--leading-body);
701
+ letter-spacing: normal;
702
+ text-transform: none;
703
+ white-space: nowrap;
704
+ }
705
+
706
+ /* Sequential page navigation — same typeface as article descriptions.
707
+ This used to cover .pagination-nav-btn too, which loads after base.css at
708
+ the same specificity and so silently overrode the listing pagination
709
+ component. Article prev/next and listing pagination are different controls
710
+ on different pages; the listing one is defined once in base.css. */
711
+ .text-page-nav a {
712
+ font-family: var(--font-subtitle);
713
+ font-weight: 400;
714
+ letter-spacing: normal;
715
+ text-transform: none;
716
+ }
717
+
718
+ /* Post thumbnail in sidebar — softer than card thumbnails, same 16:9 family */
719
+ .article-sidebar-thumbnail {
720
+ margin: 1rem 0 0;
721
+ aspect-ratio: 16 / 9;
722
+ overflow: hidden;
723
+ position: relative;
724
+ background: var(--sidebar-thumbnail-background);
725
+ width: min(100%, var(--sidebar-thumbnail-width));
726
+ max-width: var(--sidebar-thumbnail-width);
727
+ flex-shrink: 0;
728
+ }
729
+
730
+ .article-sidebar-thumbnail img {
731
+ width: 100%;
732
+ height: 100%;
733
+ object-fit: cover;
734
+ display: block;
735
+ opacity: var(--sidebar-thumbnail-image-opacity);
736
+ filter: saturate(var(--sidebar-thumbnail-filter-saturate)) contrast(var(--sidebar-thumbnail-filter-contrast)) brightness(var(--sidebar-thumbnail-filter-brightness)) sepia(var(--sidebar-thumbnail-filter-sepia));
737
+ }
738
+
739
+ .article-sidebar-thumbnail::after {
740
+ content: '';
741
+ position: absolute;
742
+ inset: 0;
743
+ background: linear-gradient(180deg,
744
+ var(--sidebar-thumbnail-overlay-start) 0%,
745
+ var(--sidebar-thumbnail-overlay-end) 100%);
746
+ pointer-events: none;
747
+ }
748
+
749
+ /* ── Article meta ── */
750
+ .article-meta {
751
+ color: var(--color-on-surface-variant);
752
+ font-size: var(--type-label-lg);
753
+ margin-bottom: 2rem;
754
+ }
755
+
756
+ @media (max-width: 1023px) {
757
+ .text-detail-sidebar .article-meta {
758
+ margin-bottom: 0;
759
+ }
760
+ }
761
+
762
+ /* ── Article table of contents ── */
763
+ .article-toc--sidebar {
764
+ display: none;
765
+ margin-top: 1.5rem;
766
+ padding-top: 1.5rem;
767
+ border-top: 1px solid var(--border-gentle-divider);
768
+ }
769
+
770
+ .article-toc-title,
771
+ .article-toc-summary {
772
+ font-family: var(--font-label);
773
+ font-size: var(--type-label-lg);
774
+ letter-spacing: 0.08em;
775
+ text-transform: uppercase;
776
+ }
777
+
778
+ .article-toc-title {
779
+ font-weight: 600;
780
+ color: var(--color-on-surface);
781
+ margin: 0 0 0.75rem;
782
+ }
783
+
784
+ .article-toc--sidebar .article-toc-list {
785
+ padding-left: 0.75rem;
786
+ }
787
+
788
+ .article-toc-list {
789
+ list-style: none;
790
+ margin: 0;
791
+ padding: 0;
792
+ }
793
+
794
+ .article-toc-item+.article-toc-item {
795
+ margin-top: 0.375rem;
796
+ }
797
+
798
+ .article-toc-link {
799
+ display: flex;
800
+ align-items: baseline;
801
+ gap: var(--space-sm);
802
+ font-family: var(--font-body);
803
+ font-size: var(--type-body);
804
+ line-height: 1.45;
805
+ color: var(--color-on-surface-variant);
806
+ text-decoration: none;
807
+ text-transform: none;
808
+ letter-spacing: normal;
809
+ font-weight: 400;
810
+ }
811
+
812
+ .article-toc-num {
813
+ flex: 0 0 auto;
814
+ min-width: 1.75rem;
815
+ font-family: var(--font-mono);
816
+ font-size: 0.9em;
817
+ letter-spacing: 0.04em;
818
+ color: var(--color-outline);
819
+ }
820
+
821
+ .article-toc-item.is-active>.article-toc-link,
822
+ .article-toc-item.is-active .article-toc-num {
823
+ color: var(--color-primary);
824
+ }
825
+
826
+ .article-toc-item.is-active>.article-toc-link {
827
+ text-decoration: underline;
828
+ text-decoration-thickness: 2px;
829
+ text-underline-offset: 0.22em;
830
+ text-decoration-color: color-mix(in srgb, var(--color-primary) 55%, transparent);
831
+ }
832
+
833
+ .article-toc-link:hover {
834
+ color: var(--color-link-hover);
835
+ text-decoration: underline;
836
+ text-underline-offset: 2px;
837
+ }
838
+
839
+ .article-toc-item--h3 .article-toc-link {
840
+ padding-left: 1.75rem;
841
+ font-size: var(--type-subtitle);
842
+ color: var(--color-on-surface-variant);
843
+ }
844
+
845
+ .article-toc--disclosure {
846
+ margin-bottom: 1.5rem;
847
+ border: 1px solid var(--border-gentle-divider);
848
+ background: var(--color-surface-container-low);
849
+ }
850
+
851
+ @media (max-width: 1023px) {
852
+ .text-detail-sidebar .article-toc--disclosure {
853
+ margin-top: 1.5em;
854
+ margin-bottom: 0;
855
+ }
856
+
857
+ .text-page-main[data-dynamic-article-detail],
858
+ .text-page-main[data-dynamic-post-detail],
859
+ .text-page-main[data-dynamic-portfolio-detail] {
860
+ margin-top: 2.5rem;
861
+ }
862
+ }
863
+
864
+ .article-toc-summary {
865
+ display: flex;
866
+ align-items: center;
867
+ justify-content: space-between;
868
+ gap: 0.75rem;
869
+ padding: 0.75rem 1rem;
870
+ cursor: pointer;
871
+ list-style: none;
872
+ font-weight: 600;
873
+ color: var(--color-on-surface-variant);
874
+ }
875
+
876
+ .article-toc-summary::-webkit-details-marker {
877
+ display: none;
878
+ }
879
+
880
+ .article-toc-summary-icon {
881
+ font-size: var(--type-prose);
882
+ color: var(--color-secondary);
883
+ transition: transform var(--motion-duration-fast) var(--motion-ease-default);
884
+ }
885
+
886
+ .article-toc--disclosure[open] .article-toc-summary-icon {
887
+ transform: rotate(180deg);
888
+ }
889
+
890
+ .article-toc-panel {
891
+ padding: 0 1rem 1rem;
892
+ border-top: 1px solid var(--border-gentle-divider);
893
+ }
894
+
895
+ .article-content h2[id],
896
+ .article-content h3[id] {
897
+ scroll-margin-top: 6rem;
898
+ }
899
+
900
+ @media (min-width: 1024px) {
901
+ .article-toc--sidebar {
902
+ display: block;
903
+ }
904
+
905
+ .article-toc--disclosure {
906
+ display: none;
907
+ }
908
+ }
909
+
910
+ /* ── Internal cross-citation links (xcite) ── */
911
+ a.xcite-ref {
912
+ color: var(--color-secondary);
913
+ text-decoration-line: underline;
914
+ text-underline-offset: 2px;
915
+ text-decoration-thickness: 1px;
916
+ text-decoration-style: dotted;
917
+ }
918
+
919
+ a.xcite-ref:hover {
920
+ color: var(--color-link-hover);
921
+ text-decoration-style: solid;
922
+ }
923
+
924
+ .xcite-missing {
925
+ color: var(--color-error);
926
+ font-family: monospace;
927
+ font-size: var(--type-meta);
928
+ }
929
+
930
+ /* ── Inline note block (gray, similar to CV/portfolio boxes) ── */
931
+ .article-content .article-note {
932
+ background: var(--color-surface-container-low);
933
+ padding: 1rem 1.5rem;
934
+ margin: 1.5em 0;
935
+ font-size: var(--type-subtitle);
936
+ font-weight: var(--weight-body);
937
+ color: var(--color-on-surface);
938
+ }
939
+
940
+ .article-content .article-note> :last-child {
941
+ margin-bottom: 0;
942
+ }
943
+
944
+ /* ══ Code blocks ══
945
+ A fence used to render as a bare grey box: no indication of what language
946
+ was in it, and nothing to do with it but select the text by hand. It now
947
+ carries the chrome an editor gives a file — a bar across the top with the
948
+ language on a tab and a copy control at the right edge, and the pane below.
949
+
950
+ The tab is drawn in the pane's own fill with a rule of primary across its
951
+ top edge, which is the one idiom every editor shares for "this is the file
952
+ you are looking at". It is the same square, hairline-bordered construction
953
+ the ledger cards use; nothing here is rounded and nothing floats.
954
+
955
+ The markup is assembled from the rendered DOM by
956
+ _includes/content-runtime/25-code-blocks.html, so these rules never see a
957
+ `pre` that has not been wrapped. */
958
+ :is(.article-content, .article-cite-block) .code-block {
959
+ margin: 1.5rem 0;
960
+ background: var(--color-surface-container);
961
+ border: 1px solid var(--border-gentle-subtle);
962
+ }
963
+
964
+ :is(.article-content, .article-cite-block) .code-block-bar {
965
+ display: flex;
966
+ align-items: stretch;
967
+ justify-content: space-between;
968
+ gap: var(--space-md);
969
+ background: var(--color-surface-container-high);
970
+ border-bottom: 1px solid var(--border-gentle-subtle);
971
+ }
972
+
973
+ :is(.article-content, .article-cite-block) .code-block-tab {
974
+ display: inline-flex;
975
+ align-items: center;
976
+ padding: 0.4rem 0.9rem;
977
+ background: var(--color-surface-container);
978
+ border-right: 1px solid var(--border-gentle-subtle);
979
+ /* An inset shadow rather than a border-top, so the rule does not add a
980
+ pixel to the tab's height and push the bar taller than the button. */
981
+ box-shadow: inset 0 2px 0 var(--color-primary);
982
+ color: var(--color-on-surface);
983
+ font-size: var(--type-label);
984
+ font-weight: 500;
985
+ letter-spacing: 0.12em;
986
+ line-height: 1;
987
+ }
988
+
989
+ :is(.article-content, .article-cite-block) .code-block-copy {
990
+ padding: 0.4rem 0.9rem;
991
+ border: 0;
992
+ background: transparent;
993
+ color: var(--color-on-surface-variant);
994
+ font-size: var(--type-label);
995
+ letter-spacing: 0.12em;
996
+ line-height: 1;
997
+ cursor: pointer;
998
+ transition: color var(--motion-duration-fast) var(--motion-ease-default);
999
+ }
1000
+
1001
+ :is(.article-content, .article-cite-block) .code-block-copy:hover,
1002
+ :is(.article-content, .article-cite-block) .code-block-copy:focus-visible {
1003
+ color: var(--color-primary);
1004
+ }
1005
+
1006
+ /* The acknowledgement is the secondary green, which is the colour this site
1007
+ already uses for a state rather than for a link. */
1008
+ :is(.article-content, .article-cite-block) .code-block-copy.is-copied {
1009
+ color: var(--color-secondary);
1010
+ }
1011
+
1012
+ /* The container carries the fill and the frame now, so the pane inside is
1013
+ only the scroll box and the padding. */
1014
+ :is(.article-content, .article-cite-block) .code-block pre {
1015
+ margin: 0;
1016
+ background: none;
1017
+ border: 0;
1018
+ }
1019
+
1020
+ /* ── Terminal captures ──
1021
+ An `ansi` fence is a real capture, escape codes and all, rendered by
1022
+ renderAnsiHtml into spans that paint from the sixteen custom properties
1023
+ below. The ground is stated outright instead of being taken from the
1024
+ surface ladder, and it does not follow the theme: ANSI colours are defined
1025
+ against a dark terminal and a bright yellow on the light palette's white
1026
+ canvas is illegible. A capture of a terminal brings its own surface with
1027
+ it, the way a photograph brings its own light.
1028
+
1029
+ The palette is Tango, which is what Ubuntu's terminal ships and therefore
1030
+ what the capture in the WSL article was actually drawn with. Bold maps to
1031
+ the bright half in the renderer, not here, because that is a terminal
1032
+ behaviour rather than a typographic one. */
1033
+ .article-content .code-block--ansi {
1034
+ --ansi-bg: #12130f;
1035
+ --ansi-fg: #d3d7cf;
1036
+ --ansi-fg-bold: #eeeeec;
1037
+ /* The resting colour of the copy control. `--ansi-8`, the bright black the
1038
+ palette would otherwise suggest, is 2.5 : 1 on this ground; this is 4.9 : 1
1039
+ against the bar it sits on. */
1040
+ --ansi-fg-dim: #9aa09a;
1041
+ --ansi-0: #2e3436;
1042
+ --ansi-1: #cc0000;
1043
+ --ansi-2: #4e9a06;
1044
+ --ansi-3: #c4a000;
1045
+ --ansi-4: #3465a4;
1046
+ --ansi-5: #75507b;
1047
+ --ansi-6: #06989a;
1048
+ --ansi-7: #d3d7cf;
1049
+ --ansi-8: #555753;
1050
+ --ansi-9: #ef2929;
1051
+ --ansi-10: #8ae234;
1052
+ --ansi-11: #fce94f;
1053
+ --ansi-12: #729fcf;
1054
+ --ansi-13: #ad7fa8;
1055
+ --ansi-14: #34e2e2;
1056
+ --ansi-15: #eeeeec;
1057
+ background: var(--ansi-bg);
1058
+ border-color: var(--ansi-0);
1059
+ }
1060
+
1061
+ .article-content .code-block--ansi .code-block-bar {
1062
+ background: var(--ansi-0);
1063
+ border-bottom-color: var(--ansi-8);
1064
+ }
1065
+
1066
+ .article-content .code-block--ansi .code-block-tab {
1067
+ background: var(--ansi-bg);
1068
+ border-right-color: var(--ansi-8);
1069
+ color: var(--ansi-fg);
1070
+ }
1071
+
1072
+ .article-content .code-block--ansi .code-block-copy {
1073
+ color: var(--ansi-fg-dim);
1074
+ }
1075
+
1076
+ .article-content .code-block--ansi .code-block-copy:hover,
1077
+ .article-content .code-block--ansi .code-block-copy:focus-visible {
1078
+ color: var(--ansi-fg);
1079
+ }
1080
+
1081
+ .article-content .code-block--ansi .code-block-copy.is-copied {
1082
+ color: var(--ansi-10);
1083
+ }
1084
+
1085
+ /* Box drawing made of `s`, `o` and `/` only holds together on a tight grid,
1086
+ so the line height is closed up and the size dropped below the prose
1087
+ `code`. A capture is as wide as the terminal that took it — the neofetch
1088
+ block below runs to about 100 columns — and the reading measure is not
1089
+ negotiable, so past that width the pane scrolls. */
1090
+ .article-content .code-block--ansi pre {
1091
+ padding: 0.9rem 1rem;
1092
+ color: var(--ansi-fg);
1093
+ overflow-x: auto;
1094
+ /* Both terms sit on the `pre` rather than on the `code` inside it. `code` is
1095
+ inline, so the block it sits in is what establishes the line boxes: a
1096
+ line-height set on the `code` only sizes its own inline box, and the
1097
+ taller of that and the block's strut wins. With detail.css setting the
1098
+ `pre` to 0.85rem/1.6, the strut was 23.9px against a 15.6px inline box,
1099
+ and the capture rendered at two-thirds again the leading it asked for.
1100
+ Stating the size here as well keeps the strut and the content in step.
1101
+
1102
+ 1.15 is a terminal cell. VTE — what Ubuntu's terminal is built on — takes
1103
+ its row height from the font's own ascent plus descent and adds nothing,
1104
+ which lands between 1.16 and 1.20 for the monospace faces it ships. Box
1105
+ drawing made of `s`, `o` and `/` needs that: at prose leading the Ubuntu
1106
+ logo's swirl breaks into twenty unrelated rows of punctuation. */
1107
+ font-size: var(--type-micro);
1108
+ line-height: 1.15;
1109
+ }
1110
+
1111
+ .article-content .code-block--ansi code {
1112
+ font-size: inherit;
1113
+ line-height: inherit;
1114
+ -webkit-font-smoothing: antialiased;
1115
+ }
1116
+
1117
+ /* ── Definition cards & social quote callouts (e.g. sovereignty) ── */
1118
+ .article-content .sov-grid {
1119
+ display: grid;
1120
+ grid-template-columns: 1fr;
1121
+ gap: 1.0rem;
1122
+ margin: 1.25rem 0 1.75rem;
1123
+ }
1124
+
1125
+ @media (min-width: 720px) {
1126
+ .article-content .sov-grid {
1127
+ grid-template-columns: 1fr 1fr;
1128
+ }
1129
+ }
1130
+
1131
+ .article-content .sov-card {
1132
+ border: 1px solid color-mix(in srgb, var(--color-outline, #888) 45%, transparent);
1133
+ border-left: 3px solid var(--sov-accent, var(--color-primary, #5b6cff));
1134
+ border-radius: 0;
1135
+ background: color-mix(in srgb, var(--color-surface-card, transparent) 92%, transparent);
1136
+ padding: 0.7rem 0.85rem 0.8rem;
1137
+ display: flex;
1138
+ flex-direction: column;
1139
+ gap: 0.5rem;
1140
+ min-width: 0;
1141
+ }
1142
+
1143
+ .article-content .sov-card p {
1144
+ margin: 0;
1145
+ }
1146
+
1147
+ .article-content .sov-card__head {
1148
+ display: flex;
1149
+ align-items: center;
1150
+ gap: 0.55rem;
1151
+ flex-wrap: wrap;
1152
+ }
1153
+
1154
+ .article-content .sov-card__flag {
1155
+ display: inline-flex;
1156
+ align-items: center;
1157
+ justify-content: center;
1158
+ height: 1.65rem;
1159
+ padding: 0 0.4rem;
1160
+ border: 1.5px solid color-mix(in srgb, currentColor 40%, var(--border-ink, #444));
1161
+ border-radius: 0;
1162
+ line-height: 1;
1163
+ }
1164
+
1165
+ .article-content .sov-card__flag .fi {
1166
+ font-size: 1.05rem;
1167
+ }
1168
+
1169
+ .article-content .sov-card__meta {
1170
+ font-size: 0.72rem;
1171
+ letter-spacing: 0.04em;
1172
+ text-transform: uppercase;
1173
+ opacity: 0.8;
1174
+ font-weight: 600;
1175
+ }
1176
+
1177
+ .article-content .sov-card__lemma {
1178
+ font-style: italic;
1179
+ font-weight: 400;
1180
+ margin: 0;
1181
+ font-size: 1.05rem;
1182
+ color: var(--color-on-surface);
1183
+ }
1184
+
1185
+ .article-content .sov-card__def {
1186
+ margin: 0;
1187
+ padding: 0.5rem 0.7rem;
1188
+ border-left: 2px solid color-mix(in srgb, var(--sov-accent, #5b6cff) 55%, transparent);
1189
+ background: color-mix(in srgb, var(--sov-accent, #5b6cff) 8%, transparent);
1190
+ font-size: 0.88rem;
1191
+ line-height: 1.68;
1192
+ color: var(--color-on-surface);
1193
+ }
1194
+
1195
+ .article-content .sov-card__def p {
1196
+ margin: 0;
1197
+ font-size: inherit;
1198
+ line-height: inherit;
1199
+ }
1200
+
1201
+ .article-content .sov-card blockquote {
1202
+ margin: 0;
1203
+ border: none;
1204
+ border-left: 2px solid color-mix(in srgb, var(--sov-accent, #5b6cff) 55%, transparent);
1205
+ border-radius: 0;
1206
+ padding: 0.5rem 0.7rem;
1207
+ background: color-mix(in srgb, var(--sov-accent, #5b6cff) 8%, transparent);
1208
+ font-size: 0.88rem;
1209
+ line-height: 1.68;
1210
+ color: var(--color-on-surface);
1211
+ }
1212
+
1213
+ .article-content .sov-card blockquote p {
1214
+ margin: 0;
1215
+ font-size: inherit;
1216
+ line-height: inherit;
1217
+ }
1218
+
1219
+ .article-content .sov-card__note {
1220
+ margin: 0;
1221
+ font-size: 0.82rem;
1222
+ line-height: 1.55;
1223
+ opacity: 0.9;
1224
+ }
1225
+
1226
+ .article-content .sov-card__cite {
1227
+ margin: 0;
1228
+ font-size: 0.76rem;
1229
+ line-height: 1.45;
1230
+ opacity: 0.75;
1231
+ }
1232
+
1233
+ .article-content .sov-card__cite a {
1234
+ color: inherit;
1235
+ text-decoration: underline;
1236
+ text-underline-offset: 2px;
1237
+ }
1238
+
1239
+ .sov-card--us {
1240
+ --sov-accent: #003494;
1241
+ }
1242
+
1243
+ .sov-card--gb {
1244
+ --sov-accent: #797300;
1245
+ }
1246
+
1247
+ .sov-card--br {
1248
+ --sov-accent: #118826;
1249
+ }
1250
+
1251
+ .sov-card--fr {
1252
+ --sov-accent: #9f020a;
1253
+ }
1254
+
1255
+ .article-content :is(.sov-tweet, .article-tweet) {
1256
+ max-width: 34rem;
1257
+ margin: 1.25rem 0 1.75rem;
1258
+ padding: 0.95rem 1.15rem 1rem;
1259
+ border: 1px solid color-mix(in srgb, var(--color-outline, #888) 45%, transparent);
1260
+ border-radius: 0;
1261
+ background: color-mix(in srgb, var(--color-surface-card, transparent) 92%, transparent);
1262
+ font-size: 0.95rem;
1263
+ }
1264
+
1265
+ .article-content :is(.sov-tweet, .article-tweet) p {
1266
+ margin: 0;
1267
+ }
1268
+
1269
+ .article-content :is(.sov-tweet__head, .article-tweet__head) {
1270
+ display: flex;
1271
+ align-items: center;
1272
+ gap: 0.55rem;
1273
+ margin-bottom: 0.85rem;
1274
+ }
1275
+
1276
+ .article-content :is(.sov-tweet__avatar, .article-tweet__avatar) {
1277
+ width: 2.2rem;
1278
+ height: 2.2rem;
1279
+ border-radius: 0%;
1280
+ background: color-mix(in srgb, var(--color-primary, #7b8896) 25%, transparent);
1281
+ display: inline-flex;
1282
+ align-items: center;
1283
+ justify-content: center;
1284
+ font-weight: 300;
1285
+ font-size: 1.25rem;
1286
+ }
1287
+
1288
+ .article-content :is(.sov-tweet__id, .article-tweet__id) {
1289
+ display: flex;
1290
+ flex-direction: column;
1291
+ line-height: 1.15;
1292
+ }
1293
+
1294
+ .article-content :is(.sov-tweet__name, .article-tweet__name) {
1295
+ font-weight: 700;
1296
+ font-size: 0.95rem;
1297
+ }
1298
+
1299
+ .article-content :is(.sov-tweet__handle, .article-tweet__handle) {
1300
+ font-size: 0.82rem;
1301
+ opacity: 0.75;
1302
+ }
1303
+
1304
+ .article-content :is(.sov-tweet__body, .article-tweet__body) {
1305
+ margin: 0 0 0.5rem !important;
1306
+ font-size: 0.98rem;
1307
+ line-height: 1.65;
1308
+ }
1309
+
1310
+ .article-content :is(.sov-tweet__body, .article-tweet__body) em {
1311
+ font-style: italic;
1312
+ }
1313
+
1314
+ .article-content :is(.sov-tweet__cite, .article-tweet__cite) {
1315
+ margin: 0 0 0.55rem !important;
1316
+ font-size: 0.82rem;
1317
+ line-height: 1.5;
1318
+ opacity: 0.8;
1319
+ }
1320
+
1321
+ .article-content :is(.sov-tweet__cite, .article-tweet__cite) a {
1322
+ text-decoration: none;
1323
+ }
1324
+
1325
+ .article-content :is(.sov-tweet__note, .article-tweet__note) {
1326
+ margin: 0 !important;
1327
+ font-size: 0.88rem;
1328
+ line-height: 1.6;
1329
+ opacity: 0.9;
1330
+ }