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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +77 -0
- data/DESIGN.md +1434 -0
- data/LICENSE +21 -0
- data/README.md +81 -0
- data/_data/themes/dark.yml +97 -0
- data/_data/themes/light.yml +120 -0
- data/_data/themes/shared.yml +37 -0
- data/_data/translations.yml +85 -0
- data/_includes/chord-tab.html +38 -0
- data/_includes/content-runtime/00-config.html +22 -0
- data/_includes/content-runtime/10-utils.html +176 -0
- data/_includes/content-runtime/20-markdown.html +264 -0
- data/_includes/content-runtime/25-code-blocks.html +315 -0
- data/_includes/content-runtime/30-includes-bib.html +190 -0
- data/_includes/content-runtime/30-includes-xcite.html +122 -0
- data/_includes/content-runtime/30-includes.html +365 -0
- data/_includes/content-runtime/40-listing.html +225 -0
- data/_includes/content-runtime/41-listing-helpers.html +71 -0
- data/_includes/content-runtime/42-listing-filter.html +208 -0
- data/_includes/content-runtime/43-blocks.html +379 -0
- data/_includes/content-runtime/50-detail.html +861 -0
- data/_includes/content-runtime/55-toc.html +176 -0
- data/_includes/content-runtime/60-fragments.html +567 -0
- data/_includes/content-runtime/90-bootstrap.html +76 -0
- data/_includes/cut-in.html +10 -0
- data/_includes/cv/cv-box.html +18 -0
- data/_includes/dict-card.html +24 -0
- data/_includes/figure.html +36 -0
- data/_includes/home/band-index.html +43 -0
- data/_includes/home/band-listing.html +49 -0
- data/_includes/mathjax.html +10 -0
- data/_includes/ongoing-product.html +1 -0
- data/_includes/page/content-runtime.html +20 -0
- data/_includes/page/date_evolution.html +19 -0
- data/_includes/page/figure-lightbox.html +102 -0
- data/_includes/page/formatted_date.html +15 -0
- data/_includes/page/hero-default.html +18 -0
- data/_includes/page/listing-runtime-attrs.html +8 -0
- data/_includes/page/mermaid.html +101 -0
- data/_includes/page/network-graph.html +1253 -0
- data/_includes/page/page-header.html +41 -0
- data/_includes/page/professional-connections.html +2 -0
- data/_includes/page/site-footer.html +101 -0
- data/_includes/page/timeline-section.html +868 -0
- data/_includes/plotly.html +22 -0
- data/_includes/theme/dev-reexport.html +100 -0
- data/_includes/theme/favicons.html +4 -0
- data/_includes/theme/font-loader.html +55 -0
- data/_includes/theme/shell-theme-boot.html +115 -0
- data/_includes/theme/site-logo.html +6 -0
- data/_includes/theme/tailwind-theme-colors.json.liquid +65 -0
- data/_includes/theme/tailwind-theme-fonts.json.liquid +6 -0
- data/_includes/theme/theme-context.liquid +20 -0
- data/_includes/theme/theme-toggle.html +94 -0
- data/_includes/theme/theme-vars-block.liquid +161 -0
- data/_includes/theme/theme-vars.html +16 -0
- data/_includes/transcription.html +80 -0
- data/_includes/tweet.html +22 -0
- data/_includes/video.html +11 -0
- data/_includes/xcite.html +3 -0
- data/_includes/youtube-short.html +26 -0
- data/_includes/youtube.html +12 -0
- data/_layouts/article.html +27 -0
- data/_layouts/articles.html +534 -0
- data/_layouts/bibliography.html +417 -0
- data/_layouts/blog.html +544 -0
- data/_layouts/cv.html +393 -0
- data/_layouts/home.html +319 -0
- data/_layouts/network.html +9 -0
- data/_layouts/pandorga/articles.html +4 -0
- data/_layouts/pandorga/bibliography.html +4 -0
- data/_layouts/pandorga/blog.html +4 -0
- data/_layouts/pandorga/cv.html +4 -0
- data/_layouts/pandorga/media.html +4 -0
- data/_layouts/pandorga/network.html +4 -0
- data/_layouts/pandorga/portfolio.html +4 -0
- data/_layouts/post.html +7 -0
- data/_layouts/product.html +5 -0
- data/_layouts/project.html +27 -0
- data/_layouts/projects.html +534 -0
- data/_layouts/resources.html +387 -0
- data/_layouts/shell.html +203 -0
- data/_layouts/text.html +246 -0
- data/_plugins/bibliography_artifact.rb +57 -0
- data/_plugins/content_backend.rb +37 -0
- data/_plugins/content_collections.rb +51 -0
- data/_plugins/content_detail_paths.rb +58 -0
- data/_plugins/content_headers.rb +73 -0
- data/_plugins/content_keys.rb +310 -0
- data/_plugins/content_local_serve.rb +168 -0
- data/_plugins/content_site_adapter.rb +159 -0
- data/_plugins/content_validators_hook.rb +20 -0
- data/_plugins/content_writing_index.rb +52 -0
- data/_plugins/content_writing_paths.rb +55 -0
- data/_plugins/force_utf8_encoding.rb +13 -0
- data/_plugins/git_chronology.rb +211 -0
- data/_plugins/git_metadata.rb +43 -0
- data/_plugins/job_sections.rb +25 -0
- data/_plugins/lib/content_validators.rb +27 -0
- data/_plugins/lib/resource_validator.rb +56 -0
- data/_plugins/lib/tag_validator.rb +153 -0
- data/_plugins/lib/text_excerpt.rb +56 -0
- data/_plugins/lib/writing_entry.rb +107 -0
- data/_plugins/local_detail_rewrites.rb +194 -0
- data/_plugins/timeline.rb +507 -0
- data/_plugins/writing_slug_limits.rb +70 -0
- data/assets/css/article.css +1330 -0
- data/assets/css/base.css +1432 -0
- data/assets/css/cv.css +13 -0
- data/assets/css/global-link.css +94 -0
- data/assets/css/ledger/bibliography.css +307 -0
- data/assets/css/ledger/chrome.css +118 -0
- data/assets/css/ledger/cv.css +836 -0
- data/assets/css/ledger/detail.css +1270 -0
- data/assets/css/ledger/footer.css +298 -0
- data/assets/css/ledger/home.css +1490 -0
- data/assets/css/ledger/network.css +106 -0
- data/assets/css/ledger/projects.css +716 -0
- data/assets/css/ledger/rascunhos.css +379 -0
- data/assets/css/ledger/sources.css +327 -0
- data/assets/css/ledger/writing.css +754 -0
- data/assets/tailwind-play.js +15 -0
- data/examples/full/Gemfile +7 -0
- data/examples/full/_config.yml +92 -0
- data/examples/full/_redirects +3 -0
- data/examples/full/content/collections/articles/2026-01-01-welcome.md +9 -0
- data/examples/full/content/collections/data/contacts.yml +3 -0
- data/examples/full/content/collections/data/education.yml +4 -0
- data/examples/full/content/collections/jobs/research-fellow.md +12 -0
- data/examples/full/content/collections/posts/2026-01-02-note.md +9 -0
- data/examples/full/content/collections/products/ledger-tool.md +6 -0
- data/examples/full/content/collections/projects/demo-kit.md +10 -0
- data/examples/full/content/collections/resources/style-guide.md +7 -0
- data/examples/full/content/pages/cv/summary.md +1 -0
- data/examples/full/content/pages/headers.yml +20 -0
- data/examples/full/index.html +4 -0
- data/examples/minimal/Gemfile +7 -0
- data/examples/minimal/_config.yml +47 -0
- data/examples/minimal/_redirects +3 -0
- data/examples/minimal/content/collections/articles/2026-01-01-hello.md +10 -0
- data/examples/minimal/content/collections/articles/2026-01-02-second.md +9 -0
- data/examples/minimal/content/pages/headers.yml +6 -0
- data/examples/minimal/index.html +4 -0
- data/exe/pandorga +7 -0
- data/functions/api/studio/[[path]].js +562 -0
- data/functions/api/studio/_lib/auth.js +131 -0
- data/functions/api/studio/_lib/blocks.js +156 -0
- data/functions/api/studio/_lib/gemini.js +62 -0
- data/functions/api/studio/_lib/github.js +278 -0
- data/functions/api/studio/_lib/guidelines.js +143 -0
- data/functions/api/studio/_lib/paths.js +76 -0
- data/functions/api/studio/_lib/prompts.js +165 -0
- data/functions/api/studio/_lib/rate-limit.js +40 -0
- data/functions/api/studio/_lib/rewrite.js +136 -0
- data/functions/api/studio/_lib/sanitize.js +143 -0
- data/lib/pandorga/cli.rb +66 -0
- data/lib/pandorga/commands/doctor.rb +66 -0
- data/lib/pandorga/commands/export.rb +30 -0
- data/lib/pandorga/commands/install_functions.rb +41 -0
- data/lib/pandorga/commands/new.rb +113 -0
- data/lib/pandorga/commands/publish.rb +53 -0
- data/lib/pandorga/commands/serve.rb +34 -0
- data/lib/pandorga/commands/studio_schema.rb +309 -0
- data/lib/pandorga/jekyll/generator.rb +74 -0
- data/lib/pandorga/jekyll/plugins/bibliography_artifact.rb +57 -0
- data/lib/pandorga/jekyll/plugins/content_backend.rb +37 -0
- data/lib/pandorga/jekyll/plugins/content_collections.rb +51 -0
- data/lib/pandorga/jekyll/plugins/content_detail_paths.rb +58 -0
- data/lib/pandorga/jekyll/plugins/content_headers.rb +73 -0
- data/lib/pandorga/jekyll/plugins/content_keys.rb +310 -0
- data/lib/pandorga/jekyll/plugins/content_local_serve.rb +168 -0
- data/lib/pandorga/jekyll/plugins/content_site_adapter.rb +159 -0
- data/lib/pandorga/jekyll/plugins/content_validators_hook.rb +20 -0
- data/lib/pandorga/jekyll/plugins/content_writing_index.rb +52 -0
- data/lib/pandorga/jekyll/plugins/content_writing_paths.rb +55 -0
- data/lib/pandorga/jekyll/plugins/force_utf8_encoding.rb +13 -0
- data/lib/pandorga/jekyll/plugins/git_chronology.rb +211 -0
- data/lib/pandorga/jekyll/plugins/git_metadata.rb +43 -0
- data/lib/pandorga/jekyll/plugins/job_sections.rb +25 -0
- data/lib/pandorga/jekyll/plugins/lib/content_validators.rb +27 -0
- data/lib/pandorga/jekyll/plugins/lib/resource_validator.rb +56 -0
- data/lib/pandorga/jekyll/plugins/lib/tag_validator.rb +153 -0
- data/lib/pandorga/jekyll/plugins/lib/text_excerpt.rb +56 -0
- data/lib/pandorga/jekyll/plugins/lib/writing_entry.rb +107 -0
- data/lib/pandorga/jekyll/plugins/local_detail_rewrites.rb +194 -0
- data/lib/pandorga/jekyll/plugins/timeline.rb +507 -0
- data/lib/pandorga/jekyll/plugins/writing_slug_limits.rb +70 -0
- data/lib/pandorga/jekyll.rb +15 -0
- data/lib/pandorga/object_store_env.rb +51 -0
- data/lib/pandorga/registry/schema.yml +115 -0
- data/lib/pandorga/registry.rb +279 -0
- data/lib/pandorga/version.rb +5 -0
- data/lib/pandorga.rb +19 -0
- data/scripts/cdn-allowlist.json +19 -0
- data/scripts/content/backfill-content-keys.rb +99 -0
- data/scripts/content/band_art.rb +362 -0
- data/scripts/content/export-content-json.rb +741 -0
- data/scripts/content/flatten-ansi-capture.mjs +153 -0
- data/scripts/content/hero_portraits.rb +28 -0
- data/scripts/content/publish-content-to-object-store.sh +78 -0
- data/scripts/content/publish-content-to-r2.sh +5 -0
- data/scripts/content/sync-content-json-to-r2.rb +167 -0
- data/scripts/content/sync-content-redirects.rb +12 -0
- data/scripts/generate-configuration-doc.rb +127 -0
- data/scripts/lib/content_license.rb +50 -0
- data/scripts/lib/content_storage_config.rb +70 -0
- data/scripts/lib/normalize-display-math.mjs +52 -0
- data/scripts/lib/r2_json_sync.rb +76 -0
- data/scripts/lib/withdrawn_public_objects.rb +23 -0
- data/scripts/lib/writing_index_contract.rb +61 -0
- data/scripts/pdf/md-to-pdf.mjs +1400 -0
- data/scripts/pdf/md-to-pdf.sh +27 -0
- data/scripts/test/fixtures/dual-template/_config.yml +32 -0
- data/scripts/test/fixtures/dual-template/index.html +4 -0
- data/scripts/test/render-parity/article-note.json +5 -0
- data/scripts/test/render-parity/blockquote.json +5 -0
- data/scripts/test/render-parity/blocks/ansi-basic.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-bold-is-bright.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-drops-cursor-moves.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-escapes-markup.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-extended-colour.json +7 -0
- data/scripts/test/render-parity/blocks/ansi-real-control-byte.json +7 -0
- data/scripts/test/render-parity/blocks/archive-card-date-once.json +19 -0
- data/scripts/test/render-parity/blocks/archive-card-empty-foot.json +7 -0
- data/scripts/test/render-parity/blocks/archive-card-footer-dates.json +22 -0
- data/scripts/test/render-parity/blocks/archive-card-meta-parts.json +26 -0
- data/scripts/test/render-parity/blocks/archive-card.json +32 -0
- data/scripts/test/render-parity/blocks/cta-link.json +13 -0
- data/scripts/test/render-parity/blocks/lang-pill-pt.json +8 -0
- data/scripts/test/render-parity/blocks/lang-pill.json +8 -0
- data/scripts/test/render-parity/blocks/ledger-row-meta-parts.json +29 -0
- data/scripts/test/render-parity/blocks/ledger-row.json +35 -0
- data/scripts/test/render-parity/blocks/meta-strip.json +27 -0
- data/scripts/test/render-parity/blocks/poster-media-image.json +8 -0
- data/scripts/test/render-parity/blocks/poster-media-video.json +9 -0
- data/scripts/test/render-parity/blocks/section-banner.json +13 -0
- data/scripts/test/render-parity/blocks/system-card-custom-meta.json +22 -0
- data/scripts/test/render-parity/blocks/system-card.json +48 -0
- data/scripts/test/render-parity/blocks/tag-chips-links.json +12 -0
- data/scripts/test/render-parity/blocks/tag-chips.json +9 -0
- data/scripts/test/render-parity/cut-in-blank-line.json +9 -0
- data/scripts/test/render-parity/cut-in-right.json +9 -0
- data/scripts/test/render-parity/cut-in.json +8 -0
- data/scripts/test/render-parity/figure.json +5 -0
- data/scripts/test/render-parity/load-runtime.mjs +337 -0
- data/scripts/test/render-parity/ongoing-product.json +5 -0
- data/scripts/test/render-parity/plotly.json +5 -0
- data/scripts/test/render-parity/poem.json +5 -0
- data/scripts/test/render-parity/run-absent-field-checks.mjs +232 -0
- data/scripts/test/render-parity/run-block-fixture.mjs +86 -0
- data/scripts/test/render-parity/run-detail-parity-checks.mjs +123 -0
- data/scripts/test/render-parity/run-field-flow-checks.mjs +227 -0
- data/scripts/test/render-parity/run-fixture.mjs +159 -0
- data/scripts/test/render-parity/selectors.mjs +43 -0
- data/scripts/test/render-parity/video.json +5 -0
- data/scripts/test/render-parity/youtube-short.json +10 -0
- data/scripts/test/render-parity/youtube.json +8 -0
- data/scripts/test/test-absent-field-absent-element.rb +43 -0
- data/scripts/test/test-cdn-pins.rb +44 -0
- data/scripts/test/test-cloudflare-watch-paths-doc.rb +31 -0
- data/scripts/test/test-configuration-doc.rb +36 -0
- data/scripts/test/test-content-detail-urls.rb +346 -0
- data/scripts/test/test-content-keys.rb +110 -0
- data/scripts/test/test-content-license.rb +45 -0
- data/scripts/test/test-content-render-parity.rb +38 -0
- data/scripts/test/test-content-validators.rb +12 -0
- data/scripts/test/test-css-structure.rb +72 -0
- data/scripts/test/test-cv-export-contract.rb +158 -0
- data/scripts/test/test-detail-render-parity.rb +40 -0
- data/scripts/test/test-dev-reexport.rb +122 -0
- data/scripts/test/test-doctor.rb +39 -0
- data/scripts/test/test-dual-template-instances.rb +87 -0
- data/scripts/test/test-dual-template-runtime.rb +167 -0
- data/scripts/test/test-examples-build.rb +60 -0
- data/scripts/test/test-export-content-json-incremental.rb +138 -0
- data/scripts/test/test-export.rb +53 -0
- data/scripts/test/test-exported-fields-reach-the-view.rb +60 -0
- data/scripts/test/test-form-control-theming.rb +143 -0
- data/scripts/test/test-git-chronology.rb +414 -0
- data/scripts/test/test-hero-portraits.mjs +97 -0
- data/scripts/test/test-hero-portraits.rb +162 -0
- data/scripts/test/test-home-band-art.rb +135 -0
- data/scripts/test/test-install-functions.rb +39 -0
- data/scripts/test/test-language-export.rb +79 -0
- data/scripts/test/test-listing-filter-contract.rb +119 -0
- data/scripts/test/test-math-row-breaks.mjs +79 -0
- data/scripts/test/test-math-row-breaks.rb +18 -0
- data/scripts/test/test-new.rb +113 -0
- data/scripts/test/test-no-personal-data.rb +85 -0
- data/scripts/test/test-public-cv-export.rb +164 -0
- data/scripts/test/test-r2-config-consistency.rb +57 -0
- data/scripts/test/test-r2-json-sync.rb +193 -0
- data/scripts/test/test-registry-home.rb +34 -0
- data/scripts/test/test-runtime-js-syntax.rb +64 -0
- data/scripts/test/test-slim-writing-index.rb +126 -0
- data/scripts/test/test-studio-api-contract.rb +99 -0
- data/scripts/test/test-studio-content-files.rb +79 -0
- data/scripts/test/test-studio-liquid-roundtrip.mjs +179 -0
- data/scripts/test/test-studio-rate-limit.mjs +70 -0
- data/scripts/test/test-studio-rewrite-api-contract.rb +59 -0
- data/scripts/test/test-studio-rewrite-sanitize.mjs +210 -0
- data/scripts/test/test-studio-schema-composition.rb +126 -0
- data/scripts/test/test-studio-slug-config.rb +167 -0
- data/scripts/test/test-template-packages.rb +51 -0
- data/scripts/test/test-text-excerpt.rb +51 -0
- data/scripts/test/test-unwrap-media-src.mjs +39 -0
- data/scripts/test/test-unwrap-media-src.rb +19 -0
- data/scripts/test/test-withdrawn-content-export.rb +69 -0
- data/scripts/test/test-writing-index-export-slugs.rb +84 -0
- data/scripts/test/test-writing-slug-limits.rb +61 -0
- data/scripts/test/test-xcite-chronology.rb +106 -0
- data/scripts/validate.sh +74 -0
- data/scripts/visual/build-cv-pages-json.js +33 -0
- data/scripts/visual/build-site-pages-json.js +94 -0
- data/scripts/visual/publish-visual-inspection-to-r2.sh +42 -0
- data/scripts/visual/take-screenshots.js +88 -0
- data/scripts/visual/visual-inspection-pages.json +113 -0
- data/studio/assets/index-B_j0pMD5.css +1 -0
- data/studio/assets/index-Ch4YEv9g.js +285 -0
- data/studio/index.html +27 -0
- data/studio/schema.yml +869 -0
- data/studio-app/README.md +27 -0
- data/studio-app/index.html +26 -0
- data/studio-app/package-lock.json +2499 -0
- data/studio-app/package.json +26 -0
- data/studio-app/skills-lock.json +47 -0
- data/studio-app/src/api/client.js +71 -0
- data/studio-app/src/editor/body-editor.js +70 -0
- data/studio-app/src/editor/liquid-core.js +382 -0
- data/studio-app/src/editor/liquid.js +77 -0
- data/studio-app/src/lib/document.js +207 -0
- data/studio-app/src/lib/list-controls.js +317 -0
- data/studio-app/src/lib/schema.js +66 -0
- data/studio-app/src/lib/yaml-doc.js +100 -0
- data/studio-app/src/main.js +3693 -0
- data/studio-app/src/media/browser.js +578 -0
- data/studio-app/src/preview/render.js +430 -0
- data/studio-app/src/styles/studio.css +2826 -0
- data/studio-app/vite.config.js +25 -0
- data/templates/articles/README.md +40 -0
- data/templates/articles/fixtures/article.md +9 -0
- data/templates/articles/screenshots/README.md +6 -0
- data/templates/articles/studio.yml +73 -0
- data/templates/articles/template.yml +21 -0
- data/templates/bibliography/README.md +41 -0
- data/templates/bibliography/fixtures/references.json +9 -0
- data/templates/bibliography/screenshots/README.md +6 -0
- data/templates/bibliography/studio.yml +66 -0
- data/templates/bibliography/template.yml +18 -0
- data/templates/blog/README.md +38 -0
- data/templates/blog/fixtures/post.md +9 -0
- data/templates/blog/screenshots/README.md +6 -0
- data/templates/blog/studio.yml +69 -0
- data/templates/blog/template.yml +20 -0
- data/templates/cv/README.md +34 -0
- data/templates/cv/fixtures/job.md +12 -0
- data/templates/cv/screenshots/README.md +6 -0
- data/templates/cv/studio.yml +200 -0
- data/templates/cv/template.yml +25 -0
- data/templates/media/README.md +38 -0
- data/templates/media/fixtures/resource.md +7 -0
- data/templates/media/screenshots/README.md +6 -0
- data/templates/media/studio.yml +58 -0
- data/templates/media/template.yml +17 -0
- data/templates/network/README.md +34 -0
- data/templates/network/fixtures/sources.json +3 -0
- data/templates/network/screenshots/README.md +6 -0
- data/templates/network/studio.yml +28 -0
- data/templates/network/template.yml +10 -0
- data/templates/portfolio/README.md +39 -0
- data/templates/portfolio/fixtures/project.md +10 -0
- data/templates/portfolio/screenshots/README.md +6 -0
- data/templates/portfolio/studio.yml +89 -0
- data/templates/portfolio/template.yml +18 -0
- metadata +505 -0
data/_layouts/cv.html
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: shell
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<main class="cv-page{% if page.accent and page.accent != 'blue' %} page-accent--{{ page.accent }}{% endif %} max-w-7xl mx-auto px-4 sm:px-8 md:px-16 pt-10 pb-16 min-h-screen">
|
|
6
|
+
<div id="cv-page-header">
|
|
7
|
+
<div class="cv-dossier">
|
|
8
|
+
<span class="shard-accent cv-dossier-shard" aria-hidden="true"></span>
|
|
9
|
+
<span class="shard-accent shard-accent-beta cv-dossier-shard cv-dossier-shard--beta" aria-hidden="true"></span>
|
|
10
|
+
<div class="cv-dossier-inner">
|
|
11
|
+
{% include page/page-header.html
|
|
12
|
+
key=page.key
|
|
13
|
+
header_class="cv-dossier-head"
|
|
14
|
+
tags_class="cv-dossier-tags code-technical hidden"
|
|
15
|
+
standfirst_class="cv-dossier-standfirst font-body hidden" %}
|
|
16
|
+
<div class="cv-dossier-aside">
|
|
17
|
+
<div class="cv-contact-cta" id="contact">
|
|
18
|
+
<span class="cv-contact-cta-label">Contact</span>
|
|
19
|
+
{% include page/professional-connections.html wrapper_class="cv-contacts" %}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div id="cv-disclaimer" class="cv-disclaimer-box">
|
|
27
|
+
<span class="material-symbols-outlined cv-dossier-note-icon" aria-hidden="true">info</span>
|
|
28
|
+
<div class="cv-dossier-note font-body" data-content-fragment="cv/disclaimer">
|
|
29
|
+
<p class="font-body text-on-surface-variant italic">Loading...</p>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div id="cv-listing">
|
|
34
|
+
{% capture cv_summary %}
|
|
35
|
+
<div class="cv-summary font-body" data-content-fragment="cv/summary">
|
|
36
|
+
<p class="font-body text-on-surface-variant italic">Loading...</p>
|
|
37
|
+
</div>
|
|
38
|
+
{% endcapture %}
|
|
39
|
+
{% include cv/cv-box.html id="summary" title="Summary" content=cv_summary %}
|
|
40
|
+
|
|
41
|
+
{% capture cv_experience %}
|
|
42
|
+
<div id="cv-experience-list" class="cv-entry-stack">
|
|
43
|
+
<p class="font-body text-on-surface-variant italic">Loading experience...</p>
|
|
44
|
+
</div>
|
|
45
|
+
{% endcapture %}
|
|
46
|
+
{% include cv/cv-box.html id="experience" title="Experience" content=cv_experience %}
|
|
47
|
+
|
|
48
|
+
{% capture cv_products %}
|
|
49
|
+
<div id="cv-products-grid" class="cv-product-grid">
|
|
50
|
+
<p class="font-body text-on-surface-variant italic">Loading products...</p>
|
|
51
|
+
</div>
|
|
52
|
+
{% endcapture %}
|
|
53
|
+
{% include cv/cv-box.html id="products" title="Products" content=cv_products %}
|
|
54
|
+
|
|
55
|
+
{% capture cv_education %}
|
|
56
|
+
<div id="cv-education-list" class="cv-entry-stack">
|
|
57
|
+
<p class="font-body text-on-surface-variant italic">Loading education...</p>
|
|
58
|
+
</div>
|
|
59
|
+
{% endcapture %}
|
|
60
|
+
{% include cv/cv-box.html id="education" title="Education" content=cv_education %}
|
|
61
|
+
|
|
62
|
+
{% capture cv_languages %}
|
|
63
|
+
<div id="cv-languages-list" class="cv-language-list">
|
|
64
|
+
<p class="font-body text-on-surface-variant italic">Loading languages...</p>
|
|
65
|
+
</div>
|
|
66
|
+
{% endcapture %}
|
|
67
|
+
{% include cv/cv-box.html id="languages" title="Languages" content=cv_languages %}
|
|
68
|
+
|
|
69
|
+
{% include page/timeline-section.html %}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div id="cv-detail" class="hidden">
|
|
73
|
+
<div class="grid grid-cols-1 gap-8 lg:gap-12 items-start text-detail-grid">
|
|
74
|
+
<aside id="cv-detail-sidebar"></aside>
|
|
75
|
+
<div id="cv-detail-main"></div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</main>
|
|
79
|
+
|
|
80
|
+
<script>
|
|
81
|
+
(function () {
|
|
82
|
+
'use strict';
|
|
83
|
+
|
|
84
|
+
var runtime = window.ContentRuntime;
|
|
85
|
+
if (!runtime) return;
|
|
86
|
+
|
|
87
|
+
var experienceList = document.getElementById('cv-experience-list');
|
|
88
|
+
var educationList = document.getElementById('cv-education-list');
|
|
89
|
+
var languagesList = document.getElementById('cv-languages-list');
|
|
90
|
+
var productsGrid = document.getElementById('cv-products-grid');
|
|
91
|
+
var listing = document.getElementById('cv-listing');
|
|
92
|
+
var detail = document.getElementById('cv-detail');
|
|
93
|
+
var detailSidebar = document.getElementById('cv-detail-sidebar');
|
|
94
|
+
var detailMain = document.getElementById('cv-detail-main');
|
|
95
|
+
var pageHeader = document.getElementById('cv-page-header');
|
|
96
|
+
var disclaimer = document.getElementById('cv-disclaimer');
|
|
97
|
+
var selectedProduct = runtime.contentSlug('product');
|
|
98
|
+
|
|
99
|
+
var EN_DASH = '–';
|
|
100
|
+
|
|
101
|
+
function isProductOngoing(fm) {
|
|
102
|
+
fm = fm || {};
|
|
103
|
+
if (fm.status === 'ongoing') return true;
|
|
104
|
+
return !String(fm.end_date || '').trim();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatProductPeriod(fm) {
|
|
108
|
+
fm = fm || {};
|
|
109
|
+
if (!fm.start_date) return '';
|
|
110
|
+
if (isProductOngoing(fm)) return String(fm.start_date) + ' ' + EN_DASH + ' Ongoing';
|
|
111
|
+
return String(fm.start_date) + ' ' + EN_DASH + ' ' + fm.end_date;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* An open-ended range is a fact about the role, not a missing field:
|
|
115
|
+
jobs and degrees both write the end date only once it exists. */
|
|
116
|
+
function formatSpan(start, end, openLabel) {
|
|
117
|
+
var from = String(start || '').trim();
|
|
118
|
+
var to = String(end || '').trim();
|
|
119
|
+
if (!from) return to;
|
|
120
|
+
return from + ' ' + EN_DASH + ' ' + (to || openLabel);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function compareProductsByEndDate(a, b) {
|
|
124
|
+
var afm = (a && a.front_matter) || {};
|
|
125
|
+
var bfm = (b && b.front_matter) || {};
|
|
126
|
+
var aOngoing = isProductOngoing(afm);
|
|
127
|
+
var bOngoing = isProductOngoing(bfm);
|
|
128
|
+
if (aOngoing !== bOngoing) return aOngoing ? -1 : 1;
|
|
129
|
+
if (aOngoing && bOngoing) {
|
|
130
|
+
return String(bfm.start_date || '').localeCompare(String(afm.start_date || ''));
|
|
131
|
+
}
|
|
132
|
+
return String(bfm.end_date || '').localeCompare(String(afm.end_date || ''));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Register entry: the one card shape the CV repeats. The label line
|
|
136
|
+
names the organisation and the unit inside it, the display line
|
|
137
|
+
names the post held there, and every part disappears when its
|
|
138
|
+
field is empty rather than leaving a dangling separator. */
|
|
139
|
+
function renderEntry(parts) {
|
|
140
|
+
var el = document.createElement('article');
|
|
141
|
+
el.className = 'cv-entry';
|
|
142
|
+
|
|
143
|
+
var ident = [];
|
|
144
|
+
if (parts.lead) {
|
|
145
|
+
ident.push('<span class="cv-entry-lead card-meta">' +
|
|
146
|
+
runtime.escapeHtml(parts.lead) + '</span>');
|
|
147
|
+
}
|
|
148
|
+
/* The separator is punctuation between two fields, so it needs
|
|
149
|
+
both of them: one job declares no department, and its label
|
|
150
|
+
line has to stop after the company. */
|
|
151
|
+
if (parts.unit) {
|
|
152
|
+
if (ident.length) {
|
|
153
|
+
ident.push('<span class="cv-entry-sep" aria-hidden="true">›</span>');
|
|
154
|
+
}
|
|
155
|
+
ident.push('<span class="cv-entry-unit card-meta">' +
|
|
156
|
+
runtime.escapeHtml(parts.unit) + '</span>');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
var head = [
|
|
160
|
+
ident.length ? '<div class="cv-entry-ident">' + ident.join('') + '</div>' : '',
|
|
161
|
+
parts.span ? '<span class="cv-entry-span code-technical">' +
|
|
162
|
+
runtime.escapeHtml(parts.span) + '</span>' : ''
|
|
163
|
+
].join('');
|
|
164
|
+
|
|
165
|
+
var html = [
|
|
166
|
+
head ? '<div class="cv-entry-head">' + head + '</div>' : '',
|
|
167
|
+
parts.title ? '<h3 class="card-title cv-entry-title">' +
|
|
168
|
+
runtime.escapeHtml(parts.title) + '</h3>' : '',
|
|
169
|
+
parts.place ? '<div class="cv-entry-place font-body">' +
|
|
170
|
+
runtime.escapeHtml(parts.place) + '</div>' : '',
|
|
171
|
+
parts.bodyHtml ? '<div class="cv-entry-copy font-body">' + parts.bodyHtml + '</div>' : '',
|
|
172
|
+
parts.footHtml || ''
|
|
173
|
+
].join('');
|
|
174
|
+
|
|
175
|
+
el.innerHTML = html;
|
|
176
|
+
return el;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function renderJob(job, productMap) {
|
|
180
|
+
var fm = job.front_matter || {};
|
|
181
|
+
var description = fm.body_public
|
|
182
|
+
? runtime.renderMarkdown(fm.body_public)
|
|
183
|
+
: runtime.renderMarkdown(job.body_markdown);
|
|
184
|
+
|
|
185
|
+
/* Role to product linkage. The chips carry the product name, so
|
|
186
|
+
the href lookup is keyed by the label the chip shows. */
|
|
187
|
+
var hrefByLabel = {};
|
|
188
|
+
var labels = (Array.isArray(fm.products) ? fm.products : [])
|
|
189
|
+
.filter(Boolean)
|
|
190
|
+
.map(function (productKey) {
|
|
191
|
+
var product = productMap[productKey];
|
|
192
|
+
var label = (product && product.front_matter && product.front_matter.product) || productKey;
|
|
193
|
+
hrefByLabel[label] = runtime.productUrl(product ? product.slug : productKey);
|
|
194
|
+
return label;
|
|
195
|
+
});
|
|
196
|
+
var chips = runtime.renderTagChips(labels, {
|
|
197
|
+
hrefFor: function (label) { return hrefByLabel[label] || '#'; }
|
|
198
|
+
});
|
|
199
|
+
var foot = chips
|
|
200
|
+
? '<div class="cv-entry-foot"><span class="cv-entry-foot-label card-meta">Products</span>' +
|
|
201
|
+
chips + '</div>'
|
|
202
|
+
: '';
|
|
203
|
+
|
|
204
|
+
return renderEntry({
|
|
205
|
+
lead: fm.company,
|
|
206
|
+
unit: fm.department,
|
|
207
|
+
span: formatSpan(fm.start, fm.end, 'Present'),
|
|
208
|
+
title: fm.position,
|
|
209
|
+
place: fm.location,
|
|
210
|
+
bodyHtml: description,
|
|
211
|
+
footHtml: foot
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* A school has no department, and the campus is what tells two
|
|
216
|
+
entries of the same institution apart, so the location takes the
|
|
217
|
+
unit slot a job gives its department. */
|
|
218
|
+
function renderEducation(entry) {
|
|
219
|
+
return renderEntry({
|
|
220
|
+
lead: entry.institution,
|
|
221
|
+
unit: entry.location,
|
|
222
|
+
span: formatSpan(entry.start, entry.end, 'Ongoing'),
|
|
223
|
+
title: entry.degree,
|
|
224
|
+
bodyHtml: entry.detail
|
|
225
|
+
? '<p>' + runtime.escapeHtml(entry.detail) + '</p>'
|
|
226
|
+
: ''
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderLanguage(entry) {
|
|
231
|
+
var el = document.createElement('div');
|
|
232
|
+
el.className = 'cv-language';
|
|
233
|
+
el.innerHTML = [
|
|
234
|
+
'<span class="cv-language-name font-body">' + runtime.escapeHtml(entry.name || '') + '</span>',
|
|
235
|
+
'<span class="cv-language-rule" aria-hidden="true"></span>',
|
|
236
|
+
'<span class="cv-language-level code-technical">' + runtime.escapeHtml(entry.level || '') + '</span>'
|
|
237
|
+
].join('');
|
|
238
|
+
return el;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function renderProduct(product, opts) {
|
|
242
|
+
opts = opts || {};
|
|
243
|
+
var fm = product.front_matter || {};
|
|
244
|
+
var card = document.createElement('article');
|
|
245
|
+
card.className = 'cv-product group';
|
|
246
|
+
card.setAttribute('data-dynamic-product', 'true');
|
|
247
|
+
|
|
248
|
+
var productUrl = runtime.productUrl(product.slug);
|
|
249
|
+
var productName = fm.product || fm.title || product.slug;
|
|
250
|
+
var period = formatProductPeriod(fm);
|
|
251
|
+
/* Several products carry the same string in `product` and
|
|
252
|
+
`title`; printing it twice reads as a rendering fault. */
|
|
253
|
+
var subtitle = fm.title && fm.title !== productName ? fm.title : '';
|
|
254
|
+
|
|
255
|
+
card.innerHTML = [
|
|
256
|
+
'<div class="cv-product-head">',
|
|
257
|
+
fm.company ? '<span class="cv-product-company card-meta">' +
|
|
258
|
+
runtime.escapeHtml(fm.company) + '</span>' : '<span></span>',
|
|
259
|
+
period ? '<time class="cv-product-span code-technical">' +
|
|
260
|
+
runtime.escapeHtml(period) + '</time>' : '',
|
|
261
|
+
'</div>',
|
|
262
|
+
'<h3 class="card-title cv-product-title"><a href="' + runtime.escapeAttr(productUrl) + '">' +
|
|
263
|
+
runtime.escapeHtml(productName) + '</a></h3>',
|
|
264
|
+
subtitle ? '<p class="cv-product-copy font-body">' + runtime.escapeHtml(subtitle) + '</p>' : '',
|
|
265
|
+
'<div class="cv-product-foot">' + runtime.renderCtaLink(productUrl, 'Details') + '</div>'
|
|
266
|
+
].join('');
|
|
267
|
+
return card;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function showListing() {
|
|
271
|
+
if (listing) listing.classList.remove('hidden');
|
|
272
|
+
if (detail) detail.classList.add('hidden');
|
|
273
|
+
if (pageHeader) pageHeader.classList.remove('hidden');
|
|
274
|
+
if (disclaimer) disclaimer.classList.remove('hidden');
|
|
275
|
+
if (detailMain) detailMain.innerHTML = '';
|
|
276
|
+
if (detailSidebar) detailSidebar.innerHTML = '';
|
|
277
|
+
runtime.removeRuntimeBackLink();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function renderProductDetail(item) {
|
|
281
|
+
var fm = item.front_matter || {};
|
|
282
|
+
if (!fm.layout) fm.layout = 'product';
|
|
283
|
+
|
|
284
|
+
if (listing) listing.classList.add('hidden');
|
|
285
|
+
if (pageHeader) pageHeader.classList.add('hidden');
|
|
286
|
+
if (disclaimer) disclaimer.classList.add('hidden');
|
|
287
|
+
if (detail) detail.classList.remove('hidden');
|
|
288
|
+
|
|
289
|
+
if (detailSidebar) {
|
|
290
|
+
detailSidebar.innerHTML = runtime.buildTextSidebar({
|
|
291
|
+
frontMatter: fm,
|
|
292
|
+
fallbackTitle: item.slug
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
var allItems = await runtime.loadWritingIndex('products');
|
|
297
|
+
var sorted = allItems.slice().sort(compareProductsByEndDate);
|
|
298
|
+
var index = sorted.findIndex(function (entry) { return entry.slug === item.slug; });
|
|
299
|
+
|
|
300
|
+
await runtime.renderRuntimeDetailSection({
|
|
301
|
+
detailAttribute: 'data-dynamic-product-detail',
|
|
302
|
+
bodyMarkdown: item.body_markdown,
|
|
303
|
+
frontMatter: fm,
|
|
304
|
+
mainColumn: detailMain,
|
|
305
|
+
footerNav: {
|
|
306
|
+
lang: 'enus',
|
|
307
|
+
previousUrl: index >= 0 && index < sorted.length - 1 ? runtime.productUrl(sorted[index + 1].slug) : null,
|
|
308
|
+
nextUrl: index > 0 ? runtime.productUrl(sorted[index - 1].slug) : null
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function loadListing() {
|
|
314
|
+
showListing();
|
|
315
|
+
|
|
316
|
+
if (experienceList) {
|
|
317
|
+
runtime.Listing.showLoadingState(experienceList, 'Loading experience...');
|
|
318
|
+
Promise.all([
|
|
319
|
+
runtime.loadCollection('collections/jobs', 'jobs'),
|
|
320
|
+
runtime.loadCollection('collections/products', 'products')
|
|
321
|
+
]).then(function (results) {
|
|
322
|
+
var jobs = results[0];
|
|
323
|
+
var productMap = {};
|
|
324
|
+
results[1].forEach(function (product) {
|
|
325
|
+
var key = product.key || (product.front_matter && product.front_matter.key) || product.slug;
|
|
326
|
+
productMap[key] = product;
|
|
327
|
+
productMap[product.slug] = product;
|
|
328
|
+
});
|
|
329
|
+
experienceList.innerHTML = '';
|
|
330
|
+
jobs.sort(function (a, b) {
|
|
331
|
+
var as = (a.front_matter && a.front_matter.start) || '';
|
|
332
|
+
var bs = (b.front_matter && b.front_matter.start) || '';
|
|
333
|
+
return String(bs).localeCompare(String(as));
|
|
334
|
+
}).forEach(function (job) {
|
|
335
|
+
experienceList.appendChild(renderJob(job, productMap));
|
|
336
|
+
});
|
|
337
|
+
}).catch(function (error) {
|
|
338
|
+
console.error(error);
|
|
339
|
+
runtime.Listing.showErrorState(experienceList, 'Experience could not be loaded right now.');
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (educationList) {
|
|
344
|
+
runtime.Listing.showLoadingState(educationList, 'Loading education...');
|
|
345
|
+
runtime.loadJson('data/education.json').then(function (entries) {
|
|
346
|
+
educationList.innerHTML = '';
|
|
347
|
+
entries.forEach(function (entry) { educationList.appendChild(renderEducation(entry)); });
|
|
348
|
+
}).catch(function (error) {
|
|
349
|
+
console.error(error);
|
|
350
|
+
runtime.Listing.showErrorState(educationList, 'Education could not be loaded right now.');
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (languagesList) {
|
|
355
|
+
runtime.Listing.showLoadingState(languagesList, 'Loading languages...');
|
|
356
|
+
runtime.loadJson('data/languages.json').then(function (entries) {
|
|
357
|
+
languagesList.innerHTML = '';
|
|
358
|
+
entries.forEach(function (entry) { languagesList.appendChild(renderLanguage(entry)); });
|
|
359
|
+
}).catch(function (error) {
|
|
360
|
+
console.error(error);
|
|
361
|
+
runtime.Listing.showErrorState(languagesList, 'Languages could not be loaded right now.');
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (productsGrid) {
|
|
366
|
+
runtime.Listing.showLoadingState(productsGrid, 'Loading products...');
|
|
367
|
+
runtime.loadWritingIndex('products').then(function (products) {
|
|
368
|
+
productsGrid.innerHTML = '';
|
|
369
|
+
var ordered = products.slice().sort(compareProductsByEndDate);
|
|
370
|
+
ordered.forEach(function (product) {
|
|
371
|
+
productsGrid.appendChild(renderProduct(product));
|
|
372
|
+
});
|
|
373
|
+
}).catch(function (error) {
|
|
374
|
+
console.error(error);
|
|
375
|
+
runtime.Listing.showErrorState(productsGrid, 'Products could not be loaded right now.');
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (selectedProduct) {
|
|
381
|
+
runtime.loadCollectionItem('products', selectedProduct).then(function (item) {
|
|
382
|
+
return renderProductDetail(item);
|
|
383
|
+
}).catch(function (error) {
|
|
384
|
+
console.error(error);
|
|
385
|
+
loadListing();
|
|
386
|
+
if (productsGrid) runtime.Listing.showErrorState(productsGrid, 'This product could not be loaded right now.');
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
loadListing();
|
|
392
|
+
})();
|
|
393
|
+
</script>
|