Archilyzer · Source

archilyzer

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

commit 9b4b7f8ceacd6864e27d5409e8be9bfdb075a5d0
parent c5b5c6b354c57f05d48a8f06c333f6f32aa7ef6b
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 02:59:59 -0400

common: the mark's tile keeps an edge under forced colours — a 1px CanvasText outline on every base

In forced-colours mode (Windows High Contrast) the reader's Canvas replaces
the page on every base and may be as dark as the tile, while the ring is
drawn only on dark. BRAND_MARK_RING_CLASS gains forced-colors:outline and
forced-colors:outline-[color:CanvasText]: under
`@media (forced-colors: active)`, outside the tile and following its corner,
no layout. Outside forced colours the two classes match nothing, so light,
sepia and dark render as before. (Chromium keeps an svg's box-shadow in that
mode — its UA sheet's forced-color-adjust: preserve-parent-color — and the
outline is painted over the same pixel ring.)

BrandMark.test.ts pins the classes; the homepage brand.spec emulates
forced-colors (Playwright applies the forced palette) and reads the computed
outline (solid 1px CanvasText, offset 0, radius 21.875%, 28 × 28) on all
three bases, and outline none in the normal-mode test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/components/BrandMark.test.ts | 18++++++++++++++++++
Mcommon/components/BrandMark.tsx | 15++++++++++++++-
Mhomepage/e2e/brand.spec.ts | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 86 insertions(+), 3 deletions(-)

diff --git a/common/components/BrandMark.test.ts b/common/components/BrandMark.test.ts @@ -28,6 +28,24 @@ test("the ring's corner is the ground's: MARK_GROUND_RX of MARK_VIEWBOX", () => assert.ok(BRAND_MARK_RING_CLASS.split(" ").includes("overflow-visible")); }); +test("forced colours: a 1px CanvasText outline, the one ring the mode keeps", () => { + // Forced-colours mode puts the tile on the reader's Canvas on every base, so + // the tile gets an outline in the reader's text colour — on every base (no + // `dark:`), 1px (the bare `outline` utility), with no offset, so it sits + // where the ring does and follows the same rounded corner. Every class of it is forced-colours only: + // the other modes render exactly as they did. + const classes = BRAND_MARK_RING_CLASS.split(" "); + const forced = classes.filter((c) => c.startsWith("forced-colors:")); + assert.deepEqual(forced, ["forced-colors:outline", "forced-colors:outline-[color:CanvasText]"]); + assert.deepEqual( + classes.filter((c) => c.includes("outline")), + forced, + "no outline outside forced colours", + ); + assert.ok(classes.includes("rounded-[21.875%]")); + assert.ok(!classes.some((c) => /^forced-colors:.*(offset|dark)/.test(c))); +}); + test("each palette rings in its own dim, set as --mark-ring on the svg", () => { const parent = render(createElement(BrandMark, { palette: ICON_PALETTES.archilyzer })); assert.match(parent, /style="--mark-ring:#586977"/); diff --git a/common/components/BrandMark.tsx b/common/components/BrandMark.tsx @@ -20,9 +20,22 @@ export type BrandMarkPalette = Readonly<Record<MarkTone, string>>; // - `overflow-visible`: an <svg> clips its content by default, and with a // border-radius that clip would be a second anti-aliased edge laid over // the ground's own rounded corner. +// FORCED COLOURS (Windows High Contrast, release 11): the reader's palette +// replaces the page on every base, and its Canvas may be as dark as the tile +// (a black high-contrast theme), while the ring is only drawn on dark — and an +// engine that forces `box-shadow: none` there drops even that. So under +// `@media (forced-colors: active)`, on every base, the tile gets a 1px OUTLINE +// in the system `CanvasText`, outside it and following the same corner (an +// outline follows border-radius). An <svg> keeps its own colours there +// (Chromium's UA sheet gives it `forced-color-adjust: preserve-parent-color`, +// which also keeps the dark ring; the outline is painted over the same pixel +// ring), so the outline is the one part drawn in the reader's colours. +// Outside forced colours the two classes match nothing: every other mode +// renders as before. // Complete literal class strings — Tailwind v4 only sees whole literals. export const BRAND_MARK_RING_CLASS = - "rounded-[21.875%] overflow-visible dark:shadow-[0_0_0_1px_var(--mark-ring)]"; + "rounded-[21.875%] overflow-visible dark:shadow-[0_0_0_1px_var(--mark-ring)] " + + "forced-colors:outline forced-colors:outline-[color:CanvasText]"; // The Found-line mark, inline — drawn from the same MARK shape list as // markSvg (the icon files), so the two cannot drift. Decorative: the text diff --git a/homepage/e2e/brand.spec.ts b/homepage/e2e/brand.spec.ts @@ -53,7 +53,8 @@ async function onBase(page: Page, base: "light" | "sepia" | "dark") { } // The mark's visible box-shadow layers (Tailwind's composite lists four -// transparent ones before the ring), its corner, and its laid-out size. +// transparent ones before the ring), its outline, its corner, and its laid-out +// size. async function markRing(mark: Locator) { return mark.evaluate((svg) => { const cs = getComputedStyle(svg); @@ -61,6 +62,7 @@ async function markRing(mark: Locator) { const layers = cs.boxShadow === "none" ? [] : cs.boxShadow.split(/,(?![^(]*\))/); return { ring: layers.map((l) => l.trim()).filter((l) => !l.startsWith("rgba(0, 0, 0, 0)")), + outline: cs.outlineStyle, radius: cs.borderTopLeftRadius, size: [r.width, r.height], }; @@ -78,12 +80,62 @@ test("on dark the header mark has a 1px ring in the slate's dim; on light and se await onBase(page, "dark"); expect(await markRing(mark)).toEqual({ ring: ["rgb(88, 105, 119) 0px 0px 0px 1px"], // ICON_PALETTES.archilyzer.dim, #586977 + outline: "none", radius: "21.875%", size: [28, 28], }); for (const base of ["light", "sepia"] as const) { await onBase(page, base); - expect(await markRing(mark), base).toEqual({ ring: [], radius: "21.875%", size: [28, 28] }); + expect(await markRing(mark), base).toEqual({ + ring: [], + outline: "none", + radius: "21.875%", + size: [28, 28], + }); } }); + +// Forced colours (Windows High Contrast; release 11): the reader's Canvas +// replaces the page on every base and may be as dark as the tile, and the ring +// is only drawn on dark (an engine that forces box-shadow off drops even that). +// So there the tile gets a 1px outline in the system CanvasText, on every base, +// outside it and following the same corner; the mark's box is unchanged. +// Playwright's emulation applies the forced palette itself (a white Canvas). +// (Outside forced colours the test above holds: no outline.) +test.describe("under forced colours", () => { + test("the header mark's tile is outlined in CanvasText on every base", async ({ page }) => { + await page.emulateMedia({ forcedColors: "active" }); + await page.goto("/"); + const mark = page + .getByRole("link", { name: "Archilyzer home", exact: true }) + .locator("svg[data-brand-mark]"); + for (const base of ["dark", "light", "sepia"] as const) { + await onBase(page, base); + expect(await page.evaluate(() => matchMedia("(forced-colors: active)").matches)).toBe(true); + const got = await mark.evaluate((svg) => { + const cs = getComputedStyle(svg); + const probe = document.createElement("span"); + probe.style.color = "CanvasText"; + document.body.append(probe); + const canvasText = getComputedStyle(probe).color; + probe.remove(); + const r = svg.getBoundingClientRect(); + return { + outline: `${cs.outlineStyle} ${cs.outlineWidth}`, + isCanvasText: cs.outlineColor === canvasText, + offset: cs.outlineOffset, + radius: cs.borderTopLeftRadius, + size: [r.width, r.height], + }; + }); + expect(got, base).toEqual({ + outline: "solid 1px", + isCanvasText: true, + offset: "0px", + radius: "21.875%", + size: [28, 28], + }); + } + }); +});