:root {
  /* Tells the browser this page supports both themes and to draw native
     control chrome (date pickers, select arrows, etc.) accordingly,
     instead of guessing — a mismatch here is a likely cause of a native
     date input's own internal "mm/dd/yyyy" hint rendering with too little
     contrast against whatever background it falls back to. */
  color-scheme: light dark;
  --bg: #f4f1ea;
  --surface: #ffffff;
  --ink: #2b2620;
  --ink-soft: #6b6355;
  --accent: #a8562f;
  --accent-dark: #7c3f21;
  /* Solid-fill button colors as their own tokens rather than reusing
     --accent/--accent-dark directly: Midnight Magenta needs its buttons
     white-on-black while --accent itself stays magenta everywhere else
     (borders/rings). Keeping this as a single themed variable, the same
     pattern as every other color in this file, means .btn-primary etc.
     never need a second, higher-specificity rule to win against a "wrong"
     base value -- there is only ever one value to resolve. */
  --btn-fill: var(--accent);
  --btn-fill-hover: var(--accent-dark);
  --btn-fill-ink: #ffffff;
  --line: #b79f7c;
  --card-border: #d8cdb8;
  --danger: #b23a3a;
  --shadow: 0 2px 6px rgba(60, 45, 20, 0.15);
  --radius: 10px;
  /* Centric view's stepped ring colors (see drawCentricGrid in app.js) --
     dedicated tokens rather than reusing --ink/--surface, since those
     flip which one reads as "light" between themes while this always
     needs --centric-inner to be the lighter of the two. Inner matches
     --bg exactly (the same beige used for the page itself) so the
     center ring reads as an extension of the app's own background;
     outer is a light grey, kept close to the inner value so each ring's
     step is a subtle shade change rather than a bold color jump. */
  --centric-inner: #f4f1ea;
  --centric-outer: #c9c9c9;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  background: var(--bg);
  color: var(--ink);
}

body {
  display: flex;
  flex-direction: column;
}

/* ---------- Toolbar ---------- */
.toolbar {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  background: var(--surface);
  border-bottom: 1px solid var(--card-border);
  z-index: 20;
  flex-wrap: nowrap;
  flex-shrink: 0;
}

.toolbar-title {
  font-size: 0.85rem;
  font-weight: 600;
  margin: 0;
  white-space: nowrap;
  color: var(--accent-dark);
}

.toolbar-spacer {
  flex: 1 1 auto;
  min-width: 0;
}

.view-mode-select {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--accent-dark);
  background: var(--surface);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  padding: 5px 6px;
  max-width: 118px;
  flex-shrink: 1;
  min-width: 0;
}

.sync-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--ink-soft);
}
.sync-dot.local { background: #b7a98a; }
.sync-dot.connecting, .sync-dot.saving { background: #c99a2e; }
.sync-dot.connected { background: #2e7d3d; }
.sync-dot.error { background: var(--danger); }

/* ---------- Floating quick-actions stack ---------- */
/* Add person / search / help / fit-to-view, stacked as one column of
   circular buttons over the bottom-right corner of the tree viewport --
   grouping them here (rather than spread across the header) keeps every
   "do something to the tree" action within a thumb's reach on a phone or
   tablet, where the header itself can otherwise scroll out of easy reach. */
.quick-actions {
  position: absolute;
  right: 16px;
  bottom: 16px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.quick-action-btn {
  appearance: none;
  -webkit-appearance: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--card-border);
  background: var(--surface);
  color: var(--ink-soft);
  box-shadow: var(--shadow);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1;
  transition: background .15s, border-color .15s, color .15s, transform .1s;
}
.quick-action-btn svg { width: 18px; height: 18px; }
.quick-action-btn:hover { background: var(--btn-fill); border-color: var(--btn-fill); color: var(--btn-fill-ink); }
.quick-action-btn:active { transform: scale(0.92); }

.quick-action-primary {
  background: var(--btn-fill);
  border-color: var(--btn-fill);
  color: var(--btn-fill-ink);
  font-size: 1.4rem;
}
.quick-action-primary:hover { background: var(--btn-fill-hover); border-color: var(--btn-fill-hover); }

/* The search toggle is itself a .quick-action-btn (same circle, same
   spot in the stack); opening it slides a pill-shaped input out from
   behind it to the left, rather than growing the circle itself, so the
   button's position in the column never shifts. */
.search-wrap { position: relative; width: 44px; height: 44px; }
.search-input {
  position: absolute;
  top: 0;
  right: 44px;
  height: 44px;
  width: 0;
  padding: 0;
  margin: 0;
  border: 1px solid var(--card-border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  outline: none;
  box-shadow: var(--shadow);
  opacity: 0;
  overflow: hidden;
  transition: width .22s ease, opacity .18s ease, padding .22s ease;
}
.search-wrap.open .search-input {
  width: 200px;
  opacity: 1;
  padding: 0 14px;
}

.btn {
  /* Real Safari draws native button chrome (a subtle system gradient/
     shadow) on top of author colors unless appearance is reset -- on a
     light custom background (e.g. Midnight Magenta's white buttons) that
     overlay visibly dulls/grays it, which Chromium doesn't reproduce the
     same way. Same reason .location-date-part resets appearance below. */
  appearance: none;
  -webkit-appearance: none;
  padding: 7px 12px;
  border: 1px solid var(--card-border);
  background: var(--surface);
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--ink);
  transition: background .15s, border-color .15s;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.btn[hidden] { display: none; }
.btn:hover { background: #f1ead9; border-color: var(--accent); }
.btn-primary { background: var(--btn-fill); border-color: var(--btn-fill); color: var(--btn-fill-ink); }
.btn-primary:hover { background: var(--btn-fill-hover); border-color: var(--btn-fill-hover); }
.btn-danger { background: transparent; color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: #fff; }
.btn-subtle { background: transparent; border-color: transparent; color: var(--ink-soft); }
.icon-btn {
  appearance: none;
  -webkit-appearance: none;
  background: none; border: none; font-size: 1.1rem; cursor: pointer; color: var(--ink-soft);
  padding: 4px 8px; border-radius: 6px;
}
.icon-btn:hover { background: #f1ead9; }
.icon-btn svg { display: block; width: 16px; height: 16px; }

/* ---------- Tree viewport ---------- */
.tree-viewport {
  position: relative;
  flex: 1;
  overflow: hidden;
  cursor: grab;
  touch-action: none;
  background-image: radial-gradient(circle, #e3dac6 1px, transparent 1px);
  background-size: 22px 22px;
}
.tree-viewport.grabbing { cursor: grabbing; }

.tree-canvas {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}

.lines-svg {
  position: absolute;
  top: 0;
  left: 0;
  overflow: visible;
  pointer-events: none;
}

/* One-shot Midnight Magenta overlay (see playChronoDotAssemble in app.js)
   -- viewport-fixed, not part of #treeCanvas's pannable/zoomable transform,
   matching where the ambient dot-matrix background it stands in for
   actually lives. */
.chrono-dot-assemble {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
}

.tree-content {
  position: relative;
}

/* Chronological view's year ruler. Sits outside #treeCanvas so horizontal
   pan never moves it (it stays pinned to the viewport's left edge), while
   its inner layer gets the SAME vertical translate+scale as the canvas
   applied via JS, so it still tracks vertical pan/zoom in step with the
   rows it's labeling. */
.chrono-ruler {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 56px;
  background: var(--bg);
  border-right: 1px solid var(--card-border);
  overflow: hidden;
  pointer-events: none;
  z-index: 2;
  /* Faded via opacity (toggling the .visible class), not the hidden
     attribute -- display:none can't transition, and this needs to fade
     in/out alongside the view-mode switch rather than popping. */
  opacity: 0;
  transition: opacity .32s ease;
}
.chrono-ruler.visible { opacity: 1; }
.chrono-ruler-inner {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  transform-origin: 0 0;
}
.chrono-year-label {
  position: absolute;
  left: 0;
  width: 100%;
  text-align: center;
  transform: translateY(-50%);
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}
.chrono-year-label.today {
  color: var(--accent-dark);
  font-weight: 700;
}

.zodiac-column-header {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding-bottom: 8px;
  border-bottom: 2px solid var(--card-border);
  color: var(--ink-soft);
  text-align: center;
}
.zodiac-header-emoji { font-size: 1.6rem; line-height: 1; }
.zodiac-header-label {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.centric-metric-toggle {
  position: absolute;
  left: 16px;
  top: 16px;
  display: flex;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--card-border);
  border-radius: 999px;
  box-shadow: var(--shadow);
  padding: 3px;
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s ease;
}
.centric-metric-toggle.visible { opacity: 1; pointer-events: auto; }
.centric-metric-btn {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: none;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background .15s, color .15s;
}
.centric-metric-btn:hover:not(.active) { background: #f1ead9; }
.centric-metric-btn.active { background: var(--btn-fill); color: var(--btn-fill-ink); }

.centric-center-card .person-photo {
  border-color: var(--accent);
  box-shadow: 0 6px 18px rgba(60,45,20,0.28);
}

.empty-state {
  position: absolute;
  top: 45%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--ink-soft);
  text-align: center;
  max-width: 320px;
  font-size: 1rem;
}

/* ---------- Person card ---------- */
.person-card {
  position: absolute;
  width: 150px;
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  user-select: none;
  /* left/top drive the animated re-layout transition (see animateLayoutIn
     in app.js) -- kept separate from the hover lift's own transform
     transition above so the two never fight over one property. */
  transition: transform .12s, left .38s ease, top .38s ease;
  flex-shrink: 0;
}
.person-card:hover { transform: translateY(-3px); }
.person-card:hover .person-photo { box-shadow: 0 6px 14px rgba(60,45,20,0.22); }
.person-card.deceased { filter: grayscale(0.35); opacity: 0.88; }
.person-card.married-in .person-photo { border-style: dashed; border-color: var(--line); }
.person-card.highlight .person-photo {
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 6px #ffd54a;
}

.person-photo {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  border: 2px solid var(--card-border);
  background: #eee2c9;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.1rem;
  color: #b79f7c;
  overflow: hidden;
  /* Border/shadow (state rings) are the only things that ever change here
     -- position/layout transitions live on .person-card itself. */
  transition: border-color .12s, box-shadow .12s;
}
.person-photo img { width: 100%; height: 100%; object-fit: cover; }

.person-info { margin-top: 8px; text-align: center; width: 100%; }
.person-name {
  font-weight: 600;
  font-size: 0.8rem;
  line-height: 1.2;
  word-break: break-word;
}
.person-dates {
  font-size: 0.68rem;
  color: var(--ink-soft);
  margin-top: 2px;
}

/* ---------- Modal ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  /* Shared by the Add/Edit form and the Person View card -- kept the same
     dimness on both so the backdrop never visibly changes just from
     tapping Edit on an open card, or Close on the edit form back to it. */
  background: rgba(20, 16, 10, 0.78);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
}
.modal-overlay[hidden] {
  display: none;
}

.modal {
  background: var(--surface);
  border-radius: 12px;
  width: 100%;
  max-width: 460px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--card-border);
}
.modal-header h2 { margin: 0; font-size: 1.1rem; }

.modal-body {
  padding: 16px 20px;
  overflow-y: auto;
}

.form-row { margin-bottom: 14px; display: flex; flex-direction: column; gap: 5px; }
.form-row label { font-size: 0.82rem; font-weight: 600; color: var(--ink-soft); }
.form-row input[type=text],
.form-row select,
.form-row textarea,
.editable-text {
  padding: 8px 10px;
  border: 1px solid var(--card-border);
  border-radius: 6px;
  /* Explicit, not inherited: applying a custom border/radius to a native
     control (select) can make iOS Safari fall back to its own internal
     control colors for whatever it still draws itself instead of the
     page's colors — and if that happens to render low- or no-contrast
     against its own fill, the field looks like a solid blank box even
     though it isn't. */
  background: var(--surface);
  color: var(--ink);
  /* 16px, not a smaller rem value: iOS Safari auto-zooms the whole page in
     on focus for any text input under 16px, and doesn't reliably zoom back
     out once the modal containing it closes — which was the real cause of
     the header appearing to vanish after Save, not a scroll issue. */
  font-size: 16px;
  font-family: inherit;
  width: 100%;
}
.form-row select[multiple] { height: auto; }

/* Full name/Location: a plain contenteditable div, not an <input>, so iOS
   Safari's Contacts/AutoFill heuristic (which kept suggesting to fill the
   Name field from the Contacts app, unrelated to and unaffected by
   autocomplete="off") has no native form field to key off of at all. We
   draw our own placeholder since contenteditable has no native one. */
.editable-text {
  min-height: 1.4em;
  white-space: pre-wrap;
  word-break: break-word;
}
.editable-text.is-empty::before {
  content: attr(data-placeholder);
  color: var(--ink-soft);
  pointer-events: none;
}
.two-col { flex-direction: row; flex-wrap: wrap; gap: 12px; }
.two-col > div { flex: 1 1 130px; min-width: 130px; display: flex; flex-direction: column; gap: 5px; }

/* Born/Died: a native <input type=date>'s own internal rendering (the
   mm/dd/yyyy hint, the calendar icon, a populated value) has a
   platform-dependent minimum width that iOS Safari won't shrink below,
   which repeatedly caused it to render blank or overflow its box when
   squeezed into a two-column layout. Instead, the real input is made
   fully invisible but still on top (so a tap opens the native picker
   exactly as normal) and .date-display — a plain, fully custom text box
   we render ourselves — shows the value/placeholder underneath. Being
   ordinary CSS, it can only ever ellipsis-truncate if truly out of room;
   it can never overflow its box or render invisible content. */
.date-field { position: relative; }
.date-field input[type=date] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  z-index: 1;
  /* Kept at 16px (even though invisible) so tapping it doesn't trigger
     iOS Safari's auto-zoom-on-focus for sub-16px text inputs. */
  font-size: 16px;
}
.date-display {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  padding: 8px 6px;
  border: 1px solid var(--card-border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink);
  font-size: 16px;
  white-space: nowrap;
  overflow: hidden;
}
.date-display-text {
  overflow: hidden;
  text-overflow: ellipsis;
}
.date-display.placeholder .date-display-text { color: var(--ink-soft); }
.date-icon {
  flex-shrink: 0;
  display: flex;
  color: var(--ink-soft);
}
.date-icon svg { width: 15px; height: 15px; }
.hint { color: var(--ink-soft); font-size: 0.72rem; }
.zodiac-adjusted-hint { margin: 6px 0 0; }

.combo {
  position: relative;
}

.location-field {
  position: relative;
  flex: 1;
}
/* Room for the calendar icon (see .location-dates-btn) sitting inside the
   field's own right edge -- same placement as the Documented birthday
   field's calendar icon. Birth location gets the same treatment as each
   Location(s) row now that it has its own date-range toggle too (see
   setupBirthLocationDates in app.js). */
.location-field .location-row-input,
.location-field #birthLocationInput { padding-right: 30px; }
.location-suggestions .combo-options { max-height: 220px; }

.locations-list { display: flex; flex-direction: column; gap: 8px; }
.location-row { display: flex; align-items: center; gap: 8px; position: relative; z-index: 1; }
.location-row.dragging { z-index: 2; }
/* Each row's own z-index: 1 makes it a stacking context of its own, which
   traps its date popover's z-index: 50 inside that context -- painted as
   one unit, the whole row (popover included) sits BEHIND any later
   sibling row's stacking context, since two contexts at an equal z-index
   paint in DOM order. Without this, only the very last row's popover
   could ever show; every other row's was silently covered by the rows
   below it. Bumped the same way .dragging already bumps a row while it's
   being dragged. */
.location-row.dates-open { z-index: 3; }
.location-row-handle {
  flex-shrink: 0;
  display: flex;
  color: var(--ink-soft);
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.location-row-handle svg { width: 18px; height: 18px; }
.location-row.dragging .location-row-handle { cursor: grabbing; }
.location-current-tag {
  flex-shrink: 0;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--accent-dark);
  border: 1px solid var(--accent-dark);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
}
/* Kept in flow (not the native [hidden]{display:none}) so every row's
   .location-field is the SAME width whether or not it's the current one --
   without this, the current row's field was narrower (this tag eating
   flex space only there), so ITS date popover -- sized to match its own
   field, see .location-date-popover -- ended up narrower than the row
   below it and visually misaligned with it once opened. */
.location-current-tag[hidden] {
  display: inline-flex;
  visibility: hidden;
}
.location-row-remove {
  flex-shrink: 0;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 0.85rem;
  padding: 4px 6px;
}
.location-row-remove:hover { color: var(--danger); }

/* Calendar-icon date-range toggle -- sits inside the location field
   itself, at its trailing edge (see the field's own right-padding above),
   same placement as the Documented birthday field's calendar icon. Muted
   while neither a start nor end date is set, accent-colored once either
   is (see .has-dates, toggled in addLocationRow), so the row hints
   whether a date range is recorded without opening the popover to check. */
.location-dates-btn {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.location-dates-btn svg { width: 15px; height: 15px; }
.location-dates-btn:hover { background: #f1ead9; color: var(--accent-dark); }
.location-dates-btn.has-dates { color: var(--accent); }
/* Start and End side by side (not stacked) -- both are just one label +
   one year <select>, short enough that a horizontal layout reads as one
   line rather than needing two. width: 100% (of .location-field, its
   positioned ancestor) so the popover matches a narrow field exactly, but
   capped with max-width so a wide, mostly-empty field (long placeholder
   text, no location typed yet) doesn't stretch it into an oddly thin,
   overly spread-out bar -- it should never need to be wider than this to
   comfortably fit Start/End/close. Anchored via right: 0 either way, so
   it stays flush with the field's own right edge. */
.location-date-popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  /* space-between, not a flex: 1 group each: the Start/End groups and the
     close button all size to their own content, and whatever room is left
     over (so the popover still spans the field's full width, up to its
     own max-width) becomes the space between them instead of stretching
     either group -- see .location-date-group below for why stretching
     them was the wrong fix. */
  justify-content: space-between;
  width: 100%;
  max-width: 280px;
  padding: 5px 12px;
  background: var(--surface);
  border: 1px solid var(--card-border);
  border-radius: 8px;
  box-shadow: var(--shadow);
}
/* Needs its own [hidden] override, same reason as .view-photo/.view-section
   elsewhere -- this rule's own explicit display:flex would otherwise beat
   the UA stylesheet's [hidden]{display:none} regardless of the attribute. */
.location-date-popover[hidden] { display: none; }
/* Content-sized (not flex: 1) -- a stretched group with a fixed-width
   label left "Start"/"End" sitting at different distances from their own
   trailing edge (the shorter "End" text stranding blank space inside its
   own label box before the gap even starts), which read as an
   inconsistent label-to-select gap even though the flex `gap` itself was
   identical. Hugging its own content keeps that gap visually uniform. */
.location-date-group { display: flex; align-items: center; gap: 6px; }
.location-date-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
}
/* Doubly-qualified (not just .location-date-part alone) so this actually
   beats .form-row select's (0,1,1) specificity -- this select DOES sit
   inside a .form-row, and without this its 16px font-size and 8px
   vertical padding silently won, clipping "yyyy" and making the box
   taller than intended. A fixed width (not flex: 1, now that its group no
   longer stretches) so Start's and End's selects are still the same size
   as each other regardless of their own label's text length.
   appearance: none + a drawn-in-CSS chevron, rather than the browser's own
   native arrow -- Chromium/Firefox both reserve a fixed ~20px of built-in
   space for their own arrow regardless of padding, which left a much
   wider gap after "yyyy" than this cramped a field has room for. This
   doesn't affect iOS's own spinning-wheel picker still opening on tap --
   that's driven by the element being a <select> at all, not by how its
   closed-state box is painted. */
.location-date-group .location-date-part {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 64px;
  padding: 4px 14px 4px 6px;
  border: 1px solid var(--card-border);
  border-radius: 6px;
  background-color: var(--surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b6355' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 3px center;
  background-size: 11px;
  color: var(--ink);
  font-size: 0.82rem;
  font-family: inherit;
}
/* Explicit close, in addition to the existing outside-click-to-close --
   the popover has no other visible affordance for "I'm done" now that
   it's a compact inline row rather than a dropdown-shaped panel. */
.location-date-close {
  flex-shrink: 0;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 0.85rem;
  padding: 4px 0;
}
.location-date-close:hover { color: var(--danger); }
.add-location-btn {
  align-self: flex-start;
  margin-top: 8px;
  border: none;
  background: none;
  color: var(--accent-dark);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  padding: 4px 0;
}

.contacts-list { display: flex; flex-direction: column; gap: 8px; }
.contact-row { display: flex; align-items: center; gap: 8px; }
.contact-field { flex: 1; }
.contact-row-remove {
  flex-shrink: 0;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 0.85rem;
  padding: 4px 6px;
}
.contact-row-remove:hover { color: var(--danger); }
.add-contact-btn {
  align-self: flex-start;
  margin-top: 8px;
  border: none;
  background: none;
  color: var(--accent-dark);
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  padding: 4px 0;
}
.contact-gmail-btn {
  flex-shrink: 0;
  white-space: nowrap;
  border: 1px solid var(--accent-dark);
  background: transparent;
  color: var(--accent-dark);
  font-weight: 600;
  font-size: 0.78rem;
  border-radius: 999px;
  padding: 6px 10px;
  cursor: pointer;
}

/* A real <button>, not a text input: tapping it must never raise the
   on-screen keyboard — only the .combo-filter inside the open dropdown
   should do that, and only once someone deliberately taps it. */
.combo-trigger {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--card-border);
  border-radius: 6px;
  background: var(--surface);
  font-size: 16px;
  font-family: inherit;
  text-align: left;
  color: var(--ink);
  cursor: pointer;
}
.combo-trigger.placeholder .combo-trigger-text { color: var(--ink-soft); }
.combo-clear {
  position: absolute;
  right: 6px;
  top: 6px;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 0.85rem;
  padding: 4px 6px;
}
.combo-clear:hover { color: var(--danger); }
.combo-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  z-index: 50;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.combo-filter-wrap { position: relative; }
.combo-filter-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-soft);
  pointer-events: none;
  display: flex;
}
.combo-filter-icon svg { width: 15px; height: 15px; }
.combo-filter {
  display: block;
  width: 100%;
  padding: 8px 10px 8px 32px;
  border: none;
  border-bottom: 1px solid var(--card-border);
  border-radius: 6px 6px 0 0;
  font-size: 16px;
  font-family: inherit;
  background: var(--bg);
}
.combo-options {
  max-height: 180px;
  overflow-y: auto;
}
.combo-option {
  padding: 9px 10px;
  cursor: pointer;
  font-size: 0.87rem;
}
.combo-option:hover { background: #f1ead9; }
.combo-option-create {
  font-weight: 600;
  color: var(--accent-dark);
  border-bottom: 1px solid var(--card-border);
}
.combo-option-empty {
  padding: 9px 10px;
  font-size: 0.85rem;
  font-style: italic;
  color: var(--ink-soft);
}
.combo-confirm { padding: 10px; }
.combo-confirm-text { margin: 0 0 8px; font-size: 0.85rem; }
.combo-confirm-actions { display: flex; gap: 8px; }
.combo-confirm-actions .btn { flex: 1; padding: 7px 10px; font-size: 0.85rem; }
.combo-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.combo-chips:not(:empty) { margin-bottom: 6px; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: #f1ead9;
  border: 1px solid var(--card-border);
  border-radius: 999px;
  padding: 3px 6px 3px 10px;
  font-size: 0.8rem;
  /* Fixed, not var(--ink): the chip's own background stays this same light
     cream in both themes, so its text needs to stay dark right along with
     it rather than following dark mode's light --ink. */
  color: #2b2620;
}
.chip-remove {
  border: none;
  background: none;
  cursor: pointer;
  color: #6b6355; /* fixed, see .chip */
  font-size: 0.75rem;
  line-height: 1;
  padding: 2px;
}
.chip-remove:hover { color: var(--danger); }

/* The spouse chip's current/former toggle -- a small pill of its own
   inside the chip, tap to flip. Fixed colors for the same reason as
   .chip: this whole row sits on the chip's own always-light background,
   never the theme's surface. */
.chip-status {
  border: 1px solid #b79f7c;
  background: transparent;
  color: #6b6355;
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 999px;
  cursor: pointer;
}
.chip-status-former {
  border-color: var(--danger);
  color: var(--danger);
}

.photo-row {
  flex-direction: row;
  align-items: center;
  gap: 16px;
}
.photo-preview {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
  border: 1px solid var(--card-border);
  color: var(--ink-soft);
  cursor: pointer;
}
.photo-preview img { width: 100%; height: 100%; object-fit: cover; }
#photoPlaceholder { display: flex; }
/* Higher-specificity #id rules above (needed to center the icon) would
   otherwise beat the UA's [hidden]{display:none} and keep this visible
   even while hidden -- the exact bug that once broke the person modal. */
#photoPlaceholder[hidden] { display: none; }
#photoPlaceholder svg { width: 30px; height: 30px; }
.photo-controls { display: flex; flex-direction: row; align-items: center; gap: 8px; }

.modal-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-top: 6px;
}
.modal-footer .spacer { flex: 1; }

/* ---------- Photo crop overlay ---------- */
.crop-modal { max-width: 380px; }
.crop-viewport {
  position: relative;
  width: 100%;
  max-width: 320px;
  aspect-ratio: 150 / 110;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 8px;
  border: 2px solid var(--card-border);
  background: #1a1712;
  cursor: grab;
  touch-action: none;
}
.crop-viewport.grabbing { cursor: grabbing; }
.crop-viewport img {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;
  user-select: none;
  -webkit-user-drag: none;
}
.crop-zoom-row {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 320px;
  margin: 12px auto 4px;
  color: var(--ink-soft);
}
.crop-zoom-row input[type=range] { flex: 1; }
.crop-modal .hint { display: block; text-align: center; margin-bottom: 4px; }

/* ---------- Help guide ---------- */
.help-modal { max-width: 480px; }
.help-modal-body { padding-top: 4px; }
/* Sibling of .help-modal-body (not nested inside it, see index.html), so
   it needs its own full padding -- the base .modal-footer rule only ever
   supplied padding-top, relying on being nested inside a padded
   .modal-body everywhere else it's used. */
.help-modal .modal-footer {
  padding: 10px 20px 16px;
  border-top: 1px solid var(--card-border);
}
.help-section { margin-top: 18px; }
.help-section:first-child { margin-top: 0; }
.help-section h3 {
  margin: 0 0 8px;
  padding-bottom: 8px;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--card-border);
}
.help-section p { margin: 0; font-size: 0.88rem; line-height: 1.45; }
.help-section p + p { margin-top: 10px; }
.help-list { margin: 0; padding-left: 18px; font-size: 0.88rem; line-height: 1.5; }

/* Small directional-pad illustration for the swipe-to-navigate feature --
   its own standalone chevron glyphs, purely explanatory (the real card's
   own swipe indicators are the dot strips around the photo, not chevrons;
   see renderDots in app.js). */
.help-swipe-diagram {
  display: grid;
  grid-template-columns: repeat(3, 36px);
  grid-template-rows: repeat(3, 36px);
  justify-content: center;
  gap: 2px;
  margin: 12px auto 6px;
}
.help-swipe-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent-dark);
}
.help-swipe-arrow svg { width: 18px; height: 18px; }
.help-swipe-up { grid-column: 2; grid-row: 1; }
.help-swipe-left { grid-column: 1; grid-row: 2; }
.help-swipe-right { grid-column: 3; grid-row: 2; }
.help-swipe-down { grid-column: 2; grid-row: 3; }
.help-swipe-center {
  grid-column: 2;
  grid-row: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--card-border);
  border-radius: 50%;
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.help-swipe-legend { margin: 0; font-size: 0.8rem; color: var(--ink-soft); text-align: center; }
.help-list li { margin-bottom: 6px; }
.help-list li:last-child { margin-bottom: 0; }

/* ---------- First-time walkthrough (Driver.js) ---------- */
/* Driver.js's own DOM/classes (see startTour() in app.js), restyled to
   match the app's theme instead of its default light popover. */
.driver-popover {
  background: var(--surface);
  color: var(--ink);
  border-radius: 10px;
  box-shadow: 0 10px 40px rgba(0,0,0,0.35);
  padding: 16px;
  font-family: inherit;
}
.driver-popover-description {
  color: var(--ink);
  font-size: 0.88rem;
  line-height: 1.45;
  margin: 0;
}
.driver-popover-footer { margin-top: 12px; }
.driver-popover-progress-text { color: var(--ink-soft); font-size: 0.72rem; }
.driver-popover-footer-btn {
  appearance: none;
  -webkit-appearance: none;
  padding: 7px 12px;
  border: 1px solid var(--card-border);
  background: var(--surface);
  border-radius: 6px;
  font-size: 0.9rem;
  color: var(--ink);
  transition: background .15s, border-color .15s;
}
.driver-popover-footer-btn:hover { background: #f1ead9; border-color: var(--accent); }
.driver-popover-next-btn, .driver-popover-done-btn {
  background: var(--btn-fill);
  border-color: var(--btn-fill);
  color: var(--btn-fill-ink);
}
.driver-popover-next-btn:hover, .driver-popover-done-btn:hover {
  background: var(--btn-fill-hover);
  border-color: var(--btn-fill-hover);
}
/* Relabeled "Skip" via onPopoverRender in app.js -- reset out of the
   default unlabeled corner "x" icon's styling into a plain text link. */
.driver-popover-close-btn {
  all: unset;
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 4px 6px;
  position: absolute;
  top: 14px;
  right: 14px;
  transition: color .15s;
}
.driver-popover-close-btn:hover, .driver-popover-close-btn:focus { color: var(--ink); }
/* The currently-highlighted control -- Driver.js cuts it out of the
   dimmed backdrop itself, this just adds back the accent-colored ring
   the old hand-rolled tour drew around it. */
.driver-active-element { box-shadow: 0 0 0 3px var(--accent); }

/* ---------- Person view (read-only detail) modal ---------- */
/* Top-aligned rather than the .modal-overlay default of vertically
   centered: a single card (birth location/zodiac/contacts/etc.) and a
   couple card are rarely the same height, and centering would put their
   top edges -- header, photo, swipe zone -- at a different Y each time,
   so swiping between them would visibly jerk the whole card up or down
   even though nothing about the gesture itself changed. Fixing the top
   edge in place means only the *bottom* of the card moves with content
   height, never the part you're actually looking at and dragging. */
#personViewModal { align-items: flex-start; }
/* A fixed height (not just a cap) rather than sizing to content: single
   and couple cards, and cards with very different numbers of relation-
   list rows, would otherwise never be quite the same size, which is what
   the swipe-drag overlay's outgoing/incoming dimensions had to work
   around. Making every card the same size removes the problem at the
   source -- a short card just shows blank space above its footer, and a
   long one scrolls internally same as before.
   On the class, not "#personViewCard": the swipe-drag overlay strips ids
   off its clones (see stripIds in app.js) so they never collide with the
   real, still-present card -- an id-selector rule (like the header-height
   bug this same overlay caused before) would silently stop applying to
   the clones the instant that happened, which is exactly what let their
   heights diverge back to their natural content size mid-swipe. */
.view-modal { max-width: 420px; height: min(800px, 88vh); }
/* A bit more top padding than the default .modal-body -- with no header
   bar above it anymore, the card's top edge needs its own breathing room
   instead of relying on a header to separate it from the swipe zone. */
.view-modal-body { flex: 1; min-height: 0; padding-top: 40px; padding-bottom: 24px; }
/* Sibling of .view-modal-body now (see index.html), not nested inside
   it, so it stays pinned to the card's bottom edge instead of scrolling
   away with the content or floating right after it on a short card. It
   needs its own full padding since it no longer sits inside
   .modal-body's padded box. */
.view-modal-footer {
  padding: 6px 20px 16px;
  border-top: 1px solid var(--card-border);
}
/* The photo/name/dates header doubles as a swipe pad -- disabling the
   browser's own touch scrolling here (rather than on the whole modal body,
   which still needs to scroll normally for long relation lists) is what
   lets a vertical drag mean "go to parent/child" instead of just scrolling. */
.view-swipe-zone { position: relative; touch-action: none; cursor: grab; }
.view-swipe-zone:active { cursor: grabbing; }

/* ---------- Swipe drag-follow overlay ---------- */
/* While a card swipe is in progress, the real #personViewCard (the whole
   modal card -- header, photo/info, relation lists, footer) is hidden and
   this fixed-position layer takes over: a pixel-perfect clone of the
   outgoing card plus a full clone of the incoming one (see
   startSwipeDrag), both translated 1:1 with the pointer so the whole next
   card visibly slides in as you drag -- see the pointermove handler in
   app.js. Removed again once the gesture ends (committed or sprung back)
   and the real card is shown again. */
.swipe-drag-layer {
  position: fixed;
  overflow: hidden;
  z-index: 150;
  pointer-events: none;
}
.swipe-card-slot {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
}
.swipe-card-slot.swipe-settling { transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1); }

.view-person {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  width: 100%;
}
.view-person[hidden] { display: none; }
.view-photo-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 100%;
}
/* Couple mode's photo pair (106px) is shorter than the single photo
   (160px) it stands in for -- this extra 27px top+bottom padding makes up
   exactly that 54px difference, so the pair's own vertical center still
   lands on the same line the single photo's center would, keeping the
   dot strips' spacing feel consistent between the two instead of the
   whole block shrinking to fit the smaller photo. */
.view-photo-block.is-couple { padding: 27px 0; }
/* Fixed 1fr/auto/1fr columns (not just a centered flex row) so the photo
   stays exactly centered even when one side has more dots than the
   other -- an empty or hidden side dot strip still holds its own 1fr
   column, rather than letting the photo drift toward whichever side has
   fewer dots. */
.view-photo-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  justify-items: center;
  width: 100%;
}
/* Pin the photo to the middle track explicitly -- a hidden (display:none)
   side-dot strip drops out of grid flow entirely, so without this,
   auto-placement would slide the photo into column 1 (and any dots on
   the other side into column 2) the moment a person has siblings on only
   one side, or none at all. */
.view-photo, .view-photo-pair { grid-column: 2; }
#viewDotsSideLeft { grid-column: 1; }
#viewDotsSideRight { grid-column: 3; }
.view-dots { display: flex; align-items: center; gap: 5px; min-height: 6px; }
.view-dots[hidden] { display: none; }
.view-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink-soft);
  flex-shrink: 0;
}
.view-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
}
.view-name-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 100%;
}
.view-divider { width: 100%; height: 1px; background: var(--card-border); flex-shrink: 0; }
.view-divider[hidden] { display: none; }
.view-details {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  text-align: center;
  color: var(--ink-soft);
  font-size: 0.88rem;
}
.view-details p { margin: 0; }
.view-details[hidden] { display: none; }
.view-photo {
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background: #eee2c9;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  color: #b79f7c;
}
.view-photo img { width: 100%; height: 100%; object-fit: cover; }
/* An #id rule needs its own [hidden] override to not beat the UA
   stylesheet -- .view-photo's own explicit display:flex above otherwise
   wins over [hidden]'s display:none regardless of the hidden attribute,
   the same bug already noted for #photoPlaceholder/#viewPhotoPlaceholder
   below. Only matters now that couple mode toggles #viewPhoto's hidden
   state at all (it used to always stay visible). */
.view-photo[hidden] { display: none; }
#viewPhotoPlaceholder { display: flex; }
/* See #photoPlaceholder above: an #id rule needs its own [hidden] override
   to not beat the UA stylesheet once the img is shown instead. */
#viewPhotoPlaceholder[hidden] { display: none; }
#viewPhotoPlaceholder svg { width: 56px; height: 56px; }

/* Spouse/partner quick links: small avatars riding just under the big
   photo -- tap one to jump straight to that person's own view. */
.view-spouse-avatars {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 10px;
}
.view-spouse-avatars[hidden] { display: none; }
.view-spouse-avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  overflow: hidden;
  border: 2px solid var(--surface);
  box-shadow: var(--shadow);
  background: #eee2c9;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  color: #b79f7c;
  cursor: pointer;
  flex-shrink: 0;
}
.view-spouse-avatar img { width: 100%; height: 100%; object-fit: cover; }

/* Couple mode's photo pair -- two tappable photos side by side in place of
   the single #viewPhoto, the selected one ringed. Everything below the
   photos (name, meta, contact chips, details, Parents/Siblings/etc.)
   already belongs to whichever person is selected here (see
   renderPersonView) -- a couple card is this same single-person info
   block with only its photo swapped for a pair, not a separate layout. */
.view-photo-pair {
  display: flex;
  align-items: center;
  gap: 10px;
}
.view-photo-pair[hidden] { display: none; }
.view-photo-pair-member {
  width: 106px;
  height: 106px;
  box-sizing: border-box;
  border-radius: 50%;
  background: #eee2c9;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  color: #b79f7c;
  border: 3px solid transparent;
  cursor: pointer;
  flex-shrink: 0;
}
/* The colored ring: which of the two is "selected" -- their own name,
   contact info, details, and parents/siblings show below, and further
   swipes follow their tree. */
.view-photo-pair-member.selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--surface);
}
.view-photo-pair-member img { width: 100%; height: 100%; object-fit: cover; }
.view-photo-pair-member svg { width: 36px; height: 36px; }

.view-name {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 700;
  text-align: center;
}
/* Meta row: age, current location, and zodiac sign on one line under the
   name (see renderPersonView) -- the selected person's own, single or
   couple mode alike, now that a couple card no longer shows a separate
   shared-location line. */
.view-meta {
  margin: 0;
  text-align: center;
  color: var(--ink-soft);
  font-size: 0.88rem;
}
.view-meta[hidden] { display: none; }

/* Contact chips: compact, tappable pills for whichever person is selected --
   single or couple mode alike (see renderPersonView). */
.view-contact {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin: 0;
}
.view-contact[hidden] { display: none; }

.contact-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 1px solid var(--card-border);
  background: var(--bg);
  border-radius: 999px;
  padding: 5px 11px 5px 9px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--accent-dark);
  text-decoration: none;
  cursor: pointer;
}
.contact-chip:hover { border-color: var(--accent); }
.contact-chip-icon { width: 13px; height: 13px; flex-shrink: 0; display: flex; }
.contact-chip-icon svg { width: 100%; height: 100%; }

/* Notes, the relations divider, and every relation section below all sit
   in this one flex column, spaced by its own gap -- matching .view-info's
   rhythm above -- rather than each carrying its own margin. A hidden
   child (display:none) drops out of flex flow entirely, so the gap
   automatically closes up around whichever notes/divider/sections aren't
   currently shown. */
.view-relations {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  margin-top: 10px;
}
.view-notes {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.45;
  white-space: pre-wrap;
}

.view-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Needs its own [hidden] override, same reason as .view-photo/.view-photo-pair
   above -- .view-section's own explicit display:flex would otherwise beat
   the UA stylesheet's [hidden]{display:none} regardless of the attribute. */
.view-section[hidden] { display: none; }
.view-section h4 {
  margin: 0;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.view-relation-list { list-style: none; margin: 0; padding: 0; }
/* Siblings/Children only (see buildRelationRow's useFirstName) -- Parents
   and Spouses stay single-column since they're rarely more than a
   person or two, where two columns would just leave one side empty. */
.view-relation-grid { display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 16px; }
.view-relation-list li {
  display: flex;
  align-items: baseline;
  /* Age sits right next to its own name (not pushed to the row's far
     edge) -- in the two-column grid, a right-aligned age in the left
     column read as visually closer to the *next* column's name than to
     its own, a proximity mismatch that space-between doesn't have room
     to avoid once there's a neighboring column immediately to its right. */
  justify-content: flex-start;
  gap: 5px;
  padding: 7px 0;
}
.view-relation-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: 400;
  color: var(--accent-dark);
  cursor: pointer;
  text-align: left;
}
.view-relation-link:hover { text-decoration: underline; }
.view-relation-age {
  flex-shrink: 0;
  color: var(--ink-soft);
  font-size: 0.82rem;
}
.view-location-text { font-weight: 400; }
.view-location-dates {
  display: block;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #201c16;
    --surface: #2b2620;
    --ink: #ece5d6;
    --ink-soft: #b3a892;
    --card-border: #4a4131;
    --shadow: 0 2px 8px rgba(0,0,0,0.4);
    --centric-inner: #6a6a6a;
    --centric-outer: #2c2c2c;
  }
  .tree-viewport { background-image: radial-gradient(circle, #362f22 1px, transparent 1px); }
  .person-photo { background: #3a3324; color: #6f6650; }
  .view-photo { background: #3a3324; color: #6f6650; }
  .view-spouse-avatar { background: #3a3324; color: #6f6650; }
  /* --accent-dark itself stays put -- it's also used as a button-hover
     *background* with white text (see .btn-primary:hover), which already
     has plenty of contrast and would lose it if lightened. This link only
     needs a lighter tint because it sits as *text* on the dark --surface
     behind it, where the unlit --accent-dark reads as roughly 1.8:1 --
     well under WCAG AA's 4.5:1 floor for body text. */
  .view-relation-link { color: #e08a5c; }
  .view-photo-pair-member { background: #3a3324; color: #6f6650; }
  /* Same chevron as the light-mode rule, just re-colored to dark mode's
     own --ink-soft -- a background-image can't follow a CSS variable on
     its own, so it needs its own literal-hex copy here (see
     .location-date-group .location-date-part above). */
  .location-date-group .location-date-part {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b3a892' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  }
}

/* ---------- "Midnight Magenta" theme ---------- */
/* The app's permanent default now (see the unconditional data-theme
   attribute set in app.js) -- always applied regardless of the default
   theme's own automatic light/dark switch above, which is left in place
   but permanently outranked. Values pulled straight from the Figma theme tester
   (https://www.figma.com/design/ugCiusgV5SqfpsBs8iPr9L) -- a flat
   charcoal ground with a magenta accent, rather than the default theme's
   warm beige/rust. Doesn't touch a single existing variable's default or
   dark-mode value; this only ever applies under the [data-theme] attribute,
   so the original two themes are unaffected regardless of OS preference. */
:root[data-theme="midnight-magenta"] {
  --bg: #363636;
  --surface: #363636;
  --ink: #ffffff;
  --ink-soft: #cecece;
  --accent: #dc14d2;
  --accent-dark: #ffffff;
  --line: #ffffff;
  --card-border: #5d5d5d;
  --danger: #ff1818;
  --shadow: 0 2px 8px rgba(0,0,0,0.4);
  --centric-inner: #6a6a6a;
  --centric-outer: #2c2c2c;
  /* Solid buttons fill white/black instead of magenta in this theme --
     --accent itself stays magenta above (still used for borders/rings,
     e.g. Centric view's centered-photo ring). Defined here as the same
     --btn-fill* tokens .btn-primary etc. already read from, so this is
     the only place their color changes -- no separate, higher-specificity
     button rule ever has to override a "wrong" base value. */
  --btn-fill: #ffffff;
  --btn-fill-hover: #ffffff;
  --btn-fill-ink: #000000;
}
:root[data-theme="midnight-magenta"] .toolbar { box-shadow: 0 4px 16px 4px rgba(0, 0, 0, 0.5); }
:root[data-theme="midnight-magenta"] .tree-viewport { background-image: radial-gradient(circle, #4a4a4a 1px, transparent 1px); }
:root[data-theme="midnight-magenta"] .person-photo,
:root[data-theme="midnight-magenta"] .view-photo,
:root[data-theme="midnight-magenta"] .view-spouse-avatar,
:root[data-theme="midnight-magenta"] .view-photo-pair-member { background: #4a4a4a; color: #8a8a8a; }
:root[data-theme="midnight-magenta"] .location-date-group .location-date-part {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23cecece' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
}
/* The couple card's selected-photo ring. */
:root[data-theme="midnight-magenta"] .view-photo-pair-member.selected { border-color: #ffffff; }
:root[data-theme="midnight-magenta"] .btn:hover,
:root[data-theme="midnight-magenta"] .icon-btn:hover,
:root[data-theme="midnight-magenta"] .centric-metric-btn:hover:not(.active),
:root[data-theme="midnight-magenta"] .location-dates-btn:hover,
:root[data-theme="midnight-magenta"] .combo-option:hover { background: #4a4a4a; }
