Real Estate — Vacation / Short-term Rental Landing
A bright, breezy single-page landing for a fictional vacation-rental brand in a sand, teal, and coral palette. Features an airy escape hero with a destination and dates search, a guests stepper popover, a filterable getaways grid with gradient property photos, nightly prices, ratings and sleeps badges, a scrollable destinations carousel, a host CTA with a live earnings estimator, and trust highlights — all vanilla HTML, CSS, and JS with toast feedback.
MCP
الكود
:root {
/* Vacation / short-term rental palette */
--sand:#efe2cf;
--sand-2:#f7efe2;
--paper:#fffdf9;
--white:#ffffff;
--teal:#2a9d9b;
--teal-d:#1f7c7b;
--teal-700:#246f6e;
--teal-50:#e1f1f0;
--coral:#f08a6c;
--coral-d:#dc6f50;
--coral-50:#fdeae3;
--ink:#22302e;
--ink-2:#3c4d4a;
--muted:#6d7d79;
--line:rgba(34,48,46,0.12);
--line-2:rgba(34,48,46,0.20);
--ok:#2f9e6f;
--warn:#c98a2b;
--danger:#c4503e;
--r-sm:8px;
--r-md:14px;
--r-lg:22px;
--sh-1:0 1px 2px rgba(34,48,46,.06), 0 4px 14px rgba(34,48,46,.06);
--sh-2:0 10px 30px rgba(34,48,46,.10), 0 2px 6px rgba(34,48,46,.06);
--sh-3:0 24px 60px rgba(34,48,46,.16);
--maxw:1180px;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin:0;
font-family:"Inter", system-ui, -apple-system, Segoe UI, sans-serif;
font-size:16px;
line-height:1.55;
color:var(--ink);
background:var(--sand);
-webkit-font-smoothing:antialiased;
text-rendering:optimizeLegibility;
}
h1,h2,h3,h4 { font-family:"Inter", system-ui, sans-serif; line-height:1.08; letter-spacing:-0.02em; margin:0; }
p { margin:0; }
a { color:inherit; text-decoration:none; }
img { max-width:100%; display:block; }
.wrap { width:100%; max-width:var(--maxw); margin-inline:auto; padding-inline:24px; }
.eyebrow {
font-size:12px; font-weight:700; letter-spacing:.16em; text-transform:uppercase;
color:var(--teal-d); margin-bottom:12px;
}
.sr-only {
position:absolute; width:1px; height:1px; padding:0; margin:-1px;
overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
/* ---------- Buttons ---------- */
.btn {
font:inherit; font-weight:600; cursor:pointer; border:0;
border-radius:999px; padding:11px 20px;
display:inline-flex; align-items:center; gap:8px; justify-content:center;
transition:transform .15s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.btn:active { transform:translateY(1px); }
.btn-solid { background:var(--teal); color:#fff; box-shadow:var(--sh-1); }
.btn-solid:hover { background:var(--teal-d); box-shadow:var(--sh-2); }
.btn-ghost { background:transparent; color:var(--ink); }
.btn-ghost:hover { background:rgba(34,48,46,.06); }
.btn-coral { background:var(--coral); color:#fff; box-shadow:0 8px 22px rgba(240,138,108,.4); }
.btn-coral:hover { background:var(--coral-d); }
.btn:focus-visible, a:focus-visible, input:focus-visible, .chip:focus-visible, .step:focus-visible, .round:focus-visible, .guests-toggle:focus-visible {
outline:3px solid var(--teal); outline-offset:2px;
}
/* ---------- Header ---------- */
.site {
position:sticky; top:0; z-index:40;
background:rgba(239,226,207,.72);
backdrop-filter:saturate(140%) blur(10px);
border-bottom:1px solid transparent;
transition:background .25s ease, border-color .25s ease, box-shadow .25s ease;
}
.site.scrolled {
background:rgba(255,253,249,.86);
border-bottom-color:var(--line);
box-shadow:var(--sh-1);
}
.nav { display:flex; align-items:center; gap:24px; height:68px; }
.brand { display:flex; align-items:center; gap:10px; font-weight:700; }
.brand-mark {
width:26px; height:26px; border-radius:8px;
background:linear-gradient(135deg, var(--teal) 0%, var(--coral) 120%);
box-shadow:inset 0 0 0 2px rgba(255,255,255,.4);
}
.brand-name { font-size:19px; letter-spacing:-0.01em; }
.nav-links { display:flex; gap:26px; margin-left:14px; font-weight:500; font-size:15px; }
.nav-links a { color:var(--ink-2); padding:6px 2px; position:relative; }
.nav-links a:hover { color:var(--teal-d); }
.nav-cta { margin-left:auto; display:flex; align-items:center; gap:8px; }
/* ---------- Hero ---------- */
.hero { position:relative; overflow:hidden; padding:56px 0 72px; isolation:isolate; }
.hero-art {
position:absolute; inset:0; z-index:-1;
background:
radial-gradient(120% 90% at 80% -10%, #ffe8c2 0%, transparent 55%),
radial-gradient(140% 120% at 12% 0%, #bfeae6 0%, transparent 48%),
linear-gradient(180deg, #fdf3e3 0%, #f4ead7 52%, #e9f4f1 100%);
}
.hero-art .sun {
position:absolute; top:8%; right:14%; width:170px; height:170px; border-radius:50%;
background:radial-gradient(circle at 50% 50%, #ffd277 0%, #ffb24d 55%, rgba(255,178,77,0) 72%);
filter:blur(2px); opacity:.85;
}
.hero-art .wave { position:absolute; left:-5%; right:-5%; height:120px; border-radius:50%; opacity:.55; }
.hero-art .wave-1 { bottom:-60px; background:radial-gradient(60% 100% at 50% 0,#7fcfca 0,rgba(127,207,202,0) 70%); }
.hero-art .wave-2 { bottom:-92px; background:radial-gradient(60% 100% at 50% 0,#46b3ae 0,rgba(70,179,174,0) 70%); }
.hero-art .palm {
position:absolute; bottom:0; right:6%; width:220px; height:260px;
background:
radial-gradient(34% 60% at 50% 0, #2f7d5f 0 40%, transparent 42%),
conic-gradient(from 200deg at 50% 12%, #3a8d6c, #2f7d5f, #256a52, #3a8d6c);
-webkit-mask:radial-gradient(38% 30% at 50% 8%, #000 60%, transparent 62%);
opacity:.0;
}
.hero-inner { position:relative; max-width:760px; }
.hero h1 {
font-size:clamp(36px, 6.2vw, 62px);
font-weight:700;
margin:0 0 16px;
}
.hero-sub { font-size:clamp(16px,2.1vw,19px); color:var(--ink-2); max-width:560px; }
/* ---------- Search bar ---------- */
.searchbar {
margin-top:28px;
background:var(--white);
border:1px solid var(--line);
border-radius:var(--r-lg);
box-shadow:var(--sh-2);
padding:10px;
display:grid;
grid-template-columns:1.4fr 1fr 1fr 1fr auto;
gap:6px;
align-items:stretch;
}
.field { position:relative; padding:8px 14px; border-radius:var(--r-md); display:flex; flex-direction:column; gap:3px; }
.field + .field { box-shadow:inset 1px 0 0 var(--line); }
.field label { font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }
.field input {
border:0; background:transparent; font:inherit; font-size:15px; font-weight:500; color:var(--ink);
padding:2px 0; width:100%; outline:none;
}
.field input::placeholder { color:#9aa7a3; font-weight:400; }
.field:hover { background:var(--sand-2); }
.field-guests { display:flex; flex-direction:column; gap:3px; }
.guests-toggle {
border:0; background:transparent; font:inherit; font-size:15px; font-weight:500; color:var(--ink);
display:flex; align-items:center; justify-content:space-between; gap:8px; cursor:pointer; padding:2px 0;
width:100%;
}
.guests-toggle svg { color:var(--muted); transition:transform .2s ease; }
.guests-toggle[aria-expanded="true"] svg { transform:rotate(180deg); }
.guests-pop {
position:absolute; top:calc(100% + 10px); right:0; z-index:30;
width:300px; max-width:80vw;
background:var(--white); border:1px solid var(--line); border-radius:var(--r-md);
box-shadow:var(--sh-3); padding:8px 16px;
}
.stepper-row { display:flex; align-items:center; justify-content:space-between; padding:14px 0; }
.stepper-row + .stepper-row { border-top:1px solid var(--line); }
.stepper-name { display:block; font-weight:600; font-size:15px; }
.stepper-meta { display:block; font-size:13px; color:var(--muted); }
.stepper { display:flex; align-items:center; gap:14px; }
.step {
width:32px; height:32px; border-radius:50%; border:1.5px solid var(--line-2); background:#fff;
font-size:18px; line-height:1; color:var(--teal-d); cursor:pointer; display:grid; place-items:center;
transition:border-color .15s ease, color .15s ease, background .15s ease;
}
.step:hover:not(:disabled) { border-color:var(--teal); background:var(--teal-50); }
.step:disabled { opacity:.35; cursor:not-allowed; }
.step-count { min-width:18px; text-align:center; font-weight:600; font-variant-numeric:tabular-nums; }
.btn-search {
background:var(--coral); color:#fff; align-self:stretch; padding-inline:22px;
box-shadow:0 8px 22px rgba(240,138,108,.42);
}
.btn-search:hover { background:var(--coral-d); }
.hero-trust { list-style:none; margin:26px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:26px; }
.hero-trust li { font-size:14px; color:var(--ink-2); }
.hero-trust strong { color:var(--teal-d); font-weight:700; }
/* ---------- Sections ---------- */
.section { padding:72px 0; }
.section-tint { background:var(--sand-2); }
.section-head { display:flex; align-items:flex-end; justify-content:space-between; gap:24px; margin-bottom:34px; flex-wrap:wrap; }
.section-head h2 { font-size:clamp(26px,3.6vw,38px); font-weight:700; }
.section-head .eyebrow { margin-bottom:8px; }
/* ---------- Filter chips ---------- */
.chips { display:flex; flex-wrap:wrap; gap:8px; }
.chip {
font:inherit; font-weight:600; font-size:14px; cursor:pointer;
border:1px solid var(--line-2); background:#fff; color:var(--ink-2);
padding:8px 16px; border-radius:999px; transition:all .15s ease;
}
.chip:hover { border-color:var(--teal); color:var(--teal-d); }
.chip.is-active { background:var(--teal); border-color:var(--teal); color:#fff; box-shadow:var(--sh-1); }
/* ---------- Getaways grid ---------- */
.grid { display:grid; grid-template-columns:repeat(3, 1fr); gap:24px; }
.empty { text-align:center; color:var(--muted); padding:40px 0; }
.card {
background:var(--white); border:1px solid var(--line); border-radius:var(--r-lg);
overflow:hidden; box-shadow:var(--sh-1);
transition:transform .2s ease, box-shadow .25s ease;
display:flex; flex-direction:column;
}
.card:hover { transform:translateY(-4px); box-shadow:var(--sh-3); }
.card-photo { position:relative; aspect-ratio:4/3; background:#cfe; }
.card-photo .badge {
position:absolute; top:12px; left:12px; z-index:2;
background:rgba(255,255,255,.92); color:var(--ink); font-size:12px; font-weight:700;
padding:5px 11px; border-radius:999px; box-shadow:var(--sh-1); letter-spacing:.01em;
}
.card-photo .like {
position:absolute; top:10px; right:10px; z-index:2;
width:34px; height:34px; border-radius:50%; border:0; cursor:pointer;
background:rgba(255,255,255,.9); color:var(--muted); font-size:16px; line-height:1;
display:grid; place-items:center; box-shadow:var(--sh-1); transition:transform .15s ease, color .15s ease;
}
.card-photo .like:hover { transform:scale(1.1); }
.card-photo .like.on { color:var(--coral); }
.card-body { padding:16px 18px 18px; display:flex; flex-direction:column; gap:6px; flex:1; }
.card-top { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.card-loc { font-size:13px; font-weight:600; color:var(--teal-d); letter-spacing:.02em; }
.card-rate { font-size:13.5px; font-weight:600; color:var(--ink); white-space:nowrap; }
.card-rate .star { color:var(--coral); }
.card-title { font-size:17px; font-weight:600; letter-spacing:-0.01em; color:var(--ink); }
.card-meta { font-size:13.5px; color:var(--muted); display:flex; align-items:center; gap:7px; flex-wrap:wrap; }
.card-meta .sleeps {
background:var(--teal-50); color:var(--teal-700); font-weight:600; font-size:12px;
padding:2px 9px; border-radius:999px;
}
.card-foot { margin-top:auto; padding-top:12px; display:flex; align-items:baseline; gap:6px; border-top:1px solid var(--line); }
.card-price { font-size:20px; font-weight:700; color:var(--ink); }
.card-per { font-size:13px; color:var(--muted); }
/* ---------- Carousel ---------- */
.carousel-nav { display:flex; gap:8px; }
.round {
width:42px; height:42px; border-radius:50%; border:1px solid var(--line-2); background:#fff; cursor:pointer;
display:grid; place-items:center; color:var(--ink); transition:all .15s ease;
}
.round:hover { border-color:var(--teal); color:var(--teal-d); background:var(--teal-50); }
.round:disabled { opacity:.4; cursor:not-allowed; }
.carousel {
display:flex; gap:18px; overflow-x:auto; scroll-snap-type:x mandatory;
padding:6px 24px 18px; margin-top:4px;
max-width:var(--maxw); margin-inline:auto;
scrollbar-width:none;
}
.carousel::-webkit-scrollbar { display:none; }
.slide {
position:relative; flex:0 0 280px; scroll-snap-align:start;
aspect-ratio:3/4; border-radius:var(--r-lg); overflow:hidden; cursor:pointer;
box-shadow:var(--sh-1); transition:transform .2s ease, box-shadow .25s ease;
}
.slide:hover { transform:translateY(-4px); box-shadow:var(--sh-3); }
.slide::after { content:""; position:absolute; inset:0; background:linear-gradient(180deg, transparent 38%, rgba(20,30,28,.62) 100%); }
.slide-text { position:absolute; left:18px; bottom:16px; right:18px; z-index:2; color:#fff; }
.slide-text h3 { font-size:21px; font-weight:700; }
.slide-text span { font-size:13.5px; opacity:.92; }
/* ---------- Host CTA ---------- */
.host { background:linear-gradient(180deg, var(--sand) 0%, var(--sand-2) 100%); }
.host-grid { display:grid; grid-template-columns:1.05fr .95fr; gap:48px; align-items:center; }
.host-copy h2 { font-size:clamp(28px,3.8vw,42px); font-weight:700; margin-bottom:16px; }
.lead { font-size:17px; color:var(--ink-2); }
.lead strong { color:var(--teal-d); }
.host-points { list-style:none; margin:22px 0 26px; padding:0; display:grid; gap:12px; }
.host-points li { display:flex; align-items:center; gap:11px; font-size:15px; color:var(--ink-2); }
.host-points .dot { width:9px; height:9px; border-radius:50%; background:var(--coral); flex:0 0 auto; box-shadow:0 0 0 4px var(--coral-50); }
.host-form { display:flex; gap:10px; flex-wrap:wrap; }
.host-form input {
flex:1 1 240px; min-width:0; font:inherit; font-size:15px; padding:13px 18px;
border:1px solid var(--line-2); border-radius:999px; background:#fff; color:var(--ink); outline:none;
}
.host-form input:focus-visible { outline:3px solid var(--teal); outline-offset:2px; border-color:var(--teal); }
.host-fine { margin-top:12px; font-size:13px; color:var(--muted); }
.host-card { background:#fff; border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden; box-shadow:var(--sh-2); }
.host-card-art {
aspect-ratio:16/10;
background:
radial-gradient(90% 70% at 20% 10%, #ffd9a3 0%, transparent 55%),
radial-gradient(100% 90% at 90% 0%, #8fd6d1 0%, transparent 50%),
linear-gradient(135deg, #f3b08e 0%, #2a9d9b 120%);
}
.host-card-body { padding:22px; }
.host-stat { display:flex; align-items:baseline; gap:10px; margin-bottom:18px; }
.host-stat-num { font-size:34px; font-weight:700; letter-spacing:-0.02em; color:var(--ink); font-variant-numeric:tabular-nums; }
.host-stat-label { font-size:13px; color:var(--muted); }
.slider-label { display:flex; justify-content:space-between; font-size:13px; color:var(--ink-2); margin-bottom:8px; font-weight:500; }
.slider-label strong { color:var(--teal-d); }
input[type="range"] {
-webkit-appearance:none; appearance:none; width:100%; height:6px; border-radius:999px;
background:var(--teal-50); outline:none; cursor:pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance:none; width:22px; height:22px; border-radius:50%; background:var(--coral);
border:3px solid #fff; box-shadow:var(--sh-1); cursor:pointer;
}
input[type="range"]::-moz-range-thumb {
width:18px; height:18px; border-radius:50%; background:var(--coral); border:3px solid #fff; cursor:pointer;
}
.host-mini { display:flex; gap:10px; margin-top:22px; }
.host-mini div { flex:1; text-align:center; padding:12px 6px; background:var(--sand-2); border-radius:var(--r-md); }
.host-mini span { display:block; font-weight:700; font-size:16px; color:var(--ink); }
.host-mini small { font-size:11.5px; color:var(--muted); }
/* ---------- Trust ---------- */
.trust { background:var(--teal); color:#fff; }
.trust-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:28px; }
.trust-item { background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.16); border-radius:var(--r-lg); padding:26px; }
.trust-ic { font-size:24px; color:var(--coral); display:block; margin-bottom:12px; }
.trust-item h3 { font-size:19px; font-weight:700; margin-bottom:8px; }
.trust-item p { color:rgba(255,255,255,.82); font-size:14.5px; }
/* ---------- Footer ---------- */
.site-foot { background:var(--ink); color:rgba(255,255,255,.78); padding:56px 0 28px; }
.foot-grid { display:grid; grid-template-columns:1.6fr 1fr 1fr 1fr; gap:32px; }
.foot-brand .brand-name { color:#fff; }
.foot-brand p { margin-top:12px; font-size:14px; max-width:280px; }
.foot-brand { display:flex; flex-direction:column; gap:4px; }
.foot-brand .brand-mark { margin-bottom:4px; }
.site-foot nav h4 { color:#fff; font-size:13px; text-transform:uppercase; letter-spacing:.08em; margin-bottom:14px; }
.site-foot nav a { display:block; font-size:14.5px; padding:5px 0; color:rgba(255,255,255,.74); }
.site-foot nav a:hover { color:var(--coral); }
.foot-base {
margin-top:42px; padding-top:22px; border-top:1px solid rgba(255,255,255,.14);
display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; font-size:13px; color:rgba(255,255,255,.6);
}
/* ---------- Toast ---------- */
.toast {
position:fixed; left:50%; bottom:28px; transform:translateX(-50%) translateY(20px);
background:var(--ink); color:#fff; padding:13px 22px; border-radius:999px;
font-size:14.5px; font-weight:500; box-shadow:var(--sh-3); z-index:90;
opacity:0; pointer-events:none; transition:opacity .25s ease, transform .25s ease; max-width:90vw;
}
.toast.show { opacity:1; transform:translateX(-50%) translateY(0); }
/* ---------- Responsive ---------- */
@media (max-width:980px) {
.grid { grid-template-columns:repeat(2,1fr); }
.host-grid { grid-template-columns:1fr; gap:32px; }
.trust-grid { grid-template-columns:1fr; }
.foot-grid { grid-template-columns:1fr 1fr; }
.searchbar { grid-template-columns:1fr 1fr; }
.field-where { grid-column:1 / -1; }
.btn-search { grid-column:1 / -1; }
.field + .field { box-shadow:none; border-top:1px solid var(--line); }
}
@media (max-width:760px) {
.nav-links { display:none; }
}
@media (max-width:520px) {
.wrap { padding-inline:18px; }
.section { padding:48px 0; }
.hero { padding:36px 0 48px; }
.grid { grid-template-columns:1fr; }
.searchbar { grid-template-columns:1fr; padding:8px; }
.field + .field { box-shadow:none; border-top:1px solid var(--line); }
.field-where, .btn-search { grid-column:auto; }
.guests-pop { right:auto; left:0; }
.foot-grid { grid-template-columns:1fr 1fr; gap:24px; }
.foot-base { flex-direction:column; gap:6px; }
.nav-cta .btn-ghost { display:none; }
.hero-trust { gap:16px; }
.slide { flex-basis:230px; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2600);
}
document.querySelectorAll("[data-toast]").forEach(function (el) {
el.addEventListener("click", function (e) {
if (el.getAttribute("href") === "#") e.preventDefault();
toast(el.getAttribute("data-toast"));
});
});
/* ---------- Sticky header shadow ---------- */
var header = document.getElementById("siteHeader");
function onScroll() {
if (window.scrollY > 12) header.classList.add("scrolled");
else header.classList.remove("scrolled");
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ---------- Listing data ---------- */
var stays = [
{ cat: "beach", loc: "Tulum, Mexico", title: "Casa Marea — beachfront palapa", rate: "4.97", reviews: 184, guests: 6, beds: 3, baths: 2, price: 268, art: "linear-gradient(135deg,#ffd28a 0%,#f0a36b 38%,#2a9d9b 130%)" },
{ cat: "mountain", loc: "Lake Tahoe, USA", title: "Pinecrest A-frame with hot tub", rate: "4.92", reviews: 211, guests: 8, beds: 4, baths: 3, price: 342, art: "linear-gradient(150deg,#9fd6c9 0%,#5b9aa0 45%,#2e4f4b 130%)" },
{ cat: "city", loc: "Lisbon, Portugal", title: "Alfama tiled loft with terrace", rate: "4.89", reviews: 156, guests: 4, beds: 2, baths: 1, price: 174, art: "linear-gradient(135deg,#f7d9b0 0%,#e79b7c 50%,#9c5e6a 130%)" },
{ cat: "beach", loc: "Amalfi Coast, Italy", title: "Limone cliff villa, sea view", rate: "4.99", reviews: 98, guests: 5, beds: 3, baths: 2, price: 410, art: "linear-gradient(135deg,#bfe9ff 0%,#5ec0d8 45%,#1f7c7b 130%)" },
{ cat: "countryside", loc: "Provence, France", title: "Lavender farmhouse & pool", rate: "4.94", reviews: 142, guests: 7, beds: 4, baths: 3, price: 298, art: "linear-gradient(135deg,#e3d4f0 0%,#b39ad1 40%,#6f7f9c 130%)" },
{ cat: "mountain", loc: "Banff, Canada", title: "Glasswall cabin under the peaks", rate: "4.96", reviews: 173, guests: 6, beds: 3, baths: 2, price: 315, art: "linear-gradient(160deg,#cfe6e1 0%,#7aa6a2 45%,#2f4a4f 130%)" },
{ cat: "city", loc: "Mexico City, MX", title: "Roma Norte mid-century flat", rate: "4.91", reviews: 207, guests: 3, beds: 1, baths: 1, price: 132, art: "linear-gradient(135deg,#ffe1a8 0%,#f0926e 50%,#b85c5c 130%)" },
{ cat: "beach", loc: "Bali, Indonesia", title: "Uluwatu jungle bungalow", rate: "4.95", reviews: 264, guests: 4, beds: 2, baths: 2, price: 156, art: "linear-gradient(135deg,#d5f0c0 0%,#5cb39b 45%,#1f6a64 130%)" },
{ cat: "countryside", loc: "Tuscany, Italy", title: "Vineyard stone villa & olive grove", rate: "4.98", reviews: 121, guests: 9, beds: 5, baths: 4, price: 388, art: "linear-gradient(150deg,#f3e2a8 0%,#cda154 45%,#6a5a32 130%)" }
];
var grid = document.getElementById("getawaysGrid");
var emptyEl = document.getElementById("getawaysEmpty");
function fmt(n) { return "$" + n.toLocaleString("en-US"); }
function cardHTML(s) {
return (
'<article class="card" data-cat="' + s.cat + '">' +
'<div class="card-photo" style="background:' + s.art + '">' +
'<span class="badge">★ Guest favourite</span>' +
'<button class="like" type="button" aria-label="Save ' + s.title + '" aria-pressed="false">♥</button>' +
'</div>' +
'<div class="card-body">' +
'<div class="card-top">' +
'<span class="card-loc">' + s.loc + '</span>' +
'<span class="card-rate"><span class="star">★</span> ' + s.rate + ' <span style="color:var(--muted);font-weight:500">(' + s.reviews + ')</span></span>' +
'</div>' +
'<h3 class="card-title">' + s.title + '</h3>' +
'<div class="card-meta">' +
'<span class="sleeps">Sleeps ' + s.guests + '</span>' +
'<span>' + s.beds + ' beds · ' + s.baths + ' baths</span>' +
'</div>' +
'<div class="card-foot">' +
'<span class="card-price">' + fmt(s.price) + '</span>' +
'<span class="card-per">/ night</span>' +
'</div>' +
'</div>' +
'</article>'
);
}
function render(filter) {
var list = filter === "all" ? stays : stays.filter(function (s) { return s.cat === filter; });
grid.innerHTML = list.map(cardHTML).join("");
emptyEl.hidden = list.length !== 0;
grid.querySelectorAll(".like").forEach(function (btn) {
btn.addEventListener("click", function () {
var on = btn.classList.toggle("on");
btn.setAttribute("aria-pressed", on ? "true" : "false");
toast(on ? "Saved to your wishlist" : "Removed from wishlist");
});
});
}
render("all");
/* ---------- Filter chips ---------- */
var chips = document.querySelectorAll(".chip");
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-selected", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-selected", "true");
render(chip.getAttribute("data-filter"));
});
});
/* ---------- Guests stepper ---------- */
var guestsToggle = document.getElementById("guestsToggle");
var guestsPop = document.getElementById("guestsPop");
var guestsValue = document.getElementById("guestsValue");
var counts = { adults: 2, children: 0, pets: 0 };
var limits = { adults: [1, 16], children: [0, 10], pets: [0, 5] };
function updateGuestsLabel() {
var people = counts.adults + counts.children;
var parts = [people + (people === 1 ? " guest" : " guests")];
if (counts.pets) parts.push(counts.pets + (counts.pets === 1 ? " pet" : " pets"));
guestsValue.textContent = parts.join(", ");
}
function syncSteppers() {
document.querySelectorAll(".step-count").forEach(function (el) {
el.textContent = counts[el.getAttribute("data-count")];
});
document.querySelectorAll(".step").forEach(function (btn) {
var key = btn.getAttribute("data-step");
var dir = parseInt(btn.getAttribute("data-dir"), 10);
var v = counts[key];
btn.disabled = dir < 0 ? v <= limits[key][0] : v >= limits[key][1];
});
updateGuestsLabel();
}
document.querySelectorAll(".step").forEach(function (btn) {
btn.addEventListener("click", function () {
var key = btn.getAttribute("data-step");
var dir = parseInt(btn.getAttribute("data-dir"), 10);
var next = counts[key] + dir;
if (next < limits[key][0] || next > limits[key][1]) return;
counts[key] = next;
syncSteppers();
});
});
function openGuests(open) {
guestsPop.hidden = !open;
guestsToggle.setAttribute("aria-expanded", open ? "true" : "false");
}
guestsToggle.addEventListener("click", function (e) {
e.stopPropagation();
openGuests(guestsPop.hidden);
});
document.addEventListener("click", function (e) {
if (!guestsPop.hidden && !guestsPop.contains(e.target) && e.target !== guestsToggle) openGuests(false);
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !guestsPop.hidden) { openGuests(false); guestsToggle.focus(); }
});
syncSteppers();
/* ---------- Search submit ---------- */
var searchForm = document.getElementById("searchForm");
searchForm.addEventListener("submit", function (e) {
e.preventDefault();
var where = document.getElementById("where").value.trim() || "Anywhere";
var people = counts.adults + counts.children;
toast("Searching " + where + " for " + people + (people === 1 ? " guest" : " guests") + "…");
openGuests(false);
});
/* ---------- Destinations carousel ---------- */
var destinations = [
{ name: "Tulum", meta: "1,240 stays", art: "linear-gradient(180deg,#ffd28a 0%,#2a9d9b 130%)" },
{ name: "Amalfi", meta: "612 stays", art: "linear-gradient(180deg,#bfe9ff 0%,#1f7c7b 130%)" },
{ name: "Lake Tahoe", meta: "884 stays", art: "linear-gradient(180deg,#9fd6c9 0%,#2e4f4b 130%)" },
{ name: "Lisbon", meta: "1,506 stays", art: "linear-gradient(180deg,#f7d9b0 0%,#9c5e6a 130%)" },
{ name: "Bali", meta: "2,031 stays", art: "linear-gradient(180deg,#d5f0c0 0%,#1f6a64 130%)" },
{ name: "Provence", meta: "498 stays", art: "linear-gradient(180deg,#e3d4f0 0%,#6f7f9c 130%)" },
{ name: "Banff", meta: "327 stays", art: "linear-gradient(180deg,#cfe6e1 0%,#2f4a4f 130%)" },
{ name: "Tuscany", meta: "742 stays", art: "linear-gradient(180deg,#f3e2a8 0%,#6a5a32 130%)" }
];
var carousel = document.getElementById("carousel");
carousel.innerHTML = destinations.map(function (d) {
return (
'<button class="slide" type="button" style="background:' + d.art + '" aria-label="Explore ' + d.name + '">' +
'<span class="slide-text"><h3>' + d.name + '</h3><span>' + d.meta + '</span></span>' +
'</button>'
);
}).join("");
carousel.querySelectorAll(".slide").forEach(function (slide, i) {
slide.addEventListener("click", function () { toast("Exploring " + destinations[i].name + " — demo only"); });
});
var prevBtn = document.getElementById("carPrev");
var nextBtn = document.getElementById("carNext");
function scrollAmount() {
var first = carousel.querySelector(".slide");
var step = first ? first.getBoundingClientRect().width + 18 : 300;
return step * Math.max(1, Math.floor(carousel.clientWidth / step));
}
function updateCarBtns() {
prevBtn.disabled = carousel.scrollLeft <= 4;
nextBtn.disabled = carousel.scrollLeft + carousel.clientWidth >= carousel.scrollWidth - 4;
}
prevBtn.addEventListener("click", function () { carousel.scrollBy({ left: -scrollAmount(), behavior: "smooth" }); });
nextBtn.addEventListener("click", function () { carousel.scrollBy({ left: scrollAmount(), behavior: "smooth" }); });
carousel.addEventListener("scroll", updateCarBtns, { passive: true });
window.addEventListener("resize", updateCarBtns);
updateCarBtns();
/* ---------- Host earnings slider ---------- */
var nights = document.getElementById("nights");
var nightsOut = document.getElementById("nightsOut");
var estValue = document.getElementById("estValue");
var NIGHTLY = 142; // assumed avg nightly net for the demo estimate
function updateEst() {
var n = parseInt(nights.value, 10);
nightsOut.textContent = n;
estValue.textContent = "$" + (n * NIGHTLY).toLocaleString("en-US");
}
nights.addEventListener("input", updateEst);
updateEst();
/* ---------- Host email form ---------- */
var hostForm = document.getElementById("hostForm");
hostForm.addEventListener("submit", function (e) {
e.preventDefault();
var email = document.getElementById("hostEmail");
if (!email.checkValidity()) { email.reportValidity(); return; }
toast("Estimate sent to " + email.value + " — happy hosting!");
email.value = "";
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sandline — Vacation & Short-term Rentals</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="site" id="siteHeader">
<div class="wrap nav">
<a class="brand" href="#top" aria-label="Sandline home">
<span class="brand-mark" aria-hidden="true"></span>
<span class="brand-name">Sandline</span>
</a>
<nav class="nav-links" aria-label="Primary">
<a href="#getaways">Stays</a>
<a href="#destinations">Destinations</a>
<a href="#host">Become a host</a>
</nav>
<div class="nav-cta">
<button class="btn btn-ghost" type="button" data-toast="Welcome back — sign in coming soon.">Sign in</button>
<a class="btn btn-solid" href="#host">List your place</a>
</div>
</div>
</header>
<main id="top">
<!-- HERO -->
<section class="hero" aria-labelledby="heroTitle">
<div class="hero-art" aria-hidden="true">
<span class="sun"></span>
<span class="wave wave-1"></span>
<span class="wave wave-2"></span>
<span class="palm"></span>
</div>
<div class="wrap hero-inner">
<p class="eyebrow">Over 4,200 hand-picked escapes</p>
<h1 id="heroTitle">Find the place you’ll never want to leave.</h1>
<p class="hero-sub">Beach houses, mountain cabins and city lofts — booked direct, no booking fees, with hosts who actually answer.</p>
<form class="searchbar" id="searchForm" aria-label="Search stays">
<div class="field field-where">
<label for="where">Where to</label>
<input id="where" name="where" list="destList" placeholder="Anywhere" autocomplete="off" />
<datalist id="destList">
<option value="Tulum, Mexico"></option>
<option value="Amalfi Coast, Italy"></option>
<option value="Lake Tahoe, USA"></option>
<option value="Lisbon, Portugal"></option>
<option value="Bali, Indonesia"></option>
</datalist>
</div>
<div class="field field-dates">
<label for="checkin">Check in</label>
<input id="checkin" name="checkin" type="date" />
</div>
<div class="field field-dates">
<label for="checkout">Check out</label>
<input id="checkout" name="checkout" type="date" />
</div>
<div class="field field-guests">
<label id="guestsLabel">Guests</label>
<button class="guests-toggle" type="button" id="guestsToggle" aria-haspopup="true" aria-expanded="false" aria-labelledby="guestsLabel guestsValue">
<span id="guestsValue">2 guests</span>
<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<div class="guests-pop" id="guestsPop" role="dialog" aria-label="Choose guests" hidden>
<div class="stepper-row">
<div>
<span class="stepper-name">Adults</span>
<span class="stepper-meta">Ages 13+</span>
</div>
<div class="stepper">
<button type="button" class="step" data-step="adults" data-dir="-1" aria-label="Decrease adults">−</button>
<span class="step-count" data-count="adults">2</span>
<button type="button" class="step" data-step="adults" data-dir="1" aria-label="Increase adults">+</button>
</div>
</div>
<div class="stepper-row">
<div>
<span class="stepper-name">Children</span>
<span class="stepper-meta">Ages 2–12</span>
</div>
<div class="stepper">
<button type="button" class="step" data-step="children" data-dir="-1" aria-label="Decrease children">−</button>
<span class="step-count" data-count="children">0</span>
<button type="button" class="step" data-step="children" data-dir="1" aria-label="Increase children">+</button>
</div>
</div>
<div class="stepper-row">
<div>
<span class="stepper-name">Pets</span>
<span class="stepper-meta">Bringing a friend?</span>
</div>
<div class="stepper">
<button type="button" class="step" data-step="pets" data-dir="-1" aria-label="Decrease pets">−</button>
<span class="step-count" data-count="pets">0</span>
<button type="button" class="step" data-step="pets" data-dir="1" aria-label="Increase pets">+</button>
</div>
</div>
</div>
</div>
<button class="btn btn-search" type="submit" aria-label="Search stays">
<svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2.2"/><path d="M21 21l-4-4" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
<span>Search</span>
</button>
</form>
<ul class="hero-trust" aria-label="Why book with us">
<li><strong>0%</strong> guest booking fee</li>
<li><strong>24/7</strong> real host support</li>
<li><strong>4.9★</strong> average stay rating</li>
</ul>
</div>
</section>
<!-- GETAWAYS -->
<section class="section" id="getaways" aria-labelledby="getawaysTitle">
<div class="wrap">
<div class="section-head">
<div>
<p class="eyebrow">Trending stays</p>
<h2 id="getawaysTitle">Getaways guests are loving</h2>
</div>
<div class="chips" role="tablist" aria-label="Filter stays by destination">
<button class="chip is-active" role="tab" aria-selected="true" data-filter="all">All</button>
<button class="chip" role="tab" aria-selected="false" data-filter="beach">Beach</button>
<button class="chip" role="tab" aria-selected="false" data-filter="mountain">Mountain</button>
<button class="chip" role="tab" aria-selected="false" data-filter="city">City</button>
<button class="chip" role="tab" aria-selected="false" data-filter="countryside">Countryside</button>
</div>
</div>
<div class="grid" id="getawaysGrid"><!-- cards injected by script.js --></div>
<p class="empty" id="getawaysEmpty" hidden>No stays match that filter yet — try “All”.</p>
</div>
</section>
<!-- DESTINATIONS CAROUSEL -->
<section class="section section-tint" id="destinations" aria-labelledby="destTitle">
<div class="wrap">
<div class="section-head">
<div>
<p class="eyebrow">Where to next</p>
<h2 id="destTitle">Popular destinations</h2>
</div>
<div class="carousel-nav">
<button class="round" id="carPrev" type="button" aria-label="Previous destinations">
<svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="round" id="carNext" type="button" aria-label="Next destinations">
<svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
</div>
</div>
<div class="carousel" id="carousel" tabindex="0" aria-label="Destination carousel"><!-- slides injected --></div>
</section>
<!-- HOST CTA -->
<section class="section host" id="host" aria-labelledby="hostTitle">
<div class="wrap host-grid">
<div class="host-copy">
<p class="eyebrow">Earn with Sandline</p>
<h2 id="hostTitle">List your property and host your way.</h2>
<p class="lead">Hosts on Sandline earn an estimated <strong>$1,840 / month</strong> on a typical two-bedroom. Set your own calendar, your own rules, and keep more — we charge a flat host fee, no surprises.</p>
<ul class="host-points">
<li><span class="dot" aria-hidden="true"></span> Free listing & pro photos on your first home</li>
<li><span class="dot" aria-hidden="true"></span> $1M host protection on every booking</li>
<li><span class="dot" aria-hidden="true"></span> Payouts within 24 hours of check-in</li>
</ul>
<form class="host-form" id="hostForm">
<label class="sr-only" for="hostEmail">Email address</label>
<input id="hostEmail" type="email" name="hostEmail" placeholder="[email protected]" required />
<button class="btn btn-coral" type="submit">Estimate my earnings</button>
</form>
<p class="host-fine">No commitment — see your nightly estimate in seconds.</p>
</div>
<aside class="host-card" aria-label="Host earnings preview">
<div class="host-card-art" aria-hidden="true"></div>
<div class="host-card-body">
<div class="host-stat">
<span class="host-stat-num" id="estValue">$1,840</span>
<span class="host-stat-label">est. monthly earnings</span>
</div>
<label class="slider-label" for="nights">Nights booked / month <strong id="nightsOut">14</strong></label>
<input id="nights" type="range" min="2" max="28" value="14" step="1" aria-describedby="estValue" />
<div class="host-mini">
<div><span>4.96★</span><small>host rating</small></div>
<div><span>312</span><small>guests hosted</small></div>
<div><span>9 yrs</span><small>on Sandline</small></div>
</div>
</div>
</aside>
</div>
</section>
<!-- TRUST -->
<section class="section trust" aria-label="What makes Sandline different">
<div class="wrap trust-grid">
<article class="trust-item">
<span class="trust-ic" aria-hidden="true">◇</span>
<h3>Verified stays</h3>
<p>Every home is checked in person before it ever goes live. What you see is what you book.</p>
</article>
<article class="trust-item">
<span class="trust-ic" aria-hidden="true">☂</span>
<h3>Flexible cancellation</h3>
<p>Plans change. Most stays are fully refundable up to 5 days before check-in.</p>
</article>
<article class="trust-item">
<span class="trust-ic" aria-hidden="true">☎</span>
<h3>Humans, anytime</h3>
<p>Real support around the clock — and a direct line to your host the moment you book.</p>
</article>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-grid">
<div class="foot-brand">
<span class="brand-mark" aria-hidden="true"></span>
<span class="brand-name">Sandline</span>
<p>Direct-booking escapes for people who’d rather be somewhere else.</p>
</div>
<nav aria-label="Explore"><h4>Explore</h4><a href="#getaways">Stays</a><a href="#destinations">Destinations</a><a href="#host">Become a host</a></nav>
<nav aria-label="Company"><h4>Company</h4><a href="#" data-toast="About page — demo only.">About</a><a href="#" data-toast="Careers — demo only.">Careers</a><a href="#" data-toast="Press — demo only.">Press</a></nav>
<nav aria-label="Support"><h4>Support</h4><a href="#" data-toast="Help center — demo only.">Help center</a><a href="#" data-toast="Cancellation — demo only.">Cancellation</a><a href="#" data-toast="Contact — demo only.">Contact</a></nav>
</div>
<div class="wrap foot-base">
<span>© 2026 Sandline Stays — fictional demo. Not a real rental service.</span>
<span>Made for showcase purposes.</span>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Vacation / Short-term Rental Landing
A sun-drenched, lifestyle-forward landing for Sandline, a fictional direct-booking platform for vacation and short-term rentals. The sand-teal-coral palette and an airy Inter type scale set an escape-and-breeze mood from the hero down. The hero pairs a soft sunset gradient backdrop with a pill search bar — destination autocomplete, check-in / check-out dates, and a guests popover with adults, children and pets steppers that enforce sensible min/max limits and roll up into a friendly summary.
Below the fold, a getaways grid renders nine realistic stays with CSS-gradient “listing photography”, guest-favourite badges, ratings, sleeps badges, bed/bath counts and nightly prices. Destination chips filter the grid in place, each card has a save-to-wishlist heart, and an empty state covers any filter with no matches. A horizontally snapping destinations carousel scrolls via prev/next buttons that disable at each end, and a host CTA section includes a live earnings estimator driven by a nights slider plus an email capture that validates before firing a toast.
Every interaction — search, filtering, the guests stepper, wishlist hearts, carousel paging, the earnings slider and form submits — is handled in dependency-free vanilla JS, with a small toast() helper for confirmation feedback and a sticky header that gains a subtle shadow on scroll.
Illustrative UI only — sample listings and data are fictional; not a real real-estate service.