igneum/docs/design/site-polish-2026-10-04.md
igneum-labs 970a51b57a Site polish: one header and footer from partials on every page, litepaper section router fixed, self-hosted fonts, 404 page, live page idle cost
Header: site/partials/nav.html, head.html, footer.html, injected by site/build.mjs between markers in every page (the bench template too) with the active link marked; the master mark in its black square, the wordmark, Litepaper, Live devnet, Engineering log, Evidence, GitHub and the miner button, in that order everywhere; one menu script (closes on a link, a tap outside, Escape with focus back on the button, a resize past 900 px); a skip link; one focus ring and one reduced-motion rule.

Litepaper: the hash routed by a click delegate and popstate instead of the browser's fragment jump. Root cause of the tabs fault: show() scrolled to the top of the article, never to the section; in whole-paper mode the fragment jump landed on the section and the smooth scroll then dragged the reader back to the Abstract; in one-section mode a deep link scrolled while the section was still display:none and landed on the cover. Now: every section and all 29 subsection headings reachable by URL in both modes, scroll-margin from the measured bar height (nav, plus the contents row on a phone), focus moved to the heading, a re-aim after a late font batch, mode buttons stacked in the sidebar.

Pages: homepage headline capped at 9cqw so it is two lines at 390 and 1440 (was four with "fire." alone), journey inlined by the build (no fetch, no shift), dates as "4 Oct 2026"; live page legend no longer forces 420 px of width at 390, the hash-rate unit as small mono, idle redraw at 30/s with a 1.5x backing store (0.7 to 1.2% of one core idle in Chrome for Testing, taken under load), the proving line wraps on a phone; evidence sort headings are buttons, chips carry aria-pressed, a scroll hint under 1,140 px; bench contents in a sticky scroll box, og.png?v=3; 404.html; sitemap with /evidence; vercel.json caches fonts a year and images a day.

Fonts: eight latin woff2 files in site/fonts (139 KB for all, 118 KB a typical page), preloaded first-paint faces, fallback faces with size-adjust and ascent overrides from the font tables; every page lost its render-blocking fonts.googleapis.com stylesheet and the gstatic origin.

HTML per page before to after (gzip): / 17,265 to 21,810 B; /litepaper 20,556 to 24,957; /live 15,640 to 17,777; /evidence 18,899 to 22,493; /bench 94,723 to 98,289. Third-party requests before to after: 3 to 1 (jsdelivr, light client), 1 to 0, 3 to 0, 1 to 0, 1 to 0. Lighthouse before and after is queued under the measure lock (held by reliability runs since 19:16Z, load average 258) and recorded in docs/design/site-polish-2026-10-04.md when it runs.

Checks: node site/build.mjs, tools/ci/link-check.mjs (246 links, 0 broken), identity grep, and a Chrome for Testing run over every page at 390, 768 and 1440 (no overflow, no console errors, menu, router in both modes, deep links on reload, keyboard).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 20:57:20 +00:00

12 KiB

Site polish, 4 October 2026

Branch site-polish. The brief: one header on every page, the litepaper's tabs fixed, every element of every page checked at 390, 768 and 1440 px in light and dark, performance measured before and after, copy law on every sentence, the build green. This file records what was broken, what changed, the numbers, and what was left alone.

1. What was broken

# Where Fault Root cause
1 Litepaper, whole-paper mode Every contents click, pager click and back press landed on the section for a moment and then slid back to the Abstract show() always scrolled to the top of <article>, never to the section. In one-section mode the section is the top of the article, so the fault was invisible; in whole-paper mode the browser's own fragment jump landed first and the smooth scroll then dragged the reader to the top
2 Litepaper, one-section mode A deep link (/litepaper#limits) opened on the cover, not the section The browser's fragment scroll ran while the section was still display:none; the router then showed the section with scroll=false
3 Litepaper, phone The sticky contents row covered the heading after a jump No scroll-margin-top; the hand-tuned 70 px offset did not match the row's real height (99 px)
4 Litepaper, keyboard and screen readers After choosing a section, focus stayed in the contents list Focus was never moved
5 Litepaper 29 subsection headings had no ids, so only the 17 sections were addressable by URL Never generated
6 Header, every page Five different headers: 11 links on the homepage, 10 on the live page (section anchors into the homepage), 6 plain links on the evidence and log pages, no header at all on the litepaper (a cover with two arrow links) Each page carried its own copy
7 Header, mobile The menu on the live page closed on tap-outside but not on Escape and not on a link tap; the evidence, log and litepaper pages had no menu, their links wrapped Three different scripts, two pages without one
8 Header, every page The mark was the bare flame, not the master mark in its black square Pre-dates the brand master of 4 October
9 Live page, phone The page scrolled sideways to 420 px at 390 px Three legend captions were white-space:nowrap spans ("one lane per miner, colour from its id; hover or tap a block")
10 Live page, phone The hash-rate cell clipped to "264.1 M…" 20 px Unbounded in a 125 px cell with text-overflow:ellipsis
11 Homepage, 390 px and 1440 px The headline wrapped to four lines with "fire." alone on the last clamp(40px, 7vw, 76px) never fitted "Mined by GPUs." in the column at either end
12 Homepage The journey block arrived after a fetch of journey.json, after first paint, and pushed the page down Rendered from a fetch
13 Every page A render-blocking stylesheet from fonts.googleapis.com, then the woff2 files from a second origin; no font fallback metrics, so the swap moved every line Google Fonts link in each <head>
14 404 Vercel's plain text "NOT_FOUND" No 404.html
15 Sitemap /evidence missing; every lastmod 3 October Not updated
16 Log page share card og.png?v=2 while every other page was on ?v=3 The build template was not bumped with the pages
17 Evidence table Column sort only by mouse (the <th> was the click target, not focusable); filter chips had no pressed state Markup

Items 9, 10, 11 and 13 were found by the checks in this round, not by eye.

2. What changed

Shared chrome (one source, injected by the build)

site/partials/head.html, nav.html, footer.html. site/build.mjs injects them into every page between <!-- head:start -->, <!-- nav:start --> and <!-- footer:start --> markers, marks the active link with aria-current="page", and inlines journey.json into the homepage between <!-- journey:start --> markers. The committed pages carry the injected copy, so they are right with or without a build; CI runs the build and the link check on every push.

  • Header: the master mark (black square, no ring) at 36 px, the wordmark, five links in one order on every page (Litepaper, Live devnet, Engineering log, Evidence, GitHub) and the Get the miner button. Sticky, 68 px, blurred backdrop. Under 901 px a menu: opens on the button, closes on any link, a tap outside, Escape (focus returns to the button) or a resize past 900 px; aria-expanded and aria-controls set. A skip link to #main on every page.
  • Footer: one footer, six Read links and four Follow links, the same everywhere. Page-specific lines (the evidence "as of" date, the log's "generated at build time") moved into the page body above it.
  • Litepaper: the chrome follows the paper's scheme (light by default, dark with the system) through --ui-* tokens; every other page is obsidian.
  • Fonts: self-hosted latin subsets in site/fonts/ (Unbounded 500/700/900, IBM Plex Sans 400/500/600, IBM Plex Mono 400/500; OFL), 139 KB for all eight, 118 KB for a typical page, font-display:swap, the two first-paint faces preloaded. Fallback faces (Unbounded Fallback on Arial Black, Plex Sans Fallback on Arial, Plex Mono Fallback on Courier New) carry size-adjust, ascent-override and descent-override computed from the font tables with fonttools, so the swap does not move the layout (a deep link into the litepaper lands within 3 px before and after the fonts arrive, and is re-aimed on document.fonts.ready).
  • Site-wide: text-wrap:balance on h1 to h3, text-wrap:pretty on paragraphs and list items; one :focus-visible ring; one prefers-reduced-motion rule that stops every animation and transition.

Litepaper

New section router. The hash names a section or any heading inside one, in both reading modes. In-page links are routed by a click delegate (pushState + go()), never by the browser's fragment jump; back and forward replay through popstate; every scroll clears the sticky bar and, on a phone, the contents row (--lp-off, measured from the real heights; [id]{scroll-margin-top:var(--lp-off)} so a native jump lands in the same place); focus moves to the heading with preventScroll; every h3 gets an id from its text (29 added, so /litepaper#what-is-not-here works). The mode buttons carry aria-pressed and stack vertically in the desktop sidebar (they wrapped to two lines before). The cover lost its duplicate brand block and arrow links.

Homepage

Header reduced to the five links; the in-page sections are still there for scrolling. The headline is capped at 9cqw of its column so it is two lines at every width. Journey data is inlined by the build (no fetch, no shift); dates read "4 Oct 2026" with the day shown once per group; the toggle carries aria-expanded; the NOW label lists the active phases with commas and balances its lines. A preconnect to cdn.jsdelivr.net for the light client's libraries.

Live page

Legend captions wrap. The hash-rate value shows its unit in small mono beside the number. Idle, the scene is drawn 30 times a second instead of 60 (a pointer or a pinned block gets every frame); the DAG canvas backing store is capped at 1.5x. The proving strip's "not yet activated" line breaks at its semicolon when wider than the strip on a phone. ?perf=1 exposes window.__igneumPerf (frames, draw milliseconds) for the next measurement.

Evidence

Column headings are real buttons (keyboard sort, aria-sort kept); filter chips carry aria-pressed; a "scroll sideways" hint above the 1,100 px table under 1,140 px; the "as of" line moved into the page.

Engineering log (build template)

Contents in a labelled box: sticky and scrollable beside the article on desktop, a 40 vh scroll box on a phone (57 entries). Headings land under the bar (scroll-margin-top). og.png?v=3.

404

site/404.html: the four pages as cards. Vercel serves it for any unknown path (checked locally with the same clean-URL rules; noindex).

Headers and sitemap

vercel.json: fonts immutable for a year, images and the manifest a day with a week of stale-while-revalidate; HTML stays must-revalidate. sitemap.xml: /evidence added, lastmod 4 October.

3. Checks

check.mjs (scratchpad, Chrome for Testing 154, never the owner's Chrome) over /, /litepaper, /live, /bench, /evidence and a 404 path at 390, 768 and 1440 px: no horizontal overflow, no console errors, the five links in order and the right one active, the square mark present, the menu opens, closes on tap-outside and on Escape with focus back on the button; on the litepaper in both modes at 390 and 1440: a contents click, a pager click, back, a deep link to a section and to an h3 on reload, and Enter on a focused pill each land within 2 px of the measured offset with focus on the heading. node site/build.mjs and tools/ci/link-check.mjs (245 internal links, 0 broken) pass.

4. Numbers

Static, from the files (before = the committed site at 8fd492c, after = this branch):

Page HTML before (gzip) HTML after (gzip) Third-party requests before After
/ 57,261 B (17,265) 74,065 B (21,810) 3 (fonts CSS, 2 preconnects) + the woff2 files 1 (jsdelivr, light client only)
/litepaper 63,628 B (20,556) 79,208 B (24,957) 1 + woff2 0
/live 47,680 B (15,640) 56,357 B (17,777) 3 + woff2 0
/evidence 56,729 B (18,899) 69,890 B (22,493) 1 + woff2 0
/bench 267,259 B (94,723) 280,179 B (98,289) 1 + woff2 0

The HTML grew by the inlined font-face block, the shared chrome and, on the homepage, the inlined journey (about 4 KB gzipped a page). In exchange every page lost a render-blocking stylesheet from a third origin and two DNS lookups.

Live page, idle draw cost (Chrome for Testing, 2x display, canvas in view, 10 s, ?perf=1; taken while cargo builds and a reliability run held the Mac, so an upper bound): 1440 px, 30.0 draws/s, 69 ms of draw in 10 s = 0.7% of one core; 390 px, 30.0 draws/s, 60 ms = 0.6%. Before the change the loop drew at the display rate (60/s) with a 2x backing store, so the same work was about 2.6x this, approximate. Off screen or in a hidden tab the loop stops, as before.

Lighthouse (mobile simulation, performance only, before on a frozen copy of the committed site at port 4174, after on port 4173, Chrome for Testing 154 downloaded into the scratchpad): queued at 19:45Z under tools/lock/with-lock.sh measure and not run by the time this was written. The measure lock was held by reliability runs from 19:16Z onwards (pid 66963, then pid 5534 from 20:18Z) with three cargo builds beside them and a load average of 258, and a paint time taken under that is not a number. The runner is scratchpad/lh.sh (before and after, five pages, score, FCP, LCP, TBT, CLS, speed index, bytes, requests, render-blocking resources); run it under the measure lock on a quiet machine and paste the table here. What can be stated without a timer: every page lost its only render-blocking third-party stylesheet and the two origins behind it; the fonts a page needs are one origin, preloaded, cached a year; the homepage lost the post-paint journey fetch; the fallback metrics hold the layout within 3 px across the swap.

Deep-link landing: within 2 px of the measured offset in every case but one. In one-section mode at 1440 px the Finality section is short, "What is not here" is its last heading, and the page cannot scroll far enough to put it under the bar; it lands at the page's maximum scroll, 3 px short. A padding trick would add a blank screen under every section, so it is left as it is.

5. Left alone, and why

  • site/api/live.mjs, checkpoint.mjs, log.mjs: the observer agent's. The homepage light-client card currently reads "could not verify: voter 0 key does not hash to its vote_key_hash" on production as well as locally; that is the checkpoint data, not the page, and is reported, not fixed here.
  • site/verify/: the light client, unchanged.
  • The GitHub link (github.com/igneum-network/spec, 200 on 4 October) stays; docs/fud-fixes.md X1 asked for it to go only while private.
  • Download buttons: the homepage's Windows, macOS, Linux and HiveOS buttons link to #journey as before (no public build yet); no dl. URL exists on the site, the scrub replaces the host name on the log page.
  • The litepaper's light scheme by default: the paper is the one light surface on purpose.
  • Three glowing active phase cards on the homepage: kept, stopped under reduced motion.
  • The evidence table's 1,100 px minimum width: it is a 7-column reference table; it scrolls inside its box and says so on narrow screens.