/* ============================================================
   ui-perf.css â€” UI and performance overrides
   Loaded last. Additive-only: it does not rewrite existing stylesheets.
   ============================================================ */

/* ---- Sidebar collapse hardening ----
   The collapse behaviour already exists; these ID-scoped rules make sure the
   collapsed flex-basis and content margin win over earlier layout rules. */
#sidebar.collapsed {
  width: var(--sidebar-w-collapsed);
  flex: 0 0 var(--sidebar-w-collapsed);
  padding: 22px 10px 24px;
}
#sidebar.collapsed ~ #main { margin-left: var(--sidebar-w-collapsed); }

/* ---- Smaller, draggable collapse handle ----
   JS already supports pointer drag and persists the vertical position. The
   control is shorter than before while remaining an easy touch target. */
#sidebarToggle.sidebar-toggle {
  /* Vertically centred on the sidebar by default. It was pinned at 88px from
     the top, which reads as "floating near the header" rather than as a control
     for the panel beside it. sidebar.js still writes an inline `top` when a
     dragged position has been saved, and clears the transform at the same time,
     so a deliberate drag still wins over this default. */
  top: 50%;
  transform: translateY(-50%);
  right: -22px;
  width: 22px;
  height: 44px;
  border-radius: 0 15px 15px 0;
  touch-action: none;
  /* Unmissable: brand fill, white glyph, soft glow. */
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
  box-shadow: 0 4px 18px rgba(87, 116, 183, .55);
}
#sidebarToggle.sidebar-toggle:hover {
  background: var(--primary-dark);
  color: #fff;
  box-shadow: 0 4px 22px rgba(87, 116, 183, .7);
}
#sidebarToggle.sidebar-toggle svg { width: 13px; height: 13px; }

/* ---- Selected sidebar link: glass white ----
   One translucent white selection surface for expanded and collapsed states.
   The active glyph stays brand-coloured so it remains legible on white. */
.sidebar .nav a.active,
.sidebar .nav a.active:hover,
.sidebar.collapsed .nav a.active,
.sidebar.collapsed .nav a.active:hover {
  background: rgba(255, 255, 255, .78);
  -webkit-backdrop-filter: blur(14px) saturate(145%);
  backdrop-filter: blur(14px) saturate(145%);
  border: 1px solid rgba(255, 255, 255, .7);
  box-shadow: 0 8px 22px rgba(15, 17, 23, .16);
  color: #1b1e27;
}
/* The travelling pill behind the active link carries that section's tone
   colour, and on the darker tones it swallowed the icon entirely - the glyph
   was brand blue sitting on a saturated fill of a similar hue, so the mark
   disappeared rather than stood out. White reads on every tone in the set, and
   the dark drop-shadow keeps its edges defined against light ones as well. */
.sidebar .nav a.active .i,
.sidebar .nav a.active .chev,
.sidebar.collapsed .nav a.active .i,
.sidebar.collapsed .nav a.active .chev {
  color: #fff;
  stroke: #fff;
  filter: drop-shadow(0 1px 2.5px rgba(0, 0, 0, .6));
  opacity: 1;
}
.sidebar .nav a.active .badge,
.sidebar.collapsed .nav a.active .badge {
  background: var(--primary) !important;
  color: #fff !important;
}

/* Collapsed icons keep their per-section tone colours. The section hues come
   from data-tone attributes in index.html; this only guarantees the tone wins
   over the neutral sidebar icon colour. */
.sidebar.collapsed .nav a .i { color: var(--tone-color, var(--sidebar-muted)); }

/* ---- Cheaper ambient background ----
   The previous orbs animated rotation, scale, opacity, blur, and screen blend
   on very large layers. Rotation is invisible on a radial circle, opacity
   forces repaints, and nested screen blending forces extra compositing. These
   overrides keep the same visual drift but animate transform only. */
.bg-orbs {
  contain: strict;
  mix-blend-mode: normal;
  opacity: .48;
}
.orb {
  mix-blend-mode: normal;
  filter: blur(38px);
  opacity: .52;
  will-change: transform;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
.orb-a { animation-name: orb-pan-a; animation-duration: 24s; }
.orb-b { animation-name: orb-pan-b; animation-duration: 28s; }
.orb-c { animation-name: orb-pan-c; animation-duration: 26s; }
@keyframes orb-pan-a {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(8vw, 7vh, 0); }
}
@keyframes orb-pan-b {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-9vw, 8vh, 0); }
}
@keyframes orb-pan-c {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(7vw, -7vh, 0); }
}
body::before { background-attachment: scroll; }

/* Strong frost on the two most common large blurred surfaces. */
.card.kpi,
.table-wrap {
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
}

/* Home greeting keeps its colour sweep, but the duplicate travelling glow and
   continuously pulsing text halo are removed. They added blurred layers while
   contributing little after the first glance. */
.hero-greet::before { display: none; animation: none; }
#heroName::after { animation: none; opacity: .34; }

@media (prefers-reduced-motion: reduce) {
  .bg-orbs { opacity: .32; }
  .orb { animation: none; }
}

/* ---- Dashboard-wide glass surfaces ----
   One translucent language for cards, panels, tables, charts and containers.
   Blur is intentionally modest and shared, so dozens of surfaces do not each
   force an expensive backdrop pass. */
.card,
.panel,
.table-wrap,
.chart,
.perf-chart-wrap,
.comm-panel,
.post-card,
.ins-card,
.ecp-out,
.int-group,
.cq-card,
.qcard,
.thread,
.composer,
.comm-list,
.comm-detail,
.exp-modal {
  background: color-mix(in srgb, var(--card) 46%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  box-shadow: 0 8px 24px rgba(10, 15, 30, .08), inset 0 1px 0 rgba(255, 255, 255, .45);
}

/* ---- Extended glass coverage ----
   Feature stylesheets give many containers their own opaque single-class
   backgrounds (.rc, .group-card, .chip, .modal, .odp, ...). Those never looked
   glassy anywhere because they were never in the glass selector list â€” which
   is why only Home (built on .card/.qcard) showed the effect. */
 .rc,
 .ro,
.modal,
.odp,
.chip,
.btn-ghost,
.btn-refresh,
.search-box,
.per-page,
.circ-btn,
.sync,
.cx-search,
 .prod,
 .confirm-box,
.msel .menu,
.rcards,
.exp-row,
.ps-topbar,
.cx-filters,
.odp-actions button,
.mini-btn,
.chart-tooltip,
.ov-topbar,
.status-filter button,
.input,
select.input,
.settings-input {
  background: color-mix(in srgb, var(--card) 46%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border-color: color-mix(in srgb, var(--border) 55%, transparent);
}

/* ---- Communication tabs ----
   Each row is its own stacking context. That is the overlap fix: the tab
   buttons carry z-index:1 so they sit above their own travelling pill, and a
   positive z-index paints after every positioned sibling — so `.comm-tab`
   buttons used to paint straight over the view-tab row underneath them.
   Containing each row stops that leak. Corners are 2px throughout, per spec. */
.communication-view {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}
.communication-view .comm-tabs,
.communication-view .comm-views {
  position: relative;
  z-index: 0;
  isolation: isolate;
  flex: none;
  max-width: 100%;
  border-radius: 2px;
}
/* No tray. The channel row is a bare grid: each channel is its own elevated
   card, so a background behind all three only added a second surface the cards
   had to sit inside. Layout lives in the "Channel cards" block further down. */
.communication-view .comm-tabs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(236px, 1fr));
  gap: 14px;
  width: 100%;
}
/* Inside the panel the view row is a header strip, not a floating control, so
   it loses its own border and spans the panel width. */
.communication-view .comm-panel > .comm-views {
  align-self: stretch;
  width: auto;
  border: 0;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 8px 14px;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.communication-view .comm-views {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  overflow: visible;
  padding: 3px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--elev) 72%, transparent);
}
.communication-view .comm-tab,
.communication-view .comm-vtab,
.communication-view .comm-filters .seg-btn {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--label);
  font-family: inherit;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
/* ---- Channel cards ----
   Each channel is a free-standing elevated card, not a segment inside a tray.
   The surface is the app's KPI treatment verbatim — card at 46% over a 22px
   saturate-180% blur, a 55% border and the 0 8px 24px KPI shadow — so these sit
   in the same family as the Orders and Analytics figures instead of borrowing a
   different one. Layout (grid, gap) is declared once, above. */
.communication-view .comm-tab {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
  min-height: 76px;
  padding: 12px 13px;
  border-radius: 2px;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 46%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  box-shadow: 0 8px 24px rgba(10, 15, 30, .08);
  font-size: 11.5px;
  text-align: left;
  transition: transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.communication-view .comm-tab:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-pop);
  border-color: color-mix(in srgb, var(--border) 85%, transparent);
}
/* The active card must not lift. The pill is positioned from the button's
   layout box (offsetLeft/offsetTop), and transforms do not change those, so a
   translateY would leave the pill sitting 2px above the card it is meant to
   fill. It gets its depth from the brand shadow instead. */
.communication-view .comm-tab.on,
.communication-view .comm-tab.on:hover { transform: none; }
/* Icon chip: a soft brand tint rather than a solid brand block, so three brand
   colours can sit side by side without the row turning into a colour swatch.
   Fixed 32px box and a whole-pixel glyph keep the mark crisp — these are filled
   outlines drawn at 24x24, so any fractional scale smears them. */
.communication-view .comm-tab .ic {
  width: 32px;
  height: 32px;
  flex: 0 0 32px;
  border-radius: 2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--ch-brand) 13%, transparent);
  color: var(--ch-brand);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ch-brand) 24%, transparent);
  transition: background-color .6s cubic-bezier(.32, 0, .18, 1),
    color .6s cubic-bezier(.32, 0, .18, 1),
    box-shadow .6s cubic-bezier(.32, 0, .18, 1);
}
.communication-view .comm-tab .ic .i { width: 17px; height: 17px }
.communication-view .comm-tab .t { min-width: 0; flex: 1 }
/* `display:block` is load-bearing: without it <b> and <span> are inline and the
   channel name runs straight into its description on one line. */
.communication-view .comm-tab .t b,
.communication-view .comm-tab .t span {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.communication-view .comm-tab .t b {
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -.01em;
  color: var(--text);
}
.communication-view .comm-tab .t span {
  font-size: 10.5px;
  line-height: 1.35;
  margin-top: 3px;
  color: var(--muted);
}

/* Empty / connect states reuse the channel mark. Same chip, larger, and tinted
   from --ch-brand now that the inline background is gone. */
.communication-view .comm-empty .ic {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  border-radius: 2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--ch-brand) 13%, transparent);
  color: var(--ch-brand);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ch-brand) 24%, transparent);
}
.communication-view .comm-empty .ic .i { width: 22px; height: 22px }
.communication-view .comm-empty .ic.muted {
  background: var(--elev);
  color: var(--icon);
  box-shadow: inset 0 0 0 1px var(--border);
}

/* Active channel: the travelling pill carries the brand fill, so the tab must not
   paint one underneath it. The icon chip inverts to white to keep the mark
   legible against that saturated fill.

   The label and badge cross-fade on the same slow curve as the pill. A fast
   colour flip here under a slow-moving pill is what made a channel change read
   as a jump instead of a handover. */
.communication-view .comm-tab .t b,
.communication-view .comm-tab .t span,
.communication-view .comm-tab .cnt {
  transition: color .6s cubic-bezier(.32, 0, .18, 1),
    background-color .6s cubic-bezier(.32, 0, .18, 1),
    border-color .6s cubic-bezier(.32, 0, .18, 1);
}
.communication-view .comm-tabs.has-glider > .comm-tab.on {
  background: none;
  border-color: transparent;
  box-shadow: 0 10px 28px -8px color-mix(in srgb, var(--ch-brand) 45%, transparent);
}
.communication-view .comm-tabs.has-glider > .comm-tab.on .t b { color: #fff }
.communication-view .comm-tabs.has-glider > .comm-tab.on .t span {
  color: color-mix(in srgb, #fff 76%, transparent);
}
.communication-view .comm-tabs.has-glider > .comm-tab.on .ic {
  background: color-mix(in srgb, #fff 22%, transparent);
  color: #fff;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #fff 36%, transparent);
}
.communication-view .comm-tabs.has-glider .glider-pill,
.communication-view .comm-views.has-glider .glider-pill {
  background: var(--ch-brand, var(--primary));
  border-radius: 2px;
  box-shadow: 0 6px 18px -4px color-mix(in srgb, var(--ch-brand, var(--primary)) 55%, transparent);
  /* Colour travels with the pill, and slowly. --ch-brand changes the moment the
     active channel changes; a 0.85s cross-fade means the outgoing colour is
     still visible as the incoming one builds, instead of a hard flip. */
  transition: transform .34s cubic-bezier(.4, 1.3, .5, 1), width .34s cubic-bezier(.4, 1.3, .5, 1),
    height .34s cubic-bezier(.4, 1.3, .5, 1),
    background-color .85s cubic-bezier(.32, 0, .18, 1);
}
.communication-view .comm-vtab,
.communication-view .comm-filters .seg-btn {
  height: 28px;
  padding: 0 14px;
  border-radius: 2px;
  font-size: 10.5px;
  line-height: 1;
}
/* A view this channel does not offer is hidden, not removed — see VIEW_DEFS. */
.communication-view .comm-vtab.is-off { display: none; }
.communication-view .comm-tab.on,
.communication-view .comm-vtab.on,
.communication-view .comm-filters .seg-btn.on {
  background: none;
  box-shadow: none;
  color: var(--text);
}

/* Connection state badge on a channel tab. It had no rule at all, so it
   rendered as unstyled text hanging off the row and read as overlap. */
.communication-view .comm-tab .cnt {
  flex: none;
  align-self: center;
  margin-left: auto;
  padding: 2px 7px;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: color-mix(in srgb, var(--elev) 70%, transparent);
  color: var(--muted);
  font-size: 9px;
  font-weight: 700;
  line-height: 1.5;
  white-space: nowrap;
}
.communication-view .comm-tab .cnt.ok {
  color: #1f9d6b;
  border-color: color-mix(in srgb, #1f9d6b 38%, transparent);
  background: color-mix(in srgb, #1f9d6b 12%, transparent);
}
.communication-view .comm-tab .cnt.soon {
  color: var(--label);
  background: color-mix(in srgb, var(--elev) 70%, transparent);
}
/* On the active tab the badge sits on the brand fill, so the green-on-white
   pairing above stops reading. It inverts to a plain white outline instead. */
.communication-view .comm-tabs.has-glider > .comm-tab.on .cnt {
  color: #fff;
  border-color: color-mix(in srgb, #fff 52%, transparent);
  background: color-mix(in srgb, #fff 16%, transparent);
}

/* Per-channel brand theming for the view row. --ch-brand is set on the row by
   CommunicationFeature from the channel's own config colour, so Inbox/Comments/
   Posts carry the active channel's blue, pink or green rather than a flat app
   blue that ignores which channel you are on. */
.communication-view .comm-views.has-glider .glider-pill {
  background: var(--ch-brand, var(--primary));
  border-radius: 2px;
  box-shadow: 0 6px 18px -4px color-mix(in srgb, var(--ch-brand, var(--primary)) 50%, transparent);
  transition: transform .34s cubic-bezier(.4, 1.3, .5, 1), width .34s cubic-bezier(.4, 1.3, .5, 1),
    height .34s cubic-bezier(.4, 1.3, .5, 1),
    background-color .85s cubic-bezier(.32, 0, .18, 1);
}
.communication-view .comm-views.has-glider > .comm-vtab.on { color: #fff }
.communication-view .comm-views.has-glider > .comm-vtab:not(.on) { color: var(--label) }

/* The comments-view filter row (All / Needs reply / Hidden). Same pill, same
   brand, same morph as the other two rows so the whole section reads as one
   control family. --ch-brand is inherited from the panel. */
.communication-view .comm-filters.has-glider {
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: color-mix(in srgb, var(--elev) 72%, transparent);
}
.communication-view .comm-filters.has-glider .glider-pill {
  background: var(--ch-brand, var(--primary));
  border-radius: 2px;
  box-shadow: 0 6px 18px -4px color-mix(in srgb, var(--ch-brand, var(--primary)) 50%, transparent);
  transition: transform .34s cubic-bezier(.4, 1.3, .5, 1), width .34s cubic-bezier(.4, 1.3, .5, 1),
    height .34s cubic-bezier(.4, 1.3, .5, 1),
    background-color .85s cubic-bezier(.32, 0, .18, 1);
}
.communication-view .comm-filters.has-glider > .seg-btn { transition: color .2s ease; }
.communication-view .comm-filters.has-glider > .seg-btn.on { color: #fff }

/* ---- Dashboard-wide tab rows ----
   Every switching control uses the same sliding pill. Buttons stay above it;
   the pill itself carries the motion. */
.seg,
.perf-tabs,
.chips,
.mtabs,
.comm-tabs,
.comm-views,
.comm-filters,
.pagination .pages,
#pager .pages,
.settings-nav {
  position: relative;
}
.has-glider button,
.has-glider-soft button {
  position: relative;
  z-index: 1;
}
.has-glider button.on,
.has-glider button.active,
.has-glider-soft button.on,
.has-glider-soft button.active {
  background: none;
  box-shadow: none;
}
.has-glider button.on,
.has-glider button.active { color: #fff; }
.has-glider-soft button.on,
.has-glider-soft button.active { color: var(--text); }
/* The soft pill is a real pill: it has to be taken out of flow, because
   attachGlider positions it with transform/width/height against the container.
   Without this it stayed `static` and became a phantom flex/grid item at the
   start of the row — a stray tinted blob, and an extra cell in the track. */
.glider-soft {
  position: absolute;
  z-index: 0;
  top: 0;
  left: 0;
  pointer-events: none;
  will-change: transform, width;
  transition: transform .34s cubic-bezier(.4, 1.3, .5, 1), width .34s cubic-bezier(.4, 1.3, .5, 1),
    height .34s cubic-bezier(.4, 1.3, .5, 1);
  border-radius: 14px;
  background: color-mix(in srgb, var(--primary) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 34%, transparent);
}
.pagination .pages,
#pager .pages {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: color-mix(in srgb, var(--card) 66%, transparent);
}
.pagination .pages button,
#pager .pages button {
  min-width: 30px;
  height: 28px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--label);
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 600;
  cursor: pointer;
}

/* Orders chips stay readable in light mode: the travelling pill supplies the
   colour, so the active label is white only while it sits above that pill. */
.orders-view #statusChips .chip { color: var(--label); }
.orders-view #statusChips .chip.on { color: #fff; }

/* ---- Orders KPIs fill the row ----
   Four large cards across the full width, taller with bigger figures. */
.orders-view .kpis {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}
.orders-view .kpi-lg {
  min-height: 154px;
  padding: 18px 18px 15px;
  /* Explicit frost: the generic .card rule alone left these reading solid,
     so the KPI cards get their own deeper, more translucent glass. */
  background: color-mix(in srgb, var(--card) 30%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  box-shadow: 0 8px 24px rgba(10, 15, 30, .08);
}
.orders-view .kpi-lg .num { font-size: 42px; }
.orders-view .kpi-lg .num.money { font-size: 34px; }
.orders-view .kpi-lg .num.money { font-size: 34px; }
.orders-view .kpi-lg .lbl { font-size: 12px; }
.orders-view .kpi-lg .foot { font-size: 11px; }

/* Orders date control is a lone range trigger now â€” style it as the pill. */
.orders-view .ord-date-direct .tp-trigger { min-width: 220px; }
@media (max-width: 1200px) {
  .orders-view .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- Date filter panel: single scrolling column, no overlapping Apply ----
   The panel was a flex column with `max-height`, while the custom range area
   also had `overflow-y:auto`. Two scroll containers inside one capped flex box
   fought for the same space and the sticky Apply landed on top of the range
   fields instead of below them.

   Now the PANEL is the only scroller and Apply is a normal flow child at the
   end, so it can never overlap anything. */
.date-dropdown.open .date-dropdown-panel {
  display: block;
  max-height: min(72vh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.date-dropdown-panel .dd-custom {
  overflow: visible;
  min-height: 0;
  padding: 6px 10px 4px;
}
.date-dropdown-panel .dd-apply {
  position: static;
  width: calc(100% - 12px);
  margin: 6px;
}
/* The themed calendar floats above every dropdown menu, never under one. */
.tp-panel { z-index: 500; }

/* ---- Orders date filter: picker only ----
   Quick dates and Apply are gone: the pill opens the single range calendar
   directly and the second picked day applies itself. */
#ordDateDropdown .dd-section,
#ordDateDropdown .dd-opt,
#ordDateDropdown .dd-divider,
#ordDateDropdown #ordDateApply { display: none; }
#ordDateDropdown .date-dropdown-panel { min-width: 0; padding: 6px; }
#ordDateDropdown .dd-custom { padding: 0; }
#ordDateDropdown .tp-wrap-range { min-width: 210px; }

/* ---- Compact filter panel ----
   Narrower, self-scrolling, with Apply/Clear pinned where they stay visible. */
.orders-view .flt-panel {
  width: min(340px, 92vw);
  max-height: min(70vh, 520px);
  overflow-y: auto;
  padding: 14px;
}
.orders-view .flt-foot {
  position: sticky;
  bottom: 0;
  margin: 12px -14px -14px;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
}
.orders-view .flt-foot .btn-ghost,
.orders-view .flt-foot .btn-solid { flex: 1; justify-content: center; }

/* ---- Orders table: room to breathe ----
   A guaranteed minimum width forces the horizontal scrollbar instead of
   crushing columns; key columns read larger. */
/* ---- Orders: filter popups must clear the table ----
   The table header is `position: sticky` with z-index 2 so it keeps its fill
   while the body scrolls. That same z-index beat every filter popup, which
   defaults to `auto`, so the date/filter menu opened and then had its top
   third painted over by the header row - it read as "the menu is underneath
   the table". The popups get an explicit stack well above the sticky row. */
.orders-view .tp-wrap,
.orders-view .tp-panel,
.orders-view .flt-panel,
.orders-view .flt-drop-list,
.orders-view .date-dropdown-panel,
.orders-view .acts-menu,
.orders-view .actions-menu,
.orders-view .pp-panel,
/* The search-scope picker lives INSIDE .search-box, which is the control the
   user is reaching for - it was the one popup missing from the list above, so it
   kept `z-index: auto` and was painted over by the sticky table header. */
.orders-view .search-scope-menu { z-index: 60; position: absolute }
.orders-view .search-box { position: relative; z-index: 61 }
.orders-view .tp-wrap,
.orders-view .tp-panel,
.orders-view .flt-panel,
.orders-view .date-dropdown-panel { position: relative }
/* And the sticky header stays where it belongs, below them. */
.orders-view .table-scroll thead th,
.orders-view .table-wrap thead th { z-index: 2 }

.orders-view .table-scroll table { min-width: 1340px; }
.orders-view .order-row td:nth-child(4),
.orders-view .order-row td:nth-child(5),
.orders-view .order-row td:nth-child(6) {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
}
.orders-view .order-row td.td-amt {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--text);
}
.orders-view .order-row .td-track { font-size: 12.5px; font-weight: 700; }
.orders-view .order-row td.td-date { white-space: nowrap; min-width: 108px; }
.orders-view .order-row td .pill { font-size: 10.5px; padding: 3px 9px; }

/* ---- Order drawer: important fields bigger ---- */
.odp-tracking { font-size: 19px; }
.odp .title h3 { font-size: 16px; }
.odp .title p { font-size: 11.5px; }
.odp-field .fv { font-size: 13.5px; }
.odp-field .fl { font-size: 9.5px; }
.odp .pill { font-size: 11px; padding: 4px 10px; }

/* ---- Custom top table scrollbar ----
   attachTopScroll injects this track above wide tables; without these rules it
   rendered as invisible unstyled divs. */
.tsb {
  height: 10px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--elev) 70%, transparent);
  margin-bottom: 6px;
  user-select: none;
}
.tsb-inner { position: relative; height: 100%; }
.tsb-thumb {
  position: absolute;
  top: 1px; bottom: 1px; left: 0;
  border-radius: 4px;
  background: color-mix(in srgb, var(--muted) 60%, transparent);
  cursor: grab;
  min-width: 44px;
}
.tsb.is-live .tsb-thumb:hover { background: var(--primary); }
.tsb.is-dragging .tsb-thumb { cursor: grabbing; background: var(--primary); }

/* ---- Analytics: courier panel takes a fixed height ----
   Both cards in the courier/cities row stretch to the same fixed height
   instead of sizing to their content; the courier list scrolls inside. */
.analytics-view .grid-courier-cities { align-items: stretch; }
.analytics-view .analytics-couriers,
.analytics-view .analytics-cities {
  min-height: 440px;
  display: flex;
  flex-direction: column;
}
.analytics-view .analytics-couriers .cq-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}
.analytics-view .analytics-cities .pie-legend {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* ---- Analytics: funnel as side-by-side vertical batteries ----
   Each stage is a tall rounded track with the fill anchored to the bottom, so
   the column reads like a battery level rather than a proportion bar. The share
   label sits inside the fill, attached to the bar it describes. */
.funnel-bars {
  flex-direction: row;
  align-items: flex-start;
  gap: 14px;
}
.funnel-bars .fn-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Read-only, so no pointer. `default` is honest: the hand cursor is what
     advertises a click. `help` is literally a question mark, which read as
     a broken icon rather than as "there is information here". */
  cursor: default;
  padding: 4px;
  border-radius: 14px;
  transition: background var(--dur-fast);
}
.funnel-bars .fn-col:hover { background: color-mix(in srgb, var(--primary) 9%, transparent); }
.funnel-bars .fn-track {
  /* The vessel, not the wave. This stays at its full height - the request was
     to shrink the liquid inside it, not the battery around it. The wave is
     made of .fn-liquid::before / ::after and .fn-wave, all sized in the block
     in sections-delta.css. Note this selector is duplicated in that file at
     the same specificity; this one loads later and wins, so the height belongs
     here. */
  height: 190px;
  width: 100%;
  display: flex;
  align-items: flex-end;
  /* iPhone-style battery: one capsule, no inner padding. Padding on a
     999px-radius box leaves the fill floating inside with visible gaps at the
     top corners instead of touching every inner edge. */
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  background: color-mix(in srgb, var(--elev) 46%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  overflow: hidden;
  /* The pool is absolutely positioned (left:-8%, bottom:0) so it can overhang
     the capsule and carry its drifting surface. Without this the vessel is not
     its containing block and the liquid anchors to the viewport instead. */
  position: relative;
  isolation: isolate;
}
.funnel-bars .fn-liquid {
  /* Height, gradient, surface and sheen all live in sections-delta.css. This
     file loads last, so only the containing-block setup is restated here. */
  position: absolute;
}
.funnel-bars .fn-val {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: center;
  line-height: 1.2;
}
.funnel-bars .fn-cap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
}
.funnel-bars .fn-cap b { font-size: 13px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.funnel-bars .fn-cap span { font-size: 10px; color: var(--muted); line-height: 1.3; white-space: normal; }
/* The timing tiles sit under the batteries, matching Daily Average styling. */
.funnel-bars + .perf-chart-wrap + .fn-times { margin-top: 14px; }
.fn-times .micro-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* ---- Analytics: Daily Average + micro stats ----
   Each average reads as its own glass tile with an accent tab. */
.micro-stats {
  display: grid;
  /* Container-driven, and this file loads AFTER sections-delta.css — so this
     rule, not the one there, is what the browser actually applies. It used to
     floor the columns at 128px, which in a half-width panel only fit three, and
     the remaining two fell to a second row that hung past the panel edge. 78px
     is the floor that fits all five at the panel's real content width (~519px):
     5x78 + 4x10 = 440px. Below that floor it wraps 3+2, which auto-fit does
     evenly rather than stranding a card. */
  grid-template-columns: repeat(auto-fit, minmax(78px, 1fr));
  gap: 10px;
  align-content: start;
}
.micro-stat {
  position: relative;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  border-radius: 12px;
  padding: 11px 12px 10px;
  background: color-mix(in srgb, var(--card) 52%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
}
.micro-stat::before {
  content: "";
  position: absolute;
  left: 0; top: 10px; bottom: 10px;
  width: 3px;
  border-radius: 2px;
  background: var(--ms-accent, var(--primary));
}
.micro-stat .ms-label { padding-left: 8px; }
.micro-stat .ms-value {
  padding-left: 8px;
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.micro-stat .ms-trend { padding-left: 8px; }

/* ---- Analytics: Payment & Settlement donut ----
   Labels run down the left with the chart on the right. The four money figures
   are read straight off the labels; the ring shows shape and ordering. */
.pay-donut-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 214px;
  gap: 16px;
  align-items: center;
}
.pay-legend { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.pay-lg {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  grid-template-areas:
    "sw key val"
    "sw sub sub";
  align-items: baseline;
  column-gap: 9px;
  row-gap: 1px;
  padding: 7px 10px;
  border-radius: 10px;
  /* Glass: translucent fill over the card, plus a lighter top edge. The edge is
     what actually reads as glass — a uniform 1px border just looks like an
     outlined card. */
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  background: color-mix(in srgb, var(--card) 46%, transparent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 26%, transparent),
    0 4px 14px -8px rgba(0, 0, 0, .22);
  -webkit-backdrop-filter: blur(18px) saturate(170%);
  backdrop-filter: blur(18px) saturate(170%);
  transition: background var(--dur-fast), border-color var(--dur-fast);
}
  .pay-lg:hover {
    background: color-mix(in srgb, var(--card) 62%, transparent);
    border-color: color-mix(in srgb, var(--border) 78%, transparent);
  }
  /* Each legend row is washed with the colour of its own donut slice, passed in
     as --lg by _renderPaymentChart. A neutral row list meant the only link
     between a figure and its arc was a 10px swatch; the shared hue makes the
     pairing obvious, and Gross COD reads green for the same reason its arc
     does. The wash is deliberately weak - a saturated fill at this size would
     out-shout the figures. */
  .pay-lg {
    background: color-mix(in srgb, var(--lg, var(--card)) 15%, transparent);
    border-left: 3px solid var(--lg, var(--border));
  }
  .pay-lg:hover {
    background: color-mix(in srgb, var(--lg, var(--card)) 26%, transparent);
  }
.pay-lg-sw { grid-area: sw; width: 10px; height: 10px; border-radius: 3px; align-self: center; }
.pay-lg-k { grid-area: key; font-size: 11px; line-height: 1.25; font-weight: 600; color: var(--label); }
.pay-lg-v {
  grid-area: val;
  font-size: 13.5px;
  /* Line-height has to be stated, not inherited: the card sets ~1.5, which at
     13.5px made this the tallest item in the row and pushed the whole legend
     past the height of the donut it sits beside. */
  line-height: 1.15;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.pay-lg-s { grid-area: sub; font-size: 9.5px; color: var(--muted); line-height: 1.3; }
.pay-donut-box { display: flex; align-items: center; justify-content: center; min-width: 0; }
/* The donut carries the shape of the split; the four figures beside it carry the
   exact money. Grown from 168px so the arcs are readable without hovering. */
.pay-donut { width: 214px; height: 214px; display: block; }
/* The arcs are the hover target for this chart. SVG paths only receive pointer
   events over their painted geometry, and a donut ring is a thin band — at
   168px the ring is ~16px wide but a 4%-slice arc is a sliver, hard to acquire.
   A transparent stroke laid over the same geometry doubles the hit area
   without painting anything, so the menu is reachable across the whole band. */
.pay-donut .pay-arc { cursor: pointer; pointer-events: visiblePainted; }
.pay-donut .pay-arc-hit { cursor: pointer; pointer-events: stroke; }
/* Sized against the hole, which is rI*2 = 74 user units wide. The widest string
   fmtCompactMoney can emit is 11 characters ("PKR 999.99M"), and at 12 units that
   measured 136px inside a 137px hole - technically inside, but touching the ring
   under any font fallback. 10.5 leaves real margin and still reads large. */
 .pay-donut-cap { font-size: 13px; fill: var(--muted); font-family: inherit; }
 .pay-donut-val { font-size: 19px; font-weight: 700; fill: var(--text); font-family: inherit; letter-spacing: -.4px; }
@media (max-width: 1180px) {
  .pay-donut-wrap { grid-template-columns: minmax(0, 1fr); }
  .pay-donut-box { order: -1; }
}

/* ---- Analytics: courier cards, more air and no run-together text ----
   Rates sit side by side on one row (the divider column collapsed them into an
   unreadable stack) and the comparison note is a footnote under them, not
   jammed against the courier name. */
.analytics-view .cq-cards { gap: 12px; }
.analytics-view .cq-card { padding: 14px 15px; gap: 12px; }
.analytics-view .cqc-top { margin-bottom: 4px; }
.analytics-view .cqc-nm { font-size: 13px; }
.analytics-view .cqc-duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-top: 0;
}
.analytics-view .cqc-sep { display: none; }
.analytics-view .cqc-rate {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin-top: 0;
  padding: 7px 9px;
  border-radius: 9px;
  background: color-mix(in srgb, var(--elev) 52%, transparent);
  font-size: 17px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.analytics-view .cqc-rate i { font-size: 11px; font-style: normal; }
.analytics-view .cqc-rate em {
  margin-left: auto;
  font-size: 9.5px;
  font-style: normal;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--muted);
}
.analytics-view .cqc-flag {
  display: block;
  margin: 0;
  padding: 6px 9px;
  border-radius: 8px;
  border: 1px solid color-mix(in srgb, #F5A524 40%, transparent);
  background: color-mix(in srgb, #F5A524 10%, transparent);
  color: #8a5c05;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.35;
  text-transform: none;
  letter-spacing: 0;
}
[data-theme="dark"] .analytics-view .cqc-flag { color: #F5C46B; }
/* Stats grid: four stacked cells. The number sits on its own line above the
   caption — inline <b>/<i> collapsed them into one run of text. */
.analytics-view .cqc-stats {
  /* Deliberate 2x2. Four columns on a card this narrow squeezed each cell until
     "In Transit" wrapped onto a second line inside its own cell, which read as
     the card having a stray extra row. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 9px 12px;
  padding: 11px 0;
  border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.analytics-view .cqc-stats > div {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 0;
}
.analytics-view .cqc-stats b {
  font-size: 13px;
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.analytics-view .cqc-stats b.cqc-d { color: #22C55E; }
.analytics-view .cqc-stats b.cqc-r { color: #EF4F57; }
.analytics-view .cqc-stats b.cqc-t { color: #3B82F6; }
.analytics-view .cqc-stats i {
  font-size: 8.5px;
  font-style: normal;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .3px;
  color: var(--muted);
  line-height: 1.2;
}
/* ============================================================
   AI ASSISTANT
   Two surfaces: the chat itself, and the provider-key screen in Settings.
   Both use the same glass/ring language as the rest of the app rather than
   borrowing the prototype's flat white cards, which read as a different app.
   ============================================================ */

/* ---- chat shell ----
   A row: history rail + conversation. The rail is 240px and collapses to zero
   width rather than display:none, so the conversation reflows instead of
   jumping. min-height:0 on the flex column is what lets the message list scroll
   internally instead of stretching the page. */
  .chat-view {
    display: flex;
    /* The history rail sits on the RIGHT. It was on the left, directly beside the
       app's own navigation, so the two vertical panels read as one broken
       sidebar - two rails of stacked text with a gap between them. Flipping the
       axis keeps the conversation next to the app nav and moves history to the
       far edge, where it cannot be mistaken for it. */
    flex-direction: row-reverse;
    flex: 1;
    min-height: 0;
    height: 100%;
    max-height: 100%;
    overflow: hidden;
    /* Deliberately no background of its own: the route sits inside #content and
       inherits the app's existing page wash, so the chat is not a second,
       competing gradient sitting on top of the main one. */
  }

/* ---- history rail ---- */
.chat-side {
    flex: none;
    width: 240px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    /* Fixed, so the rail genuinely touches the top and bottom of the screen. It
       was `position: sticky; height: 100%`, which resolved against the chat
       host - and that host is 100vh minus 80px for the top bar, so the rail
       stopped short and left a dead band under it. Fixed against the viewport
       removes the dependency entirely. */
    position: fixed;
    top: 0; right: 0; bottom: 0;
    z-index: 30;
    padding: 18px 12px 16px;
    /* Dividing line on the left now that the rail is the right-hand panel. */
    border-left: 1px solid color-mix(in srgb, var(--border) 62%, transparent);
    background: var(--chat-rail);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    transition: width .28s cubic-bezier(.4, 0, .2, 1),
                opacity .22s ease, padding .28s ease, border-color .28s ease;
    overflow: hidden;
}
/* Out of flow now, so the conversation reserves the rail's width itself. */
.chat-view:has(.chat-side.is-open) .chat-main { margin-right: 240px; }
  /* Collapsed. Width and padding collapse together so nothing is clipped oddly. */
  .chat-view:not(:has(.chat-side.is-open)) .chat-side {
    width: 0;
    padding-left: 0;
    padding-right: 0;
    opacity: 0;
    border-left-color: transparent;
    pointer-events: none;
  }
.chat-side-new {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; padding: 7px 10px;
  border-radius: 10px; cursor: pointer; font-family: inherit;
  font-size: 12px; font-weight: 600; white-space: nowrap;
    color: var(--text);
  background: var(--chat-rail);
    border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
    box-shadow: 0 2px 4px rgba(0, 0, 0, .03);
    transition: background .18s ease, color .18s ease, box-shadow .18s ease;
  }
  .chat-side-new:hover {
    color: var(--primary);
  background: var(--chat-rail-solid);
    box-shadow: 0 10px 25px -5px rgba(0, 0, 0, .09);
  }
  .chat-side-title {
    margin: 16px 8px 8px;
    font-size: 10px; font-weight: 700; letter-spacing: .07em;
    text-transform: uppercase; color: var(--muted); white-space: nowrap;
  }
  .chat-side-list { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
  .chat-thread {
    display: flex; align-items: center; gap: 9px;
    width: 100%; padding: 9px 11px;
    border: 0; border-radius: 10px; cursor: pointer; font-family: inherit;
    font-size: 12.5px; color: var(--label); text-align: left;
    background: transparent;
    transition: background .16s ease, color .16s ease;
  }
  .chat-thread:hover { background: var(--chat-rail); color: var(--primary); }
  .chat-thread.is-active {
  background: var(--chat-rail-solid);
    color: var(--primary); font-weight: 600;
    box-shadow: 0 2px 4px rgba(0, 0, 0, .04);
  }
  /* Wrapper introduced with the per-thread delete button. Without this the two
     buttons stack vertically and every entry grows a second line, which reads as
     a gap between chats. */
  .chat-thread-row { position: relative; display: flex; align-items: center; }
  .chat-thread-row > .chat-thread { flex: 1; min-width: 0; }
  .chat-thread-row.is-active > .chat-thread { background: var(--chat-rail-solid); }

  .chat-thread-ic { flex: none; display: flex; opacity: .8; }
  .chat-thread-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* Per-thread overflow menu. Hidden until the row is hovered or the button has
     focus, so the rail stays clean without dropping keyboard access. */
  .chat-thread { position: relative; }
  .chat-thread-more {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; margin-right: -3px; padding: 0;
    border: 0; border-radius: 6px; cursor: pointer; font-family: inherit;
    color: var(--muted); background: transparent;
    opacity: 0; transition: opacity .15s ease, background .15s ease, color .15s ease;
  }
  .chat-thread:hover .chat-thread-more,
  .chat-thread-more:focus-visible,
  .chat-thread-more[aria-expanded="true"] { opacity: 1; }
  .chat-thread-more:hover { background: color-mix(in srgb, var(--muted) 18%, transparent); color: var(--text); }

  .chat-thread-menu {
    position: absolute; right: 4px; top: calc(100% + 2px); z-index: 12;
    min-width: 132px; padding: 4px;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--card); color: var(--text);
    box-shadow: 0 12px 30px rgba(0, 0, 0, .22);
  }
  .chat-thread-menu button {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 7px 9px; border: 0; border-radius: 7px; cursor: pointer;
    font-family: inherit; font-size: 11.5px; text-align: left;
    background: transparent; color: var(--danger);
  }
  .chat-thread-menu button:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }
  .chat-side-empty { padding: 14px 10px; font-size: 11.5px; line-height: 1.55; color: var(--muted); }

/* ---- conversation column ----
   There is no header element any more: it rendered as an empty band between the
   top of the column and the first message, which read as a dead strip. */
.chat-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* The toggle moves to the right edge with the rail it controls. It was the
   first child of .chat-top, so once the rail flipped to the right the button
   stayed behind on the far left - pointing at a panel it could no longer reach.
   Closed it sits on the right edge; open it docks INSIDE the rail, and the
   rail's New Chat button shifts right to make room for it. */
.chat-view { position: relative; }
.chat-side-toggle {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; cursor: pointer;
  border-radius: 10px; font-family: inherit;
  color: var(--muted);
  background: var(--chat-rail);
  border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
  backdrop-filter: blur(10px);
  position: absolute; top: 14px; right: 10px;
  z-index: 25;
  transition: background .18s ease, color .18s ease, transform .18s ease,
              right .28s cubic-bezier(.4, 0, .2, 1);
}
.chat-view:has(.chat-side.is-open) .chat-side-toggle {
  /* The rail is fixed to the viewport, but this button is still a child of
     .chat-main, whose right edge sits inside .main's gutter - so right:196px
     measured against .chat-view would have missed the rail entirely once the
     rail stopped being a flex sibling. Going fixed puts the button in the same
     coordinate space as the rail: 14px in from its top-right corner.
     z-index has to clear the rail's 30, or the button docks inside the rail
     and vanishes behind it the moment the rail opens. */
  position: fixed;
  top: 16px; right: 14px;
  z-index: 31;
}
/* Reserve that top-right corner so the rail's own button never sits under it. */
.chat-view:has(.chat-side.is-open) .chat-side { padding-top: 58px; }
  .chat-side-toggle:hover { color: var(--primary); background: var(--card); transform: scale(1.04); }
/* One rule, not two. The footer previously had a flex definition earlier in
   this file and a text-align definition later; the later one silently kept the
   flex (display was never reset) while fighting over the margin, so the note
   and the status line landed left/right by accident of which properties
   survived. space-between is the intent: note left, status right. */
.chat-foot {
  max-width: 760px; margin: 10px auto 0;
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
}
.chat-foot-note {
  font-size: 11px; color: var(--muted); white-space: nowrap;
}
/* Status and errors ride here now instead of in the removed header. It is
   flex:1 in the old rule because it shared a bar with a model label; in a
   footer it only needs to take the slack on its own. */
.chat-sub {
  flex: 1; min-width: 0; font-size: 11px; color: var(--muted);
  text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-sub:empty { display: none; }
.chat-sub.is-err { color: var(--danger); }

  /* ---- scroll area + centred hero ---- */
  .chat-scroll {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: 8px 22px 24px;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
  }
  .chat-hero {
    max-width: 620px; margin: 0 auto; padding: 4vh 20px 24px;
    text-align: center;
    display: flex; flex-direction: column; align-items: center; gap: 14px;
    animation: chat-fade-up .7s ease-out forwards;
  }
  .chat-hero-avatar {
    width: 52px; height: 52px; border-radius: 16px;
    display: flex; align-items: center; justify-content: center;
    color: var(--primary);
  background: var(--chat-rail-solid);
    border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
    box-shadow: 0 10px 25px -8px rgba(0, 0, 0, .16);
    animation: chat-fade .7s ease-out forwards;
  }
  .chat-hero-title {
    margin: 0;
    font-family: var(--font-display, inherit);
    font-size: 30px; font-weight: 800; line-height: 1.18;
    letter-spacing: -.03em; color: var(--text);
    min-height: 1.2em;
  }
  .chat-hero-sub {
    margin: 0; max-width: 520px;
    font-size: 13.5px; line-height: 1.6; color: var(--muted);
    animation: chat-fade-up .7s ease-out .1s both;
  }
  /* Blinking bar after the typed headline, removed by JS when it finishes. */
  .typing-cursor::after {
    content: '';
    display: inline-block; width: 2px; height: .95em;
    margin-left: 3px; vertical-align: -.06em;
    background: var(--primary); border-radius: 1px;
    animation: chat-blink .8s steps(2, start) infinite;
  }
  .chat-suggestions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 4px; }
  .chat-chip {
    padding: 7px 13px; border-radius: 999px;
    font-size: 11.5px; font-weight: 500; font-family: inherit; cursor: pointer;
    border: 1px solid color-mix(in srgb, var(--border) 62%, transparent);
  background: var(--chat-chip);
    color: var(--label);
    transition: background .15s ease, border-color .15s ease, transform .15s ease;
  }
  .chat-chip:hover {
    background: var(--primary-soft);
    border-color: color-mix(in srgb, var(--primary) 40%, transparent);
    transform: translateY(-1px);
  }

  /* ---- transcript ---- */
  .chat-list {
    display: flex; flex-direction: column; gap: 16px;
    max-width: 760px; margin: 0 auto;
  }
  .chat-row { display: flex; flex-direction: column; gap: 5px; }
  .chat-row.is-user { align-items: flex-end; }
  .chat-bubble {
    max-width: 82%;
    padding: 11px 14px;
    border-radius: 16px;
    font-size: 13px; line-height: 1.65;
    color: var(--text);
  background: var(--chat-bubble);
    border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  }
  .chat-row.is-user .chat-bubble {
    color: var(--chat-bubble-user-text);
    background: var(--primary);
    border-color: transparent;
    border-bottom-right-radius: 6px;
  }
  .chat-row.is-assistant .chat-bubble { border-bottom-left-radius: 6px; }
  .chat-row.is-error .chat-bubble { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
  .chat-text { white-space: pre-wrap; overflow-wrap: anywhere; }
  .chat-meta { font-size: 10px; color: var(--muted); padding: 0 4px; }
  .chat-tool { font-size: 12px; font-style: italic; color: var(--muted); }
  .chat-dots { display: inline-flex; gap: 4px; padding: 3px 0; }
  .chat-dots i {
    width: 5px; height: 5px; border-radius: 50%;
    background: color-mix(in srgb, var(--muted) 70%, transparent);
    animation: chat-bounce 1.1s ease-in-out infinite;
  }
  .chat-dots i:nth-child(2) { animation-delay: .16s; }
  .chat-dots i:nth-child(3) { animation-delay: .32s; }

  /* Blinking cursor while tokens stream in. */
  .chat-caret {
    display: inline-block; width: 2px; height: 1.02em;
    margin-left: 2px; vertical-align: text-bottom;
    background: var(--primary); border-radius: 1px;
    animation: chat-blink 1.05s steps(2, start) infinite;
  }

  /* ---- docked composer ----
     Bottom padding is generous so the composer floats clear of the window edge
     instead of sitting flush against it. */
  .chat-dock { flex: none; padding: 4px 22px 30px; }
/* The composer fill is written per theme with a literal value rather than a
     --chat-* token. The inherited token resolved correctly on this element while
     the background property consuming it stayed stale, so a theme flip left a
     white input sitting on a dark page. A literal keyed off the attribute does
     not depend on custom-property inheritance being invalidated. */
.chat-inputwrap {
      max-width: 760px; margin: 0 auto;
      padding: 13px 16px;
      border-radius: 20px;
      background: var(--chat-glass);
      backdrop-filter: blur(16px);
      -webkit-backdrop-filter: blur(16px);
      border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
      box-shadow: 0 20px 25px -5px rgba(0, 0, 0, .08), 0 8px 10px -6px rgba(0, 0, 0, .03);
      transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
    }
  /* Focus changes the ring, not the fill. Calmer, and it keeps one rule owning
     the background so a theme flip has a single property to re-resolve. */
  .chat-inputwrap:focus-within {
    border-color: color-mix(in srgb, var(--primary) 50%, transparent);
    box-shadow: 0 20px 25px -5px color-mix(in srgb, var(--primary) 14%, transparent),
                0 8px 10px -6px rgba(0, 0, 0, .04);
  }
  .chat-inputbox { display: flex; align-items: flex-end; gap: 12px; }
  .chat-inputbox textarea {
    flex: 1; min-width: 0;
    border: 0; outline: 0; resize: none;
    background: transparent; color: var(--text);
    font-family: inherit; font-size: 13.5px; line-height: 1.5;
    max-height: 180px; min-height: 26px; padding: 2px 0;
  }
  .chat-inputbox textarea::placeholder { color: color-mix(in srgb, var(--muted) 85%, transparent); }
  .chat-send {
    flex: none;
    width: 38px; height: 38px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    border: 0; border-radius: 12px; cursor: pointer;
    color: #fff; background: var(--primary);
    transition: background .18s ease, transform .18s ease, opacity .18s ease;
  }
  .chat-send:hover { background: var(--primary-dark); transform: scale(1.05); }
  .chat-send:disabled { opacity: .45; cursor: not-allowed; transform: none; }

  /* Shown instead of the conversation when no provider is configured. Sits above
     the dock so the composer stays reachable and the way out stays obvious. */
  .chat-setup-note {
    max-width: 760px; margin: 0 auto 12px;
    display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
    padding: 16px 18px; border-radius: 16px;
    border: 1px solid color-mix(in srgb, var(--primary) 34%, transparent);
    background: color-mix(in srgb, var(--primary-soft) 60%, transparent);
  }
  .chat-setup-note b { font-size: 13px; color: var(--text); }
  .chat-setup-note span { font-size: 12px; line-height: 1.55; color: var(--muted); max-width: 62ch; }

  /* ---- dark mode ----
     Nothing to write here. Every glass surface reads a --chat-* token that
     tokens.css defines per theme, so flipping data-theme re-tints them together.
     The hero avatar is the one exception: it wants the solid surface in both
     themes rather than the translucent one. */
  [data-theme="dark"] .chat-hero-avatar { background: var(--chat-rail-solid); }

/* The rail is an overlay on narrow screens rather than a second column. It
   slides in from the RIGHT, matching where it lives on wide screens; it was
   still pinned to inset left, so on mobile the history opened over the
   conversations while the toggle it is reached from sat on the opposite edge. */
@media (max-width: 860px) {
  .chat-side {
    position: absolute; inset: 0 0 0 auto; z-index: 5;
    width: 240px; opacity: 0; pointer-events: none;
    box-shadow: 0 0 40px rgba(0, 0, 0, .3);
  }
    .chat-side.is-open { opacity: 1; pointer-events: auto; }
    .chat-view:not(:has(.chat-side.is-open)) .chat-side {
      width: 240px; padding-left: 12px; padding-right: 12px;
    }
    .chat-view { position: relative; }
    .chat-dock, .chat-top { padding-left: 14px; padding-right: 14px; }
    .chat-scroll { padding-left: 14px; padding-right: 14px; }
    .chat-hero-title { font-size: 24px; }
  }

  @keyframes chat-fade { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
  @keyframes chat-fade-up { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
  @keyframes chat-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
  @keyframes chat-bounce {
    0%, 60%, 100% { transform: translateY(0); opacity: .45; }
    30% { transform: translateY(-4px); opacity: 1; }
  }
  @media (prefers-reduced-motion: reduce) {
    .chat-caret, .typing-cursor::after, .chat-dots i { animation: none; }
    .chat-hero, .chat-hero-avatar, .chat-hero-sub { animation: none; opacity: 1; }
    .chat-side { transition: none; }
  }
/* ---- Settings: AI Assistant ---- */

/* Settings takes over the viewport, but the app sidebar is still there and is
   how you navigate away from Settings, so it has to stay visible. inset:0 spans
   the full width including that sidebar: the settings rail sat flush against
   x=0 and the content slid under the sidebar with no gap at all. Inset by the
   live sidebar width, collapsed or not, matching how the city report already
   handles the same overlay. */
  .settings-view { left: var(--sidebar-w); }
  #sidebar.collapsed ~ #main .settings-view { left: var(--sidebar-w-collapsed); }
  @media (max-width: 768px) { .settings-view { left: 0; } }
.ai-settings { display: flex; flex-direction: column; gap: 18px; max-width: 860px; }
.ai-intro { display: flex; flex-direction: column; gap: 4px; }
.ai-intro b { font-size: 13.5px; color: var(--text); }
.ai-intro span { font-size: 11.5px; line-height: 1.6; color: var(--muted); max-width: 70ch; }

.ai-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
@media (max-width: 760px) { .ai-cards { grid-template-columns: minmax(0, 1fr); } }

.ai-card {
  padding: 15px 16px;
  border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 62%, transparent);
  display: flex; flex-direction: column; gap: 9px;
  transition: border-color .16s ease, background .16s ease;
}
.ai-card.is-active {
  border-color: color-mix(in srgb, var(--primary) 48%, transparent);
  background: color-mix(in srgb, var(--primary) 6%, transparent);
}
.ai-card-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ai-radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.ai-radio input { position: absolute; opacity: 0; width: 0; height: 0; }
.ai-radio-mark {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--border) 80%, transparent);
  position: relative; flex: 0 0 15px;
  transition: border-color .15s ease;
}
.ai-radio input:checked + .ai-radio-mark { border-color: var(--primary); }
.ai-radio input:checked + .ai-radio-mark::after {
  content: ''; position: absolute; inset: 2.5px;
  border-radius: 50%; background: var(--primary);
}
.ai-radio input:focus-visible + .ai-radio-mark {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent);
}
.ai-card-name { font-size: 12.5px; font-weight: 700; color: var(--text); }
.ai-card-doc { font-size: 10px; color: var(--muted); margin-top: -4px; }
.ai-card-model { margin-top: 6px; font-weight: 600; color: var(--text); }

.ai-key-state {
  font-size: 9.5px; font-weight: 600; white-space: nowrap;
  padding: 3px 9px; border-radius: 999px;
  color: var(--muted);
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.ai-key-state.is-ok {
  color: #16A34A;
  border-color: color-mix(in srgb, #22C55E 45%, transparent);
  background: color-mix(in srgb, #22C55E 10%, transparent);
}
[data-theme="dark"] .ai-key-state.is-ok { color: #4ADE80; }

.ai-key-row { display: flex; gap: 8px; }
.ai-key-row input {
  flex: 1; min-width: 0; height: 34px; padding: 0 11px;
  border-radius: 9px; font-family: inherit; font-size: 11px;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  background: var(--elev); color: var(--text); outline: none;
}
.ai-key-row input:focus {
  border-color: color-mix(in srgb, var(--primary) 55%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent);
}
.ai-mini {
  height: 34px; padding: 0 11px; flex: 0 0 auto;
  border-radius: 9px; cursor: pointer; font-family: inherit;
  font-size: 10px; font-weight: 600;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  background: var(--card); color: var(--label);
}
.ai-mini:hover { background: var(--elev); }
.ai-mini:disabled { opacity: .55; cursor: not-allowed; }

.ai-hint { font-size: 9.5px; color: var(--muted); line-height: 1.5; }
.ai-hint code {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 9.5px;
  padding: 1px 4px; border-radius: 4px;
  background: var(--elev); color: var(--label);
}
.ai-model-row { display: flex; gap: 8px; }
.ai-model-row select {
  flex: 1; min-width: 0; height: 32px; padding: 0 9px;
  border-radius: 9px; font-family: inherit; font-size: 10.5px;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  background: var(--elev); color: var(--text); outline: none;
}
.ai-model-current { font-size: 9.5px; color: var(--muted); margin-top: -4px; }
.ai-model-current b { color: var(--label); font-weight: 600; }

.ai-actions { display: flex; gap: 8px; margin-top: 2px; }
.ai-actions .btn-solid,
.ai-actions .btn-ghost { height: 30px; font-size: 10.5px; padding: 0 12px; border-radius: 8px; }

.ai-note { display: flex; flex-direction: column; gap: 6px; }
.ai-note label { font-size: 11px; font-weight: 600; color: var(--label); }
.ai-note label span { font-weight: 400; color: var(--muted); }
.ai-note textarea {
  padding: 9px 11px; border-radius: 10px; resize: vertical;
  font-family: inherit; font-size: 11.5px; line-height: 1.55;
  border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  background: var(--elev); color: var(--text); outline: none;
}
.ai-note textarea:focus {
  border-color: color-mix(in srgb, var(--primary) 55%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent);
}
.ai-note .hint { font-size: 9.5px; color: var(--muted); }

/* Shown when the ai-assistant function is not deployed yet. The form stays
   usable below it — this is a first-run state, not a dead end. */
.ai-offline {
  display: flex; flex-direction: column; gap: 5px;
  padding: 13px 15px; border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 8%, transparent);
}
.ai-offline b { font-size: 12px; color: var(--text); }
.ai-offline span { font-size: 10.5px; line-height: 1.55; color: var(--muted); }
.ai-offline code {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 10px; padding: 1px 5px; border-radius: 4px;
  background: var(--elev); color: var(--label);
}

/* Where the key actually lives. Both states are stated plainly rather than the
   browser one being dressed up as the secure one — the difference matters to
   whoever is deciding whether to share this dashboard. */
.ai-transport {
  display: flex; flex-direction: column; gap: 4px;
  padding: 12px 14px; border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--warn) 42%, transparent);
  background: color-mix(in srgb, var(--warn) 7%, transparent);
}
.ai-transport b { font-size: 11.5px; color: var(--text); }
.ai-transport span { font-size: 10.5px; line-height: 1.6; color: var(--muted); }
.ai-transport.is-vault {
  border-color: color-mix(in srgb, #22C55E 40%, transparent);
  background: color-mix(in srgb, #22C55E 7%, transparent);
}

.ai-status { font-size: 11px; min-height: 16px; color: var(--muted); margin: 2px 0 8px; }
.ai-status.is-ok { color: #16A34A; }
.ai-status.is-error { color: var(--danger); }
[data-theme="dark"] .ai-status.is-ok { color: #4ADE80; }

/* Worker URL block. Highlighted only once a URL is saved, so "no worker" is
   visible at a glance instead of silently falling back to the browser. */
.ai-worker {
  display: flex; flex-direction: column; gap: 6px;
  padding: 14px; border-radius: 12px;
  border: 1px dashed color-mix(in srgb, var(--muted) 38%, transparent);
  background: color-mix(in srgb, var(--muted) 5%, transparent);
  margin-bottom: 12px;
}
.ai-worker.is-set {
  border-style: solid;
  border-color: color-mix(in srgb, #22C55E 40%, transparent);
  background: color-mix(in srgb, #22C55E 6%, transparent);
}
.ai-worker > label { font-size: 11.5px; font-weight: 700; color: var(--text); }
.ai-worker-row { display: flex; gap: 8px; align-items: center; }
.ai-worker-row input {
  flex: 1; min-width: 0;
  font-size: 12px; font-family: var(--mono, monospace);
  padding: 8px 10px; border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface, var(--bg));
  color: var(--text);
}
.ai-worker-row input:focus { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 1px; }
.ai-worker .hint { font-size: 10.5px; line-height: 1.6; color: var(--muted); }
.ai-worker code, .ai-keys-help code {
  font-family: var(--mono, monospace); font-size: 10.5px;
  padding: 1px 5px; border-radius: 5px;
  background: color-mix(in srgb, var(--muted) 14%, transparent);
  color: var(--text);
}

/* Copy-pasteable secret command. Wraps rather than overflowing, because the
   company code is part of the string and company codes vary in length. */
.ai-keys-help {
  display: flex; flex-direction: column; gap: 6px;
  margin-top: 14px; padding: 12px 14px; border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}
.ai-keys-help b { font-size: 11.5px; color: var(--text); }
.ai-keys-help span { font-size: 10.5px; line-height: 1.6; color: var(--muted); }
.ai-keys-help code { word-break: break-all; white-space: normal; }

.ai-card.is-active {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}
.ai-card:has(.ai-radio input:disabled) { opacity: .72; }
.ai-key-state.is-ok { color: #16A34A; font-size: 10.5px; font-weight: 700; }
[data-theme="dark"] .ai-key-state.is-ok { color: #4ADE80; }

/* ---- City drawer KPI grid ----
   Replaces the bespoke .city-summary strip so the drill-down reads in the same
   card language as the dashboard it was opened from. */
.city-kpi-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}
.city-kpi-grid .kpi {
  height: auto;
  min-height: 82px;
  padding: 12px 14px;
  gap: 3px;
}
.city-kpi-grid .kpi .num { font-size: 24px; }
.city-kpi-grid .kpi .num.money { font-size: 19px; }
.city-kpi-grid .kpi .lbl { font-size: 10px; }
.city-kpi-grid .kpi .foot { font-size: 9.5px; color: var(--muted); margin-top: 2px; }
@media (max-width: 620px) {
  .city-kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- Period drawer: KPI cards, not label/value rows ----
   The drawer used label/value pairs while the panel it opened from showed KPI
   cards, so the same numbers arrived in two different visual languages. Now it
   reuses .card.kpi and reads identically. */
.analytics-view .sd-kpis,
.sales-drawer .sd-kpis {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}
.sales-drawer .sd-kpis-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.sales-drawer .sd-kpis .kpi {
  height: auto;
  min-height: 86px;
  padding: 13px 14px;
  gap: 3px;
}
.sales-drawer .sd-kpis .kpi .num { font-size: 26px; }
.sales-drawer .sd-kpis .kpi .lbl { font-size: 10px; }
.sales-drawer .sd-kpis .kpi .foot {
  font-size: 9.5px;
  color: var(--muted);
  margin-top: 2px;
}
@media (max-width: 520px) {
  .sales-drawer .sd-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- Payment & Settlement: the chart is the hover target ----
   The menu moved off the legend rows and onto the arcs, so the arcs need a
   visible hover response or they look inert. */
.analytics-view .pay-arc {
  cursor: pointer;
  transition: filter .13s ease, opacity .13s ease;
  outline: none;
}
.analytics-view .pay-arc:hover,
.analytics-view .pay-arc:focus-visible { filter: brightness(1.16) saturate(1.12); }
.analytics-view .pay-lg { cursor: default; }

/* Three-band mix bar: delivered green, returned red, in transit blue. */
.analytics-view .cq-mixbar[data-mix="1"] {
  height: 8px;
  border-radius: 4px;
  margin: 10px 0 0;
  overflow: hidden;
  background: color-mix(in srgb, var(--border) 70%, transparent);
}
.analytics-view .cq-mixbar[data-mix="1"] > i {
  display: block;
  height: 100%;
  flex: 0 0 auto;
}
.analytics-view .cq-mix-del { background: #22C55E; }
.analytics-view .cq-mix-ret { background: #EF4F57; }
.analytics-view .cq-mix-tra { background: #3B82F6; }
/* The three bands are only distinguishable by hue, which is exactly what a
   colour-blind reader or a greyscale print cannot resolve. Each band now lifts
   on hover and names itself, so the meaning never depends on the colour alone. */
.analytics-view .cq-mixbar[data-mix="1"] > i {
  transition: filter .12s ease, transform .12s ease;
}
.analytics-view .cq-mixbar[data-mix="1"]:hover > i { filter: brightness(1.12) saturate(1.1); }
.analytics-view .cq-mixbar[data-mix="1"] > i:hover {
  filter: brightness(1.22) saturate(1.15);
  transform: scaleY(1.35);
}
.analytics-view .cq-mixcap {
  display: flex;
  justify-content: space-between;
  gap: 6px;
  margin: 6px 0 0;
  font-size: 9.5px;
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
  color: var(--label);
}
.analytics-view .cq-mixcap span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.analytics-view .cq-mixcap i {
  width: 7px;
  height: 7px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.analytics-view .cq-mixcap i.d { background: #22C55E; }
.analytics-view .cq-mixcap i.r { background: #EF4F57; }
.analytics-view .cq-mixcap i.t { background: #3B82F6; }
.analytics-view .cqc-foot { font-size: 10px; color: var(--muted); }
.analytics-view .cqc-foot-money {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 10px;
}
.analytics-view .cqc-foot-money span { color: var(--muted); }
.analytics-view .cqc-foot-money b {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ---- Popups and overlays stay solid ----
   Panels, cards and charts are glass; anything that floats above them must not
   be, because a translucent dialog lets the page read through the text it is
   meant to contain. These overrides come last so they win over the broad glass
   list further up this file. */
.modal,
.odp,
.drawer,
.exp-modal,
.cq-popup,
.card.modal,
.msel .menu,
.print-bar {
  background: var(--card);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* Pickers and hover menus are small and sit over content, so they keep a trace
   of frost, but they are never see-through enough to misread text. */
.actions-menu,
.acts-menu,
.search-scope-menu,
.date-dropdown-panel,
.tp-panel,
.flt-drop-list,
.flt-panel,
.pp-panel,
.cpick-menu,
.exp-fmt-menu {
  background: color-mix(in srgb, var(--card) 88%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  backdrop-filter: blur(18px) saturate(180%);
}
/* .chart-tooltip is defined once, further up, consolidated with .perf-tooltip. */

/* ---- Analytics: chart hosts are glass, not white ----
   The chart containers carried their own opaque backgrounds, which punched
   solid-white holes in the middle of otherwise translucent panels. */
.analytics-view .chart,
.analytics-view #salesChart,
.analytics-view #perfChart,
.analytics-view #statusChart,
.analytics-view .sc-root,
.analytics-view .sc-view,
.analytics-view .perf-chart-wrap {
  background: transparent;
  border: 0;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* Every plot fills its host, whatever the row count. A chart sized from the
   data shrank to a stub when the range covered only a day or two; two points
   should still be drawn across the full width rather than huddled in the
   middle. */
.analytics-view #perfChart,
.analytics-view #perfChart svg,
.analytics-view .perf-chart-wrap svg,
.analytics-view .sc-root svg,
.analytics-view .sc-root canvas { width: 100%; display: block }
/* Each chart gets its own inset glass plate so the plot area still reads as a
   surface, just a translucent one rather than an opaque white block. */
.analytics-view .analytics-sales > .chart,
.analytics-view #perfChart {
  /* Fixed height rather than content-driven. With no rows to draw the chart
     collapsed to nothing, so the Sales panel visibly shrank and the row beside
     it no longer lined up. The plot keeps its full box whether or not there is
     anything to plot, and the empty state draws inside it. */
  min-height: 300px;
  padding: 12px 12px 6px;
  border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  background: color-mix(in srgb, var(--card) 38%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
}

/* ---- Analytics: Delivered / Returned read green and red ----
   Only the label was tinted by kpi-good / kpi-bad, so the percentage itself —
   the number the card exists to show — stayed neutral grey. The value now
   carries the same meaning as its label.

   Bold as well as coloured: these two are the headline outcome figures, and a
   mid-weight tinted numeral still read as weak next to the neutral cards. */
.analytics-view .kpi-good .num {
  color: #16A34A;
  font-weight: 800;
  -webkit-text-stroke: .3px currentColor;
}
.analytics-view .kpi-bad .num {
  color: var(--danger);
  font-weight: 800;
  -webkit-text-stroke: .3px currentColor;
}
/* Shipping Charges is money leaving the business — a cost, same family as
   Returned. It carried the amber `kpi-warn` label only, so the figure itself
   stayed neutral and read as neither a gain nor a cost. */
.analytics-view .kpi-warn .num { color: var(--danger); }
[data-theme="dark"] .analytics-view .kpi-good .num { color: #4ADE80; }

/* The percentage / raw-count switch. Glassed but only lightly: the button sits
   ON a glass KPI card, so a heavy frost matched the card it sits on and the
   control disappeared into it. A faint edge plus a small tonal lift is enough
   to read as a separate control in both themes. */
.kpi-toggle {
  background: color-mix(in srgb, var(--card) 26%, transparent);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
  backdrop-filter: blur(6px) saturate(120%);
  border-color: color-mix(in srgb, var(--border) 78%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent);
}
.kpi-toggle:hover {
  background: color-mix(in srgb, var(--primary) 20%, transparent);
  border-color: var(--primary);
  color: var(--primary);
}

/* ---- Analytics: one even row, with the three headline cards weighted ----
   sections.css pins .kpis to repeat(7, 1fr), which is a leftover from a seven
   card row and left six cards sharing the width of seven columns. This is a
   single row of equal columns.

   Total Orders / Delivered / Returned are the figures the dashboard exists to
   report, so they get a wider span and a taller card than the three money
   cards beside them. `auto-fit` would collapse that difference on narrow
   screens, hence the explicit minimum. */
/* The Analytics toolbar, once it has been moved into the app's top bar beside
   the profile icon. It was a full-width strip of its own above the KPI row,
   which pushed the whole page down and cost the KPIs a band of height. In the
   bar it is a single right-aligned cluster, and `.top-right` already lays its
   children out in a row - so this only has to stop the old full-width
   assumptions from stretching it. */
.topbar .toolbar-in-topbar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
  width: auto;
  margin: 0;
  padding: 0;
  min-width: 0;
  background: none;
  border: 0;
  box-shadow: none;
}
.topbar .toolbar-in-topbar .toolbar-group { display: flex; align-items: center; gap: 8px; margin: 0 }
.topbar .toolbar-in-topbar .upd { display: none }
/* Refresh and Export stay visible at every width. A rule here hid the .mini-btn
   pair under 1400px to make room for the pickers - and both buttons simply
   disappeared, since those are the only ways to reload or export. They are
   icon-sized, so instead of hiding them the GAP is what gives way. */
.topbar .toolbar-in-topbar .mini-btn { display: inline-flex; flex: 0 0 auto }
@media (max-width: 1200px) {
  .topbar .toolbar-in-topbar { gap: 6px }
  .topbar .toolbar-in-topbar .toolbar-group { gap: 6px }
}

/* KPI text stays neutral. Six figures in a row each painted green or red read
   as a dashboard-wide alarm rather than as one metric moving, and on the money
   row it was the loudest thing on the page. Direction is still carried by the
   arrow, and the sign is still in the number. Colour is reserved for the
   status chips, where it means something discrete. */
.analytics-view .kpi .kdelta.up,
.analytics-view .kpi .kdelta.down,
.analytics-view .kpi .kdelta.flat { color: var(--muted) !important }
.analytics-view .kpi .kd-pct { color: inherit }
.analytics-view .kpi.kpi-good .num,
.analytics-view .kpi.kpi-bad .num,
.analytics-view .kpi.kpi-cash .num,
.analytics-view .kpi.kpi-warn .num,
.analytics-view .kpi .lbl { color: var(--text) !important }
.analytics-view .kpi.kpi-good .lbl,
.analytics-view .kpi.kpi-bad .lbl,
.analytics-view .kpi.kpi-cash .lbl,
.analytics-view .kpi.kpi-warn .lbl { color: var(--muted) !important }

/* Refresh and Export are `.mini-btn`, whose styling was written against the
   page toolbar. In the top bar that context is gone, so they rendered as bare
   text. They are restyled here to match the other top-bar controls. */
.topbar .toolbar-in-topbar .mini-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  height: 30px;
  padding: 0 12px;
  border-radius: 8px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  color: var(--text);
  background: color-mix(in srgb, var(--card) 46%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--border) 58%, transparent);
  transition: border-color .18s ease, background-color .18s ease;
}
.topbar .toolbar-in-topbar .mini-btn:hover {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--card) 66%, transparent);
}
.topbar .toolbar-in-topbar .mini-btn svg { width: 12px; height: 12px; flex: 0 0 12px }
.topbar .toolbar-in-topbar .mini-btn:disabled { opacity: .6; cursor: not-allowed }

.analytics-view .kpis {
        /* Three across, two rows, filling the panel. Six cards in one row left
           each about a sixth of the width with a lot of dead space inside it;
           three per row gives each card real room while still filling the
           full width, and puts the three order counts on row one and the three
           money figures on row two. */
        grid-template-columns: repeat(3, minmax(0, 1fr));
        display: grid;
        gap: 12px;
        margin-bottom: 16px;
}
/* Every card is the same size. The first three still carry `kpi-lg` in the
   markup (they hold the largest figures), and that class carries a 132px
   min-height against the base 118px — so the row rendered at two different
   heights. The class is kept for the figure size, but the box is normalised
   here so all six cards match. */
.analytics-view .kpis > .kpi,
.analytics-view .kpis > .kpi-lg {
  min-height: 118px;
  padding: 14px 14px 12px;
}
.analytics-view .kpis > .kpi-lg .num { font-size: 30px; }
.analytics-view .kpis > .kpi-lg .num.money { font-size: 24px; }
.analytics-view .kpis > .kpi-lg .lbl { font-size: 9.5px; }
.analytics-view #insights { margin-bottom: 16px; }
.analytics-view #analyticsBody > .grid-sales-funnel { margin-top: 0; }

/* ---- Order popup opens centred ----
   `.modal-bg` is a right-anchored drawer by default (align-items: stretch,
   justify-content: flex-end) and `.odp` slides in from the right. Both are
   right for the full-page city report, but an order popup is a peek at one
   record, and hanging it off the right edge pushes it half off-screen on
   narrow windows and reads as a drawer rather than a dialog. */
#orderPopupAn {
  align-items: center;
  justify-content: center;
  padding: 24px;
}
#orderPopupAn > .order-pop {
  width: min(680px, 100%);
  max-height: calc(100vh - 48px);
  border-radius: 16px;
  animation: popIn .22s cubic-bezier(.2, .9, .3, 1.05);
  box-shadow: 0 24px 64px rgba(15, 17, 23, .28);
}
@keyframes popIn {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  #orderPopupAn > .order-pop { animation: none; }
}

/* ---- Performance Overview footer ----
   Three problems in one place. The KPI strip sat flush against the chart with
   no gap, so the two read as one block; the cards were flat tinted fills on a
   glass panel, indistinguishable from the panel behind them; and the chart
   carried a solid card background inside an already-glass panel, so it read as
   a white slab. Cards are now solid and lifted, the chart is a glass plate. */
.analytics-view .perf-kpis {
  margin-bottom: 18px;
  gap: 12px;
}
.analytics-view .perf-kpi {
  background: #FFFFFF;
  color: #1B1E27;
  border-radius: 12px;
  padding: 13px 16px;
  box-shadow: 0 1px 2px rgba(15, 17, 23, .06), 0 6px 18px rgba(15, 17, 23, .10);
  border: 1px solid rgba(15, 17, 23, .05);
  transition: transform .15s ease, box-shadow .15s ease;
}
.analytics-view .perf-kpi:hover {
  transform: translateY(-2px);
  box-shadow: 0 2px 4px rgba(15, 17, 23, .08), 0 10px 26px rgba(15, 17, 23, .14);
}
.analytics-view .perf-kpi .pk-label { color: #6B7280; }
.analytics-view .perf-kpi .pk-value { color: #1B1E27; }
.analytics-view .perf-kpi .pk-sub { color: #6B7280; }
[data-theme="dark"] .analytics-view .perf-kpi {
  background: #1A1D28;
  color: var(--text);
  border-color: rgba(255, 255, 255, .07);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 6px 18px rgba(0, 0, 0, .35);
}
[data-theme="dark"] .analytics-view .perf-kpi .pk-label,
[data-theme="dark"] .analytics-view .perf-kpi .pk-sub { color: var(--muted); }
[data-theme="dark"] .analytics-view .perf-kpi .pk-value { color: var(--text); }

/* The empty-state message is a child of a five-column grid, so it was being
   laid out inside column one — a narrow ribbon of text beside a mostly empty
   row. Span the whole grid instead. */
.analytics-view .perf-empty {
  grid-column: 1 / -1;
  padding: 26px;
  text-align: center;
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.6;
}
.analytics-view .perf-empty b {
  display: block;
  color: var(--text);
  font-size: 13px;
  margin-bottom: 5px;
}

/* Nothing should be hit-testable once a tab has no chart. The wrapper keeps its
   box, so a click landed on an invisible plot.

   The selector used to be :not(:has(canvas)), but SeriesChart renders SVG, not
   canvas - so the condition was permanently true and the Performance chart lost
   every hover, click and drag. Test for real chart content instead: a drawn
   plot is an <svg>, and an empty tab is the "No data"/"Not enough data"
   placeholder text or nothing at all. */
.analytics-view #perfChart:not(:has(svg)):not(:has(canvas)) { pointer-events: none; }

/* ---- Analytics toolbar: Refresh and Export read as buttons ---- */
.analytics-view .toolbar-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 2px 2px 0;
}
.analytics-view .toolbar-top .mini-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 52%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
}
.analytics-view .toolbar-top .mini-btn:hover:not(:disabled) {
  border-color: var(--primary);
  color: var(--primary);
}
.analytics-view .toolbar-top .mini-btn:disabled { opacity: .55; cursor: default; }
.analytics-view .toolbar-top .mini-btn .i { width: 13px; height: 13px; flex: 0 0 auto; }
.analytics-view .toolbar-top .upd { font-size: 10.5px; }

/* The Performance range control sits inline with its tab row and matches the
   date trigger language used everywhere else. */
.analytics-view #perfDateDirect { flex: none; }
.analytics-view #perfDateDirect .tp-wrap-range { min-width: 208px; }
.analytics-view #perfRangeHint { white-space: nowrap; }

/* ---- Detailed City Report: solid white full page, clear of the sidebar ----
   The panel inherited .odp's drawer width and the translucent card background,
   so the table floated over the sidebar and the rows read against the moving
   page behind it. It is now an opaque sheet inset from the sidebar edge. */
/* The overlay is position:fixed inset:0, so it spans the sidebar too â€” the
   report used to slide under it and lose its toolbar. Inset by the live sidebar
   width (expanded or collapsed) so there is always a real gap. */
/* C4 - View All Cities takes over the ENTIRE screen, sidebar included. This
   rule used to inset the panel by the live sidebar width (the old "sheet
   beside the sidebar" design), and because ui-perf.css loads last that inset
   beat sections-delta's `padding: 0` for the same selector - the report never
   actually went full width. .modal-bg is already position:fixed inset:0 at
   z-index 200, well above the sidebar's 50, so removing the insets is all the
   takeover needs. Scoped to #cityReportPage only: the Payment modal keeps the
   shared .modal-bg pattern untouched. */
.analytics-view .modal-bg#cityReportPage {
  background: rgba(9, 13, 24, .55);
  padding: 0;
  justify-content: stretch;
  align-items: stretch;
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
#sidebar.collapsed ~ #main .analytics-view .modal-bg#cityReportPage {
  padding: 0;
}
@media (max-width: 768px) {
  .analytics-view .modal-bg#cityReportPage { padding: 0; }
}
[data-theme="dark"] .analytics-view .modal-bg#cityReportPage { background: rgba(0, 0, 0, .72); }
/* Edge to edge now that the sheet owns the whole viewport. */
.analytics-view .modal-bg#cityReportPage > .odp {
  width: 100%;
  max-width: 100%;
  background: var(--card);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  overflow: hidden;
}
.analytics-view .modal-bg#cityReportPage > .odp .odp-body { flex: 1; overflow-y: auto; }
.analytics-view .modal-bg#cityReportPage .table-scroll {
  background: var(--card);
  border-radius: 12px;
}
/* Same treatment for the city detail sheet so the drill-down is readable too. */
.analytics-view .drawer { background: var(--card); }

/* ---- City report: KPI strip and lazy "show more" ---- */
.analytics-view .city-report-kpis {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 10px;
  margin: 0 0 16px;
}
.analytics-view .city-report-kpis .kpi-mini {
  height: auto;
  min-height: 84px;
  padding: 11px 12px 10px;
  cursor: default;
}
.analytics-view .city-report-kpis .kpi-mini:hover { transform: none; box-shadow: none; }
.analytics-view .city-report-kpis .kpi-mini .num { font-size: 16px; margin-top: 4px; }
.analytics-view .city-report-kpis .kpi-mini .lbl { font-size: 9px; }
.analytics-view .city-report-kpis .kpi-mini .foot {
  margin-top: 5px;
  padding-top: 0;
  font-size: 9.5px;
  line-height: 1.35;
  white-space: normal;
}
.analytics-view .city-report-kpis .kpi-best .num { font-size: 14px; }
@media (max-width: 1100px) {
  .analytics-view .city-report-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.analytics-view .city-report-more {
  display: flex;
  justify-content: center;
  padding: 12px 0 4px;
}
.analytics-view .city-report-more .mini-btn {
  height: 32px;
  padding: 0 16px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 52%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}
.analytics-view .city-report-more .mini-btn:hover { border-color: var(--primary); color: var(--primary); }

/* ---- Order popup opened from Analytics ----
   Solid sheet, not glass: it floats over the city report and a translucent
   dialog lets the table read through the order's own figures. */
.order-pop {
  position: relative;
  width: min(620px, 94vw);
  max-height: 88vh;
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 24px 60px rgba(9, 13, 24, .3);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.order-pop .mh {
  position: sticky;
  top: 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px;
  background: var(--card);
  border-bottom: 1px solid var(--border);
  border-radius: 16px 16px 0 0;
  z-index: 2;
}
.order-pop .mh h3 { font-size: 15px; color: var(--text); margin: 0; }
.order-pop .mh p { font-size: 10.5px; color: var(--muted); margin-top: 3px; }
.order-pop .mh .x {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--muted);
  font-size: 18px; line-height: 1; cursor: pointer; font-family: inherit;
}
.order-pop .mh .x:hover { background: var(--elev); color: var(--text); }
.order-pop .mb { padding: 4px 20px 20px; }
.order-pop .odp-addr {
  padding: 11px 12px;
  border-radius: 10px;
  background: var(--elev);
  color: var(--label);
  font-size: 12px;
  line-height: 1.5;
}

/* ---- Analytics: Performance period popup ----
   Clicking a bucket in the Performance chart opens #periodPopup, which reuses
   the .pp-card / .pp-row class names. Those were only ever styled under
   .orders-view, so outside Orders the dialog rendered as bare unstyled divs â€”
   no panel, no header, no spacing between the figures.
   The popup is appended to document.body, so it is scoped by its own id rather
   than by .analytics-view. */
#periodPopup .pp-card {
  width: min(380px, 92vw);
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--card);
  box-shadow: 0 24px 60px rgba(9, 13, 24, .3);
  overflow: hidden;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
#periodPopup .pp-card .mh {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--elev) 55%, transparent);
}
#periodPopup .pp-card .mh b { display: block; font-size: 14px; font-weight: 700; color: var(--text); }
#periodPopup .pp-card .mh span { display: block; font-size: 10.5px; color: var(--muted); margin-top: 2px; }
#periodPopup .pp-card .mh .x {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: transparent; color: var(--muted);
  font-size: 18px; line-height: 1; cursor: pointer; font-family: inherit;
}
#periodPopup .pp-card .mh .x:hover { background: var(--elev); color: var(--text); }
#periodPopup .pp-card .mb { padding: 14px 18px 18px; }
#periodPopup .pp-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  font-size: 11.5px;
  color: var(--label);
}
#periodPopup .pp-row + .pp-row { border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }
#periodPopup .pp-row b {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ---- Products tab empty state ----
   The markup uses .es-title/.es-body/.es-actions, but only .empty-msg and
   .empty-hint were ever styled. With no rules at all the title inherited body
   font sizing and the body text wrapped into a narrow ribbon in the corner —
   which is exactly what the screenshot showed above the real empty state. */
.analytics-view .empty-state .es-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
  line-height: 1.35;
}
.analytics-view .empty-state .es-body {
  max-width: 460px;
  padding: 0;
  font-size: 12px;
  line-height: 1.6;
  color: var(--muted);
  text-align: center;
}
.analytics-view .empty-state .es-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 4px;
}
.analytics-view .empty-state .es-actions .mini-btn {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 16px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 52%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}
.analytics-view .empty-state .es-actions .mini-btn.solid {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}
.analytics-view .empty-state .es-actions .mini-btn:hover { border-color: var(--primary); color: var(--primary); }
.analytics-view .empty-state .es-actions .mini-btn.solid:hover { color: #fff; }

/* ---- Crosshair cursor over the plots ----
   A plain arrow over a time-series plot gives no hint that the value under it
   is about to be reported. The SVG itself carries the text, so the crosshair
   glyph matches it. */
.sc-root { cursor: crosshair; }
.sc-root.is-panning { cursor: grabbing; }
.sc-reset { cursor: pointer; }

/* ---- Sales drawer opened by clicking a point on the graph ---- */
.sales-drawer {
  width: min(400px, 100%);
  height: 100%;
  background: var(--card);
  border-left: 1px solid var(--border);
  box-shadow: -16px 0 44px rgba(0, 0, 0, .22);
  display: flex;
  flex-direction: column;
}
.sales-drawer .odp-body { flex: 1; overflow-y: auto; padding-bottom: 20px; }
.sales-drawer .sd-rates {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 18px;
}
.sales-drawer .sd-rate {
  padding: 12px 10px;
  border-radius: 12px;
  text-align: center;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 50%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
}
.sales-drawer .sd-rate b {
  display: block;
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.sales-drawer .sd-rate i {
  display: block;
  margin-top: 3px;
  font-size: 9.5px;
  font-style: normal;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--muted);
}
.sales-drawer .sd-rows {
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  border-radius: 12px;
  overflow: hidden;
}
.sales-drawer .pp-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 13px;
  font-size: 11.5px;
  color: var(--label);
}
.sales-drawer .pp-row + .pp-row { border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); }
.sales-drawer .pp-row b {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ---- Whoosh on the Analytics segmented controls ----
   attachGlider already writes width/height/transform onto a single pill, but
   without a transition those writes apply instantly and no motion is visible.
   The easing is deliberately slow — the pill crosses the group rather than
   snapping — which is the same treatment Orders' status chips use. */
.has-glider { position: relative; isolation: isolate; }
.has-glider > * { position: relative; z-index: 1; }
.has-glider .glider-pill,
.has-glider .chip-glider {
  position: absolute;
  z-index: 0;
  top: 0;
  left: 0;
  border-radius: 999px;
  background: var(--primary);
  pointer-events: none;
  will-change: transform, width;
  transition: transform .34s cubic-bezier(.4, 1.3, .5, 1), width .34s cubic-bezier(.4, 1.3, .5, 1),
    height .34s cubic-bezier(.4, 1.3, .5, 1);
}
.has-glider > .seg-btn.active,
.has-glider > .chip.active,
.has-glider > .perf-tab.active {
  color: #fff;
  background: none;
  box-shadow: none;
}
/* The active tab must not paint its own fill underneath the pill. */
.has-glider > .seg-btn { transition: color .2s ease; }

/* ---- liquid tab treatment ----
   Ported from the tabs.html reference: a glass tray holding pill-shaped tabs,
   with the active pill glowing. The reference's SVG gooey filter is
   deliberately absent — see the note in glider.js about it making a containing
   block and breaking every position:fixed modal inside a tab row.

   Scoped to the three tab families asked for rather than to .has-glider
   wholesale: .has-glider also covers Orders status chips, pagination and the
   sidebar, which have their own density and would not survive this sizing. */
/* Performance Overview tabs carry no tray and no per-tab surface. The Finance /
   Products pair was the one place in the dashboard where two tabs sat inside a
   filled box, which boxed them in against the panel they live in. The travelling
   pill alone marks the active tab. */
.perf-tabs.has-glider {
  padding: 0;
  gap: 2px;
  background: none;
  border: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.perf-tabs.has-glider > .perf-tab {
  background: none;
  border: 1px solid transparent;
}
/* Orders status chips: no tray. The chip row sits directly on the card, so the
   sliding pill has nothing to sit in and the row reads as part of the panel
   rather than a control floating on it. Only the glass surface is removed - the
   padding and gap that give the pill room to travel are kept. */
.chips.has-glider {
  padding: 5px 0;
  gap: 6px;
  background: none;
  border: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.chips.has-glider > .chip,
.perf-tabs.has-glider > .perf-tab {
  border-radius: 2px;
  background: color-mix(in srgb, var(--card) 62%, transparent);
  border: 1px solid transparent;
  transition: color .25s ease, background .25s ease, border-color .25s ease;
}
/* The Finance / Products pair opts out of that surface (see above); everything
   else in the chip family keeps it. */
.perf-tabs.has-glider > .perf-tab { background: none; }
.chips.has-glider > .chip:hover,
.perf-tabs.has-glider > .perf-tab:hover { color: var(--text); }
.chips.has-glider > .chip.on,
.perf-tabs.has-glider > .perf-tab.active {
  background: none;
  border-color: transparent;
}

/* The glow and the travel morph. is-animating is added by glider.js for the
   duration of a move and removed again when it settles. */
.chips.has-glider .glider-pill,
.chips.has-glider .chip-glider,
.perf-tabs.has-glider .glider-pill,
.comm-tabs.has-glider .glider-pill,
.comm-views.has-glider .glider-pill,
.comm-filters.has-glider .glider-pill,
.comm-tabs.has-glider-soft .glider-soft {
  box-shadow: 0 6px 18px -4px color-mix(in srgb, var(--primary) 55%, transparent);
}
/* is-animating REPLACES the whole transition property, so background has to be
   repeated here or the channel colour snaps the instant the pill starts moving
   instead of blending. Duration is deliberately long and eased: the pill
   crosses quickly, the colour arrives slowly, so the new channel fades up over
   the old one rather than flipping. */
.chips.has-glider.is-animating .glider-pill,
.chips.has-glider.is-animating .chip-glider,
.perf-tabs.has-glider.is-animating .glider-pill,
.comm-tabs.has-glider.is-animating .glider-pill,
.comm-views.has-glider.is-animating .glider-pill,
.comm-filters.has-glider.is-animating .glider-pill,
.comm-tabs.has-glider-soft.is-animating .glider-soft {
  transform-origin: center;
  transition: transform .2s ease-in, width .2s ease-in, height .2s ease-in,
    background-color .85s cubic-bezier(.32, 0, .18, 1);
}

/* Count badge inside a tab, as in the reference. Optional: a tab without one is
   unaffected. */
.tab-count,
.chip .tab-count,
.comm-tab .tab-count,
.perf-tab .tab-count {
  font-size: .85em; font-weight: 700; opacity: .85; font-variant-numeric: tabular-nums;
}

/* ---- Sidebar section pill ----
   The nav is one logical tab row split across three <nav> groups, so the pill
   is a child of the sidebar rather than of any one group. That is what lets it
   travel between groups — crossing the "Operations" label — instead of resetting
   at each boundary. It has to be white rather than --primary: the sidebar sits
   on the opposite of the theme, and a solid primary bar there reads as a
   different element from the white active link it replaces.

   The container hook is `has-nav-glider`, NOT `has-glider`. The latter carries
   `position: relative` on the container and on its children; on the sidebar
   that ties with `.sidebar { position: fixed }`, wins on source order, and drops
   the sidebar into normal flow — which pushed the entire dashboard ~630px down
   the page. Nothing here sets position at all: the sidebar is already fixed,
   which is the containing block the pill needs. */
.sidebar.has-nav-glider .nav-glider {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  border-radius: var(--r-sm);
  /* Per-section. Every link carries a data-tone, which tokens.css resolves to
     --tone-color, so the active pill carries the colour of the section it marks
     instead of every section sharing one white bar. Colour transitions with the
     pill so switching sections blends rather than snaps. */
  background: var(--tone-color, #FFFFFF);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--tone-color, #1B1E27) 34%, transparent);
  pointer-events: none;
  will-change: transform, width;
  transition: transform .42s cubic-bezier(.4, 1.3, .5, 1), width .42s cubic-bezier(.4, 1.3, .5, 1),
    height .42s cubic-bezier(.4, 1.3, .5, 1), opacity .2s,
    background .38s cubic-bezier(.32, 0, .18, 1),
    box-shadow .38s cubic-bezier(.32, 0, .18, 1);
}
/* Lift the links above the pill, and drop the active link's own white fill so
   the pill is what you actually see. Its dark text and primary icon stay. */
.sidebar.has-nav-glider .nav a { position: relative; z-index: 1; }
.sidebar.has-nav-glider .nav a.active,
.sidebar.has-nav-glider .nav a.active:hover { background: none; }
/* Tone is declared on the link, but the pill is a sibling of the nav, so it
   does not inherit it. JS mirrors the active link's tone onto the pill; this is
   the no-JS / pre-hydration fallback and keeps the two in step if JS is late. */
/* ONE active colour for every section.
   The pill used to inherit --tone-color from the active link's data-tone, so
   Communication was green, Analytics violet, Orders indigo... The active state
   read as a different feature each time instead of as "you are here". The tone
   tokens stay in place for .ot-btn and the KPI cards, where per-section colour
   is meaningful; only the sidebar's active bar is pinned to --primary.
   router.js still mirrors data-tone onto the pill, but it no longer has any
   effect on the fill. */
.sidebar.has-nav-glider .nav-glider,
.sidebar.has-nav-glider[data-tone] .nav-glider {
  background: var(--primary, #3B82F6);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--primary, #3B82F6) 34%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .sidebar.has-nav-glider .nav-glider { transition: none; }
}

/* ---- Appearance: theme choice cards ----
   Two full-width cards rather than a switch: the choice is between two named
   looks, not an on/off, and a switch cannot show which one is active without
   the label changing underneath it. */
.theme-modes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin: 16px 0 12px;
}
.theme-mode {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 16px 15px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--border) 62%, transparent);
  background: color-mix(in srgb, var(--card) 46%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(165%);
  backdrop-filter: blur(18px) saturate(165%);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 20%, transparent);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: var(--text);
  transition: border-color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.theme-mode:hover {
  transform: translateY(-1px);
  border-color: color-mix(in srgb, var(--primary) 55%, transparent);
}
.theme-mode.is-on {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 11%, var(--card));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 34%, transparent),
    0 8px 22px -14px rgba(0, 0, 0, .26);
}
.theme-mode-ic {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--elev);
  color: var(--label);
  border: 1px solid var(--border);
}
.theme-mode-ic .i { width: 19px; height: 19px; }
.theme-mode.is-on .theme-mode-ic {
  background: var(--primary);
  color: #fff;
  border-color: transparent;
}
.theme-mode-t { min-width: 0; flex: 1; }
.theme-mode-t b {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.25;
}
.theme-mode-t span {
  display: block;
  margin-top: 3px;
  font-size: 10.5px;
  color: var(--muted);
  line-height: 1.35;
}
/* The check only appears on the chosen card, so the two options never both look
   active at a glance. */
.theme-mode-check {
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--primary);
  color: #fff;
  opacity: 0;
  transform: scale(.7);
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.theme-mode.is-on .theme-mode-check { opacity: 1; transform: scale(1); }
.theme-mode-check .i { width: 12px; height: 12px; }
.appearance-note {
  font-size: 10.5px;
  color: var(--muted);
  line-height: 1.5;
}
@media (max-width: 620px) {
  .theme-modes { grid-template-columns: 1fr; }
}

/* Without the glider (or before it attaches) the active link still needs its own
   section colour, so the tone is applied to the link itself rather than relying
   entirely on the pill. */
.sidebar .nav a.active {
  background: color-mix(in srgb, var(--tone-color, #FFFFFF) 92%, transparent);
  color: #fff;
}
.sidebar .nav a.active .i,
.sidebar .nav a.active .chev { color: #fff; }
.sidebar .nav a.active .nav-text { color: #fff; }
/* With the pill present the link sits on top of it, so the link must go
   transparent and let the pill's tone show through instead. */
.sidebar.has-nav-glider .nav a.active { background: none; color: #fff; }
.sidebar.has-nav-glider .nav a.active .i,
.sidebar.has-nav-glider .nav a.active .chev,
.sidebar.has-nav-glider .nav a.active .nav-text { color: #fff; }

/* ---- Analytics hover states ---- */
.cq-tr { transition: background var(--dur-fast); }
.cq-tr:hover { background: color-mix(in srgb, var(--primary) 9%, transparent); }
.cq-card { transition: transform var(--dur-fast), box-shadow var(--dur-fast), border-color var(--dur-fast); }
.cq-card:hover {
  transform: translateY(-2px);
  border-color: var(--primary);
  box-shadow: 0 10px 26px rgba(10, 15, 30, .14);
}
.bar-row { border-radius: 8px; transition: background var(--dur-fast); }
.bar-row:hover { background: color-mix(in srgb, var(--primary) 8%, transparent); }
.pay-status-row { transition: border-color var(--dur-fast), background var(--dur-fast); }
.pay-status-row:hover {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 7%, transparent);
}
/* ---- Hover menu: one bold glassed plate ----
   Three separate rules were styling .perf-tooltip at different points in the
   cascade (an opaque card, then a frosted card, then a dark plate), and the
   winner depended on file order. Consolidated here so the menu is defined once:
   a dark frosted plate with a hairline edge and a real drop shadow, so it reads
   clearly on top of the glass panels it floats over. */
.perf-tooltip,
.chart-tooltip {
  background: color-mix(in srgb, var(--tt-bg, #2A2C3B) 92%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid var(--tt-rule, rgba(255, 255, 255, .16));
  border-radius: 10px;
  box-shadow: var(--tt-shadow, 0 8px 24px rgba(15, 17, 23, .32));
  color: var(--tt-fg);
  padding: 12px 14px;
  font-weight: 600;
}
/* The "pop" on hover, from the reference: the arc thickens and takes a glow in
   its own colour. Driven by the class below rather than by CSS :hover - an
   invisible hit-arc is stroked wider than the painted one and sits on top of
   it, so the cursor is over the hit-arc and the painted arc never receives
   :hover. The JS already knows which slice is under the cursor, so it says so
   directly and the pop can never disagree with the menu. */
/* Top Cities donut: the same pop as Payment - the wedge thickens and takes a
   glow in its own colour. The arcs here are their own hit targets (there is no
   invisible wider arc over them, as there is on the Payment donut), so plain
   :hover is enough. */
.pie-svg path {
  transition: stroke-width .2s ease, filter .2s ease, opacity .2s ease;
}
.pie-svg path:hover {
  stroke-width: 25;
  filter: drop-shadow(0 0 7px currentColor);
}
.pay-donut .pay-arc {
  transition: stroke-width .2s ease, filter .2s ease, opacity .2s ease;
}
.pay-donut .pay-arc.is-hot {
  stroke-width: 38;
  filter: drop-shadow(0 0 8px currentColor);
}
/* ---- Payment & Settlement hover menu ----
   Ported from "Payment and Settlement.html". Kept as its own element and its
   own rules rather than borrowing the shared .perf-tooltip, so nothing about
   this chart's behaviour can be changed by a rule aimed at another chart. */
.pay-tip {
  /* `absolute`, not `fixed`. The panel carries `backdrop-filter: blur(20px)`,
     and a filtered ancestor BECOMES the containing block for position:fixed
     descendants - so a fixed menu was being positioned against the panel box
     while the coordinates written into it were viewport coordinates, and it
     landed in the middle of the card. Absolute resolves against the chart box
     and the maths below is done in the same space. */
  position: absolute;
  z-index: 100;
  /* Was 220px with 14/16 padding and 15/13px type - wider than the 180px chart
     box it lives in, so it could never sit beside the cursor and the edge
     clamp kept pushing it back toward the middle. Trimmed to fit. */
  min-width: 168px;
  padding: 10px 12px;
  border: 2.5px solid transparent;
  border-radius: 12px;
  background: rgba(15, 23, 42, .96);
  color: #fff;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .5);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity .2s ease, transform .2s ease, visibility .2s ease;
}
.pay-tip.visible { opacity: 1; visibility: visible; transform: translateY(0) }
.pay-tip-h {
  display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 700; margin-bottom: 7px;
}
.pay-tip-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; margin-bottom: 5px; font-size: 11.5px;
}
.pay-tip-row:last-child { margin-bottom: 0 }
.pay-tip-v { font-weight: 700; text-align: right }
.pay-tip-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px }
.pay-tip-dot-muted { background: rgba(255, 255, 255, .45) }
/* The metric card hover, exactly as the reference: fill, lift and shadow all
   transitioning together. */
.analytics-view .pay-lg {
  transition: background .25s ease, transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
/* Title and figures carry the weight; the labels stay quieter so the numbers
   lead. This is the "bold menu" — at the previous weights the rows read as
   loose text rather than as a menu. */
.perf-tooltip .tt-date {
  color: var(--tt-fg);
  font-weight: 800;
  font-size: 13.5px;
  letter-spacing: -.01em;
  margin-bottom: 8px;
}
.perf-tooltip .tt-row {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  line-height: 1.7;
}
.perf-tooltip .tt-k {
  color: var(--tt-muted);
  font-weight: 600;
  flex: 1;
  min-width: 0;
}
.perf-tooltip .tt-row b,
.perf-tooltip .tt-val {
  color: var(--tt-fg);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.os-row { transition: background var(--dur-fast); }
.os-row:hover { background: color-mix(in srgb, var(--primary) 8%, transparent); }

/* ---- SeriesChart chrome + hover tooltip ----
   The chart engine renders its viewport, toolbar and cursor tooltip with these
   classes, but no stylesheet ever styled them: the tooltip appeared as loose
   unboxed text instead of a menu box at the cursor. */
.sc-root { position: relative; }
/* Crosshair over the plot: the chart reports a value for any pointer position
   but inherited the text cursor, so nothing indicated it was interactive. */
.sc-plot { cursor: crosshair; }
.sc-plot.is-panning { cursor: grabbing; }
.sc-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
}
.sc-hint { font-size: 9.5px; color: var(--muted); }
.sc-reset {
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--card) 60%, transparent);
  color: var(--label);
  font-family: inherit;
  font-size: 10px;
  font-weight: 600;
  cursor: pointer;
}
.sc-reset:hover { border-color: var(--primary); color: var(--primary); }
.sc-view {
  position: relative;
  overflow-x: auto;
  overflow-y: hidden;
  border-radius: 10px;
}
.sc-tip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 5;
  min-width: 150px;
  max-width: 240px;
  padding: 9px 11px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 82%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  box-shadow: 0 12px 28px rgba(10, 15, 30, .22);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
}
.sc-tip.on { opacity: 1; }
.sc-tip-h {
  font-size: 11px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 5px;
}
.sc-tip-r {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10.5px;
  color: var(--label);
  padding: 1px 0;
}
.sc-tip-r .sc-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
}
.sc-tip-r .sc-tip-k { flex: 1; }
.sc-tip-r b { color: var(--text); font-variant-numeric: tabular-nums; }

/* ---- Dashboard-wide custom scrollbars ---- */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--muted) 55%, transparent);
  border-radius: 4px;
}
*::-webkit-scrollbar-thumb:hover { background: var(--muted); }
*::-webkit-scrollbar-corner { background: transparent; }

/* ---- Lively ambient background ----
   The orbs used to be pinned near their own corners and only nudged a few vw,
   so the wash never travelled: it looked like a fixed gradient rather than two
   colours moving through each other. Each one now crosses the full viewport on a
   long, unending cycle. `alternate` on a 0%/100% pair repeats forever, so the
   motion never restarts.

   Cost is kept low deliberately — transform and opacity only (both compositor
   properties), no filter animation, long durations so the drift reads as slow
   rather than busy, and will-change is set once in global.css. */
.bg-orbs { opacity: .62; }
.orb { opacity: .66; filter: blur(52px); }
/* Each orb starts anchored at its corner, travels diagonally across the
   viewport, then returns. The middle keyframe is what carries the colour into
   the centre of the screen where the cards sit, so the two washes actually meet
   and blend. */
.orb-a { animation-name: orb-pan-a; animation-duration: 46s; }
.orb-b { animation-name: orb-pan-b; animation-duration: 58s; }
.orb-c { animation-name: orb-pan-c; animation-duration: 52s; }
@keyframes orb-pan-a {
  0%   { transform: translate3d(0, 0, 0) scale(1); opacity: .55; }
  50%  { transform: translate3d(46vw, 30vh, 0) scale(1.22); opacity: .82; }
  100% { transform: translate3d(0, 0, 0) scale(1); opacity: .55; }
}
@keyframes orb-pan-b {
  0%   { transform: translate3d(0, 0, 0) scale(1); opacity: .5; }
  50%  { transform: translate3d(-52vw, 34vh, 0) scale(1.18); opacity: .8; }
  100% { transform: translate3d(0, 0, 0) scale(1); opacity: .5; }
}
@keyframes orb-pan-c {
  0%   { transform: translate3d(0, 0, 0) scale(1); opacity: .5; }
  50%  { transform: translate3d(40vw, -38vh, 0) scale(1.2); opacity: .78; }
  100% { transform: translate3d(0, 0, 0) scale(1); opacity: .5; }
}
/* Date glyph follows the theme instead of rendering as a grey OS icon. */
.tp-ic { color: var(--icon); }
.tp-chev { color: var(--muted); }

/* Chevron glyphs inside filter/drop/per-page buttons keep button size instead
   of exploding to the SVG default and covering the panel behind them. */
.flt-drop-btn .chev,
.pp-trigger .chev,
.date-dropdown-trigger .chev,
.tp-trigger .tp-chev {
  width: 12px;
  height: 12px;
  flex: 0 0 auto;
}
.flt-drop-btn > span:first-child,
.pp-trigger > span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.flt-drop { min-width: 0; }
.flt-drop-btn { width: 100%; }

/* Rows-per-page opens below its trigger like the date filter button. */
.orders-view .pp-panel {
  top: calc(100% + 8px);
  bottom: auto;
}

/* The order drawer's Actions menu is closed until its button is pressed. */
.orders-view .acts-menu { display: none; }
.orders-view .acts-menu.open { display: block; }

/* ---- Orders table: full page height, uncrushed rows ----
   The scroll box no longer caps the list; rows flow down the whole page. Key
   values read larger, and long address/product cells wrap instead of crushing
   their neighbours. */
/* Orders: the window scrolls, the table does not.

   .orders-view is a flex column, so every direct child is a flex item and can
   be shrunk. The toolbar had no flex-basis, which let a tall table crush it -
   and because .toolbar wraps, its controls spilled onto extra lines and landed
   visually below the table header. flex:none gives each band its natural height.

   max-height:none stops the scroller being a bounded pane; overflow-x:auto is
   what lets the min-width:1340px table actually produce a horizontal scrollbar
   on narrow windows. Previously this was `overflow: visible`, meant to hand
   vertical scrolling to the window for the lazy-load sentinel (see the note in
   OrdersFeature._lazyCleanup) - but that also suppressed horizontal scrolling,
   so a wide table was simply clipped at the right edge with no way to reach the
   last columns. Only overflow-x is auto here; the box still grows to its content
   (max-height:none), so it never scrolls vertically itself and the window stays
   the vertical scroller. */
  .orders-view .kpis,
  .orders-view .chips,
  .orders-view .toolbar { flex: none; }
  .orders-view .table-wrap { flex: none; }
  .orders-view .table-scroll { max-height: none; overflow-x: auto; overflow-y: visible; }

  /* Rows-per-page menu. sections-delta scopes .orders-view .pp-panel and sets a
     flat `background: var(--card)`, and that two-class selector wins over the
     plain `.pp-panel` glass rule further down this file no matter which loads
     last - so in dark mode --card is near-black and the menu reads as a hole.
     Matching the specificity here puts it back in line with every other menu. */
  .orders-view .pp-panel {
    background: color-mix(in srgb, var(--card) 88%, transparent);
    -webkit-backdrop-filter: blur(22px) saturate(180%);
    backdrop-filter: blur(22px) saturate(180%);
    border-color: color-mix(in srgb, var(--border) 48%, transparent);
  }

  /* Column headers sit directly on rows, so a flat --elev plate reads as a heavy
     band. Frosted instead, matching the panels they head. */
  .orders-view thead th {
    background: color-mix(in srgb, var(--card) 55%, transparent);
    -webkit-backdrop-filter: blur(16px) saturate(160%);
    backdrop-filter: blur(16px) saturate(160%);
  }
  /* Sticky headers need an opaque fallback behind the frost or rows show
     through the gaps while scrolling. */
  .orders-view thead th::before {
    content: '';
    position: absolute; inset: 0;
    background: color-mix(in srgb, var(--card) 88%, transparent);
    z-index: -1;
  }
  .orders-view thead th { position: sticky; }
.orders-view .order-row td { padding-top: 10px; padding-bottom: 10px; }
.orders-view .order-row td:nth-child(3),
.orders-view .order-row td:nth-child(4),
.orders-view .order-row td:nth-child(5) {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
}
.orders-view .order-row td.td-amt {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}
.orders-view .order-row td.td-prod {
  white-space: normal;
  min-width: 170px;
  line-height: 1.45;
}

/* ---- Order detail drawer: important fields read bigger ---- */
.odp-tracking { font-size: 17px; font-weight: 700; }
.odp .title h3 { font-size: 15px; }
.odp-field .fv { font-size: 12.5px; }

/* ---- Row selection checkboxes ----
   Checkboxes only exist in print mode; otherwise the column stays hidden. */
.orders-view .sel-col { display: none; width: 34px; }
.orders-view.print-mode .sel-col { display: table-cell; }
.orders-view .row-sel,
.orders-view #selAll {
  width: 15px;
  height: 15px;
  accent-color: var(--primary);
  cursor: pointer;
  vertical-align: middle;
}

/* ---- Floating print-selection bar ---- */
.print-bar {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 300;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px 10px 16px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: color-mix(in srgb, var(--card) 60%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  box-shadow: 0 14px 34px rgba(10, 15, 30, .2);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}
.print-bar[hidden] { display: none !important; }
.print-bar .mini-btn.solid {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

/* ---- Search field-scope picker ----
   A compact scope control docked inside the search box. */
.search-box { position: relative; }
.search-scope {
  flex: 0 0 auto;
  height: 26px;
  border: 0;
  border-left: 1px solid var(--border);
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 600;
  padding: 0 4px 0 10px;
  cursor: pointer;
  outline: none;
}
.search-scope:hover { color: var(--primary); }
/* Filter icon docked at the end of the search field, with a ticked menu. */
.search-scope-btn {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border: 0;
  border-left: 1px solid var(--border);
  background: none;
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 0 0 6px;
}
.search-scope-btn svg { width: 16px; height: 16px; }
.search-scope-btn:hover { color: var(--primary); }
.search-scope-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 220;
  min-width: 160px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--border) 48%, transparent);
  background: color-mix(in srgb, var(--card) 80%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  box-shadow: 0 16px 38px rgba(10, 15, 30, .18);
}
.search-scope-menu[hidden] { display: none !important; }
.search-scope-menu label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border-radius: 8px;
  font-size: 11px;
  font-weight: 500;
  color: var(--label);
  cursor: pointer;
}
.search-scope-menu label:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }
.search-scope-menu input { accent-color: var(--primary); margin: 0; }

/* ---- Dropdown triggers and menus ----
   Rounded, theme-aware text, glass background everywhere. */
.tp-trigger,
.date-dropdown-trigger,
.flt-drop-btn,
.pp-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--border) 48%, transparent);
  background: color-mix(in srgb, var(--card) 44%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.tp-trigger:hover,
.date-dropdown-trigger:hover,
.flt-drop-btn:hover,
.pp-trigger:hover { border-color: var(--primary); }
.tp-trigger .tp-label,
.date-dropdown-trigger span,
.flt-drop-btn span,
.pp-trigger span { overflow: hidden; text-overflow: ellipsis; }
.tp-panel,
.tp-list,
.date-dropdown-panel,
.actions-menu,
.flt-drop-list,
.flt-panel,
.pp-panel,
.cpick-menu,
.exp-fmt-menu,
.acts-menu {
  border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--border) 48%, transparent);
  background: color-mix(in srgb, var(--card) 44%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  color: var(--text);
}
.tp-panel[hidden],
.tp-list[hidden],
.flt-drop-list[hidden],
.pp-panel[hidden],
.acts-menu[hidden],
.exp-fmt-menu[hidden] { display: none !important; }

/* ---- Themed date and select controls ----
   The JS upgrade was already emitting these classes, but no stylesheet styled
   them, so native OS chrome leaked through. */
.tp-wrap { position: relative; display: inline-flex; align-items: center; }
.tp-wrap-range { min-width: 190px; }
.tp-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}

/* Suppress native date inputs while a section is still rendering. The router
   adds .forms-pending to #content, calls the async feature render, and only
   then upgrades the inputs and removes the flag. Without this the browser can
   paint the raw control - OS chrome, grey calendar glyph - before the upgrade,
   which is the flash of an unstyled date picker. Inputs marked data-native-date
   are deliberately left alone: that is the opt-out for staying native. */
#content.forms-pending input[type="date"]:not([data-native-date]) {
  position: absolute !important;
  width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}
.tp-label.is-empty::before { content: 'Select date range'; color: var(--muted); font-weight: 500; }
.tp-ic { width: 14px; height: 14px; flex: 0 0 auto; color: var(--icon); }
.tp-chev { width: 12px; height: 12px; flex: 0 0 auto; color: var(--muted); }
.tp-panel {
  position: fixed;
  z-index: 500;
  width: 284px;
  padding: 12px;
}
.tp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.tp-title { font-size: 12px; font-weight: 700; color: var(--text); }
.tp-nav {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--label);
  cursor: pointer;
}
.tp-nav:hover { border-color: var(--primary); color: var(--primary); }
.tp-week {
  display: grid; grid-template-columns: repeat(7, 1fr);
  margin-bottom: 5px;
  font-size: 9px; font-weight: 700; text-transform: uppercase;
  color: var(--muted); text-align: center;
}
.tp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.tp-day {
  height: 30px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
  cursor: pointer;
}
.tp-day:hover { background: color-mix(in srgb, var(--primary) 14%, transparent); }
.tp-day.is-out { visibility: hidden; }
.tp-day.is-today { box-shadow: inset 0 0 0 1px var(--primary); }
.tp-day.is-sel { background: var(--primary); color: #fff; font-weight: 700; }
.tp-day.in-range { background: color-mix(in srgb, var(--primary) 16%, transparent); border-radius: 0; }
.tp-foot { display: flex; justify-content: space-between; margin-top: 10px; }
.tp-today, .tp-clear {
  border: 0;
  background: none;
  color: var(--primary);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  padding: 4px 6px;
}
.tp-x {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: none;
  color: var(--muted);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  flex: 0 0 auto;
}
.tp-x:hover { border-color: var(--danger); color: var(--danger); }
.print-bar .print-x {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  justify-content: center;
}

/* Account menu sits below the header avatar: anchored to the viewport's right
   edge like the avatar row, but absolutely positioned so it never trails the
   page scroll like a fixed layer does. */
.sidebar .acct-menu {
  position: absolute;
  top: 64px;
  left: calc(100vw - 278px);
  right: auto;
  margin-left: 0;
}
.tp-clear { color: var(--muted); }
.tp-hint { font-size: 10px; font-weight: 600; color: var(--primary); text-align: center; margin-bottom: 7px; }
.tp-list { padding: 5px; min-width: 170px; }
.tp-opt {
  display: flex;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--label);
  font-family: inherit;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}
.tp-opt:hover { background: color-mix(in srgb, var(--primary) 12%, transparent); }
.tp-opt.is-sel { color: var(--primary); font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
  .glider-pill, .chip-glider { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .card, .panel, .table-wrap, .chart, .perf-chart-wrap, .comm-panel,
  .tp-trigger, .date-dropdown-trigger, .tp-panel, .date-dropdown-panel {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
/* ---- Pagination liquid pill ------------------------------------------
   Styled for the pill tabs.js already attaches (its rule list includes
   `.pagination .pages`, and it keeps a snapshot so a rebuilt pager's pill
   slides to the new page instead of popping in). The feature does NOT attach
   its own glider here - two pills on one row fight each other. Only the
   manager's .glider-pill is targeted below.

   The active page button goes transparent and the pill supplies the fill, same
   as .chips. Prev/Next are actions, not a selection, so the pill never sits on
   them. */
.pagination .pages.has-glider { position: relative; }
.pagination .pages button.on {
  background: none;
  border-color: transparent;
  color: #fff;
  position: relative;
  z-index: 1;
}
.pagination .pages > .glider-pill {
  position: absolute; top: 0; left: 0;
  border-radius: var(--r-sm);
  background: var(--primary);
  box-shadow: 0 4px 12px -3px color-mix(in srgb, var(--primary) 60%, transparent);
  z-index: 0; pointer-events: none;
  transition: transform .32s cubic-bezier(.22,.9,.28,1.08),
              width .32s cubic-bezier(.22,.9,.28,1.08),
              height .32s cubic-bezier(.22,.9,.28,1.08);
}
.pagination .pages.has-glider.is-animating > .glider-pill {
  transition-duration: .18s;
  transition-timing-function: ease-in;
}
/* Keep Prev/Next readable above the pill layer. */
.pagination .pages .pag-prev,
.pagination .pages .pag-next { position: relative; z-index: 1; }

/* ---- Order tools in the Orders header -------------------------------
   Book Order / Mark Returns / Cancel Orders sit beside the settings gear.

   Each button carries its OWN tint at rest (from the data-tone on the anchor),
   so the three are told apart without hovering. Hover does not open a menu -
   the button itself widens and the name slides in beside the icon, the way the
   classic sidebar rail behaves. Width animates, so the name is revealed rather
   than popped.

   Colours are color-mix over the tone, which is a fixed hex per tone, so this
   reads the same in both themes; only the surface behind it flips. */
.order-tools {
  display: flex; align-items: center; gap: 6px;
  margin-right: 2px; padding-right: 8px;
  border-right: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.order-tools[hidden] { display: none; }
/* `.btn-icon` sets `display: flex`, and an author rule beats the user-agent's
   `[hidden] { display: none }`. So setting `.hidden` on a header icon button did
   nothing and it stayed on screen - which is how the assistant button kept
   appearing on Orders after the router asked for it to be hidden. The
   notifications button had been getting away with it only because it carries an
   inline `style="display:none"` rather than the attribute. Same treatment as
   `.order-tools` above. */
.btn-icon[hidden] { display: none; }

.ot-btn {
  --ot: var(--tone-color, var(--primary));
  /* A concrete expanded width. `width: auto` on hover is NOT interpolable, so
     the button used to jump to full size instantly and only the label looked
     animated. 150px clears the longest label ("Cancel Orders" ~138px) with a
     little right padding to spare. */
  --ot-w: 150px;
  position: relative; overflow: hidden;
  width: 34px; height: 34px; flex: 0 0 34px;
  display: flex; align-items: center; justify-content: flex-start;
  padding: 0; border-radius: 17px;
  text-decoration: none; cursor: pointer;
  color: #fff;
  background: var(--ot);
  border: 1px solid color-mix(in srgb, var(--ot) 70%, #000 10%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 30%, transparent),
    0 4px 12px -6px color-mix(in srgb, var(--ot) 55%, transparent);
  /* Smooth on purpose: .26s read as a snap. .45s on the ease-out curve makes
     the widening itself the motion, with the colour and label following. */
  transition: width .45s var(--ease), flex-basis .45s var(--ease),
              background .35s ease, box-shadow .35s ease, border-radius .45s var(--ease);
}
.ot-btn .i { width: 15px; height: 15px; margin: 0 9.5px; flex: 0 0 15px; }

/* The name lives inside the button and is revealed by the widening, so there
   is no separate tooltip element and nothing floats over the page. */
.ot-btn .ot-label {
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  opacity: 0; transform: translateX(-4px);
  /* flex: 0 0 auto keeps the label at its natural width. Without it the label
     is a shrinkable flex item, so while the button is still narrow the text
     squeezes and gets cut mid-word ("Cancel" instead of "Cancel Orders")
     before the widening finishes. Pinned, it simply gets revealed. */
  flex: 0 0 auto;
  /* Slower fade-in so the name arrives with the widening rather than after it. */
  transition: opacity .3s ease .06s, transform .35s var(--ease) .06s;
  pointer-events: none;
}

.ot-btn:hover,
.ot-btn:focus-visible {
  width: var(--ot-w); flex-basis: var(--ot-w); border-radius: 17px;
  padding-right: 14px;
  background: color-mix(in srgb, var(--ot) 88%, #fff 12%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 34%, transparent),
    0 8px 20px -6px color-mix(in srgb, var(--ot) 70%, transparent);
}
.ot-btn:hover .ot-label,
.ot-btn:focus-visible .ot-label { opacity: 1; transform: translateX(0); }
/* Keep the icon from drifting as the label appears. */
.ot-btn:hover .i,
.ot-btn:focus-visible .i { margin-right: 0; }

.ot-btn:active { background: color-mix(in srgb, var(--ot) 80%, #000 20%); }
.ot-btn:focus-visible { outline: 2px solid var(--ot); outline-offset: 2px; }

/* Per-button expanded widths: measured label width + 14px right padding + a
   few px of slack, so each button glides open just far enough for its own
   name instead of one wide shared box. The 150px default covers any button
   added later without a tuned value. */
.ot-btn[data-ot="booking"] { --ot-w: 104px; }
.ot-btn[data-ot="returns"] { --ot-w: 122px; }
.ot-btn[data-ot="cancel"]  { --ot-w: 128px; }
#invBulkBtn   { --ot-w: 114px; }
#invExportBtn { --ot-w: 122px; }
#invAddBtn    { --ot-w: 116px; }

/* ---- Back to Orders ----
   Stands in for the whole header on the three sub-sections, so it carries the
   label rather than just a chevron - with no topbar there is nothing else
   naming where you are or how to get out. */
.orders-back[hidden] { display: none; }
.orders-back { margin-bottom: 14px; }
.ob-btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 14px 0 11px;
  border-radius: 17px;
  font-size: 11.5px; font-weight: 600;
  color: var(--label); text-decoration: none; cursor: pointer;
  background: color-mix(in srgb, var(--card) 52%, transparent);
  border: 1px solid color-mix(in srgb, var(--border) 58%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(175%);
  backdrop-filter: blur(18px) saturate(175%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 26%, transparent),
    0 4px 14px -8px rgba(0, 0, 0, .3);
  transition: background var(--dur-fast), border-color var(--dur-fast),
              color var(--dur-fast), transform var(--dur-fast);
}
.ob-btn .i { width: 14px; height: 14px; color: var(--icon); }
.ob-btn:hover {
  color: var(--primary);
  border-color: color-mix(in srgb, var(--primary) 55%, transparent);
  background: color-mix(in srgb, var(--card) 72%, transparent);
  transform: translateX(-2px);
}
.ob-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ---- Inventory: action tools, big KPIs, whoosh on both tab rows ----
   Bulk Update / Export Report / Add Product moved out of the deleted Quick
   Actions panel into the toolbar, wearing the same expanding icon-button
   treatment as the Orders header tools (reuses .ot-btn). The KPI row is
   sized like Orders': taller frosted cards with large figures, on auto-fit
   tracks so the row fills the whole width at any window size. The category
   strip (.cats) and the Active/Non-active segment (.status-filter) are now
   glider rows (see tabs.js RULES); the generic .has-glider rules strip their
   own fills so the travelling pill carries the active state. */
.inv-tools {
  display: flex; align-items: center; gap: 6px;
  margin-left: 2px; padding-left: 10px;
  border-left: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.inv-tools .ot-btn { font-family: inherit; }

.inventory-view .kpis {
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px;
}
.inventory-view .kpi {
  min-height: 150px;
  padding: 18px 18px 15px;
  /* Explicit frost: the generic .card rule alone left these reading solid. */
  background: color-mix(in srgb, var(--card) 30%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  box-shadow: 0 8px 24px rgba(10, 15, 30, .08);
}
.inventory-view .kpi .num { font-size: 40px; }
.inventory-view .kpi .num.money { font-size: 32px; }
.inventory-view .kpi .lbl { font-size: 12px; }
.inventory-view .kpi .foot { font-size: 11px; }

/* Retailers gets the same KPI sizing. The first card used to carry kpi-lg,
   which left it a different size from its siblings — the class is dropped
   from the markup so the row is uniform. */
.retailers-view .kpis {
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px;
}
.retailers-view .kpi {
  min-height: 150px;
  padding: 18px 18px 15px;
  background: color-mix(in srgb, var(--card) 30%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  box-shadow: 0 8px 24px rgba(10, 15, 30, .08);
}
.retailers-view .kpi .num { font-size: 40px; }
.retailers-view .kpi .num.money { font-size: 32px; }
.retailers-view .kpi .lbl { font-size: 12px; }
.retailers-view .kpi .kdelta { font-size: 11px; }

/* Both inventory tab rows keep their own button shape on the pill, gain the
   glow and the travel morph, and read white over the pill while active. */
.cats.has-glider .glider-pill { border-radius: 14px; }
.status-filter.has-glider .glider-pill { border-radius: 6px; }
/* Inventory grid/list toggle. Same family as `.status-filter` — a small tray
   with a 6px pill and an icon on top of it. */
.inv-view.has-glider .glider-pill { border-radius: 6px; }
.inv-view.has-glider .glider-pill,
.inv-view.has-glider.is-animating .glider-pill {
  box-shadow: 0 6px 18px -4px color-mix(in srgb, var(--primary) 55%, transparent);
}
.inv-view.has-glider > button.on { background: none; color: #fff; }
.inv-view.has-glider.is-animating .glider-pill {
  transform-origin: center;
  transition: transform .2s ease-in, width .2s ease-in, height .2s ease-in;
}
.cats.has-glider .glider-pill,
.status-filter.has-glider .glider-pill {
  box-shadow: 0 6px 18px -4px color-mix(in srgb, var(--primary) 55%, transparent);
}
.cats.has-glider.is-animating .glider-pill,
.status-filter.has-glider.is-animating .glider-pill {
  transform-origin: center;
  transition: transform .2s ease-in, width .2s ease-in, height .2s ease-in;
}


/* ---- Retailers: real KPI cards on the summary rows ----
   The .rs rows (retailer page, group page, order page) were small grey boxes
   that did not read as KPIs. They now wear the same frosted-card treatment as
   the main retailers KPI row: full-width auto-fit grid, tall cards, large
   figures, uppercase labels. Markup is unchanged - only the look. */
.retailers-view .rs {
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px;
}
.retailers-view .rs-box {
  min-height: 150px;
  padding: 18px 18px 15px;
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--card) 30%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  backdrop-filter: blur(22px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  box-shadow: 0 8px 24px rgba(10, 15, 30, .08);
  border-radius: 12px;
  transition: transform var(--dur-fast), box-shadow var(--dur-fast);
}
.retailers-view .rs-box:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-pop);
}
/* The gold variant keeps the amber accent on the label instead of tinting the
   whole card, so every card in the row reads as one set. */
.retailers-view .rs-box.gold { background: color-mix(in srgb, var(--card) 30%, transparent); }
.retailers-view .rs-box .l {
  font-size: 12px;
  color: var(--label);
}
.retailers-view .rs-box.gold .l { color: #B8760F; }
.retailers-view .rs-box .v {
  font-size: 40px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -.9px;
  line-height: 1.05;
  margin-top: 8px;
  overflow-wrap: anywhere;
}
.retailers-view .rs-box .v.money { font-size: 32px; }

/* ---- Retailers: group cards as Windows folders ----
   The folder silhouette stays - the tab sitting behind the front sheet, built
   from ::before/::after so the markup stays pure. What changed is the colour:
   it was a hard-coded Manila yellow (#ffd76a → #f0a500) with brown ink in
   BOTH themes, which is the one surface on the page that ignored the theme.
   It is now built from tokens - a primary-tinted sheet, `--text` ink, a
   `--primary` tab - so it re-tints with the rest of the app in light and dark
   without a second set of rules. */
.retailers-view .groups-grid {
  row-gap: 22px; /* room for the tab to overhang the card above */
}
.retailers-view .groups-grid .group-card {
  position: relative;
  align-items: flex-start;
  text-align: left;
  min-height: 150px;
  margin-top: 10px;
  padding: 26px 18px 16px;
  border: 1px solid color-mix(in srgb, var(--primary) 22%, transparent);
  border-radius: 6px 16px 16px 16px;
  background:
    linear-gradient(165deg,
      color-mix(in srgb, var(--primary) 16%, var(--card)) 0%,
      color-mix(in srgb, var(--primary) 10%, var(--card)) 55%,
      color-mix(in srgb, var(--primary) 15%, var(--card)) 100%);
  color: var(--text);
  box-shadow:
    0 10px 24px -12px color-mix(in srgb, var(--primary) 34%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--card) 60%, transparent);
}
/* The tab. Sits proud of the top edge so it reads as behind the front sheet. */
.retailers-view .groups-grid .group-card::before {
  content: '';
  position: absolute;
  top: -9px;
  left: 0;
  width: 46%;
  max-width: 110px;
  height: 20px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--primary) 26%, var(--card)),
    color-mix(in srgb, var(--primary) 18%, var(--card)));
  border-radius: 8px 14px 0 0;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--card) 55%, transparent);
}
/* Sheen across the front sheet. */
.retailers-view .groups-grid .group-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, color-mix(in srgb, var(--card) 34%, transparent), transparent 42%);
  pointer-events: none;
}
.retailers-view .groups-grid .group-card:hover {
  border-color: color-mix(in srgb, var(--primary) 45%, transparent);
  transform: translateY(-2px);
  box-shadow:
    0 16px 30px -14px color-mix(in srgb, var(--primary) 40%, transparent),
    inset 0 1px 0 color-mix(in srgb, var(--card) 65%, transparent);
}
.retailers-view .groups-grid .group-card > * { position: relative; z-index: 1; }
.retailers-view .groups-grid .group-card .gc-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  margin-bottom: 10px;
  background: color-mix(in srgb, var(--card) 55%, transparent);
  color: var(--primary);
}
.retailers-view .groups-grid .group-card .gc-icon .i { color: var(--primary); }
.retailers-view .groups-grid .group-card .gc-name { color: var(--text); }
.retailers-view .groups-grid .group-card .gc-count { color: var(--muted); }
.retailers-view .groups-grid .group-card .gc-stat { color: var(--muted); }
.retailers-view .groups-grid .group-card .gc-stat b { color: var(--text); }
/* Received is a positive figure, so it stays green - but from the token rather
   than a fixed #0b5c2b, which went muddy against the yellow. */
.retailers-view .groups-grid .group-card .gc-stat.recv,
.retailers-view .groups-grid .group-card .gc-stat.recv b { color: var(--success-dark); }
.retailers-view .groups-grid .group-card .gc-stat.gold,
.retailers-view .groups-grid .group-card .gc-stat.gold b { color: var(--warn-text); }
/* The dashed "new group" card keeps its add affordance, tinted to match. */
.retailers-view .groups-grid .group-card.create {
  margin-top: 10px;
  border: 1.5px dashed color-mix(in srgb, var(--primary) 42%, transparent);
  background: color-mix(in srgb, var(--primary) 7%, transparent);
  border-radius: 12px;
  color: var(--muted);
  box-shadow: none;
}
.retailers-view .groups-grid .group-card.create::before,
.retailers-view .groups-grid .group-card.create::after { display: none; }
.retailers-view .groups-grid .group-card.create:hover {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 13%, transparent);
  box-shadow: none;
}

/* ---- Chat history: liquid pill on the active thread ----
   Same sliding treatment as every other tab row. The active button goes
   transparent and the pill supplies the surface, so switching conversations
   glides instead of snapping. `.chat-thread-row` also carries is-active for its
   own background, so that is cleared here too - otherwise the row's fill sits
   on top of the pill and the animation is hidden behind a flat block. */
.chat-side-list.has-glider { position: relative; }
.chat-thread.is-active,
.chat-thread-row.is-active > .chat-thread {
  background: none;
  color: var(--primary);
}
.chat-side-list.has-glider > .glider-pill {
  position: absolute; top: 0; left: 0;
  border-radius: 10px;
  background: var(--chat-rail-solid);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 26%, transparent);
  z-index: 0; pointer-events: none;
  transition: transform .3s cubic-bezier(.22,.9,.28,1.08),
              width .3s cubic-bezier(.22,.9,.28,1.08),
              height .3s cubic-bezier(.22,.9,.28,1.08);
}
/* The label, icon and label text all sit above the pill. */
.chat-thread { position: relative; z-index: 1; }
.chat-thread-more { position: relative; z-index: 2; }
.chat-side-list.has-glider.is-animating > .glider-pill {
  transition-duration: .18s;
  transition-timing-function: ease-in;
}
