/* Self-contained utility stylesheet -- no CDN, no build step, no internet
   dependency at runtime. Deliberately a small hand-written subset of
   Tailwind-style utility classes covering exactly what this app's templates
   use, so the platform renders identically whether the server has internet
   access or not. */

:root {
    /* RadCare Services brand palette, sampled from radcareservices.com and the
       company logo: deep indigo/purple for identity, lavender accent, sage
       green for calls to action, near-white lavender page background. */
    --brand-purple: #3d2e69;
    --brand-purple-dark: #2f2350;
    --brand-lavender: #ac87fc;
    --brand-lavender-light: #e6deee;
    --brand-lavender-band: #d1bef0;
    --brand-green: #415f4d;
    --brand-green-dark: #344d3e;

    --slate-50: #fbf9fe;
    --slate-100: #f1f5f9;
    --slate-200: #e2e8f0;
    --slate-300: #cbd5e1;
    --slate-400: #94a3b8;
    --slate-500: #64748b;
    --slate-600: #475569;
    --slate-700: #334155;
    --slate-800: #1e293b;
    --slate-900: #0f172a;

    --red-50: #fef2f2;
    --red-100: #fee2e2;
    --red-200: #fecaca;
    --red-600: #dc2626;
    --red-700: #b91c1c;

    --amber-100: #fef3c7;
    --amber-700: #b45309;

    --emerald-50: #ecfdf5;
    --emerald-200: #a7f3d0;
    --emerald-700: #047857;
    --emerald-800: #065f46;
}

* { box-sizing: border-box; }
body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    margin: 0;
}
table { border-collapse: collapse; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; border: none; }
input, select { font: inherit; }
code { background: var(--slate-100); padding: 0.1rem 0.35rem; border-radius: 0.25rem; font-size: 0.85em; }

/* Layout */
.flex { display: flex; }
.grid { display: grid; }
.flex-1 { flex: 1 1 0%; }
/* min-width:0 overrides a flex item's default min-width:auto, which otherwise refuses
   to shrink below its text's natural width - without it, text-overflow:ellipsis never
   gets a chance to kick in and the row wraps onto a second line instead. */
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.items-end { align-items: flex-end; }
.justify-between { justify-content: space-between; }
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; }
.gap-6 { gap: 1.5rem; }
.ml-auto { margin-left: auto; }
.space-y-4 > * + * { margin-top: 1rem; }
.overflow-x-auto { overflow-x: auto; }
.overflow-y-auto { overflow-y: auto; }
.min-h-screen { min-height: 100vh; }

@media (min-width: 768px) {
    .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Sizing */
.w-full { width: 100%; }
.max-w-sm { max-width: 24rem; }
.max-w-xl { max-width: 36rem; }
.max-w-2xl { max-width: 42rem; }
.max-w-7xl { max-width: 80rem; }
.mx-auto { margin-left: auto; margin-right: auto; }

/* Spacing */
.p-4 { padding: 1rem; } .p-5 { padding: 1.25rem; } .p-6 { padding: 1.5rem; } .p-8 { padding: 2rem; }
.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
.py-1\.5 { padding-top: 0.375rem; padding-bottom: 0.375rem; }
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
.pb-2 { padding-bottom: 0.5rem; }
.pt-2 { padding-top: 0.5rem; }
.pt-4 { padding-top: 1rem; }
.mb-1 { margin-bottom: 0.25rem; } .mb-2 { margin-bottom: 0.5rem; } .mb-3 { margin-bottom: 0.75rem; }
.mb-4 { margin-bottom: 1rem; } .mb-6 { margin-bottom: 1.5rem; }
.mt-1 { margin-top: 0.25rem; } .mt-2 { margin-top: 0.5rem; } .mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; } .mt-8 { margin-top: 2rem; } .mt-10 { margin-top: 2.5rem; } .mt-24 { margin-top: 6rem; }
.ml-1 { margin-left: 0.25rem; }
.mr-2 { margin-right: 0.5rem; }
.whitespace-nowrap { white-space: nowrap; }

/* Typography */
.text-xs { font-size: 0.75rem; line-height: 1rem; }
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
.text-2xl { font-size: 1.5rem; line-height: 2rem; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.uppercase { text-transform: uppercase; }
.tracking-wide { letter-spacing: 0.025em; }
.tracking-tight { letter-spacing: -0.025em; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.underline { text-decoration: underline; }
.italic { font-style: italic; }
.list-disc { list-style-type: disc; }
.list-inside { list-style-position: inside; }

/* Small colored initial avatar for the Trial Cases / First-Time Customers row list
   (2026-09-24) - background-color is set inline per-row (see _with_avatars() in
   app/routers/scanning.py), everything else fixed here. Hidden by default (a
   real logo, .kpi-logo below, is the default icon) - only shown via the logo
   img's onerror handler in scanning.html when the logo request fails outright.
   Sized up to match `.kpi-logo`, 2026-09-30 - see that class's own comment. */
.kpi-avatar {
    width: 34px;
    height: 34px;
    border-radius: 9999px;
    align-items: center;
    justify-content: center;
    color: #ffffff;
    font-size: 0.8rem;
    font-weight: 600;
    flex-shrink: 0;
}

/* Real per-customer logo icon, same row (2026-09-24) - see LOGO_URL_TEMPLATE/
   _guess_domain() in app/routers/scanning.py. object-fit:contain (not cover) since
   favicons/logos are rarely perfect squares - avoids cropping into the mark itself.

   Sized up 28px -> 34px, 2026-09-30 alongside `.kpi-avatar`/`.ticker-pill`/
   `.ticker-wrap` below (Trevor, from a screenshot of the page zoomed out to 50%:
   "we could also increase the width of the ticker so its easier to see") - both
   `.kpi-logo` and `.kpi-avatar` are only ever used inside the ticker's pills now
   (the separate First-Time Customers card they originally matched was removed
   2026-09-25), so sizing them up only affects the ticker, not anything else. */
.kpi-logo {
    width: 34px;
    height: 34px;
    border-radius: 9999px;
    object-fit: contain;
    flex-shrink: 0;
    background: var(--slate-100);
}

/* Auto-scrolling ticker (2026-09-24) - added for First-Time Customers In-House,
   below the page title (Trevor: "an auto scrolling ticker... across the top of the
   page"). "Scrolling pills" was the chosen style out of 3 options shown - each pill
   reuses .kpi-avatar/.kpi-logo as-is for the same real-logo-with-fallback treatment
   already on the KPI cards, not a separate icon system. The track is rendered twice
   in the template (second copy `aria-hidden`) so the loop from -50% back to 0% is
   seamless - CSS alone can't repeat content. Pauses on hover so it's actually
   readable if someone's standing in front of it, e.g. on a warehouse TV.

   Gained a title + total-count badge 2026-09-25 (Trevor: "title it... and put a box
   with a total value in it, maybe a light purple box") - first tried as a fixed
   segment inside the ticker bar itself, then moved to its own header line above the
   bar the same day (Trevor: "put the title just above the ticker and not in it") -
   see `.ticker-header`/`.ticker-count` below; `.ticker-wrap` is the scrolling box
   alone again, no label segment inside it. */
.ticker-header {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 0.25rem;
    white-space: nowrap;
    flex-shrink: 0;
}
.ticker-count {
    background: var(--brand-lavender-light);
    color: var(--brand-purple);
    font-size: 0.8125rem;
    font-weight: 700;
    padding: 2px 10px;
    border-radius: 9999px;
}
/* Sized up (48px -> 60px bar, larger pill text/padding), 2026-09-30 (Trevor, from
   a 50%-zoomed-out screenshot: "we could also increase the width of the ticker so
   its easier to see") - "width" there meant the pills themselves reading bigger,
   not the bar's own already-full-bleed width, so the bar grew taller and the
   pills/logos (`.kpi-logo`/`.kpi-avatar` above) grew with it rather than the
   outer `.ticker-wrap` box gaining any new horizontal size. */
.ticker-wrap {
    overflow: hidden;
    background: #ffffff;
    border: 1px solid var(--slate-200);
    border-radius: 0.5rem;
    height: 60px;
    display: flex;
    align-items: center;
    padding: 0 4px;
}
.ticker-track {
    display: flex;
    gap: 8px;
    white-space: nowrap;
    animation: ticker-scroll 30s linear infinite;
}
.ticker-wrap:hover .ticker-track {
    animation-play-state: paused;
}
.ticker-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--slate-50);
    border: 1px solid var(--slate-200);
    color: var(--brand-purple);
    font-size: 0.9rem;
    font-weight: 600;
    padding: 6px 18px 6px 6px;
    border-radius: 9999px;
    flex-shrink: 0;
}
@keyframes ticker-scroll {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
}
/* Added 2026-10-01 for the case where there are few enough first-time
   customers to fit the bar without scrolling (e.g. just 1) - the endless
   `ticker-scroll` loop on a track narrower than its own duplicate-for-
   seamlessness copy looked broken (content yanking back and forth) rather
   than just sitting still. `.ticker-static` is toggled by the measuring
   script in _dashboard_overview.html, not set server-side - see that script
   for why. The aria-hidden second copy (same template) is only ever there to
   make the infinite scroll seamless, so it's hidden here rather than left to
   sit unused next to the real one. */
.ticker-wrap.ticker-static .ticker-track {
    animation: none;
}
.ticker-wrap.ticker-static .ticker-pill[aria-hidden] {
    display: none;
}

/* In-Process Service Orders table header, reworked twice 2026-09-25. First tried
   putting the title in the same row as the stats/button via a 3-column grid - dropped
   in favor of moving the title outside the white card entirely (Trevor: "move the
   title of this table just outside the top of the white table to not compress the
   UI"), same "title sits above its own box" pattern as the ticker header. Left as a
   plain flex row now - just the two stat values (left) and Show Stragglers (right).
   `.ticker-header`'s own `white-space: nowrap; flex-shrink: 0` (see above) keeps
   "Service Orders In Process"/"Garments In Process" each on one line even when the
   row gets tight, per Trevor's explicit ask - without it the label text itself was
   wrapping mid-phrase. Reuses .ticker-header/.ticker-count as-is for the two stat
   values (same light-purple badge as the ticker), not a new style. */
.workload-header-stats {
    display: flex;
    align-items: center;
    gap: 20px;
}

/* Nav hamburger drawer, added 2026-09-25 as a mobile-only pattern (Trevor: "how can
   we make the top navigation bar collapsed, maybe a hamburger style menu" - shown 2
   live options, picked "option 1" - the slide-down drawer with a dimming overlay,
   over a compact dropdown). `header` gets `position:relative` (inline, in
   base.html) so this drawer can anchor to `top:100%` and stretch full-width, rather
   than a narrow side panel. Toggled via toggleNavDrawer() in base.html's own
   <script> block - no separate JS file for one small interaction.

   **Widened to every screen size, 2026-09-30** (Trevor, shown 3 mockups of a
   collapsed header: "the top nav bar is becoming too busy... utilize some type of
   hamburger menu to collapse all the screens" - picked the full-collapse option,
   keeping one always-visible "Performance Dashboard" link outside the drawer at
   first) - the trigger button and this drawer both dropped their old `md:hidden`
   class (see base.html's own comment on the header for the fuller story), so the
   exact same hamburger now collapses Repairs/Customer Analytics/Outbound
   Metrics/Users, account info, and the refresh button/last-refreshed caption on
   desktop too, not just on phones. "Performance Dashboard" itself moved INTO the
   drawer too later the same day (Trevor: "can you removwe the RCS and below
   words and just keep the shield logo on the nav menu. also can you put the
   performance dashboard in the menu dropdown") - the always-visible bar is just
   the hamburger + shield mark now, nothing left outside the drawer at all. No
   longer reuses `{{ self.nav() }}` either - with only one page template's worth of
   links left (this drawer's own, not a per-page block anymore), there's nothing
   left to re-render a second copy of. `#nav-drawer a` stays scoped rather than
   per-link classes, for the same consistent block-level tap target reason as
   before. */
.nav-hamburger {
    background: none;
    border: none;
    font-size: 1.35rem;
    line-height: 1;
    color: var(--brand-purple);
    cursor: pointer;
    padding: 4px 6px;
}
/* Purple home icon, added 2026-09-30 next to the hamburger (Trevor: "put a purple
   home icon next to the nav hamburger menu") - took over the hamburger-adjacent
   spot the shield logo used to sit in, once that logo moved next to the page
   title instead (see .nav-title-row below). Plain inline SVG (`currentColor`
   stroke picks up this link's own `color`), same "hand-rolled inline SVG, no
   icon font/library" convention as the rest of this app's charts. */
.nav-home-link {
    display: flex;
    align-items: center;
    padding: 4px 6px;
    color: var(--brand-purple);
}
.nav-home-link:hover {
    opacity: 0.75;
}
/* Per-page nav bar title, 2026-09-30 (Trevor: "instead of putting the title of
   the screen on the page itself, can you put it in the middle of the nav bar
   when you are on a selected page") - see base.html's `page_heading` block for
   the fuller story.

   **Switched from absolute-centered-on-the-whole-bar to flex-centered-in-the-
   remaining-space, 2026-09-30** (Trevor: "put the shield logo [] to the right of
   screen title in the nav bar") - the logo can only sit truly adjacent to the
   title (not just visually near it) if they're real flow siblings, which the
   old `position:absolute` title fought against (nothing else could share its
   exact centering behavior). `.nav-title-row` is the `flex:1` sibling of the
   hamburger+home-icon pair (base.html) that centers this title+logo pair
   together in the space left over - not the true center of the entire bar
   anymore, but close, and title/logo now move as one unit either way. */
.nav-title-row {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-width: 0;
}
.nav-page-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--brand-purple);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.nav-title-logo {
    height: 24px;
    width: auto;
    flex-shrink: 0;
}
#nav-drawer {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    /* Light purple, 2026-09-30 (Trevor, from the mockups: "can we make the
       background of the drop down this purple color like you mocked up") -
       `var(--brand-lavender-light)`, the same light-purple fill already used
       elsewhere (the ticker's count badge, the dashboard title's own fill) - not a
       new color, and distinct enough from the white header/page behind it to read
       as its own dropdown surface instead of plain white-on-white. */
    background: var(--brand-lavender-light);
    border-bottom: 1px solid var(--slate-200);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    z-index: 40;
    /* Closed state + open transition, 2026-09-30 (Trevor: "can we make the UX
       smoother, when you click on the nav hamburger menu, it opens and has a
       softer landing when it fully expands... similar to a soft close drawer") -
       used to be a plain `display:none`/`block` toggle in JS (toggleNavDrawer(),
       base.html), which snapped open/closed with no motion since `display` can't
       be transitioned. Opacity+transform can, so those now carry the open/closed
       state instead, toggled via the `.nav-drawer-open` class - `display` is
       never touched, so the drawer stays `position:absolute` (out of layout flow)
       the whole time regardless of state. `pointer-events:none` here keeps it
       unclickable/untabbable while closed, same as `display:none` used to.
       `cubic-bezier(0.16, 1, 0.3, 1)` is a smooth-deceleration ease-out (no
       bounce/overshoot) - reads as a gentle landing without feeling springy in a
       work dashboard. */
    opacity: 0;
    transform: translateY(-6px);
    pointer-events: none;
    transition: opacity 0.22s cubic-bezier(0.16, 1, 0.3, 1), transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
#nav-drawer.nav-drawer-open {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}
#nav-drawer a, #nav-drawer span {
    display: block;
    padding: 10px 16px;
}
/* Log out is a POST form button (see base.html), styled to match the link rows above it. */
#nav-drawer .nav-logout-btn {
    display: block;
    width: 100%;
    text-align: left;
    padding: 10px 16px;
    background: none;
    font-size: inherit;
}
/* "In Development" tag next to Customer Analytics, 2026-09-30 (Trevor: "can we add
   a label next to the customer analytics tab name that says 'In development'
   maybe with a light yellow border") - `#nav-drawer span` above defaults every
   span in this drawer to a full-width block row (that rule is for the plain
   account-info rows sharing this drawer, e.g. the user's name), so this inline
   tag needs its own higher-specificity override back to a small pill sitting on
   the same line as the link text. */
#nav-drawer .nav-badge-dev {
    display: inline-block;
    padding: 1px 8px;
    margin-left: 6px;
    border: 1px solid var(--amber-100);
    border-radius: 9999px;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--amber-700);
    vertical-align: middle;
}
#nav-drawer-overlay {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.35);
    z-index: 39;
    /* Fades in step with #nav-drawer above, same reasoning/date. */
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s ease;
}
#nav-drawer-overlay.nav-drawer-open {
    opacity: 1;
    pointer-events: auto;
}

/* Open Supplemental Service Cases spotlight KPI tile (2026-09-26) - replaces the
   Trial Cases In-House tile (which itself briefly cross-faded one case at a time via
   .trial-spotlight-case, then showed every case at once via .trial-case-chip - both
   removed now). Every category's slide is rendered into the same tile footprint and
   stacked with `position: absolute`, then cross-faded via opacity - without the
   absolute positioning here, all 5 slides render in normal flow at once, stacked
   tall (a real bug hit live the same day: the HTML/JS landed before this CSS did).

   **Resized 2026-09-29** when this card moved from its own wide card next to the
   Volume chart into the top KPI row as a 7th equal column (Trevor: "All 7 cards
   should be the same size and should only be a bit smaller version than the 6 that
   are currently there now"). The old 270px min-height/flex:1/3rem value/facepile
   (`.supp-trial-list`/`.supp-trial-chip`, dropped the same day per Trevor - "drop
   the facepile") were all sized to stretch against a much taller sibling (first the
   Trial Service account list, later the Projected Volume chart it briefly shared
   this cross-fade mechanic with) - none of that applies in a compact KPI tile, so
   `min-height`/`flex` are gone and `.supp-spotlight-value` now matches the other 6
   tiles' own value size (`text-2xl`, 1.5rem) instead of towering over them. */
.supp-spotlight {
    position: relative;
    min-height: 44px;
}
.supp-spotlight-slide {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    opacity: 0;
    transition: opacity 0.6s ease;
    pointer-events: none;
}
.supp-spotlight-slide.supp-spotlight-active {
    opacity: 1;
    pointer-events: auto;
}
.supp-spotlight-value {
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--slate-900);
    margin-top: 2px;
}
/* .supp-spotlight-dots/.supp-spotlight-dot(-active) removed 2026-09-30 (Trevor:
   "lets remove the dots at the bottom and just keep the auto scroll") - the slide
   still auto-cycles, just with no position-indicator dots under it anymore. */

/* Projected Volumes card - 4 always-visible stationary charts, 2026-09-28 (Trevor:
   "these should be stationary charts since we are breaking them out into 4... lets
   see the inbound on the top row and outbound on the bottom row") - replaced the
   old cross-fading spotlight card (title/badge row, per-bar cycling callout bubble)
   entirely; see scanning.html's own comment at this card's markup for that fuller
   history.

   **Went from a 2-column grid to a single 4-across row and back again, 2026-09-29**
   - briefly went full-width-single-row the same day the card itself went full-width
   (Trevor: "make the 4 charts extend the entire width of the screen"), reverted
   within the hour once he saw it ("can we go back to the 2x2 grid for the charts,
   we have the room for it and I think it looks better"). Named `grid-template-areas`
   now, not source order, since a 3rd column (the size-color legend, right of the
   Loaner column only - see `.volume-size-legend` below) needs explicit placement a
   plain `repeat(2, ...)` auto-flow can't express; each chart cell sets its own
   `grid-area` inline in scanning.html rather than relying on DOM order. */
.volume-grid {
    display: grid;
    grid-template-columns: 1fr 1fr auto;
    grid-template-areas:
        "cust-in loaner-in legend"
        "cust-out loaner-out legend";
    gap: 20px 16px;
    align-items: center;
}
/* Bordered/bold, 2026-09-29 (Trevor: "can we put a boarder around the titles for
   the charts and bold the text in the titles so so theey stand out more?") - same
   bordered-pill treatment as the "In-Process Service Orders" section title
   (`_in_process_table.html`), for visual consistency rather than a new look.
   `display: inline-block` (needed so the border only wraps the text, not the full
   cell width) drops this element's own block-level auto-centering - its own
   `text-align: center` only centers content INSIDE it, not itself - so each chart
   cell's wrapping div (scanning.html) now carries `text-align: center` to actually
   center the pill (Trevor: "the titles of the charts should be centered at the top
   for each chart" - a real regression this same round, not present before the
   border/bold change). */
.volume-grid-label {
    display: inline-block;
    font-size: 0.75rem;
    font-weight: 700;
    color: var(--slate-700);
    text-align: center;
    border: 1px solid var(--slate-200);
    border-radius: 0.375rem;
    padding: 4px 10px;
    margin-bottom: 4px;
}
/* Loaner size-color legend, added 2026-09-29 alongside the stacked/color-coded
   Loaner Volume bars (core/loaner_volume.py's SIZE_COLORS) - see scanning.html.
   **Moved to its own column right of the Loaner charts, same day** (Trevor: "since
   the legend for color sizes only applies to loaners, can we put the legend on the
   right of both these charts?") - stacked vertically (was a horizontal wrapped row
   below the whole grid) since it's now a narrow sidebar, not a full-width footer;
   `justify-content: center` centers it against the combined height of both Loaner
   chart rows (its grid area spans both, see `.volume-grid` above). */
.volume-size-legend {
    grid-area: legend;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 10px;
    padding-left: 16px;
    border-left: 1px solid var(--slate-200);
}
.volume-size-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.75rem;
    color: var(--slate-500);
    white-space: nowrap;
}
.volume-size-swatch {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    flex-shrink: 0;
}

/* The In-Process Service Orders table and the ticker/KPI/Volume section both used
   to have their own click-to-fullscreen-a-div button (real Fullscreen API,
   requestFullscreen() on a wrapping div) for putting either half on its own
   warehouse TV - added 2026-09-25 and 2026-09-29. Removed entirely 2026-09-30,
   not just hidden (Trevor: "if the KPI cards and chart will be on one tv and the
   table of in process service orders on another tv, maybe this isn't the best
   way to break things out, should they just be two tabs on the nav bar?") - the
   Fullscreen API approach was a real source of bugs that same session (scrolling
   not reliably working once Chromium promotes an element into the fullscreen top
   layer, then needing a rescale to fit one screen), all traceable to fighting the
   browser's own top-layer rendering rather than working with it. Replaced same day
   by two dedicated TV routes, then those were ALSO removed later that day (Trevor:
   "lets remove the dashboard overview TV and In-Process Order TV for now, we'll
   figure out the best method for displaying these later") - still no Fullscreen
   API/click-to-enlarge button on this page either way; the two sections just
   render back to back on the one combined /scanning-dashboard page for now. */

/* Text colors */
.text-white { color: #ffffff; }
.text-slate-300 { color: var(--slate-300); }
.text-slate-400 { color: var(--slate-400); }
.text-slate-500 { color: var(--slate-500); }
.text-slate-600 { color: var(--slate-600); }
.text-slate-700 { color: var(--slate-700); }
.text-red-600 { color: var(--red-600); }
.text-red-700 { color: var(--red-700); }
.text-amber-700 { color: var(--amber-700); }
.text-emerald-700 { color: var(--emerald-700); }
.text-emerald-800 { color: var(--emerald-800); }
.text-brand-purple { color: var(--brand-purple); }

/* Backgrounds */
.bg-white { background-color: #ffffff; }
.bg-slate-50 { background-color: var(--slate-50); }
.bg-slate-900 { background-color: var(--slate-900); }
.bg-red-50 { background-color: var(--red-50); }
.bg-red-100 { background-color: var(--red-100); }
.bg-amber-100 { background-color: var(--amber-100); }
.bg-emerald-50 { background-color: var(--emerald-50); }
.bg-brand-green { background-color: var(--brand-green); }
.bg-brand-purple { background-color: var(--brand-purple); }
.bg-brand-lavender-light { background-color: var(--brand-lavender-light); }
.bg-brand-lavender-band { background-color: var(--brand-lavender-band); }

/* Borders / shape */
.border { border: 1px solid var(--slate-200); }
.border-t { border-top: 1px solid var(--slate-200); }
.border-b { border-bottom: 1px solid var(--slate-200); }
.border-slate-100 { border-color: var(--slate-100); }
.border-slate-200 { border-color: var(--slate-200); }
.border-slate-300 { border-color: var(--slate-300); }
.border-slate-50 { border-color: var(--slate-50); }
.border-red-200 { border-color: var(--red-200); }
.border-emerald-200 { border-color: var(--emerald-200); }
.border-brand-lavender { border-color: var(--brand-lavender); }
.rounded { border-radius: 0.25rem; }
.rounded-md { border-radius: 0.375rem; }
.rounded-lg { border-radius: 0.5rem; }
.rounded-full { border-radius: 9999px; }
.shadow-sm { box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); }

/* Interactive states */
.hover\:bg-slate-800:hover { background-color: var(--slate-800); }
.hover\:bg-slate-50:hover { background-color: var(--slate-50); }
.hover\:bg-brand-green-dark:hover { background-color: var(--brand-green-dark); }
.hover\:text-white:hover { color: #ffffff; }
.hover\:text-brand-purple:hover { color: var(--brand-purple); }
.hover\:underline:hover { text-decoration: underline; }
.focus\:outline-none:focus { outline: none; }
.focus\:ring-2:focus { box-shadow: 0 0 0 2px var(--slate-800); }

.badge {
    display: inline-block;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
    font-size: 0.75rem;
    font-weight: 600;
    margin-right: 0.25rem;
}

/* Customer Analytics hierarchy tree (2026-09-18) - modeled after the native
   Salesforce Account Hierarchy view (Trevor attached a screenshot). A CSS-grid "table"
   built from stacked rows rather than a real <table>, since <details> (used for
   expand/collapse - no JS, same pattern as the Daily Huddle page) can't nest cleanly
   inside <tr>/<td>. */
.hierarchy-row {
    display: grid;
    grid-template-columns: minmax(220px, 1fr) 150px 110px 120px 120px 110px;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.75rem;
    font-size: 0.875rem;
}

.hierarchy-header {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--slate-500);
    background: var(--slate-50);
    border-bottom: 1px solid var(--slate-200);
}

.hierarchy-node > summary {
    cursor: pointer;
    list-style: none;
    border-top: 1px solid var(--slate-100);
}

.hierarchy-node > summary::-webkit-details-marker {
    display: none;
}

/* The chevron lives on .hierarchy-name (a real grid-cell child), not on <summary>
   itself - <summary> has display:grid (via .hierarchy-row), and a ::before directly
   on a grid container becomes a genuine extra grid item (the box generated by a
   pseudo-element on the grid box IS a grid item per spec), which silently shifted
   every real column over by one and wrapped the last one onto a second row. Real bug
   hit and fixed immediately after this first rendered against real data. */
.hierarchy-node > summary .hierarchy-name::before {
    content: "\25B8"; /* small right-pointing triangle, like Salesforce's collapsed chevron */
    display: inline-block;
    width: 1em;
    margin-right: 0.15rem;
    color: var(--slate-400);
    transition: transform 0.15s ease;
}

.hierarchy-node[open] > summary .hierarchy-name::before {
    transform: rotate(90deg);
}

.hierarchy-leaf {
    border-top: 1px solid var(--slate-100);
    color: var(--slate-600);
}

.hierarchy-name {
    color: var(--brand-purple);
    font-weight: 600;
}

.hierarchy-leaf .hierarchy-name {
    font-weight: 400;
    color: var(--slate-700);
    padding-left: 1.15em; /* aligns with a parent row's name text, which sits after its chevron */
}

.hierarchy-depth-0 { padding-left: 0.75rem; }
.hierarchy-depth-1 { padding-left: 2.25rem; }
.hierarchy-depth-2 { padding-left: 3.75rem; }

/* KPI card value text sized up 1.5rem -> 1.875rem, 2026-09-30 (Trevor, from a
   50%-zoomed-out screenshot: "we can make the text in the KPI cards a bit larger,
   if we scale it back like this, so they are easier to see") - scoped to
   `.kpi-grain-value` specifically rather than bumping the shared `.text-2xl`
   utility it's paired with in scanning.html, since `.text-2xl` is also every
   other page's own h1/KPI-value size (Repairs, Customer Analytics, Outbound
   Metrics, Daily Huddle) and this ask was about these 7 cards only. Comes after
   `.text-2xl` in the cascade so it wins the tie (both are single-class
   selectors, same specificity - source order decides). */
.kpi-grain-value {
    font-size: 1.875rem;
}

/* KPI grain filter (Day/Week/Month/Year) - a rounded pill segmented control,
   replacing the old plain bordered buttons (2026-09-18, Trevor: "make this more
   aesthetically pleasing"). One shared light-gray track (.kpi-grain-group) holds the
   buttons, each styled as .kpi-grain-control - see setKpiGrain() in scanning.html. */
.kpi-grain-group {
    display: inline-flex;
    align-items: center;
    gap: 0.125rem;
    background: var(--slate-100);
    padding: 0.25rem;
    border-radius: 9999px;
    width: fit-content;
}

.kpi-grain-control {
    -webkit-appearance: none;
    appearance: none;
    border: none;
    background: transparent;
    color: var(--slate-600);
    font-size: 0.8rem;
    font-weight: 500;
    padding: 0.4rem 0.9rem;
    border-radius: 9999px;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.kpi-grain-control:hover {
    color: var(--brand-purple);
}

.kpi-grain-control.kpi-grain-active {
    background: var(--brand-purple);
    color: #ffffff;
    box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.15);
}

.kpi-grain-control.kpi-grain-active:hover {
    color: #ffffff;
}

/* Today/Week/Month/Year filter, redesigned into a vertical radio-style control just
   left of the COGS Scanned In KPI card, 2026-09-26 (Trevor: "move the today, week
   month year filter to a radio style button just to the left of the cogs scanned in
   KPI card") - shown as a design option and approved ("option 1 for the filter is
   great") well before this landed; the actual markup change slipped through amid the
   Volume-card work the same session and had to be caught once Trevor noticed the old
   horizontal pill row was still there.

   `.kpi-filter-radio` is a MODIFIER on `.kpi-grain-group`/`.kpi-grain-control` (same
   elements, extra class), not a parallel set of classes - setKpiGrain() (base.html)
   looks for those exact class names via `.closest(".kpi-grain-group")` and toggles
   `.kpi-grain-active`, so reusing them keeps the JS untouched. The rules below only
   override the visual shape (a light-gray pill track -> a bordered vertical list with
   a radio dot per row); `outbound_metrics.html`'s own reuse of the plain
   `.kpi-grain-group`/`.kpi-grain-control` for its gauge-period filter is completely
   unaffected, since it never gets this modifier class. */
.kpi-grain-group.kpi-filter-radio {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    background: #ffffff;
    border: 1px solid var(--slate-200);
    border-radius: 0.5rem;
    padding: 10px 8px;
    width: auto;
    flex: 0 0 92px;
}
.kpi-filter-radio .kpi-grain-control {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 6px;
    border-radius: 6px;
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--slate-500);
    text-align: left;
}
.kpi-filter-radio .kpi-grain-control::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 9999px;
    border: 1.5px solid var(--slate-300);
    flex-shrink: 0;
}
.kpi-filter-radio .kpi-grain-control:hover {
    color: var(--brand-purple);
}
.kpi-filter-radio .kpi-grain-control.kpi-grain-active {
    background: var(--brand-lavender-light);
    color: var(--brand-purple);
    box-shadow: none;
}
.kpi-filter-radio .kpi-grain-control.kpi-grain-active:hover {
    color: var(--brand-purple);
}
.kpi-filter-radio .kpi-grain-control.kpi-grain-active::before {
    border-color: var(--brand-purple);
    background: var(--brand-purple);
}

/* Dashboard visual refresh (2026-10-02, built in the sandbox first): KPI cards
   with trend lines, Projected Volumes as four titled cards, and a more scannable
   In-Process Service Orders table. Color carries meaning, same as the rest of the app:
   purple = inbound / general, teal = outbound, amber = needs attention, gray = neutral;
   red/amber/green only for overdue / due soon / complete. Every class here is new - no
   existing page's styles were changed. */
.dash-toprow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 8px;
}
.kpi-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 12px;
    align-items: stretch;
}
.kcard {
    background: #ffffff;
    border: 1px solid var(--slate-200);
    border-radius: 12px;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}
.kcard-attn {
    border-color: #fac775;
    background: #fffaf0;
}
.kcard-attn .kcard-value { color: var(--amber-700); }
.kcard-value.kcard-soon { font-size: 1.25rem; font-weight: 500; color: var(--slate-400, #94a3b8); }
.kcard-head {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--slate-500);
    font-size: 0.8125rem;
    font-weight: 500;
    line-height: 1.2;
}
.kcard-value {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    font-size: 2.25rem;
    font-weight: 600;
    color: var(--slate-900);
    line-height: 1.1;
}
.kcard-total { font-size: 1.75rem; font-weight: 600; color: var(--slate-900); line-height: 1; }
.kcard-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    margin-top: auto;
    min-height: 24px;
}
.kdelta { font-size: 0.75rem; color: var(--slate-500); line-height: 1.25; }
.kdelta-up { color: var(--emerald-700); }
.kdelta-down { color: var(--red-700); }
.kdelta-warn { color: var(--amber-700); }
.kring { gap: 14px; }
.kring-svg { flex: none; }
.kring-text { display: flex; flex-direction: column; align-items: flex-start; }
.kring-num { font-size: 2.25rem; font-weight: 600; line-height: 1.1; color: var(--slate-900); }
.kring-sub { font-size: 0.8125rem; font-weight: 400; color: var(--slate-500); line-height: 1.2; }
.sscroll { height: 72px; overflow: hidden; margin-top: 2px; }
.slist { display: flex; flex-direction: column; animation: sscroll-move 12s linear infinite; }
.sscroll:hover .slist { animation-play-state: paused; }
@keyframes sscroll-move {
    from { transform: translateY(0); }
    to { transform: translateY(-50%); }
}
.srow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    height: 24px;
    flex: none;
    border-top: 1px solid var(--slate-100);
    font-size: 0.8125rem;
    color: var(--slate-400);
}
.srow-n { font-weight: 600; min-width: 1.5rem; text-align: center; }
.srow-on { color: var(--slate-700); }
.srow-on .srow-n { background: var(--amber-100); color: #633806; border-radius: 9999px; padding: 1px 8px; }

.vol-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 10px;
}
.vol-title { font-size: 0.9375rem; font-weight: 600; color: var(--brand-purple); }
.vol-sub { font-size: 0.8125rem; color: var(--slate-500); }
.vol-legend { margin-left: auto; display: flex; flex-wrap: wrap; gap: 12px; }
.vol-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}
.vcard {
    background: #ffffff;
    border: 1px solid var(--slate-200);
    border-radius: 12px;
    padding: 14px;
    min-width: 0;
}
.vcard-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 4px; }
.vcard-title { font-size: 0.8125rem; color: var(--slate-500); }
.vcard-total { display: flex; align-items: baseline; gap: 6px; }
.vcard-total strong { font-size: 1.5rem; font-weight: 600; color: var(--slate-900); line-height: 1.2; }
.vcard-unit { font-size: 0.75rem; color: var(--slate-500); }
.vchip {
    margin-left: auto;
    border-radius: 9999px;
    padding: 2px 10px;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}
.vchip-purple { background: var(--brand-lavender-light); color: var(--brand-purple); }
.vchip-teal { background: #e1f5ee; color: #085041; }
.vcard-foot { margin: 2px 0 0; font-size: 0.75rem; color: var(--slate-500); }
.vgrp { cursor: default; }
.vgrp:hover path { opacity: 0.85; }
#vol-tip {
    display: none;
    position: fixed;
    z-index: 60;
    pointer-events: none;
    background: #1e293b;
    color: #ffffff;
    font-size: 0.75rem;
    line-height: 1.5;
    padding: 6px 10px;
    border-radius: 8px;
    white-space: pre-line;
    max-width: 340px;
}

.ipt-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 10px;
}
.ipt-title { margin: 0; font-size: 0.9375rem; font-weight: 600; color: var(--brand-purple); }
.ipt-controls { display: flex; flex-wrap: wrap; gap: 6px; }
.ipt-chip {
    border: 1px solid var(--slate-200);
    background: #ffffff;
    color: var(--slate-600);
    font-size: 0.75rem;
    padding: 4px 12px;
    border-radius: 9999px;
    cursor: pointer;
}
.ipt-chip-on { background: var(--brand-lavender-light); border-color: var(--brand-lavender); color: var(--brand-purple); }
.ipt-chip-ghost { margin-left: 6px; color: var(--brand-purple); }
.ipt-chip-n { opacity: 0.7; margin-left: 2px; }
.ipt-stats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 260px));
    gap: 12px;
    margin-bottom: 12px;
}
.ipt-stat { flex-direction: row; align-items: baseline; gap: 8px; }
.ipt-stat-value { font-size: 1.5rem; font-weight: 600; color: var(--slate-900); }
.ipt-stat-label { font-size: 0.8125rem; color: var(--slate-500); }
.ipt-card {
    background: #ffffff;
    border: 1px solid var(--slate-200);
    border-radius: 12px;
    overflow: hidden;
}
.ipt-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.ipt-table th {
    text-align: left;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    color: var(--slate-500);
    padding: 10px 12px;
    background: var(--slate-50);
    border-bottom: 1px solid var(--slate-200);
    white-space: nowrap;
}
.ipt-table td { padding: 10px 12px; border-bottom: 1px solid var(--slate-100); vertical-align: middle; }
.ipt-table tr.ipt-row:hover td { background: var(--slate-50); }
.ipt-num { text-align: center !important; }
.ipt-name-text { font-weight: 600; color: var(--brand-purple); }
.ipt-date { margin-left: 6px; font-size: 0.75rem; color: var(--slate-400); }
.ipt-rail-overdue { box-shadow: inset 3px 0 0 var(--red-600); }
.ipt-rail-soon { box-shadow: inset 3px 0 0 #ba7517; }
.ipt-tag {
    display: inline-block;
    border-radius: 9999px;
    padding: 1px 9px;
    font-size: 0.75rem;
    font-weight: 500;
    background: var(--brand-lavender-light);
    color: var(--brand-purple);
}
.ipt-muted { color: var(--slate-400); }
.ipt-prog-text { font-size: 0.875rem; color: var(--slate-600); margin-bottom: 4px; }
.ipt-done { color: var(--emerald-700); }
.ipt-prog { height: 6px; border-radius: 3px; background: var(--brand-lavender-light); overflow: hidden; }
.ipt-prog span { display: block; height: 100%; background: #534ab7; border-radius: 3px; }
.ipt-prog-done span { background: #1d9e75; }
.ipt-pill {
    display: inline-block;
    border-radius: 9999px;
    padding: 2px 10px;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}
.ipt-pill-red { background: var(--red-100); color: #791f1f; }
.ipt-pill-amber { background: var(--amber-100); color: #633806; }
.ipt-pill-gray { background: var(--slate-100); color: var(--slate-600); }
.ipt-sub { font-size: 0.6875rem; color: var(--slate-500); margin-top: 3px; }
.ipt-note {
    max-width: 300px;
    font-size: 0.75rem;
    line-height: 1.35;
    color: var(--slate-700, #334155);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.ipt-indicator { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.ipt-dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid rgba(15, 23, 42, 0.25); flex: none; }
.ipt-empty { padding: 20px 12px; text-align: center; color: var(--slate-400); }

@media print {
    .no-print { display: none !important; }
    body { background: white !important; }
    main { max-width: 100% !important; padding: 0 !important; }
}

/* KPI click-through dialog (2026-10-06): every KPI card except Supplemental opens it. */
.kcard-click { cursor: pointer; transition: border-color 0.15s, box-shadow 0.15s; }
.kcard-click:hover { border-color: var(--purple-400, #afa9ec); box-shadow: 0 2px 10px rgba(83, 74, 183, 0.12); }
.kcard-click:focus-visible { outline: 2px solid #534ab7; outline-offset: 2px; }
.kdlg {
    border: 1px solid var(--slate-200);
    border-radius: 14px;
    padding: 0;
    width: min(560px, 92vw);
    max-height: 80vh;
    box-shadow: 0 20px 50px rgba(15, 23, 42, 0.25);
    color: var(--slate-900, #0f172a);
    overflow: hidden;
}
.kdlg[open] { display: flex; flex-direction: column; }
.kdlg::backdrop { background: rgba(15, 23, 42, 0.45); }
.kdlg-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding: 16px 18px 10px; }
.kdlg-title { font-size: 1.15rem; font-weight: 600; margin: 0; }
.kdlg-sub { font-size: 0.8rem; color: var(--slate-500, #64748b); margin-top: 2px; }
.kdlg-close { background: none; border: none; font-size: 1.6rem; line-height: 1; cursor: pointer; color: var(--slate-500, #64748b); padding: 0 4px; }
.kdlg-close:hover { color: #0f172a; }
.kdlg-note { margin: 0 18px 8px; padding: 8px 10px; border-radius: 8px; background: #f5f3ff; color: #4c4399; font-size: 0.8rem; }
.kdlg-body { overflow-y: auto; padding: 0 18px; flex: 1; min-height: 0; }
.kdlg-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.kdlg-table th { position: sticky; top: 0; background: #fff; text-align: left; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--slate-500, #64748b); padding: 6px 0; border-bottom: 1px solid var(--slate-200); }
.kdlg-table td { padding: 7px 0; border-bottom: 1px solid #f1f5f9; }
.kdlg-table .num { text-align: right; font-variant-numeric: tabular-nums; padding-left: 16px; white-space: nowrap; }
.kdlg-empty { padding: 24px 0; text-align: center; color: var(--slate-500, #64748b); }
.kdlg-foot { display: flex; justify-content: space-between; padding: 12px 18px; border-top: 1px solid var(--slate-200); font-weight: 600; background: #fafafa; }
