Hotel PMS — Front Desk Dashboard
Property Management System dashboard for the front desk: today's arrivals · in-house · departures with occupancy KPI band, live activity feed and quick-action sidebar.
MCP
Codice
:root {
--navy: #1a2b4a;
--navy-d: #0f1d36;
--navy-2: #2d4570;
--cream: #f7f3eb;
--cream-2: #ece5d4;
--bone: #fbf8f2;
--gold: #c9a649;
--gold-light: #e0c378;
--gold-d: #a88a2e;
--ink: #161e2c;
--ink-2: #2e3a52;
--warm-gray: #6c7280;
--line: rgba(22, 30, 44, 0.1);
--line-strong: rgba(22, 30, 44, 0.18);
--success: #4a7752;
--danger: #b34232;
--warning: #d99020;
--info: #4a6da0;
--font-display: "Cormorant Garamond", Georgia, serif;
--font-body: "Inter", system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
--shadow-1: 0 1px 2px rgba(22, 30, 44, 0.06), 0 2px 8px rgba(22, 30, 44, 0.06);
--shadow-2: 0 10px 30px rgba(22, 30, 44, 0.14);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
overflow: hidden;
}
body {
font-family: var(--font-body);
background: var(--cream);
color: var(--ink);
-webkit-font-smoothing: antialiased;
}
.pms {
height: 100vh;
display: grid;
grid-template-columns: 230px 1fr 320px;
}
/* ── Rail ── */
.rail {
background: var(--navy);
color: var(--bone);
display: flex;
flex-direction: column;
padding: 22px 16px 14px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
padding: 0 4px 18px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.brand-mark {
width: 38px;
height: 38px;
display: grid;
place-items: center;
border-radius: var(--r-md);
background: var(--gold);
color: var(--navy-d);
font-family: var(--font-display);
font-weight: 700;
font-size: 1.35rem;
}
.brand-name {
font-family: var(--font-display);
font-size: 1.1rem;
font-weight: 700;
letter-spacing: 0.01em;
}
.brand-prop {
font-size: 0.7rem;
color: var(--gold-light);
letter-spacing: 0.08em;
text-transform: uppercase;
margin-top: 2px;
}
.nav {
margin-top: 18px;
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
.nav-item {
font-size: 0.86rem;
font-weight: 500;
color: rgba(251, 248, 242, 0.78);
text-decoration: none;
padding: 10px 12px;
border-radius: var(--r-md);
display: flex;
align-items: center;
gap: 10px;
}
.nav-item:hover {
background: rgba(255, 255, 255, 0.06);
color: var(--bone);
}
.nav-item.is-active {
background: rgba(201, 166, 73, 0.16);
color: var(--gold-light);
font-weight: 600;
}
.nav-dot {
width: 6px;
height: 6px;
border-radius: 999px;
background: var(--gold);
}
.nav-item:not(.is-active) .nav-dot {
display: none;
}
.rail-actions {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 14px;
}
.quick-btn {
background: var(--gold);
color: var(--navy-d);
border: none;
font-family: inherit;
font-weight: 600;
font-size: 0.84rem;
padding: 10px 12px;
border-radius: var(--r-md);
cursor: pointer;
text-align: left;
}
.quick-btn:hover {
background: var(--gold-light);
}
.quick-btn.ghost {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.18);
color: var(--bone);
}
.quick-btn.ghost:hover {
background: rgba(255, 255, 255, 0.06);
}
.rail-foot {
display: flex;
flex-direction: column;
gap: 2px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.clock {
font-family: var(--font-mono);
font-weight: 600;
font-size: 0.88rem;
color: var(--gold-light);
}
.agent {
font-size: 0.72rem;
color: rgba(251, 248, 242, 0.6);
letter-spacing: 0.04em;
}
/* ── Main ── */
.main {
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 22px 28px 28px;
background: var(--cream);
}
.topbar {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 20px;
margin-bottom: 18px;
}
.kicker {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--gold-d);
font-weight: 600;
}
.topbar h1 {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.95rem;
letter-spacing: -0.005em;
margin-top: 2px;
}
.topbar h1 span {
color: var(--navy-2);
font-weight: 500;
}
.topbar-actions {
display: flex;
align-items: center;
gap: 10px;
}
.search {
display: flex;
align-items: center;
gap: 8px;
background: var(--bone);
border: 1px solid var(--line);
padding: 9px 14px;
border-radius: 999px;
color: var(--warm-gray);
width: 320px;
}
.search input {
background: transparent;
border: none;
outline: none;
font-family: inherit;
font-size: 0.86rem;
color: var(--ink);
width: 100%;
}
.icon-btn {
position: relative;
background: var(--bone);
border: 1px solid var(--line);
width: 38px;
height: 38px;
border-radius: 999px;
display: grid;
place-items: center;
cursor: pointer;
color: var(--ink-2);
}
.icon-btn:hover {
background: var(--cream-2);
}
.badge-num {
position: absolute;
top: -3px;
right: -3px;
background: var(--danger);
color: white;
font-size: 0.6rem;
font-weight: 700;
border-radius: 999px;
min-width: 16px;
height: 16px;
display: grid;
place-items: center;
padding: 0 4px;
}
/* ── KPI band ── */
.kpis {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin-bottom: 20px;
}
.kpi {
background: var(--bone);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 14px 16px;
position: relative;
overflow: hidden;
}
.kpi::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 3px;
background: var(--gold);
}
.kpi-label {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--warm-gray);
font-weight: 600;
}
.kpi-value {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.95rem;
color: var(--navy-d);
margin-top: 4px;
letter-spacing: -0.01em;
font-variant-numeric: tabular-nums;
}
.kpi-value small {
font-size: 1.1rem;
font-weight: 600;
color: var(--warm-gray);
margin-left: 1px;
}
.kpi-trend {
font-size: 0.74rem;
font-weight: 600;
margin-top: 2px;
letter-spacing: 0.02em;
}
.kpi-trend.up {
color: var(--success);
}
.kpi-trend.down {
color: var(--danger);
}
/* ── Panel ── */
.panel {
background: var(--bone);
border: 1px solid var(--line);
border-radius: var(--r-md);
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel-head {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
gap: 14px;
}
.tabs {
display: flex;
gap: 4px;
}
.tab {
background: transparent;
border: none;
font-family: inherit;
font-size: 0.92rem;
font-weight: 600;
color: var(--warm-gray);
padding: 9px 14px;
border-radius: 999px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
}
.tab:hover {
color: var(--ink);
}
.tab.is-active {
background: var(--navy);
color: var(--bone);
}
.tab-count {
font-family: var(--font-mono);
font-size: 0.72rem;
background: rgba(22, 30, 44, 0.08);
color: inherit;
padding: 2px 8px;
border-radius: 999px;
font-weight: 700;
}
.tab.is-active .tab-count {
background: rgba(251, 248, 242, 0.2);
color: var(--bone);
}
.filter {
display: flex;
gap: 4px;
}
.chip {
background: transparent;
border: 1px solid var(--line-strong);
font-family: inherit;
font-size: 0.76rem;
font-weight: 600;
color: var(--ink-2);
padding: 6px 11px;
border-radius: 999px;
cursor: pointer;
}
.chip:hover {
background: var(--cream-2);
}
.chip.is-active {
background: var(--gold);
color: var(--navy-d);
border-color: var(--gold);
}
.rows {
flex: 1;
overflow-y: auto;
padding: 4px 0 8px;
}
.row {
display: grid;
grid-template-columns: 80px 1fr 120px 100px 110px auto;
align-items: center;
gap: 14px;
padding: 14px 16px;
border-bottom: 1px solid var(--line);
transition: background 0.12s;
}
.row:hover {
background: var(--cream);
}
.row:last-child {
border-bottom: none;
}
.row.is-vip .guest-name::after {
content: "VIP";
background: var(--gold);
color: var(--navy-d);
font-size: 0.6rem;
font-weight: 700;
padding: 2px 6px;
border-radius: 999px;
letter-spacing: 0.08em;
margin-left: 8px;
vertical-align: middle;
}
.room {
font-family: var(--font-mono);
font-weight: 700;
font-size: 0.94rem;
color: var(--navy-d);
}
.guest {
min-width: 0;
}
.guest-name {
font-weight: 600;
font-size: 0.94rem;
color: var(--ink);
letter-spacing: -0.005em;
}
.guest-meta {
font-size: 0.74rem;
color: var(--warm-gray);
margin-top: 2px;
}
.dates {
font-size: 0.78rem;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.dates strong {
display: block;
font-weight: 600;
color: var(--navy-d);
}
.amount {
font-family: var(--font-mono);
font-weight: 700;
font-size: 0.9rem;
color: var(--ink);
text-align: right;
font-variant-numeric: tabular-nums;
}
.status {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 999px;
display: inline-block;
}
.status.expected {
background: rgba(74, 109, 160, 0.14);
color: var(--info);
}
.status.checked-in {
background: rgba(74, 119, 82, 0.14);
color: var(--success);
}
.status.due-out {
background: rgba(217, 144, 32, 0.16);
color: var(--warning);
}
.status.late {
background: rgba(179, 66, 50, 0.14);
color: var(--danger);
}
.row-actions {
display: flex;
gap: 6px;
justify-content: flex-end;
}
.action {
background: var(--cream);
border: 1px solid var(--line);
font-family: inherit;
font-size: 0.76rem;
font-weight: 600;
color: var(--ink-2);
padding: 7px 11px;
border-radius: var(--r-sm);
cursor: pointer;
white-space: nowrap;
}
.action:hover {
border-color: var(--navy-2);
color: var(--navy-d);
}
.action.primary {
background: var(--navy);
color: var(--bone);
border-color: var(--navy);
}
.action.primary:hover {
background: var(--navy-d);
color: var(--bone);
}
.empty {
padding: 60px 24px;
text-align: center;
color: var(--warm-gray);
font-style: italic;
font-size: 0.9rem;
}
/* ── Activity rail ── */
.activity {
background: var(--bone);
border-left: 1px solid var(--line);
display: flex;
flex-direction: column;
overflow: hidden;
}
.act-head {
padding: 22px 20px 14px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--line);
}
.act-head h2 {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.2rem;
letter-spacing: -0.005em;
}
.link-btn {
background: transparent;
border: none;
font-family: inherit;
font-size: 0.74rem;
font-weight: 600;
color: var(--navy-2);
cursor: pointer;
letter-spacing: 0.04em;
}
.link-btn:hover {
color: var(--gold-d);
}
.act-list {
list-style: none;
flex: 1;
overflow-y: auto;
padding: 8px 16px;
}
.act {
display: grid;
grid-template-columns: 32px 1fr auto;
gap: 10px;
padding: 12px 4px;
border-bottom: 1px dashed var(--line);
}
.act:last-child {
border-bottom: none;
}
.act-icon {
width: 32px;
height: 32px;
border-radius: 999px;
display: grid;
place-items: center;
font-size: 0.9rem;
font-weight: 700;
background: var(--cream);
color: var(--navy-d);
}
.act.checkin .act-icon {
background: rgba(74, 119, 82, 0.16);
color: var(--success);
}
.act.payment .act-icon {
background: rgba(201, 166, 73, 0.2);
color: var(--gold-d);
}
.act.housekeeping .act-icon {
background: rgba(74, 109, 160, 0.16);
color: var(--info);
}
.act.alert .act-icon {
background: rgba(179, 66, 50, 0.16);
color: var(--danger);
}
.act-body p {
font-size: 0.84rem;
color: var(--ink);
line-height: 1.4;
}
.act-body small {
font-size: 0.72rem;
color: var(--warm-gray);
}
.act-time {
font-family: var(--font-mono);
font-size: 0.72rem;
color: var(--warm-gray);
white-space: nowrap;
}
.act-foot {
border-top: 1px solid var(--line);
padding: 16px 20px 22px;
background: var(--cream);
}
.act-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--gold-d);
margin-bottom: 8px;
}
.ghost-btn {
width: 100%;
background: transparent;
border: 1px solid var(--line-strong);
font-family: inherit;
font-size: 0.82rem;
font-weight: 600;
color: var(--ink-2);
padding: 9px 12px;
border-radius: var(--r-sm);
cursor: pointer;
text-align: left;
margin-bottom: 6px;
}
.ghost-btn:hover {
border-color: var(--navy-2);
color: var(--navy-d);
background: var(--bone);
}
/* ── Toast ── */
.toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: var(--navy-d);
color: var(--bone);
padding: 11px 20px;
border-radius: 999px;
font-size: 0.86rem;
font-weight: 600;
box-shadow: var(--shadow-2);
}
/* ── Responsive ── */
@media (max-width: 1300px) {
.pms {
grid-template-columns: 200px 1fr 280px;
}
.search {
width: 220px;
}
.row {
grid-template-columns: 70px 1fr 110px 90px 100px auto;
}
}
@media (max-width: 1024px) {
.pms {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr auto;
height: auto;
}
html,
body {
overflow: auto;
}
.activity {
border-left: none;
border-top: 1px solid var(--line);
}
.kpis {
grid-template-columns: repeat(2, 1fr);
}
}// ── Mock data ───────────────────────────────────────────────────────────────
const ARRIVALS = [
{
room: "204",
name: "Mariana Sosa",
meta: "2 adults · 1 child · Deluxe Suite",
in: "2026-05-24",
out: "2026-05-27",
amount: "€612",
status: "expected",
vip: true,
due: false,
},
{
room: "118",
name: "Thomas Reuter",
meta: "1 adult · Classic Double",
in: "2026-05-24",
out: "2026-05-25",
amount: "€184",
status: "expected",
vip: false,
due: true,
},
{
room: "302",
name: "Aiko Tanaka",
meta: "2 adults · Junior Suite",
in: "2026-05-24",
out: "2026-05-29",
amount: "€1,205",
status: "expected",
vip: true,
due: false,
},
{
room: "121",
name: "Olivier Banks",
meta: "2 adults · Twin Standard",
in: "2026-05-24",
out: "2026-05-26",
amount: "€368",
status: "late",
vip: false,
due: true,
},
{
room: "205",
name: "Fátima Cervantes",
meta: "1 adult · Single Standard",
in: "2026-05-24",
out: "2026-05-25",
amount: "€142",
status: "expected",
vip: false,
due: false,
},
];
const INHOUSE = [
{
room: "102",
name: "Karl Henriksen",
meta: "1 adult · Classic Double",
in: "2026-05-22",
out: "2026-05-26",
amount: "€736",
status: "checked-in",
vip: false,
due: false,
},
{
room: "210",
name: "Pilar Romero",
meta: "2 adults · Junior Suite",
in: "2026-05-23",
out: "2026-05-25",
amount: "€482",
status: "checked-in",
vip: true,
due: false,
},
{
room: "308",
name: "Hassan Najjar",
meta: "2 adults · 1 child · Family Suite",
in: "2026-05-21",
out: "2026-05-28",
amount: "€2,108",
status: "checked-in",
vip: false,
due: false,
},
{
room: "114",
name: "Sofia Bellini",
meta: "1 adult · Deluxe Double",
in: "2026-05-23",
out: "2026-05-26",
amount: "€516",
status: "checked-in",
vip: false,
due: false,
},
];
const DEPARTURES = [
{
room: "207",
name: "Elena Vasquez",
meta: "2 adults · Junior Suite",
in: "2026-05-20",
out: "2026-05-24",
amount: "€964",
status: "due-out",
vip: true,
due: true,
},
{
room: "117",
name: "Ruiqi Chen",
meta: "1 adult · Classic Double",
in: "2026-05-21",
out: "2026-05-24",
amount: "€552",
status: "due-out",
vip: false,
due: false,
},
{
room: "311",
name: "Marc Dupuis",
meta: "2 adults · Deluxe Suite",
in: "2026-05-22",
out: "2026-05-24",
amount: "€408",
status: "due-out",
vip: false,
due: true,
},
];
const ACTS = [
{
type: "checkin",
icon: "✓",
title: "Mariana Sosa checked in",
sub: "Room 204 · key issued",
time: "08:42",
},
{
type: "payment",
icon: "€",
title: "Folio payment posted",
sub: "€612.00 · card · 204",
time: "08:41",
},
{
type: "housekeeping",
icon: "✦",
title: "Room 117 reported clean",
sub: "Ines · housekeeping",
time: "08:36",
},
{
type: "alert",
icon: "!",
title: "Late check-in flagged",
sub: "Olivier Banks · room 121",
time: "08:21",
},
{
type: "checkin",
icon: "✓",
title: "Pre-check-in completed",
sub: "Aiko Tanaka · room 302",
time: "08:18",
},
{
type: "payment",
icon: "€",
title: "Deposit captured",
sub: "€184.00 · 118 · hold",
time: "08:11",
},
{
type: "housekeeping",
icon: "✦",
title: "Room 207 inspected",
sub: "Departure clean",
time: "08:02",
},
];
// ── Render ──────────────────────────────────────────────────────────────────
const rows = document.getElementById("rows");
const acts = document.getElementById("acts");
const toast = document.getElementById("toast");
let activeTab = "arrivals";
let activeFilter = "all";
function getList() {
if (activeTab === "arrivals") return ARRIVALS;
if (activeTab === "inhouse") return INHOUSE;
return DEPARTURES;
}
function getPrimaryAction() {
if (activeTab === "arrivals") return "Check in";
if (activeTab === "inhouse") return "Open folio";
return "Check out";
}
function renderRows() {
const query = (document.getElementById("search").value || "").toLowerCase().trim();
const list = getList().filter((r) => {
if (activeFilter === "vip" && !r.vip) return false;
if (activeFilter === "due" && !r.due) return false;
if (query) {
return (
r.name.toLowerCase().includes(query) ||
r.room.includes(query) ||
r.meta.toLowerCase().includes(query)
);
}
return true;
});
if (!list.length) {
rows.innerHTML = `<p class="empty">No reservations match the current filter.</p>`;
return;
}
rows.innerHTML = list
.map(
(r) => `
<article class="row ${r.vip ? "is-vip" : ""}">
<div class="room">${r.room}</div>
<div class="guest">
<div class="guest-name">${r.name}</div>
<div class="guest-meta">${r.meta}</div>
</div>
<div class="dates">
<strong>${fmtShort(r.in)}</strong>
<span>→ ${fmtShort(r.out)}</span>
</div>
<div class="amount">${r.amount}</div>
<div><span class="status ${r.status}">${r.status.replace("-", " ")}</span></div>
<div class="row-actions">
<button class="action">Folio</button>
<button class="action primary">${getPrimaryAction()}</button>
</div>
</article>`
)
.join("");
}
function renderActs() {
acts.innerHTML = ACTS.map(
(a) => `
<li class="act ${a.type}">
<span class="act-icon">${a.icon}</span>
<div class="act-body">
<p>${a.title}</p>
<small>${a.sub}</small>
</div>
<span class="act-time">${a.time}</span>
</li>`
).join("");
}
function fmtShort(iso) {
const d = new Date(iso + "T00:00");
return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short" });
}
function showToast(msg) {
toast.textContent = msg;
toast.hidden = false;
clearTimeout(showToast._t);
showToast._t = setTimeout(() => (toast.hidden = true), 1800);
}
// ── Wiring ──────────────────────────────────────────────────────────────────
document.querySelectorAll(".tab").forEach((t) => {
t.addEventListener("click", () => {
document.querySelectorAll(".tab").forEach((x) => x.classList.remove("is-active"));
t.classList.add("is-active");
activeTab = t.dataset.tab;
renderRows();
});
});
document.querySelectorAll(".chip").forEach((c) => {
c.addEventListener("click", () => {
document.querySelectorAll(".chip").forEach((x) => x.classList.remove("is-active"));
c.classList.add("is-active");
activeFilter = c.dataset.filter;
renderRows();
});
});
document.getElementById("search").addEventListener("input", renderRows);
rows.addEventListener("click", (e) => {
const btn = e.target.closest("button.action");
if (!btn) return;
const name = btn.closest(".row").querySelector(".guest-name").textContent;
showToast(`${btn.textContent} · ${name}`);
});
document
.querySelectorAll(".ghost-btn, .quick-btn")
.forEach((b) => b.addEventListener("click", () => showToast(b.textContent.trim())));
// ── Clock + label ───────────────────────────────────────────────────────────
const clock = document.getElementById("clock");
const todayLabel = document.getElementById("todayLabel");
function tick() {
const now = new Date();
const hhmm = now.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
const wk = now.toLocaleDateString("en-GB", { weekday: "short" });
clock.textContent = `${hhmm} · ${wk}`;
todayLabel.textContent = now.toLocaleDateString("en-GB", {
weekday: "long",
day: "numeric",
month: "long",
});
}
tick();
setInterval(tick, 1000);
renderRows();
renderActs();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700&display=swap"
/>
<link rel="stylesheet" href="style.css" />
<title>Aurelia Hotels · Front Desk</title>
</head>
<body>
<main class="pms">
<aside class="rail">
<div class="brand">
<span class="brand-mark">Æ</span>
<div>
<p class="brand-name">Aurelia Hotels</p>
<p class="brand-prop">Aurelia · Madrid</p>
</div>
</div>
<nav class="nav">
<a class="nav-item is-active" href="#"><span class="nav-dot"></span>Dashboard</a>
<a class="nav-item" href="#">Reservations</a>
<a class="nav-item" href="#">Room rack</a>
<a class="nav-item" href="#">Housekeeping</a>
<a class="nav-item" href="#">Folios</a>
<a class="nav-item" href="#">Reports</a>
</nav>
<div class="rail-actions">
<button class="quick-btn">+ New booking</button>
<button class="quick-btn ghost">Walk-in</button>
</div>
<footer class="rail-foot">
<span class="clock" id="clock">--:-- · --</span>
<span class="agent">Reception · Lena</span>
</footer>
</aside>
<section class="main">
<header class="topbar">
<div>
<p class="kicker">Front Desk</p>
<h1>Today · <span id="todayLabel">—</span></h1>
</div>
<div class="topbar-actions">
<div class="search">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2"/>
<path d="m20 20-3.5-3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
<input id="search" type="search" placeholder="Search guest, reservation, room…" />
</div>
<button class="icon-btn" title="Notifications">
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M12 22a2 2 0 0 0 2-2h-4a2 2 0 0 0 2 2zm6-6V11a6 6 0 0 0-12 0v5l-2 2v1h16v-1l-2-2z" fill="currentColor"/></svg>
<span class="badge-num">3</span>
</button>
</div>
</header>
<section class="kpis">
<article class="kpi">
<p class="kpi-label">Occupancy</p>
<p class="kpi-value"><span id="occ">82</span><small>%</small></p>
<p class="kpi-trend up">▲ 4.2% vs LW</p>
</article>
<article class="kpi">
<p class="kpi-label">ADR</p>
<p class="kpi-value">€<span id="adr">184</span></p>
<p class="kpi-trend up">▲ €6.40</p>
</article>
<article class="kpi">
<p class="kpi-label">RevPAR</p>
<p class="kpi-value">€<span id="revpar">150.88</span></p>
<p class="kpi-trend up">▲ 5.1%</p>
</article>
<article class="kpi">
<p class="kpi-label">Outstanding folios</p>
<p class="kpi-value">€<span id="folios">2,481</span></p>
<p class="kpi-trend down">▼ 4 open</p>
</article>
</section>
<section class="panel">
<header class="panel-head">
<div class="tabs" role="tablist">
<button class="tab is-active" data-tab="arrivals">
Arrivals <span class="tab-count" id="cArr">12</span>
</button>
<button class="tab" data-tab="inhouse">
In-house <span class="tab-count" id="cIn">87</span>
</button>
<button class="tab" data-tab="departures">
Departures <span class="tab-count" id="cDep">9</span>
</button>
</div>
<div class="filter">
<button class="chip is-active" data-filter="all">All</button>
<button class="chip" data-filter="vip">VIP</button>
<button class="chip" data-filter="due">Action due</button>
</div>
</header>
<div class="rows" id="rows"></div>
</section>
</section>
<aside class="activity">
<header class="act-head">
<h2>Live activity</h2>
<button class="link-btn">View all</button>
</header>
<ul class="act-list" id="acts"></ul>
<footer class="act-foot">
<p class="act-label">Quick actions</p>
<button class="ghost-btn">Print arrivals list</button>
<button class="ghost-btn">Run night audit</button>
<button class="ghost-btn">Export folios CSV</button>
</footer>
</aside>
</main>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Front Desk PMS Dashboard
The home screen of a hotel property management system. A top KPI band shows occupancy, ADR, RevPAR and outstanding folios for the day. Three tab panels switch between Arrivals (today’s expected check-ins), In-house (current guests) and Departures (today’s check-outs), each with action buttons (Check in · Open folio · Check out). A right rail lists live activity (housekeeping updates, payment posts, new bookings) and a quick-actions footer.
Vanilla JS — tab switching, search filter, status badges, and a clock. Shared hotel design tokens: deep navy + warm gold + cream, Cormorant for display, Inter for body.