Layout Components
Live — enforced by check-lockup.mjs
Simulated on a page that isn't any one product's own site (all four render as cross-links). Order is fixed — oldest first, Accounts last, no status label. Separators are aria-hidden.
Non-Live — registered, no footer requires this yet
Not a real footer anyone renders — a preview of the order these would join in, once promoted (node tools/promote-to-lockup.mjs). See Product Order.
Live — mobile (≤560px)
A genuinely different layout below 560px, not the row above just narrower — .family-mark-wrap switches to display:block and each name becomes its own full-width, thumb-sized row.
Rows are 44px, the WCAG 2.2 AA target size — not the 40px the Website currently ships (the spec's own note: the Website is the one that should change, not the target). “Amoleo” gets its own row first, same order otherwise.
Compact — for space-constrained pages
Connections' SiteFooter compact prop is the reference — a page where something else (a map, a canvas, an editor) genuinely needs the vertical space back, not a default and not a reaction to the full footer merely looking big.
TMDB attribution notice (required, never dropped) · © Amoleo. All rights reserved.
Allowed to drop: supplementary prose only — a data-sources sentence, extra attribution.
Never drops: the lockup in full (all products, correct order, aria-hidden separators), any required third-party notice, or the copyright line.
The lockup is reordered first via order: -1, not moved in the document — single line above ~1370px, folding to lockup-then-(notice+copyright) from there down to the 1025px floor shown above.
Lockup adopted
Whether the repo's own live footer actually ships the current-shape lockup — repos.json's adopted field, separate from being listed in lockup.json at all.
| Repo | Adopted |
|---|---|
| Amoleo-Website | Yes |
| Amoleo-Rissbrook | Yes |
| Amoleo-Pets | Yes |
| Amoleo-Connections | Yes |
| Amoleo-Accounts | Yes |
| Amoleo-Todo | — |
| Amoleo-Editions | — |
| Amoleo-Upnext | — |
| Amoleo-Collections | — |
| Amoleo-Communications | — |
| Amoleo-Reviews | — |
| Amoleo-Images | — |
| Amoleo-Registry | — |
| Amoleo-Recommendations | — |
| Amoleo-Worldbook | — |
| Amoleo-Health | — |
Family footer — the shared lockup
The footer lockup carried by every Amoleo site. It lives here, in the umbrella workspace, because it belongs to no single app — it began as a note inside Amoleo-Pets, where it was gitignored and so untracked, which is the one place a cross-repo spec should not be.
Amoleo-Website is the reference implementation. Where this document and that
code disagree, the code is right and this should be corrected.
The wordmark's own mechanics — size, weight, alignment, colour rule, link
behaviour — live in docs/family-wordmark.md, not
here. This doc covers what's specific to the footer: the lockup's
structure, order, per-product colour values, and the compact variant. Where
the two used to say the same thing twice, the wordmark doc is now the one
copy.
The literal CSS in §3 and §6 now lives in
tools/family-css/components/footer-lockup.css,
compiled into family-css/base.css and copied verbatim into every wired
repo — see docs/family-css-system.md. This doc is
still the record of why; that file is the one copy of the literal rules.
Two checkers enforce this document across all five repos, from machine-readable
copies of what it says: tools/tokens/base.tokens.json
for the colours in §2, and tools/lockup.json for the
order, hosts and structure in §1 and §5. Change a value in the JSON and in the
prose in the same commit. Run both after touching brand CSS or a footer
anywhere:
node tools/check-brand.mjs
node tools/check-lockup.mjs
check-lockup --strict also fails on the repos still shipping the two-name
version, which §0 lists.
The goal: one lockup, five names —
Amoleo · Pets · Connections · Rissbrook · Accounts
One "Amoleo" shared rather than repeated, each product name in its own colour, each linking to its own site. The site you're currently in is not a link, because there's nowhere for it to go. On a phone the rows stack full-width so each is a thumb-sized tap target.
The family name is the one part that never varies. "Amoleo" is the same neutral ink on every site — see §2 — and only the product name after it carries colour.
The point of the exercise: a bare Amoleo · Pets link next to a copyright line
read as though the copyright belonged to Pets. Separating the family lockup from
the copyright fixes that, and gives every app a visible relationship to the
others.
0. Status — who ships what
| Repo | Site | Ships the lockup? |
|---|---|---|
Amoleo-Website |
www.amoleo.com |
Five names. |
Amoleo-Pets |
pets.amoleo.com |
Five names. 4 Aug 2026 |
Amoleo-Connections |
connections.amoleo.com |
Five names. 4 Aug 2026 |
Amoleo-Rissbrook |
www.rissbrook.co.uk |
Five names, plus the header wordmark. 1 Aug 2026 |
Amoleo-Accounts |
accounts.amoleo.com |
Five names. 27 Aug 2026 |
All five apps now ship the five-name lockup (see Amoleo-Family/CLAUDE.md
— commits stayed per-repo, one per app).
Two things Rissbrook established that the others should follow rather than re-decide:
- The header wordmark is part of this lockup's family, and shares its CSS.
Rissbrook keeps both in one
brand.cssrather than scoping the rules to each component, so the two cannot drift. - The stacked rows are 44px, not 40px. Rissbrook targets WCAG 2.2 AA, whose minimum target size is 44px. §6 below still says 40px because that is what the Website ships; the Website is the one that should change.
Pets established a third, 5 August 2026:
- Each row's dot+suffix pair sits inside its own
.family-link-pairwrapper (§5), nudged 1em left of centre on the mobile stacked rows (§6) — an eyeballed visual correction, not a token, for the leading separator dot making the centred string read as off-centre. The Website adopted it 5 August 2026, matching Pets exactly, including the CSS rule's placement inside the@media (max-width: 560px)block rather than alongside the other.family-markrules — an interim commit put it there first and a follow-up relocated it once the mobile-only scope was double-checked against §6 below. Not yet in Rissbrook or Connections markup.
1. The apps, in order
Order is fixed across every site — oldest first, with Accounts last as the infrastructure the others will sit on.
| key | Name | href | Accent |
|---|---|---|---|
pets |
Pets | https://pets.amoleo.com/ |
terracotta #D9603F |
connections |
Connections | https://connections.amoleo.com/ |
cyan-blue |
rissbrook |
Rissbrook | https://www.rissbrook.co.uk/ |
teal #2F7994 |
accounts |
Accounts | https://accounts.amoleo.com/ |
slate grey #6E7178 |
Pets' accent is #D9603F as of 6 August 2026, superseding #c45032. The
new value matches what pets.amoleo.com itself actually ships — #c45032
was the family's own approximation of Pets' terracotta, not a colour lifted
from the live site, and the two had drifted apart. Amoleo-Pets's own
App.css already carries #D9603F; the family's token source (then
brand-tokens.json, now tools/tokens/base.tokens.json — see
docs/family-css-system.md) was updated in the same move. Rissbrook and
Connections still carry the old value and will report as diverging until
they're updated — same
situation as the Rissbrook teal swap below, and the same fix: update the
token, not override it locally.
One side effect worth knowing about, not yet acted on: the Website's
--brand-pets-ink (family-wordmark.md §7) — a deliberately different,
AA-nudged terracotta used only for its product-heading text — has a dark
value of #d9603f, which is now identical to the new --brand-pets itself.
The exception isn't wrong, but half of what it was compensating for may now
be moot; worth a look next time that heading is touched, not urgent on its
own.
Accounts carried no fixed colour until 4 August 2026 — it took each site's own
muted-text colour (--muted / --text-light) back when it was pure unshipped
infrastructure rather than a product with its own identity. It now has a real,
boring, fixed grey like every other product's accent, for the same reason as
the rest of this section: a colour that changes per site is not a family
colour.
Todo has a colour (#8A6D1A, olive gold) but is not in the lockup yet. It
is recorded in tools/tokens/base.tokens.json as --brand-todo so it exists when
Todo ships, but adding a fifth product to the lockup is its own decision —
order, and whether it follows Accounts' "linked before it resolves" precedent —
not a side effect of picking its colour.
Accounts is linked even though the host does not resolve yet, and carries no "in development" label. The family reads as four products rather than three and a placeholder, and the link needs no revisiting when the host comes up. The trade is a dead link until then — accepted deliberately, not overlooked.
A product is never named bare. In the footer it is the full Amoleo · Pets.
In body copy the short form is allowed — "Visit · Pets" — but the separator is
not optional, because it is what ties the name to the family. "Visit Pets" is
the thing this rule exists to prevent.
Inline mentions borrow the wordmark's uppercase, 0.10em letterspacing and
weight, but not its sizing: they sit at the size of the words either side of
them, not at 0.6em. They must also be ordinary inline text — the wordmark
proper is display: flex, and nesting it inside an inline-flex link in a narrow
column breaks the verb, the name and the arrow onto three separate lines.
The separator takes the app's accent; the name itself inherits the link colour, so the link still reads as a link and so the name is not held to whatever contrast the accent happens to have at body size.
Rissbrook's accent is #2F7994 as of 5 August 2026, superseding #183D4A
(itself settled 1 August 2026, superseding the provisional #2f6b4f). All
three are from Robert Bickley's own our-family-blue.css — this is still his
palette, not a hue assigned to fill a gap — but #183D4A (his "dk teal", the
background of every page he wrote and the colour of every heading, recorded as
Amoleo-Rissbrook's ADR-008) turned out to measure 1.63:1 against the family's
dark background: it fails WCAG AA at every text size, not a size-dependent
problem the footer's larger type could fix. See "History" in §2 below for how
that got missed the first time.
#2F7994 is "Side Bar Header" from the same stylesheet — not invented,
still something he actually used, just a different rule in the same file — and
it's the one colour in his blue palette that clears the WCAG large-text bar
(3:1) on both the family's light and dark backgrounds at once, which is what
lets it stay one fixed value rather than needing a per-theme override the way
#183D4A would have. Two other candidates from the same palette were checked
and rejected: "Header Bar" #CBDDE4 clears dark beautifully but fails light at
1.31:1 (Rissbrook's own site is light-only, so that's not a secondary context —
it's the primary one); "Side Bar" rgb(99,155,175) clears dark but just misses
light at 2.87:1.
It sits at hue ~197, close to Connections' ~195 — the two are separated by lightness and saturation rather than hue, which is a weaker distinction than the other pairs have. Accepted: provenance beats palette spacing here, and no site shows the two names in isolation from the rest of the lockup.
Amoleo-Website carried #183D4A from 1 August 2026 (along with its OG card)
until the 5 August fix. Pets and Connections never carried a Rissbrook colour
before going to five names; both picked up #183D4A as part of that move (4
August 2026) and need the same swap to #2F7994 in their own repos —
check-brand.mjs will report them as diverging from the family's token
source until they do.
2. The colours
Every product's accent is one fixed value, not a light/dark pair — decided 4
August 2026. --brand-amoleo is the one exception, because it is genuinely
two different inks (near-black on light, white on dark), not a product accent
that happens to need adjusting per theme.
:root, [data-theme="dark"] {
--brand-amoleo: #1c1c2e; /* dark; #ffffff in [data-theme="dark"] — the one exception, see above */
--brand-pets: #D9603F; /* was #c45032 until 6 Aug 2026 — see §1 */
--brand-connections: #0b6e8f;
--brand-rissbrook: #2f7994; /* his "Side Bar Header" — see §1 */
--brand-accounts: #6e7178; /* "slate grey" — see §1 */
--brand-todo: #8a6d1a; /* "olive gold" — not shipped yet, see §1 */
}
Until 4 August 2026, Connections and Rissbrook each had a second, lighter value
for [data-theme="dark"] (#3fb6dc, #98bdca), because their light values
don't carry enough contrast on a dark ground on their own. Rather than keep
inventing a second hex per colour per theme, the footer's type got bigger and
bolder instead (§6) — large enough to clear WCAG's large-text contrast bar
(3:1) instead of the normal-text one (4.5:1). That policy holds for Pets,
Connections and Accounts, each of which clears 3:1 against both a white and a
near-black background at this size. It did not, in fact, hold for the
#183D4A Rissbrook shipped with on 4 August — see "History" below — which is
why Rissbrook's value changed again on 5 August rather than the policy being
reopened. The theme accommodates the colour; the colour doesn't change per
theme, and now genuinely doesn't for all four. This is the approach Pets'
--brand-pets used from the start — a single terracotta, unapologetically the
one colour that fails the old 4.5:1 bar in dark mode at the old, smaller
size — generalised to the rest of the family rather than kept as its one
exception.
"Amoleo" takes --brand-amoleo, not the site's strong text colour — the
wordmark doc's colour rule (§4), stated here because of what happened the one
time a repo didn't follow it. This is the one rule the lockup cannot bend, because the family name is the part
that has to look identical on five sites for the lockup to read as one mark. It
matters most where an app's own brand colour is also its text colour: Rissbrook
carried --text-strong: #183D4A and so rendered AMOLEO and RISSBROOK in the
same teal, losing the two-tone step and tinting the shared half of the mark with
a colour that belongs to one site. Fixed 1 August 2026 (Luke).
The values are the Website's existing ink, so nothing moves there. Pets and
Connections both now define the token rather than leaving the wordmark on
--text-strong; in Pets in particular that would have tied the family name to
whichever of the nine cat themes is active.
History: Rissbrook needed a second Dark value, then needed a new value
entirely. #183D4A is 11.9:1 on white and 1.63:1 on #101013 — unreadable
on a dark ground, and that number does not change with font size: contrast
ratio is fixed by the two colours involved, only the required bar changes
with size (4.5:1 normal text, 3:1 large text). Until 4 August 2026
[data-theme="dark"] carried #98bdca instead, his palest teal, 9.5:1 on
#101013, specifically to work around this.
The 4 August single-colour policy got this one wrong. It retired #98bdca
on the reasoning that the footer's larger, bolder type (§6) was "what makes
#183D4A itself work on dark — 5.9:1 at the new size, comfortably past the
3:1 large-text bar." That 5.9:1 figure was never real — recomputing it
directly gives 1.63:1, the same number as above, because a type-size increase
cannot change a fixed pair of colours' contrast ratio, only which bar applies
to it. #183D4A fails even the loosened 3:1 large-text bar, so the footer was
shipping unreadable Rissbrook text on every dark-themed page in the family
between 4 and 5 August. Fixed by replacing the value itself, #2F7994, rather
than by retrying the type-size argument — see above. Rissbrook's own site is
light-only (Luke, 1 Aug 2026), so none of this ever affected it directly; it
existed for the Website, Pets and Connections.
History: Pets' terracotta in Dark. #c45032 on #101013 was 4.11:1. At
the lockup's old 7.8px (13px × 0.6em) that failed WCAG AA for normal text — the
one colour in the lockup that did, flagged 31 July 2026. The fix that shipped 4
August 2026 wasn't a lightened Dark value; it was the same type-size increase
that resolved Connections and Rissbrook — 4.11:1 clears the 3:1 large-text bar
the new size qualifies for, so the colour didn't need to change at all then.
#D9603F (6 August 2026) shifts the numbers rather than removing the
question. It's 3.69:1 on white and 5.15:1 on #101013 — lighter and more
saturated than #c45032, so the trade flips: worse on a light ground, better
on a dark one. Both figures still clear the 3:1 large-text bar the footer's
19px bold qualifies for; neither clears 4.5:1 normal-text on its own (the
white figure isn't close). Same policy as the rest of §2 — the type size is
what makes this colour work at lockup scale, not the hex — but it's worth
re-checking anywhere Pets' terracotta renders at body text size against a
light background, since that combination has never cleared AA at either
value.
One note per repo:
- In Pets, the dark theme selector is
[data-theme="midnight"], not[data-theme="dark"]— everything else, including its own--brand-pets, is the same as everywhere else. It briefly trackedvar(--secondary)instead, so the wordmark (header and footer both) changed colour on every one of the nine cat themes; corrected 4 Aug 2026. Every app's product colour is fixed in all themes — Pets is not a special case. - Elsewhere (Website, Connections) there are only the two themes, and
--mutedexists in both.
3. Two existing rules have to accept a per-app colour
.brand-wordmark .brand-dot and .brand-wordmark .brand-suffix currently
hard-code a single accent. They need a variable a wrapper can override, falling
back to exactly what they do now — so nothing else changes:
.brand-wordmark .brand-dot { color: var(--wordmark-accent, var(--accent)); }
.brand-wordmark .brand-suffix { color: var(--wordmark-accent, var(--accent)); }
In Pets the fallback is var(--secondary) rather than var(--accent). Leave
every other property in those rules alone.
The rule they hang off takes the family colour rather than the site's:
.brand-wordmark { color: var(--brand-amoleo); }
4. The component (React — Pets, Connections)
client/src/components/FamilyMark.jsx. Identical in every app apart from the
current default.
/**
* The family as one lockup: Amoleo · Pets · Connections · Rissbrook · Accounts.
*
* One "Amoleo" shared rather than repeated, each product name in its own colour
* — which is what makes it read as a family rather than a list of links. The
* separator takes its name's colour too, so each dot-and-word is one unit, and
* it sits inside the link so that when these stack on a phone the whole row is
* the tap target rather than a four-character word.
*
* The site you're already in isn't a link. There's nowhere for it to go. Every
* other name is, including Accounts, whose host does not resolve yet.
*/
const APPS = [
{ key: 'pets', name: 'Pets', href: 'https://pets.amoleo.com/' },
{ key: 'connections', name: 'Connections', href: 'https://connections.amoleo.com/' },
{ key: 'rissbrook', name: 'Rissbrook', href: 'https://www.rissbrook.co.uk/' },
{ key: 'accounts', name: 'Accounts', href: 'https://accounts.amoleo.com/' },
];
export default function FamilyMark({ current = 'pets' }) {
return (
<div className="brand-wordmark family-mark">
<a className="family-name" href="https://www.amoleo.com" target="_blank" rel="noreferrer">
Amoleo
</a>
{APPS.map((app) => {
const inner = (
// Grouped so the dot and suffix shift together as one unit on the
// mobile stacked rows (§6's max-width: 560px block) — the row
// itself stays centred, only this pair nudges left within it.
<span className="family-link-pair">
<span className="brand-dot" aria-hidden="true">·</span>
<span className="brand-suffix">{app.name}</span>
</span>
);
const linked = app.href && app.key !== current;
return (
<span key={app.key} className={`family-app ${app.key}`}>
{linked
? (
<a className="family-link" href={app.href} target="_blank" rel="noreferrer">
{inner}
</a>
)
: <span className="family-link">{inner}</span>}
</span>
);
})}
</div>
);
}
On www.amoleo.com there is no current app — pass current={null} so all
four products link out, and drop the href from .family-name instead, since
that is the page you are already on.
5. The markup (static — Website, Rissbrook)
Same classes, same structure, no framework. aria-hidden on the separators
matters: without it a screen reader reads "Amoleo middot Pets middot
Connections" as one run-on string.
<div class="family-mark-wrap">
<div class="brand-wordmark family-mark">
<span class="family-name">Amoleo</span>
<span class="family-app pets">
<a class="family-link" href="https://pets.amoleo.com/">
<span class="family-link-pair">
<span class="brand-dot" aria-hidden="true">·</span>
<span class="brand-suffix">Pets</span>
</span>
</a>
</span>
<span class="family-app connections">
<a class="family-link" href="https://connections.amoleo.com/">
<span class="family-link-pair">
<span class="brand-dot" aria-hidden="true">·</span>
<span class="brand-suffix">Connections</span>
</span>
</a>
</span>
<span class="family-app rissbrook">
<a class="family-link" href="https://www.rissbrook.co.uk/">
<span class="family-link-pair">
<span class="brand-dot" aria-hidden="true">·</span>
<span class="brand-suffix">Rissbrook</span>
</span>
</a>
</span>
<span class="family-app accounts">
<a class="family-link" href="https://accounts.amoleo.com/">
<span class="family-link-pair">
<span class="brand-dot" aria-hidden="true">·</span>
<span class="brand-suffix">Accounts</span>
</span>
</a>
</span>
</div>
</div>
The .family-link-pair wrapper exists for one reason: the mobile nudge in
§6. It has no purpose above the 560px breakpoint — the dot and suffix
could sit directly inside .family-link there, same as before — but
adding the wrapper only below the breakpoint would mean two different
markup shapes for the same component depending on viewport, which no CSS
rule can express on its own (the DOM is the DOM at every width). One
wrapper, present always, moved only in the one context that needs it.
Originated in Amoleo-Pets; the Website carries it too now (5 August
2026). Not yet in Rissbrook or Connections markup — see §6.
6. The CSS
24px/19px bold is the wordmark's base size (family-wordmark.md §2) —
the footer sits at the floor, not above it. Sized up from 13px/7.8px on 4
August 2026, because at that size several of the family's own colours failed
WCAG AA on a dark ground (§2 above). See family-wordmark.md §2–3 for why
the size lives there as a reference rather than restated per context, and
§5 for the alignment fix (no manual top offset — flexbox's own centering
is enough).
Both lines below are explicit, not left to inherit the base rule's em
values — this is what Amoleo-Website (the reference implementation, along
with Rissbrook and Connections) actually ships, and it's load-bearing, not
tidiness:
.family-mark .brand-suffixcarriestop: 0;even though the base.brand-wordmark .brand-suffixrule sets notopat all. Writing it is the record that the oldtop: 0.15emhang-below offset (family-wordmark.md§5) was deliberately zeroed here, not merely never added — a future edit that reintroduces an offset at the base has an explicit0at the footer to fight rather than silence to fill..family-mark .brand-dotgets the identical19px, not the base rule's0.79em.19pxand24px × 0.79em(18.96px) round differently — the exact sub-pixel misalignmentfamily-wordmark.md§5 describes — so the footer fixes the dot's size explicitly rather than trusting two independentemcomputations to agree.
.family-mark { font-size: 24px; flex-wrap: nowrap; justify-content: center; white-space: nowrap; }
.family-app, .family-link { display: inline-flex; align-items: center; }
.family-mark .brand-suffix { font-size: 19px; font-weight: 700; top: 0; }
.family-mark .brand-dot { font-size: 19px; }
.family-mark .pets { --wordmark-accent: var(--brand-pets); }
.family-mark .connections { --wordmark-accent: var(--brand-connections); }
.family-mark .rissbrook { --wordmark-accent: var(--brand-rissbrook); }
.family-mark .accounts { --wordmark-accent: var(--brand-accounts); }
/* Self-link vs. cross-link — family-wordmark.md §6. The product names are
cross-links: the underline goes directly on .brand-suffix, never on the
<a> — putting it on the <a> and excluding the dot looked right but
wasn't (the dot has no size override so an ancestor-sized line sits at
the wrong height, and some browsers leak a stray fragment under the
dot's margin even with it excluded). Decorating the suffix directly also
makes the underline's colour follow the product's own accent for free,
instead of the page's generic link colour. */
.family-mark a { text-decoration: none; }
.family-mark a:hover .brand-suffix { text-decoration: underline; }
/* The family name is a self-link too (it links to amoleo.com), but not a
zero-decoration one the way the header logo is — changed 5 Aug 2026. It
sits in a row with four underline-on-hover cross-links; a link that
behaves differently from its immediate neighbours for no visible reason
is the same WCAG 2.1 SC 1.4.1 (Use of Color) territory the cross-link
rule above is already addressing, so the family name gets the identical
hover treatment. Set directly on the <a> (not a child span, the way
.brand-suffix is) because family-name wraps no separate element to
decorate instead, and there's no dot or sibling here for a line to leak
onto. color: inherit already pulls --brand-amoleo from .family-mark, so
the underline defaults to that same colour with nothing extra to write.
At rest it's undecorated, same as the rest of the lockup — only the
hover state differs from the header logo. See family-wordmark.md §6. */
.family-mark a.family-name:hover { text-decoration: underline; }
/* On a phone they stack into rows you can hit with a thumb. */
@media (max-width: 560px) {
/* Without this the rows are about 100px wide: the wrapper centres its
content, so it is only as wide as the words, and width:100% of that is
100% of nothing much. */
.family-mark-wrap { display: block; }
.family-mark {
width: 100%;
flex-direction: column;
align-items: stretch;
white-space: normal;
gap: 2px;
}
.family-mark .family-name,
.family-mark .family-link {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 40px;
padding: 2px 8px;
border-radius: var(--radius-sm);
}
.family-mark .family-name:hover,
.family-mark a.family-link:hover { background: var(--border); text-decoration: none; }
/* Nudges the dot+suffix pair 1em left within its own centred row — the
row itself (and the "Amoleo" row above it) stays put; only this pair
moves. Approved as a visual mockup, not derived from a design token —
centring the literal string "· Pets" isn't the same as centring the
word a reader's eye lands on, because the leading dot and its margin
add width the reader doesn't register as part of the name. Shifting
the pair left compensates. Originated in Amoleo-Pets, 5 Aug 2026. */
.family-mark .family-link-pair {
margin-left: -1em;
}
}
In Pets, --radius-sm and --border both exist; in Connections --border is
--panel-2. Check the token exists before pasting.
The compact variant — for space-constrained pages
Some pages can't afford the full footer's height — a page built around a map
or a canvas is a fixed screen height with the content taking whatever the
footer leaves it, so every row the footer adds is a row taken from the thing
the page is actually for. Connections' SiteFooter component is the existing
example: a compact prop that drops the optional elaboration and nothing
else.
What compact is allowed to drop: supplementary prose — a sentence explaining where data comes from, a longer attribution, anything that exists to inform rather than to satisfy an obligation. Connections drops "Built on data from TMDB, Wikidata, Wikipedia and Fandom wikis" in compact mode; the required TMDB notice itself (with the logo) stays in every mode, because that one isn't optional — a third party's terms, not the family's choice.
What compact must never drop: the lockup itself, in full — all products,
correct order, family-mark-wrap, aria-hidden separators — and the
copyright line. Compact shortens the footer; it does not exempt a page from
carrying the lockup. A page that needs more room than compact still gives
back is a page whose layout problem is not the footer's to solve.
The lockup wraps against the small print as a unit, not against itself.
Reordering the lockup in front with order: -1 — not moving it in the
document, order has no effect on the full non-compact footer's block
layout — leaves the TMDB notice and copyright adjacent to each other in the
resulting visual sequence (lockup, notice, copyright), so flex-wrap only ever
breaks between the lockup and the small print, never mid-print. Above
roughly 1370px window width (the widest that fits all three side by side,
given 24px/19px suffixes and the required TMDB text) it's a single line;
from there down to the 1025px floor it folds to two — lockup on top, notice
and copyright together underneath.
A first attempt at this fix — 4 August 2026, same day as the type-size
increase that made it necessary — forced the lockup onto its own line
unconditionally with flex-basis: 100%. That fixed the internal-wrap
problem it was written for but cost the compact footer its single-line case
entirely: three lines on every screen, wide or not, which is the opposite of
what compact exists for.
.site-footer.compact { display: flex; flex-wrap: wrap; /* ...gap, alignment... */ }
.site-footer.compact .footer-family { order: -1; }
When to reach for it: a page where another element genuinely needs the
vertical space back — a map, a canvas, an editor. Not a default, and not a
reaction to the footer merely looking big; the full footer is correct
everywhere else, including a product's own marketing/landing page (Amoleo ·
Connections' Landing.jsx uses the full footer deliberately — see
docs/family-header.md, "Two states, not three" — because that page is
making a case and can afford the fuller footer, the opposite situation from
the pages compact exists for).
6.1 Below the fold-to-one-line width — a second, later pass
Everything above describes compact at ≥1025px. Compact's own narrow-width
behaviour was added later (6 Aug 2026, in the same pass that trimmed the
compact footer's height generally) — before that, compact had no special
treatment below 1025px at all and just inherited the shared file's own
≤560px stacking rule, the one thing compact exists to avoid. Connections'
SiteFooter/styles.css is still the only implementation, but this is
now promoted into the shared footer-lockup.css itself rather than
waiting for a second one — the rules below target .site-footer.compact's
top-level rows generically (> *, > p, .family-mark-wrap) rather than
Connections' own row class names (.tmdb-notice, .footer-family), so
they don't assume any repo-specific content and reach a future repo's own
compact footer without it needing to reinvent this pass.
The lockup becomes a horizontally swipeable strip, not a taller
stack. overflow-x: auto plus scroll-snap-type: x proximity on
.family-mark itself, with a mask-image fading both edges so the next
name visibly peeks in — that peek is what actually signals "more here,
swipe" rather than a static arrow doing the same job. Tried first: a
collapse-behind-a-caret control (tap "Amoleo" to reveal the rest).
Reverted the same session — a toggle read as one more thing to puzzle
out, where a swipeable row invites the gesture on its own. Neither the
font-size nor the tap-target height of the product names changes in
either state; only the layout around them does.
Real height cuts, not just the layout change — measured 147.4px →
~120px on a 375px phone, none of it from shrinking the family links: the
footer's own 20px top/bottom padding (never had a compact-specific
reduction below 1025px) down to 8px below it; the 10px/10px/6px gaps
between the three rows (also only ever zeroed at ≥1025px) down to 0 via
.site-footer.compact > *; the family row's own 4px vertical padding
down to 0; and any prose row's line-height — inherited from
primitives.css's body default, 1.65, sized for reading prose rather
than a caption sandwiched between two other rows — down to 1.35 via
.site-footer.compact > p (a single shared value, not Connections' own
original 1.3/1.4 split by content class, once this promoted — a repo's
own notice text, whatever it says, gets the same treatment for free).
Each of these is whitespace around a row, not a change to the row's own
content — the distinction that made "reduce the gaps above and below the
family links, don't reduce the links" a coherent instruction to give and
follow.
A real alignment correction lives here too, distinct from the plain
footer's -1.2px. .family-mark sits one flex layer deeper at
≥1025px — a flex item inside .site-footer.compact's own flex-wrap
row — which shifts the dot/suffix pair's sub-pixel rounding: measured a
real 0.81px residual on top of the shared correction, closed with an
additional -2px scoped to .site-footer.compact .family-mark. See
docs/family-wordmark.md §5 — nesting the mark inside another flex
context is now documented there as its own reason to re-measure, found
here.
7. Where it goes
Replace the single copyright line —
<span>© {new Date().getFullYear()} Amoleo · Pets. All rights reserved.</span>
— with the lockup and a copyright of its own. Keeping them separate is the whole point; merged, the copyright reads as belonging to one app:
<div className="family-mark-wrap"><FamilyMark current="pets" /></div>
<span>© {new Date().getFullYear()} <span className="brand-wordmark inline-mark">Amoleo</span>. All rights reserved.</span>
The copyright's "Amoleo" carries the wordmark, not bare text — the same
.brand-wordmark.inline-mark construct as any other mention of "Amoleo" in
running prose (family-wordmark.md §9), sized off the footer's own small
copyright text rather than the lockup's 24px. It's still --brand-amoleo,
still uppercase/tracked/800-weight, just at 0.85em of whatever the
copyright line's own font-size is instead of a fixed pixel value — the
lockup above it is the one part of the footer that gets an absolute size
(§6); the copyright line is running text, so it scales the way any other
inline mention does. It is not a link on any site, including the
site's own — there's a separate self-link for that already, in the lockup
(§6 of family-wordmark.md) — bare "Amoleo" here, wordmarked but inert.
Shipped on Amoleo-Website 5 August 2026; Pets, Connections and Rissbrook
still have the plain-text version and haven't been asked to change it yet —
this isn't check-lockup.mjs-enforced the way the five-name lockup itself
is, so it won't show as a failure until it's actually rolled out repo by
repo.
8. Three things that will otherwise cost an hour
- Keep the hierarchy, not the old sizes. The product name still has to read as smaller than "Amoleo" — that's what makes it hang off the family name rather than read as plain text (tried flattened once, in Connections, reverted). What changed 4 August 2026 is the absolute sizes, 13px/7.8px to 24px/19px, not whether one is smaller than the other.
.family-mark .brand-suffixmust be pasted after the base.brand-wordmark .brand-suffixrule (the one the header wordmark also uses) in whichever stylesheet it lands in. Same specificity, so source order decides — every existing repo already pastes the family-mark block after the base wordmark rule, which is what makes this override win without raising specificity. Get the order backwards and the footer silently falls back to the header's0.6em..family-mark-wrapneedsdisplay: blockat the breakpoint, or the stacked rows are the width of the text rather than the width of the footer.- Six stacked rows is more footer on a phone now that the text is bigger. At 40px minimum row height that was already ~250px; 19px bold text needs closer to the full 40px just for the glyphs, before padding. Check it against the rest of the footer after the size change rather than assuming the old row height still has headroom.
9. What to check afterwards
- Wide screen: one line,
Amoleoat 24px with all suffixes at 19px bold, every word on the same baseline. .family-mark .brand-suffixcarries an explicittop: 0;and.family-mark .brand-dotan explicitfont-size: 19px;— both literal values, not the base rule'sem-computed ones.check-wordmark.mjsfails a repo that's missing either, matching what the Website, Rissbrook and Connections all actually ship (§6).- 375px: six rows, each the full width of the footer's content box and at least 40px tall.
- At that same width, each product row's dot+suffix pair (
.family-link-pair) sits nudged 1em left of dead centre — the row itself is still centred, only the pair inside it shifts. The "Amoleo" row above stays centred as normal, with no such nudge. Originated in Pets, now also shipped on the Website (5 August 2026); not yet checker-enforced as a failure or rolled out to Rissbrook or Connections markup (§5). - Every theme: in Pets, its own half follows
--secondarywhile the other four don't; everywhere else all colours are fixed, in every theme, at a single value — no repo should have a[data-theme="dark"]override for a--brand-*token anymore (--brand-amoleois the one exception). - "Amoleo" is the same ink on every site — compare two of them side by side. If
it has picked up the local brand colour, the wordmark is still on
--text-strongsomewhere. - Accounts renders in its own fixed slate grey (not a per-site muted colour), is a link, and carries no status label.
- No product name appears on its own anywhere on the page — every one is the
full
Amoleo · Namelockup. - A screen reader reads five distinct link names, not one run-on string.
- The lockup's family-name underlines on hover, in
--brand-amoleo— same as the cross-links beside it — while the header's own logo wordmark still shows no decoration in any state. These are deliberately different now (§6); don't reconcile them back to one rule. At rest the family-name stays undecorated, same as the rest of the lockup. - The footer's copyright line uses the wordmark (
.brand-wordmark .inline-mark) for "Amoleo", not bare text — where it's been rolled out. Not yet checker-enforced (§7), so a repo still on bare text isn't a failure, just not caught up yet.
Generated from docs/family-footer.md + tools/lockup.json + tools/repos.json. Edit there and re-run npm run build in docs-site-src/ — never hand-edit a page.