Backpacker Hostel Landing
A playful, energetic single-page landing for a backpacker hostel featuring dorm and private room options, a social events section, and an interactive reservation widget with nights calculator, bed-type selector, and form validation toast feedback.
MCP
Код
/* ── Theme: Basecamp Hostel — lime + slate + warm white ── */
:root {
--lime: #a8e63d;
--lime-d: #7ec41a;
--lime-2: #c9f56a;
--slate: #2d3748;
--slate-d: #1a202c;
--slate-2: #4a5568;
--slate-3: #718096;
--warm-white: #fafaf7;
--warm-white-2: #f0f0eb;
--ink: #1a202c;
--line: rgba(26, 32, 44, 0.1);
--line-strong: rgba(26, 32, 44, 0.2);
--success: #38a169;
--danger: #e53e3e;
--font-display: "Archivo", system-ui, sans-serif;
--font-body: "Space Grotesk", system-ui, sans-serif;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--r-pill: 999px;
--shadow-1: 0 2px 8px rgba(26, 32, 44, 0.08), 0 1px 2px rgba(26, 32, 44, 0.06);
--shadow-2: 0 12px 40px rgba(26, 32, 44, 0.18);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-body);
background: var(--warm-white);
color: var(--ink);
-webkit-font-smoothing: antialiased;
}
a {
text-decoration: none;
}
/* ── Nav ── */
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
gap: 20px;
padding: 14px 40px;
background: var(--slate-d);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
transform: translateY(-100%);
transition: transform 0.3s ease;
}
.nav.visible {
transform: translateY(0);
}
.brand {
display: flex;
align-items: center;
gap: 10px;
color: var(--warm-white);
font-family: var(--font-display);
font-weight: 900;
font-size: 1.2rem;
}
.brand-mark {
font-size: 1.4rem;
line-height: 1;
}
.nav-links {
display: flex;
gap: 24px;
margin-left: auto;
}
.nav-links a {
color: rgba(250, 250, 247, 0.8);
font-size: 0.9rem;
font-weight: 500;
transition: color 0.15s;
}
.nav-links a:hover {
color: var(--lime);
}
.nav-cta {
background: var(--lime);
color: var(--slate-d);
font-family: var(--font-display);
font-weight: 700;
font-size: 0.86rem;
padding: 9px 18px;
border-radius: var(--r-pill);
transition: background 0.15s;
}
.nav-cta:hover {
background: var(--lime-2);
}
/* ── Hero ── */
.hero {
position: relative;
min-height: 100vh;
padding: 80px 40px 60px;
display: flex;
align-items: center;
overflow: hidden;
background: var(--slate-d);
}
.hero-bg {
position: absolute;
inset: 0;
background: radial-gradient(ellipse 80% 60% at 100% 0%, rgba(168, 230, 61, 0.25), transparent 55%),
radial-gradient(ellipse 70% 70% at 0% 100%, rgba(45, 55, 72, 0.9), transparent 60%),
linear-gradient(145deg, #1a202c 0%, #2d3748 60%, #1a3020 100%);
pointer-events: none;
}
/* decorative sticker blobs */
.hero-bg::after {
content: "";
position: absolute;
width: 340px;
height: 340px;
right: 8%;
top: 15%;
border-radius: 60% 40% 55% 45% / 50% 60% 40% 50%;
background: linear-gradient(135deg, rgba(168, 230, 61, 0.18), rgba(126, 196, 26, 0.08));
pointer-events: none;
}
.hero-content {
position: relative;
z-index: 2;
width: 100%;
max-width: 1100px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 380px;
gap: 48px;
align-items: center;
}
.hero-copy {
color: var(--warm-white);
}
.tag {
display: inline-block;
background: rgba(168, 230, 61, 0.18);
border: 1px solid rgba(168, 230, 61, 0.4);
color: var(--lime);
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.05em;
padding: 5px 12px;
border-radius: var(--r-pill);
margin-bottom: 20px;
}
.hero-copy h1 {
font-family: var(--font-display);
font-weight: 900;
font-size: clamp(3rem, 7vw, 5.5rem);
line-height: 0.95;
letter-spacing: -0.03em;
color: var(--warm-white);
margin-bottom: 20px;
}
.lede {
font-size: 1.1rem;
line-height: 1.6;
color: rgba(250, 250, 247, 0.78);
max-width: 480px;
margin-bottom: 28px;
}
.lede strong {
color: var(--lime);
}
.badges {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.badge {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.18);
color: var(--warm-white);
font-size: 0.82rem;
font-weight: 600;
padding: 6px 14px;
border-radius: var(--r-pill);
}
/* ── Reservation widget ── */
.widget {
background: var(--warm-white);
border-radius: var(--r-lg);
padding: 24px;
box-shadow: var(--shadow-2);
}
.widget-title {
font-family: var(--font-display);
font-weight: 700;
font-size: 1rem;
color: var(--slate-d);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 18px;
padding-bottom: 14px;
border-bottom: 2px solid var(--lime);
display: flex;
align-items: center;
gap: 8px;
}
.widget-title::before {
content: "📅";
font-size: 1.1rem;
}
.widget-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 12px;
}
.wfield {
display: flex;
flex-direction: column;
gap: 5px;
}
.wfield-full {
grid-column: 1 / -1;
}
.wfield label {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.12em;
font-weight: 700;
color: var(--slate-2);
}
.wfield input[type="date"],
.wfield select {
font-family: var(--font-body);
font-size: 0.95rem;
font-weight: 600;
color: var(--slate-d);
background: var(--warm-white-2);
border: 1.5px solid var(--line-strong);
border-radius: var(--r-sm);
padding: 9px 10px;
outline: none;
width: 100%;
transition: border-color 0.15s;
}
.wfield input[type="date"]:focus,
.wfield select:focus {
border-color: var(--lime-d);
}
.stepper-inline {
display: flex;
align-items: center;
gap: 10px;
background: var(--warm-white-2);
border: 1.5px solid var(--line-strong);
border-radius: var(--r-sm);
padding: 6px 10px;
}
.stepper-inline span {
font-weight: 700;
font-size: 1rem;
min-width: 20px;
text-align: center;
color: var(--slate-d);
}
.step-btn {
width: 28px;
height: 28px;
border-radius: 999px;
border: 1.5px solid var(--line-strong);
background: var(--warm-white);
color: var(--slate-d);
font-size: 1.1rem;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.12s, border-color 0.12s;
}
.step-btn:hover:not(:disabled) {
background: var(--lime);
border-color: var(--lime-d);
}
.step-btn:disabled {
opacity: 0.3;
cursor: default;
}
.nights-display {
justify-content: center;
}
.nights-val {
font-family: var(--font-display);
font-weight: 900;
font-size: 1.8rem;
color: var(--lime-d);
line-height: 1;
}
.widget-total {
background: rgba(168, 230, 61, 0.12);
border: 1px solid rgba(168, 230, 61, 0.3);
border-radius: var(--r-sm);
padding: 10px 14px;
font-size: 0.9rem;
font-weight: 600;
color: var(--slate-2);
margin-bottom: 14px;
min-height: 42px;
display: flex;
align-items: center;
}
.widget-total strong {
color: var(--slate-d);
}
.widget-total.error {
background: rgba(229, 62, 62, 0.08);
border-color: rgba(229, 62, 62, 0.3);
color: var(--danger);
}
.widget-btn {
width: 100%;
background: var(--lime);
color: var(--slate-d);
border: none;
font-family: var(--font-display);
font-weight: 900;
font-size: 1rem;
padding: 14px;
border-radius: var(--r-md);
cursor: pointer;
letter-spacing: 0.02em;
transition: background 0.15s, transform 0.1s;
}
.widget-btn:hover {
background: var(--lime-2);
transform: translateY(-1px);
}
.widget-btn:active {
transform: translateY(0);
}
/* ── Section shared ── */
.section-head {
text-align: center;
margin-bottom: 40px;
}
.section-head h2 {
font-family: var(--font-display);
font-weight: 900;
font-size: clamp(2rem, 4vw, 3rem);
color: var(--slate-d);
letter-spacing: -0.02em;
margin-top: 6px;
}
.section-head--light h2 {
color: var(--warm-white);
}
.eyebrow {
font-size: 0.74rem;
text-transform: uppercase;
letter-spacing: 0.2em;
font-weight: 700;
color: var(--slate-2);
}
.eyebrow--lime {
color: var(--lime);
}
/* ── Rooms ── */
.rooms {
max-width: 1100px;
margin: 80px auto;
padding: 0 40px;
}
.room-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.rcard {
background: var(--warm-white);
border: 1.5px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--shadow-1);
position: relative;
transition: transform 0.2s, box-shadow 0.2s;
}
.rcard:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-2);
}
.rcard-img {
height: 190px;
position: relative;
}
.rcard-img--dorm4 {
background: linear-gradient(135deg, #4a6741 0%, #2d4728 60%, #1a2d18 100%);
}
.rcard-img--dorm4::after {
content: "🛏️🛏️🛏️🛏️";
position: absolute;
bottom: 14px;
left: 16px;
font-size: 1.4rem;
letter-spacing: 2px;
}
.rcard-img--dorm6 {
background: linear-gradient(135deg, #3a4f6a 0%, #2d3d55 60%, #1a2438 100%);
}
.rcard-img--dorm6::after {
content: "🛏️🛏️🛏️🛏️🛏️🛏️";
position: absolute;
bottom: 14px;
left: 16px;
font-size: 1.2rem;
letter-spacing: 1px;
}
.rcard-img--private {
background: linear-gradient(135deg, #6a4a3a 0%, #4a3028 60%, #2d1c14 100%);
}
.rcard-img--private::after {
content: "🛏️ 🌙";
position: absolute;
bottom: 14px;
left: 16px;
font-size: 1.6rem;
}
.rcard-sticker {
position: absolute;
top: 14px;
right: 14px;
background: var(--lime);
color: var(--slate-d);
font-family: var(--font-display);
font-weight: 900;
font-size: 0.72rem;
padding: 4px 10px;
border-radius: var(--r-pill);
text-transform: uppercase;
letter-spacing: 0.06em;
transform: rotate(2deg);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
.rcard-sticker--couple {
background: #ff8fab;
color: #1a1a2e;
transform: rotate(-2deg);
}
.rcard-body {
padding: 20px;
}
.rcard-body h3 {
font-family: var(--font-display);
font-weight: 900;
font-size: 1.3rem;
color: var(--slate-d);
margin-bottom: 4px;
}
.rcard-meta {
font-size: 0.78rem;
color: var(--slate-3);
font-weight: 500;
margin-bottom: 10px;
}
.rcard-desc {
font-size: 0.88rem;
color: var(--slate-2);
line-height: 1.55;
margin-bottom: 14px;
}
.rcard-price {
font-size: 0.9rem;
color: var(--slate-2);
font-weight: 600;
}
.rcard-price strong {
font-family: var(--font-display);
font-size: 1.6rem;
color: var(--lime-d);
font-weight: 900;
}
/* ── Events ── */
.events {
background: var(--slate-d);
padding: 80px 40px;
}
.events .section-head {
margin-bottom: 40px;
}
.event-list {
max-width: 860px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 0;
}
.event-item {
display: flex;
align-items: flex-start;
gap: 24px;
padding: 20px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.event-item:last-child {
border-bottom: none;
}
.event-date {
flex-shrink: 0;
width: 58px;
background: rgba(168, 230, 61, 0.12);
border: 1px solid rgba(168, 230, 61, 0.25);
border-radius: var(--r-md);
padding: 8px;
text-align: center;
color: var(--lime);
}
.event-date span {
display: block;
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.event-date strong {
display: block;
font-family: var(--font-display);
font-size: 1.8rem;
font-weight: 900;
line-height: 1;
color: var(--lime-2);
}
.event-body {
flex: 1;
}
.event-body h4 {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.08rem;
color: var(--warm-white);
margin-bottom: 4px;
}
.event-body p {
font-size: 0.86rem;
color: rgba(250, 250, 247, 0.65);
line-height: 1.5;
margin-bottom: 8px;
}
.event-tag {
display: inline-block;
background: rgba(168, 230, 61, 0.14);
border: 1px solid rgba(168, 230, 61, 0.3);
color: var(--lime);
font-size: 0.72rem;
font-weight: 700;
padding: 3px 10px;
border-radius: var(--r-pill);
letter-spacing: 0.05em;
}
/* ── Story ── */
.story {
padding: 90px 40px;
background: var(--warm-white-2);
}
.story-inner {
max-width: 1100px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 56px;
align-items: center;
}
.story-img {
height: 420px;
border-radius: var(--r-lg);
background: radial-gradient(circle at 30% 70%, rgba(168, 230, 61, 0.25), transparent 50%),
linear-gradient(145deg, #2d3748, #1a202c);
position: relative;
overflow: hidden;
box-shadow: var(--shadow-2);
}
.story-img::before {
content: "🏠";
position: absolute;
font-size: 8rem;
top: 50%;
left: 50%;
transform: translate(-50%, -58%);
opacity: 0.18;
}
.story-img::after {
content: "Est. 2019 · Alfama, Lisbon";
position: absolute;
bottom: 24px;
left: 24px;
background: rgba(168, 230, 61, 0.9);
color: var(--slate-d);
font-family: var(--font-display);
font-weight: 900;
font-size: 0.78rem;
padding: 6px 14px;
border-radius: var(--r-pill);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.story-copy .eyebrow {
margin-bottom: 10px;
}
.story-copy h2 {
font-family: var(--font-display);
font-weight: 900;
font-size: clamp(1.8rem, 3.5vw, 2.6rem);
color: var(--slate-d);
letter-spacing: -0.02em;
margin-bottom: 18px;
line-height: 1.1;
}
.story-copy p {
font-size: 0.96rem;
color: var(--slate-2);
line-height: 1.7;
margin-bottom: 14px;
}
.story-copy p strong {
color: var(--slate-d);
}
.story-stats {
display: flex;
gap: 28px;
margin-top: 28px;
padding-top: 24px;
border-top: 2px solid var(--lime);
}
.stat strong {
display: block;
font-family: var(--font-display);
font-weight: 900;
font-size: 2rem;
color: var(--lime-d);
line-height: 1;
}
.stat span {
font-size: 0.78rem;
color: var(--slate-3);
font-weight: 500;
margin-top: 3px;
display: block;
}
/* ── Find us / Location ── */
.find-us {
background: var(--slate);
padding: 80px 40px;
}
.find-inner {
max-width: 1100px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1.4fr;
gap: 48px;
align-items: center;
}
.find-info .eyebrow {
margin-bottom: 10px;
}
.find-info h2 {
font-family: var(--font-display);
font-weight: 900;
font-size: 2.2rem;
color: var(--warm-white);
letter-spacing: -0.02em;
margin-bottom: 24px;
}
.hours-list {
list-style: none;
margin-bottom: 28px;
}
.hours-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
font-size: 0.9rem;
}
.hours-list li span {
color: rgba(250, 250, 247, 0.65);
}
.hours-list li strong {
color: var(--warm-white);
font-weight: 700;
}
.address {
padding: 18px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: var(--r-md);
color: rgba(250, 250, 247, 0.75);
font-size: 0.88rem;
line-height: 1.7;
}
.address-name {
font-weight: 700;
color: var(--warm-white);
margin-bottom: 2px;
}
.map-placeholder {
height: 300px;
border-radius: var(--r-lg);
background: #263040;
position: relative;
overflow: hidden;
box-shadow: var(--shadow-2);
}
.map-grid {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(168, 230, 61, 0.07) 1px, transparent 1px),
linear-gradient(90deg, rgba(168, 230, 61, 0.07) 1px, transparent 1px);
background-size: 40px 40px;
}
.map-pin {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -60%);
font-size: 2.5rem;
filter: drop-shadow(0 4px 10px rgba(0, 0, 0, 0.5));
animation: bounce 2s infinite;
}
@keyframes bounce {
0%,
100% {
transform: translate(-50%, -60%);
}
50% {
transform: translate(-50%, -70%);
}
}
.map-label {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: var(--slate-d);
color: var(--warm-white);
font-size: 0.8rem;
font-weight: 700;
padding: 8px 16px;
border-radius: var(--r-pill);
text-align: center;
white-space: nowrap;
border: 1px solid rgba(168, 230, 61, 0.3);
}
/* ── Reserve footer ── */
.reserve-footer {
background: var(--lime);
padding: 72px 40px;
}
.reserve-inner {
max-width: 960px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 48px;
align-items: center;
}
.reserve-copy h2 {
font-family: var(--font-display);
font-weight: 900;
font-size: 2.6rem;
color: var(--slate-d);
letter-spacing: -0.03em;
margin-bottom: 12px;
}
.reserve-copy p {
font-size: 0.95rem;
color: var(--slate-2);
line-height: 1.6;
}
.reserve-form {
display: flex;
flex-direction: column;
gap: 12px;
}
.reserve-form input {
font-family: var(--font-body);
font-size: 0.96rem;
font-weight: 500;
color: var(--slate-d);
background: rgba(255, 255, 255, 0.7);
border: 1.5px solid rgba(45, 55, 72, 0.25);
border-radius: var(--r-md);
padding: 13px 16px;
outline: none;
transition: background 0.15s, border-color 0.15s;
}
.reserve-form input:focus {
background: #fff;
border-color: var(--slate-d);
}
.reserve-form button {
background: var(--slate-d);
color: var(--warm-white);
border: none;
font-family: var(--font-display);
font-weight: 900;
font-size: 1rem;
padding: 14px;
border-radius: var(--r-md);
cursor: pointer;
letter-spacing: 0.02em;
transition: background 0.15s;
}
.reserve-form button:hover {
background: var(--slate);
}
/* ── Toast ── */
.toast {
position: fixed;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
background: var(--slate-d);
color: var(--lime);
padding: 13px 26px;
border-radius: var(--r-pill);
font-family: var(--font-display);
font-size: 0.9rem;
font-weight: 700;
box-shadow: var(--shadow-2);
z-index: 200;
border: 1.5px solid rgba(168, 230, 61, 0.35);
white-space: nowrap;
}
/* ── Responsive ── */
@media (max-width: 960px) {
.hero-content {
grid-template-columns: 1fr;
}
.widget {
max-width: 480px;
}
.room-cards {
grid-template-columns: 1fr;
max-width: 480px;
margin: 0 auto;
}
.story-inner {
grid-template-columns: 1fr;
}
.story-img {
height: 260px;
}
.find-inner {
grid-template-columns: 1fr;
}
.reserve-inner {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
.nav {
padding: 12px 20px;
}
.nav-links {
display: none;
}
.hero {
padding: 60px 20px 48px;
}
.rooms,
.story,
.events,
.find-us,
.reserve-footer {
padding-left: 20px;
padding-right: 20px;
}
.widget-row {
grid-template-columns: 1fr;
}
.story-stats {
flex-direction: column;
gap: 16px;
}
.event-item {
flex-direction: column;
gap: 12px;
}
.event-date {
width: auto;
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
}
.event-date strong {
font-size: 1.2rem;
}
}// ── Toast helper ─────────────────────────────────────────────────────────────
const toast = document.getElementById("toast");
function showToast(msg) {
toast.textContent = msg;
toast.hidden = false;
clearTimeout(showToast._t);
showToast._t = setTimeout(() => (toast.hidden = true), 2200);
}
// ── Sticky nav on scroll ──────────────────────────────────────────────────────
const nav = document.getElementById("nav");
const HERO_THRESH = window.innerHeight * 0.7;
window.addEventListener(
"scroll",
() => {
nav.classList.toggle("visible", window.scrollY > HERO_THRESH);
},
{ passive: true }
);
// ── Date helpers ──────────────────────────────────────────────────────────────
function iso(d) {
return d.toISOString().slice(0, 10);
}
function addDays(date, n) {
const d = new Date(date);
d.setDate(d.getDate() + n);
return d;
}
// ── Reservation widget ────────────────────────────────────────────────────────
const cinEl = document.getElementById("cin");
const coutEl = document.getElementById("cout");
const bedEl = document.getElementById("bedtype");
const gCountEl = document.getElementById("gCount");
const gMinus = document.getElementById("gMinus");
const gPlus = document.getElementById("gPlus");
const nightsEl = document.getElementById("nightsVal");
const totalEl = document.getElementById("widgetTotal");
// Bed prices map
const BED_PRICES = {
dorm4: 18,
dorm6: 15,
dorm10: 12,
private: 62,
twin: 74,
};
// Set defaults: check-in Jun 9, check-out Jun 12
const today = new Date("2026-06-08");
const defIn = new Date("2026-06-09");
const defOut = new Date("2026-06-12");
cinEl.value = iso(defIn);
coutEl.value = iso(defOut);
cinEl.min = iso(today);
coutEl.min = iso(addDays(today, 1));
let guestCount = 1;
function getPrice(key) {
return BED_PRICES[key] ?? 18;
}
function calcNights() {
if (!cinEl.value || !coutEl.value) return 0;
const ms = new Date(coutEl.value) - new Date(cinEl.value);
return Math.round(ms / 86400000);
}
function renderWidget() {
const n = calcNights();
const price = getPrice(bedEl.value);
const total = n * price * guestCount;
if (n <= 0) {
nightsEl.textContent = "—";
totalEl.classList.add("error");
totalEl.textContent = "⚠ Check-out must be after check-in";
return;
}
totalEl.classList.remove("error");
nightsEl.textContent = n;
totalEl.innerHTML = `<strong>€${total}</strong> total · ${n} night${n === 1 ? "" : "s"} · ${guestCount} guest${guestCount === 1 ? "" : "s"} · €${price}/night`;
}
// Guest stepper
function syncGuests() {
gCountEl.textContent = guestCount;
gMinus.disabled = guestCount <= 1;
gPlus.disabled = guestCount >= 10;
}
gMinus.addEventListener("click", () => {
if (guestCount > 1) {
guestCount--;
syncGuests();
renderWidget();
}
});
gPlus.addEventListener("click", () => {
if (guestCount < 10) {
guestCount++;
syncGuests();
renderWidget();
}
});
// Date / bed change listeners
cinEl.addEventListener("change", () => {
const inD = new Date(cinEl.value);
const minOut = iso(addDays(inD, 1));
coutEl.min = minOut;
if (new Date(coutEl.value) <= inD) coutEl.value = minOut;
renderWidget();
});
coutEl.addEventListener("change", renderWidget);
bedEl.addEventListener("change", renderWidget);
// Widget form submit
document.getElementById("reserveForm").addEventListener("submit", (e) => {
e.preventDefault();
const n = calcNights();
if (n <= 0) {
renderWidget();
return;
}
const bed = bedEl.options[bedEl.selectedIndex].text.split(" —")[0];
const price = getPrice(bedEl.value);
const total = n * price * guestCount;
showToast(
`🎒 ${bed} · ${n} nights · ${guestCount} guest${guestCount === 1 ? "" : "s"} · €${total}`
);
});
syncGuests();
renderWidget();
// ── Reserve footer form ───────────────────────────────────────────────────────
document.getElementById("reserveFooterForm").addEventListener("submit", (e) => {
e.preventDefault();
const name = document.getElementById("rName").value.trim();
const email = document.getElementById("rEmail").value.trim();
if (!name) {
showToast("⚠ Please enter your name.");
return;
}
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
showToast("⚠ Please enter a valid email.");
return;
}
showToast(`✅ Reservation request sent for ${name}! Check your inbox.`);
document.getElementById("rName").value = "";
document.getElementById("rEmail").value = "";
});<!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=Archivo:wght@400;600;700;900&family=Space+Grotesk:wght@400;500;700&display=swap"
/>
<link rel="stylesheet" href="style.css" />
<title>Basecamp Hostel · Sleep Less, See More</title>
</head>
<body>
<!-- ── Sticky nav (revealed on scroll) ── -->
<header class="nav" id="nav">
<a class="brand" href="#">
<span class="brand-mark">⛺</span>
<span class="brand-name">Basecamp</span>
</a>
<nav class="nav-links">
<a href="#rooms">Rooms</a>
<a href="#events">Events</a>
<a href="#story">Story</a>
<a href="#find-us">Find us</a>
</nav>
<a class="nav-cta" href="#reserve">Book a bed</a>
</header>
<!-- ── Hero ── -->
<section class="hero" id="top">
<div class="hero-bg"></div>
<div class="hero-content">
<div class="hero-copy">
<p class="tag">🌍 Lisbon · Portugal</p>
<h1>Sleep Less,<br />See More.</h1>
<p class="lede">
Basecamp puts you in the heart of Alfama, 5 min from the tram. Dorm beds from
<strong>€18/night</strong>. Private rooms from <strong>€62/night</strong>.
</p>
<div class="badges">
<span class="badge">✅ Free lockers</span>
<span class="badge">☕ Free breakfast</span>
<span class="badge">🎉 Nightly events</span>
</div>
</div>
<!-- Inline reservation widget -->
<form class="widget" id="reserveForm" autocomplete="off">
<div class="widget-title">Quick reservation</div>
<div class="widget-row">
<div class="wfield">
<label for="cin">Check-in</label>
<input type="date" id="cin" name="cin" />
</div>
<div class="wfield">
<label for="cout">Check-out</label>
<input type="date" id="cout" name="cout" />
</div>
</div>
<div class="widget-row">
<div class="wfield wfield-full">
<label for="bedtype">Bed type</label>
<select id="bedtype" name="bedtype">
<option value="dorm4">4-bed dorm — €18/night</option>
<option value="dorm6">6-bed dorm — €15/night</option>
<option value="dorm10">10-bed dorm — €12/night</option>
<option value="private">Private room — €62/night</option>
<option value="twin">Twin private — €74/night</option>
</select>
</div>
</div>
<div class="widget-row">
<div class="wfield">
<label for="guests">Guests</label>
<div class="stepper-inline">
<button type="button" class="step-btn" id="gMinus" aria-label="Fewer guests">−</button>
<span id="gCount">1</span>
<button type="button" class="step-btn" id="gPlus" aria-label="More guests">+</button>
</div>
</div>
<div class="wfield nights-display">
<label>Nights</label>
<span class="nights-val" id="nightsVal">—</span>
</div>
</div>
<div class="widget-total" id="widgetTotal">Select dates to see total</div>
<button type="submit" class="widget-btn">Book now →</button>
</form>
</div>
</section>
<!-- ── Rooms ── -->
<section class="rooms" id="rooms">
<div class="section-head">
<p class="eyebrow">Where you'll sleep</p>
<h2>Pick your vibe</h2>
</div>
<div class="room-cards">
<article class="rcard">
<div class="rcard-img rcard-img--dorm4"></div>
<div class="rcard-sticker">Most popular</div>
<div class="rcard-body">
<h3>4-Bed Dorm</h3>
<p class="rcard-meta">Mixed · Ensuite · Locker · Reading light</p>
<p class="rcard-desc">Cozy pods with privacy curtains, power plugs at each bunk, and a shared social area just outside.</p>
<div class="rcard-price">from <strong>€18</strong> <span>/night per bed</span></div>
</div>
</article>
<article class="rcard">
<div class="rcard-img rcard-img--dorm6"></div>
<div class="rcard-body">
<h3>6-Bed Dorm</h3>
<p class="rcard-meta">Mixed · Shared bath · Locker</p>
<p class="rcard-desc">The classic backpacker experience. Meet fellow travellers, share tips, and split a €3 wine from the corner shop.</p>
<div class="rcard-price">from <strong>€15</strong> <span>/night per bed</span></div>
</div>
</article>
<article class="rcard">
<div class="rcard-img rcard-img--private"></div>
<div class="rcard-sticker rcard-sticker--couple">Couples ♥</div>
<div class="rcard-body">
<h3>Private Room</h3>
<p class="rcard-meta">Queen bed · Ensuite · AC · Safe</p>
<p class="rcard-desc">All the hostel energy, none of the snoring. Your own space with a proper bed, fresh towels, and Netflix.</p>
<div class="rcard-price">from <strong>€62</strong> <span>/night</span></div>
</div>
</article>
</div>
</section>
<!-- ── Events ── -->
<section class="events" id="events">
<div class="section-head section-head--light">
<p class="eyebrow eyebrow--lime">What's on</p>
<h2>This week's events</h2>
</div>
<div class="event-list">
<article class="event-item">
<div class="event-date"><span>TUE</span><strong>9</strong><span>Jun</span></div>
<div class="event-body">
<h4>Fado Night + Free Wine 🍷</h4>
<p>Authentic live Fado performance in the common room. Two free glasses included for hostel guests.</p>
<span class="event-tag">Free for guests</span>
</div>
</article>
<article class="event-item">
<div class="event-date"><span>WED</span><strong>10</strong><span>Jun</span></div>
<div class="event-body">
<h4>Street Art Walking Tour 🎨</h4>
<p>Explore Lisbon's legendary murals with local artist Carla. Meet in reception at 10 am.</p>
<span class="event-tag">€8/person</span>
</div>
</article>
<article class="event-item">
<div class="event-date"><span>THU</span><strong>11</strong><span>Jun</span></div>
<div class="event-body">
<h4>Rooftop Movie Night 🎬</h4>
<p>Classic road trip film under the Alfama stars. BYO snacks or grab from the hostel kitchen.</p>
<span class="event-tag">Free for guests</span>
</div>
</article>
<article class="event-item">
<div class="event-date"><span>FRI</span><strong>12</strong><span>Jun</span></div>
<div class="event-body">
<h4>Pub Crawl — Bairro Alto 🍻</h4>
<p>5 bars, free shots at each stop, new best friends guaranteed. Starts at reception, 9 pm.</p>
<span class="event-tag">€15/person</span>
</div>
</article>
</div>
</section>
<!-- ── Story ── -->
<section class="story" id="story">
<div class="story-inner">
<div class="story-img"></div>
<div class="story-copy">
<p class="eyebrow">Our story</p>
<h2>Born from a backpack & a bold idea</h2>
<p>
Basecamp started in 2019 when three friends returned from a year of couch-surfing across Asia
and realised hostels didn't have to be dingy. We gutted a 1920s Alfama townhouse, kept the
original tiles, added solar panels, and built a place we'd actually want to stay.
</p>
<p>
Five years and 40,000 guests later, Basecamp is rated the <strong>#1 hostel in Lisbon</strong> on
Hostelworld. We're proud of it. Come stay.
</p>
<div class="story-stats">
<div class="stat"><strong>40k+</strong><span>guests hosted</span></div>
<div class="stat"><strong>9.4</strong><span>Hostelworld rating</span></div>
<div class="stat"><strong>52</strong><span>beds available</span></div>
</div>
</div>
</div>
</section>
<!-- ── Hours + Location ── -->
<section class="find-us" id="find-us">
<div class="find-inner">
<div class="find-info">
<p class="eyebrow eyebrow--lime">Get here</p>
<h2>Hours & location</h2>
<ul class="hours-list">
<li><span>Check-in</span><strong>14:00 – 00:00</strong></li>
<li><span>Check-out</span><strong>until 11:00</strong></li>
<li><span>Reception</span><strong>24 hours</strong></li>
<li><span>Breakfast</span><strong>07:30 – 10:00</strong></li>
</ul>
<div class="address">
<p class="address-name">Basecamp Hostel Lisbon</p>
<p>Rua de Santa Cruz do Castelo 12</p>
<p>1100-129 Alfama, Lisbon</p>
<p>+351 21 000 4200</p>
</div>
</div>
<div class="map-placeholder">
<div class="map-grid"></div>
<div class="map-pin">📍</div>
<div class="map-label">Basecamp Hostel<br />Alfama, Lisbon</div>
</div>
</div>
</section>
<!-- ── Reserve footer ── -->
<section class="reserve-footer" id="reserve">
<div class="reserve-inner">
<div class="reserve-copy">
<h2>Ready to go?</h2>
<p>No booking fees. Free cancellation up to 48 hours before arrival. Pay on arrival or online — your choice.</p>
</div>
<form class="reserve-form" id="reserveFooterForm">
<input type="text" placeholder="Your name" id="rName" autocomplete="name" />
<input type="email" placeholder="Email address" id="rEmail" autocomplete="email" />
<button type="submit">Reserve my bed 🎒</button>
</form>
</div>
</section>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Backpacker Hostel Landing
A bold, sticker-ish single-page landing for Basecamp Hostel — a social backpacker hub with dorm beds and private rooms. The page scrolls through a lime-and-slate hero with an inline reservation widget, a three-card rooms section with CSS-gradient imagery, a social events board, an about/story block, and a location + hours strip. The reservation widget calculates nights live, enforces check-in before check-out, lets the user pick bed type and headcount, and fires a toast on submit. A sticky nav appears once the user scrolls past the hero. All layout collapses responsively at 960 px and 560 px.