Auto — Repair Status Tracker
A customer-facing repair status tracker styled like a phone chat with the shop. It pairs a five-stage step tracker (checked in, diagnosing, awaiting approval, in repair, ready) with an animated progress bar, a vehicle and ETA panel showing plate, VIN, odometer and bay, plus a live text-message feed. New shop updates pulse in, an add-on approval banner lets the customer approve extra work, and the work-order total updates live.
MCP
Code
:root {
--garage: #141518;
--garage-2: #1f2127;
--steel: #5b6470;
--steel-l: #8a929d;
--orange: #ff6a13;
--orange-d: #e2540a;
--orange-50: #fff0e6;
--ink: #16181c;
--ink-2: #3b4049;
--muted: #737a85;
--bg: #f3f4f6;
--surface: #ffffff;
--line: rgba(20, 21, 24, 0.1);
--line-2: rgba(20, 21, 24, 0.18);
--ok: #2f9e6f;
--warn: #e0962a;
--danger: #d4493e;
--waiting: #e0962a;
--inprogress: #2b7fff;
--done: #2f9e6f;
--hold: #d4493e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 18px;
--shadow: 0 1px 2px rgba(20, 21, 24, 0.06), 0 8px 24px rgba(20, 21, 24, 0.06);
--shadow-lg: 0 12px 40px rgba(20, 21, 24, 0.12);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.mono { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
h1, h2 { margin: 0; }
.app {
max-width: 980px;
margin: 0 auto;
padding: 18px;
}
/* ---------- Topbar ---------- */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
background: var(--garage);
background-image: linear-gradient(180deg, var(--garage-2), var(--garage));
color: #fff;
padding: 14px 18px;
border-radius: var(--r-lg);
box-shadow: var(--shadow);
}
.brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
.brand-mark {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 11px;
background: var(--orange);
color: #fff;
flex: none;
box-shadow: 0 6px 16px rgba(255, 106, 19, 0.4);
}
.brand-txt { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.brand-txt strong { font-size: 15px; font-weight: 700; }
.brand-txt span { font-size: 12px; color: var(--steel-l); }
.topbar-right { display: flex; align-items: center; gap: 10px; }
.ro-pill {
font: 600 12px/1 "Inter", sans-serif;
font-variant-numeric: tabular-nums;
color: #fff;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.16);
padding: 7px 10px;
border-radius: 999px;
}
.icon-btn {
display: grid;
place-items: center;
width: 36px;
height: 36px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.16);
background: rgba(255, 255, 255, 0.06);
color: #fff;
cursor: pointer;
transition: background 0.15s, transform 0.4s;
}
.icon-btn:hover { background: rgba(255, 255, 255, 0.14); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn.spinning svg { animation: spin 0.6s linear; }
@keyframes spin { to { transform: rotate(360deg); } }
/* ---------- Layout ---------- */
.layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
margin-top: 14px;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow);
padding: 18px;
}
.vehicle { grid-column: 1 / -1; display: grid; grid-template-columns: auto 1fr auto; gap: 18px; align-items: stretch; }
.tracker { grid-column: 1 / -1; }
.feed { grid-column: 1 / 2; }
.order { grid-column: 2 / 3; }
.card-hd {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 14px;
}
.card-hd h2 { font-size: 14px; font-weight: 700; letter-spacing: 0.01em; }
/* ---------- Vehicle card ---------- */
.veh-photo {
position: relative;
width: 150px;
border-radius: var(--r-md);
background:
radial-gradient(120% 120% at 20% 0%, rgba(255, 255, 255, 0.12), transparent 60%),
linear-gradient(150deg, var(--garage-2), var(--garage));
color: var(--steel-l);
display: grid;
place-items: center;
overflow: hidden;
}
.veh-photo::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(115deg, transparent 40%, rgba(255, 106, 19, 0.18) 50%, transparent 60%);
}
.veh-badge {
position: absolute;
top: 10px;
left: 10px;
font: 700 11px/1 "Inter", sans-serif;
font-variant-numeric: tabular-nums;
background: var(--orange);
color: #fff;
padding: 4px 7px;
border-radius: 6px;
}
.veh-icon { position: relative; z-index: 1; }
.veh-info { display: flex; flex-direction: column; min-width: 0; }
.veh-info h1 { font-size: 22px; font-weight: 800; letter-spacing: -0.02em; }
.veh-sub { margin: 2px 0 14px; color: var(--muted); font-size: 13px; }
.veh-meta {
margin: auto 0 0;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.veh-meta dt { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0; }
.veh-meta dd { margin: 2px 0 0; font-size: 14px; font-weight: 600; color: var(--ink); }
.eta {
border-left: 1px solid var(--line);
padding-left: 18px;
display: flex;
flex-direction: column;
justify-content: center;
min-width: 168px;
}
.eta-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }
.eta-time { font-size: 19px; font-weight: 800; margin: 4px 0 4px; color: var(--ink); }
.eta-note { font-size: 12px; color: var(--ok); font-weight: 600; }
/* ---------- Status chip ---------- */
.status-chip {
font-size: 12px;
font-weight: 700;
padding: 6px 11px;
border-radius: 999px;
border: 1px solid transparent;
}
.status-chip[data-state="inrepair"] { color: var(--inprogress); background: rgba(43, 127, 255, 0.1); border-color: rgba(43, 127, 255, 0.25); }
.status-chip[data-state="approval"] { color: var(--warn); background: rgba(224, 150, 42, 0.12); border-color: rgba(224, 150, 42, 0.3); }
.status-chip[data-state="ready"] { color: var(--done); background: rgba(47, 158, 111, 0.12); border-color: rgba(47, 158, 111, 0.3); }
/* ---------- Steps ---------- */
.steps {
list-style: none;
margin: 0 0 16px;
padding: 0;
display: flex;
justify-content: space-between;
position: relative;
gap: 6px;
}
.steps::before {
content: "";
position: absolute;
top: 9px;
left: 14px;
right: 14px;
height: 2px;
background: var(--line-2);
z-index: 0;
}
.step {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex: 1;
min-width: 0;
}
.step-dot {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--surface);
border: 2px solid var(--line-2);
margin-bottom: 8px;
transition: all 0.3s ease;
}
.step.done .step-dot {
background: var(--done);
border-color: var(--done);
box-shadow: 0 0 0 3px rgba(47, 158, 111, 0.16);
}
.step.done .step-dot::after {
content: "";
display: block;
width: 6px;
height: 10px;
margin: 1px auto;
border: solid #fff;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.step.active .step-dot {
background: var(--orange);
border-color: var(--orange);
box-shadow: 0 0 0 0 rgba(255, 106, 19, 0.5);
animation: pulse 1.8s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 106, 19, 0.45); }
70% { box-shadow: 0 0 0 9px rgba(255, 106, 19, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 106, 19, 0); }
}
.step-body { display: flex; flex-direction: column; }
.step-name { font-size: 12px; font-weight: 600; color: var(--muted); }
.step.done .step-name, .step.active .step-name { color: var(--ink); }
.step-time { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.step.active .step-time { color: var(--orange-d); font-weight: 600; }
.progress-track {
height: 8px;
border-radius: 999px;
background: var(--line);
overflow: hidden;
}
.progress-fill {
height: 100%;
width: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--orange), var(--orange-d));
transition: width 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
/* ---------- Approval banner ---------- */
.approval {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
flex-wrap: wrap;
background: linear-gradient(180deg, #fff7ef, var(--orange-50));
border: 1px solid rgba(224, 150, 42, 0.4);
border-left: 4px solid var(--warn);
border-radius: var(--r-md);
padding: 14px 18px;
box-shadow: var(--shadow);
animation: slideIn 0.4s ease;
}
@keyframes slideIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.approval-txt { display: flex; flex-direction: column; }
.approval-txt strong { font-size: 14px; }
.approval-txt span { font-size: 13px; color: var(--ink-2); }
.approval-actions { display: flex; gap: 8px; }
/* ---------- Buttons ---------- */
.btn {
font: 600 13px/1 "Inter", sans-serif;
border-radius: var(--r-sm);
padding: 10px 16px;
border: 1px solid transparent;
cursor: pointer;
transition: transform 0.12s, background 0.15s, box-shadow 0.15s;
}
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--orange); color: #fff; box-shadow: 0 6px 16px rgba(255, 106, 19, 0.35); }
.btn.primary:hover { background: var(--orange-d); }
.btn.ghost { background: var(--surface); color: var(--ink-2); border-color: var(--line-2); }
.btn.ghost:hover { background: #fafafa; }
.btn:focus-visible, .icon-btn:focus-visible, input:focus-visible {
outline: 2px solid var(--orange);
outline-offset: 2px;
}
/* ---------- Feed ---------- */
.pulse-dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--ok);
box-shadow: 0 0 0 0 rgba(47, 158, 111, 0.5);
}
.pulse-dot.live { animation: pulse-sm 1.4s infinite; }
@keyframes pulse-sm {
0% { box-shadow: 0 0 0 0 rgba(47, 158, 111, 0.5); }
70% { box-shadow: 0 0 0 7px rgba(47, 158, 111, 0); }
100% { box-shadow: 0 0 0 0 rgba(47, 158, 111, 0); }
}
.thread {
list-style: none;
margin: 0 0 12px;
padding: 0;
display: flex;
flex-direction: column;
gap: 9px;
max-height: 300px;
overflow-y: auto;
}
.bubble {
max-width: 82%;
padding: 9px 12px;
border-radius: 14px;
font-size: 13.5px;
display: flex;
flex-direction: column;
gap: 3px;
animation: bub 0.3s ease;
}
@keyframes bub { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.bubble.shop {
align-self: flex-start;
background: #eef0f3;
color: var(--ink);
border-bottom-left-radius: 5px;
}
.bubble.me {
align-self: flex-end;
background: var(--garage);
color: #fff;
border-bottom-right-radius: 5px;
}
.bub-time { font-size: 10.5px; opacity: 0.6; align-self: flex-end; font-variant-numeric: tabular-nums; }
.dtc {
font-variant-numeric: tabular-nums;
background: rgba(212, 73, 62, 0.12);
color: var(--danger);
padding: 1px 5px;
border-radius: 5px;
font-weight: 700;
}
.composer { display: flex; gap: 8px; }
.composer input {
flex: 1;
font: 400 13.5px/1.4 "Inter", sans-serif;
padding: 10px 13px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: var(--bg);
color: var(--ink);
}
.composer input::placeholder { color: var(--muted); }
.btn.send {
display: grid;
place-items: center;
width: 42px;
padding: 0;
background: var(--orange);
color: #fff;
flex: none;
}
.btn.send:hover { background: var(--orange-d); }
/* ---------- Work order ---------- */
.lines { list-style: none; margin: 0 0 14px; padding: 0; }
.line {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 11px 0;
border-bottom: 1px solid var(--line);
}
.line:last-child { border-bottom: none; }
.line-name { display: flex; flex-direction: column; font-size: 13.5px; font-weight: 600; }
.line-name small { font-weight: 500; font-size: 11.5px; color: var(--muted); }
.line-amt { font-size: 14px; font-weight: 700; }
#addonLine { animation: slideIn 0.35s ease; }
#addonLine .line-amt { color: var(--ok); }
.totals { border-top: 2px solid var(--line-2); padding-top: 12px; }
.tot-row { display: flex; align-items: baseline; justify-content: space-between; }
.tot-row span { font-size: 13px; color: var(--ink-2); font-weight: 600; }
.tot-row strong { font-size: 20px; font-weight: 800; }
.tot-note { display: block; margin-top: 4px; font-size: 11px; color: var(--muted); }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 22px;
transform: translate(-50%, 20px);
background: var(--garage);
color: #fff;
font-size: 13px;
font-weight: 600;
padding: 11px 18px;
border-radius: 999px;
box-shadow: var(--shadow-lg);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 50;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* ---------- Responsive ---------- */
@media (max-width: 760px) {
.layout { grid-template-columns: 1fr; }
.feed, .order { grid-column: 1 / -1; }
.vehicle { grid-template-columns: 1fr; }
.veh-photo { width: 100%; height: 120px; }
.eta { border-left: none; border-top: 1px solid var(--line); padding-left: 0; padding-top: 14px; min-width: 0; }
}
@media (max-width: 520px) {
.app { padding: 12px; }
.topbar { padding: 12px 14px; }
.brand-txt strong { font-size: 14px; }
.card { padding: 14px; }
.veh-info h1 { font-size: 19px; }
.veh-meta { grid-template-columns: 1fr 1fr; gap: 10px; }
.steps { gap: 2px; }
.step-name { font-size: 10.5px; }
.step-time { display: none; }
.step.active .step-time { display: block; }
.approval { flex-direction: column; align-items: stretch; }
.approval-actions { justify-content: stretch; }
.approval-actions .btn { flex: 1; }
.bubble { max-width: 90%; }
}(function () {
"use strict";
var STEP_ORDER = ["checkin", "diagnosing", "approval", "repair", "ready"];
var PROGRESS = { checkin: 12, diagnosing: 34, approval: 52, repair: 72, ready: 100 };
var els = {
steps: document.getElementById("steps"),
fill: document.getElementById("progressFill"),
chip: document.getElementById("statusChip"),
track: document.querySelector(".progress-track"),
thread: document.getElementById("thread"),
composer: document.getElementById("composer"),
msgInput: document.getElementById("msgInput"),
feedPulse: document.getElementById("feedPulse"),
refreshBtn: document.getElementById("refreshBtn"),
approval: document.getElementById("approvalBanner"),
approveBtn: document.getElementById("approveBtn"),
declineBtn: document.getElementById("declineBtn"),
addonLine: document.getElementById("addonLine"),
orderTotal: document.getElementById("orderTotal"),
repairTime: document.getElementById("repairTime"),
etaTime: document.getElementById("etaTime"),
etaNote: document.getElementById("etaNote"),
toast: document.getElementById("toast"),
};
var state = { current: "repair", total: 578.4, approvalShown: false };
var toastTimer = null;
function toast(msg) {
if (!els.toast) return;
els.toast.textContent = msg;
els.toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
els.toast.classList.remove("show");
}, 2400);
}
function nowLabel() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var ap = h >= 12 ? "PM" : "AM";
h = h % 12 || 12;
return h + ":" + (m < 10 ? "0" + m : m) + " " + ap;
}
function money(n) {
return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function setProgress(stepKey) {
var pct = PROGRESS[stepKey] || 0;
els.fill.style.width = pct + "%";
if (els.track) els.track.setAttribute("aria-valuenow", String(pct));
}
function renderSteps() {
var idx = STEP_ORDER.indexOf(state.current);
Array.prototype.forEach.call(els.steps.children, function (li, i) {
li.classList.remove("done", "active");
if (i < idx) li.classList.add("done");
else if (i === idx) li.classList.add("active");
});
}
function addBubble(text, who, opts) {
opts = opts || {};
var li = document.createElement("li");
li.className = "bubble " + (who === "me" ? "me" : "shop");
var span = document.createElement("span");
span.className = "bub-text";
if (opts.html) span.innerHTML = text;
else span.textContent = text;
var time = document.createElement("span");
time.className = "bub-time";
time.textContent = nowLabel();
li.appendChild(span);
li.appendChild(time);
els.thread.appendChild(li);
els.thread.scrollTop = els.thread.scrollHeight;
if (who !== "me") pulseFeed();
}
function pulseFeed() {
els.feedPulse.classList.add("live");
setTimeout(function () {
els.feedPulse.classList.remove("live");
}, 4200);
}
function showApproval() {
if (state.approvalShown) return;
state.approvalShown = true;
els.approval.hidden = false;
els.chip.dataset.state = "approval";
els.chip.textContent = "Needs approval";
state.current = "approval";
renderSteps();
setProgress("approval");
addBubble(
"While we're in there — rear brake pads are down to <b>2mm</b>. Want us to swap them for <b>$184.00</b>? Approve below.",
"shop",
{ html: true }
);
toast("New request from the shop");
}
// Composer
els.composer.addEventListener("submit", function (e) {
e.preventDefault();
var v = els.msgInput.value.trim();
if (!v) return;
addBubble(v, "me");
els.msgInput.value = "";
setTimeout(function () {
addBubble("Got it — passing that along to your tech. 👍", "shop");
}, 1100);
});
// Refresh
els.refreshBtn.addEventListener("click", function () {
els.refreshBtn.classList.add("spinning");
setTimeout(function () {
els.refreshBtn.classList.remove("spinning");
}, 600);
if (!state.approvalShown) {
showApproval();
} else {
toast("You're all caught up");
}
});
// Approve
els.approveBtn.addEventListener("click", function () {
els.approval.hidden = true;
els.addonLine.hidden = false;
state.total += 184;
els.orderTotal.textContent = money(state.total);
state.current = "repair";
els.chip.dataset.state = "inrepair";
els.chip.textContent = "In repair";
renderSteps();
setProgress("repair");
addBubble("Approved — go ahead with the brakes.", "me");
setTimeout(function () {
addBubble("Thanks! Brake pads added to the order. Knocking it all out now.", "shop");
}, 1000);
els.etaTime.textContent = "Today, 5:10 PM";
els.etaNote.textContent = "Updated · added brake work";
toast("Work approved — total updated");
});
// Decline
els.declineBtn.addEventListener("click", function () {
els.approval.hidden = true;
state.current = "repair";
els.chip.dataset.state = "inrepair";
els.chip.textContent = "In repair";
renderSteps();
setProgress("repair");
addBubble("Let's skip the brakes for today, thanks.", "me");
setTimeout(function () {
addBubble("No problem — we'll note it for next visit. Finishing the coil job now.", "shop");
}, 1000);
toast("Add-on declined");
});
// Scripted live feed → leads into approval request
function startTimeline() {
renderSteps();
setTimeout(function () {
setProgress("repair");
}, 400);
setTimeout(function () {
addBubble("Old coil pack is out. New one and plugs going in now — nice clean install.", "shop");
}, 3200);
setTimeout(showApproval, 7000);
}
startTimeline();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Auto — Repair Status Tracker</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>
<div class="app">
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
</span>
<div class="brand-txt">
<strong>Ironwood Auto & Diesel</strong>
<span>Service status</span>
</div>
</div>
<div class="topbar-right">
<span class="ro-pill">RO #48217</span>
<button class="icon-btn" id="refreshBtn" type="button" aria-label="Refresh updates">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>
</button>
</div>
</header>
<main class="layout">
<!-- Vehicle + ETA card -->
<section class="card vehicle" aria-labelledby="vehHd">
<div class="veh-photo" aria-hidden="true">
<span class="veh-badge">2019</span>
<svg class="veh-icon" viewBox="0 0 24 24" width="64" height="64" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 17h14M3 17V12l2.5-5.5A2 2 0 0 1 7.3 5h9.4a2 2 0 0 1 1.8 1.1L21 12v5M3 17v2h3v-2M18 17v2h3v-2M5.5 12h13"/><circle cx="8" cy="17" r="1.2"/><circle cx="16" cy="17" r="1.2"/></svg>
</div>
<div class="veh-info">
<h1 id="vehHd">Ford F-150 XLT</h1>
<p class="veh-sub">Oxford White · 3.5L EcoBoost</p>
<dl class="veh-meta">
<div><dt>Plate</dt><dd class="mono">7KJ A92</dd></div>
<div><dt>VIN</dt><dd class="mono">1FTFW1E4XKF…839</dd></div>
<div><dt>Odometer</dt><dd class="mono">68,420 mi</dd></div>
<div><dt>Bay</dt><dd class="mono">Bay 4</dd></div>
</dl>
</div>
<div class="eta">
<span class="eta-label">Estimated ready</span>
<strong class="eta-time" id="etaTime">Today, 4:30 PM</strong>
<span class="eta-note" id="etaNote">On schedule · advisor Dana R.</span>
</div>
</section>
<!-- Step tracker -->
<section class="card tracker" aria-labelledby="trkHd">
<div class="card-hd">
<h2 id="trkHd">Repair progress</h2>
<span class="status-chip" id="statusChip" data-state="inrepair">In repair</span>
</div>
<ol class="steps" id="steps">
<li class="step done" data-step="checkin">
<span class="step-dot" aria-hidden="true"></span>
<div class="step-body"><span class="step-name">Checked in</span><span class="step-time">8:02 AM</span></div>
</li>
<li class="step done" data-step="diagnosing">
<span class="step-dot" aria-hidden="true"></span>
<div class="step-body"><span class="step-name">Diagnosing</span><span class="step-time">9:15 AM</span></div>
</li>
<li class="step done" data-step="approval">
<span class="step-dot" aria-hidden="true"></span>
<div class="step-body"><span class="step-name">Awaiting approval</span><span class="step-time">10:40 AM</span></div>
</li>
<li class="step active" data-step="repair">
<span class="step-dot" aria-hidden="true"></span>
<div class="step-body"><span class="step-name">In repair</span><span class="step-time" id="repairTime">In progress</span></div>
</li>
<li class="step" data-step="ready">
<span class="step-dot" aria-hidden="true"></span>
<div class="step-body"><span class="step-name">Ready for pickup</span><span class="step-time">—</span></div>
</li>
</ol>
<div class="progress-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="68" aria-label="Overall repair progress">
<div class="progress-fill" id="progressFill"></div>
</div>
</section>
<!-- Approval banner -->
<section class="approval" id="approvalBanner" hidden aria-live="polite">
<div class="approval-txt">
<strong>Additional work needs your OK</strong>
<span id="approvalDesc">Rear brake pads at 2mm — recommend replacement. +$184.00</span>
</div>
<div class="approval-actions">
<button class="btn ghost" id="declineBtn" type="button">Not now</button>
<button class="btn primary" id="approveBtn" type="button">Approve work</button>
</div>
</section>
<!-- Updates feed -->
<section class="card feed" aria-labelledby="feedHd">
<div class="card-hd">
<h2 id="feedHd">Updates from the shop</h2>
<span class="pulse-dot" id="feedPulse" aria-hidden="true"></span>
</div>
<ul class="thread" id="thread" aria-live="polite">
<li class="bubble shop">
<span class="bub-text">Morning! We've got your F-150 checked in at Bay 4. Starting the multi-point inspection now.</span>
<span class="bub-time">8:04 AM</span>
</li>
<li class="bubble shop">
<span class="bub-text">Diagnostics found a misfire on cylinder 3 — code <b class="dtc">P0303</b>. Pulling the coil pack to confirm.</span>
<span class="bub-time">9:18 AM</span>
</li>
<li class="bubble me">
<span class="bub-text">Thanks for the heads up. Go ahead with whatever it needs.</span>
<span class="bub-time">9:31 AM</span>
</li>
<li class="bubble shop">
<span class="bub-text">Confirmed — failed ignition coil + plugs. Replacing the set. Truck's up on the lift now.</span>
<span class="bub-time">10:52 AM</span>
</li>
</ul>
<form class="composer" id="composer">
<input id="msgInput" type="text" autocomplete="off" placeholder="Reply to the shop…" aria-label="Reply to the shop" />
<button class="btn send" type="submit" aria-label="Send reply">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2 11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>
</button>
</form>
</section>
<!-- Work order summary -->
<section class="card order" aria-labelledby="ordHd">
<div class="card-hd"><h2 id="ordHd">Work order</h2></div>
<ul class="lines">
<li class="line"><span class="line-name">Diagnostic — drivability<small>1.0 hr labor</small></span><span class="line-amt mono">$140.00</span></li>
<li class="line"><span class="line-name">Ignition coil pack (×1)<small>parts</small></span><span class="line-amt mono">$118.40</span></li>
<li class="line"><span class="line-name">Spark plugs, set of 6<small>parts</small></span><span class="line-amt mono">$96.00</span></li>
<li class="line"><span class="line-name">R&R coil + plugs<small>1.6 hr labor</small></span><span class="line-amt mono">$224.00</span></li>
<li class="line" id="addonLine" hidden><span class="line-name">Rear brake pads<small>approved add-on</small></span><span class="line-amt mono">$184.00</span></li>
</ul>
<div class="totals">
<div class="tot-row"><span>Estimate total</span><strong class="mono" id="orderTotal">$578.40</strong></div>
<span class="tot-note">Tax calculated at checkout · fictional figures</span>
</div>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
</div>
<script src="script.js"></script>
</body>
</html>Repair Status Tracker
A status page a service customer would open after dropping their truck off. The top panel shows the vehicle with a gradient photo placeholder, plate, VIN, odometer and bay number in tabular figures, alongside an estimated-ready time and assigned advisor. Below it, a five-stage tracker — checked in, diagnosing, awaiting approval, in repair, ready — marks completed steps with checks and pulses the active stage in safety orange, backed by an animated progress bar.
The right side reads like a text thread with the shop: bubbles for the advisor and the customer, time stamps, and inline diagnostic codes such as P0303. A live timeline drips in new messages with a green pulse on the feed indicator, and a reply composer lets the customer message back. When the shop finds extra work, an approval banner slides in; approving it adds the line item to the work order and bumps the total and ETA in real time.
Everything is self-contained vanilla HTML, CSS and JS — no frameworks, no build. The layout collapses to a mobile-first single column at narrow widths, buttons and inputs are keyboard-usable, and a small toast() helper surfaces confirmations.
Illustrative UI only — fictional shop/dealership, not a real service system.