Real Estate — Brokerage Dashboard
An editorial brokerage admin dashboard that opens with a four-metric KPI row for total volume, gross commission, units closed, and average days on market, each carrying a period delta and inline sparkline. A brass-toned SVG area chart traces closed sales volume, a status donut splits the active book, a top-agents leaderboard ranks producers by volume with share bars, and a recent-closings feed lists settled deals. A date-range toggle re-renders every figure with animated count-ups and a redrawn chart.
MCP
Codice
: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-sm: 0 1px 2px rgba(22, 48, 42, 0.06), 0 1px 1px rgba(22, 48, 42, 0.04);
--sh-md: 0 8px 24px -12px rgba(22, 48, 42, 0.22), 0 2px 6px rgba(22, 48, 42, 0.06);
--sh-lg: 0 24px 60px -28px rgba(22, 48, 42, 0.4), 0 6px 18px rgba(22, 48, 42, 0.08);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--ivory);
color: var(--ink);
font-family: "Inter", system-ui, sans-serif;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3 {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 600;
color: var(--green-d);
margin: 0;
line-height: 1.1;
letter-spacing: 0.2px;
}
p { margin: 0; }
a { color: inherit; text-decoration: none; }
/* ---------- Shell ---------- */
.shell {
display: grid;
grid-template-columns: 256px 1fr;
min-height: 100vh;
}
/* ---------- Sidebar ---------- */
.sidebar {
background: linear-gradient(180deg, var(--green) 0%, var(--green-d) 100%);
color: var(--ivory);
padding: 26px 20px;
display: flex;
flex-direction: column;
gap: 26px;
position: sticky;
top: 0;
height: 100vh;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand-mark {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 12px;
background: var(--brass);
color: var(--green-d);
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 700;
font-size: 16px;
letter-spacing: 0.5px;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25);
}
.brand-name {
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 22px;
font-weight: 600;
color: var(--paper);
}
.nav {
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 11px 14px;
border-radius: var(--r-sm);
font-size: 14px;
font-weight: 500;
color: rgba(247, 244, 236, 0.74);
transition: background 0.18s ease, color 0.18s ease;
}
.nav-item .dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: rgba(247, 244, 236, 0.35);
transition: background 0.18s ease, box-shadow 0.18s ease;
}
.nav-item:hover { background: rgba(255, 255, 255, 0.07); color: var(--paper); }
.nav-item.is-active {
background: rgba(176, 141, 87, 0.18);
color: var(--paper);
box-shadow: inset 0 0 0 1px rgba(176, 141, 87, 0.4);
}
.nav-item.is-active .dot { background: var(--brass); box-shadow: 0 0 0 4px rgba(176, 141, 87, 0.22); }
.side-card {
margin-top: auto;
padding: 16px;
border-radius: var(--r-md);
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.side-card__eyebrow {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1.4px;
color: var(--brass);
font-weight: 600;
}
.side-card__big {
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 34px;
font-weight: 700;
color: var(--paper);
margin: 2px 0 10px;
}
.side-bar {
height: 6px;
border-radius: 99px;
background: rgba(255, 255, 255, 0.14);
overflow: hidden;
}
.side-bar span {
display: block;
height: 100%;
border-radius: 99px;
background: linear-gradient(90deg, var(--brass-d), var(--brass));
transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
}
.side-card__note {
margin-top: 10px;
font-size: 12px;
color: rgba(247, 244, 236, 0.6);
}
/* ---------- Main ---------- */
.main {
padding: 30px 38px 40px;
max-width: 1280px;
width: 100%;
}
.topbar {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 26px;
}
.eyebrow {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1.6px;
color: var(--brass-d);
font-weight: 600;
margin-bottom: 4px;
}
.topbar h1 { font-size: 38px; }
.sub { color: var(--muted); font-size: 14px; margin-top: 4px; }
.topbar__right { display: flex; align-items: center; gap: 16px; }
.range {
display: inline-flex;
background: var(--white);
border: 1px solid var(--line);
border-radius: 99px;
padding: 4px;
box-shadow: var(--sh-sm);
}
.range__btn {
border: 0;
background: transparent;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--muted);
padding: 7px 16px;
border-radius: 99px;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease;
}
.range__btn:hover { color: var(--green); }
.range__btn.is-active {
background: var(--green);
color: var(--paper);
box-shadow: var(--sh-sm);
}
.range__btn:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.avatar {
display: grid;
place-items: center;
width: 44px;
height: 44px;
border-radius: 50%;
background: var(--brass-50);
color: var(--brass-d);
font-weight: 700;
font-size: 14px;
border: 1px solid var(--brass);
}
/* ---------- KPI ---------- */
.kpis {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
margin-bottom: 22px;
}
.kpi {
position: relative;
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 18px 14px;
box-shadow: var(--sh-sm);
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.kpi::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 3px;
background: linear-gradient(180deg, var(--brass), var(--brass-d));
}
.kpi:hover { transform: translateY(-3px); box-shadow: var(--sh-md); }
.kpi__label {
font-size: 12px;
color: var(--muted);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.6px;
}
.kpi__value {
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 36px;
font-weight: 700;
color: var(--green-d);
margin: 6px 0 6px;
line-height: 1;
display: flex;
align-items: baseline;
}
.kpi__value .cur { font-size: 22px; color: var(--brass-d); margin-right: 1px; }
.kpi__value .suf { font-size: 18px; color: var(--ink-2); margin-left: 2px; font-family: "Inter", sans-serif; font-weight: 600; }
.kpi__delta {
display: inline-block;
font-size: 12px;
font-weight: 600;
padding: 2px 0;
}
.kpi__delta.up { color: var(--ok); }
.kpi__delta.down { color: var(--danger); }
.kpi__delta.good { color: var(--ok); }
.spark {
position: absolute;
right: 14px;
bottom: 12px;
width: 86px;
height: 30px;
opacity: 0.85;
}
.spark svg { width: 100%; height: 100%; display: block; overflow: visible; }
.spark path { stroke: var(--brass); stroke-width: 2; fill: none; }
/* ---------- Grid layout ---------- */
.grid {
display: grid;
grid-template-columns: 2fr 1fr;
grid-auto-rows: min-content;
gap: 18px;
}
.card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 22px;
box-shadow: var(--sh-sm);
}
.card--chart { grid-column: 1; grid-row: 1; }
.card--donut { grid-column: 2; grid-row: 1 / span 1; }
.card--table { grid-column: 1; grid-row: 2; }
.card--feed { grid-column: 2; grid-row: 2; }
.card__head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
margin-bottom: 18px;
}
.card h2 { font-size: 25px; }
.card__sub { font-size: 13px; color: var(--muted); margin-top: 3px; }
.chart-total, .chart-total .cur, .chart-total .suf {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 700;
color: var(--green-d);
}
.chart-total { font-size: 28px; line-height: 1; white-space: nowrap; }
.chart-total .cur { font-size: 18px; color: var(--brass-d); }
.chart-total .suf { font-size: 16px; }
/* ---------- Trend chart ---------- */
.chart {
position: relative;
width: 100%;
}
.chart svg { width: 100%; height: 280px; display: block; overflow: visible; }
.grid-lines line { stroke: var(--line); stroke-width: 1; }
.area {
opacity: 0;
transition: opacity 0.6s ease 0.3s;
}
.area.in { opacity: 1; }
.line {
stroke: var(--brass-d);
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 4px 8px rgba(148, 115, 63, 0.25));
}
.dot {
fill: var(--paper);
stroke: var(--brass-d);
stroke-width: 2;
cursor: pointer;
transition: r 0.15s ease;
}
.dot:hover { r: 6; }
.chart-axis {
display: flex;
justify-content: space-between;
margin-top: 8px;
font-size: 11px;
color: var(--muted);
letter-spacing: 0.4px;
}
.chart-tip {
position: absolute;
transform: translate(-50%, -118%);
background: var(--green-d);
color: var(--paper);
padding: 6px 10px;
border-radius: var(--r-sm);
font-size: 12px;
font-weight: 600;
pointer-events: none;
white-space: nowrap;
box-shadow: var(--sh-md);
z-index: 4;
}
.chart-tip::after {
content: "";
position: absolute;
left: 50%;
bottom: -5px;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: var(--green-d);
border-bottom: 0;
}
.chart-tip span { color: var(--brass); }
/* ---------- Donut ---------- */
.donut-wrap {
position: relative;
width: 200px;
max-width: 100%;
margin: 4px auto 16px;
}
.donut { width: 100%; height: auto; transform: rotate(-90deg); }
.donut-track { fill: none; stroke: var(--line); stroke-width: 22; }
.donut-seg {
fill: none;
stroke-width: 22;
stroke-linecap: butt;
stroke-dasharray: 0 999;
transition: stroke-dasharray 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
#seg-active { stroke: var(--green); }
#seg-pending { stroke: var(--brass); }
#seg-closed { stroke: var(--ok); }
#seg-withdrawn { stroke: var(--danger); }
.donut-center {
position: absolute;
inset: 0;
display: grid;
place-content: center;
text-align: center;
}
.donut-center__num {
font-family: "Cormorant Garamond", Georgia, serif;
font-size: 40px;
font-weight: 700;
color: var(--green-d);
line-height: 1;
}
.donut-center__label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1.4px;
color: var(--muted);
}
.legend { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.legend li {
display: flex;
align-items: center;
gap: 9px;
font-size: 13px;
color: var(--ink-2);
}
.legend .sw { width: 11px; height: 11px; border-radius: 3px; flex: none; }
.legend .lg-val { margin-left: auto; font-weight: 600; color: var(--green-d); }
/* ---------- Leaderboard ---------- */
.table-scroll { overflow-x: auto; }
.lb { width: 100%; border-collapse: collapse; font-size: 14px; }
.lb thead th {
text-align: left;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--muted);
font-weight: 600;
padding: 0 12px 10px;
border-bottom: 1px solid var(--line);
}
.lb th.num-col, .lb td.num-col { text-align: right; }
.lb tbody td { padding: 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.lb tbody tr { transition: background 0.15s ease; }
.lb tbody tr:hover { background: var(--green-50); }
.lb tbody tr:last-child td { border-bottom: 0; }
.rank {
display: grid;
place-items: center;
width: 26px;
height: 26px;
border-radius: 50%;
font-size: 12px;
font-weight: 700;
background: var(--ivory);
color: var(--ink-2);
border: 1px solid var(--line);
}
.rank.top { background: var(--brass-50); color: var(--brass-d); border-color: var(--brass); }
.agent-cell { display: flex; align-items: center; gap: 11px; }
.agent-av {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 50%;
font-size: 12px;
font-weight: 700;
color: var(--paper);
flex: none;
}
.agent-name { font-weight: 600; color: var(--green-d); }
.agent-team { font-size: 12px; color: var(--muted); }
.lb .num-col { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }
.share { display: flex; align-items: center; gap: 8px; min-width: 110px; }
.share-bar { flex: 1; height: 6px; border-radius: 99px; background: var(--line); overflow: hidden; }
.share-bar span {
display: block;
height: 100%;
border-radius: 99px;
background: linear-gradient(90deg, var(--green-700), var(--green));
transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
}
.share-pct { font-size: 12px; color: var(--muted); width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
/* ---------- Feed ---------- */
.feed { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.feed li {
display: grid;
grid-template-columns: 56px 1fr auto;
gap: 12px;
align-items: center;
padding: 10px;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: var(--white);
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.feed li:hover { transform: translateY(-2px); box-shadow: var(--sh-md); }
.thumb {
width: 56px;
height: 48px;
border-radius: var(--r-sm);
position: relative;
overflow: hidden;
box-shadow: inset 0 0 0 1px rgba(31, 61, 52, 0.08);
}
.thumb::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(255,255,255,0.18), rgba(0,0,0,0.12));
}
.feed-addr { font-weight: 600; color: var(--green-d); font-size: 13.5px; line-height: 1.3; }
.feed-meta { font-size: 12px; color: var(--muted); display: flex; gap: 8px; flex-wrap: wrap; }
.feed-meta .badge {
font-size: 10.5px;
font-weight: 600;
padding: 1px 7px;
border-radius: 99px;
background: var(--green-50);
color: var(--green-700);
}
.feed-price {
font-family: "Cormorant Garamond", Georgia, serif;
font-weight: 700;
font-size: 19px;
color: var(--brass-d);
text-align: right;
white-space: nowrap;
}
.feed-when { font-size: 11px; color: var(--muted); text-align: right; }
/* ---------- Foot ---------- */
.foot {
margin-top: 26px;
padding-top: 16px;
border-top: 1px solid var(--line);
font-size: 12px;
color: var(--muted);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 20px);
background: var(--green-d);
color: var(--paper);
padding: 11px 18px;
border-radius: 99px;
font-size: 13px;
font-weight: 500;
box-shadow: var(--sh-lg);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 50;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.toast .tk { color: var(--brass); font-weight: 700; }
@media (prefers-reduced-motion: reduce) {
* { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
/* ---------- Responsive ---------- */
@media (max-width: 1080px) {
.shell { grid-template-columns: 1fr; }
.sidebar {
position: static;
height: auto;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: 16px;
}
.nav { flex-direction: row; flex-wrap: wrap; margin-left: auto; }
.side-card { display: none; }
.grid { grid-template-columns: 1fr; }
.card--chart, .card--donut, .card--table, .card--feed { grid-column: 1; grid-row: auto; }
}
@media (max-width: 760px) {
.kpis { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.main { padding: 22px 16px 30px; }
.topbar h1 { font-size: 30px; }
.kpis { grid-template-columns: 1fr 1fr; gap: 12px; }
.kpi__value { font-size: 30px; }
.spark { display: none; }
.nav { display: none; }
.sidebar { padding: 16px; }
.card { padding: 18px; border-radius: var(--r-md); }
.card h2 { font-size: 22px; }
.hide-sm { display: none; }
.topbar__right { width: 100%; justify-content: space-between; }
.range__btn { padding: 7px 13px; }
.feed li { grid-template-columns: 48px 1fr; }
.feed-price, .feed-when { grid-column: 2; text-align: left; }
}
@media (max-width: 360px) {
.kpis { grid-template-columns: 1fr; }
}(function () {
"use strict";
/* ---------------- Data ---------------- */
// Per-range dataset. Numbers are illustrative & fictional.
var RANGES = {
"7": {
label: "last 7 days",
kpis: { volume: 6.4, gci: 161, units: 7, dom: 19 },
deltas: { volume: 4, gci: 3, units: 2, dom: -6 },
series: [0.7, 0.9, 0.6, 1.1, 0.8, 1.3, 1.0],
axis: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
status: { active: 18, pending: 9, closed: 7, withdrawn: 2 },
agents: [
["Camille Hart", "Vale · Hillside", 2.1, 2, 52, "#1f3d34"],
["Théo Marchand", "Vale · Harbor", 1.6, 2, 41, "#94733f"],
["Priya Raman", "Vale · Midtown", 1.2, 1, 31, "#26493e"],
["Owen Castellan", "Vale · Coastal", 0.9, 1, 23, "#6b7a72"],
["Lina Sørensen", "Vale · Estates", 0.6, 1, 16, "#c98a2b"]
]
},
"30": {
label: "last 30 days",
kpis: { volume: 21.8, gci: 548, units: 24, dom: 23 },
deltas: { volume: 9, gci: 7, units: 11, dom: -4 },
series: [3.1, 4.0, 3.4, 4.6, 3.9, 5.2, 4.4, 5.8],
axis: ["W1", "", "W2", "", "W3", "", "W4", ""],
status: { active: 31, pending: 14, closed: 24, withdrawn: 4 },
agents: [
["Camille Hart", "Vale · Hillside", 6.4, 7, 161, "#1f3d34"],
["Théo Marchand", "Vale · Harbor", 5.1, 6, 128, "#94733f"],
["Priya Raman", "Vale · Midtown", 4.2, 5, 105, "#26493e"],
["Owen Castellan", "Vale · Coastal", 3.5, 4, 88, "#6b7a72"],
["Lina Sørensen", "Vale · Estates", 2.6, 2, 66, "#c98a2b"]
]
},
"90": {
label: "last 90 days",
kpis: { volume: 64.2, gci: 1611, units: 71, dom: 27 },
deltas: { volume: 12, gci: 14, units: 8, dom: -9 },
series: [5.4, 6.8, 6.1, 7.9, 7.2, 8.6, 8.0, 9.4, 9.1, 10.3, 9.8, 11.2],
axis: ["Mar", "", "", "Apr", "", "", "May", "", "", "Jun", "", ""],
status: { active: 42, pending: 19, closed: 71, withdrawn: 6 },
agents: [
["Camille Hart", "Vale · Hillside", 18.9, 20, 474, "#1f3d34"],
["Théo Marchand", "Vale · Harbor", 14.7, 17, 369, "#94733f"],
["Priya Raman", "Vale · Midtown", 12.1, 14, 304, "#26493e"],
["Owen Castellan", "Vale · Coastal", 10.4, 12, 261, "#6b7a72"],
["Lina Sørensen", "Vale · Estates", 8.1, 8, 203, "#c98a2b"]
]
},
"365": {
label: "year to date",
kpis: { volume: 248.6, gci: 6237, units: 286, dom: 31 },
deltas: { volume: 18, gci: 21, units: 15, dom: -12 },
series: [16, 19, 17, 22, 24, 21, 27, 29, 26, 31, 28, 34],
axis: ["Jan", "", "", "Apr", "", "", "Jul", "", "", "Oct", "", ""],
status: { active: 47, pending: 22, closed: 286, withdrawn: 11 },
agents: [
["Camille Hart", "Vale · Hillside", 72.4, 79, 1816, "#1f3d34"],
["Théo Marchand", "Vale · Harbor", 58.9, 66, 1478, "#94733f"],
["Priya Raman", "Vale · Midtown", 46.2, 54, 1159, "#26493e"],
["Owen Castellan", "Vale · Coastal", 39.8, 47, 998, "#6b7a72"],
["Lina Sørensen", "Vale · Estates", 31.3, 40, 786, "#c98a2b"]
]
}
};
var CLOSINGS = [
["218 Marisol Terrace", "Hillside", "4 bd · 3 ba", 2480000, "Camille Hart", "linear-gradient(140deg,#3a5a4e,#1f3d34 55%,#16302a)", "2d ago"],
["77 Cedar Walk", "Midtown", "3 bd · 2 ba", 1145000, "Priya Raman", "linear-gradient(140deg,#c79a63,#94733f 60%,#6e5430)", "3d ago"],
["9 Harbor Light Ln", "Harbor", "5 bd · 4 ba", 3920000, "Théo Marchand", "linear-gradient(140deg,#6c8a9a,#3f6172 55%,#28414d)", "5d ago"],
["1340 Olive Crest", "Coastal", "2 bd · 2 ba", 865000, "Owen Castellan", "linear-gradient(140deg,#caa37a,#9c7c54 60%,#6b5536)", "6d ago"],
["52 Bellrose Court", "Estates", "6 bd · 5 ba", 5260000, "Lina Sørensen", "linear-gradient(140deg,#4a6b5d,#2c5043 55%,#1c382f)", "1w ago"]
];
var STATUS_LABELS = { active: "Active", pending: "Pending", closed: "Closed", withdrawn: "Withdrawn" };
var STATUS_COLORS = { active: "#1f3d34", pending: "#b08d57", closed: "#2f9e6f", withdrawn: "#c4503e" };
/* ---------------- Helpers ---------------- */
function $(s, ctx) { return (ctx || document).querySelector(s); }
function $all(s, ctx) { return Array.prototype.slice.call((ctx || document).querySelectorAll(s)); }
var prefersReduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function fmtMoney(n) {
return "$" + n.toLocaleString("en-US");
}
function fmtVal(v, kind) {
if (kind === "m") return (Math.round(v * 10) / 10).toFixed(1);
if (kind === "k") return Math.round(v).toLocaleString("en-US");
return Math.round(v).toLocaleString("en-US");
}
function initials(name) {
return name.split(" ").map(function (w) { return w[0]; }).join("").slice(0, 2).toUpperCase();
}
var toastEl = $("#toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.innerHTML = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2400);
}
/* ---------------- Count-up numbers ---------------- */
function animateNum(el, to, kind) {
var from = parseFloat(el.getAttribute("data-current")) || 0;
el.setAttribute("data-current", to);
if (prefersReduced) { el.textContent = fmtVal(to, kind); return; }
var start = performance.now();
var dur = 700;
function step(now) {
var p = Math.min(1, (now - start) / dur);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = fmtVal(from + (to - from) * eased, kind);
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
/* ---------------- Sparklines ---------------- */
function buildSpark(host, series) {
var w = 86, h = 30, pad = 3;
var min = Math.min.apply(null, series), max = Math.max.apply(null, series);
var span = max - min || 1;
var pts = series.map(function (v, i) {
var x = pad + (i / (series.length - 1)) * (w - pad * 2);
var y = h - pad - ((v - min) / span) * (h - pad * 2);
return x.toFixed(1) + "," + y.toFixed(1);
});
host.innerHTML =
'<svg viewBox="0 0 ' + w + ' ' + h + '"><path d="M' + pts.join(" L") + '"/></svg>';
}
/* ---------------- Trend chart ---------------- */
var chartW = 720, chartH = 280, padX = 16, padY = 24;
var linePath = $("#linePath"), areaPath = $("#areaPath"), dotsG = $("#dots"),
gridG = $("#gridLines"), axisEl = $("#chartAxis"), tipEl = $("#chartTip");
var chartPoints = [];
function buildChart(data) {
var series = data.series;
var max = Math.max.apply(null, series) * 1.12;
var min = 0;
var n = series.length;
// grid lines
var gl = "";
for (var g = 0; g <= 4; g++) {
var gy = padY + (g / 4) * (chartH - padY * 2);
gl += '<line x1="0" y1="' + gy.toFixed(1) + '" x2="' + chartW + '" y2="' + gy.toFixed(1) + '"/>';
}
gridG.innerHTML = gl;
chartPoints = series.map(function (v, i) {
var x = padX + (i / (n - 1)) * (chartW - padX * 2);
var y = chartH - padY - ((v - min) / (max - min)) * (chartH - padY * 2);
return { x: x, y: y, v: v };
});
var d = chartPoints.map(function (p, i) {
return (i === 0 ? "M" : "L") + p.x.toFixed(1) + " " + p.y.toFixed(1);
}).join(" ");
var area = "M" + chartPoints[0].x.toFixed(1) + " " + (chartH - padY) +
" L" + chartPoints.map(function (p) { return p.x.toFixed(1) + " " + p.y.toFixed(1); }).join(" L") +
" L" + chartPoints[n - 1].x.toFixed(1) + " " + (chartH - padY) + " Z";
linePath.setAttribute("d", d);
areaPath.setAttribute("d", area);
// line draw animation
var len = linePath.getTotalLength();
if (!prefersReduced) {
linePath.style.transition = "none";
linePath.style.strokeDasharray = len;
linePath.style.strokeDashoffset = len;
// force reflow
void linePath.getBoundingClientRect();
linePath.style.transition = "stroke-dashoffset 0.9s cubic-bezier(0.22,1,0.36,1)";
linePath.style.strokeDashoffset = "0";
} else {
linePath.style.strokeDasharray = "none";
linePath.style.strokeDashoffset = "0";
}
areaPath.classList.remove("in");
void areaPath.getBoundingClientRect();
areaPath.classList.add("in");
// dots
dotsG.innerHTML = chartPoints.map(function (p, i) {
return '<circle class="dot" data-i="' + i + '" cx="' + p.x.toFixed(1) + '" cy="' + p.y.toFixed(1) + '" r="4"></circle>';
}).join("");
// axis labels
axisEl.innerHTML = data.axis.map(function (a) { return "<span>" + a + "</span>"; }).join("");
}
function chartTipShow(i) {
var p = chartPoints[i];
if (!p) return;
var host = $("#chart");
var rect = host.getBoundingClientRect();
tipEl.hidden = false;
tipEl.innerHTML = "<span>$" + p.v.toFixed(1) + "M</span> closed";
tipEl.style.left = (p.x / chartW * rect.width) + "px";
tipEl.style.top = (p.y / chartH * 280) + "px";
}
if (dotsG) {
dotsG.addEventListener("mouseover", function (e) {
var d = e.target.getAttribute && e.target.getAttribute("data-i");
if (d !== null && d !== undefined) chartTipShow(parseInt(d, 10));
});
dotsG.addEventListener("mouseout", function () { tipEl.hidden = true; });
}
/* ---------------- Donut ---------------- */
var R = 76, CIRC = 2 * Math.PI * R;
function buildDonut(status) {
var keys = ["active", "pending", "closed", "withdrawn"];
var total = keys.reduce(function (s, k) { return s + status[k]; }, 0);
var offset = 0;
keys.forEach(function (k) {
var seg = document.getElementById("seg-" + k);
var frac = total ? status[k] / total : 0;
var len = frac * CIRC;
// small reset then animate
seg.setAttribute("stroke-dasharray", "0 " + CIRC);
seg.setAttribute("stroke-dashoffset", -offset);
(function (s, l) {
requestAnimationFrame(function () {
requestAnimationFrame(function () {
s.setAttribute("stroke-dasharray", l.toFixed(2) + " " + (CIRC - l).toFixed(2));
});
});
})(seg, len);
offset += len;
});
$("#donutTotal").textContent = total;
var legend = $("#legend");
legend.innerHTML = keys.map(function (k) {
return '<li><span class="sw" style="background:' + STATUS_COLORS[k] + '"></span>' +
STATUS_LABELS[k] + '<span class="lg-val">' + status[k] + "</span></li>";
}).join("");
}
/* ---------------- Leaderboard ---------------- */
function buildLeaderboard(agents) {
var maxVol = Math.max.apply(null, agents.map(function (a) { return a[2]; }));
var totalVol = agents.reduce(function (s, a) { return s + a[2]; }, 0);
var body = $("#lbBody");
body.innerHTML = agents.map(function (a, i) {
var name = a[0], team = a[1], vol = a[2], units = a[3], gci = a[4], color = a[5];
var share = totalVol ? Math.round((vol / totalVol) * 100) : 0;
return "<tr>" +
'<td><span class="rank ' + (i === 0 ? "top" : "") + '">' + (i + 1) + "</span></td>" +
'<td><div class="agent-cell">' +
'<span class="agent-av" style="background:' + color + '">' + initials(name) + "</span>" +
"<div><div class=\"agent-name\">" + name + "</div><div class=\"agent-team\">" + team + "</div></div>" +
"</div></td>" +
'<td class="num-col">$' + vol.toFixed(1) + "M</td>" +
'<td class="num-col">' + units + "</td>" +
'<td class="num-col hide-sm">$' + gci + "K</td>" +
'<td><div class="share"><div class="share-bar"><span style="width:' +
Math.round((vol / maxVol) * 100) + '%"></span></div><span class="share-pct">' + share + "%</span></div></td>" +
"</tr>";
}).join("");
}
/* ---------------- Closings feed ---------------- */
function buildFeed() {
var feed = $("#feed");
feed.innerHTML = CLOSINGS.map(function (c) {
var addr = c[0], hood = c[1], facts = c[2], price = c[3], agent = c[4], grad = c[5], when = c[6];
return "<li>" +
'<span class="thumb" style="background:' + grad + '"></span>' +
"<div>" +
'<div class="feed-addr">' + addr + "</div>" +
'<div class="feed-meta"><span class="badge">' + hood + "</span><span>" + facts + "</span><span>· " + agent + "</span></div>" +
"</div>" +
"<div><div class=\"feed-price\">" + fmtMoney(price) + "</div><div class=\"feed-when\">" + when + "</div></div>" +
"</li>";
}).join("");
}
/* ---------------- Render a range ---------------- */
function render(key, announce) {
var data = RANGES[key];
if (!data) return;
// KPIs
$all("[data-kpi]").forEach(function (el) {
var kpi = el.getAttribute("data-kpi");
var kind = el.getAttribute("data-fmt");
animateNum(el, data.kpis[kpi], kind);
});
// deltas
$all("[data-delta]").forEach(function (el) {
var key2 = el.getAttribute("data-delta");
var d = data.deltas[key2];
var card = el.closest(".kpi");
var positiveGood = key2 !== "dom"; // lower DOM is good
var arrow = d >= 0 ? "▲" : "▼";
el.textContent = arrow + " " + Math.abs(d) + "%";
var good = (d >= 0) === positiveGood;
el.className = "kpi__delta " + (good ? "up" : "down");
});
// sparks
$all("[data-spark]").forEach(function (el) {
buildSpark(el, data.series);
});
// chart total
var chartTotalEl = $("#chartTotal");
var totalSeries = data.series.reduce(function (s, v) { return s + v; }, 0);
if (prefersReduced) {
chartTotalEl.textContent = totalSeries.toFixed(1);
} else {
var ct0 = parseFloat(chartTotalEl.getAttribute("data-cur")) || 0;
chartTotalEl.setAttribute("data-cur", totalSeries);
var s0 = performance.now();
(function () {
function step(now) {
var p = Math.min(1, (now - s0) / 700);
var e = 1 - Math.pow(1 - p, 3);
chartTotalEl.textContent = (ct0 + (totalSeries - ct0) * e).toFixed(1);
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
})();
}
buildChart(data);
buildDonut(data.status);
buildLeaderboard(data.agents);
if (announce) toast('Range set to <span class="tk">' + data.label + "</span>");
}
/* ---------------- Range toggle ---------------- */
$all(".range__btn").forEach(function (btn) {
btn.addEventListener("click", function () {
$all(".range__btn").forEach(function (b) {
b.classList.remove("is-active");
b.removeAttribute("aria-pressed");
});
btn.classList.add("is-active");
btn.setAttribute("aria-pressed", "true");
render(btn.getAttribute("data-range"), true);
});
});
/* ---------------- Init ---------------- */
buildFeed();
render("90", false);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Maison & Vale — Brokerage Dashboard</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>
<div class="shell">
<!-- Sidebar -->
<aside class="sidebar" aria-label="Primary navigation">
<div class="brand">
<span class="brand-mark" aria-hidden="true">M&V</span>
<span class="brand-name">Maison & Vale</span>
</div>
<nav class="nav">
<a class="nav-item is-active" href="#" aria-current="page"><span class="dot" aria-hidden="true"></span>Overview</a>
<a class="nav-item" href="#"><span class="dot" aria-hidden="true"></span>Listings</a>
<a class="nav-item" href="#"><span class="dot" aria-hidden="true"></span>Agents</a>
<a class="nav-item" href="#"><span class="dot" aria-hidden="true"></span>Transactions</a>
<a class="nav-item" href="#"><span class="dot" aria-hidden="true"></span>Reports</a>
</nav>
<div class="side-card">
<p class="side-card__eyebrow">Q2 target</p>
<p class="side-card__big" id="targetPct">82%</p>
<div class="side-bar"><span id="targetFill" style="width:82%"></span></div>
<p class="side-card__note">$48.0M of $58.5M closed volume</p>
</div>
</aside>
<!-- Main -->
<main class="main">
<header class="topbar">
<div>
<p class="eyebrow">Brokerage performance</p>
<h1>Good afternoon, Adrienne</h1>
<p class="sub">Here's how the Vale Group is tracking this period.</p>
</div>
<div class="topbar__right">
<div class="range" role="group" aria-label="Date range">
<button class="range__btn" data-range="7" type="button">7D</button>
<button class="range__btn" data-range="30" type="button">30D</button>
<button class="range__btn is-active" data-range="90" type="button" aria-pressed="true">90D</button>
<button class="range__btn" data-range="365" type="button">YTD</button>
</div>
<div class="avatar" aria-hidden="true">AV</div>
</div>
</header>
<!-- KPI row -->
<section class="kpis" aria-label="Key metrics">
<article class="kpi">
<p class="kpi__label">Total volume</p>
<p class="kpi__value"><span class="cur">$</span><span class="num" data-fmt="m" data-kpi="volume">0</span><span class="suf">M</span></p>
<p class="kpi__delta up" data-delta="volume">▲ 0%</p>
<div class="spark" data-spark="volume" aria-hidden="true"></div>
</article>
<article class="kpi">
<p class="kpi__label">Gross commission (GCI)</p>
<p class="kpi__value"><span class="cur">$</span><span class="num" data-fmt="k" data-kpi="gci">0</span><span class="suf">K</span></p>
<p class="kpi__delta up" data-delta="gci">▲ 0%</p>
<div class="spark" data-spark="gci" aria-hidden="true"></div>
</article>
<article class="kpi">
<p class="kpi__label">Units closed</p>
<p class="kpi__value"><span class="num" data-fmt="int" data-kpi="units">0</span></p>
<p class="kpi__delta up" data-delta="units">▲ 0%</p>
<div class="spark" data-spark="units" aria-hidden="true"></div>
</article>
<article class="kpi">
<p class="kpi__label">Avg. days on market</p>
<p class="kpi__value"><span class="num" data-fmt="int" data-kpi="dom">0</span><span class="suf"> days</span></p>
<p class="kpi__delta down" data-delta="dom">▼ 0%</p>
<div class="spark" data-spark="dom" aria-hidden="true"></div>
</article>
</section>
<div class="grid">
<!-- Trend chart -->
<section class="card card--chart" aria-label="Sales volume trend">
<div class="card__head">
<div>
<h2>Sales volume</h2>
<p class="card__sub">Closed dollar volume over the selected period</p>
</div>
<p class="chart-total"><span class="cur">$</span><span id="chartTotal">0</span><span class="suf">M</span></p>
</div>
<div class="chart" id="chart">
<svg viewBox="0 0 720 280" preserveAspectRatio="none" role="img" aria-label="Sales volume area chart">
<defs>
<linearGradient id="areaFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(176,141,87,0.34)" />
<stop offset="100%" stop-color="rgba(176,141,87,0)" />
</linearGradient>
</defs>
<g class="grid-lines" id="gridLines"></g>
<path id="areaPath" class="area" d="" fill="url(#areaFill)" />
<path id="linePath" class="line" d="" fill="none" />
<g id="dots"></g>
</svg>
<div class="chart-axis" id="chartAxis" aria-hidden="true"></div>
<div class="chart-tip" id="chartTip" role="status" aria-live="polite" hidden></div>
</div>
</section>
<!-- Donut -->
<section class="card card--donut" aria-label="Listings by status">
<div class="card__head">
<h2>Listings by status</h2>
</div>
<div class="donut-wrap">
<svg class="donut" viewBox="0 0 200 200" role="img" aria-label="Listings by status donut chart">
<circle class="donut-track" cx="100" cy="100" r="76" />
<circle class="donut-seg" id="seg-active" cx="100" cy="100" r="76" />
<circle class="donut-seg" id="seg-pending" cx="100" cy="100" r="76" />
<circle class="donut-seg" id="seg-closed" cx="100" cy="100" r="76" />
<circle class="donut-seg" id="seg-withdrawn" cx="100" cy="100" r="76" />
</svg>
<div class="donut-center">
<p class="donut-center__num" id="donutTotal">0</p>
<p class="donut-center__label">listings</p>
</div>
</div>
<ul class="legend" id="legend"></ul>
</section>
<!-- Leaderboard -->
<section class="card card--table" aria-label="Top agents leaderboard">
<div class="card__head">
<div>
<h2>Top agents</h2>
<p class="card__sub">Ranked by closed volume this period</p>
</div>
</div>
<div class="table-scroll">
<table class="lb">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Agent</th>
<th scope="col" class="num-col">Volume</th>
<th scope="col" class="num-col">Units</th>
<th scope="col" class="num-col hide-sm">GCI</th>
<th scope="col">Share</th>
</tr>
</thead>
<tbody id="lbBody"></tbody>
</table>
</div>
</section>
<!-- Recent closings -->
<section class="card card--feed" aria-label="Recent closings">
<div class="card__head">
<div>
<h2>Recent closings</h2>
<p class="card__sub">Last settled transactions</p>
</div>
</div>
<ul class="feed" id="feed"></ul>
</section>
</div>
<footer class="foot">
<p>Maison & Vale Realty · Internal dashboard · Figures update on range change.</p>
</footer>
</main>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Brokerage Dashboard
A back-office overview for a boutique residential brokerage, framed in the editorial real-estate palette of ivory paper, deep green, and brass hairlines. A deep-green sidebar carries the Maison & Vale brand, primary navigation, and a quarterly-target gauge, while the main column leads with a four-tile KPI row — total volume, gross commission income, units closed, and average days on market — each tile pairing a serif headline number with a colour-coded period delta and a tiny SVG sparkline.
Below the metrics, a brass-filled SVG area chart traces closed sales volume across the period, with a gridded backdrop, an animated draw-on line, and hover tooltips on each data point. A donut chart breaks the listing book into Active, Pending, Closed, and Withdrawn shares beside a live legend; a top-agents leaderboard ranks producers by closed volume with avatars, units, GCI, and proportional share bars; and a recent-closings feed lists settled transactions with gradient property thumbnails, neighbourhood badges, and prices.
The date-range toggle (7D / 30D / 90D / YTD) is the centerpiece interaction: selecting a range swaps the underlying dataset and re-renders everything at once — KPI numbers count up to their new values, deltas flip direction and colour, sparklines redraw, the area chart re-animates its line and fill, the donut segments retween, and the leaderboard rebuilds — with a small toast confirming the active window. The layout collapses gracefully from a two-column grid to a single stack down to ~360px, and respects reduced-motion preferences.
Illustrative UI only — sample listings and data are fictional; not a real real-estate service.