/* Wijnbladh Heritage — "Shield": deep navy field, olive-green leaf, with the
   heraldic vine green as the secondary voice — the arms' own colours rather
   than the old brass-on-leather. Google Sans Flex throughout — there is no
   second typeface anywhere in the app, chrome or canvas.
   DARK is the default; the light theme is a cool-paper counterpart. */

:root {
  /* ---- WijnUI (repos/WijnCode/wijnui), product "heritage" ----
     The house tokens own every colour; these names are the app's aliases for
     them, so a change to a shared value happens in WijnUI, not here. Light,
     dark and black come from data-wui-theme, set by wijnui.appearance.js. */
  --bg-background: var(--wui-bg);
  --bg-surface: var(--wui-surface);
  --bg-surface-variant: var(--wui-surface-high);
  --bg-input: var(--wui-surface-highest);
  /* One quiet hover wash, used by every interactive row/cell. */
  --bg-hover: color-mix(in srgb, var(--wui-on-bg) 6%, transparent);

  --text-primary: var(--wui-on-bg);
  --text-secondary: var(--wui-on-surface);
  --text-muted: var(--wui-on-faint);

  --border-base: var(--wui-rule);
  --border-strong: color-mix(in srgb, var(--wui-rule) 70%, var(--wui-on-faint));

  /* Brand = UI state (active, selected, focused); the fill is the one primary action. */
  --primary: var(--wui-brand);
  --on-primary: var(--wui-on-brand);
  --primary-btn: var(--wui-brand-fill);
  --on-primary-btn: var(--wui-on-brand-fill);
  --secondary: var(--wui-brand-dim);

  /* Domain signal — never chrome. */
  --danger: var(--wui-crit);
  --success: var(--wui-ok);
  --warning: var(--wui-warn);

  --accent-soft: var(--wui-nav-indicator);
  --accent-soft-line: color-mix(in srgb, var(--wui-brand) 34%, transparent);
  --accent-glow: color-mix(in srgb, var(--wui-brand) 42%, transparent);

  /* The border carries the edge; the shadow only lifts. */
  --shadow-1: 0 1px 2px rgba(0,0,0,.28), 0 6px 18px -8px rgba(0,0,0,.46);
  --shadow-lg: 0 2px 5px rgba(0,0,0,.30), 0 20px 40px -20px rgba(0,0,0,.62);
  /* The machine's own surfaces — the assistant — wear the one violet. */
  --ai-wash: var(--wui-agent);

  /* Data-viz ramp — olive → vine → steel, i.e. the arms, not a stock blue.
     Charts are domain data, so they keep their own ramp; the lead is the brand.
     Read by charts.js / viz.js / journeys.js so every canvas matches the UI. */
  --viz-1: #A8BF7A;
  --viz-2: #6FB58C;
  --viz-3: #7FA8D4;
  --viz-4: #D9C27E;
  --viz-5: #4E8FA8;
  --viz-6: #8FD3AE;
  --viz-7: #C3D49A;
  --viz-8: #35785A;
  --viz-grid: color-mix(in srgb, var(--wui-on-bg) 8%, transparent);
  --viz-stroke: var(--wui-bg);

  /* ---- Type (shared by both themes) ---- */
  --font-sans: var(--wui-font-ui);
  /* Display and body are the same family — weight and optical size do the work. */
  --font-display: var(--font-sans);

  /* ---- Geometry & shell (shared by both themes) ---- */
  /* Radius scales with the surface: small for dense inner parts, medium for
     cards and panels, large only for hero surfaces (a dialog, a sheet). */
  --radius-sm: var(--wui-radius-small);
  --radius-md: var(--wui-radius-medium);
  --radius-lg: var(--wui-radius-medium);
  --radius-xl: var(--wui-radius-medium);
  --radius-2xl: var(--wui-radius-large);
  --pill: var(--wui-radius-full);

  --topbar-h: 64px;
  --bottomnav-h: 62px;
  --safe-top: env(safe-area-inset-top, 0px);
  /* A floor as well as an inset: on a home-button iPhone and on Android the
     inset is legitimately 0, and the bar was then sitting hard on the screen
     edge with nothing under the labels. */
  --safe-bottom: max(env(safe-area-inset-bottom, 0px), 8px);
  /* Bottom chrome the content has to clear — 0 wherever the bottom nav is hidden. */
  --shell-bottom: 0px;

  /* Control metrics — one height for every button, chip and field on a row. */
  --ctl-h: 38px;
  --ctl-h-sm: 32px;

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ring: 0 0 0 2px var(--bg-background), 0 0 0 4px var(--accent-glow);
}

/* Light: the house light theme carries the surfaces; only what the house does
   not define (shadows, the chart ramp on paper) is adjusted here. */
:root[data-wui-theme="light"] {
  --shadow-1: 0 1px 2px rgba(17,24,34,.05), 0 6px 18px -10px rgba(17,24,34,.20);
  --shadow-lg: 0 2px 6px rgba(17,24,34,.07), 0 22px 48px -22px rgba(17,24,34,.28);
  --viz-1: #5E7A33; --viz-2: #2F6B4E; --viz-3: #2E5E86; --viz-4: #A88A3A;
  --viz-5: #3E7F9A; --viz-6: #6FAE8E; --viz-7: #8FA65E; --viz-8: #1F4E39;
}

/* ============================ Typography ============================ */
/* One family, everywhere — including third-party widgets (Leaflet) and the
   SVG text inside every d3 chart, which otherwise fall back to their own. */
*, *::before, *::after { -webkit-font-smoothing: antialiased; }
html, body, button, input, select, textarea, svg text, .leaflet-container {
  font-family: var(--font-sans);
}
body { letter-spacing: -0.006em; font-variant-numeric: tabular-nums; }

/* The app is a shell, not a document: the page itself never scrolls — the one
   scroller is .main-scroll, so the topbar and bottom nav stay put. */
html, body { height: 100dvh; margin: 0; overflow: hidden; }

h1, h2, h3, h4, .font-serif {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  letter-spacing: -0.018em;
  text-wrap: balance;
}

/* Page header: an editorial title with a quiet eyebrow above it. */
.page-title {
  font-family: var(--font-display);
  font-weight: 400; font-size: clamp(27px, 3.6vw, 38px);
  line-height: 1.08; letter-spacing: -0.024em; color: var(--text-primary);
}
.page-sub { margin-top: 8px; max-width: 46rem; color: var(--text-secondary); line-height: 1.55; }
.page-head { margin-bottom: 30px; }
/* Header with controls on the right; stacks below the title on small screens.
   min-width:0 everywhere is what lets the controls shrink on a phone — without
   it a wide tab rail sizes the whole header and the page scrolls sideways. */
.page-head-row { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 18px; }
.page-head-row > * { min-width: 0; max-width: 100%; }
.page-head-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.page-head-actions > * { min-width: 0; max-width: 100%; }
.page-head-actions > .subtabs { flex: 0 1 auto; }

.eyebrow {
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
  font-weight: 600; color: var(--text-muted);
}
/* Card/section heading — the one title style inside a surface. */
.card-title { font-family: var(--font-display); font-size: 17px; font-weight: 600; letter-spacing: -0.012em; color: var(--text-primary); }
.card-title-lg { font-size: 20px; font-weight: 500; }
.pcard-title { font-family: var(--font-display); font-size: 18px; font-weight: 600; letter-spacing: -0.012em; color: var(--text-primary); }
/* Title row inside a card: heading left, count/action right. */
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.card-count { font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

::selection { background: var(--accent-soft); color: var(--text-primary); }

/* One visible focus treatment across the whole app. */
:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-sm); }
.btn:focus-visible, .chip:focus-visible, .fchip:focus-visible,
.icon-btn:focus-visible, .field:focus-visible, .send:focus-visible { border-radius: var(--pill); }

/* The ambient glow is retired: WijnUI grounds are flat. */
.glow { display: none; }

/* ============================ App shell ============================ */
.appshell { display: flex; flex-direction: column; height: 100dvh; min-height: 0; }
/* Beat Tailwind's .hidden regardless of stylesheet order. */
.appshell.hidden { display: none; }

.topbar {
  flex: none;
  display: flex; align-items: center; gap: 20px;
  height: calc(var(--topbar-h) + var(--safe-top));
  padding: var(--safe-top) clamp(14px, 4vw, 40px) 0;
  background: color-mix(in srgb, var(--bg-background) 82%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid var(--border-base);
  z-index: 20;
}
.brand { display: flex; align-items: center; gap: 11px; min-width: 0; text-decoration: none; }
.brand img { height: 32px; width: auto; display: block; }
/* Mixed case, not the old cramped all-caps: uppercase at 700 with tight tracking
   set solid and read as a shout. The family name carries the weight; "Heritage"
   sits beside it in the muted tone, so the two read as one lockup. */
.brand-name {
  display: flex; align-items: baseline; gap: 7px;
  white-space: nowrap; min-width: 0;
}
.brand-name b {
  font-family: var(--font-display);
  font-size: 1.125rem; font-weight: 600; letter-spacing: -0.021em;
  color: var(--text-primary);
}
.brand-name > span {
  font-size: 0.8125rem; font-weight: 500; letter-spacing: 0.055em;
  text-transform: uppercase; color: var(--text-muted);
}
/* Below the medium breakpoint the mark alone stands for the app. */
@media (max-width: 700px) { .brand-name > span { display: none; } }
.topnav { display: none; align-items: center; gap: 2px; min-width: 0; }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }
@media (min-width: 900px) { .topnav { display: flex; } }
@media (max-width: 430px) { .brand-name { display: none; } }

.main-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto;
  /* A stray wide child gets clipped rather than turning the app into a
     horizontal scroller; deliberate scrollers set their own overflow-x. */
  overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
  scroll-behavior: smooth;
}
.main-scroll::-webkit-scrollbar { width: 12px; }
.main-scroll::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: 6px;
  border: 3px solid transparent; background-clip: padding-box;
}
/* The bottom nav is a flex sibling, not an overlay, so content needs no
   allowance for it here. */
.shell-view {
  max-width: 72rem; margin: 0 auto;
  padding: 34px clamp(14px, 4vw, 40px) 48px;
}
/* Immersive routes (tree, map, chat) drop the reading column and fill the
   scroller. Full-bleed children break out with --gutter, never with vw —
   inside a scroller, 100vw is wider than the content box by the scrollbar. */
.shell-view--wide {
  --gutter: clamp(14px, 4vw, 40px);
  max-width: none;
  padding: 18px var(--gutter) 0;
}
/* Height a wide route can fill without making the shell scroll. */
.shell-view--wide { --stage-h: calc(100dvh - var(--topbar-h) - var(--safe-top) - var(--shell-bottom)); }

/* Vertical rhythm between the blocks of a page. */
.stack { display: flex; flex-direction: column; gap: 24px; }

/* --- Top nav links --- */
.nav-link {
  padding: 0.45rem 0.85rem; border-radius: var(--pill);
  font-size: 0.875rem; font-weight: 500; color: var(--text-secondary);
  white-space: nowrap; transition: color 0.18s var(--ease), background 0.18s var(--ease);
}
.nav-link:hover { color: var(--text-primary); background: var(--bg-hover); }
.nav-link.active { color: var(--primary); background: var(--accent-soft); }

/* --- Bottom nav (small screens) --- */
.bottomnav { display: none; }
@media (max-width: 899px) {
  :root { --shell-bottom: calc(var(--bottomnav-h) + var(--safe-bottom)); }
  .bottomnav {
    flex: none;
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
    height: calc(var(--bottomnav-h) + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    background: color-mix(in srgb, var(--bg-surface) 88%, transparent);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--border-base);
    z-index: 20;
  }
  .shell-view { padding-top: 24px; }
}
.bottomnav .navitem {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-width: 0; padding: 6px 2px;
  color: var(--text-muted); font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.01em; text-decoration: none; cursor: pointer;
  transition: color 0.16s var(--ease);
}
.bottomnav .navitem svg { width: 21px; height: 21px; }
.bottomnav .navitem span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bottomnav .navitem.active { color: var(--primary); }
.bottomnav .navitem:active { color: var(--text-primary); }
/* An admin sees seven slots; at phone widths the longest Swedish labels
   ("Släktträd", "Personer") need the tighter setting to stay whole. Tablets,
   which have room to spare, keep the roomier default. */
@media (max-width: 440px) {
  .bottomnav .navitem { padding: 6px 1px; font-size: 9.5px; letter-spacing: 0; gap: 3px; }
  .bottomnav .navitem svg { width: 19px; height: 19px; }
}

/* --- Topbar controls --- */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 36px; width: 36px; flex: none;
  border: 1px solid var(--border-base); border-radius: var(--pill);
  background: transparent; color: var(--text-secondary); cursor: pointer;
  transition: color 0.16s var(--ease), background 0.16s var(--ease), border-color 0.16s var(--ease);
}
.icon-btn:hover { color: var(--text-primary); background: var(--bg-hover); border-color: var(--border-strong); }
.btn-ghost {
  height: 36px; padding: 0 15px;
  border: 1px solid var(--border-base); border-radius: var(--pill);
  background: transparent; color: var(--text-secondary);
  font-size: 0.8125rem; font-weight: 600; white-space: nowrap; cursor: pointer;
  transition: color 0.16s var(--ease), background 0.16s var(--ease), border-color 0.16s var(--ease);
}
.btn-ghost:hover { color: var(--text-primary); background: var(--bg-hover); border-color: var(--border-strong); }
.langtoggle {
  display: flex; align-items: center; flex: none;
  border: 1px solid var(--border-base); border-radius: var(--pill);
  padding: 2px; font-size: 0.6875rem; font-weight: 600;
}
.userchip { display: none; align-items: center; gap: 8px; min-width: 0; }
.userchip img { height: 30px; width: 30px; border-radius: var(--pill); border: 1px solid var(--border-base); object-fit: cover; }
.userchip span { font-size: 0.8125rem; color: var(--text-secondary); white-space: nowrap; }
.userchip.hidden { display: none; }
@media (min-width: 640px) { .userchip:not(.hidden) { display: flex; } }
/* app.js adds Tailwind's .flex when signing in; the id beats it on narrow screens. */
@media (max-width: 639px) { #userChip { display: none; } }
/* "Add" is a top-nav link on desktop, so the topbar + is a small-screen affordance. */
@media (min-width: 900px) { .icon-btn#addBtn { display: none; } }

/* ============================ Sub-tabs ============================ */
/* A segmented pill rail: scrolls sideways instead of wrapping to three ragged
   rows on a phone, and reads as one control rather than a row of links. */
/* Hugs its tabs rather than stretching the full column — a rail of five tabs
   spanning 1200px reads as an empty bar. */
.subtabs {
  display: inline-flex; align-items: center; gap: 4px;
  width: fit-content; max-width: 100%;
  margin-bottom: 28px; padding: 4px;
  border: 1px solid var(--border-base); border-radius: var(--pill);
  background: var(--bg-surface);
  overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.subtabs::-webkit-scrollbar { display: none; }
/* The rail scrolls, but with the scrollbar hidden a tab clipped mid-word just
   reads as a broken layout. Fade whichever edge still has tabs behind it, so
   the cut looks deliberate and the rail looks scrollable. `data-edge` is set in
   js/app.js; with no JS the rail simply keeps its old hard edge. */
.subtabs[data-edge="end"], .treeclimb[data-edge="end"] {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
}
.subtabs[data-edge="start"], .treeclimb[data-edge="start"] {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 32px), transparent);
          mask-image: linear-gradient(to left, #000 calc(100% - 32px), transparent);
}
.subtabs[data-edge="both"], .treeclimb[data-edge="both"] {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 32px, #000 calc(100% - 32px), transparent);
          mask-image: linear-gradient(to right, transparent, #000 32px, #000 calc(100% - 32px), transparent);
}
/* Rendered as <a> for routes and <button> for in-page switches — the reset
   keeps the two visually identical. */
.subtab {
  flex: none;
  padding: 7px 15px; border-radius: var(--pill);
  border: 0; background: transparent;
  font-family: inherit; font-size: 13.5px; font-weight: 500; color: var(--text-secondary);
  white-space: nowrap; text-decoration: none; cursor: pointer;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.subtab:hover { color: var(--text-primary); background: var(--bg-hover); }
.subtab.active { color: var(--on-primary); background: var(--primary); font-weight: 600; }

/* ============================ Surfaces ============================ */
.card {
  background: var(--bg-surface); border: 1px solid var(--border-base);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-1);
  transition: transform 0.2s var(--ease), border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
a.card:hover, .card.hoverable:hover { transform: translateY(-2px); border-color: var(--accent-soft-line); box-shadow: var(--shadow-lg); }
/* Chart.js writes a pixel width onto its canvas; a grid item's default
   min-width:auto then lets that stale width prop the column open when the
   viewport narrows. Zero the floor and cap the canvas so a card never
   outgrows its column. */
.grid > .card { min-width: 0; }
.card canvas { max-width: 100%; }
/* ...and the same floor on the TRACK, which the item rule cannot reach. A grid
   with no column utility at this breakpoint (`grid ... lg:grid-cols-3` on a
   phone) has one implicit `auto` column, and `auto` refuses to shrink below its
   widest item's min-content — so a single nowrap chip sized the column at 379px
   inside a 359px page and gave every phone a 4px horizontal scroll.
   `:where()` keeps this at zero specificity, so any explicit `grid-cols-*`
   utility still wins at the breakpoint that sets it. */
:where(.grid) { grid-template-columns: minmax(0, 1fr); }
/* A quieter surface for nested blocks — no shadow competing with its parent. */
.panel { background: var(--bg-surface-variant); border: 1px solid var(--border-base); border-radius: var(--radius-md); }

/* Stat tile — one number, one label. */
.stat { padding: 16px 18px; }
.stat-v { font-family: var(--font-display); font-size: 26px; font-weight: 500; line-height: 1.1; letter-spacing: -0.02em; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.stat-k { margin-top: 4px; font-size: 12px; color: var(--text-secondary); }
/* Two tiles per row on a phone — a value like "1650–1836" must not wrap. */
@media (max-width: 480px) { .stat { padding: 14px 15px; } .stat-v { font-size: 21px; } }

/* Loading skeleton — a calm shimmer instead of the word "Loading…". */
.skel { background: var(--bg-surface-variant); border-radius: var(--radius-sm); position: relative; overflow: hidden; }
.skel::after {
  content: ""; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--bg-hover), transparent);
  animation: skel 1.5s infinite;
}
@keyframes skel { to { transform: translateX(100%); } }
.skel-line { height: 12px; }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; } }

/* Empty state */
.empty { max-width: 34rem; margin: 8px auto; padding: 44px 32px; text-align: center; }
.empty-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px; border-radius: var(--pill);
  background: var(--accent-soft); color: var(--primary); font-size: 26px;
  border: 1px solid var(--accent-soft-line);
}
.empty h2 { margin-top: 18px; font-size: 21px; font-weight: 600; color: var(--text-primary); }
.empty p { margin-top: 8px; color: var(--text-secondary); line-height: 1.55; }

/* A chart with nothing to plot — fills the canvas slot so the card keeps its
   shape in the grid, and names the fact that is missing. */
.chart-empty {
  height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 12px;
  padding: 20px 28px; text-align: center;
}
.chart-empty svg { width: 26px; height: 26px; color: var(--text-muted); opacity: 0.5; }
.chart-empty p { max-width: 26rem; color: var(--text-muted); font-size: 13.5px; line-height: 1.5; }

/* ============================ Buttons ============================ */
/* One button system. `.pbtn` is kept as an alias — the person view shipped
   with that name and it reads fine in place. */
.btn, .pbtn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: var(--ctl-h); padding: 0 17px;
  border: 1px solid transparent; border-radius: var(--pill);
  font-size: 13.5px; font-weight: 600; line-height: 1; white-space: nowrap;
  text-decoration: none; cursor: pointer;
  transition: opacity 0.15s var(--ease), background 0.15s var(--ease),
              border-color 0.15s var(--ease), color 0.15s var(--ease), transform 0.1s var(--ease);
}
.btn:active, .pbtn:active { transform: translateY(0.5px); }
.btn .ic, .btn svg, .pbtn .ic, .pbtn svg { width: 16px; height: 16px; }
.btn-sm, .pbtn-sm { height: var(--ctl-h-sm); padding: 0 13px; font-size: 12.5px; }
.btn-sm .ic, .btn-sm svg, .pbtn-sm .ic, .pbtn-sm svg { width: 14px; height: 14px; }

.btn-primary, .pbtn-primary { background: var(--primary-btn); color: var(--on-primary-btn); }
.btn-primary:hover, .pbtn-primary:hover { opacity: 0.9; }
.btn-ghost, .pbtn-ghost { border-color: var(--border-base); color: var(--text-secondary); background: transparent; }
.btn-ghost:hover, .pbtn-ghost:hover { border-color: var(--border-strong); color: var(--text-primary); background: var(--bg-hover); }
/* Destructive. Carries the danger colour so it never reads as a peer of the
   action beside it — revoking access sat next to "Make admin" in the same
   neutral pill, and the two were one misclick apart. */
.btn-danger { background: var(--wui-crit-bg); border-color: var(--wui-crit-border); color: var(--danger); }
.btn-danger:hover { background: var(--danger); border-color: var(--danger); color: var(--wui-bg); }
.btn-danger:hover { opacity: 0.9; }
.a-tool-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.a-tool-danger:hover { color: var(--danger); border-color: var(--danger); background: color-mix(in srgb, var(--danger) 12%, transparent); }
/* Borderless — for tertiary actions that shouldn't add another outline. */
.btn-quiet { color: var(--text-muted); background: transparent; }

/* ---- Confirm dialog: the one gate in front of anything irreversible ---- */
.confirm::backdrop { background: color-mix(in srgb, var(--bg-background) 72%, transparent); backdrop-filter: blur(3px); }
.confirm {
  border: 1px solid var(--border-base); border-radius: var(--radius-lg);
  background: var(--bg-surface); color: var(--text-primary);
  padding: 26px; width: min(30rem, calc(100vw - 32px)); max-height: calc(100dvh - 48px);
  overflow-y: auto; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
.confirm h2 { font-size: 18px; font-weight: 600; line-height: 1.3; }
.confirm > p { margin-top: 9px; color: var(--text-secondary); font-size: 14.5px; line-height: 1.55; }
/* What the action will actually touch — a list the user can check before saying yes. */
.confirm-detail {
  margin-top: 14px; padding: 12px 14px; max-height: 12rem; overflow-y: auto;
  border: 1px solid var(--border-base); border-radius: var(--radius-sm);
  background: var(--bg-surface-variant); font-size: 13px; line-height: 1.7; color: var(--text-secondary);
}
.confirm-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 9px; margin-top: 22px; }
/* A long confirm label wraps to its own ragged row on a phone; stack both
   full-width instead, safe option first. */
@media (max-width: 480px) {
  .confirm-actions { flex-direction: column; }
  .confirm-actions .btn { width: 100%; }
}
.btn-quiet:hover { color: var(--text-primary); background: var(--bg-hover); }
.btn:disabled, .pbtn:disabled { opacity: 0.55; cursor: default; transform: none; }

/* Small brass accent link, used where a card offers one onward move. */
.btn-link {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 13px; font-weight: 600; color: var(--primary); text-decoration: none;
  transition: opacity 0.15s var(--ease);
}
.btn-link:hover { opacity: 0.78; }
.btn-link svg, .btn-link .ic { width: 14px; height: 14px; }

/* --- Suggestion chips --- */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--border-base); background: var(--bg-surface-variant);
  color: var(--text-secondary); font-size: 13px; padding: 7px 13px; border-radius: var(--pill);
  cursor: pointer; text-decoration: none;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.chip:hover { color: var(--primary); border-color: var(--accent-soft-line); background: var(--accent-soft); }
.chip-yr { color: var(--text-muted); font-size: 12px; }

/* ============================ Inputs ============================ */
input, textarea, select { color: var(--text-primary); }
input::placeholder, textarea::placeholder { color: var(--text-muted); }

.field {
  height: var(--ctl-h); padding: 0 16px;
  background: var(--bg-surface); border: 1px solid var(--border-base);
  border-radius: var(--pill); color: var(--text-primary);
  font-size: 13.5px; outline: none;
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.field:focus { border-color: var(--accent-soft-line); box-shadow: 0 0 0 3px var(--accent-soft); }
/* Multi-line and boxed variants keep the same focus language. */
.field-box { border-radius: var(--radius-md); }
.field-area { height: auto; padding: 11px 14px; border-radius: var(--radius-md); line-height: 1.55; resize: vertical; }
.field-label { display: block; margin-bottom: 6px; font-size: 12.5px; font-weight: 600; color: var(--text-secondary); }

/* Search-suggestion menu — one popover style for every lookup in the app. */
.menu {
  position: absolute; z-index: 40; margin-top: 6px; min-width: 260px;
  border: 1px solid var(--border-base); border-radius: var(--radius-md);
  background: var(--bg-surface); box-shadow: var(--shadow-lg);
  padding: 5px; max-height: 320px; overflow-y: auto;
}
.menu-item {
  display: flex; width: 100%; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  font-size: 13.5px; color: var(--text-primary); text-align: left;
  text-decoration: none; cursor: pointer; background: none; border: 0;
  transition: background 0.12s var(--ease);
}
.menu-item:hover { background: var(--bg-hover); }
.menu-item-yr { color: var(--text-muted); font-size: 12px; margin-left: auto; padding-left: 8px; }
.menu-empty { padding: 9px 10px; font-size: 13px; color: var(--text-muted); }

/* --- Ask bar — the AI-first hero. Gradient hairline + soft lift. --- */
.ask {
  width: 100%; background: var(--bg-surface); border: 1px solid var(--border-base);
  border-radius: var(--radius-md); padding: 14px 16px;
  box-shadow: var(--shadow-lg); transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.ask:focus-within { border-color: transparent;
  box-shadow: 0 0 0 1.5px transparent, var(--shadow-lg);
  border: 1.5px solid var(--ai-wash); }
.ask input, .ask textarea { flex: 1; min-width: 0; background: none; border: none; outline: none; color: var(--text-primary); font: inherit; font-size: 16px; resize: none; }
.ask input::placeholder, .ask textarea::placeholder { color: var(--text-muted); }
.send { width: 36px; height: 36px; border-radius: var(--pill); background: var(--primary-btn); color: var(--on-primary-btn);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  transition: opacity 0.15s var(--ease), transform 0.15s var(--ease); border: none; cursor: pointer; }
.send:hover { opacity: 0.92; transform: translateY(-1px); }
.send:disabled { opacity: 0.4; cursor: default; transform: none; }
.send svg { width: 19px; height: 19px; }
/* Same control, stopping the answer instead of sending one. */
.send.is-stop { background: transparent; color: var(--text-primary); box-shadow: inset 0 0 0 1px var(--border-strong); }
/* A growing composer keeps the icon and send button on the last line. */
.ask > .flex { align-items: flex-end; }
.ask textarea { max-height: 33vh; overflow-y: auto; line-height: 1.5; padding: 4px 0; scrollbar-width: thin; }
.ask > .flex > .ic-lg { padding-bottom: 6px; }

/* ============================ Ask thread ============================ */
.turn { border-top: 1px solid var(--border-base); padding: 28px 0; }
.turn:first-child { border-top: none; padding-top: 6px; }
.q { font-family: var(--font-display); font-size: 25px; font-weight: 400; line-height: 1.18; color: var(--text-primary); letter-spacing: -0.022em; text-wrap: pretty; }
.a { color: var(--text-secondary); font-size: 15.5px; line-height: 1.72; }
.a p { margin: 0.5rem 0; }
.a p:first-child { margin-top: 0; }
.a strong { color: var(--text-primary); font-weight: 600; }
.a ul { margin: 0.5rem 0; padding-left: 1.2rem; list-style: disc; }
.a li { margin: 0.2rem 0; }
.a a { color: var(--primary); text-underline-offset: 2px; }

/* Person card */
.pcard { display: flex; align-items: center; gap: 12px; padding: 11px 13px; border: 1px solid var(--border-base); border-radius: var(--radius-md); background: var(--bg-surface); box-shadow: var(--shadow-1); transition: border-color 0.15s var(--ease), transform 0.15s var(--ease); cursor: pointer; text-decoration: none; }
.pcard:hover { border-color: var(--accent-soft-line); transform: translateY(-1px); }
.mono, .pcard .mono {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; border-radius: var(--pill); flex-shrink: 0;
  background: var(--accent-soft); color: var(--primary);
  font-family: var(--font-display); font-size: 18px; font-weight: 500;
  border: 1px solid var(--accent-soft-line);
}

/* ============================ d3 tree ============================ */
.node-card { cursor: pointer; }
.node-card rect { fill: var(--bg-surface); stroke: var(--border-strong); stroke-width: 1.5; transition: stroke 0.15s var(--ease); }
.node-card:hover rect { stroke: var(--primary); }
.node-card.focus rect { stroke: var(--primary); stroke-width: 2.5; fill: color-mix(in srgb, var(--primary) 12%, var(--bg-surface)); }
.node-card text { font-family: var(--font-display); }
.node-name { font-weight: 600; font-size: 13px; fill: var(--text-primary); }
.node-dates { font-size: 11px; fill: var(--text-muted); }
.node-male rect { fill: color-mix(in srgb, var(--primary) 9%, var(--bg-surface)); }
.node-female rect { fill: color-mix(in srgb, var(--secondary) 11%, var(--bg-surface)); }
.link { fill: none; stroke: var(--border-strong); stroke-width: 1.5; }
.node-avph { fill: var(--accent-soft); stroke: var(--accent-soft-line); stroke-width: 1; }
/* Per-node details button */
.node-info { cursor: pointer; }
.node-info-bg { fill: var(--accent-soft); stroke: var(--border-strong); stroke-width: 1; transition: fill .15s var(--ease), stroke .15s var(--ease); }
.node-info-glyph {
  font-family: var(--font-display);
  font-size: 12px; font-weight: 700; font-style: italic;
  fill: var(--text-secondary); pointer-events: none; transition: fill .15s var(--ease);
}
.node-info:hover .node-info-bg, .node-info:focus-visible .node-info-bg { fill: var(--primary); stroke: var(--primary); }
.node-info:hover .node-info-glyph, .node-info:focus-visible .node-info-glyph { fill: var(--on-primary); }
.node-avtext { font-size: 13px; font-weight: 600; fill: var(--primary); font-family: var(--font-display); }
.node-avring { fill: none; stroke: var(--border-base); stroke-width: 1.5; }

/* The fan is square; bound its height so the whole chart is on screen at once. */
.fanchart { display: block; width: 100%; height: min(68vh, 600px); }

/* Shared d3 canvas text (fan chart, lifelines, network) */
.viz-axis { font-size: 11px; fill: var(--text-muted); }
.viz-label { font-size: 10px; fill: var(--text-muted); }

/* Chat scrollbar / dots */
.chat-scroll::-webkit-scrollbar { width: 8px; }
.chat-scroll::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
.dots { display: inline-flex; gap: 4px; align-items: center; }
.dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--text-muted); display: block; animation: blink 1.4s infinite both; }
.dots i:nth-child(2) { animation-delay: 0.2s; }
.dots i:nth-child(3) { animation-delay: 0.4s; }
.dot { animation: blink 1.4s infinite both; }
.dot:nth-child(2) { animation-delay: 0.2s; }
.dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes blink { 0%,80%,100% { opacity: 0.2; } 40% { opacity: 1; } }

.fade-in { animation: fade 0.28s var(--ease); }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .fade-in { animation: none; }
  html { scroll-behavior: auto; }
  .main-scroll { scroll-behavior: auto; }
}

/* Leaflet, dressed in WijnUI. OSM only ships light tiles, so in the dark theme
   they're inverted and warmed — a white slab in a dark shell is the single most
   jarring thing on the page. Markers sit in their own pane and are unaffected. */
:root:not([data-wui-theme="light"]) .leaflet-tile-pane {
  filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.88) saturate(0.75) sepia(0.16);
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--bg-surface); color: var(--text-primary); }
.leaflet-popup-content-wrapper { border-radius: var(--radius-md); box-shadow: var(--shadow-lg); }
.leaflet-container a.leaflet-popup-close-button { color: var(--text-muted); }
.leaflet-bar a, .leaflet-bar a:hover {
  background: var(--bg-surface); color: var(--text-primary); border-color: var(--border-base);
}
.leaflet-bar a:hover { background: var(--bg-surface-variant); }
.leaflet-control-attribution { background: color-mix(in srgb, var(--bg-surface) 78%, transparent) !important; color: var(--text-muted) !important; }
.leaflet-control-attribution a { color: var(--text-secondary) !important; }

/* Inline line-icons */
.ic { display: inline-flex; align-items: center; }
.ic-lg { font-size: 20px; }

/* Any icon can spin — used by every button that's waiting on the model. */
.spin { animation: astep-spin 0.9s linear infinite; }
@media (prefers-reduced-motion: reduce) { .spin { animation: none; } }

/* ---- Ask: agentic "what I'm doing" status ---- */
.agentic { display: flex; flex-direction: column; gap: 2px; padding: 4px 0; }
.astep {
  display: flex; align-items: center; gap: 9px;
  font-size: 14px; color: var(--text-muted);
  padding: 3px 0; transition: color .2s var(--ease);
}
.astep.is-active { color: var(--text-primary); }
.astep .astep-done { color: var(--primary); }
.astep .astep-spin { color: var(--primary); animation: astep-spin 0.9s linear infinite; }
@keyframes astep-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .astep .astep-spin { animation: none; } }

/* ---- Ask: floating visual artifacts (photos, charts, tables) ---- */
.art-float > * { animation: art-rise .45s cubic-bezier(.16,.84,.44,1) both; }
.art-float > *:nth-child(2) { animation-delay: .06s; }
.art-float > *:nth-child(3) { animation-delay: .12s; }
.art-float > *:nth-child(4) { animation-delay: .18s; }
.art-float > *:nth-child(n+5) { animation-delay: .24s; }
@keyframes art-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .art-float > * { animation: none; } }

.art-chart canvas { width: 100% !important; }
.art-table-scroll { overflow-x: auto; }
.art-table table { width: 100%; border-collapse: collapse; font-size: 14px; }
.art-table th {
  text-align: left; font-weight: 600; color: var(--text-muted);
  font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  padding: 0 12px 8px; white-space: nowrap;
}
.art-table td { padding: 8px 12px; border-top: 1px solid var(--border-base); color: var(--text-secondary); white-space: nowrap; }
.art-table td:first-child { white-space: normal; }
.art-table tbody tr:hover { background: var(--bg-hover); }
.art-ava {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: var(--pill); flex: none;
  background: var(--accent-soft); color: var(--primary); font-size: 13px; font-weight: 600;
}

/* ---- Ask: generated image + on-file photo gallery ---- */
.art-image { margin: 0; }
.art-image-wrap { position: relative; border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--border-base); background: var(--bg-surface-variant); }
.art-image-wrap img { display: block; width: 100%; max-height: 520px; object-fit: cover; }
.ai-stamp {
  position: absolute; top: 10px; right: 10px; display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px; border-radius: var(--pill); font-size: 11px; font-weight: 600; letter-spacing: .02em;
  color: #fff; background: rgba(20, 18, 14, 0.62); backdrop-filter: blur(6px);
}
.art-image figcaption { margin-top: 8px; font-size: 13px; color: var(--text-secondary); }
.art-gallery { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.art-gallery-item { flex: 0 0 auto; }
.art-gallery-item img {
  height: 128px; width: auto; border-radius: var(--radius-md); border: 1px solid var(--border-base);
  object-fit: cover; transition: transform .15s var(--ease);
}
.art-gallery-item:hover img { transform: scale(1.03); }

/* ---- Ask: the visuals the assistant draws (show_visual) ---- */
.art-visual-body > * { min-width: 0; }
.art-tree { height: 280px; overflow: hidden; border-radius: var(--radius-md); background: var(--bg-surface-variant); }
.art-map { height: 280px; border-radius: var(--radius-md); overflow: hidden; }
.art-map-foot, .art-path-foot { margin-top: 8px; font-size: 12px; color: var(--text-muted); }
.art-path-label { font-size: 14.5px; color: var(--text-secondary); margin-bottom: 12px; }
.art-path-label b, .art-path-foot b { color: var(--text-primary); font-weight: 600; }

/* The path from one person to another, read left to right. */
.art-path { display: flex; align-items: center; gap: 6px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: thin; }
.pathchip {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  padding: 6px 12px 6px 6px; border-radius: var(--pill);
  border: 1px solid var(--border-base); background: var(--bg-surface-variant);
  color: var(--text-secondary); font-size: 13px; cursor: pointer; white-space: nowrap;
  transition: border-color .15s var(--ease), color .15s var(--ease);
}
.pathchip:hover { border-color: var(--accent-soft-line); color: var(--text-primary); }
.pathchip.is-end { border-color: var(--accent-soft-line); background: var(--accent-soft); color: var(--text-primary); font-weight: 600; }
.pathchip.is-anc { border-style: dashed; }
.pathchip-ava {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: var(--pill); flex: none;
  background: var(--bg-surface); color: var(--primary); font-size: 11px; font-weight: 700;
}
.pathchip-yr { color: var(--text-muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.pathsep { flex: none; color: var(--text-muted); font-size: 13px; }

/* One life on a rail: birth, each child, death. */
.art-tl-head {
  display: block; width: 100%; text-align: left; background: none; border: 0; cursor: pointer;
  color: var(--text-secondary); font-size: 14px; padding: 0 0 6px;
}
.art-tl-head b { color: var(--text-primary); font-weight: 600; }
.art-tl-head span { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.tl-track { position: relative; height: 190px; margin: 0 10px; }
.tl-line {
  position: absolute; left: 0; right: 0; top: 50%; height: 2px; border-radius: 2px;
  background: var(--accent-soft-line);
}
.tl-ev { position: absolute; top: 50%; transform: translateX(-50%); }
.tl-dot {
  position: absolute; left: 50%; top: -5px; transform: translateX(-50%);
  width: 10px; height: 10px; border-radius: var(--pill);
  background: var(--primary); border: 2px solid var(--bg-surface);
}
.tl-child .tl-dot { background: var(--secondary); }
.tl-death .tl-dot { background: var(--text-muted); }
.tl-cap {
  position: absolute; left: 50%; transform: translateX(-50%);
  width: 120px; text-align: center; font-size: 11.5px; line-height: 1.35; color: var(--text-secondary);
}
.tl-cap b { display: block; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.tl-note { display: block; color: var(--text-muted); font-size: 10.5px; }
/* --lane is set by layoutLanes(): captions that would collide stack outwards. */
.tl-ev.is-up .tl-cap { bottom: calc(16px + var(--lane, 0) * 46px); }
.tl-ev.is-dn .tl-cap { top: calc(16px + var(--lane, 0) * 46px); }
.tl-ev.is-first .tl-cap { left: 0; transform: none; text-align: left; }
.tl-ev.is-last .tl-cap { left: auto; right: 0; transform: none; text-align: right; }

/* Two to four people on the same set of facts. Below ~430px the columns would
   squeeze into unreadable slivers, so the block scrolls sideways instead. */
.art-cmp-scroll { overflow-x: auto; scrollbar-width: thin; }
.art-cmp { display: grid; gap: 0; min-width: max(100%, calc(92px + var(--cmp-cols) * 130px)); }
.cmp-row {
  display: grid; grid-template-columns: 92px repeat(var(--cmp-cols), minmax(0, 1fr));
  gap: 10px; align-items: center; padding: 9px 2px; border-top: 1px solid var(--border-base);
}
.cmp-row.cmp-head { border-top: 0; padding-top: 0; align-items: end; }
.cmp-k { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted); }
.cmp-v { font-size: 13.5px; color: var(--text-secondary); min-width: 0; }
.cmp-person {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0;
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
}
.cmp-ava {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: var(--pill); margin-bottom: 4px;
  background: var(--accent-soft); color: var(--primary); font-size: 12px; font-weight: 700;
}
.cmp-name { font-size: 13.5px; font-weight: 600; color: var(--text-primary); }
.cmp-person:hover .cmp-name { color: var(--primary); }
.cmp-yr { font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* ---- Ask: per-answer actions ---- */
/* A turn that failed. The question above it is left untouched, so the way on
   is to ask it again rather than retype it. */
.a-error {
  display: flex; align-items: flex-start; gap: 12px; margin-top: 4px;
  padding: 14px 16px; border-radius: var(--radius-lg);
  border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
  background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.a-error > .ic { color: var(--danger); font-size: 18px; flex: none; margin-top: 1px; }
.a-error > div { flex: 1; min-width: 0; }
.a-error p { color: var(--text-primary); font-size: 14.5px; line-height: 1.5; }
.a-error .a-error-why { margin-top: 4px; color: var(--text-muted); font-size: 12.5px; word-break: break-word; }
.a-error .btn { flex: none; }
@media (max-width: 520px) { .a-error { flex-wrap: wrap; } .a-error .btn { width: 100%; } }

/* Follow-up questions under the newest answer. */
.askws-followups { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 22px; }
.askws-followups-k {
  font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-muted); margin-right: 2px;
}
.askws-followups .fchip { height: var(--ctl-h-sm); font-size: 12.5px; padding: 0 13px; white-space: normal; text-align: left; }

.a-tools { display: flex; gap: 8px; margin-top: 14px; }
.a-tool {
  display: inline-flex; align-items: center; gap: 7px;
  height: var(--ctl-h-sm); padding: 0 13px; border-radius: var(--pill);
  border: 1px solid var(--border-base); background: transparent;
  color: var(--text-muted); font-size: 12.5px; font-weight: 500; cursor: pointer;
  transition: border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease);
}
.a-tool:hover { border-color: var(--accent-soft-line); color: var(--primary); background: var(--accent-soft); }
.a-tool svg, .a-tool .ic { width: 14px; height: 14px; }

/* ---- Migration map: full-bleed + filter toolbar ---- */
.mapfull { margin-left: calc(-1 * var(--gutter, 0px)); margin-right: calc(-1 * var(--gutter, 0px)); width: auto; padding: 0 clamp(12px, 4vw, 44px); }
.mapbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 4px 2px 14px; }
.mapbar-group {
  display: flex; align-items: center; gap: 2px; flex: none;
  padding: 3px; border: 1px solid var(--border-base); border-radius: var(--pill); background: var(--bg-surface);
}
/* Chips inside a group sit flush — the group's own border is the outline. */
.mapbar-group .fchip { border-color: transparent; background: transparent; height: 30px; padding: 0 12px; }
.mapbar-group .fchip:hover { background: var(--bg-hover); }
.fchip {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: var(--ctl-h); padding: 0 15px; border-radius: var(--pill);
  border: 1px solid var(--border-base); background: var(--bg-surface);
  color: var(--text-secondary); font-size: 13.5px; font-weight: 500; white-space: nowrap;
  cursor: pointer; text-decoration: none;
  transition: color .15s var(--ease), background .15s var(--ease), border-color .15s var(--ease);
}
.fchip svg, .fchip .ic { width: 15px; height: 15px; }
.fchip:hover { border-color: var(--border-strong); color: var(--text-primary); background: var(--bg-hover); }
.fchip.is-on { background: var(--accent-soft); border-color: var(--accent-soft-line); color: var(--primary); font-weight: 600; }
.mapbar-group .fchip.is-on { border-color: transparent; }
.mapbar-field { display: inline-flex; align-items: center; gap: 8px; height: var(--ctl-h); padding: 0 6px 0 14px; border: 1px solid var(--border-base); border-radius: var(--pill); background: var(--bg-surface); font-size: 12.5px; color: var(--text-muted); white-space: nowrap; }
.mapbar-field input {
  border: 0; background: transparent; color: var(--text-primary);
  padding: 6px 6px; font-size: 13.5px; outline: none; min-width: 0;
}
.mapbar-field input[type="number"] { width: 62px; }
.mapbar-field b { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.mapbar-count { margin-left: auto; font-size: 13px; color: var(--text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; }
#mapCanvas { border: 1px solid var(--border-base); border-radius: var(--radius-lg); overflow: hidden; }
/* Says what a dot and a line actually mean — without it the map is decoration. */
.maplegend { display: flex; flex-wrap: wrap; gap: 8px 22px; padding: 0 2px 12px; font-size: 12px; color: var(--text-muted); }
.maplegend-item { display: inline-flex; align-items: center; gap: 8px; }
.maplegend-dot { width: 11px; height: 11px; border-radius: 50%; flex: none; background: color-mix(in srgb, var(--viz-2) 50%, transparent); border: 1.5px solid var(--viz-2); }
.maplegend-line { width: 22px; height: 0; flex: none; border-top: 1.5px solid var(--viz-1); opacity: .7; }
.maplegend .mapbar-count { margin-left: auto; }

/* A named anchor on the heat wash — a dot plus its place name, so the warm
   patches can be read without clicking. */
.map-pin { pointer-events: none; }
.map-pin-dot {
  position: absolute; left: -4px; top: -4px; width: 8px; height: 8px; border-radius: 50%;
  background: var(--bg-background); border: 2px solid var(--primary);
}
.map-pin-label {
  position: absolute; left: 9px; top: -9px; white-space: nowrap;
  padding: 2px 7px; border-radius: var(--pill);
  background: color-mix(in srgb, var(--bg-background) 80%, transparent);
  border: 1px solid var(--border-base); backdrop-filter: blur(4px);
  font-size: 11px; font-weight: 500; color: var(--text-secondary);
}
.map-pin-label b { color: var(--text-primary); font-variant-numeric: tabular-nums; }

/* Routes: a slow travelling dash reads as movement and gives the arc a
   direction without cluttering the map with arrowheads. */
.flow-line { stroke-dasharray: 7 9; animation: flow-dash 2.6s linear infinite; }
.flow-union { stroke-dasharray: 2 7; stroke-linecap: round; animation-duration: 4.5s; }
@keyframes flow-dash { to { stroke-dashoffset: -32; } }
@media (prefers-reduced-motion: reduce) { .flow-line { animation: none; } }

/* ---- Tree explorer: near-immersive, readable-first ---- */
/* The tree is a page of its own now — sit it at the top of the shell and
   reclaim the page's bottom padding so the canvas gets nearly the whole
   viewport. */
.treestage { margin-top: -4px; margin-bottom: -2.5rem; padding-bottom: 14px; }

/* Header: who the tree is centred on, and the ways to move it elsewhere. */
.treehead { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 14px; padding-bottom: 14px; }
.treehead-name { font-family: var(--font-display); font-size: clamp(20px, 2.6vw, 26px); font-weight: 500; letter-spacing: -0.02em; line-height: 1.15; }
.treehead-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* Depth: two labelled segmented controls instead of bare −/+ steppers. */
.treecontrols { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; padding-bottom: 12px; }
/* The row may shrink to the page width on a phone; the pill rail then scrolls
   inside it rather than pushing the whole page sideways. */
.treecontrol { display: inline-flex; align-items: center; gap: 9px; max-width: 100%; min-width: 0; }
.treecontrol .subtabs { flex: 0 1 auto; min-width: 0; }
.treecontrol-k { font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted); white-space: nowrap; }
.treecontrol .subtabs .subtab { padding: 5px 11px; font-size: 13px; font-variant-numeric: tabular-nums; }

/* The canvas, with the two directions of the hourglass named on its edges —
   the layout is meaningless until you're told which way is which. */
.treestage-canvas { position: relative; }
.tree-axis {
  position: absolute; left: 14px; z-index: 2;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: var(--pill);
  background: color-mix(in srgb, var(--bg-surface) 88%, transparent);
  backdrop-filter: blur(6px); border: 1px solid var(--border-base);
  font-size: 11.5px; font-weight: 600; letter-spacing: .02em; color: var(--text-muted);
  pointer-events: none; white-space: nowrap;
}
.tree-axis svg { width: 13px; height: 13px; color: var(--primary); }
/* The zoom tools sit in the top-right, so the upper label stops short of them. */
.tree-axis-up { top: 12px; max-width: calc(100% - 120px); }
.tree-axis-down { bottom: 12px; max-width: calc(100% - 28px); }
.tree-axis-down svg { transform: rotate(180deg); }
/* Below this width the gloss would be truncated to noise — drop it and keep the
   one word that matters ("Ancestors" / "Descendants"). */
@media (max-width: 720px) { .tree-axis-more { display: none; } }
/* Shown over the stage when the focus person has nobody either side, so the
   empty canvas explains itself instead of looking like a failed render. */
.tree-empty {
  position: absolute; inset: 0; z-index: 3; margin: auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; text-align: center;
  /* The stage runs under the bottom bar on a phone; keep the action clear of it. */
  padding: 32px 24px calc(32px + var(--shell-bottom));
  /* Nearly opaque: at a lighter wash the focus card read through the copy. */
  background: color-mix(in srgb, var(--bg-background) 94%, transparent);
  backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px);
}
.tree-empty h3 { max-width: 30rem; font-size: 17px; font-weight: 600; color: var(--text-primary); line-height: 1.35; }
.tree-empty p { max-width: 30rem; color: var(--text-secondary); font-size: 14px; line-height: 1.55; }
.tree-empty-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 4px; }

.tree-tools { position: absolute; top: 12px; right: 12px; z-index: 2; display: flex; gap: 6px; }
.tree-tools .icon-btn { background: color-mix(in srgb, var(--bg-surface) 88%, transparent); backdrop-filter: blur(6px); }

.treecanvas {
  /* The reservation covers the head, depth controls, climb trail and hint —
     the sub-tab rail is gone, so the canvas takes that space back. */
  height: calc(var(--stage-h, 100dvh) - 220px); min-height: 360px;
  border: 1px solid var(--border-base); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--bg-surface);
}
.treehint { margin-top: 10px; font-size: 12px; color: var(--text-muted); }
#tCount { color: var(--text-secondary); font-variant-numeric: tabular-nums; }
/* The header wraps to more rows on narrow screens — give the canvas less. */
@media (max-width: 760px) { .treecanvas { height: calc(var(--stage-h, 100dvh) - 310px); } }

/* A record whose stored name is a placeholder ("*", "?", "N.N.") — shown as a
   label rather than printing the punctuation as if it were somebody's name. */
.name-unknown { color: var(--text-muted); font-style: italic; font-weight: 400; }
.raw-name {
  display: inline-block; min-width: 2.2em; text-align: center;
  padding: 1px 7px; border-radius: var(--radius-sm);
  background: var(--bg-surface-variant); border: 1px solid var(--border-base);
  color: var(--text-secondary); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
}

/* Provenance badges (where a person's data came from) */
.prov-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border-radius: var(--pill); font-size: 11px; font-weight: 600; border: 1px solid transparent; white-space: nowrap; }
.prov-pelle { background: color-mix(in srgb, var(--secondary) 14%, var(--bg-surface)); color: var(--secondary); border-color: color-mix(in srgb, var(--secondary) 30%, transparent); }
.prov-web { background: color-mix(in srgb, var(--warning) 16%, var(--bg-surface)); color: var(--warning); border-color: color-mix(in srgb, var(--warning) 32%, transparent); }
.prov-ai { background: var(--accent-soft); color: var(--primary); border-color: var(--accent-soft-line); }
.prov-note { margin-top: 10px; font-size: 11.5px; color: var(--text-muted); font-style: italic; }

/* Climb-the-tree lineage ladder (focus → forebears, scrolls if long) */
.treeclimb {
  display: flex; align-items: center; gap: 7px;
  overflow-x: auto; overflow-y: hidden; white-space: nowrap;
  padding: 2px 2px 12px; scrollbar-width: thin;
}
.treeclimb::-webkit-scrollbar { height: 6px; }
.treeclimb::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }
.climb-label { flex: none; font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); }
.climbchip {
  flex: none; display: inline-flex; align-items: baseline; gap: 5px;
  padding: 5px 12px; border-radius: var(--pill);
  border: 1px solid var(--border-base); background: var(--bg-surface);
  color: var(--text-secondary); text-decoration: none; font-size: 13px;
  transition: color .15s var(--ease), border-color .15s var(--ease), background .15s var(--ease);
}
.climbchip:hover { border-color: var(--border-strong); color: var(--text-primary); background: var(--bg-hover); }
.climbchip.is-focus { background: var(--accent-soft); border-color: var(--accent-soft-line); color: var(--primary); font-weight: 600; }
.climbchip-yr { color: var(--text-muted); font-size: 11px; }
.climb-you { font-weight: 700; color: var(--primary); }
.climbsep { flex: none; color: var(--text-muted); font-size: 12px; }

/* --- Ask workspaces (multi-conversation) --- */
.askws {
  display: flex;
  gap: 0;
  margin: -18px calc(-1 * var(--gutter, 0px)) 0;
  height: var(--stage-h, calc(100dvh - var(--topbar-h)));
  min-height: 420px;
  position: relative;
}
.askws-side {
  flex: 0 0 264px;
  width: 264px;
  border-right: 1px solid var(--border-base);
  display: flex;
  flex-direction: column;
  padding: 16px 12px;
  gap: 12px;
  background: var(--bg-surface);
  overflow: hidden;
}
.askws-new {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: var(--ctl-h);
  border: 1px solid var(--border-base);
  border-radius: var(--pill);
  font-size: 13.5px; font-weight: 600;
  color: var(--text-primary);
  background: var(--bg-background);
  cursor: pointer;
  transition: background .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease);
}
.askws-new:hover { border-color: var(--accent-soft-line); color: var(--primary); background: var(--accent-soft); }
.askws-new .ic, .askws-new svg { width: 16px; height: 16px; }
.askws-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; margin: 0 -4px; padding: 0 4px; }
.askws-item {
  display: flex; align-items: center;
  border-radius: var(--radius-sm);
  transition: background .12s var(--ease);
}
.askws-item:hover { background: var(--bg-hover); }
.askws-item.is-active { background: var(--accent-soft); }
.askws-item-open {
  flex: 1; min-width: 0;
  text-align: left;
  padding: 9px 10px;
  font-size: 13.5px;
  color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer; background: none; border: 0;
}
.askws-item.is-active .askws-item-open { color: var(--primary); font-weight: 600; }
.askws-del {
  flex: 0 0 auto;
  opacity: 0; padding: 6px 8px; margin-right: 2px;
  color: var(--text-muted); cursor: pointer; border-radius: var(--radius-sm);
  background: none; border: 0;
  transition: opacity .12s var(--ease), color .12s var(--ease), background .12s var(--ease);
}
.askws-item:hover .askws-del, .askws-del:focus-visible { opacity: .7; }
.askws-del:hover { opacity: 1; color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.askws-del svg, .askws-del .ic { width: 15px; height: 15px; }
.askws-main { flex: 1; min-width: 0; display: flex; flex-direction: column; position: relative; }
.askws-toggle {
  display: none;
  position: absolute; top: 10px; left: 12px; z-index: 5;
  padding: 8px; border: 1px solid var(--border-base); border-radius: var(--radius-sm);
  background: var(--bg-background); color: var(--text-secondary); cursor: pointer;
}
.askws-toggle svg, .askws-toggle .ic { width: 18px; height: 18px; }
.askws-thread { flex: 1; overflow-y: auto; padding: 24px 20px 8px; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.askws-thread-inner { max-width: 760px; margin: 0 auto; }
.askws-bar { padding: 12px 20px 18px; }
.askws-bar .ask { max-width: 760px; margin: 0 auto; }
/* Sits in the middle of the thread rather than clinging to the top — the
   composer is at the bottom, so a top-anchored empty state leaves a hole. */
.askws-welcome {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 6px; color: var(--text-secondary);
  min-height: min(58vh, 540px);
}
.askws-main.is-empty .askws-bar { padding-bottom: 8px; }
/* Size via font-size: the glyphs are 1em SVGs, so setting the span's box alone
   left a 16px icon floating in a 38px hole. */
.askws-welcome > .ic { font-size: 34px; color: var(--primary); }
.askws-welcome > .ic > svg, .askws-welcome > svg { width: 1em; height: 1em; }
.askws-welcome h2 { font-size: 20px; font-weight: 600; color: var(--text-primary); }
.askws-suggest {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px; width: 100%; max-width: 620px; margin-top: 18px; text-align: left;
}
/* Odd one out spans the full row so the grid never ends ragged. */
.askws-suggest > .askws-sugg:last-child:nth-child(odd) { grid-column: 1 / -1; }
.askws-sugg {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 14px;
  border: 1px solid var(--border-base); border-radius: var(--radius-md);
  background: var(--bg-background);
  font-size: 13.5px; line-height: 1.35; color: var(--text-secondary);
  cursor: pointer; text-align: left;
  transition: border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease), transform .15s var(--ease);
}
.askws-sugg:hover { border-color: var(--accent-soft-line); color: var(--text-primary); background: var(--bg-surface); transform: translateY(-1px); }
.askws-sugg-ic { flex: 0 0 auto; display: flex; color: var(--primary); }
.askws-welcome .askws-sugg-ic svg { width: 17px; height: 17px; }
.askws-sugg-q { min-width: 0; }
@media (max-width: 720px) {
  .askws-suggest { grid-template-columns: 1fr; max-width: 420px; }
  .askws { margin-top: -18px; }
  .askws-side {
    position: absolute; left: 0; top: 0; bottom: 0; z-index: 20;
    transform: translateX(-102%); transition: transform .22s var(--ease);
    box-shadow: 0 8px 40px rgba(0,0,0,.28);
  }
  .askws-side.open { transform: translateX(0); }
  .askws-toggle { display: block; }
  .askws-thread { padding-top: 56px; }
}

/* ============================ Person profile ============================ */
.person-hero {
  display: flex; align-items: center; gap: 26px;
  padding: 30px;
  position: relative; overflow: hidden;
}
.person-hero::before {
  content: ""; position: absolute; inset: 0;
  background: none;
  pointer-events: none;
}
.person-hero-media { position: relative; flex: 0 0 auto; width: 128px; height: 128px; border-radius: var(--pill); }
.person-hero-pic, .person-hero-media .mono {
  width: 128px !important; height: 128px !important;
  border-radius: var(--pill);
  box-shadow: 0 0 0 4px var(--bg-background), 0 0 0 5px var(--border-base), 0 8px 28px rgba(0,0,0,.14);
}
.person-hero-media .mono { display: flex; align-items: center; justify-content: center; font-size: 50px !important; }
.person-hero-upload {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  border-radius: var(--pill); background: rgba(0,0,0,.55); color: #fff;
  font-size: 11px; font-weight: 600; opacity: 0; cursor: pointer; transition: opacity .15s var(--ease);
}
.person-hero-media:hover .person-hero-upload, .person-hero-upload:focus-within { opacity: 1; }
.person-hero-meta { min-width: 0; display: flex; flex-direction: column; gap: 8px; position: relative; }
.person-hero-name {
  font-family: var(--font-display);
  font-weight: 400; line-height: 1.08; color: var(--text-primary);
  font-size: clamp(26px, 3.4vw, 36px); letter-spacing: -0.024em; text-wrap: balance;
}
.person-hero-dates { color: var(--text-secondary); font-size: 15px; }
.person-hero-sex { color: var(--text-muted); }
.person-hero-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
/* Inline relationship tag used in lists ("your 3rd cousin"). Deliberately quiet
   — it rides alongside a name, it isn't the headline. */
.rel-tag {
  display: inline-flex; align-items: center; flex: none;
  padding: 1.5px 8px; border-radius: var(--pill);
  font-size: 11px; font-weight: 600; letter-spacing: .01em; white-space: nowrap;
  background: var(--accent-soft); color: var(--primary); border: 1px solid var(--accent-soft-line);
}
.rel-tag-you { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
/* "no recorded link to you" — states the absence rather than dropping the tag
   and leaving the card looking like the relationship simply failed to load. */
.rel-tag-none { background: transparent; color: var(--text-muted); border-color: var(--border-base); font-weight: 500; }
/* Relationships reached through a marriage ("Fredriks släkting 3 led upp via
   ett syskon") outrun a single line; where the card has vertical room, wrap
   rather than spill past its edge. */
.rel-tag-wrap { white-space: normal; text-align: left; max-width: 100%; }

/* Small brass pill, e.g. "your 3rd cousin" / "This is you". */
.rel-chip {
  display: inline-flex; align-items: center; align-self: flex-start;
  padding: 4px 12px; border-radius: var(--pill);
  font-size: 12px; font-weight: 600;
  background: var(--accent-soft); color: var(--primary); border: 1px solid var(--accent-soft-line);
}
.pfact { display: flex; gap: 14px; padding: 10px 0; font-size: 14px; border-bottom: 1px solid var(--border-base); }
.pfact:last-child { border-bottom: 0; }
.pfact-k { flex: 0 0 88px; color: var(--text-muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; font-weight: 600; padding-top: 2px; }
.pfact-v { color: var(--text-primary); }
.prel {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 13px 4px 4px; border-radius: var(--pill);
  border: 1px solid var(--border-base); background: var(--bg-background);
  font-size: 13.5px; color: var(--text-primary); text-decoration: none;
  transition: border-color .12s var(--ease), background .12s var(--ease);
}
.prel:hover { border-color: var(--accent-soft-line); background: var(--accent-soft); }
.prel-pic { width: 26px !important; height: 26px !important; font-size: 11px !important; }
.prel-name { line-height: 1; }
.prel-yr { color: var(--text-muted); font-size: 12px; }
.rel-group-label { margin-bottom: 8px; font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); }
@media (max-width: 640px) {
  .person-hero { flex-direction: column; text-align: center; align-items: center; padding: 26px 18px; }
  .person-hero-actions { justify-content: center; }
  .person-hero-dates { justify-content: center; }
  .rel-chip { align-self: center; }
}

/* Timeline of a direct line (the "You" page) */
.line-list { position: relative; margin-left: 4px; padding-left: 18px; border-left: 1px solid var(--border-base); list-style: none; }
.line-list li { position: relative; padding-bottom: 14px; }
.line-list li:last-child { padding-bottom: 0; }
.line-list li::before {
  content: ""; position: absolute; left: -23px; top: 6px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--bg-surface); border: 1.5px solid var(--border-strong);
}
.line-list li.is-you::before { background: var(--primary); border-color: var(--primary); }
.line-list a, .line-list span.line-name { font-size: 14px; color: var(--text-secondary); text-decoration: none; }
.line-list a:hover { color: var(--primary); }
.line-list li.is-you .line-name { color: var(--text-primary); font-weight: 600; }
.line-yr { margin-left: 6px; font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* Toast */
#toast {
  border-radius: var(--pill); box-shadow: var(--shadow-lg);
  background: var(--bg-surface); border: 1px solid var(--border-base);
}

/* ---- Data cleanup (admins): a worklist with the fix on the row ---- */
.subtab-n {
  margin-left: 6px; padding: 1px 6px; border-radius: var(--pill);
  background: var(--bg-surface-variant); color: var(--text-muted);
  font-size: 11px; font-variant-numeric: tabular-nums;
}
/* The count arrives after the rail paints; show nothing rather than an empty pill. */
.subtab-n:empty { display: none; }
.subtab.active .subtab-n { background: color-mix(in srgb, var(--on-primary-btn) 18%, transparent); color: inherit; }
/* Foot of a cleanup worklist: how much of it you are looking at, and the way
   to the rest. Every tab used to stop dead at the first page. */
.clean-more {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px; padding: 14px 2px 2px; color: var(--text-muted); font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.clean-row { padding: 16px 18px; display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px 20px; }
.clean-row.is-done { opacity: .45; }
.clean-row.is-done .clean-actions { display: none; }
.clean-head { flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; }
.clean-why { font-size: 12.5px; color: var(--warning); }
.clean-fields { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; min-width: 0; }
.clean-f { display: inline-flex; flex-direction: column; gap: 4px; min-width: 0; }
.clean-f > span { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted); }
.clean-note { flex: 1 1 100%; margin: 0; font-size: 12px; color: var(--text-muted); }
.clean-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }
@media (max-width: 640px) { .clean-actions { margin-left: 0; flex: 1 1 100%; } }

/* ---- Home feed: one card per screen, snapped ------------------------------
   The shell scroller can't do this — snapping needs the scroll container to be
   exactly one card tall, so the feed owns its own scrolling and fills the
   stage the wide routes already measure. */
.shell-view--feed { padding: 0; }
.feed {
  height: var(--stage-h);
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
}
.feed::-webkit-scrollbar { display: none; }

.feed-card {
  position: relative;
  height: var(--stage-h);
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex; flex-direction: column;
}
/* On a wide screen a full-bleed card would stretch a portrait into a mural;
   keep the same scroller inside a centred column instead. */
@media (min-width: 900px) {
  .feed-card { max-width: 33rem; margin-inline: auto; }
}

.feed-art { flex: 1 1 auto; min-height: 0; overflow: hidden; }
.feed-art img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* No picture: the crest on the family's own navy, rather than a grey hole. */
.feed-art--crest {
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--primary) 8%, var(--bg-surface));
}
.feed-art--crest img { width: min(40%, 190px); height: auto; object-fit: contain; opacity: .92; }

.feed-body {
  flex: none; position: relative; margin-top: -96px;
  padding: 0 clamp(16px, 5vw, 28px) clamp(22px, 5vw, 32px);
  background: linear-gradient(to top, var(--bg-background) 58%, color-mix(in srgb, var(--bg-background) 70%, transparent) 82%, transparent);
  padding-top: 96px;
}
.feed-kind {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--feed-accent);
}
.feed-title {
  margin-top: 10px;
  font-size: clamp(23px, 6vw, 31px); font-weight: 500;
  line-height: 1.14; letter-spacing: -.024em;
}
.feed-text {
  margin-top: 10px; color: var(--text-secondary); line-height: 1.55;
  /* Four lines is what fits above the fold on the smallest phone; the card
     opens the profile for the rest rather than truncating mid-thought. */
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.feed-action {
  display: inline-block; margin-top: 16px; padding: 10px 18px;
  border-radius: 999px; border: 1px solid var(--border-base);
  color: var(--text-primary); text-decoration: none; font-size: 14px; font-weight: 600;
}
.feed-action:hover { border-color: var(--border-strong); background: var(--bg-surface-variant); }

/* ---- Bottom sheet behind "More" ---- */
.sheet-scrim {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: flex-end;
  background: color-mix(in srgb, #000 55%, transparent);
  backdrop-filter: blur(2px);
}
.sheet {
  width: 100%; background: var(--bg-surface);
  border: 1px solid var(--border-base); border-bottom: 0;
  border-radius: var(--wui-radius-large) var(--wui-radius-large) 0 0;
  padding: 8px 12px calc(14px + var(--safe-bottom));
}
.sheet-grip { width: 40px; height: 4px; border-radius: 2px; background: var(--border-base); margin: 6px auto 10px; }
.sheet-item {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 12px; border-radius: var(--radius-sm); min-height: var(--wui-touch-row);
  color: var(--text-primary); text-decoration: none; font-weight: 500;
}
.sheet-item.active { color: var(--primary); background: var(--bg-surface-variant); }
@media (min-width: 900px) {
  .sheet-scrim { align-items: center; justify-content: center; }
  .sheet { max-width: 420px; border-radius: var(--wui-radius-large); border-bottom: 1px solid var(--border-base); padding-bottom: 14px; }
}

/* A derived card has no picture, so its motif is the picture: set big enough to
   carry the screen, tinted by kind so the feed doesn't read as one template. */
.feed-art--motif {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 24px; text-align: center;
  background: color-mix(in srgb, var(--feed-motif-tint) 10%, var(--bg-surface));
  --feed-motif-tint: var(--primary);
}
.feed-art--motif[data-kind="onthisday"] { --feed-motif-tint: var(--secondary); }
/* Records are domain data, not a warning: the arms' gold from the data ramp. */
.feed-art--motif[data-kind="record"]    { --feed-motif-tint: var(--viz-4); }
.feed-motif {
  font-size: clamp(64px, 22vw, 128px); font-weight: 300; line-height: 1;
  letter-spacing: -.04em; color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.feed-motif-sub {
  max-width: 22ch; font-size: 13px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-muted);
}

/* ---- One frame, six compositions ------------------------------------------
   Every card is exactly one screen and the bottom strip never moves, so the
   thumb always knows where the button is. What changes is the upper two-thirds:
   a feed where all six kinds filled that frame the same way read as one
   template refilled, which is what made it feel like a list of dates. Colour
   comes from --feed-accent, set per kind, so the whole thing rethemes with the
   Shield tokens rather than hard-coded hexes. */
.feed-card                      { --feed-accent: var(--primary); }
.feed-card[data-kind="onthisday"] { --feed-accent: var(--secondary); }
.feed-card[data-kind="record"]    { --feed-accent: var(--viz-4); }
.feed-card[data-kind="context"]   { --feed-accent: var(--secondary); }
.feed-card[data-kind="person"]    { --feed-accent: var(--text-muted); }

.feed-art--stamp, .feed-art--stat, .feed-art--quote,
.feed-art--timeline, .feed-art--plate {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; text-align: center;
  /* The body strip pulls up 96px over the art. Clearing all of it left a dead
     band across every card, and the top of that gradient is transparent
     anyway — so the composition sits into it, the way a poster would. */
  padding: 24px 24px 64px;
  background: color-mix(in srgb, var(--feed-accent) 8%, var(--bg-surface));
}

/* On this day — a date stamp, because the date is the whole point of the card. */
.stamp-day {
  font-size: clamp(72px, 26vw, 148px); font-weight: 200; line-height: .9;
  letter-spacing: -.05em; color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.stamp-month {
  font-size: 15px; font-weight: 600; letter-spacing: .26em;
  text-transform: uppercase; color: var(--feed-accent);
}
.stamp-rule { width: 56px; height: 1px; background: var(--border-strong); margin: 4px 0; }
.stamp-year {
  font-size: 27px; font-weight: 400; letter-spacing: .06em;
  color: var(--text-secondary); font-variant-numeric: tabular-nums;
}
/* A round anniversary is about the span of years, not the day it fell on. */
.feed-art--stamp[data-tone="round"] .stamp-day { font-size: clamp(54px, 18vw, 104px); }

/* Record — the number, then the bar that gives it a size. */
.stat-value {
  font-size: clamp(76px, 26vw, 148px); font-weight: 300; line-height: .92;
  letter-spacing: -.045em; color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.stat-unit {
  font-size: 13px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--text-muted);
}
.stat-bars { width: min(80%, 300px); margin-top: 18px; display: flex; flex-direction: column; gap: 8px; }
.statbar {
  position: relative; display: flex; align-items: center; height: 26px;
  border-radius: 7px; background: var(--bg-surface-variant); overflow: hidden;
}
.statbar-fill {
  position: absolute; inset: 0 auto 0 0;
  background: color-mix(in srgb, var(--feed-accent) 52%, transparent);
}
.statbar--avg .statbar-fill { background: color-mix(in srgb, var(--text-muted) 28%, transparent); }
.statbar-label {
  position: relative; padding: 0 10px; white-space: nowrap;
  font-size: 11px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* Did you know — the number pulled out of the sentence below it. */
.quote-num {
  font-size: clamp(88px, 30vw, 164px); font-weight: 200; line-height: .86;
  letter-spacing: -.05em; color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.quote-sub {
  max-width: 22ch; font-size: 13px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-muted);
}

/* In context — two dated points on one rail, oldest at the top. */
.feed-art--timeline {
  align-items: stretch; text-align: left;
  padding-inline: clamp(20px, 7vw, 46px); gap: 0;
}
/* The mark column has to stretch the full height of the row, or the connector
   between the two dots has nothing to span. These carry a ctx- prefix because
   .tl-dot and .tl-line already belong to the per-person life rail above, whose
   absolute positioning silently broke this one. */
.ctxrow { display: grid; grid-template-columns: auto 22px 1fr; align-items: stretch; gap: 0 12px; }
.ctxyear {
  align-self: start;
  min-width: 4ch; font-size: 19px; font-weight: 600;
  color: var(--text-secondary); font-variant-numeric: tabular-nums;
}
.ctxmark {
  position: relative; display: flex; justify-content: center;
  align-items: flex-start; padding-top: 6px;
}
.ctxdot {
  width: 11px; height: 11px; border-radius: 50%; z-index: 1;
  border: 2px solid var(--feed-accent); background: var(--bg-surface);
}
.ctxline {
  position: absolute; top: 19px; bottom: 0; width: 2px;
  background: color-mix(in srgb, var(--feed-accent) 40%, transparent);
}
.ctxwhat { font-size: 15px; line-height: 1.4; color: var(--text-secondary); padding-bottom: 46px; }
.ctxrow:last-child .ctxwhat { padding-bottom: 0; }
.ctxrow--self .ctxdot { background: var(--feed-accent); }
.ctxrow--self .ctxwhat { color: var(--text-primary); font-weight: 500; }
.ctxrow--self .ctxyear { color: var(--text-primary); }

/* From the family — a monogram plate, the one kind that is about a person
   rather than a number. */
.plate-mono {
  font-size: clamp(80px, 28vw, 156px); font-weight: 200; line-height: 1;
  letter-spacing: .02em; color: var(--text-primary);
}
.plate-rule { width: 44px; height: 1px; background: var(--border-strong); }
.plate-sub {
  font-size: 13px; font-weight: 600; letter-spacing: .2em;
  text-transform: uppercase; color: var(--text-muted);
}

/* Between the phone and the centred desktop column the card is full-bleed at
   ~810px, and type clamped for a 393px phone left it marooned in the middle of
   a tablet screen. Desktop is deliberately excluded: there the card is a 33rem
   column, so the phone setting is already the right size for it. */
@media (min-width: 700px) and (max-width: 899px) {
  .stamp-day, .stat-value, .plate-mono { font-size: clamp(148px, 22vw, 208px); }
  .quote-num { font-size: clamp(160px, 24vw, 224px); }
  .stamp-month, .plate-sub { font-size: 17px; }
  .stat-unit, .quote-sub { font-size: 15px; }
  .stamp-year { font-size: 34px; }
  .stat-bars { width: min(62%, 380px); }
  .statbar { height: 32px; }
  .statbar-label { font-size: 13px; }
  .ctxyear { font-size: 24px; }
  .ctxwhat { font-size: 19px; }
}

/* ---- Phone chrome: one control instead of four ---------------------------
   Below the desktop breakpoint the topbar carried a theme toggle, a language
   pair and a sign-out pill — three things the reader did not come for, ahead of
   everything they did. They fold into the account sheet. */
.topbar-more { display: none; padding: 0; overflow: hidden; }
.topbar-more img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
@media (max-width: 899px) {
  .topbar-actions #appearanceBtn,
  .topbar-actions .langtoggle,
  .topbar-actions .userchip,
  .topbar-actions #signOutBtn { display: none !important; }
  .topbar-more { display: inline-flex; align-items: center; justify-content: center; }
}

.sheet-head {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 12px 14px; color: var(--text-muted);
  font-size: 13px; font-weight: 600; letter-spacing: .04em;
}
.sheet-avatar { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
.sheet-appearance { padding: 4px 12px 8px; }
.sheet-item--lang { justify-content: space-between; }
.sheet-seg { display: inline-flex; gap: 2px; padding: 3px; border-radius: var(--pill); background: var(--bg-surface-variant); }
.sheet-seg button {
  border: 0; background: transparent; cursor: pointer;
  padding: 5px 12px; border-radius: var(--pill);
  font-size: 12px; font-weight: 700; letter-spacing: .06em; color: var(--text-muted);
}
.sheet-seg button.is-on { background: var(--primary-btn); color: var(--on-primary-btn); }
.sheet-item--quiet { color: var(--text-secondary); }

/* ---- Page heads earn their space back on a phone ----
   Eyebrow + display title + a three-line lede was ~40% of the first screen
   before any content. The eyebrow repeats the nav, so it goes; the lede stays,
   clamped to the two lines that actually orient someone. */
@media (max-width: 640px) {
  .page-head .eyebrow:not(.eyebrow--keep) { display: none; }
  .page-sub {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  /* "GENERATIONER UPPÅT" ate the row the 0–5 rail needed. */
  .treecontrol-k .tk-long { display: none; }
}
.treecontrol-k .tk-short { display: none; }
@media (max-width: 640px) { .treecontrol-k .tk-short { display: inline; } }

/* The rail fade lives with .subtabs, keyed on data-edge — a width query cannot
   tell a rail that overflows from one that happens to fit. */
