ADR-043: The site wears the project’s brand: artwork by path, favicon rasterized at build time

Status Active · Filed 2026-08-10 · Issue #13 · Influenced by ADR-042

Context

ADR-042 put the record on the web wearing Quartz’s clothes: the generator’s off-white, the generator’s slate-blue links, and the word luria set in the sidebar as plain text. The project has a brand kit — a brain-shelled snail, a wordmark, two colours and a documented ink variable — and none of it reached the page. The site’s own front page carried the lockup only because the README happened to embed it.

A favicon is the sharpest case. It is the one piece of a site that appears somewhere the site does not control, next to a dozen other tabs, and Quartz serves whatever sits at quartz/static/icon.png — which, unaddressed, is Quartz’s icon on someone else’s record.

Decision

[luria.site] gains four branding keysicon, logo, logo_dark, and a theme table — all optional, all pointing at files the project already maintains. Two of them carry a constraint worth stating.

The favicon is rasterized during the build, and nothing derived from the artwork is committed. luria site copies the icon source verbatim; actions/site renders it to static/icon.png with sharp, which is Quartz’s own dependency, so this costs no new install and no new system package. A project therefore points at the vector master it maintains, and the rendered icon exists only inside a build. The alternative — commit a PNG — is a hand-maintained projection of a source of truth, and DP-3 is unambiguous about the rate at which those go wrong.

The corollary, learned by rendering it: an icon’s first reader is a rasterizer, and a rasterizer resolves neither a CSS custom property nor a media query. Artwork that inverts itself through --luria-ink renders as a solid black square. So an icon carries literal fills, with the dark theme as an override on top — a rule that applies to the artwork, not to Luria, and is written down in the icon file itself.

The logo is baked once per theme. Not because artwork cannot invert itself, but because whether it does is a property of the reader’s browser: Quartz declares color-scheme per theme, and a browser that carries that into an embedded SVG resolves the artwork’s own prefers-color-scheme rules against the site’s toggle — measured, and what Chromium does — while one that doesn’t resolves them against the operating system, which the toggle has nothing to do with. Two variants make the answer the same everywhere, and are the only way to theme a logo that has no media query at all. An SVG that exposes --luria-ink is re-inked automatically; one that doesn’t is used as it stands, so the accommodation costs nothing to a project that never heard of it.

The palette merges over the generator’s defaults by name, per mode, and an unknown colour name is refused with the known ones listed. A palette key silently ignored is a project wondering why its brand didn’t take.

Luria ships none of this as a default. The package’s defaults are Quartz’s palette and Quartz’s icon; this project’s brand lives in this project’s luria.toml.

Alternatives considered

  • Ship Luria’s brand as the default. Every adopter’s record would arrive wearing a snail. A package that hands out its own identity as a default is handing out a costume nobody asked for, and the ones who notice have to work out which knob turns it off.
  • Commit a rendered icon.png beside the vector. One less moving part in CI, and a derived file that drifts the first time the mark is redrawn — silently, because nothing compares them. DP-3 rung 1 is available here, so taking rung 3 would need an argument nobody has.
  • Rasterize in luria site itself. Python has no SVG rasterizer in the standard library, so this means a new dependency (cairosvg and its system libraries) on every install of a package whose other nine commands never touch an image. sharp is already there, in the one place the rendering is needed.
  • Compose the icon inside Luria — take a wordmark, crop it, pad it, round the corners. Tempting, since this project’s own mark needed exactly that treatment. It is design work, it does not generalize past one logo’s proportions, and it would put the package in the position of arguing about taste. The composition happened once, in the brand kit, where a designer can disagree with it.
  • One self-inverting logo file. Correct in the browsers that propagate color-scheme into images, which is most of them now. It fails silently in the others, and the failure is a logo that vanishes into the page — the kind of defect nobody reports because it looks like nothing at all.
  • A custom Quartz component for the sidebar. More control over the markup, and a much deeper coupling to the generator’s component API than a stylesheet rule needs. custom.scss is a documented extension point; replacing the title’s background is the smallest thing that brands it.

Consequences

  • The published record looks like the project: its paper, its ink, its lockup in the sidebar, its slug in the browser tab.
  • Adopters get a documented brand surface, and one they can ignore entirely — every key is optional, and the site without them is exactly the site ADR-042 shipped.
  • The staging directory now has more than files in it. The guard that ties stage to actions/site had to widen to directories, and to match cp commands rather than mentions of a name — asking whether static appeared anywhere in the action passed on three unrelated lines.
  • A palette is only as good as its contrast, and nothing checks it. The numbers were computed by hand for this one (every text pair above 6.5:1) and are recorded in the devlog; a project that overrides carelessly gets no warning. That is a report this record does not yet have.