UIコンポーネント 簡単
Hotel PMS — Walk-in Booking Sheet
Compact sheet for a guest who walks up to the front desk: single-screen form for nights, room category, guests, basic details, with available-room pills and instant rate quote.
Labで開く
MCP
html css vanilla-js
ターゲット: JS HTML
コード
: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.2);
--success: #4a7752;
--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;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
body {
font-family: var(--font-body);
color: var(--ink);
-webkit-font-smoothing: antialiased;
background: radial-gradient(circle at 22% 18%, rgba(201, 166, 73, 0.2), transparent 55%),
linear-gradient(160deg, #14213b 0%, #0f1d36 100%);
}
.bg {
min-height: 100vh;
display: grid;
place-items: center;
padding: 28px;
}
.sheet {
width: min(560px, 100%);
background: var(--bone);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 40px 100px rgba(15, 29, 54, 0.55);
display: flex;
flex-direction: column;
max-height: 92vh;
}
.sheet-head {
padding: 22px 26px 18px;
border-bottom: 1px solid var(--line);
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.kicker {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--gold-d);
font-weight: 700;
}
.sheet-head h2 {
font-family: var(--font-display);
font-size: 1.75rem;
font-weight: 700;
letter-spacing: -0.005em;
color: var(--navy-d);
margin-top: 2px;
}
.badge {
background: rgba(74, 119, 82, 0.14);
color: var(--success);
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
padding: 5px 12px;
border-radius: 999px;
}
.sheet-body {
padding: 18px 26px;
overflow-y: auto;
}
.block {
padding: 14px 0;
border-bottom: 1px solid var(--line);
}
.block:last-child {
border-bottom: none;
}
.block h3 {
font-size: 0.74rem;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--gold-d);
font-weight: 700;
margin-bottom: 10px;
}
.row-2 {
display: grid;
grid-template-columns: auto 1fr;
gap: 18px;
align-items: center;
}
.stepper {
display: inline-flex;
align-items: stretch;
background: var(--cream);
border: 1px solid var(--line-strong);
border-radius: var(--r-md);
padding: 4px;
gap: 4px;
}
.stepper button {
width: 36px;
background: var(--bone);
border: 1px solid var(--line);
border-radius: var(--r-sm);
font-family: var(--font-display);
font-size: 1.3rem;
font-weight: 700;
cursor: pointer;
color: var(--navy-d);
}
.stepper button:hover {
background: var(--cream-2);
}
.step-val {
padding: 4px 14px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 76px;
}
.step-val strong {
font-family: var(--font-display);
font-size: 1.6rem;
font-weight: 700;
color: var(--navy-d);
line-height: 1;
}
.step-val span {
font-size: 0.74rem;
color: var(--warm-gray);
font-weight: 600;
}
.dates p {
font-size: 0.86rem;
color: var(--ink-2);
padding: 2px 0;
}
.dates p strong {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--warm-gray);
font-weight: 700;
margin-right: 6px;
}
.dates p span {
font-family: var(--font-mono);
font-weight: 600;
color: var(--ink);
}
.cats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.cat {
background: var(--cream);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 10px 12px;
text-align: left;
font-family: inherit;
color: inherit;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 2px;
}
.cat:hover {
border-color: var(--gold);
}
.cat.is-active {
background: var(--bone);
border-color: var(--gold);
outline: 1px solid var(--gold);
}
.cat strong {
font-size: 0.9rem;
font-weight: 600;
color: var(--navy-d);
}
.cat small {
font-size: 0.74rem;
color: var(--warm-gray);
}
.rooms {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.rpill {
background: var(--bone);
border: 1px solid var(--line-strong);
font-family: var(--font-mono);
font-size: 0.84rem;
font-weight: 700;
color: var(--navy-d);
padding: 7px 14px;
border-radius: 999px;
cursor: pointer;
}
.rpill:hover {
border-color: var(--gold);
}
.rpill.is-selected {
background: var(--gold);
color: var(--navy-d);
border-color: var(--gold);
}
.rpill.is-empty {
color: var(--warm-gray);
background: transparent;
border-style: dashed;
cursor: not-allowed;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.field span {
font-size: 0.74rem;
color: var(--warm-gray);
font-weight: 600;
}
.field input {
font-family: inherit;
font-size: 0.9rem;
background: var(--cream);
border: 1px solid var(--line-strong);
border-radius: var(--r-sm);
padding: 9px 12px;
color: var(--ink);
outline: none;
}
.field input:focus {
border-color: var(--gold);
}
.check {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.84rem;
color: var(--ink-2);
margin-top: 10px;
cursor: pointer;
}
.check input {
accent-color: var(--navy);
width: 14px;
height: 14px;
}
/* ── Footer ── */
.sheet-foot {
padding: 16px 26px 22px;
background: var(--cream);
border-top: 1px solid var(--line);
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.quote {
display: flex;
flex-direction: column;
}
.q-label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--warm-gray);
font-weight: 700;
}
.q-val {
font-family: var(--font-display);
font-size: 1.75rem;
font-weight: 700;
color: var(--gold-d);
line-height: 1.1;
}
.q-meta {
font-size: 0.74rem;
color: var(--warm-gray);
margin-top: 2px;
}
.acts {
display: flex;
gap: 8px;
}
.ghost,
.primary {
font-family: inherit;
font-weight: 600;
font-size: 0.86rem;
padding: 11px 18px;
border-radius: 999px;
cursor: pointer;
}
.ghost {
background: transparent;
border: 1px solid var(--line-strong);
color: var(--ink-2);
}
.ghost:hover {
background: var(--bone);
border-color: var(--navy-2);
color: var(--navy-d);
}
.primary {
background: var(--navy);
color: var(--bone);
border: none;
}
.primary:hover {
background: var(--navy-d);
}
.toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: var(--navy-d);
color: var(--bone);
padding: 11px 22px;
border-radius: 999px;
font-size: 0.86rem;
font-weight: 600;
box-shadow: 0 10px 30px rgba(22, 30, 44, 0.18);
}const AVAIL = {
single: ["101", "105"],
double: ["102", "103", "208"],
deluxe: ["201", "205", "211"],
junior: ["302", "303"],
};
let nights = 1;
let cat = "single";
let rate = 142;
let room = null;
const fmt = (n) =>
`€${n.toLocaleString("en-GB", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
const $ = (id) => document.getElementById(id);
function tomorrowFmt(offset) {
const d = new Date();
d.setDate(d.getDate() + offset);
return d.toLocaleDateString("en-GB", { weekday: "short", day: "2-digit", month: "short" });
}
function renderDates() {
$("dIn").textContent = tomorrowFmt(0);
$("dOut").textContent = tomorrowFmt(nights);
}
function renderNights() {
$("nightsN").textContent = nights;
$("nightsS").textContent = nights > 1 ? "s" : "";
}
function renderRooms() {
const list = AVAIL[cat];
if (!list || list.length === 0) {
$("rooms").innerHTML = `<span class="rpill is-empty">No rooms available</span>`;
room = null;
return;
}
$("rooms").innerHTML = list
.map(
(r) =>
`<button type="button" class="rpill ${room === r ? "is-selected" : ""}" data-r="${r}">${r}</button>`
)
.join("");
}
function renderQuote() {
const subtotal = nights * rate;
const city = 1.65 * nights;
const total = subtotal + city;
$("totalQ").textContent = fmt(total);
$("quoteMeta").textContent =
`${nights} night${nights > 1 ? "s" : ""} · €${rate} BAR · incl. city tax ${fmt(city)}`;
}
document.querySelectorAll(".stepper button").forEach((b) =>
b.addEventListener("click", () => {
const delta = parseInt(b.dataset.step, 10);
nights = Math.max(1, Math.min(14, nights + delta));
renderNights();
renderDates();
renderQuote();
})
);
document.getElementById("cats").addEventListener("click", (e) => {
const c = e.target.closest(".cat");
if (!c) return;
document.querySelectorAll(".cat").forEach((x) => x.classList.remove("is-active"));
c.classList.add("is-active");
cat = c.dataset.cat;
rate = parseFloat(c.dataset.rate);
room = null;
renderRooms();
renderQuote();
});
document.getElementById("rooms").addEventListener("click", (e) => {
const p = e.target.closest(".rpill");
if (!p || p.classList.contains("is-empty")) return;
room = p.dataset.r;
renderRooms();
});
document.getElementById("checkin").addEventListener("click", () => {
const t = document.getElementById("toast");
const name = $("gName").value || "guest";
if (!room) {
t.textContent = "Pick a room from the list to continue.";
} else {
t.textContent = `Booked · ${name} → room ${room} for ${nights} night${nights > 1 ? "s" : ""}`;
}
t.hidden = false;
clearTimeout(window.__t);
window.__t = setTimeout(() => (t.hidden = true), 2000);
});
renderNights();
renderDates();
renderRooms();
renderQuote();<!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>Walk-in · Aurelia Hotels</title>
</head>
<body>
<div class="bg">
<section class="sheet" role="dialog" aria-modal="true" aria-labelledby="sTitle">
<header class="sheet-head">
<div>
<p class="kicker">Front desk</p>
<h2 id="sTitle">Walk-in booking</h2>
</div>
<span class="badge">Live availability</span>
</header>
<div class="sheet-body">
<section class="block">
<h3>Stay</h3>
<div class="row-2">
<div class="stepper" role="group" aria-label="Nights">
<button data-step="-1" type="button" aria-label="Fewer nights">−</button>
<div class="step-val">
<strong id="nightsN">1</strong>
<span>night<span id="nightsS"></span></span>
</div>
<button data-step="1" type="button" aria-label="More nights">+</button>
</div>
<div class="dates">
<p><strong>In</strong> <span id="dIn">—</span></p>
<p><strong>Out</strong> <span id="dOut">—</span></p>
</div>
</div>
</section>
<section class="block">
<h3>Room category</h3>
<div class="cats" id="cats">
<button class="cat is-active" data-rate="142" data-cat="single">
<strong>Single Standard</strong>
<small>1 adult · €142/night</small>
</button>
<button class="cat" data-rate="184" data-cat="double">
<strong>Classic Double</strong>
<small>2 adults · €184/night</small>
</button>
<button class="cat" data-rate="232" data-cat="deluxe">
<strong>Deluxe Double</strong>
<small>2 adults · €232/night</small>
</button>
<button class="cat" data-rate="241" data-cat="junior">
<strong>Junior Suite</strong>
<small>2 + 1 · €241/night</small>
</button>
</div>
</section>
<section class="block">
<h3>Available now</h3>
<div class="rooms" id="rooms"></div>
</section>
<section class="block">
<h3>Guest details</h3>
<div class="grid-2">
<label class="field"><span>Full name</span><input id="gName" type="text" placeholder="As shown on ID" /></label>
<label class="field"><span>Email</span><input id="gEmail" type="email" placeholder="optional" /></label>
<label class="field"><span>Phone</span><input id="gPhone" type="tel" placeholder="+34…" /></label>
<label class="field"><span>Country</span><input id="gCountry" type="text" placeholder="Nationality" /></label>
</div>
<label class="check"><input type="checkbox" checked /> Verify ID at check-in (passport / national ID)</label>
</section>
</div>
<footer class="sheet-foot">
<div class="quote">
<span class="q-label">Total stay</span>
<strong class="q-val" id="totalQ">€142.00</strong>
<span class="q-meta" id="quoteMeta">1 night · €142 BAR · incl. city tax €1.65</span>
</div>
<div class="acts">
<button class="ghost">Save as reservation</button>
<button class="primary" id="checkin">Book & check in</button>
</div>
</footer>
</section>
</div>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Walk-in Booking Sheet
The sheet a receptionist uses for a guest with no reservation. Picks the number of nights with a stepper, room category, guests; available-room pills update based on the category; a small inline rate quote shows tonight’s rate and total. Footer creates the reservation + checks in immediately.