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,365 @@
1
+ {% raw %}
2
+ function plainTextFromMarkup(value) {
3
+ return String(value || '')
4
+ .replace(/<[^>]+>/g, ' ')
5
+ .replace(/&nbsp;/gi, ' ')
6
+ .replace(/&amp;/g, '&')
7
+ .replace(/&lt;/g, '<')
8
+ .replace(/&gt;/g, '>')
9
+ .replace(/&quot;/g, '"')
10
+ .replace(/&#39;/g, "'")
11
+ .replace(/\s+/g, ' ')
12
+ .trim();
13
+ }
14
+
15
+ function figureLightboxCaption(caption, source) {
16
+ var parts = [];
17
+ var cap = plainTextFromMarkup(ensureCaptionPeriod(String(caption || '').trim()));
18
+ if (cap) parts.push(cap);
19
+ var src = plainTextFromMarkup(String(source || '').trim());
20
+ if (src) parts.push('Source: ' + src);
21
+ return parts.join(' ');
22
+ }
23
+
24
+ function cutInHtml(attrs) {
25
+ if (!attrs.src) return '';
26
+ var src = resolveAssetUrl(attrs.src);
27
+ var sideClass = attrs.side === 'right' ? ' article-cut-in--right' : '';
28
+ return '<figure class="article-cut-in-plate' + sideClass + '">' +
29
+ '<img class="article-cut-in' + sideClass + '" src="' + escapeAttr(src) + '" alt="' + escapeAttr(attrs.alt || '') + '" width="120" loading="lazy">' +
30
+ '</figure>';
31
+ }
32
+
33
+ function figureHtml(attrs) {
34
+ var width = attrs.width ? 'max-width:' + escapeAttr(attrs.width) + '%;' : 'max-width:100%;';
35
+ var src = resolveAssetUrl(attrs.src);
36
+ var caption = attrs.caption || '';
37
+ var lbCaption = figureLightboxCaption(caption, attrs.source);
38
+ var lbAttr = lbCaption ? ' data-lightbox-caption="' + escapeAttr(lbCaption) + '"' : '';
39
+ return [
40
+ '<figure class="my-8 mx-auto text-center w-full" style="display:block;margin-left:auto;margin-right:auto;' + width + '">',
41
+ '<img src="' + escapeAttr(src) + '" alt="' + escapeAttr(attrs.alt || caption) + '" class="figure-lightbox-trigger mx-auto max-w-full h-auto block" loading="lazy" tabindex="0" role="button" aria-label="Enlarge image"' + lbAttr + '>',
42
+ caption || attrs.source ? '<figcaption class="mt-3 font-body">' + includeCaptionHtml(caption) + figureSourceHtml(attrs.source) + '</figcaption>' : '',
43
+ '</figure>'
44
+ ].join('');
45
+ }
46
+
47
+ function plotlyHtml(attrs) {
48
+ if (!attrs.src) return '';
49
+ var width = attrs.width ? String(attrs.width).trim() : '100';
50
+ var height = attrs.height ? String(attrs.height).trim() : '480';
51
+ if (!/^\d+$/.test(height)) height = '480';
52
+ var src = resolveAssetUrl(attrs.src);
53
+ var caption = attrs.caption || '';
54
+ var title = attrs.alt || caption || 'Interactive chart';
55
+ return [
56
+ '<figure class="plotly-figure my-8 mx-auto text-center w-full" style="display:block;margin-left:auto;margin-right:auto;max-width:' + escapeAttr(width) + '%;">',
57
+ '<iframe class="plotly-embed-frame" src="' + escapeAttr(src) + '" title="' + escapeAttr(title) + '" loading="lazy" scrolling="no" style="width:100%;height:' + escapeAttr(height) + 'px;border:0;display:block;border-radius:0;background:transparent;"></iframe>',
58
+ caption || attrs.source ? '<figcaption class="mt-3 font-body">' + includeCaptionHtml(caption) + figureSourceHtml(attrs.source) + '</figcaption>' : '',
59
+ '</figure>'
60
+ ].join('');
61
+ }
62
+
63
+ function youtubeHtml(attrs, isShort) {
64
+ var id = attrs.id || attrs.youtube_id || (isShort ? attrs.src : '');
65
+ if (!id) return '';
66
+ var caption = attrs.caption || '';
67
+ if (isShort) {
68
+ return '<figure class="article-youtube-short-wrap my-8 mx-auto text-center w-full" style="margin:1.5rem auto;max-width:45%;">' +
69
+ '<div class="article-youtube-short" style="display:flex;flex-wrap:wrap;justify-content:center;gap:1rem;">' +
70
+ youtubeShortEmbed(id, false) +
71
+ '</div>' +
72
+ (caption ? '<figcaption class="mt-3 font-body">' + includeCaptionHtml(caption) + '</figcaption>' : '') +
73
+ '</figure>';
74
+ }
75
+ return '<figure class="article-youtube-landscape my-8 mx-auto text-center w-full" style="margin:1.5rem auto;max-width:100%;">' +
76
+ '<div style="aspect-ratio:16 / 9;">' +
77
+ '<iframe src="https://www.youtube.com/embed/' + escapeAttr(id) + '" title="YouTube video" loading="lazy" allowfullscreen class="w-full h-full" style="width:100%;height:100%;border:0;"></iframe>' +
78
+ '</div>' +
79
+ (caption ? '<figcaption class="mt-3 font-body">' + includeCaptionHtml(caption) + '</figcaption>' : '') +
80
+ '</figure>';
81
+ }
82
+
83
+ function youtubeShortEmbed(id, paired) {
84
+ var width = paired ? '45%' : '100%';
85
+ return '<iframe width="315" height="560" src="https://www.youtube.com/embed/' + escapeAttr(id) + '" title="YouTube short" loading="lazy" allowfullscreen style="width:' + width + ';max-width:' + (paired ? '45%' : '100%') + ';aspect-ratio:9 / 16;height:auto;border-radius:0.25rem;"></iframe>';
86
+ }
87
+
88
+ function youtubeShortHtml(attrs) {
89
+ var src = attrs.src || attrs.id || attrs.id1;
90
+ var src2 = attrs.src2 || attrs.id2;
91
+ if (!src) return '';
92
+ var paired = !!src2;
93
+ return '<figure class="article-youtube-short-wrap' + (paired ? ' article-youtube-short-wrap--pair' : '') + ' my-8 mx-auto text-center w-full" style="margin:1.5rem auto;max-width:' + (paired ? '100%' : '45%') + ';">' +
94
+ '<div class="article-youtube-short' + (paired ? ' article-youtube-short--pair' : '') + '" style="display:flex;flex-wrap:wrap;justify-content:center;gap:1rem;">' +
95
+ youtubeShortEmbed(src, paired) +
96
+ (src2 ? youtubeShortEmbed(src2, paired) : '') +
97
+ '</div>' +
98
+ '</figure>';
99
+ }
100
+
101
+ function formatDefHtml(def) {
102
+ return escapeHtml(String(def || '').trim()).replace(/\r\n|\r|\n/g, '<br>');
103
+ }
104
+
105
+ function dictCardHtml(attrs) {
106
+ var lemma = String(attrs.lemma || '').trim();
107
+ if (!lemma) return '';
108
+ var locale = String(attrs.locale || '').trim().toLowerCase();
109
+ var meta = String(attrs.meta || '').trim();
110
+ var label = String(attrs.country || meta || locale).trim();
111
+ var note = String(attrs.note || '').trim();
112
+ var source = String(attrs.source || '').trim();
113
+ var url = String(attrs.url || '').trim();
114
+ var accessed = String(attrs.accessed || '').trim();
115
+ var citeExtra = String(attrs.cite_extra || '').trim();
116
+ var linkLabel = String(attrs.link_label || url).trim();
117
+ var parts = [
118
+ '<article class="sov-card' + (locale ? ' sov-card--' + escapeAttr(locale) : '') + '" role="listitem">',
119
+ '<div class="sov-card__head">'
120
+ ];
121
+ if (locale) {
122
+ parts.push(
123
+ '<span class="sov-card__flag" title="' + escapeAttr(label) + '">' +
124
+ '<span class="fi fi-' + escapeAttr(locale) + '" role="img" aria-label="' + escapeAttr(label) + '"></span>' +
125
+ '</span>'
126
+ );
127
+ }
128
+ if (meta) parts.push('<span class="sov-card__meta">' + escapeHtml(meta) + '</span>');
129
+ parts.push('</div>');
130
+ parts.push('<p class="sov-card__lemma">' + escapeHtml(lemma) + '</p>');
131
+ if (attrs.def) parts.push('<div class="sov-card__def">' + formatDefHtml(attrs.def) + '</div>');
132
+ if (note) parts.push('<p class="sov-card__note">' + note + '</p>');
133
+ if (source) {
134
+ var cite = escapeHtml(source) + ', s.v. &ldquo;' + escapeHtml(lemma) + ',&rdquo;';
135
+ if (url) cite += ' <a href="' + escapeAttr(url) + '">' + escapeHtml(linkLabel) + '</a>';
136
+ if (accessed) cite += ', accessed ' + escapeHtml(accessed);
137
+ cite += '.';
138
+ if (citeExtra) cite += ' ' + escapeHtml(citeExtra);
139
+ parts.push('<p class="sov-card__cite">' + cite + '</p>');
140
+ }
141
+ parts.push('</article>');
142
+ return parts.join('');
143
+ }
144
+
145
+ function tweetHtml(attrs) {
146
+ var body = String(attrs.body || '').trim();
147
+ if (!body) return '';
148
+ var handle = String(attrs.handle || '').trim().replace(/^@/, '');
149
+ var icon = String(attrs.icon || 'chat').trim() || 'chat';
150
+ var url = String(attrs.url || '').trim();
151
+ var date = String(attrs.date || '').trim();
152
+ var note = String(attrs.note || '').trim();
153
+ var name = String(attrs.name || '').trim();
154
+ var linkLabel = url.replace(/^https?:\/\//, '');
155
+ var parts = [
156
+ '<article class="sov-tweet article-tweet">',
157
+ '<div class="sov-tweet__head article-tweet__head">',
158
+ '<span class="sov-tweet__avatar article-tweet__avatar" aria-hidden="true"><span class="material-symbols-outlined">' + escapeHtml(icon) + '</span></span>',
159
+ '<span class="sov-tweet__id article-tweet__id">'
160
+ ];
161
+ if (name) parts.push('<span class="sov-tweet__name article-tweet__name">' + escapeHtml(name) + '</span>');
162
+ if (handle) parts.push('<span class="sov-tweet__handle article-tweet__handle">@' + escapeHtml(handle) + '</span>');
163
+ parts.push('</span></div>');
164
+ parts.push('<p class="sov-tweet__body article-tweet__body">' + body + '</p>');
165
+ if (url || date) {
166
+ var cite = '';
167
+ if (url) cite += '<a href="' + escapeAttr(url) + '">' + escapeHtml(linkLabel) + '</a>';
168
+ if (url && date) cite += ' · ';
169
+ if (date) cite += escapeHtml(date);
170
+ parts.push('<p class="sov-tweet__cite article-tweet__cite">' + cite + '</p>');
171
+ }
172
+ if (note) parts.push('<p class="sov-tweet__note article-tweet__note">' + note + '</p>');
173
+ parts.push('</article>');
174
+ return parts.join('');
175
+ }
176
+
177
+ function wrapDictCardGrid(html) {
178
+ return String(html || '').replace(
179
+ /(?:<article\b[^>]*\bsov-card\b[\s\S]*?<\/article>\s*){2,}/g,
180
+ function (block) {
181
+ if (/\bsov-grid\b/.test(block)) return block;
182
+ return '<div class="sov-grid" role="list">' + block.trim() + '</div>\n';
183
+ }
184
+ );
185
+ }
186
+
187
+ function videoHtml(attrs) {
188
+ if (!attrs.src) return '';
189
+ var caption = attrs.caption || '';
190
+ return '<figure class="article-video-landscape my-8 mx-auto text-center" style="display:block;margin-left:auto;margin-right:auto;max-width:100%;">' +
191
+ '<video controls src="' + escapeAttr(buildUrl(attrs.src)) + '" class="mx-auto max-w-full" style="width:100%;"></video>' +
192
+ (caption ? '<figcaption class="mt-3 font-body">' + includeCaptionHtml(caption) + '</figcaption>' : '') +
193
+ '</figure>';
194
+ }
195
+
196
+ function placeholderHtml() {
197
+ return '<div class="w-full h-full flex items-center justify-center opacity-30"><span class="material-symbols-outlined text-5xl">article</span></div>';
198
+ }
199
+
200
+ function normalizeDataPath(path, fallbackPrefix) {
201
+ var value = String(path || '').trim();
202
+ if (!value) return '';
203
+ value = value.replace(/^content\/collections\/data\//, 'data/');
204
+ value = value.replace(/^_data\//, 'data/');
205
+ value = value.replace(/\.(ya?ml)$/, '.json');
206
+ if (value.indexOf('/') === -1 && fallbackPrefix) {
207
+ value = fallbackPrefix + '/' + value + '.json';
208
+ }
209
+ return value;
210
+ }
211
+
212
+ function preprocessTranscriptionAssigns(markdown) {
213
+ var assignMap = {};
214
+ var text = String(markdown || '').replace(
215
+ /\{%-?\s*assign\s+(\w+)\s*=\s*site\.data\.transcriptions\[([^\]]+)\]\.transcription\s*-?%\}\s*/g,
216
+ function (_, varName, keyExpr) {
217
+ keyExpr = String(keyExpr || '').trim();
218
+ if ((keyExpr.charAt(0) === "'" && keyExpr.charAt(keyExpr.length - 1) === "'") ||
219
+ (keyExpr.charAt(0) === '"' && keyExpr.charAt(keyExpr.length - 1) === '"')) {
220
+ assignMap[varName] = keyExpr.slice(1, -1);
221
+ } else if (keyExpr === 'page.transcription') {
222
+ assignMap[varName] = null;
223
+ } else {
224
+ assignMap[varName] = keyExpr;
225
+ }
226
+ return '';
227
+ }
228
+ );
229
+ return { text: text, assignMap: assignMap };
230
+ }
231
+
232
+ function resolveTranscriptionPath(attrs, fm, assignMap) {
233
+ var path = normalizeDataPath(attrs.path || attrs.file || attrs.src, '');
234
+ if (path) return path;
235
+
236
+ var transcriptionVar = attrs.transcription;
237
+ if (transcriptionVar && Object.prototype.hasOwnProperty.call(assignMap, transcriptionVar)) {
238
+ var assignedKey = assignMap[transcriptionVar];
239
+ if (assignedKey) {
240
+ return normalizeDataPath(assignedKey, 'data/transcriptions');
241
+ }
242
+ if (fm.transcription) {
243
+ return normalizeDataPath(fm.transcription, 'data/transcriptions');
244
+ }
245
+ }
246
+
247
+ if (fm.transcription) {
248
+ return normalizeDataPath(fm.transcription, 'data/transcriptions');
249
+ }
250
+
251
+ return '';
252
+ }
253
+
254
+ function transcriptionSpeakerLabel(name) {
255
+ return String(name || '')
256
+ .trim()
257
+ .toLowerCase()
258
+ .replace(/(^|\s)\S/g, function (ch) { return ch.toUpperCase(); });
259
+ }
260
+
261
+ function transcriptionHtml(data, attrs) {
262
+ var rows = Array.isArray(data) ? data : (data && data.transcription) || [];
263
+ var speakers = [];
264
+ rows.forEach(function (line) {
265
+ var character = String(line.character || '').trim();
266
+ if (character && speakers.indexOf(character) === -1) speakers.push(character);
267
+ });
268
+ var colors = ['primary', 'secondary', 'tertiary', 'outline'];
269
+ var header = attrs.youtube_id ? youtubeHtml({ id: attrs.youtube_id }, false) : '';
270
+ return [
271
+ '<div class="mt-16 mb-8">',
272
+ '<hr class="border-t border-gentle-divider mb-6">',
273
+ header,
274
+ '<span class="font-label text-xs text-on-surface-variant">Transcrição</span>',
275
+ '</div>',
276
+ '<div class="transcription-block space-y-4">',
277
+ rows.map(function (line) {
278
+ var character = String(line.character || '').trim();
279
+ var side = '';
280
+ if (line.timestamp) {
281
+ side = '<span class="font-mono text-xs text-on-surface-variant whitespace-nowrap" style="flex-basis:15%; flex-shrink:0; flex-grow:0; max-width:15%; text-align:left; align-self:flex-start;">[' + escapeHtml(line.timestamp) + ']</span>';
282
+ } else if (line.scene) {
283
+ side = '<span class="font-mono text-xs text-on-surface-variant whitespace-nowrap align-top" style="flex-basis:15%; flex-shrink:0; flex-grow:0; max-width:15%; writing-mode:vertical-lr; text-align:left; align-self:flex-start;">' + escapeHtml(line.scene) + '</span>';
284
+ } else {
285
+ side = '<span class="font-mono text-xs text-on-surface-variant opacity-0 whitespace-nowrap align-top" style="flex-basis:15%; flex-shrink:0; flex-grow:0; max-width:15%;">&nbsp;</span>';
286
+ }
287
+ if (!character) {
288
+ return '<div class="flex items-start gap-0 w-full" style="justify-content:flex-start;">' + side + '<span class="font-body text-on-surface italic opacity-60" style="flex-basis:85%; flex-shrink:1; flex-grow:1; max-width:85%; min-width:0;">' + escapeHtml(line.content || '') + '</span></div>';
289
+ }
290
+ var color = colors[speakers.indexOf(character)] || 'outline';
291
+ return '<div class="flex items-start gap-0 w-full" style="justify-content:flex-start;">' + side + '<span class="transcription-speaker text-' + color + '">' + escapeHtml(transcriptionSpeakerLabel(character)) + '</span><span class="font-body text-on-surface" style="flex-basis:65%; flex-shrink:1; flex-grow:1; max-width:65%; min-width:0;">' + escapeHtml(line.content || '') + '</span></div>';
292
+ }).join(''),
293
+ '</div>'
294
+ ].join('');
295
+ }
296
+
297
+ function themeFont() {
298
+ var value = getComputedStyle(document.documentElement).getPropertyValue('--font-sans').trim();
299
+ return value || "'Lato', sans-serif";
300
+ }
301
+
302
+ function themeColor(name, fallback) {
303
+ var value = getComputedStyle(document.documentElement).getPropertyValue('--color-' + name).trim();
304
+ return value || fallback;
305
+ }
306
+
307
+ function ongoingProductHtml() {
308
+ return '<span class="ongoing-product-badge" style="display: inline-block; padding: 0.15rem 0.5rem; background: var(--color-surface-container-low); border-radius: 0.25rem; font-size: 0.8rem; color: var(--color-on-surface-variant);">Currently active</span>';
309
+ }
310
+
311
+ function chordTabHtml(data) {
312
+ var sections = (data && data.sections) || [];
313
+ return [
314
+ '<div class="chord-tab-container mt-16 mb-8" style="width:100%; box-sizing:border-box; padding-left:0; padding-right:0;">',
315
+ '<h2>Cifra</h2>',
316
+ data && data.intro ? '<div class="mt-3 mb-6"><pre class="font-mono text-tertiary text-[0.8em] leading-relaxed whitespace-pre m-0 p-0 bg-transparent border-0">' + escapeHtml(data.intro) + '</pre></div>' : '',
317
+ "<div class=\"mt-4 font-mono text-[0.8em]\" style=\"display:grid; grid-template-columns:repeat(auto-fill, min(79ch, 100%)); gap:0 0.5rem; width:100%;\">",
318
+ sections.map(function (section) {
319
+ return '<div style="overflow-y:auto;">' +
320
+ (section.label ? '<div class="font-body text-[0.9em] uppercase tracking-widest text-tertiary mb-[0.35rem]">' + escapeHtml(section.label) + '</div>' : '') +
321
+ '<pre class="m-0 p-0 bg-transparent border-0 whitespace-pre overflow-x-auto leading-[1.55] text-on-surface">' + escapeHtml(section.content || '') + '</pre>' +
322
+ '<hr class="border-0 border-t border-gentle-divider my-[1.55em]">' +
323
+ '</div>';
324
+ }).join(''),
325
+ '</div>',
326
+ '</div>'
327
+ ].join('');
328
+ }
329
+
330
+ function preprocessLiquid(markdown, xciteIndex) {
331
+ var text = stripFrontMatter(markdown)
332
+ .replace(/\{%-?\s*include\s+cut-in\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
333
+ return cutInHtml(parseIncludeAttrs(raw));
334
+ })
335
+ .replace(/\{%-?\s*include\s+figure\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
336
+ return figureHtml(parseIncludeAttrs(raw));
337
+ })
338
+ .replace(/\{%-?\s*include\s+plotly\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
339
+ return plotlyHtml(parseIncludeAttrs(raw));
340
+ })
341
+ .replace(/\{%-?\s*include\s+youtube-short\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
342
+ return youtubeShortHtml(parseIncludeAttrs(raw));
343
+ })
344
+ .replace(/\{%-?\s*include\s+youtube\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
345
+ return youtubeHtml(parseIncludeAttrs(raw), false);
346
+ })
347
+ .replace(/\{%-?\s*include\s+video\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
348
+ return videoHtml(parseIncludeAttrs(raw));
349
+ })
350
+ .replace(/\{%-?\s*include\s+dict-card\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
351
+ return dictCardHtml(parseIncludeAttrs(raw));
352
+ })
353
+ .replace(/\{%-?\s*include\s+tweet\.html\s+([\s\S]*?)-?%\}/g, function (_, raw) {
354
+ return tweetHtml(parseIncludeAttrs(raw));
355
+ })
356
+ .replace(/\{%-?\s*include\s+ongoing-product\.html\s*-?%\}/g, function () {
357
+ return ongoingProductHtml();
358
+ })
359
+ .replace(/\{%-?\s*include\s+xcite\.html\s+key=["']([^"']+)["'][\s\S]*?-?%\}/g, function (_, key) {
360
+ return xciteIndex ? xciteHtml(key, xciteIndex) : '%%CONTENT_RUNTIME_XCITE_' + key + '%%';
361
+ });
362
+ return wrapDictCardGrid(text);
363
+ }
364
+
365
+ {% endraw %}
@@ -0,0 +1,225 @@
1
+ {% raw %}
2
+ function extractExcerptText(root) {
3
+ var parts = [];
4
+ var skipTags = { script: true, style: true, iframe: true, svg: true, hr: true, noscript: true, video: true };
5
+ var blockTags = { p: true, li: true, blockquote: true, tr: true, pre: true, td: true, th: true, dd: true, dt: true };
6
+
7
+ function lastChar() {
8
+ return parts.length ? parts[parts.length - 1].slice(-1) : '';
9
+ }
10
+
11
+ function ensureSpace() {
12
+ if (parts.length && lastChar() && !/\s/.test(lastChar())) {
13
+ parts.push(' ');
14
+ }
15
+ }
16
+
17
+ function append(text) {
18
+ if (text) parts.push(text);
19
+ }
20
+
21
+ function hasClass(node, name) {
22
+ return node.classList && node.classList.contains(name);
23
+ }
24
+
25
+ function visit(node) {
26
+ if (!node) return;
27
+ if (node.nodeType === Node.TEXT_NODE) {
28
+ append(node.textContent);
29
+ return;
30
+ }
31
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
32
+
33
+ var tag = node.tagName.toLowerCase();
34
+
35
+ if (skipTags[tag]) return;
36
+
37
+ if (tag === 'div' && hasClass(node, 'article-references')) return;
38
+
39
+ if (tag === 'br') {
40
+ ensureSpace();
41
+ return;
42
+ }
43
+
44
+ if (tag === 'img') {
45
+ var alt = (node.getAttribute('alt') || '').trim();
46
+ if (alt) {
47
+ ensureSpace();
48
+ append(alt);
49
+ ensureSpace();
50
+ }
51
+ return;
52
+ }
53
+
54
+ if (tag === 'pre') {
55
+ ensureSpace();
56
+ var codeText = (node.textContent || '').replace(/\s+/g, ' ').trim();
57
+ if (codeText) append(codeText);
58
+ ensureSpace();
59
+ return;
60
+ }
61
+
62
+ if (tag === 'figcaption') {
63
+ ensureSpace();
64
+ Array.prototype.forEach.call(node.childNodes, visit);
65
+ ensureSpace();
66
+ return;
67
+ }
68
+
69
+ if (/^h[1-6]$/.test(tag)) {
70
+ ensureSpace();
71
+ var heading = (node.textContent || '').trim();
72
+ if (heading) {
73
+ append(heading.replace(/:+\s*$/, '') + ': ');
74
+ }
75
+ return;
76
+ }
77
+
78
+ var isBlock = !!blockTags[tag];
79
+
80
+ if (isBlock) ensureSpace();
81
+
82
+ Array.prototype.forEach.call(node.childNodes, visit);
83
+
84
+ if (isBlock) ensureSpace();
85
+ }
86
+
87
+ visit(root);
88
+ return parts.join('')
89
+ .replace(/%%CONTENT_RUNTIME_XCITE_[^%]+%%/g, '')
90
+ .replace(/\$\$[\s\S]*?\$\$/g, ' ')
91
+ .replace(/\$[^$\n]+\$/g, ' ')
92
+ .replace(/\\\[[\s\S]*?\\\]/g, ' ')
93
+ .replace(/\\\([\s\S]*?\\\)/g, ' ')
94
+ .replace(/\{%-?[\s\S]*?-?%\}/g, ' ')
95
+ .replace(/\s+/g, ' ')
96
+ .trim();
97
+ }
98
+
99
+ function textExcerpt(markdown) {
100
+ var html = renderMarkdown(markdown);
101
+ var el = document.createElement('div');
102
+ el.innerHTML = html;
103
+ var text = extractExcerptText(el);
104
+ return text.length > 220 ? text.slice(0, 217).trim() + '...' : text;
105
+ }
106
+
107
+ function listingPreview(item) {
108
+ var fm = (item && item.front_matter) || {};
109
+ var editorial = String(fm.description || '').trim();
110
+ if (editorial) return editorial;
111
+ var excerpt = String(fm.excerpt || '').trim();
112
+ if (excerpt) return excerpt;
113
+ return textExcerpt((item && item.body_markdown) || '');
114
+ }
115
+
116
+ function formatDate(value, locale) {
117
+ if (!value) return '';
118
+ var parts = String(value).slice(0, 10).split('-').map(Number);
119
+ if (parts.length !== 3 || parts.some(isNaN)) return value;
120
+ return new Intl.DateTimeFormat(locale || 'en', {
121
+ day: '2-digit',
122
+ month: 'short',
123
+ year: 'numeric'
124
+ }).format(new Date(parts[0], parts[1] - 1, parts[2]));
125
+ }
126
+
127
+ function chronologySortKey(item) {
128
+ var fm = item.front_matter || {};
129
+ var primary = item.last_updated || fm.last_updated || item.date || fm.date || '';
130
+ var tiebreaker = item.date || fm.date || item.slug || '';
131
+ return String(primary) + '\0' + String(tiebreaker);
132
+ }
133
+
134
+ function getSessionRandomSeed(namespace) {
135
+ var key = 'listing-random-seed-' + (namespace || 'global');
136
+ var seed = null;
137
+ try {
138
+ seed = window.sessionStorage.getItem(key);
139
+ if (!seed) {
140
+ seed = String(Math.random()) + '-' + Date.now();
141
+ window.sessionStorage.setItem(key, seed);
142
+ }
143
+ } catch (e) {
144
+ seed = 'static-seed';
145
+ }
146
+ return seed;
147
+ }
148
+
149
+ function hashString(str) {
150
+ var h = 2166136261 >>> 0;
151
+ for (var i = 0; i < str.length; i++) {
152
+ h ^= str.charCodeAt(i);
153
+ h = Math.imul(h, 16777619);
154
+ }
155
+ h += h << 13;
156
+ h ^= h >>> 7;
157
+ h += h << 3;
158
+ h ^= h >>> 17;
159
+ h += h << 5;
160
+ return h >>> 0;
161
+ }
162
+
163
+ function sessionRandomCompare(aKey, bKey, namespace) {
164
+ var seed = getSessionRandomSeed(namespace);
165
+ var ha = hashString(seed + ':' + aKey);
166
+ var hb = hashString(seed + ':' + bKey);
167
+ if (ha !== hb) return ha < hb ? -1 : 1;
168
+ return String(aKey).localeCompare(String(bKey));
169
+ }
170
+
171
+ function dateKey(value) {
172
+ if (value == null || value === '') return '';
173
+ return String(value).slice(0, 10);
174
+ }
175
+
176
+ function renderDateEvolution(date, lastUpdated, locale, options) {
177
+ options = options || {};
178
+ var created = formatDate(date, locale);
179
+ var updated = formatDate(lastUpdated, locale);
180
+ var hasUpdate = dateKey(lastUpdated) && dateKey(lastUpdated) !== dateKey(date);
181
+ if (!created && !(hasUpdate && updated)) return '';
182
+ var align = options.align === 'right' ? ' items-end' : '';
183
+ var gap = options.gap || 'gap-1';
184
+ var metaClass = options.metaClass ? ' ' + options.metaClass : '';
185
+ var nowrap = options.nowrap === false ? '' : ' whitespace-nowrap';
186
+ var publishedTitle = options.publishedLabel || 'Published';
187
+ var updatedTitle = options.updatedLabel || 'Updated';
188
+ var html = '<div class="date-evolution flex flex-col ' + gap + align + metaClass + '">';
189
+ if (created) {
190
+ html += '<span class="' + nowrap.trim() + '"' + (options.showLabels ? '' : ' title="' + escapeAttr(publishedTitle) + '"') + '>';
191
+ if (options.showLabels) html += escapeHtml(publishedTitle) + ': ';
192
+ html += escapeHtml(created) + '</span>';
193
+ }
194
+ if (hasUpdate && updated) {
195
+ html += '<span class="opacity-60' + nowrap + '"' + (options.showLabels ? '' : ' title="' + escapeAttr(updatedTitle) + '"') + '>';
196
+ if (options.showLabels) html += escapeHtml(updatedTitle) + ': ';
197
+ html += escapeHtml(updated) + '</span>';
198
+ }
199
+ html += '</div>';
200
+ return html;
201
+ }
202
+
203
+ function renderCardMetaBlock(category) {
204
+ if (!category) return '';
205
+ return [
206
+ '<div class="card-meta-block">',
207
+ '<span class="card-meta px-2 py-0.5 border border-gentle text-tertiary">',
208
+ escapeHtml(category),
209
+ '</span></div>'
210
+ ].join('');
211
+ }
212
+
213
+ function renderCardFooterDates(date, lastUpdated, locale, options) {
214
+ options = options || {};
215
+ return renderDateEvolution(date, lastUpdated, locale, {
216
+ align: 'right',
217
+ gap: options.gap || 'gap-0.5',
218
+ publishedLabel: options.publishedLabel,
219
+ updatedLabel: options.updatedLabel,
220
+ showLabels: options.showLabels,
221
+ metaClass: 'card-meta text-on-surface-variant'
222
+ });
223
+ }
224
+
225
+ {% endraw %}
@@ -0,0 +1,71 @@
1
+ {% raw %}
2
+
3
+ var Listing = {
4
+ setActive: function (el) {
5
+ if (!el) return;
6
+ el.classList.add('text-secondary');
7
+ el.classList.remove('text-on-surface-variant');
8
+ },
9
+ clearActive: function (el) {
10
+ if (!el) return;
11
+ el.classList.remove('text-secondary');
12
+ el.classList.add('text-on-surface-variant');
13
+ },
14
+ buildFilterUrl: function (updates) {
15
+ var p = new URLSearchParams(window.location.search);
16
+ Object.keys(updates || {}).forEach(function (key) {
17
+ var val = updates[key];
18
+ if (val == null || val === '') p.delete(key);
19
+ else p.set(key, val);
20
+ });
21
+ p.delete('page');
22
+ var qs = p.toString();
23
+ return window.location.pathname + (qs ? '?' + qs : '');
24
+ },
25
+ pageUrl: function (pageNumber) {
26
+ var p = new URLSearchParams(window.location.search);
27
+ p.set('page', pageNumber);
28
+ return window.location.pathname + '?' + p.toString();
29
+ },
30
+ renderPagination: function (controls, currentPage, totalPages, labels) {
31
+ labels = labels || {};
32
+ if (!controls) return;
33
+ controls.innerHTML = '';
34
+ if (totalPages <= 1) {
35
+ controls.classList.add('hidden');
36
+ return;
37
+ }
38
+ controls.classList.remove('hidden');
39
+ var prevLabel = labels.prev || 'Previous';
40
+ var nextLabel = labels.next || 'Next';
41
+ // Styled by .pagination-nav-btn in base.css. Handing out utilities
42
+ // here forced every page sheet to out-specify them.
43
+ var btnClass = 'pagination-nav-btn';
44
+ var prevBtn = document.createElement('button');
45
+ prevBtn.textContent = prevLabel;
46
+ prevBtn.disabled = currentPage === 1;
47
+ prevBtn.className = btnClass;
48
+ if (currentPage > 1) prevBtn.addEventListener('click', function () { window.location.href = Listing.pageUrl(currentPage - 1); });
49
+ controls.appendChild(prevBtn);
50
+ var indicator = document.createElement('span');
51
+ indicator.className = 'pagination-count';
52
+ indicator.textContent = currentPage + ' / ' + totalPages;
53
+ controls.appendChild(indicator);
54
+ var nextBtn = document.createElement('button');
55
+ nextBtn.textContent = nextLabel;
56
+ nextBtn.disabled = currentPage === totalPages;
57
+ nextBtn.className = btnClass;
58
+ if (currentPage < totalPages) nextBtn.addEventListener('click', function () { window.location.href = Listing.pageUrl(currentPage + 1); });
59
+ controls.appendChild(nextBtn);
60
+ },
61
+ showLoadingState: function (element, message) {
62
+ if (!element) return;
63
+ element.innerHTML = '<p class="font-body text-on-surface-variant italic col-span-full">' + escapeHtml(message) + '</p>';
64
+ },
65
+ showErrorState: function (element, message) {
66
+ if (!element) return;
67
+ element.innerHTML = '<p class="font-body text-on-surface-variant italic col-span-full">' + escapeHtml(message) + '</p>';
68
+ }
69
+ };
70
+
71
+ {% endraw %}