/* HoangFam - warm editorial family album.
   Light-first (photos glow on cream); dark mode warmed to charcoal.
   Type: Fraunces (display serif) + Hanken Grotesk (body). Accent: terracotta. */

:root {
  --bg: #f6f1e8;
  --bg-2: #efe6d7;
  --surface: #fffdf8;
  --surface-2: #f4ece0;
  --ink: #2a2521;
  --ink-soft: #5c554c;
  --muted: #948b7e;
  --accent: #c15f3c;
  --accent-2: #d98a4f;
  --accent-ink: #fff8f2;
  --line: #e6ddce;
  --shadow: 30px 30px 60px rgba(80, 55, 30, 0.10);
  --shadow-sm: 0 6px 20px rgba(80, 55, 30, 0.10);
  --radius: 16px;
  --maxw: 1080px;

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body: 'Hanken Grotesk', system-ui, -apple-system, sans-serif;
}

:root[data-theme="dark"] {
  --bg: #16130e;
  --bg-2: #1e1a13;
  --surface: #221d16;
  --surface-2: #2a2319;
  --ink: #f1eade;
  --ink-soft: #c6bbaa;
  --muted: #928776;
  --accent: #e28a54;
  --accent-2: #eaa46a;
  --accent-ink: #1a1109;
  --line: #352d22;
  --shadow: 30px 30px 60px rgba(0, 0, 0, 0.45);
  --shadow-sm: 0 6px 20px rgba(0, 0, 0, 0.4);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #16130e;
    --bg-2: #1e1a13;
    --surface: #221d16;
    --surface-2: #2a2319;
    --ink: #f1eade;
    --ink-soft: #c6bbaa;
    --muted: #928776;
    --accent: #e28a54;
    --accent-2: #eaa46a;
    --accent-ink: #1a1109;
    --line: #352d22;
    --shadow: 30px 30px 60px rgba(0, 0, 0, 0.45);
    --shadow-sm: 0 6px 20px rgba(0, 0, 0, 0.4);
  }
}

* { box-sizing: border-box; }

/* The hidden attribute must always win over display rules from classes. */
[hidden] { display: none !important; }

html, body { margin: 0; padding: 0; min-height: 100%; }

body {
  background:
    radial-gradient(1200px 600px at 80% -10%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%),
    radial-gradient(900px 500px at -10% 10%, color-mix(in srgb, var(--accent-2) 10%, transparent), transparent 55%),
    var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* very subtle paper grain */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.35;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
}
body > * { position: relative; z-index: 1; }

/* --- wallpaper ---------------------------------------------------------
   A page can be dressed: an album, a family, the landing page. The layer is fixed, so
   it does not scroll away, and it sits UNDER everything (an id beats the `body > *`
   rule above, which would otherwise lift it over the content).

   The scrim is the important part. A photo archive lives or dies on whether you can
   see the photographs, and a wallpaper that competes with them has failed no matter
   how handsome it is. So whatever is behind gets pushed back with a wash of the page's
   own colour - which means the same background works in light mode and dark. */
body > #page-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: var(--page-bg, none);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
body > #page-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--bg);
  opacity: var(--page-bg-scrim, 0.62);
}

/* An uploaded photograph needs holding back harder than a flat gradient does - it has
   detail in it, and detail behind text is what makes text hard to read. */
body.bg-image > #page-bg::after { opacity: var(--page-bg-scrim, 0.72); }

/* Text that would otherwise sit straight on the wallpaper gets something to sit on.
   The album grid already has cards; a story does not. */
body.has-bg .home-narrative,
body.has-bg .family-narrative {
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.4rem 1.6rem;
  backdrop-filter: blur(6px);
}

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

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 560;
  font-optical-sizing: auto;
  letter-spacing: -0.01em;
  line-height: 1.1;
  color: var(--ink);
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: -999px; top: 0;
  background: var(--accent); color: var(--accent-ink);
  padding: 0.6rem 1rem; border-radius: 0 0 10px 0; z-index: 200;
}
.skip-link:focus { left: 0; }

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

/* ---------- logo / wordmark ---------- */
/* Crest logo: gold shield, 3 red bars, blue HF. Text kept for screen readers. */
.logo {
  display: inline-block;
  width: 38px; height: 44px;
  background: url(/img/crest.svg) center/contain no-repeat;
  font-size: 0; color: transparent; line-height: 0;
  flex-shrink: 0;
}
.logo-sm { width: 30px; height: 35px; }

.wordmark {
  font-family: var(--font-display);
  font-weight: 580; font-size: 1.3rem;
  letter-spacing: -0.01em;
  color: var(--ink); text-decoration: none;
  display: inline-flex; align-items: center; gap: 0.5rem;
}
.wordmark:hover { color: var(--accent); }

/* ---------- headers ---------- */
.site-header, .app-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem 1.5rem;
  max-width: var(--maxw); margin: 0 auto; width: 100%;
}
/* body > * gives every direct child of body z-index: 1, so the header and <main>
   tie - and <main> comes later in the DOM, so it wins and paints OVER the header.
   That buried the name menu underneath the page: it appeared behind the content and
   its items could not be clicked at all, because the clicks were landing on the
   album tiles on top of it. The header has to outrank the content it drops over. */
.site-header, .app-header { z-index: 10; }
/* usermenu.js adds .nav-open while the name menu is showing. z-index:10 is enough
   to sit over ordinary page content, but a sticky toolbar or an overlay can outrank
   it and bury the open menu (which lives inside this same stacking context). Lifting
   the whole header only while the menu is open keeps that menu on top of everything,
   without letting the header float over a modal the rest of the time. */
.site-header.nav-open, .app-header.nav-open { z-index: 1000; }
.app-header { border-bottom: 1px solid var(--line); }
.app-nav { display: flex; align-items: center; gap: 0.9rem; }
/* The header name menu. Clicking your name used to do nothing at all. */
.user-menu { position: relative; }
.who {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.4rem 0.75rem; border-radius: 999px;
  background: none; border: 1px solid transparent;
  color: var(--ink-soft); font-family: var(--font-body); font-size: 0.92rem;
  cursor: pointer;
}
.who::after { content: '\25BE'; font-size: 0.7rem; opacity: 0.7; }   /* caret */
.who:hover { background: var(--surface-2); border-color: var(--line); }

.user-dropdown {
  position: absolute; top: calc(100% + 0.5rem); right: 0;
  min-width: 230px; z-index: 50;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);
  overflow: hidden;
}
.um-head {
  display: flex; flex-direction: column; gap: 0.15rem;
  padding: 0.85rem 1rem; border-bottom: 1px solid var(--line);
}
.um-head .muted { font-size: 0.8rem; overflow-wrap: anywhere; }
.um-head .role-badge { align-self: flex-start; margin-top: 0.35rem; }
.um-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem; padding: 0.6rem 1rem;
  font-size: 0.9rem; color: var(--ink-soft);
  border-bottom: 1px solid var(--line);
}
.um-item {
  padding: 0.7rem 1rem; text-align: left;
  background: none; border: none; border-bottom: 1px solid var(--line);
  color: var(--ink); font-family: var(--font-body); font-size: 0.92rem;
  text-decoration: none; cursor: pointer;
}
.um-item:last-child { border-bottom: none; }
.um-item:hover { background: var(--surface-2); color: var(--ink); }

.theme-select {
  padding: 0.45rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-body); font-size: 0.85rem;
  cursor: pointer;
  min-height: 40px;
}
.theme-select:hover { border-color: var(--accent); }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.5rem;
  padding: 0.8rem 1.3rem;
  border-radius: 999px;
  border: 1px solid transparent;
  background: linear-gradient(145deg, var(--accent), var(--accent-2));
  color: var(--accent-ink);
  font-family: var(--font-body); font-size: 1rem; font-weight: 600;
  cursor: pointer; text-decoration: none;
  min-height: 46px;
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.btn:hover { transform: translateY(-2px); filter: brightness(1.04); }
.btn:active { transform: translateY(0); }
.btn-sm { padding: 0.5rem 1rem; font-size: 0.9rem; min-height: 40px; }

.btn-ghost {
  background: transparent; color: var(--ink-soft);
  border: 1px solid var(--line); box-shadow: none;
}
.btn-ghost:hover { color: var(--ink); border-color: var(--accent); filter: none; }

.btn-oauth {
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); box-shadow: var(--shadow-sm);
  font-weight: 600;
}
/* Apple asks for a black button, which all but vanishes against the night theme's
   near-black page - the Google button beside it looked like the only way in. Keep the
   black fill Apple wants and give it a bright rim, so the shape reads either way. */
.btn-apple { background: #000; color: #fff; border-color: #3a3a3a; }
.btn-apple:hover { background: #1f1f1f; filter: none; }
:root[data-theme="dark"] .btn-apple { border-color: rgba(255, 255, 255, 0.85); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .btn-apple { border-color: rgba(255, 255, 255, 0.85); }
}
.btn-google { background: #fff; color: #1f1f1f; border-color: #dcdcdc; }

/* ---------- hero (landing + login) ---------- */
.hero {
  flex: 1;
  display: flex; flex-direction: column;
  justify-content: center; align-items: center; text-align: center;
  padding: 3rem 1.5rem 4rem;
  max-width: 680px; margin: 0 auto; width: 100%;
}
.hero .logo { width: 66px; height: 77px; margin-bottom: 1.5rem; }
.hero h1 {
  font-size: clamp(3rem, 10vw, 5.5rem);
  margin: 0 0 0.5rem;
  font-weight: 600;
}
.hero h1 .amp { font-style: italic; color: var(--accent); }
.tagline {
  font-size: clamp(1.15rem, 3vw, 1.5rem);
  color: var(--ink-soft);
  font-family: var(--font-display);
  font-weight: 400; font-style: italic;
  margin: 0 0 2rem; max-width: 30ch;
}
.soon { color: var(--muted); font-size: 1rem; }
.soon a { color: var(--accent); }

.hero-cta { display: flex; gap: 0.75rem; flex-wrap: wrap; justify-content: center; }

/* request-access modal */
.link-btn {
  background: none; border: none; padding: 0; margin: 0;
  color: var(--accent); text-decoration: underline; cursor: pointer;
  font: inherit;
}
.badge {
  display: inline-block; font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.03em; padding: 0.15rem 0.5rem; border-radius: 999px;
  background: var(--accent-soft, #f0e4d8); color: var(--accent, #c15f3c);
}
.ra-backdrop {
  position: fixed; inset: 0; z-index: 150;
  background: rgba(20, 14, 8, 0.6);
  display: flex; align-items: center; justify-content: center; padding: 1.5rem;
  animation: fade 0.15s ease;
}
.ra-box {
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.75rem; max-width: 420px; width: 100%;
  box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: 0.7rem; text-align: left;
}
.ra-box h2 { font-family: var(--font-display); margin: 0; font-size: 1.5rem; }
.ra-box p { margin: 0 0 0.25rem; }
.ra-box input, .ra-box textarea { width: 100%; }
.ra-actions { display: flex; justify-content: flex-end; gap: 0.6rem; margin-top: 0.5rem; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

/* ---------- auth card ---------- */
.auth-card {
  display: flex; flex-direction: column; gap: 0.75rem;
  width: 100%; max-width: 360px; margin-top: 1rem;
}
.dev-only { margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }
.dev-note { color: var(--muted); font-size: 0.8rem; margin: 0 0 0.5rem; }
.dev-error { color: #c0392b; font-size: 0.9rem; min-height: 1.2em; }
:root[data-theme="dark"] .dev-error { color: #ff8a7a; }

/* ---------- email one-time code ---------- */
.auth-sep {
  display: flex; align-items: center; gap: 0.75rem;
  color: var(--muted); font-size: 0.85rem;
}
.auth-sep::before, .auth-sep::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}
.email-code { display: flex; flex-direction: column; gap: 0.6rem; }
.email-code input { width: 100%; }
.email-code input#ec-code {
  text-align: center; letter-spacing: 0.4em; font-size: 1.3rem;
  font-family: var(--font-mono, ui-monospace, "SF Mono", monospace);
}
.ec-sent { margin: 0; color: var(--ink-soft); font-size: 0.9rem; text-align: center; }

input[type="text"], input[type="email"], input:not([type]),
#dev-login input, .new-album-form input, .ra-box input, .ra-box textarea {
  padding: 0.7rem 0.9rem;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-body); font-size: 1rem;
  min-height: 46px;
}
.ra-box textarea { resize: vertical; }
input::placeholder { color: var(--muted); }
#dev-login { display: flex; gap: 0.5rem; }
#dev-login input { flex: 1; }

/* broadcast (email the whole family) */
.bc-form { max-width: 640px; margin-top: 1rem; }
.bc-label { display: block; font-weight: 600; font-size: 0.9rem; margin: 0.9rem 0 0.35rem; color: var(--ink); }
.bc-input { width: 100%; padding: 0.7rem 0.9rem; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); color: var(--ink); font-family: var(--font-body); font-size: 1rem; }
.bc-textarea { min-height: 200px; resize: vertical; line-height: 1.5; }
.bc-actions { display: flex; align-items: center; gap: 12px; margin-top: 1rem; }
#bc-msg { margin-top: 0.6rem; }

/* ---------- app main ---------- */
.app-main {
  flex: 1;
  max-width: var(--maxw); margin: 0 auto; width: 100%;
  padding: 2.5rem 1.5rem 4rem;
}
.app-main-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem;
}
.app-main h1 { font-size: clamp(2rem, 5vw, 3rem); margin: 0; }
.muted { color: var(--muted); }
.back-link {
  color: var(--ink-soft); text-decoration: none; font-size: 0.95rem;
  display: inline-block; margin-bottom: 1rem;
}
.back-link:hover { color: var(--accent); }

/* The header back arrow (drawn by usermenu.js). Hidden by default: on a wide
   screen the full nav is right there, and in a normal browser tab the back
   button already exists. It only appears where neither is true - a narrow
   screen, or the installed app with no browser chrome of its own. */
.app-back {
  display: none;
  align-items: center; justify-content: center;
  width: 40px; height: 40px; margin: -0.4rem 0.1rem -0.4rem -0.4rem;
  flex-shrink: 0;
  background: none; border: 1px solid transparent; border-radius: 999px;
  color: var(--ink); font-size: 1.4rem; line-height: 1;
  cursor: pointer;
}
.app-back:hover { color: var(--accent); }
.app-back:active { background: var(--line); }
@media (max-width: 799px) {
  .app-back { display: inline-flex; }
}
@media (display-mode: standalone) {
  .app-back { display: inline-flex; }
}

.new-album-form {
  display: flex; gap: 0.6rem; flex-wrap: wrap;
  margin: 1.25rem 0;
  padding: 1rem; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
}
.new-album-form input { flex: 1; min-width: 160px; }

/* "In memory of someone who has died" - the second kind of album, offered only to a
   family admin. The checkbox has to opt OUT of the rule above, which stretches every
   input in this form to fill the row. */
.album-kind {
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-size: 0.9rem; color: var(--muted); cursor: pointer;
}
.new-album-form .album-kind input { flex: 0 0 auto; min-width: 0; width: auto; cursor: pointer; }

/* ---------- album grid ---------- */
.album-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 1.75rem;
  margin-top: 2rem;
}
.album-card {
  display: block; text-decoration: none; color: var(--ink);
  background: var(--surface);
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
  animation: rise 0.5s ease both;
}
.album-card:hover { transform: translateY(-4px) rotate(-0.4deg); box-shadow: 0 18px 40px rgba(80,55,30,0.16); }
/* The frame a cover is clipped to.
   A zoomed cover is a scaled <img>, and a CSS transform does not affect layout - so
   without a box of its own to be clipped by, a 2x cover paints straight over the title
   and the photo count underneath it. The frame holds the shape; the image fills it and
   is cut off at its edges. */
.cover-frame {
  display: block; width: 100%; aspect-ratio: 4 / 3;
  overflow: hidden; background: var(--surface-2);
  position: relative;
  /* The photograph has to stack ABOVE its own blurred backdrop, and BELOW the album's
     name, which is painted over the cover. Without a stacking context of its own, the
     z-index that lifts it off the backdrop also lifts it over the title - and half the
     album's name disappears behind the picture. isolate keeps that fight inside the
     frame, where it belongs. */
  isolation: isolate;
}
.cover-frame-hero { aspect-ratio: 2 / 1; min-height: 260px; }

/* THE WHOLE PHOTOGRAPH, NOT A CROP OF IT.
   A 2:1 hero cannot hold a portrait of two people - no crop keeps both faces, because
   both faces do not fit in the shape. So the picture is fitted INSIDE the frame instead,
   and the bars either side are filled with the same photograph blown up, blurred and
   dimmed. Empty bars look like a broken image; bars made of the picture's own colours
   look like a decision. Opt-in, per album. */
.cover-frame.is-contain img.album-cover,
.cover-frame.is-contain img.hero-cover {
  position: relative; z-index: 1;
  object-fit: contain;
}
.cover-blur {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* Scaled up so the blur's own soft edges never show as a seam at the frame's border. */
  transform: scale(1.15);
  filter: blur(22px) saturate(1.1) brightness(0.55);
}

.album-cover {
  width: 100%; height: 100%; object-fit: cover; display: block;
  background: var(--surface-2);
}
.album-cover-empty {
  display: grid; place-items: center;
  color: var(--accent);
  font-family: var(--font-display); font-weight: 600; font-size: 1.6rem;
  background:
    repeating-linear-gradient(45deg, transparent, transparent 12px, color-mix(in srgb, var(--accent) 6%, transparent) 12px, color-mix(in srgb, var(--accent) 6%, transparent) 24px),
    var(--surface-2);
}
.album-meta { padding: 0.85rem 1rem 1rem; display: flex; flex-direction: column; gap: 0.15rem; }
.album-title { font-family: var(--font-display); font-weight: 560; font-size: 1.15rem; }
.album-count { color: var(--muted); font-size: 0.85rem; }
/* "In Memory Of", above the name on a memorial's tile.

   The same words in the same hand as the memorial's own hero, so the tile is a promise
   the page keeps. It was 0.68rem uppercase in --muted, which is the colour of a caption
   nobody is meant to read - too small and too faint to say what this album is.

   --accent, the page's OWN accent, rather than reaching for the book's --hs-accent: on
   every page a tile appears on, booktheme.css already forces --accent to the book's
   terracotta for every theme ("the book is always warm on purpose"), so this lands on
   the same #a8432a as the memorial's hero - measured. Should the book skin ever come
   off those pages, --accent goes back to following light/dark on its own, which a
   hard-coded terracotta could not do. */
.album-eyebrow {
  font-family: 'Caveat', cursive;
  font-weight: 600;
  font-size: 1.15rem; line-height: 1.1;
  color: var(--accent);
  letter-spacing: 0; text-transform: none;
  margin-bottom: 0.1rem;
}

/* stagger the first several cards */
.album-card:nth-child(1) { animation-delay: 0.02s; }
.album-card:nth-child(2) { animation-delay: 0.08s; }
.album-card:nth-child(3) { animation-delay: 0.14s; }
.album-card:nth-child(4) { animation-delay: 0.20s; }
.album-card:nth-child(5) { animation-delay: 0.26s; }
.album-card:nth-child(6) { animation-delay: 0.32s; }

@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* ---------- photo grid ---------- */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0.65rem;
  margin-top: 2rem;
}
.photo-tile {
  padding: 0; border: none;
  background: var(--surface-2);
  border-radius: 12px; overflow: hidden;
  cursor: pointer; aspect-ratio: 1 / 1;
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease;
  animation: rise 0.4s ease both;
}
.photo-tile { position: relative; }
.photo-tile:hover { transform: scale(1.02); }
.photo-tile img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }

/* per-photo hover controls */
.photo-actions {
  position: absolute; top: 6px; right: 6px;
  display: flex; gap: 4px;
  opacity: 0; transition: opacity 0.15s ease;
}
.photo-tile:hover .photo-actions,
.photo-tile:focus-within .photo-actions { opacity: 1; }

/* A phone has no hover, so hover-to-reveal hides these controls completely on the
   devices the family actually uses. Always show them where there's no pointer. */
@media (hover: none) {
  .photo-actions { opacity: 1; }
}

/* The home page: the shared album leads, at the size of many tiles - it is the point
   of the site, not one album among many. Then the story. Then each family. */
.home-hero { margin: 0 0 3rem; }
.hero-card {
  display: block; position: relative;
  border-radius: var(--radius); overflow: hidden;
  text-decoration: none; color: inherit;
  border: 1px solid var(--line);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
}
/* Roughly the footprint of a 4x2 block of album tiles. */
.hero-cover {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
}
.hero-cover-empty {
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--accent);
  font-family: var(--font-display); font-size: 3rem;
}
.hero-meta {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 2rem 1.5rem 1.25rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.72), transparent);
  color: #fff;
  /* The album's name is written OVER the photograph, so it must win against it. Said
     out loud rather than left to depend on the order the elements happen to sit in. */
  z-index: 2;
}
.hero-meta h2 {
  margin: 0; font-family: var(--font-display);
  font-size: clamp(1.6rem, 4vw, 2.6rem); line-height: 1.1;
}
.hero-meta p { margin: 0.25rem 0 0; color: rgba(255, 255, 255, 0.85); font-size: 0.95rem; }
.hero-card:hover .hero-cover { transform: scale(1.01); transition: transform 0.4s ease; }

/* The narrative runs the full width of the page, like the album above it. Long lines
   are harder to read, so it is set in two columns on a wide screen - the eye still
   gets a short line to travel, and the story fills the space it was given. On a phone
   it collapses to one column, where a single column IS the full width. */
/* NO COLUMNS. A CSS multi-column layout with break-inside:avoid puts a single
   paragraph entirely in the first column - so a short narrative (one paragraph, which
   most of them are) filled the left half of the page and left the right half empty.
   That was the "capped at half a page": not the textarea, not line endings, the
   columns. One column, full width, every time. */
.home-narrative {
  margin: 1.5rem 0 0;
  width: 100%;
  font-size: 1.05rem; line-height: 1.75; color: var(--ink-soft);
}
.home-narrative p { margin: 0 0 1rem; }
.home-narrative p:last-child { margin-bottom: 0; }

/* The lid on a long story: "Show more". It belongs to the text, so it sits tight under
   the last line rather than floating above the album grid. */
.home-narrative .story-toggle { margin-top: 0.6rem; }
.story-dots { color: var(--ink-soft); }

.home-family { margin: 0 0 3rem; }
.home-family-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; margin-bottom: 0.5rem;
  padding-bottom: 0.5rem; border-bottom: 1px solid var(--line);
}
.home-family-head h2 { margin: 0; font-family: var(--font-display); font-size: 1.6rem; }
.home-family .album-grid { margin-top: 1.25rem; }

.narrative-box {
  width: min(680px, 94vw);
  padding: 1.5rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
}
.narrative-box textarea {
  width: 100%; margin: 0.75rem 0;
  padding: 0.75rem 0.9rem; border-radius: 12px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font-family: var(--font-body); font-size: 1rem; line-height: 1.6;
  resize: vertical;
}

/* Search. */
.search-bar { display: flex; gap: 0.5rem; margin: 0.5rem 0 1.75rem; }
.search-bar input[type="search"] {
  flex: 1; min-width: 0;
  padding: 0.7rem 1rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font-family: var(--font-body); font-size: 1rem;
}
.search-bar input[type="search"]:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(193, 95, 60, 0.15);
}
.search-h {
  font-family: var(--font-display); font-size: 1.35rem;
  margin: 1.25rem 0 0.75rem;
}
/* A search result carries its caption and year, so a face you half-remember is
   identifiable without opening it. */
.search-tile { display: block; position: relative; text-decoration: none; }
.search-tile .tile-caption { pointer-events: none; }

/* Picking several photos to order together. */
.select-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 120;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: 0.9rem 1.25rem;
  background: var(--surface); border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.12);
}
.select-actions { display: flex; gap: 0.5rem; }
body.is-selecting .photo-tile { cursor: copy; }
body.is-selecting .photo-tile img { opacity: 0.75; }
.photo-tile.is-picked img { opacity: 1; }
.photo-tile.is-picked { outline: 3px solid var(--accent); outline-offset: 2px; }
.photo-tile.is-picked::after {
  content: '\2713';
  position: absolute; top: 8px; right: 8px;
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink);
  font-weight: 700; font-size: 0.9rem;
}

/* Choosing which album the selected photographs go to. */
.picker-box {
  width: min(460px, 94vw); max-height: 84vh; overflow-y: auto; text-align: left;
}
.picker-box #picker-search {
  width: 100%; margin: 0.6rem 0 0.4rem; padding: 0.5rem 0.65rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm, 6px);
  background: var(--surface); color: inherit; font: inherit;
}
.picker-list { display: flex; flex-direction: column; gap: 0.25rem; max-height: 44vh; overflow-y: auto; }
.picker-item {
  display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem;
  width: 100%; padding: 0.55rem 0.7rem; text-align: left;
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm, 6px);
  color: inherit; font: inherit; cursor: pointer;
}
.picker-item:hover, .picker-item:focus-visible { border-color: var(--line); background: var(--surface-2, rgba(0, 0, 0, 0.04)); }
.picker-item .picker-where { font-size: 0.8rem; opacity: 0.65; }

/* The basket: several photos, one order, one postage. */
.basket-box {
  width: min(560px, 94vw); max-height: 88vh; overflow-y: auto;
  padding: 1.25rem 1.5rem 1.5rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
}
.basket-rows { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.75rem; }
.basket-row {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.6rem; border: 1px solid var(--line); border-radius: 12px;
}
.basket-thumb { width: 54px; height: 54px; object-fit: cover; border-radius: 8px; flex: none; }
.basket-body { flex: 1; min-width: 0; }
.basket-body select { width: 100%; }
.basket-quality { margin: 0.25rem 0 0; font-size: 0.78rem; }
.basket-copies { width: 4rem; }
.basket-x {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 1.2rem; line-height: 1;
}
.basket-x:hover { color: var(--danger, #b4462f); }
#basket-form input { width: 100%; margin-bottom: 0.4rem; padding: 0.5rem 0.65rem;
  border-radius: 10px; border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font-family: var(--font-body); font-size: 0.95rem; }

/* Ordering a print. */
.ord-label { display: block; margin: 0.75rem 0 0.25rem; font-size: 0.85rem; color: var(--muted); }
.ord-h { font-family: var(--font-display); font-size: 1.05rem; margin: 1.25rem 0 0.5rem; }
#ord-form input, #ord-form select {
  width: 100%; margin-bottom: 0.4rem;
  padding: 0.5rem 0.65rem; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font-family: var(--font-body); font-size: 0.95rem;
}
.ord-note { margin: 0.35rem 0; font-size: 0.85rem; color: var(--muted); }
.ord-note.is-warn { color: #b4772f; }
.ord-note.is-bad { color: #b4462f; font-weight: 600; }
.ord-total { margin: 0.75rem 0 0; font-size: 1.15rem; font-weight: 600; }

/* Your prints. */
.order-banner {
  padding: 0.85rem 1rem; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--line);
  margin: 1rem 0;
}
.order-banner.is-good { border-color: #6ec07d; }
.order-list { display: flex; flex-direction: column; gap: 0.75rem; margin-top: 1.25rem; }
.order-row {
  display: flex; align-items: center; gap: 1rem;
  padding: 0.85rem 1rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.order-thumb { width: 64px; height: 64px; object-fit: cover; border-radius: 8px; flex: none; }
.order-thumb-empty { display: grid; place-items: center; background: var(--surface-2); color: var(--accent); font-weight: 700; }
.order-body { flex: 1; min-width: 0; }
.order-body p { margin: 0.15rem 0 0; font-size: 0.9rem; }
.order-status { font-weight: 600; }
.order-status.is-muted { color: var(--muted); font-weight: 400; }
.order-status.is-bad { color: #b4462f; }
.order-price { font-weight: 600; font-variant-numeric: tabular-nums; }

/* Faces. The frame wraps the image so a box drawn in percentages lands on the right
   face at any size - the img is object-fit:contain, so the layer must hug the IMAGE,
   not the stage. */
.lb-frame { position: relative; display: inline-block; line-height: 0; }
.lb-faces { position: absolute; inset: 0; pointer-events: none; }
.face-box {
  position: absolute;
  border: 2px solid rgba(255, 255, 255, 0.85);
  border-radius: 6px;
  pointer-events: auto;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
  z-index: 1;
}
.face-box.is-named { border-color: #6ec07d; }        /* settled */
.face-box.is-guess { border-color: var(--accent-2); } /* a guess, awaiting a human */

/* A face a PERSON drew, because the machine could not see it. Dashed, so nobody's eye has to
   remember which boxes the robot proposed and which ones the family pointed at. */
.face-box.is-drawn { border-style: dashed; }

/* While a box is being drawn the layer itself has to take the pointer - it does not, normally,
   or every drag over a photograph would be swallowed before it reached the lightbox. And an
   existing face must not eat the drag either: you draw THROUGH a crowd, not around it. */
.lb-faces.is-drawing { pointer-events: auto; cursor: crosshair; }
.lb-faces.is-drawing .face-box { pointer-events: none; }

/* "+ Add a face" and "Look again", under the photograph, where the faces are. */
.lb-face-tools {
  position: absolute; left: 50%; bottom: 0.75rem; transform: translateX(-50%);
  display: flex; gap: 0.5rem; z-index: 30;
}
.lb-face-tool {
  font: inherit; font-size: 0.8rem; line-height: 1.4;
  padding: 0.35rem 0.7rem;
  border: 1px solid rgba(255, 255, 255, 0.35); border-radius: 999px;
  background: rgba(0, 0, 0, 0.6); color: #fff;
  cursor: pointer;
}
.lb-face-tool:hover { background: rgba(0, 0, 0, 0.82); }
.lb-face-tool.is-armed { background: #fff; color: #111; border-color: #fff; }
.lb-face-tool:disabled { opacity: 0.6; cursor: default; }

/* Two people standing shoulder to shoulder get two boxes side by side, and their
   labels hang below and overlap - which used to bury one label's buttons under the
   next box's label, so a wrong guess could not be rejected at all. Whichever face
   you are dealing with comes to the front: on hover, while its form has focus, and
   on tap (a phone has no hover, and this is mostly used on a phone). */
.face-box:hover,
.face-box:focus-within,
.face-box.is-front { z-index: 20; }

.face-label {
  position: absolute; top: 100%; left: 50%;
  transform: translateX(-50%);
  margin-top: 5px;
  display: flex; align-items: center; gap: 0.3rem;
  padding: 0.25rem 0.45rem; border-radius: 999px;
  background: rgba(0, 0, 0, 0.78);
  color: #fff; font-size: 0.8rem; line-height: 1.6;
  white-space: nowrap;
}
/* Raised: opaque, so it cannot be read through whatever it is covering. */
.face-box:hover > .face-label,
.face-box:focus-within > .face-label,
.face-box.is-front > .face-label {
  background: #000;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
}
.face-name { font-weight: 600; }

/* "One at a time". On a crowded photo the pills bury each other; here every label but
   the current face's is hidden, and that face is stepped through with the nav. The
   others dim so the eye can find the one being named. */
.lb-faces.solo-mode .face-label { display: none; }
.lb-faces.solo-mode .face-box.is-front > .face-label { display: flex; }
.lb-faces.solo-mode .face-box:not(.is-front) { opacity: 0.4; }
.lb-face-solo-nav { display: inline-flex; align-items: center; gap: 0.4rem; }
.lb-solo-count { color: #fff; font-size: 0.8rem; min-width: 3.4em; text-align: center; }
.face-conf { color: rgba(255, 255, 255, 0.6); font-size: 0.7rem; }

.face-yes, .face-no, .face-ask, .face-edit, .face-form button {
  background: none; border: none; cursor: pointer;
  color: #fff; font-size: 0.9rem; line-height: 1;
  /* A thumb is 44px wide and these sit inches apart on a phone. */
  min-width: 1.9rem; min-height: 1.9rem;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
}
.face-yes:hover, .face-yes:focus-visible { color: #6ec07d; background: rgba(110, 192, 125, 0.16); }
.face-no:hover, .face-no:focus-visible,
.face-form .face-skip:hover, .face-form .face-skip:focus-visible { color: #ff8a70; background: rgba(255, 138, 112, 0.16); }
.face-ask { text-decoration: underline; text-underline-offset: 2px; font-size: 0.78rem; min-width: 0; padding: 0 0.35rem; }

/* A name that turned out to be wrong has to be fixable, so the name itself is the
   button that fixes it. */
.face-edit {
  font-weight: 600; font-size: 0.8rem;
  min-width: 0; padding: 0 0.3rem; gap: 0.3rem;
  text-decoration: underline; text-decoration-color: transparent;
  text-underline-offset: 3px;
}
.face-edit:hover, .face-edit:focus-visible { text-decoration-color: rgba(255, 255, 255, 0.55); }
.face-edit .face-pencil { opacity: 0.55; font-size: 0.7rem; }

.face-form { display: flex; align-items: center; gap: 0.25rem; }
.face-form input {
  width: 9rem;
  padding: 0.2rem 0.45rem; border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(0, 0, 0, 0.5); color: #fff;
  font-family: var(--font-body); font-size: 0.8rem;
}

@media print { .lb-faces { display: none !important; } }

/* People: who is in the photograph. */
.lb-people {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 0.4rem; margin-top: 0.5rem;
}
.lb-people-list { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.person-chip {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.25rem 0.5rem 0.25rem 0.7rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.22);
  font-size: 0.85rem;
}
.person-chip a { color: #fff; text-decoration: none; }
.person-chip a:hover { text-decoration: underline; }
.person-x {
  background: none; border: none; cursor: pointer;
  color: rgba(255, 255, 255, 0.65); font-size: 1rem; line-height: 1; padding: 0 0.1rem;
}
.person-x:hover { color: #fff; }
.lb-tag-form { display: flex; gap: 0.4rem; align-items: center; }
.lb-tag-form input {
  padding: 0.4rem 0.65rem; border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(0, 0, 0, 0.45); color: #fff;
  font-family: var(--font-body); font-size: 0.9rem;
}
.lb-tag-form input::placeholder { color: rgba(255, 255, 255, 0.5); }

/* A face you half-remember, findable by name. */
.people-row { display: flex; flex-wrap: wrap; gap: 1rem; }
.people-page { gap: 1.5rem; margin-top: 1.5rem; }
.person-card {
  display: flex; flex-direction: column; align-items: center; gap: 0.3rem;
  width: 120px; text-decoration: none; color: var(--ink);
}
.person-card img, .person-blank {
  width: 96px; height: 96px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--line);
}
.person-blank {
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--accent); font-weight: 700;
}
.person-card:hover img, .person-card:hover .person-blank { border-color: var(--accent); }
.person-name { font-weight: 600; font-size: 0.92rem; text-align: center; }

/* The "use their Google photo" opt-in, sat under the face controls. */
.person-google-row { display: flex; align-items: center; gap: 0.45rem; margin: 0.35rem 0 0; cursor: pointer; }
.person-google-row input { cursor: pointer; }

/* The tile says who is in it, so you can scan an album for a face. */
.tile-people {
  position: absolute; left: 6px; top: 6px; right: 6px;
  display: flex; flex-wrap: wrap; gap: 0.25rem;
  pointer-events: none;
}
.tile-person {
  padding: 0.15rem 0.4rem; border-radius: 999px;
  background: rgba(0, 0, 0, 0.55); color: #fff;
  font-size: 0.68rem; font-weight: 600;
}

/* Dragging a tile to reorder the album. */
.photo-tile[draggable="true"] { cursor: grab; }
.photo-tile.is-dragging { opacity: 0.4; cursor: grabbing; }
.photo-tile.drop-before { outline: 3px solid var(--accent); outline-offset: 2px; }

/* Dragging files over the album page (desktop). */
body.is-dropping::after {
  content: 'Drop to add to this album';
  position: fixed; inset: 1rem;
  display: grid; place-items: center;
  border: 2px dashed var(--accent);
  border-radius: var(--radius);
  background: rgba(193, 95, 60, 0.12);
  color: var(--accent); font-weight: 600; font-size: 1.1rem;
  z-index: 200; pointer-events: none;
}

/* Social marks on a tile: how many comments, and a heart you can tap without
   opening the photo. Bottom-right, clear of the cover badge (bottom-left) and the
   video badge (top-left). */
.tile-marks {
  position: absolute; right: 6px; bottom: 6px;
  display: flex; align-items: center; gap: 0.35rem;
}
.tile-mark {
  display: inline-flex; align-items: center; gap: 0.22rem;
  padding: 0.25rem 0.5rem; border-radius: 999px;
  background: rgba(0, 0, 0, 0.55); color: #fff;
  font-size: 0.75rem; font-weight: 600; line-height: 1;
  border: none; cursor: default;
}
/* A mark on a tile is read-only: the comment count, and nothing else. The heart that
   used to sit beside it is gone, and so is Talk's - which was the only other thing that
   ever built a `button.tile-mark`, so the button and is-liked rules went with it. */
.tile-mark .count:empty { display: none; }

/* A video in the grid: its poster frame, marked as playable. */
.video-badge {
  position: absolute; left: 6px; top: 6px;
  display: flex; align-items: center; gap: 0.3rem;
  padding: 0.2rem 0.5rem; border-radius: 999px;
  background: rgba(0, 0, 0, 0.62); color: #fff;
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.02em;
  pointer-events: none;
}

/* Which photo is the album cover, and the star that sets it. */
.pa-btn.is-cover { background: var(--accent); color: var(--accent-ink); }
.cover-badge {
  position: absolute; left: 6px; bottom: 6px;
  padding: 0.2rem 0.5rem; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-size: 0.7rem; font-weight: 600; letter-spacing: 0.02em;
  display: flex; align-items: center; gap: 0.25rem;
}
.photo-tile.is-cover img { outline: 3px solid var(--accent); outline-offset: -3px; }
.pa-btn {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border: none; border-radius: 8px;
  background: rgba(20, 14, 8, 0.72); color: #fff;
  font-size: 1.05rem; line-height: 1; cursor: pointer;
  backdrop-filter: blur(2px);
}
.pa-btn:hover { background: rgba(20, 14, 8, 0.9); }
.pa-del:hover { background: #c0392b; }

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

.private-badge {
  position: absolute; top: 6px; left: 6px;
  background: rgba(20, 14, 8, 0.78); color: #fff;
  font-size: 0.68rem; font-weight: 600;
  padding: 0.15rem 0.45rem; border-radius: 6px;
  letter-spacing: 0.02em;
}

/* album edit form */
.edit-form {
  display: flex; flex-direction: column; gap: 1rem;
  margin: 1.25rem 0;
  padding: 1.25rem; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
}
.ef-fields { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.ef-fields input { flex: 1; min-width: 160px; }
.ef-vis {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 0.75rem 1rem; margin: 0;
}
.ef-vis legend { padding: 0 0.4rem; color: var(--ink-soft); font-size: 0.9rem; }
.ef-vis label { display: block; padding: 0.25rem 0; cursor: pointer; }
.ef-vis input { margin-right: 0.5rem; }
.member-picker { padding: 0.5rem 0; }
.member-list { display: flex; flex-direction: column; gap: 0.5rem; }
.member-item {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding: 0.4rem 0.6rem; border-radius: 8px;
  background: var(--surface-2);
}
.member-item > label { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }
.mem-contrib-label { color: var(--ink-soft); font-size: 0.85rem; }
.mem-contrib-label input:disabled { opacity: 0.4; }
.ef-actions { display: flex; gap: 0.6rem; }

.btn-danger { background: #c0392b; color: #fff; border-color: #c0392b; box-shadow: none; }
.btn-danger:hover { filter: brightness(1.08); }

/* custom confirm modal */
.confirm-backdrop {
  position: fixed; inset: 0; z-index: 150;
  background: rgba(20, 14, 8, 0.6);
  display: flex; align-items: center; justify-content: center; padding: 1.5rem;
  animation: fade 0.15s ease;
}
.confirm-box {
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.5rem; max-width: 400px; width: 100%;
  box-shadow: var(--shadow);
}
.confirm-box p { margin: 0 0 1.25rem; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 0.6rem; }

/* ---------- how many photographs, and in what order ---------- */
.album-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin: 0.25rem 0 1rem;
}
.album-count-line { margin: 0; }
.sort-picker { display: flex; align-items: center; gap: 0.5rem; }
.sort-picker > span { color: var(--muted); font-size: 0.9rem; }
.sort-select {
  font: inherit; font-size: 0.9rem;
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 0.3rem 0.7rem;
}
.sort-note { margin: -0.5rem 0 1rem; font-size: 0.85rem; }

/* ---------- adjusting the cover ---------- */
.ca-box {
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.5rem; width: 100%; max-width: 560px;
  box-shadow: var(--shadow);
  max-height: 90vh; overflow-y: auto;
}
.ca-box h2 { margin: 0 0 0.35rem; font-size: 1.2rem; }
.ca-box > p { margin: 0 0 1rem; }

/* The whole photograph, uncropped - you cannot choose what to keep without seeing
   what there is. touch-action: none so dragging picks a point instead of scrolling
   the page out from under your thumb.

   The stage SHRINK-WRAPS the photograph (width: fit-content, and the image sized to
   fit rather than stretched). A letterboxed image inside a wider box would put dead
   margin inside the stage, and a click landing there would be read as a point on a
   part of the picture that is not there - anchoring the cover to an edge nobody
   clicked. The box the pointer is measured against must BE the photograph. */
.ca-stage {
  position: relative; touch-action: none; cursor: crosshair;
  border-radius: var(--radius); overflow: hidden;
  background: var(--surface-2);
  width: fit-content; max-width: 100%; margin: 0 auto;
}
.ca-img {
  display: block; max-width: 100%; max-height: 46vh;
  width: auto; height: auto; user-select: none; -webkit-user-drag: none;
}
.ca-dot {
  position: absolute; width: 26px; height: 26px;
  transform: translate(-50%, -50%);
  border: 2px solid #fff; border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}
.ca-dot::after {
  content: ''; position: absolute; inset: 9px;
  background: #fff; border-radius: 50%;
}

/* How far in. 1x is the photograph filling the frame - the floor, because below it the
   picture would stop covering and leave bars down the side of every album tile. */
.ca-zoom {
  display: flex; align-items: center; gap: 0.75rem;
  margin: 1rem 0 0;
}
.ca-zoom > span { color: var(--muted); font-size: 0.9rem; }
.ca-zoom input[type="range"] { flex: 1; accent-color: var(--accent); }
.ca-zoom output {
  min-width: 3.2em; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--muted); font-size: 0.9rem;
}

/* Show the whole photograph instead of cropping it. */
.ca-fit {
  display: flex; align-items: flex-start; gap: 0.6rem;
  margin: 0.9rem 0 0; cursor: pointer;
}
.ca-fit input { margin-top: 0.25rem; flex: none; accent-color: var(--accent); }
.ca-fit small { display: block; margin-top: 0.15rem; font-size: 0.8rem; line-height: 1.35; }

/* When nothing is being cropped, the anchor and the zoom genuinely do nothing. A live
   control that changes nothing reads as a bug, so they are dimmed and the dot is gone. */
.ca-stage.is-contain { cursor: default; }
.ca-zoom:has(input:disabled) { opacity: 0.45; }
.ca-zoom input:disabled { cursor: not-allowed; }

.ca-previews { display: flex; gap: 1rem; margin: 1rem 0 1.25rem; }
.ca-figure { margin: 0; flex: 1; }
.ca-figure figcaption { margin-top: 0.35rem; font-size: 0.8rem; text-align: center; }
.ca-prev {
  position: relative;
  overflow: hidden; border-radius: var(--radius); background: var(--surface-2);
}
.ca-prev img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The blurred backdrop, exactly as the real covers render it - the preview has to be
   telling the truth, or it is worse than no preview. */
.ca-prev .ca-prev-blur {
  position: absolute; inset: 0; z-index: 0;
  transform: scale(1.15);
  filter: blur(12px) saturate(1.1) brightness(0.55);
}
.ca-prev > img:not(.ca-prev-blur) { position: relative; z-index: 1; }
/* The two shapes the cover is really shown at. */
.ca-prev-card { aspect-ratio: 4 / 3; }
.ca-prev-hero { aspect-ratio: 2 / 1; }

.ca-actions { display: flex; justify-content: flex-end; gap: 0.6rem; flex-wrap: wrap; }

/* ---------- admin ---------- */
.admin-section {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.admin-section h2 {
  font-family: var(--font-display); font-size: 1.5rem; margin: 0 0 0.75rem;
}
.admin-hint { font-size: 0.85rem; margin-top: 0.25rem; }
/* A heading inside a section, for a report that comes back in more than one list. Smaller
   than the section's own h2 on purpose - it is a part of that section, not a rival to it. */
.admin-sub {
  font-family: var(--font-display); font-size: 1.05rem; margin: 1.5rem 0 0.5rem;
}

/* ---------- questions from the family ----------
   The admin console's side of the button under Who's here. A queue, not a thread: the
   open ones first, the finished ones faded but still there. */
.q-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.9rem 1.1rem; margin: 0.75rem 0;
}
.q-card.is-done { opacity: 0.55; }
.q-body { margin: 0; font-size: 0.98rem; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
.q-meta { margin: 0.45rem 0 0; font-size: 0.82rem; color: var(--muted); }
.q-acts { display: flex; gap: 0.5rem; margin-top: 0.75rem; }
/* How many things are sitting in the removed queue, beside its heading. A queue whose only
   sign is a heading that looks the same whether it holds nothing or nine is a queue nobody
   opens - and here that means somebody's photograph waiting unseen. */
.admin-count {
  display: inline-block; min-width: 1.6em; padding: 0.05em 0.5em; margin-left: 0.4rem;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 999px; font-size: 0.75em; font-weight: 700; vertical-align: middle;
}

/* Stat tiles and the disk bar, used by the Storage and Faces panels.
   These went missing: the storage panel shipped without them and has been rendering
   as a bulleted list with no bar ever since - two edits to this file crossed and one
   overwrote the other. */
.stat-row {
  list-style: none; padding: 0; margin: 1rem 0 0;
  display: flex; flex-wrap: wrap; gap: 0.75rem;
}
.stat {
  flex: 1 1 120px; min-width: 110px;
  display: flex; flex-direction: column; gap: 0.15rem;
  padding: 0.85rem 1rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.stat-n { font-family: var(--font-display); font-size: 1.75rem; line-height: 1; }
.stat-l { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }

.disk-bar {
  height: 10px; margin: 1rem 0 0.4rem;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px; overflow: hidden;
}
.disk-fill {
  height: 100%; width: 0%;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  border-radius: 999px;
  transition: width 0.3s ease;
}
/* Running out of room is the one thing an admin must not miss. */
.disk-fill.is-tight { background: linear-gradient(90deg, #b4462f, #d9663f); }

/* Backup state. A failing backup does not announce itself - it just goes quiet -
   so when it is not working, say so loudly. */
.backup-state {
  margin: 0; font-size: 1.05rem; font-weight: 600;
  display: flex; align-items: center; gap: 0.5rem;
}
.backup-state::before {
  content: ''; width: 10px; height: 10px; border-radius: 50%;
  background: var(--muted); flex: none;
}
.backup-state.is-good { color: var(--ink); }
.backup-state.is-good::before { background: #3f9a56; }
.backup-state.is-warn { color: #b4772f; }
.backup-state.is-warn::before { background: #d99a3f; }
.backup-state.is-bad { color: #b4462f; }
.backup-state.is-bad::before { background: #b4462f; }
.backup-state.is-working::before { background: var(--accent); animation: pulse 1s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: 0.25; } }

.families-list { list-style: none; padding: 0; margin: 1rem 0 0; display: flex; flex-wrap: wrap; gap: 0.5rem; }
.families-list li {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.4rem 0.5rem 0.4rem 0.85rem; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--line);
  font-size: 0.9rem;
}
.fam-x {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 1.05rem; line-height: 1; padding: 0 0.15rem;
}
.fam-x:hover { color: var(--danger, #b4462f); }
/* The rename-in-place pencil and its box. Families use it in the list; members use it
   in the table, where a relay sign-in can leave somebody called "p527r5vz7v". */
.fam-edit, .row-edit {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 0.9rem; line-height: 1; padding: 0 0.15rem;
}
.fam-edit:hover, .row-edit:hover { color: var(--ink); }
/* NOT .mem-name - that is the memorial page's headline, 3.6rem of Fraunces, and a
   members-table cell that borrowed the name inherited all of it. */
.member-name { display: inline-block; }
.row-rename {
  font: inherit; width: 9rem; max-width: 100%; padding: 0.15rem 0.4rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink);
}
.fam-rename {
  font: inherit; width: 11rem; padding: 0.15rem 0.4rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink);
}
.docs-list { list-style: none; padding: 0; margin: 1rem 0 0; display: flex; flex-direction: column; gap: 0.4rem; }
.docs-list li {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.5rem 0.75rem; border-radius: 8px;
  background: var(--surface-2); border: 1px solid var(--line);
}
.docs-list .doc-name { font-weight: 500; word-break: break-word; }
.docs-list .doc-meta { font-size: 0.82rem; margin-left: auto; white-space: nowrap; }

.admin-table {
  width: 100%; border-collapse: collapse; margin-top: 1rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.admin-table th, .admin-table td {
  text-align: left; padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--line);
}
.admin-table th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 600; }
.admin-table tr:last-child td { border-bottom: none; }
/* Upload progress. The fill tracks real bytes sent; once a photo is fully sent
   the server still has to resize it, so the bar switches to a travelling shimmer
   rather than sitting at 100% looking frozen. */
.upload-bar {
  height: 6px; margin: 0.75rem 0 0.35rem;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px; overflow: hidden;
}
.upload-fill {
  height: 100%; width: 0%;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  border-radius: 999px;
  transition: width 0.18s ease-out;
}
/* Bytes are sent, the server is resizing. The width still shows true batch
   progress - only the shimmer says "still working", so the bar never overstates
   how far along the batch actually is. */
.upload-fill.working {
  background: linear-gradient(90deg,
    var(--accent) 0%, var(--accent-2) 35%, var(--accent) 70%, var(--accent) 100%);
  background-size: 250% 100%;
  animation: upload-sweep 1.1s linear infinite;
}
@keyframes upload-sweep {
  from { background-position: 100% 0; }
  to   { background-position: -150% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .upload-fill.working { animation: none; }
}

.admin-table .ta-right { text-align: right; }
/* Apple relay addresses are long and opaque - keep them from crowding the row. */
.admin-table .mono-sm { font-size: 0.82rem; color: var(--muted); word-break: break-all; }
.admin-table input[type="text"] {
  margin-top: 0.4rem; padding: 0.35rem 0.5rem; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font-family: var(--font-body); font-size: 0.9rem;
}

.admin-table select {
  padding: 0.35rem 0.5rem; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font-family: var(--font-body); font-size: 0.9rem;
}
.role-badge {
  display: inline-block; padding: 0.2rem 0.6rem; border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent); font-size: 0.82rem; font-weight: 600;
}

@media (max-width: 640px) {
  .admin-table { font-size: 0.9rem; }
  .admin-table th, .admin-table td { padding: 0.55rem 0.6rem; }
}

/* ---------- lightbox ---------- */
.lightbox {
  position: fixed; inset: 0;
  background: rgba(20, 14, 8, 0.94);
  display: flex; align-items: center; justify-content: center;
  z-index: 100; padding: 1.5rem;
  animation: fade 0.2s ease;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.lb-stage { margin: 0; display: flex; align-items: center; justify-content: center; }
.lb-img {
  max-width: 92vw; max-height: 82vh; object-fit: contain;
  border-radius: 8px; box-shadow: 0 20px 60px rgba(0,0,0,0.6);
  touch-action: pan-y;           /* let the swipe handler own horizontal drags */
}
.lb-close {
  position: absolute; top: 1rem; right: 1.25rem;
  background: none; border: none; color: #fff;
  font-size: 2.6rem; cursor: pointer; line-height: 1;
  z-index: 2;
}
.lb-close:hover { color: var(--accent-2); }

/* Prev/next. Big tap targets - this gets used one-handed on a phone. */
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 64px; height: 45vh; max-height: 420px; min-height: 120px; border-radius: 14px;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #fff; font-size: 2rem; line-height: 1;
  cursor: pointer; z-index: 2;
  transition: background 0.15s ease, opacity 0.15s ease;
}
.lb-nav:hover { background: rgba(255, 255, 255, 0.24); }
.lb-nav:disabled { opacity: 0.25; cursor: default; }
.lb-prev { left: 1rem; }
.lb-next { right: 1rem; }

/* Bottom bar: just where you are in the album. */
.lb-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center;
  padding: 0.9rem 1.25rem;
  background: linear-gradient(to top, rgba(0,0,0,0.55), transparent);
  z-index: 2;
}
.lb-bar { justify-content: space-between; gap: 1.5rem; }
.lb-count { color: rgba(255,255,255,0.85); font-size: 0.95rem; font-variant-numeric: tabular-nums; flex: none; }

/* The caption: the point of a family archive. Sits under the photo, editable in
   place by whoever may manage it. */
.lb-caption-wrap { flex: 1; min-width: 0; }
/* When the photo was TAKEN. Above the caption, because it is the fact the caption
   is usually about. */
.lb-taken {
  margin: 0 0 0.15rem; color: rgba(255,255,255,0.72);
  font-size: 0.82rem; letter-spacing: 0.03em; text-transform: uppercase;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}
/* Who brought this one home. Quieter than the caption: it is a fact about the photograph,
   not the thing the photograph is about. */
.lb-added-by {
  margin: 0.25rem 0 0; color: rgba(255,255,255,0.6);
  font-size: 0.78rem; line-height: 1.4;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}

/* What the file says about itself. The same quiet weight as "added by" - it is a fact ABOUT the
   photograph, and dressing it as a warning would be the site making an accusation it has no
   business making. */
.lb-provenance {
  margin: 0.15rem 0 0; color: rgba(255,255,255,0.6);
  font-size: 0.78rem; line-height: 1.4;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}

.lb-caption {
  margin: 0; color: #fff; font-size: 1rem; line-height: 1.45;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
  overflow-wrap: anywhere;
}
.lb-caption:empty { display: none; }
.lb-caption-btn {
  background: none; border: none; padding: 0;
  color: rgba(255,255,255,0.75); font-family: var(--font-body); font-size: 0.9rem;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.lb-caption-btn:hover { color: #fff; }
.lb-caption-form { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.35rem; }
.lb-caption-form input {
  flex: 1; min-width: 0;
  padding: 0.5rem 0.7rem; border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.3);
  background: rgba(0,0,0,0.45); color: #fff;
  font-family: var(--font-body); font-size: 0.95rem;
}
.lb-caption-form input::placeholder { color: rgba(255,255,255,0.5); }
.lb-caption-form input[type="date"] {
  flex: 0 0 auto; width: auto;
  color-scheme: dark;                    /* so the native picker isn't white-on-white */
}
.lb-caption-err {
  margin: 0.35rem 0 0; color: #ffb4a2; font-size: 0.85rem;
  text-shadow: 0 1px 3px rgba(0,0,0,0.6);
}

/* A captioned tile says so, so you can find the ones still missing their story. */
.tile-caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 1.6rem 0.6rem 0.5rem;
  background: linear-gradient(to top, rgba(0,0,0,0.75), transparent);
  color: #fff; font-size: 0.78rem; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; pointer-events: none;
}

/* Tools: top-LEFT, deliberately far from the close button top-right. Big enough
   to hit with a thumb. */
.lb-tools {
  position: absolute; top: 1rem; left: 1.25rem;
  display: flex; gap: 0.6rem; z-index: 2;
}
.lb-tool {
  min-width: 54px; height: 54px; padding: 0 0.65rem;
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.22);
  color: #fff; line-height: 1;
  cursor: pointer; text-decoration: none;
  backdrop-filter: blur(6px);
  transition: background 0.15s ease, transform 0.1s ease;
}
.lb-tool:hover { background: rgba(255,255,255,0.3); color: #fff; }
.lb-tool:active { transform: scale(0.94); }
.lb-glyph { font-size: 1.45rem; line-height: 1; }
.lb-tool-count {
  font-size: 0.95rem; font-weight: 600; font-variant-numeric: tabular-nums;
}
.lb-tool-count:empty { display: none; }

.lb-tool.is-playing { background: var(--accent); border-color: var(--accent); }
/* A liked photo: filled heart, unmistakably on. */

/* The "more" button is a PHONE control. It does not exist anywhere there is room for the
   full row of tools. */
.lb-more { display: none; }
/* Same idea for the info toggle: a desktop has room to just show the caption/tags block,
   so this button - and the collapsing it does - only exists on phone/tablet widths. */
.lb-info-btn { display: none; }

/* The folded-away tools. Words, not glyphs - there is room for words here, and a picture
   of a printer is a guess where "Print sheet" is not. */
.lb-menu {
  position: absolute; top: 4.6rem; left: 0.6rem; z-index: 3;
  min-width: 190px;
  padding: 0.35rem;
  border-radius: 14px;
  background: rgba(28, 22, 16, 0.96);
  border: 1px solid rgba(255, 255, 255, 0.16);
  backdrop-filter: blur(8px);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
  display: flex; flex-direction: column;
}
.lb-menu-item {
  appearance: none; border: 0; background: none;
  color: #fff; font: inherit; text-align: left;
  padding: 0.75rem 0.9rem;          /* a thumb, not a mouse pointer */
  border-radius: 10px; cursor: pointer;
}
.lb-menu-item:hover, .lb-menu-item:focus-visible { background: rgba(255, 255, 255, 0.14); }

/* Phone or tablet: the caption/location/added-by/tag block is real estate the photo
   itself needs more. Collapse it behind the info button; the info button itself only
   exists at this width, so a desktop is untouched. */
@media (max-width: 799px) {
  .lb-info-btn { display: inline-flex; }
  .lb-caption-wrap { display: none; }
  .lb-caption-wrap.is-open { display: block; }
}

@media (max-width: 640px) {
  .lb-nav { width: 48px; height: 40vh; max-height: 300px; min-height: 90px; font-size: 1.6rem; }
  .lb-prev { left: 0.35rem; }
  .lb-next { right: 0.35rem; }
  .lb-img { max-width: 100vw; max-height: 74vh; }

  /* SEVEN PILLS IS 440px OF TOOLBAR, AND A PHONE IS 390px WIDE.
     The far end simply ran off the screen - and what fell off the end was download, print
     and order-a-print, which are exactly the ones nobody would think to go looking for.
     On a phone: keep the two with counts (like, comment) where a thumb expects them, and
     fold the rest behind "more". */
  .lb-tools { left: 0.6rem; top: 0.75rem; gap: 0.4rem; }
  #lb-faces-btn, #lb-play, #lb-download, #lb-print, #lb-order { display: none; }
  .lb-more { display: inline-flex; }

  /* Smaller pills too - the close button lives top-right and the two must never meet. */
  .lb-tool { min-width: 46px; height: 46px; padding: 0 0.5rem; }
  .lb-glyph { font-size: 1.25rem; }
}

/* Comments: a side panel on a desktop, a bottom sheet on a phone. */
.lb-badge {
  position: absolute; top: -4px; right: -4px;
  min-width: 18px; height: 18px; padding: 0 4px;
  border-radius: 999px; background: var(--accent); color: var(--accent-ink);
  font-size: 0.68rem; font-weight: 700; line-height: 18px; text-align: center;
}
#lb-comment { position: relative; }

.lb-comments {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(360px, 90vw);
  display: flex; flex-direction: column; gap: 0.75rem;
  padding: 1.25rem;
  background: var(--surface); border-left: 1px solid var(--line);
  z-index: 3; overflow: hidden;
}
/* With the panel open, the photo and the next button move aside rather than
   hiding underneath it. */
.lightbox.with-comments .lb-stage { margin-right: min(360px, 90vw); }
.lightbox.with-comments .lb-next { right: calc(min(360px, 90vw) + 1rem); }
.lightbox.with-comments .lb-bar { right: min(360px, 90vw); }

/* The panel's own header, with an explicit Close. Without it the only x on screen
   was the one that DELETES a comment - too easy to hit meaning "close". */
.lbc-head-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding-top: 0.25rem;
}
.lbc-title { font-family: var(--font-display); font-size: 1.25rem; margin: 0; }
.lbc-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem; }
.lbc-empty { color: var(--muted); font-size: 0.9rem; }
.lbc-item { border-bottom: 1px solid var(--line); padding-bottom: 0.6rem; }
.lbc-head { display: flex; align-items: baseline; gap: 0.5rem; }
.lbc-who { font-weight: 600; font-size: 0.9rem; }
.lbc-when { color: var(--muted); font-size: 0.75rem; }
/* A trash can, not an x - so it reads as "delete this comment", never "close". */
.lbc-del {
  margin-left: auto; background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: 0.9rem; line-height: 1; padding: 0 0.2rem;
  opacity: 0.7;
}
.lbc-del:hover { color: var(--danger, #b4462f); opacity: 1; }
.lbc-body { margin: 0.25rem 0 0; font-size: 0.95rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.lbc-form { display: flex; gap: 0.5rem; align-items: flex-end; }
.lbc-form textarea {
  flex: 1; resize: none;
  padding: 0.5rem 0.65rem; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font-family: var(--font-body); font-size: 0.95rem;
}

/* A photo with many people tagged can grow this list taller than the photo itself -
   the bar is pinned to the bottom and grows upward, so an untamed list just eats the
   picture. Cap it to a couple of rows and let the rest scroll instead. Triggers on a
   narrow screen (portrait phone) OR a short one (landscape phone) - a phone on its
   side is still a phone, even though it has "desktop" width. */
@media (max-width: 640px), (max-height: 500px) {
  .lb-people-list {
    max-height: 4.2rem;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
}

@media (max-width: 640px) {
  .lb-comments {
    top: auto; left: 0; right: 0; bottom: 0;
    width: auto; height: 62vh;
    border-left: none; border-top: 1px solid var(--line);
    border-radius: 16px 16px 0 0;
  }
  /* On a phone the panel is a bottom sheet, so nothing needs to move sideways. */
  .lightbox.with-comments .lb-stage { margin-right: 0; }
  .lightbox.with-comments .lb-next { right: 0.35rem; }
  .lightbox.with-comments .lb-bar { right: 0; bottom: 62vh; }
  .lightbox.with-comments .lb-img { max-height: 34vh; }
}

/* Contact sheet: the album grid itself, printed. Every photo with its caption
   underneath - so a printed sheet still says who and when. */
@media print {
  body.printing-sheet * { visibility: hidden !important; }
  body.printing-sheet #main,
  body.printing-sheet #album-title,
  body.printing-sheet #photos,
  body.printing-sheet .photo-tile,
  body.printing-sheet .photo-tile img,
  body.printing-sheet .tile-caption {
    visibility: visible !important;
  }
  body.printing-sheet #album-title { font-size: 20pt; margin-bottom: 0.5rem; }
  body.printing-sheet .album-actions,
  body.printing-sheet .back-link,
  body.printing-sheet #album-desc,
  body.printing-sheet #upload-status,
  body.printing-sheet .photo-actions,
  body.printing-sheet .tile-marks,
  body.printing-sheet .cover-badge,
  body.printing-sheet .video-badge,
  body.printing-sheet #lightbox {
    display: none !important;
  }
  body.printing-sheet #photos {
    display: grid !important;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
  }
  body.printing-sheet .photo-tile {
    break-inside: avoid; page-break-inside: avoid;
    border-radius: 0; box-shadow: none; background: none;
    position: relative;
  }
  body.printing-sheet .photo-tile img {
    width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover;
  }
  /* On paper the caption sits UNDER the photo, not over it - a white-on-photo
     overlay is unreadable in print. */
  body.printing-sheet .tile-caption {
    position: static; background: none !important; color: #000 !important;
    padding: 0.25rem 0 0.5rem; font-size: 8pt; -webkit-line-clamp: 3;
  }
}

/* Printing a single photo from the lightbox: the page furniture goes, the photo
   fills the sheet. */
@media print {
  body * { visibility: hidden !important; }
  #lightbox, #lightbox .lb-stage, #lightbox .lb-img,
  /* The caption and the date print WITH the photo - a printed picture with no note
     about who or when is exactly the problem these exist to solve. */
  #lightbox .lb-bar, #lightbox .lb-caption-wrap, #lightbox .lb-caption,
  #lightbox .lb-taken {
    visibility: visible !important;
  }
  /* Printing from a phone-width browser must not inherit the phone/tablet collapse
     rule above - a print with no caption defeats the whole point of this block. */
  #lightbox .lb-caption-wrap { display: block !important; }
  #lightbox .lb-taken { color: #555 !important; text-shadow: none !important; font-size: 9pt; text-align: center; }
  #lightbox {
    position: absolute; inset: 0; background: #fff !important;
    padding: 0; display: block;
  }
  #lightbox .lb-close, #lightbox .lb-nav, #lightbox .lb-comments,
  #lightbox .lb-count, #lightbox .lb-caption-btn, #lightbox .lb-caption-form {
    display: none !important;
  }
  #lightbox .lb-bar {
    position: static; background: none !important; padding: 0.75rem 1rem;
  }
  #lightbox .lb-caption {
    color: #000 !important; text-shadow: none !important;
    font-size: 12pt; text-align: center;
  }
  #lightbox .lb-img {
    max-width: 100%; max-height: 100vh;
    box-shadow: none; border-radius: 0;
  }
}

/* ---------- footer ---------- */
.site-footer {
  text-align: center; padding: 1.5rem;
  color: var(--muted); font-size: 0.85rem;
}

@media (max-width: 640px) {
  body { font-size: 16px; }
  .app-main, .site-header, .app-header { padding-left: 1.1rem; padding-right: 1.1rem; }
  .album-grid { gap: 1.25rem; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

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

/* --- the background picker ---------------------------------------------
   Choosing wallpaper is something you do by LOOKING at it, so every choice is shown
   as itself: the presets render their own CSS, and an uploaded image shows the image.
   Nothing is described in words that could be shown instead. */
.bg-picker-back {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center;
  background: rgba(20, 15, 10, 0.55);
  padding: 1rem;
}
.bg-picker {
  width: min(680px, 100%);
  max-height: 85vh; overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem;
}
.bg-picker-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.bg-picker-head h3 { margin: 0; }
.bg-close {
  background: none; border: none; cursor: pointer;
  color: var(--ink-soft); font-size: 1.5rem; line-height: 1;
  min-width: 2.2rem; min-height: 2.2rem; border-radius: 999px;
}
.bg-close:hover { background: var(--surface-2); }

.bg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 0.75rem;
}
.bg-swatch {
  display: flex; flex-direction: column; gap: 0.4rem;
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font-body); color: var(--ink-soft); font-size: 0.8rem;
}
.bg-swatch-art {
  display: block; height: 74px; border-radius: 10px;
  border: 2px solid var(--line);
  box-shadow: var(--shadow-sm);
}
.bg-swatch.is-on .bg-swatch-art { border-color: var(--accent); }
.bg-swatch.is-on .bg-swatch-label { color: var(--accent); font-weight: 600; }
.bg-swatch-none {
  background:
    linear-gradient(45deg, transparent 46%, var(--line) 46% 54%, transparent 54%),
    var(--bg);
}
.bg-swatch-label { text-align: center; }

.bg-upload {
  display: flex; align-items: center; gap: 0.75rem;
  margin-top: 1.25rem; padding-top: 1rem;
  border-top: 1px solid var(--line);
  cursor: pointer;
}
.bg-upload-note { font-size: 0.85rem; color: var(--muted); }

/* A family's name on the home page is a way in to their own page. */
.home-family-link { color: inherit; text-decoration: none; }
.home-family-link:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
/* An always-visible nudge that the family title is a link to their page - some people never
   notice a title is clickable, least of all on a phone with no hover. */
.home-family-cue {
  display: inline-block; margin-left: 10px; vertical-align: middle; white-space: nowrap;
  font-family: var(--font-body, inherit); font-size: 0.8rem; font-weight: 600;
  color: var(--accent); text-decoration: none;
}
.home-family-link:hover .home-family-cue { text-decoration: underline; }
.fam-albums-head { margin: 2.5rem 0 1rem; }
.family-narrative { margin: 1.5rem 0; }

/* --- print orders: is any of this costing me money? --------------------- */
.pr-verdict {
  margin: 0 0 1rem;
  padding: 0.7rem 1rem;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-weight: 600;
}
.pr-verdict.is-good { border-color: #6ec07d; color: #2f7d43; background: rgba(110, 192, 125, 0.10); }
.pr-verdict.is-bad  { border-color: #b4462f; color: #b4462f; background: rgba(180, 70, 47, 0.10); }
:root[data-theme="dark"] .pr-verdict.is-good { color: #8fd39f; }

/* A table must never make the page itself scroll sideways. */
.table-scroll { overflow-x: auto; }

.pr-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.pr-table th, .pr-table td { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--line); text-align: left; }
.pr-table th { color: var(--muted); font-weight: 600; font-size: 0.8rem; white-space: nowrap; }
.pr-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pr-table tr.is-bad td { background: rgba(180, 70, 47, 0.07); }
.pr-table td.is-bad { color: #b4462f; font-weight: 600; }
.pr-err { color: #b4462f; font-weight: 700; cursor: help; }


/* --- album title colours ------------------------------------------------
   One class per colour, generated from the palette in src/backgrounds.js. The album
   stores a NAME ('jade'), and the name selects a class - a colour value out of the
   database is never written into a style attribute, because a colour field is a CSS
   injection field.

   Each colour carries a light shade and a dark shade: a title that reads beautifully
   on cream can be invisible on charcoal, and the archive is read in both. */
.tc-terracotta { color: #c15f3c; }
.tc-saigon { color: #a3272b; }
.tc-gold { color: #9a7423; }
.tc-jade { color: #2f6f56; }
.tc-indigo { color: #3b4b8c; }
.tc-plum { color: #7a3b63; }
.tc-rose { color: #b03a5b; }
.tc-amber { color: #b06a12; }
.tc-olive { color: #5c6b25; }
.tc-teal { color: #1f6a70; }
.tc-sky { color: #2f6aa8; }
.tc-lavender { color: #6a4fa3; }
.tc-cocoa { color: #6b4a34; }
.tc-slate { color: #4a5560; }
.tc-ink { color: #2a2521; }

:root[data-theme="dark"] .tc-terracotta { color: #e28a54; }
:root[data-theme="dark"] .tc-saigon { color: #e2685c; }
:root[data-theme="dark"] .tc-gold { color: #d8ac4a; }
:root[data-theme="dark"] .tc-jade { color: #68b691; }
:root[data-theme="dark"] .tc-indigo { color: #8f9ee0; }
:root[data-theme="dark"] .tc-plum { color: #c98fb4; }
:root[data-theme="dark"] .tc-rose { color: #ec8fa8; }
:root[data-theme="dark"] .tc-amber { color: #e9a13b; }
:root[data-theme="dark"] .tc-olive { color: #a8bf5e; }
:root[data-theme="dark"] .tc-teal { color: #5fbec4; }
:root[data-theme="dark"] .tc-sky { color: #7db6e8; }
:root[data-theme="dark"] .tc-lavender { color: #b39ae8; }
:root[data-theme="dark"] .tc-cocoa { color: #c39a7a; }
:root[data-theme="dark"] .tc-slate { color: #aab6c2; }
:root[data-theme="dark"] .tc-ink { color: #f1eade; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tc-terracotta { color: #e28a54; }
  :root:not([data-theme="light"]) .tc-saigon { color: #e2685c; }
  :root:not([data-theme="light"]) .tc-gold { color: #d8ac4a; }
  :root:not([data-theme="light"]) .tc-jade { color: #68b691; }
  :root:not([data-theme="light"]) .tc-indigo { color: #8f9ee0; }
  :root:not([data-theme="light"]) .tc-plum { color: #c98fb4; }
  :root:not([data-theme="light"]) .tc-rose { color: #ec8fa8; }
  :root:not([data-theme="light"]) .tc-amber { color: #e9a13b; }
  :root:not([data-theme="light"]) .tc-olive { color: #a8bf5e; }
  :root:not([data-theme="light"]) .tc-teal { color: #5fbec4; }
  :root:not([data-theme="light"]) .tc-sky { color: #7db6e8; }
  :root:not([data-theme="light"]) .tc-lavender { color: #b39ae8; }
  :root:not([data-theme="light"]) .tc-cocoa { color: #c39a7a; }
  :root:not([data-theme="light"]) .tc-slate { color: #aab6c2; }
  :root:not([data-theme="light"]) .tc-ink { color: #f1eade; }
}

/* A title written OVER the cover photograph is a different problem from a title on
   cream. The scrim under it is dark whatever the site's theme is, so the LIGHT shade
   of a colour would be a dark word on a dark picture. The hero always takes the dark
   theme's shade, plus a shadow, so the name stays legible against whatever the
   photograph happens to be doing underneath it. */
.hero-meta h2.tc-terracotta { color: #e28a54; }
.hero-meta h2.tc-saigon { color: #e2685c; }
.hero-meta h2.tc-gold { color: #d8ac4a; }
.hero-meta h2.tc-jade { color: #68b691; }
.hero-meta h2.tc-indigo { color: #8f9ee0; }
.hero-meta h2.tc-plum { color: #c98fb4; }
.hero-meta h2.tc-rose { color: #ec8fa8; }
.hero-meta h2.tc-amber { color: #e9a13b; }
.hero-meta h2.tc-olive { color: #a8bf5e; }
.hero-meta h2.tc-teal { color: #5fbec4; }
.hero-meta h2.tc-sky { color: #7db6e8; }
.hero-meta h2.tc-lavender { color: #b39ae8; }
.hero-meta h2.tc-cocoa { color: #c39a7a; }
.hero-meta h2.tc-slate { color: #aab6c2; }
.hero-meta h2.tc-ink { color: #f1eade; }
.hero-meta h2[class*="tc-"] { text-shadow: 0 2px 10px rgba(0, 0, 0, 0.55); }

/* Choosing the colour of an album's name. Each swatch is written IN that colour and in
   the display face the title actually uses - you are picking how the name will LOOK, so
   the choice has to look like it. */
.ef-color { border: none; padding: 0; margin: 0.75rem 0 0; }
.ef-color legend { font-size: 0.85rem; color: var(--muted); padding: 0; margin-bottom: 0.4rem; }
.color-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.color-swatch {
  font-family: var(--font-display);
  font-size: 1.05rem; font-weight: 600;
  min-width: 2.6rem; min-height: 2.4rem;
  padding: 0 0.5rem;
  /* Filled in with its own colour. currentColor here IS the tc-* colour the swatch is
     written in, so a swatch is a wash of the colour with the letters sitting on it -
     and every colour added to the palette fills itself in, with no CSS to add. */
  background: color-mix(in srgb, currentColor 16%, var(--surface));
  border: 2px solid color-mix(in srgb, currentColor 40%, var(--line));
  border-radius: 10px;
  cursor: pointer;
}
.color-swatch:hover { background: color-mix(in srgb, currentColor 28%, var(--surface)); }
.color-swatch.is-on { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent); }

/* --- a plain stacked form (label, box, buttons) -------------------------
   Written for Talk, which is gone; the calendar's event form still uses it, so it
   keeps the shape and loses the name of the feature that no longer exists. */
.stacked-form { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 1.25rem; }
.stacked-form textarea {
  width: 100%; padding: 0.75rem; border-radius: 12px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font-family: var(--font-body); font-size: 1rem; line-height: 1.6; resize: vertical;
}

/* --- header links ------------------------------------------------------
   Albums / People / Talk, sitting to the left of your name.

   Hidden on a phone ON PURPOSE: the header there is already the wordmark plus your
   name, and three more links would wrap onto a second line and squeeze the page. They
   are in the name menu instead, which is where a phone user goes looking anyway. This
   is the one thing a wide screen has that a phone does not - room. */
.nav-links { display: none; }

@media (min-width: 800px) {
  .nav-links { display: flex; align-items: center; gap: 0.25rem; }
}

.nav-link {
  position: relative;
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.4rem 0.75rem;
  border-radius: 999px;
  color: var(--ink-soft); text-decoration: none;
  font-size: 0.92rem;
  border: 1px solid transparent;
}
.nav-link:hover { background: var(--surface-2); border-color: var(--line); color: var(--ink); }

/* Where you already are. Set from usermenu.js, which knows the path. */
.nav-link.is-here { color: var(--accent); font-weight: 600; }

/* Something new has been said. A number, not a dot: "3 new" is a reason to click and a
   bare dot is a nag. */
.nav-badge {
  min-width: 1.15rem; height: 1.15rem; padding: 0 0.3rem;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 999px;
  font-size: 0.7rem; font-weight: 700; line-height: 1;
}

/* Writing a family's story, and the button row under any of the story forms. These class
   names were used on the family page before anybody wrote a rule for them - which is the
   storage-panel bug exactly, and is why the stylesheet check now runs over every page and
   not only the admin console. */
.narrative-form { display: flex; flex-direction: column; gap: 0.6rem; margin: 1rem 0 2rem; }
.narrative-form textarea {
  width: 100%; padding: 0.9rem 1rem; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font-family: var(--font-body); font-size: 1rem; line-height: 1.7; resize: vertical;
}
.narrative-form textarea:focus { outline: none; border-color: var(--accent); }

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

/* Somebody paid and nothing is printing. The loudest thing on the page, because the
   alternative is a relative waiting for photographs that were never ordered. */
.pr-stranded {
  margin: 0 0 1.25rem;
  padding: 1rem 1.15rem;
  border: 2px solid #b4462f;
  border-radius: 12px;
  background: rgba(180, 70, 47, 0.10);
}
.pr-stranded p { margin: 0 0 0.75rem; color: #b4462f; font-weight: 600; }
.pr-stranded .admin-hint { color: var(--muted); font-weight: 400; margin: 0.6rem 0 0; }

/* ---------- the calendar ---------- */
/* The month grid and the list are the SAME events laid out twice. The grid is for a
   desktop, where there is room for squares; the list is for a phone, where there is not.
   Which one you see is decided here and nowhere else. */
.cal-grid-wrap { margin: 1.5rem 0 2.5rem; }
.cal-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
.cal-head h2 {
  margin: 0; font-family: var(--font-display); font-size: 1.4rem;
  min-width: 11ch;
}
.cal-head #cal-today { margin-left: auto; }

.cal-dow, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-dow {
  margin-bottom: 4px; color: var(--muted); font-size: 0.75rem;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.cal-dow span { padding: 0 0.35rem; }

.cal-cell {
  min-height: 92px; padding: 0.35rem;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface);
  display: flex; flex-direction: column; gap: 3px;
  /* A grid item will not shrink below its longest word unless told to. Without this,
     "Eric and Tram's Wedding Anniversary (37)" made ITS column twice the width of the
     other six - one long name, and the whole week went crooked. The chip was never
     spilling out of the cell; the cell was being stretched by the chip. */
  min-width: 0;
  overflow: hidden;
}
.cal-cell.is-empty { background: transparent; border-color: transparent; }
.cal-day { font-size: 0.8rem; color: var(--muted); }
.cal-cell.is-today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.cal-cell.is-today .cal-day { color: var(--accent); font-weight: 700; }

/* A long name WRAPS to two lines and is then cut off, rather than being squeezed onto one
   line as "Eric and Tram's We...". Two lines is enough to tell an anniversary from a
   birthday at a glance, which is all a square in a month grid has to do; the full name is
   in the list below, and in the tooltip. */
.cal-chip {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  width: 100%; text-align: left;
  font: inherit; font-size: 0.75rem; line-height: 1.25;
  padding: 0.2rem 0.35rem; border: 0; border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--ink); cursor: pointer;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: anywhere;     /* a name with no spaces must not stretch the square either */
}
.cal-chip:hover { background: color-mix(in srgb, var(--accent) 38%, transparent); }

.cal-list-h { font-family: var(--font-display); font-size: 1.4rem; margin: 0 0 0.75rem; }
.event-list { display: flex; flex-direction: column; gap: 0.75rem; }

.event-card {
  display: flex; gap: 1rem; align-items: flex-start;
  padding: 0.9rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.event-card.is-today { border-color: var(--accent); }
.event-card.is-flash { animation: flash 1.2s ease; }
@keyframes flash {
  0%, 100% { background: var(--surface); }
  30% { background: color-mix(in srgb, var(--accent) 20%, var(--surface)); }
}
.ev-when {
  flex: none; width: 3.2rem; text-align: center;
  display: flex; flex-direction: column; line-height: 1.05;
}
.ev-date-big { font-family: var(--font-display); font-size: 1.7rem; }
.ev-month { font-size: 0.7rem; text-transform: uppercase; color: var(--muted); letter-spacing: 0.05em; }
.ev-body { flex: 1; min-width: 0; }
.ev-title { margin: 0; font-size: 1.05rem; }
.ev-soon {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 0.1rem 0.4rem; border-radius: 999px; vertical-align: middle;
  background: var(--accent); color: #1a1208; font-weight: 700;
}
.ev-full { margin: 0.15rem 0 0; font-size: 0.85rem; }
.ev-desc { margin: 0.45rem 0 0; white-space: pre-wrap; }
.ev-by { margin: 0.45rem 0 0; font-size: 0.78rem; }
.ev-actions { flex: none; display: flex; flex-direction: column; gap: 0.3rem; }

.ev-dates { gap: 1rem; }
.ev-date { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.ev-date input { font: inherit; padding: 0.4rem 0.6rem; border-radius: 8px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink); }
.form-error { color: #e06a5a; margin: 0.25rem 0 0; }
.cal-past { margin-top: 2rem; }
.cal-past summary { cursor: pointer; color: var(--muted); margin-bottom: 0.75rem; }

/* WHO GETS THE GRID: a desktop, and a tablet. Not a phone.
   A month of squares on a 390px screen is unreadable, so on a phone the list IS the
   calendar. But an iPad has room for it, and the old 720px cut-off was catching tablets
   in portrait (768-834px is a hair over it, but a small tablet or a split-screen pane is
   under) and handing them the phone view.

   TWO tests, because width alone lies. A phone held SIDEWAYS is ~850px wide - wider than
   an iPad in portrait - and would sail past any width-only rule, but it is only ~390px
   TALL, and six rows of squares in 390px is nothing anybody can read. So: the list wins
   when the screen is narrow OR short, and the grid needs both room across and room down. */
@media (max-width: 599px), (max-height: 479px) {
  .cal-grid-wrap { display: none; }
}

/* A tablet has room for the grid, but not for a desktop's worth of padding in each square.
   Same layout, tightened - not a different one to maintain. */
@media (min-width: 600px) and (max-width: 900px) {
  .cal-cell { min-height: 74px; padding: 0.25rem; border-radius: 8px; }
  .cal-dow, .cal-grid { gap: 3px; }
  .cal-chip { font-size: 0.68rem; padding: 0.15rem 0.25rem; }
  .cal-head h2 { font-size: 1.2rem; min-width: 9ch; }
}

/* ---------- who's here ---------- */
.online-list { display: flex; flex-direction: column; gap: 0.5rem; margin: 1.25rem 0 2rem; }
.online-row {
  display: flex; align-items: center; gap: 0.85rem;
  padding: 0.7rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.online-avatar {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  background: var(--surface-2); color: var(--accent);
  font-weight: 700; font-size: 0.85rem;
}
.online-avatar img { width: 100%; height: 100%; object-fit: cover; }
.online-who { display: flex; flex-direction: column; min-width: 0; }
.online-name { font-weight: 600; }
.online-when { font-size: 0.82rem; }
.online-dot {
  flex: none; margin-left: auto;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--line);
}
.online-row.is-online .online-dot {
  background: #4ca96a;
  box-shadow: 0 0 0 3px color-mix(in srgb, #4ca96a 25%, transparent);
}
.online-optout { margin-top: 2rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }

/* ---------- ask the admin a question ----------
   Where Quick Chat used to stand. One button, and under it what this person has already
   asked - so nobody has to ask twice to find out the first one was heard. */
.ask { margin: 2rem 0 0; padding-top: 1.5rem; border-top: 1px solid var(--line); }
.ask-head h2 { margin: 0 0 0.15rem; font-family: var(--font-display); font-size: 1.4rem; }
.ask-head p { margin: 0 0 0.9rem; font-size: 0.9rem; max-width: 46ch; }
/* What happens AFTER you press send - the part somebody wonders about later, so it is said
   before they ask rather than left to be worked out from an empty list. */
.ask-note { font-size: 0.85rem; }

.ask-form { display: flex; flex-direction: column; gap: 0.6rem; max-width: 46rem; }
.ask-form textarea {
  width: 100%; padding: 0.75rem; border-radius: 12px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font-family: var(--font-body); font-size: 1rem; line-height: 1.6; resize: vertical;
}
.ask-form textarea:focus { outline: none; border-color: var(--accent); }
.ask-actions { display: flex; gap: 0.5rem; }

.ask-mine { margin-top: 1.5rem; }
.ask-mine-head { font-size: 0.95rem; margin: 0 0 0.5rem; }
/* Said beside the heading, not under it: who else can read this is part of what the
   heading means, and a line below would be read as a footnote or not at all. */
.ask-mine-note { font-weight: 400; color: var(--muted); font-size: 0.82rem; }
.ask-mine-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.ask-mine-item {
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  padding: 0.7rem 0.9rem;
}
.ask-mine-body { margin: 0; font-size: 0.95rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.ask-mine-meta { margin: 0.3rem 0 0; font-size: 0.78rem; color: var(--muted); }

/* On a phone this section IS the calendar; on a desktop it sits under the grid. */
.cal-list-wrap { margin-top: 1rem; }

/* ---------- choosing the face you are known by ---------- */
.online-mine { margin: 0.25rem 0 0; }

.ap-box {
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.5rem; width: 100%; max-width: 560px;
  box-shadow: var(--shadow);
  max-height: 90vh; overflow-y: auto;
}
.ap-box h2 { margin: 0 0 0.35rem; font-size: 1.2rem; }
.ap-h3 {
  margin: 1.25rem 0 0.6rem; font-size: 0.8rem;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted);
}
/* The photographs they are tagged in. Square, because the result is square - what you
   see here is what the face will be cropped to. */
.ap-choices {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 0.5rem;
}
.ap-choice {
  padding: 0; border: 2px solid transparent; border-radius: 10px;
  overflow: hidden; cursor: pointer; background: var(--surface-2);
  aspect-ratio: 1 / 1;
}
.ap-choice:hover, .ap-choice:focus-visible { border-color: var(--accent); }
.ap-choice img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ap-actions {
  display: flex; justify-content: space-between; gap: 0.6rem;
  margin-top: 1.25rem;
}
/* The repeat control on the event form. */
.ev-repeat { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.ev-repeat > span { color: var(--muted); }
.ev-repeat small { line-height: 1.35; }
/* "turns 68" / "40 years" - the arithmetic a birthday is actually for. */
.ev-years { color: var(--accent); font-weight: 600; }
/* A repeating date, marked so the grid does not read as a wall of identical chips. */
.cal-chip.is-repeat { border-left: 3px solid var(--accent); }

/* ---------- private chats ---------- */
.chat { margin: 2rem 0 0; padding-top: 1.5rem; border-top: 1px solid var(--line); }
.chat-head h2 { margin: 0 0 0.15rem; font-family: var(--font-display); font-size: 1.4rem; }
.chat-head p { margin: 0 0 0.75rem; font-size: 0.85rem; max-width: 56ch; }

/* The conversations you are in. A row per chat: who is in it, what was last said, and how
   much of it you have not read. */
.chat-rooms { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 1rem; }
.chat-room-row {
  display: flex; align-items: center; gap: 0.75rem; width: 100%;
  padding: 0.7rem 0.9rem; text-align: left;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; color: inherit; cursor: pointer;
}
.chat-room-row:hover { border-color: var(--accent); }
/* Something waiting to be read is worth showing without the badge doing all the work. */
.chat-room-row.is-unread { border-left: 3px solid var(--accent); background: var(--surface-2); }
.chat-row-faces { display: flex; flex: none; }
/* Overlapped, so three faces cost the width of about two. */
.chat-row-faces .chat-face + .chat-face { margin-left: -10px; }
.chat-row-main { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1; }
.chat-row-title { font-weight: 600; font-size: 0.95rem; }
.chat-row-preview {
  font-size: 0.82rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-row-count { flex: none; }
/* A conversation that was swept after a day of silence, said once, where it used to be. */
.chat-gone { margin: 0.75rem 0 0; font-size: 0.85rem; }

/* The head of an open conversation: back, who you are talking to, and the two things you can
   do to the room itself. */
.chat-room-head {
  display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.chat-room-naming { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.chat-room-naming strong { font-size: 1rem; overflow-wrap: anywhere; }
.chat-room-naming span { font-size: 0.78rem; }

/* Everybody in it, with a dot for who is still at their screen. */
.chat-members { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.6rem; }
.chat-member {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.15rem 0.5rem 0.15rem 0.15rem;
  background: var(--surface-2); border-radius: 999px;
  font-size: 0.78rem;
}
.chat-member .chat-face { width: 22px; height: 22px; font-size: 0.6rem; }
.chat-member-name { color: var(--ink-soft); }
.chat-member.is-online .chat-member-name { color: var(--ink); font-weight: 600; }

/* Picking who to talk to. The same panel starts a chat and adds somebody to one. */
.chat-picker {
  margin-top: 1rem; padding: 1rem;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.chat-picker-title { margin: 0 0 0.2rem; font-size: 1rem; }
.chat-picker-note { margin: 0 0 0.75rem; font-size: 0.82rem; max-width: 56ch; }
.chat-picker-list { display: flex; flex-direction: column; gap: 0.35rem; max-height: 40vh; overflow-y: auto; }
.chat-pick {
  display: flex; align-items: center; gap: 0.55rem;
  padding: 0.35rem 0.5rem; border-radius: 10px; cursor: pointer;
}
.chat-pick:hover { background: var(--surface-2); }
.chat-pick-name { font-size: 0.92rem; }
.chat-picker-acts { display: flex; gap: 0.5rem; margin-top: 0.85rem; }

/* A room with a floor and a ceiling: it scrolls inside itself, so the page does not grow
   without end and the composer never walks off the bottom of the screen. */
.chat-log {
  height: min(48vh, 420px);
  overflow-y: auto;
  padding: 0.75rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
  display: flex; flex-direction: column; gap: 0.35rem;
}
.chat-empty { margin: 0.75rem 0 0; }

.chat-day {
  align-self: center;
  margin: 0.6rem 0 0.35rem;
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted);
}

.chat-msg { display: flex; gap: 0.6rem; align-items: flex-end; max-width: 82%; }
/* Your own words sit on the other side of the room, which is how you find them at a glance. */
.chat-msg.is-me { align-self: flex-end; flex-direction: row-reverse; }
/* A run from the same person is one utterance, not five - no repeated face, no repeated name. */
.chat-msg.is-runon { margin-top: -0.25rem; }

.chat-face {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  background: var(--surface-2); color: var(--accent);
  font-size: 0.7rem; font-weight: 700;
}
.chat-face:empty { background: transparent; }
.chat-face img { width: 100%; height: 100%; object-fit: cover; }

.chat-bubble-wrap { min-width: 0; }
.chat-who { display: flex; gap: 0.4rem; align-items: baseline; margin: 0 0 0.15rem 0.2rem; }
.chat-msg.is-me .chat-who { flex-direction: row-reverse; }
.chat-name { font-size: 0.8rem; font-weight: 600; }
.chat-time { font-size: 0.72rem; }

.chat-bubble {
  position: relative;
  padding: 0.45rem 0.7rem;
  border-radius: 14px;
  background: var(--surface-2);
  overflow-wrap: anywhere;          /* a pasted URL must not stretch the room */
}
.chat-msg.is-me .chat-bubble { background: color-mix(in srgb, var(--accent) 26%, var(--surface-2)); }

.chat-del {
  position: absolute; top: -6px; right: -6px;
  width: 18px; height: 18px; line-height: 1;
  border: 0; border-radius: 50%;
  background: var(--line); color: var(--ink);
  cursor: pointer; opacity: 0; transition: opacity 0.12s ease;
}
.chat-msg:hover .chat-del, .chat-del:focus-visible { opacity: 1; }

/* Somebody reading further up must not be yanked to the bottom by a new message - they
   get told instead, and go when they choose. */
.chat-new {
  align-self: center; margin-top: 0.5rem;
  border: 0; border-radius: 999px; cursor: pointer;
  padding: 0.3rem 0.8rem; font: inherit; font-size: 0.8rem;
  background: var(--accent); color: #1a1208; font-weight: 600;
}

.chat-compose { display: flex; gap: 0.5rem; margin-top: 0.75rem; align-items: flex-end; }
.chat-compose textarea {
  flex: 1; resize: none; font: inherit;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--surface); color: var(--ink);
  max-height: 140px;
}
/* What a view-only account gets instead of the box: the room, and a sentence saying why
   there is nothing to type into. */
.chat-readonly { margin: 0.75rem 0 0; font-size: 0.85rem; }

/* ---------- being told the room is talking ---------- */
.chat-notify-on { margin: 0 0 0.75rem; font-size: 0.85rem; }

/* How many things have been said that you have not read. Sits on the "Who's here" link on
   every page - it is the whole point of the notice: you are somewhere else, and the room
   is talking. The link is built by bookheader.js, which is where the span comes from. */
.nav-chat-badge { margin-left: 0.35rem; }

/* The notice, wherever you happen to be on the site. Bottom-right on a desktop; across the
   bottom of a phone, where a thumb can reach it and where it covers nothing that matters. */
.chat-toast {
  position: fixed; right: 1.25rem; bottom: 1.25rem; z-index: 200;
  width: min(360px, calc(100vw - 2.5rem));
  display: flex; align-items: flex-start; gap: 0.5rem;
  padding: 0.75rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3);
  animation: toast-in 0.22s ease both;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.ct-link {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 0.6rem;
  text-decoration: none; color: inherit;
}
.ct-face {
  flex: none; width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  background: var(--surface-2); color: var(--accent);
  font-weight: 700; font-size: 0.85rem;
}
.ct-face img { width: 100%; height: 100%; object-fit: cover; }
.ct-text { min-width: 0; display: flex; flex-direction: column; }
.ct-who { font-weight: 600; font-size: 0.9rem; }
.ct-more { color: var(--muted); font-weight: 400; font-size: 0.8rem; }
/* One line of it. The rest is in the room, which is one tap away. */
.ct-body {
  color: var(--muted); font-size: 0.85rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ct-close {
  flex: none; border: 0; background: none; cursor: pointer;
  color: var(--muted); font-size: 1.2rem; line-height: 1; padding: 0.1rem 0.2rem;
}
@media (max-width: 599px) {
  .chat-toast { left: 0.75rem; right: 0.75rem; bottom: 0.75rem; width: auto; }
}

/* ---------- the same photograph, more than once ---------- */
.dup-groups { display: flex; flex-direction: column; gap: 1.5rem; margin-top: 1.25rem; }
.dup-group {
  padding: 1rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.dup-h {
  display: flex; align-items: center; gap: 0.6rem;
  margin: 0 0 0.75rem; font-size: 1.05rem;
}
.dup-how {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 0.15rem 0.5rem; border-radius: 999px;
  background: var(--surface-2); color: var(--muted); font-weight: 700;
}
/* An identical FILE is a fact. "Looks the same" is a judgement, and is coloured as one. */
.dup-how.is-exact { background: var(--accent); color: var(--accent-ink); }

.dup-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.dup-card {
  margin: 0; width: 190px;
  display: flex; flex-direction: column; gap: 0.5rem;
  padding: 0.6rem;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface-2);
}
/* The biggest copy - a suggestion, not a decision. Nothing acts on it. */
.dup-card.is-best { border-color: var(--accent); }
.dup-card img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover;
  border-radius: 8px; display: block;
}
.dup-card figcaption { display: flex; flex-direction: column; gap: 0.15rem; font-size: 0.8rem; }
.dup-best {
  align-self: flex-start;
  font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700;
  padding: 0.1rem 0.4rem; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
}
.dup-album { font-weight: 600; }

/* Choosing which copy survives, and what happens to the others. */
.dup-keep {
  display: flex; align-items: center; gap: 0.4rem; margin-top: 0.35rem;
  font-weight: 600; cursor: pointer;
}
.dup-card:has(input[type="radio"]:checked) { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.dup-actions {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
  margin-top: 0.9rem; padding-top: 0.9rem; border-top: 1px solid var(--line);
}
.dup-explain { flex-basis: 100%; margin: 0.2rem 0 0; font-size: 0.82rem; line-height: 1.5; }
.dup-card button { margin-top: 0.3rem; }

/* ---------- the family tree ---------- */
/*
   AN OUTLINE THAT RUNS DOWN THE PAGE, not a chart that runs across it.
   The first version drew each generation as a ROW, which meant the tree grew sideways -
   and a growing tree in a fixed box becomes a cramped strip with its own horizontal
   scrollbar, which is a miserable thing to build a family in. A page scrolls down. That is
   what pages do, and a family with six children should push the page down, not off the side
   of a little window.

   So: generations indent, children hang under their parents on a line, and nothing ever
   scrolls sideways. It reads the same on a phone as on a desktop, and it grows as far as
   the family goes.
*/
.tree { padding: 0.5rem 0 1rem; }

.tn-root, .tn-kids { list-style: none; margin: 0; padding: 0; }

/* Each generation steps in, and a line runs down beside it - the line IS the descent. */
.tn-kids {
  margin-left: 1.15rem;
  padding-left: 1.5rem;
  border-left: 2px solid var(--line);
}

.tn { position: relative; padding: 0.3rem 0; }

/* The little elbow from the line to each person. */
.tn-kids > .tn::before {
  content: '';
  position: absolute; left: -1.5rem; top: 1.45rem;
  width: 1.1rem; height: 2px;
  background: var(--line);
}

.tn-couple { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.tn-amp { color: var(--muted); font-size: 0.9rem; }

/* One person. */
.tp {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface);
  max-width: 100%;
}
/* Somebody who has died. Marked quietly - a family tree is a record, not a memorial, and a
   black band across a grandmother is not what anybody wants to open. */
.tp.is-gone { border-style: dashed; }

.tp-face {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  background: var(--surface-2); color: var(--accent);
  font-size: 0.72rem; font-weight: 700;
}
.tp-face img { width: 100%; height: 100%; object-fit: cover; }
.tp-who { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.tp-name { font-weight: 600; font-size: 0.92rem; color: inherit; text-decoration: none; }
.tp-name:hover { text-decoration: underline; }
.tp-years { font-size: 0.72rem; }

/* The tools sit ON the person, always visible - hiding them behind a hover is no use at all
   to somebody building this on an iPad. */
.tp-tools { display: flex; gap: 0.2rem; margin-left: 0.35rem; }
.tp-btn {
  width: 26px; height: 26px; border-radius: 50%;
  border: 0; background: var(--surface-2); color: var(--muted);
  cursor: pointer; font-size: 0.85rem; line-height: 1;
}
.tp-btn:hover { background: var(--accent); color: var(--accent-ink); }

/* The lock on a row of brothers and sisters. It sits OUTSIDE the person's card, beside the couple
   whose children it holds - because it is not a fact about either of them, it is a fact about the
   row underneath. A locked row is shown locked: a family should never have to press a button to
   find out whether the order they set is going to survive the next birth year somebody types. */
.tn-lock { margin-left: 0.35rem; opacity: 0.55; }
.tn-lock:hover { opacity: 1; }
.tn-lock.is-locked { opacity: 1; background: var(--accent); color: var(--accent-ink); }

.tree-toplock { margin: 0.5rem 0 0.25rem; }

.tf-title { margin: 0 0 0.25rem; font-size: 1.05rem; }
.tree-roots { margin: 1rem 0; }
.tree-roots summary { cursor: pointer; color: var(--muted); }
.tr-list { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.75rem; }
.tr-row { display: flex; align-items: center; gap: 0.75rem; }
.tr-row > span { min-width: 10ch; font-weight: 600; }

.tree-loose-wrap { margin-top: 2rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }
.tree-loose { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* On a phone the couple stacks rather than squeezing, and the indent tightens so six
   generations still fit across 390px. */
@media (max-width: 599px) {
  .tn-kids { margin-left: 0.6rem; padding-left: 0.9rem; }
  .tn-kids > .tn::before { left: -0.9rem; width: 0.6rem; }
  .tp { padding: 0.35rem 0.5rem; }
  .tp-face { width: 30px; height: 30px; }
  .tp-name { font-size: 0.85rem; }
}


.fam-tree-wrap { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--line); }
/* How somebody is joined to the rest of the family, and how to take a wrong join back. */
.tf-links { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.tf-links-list { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.tf-link {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.2rem 0.5rem; border-radius: 999px;
  background: var(--surface-2); font-size: 0.8rem;
}
.tf-hint { margin: 0 0 0.5rem; font-size: 0.85rem; }
/* Remove sits apart from Save - the two must never be next to each other. */
.tf-actions { align-items: center; }
.tf-actions #tf-delete { margin-left: auto; }
/* Choosing the photograph a person is known by. */
.photo-grid.is-picking .photo-tile { cursor: pointer; outline: 2px dashed transparent; }
.photo-grid.is-picking .photo-tile:hover { outline-color: var(--accent); }
.photo-tile.is-their-face { outline: 3px solid var(--accent); outline-offset: -3px; }
.tf-friend { margin: 0.25rem 0 0.5rem; }
/* Arranging the albums under a family - and the home page follows. */
.fam-order-hint { margin: -0.25rem 0 0.75rem; font-size: 0.85rem; }
.album-card.is-dragging { opacity: 0.45; }
.album-card.drop-before { outline: 3px solid var(--accent); outline-offset: 2px; }
/* Arranging the families - the home page follows this order. */
.fam-order { display: inline-flex; gap: 0.15rem; margin-left: auto; }
.fam-move {
  width: 24px; height: 24px; border: 0; border-radius: 6px;
  background: var(--surface-2); color: var(--muted);
  cursor: pointer; font-size: 0.65rem; line-height: 1;
}
.fam-move:hover:not(:disabled) { background: var(--accent); color: var(--accent-ink); }
.fam-move:disabled { opacity: 0.3; cursor: default; }

/* ---------- "Who is this?" ---------- */
/* The unnamed faces, grouped. One answer names the whole pile - the difference between
   tagging being a chore and tagging being something you can finish on a wet Sunday. */
.wf-piles { display: flex; flex-direction: column; gap: 1.25rem; margin-top: 1rem; }

.wf-pile {
  padding: 1rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.wf-pile.is-done { border-color: var(--accent); }
.wf-done { margin: 0; }

.wf-head { display: flex; align-items: baseline; gap: 0.6rem; }
.wf-head h2 { margin: 0; font-size: 1.1rem; }
.wf-count {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700;
  padding: 0.15rem 0.5rem; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
}
.wf-guess { margin: 0.35rem 0 0.75rem; font-size: 0.9rem; }

/* The faces, cropped out of the photographs they are in - by CSS, from the pictures the
   family already has. The box is stored as fractions of the image, so no second copy of
   anything has to be made or stored anywhere. */
/* Big enough to recognise somebody in.
   The page is asking the family to place a face. At 92 pixels it was asking them to place a
   thumbnail, which is a different and much harder question. A grid rather than a flex row, so
   nine faces read as nine faces and not as a ribbon. */
.wf-faces {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 0.6rem;
  align-items: center;
}
.wf-face {
  position: relative;
  width: 160px; height: 160px; border-radius: 10px;
  overflow: hidden; display: block;
  background: var(--surface-2);
  border: 1px solid var(--line);
  cursor: zoom-in;
}
/* The photograph is laid inside the tile and moved so that the face is the part you see.
   placeFace() does the arithmetic in pixels, once the browser knows how big the picture is.
   Until then the image is not shown at all, rather than shown in the wrong place first. */
.wf-face img {
  position: absolute;
  max-width: none;                 /* it is deliberately far bigger than its tile */
  opacity: 0; transition: opacity 0.15s;
}
.wf-face img.is-placed { opacity: 1; }
.wf-more { font-size: 0.85rem; }

/* "See her in the photograph." */
.wf-face-open {
  position: absolute; right: 5px; bottom: 5px;
  font-size: 0.7rem; line-height: 1;
  padding: 0.25rem 0.4rem; border-radius: 6px;
  background: rgba(0, 0, 0, 0.6); color: #fff;
  opacity: 0; transition: opacity 0.12s;
}
.wf-face:hover .wf-face-open,
.wf-face:focus-visible .wf-face-open { opacity: 1; }

/* "That one is not a face" - this one, on its own, for good.
   Always visible, not hover-only: it is the most-used answer on this page (half of what a
   detector finds is a lampshade), and on a touch screen there is no hover to reveal it. */
.wf-face-x {
  position: absolute; top: 5px; right: 5px; z-index: 2;
  width: 26px; height: 26px; padding: 0; border-radius: 50%;
  display: grid; place-items: center;
  font: inherit; font-size: 1rem; line-height: 1;
  background: rgba(0, 0, 0, 0.55); color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.35);
  cursor: pointer; opacity: 0.75;
  transition: opacity 0.12s, background 0.12s;
}
.wf-face-x:hover, .wf-face-x:focus-visible {
  opacity: 1; background: var(--danger, #b3261e);
}

/* The photograph, with the face in question ringed in it. */
#wf-context {
  border: 0; padding: 0; background: transparent;
  max-width: 96vw; max-height: 96vh;
  color: #fff;
}
#wf-context::backdrop { background: rgba(0, 0, 0, 0.85); }

.wf-context-stage { position: relative; display: inline-block; line-height: 0; }
.wf-context-stage img { max-width: 92vw; max-height: 80vh; border-radius: 8px; }

/* Everything except the face is pushed back by a very large shadow spreading outward from the
   ring, rather than by a filter over the photograph - so the face itself is never touched, and
   what the family are being asked to look at is the one thing shown honestly. */
.wf-ring {
  position: absolute;
  border: 2px solid #fff; border-radius: 6px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.6);
  pointer-events: none;
}

.wf-context-bar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; padding: 0.7rem 0.2rem 0;
  font-size: 0.9rem; line-height: 1.4;
}
.wf-context-bar .muted { color: rgba(255, 255, 255, 0.65); }
.wf-context-acts { display: flex; align-items: center; gap: 0.6rem; white-space: nowrap; }
.wf-context-bar a { color: #fff; }

/* The faces no pile could help with, named one at a time - same page, same crop, one name box
   each. Wider tracks than the piles: a face here carries a name box under it, and a name box
   squeezed to 160px is a name box nobody types in. */
.wf-singles { margin-top: 2rem; }
.wf-singles > .wf-head { margin-bottom: 0.35rem; }
.wf-singles-list {
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  align-items: start;
  margin-top: 1rem;
}
.wf-single {
  display: flex; flex-direction: column; gap: 0.5rem;
  padding: 0.7rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.wf-single.is-done { border-color: var(--accent); }
/* Square, like the tiles in a pile: placeFace() measures the tile's WIDTH and crops to it in
   both directions, so a tile that is not square would place the face off its own centre. */
.wf-single .wf-face { width: 100%; height: auto; aspect-ratio: 1 / 1; }
.wf-single-form { display: flex; gap: 0.4rem; }
.wf-single-name { min-width: 0; }               /* or the input pushes the button off the tile */
.wf-single-guess { margin: 0; font-size: 0.82rem; }
.wf-singles-foot { margin-top: 1rem; }

.wf-form { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.9rem; }
.wf-name {
  flex: 1; min-width: 12rem;
  font: inherit; padding: 0.45rem 0.7rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface); color: var(--ink);
}
.wf-error { margin: 0.4rem 0 0; }

/* --- their voices ---------------------------------------------------------
   A recording sits under the comments, inside the photo's side panel. It has to look
   like something a grandmother will press, so: one big button, a running clock, and
   an ordinary audio player she has seen a thousand times before. */
.lb-voice {
  border-top: 1px solid var(--line);
  padding-top: .75rem;
  margin-top: .75rem;
}
.vn-count { color: var(--muted); font-weight: 400; font-size: .85em; }
.vn-empty { margin: 0 0 .75rem; font-size: .9rem; line-height: 1.4; }

.vn-list { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .75rem; }

.vn { display: flex; align-items: flex-start; gap: .5rem; }
.vn-face {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--chip, #e5e7eb);
  color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  font-size: .72rem; font-weight: 600;
}
.vn-face img { width: 100%; height: 100%; object-fit: cover; display: block; }

.vn-body { flex: 1 1 auto; min-width: 0; }     /* or a long name stretches the panel */
.vn-who { display: flex; flex-wrap: wrap; gap: .4rem; align-items: baseline; font-size: .85rem; }
.vn-body audio { width: 100%; margin-top: .3rem; height: 36px; }

.vn-del {
  flex: 0 0 auto;
  background: none; border: 0; cursor: pointer;
  color: var(--muted); font-size: 1.1rem; line-height: 1;
  padding: .2rem .35rem; border-radius: 6px;
}
.vn-del:hover { color: var(--danger, #b91c1c); background: rgba(0,0,0,.05); }

.vn-controls { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.vn-timer {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--danger, #b91c1c);
}

/* --- The map: where photographs were taken, and where the family live ------ */

.map-main { max-width: var(--maxw); }

.map-acts { display: flex; gap: 8px; align-items: center; }

/* Google Maps cannot size itself - a map with no height renders as a blank strip. This IS the map.
   (The crosshair while placing a pin is set on the map itself via draggableCursor, not here.) */
.map-canvas {
  height: min(72vh, 720px);
  min-height: 380px;
  width: 100%;
  border-radius: var(--radius, 12px);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  z-index: 0;                 /* keep the map's own stacking context under the site header */
}

/* The address search box (Google's own autocomplete element, added by map.js). It sits above the
   map and only aims it - see map.js. Width is set here; the element lays out its own input. */
.map-search { margin: 0 0 12px; max-width: 480px; }
.map-search gmp-place-autocomplete { width: 100%; display: block; }

/* The instruction bar shown while placing a pin. */
.map-place-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 14px; margin: 0 0 12px;
  background: var(--surface-2); border: 1px solid var(--accent);
  border-radius: var(--radius, 12px); font-weight: 500;
}

/* A house for a home, so it reads apart from a photograph's pin at a glance. */
.map-home-icon {
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; line-height: 1;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}

/* The thumbnail that opens when a photograph's pin is clicked. */
.map-pop { display: block; text-decoration: none; color: inherit; max-width: 160px; }
.map-pop img {
  display: block; width: 100%; height: auto; max-height: 140px;
  object-fit: cover; border-radius: 8px; margin-bottom: 6px;
}
.map-pop-cap { font-size: 0.9rem; line-height: 1.3; }
.map-pop-home { display: grid; gap: 8px; justify-items: start; }

/* Google's InfoWindow, dressed to match the site rather than its own default white. Google wraps
   the content in several nested divs of its own; these are the ones that carry the background and
   the little pointer, so they are what has to be recoloured. */
.gm-style .gm-style-iw-c {
  background: var(--surface); color: var(--ink);
  box-shadow: var(--shadow); padding: 0;
}
.gm-style .gm-style-iw-d { overflow: hidden !important; color: var(--ink); }
.gm-style .gm-style-iw-tc::after { background: var(--surface); }   /* the pointer/tail */
.gm-style .gm-style-iw-c button > span { background: var(--muted) !important; }  /* the close X */
.gm-style-iw .map-pop, .gm-style-iw .map-pop-home { padding: 12px; }

/* "Where this was taken", under the caption in the photo viewer. */
.lb-where { margin: 2px 0 6px; font-size: 0.92rem; }
.lb-where-link { color: var(--accent); text-decoration: none; font-weight: 500; }
.lb-where-link:hover { text-decoration: underline; }
.lb-where-tools { color: var(--muted); }
.lb-where-btn {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent); font: inherit; text-decoration: none;
}
.lb-where-btn:hover { text-decoration: underline; }
.lb-where-note { font-size: 0.85rem; }

/* Person profile + family contact: bio, address/email/phone, location. */
.person-profile, .family-contact { margin: 6px 0 16px; }
.person-bio { margin: 0 0 12px; max-width: 66ch; line-height: 1.6; }
.person-contact { list-style: none; padding: 0; margin: 0 0 8px; display: grid; gap: 5px; }
.person-contact li { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.pc-label { min-width: 66px; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.person-loc { margin: 4px 0; }
.profile-form label { display: block; margin-top: 8px; font-size: 0.85rem; color: var(--muted); }
.profile-form textarea, .profile-form input { width: 100%; max-width: 480px; }
.profile-form-actions { margin-top: 10px; display: flex; gap: 8px; }
.pf-birthday { display: flex; gap: 8px; max-width: 480px; flex-wrap: wrap; }
.pf-birthday select, .pf-birthday input { width: auto; flex: 1 1 90px; min-width: 90px; }
.pf-birthday select { flex-basis: 130px; }


/* ============================================================================
   In Memory Of
   ----------------------------------------------------------------------------
   A memorial is a page of the family's book, so it is dressed in the book's own
   clothes: Fraunces at a light weight for the display type, and the warm paper
   and terracotta of story.css (album.html loads it, so the --hs-* tokens are
   here). Every one has a fallback, in case this ever renders where the book is
   not loaded.

   The shape is a tribute page's, and it is a good shape for a reason: a band of
   the album's own wallpaper, the portrait laid across its lower edge at a size
   that means it, and the name read against the picture rather than floating over
   nothing. Then quiet, well-spaced sections down a single column.

   Restraint is the whole design. This page is about somebody who died.
   ============================================================================ */

#memorial {
  --mem-paper: var(--hs-paper, #faf3e7);
  --mem-card:  #fffdf9;
  --mem-ink:   var(--hs-ink, #2c2013);
  --mem-ink-2: var(--hs-ink-2, #4a3c2b);
  --mem-muted: var(--hs-muted, #7a6a53);
  --mem-line:  var(--hs-line, #e3d4b9);
  --mem-accent: var(--hs-accent, #a8432a);
  --mem-measure: 34rem;          /* ~66 characters: the length prose is read at */
  --mem-band-h: clamp(12rem, 34vh, 21rem);   /* taller: a horizon, not a stripe */
  color: var(--mem-ink);
  margin: 0 0 3rem;
}

/* --- the hero ------------------------------------------------------------- */

/* Full-bleed: the band has to reach the edges of the window while living inside the
   page's centred column. A band with margins either side is a header, not a horizon.

   NOT 100vw. 100vw is the viewport INCLUDING the vertical scrollbar, so the usual
   `width: 100vw; margin-left: calc(50% - 50vw)` overhangs the real page by the
   scrollbar's width at each edge - 9px a side, measured - and drops a horizontal
   scrollbar along the bottom. overflow-x:clip on the root does not stop it either: the
   page still scrolled 8.9px.

   So the scrollbar is measured rather than guessed. memorial.js sets --mem-sbw from
   innerWidth - clientWidth and keeps it right on resize; --mem-page is then the true
   width of the page, scrollbar excluded. The 0px fallback degrades to a band that stops
   at the column edge, which is merely less pretty. */
.mem-hero {
  position: relative;
  --mem-page: calc(100vw - var(--mem-sbw, 0px));
  width: var(--mem-page);
  margin-left: calc(50% - var(--mem-page) / 2);
  margin-bottom: 3.5rem;
}
.mem-band {
  height: var(--mem-band-h);
  background-color: var(--hs-card, #f1e4cf);
  background-size: cover;
  background-position: center;      /* memorial.js overrides with the album's anchor */
  border-bottom: 1px solid var(--mem-line);
}
/* An admin can drag the band to choose which slice of the picture it shows. Say so:
   an invisible control is not a control. */
.mem-band-draggable { cursor: grab; touch-action: none; }
.mem-band-draggable.is-dragging { cursor: grabbing; }
.mem-band-draggable::after {
  content: 'Drag to reframe';
  position: absolute; top: 0.75rem; right: 0.9rem;
  font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: #fff; background: rgba(0, 0, 0, 0.42);
  padding: 0.3rem 0.6rem; border-radius: 999px;
  opacity: 0; transition: opacity .15s; pointer-events: none;
}
.mem-band-draggable:hover::after { opacity: 1; }
.mem-band-draggable.is-dragging::after { opacity: 0; }
@media (hover: none) { .mem-band-draggable::after { opacity: 0.75; } }

/* Only the PORTRAIT climbs into the band. The name and the dates stay down on the
   paper where they are legible - the band is a photograph or a dusk gradient and text
   laid across its edge is text laid across whatever happens to be behind it. The first
   cut lifted the whole block and put "In Memory Of" in dark terracotta on a dark
   band, where it could not be read. */
/* The padlock. Only an admin ever sees this - it is rendered for nobody else - and it
   sits on the band it governs rather than in a toolbar somewhere else. Small and quiet:
   it is a latch on a thing that is already right, not a call to action. */
.mem-lock {
  position: absolute; top: 0.85rem; right: 1rem; z-index: 2;
  width: 2.1rem; height: 2.1rem;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.95rem; line-height: 1;
  background: rgba(255, 253, 249, 0.86);
  border: 1px solid rgba(44, 32, 19, 0.12);
  border-radius: 999px;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity .15s, transform .1s, background .15s;
}
.mem-hero:hover .mem-lock, .mem-lock:focus-visible { opacity: 1; }
.mem-lock:hover { background: #fffdf9; transform: scale(1.06); }
/* Unlocked is the loud state: something on the page can be moved by accident, and the
   padlock is the way to settle it. */
.mem-lock[aria-pressed="false"] {
  opacity: 1;
  border-color: var(--mem-accent);
  box-shadow: 0 0 0 3px rgba(168, 67, 42, 0.13);
}
/* A phone has no hover, so it simply stays. */
@media (hover: none) { .mem-lock { opacity: 1; } }

.mem-ident {
  --mem-portrait-size: clamp(9rem, 22vw, 15rem);
  max-width: 62rem;
  margin: 0 auto;
  padding: 1.5rem clamp(1rem, 4vw, 3rem) 0;
  display: flex; align-items: flex-start; gap: clamp(1rem, 3vw, 2.25rem);
}
/* A round portrait, and big. It is the face of the person the page is about - it earns
   the room. The ring is the paper itself, so the picture reads as mounted on the page
   rather than pasted onto it. */
.mem-portrait {
  flex: 0 0 auto;
  width: var(--mem-portrait-size);
  aspect-ratio: 1;
  /* Just over half of it rides up onto the band; the rest sits on the paper. */
  margin: calc(var(--mem-portrait-size) * -0.62) 0 0;
  background: var(--mem-paper);
  padding: 7px;
  border-radius: 50%;
  box-shadow: 0 12px 34px rgba(44, 32, 19, 0.22);
}
.mem-portrait img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border-radius: 50%;
}

.mem-ident-text { min-width: 0; padding-top: 0.25rem; }

/* Caveat in terracotta: the book's own handwriting, the same hand as "the" in the
   wordmark and every little label in the story. It is what makes this page belong to
   this site rather than to a tribute template. */
.mem-eyebrow {
  font-family: 'Caveat', cursive;
  font-size: 1.5rem; font-weight: 500; line-height: 1;
  color: var(--mem-accent);
  margin: 0 0 0.3rem; text-transform: none; letter-spacing: 0;
}
.mem-name {
  font-family: var(--font-display, 'Fraunces', Georgia, serif);
  font-weight: 600;
  font-size: clamp(2.2rem, 5.5vw, 3.6rem);
  line-height: 1.03; letter-spacing: -0.015em;
  margin: 0 0 0.6rem; color: var(--mem-ink);
}
/* The two dates are the second thing anybody reads, so they are set like it: the
   display face, large, with a real en dash between them. */
.mem-dates {
  font-family: var(--font-display, 'Fraunces', Georgia, serif);
  font-weight: 400;
  font-size: clamp(1.15rem, 2.2vw, 1.6rem);
  line-height: 1.2; letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--mem-ink-2);
  margin: 0;
}
.mem-place {
  margin: 0.45rem 0 0; color: var(--mem-muted);
  font-size: 0.9rem; letter-spacing: 0.06em; text-transform: uppercase;
}

/* No portrait yet: nothing climbs into the band, so the name simply starts on the
   paper beneath it. */
.mem-hero-noportrait .mem-ident { padding-top: 2rem; }

/* --- the sections --------------------------------------------------------- */

.mem-sec {
  max-width: var(--mem-measure);
  margin: 0 auto 3.5rem;
  padding: 0 1rem;
}
/* A heading with a short rule under it: the one shape every section uses, so the
   page reads as one page and not a pile of parts. */
.mem-h {
  font-family: var(--font-display, 'Fraunces', Georgia, serif);
  font-weight: 600; font-size: 1.55rem;
  margin: 0 0 1.5rem; color: var(--mem-ink);
}
.mem-h::after {
  content: ''; display: block; width: 2.25rem; height: 2px;
  background: var(--mem-accent); margin-top: 0.6rem; opacity: 0.8;
}

.mem-prose p { line-height: 1.8; margin: 0 0 1.15rem; color: var(--mem-ink-2); }
.mem-prose p:last-child { margin-bottom: 0; }

/* --- a life --------------------------------------------------------------- */

/* A dot on a rule, which is how the book already draws the family's timeline
   (story.css, .hs-tl-dot). A memorial should not invent a second way of drawing
   the same idea. */
.mem-events { list-style: none; margin: 0; padding: 0; }
.mem-event {
  position: relative;
  padding: 0 2rem 1.9rem 1.5rem;
  border-left: 1px solid var(--mem-line);
}
.mem-event:last-child { border-left-color: transparent; padding-bottom: 0; }
.mem-dot {
  position: absolute; left: -4.5px; top: 0.4rem;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--mem-paper); border: 1.5px solid var(--mem-accent);
}
.mem-when {
  font-family: 'Caveat', cursive;
  font-weight: 600; font-size: 1.25rem; line-height: 1.1;
  color: var(--mem-accent);
  margin: 0 0 0.1rem; letter-spacing: 0; text-transform: none;
}
.mem-event-title {
  font-family: var(--font-display, 'Fraunces', Georgia, serif);
  font-weight: 600; font-size: 1.1rem; margin: 0; color: var(--mem-ink);
}
.mem-event-body { margin: 0.3rem 0 0; color: var(--mem-ink-2); line-height: 1.65; }

/* --- the memory wall ------------------------------------------------------ */

.mem-notes { list-style: none; margin: 2rem 0 0; padding: 0; }
.mem-note {
  position: relative;
  background: var(--mem-card);
  border: 1px solid var(--mem-line);
  border-radius: 6px;
  padding: 1.4rem 1.6rem 1.1rem;
  margin: 0 0 1rem;
  box-shadow: 0 10px 22px rgba(44, 32, 19, 0.05);
}
.mem-note-body { line-height: 1.7; color: var(--mem-ink-2); white-space: pre-wrap; }
/* Signed, not labelled: the name goes under what they wrote, as it would on paper. */
.mem-note-by {
  font-family: 'Caveat', cursive;
  font-weight: 600; font-size: 1.4rem; line-height: 1;
  color: var(--mem-accent);
  text-align: right; margin: 1rem 0 0;
}
.mem-none { color: var(--mem-muted); font-style: italic; margin: 1.5rem 0 0; }

/* --- editing -------------------------------------------------------------- */

/* An admin's affordances are QUIET. On a page about somebody who died, "Edit the
   dates" must not compete with their name. */
.mem-quiet {
  display: inline-block; margin-top: 1rem;
  background: none; border: 0; padding: 0;
  font: inherit; font-size: 0.82rem; color: var(--mem-muted);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--mem-line);
  cursor: pointer;
}
.mem-quiet:hover { color: var(--mem-accent); text-decoration-color: currentColor; }

.mem-form {
  display: flex; flex-direction: column; gap: 0.9rem;
  margin-top: 1.25rem;
  background: var(--mem-card);
  border: 1px solid var(--mem-line);
  border-radius: 6px;
  padding: 1.25rem;
  text-align: left;
}
.mem-hero-form { max-width: var(--mem-measure); margin-left: auto; margin-right: auto; }
.mem-label {
  display: flex; flex-direction: column; gap: 0.3rem;
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--mem-muted);
}
.mem-form input, .mem-form textarea {
  width: 100%; font: inherit; font-size: 0.95rem; color: var(--mem-ink);
  background: var(--mem-paper);
  border: 1px solid var(--mem-line); border-radius: 4px;
  padding: 0.6rem 0.7rem;
}
.mem-form textarea { resize: vertical; line-height: 1.6; }
.mem-form input:focus, .mem-form textarea:focus {
  outline: none; border-color: var(--mem-accent);
  box-shadow: 0 0 0 3px rgba(168, 67, 42, 0.13);
}
.mem-actions { display: flex; gap: 0.5rem; }

/* The remove buttons: present, findable, and not the first thing anybody sees. */
.mem-x {
  position: absolute; top: 0.5rem; right: 0.5rem;
  width: 1.6rem; height: 1.6rem; line-height: 1;
  background: none; border: 0; border-radius: 4px;
  color: var(--mem-muted); font-size: 1rem; cursor: pointer;
  opacity: 0; transition: opacity .15s, color .15s;
}
.mem-note:hover .mem-x, .mem-event:hover .mem-x, .mem-x:focus-visible { opacity: 1; }
.mem-x:hover { color: var(--mem-accent); }
/* A touch screen has no hover, so they simply stay. */
@media (hover: none) { .mem-x { opacity: 0.65; } }

/* --- phones --------------------------------------------------------------- */

@media (max-width: 34rem) {
  .mem-ident {
    --mem-portrait-size: 8.5rem;
    flex-direction: column; align-items: flex-start; gap: 0.9rem;
    padding-top: 0;
  }
  .mem-ident-text { padding-top: 0; }
}

/* --- On this day (the home page's memory strip) --------------------------- */
/* A horizontal shelf of anniversaries. It scrolls sideways rather than growing
   downward: on the rare day the archive has a lot to remember, the albums the
   family came for must not be pushed below the fold. */

.otd { margin: 0.5rem 0 2rem; }
.otd-title { font-family: var(--font-display); font-size: 1.5rem; margin: 0 0 0.15rem; }
.otd .muted { margin: 0 0 0.9rem; }

.otd-row {
  display: flex; gap: 0.9rem;
  overflow-x: auto;
  padding-bottom: 0.5rem;                 /* room for the scrollbar, off the cards */
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}

.otd-card {
  flex: 0 0 auto;
  width: 11rem;
  display: flex; flex-direction: column;
  text-decoration: none; color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  scroll-snap-align: start;
  transition: transform .15s;
}
.otd-card:hover { transform: translateY(-2px); }

.otd-card img {
  width: 100%; height: 8.5rem;
  object-fit: cover; display: block;
  background: var(--surface-2);
}

.otd-year {
  font-weight: 600; font-size: 0.85rem;
  color: var(--accent);
  padding: 0.5rem 0.7rem 0.1rem;
}

.otd-cap {
  font-size: 0.85rem; color: var(--ink-soft);
  padding: 0 0.7rem 0.6rem;
  overflow: hidden; text-overflow: ellipsis;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* --- Slideshow (the TV / photo-frame mode) -------------------------------- */
/* Nothing on screen but the photograph. The stage sits over everything; the
   header of the page underneath is hidden while it runs so a stray scroll can
   never reveal chrome behind a fullscreen that was refused (an iPhone). */

.ss-setup { max-width: 30rem; margin: 3rem auto; text-align: center; }
.ss-title { font-family: var(--font-display); font-size: 2rem; margin: 0 0 0.3rem; }
.ss-options {
  display: flex; flex-direction: column; gap: 0.8rem;
  align-items: center; margin: 1.5rem 0;
}
.ss-option { display: flex; align-items: center; gap: 0.6rem; color: var(--ink-soft); }
.ss-check input { width: 1.1rem; height: 1.1rem; accent-color: var(--accent); }
.ss-start-btn { font-size: 1.15rem; padding: 0.9rem 2rem; }
.ss-hint { margin-top: 1.2rem; line-height: 1.6; }

body.ss-running .app-header { display: none; }

.ss-stage {
  position: fixed; inset: 0; z-index: 200;
  background: #000;
  overflow: hidden;
  cursor: default;
}

.ss-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 1.4s ease;
}
.ss-img.is-on { opacity: 1; }

/* The slow drift. Scale only ever grows from 1 so the photograph always fills
   what it filled - shrinking would pull its edges in from the frame. */
@keyframes ss-kb-a { from { transform: scale(1) translate(0, 0); }        to { transform: scale(1.07) translate(1%, -0.7%); } }
@keyframes ss-kb-b { from { transform: scale(1.07) translate(-1%, 0.7%); } to { transform: scale(1) translate(0, 0); } }
@keyframes ss-kb-c { from { transform: scale(1) translate(0, 0); }        to { transform: scale(1.07) translate(-1%, -0.7%); } }
@keyframes ss-kb-d { from { transform: scale(1.07) translate(1%, 0.7%); }  to { transform: scale(1) translate(0, 0); } }
.ss-img.kb-a { animation: ss-kb-a 14s ease-in-out forwards; }
.ss-img.kb-b { animation: ss-kb-b 14s ease-in-out forwards; }
.ss-img.kb-c { animation: ss-kb-c 14s ease-in-out forwards; }
.ss-img.kb-d { animation: ss-kb-d 14s ease-in-out forwards; }
@media (prefers-reduced-motion: reduce) {
  .ss-img { animation: none !important; transition-duration: 0.4s; }
}

.ss-caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 2.2rem 1.5rem 1.1rem;
  text-align: center;
  color: #f1eade;
  font-size: 1.1rem;
  text-shadow: 0 1px 4px rgba(0,0,0,0.8);
  background: linear-gradient(transparent, rgba(0,0,0,0.55));
  pointer-events: none;
}

.ss-controls {
  position: absolute; left: 50%; bottom: 3.5rem;
  transform: translateX(-50%);
  display: flex; gap: 0.6rem;
  background: rgba(0,0,0,0.55);
  border-radius: 999px;
  padding: 0.4rem 0.7rem;
}
.ss-ctl {
  background: none; border: 0; cursor: pointer;
  color: #f1eade; font-size: 1.5rem; line-height: 1;
  padding: 0.5rem 0.9rem; border-radius: 999px;
}
.ss-ctl:hover, .ss-ctl:focus-visible { background: rgba(255,255,255,0.15); }
.ss-ctl-exit { font-size: 1rem; align-self: center; }

/* --- Easy view -------------------------------------------------------------- */
/* Bigger text, bigger targets, a shorter menu. Switched on from the name menu
   ("Easy view"), remembered per device (hf-easy in localStorage), and applied
   before first paint by the same inline guard that applies the theme. Everything
   below hangs off ONE attribute so turning it off leaves no residue. */

/* 125%, matching story.css - the two sheets load together on the re-skinned pages, and
   a page that grew by 120 next to a page that grew by 125 is a wobble nobody asked for. */
:root[data-easy="1"] { font-size: 125%; }

/* Fingers, not cursors: everything tappable gets more to tap. */
[data-easy="1"] .btn-sm { padding: 0.55rem 1rem; font-size: 0.95rem; }
[data-easy="1"] .btn { font-size: 1.05rem; }
[data-easy="1"] .um-item { padding: 0.7rem 1rem; font-size: 1.05rem; }
[data-easy="1"] .nav-link { font-size: 1.05rem; }
[data-easy="1"] body { line-height: 1.65; }

/* A shorter menu: the pages an elder relative actually opens. Tree, Map and Talk
   leave the top nav but stay in the name menu; the two power tools (Duplicates,
   Who is this?) step out of the menu too. Turning Easy view off brings it all back. */
[data-easy="1"] .nav-link[href="/tree"],
[data-easy="1"] .nav-link[href="/map"] { display: none; }
[data-easy="1"] .um-item[href="/duplicates"],
[data-easy="1"] .um-item[href="/whoisthis"] { display: none; }

.easy-toggle { width: 1.15rem; height: 1.15rem; accent-color: var(--accent); }

/* --- The week's question ---------------------------------------------------- */
/* One question, large, and the two ways to answer it. The answers reuse the
   voice-note row (.vn) so a spoken answer here looks like a spoken caption there. */

.q-main { max-width: 44rem; }
.q-head { font-family: var(--font-display); font-size: 2rem; margin: 0 0 0.2rem; }

.q-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 1.4rem 1.5rem;
  margin: 1.4rem 0;
}

.q-question {
  font-family: var(--font-display);
  font-size: 1.6rem; line-height: 1.35;
  margin: 0 0 1rem;
  quotes: '\201C' '\201D';
}
.q-question::before { content: open-quote; color: var(--accent); }
.q-question::after { content: close-quote; color: var(--accent); }

.q-answer-tools { display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; }
.q-rec-btn { font-size: 1.05rem; padding: 0.75rem 1.4rem; }
.q-stop-btn { background: #a8432a; }
.q-timer { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--accent); }

.q-typed { margin: 1rem 0 0.4rem; color: var(--ink-soft); }
.q-typed summary { cursor: pointer; }
.q-typed textarea {
  width: 100%; margin: 0.7rem 0 0.5rem;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); color: var(--ink);
  padding: 0.7rem; font: inherit; line-height: 1.6; resize: vertical;
}

.q-answers { margin-top: 1.2rem; }
.q-answer-text { margin: 0.3rem 0 0; line-height: 1.6; white-space: pre-wrap; }

.q-earlier-head { font-family: var(--font-display); font-size: 1.4rem; margin: 2rem 0 0.2rem; }
.q-card-past { padding: 1.1rem 1.5rem; }
.q-question-past { font-size: 1.2rem; }

/* --- Admin: the email checkboxes ------------------------------------------- */
.mail-col { white-space: nowrap; }
.mail-toggle {
  display: inline-flex; align-items: center; gap: 0.3rem;
  margin-right: 0.7rem; font-size: 0.85rem; color: var(--ink-soft);
  cursor: pointer;
}
.mail-toggle input { accent-color: var(--accent); }
