:root {
  --ink: #172A3A;            /* Memory Blue - dominant: text, buttons, headers */
  --ink-soft: #4A5763;       /* secondary text - navy-tinted grey, not raw taupe
                                 (raw taupe fails AA as text, 2.44:1 on ivory) */
  --paper: #F5F1E8;          /* Warm Ivory - default page background */
  --paper-raised: #ffffff;   /* Pure White - cards & photos only */
  --line: #DED7C7;           /* card borders */
  --line-soft: #EFE9DC;      /* inner dividers */
  --taupe: #A89B7A;          /* Eternal Taupe - non-text uses only (surfaces,
                                 dividers, icon tints); fails AA as text */
  --accent: #C7A25A;         /* Heritage Gold - decorative accent only. Never
                                 text under 18px, never twice in one block */
  --accent-label: #8C7A4E;   /* darkened gold for small label/eyebrow text
                                 (AA-large only, 3.72:1 - not for body copy) */
  --accent-text: #7A6230;    /* darker still - the only gold shade meant to
                                 carry actual informational text at any size
                                 (surname, timeline dates): 5.15:1, passes AA
                                 for normal text too. An external UX audit
                                 measured --accent (2.13:1) and --accent-label
                                 (3.72:1) both failing AA at those two exact
                                 uses - see .surname/.timeline-date below. */
  --accent-soft: color-mix(in srgb, var(--taupe) 16%, var(--paper));
  --radius: 14px;
  --shadow: 0 10px 30px -18px rgba(23, 42, 58, 0.35);
}

* { box-sizing: border-box; }

/* Several panels give a <details>'s content its own explicit `display`
   (e.g. .stack-form's `display: flex`), which in some browser engines
   outranks the UA rule that hides non-summary content while a <details>
   is closed - the content then keeps painting outside the collapsed
   <details> box (which has no overflow clipping), overlapping whatever
   comes after it in the page. Restating the rule explicitly, with
   enough weight to win regardless of what a descendant declares, keeps
   closed panels (media/archive edit forms, the manual-fact form, the
   suggestion box) genuinely collapsed. */
details:not([open]) > :not(summary) { display: none !important; }
/* <picture> (WebP + fallback, see webp_variant_url) disappears from the
   box model entirely so every existing "img { width/height: 100% }" rule
   keeps sizing the real <img> exactly as it did before this wrapper
   existed - no per-component CSS needed to accommodate it. */
picture { display: contents; }

body {
  margin: 0;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  color: var(--ink);
  background: var(--paper);
  line-height: 1.6;
}

h1, h2, h3 {
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-weight: 300;
  color: var(--ink);
  margin: 0 0 0.5em;
}

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

.muted { color: var(--ink-soft); font-size: 0.9em; }
.empty-state { color: var(--ink-soft); font-style: italic; }

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.25rem 2rem;
  border-bottom: 1px solid var(--line);
  background: var(--paper-raised);
  position: sticky;
  top: 0;
  z-index: 10;
}
.header-nav { display: flex; align-items: center; gap: 1rem; }
/* Buttons in tight flex rows (header CTA, owner toolbar) must never be
   squeezed below their own text width - a shrunk pill wraps its label
   across 2-3 lines with no text-align set to re-center them, which is
   what "mis-centered button text" on mobile actually turned out to be
   (found 2026-08-18). Let the flex row wrap onto a second line instead
   of shrinking the button. */
.header-nav .btn, .owner-bar .btn { white-space: nowrap; flex-shrink: 0; }
.lang-switcher { position: relative; }
.lang-current { font-size: 0.85rem; color: var(--ink-soft); cursor: pointer; padding: 0.4rem 0.2rem; }
.lang-menu {
  display: none;
  position: absolute; top: 100%; inset-inline-end: 0;
  background: var(--paper-raised); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: var(--shadow); padding: 0.4rem; min-width: 140px; z-index: 20;
}
.lang-switcher:hover .lang-menu, .lang-switcher:focus-within .lang-menu, .lang-switcher.is-open .lang-menu { display: flex; flex-direction: column; }
.lang-current { background: none; border: none; font-family: inherit; }
.lang-menu a { padding: 0.4rem 0.6rem; border-radius: 6px; text-decoration: none; color: var(--ink); font-size: 0.85rem; }
.lang-menu a:hover { background: var(--accent-soft); }
.lang-menu a.is-active { font-weight: 600; color: var(--ink); }

html[dir="rtl"] .timeline { border-left: none; border-right: 2px solid var(--accent-soft); }
html[dir="rtl"] .timeline li::before { left: auto; right: -6.5px; }
html[dir="rtl"] .drag-handle { transform: scaleX(-1); }
/* The biography reader's "leather book" cover (see .reader below) bakes
   in a left-edge spine and a left ruled margin line - a purely visual
   metaphor that never had an RTL counterpart, so an Arabic/Hebrew/Urdu
   reader (found during a routine live check on Mauricette's own page,
   not something Ced reported) saw perfectly right-aligned RTL prose
   sitting inside a cover whose spine was still on its "wrong" edge -
   the side where the book's pages would end, not where they begin.
   Mirrors every left/right value used by .reader/.reader-body and their
   pseudo-elements below (and their mobile overrides further down) -
   same values, opposite side, nothing about the actual text layout
   changes. */
html[dir="rtl"] .reader { padding-left: 1.5rem; padding-right: 2.5rem; }
html[dir="rtl"] .reader::before {
  left: auto; right: 0;
  background: linear-gradient(270deg, #1c0f08, #4a2f1f 65%, transparent);
  border-radius: 0 var(--radius) var(--radius) 0;
  box-shadow: inset 1px 0 0 rgba(199, 162, 90, 0.3);
}
html[dir="rtl"] .reader::after { inset: 0.5rem 1.35rem 0.5rem 0.5rem; }
html[dir="rtl"] .reader-body { padding-left: 1.6rem; padding-right: 2.1rem; }
html[dir="rtl"] .reader-body::before { left: auto; right: 1.15rem; }
.brand { display: inline-flex; align-items: center; gap: 0.55rem; text-decoration: none; }
.brand-word {
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 16px;
  font-weight: 300;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  color: var(--ink);
}

main { max-width: 920px; margin: 0 auto; padding: 2.5rem 1.5rem 5rem; }

.site-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
  padding: 2.5rem 1rem;
  color: var(--ink-soft);
  font-size: 0.85rem;
  border-top: 1px solid var(--line);
}

.btn {
  display: inline-block;
  padding: 0.7rem 1.4rem;
  border-radius: 999px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  font-weight: 500;
  cursor: pointer;
  font-size: 0.95rem;
  text-align: center;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow); }
.btn-primary { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.btn-ghost { background: transparent; color: var(--ink); }
.btn-small { padding: 0.5rem 1rem; font-size: 0.85rem; }
.btn-tiny { padding: 0.35rem 0.8rem; font-size: 0.78rem; border-radius: 999px; }

/* One visible save action for two backend-distinct forms that read as
   a single "save my identity" step to the family (civil names + main
   identity fields - see data-ajax-save-group in panels.js). Sits as a
   sibling right after both forms, not inside either one. */
.identity-save-btn { display: block; margin: 0.4rem 0 1.8rem; width: fit-content; }
.identity-save-btn:disabled { opacity: 0.6; cursor: default; transform: none; box-shadow: none; }
.btn-danger { background: transparent; border-color: #c0392b; color: #c0392b; }
.btn-danger:hover { background: #c0392b; color: #fff; }

/* Visible feedback for the "armed" (first-click) state of a two-click
   .confirm-form button (see panels.js) - a targeted UX audit found this
   state had no CSS at all, so the double-click mechanism worked but
   looked like nothing happened on the first click. */
.btn.confirm-armed { background: var(--accent-text); border-color: var(--accent-text); color: #fff; }

.hero { text-align: center; padding: 3rem 0 2.5rem; }
.hero h1 { font-size: 2.4rem; max-width: 700px; margin: 0 auto 0.6em; }
.lede { color: var(--ink-soft); max-width: 620px; margin: 0 auto 1rem; font-size: 1.05rem; }
.hero-detail { color: var(--ink-soft); max-width: 620px; margin: 0 auto 1rem; font-size: 0.98rem; }
.hero-trust { max-width: 620px; margin: 0 auto 1rem; font-size: 0.88rem; color: var(--ink-soft); }
.hero-tagline { max-width: 620px; margin: 0 auto 1.6rem; font-size: 1rem; font-style: italic; }
.home-search { display: flex; justify-content: center; gap: 0.5rem; max-width: 420px; margin: 1.6rem auto 0; }
.home-search input { flex: 1; padding: 0.6rem 0.9rem; border: 1px solid var(--line); border-radius: 999px; font-size: 0.95rem; }

.related-memorials-list { display: flex; flex-wrap: wrap; gap: 0.8rem; }
.related-memorial-card {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 0.9rem 0.5rem 0.5rem;
  border: 1px solid var(--line); border-radius: 999px; text-decoration: none; color: var(--ink);
  background: var(--paper-raised); transition: border-color 0.18s ease;
}
.related-memorial-card:hover { border-color: var(--ink-soft); }
.related-memorial-card img, .related-memorial-card .card-placeholder {
  width: 36px; height: 36px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
}
.related-memorial-card .card-placeholder { display: flex; align-items: center; justify-content: center; background: var(--paper); }

.qrcode-formats { margin: 0.6rem 0; font-size: 0.85rem; }
.qrcode-formats a { color: var(--ink); }
.qrcode-download-page { max-width: 480px; margin: 0 auto; text-align: center; }
.qrcode-download-preview { margin: 1rem auto; display: block; }
.qrcode-download-buttons { display: flex; gap: 0.6rem; justify-content: center; flex-wrap: wrap; margin: 1.2rem 0; }
/* padding-left overridden from the browser default (~40px) - on a
   narrow viewport especially, that indent reads as misaligned with
   everything else on this page (image, buttons, intro text), which are
   all flush with the article's own edge (Ced's 2026-09-15 report). */
.qrcode-download-page ul { text-align: left; margin-top: 1.2rem; padding-left: 1.3em; }
/* Same close-cross toolbar as .edit-modal-toolbar, but with nothing to
   discard (this popup previews a QR file, not a form) - flex-end instead
   of space-between keeps the X alone in the top-right corner. */
.qrcode-modal-toolbar { display: flex; justify-content: flex-end; margin: -0.4rem -0.4rem 0.4rem 0; }
.qrcode-modal-preview { display: block; margin: 0.8rem 0; max-width: 100%; }
.qr-formats-details, .family-qrcode-block { margin-top: 0.8rem; font-size: 0.85rem; }
.qr-formats-details summary, .family-qrcode-block summary { cursor: pointer; color: var(--ink-soft); }
.qr-formats-details ul { margin: 0.6rem 0 0; padding-left: 1.2rem; color: var(--ink-soft); }
.qr-formats-details li { margin-bottom: 0.35rem; }
.family-qrcode-block { padding-top: 0.8rem; border-top: 1px solid var(--line); }
.family-qrcode-block p.muted { margin-top: 0.6rem; }
/* The memorial's own QR code - its heading too, not just the image
   itself (Ced's 2026-09-10 follow-up) - and the two "Vous êtes..."
   links right below it (family access / lost link) - centered on the
   page rather than left-aligned by the module/paragraph default. */
.module-qrcode > h2 { text-align: center; }
.module-qrcode > img { display: block; margin: 0 auto; }
/* Same .field-hint negative margin-top bug as the other instances above
   (see the .export-actions/.confirm-form/.testimony-search-form + hint
   fixes) - here it's not a button but the QR code <img> right before it
   getting overlapped (Ced's 2026-09-15 report). */
.module-qrcode > img + .qrcode-formats-link { margin-top: 0.5rem; }
.qrcode-formats-link { text-align: center; }
.family-access-visitor-notice, .lost-link-notice { text-align: center; }

.why-section { max-width: 680px; margin: 3rem auto 0; }
.why-section h2 { margin-bottom: 0.4rem; }
.why-list { list-style: none; padding: 0; margin: 1.2rem 0 0; }
.why-list li { padding: 0.7rem 0; border-bottom: 1px solid var(--line); color: var(--ink-soft); }
.why-list li:last-child { border-bottom: none; }
.why-list a { margin-left: 0.4rem; font-weight: 500; }

/* The four-step "Creer/Raconter/Reunir/Transmettre" section, right under
   the hero (2026-09-10 UX brief, Lot 1 Phase 2) - the guiding principle
   made visible as a sequence rather than left as a single paragraph of
   prose in the hero lede. */
.how-it-works { max-width: 900px; margin: 3rem auto 0; text-align: center; }
.how-it-works h2 { margin-bottom: 1.6rem; }
.steps-list {
  list-style: none; padding: 0; margin: 0; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1.8rem;
}
.step-item { display: flex; flex-direction: column; align-items: center; }
.step-number {
  /* --accent (Heritage Gold) is documented as decorative-only, never a
     text/on-text color at this size (~2.4:1, fails WCAG AA) - the same
     issue as the pre-existing .badge-verified below. --accent-text is
     the darkened variant the palette itself sets aside for exactly
     this, at 5.8:1 with white text (Ralph-loop accessibility pass,
     2026-09-11). */
  width: 2.2rem; height: 2.2rem; border-radius: 50%; background: var(--accent-text);
  color: var(--paper-raised); display: flex; align-items: center; justify-content: center;
  font-weight: 600; margin-bottom: 0.6rem;
}
.step-item strong { margin-bottom: 0.3rem; }
.step-item p { color: var(--ink-soft); font-size: 0.9rem; margin: 0; }

.faq-section { max-width: 680px; margin: 3rem auto 0; }
.faq-section h2 { margin-bottom: 1rem; }

.suggestion-section { max-width: 680px; margin: 3rem auto 0; }
.suggestion-section h2 { margin-bottom: 0.4rem; }
.suggestion-section .stack-form { margin-top: 1rem; }

.family-page-cover { width: 100%; height: 220px; border-radius: var(--radius); overflow: hidden; margin-bottom: 1.2rem; }
.family-page-cover img { width: 100%; height: 100%; object-fit: cover; }
.family-page-edit { margin: 1rem 0 2rem; font-size: 0.9rem; }
.family-page-edit summary { cursor: pointer; color: var(--ink-soft); }
.family-page-edit .stack-form { margin-top: 0.8rem; }
.family-page-members { margin-top: 2rem; }
.faq-item { border-bottom: 1px solid var(--line); padding: 0.9rem 0; }
.faq-item summary { cursor: pointer; font-weight: 500; color: var(--ink); }
.faq-item p { margin: 0.7rem 0 0; color: var(--ink-soft); }

.memorial-grid-section h2 { margin-bottom: 1rem; }
.memorial-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; }
.memorial-card {
  display: block;
  text-decoration: none;
  color: inherit;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.memorial-card-portrait {
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-weight: 300;
  font-size: 2rem;
}
.memorial-card-portrait img { width: 100%; height: 100%; object-fit: cover; }
.card-placeholder { position: relative; width: 64px; height: 64px; display: flex; align-items: center; justify-content: center; }
.card-placeholder-icon { position: absolute; inset: 0; opacity: 0.28; color: var(--ink-soft); }
.card-placeholder-icon svg { width: 100%; height: 100%; }
.card-initials { position: relative; z-index: 1; letter-spacing: 0.03em; color: var(--ink); }
.memorial-card-body { padding: 0.8rem 1rem; display: flex; flex-direction: column; gap: 0.2rem; }
.memorial-card-location { font-size: 0.82rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Secondary hero CTA, LinkedIn-style: a small, clearly subordinate link
   next to the primary button - "create a memorial" stays the one thing
   most visitors came for; this is an option for the minority who want
   it, not a second button competing for the same attention. */
/* Two sober, outlined pill buttons (2026-09-11 request) instead of an
   inline "link · link →" row - reuses the existing .btn-ghost.btn-small
   pattern already used elsewhere (e.g. the owner bar) rather than a new
   button style, so it reads as part of the same design system, not a
   marketing banner. */
.hero-secondary-cta { margin: 0.9rem 0 0; display: flex; justify-content: center; gap: 0.7rem; flex-wrap: wrap; }

.family-grid-section h2 { margin-bottom: 1rem; }
.family-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; }
.family-card {
  display: block;
  text-decoration: none;
  color: inherit;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.family-card-cover {
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-soft);
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-weight: 300;
  font-size: 2rem;
}
.family-card-cover img { width: 100%; height: 100%; object-fit: cover; }
.family-card-body { padding: 0.8rem 1rem; display: flex; flex-direction: column; gap: 0.2rem; }

.form-page { max-width: 640px; margin: 0 auto; }
.stack-form { display: flex; flex-direction: column; gap: 1rem; margin-top: 1.5rem; }
.stack-form label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.9rem; color: var(--ink-soft); font-weight: 500; min-width: 0; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; min-width: 0; }
.form-row > label { min-width: 0; }
input[type=text], input[type=url], input[type=color], input[type=email], input[type=search], textarea, select {
  font-family: inherit;
  font-size: 1rem;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper-raised);
  color: var(--ink);
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
}
textarea { resize: vertical; }
.consent-box { background: var(--accent-soft); border-radius: var(--radius); padding: 1rem 1.2rem; }

/* Visually sets "Gestion partagée" apart from the plain "Lien permanent"
   block right above it in Réglages - both used the exact same neutral
   presentation before, which a targeted UX audit found let the two
   very different links (public address vs. full editing power) blend
   together. A left accent border, distinct from the tinted boxes used
   elsewhere for calmer content, reads as "handle this one with more
   care" without resorting to alarming red. */
.settings-danger-zone {
  border-left: 3px solid #c0392b; padding: 0.2rem 0 0.2rem 1rem; margin: 1.5rem 0;
}
.settings-danger-zone h2 { margin-top: 0; }
.checkbox-label { display: flex; gap: 0.6rem; align-items: flex-start; font-weight: 400; font-size: 0.92rem; color: var(--ink); }
.checkbox-label.small { font-size: 0.85rem; }
.checkbox-label input { margin-top: 0.25rem; flex-shrink: 0; }

.legal-box { display: flex; flex-direction: column; gap: 0.9rem; border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem 1.2rem; background: var(--paper-raised); }
.legal-language-notice { margin: 0; font-size: 0.82rem; }
.legal-box .checkbox-label span { font-size: 0.85rem; line-height: 1.45; color: var(--ink-soft); }

.legal-page { max-width: 720px; margin: 0 auto; }
.legal-page h2 { margin-top: 2rem; font-size: 1.15rem; }
.legal-page ul, .legal-page ol { padding-left: 1.2rem; }
.legal-page li { margin-bottom: 0.4rem; }
.content-guide .faq-section { margin: 2rem 0 0; max-width: none; }
.content-guide-cta { margin-top: 2.5rem; text-align: center; }

.banner { background: var(--accent-soft); border-radius: var(--radius); padding: 0.9rem 1.2rem; margin-bottom: 1.5rem; font-size: 0.95rem; }
.banner a { margin-left: 0.5rem; font-weight: 600; }
.banner-success { border: 1px solid var(--accent); }
.banner-warning { border: 1px solid #c0392b; background: rgba(192, 57, 43, 0.08); }
/* Shown right after confirming/accepting/rejecting a discovery item
   (app/templates/discovery.html) - a brief highlight so the action's
   outcome ("added to Identity", "ignored"...) is actually noticed
   rather than blending into a page that just silently redirected. */
.banner-flash { animation: banner-flash-in 0.5s ease-out; }
@keyframes banner-flash-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }

/* Sticky so it stays visible however far the visitor scrolls on
   demo-test-memorial (the one showcase memorial linked from the
   homepage, see home.html's "voir un memorial d'exemple" CTA) - a
   fictional profile must never read as a real one, even mid-scroll or
   on a link shared in isolation (see also is_example's SEO/Person
   JSON-LD guards in models.py/memorial.html). Discreet on purpose (a
   thin accent border, not an alarming color) to match the site's sober
   tone - clear, not jarring. Stuck at `top: 81px` (.site-header's own
   sticky height, same constant .section-nav below already relies on)
   rather than `top: 0`, so it stacks directly under the header instead
   of both racing for the same spot - and .section-nav's own sticky
   offset is pushed down by this banner's height (~40px) via :has()
   below, only on the one page that ever renders it. */
.example-banner {
  position: sticky; top: 81px; z-index: 15; text-align: center;
  background: var(--paper-raised); border-bottom: 1px solid var(--accent);
  color: var(--accent-text); font-size: 0.85rem; padding: 0.55rem 1rem;
  margin: 0 0 1.2rem;
}
body:has(.example-banner) .section-nav { top: 121px; }
.memory-cta-banner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.memory-cta-banner p { margin: 0; }
.memory-cta-banner .btn { flex-shrink: 0; }
.memory-cta-actions { display: flex; gap: 0.7rem; flex-wrap: wrap; min-width: 0; }
.memory-dashboard-heading { margin: 0 0 0.6rem; font-weight: 600; }
.memory-dashboard-stats { list-style: none; padding: 0; margin: 0 0 0.6rem; display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.9rem; color: var(--ink-soft); }
.memory-dashboard-discovery-link { margin: 0; font-size: 0.9rem; }
.dashboard-stat-details summary { cursor: pointer; color: var(--ink-soft); }
.dashboard-stat-details summary:hover { color: var(--ink); }
.dashboard-stat-detail-list { list-style: disc; margin: 0.3rem 0 0.3rem 1.4rem; padding: 0; font-size: 0.85rem; color: var(--ink-soft); }
.dashboard-stat-detail-list li { margin: 0.15rem 0; }
.dashboard-stat-detail-list li.dashboard-stat-empty { list-style: none; margin-left: -1.4rem; font-style: italic; }
/* This renders at the page's normal ~16px/weight-500 body size, not the
   "large text" WCAG carve-out --accent-label is documented safe for
   (3.72:1, AA-large only) - needs the 4.5:1 normal-text minimum, same
   family of fix as .step-number/.badge-verified (Ralph-loop
   accessibility pass, 2026-09-11). */
.media-edit-summary-prompt { color: var(--accent-text); font-weight: 500; }
.media-edit-summary-prompt svg { vertical-align: -1px; margin-right: 0.2rem; }
.media-context-hint { margin: 0.3rem 0 0.8rem; }

.owner-bar {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.6rem 1rem;
  margin-bottom: 1.5rem;
  font-size: 0.88rem;
}

.owner-bar-preview {
  background: color-mix(in srgb, var(--theme-color, #172A3A) 12%, var(--paper-raised));
  border-color: var(--theme-color, var(--line));
}

.memorial-hero { text-align: center; padding-bottom: 2rem; border-bottom: 1px solid var(--line); margin-bottom: 2rem; }
.memorial-cover { position: relative; height: 140px; border-radius: var(--radius); margin-bottom: -48px; background-size: cover; background-position: center; }
.memorial-portrait {
  position: relative;
  width: 96px; height: 96px; border-radius: 50%;
  background: var(--paper-raised);
  border: 4px solid var(--paper-raised);
  margin: 0 auto 1rem;
  display: flex; align-items: center; justify-content: center;
  font-family: "Jost", Futura, "Century Gothic", sans-serif; font-weight: 300; font-size: 2.2rem;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.memorial-portrait img { width: 100%; height: 100%; object-fit: cover; }

.edit-pencil {
  display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none; color: var(--ink-soft); font-size: 0.85em;
  opacity: 0.6; border-radius: 50%;
}
.edit-pencil:hover { opacity: 1; }
.edit-pencil-cover, .edit-pencil-portrait {
  position: absolute; width: 28px; height: 28px;
  background: var(--ink, #172A3A); color: #fff; opacity: 0.85;
}
.edit-pencil-cover { top: 8px; right: 8px; }
.edit-pencil-portrait { bottom: -2px; right: -2px; border: 2px solid var(--paper-raised); }

/* LinkedIn-style photo editing affordance: hovering the cover banner or the
   profile circle shows its own darken overlay + pointer cursor, so each
   photo reads as independently clickable — the pencil badge stays put. */
.memorial-cover.owner-editable-photo, .memorial-portrait.owner-editable-photo { cursor: pointer; }
.memorial-cover.owner-editable-photo::before {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius);
  background: rgba(0, 0, 0, 0); transition: background 0.15s ease;
}
.memorial-cover.owner-editable-photo:hover::before,
.memorial-cover.owner-editable-photo:focus-visible::before { background: rgba(0, 0, 0, 0.18); }
.memorial-portrait.owner-editable-photo img { transition: filter 0.15s ease; }
.memorial-portrait.owner-editable-photo:hover img,
.memorial-portrait.owner-editable-photo:focus-visible img { filter: brightness(0.8); }
.memorial-portrait.owner-editable-photo:focus-visible, .memorial-cover.owner-editable-photo:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.btn-icon-only { display: inline-flex; align-items: center; justify-content: center; padding: 0.5rem; }

.suggestion-box { margin-top: 1.2rem; font-size: 0.9rem; }
.suggestion-box summary { cursor: pointer; color: var(--ink-soft); }
.suggestion-box summary:hover { color: var(--ink); }

/* Collapsed-by-default optional fields on /creer (2026-09-10 UX brief,
   Lot 1 Phase 4) - a first-time visitor sees name + dates only; the rest
   of the form (still the same fields, same route) is one click away. */
.enrich-now-details { border: 1px solid var(--line); border-radius: var(--radius); padding: 0.9rem 1.2rem; }
.enrich-now-details summary { cursor: pointer; font-weight: 500; }
.enrich-now-details summary:hover { color: var(--accent-text); }
.enrich-now-details[open] summary { margin-bottom: 0.9rem; }

/* Short legal summary + expandable full text (Phase 5) - the checkbox
   itself is unchanged (same name, still required); only the wall of text
   moves behind a fold instead of always being fully shown. */
.legal-details { margin-top: 0.4rem; font-size: 0.85rem; }
.legal-details summary { cursor: pointer; color: var(--ink-soft); text-decoration: underline; }
.legal-details summary:hover { color: var(--ink); }
.legal-details p { margin-top: 0.5rem; color: var(--ink-soft); }
.suggestion-form { margin-top: 0.8rem; max-width: 480px; }
.honeypot-field { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; left: -9999px; }

.invite-emails-label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.88rem; color: var(--ink-soft); margin: 1rem 0 0.6rem; }
.invite-emails-label textarea { width: 100%; box-sizing: border-box; }

dialog.edit-modal {
  width: min(560px, 90vw);
  max-height: 85vh;
  overflow-y: auto;
  border: none;
  border-radius: var(--radius);
  padding: 1.6rem;
  background: var(--paper-raised);
  box-shadow: var(--shadow);
}
dialog.edit-modal::backdrop { background: rgba(20, 18, 14, 0.55); }
.edit-modal-toolbar { display: flex; align-items: center; justify-content: space-between; margin: -0.4rem -0.4rem 0.4rem 0; }
.edit-modal-close-form { margin: 0; }
.edit-modal-discard {
  border: none; background: transparent; font-size: 0.82rem; cursor: pointer;
  color: var(--ink-soft); padding: 0.3rem 0.5rem; border-radius: 6px; text-decoration: underline;
  transition: background 0.18s ease, color 0.18s ease;
}
.edit-modal-discard:hover { background: var(--paper); color: var(--ink); }
.edit-modal-close {
  border: none; background: transparent; font-size: 1.1rem; line-height: 1; cursor: pointer;
  color: var(--ink-soft); padding: 0.3rem 0.5rem; border-radius: 6px;
  transition: background 0.18s ease, color 0.18s ease;
}
.edit-modal-close:hover { background: var(--paper); color: var(--ink); }
.edit-modal-body h2:first-child, .edit-modal-body h3:first-child { margin-top: 0; }
.memorial-hero h1 { font-size: 2rem; }
.nickname { color: var(--ink-soft); font-style: italic; font-size: 0.7em; }
.dates { color: var(--ink-soft); margin: 0.2rem 0 1rem; }
.presentation-box {
  max-width: 560px; margin: 1rem auto 0; padding: 1rem 1.2rem;
  background: var(--accent-soft); border-radius: var(--radius); font-style: italic;
}
.share-row { display: flex; justify-content: center; gap: 0.6rem; margin-top: 1.2rem; flex-wrap: wrap; }
.link-permanent-note { flex-basis: 100%; text-align: center; margin-top: 0.2rem; }
.contribution-nudge { margin-top: 1rem !important; text-align: center; }
.share-btn {
  display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px;
  padding: 0; border-radius: 999px; border: 1px solid var(--line);
  background: var(--paper-raised); color: var(--ink-soft); text-decoration: none; cursor: pointer;
}
.share-btn:hover { background: var(--accent-soft); color: var(--ink); }
.translate-btn { background: none; border: none; color: var(--ink); font-size: 0.82rem; cursor: pointer; padding: 0.2rem 0; text-decoration: underline; }
.translate-note { font-size: 0.82rem; font-style: italic; margin-top: 0.2rem; }
.auto-translation { margin-top: 0.4rem; padding: 0.6rem 0.8rem; background: var(--accent-soft); border-radius: 8px; }
.auto-translation-label { font-size: 0.78rem; font-style: italic; color: var(--ink-soft); margin: 0 0 0.3rem; }
.auto-translation p:last-child { margin: 0; }

.module { padding: 2rem 0; border-bottom: 1px solid var(--line); }
.module:last-child { border-bottom: none; }

/* Facebook-style section nav (Ced's 2026-09-09 request): "Tout" plus one
   tab per section that actually rendered - hidden radios (see the
   .reader-control pattern used throughout this file) + :has() on the
   common .memorial-page ancestor drive both which tab shows (a tab only
   reveals itself once a matching .module-X exists ANYWHERE on the page,
   so this never has to repeat each module's own, sometimes elaborate,
   rendering condition) and which section is visible. Sticks just below
   the site's own sticky header while scrolling through any section -
   the exact behavior Ced described ("peu importe la section que l'on
   est en train de parcourir"). */
.section-nav {
  position: sticky; top: 81px; z-index: 9;
  background: var(--paper-raised); border-bottom: 1px solid var(--line);
}
/* The scroll-shadow above already signals "more tabs this way" (see the
   .section-nav-tabs comment), but a passive gradient alone turned out not
   to be enough for a mouse-only desktop visitor with no touch/trackpad
   swipe and no visible scrollbar (scrollbar-width: none) - a real
   screenshot (Ced's 2026-09-16 report) showed "Cérémonie & Sépulture"
   cut off at the edge with no obvious way to reach it. These sit right
   over that same fade zone and are the actual, clickable way to get
   there; section-nav.js shows/hides each one only when there's really
   more content in that direction, exactly the same overflow check the
   shadow's own "cover" layers already rely on. (No extra position:
   relative needed on .section-nav itself - position: sticky above
   already establishes a containing block for these, same as relative/
   absolute/fixed would.) */
.section-nav-scroll {
  position: absolute; top: 0; bottom: 0; width: 32px; z-index: 2;
  display: flex; align-items: flex-end; justify-content: center; padding-bottom: 0.55rem;
  background: none; border: none; margin: 0; cursor: pointer;
  color: var(--ink-soft); opacity: 0; pointer-events: none;
  transition: opacity 0.15s, color 0.15s;
}
.section-nav-scroll.is-visible { opacity: 1; pointer-events: auto; }
.section-nav-scroll:hover, .section-nav-scroll:focus-visible { color: var(--ink); }
.section-nav-scroll svg { width: 16px; height: 16px; }
.section-nav-scroll-left { left: 0; }
.section-nav-scroll-right { right: 0; }
.section-nav-control { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
/* gap (not a per-tab trailing margin) spaces every tab identically,
   including the last one from the container's own edge - so the gap
   before "Tout" and the one after the last tab end up equal too, both
   made only of each tab's own symmetric horizontal padding plus the
   shared page gutter and this container's own symmetric padding (bumped
   up on Ced's 2026-09-10 follow-up: the shared gutter alone read as too
   tight before "Tout"). Once enough tabs are active (the padding/gap
   above genuinely can't fit every one of them on an ordinary screen -
   Ced's 2026-09-10 follow-up, a real screenshot of "Cérémonie &
   Sépulture" sitting right at the edge, unscrolled) the row scrolls
   instead of wrapping - by design, a single sticky line. What was
   actually missing wasn't more margin (there's no amount of padding
   that makes 9 tabs fit in less width) but a visible sign this genuinely
   scrolls: a soft edge fade in whichever direction still has more tabs,
   which disappears once that direction is fully scrolled - the classic
   background-attachment:local "scroll shadow" trick, no JS. The two
   "cover" layers (local - they travel with the scrolled content) hide
   each shadow exactly when its own edge is the true start/end of the
   row; the two shadow layers themselves (scroll - fixed to the
   container's own visible edge) only ever show through once their
   cover has scrolled out of the way. */
.section-nav-tabs {
  /* Right padding bumped again (0.8rem -> 1.4rem, 2026-09-11 follow-up):
     even scrolled all the way to the true end, the last tab's own label
     (whichever one that is in the active locale - "Cerémonie &
     Sépulture" in French, but this must hold for any language) needs a
     visibly clear gap before the bandeau's own edge, not just the
     scroll-shadow's fade doing the work. Left padding bumped to match,
     per the container's own symmetric-gutter design (see comment above). */
  display: flex; gap: 1.2rem; padding: 0 1.4rem; overflow-x: auto; scrollbar-width: none;
  /* Snap to each tab's own left edge (Ced's 2026-09-15 report: on some
     reloads the row lands scrolled to a spot that cuts a tab label
     mid-word, e.g. "Galerie & médias" showing as "alerie & médias" -
     whatever nudges scrollLeft there, whether a click, a browser reload
     restoring the previously-checked radio, or a manual swipe, this
     guarantees the row always comes to rest on a clean tab boundary
     instead). scroll-padding-inline matches the container's own padding
     so a snapped tab sits exactly where it would if scrolled there by
     hand, flush with the shared gutter. */
  scroll-snap-type: x mandatory; scroll-padding-inline: 1.4rem;
  background-image:
    linear-gradient(to right, var(--paper-raised) 30%, transparent),
    linear-gradient(to left, var(--paper-raised) 30%, transparent),
    radial-gradient(farthest-side at 0% 50%, rgba(23, 20, 15, 0.18), transparent),
    radial-gradient(farthest-side at 100% 50%, rgba(23, 20, 15, 0.18), transparent);
  background-repeat: no-repeat;
  background-size: 32px 100%, 32px 100%, 14px 100%, 14px 100%;
  background-position: left top, right top, left top, right top;
  background-attachment: local, local, scroll, scroll;
}
.section-nav-tabs::-webkit-scrollbar { display: none; }
.section-nav-tab {
  display: none; flex: none; padding: 0.85rem 0.2rem; margin: 0; scroll-snap-align: start;
  border-bottom: 2px solid transparent; cursor: pointer; white-space: nowrap;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 0.88rem; color: var(--ink-soft); -webkit-tap-highlight-color: transparent;
}
.section-nav-tab:hover { color: var(--ink); }
.section-nav-tab-all { display: flex; } /* always available - "Tout" always has something to show */
.memorial-page:has(.module-biography) .section-nav-tab-biography,
.memorial-page:has(.module-timeline) .section-nav-tab-timeline,
.memorial-page:has(.module-media) .section-nav-tab-media,
.memorial-page:has(.module-transmission) .section-nav-tab-transmission,
.memorial-page:has(.module-archives) .section-nav-tab-archives,
.memorial-page:has(.module-ceremony) .section-nav-tab-ceremony,
.memorial-page:has(.module-testimonials) .section-nav-tab-testimonials,
.memorial-page:has(.module-community) .section-nav-tab-community {
  display: flex;
}
.memorial-page:has(#page-view-all:checked) .section-nav-tab-all,
.memorial-page:has(#page-view-biography:checked) .section-nav-tab-biography,
.memorial-page:has(#page-view-timeline:checked) .section-nav-tab-timeline,
.memorial-page:has(#page-view-media:checked) .section-nav-tab-media,
.memorial-page:has(#page-view-transmission:checked) .section-nav-tab-transmission,
.memorial-page:has(#page-view-archives:checked) .section-nav-tab-archives,
.memorial-page:has(#page-view-ceremony:checked) .section-nav-tab-ceremony,
.memorial-page:has(#page-view-testimonials:checked) .section-nav-tab-testimonials,
.memorial-page:has(#page-view-community:checked) .section-nav-tab-community {
  border-bottom-color: var(--accent); color: var(--accent-text); font-weight: 600;
}

/* A specific tab (not "Tout"): only that one section's module shows, at
   its full natural height - .modules-grid stays plain block flow, no
   grid/clamp rule below applies since none of them are scoped to it. */
.memorial-page:has(#page-view-biography:checked) .modules-grid .module:not(.module-biography),
.memorial-page:has(#page-view-timeline:checked) .modules-grid .module:not(.module-timeline),
.memorial-page:has(#page-view-media:checked) .modules-grid .module:not(.module-media),
.memorial-page:has(#page-view-transmission:checked) .modules-grid .module:not(.module-transmission),
.memorial-page:has(#page-view-archives:checked) .modules-grid .module:not(.module-archives),
.memorial-page:has(#page-view-ceremony:checked) .modules-grid .module:not(.module-ceremony),
.memorial-page:has(#page-view-testimonials:checked) .modules-grid .module:not(.module-testimonials),
.memorial-page:has(#page-view-community:checked) .modules-grid .module:not(.module-community) {
  display: none;
}
/* A short single section (Ced's 2026-09-11 report: "Ailleurs sur le web"
   and "Cérémonie & Sépulture" specifically, since those two happen to be
   the shortest for a real memorial) could sit so close to the document's
   own end that scrolling to align its top with the sticky nav's bottom
   would need MORE scroll room than the page actually has - the browser
   then clamps to its true max scroll, which is a position past where the
   nav's own sticky containing block still has room for it to stay
   stuck, so the nav visibly falls out of its stuck position and slides
   down (revealing --paper showing through above it, read as "a colored
   margin"). A generous min-height on the single-section container
   guarantees that room always exists, whatever the content or language -
   not scoped to any one section, since the same short-content situation
   could occur for any of them (a nearly-empty module, a short
   translation, etc.), not just these two on this one real page. */
.memorial-page:not(:has(#page-view-all:checked)) .modules-grid {
  min-height: calc(100vh - 12rem);
}

/* The reading column, only in the full single-section "Biographie" tab
   (Ced's 2026-09-12 request, "meilleurs standards de liseuses en
   ligne") - `main` alone already lets a line run up to ~90-100
   characters at 920px wide, well past the ~65-75 most reading-app
   guidance treats as comfortable. Left unscoped, this would also narrow
   the small dashboard-preview card the same book renders inside while
   browsing "Tout" (see the grid rules below) - not asked for and not
   wanted, since that card's own width already comes from the grid, not
   from a comfortable-reading concern. */
.memorial-page:not(:has(#page-view-all:checked)) .module-biography .reader {
  max-width: 44rem;
  margin-inline: auto;
}

/* "Tout" (default): every rendered section as a clamped card in a
   modular grid - a dashboard-widget overview rather than the full
   linear read, per Ced's request ("modules/blocs empilés... grille
   modulaire ou layout en mosaïque ou widget dashboard"). */
.memorial-page:has(#page-view-all:checked) .modules-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  grid-auto-flow: dense; gap: 1.5rem; align-items: start;
}
.memorial-page:has(#page-view-all:checked) .modules-grid .module {
  max-height: 300px; overflow: hidden; position: relative;
  margin: 0; padding: 1.4rem 1.5rem; border: 1px solid var(--line); border-bottom: 1px solid var(--line);
  border-radius: var(--radius); background: var(--paper-raised); box-shadow: var(--shadow);
}
/* Real weight should follow real content weight (Ced's 2026-09-10
   report: every card reading identically, from a full life story down
   to an empty "suivre les mises à jour" form, made the whole overview
   feel like an admin dashboard rather than the start of a tribute).
   Biography, Ligne de vie and the gallery are where a memorial's actual
   substance lives, so they earn a wider card - `dense` packing above
   lets the smaller, more utilitarian cards (Communauté, Ailleurs sur le
   web, Cérémonie...) fill in around them regardless of which modules the
   family happens to have active, in whatever order. Two columns is the
   minimum width auto-fit needs before a span-2 card means anything - on
   a single narrow column (see the 640px breakpoint below) every card is
   already full-width on its own. */
@media (min-width: 700px) {
  .memorial-page:has(#page-view-all:checked) .modules-grid .module-biography,
  .memorial-page:has(#page-view-all:checked) .modules-grid .module-timeline,
  .memorial-page:has(#page-view-all:checked) .modules-grid .module-media {
    grid-column: span 2;
  }
}
.memorial-page:has(#page-view-all:checked) .modules-grid .module-biography,
.memorial-page:has(#page-view-all:checked) .modules-grid .module-timeline,
.memorial-page:has(#page-view-all:checked) .modules-grid .module-media {
  max-height: 420px;
}
.memorial-page:has(#page-view-all:checked) .modules-grid .module::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 4rem;
  background: linear-gradient(180deg, transparent, var(--paper-raised)); pointer-events: none;
}
/* The gallery preview needs more room than 300px for its own heading
   plus one full row of square covers and their captions - otherwise
   every caption gets hard-clipped mid-line, unreadable (Ced's
   2026-09-09 request: "lire complètement un titre de media"). The
   "+ Créer un album" tile is an owner affordance, not content to
   preview, and competing with a real album for that first, fully-
   visible slot - left out of "Tout" specifically for that reason. */
.memorial-page:has(#page-view-all:checked) .modules-grid .module-media { max-height: 400px; }
.memorial-page:has(#page-view-all:checked) .modules-grid .module-media .album-card-add { display: none; }
/* Smaller, fixed-size covers than the gallery's own responsive grid -
   in a single, narrow mosaic column (any viewport under ~750px, where
   .modules-grid itself is down to one column) the gallery's normal
   150px-minimum columns force one huge square per row, pushing its own
   caption below the clamp before it can even render. */
.memorial-page:has(#page-view-all:checked) .modules-grid .module-media .album-grid {
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
}
/* Click-anywhere-on-the-tile (Ced's 2026-09-09 request - "n'importe quel
   endroit"), only while "Tout" is selected: covers the whole card ON TOP
   of its normal-flow content (z-index puts it above any non-positioned
   descendant - see the CSS2.1 stacking order), so a click lands on this
   first almost everywhere in the card, including over plain text. The
   one deliberate exception is the owner's own edit-pencil - a distinct,
   important action that must never be silently swallowed - promoted
   above the overlay with its own higher z-index. */
.module-card-link { display: none; }
.memorial-page:has(#page-view-all:checked) .modules-grid .module-card-link {
  display: block; position: absolute; inset: 0; z-index: 1; cursor: pointer;
}
.memorial-page:has(#page-view-all:checked) .modules-grid .module .edit-pencil {
  position: relative; z-index: 2;
}
/* The biography preview should read as "the first lines actually
   written" (Ced's 2026-09-09 request: "uniquement les textes
   renseignés"), not the reader's own chrome (reading time, comfort
   buttons, chapter title/rank/triangle, pagination, next-chapter link) -
   none of that is content, and there's no room for it in a 300px card
   anyway. Hiding .reader-accordion-title doesn't break the CSS that
   shows its .reader-accordion-body (:checked + title + body still
   matches a display:none sibling, same DOM position). */
.memorial-page:has(#page-view-all:checked) .modules-grid .module-biography .reader-meta,
.memorial-page:has(#page-view-all:checked) .modules-grid .module-biography .reader-accordion-title,
.memorial-page:has(#page-view-all:checked) .modules-grid .module-biography .reader-pagination,
.memorial-page:has(#page-view-all:checked) .modules-grid .module-biography .reader-next-chapter {
  display: none;
}
/* The preview must always show the foreword (or, failing that, chapter
   1 - whichever is genuinely first) - never whatever chapter the family
   happened to leave open while actually reading (Ced's 2026-09-10
   report: going back to "Tout" after reading could land on a closed
   accordion, an empty-looking card). Overrides the live :checked state
   entirely for this preview only - the family's real reading position
   inside the "Biographie" tab itself is untouched. */
.memorial-page:has(#page-view-all:checked) .modules-grid .module-biography .reader-accordion-body {
  display: none !important;
}
.memorial-page:has(#page-view-all:checked) .modules-grid .module-biography .reader-body > .reader-accordion-input:first-of-type + .reader-accordion-title + .reader-accordion-body {
  display: block !important;
}
@media (max-width: 640px) {
  .memorial-page:has(#page-view-all:checked) .modules-grid { grid-template-columns: 1fr; }
}

.timeline { list-style: none; padding: 0; margin: 0; }
.timeline-date { display: block; font-size: 0.8rem; color: var(--accent-text); font-weight: 600; }
/* A photo, not just a text link, earns this its place in the "Tout"
   preview - the gallery already gets to show real covers there (see
   .modules-grid .module-media above), a linked album deserves the same
   (Ced's 2026-09-10 request). */
.timeline-linked-album { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.4rem; font-size: 0.85rem; }
.timeline-linked-album img { width: 36px; height: 36px; border-radius: 6px; object-fit: cover; flex-shrink: 0; }

/* "Ligne de vie" styles, chosen by the family (Ced's 2026-09-09 request -
   see TIMELINE_STYLES in app/routers/_shared.py for the allowlist and
   panels/_timeline.html for the picker). Classic is the original design,
   unchanged in look. */
.timeline-classic { border-left: 2px solid var(--accent-soft); }
.timeline-classic li { padding: 0 0 1.4rem 1.2rem; position: relative; }
.timeline-classic li::before {
  content: ""; position: absolute; left: -6.5px; top: 4px;
  width: 11px; height: 11px; border-radius: 50%; background: var(--accent);
}

/* Cards: each event as its own bordered, shadowed block - no connecting
   line, reads as a stack of distinct memories rather than one thread. */
.timeline-cards { display: flex; flex-direction: column; gap: 0.9rem; }
.timeline-cards li {
  padding: 1rem 1.1rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper-raised); box-shadow: var(--shadow);
}

/* Alternating: a centered line, events zigzagging left/right of it - a
   classic "family history" timeline read. Two narrow columns either
   side of a centered line stop having room to breathe on a phone, so
   it collapses back to the classic single-column line under 680px. */
.timeline-alternating { position: relative; display: flex; flex-direction: column; gap: 1.6rem; }
.timeline-alternating::before {
  content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px;
  background: var(--accent-soft); transform: translateX(-50%);
}
.timeline-alternating li {
  position: relative; width: calc(50% - 1.6rem);
  padding: 0.9rem 1rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper-raised); box-shadow: var(--shadow);
}
.timeline-alternating li::before {
  content: ""; position: absolute; top: 1.1rem;
  width: 11px; height: 11px; border-radius: 50%; background: var(--accent);
}
.timeline-alternating li:nth-child(odd) { align-self: flex-start; }
.timeline-alternating li:nth-child(odd)::before { right: -1.85rem; }
.timeline-alternating li:nth-child(even) { align-self: flex-end; }
.timeline-alternating li:nth-child(even)::before { left: -1.85rem; }
@media (max-width: 680px) {
  .timeline-alternating { display: block; }
  .timeline-alternating::before { left: -6.5px; transform: none; }
  .timeline-alternating li { width: auto; margin-left: 1.2rem; }
  .timeline-alternating li:nth-child(odd), .timeline-alternating li:nth-child(even) { align-self: auto; }
  .timeline-alternating li::before { left: -1.85rem !important; right: auto !important; }
}

/* Branches: a thicker, gradient-shaded trunk instead of a plain thin
   line, each step marked with a leaf (the classic two-corners-rounded
   "CSS leaf" shape - a square with two opposite corners at border-radius
   0 and the other two at 100%, rotated 45°) instead of a dot - Ced's
   2026-09-10 request for a genuinely tree-like read, not just another
   geometric layout. */
.timeline-branches { position: relative; list-style: none; }
.timeline-branches::before {
  content: ""; position: absolute; left: 5px; top: 0; bottom: 0; width: 3px; border-radius: 3px;
  background: linear-gradient(180deg, var(--accent-text), var(--accent) 55%, var(--accent-soft));
}
.timeline-branches li { padding: 0 0 1.5rem 1.7rem; position: relative; }
.timeline-branches li::before {
  content: ""; position: absolute; left: -1.5px; top: 2px; width: 15px; height: 15px;
  background: var(--accent); border-radius: 0 100% 0 100%; transform: rotate(-45deg);
  box-shadow: 0 1px 2px rgba(23, 42, 58, 0.2);
}

/* Blossom: a delicate dotted stem instead of a solid line, each step
   marked with a small four-petal flower (layered radial-gradients, no
   extra markup or image needed) instead of a dot. */
.timeline-blossom { position: relative; list-style: none; }
.timeline-blossom::before {
  content: ""; position: absolute; left: 6px; top: 0; bottom: 0; width: 2px;
  background-image: repeating-linear-gradient(180deg, var(--accent-soft) 0 4px, transparent 4px 9px);
}
.timeline-blossom li { padding: 0 0 1.6rem 1.9rem; position: relative; }
.timeline-blossom li::before {
  content: ""; position: absolute; left: -3px; top: 0; width: 17px; height: 17px;
  background-image:
    radial-gradient(circle 4.5px at 50% 16%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 4.5px at 50% 84%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 4.5px at 16% 50%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 4.5px at 84% 50%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 3px at 50% 50%, var(--accent-text) 95%, transparent 100%);
}

.media-year-heading { margin: 1.5rem 0 0.6rem; font-size: 1.05rem; color: var(--ink-soft); }
.media-year-heading:first-of-type { margin-top: 0.5rem; }
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0.8rem; }
.media-grid img { width: 100%; height: 140px; object-fit: cover; border-radius: 10px; }
.media-grid .media-inline-video { width: 100%; height: 140px; object-fit: cover; border-radius: 10px; background: #000; }
.media-figure-audio { grid-column: 1 / -1; }
.media-inline-audio { width: 100%; }
.media-figure[data-lightbox] { cursor: zoom-in; }
.media-figure[data-lightbox] img { transition: opacity 0.15s ease; }
.media-figure[data-lightbox]:hover img { opacity: 0.85; }
.media-figure { position: relative; }
.album-owner-controls { position: absolute; top: 0.4rem; left: 0.4rem; right: 0.4rem; display: flex; justify-content: space-between; z-index: 3; }
.album-owner-controls form { margin: 0; }
.media-grid figcaption { font-size: 0.8rem; color: var(--ink-soft); margin-top: 0.3rem; }
.mood-chip {
  display: inline-flex; align-items: center; gap: 0.3rem; margin-left: 0.35rem; padding: 0.05rem 0.55rem 0.05rem 0.4rem;
  border-radius: 999px; background: var(--accent-soft); color: var(--ink);
  font-size: 0.75rem; font-style: normal;
}
.mood-chip::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }

.lightbox {
  position: fixed; inset: 0; background: rgba(10, 9, 7, 0.92); z-index: 100;
  display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 1rem; padding: 3rem 4rem;
}
.lightbox[hidden] { display: none; }
.lightbox-image { max-width: 100%; max-height: 75vh; object-fit: contain; border-radius: 6px; }
.lightbox-caption { color: var(--paper); font-size: 0.9rem; text-align: center; max-width: 60ch; }
.lightbox-close, .lightbox-prev, .lightbox-next {
  position: absolute; background: rgba(255, 255, 255, 0.12); color: #fff; border: none; border-radius: 50%;
  cursor: pointer; font-size: 1.4rem; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
}
.lightbox-close:hover, .lightbox-prev:hover, .lightbox-next:hover { background: rgba(255, 255, 255, 0.24); }
.lightbox-close { top: 1.2rem; right: 1.2rem; }
.lightbox-prev { left: 1.2rem; top: 50%; transform: translateY(-50%); }
.lightbox-next { right: 1.2rem; top: 50%; transform: translateY(-50%); }

.places-list, .letters-list, .archives-list, .testimonials-list, .fact-history { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.9rem; }
.testimonials-list li { background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem; }

/* Places, now shown inside the merged "Ligne de vie" section (see
   memorial.html) rather than their own "Carte de vie" section - styled
   as small cards (same treatment as .testimonials-list li) so they read
   as a consistent, self-contained group rather than a plain bold-text
   list sitting awkwardly under the timeline's gold-dot rail. */
.places-frame { margin-top: 1.8rem; padding-top: 1.4rem; border-top: 1px solid var(--line); }
.places-frame h3 { font-size: 1rem; color: var(--ink-soft); margin: 0 0 0.8rem; }
/* Leaflet needs an explicit height on its container - shown only once
   3+ distinct cities are geocoded (see memorial.html/models.py). */
.places-map { height: 320px; border-radius: var(--radius); overflow: hidden; margin-top: 0.8rem; z-index: 0; }
.place-card { background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem; }

.testimonial-form { margin-top: 1.5rem; max-width: 480px; }

.customize-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; gap: 1rem; flex-wrap: wrap; }
.customize-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.2rem; align-items: start; }
.panel { background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.4rem; }
.panel .stack-form { margin-top: 1rem; }
.panel-intro { margin-top: -0.4rem; margin-bottom: 0.8rem; font-size: 0.88rem; line-height: 1.4; }
.field-hint { margin-top: -0.5rem; font-size: 0.8rem; }

/* "Suggestions IA pour cette section" button + its explanatory hint
   (see _biography.html, _geography.html, _timeline.html) sit in plain
   block flow after a </form>, not inside one - .field-hint's -0.5rem
   margin-top (meant to hug a hint directly under its own form label,
   inside a flex .stack-form) doesn't apply here, and was instead
   pulling the hint text up into the button above it - most visible
   once the button's own text wrapped onto two lines. */
.ai-assist-btn { display: block; width: fit-content; margin-top: 0.8rem; }
.ai-assist-hint { margin-top: 0.5rem; font-size: 0.8rem; }
.field-error { margin: -0.3rem 0 0.4rem; font-size: 0.82rem; color: #c0392b; }
[aria-invalid="true"] { border-color: #c0392b; }
.export-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.export-actions + .field-hint { margin-top: 0.5rem; }
/* Same fix as .export-actions + .field-hint above, for the "Régénérer le
   lien" .confirm-form (_settings.html) followed by two .field-hint - the
   negative margin-top was pulling the hint text up into the button. */
.confirm-form + .field-hint { margin-top: 0.5rem; }
/* Same fix again, for the "Repérer des personnes à inviter" button
   (_invite.html) followed by its empty-state .field-hint - the negative
   margin-top made the button and the hint text below it visually run
   together (Ced's 2026-09-15 report). */
.testimony-search-form + .field-hint { margin-top: 0.5rem; }
/* Same fix again, for the "Envoyer par e-mail" .invite-send-form
   (_invite.html) followed by the "ou copiez ce message..." alternative
   hint - without this the negative margin-top pulled that hint text up
   into the submit button above it (Ced's 2026-09-15 reorder request). */
.invite-send-form + .invite-copy-alt-hint { margin-top: 0.5rem; }
.editor-toolbar { display: block; margin-bottom: 0.3rem; }
.editor-toolbar .btn-tiny { padding: 0.2rem 0.6rem; }
.editor-bold-btn { font-weight: 700; }
.prose { text-align: justify; line-height: 1.6; }

/* "Liseuse de vie" (2026-09-07, extended 2026-09-09 into a per-section
   accordion, refined again 2026-09-09 into a bound-book presentation):
   the public biography reads as a small, personal notebook rather than a
   web text block. Deliberately zero JavaScript - the fold-more-text (for
   a biography with no **bold** sections at all) is a native
   <details>/<summary>, and both the section accordion and
   reading-comfort settings are hidden radio inputs. The accordion reuses
   one purely structural CSS pattern -
   `.reader-accordion-input:checked + .reader-accordion-title + .reader-accordion-body`
   (adjacent-sibling chaining, not an ID lookup) - so it works for any
   number of sections without a per-ID rule, and clicking a *different*
   section's title natively closes the current one and opens the new one
   (same-name radio group), which is exactly what the "next chapter" link
   reuses to advance the reading - its click plays the same page-flip
   animation as the title-triangle, since both just check the same
   radio. */
.reader {
  --reader-bg: var(--paper-raised);
  position: relative;
  background:
    repeating-linear-gradient(128deg, rgba(0, 0, 0, 0.07) 0 2px, transparent 2px 7px),
    radial-gradient(140% 120% at 18% 0%, #6b4530 0%, #4a2f1f 45%, #2f1c10 100%);
  border: 1px solid #23140b;
  border-radius: var(--radius);
  box-shadow: var(--shadow), inset 0 0 0 1px rgba(199, 162, 90, 0.28), inset 0 2px 3px rgba(255, 255, 255, 0.1);
  padding: 1.4rem 1.5rem 1.4rem 2.5rem;
}
.reader::before {
  /* the bound spine along the cover's left edge */
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 1rem;
  background: linear-gradient(90deg, #1c0f08, #4a2f1f 65%, transparent);
  border-radius: var(--radius) 0 0 var(--radius);
  box-shadow: inset -1px 0 0 rgba(199, 162, 90, 0.3);
}
.reader::after {
  /* a thin gilt frame set into the leather, like an embossed cover */
  content: ""; position: absolute; inset: 0.5rem 0.5rem 0.5rem 1.35rem;
  border: 1px solid rgba(199, 162, 90, 0.38); border-radius: calc(var(--radius) - 5px);
  pointer-events: none;
}
.reader-control { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* The page itself, set into the leather cover - keeps the reading
   comfort/sepia toggle scoped to the paper, not the cover around it.
   Notebook cue #1: a soft inner margin rule, like a school cahier's
   ruled margin - everything in the reader sits to its right. */
.reader-body {
  position: relative;
  background: var(--reader-bg);
  border-radius: 8px;
  padding: 1.5rem 1.6rem 1.5rem 2.1rem;
  box-shadow: inset 0 1px 2px rgba(23, 42, 58, 0.08), 0 1px 0 rgba(255, 255, 255, 0.4);
  font-family: "Literata", Georgia, "Times New Roman", serif; color: var(--ink);
  transition: background-color 0.3s ease;
}
/* A direct override rather than reassigning --reader-bg on a :has() match:
   keeps the sepia swap a plain conditional rule (like the font-size ones
   below) instead of relying on a custom property to redefine a value
   that .reader-body also transitions - simpler to reason about and one
   less moving part between the :has() match and the property it sets. */
.reader:has(#reader-theme-sepia:checked) .reader-body { background-color: #F1E4C8; }
.reader:has(#reader-theme-sepia:checked) .reader-preview-folded::after { background: linear-gradient(180deg, transparent, #F1E4C8); }
.reader-body::before {
  content: ""; position: absolute; top: 0.2rem; bottom: 0.2rem; left: 1.15rem;
  width: 1px; background: color-mix(in srgb, var(--accent) 35%, transparent);
}
.reader:has(#reader-font-small:checked) .reader-body { font-size: 0.92rem; }
.reader:has(#reader-font-large:checked) .reader-body { font-size: 1.18rem; }

.reader-meta {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.6rem;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 0.82rem; color: var(--ink-soft); margin: 0 0 1.1rem;
}
.reader-comfort { display: inline-flex; flex-wrap: wrap; gap: 0.5rem; }
/* Ced's 2026-09-10 report: on narrow viewports the 5 buttons used to wrap
   one by one wherever they stopped fitting, which could split "Clair"/
   "Sépia" onto two different lines. Ced's 2026-09-12 follow-up asked to
   also tighten the space these controls take - both are solved the same
   way: font-size (A-/A/A+) and theme (Clair/Sépia) are each one shared-
   border segment (a single .reader-comfort-group, same class for both,
   `overflow:hidden` clipping the buttons' own transparent borders to its
   rounded corners) instead of 5 individually-bordered, individually-
   gapped buttons - the wrap point (if any) now only ever falls between
   the two segments, never inside one, and removing 4 redundant borders/
   gaps visibly shrinks the whole control row. */
.reader-comfort-group {
  display: inline-flex; flex-wrap: nowrap;
  border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
}
.reader-comfort-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.5rem; min-height: 2.75rem; padding: 0 0.35rem;
  border: 1px solid transparent; cursor: pointer;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 0.78rem; color: var(--ink-soft); background: transparent;
  -webkit-tap-highlight-color: transparent;
}
.reader-comfort-btn + .reader-comfort-btn { border-left-color: var(--line); }
.reader-comfort-btn:hover { border-color: var(--accent); color: var(--ink); }
.reader-comfort-btn:active { background: var(--accent-soft); }
/* Ced's 2026-09-12 report: switching to "Sépia" visibly shifted the
   whole Clair/Sépia segment (and everything sharing its row) - the
   `font-weight: 600` this rule used to add to the checked label made
   its text render wider than the unchecked state (measured: "Sépia"
   grew from 42.8px to 44.1px the moment it became checked), so every
   toggle nudged the segment's own width and, with it, its position in
   the row. Border/background/text-color alone are still three distinct
   visual cues for "this one is selected" - dropping the fourth (weight)
   is what removes the width change, not a smaller stylistic tweak. */
.reader:has(#reader-font-small:checked) label[for="reader-font-small"],
.reader:has(#reader-font-default:checked) label[for="reader-font-default"],
.reader:has(#reader-font-large:checked) label[for="reader-font-large"],
.reader:has(#reader-theme-light:checked) label[for="reader-theme-light"],
.reader:has(#reader-theme-sepia:checked) label[for="reader-theme-sepia"] {
  border-color: var(--accent); color: var(--accent-text); background: var(--accent-soft);
}
#reader-font-small:focus-visible ~ .reader-body label[for="reader-font-small"],
#reader-font-default:focus-visible ~ .reader-body label[for="reader-font-default"],
#reader-font-large:focus-visible ~ .reader-body label[for="reader-font-large"],
#reader-theme-light:focus-visible ~ .reader-body label[for="reader-theme-light"],
#reader-theme-sepia:focus-visible ~ .reader-body label[for="reader-theme-sepia"] {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* Accordion: each section's own title bar - clicking it (a checkbox+
   label, see the doc comment above) opens or closes just that section,
   independently of every other one (Ced's 2026-09-10 report: opening a
   later chapter used to close whichever one was already open, back when
   these were a same-name radio group instead). */
.reader-accordion-title {
  display: flex; align-items: center; gap: 0.6rem;
  min-height: 2.75rem; padding: 0.5rem 0.2rem; margin: 0 -0.2rem;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  border-bottom: 1px solid var(--line-soft); box-sizing: border-box;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 0.92rem; color: var(--ink-soft);
}
.reader-accordion-title:hover { color: var(--ink); }
.reader-accordion-title:active { background: var(--accent-soft); }
.reader-accordion-input:focus-visible + .reader-accordion-title { outline: 2px solid var(--accent); outline-offset: -2px; }
.reader-accordion-input:checked + .reader-accordion-title { color: var(--accent-text); font-weight: 600; }
.reader-accordion-title-text { flex: 1; }
/* Same disclosure convention as "Proposer un complément ou une
   correction" (a plain native <details> marker there) - a literal
   triangle here since this is a radio+label, not <details>, kept at the
   same leading position and the same behavior: it points right when
   closed, and rotates to point down once the section is open. */
.reader-accordion-triangle { color: var(--accent); font-size: 0.8em; line-height: 1; transition: transform 0.25s ease; }
.reader-accordion-input:checked + .reader-accordion-title .reader-accordion-triangle { transform: rotate(90deg); }

/* Notebook cue #2: a small "§1, §2, §3..." entry badge - only real,
   titled sections (now called chapters) count (an untitled foreword
   stays unmarked, see build_biography_reader's own reasoning). Pill
   shape, not a fixed circle, so a two-digit chapter number still fits. */
.reader-rank {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.6rem; height: 1.6rem; flex: none; border-radius: 999px;
  padding: 0 0.4rem;
  background: var(--accent-soft); color: var(--accent-text);
  font-size: 0.72rem; font-weight: 700;
}

.reader-accordion-body { display: none; padding: 1rem 0 1.2rem; }
/* Opening a chapter (whichever radio+label triggered it - the chapter's
   own triangle, or the previous chapter's "next chapter" link) plays a
   brief page-turn: `display: none -> block` restarts a CSS animation on
   an element the same way it would on first insertion, so this replays
   every time without any JS. */
.reader-accordion-input:checked + .reader-accordion-title + .reader-accordion-body {
  display: block;
  animation: reader-page-flip-in 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
}
@keyframes reader-page-flip-in {
  from { transform: perspective(1200px) rotateY(-22deg); opacity: 0.5; filter: brightness(0.88); }
  to { transform: perspective(1200px) rotateY(0deg); opacity: 1; filter: brightness(1); }
}

/* "N sur <total>" pagination - only real, ranked chapters get one (never
   the untitled foreword, which has no rank to paginate). */
.reader-pagination {
  margin: 0.8rem 0 0; font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 0.78rem; letter-spacing: 0.03em; color: var(--ink-soft);
}

.reader-next-chapter {
  display: inline-flex; align-items: center; gap: 0.55rem; min-height: 2.75rem;
  margin-top: 0.6rem; padding: 0.3rem 0.2rem; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 0.85rem; font-weight: 600; color: var(--accent-text);
}
.reader-next-chapter:hover { color: var(--ink); }
.reader-next-chapter:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
/* A dog-eared corner that lifts and turns over on hover/click - the same
   gesture as .reader-corner below, more pronounced since this button is
   the one that actually turns to a new chapter. */
.reader-page-flip {
  width: 1.1rem; height: 1.1rem; flex: none;
  background: linear-gradient(135deg, transparent 50%, var(--accent-soft) 52%);
  border: 1px solid var(--line-soft); border-top: none; border-left: none;
  border-radius: 0 0 4px 0; transform-origin: bottom right;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.reader-next-chapter:hover .reader-page-flip,
.reader-next-chapter:focus-visible .reader-page-flip {
  transform: perspective(400px) rotateY(150deg) scale(1.15);
  box-shadow: -2px -2px 6px rgba(23, 42, 58, 0.2);
}
.reader-next-chapter:active .reader-page-flip { transform: perspective(400px) rotateY(170deg) scale(1.25); }

/* Notebook cue #3: faint horizontal rules behind the prose, like lined
   paper - spaced in em so they keep pace with the comfort font-size
   setting rather than only matching the default size. */
.reader-preview, .reader-rest {
  line-height: 1.75em;
  background-image: repeating-linear-gradient(
    to bottom, transparent, transparent calc(1.75em - 1px),
    var(--line-soft) calc(1.75em - 1px), var(--line-soft) 1.75em
  );
}
.reader-preview { position: relative; }
.reader-preview-folded { padding-bottom: 0.6rem; }
.reader-preview-folded::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2.4em;
  background: linear-gradient(180deg, transparent, var(--reader-bg));
  pointer-events: none;
}
div:has(> .reader-page[open]) > .reader-preview-folded::after { opacity: 0; transition: opacity 0.3s ease; }

.reader-page { margin-top: 0.3rem; }
.reader-turn-page {
  display: inline-flex; align-items: center; gap: 0.5rem; min-height: 2.75rem;
  padding: 0.4rem 0.2rem; margin: -0.4rem -0.2rem; box-sizing: border-box;
  cursor: pointer; list-style: none; border-radius: 6px;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-size: 0.85rem; font-weight: 600; color: var(--accent-text);
  -webkit-tap-highlight-color: transparent;
}
.reader-turn-page::-webkit-details-marker { display: none; }
.reader-turn-page::marker { content: ""; }
.reader-turn-page:hover { color: var(--ink); }
.reader-turn-page:active { background: var(--accent-soft); }
.reader-turn-page:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.reader-corner {
  width: 0.9rem; height: 0.9rem; flex: none;
  background: linear-gradient(135deg, transparent 50%, var(--accent-soft) 52%);
  border: 1px solid var(--line-soft); border-top: none; border-left: none;
  border-radius: 0 0 3px 0; transform-origin: bottom right;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.reader-page:hover .reader-corner, .reader-turn-page:focus-visible .reader-corner {
  transform: rotate(-10deg) scale(1.2);
  box-shadow: -2px -2px 6px rgba(23, 42, 58, 0.15);
}
.reader-label-open { display: none; }
.reader-page[open] .reader-label-closed { display: none; }
.reader-page[open] .reader-label-open { display: inline; }
/* Overrides the UA stylesheet that hides everything but <summary> in a
   closed <details> - needed so .reader-rest can animate on its own CSS
   terms (max-height/opacity) instead of snapping open/closed instantly. */
.reader-page .reader-rest {
  display: block !important;
  max-height: 0; overflow: hidden; opacity: 0;
  transition: max-height 0.6s ease, opacity 0.45s ease;
}
.reader-page[open] .reader-rest { max-height: 6000px; opacity: 1; margin-top: 0.9rem; line-height: 1.75; }
.panel-warning { border-color: #c9a13b; background: color-mix(in srgb, #c9a13b 8%, var(--paper-raised)); }

.completeness-leads { list-style: none; padding: 0; margin: 0.6rem 0 1.6rem; display: flex; flex-direction: column; gap: 0.4rem; }
.completeness-leads li {
  padding: 0.5rem 0.8rem; border-radius: 8px; background: var(--accent-soft);
  font-size: 0.88rem; color: var(--ink);
}
.module-list { list-style: none; padding: 0; margin: 1rem 0; display: flex; flex-direction: column; gap: 0.5rem; }
.module-item {
  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
  padding: 0.6rem 0.8rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper); cursor: grab;
}
/* No-op whenever the row fits on one line (which is every width above
   ~390px, and every desktop width) - only kicks in for a long module
   name (e.g. "Galerie & médias") combined with the move/activate
   buttons on the narrowest dialog width (337px, at a 375px viewport).
   Deliberately not a media-query breakpoint tied to one pixel value -
   this reflows correctly for any future longer translated module name
   too, not just the ones measured today (found via a Ralph-loop
   desktop/responsive parity audit, see PROGRESS.md). */
.module-item.is-inactive { opacity: 0.5; }
.module-item .module-name { flex: 1; font-weight: 500; }
.drag-handle { color: var(--ink-soft); }

.media-manage-list { list-style: none; padding: 0; margin: 1rem 0; display: flex; flex-direction: column; gap: 0.5rem; }
.media-manage-item {
  display: flex; align-items: flex-start; gap: 0.7rem;
  padding: 0.6rem 0.8rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper); cursor: grab;
}
.media-manage-thumb { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
.media-manage-thumb-generic {
  display: flex; align-items: center; justify-content: center; text-align: center;
  font-size: 0.7rem; background: var(--accent-soft); color: var(--ink-soft);
}
.media-manage-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.media-edit-details summary { cursor: pointer; font-size: 0.9rem; }
.media-edit-details .stack-form { margin-top: 0.6rem; }

/* Add-by-URL, tucked away as a secondary path behind the main file
   upload (see the upload form in _media.html) - a family adding photos
   normally never sees these fields at all. */
.media-url-details { margin: 0.2rem 0 0; }
.media-url-details summary { cursor: pointer; font-size: 0.85rem; color: var(--ink-soft); }
.media-url-details[open] { display: flex; flex-direction: column; gap: 0.9rem; margin-bottom: 0.4rem; }
.media-url-details[open] summary { margin-bottom: 0.2rem; }

.timeline-manage-list { list-style: none; padding: 0; margin: 1rem 0; display: flex; flex-direction: column; gap: 0.5rem; }
.timeline-manage-item {
  display: flex; align-items: flex-start; gap: 0.7rem;
  padding: 0.6rem 0.8rem; border: 1px solid var(--line); border-radius: 10px; background: var(--paper);
}
.timeline-manage-item .timeline-edit-details { flex: 1; min-width: 0; }
.timeline-edit-details summary { cursor: pointer; font-size: 0.9rem; }
.timeline-edit-details .stack-form { margin-top: 0.6rem; }

.archive-manage-list { list-style: none; padding: 0; margin: 1rem 0; display: flex; flex-direction: column; gap: 0.5rem; }
.archive-manage-item {
  display: flex; align-items: flex-start; gap: 0.7rem;
  padding: 0.6rem 0.8rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper);
}
.archive-manage-info { flex: 1; min-width: 0; }
.archive-edit-details summary { cursor: pointer; font-size: 0.9rem; }
.archive-edit-details .stack-form { margin-top: 0.6rem; }

.inline-form { margin: 0; }
.move-buttons { display: flex; gap: 0.2rem; }

/* Trash-icon delete buttons: delete immediately on click, no
   confirmation step (the family asked for this explicitly — a photo or
   album should be gone the moment they click, not after a second click
   or a dialog). */
.btn-icon-trash {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--line);
  background: var(--paper); color: var(--ink-soft); cursor: pointer; flex-shrink: 0;
  transition: background 0.15s ease, color 0.15s ease;
}
.btn-icon-trash:hover { background: #c0392b; color: #fff; border-color: #c0392b; }

/* Album covers: a couple of the album's OWN other photos peek out behind
   the cover — real pictures, never a blank filler card — offset toward
   one corner only (not fanned both ways) for a calmer, more regular
   "stack" read. margin-bottom on the container reserves room for the
   overflow so it never bleeds into the album name below. */
.album-cover-stack { position: relative; display: block; aspect-ratio: 1 / 1; margin-bottom: 20px; }
.album-cover-back {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  border-radius: 10px; border: 2px solid var(--paper-raised);
  box-shadow: 0 2px 5px rgba(20, 16, 8, 0.14);
}
.album-cover-back-1 { transform: translate(8%, 8%); z-index: 0; }
.album-cover-back-2 { transform: translate(15%, 15%); z-index: -1; filter: brightness(0.93); }
.album-cover-thumb {
  position: relative; z-index: 2; display: block; width: 100%; height: 100%;
  object-fit: cover; border-radius: 10px; box-shadow: 0 3px 8px rgba(20, 16, 8, 0.18);
}
.album-cover-thumb-empty { background: var(--accent-soft); }

.album-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1.4rem 1.6rem; margin: 1.2rem 0 1.8rem; }
.album-card { display: flex; flex-direction: column; gap: 0.35rem; text-decoration: none; color: inherit; }
.album-card:hover .album-cover-thumb { transform: scale(1.03); transition: transform 0.15s ease; }
.album-card-name { font-weight: 600; }
.album-card-count { font-size: 0.8rem; }

/* "+ Créer un album" tile: first position in the grid, same size as a
   real album card (it reuses .album-cover-stack's own square aspect
   ratio) so creating the very first album is as visually obvious as
   opening an existing one - Ced's 2026-09-09 request. Dashed border
   reads as an empty slot to fill in, never a real photo. */
.album-card-add .album-cover-stack {
  display: flex; align-items: center; justify-content: center;
  border: 2px dashed var(--line); border-radius: 10px;
  background: transparent; transition: border-color 0.15s ease, background-color 0.15s ease;
}
.album-card-add:hover .album-cover-stack { border-color: var(--accent); background: var(--accent-soft); }
.album-card-add-icon { font-size: 2.4rem; line-height: 1; font-weight: 300; color: var(--ink-soft); }
.album-card-add:hover .album-card-add-icon { color: var(--accent-text); }
.album-card-add .album-card-name { text-align: center; color: var(--ink-soft); }
.album-card-add:hover .album-card-name { color: var(--ink); }

.album-manage-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1rem; margin: 1.2rem 0 1.6rem; }
.album-manage-card {
  border: 1px solid var(--line); border-radius: 10px; padding: 0.7rem;
  background: var(--paper);
}
.album-manage-summary {
  display: flex; flex-direction: column; gap: 0.35rem; cursor: pointer; list-style: none;
}
.album-manage-summary::-webkit-details-marker { display: none; }
.album-manage-info { display: flex; flex-direction: column; gap: 0.1rem; }
.album-manage-info strong { display: block; font-size: 0.85rem; overflow-wrap: anywhere; }
.album-manage-photos {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 0.5rem;
  margin: 0.8rem 0; padding-top: 0.8rem; border-top: 1px solid var(--line);
}
.album-manage-photo { position: relative; }
.album-manage-photo-thumb { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 8px; display: block; }
.album-manage-photo.is-cover .album-manage-photo-thumb { outline: 2px solid #C7A25A; outline-offset: 1px; }
.album-manage-photo form.inline-form { position: absolute; top: 0.25rem; right: 0.25rem; }
.album-manage-photo .btn-icon-trash { width: 26px; height: 26px; background: rgba(250, 247, 242, 0.92); }
.album-manage-photo form.album-cover-set-form { position: absolute; top: 0.25rem; left: 0.25rem; margin: 0; }
.btn-icon-star {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--line);
  background: rgba(250, 247, 242, 0.92); color: var(--ink-soft); cursor: pointer;
}
.btn-icon-star:hover { background: #C7A25A; color: #fff; border-color: #C7A25A; }
.btn-icon-add {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--line);
  background: var(--paper); color: var(--ink-soft); cursor: pointer; flex-shrink: 0;
  transition: background 0.15s ease, color 0.15s ease;
}
.btn-icon-add:hover { background: var(--ink); color: #fff; border-color: var(--ink); }
.btn-icon-add input[type="file"] { display: none; }

.album-rename-form { display: flex; gap: 0.4rem; align-items: center; margin-bottom: 0.4rem; }
.album-rename-input {
  flex: 1; min-width: 0; border: 1px solid var(--line); border-radius: 6px;
  padding: 0.35rem 0.6rem; font: inherit; background: var(--paper);
}
.album-rename-input-title { font-family: "Jost", Futura, "Century Gothic", sans-serif; font-size: 1.4rem; font-weight: 300; }
.album-cover-badge {
  position: absolute; top: 0.25rem; left: 0.25rem; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; background: #C7A25A; color: #fff;
}
.album-manage-actions { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }

.album-page { max-width: 900px; margin: 0 auto; padding: 2rem 1.2rem; }
.album-back { margin-bottom: 0.4rem; }
.album-back a { color: var(--ink-soft); text-decoration: none; }
.album-back a:hover { text-decoration: underline; }
.move-buttons button:disabled { opacity: 0.3; cursor: not-allowed; }

.contributors-credit { font-size: 0.85rem; font-style: italic; }
.follow-form { display: flex; gap: 0.5rem; margin-top: 0.6rem; max-width: 380px; }
.follow-form input { flex: 1; padding: 0.55rem 0.8rem; border: 1px solid var(--line); border-radius: 999px; font-size: 0.9rem; }
.checklist-panel { background: var(--accent-soft); }
.checklist { list-style: none; padding: 0; margin: 0.8rem 0 0; display: flex; flex-direction: column; gap: 0.5rem; }
.checklist a { color: var(--ink); text-decoration: none; font-size: 0.95rem; }
.checklist li.is-done a { color: var(--ink-soft); text-decoration: line-through; }

.portrait-form { display: flex; align-items: center; gap: 0.8rem; margin-bottom: 1.2rem; }
.portrait-preview {
  position: relative;
  width: 72px; height: 72px; border-radius: 50%; overflow: hidden;
  background: var(--accent-soft); display: flex; align-items: center; justify-content: center;
  font-family: "Jost", Futura, "Century Gothic", sans-serif; font-weight: 300; font-size: 1.6rem; flex-shrink: 0;
  cursor: grab; touch-action: none;
}
.portrait-preview.dragging-position { cursor: grabbing; }
.portrait-preview img { width: 100%; height: 100%; object-fit: cover; pointer-events: none; }
.portrait-preview input[type="file"] { display: none; }
.portrait-edit-badge {
  position: absolute; bottom: 0; right: 0; width: 26px; height: 26px; border-radius: 50%;
  background: var(--ink, #172A3A); color: #fff; display: flex; align-items: center; justify-content: center;
  font-size: 0.85rem; border: 2px solid var(--paper-raised); cursor: pointer;
}
.portrait-hint { font-size: 0.82rem; }

.cover-form { margin-bottom: 0.8rem; }
.cover-preview {
  position: relative;
  display: block;
  height: 140px; border-radius: var(--radius); cursor: grab; touch-action: none;
  background: var(--accent-soft) center/cover no-repeat;
  border: 1px dashed var(--line);
  overflow: hidden;
}
.cover-preview.dragging-position { cursor: grabbing; }
.cover-upload-form { margin: 0; }
.cover-preview input[type="file"] { display: none; }
/* A distinct circular camera badge in the corner (LinkedIn-style cover
   editor) instead of a low-contrast text pill sitting on top of
   whatever the photo happens to show — it needs to stay visible and
   findable regardless of how busy the photo underneath is. */
.cover-edit-badge {
  position: absolute; bottom: 0.7rem; right: 0.7rem; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.95); color: var(--ink);
  border: 1px solid rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(20, 16, 8, 0.3);
  cursor: pointer; transition: transform 0.15s ease, background 0.15s ease;
}
.cover-edit-badge:hover { background: #fff; transform: scale(1.07); }
.cover-preview + .field-hint { margin-top: 0.5rem; }

.suggestion-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: -0.4rem 0 0.4rem; }
.suggestion-chips .muted { width: 100%; font-size: 0.8rem; }
.suggestion-chips-label { display: inline-flex; align-items: center; gap: 0.3rem; }
.chip {
  border: 1px solid var(--line); background: var(--paper); border-radius: 999px;
  padding: 0.35rem 0.8rem; font-size: 0.8rem; cursor: pointer; color: var(--ink-soft);
  max-width: 100%; text-align: left; white-space: normal;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.chip:hover { background: var(--accent-soft); color: var(--ink); border-color: var(--accent); }
.names-list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; margin: 0.8rem 0; }
.names-list li { background: var(--paper); border: 1px solid var(--line); border-radius: 10px; padding: 0.6rem 0.8rem; display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }
.names-actions { display: flex; gap: 0.4rem; margin-top: 0.4rem; }
.featured-parts-list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; margin: 0.8rem 0; }
.featured-parts-list li { background: var(--paper); border: 1px solid var(--line); border-radius: 10px; padding: 0.5rem 0.8rem; }
.checkbox-row { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; }
.checkbox-row input[type="checkbox"] { width: auto; }

/* A field group revealed only once its own "does this even apply?"
   checkbox is ticked (see data-toggle-target in panels.js - the
   recognized/spouse name toggles in _identity.html are the first use:
   most memorials never need either, so showing both unconditionally
   read as required fields rather than the rare exception they are). */
.conditional-fields { display: flex; flex-direction: column; gap: 1rem; margin-top: 0.6rem; }
.conditional-fields[hidden] { display: none; }

.death-marker-picker { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.5rem; margin: 0.8rem 0; }
.death-marker-option {
  display: flex; align-items: center; gap: 0.5rem; cursor: pointer;
  background: var(--paper); border: 1px solid var(--line); border-radius: 10px; padding: 0.5rem 0.7rem; font-size: 0.85rem;
}
.death-marker-option input[type="radio"] { width: auto; }

.timeline-style-picker { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 0.6rem; margin: 0.8rem 0; }
.timeline-style-option {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; cursor: pointer;
  background: var(--paper); border: 1px solid var(--line); border-radius: 10px; padding: 0.7rem; font-size: 0.85rem;
}
.timeline-style-option:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.timeline-style-option input[type="radio"] { width: auto; }

/* A small CSS-only diagram of each timeline style (Ced's 2026-09-09
   request) - one shared line + 3 item bars, rearranged per style rather
   than 3 separate hand-drawn previews. Purely decorative (aria-hidden). */
.timeline-style-preview { position: relative; display: block; width: 100%; height: 44px; }
.timeline-style-preview .tsp-item { position: absolute; height: 5px; border-radius: 2px; background: var(--accent); }
.timeline-style-preview .tsp-line { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--ink-soft); }
.timeline-style-preview-classic .tsp-line { left: 6px; }
.timeline-style-preview-classic .tsp-item { left: 13px; right: 4px; }
.timeline-style-preview-classic .tsp-item:nth-child(2) { top: 3px; }
.timeline-style-preview-classic .tsp-item:nth-child(3) { top: 19px; }
.timeline-style-preview-classic .tsp-item:nth-child(4) { top: 35px; }
.timeline-style-preview-cards .tsp-line { display: none; }
.timeline-style-preview-cards .tsp-item {
  left: 0; right: 0; height: 9px; border-radius: 3px;
  background: var(--accent-soft); border: 1px solid var(--accent);
}
.timeline-style-preview-cards .tsp-item:nth-child(2) { top: 0; }
.timeline-style-preview-cards .tsp-item:nth-child(3) { top: 13px; }
.timeline-style-preview-cards .tsp-item:nth-child(4) { top: 27px; }
.timeline-style-preview-alternating .tsp-line { left: 50%; }
.timeline-style-preview-alternating .tsp-item { width: 42%; }
.timeline-style-preview-alternating .tsp-item:nth-child(2) { left: 0; top: 2px; }
.timeline-style-preview-alternating .tsp-item:nth-child(3) { right: 0; top: 19px; }
.timeline-style-preview-alternating .tsp-item:nth-child(4) { left: 0; top: 36px; }
.timeline-style-preview-branches .tsp-line {
  left: 6px; width: 3px; border-radius: 3px;
  background: linear-gradient(180deg, var(--accent-text), var(--accent) 55%, var(--accent-soft));
}
.timeline-style-preview-branches .tsp-item {
  left: 12px; width: 10px; height: 10px; border-radius: 0 100% 0 100%;
  transform: rotate(-45deg); background: var(--accent);
}
.timeline-style-preview-branches .tsp-item:nth-child(2) { top: 2px; }
.timeline-style-preview-branches .tsp-item:nth-child(3) { top: 17px; }
.timeline-style-preview-branches .tsp-item:nth-child(4) { top: 32px; }
.timeline-style-preview-blossom .tsp-line {
  left: 6px; width: 2px; background: none;
  background-image: repeating-linear-gradient(180deg, var(--accent-soft) 0 3px, transparent 3px 7px);
}
.timeline-style-preview-blossom .tsp-item {
  left: 12px; width: 12px; height: 12px; border-radius: 0; background: none;
  background-image:
    radial-gradient(circle 3.5px at 50% 16%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 3.5px at 50% 84%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 3.5px at 16% 50%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 3.5px at 84% 50%, var(--accent) 95%, transparent 100%),
    radial-gradient(circle 2.5px at 50% 50%, var(--accent-text) 95%, transparent 100%);
}
.timeline-style-preview-blossom .tsp-item:nth-child(2) { top: 1px; }
.timeline-style-preview-blossom .tsp-item:nth-child(3) { top: 16px; }
.timeline-style-preview-blossom .tsp-item:nth-child(4) { top: 31px; }
.death-marker-preview { display: inline-flex; align-items: center; justify-content: center; width: 1.4em; color: var(--ink-soft); }
.death-marker-glyph { display: inline-flex; vertical-align: -2px; }
.death-marker-om { font-size: 1.15em; line-height: 1; font-family: "Nirmala UI", "Noto Sans Devanagari", "ITF Devanagari", sans-serif; }
.surname { font-variant: small-caps; letter-spacing: 0.04em; }
.memorial-hero h1 .surname { color: var(--accent-text); }
.alt-names { color: var(--ink-soft); font-size: 0.95rem; margin: 0.2rem 0 0.6rem; }
.name-note { font-size: 0.85em; font-style: italic; }
.generate-draft-form { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.generate-draft-form p { margin: 0.4rem 0 0; }
.permalink-row { display: flex; gap: 0.5rem; margin-top: 0.8rem; align-items: flex-start; }
.permalink-row input, .permalink-row textarea { flex: 1; padding: 0.6rem 0.8rem; border: 1px solid var(--line); border-radius: 8px; background: var(--paper); color: var(--ink-soft); font-size: 0.85rem; font-family: inherit; resize: vertical; }

/* Post-creation confirmation banner (2026-09-10 UX brief, Lot 1 Phase 4) -
   QR thumbnail + public URL alongside the existing owner-link box, plus
   the two natural next actions (enrich, invite family), reusing the
   already-built memory/invite panels rather than inventing new ones. */
.welcome-public-row { display: flex; gap: 1rem; align-items: center; margin-top: 1rem; }
.welcome-public-row img { border-radius: 8px; background: var(--paper-raised); flex-shrink: 0; }
.welcome-public-col { flex: 1; min-width: 0; }
.welcome-next-actions { display: flex; gap: 0.7rem; flex-wrap: wrap; margin-top: 1rem; }

.slug-input-row {
  display: flex; align-items: stretch; border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
  background: var(--paper); font-size: 0.85rem;
}
.slug-prefix { padding: 0.6rem 0.5rem 0.6rem 0.8rem; color: var(--ink-soft); background: var(--accent-soft); white-space: nowrap; display: flex; align-items: center; }
.slug-input-row input { border: none; background: transparent; padding: 0.6rem 0.8rem 0.6rem 0.2rem; flex: 1; min-width: 0; font-family: inherit; font-size: inherit; }
.slug-input-row input:focus { outline: none; }
/* The input's own outline is suppressed above (it would clip oddly
   against the prefix pill), but a keyboard user still needs a visible
   focus signal - highlight the whole row instead (Ralph-loop
   accessibility pass, 2026-09-11: this had no focus indicator at all). */
.slug-input-row:focus-within { border-color: var(--ink); box-shadow: 0 0 0 1px var(--ink); }

.ceremony-actions { display: flex; align-items: center; gap: 1.2rem; margin-top: 1rem; flex-wrap: wrap; }
.ceremony-qrcode { border-radius: 8px; border: 1px solid var(--line); background: #fff; padding: 4px; }
.tribute-video-section { margin-top: 1.2rem; padding-top: 1rem; border-top: 1px solid var(--line); }
.tribute-video-public { margin-top: 1.2rem; }
.tribute-video-preview, .tribute-video-player {
  display: block; max-width: 100%; width: 100%; max-height: 480px;
  border-radius: var(--radius); background: #000; margin-top: 0.6rem;
}
.tribute-video-actions { display: flex; gap: 0.6rem; margin-top: 0.6rem; }
.tribute-video-processing { color: var(--ink-soft); font-style: italic; }
.tribute-video-failed { color: #c0392b; }

.moderation-list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 1rem; }
.moderation-actions { display: flex; gap: 0.5rem; margin-top: 0.4rem; }

.contributions-list { list-style: none; padding: 0; margin: 0.6rem 0; display: flex; flex-direction: column; gap: 0.4rem; }
.contributions-list li { background: var(--accent-soft); border-radius: 8px; padding: 0.5rem 0.7rem; font-size: 0.92rem; }
.contribution-credit { font-style: italic; }

.memory-profile-status { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; font-size: 0.9rem; margin-bottom: 1rem; }
.memory-profile-badge { display: inline-block; padding: 0.25rem 0.7rem; border-radius: 999px; font-weight: 600; font-size: 0.8rem; }
.memory-profile-structured { background: var(--line); }
.memory-profile-enriched { background: var(--accent-soft); }
.memory-profile-coherent { background: var(--theme-color, #172A3A); color: var(--paper); }

.discovery-actions { display: flex; gap: 0.8rem; margin: 1.2rem 0; flex-wrap: wrap; }
.fact-list { display: flex; flex-direction: column; gap: 1rem; }
.fact-card { background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.2rem; }
.fact-content { font-size: 1.02rem; }
.fact-meta { display: flex; flex-wrap: wrap; gap: 1rem; font-size: 0.82rem; color: var(--ink-soft); margin: 0.6rem 0; }
.fact-meta dt { font-weight: 600; display: inline; }
.fact-meta dd { display: inline; margin: 0 0 0 0.3rem; }
.confidence { padding: 0.1rem 0.5rem; border-radius: 999px; background: var(--accent-soft); }
.manual-fact-details { margin: 1rem 0 1.6rem; border: 1px solid var(--line); border-radius: var(--radius); padding: 0.8rem 1rem; background: var(--paper); }
.manual-fact-details summary { cursor: pointer; font-weight: 600; }
.manual-fact-details .stack-form { margin-top: 0.8rem; }
.fact-conflict-group {
  border: 1px solid #C7A25A; border-radius: var(--radius); padding: 0.8rem;
  display: flex; flex-direction: column; gap: 1rem; background: rgba(201, 146, 47, 0.06);
}
.fact-conflict-note { margin: 0; font-size: 0.85rem; font-weight: 600; color: #8a6423; }
.fact-actions { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 0.8rem; }
.fact-buttons { display: flex; gap: 0.5rem; }
.badge-ai {
  display: inline-block; font-size: 0.72rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink-soft); background: var(--accent-soft);
  border-radius: 999px; padding: 0.2rem 0.7rem; margin: 0 0 0.5rem;
}
.status { font-weight: 600; text-transform: capitalize; margin-right: 0.4rem; }
.journal-entry-details { display: inline; }
.journal-entry-details summary { display: inline; cursor: pointer; list-style: none; }
.journal-entry-details summary::-webkit-details-marker { display: none; }
.journal-entry-details[open] summary { display: none; }
.journal-entry-details p { display: inline; margin: 0; }
/* Explicit, unmistakably-clickable affordance rather than relying on
   plain truncated text alone reading as "clickable" - found live
   (2026-09-04): a bare "…" at the end of cut-off text didn't read as
   interactive at all, just as a dead end. */
.journal-read-more { color: var(--accent-text); text-decoration: underline; font-weight: 600; white-space: nowrap; }
.journal-entry-details summary:hover .journal-read-more { color: var(--ink); }
.registry-links { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 0.6rem; }
.registry-links a { display: inline-block; padding: 0.5rem 1rem; border: 1px solid var(--line); border-radius: 999px; background: var(--paper-raised); text-decoration: none; font-size: 0.88rem; transition: background 0.18s ease; }
.registry-links a:hover { background: var(--accent-soft); }

/* Assistant mémoire (/decouverte) page structure - 2026-09-04 streamlining:
   the two decision queues (sources to confirm, facts to validate) get a
   distinct "this needs your attention" treatment, while everything else
   (registries, manual links, journal) stays plain reference material. */
.discovery-person-name { font-size: 1.2rem; font-weight: 600; margin: 0.2rem 0 0; }
.discovery-subtitle { margin: 0.1rem 0 1rem; }
.discovery-actionable {
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: var(--accent-soft); padding: 1.2rem; margin: 1.2rem 0 1.6rem;
}
.discovery-actionable > .fact-list, .discovery-actionable > .empty-state { margin-top: 0.8rem; }
.discovery-manual-block { margin: 1.6rem 0; }
.discovery-registry-external { opacity: 0.85; }
.discovery-manual-links {
  border: 1px solid var(--accent); border-radius: var(--radius);
  padding: 1.2rem; margin: 1rem 0; background: var(--paper-raised);
}
.badge-verified, .badge-unverified {
  display: inline-block; font-size: 0.68rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.03em; border-radius: 999px; padding: 0.15rem 0.6rem; margin-left: 0.5rem;
  vertical-align: middle;
}
.badge-verified { background: var(--accent-text); color: var(--paper); }
.badge-unverified { background: var(--line); color: var(--ink-soft); }

@media (max-width: 640px) {
  .site-header { padding: 1rem; flex-wrap: wrap; row-gap: 0.6rem; }
  .header-nav { width: 100%; justify-content: flex-end; }
  /* .site-header wraps to two rows here, taller than the desktop value
     .section-nav's own `top` assumes - keeps it flush under the header
     instead of sliding a strip of it underneath. */
  .section-nav { top: 112px; }
  body:has(.example-banner) .section-nav { top: 152px; }
  /* The pill radius (999px) reads fine on the single-row desktop layout,
     but once the buttons wrap to 2-3 rows on a narrow screen it makes the
     curved ends visually crowd the text on the wrapped rows, reading as
     off-center (external UX audit, mobile pass). A moderate radius suits
     a multi-row box instead. */
  .owner-bar { flex-wrap: wrap; row-gap: 0.4rem; border-radius: var(--radius); justify-content: center; }
  .form-row { grid-template-columns: 1fr; }
  .hero h1 { font-size: 1.8rem; }
  /* The QR thumbnail + public-address column (welcome banner, Lot 1) sit
     side by side on desktop; on a narrow screen the column has too
     little width left once the 84px image takes its share, so it stacks
     instead (mobile-first pass, Lot 3 Phase 11). */
  .welcome-public-row { flex-direction: column; align-items: center; text-align: center; }
  /* Ced's 2026-09-12 request: the reading-comfort controls (font size +
     theme, each its own segment - see .reader-comfort-group above) sit on
     the same row as the reading-time text on desktop (.reader-meta,
     space-between) - nothing guarantees both halves still fit one line
     together on a narrow phone, so this stacks them instead of leaving it
     to chance. Also bumps the reading-time/chapter-pagination text up
     slightly - both were sized for a wide desktop line, a bit small for
     text read this often on a phone. */
  .reader-meta { flex-direction: column; align-items: flex-start; }
  .reader-time, .reader-pagination { font-size: 0.88rem; }
  /* Ced's 2026-09-13 request: justify the biography text on mobile and
     widen its actual column - at a narrow phone width the book cover's
     decorative padding (fine at its full size on a wide desktop reader,
     reserved for the spine graphic and the ruled margin line) left so
     little real text width that a short phrase ("sensible et forte à la
     fois") routinely broke into a lone 2-word orphan line. Shrinking
     both paddings reclaims real width instead of just moving the same
     narrow column around - the spine and ruled-margin line shrink by
     the same amount, so neither graphic is left floating past its own
     edge. hyphens:auto (the <html lang> this needs is already set, see
     base.html) lets a long word break instead of jumping whole to the
     next line, which is what most often produced the orphan in the
     first place, and pairs with justify to avoid the ragged whitespace
     rivers narrow justified columns are otherwise prone to. */
  .reader { padding: 1.4rem 1rem 1.4rem 1.5rem; }
  .reader::before { width: 0.6rem; }
  .reader::after { inset: 0.5rem 0.5rem 0.5rem 0.95rem; }
  .reader-body { padding: 1.2rem 1rem 1.2rem 1.3rem; }
  .reader-body::before { left: 0.7rem; }
  .reader-preview, .reader-rest {
    text-align: justify;
    -webkit-hyphens: auto;
    hyphens: auto;
  }
  /* Same left/right mirror as the desktop RTL block near .timeline
     above, just re-applied on top of this breakpoint's own narrower
     values - without this, an RTL reader on a phone (not just desktop)
     would still get the spine/ruled-margin line on the wrong edge. */
  html[dir="rtl"] .reader { padding-left: 1rem; padding-right: 1.5rem; }
  html[dir="rtl"] .reader::after { inset: 0.5rem 0.95rem 0.5rem 0.5rem; }
  html[dir="rtl"] .reader-body { padding-left: 1rem; padding-right: 1.3rem; }
  html[dir="rtl"] .reader-body::before { left: auto; right: 0.7rem; }
}

/* Sober micro-interactions, manager mode (see PROGRESS.md/EVOLUTION.md
   for the full list of where these are used). Silent register only:
   a brief fade/color transition, never an icon or added word - the
   .toast above stays reserved for moments the family just validated a
   deliberate action (see panels.js). */
.sortable-list [draggable="true"] { transition: opacity 0.18s ease; }
.sortable-list [draggable="true"].is-dragging { opacity: 0.4; }

/* Instant, silent removal for .btn-icon-trash (photo/album/archive-link/
   video delete) - a brief fade instead of the item just vanishing on
   the next full-page reload. No confirmation dialog and no added word:
   this is deliberately the same frictionless single-click deletion
   already in place, not a new safety gate. */
.item-removing { transition: opacity 0.2s ease, transform 0.2s ease; opacity: 0; transform: scale(0.97); pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Save confirmation toast: green, checkmark, auto-dismissing — for
   actions saved via fetch() in place (see panels.js), which would
   otherwise be silent since nothing on screen visibly reloads. */
.toast-container {
  position: fixed; bottom: 1.6rem; left: 50%; transform: translateX(-50%);
  /* width: max-content (not the default shrink-to-fit auto) - with only
     `left` set and no `right`, a fixed-position box's shrink-to-fit
     available width is (containing block width - left offset), which at
     left:50% halves with the viewport. On desktop that's still hundreds
     of px, so it never visibly wrapped; at a 375px mobile width it drops
     to ~187px, forcing toast text into an awkward mid-phrase wrap
     (external UX audit, mobile pass). max-content sizes to the real
     content instead, and max-width keeps it from overflowing on a
     genuinely long message. */
  width: max-content; max-width: calc(100vw - 2rem);
  z-index: 500; display: flex; flex-direction: column; gap: 0.5rem; align-items: center;
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 0.5rem;
  background: #2e7d46; color: #fff; padding: 0.55rem 1.1rem; border-radius: 999px;
  font-size: 0.88rem; font-weight: 500; box-shadow: 0 6px 18px rgba(20, 16, 8, 0.22);
  opacity: 0; transform: translateY(12px); transition: opacity 0.25s ease, transform 0.25s ease;
}
.toast.visible { opacity: 1; transform: translateY(0); }

/* Undo toast for .btn-icon-trash deletions (see panels.js) - deliberately
   NOT the green save-confirmation styling above (this is neither a save
   nor something to celebrate) and NOT a confirmation dialog before the
   click (families explicitly asked for instant single-click deletion).
   The undo window is the safety net instead. */
.toast-undo { background: var(--ink); pointer-events: auto; }
.toast-undo-btn {
  background: none; border: none; color: #fff; font: inherit; font-weight: 700;
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer; padding: 0;
}

/* Pull-to-refresh (see pull-to-refresh.js) - only ever inserted into the
   DOM when the site is running as an installed standalone app, where
   iOS/Android remove the browser's own native pull-to-refresh gesture
   with no way to opt back in. Sits above the page, pulled into view by
   the script as the finger moves; never visible in a normal browser tab. */
.ptr-indicator {
  position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 1000;
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  background: var(--paper-raised); border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow); color: var(--ink-soft); font-size: 0.82rem;
  transform: translateY(-44px); pointer-events: none;
}
.ptr-spinner {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--ink-soft);
  flex-shrink: 0;
}
.ptr-indicator.is-refreshing .ptr-spinner { animation: ptr-spin 0.7s linear infinite; }
.ptr-indicator.is-armed .ptr-spinner { border-top-color: var(--accent-text); }
@keyframes ptr-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .ptr-indicator.is-refreshing .ptr-spinner { animation: none; }
}
