Delivery — Delivery Confirm
A mobile-first driver delivery-confirmation screen for a fictional courier app. It pairs a customer and address card with a live delivery window countdown, a three-step proof checklist, mock photo-proof capture, a working canvas signature pad, quick drop-off note chips, and a leave-at-door toggle that swaps signature for drop-and-photo. A guarded confirm button plays a success animation once proof is collected, logging the stop and cueing the next.
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 3px rgba(22, 24, 29, 0.08);
--sh-md: 0 6px 18px rgba(22, 24, 29, 0.1);
--sh-lg: 0 18px 50px rgba(22, 24, 29, 0.18);
}
* { box-sizing: border-box; }
html, body { margin: 0; }
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--ink);
background: linear-gradient(160deg, #e9ebef, #f4f5f7 40%);
min-height: 100vh;
display: grid;
place-items: center;
padding: 24px 12px;
}
/* ---------- Phone shell ---------- */
.phone {
position: relative;
width: 100%;
max-width: 400px;
min-height: 760px;
background: var(--bg);
border-radius: 30px;
box-shadow: var(--sh-lg);
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid var(--line);
}
.statusbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 22px 4px;
font-size: 12px;
font-weight: 600;
color: var(--ink);
background: var(--surface);
}
.statusbar .dots { display: flex; gap: 3px; }
.statusbar .dots i { width: 4px; height: 4px; border-radius: 50%; background: var(--ink-2); }
/* ---------- App bar ---------- */
.appbar {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 14px 14px;
background: var(--surface);
border-bottom: 1px solid var(--line);
}
.appbar-title { flex: 1; display: flex; flex-direction: column; line-height: 1.2; }
.appbar-title .stop { font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.appbar-title strong { font-size: 17px; font-weight: 700; }
.route-pill {
font-size: 11px; font-weight: 700; color: var(--brand-d);
background: rgba(255, 90, 44, 0.1); padding: 5px 9px; border-radius: 999px; white-space: nowrap;
}
.iconbtn {
width: 38px; height: 38px; border-radius: 50%;
border: 1px solid var(--line); background: var(--surface);
display: grid; place-items: center; cursor: pointer; color: var(--ink);
transition: background .15s, transform .1s;
}
.iconbtn:hover { background: #f0f1f4; }
.iconbtn:active { transform: scale(.94); }
.iconbtn svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
/* ---------- Scroll area ---------- */
.scroll {
flex: 1;
overflow-y: auto;
padding: 14px 14px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px;
box-shadow: var(--sh-sm);
}
.card-h { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.card-h h3 { margin: 0; font-size: 14px; font-weight: 700; }
.req { font-size: 11px; font-weight: 700; color: var(--brand-d); background: rgba(255,90,44,.1); padding: 3px 8px; border-radius: 999px; }
.req.done { color: var(--ok); background: rgba(31,157,98,.12); }
/* ---------- Stop / customer ---------- */
.stop-top { display: flex; align-items: center; gap: 12px; }
.avatar {
width: 46px; height: 46px; border-radius: 14px; flex-shrink: 0;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff; font-weight: 800; font-size: 16px; display: grid; place-items: center;
}
.stop-meta { flex: 1; min-width: 0; }
.stop-meta h2 { margin: 0 0 2px; font-size: 16px; font-weight: 700; }
.stop-meta .addr { margin: 0; font-size: 13px; color: var(--muted); }
.pill { font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 999px; white-space: nowrap; }
.pill-track { color: var(--track); background: rgba(91,141,239,.13); }
.pill-ok { color: var(--ok); background: rgba(31,157,98,.13); }
.stop-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 12px; }
.tag { font-size: 12px; font-weight: 500; color: var(--ink-2); background: #f3f4f6; border-radius: 999px; padding: 5px 10px; }
.tag-warn { color: var(--warn); background: rgba(232,148,34,.12); }
.stop-actions { display: flex; gap: 8px; }
.ghostbtn {
flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 7px;
font-family: inherit; font-size: 13px; font-weight: 600; color: var(--ink);
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
padding: 10px; cursor: pointer; transition: background .15s, border-color .15s;
}
.ghostbtn:hover { background: #f7f8fa; border-color: rgba(22,24,29,.18); }
.ghostbtn:active { transform: scale(.98); }
.ghostbtn svg { width: 17px; height: 17px; fill: none; stroke: var(--brand); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* ---------- ETA ---------- */
.eta-card { display: flex; align-items: center; justify-content: space-between; background: linear-gradient(135deg, #16181d, #25282f); border: none; color: #fff; }
.eta-left { display: flex; flex-direction: column; }
.eta-label { font-size: 11px; font-weight: 600; color: rgba(255,255,255,.6); text-transform: uppercase; letter-spacing: .05em; }
.eta-time { font-size: 30px; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.eta-right { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.eta-sub { font-size: 11px; color: rgba(255,255,255,.6); }
/* ---------- Steps ---------- */
.steps {
list-style: none; margin: 0; padding: 0;
display: flex; align-items: center; gap: 6px;
}
.step {
flex: 1; display: flex; align-items: center; gap: 7px;
font-size: 11px; font-weight: 600; color: var(--muted);
background: var(--surface); border: 1px solid var(--line);
border-radius: 999px; padding: 7px 10px;
transition: color .2s, border-color .2s, background .2s;
}
.step .dot { width: 9px; height: 9px; border-radius: 50%; background: #d4d6db; flex-shrink: 0; transition: background .2s; }
.step.done { color: var(--ok); border-color: rgba(31,157,98,.35); background: rgba(31,157,98,.06); }
.step.done .dot { background: var(--ok); }
/* ---------- Toggle ---------- */
.switch-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; cursor: pointer; }
.switch-row strong { display: block; font-size: 14px; font-weight: 700; }
.switch-row small { font-size: 12px; color: var(--muted); }
.switch { position: relative; width: 48px; height: 28px; flex-shrink: 0; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.switch .track { position: absolute; inset: 0; background: #d4d6db; border-radius: 999px; transition: background .2s; }
.switch .track::after { content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; background: #fff; border-radius: 50%; box-shadow: var(--sh-sm); transition: transform .2s; }
.switch input:checked + .track { background: var(--ok); }
.switch input:checked + .track::after { transform: translateX(20px); }
.switch input:focus-visible + .track { outline: 2px solid var(--track); outline-offset: 2px; }
/* ---------- Photo proof ---------- */
.photo-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.photo-add, .photo-thumb {
aspect-ratio: 1; border-radius: var(--r-sm); border: 1.5px dashed rgba(22,24,29,.22);
background: #f7f8fa; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
font-family: inherit; font-size: 11px; font-weight: 600; color: var(--muted); cursor: pointer;
transition: border-color .15s, background .15s; overflow: hidden; position: relative; padding: 0;
}
.photo-add:hover { border-color: var(--brand); background: rgba(255,90,44,.05); color: var(--brand-d); }
.photo-add svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.photo-thumb { border-style: solid; border-color: var(--line); cursor: default; }
.photo-thumb .ph-img { position: absolute; inset: 0; }
.photo-thumb .ph-time { position: absolute; bottom: 4px; left: 4px; font-size: 9px; font-weight: 700; color: #fff; background: rgba(0,0,0,.55); padding: 1px 5px; border-radius: 4px; }
.photo-thumb .ph-x {
position: absolute; top: 4px; right: 4px; width: 18px; height: 18px; border-radius: 50%;
border: none; background: rgba(0,0,0,.55); color: #fff; font-size: 12px; line-height: 1; cursor: pointer; display: grid; place-items: center;
}
/* ---------- Signature ---------- */
.sign-card.hidden { display: none; }
.sign-wrap { position: relative; border: 1px solid var(--line); border-radius: var(--r-sm); background: #fbfcfd; overflow: hidden; }
#pad { display: block; width: 100%; height: 150px; touch-action: none; cursor: crosshair; }
.sign-hint { position: absolute; inset: 0; display: grid; place-items: center; font-size: 14px; font-weight: 500; color: #c2c5cc; pointer-events: none; transition: opacity .2s; }
.sign-hint.gone { opacity: 0; }
.sign-line { position: absolute; left: 14px; right: 14px; bottom: 28px; height: 1px; background: repeating-linear-gradient(90deg, rgba(22,24,29,.18) 0 6px, transparent 6px 12px); }
.sign-by { margin: 8px 2px 0; font-size: 12px; color: var(--muted); }
.textbtn { background: none; border: none; font-family: inherit; font-size: 12px; font-weight: 600; color: var(--brand-d); cursor: pointer; padding: 2px 4px; }
.textbtn:hover { text-decoration: underline; }
/* ---------- Notes ---------- */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.chip {
font-family: inherit; font-size: 12px; font-weight: 600; color: var(--ink-2);
background: #f3f4f6; border: 1px solid transparent; border-radius: 999px; padding: 6px 11px; cursor: pointer;
transition: background .15s, color .15s, border-color .15s;
}
.chip:hover { background: #eaecef; }
.chip.active { background: rgba(255,90,44,.1); color: var(--brand-d); border-color: rgba(255,90,44,.3); }
.notes-card textarea {
width: 100%; resize: none; font-family: inherit; font-size: 13px; color: var(--ink);
border: 1px solid var(--line); border-radius: var(--r-sm); padding: 10px; background: #fbfcfd;
}
.notes-card textarea:focus { outline: 2px solid rgba(91,141,239,.4); outline-offset: 0; border-color: var(--track); }
.count { display: block; text-align: right; font-size: 11px; color: var(--muted); margin-top: 4px; }
/* ---------- Footer ---------- */
.footer {
padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
background: var(--surface); border-top: 1px solid var(--line);
display: flex; flex-direction: column; gap: 8px;
}
.confirmbtn {
position: relative; width: 100%; font-family: inherit; font-size: 15px; font-weight: 700; color: #fff;
background: linear-gradient(135deg, var(--brand), var(--brand-d)); border: none; border-radius: var(--r-md);
padding: 15px; cursor: pointer; box-shadow: 0 6px 16px rgba(255,90,44,.32);
transition: transform .1s, box-shadow .15s, filter .15s; overflow: hidden;
}
.confirmbtn:hover { filter: brightness(1.04); }
.confirmbtn:active { transform: translateY(1px); box-shadow: 0 3px 10px rgba(255,90,44,.3); }
.confirmbtn:disabled { filter: grayscale(.3) brightness(.96); cursor: default; box-shadow: none; }
.confirmbtn.loading .cb-label { opacity: 0; }
.cb-spin { display: none; }
.confirmbtn.loading .cb-spin {
display: block; position: absolute; top: 50%; left: 50%; width: 20px; height: 20px; margin: -10px 0 0 -10px;
border: 2.5px solid rgba(255,255,255,.45); border-top-color: #fff; border-radius: 50%; animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.failbtn { width: 100%; font-family: inherit; font-size: 13px; font-weight: 600; color: var(--danger); background: none; border: none; padding: 6px; cursor: pointer; border-radius: var(--r-sm); }
.failbtn:hover { background: rgba(212,73,62,.07); }
/* ---------- Success overlay ---------- */
.overlay {
position: absolute; inset: 0; background: rgba(22,24,29,.55); backdrop-filter: blur(3px);
display: grid; place-items: center; padding: 24px; opacity: 0; visibility: hidden;
transition: opacity .25s, visibility .25s; z-index: 30;
}
.overlay.show { opacity: 1; visibility: visible; }
.success {
background: var(--surface); border-radius: var(--r-lg); padding: 26px 22px 22px; text-align: center;
width: 100%; max-width: 300px; box-shadow: var(--sh-lg); transform: translateY(14px) scale(.96); transition: transform .3s;
}
.overlay.show .success { transform: translateY(0) scale(1); }
.success h2 { margin: 14px 0 4px; font-size: 22px; font-weight: 800; }
.success p { margin: 0; font-size: 13px; color: var(--muted); }
.success-row { display: flex; align-items: center; justify-content: space-between; margin: 16px 0; padding: 12px 14px; background: #f3f4f6; border-radius: var(--r-sm); font-size: 13px; font-weight: 600; }
.check { width: 72px; height: 72px; margin: 0 auto; }
.check svg { width: 100%; height: 100%; }
.check circle { fill: none; stroke: var(--ok); stroke-width: 3; stroke-dasharray: 151; stroke-dashoffset: 151; animation: draw .5s ease forwards; }
.check path { fill: none; stroke: var(--ok); stroke-width: 4; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 50; stroke-dashoffset: 50; animation: draw .4s .35s ease forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }
.nextbtn { width: 100%; font-family: inherit; font-size: 14px; font-weight: 700; color: #fff; background: var(--ink); border: none; border-radius: var(--r-md); padding: 13px; cursor: pointer; transition: filter .15s, transform .1s; }
.nextbtn:hover { filter: brightness(1.2); }
.nextbtn:active { transform: translateY(1px); }
/* ---------- Toast ---------- */
.toast {
position: absolute; left: 50%; bottom: 96px; transform: translate(-50%, 16px);
background: var(--ink); color: #fff; font-size: 13px; font-weight: 600;
padding: 11px 16px; border-radius: var(--r-md); box-shadow: var(--sh-md);
opacity: 0; visibility: hidden; transition: opacity .2s, transform .2s; z-index: 40; max-width: 86%; text-align: center;
}
.toast.show { opacity: 1; visibility: visible; transform: translate(-50%, 0); }
@media (max-width: 520px) {
body { padding: 0; background: var(--surface); place-items: stretch; }
.phone { max-width: none; min-height: 100vh; min-height: 100dvh; border-radius: 0; border: none; box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2200);
}
/* ---------- State ---------- */
var state = {
photo: false,
signed: false,
leaveDoor: false
};
var steps = {
photo: document.querySelector('.step[data-step="photo"]'),
sign: document.querySelector('.step[data-step="sign"]'),
confirm: document.querySelector('.step[data-step="confirm"]')
};
/* ---------- Countdown (ETA window) ---------- */
var etaEl = document.getElementById("eta");
var remaining = 14 * 60 + 58;
setInterval(function () {
if (remaining <= 0) return;
remaining--;
var m = Math.floor(remaining / 60);
var s = remaining % 60;
etaEl.textContent = m + ":" + (s < 10 ? "0" : "") + s;
}, 1000);
/* ---------- Step + footer sync ---------- */
function refresh() {
steps.photo.classList.toggle("done", state.photo);
steps.sign.classList.toggle("done", state.leaveDoor || state.signed);
var photoReq = document.getElementById("photoReq");
if (state.photo) {
photoReq.textContent = "Captured";
photoReq.classList.add("done");
} else {
photoReq.textContent = "Required";
photoReq.classList.remove("done");
}
var ready = state.photo && (state.leaveDoor || state.signed);
steps.confirm.classList.toggle("done", ready);
}
/* ---------- Leave at door toggle ---------- */
var leaveDoor = document.getElementById("leaveDoor");
var signCard = document.getElementById("signCard");
leaveDoor.addEventListener("change", function () {
state.leaveDoor = leaveDoor.checked;
signCard.classList.toggle("hidden", state.leaveDoor);
toast(state.leaveDoor ? "Leave at door — signature skipped" : "Signature required");
refresh();
});
/* ---------- Photo capture (mock) ---------- */
var photoGrid = document.getElementById("photoGrid");
var photoAdd = document.getElementById("photoAdd");
var gradients = [
"linear-gradient(135deg,#8aa7c2,#5f7c97)",
"linear-gradient(135deg,#c2a98a,#977c5f)",
"linear-gradient(135deg,#9bbf9b,#6f9a6f)"
];
var photoIdx = 0;
function nowStamp() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
return (h % 12 || 12) + ":" + (m < 10 ? "0" : "") + m + (h < 12 ? "a" : "p");
}
photoAdd.addEventListener("click", function () {
if (photoIdx >= 3) {
toast("Max 3 photos");
return;
}
var thumb = document.createElement("div");
thumb.className = "photo-thumb";
var img = document.createElement("span");
img.className = "ph-img";
img.style.background = gradients[photoIdx % gradients.length];
var time = document.createElement("span");
time.className = "ph-time";
time.textContent = nowStamp();
var x = document.createElement("button");
x.className = "ph-x";
x.type = "button";
x.setAttribute("aria-label", "Remove photo");
x.textContent = "×";
x.addEventListener("click", function () {
thumb.remove();
photoIdx--;
if (photoIdx <= 0) state.photo = false;
refresh();
});
thumb.appendChild(img);
thumb.appendChild(time);
thumb.appendChild(x);
photoGrid.insertBefore(thumb, photoAdd);
photoIdx++;
state.photo = true;
if (photoIdx >= 3) photoAdd.style.display = "none";
toast("Photo proof captured");
refresh();
});
/* ---------- Signature pad ---------- */
var canvas = document.getElementById("pad");
var ctx = canvas.getContext("2d");
var hint = document.getElementById("signHint");
var drawing = false;
var hasInk = false;
var dpr = window.devicePixelRatio || 1;
function sizeCanvas() {
var rect = canvas.getBoundingClientRect();
if (!rect.width) return;
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.lineWidth = 2.4;
ctx.strokeStyle = "#16181d";
}
// Defer sizing until layout is ready
requestAnimationFrame(sizeCanvas);
window.addEventListener("resize", function () {
// Re-size only if empty to avoid wiping a real signature
if (!hasInk) sizeCanvas();
});
function pos(e) {
var rect = canvas.getBoundingClientRect();
var p = e.touches ? e.touches[0] : e;
return { x: p.clientX - rect.left, y: p.clientY - rect.top };
}
function start(e) {
e.preventDefault();
drawing = true;
var p = pos(e);
ctx.beginPath();
ctx.moveTo(p.x, p.y);
if (!hasInk) {
hasInk = true;
hint.classList.add("gone");
state.signed = true;
refresh();
}
}
function move(e) {
if (!drawing) return;
e.preventDefault();
var p = pos(e);
ctx.lineTo(p.x, p.y);
ctx.stroke();
}
function end() {
drawing = false;
}
canvas.addEventListener("mousedown", start);
canvas.addEventListener("mousemove", move);
window.addEventListener("mouseup", end);
canvas.addEventListener("touchstart", start, { passive: false });
canvas.addEventListener("touchmove", move, { passive: false });
canvas.addEventListener("touchend", end);
document.getElementById("clearSign").addEventListener("click", function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
hasInk = false;
state.signed = false;
hint.classList.remove("gone");
refresh();
toast("Signature cleared");
});
/* ---------- Notes ---------- */
var notes = document.getElementById("notes");
var noteCount = document.getElementById("noteCount");
notes.addEventListener("input", function () {
noteCount.textContent = notes.value.length;
});
document.querySelectorAll(".chip").forEach(function (chip) {
chip.addEventListener("click", function () {
var active = chip.classList.contains("active");
document.querySelectorAll(".chip").forEach(function (c) {
c.classList.remove("active");
});
if (active) return;
chip.classList.add("active");
var txt = chip.getAttribute("data-note");
notes.value = txt;
noteCount.textContent = txt.length;
});
});
/* ---------- Secondary actions ---------- */
document.getElementById("callBtn").addEventListener("click", function () {
toast("Calling Maya — +1 (503) 555-0148");
});
document.getElementById("navBtn").addEventListener("click", function () {
toast("Opening route to 2418 Cedarwood Ave");
});
document.getElementById("backBtn").addEventListener("click", function () {
toast("Returning to stop list");
});
document.getElementById("failBtn").addEventListener("click", function () {
toast("Marked unable to deliver — flagged for retry");
});
/* ---------- Confirm ---------- */
var confirmBtn = document.getElementById("confirmBtn");
var overlay = document.getElementById("overlay");
confirmBtn.addEventListener("click", function () {
if (!state.photo) {
toast("Capture photo proof first");
return;
}
if (!state.leaveDoor && !state.signed) {
toast("Signature required to confirm");
return;
}
confirmBtn.classList.add("loading");
confirmBtn.disabled = true;
setTimeout(function () {
confirmBtn.classList.remove("loading");
var meta = document.getElementById("successMeta");
meta.textContent = state.leaveDoor
? "Left at door · photo on file"
: "Parcel handed to Maya Adeyemi";
overlay.classList.add("show");
overlay.setAttribute("aria-hidden", "false");
}, 1100);
});
document.getElementById("nextBtn").addEventListener("click", function () {
overlay.classList.remove("show");
overlay.setAttribute("aria-hidden", "true");
toast("Loading stop 8 of 12…");
});
refresh();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Delivery — Delivery Confirm</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="phone" role="application" aria-label="Driver delivery confirmation">
<!-- Status bar -->
<div class="statusbar" aria-hidden="true">
<span class="time">2:41</span>
<span class="dots"><i></i><i></i><i></i><i></i><i></i></span>
<span class="batt">87%</span>
</div>
<!-- App header -->
<header class="appbar">
<button class="iconbtn" id="backBtn" aria-label="Go back">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg>
</button>
<div class="appbar-title">
<span class="stop">Stop 7 of 12</span>
<strong>Confirm delivery</strong>
</div>
<span class="route-pill">Route R-184</span>
</header>
<main class="scroll" id="scroll">
<!-- Stop / customer card -->
<section class="card stop-card" aria-labelledby="cust-h">
<div class="stop-top">
<div class="avatar" aria-hidden="true">MA</div>
<div class="stop-meta">
<h2 id="cust-h">Maya Adeyemi</h2>
<p class="addr">2418 Cedarwood Ave, Apt 5B<br />Portland, OR 97214</p>
</div>
<span class="pill pill-track">On site</span>
</div>
<div class="stop-tags">
<span class="tag">📦 2 parcels</span>
<span class="tag">🔔 Ring bell</span>
<span class="tag tag-warn">⚠ Gate code 4471</span>
</div>
<div class="stop-actions">
<button class="ghostbtn" id="callBtn">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3 19.5 19.5 0 0 1-6-6 19.8 19.8 0 0 1-3-8.7A2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1.9.4 1.8.7 2.7a2 2 0 0 1-.5 2.1L8.1 9.9a16 16 0 0 0 6 6l1.4-1.2a2 2 0 0 1 2.1-.5c.9.3 1.8.6 2.7.7a2 2 0 0 1 1.7 2z"/></svg>
Call
</button>
<button class="ghostbtn" id="navBtn">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 11l19-9-9 19-2-8-8-2z"/></svg>
Navigate
</button>
</div>
</section>
<!-- ETA / countdown -->
<section class="card eta-card" aria-label="Delivery window">
<div class="eta-left">
<span class="eta-label">Window closes in</span>
<span class="eta-time" id="eta">14:58</span>
</div>
<div class="eta-right">
<span class="eta-sub">SLA met</span>
<span class="pill pill-ok">On time</span>
</div>
</section>
<!-- Proof of delivery steps -->
<ol class="steps" aria-label="Proof of delivery checklist">
<li class="step" data-step="photo"><span class="dot"></span> Photo proof</li>
<li class="step" data-step="sign"><span class="dot"></span> Signature</li>
<li class="step" data-step="confirm"><span class="dot"></span> Confirm</li>
</ol>
<!-- Leave at door -->
<section class="card opt-card">
<label class="switch-row">
<span>
<strong>Leave at door</strong>
<small>Skip signature — drop & photo only</small>
</span>
<span class="switch">
<input type="checkbox" id="leaveDoor" />
<span class="track" aria-hidden="true"></span>
</span>
</label>
</section>
<!-- Photo proof -->
<section class="card photo-card" aria-labelledby="photo-h">
<div class="card-h"><h3 id="photo-h">Photo proof</h3><span class="req" id="photoReq">Required</span></div>
<div class="photo-grid" id="photoGrid">
<button class="photo-add" id="photoAdd" aria-label="Capture photo">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
<span>Capture</span>
</button>
</div>
</section>
<!-- Signature -->
<section class="card sign-card" id="signCard" aria-labelledby="sign-h">
<div class="card-h"><h3 id="sign-h">Signature</h3><button class="textbtn" id="clearSign">Clear</button></div>
<div class="sign-wrap">
<canvas id="pad" width="600" height="220" role="img" aria-label="Signature pad"></canvas>
<span class="sign-hint" id="signHint">Sign here</span>
<span class="sign-line" aria-hidden="true"></span>
</div>
<p class="sign-by">Received by <strong id="signByName">Maya Adeyemi</strong></p>
</section>
<!-- Drop-off notes -->
<section class="card notes-card" aria-labelledby="notes-h">
<div class="card-h"><h3 id="notes-h">Drop-off notes</h3></div>
<div class="chips" id="noteChips">
<button class="chip" data-note="Left with neighbor">Left with neighbor</button>
<button class="chip" data-note="Handed to recipient">Handed to recipient</button>
<button class="chip" data-note="Placed in lobby">Placed in lobby</button>
<button class="chip" data-note="Behind the planter">Behind the planter</button>
</div>
<textarea id="notes" rows="2" placeholder="Add a note for the customer…" maxlength="160"></textarea>
<span class="count"><span id="noteCount">0</span>/160</span>
</section>
</main>
<!-- Confirm footer -->
<footer class="footer">
<button class="confirmbtn" id="confirmBtn">
<span class="cb-label">Confirm delivered</span>
<span class="cb-spin" aria-hidden="true"></span>
</button>
<button class="failbtn" id="failBtn">Unable to deliver</button>
</footer>
<!-- Success overlay -->
<div class="overlay" id="overlay" aria-hidden="true">
<div class="success" role="alertdialog" aria-label="Delivery confirmed">
<div class="check" aria-hidden="true">
<svg viewBox="0 0 52 52"><circle cx="26" cy="26" r="24"/><path d="M14 27l8 8 16-18"/></svg>
</div>
<h2>Delivered</h2>
<p id="successMeta">Parcel handed to Maya Adeyemi</p>
<div class="success-row"><span>Stop 7 of 12</span><span class="pill pill-ok">Logged</span></div>
<button class="nextbtn" id="nextBtn">Next stop →</button>
</div>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
</div>
<script src="script.js"></script>
</body>
</html>Delivery Confirm
A polished proof-of-delivery flow framed inside a phone shell for a courier driver. The top of the screen anchors the stop in context: customer name, full address, parcel count, access tags (ring bell, gate code), and call / navigate shortcuts. A dark ETA card counts the delivery-window down second by second, with an on-time SLA pill so the driver always knows where the clock stands.
The core of the screen is the proof workflow. A three-step tracker (photo, signature, confirm) lights green as each requirement is met. Tapping Capture drops timestamped photo placeholders into a grid (up to three, each removable), and the signature pad is a real <canvas> you can draw on with mouse or touch — clearing it resets the step. Drop-off note chips pre-fill the textarea with common outcomes, or you can type a custom note. Flipping the leave-at-door toggle hides the signature card and switches the proof requirement to drop-and-photo.
The Confirm delivered button is guarded: it nudges you with a toast if photo proof or a signature is still missing, then shows a brief loading spinner before an animated checkmark overlay confirms the drop, logs the stop, and offers the next one. Small toasts narrate every secondary action, and the whole layout collapses to a true full-screen mobile view below 520px.
Illustrative UI only — fictional brand, not a real delivery service.