Bootstrap 5 Admin Dashboard with Collapsible Sidebar
An admin dashboard shell with a collapsible icon sidebar and tooltips, a top bar with search and notification badges, stat cards, a trend chart and an activity feed.
<section class="wdw-stage">
<div class="container" style="max-width:1180px">
<div class="wdw-card overflow-hidden db-app" id="dbApp">
<div class="db-backdrop" id="dbBackdrop" hidden></div>
<aside class="db-side" id="dbSide" aria-label="Sidebar">
<div class="db-logo"><span class="db-mark">D</span><span class="db-text fw-bold">Dashkit</span></div>
<ul class="list-unstyled mb-0 p-2 d-grid gap-1">
<li><a href="#" class="db-link active" data-label="Flights" aria-label="Flights" aria-current="page"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 2-7 20-4-9-9-4z"/><path d="M22 2 11 13"/></svg><span class="db-text">Flights</span></a></li>
<li><a href="#" class="db-link" data-label="Modules" aria-label="Modules"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg><span class="db-text">Modules</span></a></li>
<li><a href="#" class="db-link" data-label="Statistics" aria-label="Statistics"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="M7 15l4-4 3 3 5-6"/></svg><span class="db-text">Statistics</span></a></li>
<li><a href="#" class="db-link" data-label="Gifts" aria-label="Gifts"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="8" width="18" height="4" rx="1"/><path d="M12 8v13M19 12v9H5v-9M7.5 8a2.5 2.5 0 0 1 0-5C11 3 12 8 12 8s1-5 4.5-5a2.5 2.5 0 0 1 0 5"/></svg><span class="db-text">Gifts</span></a></li>
<li><a href="#" class="db-link" data-label="Settings" aria-label="Settings"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.100 2.100M17 17l2.100 2.100M4.900 19.100 7 17M17 7l2.100-2.100"/></svg><span class="db-text">Settings</span></a></li>
</ul>
</aside>
<div class="db-main">
<header class="db-top">
<button type="button" class="db-ico" id="dbToggle" aria-label="Toggle sidebar" aria-controls="dbSide" aria-expanded="false"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h16M4 12h10M4 18h16"/></svg></button>
<button type="button" class="db-ico position-relative d-none d-sm-inline-flex" aria-label="Notifications, 13 unread"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9M13.700 21a2 2 0 0 1-3.400 0"/></svg><span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" aria-hidden="true">13</span></button>
<label class="db-search flex-grow-1"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.300-4.300"/></svg><span class="visually-hidden">Search</span><input type="search" class="form-control border-0 bg-transparent shadow-none" placeholder="Search"></label>
<button type="button" class="db-ico position-relative" aria-label="Orders, 4 new"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.700 13.400a2 2 0 0 0 2 1.600h9.700a2 2 0 0 0 2-1.600L23 6H6"/></svg><span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-primary" aria-hidden="true">4</span></button>
<button type="button" class="db-ico d-none d-sm-inline-flex" aria-label="Sign out"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2v10M18.400 6.600a9 9 0 1 1-12.800 0"/></svg></button>
</header>
<main class="db-content p-3 p-lg-4 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<div><h2 class="wdw-heading h4 mb-0">Dashboard</h2><p class="small wdw-muted mb-0">Welcome back, here is today's overview.</p></div>
<button class="btn btn-primary btn-sm rounded-pill px-3">New report</button>
</div>
<div class="row g-3 mb-3">
<div class="col-6 col-xl-3"><div class="wdw-card-sm p-3 h-100"><div class="small wdw-muted mb-1">Revenue</div><div class="fs-4 fw-bold wdw-heading">$48,290</div><span class="badge rounded-pill bg-success-subtle text-success-emphasis mt-1">+12.4%</span></div></div>
<div class="col-6 col-xl-3"><div class="wdw-card-sm p-3 h-100"><div class="small wdw-muted mb-1">Active users</div><div class="fs-4 fw-bold wdw-heading">8,402</div><span class="badge rounded-pill bg-success-subtle text-success-emphasis mt-1">+4.1%</span></div></div>
<div class="col-6 col-xl-3"><div class="wdw-card-sm p-3 h-100"><div class="small wdw-muted mb-1">Bookings</div><div class="fs-4 fw-bold wdw-heading">1,284</div><span class="badge rounded-pill bg-success-subtle text-success-emphasis mt-1">+8.9%</span></div></div>
<div class="col-6 col-xl-3"><div class="wdw-card-sm p-3 h-100"><div class="small wdw-muted mb-1">Refunds</div><div class="fs-4 fw-bold wdw-heading">23</div><span class="badge rounded-pill bg-danger-subtle text-danger-emphasis mt-1">-2.3%</span></div></div>
</div>
<div class="row g-3 flex-grow-1">
<div class="col-lg-7"><div class="wdw-card-sm p-3 h-100 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center gap-2 mb-2 flex-wrap">
<div class="fw-bold">Bookings</div>
<div class="btn-group btn-group-sm" role="group" aria-label="Chart range">
<input type="radio" class="btn-check" name="dbRange" id="dbR7" value="7" checked><label class="btn btn-outline-primary rounded-start-pill px-3" for="dbR7">7d</label>
<input type="radio" class="btn-check" name="dbRange" id="dbR30" value="30"><label class="btn btn-outline-primary rounded-end-pill px-3" for="dbR30">30d</label>
</div>
</div>
<div class="db-chart-wrap flex-grow-1" id="dbChart" role="img" aria-label="Bookings per day for the selected range"></div>
</div></div>
<div class="col-lg-5"><div class="wdw-card-sm p-3 h-100">
<div class="fw-bold mb-1">Recent activity</div>
<ul class="list-unstyled mb-0">
<li class="d-flex align-items-center gap-3 py-2"><span class="rounded-circle bg-success flex-none" style="width:.6rem;height:.6rem" aria-hidden="true"></span><span class="visually-hidden">Success:</span><span class="flex-grow-1 small fw-semibold">Flight LV-218 confirmed</span><span class="small wdw-muted">2 min ago</span></li>
<li class="d-flex align-items-center gap-3 py-2"><span class="rounded-circle bg-primary flex-none" style="width:.6rem;height:.6rem" aria-hidden="true"></span><span class="visually-hidden">Info:</span><span class="flex-grow-1 small fw-semibold">New payment from Acme Co.</span><span class="small wdw-muted">18 min ago</span></li>
<li class="d-flex align-items-center gap-3 py-2"><span class="rounded-circle bg-warning flex-none" style="width:.6rem;height:.6rem" aria-hidden="true"></span><span class="visually-hidden">Warning:</span><span class="flex-grow-1 small fw-semibold">Module update available</span><span class="small wdw-muted">1 hour ago</span></li>
<li class="d-flex align-items-center gap-3 py-2"><span class="rounded-circle bg-danger flex-none" style="width:.6rem;height:.6rem" aria-hidden="true"></span><span class="visually-hidden">Alert:</span><span class="flex-grow-1 small fw-semibold">Refund requested</span><span class="small wdw-muted">3 hours ago</span></li>
<li class="d-flex align-items-center gap-3 py-2"><span class="rounded-circle bg-success flex-none" style="width:.6rem;height:.6rem" aria-hidden="true"></span><span class="visually-hidden">Success:</span><span class="flex-grow-1 small fw-semibold">Payout of $3,200 sent</span><span class="small wdw-muted">5 hours ago</span></li>
<li class="d-flex align-items-center gap-3 py-2"><span class="rounded-circle bg-primary flex-none" style="width:.6rem;height:.6rem" aria-hidden="true"></span><span class="visually-hidden">Info:</span><span class="flex-grow-1 small fw-semibold">Maya joined the team</span><span class="small wdw-muted">Yesterday</span></li>
<li class="d-flex align-items-center gap-3 py-2"><span class="rounded-circle bg-warning flex-none" style="width:.6rem;height:.6rem" aria-hidden="true"></span><span class="visually-hidden">Warning:</span><span class="flex-grow-1 small fw-semibold">Seat limit almost reached</span><span class="small wdw-muted">Yesterday</span></li>
</ul>
</div></div>
</div>
</main>
</div>
</div>
</div>
</section>
/* Web Designer Wall library style — shared base for every Bootstrap 5 snippet.
Indigo accent, Plus Jakarta Sans, soft cards, pill buttons. Works in light and dark. */
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap");
:root,
[data-bs-theme="light"] {
--wdw-font: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--bs-body-font-family: var(--wdw-font);
--bs-primary: #4f46e5;
--bs-primary-rgb: 79, 70, 229;
--bs-primary-text-emphasis: #3730a3;
--bs-primary-bg-subtle: #eef2ff;
--bs-primary-border-subtle: #c7d2fe;
--bs-link-color: #4f46e5;
--bs-link-color-rgb: 79, 70, 229;
--bs-link-hover-color: #4338ca;
--bs-link-hover-color-rgb: 67, 56, 202;
--bs-focus-ring-color: rgba(79, 70, 229, 0.25);
--bs-border-radius: 0.75rem;
--bs-border-radius-lg: 1rem;
--bs-border-radius-xl: 1.25rem;
--wdw-radius-card: 1.25rem;
--wdw-shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 16px 40px -16px rgba(16, 24, 40, 0.22);
--wdw-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06), 0 4px 12px -4px rgba(16, 24, 40, 0.12);
--wdw-stage: #f5f6fb;
}
[data-bs-theme="dark"] {
--bs-primary: #818cf8;
--bs-primary-rgb: 129, 140, 248;
--bs-primary-text-emphasis: #c7d2fe;
--bs-primary-bg-subtle: rgba(129, 140, 248, 0.14);
--bs-primary-border-subtle: rgba(129, 140, 248, 0.35);
--bs-link-color: #a5b4fc;
--bs-link-color-rgb: 165, 180, 252;
--bs-link-hover-color: #c7d2fe;
--bs-link-hover-color-rgb: 199, 210, 254;
--bs-focus-ring-color: rgba(129, 140, 248, 0.35);
--wdw-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 16px 40px -16px rgba(0, 0, 0, 0.7);
--wdw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
--wdw-stage: #0f1117;
}
body { font-family: var(--wdw-font); -webkit-font-smoothing: antialiased; }
/* Bootstrap compiles its blue into these components; point them at the accent. */
.btn-primary {
--bs-btn-bg: var(--bs-primary); --bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-bg: #4338ca; --bs-btn-hover-border-color: #4338ca;
--bs-btn-active-bg: #3730a3; --bs-btn-active-border-color: #3730a3;
--bs-btn-disabled-bg: var(--bs-primary); --bs-btn-disabled-border-color: var(--bs-primary);
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}
[data-bs-theme="dark"] .btn-primary { --bs-btn-color: #0f1117; --bs-btn-hover-bg: #a5b4fc; --bs-btn-hover-border-color: #a5b4fc; --bs-btn-hover-color: #0f1117; --bs-btn-active-color: #0f1117; }
.btn-outline-primary {
--bs-btn-color: var(--bs-primary); --bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-bg: var(--bs-primary); --bs-btn-hover-border-color: var(--bs-primary);
--bs-btn-active-bg: var(--bs-primary); --bs-btn-active-border-color: var(--bs-primary);
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}
.btn { --bs-btn-font-weight: 600; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; gap: .5rem; }
.btn-sm { min-height: 36px; }
.form-control, .form-select { min-height: 44px; border-radius: .75rem; }
.form-control:focus, .form-select:focus, .form-check-input:focus { border-color: rgba(var(--bs-primary-rgb), .6); box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.form-check-input:checked { background-color: var(--bs-primary); border-color: var(--bs-primary); }
.nav-pills { --bs-nav-pills-link-active-bg: var(--bs-primary); }
.nav { --bs-nav-link-color: var(--bs-secondary-color); --bs-nav-link-hover-color: var(--bs-emphasis-color); }
.pagination { --bs-pagination-active-bg: var(--bs-primary); --bs-pagination-active-border-color: var(--bs-primary); --bs-pagination-color: var(--bs-body-color); --bs-pagination-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.progress, .progress-stacked { --bs-progress-bar-bg: var(--bs-primary); }
.list-group { --bs-list-group-active-bg: var(--bs-primary); --bs-list-group-active-border-color: var(--bs-primary); }
.dropdown-menu { --bs-dropdown-link-active-bg: var(--bs-primary); border-radius: .875rem; box-shadow: var(--wdw-shadow-sm); }
.accordion { --bs-accordion-active-bg: var(--bs-primary-bg-subtle); --bs-accordion-active-color: var(--bs-primary-text-emphasis); --bs-accordion-btn-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.badge { font-weight: 600; }
/* Library building blocks — use these instead of re-inventing them per snippet. */
.wdw-stage { min-height: 100vh; display: flex; align-items: center; background: var(--wdw-stage); padding-block: 3rem; }
.wdw-card { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color-translucent); border-radius: var(--wdw-radius-card); box-shadow: var(--wdw-shadow); }
.wdw-card-sm { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color-translucent); border-radius: 1rem; box-shadow: var(--wdw-shadow-sm); }
.wdw-aurora { position: relative; color: #fff; background: radial-gradient(60% 55% at 100% 0, #818cf8 0, transparent 70%), radial-gradient(65% 60% at 0 100%, #ec4899 0, transparent 65%), #1e1b4b; }
.wdw-eyebrow { display: inline-block; font-size: .75rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--bs-primary); }
.wdw-heading { font-weight: 800; letter-spacing: -.02em; color: var(--bs-emphasis-color); }
.wdw-muted { color: var(--bs-secondary-color); }
.wdw-icon { width: 2.75rem; height: 2.75rem; flex: none; display: inline-flex; align-items: center; justify-content: center; border-radius: .875rem; background: var(--bs-primary-bg-subtle); color: var(--bs-primary); }
/* Component */
.db-app{position:relative;display:flex;height:700px;max-height:calc(100vh - 4rem);min-height:520px;background:var(--bs-tertiary-bg)}
.db-side{width:72px;flex:none;background:#1e1b4b;color:#c7d2fe;transition:width .25s ease;overflow:hidden;border-right:1px solid var(--bs-border-color)}
.db-app.expanded .db-side{width:220px}
.db-logo{height:64px;display:flex;align-items:center;gap:.75rem;padding:0 1.15rem;color:#fff;border-bottom:1px solid rgba(255,255,255,.08);white-space:nowrap}
.db-mark{width:2.25rem;height:2.25rem;flex:none;display:inline-flex;align-items:center;justify-content:center;border-radius:.65rem;background:#818cf8;color:#1e1b4b;font-weight:800}
.db-link{display:flex;align-items:center;gap:.9rem;height:44px;padding:0 .85rem;border-radius:.75rem;color:#c7d2fe;text-decoration:none;font-weight:600;font-size:.9rem;white-space:nowrap;transition:background .2s,color .2s}
.db-link:hover{background:rgba(255,255,255,.08);color:#fff}
.db-link.active{background:rgba(129,140,248,.25);color:#fff}
.db-link:focus-visible,.db-ico:focus-visible{outline:2px solid #818cf8;outline-offset:2px}
.db-link svg{flex:none}
.db-text{opacity:0;visibility:hidden;transition:opacity .15s,visibility 0s .15s}
.db-app.expanded .db-text{opacity:1;visibility:visible;transition:opacity .15s .1s,visibility 0s}
.db-main{flex:1;min-width:0;display:flex;flex-direction:column}
.db-top{height:64px;flex:none;display:flex;align-items:center;gap:.5rem;padding:0 1rem;background:var(--bs-body-bg);border-bottom:1px solid var(--bs-border-color-translucent)}
.db-ico{width:44px;height:44px;flex:none;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;color:var(--bs-secondary-color);border-radius:.75rem}
.db-ico:hover{background:var(--bs-tertiary-bg);color:var(--bs-emphasis-color)}
.db-search{display:flex;align-items:center;gap:.25rem;color:var(--bs-secondary-color);min-width:0}
.db-search .form-control{min-height:40px}
.db-content{flex:1;overflow:auto}
.db-chart-wrap{min-height:200px;color:var(--bs-primary)}
.db-chart-wrap svg{width:100%;height:100%;display:block}
.db-chart-wrap text{font-size:11px;fill:var(--bs-secondary-color);font-family:var(--wdw-font)}
.db-chart-wrap .db-val{fill:var(--bs-emphasis-color);font-weight:700}
.db-grid{stroke:var(--bs-border-color);stroke-width:1;stroke-dasharray:3 4}
.db-dot{fill:var(--bs-body-bg);stroke:currentColor;stroke-width:2}
.db-backdrop{position:absolute;inset:0;z-index:15;background:rgba(15,17,40,.5)}
.flex-none{flex:none}
.db-tip{--bs-tooltip-font-size:.8rem}
@media (max-width:575.98px){
.db-app{height:auto;min-height:640px;max-height:none}
.db-side{position:absolute;z-index:20;left:0;top:0;bottom:0;width:220px;transform:translateX(-100%);visibility:hidden;transition:transform .25s ease,visibility 0s .25s}
.db-app.expanded .db-side{width:220px;transform:none;visibility:visible;transition:transform .25s ease,visibility 0s}
.db-text,.db-app.expanded .db-text{opacity:1;visibility:visible}
}
@media (prefers-reduced-motion:reduce){.db-side,.db-text,.db-link{transition:none!important}}
function initDashboardSidebar() {
const app = document.getElementById('dbApp');
const btn = document.getElementById('dbToggle');
if (!app || !btn) return;
const backdrop = document.getElementById('dbBackdrop');
const mobile = window.matchMedia('(max-width:575.98px)');
const links = app.querySelectorAll('.db-link');
const tips = [...links].map((a) => new bootstrap.Tooltip(a, { title: a.dataset.label, placement: 'right', customClass: 'db-tip', trigger: 'hover focus' }));
const sync = () => {
const open = app.classList.contains('expanded');
btn.setAttribute('aria-expanded', open);
if (backdrop) backdrop.hidden = !(open && mobile.matches);
tips.forEach((t) => (open || mobile.matches ? t.disable() : t.enable()));
};
const close = () => { app.classList.remove('expanded'); sync(); };
btn.addEventListener('click', () => { app.classList.toggle('expanded'); sync(); });
if (backdrop) backdrop.addEventListener('click', close);
app.addEventListener('keydown', (e) => { if (e.key === 'Escape' && mobile.matches) { close(); btn.focus(); } });
mobile.addEventListener('change', close);
links.forEach((a) => a.addEventListener('click', (e) => {
e.preventDefault();
links.forEach((l) => { l.classList.remove('active'); l.removeAttribute('aria-current'); });
a.classList.add('active');
a.setAttribute('aria-current', 'page');
}));
sync();
}
function initBookingsChart() {
const host = document.getElementById('dbChart');
const radios = document.querySelectorAll('input[name="dbRange"]');
if (!host || !radios.length) return;
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const data = {
7: { values: [120, 150, 138, 190, 176, 232, 260], labels: days },
30: {
values: [96, 104, 99, 112, 120, 118, 131, 127, 140, 138, 149, 152, 147, 160, 171, 166, 174, 181, 177, 190, 198, 193, 205, 214, 209, 222, 231, 240, 246, 260],
labels: Array.from({ length: 30 }, (_, i) => String(i + 1))
}
};
const W = 420, H = 220, L = 34, R = 14, T = 22, B = 26;
const NS = 'http://www.w3.org/2000/svg';
const render = (range) => {
const { values, labels } = data[range];
const max = 300, n = values.length;
const x = (i) => L + (i * (W - L - R)) / (n - 1);
const y = (v) => T + (1 - v / max) * (H - T - B);
const pts = values.map((v, i) => [x(i), y(v)]);
const line = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
const area = line + ` L${x(n - 1)} ${H - B} L${x(0)} ${H - B}Z`;
const step = range === '7' ? 1 : 5;
let s = `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" aria-hidden="true"><defs><linearGradient id="dbg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="currentColor" stop-opacity=".25"/><stop offset="1" stop-color="currentColor" stop-opacity="0"/></linearGradient></defs>`;
[0, 100, 200, 300].forEach((g) => { s += `<line class="db-grid" x1="${L}" x2="${W - R}" y1="${y(g)}" y2="${y(g)}"/><text x="${L - 6}" y="${y(g) + 4}" text-anchor="end">${g}</text>`; });
s += `<path d="${area}" fill="url(#dbg)"/><path d="${line}" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>`;
labels.forEach((l, i) => { if (i % step === 0 || i === n - 1 && range === '7') s += `<text x="${x(i)}" y="${H - 6}" text-anchor="middle">${l}</text>`; });
const hi = values.indexOf(Math.max(...values));
const lo = values.indexOf(Math.min(...values));
[lo, hi].forEach((i) => {
s += `<circle class="db-dot" cx="${pts[i][0]}" cy="${pts[i][1]}" r="4"/><text class="db-val" x="${Math.min(Math.max(pts[i][0], L + 10), W - R - 8)}" y="${pts[i][1] - 9}" text-anchor="middle">${values[i]}</text>`;
});
s += '</svg>';
host.innerHTML = s;
const label = labels.length === 7 ? 'last 7 days' : 'last 30 days';
host.setAttribute('aria-label', `Bookings per day over the ${label}, from ${values[0]} to ${values[n - 1]}, peak ${Math.max(...values)}`);
};
radios.forEach((r) => r.addEventListener('change', () => { if (r.checked) render(r.value); }));
render('7');
}
initDashboardSidebar();
initBookingsChart();
Built on Bootstrap 5.3.8 with plain JavaScript — no jQuery. Supports light and dark mode via data-bs-theme.