AmoleoFamily

Foundations

Amoleo Light

bg #f7f7fb
panel #ffffff
text #3a3a4a
muted #6b6b7a
border #e0e0e6

Amoleo Dark

bg #101013
panel #191920
text #e9e9ee
muted #9a9aa8
border #2b2b33

The base theme — Amoleo Light and Amoleo Dark

The neutral palette three of the five products' own light/dark themes are built on: page background, surface background, body text, muted text, borders. Every product then puts its own accent on top — --brand-pets, --brand-connections, and so on, already governed by tools/tokens/base.tokens.json and docs/family-footer.md. This document is about the layer underneath that: the part that isn't supposed to vary by product at all.

This was never written down anywhere before 5 August 2026. It existed — Website, Pets and Connections' neutral greys are already byte-identical to each other — but only as an informal copy-by-hand. Website's own styles.css says so in a comment: "Values are copied from Connections' styles.css rather than re-derived, and should stay in step with it." That sentence is a promise with nothing enforcing it, which is exactly the shape every other piece of drift in this family started as before it got a JSON file and a checker. This is that JSON file and that checker, for this one.

The values live in tools/tokens/theme-light.tokens.json and theme-dark.tokens.json — real DTCG token files, since 8 Sept 2026 (see docs/family-css-system.md). Per-repo aliases, onlyThemes and exemptions (governance, not values) live in tools/brand-policy.json's baseTheme section instead — tools/check-brand.mjs's parser already does everything this needs: per-theme scoping, var() resolution, per-repo exemptions. The same values also compile into family-css/amoleo-light.css / amoleo-dark.css — the literal, copied-not-retyped files — see docs/family-css-system.md. Run this after touching a repo's base theme tokens:

node tools/check-brand.mjs

1. What's shared and what isn't

Shared — governed, must match:

Concept Light Dark
bg — page background #f7f7fb #101013
panel — surface/card background #ffffff #191920
text — body text #3a3a4a #e9e9ee
muted — secondary/meta text #6b6b7a #9a9aa8
border — dividers, subtle rules #e0e0e6 #2b2b33

Not shared — each site's own, by design:

Why the distinction matters going forward: a future product is free to pick --text-strong to suit its own content (a photo-heavy app might want a warmer near-black than #1c1c2e) without that being brand drift. Changing bg, panel, text, muted or border without a documented reason, however, is drift — those are what "the same family" actually looks like at the level nobody consciously notices, the way a mismatched line-height reads as "off" before a viewer could say why.


2. Naming isn't shared, even where the values are

The three repos that share this base don't agree on what to call it:

Concept Website Connections Pets
bg --bg --bg --page-bg
panel --panel --panel --bg
text --text --text --text
muted --muted --muted --text-light
border --border --border (was --panel-2 until the 6 Aug 2026 split — family-css-system.md §4) --border

Pets calling its page background --page-bg and its surface background --bg is the one worth remembering — it inverts Website/Connections' naming, where --bg is the page. check-brand.mjs resolves each repo's own alias map (baseTheme.repos.<name>.aliases) rather than matching a fixed property name, for exactly this reason.


3. Two repos that legitimately don't follow this

Rissbrook still loads the shared files (docs/family-css-system.md §3) — "exempt" below describes the values it ends up with, not a reason to skip amoleo-light.css/amoleo-dark.css. It's the repo that overrides the base the most, not the one that opts out of it.

Rissbrook is exempt in full, not failing quietly. Every colour on that site — including what would be its base theme — is Robert Bickley's own, documented in the repo's own tokens.css header: "Every colour here comes from Robert Bickley's own our-family-blue.css." Its page background is #ffffff, not the shared #f7f7fb; its ink is var(--rb-dk-teal), not the shared #1c1c2e; its muted text is #566672, not #6b6b7a. None of that is oversight — it's the same category of deliberate exception as Pets' own thirteen cat/dog accent themes, just at the base-theme level instead of the accent level. Rissbrook is also light-only, so there's no dark side to have diverged in the first place.

One token needs its own fixed override even so: --brand-amoleo. Rissbrook's other neutral tokens (--bg/--panel/--muted) are safe to pin unconditionally because nothing else ever sets them for this repo — but --brand-amoleo is genuinely theme-split family-wide (white in dark, #1c1c2e in light, §1), and the shared files only ever apply the light value behind an explicit signal — [data-theme="light"] or an OS light preference — never as the silent default. Rissbrook sets neither. Deleting its own --brand-amoleo the same way the four theme-independent --brand-* tokens were correctly deleted (family-css-system.md §5) left a visitor with their OS set to dark seeing dark's white ink against this site's own permanently-white --bg — invisible "AMOLEO" text, on both the header and the footer lockup (found and fixed 6 Aug 2026). The fix is the same shape as the neutral base tokens: a fixed, unconditional override in Rissbrook's own :root, matching amoleo-light.css's value, because this site is light-only regardless of the visitor's OS — every token that varies by theme needs its own pin here, not just the ones that happen to share a name with the neutral base.

Pets' midnight theme is only partly governed. Its light default (amoleo) matches the shared base exactly, under Pets' own names. Its dark theme (midnight) exists in shipped CSS but — per Pets' own comment — is "unreachable from theme swatches, kept for future dark-mode work." A theme nobody can select isn't shipped, so it isn't held to this yet (baseTheme.repos["Amoleo-Pets"].onlyThemes: ["light"]). Extend that the day midnight becomes selectable.

Pets' other twelve themes (ginger, tabby, and so on) are correctly not checked at all. They're accent themes, the same category as --accent itself — never meant to match the neutral base, any more than Website's navy is meant to match Pets' terracotta. The checker distinguishes these from the genuine default by looking for a [data-theme="…"] qualifier the parser doesn't recognise as light/dark/midnight, and skipping — not by name-matching each of the thirteen, which would need editing every time a theme is added or renamed.


3.5 The reciprocal rule: extra themes are free, except the wordmark

A product may add as many additional user-selectable themes as it wants — Pets' thirteen cat and dog palettes are the existing example — with no constraint beyond one: whichever theme is active, AMOLEO and · Product still have to render in the family's actual colours. Extra themes are exempt from matching the shared neutral base (§3); they are not exempt from that.

This is the mirror image of §3's exemption, not the same rule again: §3 says a custom theme's own background, text, panel colours can be anything; this says the wordmark specifically can't be, in any theme, custom or not.

check-brand.mjs's checkSuffixColour enforces the half of this that wasn't previously checked at all (checkWordmarkColour already covered the AMOLEO name, unconditionally, in every scope). It found a live bug the day it was written: Connections' own header wordmark had no --wordmark-accent override — only the footer's .family-mark .connections sets one — so it fell through to var(--accent), Connections' generic UI accent, which still carried a light/dark split (#0b6e8f / #3fb6dc) left over from before the 4 Aug single-colour policy retired --brand-connections' own split. Its own name rendered in the wrong blue in dark mode. Fixed by falling back to var(--brand-connections) directly, matching Pets' pattern, which never had this problem because it was already wired this way.

Rissbrook had the same latent shape, without the visible symptom. Its fallback was var(--accent) too — copied verbatim from Website's own pattern, where it's actually correct (Website has no sub-brand, so its own --accent is its wordmark ink). Rissbrook's --accent happens to resolve to the same value as --brand-rissbrook today (both trace back to his own --rb-mid-teal), so nothing looked wrong — but that was a coincidence of provenance, not a guarantee, and a future redesign of his site's nav colour could have silently broken the wordmark with nothing watching. Fixed the same way, to the same fallback pattern, before it ever became visible.

3.6 Typography joined the shared base 6 August 2026

Not one of the five values §1 lists, but the same idea: Website's own body typography — system-ui stack, 17px, line-height: 1.65 — is now the family's shared default, compiled into tools/family-css/components/primitives.css (docs/family-css-system.md §9). It was always just Website's own choice before; it's a real decision now, since Pets and Connections inherit it the moment they wire into that system, the same way they'll inherit bg/panel/text/muted/border. No checker enforces it yet, same gap family-css-system.md §8 records for radii and shadows.

4. Building a new product on this base

A new product (Accounts, Todo) that wants "an Amoleo Light and an Amoleo Dark theme" gets one for free by using the five shared values under whatever variable names fit its own codebase, then adding one --accent and telling check-brand.mjs its alias map. It does not need to invent its own neutral palette the way Rissbrook's heritage requirement forced — that's the exception, not the template. Register the repo in baseTheme.repos with its aliases the day its CSS ships; until then it's absent from the section entirely (not "exempt" — there's nothing to be exempt from yet).


5. What the checker actually checks

Reuses check-brand.mjs's existing declaration parser (checkTokens's own machinery: theme scoping via data-theme/prefers-color-scheme, var() resolution, short-hex normalisation) rather than a second parser. For each repo with a baseTheme.repos entry that isn't exempt:

A separate function, checkSuffixColour, covers §3.5: for a repo's own self-referencing wordmark instance (never one showing another product), it resolves .brand-suffix/.brand-dot's colour — following its fallback across every stylesheet in the repo, not just the one declaring it, since Rissbrook splits the token from the rule across two files — and requires it to land on that repo's own --brand-<product> in both light and dark. An unresolvable fallback (a token this checker can't trace to a literal colour anywhere in the repo) is reported as a note to check by eye, not a failure — a wrong guess here would be worse than an honest "can't tell."

Not checked: whether the values themselves are good design (this is a consistency checker, not a design reviewer, same limit every other checker in this family has); and --text-strong/--accent against the base theme, deliberately, per §1 — though --accent specifically feeding the wordmark's own colour is exactly what §3.5 exists to catch.

Generated from docs/family-theme.md + tools/tokens/theme-{light,dark}.tokens.json. Edit there and re-run npm run build in docs-site-src/ — never hand-edit a page.