Banking — Account Overview
A trust-first banking account overview dashboard with a total balance hero and sparkline, checking, savings and credit account cards with masked numbers, a conic-gradient spending donut with category legend, a filterable recent transactions feed showing credit and debit colors plus cleared, pending and failed status pills, quick actions for transfer, pay and deposit, and an upcoming bills panel. Hide and show balances, switch accounts and filter activity, all in self-contained vanilla JS.
MCP
Code
:root {
--navy: #0e1b3a;
--navy-2: #16264d;
--ink: #0e1726;
--ink-2: #3a4660;
--muted: #697089;
--accent: #3b6ef6;
--accent-d: #2a55cc;
--accent-50: #eaf0ff;
--teal: #0fb5a6;
--violet: #7c5cff;
--bg: #f5f7fb;
--surface: #ffffff;
--line: rgba(14, 27, 58, 0.10);
--line-2: rgba(14, 27, 58, 0.18);
--ok: #1f9d62;
--warn: #d9982b;
--danger: #d4493e;
--credit: #1f9d62;
--debit: #0e1726;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(14, 27, 58, 0.06), 0 2px 6px rgba(14, 27, 58, 0.05);
--sh-2: 0 4px 14px rgba(14, 27, 58, 0.08), 0 12px 30px rgba(14, 27, 58, 0.07);
--sh-3: 0 10px 30px rgba(14, 27, 58, 0.14), 0 30px 60px rgba(14, 27, 58, 0.16);
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
}
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.mono { font-variant-numeric: tabular-nums; }
/* ===== Layout ===== */
.app {
display: grid;
grid-template-columns: 248px 1fr;
min-height: 100vh;
}
/* ===== Sidebar ===== */
.sidebar {
background: linear-gradient(180deg, var(--navy) 0%, var(--navy-2) 100%);
color: #cfd8ee;
padding: 22px 18px;
display: flex;
flex-direction: column;
gap: 26px;
position: sticky;
top: 0;
height: 100vh;
}
.brand { display: flex; align-items: center; gap: 11px; }
.brand-mark {
width: 36px; height: 36px;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent), var(--violet));
display: grid; place-items: center;
color: #fff; font-weight: 800; font-size: 18px;
box-shadow: 0 6px 16px rgba(59, 110, 246, 0.45);
}
.brand-name { font-weight: 700; font-size: 18px; color: #fff; letter-spacing: -0.01em; }
.nav { display: flex; flex-direction: column; gap: 3px; }
.nav-item {
display: flex; align-items: center; gap: 11px;
padding: 10px 12px;
border-radius: var(--r-sm);
color: #aab6d4;
text-decoration: none;
font-size: 14px; font-weight: 500;
transition: background .16s, color .16s;
}
.nav-item:hover { background: rgba(255, 255, 255, 0.06); color: #fff; }
.nav-item.is-active { background: rgba(59, 110, 246, 0.22); color: #fff; }
.nav-ico { width: 18px; text-align: center; opacity: .9; }
.sidebar-card {
margin-top: auto;
display: flex; align-items: center; gap: 11px;
padding: 13px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--r-md);
}
.sc-lock { font-size: 18px; }
.sc-title { font-size: 13px; font-weight: 600; color: #fff; }
.sc-sub { font-size: 11.5px; color: #93a0c2; }
/* ===== Main ===== */
.main { padding: 26px 30px 44px; max-width: 1240px; }
.topbar {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 16px; margin-bottom: 22px;
}
.page-title { margin: 0; font-size: 23px; font-weight: 800; letter-spacing: -0.02em; }
.page-sub { margin: 3px 0 0; color: var(--muted); font-size: 14px; }
.topbar-actions { display: flex; align-items: center; gap: 12px; }
.avatar {
width: 40px; height: 40px; border-radius: 50%;
background: linear-gradient(135deg, var(--teal), var(--accent));
color: #fff; font-weight: 700; font-size: 14px;
display: grid; place-items: center;
box-shadow: var(--sh-1);
}
/* ===== Buttons ===== */
.btn {
font-family: inherit; font-size: 14px; font-weight: 600;
border: 1px solid var(--line-2);
background: var(--surface);
color: var(--ink);
padding: 9px 14px;
border-radius: var(--r-sm);
cursor: pointer;
display: inline-flex; align-items: center; gap: 8px;
transition: background .15s, border-color .15s, transform .08s, box-shadow .15s;
}
.btn:hover { border-color: var(--accent); color: var(--accent-d); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid var(--accent-50); outline-offset: 1px; }
.btn-ghost { background: var(--surface); box-shadow: var(--sh-1); }
.btn-sm { padding: 6px 11px; font-size: 13px; }
.eye-ico { font-size: 15px; }
/* ===== Grid ===== */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
padding: 20px;
}
.card-head {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 16px;
}
.card-title { margin: 0; font-size: 15.5px; font-weight: 700; letter-spacing: -0.01em; }
.card-tag {
font-size: 12px; font-weight: 600; color: var(--muted);
background: var(--bg); border: 1px solid var(--line);
padding: 3px 9px; border-radius: 999px;
}
.card-tag.warn { color: var(--warn); background: #fdf6e8; border-color: rgba(217, 152, 43, 0.3); }
/* ===== Hero ===== */
.hero {
grid-column: 1 / -1;
background: linear-gradient(135deg, var(--navy) 0%, var(--navy-2) 60%, #1c3268 100%);
color: #fff;
border: none;
box-shadow: var(--sh-3);
position: relative;
overflow: hidden;
}
.hero::after {
content: ""; position: absolute; right: -60px; top: -60px;
width: 260px; height: 260px; border-radius: 50%;
background: radial-gradient(circle, rgba(124, 92, 255, 0.4), transparent 70%);
pointer-events: none;
}
.hero-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; position: relative; z-index: 1; }
.hero-label {
font-size: 13px; color: #aebbdd; font-weight: 500;
display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
}
.verified {
font-size: 11px; font-weight: 700; color: #8df0e4;
background: rgba(15, 181, 166, 0.18);
padding: 2px 8px; border-radius: 999px;
border: 1px solid rgba(15, 181, 166, 0.35);
}
.hero-amount { font-size: 40px; font-weight: 800; letter-spacing: -0.02em; margin-top: 6px; }
.hero-delta { margin-top: 6px; font-size: 13.5px; font-weight: 600; color: #8df0e4; }
.hero-delta.up { color: #8df0e4; }
.hero-delta-sub { color: #9fade0; font-weight: 500; margin-left: 4px; }
.hero-spark { width: 200px; max-width: 42%; }
.hero-spark svg { width: 100%; height: 60px; display: block; }
.spark-line { fill: none; stroke: #6ee7da; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.spark-fill { fill: url(#none); stroke: none; }
.hero-spark svg .spark-fill { fill: rgba(110, 231, 218, 0.14); }
.quick-actions {
display: grid; grid-template-columns: repeat(4, 1fr);
gap: 10px; margin-top: 22px; position: relative; z-index: 1;
}
.qa {
font-family: inherit; font-size: 13.5px; font-weight: 600;
color: #fff; cursor: pointer;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: var(--r-md);
padding: 12px 8px;
display: flex; flex-direction: column; align-items: center; gap: 6px;
transition: background .15s, transform .08s;
}
.qa:hover { background: rgba(255, 255, 255, 0.16); }
.qa:active { transform: translateY(1px); }
.qa:focus-visible { outline: 3px solid rgba(110, 231, 218, 0.5); outline-offset: 1px; }
.qa-ico {
font-size: 17px;
width: 34px; height: 34px; border-radius: 9px;
background: rgba(255, 255, 255, 0.12);
display: grid; place-items: center;
}
/* ===== Spending donut ===== */
.donut-wrap { display: grid; place-items: center; margin: 6px 0 16px; }
.donut {
width: 168px; height: 168px; border-radius: 50%;
position: relative;
display: grid; place-items: center;
transition: transform .2s;
}
.donut-center {
width: 112px; height: 112px; border-radius: 50%;
background: var(--surface);
display: grid; place-items: center; text-align: center;
box-shadow: inset 0 0 0 1px var(--line);
}
.donut-total { font-size: 22px; font-weight: 800; letter-spacing: -0.01em; }
.donut-sub { font-size: 11.5px; color: var(--muted); }
.legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.legend li {
display: flex; align-items: center; gap: 10px;
font-size: 13px;
padding: 4px 0;
cursor: default;
}
.legend .dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
.legend .lg-name { font-weight: 500; color: var(--ink-2); }
.legend .lg-amt { margin-left: auto; font-weight: 700; }
.legend .lg-pct { color: var(--muted); font-size: 12px; width: 38px; text-align: right; }
/* ===== Accounts ===== */
.accounts { grid-column: 1 / -1; }
.accounts-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.acct-list {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
}
.acct {
border-radius: var(--r-lg);
padding: 18px;
color: #fff;
position: relative;
overflow: hidden;
cursor: pointer;
box-shadow: var(--sh-2);
border: 1px solid rgba(255, 255, 255, 0.08);
transition: transform .16s, box-shadow .16s;
min-height: 158px;
display: flex; flex-direction: column; justify-content: space-between;
}
.acct:hover { transform: translateY(-3px); box-shadow: var(--sh-3); }
.acct:focus-visible { outline: 3px solid var(--accent-50); outline-offset: 2px; }
.acct.is-active { outline: 2.5px solid var(--accent); outline-offset: 2px; }
.acct.checking { background: linear-gradient(135deg, #1d3a8a, #3b6ef6); }
.acct.savings { background: linear-gradient(135deg, #0a6f66, var(--teal)); }
.acct.credit { background: linear-gradient(135deg, #4a2d8f, var(--violet)); }
.acct::after {
content: ""; position: absolute; right: -40px; bottom: -50px;
width: 150px; height: 150px; border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
}
.acct-top { display: flex; justify-content: space-between; align-items: flex-start; position: relative; z-index: 1; }
.acct-type { font-size: 12.5px; font-weight: 600; opacity: .9; letter-spacing: .02em; }
.acct-chip {
width: 30px; height: 22px; border-radius: 5px;
background: linear-gradient(135deg, #f6d77c, #d9a93b);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}
.acct-num { font-size: 13px; letter-spacing: .06em; margin-top: 14px; opacity: .92; position: relative; z-index: 1; }
.acct-bot { position: relative; z-index: 1; }
.acct-bal-label { font-size: 11.5px; opacity: .82; }
.acct-bal { font-size: 22px; font-weight: 800; letter-spacing: -0.01em; margin-top: 2px; }
.acct.credit .acct-bal-label::before { content: ""; }
/* ===== Transactions ===== */
.tx-filters { display: flex; gap: 6px; }
.chip {
font-family: inherit; font-size: 12.5px; font-weight: 600;
color: var(--muted);
background: var(--bg);
border: 1px solid var(--line);
padding: 5px 11px; border-radius: 999px;
cursor: pointer; transition: all .14s;
}
.chip:hover { color: var(--accent-d); border-color: var(--line-2); }
.chip.is-active { background: var(--accent); color: #fff; border-color: var(--accent); }
.chip:focus-visible { outline: 3px solid var(--accent-50); outline-offset: 1px; }
.tx-list { list-style: none; margin: 0; padding: 0; }
.tx-row {
display: grid;
grid-template-columns: 40px 1fr auto;
align-items: center; gap: 13px;
padding: 11px 4px;
border-bottom: 1px solid var(--line);
}
.tx-row:last-child { border-bottom: none; }
.tx-ico {
width: 40px; height: 40px; border-radius: 11px;
display: grid; place-items: center;
font-size: 17px; font-weight: 700; color: #fff;
}
.tx-mid { min-width: 0; }
.tx-name { font-size: 14px; font-weight: 600; }
.tx-meta { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 7px; margin-top: 1px; }
.status {
font-size: 10.5px; font-weight: 700; padding: 1.5px 7px; border-radius: 999px;
text-transform: uppercase; letter-spacing: .03em;
}
.status.cleared { color: var(--ok); background: rgba(31, 157, 98, 0.12); }
.status.pending { color: var(--warn); background: rgba(217, 152, 43, 0.14); }
.status.failed { color: var(--danger); background: rgba(212, 73, 62, 0.12); }
.tx-amt { font-size: 14.5px; font-weight: 700; text-align: right; }
.tx-amt.credit { color: var(--credit); }
.tx-amt.debit { color: var(--debit); }
.tx-empty { text-align: center; color: var(--muted); font-size: 13.5px; padding: 26px 0; }
/* ===== Bills ===== */
.bills-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; }
.bill {
display: grid; grid-template-columns: 38px 1fr auto; align-items: center; gap: 12px;
padding: 12px; border: 1px solid var(--line); border-radius: var(--r-md);
transition: border-color .14s, background .14s;
}
.bill:hover { border-color: var(--line-2); background: #fbfcfe; }
.bill-ico { width: 38px; height: 38px; border-radius: 10px; background: var(--accent-50); color: var(--accent-d); display: grid; place-items: center; font-size: 16px; }
.bill-name { font-size: 13.5px; font-weight: 600; }
.bill-due { font-size: 12px; color: var(--muted); margin-top: 1px; }
.bill-due.soon { color: var(--warn); font-weight: 600; }
.bill-right { text-align: right; }
.bill-amt { font-size: 14px; font-weight: 700; }
.bill-pay {
margin-top: 4px; font-family: inherit; font-size: 11.5px; font-weight: 700;
color: var(--accent-d); background: var(--accent-50); border: none;
padding: 3px 10px; border-radius: 999px; cursor: pointer;
}
.bill-pay:hover { background: #dde7ff; }
/* ===== Hidden balance ===== */
.is-hidden-amt { letter-spacing: .12em; }
/* ===== Toast ===== */
.toast-wrap { position: fixed; bottom: 22px; right: 22px; display: flex; flex-direction: column; gap: 10px; z-index: 50; }
.toast {
background: var(--ink); color: #fff;
padding: 12px 16px; border-radius: var(--r-md);
font-size: 13.5px; font-weight: 500;
box-shadow: var(--sh-3);
display: flex; align-items: center; gap: 9px;
transform: translateY(12px); opacity: 0;
animation: toastIn .25s forwards;
max-width: 320px;
}
.toast.out { animation: toastOut .25s forwards; }
.toast .t-ico { color: var(--teal); font-weight: 800; }
@keyframes toastIn { to { transform: translateY(0); opacity: 1; } }
@keyframes toastOut { to { transform: translateY(12px); opacity: 0; } }
/* ===== Responsive ===== */
@media (max-width: 980px) {
.app { grid-template-columns: 1fr; }
.sidebar {
position: static; height: auto; flex-direction: row; align-items: center;
flex-wrap: wrap; gap: 14px;
}
.nav { flex-direction: row; flex-wrap: wrap; }
.sidebar-card { margin: 0; }
.acct-list { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 760px) {
.grid { grid-template-columns: 1fr; }
.acct-list { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
.main { padding: 18px 16px 36px; }
.page-title { font-size: 20px; }
.hero-amount { font-size: 32px; }
.hero-spark { display: none; }
.quick-actions { grid-template-columns: repeat(2, 1fr); }
.topbar { flex-direction: column; align-items: flex-start; }
.topbar-actions { width: 100%; justify-content: space-between; }
.card-head { flex-wrap: wrap; gap: 8px; }
.tx-filters { flex-wrap: wrap; }
.nav-item span:not(.nav-ico) { display: none; }
}(function () {
"use strict";
// ---------- Data (fictional) ----------
var accounts = [
{ id: "chk", type: "Checking", name: "Everyday Checking", num: "4821", balance: 12480.55, kind: "checking" },
{ id: "sav", type: "Savings", name: "High-Yield Savings", num: "9037", balance: 38420.18, kind: "savings" },
{ id: "crd", type: "Credit", name: "Aurora Rewards Card", num: "4242", balance: -2683.11, kind: "credit" }
];
var spending = [
{ name: "Groceries", amount: 842, color: "#3b6ef6" },
{ name: "Dining", amount: 631, color: "#0fb5a6" },
{ name: "Transport", amount: 498, color: "#7c5cff" },
{ name: "Shopping", amount: 612, color: "#d9982b" },
{ name: "Bills & Utilities", amount: 557, color: "#697089" }
];
var transactions = [
{ name: "Salary — Lumen Studios", merchant: "Direct deposit", amount: 4250.00, dir: "in", status: "cleared", date: "Jun 14", acct: "chk", color: "#1f9d62", ico: "↓" },
{ name: "Whole Foods Market", merchant: "Groceries", amount: -86.42, dir: "out", status: "cleared", date: "Jun 13", acct: "chk", color: "#3b6ef6", ico: "🛒" },
{ name: "Aurora Card payment", merchant: "Transfer", amount: -500.00, dir: "out", status: "pending", date: "Jun 13", acct: "crd", color: "#7c5cff", ico: "⇄" },
{ name: "Blue Bottle Coffee", merchant: "Dining", amount: -7.80, dir: "out", status: "cleared", date: "Jun 12", acct: "chk", color: "#0fb5a6", ico: "☕" },
{ name: "Northwind Transit", merchant: "Transport", amount: -42.00, dir: "out", status: "cleared", date: "Jun 12", acct: "chk", color: "#7c5cff", ico: "🚆" },
{ name: "Refund — Atlas Outfitters", merchant: "Shopping", amount: 64.99, dir: "in", status: "cleared", date: "Jun 11", acct: "crd", color: "#1f9d62", ico: "↩" },
{ name: "Stride Gym", merchant: "Health", amount: -29.00, dir: "out", status: "failed", date: "Jun 11", acct: "chk", color: "#d4493e", ico: "✕" },
{ name: "Lyra Streaming", merchant: "Bills & Utilities", amount: -15.99, dir: "out", status: "cleared", date: "Jun 10", acct: "crd", color: "#697089", ico: "▶" },
{ name: "Interest earned", merchant: "Savings", amount: 38.21, dir: "in", status: "cleared", date: "Jun 10", acct: "sav", color: "#1f9d62", ico: "%" }
];
var bills = [
{ name: "Rent — Maple Court", due: "Due Jun 18", amount: 1850.00, soon: true, ico: "🏠" },
{ name: "Electric — VoltCo", due: "Due Jun 20", amount: 96.40, soon: true, ico: "⚡" },
{ name: "Internet — FiberLink", due: "Due Jun 27", amount: 59.99, soon: false, ico: "🌐" },
{ name: "Phone — Cell+", due: "Due Jul 02", amount: 44.00, soon: false, ico: "📱" }
];
// ---------- State ----------
var balancesHidden = false;
var activeAcct = "all"; // "all" or account id
var txFilter = "all";
// ---------- Helpers ----------
function fmt(n) {
var sign = n < 0 ? "-" : "";
return sign + "$" + Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function fmt0(n) {
return "$" + Math.round(Math.abs(n)).toLocaleString("en-US");
}
function mask(amtText) {
return balancesHidden ? "••••••" : amtText;
}
function el(id) { return document.getElementById(id); }
// ---------- Toast ----------
function toast(msg) {
var wrap = el("toastWrap");
var t = document.createElement("div");
t.className = "toast";
t.innerHTML = '<span class="t-ico">✓</span>' + msg;
wrap.appendChild(t);
setTimeout(function () {
t.classList.add("out");
setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, 250);
}, 2400);
}
// ---------- Render: total balance ----------
function renderTotal() {
var total = accounts.reduce(function (s, a) { return s + a.balance; }, 0);
var node = el("totalBalance");
node.textContent = mask(fmt(total));
node.classList.toggle("is-hidden-amt", balancesHidden);
}
// ---------- Render: accounts ----------
function renderAccounts() {
var list = el("acctList");
list.innerHTML = "";
accounts.forEach(function (a) {
var card = document.createElement("button");
card.type = "button";
card.className = "acct " + a.kind + (activeAcct === a.id ? " is-active" : "");
card.setAttribute("aria-pressed", activeAcct === a.id ? "true" : "false");
var balLabel = a.kind === "credit" ? "Balance owed" : "Available";
var balVal = a.kind === "credit" ? Math.abs(a.balance) : a.balance;
card.innerHTML =
'<div class="acct-top">' +
'<span class="acct-type">' + a.type + '</span>' +
'<span class="acct-chip" aria-hidden="true"></span>' +
'</div>' +
'<div class="acct-num mono">•••• •••• •••• ' + a.num + '</div>' +
'<div class="acct-bot">' +
'<div class="acct-bal-label">' + balLabel + '</div>' +
'<div class="acct-bal mono">' + mask(fmt0(balVal)) + '</div>' +
'</div>';
card.addEventListener("click", function () {
activeAcct = (activeAcct === a.id) ? "all" : a.id;
renderAccounts();
renderTransactions();
toast(activeAcct === "all" ? "Showing all accounts" : "Filtered to " + a.name);
});
list.appendChild(card);
});
}
// ---------- Render: spending donut ----------
function renderSpending() {
var total = spending.reduce(function (s, c) { return s + c.amount; }, 0);
var donut = el("donut");
var stops = [];
var acc = 0;
spending.forEach(function (c) {
var start = (acc / total) * 360;
acc += c.amount;
var end = (acc / total) * 360;
stops.push(c.color + " " + start.toFixed(1) + "deg " + end.toFixed(1) + "deg");
});
donut.style.background = "conic-gradient(" + stops.join(", ") + ")";
el("spendTotal").textContent = mask(fmt0(total));
var legend = el("legend");
legend.innerHTML = "";
spending.forEach(function (c) {
var pct = Math.round((c.amount / total) * 100);
var li = document.createElement("li");
li.innerHTML =
'<span class="dot" style="background:' + c.color + '"></span>' +
'<span class="lg-name">' + c.name + '</span>' +
'<span class="lg-amt mono">' + mask(fmt0(c.amount)) + '</span>' +
'<span class="lg-pct mono">' + pct + '%</span>';
legend.appendChild(li);
});
}
// ---------- Render: transactions ----------
function renderTransactions() {
var list = el("txList");
list.innerHTML = "";
var rows = transactions.filter(function (t) {
if (activeAcct !== "all" && t.acct !== activeAcct) return false;
if (txFilter === "in") return t.dir === "in";
if (txFilter === "out") return t.dir === "out";
return true;
});
if (rows.length === 0) {
var empty = document.createElement("li");
empty.className = "tx-empty";
empty.textContent = "No transactions match this filter.";
list.appendChild(empty);
return;
}
rows.forEach(function (t) {
var li = document.createElement("li");
li.className = "tx-row";
var cls = t.amount >= 0 ? "credit" : "debit";
var amtTxt = (t.amount >= 0 ? "+" : "") + fmt(t.amount);
li.innerHTML =
'<span class="tx-ico" style="background:' + t.color + '">' + t.ico + '</span>' +
'<span class="tx-mid">' +
'<div class="tx-name">' + t.name + '</div>' +
'<div class="tx-meta">' + t.merchant + ' · ' + t.date +
' <span class="status ' + t.status + '">' + t.status + '</span>' +
'</div>' +
'</span>' +
'<span class="tx-amt ' + cls + ' mono">' + mask(amtTxt) + '</span>';
list.appendChild(li);
});
}
// ---------- Render: bills ----------
function renderBills() {
var list = el("billsList");
list.innerHTML = "";
bills.forEach(function (b, i) {
var li = document.createElement("li");
li.className = "bill";
li.innerHTML =
'<span class="bill-ico" aria-hidden="true">' + b.ico + '</span>' +
'<span>' +
'<div class="bill-name">' + b.name + '</div>' +
'<div class="bill-due' + (b.soon ? ' soon' : '') + '">' + b.due + '</div>' +
'</span>' +
'<span class="bill-right">' +
'<div class="bill-amt mono">' + mask(fmt(b.amount)) + '</div>' +
'<button class="bill-pay" type="button" data-i="' + i + '">Pay now</button>' +
'</span>';
list.appendChild(li);
});
var dueCount = bills.filter(function (b) { return b.soon; }).length;
el("billsDue").textContent = dueCount + " due soon";
}
// ---------- Toggle balances ----------
function toggleBalances() {
balancesHidden = !balancesHidden;
var btn = el("eyeBtn");
btn.setAttribute("aria-pressed", balancesHidden ? "true" : "false");
el("eyeLabel").textContent = balancesHidden ? "Show balances" : "Hide balances";
renderAll();
toast(balancesHidden ? "Balances hidden" : "Balances visible");
}
// ---------- Render all ----------
function renderAll() {
renderTotal();
renderAccounts();
renderSpending();
renderTransactions();
renderBills();
}
// ---------- Events ----------
document.addEventListener("DOMContentLoaded", function () {
renderAll();
el("eyeBtn").addEventListener("click", toggleBalances);
// Quick actions
Array.prototype.forEach.call(document.querySelectorAll(".qa"), function (b) {
b.addEventListener("click", function () {
toast(b.getAttribute("data-action") + " — opening flow…");
});
});
// Tx filter chips
Array.prototype.forEach.call(document.querySelectorAll(".chip"), function (c) {
c.addEventListener("click", function () {
Array.prototype.forEach.call(document.querySelectorAll(".chip"), function (x) {
x.classList.remove("is-active");
x.setAttribute("aria-selected", "false");
});
c.classList.add("is-active");
c.setAttribute("aria-selected", "true");
txFilter = c.getAttribute("data-filter");
renderTransactions();
});
});
// Header / generic data-action buttons
Array.prototype.forEach.call(document.querySelectorAll("[data-action]:not(.qa)"), function (b) {
b.addEventListener("click", function () {
toast(b.getAttribute("data-action") + " — coming soon");
});
});
// Bill pay (event delegation)
el("billsList").addEventListener("click", function (e) {
var btn = e.target.closest(".bill-pay");
if (!btn) return;
var b = bills[+btn.getAttribute("data-i")];
toast("Scheduled payment for " + b.name);
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Northpoint — Account Overview</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app">
<!-- Sidebar -->
<aside class="sidebar" aria-label="Primary navigation">
<div class="brand">
<div class="brand-mark" aria-hidden="true">N</div>
<div class="brand-name">Northpoint</div>
</div>
<nav class="nav">
<a class="nav-item is-active" href="#" aria-current="page">
<span class="nav-ico" aria-hidden="true">◧</span> Overview
</a>
<a class="nav-item" href="#"><span class="nav-ico" aria-hidden="true">⇄</span> Payments</a>
<a class="nav-item" href="#"><span class="nav-ico" aria-hidden="true">▤</span> Cards</a>
<a class="nav-item" href="#"><span class="nav-ico" aria-hidden="true">◔</span> Insights</a>
<a class="nav-item" href="#"><span class="nav-ico" aria-hidden="true">⚙</span> Settings</a>
</nav>
<div class="sidebar-card">
<div class="sc-lock" aria-hidden="true">🔒</div>
<div>
<div class="sc-title">2FA enabled</div>
<div class="sc-sub">Your account is protected</div>
</div>
</div>
</aside>
<!-- Main -->
<main class="main">
<header class="topbar">
<div>
<h1 class="page-title">Good afternoon, Mara</h1>
<p class="page-sub">Here's what's happening across your accounts</p>
</div>
<div class="topbar-actions">
<button class="btn btn-ghost" id="eyeBtn" type="button" aria-pressed="false">
<span class="eye-ico" aria-hidden="true">👁</span>
<span id="eyeLabel">Hide balances</span>
</button>
<div class="avatar" title="Mara Velasquez" aria-label="Mara Velasquez">MV</div>
</div>
</header>
<div class="grid">
<!-- Hero balance -->
<section class="card hero" aria-label="Total balance">
<div class="hero-top">
<div>
<div class="hero-label">Total balance <span class="verified" title="Verified account">✓ verified</span></div>
<div class="hero-amount mono" id="totalBalance" data-amount="48217.62">$48,217.62</div>
<div class="hero-delta up">▲ $1,284.10 <span class="hero-delta-sub">this month</span></div>
</div>
<div class="hero-spark" aria-hidden="true">
<svg viewBox="0 0 200 60" preserveAspectRatio="none">
<polyline class="spark-line" points="0,46 22,40 44,44 66,30 88,34 110,22 132,26 154,14 176,18 200,8" />
<polyline class="spark-fill" points="0,60 0,46 22,40 44,44 66,30 88,34 110,22 132,26 154,14 176,18 200,8 200,60" />
</svg>
</div>
</div>
<div class="quick-actions" role="group" aria-label="Quick actions">
<button class="qa" data-action="Transfer" type="button"><span class="qa-ico">⇄</span>Transfer</button>
<button class="qa" data-action="Pay" type="button"><span class="qa-ico">⤴</span>Pay</button>
<button class="qa" data-action="Deposit" type="button"><span class="qa-ico">⤵</span>Deposit</button>
<button class="qa" data-action="Statement" type="button"><span class="qa-ico">▤</span>Statement</button>
</div>
</section>
<!-- Spending donut -->
<section class="card spending" aria-label="Spending by category">
<div class="card-head">
<h2 class="card-title">Spending</h2>
<span class="card-tag">June</span>
</div>
<div class="donut-wrap">
<div class="donut" id="donut" role="img" aria-label="Spending breakdown by category">
<div class="donut-center">
<div class="donut-total mono" id="spendTotal">$3,140</div>
<div class="donut-sub">spent</div>
</div>
</div>
</div>
<ul class="legend" id="legend"></ul>
</section>
<!-- Account cards -->
<section class="accounts" aria-label="Your accounts">
<div class="accounts-head">
<h2 class="card-title">Accounts</h2>
<button class="btn btn-ghost btn-sm" type="button" data-action="Add account">+ Add</button>
</div>
<div class="acct-list" id="acctList"></div>
</section>
<!-- Transactions -->
<section class="card tx" aria-label="Recent transactions">
<div class="card-head">
<h2 class="card-title">Recent activity</h2>
<div class="tx-filters" role="tablist" aria-label="Filter transactions">
<button class="chip is-active" data-filter="all" role="tab" aria-selected="true" type="button">All</button>
<button class="chip" data-filter="in" role="tab" aria-selected="false" type="button">Income</button>
<button class="chip" data-filter="out" role="tab" aria-selected="false" type="button">Spending</button>
</div>
</div>
<ul class="tx-list" id="txList"></ul>
</section>
<!-- Upcoming bills -->
<section class="card bills" aria-label="Upcoming bills">
<div class="card-head">
<h2 class="card-title">Upcoming bills</h2>
<span class="card-tag warn" id="billsDue">2 due soon</span>
</div>
<ul class="bills-list" id="billsList"></ul>
</section>
</div>
</main>
</div>
<div class="toast-wrap" id="toastWrap" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Account Overview
A calm, dense customer banking home screen built around a navy total-balance hero with a live sparkline, a verified badge, and four quick actions (transfer, pay, deposit, statement). Below it sit three gradient account cards — checking, savings, and a credit line — each with a chip motif, masked card numbers (•••• •••• •••• 4242), and tabular-figure balances.
The right rail pairs a pure-CSS conic-gradient spending donut with a category legend showing amounts and percentages, while the activity feed lists realistic transactions with merchant, date, colored credit/debit amounts, and status pills (cleared, pending, failed). An upcoming-bills panel flags what’s due soon with inline pay buttons.
Everything is interactive in vanilla JS: toggle hide/show to mask every amount at once, click an account card to filter the activity feed to that account, switch the All / Income / Spending chips to filter by direction, and trigger toast confirmations from quick actions and bill payments. It’s fully responsive down to ~360px and keyboard-usable throughout.
Illustrative UI only — not real banking software or financial advice.