Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 1b203b6b3ee04b0961b544581fd7d10fb99d25b1
parent aaf834d18bd67f3d47715dd49a8b0e66dd31561d
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 29 Sep 2026 00:29:45 -0400

export, common: the wordmark's reservation is a minimum width, so text that renders wider than the display face's metrics widens its box instead of clipping (re-review N2)

The header sets wordmarkWidthEm as `min-width`, not `width`. The fit is
decided by the wider of the reservation and the rendered text: the same in
the fallback face and in Archivo wherever Archivo renders within the
reservation, and never a clipped last letter where it renders wider.

e2e: header.spec forces the text 8 % wider (letter-spacing) and sweeps 519 to
280 px: wherever the name shows, it does not overflow its box or the clip box.
With a fixed width it is clipped from 519 to 344 px.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mcommon/lib/wordmarkWidth.ts | 11++++++-----
Mexport/app/components/Header.tsx | 14++++++++------
Mexport/e2e/header.spec.ts | 35+++++++++++++++++++++++++++++++++++
3 files changed, 49 insertions(+), 11 deletions(-)

diff --git a/common/lib/wordmarkWidth.ts b/common/lib/wordmarkWidth.ts @@ -11,11 +11,12 @@ import { WORDMARK_METRICS } from "./wordmarkMetrics"; // The lead is weight 720 and the suffix 380 (common/components/Wordmark.tsx), // both at wdth 118; each character is its advance from the vendored font // (lib/wordmarkMetrics.ts, generated) plus `letterSpacingEm`, kerning left -// out, and MARGIN more: the web font Google serves renders 0.2–1.7 % wider -// than the vendored file's advances sum to (measured in Chromium on six -// titles), and the reserved box must never be narrower than the text. A -// character the table does not hold counts as UNMAPPED_EM, wider than any it -// does. +// out, and MARGIN more: the web font Google serves renders 0.2–2.4 % wider +// than the vendored file's advances sum to (measured in Chromium). The header +// sets this as the wordmark's MIN-width, not its width: where a platform +// renders the text wider still, the box grows with it (and the text drops a +// little sooner) rather than clipping its last letter. A character the table +// does not hold counts as UNMAPPED_EM, wider than any it does. const UNMAPPED_EM = 1.25; const MARGIN = 1.03; diff --git a/export/app/components/Header.tsx b/export/app/components/Header.tsx @@ -55,11 +55,13 @@ import MobileMenu from "./MobileMenu"; // threshold is written down: the text sits in a one-line box (`h-7`, // `overflow-hidden`, `flex-wrap`) behind a zero-width strut, and a flex item // that does not fit beside the strut wraps to the box's second line, which is -// clipped. The text keeps a WIDTH RESERVED from the display face's own -// metrics (lib/wordmarkWidth.ts, in em, so it scales with the reader's text -// size): the decision is the same in the fallback face, which is narrower, -// and after Archivo loads — a title never shows and then vanishes. The text -// stays in the DOM, so the link keeps its name. +// clipped. The text keeps a MINIMUM WIDTH reserved from the display face's +// own metrics (lib/wordmarkWidth.ts, in em, so it scales with the reader's +// text size): the decision is the same in the fallback face, which is +// narrower, and after Archivo loads — a title never shows and then vanishes. +// A minimum, not a width: where the text renders wider than the reservation, +// its box grows with it instead of clipping the last letter. The text stays +// in the DOM, so the link keeps its name. // Below `lg` the brand link takes the bar's free space (basis 0, grow 1) and // can shrink to the mark alone; only then does the group give way, and its // social row scrolls in SocialScroll's box, the last resort, its END shown @@ -111,7 +113,7 @@ export default function Header() { title={site.headerTitle} lead={site.wordmarkLead} className="ml-2.5 shrink-0 whitespace-nowrap text-[1.35rem] leading-none tracking-[-0.01em]" - style={{ width: `${wordmarkWidthEm(site.headerTitle, site.wordmarkLead, -0.01)}em` }} + style={{ minWidth: `${wordmarkWidthEm(site.headerTitle, site.wordmarkLead, -0.01)}em` }} /> </span> </Link> diff --git a/export/e2e/header.spec.ts b/export/e2e/header.spec.ts @@ -245,6 +245,41 @@ test("the title shows at the same widths in the fallback face and in Archivo", a } }); +// The reservation is a MINIMUM width: where the text renders wider than the +// display face's metrics say (here forced wider by letter-spacing), its box +// grows with it and the text drops sooner; its last letter is never clipped. +test("a wordmark that renders wider than its reservation widens its box and is never clipped", async ({ page }) => { + await installRoutes(page); + writeSite({ headerTitle: "Rekietalyzer", wordmarkLead: "Rekieta", socialLinks: lastMarked(SIX.slice(0, 4)) }); + await open(page, 519); + await page.addStyleTag({ content: "header [data-wordmark] { letter-spacing: 0.08em !important; }" }); + await page.evaluate(() => document.fonts.ready); + let shown = 0; + let hidden = 0; + for (let w = 519; w >= 280; w--) { + await page.setViewportSize({ width: w, height: 800 }); + const r = await page.evaluate(() => { + const box = document.querySelector("header [data-header-brand] span.flex-wrap") as HTMLElement; + const wm = box.lastElementChild as HTMLElement; + return { + shown: wm.offsetTop < box.clientHeight - 1, + textRight: (wm.lastElementChild as HTMLElement).getBoundingClientRect().right, + boxRight: box.getBoundingClientRect().right, + over: wm.scrollWidth - wm.clientWidth, + }; + }); + if (!r.shown) { + hidden++; + continue; + } + shown++; + expect(r.over, `${w} px: the text overflows its box`).toBeLessThanOrEqual(1); + expect(r.textRight, `${w} px: the last letter is clipped`).toBeLessThanOrEqual(r.boxRight + 0.5); + } + expect(shown, "widths with the name").toBeGreaterThan(0); + expect(hidden, "widths without it").toBeGreaterThan(0); +}); + // THE RULING: on a narrow screen the header keeps the NAME and shows only the // marked link(s), in the accessibility tree too; focus goes brand → the marked // link → the toggle → the menu. Every real site title shows in full at 360 px