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,417 @@
1
+ ---
2
+ layout: shell
3
+ ---
4
+
5
+ <main class="relative max-w-7xl mx-auto px-4 sm:px-8 md:px-16 pt-10 pb-16">
6
+ <div class="bib-masthead">
7
+ <span class="shard-accent bib-masthead-shard" aria-hidden="true"></span>
8
+ {% include page/page-header.html key=page.key listing_filter=true spacer_class="mb-8"
9
+ header_class="bib-header mb-10 md:mb-12"
10
+ filter_label="Filter"
11
+ filter_placeholder="Search authors, titles, years, keys…" %}
12
+ </div>
13
+
14
+ <section class="scroll-mt-28 bib-body" id="bibliography">
15
+ <div class="listing-card-grid listing-card-grid--bibliography" id="bib-grid">
16
+ <p class="font-body text-on-surface-variant italic col-span-full">Loading bibliography...</p>
17
+ <section id="no-results" class="col-span-full py-16 hidden">
18
+ <p class="font-body text-on-surface-variant italic">Nothing matches these filters yet.</p>
19
+ </section>
20
+ </div>
21
+ <div id="bib-pagination" class="bib-pagination"></div>
22
+ <span class="shard-accent shard-accent-beta bib-foot-shard" aria-hidden="true"></span>
23
+ </section>
24
+ </main>
25
+
26
+ <script>
27
+ (function () {
28
+ 'use strict';
29
+
30
+ var runtime = window.ContentRuntime;
31
+ if (!runtime || !runtime.ListingFilter) return;
32
+
33
+ var listing = runtime.Listing;
34
+ var params = new URLSearchParams(window.location.search);
35
+ var grid = document.getElementById('bib-grid');
36
+ var noResults = document.getElementById('no-results');
37
+ var pagination = document.getElementById('bib-pagination');
38
+ var filterRoot = document.querySelector('[data-listing-filter-root]');
39
+ var pageSize = 18;
40
+ var currentPage = 1;
41
+ var allItems = [];
42
+ var allCards = [];
43
+ var filterUi = null;
44
+
45
+ var TYPE_LABELS = {
46
+ article: 'Article',
47
+ book: 'Book',
48
+ news: 'News',
49
+ web: 'Web',
50
+ techreport: 'Tech Report',
51
+ incollection: 'Chapter',
52
+ report: 'Report',
53
+ misc: 'Misc',
54
+ };
55
+
56
+ var TYPE_OPTIONS = [
57
+ { value: 'article', label: 'Academic Articles' },
58
+ { value: 'book', label: 'Books' },
59
+ { value: 'report', label: 'Reports & Data' },
60
+ { value: 'news', label: 'News & Magazines' },
61
+ { value: 'misc', label: 'Web & Misc' }
62
+ ];
63
+
64
+ if (!grid) return;
65
+
66
+ function typeLabel(type) {
67
+ return TYPE_LABELS[type] || type || 'Reference';
68
+ }
69
+
70
+ function detectLanguage(item) {
71
+ var f = (item && item.fields) || {};
72
+ if (f.language) return f.language;
73
+ if (f.lang) return f.lang;
74
+ var text = (f.title || '') + ' ' + (f.note || '');
75
+ text = text.toLowerCase();
76
+ var hasPt = /\b(não|violência|migração|casamento|pesquisa|sobre|tribunal|revista|cerca|impunidade|dados)\b/.test(text);
77
+ if (hasPt) return 'pt';
78
+ var hasFr = /\b(étude|français|société|revue|capoeira|d’un|d\'un)\b/.test(text);
79
+ if (hasFr) return 'fr';
80
+ return '';
81
+ }
82
+
83
+ function renderTypeFacet(type) {
84
+ var label = typeLabel(type);
85
+ return '<span class="bib-facet bib-facet--type bib-facet--' + runtime.escapeAttr(type) + '">' +
86
+ runtime.escapeHtml(label) + '</span>';
87
+ }
88
+
89
+ function renderYearFacet(year) {
90
+ if (!year) return '';
91
+ return '<span class="bib-facet bib-facet--year">' + runtime.escapeHtml(year) + '</span>';
92
+ }
93
+
94
+ function renderFacets(item) {
95
+ var f = item.fields || {};
96
+ var chips = [
97
+ renderTypeFacet(item.type),
98
+ renderYearFacet(f.year)
99
+ ];
100
+ var detectedLang = detectLanguage(item);
101
+ if (detectedLang) {
102
+ chips.push(runtime.renderLangPill(detectedLang, { allowUnknown: true }));
103
+ }
104
+ return '<div class="bib-facets">' + chips.join('') + '</div>';
105
+ }
106
+
107
+ function linkHost(url) {
108
+ var val = String(url || '').trim();
109
+ if (!val) return '';
110
+ return val.replace(/^https?:\/\//i, '').replace(/^www\./i, '').split(/[/?#]/)[0];
111
+ }
112
+
113
+ function renderCard(item) {
114
+ var card = document.createElement('article');
115
+ card.className = 'ledger-card bib-card group';
116
+ card.dataset.key = item.key;
117
+ card.dataset.type = item.type;
118
+ card.dataset.year = item.fields.year || '';
119
+
120
+ var f = item.fields || {};
121
+ var link = f.url || '';
122
+
123
+ var html = '<div class="ledger-card-body bib-card-body">';
124
+ html += '<div class="bib-card-top">';
125
+ html += renderFacets(item);
126
+ html += '<span class="bib-key code-technical" title="Citation key">[' + runtime.escapeHtml(item.key) + ']</span>';
127
+ html += '</div>';
128
+
129
+ var displayTitle = f.title || f.author || item.key;
130
+ html += '<h3 class="card-title ledger-card-title bib-card-title">';
131
+ if (link) {
132
+ html += '<a href="' + runtime.escapeAttr(link) + '" target="_blank" rel="noopener noreferrer">' +
133
+ runtime.escapeHtml(displayTitle) + '</a>';
134
+ } else {
135
+ html += runtime.escapeHtml(displayTitle);
136
+ }
137
+ html += '</h3>';
138
+
139
+ var authorLine = runtime.formatIeeeAuthors(runtime.bibField(item, 'author'));
140
+ if (authorLine) {
141
+ html += '<div class="bib-author font-body text-on-surface-variant">' + authorLine + '</div>';
142
+ }
143
+
144
+ if (link) {
145
+ html += '<div class="ledger-card-foot bib-card-foot">';
146
+ html += '<span class="bib-host code-technical">' + runtime.escapeHtml(linkHost(link)) + '</span>';
147
+ html += runtime.renderCtaLink(link, 'Open link');
148
+ html += '</div>';
149
+ }
150
+
151
+ html += '</div>';
152
+ card.innerHTML = html;
153
+
154
+ Array.prototype.forEach.call(card.querySelectorAll('a[href^="http"]'), function (anchor) {
155
+ anchor.target = '_blank';
156
+ anchor.rel = 'noopener noreferrer';
157
+ });
158
+
159
+ return card;
160
+ }
161
+
162
+ var yearFrom = null;
163
+ var yearTo = null;
164
+ var currentSort = 'random';
165
+
166
+ function buildChipGroups() {
167
+ return [
168
+ {
169
+ key: 'type',
170
+ label: 'Type',
171
+ options: [{ value: null, label: 'All types' }].concat(TYPE_OPTIONS)
172
+ },
173
+ {
174
+ key: 'sort',
175
+ label: 'Sort by',
176
+ options: [
177
+ { value: 'random', label: 'Random' },
178
+ { value: 'year', label: 'Year' },
179
+ { value: 'title', label: 'Title' },
180
+ { value: 'author', label: 'Author' }
181
+ ]
182
+ }
183
+ ];
184
+ }
185
+
186
+ function matchesYearRange(yearStr) {
187
+ if (yearFrom === null && yearTo === null) return true;
188
+ var y = parseInt(yearStr, 10);
189
+ if (!y || isNaN(y)) return false;
190
+ if (yearFrom !== null && y < yearFrom) return false;
191
+ if (yearTo !== null && y > yearTo) return false;
192
+ return true;
193
+ }
194
+
195
+ function sortKey(item, field) {
196
+ var f = item.fields || {};
197
+ if (field === 'title') return (f.title || '').toLowerCase();
198
+ if (field === 'author') return (f.author || '').toLowerCase();
199
+ // year descending by default
200
+ return '';
201
+ }
202
+
203
+ function visibleItems(state) {
204
+ var sortField = state.sort || currentSort || 'random';
205
+ function sortBibliographyItems(a, b, s) {
206
+ var sf = s || sortField;
207
+ if (sf === 'random') {
208
+ var ka = a.key || (a.fields && a.fields.title) || '';
209
+ var kb = b.key || (b.fields && b.fields.title) || '';
210
+ return runtime.sessionRandomCompare(ka, kb, 'bibliography');
211
+ }
212
+ if (sf === 'year') {
213
+ var ya = parseInt(a.fields.year, 10) || 0;
214
+ var yb = parseInt(b.fields.year, 10) || 0;
215
+ if (yb !== ya) return yb - ya;
216
+ return a.key.localeCompare(b.key);
217
+ }
218
+ var ka = sortKey(a, sf);
219
+ var kb = sortKey(b, sf);
220
+ if (ka !== kb) return ka.localeCompare(kb);
221
+ return a.key.localeCompare(b.key);
222
+ }
223
+
224
+ var filtered = runtime.ListingFilter.filterItems(allItems, state, {
225
+ sortItems: sortBibliographyItems
226
+ });
227
+
228
+ return filtered.filter(function (item) {
229
+ return matchesYearRange(item.fields.year);
230
+ });
231
+ }
232
+
233
+ function renderPagination(state, totalPages) {
234
+ if (!pagination) return;
235
+ pagination.innerHTML = '';
236
+ if (totalPages <= 1) return;
237
+ var prev = document.createElement('button');
238
+ prev.type = 'button';
239
+ prev.textContent = 'Previous';
240
+ prev.disabled = currentPage === 1;
241
+ prev.className = 'pagination-nav-btn';
242
+ prev.onclick = function () {
243
+ currentPage -= 1;
244
+ renderPage(state);
245
+ window.scrollTo({ top: grid.offsetTop - 120, behavior: 'smooth' });
246
+ };
247
+ var label = document.createElement('span');
248
+ label.className = 'pagination-count';
249
+ label.textContent = currentPage + ' / ' + totalPages;
250
+ var next = document.createElement('button');
251
+ next.type = 'button';
252
+ next.textContent = 'Next';
253
+ next.disabled = currentPage === totalPages;
254
+ next.className = prev.className;
255
+ next.onclick = function () {
256
+ currentPage += 1;
257
+ renderPage(state);
258
+ window.scrollTo({ top: grid.offsetTop - 120, behavior: 'smooth' });
259
+ };
260
+ pagination.append(prev, label, next);
261
+ }
262
+
263
+ function renderPage(state) {
264
+ currentSort = state.sort || currentSort || 'random';
265
+ allCards.forEach(function (card) { card.classList.add('bib-hidden'); });
266
+ var visible = visibleItems(state);
267
+ var cardsByKey = Object.create(null);
268
+ allCards.forEach(function (card) { cardsByKey[card.dataset.key] = card; });
269
+
270
+ var matchingCards = visible.map(function (item) {
271
+ return cardsByKey[item.key];
272
+ }).filter(Boolean);
273
+
274
+ if (noResults) noResults.classList.toggle('hidden', matchingCards.length !== 0);
275
+
276
+ var totalPages = Math.max(1, Math.ceil(matchingCards.length / pageSize));
277
+ if (currentPage > totalPages) currentPage = totalPages;
278
+ var pageCards = matchingCards.slice((currentPage - 1) * pageSize, currentPage * pageSize);
279
+ pageCards.forEach(function (card) {
280
+ grid.appendChild(card);
281
+ card.classList.remove('bib-hidden');
282
+ });
283
+
284
+ renderPagination(state, totalPages);
285
+ }
286
+
287
+ function buildYearRangeUi() {
288
+ var panel = filterRoot && filterRoot.querySelector('[data-listing-filter-chips]');
289
+ if (!panel) return;
290
+ var group = document.createElement('div');
291
+ group.className = 'listing-filter-group bib-year-range-group';
292
+ var label = document.createElement('p');
293
+ label.className = 'listing-filter-group-label font-label text-on-surface-variant';
294
+ label.textContent = 'Year range';
295
+ group.appendChild(label);
296
+ var row = document.createElement('div');
297
+ row.className = 'bib-year-range-row';
298
+
299
+ function makeInput(placeholder, initial) {
300
+ var inp = document.createElement('input');
301
+ inp.type = 'number';
302
+ inp.className = 'bib-year-input font-label';
303
+ inp.placeholder = placeholder;
304
+ inp.min = '1900';
305
+ inp.max = '2100';
306
+ inp.autocomplete = 'off';
307
+ if (initial) inp.value = initial;
308
+ return inp;
309
+ }
310
+
311
+ var fromInput = makeInput('From', params.get('from') || '');
312
+ var toInput = makeInput('To', params.get('to') || '');
313
+ var sep = document.createElement('span');
314
+ sep.className = 'bib-year-sep font-label text-on-surface-variant';
315
+ sep.textContent = '–';
316
+ row.appendChild(fromInput);
317
+ row.appendChild(sep);
318
+ row.appendChild(toInput);
319
+ group.appendChild(row);
320
+ panel.appendChild(group);
321
+
322
+ yearFrom = fromInput.value ? parseInt(fromInput.value, 10) : null;
323
+ yearTo = toInput.value ? parseInt(toInput.value, 10) : null;
324
+
325
+ function onYearChange() {
326
+ var f = fromInput.value.trim();
327
+ var t = toInput.value.trim();
328
+ yearFrom = f ? parseInt(f, 10) : null;
329
+ yearTo = t ? parseInt(t, 10) : null;
330
+ if (yearFrom !== null && isNaN(yearFrom)) yearFrom = null;
331
+ if (yearTo !== null && isNaN(yearTo)) yearTo = null;
332
+ currentPage = 1;
333
+ renderPage(filterUi.getState());
334
+ syncYearUrl();
335
+ }
336
+
337
+ var debounceTimer = null;
338
+ function debounced() {
339
+ if (debounceTimer) clearTimeout(debounceTimer);
340
+ debounceTimer = setTimeout(onYearChange, 400);
341
+ }
342
+ fromInput.addEventListener('input', debounced);
343
+ toInput.addEventListener('input', debounced);
344
+ }
345
+
346
+ function syncYearUrl() {
347
+ var p = new URLSearchParams(window.location.search);
348
+ p.delete('from');
349
+ p.delete('to');
350
+ if (yearFrom !== null) p.set('from', String(yearFrom));
351
+ if (yearTo !== null) p.set('to', String(yearTo));
352
+ var qs = p.toString();
353
+ var next = window.location.pathname + (qs ? '?' + qs : '');
354
+ var current = window.location.pathname + window.location.search;
355
+ if (next !== current) history.replaceState(null, '', next);
356
+ }
357
+
358
+ function initFilter() {
359
+ filterUi = runtime.ListingFilter.create({
360
+ root: filterRoot,
361
+ initialQuery: params.get('q') || '',
362
+ initialType: params.get('type') || null,
363
+ initialSort: params.get('sort') || 'random',
364
+ defaultSort: 'random',
365
+ chipGroups: buildChipGroups(),
366
+ onChange: function (state) {
367
+ currentPage = 1;
368
+ renderPage(state);
369
+ }
370
+ });
371
+
372
+ buildYearRangeUi();
373
+ if (filterRoot) filterRoot.hidden = false;
374
+ renderPage(filterUi.getState());
375
+ }
376
+
377
+ listing.showLoadingState(grid, 'Loading bibliography...');
378
+
379
+ runtime.ensureBibliographyIndex().then(function (data) {
380
+ var entries = [];
381
+ Object.keys(data || {}).forEach(function (k) {
382
+ var it = data[k];
383
+ var f = it.fields || {};
384
+ var parts = [k, it.type || ''];
385
+ Object.keys(f).forEach(function (fk) {
386
+ if (f[fk]) parts.push(String(f[fk]));
387
+ });
388
+ entries.push({
389
+ key: k,
390
+ type: it.type || 'misc',
391
+ fields: f,
392
+ haystack: parts.join(' ').toLowerCase()
393
+ });
394
+ });
395
+
396
+ // Sort by year descending, then key
397
+ entries.sort(function (a, b) {
398
+ var ya = parseInt(a.fields.year, 10) || 0;
399
+ var yb = parseInt(b.fields.year, 10) || 0;
400
+ if (yb !== ya) return yb - ya;
401
+ return a.key.localeCompare(b.key);
402
+ });
403
+
404
+ allItems = entries;
405
+ grid.innerHTML = '';
406
+ allCards = allItems.map(function (item) {
407
+ return renderCard(item);
408
+ });
409
+ allCards.forEach(function (card) { grid.appendChild(card); });
410
+ if (noResults) grid.appendChild(noResults);
411
+ initFilter();
412
+ }).catch(function (error) {
413
+ console.error(error);
414
+ listing.showErrorState(grid, 'Bibliography could not be loaded right now.');
415
+ });
416
+ })();
417
+ </script>