/* ============================================================================
   site-nav.css — the left sidebar. Rendered by /site-nav.js, which is the ONE
   place the page list lives. Every page links both; no page hand-copies a nav.

   Uses only tokens from /css/tokens.css. No hex literals here.
   ============================================================================ */

:root { --nav-w: 210px; --nav-w-collapsed: 52px; }

/* The rail is fixed, and body is inset by its width. That keeps every page's
   existing layout (sticky topbars included) intact — sticky positioning
   respects the padding box, so nothing needed restructuring.

   `html body`, not `body`, and for the same reason `body header` is written
   that way further down: five pages (the old .header-bar set — copy corpus,
   cross-sell lookup, maintenance, tea sheets, user access) declare
   `body { … padding: 0 … }` in an inline <style> that loads after this file,
   and at equal specificity source order gave them the inset. Their content
   happened to look fine because each centred its own <main> with `margin: 0
   auto`, so the miss only showed once a full-width header arrived and rendered
   underneath the rail. One extra element outranks all five without depending
   on load order. */
html body { padding-left: var(--nav-w); transition: padding-left 0.14s ease; }
html body.nav-collapsed { padding-left: var(--nav-w-collapsed); }

/* The rail scrolls, and it has to say so.
   ---------------------------------------------------------------------------
   Measured 2026-08-17: 1151px of navigation in an 893px viewport for an admin.
   Admin and Reference — including Guide and Notifications — sat below the fold
   on every page with nothing on screen indicating there was more, so two whole
   groups were invisible to anyone who had not thought to scroll a sidebar.

   What remains here is the TOP half of a layered-background scroll shadow: a
   `local` cover gradient that rides with the content plus a `scroll` radial
   pinned to the frame, so a shadow shows only while something sits past the
   top edge. Its bottom twin was deleted 2026-08-21 — it painted at the
   scrollport edge, permanently underneath the opaque sticky `.sidenav-foot`,
   and in the dark theme a black radial over a near-black panel read as
   nothing. "More below" is now the `can-down` content fade on the footer
   (see `.sidenav-foot::before` further down).

   The rgba black is the one thing in this file that is not a token, for the
   same reason `.sidenav-scrim` below already uses one: a shadow is not a
   surface colour, and there is no shade token that would mean anything here. */
.sidenav {
  position: fixed; top: 0; left: 0; bottom: 0; width: var(--nav-w);
  display: flex; flex-direction: column;
  border-right: 1px solid var(--border);
  z-index: 900;
  overflow-y: auto; overflow-x: hidden;
  transition: width 0.14s ease;
  font-family: var(--font-ui);
  background:
    linear-gradient(var(--bg-card) 30%, rgba(0,0,0,0)) top    / 100% 22px no-repeat local,
    radial-gradient(farthest-side at 50% 0,    rgba(0,0,0,0.38), rgba(0,0,0,0)) top    / 100% 10px no-repeat scroll,
    var(--bg-card);
}
body.nav-collapsed .sidenav { width: var(--nav-w-collapsed); }

.sidenav-brand {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-4) var(--space-4) var(--space-3);
  font-family: var(--font-display);
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--gold); text-decoration: none; white-space: nowrap;
}
.sidenav-brand .mark {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: var(--radius-pill);
  border: 1px solid var(--gold-border); background: var(--gold-bg);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; letter-spacing: 0;
}

/* The sidebar owns the wordmark now, so the in-page header's copy of it is
   redundant. Hidden here rather than deleted from 26 files — and it comes back
   below 860px, where the sidebar is off-canvas and the header is all you see. */
@media (min-width: 861px) { .topbar header .brand { display: none; } }

.sidenav-group { padding: var(--space-2) 0 0; }
.sidenav-group > h3 {
  margin: 0; padding: var(--space-2) var(--space-4) var(--space-1);
  font-size: 10px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
.sidenav a.item {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 7px var(--space-4); text-decoration: none;
  color: var(--muted); font-size: 13px; line-height: 1.3;
  border-left: 2px solid transparent; white-space: nowrap;
}
.sidenav a.item:hover { color: var(--text); background: var(--bg-row); }
.sidenav a.item.active {
  color: var(--gold); border-left-color: var(--gold); background: var(--gold-bg);
}
.sidenav a.item .ico {
  flex: 0 0 auto; width: 16px; text-align: center; font-size: 13px; opacity: 0.9;
}
.sidenav a.item .lbl { overflow: hidden; text-overflow: ellipsis; }

/* Collapsed: icons only. Labels and group headings are hidden rather than
   removed, so nothing reflows and the DOM stays identical either way. */
body.nav-collapsed .sidenav .lbl,
body.nav-collapsed .sidenav-group > h3,
body.nav-collapsed .sidenav-brand span:not(.mark),
body.nav-collapsed .sidenav-foot .lbl { display: none; }
body.nav-collapsed .sidenav a.item { justify-content: center; padding-left: 0; padding-right: 0; }
body.nav-collapsed .sidenav-brand { justify-content: center; padding-left: 0; padding-right: 0; }
body.nav-collapsed .sidenav-group { border-top: 1px solid var(--border); margin-top: var(--space-2); }
body.nav-collapsed .sidenav-group > h3 { padding: 0; }

/* Sticky, so Collapse is reachable without scrolling to the end of a rail that
   is 260px taller than the window. `margin-top: auto` still pushes it down when
   the list is SHORT (a `view` user sees about half these items); the sticky
   bottom takes over when the list is long enough to scroll. Opaque background,
   or the items scrolling underneath show through it. */
.sidenav-foot {
  margin-top: auto;
  position: sticky; bottom: 0;
  background: var(--bg-card);
  padding: var(--space-2) 0 var(--space-3);
  border-top: 1px solid var(--border);
}
/* "More below." The layered-background scroll shadow above turned out to be
   invisible exactly where it mattered: its bottom copy paints at the
   scrollport edge, which this opaque sticky footer covers, and in the dark
   theme a black radial over a near-black panel reads as nothing. So the cue
   is a fade of the items themselves into the footer — theme-proof, because it
   dims content rather than darkening a dark surface. site-nav.js toggles
   `can-down` on scroll, resize, role reveal and collapse. */
.sidenav-foot::before {
  content: '';
  position: absolute; top: -20px; left: 0; right: 0; height: 20px;
  background: linear-gradient(rgba(0, 0, 0, 0), var(--bg-card));
  opacity: 0; transition: opacity 0.14s ease;
  pointer-events: none;
}
.sidenav.can-down .sidenav-foot::before { opacity: 1; }
/* Every property is stated, including the ones that look like defaults.
   Ten pages declare a bare `button { … }` in an inline <style> that loads after
   this file — a gold fill, 10px padding, uppercase — and a bare element
   selector reaches into shared chrome. That is why this control read COLLAPSE
   on user access and Collapse everywhere else, and why the theme toggle read
   DARK on customers. Naming the properties here settles it at the source
   instead of leaving each page to write `text-transform: none` back. */
.sidenav-toggle {
  width: 100%; background: transparent; border: 0; cursor: pointer;
  display: flex; align-items: center; gap: var(--space-3);
  padding: 7px var(--space-4); color: var(--muted);
  font-family: inherit; font-size: 12px;
  text-transform: none; letter-spacing: normal; font-weight: 400;
  border-radius: 0;
}
.sidenav-toggle:hover { color: var(--gold); }
body.nav-collapsed .sidenav-toggle { justify-content: center; padding-left: 0; padding-right: 0; }

/* Role-gated items start hidden and are revealed by site-nav.js once auth
   resolves. Hidden-by-default means a `view` user never sees an admin link
   flash on screen before it's removed. */
.sidenav a.item[data-min-role] { display: none; }
.sidenav a.item[data-min-role].allowed { display: flex; }
.sidenav-group.empty { display: none; }

/* ---- narrow screens: off-canvas drawer instead of a rail ----
   These two defaults MUST come before the media query, not after it: same
   specificity means source order decides, and a trailing `display:none` would
   beat the query and leave the drawer with no way to open. */
.sidenav-scrim { display: none; }
.sidenav-burger { display: none; }

@media (max-width: 860px) {
  html body, html body.nav-collapsed { padding-left: 0; }
  /* Below this width the rail becomes a drawer, and the collapsed/expanded
     state stops meaning anything — it's either off-canvas or fully open. Each
     state is written at the same specificity and ordered closed-then-open, so
     which one wins never depends on reading a selector list carefully. */
  body .sidenav,
  body.nav-collapsed .sidenav {
    width: var(--nav-w);
    transform: translateX(-100%);
    transition: transform 0.16s ease;
  }
  body.nav-open .sidenav,
  body.nav-open.nav-collapsed .sidenav { transform: translateX(0); }
  body.nav-open .sidenav .lbl,
  body.nav-open.nav-collapsed .sidenav .lbl,
  body.nav-open .sidenav-group > h3,
  body.nav-open.nav-collapsed .sidenav-group > h3 { display: block; }
  body.nav-open .sidenav a.item,
  body.nav-open.nav-collapsed .sidenav a.item { justify-content: flex-start; padding-left: var(--space-4); }
  .sidenav-scrim {
    display: block; position: fixed; inset: 0; z-index: 899;
    background: rgba(0,0,0,0.45); opacity: 0; pointer-events: none;
    transition: opacity 0.16s ease;
  }
  body.nav-open .sidenav-scrim { opacity: 1; pointer-events: auto; }
  .sidenav-burger {
    display: flex; align-items: center; justify-content: center;
    position: fixed; top: 10px; left: 10px; z-index: 901;
    width: 34px; height: 34px;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: var(--radius); color: var(--gold);
    font-size: 15px; cursor: pointer; font-family: inherit;
  }
  /* The in-page header has to clear the burger. `body header`, not `header`:
     every page's own stylesheet loads AFTER this file and most re-declare
     `header { padding: … }` (two even do it inside their own media queries),
     so a bare element selector here silently loses and the burger lands on
     top of the wordmark. One extra element of specificity outranks them all
     without caring about load order. */
  body header { padding-left: 56px; }
  /* The header's session area can carry a name, role pill, and half a dozen
     links (admins get the most). None of it wraps by default, so on a phone
     it runs off the right edge — Sign out ends up unreachable and the whole
     page gains a horizontal pan. Wrapping keeps every control on screen. */
  body header, body header > div, body header .session { flex-wrap: wrap; }
}

@media print { .sidenav, .sidenav-burger, .sidenav-scrim { display: none; } html body { padding-left: 0; } }
