Hotel In-Room Tablet
A dark, landscape in-room smart-tablet home screen for Aurelia Hotels — displays a personalised greeting, room number, live clock, and weather, alongside a grid of service tiles. Clicking a tile opens a panel: Room Service shows a small menu with an order builder and running total; Lights & Climate shows toggles and a temperature stepper. All panels close with a back button.
MCP
Código
/* ── Design tokens ── */
: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 12px 36px rgba(15, 29, 54, 0.16);
/* tablet surface colours */
--t-bg: #0b1526;
--t-surface: rgba(255, 255, 255, 0.04);
--t-surface-hover: rgba(255, 255, 255, 0.08);
--t-border: rgba(255, 255, 255, 0.07);
--t-text: rgba(255, 255, 255, 0.88);
--t-muted: rgba(255, 255, 255, 0.42);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
body {
font-family: var(--font-body);
background: var(--t-bg);
color: var(--t-text);
-webkit-font-smoothing: antialiased;
overflow: hidden;
}
/* ── Screens ── */
.screen {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.screen--panel {
background: #0d1a30;
}
/* ── Top bar ── */
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 32px 16px;
gap: 16px;
flex-shrink: 0;
background: linear-gradient(to bottom, rgba(201, 166, 73, 0.06), transparent);
border-bottom: 1px solid var(--t-border);
}
.topbar-left {
display: flex;
align-items: center;
gap: 14px;
}
.logo-mark {
width: 40px;
height: 40px;
background: linear-gradient(135deg, var(--gold), var(--gold-d));
border-radius: 12px;
display: grid;
place-items: center;
font-family: var(--font-display);
font-size: 1.4rem;
font-weight: 700;
color: var(--navy-d);
flex-shrink: 0;
}
.topbar-greeting {
font-family: var(--font-display);
font-size: 1.25rem;
font-weight: 700;
color: var(--t-text);
letter-spacing: 0.01em;
}
.topbar-sub {
font-size: 0.74rem;
color: var(--t-muted);
margin-top: 1px;
}
.topbar-right {
display: flex;
align-items: center;
gap: 24px;
}
.weather-widget {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: var(--t-surface);
border: 1px solid var(--t-border);
border-radius: 999px;
}
.weather-icon {
font-size: 1.1rem;
}
.weather-temp {
font-family: var(--font-mono);
font-size: 0.9rem;
font-weight: 700;
color: var(--gold-light);
}
.weather-label {
font-size: 0.72rem;
color: var(--t-muted);
}
.clock-widget {
text-align: right;
}
.clock-time {
font-family: var(--font-mono);
font-size: 1.5rem;
font-weight: 700;
color: var(--t-text);
letter-spacing: -0.02em;
}
.clock-date {
font-size: 0.7rem;
color: var(--t-muted);
font-weight: 600;
text-align: right;
}
.divider {
height: 1px;
background: var(--t-border);
flex-shrink: 0;
}
/* ── Tiles grid ── */
.tiles-grid {
flex: 1;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 2px;
background: rgba(255, 255, 255, 0.03);
overflow: hidden;
}
.tile {
background: var(--t-bg);
border: none;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-end;
padding: 20px 22px;
cursor: pointer;
font-family: var(--font-body);
color: var(--t-text);
position: relative;
overflow: hidden;
transition: background 0.2s;
gap: 3px;
}
.tile:hover {
background: var(--t-surface-hover);
}
.tile::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.02), transparent 60%);
pointer-events: none;
}
.tile-icon {
font-size: 1.8rem;
line-height: 1;
margin-bottom: 8px;
display: block;
}
.tile-title {
font-weight: 600;
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.9);
}
.tile-sub {
font-size: 0.7rem;
color: var(--t-muted);
font-weight: 500;
}
/* Gold accent strip on active tile */
.tile.is-active-tile::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: var(--gold);
}
/* Tile colour accents */
.tile--room-service {
background: #0d1a30;
}
.tile--housekeeping {
background: #0e1c2d;
}
.tile--spa {
background: #0f1e32;
}
.tile--tv {
background: #0d1929;
}
.tile--climate {
background: #10202e;
}
.tile--concierge {
background: #0e1c2d;
}
.tile--checkout {
background: linear-gradient(135deg, #1c1409, #0d1929);
grid-column: span 1;
}
/* Last tile: checkout fills the 4th column of row 2, first 3 tiles occupy 3 slots */
.tiles-grid > .tile:nth-child(5) {
grid-column: 1;
grid-row: 2;
}
.tiles-grid > .tile:nth-child(6) {
grid-column: 2;
grid-row: 2;
}
.tiles-grid > .tile:nth-child(7) {
grid-column: 3 / span 2;
grid-row: 2;
}
/* ── Panel shared header ── */
.panel-header {
display: flex;
align-items: center;
gap: 16px;
padding: 18px 28px 16px;
border-bottom: 1px solid var(--t-border);
flex-shrink: 0;
background: rgba(255, 255, 255, 0.02);
}
.back-btn {
background: var(--t-surface);
border: 1px solid var(--t-border);
color: var(--gold-light);
font-family: var(--font-body);
font-size: 0.82rem;
font-weight: 600;
padding: 8px 16px;
border-radius: 999px;
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s;
}
.back-btn:hover {
background: var(--t-surface-hover);
}
.panel-kicker {
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--gold-d);
font-weight: 700;
margin-bottom: 2px;
}
.panel-title {
font-family: var(--font-display);
font-size: 1.7rem;
font-weight: 700;
color: var(--t-text);
letter-spacing: -0.01em;
}
.badge-live {
margin-left: auto;
background: rgba(74, 119, 82, 0.2);
color: #7ec98a;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
padding: 5px 12px;
border-radius: 999px;
}
/* ── Panel body ── */
.panel-body {
flex: 1;
overflow-y: auto;
padding: 20px 28px;
display: flex;
flex-direction: column;
gap: 24px;
}
/* ── Room service menu ── */
.menu-section {
display: flex;
flex-direction: column;
gap: 10px;
}
.menu-section-label {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--gold-d);
font-weight: 700;
padding-bottom: 6px;
border-bottom: 1px solid var(--t-border);
}
.menu-items {
display: flex;
flex-direction: column;
gap: 2px;
}
.menu-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
background: var(--t-surface);
border-radius: var(--r-md);
border: 1px solid var(--t-border);
transition: background 0.15s;
}
.menu-item:hover {
background: var(--t-surface-hover);
}
.mi-info {
flex: 1;
}
.mi-name {
font-weight: 600;
font-size: 0.9rem;
color: var(--t-text);
}
.mi-desc {
font-size: 0.74rem;
color: var(--t-muted);
margin-top: 2px;
}
.mi-right {
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
}
.mi-price {
font-family: var(--font-mono);
font-size: 0.9rem;
font-weight: 700;
color: var(--gold-light);
min-width: 36px;
text-align: right;
}
.qty-ctrl {
display: flex;
align-items: center;
gap: 4px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--t-border);
border-radius: var(--r-sm);
padding: 2px;
}
.qty-btn {
width: 28px;
height: 28px;
background: rgba(255, 255, 255, 0.06);
border: none;
border-radius: 4px;
color: var(--t-text);
font-size: 1rem;
font-weight: 700;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.12s;
}
.qty-btn:hover {
background: rgba(201, 166, 73, 0.15);
color: var(--gold-light);
}
.qty-val {
font-family: var(--font-mono);
font-size: 0.88rem;
font-weight: 700;
color: var(--t-text);
min-width: 24px;
text-align: center;
}
/* ── Panel footer (order) ── */
.panel-footer {
padding: 14px 28px 18px;
border-top: 1px solid var(--t-border);
background: rgba(255, 255, 255, 0.02);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-shrink: 0;
flex-wrap: wrap;
}
.order-total {
display: flex;
flex-direction: column;
gap: 1px;
}
.ot-label {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--t-muted);
font-weight: 700;
}
.ot-val {
font-family: var(--font-display);
font-size: 1.8rem;
font-weight: 700;
color: var(--gold-light);
line-height: 1;
}
.ot-note {
font-size: 0.72rem;
color: var(--t-muted);
margin-top: 2px;
}
/* ── Buttons ── */
.primary-btn {
background: var(--navy);
color: var(--bone);
border: none;
font-family: var(--font-body);
font-size: 0.9rem;
font-weight: 600;
padding: 13px 28px;
border-radius: 999px;
cursor: pointer;
transition: background 0.15s, opacity 0.15s;
}
.primary-btn:hover:not(:disabled) {
background: var(--navy-2);
}
.primary-btn:disabled {
opacity: 0.38;
cursor: not-allowed;
}
.ghost-btn {
background: transparent;
color: rgba(255, 255, 255, 0.5);
border: 1px solid var(--t-border);
font-family: var(--font-body);
font-size: 0.88rem;
font-weight: 600;
padding: 12px 22px;
border-radius: 999px;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.ghost-btn:hover {
background: var(--t-surface-hover);
color: var(--t-text);
}
/* ── Climate panel ── */
.panel-body--climate {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
align-content: start;
}
.climate-section {
background: var(--t-surface);
border: 1px solid var(--t-border);
border-radius: var(--r-lg);
padding: 22px 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
.climate-label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--gold-d);
font-weight: 700;
}
.temp-ctrl {
display: flex;
align-items: center;
gap: 16px;
justify-content: center;
}
.temp-btn {
width: 44px;
height: 44px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.07);
border: 1px solid var(--t-border);
color: var(--t-text);
font-size: 1.4rem;
font-weight: 700;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.12s;
font-family: var(--font-display);
}
.temp-btn:hover {
background: rgba(201, 166, 73, 0.15);
}
.temp-display {
text-align: center;
display: flex;
align-items: flex-start;
gap: 4px;
}
.temp-val {
font-family: var(--font-display);
font-size: 3.5rem;
font-weight: 700;
color: var(--t-text);
line-height: 1;
}
.temp-unit {
font-size: 1.2rem;
color: var(--t-muted);
margin-top: 6px;
}
.temp-hint {
font-size: 0.7rem;
color: var(--t-muted);
text-align: center;
}
.light-zones {
display: flex;
flex-direction: column;
gap: 8px;
}
.light-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid var(--t-border);
cursor: pointer;
}
.light-row:last-child {
border-bottom: none;
}
.light-name {
font-size: 0.86rem;
font-weight: 500;
color: var(--t-text);
}
.light-toggle {
appearance: none;
-webkit-appearance: none;
width: 40px;
height: 22px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid var(--t-border);
position: relative;
cursor: pointer;
transition: background 0.2s;
flex-shrink: 0;
}
.light-toggle::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
transition: transform 0.2s, background 0.2s;
}
.light-toggle:checked {
background: rgba(201, 166, 73, 0.35);
border-color: rgba(201, 166, 73, 0.5);
}
.light-toggle:checked::after {
transform: translateX(18px);
background: var(--gold-light);
}
/* ── DND toggle ── */
.dnd-toggle {
display: flex;
align-items: center;
gap: 14px;
cursor: pointer;
}
.dnd-toggle input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.dnd-track {
width: 52px;
height: 28px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid var(--t-border);
position: relative;
transition: background 0.2s;
flex-shrink: 0;
}
.dnd-thumb {
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
transition: transform 0.2s, background 0.2s;
}
.dnd-toggle input:checked ~ .dnd-track {
background: rgba(179, 66, 50, 0.4);
border-color: rgba(179, 66, 50, 0.6);
}
.dnd-toggle input:checked ~ .dnd-track .dnd-thumb {
transform: translateX(24px);
background: #e07060;
}
.dnd-text {
font-size: 0.9rem;
font-weight: 600;
color: var(--t-text);
}
/* ── Simple panels ── */
.panel-body--simple {
gap: 20px;
}
.simple-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.simple-tile {
background: var(--t-surface);
border: 1px solid var(--t-border);
border-radius: var(--r-lg);
padding: 24px 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
cursor: pointer;
font-family: var(--font-body);
font-size: 0.84rem;
font-weight: 600;
color: var(--t-text);
transition: background 0.15s, border-color 0.15s;
}
.simple-tile:hover {
background: var(--t-surface-hover);
border-color: rgba(201, 166, 73, 0.3);
color: var(--gold-light);
}
.simple-tile.wide {
grid-column: 1 / -1;
flex-direction: row;
justify-content: center;
padding: 16px;
}
.st-icon {
font-size: 1.6rem;
line-height: 1;
}
.info-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.info-card {
background: var(--t-surface);
border: 1px solid var(--t-border);
border-radius: var(--r-md);
padding: 16px 18px;
}
.ic-label {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--t-muted);
font-weight: 700;
margin-bottom: 5px;
}
.ic-val {
font-family: var(--font-mono);
font-size: 0.96rem;
font-weight: 700;
color: var(--gold-light);
}
/* ── Bill summary ── */
.bill-summary {
background: var(--t-surface);
border: 1px solid var(--t-border);
border-radius: var(--r-lg);
padding: 20px 22px;
display: flex;
flex-direction: column;
gap: 10px;
max-width: 480px;
}
.bill-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
color: var(--t-muted);
}
.bill-val {
font-family: var(--font-mono);
font-weight: 700;
color: var(--t-text);
}
.bill-divider {
height: 1px;
background: var(--t-border);
margin: 4px 0;
}
.bill-row--total {
font-weight: 700;
color: var(--t-text);
font-size: 1rem;
}
.bill-row--total .bill-val {
color: var(--gold-light);
font-size: 1.1rem;
}
.checkout-actions {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
/* ── Toast ── */
.toast {
position: fixed;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
background: var(--navy-d);
color: var(--bone);
padding: 12px 24px;
border-radius: 999px;
font-size: 0.88rem;
font-weight: 600;
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.4);
white-space: nowrap;
z-index: 999;
}
/* ── Responsive ── */
@media (max-width: 960px) {
.tiles-grid {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(4, 1fr);
}
.tiles-grid > .tile:nth-child(5),
.tiles-grid > .tile:nth-child(6),
.tiles-grid > .tile:nth-child(7) {
grid-column: auto;
grid-row: auto;
}
.panel-body--climate {
grid-template-columns: 1fr;
}
.simple-grid {
grid-template-columns: repeat(2, 1fr);
}
.info-cards {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
.topbar {
flex-direction: column;
align-items: flex-start;
padding: 14px 18px 12px;
}
.topbar-right {
width: 100%;
justify-content: space-between;
}
.tiles-grid {
grid-template-columns: 1fr 1fr;
}
.panel-body {
padding: 14px 16px;
}
.panel-header {
padding: 14px 16px 12px;
}
.panel-footer {
padding: 12px 16px 14px;
}
.simple-grid {
grid-template-columns: 1fr 1fr;
}
}
/* ── Honor [hidden] over display (overlay/panel toggle fix) ── */
.screen[hidden] {
display: none;
}// ── Helpers ──
const $ = (id) => document.getElementById(id);
const toast = $("toast");
function showToast(msg) {
toast.textContent = msg;
toast.hidden = false;
clearTimeout(showToast._t);
showToast._t = setTimeout(() => (toast.hidden = true), 2200);
}
const fmt = (n) =>
`€${n.toLocaleString("en-GB", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
// ── Live clock ──
function updateClock() {
const now = new Date();
const hh = String(now.getHours()).padStart(2, "0");
const mm = String(now.getMinutes()).padStart(2, "0");
const ss = String(now.getSeconds()).padStart(2, "0");
$("clockTime").textContent = `${hh}:${mm}:${ss}`;
}
function updateDate() {
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const now = new Date();
const day = days[now.getDay()];
const d = String(now.getDate()).padStart(2, "0");
const mon = now.toLocaleString("en-GB", { month: "short" });
$("clockDate").textContent = `${day} ${d} ${mon} ${now.getFullYear()}`;
}
function updateGreeting() {
const h = new Date().getHours();
const g = h < 12 ? "Good morning" : h < 18 ? "Good afternoon" : "Good evening";
$("greeting").textContent = `${g}, Mr. Fontaine`;
}
updateClock();
updateDate();
updateGreeting();
setInterval(updateClock, 1000);
// ── Screen navigation ──
const screenHome = $("screenHome");
const panelMap = {
roomService: "panelRoomService",
housekeeping: "panelHousekeeping",
spa: "panelSpa",
tv: "panelTv",
climate: "panelClimate",
concierge: "panelConcierge",
checkout: "panelCheckout",
};
function openPanel(key) {
screenHome.hidden = true;
Object.values(panelMap).forEach((id) => $(`${id}`) && ($(`${id}`).hidden = true));
const el = $(panelMap[key]);
if (el) el.hidden = false;
}
function goHome() {
Object.values(panelMap).forEach((id) => {
const el = $(id);
if (el) el.hidden = true;
});
screenHome.hidden = false;
}
// ── Tile click → open panel ──
document
.querySelectorAll(".tile[data-panel]")
.forEach((tile) => tile.addEventListener("click", () => openPanel(tile.dataset.panel)));
// ── Back buttons ──
document
.querySelectorAll(".back-btn[data-home]")
.forEach((b) => b.addEventListener("click", goHome));
// ── Simple-tile toast buttons ──
document
.querySelectorAll(".simple-tile[data-toast]")
.forEach((b) => b.addEventListener("click", () => showToast(b.dataset.toast)));
// ── Checkout panel buttons ──
document
.querySelectorAll(
"#panelCheckout .primary-btn[data-toast], #panelCheckout .ghost-btn[data-toast]"
)
.forEach((b) =>
b.addEventListener("click", () => {
showToast(b.dataset.toast);
if (b.classList.contains("primary-btn")) setTimeout(goHome, 2400);
})
);
// ── Room service order builder ──
const ORDER_PRICES = { b1: 14, b2: 18, b3: 9, d1: 28, d2: 32, d3: 12 };
const orderQty = { b1: 0, b2: 0, b3: 0, d1: 0, d2: 0, d3: 0 };
function calcOrderTotal() {
return Object.entries(orderQty).reduce(
(sum, [id, qty]) => sum + (ORDER_PRICES[id] || 0) * qty,
0
);
}
function updateOrderUI() {
const total = calcOrderTotal();
$("orderTotal").textContent = fmt(total);
const count = Object.values(orderQty).reduce((a, b) => a + b, 0);
$("orderNote").textContent =
count > 0 ? `${count} item${count > 1 ? "s" : ""} selected` : "No items selected";
$("sendOrderBtn").disabled = total === 0;
// Highlight qty values
Object.entries(orderQty).forEach(([id, qty]) => {
const el = $(`qty-${id}`);
if (el) {
el.textContent = qty;
el.style.color = qty > 0 ? "var(--gold-light)" : "";
}
});
}
document.querySelectorAll(".menu-item").forEach((item) => {
const id = item.dataset.id;
item.querySelectorAll(".qty-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const delta = parseInt(btn.dataset.delta, 10);
orderQty[id] = Math.max(0, Math.min(9, (orderQty[id] || 0) + delta));
updateOrderUI();
});
});
});
$("sendOrderBtn").addEventListener("click", () => {
const count = Object.values(orderQty).reduce((a, b) => a + b, 0);
const total = calcOrderTotal();
showToast(`Order sent to kitchen · ${count} item${count !== 1 ? "s" : ""} · ${fmt(total)}`);
// Reset order
Object.keys(orderQty).forEach((k) => (orderQty[k] = 0));
updateOrderUI();
setTimeout(goHome, 2500);
});
// ── Climate panel ──
let tempC = 22;
const ECO_MIN = 19;
const ECO_MAX = 26;
function updateTemp() {
$("tempVal").textContent = tempC;
$("tempDown").disabled = tempC <= ECO_MIN;
$("tempUp").disabled = tempC >= ECO_MAX;
}
$("tempDown").addEventListener("click", () => {
if (tempC > ECO_MIN) {
tempC--;
updateTemp();
showToast(`Temperature set to ${tempC} °C`);
}
});
$("tempUp").addEventListener("click", () => {
if (tempC < ECO_MAX) {
tempC++;
updateTemp();
showToast(`Temperature set to ${tempC} °C`);
}
});
document.querySelectorAll(".light-toggle").forEach((toggle) => {
toggle.addEventListener("change", () => {
const zone = toggle.dataset.zone;
const state = toggle.checked ? "on" : "off";
showToast(`${zone.charAt(0).toUpperCase() + zone.slice(1)} light ${state}`);
});
});
const dndCheck = $("dndCheck");
const dndText = $("dndText");
dndCheck.addEventListener("change", () => {
dndText.textContent = dndCheck.checked ? "On — Door hanger active" : "Off";
showToast(dndCheck.checked ? "Do Not Disturb activated" : "Do Not Disturb deactivated");
});
// ── Init ──
updateOrderUI();
updateTemp();<!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@600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700&display=swap"
/>
<link rel="stylesheet" href="style.css" />
<title>In-Room Tablet · Aurelia Hotels</title>
</head>
<body>
<!-- ── Home screen ── -->
<div class="screen" id="screenHome">
<!-- Top bar -->
<header class="topbar">
<div class="topbar-left">
<div class="logo-mark" aria-hidden="true">A</div>
<div>
<p class="topbar-greeting" id="greeting">Good evening, Mr. Fontaine</p>
<p class="topbar-sub">Room 512 · Gran Vía, Barcelona</p>
</div>
</div>
<div class="topbar-right">
<div class="weather-widget">
<span class="weather-icon" aria-hidden="true">☀</span>
<span class="weather-temp">24 °C</span>
<span class="weather-label">Barcelona</span>
</div>
<div class="clock-widget">
<p class="clock-time" id="clockTime">--:--</p>
<p class="clock-date" id="clockDate">Mon 09 Jun 2026</p>
</div>
</div>
</header>
<!-- Divider -->
<div class="divider"></div>
<!-- Service tiles grid -->
<main class="tiles-grid">
<button class="tile tile--room-service" data-panel="roomService" type="button">
<span class="tile-icon" aria-hidden="true">🍽</span>
<span class="tile-title">Room Service</span>
<span class="tile-sub">24 h menu</span>
</button>
<button class="tile tile--housekeeping" data-panel="housekeeping" type="button">
<span class="tile-icon" aria-hidden="true">🛏</span>
<span class="tile-title">Housekeeping</span>
<span class="tile-sub">Request service</span>
</button>
<button class="tile tile--spa" data-panel="spa" type="button">
<span class="tile-icon" aria-hidden="true">♨</span>
<span class="tile-title">Spa & Wellness</span>
<span class="tile-sub">Bookings & info</span>
</button>
<button class="tile tile--tv" data-panel="tv" type="button">
<span class="tile-icon" aria-hidden="true">📺</span>
<span class="tile-title">TV & Media</span>
<span class="tile-sub">Cast & stream</span>
</button>
<button class="tile tile--climate is-active-tile" data-panel="climate" type="button">
<span class="tile-icon" aria-hidden="true">💡</span>
<span class="tile-title">Lights & Climate</span>
<span class="tile-sub">22 °C · All on</span>
</button>
<button class="tile tile--concierge" data-panel="concierge" type="button">
<span class="tile-icon" aria-hidden="true">🛎</span>
<span class="tile-title">Concierge</span>
<span class="tile-sub">Ask anything</span>
</button>
<button class="tile tile--checkout" data-panel="checkout" type="button">
<span class="tile-icon" aria-hidden="true">🏷</span>
<span class="tile-title">Checkout</span>
<span class="tile-sub">12 Jun 2026</span>
</button>
</main>
</div>
<!-- ── Panel: Room Service ── -->
<div class="screen screen--panel" id="panelRoomService" hidden>
<div class="panel-header">
<button class="back-btn" data-home type="button">← Back</button>
<div>
<p class="panel-kicker">24-hour service</p>
<h2 class="panel-title">Room Service</h2>
</div>
<span class="badge-live">Open now</span>
</div>
<div class="panel-body">
<div class="menu-section">
<p class="menu-section-label">Breakfast</p>
<div class="menu-items" id="menuBreakfast">
<div class="menu-item" data-id="b1" data-price="14">
<div class="mi-info">
<p class="mi-name">Continental basket</p>
<p class="mi-desc">Croissants, jam, butter, OJ, coffee</p>
</div>
<div class="mi-right">
<span class="mi-price">€14</span>
<div class="qty-ctrl">
<button class="qty-btn" data-delta="-1" type="button">−</button>
<span class="qty-val" id="qty-b1">0</span>
<button class="qty-btn" data-delta="1" type="button">+</button>
</div>
</div>
</div>
<div class="menu-item" data-id="b2" data-price="18">
<div class="mi-info">
<p class="mi-name">Full English</p>
<p class="mi-desc">Eggs, bacon, sausage, tomato, toast</p>
</div>
<div class="mi-right">
<span class="mi-price">€18</span>
<div class="qty-ctrl">
<button class="qty-btn" data-delta="-1" type="button">−</button>
<span class="qty-val" id="qty-b2">0</span>
<button class="qty-btn" data-delta="1" type="button">+</button>
</div>
</div>
</div>
<div class="menu-item" data-id="b3" data-price="9">
<div class="mi-info">
<p class="mi-name">Fresh fruit bowl</p>
<p class="mi-desc">Seasonal selection</p>
</div>
<div class="mi-right">
<span class="mi-price">€9</span>
<div class="qty-ctrl">
<button class="qty-btn" data-delta="-1" type="button">−</button>
<span class="qty-val" id="qty-b3">0</span>
<button class="qty-btn" data-delta="1" type="button">+</button>
</div>
</div>
</div>
</div>
</div>
<div class="menu-section">
<p class="menu-section-label">Evening Dining</p>
<div class="menu-items" id="menuDinner">
<div class="menu-item" data-id="d1" data-price="28">
<div class="mi-info">
<p class="mi-name">Grilled sea bass</p>
<p class="mi-desc">Lemon butter, capers, new potatoes</p>
</div>
<div class="mi-right">
<span class="mi-price">€28</span>
<div class="qty-ctrl">
<button class="qty-btn" data-delta="-1" type="button">−</button>
<span class="qty-val" id="qty-d1">0</span>
<button class="qty-btn" data-delta="1" type="button">+</button>
</div>
</div>
</div>
<div class="menu-item" data-id="d2" data-price="32">
<div class="mi-info">
<p class="mi-name">Wagyu beef fillet</p>
<p class="mi-desc">200 g, truffle jus, seasonal veg</p>
</div>
<div class="mi-right">
<span class="mi-price">€32</span>
<div class="qty-ctrl">
<button class="qty-btn" data-delta="-1" type="button">−</button>
<span class="qty-val" id="qty-d2">0</span>
<button class="qty-btn" data-delta="1" type="button">+</button>
</div>
</div>
</div>
<div class="menu-item" data-id="d3" data-price="12">
<div class="mi-info">
<p class="mi-name">Cheese board</p>
<p class="mi-desc">Three cheeses, quince, crackers</p>
</div>
<div class="mi-right">
<span class="mi-price">€12</span>
<div class="qty-ctrl">
<button class="qty-btn" data-delta="-1" type="button">−</button>
<span class="qty-val" id="qty-d3">0</span>
<button class="qty-btn" data-delta="1" type="button">+</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="panel-footer">
<div class="order-total">
<span class="ot-label">Order total</span>
<strong class="ot-val" id="orderTotal">€0.00</strong>
<span class="ot-note" id="orderNote">No items selected</span>
</div>
<button class="primary-btn" id="sendOrderBtn" type="button" disabled>Send to kitchen</button>
</div>
</div>
<!-- ── Panel: Lights & Climate ── -->
<div class="screen screen--panel" id="panelClimate" hidden>
<div class="panel-header">
<button class="back-btn" data-home type="button">← Back</button>
<div>
<p class="panel-kicker">Room 512</p>
<h2 class="panel-title">Lights & Climate</h2>
</div>
<span class="badge-live" style="background:rgba(74,109,160,0.2);color:#8ab4e8;">Smart controls</span>
</div>
<div class="panel-body panel-body--climate">
<div class="climate-section">
<p class="climate-label">Temperature</p>
<div class="temp-ctrl">
<button class="temp-btn" id="tempDown" type="button">−</button>
<div class="temp-display">
<span class="temp-val" id="tempVal">22</span>
<span class="temp-unit">°C</span>
</div>
<button class="temp-btn" id="tempUp" type="button">+</button>
</div>
<p class="temp-hint">Eco range: 19 – 26 °C</p>
</div>
<div class="climate-section">
<p class="climate-label">Lighting zones</p>
<div class="light-zones" id="lightZones">
<label class="light-row">
<span class="light-name">Main ceiling</span>
<input type="checkbox" class="light-toggle" data-zone="main" checked />
</label>
<label class="light-row">
<span class="light-name">Bedside lamps</span>
<input type="checkbox" class="light-toggle" data-zone="bedside" checked />
</label>
<label class="light-row">
<span class="light-name">Bathroom</span>
<input type="checkbox" class="light-toggle" data-zone="bathroom" />
</label>
<label class="light-row">
<span class="light-name">Wardrobe accent</span>
<input type="checkbox" class="light-toggle" data-zone="wardrobe" />
</label>
<label class="light-row">
<span class="light-name">Balcony</span>
<input type="checkbox" class="light-toggle" data-zone="balcony" checked />
</label>
</div>
</div>
<div class="climate-section">
<p class="climate-label">Do Not Disturb</p>
<label class="dnd-toggle" id="dndRow">
<input type="checkbox" id="dndCheck" />
<span class="dnd-track">
<span class="dnd-thumb"></span>
</span>
<span class="dnd-text" id="dndText">Off</span>
</label>
</div>
</div>
</div>
<!-- ── Simple panels (housekeeping, spa, tv, concierge, checkout) ── -->
<div class="screen screen--panel" id="panelHousekeeping" hidden>
<div class="panel-header">
<button class="back-btn" data-home type="button">← Back</button>
<div>
<p class="panel-kicker">Room 512</p>
<h2 class="panel-title">Housekeeping</h2>
</div>
</div>
<div class="panel-body panel-body--simple">
<div class="simple-grid">
<button class="simple-tile" data-toast="Turndown service requested for 20:00" type="button">
<span class="st-icon" aria-hidden="true">🛏</span>
<span>Turndown service</span>
</button>
<button class="simple-tile" data-toast="Extra towels on the way — 15 min" type="button">
<span class="st-icon" aria-hidden="true">🧴</span>
<span>Extra towels</span>
</button>
<button class="simple-tile" data-toast="Room cleaning scheduled for 10:00 tomorrow" type="button">
<span class="st-icon" aria-hidden="true">🧹</span>
<span>Schedule cleaning</span>
</button>
<button class="simple-tile" data-toast="Minibar restock request sent" type="button">
<span class="st-icon" aria-hidden="true">🥤</span>
<span>Restock minibar</span>
</button>
</div>
</div>
</div>
<div class="screen screen--panel" id="panelSpa" hidden>
<div class="panel-header">
<button class="back-btn" data-home type="button">← Back</button>
<div>
<p class="panel-kicker">Level B1</p>
<h2 class="panel-title">Spa & Wellness</h2>
</div>
</div>
<div class="panel-body panel-body--simple">
<div class="info-cards">
<div class="info-card">
<p class="ic-label">Opening hours</p>
<p class="ic-val">07:00 – 22:00 daily</p>
</div>
<div class="info-card">
<p class="ic-label">Pool temperature</p>
<p class="ic-val">28 °C</p>
</div>
<div class="info-card">
<p class="ic-label">Next available slot</p>
<p class="ic-val">Today 16:30</p>
</div>
</div>
<button class="simple-tile wide" data-toast="Spa reservation request sent — we will confirm shortly" type="button">
<span class="st-icon" aria-hidden="true">♨</span>
<span>Reserve a treatment</span>
</button>
</div>
</div>
<div class="screen screen--panel" id="panelTv" hidden>
<div class="panel-header">
<button class="back-btn" data-home type="button">← Back</button>
<div>
<p class="panel-kicker">Room 512</p>
<h2 class="panel-title">TV & Media</h2>
</div>
</div>
<div class="panel-body panel-body--simple">
<div class="simple-grid">
<button class="simple-tile" data-toast="Casting activated on room TV" type="button">
<span class="st-icon" aria-hidden="true">📱</span>
<span>Cast from phone</span>
</button>
<button class="simple-tile" data-toast="Netflix launched on TV" type="button">
<span class="st-icon" aria-hidden="true">🎬</span>
<span>Netflix</span>
</button>
<button class="simple-tile" data-toast="Spotify Connect enabled" type="button">
<span class="st-icon" aria-hidden="true">🎵</span>
<span>Spotify</span>
</button>
<button class="simple-tile" data-toast="TV Guide opened" type="button">
<span class="st-icon" aria-hidden="true">📺</span>
<span>Live channels</span>
</button>
</div>
</div>
</div>
<div class="screen screen--panel" id="panelConcierge" hidden>
<div class="panel-header">
<button class="back-btn" data-home type="button">← Back</button>
<div>
<p class="panel-kicker">24 h</p>
<h2 class="panel-title">Concierge</h2>
</div>
</div>
<div class="panel-body panel-body--simple">
<div class="simple-grid">
<button class="simple-tile" data-toast="Restaurant recommendations sent to your phone" type="button">
<span class="st-icon" aria-hidden="true">🍷</span>
<span>Restaurants</span>
</button>
<button class="simple-tile" data-toast="Taxi booked — arriving in 8 min" type="button">
<span class="st-icon" aria-hidden="true">🚖</span>
<span>Book a taxi</span>
</button>
<button class="simple-tile" data-toast="Tour schedule sent to your email" type="button">
<span class="st-icon" aria-hidden="true">🗺</span>
<span>City tours</span>
</button>
<button class="simple-tile" data-toast="Luggage assistance requested" type="button">
<span class="st-icon" aria-hidden="true">🧳</span>
<span>Luggage help</span>
</button>
</div>
</div>
</div>
<div class="screen screen--panel" id="panelCheckout" hidden>
<div class="panel-header">
<button class="back-btn" data-home type="button">← Back</button>
<div>
<p class="panel-kicker">Room 512</p>
<h2 class="panel-title">Checkout</h2>
</div>
</div>
<div class="panel-body panel-body--simple">
<div class="bill-summary">
<div class="bill-row">
<span>Room (3 nights × €232)</span>
<span class="bill-val">€696.00</span>
</div>
<div class="bill-row">
<span>Minibar</span>
<span class="bill-val">€24.50</span>
</div>
<div class="bill-row">
<span>Spa (1 treatment)</span>
<span class="bill-val">€85.00</span>
</div>
<div class="bill-row">
<span>City tax (3 nights × €1.65)</span>
<span class="bill-val">€4.95</span>
</div>
<div class="bill-divider"></div>
<div class="bill-row bill-row--total">
<span>Total</span>
<span class="bill-val">€810.45</span>
</div>
</div>
<div class="checkout-actions">
<button class="primary-btn" data-toast="Express checkout complete — receipt sent to your email. Safe travels!" type="button">
Express checkout
</button>
<button class="ghost-btn" data-toast="Requesting late checkout (14:00) — we will confirm shortly" type="button">
Request late checkout
</button>
</div>
</div>
</div>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Hotel In-Room Tablet
An elegant dark-themed full-screen tablet home screen for the Aurelia Hotels in-room experience. The top bar shows a personalised greeting for the guest, current room, a live clock, and today’s weather. Seven service tiles fill the main grid — Room Service, Housekeeping, Spa, TV & Media, Lights & Climate, Concierge, and Checkout. Tapping any tile slides in a dedicated panel: the Room Service panel presents a breakfast/dinner menu where items can be added with quantity steppers, showing a live running total and a “Send to kitchen” confirmation button. The Lights & Climate panel exposes individual light-zone toggles plus a temperature stepper. All panels return to the home screen via a back button. A live clock updates every second and a toast confirms sent orders.