Ctrl+F5 / cold loads previously showed a sequence of small flashes:
unstyled text first, SVG icons popping from 300×150 to their icon
size, then the hero h1 layout-shifting when Google Fonts Inter
swapped in. Each one came from a different async resource in the
loading chain — patching them one at a time was a losing game,
because every fix exposed the next link.
Root cause: web fonts are always a separately-fetched resource. No
CSS-only trick (size-adjust override, ascent/descent override) makes
the re-render imperceptible at hero-h1 sizes — at 80 px, even a few-
pixel metric delta is visible.
Replace Google Fonts with @fontsource-variable npm packages and let
Vite's assetsInlineLimit (100 MB) base64-inline the woff2 into the
stylesheet. Hand-written @font-face declarations restricted to the
latin weight-axis subset; the default index.css would have inlined
13 woff2s across cyrillic / greek / vietnamese — ~300 KB of dead
weight on a page that only renders ASCII.
Two related moves stacked in here because they're part of the same
"no async resource between HTML and first paint" rule:
- CSS now loaded via <link rel=stylesheet> in the HTML head, not
imported through main.ts. Importing through the deferred ES
module chained CSS behind the JS fetch and produced the same
unstyled-text flash for the same reason.
- Inline <svg>s gained explicit width / height attributes — without
them the browser rendered SVGs at the default 300×150 viewport
before CSS arrived, then snapped them to icon size.
The earlier metric-matched fallback @font-face declarations are
gone — they were a workaround for the async swap, no longer needed
once the font is synchronous with the CSS.
HTML grows from 31 KB / 9 KB gzipped to 150 KB / 99 KB gzipped.
One-time cost; cached after first visit; no third-party DNS or
font-CDN dependency.
Net result: first paint is the finished page on Ctrl+F5.
Replace the single-file index.html at the repo root with a proper
Vite + TS project under web/ that builds back to a single inlined
index.html via vite-plugin-singlefile. GitHub Pages keeps serving
the same path; deploy posture unchanged.
Visual:
- Sticky topbar, twin radial-gradient backdrop fixed under scroll,
Inter + JetBrains Mono. Adaptive container width: 880 default →
1120 @ ≥1200 → 1280 @ ≥1440 (patches auto-fill 3 → 4 columns).
- Hero pill reads `Verified on Claude Code <version>` from the same
`badges` branch JSON the README badge consumes.
- 4-chip stats row in hero: GitHub @0chencc, X @0chencc, live
stargazers, summed asset download_count — populated via GitHub
REST on load, formatted with k/M suffix, tabular-nums to avoid
layout shift on update.
- Install one-liners tokenized (cmd / flag / url / op / arg) with
per-token coloring; URL gets a desaturated slate (--code-url) so
it reads as classified without stealing attention.
- Copy button two-tier: Clipboard API in secure contexts, hidden-
textarea + execCommand fallback for plain-http LAN access. The
earlier single-tier silently failed on the dev server's LAN IP.
Copied / failed states colored.
- Patches grid grows a fourth section (Routing) for the v1.1.1
`claude update` redirect; four How-it-works cards summarize
Extract → Patch → Launch → Stay-current. Footer adds a
maintainer cell with @0chencc + GitHub/X icon links.
- Custom thin scrollbars (webkit + firefox) with green hover on
code blocks. prefers-reduced-motion respected.
CSS split across 7 partials (tokens / base / layout / hero /
sections / responsive / index) so future work can edit one role
without scrolling a 700-line monolith.
node_modules / dist / .vite cache gitignored. Dev server runs with
--host so phones on LAN can preview responsive layouts.