Commit Graph
2 Commits
Author SHA1 Message Date
0chencc 0c40eb5651 fix(web): inline self-hosted fonts to eliminate first-paint flash
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.
2026-04-28 22:08:30 +09:00
0chencc 3229de013a feat(web): rebuild landing page on Vite + TypeScript
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.
2026-04-28 20:31:46 +09:00