max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
31 files changed,
+504 insertions,
-315 deletions
| @@ -13,6 +13,15 @@ | |||
| 13 | 13 | server/static/geometry.css | |
| 14 | 14 | server/static/layout.css | |
| 15 | 15 | server/static/embed-geometry.css | |
| 16 | + | server/static/typography.css | |
| 17 | + | ||
| 18 | + | # The house faces, cut by quasi-type in build.rs. Same rule as the stylesheets | |
| 19 | + | # above and the same rule shop and the Alloy image follow: the pipeline is the | |
| 20 | + | # source, and a committed face is a second one that nothing rebuilds. | |
| 21 | + | server/static/fonts/QuasiMono.woff2 | |
| 22 | + | server/static/fonts/QuasiBody.woff2 | |
| 23 | + | server/static/fonts/OFL-QuasiMono.txt | |
| 24 | + | server/static/fonts/OFL-QuasiBody.txt | |
| 16 | 25 | ||
| 17 | 26 | # Environment files (contain secrets) | |
| 18 | 27 | server/.env |
| @@ -104,6 +104,21 @@ | |||
| 104 | 104 | "memchr", | |
| 105 | 105 | ] | |
| 106 | 106 | ||
| 107 | + | [[package]] | |
| 108 | + | name = "alloc-no-stdlib" | |
| 109 | + | version = "2.0.4" | |
| 110 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 111 | + | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" | |
| 112 | + | ||
| 113 | + | [[package]] | |
| 114 | + | name = "alloc-stdlib" | |
| 115 | + | version = "0.2.4" | |
| 116 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 117 | + | checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" | |
| 118 | + | dependencies = [ | |
| 119 | + | "alloc-no-stdlib", | |
| 120 | + | ] | |
| 121 | + | ||
| 107 | 122 | [[package]] | |
| 108 | 123 | name = "allocator-api2" | |
| 109 | 124 | version = "0.2.21" | |
| @@ -1432,6 +1447,27 @@ | |||
| 1432 | 1447 | "cfg_aliases", | |
| 1433 | 1448 | ] | |
| 1434 | 1449 | ||
| 1450 | + | [[package]] | |
| 1451 | + | name = "brotli" | |
| 1452 | + | version = "8.0.4" | |
| 1453 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1454 | + | checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" | |
| 1455 | + | dependencies = [ | |
| 1456 | + | "alloc-no-stdlib", | |
| 1457 | + | "alloc-stdlib", | |
| 1458 | + | "brotli-decompressor", | |
| 1459 | + | ] | |
| 1460 | + | ||
| 1461 | + | [[package]] | |
| 1462 | + | name = "brotli-decompressor" | |
| 1463 | + | version = "5.0.3" | |
| 1464 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1465 | + | checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" | |
| 1466 | + | dependencies = [ | |
| 1467 | + | "alloc-no-stdlib", | |
| 1468 | + | "alloc-stdlib", | |
| 1469 | + | ] | |
| 1470 | + | ||
| 1435 | 1471 | [[package]] | |
| 1436 | 1472 | name = "bs58" | |
| 1437 | 1473 | version = "0.5.1" | |
| @@ -1466,6 +1502,20 @@ | |||
| 1466 | 1502 | version = "1.25.0" | |
| 1467 | 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1468 | 1504 | checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" | |
| 1505 | + | dependencies = [ | |
| 1506 | + | "bytemuck_derive", | |
| 1507 | + | ] | |
| 1508 | + | ||
| 1509 | + | [[package]] | |
| 1510 | + | name = "bytemuck_derive" | |
| 1511 | + | version = "1.12.0" | |
| 1512 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1513 | + | checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" | |
| 1514 | + | dependencies = [ | |
| 1515 | + | "proc-macro2", | |
| 1516 | + | "quote", | |
| 1517 | + | "syn 3.0.3", | |
| 1518 | + | ] | |
| 1469 | 1519 | ||
| 1470 | 1520 | [[package]] | |
| 1471 | 1521 | name = "byteorder" | |
| @@ -2845,6 +2895,15 @@ | |||
| 2845 | 2895 | "windows-sys 0.48.0", | |
| 2846 | 2896 | ] | |
| 2847 | 2897 | ||
| 2898 | + | [[package]] | |
| 2899 | + | name = "euclid" | |
| 2900 | + | version = "0.22.14" | |
| 2901 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2902 | + | checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" | |
| 2903 | + | dependencies = [ | |
| 2904 | + | "num-traits", | |
| 2905 | + | ] | |
| 2906 | + | ||
| 2848 | 2907 | [[package]] | |
| 2849 | 2908 | name = "event-listener" | |
| 2850 | 2909 | version = "5.4.1" | |
| @@ -3001,6 +3060,15 @@ | |||
| 3001 | 3060 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3002 | 3061 | checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" | |
| 3003 | 3062 | ||
| 3063 | + | [[package]] | |
| 3064 | + | name = "font-types" | |
| 3065 | + | version = "0.12.3" | |
| 3066 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3067 | + | checksum = "75382bc7392ef10aad10935f92fc3db36d2d4dad0e5d96d8d65e04f89a07ec39" | |
| 3068 | + | dependencies = [ | |
| 3069 | + | "bytemuck", | |
| 3070 | + | ] | |
| 3071 | + | ||
| 3004 | 3072 | [[package]] | |
| 3005 | 3073 | name = "form_urlencoded" | |
| 3006 | 3074 | version = "1.2.2" | |
| @@ -3598,7 +3666,7 @@ | |||
| 3598 | 3666 | checksum = "1a791e6620676a875f362f3156ed213e73ca099a09bf992c18812abe65cc37b1" | |
| 3599 | 3667 | dependencies = [ | |
| 3600 | 3668 | "bstr", | |
| 3601 | - | "hashbrown 0.15.5", | |
| 3669 | + | "hashbrown 0.17.1", | |
| 3602 | 3670 | ] | |
| 3603 | 3671 | ||
| 3604 | 3672 | [[package]] | |
| @@ -4956,6 +5024,18 @@ | |||
| 4956 | 5024 | "typewit", | |
| 4957 | 5025 | ] | |
| 4958 | 5026 | ||
| 5027 | + | [[package]] | |
| 5028 | + | name = "kurbo" | |
| 5029 | + | version = "0.13.1" | |
| 5030 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5031 | + | checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" | |
| 5032 | + | dependencies = [ | |
| 5033 | + | "arrayvec", | |
| 5034 | + | "euclid", | |
| 5035 | + | "polycool", | |
| 5036 | + | "smallvec", | |
| 5037 | + | ] | |
| 5038 | + | ||
| 4959 | 5039 | [[package]] | |
| 4960 | 5040 | name = "lazy_static" | |
| 4961 | 5041 | version = "1.5.0" | |
| @@ -5257,6 +5337,7 @@ | |||
| 5257 | 5337 | "quasi-axum", | |
| 5258 | 5338 | "quasi-basics", | |
| 5259 | 5339 | "quasi-router", | |
| 5340 | + | "quasi-type", | |
| 5260 | 5341 | "quasi-webview", | |
| 5261 | 5342 | "rand 0.10.2", | |
| 5262 | 5343 | "regex", | |
| @@ -5306,9 +5387,7 @@ | |||
| 5306 | 5387 | ||
| 5307 | 5388 | [[package]] | |
| 5308 | 5389 | name = "makeover" | |
| 5309 | - | version = "2.6.0" | |
| 5310 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5311 | - | checksum = "63f2504fc551cc50ca2766440ee1addd2452bfad02ec203411afe94a7d0a47a7" | |
| 5390 | + | version = "2.7.0" | |
| 5312 | 5391 | dependencies = [ | |
| 5313 | 5392 | "include_dir", | |
| 5314 | 5393 | "serde", | |
| @@ -5317,9 +5396,7 @@ | |||
| 5317 | 5396 | ||
| 5318 | 5397 | [[package]] | |
| 5319 | 5398 | name = "makeover-build" | |
| 5320 | - | version = "0.39.0" | |
| 5321 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5322 | - | checksum = "52baae176ab62ac8d79238301be5dc112f1615630f647ab1465b57b5872edee0" | |
| 5399 | + | version = "0.40.0" | |
| 5323 | 5400 | dependencies = [ | |
| 5324 | 5401 | "makeover", | |
| 5325 | 5402 | "makeover-geometry", | |
| @@ -6228,6 +6305,15 @@ | |||
| 6228 | 6305 | "universal-hash 0.6.1", | |
| 6229 | 6306 | ] | |
| 6230 | 6307 | ||
| 6308 | + | [[package]] | |
| 6309 | + | name = "polycool" | |
| 6310 | + | version = "0.4.0" | |
| 6311 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 6312 | + | checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" | |
| 6313 | + | dependencies = [ | |
| 6314 | + | "arrayvec", | |
| 6315 | + | ] | |
| 6316 | + | ||
| 6231 | 6317 | [[package]] | |
| 6232 | 6318 | name = "polyval" | |
| 6233 | 6319 | version = "0.6.2" | |
| @@ -6583,6 +6669,22 @@ | |||
| 6583 | 6669 | "makeover-layout", | |
| 6584 | 6670 | ] | |
| 6585 | 6671 | ||
| 6672 | + | [[package]] | |
| 6673 | + | name = "quasi-type" | |
| 6674 | + | version = "0.1.0" | |
| 6675 | + | dependencies = [ | |
| 6676 | + | "brotli", | |
| 6677 | + | "font-types", | |
| 6678 | + | "kurbo", | |
| 6679 | + | "read-fonts", | |
| 6680 | + | "serde", | |
| 6681 | + | "sha2 0.10.9", | |
| 6682 | + | "skrifa", | |
| 6683 | + | "toml 1.1.3+spec-1.1.0", | |
| 6684 | + | "write-fonts", | |
| 6685 | + | "zip 6.0.0", | |
| 6686 | + | ] | |
| 6687 | + | ||
| 6586 | 6688 | [[package]] | |
| 6587 | 6689 | name = "quasi-webview" | |
| 6588 | 6690 | version = "0.18.0" | |
| @@ -6871,6 +6973,17 @@ | |||
| 6871 | 6973 | "cipher 0.4.4", | |
| 6872 | 6974 | ] | |
| 6873 | 6975 | ||
| 6976 | + | [[package]] | |
| 6977 | + | name = "read-fonts" | |
| 6978 | + | version = "0.43.0" | |
| 6979 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 6980 | + | checksum = "a6b5cae85882cfb0167038e7726a426f47ebd808e67af78c2d6bcbb02acbf3ce" | |
| 6981 | + | dependencies = [ | |
| 6982 | + | "bytemuck", | |
| 6983 | + | "font-types", | |
| 6984 | + | "once_cell", | |
| 6985 | + | ] | |
| 6986 | + | ||
| 6874 | 6987 | [[package]] | |
| 6875 | 6988 | name = "redox_syscall" | |
| 6876 | 6989 | version = "0.5.18" | |
| @@ -7884,6 +7997,16 @@ | |||
| 7884 | 7997 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 7885 | 7998 | checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" | |
| 7886 | 7999 | ||
| 8000 | + | [[package]] | |
| 8001 | + | name = "skrifa" | |
| 8002 | + | version = "0.46.0" | |
| 8003 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 8004 | + | checksum = "de0bbcc507e438bfe765692ae5228059959781db7430fe36e1cfd569e7c88c89" | |
| 8005 | + | dependencies = [ | |
| 8006 | + | "bytemuck", | |
| 8007 | + | "read-fonts", | |
| 8008 | + | ] | |
| 8009 | + | ||
| 7887 | 8010 | [[package]] | |
| 7888 | 8011 | name = "slab" | |
| 7889 | 8012 | version = "0.4.12" | |
| @@ -10144,6 +10267,19 @@ | |||
| 10144 | 10267 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 10145 | 10268 | checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" | |
| 10146 | 10269 | ||
| 10270 | + | [[package]] | |
| 10271 | + | name = "write-fonts" | |
| 10272 | + | version = "0.52.0" | |
| 10273 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 10274 | + | checksum = "843cebb7bd36359861dbce43d54ac8b33dfdf7b80a95913adc1ef7c7bb881e62" | |
| 10275 | + | dependencies = [ | |
| 10276 | + | "font-types", | |
| 10277 | + | "indexmap 2.14.0", | |
| 10278 | + | "kurbo", | |
| 10279 | + | "log", | |
| 10280 | + | "read-fonts", | |
| 10281 | + | ] | |
| 10282 | + | ||
| 10147 | 10283 | [[package]] | |
| 10148 | 10284 | name = "writeable" | |
| 10149 | 10285 | version = "0.6.3" | |
| @@ -10532,6 +10668,20 @@ | |||
| 10532 | 10668 | "zopfli", | |
| 10533 | 10669 | ] | |
| 10534 | 10670 | ||
| 10671 | + | [[package]] | |
| 10672 | + | name = "zip" | |
| 10673 | + | version = "6.0.0" | |
| 10674 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 10675 | + | checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" | |
| 10676 | + | dependencies = [ | |
| 10677 | + | "arbitrary", | |
| 10678 | + | "crc32fast", | |
| 10679 | + | "flate2", | |
| 10680 | + | "indexmap 2.14.0", | |
| 10681 | + | "memchr", | |
| 10682 | + | "zopfli", | |
| 10683 | + | ] | |
| 10684 | + | ||
| 10535 | 10685 | [[package]] | |
| 10536 | 10686 | name = "zip" | |
| 10537 | 10687 | version = "8.6.0" | |
| @@ -10623,12 +10773,16 @@ | |||
| 10623 | 10773 | ] | |
| 10624 | 10774 | ||
| 10625 | 10775 | [[patch.unused]] | |
| 10626 | - | name = "synckit-client" | |
| 10627 | - | version = "0.8.0" | |
| 10776 | + | name = "quasi-immediate" | |
| 10777 | + | version = "0.18.0" | |
| 10628 | 10778 | ||
| 10629 | 10779 | [[patch.unused]] | |
| 10630 | - | name = "synckit-config" | |
| 10631 | - | version = "0.2.0" | |
| 10780 | + | name = "quasi-store" | |
| 10781 | + | version = "0.1.0" | |
| 10782 | + | ||
| 10783 | + | [[patch.unused]] | |
| 10784 | + | name = "quasi-tauri" | |
| 10785 | + | version = "0.18.0" | |
| 10632 | 10786 | ||
| 10633 | 10787 | [[patch.unused]] | |
| 10634 | 10788 | name = "kberg" | |
| @@ -10643,13 +10797,9 @@ | |||
| 10643 | 10797 | version = "0.1.0" | |
| 10644 | 10798 | ||
| 10645 | 10799 | [[patch.unused]] | |
| 10646 | - | name = "quasi-immediate" | |
| 10647 | - | version = "0.18.0" | |
| 10800 | + | name = "synckit-client" | |
| 10801 | + | version = "0.8.0" | |
| 10648 | 10802 | ||
| 10649 | 10803 | [[patch.unused]] | |
| 10650 | - | name = "quasi-store" | |
| 10651 | - | version = "0.1.0" | |
| 10652 | - | ||
| 10653 | - | [[patch.unused]] | |
| 10654 | - | name = "quasi-tauri" | |
| 10655 | - | version = "0.18.0" | |
| 10804 | + | name = "synckit-config" | |
| 10805 | + | version = "0.2.0" |
| @@ -245,11 +245,22 @@ | |||
| 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.39.0" | |
| 248 | + | makeover-build = "0.40.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. | |
| 252 | 252 | makeover-geometry = "0.7" | |
| 253 | + | # Cuts the two house faces into `static/fonts/` at build time. It is a git | |
| 254 | + | # dependency because quasi-type is `publish = false`, which is also why | |
| 255 | + | # makeover-build cannot do this and only emits the CSS that fetches the result. | |
| 256 | + | # | |
| 257 | + | # The rev is pinned, and it is the same rev shop's `shop-font` and Alloy's | |
| 258 | + | # `QUASI_TYPE_REV` name. That is the point of pinning it here: the house face is | |
| 259 | + | # one face, so the one a browser downloads has to be the one the image installs | |
| 260 | + | # and the terminal bundles. The three move together, in one pass, or a user sees | |
| 261 | + | # two Quasi Monos. Under the tree's `[patch]` block the rev is ignored and the | |
| 262 | + | # working copy is what builds. | |
| 263 | + | quasi-type = { git = "https://makenot.work/git/max/quasi-type.git", version = "0.1", rev = "cbbb9e3dd6faa29582b9437c0685ff91af97afdb" } | |
| 253 | 264 | ||
| 254 | 265 | [dev-dependencies] | |
| 255 | 266 | tower = { version = "0.5.3", features = ["util"] } |
| @@ -47,6 +47,16 @@ | |||
| 47 | 47 | ) | |
| 48 | 48 | .expect("write static/embed-geometry.css"); | |
| 49 | 49 | ||
| 50 | + | // The typography layer, and the faces it fetches. | |
| 51 | + | // | |
| 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 | |
| 55 | + | // `makeover::WEBFONT_*_FILE`, so the agreement is a constant rather than a | |
| 56 | + | // string typed twice. | |
| 57 | + | makeover_build::typography_css("static/typography.css", "/static/fonts"); | |
| 58 | + | cut_house_faces(); | |
| 59 | + | ||
| 50 | 60 | println!("cargo::rerun-if-changed=build.rs"); | |
| 51 | 61 | ||
| 52 | 62 | // The landing shots' own dimensions, so the description can reserve their | |
| @@ -166,6 +176,66 @@ | |||
| 166 | 176 | write_if_changed(Path::new("templates/_sheet.html"), &sheet_partial); | |
| 167 | 177 | } | |
| 168 | 178 | ||
| 179 | + | /// Cut Quasi Mono and Quasi Body into `static/fonts/`, as woff2. | |
| 180 | + | /// | |
| 181 | + | /// Cut rather than committed, which is the same rule `shop-font` and the Alloy | |
| 182 | + | /// image follow: a face in the repository is a second source of truth that | |
| 183 | + | /// nothing rebuilds, so the glyph set and the shipped face drift and nobody | |
| 184 | + | /// finds out until a mark looks wrong. The pipeline is the source; these are | |
| 185 | + | /// its output, and they are gitignored. | |
| 186 | + | /// | |
| 187 | + | /// # woff2 only, and no ttf beside it | |
| 188 | + | /// | |
| 189 | + | /// The old pairs here offered `woff2` with a `truetype` fallback, which was | |
| 190 | + | /// already dead weight: every browser that can run this site has supported | |
| 191 | + | /// woff2 since 2018, and the ttf is 2-3x the bytes for a case that does not | |
| 192 | + | /// arrive. `makeover::font_face_css` declares woff2 alone, so writing a ttf | |
| 193 | + | /// would put a file in `static/` that nothing ever fetches. | |
| 194 | + | /// | |
| 195 | + | /// # The base is fetched once | |
| 196 | + | /// | |
| 197 | + | /// `quasi_type::cut` downloads the pinned base into the cache directory below | |
| 198 | + | /// and checksums it, so a warm target directory needs no network. `offline` is | |
| 199 | + | /// false because the first build on a fresh machine has to be able to fetch; | |
| 200 | + | /// a failure here is fatal rather than skipped, since a missing face is a site | |
| 201 | + | /// rendering in the fallback with nothing to say so. | |
| 202 | + | fn cut_house_faces() { | |
| 203 | + | let dir = Path::new("static/fonts"); | |
| 204 | + | fs::create_dir_all(dir).expect("create static/fonts"); | |
| 205 | + | let cache = Path::new(&std::env::var("OUT_DIR").expect("cargo sets OUT_DIR")).join("bases"); | |
| 206 | + | ||
| 207 | + | for (slot, file) in [ | |
| 208 | + | ("quasi-mono", makeover_build::WEBFONT_MONO_FILE), | |
| 209 | + | ("quasi-body", makeover_build::WEBFONT_SANS_FILE), | |
| 210 | + | ] { | |
| 211 | + | let cut = quasi_type::cut(slot, &cache, false) | |
| 212 | + | .unwrap_or_else(|e| panic!("could not cut {slot}: {e}")); | |
| 213 | + | let face = cut | |
| 214 | + | .faces | |
| 215 | + | .first() | |
| 216 | + | .unwrap_or_else(|| panic!("{slot} cut no faces")); | |
| 217 | + | // One file per slot, and it carries the whole `wght` 200-800 axis. The | |
| 218 | + | // four static files this replaces (Plex Mono and Lato, regular and | |
| 219 | + | // bold) were 101 KB of woff2 between them and covered two weights. | |
| 220 | + | write_bytes_if_changed(&dir.join(file), &face.woff2); | |
| 221 | + | write_bytes_if_changed( | |
| 222 | + | &dir.join(format!("OFL-{}.txt", cut.family.replace(' ', ""))), | |
| 223 | + | &cut.license, | |
| 224 | + | ); | |
| 225 | + | } | |
| 226 | + | } | |
| 227 | + | ||
| 228 | + | /// `write_if_changed` for bytes. | |
| 229 | + | /// | |
| 230 | + | /// Same reason as the text version: rewriting an identical file bumps its mtime | |
| 231 | + | /// and makes every downstream step think the fonts moved. | |
| 232 | + | fn write_bytes_if_changed(path: &Path, contents: &[u8]) { | |
| 233 | + | if fs::read(path).is_ok_and(|existing| existing == contents) { | |
| 234 | + | return; | |
| 235 | + | } | |
| 236 | + | fs::write(path, contents).unwrap_or_else(|e| panic!("write {}: {e}", path.display())); | |
| 237 | + | } | |
| 238 | + | ||
| 169 | 239 | /// Intrinsic dimensions of the landing screenshots, read from the files. | |
| 170 | 240 | /// | |
| 171 | 241 | /// A picture that cannot say how big it is cannot have its space reserved, so |
| @@ -11,10 +11,12 @@ | |||
| 11 | 11 | set -u | |
| 12 | 12 | ROOT="$(cd "$(dirname "$0")/.." && pwd)" | |
| 13 | 13 | STATIC="$ROOT/static" | |
| 14 | - | # The site's own sheets. The generated ones (geometry.css, layout.css) are | |
| 15 | - | # makeover's output and are never linted; they are the standard, not the code. | |
| 14 | + | # The site's own sheets. The generated ones (geometry.css, layout.css, | |
| 15 | + | # typography.css) are makeover's output and are never linted; they are the | |
| 16 | + | # standard, not the code. They are still READ, because they are where the | |
| 17 | + | # tokens the site's sheets spend are defined. | |
| 16 | 18 | SITE_SHEETS="$STATIC/style.css $STATIC/wizard.css $STATIC/media-player.css" | |
| 17 | - | GENERATED_SHEETS="$STATIC/geometry.css $STATIC/layout.css" | |
| 19 | + | GENERATED_SHEETS="$STATIC/geometry.css $STATIC/layout.css $STATIC/typography.css" | |
| 18 | 20 | ||
| 19 | 21 | violations=0 | |
| 20 | 22 |
| @@ -52,9 +52,16 @@ | |||
| 52 | 52 | // Whole value is being early: a preload discovered after the sheets | |
| 53 | 53 | // it races bought nothing. | |
| 54 | 54 | .with_head_first( | |
| 55 | - | "<link rel=\"preload\" href=\"/static/fonts/Lato-Regular.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\ | |
| 55 | + | "<link rel=\"preload\" href=\"/static/fonts/QuasiBody.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>\ | |
| 56 | 56 | <link rel=\"preload\" href=\"/static/fonts/ysrf.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>", | |
| 57 | 57 | ) | |
| 58 | + | // First, and before the sheets that use the tokens it defines. The | |
| 59 | + | // `@font-face` rules take no part in the cascade so their position | |
| 60 | + | // buys nothing there; what it buys is discovery, since a face the | |
| 61 | + | // parser has not reached yet is a face the browser has not started | |
| 62 | + | // fetching. `style.css` still wins every contest it won before: | |
| 63 | + | // this file defines two tokens and matches no element. | |
| 64 | + | .styled(format!("/static/typography.css?v={V}")) | |
| 58 | 65 | .styled(format!("/static/geometry.css?v={V}")) | |
| 59 | 66 | .styled(format!("/static/layout.css?v={V}")) | |
| 60 | 67 | .styled(format!("/static/style.css?v={V}")) | |
| @@ -131,7 +138,7 @@ | |||
| 131 | 138 | let stmt = head | |
| 132 | 139 | .find("@layer makeover, base, components, responsive;") | |
| 133 | 140 | .expect("the order is stated"); | |
| 134 | - | for sheet in ["geometry.css", "layout.css", "style.css"] { | |
| 141 | + | for sheet in ["typography.css", "geometry.css", "layout.css", "style.css"] { | |
| 135 | 142 | assert!(stmt < head.find(sheet).expect("the sheet is linked")); | |
| 136 | 143 | } | |
| 137 | 144 | } | |
| @@ -148,7 +155,11 @@ | |||
| 148 | 155 | #[test] | |
| 149 | 156 | fn the_fonts_are_preloaded_before_the_sheets_that_race_them() { | |
| 150 | 157 | let head = head(); | |
| 151 | - | assert!(head.find("Lato-Regular.woff2") < head.find("style.css")); | |
| 158 | + | assert!(head.find("QuasiBody.woff2") < head.find("style.css")); | |
| 159 | + | // The retired pair, checked by absence: a preload for a face nothing | |
| 160 | + | // declares is a download the browser makes and never uses. | |
| 161 | + | assert!(!head.contains("Lato")); | |
| 162 | + | assert!(!head.contains("IBMPlexMono")); | |
| 152 | 163 | } | |
| 153 | 164 | ||
| 154 | 165 | #[test] |
| @@ -264,7 +264,7 @@ | |||
| 264 | 264 | ||
| 265 | 265 | /* Description */ | |
| 266 | 266 | .media-description { | |
| 267 | - | font-family: var(--font-body); | |
| 267 | + | font-family: var(--font-sans); | |
| 268 | 268 | font-size: var(--text-lead); | |
| 269 | 269 | line-height: 1.9; | |
| 270 | 270 | margin-bottom: var(--gap-page); | |
| @@ -316,7 +316,7 @@ | |||
| 316 | 316 | } | |
| 317 | 317 | ||
| 318 | 318 | .chapter-title { | |
| 319 | - | font-family: var(--font-body); | |
| 319 | + | font-family: var(--font-sans); | |
| 320 | 320 | color: var(--content); | |
| 321 | 321 | } | |
| 322 | 322 |
| @@ -97,37 +97,17 @@ | |||
| 97 | 97 | font-display: swap; | |
| 98 | 98 | } | |
| 99 | 99 | ||
| 100 | - | @font-face { | |
| 101 | - | font-family: "IBM Plex Mono"; | |
| 102 | - | src: url("/static/fonts/IBMPlexMono-Regular.woff2") format("woff2"), | |
| 103 | - | url("/static/fonts/IBMPlexMono-Regular.ttf") format("truetype"); | |
| 104 | - | font-weight: normal; | |
| 105 | - | font-display: swap; | |
| 106 | - | } | |
| 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. | |
| 107 | 106 | ||
| 108 | - | @font-face { | |
| 109 | - | font-family: "IBM Plex Mono"; | |
| 110 | - | src: url("/static/fonts/IBMPlexMono-Bold.woff2") format("woff2"), | |
| 111 | - | url("/static/fonts/IBMPlexMono-Bold.ttf") format("truetype"); | |
| 112 | - | font-weight: bold; | |
| 113 | - | font-display: swap; | |
| 114 | - | } | |
| 115 | - | ||
| 116 | - | @font-face { | |
| 117 | - | font-family: "Lato"; | |
| 118 | - | src: url("/static/fonts/Lato-Regular.woff2") format("woff2"), | |
| 119 | - | url("/static/fonts/Lato-Regular.ttf") format("truetype"); | |
| 120 | - | font-weight: normal; | |
| 121 | - | font-display: swap; | |
| 122 | - | } | |
| 123 | - | ||
| 124 | - | @font-face { | |
| 125 | - | font-family: "Lato"; | |
| 126 | - | src: url("/static/fonts/Lato-Bold.woff2") format("woff2"), | |
| 127 | - | url("/static/fonts/Lato-Bold.ttf") format("truetype"); | |
| 128 | - | font-weight: bold; | |
| 129 | - | font-display: swap; | |
| 130 | - | } | |
| 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. */ | |
| 131 | 111 | ||
| 132 | 112 | /* Everything below is in the `components` cascade layer. | |
| 133 | 113 | ||
| @@ -151,10 +131,17 @@ | |||
| 151 | 131 | overriding this file's :root without needing to know the order. */ | |
| 152 | 132 | @layer components { | |
| 153 | 133 | :root { | |
| 154 | - | /* Typography (three-tier system) */ | |
| 134 | + | /* Typography: the brand tier only. | |
| 135 | + | ||
| 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. */ | |
| 155 | 144 | --font-heading: "Young Serif", serif; | |
| 156 | - | --font-mono: "IBM Plex Mono", monospace; | |
| 157 | - | --font-body: "Lato", sans-serif; | |
| 158 | 145 | ||
| 159 | 146 | /* -------------------------------------------------------------------- | |
| 160 | 147 | INTENT LAYER, the shared theme vocabulary (themeable) | |
| @@ -309,7 +296,7 @@ | |||
| 309 | 296 | margin: 0; | |
| 310 | 297 | background-color: var(--surface-page); | |
| 311 | 298 | color: var(--content); | |
| 312 | - | font-family: var(--font-body); | |
| 299 | + | font-family: var(--font-sans); | |
| 313 | 300 | line-height: 1.6; | |
| 314 | 301 | } | |
| 315 | 302 | ||
| @@ -391,7 +378,7 @@ | |||
| 391 | 378 | } | |
| 392 | 379 | ||
| 393 | 380 | p { | |
| 394 | - | font-family: var(--font-body); | |
| 381 | + | font-family: var(--font-sans); | |
| 395 | 382 | color: var(--content); | |
| 396 | 383 | } | |
| 397 | 384 | ||
| @@ -896,7 +883,7 @@ | |||
| 896 | 883 | width: 100%; | |
| 897 | 884 | border-collapse: collapse; | |
| 898 | 885 | font-size: var(--text-note); | |
| 899 | - | font-family: var(--font-body); | |
| 886 | + | font-family: var(--font-sans); | |
| 900 | 887 | margin-bottom: var(--gap-section); | |
| 901 | 888 | } | |
| 902 | 889 | ||
| @@ -1226,7 +1213,7 @@ | |||
| 1226 | 1213 | .card-description { | |
| 1227 | 1214 | font-size: var(--text-note); | |
| 1228 | 1215 | opacity: 0.8; | |
| 1229 | - | font-family: var(--font-body); | |
| 1216 | + | font-family: var(--font-sans); | |
| 1230 | 1217 | } | |
| 1231 | 1218 | ||
| 1232 | 1219 | /* =========================================== | |
| @@ -1370,7 +1357,7 @@ | |||
| 1370 | 1357 | padding: var(--gap-section); | |
| 1371 | 1358 | margin-bottom: var(--gap-section); | |
| 1372 | 1359 | font-size: var(--text-note); | |
| 1373 | - | font-family: var(--font-body); | |
| 1360 | + | font-family: var(--font-sans); | |
| 1374 | 1361 | } | |
| 1375 | 1362 | ||
| 1376 | 1363 | .info-box h3 { | |
| @@ -1399,7 +1386,7 @@ | |||
| 1399 | 1386 | padding: var(--gap-section); | |
| 1400 | 1387 | margin-bottom: var(--gap-section); | |
| 1401 | 1388 | font-size: var(--text-note); | |
| 1402 | - | font-family: var(--font-body); | |
| 1389 | + | font-family: var(--font-sans); | |
| 1403 | 1390 | } | |
| 1404 | 1391 | ||
| 1405 | 1392 | .error-message { | |
| @@ -1625,7 +1612,7 @@ | |||
| 1625 | 1612 | font-size: var(--text-body); | |
| 1626 | 1613 | opacity: 0.6; | |
| 1627 | 1614 | border-bottom: 2px solid transparent; | |
| 1628 | - | font-family: var(--font-body); | |
| 1615 | + | font-family: var(--font-sans); | |
| 1629 | 1616 | color: var(--content); | |
| 1630 | 1617 | } | |
| 1631 | 1618 | ||
| @@ -2201,7 +2188,7 @@ | |||
| 2201 | 2188 | } | |
| 2202 | 2189 | ||
| 2203 | 2190 | .article-page .article-body { | |
| 2204 | - | font-family: var(--font-body); | |
| 2191 | + | font-family: var(--font-sans); | |
| 2205 | 2192 | font-size: var(--text-lead); | |
| 2206 | 2193 | line-height: 1.9; | |
| 2207 | 2194 | } | |
| @@ -5280,7 +5267,7 @@ | |||
| 5280 | 5267 | } | |
| 5281 | 5268 | ||
| 5282 | 5269 | .paywall-desc { | |
| 5283 | - | font-family: var(--font-body); | |
| 5270 | + | font-family: var(--font-sans); | |
| 5284 | 5271 | color: var(--content-muted); | |
| 5285 | 5272 | margin-bottom: var(--gap-pane); | |
| 5286 | 5273 | } | |
| @@ -5449,7 +5436,7 @@ | |||
| 5449 | 5436 | min-height: 400px; | |
| 5450 | 5437 | padding: var(--gap-pane); | |
| 5451 | 5438 | background: var(--surface-overlay); | |
| 5452 | - | font-family: var(--font-body); | |
| 5439 | + | font-family: var(--font-sans); | |
| 5453 | 5440 | line-height: 1.8; | |
| 5454 | 5441 | } | |
| 5455 | 5442 | ||
| @@ -5933,7 +5920,7 @@ | |||
| 5933 | 5920 | } | |
| 5934 | 5921 | ||
| 5935 | 5922 | .founder-tagline { | |
| 5936 | - | font-family: var(--font-body); | |
| 5923 | + | font-family: var(--font-sans); | |
| 5937 | 5924 | font-size: var(--text-body); | |
| 5938 | 5925 | line-height: 1.55; | |
| 5939 | 5926 | margin: var(--gap-section) auto 0; | |
| @@ -6109,7 +6096,7 @@ | |||
| 6109 | 6096 | padding: var(--gap-peer) var(--gap-section); | |
| 6110 | 6097 | border: 1px solid var(--border); | |
| 6111 | 6098 | border-radius: var(--radius-control); | |
| 6112 | - | font-family: var(--font-body); | |
| 6099 | + | font-family: var(--font-sans); | |
| 6113 | 6100 | font-size: var(--text-note); | |
| 6114 | 6101 | background: var(--surface-page); | |
| 6115 | 6102 | color: var(--content); | |
| @@ -6683,7 +6670,7 @@ | |||
| 6683 | 6670 | } | |
| 6684 | 6671 | ||
| 6685 | 6672 | .doc-body { | |
| 6686 | - | font-family: var(--font-body); | |
| 6673 | + | font-family: var(--font-sans); | |
| 6687 | 6674 | font-size: var(--text-lead); | |
| 6688 | 6675 | line-height: 1.9; | |
| 6689 | 6676 | } | |
| @@ -6824,7 +6811,7 @@ | |||
| 6824 | 6811 | } | |
| 6825 | 6812 | ||
| 6826 | 6813 | .docs-section a { | |
| 6827 | - | font-family: var(--font-body); | |
| 6814 | + | font-family: var(--font-sans); | |
| 6828 | 6815 | font-size: var(--text-lead); | |
| 6829 | 6816 | color: var(--content); | |
| 6830 | 6817 | text-decoration: none; | |
| @@ -6876,7 +6863,7 @@ | |||
| 6876 | 6863 | width: 100%; | |
| 6877 | 6864 | max-width: 320px; | |
| 6878 | 6865 | padding: var(--gap-peer) var(--gap-section); | |
| 6879 | - | font-family: var(--font-body); | |
| 6866 | + | font-family: var(--font-sans); | |
| 6880 | 6867 | font-size: var(--text-body); | |
| 6881 | 6868 | background: var(--surface-sunken); | |
| 6882 | 6869 | border: 1px solid var(--border); | |
| @@ -6913,7 +6900,7 @@ | |||
| 6913 | 6900 | } | |
| 6914 | 6901 | .docs-search-result:last-child { border-bottom: none; } | |
| 6915 | 6902 | .docs-search-result:hover { background: var(--surface-sunken); } | |
| 6916 | - | .docs-search-result-title { font-family: var(--font-body); } | |
| 6903 | + | .docs-search-result-title { font-family: var(--font-sans); } | |
| 6917 | 6904 | .docs-search-result-section { font-family: var(--font-mono); font-size: var(--text-fine); opacity: 0.5; }.alert .alert-title { | |
| 6918 | 6905 | font-family: var(--font-mono); | |
| 6919 | 6906 | font-size: var(--text-note); | |
| @@ -8217,7 +8204,7 @@ | |||
| 8217 | 8204 | padding: var(--gap-peer); | |
| 8218 | 8205 | border: 1px solid var(--border); | |
| 8219 | 8206 | background: var(--surface-overlay); | |
| 8220 | - | font-family: var(--font-body); | |
| 8207 | + | font-family: var(--font-sans); | |
| 8221 | 8208 | resize: vertical; | |
| 8222 | 8209 | } | |
| 8223 | 8210 | ||
| @@ -9558,7 +9545,7 @@ | |||
| 9558 | 9545 | ||
| 9559 | 9546 | .item-page .license-text { | |
| 9560 | 9547 | white-space: pre-wrap; | |
| 9561 | - | font-family: var(--font-body); | |
| 9548 | + | font-family: var(--font-sans); | |
| 9562 | 9549 | font-size: var(--text-note); | |
| 9563 | 9550 | margin-top: var(--gap-section); | |
| 9564 | 9551 | padding: var(--gap-section); | |
| @@ -9885,7 +9872,7 @@ | |||
| 9885 | 9872 | } | |
| 9886 | 9873 | .insertion-list-table { | |
| 9887 | 9874 | width: 100%; | |
| 9888 | - | font-family: var(--font-body); | |
| 9875 | + | font-family: var(--font-sans); | |
| 9889 | 9876 | } | |
| 9890 | 9877 | .insertion-list-table th { | |
| 9891 | 9878 | text-align: left; | |
| @@ -9963,7 +9950,7 @@ | |||
| 9963 | 9950 | } | |
| 9964 | 9951 | .placement-list-table { | |
| 9965 | 9952 | width: 100%; | |
| 9966 | - | font-family: var(--font-body); | |
| 9953 | + | font-family: var(--font-sans); | |
| 9967 | 9954 | margin-bottom: var(--gap-section); | |
| 9968 | 9955 | } | |
| 9969 | 9956 | .placement-list-table th { | |
| @@ -10977,7 +10964,7 @@ | |||
| 10977 | 10964 | text-align: left; | |
| 10978 | 10965 | background: none; | |
| 10979 | 10966 | border: none; | |
| 10980 | - | font-family: var(--font-body); | |
| 10967 | + | font-family: var(--font-sans); | |
| 10981 | 10968 | font-size: var(--text-note); | |
| 10982 | 10969 | color: var(--content); | |
| 10983 | 10970 | cursor: pointer; |
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file