Skip to content

fix(landing): typography fixes across the landing page and feature shots - #81

Merged
aleexwong merged 4 commits into
mainfrom
claude/landing-page-font-oj5j32
Aug 1, 2026
Merged

fix(landing): typography fixes across the landing page and feature shots#81
aleexwong merged 4 commits into
mainfrom
claude/landing-page-font-oj5j32

Conversation

@aleexwong

@aleexwong aleexwong commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Three typography defects on the landing page, plus a clipping bug found while verifying them. Each is verified in Chromium rather than reasoned about — details below.

1. Italic text rendered upright

The Google Fonts request asked only for the upright axis of DM Sans (ital,opsz,wght@0,... — the leading 0 means upright only), and :root in src/index.css sets font-synthesis: none. No italic face plus no synthesis means the browser silently renders italics as regular upright text.

Affected the founder pull-quote in the story section and the three use-case testimonial quotes.

Fixed by adding the 1,... italic axis. DM Sans ships a real variable italic covering weights 400–700, so these are true italics, not a synthesised slant. Also collapsed the discrete weight lists to 400..700 ranges — both families are variable fonts, so this serves the same files while making intermediate weights available.

2. Feature-shot numerals fell back to the generic monospace

The animated shots routed every numeric readout through --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace. Outside macOS none of those resolve, so pace times, race predictions, elevation figures and plan details rendered in the generic monospace — DejaVu Sans Mono on Linux, Courier New on Windows. That reads as a third typeface next to DM Sans and Space Grotesk.

It also cost information: the fallback is wide enough to overrun .tp-day-detail's ellipsis clamp, so the training-plan cards showed 5 mi · 8:42/… instead of 5 mi · 8:42/mi.

Switched the readouts to Space Grotesk with tabular figures. Space Grotesk carries a real tnum table — verified in Chromium, tabular-nums holds 1111111111 and 0888888888 to the same width at 400/600/700 — so digits stay column-aligned through the count-up animations, which was the actual reason to reach for a monospace. The large 44:30 and 49.8 numerals already used exactly this treatment, so the small ones now match them.

Worth knowing for future work: DM Sans has no tnum, so font-variant-numeric: tabular-nums is a no-op on it and columns would jitter mid-animation. Anything numeric in these shots needs Space Grotesk.

Switched: .mono, .pl-pace, .vd-pred .t, .el-tip .mi, .ac-zrow .v, .tp-phase-meta .wk, .tp-day-detail.

--mono is kept for the genuinely code-like text in the agent chat shot (tool names, the server handle), matching the api.trainpace.com/api/mcp snippet on the landing itself. Its stack now picks up Cascadia Mono, Segoe UI Mono, Consolas, Roboto Mono and DejaVu Sans Mono so those chips land on a real UI monospace off macOS.

Separately, feature-shots.css declared font-family: "Space Grotesk" with no fallback in seven places — whenever the webfont failed to load, those numerals and titles dropped to the browser default serif. They now go through a --display custom property carrying system-ui, sans-serif, mirroring how --mono was already handled.

3. Hero <h1> requested an unavailable weight

The hero asked for font-extrabold (800), but Space Grotesk's variable range stops at 700 — Google Fonts returns HTTP 400 for a request at 800 — so with synthesis disabled it was already rendering at 700. Changed to font-bold so the markup matches what ships. No visual change.

4. Elevation tooltip clipped at the card edges

Found while verifying the above. The scrub tooltip is centred on the cursor via translate(-50%, -130%), and .stage is overflow: hidden, so at either end of the profile its outer half fell outside the card.

Swept the cursor across the full profile at 1440px and 900px, measuring the tooltip box against the stage on all four edges:

Position 1440px 900px
Mile 0.0 (left) ~30px clipped ~22px clipped
Mile 26.2 (right) ~32px clipped ~24px clipped
Vertical clear clear

The right-edge case is what the shot settles on, since the auto-play demo eases to the end and stops there. The left-edge case is the same bug at the opposite end.

Added a clampTipX helper holding the tooltip inside the stage with a 10px gap. There's no arrow anchoring it to the cursor, so the shift at the extremes is imperceptible. Vertical placement was clear at every sample point and is left alone.

5. Documentation (fc3f689)

These defects were all invisible in source and only showed up under measurement, so the constraints behind them are now written down.

CLAUDE.md gains a Typography section: font-synthesis: none means a face missing from the font URL fails silently rather than being synthesised, Space Grotesk stops at 700, italics need the 1,... axis, Space Grotesk has a tnum table and DM Sans does not, and webfonts always need a fallback stack.

A new verify-in-browser skill (.claude/skills/) records the Playwright setup for this sandbox and the measurement traps that produce confident wrong answers — chiefly that document.fonts.check() returns true for faces that do not exist and so cannot be used to test font availability.

Verification

  • npm run build passes; all 245 prerendered pages carry the updated font stylesheet.
  • npm run lint — 0 errors (96 pre-existing warnings, none in the touched files).
  • Italic face confirmed registered and active in Chromium (canvas metrics differ from upright).
  • Tooltip sweep re-measured after the fix: worst-case overflow 0px on all four edges at both widths, with mid-profile positions unchanged.

No unit tests exist for this area, and the change is visual, so verification is the build/lint pair plus the browser measurements above.

claude added 3 commits July 30, 2026 15:34
The Google Fonts request only asked for the upright axis of DM Sans
(`ital...@0,...`), and `:root` in index.css sets `font-synthesis: none`.
With no italic face and no synthesis, every `italic` element on the
landing page rendered fully upright — the founder pull-quote in the
story section and the three use-case testimonial quotes.

Request the `1,...` italic axis so the real italic face loads, and
collapse the discrete weight lists to `400..700` ranges (both families
are variable fonts, so this serves the same files while also making
intermediate weights available).

Two related cleanups:

- feature-shots.css declared `font-family: "Space Grotesk"` with no
  fallback in seven places. Whenever the webfont failed to load, those
  numerals, scores and titles dropped to the browser default serif
  instead of the sans stack. Route them through a new `--display`
  custom property that carries `system-ui, sans-serif`, matching how
  `--mono` is already handled.

- The hero h1 asked for `font-extrabold` (800), but Space Grotesk's
  variable range stops at 700 — Google Fonts rejects a request for 800
  outright — so with synthesis disabled it already rendered at 700.
  Use `font-bold` so the markup matches what ships.

Verified in Chromium: the italic face is now registered and active
(canvas metrics differ from upright), and all 245 prerendered pages
carry the updated stylesheet link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqMv2dS79q6iDEZKiZ9Rpn
…space

The animated feature shots routed every numeric readout through
`--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace`.
Outside macOS none of those resolve, so pace times, race predictions,
elevation figures and plan details rendered in the generic monospace —
DejaVu Sans Mono on Linux, Courier New on Windows. That reads as a third
typeface next to DM Sans and Space Grotesk, and it is wide: in the
training-plan shot it overran `.tp-day-detail`'s ellipsis clamp, so the
day cards showed "5 mi · 8:42/…" instead of "5 mi · 8:42/mi".

Space Grotesk carries a real `tnum` feature (verified in Chromium:
tabular-nums holds "1111111111" and "0888888888" to the same width at
400/600/700), so the readouts get the brand face plus tabular figures
instead. Digits stay column-aligned through the count-up animations —
which was the actual reason for reaching for a monospace — and the big
`44:30` / `49.8` numerals already used exactly this treatment, so the
small ones now match them.

DM Sans is deliberately not used here: it has no tnum table, so
`font-variant-numeric: tabular-nums` is a no-op on it and the columns
would jitter mid-animation.

Switched: .mono, .pl-pace, .vd-pred .t, .el-tip .mi, .ac-zrow .v,
.tp-phase-meta .wk, .tp-day-detail.

`--mono` stays for the genuinely code-like text in the agent chat shot
(tool names, the server handle), and its stack picks up Cascadia Mono,
Segoe UI Mono, Consolas, Roboto Mono and DejaVu Sans Mono so those chips
land on a real UI monospace off macOS rather than Courier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqMv2dS79q6iDEZKiZ9Rpn
The scrub tooltip is centred on the cursor via translate(-50%, -130%),
so at either end of the profile its outer half fell outside .stage —
which is overflow:hidden, so it got cut. Measured across the full sweep
at 1440px and 900px: ~30px lost off the left at mile 0.0 and ~32px off
the right at mile 26.2 (~22/24px at 900px), enough to swallow the grade
reading. The auto-play demo ends at mile 26.2, so the clipped state is
what the shot settles on.

Clamp the tooltip's centre so its box stays within the stage, with a
10px gap. It has no arrow anchoring it to the cursor, so sliding it at
the extremes is imperceptible, and positions away from the edges are
untouched — re-measured worst-case overflow is 0 on all four edges at
both widths, with mid-profile samples byte-identical to before.

Vertical placement was already clear at every sample point, so it is
left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqMv2dS79q6iDEZKiZ9Rpn
@netlify

netlify Bot commented Aug 1, 2026

Copy link
Copy Markdown

Deploy Preview for trainpace canceled.

Name Link
🔨 Latest commit fc3f689
🔍 Latest deploy log https://app.netlify.com/projects/trainpace/deploys/6a6e474661bcaf0008d62526

@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
trainpace Ready Ready Preview Aug 1, 2026 7:22pm

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 44 complexity · -1 duplication

Metric Results
Complexity 44
Duplication -1

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Both come out of the landing-page font work, where the defects were
invisible in source and only showed up under measurement.

CLAUDE.md gains a Typography section covering the constraints that
caused those bugs: `font-synthesis: none` means a face missing from the
Google Fonts URL fails silently instead of being synthesised, Space
Grotesk stops at 700, italics need the `1,...` axis, Space Grotesk has a
tnum table and DM Sans does not, and webfonts always need a fallback
stack.

The new verify-in-browser skill records the sandbox Playwright recipe —
executablePath for the pinned-version mismatch, the loopback bypass so
localhost is not routed through the agent proxy, and routing Google
Fonts through curl since Chromium cannot reach it — along with the
measurement traps behind this session's wrong turns:
`document.fonts.check()` returning true for faces that do not exist,
computed styles read off a proxy error page that was mistaken for the
app, and a selector that matched one element twice so a two-case claim
rested on a single screenshot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqMv2dS79q6iDEZKiZ9Rpn
@aleexwong
aleexwong merged commit fc3f689 into main Aug 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants