/* ═════════════════════════ APP SHELL & SCREENS ════════════════════════════ */

.app {
  display: grid;
  /* + var(--float-gap): the topbar now floats with a top margin on every
     screen size (not just mobile — see .topbar below), so its grid row
     needs to be tall enough to still clear the content underneath it. */
  /* Header row + the stage. There is no breadcrumb row any more — whose
     branch you're viewing is shown by the floating .tree-focus-pill over
     the canvas instead (matching the mockup), which costs no layout row. */
  grid-template-rows: calc(var(--topbar-h) + var(--safe-t) + var(--float-gap)) 1fr;
  /* dvh first would be ignored by engines that don't understand it — vh as
     the fallback, dvh overriding it wherever it's supported (handles the
     iOS Safari/PWA-standalone "100vh taller than the real viewport" bug). */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* ── Screens (boot / login) ──────────────────────────────────────────────── */
.screen {
  position: fixed;
  inset: 0;
  z-index: var(--z-screen);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 18px;
  padding: 24px;
  background: var(--stage-bg);
  /* Scrollable, NOT overflow:hidden. The login card can easily be taller
     than the viewport (demo-mode account list, or the register form with
     every field open), and with `overflow: hidden` its top and bottom were
     simply cut off and unreachable — no scrollbar, no way to get at the
     submit button. */
  overflow-y: auto;
  overflow-x: hidden;
}
.screen--boot .boot-mark { color: var(--gold); animation: breathe 2.2s var(--ease-soft) infinite; }
.boot-text { color: var(--text-dim); font-size: var(--fs-sm); letter-spacing: .04em; }
@keyframes breathe { 0%,100% { opacity: .45; transform: scale(.96);} 50% { opacity: 1; transform: scale(1.04);} }

/* Two large blurred colour blobs behind the login card — the mockup's
   "aurora" background. Pure decoration (z-index: 0, under the card).

   Deliberately NOT animated. The mockup spins these, but a solid-colour
   circle is radially symmetric, so rotating it produces no visible change
   whatsoever — while forcing the browser to re-rasterize a 420px element
   through a 90px blur on every single frame, forever. That's the single
   most expensive thing you can hand a GPU (it's why backdrop-filter is
   stripped on mobile further down this file), spent on an effect nobody
   can see. Static blobs look identical and cost nothing after first paint.

   `fixed`, not `absolute`, so they're anchored to the viewport and never
   add to the scroll height of a login card that's already overflowing. */
.screen--login::before,
.screen--login::after {
  content: "";
  position: fixed;
  width: 420px; height: 420px;
  border-radius: 50%;
  filter: blur(90px);
  z-index: 0;
  pointer-events: none;
}
.screen--login::before {
  top: 8%; left: 10%;
  background: rgba(245,158,11,.28);
}
.screen--login::after {
  bottom: 8%; right: 10%;
  background: rgba(14,165,233,.28);
}
:root[data-theme="light"] .screen--login::before,
:root[data-theme="light"] .screen--login::after { opacity: .8; }

/* This is a COLUMN flex container, so the vertical axis is controlled by
   justify-content — and `justify-content: center` clips a too-tall item at
   its TOP edge, leaving the overflow unreachable even once the container
   scrolls. flex-start + `margin-block: auto` gives the behaviour we
   actually want: perfectly centred while the card fits, and scrolled from
   the top the moment it doesn't (auto margins collapse to 0 with no free
   space). align-items stays `center` — that's the horizontal centring. */
.screen--login { justify-content: flex-start; }
.screen--login .login-card { margin-block: auto; }

.login-card {
  position: relative;
  z-index: 1;
  width: min(440px, 100%);
  padding: 34px 28px 26px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-4);
  text-align: center;
  animation: rise var(--t-base) var(--ease-out) both;
}
@keyframes rise { from { opacity: 0; transform: translateY(14px); } }
.login-mark { color: var(--gold); display: flex; justify-content: center; margin-bottom: 10px; }
.login-title { font-size: var(--fs-xl); font-weight: 800; font-family: var(--font-display); letter-spacing: var(--tracking-display); }
.login-sub { color: var(--text-muted); margin: 6px 0 22px; font-size: var(--fs-base); }
.login-note { margin-top: 14px; font-size: var(--fs-xs); color: var(--text-dim); }
.login-legal { margin-top: 18px; font-size: var(--fs-xs); color: var(--text-dim); }

.login-phone-form { margin-top: 18px; text-align: left; }
.login-phone-form .field { margin-bottom: 14px; }
.login-phone-error {
  margin: -6px 0 14px;
  padding: 9px 12px;
  border-radius: var(--r-md);
  background: var(--danger-soft);
  color: var(--danger);
  font-size: var(--fs-sm);
  text-align: center;
}
.login-switch {
  margin-top: 16px;
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.login-switch__link {
  color: var(--gold);
  font-weight: 700;
  margin-left: 5px;
}
.login-switch__link:hover { text-decoration: underline; }

.login-context {
  display: flex;
  align-items: center;
  gap: 12px;
  text-align: left;
  padding: 12px;
  margin-bottom: 18px;
  border-radius: var(--r-lg);
  background: var(--surface-2);
  border: 1px solid var(--line);
}
.login-context__text { font-size: var(--fs-sm); color: var(--text-muted); line-height: 1.35; }
.login-context__text strong { display: block; color: var(--text); font-size: var(--fs-md); }

.login-demo { margin-top: 22px; padding-top: 18px; border-top: 1px dashed var(--line); text-align: left; }
.login-demo__title { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: 10px; }
.login-demo__grid { display: grid; gap: 8px; }
.demo-account {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 9px 11px;
  border-radius: var(--r-md);
  background: var(--surface-2);
  border: 1px solid var(--line);
  text-align: left;
  transition: border-color var(--t-fast), background var(--t-fast), transform var(--t-fast);
}
.demo-account:hover { border-color: var(--gold); background: var(--surface-3); transform: translateY(-1px); }
.demo-account__name { font-weight: 600; font-size: var(--fs-sm); }
.demo-account__meta { display: flex; align-items: center; gap: 4px; font-size: var(--fs-xs); color: var(--text-dim); }
.demo-account__meta svg { flex: none; }

/* ── Top bar ──────────────────────────────────────────────────────────────────
   A floating glass pill, matching the mockup's header — inset from the
   screen edge with a full rounded border on every screen size, not just
   mobile (this used to be a flush, full-bleed bar on desktop specifically;
   the float/radius/shadow below now applies everywhere, and the mobile
   query further down only tightens the gap and drops the blur for perf). */
.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 16px;
  box-sizing: border-box;
  margin: calc(var(--safe-t) + var(--float-gap)) var(--float-gap) 0;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-3);
  z-index: var(--z-topbar);
}
.topbar__brand {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--gold);
  flex: none;
}

/* ── Brand logo (assets/icons/*Logo.svg) ──────────────────────────────────────
   The files are flat charcoal artwork (#231f20) — right for a light
   background. Dark theme (the app's default) inverts them to read as
   light/white instead; light theme leaves them untouched. */
.brand-logo { display: block; width: auto; filter: invert(1) brightness(1.35); }
:root[data-theme="light"] .brand-logo { filter: none; }
.brand-logo--icon { flex: none; }
.brand-logo--full { flex: none; }
.topbar__actions { display: flex; align-items: center; gap: 4px; margin-left: auto; flex: none; }
.iconbtn--avatar:hover { background: transparent; }

/* ── Guardian badge ──────────────────────────────────────────────────────────
   Sits beside the logo as a two-line "who looks after this tree" block
   (name above, rank below), clickable by anyone to jump to them — matching
   the mockup's header, where this is the left-hand identity, not a chip
   tucked in with the action icons on the right. */
.guardian-badge {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;
  padding: 4px 10px;
  border-radius: var(--r-md);
  background: transparent;
  border: 0;
  text-align: left;
  white-space: nowrap;
  min-width: 0;
  transition: background var(--t-fast);
}
.guardian-badge:hover { background: var(--surface-2); }
.guardian-badge__name {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: var(--tracking-display);
  color: var(--text);
  line-height: 1.15;
  max-width: 150px;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--t-fast);
}
.guardian-badge:hover .guardian-badge__name { color: var(--gold); }
.guardian-badge__rank {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--gold);
  line-height: 1.1;
}
.topbar__left { display: flex; align-items: center; gap: 8px; min-width: 0; flex: none; }
.topbar__divider { width: 1px; height: 22px; background: var(--line); flex: none; margin: 0 2px; }

/* ── Search ────────────────────────────────────────────────────────────────
   Right-aligned in the header (grouped with the action icons), not a
   centre-stretched bar — matches the mockup. On mobile it collapses to the
   icon button beside it and opens as a full-width overlay. */
.search {
  position: relative;
  flex: none;
  width: 240px;
}
.topbar__search-btn { display: none; }
.search__icon { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); color: var(--text-dim); pointer-events: none; }
.search__input {
  width: 100%;
  height: 38px;
  padding: 0 40px 0 36px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 500;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.search__input::placeholder { color: var(--text-dim); }
.search__input:focus { outline: none; border-color: var(--gold); background: var(--bg-elevated); }
.search__input::-webkit-search-cancel-button { display: none; }
.search__kbd {
  position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
  font-size: 11px; color: var(--text-dim);
  border: 1px solid var(--line); border-radius: 5px;
  padding: 1px 5px; pointer-events: none;
}
.search__input:focus ~ .search__kbd { display: none; }
.search__results {
  position: absolute;
  top: calc(100% + 8px); left: 0; right: 0;
  max-height: min(56vh, 420px);
  overflow-y: auto;
  padding: 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  z-index: var(--z-menu);
}
.search-result {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 8px 10px;
  border-radius: var(--r-md);
  text-align: left;
  transition: background var(--t-fast);
}
.search-result:hover, .search-result.is-active { background: var(--surface-3); }
.search-result__name { font-weight: 600; font-size: var(--fs-sm); }
.search-result__meta { font-size: var(--fs-xs); color: var(--text-dim); }
.search-result mark { background: rgba(245,158,11,.28); color: var(--gold); border-radius: 3px; }
.search-empty { padding: 18px 12px; text-align: center; color: var(--text-dim); font-size: var(--fs-sm); }

/* ── "Branch of" focus pill ──────────────────────────────────────────────────
   Replaces the old breadcrumb row entirely. Same job — telling you whose
   branch you're looking at — but floating over the canvas the way the
   mockup does it, instead of taking a whole layout row of vertical space
   above the tree. */
.tree-focus-pill {
  position: absolute;
  top: 14px; left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  box-shadow: var(--sh-2);
  z-index: var(--z-controls);
  pointer-events: none;
  max-width: calc(100% - 28px);
}
.tree-focus-pill > svg { color: var(--accent-amber); flex: none; }
.tree-focus-pill__label {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
  flex: none;
}
.tree-focus-pill__name {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: var(--tracking-display);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tree-focus-pill__depth {
  flex: none;
  font-size: 10px;
  font-weight: 800;
  color: var(--gold);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
  padding: 2px 9px;
  border-radius: var(--r-pill);
}
.viewing-as {
  position: absolute;
  top: 58px; left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-controls);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--warn);
  background: var(--warn-soft);
  padding: 4px 12px;
  border-radius: var(--r-pill);
}

/* ── Stage & canvas ──────────────────────────────────────────────────────── */
.stage {
  position: relative;
  overflow: hidden;
  background: var(--stage-bg);
  touch-action: none;
  cursor: grab;
  z-index: var(--z-canvas);
}
.stage.is-panning { cursor: grabbing; }
.canvas {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  transform-origin: 0 0;
  will-change: transform;
}
.connectors { position: absolute; overflow: visible; pointer-events: none; z-index: var(--z-tree-line); }
.nodes { position: absolute; top: 0; left: 0; z-index: var(--z-node); }

.stage-overlay {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--text-dim);
  pointer-events: none;   /* the overlay's own text/background never blocks clicks… */
}
.stage-overlay > * { pointer-events: auto; }  /* …but its content (buttons) still works */

/* pull-to-refresh */
.ptr {
  position: absolute;
  top: 6px; left: 50%;
  transform: translate(-50%, -50px);
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--sh-2);
  display: grid; place-items: center;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.ptr__spinner {
  width: 15px; height: 15px;
  border: 2px solid var(--line-strong);
  border-top-color: var(--gold);
  border-radius: 50%;
}
.ptr.is-spinning .ptr__spinner { animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Control bar (desktop, bottom-LEFT) ───────────────────────────────────────
   The mockup splits the bottom of the screen in two: a "how am I viewing
   this" toolbar on the left (card size, how many levels, zoom) and the
   primary actions on the right. This is the left half. */
.controls {
  position: absolute;
  left: 24px;
  bottom: 24px;
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  z-index: var(--z-controls);
}
.ctl-group { display: flex; align-items: center; gap: 2px; }
.ctl-divider { width: 1px; height: 22px; background: var(--line); margin: 0 4px; flex: none; }

.ctl {
  min-width: 34px; height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 8px;
  border-radius: var(--r-md);
  color: var(--text-dim);
  font-size: var(--fs-base);
  font-weight: 600;
  transition: background var(--t-fast), color var(--t-fast);
}
.ctl:hover { background: var(--surface-2); color: var(--text); }
/* The currently-selected card size reads as a raised, filled chip. */
.ctl--icon.is-active {
  background: var(--bg-elevated);
  color: var(--text);
  box-shadow: var(--sh-1);
}
.ctl__zoom { min-width: 46px; text-align: center; font-size: var(--fs-sm); font-weight: 700; color: var(--text-muted); font-variant-numeric: tabular-nums; }

.ctl--level {
  gap: 7px;
  padding: 0 10px;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
}
.ctl--level > svg:first-child { color: var(--accent-amber); }
.ctl--level__caret { color: var(--text-dim); }

/* ── Primary actions (desktop, bottom-RIGHT) ─────────────────────────────── */
.stage-actions {
  position: absolute;
  right: 24px;
  bottom: 24px;
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: var(--z-controls);
}
.stage-action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 44px;
  padding: 0 18px;
  border-radius: var(--r-lg);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  box-shadow: var(--sh-3);
  transition: transform var(--t-fast) var(--ease-out), background var(--t-fast), color var(--t-fast);
}
.stage-action:hover { transform: translateY(-2px) scale(1.03); }
.stage-action:hover svg { color: var(--accent-amber); }
.stage-action:active { transform: scale(.97); }
.stage-action--primary {
  background: var(--btn-primary-bg);
  color: var(--btn-primary-text);
  border-color: transparent;
}
.stage-action--primary:hover { background: var(--btn-primary-bg-hover); }
.stage-action--primary:hover svg { color: inherit; }

/* ── Mobile zoom pill + bottom nav ───────────────────────────────────────── */
.mobile-zoom { display: none; }
.bottomnav { display: none; }
.bottomnav-fade { display: none; }

@media (max-width: 720px) {
  /* Both bars float as inset, rounded islands (iOS-style) instead of
     flush full-bleed strips — this token is the gap from the screen edge
     (and from each other) that both share. Custom properties can't be
     declared directly inside an @media block without a selector, hence
     the :root wrapper. */
  :root { --float-gap: 10px; }
  .app {
    grid-template-rows: calc(var(--topbar-h) + var(--safe-t) + var(--float-gap)) 1fr auto;
  }
  .brand-logo--full { display: none; }
  .topbar__divider { display: none; }
  .guardian-badge__name { max-width: 110px; }

  /* Search collapses to the icon button; the field itself becomes a
     full-width overlay when opened (see the .search.is-open block at the
     bottom of this file). */
  .topbar__search-btn { display: inline-flex; }
  .search { display: none; }
  .search.is-open { display: block; width: auto; }
  .search__kbd { display: none; }

  /* backdrop-filter is one of the most expensive things a phone GPU can be
     asked to do, and these bars all sit fixed directly over the tree
     canvas — panning behind them forces a live re-blur on every single
     frame. That's what was showing up as stutter and missed taps while
     dragging. A flatter, still-translucent background keeps the look
     without asking for a recomputed blur 60 times a second. */
  .topbar, .controls, .bottomnav, .mobile-zoom, .tree-focus-pill {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .topbar { background: var(--surface); }
  .controls { background: var(--surface); }
  .bottomnav { background: var(--surface); }
  .tree-focus-pill { background: var(--surface); }

  /* The focus pill sits just under the floating header, and the desktop
     bottom-right action group is replaced by the bottom rail. */
  .tree-focus-pill { top: 10px; padding: 6px 12px; }
  .tree-focus-pill__label { display: none; }
  .viewing-as { top: 50px; }
  .stage-actions { display: none; }

  /* ── Top bar — floating island ─────────────────────────────────────────
     margin-top is env(safe-area-inset-top) + the flat gap, so the bar
     always clears the notch/Dynamic Island on whichever device it's on
     (0 extra inset on an ordinary phone, more on a notched one) rather
     than a fixed pixel guess. */
  .topbar {
    margin: calc(var(--safe-t) + var(--float-gap)) var(--float-gap) 0;
    padding: 8px 14px;
    border-radius: var(--r-xl);
    border: 1px solid var(--line-soft);
    box-shadow: var(--sh-3);
  }

  /* Bottom-edge clearance only — a few px so the pill isn't glued to the
     home-indicator/gesture-bar area, nowhere near the full --float-gap
     used everywhere else (that was leaving the nav floating noticeably
     higher off the bottom edge than it needs to). Same :root-wrapper
     requirement as --float-gap above — a custom property declared bare
     inside an @media block with no selector is invalid CSS and is
     silently dropped, which is exactly what was happening here before:
     .controls's bottom calc() below was resolving var(--nav-bottom-gap)
     to nothing, quietly breaking the whole calc() and leaving the
     toolbar's mobile position wrong. */
  :root { --nav-bottom-gap: 4px; }

  /* Some mobile browsers report an inflated env(safe-area-inset-bottom)
     for their own on-screen gesture pill, and on top of that some already
     shrink 100dvh to exclude that same gesture-bar area — stacking the
     two double-counts the clearance and pushes the floating nav up with a
     noticeably large dead strip of plain background beneath it. A first
     pass here capped this at 20px and it was still reported as too much
     on a real device, so it's capped tighter still — small enough to stay
     clear of a genuine home-indicator/gesture pill without adding a
     second helping of space the viewport height may already have carved
     out. */
  :root { --safe-b-capped: min(var(--safe-b), 10px); }

  /* The desktop view-toolbar is replaced on mobile by the rail's own
     "Levels" button plus the small floating zoom pill below. */
  .controls { display: none; }

  .mobile-zoom {
    display: flex;
    align-items: center;
    gap: 4px;
    position: absolute;
    right: var(--float-gap);
    bottom: calc(var(--bottomnav-h) + var(--safe-b-capped) + var(--nav-bottom-gap) + 12px);
    padding: 5px;
    border-radius: var(--r-lg);
    background: var(--surface);
    border: 1px solid var(--line-soft);
    box-shadow: var(--sh-3);
    z-index: var(--z-controls);
  }
  .mobile-zoom__btn {
    width: 32px; height: 32px;
    display: grid; place-items: center;
    border-radius: var(--r-md);
    background: var(--surface-2);
    color: var(--text-muted);
    font-size: var(--fs-base);
    font-weight: 700;
  }
  .mobile-zoom__value {
    min-width: 42px;
    text-align: center;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }

  /* ── Bottom nav — floating island, same treatment as the top bar ────── */
  .bottomnav {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    align-items: center;
    height: var(--bottomnav-h);
    margin: 0 var(--float-gap) calc(var(--safe-b-capped) + var(--nav-bottom-gap));
    border-radius: var(--r-xl);
    /* No blur here — see the backdrop-filter:none rule above this block;
       it's a fixed bar sitting directly over the pannable canvas. */
    background: var(--surface);
    border: 1px solid var(--line-soft);
    box-shadow: var(--sh-3);
    z-index: var(--z-topbar);
  }

  /* The floating pill leaves a gap to the true screen edge (see margin
     above) — that gap was just flat page background, a hard-edged "empty
     solid strip" behind the nav instead of the soft fade a native app's
     tab bar sits on. This sits behind the pill (lower z-index) and fades
     the last stretch of the tree canvas into the page background instead
     of cutting it off sharply. Multiple stops instead of one straight
     ramp — a real gradual fade reads as mostly-clear near the nav and
     only thickens toward the very bottom, not a 50/50 linear blend the
     eye still reads as a harder edge partway down. No backdrop-filter
     here either, same reason as the bar itself: this still sits directly
     over the pannable canvas, and blurring it would re-blur on every
     frame of a drag. */
  .bottomnav-fade {
    display: block;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    height: calc(var(--bottomnav-h) + var(--safe-b-capped) + var(--nav-bottom-gap) + 90px);
    background: linear-gradient(to top,
      var(--bg) 0%, color-mix(in srgb, var(--bg) 85%, transparent) 20%,
      color-mix(in srgb, var(--bg) 45%, transparent) 50%, transparent 100%);
    pointer-events: none;
    z-index: calc(var(--z-topbar) - 0.5);
  }

  .bottomnav__btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    height: calc(100% - 10px);
    margin: 5px 4px;
    border-radius: var(--r-md);
    color: var(--text-muted);
    font-size: 9px;
    font-weight: 700;
    transition: color var(--t-fast), background var(--t-fast);
  }
  .bottomnav__btn svg { width: 20px; height: 20px; }
  .bottomnav__btn:active { color: var(--gold); }
  .bottomnav__btn[data-nav="level"] svg { color: var(--accent-amber); }
  /* The mockup's Add is a solid filled tile in the rail, not a floating
     circular FAB — same theme-inverted colour as every other primary. */
  .bottomnav__btn--primary {
    background: var(--btn-primary-bg);
    color: var(--btn-primary-text);
  }
  .bottomnav__btn--primary svg { color: inherit; }
  .bottomnav__btn--primary:active { color: var(--btn-primary-text); }
}

/* ── Search overlay on mobile ────────────────────────────────────────────── */
@media (max-width: 720px) {
  .search.is-open {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: var(--z-menu);
    /* padding-top includes the safe area — inset:0 alone pins this flush
       to the true top edge of the screen, ignoring the notch/status-bar
       area every other floating surface in the app already clears (see
       .topbar's own margin above), so the search field sat partly behind
       the status bar. */
    padding: calc(var(--safe-t) + 10px) 10px 10px;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
  }
  .search.is-open .search__results { left: 10px; right: 10px; max-height: 70vh; }
}
