This document records how jseverino.com got a real brand, and how that brand
grew from a one-off script in this repository into a standalone engine that the
site, the brand kit, and the command-line tools all share. It is as much a story
as an architecture note: the interesting part is the path, not just the diagram.
The starting point was an accident, not a design.
The site ran on WordPress, and its accent color was purple. Nobody chose that purple. It was the default of the WordPress theme, inherited the day the theme was installed and never revisited. It showed up in links and headings because that is simply what the theme shipped with.
Alongside it was a yellow JS logo. Its origin is unknown. There is no source
file, no design decision, and no record of where or how it was made. It was just
the logo, the way the purple was just the accent.
So the site had two brand colors, and they had nothing to do with each other. The theme was purple by inheritance and the logo was yellow by mystery. Neither was deliberate, and the two never matched. That mismatch is what started all of this: once it becomes obvious the theme color and the logo are two different colors that nobody ever actually picked, it cannot be un-noticed.
The fix was to choose, once, on purpose.
The mark was rendered in a range of candidate colors and compared side by side.
Navy (#1E3A8A) won: it carries a trust-and-infrastructure register that fits a
security and networking portfolio, and it reads cleanly as a white glyph on a
solid tile at favicon sizes. Severino HQ, the private operations app, took its
own teal (#1f4d57) so the surfaces stay distinct while sharing one monogram.
The important move was applying the chosen color in both places at once. The
same navy became the favicon and mark tile and the site's theme color
(--color-primary, <meta name="theme-color">). For the first time the logo and
the interface were the same color, because they were now driven by the same
decision instead of two accidents.
Rather than save a static logo file, the mark became something the repository generates.
The JS monogram is composed from real Inter (weight 800) glyph outlines and
laid out programmatically into an SVG. One token file, src/lib/brand.mjs, holds
the identity (the navy, the glyph), and three consumers read from it: the favicon
generator, the social-card renderer, and the CSS that sets the theme color. Change
the color in one place and the favicon, the Open Graph card, and the interface all
follow. The site, in effect, generates its own logo from a single source of truth,
so the "two colors that never agreed" problem cannot come back: there is only one
color, in one file.
Tightening that pipeline surfaced a gap. The mark was a true vector built from
outlines, but the wordmark lockup (the tile plus the name) existed only as a
raster PNG, screenshotted from a browser. The fix made the wordmark vector-first
too: it is composed from the same Inter outlines into a wordmark.svg, and the
light/dark PNGs are rasterized from that SVG. A second, all-caps lockup was added
to match how the site sets the name in its header. The principle is simple:
geometry is vector; only things that must be raster (social cards, platform
icons) are raster.
The generators were generic from the start. The code that lays out a monogram and renders a card knows nothing specific about Joe Severino; it takes a color, a set of initials, and a name. But that generic code lived inside this site's repository, which meant it could not be reused without copying it.
So it was lifted out in two moves. First, the brand data (the navy, the glyph,
the card copy, the portrait) moved into its own kit, severino-brand, separating
"who the brand is" from "how to render it." Then the rendering engine was
extracted into a standalone package, branding-engine, leaving behind only the
data and a dependency. The site stopped owning a private copy of the engine and
became a consumer of it, like everything else.
Each step was verified by regenerating every asset and diffing it against what was already committed. The favicons, marks, social cards, and brand sheets all came out byte-for-byte identical, which is how a refactor this deep avoids quietly redrawing the logo.
The result is one engine with several consumers:
Diagram source: docs/diagrams/branding-engine-consumers.mmd,
pre-rendered with diagram.
- The site depends on the engine to regenerate its favicons, social cards, and
header wordmark, and commits the output. Its production build never runs the
engine; the header inlines the committed
wordmark-caps.svgso its glyphs pick up the link's hover color throughcurrentColor. - The brand kit (
severino-brand) is pure data plus a dependency on the engine; building it renders the navy kit, the HQ teal kit, and one-off kits for other people. - The
brandtool wraps the engine for everyday use from the terminal.
The engine itself is the public, reusable piece:
branding-engine. Anyone can
render their own kit from one accent color and a set of initials, with no
Severino-specific assumptions baked in.
A generator can make assets consistent, but consistency alone does not prove
that a redesign works once deployed. I used another tool I built,
sitedrift, to test that second
half of the problem.
For a temporary Cloudflare branch deployment, the site's primary token changed
from navy to red. branding-engine regenerated the favicon, marks, wordmark,
Open Graph card, social preview, and interface-facing brand values from that
single edit. Sitedrift then loaded the red branch as DEV and the current navy
site as LIVE on the same route.
The commit diff makes the source-of-truth relationship concrete. A small set of
palette values changed in src/lib/brand.mjs; the generated Open Graph card
changed with them. The portrait, typography, dimensions, and content stayed
fixed because the rendering system did not need to be redesigned.
The same input propagated through the GitHub social preview and transparent mark. This is why the generator matters: the repository does not rely on someone remembering to recolor a collection of unrelated exported files.
The side-by-side view shows the value of a single source of truth: every brand-colored surface moves together while the layout and content stay aligned. Diff mode makes the same claim more rigorously by suppressing identical pixels and exposing only the changed brand surfaces.
The immutable demonstration remains available at
6ef83545.jseverino.pages.dev. The
working branch was restored to navy afterward, so the experiment remains
reviewable without becoming the site's active design.
The site keeps its tokens local and borrows only the rendering:
severino-brand/brand/tokens.jsonis the upstream source of truth — the brand identity (brand: navy, glyph), the design system (designSystem: the:rootcustom properties), and the dark values for the themeable subset of those properties (designSystemDark). The site never reads it at build time.npm run sync:tokens(bin/sync-tokens.mjs) vendors it into two committed files, rewriting only the region betweentokens:start/tokens:endmarkers: theBRANDexport insrc/lib/brand.mjs(frombrand) and the:rootblock insrc/styles/tokens.css(fromdesignSystem). Same pattern assync:content— an external source of truth, vendored to a committed artifact, so the build stays self-sufficient. The read- marker-splice + render primitives live upstream in
severino-brand/brand/sync.mjsand are shared with the vault's Obsidian theme generator, so the projection logic isn't reimplemented per consumer — only the list of targets differs.
- marker-splice + render primitives live upstream in
bin/make-icons.mjs,bin/make-og-image.mjs, andbin/make-github-social.mjsimportmarkSvg/renderCardfrombranding-engineinstead of a local copy, and pass it the syncedBRAND. The engine is generic; the tokens supply the color.- The generated assets in
public/assets/are committed. To restyle the brand, edittokens.jsonupstream, runnpm run sync:tokens, re-run the generators, and commit the new tokens + assets together.
designSystemDark lists only the tokens whose value changes in dark. The renderer
folds the two maps together into a single :root block where each themeable token
holds both values at once:
:root {
color-scheme: light dark;
--color-bg: light-dark(#ffffff, #131826);
--color-text: light-dark(#0b0620, #e8eaf2);
--color-border: color-mix(in oklch, var(--color-text) 8%, transparent);
}This is why there is no dark stylesheet, no [data-theme] selector duplicating a
2,000-line file, and no per-component dark override. Three consequences worth
knowing:
- Derived tokens adapt for free.
--color-borderis acolor-mix()over--color-text, so it has no dark entry — it inherits the flip. Anything expressible as a mix of an already-themeable token should stay derived. light-dark()only accepts colors.--shadow-smis geometry plus a color, so the color half was split into--shadow-color-smand the shadow composes it. Apply the same split to any future token that isn't a bare color.- A dark key with no light counterpart throws.
mergeThemesinseverino-brand/brand/sync.mjsrefuses to emit a token that exists only in the dark map, since a typo there would otherwise vanish silently from the output.
The terminal group (--code-*, --term-*) has no dark entries on purpose: it
represents a real terminal and stays dark in both themes. --color-primary is
dual-valued too, but from brand.onDark in brandVarsCss() rather than the
design-system block, since it is brand identity and lives in /brand.css. Navy is
unreadable on a dark page; onDark.primary is the readable counterpart, and
onDark.primaryDeep is lighter than it, because "deep" means more emphasis and
emphasis moves toward the far end of the page's contrast range in either theme.
The engine is an optionalDependency, pinned to a published, provenance-attested
branding-engine npm version (^0.2.2).
Because the rendered assets are committed, the deploy never needs the engine: if
CI cannot fetch it, the install skips it (non-fatal) and the static build runs
unchanged. The engine is only ever invoked locally, on demand, to regenerate.
The site loads its writeup styling as two global stylesheets, and anything that renders writeup HTML outside the site must load both — this is the single contract that, left implicit, cost a debugging session.
src/styles/base.cssis the ordered design-system entrypoint. It imports concern-based modules for tokens, foundation, layout, content, forms, responsive behavior, software, and accessibility; Vite still emits one production stylesheet./brand.css(src/pages/brand.css.ts) is the brand identity:--color-primary/--color-primary-deep. It is swappable (the sitedrift demo changes one token and regenerates everything).
They are kept apart on purpose: brand identity (brand in tokens.json) is
swappable, the design system (designSystem) is stable, so collapsing them would
break the "change one brand value, regenerate" model. The catch is that
The entrypoint's tinted tables, links, and buttons all read --color-primary, so
base.css loaded alone renders dead. You need both.
To keep an embedder from re-deriving that, the assembly is owned once:
src/lib/brand.mjsexportsbrandVarsCss()— the:rootbrand-vars string./brand.cssemits exactly this, so the endpoint and any embedder share one definition.src/lib/web-styles.mjsexportspreviewStyles({ baseCss, fontUrl })— the expanded CSS entrypoint + the brand vars + a resolvable Inter@font-face, as one<style>blob. An embedder calls this one function and cannot forget the brand vars.baseCssandfontUrlare passed in because each embedder obtains them its own way (esbuild text/dataurl import, a fetch, a file read); only the assembly is shared.
The severino-obsidian plugin's preview pane is the first consumer: it imports
previewStyles (via an esbuild @site/web-styles alias) and hands it the
esbuild-inlined base.css and Inter woff2. That replaced a hand-rolled
--color-primary injection that had silently gone dead.
docs/Architecture.mddocs/WordPress-To-Astro-Migration.mdbranding-engine(the engine repo)




