/* GENERATED by tools/build-family-css.mjs — do not hand-edit.
   Source: tools/brand-tokens.json, tools/family-css/components/{primitives.css,wordmark.css,footer-lockup.css,auth-box.css,header.css,cookie-consent.css}.
   Regenerate with: node tools/build-family-css.mjs
   See docs/family-css-system.md before changing anything downstream of
   this file — the values live in brand-tokens.json, the component rules
   live in tools/family-css/components/, and this file is only their
   compiled output. */

:root {
  --brand-pets: #D9603F;
  --brand-connections: #0b6e8f;
  --brand-rissbrook: #2f7994;
  --brand-accounts: #6e7178;
  --brand-todo: #8a6d1a;
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-full: 9999px;
}

/* ==========================================================================
   Generic primitives — reset, typography, buttons, focus ring, layout and
   accessibility utilities. No canonical doc of its own (unlike the wordmark,
   footer lockup, auth box, header and cookie-consent components) — this is
   the baseline every one of those, and a repo's own page content, sits on
   top of. See docs/family-css-system.md.

   Body typography (font-family/font-size/line-height) is Website's own
   choice, promoted to the family's shared default here — worth knowing if
   a repo's own type doesn't match, since it now inherits this rather than
   staying silently different (docs/family-theme.md).

   .wrap and .btn-quiet reference --wrap and --panel-2, which only Website
   defines today — same "shared rule, per-repo token" shape the rest of
   this system already uses for --accent. A repo adopting these rules
   defines its own value for either, the same way it already does for
   --accent.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { color: var(--text-strong); line-height: 1.2; margin: 0 0 0.5em; }
p { margin: 0 0 1em; }
img { max-width: 100%; height: auto; }

a { color: var(--accent); text-underline-offset: 3px; }
a:hover { text-decoration: none; }

:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 12px 20px;
  background: var(--panel);
  color: var(--text-strong);
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

/* Present for assistive tech, removed from visual layout without display:none
   (which screen readers also skip) — the standard clip-based pattern. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Swap which of two duplicated copies of the same content is in the
   accessibility tree, rather than reflowing one copy across a media
   query — a wide-screen inline row and a narrow-screen alternative
   (a drawer's own copy, a different control), never both at once.
   Originated on the Pre-Auth Header's nav duplication and the Post-Auth
   Header's session-actions duplication (family-header.md §1.1, §6) but
   generic beyond either — Connections also uses .narrow-only on an
   unrelated drag handle. 768px matches the family header breakpoint
   (family-header.md §6) since that's where the pattern originates, not a
   coincidence to keep in sync separately. */
.wide-only { display: inline-flex; }
.narrow-only { display: none; }
@media (max-width: 768px) {
  .wide-only { display: none; }
  .narrow-only { display: inline-block; }
}

.btn {
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  padding: 10px 20px;
  min-height: 44px;
  border-radius: var(--radius-full);
  border: 1px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}
.btn-hero { background: var(--accent); color: var(--on-accent); }
.btn-hero:hover { filter: brightness(1.12); }
.btn-quiet {
  background: transparent;
  color: var(--text);
  border-color: var(--border);
}
.btn-quiet:hover { background: var(--panel-2); }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ==========================================================================
   The wordmark — canonical spec: docs/family-wordmark.md.

   Three classes, always present together: .brand-wordmark (the whole mark),
   .brand-dot (separator, aria-hidden), .brand-suffix (the product name).
   This file carries only what's true of the wordmark everywhere it appears —
   weight, tracking, colour, alignment, and the default 0.79em suffix/dot
   ratio (the footer's own 19px/24px, generalised — family-wordmark.md §5).

   Deliberately no font-size on .brand-wordmark itself: that's a per-context
   choice (a header logo, a footer lockup, a hero, a product heading), never
   a shared value — family-header.md's "+4px/+3px offset" language is exactly
   this, expressed as a rule rather than a number. A context that needs an
   exact pixel size overrides .brand-suffix/.brand-dot together with the same
   literal value on both, never a second independent `em` computation (§5) —
   see the footer lockup block below for the pattern in practice. Link/hover
   behaviour is likewise context-specific (self-link vs. cross-link differs
   by where the mark sits — §6) and lives with each context's own rules.
   ========================================================================== */

.brand-wordmark {
  display: flex;
  align-items: center;         /* not baseline — §5 */
  margin: 0;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  color: var(--brand-amoleo);  /* never --text-strong — §4 */
}

.brand-wordmark .brand-dot {
  color: var(--wordmark-accent, var(--accent));
  margin: 0 7px;
  font-weight: 400;
  font-size: 0.79em;            /* matches the suffix, not the name — §5 */
}

.brand-wordmark .brand-suffix {
  font-size: 0.79em;
  font-weight: 700;
  color: var(--wordmark-accent, var(--accent));
  /* No manual top offset at the base — align-items:center on the parent
     does the real work. Individual contexts (the footer lockup below, the
     inline mention further down) sometimes need a small explicit
     correction on top of that — see the comment at each one for why;
     it's a real, measured browser difference, not a base-rule concern. */
}

/* The wordmark dropped into ordinary running prose ("the whole Amoleo
   family", a footer copyright line) rather than standing alone as a
   heading or lockup — family-wordmark.md §9. inline-flex, not the base
   rule's block-level flex, so it sits on the sentence's own line; 0.85em
   because uppercase + tracking + 800-weight already reads "louder" than
   the surrounding text at equal size. Link/hover behaviour for this
   construct (e.g. a compound "Amoleo · Rissbrook" mention that links out)
   is context-specific — same as the wordmark generally, §6 — and stays
   with whichever page defines that link, not here. */
.brand-wordmark.inline-mark {
  display: inline-flex;
  vertical-align: baseline;
  font-size: 0.85em;
}
/* Chrome/Edge render this construct's dot+suffix pair sitting below the
   name's own vertical centre — align-items:center on a flex row mixing
   very different font-sizes/weights (800 vs 700/400) doesn't land
   pixel-identically across engines; Firefox already centres this
   correctly with no help. -0.132em was found empirically in Chrome (not
   derived from a formula — a clean "half the size delta" guess overshoots
   here, see docs/family-wordmark.md §5) at this construct's actual
   rendered size (0.79 × 0.85em ≈ 11.4px in a 17px paragraph), then
   expressed relative to .brand-suffix's own font-size rather than left as
   the raw px that produced it: unlike the footer lockup below (a fixed,
   literal 19px everywhere), this construct is explicitly meant to render
   at whatever size its surrounding prose is (family-wordmark.md §9 — the
   hero, the footer copyright line, a cookie-banner mention), so a frozen
   px value would silently be wrong anywhere the surrounding text isn't
   ~17px. em at least scales in the right direction there; the underlying
   browser quirk still doesn't scale perfectly linearly with size (that's
   why the footer needs its own separately-measured value, not this same
   em figure), so re-measure at a meaningfully different size rather than
   assume this one em value is exactly right everywhere it's used. */
.inline-mark .brand-suffix,
.inline-mark .brand-dot {
  position: relative;
  top: -0.132em;
}

/* ==========================================================================
   The family footer lockup — canonical spec: docs/family-footer.md §5–§6.

   Markup (HTML, not here — see the doc): .family-mark-wrap > .brand-wordmark
   .family-mark, holding .family-name plus four .family-app <span>s, each
   wrapping a .family-link around a .family-link-pair(.brand-dot + .brand-suffix).

   Requires tools/family-css/components/wordmark.css loaded first — this file
   only adds the footer's own overrides on top of the base wordmark rules
   (per-product accent variables, the footer's link/hover treatment, and the
   mobile stacked-row layout).
   ========================================================================== */

.family-mark { font-size: 24px; flex-wrap: nowrap; justify-content: center; white-space: nowrap; }
.family-app, .family-link { display: inline-flex; align-items: center; }
/* Both explicit, not inherited em values — 19px (not 0.79em of 24px) avoids
   two independent roundings landing a sub-pixel apart (family-wordmark.md
   §5). top: -1.2px on both is a real, measured correction, not the old
   "explicit zero" record it replaces: Chrome/Edge render this pair sitting
   ~1.2px below "Amoleo"'s own vertical centre — align-items:center on a
   flex row mixing very different font-sizes/weights (24px/800 name vs.
   19px/700-400 dot+suffix) doesn't land pixel-identically across engines,
   Firefox already centres it correctly with no help. Found empirically by
   measuring the actual rendered gap in Chrome and closing it to zero, not
   derived from a formula — re-measure rather than assume it still holds if
   the sizes or weights above ever change. */
.family-mark .brand-suffix { font-size: 19px; font-weight: 700; position: relative; top: -1.2px; }
.family-mark .brand-dot { font-size: 19px; position: relative; top: -1.2px; }

.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>, so it takes the product's own colour and never leaks under the dot. */
.family-mark a { text-decoration: none; }
.family-mark a:hover .brand-suffix { text-decoration: underline; }
/* Where the family name is itself a link (every site except the one it
   points to — the Website has nowhere to send itself, so .family-name is
   plain text there, not an <a>), it inherits the wordmark's own colour
   rather than the generic link colour a bare <a> would otherwise get. */
.family-mark a.family-name { color: inherit; }
/* A hover-underline to match the cross-links beside it (family-wordmark.md
   §6, WCAG 1.4.1) — promoted 7 Aug 2026, wiring Pets in: Pets already
   shipped this (its own footer CSS is where the pattern this whole file
   was extracted from originated), it just hadn't been carried into the
   shared file yet. check-wordmark.mjs previously reported this as pending
   rather than a failure for exactly that reason — real, but not yet in the
   one shared copy. */
.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%;
    /* 40px, matching what every adopted repo ships today. WCAG 2.2 AA's
       actual floor is 44px (Rissbrook's own header targets it) — a known,
       recorded gap, not fixed here so this extraction doesn't also change
       what renders. See docs/family-footer.md §0. */
    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. Originated
     in Amoleo-Pets, 5 Aug 2026; shipped on the Website 5 Aug 2026 too. */
  .family-mark .family-link-pair {
    margin-left: -1em;
  }
}

/* ==========================================================================
   The compact footer — .site-footer.compact. For a page that can't afford
   the full footer's height (a map, a canvas, an editor) — family-footer.md
   §6. Promoted 6 Aug 2026 from Connections' own implementation, the only
   one so far; a repo adopts it by adding the `compact` modifier class to
   its own `.site-footer` and keeping the same top-level row structure
   (an optional notice, `.family-mark-wrap`, the copyright line) as direct
   children — the rules below target that shape generically (`> *`, `> p`)
   rather than Connections' own row class names, so they don't assume any
   repo-specific content.
   ========================================================================== */

/* Below the ≥1025px fold-to-one-line width (below), .site-footer's own
   20px top/bottom padding (sized for the full footer's five rows) never
   had a compact-specific reduction, nor did the 10px/10px/6px gaps
   between rows — both still the full-footer's own spacing regardless of
   width. On a page where every row this footer takes is a row taken from
   the thing the page is actually for, that's padding compact shouldn't
   be paying at any width, not just the widest one. */
@media (max-width: 1024px) {
  .site-footer.compact {
    padding-top: 8px;
    padding-bottom: max(8px, env(safe-area-inset-bottom));
  }
  .site-footer.compact > * { margin: 0; }
  /* Prose rows (the optional notice, the copyright line) inherit
     primitives.css's own body line-height — 1.65, sized for reading
     prose, not a caption sandwiched between two other rows. `> p`, not a
     content-specific class, so a repo's own notice text (TMDB's required
     wording, or nothing at all) gets this for free. `.family-mark-wrap`
     is a `<div>`, so this doesn't touch the lockup's own row. */
  .site-footer.compact > p { line-height: 1.35; }
}

/* 1024px is the widest tablet, so that's where the stacked footer ends
   and this one starts; how many lines *this* one folds to depends on how
   wide the window actually is above that, per the ordering note below. */
@media (min-width: 1025px) {
  .site-footer.compact {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 4px 16px;
    padding-top: 8px;
    padding-bottom: max(8px, env(safe-area-inset-bottom));
  }
  .site-footer.compact > * { margin: 0; }

  /* Visual order only — the document order stays notice, lockup,
     copyright, because that's also the full (non-compact) footer's order
     and `order` has no effect there. In compact, the lockup moves in
     front of the other rows; it doesn't move *them*, so they stay
     adjacent to each other in the resulting sequence and flex-wrap
     therefore always breaks between the lockup and whatever's beside it,
     never mid-row. See family-footer.md §6. */
  .site-footer.compact .family-mark-wrap { order: -1; }

  /* .family-mark's own -1.2px correction (above) was measured against
     the plain, non-compact footer — here it's nested one level deeper,
     as a flex item inside .site-footer.compact's own flex-wrap row, and
     that extra layer rounds sub-pixels differently: measured a real
     0.81px residual (getBoundingClientRect(), 6 Aug 2026), not present
     in the plain footer. Re-measured for this context specifically
     rather than assumed the shared value still holds — see
     family-wordmark.md §5's "nesting is its own reason to re-measure"
     note, found here. */
  .site-footer.compact .family-mark .brand-suffix,
  .site-footer.compact .family-mark .brand-dot {
    top: -2px;
  }
}

/* Below 560px, the lockup becomes a horizontally swipeable strip instead
   of the plain footer's own vertical stack (above) — a fifth of a phone
   screen gone on a map page is exactly the cost `compact` exists to
   avoid, and there's no reason to ask a map page's screen for five
   full-width rows just to fit the same four names. Tried first: a
   collapse-behind-a-caret control (tap "Amoleo" to reveal the rest).
   Reverted the same session it shipped — a toggle read as one more thing
   to puzzle out, where a swipeable row invites the gesture on its own.
   overflow-x/scroll-snap on .family-mark itself, deliberately no width
   constraint beyond its own content, so the next name always peeks in at
   whatever width the phone actually has — the peek is what signals
   "there's more here", not a static arrow. Neither the font-size nor the
   tap-target height of the product names changes from the plain footer;
   only the layout around them does. */
@media (max-width: 560px) {
  .site-footer.compact .family-mark-wrap {
    display: block;
    width: 100%;
    /* The fade needs somewhere to cut into without clipping a focus ring
       drawn just inside the row's own edge. */
    overflow: hidden;
  }
  .site-footer.compact .family-mark {
    width: auto;
    flex-direction: row;
    align-items: center;
    white-space: nowrap;
    gap: 0;
    justify-content: flex-start;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Vertical padding is 0 — the row's own height already comes from
       the links' own min-height below, and any extra here was gap
       around them, not part of them. */
    padding: 0 28px;
    /* Fades the peeking edges rather than hard-cropping them — reads as
       "more, scroll" instead of "the word is broken". */
    mask-image: linear-gradient(
      to right, transparent, black 24px, black calc(100% - 24px), transparent
    );
  }
  .site-footer.compact .family-mark::-webkit-scrollbar { display: none; }
  .site-footer.compact .family-mark .family-name,
  .site-footer.compact .family-mark .family-link {
    display: inline-flex;
    width: auto;
    min-height: 40px;
    flex: none;
    scroll-snap-align: center;
    padding: 2px 14px;
  }
}

/* ==========================================================================
   Sign in and create account — canonical spec: docs/family-auth-box.md.
   Reference implementation: Amoleo-Connections' AuthScreen.jsx / .auth-card.

   Colour: --accent / --on-accent are the two tokens that carry a product's
   own identity here (active tab, focus ring, submit button) — never a
   literal hex. Everything else is neutral: the shared base theme
   (--bg/--panel/--border/--text/--muted, family-theme.md) plus each repo's
   own --text-strong, --danger/--danger-tint and --radius-*/--shadow-lg
   tokens, used under their existing names.
   ========================================================================== */

.auth-screen {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 24px 16px;
  background: var(--bg);
}

.auth-card {
  width: min(420px, 100%);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 30px 28px 24px;
}

.auth-card .brand-wordmark { justify-content: center; font-size: 22px; }

.auth-tagline {
  margin: 10px 0 22px;
  text-align: center;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}

/* Two tabs sharing one track, so the pair reads as a single choice. */
.auth-tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  margin-bottom: 22px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
}

.auth-tab {
  padding: 9px 12px;
  border: none;
  border-radius: var(--radius-full);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  /* Constant weight, so the pair never resizes as the selection moves. */
  font-weight: 600;
  cursor: pointer;
}
.auth-tab:hover { color: var(--text); }
.auth-tab.active { background: var(--accent); color: var(--on-accent); }

.auth-card label {
  display: block;
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-strong);
}

.auth-card input {
  width: 100%;
  padding: 11px 12px;
  margin-bottom: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 14px;
}
.auth-card input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.auth-card input[aria-invalid="true"] { border-color: var(--danger); }

.auth-card .field-hint { margin: 0 0 16px; }
.auth-card .field-hint code { font-size: 11px; word-break: break-all; }

.field-problem {
  margin: 0 0 16px;
  font-size: 12px;
  color: var(--danger);
}

/* The reveal sits inside the field rather than beside it, so the row keeps
   one height and the input keeps its full width. */
.password-field { position: relative; }
.password-field input { padding-right: 62px; }
.password-toggle {
  position: absolute;
  top: 6px;
  right: 6px;
  padding: 6px 10px;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.password-toggle:hover { color: var(--accent); }

.auth-error {
  margin: 0 0 16px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--danger);
  background: var(--danger-tint);
  color: var(--text);
  font-size: 13px;
  line-height: 1.5;
}

.auth-submit {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 4px;
  /* Fixed height: the spinner appearing beside the label must not resize it. */
  min-height: 44px;
}
.auth-submit:disabled { opacity: 0.7; cursor: default; }
.auth-submit .spinner { border-color: rgba(0, 0, 0, 0.25); border-top-color: var(--on-accent); }

.auth-switch {
  margin: 18px 0 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
.auth-switch .link { font-size: 13px; font-weight: 600; }

@media (max-width: 780px) {
  /* Restates the 16px rule an iOS-specific block elsewhere would otherwise
     be out-specified by — a 14px field is one iOS zooms into on focus and
     never zooms back out of. */
  .auth-card input { font-size: 16px; }
}

@media (max-width: 480px) {
  .auth-screen { justify-content: flex-start; padding-top: max(32px, env(safe-area-inset-top)); }
  .auth-card { padding: 24px 20px 20px; border-radius: var(--radius-md); }
}

/* ==========================================================================
   The header — canonical spec: docs/family-header.md.

   Pre-Auth Header (.family-header / .family-header-bar): shown to every
   visitor before any authentication state is known. A repo with no login
   at all (the Website) shows this permanently; a repo with login shows it
   until a visitor signs in, then switches to the Post-Auth Header (the
   compact 28px bar — family-header.md §2 — not built as a shared component
   yet; no repo has an authenticated state wired into this system).

   Two structures, not one element resizing: .family-header (desktop,
   centred, not sticky, 36px wordmark) and .family-header-bar (mobile,
   sticky, compact, 28px wordmark) — matching Connections' own
   .landing-header/.landing-bar split by breakpoint. Only one is ever in
   the accessibility tree at a given width; the other is display:none, not
   just visually hidden, so nothing doubles up for a screen reader. Nav
   content is duplicated between the two rather than moved by script — the
   desktop nav row and the mobile drawer's nav are separate DOM nodes.

   Two distinct reveal mechanisms live in .family-header-bar's right-hand
   slot, chosen by what's actually being revealed rather than by repo:
   .family-header-burger opens .family-header-drawer, an off-canvas panel
   sliding in from the side, for nav content (Website). .family-header-login
   opens .family-header-panel, a sticky in-flow drop-down directly under
   the bar with no backdrop, for an auth form (Connections) — a login
   reveals what would otherwise be at the top of the page, not a menu
   covering the page, so it doesn't get the drawer's slide/backdrop
   treatment. A repo with both nav and login would show both triggers side
   by side, each opening its own thing (family-header.md §6: the burger
   never hides the login control, and neither hides the other).
   ========================================================================== */

.family-header {
  padding: 18px max(20px, env(safe-area-inset-left)) 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
/* Pre-Auth size — base wordmark (24px, family-wordmark.md §2) + the
   pre-auth hero's own +12px offset (family-header.md §1,
   wordmarkPreAuthHero in tools/header.json) = 36px. Bigger than the
   Post-Auth bar's 28px below, never equal to it.

   Two selectors, not one: .family-header-brand is Website's own flat
   markup (`<span class="brand-wordmark family-header-brand">`);
   .family-header .brand-wordmark is the generic, container-scoped form
   that reaches a repo whose wordmark is a reusable component instead
   (Connections' <Wordmark>, always class="brand-wordmark" alone, never a
   context-specific class of its own — family-header.md §1.1). Both match
   Website's own element identically (it carries both classes), so this
   isn't two sources of truth, just two ways in depending on how a repo's
   markup is shaped. The suffix/dot pinned to 22px and the -0.7px
   correction only bite when a suffix is actually rendered here, so
   they're silent no-ops on Website's own bare "Amoleo" — found live
   6 Aug 2026 wiring Connections in, where they weren't. */
.family-header-brand,
.family-header .brand-wordmark { font-size: 36px; }
a.family-header-brand { text-decoration: none; color: var(--brand-amoleo); }
.family-header .brand-wordmark .brand-suffix,
.family-header .brand-wordmark .brand-dot {
  font-size: 22px;
  position: relative;
}
/* Corrected 6 Aug 2026, wiring Rissbrook's own header in: the original
   -0.7px (both, one shared value) was measured with
   getBoundingClientRect() line-box centering against Connections and
   read as "0.695px below centre" — but that method centres the box, not
   the glyph's ink, and at this pairing it was wrong in BOTH sign and
   magnitude for each glyph individually, not just imprecise. A first zoom
   check here read as "close enough" and the value was left alone (see
   family-wordmark.md §5's note on the 28px/22px pairing needing a similar
   correction) — that call was wrong too, caught only once looking at
   Rissbrook's actual header made "RISSBROOK" sitting visibly high next
   to "AMOLEO" obvious. Re-derived with
   CanvasRenderingContext2D.measureText()'s actualBoundingBoxAscent/
   actualBoundingBoxDescent (the real per-glyph ink extents) and confirmed
   with a before/after comparison at 6x CSS zoom, not a single glance —
   0px residual on both. Unlike the 28px/22px app-shell pairing, the dot
   and suffix land on the SAME sign here (both need to move down, not
   opposite directions) — the two pairings don't share one relationship,
   confirming family-wordmark.md §5's "not a clean formula" finding one
   more way. */
.family-header .brand-wordmark .brand-dot { top: 0.5px; }
.family-header .brand-wordmark .brand-suffix { top: 1.5px; }

.family-header-nav { display: flex; gap: 22px; }
.family-header-nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 15px;
}
.family-header-nav a:hover { color: var(--text-strong); }

/* .family-header-login: a repo with a mobile sign-in control — placement
   and the reveal it drives come from Connections' own .landing-bar-toggle,
   the reference implementation for this whole pre-auth pattern
   (tools/header.json). A repo without login (the Website) omits the
   element from its markup entirely rather than rendering it empty.
   Tapping it opens .family-header-panel below, not .family-header-drawer —
   a login control reveals a form, which reads as replacing what's at the
   top of the page, not as a nav menu sliding in from the side
   (family-css-system.md).

   Full width, own row, accent-coloured outline rather than the solid
   accent-filled pill this used to be inline beside the wordmark — that
   pairing is what overflowed a 375px phone (measured 102px over budget,
   6 Aug 2026: "AMOLEO · CONNECTIONS" alone needs 348px of a 375px bar).
   The outline keeps the accent colour so it still reads as the bar's one
   call to action, without a filled block visually competing with the
   wordmark for weight on its own row — a solid fill read as too heavy
   stacked directly under the brand mark; a fully neutral outline lost the
   "this is the actionable thing" signal the accent colour was carrying. */
.family-header-login {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 100%;
  min-height: 44px;
  padding: 9px 14px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
/* A small triangle, not an icon font or an image — rotates to point the
   other way while .family-header-panel is open. */
.family-header-login-caret {
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform 150ms ease;
}
.family-header-login[aria-expanded="true"] .family-header-login-caret {
  transform: rotate(180deg);
}

/* The mobile bar, burger, drawer and panel are all inert above the
   breakpoint — .family-header-drawer/.family-header-drawer-backdrop/
   .family-header-panel stay out of layout and out of the way entirely
   rather than existing off-screen at every width for no reason. */
.family-header-bar,
.family-header-drawer,
.family-header-drawer-backdrop,
.family-header-panel {
  display: none;
}

@media (max-width: 768px) {
  .family-header { display: none; }

  .family-header-bar {
    display: flex;
    /* Column, not row: a repo with both a wordmark and a full-width login
       control (Connections) stacks them rather than sharing one row —
       "AMOLEO · CONNECTIONS" plus a real sign-in control doesn't fit one
       64px-tall row on a phone (measured deficit: 102px over budget on a
       375px viewport, 6 Aug 2026 — dropping the product name instead was
       considered and rejected: the name has to stay everywhere). A repo
       with only a burger (Website) still gets one row in practice, since
       .family-header-burger is a small fixed-size icon button, not
       full-width — align-items below keeps that case looking like the old
       single row rather than an oddly-narrow stack. */
    flex-direction: column;
    align-items: stretch;
    /* gap and background match Connections' own .landing-bar exactly (the
       reference implementation) — Website picks up both as a small,
       deliberate correction rather than keeping its own earlier guess
       (16px gap, --bg background) now that there's a second real
       implementation to check against. */
    gap: 10px;
    position: sticky;
    top: 0;
    z-index: 20;
    padding: max(10px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
             12px max(16px, env(safe-area-inset-left));
    background: var(--panel);
    border-bottom: 1px solid var(--border);
  }
  /* The wordmark's own row: a burger-only repo (Website) puts it beside the
     burger on one row instead of stacking, since a small icon button never
     causes the overflow a full-width login control does. Excludes a repo
     with both triggers (family-header.md §6's documented future case) —
     .family-header-login is what needs its own row, regardless of whether
     a burger is also present, so that combination still stacks. */
  .family-header-bar:has(.family-header-burger):not(:has(.family-header-login)) {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    min-height: 64px;
    padding-top: max(10px, env(safe-area-inset-top));
    padding-bottom: 10px;
  }
  /* Centred, not left-aligned, specifically when a login control makes
     this a two-row stack — the wordmark reads as the row's own heading
     rather than sitting off to one side above a full-width button
     centred (by width alone) beneath it. Left-aligned stays the rule for
     the single-row burger-only shape above, unaffected by this — that
     case still matches the wordmark's position in the desktop
     .family-header it swaps with. */
  .family-header-bar:has(.family-header-login) .brand-wordmark {
    align-self: center;
  }
  /* Post-Auth size (family-wordmark.md §2's 24px base + the header's own
     +4px offset = 28px) — the compact bar is always this size, regardless
     of which header state surrounds it; only the standalone desktop
     .family-header uses the bigger Pre-Auth size above. Two selectors for
     the same reason as .family-header-brand above — Website's own flat
     class plus the generic, component-friendly form. */
  .family-header-bar-brand,
  .family-header-bar .brand-wordmark { font-size: 28px; }
  a.family-header-bar-brand { text-decoration: none; color: var(--brand-amoleo); }
  .family-header-bar .brand-wordmark .brand-suffix,
  .family-header-bar .brand-wordmark .brand-dot {
    font-size: 22px;
    position: relative;
  }
  /* Corrected 6 Aug 2026, wiring Rissbrook in: the dot and the suffix need
     OPPOSITE-signed corrections at this pairing, not the one shared
     `top: -0.6px` this rule used to carry for both. That single value was
     measured against Connections with getBoundingClientRect() on each
     element's own line box (family-wordmark.md §5's usual method) — which
     centres the box, not the glyph's ink, and a "·" sits at a different
     position relative to its own baseline than a capital letter does, even
     at the identical font-size. The gap is invisible on a value tuned to
     look right for whichever glyph happens to dominate the visual check
     (the suffix's full word, usually) and wrong on the other — caught only
     once Rissbrook's own header made the suffix's ~1.1px "too high" look
     obvious enough to flag by eye. Re-derived with
     CanvasRenderingContext2D.measureText's actualBoundingBox* metrics (the
     real per-glyph ink extents, not the line box) and confirmed against
     the live page at 6x CSS zoom — see family-wordmark.md §5 for the
     method. Not applied to the 36px/22px .family-header pairing above:
     the same analysis flags a discrepancy there too, but a zoomed visual
     check found it already reads correctly, so it's left alone rather
     than "corrected" against a model that doesn't hold as well the
     further apart the two sizes get. */
  .family-header-bar .brand-wordmark .brand-dot { top: -0.5px; }
  .family-header-bar .brand-wordmark .brand-suffix { top: 0.5px; }

  .family-header-burger {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: none;
    background: none;
    color: var(--text-strong);
    cursor: pointer;
  }
  .family-header-burger-icon {
    position: relative;
    width: 20px;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
  }
  .family-header-burger-icon::before,
  .family-header-burger-icon::after {
    content: "";
    position: absolute;
    left: 0;
    width: 20px;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
    transition: transform 0.2s ease, top 0.2s ease, opacity 0.2s ease;
  }
  .family-header-burger-icon::before { top: -6px; }
  .family-header-burger-icon::after { top: 6px; }
  /* Open state: the three bars become an X — the middle bar (the icon
     element's own background) fades out rather than needing a fourth
     element, since two rotated bars alone already read as a close icon. */
  .family-header-burger[aria-expanded="true"] .family-header-burger-icon {
    background: transparent;
  }
  .family-header-burger[aria-expanded="true"] .family-header-burger-icon::before {
    top: 0;
    transform: rotate(45deg);
  }
  .family-header-burger[aria-expanded="true"] .family-header-burger-icon::after {
    top: 0;
    transform: rotate(-45deg);
  }

  /* display:block is unconditional at this width — visibility comes from
     the transform, not from adding/removing the element, so the slide-in
     transition has something to animate. (Pets' own .header-drawer, the
     closest existing implementation of this pattern, gets away with an
     unconditional display:block on its backdrop too, but only because
     React unmounts the node entirely when closed; this is meant to work
     as static markup too, so .family-header-drawer-backdrop below is
     explicitly gated on .open instead.) */
  .family-header-drawer {
    display: block;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: 280px;
    max-width: 85vw;
    padding: max(80px, calc(env(safe-area-inset-top) + 64px)) 20px 20px;
    background: var(--panel);
    border-left: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
    transform: translateX(100%);
    transition: transform 0.3s ease;
    z-index: 200;
    overflow-y: auto;
  }
  .family-header-drawer.open { transform: translateX(0); }

  .family-header-drawer nav { display: flex; flex-direction: column; gap: 4px; }
  .family-header-drawer nav a {
    padding: 10px 4px;
    color: var(--text);
    text-decoration: none;
    font-size: 16px;
    font-weight: 600;
  }
  .family-header-drawer nav a:hover { color: var(--text-strong); }

  .family-header-drawer-backdrop.open {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 199;
  }

  /* .family-header-panel: the drop-down reveal for .family-header-login,
     matching Connections' own .landing-bar-panel exactly. Sticky and
     in-flow directly under the bar (z-index 19, one below the bar's own
     20) rather than fixed/overlaid like the drawer — no backdrop, since
     it isn't covering page content the way an off-canvas menu does, it's
     replacing what would otherwise be at the top of the page.

     Unlike the drawer, this one isn't gated on an .open class — presence
     in the DOM is the show/hide signal instead (Connections' own React
     conditionally renders it: `{open && <div className="family-header-panel">}`).
     That works for static markup too (add/remove the node via JS rather
     than toggling a class) — the drawer needs the always-present-plus-class
     approach specifically because its slide-in transform needs something
     to animate from; this panel has no such animation, so there's nothing
     an always-present node would buy here. .family-header-panel { display:
     none } above the breakpoint (grouped with the drawer/backdrop at the
     top of this file) still applies regardless of DOM presence, so an
     accidental render at desktop width stays invisible either way. */
  .family-header-panel {
    display: block;
    position: sticky;
    top: 0;
    z-index: 19;
    padding: 14px max(16px, env(safe-area-inset-right)) 18px max(16px, env(safe-area-inset-left));
    background: var(--panel);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
  }
  /* Inside the panel the auth card is the panel — it drops its own frame
     rather than sitting inside a card inside a card. */
  .family-header-panel .auth-card {
    width: 100%;
    padding: 0;
    background: none;
    border: none;
    box-shadow: none;
    border-radius: 0;
  }
}

/* ==========================================================================
   Post-Auth Header — .family-header-postauth. Shown once a visitor is
   signed in (Pets, Connections' App.jsx) or on a lighter public/read-only
   variant of the same shape (Connections' PublicBoard.jsx, no session
   actions). Promoted 6 Aug 2026 from Connections' own implementation —
   its burger/drawer reuses the Pre-Auth mechanics directly rather than
   reinventing them, and its wordmark is the same 28px/22px pairing the
   Pre-Auth mobile bar already uses, both of which argued for one shared
   definition rather than waiting for literal wording to differ later.
   Sticky-or-not stays a per-repo decision (family-header.md §3), same as
   every other Post-Auth Header property that isn't this shape itself.
   ========================================================================== */

.family-header-postauth {
  padding: 14px 20px 10px;
  min-height: 64px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
/* Same 28px/22px pairing and correction as .family-header-bar's own —
   not a coincidence, the two are the same size by definition
   (family-wordmark.md §2's base + the header's "+4px/+3px" offset) — but
   kept as its own rule rather than sharing a selector with
   .family-header-bar, since the two containers are never nested inside
   each other and a repo may adopt one without the other.

   This comment already said the two should match when it was written
   (6 Aug 2026) — the code didn't, until 7 Aug 2026: .family-header-bar's
   own dot/suffix rule got the ink-based opposite-sign fix that day
   (wiring Rissbrook in, see that rule's own comment for the method), but
   nobody cross-checked this identical-sized pairing against it, so this
   rule kept the old single -0.6px for both. Caught wiring Pets' Post-
   Auth header in, the same day — a zoomed screenshot of "AMOLEO · PETS"
   showed the dot sitting visibly high, confirmed by the same before/
   after 6x-zoom comparison family-wordmark.md §5 already prescribes.
   The shared-CSS system guarantees a fix applies everywhere a *selector*
   is loaded from; it does nothing to keep two textually-separate
   selectors that happen to need the same value in sync with each other
   — that still takes a human noticing, which is exactly what didn't
   happen for a day. Now literally the same two declarations as
   .family-header-bar's, not just asserted to be. */
.family-header-postauth .brand-wordmark { font-size: 28px; }
.family-header-postauth .brand-wordmark .brand-suffix,
.family-header-postauth .brand-wordmark .brand-dot {
  font-size: 22px;
  position: relative;
}
.family-header-postauth .brand-wordmark .brand-dot { top: -0.5px; }
.family-header-postauth .brand-wordmark .brand-suffix { top: 0.5px; }

.family-header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}
/* A link-styled action needs the same visual weight as a real button
   beside it — a.button-like is otherwise only ever seen at a smaller
   size elsewhere on a page (an image's own action row, say). */
.family-header-actions a.button-like { padding: 7px 12px; font-size: 12px; }
.family-header-actions button,
.family-header-actions a.button-like {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Desktop row vs. the drawer's own duplicate copy below — same
   duplicated-not-moved idiom the Pre-Auth nav uses (top of this file).
   .wide-only/.narrow-only are the shared visibility-swap utilities in
   primitives.css. */
.family-header-actions.wide-only { display: inline-flex; }

@media (max-width: 768px) {
  .family-header-postauth {
    padding: 10px 14px 8px;
    align-items: center;
    gap: 10px;
  }
  /* An explanatory line under the wordmark is the first thing to go on a
     phone — for a first-time visitor on a big screen, not someone who's
     scrolled past it once already. */
  .family-header-postauth .tagline { display: none; }
  .family-header-actions.wide-only { display: none; }

  /* The wordmark shrinks specifically when a burger is present and this
     pairing doesn't fit beside it — measured 16.9px overflow on a 375px
     phone with the full 28px/22px pairing, 6 Aug 2026 (Connections'
     "AMOLEO · CONNECTIONS" alone needs 347.9px of the 293px actually
     left once padding, gap and the 44px burger are accounted for). A
     header with no burger (a lighter, session-free variant) keeps the
     standard size untouched — there's nothing to make room for. */
  .family-header-postauth:has(.family-header-burger) .brand-wordmark { font-size: 22px; }
  .family-header-postauth:has(.family-header-burger) .brand-wordmark .brand-suffix,
  .family-header-postauth:has(.family-header-burger) .brand-wordmark .brand-dot {
    font-size: 17px;
    position: relative;
    /* Re-measured against the promoted, .family-header-postauth-scoped
       selector rather than trusting the value from when this was still
       Connections' own bare `header:has(...)` rule — swept -1px to +1px
       in 0.2–0.6px steps and read the actual offset at each rather than
       reasoning about the sign, after guessing wrong once (a positive
       top made the residual worse, not better). -0.2px is the value that
       actually lands closest to 0 (0.008px). family-wordmark.md §5: don't
       assume a correction still holds without checking again. */
    top: -0.2px;
  }

  .family-header-drawer .family-header-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }
  .family-header-drawer .family-header-actions button,
  .family-header-drawer .family-header-actions a.button-like {
    width: 100%;
  }
  /* Hidden in the desktop row (a phone-width username label competes
     with the wordmark for room); shown here instead, since the drawer
     has the vertical space a wide screen's single row doesn't. */
  .family-header-drawer .family-header-actions .signed-in {
    display: block;
    padding: 4px 0 8px;
    color: var(--muted);
    font-size: 13px;
    font-weight: 600;
  }
}

@media (min-width: 769px) {
  .family-header-postauth .family-header-burger { display: none; }
}

/* ==========================================================================
   The cookie-consent banner — canonical spec: docs/cookie-consent.md §6.

   .consent-inner's max-width uses --wrap, which only Website defines (its
   own layout-rhythm token, not part of the shared base). A repo without it
   overrides the one property after loading this file — docs/cookie-consent.md
   §6 records Pets using a literal 760px in place of a --wrap it doesn't have.

   z-index: 50 sits above the page and below nothing else on the Website —
   check it against any repo's own modal stack before relying on it there
   (docs/cookie-consent.md §6's own note: Pets' modals sit higher, and the
   banner should never float over a privacy modal it just opened).
   ========================================================================== */

.consent {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: 16px;
  z-index: 50;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.consent-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 28px;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 20px 24px;
}
.consent h2 { font-size: 17px; margin-bottom: 4px; }
.consent p { margin: 0; font-size: 14.5px; max-width: 62ch; }
.consent-actions { display: flex; gap: 10px; flex-wrap: wrap; }

@media (max-width: 560px) {
  .consent-actions .btn { flex: 1; }
}
