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,379 @@
1
+ /* Ledger page styles: rascunhos. Owned by the rascunhos page; shared component rules
2
+ belong in assets/css/base.css under "Architectural Ledger blocks".
3
+
4
+ Every ledger sheet is linked on every page, so each selector here is scoped
5
+ to markup only this layout emits (.rascunhos-*) or to the blog page header
6
+ (.rascunhos-header). The sidebar is the exception by design: it is the same
7
+ .writing-rail block /pages/articles/ carries, styled once in writing.css and
8
+ placed here. Colour comes from the palette custom properties alone: a
9
+ literal hex would be correct in one theme and wrong in the other. */
10
+
11
+ /* The page is a bound notebook: entries ruled off from one another, each
12
+ opening on its number in the log, and months naming each run. Both devices
13
+ are computed from `date` and `last_updated`. Nothing on this page counts
14
+ words, times a read, names a city or numbers a volume. */
15
+ .rascunhos-main {
16
+ --rascunhos-plate: 6rem;
17
+ --rascunhos-rule: 1px solid var(--border-gentle-divider);
18
+ }
19
+
20
+ /* ══ Masthead ══
21
+ The shard sits in this wrapper rather than in the shared page-header
22
+ include, which every listing renders and which no single page may restyle. */
23
+ .rascunhos-masthead {
24
+ position: relative;
25
+ margin-bottom: var(--space-2xl);
26
+ }
27
+
28
+ /* Sized to the masthead it accents. It used to be 24rem x 15rem, which fitted
29
+ while the filter deck was a strip under the title and made the masthead
30
+ 260px tall; with the filter moved into the rail the masthead is 107px and a
31
+ 15rem shard hung 50px into the body, cutting across the rail's keyword box.
32
+ An accent that reaches the content it is accenting is not an accent. */
33
+ .rascunhos-shard {
34
+ top: calc(-1 * var(--space-2xl));
35
+ right: calc(-1 * var(--space-xl));
36
+ width: 16rem;
37
+ height: 9rem;
38
+ /* Page accent (ochre via `.page-accent--ochre`), not the triad secondary. */
39
+ background: var(--color-primary);
40
+ }
41
+
42
+ /* Between the 640px shard cut-off and the desktop layout a 24rem shard is
43
+ wider than the masthead is tall: at 768px it covered the title and the
44
+ standfirst and read as a mis-clipped slab rather than a corner accent. The
45
+ writing lead and the CV dossier carry the same correction. */
46
+ @media (max-width: 1023px) {
47
+ .rascunhos-shard {
48
+ top: calc(-1 * var(--space-xl));
49
+ right: calc(-1 * var(--space-lg));
50
+ width: 12rem;
51
+ height: 8rem;
52
+ }
53
+ }
54
+
55
+ .rascunhos-header {
56
+ position: relative;
57
+ z-index: 1;
58
+ margin-bottom: 0;
59
+ }
60
+
61
+ /* ══ Body: ledger column beside the filter rail ══
62
+ Same split as /pages/articles/, to the rem: the sidebar is the same block
63
+ on both writing listings, so it takes the same column on both. Below 1024px
64
+ the rail is the shared disclosure and the grid collapses. */
65
+ .rascunhos-body {
66
+ display: block;
67
+ }
68
+
69
+ /* `.hidden` swaps the listing for the detail view; it has to beat the grid. */
70
+ .rascunhos-body.hidden {
71
+ display: none;
72
+ }
73
+
74
+ @media (min-width: 1024px) {
75
+ .rascunhos-body {
76
+ display: grid;
77
+ grid-template-columns: minmax(0, 1fr) 16rem;
78
+ gap: var(--space-2xl);
79
+ align-items: start;
80
+ }
81
+
82
+ .rascunhos-ledger {
83
+ grid-column: 1;
84
+ grid-row: 1;
85
+ min-width: 0;
86
+ }
87
+
88
+ .rascunhos-body > .writing-rail {
89
+ grid-column: 2;
90
+ grid-row: 1;
91
+ }
92
+ }
93
+
94
+ @media (min-width: 1280px) {
95
+ .rascunhos-body {
96
+ grid-template-columns: minmax(0, 1fr) 18rem;
97
+ }
98
+ }
99
+
100
+ /* A legend, not notation. The mono face came off legends and dates site-wide;
101
+ this is the same treatment `.ledger-kicker` carries in base.css. */
102
+ .rascunhos-ledger-head {
103
+ display: flex;
104
+ flex-wrap: wrap;
105
+ align-items: baseline;
106
+ justify-content: space-between;
107
+ gap: var(--space-sm);
108
+ padding-bottom: var(--space-sm);
109
+ margin-bottom: var(--space-lg);
110
+ color: var(--color-on-surface-variant);
111
+ font-family: var(--font-body);
112
+ font-size: var(--type-meta);
113
+ font-weight: var(--weight-label);
114
+ text-transform: uppercase;
115
+ letter-spacing: 0.06em;
116
+ border-bottom: 1px solid var(--border-gentle-divider);
117
+ }
118
+
119
+ /* No gap: each entry is closed by the hairline under it, so the rows have to
120
+ touch or the ruling dashes itself between them. */
121
+ .rascunhos-entries {
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: var(--space-md);
125
+ }
126
+
127
+ .rascunhos-state {
128
+ padding: var(--space-xl);
129
+ background: var(--color-surface-card);
130
+ border: 1.5px solid var(--border-ink);
131
+ border-radius: var(--radius-card);
132
+ }
133
+
134
+ /* ══ The page ══
135
+ Every child of the ledger — entry, month heading — is the same band, so the
136
+ run of entries keeps one rhythm wherever a heading interrupts it.
137
+
138
+ The band used to be two columns, with a ruled margin on the left holding the
139
+ folio number and the revision note. Both are facts about the entry, so both
140
+ now sit in the entry's own ledger line, and a ruled column holding nothing
141
+ is a rule drawn for its own sake. */
142
+ .rascunhos-band {
143
+ display: block;
144
+ }
145
+
146
+ /* The hover fill is painted on the whole band, so whatever sits hard against
147
+ the body's right edge ends up with the fill's edge drawn along its own
148
+ border. That is what the classification chip was doing: two rectangles
149
+ sharing an edge, reading as one mis-drawn box. The body keeps a gutter on
150
+ both sides and nothing touches the fill. */
151
+ .rascunhos-band-body {
152
+ /* Named because the plate cancels exactly this padding to reach the rules
153
+ above and below it; a literal in both places would drift at the first
154
+ breakpoint that changes one of them. */
155
+ --rascunhos-body-pad-top: var(--space-lg);
156
+ --rascunhos-body-pad-bottom: var(--space-lg);
157
+ min-width: 0;
158
+ padding: var(--rascunhos-body-pad-top) var(--space-lg) var(--rascunhos-body-pad-bottom);
159
+ }
160
+
161
+ /* The folio: the entry's place in the log, assigned once over the whole corpus
162
+ so a filter cannot renumber it. A figure, which is the remaining case for
163
+ monospace on this site — and a run of them down the ledger only lines up
164
+ because the face is monospaced. It opens the line, so it takes the weight
165
+ the rest of the strip does not. */
166
+ .rascunhos-folio {
167
+ color: var(--color-on-surface);
168
+ font-size: var(--type-meta);
169
+ }
170
+
171
+ /* The quieter half of the ledger line: it explains why an entry is where the
172
+ revision sort puts it without competing with the date it was written. Twin
173
+ of `.writing-revised` on the articles archive. */
174
+ .rascunhos-revision {
175
+ color: color-mix(in srgb, var(--color-on-surface-variant) 78%, transparent);
176
+ }
177
+
178
+ /* ══ Rhythm: the month opening each run ══
179
+ Not small caps and not mono — it is a legend. The space above it is what
180
+ makes a month of six entries look different from a month of one. */
181
+ .rascunhos-month > .rascunhos-band-body {
182
+ padding-top: var(--space-2xl);
183
+ padding-bottom: var(--space-xs);
184
+ }
185
+
186
+ .rascunhos-month-label {
187
+ margin: 0;
188
+ color: var(--color-on-surface-variant);
189
+ font-family: var(--font-body);
190
+ font-size: var(--type-meta);
191
+ font-weight: var(--weight-emphasis);
192
+ letter-spacing: 0.05em;
193
+ text-transform: uppercase;
194
+ }
195
+
196
+ /* Entry titles — a step above the shared ledger-row scale so the notebook
197
+ line reads as a title, not a caption. */
198
+ .rascunhos-row .ledger-row-title {
199
+ font-size: var(--type-card-lg);
200
+ line-height: 1.25;
201
+ }
202
+
203
+ /* ══ Ledger entry ══
204
+ Ruled paper, not a stack of cards: the shared row's card fill and padding
205
+ come off, and what separates two entries is the hairline under each one.
206
+ The fill returns on hover, which is the only moment a row needs to be an
207
+ object rather than a line on a page. */
208
+ .ledger-row.rascunhos-row {
209
+ gap: 0;
210
+ padding: 0;
211
+ background: var(--color-surface-card);
212
+ border: 1.5px solid var(--border-ink);
213
+ border-radius: var(--radius-card);
214
+ transition: background-color var(--motion-duration-fast) var(--motion-ease-default),
215
+ border-color var(--motion-duration-fast) var(--motion-ease-default);
216
+ }
217
+
218
+ .ledger-row.rascunhos-row:hover {
219
+ background: var(--color-surface-high);
220
+ border-color: var(--color-primary);
221
+ }
222
+
223
+ .ledger-row.rascunhos-row:hover .ledger-row-title a {
224
+ color: var(--color-primary);
225
+ }
226
+
227
+ .rascunhos-row > .rascunhos-band-body {
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: var(--space-sm);
231
+ }
232
+
233
+ .rascunhos-row .ledger-row-head {
234
+ display: flex;
235
+ flex-wrap: wrap;
236
+ align-items: center;
237
+ justify-content: space-between;
238
+ gap: var(--space-sm);
239
+ }
240
+
241
+ /* Date and language sit left, classification right, as in the mockup. */
242
+ .rascunhos-row .ledger-row-head .ledger-meta {
243
+ margin-right: auto;
244
+ }
245
+
246
+ .rascunhos-row .ledger-row-copy {
247
+ max-width: var(--reading-measure, 45rem);
248
+ }
249
+
250
+ /* The row carries no read-more link — the title is the link. The mockup's
251
+ hover arrow is not reproduced: as an inline pseudo-element it reserves a
252
+ character of line width even while invisible, which tips a title that fits
253
+ on one line onto two. The shared row already answers the hover with a
254
+ surface step and a colour change on the title.
255
+
256
+ The focus ring used to be the secondary at 45%, which washed out to 1.92:1
257
+ against the card in light and 2.54:1 in dark — below the 3:1 a focus
258
+ indicator owes, and weaker than the browser ring it was replacing.
259
+ Full-strength primary is 6.67:1 and 8.10:1 on the same two surfaces. */
260
+ .rascunhos-row .ledger-row-title a:focus-visible {
261
+ outline: 2px solid var(--color-primary);
262
+ outline-offset: 3px;
263
+ }
264
+
265
+ /* Thumbnails are set on almost every exported post, so the entry carries a
266
+ small plate to scan by. The body only becomes two columns once an image is
267
+ actually in it — a post without one, or whose remote image 404s, keeps the
268
+ plain stacked entry rather than an indent against an empty gutter. */
269
+ .rascunhos-row--illustrated > .rascunhos-band-body {
270
+ display: grid;
271
+ /* Three explicit rows — meta, title, preview — so the plate's `1 / -1`
272
+ resolves against a real explicit grid instead of collapsing to row 1 and
273
+ pushing the title down by the height of the image. */
274
+ grid-template-columns: var(--rascunhos-plate) minmax(0, 1fr);
275
+ grid-template-rows: auto auto auto;
276
+ column-gap: var(--space-lg);
277
+ row-gap: var(--space-sm);
278
+ align-items: start;
279
+ }
280
+
281
+ /* The plate is a column of the entry, not a stamp in its corner: it spans
282
+ every row of the body and stretches to the height they come out at, so an
283
+ entry with three lines of preview and an entry with one are both fully
284
+ faced. The negative block margin cancels the body's own padding, which puts
285
+ the top and bottom of the image on the rules that bound the entry — the
286
+ plate covers the card, rather than floating inside it. */
287
+ .rascunhos-row--illustrated .rascunhos-row-media {
288
+ grid-column: 1;
289
+ grid-row: 1 / -1;
290
+ align-self: stretch;
291
+ margin-block: calc(-1 * var(--rascunhos-body-pad-top))
292
+ calc(-1 * var(--rascunhos-body-pad-bottom));
293
+ }
294
+
295
+ .rascunhos-row--illustrated > .rascunhos-band-body > :not(.rascunhos-row-media) {
296
+ grid-column: 2;
297
+ }
298
+
299
+ /* No aspect ratio: the height is whatever the entry beside it works out to,
300
+ and `object-fit: cover` below crops to it. A fixed ratio here would fight
301
+ the stretch and reintroduce the fixed plate. */
302
+ .rascunhos-row-media {
303
+ width: var(--rascunhos-plate);
304
+ overflow: hidden;
305
+ background: var(--color-surface-container);
306
+ }
307
+
308
+ /* Grain. These are pasted-in crops, not hero images: held back far enough
309
+ that a column of them reads as texture beside the writing, and coming up to
310
+ full colour only for the entry under the cursor. */
311
+ .rascunhos-row-media img {
312
+ display: block;
313
+ width: 100%;
314
+ height: 100%;
315
+ object-fit: cover;
316
+ filter: grayscale(0.55) contrast(1.03);
317
+ transition: filter var(--motion-duration-base) var(--motion-ease-default);
318
+ }
319
+
320
+ .rascunhos-row:hover .rascunhos-row-media img,
321
+ .rascunhos-row:focus-within .rascunhos-row-media img {
322
+ filter: grayscale(0) contrast(1);
323
+ }
324
+
325
+ @media (prefers-reduced-motion: reduce) {
326
+ .rascunhos-row-media img {
327
+ transition: none;
328
+ }
329
+ }
330
+
331
+ /* A phone spends its width on the sentence. The entry keeps its gutters but
332
+ loses most of the block padding. */
333
+ @media (max-width: 767px) {
334
+ .rascunhos-band-body {
335
+ --rascunhos-body-pad-top: var(--space-sm);
336
+ padding-inline: var(--space-md);
337
+ }
338
+ }
339
+
340
+ /* Below the tablet breakpoint the plate goes, and the entry becomes pure
341
+ typography — the same call the home page's draft ledger makes, for the same
342
+ reason. Even at 4rem the plate left the excerpt about 23 characters a line
343
+ at 390px, which is not a measure, and it reserved a column of empty gutter
344
+ down the rest of the row. A thumbnail is decoration on a text ledger; the
345
+ sentence is not. */
346
+ @media (max-width: 639px) {
347
+ .rascunhos-row--illustrated > .rascunhos-band-body {
348
+ display: flex;
349
+ flex-direction: column;
350
+ }
351
+
352
+ .rascunhos-row--illustrated .rascunhos-row-media {
353
+ display: none;
354
+ }
355
+ }
356
+
357
+ /* ══ Pagination ══
358
+ The buttons are `.pagination-nav-btn` from base.css, shared with the writing
359
+ index and sources; only the bar around them is local. */
360
+ .rascunhos-pagination {
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ gap: var(--space-lg);
365
+ margin-top: var(--space-lg);
366
+ padding: var(--space-sm) var(--space-md);
367
+ background: var(--color-panel-contrast);
368
+ }
369
+
370
+ .rascunhos-pagination.hidden {
371
+ display: none;
372
+ }
373
+
374
+ /* Pagination count face/size live on `.pagination-count` in base.css.
375
+ Keep nowrap so "4 / 4" does not stack between the buttons at 390px. */
376
+ .rascunhos-pagination > span {
377
+ white-space: nowrap;
378
+ }
379
+
@@ -0,0 +1,327 @@
1
+ /* Ledger page styles: sources. Owned by the sources page; shared component rules
2
+ belong in assets/css/base.css under "Architectural Ledger blocks".
3
+
4
+ The sources grid is the archive card from 43-blocks.html with three additions
5
+ the resources collection earns and no other listing has: a poster from
6
+ `thumbnail`, a row of four facet chips read off `type`, `category` and
7
+ `language`, and the publisher host from `link`. Every colour is a token, so
8
+ both palettes resolve from the same rules. */
9
+
10
+ /* ── Shards ──
11
+ The page had no depth treatment of any kind, and it is the wrong shape for
12
+ the ambient pool the home bands carry: a pool beNewsreader a grid of many small
13
+ cards only darkens one corner of it. Shards instead, the two canonical
14
+ polygons from base.css, one at each end of the run — top right above the
15
+ grid, bottom left below it, the diagonal pair the CV dossier uses inside
16
+ one panel, opened out across the page. In dark the source cards stay
17
+ opaque so the shard reads in the gutter, not through the poster.
18
+
19
+ Neither container takes `overflow: hidden`: the masthead holds the filter
20
+ popover, which would be clipped by it. The offsets are therefore sized to
21
+ stay inside the page's own gutter at every width the shards survive, the
22
+ way the rascunhos masthead does it. The shared utility already unmounts
23
+ both below 640px. */
24
+ /* The masthead outranks the grid because the filter popover opens out of it.
25
+ Lifting the header over its own shard puts the popover inside a stacking
26
+ context, and the grid below has to be lifted over the foot shard in the
27
+ same way — without this the cards would be painted over an open filter
28
+ panel. */
29
+ .sources-masthead {
30
+ position: relative;
31
+ z-index: 2;
32
+ }
33
+
34
+
35
+
36
+ .sources-header {
37
+ position: relative;
38
+ z-index: 1;
39
+ }
40
+
41
+ .sources-header .page-header-tags {
42
+ color: var(--color-primary);
43
+ opacity: 0.85;
44
+ }
45
+
46
+ .sources-masthead-shard {
47
+ top: calc(-1 * var(--space-2xl));
48
+ right: calc(-1 * var(--space-xl));
49
+ width: 16rem;
50
+ height: 9rem;
51
+ background: var(--color-primary);
52
+ }
53
+
54
+ .sources-body {
55
+ position: relative;
56
+ }
57
+
58
+ /* A card is not positioned, so without this the shard — which is — would be
59
+ painted over the bottom row rather than beNewsreader it. The rule the whole
60
+ family answers to is that a backdrop never touches the object in front of
61
+ it. */
62
+ .sources-body>.listing-card-grid,
63
+ .sources-body>.source-pagination {
64
+ position: relative;
65
+ z-index: 1;
66
+ }
67
+
68
+ /* The foot of the page, beside the pagination. It reaches up beNewsreader the last
69
+ row of cards. In dark those cards are opaque again, so what shows is the
70
+ wedge in the gutter and the band across the pagination strip. */
71
+ .sources-foot-shard {
72
+ left: calc(-1 * var(--space-xl));
73
+ bottom: calc(-1 * var(--space-lg));
74
+ width: 14rem;
75
+ height: 8rem;
76
+ background: color-mix(in srgb, var(--color-primary) 70%, transparent);
77
+ }
78
+
79
+ /* Below the desktop layout the page gutter halves, so the shards have to come
80
+ back inside it or they widen the document. */
81
+ @media (max-width: 1023px) {
82
+ .sources-masthead-shard {
83
+ top: calc(-1 * var(--space-xl));
84
+ right: calc(-1 * var(--space-lg));
85
+ width: 12rem;
86
+ height: 8rem;
87
+ }
88
+
89
+ .sources-foot-shard {
90
+ left: calc(-1 * var(--space-lg));
91
+ width: 10rem;
92
+ height: 7rem;
93
+ }
94
+ }
95
+
96
+ /* Pagination hides rather than unmounts, so the cards keep their identity
97
+ between pages and the filter only has to toggle a class. */
98
+ .resource-hidden {
99
+ display: none !important;
100
+ }
101
+
102
+ /* Three across at desktop rather than four: a card now carries a poster, a
103
+ summary and a footer, and a quarter of the row is too narrow for that. */
104
+ .listing-card-grid--sources {
105
+ --listing-grid-cols: 2;
106
+ gap: var(--space-lg);
107
+ }
108
+
109
+ @media (min-width: 1024px) {
110
+ .listing-card-grid--sources {
111
+ --listing-grid-cols: 3;
112
+ }
113
+ }
114
+
115
+ @media (min-width: 1280px) {
116
+ .listing-card-grid--sources {
117
+ --listing-grid-cols: 3;
118
+ }
119
+ }
120
+
121
+ /* ── The source card ── */
122
+
123
+ /* The poster is full-bleed: it sits outside .ledger-card-body, so the card's
124
+ own padding never boxes it in. A single hairline separates it from the text
125
+ block, which is the only depth cue the surface ladder allows. Thumbnails
126
+ arrive from other people's sites at wildly different crops, so the card
127
+ imposes one ratio and lets the fill do the rest.
128
+
129
+ That ratio is the video frame, 16:9 — the native size of the YouTube
130
+ thumbnails most of this collection links to (maxresdefault is 1280x720,
131
+ hq720 likewise). At 16:10 the fill cropped a slice off the top and bottom
132
+ of every one of them; at 16:9 they land whole. The card keeps the width
133
+ the grid gives it, so the change lands in the height. */
134
+ .source-card>.ledger-poster {
135
+ aspect-ratio: 16 / 9;
136
+ border-bottom: 1px solid var(--border-gentle-subtle);
137
+ }
138
+
139
+ .source-card-title {
140
+ overflow-wrap: anywhere;
141
+ }
142
+
143
+ /* ── The facet row ──
144
+
145
+ A source declares four things about itself — medium, category, language,
146
+ country — and the card used to scatter them: two in the meta line above the
147
+ title, one in a chip row below the description, and the country nowhere at
148
+ all, even though every locale in the collection names one. They are one
149
+ fact about the source, so they are now one row of four chips.
150
+
151
+ The row is a single family: same height, same type, same hairline, each
152
+ carrying its own ink. Height is fixed rather than intrinsic, because the
153
+ chips do not all hold text — the country chip holds an image, and matching
154
+ heights by padding alone would leave it a pixel or two short of the rest.
155
+ Chips never wrap internally; the row wraps instead, so a narrow card breaks
156
+ between chips and not through one. */
157
+ .source-facets {
158
+ --source-facet-height: 1.65rem;
159
+ display: flex;
160
+ flex-wrap: wrap;
161
+ align-items: center;
162
+ gap: var(--space-xs);
163
+ }
164
+
165
+ /* The category chip comes from the shared renderer, which wraps its output in
166
+ a .ledger-chip-row. `display: contents` dissolves that wrapper so the chip
167
+ joins this row as a sibling of the other three, rather than sitting in a
168
+ nested flex box that would set its own height. The shared renderer is left
169
+ exactly as it is. */
170
+ .source-facets .ledger-chip-row {
171
+ display: contents;
172
+ }
173
+
174
+ /* One geometry for all four. The border is currentColor rather than a fixed
175
+ token, so each chip outlines itself in whatever ink it is already using and
176
+ the row stays a family across both palettes. The shared category chip is a
177
+ filled chip everywhere else on the site; here it outlines like its three
178
+ neighbors, and its hover still fills — which is how a reader can tell that
179
+ this one, alone, is a link. */
180
+ .source-facets .source-facet,
181
+ .source-facets .ledger-chip,
182
+ .source-facets .lang-pill {
183
+ display: inline-flex;
184
+ align-items: center;
185
+ height: var(--source-facet-height);
186
+ padding: 0 var(--space-sm);
187
+ border: 1.5px solid color-mix(in srgb, currentColor 40%, var(--border-ink));
188
+ border-radius: var(--radius-card);
189
+ background: transparent;
190
+ white-space: nowrap;
191
+ font-size: var(--type-label);
192
+ }
193
+
194
+ /* The two chips this page owns. The shared pill and chip bring their own
195
+ type; these match it rather than inventing a second label voice. */
196
+ .source-facet {
197
+ font-family: var(--font-label);
198
+ font-size: var(--type-label);
199
+ font-weight: var(--weight-label);
200
+ letter-spacing: 0.03em;
201
+ line-height: 1;
202
+ color: var(--color-on-surface-variant);
203
+ }
204
+
205
+ /* flag-icons sizes from the em, so the flag's own font-size is its height. */
206
+ .source-facet--country {
207
+ padding-inline: 0.35rem;
208
+ }
209
+
210
+ .source-facet--country .fi {
211
+ font-size: var(--type-dek);
212
+ }
213
+
214
+ /* The publisher, sitting opposite the call to action on the footer rule. */
215
+ .source-host {
216
+ color: var(--color-on-surface-variant);
217
+ text-transform: lowercase;
218
+ letter-spacing: 0.02em;
219
+ overflow: hidden;
220
+ text-overflow: ellipsis;
221
+ white-space: nowrap;
222
+ min-width: 0;
223
+ }
224
+
225
+ /* Filter swatches: match the exact medium accent colours used by cards
226
+ (--color-res-youtube for red, --color-res-podcast for green,
227
+ --color-res-doc for amber/brown, --color-primary for website blue). */
228
+ .source-swatch--youtube {
229
+ background: var(--color-res-youtube);
230
+ }
231
+
232
+ .source-swatch--podcast {
233
+ background: var(--color-res-podcast);
234
+ }
235
+
236
+ .source-swatch--document {
237
+ background: var(--color-res-doc);
238
+ }
239
+
240
+ .source-swatch--website {
241
+ background: var(--color-primary);
242
+ }
243
+
244
+ /* ── Pagination ── */
245
+
246
+ /* Real UI over a real index, not a registry counter: the page reads how many
247
+ pages the current filter produced, and nothing else. The buttons themselves
248
+ are `.pagination-nav-btn` from base.css, shared with the other two
249
+ listings; only the bar around them is local. */
250
+ .source-pagination {
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ gap: var(--space-lg);
255
+ margin-top: var(--space-2xl);
256
+ }
257
+
258
+ .source-pagination:empty {
259
+ margin-top: 0;
260
+ }
261
+
262
+ /* ── Medium colour ──
263
+ A source is recognised by what it is before it is read — a video, a
264
+ podcast, a site — and the palette already carries a colour per medium
265
+ (--color-res-*). The redesign had flattened all four onto the brand triad,
266
+ which cost the fastest signal on the page. The colour drives the medium
267
+ chip and an edge on the card, so it reads at a glance without turning the
268
+ grid into a set of logos. */
269
+
270
+ .source-card {
271
+ --source-accent: var(--color-tertiary);
272
+ border-left: 3px solid var(--source-accent);
273
+ background: var(--color-surface-card);
274
+ }
275
+
276
+ .source-card-media {
277
+ position: relative;
278
+ }
279
+
280
+ .source-card-media .ledger-frame-corners {
281
+ --frame-corner-size: 0.7rem;
282
+ inset: 0.3rem;
283
+ z-index: 2;
284
+ }
285
+
286
+ /* Align Sources posters with the Articles/detail plate dialect. */
287
+ .source-card .ledger-poster {
288
+ border-bottom: 1px solid color-mix(in srgb, var(--source-accent) 40%, transparent);
289
+ }
290
+
291
+ /* Light paper densifies every `.ledger-card` border to `--border-ink-strong`
292
+ (base.css). That `border-color` rewrite also paints the left edge and
293
+ erases the medium accent — pin the left colour back after the light bump. */
294
+ :root:not([data-theme="dark"]) .source-card,
295
+ html[data-theme="light"] .source-card {
296
+ border-left-color: var(--source-accent);
297
+ }
298
+
299
+ /* Transparent resting cards work on the home and writing grids; on Sources
300
+ the poster + facet chip stack needs a solid plate in dark so the shard and
301
+ canvas do not read through the thumbnail. Restore the pre-transparent
302
+ ladder card (#1a1a1e). Light keeps the shared transparent rest. */
303
+ [data-theme="dark"] .source-card {
304
+ background: #1a1a1e;
305
+ }
306
+
307
+ .source-card[data-medium="youtube"] {
308
+ --source-accent: var(--color-res-youtube);
309
+ }
310
+
311
+ .source-card[data-medium="podcast"] {
312
+ --source-accent: var(--color-res-podcast);
313
+ }
314
+
315
+ .source-card[data-medium="document"] {
316
+ --source-accent: var(--color-res-doc);
317
+ }
318
+
319
+ .source-card[data-medium="website"] {
320
+ --source-accent: var(--color-primary);
321
+ }
322
+
323
+ /* The medium chip and the card edge read the same custom property, so they
324
+ cannot drift apart: one colour, stated once, shown twice. */
325
+ .source-facet--medium {
326
+ color: var(--source-accent);
327
+ }