feat(og): per-tool share cards, generated and committed#70
Merged
Conversation
Every page currently shares the same og:image — pwa-512x512.png, a 512 square logo — so a link to any of the 32 tools looks identical and the card renders squashed. This generates a real 1200x630 card per tool per locale, 66 in all. The card design follows the site's own tokens (cream, ink, terracotta, Newsreader) rather than inventing a look. What varies is structure: a category-coloured spine down the left edge like a file tab in a toolbox drawer, the tool's own emoji as the hero in a tinted rounded slot that echoes the logo's geometry, and the logo demoted to a footer signature. Chinese titles set in Noto Serif TC to match Newsreader's serif voice; English cards use Newsreader itself. Generation is a manual script (`npm run og`), deliberately not wired into the build. Cards change only when a tool is added or renamed, and fetching fonts at build time would fail at deploy rather than on a dev machine. `src/lib/__tests__/og.test.ts` fails if any tool is missing a card, so "added a tool, forgot the image" is caught in CI instead of by a broken share card in the wild. Lib-first, as usual: - src/lib/contrast.ts — WCAG luminance and contrast ratio, plus darkenUntilReadable(). The brand terracotta only reaches 2.96:1 on cream, too weak for the small eyebrow text, so each spine colour gets a darkened twin at >= 4.5:1. Measured, not eyeballed. - src/data/categories.ts — the six spine colours and their eyebrow twins as literals, keeping src/data free of runtime imports. A test locks each literal back to darkenUntilReadable()'s output so they cannot drift. - src/lib/og.ts — the /og/<locale>/<id>.png path convention, shared by the generator and (next) BaseLayout. The PWA precache needed excluding. injectManifest's glob swept all 66 PNGs into the service worker — 2.3MB that only the Facebook, X and LINE crawlers ever fetch, downloaded by every visitor. globIgnores drops precache entries from 194 to 128; the images still ship to dist/. Fonts (Noto Serif/Sans TC are ~16MB together) and twemoji SVGs are cached under scripts/.cache/ and gitignored. twemoji is pinned so upstream art changes don't silently redraw the cards. No <meta> tags change here — the cards are generated but not yet referenced, so nothing that works today can break. BaseLayout gets wired up next. Tests: typecheck:api, 508 unit (18 new), 48 e2e. Cards inspected by eye for tofu boxes, long-title overflow and the footer mark. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
TTigger
added a commit
that referenced
this pull request
Jul 9, 2026
feat(og): point every page at its own share card The 66 cards from #70 existed but nothing referenced them. BaseLayout resolves og:image and twitter:image through ogImagePath(toolId, locale), so /tools/gear-calculator advertises /og/zh/gear-calculator.png and its English twin advertises /og/en/gear-calculator.png. Pages with no toolId fall back to the home card. - twitter:card summary -> summary_large_image. The old value rendered a small square thumbnail, which is what the 512px logo was shaped for; a 1200x630 card needs the large variant or X crops it. - og:image:width / height / alt added. Facebook and LINE size the card from the tags before fetching the image, and skip the preview entirely on a cold cache if they have to guess. - meta name=author added. - The Chinese home description was 14 characters. Our own URL Audit scores anything under 50 as a warning, and it was right: the old text ("TTigger's tool collection and portfolio") said nothing about what the site does. Rewritten to name the tool families and the fact that nothing is uploaded. The English default was already 51 and stands. Tool pages already pass their own descriptions, so only the home default needed lengthening. e2e covers what unit tests can't reach here, since BaseLayout is an Astro component: the resolved og:image URL per locale, the card dimensions and twitter:card value, that the advertised image actually returns 200 image/png rather than a 404, and that the home description lands inside the 50-160 window the audit tool checks for. Tests: typecheck:api, 508 unit, 54 e2e (6 new). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every page currently shares the same
og:image—pwa-512x512.png, a 512px square logo. A link to any of the 32 tools looks identical, and the card renders squashed. This generates a real 1200×630 card per tool per locale: 66 cards.Design
Follows the site's own tokens (cream, ink, terracotta, Newsreader) rather than inventing a look. What varies is structure:
Chinese titles are set in Noto Serif TC to match Newsreader's serif voice; English cards use Newsreader itself.
Why a manual script, not a build step
npm run og, deliberately not wired intonpm run build. Cards change only when a tool is added or renamed. Fetching fonts at build time would fail at deploy, not on a dev machine — and we've just seen that a Vercel deployment can go READY with a broken artifact inside it.The trade is a manual step that someone will forget.
src/lib/__tests__/og.test.tsfails if any tool lacks a card, so that's caught in CI rather than by a broken share card in the wild.Lib-first
src/lib/contrast.ts— WCAG luminance and contrast ratio, plusdarkenUntilReadable(). The brand terracotta only reaches 2.96:1 on cream, too weak for the small eyebrow text, so each spine colour gets a darkened twin at ≥ 4.5:1. Measured, not eyeballed.src/data/categories.ts— the six spine colours and their eyebrow twins as literals, keepingsrc/datafree of runtime imports. A test locks each literal back todarkenUntilReadable()'s output so they can't drift.src/lib/og.ts— the/og/<locale>/<id>.pngconvention, shared by the generator and (next PR)BaseLayout.The PWA precache trap
injectManifest's glob (**/*.{js,css,html,svg,png,...}) swept all 66 PNGs into the service worker — 2.3MB that only the Facebook, X and LINE crawlers ever fetch, downloaded by every visitor.globIgnores: ['og/**']drops precache entries from 194 to 128; the images still ship todist/.Scope
No
<meta>tag changes here. The cards are generated but not yet referenced, so nothing that works today can break.BaseLayoutgets wired up in the next PR, together withtwitter:card→summary_large_image.Fonts (Noto Serif/Sans TC, ~16MB together) and twemoji SVGs cache under
scripts/.cache/, gitignored. twemoji is pinned to 15.1.0 so upstream art changes don't silently redraw the cards.Tests
typecheck:api, 508 unit (18 new), 48 e2e. Cards inspected by eye for tofu boxes, long-title overflow, and the footer mark — the first draft silently dropped the logo'sT, becauseicon.svgdraws it with<text>in Georgia and resvg has no such font.🤖 Generated with Claude Code