/* Storybook — hand-written CSS, no framework. Dark theme is the default. */

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

html, body {
  margin: 0;
  padding: 0;
}

/* R3.2: class rules like `.editor__recovery { display: flex }` and
   `.btn { display: inline-flex }` outrank the UA stylesheet's
   `[hidden] { display: none }` by specificity. Elements that toggle the
   `hidden` attribute (rather than a class) must actually disappear. */
[hidden] {
  display: none !important;
}

/* ---------------------------------------------------------------------- */
/* F17: hand-drawn visual identity ("le style du ranch")                   */
/*                                                                          */
/* Every JPEG illustration carries its own cream paper background, so the  */
/* .illo class mounts it as a paper card pinned to the page: matching      */
/* cream padding, a light-theme border regardless of theme, a soft shadow  */
/* and a slight rotation. It stays cream in every theme on purpose — in    */
/* dark mode it reads as a photograph tucked into an album. The two        */
/* transparent PNGs (rope-divider, lasso-ring) are NOT cards and never get */
/* this class — they sit directly on the page background.                 */
/* ---------------------------------------------------------------------- */

.illo {
  display: block;
  padding: 0.75rem;
  /* F46: the card's mount is the paper the pack's pictures are drawn on —
     cream for the ranch, void for a pack drawn on black. A pack that
     changes its art has to be able to change what the art sits in. */
  background: var(--illo-mount);
  border: 1px solid var(--illo-mount-edge);
  border-radius: 4px;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.22);
  transform: rotate(-1.2deg);
}

.illo--tilt-right {
  transform: rotate(1deg);
}

/* F42: a page's own illustration, centered under its title. One size for
   the landscape cards, a narrower one for the portrait ones, rather than a
   per-page rule for each. */
.illo--page {
  max-width: 14rem;
  /* width/height must both be set or the width/height attributes (kept so
     nothing shifts while loading) squash the picture — same as
     .login__illo. */
  width: 100%;
  height: auto;
  margin: 0 auto 1.25rem;
}

.illo--page-tall {
  max-width: 10.5rem;
}

/* Same reasoning as .instant__illo: on the guest-facing form pages the
   form has to stay above the fold on a short phone, so the card goes
   rather than the fields. */
@media (max-height: 700px) {
  .page-login .illo--page {
    display: none;
  }
}

.lasso-spinner {
  display: inline-block;
  width: 40px;
  height: 40px;
  vertical-align: middle;
}

@media (prefers-reduced-motion: no-preference) {
  .lasso-spinner {
    animation: lasso-spin 1.4s linear infinite;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lasso-spinner {
    animation: lasso-pulse 1.4s ease-in-out infinite;
  }
}

@keyframes lasso-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes lasso-pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
}

/* R4.1: any focusable text/date/file input under 16px triggers iOS Safari's
   auto-zoom on focus, which then leaves the page zoomed in. */
input, textarea, select {
  font-size: 1rem;
}

/* ---------------------------------------------------------------------- */
/* Theme: dark is the default palette, applied at :root.                  */
/* ---------------------------------------------------------------------- */

:root {
  --color-bg: #141210;
  --color-bg-raised: #1c1916;
  --color-text: #e8e2d9;
  --color-text-dim: #b7ac9c;
  --color-accent: #d9a441;
  --color-accent-text: #14120e;
  --color-highlight-bg: rgba(217, 164, 65, 0.22);
  --color-border: #2c2822;
  --color-error: #e0796a;

  /* How much of F44's firelight wash this theme wants, as a multiplier on
     the gradient alphas. It is not an `opacity` — opacity clamps at 1, and
     the pale themes need *more* than the dark one, not less: amber added
     to a near-white page barely moves its brightness, so the same swing
     costs a good deal more paint there. */
  --firelight-strength: 1;

  --font-serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* --- F46: what a theme pack may re-draw, beyond the palette ---------- */
  /* The paper an illustration is mounted on. Deliberately the same in
     every colour scheme: in dark mode a cream card reads as a photograph
     tucked into an album, which is the effect we want. */
  --illo-mount: #f9f2e1;
  --illo-mount-edge: #e2d9c8;
  /* What a pack lays over its background, if anything. The ranch's aged
     paper sets its own further down, per scheme. */
  --surface-texture: none;
  --surface-texture-size: auto;
  /* The two pictures CSS draws rather than templates. */
  --flourish-image: url("../themes/ranch/img/rope-divider.png");
  --brand-mark: url("../themes/ranch/img/brand-star.png");
  /* The F44 ambience, as colours rather than a fire: the light it casts,
     and the shadow that returns when it dips. */
  --ambience-glow: 255, 176, 92;
  --ambience-glow-edge: 255, 140, 48;
  --ambience-shade: 26, 15, 4;

  /* The sepia "aged photo" recipe every portrait uses. --photo-sepia is
     set per-element (inline, from the photo's own stored tone); this is
     the fixed formula around it, same in every theme. */
  --photo-filter: sepia(var(--photo-sepia, 30%)) saturate(0.88) contrast(1.02) brightness(0.98);

  color-scheme: dark;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --color-bg: #faf6ef;
    --color-bg-raised: #f2ecdf;
    --color-text: #2a2520;
    --color-text-dim: #6b6255;
    --color-accent: #a9701c;
    --color-accent-text: #fff8ec;
    --color-highlight-bg: rgba(169, 112, 28, 0.18);
    --color-border: #e2d9c8;
    --color-error: #a3372a;
    --firelight-strength: 1.6;

    color-scheme: light;
  }
}

:root[data-theme="light"] {
  --color-bg: #faf6ef;
  --color-bg-raised: #f2ecdf;
  --color-text: #2a2520;
  --color-text-dim: #6b6255;
  --color-accent: #a9701c;
  --color-accent-text: #fff8ec;
  --color-highlight-bg: rgba(169, 112, 28, 0.18);
  --color-border: #e2d9c8;
  --color-error: #a3372a;
  --firelight-strength: 1.6;

  color-scheme: light;
}

:root[data-theme="dark"] {
  --color-bg: #141210;
  --color-bg-raised: #1c1916;
  --color-text: #e8e2d9;
  --color-text-dim: #b7ac9c;
  --color-accent: #d9a441;
  --color-accent-text: #14120e;
  --color-highlight-bg: rgba(217, 164, 65, 0.22);
  --color-border: #2c2822;
  --color-error: #e0796a;
  --firelight-strength: 1;

  color-scheme: dark;
}

/* Manuscript: a warm, aged-paper theme with a subtle fibrous grain — chosen
   by cycling the theme toggle, never auto-selected from system preference. */
:root[data-theme="manuscript"] {
  --color-bg: #ece1c8;
  --color-bg-raised: #e2d4b2;
  --color-text: #3a2c1c;
  --color-text-dim: #5c4a30;
  --color-accent: #8a4a2a;
  --color-accent-text: #fbf1de;
  --color-highlight-bg: rgba(138, 74, 42, 0.2);
  --color-border: #cdba90;
  --color-error: #96382a;
  --firelight-strength: 1.6;
  --paper-texture: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.35 0 0 0 0 0.28 0 0 0 0 0.18 0 0 0 0.09 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  color-scheme: light;
}

/* ---------------------------------------------------------------------- */
/* Base                                                                    */
/* ---------------------------------------------------------------------- */

body {
  background-color: var(--color-bg);
  /* F46: a theme pack may lay something over its background — the orbit
     pack tiles a starfield here. `none` for a pack that doesn't, which is
     every pack that says nothing. */
  background-image: var(--surface-texture, none);
  background-repeat: repeat;
  background-size: var(--surface-texture-size, auto);
  color: var(--color-text);
  font-family: var(--font-sans);
  line-height: 1.5;
  min-height: 100vh;
}

:root[data-theme="manuscript"] body {
  background-color: var(--color-bg);
  background-image:
    radial-gradient(ellipse at center, rgba(58, 44, 28, 0) 45%, rgba(58, 44, 28, 0.08) 100%),
    var(--paper-texture);
  background-repeat: no-repeat, repeat;
  background-size: cover, 240px 240px;
}

/* ---------------------------------------------------------------------- */
/* F44: firelight — the room the veillée happens in                        */
/*                                                                          */
/* A fixed, click-through wash of warm light over the whole page whose      */
/* brightness drifts up and down as if a fire were burning somewhere off    */
/* the screen. Two layers on deliberately mismatched cycles (11s and 7.3s)  */
/* so the combined luminance never settles into a countable beat — a fire   */
/* that pulsed on time would read as a broken animation.                    */
/*                                                                          */
/* Only `opacity` animates, so the whole effect stays on the compositor and */
/* never triggers layout or paint. It is off entirely for anyone who asked  */
/* for reduced motion, and the nav's flame button turns it off for good     */
/* (theme-boot.js applies that choice before first paint).                  */
/*                                                                          */
/* On amplitude: the first cut was so restrained nobody could see it. The   */
/* swing is what reads as fire, not the tint, so the keyframes now dip to a */
/* quarter and the ceiling only rose a little — the page is never much more */
/* orange than before, it just travels much further getting there.          */
/* ---------------------------------------------------------------------- */

.firelight {
  position: fixed;
  inset: 0;
  /* Above the page, below the lightbox (200) and the skip link (100). */
  z-index: 1;
  pointer-events: none;
}

:root[data-firelight="off"] .firelight {
  display: none;
}

.firelight__glow,
.firelight__flicker,
.firelight__shadow {
  position: absolute;
  inset: 0;
  will-change: opacity;
}

.firelight__glow {
  background: radial-gradient(120% 85% at 50% 0%,
    rgba(var(--ambience-glow), calc(0.17 * var(--firelight-strength))),
    rgba(var(--ambience-glow-edge), calc(0.075 * var(--firelight-strength))) 45%,
    rgba(var(--ambience-glow-edge), 0) 74%);
  opacity: 0.7;
}

.firelight__flicker {
  background: radial-gradient(85% 60% at 22% 100%,
    rgba(var(--ambience-glow-edge), calc(0.12 * var(--firelight-strength))),
    rgba(var(--ambience-glow-edge), 0) 68%);
  opacity: 0.6;
}

/* The third layer darkens instead of lighting, and it is the one that
   carries the effect on the pale themes: amber added to cream barely
   changes its brightness, but the corners falling into shadow reads as
   the fire dipping on any background at all. */
.firelight__shadow {
  background: radial-gradient(115% 95% at 50% 20%,
    rgba(var(--ambience-shade), 0) 42%,
    rgba(var(--ambience-shade), calc(0.14 * var(--firelight-strength))) 100%);
  opacity: 0.55;
}

@media (prefers-reduced-motion: no-preference) {
  .firelight__glow {
    animation: firelight-breathe 11s ease-in-out infinite;
  }

  .firelight__flicker {
    animation: firelight-breathe 7.3s ease-in-out infinite reverse;
  }

  /* Counter-phase: the room darkens as the flame drops. */
  .firelight__shadow {
    animation: firelight-breathe 9.1s ease-in-out infinite reverse;
  }
}

@keyframes firelight-breathe {
  0% { opacity: 0.3; }
  14% { opacity: 0.85; }
  27% { opacity: 0.45; }
  41% { opacity: 1; }
  58% { opacity: 0.52; }
  72% { opacity: 0.92; }
  88% { opacity: 0.36; }
  100% { opacity: 0.3; }
}

a {
  color: var(--color-accent);
}

/* Every "‹ Back" link across the app shares this single-class wrapper
   convention (e.g. .people__back, .import__back, .admin__back) — one rule
   covers all of them without touching each template. */
[class$="__back"] a,
.story__back {
  transition: opacity 0.15s ease;
}

[class$="__back"] a:hover,
.story__back:hover {
  text-decoration: underline;
}

mark {
  background: var(--color-highlight-bg);
  color: var(--color-text);
  border-radius: 0.15em;
  padding: 0 0.15em;
}

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

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--color-bg-raised);
  color: var(--color-text);
  padding: 0.75rem 1rem;
  z-index: 100;
}

.skip-link:focus {
  left: 0.5rem;
  top: 0.5rem;
}

/* ---------------------------------------------------------------------- */
/* Nav                                                                     */
/* ---------------------------------------------------------------------- */

.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  gap: 0.5rem 1rem;
}

.site-nav__row {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.site-nav__brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-serif);
  font-size: 1.25rem;
  text-decoration: none;
  color: var(--color-text);
  white-space: nowrap;
}

.site-nav__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}

.site-nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.25rem;
  color: var(--color-text-dim);
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  white-space: nowrap;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 1rem;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s ease;
}

.btn:hover {
  border-color: var(--color-accent);
}

.btn--primary:hover {
  filter: brightness(1.08);
}

@media (prefers-reduced-motion: no-preference) {
  .btn {
    transition: border-color 0.15s ease, transform 0.08s ease;
  }

  .btn:active {
    transform: scale(0.96);
  }
}

.btn-icon {
  width: 20px;
  height: 20px;
  flex: none;
  margin-right: 0.4rem;
}

.btn--new {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.btn--primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-text);
  font-weight: 600;
}

.theme-toggle {
  min-width: 44px;
  min-height: 44px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  cursor: pointer;
  font-size: 1.125rem;
  transition: border-color 0.15s ease;
}

.theme-toggle:hover {
  border-color: var(--color-accent);
}

/* The theme menu (F49). The button is .theme-toggle and behaves like one;
   everything here is the disclosure around it. */
.theme-menu {
  position: relative;
}

/* A <summary> is a list item by default — in every engine, and each with
   its own marker. All three have to go for it to read as the round button
   it is. */
.theme-menu__summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  list-style: none;
  /* Holding a button must not raise iOS's callout or select the glyph
     underneath the finger. */
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

.theme-menu__summary::-webkit-details-marker,
.theme-menu__summary::marker {
  display: none;
  content: "";
}

/* A hold is invisible until you try it, so the button carries a notch in
   its ring: enough to say there is more here, not enough to look like a
   second button. */
.theme-menu__summary::after {
  content: "";
  position: absolute;
  right: 0.3rem;
  bottom: 0.3rem;
  width: 0.3rem;
  height: 0.3rem;
  border-radius: 50%;
  background: var(--color-text-dim);
  opacity: 0.5;
}

.theme-menu[open] .theme-menu__summary::after,
.theme-menu__summary:hover::after {
  opacity: 1;
  background: var(--color-accent);
}

.theme-menu__panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + 0.4rem);
  left: 0;
  min-width: 13rem;
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 0.75rem;
  background: var(--color-bg-raised);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
}

/* The nav's first row starts at the left edge on a phone, so a panel hung
   from the button's left edge would run off the right. */
@media (max-width: 30rem) {
  .theme-menu__panel {
    left: -0.5rem;
    right: auto;
    min-width: 12rem;
    max-width: calc(100vw - 1.5rem);
  }
}

.theme-menu__section {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.theme-menu__heading {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

.theme-menu__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.theme-menu__chip {
  min-height: 36px;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: none;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  cursor: pointer;
}

.theme-menu__chip:hover {
  border-color: var(--color-text-dim);
}

/* Same rule as F45's toggles: the chosen one is not told apart by colour
   alone, and hover must never be able to imitate it. */
.theme-menu__chip[aria-pressed="true"] {
  background: var(--color-highlight-bg);
  border-color: var(--color-accent);
  color: var(--color-accent);
  font-weight: 600;
}

/* Remembering a colour scheme needs localStorage, so with JavaScript off
   these chips could only lie. The packs below them are a cookie and work
   either way, which is why the panel is still worth opening. */
.theme-menu__schemes {
  display: none;
}

.js .theme-menu__schemes {
  display: flex;
}

.theme-menu__make {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  text-decoration: none;
  border-top: 1px solid var(--color-border);
  padding-top: 0.6rem;
}

.theme-menu__make:hover {
  color: var(--color-accent);
}

/* Inside the menu the packs stack, with room for their names — the nav's
   width was the only reason to hide those. */
.theme-menu .pack-picker {
  flex-direction: column;
  align-items: stretch;
  gap: 0.15rem;
}

.theme-menu .pack-picker__btn {
  width: 100%;
  justify-content: flex-start;
}

@media (max-width: 30rem) {
  .theme-menu .pack-picker__name {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    clip-path: none;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .theme-toggle {
    transition: border-color 0.15s ease, transform 0.08s ease;
  }

  .theme-toggle:active {
    transform: scale(0.92);
  }
}

/* F44's firelight switch. It borrows .theme-toggle's shape and only says
   what differs: it carries a flame, and — unlike the theme cycler — it has
   a state to show. Lit is an amber flame in a ring the same colour; unlit
   is the same flame gone cold, smaller and grey. Colour alone would fail
   WCAG 1.4.1, so the ring and the size change carry the state too. These
   rules sit after .theme-toggle deliberately: both are one class deep, so
   order is what decides them. */
.js .firelight-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-accent);
  border-color: var(--color-accent);
}

/* Without JS the switch can't do anything, so it isn't offered at all. */
.firelight-toggle {
  display: none;
}

:root[data-firelight="off"] .firelight-toggle {
  color: var(--color-text-dim);
  border-color: var(--color-border);
}

:root[data-firelight="off"] .firelight-toggle:hover {
  border-color: var(--color-accent);
}

.firelight-toggle__flame {
  display: block;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

:root[data-firelight="off"] .firelight-toggle__flame {
  transform: scale(0.82);
  opacity: 0.7;
}

/* The hollow heart of the flame is the button's own background showing
   through, which is why it's a filled path and not a cut-out. */
.firelight-toggle__core {
  fill: var(--color-bg-raised);
}


/* ---------------------------------------------------------------------- */
/* Flash messages                                                          */
/* ---------------------------------------------------------------------- */

.flash-container {
  padding: 0 1rem;
}

.flash {
  border-radius: 0.5rem;
  padding: 0.75rem 1rem;
  margin: 0.5rem 0;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
}

.flash--error {
  border-color: var(--color-error);
  color: var(--color-error);
}

/* First used by the invite acceptance hand-off (F39): "your account is
   ready, now log in" is the one flash in the app that carries good news. */
.flash--success {
  border-color: var(--color-accent);
}

/* ---------------------------------------------------------------------- */
/* Login page                                                              */
/* ---------------------------------------------------------------------- */

.login {
  max-width: 24rem;
  margin: 3rem auto;
  padding: 0 1.25rem;
  text-align: center;
}

.login__title {
  font-family: var(--font-serif);
  font-size: 2.25rem;
  margin: 0 0 0.25rem;
}

.login__subtitle {
  color: var(--color-text-dim);
  margin: 0 0 2rem;
}

/* A note under a field, explaining what it wants — used by the invite
   acceptance form and the optional invite code on the request form (F39). */
.login__hint {
  color: var(--color-text-dim);
  font-size: 0.8125rem;
  margin: -0.5rem 0 1rem;
}

.login__illo {
  max-width: 20rem;
  width: 100%;
  height: auto;
  margin: 0 auto 2rem;
}

.login__form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  text-align: left;
}

.login__label {
  font-size: 0.875rem;
  color: var(--color-text-dim);
}

/* Shared text-input look: the login field, the timeline search box, the
   story/instant date+relation fields, and the editor's seal-date field
   all render as the same pill-cornered field. Only what genuinely
   differs per field (padding width, alignment, flex sizing) is set on
   the individual selectors below. */
.login__input,
.timeline__search,
.instant__line,
.instant__date,
.editor__relation,
.editor__date,
.editor__unlock,
.editor__tags,
.editor__source-url,
.editor__source-note,
.editor__born,
.editor__died,
.editor__union-since,
.editor__union-until,
.editor__milestone,
.people-picker__search {
  min-height: 44px;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 1rem;
}

.login__input,
.instant__line,
.instant__date,
.editor__relation,
.editor__date,
.editor__unlock,
.editor__tags,
.editor__source-url,
.editor__source-note,
.editor__born,
.editor__died,
.editor__union-since,
.editor__union-until,
.editor__milestone,
.people-picker__search {
  padding: 0 0.75rem;
}

.login__submit {
  margin-top: 0.5rem;
}

.login__request-link {
  margin-top: 1.5rem;
  font-size: 0.875rem;
  color: var(--color-text-dim);
}

.login__textarea {
  padding: 0.6rem 0.75rem;
  min-height: 10rem;
  resize: vertical;
  line-height: 1.5;
}

/* ---------------------------------------------------------------------- */
/* Timeline page                                                           */
/* ---------------------------------------------------------------------- */

.empty-state {
  max-width: 28rem;
  margin: 4rem auto;
  padding: 0 1.25rem;
  text-align: center;
}

.empty-state__illo {
  max-width: 20rem;
  width: 100%;
  height: auto;
  margin: 0 auto 1.5rem;
}

.empty-state p {
  font-family: var(--font-serif);
  font-size: 1.25rem;
  color: var(--color-text-dim);
  margin-bottom: 1.5rem;
}

/* Error pages (F37) reuse the empty-state layout: a heading above the
   message, and a smaller line below it for the "tell whoever set this up"
   detail that a family member should be able to ignore. */
.empty-state__heading {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin-bottom: 0.75rem;
}

.empty-state__hint {
  font-family: var(--font-sans) !important;
  font-size: 0.9375rem !important;
  line-height: 1.5;
  opacity: 0.8;
}

.timeline__onthisday {
  max-width: 40rem;
  margin: 1.25rem auto 0;
  padding: 0 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.timeline__onthisday-item {
  margin: 0;
  padding: 0.6rem 0.85rem;
  border-left: 3px solid var(--author-color, var(--color-accent));
  background: var(--color-bg-raised);
  border-radius: 0 0.4rem 0.4rem 0;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--color-text-dim);
}

.timeline__onthisday-item a {
  color: var(--color-text);
}

.timeline__nudge {
  max-width: 40rem;
  margin: 1.25rem auto 0;
  padding: 0 1.25rem;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 0.9375rem;
  color: var(--color-text-dim);
  text-align: center;
}

.timeline__nudge a {
  color: var(--color-text-dim);
}

.timeline__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  max-width: 40rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

.timeline__legend-chip,
.book-cover__author {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.timeline__legend-dot,
.book-cover__author-dot,
.editor__author-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--author-color, var(--color-accent));
}

.timeline__drafts-link {
  max-width: 40rem;
  margin: 0.35rem auto 0;
  padding: 0 1.25rem;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
}

.timeline__drafts-link a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--color-text-dim);
}

.timeline__toolbar {
  max-width: 40rem;
  margin: 0.75rem auto 0;
  padding: 0 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

.timeline__search {
  flex: 1;
  min-width: 10rem;
  padding: 0 0.85rem;
}

.timeline__jump {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  white-space: nowrap;
}

.timeline__search-empty {
  max-width: 40rem;
  margin: 1rem auto 0;
  padding: 0 1.25rem;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  text-align: center;
}

.timeline {
  position: relative;
  max-width: 40rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 5rem 3.25rem;
}

.timeline__spine {
  position: absolute;
  left: 1.5rem;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--color-border);
}

.timeline__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.timeline__year-marker {
  position: relative;
  padding: 2rem 0 0.5rem;
}

.timeline__year-marker::before {
  content: "";
  position: absolute;
  left: -1.75rem;
  top: 2.5rem;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--color-accent);
  transform: translateX(-5px);
}

.timeline__year-label {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  color: var(--color-accent);
}

.timeline__entry {
  position: relative;
}

.js .timeline__entry {
  opacity: 0;
  transform: translateY(8px);
}

@media (prefers-reduced-motion: no-preference) {
  .js .timeline__entry {
    transition: opacity 0.5s ease, transform 0.5s ease;
  }
}

.timeline__entry.is-visible {
  opacity: 1;
  transform: none;
}

.timeline__link {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  min-height: 44px;
  padding: 0.6rem 0;
  text-decoration: none;
  color: inherit;
}

.timeline__link:hover .timeline__title,
.timeline__link:hover .timeline__instant-line {
  text-decoration: underline;
}

.timeline__dot {
  position: absolute;
  left: -1.75rem;
  top: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--author-color, var(--color-accent));
  transform: translate(-4px, -50%);
}

.timeline__dot--author {
  width: 10px;
  height: 10px;
}

.timeline__date {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-accent);
  white-space: nowrap;
}

.timeline__author {
  color: var(--author-color, var(--color-accent));
}

.timeline__age {
  font-size: 0.75rem;
  color: var(--color-text-dim);
}

/* "kept to a group" on a timeline row (F40 Phase 2) — deliberately quieter
   than the milestone pill next to it: it's a fact about the story, not a
   celebration of one. */
.timeline__audience-pill {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  letter-spacing: 0.03em;
  padding: 0.1em 0.6em;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-dim);
}

.timeline__milestone-pill {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 0.1em 0.6em;
  border-radius: 999px;
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
}

.timeline__title {
  flex: 1;
  font-family: var(--font-serif);
  font-size: 1.15rem;
  color: var(--color-text);
}

.timeline__envelope {
  position: absolute;
  left: -1.85rem;
  top: 50%;
  transform: translate(-4px, -50%);
  color: var(--author-color, var(--color-accent));
  display: inline-flex;
}

.timeline__title--sealed {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-style: italic;
  color: var(--color-text-dim);
}

.timeline__thumb {
  width: 72px;
  height: 72px;
  object-fit: cover;
  border-radius: 0.5rem;
  flex-shrink: 0;
}

.timeline__thumb--instant {
  width: 56px;
  height: 56px;
}

.timeline__instant-line {
  flex: 1;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--color-text-dim);
}

/* R2.1 (REVIEW.md live review round 2): below ~480px the meta line
   (date · author · age) plus a thumbnail can crush the title into a
   one-word-per-line column. Stack the entry — meta, then title at full
   width, then the thumbnail — so the title always gets the reading width.
   R4.7 (round 4): applying this at every width, not just below 480px, also
   fixes desktop's ragged left edge, since every title now starts at the
   same x position instead of trailing a variable-length meta line. */
.timeline__link {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

.timeline__title {
  width: 100%;
}

.minimap {
  position: fixed;
  right: 0.35rem;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  z-index: 10;
}

.minimap__tick {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  color: var(--color-text-dim);
  text-decoration: none;
  padding: 0.15rem 0.35rem;
  border-radius: 0.25rem;
  transition: color 0.15s ease;
}

.minimap__tick:not(.is-active):hover {
  color: var(--color-accent);
}

.minimap__tick.is-active {
  color: var(--color-accent-text);
  background: var(--color-accent);
}

/* R2.2: the fixed right-edge minimap sat on top of .timeline__thumb images
   at phone widths (geometrically confirmed overlap). Hiding it below ~480px
   (rather than reserving a gutter for it) preserves more title width, and
   jump-to-latest/search already cover narrow-viewport navigation. */
@media (max-width: 30rem) {
  .minimap {
    display: none;
  }
}

.timeline__footer-links {
  max-width: 40rem;
  margin: 0 auto;
  padding: 0 1.25rem 2rem;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
}

.timeline__footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--color-text-dim);
}

.timeline__logout-form {
  display: inline;
}

.timeline__logout-button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.25rem;
  border: none;
  background: none;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  text-decoration: underline;
  cursor: pointer;
  transition: color 0.15s ease;
}

.timeline__logout-button:hover {
  color: var(--color-accent);
}

/* ---------------------------------------------------------------------- */
/* Drafts / Archived pages (identical layout, separate templates)          */
/* ---------------------------------------------------------------------- */

.drafts,
.archived {
  max-width: 40rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.drafts__back a,
.archived__back a,
.people__back a,
.import__back a,
.sealed__back a,
.history__back a,
.tree__back a,
.almanac__back a,
.firsts__back a,
.growth__back a,
.help__back a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--color-text-dim);
  text-decoration: none;
}

.drafts__title,
.archived__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0.5rem 0 1.5rem;
}

.drafts__empty,
.archived__empty {
  color: var(--color-text-dim);
}

.drafts__illo,
.archived__illo,
.people__illo {
  max-width: 14rem;
  width: 100%;
  height: auto;
  margin: 0 auto 1rem;
}

.drafts__list,
.archived__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.drafts__item,
.archived__item {
  border-bottom: 1px solid var(--color-border);
}

.drafts__link,
.archived__link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 44px;
  padding: 0.6rem 0;
  text-decoration: none;
  color: inherit;
}

.drafts__dot,
.archived__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--author-color, var(--color-accent));
  flex-shrink: 0;
}

.drafts__item-title,
.archived__item-title {
  flex: 1;
  font-family: var(--font-serif);
  font-size: 1.05rem;
}

.drafts__item-date,
.archived__item-date {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------- */
/* People page (F14)                                                       */
/* ---------------------------------------------------------------------- */

.people {
  max-width: 42rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.people__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 0.5rem 0 1.5rem;
}

.people__header-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.people__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0;
}

.people__empty {
  color: var(--color-text-dim);
}

.people__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
  gap: 1.25rem;
}

.people__card-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
  color: inherit;
  text-align: center;
}

.people__portrait {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 0.75rem;
  object-fit: cover;
}

img.people__portrait {
  filter: var(--photo-filter);
}

.people__portrait--placeholder {
  position: relative;
  display: block;
  overflow: hidden;
}

.people__portrait-oval {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.people__portrait-initial {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--color-text-dim);
  font-family: var(--font-serif);
  font-size: 2rem;
}

.people__name {
  font-family: var(--font-serif);
  font-size: 1rem;
}

.people__relation {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

/* ---------------------------------------------------------------------- */
/* Admin: accounts (F19)                                                   */
/* ---------------------------------------------------------------------- */

.admin {
  max-width: 32rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.admin__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 0.5rem 0 1.5rem;
}

.admin__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0;
}

.admin__subtitle {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  margin: 1.5rem 0 0.75rem;
}

.admin__hint {
  color: var(--color-text-dim);
}

/* The duplicate-account warning on a pending request (F39) — the one place
   in the admin screens where a hint is a caution, not an aside. */
.admin__hint--warn {
  color: var(--color-error);
}

.admin__empty {
  color: var(--color-text-dim);
}

.admin__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.admin__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: var(--color-bg-raised);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
}

.admin__row-name {
  font-weight: 600;
  margin-right: auto;
}

.admin__row-username {
  color: var(--color-text-dim);
  font-size: 0.8125rem;
}

/* A freshly-minted invite or write-link URL, shown exactly once. The token
   is a single ~43-character unbreakable word, so without `overflow-wrap` it
   pushes the whole page sideways on a phone — which is where someone is
   most likely to be copying it into a message. */
.admin__token {
  padding: 0.75rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: var(--color-bg-raised);
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}

/* Inline aside on an account row: a duplicate-name match on a pending
   request, or an invitation's expiry date (F39). */
.admin__row-hint {
  color: var(--color-text-dim);
  font-size: 0.8125rem;
  font-style: italic;
}

.admin__row-status {
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.admin__row-status--disabled {
  color: var(--color-error);
}

.admin__row-action {
  min-height: 36px;
  padding: 0 0.75rem;
}

.admin__row-role-form {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  max-width: 100%;
}

.admin__row-role-form select {
  min-height: 36px;
  padding: 0 0.5rem;
  min-width: 0;
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin__form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.admin__field-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  border: none;
  padding: 0;
  margin: 0;
}

.admin__checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9375rem;
  color: var(--color-text);
}

/* ---------------------------------------------------------------------- */
/* Import page                                                             */
/* ---------------------------------------------------------------------- */

.import {
  max-width: 32rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.import__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0.5rem 0 1rem;
}

.import__intro {
  color: var(--color-text-dim);
  font-size: 0.9375rem;
  margin-bottom: 1.5rem;
}

/* "Your backup will be incomplete" (F40). Must read as a caution rather
   than another paragraph of intro — someone who skims past this is the one
   person who ends up with a partial backup they believe is whole. Declared
   after .import__intro so it wins on equal specificity. */
.import__intro--warn {
  color: var(--color-error);
}

.import__form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
}

.import__submit-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.import__file-picker {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 5rem;
  padding: 0.75rem 1rem;
  border: 2px dashed var(--color-border);
  border-radius: 0.75rem;
  background: var(--color-bg-raised);
  transition: border-color 0.15s ease;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 1rem;
  cursor: pointer;
  text-align: center;
}

.import__file-picker:hover {
  border-color: var(--color-accent);
}

.import__file {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.import__result {
  margin-top: 1rem;
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
}

.import__result--error {
  border-color: var(--color-error);
  color: var(--color-error);
}

/* ---------------------------------------------------------------------- */
/* Story page — the book page                                              */
/* ---------------------------------------------------------------------- */

.page-story .site-nav {
  opacity: 0.35;
  transition: opacity 0.2s ease;
}

.page-story .site-nav:hover,
.page-story .site-nav:focus-within {
  opacity: 1;
}

.story {
  max-width: 42rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.story__header {
  text-align: center;
  margin-bottom: 1.5rem;
}

.story__date {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin: 0 0 0.5rem;
}

.story__author {
  color: var(--author-color, var(--color-accent));
}

.person-life-dates {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  margin: -0.75rem 0 1rem;
}

.story__draft-pill,
.story__archived-pill {
  display: inline-block;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: 0.1em 0.5em;
  border-radius: 999px;
  border: 1px solid var(--color-text-dim);
  color: var(--color-text-dim);
  margin-right: 0.5em;
  vertical-align: middle;
}

.story__milestone {
  margin: 0 0 0.75rem;
}

/* "Kept to Just us" under a story's title (F40 Phase 2). */
.story__audience {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  margin: 0.35rem 0 0;
}

.story__milestone-pill {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 0.2em 0.7em;
  border-radius: 999px;
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
}

.story__author-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--author-color, var(--color-accent));
  margin-right: 0.35em;
}

.story__title {
  font-family: var(--font-serif);
  font-size: clamp(2rem, 5vw, 3rem);
  line-height: 1.15;
  margin: 0 0 1rem;
}

.story__people {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--color-text-dim);
  margin: 0 0 0.5rem;
}

.story__person-link {
  color: inherit;
}

.story__tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.4rem;
  margin: 0 0 0.75rem;
}

.story__tag {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  padding: 0.1em 0.6em;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text-dim);
}

.story__flourish {
  width: 160px;
  height: 23px;
  border: none;
  margin: 0 auto;
  /* Longhands, not the shorthand: a pack may supply several gradient
     layers here instead of one image, and the shorthand would apply the
     positioning to the last layer only. */
  background-image: var(--flourish-image);
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

.story__sources {
  list-style: none;
  margin: 2rem 0 0;
  padding: 0.75rem 0 0;
  border-top: 1px solid var(--color-border);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

.story__sources li + li {
  margin-top: 0.35rem;
}

.story__cover {
  display: block;
  width: 100%;
  border-radius: 0.75rem;
  margin: 1.5rem 0;
  cursor: zoom-in;
}

.page-person .story__cover {
  filter: var(--photo-filter);
}

.story__body {
  font-family: var(--font-serif);
  font-size: 1.125rem;
  line-height: 1.7;
}

@media (min-width: 48rem) {
  .story__body {
    font-size: 1.1875rem;
  }
}

.story__body p {
  margin: 0 0 1.25rem;
  text-wrap: pretty;
}

.story__body p:first-of-type::first-letter {
  font-size: 3.2em;
  line-height: 0.85;
  font-weight: 600;
  float: left;
  margin: 0.05em 0.1em 0 0;
  color: var(--color-accent);
}

/* Language picker (F38). One small form per language so it works without
   JS; laid out as a single row that sits under the brand on a phone and
   inline on a wide screen. */
.lang-picker {
  display: flex;
  gap: 0.25rem;
  align-items: center;
}

.lang-picker__form {
  margin: 0;
  display: flex;
}

.lang-picker__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 44px;
  padding: 0.25rem 0.5rem;
  border: 1px solid transparent;
  border-radius: 0.5rem;
  background: none;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  opacity: 0.65;
}

.lang-picker__btn:hover,
.lang-picker__btn:focus-visible {
  opacity: 1;
}

.lang-picker__btn--active {
  opacity: 1;
  color: var(--color-text);
  border-color: var(--color-rule);
}

.lang-picker__flag {
  display: block;
  border-radius: 2px;
  /* A white flag stripe on a cream page needs an edge to read as a flag. */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18);
}

.lang-picker__code {
  font-weight: 600;
}

/* Theme-pack picker (F48). Deliberately the language picker's shape — it
   sits beside it, does the same kind of thing, and two neighbouring
   controls that behave alike should look alike. */
.pack-picker {
  display: flex;
  gap: 0.25rem;
  align-items: center;
}

.pack-picker__form {
  margin: 0;
  display: flex;
}

.pack-picker__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  padding: 0.25rem 0.5rem;
  border: 1px solid transparent;
  border-radius: 0.5rem;
  background: none;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  opacity: 0.65;
}

.pack-picker__btn:hover,
.pack-picker__btn:focus-visible {
  opacity: 1;
}

.pack-picker__btn--active {
  opacity: 1;
  color: var(--color-text);
  border-color: var(--color-rule);
}

/* The dots overlap like paint chips fanned out, so three colours read as
   one object at a glance rather than as three separate buttons. */
.pack-picker__swatch {
  display: inline-flex;
  align-items: center;
}

.pack-picker__dot {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  /* A near-black dot on a dark page, or a cream one on the manuscript,
     needs an edge to read as a dot at all. */
  box-shadow: 0 0 0 1px var(--color-rule);
}

.pack-picker__dot + .pack-picker__dot {
  margin-left: -0.25rem;
}

.pack-picker__name {
  font-weight: 600;
}

/* On a phone the nav is already carrying a brand, two toggles, the
   language flags and the links — the pack names are the first thing that
   can go, leaving the swatches, which are what you actually aim at. */
@media (max-width: 30rem) {
  .pack-picker__name {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* An image the author mixed into a paragraph stays inline — that's what
   they wrote — but must never break out of the column at its full pixel
   width (F35), and should still look like the photos around it. Photos on
   their own line become figures below instead. */
.story__body img {
  max-width: 100%;
  height: auto;
  border-radius: 0.75rem;
}

/* Added by story.js only to images it actually wired to the lightbox, so
   nothing invites a click that does nothing. */
.story__body img.story__zoomable {
  cursor: zoom-in;
}

.story__body figure {
  margin: 2rem 0;
}

.story__body figure img {
  display: block;
  width: 100%;
  border-radius: 0.75rem;
  cursor: zoom-in;
}

.story__body figcaption {
  font-family: var(--font-sans);
  font-style: italic;
  font-size: 0.875rem;
  color: var(--color-text-dim);
  text-align: center;
  margin-top: 0.5rem;
}

.story__body blockquote {
  margin: 1.5rem 0;
  padding-left: 1rem;
  border-left: 3px solid var(--color-accent);
  color: var(--color-text-dim);
}

.story__body mark {
  padding: 0.05em 0.2em;
}

.story__listen {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
}

/* Shared "eyebrow" label look: small uppercase sans-serif, dimmed to the
   accent color. Only each one's own margin/padding differs below. */
.story__listen-heading,
.person-family__heading,
.editor__family-legend,
.tree__others-heading {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.story__listen-heading {
  margin: 0 0 1rem;
}

.story__memo {
  margin-bottom: 1rem;
}

.story__memo audio {
  width: 100%;
}

.story__transcript {
  margin-top: 0.5rem;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--color-text-dim);
}

.story__transcript summary {
  cursor: pointer;
  color: var(--color-text);
  transition: color 0.15s ease;
}

.story__transcript summary:hover {
  color: var(--color-accent);
}

.story__transcript p {
  margin: 0.5rem 0 0;
}

.person-family {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
}

.person-family__heading {
  margin: 0 0 0.75rem;
}

.person-family__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.person-family__item {
  margin: 0;
}

.person-family__item--union {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}

.person-family__union-note {
  padding-left: 0.25rem;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
}

.person-family__link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0.25rem 0.75rem 0.25rem 0.25rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  color: var(--color-text);
  text-decoration: none;
  background: var(--color-bg-raised);
  transition: border-color 0.15s ease;
}

.person-family__link:hover {
  border-color: var(--color-accent);
}

.person-family__thumb {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}

img.person-family__thumb {
  filter: var(--photo-filter);
}

.person-family__thumb--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-border);
  color: var(--color-text-dim);
  font-family: var(--font-serif);
  font-size: 0.9375rem;
}

.person-family__name {
  font-size: 0.9375rem;
}

.story__footer {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
  font-family: var(--font-sans);
  font-size: 0.875rem;
}

.story__pagenav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1rem;
}

.story__prev,
.story__next {
  color: var(--color-text);
  text-decoration: none;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.story__prev {
  grid-column: 1;
  justify-self: start;
}

.story__random {
  grid-column: 2;
  justify-self: center;
  color: var(--color-text-dim);
  text-decoration: none;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}

.story__next {
  grid-column: 3;
  justify-self: end;
  text-align: right;
}

.story__navrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.story__back,
.story__edit {
  color: var(--color-text-dim);
  text-decoration: none;
  min-height: 44px;
  min-width: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ---------------------------------------------------------------------- */
/* Sealed letter page                                                      */
/* ---------------------------------------------------------------------- */

.sealed {
  max-width: 26rem;
  margin: 5rem auto;
  padding: 0 1.25rem;
  text-align: center;
}

.sealed__illo {
  max-width: 18rem;
  width: 100%;
  height: auto;
  margin: 0 auto 1.5rem;
}

.sealed__label {
  font-family: var(--font-serif);
  font-size: 1.5rem;
  margin: 0 0 0.5rem;
}

.sealed__opens {
  color: var(--color-text-dim);
  margin: 0 0 2rem;
}

/* ---------------------------------------------------------------------- */
/* Lightbox (F7)                                                           */
/* ---------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.92);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 1.5rem;
  z-index: 200;
  cursor: zoom-out;
}

@media (prefers-reduced-motion: no-preference) {
  .lightbox {
    animation: lightbox-fade-in 150ms ease;
  }
}

@keyframes lightbox-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.lightbox__close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  min-width: 44px;
  min-height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(232, 226, 217, 0.35);
  background: rgba(255, 255, 255, 0.08);
  color: #f2ede4;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.lightbox__close:hover {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(232, 226, 217, 0.6);
}

.lightbox__image {
  max-width: 100%;
  max-height: 85vh;
  object-fit: contain;
}

.lightbox__caption {
  color: var(--color-text);
  font-family: var(--font-sans);
  font-style: italic;
  font-size: 0.875rem;
  text-align: center;
  margin: 0;
}

/* ---------------------------------------------------------------------- */
/* In-app camera (F34)                                                     */
/* ---------------------------------------------------------------------- */

.camera {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.94);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 1.25rem;
  z-index: 200;
}

.camera__stage {
  flex: 1;
  width: 100%;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.camera__video,
.camera__still {
  max-width: 100%;
  max-height: 100%;
  border-radius: 0.5rem;
  object-fit: contain;
  background: #000;
}

.camera__message {
  margin: 0;
  max-width: 24rem;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: #f2ede4;
}

.camera__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.camera__btn {
  min-height: 44px;
}

/* The overlay is always dark, whatever the page theme, so the secondary
   buttons need light-on-dark of their own rather than .btn's page colors. */
.camera__btn:not(.btn--primary) {
  border-color: rgba(232, 226, 217, 0.35);
  background: rgba(255, 255, 255, 0.08);
  color: #f2ede4;
}

.camera__btn:not(.btn--primary):hover {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(232, 226, 217, 0.6);
}

/* ---------------------------------------------------------------------- */
/* Book view (F10)                                                         */
/* ---------------------------------------------------------------------- */

.book-cover {
  min-height: 60vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1.25rem;
}

.book-cover__frame-wrap {
  position: relative;
  width: 100%;
  max-width: 24rem;
  margin: 0 auto;
}

.book-cover__frame {
  display: block;
  width: 100%;
  height: auto;
}

/* The frame's opening is baked into the JPEG as the same cream as every
   other illustration, regardless of site theme — so the overlaid text is
   a fixed dark ink color rather than the theme's --color-text, exactly
   like the lightbox close button is fixed against its always-dark backdrop.
   The inset itself is narrower than the frame's outer bounding box
   suggests: book-frame.jpg's actual rope-free interior (measured directly
   from the image pixels, not eyeballed) is only ~18.3%-20.7% inset
   horizontally and ~14.5%-16.2% vertically at the height this text sits
   at, since the rope curves inward unevenly. 20% horizontal clears the
   tightest sampled point with a small safety margin. */
.book-cover__content {
  position: absolute;
  inset: 22% 20%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.5rem;
  overflow: hidden;
  color: #2a2520;
}

.book-cover__title {
  font-family: var(--font-serif);
  font-size: clamp(1.125rem, 4.2vw, 1.75rem);
  margin: 0;
  color: inherit;
}

/* Fixed-size text in this box stays a constant absolute width while the
   frame (and the .book-cover__content box scaling with it) shrinks on
   narrower viewports, so a long custom STORYBOOK_TITLE/date range/author
   name grazes or crosses the decorative frame right where the box is
   smallest — matching .book-cover__title's existing clamp() so every
   line here scales down together with the frame instead of just the
   title. */
.book-cover__range {
  font-size: clamp(0.8125rem, 3.2vw, 1rem);
  color: inherit;
  opacity: 0.75;
  margin: 0;
}

.book-cover__authors {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.35rem 1rem;
  margin-top: 0.5rem;
  font-family: var(--font-sans);
  font-size: clamp(0.6875rem, 2.6vw, 0.8125rem);
  color: inherit;
  opacity: 0.75;
}

.book__ornament {
  display: block;
  width: 240px;
  height: auto;
  margin: 0.5rem auto;
}

.book__year-chapter {
  text-align: center;
  padding: 3rem 0 1.5rem;
}

.book__year-chapter-title {
  font-family: var(--font-serif);
  font-size: 2.5rem;
  margin: 0;
  color: var(--color-text);
}

.book__year-chapter-age {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--color-text-dim);
  margin: 0.35rem 0 0;
}

.book__year-chapter-flourish {
  margin: 1.25rem auto 0;
}

.book__instant {
  max-width: 42rem;
  margin: 1.5rem auto;
  padding: 0 1.25rem;
  text-align: center;
}

.book__instant-photo {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 0 auto 0.5rem;
  border-radius: 0.5rem;
}

.book__instant-caption {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--color-text-dim);
}

.book__print-btn {
  display: inline-flex;
  align-items: center;
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  min-height: 44px;
  padding: 0 1.25rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 0.875rem;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  transition: border-color 0.15s ease;
}

.book__print-btn:hover {
  border-color: var(--color-accent);
}

@media (prefers-reduced-motion: no-preference) {
  .book__print-btn {
    transition: border-color 0.15s ease, transform 0.08s ease;
  }

  .book__print-btn:active {
    transform: scale(0.96);
  }
}

/* ---------------------------------------------------------------------- */
/* Instant capture page (F13)                                              */
/* ---------------------------------------------------------------------- */

.instant {
  max-width: 28rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.instant__illo {
  display: block;
  max-height: 9rem;
  width: auto;
  margin: 0 auto 0.75rem;
}

@media (max-height: 700px) {
  .instant__illo {
    display: none;
  }
}

.instant__photo-picker {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 12rem;
  border: 2px dashed var(--color-border);
  border-radius: 0.75rem;
  background: var(--color-bg-raised);
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 1rem;
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.15s ease;
}

.instant__photo-picker:hover {
  border-color: var(--color-accent);
}

.instant__photo-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.instant__photo-picker.has-photo .instant__photo-label {
  display: none;
}

.instant__file {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.instant__camera {
  align-self: flex-start;
}

.instant__save-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

.instant__save {
  flex: 1;
}

/* ---------------------------------------------------------------------- */
/* Editor page                                                             */
/* ---------------------------------------------------------------------- */

.editor {
  max-width: 42rem;
  margin: 0 auto;
  padding: 1.25rem 1.25rem 6rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.editor__recovery {
  max-width: 42rem;
  margin: 1rem auto 0;
  padding: 0.85rem 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  border: 1px solid var(--color-accent);
  border-radius: 0.5rem;
  background: var(--color-bg-raised);
  font-family: var(--font-sans);
  font-size: 0.875rem;
}

.editor__prompt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 0.9375rem;
  color: var(--color-text-dim);
}

.editor__prompt-cycle {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border: none;
  background: none;
  color: var(--color-text-dim);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  border-radius: 50%;
}

.editor__prompt-cycle:hover {
  color: var(--color-accent);
}

.editor__recovery-text {
  margin: 0;
}

.editor__recovery-actions {
  display: flex;
  gap: 0.5rem;
}

.editor__title {
  border: none;
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-serif);
  font-size: 1.75rem;
  padding: 0.25rem 0;
  border-bottom: 1px solid var(--color-border);
}

.editor__title::placeholder {
  color: var(--color-text-dim);
}

.editor__relation::placeholder {
  color: var(--color-text-dim);
}

.editor__date {
  align-self: flex-start;
}

.editor__date-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.editor__unlock-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.editor__unlock-label {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  white-space: nowrap;
}

.editor__toolrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.editor__authors {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.editor__toggles {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-left: auto;
}

/* Only the properties that differ from .btn are set here (templates apply
   both classes) — pill shape, tighter padding/font-size, and a dimmer
   default text color that brightens to the accent when pressed, matching
   the .tree__view-btn/.book__print-btn pattern elsewhere in this file. */
.editor__toggle-chip {
  padding: 0 0.85rem;
  border-radius: 999px;
  color: var(--color-text-dim);
  font-size: 0.875rem;
}

.editor__toggle-chip[aria-pressed="true"] {
  /* The fill is what makes "on" unmistakable. Border and text colour alone
     were not enough: `.btn:hover` also borrows the accent for its border,
     so a chip you had just switched off went on looking lit for as long as
     the pointer stayed on it — and on a touch screen `:hover` sticks to the
     last thing tapped, so it looked lit until you tapped somewhere else.
     Hover can change a border; only a press fills the pill. */
  background: var(--color-highlight-bg);
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* The other half of the same fix: on a toggle the accent *means* on, so
   hovering one that is off must not reach for it. These stop at a neutral
   border instead — the hover feedback F23 asked for, in a colour that
   can't be mistaken for a state. */
.editor__toggle-chip[aria-pressed="false"]:hover,
.editor__gender-btn[aria-pressed="false"]:hover,
.editor__author-chip[aria-pressed="false"]:hover {
  border-color: var(--color-text-dim);
}

/* Who a story is kept to (F40 Phase 2). The chips reuse the toggle-chip
   look; the state line under them is the part that matters, because "no
   chips lit" and "everyone can read this" have to read as the same thing. */
.editor__audience {
  margin: 0.75rem 0;
}

.editor__audience-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.editor__audience-state {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  margin: 0.5rem 0 0;
}

.editor__audience-state--scoped {
  color: var(--color-accent);
}

.editor__author-chip {
  gap: 0.4rem;
  padding: 0 0.85rem;
  border-radius: 999px;
  font-size: 0.875rem;
}

.editor__author-chip[aria-pressed="true"] {
  border-color: var(--author-color, var(--color-accent));
  color: var(--author-color, var(--color-accent));
}

.editor__photo {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 1rem 0;
}

.editor__photo-preview {
  position: relative;
  width: 100%;
  max-width: 16rem;
  aspect-ratio: 1 / 1;
  border-radius: 0.75rem;
  overflow: hidden;
  border: 1px dashed var(--color-border);
  background: var(--color-bg-raised);
  display: flex;
  align-items: center;
  justify-content: center;
}

.editor__photo-placeholder {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.875rem;
}

.editor__photo-preview-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--photo-filter);
  pointer-events: none;
}

.editor__photo-cropper {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-width: 16rem;
}

.editor__photo-cropper-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 0.75rem;
  background: #000;
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.editor__photo-cropper-stage:active {
  cursor: grabbing;
}

.editor__photo-cropper-img {
  position: absolute;
  pointer-events: none;
}

.editor__photo-cropper-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid #8a7a5f;
  box-shadow: 0 0 0 9999px rgba(20, 18, 16, 0.6);
  pointer-events: none;
}

.editor__photo-zoom-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.editor__photo-zoom-controls input[type="range"] {
  flex: 1;
}

.editor__photo-zoom-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.15s ease;
}

.editor__photo-zoom-btn:hover {
  border-color: var(--color-accent);
}

.editor__photo-cropper-hint {
  margin: 0;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
}

.editor__photo-cropper-actions {
  display: flex;
  gap: 0.5rem;
}

.editor__photo-file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.editor__photo-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.editor__photo-upload-btn {
  align-self: flex-start;
}

.editor__photo-upload-btn[aria-disabled="true"] {
  opacity: 0.6;
  pointer-events: none;
}

.editor__photo-sepia-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  max-width: 16rem;
}

.editor__photo-sepia-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.editor__photo-sepia-controls input[type="range"] {
  flex: 1;
}

.editor__photo-sepia-controls input[type="number"] {
  width: 4rem;
}

.editor__family {
  margin: 0;
  padding: 0;
  border: none;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.editor__family-legend {
  padding: 0;
}

.editor__family-group {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.editor__family-label {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

/* F42: .btn-icon needs a flex parent (F22), and only the audience label
   carries one — the other .editor__family-label uses stay plain text. */
.editor__audience-label {
  display: flex;
  align-items: center;
}

.editor__author-color {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.editor__author-color input[type="color"] {
  width: 3.5rem;
  height: 2.5rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  cursor: pointer;
}

.people-picker {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.people-picker__list {
  display: flex;
  flex-direction: column;
  max-height: 14rem;
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
}

.people-picker__row {
  justify-content: flex-start;
  width: 100%;
  min-height: 44px;
  gap: 0.5rem;
  border: none;
  border-radius: 0;
  border-bottom: 1px solid var(--color-border);
  white-space: normal;
  text-align: left;
}

.people-picker__row:last-child {
  border-bottom: none;
}

.people-picker__row[hidden] {
  display: none;
}

.people-picker__row[aria-pressed="true"] {
  color: var(--color-accent);
}

.people-picker__row:hover {
  background: var(--color-border);
  border-color: var(--color-border);
}

.people-picker__row:active {
  transform: none;
}

.people-picker__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.people-picker__check {
  flex: none;
  visibility: hidden;
  color: var(--color-accent);
}

.people-picker__row[aria-pressed="true"] .people-picker__check {
  visibility: visible;
}

.family-chip__thumb {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}

img.family-chip__thumb {
  filter: var(--photo-filter);
}

.family-chip__thumb--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-border);
  color: var(--color-text-dim);
  font-family: var(--font-serif);
  font-size: 0.75rem;
}

.editor__tags,
.editor__milestone {
  width: 100%;
}

.editor__life-dates {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.editor__life-dates .editor__family-group {
  flex: 1 1 10rem;
}

.editor__unions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.editor__unions-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.editor__union-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.editor__union-partner,
.editor__union-kind {
  min-height: 44px;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  padding: 0 0.5rem;
}

.editor__union-since,
.editor__union-until {
  flex: 1 1 8rem;
  min-width: 0;
}

.editor__union-remove {
  flex: none;
  padding: 0 0.6rem;
  color: var(--color-text-dim);
}

.editor__unions-message {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

.editor__sources {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.editor__sources-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.editor__source-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.editor__source-url {
  flex: 2 1 12rem;
  min-width: 0;
}

.editor__source-note {
  flex: 1 1 8rem;
  min-width: 0;
}

.editor__source-remove {
  flex: none;
  padding: 0 0.6rem;
  color: var(--color-text-dim);
}

.editor__family-hint {
  margin: 0;
  padding: 0.75rem 0.9rem;
  border: 1px dashed var(--color-border);
  border-radius: 0.4rem;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

.editor__gender-segmented {
  display: flex;
  gap: 0.5rem;
}

.editor__gender-btn {
  min-width: 44px;
  padding: 0 0.85rem;
  border-radius: 999px;
  color: var(--color-text-dim);
  font-size: 0.875rem;
}

/* Same three-part lit state as .editor__toggle-chip, and for the same
   reason — see the note there. */
.editor__gender-btn[aria-pressed="true"] {
  background: var(--color-highlight-bg);
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.editor__root {
  margin-top: 0.5rem;
}

.editor__source {
  width: 100%;
  min-height: 40vh;
  display: none;
  padding: 0.85rem;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  resize: vertical;
}

.editor__source--visible {
  display: block;
}

.editor__fallback-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

.editor__fallback-btn {
  min-height: 44px;
  padding: 0 0.75rem;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 0.875rem;
  cursor: pointer;
  transition: border-color 0.15s ease;
}

.editor__fallback-btn:hover {
  border-color: var(--color-accent);
}

.editor__fallback-file-input {
  display: none;
}

.editor__camera {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
}

.editor__camera-heading {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

.editor__camera-message {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--color-text-dim);
}

.editor__voice {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
}

.editor__voice-heading {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

.editor__voice-recorder {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.editor__voice-timer {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--color-text-dim);
}

/* The recording level (F47). Decorative on its own — what it is for is
   letting someone see that the microphone has gone dead while they are
   still talking, which no wording on the page can do. */
.editor__voice-meter {
  flex: 1;
  min-width: 4rem;
  max-width: 10rem;
  height: 0.375rem;
  border-radius: 999px;
  background: var(--color-border);
  overflow: hidden;
}

.editor__voice-meter-fill {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--color-accent);
  transition: width 80ms linear;
}

@media (prefers-reduced-motion: reduce) {
  .editor__voice-meter-fill {
    transition: none;
  }
}

.editor__voice-message {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--color-text-dim);
}

.editor__voice-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.editor__voice-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.editor__voice-item audio {
  flex: 1;
  min-width: 12rem;
}

.editor__save-bar {
  position: sticky;
  bottom: 0;
  padding: 0.75rem 0;
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

.editor__save {
  flex: 1;
}

.editor__save-message {
  margin: 0.5rem 0 0;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--color-error);
}

@media (min-width: 40rem) {
  .editor__save-bar {
    justify-content: flex-end;
  }

  .editor__save {
    flex: none;
    width: auto;
    padding: 0 2rem;
  }

  .editor__save-message {
    margin: 0;
    flex-basis: 100%;
    text-align: right;
  }
}

.editor__history-link {
  max-width: 42rem;
  margin: 1rem auto 0;
  padding: 0 1.25rem;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
}

.editor__history-link a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--color-text-dim);
}

/* ---------------------------------------------------------------------- */
/* Version history page                                                    */
/* ---------------------------------------------------------------------- */

.history {
  max-width: 40rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.history__title {
  font-family: var(--font-serif);
  font-size: 1.5rem;
  margin: 0.5rem 0 1.5rem;
}

.history__empty {
  color: var(--color-text-dim);
}

.history__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.history__item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 44px;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--color-border);
}

.history__item-title {
  flex: 1;
  font-family: var(--font-serif);
  font-size: 1rem;
}

.history__item-date {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------- */
/* Manuscript theme: content sits on a raised, grained "page" over the     */
/* textured desk background set on <body> above.                          */
/* ---------------------------------------------------------------------- */

:root[data-theme="manuscript"] .story,
:root[data-theme="manuscript"] .timeline,
:root[data-theme="manuscript"] .editor,
:root[data-theme="manuscript"] .login {
  background-color: var(--color-bg-raised);
  background-image: var(--paper-texture);
  background-size: 240px 240px;
  border-radius: 0.25rem;
  box-shadow: 0 1px 2px rgba(58, 44, 28, 0.12), 0 12px 28px rgba(58, 44, 28, 0.14);
  margin-top: 1.5rem;
  margin-bottom: 1.5rem;
}

:root[data-theme="manuscript"] .editor__save-bar {
  background: transparent;
}

/* ---------------------------------------------------------------------- */
/* Family tree (F18) — the vendored family-chart cards, ranch-restyled.    */
/* ---------------------------------------------------------------------- */

.tree {
  display: flex;
  flex-direction: column;
}

.tree__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0.5rem 0 1rem;
}

.tree__flourish {
  margin: 0 auto 1.25rem;
}

.tree__illo {
  max-width: 16rem;
  width: 100%;
  height: auto;
  margin: 1.5rem auto 0;
}

/* The chart sits on the ranch survey map (R5.7): dark leather by default
   (matching the token blocks' dark-first cascade), parchment in the light
   and manuscript themes. The container carries only the base tone — the
   grid itself is an SVG pattern injected inside the chart's pan/zoom
   group (tree.js), so it moves and scales with the tree instead of
   sitting still behind it like the old JPEG background did. */
.tree__chart {
  position: relative;
  width: 100%;
  height: 58vh;
  min-height: 22rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  overflow: hidden;
  background-color: #211b15;
  color: var(--color-text-dim);
}

/* Pinned over the top-right corner of the map (tree.js), so a reader who
   has panned/zoomed away can get back without reloading the page. Only
   the properties that differ from .btn are set here — flex-centering,
   font and cursor come from the shared class (tree.js applies both). The
   translucent border/background are bespoke rather than the flat
   --color-* tokens, since this floats over the moving map image and
   needs its own contrast. */
.tree__recenter-btn {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 5;
  min-height: 36px;
  padding: 0 0.75rem;
  border-radius: 999px;
  border-color: rgba(236, 224, 200, 0.35);
  background: rgba(33, 27, 21, 0.72);
  color: #f4ecdb;
  font-size: 0.8125rem;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .tree__recenter-btn {
    border-color: rgba(92, 74, 51, 0.3);
    background: rgba(244, 236, 219, 0.78);
    color: #2c261e;
  }
}

:root[data-theme="light"] .tree__recenter-btn,
:root[data-theme="manuscript"] .tree__recenter-btn {
  border-color: rgba(92, 74, 51, 0.3);
  background: rgba(244, 236, 219, 0.78);
  color: #2c261e;
}

@media (min-width: 700px) {
  .tree__chart {
    height: 70vh;
  }
}

/* Levels ≥ 1 replace the single chart with one small hourglass per
   ancestor couple at that depth (paternal/maternal branches, etc.), so
   they can all be read together instead of toggled one at a time. Each
   panel reuses .tree__chart's map/border/background styling (it carries
   both classes) — only the height shrinks here to fit several on screen.
   Stacked on phones; a two-up grid once there's room. */
.tree__panels {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

@media (min-width: 700px) {
  .tree__panels {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  }
}

.tree__panel-wrap {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.tree__panel-label {
  margin: 0;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.tree__panels .tree__chart {
  height: 40vh;
  min-height: 16rem;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .tree__chart {
    background-color: #f4ecdb;
  }
}

:root[data-theme="light"] .tree__chart,
:root[data-theme="manuscript"] .tree__chart {
  background-color: #f4ecdb;
}

/* View scopes: Direct line / Grandparents' branch / … / Whole family,
   built by tree.js from the ancestor levels that actually exist. */
.tree__views {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
}

.tree__views-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Only the properties that differ from .btn are set here (tree.js
   applies both classes) — pill shape, tighter padding/font-size, and a
   dimmer default text color that brightens to the accent when pressed. */
.tree__view-btn {
  padding: 0 0.85rem;
  border-radius: 999px;
  color: var(--color-text-dim);
  font-size: 0.875rem;
}

.tree__view-btn[aria-pressed="true"] {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.tree__empty {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  padding: 2rem 0;
  text-align: center;
}

.tree__others {
  margin-top: 2rem;
}

.tree__others-heading {
  margin: 0 0 0.75rem;
}

.tree__others-list,
.tree__print-outline-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.tree__others-item,
.tree__print-outline-item {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
}

.tree__others-item a {
  color: var(--color-text);
}

.tree__others-note {
  color: var(--color-text-dim);
  margin-left: 0.4rem;
}

.tree__others-note a {
  color: var(--color-text-dim);
}

/* Hidden on screen — the interactive chart/panels cover this ground
   there. Shown only in @media print below, as a plain-text stand-in for
   the chart (which can't survive print): one heading per generation,
   oldest first, each name with its computed kinship label. Structural
   rules live here unconditionally rather than inside the print query —
   harmless since the whole section is display:none until print anyway,
   and it keeps the print query itself down to the visibility flip. */
.tree__print-outline {
  display: none;
}

.tree__print-outline-group {
  break-inside: avoid;
  margin-bottom: 1.25rem;
}

.tree__print-outline-heading {
  font-family: var(--font-serif);
  font-size: 1.0625rem;
  margin: 0 0 0.4rem;
}

/* The vendor stylesheet paints card faces from these variables (and it
   loads after main.css, so plain background overrides lose). Point them
   at the fixed paper values — cream cards in every theme (R5.1). */
.page-tree .f3 {
  --female-color: #f7f1e2;
  --male-color: #f7f1e2;
  --genderless-color: #f7f1e2;
  --background-color: #f7f1e2;
  --text-color: #2c261e;
  font-family: var(--font-sans);
}

/* Paper name-tag cards (R5.1): cream in every theme, like the F17 paper
   cards — on dark they read as tags pinned to the leather map. */
.page-tree .f3 div.card-inner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 12rem;
  padding: 0.4rem 0.6rem;
  position: relative;
  background: #f7f1e2;
  border: 1.5px solid #8a7a5f;
  border-radius: 0.45rem;
  outline: none !important;
  box-shadow: 0 2px 4px rgba(40, 30, 15, 0.28);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.page-tree .f3 div.card-inner:hover {
  border-color: #6b5d45;
  box-shadow: 0 3px 8px rgba(40, 30, 15, 0.38);
}

/* In rooted views (Grandparents' branch, Whole family) the gold brand
   follows the new root card, so the focus person gets this thinner gold
   ring to stay findable. Applied by tree.js's card HTML creator. Needs
   the redundant `div.card-inner` in the selector — matching specificity
   to the plain card-inner rule above, not just .card-inner--focus alone
   — or that rule's own border/box-shadow always wins the cascade and
   the ring never actually shows. Same reasoning extends to its own
   :hover — it must repeat the gold border or the plain-card :hover
   above would win it back on cursor-over. */
.page-tree .f3 div.card-inner.card-inner--focus {
  border-color: #b3801f;
  box-shadow: 0 0 0 2px rgba(179, 128, 31, 0.22),
              0 2px 4px rgba(40, 30, 15, 0.28);
}

.page-tree .f3 div.card-inner.card-inner--focus:hover {
  border-color: #b3801f;
  box-shadow: 0 0 0 2px rgba(179, 128, 31, 0.22),
              0 3px 8px rgba(40, 30, 15, 0.38);
}

/* The anchor's gold brand ring (R5.2) — fixed gold, legible against the
   always-cream card face rather than the theme background. */
.page-tree .f3 div.card-main .card-inner {
  border: 2px solid #b3801f;
  box-shadow: 0 0 0 3px rgba(179, 128, 31, 0.28),
              0 2px 5px rgba(40, 30, 15, 0.32);
}

.page-tree .f3 div.card-main .card-inner:hover {
  border-color: #b3801f;
  box-shadow: 0 0 0 3px rgba(179, 128, 31, 0.28),
              0 3px 8px rgba(40, 30, 15, 0.38);
}

/* The anchor's brand stamp (R5.9), breaking the card's top-right corner.
   It follows whichever card becomes .card-main on re-rooting. */
.page-tree .f3 div.card-main .card-inner::after {
  content: "";
  position: absolute;
  top: -12px;
  right: -10px;
  width: 28px;
  height: 28px;
  background-image: var(--brand-mark);
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

.page-tree .f3 .f3-card-avatar {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  border: 1.5px solid #8a7a5f;
  background: #efe6cf;
}

.page-tree .f3 .f3-card-avatar--photo {
  filter: var(--photo-filter);
}

.page-tree .f3 .f3-card-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 0.05rem;
}

.page-tree .f3 .f3-card-name {
  font-family: var(--font-serif);
  font-size: 0.875rem;
  color: #2c261e;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The computed kinship label ("your uncle"), only present when
   STORYBOOK_CHILD is configured and the person is reachable from it —
   see kinship_label() in app/kinship.py. */
.page-tree .f3 .f3-card-kinship {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #8a7a5f;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Saddle-stitch connectors (R5.4): dark-first like the theme tokens,
   lifted slightly lighter on the dark leather map. */
.page-tree .f3 .link {
  stroke: #a08b66;
  stroke-width: 1.6px;
  stroke-dasharray: 7 4;
  stroke-linecap: round;
  opacity: 0.95;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .page-tree .f3 .link {
    stroke: #8a7a5f;
  }
}

:root[data-theme="light"] .page-tree .f3 .link,
:root[data-theme="manuscript"] .page-tree .f3 .link {
  stroke: #8a7a5f;
}

.page-tree .f3 .mini-tree rect.card-male,
.page-tree .f3 .mini-tree rect.card-female {
  fill: var(--color-accent);
}

/* Marks a person who appears twice within a single family-chart instance
   (Direct line, or a branch panel) — pedigree collapse, e.g. a
   first-cousin marriage putting the same shared grandparent on two
   different lines of one person's own ancestry. Unrelated to the
   "Everyone" view (tree-graph-logic.js's layout never duplicates
   anyone); this is family-chart's own node.duplicate flag, still
   surfaced by cardInnerHtml wherever family-chart itself is used. */
.page-tree .f3 .f3-card-duplicate {
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-style: italic;
  color: #a08b66;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The "Everyone" view: a from-scratch graph (tree-graph-logic.js's
   layout, rendered by tree.js's renderFamilyGraph) rather than
   family-chart — every person gets exactly one card, however many
   marriages connect them to the rest of the family, so none of the
   .f3/.card-inner rules above apply here. SVG for the connector lines,
   plain HTML for cards, both repositioned by tree.js on every zoom/pan
   event rather than by CSS layout — the rules below are sizing and
   typography only. Reuses .tree__chart for the container chrome
   (leather/parchment background, border, height) — tree.js sets both
   classes on the mount element. */
.tree-graph {
  position: relative;
}

.tree-graph__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.tree-graph__cards {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  /* Empty space stays pannable (drag reaches the SVG underneath); only
     an actual card should intercept the click. */
  pointer-events: none;
}

.tree-graph__card {
  position: absolute;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  background: #f7f1e2;
  border: 1.5px solid #8a7a5f;
  border-radius: 0.45rem;
  box-shadow: 0 2px 4px rgba(40, 30, 15, 0.28);
  cursor: pointer;
  pointer-events: auto;
}

/* The anchor's gold ring (matches the family-chart views' focus-card
   treatment), so the person /tree is about stays findable in a chart
   with everyone else in it too. */
.tree-graph__card--focus {
  border-color: #b3801f;
  box-shadow: 0 0 0 2px rgba(179, 128, 31, 0.22),
              0 2px 4px rgba(40, 30, 15, 0.28);
}

.tree-graph__avatar {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  border: 1.5px solid #8a7a5f;
  background: #efe6cf;
}

.tree-graph__avatar--photo {
  filter: var(--photo-filter);
}

.tree-graph__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: 0.05rem;
}

.tree-graph__name {
  font-family: var(--font-serif);
  font-size: 0.875rem;
  color: #2c261e;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tree-graph__kinship {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #8a7a5f;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Parent-child edges: dashed, matching the family-chart views' own
   saddle-stitch connectors. Partner edges: solid, so a marriage reads
   differently at a glance from a generation drop-line. */
.tree-graph__link {
  fill: none;
  stroke: #a08b66;
  stroke-width: 1.6px;
  stroke-dasharray: 7 4;
  stroke-linecap: round;
  opacity: 0.95;
}

.tree-graph__link--partner {
  stroke-dasharray: none;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .tree-graph__link {
    stroke: #8a7a5f;
  }
}

:root[data-theme="light"] .tree-graph__link,
:root[data-theme="manuscript"] .tree-graph__link {
  stroke: #8a7a5f;
}

/* ---------------------------------------------------------------------- */
/* Almanac (F27) — the family's recurring life dates, month by month       */
/* ---------------------------------------------------------------------- */

.almanac {
  display: flex;
  flex-direction: column;
}

.almanac__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0.5rem 0 1rem;
}

.almanac__flourish {
  margin: 0 auto 1.25rem;
}

.almanac__empty {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  padding: 2rem 0;
  text-align: center;
}

.almanac__month {
  margin-bottom: 1.5rem;
}

.almanac__month-heading {
  font-family: var(--font-serif);
  font-size: 1.0625rem;
  margin: 0 0 0.5rem;
}

.almanac__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.almanac__item {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
}

.almanac__day {
  flex: none;
  width: 1.75rem;
  color: var(--color-text-dim);
  text-align: right;
}

.almanac__desc a {
  color: var(--color-text);
}

/* ---------------------------------------------------------------------- */
/* Firsts (F28) — the register of firsts, in the order they happened       */
/* ---------------------------------------------------------------------- */

.firsts {
  display: flex;
  flex-direction: column;
}

.firsts__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0.5rem 0 1rem;
}

.firsts__flourish {
  margin: 0 auto 1.25rem;
}

.firsts__empty {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  padding: 2rem 0;
  text-align: center;
}

.firsts__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.firsts__link {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  text-decoration: none;
  transition: border-color 0.15s ease;
}

.firsts__link:hover {
  border-color: var(--color-accent);
}

.firsts__milestone {
  font-family: var(--font-serif);
  font-size: 1.0625rem;
  color: var(--color-text);
}

.firsts__date {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-accent);
}

.firsts__story-title {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

/* ---------------------------------------------------------------------- */
/* Growing up (F29) — the photo nearest each birthday, one per year        */
/* ---------------------------------------------------------------------- */

.growth {
  display: flex;
  flex-direction: column;
}

.growth__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0.5rem 0 1rem;
}

.growth__flourish {
  margin: 0 auto 1.25rem;
}

.growth__empty {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  padding: 2rem 0;
  text-align: center;
}

.growth__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 1rem;
}

.growth__card-link {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  text-decoration: none;
}

.growth__photo {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  filter: var(--photo-filter);
}

.growth__age {
  font-family: var(--font-serif);
  font-size: 0.9375rem;
  color: var(--color-text);
}

.growth__caption {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--color-text-dim);
}

/* ---------------------------------------------------------------------- */
/* Help (F33) — a plain-language guide for the family, not developers      */
/* ---------------------------------------------------------------------- */

.help {
  max-width: 40rem;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

.help__title {
  font-family: var(--font-serif);
  font-size: 1.75rem;
  margin: 0.5rem 0 1rem;
}

.help__flourish {
  margin: 0 auto 1.5rem;
}

.help__intro {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  margin-bottom: 2rem;
}

.help__section {
  margin-bottom: 1.75rem;
}

.help__section h2 {
  font-family: var(--font-serif);
  font-size: 1.1875rem;
  margin: 0 0 0.5rem;
}

.help__section p {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  line-height: 1.6;
  margin: 0 0 0.75rem;
}

.help__section p:last-child {
  margin-bottom: 0;
}

/* F42: the help page is a glossary, not an essay — one term, one line. */
.help__defs {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  line-height: 1.55;
  margin: 0 0 0.75rem;
}

.help__defs dt {
  font-weight: 600;
  margin-top: 0.7rem;
}

.help__defs dt:first-child {
  margin-top: 0;
}

.help__defs dd {
  color: var(--color-text-dim);
  margin: 0.1rem 0 0;
}

.help__defs + p {
  margin-top: 1rem;
}

/* ---------------------------------------------------------------------- */
/* Licences page (F53) — borrows the help page's column and rhythm         */
/* ---------------------------------------------------------------------- */

.licences__item {
  margin: 0 0 1.5rem;
}

.licences__name {
  font-family: var(--font-serif);
  font-size: 1rem;
  margin: 0 0 0.15rem;
}

.licences__version {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: normal;
  margin-left: 0.4rem;
}

.licences__purpose {
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.875rem;
  margin: 0 0 0.5rem;
}

/* Licence text is wrapped rather than scrolled: these are ~80-column files,
   and a phone reader should not have to pan sideways through a licence.
   `pre-wrap` keeps the blank lines between paragraphs, which is the only
   structure an MIT licence has. */
.licences__text {
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  color: var(--color-text-dim);
  font-size: 0.75rem;
  line-height: 1.5;
  margin: 0;
  overflow-wrap: break-word;
  padding: 0.75rem;
  white-space: pre-wrap;
}

.licences__list {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  line-height: 1.6;
  margin: 0;
  padding-left: 1.2rem;
}

/* ---------------------------------------------------------------------- */
/* Print (F10) — applies on every page, matters most for /book             */
/* ---------------------------------------------------------------------- */

@media print {
  :root {
    --color-bg: #ffffff !important;
    --color-bg-raised: #ffffff !important;
    --color-text: #111111 !important;
    --color-text-dim: #333333 !important;
    --color-accent: #111111 !important;
    --color-accent-text: #ffffff !important;
    --color-highlight-bg: #f5dfa6 !important;
    --color-border: #cccccc !important;
    --color-error: #a3372a !important;
  }

  body {
    background: #fff;
    color: #111;
  }

  .story,
  .timeline,
  .editor,
  .login,
  .book-cover,
  .book__story {
    background: none !important;
    background-image: none !important;
    box-shadow: none !important;
  }

  .site-nav,
  .minimap,
  .timeline__legend,
  .timeline__drafts-link,
  .timeline__onthisday,
  .timeline__toolbar,
  .timeline__search-empty,
  .timeline__footer-links,
  .story__footer,
  .flash-container,
  .skip-link,
  .firelight,
  .theme-toggle,
  .book__print-btn,
  .tree__back,
  .tree__views,
  .tree__chart,
  .tree__panels,
  button,
  .btn {
    display: none !important;
  }

  .tree__print-outline {
    display: block !important;
    color: #111;
  }

  .book__story {
    break-before: page;
  }

  .book__story:first-of-type {
    break-before: avoid;
  }

  .book-cover {
    break-after: page;
  }

  .book__year-chapter {
    break-before: page;
    break-after: avoid;
    break-inside: avoid;
  }

  /* A story right after its year's chapter title joins that same page
     (the chapter break above already turned the page) rather than
     forcing a second, near-blank page of its own. */
  .book__year-chapter + .book__story {
    break-before: avoid;
  }

  .story__body {
    font-size: 11pt;
    line-height: 1.5;
  }

  .story__body img,
  .story__cover {
    max-width: 100%;
    max-height: 22cm;
    break-inside: avoid;
  }

  .book__instant {
    break-inside: avoid;
  }

  .book__instant-photo {
    max-height: 10cm;
  }

  mark {
    background: #f5dfa6 !important;
    color: #000 !important;
  }

  @page {
    margin: 20mm;
  }
}

/* --- Making a theme (F50) -------------------------------------------------
   The two pages behind "Make a theme": a small form for the colours, and
   the sheet of thirty-five pictures with a prompt beside each. Both are
   admin pages and borrow .admin's frame; only what is particular to them
   is here. */

.theme-form {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  max-width: 34rem;
}

.theme-form__row {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.theme-form__label {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

.theme-form__note {
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

.theme-form__schemes {
  margin: 0;
  padding: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.theme-form__schemes legend {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
  padding: 0 0.4rem;
}

.theme-form__scheme {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.theme-form__scheme-on {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  font-family: var(--font-sans);
  font-weight: 600;
}

.theme-form__colours {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.theme-form__colour {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.theme-form__colour-name {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--color-text-dim);
  min-width: 3.5rem;
}

.theme-form__swatch {
  width: 44px;
  height: 44px;
  padding: 2px;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: none;
  cursor: pointer;
}

.theme-form__hex {
  width: 7.5rem;
  font-family: var(--font-mono, monospace);
}

.theme-form__actions,
.theme-form__delete {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.theme-form__delete {
  margin-top: 2.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border);
}

/* ---------------------------------------------------------------------- */
/* F52: the theme editor's live preview                                    */
/*                                                                          */
/* Sticky at the top of the form, because the thing you are looking at and  */
/* the field you are changing are otherwise a phone-screen apart: eighteen  */
/* hex fields is a long scroll, and a preview you have to scroll back to is */
/* a preview you stop using.                                                */
/*                                                                          */
/* Everything inside .theme-preview__pane is written in var(--color-...)    */
/* exactly as main.css writes it, and theme-preview.js sets those variables */
/* on the pane itself. That is the whole trick, and it is what stops the    */
/* miniature from drifting away from the real screens: there is no second   */
/* set of colours to keep in step.                                          */
/* ---------------------------------------------------------------------- */

.theme-preview {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem 0 0.85rem;
  /* Opaque: the colour fields scroll underneath this. */
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
}

.theme-preview__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.theme-preview__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.theme-preview__tab {
  min-height: 32px;
  padding: 0 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: none;
  color: var(--color-text-dim);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  cursor: pointer;
}

.theme-preview__tab[aria-pressed="true"] {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

/* The miniature. Its own --color-* variables arrive inline from
   theme-preview.js, so nothing below names a colour. */
.theme-preview__pane {
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  overflow: hidden;
  background-color: var(--color-bg);
  color: var(--color-text);
}

.theme-preview__nav {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem;
  border-bottom: 1px solid var(--color-border);
}

.theme-preview__brand {
  flex: 1;
  font-family: var(--font-serif);
  font-size: 1rem;
  color: var(--color-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.theme-preview__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-size: 0.8125rem;
}

/* The nav's own button is outlined, not filled — .btn--new, not
   .btn--primary. Getting this wrong in a preview would flatter an accent
   that is actually only ever drawn as an outline. */
.theme-preview__btn {
  flex: none;
  padding: 0.25rem 0.6rem;
  border-radius: 0.4rem;
  border: 1px solid var(--color-accent);
  background: var(--color-bg-raised);
  color: var(--color-accent);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
}

.theme-preview__body {
  padding: 0.7rem 0.85rem 0.85rem;
}

.theme-preview__card {
  margin: 0 0 0.6rem;
  padding: 0.4rem 0.6rem;
  border-left: 3px solid var(--color-accent);
  border-radius: 0 0.4rem 0.4rem 0;
  background: var(--color-bg-raised);
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--color-text-dim);
}

.theme-preview__year {
  margin: 0;
  font-family: var(--font-serif);
  font-size: 1.15rem;
  color: var(--color-accent);
}

.theme-preview__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.2rem 0 0.1rem;
}

.theme-preview__dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--color-accent);
}

.theme-preview__date {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  color: var(--color-accent);
}

.theme-preview__pill {
  font-family: var(--font-sans);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 0.1em 0.5em;
  border-radius: 999px;
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
}

.theme-preview__title {
  margin: 0 0 0.6rem;
  font-family: var(--font-serif);
  font-size: 0.9375rem;
  color: var(--color-text);
}

/* Where a picture goes: the card the app draws around every illustration,
   which a pack colours through --illo-mount even before it has any art. */
.theme-preview__mount {
  display: block;
  height: 34px;
  border-radius: 4px;
  background: var(--illo-mount);
  border: 1px solid var(--illo-mount-edge);
}

.theme-preview__empty,
.theme-preview__warning {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

.theme-preview__warning {
  color: var(--color-error);
}

.theme-preview__checks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.9rem;
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: var(--color-text-dim);
}

.theme-preview__check {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
}

.theme-preview__check-value {
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

/* Below the floor the app itself holds that pair to. Said in colour and in
   the number, never in colour alone. */
.theme-preview__check.is-low .theme-preview__check-value {
  color: var(--color-error);
}

.theme-preview__check.is-low .theme-preview__check-value::after {
  content: " \26A0";
}

@media (min-width: 48rem) {
  .theme-preview__pane {
    max-width: 26rem;
  }
}

/* The sheet. One row per picture: what the book draws there now on the
   left, the prompt and the upload on the right. */
.asset-sheet {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.asset-sheet__row {
  display: flex;
  gap: 1rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--color-border);
}

.asset-sheet__shot {
  flex: 0 0 7rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
}

.asset-sheet__shot img {
  width: 100%;
  height: auto;
  border-radius: 0.5rem;
  background: var(--illo-mount, var(--color-bg-raised));
}

.asset-sheet__state {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

.asset-sheet__state.is-drawn {
  color: var(--color-accent);
  font-weight: 600;
}

.asset-sheet__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.asset-sheet__name {
  margin: 0;
  font-size: 1.05rem;
}

.asset-sheet__file {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--color-text-dim);
}

.asset-sheet__prompt-label {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

.asset-sheet__prompt {
  width: 100%;
  font-size: 0.8125rem;
  line-height: 1.45;
  resize: vertical;
}

.asset-sheet__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.asset-sheet__upload {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.asset-sheet__upload input[type="file"] {
  max-width: 100%;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
}

/* On a phone the picture goes above its prompt rather than beside it —
   there is no width to put a 7rem column next to a textarea. */
@media (max-width: 34rem) {
  .asset-sheet__row {
    flex-direction: column;
  }

  .asset-sheet__shot {
    flex: none;
    align-items: flex-start;
    max-width: 8rem;
  }
}
