From 82cfa67951d00a228de52ac4d0bf763a09e05c96 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 06:31:04 +0000 Subject: [PATCH] site(covers): cover spec gate and build-time dark cover variants Decisions 3 (gate) and 4 of the #340 visual-style ruling. - scripts/cover-spec.mjs runs in `pnpm build`. Every cover*.svg of a post dated on or after SPEC_FROM (2026-11-17) is held to: at most 2 font sizes, title at least 56px, nothing under 28px, at most 8 text runs, at most 6 fill colours. Existing covers are grandfathered by the post's date. SPEC_FROM is the day after the last queued slot (11-16), because the publish queue re-dates its six pre-spec posts to their publish days. A queued post that slips past it takes a dated, named LATE_QUEUE entry; the list is empty, and a stale entry fails. --report prints one row per cover and never fails (88 of 88 break the spec today). - scripts/cover-dark.mjs writes cover*.dark.svg (gitignored) beside each light SVG cover, before `astro build` and in `pnpm dev`. Site tokens map to their dark values. Every other colour goes through a luminance inversion that keeps WCAG contrast; the script checks this for all 1,670 texts. The two covers with a dark ground are skipped. - ArticleCard, the BlogPost hero, and the home and cluster cards render both covers lazily and hide one by :root[data-theme]. Only one is downloaded per theme. The dark variant takes no --cover-filter. - svg-text-fit skips *.dark.svg, exports its parse helpers, and carries computed fills in checkSvg. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr --- .gitignore | 2 + package.json | 4 +- scripts/cover-dark.mjs | 193 +++++++++++++++++++++++++++ scripts/cover-spec.mjs | 204 +++++++++++++++++++++++++++++ scripts/dev.mjs | 8 ++ scripts/lib/cover-colors.mjs | 179 +++++++++++++++++++++++++ scripts/lib/cover-inventory.mjs | 121 +++++++++++++++++ scripts/svg-text-fit.mjs | 21 +-- src/components/ArticleCard.astro | 54 ++++++-- src/components/ClusterPage.astro | 56 ++++++-- src/components/MarketingHome.astro | 54 ++++++-- src/layouts/BlogPost.astro | 60 +++++++-- src/lib/cover-variant.ts | 30 +++++ 13 files changed, 939 insertions(+), 47 deletions(-) create mode 100644 scripts/cover-dark.mjs create mode 100644 scripts/cover-spec.mjs create mode 100644 scripts/lib/cover-colors.mjs create mode 100644 scripts/lib/cover-inventory.mjs create mode 100644 src/lib/cover-variant.ts diff --git a/.gitignore b/.gitignore index 52edfd4d..a00c05aa 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ pnpm-debug.log* .env.production # macOS .DS_Store +# dark cover variants, written by scripts/cover-dark.mjs on every build and dev start +content/blog/**/*.dark.svg diff --git a/package.json b/package.json index 404a33c6..cc2cc472 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,10 @@ "positioning:check": "node scripts/check-positioning.mjs", "seo:smoke": "node scripts/seo-smoke.mjs", "svg:lint": "node scripts/svg-text-fit.mjs", + "cover:spec": "node scripts/cover-spec.mjs", + "cover:dark": "node scripts/cover-dark.mjs", "og:images": "node scripts/og-images.mjs", - "build": "node scripts/check-positioning.mjs && node scripts/gen-zh-hant.mjs && node scripts/content-lint.mjs --published && node scripts/svg-text-fit.mjs && astro build && node scripts/og-images.mjs && node scripts/content-lint.mjs --dist", + "build": "node scripts/check-positioning.mjs && node scripts/gen-zh-hant.mjs && node scripts/content-lint.mjs --published && node scripts/svg-text-fit.mjs && node scripts/cover-spec.mjs && node scripts/cover-dark.mjs && astro build && node scripts/og-images.mjs && node scripts/content-lint.mjs --dist", "preview": "astro preview", "check": "astro check" }, diff --git a/scripts/cover-dark.mjs b/scripts/cover-dark.mjs new file mode 100644 index 00000000..85ef5fd7 --- /dev/null +++ b/scripts/cover-dark.mjs @@ -0,0 +1,193 @@ +#!/usr/bin/env node +// Dark cover variants: beside every light `cover*.svg` under content/blog, +// write `cover*.dark.svg`, the same drawing with each colour swapped for its +// dark counterpart (scripts/lib/cover-colors.mjs: the site's token pairs, +// then OKLCH lightness mirroring for every other colour). +// +// node scripts/cover-dark.mjs [--verbose] (pnpm cover:dark; runs in `pnpm build` and `pnpm dev`) +// +// The variants are build output, not source: .gitignore keeps them out of the +// repo and this script rewrites them from the light covers on every build, so +// a cover edit cannot leave a stale dark copy behind. The templates +// (src/lib/cover-variant.ts) render both images, lazy, and show one by the +// page's `data-theme`; the hidden one is never fetched. +// +// What is rewritten: `fill`, `stroke`, `stop-color` and `color`, as +// attributes, in `style` attributes and in `