Skip to main content

max / makenotwork

Declare Young Serif as an override, not as a stylesheet nobody unified Layer 0 of the font model, makeover 174ab3c1. The brand tier was already exempt by decision, but the exemption was enforced by Young Serif simply not being in the vocabulary: it reached the page through an @font-face this repo maintained by hand and a --font-heading nothing outside it had heard of. That is the shape the unification deletes everywhere else. build.rs declares it as an override of the display slot and the generated typography.css carries both halves. The @font-face it emits is byte-identical to the one that left style.css, and the .ttf/.woff2 stay committed rather than cut: the face is upstream's, unmodified, and quasi-type has no part in it. --font-heading becomes --font-display, 64 sites. goingson already calls its brand slot --font-display and uses --font-heading for something else entirely, so one name now means one thing tree-wide. No face renders differently. The embeds take their own house-only sheet, embed-typography.css, the same shape as embed-geometry.css and for a reason of the same kind: an embed carries no brand face by decision, and the alternative is 21 KB of a serif on somebody else's page to style one card title. Both sheets come out of one Typography in one build script, so they cannot drift on the slots they share. Also takes quasi 0.19, which makeover-build 0.41.0 forces: it wants makeover-webview 0.47.0 and quasi-webview 0.18 pinned ^0.46, and that crate carries `links`, so two copies in one graph is a resolver error.
Author: Max Johnson <me@maxj.phd> · 2026-08-17 19:11 UTC
Signed with PGP, not checked
Commit: e6f98b611e8084726dce40e58a22ea403c3e7b4f
Parent: 56a4b65
15 files changed, +194 insertions, -145 deletions
M .gitignore +1
@@ -14,6 +14,7 @@
14 14 server/static/layout.css
15 15 server/static/embed-geometry.css
16 16 server/static/typography.css
17 + server/static/embed-typography.css
17 18
18 19 # The house faces, cut by quasi-type in build.rs. Same rule as the stylesheets
19 20 # above and the same rule shop and the Alloy image follow: the pipeline is the
M server/Cargo.lock +30 -28
@@ -5387,9 +5387,9 @@
5387 5387
5388 5388 [[package]]
5389 5389 name = "makeover"
5390 - version = "2.7.0"
5390 + version = "2.8.0"
5391 5391 source = "registry+https://github.com/rust-lang/crates.io-index"
5392 - checksum = "21a3bfb88eb028ac51b3b9cf0358fec753e4cea7a2d651aa11c9b779b5e46a75"
5392 + checksum = "39b782e6e693c5134b50949f8c3407bb7d24a77b7f628281b0490c6380d65151"
5393 5393 dependencies = [
5394 5394 "include_dir",
5395 5395 "serde",
@@ -5398,9 +5398,9 @@
5398 5398
5399 5399 [[package]]
5400 5400 name = "makeover-build"
5401 - version = "0.40.0"
5401 + version = "0.41.0"
5402 5402 source = "registry+https://github.com/rust-lang/crates.io-index"
5403 - checksum = "a6d883a5d60738118d7a862c6e8756578786e60c40463efde98d13d349fa94bc"
5403 + checksum = "12050a224c62044ec8ba0a1b5b64cac7559e24f9e07714a5b826b6b68f7010cd"
5404 5404 dependencies = [
5405 5405 "makeover",
5406 5406 "makeover-geometry",
@@ -5415,15 +5415,11 @@
5415 5415
5416 5416 [[package]]
5417 5417 name = "makeover-layout"
5418 - version = "0.27.5"
5419 - source = "registry+https://github.com/rust-lang/crates.io-index"
5420 - checksum = "ed4a0a89e118db1cc010c72e696741b3701c83bfe6bec5db06e29515b4c1d216"
5418 + version = "0.28.0"
5421 5419
5422 5420 [[package]]
5423 5421 name = "makeover-touch"
5424 - version = "0.19.0"
5425 - source = "registry+https://github.com/rust-lang/crates.io-index"
5426 - checksum = "c09c064316da4e4d403dd09bd0617b25033b2503cba539c02b4b5b857435c089"
5422 + version = "0.20.0"
5427 5423 dependencies = [
5428 5424 "makeover-geometry",
5429 5425 "makeover-layout",
@@ -5431,9 +5427,7 @@
5431 5427
5432 5428 [[package]]
5433 5429 name = "makeover-webview"
5434 - version = "0.46.0"
5435 - source = "registry+https://github.com/rust-lang/crates.io-index"
5436 - checksum = "8389b1ddc25547f992d51334f19a5391c9a8fc36968b5aa02da9da283100b7fc"
5430 + version = "0.47.0"
5437 5431 dependencies = [
5438 5432 "makeover-geometry",
5439 5433 "makeover-layout",
@@ -6640,7 +6634,7 @@
6640 6634
6641 6635 [[package]]
6642 6636 name = "quasi-axum"
6643 - version = "0.18.0"
6637 + version = "0.19.0"
6644 6638 dependencies = [
6645 6639 "axum",
6646 6640 "http 1.4.2",
@@ -6651,7 +6645,7 @@
6651 6645
6652 6646 [[package]]
6653 6647 name = "quasi-basics"
6654 - version = "0.18.0"
6648 + version = "0.19.0"
6655 6649 dependencies = [
6656 6650 "makeover-layout",
6657 6651 "quasi-router",
@@ -6659,7 +6653,7 @@
6659 6653
6660 6654 [[package]]
6661 6655 name = "quasi-http"
6662 - version = "0.18.0"
6656 + version = "0.19.0"
6663 6657 dependencies = [
6664 6658 "form_urlencoded",
6665 6659 "http 1.4.2",
@@ -6668,7 +6662,7 @@
6668 6662
6669 6663 [[package]]
6670 6664 name = "quasi-router"
6671 - version = "0.18.0"
6665 + version = "0.19.0"
6672 6666 dependencies = [
6673 6667 "makeover-layout",
6674 6668 ]
@@ -6691,7 +6685,7 @@
6691 6685
6692 6686 [[package]]
6693 6687 name = "quasi-webview"
6694 - version = "0.18.0"
6688 + version = "0.19.0"
6695 6689 dependencies = [
6696 6690 "docengine",
6697 6691 "makeover-layout",
@@ -10777,16 +10771,20 @@
10777 10771 ]
10778 10772
10779 10773 [[patch.unused]]
10780 - name = "quasi-immediate"
10781 - version = "0.18.0"
10774 + name = "synckit-client"
10775 + version = "0.8.0"
10782 10776
10783 10777 [[patch.unused]]
10784 - name = "quasi-store"
10785 - version = "0.1.0"
10778 + name = "synckit-config"
10779 + version = "0.2.0"
10786 10780
10787 10781 [[patch.unused]]
10788 - name = "quasi-tauri"
10789 - version = "0.18.0"
10782 + name = "makeover-immediate"
10783 + version = "0.28.0"
10784 +
10785 + [[patch.unused]]
10786 + name = "makeover-tui"
10787 + version = "0.27.0"
10790 10788
10791 10789 [[patch.unused]]
10792 10790 name = "kberg"
@@ -10801,9 +10799,13 @@
10801 10799 version = "0.1.0"
10802 10800
10803 10801 [[patch.unused]]
10804 - name = "synckit-client"
10805 - version = "0.8.0"
10802 + name = "quasi-immediate"
10803 + version = "0.19.0"
10806 10804
10807 10805 [[patch.unused]]
10808 - name = "synckit-config"
10809 - version = "0.2.0"
10806 + name = "quasi-store"
10807 + version = "0.1.0"
10808 +
10809 + [[patch.unused]]
10810 + name = "quasi-tauri"
10811 + version = "0.19.0"
@@ -152,28 +152,28 @@
152 152 git-command = { path = "../shared/git-command" }
153 153
154 154 # Shared theme palette + the bundled theme set (Tier 0 creator theming).
155 - makeover = "2.5.0"
155 + makeover = "2.8.0"
156 156
157 157 # The description layer, for the tier G1 spike only: one read-only screen served
158 158 # through quasi beside its Askama equivalent, so the cost of describing a screen
159 159 # here is measured rather than argued. Not load-bearing for any shipped route.
160 160 # See wiki look-wave-2, tier G.
161 - quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.18" }
161 + quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.19" }
162 162 # The description vocabulary quasi's screen types are built from. Pinned here
163 163 # rather than reached through quasi-router's re-export because a described
164 164 # screen names FieldKind and Tone directly; it has to track what quasi-router
165 165 # resolves or the two `layout::` paths are different crates.
166 - makeover-layout = "0.27.5"
166 + makeover-layout = "0.28.0"
167 167 # For the request head the per-viewer state factory reads. axum re-exports it,
168 168 # but the factory's signature is quasi-axum's and names `http::request::Parts`.
169 169 http = "1.3.1"
170 - quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.18" }
171 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.18" }
170 + quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.19" }
171 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.19" }
172 172 # The first-party widget set. The carousel was proved here against three pages
173 173 # and then moved there; what is left in `quasi/widgets/carousel.rs` is the
174 174 # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it
175 175 # sits above quasi-router rather than inside the description suite.
176 - quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.18" }
176 + quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.19" }
177 177
178 178 # Git source browser
179 179 gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] }
@@ -245,7 +245,7 @@
245 245 # Emits static/geometry.css (makeover-geometry) and static/layout.css
246 246 # (makeover-webview). The same generator GO and BB run; only the output paths
247 247 # differ, since the server serves its stylesheets rather than bundling them.
248 - makeover-build = "0.40.0"
248 + makeover-build = "0.41.0"
249 249 # Read directly for the embeds' own copy of the spacing layer, which they need
250 250 # because an iframe cannot link a stylesheet. makeover-build does not re-export
251 251 # it, so the pin lives here and has to track the one makeover-build resolves.
M server/build.rs +37 -3
@@ -50,11 +50,45 @@
50 50 // The typography layer, and the faces it fetches.
51 51 //
52 52 // Two halves that have to agree: makeover_build writes the `@font-face`
53 - // rules and the two tokens, and `cut_house_faces` puts the woff2 files
54 - // where those rules point. Both name the file through
53 + // rules and the tokens, and `cut_house_faces` puts the woff2 files where
54 + // those rules point. Both name the file through
55 55 // `makeover::WEBFONT_*_FILE`, so the agreement is a constant rather than a
56 56 // string typed twice.
57 - makeover_build::typography_css("static/typography.css", "/static/fonts");
57 + //
58 + // Young Serif is the third token, and it is here rather than in style.css
59 + // because that is what layer 0 is for. It used to be a hand-written
60 + // `@font-face` plus a `--font-heading` nothing outside this repository had
61 + // heard of; declared as an override it is the same shape as the two house
62 + // slots, and `--font-display` is the name every product uses for its brand
63 + // tier. The face itself is committed rather than cut: it is upstream's,
64 + // unmodified, and quasi-type has no part in it.
65 + makeover_build::typography_css_from(
66 + "static/typography.css",
67 + &makeover_build::Typography::house("/static/fonts").with_override(
68 + makeover_build::FontOverride::new(
69 + makeover_build::FontSlot::Display,
70 + "\"Young Serif\", serif",
71 + )
72 + .with_face(makeover_build::FontFace::new(
73 + "Young Serif",
74 + ["ysrf.woff2", "ysrf.ttf"],
75 + )),
76 + ),
77 + );
78 +
79 + // The embeds get the house layer WITHOUT the override, for the same reason
80 + // they get their own spacing layer: different context, different answer.
81 + // An embed carries no brand face by decision — `--font-display` is left
82 + // undefined so the `var(--font-display, Georgia, serif)` fallback in each
83 + // template renders — and the alternative is 21 KB of a serif face on
84 + // somebody else's page to style one card title. The two files are
85 + // generated from one `Typography` vocabulary, so they cannot disagree
86 + // about the house slots the way two hand-written sheets did.
87 + makeover_build::typography_css_from(
88 + "static/embed-typography.css",
89 + &makeover_build::Typography::house("/static/fonts"),
90 + );
91 +
58 92 cut_house_faces();
59 93
60 94 println!("cargo::rerun-if-changed=build.rs");
@@ -11,7 +11,7 @@
11 11 | Tier | Token group | Where defined |
12 12 |---|---|---|
13 13 | Color | Surfaces `--surface-page/-raised/-sunken/-overlay/-well` and the bevel pair; content `--content/-secondary/-muted/-on-action`; `--action/-hover`; semantic `--success`, `--warning`, `--danger`, `--info`; `--border/-strong`, `--focus-ring`, `--hover-surface`, `--overlay`; `--category-one`..`--category-six`; fixed anchors `--primary-dark/-light`, `--stripe`, diff (`--diff-add/-bg`, `--diff-del/-bg`) and the base16 Tomorrow `--syntax-*` set | `style.css` `:root` |
14 - | Typeface | `--font-heading` (Young Serif), `--font-mono` (IBM Plex Mono), `--font-body` (Lato) | `style.css:42-44` |
14 + | Typeface | `--font-mono` (Quasi Mono), `--font-sans` (Quasi Body), `--font-display` (Young Serif) | `typography.css` (generated by makeover-build) |
15 15 | Type size | `--text-fine` / `--text-note` / `--text-body` / `--text-lead` / `--text-subhead` / `--text-head` / `--text-title` / `--text-display` / `--text-hero`, named for what the text is, not the size | `geometry.css` (generated by makeover-build) |
16 16 | Spacing | `--gap-bound` / `--gap-peer` / `--gap-group` / `--gap-section` / `--gap-pane` / `--gap-page`, named for the relationship they express, not the size | `geometry.css` (generated by makeover-build) |
17 17 | Radius | `--radius-square` (0, containers: cards, panels, dropdowns), `--radius-fine` (2px, inline code, small badges), `--radius-control` (4px, buttons, inputs, selects), `--radius-panel` (8px, media covers, callout boxes), `--radius-round` (50%), named for what the corner belongs to | `geometry.css` (generated by makeover-build) |
@@ -27,11 +27,16 @@
27 27
28 28 ### Typeface tiers (from `brand.md`)
29 29
30 - - **H1 / wordmark / section heads**: `--font-heading` (Young Serif), `normal` weight, color `--detail`.
31 - - **H2 / H3 / meta / taglines / footer**: `--font-mono` (IBM Plex Mono), `normal` weight.
32 - - **Body / lists / table cells / labels**: `--font-body` (Lato).
30 + - **H1 / wordmark / section heads**: `--font-display` (Young Serif), `normal` weight, color `--detail`.
31 + - **H2 / H3 / meta / taglines / footer**: `--font-mono` (Quasi Mono), `normal` weight.
32 + - **Body / lists / table cells / labels**: `--font-sans` (Quasi Body).
33 33
34 - No fourth typeface. No `font-family` declarations in templates.
34 + Three tokens and no fourth. No `font-family` declarations in templates, and
35 + none of the three is defined in `style.css`: `--font-mono` and `--font-sans`
36 + are the house slots every product shares, and `--font-display` is MNW's own
37 + override of the brand slot, declared in `build.rs`. All three arrive in the
38 + generated `typography.css`. Adding a face means adding an override there, not a
39 + `font-family` here. See wiki `typography-standard`.
35 40
36 41 ### Size tiers
37 42
@@ -26,7 +26,7 @@
26 26 display: flex;
27 27 align-items: center;
28 28 justify-content: center;
29 - font-family: var(--font-heading);
29 + font-family: var(--font-display);
30 30 font-size: var(--text-subhead);
31 31 color: var(--content);
32 32 }
@@ -58,7 +58,7 @@
58 58
59 59 /* Title */
60 60 .media-title {
61 - font-family: var(--font-heading);
61 + font-family: var(--font-display);
62 62 font-size: var(--text-display);
63 63 font-weight: normal;
64 64 line-height: 1.2;
@@ -90,24 +90,18 @@
90 90 when you add a primitive.
91 91 ============================================================================ */
92 92
93 - @font-face {
94 - font-family: "Young Serif";
95 - src: url("/static/fonts/ysrf.woff2") format("woff2"),
96 - url("/static/fonts/ysrf.ttf") format("truetype");
97 - font-display: swap;
98 - }
93 + /* No `@font-face` here at all any more, and no font token either. All three
94 + faces are fetched by the generated `typography.css`: Quasi Mono and Quasi
95 + Body from makeover's house layer, and Young Serif from MNW's own override,
96 + declared in build.rs. What left first: four blocks for IBM Plex Mono and
97 + Lato, regular and bold, 101 KB of woff2 covering two weights, replaced by
98 + 81 KB carrying the whole 200-800 axis. What left second: the Young Serif
99 + block that used to sit here, and the `--font-display` beside it.
99 100
100 - /* The mono and body faces are NOT declared here any more. They are Quasi Mono
101 - and Quasi Body, cut by quasi-type and fetched by the generated
102 - `typography.css`, which also defines `--font-mono` and `--font-sans`. What
103 - left: four `@font-face` blocks for IBM Plex Mono and Lato, regular and bold,
104 - 101 KB of woff2 covering two weights. The two that replaced them are 81 KB
105 - and carry the whole 200-800 axis.
106 -
107 - Young Serif stays above, and stays here rather than moving with them. It is
108 - the brand tier: per product, exempt by decision, and named by
109 - `--font-heading` below, which is MNW's alone. When the app-override layer
110 - lands (makeover 174ab3c1) this is the declaration that moves into it. */
101 + Young Serif is still the brand tier and still MNW's alone. That has not
102 + changed and is not what moved. What changed is that the exemption is a
103 + mechanism now rather than a gap in the vocabulary: the face is declared
104 + once, as an override of the `display` slot, and reads as one. */
111 105
112 106 /* Everything below is in the `components` cascade layer.
113 107
@@ -131,17 +125,14 @@
131 125 overriding this file's :root without needing to know the order. */
132 126 @layer components {
133 127 :root {
134 - /* Typography: the brand tier only.
128 + /* Typography: nothing. All three tokens come from typography.css, which
129 + loads before this file.
135 130
136 - `--font-mono` and `--font-sans` are the house tokens and come from
137 - typography.css, which loads before this file. Do not redefine either here
138 - -- that is the divergence the unification deleted, and this file is the
139 - one that would win.
140 -
141 - `--font-heading` is MNW's own and stays: the display tier is exempt per
142 - product, no renderer emits it, and no described screen resolves a token to
143 - it. */
144 - --font-heading: "Young Serif", serif;
131 + `--font-mono` and `--font-sans` are the house slots and `--font-display`
132 + is MNW's own, an override declared in build.rs. Do not redefine any of the
133 + three here -- that is the divergence the unification deleted, and this
134 + file is the one that would win. A product face belongs in the override,
135 + where the whole tree can read it. */
145 136
146 137 /* --------------------------------------------------------------------
147 138 INTENT LAYER, the shared theme vocabulary (themeable)
@@ -330,7 +321,7 @@
330 321 }
331 322
332 323 h1 {
333 - font-family: var(--font-heading);
324 + font-family: var(--font-display);
334 325 font-weight: normal;
335 326 color: var(--content);
336 327 text-align: center;
@@ -356,7 +347,7 @@
356 347 (existing rule, see SECTIONS block). */
357 348 .brand-h1,
358 349 .page-title {
359 - font-family: var(--font-heading);
350 + font-family: var(--font-display);
360 351 font-weight: normal;
361 352 color: var(--content);
362 353 text-align: center;
@@ -694,7 +685,7 @@
694 685
695 686 input[type="submit"],
696 687 form button {
697 - font-family: var(--font-heading);
688 + font-family: var(--font-display);
698 689 font-size: var(--text-body);
699 690 color: var(--content);
700 691 background: var(--surface-overlay);
@@ -1200,7 +1191,7 @@
1200 1191 .card-title {
1201 1192 font-size: var(--text-subhead);
1202 1193 margin-bottom: var(--gap-peer);
1203 - font-family: var(--font-heading);
1194 + font-family: var(--font-display);
1204 1195 }
1205 1196
1206 1197 .card-meta {
@@ -1241,7 +1232,7 @@
1241 1232 }
1242 1233
1243 1234 .stat-box .number {
1244 - font-family: var(--font-heading);
1235 + font-family: var(--font-display);
1245 1236 font-weight: bold;
1246 1237 font-size: var(--text-head);
1247 1238 }
@@ -1316,7 +1307,7 @@
1316 1307 }
1317 1308
1318 1309 .discussion-section h3 {
1319 - font-family: var(--font-heading);
1310 + font-family: var(--font-display);
1320 1311 font-size: var(--text-subhead);
1321 1312 margin: 0 0 var(--gap-peer);
1322 1313 }
@@ -1868,7 +1859,7 @@
1868 1859 .project-page .item-title {
1869 1860 font-size: var(--text-subhead);
1870 1861 margin-bottom: var(--gap-peer);
1871 - font-family: var(--font-heading);
1862 + font-family: var(--font-display);
1872 1863 font-weight: bold;
1873 1864 }
1874 1865
@@ -1934,7 +1925,7 @@
1934 1925 }
1935 1926
1936 1927 .project-page .tier-name {
1937 - font-family: var(--font-heading);
1928 + font-family: var(--font-display);
1938 1929 font-weight: bold;
1939 1930 font-size: var(--text-subhead);
1940 1931 margin-bottom: var(--gap-peer);
@@ -2147,7 +2138,7 @@
2147 2138 display: flex;
2148 2139 align-items: center;
2149 2140 justify-content: center;
2150 - font-family: var(--font-heading);
2141 + font-family: var(--font-display);
2151 2142 font-size: var(--text-subhead);
2152 2143 color: var(--content);
2153 2144 }
@@ -2170,7 +2161,7 @@
2170 2161 }
2171 2162
2172 2163 .article-page .article-title {
2173 - font-family: var(--font-heading);
2164 + font-family: var(--font-display);
2174 2165 font-size: var(--text-display);
2175 2166 font-weight: normal;
2176 2167 line-height: 1.2;
@@ -2196,7 +2187,7 @@
2196 2187 .article-page .article-body p { margin-bottom: var(--gap-pane); }
2197 2188
2198 2189 .article-page .article-body h1 {
2199 - font-family: var(--font-heading);
2190 + font-family: var(--font-display);
2200 2191 font-size: var(--text-title);
2201 2192 font-weight: normal;
2202 2193 margin-top: var(--gap-page);
@@ -2993,7 +2984,7 @@
2993 2984 .purchase-page .item-title {
2994 2985 font-size: var(--text-subhead);
2995 2986 margin-bottom: var(--gap-peer);
2996 - font-family: var(--font-heading);
2987 + font-family: var(--font-display);
2997 2988 font-weight: bold;
2998 2989 }
2999 2990
@@ -3297,7 +3288,7 @@
3297 3288 gap: var(--gap-section);
3298 3289 }
3299 3290 .cart-subtotal {
3300 - font-family: var(--font-heading);
3291 + font-family: var(--font-display);
3301 3292 font-size: var(--text-lead);
3302 3293 }
3303 3294 .cart-fee-line {
@@ -3568,7 +3559,7 @@
3568 3559 .user-page .link-title {
3569 3560 font-size: var(--text-lead);
3570 3561 margin-bottom: var(--gap-bound);
3571 - font-family: var(--font-heading);
3562 + font-family: var(--font-display);
3572 3563 font-weight: bold;
3573 3564 }
3574 3565
@@ -3577,7 +3568,7 @@
3577 3568 .user-page .project-title {
3578 3569 font-size: var(--text-lead);
3579 3570 margin-bottom: var(--gap-peer);
3580 - font-family: var(--font-heading);
3571 + font-family: var(--font-display);
3581 3572 font-weight: bold;
3582 3573 }
3583 3574
@@ -3659,7 +3650,7 @@
3659 3650 }
3660 3651
3661 3652 .tag-tree-page .tag-card-name {
3662 - font-family: var(--font-heading);
3653 + font-family: var(--font-display);
3663 3654 font-weight: bold;
3664 3655 font-size: var(--text-body);
3665 3656 }
@@ -3702,7 +3693,7 @@
3702 3693 }
3703 3694
3704 3695 .project-paywall-page .paywall-title {
3705 - font-family: var(--font-heading);
3696 + font-family: var(--font-display);
3706 3697 font-size: var(--text-hero);
3707 3698 margin-bottom: var(--gap-peer);
3708 3699 }
@@ -3744,7 +3735,7 @@
3744 3735 }
3745 3736
3746 3737 .project-paywall-page .paywall-tiers .tier-card h3 {
3747 - font-family: var(--font-heading);
3738 + font-family: var(--font-display);
3748 3739 font-weight: bold;
3749 3740 font-size: var(--text-subhead);
3750 3741 margin-bottom: var(--gap-peer);
@@ -3792,7 +3783,7 @@
3792 3783 .collection-page .collection-item-info { flex: 1; }
3793 3784
3794 3785 .collection-page .collection-item-title {
3795 - font-family: var(--font-heading);
3786 + font-family: var(--font-display);
3796 3787 font-weight: bold;
3797 3788 font-size: var(--text-body);
3798 3789 margin-bottom: var(--gap-bound);
@@ -3992,7 +3983,7 @@
3992 3983 .dashboard-user-page .project-info { flex: 1; }
3993 3984
3994 3985 .dashboard-user-page .project-title {
3995 - font-family: var(--font-heading);
3986 + font-family: var(--font-display);
3996 3987 font-weight: bold;
3997 3988 font-size: var(--text-subhead);
3998 3989 margin-bottom: var(--gap-bound);
@@ -4617,7 +4608,7 @@
4617 4608 }
4618 4609
4619 4610 .site-logo {
4620 - font-family: var(--font-heading);
4611 + font-family: var(--font-display);
4621 4612 font-size: var(--text-subhead);
4622 4613 text-decoration: none;
4623 4614 color: var(--content);
@@ -4699,7 +4690,7 @@
4699 4690 }
4700 4691 /* Logo */
4701 4692 .logo {
4702 - font-family: var(--font-heading);
4693 + font-family: var(--font-display);
4703 4694 color: var(--content);
4704 4695 font-size: var(--text-head);
4705 4696 }
@@ -5255,7 +5246,7 @@
5255 5246 }
5256 5247
5257 5248 .paywall-box h2 {
5258 - font-family: var(--font-heading);
5249 + font-family: var(--font-display);
5259 5250 font-weight: normal;
5260 5251 margin-bottom: var(--gap-peer);
5261 5252 }
@@ -5697,7 +5688,7 @@
5697 5688 }
5698 5689
5699 5690 .grid-card-title {
5700 - font-family: var(--font-heading);
5691 + font-family: var(--font-display);
5701 5692 font-size: var(--text-lead);
5702 5693 }
5703 5694
@@ -5789,7 +5780,7 @@
5789 5780 }
5790 5781
5791 5782 .list-item-title {
5792 - font-family: var(--font-heading);
5783 + font-family: var(--font-display);
5793 5784 font-size: var(--text-lead);
5794 5785 }
5795 5786
@@ -5814,7 +5805,7 @@
5814 5805 }
5815 5806
5816 5807 .landing-headline {
5817 - font-family: var(--font-heading);
5808 + font-family: var(--font-display);
5818 5809 font-size: var(--text-head);
5819 5810 margin-bottom: var(--gap-peer);
5820 5811 }
@@ -5997,7 +5988,7 @@
5997 5988 }
5998 5989
5999 5990 .founder-tier-price {
6000 - font-family: var(--font-heading);
5991 + font-family: var(--font-display);
6001 5992 font-size: var(--text-body);
6002 5993 line-height: 1.1;
6003 5994 }
@@ -6028,7 +6019,7 @@
6028 6019 }
6029 6020
6030 6021 .founder-slots-number {
6031 - font-family: var(--font-heading);
6022 + font-family: var(--font-display);
6032 6023 font-size: var(--text-head);
6033 6024 color: var(--action);
6034 6025 font-weight: 700;
@@ -6140,7 +6131,7 @@
6140 6131 }
6141 6132
6142 6133 .tier-price {
6143 - font-family: var(--font-heading);
6134 + font-family: var(--font-display);
6144 6135 font-size: var(--text-lead);
6145 6136 margin-bottom: var(--gap-bound);
6146 6137 }
@@ -6373,13 +6364,13 @@
6373 6364 }
6374 6365
6375 6366 .pricing-currency {
6376 - font-family: var(--font-heading);
6367 + font-family: var(--font-display);
6377 6368 font-size: var(--text-display);
6378 6369 opacity: 0.5;
6379 6370 }
6380 6371
6381 6372 .pricing-input {
6382 - font-family: var(--font-heading);
6373 + font-family: var(--font-display);
6383 6374 font-size: var(--text-display);
6384 6375 width: 5ch;
6385 6376 text-align: center;
@@ -6510,7 +6501,7 @@
6510 6501 place the calculator uses color to make a claim, so they carry the same
6511 6502 weight in both directions: losing is stated as plainly as winning. */
6512 6503 .calc-verdict {
6513 - font-family: var(--font-heading);
6504 + font-family: var(--font-display);
6514 6505 font-size: var(--text-title);
6515 6506 text-align: center;
6516 6507 padding: var(--gap-pane);
@@ -6660,7 +6651,7 @@
6660 6651 .doc-breadcrumb a:hover { text-decoration: underline; }
6661 6652
6662 6653 .doc-title {
6663 - font-family: var(--font-heading);
6654 + font-family: var(--font-display);
6664 6655 font-size: var(--text-display);
6665 6656 font-weight: normal;
6666 6657 line-height: 1.2;
@@ -6678,7 +6669,7 @@
6678 6669 .doc-body p { margin-bottom: var(--gap-pane); }
6679 6670
6680 6671 .doc-body h1 {
6681 - font-family: var(--font-heading);
6672 + font-family: var(--font-display);
6682 6673 font-size: var(--text-title);
6683 6674 font-weight: normal;
6684 6675 margin-top: var(--gap-page);
@@ -6780,7 +6771,7 @@
6780 6771 /* .docs-index takes the shared reading column near the top of this file. */
6781 6772
6782 6773 .docs-index-title {
6783 - font-family: var(--font-heading);
6774 + font-family: var(--font-display);
6784 6775 font-size: var(--text-display);
6785 6776 font-weight: normal;
6786 6777 line-height: 1.2;
@@ -6948,7 +6939,7 @@
6948 6939 }
6949 6940
6950 6941 .use-case-title {
6951 - font-family: var(--font-heading);
6942 + font-family: var(--font-display);
6952 6943 font-size: var(--text-lead);
6953 6944 margin-bottom: var(--gap-bound);
6954 6945 }
@@ -7828,7 +7819,7 @@
7828 7819 .discover-layout.no-sidebar .discover-sidebar { display: none; }
7829 7820 .discover-sidebar { font-size: var(--text-note); }
7830 7821 .filter-section { margin-bottom: var(--gap-section); }
7831 - .filter-title { font-family: var(--font-heading); font-weight: bold; font-size: var(--text-note); margin-bottom: var(--gap-peer); color: var(--content); }
7822 + .filter-title { font-family: var(--font-display); font-weight: bold; font-size: var(--text-note); margin-bottom: var(--gap-peer); color: var(--content); }
7832 7823 .filter-list { list-style: none; }
7833 7824 /* The row carries selection (`.is-selected` recipe) and hover; the filter itself
7834 7825 is a real <button> child so Enter/Space work without scripting. */
@@ -8355,7 +8346,7 @@
8355 8346 }
8356 8347 .checklist-title {
8357 8348 margin: 0;
8358 - font-family: var(--font-heading);
8349 + font-family: var(--font-display);
8359 8350 font-weight: bold;
8360 8351 }
8361 8352 .checklist-mark {
@@ -8568,7 +8559,7 @@
8568 8559 flex-wrap: wrap;
8569 8560 }
8570 8561 .creator-plan-name {
8571 - font-family: var(--font-heading);
8562 + font-family: var(--font-display);
8572 8563 font-weight: bold;
8573 8564 font-size: var(--text-lead);
8574 8565 }
@@ -8602,7 +8593,7 @@
8602 8593 text-align: center;
8603 8594 }
8604 8595 .creator-tier-name {
8605 - font-family: var(--font-heading);
8596 + font-family: var(--font-display);
8606 8597 font-weight: bold;
8607 8598 font-size: var(--text-body);
8608 8599 margin-bottom: var(--gap-bound);
@@ -9324,7 +9315,7 @@
9324 9315 }
9325 9316 .proj-overview-setup-title {
9326 9317 margin: 0 0 var(--gap-section) 0;
9327 - font-family: var(--font-heading);
9318 + font-family: var(--font-display);
9328 9319 font-weight: bold;
9329 9320 }
9330 9321 .proj-overview-setup-row {
@@ -9367,7 +9358,7 @@
9367 9358 .proj-overview-tools-summary {
9368 9359 cursor: pointer;
9369 9360 font-size: var(--text-body);
9370 - font-family: var(--font-heading);
9361 + font-family: var(--font-display);
9371 9362 font-weight: bold;
9372 9363 }
9373 9364 .proj-overview-tools-grid {
@@ -9585,7 +9576,7 @@
9585 9576 .join-complete-choice:hover .join-complete-card,
9586 9577 .join-complete-card--active { border-color: var(--content); }
9587 9578 .join-complete-card-title {
9588 - font-family: var(--font-heading); font-weight: bold;
9579 + font-family: var(--font-display); font-weight: bold;
9589 9580 font-size: var(--text-lead); margin-bottom: var(--gap-peer);
9590 9581 }
9591 9582 .join-complete-card-desc {
@@ -10798,7 +10789,7 @@
10798 10789 /* Library-downloads description <details> summary + body. */
10799 10790 .library-page .lib-summary {
10800 10791 cursor: pointer;
10801 - font-family: var(--font-heading);
10792 + font-family: var(--font-display);
10802 10793 font-size: var(--text-subhead);
10803 10794 }
10804 10795 .library-page .lib-summary-body { margin-top: var(--gap-section); }
@@ -11763,7 +11754,7 @@
11763 11754 all-fuzzy page there is no preceding row, and the same border supplies the
11764 11755 table's missing top edge. */
11765 11756 .results-tier-label {
11766 - font-family: var(--font-heading);
11757 + font-family: var(--font-display);
11767 11758 font-weight: bold;
11768 11759 font-size: var(--text-fine);
11769 11760 text-transform: uppercase;
@@ -21,7 +21,7 @@
21 21 }
22 22
23 23 .wizard-sidebar h2 {
24 - font-family: var(--font-heading); font-weight: bold;
24 + font-family: var(--font-display); font-weight: bold;
25 25 font-size: var(--text-subhead);
26 26 margin-bottom: var(--gap-pane);
27 27 }
@@ -107,7 +107,7 @@
107 107 }
108 108
109 109 .wizard-step h2 {
110 - font-family: var(--font-heading); font-weight: bold;
110 + font-family: var(--font-display); font-weight: bold;
111 111 font-size: var(--text-head);
112 112 margin-bottom: var(--gap-bound);
113 113 }
@@ -1,7 +1,7 @@
1 1 <div style="max-width: 400px; text-align: center;">
2 2 <header style="margin-bottom: 2rem;">
3 - <div style="width: 100px; height: 100px; background: var(--light-background); margin: 0 auto 1rem; display: flex; align-items: center; justify-content: center; font-size: 2.5rem; opacity: 0.5; font-family: var(--font-heading);">AK</div>
4 - <h1 style="font-size: 1.8rem; margin-bottom: 0.25rem; font-family: var(--font-heading);">Ada Karras</h1>
3 + <div style="width: 100px; height: 100px; background: var(--light-background); margin: 0 auto 1rem; display: flex; align-items: center; justify-content: center; font-size: 2.5rem; opacity: 0.5; font-family: var(--font-display);">AK</div>
4 + <h1 style="font-size: 1.8rem; margin-bottom: 0.25rem; font-family: var(--font-display);">Ada Karras</h1>
5 5 <div style="font-size: 0.9rem; opacity: 0.7; margin-bottom: 0.75rem;">adakarras</div>
6 6 <p style="font-size: 0.95rem; max-width: 340px; margin: 0 auto;">Ambient composer and field recordist. New album every spring. Samples and stems available for remix.</p>
7 7 <div style="margin-top: 0.75rem; display: flex; gap: 0.75rem; justify-content: center; align-items: center;">
@@ -15,7 +15,7 @@
15 15
16 16 <section style="margin-bottom: 1.5rem;">
17 17 <div class="card" style="text-align: center; padding: 0.75rem 1rem;">
18 - <div style="font-family: var(--font-heading); font-weight: bold; font-size: 1rem;">adakarras.example/music</div>
18 + <div style="font-family: var(--font-display); font-weight: bold; font-size: 1rem;">adakarras.example/music</div>
19 19 <div style="font-size: 0.8rem; opacity: 0.7;">Back catalog and vinyl</div>
20 20 </div>
21 21 </section>
@@ -23,12 +23,12 @@
23 23 <section>
24 24 <h2 class="section-header" style="font-size: 1rem; text-align: left;">Projects</h2>
25 25 <a class="card" style="display: block; text-decoration: none; color: inherit;">
26 - <div style="font-family: var(--font-heading); font-weight: bold; font-size: 1.05rem; margin-bottom: 0.25rem;">Liminality</div>
26 + <div style="font-family: var(--font-display); font-weight: bold; font-size: 1.05rem; margin-bottom: 0.25rem;">Liminality</div>
27 27 <div style="font-size: 0.8rem; opacity: 0.7; margin-bottom: 0.25rem;">Music &middot; 8 items</div>
28 28 <div style="font-size: 0.85rem; opacity: 0.8;">Field recordings and processed ambient. Spring 2026.</div>
29 29 </a>
30 30 <a class="card" style="display: block; text-decoration: none; color: inherit;">
31 - <div style="font-family: var(--font-heading); font-weight: bold; font-size: 1.05rem; margin-bottom: 0.25rem;">Sample Library</div>
31 + <div style="font-family: var(--font-display); font-weight: bold; font-size: 1.05rem; margin-bottom: 0.25rem;">Sample Library</div>
32 32 <div style="font-size: 0.8rem; opacity: 0.7; margin-bottom: 0.25rem;">Sample &middot; 24 items</div>
33 33 <div style="font-size: 0.85rem; opacity: 0.8;">Field recordings, textures, and atmospheres for your productions.</div>
34 34 </a>
@@ -16,9 +16,15 @@
16 16
17 17 /// The typography layer, inlined for the same reason as the spacing one.
18 18 ///
19 - /// The very file the pages link, not a second copy: geometry needs its own
20 - /// because an embed runs at Pointer density and no `@media` block, and nothing
21 - /// about the fonts differs that way.
19 + /// Its own file, like geometry's, and for a reason of the same shape: geometry
20 + /// differs because an embed runs at Pointer density with no `@media` block,
21 + /// and typography differs because **an embed carries the house slots and not
22 + /// MNW's brand override**. Young Serif reaches the site through layer 0, but an
23 + /// embed leaves `--font-display` undefined so the
24 + /// `var(--font-display, Georgia, serif)` fallback in each template renders.
25 + /// That is the older decision unchanged, now stated as a second `Typography`
26 + /// rather than as a token nobody defined; both files come out of one
27 + /// vocabulary in build.rs, so they cannot drift on the slots they share.
22 28 ///
23 29 /// The `@font-face` URLs are absolute (`/static/fonts/...`) and the iframe's
24 30 /// document is served from this origin, so they resolve here rather than
@@ -34,7 +40,7 @@
34 40 /// like our product and one that looks like whatever the visitor's browser
35 41 /// prefers, and it is not close. `font-display: swap` keeps the card readable
36 42 /// while the faces arrive.
37 - pub const EMBED_TYPOGRAPHY_CSS: &str = include_str!("../../static/typography.css");
43 + pub const EMBED_TYPOGRAPHY_CSS: &str = include_str!("../../static/embed-typography.css");
38 44
39 45 /// The colour layer for an embed: the platform default theme, resolved through
40 46 /// makeover the same way every page's is.
@@ -12,12 +12,14 @@
12 12 tokens from makeover, with the faces they fetch. Do not write a literal
13 13 below: nothing would catch it drifting.
14 14
15 - `--font-heading` is deliberately NOT defined here and its fallback is what
15 + `--font-display` is deliberately NOT defined here and its fallback is what
16 16 renders. The display tier is per product and an embed carries no brand
17 17 face; the old `'Young Serif', Georgia, serif` fell through to Georgia for
18 18 the same reason, since the face was only ever declared in style.css, which
19 - an embed does not load. When the app-override layer lands this is where
20 - MNW's own declaration would arrive. */
19 + an embed does not load. The app-override layer has landed and the decision
20 + did not change: the site's build declares Young Serif as an override and
21 + the embeds take a second, house-only typography sheet, so the fallback
22 + below is still what a host page gets. */
21 23 {{ theme_css|safe }}
22 24 {{ geometry_css|safe }}
23 25 {{ typography_css|safe }}