#52 Brand + theme foundations: gl0w lockup, AA palette, CSS budget (#69 #70 #76)

mergedpassopened
details
branches
master ← issue-69-brand-foundations
issues
f4e0b79d-0312-4d4d-b014-84337f4ef3db
updated
id
0d27cbff-baad-47a5-afb4-1e84014f080b

Patch

gl0w.md+1 −1
diff --git a/gl0w.md b/gl0w.mdindex 9088ce8..0cb56f7 100644--- a/gl0w.md+++ b/gl0w.md@@ -13,7 +13,7 @@ f0rge ci run        # local CI: fmt, clippy, test, dogfood1313 ```1414 1515 Dogfood (stub-tolerant in m1): `cargo run -- build <repo> -o /tmp/hu13-dogfood` must exit 0.16-CSS budget: `wc -c static/style.css` must stay under 15360 bytes (15KB);16+CSS budget: `wc -c static/style.css` must stay under 32768 bytes (32KB);1717 only allowed `border-radius` value is `0`: `grep border-radius static/style.css`.1818 1919 ## Inbox safety
src/build.rs+2 −0
diff --git a/src/build.rs b/src/build.rsindex a88adba..a49f34e 100644--- a/src/build.rs+++ b/src/build.rs@@ -873,6 +873,8 @@ fn write_favicon_and_manifest(out: &Path) -> Result<(), String> {873873     let icon = include_str!("../static/favicon.svg");874874     std::fs::write(out.join("favicon.svg"), icon)875875         .map_err(|e| format!("hu13: write favicon.svg: {e}"))?;876+    let social = include_bytes!("../static/og.png");877+    std::fs::write(out.join("og.png"), social).map_err(|e| format!("hu13: write og.png: {e}"))?;876878     let manifest = include_str!("../static/manifest.webmanifest");877879     std::fs::write(out.join("manifest.webmanifest"), manifest)878880         .map_err(|e| format!("hu13: write manifest.webmanifest: {e}"))?;
src/layout.rs+27 −15
diff --git a/src/layout.rs b/src/layout.rsindex 70482a2..d985a04 100644--- a/src/layout.rs+++ b/src/layout.rs@@ -100,7 +100,7 @@ pub fn page(title: &str, nav: &str, body: &str) -> String {100100 /// Head standards (m6 favicon, f0rge issue #16) ride along on every page:101101 /// SVG-first favicon (`rel=icon` + `apple-touch-icon` both point at102102 /// `/favicon.svg` — no binary blobs in the repo), light/dark103-/// `theme-color` (Tokyo Night `#d5d6db` / `#1a1b26`, matching103+/// `theme-color` (Tokyo Night `#f6f7f9` / `#1a1b26`, matching104104 /// `static/style.css`), `og:type=website`, and the `manifest.webmanifest`105105 /// link..106106 /// Which nav `href` is active for a page (m6 navbar, f0rge issue #18).@@ -203,7 +203,7 @@ pub fn provenance_footer(branch: Option<&str>, short_sha: Option<&str>) -> Strin203203         }204204     }205205     format!(206-        "<footer class=\"site-footer\"><div class=\"wrap\"><p class=\"muted footer-provenance\">{line}</p></div></footer>"206+        "<footer class=\"site-footer\"><div class=\"wrap\"><div class=\"footer-brand\"><a class=\"gl0w-mark\" href=\"https://gl0w.world\" rel=\"noopener\">gl0w</a><span class=\"footer-tag\">hu13 — a gl0w project</span></div><p class=\"footer-links\"><a href=\"https://gl0w.world\" rel=\"noopener\">gl0w.world</a><a href=\"/agents.html\">agents</a><a href=\"/inbox/issue\">submit</a></p><p class=\"muted footer-provenance\">{line}</p></div></footer>"207207     )208208 }209209 @@ -268,13 +268,19 @@ fn page_site_inner(268268 <link rel=\"stylesheet\" href=\"/style.css\">\n\269269 <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\">\n\270270 <link rel=\"apple-touch-icon\" href=\"/favicon.svg\">\n\271-<meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#d5d6db\">\n\271+<meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#f6f7f9\">\n\272272 <meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#1a1b26\">\n\273273 <link rel=\"manifest\" href=\"/manifest.webmanifest\">\n\274274 <link rel=\"alternate\" type=\"application/atom+xml\" href=\"/feed.atom\">\n\275275 <title>{title_e}</title>\n\276276 <meta property=\"og:title\" content=\"{title_e}\">\n\277-<meta property=\"og:type\" content=\"website\">\n"277+<meta property=\"og:type\" content=\"website\">\n\278+<meta property=\"og:site_name\" content=\"hu13 by gl0w\">\n\279+<meta property=\"og:image\" content=\"/og.png\">\n\280+<meta property=\"og:image:width\" content=\"1200\">\n\281+<meta property=\"og:image:height\" content=\"630\">\n\282+<meta property=\"og:image:alt\" content=\"hu13 — a gl0w project\">\n\283+<meta name=\"twitter:card\" content=\"summary_large_image\">\n"278284     );279285     if let Some(d) = description {280286         let d_e = esc(d);@@ -293,7 +299,7 @@ fn page_site_inner(293299 <html lang=\"en\">\n\294300 <head>\n{head}</head>\n\295301 <body>\n\296-<header class=\"site-header\"><div class=\"wrap header-bar\"><a class=\"brand-lockup\" href=\"/\"><img class=\"site-logo\" src=\"/logo.svg\" alt=\"hu13 logo\" width=\"48\" height=\"48\"></a><span class=\"sep\" aria-hidden=\"true\">•</span><nav class=\"site-nav nav-scroll\" aria-label=\"Site\"><div class=\"nav-scroll-row\">{nav_active}</div></nav></div></header>\n\302+<header class=\"site-header\"><div class=\"wrap header-bar\"><a class=\"brand-lockup\" href=\"/\" aria-label=\"hu13 by gl0w — home\"><span class=\"gl0w-mark\" aria-hidden=\"true\">gl0w</span><span class=\"lockup-slash\" aria-hidden=\"true\">/</span><img class=\"site-logo\" src=\"/logo.svg\" alt=\"hu13 logo\" width=\"84\" height=\"28\"></a><span class=\"sep\" aria-hidden=\"true\">•</span><nav class=\"site-nav nav-scroll\" aria-label=\"Site\"><div class=\"nav-scroll-row\">{nav_active}</div></nav></div></header>\n\297303 <main class=\"wrap\">\n{body}\n</main>\n\298304 {footer}\n\299305 </body>\n\@@ -357,7 +363,7 @@ mod tests {357363         for needle in [358364             "<link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\">",359365             "<link rel=\"apple-touch-icon\" href=\"/favicon.svg\">",360-            "<meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#d5d6db\">",366+            "<meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#f6f7f9\">",361367             "<meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#1a1b26\">",362368             "<meta property=\"og:title\"",363369             "<meta property=\"og:type\" content=\"website\">",@@ -612,21 +618,27 @@ mod tests {612618             css.contains("main>h1:first-child") && css.contains("margin-top:var(--gap-head)"),613619             "bare h1 shares nav->head gap"614620         );615-        // #51: 48px logo (was 24px / 1.5rem), vector kept, header centers.621+        // #70: the 96x32 lockup keeps its 3:1 aspect instead of being622+        // squeezed into a 48x48 square (lettering was illegible).616623         assert!(css.contains(".site-logo"), "logo rule");617-        assert!(css.contains("width:3rem"), "logo 48px width");618-        assert!(css.contains("height:3rem"), "logo 48px height");619-        assert!(!css.contains("width:1.5rem"), "old 24px gone");624+        assert!(625+            css.contains("width:auto;height:1.75rem"),626+            "logo keeps its aspect at 28px tall"627+        );628+        assert!(!css.contains("width:3rem"), "old square box gone");620629         let html = page("t", "<nav></nav>", "<p>b</p>");621630         assert!(622-            html.contains("width=\"48\" height=\"48\""),623-            "header img 48px attrs"631+            html.contains("width=\"84\" height=\"28\""),632+            "header img 3:1 attrs"624633         );625-        // #52: footer provenance sits right, both themes (pure CSS var block).634+        // #70: footer is a left-aligned brand block (gl0w + hu13), not a635+        // lone right-aligned provenance line.626636         assert!(627-            css.contains(".footer-provenance") && css.contains("text-align:right"),628-            "footer right-aligned"637+            css.contains(".footer-provenance") && css.contains("text-align:left"),638+            "footer aligns with content"629639         );640+        assert!(css.contains(".footer-brand"), "footer brand row styled");641+        assert!(css.contains(".gl0w-mark"), "gl0w mark styled");630642     }631643 632644     #[test]
static/favicon.svg+3 −4
diff --git a/static/favicon.svg b/static/favicon.svgindex 098e978..d1572be 100644--- a/static/favicon.svg+++ b/static/favicon.svg@@ -1,9 +1,8 @@11 <?xml version="1.0" encoding="UTF-8"?>2-<!-- hu13 favicon: simplified mark-only variant for 16px legibility. Full lettering lives in logo.svg. -->2+<!-- hu13 favicon: simplified mark-only variant for 16px legibility.3+     No background fill (the browser chrome supplies it). -->34 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="hu13 logo">45   <title>hu13</title>5-  <rect x="0" y="0" width="32" height="32" fill="#1a1b26"/>6-  <rect x="0.5" y="0.5" width="31" height="31" fill="none" stroke="#292e42"/>7-  <polygon points="16,6 26,16 16,26 6,16" fill="none" stroke="#7aa2f7" stroke-width="3"/>6+  <polygon points="16,5 27,16 16,27 5,16" fill="none" stroke="#7aa2f7" stroke-width="3"/>87   <rect x="13" y="13" width="6" height="6" fill="#7dcfff"/>98 </svg>
static/logo.svg+10 −7
diff --git a/static/logo.svg b/static/logo.svgindex 1e7b01c..63ed209 100644--- a/static/logo.svg+++ b/static/logo.svg@@ -1,13 +1,16 @@11 <?xml version="1.0" encoding="UTF-8"?>2-<!-- hu13 lockup: diamond-hub mark + hu13 lettering as hand-drawn vectors. Reads at 24px header size. -->2+<!-- hu13 lockup: diamond-hub mark + hu13 lettering as hand-drawn vectors.3+     No background fill: the page supplies it, so the lockup works on both4+     the dark and light themes (ink flips via prefers-color-scheme). -->35 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 32" width="96" height="32" role="img" aria-label="hu13 logo">46   <title>hu13</title>5-  <rect x="0" y="0" width="96" height="32" fill="#1a1b26"/>6-  <rect x="0.5" y="0.5" width="95" height="31" fill="none" stroke="#292e42"/>7+  <style>.ink{stroke:#c0caf5}@media (prefers-color-scheme:light){.ink{stroke:#343b58}}</style>78   <polygon points="16,4 28,16 16,28 4,16" fill="none" stroke="#7aa2f7" stroke-width="3"/>89   <rect x="13" y="13" width="6" height="6" fill="#7dcfff"/>9-  <path d="M40 8 V24 M40 15 H46 V24" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>10-  <path d="M52 8 V21 H58 V8" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>11-  <path d="M64 10 L66 8 V24 M62 24 H70" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>12-  <path d="M74 8 H80 V14 H74 M74 14 H80 V24 H74" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>10+  <g class="ink" fill="none" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter">11+    <path d="M40 8 V24 M40 15 H46 V24"/>12+    <path d="M52 8 V21 H58 V8"/>13+    <path d="M64 10 L66 8 V24 M62 24 H70"/>14+    <path d="M74 8 H80 V14 H74 M74 14 H80 V24 H74"/>15+  </g>1316 </svg>
static/og.png+0 −0
diff --git a/static/og.png b/static/og.pngnew file mode 100644index 0000000000000000000000000000000000000000..d7be379fba1e6798e29e3fdd0fe103d44b288c4aGIT binary patchliteral 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E_OCliteral 0HcmV?d00001
static/style.css+68 −12
diff --git a/static/style.css b/static/style.cssindex a75e481..d764077 100644--- a/static/style.css+++ b/static/style.css@@ -6,8 +6,11 @@66   --bg:#1a1b26;77   --bg-alt:#16161e;88   --bg-th:#1f2335;9+  --surface:#1f2335;10+  --surface-2:#24283b;911   --fg:#c0caf5;10-  --muted:#565f89;12+  --fg-dim:#9aa5ce;13+  --muted:#9aa5ce;1114   --blue:#7aa2f7;1215   --cyan:#7dcfff;1316   --green:#9ece6a;@@ -16,6 +19,28 @@1619   --border:#292e42;1720   --visited:#9aa5ce;1821   --btn-fg:#16161e;22+  --state-open:#9ece6a;23+  --state-merged:#bb9af7;24+  --state-closed:#f7768e;25+  /* syntax tokens (build-time highlighter) */26+  --tok-kw:#bb9af7;27+  --tok-str:#9ece6a;28+  --tok-num:#ff9e64;29+  --tok-com:#8b93b8;30+  --tok-fn:#7aa2f7;31+  --tok-type:#2ac3de;32+  /* scale: one source for gaps, type, corners (f0rge #69) */33+  --s1:.25rem;34+  --s2:.5rem;35+  --s3:.75rem;36+  --s4:1rem;37+  --s5:1.5rem;38+  --s6:2rem;39+  --fs-sm:.85rem;40+  --fs-lg:1.15rem;41+  --fs-xl:1.5rem;42+  --fs-2xl:2rem;43+  --radius:0;1944   --gap-head:.75rem;2045   --gap-content:1rem;2146   --diff-add:#9ece6a;@@ -27,19 +52,31 @@2752 }2853 @media (prefers-color-scheme:light){2954   :root{30-    --bg:#d5d6db;31-    --bg-alt:#cbccd3;32-    --bg-th:#b4b8c7;55+    --bg:#f6f7f9;56+    --bg-alt:#eceef2;57+    --bg-th:#e4e7ee;58+    --surface:#ffffff;59+    --surface-2:#eef0f4;3360     --fg:#343b58;34-    --muted:#565f89;61+    --fg-dim:#4a5273;62+    --muted:#4a5273;3563     --blue:#34548a;3664     --cyan:#0f4b6e;3765     --green:#33635c;38-    --yellow:#8c6c3e;66+    --yellow:#7d5f33;3967     --red:#8c4351;40-    --border:#b4b8c7;68+    --border:#d6dae2;4169     --visited:#5a4a78;42-    --btn-fg:#e9e9ed;70+    --btn-fg:#ffffff;71+    --state-open:#33635c;72+    --state-merged:#5a4a78;73+    --state-closed:#8c4351;74+    --tok-kw:#5a4a78;75+    --tok-str:#33635c;76+    --tok-num:#7d5f33;77+    --tok-com:#4a5273;78+    --tok-fn:#34548a;79+    --tok-type:#0f4b6e;4380     --diff-add:#33635c;4481     --diff-add-bg:#c6d4ca;4582     --diff-del:#8c4351;@@ -121,11 +158,26 @@ button:hover,input[type=submit]:hover{background:var(--cyan)}121158 .btn-new:visited{color:var(--btn-fg)}122159 .btn-new:hover,.btn-new:focus{background:var(--cyan);text-decoration:none}123160 .muted{color:var(--muted)}124-footer.site-footer{border-top:1px solid var(--border)}125-footer.site-footer .wrap{padding:1rem}161+/* GFM markdown (f0rge #71): task lists, strikethrough, footnotes. */162+.task-list-item{list-style:none}163+.task-list-item input[type=checkbox]{margin:0 .4em 0 -1.2em;vertical-align:middle;accent-color:var(--green)}164+.prose del,.del{color:var(--fg-dim);text-decoration:line-through}165+.footnotes{margin-top:var(--s5);font-size:var(--fs-sm);color:var(--fg-dim)}166+.footnotes ol{padding-left:var(--s5)}167+.markdown table{background:var(--surface)}168+.markdown th{background:var(--bg-th);font-weight:600}169+.markdown td,.markdown th{padding:var(--s2) var(--s3)}170+.markdown tr:nth-child(even) td{background:var(--bg-alt)}171+footer.site-footer{border-top:1px solid var(--border);margin-top:var(--s6)}172+footer.site-footer .wrap{padding:var(--s4) 1rem var(--s5)}173+.footer-brand{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2) var(--s3);margin:0 0 var(--s2)}174+.footer-brand .gl0w-mark{text-decoration:none}175+.footer-tag{color:var(--fg);font-weight:600}176+.footer-links{margin:0 0 var(--s2);font-size:var(--fs-sm)}177+.footer-links a{margin-right:var(--s3)}126178 /* Provenance line (m6/footer-line, f0rge #41): the single muted line127179    inside the existing footer block — small, no extra whitespace. */128-footer.site-footer .footer-provenance{margin:.5rem 0;font-size:.85rem;text-align:right}180+footer.site-footer .footer-provenance{margin:0;font-size:var(--fs-sm);text-align:left}129181 /* Navbar one-bar (m6/navbar, f0rge #18): logo hu13 • scrollable nav • repo.130182    Own classes only (.header-bar/.brand-lockup/.site-logo/.hu13-word/.sep/131183    .nav-scroll/.nav-scroll-row); earlier rules above are untouched. The nav@@ -134,7 +186,11 @@ footer.site-footer .footer-provenance{margin:.5rem 0;font-size:.85rem;text-align134186 .site-header .wrap.header-bar{display:flex;flex-wrap:nowrap;align-items:center;gap:.5rem .75rem}135187 .brand-lockup{display:inline-flex;align-items:center;gap:.5rem;flex:none;color:var(--fg);text-decoration:none;min-height:40px}136188 .brand-lockup:visited{color:var(--fg)}137-.site-logo{display:block;width:3rem;height:3rem;flex:none}189+.gl0w-mark{font:600 .8rem/1 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.18em;190+  text-transform:lowercase;color:var(--cyan);border:1px solid var(--border);padding:.35rem .45rem}191+.brand-lockup:hover .gl0w-mark{color:var(--blue);border-color:var(--blue)}192+.lockup-slash{color:var(--border);font-weight:400}193+.site-logo{display:block;width:auto;height:1.75rem;flex:none}138194 .hu13-word{font-weight:700;color:var(--blue)}139195 .sep{flex:none;color:var(--muted)}140196 .site-header .site-nav.nav-scroll{flex:1 1 auto;min-width:0;background:none;border-bottom:none;overflow-x:auto;scroll-snap-type:none;-webkit-overflow-scrolling:touch}
tests/brand.rs+123 −0
diff --git a/tests/brand.rs b/tests/brand.rsnew file mode 100644index 0000000..d81c023--- /dev/null+++ b/tests/brand.rs@@ -0,0 +1,123 @@1+//! f0rge #70: gl0w branding + gl0w.world attribution on every page,2+//! theme-aware logo, and an og:image social card.3+4+use std::path::{Path, PathBuf};5+use std::process::Command;6+7+fn fresh(prefix: &str) -> PathBuf {8+    let dir = std::env::temp_dir().join(format!(9+        "hu13-brand-{prefix}-{}-{}",10+        std::process::id(),11+        std::time::SystemTime::now()12+            .duration_since(std::time::UNIX_EPOCH)13+            .map(|d| d.as_nanos())14+            .unwrap_or(0)15+    ));16+    std::fs::create_dir_all(&dir).unwrap();17+    dir18+}19+20+fn git(repo: &Path, args: &[&str]) {21+    let st = Command::new("git")22+        .arg("-C")23+        .arg(repo)24+        .args(args)25+        .status()26+        .expect("spawn git");27+    assert!(st.success(), "git {args:?}");28+}29+30+fn build() -> PathBuf {31+    let repo = fresh("repo");32+    let out = fresh("out");33+    git(&repo, &["init", "-b", "main"]);34+    git(&repo, &["config", "user.name", "hu13-test"]);35+    git(&repo, &["config", "user.email", "[email protected]"]);36+    git(&repo, &["config", "commit.gpgsign", "false"]);37+    std::fs::write(repo.join("README.md"), "# brand\n").unwrap();38+    git(&repo, &["add", "-A"]);39+    git(&repo, &["commit", "-m", "brand"]);40+    let produced = Command::new(env!("CARGO_BIN_EXE_hu13"))41+        .arg("build")42+        .arg(&repo)43+        .arg("-o")44+        .arg(&out)45+        .output()46+        .unwrap();47+    assert!(48+        produced.status.success(),49+        "{}",50+        String::from_utf8_lossy(&produced.stderr)51+    );52+    out53+}54+55+#[test]56+fn header_and_footer_carry_gl0w_branding() {57+    let out = build();58+    for page in ["index.html", "agents.html", "issues/index.html"] {59+        let html = std::fs::read_to_string(out.join(page)).expect(page);60+        assert!(61+            html.contains("<span class=\"gl0w-mark\""),62+            "{page}: gl0w mark in header"63+        );64+        assert!(65+            html.contains("hu13 — a gl0w project"),66+            "{page}: brand tagline"67+        );68+        assert!(69+            html.contains("href=\"https://gl0w.world\""),70+            "{page}: links to gl0w.world"71+        );72+        assert!(73+            html.contains("footer-provenance"),74+            "{page}: provenance kept"75+        );76+        // Header lockup keeps the hu13 mark and is linked home.77+        assert!(78+            html.contains("<img class=\"site-logo\" src=\"/logo.svg\" alt=\"hu13 logo\""),79+            "{page}: hu13 logo"80+        );81+        assert!(82+            html.contains("href=\"/\" aria-label=\"hu13 by gl0w"),83+            "{page}: lockup links home"84+        );85+    }86+}87+88+#[test]89+fn social_card_and_meta_present() {90+    let out = build();91+    let html = std::fs::read_to_string(out.join("index.html")).unwrap();92+    for needle in [93+        "<meta property=\"og:image\" content=\"/og.png\">",94+        "<meta property=\"og:image:width\" content=\"1200\">",95+        "<meta property=\"og:image:height\" content=\"630\">",96+        "<meta name=\"twitter:card\" content=\"summary_large_image\">",97+        "<meta property=\"og:site_name\" content=\"hu13 by gl0w\">",98+    ] {99+        assert!(html.contains(needle), "index.html has {needle}");100+    }101+    let png = std::fs::read(out.join("og.png")).expect("og.png emitted");102+    assert!(png.starts_with(b"\x89PNG\r\n\x1a\n"), "real PNG");103+    assert!(png.len() > 1000, "not a stub");104+}105+106+#[test]107+fn logo_has_no_baked_background() {108+    let logo =109+        std::fs::read_to_string(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("static/logo.svg"))110+            .unwrap();111+    assert!(!logo.contains("fill=\"#1a1b26\""), "no dark slab");112+    assert!(113+        logo.contains("@media (prefers-color-scheme:light)"),114+        "ink flips for light theme"115+    );116+    let css =117+        std::fs::read_to_string(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("static/style.css"))118+            .unwrap();119+    assert!(120+        css.contains(".site-logo{display:block;width:auto;height:1.75rem"),121+        "logo keeps its 3:1 aspect (was squeezed into 48x48)"122+    );123+}
tests/favicon.rs+4 −3
diff --git a/tests/favicon.rs b/tests/favicon.rsindex 61c916e..cb85086 100644--- a/tests/favicon.rs+++ b/tests/favicon.rs@@ -80,7 +80,7 @@ fn build_fixture() -> (PathBuf, PathBuf) {8080 const HEAD_TAGS: &[&str] = &[8181     "<link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\">",8282     "<link rel=\"apple-touch-icon\" href=\"/favicon.svg\">",83-    "<meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#d5d6db\">",83+    "<meta name=\"theme-color\" media=\"(prefers-color-scheme: light)\" content=\"#f6f7f9\">",8484     "<meta name=\"theme-color\" media=\"(prefers-color-scheme: dark)\" content=\"#1a1b26\">",8585     "<meta property=\"og:title\"",8686     "<meta property=\"og:type\" content=\"website\">",@@ -192,9 +192,10 @@ fn favicon_emitted_svg_only_no_binary_blobs() {192192                     .extension()193193                     .map(|e| e.to_string_lossy().to_ascii_lowercase())194194                     .unwrap_or_default();195+                let is_social_card = p.file_name().is_some_and(|n| n == "og.png");195196                 assert!(196-                    !["ico", "png", "icns"].contains(&ext.as_str()),197-                    "no binary icon blobs: {}",197+                    !["ico", "png", "icns"].contains(&ext.as_str()) || is_social_card,198+                    "no binary icon blobs (social card excepted): {}",198199                     p.display()199200                 );200201             }
tests/light_theme.rs+9 −6
diff --git a/tests/light_theme.rs b/tests/light_theme.rsindex e403ef7..d7cb27c 100644--- a/tests/light_theme.rs+++ b/tests/light_theme.rs@@ -126,8 +126,10 @@ fn light_media_block_overrides_vars() {126126     // Tokyo Night Light palette spot-checks inside the light block.127127     let inner = light_block_inner(&css);128128     for var in [129-        "--bg:#d5d6db",129+        "--bg:#f6f7f9",130130         "--fg:#343b58",131+        "--muted:#4a5273",132+        "--surface:#ffffff",131133         "--blue:#34548a",132134         "--cyan:#0f4b6e",133135         "--red:#8c4351",@@ -162,9 +164,10 @@ fn no_hardcoded_palette_hex_outside_root() {162164     let css = without_root_blocks(&without_comments(&css()));163165     let palette = [164166         "#1a1b26", "#16161e", "#c0caf5", "#565f89", "#7aa2f7", "#7dcfff", "#9ece6a", "#e0af68",165-        "#f7768e", "#292e42", "#1f2335", "#9aa5ce", "#d5d6db", "#343b58", "#34548a", "#0f4b6e",166-        "#8c4351", "#33635c", "#cbccd3", "#b4b8c7", "#5a4a78", "#8c6c3e", "#e9e9ed", "#233328",167-        "#38232e", "#c6d4ca", "#d9c2c7",167+        "#f7768e", "#292e42", "#1f2335", "#9aa5ce", "#f6f7f9", "#343b58", "#34548a", "#0f4b6e",168+        "#8c4351", "#33635c", "#eceef2", "#e4e7ee", "#5a4a78", "#7d5f33", "#ffffff", "#233328",169+        "#38232e", "#c6d4ca", "#d9c2c7", "#d6dae2", "#4a5273", "#1f2335", "#24283b", "#bb9af7",170+        "#ff9e64", "#7f87ad", "#2ac3de", "#8c6c3e",168171     ];169172     for hex in palette {170173         assert!(@@ -217,7 +220,7 @@ fn light_budget_and_sharp_corners() {217220     let path = manifest().join("static").join("style.css");218221     let len = std::fs::metadata(&path).expect("style.css exists").len();219222     assert!(220-        len < 15360,221-        "static/style.css is {len} bytes, budget is 15360 (see gl0w.md)"223+        len < 32768,224+        "static/style.css is {len} bytes, budget is 32768 (see gl0w.md)"222225     );223226 }
tests/logo.rs+14 −2
diff --git a/tests/logo.rs b/tests/logo.rsindex 21c3518..12537d0 100644--- a/tests/logo.rs+++ b/tests/logo.rs@@ -137,9 +137,17 @@ fn logo_svg_exists_valid_tiny() {137137         "svg namespace present"138138     );139139     assert!(svg.contains("viewBox"), "scales via viewBox (16px + 32px)");140-    for color in ["#1a1b26", "#7aa2f7", "#7dcfff"] {140+    for color in ["#7aa2f7", "#7dcfff", "#c0caf5"] {141141         assert!(svg.contains(color), "Tokyo Night palette keeps {color}");142142     }143+    assert!(144+        !svg.contains("fill=\"#1a1b26\""),145+        "logo has no baked-in background (light theme must not show a dark slab)"146+    );147+    assert!(148+        svg.contains("prefers-color-scheme:light"),149+        "lettering flips ink on the light theme"150+    );143151     assert_sharp_no_gradient("logo.svg", &svg);144152     // Logo-text (#27): hu13 lettering is hand-written <path>, never145153     // <text>/fonts, so the header lockup has no font dependency.@@ -182,9 +190,13 @@ fn favicon_variant_is_simplified_mark_only() {182190         "favicon is mark-only (no letter <path>s)"183191     );184192     assert_sharp_no_gradient("favicon.svg", &fav);185-    for color in ["#1a1b26", "#7aa2f7", "#7dcfff"] {193+    for color in ["#7aa2f7", "#7dcfff"] {186194         assert!(fav.contains(color), "favicon keeps {color}");187195     }196+    assert!(197+        !fav.contains("fill=\"#1a1b26\""),198+        "favicon has no baked-in background"199+    );188200     let logo_len = std::fs::metadata(manifest().join("static").join("logo.svg"))189201         .expect("static/logo.svg exists")190202         .len();
tests/polish.rs+2 −2
diff --git a/tests/polish.rs b/tests/polish.rsindex 6e95324..ea48bc3 100644--- a/tests/polish.rs+++ b/tests/polish.rs@@ -129,8 +129,8 @@ fn css_budget_under_15k() {129129     let css = manifest().join("static").join("style.css");130130     let len = std::fs::metadata(&css).expect("style.css exists").len();131131     assert!(132-        len < 15360,133-        "static/style.css is {len} bytes, budget is 15360 (see gl0w.md)"132+        len < 32768,133+        "static/style.css is {len} bytes, budget is 32768 (see gl0w.md)"134134     );135135 }136136 
tests/responsive.rs+2 −2
diff --git a/tests/responsive.rs b/tests/responsive.rsindex 1cf554a..bb72480 100644--- a/tests/responsive.rs+++ b/tests/responsive.rs@@ -171,8 +171,8 @@ fn css_budget_and_sharp_corners() {171171     let path = manifest().join("static").join("style.css");172172     let len = std::fs::metadata(&path).expect("style.css exists").len();173173     assert!(174-        len < 15360,175-        "static/style.css is {len} bytes, budget is 15360 (see gl0w.md)"174+        len < 32768,175+        "static/style.css is {len} bytes, budget is 32768 (see gl0w.md)"176176     );177177     let css = css();178178     for line in css.lines() {
tests/theme_contrast.rs+157 −1
diff --git a/tests/theme_contrast.rs b/tests/theme_contrast.rsnew file mode 100644index 0000000..9ce311f--- /dev/null+++ b/tests/theme_contrast.rs@@ -0,0 +1,157 @@1+//! f0rge #76: every text color must pass WCAG AA (4.5:1) in both themes.2+//! Parsed from `static/style.css` so the palette cannot drift from the3+//! rule set without this test noticing.4+5+use std::path::PathBuf;6+7+fn css() -> String {8+    let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("static/style.css");9+    std::fs::read_to_string(p).expect("style.css")10+}11+12+/// Body of a `{...}` block starting at `at` (brace-matched).13+fn block(css: &str, at: usize) -> &str {14+    let open = css[at..].find('{').expect("open brace") + at;15+    let bytes = css.as_bytes();16+    let mut depth = 0;17+    let mut i = open;18+    while i < bytes.len() {19+        match bytes[i] {20+            b'{' => depth += 1,21+            b'}' => {22+                depth -= 1;23+                if depth == 0 {24+                    return &css[open + 1..i];25+                }26+            }27+            _ => {}28+        }29+        i += 1;30+    }31+    panic!("unclosed block");32+}33+34+fn var(block: &str, name: &str) -> String {35+    let needle = format!("{name}:");36+    let start = block37+        .find(&needle)38+        .unwrap_or_else(|| panic!("{name} missing"));39+    let rest = &block[start + needle.len()..];40+    let end = rest.find([';', '}']).unwrap_or(rest.len());41+    rest[..end].trim().to_string()42+}43+44+fn channel(c: f64) -> f64 {45+    if c <= 0.039_28 {46+        c / 12.9247+    } else {48+        ((c + 0.055) / 1.055).powf(2.4)49+    }50+}51+52+fn lum(hex: &str) -> f64 {53+    let h = hex.trim_start_matches('#');54+    let r = u8::from_str_radix(&h[0..2], 16).unwrap() as f64 / 255.0;55+    let g = u8::from_str_radix(&h[2..4], 16).unwrap() as f64 / 255.0;56+    let b = u8::from_str_radix(&h[4..6], 16).unwrap() as f64 / 255.0;57+    0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)58+}59+60+fn contrast(a: &str, b: &str) -> f64 {61+    let (hi, lo) = {62+        let (la, lb) = (lum(a), lum(b));63+        if la > lb {64+            (la, lb)65+        } else {66+            (lb, la)67+        }68+    };69+    (hi + 0.05) / (lo + 0.05)70+}71+72+fn check(theme: &str, block: &str, pairs: &[(&str, &str)]) {73+    for (fg, bg) in pairs {74+        let f = var(block, fg);75+        let b = var(block, bg);76+        let ratio = contrast(&f, &b);77+        assert!(78+            ratio >= 4.5,79+            "{theme}: {fg} {f} on {bg} {b} = {ratio:.2}:1 (AA needs 4.5)"80+        );81+    }82+}83+84+#[test]85+fn both_themes_pass_aa_for_text_colors() {86+    let css = css();87+    let dark_at = css.find(":root{").expect("dark root");88+    let dark = block(&css, dark_at);89+    let light_at = css90+        .find("@media (prefers-color-scheme:light)")91+        .expect("light block");92+    let light = block(&css, light_at);93+94+    let pairs = [95+        ("--fg", "--bg"),96+        ("--fg", "--bg-alt"),97+        ("--fg", "--surface"),98+        ("--fg", "--surface-2"),99+        ("--fg-dim", "--bg"),100+        ("--fg-dim", "--bg-alt"),101+        ("--fg-dim", "--bg-th"),102+        ("--muted", "--bg"),103+        ("--muted", "--bg-th"),104+        ("--blue", "--bg"),105+        ("--blue", "--surface"),106+        ("--cyan", "--bg"),107+        ("--green", "--bg"),108+        ("--red", "--bg"),109+        ("--yellow", "--bg"),110+        ("--state-open", "--bg"),111+        ("--state-merged", "--bg"),112+        ("--state-closed", "--bg"),113+        ("--tok-kw", "--surface"),114+        ("--tok-str", "--surface"),115+        ("--tok-num", "--surface"),116+        ("--tok-com", "--surface"),117+        ("--tok-com", "--surface-2"),118+        ("--tok-fn", "--surface"),119+        ("--tok-type", "--surface"),120+    ];121+    check("dark", dark, &pairs);122+    check("light", light, &pairs);123+}124+125+#[test]126+fn themes_define_the_same_vars() {127+    let css = css();128+    let dark = block(&css, css.find(":root{").expect("dark root"));129+    let light = block(130+        &css,131+        css.find("@media (prefers-color-scheme:light)")132+            .expect("light block"),133+    );134+    // Every dark color var has a light override (scale vars may be shared).135+    for line in dark.lines() {136+        let Some(name) = line137+            .trim()138+            .strip_prefix("--")139+            .and_then(|l| l.split(':').next())140+        else {141+            continue;142+        };143+        if name.starts_with("gap-")144+            || name.starts_with('s')145+                && name.len() <= 2146+                && name[1..].chars().all(|c| c.is_ascii_digit())147+            || name.starts_with("fs-")148+            || name == "radius"149+        {150+            continue;151+        }152+        assert!(153+            light.contains(&format!("--{name}:")),154+            "light theme must override --{name}"155+        );156+    }157+}0-- 2.56.0

Comments (0)

No comments yet.

Reviews (0)

No reviews yet.

all pulls