/* =====================================================================
   Main application shell — Roach / Mertson Family Tree
   ===================================================================== */

/* ── Layout ──────────────────────────────────────────────────────────── */
/* iOS: 100vh is the LARGE viewport (as if the URL bar were hidden), so a 100vh box is taller
   than what you can actually see. The overflow then can't settle and Safari rubber-bands the
   top back out of reach. 100dvh tracks the real visible height. Keep the 100vh line above it
   as the fallback for browsers without dvh. */
body.page-app {
    min-height:  100vh;
    min-height:  100dvh;
    display:     flex;
    overflow:    hidden;
}

/* ── Sidebar (desktop) ───────────────────────────────────────────────── */
#sidebar {
    width:          var(--sidebar-width);
    background:     var(--sidebar-bg);
    display:        flex;
    flex-direction: column;
    flex-shrink:    0;
    height:         100vh;
    height:         100dvh;   /* iOS: dynamic viewport excludes the browser toolbar */
    position:       sticky;
    top:            0;
    overflow:       hidden;   /* the NAV scrolls; brand + footer stay pinned */
    z-index:        var(--z-sticky);
    transition:     transform var(--transition-slow);
}
.sidebar-brand { flex-shrink: 0; }
.sidebar-footer { flex-shrink: 0; }

.sidebar-brand {
    padding:       var(--space-5) var(--space-4) var(--space-4);
    border-bottom: 1px solid rgba(255,255,255,.08);
}
/* Brand mark. Currently a text glyph rather than an <img>, so it needs explicit
   centring and a font-size — object-fit alone did nothing for a text node.
   ⚠️ This element is ALSO the force-refresh control: app.js binds showRefreshModal()
   to `.sidebar-brand__logo`. Keep that class on whatever renders here, image or not,
   or clearing caches on a device becomes impossible from the UI. */
.sidebar-brand__logo {
    width:          40px;
    height:         40px;
    border-radius:  var(--radius-md);
    display:        flex;
    align-items:    center;
    justify-content:center;
    font-size:      28px;
    line-height:    1;
    object-fit:     contain;
    margin-bottom:  var(--space-3);
    flex-shrink:    0;
}
.topbar-logo {
    width:          32px;
    height:         32px;
    display:        inline-flex;
    align-items:    center;
    justify-content:center;
    font-size:      22px;
    line-height:    1;
    object-fit:     contain;
    border-radius:  var(--radius-sm);
    flex-shrink:    0;
}
.sidebar-brand__name {
    color:       var(--sidebar-text-active);
    font-size:   var(--font-size-sm);
    font-weight: 800;
    line-height: 1.3;
}
.sidebar-brand__sub {
    color:     rgba(200,216,228,.5);
    font-size: var(--font-size-xs);
}

.sidebar-nav {
    flex:       1;
    min-height: 0;
    overflow-y: auto;   /* scroll the tab list; Sign Out stays reachable at the bottom */
    padding:    var(--space-3) 0;
}
.sidebar-nav-section {
    padding:       var(--space-3) var(--space-4) var(--space-1);
    font-size:     10px;
    font-weight:   700;
    letter-spacing:.08em;
    text-transform:uppercase;
    color:         rgba(200,216,228,.4);
}
.sidebar-nav-item {
    display:       flex;
    align-items:   center;
    gap:           var(--space-3);
    padding:       var(--space-3) var(--space-4);
    color:         var(--sidebar-text);
    font-size:     var(--font-size-sm);
    font-weight:   500;
    cursor:        pointer;
    border:        none;
    background:    none;
    width:         100%;
    text-align:    left;
    border-radius: 0;
    transition:    background var(--transition-fast), color var(--transition-fast);
    border-left:   3px solid transparent;
}
.sidebar-nav-item:hover {
    background:  var(--sidebar-item-hover);
    color:       var(--sidebar-text-active);
}
.sidebar-nav-item.active {
    background:  rgba(224,112,16,.15);
    color:       var(--sidebar-text-active);
    border-left-color: var(--color-accent);
    font-weight: 600;
}
.sidebar-nav-item__icon { font-size: 18px; flex-shrink: 0; width: 20px; text-align: center; }

.sidebar-footer {
    padding:       var(--space-4);
    border-top:    1px solid rgba(255,255,255,.08);
}
.sidebar-user {
    display:       flex;
    align-items:   center;
    gap:           var(--space-3);
    margin-bottom: var(--space-3);
    padding:       var(--space-2) var(--space-2);
    border-radius: var(--radius-md);
}
.sidebar-user__avatar {
    width:          32px;
    height:         32px;
    border-radius:  50%;
    background:     var(--color-accent);
    display:        flex;
    align-items:    center;
    justify-content:center;
    font-weight:    700;
    font-size:      var(--font-size-sm);
    color:          #fff;
    flex-shrink:    0;
}
.sidebar-user__name  { color: var(--sidebar-text-active); font-size: var(--font-size-sm); font-weight: 600; }
.sidebar-user__role  { color: rgba(200,216,228,.5); font-size: var(--font-size-xs); }

/* ── Main content area ───────────────────────────────────────────────── */
#main-content {
    flex:       1;
    min-width:  0;
    display:    flex;
    flex-direction: column;
    height:     100vh;
    height:     100dvh;   /* iOS: see body.page-app — 100vh overflows the visible area and the
                             scroll snaps back, taking the topbar + search out of reach */
    overflow-y: auto;
    overscroll-behavior: contain;          /* don't hand the bounce to the page behind */
    -webkit-overflow-scrolling: touch;     /* momentum scrolling on iOS */
}

/* ── Top bar ─────────────────────────────────────────────────────────── */
#topbar {
    height:          var(--header-height);
    background:      var(--color-bg-surface);
    border-bottom:   1px solid var(--color-border-subtle);
    display:         flex;
    align-items:     center;
    padding:         0 var(--space-6);
    gap:             var(--space-4);
    position:        sticky;
    top:             0;
    z-index:         var(--z-sticky);
    box-shadow:      var(--shadow-sm);
    flex-shrink:     0;
}
#topbar-menu-btn {
    display:  none;
    padding:  var(--space-2);
    border:   none;
    background: none;
    cursor:   pointer;
    font-size: 20px;
    color:    var(--color-text-secondary);
}
#topbar-title {
    font-size:   var(--font-size-lg);
    font-weight: 700;
    flex:        1;
}
#topbar-actions { display: flex; align-items: center; gap: var(--space-3); }

/* ── Tab panels ─────────────────────────────────────────────────────── */
.tab-panel {
    display:  none;
    padding:  var(--space-6);
    animation:fadeIn var(--transition-base) ease both;
}
.tab-panel.active { display: block; }
.tab-panel .panel-header {
    display:       flex;
    align-items:   center;
    justify-content: space-between;
    margin-bottom: var(--space-6);
    gap:           var(--space-4);
    flex-wrap:     wrap;
}
.tab-panel .panel-title {
    font-size:   var(--font-size-xl);
    font-weight: 800;
}
.panel-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; }

/* ── Dashboard grid ─────────────────────────────────────────────────── */
.dashboard-stats {
    display:               grid;
    /* 260px, not 220 — a card holds a 48px icon plus its gap and padding, which left barely
       100px for the number. Inventory value at this plant can run to 8 figures.
       min(…,100%) so the track can never demand more width than the viewport has. */
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
    gap:                   var(--space-4);
    margin-bottom:         var(--space-6);
}
.dashboard-grid {
    display:               grid;
    grid-template-columns: 1fr 1fr;
    gap:                   var(--space-4);
}
@media (max-width: 900px) {
    .dashboard-grid { grid-template-columns: 1fr; }
}

/* ── Mobile: bottom nav ─────────────────────────────────────────────── */
#bottom-nav {
    display: none;
}

/* ── Mobile breakpoint ───────────────────────────────────────────────── */
@media (max-width: 768px) {
    #sidebar {
        position:  fixed;
        left:      0; top: 0; bottom: 0;
        transform: translateX(calc(-1 * var(--sidebar-width)));
        z-index:   var(--z-modal);
    }
    #sidebar.open { transform: translateX(0); }

    #sidebar-overlay {
        display:    none;
        position:   fixed; inset: 0;
        background: rgba(0,0,0,.5);
        z-index:    calc(var(--z-modal) - 1);
    }
    #sidebar-overlay.visible { display: block; }

    #topbar-menu-btn { display: flex; }
    #topbar { padding: 0 var(--space-4); }

    .tab-panel { padding: var(--space-4) var(--space-3); padding-bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px) + var(--space-4)); }
    /* Supervisor and up get no bottom bar — reclaim the space it was reserving. */
    body.no-bottom-nav .tab-panel { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-4)); }
    body.no-bottom-nav #bottom-nav { display: none !important; }
    /* Without the bar, the hamburger IS the navigation below 768px — make it a real target. */
    #topbar-menu-btn { min-width: 44px; min-height: 44px; font-size: 24px; margin-left: calc(var(--space-2) * -1); }

    #bottom-nav {
        display:         flex;
        position:        fixed;
        bottom:          0; left: 0; right: 0;
        height:          var(--bottom-nav-h);
        /* iOS: keep the buttons clear of the home indicator / Safari's bottom chrome, which
           otherwise sits on top of a fixed bar and eats the taps. */
        height:          calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px));
        padding-bottom:  env(safe-area-inset-bottom, 0px);
        background:      var(--color-bg-surface);
        border-top:      1px solid var(--color-border-subtle);
        z-index:         var(--z-sticky);
        box-shadow:      0 -2px 8px rgba(0,0,0,.08);
    }
    .bottom-nav-item {
        flex:          1;
        display:       flex;
        flex-direction:column;
        align-items:   center;
        justify-content:center;
        gap:           2px;
        font-size:     10px;
        font-weight:   600;
        color:         var(--color-text-muted);
        border:        none;
        background:    none;
        cursor:        pointer;
        padding:       var(--space-2) var(--space-1);
        transition:    color var(--transition-fast);
    }
    .bottom-nav-item__icon { font-size: 20px; }
    .bottom-nav-item.active { color: var(--color-primary); }

}

/* One card per row on a PHONE only. Two hard columns couldn't fit a money value that must not
   wrap — the track blew past the viewport and scrolled the whole page sideways. Above this the
   auto-fit rule handles it: iPad portrait (768) still gets two columns rather than one tall
   column of half-empty cards. */
@media (max-width: 560px) {
    .dashboard-stats { grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PEOPLE — the person card is the ATOM (docs/SCOPING.md)
   The same card renders in the list, in a parent row, in a child row, and
   later in a chart node and in print. Styling it once is most of the visual
   identity, so the variants are modifiers rather than separate components.
   ═══════════════════════════════════════════════════════════════════════════ */
.pp-searchbar { margin-bottom: var(--space-4); max-width: 520px; }
.pp-searchbar input { font-size: var(--font-size-base); }

/* One column on a phone, two on a tablet, three on a wide screen. 3,186 people
   need density, but a name that wraps mid-surname is worse than fewer columns. */
.pp-results {
    display: grid; gap: var(--space-2);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
}
.pp-empty { padding: var(--space-8); text-align: center; color: var(--color-text-muted); }
.pp-more { margin-top: var(--space-4); text-align: center; }

.person-card {
    display: flex; align-items: center; gap: var(--space-3);
    width: 100%; text-align: left; cursor: pointer;
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border-subtle);
    border-left: 3px solid transparent;
    border-radius: var(--radius-md);
    transition: border-color var(--transition-fast), background var(--transition-fast);
}
.person-card:hover { background: var(--color-bg-subtle); border-left-color: var(--color-accent); }
.person-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.person-card--compact { padding: var(--space-2) var(--space-3); background: transparent; border-style: dashed; }

.pc-sex   { flex-shrink: 0; width: 18px; text-align: center; color: var(--color-text-muted); font-size: 15px; }
.pc-main  { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.pc-name  { font-weight: 600; font-size: var(--font-size-sm); overflow-wrap: anywhere; }
.pc-dates { font-size: var(--font-size-xs); color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.pc-place { font-size: var(--font-size-xs); color: var(--color-text-muted);
            white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pc-living { flex-shrink: 0; }

/* ── Person detail ──────────────────────────────────────────────────────── */
.pd-aka { font-size: var(--font-size-xs); color: var(--color-text-muted);
          margin-bottom: var(--space-4); font-style: italic; }
.pd-section { margin-bottom: var(--space-6); }
.pd-section__title {
    font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--color-text-secondary); padding-bottom: var(--space-1);
    border-bottom: 1px solid var(--color-border-subtle); margin-bottom: var(--space-3);
}
.pd-notes { font-size: var(--font-size-sm); white-space: pre-wrap; }

.pd-event { padding: var(--space-2) 0; border-bottom: 1px dashed var(--color-border-subtle); }
.pd-event:last-child { border-bottom: none; }
.pd-event__head  { display: flex; gap: var(--space-3); align-items: baseline; flex-wrap: wrap; }
.pd-event__label { font-weight: 700; font-size: var(--font-size-sm); min-width: 84px; }
/* tabular-nums so a column of dates lines up; her text is never reformatted, but it
   can at least be legible in a column. */
.pd-event__when  { font-size: var(--font-size-sm); font-variant-numeric: tabular-nums; }
.ev-qual         { color: var(--color-text-muted); font-size: var(--font-size-xs); }
.ev-unsorted     { color: var(--color-accent); cursor: help; }
.pd-event__place { font-size: var(--font-size-xs); color: var(--color-text-secondary); margin-left: 84px; }
.pd-event__desc  { font-size: var(--font-size-xs); margin-left: 84px; }

/* A source sits UNDER the fact it supports. Indented and quiet: it is evidence,
   not the headline. Suzanne's steer — sources have value, at a lower level. */
.pd-cites { margin: var(--space-1) 0 0 84px; }
.pd-cite  { display: flex; gap: var(--space-2); padding: 2px 0; font-size: var(--font-size-xs); }
.pd-cite__mark  { color: var(--color-text-tertiary, var(--color-text-muted)); flex-shrink: 0; }
.pd-cite__body  { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.pd-cite__title { color: var(--color-text-secondary); font-weight: 600; }
.pd-cite__page  { color: var(--color-text-muted); overflow-wrap: anywhere; }
.pd-cite__fact  { align-self: flex-start; font-size: 9px; text-transform: uppercase;
                  letter-spacing: .06em; color: var(--color-text-muted);
                  border: 1px solid var(--color-border-subtle); border-radius: var(--radius-sm);
                  padding: 0 4px; }
.pd-cite__link  { color: var(--color-primary); }

.pd-group { margin-bottom: var(--space-2); }
.pd-group__note { font-size: var(--font-size-xs); color: var(--color-accent);
                  text-transform: uppercase; letter-spacing: .05em; }

/* One household as one card with its children nested inside — the TDSS pattern,
   borrowed for its layout and not for its schema. */
.pd-family { border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md);
             padding: var(--space-3); margin-bottom: var(--space-3); }
.pd-family__head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.pd-family__marr { font-size: var(--font-size-xs); color: var(--color-text-secondary); }
.pd-children { margin-top: var(--space-2); padding-left: var(--space-4);
               border-left: 2px solid var(--color-border-subtle); }
.pd-children__label { font-size: 10px; font-weight: 700; letter-spacing: .06em;
                      text-transform: uppercase; color: var(--color-text-muted);
                      margin-bottom: var(--space-1); }

.pd-media { display: flex; gap: var(--space-2); align-items: flex-start;
            padding: var(--space-2) 0; font-size: var(--font-size-sm); }
.pd-media__body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.pd-media__title { font-weight: 600; }
.pd-media__missing { font-size: var(--font-size-xs); color: var(--color-text-muted); font-style: italic; }
.pd-story__toggle { display: flex; gap: var(--space-2); align-items: baseline; width: 100%;
                    text-align: left; background: none; border: none; cursor: pointer;
                    font-size: var(--font-size-sm); font-weight: 600; color: var(--color-text);
                    padding: var(--space-2) 0; }
.pd-story__body { display: none; font-size: var(--font-size-sm); white-space: pre-wrap;
                  max-height: 300px; overflow-y: auto; padding: var(--space-3);
                  background: var(--color-bg-subtle); border-radius: var(--radius-md); }
.pd-story__body.open { display: block; }

/* ═══════════════════════════════════════════════════════════════════════════
   FAMILIES — one household per card, children nested inside.
   TDSS's layout, borrowed for its shape. Not its schema: teens.parent_id
   cannot express a remarriage, and Suzanne appears in two households.
   ═══════════════════════════════════════════════════════════════════════════ */
.fm-results {
    display: grid; gap: var(--space-3);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr));
    align-items: start;
}
.fm-card {
    background: var(--color-bg-surface);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    box-shadow: var(--shadow-sm);
}
.fm-card__head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.fm-partners   { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; flex: 1; min-width: 0; }
.fm-partner {
    display: inline-flex; align-items: center; gap: var(--space-2);
    background: none; border: none; cursor: pointer; padding: 2px 0;
    font-size: var(--font-size-base); font-weight: 700; color: var(--color-text);
    text-align: left; overflow-wrap: anywhere;
}
.fm-partner:hover .fm-partner__name { color: var(--color-primary); text-decoration: underline; }
.fm-amp { color: var(--color-text-muted); font-weight: 400; }

.fm-marr { font-size: var(--font-size-xs); color: var(--color-text-secondary);
           margin-top: 2px; font-variant-numeric: tabular-nums; }

/* A real fact about the household, not missing data — 219 of 1,219. */
.fm-tag {
    flex-shrink: 0; font-size: 9px; text-transform: uppercase; letter-spacing: .06em;
    color: var(--color-text-muted); border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-sm); padding: 1px 5px;
}
.fm-tag--rel { color: var(--color-accent); border-color: var(--color-accent); }

/* The nesting IS the point: the rule down the left reads as "these belong to those". */
.fm-children {
    margin-top: var(--space-3); padding-left: var(--space-3);
    border-left: 2px solid var(--color-border);
}
.fm-children__label {
    font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--color-text-muted); margin-bottom: var(--space-1);
}
.fm-children__none {
    margin-top: var(--space-2); font-size: var(--font-size-xs);
    color: var(--color-text-muted); font-style: italic;
}
.fm-child {
    display: flex; align-items: center; gap: var(--space-2); width: 100%;
    background: none; border: none; cursor: pointer; text-align: left;
    padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm);
}
.fm-child:hover { background: var(--color-bg-subtle); }
.fm-child__sex   { flex-shrink: 0; width: 14px; text-align: center;
                   color: var(--color-text-muted); font-size: 13px; }
.fm-child__main  { display: flex; flex-direction: column; gap: 0; min-width: 0; flex: 1; }
.fm-child__name  { font-size: var(--font-size-sm); font-weight: 600; overflow-wrap: anywhere; }
.fm-child__dates { font-size: var(--font-size-xs); color: var(--color-text-secondary);
                   font-variant-numeric: tabular-nums; }

/* ═══════════════════════════════════════════════════════════════════════════
   ENTRY — typing from the handwritten sheet (Phase 2)
   The whole layout serves one motion: eyes on the page, hands on the keys,
   Enter to move on. Nothing here should ever need a mouse.
   ═══════════════════════════════════════════════════════════════════════════ */
.en-gate { max-width: 560px; }
.en-gate__title { font-size: var(--font-size-lg); font-weight: 700; margin-bottom: var(--space-2); }
.en-gate__why   { font-size: var(--font-size-sm); color: var(--color-text-secondary);
                  margin-bottom: var(--space-5); }

.en-src-chip {
    display: inline-flex; align-items: center; gap: var(--space-2);
    font-size: var(--font-size-xs); color: var(--color-text-secondary);
    background: var(--color-bg-subtle); border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md); padding: var(--space-1) var(--space-3);
}
.en-src-change { background: none; border: none; cursor: pointer; padding: 0;
                 color: var(--color-primary); font-size: var(--font-size-xs);
                 text-decoration: underline; }

.en-form { background: var(--color-bg-surface); border: 1px solid var(--color-border-subtle);
           border-radius: var(--radius-lg); padding: var(--space-4); margin-bottom: var(--space-5); }
/* Auto-fit rather than fixed columns: the row collapses to one column on a phone and
   spreads on a desktop without a media query per breakpoint. */
.en-grid { display: grid; gap: var(--space-3);
           grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.en-field { display: flex; flex-direction: column; gap: 2px; }
.en-field--wide   { grid-column: span 2; }
.en-field--narrow { max-width: 90px; }
@media (max-width: 700px) { .en-field--wide { grid-column: span 1; } }

.en-label { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
            color: var(--color-text-secondary); }
/* "as written" is a promise, not a hint: whatever she types is what gets stored. */
.en-hint  { font-weight: 400; text-transform: none; letter-spacing: 0;
            color: var(--color-accent); font-style: italic; }

.en-actions { display: flex; align-items: center; gap: var(--space-4);
              margin-top: var(--space-4); flex-wrap: wrap; }
.en-living  { margin: 0; }
.en-status  { font-size: var(--font-size-sm); color: var(--color-text-muted); }
.en-status--ok   { color: var(--color-success, #2e7d32); }
.en-status--warn { color: var(--color-danger, #c62828); }

.en-keys { margin-top: var(--space-3); font-size: var(--font-size-xs);
           color: var(--color-text-muted); }
.en-keys kbd, .en-actions kbd {
    font-family: inherit; font-size: 10px; padding: 1px 5px;
    border: 1px solid var(--color-border); border-bottom-width: 2px;
    border-radius: var(--radius-sm); background: var(--color-bg-subtle);
}

.en-recent__empty { font-size: var(--font-size-sm); max-width: 46ch; }
.en-row { display: flex; align-items: center; gap: var(--space-3);
          padding: var(--space-2) 0; border-bottom: 1px dashed var(--color-border-subtle); }
.en-row__name  { font-weight: 600; font-size: var(--font-size-sm); }
.en-row__dates { flex: 1; font-size: var(--font-size-xs); color: var(--color-text-secondary);
                 font-variant-numeric: tabular-nums; }
.en-row__acts  { display: flex; gap: var(--space-1); }

/* ═══════════════════════════════════════════════════════════════════════════
   Charts (Phase 4)

   Everything here is driven by the theme variables, so the chart follows light
   and dark like the rest of the app — EXCEPT in print, where it is forced to
   ink on white further down. A dark-mode chart printed as-is would be a black
   rectangle, and printing is a real requirement here, not an afterthought.
   ═══════════════════════════════════════════════════════════════════════════ */

.ch-toolbar {
    display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
    padding: var(--space-3) 0; margin-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-border-subtle);
}
.ch-root-btn { font-weight: 600; max-width: 40ch; overflow: hidden; text-overflow: ellipsis;
               white-space: nowrap; }
.ch-gens { display: flex; align-items: center; gap: var(--space-2); }
.ch-gens select { width: auto; padding-top: 4px; padding-bottom: 4px;
                  font-size: var(--font-size-sm); }
.ch-zoom { display: flex; gap: 2px; margin-left: auto; }

/* Segmented control — direction is a choice between two, not a dropdown. */
.ch-seg { display: inline-flex; border: 1px solid var(--color-border);
          border-radius: var(--radius-md); overflow: hidden; }
.ch-seg__btn {
    padding: var(--space-2) var(--space-3); font-size: var(--font-size-sm);
    background: transparent; border: 0; cursor: pointer; color: var(--color-text-secondary);
}
.ch-seg__btn + .ch-seg__btn { border-left: 1px solid var(--color-border); }
.ch-seg__btn.active { background: var(--color-primary); color: #fff; font-weight: 600; }

.ch-notice {
    padding: var(--space-3) var(--space-4); margin-bottom: var(--space-3);
    border-radius: var(--radius-md); border-left: 3px solid var(--color-warning);
    background: var(--color-warning-bg); font-size: var(--font-size-sm);
}

/* The stage clips the drawing; pan/zoom moves the <g> inside it. touch-action:none
   hands every gesture to the pointer handlers — without it iOS Safari takes the
   pinch for itself and zooms the whole page instead of the chart. */
.ch-stage {
    position: relative; overflow: hidden; touch-action: none;
    height: min(78vh, 1000px);
    border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md);
    background: var(--color-bg-surface);
    background-image: radial-gradient(var(--color-border-subtle) 1px, transparent 1px);
    background-size: 22px 22px;
}
.chart-svg { display: block; width: 100%; height: 100%; cursor: grab; }
.chart-svg:active { cursor: grabbing; }

.ch-empty { display: flex; flex-direction: column; align-items: center; justify-content: center;
            height: 100%; gap: var(--space-3); color: var(--color-text-muted); text-align: center; }
.ch-empty__icon { font-size: 40px; }

/* ── The node, drawn as rect + text (never foreignObject) ─────────────────── */
.chart-node { cursor: pointer; }
.cn-box {
    fill: var(--color-bg-surface); stroke: var(--color-border);
    stroke-width: 1; transition: stroke var(--transition-fast);
}
.chart-node:hover  .cn-box { stroke: var(--color-accent); stroke-width: 2; }
.chart-node:focus  { outline: none; }
.chart-node:focus-visible .cn-box { stroke: var(--color-primary); stroke-width: 2.5; }

/* Sex tints the LEFT EDGE only. A fully coloured box turns a 180-node chart into
   a colour chart, and the name is the thing that has to be readable. */
.chart-node--m .cn-box { fill: color-mix(in srgb, var(--color-info-bg) 55%, var(--color-bg-surface)); }
.chart-node--f .cn-box { fill: color-mix(in srgb, var(--color-danger-bg) 40%, var(--color-bg-surface)); }

.cn-name   { fill: var(--color-text); }
.cn-dates  { fill: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.cn-place  { fill: var(--color-text-muted); }
.cn-sex    { fill: var(--color-text-muted); }
.cn-hidden { fill: var(--color-text-muted); font-style: italic; }

/* A living person's node is marked, not hidden. The chart is meant to show that
   they are there — it withholds who they are, which the server already did. */
.chart-node--living .cn-box { stroke-dasharray: 3 2; }
.cn-living-mark { fill: var(--color-text-muted); }

.chart-edge { fill: none; stroke: var(--color-border); stroke-width: 1.5; }
.chart-edge--spouse { stroke: var(--color-text-muted); stroke-width: 1; }
.chart-marr { fill: var(--color-text-muted); }

/* ── Node menu ─────────────────────────────────────────────────────────────── */
.ch-menu {
    position: fixed; z-index: 1200; min-width: 208px;
    background: var(--color-bg-surface); border: 1px solid var(--color-border);
    border-radius: var(--radius-md); box-shadow: 0 8px 24px rgba(0,0,0,.18);
    padding: var(--space-2); display: flex; flex-direction: column; gap: 2px;
}
.ch-menu__name {
    font-weight: 600; font-size: var(--font-size-sm);
    padding: var(--space-2) var(--space-2) var(--space-2);
    border-bottom: 1px solid var(--color-border-subtle); margin-bottom: var(--space-1);
    display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
}
.ch-menu__item {
    text-align: left; padding: var(--space-2) var(--space-3); border: 0; cursor: pointer;
    background: transparent; border-radius: var(--radius-sm);
    font-size: var(--font-size-sm); color: var(--color-text);
}
.ch-menu__item:hover:not(:disabled) { background: var(--color-bg-subtle); }
.ch-menu__item:disabled { color: var(--color-text-muted); cursor: default; }

.ch-hits { margin-top: var(--space-3); display: flex; flex-direction: column; gap: var(--space-1);
           max-height: 46vh; overflow-y: auto; }

/* ── Print ────────────────────────────────────────────────────────────────────
   The stage's fixed height and clipping exist for the screen; on paper they are
   exactly wrong. Releasing both lets the viewBox — the true content bounds —
   scale the whole chart onto the sheet. charts.js drops the pan/zoom transform
   on beforeprint and restores it after, so what prints is the chart and not
   whichever quarter of it happened to be visible. */
@media print {
    .ch-toolbar, .ch-notice, .ch-menu { display: none !important; }
    .ch-stage {
        height: auto !important; overflow: visible !important;
        border: 0 !important; background: none !important;
    }
    .chart-svg { width: 100% !important; height: auto !important; max-height: 100vh; }
    .cn-box       { fill: #fff !important; stroke: #444 !important; }
    .cn-name      { fill: #000 !important; }
    .cn-dates, .cn-place, .cn-sex, .cn-hidden, .chart-marr { fill: #333 !important; }
    .chart-edge   { stroke: #666 !important; }
    .cn-living-mark { fill: #888 !important; }
}
/* The count sits at the end of the toolbar rather than in a panel header — see the
   note in charts.js shell(). On a narrow screen it wraps onto its own line. */
.ch-summary { flex-basis: 100%; text-align: right; }
@media (min-width: 900px) { .ch-summary { flex-basis: auto; margin-left: var(--space-2); } }
/* The hint under "Descendants from here" — the menu item that answers "where are all
   the siblings?", which a pedigree structurally cannot show. */
.ch-menu__item { display: flex; flex-direction: column; gap: 1px; }
.ch-menu__hint { font-size: 10px; color: var(--color-text-muted); }

/* Duplicate records — marked, described, and collapsible. Her tree records some people
   more than once; the chart says so rather than silently drawing them twice. */
.cn-dupe { fill: var(--color-accent); font-weight: 700; }
.chart-node--dupe .cn-box { stroke: var(--color-accent); stroke-dasharray: 5 2; }
.ch-merge { display: flex; align-items: center; gap: var(--space-2);
            font-size: var(--font-size-sm); color: var(--color-text-secondary);
            cursor: pointer; white-space: nowrap; }
.ch-menu__dupe { font-size: var(--font-size-xs); color: var(--color-text-secondary);
                 background: var(--color-warning-bg); border-radius: var(--radius-sm);
                 padding: var(--space-2); margin-bottom: var(--space-1);
                 display: flex; flex-direction: column; gap: 2px; }
@media print { .cn-dupe { fill: #000 !important; } }
