Files
dailygrubs_console/src/pages/nearle/dispatch/Dispatch.css

9848 lines
220 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root {
--bg: #ffffff;
--bg-sub: #f8fafc;
--bg-card: #ffffff;
--border: #e2e8f0;
--border-active: #3b82f6;
--text: #1e293b;
--text-muted: #64748b;
--accent: #3b82f6;
--accent-soft: rgba(59, 130, 246, 0.08);
--kitchen: #f59e0b;
--kitchen-soft: rgba(245, 158, 11, 0.1);
--success: #22c55e;
--shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.08);
}
.dispatch-container {
width: calc(100% + 48px);
height: calc(100vh - 88px);
margin: -24px;
display: flex;
flex-direction: column;
background: var(--bg);
color: var(--text);
font-family: 'Inter', -apple-system, sans-serif;
overflow: hidden;
position: relative;
}
/* Embedded mode: rendered inside a parent container (e.g. a Dialog),
so drop the negative margin and viewport-based sizing that assumes
the standalone /dispatch page is wrapped in MainCard's 24px padding. */
.dispatch-container.embedded {
width: 100%;
height: 100%;
margin: 0;
flex: 1;
min-height: 0;
}
.dispatch-container * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Header */
.dispatch-container #hdr {
height: 56px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
background: var(--bg);
border-bottom: 1px solid var(--border);
z-index: 1010;
}
.dispatch-container .logo {
display: flex;
align-items: center;
gap: 12px;
}
.dispatch-container .logo-badge {
width: 32px;
height: 32px;
border-radius: 8px;
background: linear-gradient(135deg, #3b82f6, #2563eb);
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 14px;
color: #fff;
}
.dispatch-container .logo-name {
font-size: 18px;
font-weight: 800;
color: var(--text);
letter-spacing: -0.02em;
}
.dispatch-container .logo-name em {
color: var(--accent);
font-style: normal;
opacity: 0.8;
}
/* Operating-city pill — sits to the RIGHT of the "Dispatch" heading inline. */
/* The location pill is now an interactive dropdown trigger. Wrapped in
.logo-city-wrap so the absolute-positioned menu below anchors to it. */
.dispatch-container .logo-city-wrap {
position: relative;
display: inline-block;
}
.dispatch-container .logo-city {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 8px 4px 10px;
border-radius: 999px;
background: rgba(123, 31, 162, 0.08);
border: 1px solid rgba(123, 31, 162, 0.25);
color: #7b1fa2;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.02em;
line-height: 1;
cursor: pointer;
font-family: inherit;
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.dispatch-container .logo-city:hover {
background: rgba(123, 31, 162, 0.14);
border-color: rgba(123, 31, 162, 0.45);
}
.dispatch-container .logo-city.open {
background: rgba(123, 31, 162, 0.18);
border-color: rgba(123, 31, 162, 0.55);
box-shadow: 0 4px 12px rgba(123, 31, 162, 0.18);
}
.dispatch-container .logo-city svg {
font-size: 13px;
flex-shrink: 0;
}
.dispatch-container .logo-city-caret {
font-size: 15px;
transition: transform 0.2s ease;
}
.dispatch-container .logo-city.open .logo-city-caret {
transform: rotate(180deg);
}
.dispatch-container .logo-city-text {
max-width: 180px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Dropdown menu — anchored under the trigger, scrolls if there are many hubs. */
.dispatch-container .logo-city-menu {
position: absolute;
top: calc(100% + 6px);
left: 0;
min-width: 200px;
max-height: 320px;
overflow-y: auto;
background: #fff;
border: 1px solid rgba(123, 31, 162, 0.18);
border-radius: 12px;
box-shadow: 0 16px 36px rgba(15, 23, 42, 0.16);
padding: 6px;
z-index: 1000;
animation: logo-city-menu-in 0.14s ease-out;
}
@keyframes logo-city-menu-in {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dispatch-container .logo-city-menu::-webkit-scrollbar {
width: 6px;
}
.dispatch-container .logo-city-menu::-webkit-scrollbar-thumb {
background: rgba(123, 31, 162, 0.3);
border-radius: 999px;
}
.dispatch-container .logo-city-option {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
border: 0;
background: transparent;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
color: #1e293b;
cursor: pointer;
font-family: inherit;
text-align: left;
transition: background 0.12s ease;
}
.dispatch-container .logo-city-option:hover {
background: rgba(123, 31, 162, 0.06);
}
.dispatch-container .logo-city-option.active {
background: rgba(123, 31, 162, 0.1);
color: #7b1fa2;
}
.dispatch-container .logo-city-option-icon {
font-size: 14px;
color: #7b1fa2;
flex-shrink: 0;
}
.dispatch-container .logo-city-option span:not(.logo-city-option-check) {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .logo-city-option-check {
color: #7b1fa2;
font-weight: 800;
flex-shrink: 0;
}
.dispatch-container .hdr-sep {
width: 1px;
height: 20px;
background: var(--border);
margin: 0 4px;
}
.dispatch-container .hdr-meta {
font-size: 12px;
color: var(--text-muted);
font-weight: 500;
}
.dispatch-container #clock {
font-size: 13px;
color: var(--text);
font-weight: 600;
font-family: 'JetBrains Mono', monospace;
background: var(--bg-sub);
padding: 7px 16px;
border-radius: 10px;
border: 1px solid var(--border);
}
/* Header right-cluster — profit/loss + orders pill + date picker, sits to the
LEFT of the running clock. Pushed against the clock with margin-left:auto so
the .logo on the left stays anchored and the cluster floats right. */
.dispatch-container .hdr-stats {
display: flex;
align-items: center;
gap: 16px;
margin-left: auto;
margin-right: 16px;
min-width: 0;
flex-wrap: nowrap;
}
/* Tabs */
.dispatch-container #strat-row {
height: 54px;
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8px;
padding: 0 24px;
background: var(--bg);
border-bottom: 1px solid var(--border);
}
.dispatch-container .sbt {
padding: 8px 14px;
border-radius: 10px;
border: 1px solid rgba(15, 23, 42, 0.08);
background: var(--bg);
color: var(--text-muted);
font-size: 13px;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
line-height: 1;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
font-family: inherit;
}
.dispatch-container .sbt:hover {
background: var(--bg-sub);
color: var(--text);
border-color: var(--text-muted);
}
.dispatch-container .sbt.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.25);
}
/* SVG icon slot inside each tab button — fixed square, color inherits from button
so active-state white propagates without per-tab overrides. */
.dispatch-container .sbt .sbt-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
font-size: 18px;
line-height: 1;
flex-shrink: 0;
color: inherit;
}
.dispatch-container .sbt .sbt-icon svg {
width: 1em;
height: 1em;
display: block;
/* react-icons SVGs fill with currentColor by default — this just ensures
consistent baseline alignment with the label next to them. */
vertical-align: middle;
}
/* Strat-row quick stats — total orders + profit/loss chips next to the view-mode buttons */
.dispatch-container .strat-stats {
display: inline-flex;
align-items: center;
gap: 8px;
margin-left: 8px;
padding-left: 12px;
border-left: 1px solid var(--border);
height: 32px;
}
/* Right-floating variant — used for the profit/loss chip when there's no
live-controls block to nest inside. */
.dispatch-container .strat-stats.strat-stats-right {
margin-left: auto;
padding-left: 0;
border-left: none;
}
.dispatch-container .strat-stat {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 7px 15px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
line-height: 1;
border: 1px solid var(--border);
background: var(--bg);
color: var(--text);
transition: all 0.15s ease;
white-space: nowrap;
}
.dispatch-container .strat-stat-icon {
font-size: 13px;
line-height: 1;
}
.dispatch-container .strat-stat-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
}
.dispatch-container .strat-stat-value {
font-size: 13px;
font-weight: 800;
}
.dispatch-container .strat-stat-orders {
background: var(--accent-soft);
border-color: rgba(59, 130, 246, 0.25);
}
.dispatch-container .strat-stat-orders .strat-stat-value {
color: var(--accent);
}
.dispatch-container .strat-stat-profit {
background: rgba(34, 197, 94, 0.1);
border-color: rgba(34, 197, 94, 0.3);
}
.dispatch-container .strat-stat-profit .strat-stat-value,
.dispatch-container .strat-stat-profit .strat-stat-label {
color: var(--success);
}
.dispatch-container .strat-stat-loss {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.35);
}
.dispatch-container .strat-stat-loss .strat-stat-value,
.dispatch-container .strat-stat-loss .strat-stat-label {
color: #dc2626;
}
/* Live data controls (date picker + load status) */
.dispatch-container .live-controls {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
}
.dispatch-container .live-status {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
padding: 7px 15px;
border-radius: 999px;
background: var(--bg-sub);
border: 1px solid var(--border);
}
.dispatch-container .live-status-ready {
color: var(--success);
}
.dispatch-container .live-status-error {
color: #ef4444;
}
.dispatch-container .live-status-sub {
color: var(--text-muted);
font-weight: 500;
font-size: 11px;
opacity: 0.85;
}
.dispatch-container .live-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
animation: live-pulse 1.2s ease-in-out infinite;
}
.dispatch-container .live-dot.ready {
background: var(--success);
animation: none;
}
.dispatch-container .live-dot.error {
background: #ef4444;
animation: none;
}
@keyframes live-pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.4;
transform: scale(0.85);
}
}
/* ── Date picker chip ─────────────────────────────────────────────
Three-part pill: prev-day arrow ◂ | formatted-date card | ▸ next-day
arrow. The center card overlays a transparent native <input type="date">
so clicking anywhere on the chip opens the OS date dialog while still
showing a glanceable formatted value (`Mon, May 25, 2026`). A small
"Today" badge appears when the picked date matches today, and the
next-day arrow disables itself there.
Design language: matches the Compare-button family — soft white card,
indigo border + halo on hover/focus, subtle lift on interaction.
──────────────────────────────────────────────────────────────── */
.dispatch-container .date-chip {
position: relative;
/* anchors .date-cal-popover */
display: inline-flex;
align-items: stretch;
gap: 0;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 12px;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04),
0 4px 12px rgba(15, 23, 42, 0.06);
transition: border-color 0.18s ease, box-shadow 0.18s ease,
transform 0.18s ease;
}
.dispatch-container .date-chip.is-open {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
0 12px 30px rgba(99, 102, 241, 0.22);
}
.dispatch-container .date-chip:hover {
border-color: rgba(99, 102, 241, 0.45);
box-shadow: 0 2px 4px rgba(15, 23, 42, 0.06),
0 8px 22px rgba(99, 102, 241, 0.15);
transform: translateY(-1px);
}
.dispatch-container .date-chip:focus-within {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2),
0 8px 22px rgba(99, 102, 241, 0.22);
}
/* Center card — visible chrome the operator reads. Renders as a <button>
that toggles the custom calendar popover. Native focus ring is replaced
by the parent's focus-within shadow. */
.dispatch-container .date-chip-main {
appearance: none;
position: relative;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 8px 18px;
cursor: pointer;
min-width: 0;
background: transparent;
border: 0;
color: inherit;
font: inherit;
text-align: left;
}
.dispatch-container .date-chip-main:focus {
outline: none;
}
.dispatch-container .date-chip-main:hover {
background: rgba(99, 102, 241, 0.04);
}
.dispatch-container .date-chip-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 8px;
background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 100%);
color: #4338ca;
font-size: 15px;
flex-shrink: 0;
}
.dispatch-container .date-chip-text {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: 1.1;
gap: 2px;
min-width: 0;
}
.dispatch-container .date-chip-label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 10px;
font-weight: 800;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.dispatch-container .date-chip-today-pill {
display: inline-flex;
align-items: center;
padding: 1px 6px;
border-radius: 999px;
background: linear-gradient(135deg, #6366f1, #3b82f6);
color: #ffffff;
font-size: 9px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.35);
}
.dispatch-container .date-chip-value {
font-size: 13px;
font-weight: 700;
color: #0f172a;
letter-spacing: 0.01em;
white-space: nowrap;
}
.dispatch-container .date-chip-chevron {
display: inline-flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 18px;
margin-left: 2px;
transition: color 0.18s ease, transform 0.18s ease;
}
.dispatch-container .date-chip-main:hover .date-chip-chevron {
color: #4338ca;
}
/* Chevron flips up when the popover is open — same micro-interaction every
dropdown in the UI uses, so the operator instantly knows the state. */
.dispatch-container .date-chip-chevron.is-open {
color: #4338ca;
transform: rotate(180deg);
}
/* ── Calendar popover ────────────────────────────────────────────
Floats below the chip, right-aligned so it doesn't fly off the
right edge of the toolbar. Glassy white card with a tip pointing
up at the chip; same shadow language as the order-popup. */
.dispatch-container .date-cal-popover {
position: absolute;
top: calc(100% + 10px);
right: 0;
z-index: 1000;
width: 304px;
padding: 14px 14px 10px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 16px;
box-shadow: 0 18px 44px rgba(15, 23, 42, 0.18),
0 4px 12px rgba(15, 23, 42, 0.06);
animation: date-cal-in 0.18s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Tiny tip pointing up at the chip — three layered triangles for a
1px hairline border that aligns with the popover's border. */
.dispatch-container .date-cal-popover::before,
.dispatch-container .date-cal-popover::after {
content: '';
position: absolute;
bottom: 100%;
right: 28px;
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
}
.dispatch-container .date-cal-popover::before {
border-bottom: 8px solid rgba(15, 23, 42, 0.08);
}
.dispatch-container .date-cal-popover::after {
border-bottom: 8px solid #ffffff;
margin-bottom: -1px;
}
@keyframes date-cal-in {
from {
opacity: 0;
transform: translateY(-6px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* Month header ─────────────────────────────────────────────────── */
.dispatch-container .date-cal-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
}
.dispatch-container .date-cal-title {
flex: 1;
text-align: center;
font-size: 14px;
font-weight: 800;
color: #0f172a;
letter-spacing: 0.01em;
}
.dispatch-container .date-cal-nav {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border: 0;
border-radius: 8px;
background: rgba(248, 250, 252, 0.9);
color: #475569;
font-size: 18px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.dispatch-container .date-cal-nav:hover:not(:disabled) {
background: rgba(99, 102, 241, 0.1);
color: #4338ca;
}
.dispatch-container .date-cal-nav:disabled {
opacity: 0.35;
cursor: not-allowed;
}
/* Weekday row + day grid ───────────────────────────────────────── */
.dispatch-container .date-cal-weekdays,
.dispatch-container .date-cal-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
}
.dispatch-container .date-cal-weekdays {
margin-bottom: 4px;
}
.dispatch-container .date-cal-weekday {
text-align: center;
font-size: 10px;
font-weight: 800;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 4px 0;
}
.dispatch-container .date-cal-day {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
height: 36px;
border: 0;
border-radius: 9px;
background: transparent;
color: #0f172a;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease,
box-shadow 0.18s ease, transform 0.12s ease;
}
.dispatch-container .date-cal-day:hover:not(:disabled):not(.is-selected) {
background: rgba(99, 102, 241, 0.1);
color: #4338ca;
}
/* "Other-month" days fade so the visible month reads as the active
region but the operator still gets visual continuity at week edges. */
.dispatch-container .date-cal-day.is-other-month {
color: #cbd5e1;
font-weight: 500;
}
/* Today gets a subtle indigo ring so it's always findable, even when
the operator has navigated months away from it. */
.dispatch-container .date-cal-day.is-today {
box-shadow: inset 0 0 0 1.5px rgba(99, 102, 241, 0.55);
color: #4338ca;
font-weight: 800;
}
/* Selected = the date currently in selectedDate. Filled indigo
gradient (same as the Today pill / Compare button family). */
.dispatch-container .date-cal-day.is-selected {
background: linear-gradient(135deg, #6366f1, #3b82f6);
color: #ffffff;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);
font-weight: 800;
}
.dispatch-container .date-cal-day.is-selected.is-today {
/* Selected + today: drop the inset ring so the fill reads cleanly. */
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);
}
.dispatch-container .date-cal-day.is-selected:hover {
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.45);
}
.dispatch-container .date-cal-day.is-disabled {
color: #e2e8f0;
cursor: not-allowed;
pointer-events: none;
}
.dispatch-container .date-cal-day:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.5);
outline-offset: 1px;
}
/* Quick presets ────────────────────────────────────────────────── */
.dispatch-container .date-cal-presets {
display: flex;
gap: 6px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(15, 23, 42, 0.06);
}
.dispatch-container .date-cal-preset {
appearance: none;
flex: 1;
padding: 7px 10px;
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 9px;
background: #ffffff;
color: #475569;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.02em;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease,
color 0.15s ease, transform 0.12s ease;
}
.dispatch-container .date-cal-preset:hover {
background: rgba(99, 102, 241, 0.08);
border-color: rgba(99, 102, 241, 0.35);
color: #4338ca;
transform: translateY(-1px);
}
.dispatch-container .date-cal-preset:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.5);
outline-offset: 1px;
}
/* Prev / next day arrows — flank the main card. Same look as the
chevron buttons used elsewhere in the toolbar. */
.dispatch-container .date-chip-nav {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
border: 0;
border-left: 1px solid rgba(15, 23, 42, 0.06);
border-right: 1px solid rgba(15, 23, 42, 0.06);
background: rgba(248, 250, 252, 0.7);
color: #64748b;
font-size: 18px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.dispatch-container .date-chip-nav:first-child {
border-left: 0;
border-right: 1px solid rgba(15, 23, 42, 0.06);
/* Mirrors the chip's border-radius - 1px so the nav button rounds with
the outer card now that the chip itself isn't clipping with
overflow:hidden (popover anchors to it and can't be clipped). */
border-radius: 11px 0 0 11px;
}
.dispatch-container .date-chip-nav:last-child {
border-right: 0;
border-left: 1px solid rgba(15, 23, 42, 0.06);
border-radius: 0 11px 11px 0;
}
.dispatch-container .date-chip-nav:hover:not(:disabled) {
background: rgba(99, 102, 241, 0.08);
color: #4338ca;
}
.dispatch-container .date-chip-nav:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* Focus ring on the prev/next arrows for keyboard users — the
focus-within on the parent already handles the main card. */
.dispatch-container .date-chip-nav:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.5);
outline-offset: -2px;
}
/* ── Batch selector (live /dispatch only) ─────────────────────── */
.dispatch-container #batch-row {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 24px;
background: var(--bg-sub);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
/* Prevent the row itself from squishing when the chip list overflows. */
min-width: 0;
}
/* Horizontal scroller for the slot chips. Keeps the "Slot" label fixed on the
left and lets the chip list scroll when it overflows the viewport. */
.dispatch-container .batch-scroll {
display: flex;
align-items: center;
gap: 10px;
overflow-x: auto;
overflow-y: hidden;
flex: 1;
min-width: 0;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
scrollbar-color: rgba(100, 116, 139, 0.4) transparent;
padding-bottom: 2px;
}
.dispatch-container .batch-scroll::-webkit-scrollbar {
height: 6px;
}
.dispatch-container .batch-scroll::-webkit-scrollbar-track {
background: transparent;
}
.dispatch-container .batch-scroll::-webkit-scrollbar-thumb {
background: rgba(100, 116, 139, 0.3);
border-radius: 999px;
}
.dispatch-container .batch-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(100, 116, 139, 0.55);
}
.dispatch-container .batch-label {
font-size: 11px;
font-weight: 800;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-right: 4px;
flex-shrink: 0;
}
/* Slot-time-field dropdown — picks which timestamp column drives slot
bucketing. Styled to match the location-pill dropdown in the header so
both feel like the same kind of filter control. */
.dispatch-container .time-field-wrap {
position: relative;
display: inline-block;
flex-shrink: 0;
margin-right: 4px;
}
.dispatch-container .time-field-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 12px;
border-radius: 999px;
background: rgba(123, 31, 162, 0.08);
border: 1px solid rgba(123, 31, 162, 0.25);
color: #7b1fa2;
font-size: 12.5px;
font-weight: 700;
letter-spacing: 0.02em;
line-height: 1;
cursor: pointer;
font-family: inherit;
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.dispatch-container .time-field-btn:hover {
background: rgba(123, 31, 162, 0.14);
border-color: rgba(123, 31, 162, 0.45);
}
.dispatch-container .time-field-btn.open {
background: rgba(123, 31, 162, 0.18);
border-color: rgba(123, 31, 162, 0.55);
box-shadow: 0 4px 12px rgba(123, 31, 162, 0.18);
}
.dispatch-container .time-field-btn svg {
font-size: 13px;
flex-shrink: 0;
}
.dispatch-container .time-field-caret {
font-size: 15px;
transition: transform 0.2s ease;
}
.dispatch-container .time-field-btn.open .time-field-caret {
transform: rotate(180deg);
}
.dispatch-container .time-field-text {
white-space: nowrap;
}
.dispatch-container .time-field-menu {
position: absolute;
top: calc(100% + 6px);
left: 0;
min-width: 180px;
background: #fff;
border: 1px solid rgba(123, 31, 162, 0.18);
border-radius: 12px;
box-shadow: 0 16px 36px rgba(15, 23, 42, 0.16);
padding: 6px;
z-index: 1000;
animation: logo-city-menu-in 0.14s ease-out;
}
.dispatch-container .time-field-option {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
border: 0;
background: transparent;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
color: #1e293b;
cursor: pointer;
font-family: inherit;
text-align: left;
transition: background 0.12s ease;
}
.dispatch-container .time-field-option:hover {
background: rgba(123, 31, 162, 0.06);
}
.dispatch-container .time-field-option.active {
background: rgba(123, 31, 162, 0.1);
color: #7b1fa2;
}
.dispatch-container .time-field-option-icon {
font-size: 14px;
color: #7b1fa2;
flex-shrink: 0;
}
.dispatch-container .time-field-option-check {
margin-left: auto;
color: #7b1fa2;
font-weight: 800;
}
/* Slot timings editor — popover anchored to a small "Edit slots" button in
the batch row. Lets the operator tweak start/end hours, add new slots,
delete existing ones, or reset to the default 5-slot layout. */
.dispatch-container .slot-edit-wrap {
position: relative;
display: inline-block;
flex-shrink: 0;
margin-right: 4px;
}
.dispatch-container .slot-edit-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 12px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.04);
border: 1px dashed rgba(15, 23, 42, 0.18);
color: #475569;
font-size: 12.5px;
font-weight: 700;
letter-spacing: 0.02em;
line-height: 1;
cursor: pointer;
font-family: inherit;
}
.dispatch-container .slot-edit-btn:hover {
background: rgba(15, 23, 42, 0.08);
border-color: rgba(15, 23, 42, 0.32);
color: #0f172a;
}
.dispatch-container .slot-edit-btn.open {
background: rgba(123, 31, 162, 0.1);
border-color: rgba(123, 31, 162, 0.5);
border-style: solid;
color: #7b1fa2;
}
.dispatch-container .slot-edit-btn svg {
font-size: 13px;
flex-shrink: 0;
}
.dispatch-container .slot-edit-panel {
position: absolute;
top: calc(100% + 6px);
left: 0;
min-width: 340px;
background: #fff;
border: 1px solid rgba(123, 31, 162, 0.18);
border-radius: 14px;
box-shadow: 0 20px 44px rgba(15, 23, 42, 0.2);
padding: 12px;
z-index: 1000;
animation: logo-city-menu-in 0.14s ease-out;
}
.dispatch-container .slot-edit-head {
margin-bottom: 10px;
}
.dispatch-container .slot-edit-title {
font-size: 13px;
font-weight: 800;
color: #0f172a;
}
.dispatch-container .slot-edit-sub {
font-size: 11px;
color: #64748b;
margin-top: 2px;
}
.dispatch-container .slot-edit-list {
display: flex;
flex-direction: column;
gap: 8px;
max-height: 260px;
overflow-y: auto;
padding-right: 2px;
}
.dispatch-container .slot-edit-row {
display: grid;
grid-template-columns: 22px 70px 70px 1fr 28px;
align-items: center;
gap: 8px;
}
.dispatch-container .slot-edit-idx {
width: 22px;
height: 22px;
border-radius: 6px;
background: rgba(123, 31, 162, 0.12);
color: #7b1fa2;
font-size: 11px;
font-weight: 800;
display: inline-flex;
align-items: center;
justify-content: center;
}
.dispatch-container .slot-edit-field {
display: flex;
flex-direction: column;
gap: 2px;
}
.dispatch-container .slot-edit-field-label {
font-size: 9px;
font-weight: 700;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.dispatch-container .slot-edit-field input {
width: 100%;
border: 1px solid rgba(15, 23, 42, 0.16);
border-radius: 8px;
padding: 5px 8px;
font-size: 12px;
font-weight: 700;
color: #0f172a;
font-family: inherit;
background: #fff;
}
.dispatch-container .slot-edit-field input:focus {
outline: none;
border-color: #7b1fa2;
box-shadow: 0 0 0 3px rgba(123, 31, 162, 0.18);
}
.dispatch-container .slot-edit-preview {
font-size: 11px;
color: #475569;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .slot-edit-remove {
width: 26px;
height: 26px;
border-radius: 50%;
border: 1px solid rgba(220, 38, 38, 0.32);
background: rgba(220, 38, 38, 0.06);
color: #dc2626;
font-size: 16px;
font-weight: 800;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
padding: 0;
}
.dispatch-container .slot-edit-remove:hover:not(:disabled) {
background: rgba(220, 38, 38, 0.14);
border-color: rgba(220, 38, 38, 0.55);
}
.dispatch-container .slot-edit-remove:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.dispatch-container .slot-edit-actions {
display: flex;
gap: 8px;
margin-top: 12px;
padding-top: 10px;
border-top: 1px dashed rgba(15, 23, 42, 0.1);
}
.dispatch-container .slot-edit-add,
.dispatch-container .slot-edit-reset {
flex: 1;
border-radius: 8px;
padding: 7px 10px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.02em;
cursor: pointer;
font-family: inherit;
border: 1px solid transparent;
}
.dispatch-container .slot-edit-add {
background: #7b1fa2;
color: #fff;
border-color: #7b1fa2;
}
.dispatch-container .slot-edit-add:hover {
background: #6a1591;
}
.dispatch-container .slot-edit-reset {
background: #fff;
color: #475569;
border-color: rgba(15, 23, 42, 0.16);
}
.dispatch-container .slot-edit-reset:hover {
background: rgba(15, 23, 42, 0.04);
color: #0f172a;
}
.dispatch-container .batch-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--bg);
font-size: 13.5px;
font-weight: 600;
color: var(--text-muted);
cursor: pointer;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
font-family: inherit;
/* Chips must not shrink — the scroller takes the overflow instead. */
flex-shrink: 0;
white-space: nowrap;
}
.dispatch-container .batch-btn:hover {
border-color: var(--text-muted);
color: var(--text);
}
.dispatch-container .batch-btn.active {
color: #fff;
border-color: transparent;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
/* Unified active style for hourly slot chips — single accent gradient instead of
per-wave colors since 12 different colors would be visually noisy. */
.dispatch-container .batch-btn.batch-slot.active {
background: linear-gradient(135deg, #3b82f6, #6366f1);
}
.dispatch-container .batch-btn-icon {
font-size: 14px;
line-height: 1;
}
.dispatch-container .batch-btn-label {
letter-spacing: 0.01em;
}
.dispatch-container .batch-btn-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 20px;
padding: 0 7px;
border-radius: 999px;
background: var(--bg-sub);
color: var(--text);
font-size: 11px;
font-weight: 800;
font-variant-numeric: tabular-nums;
}
.dispatch-container .batch-btn.active .batch-btn-count {
background: rgba(255, 255, 255, 0.28);
color: #fff;
}
/* Status chips on step rows (kept for marker popup which still uses pill style) */
.dispatch-container .status-chip {
display: inline-flex;
align-items: center;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
padding: 3px 8px;
border-radius: 999px;
margin-left: 8px;
flex-shrink: 0;
}
/* Flag indicator inside step rows — matches the map marker flag visually */
.dispatch-container .step-flag {
display: inline-flex;
align-items: center;
gap: 5px;
margin-left: 8px;
flex-shrink: 0;
}
.dispatch-container .step-flag-svg {
width: 14px;
height: 18px;
flex-shrink: 0;
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.15));
}
.dispatch-container .step-flag-label {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
white-space: nowrap;
}
/* Marker status flag (pole + banner above the numbered marker) */
.dispatch-container .cmark {
position: relative;
width: 100%;
height: 100%;
display: block;
}
/* Pulse: a marker glows when its row is hovered in the assignment table */
.dispatch-container .cmark.pulse {
z-index: 1500 !important;
animation: cmark-pulse 0.8s ease-out infinite;
}
@keyframes cmark-pulse {
0% {
transform: scale(1);
filter: drop-shadow(0 2px 4px rgba(59, 130, 246, 0.4));
}
50% {
transform: scale(1.2);
filter: drop-shadow(0 4px 8px rgba(59, 130, 246, 0.7));
}
100% {
transform: scale(1);
filter: drop-shadow(0 2px 4px rgba(59, 130, 246, 0.4));
}
}
/* Leaflet sets overflow:hidden on its panes; the flag pokes up past the marker bounds,
so we let the divIcon container overflow visibly. */
.dispatch-container .cmark .cmark-flag {
width: 100%;
height: 100%;
display: block;
pointer-events: none;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.25));
}
/* Styling when a specific rider is focused (shows circle step sequence badges + flag) */
.dispatch-container .cmark.is-rider-focused {
border-radius: 50%;
border: 3px solid #fff;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 800;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
letter-spacing: 0.02em;
position: relative;
}
.dispatch-container .cmark.is-rider-focused .cmark-flag {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-2px);
width: 18px;
height: 22px;
pointer-events: none;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
}
.dispatch-container .cmark.is-rider-focused.pulse {
z-index: 1500 !important;
animation: cmark-focused-pulse 0.8s ease-out infinite;
}
@keyframes cmark-focused-pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.55), 0 4px 12px rgba(0, 0, 0, 0.4);
transform: scale(1);
}
70% {
box-shadow: 0 0 0 14px rgba(59, 130, 246, 0), 0 4px 12px rgba(0, 0, 0, 0.4);
transform: scale(1.18);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0), 0 4px 12px rgba(0, 0, 0, 0.4);
transform: scale(1);
}
}
/* Live rider pin (from /partners/getriderlogs/) — colored teardrop with a
floating label showing the rider's username + current order. Status drives
the color: green for active, red otherwise. */
.dispatch-container .live-rider-pin {
--pin-color: #16a34a;
position: relative;
width: 24px;
height: 41px;
}
.dispatch-container .live-rider-pin-marker {
position: absolute;
left: 0;
top: 0;
width: 24px;
height: 24px;
background: var(--pin-color);
border: 3px solid #fff;
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
.dispatch-container .live-rider-pin-marker::after {
content: '';
position: absolute;
inset: 4px;
background: #fff;
border-radius: 50%;
}
.dispatch-container .live-rider-pin-label {
position: absolute;
left: 30px;
top: 2px;
background: var(--pin-color);
color: #fff;
font-size: 11px;
font-weight: 700;
padding: 3px 8px;
border-radius: 4px;
white-space: nowrap;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
line-height: 1.2;
}
.dispatch-container .live-rider-pin-label span {
font-weight: 500;
opacity: 0.85;
margin-left: 4px;
}
/* Body layout */
.dispatch-container #body {
flex: 1;
display: flex;
min-height: 0;
overflow: hidden;
position: relative;
/* anchor for the compare-divider-badge */
}
/* Sidebar */
.dispatch-container #sidebar {
width: 400px;
flex: 0 0 400px;
background: var(--bg-sub);
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
z-index: 5;
transition: width 0.32s cubic-bezier(0.4, 0, 0.2, 1),
flex-basis 0.32s cubic-bezier(0.4, 0, 0.2, 1),
border-right-color 0.2s ease;
overflow: hidden;
}
/* Collapsed state — slide the sidebar out so the maps can use the full width.
Children stay rendered (their state is preserved) but are masked by
overflow:hidden. The peek tab below stays visible to re-open. */
.dispatch-container #body.sidebar-collapsed #sidebar {
width: 0;
flex: 0 0 0;
border-right-color: transparent;
}
/* Peek tab — vertical pill that hugs the sidebar's right edge. Tracks the
sidebar width so it sits flush against whichever side is currently visible:
at left:400px when expanded, at left:0 when collapsed. */
.dispatch-container .sidebar-toggle-tab {
position: absolute;
top: 50%;
left: 400px;
transform: translate(-50%, -50%);
width: 22px;
height: 56px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid var(--border, rgba(15, 23, 42, 0.12));
border-radius: 10px;
background: #fff;
color: var(--text, #0f172a);
font-size: 18px;
line-height: 1;
cursor: pointer;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12),
0 1px 3px rgba(15, 23, 42, 0.06);
z-index: 1200;
transition: left 0.32s cubic-bezier(0.4, 0, 0.2, 1),
background 0.18s ease,
color 0.18s ease,
transform 0.18s ease,
box-shadow 0.18s ease;
}
.dispatch-container .sidebar-toggle-tab:hover {
background: linear-gradient(135deg, #6366f1, #3b82f6);
color: #fff;
transform: translate(-50%, -50%) scale(1.06);
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.35);
}
.dispatch-container .sidebar-toggle-tab:focus-visible {
outline: 2px solid var(--accent, #3b82f6);
outline-offset: 2px;
}
.dispatch-container .sidebar-toggle-tab.is-collapsed {
left: 0;
transform: translate(0, -50%);
border-radius: 0 10px 10px 0;
border-left: none;
}
.dispatch-container .sidebar-toggle-tab.is-collapsed:hover {
transform: translate(0, -50%) scale(1.06);
}
.dispatch-container .sidebar-toggle-tab svg {
display: block;
}
/* Sidebar header — moved here from the top bar. Layered card: title row with
a scope badge, an area chip, then two stat tiles for orders + riders. */
.dispatch-container .sb-header {
position: relative;
padding: 18px 18px 16px;
background: linear-gradient(180deg, #ffffff 0%, var(--bg-sub) 100%);
border-bottom: 1px solid var(--border);
overflow: hidden;
}
.dispatch-container .sb-header::before {
content: '';
position: absolute;
inset: -40px -40px auto auto;
width: 160px;
height: 160px;
border-radius: 50%;
background: radial-gradient(circle at center, var(--accent-soft) 0%, transparent 70%);
pointer-events: none;
}
.dispatch-container .sb-header>* {
position: relative;
}
/* Top row — title on the left, active-scope badge on the right */
.dispatch-container .sb-header-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 12px;
}
.dispatch-container .sb-header-title {
display: inline-flex;
align-items: center;
gap: 8px;
}
.dispatch-container .sb-title-bar {
display: inline-block;
width: 3px;
height: 14px;
border-radius: 2px;
background: linear-gradient(180deg, var(--accent), #6366f1);
}
.dispatch-container .sb-title-text {
font-size: 12px;
font-weight: 800;
letter-spacing: 0.12em;
color: var(--text);
}
.dispatch-container .sb-header-scope {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 9px;
border-radius: 999px;
font-size: 9px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
background: var(--bg-sub);
border: 1px solid var(--border);
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .sb-scope-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--success);
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.18);
flex-shrink: 0;
}
/* Area chip — small location pill */
.dispatch-container .sb-header-area {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 14px;
padding: 4px 10px 4px 8px;
border-radius: 999px;
background: var(--accent-soft);
border: 1px solid rgba(59, 130, 246, 0.22);
color: var(--accent);
font-size: 11px;
font-weight: 700;
}
.dispatch-container .sb-area-icon {
display: inline-flex;
align-items: center;
font-size: 14px;
line-height: 1;
}
/* Stat tiles — two side-by-side cards, large numerals */
.dispatch-container .sb-header-tiles {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.dispatch-container .sb-tile {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: 12px;
background: #fff;
border: 1px solid var(--border);
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.04);
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.dispatch-container .sb-tile:hover {
transform: translateY(-1px);
box-shadow: 0 6px 14px rgba(15, 23, 42, 0.06);
}
.dispatch-container .sb-tile-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 10px;
font-size: 18px;
flex-shrink: 0;
}
.dispatch-container .sb-tile-orders .sb-tile-icon {
background: var(--accent-soft);
color: var(--accent);
}
.dispatch-container .sb-tile-riders .sb-tile-icon {
background: rgba(245, 158, 11, 0.12);
color: var(--kitchen);
}
.dispatch-container .sb-tile-body {
min-width: 0;
line-height: 1.1;
}
.dispatch-container .sb-tile-value {
font-size: 22px;
font-weight: 800;
letter-spacing: -0.01em;
color: var(--text);
font-variant-numeric: tabular-nums;
}
.dispatch-container .sb-tile-label {
margin-top: 2px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted);
}
.dispatch-container #stats-strip {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
padding: 16px;
background: var(--bg);
border-bottom: 1px solid var(--border);
}
.dispatch-container .sc {
background: var(--bg-sub);
padding: 12px;
border-radius: 12px;
border: 1px solid var(--border);
}
.dispatch-container .sc-lbl {
font-size: 10px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 4px;
}
.dispatch-container .sc-val {
font-size: 22px;
font-weight: 800;
color: var(--text);
line-height: 1;
}
.dispatch-container .sc-val.g {
color: var(--success);
}
.dispatch-container .sc-sub {
font-size: 11px;
color: var(--text-muted);
margin-top: 4px;
}
.dispatch-container #riders-panel {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.dispatch-container .ph {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 12px;
}
.dispatch-container .ph::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
/* Cards */
.dispatch-container .rcard {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 16px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--shadow);
}
.dispatch-container .rcard:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
border-color: var(--accent);
}
.dispatch-container .rcard-top {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.dispatch-container .kitchen-mark {
background: #f59e0b;
color: #fff;
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 14px;
border: 3px solid #fff;
box-shadow: 0 0 20px rgba(245, 158, 11, 0.6), 0 0 40px rgba(245, 158, 11, 0.3);
}
.dispatch-container .rcard-info {
flex: 1;
}
.dispatch-container .rcard-emo {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
border: 1.5px solid var(--border);
}
.dispatch-container .rcard-name {
font-size: 15px;
font-weight: 700;
color: var(--text);
}
.dispatch-container .rcard-zone {
font-size: 12px;
color: var(--text-muted);
margin-top: 2px;
}
.dispatch-container .rcard-badge {
font-size: 12px;
font-weight: 700;
padding: 4px 10px;
border-radius: 8px;
background: var(--bg-sub);
font-variant-numeric: tabular-nums;
font-feature-settings: 'tnum';
white-space: nowrap;
}
/* All deliveries done — flip to green so it pops vs the per-rider tint
(mirrors the old right-corner .rchip-n.is-done treatment). */
.dispatch-container .rcard-badge.is-done {
background: rgba(22, 163, 74, 0.12);
color: #16a34a;
}
.dispatch-container .bar-bg {
background: var(--bg-sub);
border-radius: 4px;
height: 5px;
overflow: hidden;
margin-bottom: 12px;
}
.dispatch-container .bar-fg {
height: 100%;
border-radius: 4px;
}
.dispatch-container .rcard-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
color: var(--text-muted);
font-weight: 500;
}
.dispatch-container .step-ids {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.dispatch-container .step-id {
font-size: 10px;
font-weight: 700;
padding: 2px 6px;
border-radius: 4px;
background: var(--bg-sub);
color: var(--text-muted);
border: 1px solid var(--border);
}
.dispatch-container .zone-order-change-rider {
flex-shrink: 0;
width: 26px;
height: 26px;
border-radius: 7px;
border: 1px solid #e2e8f0;
background: #ffffff;
color: #4f46e5;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 15px;
margin-left: 6px;
align-self: flex-start;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.dispatch-container .zone-order-change-rider:hover {
background: #eef2ff;
border-color: #c7d2fe;
color: #4338ca;
}
/* Detail View */
.dispatch-container #route-detail {
flex: 1;
overflow-y: auto;
padding: 20px;
background: var(--bg);
}
.dispatch-container .rd-back {
background: var(--bg-sub);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 16px;
border-radius: 10px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
margin-bottom: 20px;
transition: all 0.2s;
}
.dispatch-container .rd-back:hover {
background: var(--border);
}
.dispatch-container .rd-rider-name {
font-size: 28px;
font-weight: 800;
letter-spacing: -0.02em;
margin-bottom: 6px;
line-height: 1.1;
}
.dispatch-container .rd-rider-sub {
font-size: 13px;
color: var(--text-muted);
margin-bottom: 24px;
display: flex;
gap: 16px;
}
/* Focused-rider stat grid — three tiles: orders / distance / profit */
.dispatch-container .rd-stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin: 14px 0 22px;
}
.dispatch-container .rd-stat {
padding: 14px 10px 12px;
border-radius: 12px;
text-align: center;
border: 1px solid var(--border);
background: var(--bg-sub);
transition: transform 0.15s, box-shadow 0.15s;
}
.dispatch-container .rd-stat:hover {
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
.dispatch-container .rd-stat-icon {
font-size: 18px;
line-height: 1;
margin-bottom: 6px;
opacity: 0.9;
}
.dispatch-container .rd-stat-value {
font-size: 22px;
font-weight: 800;
line-height: 1;
letter-spacing: -0.02em;
color: var(--text);
font-variant-numeric: tabular-nums;
}
.dispatch-container .rd-stat-unit {
font-size: 12px;
font-weight: 700;
margin-left: 3px;
opacity: 0.7;
}
.dispatch-container .rd-stat-label {
font-size: 10px;
font-weight: 800;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-top: 6px;
}
/* Per-stat color theming */
.dispatch-container .rd-stat-orders {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.09), rgba(99, 102, 241, 0.04));
border-color: rgba(59, 130, 246, 0.22);
}
.dispatch-container .rd-stat-orders .rd-stat-value {
color: #2563eb;
}
.dispatch-container .rd-stat-distance {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.10), rgba(249, 115, 22, 0.04));
border-color: rgba(245, 158, 11, 0.25);
}
.dispatch-container .rd-stat-distance .rd-stat-value {
color: #d97706;
}
.dispatch-container .rd-stat-profit.is-gain {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.12), rgba(20, 184, 166, 0.04));
border-color: rgba(34, 197, 94, 0.35);
}
.dispatch-container .rd-stat-profit.is-gain .rd-stat-value {
color: #16a34a;
}
.dispatch-container .rd-stat-profit.is-gain .rd-stat-label {
color: #16a34a;
opacity: 0.75;
}
.dispatch-container .rd-stat-profit.is-loss {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.12), rgba(244, 63, 94, 0.04));
border-color: rgba(239, 68, 68, 0.35);
}
.dispatch-container .rd-stat-profit.is-loss .rd-stat-value {
color: #dc2626;
}
.dispatch-container .rd-stat-profit.is-loss .rd-stat-label {
color: #dc2626;
opacity: 0.75;
}
.dispatch-container .trip-block {
margin-bottom: 24px;
border: 1px solid var(--border);
border-radius: 16px;
background: var(--bg-sub);
overflow: hidden;
}
.dispatch-container .trip-header {
padding: 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
background: #fff;
}
.dispatch-container .th-badge {
padding: 4px 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 800;
color: #fff;
}
.dispatch-container .trip-stats {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
display: flex;
gap: 12px;
}
.dispatch-container .step-wrap {
padding: 16px;
}
.dispatch-container .step-row {
display: flex;
gap: 16px;
padding-bottom: 20px;
position: relative;
border-radius: 8px;
transition: background 0.15s ease, box-shadow 0.15s ease;
}
.dispatch-container .step-row.clickable {
cursor: pointer;
}
.dispatch-container .step-row.clickable:hover {
background: rgba(99, 102, 241, 0.06);
}
.dispatch-container .step-row.clickable:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.dispatch-container .step-row.active {
background: rgba(99, 102, 241, 0.1);
box-shadow: inset 3px 0 0 #6366f1;
padding-left: 8px;
margin-left: -8px;
}
.dispatch-container .step-row:not(:last-child)::before {
content: '';
position: absolute;
left: 15px;
top: 32px;
bottom: 0;
width: 2px;
background: var(--border);
}
.dispatch-container .step-dot {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 800;
z-index: 2;
flex-shrink: 0;
}
.dispatch-container .step-dot.kitchen {
background: var(--kitchen);
color: #fff;
}
.dispatch-container .step-dot.delivery {
background: #fff;
border: 2px solid var(--border);
color: var(--text-muted);
}
.dispatch-container .step-label {
font-size: 15px;
font-weight: 700;
}
.dispatch-container .step-label-row {
display: flex;
align-items: flex-start;
}
.dispatch-container .step-customer {
flex: 1;
min-width: 0;
word-break: break-word;
overflow-wrap: anywhere;
line-height: 1.35;
}
.dispatch-container .kitchen-tag {
color: var(--kitchen);
}
.dispatch-container .step-dest {
font-size: 13px;
color: var(--text-muted);
margin-top: 3px;
}
.dispatch-container .step-detail {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 6px;
font-size: 13px;
font-weight: 600;
}
.dispatch-container .step-profit {
color: var(--success);
}
.dispatch-container .step-profit.is-loss {
color: #dc2626;
}
/* Enriched step row metadata */
.dispatch-container .step-location {
font-size: 11px;
color: var(--text-muted);
margin-top: 3px;
font-weight: 500;
}
.dispatch-container .step-notes {
font-size: 11px;
color: var(--text-muted);
margin-top: 3px;
padding: 4px 8px;
background: rgba(245, 158, 11, 0.08);
border-left: 2px solid rgba(245, 158, 11, 0.6);
border-radius: 4px;
font-style: italic;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.dispatch-container .step-charges {
color: #0074e7;
font-weight: 600;
}
.dispatch-container .step-type {
font-size: 10px;
font-weight: 800;
padding: 2px 7px;
border-radius: 999px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.dispatch-container .step-type.type-economy {
background: rgba(34, 197, 94, 0.15);
color: #16a34a;
}
.dispatch-container .step-type.type-risky {
background: rgba(239, 68, 68, 0.15);
color: #dc2626;
}
.dispatch-container .step-type.type-express {
background: rgba(59, 130, 246, 0.15);
color: #2563eb;
}
.dispatch-container .step-type:not(.type-economy):not(.type-risky):not(.type-express) {
background: rgba(100, 116, 139, 0.15);
color: #475569;
}
/* ── Zone card (in panel list) ──────────────────────────────── */
.dispatch-container .rcard.zone-card {
padding: 14px 14px 12px;
background: linear-gradient(180deg, #ffffff 0%, #fafbff 100%);
position: relative;
overflow: hidden;
}
.dispatch-container .rcard.zone-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 3px;
height: 100%;
background: linear-gradient(180deg, #3b82f6, #6366f1);
opacity: 0.55;
transition: opacity 0.2s, width 0.2s;
}
.dispatch-container .rcard.zone-card:hover {
border-color: rgba(59, 130, 246, 0.5);
background: linear-gradient(180deg, #ffffff 0%, #f0f7ff 100%);
box-shadow: 0 8px 22px rgba(59, 130, 246, 0.12);
}
.dispatch-container .rcard.zone-card:hover::before {
opacity: 1;
width: 4px;
}
.dispatch-container .zone-card-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.dispatch-container .zone-card-emoji {
width: 36px;
height: 36px;
border-radius: 10px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.12), rgba(99, 102, 241, 0.14));
border: 1px solid rgba(59, 130, 246, 0.22);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.dispatch-container .zone-card-titles {
flex: 1;
min-width: 0;
}
.dispatch-container .zone-card-name {
font-size: 15px;
font-weight: 800;
color: var(--text);
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .zone-card-sub {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
font-weight: 500;
}
.dispatch-container .zone-card-arrow {
font-size: 18px;
font-weight: 800;
color: var(--accent);
opacity: 0.4;
transition: transform 0.2s, opacity 0.2s;
flex-shrink: 0;
}
.dispatch-container .rcard.zone-card:hover .zone-card-arrow {
opacity: 1;
transform: translateX(4px);
}
/* Progress row: status bar + delivered/total counter */
.dispatch-container .zone-progress-row {
display: flex;
align-items: center;
gap: 10px;
margin: 10px 0 4px;
}
.dispatch-container .zone-progress-row .zone-status-bar {
flex: 1;
margin: 0;
height: 6px;
}
.dispatch-container .zone-progress-label {
font-size: 10px;
font-weight: 800;
color: var(--text-muted);
white-space: nowrap;
letter-spacing: 0.02em;
font-variant-numeric: tabular-nums;
}
/* Stat pills row */
.dispatch-container .zone-stat-pills {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-top: 10px;
}
.dispatch-container .zone-stat-pill {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 9px;
border-radius: 8px;
background: var(--bg-sub);
border: 1px solid var(--border);
font-size: 11px;
font-weight: 600;
transition: all 0.15s;
}
.dispatch-container .zone-stat-icon {
font-size: 12px;
opacity: 0.85;
}
.dispatch-container .zone-stat-value {
font-weight: 800;
color: var(--text);
font-variant-numeric: tabular-nums;
}
.dispatch-container .zone-stat-label {
font-size: 10px;
color: var(--text-muted);
font-weight: 600;
}
.dispatch-container .zone-stat-pill.profit-positive {
background: rgba(34, 197, 94, 0.08);
border-color: rgba(34, 197, 94, 0.25);
}
.dispatch-container .zone-stat-pill.profit-positive .zone-stat-value {
color: var(--success);
}
.dispatch-container .zone-stat-pill.profit-negative {
background: rgba(239, 68, 68, 0.08);
border-color: rgba(239, 68, 68, 0.25);
}
.dispatch-container .zone-stat-pill.profit-negative .zone-stat-value {
color: #ef4444;
}
/* Suburb preview line at the bottom of the card */
.dispatch-container .zone-card-suburbs {
display: flex;
align-items: center;
gap: 6px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed var(--border);
font-size: 11px;
color: var(--text-muted);
line-height: 1.4;
}
.dispatch-container .zone-card-suburbs-text {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600;
}
.dispatch-container .zone-card-suburbs-more {
flex-shrink: 0;
font-size: 10px;
font-weight: 800;
color: var(--accent);
background: var(--accent-soft);
padding: 1px 7px;
border-radius: 999px;
}
/* ── Status bar (proportional segments) ─────────────────────── */
.dispatch-container .zone-status-bar {
display: flex;
height: 5px;
border-radius: 3px;
overflow: hidden;
margin-top: 10px;
background: var(--bg-sub);
border: 1px solid var(--border);
}
.dispatch-container .zone-status-bar.tall {
height: 16px;
border-radius: 4px;
}
.dispatch-container .zone-status-seg {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: filter 0.2s;
min-width: 1px;
}
.dispatch-container .zone-status-bar.tall .zone-status-seg {
font-size: 10px;
font-weight: 800;
color: #fff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.dispatch-container .zone-status-seg:hover {
filter: brightness(1.1);
}
.dispatch-container .zone-status-seg-label {
padding: 0 4px;
}
.dispatch-container .zone-status-legend {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
font-size: 11px;
color: var(--text-muted);
}
.dispatch-container .legend-item {
display: inline-flex;
align-items: center;
gap: 5px;
}
.dispatch-container .legend-item strong {
color: var(--text);
font-weight: 700;
margin-left: 2px;
}
.dispatch-container .legend-dot {
width: 9px;
height: 9px;
border-radius: 50%;
flex-shrink: 0;
}
/* ── Zone detail sections ───────────────────────────────────── */
.dispatch-container .zone-detail-section {
margin: 18px 0;
}
.dispatch-container .zone-section-label {
font-size: 10px;
font-weight: 800;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.dispatch-container .zone-section-label::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.dispatch-container .section-count {
color: var(--accent);
font-weight: 700;
}
/* ── Chips (suburbs + kitchens) ─────────────────────────────── */
.dispatch-container .zone-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.dispatch-container .zone-chip {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 11px;
font-weight: 600;
padding: 4px 4px 4px 10px;
border-radius: 999px;
background: var(--bg-sub);
border: 1px solid var(--border);
color: var(--text);
transition: all 0.15s;
}
.dispatch-container .zone-chip:hover {
border-color: var(--accent);
background: var(--accent-soft);
}
.dispatch-container .zone-chip.kitchen {
background: rgba(245, 158, 11, 0.06);
border-color: rgba(245, 158, 11, 0.25);
}
.dispatch-container .zone-chip.kitchen:hover {
background: rgba(245, 158, 11, 0.12);
border-color: rgba(245, 158, 11, 0.5);
}
.dispatch-container .zone-chip-name {
white-space: nowrap;
}
.dispatch-container .zone-chip-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 18px;
padding: 0 6px;
border-radius: 999px;
background: var(--accent);
color: #fff;
font-size: 10px;
font-weight: 800;
}
.dispatch-container .zone-chip-count.kitchen {
background: var(--kitchen);
}
/* Clickable area chip (button variant) — same look as the chip but with cursor +
stronger active state for the currently-expanded suburb. */
.dispatch-container .zone-chip.zone-chip-clickable {
cursor: pointer;
font-family: inherit;
}
.dispatch-container .zone-chip.zone-chip-clickable.active {
background: var(--accent);
border-color: var(--accent);
color: #fff;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.25);
}
.dispatch-container .zone-chip.zone-chip-clickable.active .zone-chip-count {
background: rgba(255, 255, 255, 0.25);
color: #fff;
}
/* Inline drill-down panel for a selected suburb */
.dispatch-container .zone-suburb-panel {
margin-top: 12px;
border: 1px solid var(--border);
border-radius: 12px;
background: #fff;
overflow: hidden;
animation: zone-suburb-panel-in 0.18s ease-out;
}
@keyframes zone-suburb-panel-in {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dispatch-container .zone-suburb-panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 14px;
background: var(--accent-soft);
border-bottom: 1px solid var(--border);
}
.dispatch-container .zone-suburb-panel-title {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 800;
color: var(--text);
letter-spacing: -0.01em;
}
.dispatch-container .zone-suburb-panel-count {
margin-left: 6px;
padding: 2px 8px;
border-radius: 999px;
background: var(--accent);
color: #fff;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.04em;
}
.dispatch-container .zone-suburb-panel-close {
width: 24px;
height: 24px;
border-radius: 50%;
border: none;
background: rgba(15, 23, 42, 0.06);
color: var(--text-muted);
font-size: 18px;
font-weight: 700;
line-height: 1;
cursor: pointer;
transition: all 0.15s;
}
.dispatch-container .zone-suburb-panel-close:hover {
background: rgba(239, 68, 68, 0.15);
color: #dc2626;
}
.dispatch-container .zone-suburb-panel-empty {
padding: 16px;
font-size: 12px;
color: var(--text-muted);
text-align: center;
}
/* ---------- Per-order cards inside a focused zone ---------- */
/* Each order is a self-contained card so the list reads as a deck of delivery
tickets rather than a wall of text. All accents use the console's base
purple (#7b1fa2) so the page sits inside the existing design language. */
.dispatch-container .zone-order-grid {
display: flex;
flex-direction: column;
gap: 10px;
padding: 4px 2px 12px;
}
.dispatch-container .zone-order-card {
position: relative;
background: #ffffff;
border: 1px solid rgba(123, 31, 162, 0.14);
border-radius: 12px;
padding: 12px 14px 12px 18px;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
overflow: hidden;
}
.dispatch-container .zone-order-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: #7b1fa2;
opacity: 0.85;
transition: width 0.18s ease, opacity 0.18s ease;
}
.dispatch-container .zone-order-card.clickable {
cursor: pointer;
}
.dispatch-container .zone-order-card.clickable:hover {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(123, 31, 162, 0.16);
border-color: rgba(123, 31, 162, 0.45);
}
.dispatch-container .zone-order-card.clickable:hover::before {
width: 5px;
opacity: 1;
}
.dispatch-container .zone-order-card.active {
border-color: #7b1fa2;
box-shadow: 0 10px 24px rgba(123, 31, 162, 0.22);
background: linear-gradient(180deg, #ffffff 0%, #f7eaff 100%);
}
.dispatch-container .zone-order-card.active::before {
width: 6px;
opacity: 1;
}
/* `going-on` = this is the rider's currently-active stop. Overrides the
purple accent with a green rail + soft green wash so the operator can
pick the in-progress card out of the trip at a glance. */
.dispatch-container .zone-order-card.going-on {
background: linear-gradient(180deg, #ffffff 0%, #ecfdf5 100%);
border-color: rgba(34, 197, 94, 0.4);
box-shadow: 0 6px 18px rgba(34, 197, 94, 0.15);
}
.dispatch-container .zone-order-card.going-on::before {
background: #16a34a;
opacity: 1;
}
/* When the in-progress card is the focused stop, keep the green priority
signal but tint a hair stronger so the click state is still felt. */
.dispatch-container .zone-order-card.going-on.active {
background: linear-gradient(180deg, #ffffff 0%, #d1fae5 100%);
border-color: #16a34a;
}
/* Header: number badge + order id/rider + status pill */
.dispatch-container .zone-order-card-head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.dispatch-container .zone-order-num {
width: 28px;
height: 28px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 800;
font-size: 13px;
flex-shrink: 0;
background: #7b1fa2;
border: 1px solid #7b1fa2;
box-shadow: 0 2px 6px rgba(123, 31, 162, 0.22);
}
.dispatch-container .zone-order-id-block {
flex: 1;
min-width: 0;
}
.dispatch-container .zone-order-id {
font-size: 12px;
font-weight: 700;
color: var(--text);
letter-spacing: -0.01em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Icon flows inline with the rider name — keeps them visually glued
together instead of split across a flex gap. */
.dispatch-container .zone-order-rider {
font-size: 11px;
font-weight: 600;
color: #7b1fa2;
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .zone-order-status {
font-size: 10px;
font-weight: 700;
padding: 3px 8px;
border-radius: 999px;
text-transform: uppercase;
letter-spacing: 0.04em;
flex-shrink: 0;
}
/* Right-side header cluster: status pill on top, delivery time below it
so the operator reads the outcome and the wall-clock together. */
.dispatch-container .zone-order-status-stack {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 3px;
flex-shrink: 0;
}
.dispatch-container .zone-order-time {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 700;
color: #1e293b;
font-variant-numeric: tabular-nums;
letter-spacing: 0.01em;
white-space: nowrap;
}
.dispatch-container .zone-order-time svg {
font-size: 14px;
color: #7b1fa2;
flex-shrink: 0;
}
/* Expected (not yet delivered) — muted color + dashed icon tint so the
operator can tell at a glance which orders are still in flight. */
.dispatch-container .zone-order-time.is-expected {
color: #64748b;
font-weight: 600;
}
.dispatch-container .zone-order-time.is-expected svg {
color: #94a3b8;
}
/* Estimated distance to drop, shown under the delivery time only for
in-flight orders. Compact icon + value, muted to defer to the status
pill and ETA above it. */
.dispatch-container .zone-order-est-drop {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
font-weight: 600;
color: #475569;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.dispatch-container .zone-order-est-drop svg {
font-size: 13px;
color: #0ea5e9;
flex-shrink: 0;
}
/* Customer line — visually the most important text in the card. Inline
icon flows with text so there's no awkward gap on short names. */
.dispatch-container .zone-order-customer {
font-size: 14px;
font-weight: 700;
color: var(--text);
letter-spacing: -0.01em;
margin-bottom: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Generic icon + text row for kitchen / address / notes. Uses -webkit-box
so the icon stays inline at the very start of the text and any wrap goes
under it (not back to the left edge), keeping the visual block tight. */
.dispatch-container .zone-order-line {
font-size: 12px;
color: var(--text-muted);
line-height: 1.4;
margin-top: 3px;
/* Force a single-line, ellipsised row — long unstructured addresses used to
wrap to 2-3 lines and made cards look noisy. Full address still surfaces
on hover via the `title` attribute. */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .zone-order-notes {
font-style: italic;
color: #475569;
/* Notes can be longer; let them breathe over 2 lines and override the
single-line ellipsis applied to .zone-order-line above. */
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: initial;
}
/* Footer stat chips */
.dispatch-container .zone-order-stats {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed rgba(123, 31, 162, 0.14);
}
.dispatch-container .zone-order-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background: rgba(123, 31, 162, 0.06);
border: 1px solid rgba(123, 31, 162, 0.14);
border-radius: 999px;
font-size: 11px;
font-weight: 600;
color: var(--text);
}
.dispatch-container .zone-order-chip.is-profit {
background: rgba(34, 197, 94, 0.1);
border-color: rgba(34, 197, 94, 0.25);
color: #16a34a;
}
.dispatch-container .zone-order-chip.is-loss {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.25);
color: #dc2626;
}
.dispatch-container .zone-order-chip.zone-order-trip {
background: rgba(123, 31, 162, 0.1);
border-color: rgba(123, 31, 162, 0.25);
color: #7b1fa2;
}
.dispatch-container .zone-order-type {
text-transform: uppercase;
letter-spacing: 0.05em;
}
.dispatch-container .kitchen-transition {
padding: 12px;
background: var(--kitchen-soft);
border: 1px dashed var(--kitchen);
border-radius: 10px;
margin: 8px 0 20px 40px;
font-size: 12px;
font-weight: 600;
}
/* When the kitchen switch marker sits between two zone-order-cards, drop the
step-row indent and let it span the full card width. */
.dispatch-container .zone-order-grid .kitchen-transition {
margin: 2px 0;
padding: 8px 12px;
}
/* Map */
.dispatch-container #map-wrap {
flex: 1;
position: relative;
transition: flex 0.32s cubic-bezier(0.4, 0, 0.2, 1);
}
/* When Compare mode is on, the dispatch map shrinks to half the body so the
actual-tracks pane (#compare-map-wrap) can sit beside it. A real gutter
between the two maps (margin-right) replaces the old hairline border so
the two halves read as clearly separate panels. */
.dispatch-container #map-wrap.compare-split {
flex: 1 1 calc(50% - 8px);
min-width: 0;
margin-right: 16px;
border-right: 0;
border-radius: 0 14px 14px 0;
box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.06);
overflow: hidden;
}
/* "Planned Route" badge floating on the left map when compare is open */
.dispatch-container .compare-planned-label {
position: absolute;
top: 12px;
left: 12px;
z-index: 1000;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px 5px 9px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
border: 1px solid rgba(99, 102, 241, 0.3);
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.1);
font-size: 10px;
font-weight: 800;
color: #4338ca;
letter-spacing: 0.06em;
text-transform: uppercase;
pointer-events: none;
animation: compare-label-in 0.22s ease-out;
}
.dispatch-container .compare-planned-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: linear-gradient(135deg, #6366f1, #4338ca);
flex-shrink: 0;
}
/* "Actual GPS" badge — mirror of the planned label, lives on the actual
map (top half in compare mode). Same shape and animation, but tinted
cyan/sky to read as "live data" rather than "plan". */
.dispatch-container .compare-actual-label {
position: absolute;
top: 12px;
left: 12px;
z-index: 1000;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px 5px 9px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
border: 1px solid rgba(14, 165, 233, 0.35);
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.1);
font-size: 10px;
font-weight: 800;
color: #0369a1;
letter-spacing: 0.06em;
text-transform: uppercase;
pointer-events: none;
animation: compare-label-in 0.22s ease-out;
}
.dispatch-container .compare-actual-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: linear-gradient(135deg, #0ea5e9, #0369a1);
flex-shrink: 0;
box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.15);
animation: compare-actual-pulse 1.6s ease-in-out infinite;
}
@keyframes compare-actual-pulse {
0%,
100% {
box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.15);
}
50% {
box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.05);
}
}
@keyframes compare-label-in {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Right half of Compare mode */
.dispatch-container #compare-map-wrap {
flex: 1 1 calc(50% - 8px);
min-width: 0;
position: relative;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 14px 0 0 14px;
box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.06);
overflow: hidden;
animation: compare-slide-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes compare-slide-in {
from {
opacity: 0;
transform: translateX(18px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.dispatch-container #compare-map-wrap .leaflet-container {
flex: 1;
min-height: 0;
background: #f0f4f8 !important;
}
/* Wraps the right MapContainer so a position:relative anchor exists for the
bottom-right Animate Routes overlay (mirrors #ov-br on the main map). The
delta panel sits outside this wrapper so the overlay can't accidentally
land on top of the stats cards. */
.dispatch-container .compare-map-area {
flex: 1;
min-height: 0;
position: relative;
display: flex;
flex-direction: column;
}
.dispatch-container .compare-ov-br {
position: absolute;
right: 12px;
bottom: 12px;
z-index: 1000;
display: flex;
gap: 8px;
}
/* Compare header */
.dispatch-container .compare-header {
padding: 10px 14px 8px;
border-bottom: 1px solid var(--border, rgba(15, 23, 42, 0.08));
background: linear-gradient(180deg, #fff 0%, #f8fafc 100%);
flex-shrink: 0;
}
.dispatch-container .compare-header-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 7px;
}
.dispatch-container .compare-title {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12px;
font-weight: 800;
color: #0f172a;
letter-spacing: 0.01em;
min-width: 0;
}
.dispatch-container .compare-title-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
}
.dispatch-container .compare-title-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .compare-title-badge {
display: inline-flex;
align-items: center;
padding: 3px 9px;
border-radius: 999px;
background: rgba(14, 165, 233, 0.1);
border: 1px solid rgba(14, 165, 233, 0.25);
color: #0284c7;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.07em;
text-transform: uppercase;
flex-shrink: 0;
}
/* Track-load progress bar */
.dispatch-container .compare-progress {
display: flex;
align-items: center;
gap: 8px;
}
.dispatch-container .compare-progress-bar-wrap {
flex: 1;
height: 3px;
background: rgba(15, 23, 42, 0.07);
border-radius: 999px;
overflow: hidden;
}
.dispatch-container .compare-progress-bar-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, #0ea5e9, #6366f1);
transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
.dispatch-container .compare-progress-bar-fill.is-done {
background: linear-gradient(90deg, #22c55e, #16a34a);
}
.dispatch-container .compare-progress-text {
font-size: 12px;
font-weight: 700;
color: #94a3b8;
white-space: nowrap;
font-variant-numeric: tabular-nums;
flex-shrink: 0;
}
/* Per-delivery track legend — sits between map and stats card */
.dispatch-container .compare-track-legend {
flex-shrink: 0;
max-height: 150px;
overflow-y: auto;
border-bottom: 1px solid var(--border, rgba(15, 23, 42, 0.07));
scrollbar-width: thin;
scrollbar-color: rgba(100, 116, 139, 0.25) transparent;
}
.dispatch-container .compare-track-legend::-webkit-scrollbar {
width: 4px;
}
.dispatch-container .compare-track-legend::-webkit-scrollbar-thumb {
background: rgba(100, 116, 139, 0.25);
border-radius: 999px;
}
.dispatch-container .compare-track-item {
display: flex;
align-items: center;
gap: 9px;
padding: 5px 14px;
border-bottom: 1px solid rgba(15, 23, 42, 0.04);
transition: background 0.12s;
}
.dispatch-container .compare-track-item:last-child {
border-bottom: 0;
}
.dispatch-container .compare-track-item:hover {
background: rgba(15, 23, 42, 0.025);
}
.dispatch-container .compare-track-num {
width: 20px;
height: 20px;
border-radius: 50%;
color: #fff;
font-size: 9px;
font-weight: 800;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
.dispatch-container .compare-track-info {
flex: 1;
min-width: 0;
}
.dispatch-container .compare-track-customer {
font-size: 11px;
font-weight: 700;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .compare-track-meta {
font-size: 10px;
color: #94a3b8;
font-weight: 600;
margin-top: 1px;
}
.dispatch-container .compare-track-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 3px;
flex-shrink: 0;
}
.dispatch-container .compare-track-status {
display: inline-flex;
align-items: center;
padding: 1px 6px;
border-radius: 999px;
font-size: 8px;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.dispatch-container .compare-track-kms {
font-size: 10px;
font-weight: 700;
color: #64748b;
}
.dispatch-container .compare-track-no-data {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 9px;
font-weight: 600;
color: #cbd5e1;
}
.dispatch-container .compare-track-spinner {
width: 9px;
height: 9px;
border-radius: 50%;
border: 1.5px solid rgba(100, 116, 139, 0.18);
border-top-color: #94a3b8;
animation: compare-spin 0.65s linear infinite;
flex-shrink: 0;
}
@keyframes compare-spin {
to {
transform: rotate(360deg);
}
}
/* Summary stats card at the bottom of the Compare pane */
.dispatch-container .compare-overall-card {
background: linear-gradient(180deg, #fff 0%, #f8fafc 100%);
border-top: 1px solid var(--border, rgba(15, 23, 42, 0.07));
padding: 10px 14px;
flex-shrink: 0;
}
.dispatch-container .compare-overall-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 9px;
}
.dispatch-container .compare-overall-dot {
width: 11px;
height: 11px;
border-radius: 50%;
flex-shrink: 0;
}
.dispatch-container .compare-overall-name {
font-size: 12px;
font-weight: 800;
color: #0f172a;
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .compare-overall-rate {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
font-size: 9px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.22);
color: #16a34a;
flex-shrink: 0;
}
.dispatch-container .compare-overall-rate.is-partial {
background: rgba(245, 158, 11, 0.1);
border-color: rgba(245, 158, 11, 0.25);
color: #b45309;
}
.dispatch-container .compare-overall-rate.is-zero {
background: rgba(100, 116, 139, 0.08);
border-color: rgba(100, 116, 139, 0.2);
color: #64748b;
}
.dispatch-container .compare-overall-stats {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 6px;
}
.dispatch-container .compare-overall-stat {
background: rgba(15, 23, 42, 0.03);
border: 1px solid rgba(15, 23, 42, 0.06);
border-radius: 10px;
padding: 7px 6px 6px;
text-align: center;
transition: background 0.15s, transform 0.15s;
}
.dispatch-container .compare-overall-stat:hover {
background: rgba(15, 23, 42, 0.055);
transform: translateY(-1px);
}
.dispatch-container .compare-overall-stat-icon {
font-size: 13px;
line-height: 1;
margin-bottom: 3px;
color: #94a3b8;
display: flex;
align-items: center;
justify-content: center;
}
.dispatch-container .compare-overall-stat-value {
font-size: 14px;
font-weight: 800;
color: #0f172a;
line-height: 1.1;
}
.dispatch-container .compare-overall-stat-unit {
font-size: 9px;
font-weight: 700;
color: #94a3b8;
margin-left: 2px;
}
.dispatch-container .compare-overall-stat-label {
font-size: 9px;
font-weight: 700;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-top: 2px;
}
.dispatch-container .compare-overall-stat.is-profit {
background: rgba(34, 197, 94, 0.06);
border-color: rgba(34, 197, 94, 0.16);
}
.dispatch-container .compare-overall-stat.is-profit .compare-overall-stat-value {
color: #16a34a;
}
.dispatch-container .compare-overall-stat.is-profit .compare-overall-stat-icon {
color: #22c55e;
}
.dispatch-container .compare-overall-stat.is-loss {
background: rgba(239, 68, 68, 0.06);
border-color: rgba(239, 68, 68, 0.16);
}
.dispatch-container .compare-overall-stat.is-loss .compare-overall-stat-value {
color: #dc2626;
}
.dispatch-container .compare-overall-stat.is-loss .compare-overall-stat-icon {
color: #ef4444;
}
/* ── Compare UI v2 ──────────────────────────────────────────────
Redesigned compare pane: rich header (title + sync toggle + step
timeline + legend), and a delta panel below the map that compares
planned-vs-actual per focused step or rolled up across the day.
─────────────────────────────────────────────────────────────────── */
.dispatch-container .compare-header-v2 {
padding: 12px 14px 10px;
border-bottom: 1px solid var(--border, rgba(15, 23, 42, 0.08));
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.dispatch-container .compare-header-row {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.dispatch-container .compare-header-row .compare-title {
flex: 1;
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: 800;
color: #0f172a;
min-width: 0;
}
.dispatch-container .compare-header-tools {
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.dispatch-container .compare-overall-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid rgba(99, 102, 241, 0.28);
background: linear-gradient(135deg, rgba(99, 102, 241, 0.08), rgba(59, 130, 246, 0.08));
color: #4338ca;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.18s ease;
}
.dispatch-container .compare-overall-btn:hover {
background: linear-gradient(135deg, #6366f1, #3b82f6);
border-color: #6366f1;
color: #fff;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.28);
transform: translateY(-1px);
}
.dispatch-container .compare-overall-btn svg {
font-size: 15px;
}
.dispatch-container .compare-sync-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid var(--border, rgba(15, 23, 42, 0.12));
background: #fff;
color: #64748b;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.18s ease;
}
.dispatch-container .compare-sync-toggle:hover {
border-color: rgba(99, 102, 241, 0.4);
color: #4338ca;
}
.dispatch-container .compare-sync-toggle.is-on {
background: linear-gradient(135deg, #22c55e, #16a34a);
border-color: #16a34a;
color: #fff;
box-shadow: 0 4px 10px rgba(34, 197, 94, 0.22);
}
.dispatch-container .compare-sync-toggle svg {
font-size: 15px;
}
/* Chevron toggle that collapses/expands the planned/actual timeline below
the compare header. Sits to the right of the Sync button. Defaults to
"open" (chevron points down); rotates 180° when collapsed. */
.dispatch-container .compare-timeline-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
border-radius: 8px;
border: 1px solid var(--border, rgba(15, 23, 42, 0.12));
background: #fff;
color: #64748b;
cursor: pointer;
transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.dispatch-container .compare-timeline-toggle:hover {
border-color: rgba(99, 102, 241, 0.4);
color: #4338ca;
background: rgba(99, 102, 241, 0.06);
}
.dispatch-container .compare-timeline-toggle svg {
font-size: 18px;
transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
.dispatch-container .compare-timeline-toggle.is-open svg {
transform: rotate(180deg);
color: #4338ca;
}
/* Step timeline — a horizontally scrollable row of step dots. Each step
is a button so it's keyboard-focusable + screen-reader friendly. */
.dispatch-container .compare-timeline-wrap {
display: flex;
flex-direction: column;
gap: 6px;
}
.dispatch-container .compare-timeline-container {
display: flex;
align-items: stretch;
background: rgba(15, 23, 42, 0.02);
border: 1px solid rgba(15, 23, 42, 0.06);
border-radius: 12px;
padding: 18px 18px;
gap: 16px;
box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.02);
}
.dispatch-container .compare-timeline-labels {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 14px;
border-right: 1.5px dashed rgba(15, 23, 42, 0.08);
flex-shrink: 0;
gap: 16px;
}
.dispatch-container .compare-timeline-label {
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
display: flex;
align-items: center;
height: 32px;
/* aligns with circle height */
}
.dispatch-container .compare-timeline-scrollable {
flex: 1;
overflow-x: auto;
display: flex;
flex-direction: column;
gap: 16px;
scrollbar-width: thin;
scrollbar-color: rgba(100, 116, 139, 0.2) transparent;
padding-bottom: 2px;
}
.dispatch-container .compare-timeline-scrollable::-webkit-scrollbar {
height: 4px;
}
.dispatch-container .compare-timeline-scrollable::-webkit-scrollbar-thumb {
background: rgba(100, 116, 139, 0.25);
border-radius: 999px;
}
.dispatch-container .compare-timeline-track {
display: flex;
align-items: center;
gap: 0;
position: relative;
}
/* Planned track now also carries a time tick under the circle, so it uses the
same column layout as the actual row (circle stacked above the tick). */
.dispatch-container .compare-timeline-track.is-planned .compare-step-spacer {
margin-bottom: 22px;
/* Centers spacer dynamically relative to the 32px circle (matches actual). */
}
/* Actual track overrides for the spacer alignment */
.dispatch-container .compare-timeline-track.is-actual .compare-step-spacer {
margin-bottom: 22px;
/* Centers spacer dynamically relative to the 32px circle */
}
.dispatch-container .compare-step-spacer {
width: 16px;
height: 2px;
background: linear-gradient(90deg, rgba(148, 163, 184, 0), rgba(148, 163, 184, 0.55) 30%, rgba(148, 163, 184, 0.55) 70%, rgba(148, 163, 184, 0));
flex-shrink: 0;
}
.dispatch-container .compare-step {
display: inline-flex;
flex-direction: column;
align-items: center;
/* gap was 3px — when the focused step's outer ring rendered, it covered
the time label below the circle. 11px gives the ring (~4-5px outside
the scaled circle) breathing room with a couple of pixels to spare. */
gap: 11px;
padding: 2px 2px 0;
background: transparent;
border: 0;
cursor: pointer;
flex-shrink: 0;
position: relative;
transition: transform 0.18s ease;
}
.dispatch-container .compare-step:hover {
transform: translateY(-1px);
}
.dispatch-container .compare-step-circle {
width: 32px;
height: 32px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--step-color, #94a3b8);
color: #fff;
font-size: 13px;
font-weight: 800;
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.14), 0 0 0 1px rgba(255, 255, 255, 0.6);
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
.dispatch-container .compare-step:hover .compare-step-circle {
transform: scale(1.08);
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.6);
}
/* Focused step — bumps scale + adds a tight glow ring tinted with the step's
own color so it visually matches the polyline / drop pin on the map. The
glow is intentionally contained (low blur + low spread) so the tick label
below stays readable; a wider halo would bleed through the time text. */
.dispatch-container .compare-step.is-focused .compare-step-circle {
transform: scale(1.18);
box-shadow:
0 4px 10px rgba(15, 23, 42, 0.22),
0 0 0 2px #fff,
0 0 0 4px var(--step-color, #6366f1);
}
/* Step number stays crisp on the focused circle */
.dispatch-container .compare-step.is-focused .compare-step-tick {
color: var(--step-color, #4338ca);
font-weight: 800;
}
.dispatch-container .compare-step.is-pending .compare-step-circle {
background: #fff;
border: 2px solid var(--step-color, #cbd5e1);
color: var(--step-color, #94a3b8);
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
}
.dispatch-container .compare-step.is-skipped .compare-step-circle {
opacity: 0.42;
background: #cbd5e1;
}
.dispatch-container .compare-step.is-loading .compare-step-circle {
background: #fff;
border: 2px solid rgba(99, 102, 241, 0.45);
color: transparent;
}
.dispatch-container .compare-step.is-no-data .compare-step-circle {
background: repeating-linear-gradient(45deg,
#e2e8f0 0 4px,
#f1f5f9 4px 8px);
color: #94a3b8;
}
.dispatch-container .compare-step-spin {
width: 14px;
height: 14px;
border-radius: 50%;
border: 2px solid rgba(99, 102, 241, 0.18);
border-top-color: #6366f1;
animation: compare-step-spin 0.7s linear infinite;
}
@keyframes compare-step-spin {
to {
transform: rotate(360deg);
}
}
.dispatch-container .compare-step-tick {
font-size: 11px;
font-weight: 700;
color: #64748b;
font-variant-numeric: tabular-nums;
letter-spacing: -0.01em;
line-height: 1;
}
/* (focused-tick styling consolidated above with the step color) */
.dispatch-container .compare-step-flag {
position: absolute;
top: -2px;
right: -2px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #dc2626;
border: 1.5px solid #fff;
box-shadow: 0 2px 4px rgba(220, 38, 38, 0.45);
}
/* Progress strip — sits under the timeline, reuses the existing progress
bar children styles. Same role as the old compare-progress block. */
.dispatch-container .compare-progress-strip {
display: flex;
align-items: center;
gap: 8px;
}
/* Legend strip — horizontal row of swatches identifying line styles. */
.dispatch-container .compare-legend {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
padding-top: 2px;
border-top: 1px dashed rgba(15, 23, 42, 0.06);
}
.dispatch-container .compare-legend-item {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12px;
font-weight: 700;
color: #64748b;
letter-spacing: 0.02em;
}
.dispatch-container .compare-legend-swatch {
width: 22px;
height: 4px;
border-radius: 2px;
flex-shrink: 0;
}
.dispatch-container .compare-legend-swatch.is-planned {
background: repeating-linear-gradient(90deg,
#6366f1 0 5px,
transparent 5px 9px);
height: 3px;
}
.dispatch-container .compare-legend-swatch.is-actual {
background: linear-gradient(90deg, currentColor, currentColor);
height: 4px;
}
/* Solid step-color swatch used by both Planned and Actual legend entries
on the unified compare map — they share the same per-step palette and
are distinguished by stroke style instead. */
.dispatch-container .compare-legend-swatch.is-step-color {
height: 4px;
border-radius: 2px;
}
/* Dashed variant — mirrors the planned polyline's dashed stroke in
Combined view. Masks the gradient swatch with a tiled transparent
gap so the eye reads "dashed line" without losing the step-color
gradient underneath. */
.dispatch-container .compare-legend-swatch.is-step-color.is-dashed {
-webkit-mask-image: repeating-linear-gradient(90deg,
#000 0 5px,
transparent 5px 9px);
mask-image: repeating-linear-gradient(90deg,
#000 0 5px,
transparent 5px 9px);
}
/* Small status note in the legend strip — replaces the now-removed
"Transit (no GPS)" item with a one-liner telling the operator how the
actual-GPS polyline gets built (Kalman smooth + OSRM road-snap). */
.dispatch-container .compare-legend-note {
margin-left: auto;
font-size: 11px;
font-weight: 700;
color: #94a3b8;
letter-spacing: 0.04em;
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .compare-legend-swatch.is-transit {
background: repeating-linear-gradient(90deg,
#94a3b8 0 3px,
transparent 3px 6px);
height: 2px;
}
/* Delta panel — sits below the actual-GPS map. Per-step view when a step
is focused, day-summary view otherwise. */
.dispatch-container .compare-delta {
padding: 12px 14px 14px;
border-top: 1px solid var(--border, rgba(15, 23, 42, 0.07));
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 10px;
animation: compare-delta-in 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
.dispatch-container .compare-delta.is-anomaly {
background: linear-gradient(180deg, #fff 0%, #fef2f2 100%);
border-top-color: rgba(220, 38, 38, 0.25);
}
@keyframes compare-delta-in {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dispatch-container .compare-delta-title {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.dispatch-container .compare-delta-step-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background: #6366f1;
color: #fff;
font-size: 14px;
font-weight: 800;
flex-shrink: 0;
box-shadow: 0 3px 10px rgba(15, 23, 42, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.5);
}
.dispatch-container .compare-delta-step-badge svg {
font-size: 17px;
}
.dispatch-container .compare-delta-title-text {
flex: 1;
min-width: 0;
}
.dispatch-container .compare-delta-title-main {
font-size: 16px;
font-weight: 800;
color: #0f172a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.25;
}
.dispatch-container .compare-delta-title-sub {
font-size: 13px;
font-weight: 600;
color: #94a3b8;
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .compare-delta-status {
display: inline-flex;
align-items: center;
padding: 4px 11px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.07em;
text-transform: uppercase;
flex-shrink: 0;
}
.dispatch-container .compare-delta-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.dispatch-container .compare-delta-cell {
display: flex;
flex-direction: column;
gap: 4px;
padding: 11px 13px 10px;
border-radius: 12px;
background: #fff;
border: 1px solid var(--border, rgba(15, 23, 42, 0.08));
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.dispatch-container .compare-delta-cell:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06);
}
.dispatch-container .compare-delta-cell.is-anomaly {
border-color: rgba(220, 38, 38, 0.42);
background: linear-gradient(180deg, #fff, #fef2f2);
box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.18) inset;
}
.dispatch-container .compare-delta-cell-label {
font-size: 11px;
font-weight: 800;
color: #94a3b8;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.dispatch-container .compare-delta-cell-val {
font-size: 22px;
font-weight: 800;
color: #0f172a;
font-variant-numeric: tabular-nums;
line-height: 1.15;
}
.dispatch-container .compare-delta-cell-val.is-over {
color: #dc2626;
}
.dispatch-container .compare-delta-cell-val.is-under {
color: #16a34a;
}
.dispatch-container .compare-delta-cell-unit {
font-size: 13px;
font-weight: 700;
color: #94a3b8;
margin-left: 2px;
}
.dispatch-container .compare-delta-cell-sub {
font-size: 12px;
font-weight: 600;
color: #64748b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Numbered drop pin on the Compare map — one per delivery, planted at the
delivery's last GPS ping (the drop). Carries the step number and, when
delivered, a green check overlay. The .is-focused variant pulses + scales
so the operator can spot the currently scrutinized step at a glance. */
.dispatch-container .compare-step-pin {
position: relative;
width: 34px;
height: 34px;
border-radius: 50%;
background: var(--pin-color, #475569);
color: #fff;
border: 3px solid #fff;
font-size: 13px;
font-weight: 800;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.32),
0 0 0 1px rgba(255, 255, 255, 0.18);
cursor: pointer;
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.2s ease;
}
.dispatch-container .compare-step-pin:hover {
transform: scale(1.08);
z-index: 1200;
}
.dispatch-container .compare-step-pin-num {
position: relative;
z-index: 1;
line-height: 1;
}
.dispatch-container .compare-step-pin.is-skipped {
opacity: 0.45;
filter: grayscale(0.6);
}
.dispatch-container .compare-step-pin.is-focused {
transform: scale(1.22);
z-index: 1300;
box-shadow:
0 8px 22px rgba(15, 23, 42, 0.38),
0 0 0 3px #ffffff,
0 0 0 5px var(--pin-color, #6366f1),
0 0 18px 6px color-mix(in srgb, var(--pin-color, #6366f1) 35%, transparent);
animation: compare-pin-pulse 1.6s ease-in-out infinite;
}
.dispatch-container .compare-step-pin.is-focused:hover {
transform: scale(1.3);
}
/* Pulse halo for the focused step's drop pin. Uses a separate pseudo so the
pin itself can scale on hover without distorting the halo. */
.dispatch-container .compare-step-pin.is-focused::before {
content: '';
position: absolute;
inset: -6px;
border-radius: 50%;
border: 2px solid var(--pin-color, #6366f1);
opacity: 0.65;
animation: compare-pin-halo 1.6s ease-out infinite;
pointer-events: none;
}
@keyframes compare-pin-pulse {
0%,
100% {
box-shadow:
0 8px 22px rgba(15, 23, 42, 0.38),
0 0 0 3px #ffffff,
0 0 0 5px var(--pin-color, #6366f1),
0 0 18px 6px color-mix(in srgb, var(--pin-color, #6366f1) 35%, transparent);
}
50% {
box-shadow:
0 10px 28px rgba(15, 23, 42, 0.5),
0 0 0 3px #ffffff,
0 0 0 6px var(--pin-color, #6366f1),
0 0 28px 10px color-mix(in srgb, var(--pin-color, #6366f1) 55%, transparent);
}
}
@keyframes compare-pin-halo {
0% {
inset: -2px;
opacity: 0.7;
}
100% {
inset: -16px;
opacity: 0;
}
}
/* Anomaly ring — replaces the colored outer ring with a red one when the
step is flagged (route deviation > 25% or arrival > 15 min late). */
.dispatch-container .compare-step-pin.is-anomaly {
box-shadow:
0 4px 14px rgba(220, 38, 38, 0.35),
0 0 0 1px rgba(255, 255, 255, 0.18),
0 0 0 3px #ffffff,
0 0 0 5px #dc2626;
}
.dispatch-container .compare-step-pin.is-anomaly.is-focused {
box-shadow:
0 8px 22px rgba(220, 38, 38, 0.5),
0 0 0 3px #ffffff,
0 0 0 5px #dc2626,
0 0 22px 8px rgba(220, 38, 38, 0.45);
animation: compare-pin-pulse-anomaly 1.4s ease-in-out infinite;
}
@keyframes compare-pin-pulse-anomaly {
0%,
100% {
box-shadow:
0 8px 22px rgba(220, 38, 38, 0.5),
0 0 0 3px #ffffff,
0 0 0 5px #dc2626,
0 0 22px 8px rgba(220, 38, 38, 0.45);
}
50% {
box-shadow:
0 10px 28px rgba(220, 38, 38, 0.65),
0 0 0 3px #ffffff,
0 0 0 6px #dc2626,
0 0 32px 12px rgba(220, 38, 38, 0.55);
}
}
/* Delivered checkmark — small green badge in the lower-right corner of the
drop pin. Reads as "this drop completed" without needing the status tag
that lives in the timeline + delta panel. */
.dispatch-container .compare-step-pin-check {
position: absolute;
bottom: -3px;
right: -3px;
width: 14px;
height: 14px;
border-radius: 50%;
background: #16a34a;
border: 1.5px solid #fff;
padding: 1px;
box-shadow: 0 2px 5px rgba(15, 23, 42, 0.34);
z-index: 2;
}
/* Pickup pin — sits at the rider's day origin (where the order was picked
up). Glyph is a shopping-bag / takeout-bag in the rider's color, so the
meaning reads at a glance as "this is the pickup point". Sized larger
than the numbered drop pins (40 vs 34) so the marker reads prominently
even at deep map zooms — Leaflet's divIcons hold a fixed pixel size at
every zoom level, so a too-small pin visually shrinks against the
surrounding street/building detail as you zoom in. The bigger fixed
pixel size keeps it readable from city-level (z12) down to street-level
(z18+). Only rendered for sequenceStep === 1; subsequent steps don't
get a start marker since their origin is the previous step's drop. */
.dispatch-container .compare-start-pin {
width: 40px;
height: 40px;
border-radius: 50%;
background: #ffffff;
color: var(--pin-color, #475569);
border: 3px solid var(--pin-color, #475569);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 5px 14px rgba(15, 23, 42, 0.32),
0 0 0 1px rgba(255, 255, 255, 0.6);
transition: transform 0.18s ease, box-shadow 0.18s ease;
cursor: pointer;
}
.dispatch-container .compare-start-pin:hover {
transform: scale(1.1);
z-index: 1100;
box-shadow: 0 7px 20px rgba(15, 23, 42, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.7);
}
.dispatch-container .compare-start-pin svg {
width: 22px;
height: 22px;
display: block;
}
/* Lightweight tooltip shown on marker hover. Replaces the older heavy popup
(which clipped and forced an auto-pan). Just a teaser; persistent details
live in the delta panel below the map. */
.dispatch-container .compare-tooltip {
background: rgba(15, 23, 42, 0.95);
color: #f8fafc;
border: 0;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.06);
padding: 0;
white-space: normal;
font-family: inherit;
backdrop-filter: blur(6px);
}
/* Leaflet draws a triangular tip pointing at the marker via a CSS border on
::before. Re-tint it to match the dark tooltip body. */
.dispatch-container .compare-tooltip.leaflet-tooltip-top::before {
border-top-color: rgba(15, 23, 42, 0.95);
}
.dispatch-container .compare-tooltip.leaflet-tooltip-bottom::before {
border-bottom-color: rgba(15, 23, 42, 0.95);
}
.dispatch-container .compare-tooltip.leaflet-tooltip-left::before {
border-left-color: rgba(15, 23, 42, 0.95);
}
.dispatch-container .compare-tooltip.leaflet-tooltip-right::before {
border-right-color: rgba(15, 23, 42, 0.95);
}
.dispatch-container .cmp-tip {
padding: 9px 12px 8px;
min-width: 200px;
max-width: 260px;
}
.dispatch-container .cmp-tip-header {
display: flex;
align-items: center;
gap: 9px;
min-width: 0;
}
.dispatch-container .cmp-tip-step {
width: 24px;
height: 24px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
background: #6366f1;
color: #fff;
font-size: 11px;
font-weight: 800;
flex-shrink: 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
.dispatch-container .cmp-tip-step svg {
font-size: 14px;
}
.dispatch-container .cmp-tip-title-stack {
flex: 1;
min-width: 0;
}
.dispatch-container .cmp-tip-title {
font-size: 12px;
font-weight: 800;
color: #f8fafc;
letter-spacing: 0.01em;
line-height: 1.25;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .cmp-tip-sub {
font-size: 10px;
font-weight: 600;
color: #cbd5e1;
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .cmp-tip-tag {
display: inline-flex;
align-items: center;
padding: 2px 7px;
border-radius: 999px;
font-size: 8px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
flex-shrink: 0;
}
.dispatch-container .cmp-tip-anomaly {
margin-top: 7px;
padding: 5px 8px;
border-radius: 8px;
background: rgba(220, 38, 38, 0.16);
border: 1px solid rgba(220, 38, 38, 0.32);
color: #fecaca;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.02em;
}
.dispatch-container .cmp-tip-action {
margin-top: 7px;
padding-top: 6px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
font-size: 9px;
font-weight: 700;
color: #94a3b8;
letter-spacing: 0.08em;
text-transform: uppercase;
text-align: center;
}
/* Compare-map popup — styled card with header (step pin + title + status
tag) and a key/value table of order details. Leaflet's default popup is
a plain white tooltip; this overrides the wrapper/tip so the popup
matches the rest of the dispatch UI. */
.dispatch-container .compare-popup .leaflet-popup-content-wrapper {
padding: 0;
border-radius: 14px;
box-shadow: 0 12px 32px rgba(15, 23, 42, 0.22), 0 0 0 1px rgba(15, 23, 42, 0.06);
overflow: hidden;
background: #fff;
}
.dispatch-container .compare-popup .leaflet-popup-content {
margin: 0;
width: auto !important;
min-width: 240px;
}
.dispatch-container .compare-popup .leaflet-popup-tip {
background: #fff;
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.18);
}
.dispatch-container .compare-popup .leaflet-popup-close-button {
top: 6px;
right: 6px;
color: #94a3b8;
font-size: 18px;
font-weight: 700;
padding: 4px 6px;
}
.dispatch-container .compare-popup .leaflet-popup-close-button:hover {
color: #0f172a;
}
.dispatch-container .cmp-pop {
font-family: 'Inter', -apple-system, sans-serif;
color: #0f172a;
line-height: 1.35;
}
.dispatch-container .cmp-pop-head {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 32px 12px 14px;
background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
}
.dispatch-container .cmp-pop-pin {
width: 30px;
height: 30px;
border-radius: 50%;
color: #fff;
font-weight: 800;
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
border: 2.5px solid #fff;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
flex-shrink: 0;
}
.dispatch-container .cmp-pop-titles {
flex: 1;
min-width: 0;
}
.dispatch-container .cmp-pop-title {
font-size: 13px;
font-weight: 800;
color: #0f172a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .cmp-pop-sub {
font-size: 9.5px;
font-weight: 700;
color: #64748b;
letter-spacing: 0.05em;
text-transform: uppercase;
margin-top: 2px;
}
.dispatch-container .cmp-pop-tag {
padding: 3px 8px;
border-radius: 999px;
font-size: 9px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
background: #e0e7ff;
color: #4338ca;
flex-shrink: 0;
white-space: nowrap;
}
.dispatch-container .cmp-pop-tag-start {
background: #ecfeff;
color: #0e7490;
}
.dispatch-container .cmp-pop-rows {
padding: 8px 14px 12px;
display: flex;
flex-direction: column;
}
.dispatch-container .cmp-pop-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
font-size: 12px;
padding: 6px 0;
border-bottom: 1px dashed rgba(15, 23, 42, 0.07);
}
.dispatch-container .cmp-pop-row:last-child {
border-bottom: 0;
}
.dispatch-container .cmp-pop-k {
color: #64748b;
font-weight: 600;
white-space: nowrap;
}
.dispatch-container .cmp-pop-v {
color: #0f172a;
font-weight: 700;
font-variant-numeric: tabular-nums;
text-align: right;
}
.dispatch-container .cmp-pop-v.is-loss {
color: #dc2626;
}
.dispatch-container .cmp-pop-v.is-profit {
color: #16a34a;
}
.dispatch-container .cmp-pop-coord {
font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 10.5px;
font-weight: 600;
color: #475569;
}
.dispatch-container .leaflet-container {
background: #f1f5f9 !important;
}
/* Overlays */
.dispatch-container #ov-tl {
position: absolute;
top: 16px;
left: 16px;
z-index: 1000;
}
.dispatch-container .ov-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(8px);
border: 1px solid var(--border);
border-radius: 16px;
padding: 16px 20px;
box-shadow: var(--shadow-lg);
}
.dispatch-container .ov-stats {
display: flex;
gap: 24px;
}
.dispatch-container .osv {
font-size: 24px;
font-weight: 800;
}
.dispatch-container .osv.g {
color: var(--success);
}
.dispatch-container .osl {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
margin-top: 2px;
}
.dispatch-container #ov-tr {
position: absolute;
top: 16px;
right: 16px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 8px;
width: 200px;
}
/* Hide floating chips overlay when split-map Compare Mode is active,
since the operator is focused on one single rider and list is redundant. */
.dispatch-container #body.compare-mode #ov-tr {
display: none !important;
}
.dispatch-container #ov-br {
position: absolute;
bottom: 20px;
right: 80px;
z-index: 1000;
}
.dispatch-container .rchip {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(8px);
border: 1px solid var(--border);
border-radius: 10px;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
box-shadow: var(--shadow);
transition: all 0.2s;
}
.dispatch-container .rchip.active {
border-color: var(--accent);
background: #fff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
}
.dispatch-container .rchip-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.dispatch-container .rchip-n {
margin-left: auto;
font-weight: 800;
color: var(--accent);
font-variant-numeric: tabular-nums;
font-feature-settings: 'tnum';
white-space: nowrap;
}
/* All deliveries done — flip the count to green so it pops vs the in-progress
accent color. */
.dispatch-container .rchip-n.is-done {
color: #16a34a;
}
/* Markers - styled as clean flags natively in Dispatch.js */
.dispatch-container .kitchen-mark {
background: var(--kitchen);
border: 3px solid #fff;
border-radius: 50%;
width: 46px;
height: 46px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 900;
font-size: 18px;
box-shadow: 0 0 20px rgba(245, 158, 11, 0.8), 0 0 40px rgba(245, 158, 11, 0.4);
}
/* Focused kitchen marker — larger, brighter, with a pulsing halo so users
never lose sight of the kitchen they drilled into. */
.dispatch-container .kitchen-mark.is-focused {
width: 56px;
height: 56px;
font-size: 22px;
border-width: 4px;
animation: kitchen-mark-pulse 1.8s ease-in-out infinite;
}
@keyframes kitchen-mark-pulse {
0%,
100% {
box-shadow: 0 0 20px rgba(245, 158, 11, 0.9), 0 0 40px rgba(245, 158, 11, 0.5), 0 0 0 0 rgba(245, 158, 11, 0.55);
}
50% {
box-shadow: 0 0 30px rgba(245, 158, 11, 1), 0 0 60px rgba(245, 158, 11, 0.7), 0 0 0 18px rgba(245, 158, 11, 0);
}
}
/* Popups - Clean White Look */
.dispatch-container .leaflet-popup-content-wrapper {
background: #ffffff;
color: #1e293b;
border-radius: 12px;
padding: 0;
/* overflow: hidden; <-- This was cutting off the tip arrow */
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15);
}
.dispatch-container .leaflet-popup-tip-container {
width: 20px;
height: 10px;
left: 50%;
margin-left: -10px;
overflow: hidden;
position: absolute;
bottom: -10px;
}
.dispatch-container .leaflet-popup-tip {
width: 14px;
height: 14px;
padding: 0;
margin: -10px auto 0;
transform: rotate(45deg);
background: #ffffff;
box-shadow: none;
}
.dispatch-container .pu-id {
background: #f8fafc;
padding: 10px 14px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.08em;
border-bottom: 1px solid #e2e8f0;
color: #64748b;
border-radius: 12px 12px 0 0;
display: flex;
align-items: center;
gap: 6px;
}
.dispatch-container .pu-live-dot {
width: 6px;
height: 6px;
background: #22c55e;
border-radius: 50%;
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
animation: pu-pulse 1.2s ease-in-out infinite;
}
@keyframes pu-pulse {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.5;
}
}
.dispatch-container .pu-rider-wrap {
padding: 14px 14px 8px;
display: flex;
align-items: center;
gap: 8px;
}
.dispatch-container .pu-rider-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.15);
}
.dispatch-container .pu-rider-name {
font-size: 14.5px;
font-weight: 800;
color: #0f172a;
letter-spacing: -0.01em;
}
.dispatch-container .pu-body-content {
padding: 0 14px 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.dispatch-container .pu-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #64748b;
padding: 2px 0;
}
.dispatch-container .pu-row span:first-child {
color: #64748b;
font-weight: 600;
margin-right: 12px;
flex-shrink: 0;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.dispatch-container .pu-row span:last-child {
color: #334155;
font-weight: 600;
text-align: right;
word-break: break-word;
overflow-wrap: anywhere;
max-width: 70%;
}
/* Custom Status Tags inside the Popup */
.dispatch-container .pu-status-tag {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 6px;
font-size: 10.5px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.dispatch-container .pu-status-tag.is-idle {
background: #f1f5f9;
color: #475569;
}
.dispatch-container .pu-status-tag.is-active {
background: #e0f2fe;
color: #0284c7;
}
.dispatch-container .pu-status-tag.is-delivered {
background: #dcfce7;
color: #16a34a;
}
.dispatch-container .pu-status-tag.is-generic {
background: #f1f5f9;
color: #475569;
}
/* Custom Order ID & Phone tags */
.dispatch-container .pu-order-tag {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: #4f46e5 !important;
background: #f5f3ff;
padding: 1px 6px;
border-radius: 4px;
font-weight: 700 !important;
}
.dispatch-container .pu-phone-tag {
font-family: 'JetBrains Mono', monospace;
font-size: 11.5px;
color: #334155;
}
.dispatch-container .pu-coord-tag {
font-family: 'JetBrains Mono', monospace;
font-size: 10.5px;
color: #64748b !important;
}
/* --- Live Rider GPS Popup Custom Styling --- */
.dispatch-container .live-rider-popup .leaflet-popup-content-wrapper {
padding: 0;
border-radius: 16px;
box-shadow: 0 20px 48px rgba(15, 23, 42, 0.22);
overflow: hidden;
min-width: 260px;
max-width: 280px;
border: 1px solid rgba(255, 255, 255, 0.85);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
animation: dispatch-popup-in 0.18s cubic-bezier(0.4, 0, 0.2, 1);
}
.dispatch-container .live-rider-popup .leaflet-popup-content {
min-width: 260px;
margin: 0;
}
.dispatch-container .live-rider-popup .pu-hdr-live {
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
padding: 10px 14px;
display: flex;
align-items: center;
justify-content: space-between;
}
.dispatch-container .live-rider-popup .pu-hdr-left {
display: flex;
align-items: center;
gap: 8px;
}
.dispatch-container .live-rider-popup .pu-live-indicator {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
}
.dispatch-container .live-rider-popup .pu-live-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background-color: var(--pulse-color, #dc2626);
z-index: 2;
}
.dispatch-container .live-rider-popup .pu-live-indicator::after {
content: '';
position: absolute;
width: 15px;
height: 15px;
border-radius: 50%;
border: 2px solid var(--pulse-color, #dc2626);
animation: pu-pulse-ring 1.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
opacity: 0;
z-index: 1;
}
@keyframes pu-pulse-ring {
0% {
transform: scale(0.5);
opacity: 0.8;
}
100% {
transform: scale(1.6);
opacity: 0;
}
}
.dispatch-container .live-rider-popup .pu-hdr-title {
font-size: 11px;
font-weight: 800;
letter-spacing: 0.05em;
color: #64748b;
text-transform: uppercase;
}
.dispatch-container .live-rider-popup .pu-status-badge {
font-size: 9px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 2px 8px;
border-radius: 999px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.dispatch-container .live-rider-popup .pu-status-badge.active {
background: #e0f2fe;
color: #0369a1;
}
.dispatch-container .live-rider-popup .pu-status-badge.idle {
background: #f1f5f9;
color: #475569;
}
.dispatch-container .live-rider-popup .pu-rider-profile {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px 10px;
background: linear-gradient(180deg, rgba(248, 250, 252, 0.5) 0%, rgba(255, 255, 255, 0) 100%);
}
.dispatch-container .live-rider-popup .pu-avatar {
width: 38px;
height: 38px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.2);
}
.dispatch-container .live-rider-popup .pu-rider-info-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.dispatch-container .live-rider-popup .pu-rider-name {
font-size: 15px;
font-weight: 800;
color: #0f172a;
letter-spacing: -0.01em;
line-height: 1.2;
}
.dispatch-container .live-rider-popup .pu-rider-name-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.dispatch-container .live-rider-popup .pu-rider-meta {
font-size: 10px;
font-weight: 600;
color: #94a3b8;
letter-spacing: 0.02em;
}
.dispatch-container .live-rider-popup .pu-body-content {
padding: 4px 16px 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.dispatch-container .live-rider-popup .pu-info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
border-bottom: 1px dashed #f1f5f9;
}
.dispatch-container .live-rider-popup .pu-info-row:last-child {
border-bottom: none;
padding-bottom: 0;
}
.dispatch-container .live-rider-popup .pu-info-label {
font-size: 10.5px;
font-weight: 700;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.dispatch-container .live-rider-popup .pu-info-value {
font-size: 12px;
font-weight: 700;
color: #334155;
text-align: right;
}
.dispatch-container .live-rider-popup .pu-order-badge {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: #4f46e5;
background: #f5f3ff;
padding: 2px 8px;
border-radius: 6px;
font-weight: 800;
border: 1px solid rgba(79, 70, 229, 0.12);
}
.dispatch-container .live-rider-popup .pu-phone-link {
color: #0284c7;
text-decoration: none;
transition: color 0.15s ease;
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
}
.dispatch-container .live-rider-popup .pu-phone-link:hover {
color: #0369a1;
text-decoration: underline;
}
.dispatch-container .live-rider-popup .pu-time-stamp {
display: inline-flex;
align-items: center;
gap: 4px;
color: #475569;
}
.dispatch-container .live-rider-popup .pu-time-stamp .inline-icon {
font-size: 13px;
color: #64748b;
}
.dispatch-container .live-rider-popup .pu-coordinates {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: #64748b;
background: #f8fafc;
padding: 2px 6px;
border-radius: 4px;
border: 1px solid #e2e8f0;
}
/* Small purple section label between groups (Timeline, Details). */
.dispatch-container .pu-section-label {
margin: 10px 14px 4px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #7b1fa2;
border-bottom: 1px solid rgba(123, 31, 162, 0.18);
padding-bottom: 4px;
}
/* Compact vertical timeline of delivery stages. Each row has a dot + label
on the left and the time (HH:mm:ss) on the right, lined up in a tabular
monospaced look so the column reads cleanly at a glance. */
.dispatch-container .pu-timeline {
padding: 4px 14px 4px 16px;
display: flex;
flex-direction: column;
gap: 5px;
position: relative;
}
.dispatch-container .pu-timeline::before {
content: '';
position: absolute;
top: 8px;
bottom: 8px;
left: 19px;
width: 1px;
background: rgba(123, 31, 162, 0.18);
}
.dispatch-container .pu-tl-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
position: relative;
z-index: 1;
}
.dispatch-container .pu-tl-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #fff;
border: 2px solid rgba(123, 31, 162, 0.45);
flex-shrink: 0;
box-sizing: border-box;
}
.dispatch-container .pu-tl-row.delivered .pu-tl-dot {
background: #16a34a;
border-color: #16a34a;
box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.18);
}
.dispatch-container .pu-tl-label {
flex: 1;
color: #64748b;
font-weight: 500;
}
.dispatch-container .pu-tl-row.delivered .pu-tl-label {
color: #16a34a;
font-weight: 700;
}
.dispatch-container .pu-tl-time {
color: #1e293b;
font-weight: 700;
font-variant-numeric: tabular-nums;
font-feature-settings: 'tnum';
}
.dispatch-container .dispatch-popup .leaflet-popup-content {
margin: 0;
width: auto !important;
}
/* Order popup gets a wider, taller content wrapper than the default leaflet
popup so the timeline + 2-column detail grid breathe properly. The explicit
min-width forces the wrapper to honor the Popup component's minWidth even
when the inner content (.leaflet-popup-content has width: auto !important)
would otherwise size to text. */
.dispatch-container .dispatch-popup .leaflet-popup-content-wrapper {
padding: 0;
border-radius: 14px;
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.18);
overflow: hidden;
min-width: 580px;
animation: dispatch-popup-in 0.18s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Soften the popup tip the same way — without this, the tip pops in at
full opacity while the wrapper fades, which reads as a jarring snap. */
.dispatch-container .dispatch-popup .leaflet-popup-tip {
animation: dispatch-popup-in 0.18s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes dispatch-popup-in {
from {
opacity: 0;
transform: translateY(4px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.dispatch-container .dispatch-popup .leaflet-popup-content {
min-width: 580px;
}
/* --- Centered order popup overlay ---
Rendered as a child of .dispatch-container (NOT inside leaflet's
transformed panes), so position: fixed centers on the viewport instead
of inheriting the map's pan offset. Keeps the rich order card fully
visible on small laptop displays where the marker-attached popup would
spill above/below the map and get clipped. */
.dispatch-container .dispatch-popup-center {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1700;
pointer-events: auto;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 32px);
display: flex;
animation: dispatch-popup-in 0.18s cubic-bezier(0.4, 0, 0.2, 1);
}
/* The card itself — mirrors the chrome the old leaflet-popup-content-wrapper
provided (rounded corners, soft shadow, hidden overflow) so the inner
.pu-header / .pu-body / .pu-distance-row blocks render identically. */
.dispatch-container .dispatch-popup-center .dispatch-popup-card {
position: relative;
background: #fff;
border-radius: 14px;
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.28);
/* min() clamps the minimum width so it shrinks gracefully on narrow
viewports instead of forcing horizontal overflow. */
min-width: min(580px, calc(100vw - 32px));
max-width: 680px;
max-height: calc(100vh - 32px);
overflow-x: hidden;
overflow-y: auto;
}
/* Close button — sits in the top-right corner over the purple header. */
.dispatch-container .dispatch-popup-center-close {
position: absolute;
top: 8px;
right: 8px;
width: 26px;
height: 26px;
border: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
color: #fff;
font-size: 20px;
font-weight: 700;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
z-index: 2;
transition: background 0.15s ease;
}
.dispatch-container .dispatch-popup-center-close:hover {
background: rgba(255, 255, 255, 0.35);
}
/* Reserve room on the right of the header so the close button doesn't
overlap the status chip. Only applied when the popup is rendered in the
centered overlay (the leaflet-attached variant didn't have a close X). */
.dispatch-container .dispatch-popup-center .dispatch-popup .pu-header {
padding-right: 44px;
}
/* --- Header: purple gradient with order id + status + rider --- */
.dispatch-container .dispatch-popup .pu-header {
background: linear-gradient(135deg, #7b1fa2 0%, #9c27b0 50%, #ab47bc 100%);
padding: 14px 16px 12px;
color: #fff;
border-radius: 12px 12px 0 0;
}
.dispatch-container .dispatch-popup .pu-header-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 8px;
}
.dispatch-container .dispatch-popup .pu-id {
/* Override the legacy pu-id styling — no separate background, sits on
the purple gradient instead. */
background: transparent;
border: 0;
border-radius: 0;
padding: 0;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.06em;
color: rgba(255, 255, 255, 0.95);
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .dispatch-popup .pu-status-chip {
font-size: 10px;
font-weight: 800;
padding: 4px 10px;
border-radius: 999px;
letter-spacing: 0.06em;
text-transform: uppercase;
white-space: nowrap;
flex-shrink: 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
}
.dispatch-container .dispatch-popup .pu-rider {
padding: 0;
font-size: 16px;
font-weight: 800;
color: #fff !important;
display: flex;
align-items: center;
gap: 6px;
letter-spacing: -0.01em;
}
.dispatch-container .dispatch-popup .pu-rider svg {
font-size: 18px;
opacity: 0.9;
}
.dispatch-container .dispatch-popup .pu-rider span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .dispatch-popup .pu-delivery-id {
margin-top: 6px;
font-size: 11px;
font-weight: 600;
color: rgba(255, 255, 255, 0.85);
letter-spacing: 0.04em;
}
/* --- Body sections ---
No scroll: the popup expands to fit its content. Width is the dimension
we constrain (via leaflet's maxWidth prop) so the body grows downward as
needed for the timeline + details to render in full. */
.dispatch-container .dispatch-popup .pu-body {
padding: 4px 16px 12px;
}
.dispatch-container .dispatch-popup .pu-section {
margin-top: 8px;
}
.dispatch-container .dispatch-popup .pu-section-label {
/* Scoped override: no horizontal margin since pu-body already provides
the gutter. Sits flush with section content. */
margin: 0 0 6px;
padding-bottom: 4px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #7b1fa2;
border-bottom: 1px solid rgba(123, 31, 162, 0.18);
}
/* --- Timeline: lay events out as a 2-column grid so the 6-row vertical
stack collapses to 3 rows. Keeps the popup short enough to fit on
small-laptop map heights. The connecting line (::before) is hidden
in this layout since the rows no longer form a single column. --- */
.dispatch-container .dispatch-popup .pu-timeline {
padding: 2px 0;
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 14px;
row-gap: 4px;
position: relative;
}
.dispatch-container .dispatch-popup .pu-timeline::before {
display: none;
}
/* --- Details grid: 2 columns of icon/label/value tiles --- */
.dispatch-container .dispatch-popup .pu-details-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.dispatch-container .dispatch-popup .pu-detail {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px 10px;
background: rgba(123, 31, 162, 0.05);
border: 1px solid rgba(123, 31, 162, 0.12);
border-radius: 10px;
min-width: 0;
}
.dispatch-container .dispatch-popup .pu-detail-icon {
width: 26px;
height: 26px;
border-radius: 7px;
background: rgba(123, 31, 162, 0.12);
color: #7b1fa2;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 14px;
}
.dispatch-container .dispatch-popup .pu-detail-icon svg {
font-size: 15px;
}
.dispatch-container .dispatch-popup .pu-detail-body {
min-width: 0;
flex: 1;
}
.dispatch-container .dispatch-popup .pu-detail-label {
font-size: 9px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #64748b;
margin-bottom: 2px;
}
.dispatch-container .dispatch-popup .pu-detail-value {
font-size: 12px;
font-weight: 700;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
}
/* --- Distance chip row sits below the details grid --- */
.dispatch-container .dispatch-popup .pu-distance-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed rgba(123, 31, 162, 0.18);
}
.dispatch-container .dispatch-popup .pu-distance-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
background: linear-gradient(135deg, rgba(123, 31, 162, 0.08), rgba(156, 39, 176, 0.06));
border: 1px solid rgba(123, 31, 162, 0.2);
border-radius: 999px;
font-size: 11px;
font-weight: 600;
}
.dispatch-container .dispatch-popup .pu-distance-icon {
display: inline-flex;
color: #7b1fa2;
}
.dispatch-container .dispatch-popup .pu-distance-icon svg {
font-size: 14px;
}
.dispatch-container .dispatch-popup .pu-distance-label {
color: #64748b;
font-weight: 600;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.dispatch-container .dispatch-popup .pu-distance-value {
color: #1e293b;
font-weight: 800;
font-variant-numeric: tabular-nums;
}
/* Kitchen Popup */
.dispatch-container .kitchen-popup .kp-header {
background: #f8fafc;
color: #64748b;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
padding: 8px 14px;
border-bottom: 1px solid #e2e8f0;
border-radius: 12px 12px 0 0;
}
.dispatch-container .kitchen-popup .kp-name {
padding: 14px 14px 4px;
font-size: 16px;
font-weight: 800;
color: var(--kitchen);
}
.dispatch-container .kitchen-popup .kp-stat {
display: flex;
justify-content: space-between;
padding: 8px 14px 16px;
}
.dispatch-container .kitchen-popup .kp-stat-lbl {
font-size: 12px;
color: #64748b;
}
.dispatch-container .kitchen-popup .kp-stat-val {
font-size: 16px;
font-weight: 800;
color: #1e293b;
}
/* Empty slot state — shown in the sidebar list when no orders match the selected batch */
.dispatch-container .empty-slot {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
padding: 48px 24px;
text-align: center;
}
.dispatch-container .empty-slot-icon {
font-size: 36px;
color: var(--border);
line-height: 1;
}
.dispatch-container .empty-slot-title {
font-size: 14px;
font-weight: 700;
color: var(--text-muted);
}
.dispatch-container .empty-slot-sub {
font-size: 12px;
font-weight: 500;
color: var(--border);
max-width: 220px;
line-height: 1.5;
}
.dispatch-container #desc {
padding: 16px 20px;
font-size: 12px;
font-weight: 500;
color: var(--text-muted);
border-top: 1px solid var(--border);
background: var(--bg);
}
/* ── Responsive breakpoints ─────────────────────────────────────
Targets: laptop 1280px, compact laptop 1100px, small 960px.
The sidebar is the primary layout element to shrink — the map
takes the freed space automatically (it's flex: 1).
────────────────────────────────────────────────────────────────── */
/* Large laptop — subtle sidebar reduction */
@media (max-width: 1280px) {
.dispatch-container #sidebar {
width: 360px;
flex-basis: 360px;
}
.dispatch-container .sidebar-toggle-tab {
left: 360px;
}
.dispatch-container .sidebar-toggle-tab.is-collapsed {
left: 0;
}
}
/* Compact laptop (common 1366×768 screens) */
@media (max-width: 1180px) {
.dispatch-container #sidebar {
width: 320px;
flex-basis: 320px;
}
.dispatch-container .sidebar-toggle-tab {
left: 320px;
}
.dispatch-container .sidebar-toggle-tab.is-collapsed {
left: 0;
}
.dispatch-container .rd-rider-name {
font-size: 24px;
}
.dispatch-container .rd-stat-value {
font-size: 20px;
}
.dispatch-container .sb-tile-value {
font-size: 20px;
}
.dispatch-container #hdr {
padding: 0 16px;
}
.dispatch-container #strat-row {
padding: 0 16px;
gap: 6px;
}
.dispatch-container #batch-row {
padding: 8px 16px;
}
.dispatch-container .sbt {
padding: 7px 11px;
font-size: 12px;
gap: 6px;
}
}
/* Small laptop / 1024px */
@media (max-width: 1080px) {
.dispatch-container #sidebar {
width: 290px;
flex-basis: 290px;
}
.dispatch-container .sidebar-toggle-tab {
left: 290px;
}
.dispatch-container .sidebar-toggle-tab.is-collapsed {
left: 0;
}
/* Header — hide decorative city pill, tighten spacing */
.dispatch-container .logo-city,
.dispatch-container .logo-city-wrap {
display: none;
}
.dispatch-container .logo-name {
font-size: 16px;
}
.dispatch-container #clock {
font-size: 12px;
padding: 5px 10px;
}
.dispatch-container .hdr-stats {
gap: 6px;
margin-right: 8px;
}
.dispatch-container .strat-stat {
padding: 5px 9px;
font-size: 11px;
gap: 4px;
}
/* Hide the verbose "Profit / Loss" text label; keep icon + value */
.dispatch-container .strat-stat-label {
display: none;
}
.dispatch-container .live-status {
font-size: 11px;
padding: 5px 8px;
}
/* Hide the "/ N today" sub-text to keep status compact */
.dispatch-container .live-status-sub {
display: none;
}
/* Tabs — smaller */
.dispatch-container .sbt {
padding: 7px 10px;
font-size: 12px;
gap: 5px;
}
.dispatch-container .sbt .sbt-icon {
width: 16px;
height: 16px;
font-size: 16px;
}
/* Batch slots — smaller pills */
.dispatch-container .batch-btn {
padding: 5px 9px;
font-size: 11px;
gap: 4px;
}
.dispatch-container .batch-btn-count {
min-width: 18px;
height: 16px;
font-size: 9px;
padding: 0 4px;
}
/* Sidebar content */
.dispatch-container .sb-header {
padding: 14px 14px 12px;
}
.dispatch-container .sb-tile-value {
font-size: 18px;
}
.dispatch-container .sb-tile {
padding: 8px 10px;
gap: 8px;
}
.dispatch-container .sb-tile-icon {
width: 28px;
height: 28px;
font-size: 16px;
}
.dispatch-container .rcard {
padding: 12px;
}
.dispatch-container .rcard-name {
font-size: 13px;
}
.dispatch-container .rcard-zone {
font-size: 11px;
}
.dispatch-container .step-wrap {
padding: 12px;
}
.dispatch-container #route-detail {
padding: 16px;
}
.dispatch-container .rd-rider-name {
font-size: 20px;
}
.dispatch-container .rd-stat-value {
font-size: 17px;
}
.dispatch-container .rd-stat {
padding: 12px 8px 10px;
}
/* Map overlay chips — narrower */
.dispatch-container #ov-tr {
width: 160px;
}
.dispatch-container .rchip {
padding: 6px 8px;
font-size: 11px;
}
}
/* Very small laptop / tablet landscape — 960px */
@media (max-width: 960px) {
.dispatch-container #sidebar {
width: 250px;
flex-basis: 250px;
}
.dispatch-container .sidebar-toggle-tab {
left: 250px;
}
.dispatch-container .sidebar-toggle-tab.is-collapsed {
left: 0;
}
/* Make strat-row horizontally scrollable if buttons overflow */
.dispatch-container #strat-row {
overflow-x: auto;
overflow-y: hidden;
flex-wrap: nowrap;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.dispatch-container #strat-row::-webkit-scrollbar {
display: none;
}
/* Keep buttons from shrinking inside the scroll container */
.dispatch-container .sbt {
flex-shrink: 0;
padding: 7px 9px;
font-size: 11px;
}
/* Zone stat pills — drop the text label, keep icon + value */
.dispatch-container .zone-stat-label {
display: none;
}
.dispatch-container .zone-stat-pill {
padding: 3px 7px;
gap: 3px;
}
.dispatch-container .zone-stat-value {
font-size: 12px;
}
/* Focused-rider stat tiles */
.dispatch-container .rd-stats-grid {
gap: 6px;
}
.dispatch-container .rd-stat {
padding: 10px 6px 8px;
}
.dispatch-container .rd-stat-value {
font-size: 15px;
}
.dispatch-container .rd-stat-label {
font-size: 9px;
}
.dispatch-container .rd-stat-icon {
font-size: 15px;
}
/* Hide map overlay rider/kitchen chip list — not enough space */
.dispatch-container #ov-tr {
display: none;
}
/* Zone card adjustments */
.dispatch-container .zone-card-name {
font-size: 13px;
}
.dispatch-container .zone-card-sub {
font-size: 10px;
}
/* Trim padding in various panels */
.dispatch-container #riders-panel {
padding: 12px;
}
.dispatch-container .trip-header {
padding: 10px 12px;
}
}
/* =========================================================================
Rider Info view — full-page sidebar + detail layout. Opened from the
"Rider Info" button in the view-mode toggle. Left sidebar lists riders
with a search; right panel shows the selected rider's getriderperiodiclogs
snapshot.
========================================================================= */
.dispatch-container .rider-info-mode {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
background: var(--bg);
}
.dispatch-container .ri-sidebar {
width: 320px;
min-width: 280px;
flex-shrink: 0;
background: var(--bg-sub, #f8fafc);
border-right: 1px solid var(--border, rgba(15, 23, 42, 0.08));
display: flex;
flex-direction: column;
overflow: hidden;
}
.dispatch-container .ri-sb-head {
padding: 16px 18px 12px;
border-bottom: 1px solid var(--border, rgba(15, 23, 42, 0.08));
}
.dispatch-container .ri-sb-title {
font-size: 20px;
font-weight: 800;
color: #1e293b;
letter-spacing: -0.01em;
}
.dispatch-container .ri-sb-sub {
font-size: 13px;
font-weight: 600;
color: #64748b;
margin-top: 4px;
}
.dispatch-container .ri-main {
flex: 1;
min-width: 0;
overflow-y: auto;
padding: 24px 28px;
background: #fff;
}
.dispatch-container .ri-main::-webkit-scrollbar {
width: 6px;
}
.dispatch-container .ri-main::-webkit-scrollbar-thumb {
background: rgba(123, 31, 162, 0.25);
border-radius: 999px;
}
/* Placeholder when no rider has been selected yet. */
.dispatch-container .ri-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 60px 20px;
text-align: center;
color: #64748b;
}
.dispatch-container .ri-placeholder-icon {
width: 64px;
height: 64px;
border-radius: 16px;
background: rgba(123, 31, 162, 0.08);
color: #7b1fa2;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
margin-bottom: 16px;
}
.dispatch-container .ri-placeholder-icon svg {
font-size: 36px;
}
.dispatch-container .ri-placeholder-title {
font-size: 16px;
font-weight: 800;
color: #1e293b;
letter-spacing: -0.01em;
}
.dispatch-container .ri-placeholder-sub {
margin-top: 6px;
font-size: 12px;
font-weight: 500;
max-width: 320px;
line-height: 1.5;
}
/* Search input — used in the sidebar */
.dispatch-container .ri-search {
padding: 12px 14px 4px;
position: relative;
}
.dispatch-container .ri-search-icon {
position: absolute;
left: 26px;
top: 50%;
transform: translateY(-50%);
color: #94a3b8;
font-size: 18px;
pointer-events: none;
}
.dispatch-container .ri-search-input {
width: 100%;
padding: 10px 12px 10px 38px;
border-radius: 10px;
border: 1px solid rgba(123, 31, 162, 0.18);
background: #f8fafc;
font-size: 13px;
font-weight: 500;
font-family: inherit;
color: #1e293b;
outline: none;
transition: border-color 0.15s ease, background 0.15s ease;
}
.dispatch-container .ri-search-input:focus {
border-color: #7b1fa2;
background: #fff;
}
.dispatch-container .ri-rider-list {
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px 14px 16px;
overflow-y: auto;
flex: 1;
min-height: 0;
}
.dispatch-container .ri-rider-list::-webkit-scrollbar {
width: 6px;
}
.dispatch-container .ri-rider-list::-webkit-scrollbar-thumb {
background: rgba(123, 31, 162, 0.25);
border-radius: 999px;
}
.dispatch-container .ri-rider-item {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 12px 14px;
border: 1px solid rgba(123, 31, 162, 0.12);
border-radius: 10px;
background: #fff;
cursor: pointer;
font-family: inherit;
text-align: left;
transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.dispatch-container .ri-rider-item:hover {
border-color: rgba(123, 31, 162, 0.4);
background: rgba(123, 31, 162, 0.04);
transform: translateX(2px);
}
.dispatch-container .ri-rider-item.active {
border-color: #7b1fa2;
background: linear-gradient(180deg, #fbf3ff 0%, #f0e0fa 100%);
box-shadow: 0 4px 12px rgba(123, 31, 162, 0.18);
}
.dispatch-container .ri-rider-item.active .ri-rider-name {
color: #7b1fa2;
}
.dispatch-container .ri-rider-item.active .ri-rider-arrow {
opacity: 1;
transform: translateX(2px);
}
.dispatch-container .ri-rider-dot {
width: 14px;
height: 14px;
border-radius: 50%;
flex-shrink: 0;
box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(15, 23, 42, 0.08);
}
.dispatch-container .ri-rider-info-block {
flex: 1;
display: flex;
flex-direction: column;
gap: 3px;
min-width: 0;
}
.dispatch-container .ri-rider-name {
font-size: 15px;
font-weight: 700;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: -0.01em;
}
.dispatch-container .ri-rider-meta {
font-size: 13px;
font-weight: 600;
color: #64748b;
}
.dispatch-container .ri-rider-arrow {
color: #7b1fa2;
font-size: 18px;
font-weight: 800;
opacity: 0.4;
transition: opacity 0.15s ease, transform 0.15s ease;
}
.dispatch-container .ri-rider-item:hover .ri-rider-arrow {
opacity: 1;
transform: translateX(2px);
}
.dispatch-container .ri-empty {
padding: 32px 16px;
text-align: center;
font-size: 12px;
color: #64748b;
}
.dispatch-container .ri-loading,
.dispatch-container .ri-error {
padding: 32px 16px;
text-align: center;
font-size: 13px;
color: #64748b;
}
.dispatch-container .ri-error {
color: #dc2626;
}
.dispatch-container .ri-snap-head {
padding: 6px 0 18px;
border-bottom: 1px dashed rgba(123, 31, 162, 0.18);
margin-bottom: 20px;
}
.dispatch-container .ri-snap-name {
font-size: 18px;
font-weight: 800;
color: #1e293b;
letter-spacing: -0.01em;
}
.dispatch-container .ri-snap-meta {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
font-size: 12px;
font-weight: 600;
color: #64748b;
}
.dispatch-container .ri-status {
padding: 2px 10px;
border-radius: 999px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
background: rgba(100, 116, 139, 0.18);
color: #475569;
}
.dispatch-container .ri-status-idle {
background: rgba(245, 158, 11, 0.18);
color: #b45309;
}
.dispatch-container .ri-status-active,
.dispatch-container .ri-status-online,
.dispatch-container .ri-status-ongoing {
background: rgba(34, 197, 94, 0.18);
color: #15803d;
}
.dispatch-container .ri-status-offline {
background: rgba(239, 68, 68, 0.18);
color: #b91c1c;
}
/* Live pill — sits next to the status to signal the snapshot auto-refreshes
every 30s. Green pulsing dot when idle, brief flash + 'Updating…' label
while a refetch is in flight. */
.dispatch-container .ri-live {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 2px 8px 2px 6px;
border-radius: 999px;
background: rgba(34, 197, 94, 0.12);
color: #15803d;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.dispatch-container .ri-live.is-refetching {
background: rgba(123, 31, 162, 0.12);
color: #7b1fa2;
}
.dispatch-container .ri-live-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #16a34a;
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55);
animation: ri-live-pulse 1.6s ease-in-out infinite;
}
.dispatch-container .ri-live.is-refetching .ri-live-dot {
background: #7b1fa2;
box-shadow: 0 0 0 0 rgba(123, 31, 162, 0.55);
}
@keyframes ri-live-pulse {
0%,
100% {
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55);
}
50% {
box-shadow: 0 0 0 5px rgba(34, 197, 94, 0);
}
}
.dispatch-container .ri-snap-time {
display: inline-flex;
align-items: center;
gap: 4px;
margin-top: 6px;
font-size: 11px;
font-weight: 500;
color: #64748b;
font-variant-numeric: tabular-nums;
}
.dispatch-container .ri-snap-time svg {
font-size: 13px;
}
/* Stats grid — 2 columns of icon tiles, like the popup details grid */
.dispatch-container .ri-snap-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.dispatch-container .ri-stat {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: rgba(123, 31, 162, 0.05);
border: 1px solid rgba(123, 31, 162, 0.12);
border-radius: 10px;
}
.dispatch-container .ri-stat-warn {
background: rgba(239, 68, 68, 0.06);
border-color: rgba(239, 68, 68, 0.22);
}
.dispatch-container .ri-stat-icon {
width: 32px;
height: 32px;
border-radius: 9px;
background: rgba(123, 31, 162, 0.12);
color: #7b1fa2;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
flex-shrink: 0;
}
.dispatch-container .ri-stat-warn .ri-stat-icon {
background: rgba(239, 68, 68, 0.16);
color: #b91c1c;
}
.dispatch-container .ri-stat-icon svg {
font-size: 18px;
}
.dispatch-container .ri-stat-body {
flex: 1;
min-width: 0;
}
.dispatch-container .ri-stat-label {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #64748b;
margin-bottom: 2px;
}
.dispatch-container .ri-stat-value {
font-size: 13px;
font-weight: 700;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
gap: 6px;
}
.dispatch-container .ri-stat-tag {
font-size: 9px;
font-weight: 800;
padding: 2px 6px;
border-radius: 999px;
background: rgba(34, 197, 94, 0.18);
color: #15803d;
letter-spacing: 0.04em;
text-transform: uppercase;
}
/* Coordinates footer */
/* Map section — coords header above an embedded Leaflet map showing the
rider's last reported position. */
.dispatch-container .ri-map-section {
margin-top: 14px;
}
.dispatch-container .ri-coords-label {
font-size: 12px;
font-weight: 700;
color: #1e293b;
font-variant-numeric: tabular-nums;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 8px 14px;
background: linear-gradient(180deg, #ffffff 0%, #fbf5ff 100%);
border: 1px solid rgba(123, 31, 162, 0.18);
border-bottom: 0;
border-radius: 10px 10px 0 0;
width: 100%;
box-sizing: border-box;
}
.dispatch-container .ri-coords-label svg {
color: #7b1fa2;
font-size: 16px;
}
.dispatch-container .ri-map {
height: 260px;
width: 100%;
border: 1px solid rgba(123, 31, 162, 0.18);
border-radius: 0 0 10px 10px;
overflow: hidden;
position: relative;
z-index: 0;
/* Keep leaflet panes below the strat-row tooltips */
}
.dispatch-container .ri-map .leaflet-container {
height: 100%;
width: 100%;
font-family: inherit;
}
/* Permanent banner sitting above the rider's GPS pin in the Rider Info map.
Shows the suburb/area name reverse-geocoded from lat/lon so the operator
can read the location without opening the popup. Styled to override the
default leaflet tooltip chrome (rounded chip, brand purple). */
.dispatch-container .ri-map .leaflet-tooltip.ri-area-banner {
background: #7b1fa2;
color: #fff;
border: 0;
border-radius: 8px;
padding: 4px 10px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.02em;
white-space: nowrap;
box-shadow: 0 4px 10px rgba(15, 23, 42, 0.25);
}
.dispatch-container .ri-map .leaflet-tooltip.ri-area-banner::before {
border-top-color: #7b1fa2;
}
/* Mobile — collapse the sidebar above the main panel, single-column stats */
@media (max-width: 600px) {
.dispatch-container .rider-info-mode {
flex-direction: column;
}
.dispatch-container .ri-sidebar {
width: 100%;
min-width: 0;
max-height: 40vh;
border-right: 0;
border-bottom: 1px solid var(--border, rgba(15, 23, 42, 0.08));
}
.dispatch-container .ri-main {
padding: 16px;
}
.dispatch-container .ri-snap-grid {
grid-template-columns: 1fr;
}
}
/* ── Laptop Responsive Tuning (max-width: 1366px) ── */
@media (max-width: 1366px) {
/* Header adjustments */
.dispatch-container #hdr {
height: 48px;
padding: 0 16px;
}
.dispatch-container .logo-name {
font-size: 15px;
}
.dispatch-container .logo-badge {
width: 28px;
height: 28px;
font-size: 13px;
border-radius: 6px;
}
.dispatch-container .logo {
gap: 8px;
}
.dispatch-container #clock {
font-size: 11px;
padding: 4px 10px;
}
.dispatch-container .hdr-stats {
gap: 8px;
margin-right: 8px;
}
.dispatch-container .strat-stat {
padding: 4px 8px;
font-size: 11px;
gap: 4px;
}
.dispatch-container .strat-stat-label {
display: none;
/* Hide profit/loss labels early to fit numbers */
}
.dispatch-container .live-status {
font-size: 11px;
padding: 4px 8px;
}
.dispatch-container .live-status-sub {
display: none;
/* Hide total orders suffix to save space */
}
/* Date chip — compact mode: drop the "Date" mini-label and tighten the
main card so the chip fits in cramped mobile toolbars. The prev/next
day arrows stay since they're the fastest way to scrub by day on a
phone where opening a system date dialog feels heavy. */
.dispatch-container .date-chip-label {
display: none;
}
.dispatch-container .date-chip-main {
padding: 6px 10px;
gap: 8px;
}
.dispatch-container .date-chip-icon {
width: 24px;
height: 24px;
font-size: 13px;
}
.dispatch-container .date-chip-value {
font-size: 12px;
}
.dispatch-container .date-chip-nav {
width: 28px;
font-size: 16px;
}
/* Strategy Tab Row adjustments */
.dispatch-container #strat-row {
height: 48px;
padding: 0 16px;
gap: 8px;
}
.dispatch-container .sbt {
padding: 6px 12px;
font-size: 12px;
border-radius: 8px;
border: 1px solid rgba(15, 23, 42, 0.08);
gap: 6px;
}
.dispatch-container .sbt .sbt-icon {
width: 16px;
height: 16px;
font-size: 16px;
}
/* Batch Slots Row adjustments */
.dispatch-container #batch-row {
padding: 6px 16px;
gap: 6px;
}
.dispatch-container .batch-label {
font-size: 11px;
}
.dispatch-container .batch-btn {
padding: 4px 8px;
font-size: 11px;
}
/* Sidebar Layout adjustments */
.dispatch-container #sidebar {
width: 320px;
flex-basis: 320px;
}
.dispatch-container .sidebar-toggle-tab {
left: 320px;
}
/* Dynamic reduction in Compare Mode to keep dual maps wide enough */
.dispatch-container #body.compare-mode #sidebar {
width: 250px;
flex-basis: 250px;
}
.dispatch-container #body.compare-mode .sidebar-toggle-tab {
left: 250px;
}
.dispatch-container #body.compare-mode .sidebar-toggle-tab.is-collapsed {
left: 0;
}
/* Trim sidebar item paddings to increase visual density */
.dispatch-container .sb-header {
padding: 10px 12px;
}
.dispatch-container .sb-tile {
padding: 6px 8px;
gap: 6px;
}
.dispatch-container .sb-tile-value {
font-size: 16px;
}
.dispatch-container .rcard {
padding: 10px;
}
.dispatch-container .rcard-name {
font-size: 12px;
}
.dispatch-container .rcard-zone {
font-size: 10px;
}
.dispatch-container .step-wrap {
padding: 10px;
}
.dispatch-container #route-detail {
padding: 12px;
}
.dispatch-container .rd-rider-name {
font-size: 18px;
}
.dispatch-container .rd-stats-grid {
gap: 4px;
}
.dispatch-container .rd-stat {
padding: 8px 4px 6px;
}
.dispatch-container .rd-stat-value {
font-size: 14px;
}
.dispatch-container .rd-stat-label {
font-size: 9px;
}
/* Dual-map Compare Mode Header compression for 14" laptops */
.dispatch-container .compare-header-v2 {
padding: 8px 12px 6px;
gap: 6px;
}
.dispatch-container .compare-header-row .compare-title {
font-size: 13px;
gap: 8px;
}
.dispatch-container .compare-title-dot {
width: 8px;
height: 8px;
}
.dispatch-container .compare-title-badge {
padding: 2px 6px;
font-size: 9px;
}
.dispatch-container .compare-overall-btn,
.dispatch-container .compare-sync-toggle {
padding: 4px 8px;
font-size: 10px;
gap: 4px;
}
.dispatch-container .compare-overall-btn svg,
.dispatch-container .compare-sync-toggle svg {
font-size: 12px;
}
/* Compare Mode Step Timeline Compression for 14" laptops */
.dispatch-container .compare-timeline-wrap {
gap: 4px;
}
.dispatch-container .compare-timeline-container {
padding: 12px 12px;
gap: 12px;
border-radius: 8px;
}
.dispatch-container .compare-timeline-labels {
padding-right: 10px;
gap: 12px;
border-right-width: 1px;
}
.dispatch-container .compare-timeline-label {
font-size: 9px;
height: 24px;
/* aligns with 24px circle height */
}
.dispatch-container .compare-timeline-scrollable {
flex: 1;
overflow-x: auto;
display: flex;
flex-direction: column;
gap: 12px;
scrollbar-width: none;
/* Hide standard Firefox scrollbar */
-ms-overflow-style: none;
/* Hide IE scrollbar */
}
.dispatch-container .compare-timeline-scrollable::-webkit-scrollbar {
height: 4px;
}
.dispatch-container .compare-timeline-scrollable::-webkit-scrollbar-track {
background: transparent;
}
.dispatch-container .compare-timeline-scrollable::-webkit-scrollbar-thumb {
background: rgba(99, 102, 241, 0.25);
border-radius: 999px;
}
.dispatch-container .compare-timeline-scrollable:hover::-webkit-scrollbar-thumb {
background: rgba(99, 102, 241, 0.5);
}
/* Planned track now also carries a time tick under the circle, so the spacer
aligns the same way as the actual row (mirrors the 24px circle center). */
.dispatch-container .compare-timeline-track.is-planned .compare-step-spacer {
margin-bottom: 14px;
}
/* Actual track overrides for the spacer alignment */
.dispatch-container .compare-timeline-track.is-actual .compare-step-spacer {
margin-bottom: 14px;
/* Centers spacer dynamically relative to the 24px circle */
}
.dispatch-container .compare-step {
gap: 4px;
/* Tighten spacer-circle layout vertical spacing */
}
.dispatch-container .compare-step-circle {
width: 24px;
height: 24px;
font-size: 10px;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.6);
}
.dispatch-container .compare-step.is-focused .compare-step-circle {
transform: scale(1.1);
box-shadow:
0 2px 6px rgba(15, 23, 42, 0.15),
0 0 0 2px #fff,
0 0 0 3px var(--step-color, #6366f1);
}
.dispatch-container .compare-step-spacer {
width: 10px;
margin-bottom: 14px;
/* Shift connecting lines up for 24px circles */
}
.dispatch-container .compare-step-tick {
font-size: 9px;
}
.dispatch-container .compare-step-flag {
top: -3px;
right: -3px;
width: 8px;
height: 8px;
border-width: 1px;
}
/* Progress Strip inside Timeline */
.dispatch-container .compare-progress-strip {
margin-top: 2px;
}
.dispatch-container .compare-progress-text {
font-size: 9px;
}
/* Legend compacting for laptops */
.dispatch-container .compare-legend {
padding-top: 2px;
margin-top: 0;
gap: 8px;
}
.dispatch-container .compare-legend-item {
font-size: 9px;
}
.dispatch-container .compare-legend-swatch {
width: 10px;
height: 10px;
}
.dispatch-container .compare-legend-note {
display: none;
/* Hide wordy GPS smoothing notes */
}
/* Bottom Delta Card panel compression for 14" laptops */
.dispatch-container .compare-delta {
padding: 8px 12px;
gap: 6px;
}
.dispatch-container .compare-delta-title {
margin-bottom: 4px;
gap: 8px;
}
.dispatch-container .compare-delta-step-badge {
width: 20px;
height: 20px;
font-size: 11px;
}
.dispatch-container .compare-delta-title-main {
font-size: 12px;
}
.dispatch-container .compare-delta-title-sub {
font-size: 9px;
}
.dispatch-container .compare-delta-status {
padding: 2px 6px;
font-size: 9px;
}
.dispatch-container .compare-delta-grid {
gap: 6px;
}
.dispatch-container .compare-delta-cell {
padding: 5px 8px 4px;
border-radius: 8px;
}
.dispatch-container .compare-delta-cell-label {
font-size: 9px;
margin-bottom: 1px;
}
.dispatch-container .compare-delta-cell-val {
font-size: 13px;
}
.dispatch-container .compare-delta-cell-unit {
font-size: 8px;
}
.dispatch-container .compare-delta-cell-sub {
font-size: 9px;
margin-top: 1px;
}
}
/* ── Laptop Height Tuning (max-height: 750px) ── */
@media (max-height: 750px) {
/* Let's shrink header rows even further on short screen heights */
.dispatch-container #hdr {
height: 42px;
}
.dispatch-container #strat-row {
height: 42px;
}
.dispatch-container #strat-row .sbt {
padding: 5px 10px;
font-size: 11px;
border-radius: 6px;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.dispatch-container #batch-row {
padding: 4px 16px;
}
/* Dual-map Compare Mode Header compression */
.dispatch-container .compare-header-v2 {
padding: 8px 12px 6px;
gap: 6px;
}
.dispatch-container .compare-header-row .compare-title {
font-size: 13px;
gap: 8px;
}
.dispatch-container .compare-title-dot {
width: 8px;
height: 8px;
}
.dispatch-container .compare-title-badge {
padding: 2px 6px;
font-size: 9px;
}
.dispatch-container .compare-overall-btn,
.dispatch-container .compare-sync-toggle {
padding: 4px 8px;
font-size: 10px;
gap: 4px;
}
.dispatch-container .compare-overall-btn svg,
.dispatch-container .compare-sync-toggle svg {
font-size: 12px;
}
/* Compare Mode Step Timeline Compression */
.dispatch-container .compare-timeline-wrap {
gap: 4px;
}
.dispatch-container .compare-timeline-container {
padding: 12px 12px;
gap: 12px;
border-radius: 8px;
}
.dispatch-container .compare-timeline-labels {
padding-right: 10px;
gap: 12px;
border-right-width: 1px;
}
.dispatch-container .compare-timeline-label {
font-size: 9px;
height: 24px;
/* aligns with 24px circle height */
}
.dispatch-container .compare-timeline-scrollable {
flex: 1;
overflow-x: auto;
display: flex;
flex-direction: column;
gap: 12px;
padding-bottom: 2px;
}
/* Planned track now also carries a time tick under the circle, so the spacer
aligns the same way as the actual row (mirrors the 24px circle center). */
.dispatch-container .compare-timeline-track.is-planned .compare-step-spacer {
margin-bottom: 14px;
}
/* Actual track overrides for the spacer alignment */
.dispatch-container .compare-timeline-track.is-actual .compare-step-spacer {
margin-bottom: 14px;
/* Centers spacer dynamically relative to the 24px circle */
}
.dispatch-container .compare-step {
gap: 4px;
/* Reduced from 11px to bring timeline elements tighter */
}
.dispatch-container .compare-step-circle {
width: 24px;
height: 24px;
font-size: 10px;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.6);
}
.dispatch-container .compare-step.is-focused .compare-step-circle {
transform: scale(1.1);
box-shadow:
0 2px 6px rgba(15, 23, 42, 0.15),
0 0 0 2px #fff,
0 0 0 3px var(--step-color, #6366f1);
}
.dispatch-container .compare-step-spacer {
width: 10px;
margin-bottom: 14px;
/* Shift spacer dynamically up to align with 24px circles */
}
.dispatch-container .compare-step-tick {
font-size: 9px;
}
/* Progress Strip inside Timeline */
.dispatch-container .compare-progress-strip {
margin-top: 2px;
}
.dispatch-container .compare-progress-text {
font-size: 9px;
}
/* Legend compacting */
.dispatch-container .compare-legend {
padding-top: 2px;
margin-top: 0;
gap: 8px;
}
.dispatch-container .compare-legend-item {
font-size: 9px;
}
.dispatch-container .compare-legend-swatch {
width: 10px;
height: 10px;
}
.dispatch-container .compare-legend-note {
display: none;
/* Hide verbose Kalman note on short screens */
}
/* Bottom Delta Card panel compression */
.dispatch-container .compare-delta {
padding: 8px 12px;
gap: 6px;
}
.dispatch-container .compare-delta-title {
margin-bottom: 4px;
gap: 8px;
}
.dispatch-container .compare-delta-step-badge {
width: 20px;
height: 20px;
font-size: 11px;
}
.dispatch-container .compare-delta-title-main {
font-size: 12px;
}
.dispatch-container .compare-delta-title-sub {
font-size: 9px;
}
.dispatch-container .compare-delta-status {
padding: 2px 6px;
font-size: 9px;
}
/* Delta Grid - cells and labels */
.dispatch-container .compare-delta-grid {
gap: 6px;
}
.dispatch-container .compare-delta-cell {
padding: 5px 8px 4px;
border-radius: 8px;
}
.dispatch-container .compare-delta-cell-label {
font-size: 9px;
margin-bottom: 1px;
}
.dispatch-container .compare-delta-cell-val {
font-size: 13px;
}
.dispatch-container .compare-delta-cell-unit {
font-size: 8px;
}
.dispatch-container .compare-delta-cell-sub {
font-size: 9px;
margin-top: 1px;
}
}
/* Day summary toggle styles */
.dispatch-container .compare-delta.is-collapsible.is-collapsed {
padding-bottom: 12px;
}
.dispatch-container .compare-delta-toggle-icon {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-muted, #64748b);
font-size: 18px;
transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1), color 0.15s ease;
}
.dispatch-container .compare-delta-title:hover .compare-delta-toggle-icon {
color: var(--accent, #6366f1);
}
@media (max-width: 1366px) {
.dispatch-container .compare-delta.is-collapsible.is-collapsed {
padding-bottom: 8px;
}
.dispatch-container .compare-delta-toggle-icon {
font-size: 15px;
}
}
@media (max-height: 750px) {
.dispatch-container .compare-delta.is-collapsible.is-collapsed {
padding-bottom: 8px;
}
.dispatch-container .compare-delta-toggle-icon {
font-size: 15px;
}
}
/* ============================================================
Compare screen — unified layout. Compare mode takes over the
body: one big map fills the left column (with a Planned /
Actual / Combined view switcher pinned to its top-left), a
compact timeline + legend strip sits above the map, and the
scrollable data panel (deviations, per-step correctness,
delivery times, profit) anchors the right column.
============================================================ */
.dispatch-container #body.compare-mode {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
grid-template-rows: auto minmax(0, 1fr);
gap: 12px;
padding: 12px;
background: linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
transition: grid-template-columns 0.32s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.dispatch-container #body.compare-mode #sidebar,
.dispatch-container #body.compare-mode .sidebar-toggle-tab {
display: none !important;
}
/* Collapsed-data-panel state — drop the right column entirely so the map
claims the full body width. The panel itself is masked via overflow on
the body grid; the peek tab below stays visible to re-open. */
.dispatch-container #body.compare-mode.compare-data-collapsed {
grid-template-columns: minmax(0, 1fr) 0;
gap: 0;
}
.dispatch-container #body.compare-mode.compare-data-collapsed .compare-data-panel {
opacity: 0;
pointer-events: none;
transform: translateX(20px);
}
.dispatch-container .compare-data-panel {
transition: opacity 0.24s ease, transform 0.32s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Peek tab for the right-side compare data panel — vertical pill mirroring
the left sidebar's toggle, but anchored to the panel's left edge. Tracks
the panel by sitting at right:0 when expanded (so it hugs the panel's
outside-left edge) and snaps flush to the viewport's right side when
collapsed. */
.dispatch-container .compare-data-toggle-tab {
position: absolute;
top: 50%;
/* Anchor flush against the panel's outside-left edge. Panel max width is
440px (see compare-mode grid-template-columns above) plus the 12px grid
gap; transform: translate(50%, …) re-centres the 22-wide pill on that
boundary so half of it sits on the panel side and half on the map side
— same visual treatment as the left sidebar's peek tab. */
right: calc(440px + 12px);
transform: translate(50%, -50%);
width: 22px;
height: 56px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid var(--border, rgba(15, 23, 42, 0.12));
border-radius: 10px;
background: #fff;
color: var(--text, #0f172a);
font-size: 18px;
line-height: 1;
cursor: pointer;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12),
0 1px 3px rgba(15, 23, 42, 0.06);
z-index: 1200;
transition: right 0.32s cubic-bezier(0.4, 0, 0.2, 1),
background 0.18s ease,
color 0.18s ease,
transform 0.18s ease,
box-shadow 0.18s ease;
}
.dispatch-container .compare-data-toggle-tab:hover {
background: linear-gradient(135deg, #6366f1, #3b82f6);
color: #fff;
transform: translate(50%, -50%) scale(1.06);
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.35);
}
.dispatch-container .compare-data-toggle-tab:focus-visible {
outline: 2px solid var(--accent, #3b82f6);
outline-offset: 2px;
}
.dispatch-container .compare-data-toggle-tab.is-collapsed {
right: 0;
transform: translate(0, -50%);
border-radius: 10px 0 0 10px;
border-right: none;
}
.dispatch-container .compare-data-toggle-tab.is-collapsed:hover {
transform: translate(0, -50%) scale(1.06);
}
.dispatch-container .compare-data-toggle-tab svg {
display: block;
}
/* Header strip — sits above the unified map (row 1, col 1) and
carries the rider title, the step timeline + load progress, and
the layer legend. The Sync toggle was removed when the second
map went away; the layer switcher is overlaid on the map itself. */
.dispatch-container #body.compare-mode #compare-map-wrap {
grid-column: 1;
grid-row: 1;
flex: none;
min-width: 0;
margin: 0;
background: #ffffff;
border-radius: 14px;
box-shadow: 0 6px 24px rgba(15, 23, 42, 0.08),
0 0 0 1px rgba(15, 23, 42, 0.06);
overflow: hidden;
animation: compare-slide-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* The header v2 block sat inside a flex column inside the old
compare-map-wrap. With the wrapper now sized to content, the
inner block doesn't need its own border-bottom anymore — the
wrapper's rounded card provides the visual containment. */
.dispatch-container #body.compare-mode #compare-map-wrap .compare-header-v2 {
border-bottom: 0;
background: transparent;
}
.dispatch-container #body.compare-mode #map-wrap,
.dispatch-container #body.compare-mode #map-wrap.compare-split {
grid-column: 1;
grid-row: 2;
flex: none;
min-width: 0;
min-height: 0;
margin: 0;
border-radius: 14px;
border-right: 0;
box-shadow: 0 6px 24px rgba(15, 23, 42, 0.08),
0 0 0 1px rgba(15, 23, 42, 0.06);
overflow: hidden;
}
/* Layer switcher — pinned to the top-left corner of the unified
compare map. Segmented control: Actual | Planned | Combined.
Active button uses the same indigo→blue gradient as the Compare
pill in #ov-br so the operator can visually trace "Compare on"
to "this is the active layer". */
.dispatch-container .compare-view-switcher {
position: absolute;
top: 12px;
left: 12px;
z-index: 600;
display: inline-flex;
align-items: stretch;
gap: 2px;
padding: 4px;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(8px);
border-radius: 10px;
box-shadow: 0 6px 20px rgba(15, 23, 42, 0.12),
0 0 0 1px rgba(15, 23, 42, 0.08);
animation: compare-label-in 0.22s ease-out;
}
.dispatch-container .compare-view-switcher button {
appearance: none;
border: 0;
background: transparent;
padding: 6px 14px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #475569;
border-radius: 7px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease,
box-shadow 0.15s ease, transform 0.15s ease;
white-space: nowrap;
}
.dispatch-container .compare-view-switcher button:hover {
background: rgba(99, 102, 241, 0.08);
color: #4338ca;
}
.dispatch-container .compare-view-switcher button:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.5);
outline-offset: 1px;
}
.dispatch-container .compare-view-switcher button.is-active {
background: linear-gradient(135deg, #6366f1, #3b82f6);
color: #ffffff;
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.35);
}
.dispatch-container .compare-view-switcher button.is-active:hover {
color: #ffffff;
transform: translateY(-0.5px);
}
/* Data panel ------------------------------------------------- */
.dispatch-container .compare-data-panel {
grid-column: 2;
grid-row: 1 / 3;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
background: #ffffff;
border-radius: 14px;
box-shadow: 0 6px 24px rgba(15, 23, 42, 0.08),
0 0 0 1px rgba(15, 23, 42, 0.06);
overflow: hidden;
animation: compare-slide-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.dispatch-container .cdp-head {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
background: linear-gradient(135deg, #6366f1 0%, #3b82f6 100%);
color: #fff;
flex-shrink: 0;
}
.dispatch-container .cdp-head-title {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.dispatch-container .cdp-rider-dot {
width: 14px;
height: 14px;
border-radius: 50%;
flex-shrink: 0;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4);
}
.dispatch-container .cdp-head-text {
display: flex;
flex-direction: column;
min-width: 0;
}
.dispatch-container .cdp-rider-name {
font-size: 15px;
font-weight: 800;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: 0.01em;
}
.dispatch-container .cdp-head-badge {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
color: rgba(255, 255, 255, 0.85);
text-transform: uppercase;
}
.dispatch-container .cdp-close {
width: 32px;
height: 32px;
border: 0;
border-radius: 8px;
background: rgba(255, 255, 255, 0.18);
color: #fff;
font-size: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.15s ease, transform 0.15s ease;
}
.dispatch-container .cdp-close:hover {
background: rgba(255, 255, 255, 0.32);
transform: rotate(90deg);
}
.dispatch-container .cdp-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 14px 14px 18px;
display: flex;
flex-direction: column;
gap: 14px;
}
.dispatch-container .cdp-scroll::-webkit-scrollbar {
width: 8px;
}
.dispatch-container .cdp-scroll::-webkit-scrollbar-track {
background: transparent;
}
.dispatch-container .cdp-scroll::-webkit-scrollbar-thumb {
background: rgba(15, 23, 42, 0.14);
border-radius: 999px;
}
.dispatch-container .cdp-section {
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.06);
border-radius: 12px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 10px;
}
.dispatch-container .cdp-section-head {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #475569;
}
.dispatch-container .cdp-section-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 6px;
background: rgba(99, 102, 241, 0.12);
color: #4338ca;
font-size: 14px;
}
.dispatch-container .cdp-section-icon.cdp-icon-warn {
background: rgba(239, 68, 68, 0.12);
color: #dc2626;
}
.dispatch-container .cdp-section-title {
flex: 1;
min-width: 0;
}
.dispatch-container .cdp-section-sub {
font-size: 10px;
font-weight: 700;
color: #94a3b8;
letter-spacing: 0.04em;
text-transform: none;
}
.dispatch-container .cdp-section-clear {
border: 0;
background: rgba(99, 102, 241, 0.1);
color: #4338ca;
padding: 3px 9px;
border-radius: 999px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.04em;
cursor: pointer;
text-transform: none;
}
.dispatch-container .cdp-section-clear:hover {
background: rgba(99, 102, 241, 0.2);
}
/* Day overview tiles ---------------------------------------- */
.dispatch-container .cdp-tiles {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.dispatch-container .cdp-tile {
background: #fff;
border: 1px solid rgba(15, 23, 42, 0.07);
border-radius: 10px;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 4px;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.dispatch-container .cdp-tile:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06);
}
.dispatch-container .cdp-tile.is-warn {
background: linear-gradient(180deg, #fff7ed 0%, #fff 100%);
border-color: rgba(249, 115, 22, 0.25);
}
.dispatch-container .cdp-tile.is-loss {
background: linear-gradient(180deg, #fef2f2 0%, #fff 100%);
border-color: rgba(239, 68, 68, 0.25);
}
.dispatch-container .cdp-tile.is-gain {
background: linear-gradient(180deg, #ecfdf5 0%, #fff 100%);
border-color: rgba(16, 185, 129, 0.25);
}
.dispatch-container .cdp-tile-label {
display: flex;
align-items: center;
gap: 5px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #64748b;
}
.dispatch-container .cdp-tile-label svg {
font-size: 13px;
}
.dispatch-container .cdp-tile-value {
font-size: 20px;
font-weight: 800;
color: #0f172a;
line-height: 1.1;
letter-spacing: -0.01em;
}
.dispatch-container .cdp-tile-value.is-over {
color: #dc2626;
}
.dispatch-container .cdp-tile-value.is-under {
color: #16a34a;
}
.dispatch-container .cdp-tile-unit {
font-size: 12px;
font-weight: 700;
color: #94a3b8;
margin-left: 2px;
}
.dispatch-container .cdp-tile-sub {
font-size: 11px;
font-weight: 600;
color: #94a3b8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Deviations list ------------------------------------------- */
.dispatch-container .cdp-dev-list,
.dispatch-container .cdp-step-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.dispatch-container .cdp-dev-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
background: #fff;
border: 1px solid rgba(239, 68, 68, 0.2);
border-radius: 10px;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.dispatch-container .cdp-dev-item:hover {
transform: translateX(2px);
box-shadow: 0 3px 10px rgba(239, 68, 68, 0.12);
}
.dispatch-container .cdp-dev-item.is-focused {
border-color: rgba(239, 68, 68, 0.6);
background: #fef2f2;
}
.dispatch-container .cdp-dev-num {
width: 30px;
height: 30px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 14px;
font-weight: 800;
flex-shrink: 0;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.6),
0 1px 3px rgba(15, 23, 42, 0.15);
}
.dispatch-container .cdp-dev-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 5px;
}
.dispatch-container .cdp-dev-title {
font-size: 14px;
font-weight: 700;
color: #0f172a;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .cdp-dev-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.dispatch-container .cdp-dev-chip {
display: inline-flex;
align-items: center;
padding: 3px 9px;
font-size: 12px;
font-weight: 700;
border-radius: 999px;
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.dispatch-container .cdp-dev-chip.is-over {
background: rgba(239, 68, 68, 0.12);
color: #dc2626;
}
/* Per-step list --------------------------------------------- */
.dispatch-container .cdp-step {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px;
background: #fff;
border: 1px solid rgba(15, 23, 42, 0.07);
border-radius: 10px;
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.dispatch-container .cdp-step:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
border-color: rgba(99, 102, 241, 0.3);
}
.dispatch-container .cdp-step.is-focused {
border-color: rgba(99, 102, 241, 0.65);
background: linear-gradient(180deg, #eef2ff 0%, #fff 100%);
box-shadow: 0 4px 14px rgba(99, 102, 241, 0.18);
}
.dispatch-container .cdp-step.is-correct {
border-color: rgba(16, 185, 129, 0.25);
}
.dispatch-container .cdp-step.is-anomaly {
border-color: rgba(239, 68, 68, 0.4);
background: linear-gradient(180deg, #fef2f2 0%, #fff 100%);
}
.dispatch-container .cdp-step.is-skipped {
opacity: 0.65;
}
.dispatch-container .cdp-step.is-loading {
opacity: 0.7;
}
.dispatch-container .cdp-step-num {
width: 34px;
height: 34px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 15px;
font-weight: 800;
flex-shrink: 0;
position: relative;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.6),
0 1px 3px rgba(15, 23, 42, 0.15);
}
.dispatch-container .cdp-step-check,
.dispatch-container .cdp-step-flag {
position: absolute;
bottom: -3px;
right: -3px;
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 13px;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.2);
}
.dispatch-container .cdp-step-check {
color: #16a34a;
}
.dispatch-container .cdp-step-flag {
color: #dc2626;
}
.dispatch-container .cdp-step-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.dispatch-container .cdp-step-title-row {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.dispatch-container .cdp-step-title {
font-size: 15px;
font-weight: 700;
color: #0f172a;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.dispatch-container .cdp-step-status {
display: inline-flex;
align-items: center;
padding: 3px 9px;
font-size: 11px;
font-weight: 800;
border-radius: 999px;
letter-spacing: 0.05em;
text-transform: uppercase;
flex-shrink: 0;
}
.dispatch-container .cdp-step-sub {
font-size: 13px;
font-weight: 600;
color: #94a3b8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .cdp-step-deltas {
display: flex;
flex-wrap: wrap;
gap: 6px 12px;
margin-top: 3px;
}
.dispatch-container .cdp-step-delta {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 13px;
font-weight: 700;
color: #475569;
}
.dispatch-container .cdp-step-delta svg {
font-size: 15px;
color: #94a3b8;
}
.dispatch-container .cdp-step-delta small {
font-size: 12px;
font-weight: 700;
color: #94a3b8;
}
.dispatch-container .cdp-step-delta small.is-over {
color: #dc2626;
}
.dispatch-container .cdp-step-delta.is-over {
color: #dc2626;
}
.dispatch-container .cdp-step-delta.is-over svg {
color: #dc2626;
}
.dispatch-container .cdp-step-delta.is-under {
color: #16a34a;
}
.dispatch-container .cdp-step-delta.is-under svg {
color: #16a34a;
}
/* Responsive — narrow screens collapse to a single column. Header strip
stacks on top, then the unified map, then the data panel scrolls
below. The map gets a generous min-height so the layer-switcher
overlay and timeline stay usable on tablets. */
@media (max-width: 1100px) {
.dispatch-container #body.compare-mode {
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto minmax(360px, 1fr) auto;
}
.dispatch-container #body.compare-mode #compare-map-wrap {
grid-column: 1;
grid-row: 1;
}
.dispatch-container #body.compare-mode #map-wrap,
.dispatch-container #body.compare-mode #map-wrap.compare-split {
grid-column: 1;
grid-row: 2;
}
.dispatch-container .compare-data-panel {
grid-column: 1;
grid-row: 3;
max-height: 50vh;
}
/* Single-column layout stacks the panel BELOW the map, so the
side-anchored peek tab no longer makes geometric sense — hide it. */
.dispatch-container .compare-data-toggle-tab {
display: none;
}
}
/* Hide filter chrome when Compare takes over the screen — view-mode
tabs (#strat-row) and the slot picker (#batch-row) would clutter
the dedicated compare view, and the right-side data panel already
carries everything the operator needs. */
.dispatch-container.compare-open #strat-row,
.dispatch-container.compare-open #batch-row {
display: none !important;
}
/* Compliance score gauge ------------------------------------ */
.dispatch-container .cdp-score-section {
background: linear-gradient(135deg, #f8fafc 0%, #eef2ff 100%);
border-color: rgba(99, 102, 241, 0.18);
padding: 14px;
}
.dispatch-container .cdp-score-wrap {
display: flex;
align-items: center;
gap: 16px;
}
.dispatch-container .cdp-score-ring {
width: 84px;
height: 84px;
border-radius: 50%;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.1);
transition: background 0.5s ease;
}
.dispatch-container .cdp-score-inner {
width: 70px;
height: 70px;
border-radius: 50%;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1px;
}
.dispatch-container .cdp-score-value {
font-size: 26px;
font-weight: 900;
line-height: 1;
letter-spacing: -0.02em;
}
.dispatch-container .cdp-score-unit {
font-size: 10px;
font-weight: 700;
color: #94a3b8;
letter-spacing: 0.05em;
}
.dispatch-container .cdp-score-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.dispatch-container .cdp-score-label {
font-size: 11px;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.dispatch-container .cdp-score-title {
font-size: 15px;
font-weight: 800;
color: #0f172a;
letter-spacing: -0.01em;
}
.dispatch-container .cdp-score-sub {
font-size: 11px;
font-weight: 600;
color: #64748b;
}
/* Timing — clock-style timeline ----------------------------- */
/* Premium "workday window" composition: two digital clock faces flank
a gradient track with the active-time centerpiece. Soft radial
wash on the container, "Started"/"Finished" captions tell the
narrative. Below: stats with mini-visualizations. */
.dispatch-container .cdp-timing-section .cdp-section-head {
display: flex;
align-items: center;
gap: 8px;
}
.dispatch-container .cdp-timing-active-tag {
display: inline-flex;
align-items: center;
gap: 5px;
margin-left: auto;
padding: 2px 8px 2px 6px;
border-radius: 999px;
background: rgba(16, 185, 129, 0.1);
color: #16a34a;
font-size: 9px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.dispatch-container .cdp-timing-active-pulse {
width: 6px;
height: 6px;
border-radius: 50%;
background: #10b981;
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.55);
animation: cdp-timing-pulse 1.8s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes cdp-timing-pulse {
0% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.55);
}
70% {
box-shadow: 0 0 0 8px rgba(16, 185, 129, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
}
}
.dispatch-container .cdp-timing-clock {
position: relative;
display: grid;
grid-template-columns: 1fr minmax(70px, 1.3fr) 1fr;
align-items: center;
gap: 8px;
padding: 18px 14px 16px;
background:
radial-gradient(120% 80% at 50% 0%, rgba(99, 102, 241, 0.07) 0%, transparent 60%),
linear-gradient(180deg, #fbfcff 0%, #fff 100%);
border-radius: 14px;
border: 1px solid rgba(15, 23, 42, 0.07);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
}
.dispatch-container .cdp-clock-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
min-width: 0;
}
.dispatch-container .cdp-clock-label {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 10px;
font-weight: 800;
color: #64748b;
letter-spacing: 0.07em;
text-transform: uppercase;
}
.dispatch-container .cdp-clock-label svg {
font-size: 13px;
}
.dispatch-container .cdp-clock-card.is-start .cdp-clock-label svg {
color: #10b981;
}
.dispatch-container .cdp-clock-card.is-end .cdp-clock-label svg {
color: #f59e0b;
}
.dispatch-container .cdp-clock-face {
position: relative;
display: inline-flex;
align-items: baseline;
gap: 5px;
padding: 10px 14px;
border-radius: 12px;
background: linear-gradient(180deg, #1e293b 0%, #0b1220 100%);
box-shadow:
0 2px 4px rgba(15, 23, 42, 0.18),
0 8px 18px rgba(15, 23, 42, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.07),
inset 0 -1px 0 rgba(0, 0, 0, 0.4);
}
.dispatch-container .cdp-clock-face::after {
content: '';
position: absolute;
inset: 1px;
border-radius: 11px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, transparent 50%);
pointer-events: none;
}
.dispatch-container .cdp-clock-card.is-start .cdp-clock-face {
border-top: 1px solid rgba(16, 185, 129, 0.4);
}
.dispatch-container .cdp-clock-card.is-end .cdp-clock-face {
border-top: 1px solid rgba(245, 158, 11, 0.4);
}
.dispatch-container .cdp-clock-time {
font-family: 'SF Mono', 'Roboto Mono', 'Menlo', 'Consolas', monospace;
font-size: 24px;
font-weight: 700;
color: #f1f5f9;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
line-height: 1;
text-shadow: 0 0 12px rgba(167, 195, 255, 0.18);
}
.dispatch-container .cdp-clock-period {
font-size: 10px;
font-weight: 800;
color: #cbd5e1;
letter-spacing: 0.1em;
padding: 1px 5px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.08);
}
.dispatch-container .cdp-clock-caption {
font-size: 9px;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #94a3b8;
}
.dispatch-container .cdp-clock-card.is-start .cdp-clock-caption {
color: #16a34a;
}
.dispatch-container .cdp-clock-card.is-end .cdp-clock-caption {
color: #d97706;
}
/* Timeline track — line stretches the full column with the duration
badge anchored on top. */
.dispatch-container .cdp-clock-track {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 60px;
min-width: 70px;
}
.dispatch-container .cdp-clock-track-line {
position: absolute;
left: 4px;
right: 4px;
top: 50%;
height: 4px;
transform: translateY(-50%);
background: linear-gradient(90deg, #10b981 0%, #6366f1 50%, #f59e0b 100%);
border-radius: 999px;
box-shadow: 0 1px 2px rgba(99, 102, 241, 0.18);
}
.dispatch-container .cdp-clock-track-dot {
position: absolute;
top: 50%;
width: 11px;
height: 11px;
border-radius: 50%;
transform: translateY(-50%);
z-index: 1;
}
.dispatch-container .cdp-clock-track-dot.is-start {
left: 0;
background: #10b981;
box-shadow:
0 0 0 2px #fff,
0 0 0 5px rgba(16, 185, 129, 0.25);
}
.dispatch-container .cdp-clock-track-dot.is-end {
right: 0;
background: #f59e0b;
box-shadow:
0 0 0 2px #fff,
0 0 0 5px rgba(245, 158, 11, 0.25);
}
.dispatch-container .cdp-clock-duration {
position: relative;
z-index: 2;
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 0;
padding: 6px 10px 5px;
border-radius: 12px;
background: #fff;
border: 1px solid rgba(99, 102, 241, 0.2);
box-shadow:
0 4px 12px rgba(15, 23, 42, 0.08),
0 1px 3px rgba(99, 102, 241, 0.12);
white-space: nowrap;
min-width: 56px;
}
.dispatch-container .cdp-clock-duration-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(135deg, #6366f1 0%, #4338ca 100%);
color: #fff;
font-size: 12px;
margin-bottom: 3px;
box-shadow: 0 2px 4px rgba(99, 102, 241, 0.35);
}
.dispatch-container .cdp-clock-duration-val {
font-size: 14px;
font-weight: 800;
color: #0f172a;
font-variant-numeric: tabular-nums;
letter-spacing: -0.01em;
line-height: 1.1;
}
.dispatch-container .cdp-clock-duration-sub {
font-size: 8px;
font-weight: 800;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-top: 1px;
}
/* Supporting timing stats with mini-visualizations. */
.dispatch-container .cdp-timing-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin-top: 12px;
}
.dispatch-container .cdp-timing-stat {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
background: #fff;
border-radius: 12px;
border: 1px solid rgba(15, 23, 42, 0.07);
transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}
.dispatch-container .cdp-timing-stat:hover {
border-color: rgba(99, 102, 241, 0.3);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.1);
transform: translateY(-1px);
}
.dispatch-container .cdp-timing-stat-head {
display: flex;
align-items: center;
gap: 10px;
}
.dispatch-container .cdp-timing-stat-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 10px;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(67, 56, 202, 0.1) 100%);
color: #4338ca;
font-size: 20px;
flex-shrink: 0;
box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.12);
}
.dispatch-container .cdp-timing-stat-body {
display: flex;
flex-direction: column;
gap: 1px;
min-width: 0;
}
.dispatch-container .cdp-timing-stat-value {
font-size: 18px;
font-weight: 800;
color: #0f172a;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.dispatch-container .cdp-timing-stat-unit {
font-size: 11px;
font-weight: 700;
color: #94a3b8;
margin-left: 3px;
}
.dispatch-container .cdp-timing-stat-label {
font-size: 10px;
font-weight: 800;
color: #94a3b8;
letter-spacing: 0.06em;
text-transform: uppercase;
}
/* Mini-visualizations under each stat. */
.dispatch-container .cdp-timing-stat-viz {
display: flex;
align-items: center;
gap: 6px;
padding-top: 8px;
border-top: 1px dashed rgba(15, 23, 42, 0.08);
}
.dispatch-container .cdp-timing-stat-viz-label {
font-size: 10px;
font-weight: 700;
color: #94a3b8;
margin-left: auto;
white-space: nowrap;
}
/* Stops-row — small dots, one per stop (capped at 12). */
.dispatch-container .cdp-stops-dots {
flex-wrap: wrap;
}
.dispatch-container .cdp-stop-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: linear-gradient(135deg, #6366f1 0%, #4338ca 100%);
box-shadow: 0 1px 2px rgba(99, 102, 241, 0.3);
flex-shrink: 0;
}
/* Speed gauge — 0-60 scale bar with filled segment. */
.dispatch-container .cdp-speed-gauge {
flex-direction: column;
align-items: stretch;
gap: 4px;
}
.dispatch-container .cdp-speed-gauge-track {
position: relative;
height: 6px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.06);
overflow: hidden;
}
.dispatch-container .cdp-speed-gauge-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, #10b981 0%, #6366f1 60%, #f59e0b 100%);
box-shadow: 0 1px 3px rgba(99, 102, 241, 0.3);
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.dispatch-container .cdp-speed-gauge-scale {
display: flex;
justify-content: space-between;
font-size: 9px;
font-weight: 700;
color: #94a3b8;
font-variant-numeric: tabular-nums;
letter-spacing: 0.04em;
}
/* Highlights (best / worst) --------------------------------- */
/* Full-width vertically-stacked cards. A 4px colored rail on the left
encodes good/bad. Inside: a label-icon header + a right-aligned step
chip, then the customer name as the headline, then metric pills. */
.dispatch-container .cdp-highlights {
display: flex;
flex-direction: column;
gap: 10px;
}
.dispatch-container .cdp-highlight {
position: relative;
display: flex;
align-items: stretch;
padding: 0;
border-radius: 12px;
background: #fff;
border: 1px solid rgba(15, 23, 42, 0.07);
cursor: pointer;
overflow: hidden;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.dispatch-container .cdp-highlight:hover {
transform: translateX(2px);
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
}
.dispatch-container .cdp-highlight-rail {
width: 4px;
flex-shrink: 0;
align-self: stretch;
}
.dispatch-container .cdp-highlight-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 14px;
}
.dispatch-container .cdp-highlight.is-best {
background: linear-gradient(90deg, rgba(16, 185, 129, 0.07) 0%, #fff 65%);
border-color: rgba(16, 185, 129, 0.28);
}
.dispatch-container .cdp-highlight.is-best:hover {
border-color: rgba(16, 185, 129, 0.5);
}
.dispatch-container .cdp-highlight.is-best .cdp-highlight-rail {
background: linear-gradient(180deg, #10b981 0%, #16a34a 100%);
}
.dispatch-container .cdp-highlight.is-worst {
background: linear-gradient(90deg, rgba(239, 68, 68, 0.07) 0%, #fff 65%);
border-color: rgba(239, 68, 68, 0.28);
}
.dispatch-container .cdp-highlight.is-worst:hover {
border-color: rgba(239, 68, 68, 0.5);
}
.dispatch-container .cdp-highlight.is-worst .cdp-highlight-rail {
background: linear-gradient(180deg, #ef4444 0%, #dc2626 100%);
}
.dispatch-container .cdp-highlight-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.dispatch-container .cdp-highlight-label {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
min-width: 0;
}
.dispatch-container .cdp-highlight.is-best .cdp-highlight-label {
color: #16a34a;
}
.dispatch-container .cdp-highlight.is-worst .cdp-highlight-label {
color: #dc2626;
}
.dispatch-container .cdp-highlight-chip {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 7px;
font-size: 14px;
color: #fff;
flex-shrink: 0;
}
.dispatch-container .cdp-highlight.is-best .cdp-highlight-chip {
background: linear-gradient(135deg, #10b981 0%, #16a34a 100%);
box-shadow: 0 1px 3px rgba(16, 185, 129, 0.35);
}
.dispatch-container .cdp-highlight.is-worst .cdp-highlight-chip {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 1px 3px rgba(239, 68, 68, 0.35);
}
.dispatch-container .cdp-highlight-step-chip {
display: inline-flex;
align-items: center;
padding: 3px 9px;
border-radius: 999px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #fff;
flex-shrink: 0;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.15);
}
.dispatch-container .cdp-highlight-title {
font-size: 15px;
font-weight: 700;
color: #0f172a;
line-height: 1.25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: -0.01em;
}
.dispatch-container .cdp-highlight-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.dispatch-container .cdp-highlight-pill {
display: inline-flex;
align-items: center;
padding: 3px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.01em;
font-variant-numeric: tabular-nums;
}
.dispatch-container .cdp-highlight-pill.is-bad {
background: rgba(239, 68, 68, 0.12);
color: #dc2626;
}
.dispatch-container .cdp-highlight-pill.is-good {
background: rgba(16, 185, 129, 0.12);
color: #16a34a;
}
/* Trips breakdown ------------------------------------------- */
.dispatch-container .cdp-trips {
display: flex;
flex-direction: column;
gap: 8px;
}
.dispatch-container .cdp-trip {
display: flex;
flex-direction: column;
gap: 6px;
padding: 10px 12px;
background: #fff;
border: 1px solid rgba(15, 23, 42, 0.07);
border-left: 3px solid rgba(99, 102, 241, 0.6);
border-radius: 10px;
}
.dispatch-container .cdp-trip-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.dispatch-container .cdp-trip-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
border-radius: 999px;
background: rgba(99, 102, 241, 0.12);
color: #4338ca;
}
.dispatch-container .cdp-trip-meta {
font-size: 11px;
font-weight: 700;
color: #64748b;
}
.dispatch-container .cdp-trip-stats {
display: flex;
flex-wrap: wrap;
gap: 6px 14px;
}
.dispatch-container .cdp-trip-stats span {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 700;
color: #475569;
}
.dispatch-container .cdp-trip-stats span svg {
font-size: 13px;
color: #94a3b8;
}
.dispatch-container .cdp-trip-stats span small {
font-size: 10px;
font-weight: 700;
color: #94a3b8;
}
.dispatch-container .cdp-trip-stats span.is-over {
color: #dc2626;
}
.dispatch-container .cdp-trip-stats span.is-over svg {
color: #dc2626;
}
/* Route sequence comparison -------------------------------- */
.dispatch-container .cdp-section-head-clickable {
cursor: pointer;
user-select: none;
}
.dispatch-container .cdp-section-head-clickable:hover .cdp-section-title {
color: #4338ca;
}
.dispatch-container .cdp-seq-status {
font-size: 12px;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
padding: 3px 10px;
border-radius: 999px;
flex-shrink: 0;
}
.dispatch-container .cdp-seq-status.is-good {
background: rgba(16, 185, 129, 0.12);
color: #16a34a;
}
.dispatch-container .cdp-seq-status.is-warn {
background: rgba(239, 68, 68, 0.12);
color: #dc2626;
}
.dispatch-container .cdp-seq-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #94a3b8;
transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
.dispatch-container .cdp-seq-toggle.is-open {
transform: rotate(180deg);
color: #4338ca;
}
.dispatch-container .cdp-seq {
display: flex;
flex-direction: column;
gap: 10px;
background: #fff;
border-radius: 10px;
border: 1px solid rgba(15, 23, 42, 0.07);
padding: 12px;
animation: cdp-seq-in 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes cdp-seq-in {
from {
opacity: 0;
transform: translateY(-3px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dispatch-container .cdp-seq-diffs {
list-style: none;
margin: 4px 0 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.dispatch-container .cdp-seq-diff {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
border-radius: 8px;
background: linear-gradient(180deg, #fef2f2 0%, #fff 100%);
border: 1px solid rgba(239, 68, 68, 0.2);
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.dispatch-container .cdp-seq-diff:hover {
transform: translateX(2px);
box-shadow: 0 3px 10px rgba(239, 68, 68, 0.12);
}
.dispatch-container .cdp-seq-diff.is-focused {
border-color: rgba(239, 68, 68, 0.6);
box-shadow: 0 3px 12px rgba(239, 68, 68, 0.2);
}
.dispatch-container .cdp-seq-diff-num {
width: 30px;
height: 30px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 14px;
font-weight: 800;
flex-shrink: 0;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.7),
0 1px 3px rgba(15, 23, 42, 0.15);
}
.dispatch-container .cdp-seq-diff-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.dispatch-container .cdp-seq-diff-title {
font-size: 14px;
font-weight: 700;
color: #0f172a;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .cdp-seq-diff-sub {
font-size: 13px;
font-weight: 600;
color: #64748b;
}
.dispatch-container .cdp-seq-diff-tag {
font-size: 13px;
font-weight: 800;
padding: 3px 10px;
border-radius: 999px;
background: rgba(239, 68, 68, 0.12);
color: #dc2626;
flex-shrink: 0;
}
.dispatch-container .cdp-seq-good {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 700;
color: #16a34a;
padding: 6px 10px;
background: rgba(16, 185, 129, 0.08);
border-radius: 8px;
}
.dispatch-container .cdp-seq-good svg {
font-size: 18px;
}
/* Cascade-aware sequence diff groups — when N consecutive shifted steps
share the same delta, they collapse into one summary card. Click expands
the card to reveal its individual diff rows, indented under the group. */
.dispatch-container .cdp-seq-diff.is-group {
background: linear-gradient(180deg, #eef2ff 0%, #fff 100%);
border-color: rgba(99, 102, 241, 0.3);
position: relative;
padding-right: 40px;
}
.dispatch-container .cdp-seq-diff.is-group:hover {
border-color: rgba(99, 102, 241, 0.55);
box-shadow: 0 3px 10px rgba(99, 102, 241, 0.15);
}
.dispatch-container .cdp-seq-diff.is-group.is-expanded {
border-color: rgba(99, 102, 241, 0.6);
background: linear-gradient(180deg, #e0e7ff 0%, #f5f7ff 100%);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.dispatch-container .cdp-seq-group-num {
position: relative;
width: 30px;
height: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.dispatch-container .cdp-seq-group-num-bg {
position: absolute;
inset: 0;
border-radius: 8px;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.7),
0 1px 3px rgba(15, 23, 42, 0.15);
}
.dispatch-container .cdp-seq-group-num-label {
position: relative;
color: #fff;
font-size: 13px;
font-weight: 800;
letter-spacing: -0.01em;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.dispatch-container .cdp-seq-group-delta {
display: inline-flex;
align-items: center;
padding: 2px 8px;
margin-left: 4px;
font-size: 13px;
font-weight: 800;
border-radius: 999px;
background: rgba(99, 102, 241, 0.15);
color: #4338ca;
vertical-align: 1px;
}
.dispatch-container .cdp-seq-group-toggle {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 6px;
color: #4338ca;
background: rgba(255, 255, 255, 0.7);
font-size: 18px;
transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
}
.dispatch-container .cdp-seq-group-toggle.is-open {
transform: translateY(-50%) rotate(180deg);
background: rgba(99, 102, 241, 0.18);
}
/* Children container — wraps a nested <ul> so list semantics stay valid
(we render an <li> wrapper around the nested list per group). */
.dispatch-container .cdp-seq-group-children-wrap {
list-style: none;
margin: -2px 0 0;
padding: 0;
background: rgba(99, 102, 241, 0.05);
border-left: 2px solid rgba(99, 102, 241, 0.35);
border-right: 1px solid rgba(99, 102, 241, 0.2);
border-bottom: 1px solid rgba(99, 102, 241, 0.2);
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
margin-left: 4px;
animation: cdp-seq-group-in 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes cdp-seq-group-in {
from {
opacity: 0;
transform: translateY(-3px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dispatch-container .cdp-seq-group-children {
list-style: none;
margin: 0;
padding: 8px 8px 8px 14px;
display: flex;
flex-direction: column;
gap: 4px;
}
/* Nested diff rows inside an expanded group — smaller, tighter, with a
subtle left rail so they read as belonging to the group above. */
.dispatch-container .cdp-seq-diff.is-nested {
padding: 6px 8px;
background: #fff;
border-color: rgba(99, 102, 241, 0.15);
border-radius: 8px;
}
.dispatch-container .cdp-seq-diff.is-nested .cdp-seq-diff-num {
width: 26px;
height: 26px;
font-size: 13px;
}
.dispatch-container .cdp-seq-diff.is-nested .cdp-seq-diff-title {
font-size: 13.5px;
}
.dispatch-container .cdp-seq-diff.is-nested .cdp-seq-diff-sub {
font-size: 12.5px;
}
.dispatch-container .cdp-seq-diff.is-nested .cdp-seq-diff-tag {
font-size: 12px;
padding: 2px 8px;
}
/* Sidebar Rider Card Est. Meters badge */
.dispatch-container .rcard-est-meters {
display: inline-flex;
align-items: center;
gap: 3px;
color: var(--accent, #3b82f6);
background: var(--accent-soft, rgba(59, 130, 246, 0.08));
padding: 2px 8px;
border-radius: 6px;
font-weight: 700;
font-size: 11px;
}
/* Sidebar Rider Route Detail Order Est. Meters chip */
.dispatch-container .zone-order-chip.est-meters-chip {
background: rgba(37, 99, 235, 0.08);
border-color: rgba(37, 99, 235, 0.2);
color: #2563eb;
}
/* Leaflet Map Popup Est. Meters chip */
.dispatch-container .pu-distance-chip.pu-est-meters {
background: rgba(37, 99, 235, 0.08);
border-color: rgba(37, 99, 235, 0.2);
}
.dispatch-container .pu-distance-chip.pu-est-meters .pu-distance-icon {
color: #2563eb;
}
.dispatch-container .pu-distance-chip.pu-est-meters .pu-distance-value {
color: #2563eb;
font-weight: 700;
}
/* ============================================================
Top-level Live / Analysis tabs (pinned inside header, left of profit)
============================================================ */
.dispatch-container #dispatch-top-tabs {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0;
background: transparent;
flex-shrink: 0;
}
.dispatch-container #dispatch-top-tabs.dtt-inline {
margin-right: 4px;
}
.dispatch-container .dtt-tab {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--bg);
font-size: 12px;
font-weight: 700;
color: var(--text-muted);
cursor: pointer;
line-height: 1;
transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.dispatch-container .dtt-tab:hover {
color: var(--text);
background: var(--bg-sub);
}
.dispatch-container .dtt-tab.active {
color: #fff;
background: var(--accent);
border-color: var(--accent);
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.25);
}
.dispatch-container .dtt-icon {
display: inline-flex;
align-items: center;
font-size: 14px;
}
/* ============================================================
Analysis panel (standalone Dispatch)
============================================================ */
.dispatch-container #dispatch-analysis {
flex: 1;
overflow: auto;
padding: 20px;
background: #f8fafc;
display: flex;
flex-direction: column;
gap: 18px;
}
.dispatch-container .da-intro-title {
font-size: 16px;
font-weight: 700;
color: var(--text);
margin-bottom: 4px;
}
.dispatch-container .da-intro-sub {
font-size: 12.5px;
color: var(--text-muted);
line-height: 1.5;
}
.dispatch-container .da-code {
background: #eef2ff;
color: #4f46e5;
padding: 1px 6px;
border-radius: 4px;
margin: 0 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11.5px;
}
.dispatch-container .da-picker-row {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
@media (max-width: 720px) {
.dispatch-container .da-picker-row {
grid-template-columns: 1fr;
}
}
.dispatch-container .da-picker {
text-align: left;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 14px;
background: #fff;
cursor: pointer;
transition: box-shadow 0.15s, transform 0.15s, border-color 0.15s;
font-family: inherit;
}
.dispatch-container .da-picker:hover:not(.is-loading) {
box-shadow: 0 4px 14px -6px rgba(15, 23, 42, 0.15);
transform: translateY(-1px);
}
.dispatch-container .da-picker.is-loading {
cursor: wait;
opacity: 0.7;
}
.dispatch-container .da-picker-head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 6px;
}
.dispatch-container .da-picker-badge {
width: 32px;
height: 32px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 13px;
}
.dispatch-container .da-picker-meta {
flex: 1;
min-width: 0;
}
.dispatch-container .da-picker-name {
font-size: 14px;
font-weight: 700;
color: var(--text);
line-height: 1.2;
}
.dispatch-container .da-picker-range {
font-size: 11px;
color: var(--text-muted);
}
.dispatch-container .da-picker-status {
flex-shrink: 0;
font-size: 10.5px;
font-weight: 700;
padding: 4px 8px;
border-radius: 12px;
}
.dispatch-container .da-picker-sub {
font-size: 11.5px;
color: var(--text-muted);
}
.dispatch-container .da-empty {
border: 1px dashed #cbd5e1;
border-radius: 12px;
padding: 40px;
text-align: center;
background: #fff;
color: #94a3b8;
font-size: 13px;
}
.dispatch-container .da-result-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 14px;
align-items: stretch;
}
.dispatch-container .da-result-card {
background: #fff;
border: 1px solid #e2e8f0;
border-top: 4px solid #6366f1;
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.dispatch-container .da-result-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.dispatch-container .da-result-title {
font-size: 15px;
font-weight: 700;
color: var(--text);
}
.dispatch-container .da-result-sub {
font-size: 11.5px;
color: var(--text-muted);
}
.dispatch-container .da-result-refresh {
border: none;
width: 30px;
height: 30px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 16px;
cursor: pointer;
transition: filter 0.15s;
}
.dispatch-container .da-result-refresh:hover {
filter: brightness(0.95);
}
.dispatch-container .da-result-refresh:disabled {
opacity: 0.5;
cursor: wait;
}
.dispatch-container .da-metric-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.dispatch-container .da-metric {
padding: 8px 10px;
border-radius: 8px;
background: #f8fafc;
border: 1px solid #eef2f6;
}
.dispatch-container .da-metric-label {
font-size: 10px;
font-weight: 700;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.4px;
margin-bottom: 2px;
}
.dispatch-container .da-metric-value {
font-size: 14px;
font-weight: 800;
color: var(--text);
line-height: 1.3;
}
.dispatch-container .da-riders-label {
font-size: 11px;
font-weight: 700;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.4px;
margin-bottom: 6px;
}
.dispatch-container .da-riders-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.dispatch-container .da-rider-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
border-radius: 6px;
background: #f8fafc;
border: 1px solid #eef2f6;
}
.dispatch-container .da-rider-name {
font-size: 12px;
font-weight: 600;
color: var(--text);
display: inline-flex;
align-items: center;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .da-rider-stat {
font-size: 11px;
color: var(--text-muted);
flex-shrink: 0;
}
.dispatch-container .da-error {
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
padding: 12px;
color: #991b1b;
display: flex;
flex-direction: column;
gap: 4px;
}
.dispatch-container .da-error-title {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12.5px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.3px;
color: #b91c1c;
}
.dispatch-container .da-error-msg {
font-size: 13px;
line-height: 1.5;
color: #7f1d1d;
}
.dispatch-container .da-error-meta {
font-size: 11px;
color: #b45353;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* ---- Single-batch detail view ---- */
.dispatch-container .da-detail {
display: flex;
flex-direction: column;
gap: 16px;
}
.dispatch-container .da-detail-head {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid #e2e8f0;
border-top: 4px solid #6366f1;
background: #f8fafc;
}
.dispatch-container .da-detail-title {
font-size: 16px;
font-weight: 800;
color: var(--text);
text-transform: capitalize;
}
.dispatch-container .da-detail-sub-inline {
font-size: 12.5px;
font-weight: 600;
color: var(--text-muted);
margin-left: 6px;
}
.dispatch-container .da-detail-sub {
font-size: 11.5px;
color: var(--text-muted);
margin-top: 2px;
}
.dispatch-container .da-section {
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.dispatch-container .da-section-label {
font-size: 12px;
font-weight: 800;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.4px;
}
.dispatch-container .da-section-count {
color: #94a3b8;
font-weight: 700;
margin-left: 4px;
}
.dispatch-container .da-metric-grid-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 720px) {
.dispatch-container .da-metric-grid-3 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
/* ---- Top recommendation ---- */
.dispatch-container .da-rec {
background: linear-gradient(135deg, #eef2ff 0%, #f0fdf4 100%);
border: 1px solid #c7d2fe;
border-radius: 10px;
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 8px;
}
.dispatch-container .da-rec.da-rec-empty {
background: #f8fafc;
border: 1px dashed #e2e8f0;
color: #64748b;
}
.dispatch-container .da-rec.da-rec-empty .da-rec-action {
color: #64748b;
text-transform: none;
font-weight: 500;
letter-spacing: 0;
}
.dispatch-container .da-rec-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.dispatch-container .da-rec-action {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 800;
color: #4338ca;
text-transform: capitalize;
}
.dispatch-container .da-rec-improve {
font-size: 11.5px;
font-weight: 700;
padding: 4px 10px;
border-radius: 14px;
}
.dispatch-container .da-rec-line {
font-size: 13px;
color: var(--text);
line-height: 1.5;
}
.dispatch-container .da-rec-desc {
font-size: 12.5px;
color: #475569;
line-height: 1.55;
padding: 8px 10px;
background: #fff;
border: 1px solid #e0e7ff;
border-radius: 8px;
}
.dispatch-container .da-rec-rules {
background: #fff;
border: 1px solid #e0e7ff;
border-radius: 8px;
padding: 8px 10px;
display: flex;
flex-direction: column;
gap: 4px;
}
.dispatch-container .da-rec-rules-head {
font-size: 11px;
font-weight: 700;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.3px;
margin-bottom: 2px;
}
.dispatch-container .da-rec-rule {
font-size: 12px;
color: #334155;
}
.dispatch-container .da-rec-rule code {
background: #f1f5f9;
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11.5px;
color: #1e293b;
}
.dispatch-container .da-rec-rule-why {
color: var(--text-muted);
font-size: 11.5px;
}
/* ---- Rider timelines ---- */
.dispatch-container .da-timeline-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 10px;
}
.dispatch-container .da-timeline-card {
border: 1px solid #eef2f6;
border-radius: 10px;
padding: 10px 12px;
background: #f8fafc;
display: flex;
flex-direction: column;
gap: 8px;
}
.dispatch-container .da-timeline-top {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.dispatch-container .da-timeline-name {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 700;
color: var(--text);
min-width: 0;
}
.dispatch-container .da-timeline-id {
font-size: 10.5px;
color: var(--text-muted);
font-weight: 600;
}
.dispatch-container .da-pill {
font-size: 10.5px;
font-weight: 800;
padding: 3px 9px;
border-radius: 12px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.dispatch-container .da-pill.is-active {
background: #dcfce7;
color: #166534;
}
.dispatch-container .da-pill.is-idle {
background: #fef3c7;
color: #92400e;
}
.dispatch-container .da-timeline-mid {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.dispatch-container .da-chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: #fff;
border: 1px solid #e2e8f0;
color: #334155;
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 12px;
}
/* ---- Substitution opportunities ---- */
.dispatch-container .da-sub-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.dispatch-container .da-sub-card {
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 12px;
background: #fff;
display: flex;
flex-direction: column;
gap: 8px;
}
.dispatch-container .da-sub-head {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.dispatch-container .da-sub-title {
font-size: 13px;
color: var(--text);
text-transform: capitalize;
}
.dispatch-container .da-sub-improve {
font-size: 11.5px;
font-weight: 800;
padding: 3px 10px;
border-radius: 12px;
}
.dispatch-container .da-sub-meta {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.dispatch-container .da-sub-relieved {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #166534;
background: #f0fdf4;
border: 1px solid #bbf7d0;
border-radius: 8px;
padding: 6px 10px;
}
.dispatch-container .da-sub-transfers {
background: #f8fafc;
border: 1px solid #eef2f6;
border-radius: 8px;
padding: 8px 10px;
display: flex;
flex-direction: column;
gap: 4px;
}
.dispatch-container .da-sub-transfers-head {
font-size: 11px;
font-weight: 700;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.3px;
margin-bottom: 2px;
}
.dispatch-container .da-transfer-row {
display: grid;
grid-template-columns: 80px 1fr auto auto;
align-items: center;
gap: 8px;
font-size: 11.5px;
color: var(--text);
padding: 4px 0;
border-bottom: 1px dashed #e2e8f0;
}
.dispatch-container .da-transfer-row:last-child {
border-bottom: none;
}
.dispatch-container .da-transfer-id {
font-weight: 800;
color: #1e293b;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.dispatch-container .da-transfer-from {
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .da-transfer-time {
color: #334155;
font-size: 11px;
}
.dispatch-container .da-transfer-imp {
font-size: 10.5px;
font-weight: 800;
padding: 2px 8px;
border-radius: 10px;
}