Auto — Repair Estimate
A customer-facing repair estimate for an auto shop, splitting every job into labor and parts with diagnostic codes, recommended versus optional tags, and per-line approve or decline controls. Optional jobs ride on toggle switches that add or remove themselves from scope, and a sticky summary panel recomputes labor, parts, shop supplies, tax and total live. A sign-off block stays locked until nothing is pending, then authorizes the work and opens the order.
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;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 18px;
--sh-sm: 0 1px 2px rgba(20, 21, 24, 0.06);
--sh-md: 0 6px 20px rgba(20, 21, 24, 0.08);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
font-feature-settings: "cv05", "ss01";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
}
.mono { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.muted { color: var(--muted); }
.shell {
max-width: 1080px;
margin: 0 auto;
padding: 22px 20px 56px;
}
/* Topbar */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
background: var(--garage);
background-image: linear-gradient(135deg, var(--garage), var(--garage-2));
color: #fff;
border-radius: var(--r-lg);
padding: 14px 18px;
box-shadow: var(--sh-md);
}
.brand { display: flex; align-items: center; gap: 12px; }
.logo {
display: grid; place-items: center;
width: 40px; height: 40px;
border-radius: var(--r-sm);
background: var(--orange);
color: #fff;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}
.brand-txt { display: flex; flex-direction: column; line-height: 1.25; }
.brand-txt strong { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.brand-txt span { font-size: 12px; color: var(--steel-l); }
.topbar-meta { display: flex; align-items: center; gap: 8px; }
.chip {
font-size: 12px; font-weight: 600;
padding: 5px 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
color: #e9ecf1;
border: 1px solid rgba(255, 255, 255, 0.14);
}
.chip-ok { background: rgba(47, 158, 111, 0.18); color: #6fe0b0; border-color: rgba(47, 158, 111, 0.4); }
.chip-signed { background: var(--orange); color: #fff; border-color: var(--orange-d); }
/* Grid */
.grid {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 18px;
margin-top: 18px;
}
.col-main { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.col-side { display: flex; flex-direction: column; gap: 18px; }
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--sh-sm);
overflow: hidden;
}
.card-head { padding: 16px 18px 6px; }
.card-head h2 { margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -0.01em; }
.card-head p { margin: 4px 0 0; font-size: 13px; }
/* Vehicle */
.vehicle { display: grid; grid-template-columns: 150px 1fr; }
.veh-photo {
position: relative;
background:
radial-gradient(120% 80% at 20% 10%, rgba(255, 106, 19, 0.35), transparent 60%),
linear-gradient(160deg, var(--garage-2), var(--garage));
min-height: 100%;
}
.veh-photo::after {
content: "";
position: absolute; inset: 0;
background-image: repeating-linear-gradient(115deg, rgba(255, 255, 255, 0.05) 0 2px, transparent 2px 9px);
}
.veh-tag {
position: absolute; left: 12px; bottom: 12px;
font-size: 12px; font-weight: 700;
color: #fff;
background: rgba(20, 21, 24, 0.55);
border: 1px solid rgba(255, 255, 255, 0.18);
padding: 3px 9px; border-radius: 999px;
font-variant-numeric: tabular-nums;
}
.veh-info { padding: 16px 18px; }
.veh-info h1 { margin: 0 0 12px; font-size: 18px; font-weight: 800; letter-spacing: -0.02em; }
.specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 18px; margin: 0; }
.specs div { display: flex; flex-direction: column; }
.specs dt { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 600; }
.specs dd { margin: 1px 0 0; font-size: 13.5px; font-weight: 600; color: var(--ink); }
/* Lines */
.lines { list-style: none; margin: 10px 0 0; padding: 0; }
.line {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: start;
padding: 14px 18px;
border-top: 1px solid var(--line);
transition: background 0.15s ease;
}
.line:hover { background: #fafbfc; }
.line.declined { opacity: 0.55; }
.line.declined .line-title { text-decoration: line-through; }
.line.optional-off { opacity: 0.5; }
.line-flag {
width: 8px; height: 8px; border-radius: 50%;
margin-top: 7px;
background: var(--steel-l);
}
.line.is-approved .line-flag { background: var(--ok); }
.line.declined .line-flag { background: var(--danger); }
.line-body { min-width: 0; }
.line-title { font-size: 14.5px; font-weight: 700; letter-spacing: -0.01em; }
.line-sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.line-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.tag {
font-size: 11px; font-weight: 600;
padding: 3px 8px; border-radius: 999px;
background: #f1f2f4; color: var(--ink-2);
border: 1px solid var(--line);
}
.tag.rec { background: var(--orange-50); color: var(--orange-d); border-color: rgba(255, 106, 19, 0.25); }
.tag.opt { background: #eef4ff; color: #2057c4; border-color: rgba(43, 127, 255, 0.22); }
.tag.dtc { font-variant-numeric: tabular-nums; }
.line-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.line-price { font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.line-price small { font-weight: 500; color: var(--muted); font-size: 11px; display: block; text-align: right; }
.actions { display: flex; gap: 6px; }
.opt-toggle { display: flex; align-items: center; gap: 7px; margin-bottom: 2px; }
.switch { position: relative; display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .track {
width: 38px; height: 22px; border-radius: 999px;
background: var(--line-2); transition: background 0.18s ease;
position: relative; flex: none;
}
.switch .track::after {
content: ""; position: absolute; top: 2px; left: 2px;
width: 18px; height: 18px; border-radius: 50%;
background: #fff; box-shadow: var(--sh-sm);
transition: transform 0.18s ease;
}
.switch input:checked + .track { background: var(--orange); }
.switch input:checked + .track::after { transform: translateX(16px); }
.switch input:focus-visible + .track { outline: 2px solid var(--orange-d); outline-offset: 2px; }
.switch-lbl { font-size: 11px; font-weight: 600; color: var(--muted); }
/* Buttons */
.btn {
font: inherit; font-size: 12.5px; font-weight: 600;
border-radius: var(--r-sm);
padding: 7px 12px;
border: 1px solid var(--line-2);
background: #fff; color: var(--ink-2);
cursor: pointer;
transition: transform 0.08s ease, background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.btn:hover { background: #f7f8f9; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--orange-d); outline-offset: 2px; }
.btn-approve.is-on { background: var(--ok); border-color: var(--ok); color: #fff; }
.btn-decline.is-on { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-primary {
background: var(--orange); border-color: var(--orange-d); color: #fff;
width: 100%; padding: 11px 14px; font-size: 14px; font-weight: 700;
}
.btn-primary:hover { background: var(--orange-d); }
.btn-primary:disabled { background: var(--steel-l); border-color: var(--steel-l); color: #fff; cursor: not-allowed; opacity: 0.7; }
/* Summary */
.summary { padding: 16px 18px; position: sticky; top: 16px; }
.summary h2 { margin: 0 0 12px; font-size: 15px; font-weight: 700; }
.sum-rows { display: flex; flex-direction: column; gap: 9px; }
.sum-row { display: flex; justify-content: space-between; font-size: 13.5px; color: var(--ink-2); }
.sum-row em { font-style: normal; color: var(--muted); font-size: 12px; }
.sum-row.sub { padding-top: 9px; border-top: 1px dashed var(--line-2); font-weight: 600; color: var(--ink); }
.sum-row.total {
margin-top: 6px; padding-top: 12px; border-top: 2px solid var(--garage);
font-size: 16px; font-weight: 800; color: var(--garage);
}
.sum-meta {
display: flex; justify-content: space-between; gap: 6px;
margin-top: 14px; padding: 10px 12px;
background: var(--bg); border-radius: var(--r-sm);
font-size: 11.5px; color: var(--muted);
}
.sum-meta strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.signoff { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.check { display: flex; gap: 9px; font-size: 12.5px; color: var(--ink-2); cursor: pointer; margin-bottom: 12px; }
.check input { margin-top: 2px; width: 16px; height: 16px; accent-color: var(--orange); flex: none; }
.signed {
display: flex; align-items: center; gap: 12px;
margin-top: 16px; padding: 14px;
background: rgba(47, 158, 111, 0.08);
border: 1px solid rgba(47, 158, 111, 0.3);
border-radius: var(--r-sm);
color: var(--ok);
}
.signed strong { display: block; color: var(--ink); font-size: 13.5px; }
.signed span { font-size: 11.5px; color: var(--muted); }
.note { padding: 16px 18px; }
.note h3 { margin: 0 0 8px; font-size: 13px; font-weight: 700; }
.note ul { margin: 0; padding-left: 18px; }
.note li { font-size: 12.5px; color: var(--muted); margin-bottom: 5px; }
/* Toast */
.toast {
position: fixed; left: 50%; bottom: 24px;
transform: translate(-50%, 16px);
background: var(--garage); color: #fff;
font-size: 13px; font-weight: 600;
padding: 11px 18px; border-radius: 999px;
box-shadow: 0 10px 30px rgba(20, 21, 24, 0.3);
opacity: 0; pointer-events: none;
transition: opacity 0.22s ease, transform 0.22s ease;
z-index: 50;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* Responsive */
@media (max-width: 860px) {
.grid { grid-template-columns: 1fr; }
.summary { position: static; }
}
@media (max-width: 520px) {
.shell { padding: 14px 12px 44px; }
.vehicle { grid-template-columns: 1fr; }
.veh-photo { min-height: 92px; }
.specs { grid-template-columns: 1fr 1fr; }
.veh-info h1 { font-size: 16px; }
.line { grid-template-columns: auto 1fr; }
.line-right { grid-column: 1 / -1; flex-direction: row; align-items: center; justify-content: space-between; padding-left: 20px; }
.topbar { flex-wrap: wrap; }
}
@media (max-width: 360px) {
.specs { grid-template-columns: 1fr; }
}(function () {
"use strict";
var TAX_RATE = 0.0825;
var SUPPLY_RATE = 0.04; // shop supplies = 4% of parts subtotal, capped
var SUPPLY_CAP = 35;
// Each line: labor + parts. Recommended lines default approved; optional default off.
var LINES = [
{
id: "L1", title: "Cylinder 1 misfire — ignition coil & spark plug set",
sub: "Replace failed coil-on-plug, full plug set (6), clear codes, road test.",
dtc: "P0301", kind: "rec", labor: 168.0, parts: 214.5,
laborHrs: 1.4, state: "approved", optional: false
},
{
id: "L2", title: "Front brake pads & rotors",
sub: "Pads at 2mm. Resurface not advised — replace rotors, lubricate hardware.",
dtc: null, kind: "rec", labor: 132.0, parts: 286.0,
laborHrs: 1.1, state: "pending", optional: false
},
{
id: "L3", title: "Lean condition diagnosis — intake & MAF",
sub: "Clean MAF sensor, smoke-test intake boots for vacuum leak.",
dtc: "P0171", kind: "rec", labor: 96.0, parts: 28.0,
laborHrs: 0.8, state: "pending", optional: false
},
{
id: "L4", title: "Synthetic oil & filter change",
sub: "6.0 qt 5W-30 full synthetic, OEM filter, reset oil life.",
dtc: null, kind: "opt", labor: 24.0, parts: 62.0,
laborHrs: 0.3, state: "approved", optional: true, enabled: true
},
{
id: "L5", title: "Cabin & engine air filter set",
sub: "Both filters restricted. Recommended at this mileage.",
dtc: null, kind: "opt", labor: 18.0, parts: 44.0,
laborHrs: 0.2, state: "pending", optional: true, enabled: false
},
{
id: "L6", title: "Four-wheel alignment",
sub: "Slight inner-edge tire wear. Set toe/camber to spec.",
dtc: null, kind: "opt", labor: 110.0, parts: 0.0,
laborHrs: 1.0, state: "pending", optional: true, enabled: false
}
];
var signed = false;
var $ = function (s, r) { return (r || document).querySelector(s); };
var fmt = function (n) {
return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
var toastEl = $("#toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2200);
}
// A line is "active" (counts toward total) when approved AND (not optional or enabled).
function isActive(l) {
if (l.optional && !l.enabled) return false;
return l.state === "approved";
}
function isCounted(l) {
// Optional-off lines are excluded from the work scope entirely.
return !(l.optional && !l.enabled);
}
function render() {
var list = $("#lineList");
list.innerHTML = "";
LINES.forEach(function (l) {
var li = document.createElement("li");
li.className = "line";
if (l.state === "approved") li.classList.add("is-approved");
if (l.state === "declined") li.classList.add("declined");
if (l.optional && !l.enabled) li.classList.add("optional-off");
var lineTotal = l.labor + l.parts;
var optToggle = "";
if (l.optional) {
optToggle =
'<div class="opt-toggle">' +
'<label class="switch">' +
'<input type="checkbox" data-toggle="' + l.id + '" ' + (l.enabled ? "checked" : "") +
' aria-label="Include ' + l.title + '" />' +
'<span class="track"></span>' +
'</label>' +
'<span class="switch-lbl">' + (l.enabled ? "Included" : "Not included") + '</span>' +
'</div>';
}
var dtcTag = l.dtc ? '<span class="tag dtc">DTC ' + l.dtc + '</span>' : "";
var kindTag = l.kind === "rec"
? '<span class="tag rec">Recommended</span>'
: '<span class="tag opt">Optional</span>';
var disabled = (l.optional && !l.enabled);
li.innerHTML =
'<span class="line-flag" aria-hidden="true"></span>' +
'<div class="line-body">' +
'<div class="line-title">' + l.title + '</div>' +
'<div class="line-sub">' + l.sub + '</div>' +
'<div class="line-meta">' +
kindTag + dtcTag +
'<span class="tag">' + l.laborHrs.toFixed(1) + ' hr labor</span>' +
'<span class="tag">' + fmt(l.parts) + ' parts</span>' +
'</div>' +
'</div>' +
'<div class="line-right">' +
optToggle +
'<div class="line-price">' + fmt(lineTotal) + '<small>labor ' + fmt(l.labor) + '</small></div>' +
'<div class="actions">' +
'<button class="btn btn-approve' + (l.state === "approved" ? " is-on" : "") + '" ' +
'data-act="approve" data-id="' + l.id + '"' + (disabled ? " disabled" : "") +
' aria-pressed="' + (l.state === "approved") + '">Approve</button>' +
'<button class="btn btn-decline' + (l.state === "declined" ? " is-on" : "") + '" ' +
'data-act="decline" data-id="' + l.id + '"' + (disabled ? " disabled" : "") +
' aria-pressed="' + (l.state === "declined") + '">Decline</button>' +
'</div>' +
'</div>';
list.appendChild(li);
});
recompute();
}
function recompute() {
var labor = 0, parts = 0, approvedCount = 0, declinedCount = 0, pendingCount = 0;
LINES.forEach(function (l) {
if (!isCounted(l)) return; // optional-off excluded from scope counts
if (l.state === "approved") approvedCount++;
else if (l.state === "declined") declinedCount++;
else pendingCount++;
if (isActive(l)) { labor += l.labor; parts += l.parts; }
});
var subtotal = labor + parts;
var supplies = Math.min(parts * SUPPLY_RATE, SUPPLY_CAP);
supplies = subtotal > 0 ? supplies : 0;
var taxable = subtotal + supplies;
var tax = taxable * TAX_RATE;
var total = taxable + tax;
$("#sumLabor").textContent = fmt(labor);
$("#sumParts").textContent = fmt(parts);
$("#sumSub").textContent = fmt(subtotal);
$("#sumSupplies").textContent = fmt(supplies);
$("#sumTax").textContent = fmt(tax);
$("#sumTotal").textContent = fmt(total);
$("#cntApproved").textContent = approvedCount;
$("#cntDeclined").textContent = declinedCount;
$("#cntPending").textContent = pendingCount;
updateSignState(pendingCount, approvedCount);
}
function updateSignState(pending, approved) {
var agree = $("#agree");
var btn = $("#signBtn");
if (signed) return;
// Can authorize once nothing is pending and at least one job approved.
var ready = pending === 0 && approved > 0 && agree.checked;
btn.disabled = !ready;
if (pending > 0) {
btn.textContent = "Resolve " + pending + " pending job" + (pending === 1 ? "" : "s");
} else if (approved === 0) {
btn.textContent = "Approve at least one job";
} else {
btn.textContent = "Sign & approve estimate";
}
}
function getLine(id) {
for (var i = 0; i < LINES.length; i++) if (LINES[i].id === id) return LINES[i];
return null;
}
// Event delegation on the list
$("#lineList").addEventListener("click", function (e) {
var btn = e.target.closest("button[data-act]");
if (!btn || btn.disabled || signed) return;
var l = getLine(btn.dataset.id);
if (!l) return;
var act = btn.dataset.act;
if (act === "approve") {
l.state = l.state === "approved" ? "pending" : "approved";
toast(l.state === "approved" ? "Approved: " + shortName(l) : "Set to pending");
} else {
l.state = l.state === "declined" ? "pending" : "declined";
toast(l.state === "declined" ? "Declined: " + shortName(l) : "Set to pending");
}
render();
});
$("#lineList").addEventListener("change", function (e) {
var t = e.target.closest("input[data-toggle]");
if (!t || signed) return;
var l = getLine(t.dataset.toggle);
if (!l) return;
l.enabled = t.checked;
toast(l.enabled ? "Added: " + shortName(l) : "Removed: " + shortName(l));
render();
});
function shortName(l) {
return l.title.split("—")[0].split(" & ")[0].trim();
}
$("#agree").addEventListener("change", recompute);
$("#signBtn").addEventListener("click", function () {
if (signed) return;
signed = true;
var now = new Date();
$("#signoff").hidden = true;
var signedBox = $("#signed");
signedBox.hidden = false;
$("#signedMeta").textContent =
"Marcus Delgado · " +
now.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) +
" " + now.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
var chip = $("#statusChip");
chip.textContent = "Authorized";
chip.classList.remove("chip-ok");
chip.classList.add("chip-signed");
// Lock all controls
document.querySelectorAll("#lineList button, #lineList input").forEach(function (el) {
el.disabled = true;
});
toast("Estimate authorized — work order opened");
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Auto — Repair Estimate</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="shell">
<header class="topbar">
<div class="brand">
<span class="logo" 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="M3 13l2-5a3 3 0 0 1 2.8-2h8.4A3 3 0 0 1 19 8l2 5"/>
<path d="M3 13h18v4a1 1 0 0 1-1 1h-1a2 2 0 0 1-4 0H9a2 2 0 0 1-4 0H4a1 1 0 0 1-1-1z"/>
</svg>
</span>
<div class="brand-txt">
<strong>Ironwood Auto & Diesel</strong>
<span>Estimate · EST-4471</span>
</div>
</div>
<div class="topbar-meta">
<span class="chip">Bay 3</span>
<span class="chip chip-ok" id="statusChip">Draft</span>
</div>
</header>
<main class="grid">
<section class="col-main">
<!-- Vehicle / customer card -->
<article class="card vehicle">
<div class="veh-photo" aria-hidden="true">
<span class="veh-tag">2019</span>
</div>
<div class="veh-info">
<h1>2019 Ford F-150 XLT 3.5L EcoBoost</h1>
<dl class="specs">
<div><dt>Customer</dt><dd>Marcus Delgado</dd></div>
<div><dt>Plate</dt><dd class="mono">7HJ·Y412</dd></div>
<div><dt>VIN</dt><dd class="mono">1FTFW1E4XKF·A8821</dd></div>
<div><dt>Odometer</dt><dd class="mono">84,217 mi</dd></div>
<div><dt>Service Advisor</dt><dd>D. Okafor</dd></div>
<div><dt>Diagnostic</dt><dd class="mono">P0301 · P0171</dd></div>
</dl>
</div>
</article>
<!-- Line items -->
<article class="card">
<div class="card-head">
<h2>Recommended work</h2>
<p class="muted">Approve or decline each job. Optional jobs can be toggled on or off.</p>
</div>
<ul class="lines" id="lineList" aria-label="Estimate line items"></ul>
</article>
</section>
<aside class="col-side">
<article class="card summary" aria-live="polite">
<h2>Customer summary</h2>
<div class="sum-rows">
<div class="sum-row"><span>Labor</span><span class="mono" id="sumLabor">$0.00</span></div>
<div class="sum-row"><span>Parts</span><span class="mono" id="sumParts">$0.00</span></div>
<div class="sum-row sub"><span>Subtotal</span><span class="mono" id="sumSub">$0.00</span></div>
<div class="sum-row"><span>Shop supplies</span><span class="mono" id="sumSupplies">$0.00</span></div>
<div class="sum-row"><span>Tax <em>(8.25%)</em></span><span class="mono" id="sumTax">$0.00</span></div>
<div class="sum-row total"><span>Total due</span><span class="mono" id="sumTotal">$0.00</span></div>
</div>
<div class="sum-meta">
<span><strong id="cntApproved">0</strong> approved</span>
<span><strong id="cntDeclined">0</strong> declined</span>
<span><strong id="cntPending">0</strong> pending</span>
</div>
<div class="signoff" id="signoff">
<label class="check">
<input type="checkbox" id="agree" />
<span>I authorize the approved work above and the listed charges.</span>
</label>
<button class="btn btn-primary" id="signBtn" disabled>Sign & approve estimate</button>
</div>
<div class="signed" id="signed" hidden>
<svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
<div>
<strong>Estimate authorized</strong>
<span id="signedMeta" class="mono"></span>
</div>
</div>
</article>
<article class="card note">
<h3>Good to know</h3>
<ul>
<li>Parts carry a 24-month / 24k-mile warranty.</li>
<li>Diagnostic fee waived if work is approved.</li>
<li>Estimate valid for 14 days from issue.</li>
</ul>
</article>
</aside>
</main>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Repair Estimate
A single-screen repair estimate for Ironwood Auto & Diesel, led by a vehicle card that pairs a gradient photo placeholder with the VIN, plate, odometer and diagnostic codes for a 2019 F-150. Beneath it, each recommended job is a line item split into labor and parts, carrying its hours, parts cost, a recommended or optional tag, and any tied diagnostic trouble code such as P0301 or P0171. A sticky customer summary on the right tallies labor, parts, shop supplies, an 8.25% tax and the total due.
Every line is interactive. Approve and decline buttons toggle each job’s state and update its flag colour and the running approved / declined / pending counts. Optional jobs sit behind a toggle switch that pulls them in or out of the estimate entirely, and any change recomputes the summary instantly. The authorize button stays disabled until nothing is left pending and the customer ticks the authorization box, after which sign-off stamps a name and timestamp, locks the controls, flips the status chip and confirms with a toast.
The whole thing is vanilla HTML, CSS and JavaScript with no frameworks, build step or external assets beyond the Inter font. Money fields use tabular figures, the layout reflows to a stacked mobile view below 520px, and buttons and inputs stay keyboard-usable throughout.
Illustrative UI only — fictional shop/dealership, not a real service system.