Delivery — Map Route
A mobile-first live tracking component that draws a stylized CSS-grid map with roads, an SVG route polyline from pickup to drop-off, and an animated driver marker that glides along the path. A play, pause, and restart control plus 1x, 2x, and 4x speeds drive the trip, while a big countdown ETA, distance-remaining, arrival clock, and a progress bar update in real time. Pickup and drop pins, a recenter button, and a status step tracker round out the screen with delivered and pending pills.
MCP
Code
:root {
--brand: #ff5a2c;
--brand-d: #e0461d;
--ink: #16181d;
--ink-2: #3b3f4a;
--muted: #71757f;
--bg: #f4f5f7;
--surface: #ffffff;
--line: rgba(22, 24, 29, 0.1);
--ok: #1f9d62;
--warn: #e89422;
--danger: #d4493e;
--track: #5b8def;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-sm: 0 1px 2px rgba(22, 24, 29, 0.08);
--sh-md: 0 6px 18px rgba(22, 24, 29, 0.1);
--sh-lg: 0 18px 40px rgba(22, 24, 29, 0.16);
--map-bg: #e8edf3;
--map-land: #eef2f6;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: start center;
padding: 28px 16px 48px;
background:
radial-gradient(1200px 500px at 80% -10%, rgba(91, 141, 239, 0.12), transparent 60%),
radial-gradient(900px 500px at -10% 110%, rgba(255, 90, 44, 0.1), transparent 60%),
var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
font-size: 15px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.app {
width: 100%;
max-width: 420px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-lg);
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Topbar */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 18px;
border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
.brand__mark {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 12px;
color: #fff;
background: linear-gradient(140deg, var(--brand), var(--brand-d));
box-shadow: 0 6px 14px rgba(255, 90, 44, 0.35);
flex: none;
}
.brand h1 { margin: 0; font-size: 16px; font-weight: 800; letter-spacing: -0.01em; }
.brand__sub { margin: 1px 0 0; font-size: 12.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.status {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12.5px;
font-weight: 600;
color: var(--ok);
background: rgba(31, 157, 98, 0.1);
border: 1px solid rgba(31, 157, 98, 0.22);
padding: 6px 11px;
border-radius: 999px;
flex: none;
}
.status__dot {
width: 8px; height: 8px; border-radius: 50%; background: var(--ok);
box-shadow: 0 0 0 0 rgba(31, 157, 98, 0.5);
animation: ping 1.8s ease-out infinite;
}
@keyframes ping {
0% { box-shadow: 0 0 0 0 rgba(31, 157, 98, 0.5); }
70%, 100% { box-shadow: 0 0 0 7px rgba(31, 157, 98, 0); }
}
/* Map */
.map-wrap { padding: 14px 14px 0; }
.map {
position: relative;
height: 300px;
border-radius: var(--r-md);
overflow: hidden;
background:
linear-gradient(0deg, rgba(91, 141, 239, 0.05), rgba(91, 141, 239, 0.05)),
var(--map-bg);
border: 1px solid var(--line);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.4);
}
.map__grid {
position: absolute;
inset: -2px;
background-image:
linear-gradient(rgba(22, 24, 29, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(22, 24, 29, 0.05) 1px, transparent 1px);
background-size: 34px 34px;
}
.map__roads { position: absolute; inset: 0; }
.road { position: absolute; background: var(--map-land); box-shadow: 0 0 0 1px rgba(22,24,29,0.04); }
.road--h { left: -4%; right: -4%; height: 12px; transform: translateY(-50%); }
.road--v { top: -4%; bottom: -4%; width: 12px; transform: translateX(-50%); }
.map__svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.route { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.route--shadow { stroke: rgba(22, 24, 29, 0.14); stroke-width: 9; transform: translateY(2px); }
.route--base { stroke: var(--track); stroke-width: 6; opacity: 0.4; }
.route--done { stroke: var(--brand); stroke-width: 6; }
.pin {
position: absolute;
transform: translate(-50%, -100%);
display: flex;
flex-direction: column;
align-items: center;
pointer-events: none;
z-index: 3;
}
.pin__head {
width: 16px; height: 16px;
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
box-shadow: 0 4px 8px rgba(22, 24, 29, 0.3);
border: 2px solid #fff;
}
.pin--pickup .pin__head { background: var(--ok); }
.pin--drop .pin__head { background: var(--brand); }
.pin__label {
margin-top: 6px;
font-size: 10.5px;
font-weight: 700;
color: var(--ink);
background: rgba(255, 255, 255, 0.92);
border: 1px solid var(--line);
padding: 3px 7px;
border-radius: 999px;
white-space: nowrap;
box-shadow: var(--sh-sm);
}
.driver {
position: absolute;
transform: translate(-50%, -50%);
z-index: 4;
transition: left 0.08s linear, top 0.08s linear;
}
.driver__pulse {
position: absolute;
inset: 50%;
width: 38px; height: 38px;
margin: -19px 0 0 -19px;
border-radius: 50%;
background: rgba(255, 90, 44, 0.22);
animation: drvpulse 1.6s ease-out infinite;
}
@keyframes drvpulse {
0% { transform: scale(0.5); opacity: 0.8; }
100% { transform: scale(1.4); opacity: 0; }
}
.driver__dot {
position: relative;
display: grid;
place-items: center;
width: 30px; height: 30px;
border-radius: 50%;
color: #fff;
background: linear-gradient(140deg, var(--brand), var(--brand-d));
border: 3px solid #fff;
box-shadow: 0 6px 14px rgba(255, 90, 44, 0.45);
}
.recenter {
position: absolute;
right: 12px;
bottom: 12px;
width: 40px; height: 40px;
display: grid;
place-items: center;
border-radius: 50%;
border: 1px solid var(--line);
background: var(--surface);
color: var(--ink-2);
cursor: pointer;
box-shadow: var(--sh-md);
z-index: 5;
transition: transform 0.12s ease, color 0.12s ease;
}
.recenter:hover { color: var(--brand); }
.recenter:active { transform: scale(0.92); }
.recenter:focus-visible { outline: 3px solid rgba(91, 141, 239, 0.45); outline-offset: 2px; }
.recenter.is-spin svg { animation: spin 0.5s ease; }
@keyframes spin { to { transform: rotate(360deg); } }
.map__legend {
position: absolute;
left: 12px;
bottom: 12px;
display: flex;
gap: 10px;
font-size: 10.5px;
font-weight: 600;
color: var(--ink-2);
background: rgba(255, 255, 255, 0.9);
border: 1px solid var(--line);
padding: 5px 9px;
border-radius: 999px;
box-shadow: var(--sh-sm);
z-index: 5;
}
.map__legend span { display: inline-flex; align-items: center; gap: 5px; }
.lg { width: 12px; height: 4px; border-radius: 2px; }
.lg--done { background: var(--brand); }
.lg--base { background: var(--track); opacity: 0.5; }
/* ETA */
.eta {
display: flex;
align-items: stretch;
gap: 14px;
padding: 16px 18px 8px;
}
.eta__main {
display: flex;
flex-direction: column;
justify-content: center;
padding-right: 14px;
border-right: 1px solid var(--line);
}
.eta__label { font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }
.eta__time {
font-size: 40px;
font-weight: 800;
line-height: 1;
letter-spacing: -0.03em;
color: var(--ink);
display: flex;
align-items: baseline;
gap: 4px;
}
.eta__time small { font-size: 15px; font-weight: 700; color: var(--muted); }
.eta__meta { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 10px; }
.eta__row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.eta__dist { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.pill {
display: inline-flex;
align-items: center;
font-size: 11.5px;
font-weight: 700;
padding: 4px 9px;
border-radius: 999px;
border: 1px solid transparent;
white-space: nowrap;
}
.pill--ok { color: var(--ok); background: rgba(31, 157, 98, 0.1); border-color: rgba(31, 157, 98, 0.22); }
.pill--track { color: var(--track); background: rgba(91, 141, 239, 0.12); border-color: rgba(91, 141, 239, 0.25); }
.pill--done { color: var(--ok); background: rgba(31, 157, 98, 0.1); border-color: rgba(31, 157, 98, 0.22); }
.pill--pending { color: var(--muted); background: rgba(113, 117, 127, 0.1); border-color: rgba(113, 117, 127, 0.2); }
.pill--warn { color: var(--brand-d); background: rgba(232, 148, 34, 0.14); border-color: rgba(232, 148, 34, 0.3); }
.progress {
height: 8px;
border-radius: 999px;
background: rgba(91, 141, 239, 0.16);
overflow: hidden;
}
.progress__bar {
display: block;
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--brand), var(--brand-d));
transition: width 0.1s linear;
}
/* Controls */
.controls {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 18px 14px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font: inherit;
font-weight: 700;
cursor: pointer;
border-radius: var(--r-sm);
border: 1px solid transparent;
transition: transform 0.1s ease, background 0.15s ease, box-shadow 0.15s ease;
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid rgba(91, 141, 239, 0.45); outline-offset: 2px; }
.btn--primary {
flex: 1;
height: 46px;
color: #fff;
background: linear-gradient(140deg, var(--brand), var(--brand-d));
box-shadow: 0 8px 18px rgba(255, 90, 44, 0.35);
}
.btn--primary:hover { box-shadow: 0 10px 22px rgba(255, 90, 44, 0.45); }
.btn--ghost {
width: 46px;
height: 46px;
color: var(--ink-2);
background: var(--surface);
border-color: var(--line);
}
.btn--ghost:hover { color: var(--brand); border-color: rgba(255, 90, 44, 0.4); }
.ic-pause { display: none; }
.btn--primary.is-playing .ic-play { display: none; }
.btn--primary.is-playing .ic-pause { display: inline; }
.speed {
display: inline-flex;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 3px;
gap: 2px;
}
.speed__btn {
font: inherit;
font-size: 12.5px;
font-weight: 700;
color: var(--muted);
background: transparent;
border: none;
border-radius: 6px;
padding: 7px 10px;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease;
}
.speed__btn[aria-pressed="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--sh-sm); }
.speed__btn:focus-visible { outline: 2px solid rgba(91, 141, 239, 0.5); outline-offset: 1px; }
/* Steps */
.stops { padding: 4px 18px 20px; border-top: 1px solid var(--line); margin-top: 4px; }
.steps { list-style: none; margin: 6px 0 0; padding: 0; }
.step {
position: relative;
display: flex;
align-items: center;
gap: 14px;
padding: 12px 0;
}
.step + .step::before {
content: "";
position: absolute;
left: 7px;
top: -22px;
height: 22px;
width: 2px;
background: var(--line);
}
.step.is-done + .step::before,
.step.is-done::after { background: var(--ok); }
.step__dot {
position: relative;
z-index: 1;
flex: none;
width: 16px; height: 16px;
border-radius: 50%;
background: var(--surface);
border: 3px solid var(--line);
}
.step.is-done .step__dot { background: var(--ok); border-color: var(--ok); }
.step.is-active .step__dot {
border-color: var(--brand);
box-shadow: 0 0 0 4px rgba(255, 90, 44, 0.16);
animation: stepblink 1.6s ease-in-out infinite;
}
@keyframes stepblink {
0%, 100% { box-shadow: 0 0 0 4px rgba(255, 90, 44, 0.16); }
50% { box-shadow: 0 0 0 7px rgba(255, 90, 44, 0.06); }
}
.step__body { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.step__title { font-size: 13.5px; font-weight: 700; color: var(--ink); }
.step.is-done:not(.is-active) .step__title { color: var(--ink-2); }
.step__sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Toast */
.toast-host {
position: fixed;
left: 50%;
bottom: 22px;
transform: translateX(-50%);
display: flex;
flex-direction: column;
gap: 8px;
z-index: 50;
width: max-content;
max-width: 92vw;
pointer-events: none;
}
.toast {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
font-weight: 600;
color: #fff;
background: var(--ink);
padding: 10px 14px;
border-radius: 999px;
box-shadow: var(--sh-lg);
opacity: 0;
transform: translateY(8px);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast.is-in { opacity: 1; transform: translateY(0); }
.toast::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--brand); }
.toast--ok::before { background: var(--ok); }
@media (max-width: 520px) {
body { padding: 0; place-items: stretch; }
.app { max-width: none; min-height: 100vh; border: none; border-radius: 0; box-shadow: none; }
.map { height: 44vh; min-height: 260px; }
.eta__time { font-size: 36px; }
}
@media (prefers-reduced-motion: reduce) {
.status__dot, .driver__pulse, .step.is-active .step__dot, .recenter.is-spin svg { animation: none; }
.driver { transition: none; }
}(function () {
"use strict";
var svg = document.getElementById("route-svg");
var routeBase = document.getElementById("route-base");
var routeDone = document.getElementById("route-done");
var driver = document.getElementById("driver");
var pinPickup = document.getElementById("pin-pickup");
var pinDrop = document.getElementById("pin-drop");
var etaTime = document.getElementById("eta-time");
var etaDist = document.getElementById("eta-dist");
var etaClock = document.getElementById("eta-clock");
var progress = document.getElementById("progress");
var progressBar = document.getElementById("progress-bar");
var stepLive = document.getElementById("step-live");
var steps = Array.prototype.slice.call(document.querySelectorAll(".step"));
var playBtn = document.getElementById("play");
var playLabel = document.getElementById("play-label");
var resetBtn = document.getElementById("reset");
var recenterBtn = document.getElementById("recenter");
var speedBtns = Array.prototype.slice.call(document.querySelectorAll(".speed__btn"));
var toastHost = document.getElementById("toast-host");
// Trip model
var TOTAL_MIN = 17; // total trip minutes when t = 0..1
var TOTAL_MI = 4.1; // total route distance
var DURATION_MS = 26000; // base wall-clock for full route at 1x
var t = 0.42; // normalized progress along path (matches markup)
var speed = 1;
var playing = false;
var rafId = null;
var lastTs = 0;
var pathLen = 0;
function clamp01(v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
// --- Toast helper ---
function toast(msg, kind) {
var el = document.createElement("div");
el.className = "toast" + (kind === "ok" ? " toast--ok" : "");
el.textContent = msg;
toastHost.appendChild(el);
requestAnimationFrame(function () { el.classList.add("is-in"); });
setTimeout(function () {
el.classList.remove("is-in");
setTimeout(function () { el.remove(); }, 220);
}, 2200);
}
// --- Geometry: map SVG userspace point to map pixel coords ---
function svgPointToMap(pt) {
var rect = svg.getBoundingClientRect();
return {
x: (pt.x / 600) * rect.width,
y: (pt.y / 400) * rect.height
};
}
function placeAt(el, pt, anchorBottom) {
var p = svgPointToMap(pt);
el.style.left = p.x + "px";
el.style.top = p.y + "px";
}
function pointAtT(frac) {
return routeBase.getPointAtLength(pathLen * clamp01(frac));
}
// --- Render driver + traveled overlay for current t ---
function render() {
pathLen = routeBase.getTotalLength();
var done = pathLen * t;
routeDone.style.strokeDasharray = pathLen + " " + pathLen;
routeDone.style.strokeDashoffset = (pathLen - done);
placeAt(driver, pointAtT(t));
placeAt(pinPickup, routeBase.getPointAtLength(0));
placeAt(pinDrop, routeBase.getPointAtLength(pathLen));
var remainMin = Math.max(0, Math.round(TOTAL_MIN * (1 - t)));
var remainMi = Math.max(0, TOTAL_MI * (1 - t));
etaTime.innerHTML = (t >= 1 ? "0" : remainMin) + "<small>min</small>";
etaDist.textContent = (t >= 1 ? "Arrived" : remainMi.toFixed(1) + " mi left");
var arrive = new Date(Date.now() + remainMin * 60000);
etaClock.textContent = "ETA " + fmtClock(arrive);
var pct = Math.round(t * 100);
progressBar.style.width = pct + "%";
progress.setAttribute("aria-valuenow", String(pct));
updateSteps();
}
function fmtClock(d) {
var h = d.getHours();
var m = d.getMinutes();
var ap = h >= 12 ? "PM" : "AM";
h = h % 12; if (h === 0) h = 12;
return h + ":" + (m < 10 ? "0" + m : m) + " " + ap;
}
function updateSteps() {
// step 0 (pickup) always done; step 1 active until t>=1; step 2 done at arrival
steps.forEach(function (s) { s.classList.remove("is-active"); });
if (t >= 1) {
steps[1].classList.add("is-done");
steps[2].classList.add("is-done", "is-active");
var p = steps[2].querySelector(".pill");
p.textContent = "Delivered";
p.className = "pill pill--done";
stepLive.textContent = "Complete";
stepLive.className = "pill pill--done";
} else {
steps[1].classList.add("is-active");
steps[2].classList.remove("is-done", "is-active");
var p2 = steps[2].querySelector(".pill");
p2.textContent = "Pending";
p2.className = "pill pill--pending";
stepLive.textContent = "In transit";
stepLive.className = "pill pill--track";
}
}
// --- Animation loop ---
var arrivedFired = false;
function tick(ts) {
if (!playing) return;
if (!lastTs) lastTs = ts;
var dt = ts - lastTs;
lastTs = ts;
t = clamp01(t + (dt / DURATION_MS) * speed);
render();
if (t >= 1) {
if (!arrivedFired) {
arrivedFired = true;
toast("Driver arrived at 1140 Larkspur Ave", "ok");
}
stop();
return;
}
rafId = requestAnimationFrame(tick);
}
function start() {
if (t >= 1) { restart(); }
playing = true;
arrivedFired = false;
lastTs = 0;
playBtn.classList.add("is-playing");
playBtn.setAttribute("aria-pressed", "true");
playLabel.textContent = "Pause trip";
rafId = requestAnimationFrame(tick);
}
function stop() {
playing = false;
if (rafId) cancelAnimationFrame(rafId);
rafId = null;
playBtn.classList.remove("is-playing");
playBtn.setAttribute("aria-pressed", "false");
playLabel.textContent = t >= 1 ? "Trip complete" : "Resume trip";
}
function restart() {
t = 0;
arrivedFired = false;
render();
playLabel.textContent = "Start trip";
}
// --- Recenter (subtle nudge + spin feedback) ---
function recenter() {
recenterBtn.classList.remove("is-spin");
void recenterBtn.offsetWidth;
recenterBtn.classList.add("is-spin");
render();
toast("Recentered on driver");
}
// --- Events ---
playBtn.addEventListener("click", function () {
if (playing) { stop(); } else { start(); }
});
resetBtn.addEventListener("click", function () {
stop();
restart();
toast("Trip restarted");
});
recenterBtn.addEventListener("click", recenter);
speedBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
speed = parseFloat(btn.getAttribute("data-speed"));
speedBtns.forEach(function (b) { b.setAttribute("aria-pressed", "false"); });
btn.setAttribute("aria-pressed", "true");
toast("Playback " + speed + "×");
});
});
// Recompute positions on resize (path is percentage-mapped)
var rt;
window.addEventListener("resize", function () {
clearTimeout(rt);
rt = setTimeout(render, 80);
});
// Initial paint (wait a frame so map has layout)
requestAnimationFrame(function () {
requestAnimationFrame(render);
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Delivery — Map Route</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="app" aria-labelledby="app-title">
<header class="topbar">
<div class="brand">
<span class="brand__mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l18-7-7 18-2.5-7.5L3 11z"/></svg>
</span>
<div>
<h1 id="app-title">RushPin Live</h1>
<p class="brand__sub">Order #RP-48217 · Marisol Vega</p>
</div>
</div>
<div class="status" role="status">
<span class="status__dot" aria-hidden="true"></span>
<span>On the way</span>
</div>
</header>
<section class="map-wrap" aria-label="Live route map">
<div class="map" id="map">
<div class="map__grid" aria-hidden="true"></div>
<div class="map__roads" aria-hidden="true">
<span class="road road--h" style="top:22%"></span>
<span class="road road--h" style="top:58%"></span>
<span class="road road--h" style="top:82%"></span>
<span class="road road--v" style="left:18%"></span>
<span class="road road--v" style="left:52%"></span>
<span class="road road--v" style="left:80%"></span>
</div>
<svg class="map__svg" id="route-svg" viewBox="0 0 600 400" preserveAspectRatio="none" aria-hidden="true">
<path id="route-shadow" class="route route--shadow"
d="M70 330 L70 250 Q70 230 110 230 L250 230 Q300 230 300 180 L300 110 Q300 70 360 70 L520 70" />
<path id="route-base" class="route route--base"
d="M70 330 L70 250 Q70 230 110 230 L250 230 Q300 230 300 180 L300 110 Q300 70 360 70 L520 70" />
<path id="route-done" class="route route--done"
d="M70 330 L70 250 Q70 230 110 230 L250 230 Q300 230 300 180 L300 110 Q300 70 360 70 L520 70" />
</svg>
<div class="pin pin--pickup" id="pin-pickup">
<span class="pin__head" aria-hidden="true"></span>
<span class="pin__label">Pickup · Ember & Oak</span>
</div>
<div class="pin pin--drop" id="pin-drop">
<span class="pin__head" aria-hidden="true"></span>
<span class="pin__label">Drop · 1140 Larkspur Ave</span>
</div>
<div class="driver" id="driver" aria-label="Driver location">
<span class="driver__pulse" aria-hidden="true"></span>
<span class="driver__dot" aria-hidden="true">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l18-7-7 18-2.5-7.5L3 11z"/></svg>
</span>
</div>
<button class="recenter" id="recenter" type="button" aria-label="Recenter map on driver">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>
</button>
<div class="map__legend" aria-hidden="true">
<span><i class="lg lg--done"></i>Traveled</span>
<span><i class="lg lg--base"></i>Remaining</span>
</div>
</div>
</section>
<section class="eta" aria-label="Estimated time of arrival">
<div class="eta__main">
<span class="eta__label">Arriving in</span>
<strong class="eta__time" id="eta-time">14<small>min</small></strong>
</div>
<div class="eta__meta">
<div class="eta__row">
<span class="eta__dist" id="eta-dist">2.4 mi left</span>
<span class="pill pill--ok" id="eta-clock">ETA 6:42 PM</span>
</div>
<div class="progress" role="progressbar" aria-label="Route progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" id="progress">
<span class="progress__bar" id="progress-bar"></span>
</div>
</div>
</section>
<section class="controls" aria-label="Playback controls">
<button class="btn btn--primary" id="play" type="button" aria-pressed="false">
<svg class="ic-play" viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
<svg class="ic-pause" viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M6 5h4v14H6zM14 5h4v14h-4z"/></svg>
<span id="play-label">Start trip</span>
</button>
<div class="speed" role="group" aria-label="Playback speed">
<button class="speed__btn" data-speed="1" type="button" aria-pressed="true">1×</button>
<button class="speed__btn" data-speed="2" type="button" aria-pressed="false">2×</button>
<button class="speed__btn" data-speed="4" type="button" aria-pressed="false">4×</button>
</div>
<button class="btn btn--ghost" id="reset" type="button" aria-label="Restart trip">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 109-9 9 9 0 00-7 3.3"/><path d="M3 3v4h4"/></svg>
</button>
</section>
<section class="stops" aria-label="Route stops">
<ol class="steps" id="steps">
<li class="step is-done" data-at="0">
<span class="step__dot" aria-hidden="true"></span>
<div class="step__body">
<span class="step__title">Order picked up</span>
<span class="step__sub">Ember & Oak Kitchen · 6:18 PM</span>
</div>
<span class="pill pill--done">Done</span>
</li>
<li class="step is-active" data-at="0.42">
<span class="step__dot" aria-hidden="true"></span>
<div class="step__body">
<span class="step__title">En route to drop-off</span>
<span class="step__sub">Crossing Larkspur Ave</span>
</div>
<span class="pill pill--track" id="step-live">In transit</span>
</li>
<li class="step" data-at="1">
<span class="step__dot" aria-hidden="true"></span>
<div class="step__body">
<span class="step__title">Hand to customer</span>
<span class="step__sub">1140 Larkspur Ave, Apt 5C</span>
</div>
<span class="pill pill--pending">Pending</span>
</li>
</ol>
</section>
</main>
<div class="toast-host" id="toast-host" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Map Route
A self-contained live-tracking card for a delivery in progress. The map is drawn entirely in CSS — a faint coordinate grid with lighter road bands underneath an SVG route polyline that curves from the pickup pin at Ember & Oak to the drop-off pin on Larkspur Ave. A traveled-overlay stroke fills the route in delivery orange as the driver marker, with its pulsing halo, animates smoothly along the path using getPointAtLength.
The big ETA block counts down minutes-to-arrival and keeps the distance-remaining, a recalculated arrival clock, and a slim progress bar in sync on every frame. Playback controls let you start, pause, resume, and restart the trip, and the 1x / 2x / 4x speed group rescales the animation in place. A floating recenter button nudges the view back onto the driver with a spin of feedback, and every action raises a small toast.
Below the map, a vertical step tracker shows the order picked up, the en-route leg, and the final hand-off, swapping its in-transit and pending pills to a delivered state the moment the marker reaches the door. It is vanilla JavaScript with no dependencies and holds its layout from desktop down to a 360px phone.
Illustrative UI only — fictional brand, not a real delivery service.