9848 lines
220 KiB
CSS
9848 lines
220 KiB
CSS
: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;
|
||
} |