Real Estate — Compliance / Document Tracker
An editorial real-estate compliance tracker that follows required documents across every open transaction — listing agreement, seller disclosures, inspection report, appraisal, and closing statement — each with a colour-coded status of Missing, Pending Review, Approved, or Expired, a due date, and contextual upload or review actions. A per-transaction progress ring recomputes as statuses change, and an incomplete-only filter surfaces deals still needing attention. Plain HTML, CSS, and vanilla JavaScript, fully responsive.
MCP
コード
:root {
--ivory: #f7f4ec;
--paper: #fffdf8;
--white: #ffffff;
--green: #1f3d34;
--green-d: #16302a;
--green-700: #26493e;
--green-50: #e8efea;
--brass: #b08d57;
--brass-d: #94733f;
--brass-50: #f3ead9;
--ink: #1c2a25;
--ink-2: #33433d;
--muted: #6b7a72;
--line: rgba(31, 61, 52, 0.12);
--line-2: rgba(31, 61, 52, 0.22);
--ok: #2f9e6f;
--warn: #c98a2b;
--danger: #c4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--sh-1: 0 1px 2px rgba(28, 42, 37, 0.05), 0 1px 1px rgba(28, 42, 37, 0.04);
--sh-2: 0 6px 18px rgba(28, 42, 37, 0.08), 0 2px 6px rgba(28, 42, 37, 0.05);
--sh-3: 0 18px 40px rgba(28, 42, 37, 0.14), 0 6px 14px rgba(28, 42, 37, 0.08);
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
font-family: "Inter", system-ui, sans-serif;
line-height: 1.55;
color: var(--ink);
background: var(--ivory);
}
h1, h2, h3 {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 600;
letter-spacing: 0.01em;
margin: 0;
}
a {
color: inherit;
}
.skip-link {
position: absolute;
left: -999px;
top: 0;
background: var(--green);
color: var(--paper);
padding: 10px 16px;
border-radius: 0 0 var(--r-sm) 0;
z-index: 50;
}
.skip-link:focus {
left: 0;
}
/* ---------- Topbar ---------- */
.topbar {
display: flex;
align-items: center;
gap: 24px;
padding: 16px clamp(18px, 4vw, 48px);
background: var(--green);
color: var(--ivory);
border-bottom: 1px solid rgba(176, 141, 87, 0.35);
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand-mark {
color: var(--brass);
font-size: 18px;
line-height: 1;
}
.brand-text {
display: flex;
flex-direction: column;
line-height: 1.15;
}
.brand-text strong {
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 20px;
font-weight: 700;
letter-spacing: 0.02em;
}
.brand-text span {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.18em;
color: rgba(243, 234, 217, 0.7);
}
.topnav {
display: flex;
gap: 6px;
margin-left: 12px;
}
.topnav a {
text-decoration: none;
font-size: 13.5px;
font-weight: 500;
color: rgba(243, 234, 217, 0.78);
padding: 8px 14px;
border-radius: 999px;
transition: background 0.18s, color 0.18s;
}
.topnav a:hover {
color: var(--ivory);
background: rgba(255, 255, 255, 0.07);
}
.topnav a.active {
color: var(--green-d);
background: var(--brass-50);
}
.topbar-right {
margin-left: auto;
}
.agent-pill {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 13.5px;
font-weight: 500;
padding: 6px 14px 6px 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(176, 141, 87, 0.35);
}
.agent-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
display: grid;
place-items: center;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--green-d);
background: linear-gradient(140deg, var(--brass-50), var(--brass));
}
/* ---------- Layout ---------- */
.wrap {
max-width: 1180px;
margin: 0 auto;
padding: clamp(28px, 5vw, 56px) clamp(18px, 4vw, 48px) 80px;
}
.page-head {
display: flex;
flex-wrap: wrap;
gap: 28px;
align-items: flex-end;
justify-content: space-between;
padding-bottom: 26px;
border-bottom: 1px solid var(--line);
}
.eyebrow {
margin: 0 0 6px;
font-size: 11.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.2em;
color: var(--brass-d);
}
.page-head h1 {
font-size: clamp(34px, 5vw, 46px);
line-height: 1.05;
color: var(--green-d);
}
.lede {
margin: 12px 0 0;
max-width: 52ch;
color: var(--ink-2);
font-size: 15px;
}
.summary {
display: flex;
gap: 10px;
}
.summary-stat {
min-width: 92px;
padding: 14px 16px;
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--sh-1);
text-align: center;
}
.summary-stat .num {
display: block;
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 30px;
font-weight: 700;
line-height: 1;
color: var(--green);
}
.summary-stat .lbl {
display: block;
margin-top: 4px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
}
.summary-stat.warn .num {
color: var(--warn);
}
/* ---------- Toolbar ---------- */
.toolbar {
display: flex;
align-items: center;
gap: 18px;
flex-wrap: wrap;
margin: 26px 0 22px;
}
.filters {
display: inline-flex;
gap: 6px;
padding: 4px;
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
box-shadow: var(--sh-1);
}
.chip {
appearance: none;
border: 0;
cursor: pointer;
font-family: inherit;
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
background: transparent;
padding: 8px 16px;
border-radius: 999px;
transition: background 0.18s, color 0.18s, box-shadow 0.18s;
}
.chip:hover {
color: var(--green-d);
}
.chip.is-active {
color: var(--paper);
background: var(--green);
box-shadow: var(--sh-1);
}
.chip:focus-visible {
outline: 2px solid var(--brass);
outline-offset: 2px;
}
.toolbar-note {
margin: 0;
font-size: 13px;
color: var(--muted);
}
/* ---------- Board ---------- */
.board {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 22px;
}
.txn {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-2);
overflow: hidden;
display: flex;
flex-direction: column;
transition: box-shadow 0.22s, transform 0.22s;
}
.txn:hover {
box-shadow: var(--sh-3);
transform: translateY(-2px);
}
.txn-photo {
position: relative;
aspect-ratio: 16 / 7;
background: linear-gradient(115deg, #2a4a3e 0%, #38604f 45%, #6f8a72 100%);
}
.txn-photo::after {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(120% 90% at 18% 12%, rgba(255, 245, 222, 0.42), transparent 55%),
radial-gradient(90% 130% at 92% 100%, rgba(22, 48, 42, 0.6), transparent 60%);
}
.txn:nth-child(3n + 2) .txn-photo {
background: linear-gradient(120deg, #5a4326 0%, #8a6a3c 48%, #c4a36a 100%);
}
.txn:nth-child(3n) .txn-photo {
background: linear-gradient(125deg, #34433f 0%, #4d6a5c 40%, #9bb0a0 100%);
}
.txn-photo .ph-label {
position: absolute;
left: 14px;
bottom: 12px;
z-index: 1;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ivory);
background: rgba(22, 48, 42, 0.5);
border: 1px solid rgba(243, 234, 217, 0.3);
padding: 4px 10px;
border-radius: 999px;
backdrop-filter: blur(2px);
}
.txn-photo .price-badge {
position: absolute;
right: 14px;
top: 14px;
z-index: 1;
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 18px;
font-weight: 700;
color: var(--green-d);
background: var(--brass-50);
padding: 5px 12px;
border-radius: 999px;
box-shadow: var(--sh-1);
}
.txn-head {
display: flex;
gap: 14px;
align-items: flex-start;
padding: 18px 20px 14px;
border-bottom: 1px solid var(--line);
}
.txn-meta {
min-width: 0;
}
.txn-meta h2 {
font-size: 22px;
line-height: 1.1;
color: var(--green-d);
}
.txn-sub {
margin: 3px 0 8px;
font-size: 12.5px;
color: var(--muted);
}
.tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.tag {
font-size: 11px;
font-weight: 600;
color: var(--ink-2);
background: var(--green-50);
border: 1px solid var(--line);
padding: 2px 9px;
border-radius: 999px;
}
/* ---------- Progress ring ---------- */
.ring {
flex: none;
position: relative;
width: 62px;
height: 62px;
}
.ring svg {
width: 62px;
height: 62px;
transform: rotate(-90deg);
}
.ring .track {
fill: none;
stroke: var(--green-50);
stroke-width: 7;
}
.ring .bar {
fill: none;
stroke: var(--brass);
stroke-width: 7;
stroke-linecap: round;
transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1), stroke 0.4s;
}
.ring.is-complete .bar {
stroke: var(--ok);
}
.ring .ring-pct {
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-size: 13px;
font-weight: 700;
color: var(--green-d);
}
/* ---------- Doc list ---------- */
.docs {
list-style: none;
margin: 0;
padding: 6px 12px 12px;
}
.doc {
display: grid;
grid-template-columns: 1fr auto;
gap: 6px 12px;
align-items: center;
padding: 12px 8px;
border-bottom: 1px solid var(--line);
}
.doc:last-child {
border-bottom: 0;
}
.doc-name {
font-size: 14px;
font-weight: 600;
color: var(--ink);
}
.doc-due {
grid-column: 1;
font-size: 12px;
color: var(--muted);
}
.doc-due.is-overdue {
color: var(--danger);
font-weight: 600;
}
.status {
grid-column: 2;
grid-row: 1 / span 2;
display: inline-flex;
align-items: center;
gap: 6px;
justify-self: end;
font-size: 11.5px;
font-weight: 600;
padding: 5px 11px;
border-radius: 999px;
border: 1px solid transparent;
white-space: nowrap;
}
.status .dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
}
.status.s-missing {
color: var(--danger);
background: rgba(196, 80, 62, 0.1);
border-color: rgba(196, 80, 62, 0.25);
}
.status.s-pending {
color: var(--brass-d);
background: var(--brass-50);
border-color: rgba(176, 141, 87, 0.4);
}
.status.s-approved {
color: var(--ok);
background: rgba(47, 158, 111, 0.12);
border-color: rgba(47, 158, 111, 0.3);
}
.status.s-expired {
color: var(--ink-2);
background: rgba(31, 61, 52, 0.08);
border-color: var(--line-2);
}
.doc-actions {
grid-column: 1 / -1;
display: flex;
gap: 8px;
margin-top: 4px;
}
.btn {
appearance: none;
cursor: pointer;
font-family: inherit;
font-size: 12.5px;
font-weight: 600;
padding: 7px 13px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: var(--white);
color: var(--green-d);
transition: background 0.16s, border-color 0.16s, transform 0.08s;
}
.btn:hover {
background: var(--green-50);
border-color: var(--brass);
}
.btn:active {
transform: translateY(1px);
}
.btn.primary {
background: var(--green);
color: var(--paper);
border-color: var(--green);
}
.btn.primary:hover {
background: var(--green-700);
}
.btn:focus-visible {
outline: 2px solid var(--brass);
outline-offset: 2px;
}
.txn-foot {
margin-top: auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
background: rgba(232, 239, 234, 0.5);
border-top: 1px solid var(--line);
font-size: 12.5px;
color: var(--ink-2);
}
.txn-foot .done-flag {
display: inline-flex;
align-items: center;
gap: 6px;
font-weight: 600;
color: var(--ok);
}
.empty {
margin: 48px auto 0;
max-width: 40ch;
text-align: center;
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 22px;
color: var(--green-700);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
background: var(--green-d);
color: var(--ivory);
font-size: 13.5px;
font-weight: 500;
padding: 11px 20px;
border-radius: 999px;
box-shadow: var(--sh-3);
border: 1px solid rgba(176, 141, 87, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 60;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 760px) {
.topnav {
display: none;
}
}
@media (max-width: 520px) {
.topbar {
gap: 12px;
padding: 12px 16px;
}
.agent-pill .agent-avatar {
margin: 0;
}
.agent-pill {
font-size: 0;
padding: 4px;
gap: 0;
}
.page-head {
align-items: flex-start;
}
.summary {
width: 100%;
}
.summary-stat {
flex: 1;
min-width: 0;
padding: 12px 8px;
}
.board {
grid-template-columns: 1fr;
}
.doc-actions {
flex-wrap: wrap;
}
.toolbar {
margin-top: 20px;
}
}(function () {
"use strict";
// ---- Data: fictional transactions, each with a required-document checklist ----
var STATUSES = {
missing: { label: "Missing", cls: "s-missing" },
pending: { label: "Pending Review", cls: "s-pending" },
approved: { label: "Approved", cls: "s-approved" },
expired: { label: "Expired", cls: "s-expired" }
};
var DOC_TEMPLATE = [
{ key: "listing", name: "Listing Agreement" },
{ key: "disclosures", name: "Seller Disclosures" },
{ key: "inspection", name: "Inspection Report" },
{ key: "appraisal", name: "Appraisal" },
{ key: "closing", name: "Closing Statement" }
];
var transactions = [
{
id: "tx-1",
address: "412 Juniper Hollow Rd",
city: "Asheton Heights, CT",
price: "$1.24M",
tags: ["4 bd", "3 ba", "3,180 sqft"],
label: "Under Contract",
docs: [
{ key: "listing", status: "approved", due: "Closed 04/02" },
{ key: "disclosures", status: "approved", due: "Closed 04/05" },
{ key: "inspection", status: "pending", due: "Due Jun 11" },
{ key: "appraisal", status: "missing", due: "Due Jun 14" },
{ key: "closing", status: "missing", due: "Due Jun 27" }
]
},
{
id: "tx-2",
address: "88 Wren Court",
city: "Lake Marlowe, CT",
price: "$685K",
tags: ["3 bd", "2 ba", "1,940 sqft"],
label: "Active",
docs: [
{ key: "listing", status: "approved", due: "Closed 05/19" },
{ key: "disclosures", status: "pending", due: "Due Jun 09" },
{ key: "inspection", status: "missing", due: "Due Jun 16" },
{ key: "appraisal", status: "missing", due: "Due Jun 21" },
{ key: "closing", status: "missing", due: "Due Jul 02" }
]
},
{
id: "tx-3",
address: "1207 Cedar Bluff Ave",
city: "Harlow Park, CT",
price: "$2.05M",
tags: ["5 bd", "4 ba", "4,610 sqft"],
label: "Closing Soon",
docs: [
{ key: "listing", status: "approved", due: "Closed 03/28" },
{ key: "disclosures", status: "approved", due: "Closed 04/02" },
{ key: "inspection", status: "approved", due: "Closed 05/30" },
{ key: "appraisal", status: "expired", due: "Expired Jun 04" },
{ key: "closing", status: "pending", due: "Due Jun 10" }
]
},
{
id: "tx-4",
address: "26 Saffron Lane",
city: "Edenfield, CT",
price: "$540K",
tags: ["2 bd", "2 ba", "1,310 sqft"],
label: "Active",
docs: [
{ key: "listing", status: "approved", due: "Closed 05/26" },
{ key: "disclosures", status: "approved", due: "Closed 05/29" },
{ key: "inspection", status: "approved", due: "Closed 06/03" },
{ key: "appraisal", status: "approved", due: "Closed 06/05" },
{ key: "closing", status: "approved", due: "Closed 06/06" }
]
}
];
var RING_C = 2 * Math.PI * 26; // r = 26
var currentFilter = "all";
var board = document.getElementById("board");
var emptyState = document.getElementById("emptyState");
var toolbarNote = document.getElementById("toolbarNote");
var toastEl = document.getElementById("toast");
var toastTimer;
// ---- Helpers ----
function docName(key) {
for (var i = 0; i < DOC_TEMPLATE.length; i++) {
if (DOC_TEMPLATE[i].key === key) return DOC_TEMPLATE[i].name;
}
return key;
}
function findTxn(id) {
return transactions.filter(function (t) { return t.id === id; })[0];
}
function approvedCount(txn) {
return txn.docs.filter(function (d) { return d.status === "approved"; }).length;
}
function isComplete(txn) {
return approvedCount(txn) === txn.docs.length;
}
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2200);
}
// ---- Rendering ----
function render() {
board.innerHTML = "";
var shown = 0;
transactions.forEach(function (txn) {
if (currentFilter === "incomplete" && isComplete(txn)) return;
shown++;
board.appendChild(buildCard(txn));
});
emptyState.hidden = shown > 0;
updateSummary();
}
function buildCard(txn) {
var done = approvedCount(txn);
var total = txn.docs.length;
var pct = Math.round((done / total) * 100);
var complete = done === total;
var card = document.createElement("article");
card.className = "txn";
card.setAttribute("aria-label", txn.address);
var photo =
'<div class="txn-photo">' +
'<span class="price-badge">' + txn.price + "</span>" +
'<span class="ph-label">' + txn.label + "</span>" +
"</div>";
var head =
'<div class="txn-head">' +
'<div class="txn-meta">' +
"<h2>" + txn.address + "</h2>" +
'<p class="txn-sub">' + txn.city + "</p>" +
'<div class="tags">' +
txn.tags.map(function (t) { return '<span class="tag">' + t + "</span>"; }).join("") +
"</div>" +
"</div>" +
buildRing(txn.id, pct, complete) +
"</div>";
var list = document.createElement("ul");
list.className = "docs";
txn.docs.forEach(function (doc) {
list.appendChild(buildDocRow(txn.id, doc));
});
var foot =
'<div class="txn-foot">' +
"<span>" + done + " of " + total + " documents approved</span>" +
(complete
? '<span class="done-flag">✓ Compliant</span>'
: '<span>' + (total - done) + ' outstanding</span>') +
"</div>";
card.innerHTML = photo + head;
card.appendChild(list);
card.insertAdjacentHTML("beforeend", foot);
return card;
}
function buildRing(id, pct, complete) {
var offset = RING_C * (1 - pct / 100);
return (
'<div class="ring' + (complete ? " is-complete" : "") + '" data-ring="' + id + '" ' +
'role="img" aria-label="' + pct + ' percent compliant">' +
'<svg viewBox="0 0 62 62" aria-hidden="true">' +
'<circle class="track" cx="31" cy="31" r="26"></circle>' +
'<circle class="bar" cx="31" cy="31" r="26" ' +
'stroke-dasharray="' + RING_C.toFixed(1) + '" ' +
'stroke-dashoffset="' + offset.toFixed(1) + '"></circle>' +
"</svg>" +
'<span class="ring-pct">' + pct + "%</span>" +
"</div>"
);
}
function buildDocRow(txnId, doc) {
var meta = STATUSES[doc.status];
var overdue = /Expired/.test(doc.due);
var li = document.createElement("li");
li.className = "doc";
li.dataset.txn = txnId;
li.dataset.doc = doc.key;
li.innerHTML =
'<span class="doc-name">' + docName(doc.key) + "</span>" +
'<span class="status ' + meta.cls + '"><span class="dot" aria-hidden="true"></span>' + meta.label + "</span>" +
'<span class="doc-due' + (overdue ? " is-overdue" : "") + '">' + doc.due + "</span>";
var actions = document.createElement("div");
actions.className = "doc-actions";
if (doc.status === "missing" || doc.status === "expired") {
actions.appendChild(makeBtn("Upload", "primary", function () {
setStatus(txnId, doc.key, "pending", "Due Jun 30");
toast(docName(doc.key) + " uploaded — sent for review.");
}));
} else if (doc.status === "pending") {
actions.appendChild(makeBtn("Approve", "primary", function () {
setStatus(txnId, doc.key, "approved", "Closed " + today());
toast(docName(doc.key) + " approved.");
}));
actions.appendChild(makeBtn("Request changes", "", function () {
setStatus(txnId, doc.key, "missing", "Re-upload required");
toast("Changes requested on " + docName(doc.key) + ".");
}));
} else if (doc.status === "approved") {
actions.appendChild(makeBtn("Mark expired", "", function () {
setStatus(txnId, doc.key, "expired", "Expired " + today());
toast(docName(doc.key) + " marked expired.");
}));
}
li.appendChild(actions);
return li;
}
function makeBtn(text, variant, onClick) {
var b = document.createElement("button");
b.className = "btn" + (variant ? " " + variant : "");
b.type = "button";
b.textContent = text;
b.addEventListener("click", onClick);
return b;
}
function today() {
var d = new Date();
var mm = String(d.getMonth() + 1).padStart(2, "0");
var dd = String(d.getDate()).padStart(2, "0");
return mm + "/" + dd;
}
// ---- State mutation ----
function setStatus(txnId, docKey, status, due) {
var txn = findTxn(txnId);
if (!txn) return;
txn.docs.forEach(function (d) {
if (d.key === docKey) {
d.status = status;
if (due) d.due = due;
}
});
render();
}
function updateSummary() {
var approved = 0, pending = 0, open = 0;
transactions.forEach(function (txn) {
txn.docs.forEach(function (d) {
if (d.status === "approved") approved++;
else if (d.status === "pending") pending++;
else open++; // missing or expired
});
});
document.getElementById("statApproved").textContent = approved;
document.getElementById("statPending").textContent = pending;
document.getElementById("statOpen").textContent = open;
}
// ---- Filters ----
var chips = document.querySelectorAll(".chip");
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-pressed", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
currentFilter = chip.dataset.filter;
toolbarNote.textContent =
currentFilter === "incomplete"
? "Showing transactions with outstanding documents."
: "Showing all open transactions.";
render();
});
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Compliance — Document 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=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">◆</span>
<div class="brand-text">
<strong>Marlowe & Crane</strong>
<span>Transaction Compliance</span>
</div>
</div>
<nav class="topnav" aria-label="Primary">
<a href="#" class="active" aria-current="page">Compliance</a>
<a href="#">Listings</a>
<a href="#">Closings</a>
<a href="#">Team</a>
</nav>
<div class="topbar-right">
<span class="agent-pill">
<span class="agent-avatar" aria-hidden="true">RH</span>
Rosalind Hart
</span>
</div>
</header>
<main id="main" class="wrap">
<section class="page-head">
<div>
<p class="eyebrow">Brokerage Operations</p>
<h1>Document Compliance</h1>
<p class="lede">Track required documents across every open transaction — from listing agreement to closing — and clear what's still outstanding before each deadline.</p>
</div>
<div class="summary">
<div class="summary-stat">
<span class="num" id="statApproved">0</span>
<span class="lbl">Approved</span>
</div>
<div class="summary-stat">
<span class="num" id="statPending">0</span>
<span class="lbl">Pending</span>
</div>
<div class="summary-stat warn">
<span class="num" id="statOpen">0</span>
<span class="lbl">Need action</span>
</div>
</div>
</section>
<div class="toolbar" role="region" aria-label="Filters">
<div class="filters" role="group" aria-label="Filter transactions">
<button class="chip is-active" data-filter="all" aria-pressed="true">All transactions</button>
<button class="chip" data-filter="incomplete" aria-pressed="false">Incomplete only</button>
</div>
<p class="toolbar-note" id="toolbarNote">Showing all open transactions.</p>
</div>
<section id="board" class="board" aria-live="polite"></section>
<p class="empty" id="emptyState" hidden>Every transaction is fully compliant. Nothing outstanding.</p>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Compliance / Document Tracker
A brokerage-grade compliance board for keeping paperwork honest. Each open transaction renders as a refined card with a CSS-simulated listing photo, price badge, address, and bed/bath/sqft tags, paired with a brass progress ring that shows how many required documents have been approved. Beneath sits the checklist — listing agreement, seller disclosures, inspection report, appraisal, and closing statement — every row carrying a colour-coded status chip (Missing, Pending Review, Approved, or Expired), a due date, and the actions that make sense for that state.
Interactions are vanilla JavaScript only. Uploading a missing document moves it to Pending Review; approving a pending one flips it green and recomputes the ring on the spot; requesting changes sends it back to Missing; and an approved document can be marked Expired. The header stat band — Approved, Pending, and Need action — recalculates after every change, while a small toast confirms each action.
A two-button filter toggles between all transactions and incomplete only, hiding fully compliant deals so outstanding work stays front and centre; when nothing remains, an editorial empty state takes over. The whole layout is keyboard-usable, AA-contrast, and reflows cleanly down to roughly 360px wide.
Illustrative UI only — sample listings and data are fictional; not a real real-estate service.