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

7219 lines
167 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
:root {
--bg: #ffffff;
--bg-sub: #f8fafc;
--bg-card: #ffffff;
--border: #e2e8f0;
--border-active: #3b82f6;
--text: #1e293b;
--text-muted: #64748b;
--accent: #3b82f6;
--accent-soft: rgba(59, 130, 246, 0.08);
--kitchen: #f59e0b;
--kitchen-soft: rgba(245, 158, 11, 0.1);
--success: #22c55e;
--shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.08);
}
.dispatch-container {
width: calc(100% + 48px);
height: calc(100vh - 88px);
margin: -24px;
display: flex;
flex-direction: column;
background: var(--bg);
color: var(--text);
font-family: 'Inter', -apple-system, sans-serif;
overflow: hidden;
position: relative;
}
/* Embedded mode: rendered inside a parent container (e.g. a Dialog),
so drop the negative margin and viewport-based sizing that assumes
the standalone /dispatch page is wrapped in MainCard's 24px padding. */
.dispatch-container.embedded {
width: 100%;
height: 100%;
margin: 0;
flex: 1;
min-height: 0;
}
.dispatch-container * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Header */
.dispatch-container #hdr {
height: 56px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
background: var(--bg);
border-bottom: 1px solid var(--border);
z-index: 10;
}
.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: 6px 14px;
border-radius: 8px;
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: 10px;
margin-left: auto;
margin-right: 12px;
min-width: 0;
flex-wrap: nowrap;
}
/* Tabs */
.dispatch-container #strat-row {
height: 48px;
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 var(--border);
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: 6px;
padding: 6px 12px;
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: 6px;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
padding: 6px 10px;
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); }
}
.dispatch-container .live-date-label {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.dispatch-container .live-date-label input[type="date"] {
font-family: inherit;
font-size: 13px;
font-weight: 600;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 7px 10px;
cursor: pointer;
outline: none;
transition: border-color 0.15s;
}
.dispatch-container .live-date-label input[type="date"]:hover,
.dispatch-container .live-date-label input[type="date"]:focus {
border-color: var(--accent);
}
/* ── Batch selector (live /dispatch only) ─────────────────────── */
.dispatch-container #batch-row {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 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: 8px;
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: 10px;
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: 5px 8px 5px 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 .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: 5px 10px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.04);
border: 1px dashed rgba(15, 23, 42, 0.18);
color: #475569;
font-size: 11px;
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: 6px;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--bg);
font-size: 12px;
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: 22px;
height: 18px;
padding: 0 6px;
border-radius: 999px;
background: var(--bg-sub);
color: var(--text);
font-size: 10px;
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;
}
/* 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% { 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); }
}
/* 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 {
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));
}
/* Live rider position bike */
.dispatch-container .rider-bike {
--rider-color: #475569;
position: relative;
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.dispatch-container .rider-bike-ring {
position: absolute;
inset: 0;
border-radius: 50%;
background: var(--rider-color);
border: 3px solid #fff;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35), 0 0 0 4px rgba(255, 255, 255, 0.25);
animation: rider-pulse 1.8s ease-in-out infinite;
}
.dispatch-container .rider-bike-svg {
position: relative;
width: 26px;
height: 26px;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
/* Bike stays upright — direction is conveyed by the route line. */
}
.dispatch-container .rider-bike-svg svg {
width: 100%;
height: 100%;
display: block;
}
.dispatch-container .rider-bike-progress {
position: absolute;
bottom: -16px;
left: 50%;
transform: translateX(-50%);
background: #0f172a;
color: #fff;
font-size: 10px;
font-weight: 800;
letter-spacing: 0.04em;
padding: 2px 6px;
border-radius: 999px;
white-space: nowrap;
border: 1.5px solid #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
z-index: 3;
}
@keyframes rider-pulse {
0%, 100% { box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35), 0 0 0 4px rgba(255, 255, 255, 0.25); }
50% { box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35), 0 0 0 8px rgba(255, 255, 255, 0.15); }
}
/* 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. Lives next to the synthetic
bike markers but uses a distinct visual so the operator can tell that this
one is real-GPS, not route-progress estimate. */
.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);
}
.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);
}
/* 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;
}
/* 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: 10px 14px;
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 overrides to align vertically centered since there are no ticks */
.dispatch-container .compare-timeline-track.is-planned .compare-step {
gap: 0;
padding: 0;
height: 32px;
}
.dispatch-container .compare-timeline-track.is-planned .compare-step-spacer {
margin-bottom: 0;
align-self: center;
}
/* 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 (left)" and "Actual GPS
(right)" entries — they now share the same per-step palette so the same
color appears on both maps for the same delivery. */
.dispatch-container .compare-legend-swatch.is-step-color {
height: 4px;
border-radius: 2px;
}
/* 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 */
.dispatch-container .cmark {
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;
}
.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: 11px;
font-weight: 800;
letter-spacing: 0.05em;
border-bottom: 1px solid #e2e8f0;
color: #64748b;
border-radius: 12px 12px 0 0;
/* Apply rounding here instead */
}
.dispatch-container .pu-rider {
padding: 12px 14px 4px;
font-size: 15px;
font-weight: 800;
}
.dispatch-container .pu-row {
display: flex;
justify-content: space-between;
padding: 4px 14px;
font-size: 12px;
color: #64748b;
}
.dispatch-container .pu-row:last-child {
padding-bottom: 0;
}
.dispatch-container .pu-row span:first-child {
color: #64748b;
font-weight: 500;
margin-right: 12px;
flex-shrink: 0;
}
.dispatch-container .pu-row span:last-child {
color: #1e293b;
font-weight: 600;
text-align: right;
word-break: break-word;
overflow-wrap: anywhere;
max-width: 65%;
}
/* 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: 460px;
}
.dispatch-container .dispatch-popup .leaflet-popup-content {
min-width: 460px;
}
/* --- 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 18px 16px;
}
.dispatch-container .dispatch-popup .pu-section {
margin-top: 12px;
}
.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 8px;
padding-bottom: 6px;
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 (scoped override of the earlier rules so paddings match
the new pu-body gutter) --- */
.dispatch-container .dispatch-popup .pu-timeline {
padding: 4px 0 4px 4px;
display: flex;
flex-direction: column;
gap: 6px;
position: relative;
}
.dispatch-container .dispatch-popup .pu-timeline::before {
left: 7px;
}
/* --- 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: 10px;
padding-top: 10px;
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: 10px;
width: 100%;
padding: 10px 12px;
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: 12px;
height: 12px;
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: 2px;
min-width: 0;
}
.dispatch-container .ri-rider-name {
font-size: 13px;
font-weight: 700;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dispatch-container .ri-rider-meta {
font-size: 11px;
font-weight: 500;
color: #64748b;
}
.dispatch-container .ri-rider-arrow {
color: #7b1fa2;
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-bottom: 12px;
border-bottom: 1px dashed rgba(123, 31, 162, 0.18);
margin-bottom: 14px;
}
.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 */
}
.dispatch-container .live-date-label {
font-size: 11px;
gap: 6px;
}
.dispatch-container .live-date-label span {
display: none; /* Hide the word 'Date' */
}
.dispatch-container .live-date-label input[type="date"] {
font-size: 12px;
padding: 4px 8px;
}
/* Strategy Tab Row adjustments */
.dispatch-container #strat-row {
height: 38px;
padding: 0 16px;
gap: 6px;
}
.dispatch-container .sbt {
padding: 6px 10px;
font-size: 11px;
gap: 5px;
}
.dispatch-container .sbt .sbt-icon {
width: 15px;
height: 15px;
font-size: 15px;
}
/* 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: 6px 10px;
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 overrides to align vertically centered since there are no ticks */
.dispatch-container .compare-timeline-track.is-planned .compare-step {
height: 24px;
}
.dispatch-container .compare-timeline-track.is-planned .compare-step-spacer {
margin-bottom: 0;
align-self: center;
}
/* 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: 34px;
}
.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: 6px 10px;
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 overrides to align vertically centered since there are no ticks */
.dispatch-container .compare-timeline-track.is-planned .compare-step {
height: 24px;
}
.dispatch-container .compare-timeline-track.is-planned .compare-step-spacer {
margin-bottom: 0;
align-self: center;
}
/* 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 — new layout (takes over the body when Compare
is open). 50% left column is a vertical stack: actual map on
top, planned map on bottom. 50% right column is a scrollable
data panel with deviations, per-step correctness, delivery
times and profit.
============================================================ */
.dispatch-container #body.compare-mode {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
gap: 12px;
padding: 12px;
background: linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
}
.dispatch-container #body.compare-mode #sidebar,
.dispatch-container #body.compare-mode .sidebar-toggle-tab {
display: none !important;
}
/* Explicit grid placement — the actual GPS map (#compare-map-wrap) takes
the TOP-LEFT cell, the planned route map (#map-wrap) takes the
BOTTOM-LEFT cell, and the data panel spans the entire right column.
We use grid-row/grid-column rather than grid-template-areas so the
placement can't be quietly broken by a later override of the parent's
areas list. */
.dispatch-container #body.compare-mode #compare-map-wrap {
grid-column: 1;
grid-row: 1;
flex: none;
min-width: 0;
min-height: 0;
margin: 0;
border-radius: 14px;
box-shadow: 0 6px 24px rgba(15, 23, 42, 0.08),
0 0 0 1px rgba(15, 23, 42, 0.06);
}
.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;
}
.dispatch-container #body.compare-mode .compare-planned-label {
background: rgba(255, 255, 255, 0.98);
}
/* 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: 26px;
height: 26px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 12px;
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: 4px;
}
.dispatch-container .cdp-dev-title {
font-size: 12px;
font-weight: 700;
color: #0f172a;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .cdp-dev-meta {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.dispatch-container .cdp-dev-chip {
display: inline-flex;
align-items: center;
padding: 2px 7px;
font-size: 10px;
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: 30px;
height: 30px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 13px;
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: 14px;
height: 14px;
border-radius: 50%;
background: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
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: 13px;
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: 2px 7px;
font-size: 9px;
font-weight: 800;
border-radius: 999px;
letter-spacing: 0.05em;
text-transform: uppercase;
flex-shrink: 0;
}
.dispatch-container .cdp-step-sub {
font-size: 11px;
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 10px;
margin-top: 2px;
}
.dispatch-container .cdp-step-delta {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 11px;
font-weight: 700;
color: #475569;
}
.dispatch-container .cdp-step-delta svg {
font-size: 13px;
color: #94a3b8;
}
.dispatch-container .cdp-step-delta small {
font-size: 10px;
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 with the
data panel below the two maps so the maps stay usable on tablets. */
@media (max-width: 1100px) {
.dispatch-container #body.compare-mode {
grid-template-columns: minmax(0, 1fr);
grid-template-rows: minmax(220px, 1fr) minmax(220px, 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;
}
}
/* 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;
}
/* KPI row ---------------------------------------------------- */
.dispatch-container .cdp-kpi-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
gap: 8px;
}
.dispatch-container .cdp-kpi {
background: #fff;
border: 1px solid rgba(15, 23, 42, 0.07);
border-radius: 9px;
padding: 8px 10px;
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.dispatch-container .cdp-kpi-label {
font-size: 9px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #94a3b8;
}
.dispatch-container .cdp-kpi-value {
font-size: 15px;
font-weight: 800;
color: #0f172a;
letter-spacing: -0.01em;
}
.dispatch-container .cdp-kpi-unit {
font-size: 10px;
font-weight: 700;
color: #94a3b8;
margin-left: 2px;
}
/* Highlights (best / worst) --------------------------------- */
.dispatch-container .cdp-highlights {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.dispatch-container .cdp-highlight {
display: flex;
gap: 10px;
padding: 10px;
border-radius: 10px;
background: #fff;
border: 1px solid rgba(15, 23, 42, 0.07);
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.dispatch-container .cdp-highlight:hover {
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
}
.dispatch-container .cdp-highlight.is-best {
background: linear-gradient(180deg, #ecfdf5 0%, #fff 100%);
border-color: rgba(16, 185, 129, 0.3);
}
.dispatch-container .cdp-highlight.is-worst {
background: linear-gradient(180deg, #fef2f2 0%, #fff 100%);
border-color: rgba(239, 68, 68, 0.3);
}
.dispatch-container .cdp-highlight-num {
width: 28px;
height: 28px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 12px;
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-highlight-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.dispatch-container .cdp-highlight-label {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 9px;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.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-title {
font-size: 12px;
font-weight: 700;
color: #0f172a;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .cdp-highlight-meta {
font-size: 11px;
font-weight: 600;
color: #64748b;
}
/* 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: 10px;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
padding: 2px 8px;
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: 26px;
height: 26px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 12px;
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: 12px;
font-weight: 700;
color: #0f172a;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dispatch-container .cdp-seq-diff-sub {
font-size: 11px;
font-weight: 600;
color: #64748b;
}
.dispatch-container .cdp-seq-diff-tag {
font-size: 11px;
font-weight: 800;
padding: 3px 8px;
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: 12px;
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: 16px;
}
/* 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: 11px;
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: 1px 7px;
margin-left: 4px;
font-size: 11px;
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: 22px;
height: 22px;
font-size: 11px;
}
.dispatch-container .cdp-seq-diff.is-nested .cdp-seq-diff-title {
font-size: 11.5px;
}
.dispatch-container .cdp-seq-diff.is-nested .cdp-seq-diff-sub {
font-size: 10.5px;
}
.dispatch-container .cdp-seq-diff.is-nested .cdp-seq-diff-tag {
font-size: 10px;
padding: 2px 6px;
}