/* ═══════════════════════════════════════════════════════════
   desktop.css — persistent-sidebar desktop shell (≥1024px)

   Loaded AFTER app.css, so at equal specificity these rules win.
   EVERYTHING here is gated behind @media (min-width:1024px) — mobile
   and tablet (<1024px) render exactly as app.css defines them.

   Uses the same design tokens as app.css (--space-*, --header-bg,
   --card, --border, --accent, …) so it themes with every palette and
   dark mode automatically (theme.js sets those vars on :root).
   ═══════════════════════════════════════════════════════════ */

@media (min-width: 1024px) {

  /* ── App shell → two-column grid ─────────────────────────────
     Column 1 is the persistent sidebar (spans both rows); column 2
     stacks the app bar over the scrolling content. minmax(0,1fr)
     lets wide children (tables, charts) shrink/scroll instead of
     blowing out the column width. */
  #app {
    display: grid;
    grid-template-columns: 260px minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "sidebar header"
      "sidebar main";
    min-height: 100vh;
  }

  /* Mobile-only chrome has no place in the persistent layout. */
  #sidebar-overlay,
  #sidebar-close,
  .hamburger-btn,
  .bottom-nav {
    display: none !important;
  }

  /* Desktop-only chrome, hidden in app.css: the drag-and-drop week
     organizer only makes sense with a pointer and a 7-column board. */
  .rt-organize-btn { display: inline-flex; }

  /* ── Persistent sidebar ──────────────────────────────────────
     Static, always visible, pinned to the viewport while content
     scrolls. Reads as a real product rail, not a frozen drawer. */
  #sidebar {
    grid-area: sidebar;
    position: sticky;
    top: 0;
    left: auto;
    align-self: start;
    width: auto;
    height: 100vh;
    transform: none;
    transition: none;
    border-right: 1px solid rgba(255, 255, 255, 0.07);
    box-shadow: 2px 0 12px rgba(0, 0, 0, 0.10);
  }

  .sidebar-top {
    padding: 20px 18px 12px;
  }
  .sidebar-user-info {
    padding: 12px 18px 16px;
  }

  .sidebar-nav {
    padding: 8px 10px;
    gap: 2px;
  }

  /* Nav items become inset pills so hover/active read as discrete
     targets — the product-sidebar look. */
  #sidebar .nav-btn {
    padding: 10px 12px;
    border-radius: var(--radius-md);
    font-size: var(--fs-base);
  }
  #sidebar .nav-btn:hover {
    background: rgba(255, 255, 255, 0.09);
    color: #fff;
  }
  #sidebar .nav-btn.active {
    background: rgba(255, 255, 255, 0.16);
    box-shadow: inset 3px 0 0 0 var(--accent);
  }

  /* ── Admin group: always expanded, no accordion ──────────────
     There's vertical room on desktop, so show the whole group and
     turn the toggle into a static section label. !important beats
     the inline display:none/flex that app.js toggles, so we never
     fight its state. */
  #admin-nav-items {
    display: flex !important;
  }
  #admin-nav-arrow {
    display: none !important;
  }
  .nav-group {
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
  }
  .nav-group-toggle {
    pointer-events: none;
    cursor: default;
    padding: 8px 12px 4px;
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(255, 255, 255, 0.4);
  }
  .nav-group-toggle:hover {
    background: transparent;
    color: rgba(255, 255, 255, 0.4);
  }
  #sidebar .nav-btn.nav-sub {
    padding-left: 12px;
    font-size: var(--fs-base);
    opacity: 1;
  }

  .sidebar-logout {
    margin: 8px 10px 0;
    padding: 12px;
    border-radius: var(--radius-md);
    border-top: none;
  }
  .sidebar-logout:hover {
    background: rgba(255, 255, 255, 0.08);
  }

  /* ── App bar (header, column 2) ──────────────────────────────
     Sticks to the top of the content column as a clean desktop bar. */
  .app-header {
    grid-area: header;
    padding: 14px 36px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  }
  .header-title {
    font-size: var(--fs-xl);
  }
  #header-date {
    font-size: var(--fs-sm);
  }

  /* ── Content column ──────────────────────────────────────────
     Drop the 700px phone cap; give a comfortable desktop measure
     with generous gutters and no bottom-nav padding to reserve. */
  .app-main {
    grid-area: main;
    max-width: 1400px;
    margin: 0 auto;
    padding: 28px 36px 56px;
  }

  /* ═══════════════════════════════════════════════════════════
     Admin dashboard — multi-column executive layout
     Lay the panel's direct children onto a named grid so we control
     placement independent of DOM order (no reordering = mobile DOM
     is byte-for-byte unchanged). Cards keep their own classes; we
     just place them. */
  #tab-dashboard.active {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-4);
    align-content: start;
    grid-template-areas:
      "dtitle    dtitle    dtitle    dtitle"
      "dstats    dstats    dstats    dstats"
      "doutst    doutst    doutst    dshop"
      "drevenue  drevenue  drevenue  drecent"
      "dcarousel dcarousel dcarousel dcarousel";
  }
  /* Kill per-card bottom margins (incl. inline ones) — the grid gap
     owns spacing now. Cards are left at the grid default (align-self:
     stretch) so the two cards sharing a row end on the SAME baseline:
     top-aligning them meant Shopping List stopped short of Outstanding
     & Overdue and the row read as broken. The shorter card just gains
     trailing whitespace; nothing inside it is stretched (the revenue
     chart's height is fixed by .chart-wrap). */
  #tab-dashboard.active > * {
    margin-bottom: 0 !important;
  }
  /* …but the panel's own headline must not stretch to a full row. */
  #tab-dashboard.active > .dash-title { align-self: start; }

  /* When a card is stretched past its content, park its footer link on
     the bottom edge so the extra space lands inside the card instead of
     leaving the link floating in the middle of it. */
  #tab-dashboard.active > .dash-shopping {
    display: flex;
    flex-direction: column;
  }
  #tab-dashboard.active > .dash-shopping > :last-child {
    margin-top: auto;
  }

  #tab-dashboard.active > .dash-title       { grid-area: dtitle; }
  #tab-dashboard.active > .stat-grid        { grid-area: dstats; }
  #tab-dashboard.active > .dash-outstanding { grid-area: doutst; }
  #tab-dashboard.active > .dash-revenue     { grid-area: drevenue; }
  #tab-dashboard.active > .dash-recent      { grid-area: drecent; }
  #tab-dashboard.active > .dash-carousel-wrap { grid-area: dcarousel; }
  /* Every direct child of the panel MUST be placed — an unplaced element gets
     auto-flowed into the first free cell and shoves the named layout apart.
     The shopping list takes the right-hand column beside Outstanding. */
  #tab-dashboard.active > .dash-shopping    { grid-area: dshop; }

  /* Four stat cards across the top in a single row. */
  #tab-dashboard.active > .stat-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-4);
  }

  /* Give the headline revenue chart real presence at desktop width. */
  #tab-dashboard.active .dash-revenue .chart-wrap {
    height: 340px;
    margin-bottom: 0;
  }

  /* ── Carousel → static multi-column card grid ────────────────
     No swiping on desktop: the two donut cards sit side by side and
     the 12-month chemical-cost trend spans the full width beneath. */
  #tab-dashboard.active .dash-carousel {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
    overflow: visible;
    scroll-snap-type: none;
  }
  #tab-dashboard.active .dash-carousel > .dash-slide {
    flex: none;
    width: auto;
    scroll-snap-align: none;
  }
  #tab-dashboard.active .dash-carousel > .dash-slide:nth-child(3) {
    grid-column: 1 / -1;
  }
  /* Dots carry an inline display:flex — override needs !important. */
  #tab-dashboard.active .dash-carousel-dots {
    display: none !important;
  }

  /* ═══════════════════════════════════════════════════════════
     Routes — three-day carousel (desktop only)

     Below 1024px the Routes tab shows ONE day, full width, and the
     touch swipe (_attachRouteSwipe / _slideToDay in routes.js) moves
     between days. None of the markup below exists there — render()
     only builds it when _isDesktop() is true — so mobile is untouched.

     At desktop width that single card was simply stretched across the
     1400px content column, which reads as a wall of whitespace. Here
     the strip becomes prev · focused · next: the focused day keeps a
     phone-ish reading measure in the middle, and the neighbours peek
     in from the gutters, scaled down, desaturated and dissolving into
     the page background.

     Three rules are load-bearing, not taste:
       1. NEVER transform .rt-car-focus — or any ANCESTOR of it, the
          strip itself included. Stop-card drag & drop hit-tests
          against getBoundingClientRect snapshots taken at drag start
          (routes.js → beginPointerDrag / _computeDropIdx); a
          transformed ancestor would skew every drop slot. Only
          .rt-car-side-inner is scaled, and the day-change slide-in at
          the bottom of this section animates the columns rather than
          the strip for exactly this reason.
       2. No overflow on #rt-route-swipe-wrap or the strip. Either one
          would become a scrollport and un-pin the focused day's
          sticky header — the exact trap documented in
          _attachRouteSwipe. All clipping happens on .rt-car-side.
       3. The neighbours ARE clipped, which makes them scrollports, so
          their own sticky headers would slide down over the first
          stop. .rt-route-head is pinned static inside a preview. */

  #rt-route-swipe-strip.rt-carousel {
    /* One shared measure so the neighbours are true scaled-down copies
       of the focused card rather than differently-proportioned ones.
       In vw, not %, because .rt-car-side-inner's containing block is
       its (narrow) side column — a percentage there would resolve
       against the wrong box. Derived from this shell's geometry:
       content column = min(1400px, 100vw − 260px sidebar) − 72px
       gutters. Lands at 440px around 1024px wide (still a wider
       reading measure than a phone) and tops out at 720px from
       ~1660px up; the 1fr side tracks absorb the leftover, so
       tweaking the sidebar width degrades gracefully rather than
       breaking. */
    --rt-car-w: clamp(440px, calc(44vw - 11px), 720px);
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--rt-car-w) minmax(0, 1fr);
    gap: var(--space-4);
    align-items: stretch;
  }

  /* The focused day: normal size, normal opacity, fully interactive,
     and — see rule 1 above — never transformed and never clipped. */
  .rt-car-focus { min-width: 0; }

  /* Neighbour columns. They contribute no height of their own (the
     preview inside is absolutely positioned), so the grid row is
     exactly as tall as the focused day and `stretch` sizes them to
     match — capped, so a 40-stop day doesn't grow two 6000px ghosts. */
  .rt-car-side {
    position: relative;
    min-width: 0;
    max-height: min(72vh, 860px);
    overflow: hidden;
    cursor: pointer;
    border-radius: var(--radius);
  }
  .rt-car-side:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  .rt-car-side-inner {
    position: absolute;
    top: 34px;              /* clears the day pill */
    width: var(--rt-car-w);
    pointer-events: none;   /* clicks belong to the column, not the copy */
    user-select: none;
    opacity: 0.42;
    filter: saturate(0.55);
    transform: scale(0.88);
    transition: opacity 0.18s ease, filter 0.18s ease, transform 0.18s ease;
  }
  /* Anchor each preview to its INNER edge and shrink toward it, so the
     day nearest the focused card stays flush and the far side is what
     gets cut off. */
  .rt-car-prev .rt-car-side-inner { right: 0; transform-origin: 100% 0; }
  .rt-car-next .rt-car-side-inner { left: 0;  transform-origin: 0 0; }

  .rt-car-side:hover .rt-car-side-inner,
  .rt-car-side:focus-visible .rt-car-side-inner {
    opacity: 0.8;
    filter: saturate(0.95);
    transform: scale(0.9);
  }

  /* A preview is a picture of a day, not a second copy of one: kill the
     sticky header (see rule 3) and any lingering drag affordance. */
  .rt-car-side-inner .rt-route-head {
    position: static !important;
    box-shadow: none !important;
  }
  .rt-car-side-inner .drag-handle { visibility: hidden; }

  /* Dissolve the outer edge and the bottom into the page background so
     the preview reads as receding rather than as a hard crop. Painted
     as overlays (not mask-image) so they sit at full strength above the
     faded copy underneath. */
  .rt-car-side::after,
  .rt-car-side::before {
    content: '';
    position: absolute;
    pointer-events: none;
    z-index: 2;
  }
  .rt-car-side::after { inset: 0; }
  /* The fade is a share of the column, not a fixed distance, so the
     ~110px slivers at 1024px keep a crisp inner edge instead of
     dissolving almost entirely. */
  .rt-car-prev::after {
    background: linear-gradient(to left, rgba(0, 0, 0, 0) 62%, var(--bg) 100%);
  }
  .rt-car-next::after {
    background: linear-gradient(to right, rgba(0, 0, 0, 0) 62%, var(--bg) 100%);
  }
  .rt-car-side::before {
    /* Percentage cap so a short focused day (an empty route, say) doesn't end
       up as 150px of fade over 250px of column. */
    left: 0; right: 0; bottom: 0; height: min(150px, 40%);
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg));
  }

  /* The clickable affordance: a crisp day pill on the inner edge. The
     preview under it is deliberately too faded to read, so this is what
     actually names the day. */
  .rt-car-label {
    position: absolute;
    top: 0;
    z-index: 4;
    display: inline-flex;
    align-items: center;
    padding: 5px 12px;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--text-muted);
    white-space: nowrap;
    transition: color 0.18s ease, border-color 0.18s ease;
  }
  .rt-car-prev .rt-car-label { right: 6px; }
  .rt-car-next .rt-car-label { left: 6px; }
  .rt-car-side:hover .rt-car-label,
  .rt-car-side:focus-visible .rt-car-label {
    color: var(--accent);
    border-color: var(--accent);
  }

  /* Placeholder for a neighbour whose day isn't in the route cache yet
     (_carFillNeighbors backfills it in place), and the terminal state when
     that fetch fails — offline, mostly. */
  .rt-car-skeleton { pointer-events: none; }
  .rt-car-unavailable {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    text-align: center;
    padding: 28px 12px;
  }
  .rt-car-sk-card {
    border: 1.5px solid var(--border);
    border-radius: 8px;
    padding: 12px;
    margin-bottom: 6px;
  }
  .rt-car-sk-bar {
    height: 11px;
    border-radius: 5px;
    background: var(--border);
    margin-bottom: 7px;
    animation: rt-car-pulse 1.4s ease-in-out infinite;
  }
  @keyframes rt-car-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.85; } }

  /* Day change rebuilds the panel wholesale (selectDay → load(true)),
     so the NEW strip is animated in from the side the user moved
     toward instead of tweening the old DOM into it.

     The trigger class still lands on the strip (routes.js →
     _attachCarousel), but the animations are declared on its CHILDREN,
     never on the strip itself. Putting `transform: translateX()` on the
     strip transforms an ANCESTOR of .rt-car-focus, which is precisely
     what rule 1 above forbids: a mousedown landing inside the 0.26s
     window (reachable — _carBusy only locks navigation for 0.34s) makes
     beginPointerDrag snapshot getBoundingClientRect mid-slide, so every
     drop slot it computed would be offset by up to 48px and the stop
     would land in the wrong position.

     So: the two side columns keep the directional slide — they are inert
     previews (pointer-events:none, `inert`, skipped by
     attachPointerListeners), nothing hit-tests inside them — and the
     focused column gets a pure opacity fade. Opacity does not move the
     column's box, does not change the containing block, and so leaves
     both the drag snapshots and the sticky route header untouched. */
  @keyframes rt-car-focus-in {
    from { opacity: 0.3; }
    to   { opacity: 1; }
  }
  @keyframes rt-car-side-in-next {
    from { opacity: 0.3; transform: translateX(48px); }
    to   { opacity: 1;   transform: translateX(0); }
  }
  @keyframes rt-car-side-in-prev {
    from { opacity: 0.3; transform: translateX(-48px); }
    to   { opacity: 1;   transform: translateX(0); }
  }
  #rt-route-swipe-strip.rt-carousel.rt-car-in-next > .rt-car-focus,
  #rt-route-swipe-strip.rt-carousel.rt-car-in-prev > .rt-car-focus {
    animation: rt-car-focus-in 0.26s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  #rt-route-swipe-strip.rt-carousel.rt-car-in-next > .rt-car-side {
    animation: rt-car-side-in-next 0.26s cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  #rt-route-swipe-strip.rt-carousel.rt-car-in-prev > .rt-car-side {
    animation: rt-car-side-in-prev 0.26s cubic-bezier(0.2, 0.7, 0.2, 1);
  }

  @media (prefers-reduced-motion: reduce) {
    #rt-route-swipe-strip.rt-carousel.rt-car-in-next > .rt-car-focus,
    #rt-route-swipe-strip.rt-carousel.rt-car-in-prev > .rt-car-focus,
    #rt-route-swipe-strip.rt-carousel.rt-car-in-next > .rt-car-side,
    #rt-route-swipe-strip.rt-carousel.rt-car-in-prev > .rt-car-side { animation: none; }
    .rt-car-sk-bar { animation: none; }
    .rt-car-side-inner { transition: none; }
  }
}
