Skip to content

Pair Space Grotesk with Inter, from one variable file each - #22

Merged
seenu-k merged 1 commit into
mainfrom
feat/brand-typography
Aug 7, 2026
Merged

Pair Space Grotesk with Inter, from one variable file each#22
seenu-k merged 1 commit into
mainfrom
feat/brand-typography

Conversation

@seenu-k

@seenu-k seenu-k commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Typography and the default theme, as the first step of the brand work. Colour on the engine's own pages and the docs site follows from the decision made here.

The static weights were working around a fixed bug

Nine static Inter weights were fetched from fonts.gstatic.com by content hash. That existed because FontWeight did not drive the wght axis — one variable file meant one rendered weight. It landed in Flutter 3.41, and this package already requires 3.44.

So the workaround outlived the problem, and cost:

Before After
Files 9 2
Payload 2.7 MB 988 KB
Families Inter Inter + Space Grotesk
opsz axis absent from the hashed build present, resting at its default of 14

Both are declared with no weight: entries — adding them puts Flutter back into matching a file per weight and defeats the axis.

Measured, not assumed

A variable font whose axis is ignored still lays out; it just lays out the same at every weight. That is exactly the failure the static weights were hiding, so the test measures rendered width rather than trusting the release notes:

  • Inter w300 < w400 < w700
  • Space Grotesk w300 < w700
  • w450 lands between w400 and w500 — an intermediate weight no static file ever provided
  • the two families measure differently, which is how a font wiring mistake usually hides: everything still renders, in the same face

A flutter build web of the example confirms both faces reach the bundle and register under the package-qualified names the theme uses.

Why the roles split where they do

Space Grotesk takes display and headline only. It is drawn for size — tight sidebearings, single-storey g — which reads as character in a headline and as noise in a paragraph. Inter is drawn for UI at small sizes and keeps everything a player reads at length, including the title role, which labels list rows rather than heading a screen.

The families are applied through the text theme rather than ThemeData.fontFamily, so sizes, weights, tracking and line heights stay exactly as Material 3 defines them. That is what should carry this through to Material Expressive, which changes the scale and shapes rather than the mechanism.

Defaults that look deliberate

Branding.seedColor now defaults to the EigenInteractive teal and Branding.displayFontFamily to Space Grotesk. A scaffolded game looks intentional before anyone has thought about colour, instead of looking like Material's baseline purple, and each is one line to make its own.

Worth recording for whoever picks a seed later: Material 3 treats it as a hue, not a colour. It pulls whatever you give it to tone 40 and rebuilds the ramp, so #2f6b5e, Colors.teal and Colors.tealAccent all arrive within a few percent of each other — and a near-black seed comes back chromatic, not neutral.

The download script is gone rather than updated

fonts.google.com/download?family=… answers with the site's HTML rather than an archive — I checked — so anything automated here would be scraping a page free to change. fonts/README.md documents provenance and the update procedure instead, beside the OFL notices the licence requires to travel with the files.

Verified

dart analyze clean, flutter test 197 passing (5 theme + 4 measurement tests new), flutter build web --release on the example.

🤖 Generated with Claude Code

Nine static Inter weights were fetched from gstatic by content hash, which
pinned a build of Inter with no `opsz` axis, could be verified against nothing,
and cost 2.7 MB. They existed to work around `FontWeight` not driving the
`wght` axis — fixed in Flutter 3.41, and this package already requires 3.44.

Both faces are now single variable files with no per-weight entries, together
under 1 MB, and every weight is the axis moving rather than a file being
matched. A test measures that rather than trusting it: laid-out width rises
across w300/w400/w700, w450 lands between w400 and w500 where no static file
existed, and the two families measure differently, which is how a font wiring
mistake usually hides — everything still renders.

Space Grotesk takes the display and headline roles. It is drawn for size, and
its tight sidebearings read as character in a headline and as noise in a
paragraph, so the title role stays on Inter too: it labels list rows rather
than heading a screen. The families are applied through the text theme, not
`ThemeData.fontFamily`, so every other property of the Material 3 type scale
stays as the framework defines it — which is what should carry this through to
Material Expressive.

`Branding.seedColor` defaults to the EigenInteractive teal and
`Branding.displayFontFamily` to Space Grotesk, so a game looks deliberate
before anyone has configured it and is one line each to make its own.

The download script is deleted rather than updated: `fonts.google.com/download`
answers with the site's HTML rather than an archive, so anything automated
would be scraping a page free to change. fonts/README.md documents the
provenance instead, alongside the OFL notices the licence requires to travel
with the files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@seenu-k
seenu-k merged commit 7dd4b7a into main Aug 7, 2026
3 checks passed
@seenu-k
seenu-k deleted the feat/brand-typography branch August 7, 2026 06:53
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.

1 participant