/* ═══════════════════════════════════════════════════════════════════════════════════════
   APP SHELL — side nav + top bar, shared
   Used by:  index.html (Routes)  ·  voodoo/schedule.html (Schedule)

   Lifted from voodoo/schedule.html, which is the version both pages now run. The Routes page
   previously had its own shell — a 52px collapsible icon rail plus a mobile drawer — that had
   drifted far enough to be a different implementation rather than a copy (22 of 28 shared
   selectors disagreed). That is gone; this is the single source.

   The schedule scoped much of this under .device-frame, which the Routes page has no
   equivalent of, so the scope is stripped here and the rules apply per page.

   Markup comes from shared/app-shell.js — each page leaves a placeholder where its shell used
   to sit, so the sidebar stays the first flex child and the top bar stays above the content.
   ═══════════════════════════════════════════════════════════════════════════════════════ */
/* Focus is black everywhere, never the browser's blue. :where() keeps this at zero
   specificity, so any control's own focus style still wins; it only replaces Chrome's
   default blue ring on elements that have none. */
:where(:focus-visible) { outline: 2px solid #1A1919; outline-offset: 2px; }


/* ── Reserved boxes, for the frame before the shell mounts ──────────────────────────────
   The markup is injected on DOMContentLoaded, so for one paint the placeholders are zero-size
   and the page's own content — on Routes, the map — fills the space the nav and top bar are
   about to occupy. Navigating between the two pages flashed it every time.

   These give each placeholder the exact box its replacement will have, opaque and in the
   right place, so the first paint already looks like the shell is there. Geometry matches the
   real rules below; if one changes, change both. */
/* Cross-document view transitions: the sidebar and topbar appear frozen while the page
   content swaps. Chrome 126+; no-ops gracefully in other browsers. */
@view-transition { navigation: auto; }

[data-app-shell="sidebar"] {
  width: 208px; min-width: 208px; flex-shrink: 0; height: 100%;
  background: #fff; border-right: 1px solid var(--border);
  view-transition-name: app-sidebar;
}
[data-app-shell="topbar"] {
  height: 56px; min-height: 56px; flex-shrink: 0;
  background: #fff; box-shadow: 0 1px 0 var(--border);
  view-transition-name: app-topbar;
}

.app-sidebar { width: 208px; min-width: 208px; background: white; border-right: 1px solid var(--border); display: flex; flex-direction: column; height: 100%; flex-shrink: 0; z-index: 2; overflow: hidden; view-transition-name: app-sidebar; }
.sidebar-header { display: flex; align-items: center; padding: 12px 8px; }
.nav-collapse-btn { width: 28px; height: 28px; border: none; background: transparent; border-radius: var(--radius-control); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; color: var(--fg-hushed); padding: 0; margin-left: auto; flex-shrink: 0; }
.nav-collapse-btn:hover { background: var(--bg-tertiary); color: var(--fg-primary); }
.app-sidebar { transition: width 0.2s ease, min-width 0.2s ease; }
/* 48, not 52. Expanded, an icon's centre sits at 8px (section padding) + 8px (item padding)
   + 8px (half a 16px icon) = 24px. Collapsed, the item and its icon are both centred, so the
   icon's centre is simply half the rail — and only a 48px rail puts that on the same 24px.
   At 52 the icons landed 2px right of where they sit expanded and visibly jumped on toggle. */
.app-sidebar.is-collapsed,
.bp-widescreen .app-sidebar.is-collapsed,
.bp-desktop .app-sidebar.is-collapsed { width: 48px; min-width: 48px; }
.app-sidebar.is-collapsed .nav-text,
.app-sidebar.is-collapsed .nav-chevron,
.app-sidebar.is-collapsed .account-switcher,
.app-sidebar.is-collapsed .nav-item.sub { display: none; }
.app-sidebar.is-collapsed .nav-children { display: none; }
/* Collapsed, the header is still ONE row the height of the logo — it used to become a column
   with the toggle stacked underneath, which added a row and pushed every nav icon down, so the
   icons sat at different heights expanded vs collapsed. Same padding as the expanded header
   keeps the rhythm identical in both states.

   The toggle now lives ON the logo: absolutely centred over it, revealed on hovering the
   header, with the logo fading out underneath. Hovering the mark is how you get the control,
   which is why the header itself is the hover target rather than a 28px button. */
.app-sidebar.is-collapsed .sidebar-header {
  position: relative;
  padding: 12px 8px;
  flex-direction: row; align-items: center; justify-content: center; gap: 0;
}
.app-sidebar.is-collapsed .logo-btn { transition: opacity 0.12s ease; }
.app-sidebar.is-collapsed .sidebar-header:hover .logo-btn { opacity: 0; }
.app-sidebar.is-collapsed .nav-collapse-btn {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  opacity: 0; pointer-events: none;
  transition: opacity 0.12s ease;
}
.app-sidebar.is-collapsed .sidebar-header:hover .nav-collapse-btn,
.app-sidebar.is-collapsed .nav-collapse-btn:focus-visible {
  opacity: 1; pointer-events: auto;
}
/* Vertical metrics must match the expanded nav exactly, or every icon lands on a different
   line between the two states: the section's 2px vertical padding and the item's 32px height
   are the expanded values. Only the horizontal box changes — a 36px-wide square target on a
   52px rail, centred. */
/* Collapsed metrics are derived from PADDING, not from centring, so the icon lands on the
   same 16px it does expanded: 8px section + 8px item. Centring looked equivalent but is not —
   .app-sidebar carries a 1px right border inside its border-box, so the centre of a 48px rail
   is 23.5, and the icons came out half a pixel left of their expanded position.
   The 32px item is border-box, so its 8px padding leaves exactly the icon's 16px. */
.app-sidebar.is-collapsed .nav-section { padding: 2px 8px; align-items: stretch; }
.app-sidebar.is-collapsed .nav-section-bottom { padding-bottom: 12px; }
.app-sidebar.is-collapsed .nav-item { justify-content: flex-start; padding: 4px 8px; gap: 0; width: 32px; height: 32px; border-radius: 8px; margin: 0; }
.app-sidebar.is-collapsed .nav-icon { width: var(--icon-md); height: var(--icon-md); }
.logo-btn { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 8px; border-radius: var(--radius-control); flex-shrink: 0; cursor: pointer; }
.logo-icon { width: 28px; height: 28px; }
.account-switcher { display: flex; align-items: center; gap: 8px; padding: 6px 8px 6px 4px; flex: 1; overflow: hidden; }
.account-name { font-size: 14px; color: var(--fg-hushed); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-section { padding: 2px 8px; display: flex; flex-direction: column; gap: 4px; }
/* The bottom section is pinned to the sidebar's floor, so its 2px was the whole gap between
   Settings and the card edge. */
.nav-section-bottom { padding-bottom: 12px; }
.nav-divider { display: none; }
.nav-item { display: flex; align-items: center; gap: 8px; height: 32px; padding: 4px 8px; border-radius: var(--radius-control); cursor: pointer; overflow: hidden; }
.nav-item:hover { background: #f6f4f2; }
/* Demo chrome with nowhere to go: it hovers like every other row so the nav feels whole, but it
   has no link and no click handler, so clicking it does nothing. */
.nav-item.is-inert { cursor: pointer; }
.nav-item.sub { padding-left: 32px; }
.nav-item.active { background: #ede9e5; box-shadow: none; }
.nav-item.active .nav-text { color: var(--fg-primary); }

/* ── Section parent + children (Figma 1368:851) ───────────────────────────────────────────
   The parent is a disclosure: icon, label, and a chevron that turns over when the section is
   open. The children sit under it with no rule or elbow, their labels on the parent's label
   edge; only the page you are on is filled. */
.nav-group { position: relative; display: flex; flex-direction: column; }
.nav-children {
  display: none; flex-direction: column; gap: 4px;
  overflow: hidden; max-height: 0; opacity: 0;
  transition: max-height 0.22s cubic-bezier(0.4,0,0.2,1), opacity 0.18s ease 0.04s;
}
.nav-group.is-open .nav-children { display: flex; padding-top: 4px; }
.nav-group.is-open.is-revealed .nav-children { max-height: 240px; opacity: 1; }
.nav-group.no-anim .nav-children { transition: none; }
/* Label edge: 8px row padding + 16px icon + 8px gap = the parent's. */
.nav-group .nav-item.sub { padding-left: 32px; }
.nav-group .nav-item.sub.active { background: #ede9e5; }
.nav-group .nav-item.sub.active .nav-text { color: var(--fg-primary); }
.nav-parent .nav-chevron { width: 12px; height: 12px; margin: 0 2px 0 auto; opacity: 0.6; transition: transform 0.18s ease; }
.nav-group.is-open .nav-parent .nav-chevron { transform: rotate(180deg); }

.nav-chevron { width: var(--icon-md); height: var(--icon-md); flex-shrink: 0; opacity: 0.4; margin-left: auto; }
.nav-icon { width: var(--icon-md); height: var(--icon-md); flex-shrink: 0; }
.app-sidebar .nav-icon path,
.app-sidebar .nav-icon circle,
.app-sidebar .nav-icon rect,
.app-sidebar .nav-chevron path { stroke: var(--fg-primary) !important; }
.nav-text { font-size: 14px; font-weight: 400; color: var(--fg-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; padding-left: 4px; }
.app-topbar { height: 56px; min-height: 56px; background: #fff; box-shadow: 0 1px 0 var(--border);
    display: flex; align-items: center; gap: 8px; padding: 8px var(--page-gutter, 24px);
    flex-shrink: 0; position: relative; z-index: 40; font-family: var(--font-sans);
    view-transition-name: app-topbar; }
.app-topbar .search-bar { flex: 0 0 320px; width: 320px; min-width: 0; height: 36px;
    display: flex; align-items: center; gap: 8px; padding: 0 8px;
    background: rgba(26, 25, 25, 0.04); border-radius: var(--radius-control); cursor: pointer; }
.app-topbar .search-bar:hover { background: rgba(26, 25, 25, 0.07); }
.search-placeholder { font-size: 14px; color: var(--fg-primary); flex: 1;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar-actions { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.icon-btn { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
    border-radius: 50%; cursor: pointer; flex-shrink: 0; color: var(--fg-primary); }
.icon-btn:hover { background: var(--bg-tertiary); }
.avatar-btn { width: 32px; height: 32px; border-radius: 50%; overflow: hidden; flex-shrink: 0;
    cursor: pointer; background: #e8e8dc; display: flex; align-items: center; justify-content: center;
    margin-left: 8px; }
.nav-overlay { position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.32);
    backdrop-filter: blur(1px);
    -webkit-backdrop-filter: blur(1px);
    z-index: 70;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s;
    pointer-events: none; }
.app-page.bp-tablet.nav-open .nav-overlay,
.app-page.bp-phablet.nav-open .nav-overlay,
.app-page.bp-mobile.nav-open .nav-overlay { opacity: 1;
    visibility: visible;
    pointer-events: auto; }
.drawer-close { display: none; }
.app-page.bp-tablet .drawer-close,
.app-page.bp-phablet .drawer-close,
.app-page.bp-mobile .drawer-close { display: flex;
    position: absolute;
    top: 12px; right: 12px;
    width: 32px; height: 32px;
    align-items: center; justify-content: center;
    border: 0; background: none; cursor: pointer; border-radius: var(--radius-control);
    z-index: 2; }
.drawer-close:hover { background: rgba(0,0,0,0.04); }
.app-page.bp-tablet .sidebar-header,
.app-page.bp-phablet .sidebar-header,
.app-page.bp-mobile .sidebar-header { flex: 0 0 auto;
    padding: 12px 16px;
    gap: 8px;
    border-bottom: 1px solid var(--border);
    background: white; }
.app-page.bp-tablet .account-switcher,
.app-page.bp-phablet .account-switcher,
.app-page.bp-mobile .account-switcher { padding-left: 4px; }
.app-page.bp-tablet .logo-btn,
.app-page.bp-phablet .logo-btn,
.app-page.bp-mobile .logo-btn { width: 16px;
    height: 16px;
    padding: 0; }
.app-page.bp-tablet .logo-icon,
.app-page.bp-phablet .logo-icon,
.app-page.bp-mobile .logo-icon { width: 16px;
    height: 16px; }
.app-page.bp-tablet .drawer-scroll,
.app-page.bp-phablet .drawer-scroll,
.app-page.bp-mobile .drawer-scroll { flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-top: 12px;
    padding-bottom: 56px; }
.app-page.bp-tablet .drawer-close,
.app-page.bp-phablet .drawer-close,
.app-page.bp-mobile .drawer-close { position: static; margin-left: auto; }
.bp-tablet .app-sidebar,
.bp-phablet .app-sidebar,
.bp-mobile .app-sidebar { display: flex;
    flex-direction: column;
    position: absolute;
    top: 104px; left: 0; right: 0; bottom: 0;
    height: auto;   /* not the desktop 100%: with top 104 it ran 104px past the card and hid Settings */
    width: 100%;
    z-index: 80;
    background: white;
    border-right: 0;
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -8px 24px rgba(0,0,0,0.08);
    overflow: hidden;
    transform: translateY(100%);
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.app-page.bp-tablet.nav-open .app-sidebar,
.app-page.bp-phablet.nav-open .app-sidebar,
.app-page.bp-mobile.nav-open .app-sidebar { transform: translateY(0); }
/* The nav is 208px at every desktop size. It used to widen to 256 at widescreen, which is
   the one breakpoint class the schedule page's device previewer sets and the Routes page never
   does — so the shared component rendered 256 on one page and 208 on the other. A single
   width is the point of sharing it. */
.bp-widescreen .app-topbar { --page-gutter: 24px; }
.bp-desktop .app-topbar { --page-gutter: 24px; }
.bp-tablet .app-topbar { display: none; }
.bp-tablet .app-mobile-header .avatar-btn { margin-right: 8px; }
.bp-phablet .app-topbar { display: none; }
.bp-phablet .app-mobile-header .avatar-btn { margin-right: 8px; }
.bp-mobile .app-topbar { display: none; }
.bp-mobile .app-mobile-header .avatar-btn { margin-right: 8px; }
.spacing-on .nav-item { outline: 1px dashed rgba(99,102,241,0.6);
    outline-offset: -1px; }
.spacing-on .nav-section { background-color: rgba(239,68,68,0.05);
    box-shadow: inset 0 0 0 1px rgba(239,68,68,0.35); }
.spacing-on .nav-divider { background: rgba(99,102,241,0.7);
    height: 2px; }

/* ═══════════════════════════════════════════════════════════════════════════════════════
   TABLET AND BELOW — mobile header + the nav as a bottom sheet
   ═══════════════════════════════════════════════════════════════════════════════════════
   Ported from voodoo/jobs.html, which is the reference for this behaviour. There it is driven
   by bp-tablet / bp-phablet / bp-mobile classes that its device previewer stamps on
   #app-page — so resizing the actual window does nothing. The schedule page inherited that
   (its previewer hardcodes bp-widescreen) and the Routes page has no previewer at all, which
   is why neither responded to a window resize.

   So the same rules are restated here against real width. 991px is one below the previewer's
   desktop step (992), and jobs applies an identical shell treatment at all three of its
   smaller steps, so one query covers them. The bp-* rules above are left in place: the
   previewer still works on the pages that have one.

   Selectors name both containers — .app on Routes, .app-page on the schedule — because the
   two pages wrap their shell differently. */

/* The compact header that replaces the top bar. Hidden by default at every width. */
.app-mobile-header {
  display: none; height: 56px; min-height: 56px;
  /* A shadow, not a border-bottom: the header's height is fixed, so a border ate a pixel out of
     it and centred the icons on a half pixel. It also has to be the ONLY stroke here — the pages
     that style this header set the same shadow, and a border underneath it painted a second
     line in a slightly different grey. */
  box-shadow: 0 1px 0 var(--border, #e5e1de); background: white;
  align-items: center; justify-content: space-between;
  padding: 0 8px; flex-shrink: 0;
  /* Stacked like the desktop top bar (z-index 40), and for the same reason: the row below is
     opaque white with its own z-index, and it painted straight over this header's 1px shadow —
     so at every narrow breakpoint the header and that row read as one undivided block. */
  position: relative; z-index: 40;
}
.hamburger-btn {
  width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-control); flex-shrink: 0; cursor: pointer;
}
.hamburger-btn:hover { background: var(--bg-tertiary); }

@media (max-width: 991px) {
  .app-topbar { display: none; }
  .app-mobile-header { display: flex; }
  .app-mobile-header .avatar-btn { margin-right: 8px; }

  /* The nav leaves the layout and becomes a sheet hinged to the bottom. 104px down from the
     top is jobs' value — it clears its own mobile header and leaves the page's first rows
     visible behind the sheet. */
  .app-sidebar {
    display: flex; flex-direction: column;
    position: absolute; top: 104px; left: 0; right: 0; bottom: 0;
    width: 100%; min-width: 0;
    height: auto;   /* top + bottom size the sheet; the desktop height:100% pushed it past the card */
    /* 1570, not jobs' 80. That page has nothing floating above its content; the Routes page
       has map chrome at 1000, the visit panel at 1550 and the publish bar at 1400, all of
       which painted straight over the sheet. The nav is global, so it goes above all of them
       and stays below the modals at 2000. Only raised inside this query — desktop stacking is
       untouched. */
    z-index: 1570;
    background: white;
    border-right: 0;
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -8px 24px rgba(0,0,0,0.08);
    overflow: hidden;
    transform: translateY(100%);
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  }
  .app.nav-open .app-sidebar,
  .app-page.nav-open .app-sidebar { transform: translateY(0); }

  .nav-overlay { z-index: 1560; }   /* right beneath the sheet, over everything else */
  .app.nav-open .nav-overlay,
  .app-page.nav-open .nav-overlay { opacity: 1; visibility: visible; pointer-events: auto; }

  /* Sheet chrome: a close button in the corner, the account row tightened, the logo down to
     16px, and Settings pinned to the sheet's floor with the list scrolling under it. */
  .drawer-close {
    display: flex;
    position: absolute; top: 12px; right: 12px;
    width: 32px; height: 32px;
    align-items: center; justify-content: center;
    border: 0; background: none; cursor: pointer; border-radius: var(--radius-control);
    z-index: 2;
  }
  .sidebar-header {
    flex: 0 0 auto; padding: 12px 16px; gap: 8px;
    border-bottom: 1px solid var(--border); background: white;
  }
  .account-switcher { padding-left: 4px; }
  .logo-btn  { width: 16px; height: 16px; padding: 0; }
  .logo-icon { width: 16px; height: 16px; }
  .drawer-scroll {
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    overscroll-behavior: contain;
    padding-top: 12px; padding-bottom: 56px;
  }
  .nav-section-bottom {
    position: absolute; bottom: 0; left: 0; right: 0;
    background: white; border-top: 1px solid var(--border);
    padding-bottom: 12px;
  }
  /* The collapse toggle is a desktop affordance — the sheet has a close button instead. */
  .nav-collapse-btn { display: none; }
  /* A collapsed rail must not survive into the sheet. */
  .app-sidebar.is-collapsed { width: 100%; min-width: 0; }
  .app-sidebar.is-collapsed .nav-text,
  .app-sidebar.is-collapsed .nav-chevron,
  .app-sidebar.is-collapsed .account-switcher,
  .app-sidebar.is-collapsed .nav-item.sub { display: revert; }
  .app-sidebar.is-collapsed .nav-item { width: auto; justify-content: flex-start; gap: 8px; }
}
