AmoleoFamily

Process & Registry

vs. Theme

Theme is the decision — which five concepts are shared (bg/panel/text/muted/border), why, and what's exempt (Rissbrook, Pets' extra accent themes). Family CSS System is the pipeline that turns that decision, plus the wordmark/footer/ auth-box/header/cookie-consent component rules, into real, distributed, checked CSS files. Theme tells you what the neutral palette is; this page tells you how that becomes the actual bytes every repo loads.

tokens/*.tokens.json + family-css/components/*.css style-dictionary.config.mjs + build-family-css.mjs family-css/*.css (canonical) sync-family-css.mjs, or the npm package pilot every familyCss:true repo

This docs site borrows the same discipline, one step further: generate, never hand-edit, and — since 8 Sept 2026 — actually deployed, not just generated locally.

The family CSS system — base.css, amoleo-light.css, amoleo-dark.css

Three generated, checked-in files — family-css/base.css, family-css/ amoleo-light.css, family-css/amoleo-dark.css — that every adopted repo copies verbatim into its own family-css/ directory and never hand-edits. Everything genuinely per-product (a site's own --accent, its extra themes, Rissbrook's entire heritage palette) lives in that repo's own stylesheet, layered on top.

Why this exists. Before this system, the shared CSS in family-wordmark.md, family-footer.md and family-auth-box.md was prose with code fences — each repo hand-retyped the fences into its own stylesheet, with no canonical file to copy from. The two Amoleo-Website commits that prompted this doc (5 Aug 2026) are the failure mode in miniature: a 6-line .family-link-pair rule got added in the wrong place in styles.css, then moved, because there was nothing to copy-paste — only a paragraph to remember correctly. This doesn't replace the docs (they're still the why); it gives the what a single literal source instead of N hand-typed ones.

The repo still doesn't link CSS across sites at runtimeAmoleo-Family/CLAUDE.md's load-bearing rule (each site independently deployable, no first-paint dependency on another repo being up) is unchanged. These three files are copied, the same as everything else in this family; they just have one real, byte-exact master copy now instead of a paragraph everyone re-types.

node tools/build-family-css.mjs      # regenerate family-css/*.css from source
node tools/sync-family-css.mjs       # copy them into every familyCss:true repo
node tools/check-family-css-sync.mjs # verify every copy still matches

1. Where the content actually lives

Three source locations, matching the family's existing "JSON is the authority on values, markdown is the authority on why" split (CLAUDE.md):

Change a value: edit the matching tools/tokens/*.tokens.json file. Change a shared rule: edit the matching file under tools/family-css/components/. Either way, run node tools/build-family-css.mjs and commit the regenerated output in the same change — same discipline check-brand.mjs's JSON already requires, extended to the CSS it produces.

2. Dark is the unconditioned default — a wrinkle worth knowing about

amoleo-light.css and amoleo-dark.css are not simply ":root" and "[data-theme=\"dark\"]". Website and Connections both default to dark — "a browser that reports no preference gets the theme the apps default to, rather than a flash of the other one" (Website's own comment, predating this system) — by putting the dark values on the unconditioned :root and treating light as the thing that needs an explicit signal to activate. amoleo-dark.css reproduces that exactly: :root, [data-theme="dark"]. amoleo-light.css only ever applies via @media (prefers-color-scheme: light) or an explicit [data-theme="light"], never on bare :root. Get this backwards and every visitor with no stored preference sees light instead of the family's actual default — a real, silent regression the first version of this generator shipped internally before being caught in verification.

3. Every adopted repo loads all three files — including Rissbrook

The original draft of this system treated Rissbrook as skipping amoleo-light.css/amoleo-dark.css outright, on the reasoning that family-theme.md §3 documents it as exempt from the shared neutral base. That's the wrong shape. Rissbrook isn't exempt from loading the base — it's the repo whose own rissbrook.css overrides the base the most, to stay Robert Bickley's own palette, not a repo that opts out of the system. Every adopted repo, Rissbrook included, links all three shared files and then layers its own product stylesheet on top:

<link rel="stylesheet" href="/family-css/amoleo-dark.css">
<link rel="stylesheet" href="/family-css/amoleo-light.css">
<link rel="stylesheet" href="/family-css/base.css">
<link rel="stylesheet" href="/rissbrook.css">  <!-- or pets.css, connections.css, styles.css -->

Dark before light — not alphabetical, and not optional. Found live 6 Aug 2026 while wiring in Connections, whose theme genuinely switches at runtime (data-theme set by boot.js/theme.js, not left to prefers-color-scheme alone the way Website's does): amoleo-dark.css's :root, [data-theme="dark"] rule always matches the root element, even on a page with data-theme="light" set, since :root carries no condition of its own. That ties in specificity with amoleo-light.css's [data-theme="light"] rule, and a tie goes to whichever file loads later — so light has to load second, or an explicit light theme silently renders dark's colours instead. Verified by loading the two generated files directly and reading getComputedStyle(--bg) under data-theme="light" before and after swapping the order. Website carried the wrong order harmlessly (it never sets data-theme, so the bug was never reachable) until this fix corrected it too, 6 Aug 2026.

What differs by repo is only how much that last file overrides:

4. The rename Pets needs before it can wire in

family-css/amoleo-light.css / amoleo-dark.css declare --bg, --panel, --text, --muted, --border literally. For them to take effect, a consuming repo has to actually use those names — family-theme.md §2's existing alias table is exactly the gap:

Concept Website today Connections before 6 Aug 2026 Pets today Canonical (this system)
page background --bg --bg --page-bg --bg
surface background --panel --panel --bg --panel
muted text --muted --muted --text-light --muted
border --border --panel-2 (doing double duty — see below) --border --border

Decided this session: rename, don't alias. Connections did this 6 Aug 2026: --panel-2 had been carrying two unrelated concepts under one name — genuine borders, and a real background/fill colour on chips, hover states and buttons. Reading every one of its ~50 call sites in styles.css split them: pure-border uses became --border, pure-background uses stayed --panel-2 (now legitimately its own second-surface token, the same relationship Website's --panel-2 already has to --border), and the rare rule using --panel-2 for both border and fill on the same element (flat chips, matching edge and fill on purpose) kept --panel-2 on both properties rather than being decoupled — the two happen to share a value today, but that's not a reason to force a relationship that isn't there. Pets still owes its own version of this: renames --page-bg--bg, --bg--panel (careful — the same name means a different concept before and after), --text-light--muted, throughout its own CSS (and any JS that reads these custom properties). Once renamed, it wires in exactly like Website and Connections — no ongoing alias layer to maintain.

5. Per-repo status

Repo familyCss Notes
Amoleo-Website true — wired 5 Aug 2026, extended 6 Aug 2026 Reference implementation for every component shipped so far, including the header (family-header.md §1.1) and the generic primitives (§9). Zero rename needed (names already matched). What's left in its own styles.css: --accent/--on-accent/--text-strong/--panel-2/--wrap/--step (per-site tokens), .brand-name and the compound-link hover rules (no equivalent construct exists in Pets/Connections to make them shared), and this one page's actual content.
Amoleo-Connections true — wired 6 Aug 2026 The reference implementation for the header's drop-down panel (family-header.md §6) and the whole auth box (family-auth-box.md) adopting its own reflection — both shared files were extracted from this app's CSS, so the visual result is unchanged. Did the §4 --panel-2/--border split. familyCssDest: "client/public/family-css" (a Vite app; static assets serve from client/public/, not the repo root — <link> tags added to client/index.html). Kept locally: node/edge/overlay colours (never brand colours), --accent/--on-accent/--text-strong/--panel-2 (per-site tokens), the .compact footer variant, .landing-* page content, and two real per-context overrides the shared files don't need to know about (.landing-auth .auth-card's padding delta; the reduced-motion .spinner exception, which needs !important to out-rank primitives.css's own blanket !important rule at equal specificity). One deliberate, unresolved conflict: the shared footer's 560px stacking breakpoint replaced Connections' own 1024px (reasoned as "a tablet is a thumb too") — the family value won as the default for a newly-wired repo, not silently kept; a family-wide breakpoint change is a separate discussion if tablet coverage turns out to matter in practice.
Amoleo-Pets false — follow-up Needs the §4 rename first.
Amoleo-Rissbrook true — wired 6 Aug 2026 Renamed its own neutral tokens (--page-bg--bg, --surface--panel, --text-muted--muted) despite being exempt on values (family-theme.md §3) — "rename, don't alias" applied here too, the same choice Pets still owes. familyCssDest: "public/family-css" (an Astro site; static assets serve from public/). Also adopted the shared Pre-Auth Header component (family-header.md §1.1) the same session, replacing its own bespoke .site-header/.site-brand — not part of this system directly, but found and fixed alongside it, since the two migrations touched the same files. One real gap caught wiring this in: --brand-amoleo (unlike the four other --brand-* tokens) is genuinely theme-split, and deleting it here the same way the theme-independent four were deleted left a dark-OS visitor seeing white-on-white "AMOLEO" text — pinned back as Rissbrook's own fixed override, family-theme.md §3.5. Kept locally: --surface-alt, .family-mark a:hover{background:none} (counters this site's own global a:hover rule), and a 44px (not the shared 40px) mobile footer touch-target — deliberately ahead of the family default, not behind it.
Amoleo-Accounts true — wired via #19 Vite app, static assets serve from client/public/ (familyCssDest: "client/public/family-css"), same reason as Connections'. This row said "no code yet" for some time after that landed — corrected 3 Sept 2026; tools/repos.json had the current state right the whole time, only this table had drifted.
Amoleo-Todo false — no code yet Wire in as part of shipping, like every other family system.

tools/repos.json's familyCss flag is what sync-family-css.mjs and check-family-css-sync.mjs read — flip it only once a repo actually links the three files and has its duplicated rules removed, same idiom as adopted for the footer lockup.

6. What a component file is allowed to assume

Written while extracting wordmark.css: the "canonical" rule is whatever Website (and where they agree, Pets) actually ship, not the most literal reading of a doc's illustrative code sample. Two corrections made during this extraction, worth remembering next time a component file is touched:

8. Design tokens have a source, not a checker yet

tools/tokens/base.tokens.json's radius group and the two theme files' shadow/shadow-lg (radii, shadows) exist purely because primitives.css and header.css needed real values and the family's own rule is "the JSON is the authority on values" — they aren't yet read by check-brand.mjs the way the --brand-* accents and base-theme neutrals are. A repo's radius/shadow values drifting from these wouldn't currently be caught by anything. Website and Connections already agree on every value here; Pets diverges on radius.md/radius.lg (16px/24px, not 14px/20px) and has no comparable shadow system at all — its own --shadow is a bare colour component re-tinted per accent theme (thirteen of them), not a fixed box-shadow. Recorded here as a known gap and a known divergence, not silently reconciled or silently enforced.

9. primitives.css has no canonical doc of its own

Every other component traces back to a doc that explains why — the wordmark to family-wordmark.md, the footer to family-footer.md, and so on. The base reset, typography, buttons, focus ring, .wrap, .skip-link, .visually-hidden and the reduced-motion query don't have one, because they were never really Website's own design decision to begin with — a CSS reset and :focus-visible are close to universal defaults, not a choice specific to this family the way the wordmark's 0.79em ratio is. Website's specific values within that (17px body text, the system-ui stack) are still a real decision, now the family's shared one — worth a line in docs/family-theme.md for exactly that reason, even without a dedicated primitives doc of its own.

10. What to check afterwards

Generated from docs/family-css-system.md. Edit there and re-run npm run build in docs-site-src/ — never hand-edit a page.