use std::collections::hash_map::DefaultHasher; use std::fmt::Write as _; use std::hash::{Hash, Hasher}; use std::process::Command; use std::{fs, path::Path}; fn main() { // Set GIT_HASH env var for compile-time inclusion via option_env!() let hash = Command::new("git") .args(["rev-parse", "--short", "HEAD"]) .output() .ok() .filter(|o| o.status.success()) .and_then(|o| String::from_utf8(o.stdout).ok()) .map(|s| s.trim().to_string()) .unwrap_or_default(); println!("cargo::rustc-env=GIT_HASH={hash}"); // Only re-run when HEAD changes println!("cargo::rerun-if-changed=.git/HEAD"); // Compile the TypeScript frontend to static/dist/ (best-effort, see fn). build_frontend(); // The two generated stylesheets. Spacing comes from makeover-geometry and // composition from makeover-webview, the same way colour already comes // from makeover through theming.rs. Written into static/ rather than a // bundle directory because the server serves its stylesheets; both are // gitignored, since the crates are the source and a checked-in copy would // drift from the pin. // // No explicit touch selector: density hangs off (hover: none), // (pointer: coarse) alone, because the server has no mode class to key it // on. GO passes `.ui-mode-mobile` because it has one. makeover_build::geometry_css("static/geometry.css", None); makeover_build::layout_css("static/layout.css", &makeover_build::Emit::default()); // The embeds get their own copy of the spacing layer, because they are // served into third-party iframes and cannot link a stylesheet. Pointer // density only, and no `@media` block: an embed body is `height: 100vh` // inside an iframe the host page sized, so growing the gaps on a coarse // pointer clips rather than reflows, and the host author never sees it // happen. Revisit if embeds ever gain a resize protocol. fs::write( "static/embed-geometry.css", makeover_geometry::geometry_css_vars(makeover_geometry::Density::Pointer), ) .expect("write static/embed-geometry.css"); println!("cargo::rerun-if-changed=build.rs"); // The landing shots' own dimensions, so the description can reserve their // space. Generated rather than written down; see `shot_dimensions`. let shots = shot_dimensions(); let mut table = String::from( "/// Intrinsic size of each landing screenshot, by URL path.\n\ /// Generated by build.rs from the files themselves.\n\ pub static SHOT_DIMENSIONS: &[(&str, u32, u32)] = &[\n", ); for (path, w, h) in &shots { let _ = writeln!(table, " ({path:?}, {w}, {h}),"); } table.push_str("];\n"); fs::write( Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR")).join("shot_dimensions.rs"), table, ) .expect("write shot_dimensions.rs"); // The same check goingson and balanced_breakfast run, out of // makeover-build rather than a third copy of it. The named-list form: // static/ holds the generated stylesheets and a bundler's output beside // the hand-written ones, so there is no directory to scan. No tuning // widths -- every threshold here is a shell boundary. makeover_build::check_breakpoints_files(&HAND_WRITTEN_CSS, &[]); makeover_build::check_vocabulary_files( &HAND_WRITTEN_CSS, &makeover_build::Emit::default(), REVIEWED_OVERLAPS, ); makeover_build::check_vocabulary_use( &markup_files(), &makeover_build::Emit::default(), DEAD_VOCABULARY_HIGH_WATER, ); // --- Static asset fingerprinting --- // Hash the content of key static files to produce a version suffix. // When any watched file changes, URLs in templates get a new ?v= param, // busting browser caches automatically. let static_files = [ "static/style.css", // The two per-page sheets. Linked from page templates rather than // from the head, which is why they were outside the fingerprint and // served stale to any browser holding a cached copy. "static/wizard.css", "static/media-player.css", // Same case again, found 2026-08-15: linked from a