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/DESIGN.md
ADDED
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
---
|
|
2
|
+
version: "2.0"
|
|
3
|
+
name: "Brazilian Ledger"
|
|
4
|
+
description: >-
|
|
5
|
+
Editorial-brutalist personal site, rebuilt on the "Architectural Ledger"
|
|
6
|
+
component layer. Pure-white (or near-black) paper, square cards with thin
|
|
7
|
+
clear ink frames (Reimagined Borders), a slate/forest/ochre triad with
|
|
8
|
+
raised chroma (2026-09 vivid pass), serif display titles over a humanist
|
|
9
|
+
sans body, monospace for notation, and translucent "cubist shards" reduced
|
|
10
|
+
to two canonical polygons. Depth is a four-step surface ladder — canvas,
|
|
11
|
+
panel, card, hover — rather than shadow. Every card, row, chip, meta strip
|
|
12
|
+
and CTA comes from one shared JavaScript block library. Two complete
|
|
13
|
+
palettes, light and dark, selected by data-theme on <html>.
|
|
14
|
+
omitted:
|
|
15
|
+
- name: "Elevation"
|
|
16
|
+
reason: >-
|
|
17
|
+
Deliberate. Content carries no shadow at all; the only two box-shadows
|
|
18
|
+
in the build are on floating overlays. Depth is the surface ladder plus
|
|
19
|
+
hairline borders.
|
|
20
|
+
|
|
21
|
+
# ─────────────────────────────────────────────────────────────
|
|
22
|
+
# Colors. Two full palettes. Source of truth: _data/themes/*.yml
|
|
23
|
+
# rendered into CSS custom properties by _includes/theme/theme-vars-block.liquid.
|
|
24
|
+
# Light is also bound to :root, so it is the no-JS default.
|
|
25
|
+
# ─────────────────────────────────────────────────────────────
|
|
26
|
+
colors:
|
|
27
|
+
light:
|
|
28
|
+
primary: "#1c5a7a" # ice slate (vivid 2026-09)
|
|
29
|
+
secondary: "#287445" # forest green (vivid 2026-09)
|
|
30
|
+
tertiary: "#746f30" # ochre gold (vivid 2026-09)
|
|
31
|
+
primaryContainer: "#a8c5d4"
|
|
32
|
+
secondaryContainer: "#b4d4c0"
|
|
33
|
+
tertiaryContainer: "#d0ceb0"
|
|
34
|
+
onPrimaryContainer: "#0a1419"
|
|
35
|
+
onSecondaryContainer: "#0c1710"
|
|
36
|
+
onTertiaryContainer: "#17160c"
|
|
37
|
+
onPrimary: "#ffffff"
|
|
38
|
+
onSecondary: "#ffffff"
|
|
39
|
+
onTertiary: "#ffffff"
|
|
40
|
+
# ── The Architectural Ledger surface ladder ──
|
|
41
|
+
# White-paper edition 2026-09: canvas is pure #ffffff. Cards rest
|
|
42
|
+
# transparent (panel/canvas and shards show through) and take a fill
|
|
43
|
+
# only on hover. Panel contrast is near-neutral grey (#f4f4f4) — cream
|
|
44
|
+
# pulled ochre, blue-grey still read as slate; equal-RGB greys keep the
|
|
45
|
+
# ladder quiet. Stitch alabaster #fcf9f6 is deliberately not used
|
|
46
|
+
# (yellow cast on printed CV).
|
|
47
|
+
#
|
|
48
|
+
# The triad keeps the Ledger hues (slate / forest / ochre) with higher
|
|
49
|
+
# chroma — Stitch inspires vividness, it does not replace the system.
|
|
50
|
+
background: "#ffffff" # step 1 — canvas, white paper
|
|
51
|
+
backgroundSecondary: "#f4f4f4"
|
|
52
|
+
panelContrast: "#f4f4f4" # step 2 — panel / alternating band
|
|
53
|
+
surfaceCard: "transparent" # step 3 — card at rest (no fill)
|
|
54
|
+
surfaceCardContrast: "transparent" # step 3b — same on contrast panel
|
|
55
|
+
surfaceHigh: "#ebebeb" # step 4 — hover
|
|
56
|
+
surfaceHighContrast: "#e2e2e2" # step 4b — hover on contrast card
|
|
57
|
+
# ── The older Material surface ramp, still emitted and still used ──
|
|
58
|
+
surface: "#ffffff"
|
|
59
|
+
surfaceContainerLowest: "#ffffff"
|
|
60
|
+
surfaceContainerLow: "#f4f4f4"
|
|
61
|
+
surfaceContainer: "#ebebeb"
|
|
62
|
+
surfaceContainerHigh: "#e2e2e2"
|
|
63
|
+
surfaceContainerHighest: "#d8d8d8"
|
|
64
|
+
surfaceVariant: "#d8d8d8"
|
|
65
|
+
surfaceDim: "#d8d8d8"
|
|
66
|
+
panelGrey: "#f1f1f1" # color-mix(surfaceContainer 82%, background)
|
|
67
|
+
onSurface: "#000000"
|
|
68
|
+
onSurfaceVariant: "#5e5d59"
|
|
69
|
+
onBackground: "#000000"
|
|
70
|
+
outline: "#5c5c5c"
|
|
71
|
+
outlineVariant: "#b8b8b8" # every hairline is an opacity step of this
|
|
72
|
+
link: "#1c5a7a"
|
|
73
|
+
linkHover: "#2a6f92"
|
|
74
|
+
inversePrimary: "#a4c8da"
|
|
75
|
+
inverseSurface: "#303030"
|
|
76
|
+
inverseOnSurface: "#f2f2f2"
|
|
77
|
+
error: "#ba1a1a"
|
|
78
|
+
onError: "#ffffff"
|
|
79
|
+
errorContainer: "#ffdad6"
|
|
80
|
+
onErrorContainer: "#93000a"
|
|
81
|
+
resYoutube: "#c62828"
|
|
82
|
+
resPodcast: "#2e7d32"
|
|
83
|
+
resDoc: "#b45309"
|
|
84
|
+
chartProject: "#6fb387"
|
|
85
|
+
sidebarThumbnailBackground: "#ebebeb"
|
|
86
|
+
dark:
|
|
87
|
+
primary: "#9dc2d6"
|
|
88
|
+
secondary: "#8fc29f"
|
|
89
|
+
tertiary: "#d7d4a5"
|
|
90
|
+
primaryContainer: "#475760"
|
|
91
|
+
secondaryContainer: "#405748"
|
|
92
|
+
tertiaryContainer: "#67664f"
|
|
93
|
+
onPrimaryContainer: "#b6d1e0"
|
|
94
|
+
onSecondaryContainer: "#abd1b7"
|
|
95
|
+
onTertiaryContainer: "#e1dfbc"
|
|
96
|
+
onPrimary: "#0f1a22"
|
|
97
|
+
onSecondary: "#141a15"
|
|
98
|
+
onTertiary: "#1b1c12"
|
|
99
|
+
# ── The Architectural Ledger surface ladder ──
|
|
100
|
+
# Re-based 2026-09 in two moves. First, neutralised: the old values had G
|
|
101
|
+
# as the highest channel across the whole ladder, which read as an
|
|
102
|
+
# olive/green cast rather than black. Then widened: panel and hover keep
|
|
103
|
+
# the ~+4 / ~+6 L* steps; resting cards are transparent so shards and
|
|
104
|
+
# the panel show through. Vivid 2026-09: triad chroma raised.
|
|
105
|
+
background: "#0c0c0e" # step 1 — canvas, near-black, neutral
|
|
106
|
+
panelContrast: "#141418" # step 2 — panel / alternating band
|
|
107
|
+
surfaceCard: "transparent" # step 3 — card at rest (no fill)
|
|
108
|
+
surfaceHigh: "#26262b" # step 4 — hover, chip fill
|
|
109
|
+
# ── The older Material surface ramp, still emitted and still used ──
|
|
110
|
+
backgroundSecondary: "#141418"
|
|
111
|
+
surface: "#111114" # distinct from background in dark
|
|
112
|
+
surfaceContainerLowest: "#08080a"
|
|
113
|
+
surfaceContainerLow: "#17171b"
|
|
114
|
+
surfaceContainer: "#1f1f24"
|
|
115
|
+
surfaceContainerHigh: "#26262b"
|
|
116
|
+
surfaceContainerHighest: "#33342e" # documented here only — not a field in dark.yml
|
|
117
|
+
surfaceVariant: "#2e2e34"
|
|
118
|
+
surfaceDim: "#060607"
|
|
119
|
+
panelGrey: "#22231f" # documented here only — not a field in dark.yml
|
|
120
|
+
onSurface: "#ffffff"
|
|
121
|
+
onSurfaceVariant: "#b8b7ae"
|
|
122
|
+
onBackground: "#ffffff"
|
|
123
|
+
outline: "#93938d"
|
|
124
|
+
outlineVariant: "#45454c" # neutralised 2026-09; every hairline is an opacity step of this
|
|
125
|
+
link: "#a9cbdc"
|
|
126
|
+
linkHover: "#bfd7e4"
|
|
127
|
+
inversePrimary: "#255b75"
|
|
128
|
+
inverseSurface: "#e8e6e1"
|
|
129
|
+
inverseOnSurface: "#141413"
|
|
130
|
+
error: "#ffb4ab"
|
|
131
|
+
onError: "#690005"
|
|
132
|
+
errorContainer: "#93000a"
|
|
133
|
+
onErrorContainer: "#ffdad6"
|
|
134
|
+
resYoutube: "#de6461"
|
|
135
|
+
resPodcast: "#5dc462"
|
|
136
|
+
resDoc: "#e4b268"
|
|
137
|
+
chartProject: "#90c0a3"
|
|
138
|
+
sidebarThumbnailBackground: "#1f1f24"
|
|
139
|
+
|
|
140
|
+
# Borders are never a raw color. They are opacity steps of outlineVariant
|
|
141
|
+
# (gentle ladder) plus denser ink-frame mixes of onSurface for sketchbook
|
|
142
|
+
# cards (--border-ink / --border-ink-strong), identical token names in both
|
|
143
|
+
# themes.
|
|
144
|
+
borders:
|
|
145
|
+
gentleFaint: "color-mix(in srgb, {colors.*.outlineVariant} 22%, transparent)"
|
|
146
|
+
gentleSubtle: "color-mix(in srgb, {colors.*.outlineVariant} 32%, transparent)"
|
|
147
|
+
gentleDivider: "color-mix(in srgb, {colors.*.outlineVariant} 35%, transparent)"
|
|
148
|
+
gentle: "color-mix(in srgb, {colors.*.outlineVariant} 62%, transparent)"
|
|
149
|
+
gentleHover: "color-mix(in srgb, {colors.*.outlineVariant} 78%, transparent)"
|
|
150
|
+
ink: "color-mix(in srgb, {colors.*.onSurface} 72%, transparent)"
|
|
151
|
+
inkStrong: "color-mix(in srgb, {colors.*.onSurface} 88%, transparent)"
|
|
152
|
+
|
|
153
|
+
# ─────────────────────────────────────────────────────────────
|
|
154
|
+
# Typography. Root font-size is 110%, so 1rem ≈ 17.6px.
|
|
155
|
+
# Faces live once in `_data/themes/shared.yml` (not per colour theme).
|
|
156
|
+
# ─────────────────────────────────────────────────────────────
|
|
157
|
+
typography:
|
|
158
|
+
fontFamilies:
|
|
159
|
+
title: "'Cinzel Decorative', serif"
|
|
160
|
+
subtitle: "'Newsreader', serif"
|
|
161
|
+
label: "'Marcellus SC', serif"
|
|
162
|
+
body: "'Newsreader', sans-serif"
|
|
163
|
+
mono: "'Courier Prime', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace"
|
|
164
|
+
code: "'Courier Prime', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace"
|
|
165
|
+
rootFontSize: "110%"
|
|
166
|
+
|
|
167
|
+
pageTitle: # 404 only; the home hero no longer uses it
|
|
168
|
+
fontFamily: "{typography.fontFamilies.title}"
|
|
169
|
+
fontSize: "2.75rem"
|
|
170
|
+
fontSizeMd: "3.75rem"
|
|
171
|
+
fontWeight: 400
|
|
172
|
+
homeHeroTitle: # copy-column cqi; floor is the band title
|
|
173
|
+
fontFamily: "{typography.fontFamilies.title}"
|
|
174
|
+
fontSize: "clamp(var(--type-section), calc(100cqi * 0.92 / 14.5), var(--type-page-lg))"
|
|
175
|
+
fontSizeSm: "clamp(var(--type-section-lg), calc(100cqi * 0.92 / 14.5), var(--type-page-lg))" # ≥640px, same step as sectionTitle
|
|
176
|
+
fontWeight: 400
|
|
177
|
+
pageHeaderTitle: # listing pages
|
|
178
|
+
fontFamily: "{typography.fontFamilies.title}"
|
|
179
|
+
fontSize: "2.35rem"
|
|
180
|
+
fontSizeMd: "2.85rem"
|
|
181
|
+
fontWeight: 400
|
|
182
|
+
textTitle: # article / post detail title
|
|
183
|
+
fontFamily: "{typography.fontFamilies.subtitle}"
|
|
184
|
+
fontSize: "1.95rem"
|
|
185
|
+
fontSizeMd: "2.45rem"
|
|
186
|
+
fontWeight: 400
|
|
187
|
+
sectionTitle: # band headers, register rails, banners
|
|
188
|
+
fontFamily: "{typography.fontFamilies.title}"
|
|
189
|
+
fontSize: "1.75rem"
|
|
190
|
+
fontSizeSm: "2.05rem"
|
|
191
|
+
fontWeight: 400
|
|
192
|
+
cardTitle:
|
|
193
|
+
fontFamily: "{typography.fontFamilies.subtitle}"
|
|
194
|
+
fontSize: "1.4rem"
|
|
195
|
+
fontWeight: 400
|
|
196
|
+
subtitle:
|
|
197
|
+
fontFamily: "{typography.fontFamilies.subtitle}"
|
|
198
|
+
fontSize: "1.05rem"
|
|
199
|
+
fontWeight: 400
|
|
200
|
+
body:
|
|
201
|
+
fontFamily: "{typography.fontFamilies.body}"
|
|
202
|
+
fontSize: "1rem"
|
|
203
|
+
fontWeight: 300
|
|
204
|
+
lineHeight: 1.5
|
|
205
|
+
prose: # .article-content, long-form reading
|
|
206
|
+
fontFamily: "{typography.fontFamilies.body}"
|
|
207
|
+
fontSize: "1.2rem" # mobile prose base at 110% root
|
|
208
|
+
fontSizeMd: "1.25rem" # desktop prose base
|
|
209
|
+
sizeOffset: "-4pt … +4pt" # --prose-size-offset; 0 is this default
|
|
210
|
+
lineHeight: 1.6
|
|
211
|
+
dropCapLines: 2 # --drop-cap-lines; _config.yml drop_cap_lines
|
|
212
|
+
color: "{colors.*.onSurfaceVariant}"
|
|
213
|
+
paragraphMargin: "1.15em"
|
|
214
|
+
proseHeading2: # the display serif, with a rule above
|
|
215
|
+
fontFamily: "{typography.fontFamilies.title}"
|
|
216
|
+
fontSize: "1.7em" # 1.5em below 768px
|
|
217
|
+
fontWeight: 400
|
|
218
|
+
lineHeight: 1.2
|
|
219
|
+
borderTop: "1px solid {borders.gentleDivider}"
|
|
220
|
+
proseHeading3: # the label face, uppercase, primary
|
|
221
|
+
fontFamily: "{typography.fontFamilies.label}"
|
|
222
|
+
fontSize: "0.95em"
|
|
223
|
+
fontWeight: 400
|
|
224
|
+
letterSpacing: "0.1em"
|
|
225
|
+
textTransform: "uppercase"
|
|
226
|
+
color: "{colors.*.primary}"
|
|
227
|
+
proseHeading4:
|
|
228
|
+
fontFamily: "{typography.fontFamilies.body}"
|
|
229
|
+
fontSize: "1em"
|
|
230
|
+
fontWeight: 400
|
|
231
|
+
marginalia:
|
|
232
|
+
fontFamily: "{typography.fontFamilies.label}"
|
|
233
|
+
fontSize: "0.75rem"
|
|
234
|
+
fontWeight: 400
|
|
235
|
+
letterSpacing: "0.05em"
|
|
236
|
+
cardMeta:
|
|
237
|
+
fontFamily: "{typography.fontFamilies.label}"
|
|
238
|
+
fontSize: "0.8125rem"
|
|
239
|
+
fontWeight: 400
|
|
240
|
+
letterSpacing: "0.04em"
|
|
241
|
+
cardTag:
|
|
242
|
+
fontFamily: "{typography.fontFamilies.label}"
|
|
243
|
+
fontSize: "0.75rem"
|
|
244
|
+
fontWeight: 400
|
|
245
|
+
letterSpacing: "0.03em"
|
|
246
|
+
codeTechnical: # the fourth role — notation
|
|
247
|
+
fontFamily: "{typography.fontFamilies.mono}"
|
|
248
|
+
fontSize: "0.8125rem"
|
|
249
|
+
lineHeight: 1.45
|
|
250
|
+
fontVariantNumeric: "tabular-nums"
|
|
251
|
+
codeTechnicalSm:
|
|
252
|
+
fontFamily: "{typography.fontFamilies.mono}"
|
|
253
|
+
fontSize: "0.75rem"
|
|
254
|
+
lineHeight: 1.4
|
|
255
|
+
fontVariantNumeric: "tabular-nums"
|
|
256
|
+
code: # inline code inside prose
|
|
257
|
+
fontFamily: "{typography.fontFamilies.mono}"
|
|
258
|
+
fontSize: "0.85em"
|
|
259
|
+
|
|
260
|
+
# The apparatus inside an article descends from the prose in four steps, all
|
|
261
|
+
# in onSurfaceVariant. Every one of them is a sentence, which is why none
|
|
262
|
+
# takes the label or notation voice for its size.
|
|
263
|
+
proseQuote: # blockquote, `>` in Markdown
|
|
264
|
+
fontFamily: "{typography.fontFamilies.body}"
|
|
265
|
+
fontSize: "0.95rem" # identical to proseNote by intent
|
|
266
|
+
lineHeight: "{typography.body.lineHeight}"
|
|
267
|
+
plate: "surfaceContainerLow, 2px tertiary left rule"
|
|
268
|
+
proseNote: # .article-note, authored per article
|
|
269
|
+
fontFamily: "{typography.fontFamilies.body}"
|
|
270
|
+
fontSize: "0.95rem"
|
|
271
|
+
lineHeight: "{typography.body.lineHeight}"
|
|
272
|
+
plate: "surfaceCard, 3px tertiary bar"
|
|
273
|
+
proseCaption: # figcaption, and the lightbox caption
|
|
274
|
+
fontFamily: "{typography.fontFamilies.body}"
|
|
275
|
+
fontSize: "0.9rem"
|
|
276
|
+
lineHeight: 1.55
|
|
277
|
+
proseBibliography: # .article-references .bibliography li
|
|
278
|
+
fontFamily: "{typography.fontFamilies.mono}"
|
|
279
|
+
fontSize: "0.85rem"
|
|
280
|
+
lineHeight: 1.55
|
|
281
|
+
|
|
282
|
+
# ─────────────────────────────────────────────────────────────
|
|
283
|
+
# Shapes. Soft paper radius on cards; true pills stay circular.
|
|
284
|
+
# ─────────────────────────────────────────────────────────────
|
|
285
|
+
rounded:
|
|
286
|
+
none: "0"
|
|
287
|
+
card: "0" # --radius-card; square Borders Edition
|
|
288
|
+
full: "9999px" # footer contact discs, timeline dot key
|
|
289
|
+
tailwindScale: # remapped in assets/tailwind-play.js
|
|
290
|
+
DEFAULT: "0.125rem"
|
|
291
|
+
lg: "0.25rem"
|
|
292
|
+
xl: "0.5rem"
|
|
293
|
+
full: "0.75rem" # NOTE: not a pill
|
|
294
|
+
|
|
295
|
+
# The two canonical shard polygons, declared once in assets/css/base.css.
|
|
296
|
+
shards:
|
|
297
|
+
alpha: "polygon(26% 0, 100% 0, 78% 100%, 0 68%)" # --shard-alpha
|
|
298
|
+
beta: "polygon(0 0, 74% 18%, 100% 100%, 18% 84%)" # --shard-beta
|
|
299
|
+
opacity: "12%" # --cubist-shard-opacity
|
|
300
|
+
opacityStrong: "16%" # --cubist-shard-opacity-strong
|
|
301
|
+
utility: ".shard-accent / .shard-accent-beta"
|
|
302
|
+
suppressedBelow: "640px"
|
|
303
|
+
|
|
304
|
+
# ─────────────────────────────────────────────────────────────
|
|
305
|
+
# Spacing. A named scale in custom properties, in assets/css/base.css.
|
|
306
|
+
# Tailwind's default 0.25rem rungs are still in use in the Liquid chrome.
|
|
307
|
+
# ─────────────────────────────────────────────────────────────
|
|
308
|
+
spacing:
|
|
309
|
+
scale:
|
|
310
|
+
xs: "0.25rem" # --space-xs
|
|
311
|
+
sm: "0.5rem" # --space-sm
|
|
312
|
+
md: "1rem" # --space-md
|
|
313
|
+
lg: "1.5rem" # --space-lg
|
|
314
|
+
xl: "2rem" # --space-xl
|
|
315
|
+
"2xl": "3rem" # --space-2xl
|
|
316
|
+
"3xl": "4.5rem" # --space-3xl
|
|
317
|
+
gutter: "2rem" # --gutter
|
|
318
|
+
gutterMobile: "1rem" # --gutter-mobile
|
|
319
|
+
tailwindRungsInUse: ["0", "0.5", "1", "1.5", "2", "2.5", "3", "3.5", "4", "5", "6", "8", "10", "12", "16"]
|
|
320
|
+
|
|
321
|
+
layoutTokens:
|
|
322
|
+
shellMaxWidth: "80rem" # Tailwind max-w-7xl
|
|
323
|
+
navPaddingX: ["1.5rem", "2rem", "2.5rem"] # base / sm / md
|
|
324
|
+
listingPaddingX: ["2rem", "4rem"] # base / md — px-8 md:px-16
|
|
325
|
+
homeBandPaddingX: ["{spacing.gutterMobile}", "{spacing.gutter}"] # base / md
|
|
326
|
+
homeBandPaddingY: ["3rem", "4.5rem"] # base / md — space-2xl / 3xl
|
|
327
|
+
readingMeasure: "45rem" # --reading-measure; every page, prose and media alike
|
|
328
|
+
legacyContainer: "64rem"
|
|
329
|
+
breakpoints:
|
|
330
|
+
sm: "640px"
|
|
331
|
+
md: "768px"
|
|
332
|
+
lg: "1024px"
|
|
333
|
+
xl: "1280px"
|
|
334
|
+
ownBreakpoints: ["639px", "767px", "900px"] # max-width queries in ledger CSS
|
|
335
|
+
|
|
336
|
+
# ─────────────────────────────────────────────────────────────
|
|
337
|
+
# Components. The block library first — it renders most of the site.
|
|
338
|
+
# Source of truth: _includes/content-runtime/43-blocks.html for the markup,
|
|
339
|
+
# assets/css/base.css ("Architectural Ledger blocks") for the styling.
|
|
340
|
+
# ─────────────────────────────────────────────────────────────
|
|
341
|
+
components:
|
|
342
|
+
langPill: # renderLangPill(language)
|
|
343
|
+
typography: "{typography.fontFamilies.label} 0.725rem / 0.08em uppercase"
|
|
344
|
+
padding: "0.1rem 0.4rem"
|
|
345
|
+
border: "1px solid color-mix(in srgb, currentColor 35%, transparent)"
|
|
346
|
+
color: "{colors.*.primary} for EN, {colors.*.secondary} for PT"
|
|
347
|
+
backingField: "language"
|
|
348
|
+
chip: # renderTagChips(tags)
|
|
349
|
+
typography: "{typography.fontFamilies.label} 0.725rem / 0.08em uppercase"
|
|
350
|
+
padding: "0.15rem 0.45rem"
|
|
351
|
+
backgroundColor: "{colors.*.surfaceHigh}"
|
|
352
|
+
textColor: "{colors.*.onSurfaceVariant}"
|
|
353
|
+
hoverBackgroundColor: "{colors.*.surfaceContainerHigh}"
|
|
354
|
+
rounded: "{rounded.none}"
|
|
355
|
+
backingField: "tags"
|
|
356
|
+
metaStrip: # renderMetaStrip([parts])
|
|
357
|
+
typography: "{typography.codeTechnical}"
|
|
358
|
+
letterSpacing: "0.06em"
|
|
359
|
+
textTransform: "uppercase"
|
|
360
|
+
gap: "{spacing.scale.sm}"
|
|
361
|
+
separator: "/ in outlineVariant, only between surviving parts"
|
|
362
|
+
cta: # renderCtaLink(href, label)
|
|
363
|
+
typography: "{typography.fontFamilies.label} 0.8rem / 0.1em uppercase"
|
|
364
|
+
textColor: "{colors.*.primary}"
|
|
365
|
+
hoverTextColor: "{colors.*.linkHover}"
|
|
366
|
+
icon: "→ (U+2192) text glyph, aria-hidden, translateX(4px) on hover"
|
|
367
|
+
sectionBanner: # renderSectionBanner({kicker, title, note})
|
|
368
|
+
kicker: "{typography.codeTechnical}, 0.12em, onSurfaceVariant"
|
|
369
|
+
title: "{typography.sectionTitle}"
|
|
370
|
+
note: "{typography.codeTechnical}, 0.08em, onSurfaceVariant"
|
|
371
|
+
marginBottom: "{spacing.scale.xl}"
|
|
372
|
+
ledgerRow: # renderLedgerRow(item)
|
|
373
|
+
backgroundColor: "{colors.*.surfaceCard}"
|
|
374
|
+
hoverBackgroundColor: "{colors.*.surfaceHigh}"
|
|
375
|
+
padding: "{spacing.scale.lg}"
|
|
376
|
+
rounded: "{rounded.none}"
|
|
377
|
+
boxShadow: "none"
|
|
378
|
+
titleHoverColor: "{colors.*.primary}"
|
|
379
|
+
ledgerCard: # renderArchiveCard(item)
|
|
380
|
+
backgroundColor: "{colors.*.surfaceCard}"
|
|
381
|
+
hoverBackgroundColor: "{colors.*.surfaceHigh}"
|
|
382
|
+
borderColor: "{borders.gentleSubtle}"
|
|
383
|
+
borderWidth: "1px"
|
|
384
|
+
padding: "{spacing.scale.lg}"
|
|
385
|
+
rounded: "{rounded.none}"
|
|
386
|
+
footBorderTop: "1px solid {borders.gentleDivider}"
|
|
387
|
+
systemCard: # renderSystemCard(item)
|
|
388
|
+
inherits: "{components.ledgerCard}"
|
|
389
|
+
extras: "poster, label/status meta strip, repository + article + external links"
|
|
390
|
+
poster: # renderPosterMedia(src, alt)
|
|
391
|
+
aspectRatio: "16 / 9"
|
|
392
|
+
objectFit: "cover"
|
|
393
|
+
backgroundColor: "{colors.*.surfaceContainer}"
|
|
394
|
+
filter: "saturate(0.92) contrast(1.02)"
|
|
395
|
+
hoverFilter: "saturate(1.05) contrast(1)"
|
|
396
|
+
hoverTransform: "scale(1.02)" # suppressed under prefers-reduced-motion
|
|
397
|
+
videoBehaviour: "controls, preload=none — never autoplays"
|
|
398
|
+
pageHeaderStandfirst:
|
|
399
|
+
maxWidth: "{layoutTokens.readingMeasure}"
|
|
400
|
+
fontSize: "1.05rem"
|
|
401
|
+
lineHeight: 1.5 # --leading-body
|
|
402
|
+
backingField: "standfirst on the page header JSON"
|
|
403
|
+
buttonSolid: # .home-cta--solid, .writing-lead-cta
|
|
404
|
+
backgroundColor: "{colors.*.primary}"
|
|
405
|
+
textColor: "{colors.*.onPrimary}"
|
|
406
|
+
padding: "0.85rem {spacing.scale.lg}"
|
|
407
|
+
rounded: "{rounded.none}"
|
|
408
|
+
buttonOutline: # .home-cta--outline
|
|
409
|
+
backgroundColor: "{colors.*.surfaceCard}"
|
|
410
|
+
borderColor: "{borders.gentle}"
|
|
411
|
+
textColor: "{colors.*.primary}"
|
|
412
|
+
hoverBackgroundColor: "{colors.*.surfaceHigh}"
|
|
413
|
+
hoverBorderColor: "{colors.*.primary}"
|
|
414
|
+
filterChip: # .listing-filter-chip, shared controller
|
|
415
|
+
backgroundColor: "{colors.*.background}"
|
|
416
|
+
borderColor: "{borders.gentle}"
|
|
417
|
+
padding: "0.35rem 0.65rem"
|
|
418
|
+
rounded: "{rounded.none}"
|
|
419
|
+
activeBorderColor: "{colors.*.secondary}"
|
|
420
|
+
activeBackgroundColor: "color-mix(in srgb, {colors.*.secondary} 8%, {colors.*.background})"
|
|
421
|
+
input:
|
|
422
|
+
backgroundColor: "{colors.*.background}"
|
|
423
|
+
borderColor: "{borders.gentleSubtle}"
|
|
424
|
+
padding: "0.5rem 0.65rem"
|
|
425
|
+
rounded: "{rounded.none}"
|
|
426
|
+
focusOutline: "2px solid color-mix(in srgb, {colors.*.secondary} 45%, transparent)"
|
|
427
|
+
popover: # .listing-filter-panel, nav menu
|
|
428
|
+
backgroundColor: "{colors.*.surface}"
|
|
429
|
+
borderColor: "{borders.gentle}"
|
|
430
|
+
rounded: "{rounded.none}"
|
|
431
|
+
boxShadow: "0 12px 32px color-mix(in srgb, {colors.*.onSurface} 12%, transparent)"
|
|
432
|
+
|
|
433
|
+
motion:
|
|
434
|
+
stateChange: "0.15s ease" # 23 declarations — the de facto standard
|
|
435
|
+
legacyChrome: "0.2s ease" # 8 declarations, pre-redesign chrome
|
|
436
|
+
mediaFilter: "0.4s ease"
|
|
437
|
+
mediaTransform: ["0.6s ease", "0.7s ease"]
|
|
438
|
+
lightbox: ["160ms ease-out", "180ms ease-out"]
|
|
439
|
+
readingProgress: "scroll(root block) timeline, beNewsreader @supports"
|
|
440
|
+
reducedMotion: "5 rules — see Motion below"
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
# DESIGN.md — Brazilian Ledger
|
|
444
|
+
|
|
445
|
+
This file **describes the site as it is built today**, in both themes. It is a
|
|
446
|
+
record, not an aspiration: it says what the browser actually renders, not what
|
|
447
|
+
a mockup or an earlier document intended. Where the built site and a mockup
|
|
448
|
+
disagree, the built site wins and this file records the built site.
|
|
449
|
+
|
|
450
|
+
Source of truth for the values above:
|
|
451
|
+
|
|
452
|
+
| Concern | File |
|
|
453
|
+
| :--- | :--- |
|
|
454
|
+
| Palettes | [`_data/themes/light.yml`](_data/themes/light.yml), [`_data/themes/dark.yml`](_data/themes/dark.yml) |
|
|
455
|
+
| Shared type / motion | [`_data/themes/shared.yml`](_data/themes/shared.yml) — fonts and non-colour tokens used by both palettes |
|
|
456
|
+
| Token emission | [`_includes/theme/theme-vars-block.liquid`](_includes/theme/theme-vars-block.liquid) |
|
|
457
|
+
| Tailwind binding | [`_includes/theme/tailwind-theme-colors.json.liquid`](_includes/theme/tailwind-theme-colors.json.liquid), [`_includes/theme/tailwind-theme-fonts.json.liquid`](_includes/theme/tailwind-theme-fonts.json.liquid), [`assets/tailwind-play.js`](assets/tailwind-play.js) |
|
|
458
|
+
| Type, spacing, shards, shared components | [`assets/css/base.css`](assets/css/base.css) |
|
|
459
|
+
| **Block library (markup)** | [`_includes/content-runtime/43-blocks.html`](_includes/content-runtime/43-blocks.html) |
|
|
460
|
+
| Per-page styles | [`assets/css/ledger/`](assets/css/ledger/) — `home`, `writing`, `rascunhos`, `projects`, `sources`, `detail`, `cv` |
|
|
461
|
+
| Long-form prose (superseded in part) | [`assets/css/article.css`](assets/css/article.css) |
|
|
462
|
+
| Print | [`assets/css/cv-print.css`](assets/css/cv-print.css) |
|
|
463
|
+
| Shell chrome | [`_layouts/shell.html`](_layouts/shell.html) |
|
|
464
|
+
|
|
465
|
+
A note on names. The palette files still call the system the **Brazilian
|
|
466
|
+
Ledger**; the component layer the redesign added calls itself the
|
|
467
|
+
**Architectural Ledger**. They are the same design system. Nothing in the code
|
|
468
|
+
reconciles the two names.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Overview
|
|
473
|
+
|
|
474
|
+
The site is a personal editorial archive: CV, long-form articles in English,
|
|
475
|
+
`rascunhos` (drafts) in Portuguese, projects, a bibliography of sources, and
|
|
476
|
+
an interactive network of ideas. The register is **structural rigour with
|
|
477
|
+
artistic soul** — a draughtsman's sheet rather than a SaaS landing page.
|
|
478
|
+
|
|
479
|
+
Four ideas carry it:
|
|
480
|
+
|
|
481
|
+
1. **Framed paper.** Cards and plates carry a thin, clear ink frame and stay
|
|
482
|
+
square (`--radius-card: 0`). Depth still comes from the surface ladder
|
|
483
|
+
and borders, not drop shadows. Drafting corner brackets and empty
|
|
484
|
+
crosshairs are decoration only — never labels. Italic section-title
|
|
485
|
+
halves and the hero family name take a marker highlight wash.
|
|
486
|
+
2. **Paper, not glass.** No drop shadows on content. Depth comes from the
|
|
487
|
+
four-step surface ladder and from hairline / ink borders.
|
|
488
|
+
3. **A ledger voice for data.** Dates, spans, classifications, hosts, counts
|
|
489
|
+
and reference numbers are set in monospace with tabular figures. This is
|
|
490
|
+
the fourth type role, and it is what makes the site read as a register
|
|
491
|
+
rather than a blog.
|
|
492
|
+
4. **Cubist shards, rationed.** Two canonical polygons, one utility class,
|
|
493
|
+
five uses of it plus one hand-rolled pseudo-element. The motif is a
|
|
494
|
+
signature now rather than a texture. The logo repeats it as three coloured
|
|
495
|
+
facets.
|
|
496
|
+
|
|
497
|
+
**Theme switching.** `<html data-theme="light|dark">` is set before paint by
|
|
498
|
+
[`_includes/theme/shell-theme-boot.html`](_includes/theme/shell-theme-boot.html),
|
|
499
|
+
persisted under `localStorage["site-theme"]`, default `system`. Light is
|
|
500
|
+
additionally bound to `:root`, so it is the no-JS fallback. Both palettes emit
|
|
501
|
+
the *same token names*, so every component is theme-agnostic.
|
|
502
|
+
|
|
503
|
+
**How pages are built.** Listings and detail views are not Liquid. They render
|
|
504
|
+
in the browser from `_content_json` via JavaScript string templates in
|
|
505
|
+
[`_includes/content-runtime/`](_includes/content-runtime/), and the shared
|
|
506
|
+
block library is the bottom of that stack. Only the shell, the page headers,
|
|
507
|
+
the CV register frames, the timeline and the network graph are server-rendered.
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## The block library
|
|
512
|
+
|
|
513
|
+
This is the most important thing to understand about the current build.
|
|
514
|
+
**Layouts no longer style their own cards.** Nine renderers in
|
|
515
|
+
[`_includes/content-runtime/43-blocks.html`](_includes/content-runtime/43-blocks.html)
|
|
516
|
+
produce every card, row, chip, pill, meta strip and CTA on the site; their CSS
|
|
517
|
+
lives in one block in [`assets/css/base.css`](assets/css/base.css) under
|
|
518
|
+
*Architectural Ledger blocks*. All nine are re-exported onto
|
|
519
|
+
`window.ContentRuntime` by `90-bootstrap.html`.
|
|
520
|
+
|
|
521
|
+
| Renderer | Renders | Root class | Backing field |
|
|
522
|
+
| :--- | :--- | :--- | :--- |
|
|
523
|
+
| `renderLangPill(language)` | a bordered `EN` or `PT` pill | `.lang-pill` | `language` |
|
|
524
|
+
| `renderTagChips(tags, {hrefFor, limit})` | row of filled chips, optionally links | `.ledger-chip-row` | `tags` |
|
|
525
|
+
| `renderMetaStrip([parts], {className})` | mono ledger line with `/` separators | `.ledger-meta.code-technical` | whatever the caller passes |
|
|
526
|
+
| `renderCtaLink(href, label)` | label + `→`, arrow slides on hover | `.ledger-cta` | `url` / an external link field |
|
|
527
|
+
| `renderSectionBanner({kicker, title, note})` | kicker over a section title, note right | `.ledger-section-banner` | static labels |
|
|
528
|
+
| `renderLedgerRow(item, opts)` | dense horizontal entry | `.ledger-row` | `title`, `date`, `language`, `tags`, preview — `opts.metaParts` replaces the date + pill strip |
|
|
529
|
+
| `renderArchiveCard(item, opts)` | vertical grid card | `.ledger-card` | `title`, `date`, `language`, `tags`, preview |
|
|
530
|
+
| `renderSystemCard(item, opts)` | project card with poster | `.ledger-system-card` | `project`, `thumbnail`, `label`, `status`, `tagline`, `tags`, `github`, `article_url`, `external_url` |
|
|
531
|
+
| `renderPosterMedia(src, alt)` | image, or video when the URL ends `.mp4`/`.webm`/`.mov` | `.ledger-poster` | `thumbnail` |
|
|
532
|
+
|
|
533
|
+
`blockFields(item)` is the shared reader underneath them. It takes
|
|
534
|
+
`front_matter.title | date | last_updated | language | tags`, falling back to
|
|
535
|
+
the item's own keys, and calls `listingPreview(item)` for the summary
|
|
536
|
+
(`description` → `excerpt` → an extract from `body_markdown`).
|
|
537
|
+
|
|
538
|
+
### The house rule
|
|
539
|
+
|
|
540
|
+
> **A block renders nothing when its field is absent.**
|
|
541
|
+
|
|
542
|
+
This is the project's central invariant and it is enforced at the renderer, not
|
|
543
|
+
at the call site. `renderLangPill` returns an empty string for an undeclared
|
|
544
|
+
language. `renderTagChips` returns an empty string for an empty array.
|
|
545
|
+
`renderMetaStrip` drops empty parts *and the separators that would have sat
|
|
546
|
+
beside them*, so a missing date never leaves a dangling slash.
|
|
547
|
+
`renderSectionBanner` returns nothing without a title. `renderCtaLink` returns
|
|
548
|
+
nothing without both an href and a label.
|
|
549
|
+
|
|
550
|
+
Nothing in the library derives a value, counts anything, or substitutes a
|
|
551
|
+
placeholder. There are no reading times, word counts, entry numbers, sequence
|
|
552
|
+
counters, status pills, version badges or fabricated metrics anywhere in the
|
|
553
|
+
build, and an absent field is never rendered as a dash, a placeholder string
|
|
554
|
+
or an "Unknown".
|
|
555
|
+
|
|
556
|
+
This matters because the corpus is uneven: of 67 exported entries, **29
|
|
557
|
+
declare a language**. The pill is therefore absent on more than half the site,
|
|
558
|
+
by design. A card with no tags is a card with no chip row, not a card with an
|
|
559
|
+
empty chip row.
|
|
560
|
+
|
|
561
|
+
Two listings go further and suppress the pill even where the field is set,
|
|
562
|
+
because every entry in them shares one language and the pill would only be
|
|
563
|
+
marking which entries declared the field: the rascunhos ledger
|
|
564
|
+
(`_layouts/blog.html`, all Portuguese) and the writing index
|
|
565
|
+
(`_layouts/articles.html`, all English). It still renders on the home page
|
|
566
|
+
bands, on the sources index — where French, English and Portuguese genuinely
|
|
567
|
+
mix — and on the article detail view.
|
|
568
|
+
|
|
569
|
+
`renderPosterMedia` extends the rule to runtime failure: a thumbnail that
|
|
570
|
+
404s removes its own `.ledger-poster` wrapper via an inline `onerror`, so a
|
|
571
|
+
dead third-party image leaves no empty plate beNewsreader.
|
|
572
|
+
|
|
573
|
+
### Where the library is not yet the only source
|
|
574
|
+
|
|
575
|
+
Recorded honestly, because the rule above is the design goal and these are the
|
|
576
|
+
gaps:
|
|
577
|
+
|
|
578
|
+
* **`_layouts/resources.html`** hand-builds the whole source card
|
|
579
|
+
(`article.ledger-card.source-card`) rather than calling `renderArchiveCard`.
|
|
580
|
+
It also forks `renderLangPill` into a local `languagePill()` to get a third
|
|
581
|
+
`.lang-pill--other` variant for sources that are neither English nor
|
|
582
|
+
Portuguese, and forks the pagination controls.
|
|
583
|
+
* **`_layouts/cv.html`** carries three local card shapes — `.cv-entry`,
|
|
584
|
+
`.cv-language`, `.cv-product`. `renderProduct` in particular is structurally
|
|
585
|
+
an archive card built from scratch.
|
|
586
|
+
* **`_layouts/blog.html`** builds its own thumbnail plate
|
|
587
|
+
(`.rascunhos-row-media`) instead of calling `renderPosterMedia`, giving the
|
|
588
|
+
site two poster implementations with different failure behaviour (an inline
|
|
589
|
+
`onerror` attribute versus an attached `error` listener). It also re-parents
|
|
590
|
+
the shared row's chip row into the head and deletes `.ledger-row-foot`.
|
|
591
|
+
* **`_layouts/home.html`** wraps shared leaves in a local `.home-featured`
|
|
592
|
+
shape, and hand-writes its hero CTAs and four section banners in Liquid —
|
|
593
|
+
class-for-class identical to `renderCtaLink` and `renderSectionBanner`, but
|
|
594
|
+
duplicated, because those renderers are JavaScript.
|
|
595
|
+
* **`_layouts/articles.html`** wraps shared leaves in a local `.writing-lead`.
|
|
596
|
+
Its rows now pass their strip in through `renderLedgerRow`'s `metaParts`
|
|
597
|
+
(date, then a `span.writing-revised` when the entry has been rewritten)
|
|
598
|
+
rather than grafting the revision onto `.ledger-meta` after construction.
|
|
599
|
+
* **`_layouts/text.html`** duplicates `renderTagChips` and `renderLangPill` in
|
|
600
|
+
Liquid. This one is structural: `text.html` is the studio-only server-rendered
|
|
601
|
+
path and cannot call the JavaScript renderers. Its markup is kept
|
|
602
|
+
byte-compatible with theirs on purpose, so
|
|
603
|
+
[`assets/css/ledger/detail.css`](assets/css/ledger/detail.css) styles both.
|
|
604
|
+
|
|
605
|
+
[`_layouts/projects.html`](_layouts/projects.html) is the clean case: one
|
|
606
|
+
`renderSystemCard` call and nothing else.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Colours
|
|
611
|
+
|
|
612
|
+
### Brand triad
|
|
613
|
+
|
|
614
|
+
Three roles, configured per theme, used for **text, borders, rules, links,
|
|
615
|
+
solid call-to-action fills and cubist shards**. The redesign added the one
|
|
616
|
+
exception to the old rule that brand hues are never a fill: the primary
|
|
617
|
+
call-to-action (`.home-cta--solid`, `.writing-lead-cta`, the active filter tab
|
|
618
|
+
on the writing index) is a solid `primary` block with `onPrimary` text.
|
|
619
|
+
|
|
620
|
+
| Role | Light | Dark | Used for |
|
|
621
|
+
| :--- | :--- | :--- | :--- |
|
|
622
|
+
| Primary | `#255b75` ice slate | `#9dc2d6` | Links, family name, CTAs, active tab, `h3` in prose, reading-progress bar, register rules, text selection |
|
|
623
|
+
| Secondary | `#33774c` forest green | `#8fc29f` | Status labels, licence and marginalia chrome, footer, active filter chip |
|
|
624
|
+
| Tertiary | `#6e6c3d` ochre gold | `#d7d4a5` | Detail-page meta, print dates, the third card rule |
|
|
625
|
+
|
|
626
|
+
Contrast against each theme's canvas (re-measured after the 2026-09 vivid pass):
|
|
627
|
+
|
|
628
|
+
| Role | Light vs `#ffffff` | Dark vs `#0c0c0e` |
|
|
629
|
+
| :--- | :--- | :--- |
|
|
630
|
+
| Primary | 7.42 : 1 | 10.35 : 1 |
|
|
631
|
+
| Secondary | 5.41 : 1 | 9.66 : 1 |
|
|
632
|
+
| Tertiary | 5.42 : 1 | 12.89 : 1 |
|
|
633
|
+
|
|
634
|
+
All pass WCAG AA for body text on the canvas. `onPrimary` on `primary` stays
|
|
635
|
+
white-on-slate in light and dark-ink-on-light-slate in dark.
|
|
636
|
+
|
|
637
|
+
The 2026-09 vivid pass raises chroma on the existing Ledger hues rather than
|
|
638
|
+
adopting Stitch pigment hexes. In light the triad still separates mainly by
|
|
639
|
+
hue; in dark `tertiary` remains the brightest accent after body copy.
|
|
640
|
+
|
|
641
|
+
### The surface ladder
|
|
642
|
+
|
|
643
|
+
The redesign replaced the two-tone band alternation with a four-step ladder.
|
|
644
|
+
Both palettes emit the same three new tokens on top of `background`:
|
|
645
|
+
|
|
646
|
+
| Step | Token | Light | Dark | Used for |
|
|
647
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
648
|
+
| 1 — canvas | `--color-background` | `#ffffff` | `#0c0c0e` | page, default home band |
|
|
649
|
+
| 2 — panel | `--color-panel-contrast` | `#f4f4f4` | `#141418` | alternating bands, filter decks, CV dossier, writing lead, detail sidebar heading, marginalia panels |
|
|
650
|
+
| 3 — card | `--color-surface-card` | `#ffffff` | `#0c0c0e` | every `.ledger-row`, `.ledger-card`, `.ledger-system-card` at rest (solid background matching ambient layer; masks ambient background grid/patterns) |
|
|
651
|
+
| 4 — hover | `--color-surface-high` | `#ebebeb` | `#26262b` | row and card hover, chip fill |
|
|
652
|
+
|
|
653
|
+
Cards sit with solid surface color matching their parent plane (`#ffffff` canvas / `#f4f4f4` contrast band in light; `#0c0c0e` canvas / `#141418` contrast band in dark) so that ambient mesh and background grid patterns do not bleed through card content. Hover rungs lift via `--color-surface-high`.
|
|
654
|
+
|
|
655
|
+
### The older Material ramp
|
|
656
|
+
|
|
657
|
+
The eight-step Material surface ramp (`surfaceContainerLowest` through
|
|
658
|
+
`surfaceVariant`, plus `panelGrey`) is still emitted and still used — by the
|
|
659
|
+
nav, the footer, the filter popover, the prose `pre`/`code`/`blockquote` fills,
|
|
660
|
+
the timeline rails and the prev/next panels. It sits beside the ladder rather
|
|
661
|
+
than underneath it, and the two overlap: `surfaceCard` and
|
|
662
|
+
`surfaceContainerLow` differ by 1.037 : 1 in light and 1.014 : 1 in dark.
|
|
663
|
+
Two token families name nearly the same colour.
|
|
664
|
+
|
|
665
|
+
### Borders
|
|
666
|
+
|
|
667
|
+
Never a raw colour. Five opacity steps of `outlineVariant`, shared by both
|
|
668
|
+
themes: `gentleFaint` 22%, `gentleSubtle` 32%, `gentleDivider` 35%, `gentle`
|
|
669
|
+
62%, `gentleHover` 78%.
|
|
670
|
+
|
|
671
|
+
### Semantic and media colours
|
|
672
|
+
|
|
673
|
+
`error` / `errorContainer`, plus three resource-type accents that lighten in
|
|
674
|
+
dark (`resYoutube` `#c62828` → `#ef5350`, `resPodcast` `#2e7d32` → `#66bb6a`,
|
|
675
|
+
`resDoc` `#b45309` → `#ffb74d`). Note that the sources page does **not** use
|
|
676
|
+
them: `assets/css/ledger/sources.css` colours its medium labels and filter
|
|
677
|
+
swatches from the brand triad instead (`primary` for video, `secondary` for
|
|
678
|
+
audio, `tertiary` for documents, `outline` for websites), and every label
|
|
679
|
+
carries its own word so the hue never has to carry the reading alone.
|
|
680
|
+
|
|
681
|
+
The CV timeline carries the one genuinely categorical scale in the build, in
|
|
682
|
+
`_data/themes/*.yml` under `timeline:` and emitted as `--timeline-employment`
|
|
683
|
+
… `--timeline-post`. Six kinds of event are drawn at once and have to be told
|
|
684
|
+
apart, which three inks cannot do — before this existed, employment shared
|
|
685
|
+
`primary` with article and product shared `tertiary` with post. Hues are
|
|
686
|
+
spread roughly 18 / 42 / 140 / 168 / 200 / 245 and desaturated to stay in the
|
|
687
|
+
editorial register; dark lifts each in lightness rather than reusing the light
|
|
688
|
+
values.
|
|
689
|
+
|
|
690
|
+
It is held outside `colors:` on purpose. A `.page-accent--*` class rebinds
|
|
691
|
+
`--color-primary`, and the CV reads green — a chart drawn from the rebound
|
|
692
|
+
triad comes out one hue, which is a chart that cannot be read. **A chart's
|
|
693
|
+
palette is not the page's accent.** The lane tokens are also what the legend
|
|
694
|
+
swatches paint from, so the key cannot drift from the thing it explains.
|
|
695
|
+
|
|
696
|
+
### Terminal captures
|
|
697
|
+
|
|
698
|
+
The one palette on the site that does not follow the theme. An ` ```ansi `
|
|
699
|
+
fence in article prose renders a real terminal capture in colour, and the
|
|
700
|
+
sixteen base colours plus the ground are stated outright in
|
|
701
|
+
`assets/css/article.css` rather than drawn from the ladder:
|
|
702
|
+
|
|
703
|
+
| Property | Value | Note |
|
|
704
|
+
| :--- | :--- | :--- |
|
|
705
|
+
| `--ansi-bg` | `#12130f` | Fixed in both themes |
|
|
706
|
+
| `--ansi-fg` | `#d3d7cf` | Tango's white |
|
|
707
|
+
| `--ansi-fg-bold` | `#eeeeec` | Bold with no colour set |
|
|
708
|
+
| `--ansi-fg-dim` | `#9aa09a` | Resting copy control, 4.9 : 1 on the bar |
|
|
709
|
+
| `--ansi-0` … `--ansi-15` | Tango | Ubuntu's terminal palette |
|
|
710
|
+
|
|
711
|
+
Two reasons it is an exception rather than an oversight. ANSI colours are
|
|
712
|
+
defined against a dark terminal — bright yellow (`--ansi-11`, `#fce94f`) is
|
|
713
|
+
1.2 : 1 on the light palette's white canvas and simply disappears. And a
|
|
714
|
+
capture is a picture of something: it brings its own surface the way a
|
|
715
|
+
photograph brings its own light, and repainting it per theme would be
|
|
716
|
+
repainting the subject.
|
|
717
|
+
|
|
718
|
+
Tango specifically, because it is what Ubuntu's terminal ships, and the one
|
|
719
|
+
capture the site currently publishes — the `neofetch` block in the WSL
|
|
720
|
+
article — was drawn with it. Indices past 15 are the 6×6×6 cube and the
|
|
721
|
+
24-step grey ramp; those are arithmetic in the standard and are computed in
|
|
722
|
+
`_includes/content-runtime/25-code-blocks.html` rather than tabulated here.
|
|
723
|
+
|
|
724
|
+
Bold maps to the bright half of the palette in the renderer, not in CSS: it
|
|
725
|
+
is a terminal behaviour rather than a typographic one, and every tool that
|
|
726
|
+
paints a bold red means the bright one.
|
|
727
|
+
|
|
728
|
+
The type is `0.68rem / 1.15`, and both terms are set on the `pre` rather than
|
|
729
|
+
on the `code` inside it — `code` is inline, so the block establishes the line
|
|
730
|
+
boxes and a leading set on the `code` loses to the block's strut. 1.15 is a
|
|
731
|
+
terminal cell: VTE, which Ubuntu's terminal is built on, takes its row height
|
|
732
|
+
from the font's ascent plus descent and adds nothing, landing between 1.16 and
|
|
733
|
+
1.20 for the faces it ships. This is the one place on the site where prose
|
|
734
|
+
leading is actively wrong rather than merely loose — box drawing made of `s`,
|
|
735
|
+
`o` and `/` stops being a shape at anything near it.
|
|
736
|
+
|
|
737
|
+
---
|
|
738
|
+
|
|
739
|
+
## Typography
|
|
740
|
+
|
|
741
|
+
Owner stack (2026-10-01): **Cinzel Decorative** for *page / display* titles,
|
|
742
|
+
**Newsreader** for body and content titles (**Light 300** / **SemiBold 600**),
|
|
743
|
+
**Newsreader** for subtitle / standfirst roles that still use `--font-subtitle`,
|
|
744
|
+
**Marcellus SC** for labels / marginalia, **Courier Prime** for notation and
|
|
745
|
+
code. Declared once in `_data/themes/shared.yml` — light and dark only swap
|
|
746
|
+
colour. Root **110%**.
|
|
747
|
+
Titles are **+2pt** over the Stitch rem steps; chrome (meta, dek,
|
|
748
|
+
labels, nav, CTAs, mono) stays on the bare rem steps so dates, pills, and
|
|
749
|
+
“Read article” links do not outgrow the prose. Detail prose is **1.2rem** below 1024px (about **21.1px**) and
|
|
750
|
+
**1.25rem** from there (about **22px**). A reader can step that default
|
|
751
|
+
by −4…+4 pt (`--prose-size-offset`) from a compact dropdown.
|
|
752
|
+
|
|
753
|
+
Print CV keeps its own Lora / Overlock path in `cv-print.css` (untouched).
|
|
754
|
+
|
|
755
|
+
Type sizes live as `--type-*` tokens on `html` in `base.css`, mapped from the
|
|
756
|
+
Stitch Borders scale (§19.4 of reimagined-borders). Page CSS must consume
|
|
757
|
+
those tokens — no one-off rem sizes for roles that already have a step.
|
|
758
|
+
|
|
759
|
+
| Role | Family | Weight | Size token |
|
|
760
|
+
| :--- | :--- | :--- | :--- |
|
|
761
|
+
| Display | Cinzel Decorative | 400 | `--type-display` / `-lg` |
|
|
762
|
+
| Page H1 | Cinzel Decorative | 400 | `--type-page` / `-lg` |
|
|
763
|
+
| Section | Cinzel Decorative | 400 | `--type-section` / `-lg` |
|
|
764
|
+
| Detail title | Newsreader | 600 | `--type-title` / `-lg` |
|
|
765
|
+
| Card / row title | Newsreader | 600 | `--type-card` / `-lg` |
|
|
766
|
+
| Prose | Newsreader | 300 | `--type-prose` |
|
|
767
|
+
| UI body | Newsreader | 300 | `--type-body` |
|
|
768
|
+
| Detail standfirst | Newsreader | 300 | `--type-subtitle` (= body − 1pt) |
|
|
769
|
+
| Dek / masthead tags | Newsreader | 300 | `--type-dek` |
|
|
770
|
+
| Meta / dates / kickers | Newsreader | 300 | `--type-meta` |
|
|
771
|
+
| Chips / nav / SC | Marcellus SC | 400 | `--type-label` / `-lg` |
|
|
772
|
+
| Notation | Courier Prime | 400 | `--type-mono` |
|
|
773
|
+
| Code | Courier Prime | 400 | `--font-code` |
|
|
774
|
+
|
|
775
|
+
### Scale (Stitch-aligned)
|
|
776
|
+
|
|
777
|
+
| Token | rem | ~px at 110% root | Stitch analogue |
|
|
778
|
+
| :--- | :--- | :--- | :--- |
|
|
779
|
+
| `--type-display-lg` | 3.6515 | ~64 | display-hero |
|
|
780
|
+
| `--type-page-lg` | 2.9015 | ~51 | headline-lg |
|
|
781
|
+
| `--type-section-lg` | 2.1515 | ~38 | headline-md |
|
|
782
|
+
| `--type-title-lg` | 2.4015 | ~42 | detail H1 |
|
|
783
|
+
| `--type-card` | 1.4015 | ~25 | headline-sm |
|
|
784
|
+
| `--type-prose` | 1.2 | ~21.1 | reading body, under 1024px |
|
|
785
|
+
| `--type-prose` (≥1024px) | 1.25 | ~22 | reading body, desktop |
|
|
786
|
+
| `--type-body` | 1.0758 | ~19 | body-md |
|
|
787
|
+
| `--type-meta` | 0.8125 | ~14 | body-sm |
|
|
788
|
+
| `--type-label` | 0.8485 | ~15 | label-md |
|
|
789
|
+
| `--type-mono` | 0.8504 | ~15 | notation |
|
|
790
|
+
|
|
791
|
+
---
|
|
792
|
+
|
|
793
|
+
## Typography (implementation notes)
|
|
794
|
+
|
|
795
|
+
A page with `drop_cap: true` raises the first letter of the opening paragraph
|
|
796
|
+
across `--drop-cap-lines` (2 unless `_config.yml` says otherwise) in the
|
|
797
|
+
reading face, Newsreader, when the body does not open with a heading.
|
|
798
|
+
|
|
799
|
+
One leading token, `--leading-body: 1.5`, covers chrome and short body copy;
|
|
800
|
+
`--leading-prose: 1.6` covers long-form. Both are declared on `html` in
|
|
801
|
+
`base.css` and applied through `html body` rather than `body`, because
|
|
802
|
+
Tailwind's Preflight ships `body { line-height: inherit }` from the CDN
|
|
803
|
+
`<style>` that loads after this repo's sheets and wins a specificity tie on
|
|
804
|
+
source order.
|
|
805
|
+
|
|
806
|
+
Consumers spell `var(--type-*)`, `var(--leading-body)` or
|
|
807
|
+
`var(--leading-prose)` rather than repeat numbers. Code blocks, figure
|
|
808
|
+
captions, verse and chart labels may keep local leading; they are not body
|
|
809
|
+
copy.
|
|
810
|
+
|
|
811
|
+
### The notation role
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
`.code-technical` (0.85rem / 1.5) and `.code-technical-sm` (0.75rem / 1.4) both
|
|
816
|
+
set `font-variant-numeric: tabular-nums`. This is the role that carries the
|
|
817
|
+
ledger reading: `renderMetaStrip` applies `.code-technical` unconditionally, so
|
|
818
|
+
every meta line on every card is mono, and the CV, the rascunhos ledger head,
|
|
819
|
+
the sources host line, the network hint and the detail back link all follow.
|
|
820
|
+
`.code-technical-sm` is defined but nothing uses it.
|
|
821
|
+
|
|
822
|
+
### Display scale
|
|
823
|
+
|
|
824
|
+
| Token | Base | Larger | Line height |
|
|
825
|
+
| :--- | :--- | :--- | :--- |
|
|
826
|
+
| `pageTitle` | 3.625rem | 5.875rem (≥768px) | 0.95 |
|
|
827
|
+
| `homeHeroTitle` | `clamp(var(--type-section), calc(100cqi * 0.92 / 14.5), var(--type-page-lg))` | floor `--type-section-lg` (≥640px) | 1.05 |
|
|
828
|
+
| `pageHeaderTitle` | 2.5rem | 3.5rem (≥768px) | 1.15 → 1.1 |
|
|
829
|
+
| `textTitle` | 2.15rem | 2.65rem (≥768px) | 1.12 |
|
|
830
|
+
| `textTitle` in the dossier | 1.95rem | 2.25rem (≥1024px) | 1.15 |
|
|
831
|
+
| `sectionTitle` | 2rem | 2.375rem (≥640px) | 1.1 |
|
|
832
|
+
| `cardTitle` | 1.625rem | — | 1.2 |
|
|
833
|
+
|
|
834
|
+
The home hero sizes to its copy column through a container query on
|
|
835
|
+
`.home-hero-copy`. The preferred term is `100cqi * 0.92 / 14.5`, capped at
|
|
836
|
+
`--type-page-lg`. The floor is the band-title step (`--type-section`, then
|
|
837
|
+
`--type-section-lg` from 640px) so the name meets Articles, Rascunhos, and
|
|
838
|
+
Projects on a phone instead of falling to the card size. Below 768px the
|
|
839
|
+
name may wrap (`white-space: normal`); from 768px it stays one line. The
|
|
840
|
+
given name is `onSurface` and the family name is `primary` on the same
|
|
841
|
+
line. An earlier container-query fit —
|
|
842
|
+
`min(4.875rem, calc(100cqw * 0.96 / 4.5))`, whose `4.5` was the advance of
|
|
843
|
+
`"Hello, I am"` in Lora — remains in `base.css` but reaches no markup.
|
|
844
|
+
|
|
845
|
+
### Prose
|
|
846
|
+
|
|
847
|
+
`.article-content` runs at `1.1rem / var(--leading-body)` inside a `45rem`
|
|
848
|
+
measure, paragraph margin `1.15em`. At 1280px and above that measure holds
|
|
849
|
+
about 81 characters to the line, which is long for the leading; the measure is
|
|
850
|
+
a known open question rather than a tuned value.
|
|
851
|
+
|
|
852
|
+
Its ink is `onSurface` — pure black in light, pure white in dark — so body
|
|
853
|
+
copy and titles share the full foreground. Meta, captions, chips and other
|
|
854
|
+
chrome stay on `onSurfaceVariant`.
|
|
855
|
+
|
|
856
|
+
* **`h2`** is the display serif at `1.6em / 400`, with a `gentleDivider` rule
|
|
857
|
+
above and `--space-2xl` of air, so a long article reads as a sequence of
|
|
858
|
+
plates. It drops to `1.4em` below 768px, and the first `h2` in the column
|
|
859
|
+
loses its rule.
|
|
860
|
+
* **`h3`** is the label face at `0.95em`, uppercase, `0.1em` tracking, in
|
|
861
|
+
`primary`.
|
|
862
|
+
* **`h4`** is the body sans at `1em / 600`.
|
|
863
|
+
* **`h1`** is untouched from the old build: body sans, 600, `1.4em`. It is
|
|
864
|
+
rare in prose and has no rule above it.
|
|
865
|
+
|
|
866
|
+
`pre` takes a 2px `outlineVariant` left rule on `surfaceContainer`;
|
|
867
|
+
`blockquote`, `.article-note`, tables with zebra `nth-child(even)` rows, MathJax
|
|
868
|
+
display blocks, Mermaid diagrams and the superscript citation apparatus are all
|
|
869
|
+
styled in [`assets/css/ledger/detail.css`](assets/css/ledger/detail.css), which
|
|
870
|
+
loads after and overrides much of `article.css`.
|
|
871
|
+
|
|
872
|
+
**Media sits at the measure, everywhere.** Every figure, video, Plotly embed
|
|
873
|
+
and Mermaid diagram is capped by `--reading-measure`, the same as the prose
|
|
874
|
+
around it, and a diagram wider than that scrolls inside its own plate rather
|
|
875
|
+
than pushing past the column. The projects page used to be the exception: it
|
|
876
|
+
released its whole body from the measure and handed the measure back to a
|
|
877
|
+
whitelist of text blocks, so a plate ran 18% wider than its paragraph at
|
|
878
|
+
1440px and 6% wider at 1280px. Neither width read as a deliberate break out of
|
|
879
|
+
the column, and the same includes sat differently there than on an article.
|
|
880
|
+
|
|
881
|
+
**Every piece of media sits on a plate.** `.article-content figure` gives all
|
|
882
|
+
of them one ground in both themes, so a light Matplotlib export and a dark
|
|
883
|
+
simulation video do not arrive as two surfaces. That means the `figure`
|
|
884
|
+
element is the contract: an include that emits a bare `<div>` or a bare
|
|
885
|
+
`<img>` silently opts out of it, which is how YouTube embeds and cut-in
|
|
886
|
+
portraits went unplated for as long as they did.
|
|
887
|
+
|
|
888
|
+
**The apparatus scale.** Below the prose's `1.1rem` the article descends in
|
|
889
|
+
four steps, all set in the body ink: a quote and a note share `0.95rem`, a
|
|
890
|
+
caption takes `0.9rem`, a bibliography entry `0.85rem`. A quote and a note are
|
|
891
|
+
deliberately indistinguishable as type, since both are a passage set aside from
|
|
892
|
+
the argument; only the plate tells them apart. Captions are the one step that
|
|
893
|
+
moves in two files, because `article.css` scopes `.article-content figure
|
|
894
|
+
figcaption` at `(0,1,2)` and outranks `detail.css`'s `(0,1,1)` — change one
|
|
895
|
+
without the other and figures keep the old size while a bare caption moves.
|
|
896
|
+
|
|
897
|
+
The bibliography is the last thing in an article still set in the notation face
|
|
898
|
+
while being made of sentences, which the notation rule above says it should not
|
|
899
|
+
be. Left as-is for now; its size no longer compounds the problem.
|
|
900
|
+
|
|
901
|
+
**Code blocks.** A fenced block is wrapped in a `.code-block` container by
|
|
902
|
+
`_includes/content-runtime/25-code-blocks.html`, which gives it the chrome an
|
|
903
|
+
editor gives a file: a `surfaceContainerHigh` bar over a `surfaceContainer`
|
|
904
|
+
pane, the language on a tab drawn in the pane's own fill with a 2px `primary`
|
|
905
|
+
inset rule across its top edge, and a copy control at the right in the label
|
|
906
|
+
register. Square and hairline-bordered like the ledger cards. The container
|
|
907
|
+
carries the fill and the frame, so the `pre` inside is zeroed back to a scroll
|
|
908
|
+
box — that override is three classes deep and so beats the `detail.css` rule
|
|
909
|
+
above without depending on sheet order. A ` ```mermaid ` fence is skipped; an
|
|
910
|
+
` ```ansi ` fence takes the terminal palette instead (see
|
|
911
|
+
[Terminal captures](#terminal-captures)).
|
|
912
|
+
|
|
913
|
+
### Label register
|
|
914
|
+
|
|
915
|
+
Uppercase label and notation text is the site's connective tissue.
|
|
916
|
+
Letter-spacing in use, by frequency: `0.1em` (12), `0.08em` (10), `0.12em` (7),
|
|
917
|
+
`0.04em` (4), `0.14em` (3), `0.05em` (3), `0.2em` (2), `0.06em` (2), `0.02em`
|
|
918
|
+
(2), `0.25em` (1), `0.16em` (1), plus `-0.01em` (3) as a display pull.
|
|
919
|
+
|
|
920
|
+
---
|
|
921
|
+
|
|
922
|
+
## Layout
|
|
923
|
+
|
|
924
|
+
* **Shell.** `max-width: 80rem` centred. The nav pads `1.5rem` → `2rem` (sm) →
|
|
925
|
+
`2.5rem` (md); listing and detail mains pad `2rem` → `4rem` (md); home bands
|
|
926
|
+
pad `--gutter-mobile` → `--gutter` (md).
|
|
927
|
+
* **Nav.** Sticky, `bg-surface`, logo + `|` divider + theme toggle on the left,
|
|
928
|
+
a single **Explore** hamburger on the right *at every breakpoint* — there is
|
|
929
|
+
still no persistent horizontal nav, and no responsive visibility class
|
|
930
|
+
anywhere in the bar. The menu is a bordered popover grouped from `_data/nav`.
|
|
931
|
+
A `1px` tonal strip closes the bar.
|
|
932
|
+
* **Home.** Six full-bleed `.home-band` sections, `--space-2xl` → `--space-3xl`
|
|
933
|
+
(md) of vertical padding, each closed by a `gentleDivider` rule, alternating
|
|
934
|
+
`background` and `.home-band--contrast` (`panelContrast`): hero, featured,
|
|
935
|
+
writing, rascunhos, systems, ideas graph. The featured band carries a 6rem ×
|
|
936
|
+
3px `primary` rule at its top-left corner as a structural mark. The three
|
|
937
|
+
writing cards take `primary` / `secondary` / `tertiary` 2px top rules in
|
|
938
|
+
rotation. The systems grid is a 12-column asymmetric pair — 7/5 then 5/7 —
|
|
939
|
+
from 1024px. Below 768px the hero portrait runs to the screen edge, pulled
|
|
940
|
+
out of the band's gutter by exactly `--gutter-mobile` on each side and losing
|
|
941
|
+
its side hairlines, which at the edge would read as a frame cut off.
|
|
942
|
+
* **Writing index.** Masthead over a hairline, then a `panelContrast` filter
|
|
943
|
+
deck (tag tabs, sort, keyword), then a `panelContrast` lead panel for the
|
|
944
|
+
newest entry, then an archive of `.ledger-row`s under a section banner.
|
|
945
|
+
* **Rascunhos.** Masthead with a shard, the shared filter panel unfolded into a
|
|
946
|
+
full-width strip, then a two-column body: a 3/1 split at 1024px of ledger
|
|
947
|
+
rows beside a sticky marginalia column. Rows become two-column
|
|
948
|
+
(`.rascunhos-row--illustrated`, a 7.5rem plate, 4rem on phones) only when an
|
|
949
|
+
image is actually present.
|
|
950
|
+
* **Projects.** A Tailwind `grid-cols-1 md:grid-cols-2` of system cards with
|
|
951
|
+
`align-items: start`, so a card without a poster does not stretch to match
|
|
952
|
+
one that has one.
|
|
953
|
+
* **Sources.** `.listing-card-grid--sources`: one column, two from 768px,
|
|
954
|
+
three from 1024px, posters at `16 / 9` — the video frame, which is what most
|
|
955
|
+
of the collection links to. Filtering hides pre-rendered cards with a class
|
|
956
|
+
rather than re-rendering them.
|
|
957
|
+
* **Detail pages.** Two columns from `768px` — a 3/9 split of a left `aside`
|
|
958
|
+
carrying a `panelContrast` dossier (title, description or thumbnail, tags,
|
|
959
|
+
language, dates) and the prose column at `45rem` on the right. The sticky
|
|
960
|
+
table of contents appears in that sidebar only from `1024px`; below it, the
|
|
961
|
+
same list is mounted as a `<details>` disclosure instead. A 2px `primary`
|
|
962
|
+
reading-progress bar is drawn on a pseudo-element of the detail section with
|
|
963
|
+
`animation-timeline: scroll(root block)`, beNewsreader an `@supports` guard, so
|
|
964
|
+
engines without scroll-driven animation simply get no bar.
|
|
965
|
+
* **CV.** A `panelContrast` dossier band with two shards, then five
|
|
966
|
+
`.cv-register` sections and a timeline band. Each register is a 3/9 grid at
|
|
967
|
+
1024px with the section title in a sticky left rail under a 2.5rem `primary`
|
|
968
|
+
rule, and the entries running past on the right.
|
|
969
|
+
* **Breakpoints.** Tailwind's `640 / 768 / 1024 / 1280` for `min-width`; the
|
|
970
|
+
ledger sheets add `max-width` queries at `639px` (shard suppression, phone
|
|
971
|
+
row metrics), `767px` (prose `h2`, sidebar padding, filter facet stacking) and
|
|
972
|
+
`900px` (the writing deck's own column collapse).
|
|
973
|
+
|
|
974
|
+
---
|
|
975
|
+
|
|
976
|
+
## Elevation & depth
|
|
977
|
+
|
|
978
|
+
The model is **tonal layering plus hairlines, never shadow**.
|
|
979
|
+
|
|
980
|
+
* **Content shadows: none.** Exactly two non-`none` `box-shadow` declarations
|
|
981
|
+
exist in the whole build: the listing filter popover (`0 12px 32px` of
|
|
982
|
+
`onSurface` at 12%, dropped below 768px where the panel goes inline) and a
|
|
983
|
+
1px ring on the timeline's hovered bar. The nav menu adds Tailwind
|
|
984
|
+
`shadow-sm`. The print sheet blanket-zeroes `box-shadow` on every descendant.
|
|
985
|
+
* **Hairline borders do the real work.** Because no rung of the ladder clears
|
|
986
|
+
1.16 : 1, boundaries are carried by `1px solid` borders at one of the five
|
|
987
|
+
`gentle*` opacities. There are 63 such declarations in the hand-written
|
|
988
|
+
stylesheets — 13 in `base.css`, 9 in `article.css`, 37 across `ledger/`, 4 in
|
|
989
|
+
`cv-print.css`.
|
|
990
|
+
* **Hover never lifts.** It steps the surface (`surfaceCard` → `surfaceHigh`),
|
|
991
|
+
shifts the title to `primary`, shifts a border toward `gentleHover` or
|
|
992
|
+
`primary`, or warms and scales a poster.
|
|
993
|
+
* **The print sheet has no surfaces at all.** Paper gets rules, not bands: a
|
|
994
|
+
full-strength hairline for a section, a half-strength one for an entry.
|
|
995
|
+
|
|
996
|
+
---
|
|
997
|
+
|
|
998
|
+
## Motion
|
|
999
|
+
|
|
1000
|
+
**Five tokens, and nothing outside them.** The scale is emitted by
|
|
1001
|
+
`_includes/theme/theme-vars-block.liquid` onto `:root` and every `[data-theme]`,
|
|
1002
|
+
so it survives a palette flip like any other custom property:
|
|
1003
|
+
|
|
1004
|
+
```css
|
|
1005
|
+
--motion-duration-fast: 0.15s; /* state change: colour, border, background */
|
|
1006
|
+
--motion-duration-base: 0.4s; /* poster filter warm-up */
|
|
1007
|
+
--motion-duration-slow: 0.7s; /* poster and plate scale */
|
|
1008
|
+
--motion-ease-default: ease;
|
|
1009
|
+
--motion-ease-out: ease-out;
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
Every `transition` and `animation` in the hand-written CSS reads them — 40
|
|
1013
|
+
declarations, 60 duration references, 53 of them `fast` — including the two
|
|
1014
|
+
inline `<style>` blocks in `_includes/page/`. Adopting the scale collapsed the
|
|
1015
|
+
near-duplicates the convention had accumulated: the old `0.2s`, `180ms` and
|
|
1016
|
+
`150ms` state changes are all `fast` now, the `0.6s` poster scales are `slow`,
|
|
1017
|
+
and the lightbox's `160ms`/`180ms ease-out` keyframes are `fast` plus
|
|
1018
|
+
`--motion-ease-out`, which is the token's only use.
|
|
1019
|
+
|
|
1020
|
+
Two literals survive on purpose, neither of them motion: the `99999s ... 0s`
|
|
1021
|
+
WebKit autofill suppression on the filter input (`base.css`), and the `1ms
|
|
1022
|
+
linear` on the reading-progress bar, where the duration is a placeholder that
|
|
1023
|
+
`animation-timeline: scroll(root block)` overrides.
|
|
1024
|
+
|
|
1025
|
+
**Scroll-driven animation** is new: the reading-progress bar in
|
|
1026
|
+
`ledger/detail.css` uses `animation-timeline: scroll(root block)` beNewsreader an
|
|
1027
|
+
`@supports` guard, with a 1ms `linear` animation scaling a fixed 2px `primary`
|
|
1028
|
+
bar from 0 to 1.
|
|
1029
|
+
|
|
1030
|
+
**`prefers-reduced-motion: reduce` is honoured globally.** One query in
|
|
1031
|
+
`_includes/theme/theme-vars.html` zeroes the three duration tokens, so every
|
|
1032
|
+
transition and animation that reads one stops dead. Nothing has to opt in, and
|
|
1033
|
+
a new rule cannot forget to.
|
|
1034
|
+
|
|
1035
|
+
Six local blocks remain, because a zero duration removes the *tween*, not the
|
|
1036
|
+
end state — a `transform` still lands, it just lands instantly:
|
|
1037
|
+
|
|
1038
|
+
| File | What it disables |
|
|
1039
|
+
| :--- | :--- |
|
|
1040
|
+
| `base.css` | the system card poster's `scale(1.02)` |
|
|
1041
|
+
| `ledger/home.css` | the hero plate image's `scale(1.02)` |
|
|
1042
|
+
| `ledger/writing.css` | the writing lead poster's `scale` |
|
|
1043
|
+
| `ledger/rascunhos.css` | the thumbnail filter transition |
|
|
1044
|
+
| `ledger/detail.css` | the reading-progress bar, which is scroll-driven and so outside the token scale |
|
|
1045
|
+
| `article.css` | the lightbox keyframes, belt-and-braces since they now read `fast` |
|
|
1046
|
+
|
|
1047
|
+
Colour and background fades are no longer an exception: they read `fast` like
|
|
1048
|
+
everything else, so the query catches them too.
|
|
1049
|
+
|
|
1050
|
+
---
|
|
1051
|
+
|
|
1052
|
+
## Shapes
|
|
1053
|
+
|
|
1054
|
+
* **`--radius-card: 0` keeps cards square.** Ledger cards, system cards,
|
|
1055
|
+
chips, emphasis CTAs, the home hero plate and index panels stay hard
|
|
1056
|
+
rectangles. Drafting corner brackets (`.ledger-frame-corners`) and empty
|
|
1057
|
+
crosshairs (`.ledger-frame-crosshairs`) are pure decoration with no text.
|
|
1058
|
+
Marker washes (`.ledger-marker`, `.section-title em`) highlight field-backed
|
|
1059
|
+
words only. `ledger/detail.css` still zeroes radius on prose `code`,
|
|
1060
|
+
`pre`, `blockquote` and `.article-note` where needed for reading;
|
|
1061
|
+
`cv-print.css` zeroes radius on every descendant for print.
|
|
1062
|
+
* **True pills stay circular.** Footer contact discs and the timeline legend
|
|
1063
|
+
mark use `9999px`. `404.html`'s `rounded-xl` buttons resolve through
|
|
1064
|
+
`assets/tailwind-play.js`'s remapped radius scale.
|
|
1065
|
+
* **Cubist shards** are now a primitive. Two polygons are declared once in
|
|
1066
|
+
`base.css`:
|
|
1067
|
+
|
|
1068
|
+
```css
|
|
1069
|
+
--shard-alpha: polygon(26% 0, 100% 0, 78% 100%, 0 68%);
|
|
1070
|
+
--shard-beta: polygon(0 0, 74% 18%, 100% 100%, 18% 84%);
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
`.shard-accent` is an absolutely positioned, `pointer-events: none`,
|
|
1074
|
+
`z-index: 0` element clipped by `--shard-alpha` at
|
|
1075
|
+
`--cubist-shard-opacity: 12%`; `.shard-accent-beta` swaps in the other
|
|
1076
|
+
polygon. A single `@media (max-width: 639px)` rule hides every one of them.
|
|
1077
|
+
Placement, size and hue are set per use by the page stylesheet, never by the
|
|
1078
|
+
utility.
|
|
1079
|
+
|
|
1080
|
+
Fifteen uses site-wide, all `aria-hidden`. No panel carries two shards in
|
|
1081
|
+
the same corner or the same hue, and no two panels carry the same pair of
|
|
1082
|
+
corners, which is the whole rule the placement follows:
|
|
1083
|
+
|
|
1084
|
+
| Where | Class | Corner | Hue |
|
|
1085
|
+
| :--- | :--- | :--- | :--- |
|
|
1086
|
+
| Home hero band | `.home-hero-pandorga` (geometric kite & ribbon) | background | `primary` / `secondary` / `tertiary` |
|
|
1087
|
+
| Home hero portrait | `.shard-accent.home-hero-shard` | top-right | `primary` |
|
|
1088
|
+
| Home featured card | `.shard-accent.home-featured-shard` | top-left | `tertiary` |
|
|
1089
|
+
| Home featured card | `.shard-accent-beta.home-featured-shard--beta` | bottom-right | `secondary` |
|
|
1090
|
+
| Home index panel | `.shard-accent.home-index-shard` | bottom-right | `secondary` |
|
|
1091
|
+
| Home index panel | `.shard-accent-beta.home-index-shard--beta` | bottom-left | `primary` |
|
|
1092
|
+
| Writing index lead panel | `.shard-accent.writing-lead-shard` | bottom-left | `primary` |
|
|
1093
|
+
| Writing index lead panel | `.shard-accent-beta.writing-lead-shard--beta` | top-right | `tertiary` |
|
|
1094
|
+
| CV dossier | `.shard-accent.cv-dossier-shard` | top-right | `secondary` |
|
|
1095
|
+
| CV dossier | `.shard-accent-beta.cv-dossier-shard--beta` | bottom-left | `primary` |
|
|
1096
|
+
| Projects lead | `.shard-accent.projects-lead-shard` | top-right | `primary` |
|
|
1097
|
+
| Projects hero | `.shard-accent.projects-hero-shard` | bottom-right | `secondary` |
|
|
1098
|
+
| Rascunhos masthead | `.shard-accent.rascunhos-shard` | top-right | `secondary` |
|
|
1099
|
+
| Sources masthead | `.shard-accent.sources-masthead-shard` | top-right | `secondary` |
|
|
1100
|
+
| Sources foot | `.shard-accent-beta.sources-foot-shard` | bottom-left | `tertiary` |
|
|
1101
|
+
| Site footer | `.shard-accent.site-footer-shard` | bottom-right | `secondary` |
|
|
1102
|
+
| Site footer | `.shard-accent-beta.site-footer-shard--beta` | bottom-left | `primary` |
|
|
1103
|
+
|
|
1104
|
+
Two placement traps this build has already fallen into. A `--beta` element
|
|
1105
|
+
carries both class names, so any media query that repositions the pair has to
|
|
1106
|
+
restate the base rule's `auto` offsets on the beta: leave all four set and
|
|
1107
|
+
the browser resolves to top and left, and the pair collapses into one corner.
|
|
1108
|
+
And a shard beNewsreader an opaque plate is a shard nobody sees, which is why the
|
|
1109
|
+
home featured pair goes around its poster rather than across the card.
|
|
1110
|
+
|
|
1111
|
+
On the writing index the shard was for a long time written to stand down
|
|
1112
|
+
whenever the featured entry carried a thumbnail — and that panel selects the
|
|
1113
|
+
first entry that *has* one, so the condition was always true and the shard
|
|
1114
|
+
never rendered on a built page.
|
|
1115
|
+
|
|
1116
|
+
A further shard sits on the detail sidebar heading. It is a `::after`
|
|
1117
|
+
pseudo-element, so it cannot take the utility class and re-implements it by
|
|
1118
|
+
hand — including its own 639px suppression.
|
|
1119
|
+
* **Icons** are Material Symbols Outlined, `FILL 0, wght 400, GRAD 0, opsz 24`,
|
|
1120
|
+
sized explicitly per context (`1rem` in the back link, `1.05rem` on the deck
|
|
1121
|
+
search, `1.1rem` on a system card, `1.25rem` in chrome).
|
|
1122
|
+
* **Card media** is a `16 / 9` `object-fit: cover` crop — `4 / 3` on rascunhos
|
|
1123
|
+
rows — never letterboxed. The home page is the exception: both its plates are
|
|
1124
|
+
measured against the copy beside them rather than against a ratio. The
|
|
1125
|
+
featured plate takes `--home-featured-plate-height` (its title's line plus
|
|
1126
|
+
four lines of description), and the draft rows' plates `align-self: stretch`
|
|
1127
|
+
so they square off against the chip row that closes the entry.
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1131
|
+
## Page chrome
|
|
1132
|
+
|
|
1133
|
+
### Bands and plates
|
|
1134
|
+
|
|
1135
|
+
`.home-band` is the universal full-bleed container: no border except the
|
|
1136
|
+
`gentleDivider` rule that closes it, no shadow, `panelContrast` when it carries
|
|
1137
|
+
`.home-band--contrast`. Four panel-step plates carry a shard —
|
|
1138
|
+
`.writing-lead`, `.rascunhos-panel`, `.cv-dossier` and
|
|
1139
|
+
`.text-detail-sidebar .article-sidebar-heading` — so each takes
|
|
1140
|
+
`overflow: hidden` and lifts its content clear with `position: relative;
|
|
1141
|
+
z-index: 1`. The filter decks and `.rascunhos-pagination` are plain
|
|
1142
|
+
`panelContrast` strips. `.home-hero-plate` and `.home-graph-plate` sit a rung
|
|
1143
|
+
lower, on `surfaceCard`, beNewsreader a `gentleSubtle` hairline.
|
|
1144
|
+
|
|
1145
|
+
### Filter deck
|
|
1146
|
+
|
|
1147
|
+
Two shapes over one controller (`_includes/content-runtime/42-listing-filter.html`).
|
|
1148
|
+
The writing index renders it inline as a `panelContrast` strip of tag **tabs**
|
|
1149
|
+
— solid `surfaceCard` fills, the active one inverting to a solid `primary`
|
|
1150
|
+
block — plus a sort group and a mono keyword field. Rascunhos takes the shared
|
|
1151
|
+
`page-header.html` popover and unfolds it into a full-width strip, removing the
|
|
1152
|
+
trigger button so it cannot re-collapse. Sources and the network graph keep the
|
|
1153
|
+
popover as a popover. The classification axis changed from `category` to `tag`
|
|
1154
|
+
in the controller; resources still filter on `category`.
|
|
1155
|
+
|
|
1156
|
+
### CV
|
|
1157
|
+
|
|
1158
|
+
`.cv-register` sections separated by a `gentleDivider` top rule and
|
|
1159
|
+
`--space-3xl`. Entries are hairline-separated rows, not boxes: a mono role
|
|
1160
|
+
line, a display institution line, a mono place line, body copy, and an optional
|
|
1161
|
+
chip row of related products. Languages are a name, a dotted leader and a mono
|
|
1162
|
+
level.
|
|
1163
|
+
|
|
1164
|
+
### Print CV
|
|
1165
|
+
|
|
1166
|
+
[`assets/css/cv-print.css`](assets/css/cv-print.css) now runs the same four
|
|
1167
|
+
type roles on paper: Simonetta for the name and entry titles, Newsreader for
|
|
1168
|
+
prose, small-caps section labels and organisations, mono for
|
|
1169
|
+
dates, URLs, email and phone. The header carries a mono kicker above the name —
|
|
1170
|
+
the paper counterpart of the dossier strip. Sections are a small-caps label
|
|
1171
|
+
with a hairline running out to the margin; jobs are separated by half-strength
|
|
1172
|
+
rules rather than wrapped in cards, because the mockup's card shape would have
|
|
1173
|
+
put eight grey blocks on an A4 sheet. The layout pins `data-theme="light"`, so
|
|
1174
|
+
only the light palette is ever in play, and a local reset zeroes radius and
|
|
1175
|
+
shadow on every descendant because the sheet ships no utility framework.
|
|
1176
|
+
|
|
1177
|
+
### Footer
|
|
1178
|
+
|
|
1179
|
+
`surfaceContainerLow` with a `gentleDivider` top border, `3rem` vertical
|
|
1180
|
+
padding, marginalia in `secondary`, contacts injected at runtime as circular
|
|
1181
|
+
discs.
|
|
1182
|
+
|
|
1183
|
+
---
|
|
1184
|
+
|
|
1185
|
+
## The field contract
|
|
1186
|
+
|
|
1187
|
+
An element renders only if a populated field backs it. The classification axes,
|
|
1188
|
+
as enforced by [`_plugins/lib/tag_validator.rb`](_plugins/lib/tag_validator.rb):
|
|
1189
|
+
|
|
1190
|
+
* **Articles** classify on `tags`, one to three, from a closed English
|
|
1191
|
+
vocabulary: `Aesthetics`, `AI`, `Data`, `DevOps`, `Mathematics`,
|
|
1192
|
+
`Robotics`, `Social`. All 22 carry them.
|
|
1193
|
+
* **Rascunhos** classify on `tags` too, by subject and in Portuguese, **zero to
|
|
1194
|
+
three**: `arte`, `brasilidade`, `carme`, `devaneio`, `engraçadinho`,
|
|
1195
|
+
`música`, `pesquisa`, `provocação`, `técnico`, `trabalho`. Unlike articles
|
|
1196
|
+
the field may be empty — a notebook holds pages that file nowhere. 29 of 30
|
|
1197
|
+
carry at least one.
|
|
1198
|
+
* `category` is rejected on both collections — one classification axis, not
|
|
1199
|
+
two.
|
|
1200
|
+
* **Projects** carry free `tags` (all 8 do), plus `label`, `status`,
|
|
1201
|
+
`start_date`, `end_date`, `icon`, `thumbnail`, `github`, `article_url`,
|
|
1202
|
+
`external_url`.
|
|
1203
|
+
* **Resources** are the exception that keeps `category` (`Academic`,
|
|
1204
|
+
`Communication`, `Humor`, `Music`, `Professional`) alongside a `type` of
|
|
1205
|
+
`youtube` / `podcast` / `website`.
|
|
1206
|
+
* **`language`** (`enus` / `ptbr`) is optional and exported into both
|
|
1207
|
+
`writing_index.json` and the per-object JSON. 29 of 67 entries declare one.
|
|
1208
|
+
|
|
1209
|
+
`writing_index.json` carries 29 keys per entry; detail views read the full
|
|
1210
|
+
`front_matter` plus `body_markdown` from
|
|
1211
|
+
`_content_json/collections/<coll>/<slug>.json`.
|
|
1212
|
+
|
|
1213
|
+
---
|
|
1214
|
+
|
|
1215
|
+
## Do's and Don'ts
|
|
1216
|
+
|
|
1217
|
+
### Do
|
|
1218
|
+
|
|
1219
|
+
* **Do reach for a block before writing a card.** If a layout needs a card,
|
|
1220
|
+
row, chip, pill, meta strip or CTA, call the renderer in `43-blocks.html`.
|
|
1221
|
+
Another local card shape is the failure mode this library exists to stop.
|
|
1222
|
+
* **Do let an absent field render nothing.** No placeholder, no dash, no
|
|
1223
|
+
derived stand-in, no counter.
|
|
1224
|
+
* **Do keep every corner at `0`.**
|
|
1225
|
+
* **Do take depth from the ladder**, and pair it with a hairline — no rung
|
|
1226
|
+
clears 1.16 : 1 on its own.
|
|
1227
|
+
* **Do set notation in `.code-technical`** — dates, spans, hosts, counts,
|
|
1228
|
+
reference numbers, kickers.
|
|
1229
|
+
* **Do keep long-form prose at `var(--reading-measure)`.**
|
|
1230
|
+
* **Do reach for `--space-*` and `--gutter`** in any new CSS rather than a
|
|
1231
|
+
literal length.
|
|
1232
|
+
* **Do use `--shard-alpha` / `--shard-beta`.** Do not draw a new polygon.
|
|
1233
|
+
* **Do emit identical token names in both palettes** so components never branch
|
|
1234
|
+
on theme.
|
|
1235
|
+
* **Do set `font-synthesis: none` on title text.** Simonetta ships 400 and 900 and nothing between; the gap must not be faked.
|
|
1236
|
+
* **Do reuse `.ledger-cta` / `.ledger-cta--emphasis` / `.ledger-cta--emphasis-alt`
|
|
1237
|
+
(`base.css`) for any CTA that used to reach for a padded, solid-filled
|
|
1238
|
+
button.** `--emphasis` is the primary hue tinted fill; `--emphasis-alt` swaps
|
|
1239
|
+
to the secondary hue for a second button-level action beside it (e.g. the
|
|
1240
|
+
home hero's "Full curriculum vitæ" / "Contacts" pair). Don't add another one-off
|
|
1241
|
+
button class — the old `.home-cta` / `--solid` / `--outline` family was
|
|
1242
|
+
retired in 2026-09 specifically because every page had started growing its
|
|
1243
|
+
own.
|
|
1244
|
+
* **Do scope a page's accent with `.page-accent--<color>` on its top-level
|
|
1245
|
+
container.** The two classes live in `base.css` (`--green` for Articles and
|
|
1246
|
+
the CV, `--ochre` for Rascunhos and Network of Ideas; Projects and Sources
|
|
1247
|
+
need none, blue is the default primary). Each rebinds `--color-primary` /
|
|
1248
|
+
`--color-link` / `--color-link-hover` to an existing triad token — never a
|
|
1249
|
+
new hex — and everything under it that reads those variables (card titles,
|
|
1250
|
+
CTAs, section-title `<em>` accents, filter chips) follows automatically
|
|
1251
|
+
through CSS inheritance. Categorical drawings that must keep the absolute
|
|
1252
|
+
slate / forest / ochre map (network graph nodes, type-keyed ambient pools)
|
|
1253
|
+
read `--triad-primary` / `--triad-secondary` / `--triad-tertiary` instead —
|
|
1254
|
+
those aliases are frozen in the theme vars block and are not rebound. Add a
|
|
1255
|
+
variant in `base.css`, not in a `ledger/*.css`: the green one was duplicated
|
|
1256
|
+
across two page stylesheets within a week of being written.
|
|
1257
|
+
* **Do let tag chips carry the page accent.** `.ledger-chip` mixes
|
|
1258
|
+
`--color-primary` into both its fill (10%) and its ink (78%), so a chip is
|
|
1259
|
+
blue on Projects and Sources, green on Articles and the CV, ochre on
|
|
1260
|
+
Rascunhos and Network, with no per-page rule. Hover steps the fill to 22%
|
|
1261
|
+
and adds a 35% accent hairline as an inset ring rather than a border — a
|
|
1262
|
+
border would have to exist at rest to avoid a 2px jump, and its padding
|
|
1263
|
+
compensation only nets to zero at integer device pixel ratios. The
|
|
1264
|
+
page-header tag line (`data-content-header-tags`, e.g. the CV's
|
|
1265
|
+
"Engineering Leadership. Team Building…") is deliberately not a chip and
|
|
1266
|
+
stays neutral.
|
|
1267
|
+
* **Do introduce a home band with a kicker, a title and a note**, all three
|
|
1268
|
+
from `content/pages/headers.yml` so the owner can edit them in Studio. The
|
|
1269
|
+
title is the same field the band's listing page reads for its own header.
|
|
1270
|
+
A band that opens straight onto cards gives the reader no way in. Each of
|
|
1271
|
+
the three renders nothing when its field is empty.
|
|
1272
|
+
* **Do mix a hover tone toward `--color-on-surface`, not toward a
|
|
1273
|
+
`*-fixed-dim` token.** Those tokens carry the same value in both palettes,
|
|
1274
|
+
so they lighten a link on hover in *both* — right against a near-black
|
|
1275
|
+
canvas, wrong against white paper, where emphasis has to go darker. The
|
|
1276
|
+
foreground ink is dark in light and light in dark, so mixing toward it
|
|
1277
|
+
moves the correct direction in each theme on its own.
|
|
1278
|
+
* **Do give a project-card link an icon that names its destination**
|
|
1279
|
+
(`renderCtaLink`'s `icon` option in `43-blocks.html`: `code` for a
|
|
1280
|
+
repository, `article` for a write-up, `public` for an external site, the
|
|
1281
|
+
entry's own `fm.icon` for "Details"). A destination icon is a *leading*
|
|
1282
|
+
marker and renders before the label, in `.ledger-cta-icon`, with no hover
|
|
1283
|
+
slide. Every other CTA keeps the trailing directional glyph — since 2026-09
|
|
1284
|
+
a Material `chevron_right` rather than a `→` — in `.ledger-cta-arrow`,
|
|
1285
|
+
which does slide on hover.
|
|
1286
|
+
|
|
1287
|
+
### Don't
|
|
1288
|
+
|
|
1289
|
+
* **Don't write a hex.** Everything resolves through `_data/themes/*.yml` → CSS
|
|
1290
|
+
custom properties → Tailwind token names. A literal is correct in one palette
|
|
1291
|
+
and wrong in the other.
|
|
1292
|
+
* **Don't fill a panel, card or row with a brand hue.** A CTA earns a tinted
|
|
1293
|
+
fill (`.ledger-cta--emphasis`, `~14%` of the accent colour, brightening to a
|
|
1294
|
+
full fill on hover) for the one or two truly primary actions per page — that
|
|
1295
|
+
is the sanctioned exception, not a flat opaque brand-colour block.
|
|
1296
|
+
* **Don't add drop shadows to content.** Shadows are for floating overlays.
|
|
1297
|
+
Depth on a card or panel is the ladder rung plus its hairline, and on hover
|
|
1298
|
+
the rung plus a hairline warmed toward the page accent (`.ledger-card:hover`
|
|
1299
|
+
mixes `--color-primary` at 40% into the border — the reference mockup's
|
|
1300
|
+
"hairline transitions to warm antique brass at 40%", made accent-aware).
|
|
1301
|
+
One sanctioned exception, added 2026-09: a wide, very low opacity accent
|
|
1302
|
+
pool sits *beNewsreader* a band. It is a backdrop, not a content effect — no card,
|
|
1303
|
+
panel or row gains a glow, and the pool is blurred past any visible edge so
|
|
1304
|
+
it reads as ambient light in the room rather than as a shape. Three of them
|
|
1305
|
+
ship: the home hero at 7% (`.home-band--hero::before`), the home Sources and
|
|
1306
|
+
Network band at 5% (`.home-band--index::before`), and one beNewsreader the Network
|
|
1307
|
+
of Ideas graph at 7%, whose centre and width are read off the settled D3
|
|
1308
|
+
layout rather than fixed (`assets/css/ledger/network.css`).
|
|
1309
|
+
|
|
1310
|
+
**All three are dark-palette only**, gated on `[data-theme="dark"]`. The
|
|
1311
|
+
hero pool used to be unconditional, on the written assumption that it
|
|
1312
|
+
disappeared against a white light-theme canvas; the canvas has been warm
|
|
1313
|
+
paper since the ladder was inverted, and `--color-primary` inverts polarity
|
|
1314
|
+
between the palettes, so on light the rule painted a dark accent over paper.
|
|
1315
|
+
Near-equal RGB distance (20.3 dark, 19.3 light) resolves to 0.53 points of
|
|
1316
|
+
luminance on the near-black canvas and 9.11 on paper — about seventeen times
|
|
1317
|
+
the shift, in the palette the effect was never designed for. There is no
|
|
1318
|
+
light variant: paper has no headroom to lighten into.
|
|
1319
|
+
* **Don't tint a page background off-brand.** Dark is a neutral-to-blue
|
|
1320
|
+
near-black (`#0c0c0e` canvas) — not olive or green. Light *is* warm paper
|
|
1321
|
+
(`#f4f2ed`), and that is deliberate: it is where the *gauchismo* half of
|
|
1322
|
+
the identity actually lives. This rule used to read "light is pure
|
|
1323
|
+
`#ffffff`, never cream" — that was written when cards were grey on white,
|
|
1324
|
+
and it was reversed in 2026-09 along with the ladder. Cream as a *canvas*
|
|
1325
|
+
is correct; cream as a random panel tint still is not.
|
|
1326
|
+
* **Do step a card toward the light, in both themes.** Dark: canvas `#0c0c0e`
|
|
1327
|
+
→ card `#1a1a1e`. Light: canvas `#f4f2ed` → card `#fffdf9`. A card that
|
|
1328
|
+
steps away from the light reads as recessed and, on white, as disabled —
|
|
1329
|
+
which is what the light theme did until 2026-09.
|
|
1330
|
+
* **Don't introduce a fourth webfont.** Four roles, three families plus the
|
|
1331
|
+
system mono stack.
|
|
1332
|
+
* **Don't autoplay a poster.** Project captures run to several megabytes;
|
|
1333
|
+
`renderPosterMedia` ships `preload="none"` and `controls`.
|
|
1334
|
+
* **Don't invent content furniture** — no reading times, word counts, entry
|
|
1335
|
+
numbers, sequence counters, invented status pills, fabricated metrics,
|
|
1336
|
+
benchmarks, commit hashes or version badges.
|
|
1337
|
+
* **Don't edit `43-blocks.html` for one page's needs.** A block that only one
|
|
1338
|
+
layout wants belongs in that layout's `ledger/*.css` and markup.
|
|
1339
|
+
|
|
1340
|
+
---
|
|
1341
|
+
|
|
1342
|
+
## Known inconsistencies in the current build
|
|
1343
|
+
|
|
1344
|
+
Recorded so the next pass resolves them deliberately rather than re-copying
|
|
1345
|
+
them.
|
|
1346
|
+
|
|
1347
|
+
1. **Two names for one system.** `_data/themes/*.yml` says "Brazilian Ledger";
|
|
1348
|
+
`base.css`, `cv-print.css` and every `ledger/*.css` header say
|
|
1349
|
+
"Architectural Ledger". Nothing reconciles them.
|
|
1350
|
+
2. **The block library is not yet the only source of cards.** Five layouts
|
|
1351
|
+
still hand-build card or row markup — see *Where the library is not yet the
|
|
1352
|
+
only source*. The sources card and the CV's `renderProduct` are archive
|
|
1353
|
+
cards rebuilt from scratch; rascunhos is a second poster implementation with
|
|
1354
|
+
different failure behaviour; `renderLangPill` is forked twice (sources'
|
|
1355
|
+
`lang-pill--other`, `text.html`'s Liquid copy).
|
|
1356
|
+
3. ~~**The ladder's rungs are smaller than the step they replaced.**~~
|
|
1357
|
+
**Addressed 2026-09.** The dark canvas dropped to `#0c0c0e` and the rungs
|
|
1358
|
+
were respread to roughly +4.3 / +3.1 / +6.0 L\*, which is the mockup's own
|
|
1359
|
+
spacing; light was deepened more modestly, since white paper has less room
|
|
1360
|
+
to give and its warm cast is deliberate. Note that contrast *ratio* is the
|
|
1361
|
+
wrong instrument down here — the +0.05 flare term in the WCAG formula
|
|
1362
|
+
flattens every near-black pair to ≈1.05 : 1 no matter how far apart they
|
|
1363
|
+
look — so the rungs are specified in L\* instead.
|
|
1364
|
+
4. **Two surface families name the same colours.** `surfaceCard` vs
|
|
1365
|
+
`surfaceContainerLow` and `surfaceHigh` vs `surfaceContainerHigh` still
|
|
1366
|
+
name near-identical values. The consequence this entry used to cite —
|
|
1367
|
+
`.ledger-chip`'s hover being invisible, since it stepped from one family
|
|
1368
|
+
to the other — was fixed in 2026-09 by giving the chip an accent-tinted
|
|
1369
|
+
fill and hairline of its own instead of a ladder rung (it was a literal
|
|
1370
|
+
no-op in dark: the two values were identical). Collapsing the two families
|
|
1371
|
+
into one is still the real fix and is still open.
|
|
1372
|
+
5. **Two spacing systems.** `--space-*` governs the hand CSS; 176 Tailwind
|
|
1373
|
+
rungs govern the Liquid chrome. `home.html` is fully on the former,
|
|
1374
|
+
`shell.html`, `text.html` and `404.html` fully on the latter. The Tailwind
|
|
1375
|
+
config extends colours and fonts but **not** spacing, so the named scale is
|
|
1376
|
+
unavailable as a utility.
|
|
1377
|
+
6. **The type scale is still not a scale.** 55 distinct `font-size` values
|
|
1378
|
+
across the eleven screen stylesheets, plus 25 more in `cv-print.css`; 19
|
|
1379
|
+
`line-height` values; 12 uppercase `letter-spacing` values. No ratio
|
|
1380
|
+
connects them.
|
|
1381
|
+
7. **`article.css` was never updated.** It is 723 lines describing the old
|
|
1382
|
+
prose voice, kept alive only because `ledger/detail.css` loads after it and
|
|
1383
|
+
overrides most of it. Its radius declarations and its body-sans heading
|
|
1384
|
+
block are dead in the browser but live in the file. `.article-content h1`
|
|
1385
|
+
escaped the override and is still body sans at 600 / `1.4em`.
|
|
1386
|
+
8. **About a third of `base.css`'s class selectors are dead.** `.home-panel`,
|
|
1387
|
+
`.home-panel-inner`, `.home-band-grey`, `.home-gateway`, `.home-hero-panel*`
|
|
1388
|
+
(including the stale container-query hero fit measured for Lora),
|
|
1389
|
+
`.home-portrait-*`, `.home-cv-*`, `.home-header-grid`, `.geometric-panel`,
|
|
1390
|
+
`.cv-box`, `.card-footer*`, `.card-thumbnail`, `.card-tag`, `.cat-link`,
|
|
1391
|
+
`.listing-card-grid--3`, `.serif-font`, `.font-headline`, `.font-editorial`
|
|
1392
|
+
— 33 of its 96 class selectors reach no markup. `.code-technical-sm` is the mirror
|
|
1393
|
+
case: a new token nothing consumes.
|
|
1394
|
+
9. ~~**The CV timeline is stuck in the light palette.**~~ **Fixed 2026-09.**
|
|
1395
|
+
The chart now reads its lane colours from live CSS custom properties
|
|
1396
|
+
(`--timeline-*`) instead of Liquid-baked hex, and redraws on the
|
|
1397
|
+
`shell-theme-change` event, so it follows `data-theme` like everything
|
|
1398
|
+
else. Two related defects went with it: six event types were sharing four
|
|
1399
|
+
colours (employment collided with article, product with post), and the
|
|
1400
|
+
density bars and legend swatches read `--color-primary` / `--color-tertiary`
|
|
1401
|
+
— rebound to green on the CV by `.page-accent--green`, which flattened the
|
|
1402
|
+
chart into one hue. The lane palette is now its own categorical scale in
|
|
1403
|
+
`_data/themes/*.yml` under `timeline:`, deliberately outside the UI triad:
|
|
1404
|
+
see *Semantic and media colours*.
|
|
1405
|
+
10. **`404.html` was not rebuilt.** It still carries `rounded-xl` on two
|
|
1406
|
+
buttons, `shadow-sm`, and a `bg-gradient-to-b from-primary
|
|
1407
|
+
to-primary-container` fill — three of the system's rules broken on one
|
|
1408
|
+
page.
|
|
1409
|
+
11. **Navigation is still fully hidden.** A rich, deep site sits beNewsreader one
|
|
1410
|
+
hamburger at every breakpoint, so its structure is invisible on arrival.
|
|
1411
|
+
This survived the redesign untouched.
|
|
1412
|
+
12. **Tailwind runs from the Play CDN in production**
|
|
1413
|
+
(`cdn.tailwindcss.com?plugins=forms,container-queries`), so utilities are
|
|
1414
|
+
unpurged, unversioned, and injected *after* the hand CSS. The redesign now
|
|
1415
|
+
depends on beating it twice: `base.css` re-specifies the filter input
|
|
1416
|
+
because the forms plugin paints it white in dark mode, and
|
|
1417
|
+
`ledger/detail.css` documents writing rules with two compound parts on
|
|
1418
|
+
purpose for the same reason.
|
|
1419
|
+
13. ~~**`surface` still diverges between themes.**~~ **Fixed 2026-09.** Light
|
|
1420
|
+
had `surface == background == #ffffff`, so any component distinguishing
|
|
1421
|
+
the two — the nav, the filter popover — was invisible there. The ladder
|
|
1422
|
+
inversion gave `surface` the card value (`#fffdf9`) against a paper
|
|
1423
|
+
canvas, so the two now differ in both themes.
|
|
1424
|
+
14. ~~**Light has no `sidebar_thumbnail` block.**~~ **Fixed 2026-09.** Light
|
|
1425
|
+
defines its own block now, so neither theme falls through to
|
|
1426
|
+
`_config.yml`.
|
|
1427
|
+
15. **The Mermaid theme map is duplicated verbatim** in
|
|
1428
|
+
`_includes/page/mermaid.html` and `_includes/content-runtime/20-markdown.html`
|
|
1429
|
+
— nine `themeColor(token, '#hex')` fallbacks in each, byte-identical, with
|
|
1430
|
+
nothing keeping them in step.
|
|
1431
|
+
16. **`renderArchiveCard`'s `showFooterDates` path is dead.** No layout passes
|
|
1432
|
+
the option, and `ledger/home.css` additionally hides
|
|
1433
|
+
`.ledger-card-foot .date-evolution` with CSS — a belt for a brace that is
|
|
1434
|
+
already off.
|