Interior — Landing Residential
A warm editorial landing page for a residential interior design studio, pairing a magazine-style hero with a room-by-room explorer, layered project cards, a process timeline and a consultation booking form. Filterable room tabs swap curated imagery and captions, testimonials rotate, and a live estimate strip responds to home size and scope so prospective clients feel guided from first glance to booking.
MCP
Code
:root {
--bg: #f6f2ec;
--paper: #fbf9f5;
--ink: #2c2620;
--ink-2: #5a5147;
--muted: #8a8175;
--clay: #b08968;
--clay-d: #8c6a4f;
--walnut: #5c4433;
--sage: #9caf88;
--line: rgba(44, 38, 32, 0.12);
--white: #fff;
--r-sm: 4px;
--r-md: 8px;
--r-lg: 14px;
--shadow: 0 18px 44px -24px rgba(44, 38, 32, 0.4);
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
line-height: 1.5;
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-osx-font-smoothing: grayscale;
}
img { max-width: 100%; display: block; }
.wrap { width: min(1120px, 92vw); margin-inline: auto; }
.visually-hidden {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;
clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.eyebrow {
font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
color: var(--clay-d); font-weight: 600; margin: 0 0 0.85rem;
}
h1, h2, h3 { font-family: var(--serif); font-weight: 500; color: var(--ink); letter-spacing: 0.005em; }
/* ---------- Buttons ---------- */
.btn {
--pad: 0.72rem 1.35rem;
display: inline-flex; align-items: center; justify-content: center;
padding: var(--pad); border-radius: 999px; font-family: var(--sans);
font-weight: 600; font-size: 0.92rem; text-decoration: none;
border: 1px solid transparent; cursor: pointer; transition: transform .18s ease, background .2s ease, box-shadow .2s ease, color .2s ease;
}
.btn:focus-visible { outline: 2px solid var(--walnut); outline-offset: 3px; }
.btn-primary { background: var(--clay); color: var(--white); box-shadow: 0 10px 22px -12px rgba(140, 106, 79, 0.9); }
.btn-primary:hover { background: var(--clay-d); transform: translateY(-2px); }
.btn-primary:active { transform: translateY(0); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { border-color: var(--clay); color: var(--clay-d); transform: translateY(-2px); }
.btn-block { width: 100%; margin-top: 0.4rem; }
.link-arrow {
display: inline-flex; align-items: center; gap: 0.4rem;
color: var(--clay-d); font-weight: 600; font-size: 0.9rem; text-decoration: none;
border-bottom: 1px solid transparent; transition: gap .2s ease, border-color .2s ease;
}
.link-arrow:hover { gap: 0.7rem; border-color: var(--clay); }
.link-arrow:focus-visible { outline: 2px solid var(--walnut); outline-offset: 3px; }
/* ---------- Header ---------- */
.site-header {
position: sticky; top: 0; z-index: 40;
background: rgba(246, 242, 236, 0.82);
backdrop-filter: saturate(140%) blur(10px);
border-bottom: 1px solid var(--line);
}
.header-inner { display: flex; align-items: center; gap: 1.5rem; height: 68px; }
.brand { display: inline-flex; align-items: center; gap: 0.6rem; text-decoration: none; color: var(--ink); }
.brand-mark {
display: inline-grid; place-items: center; width: 34px; height: 34px;
border-radius: 50%; background: var(--walnut); color: var(--paper);
font-family: var(--serif); font-size: 0.92rem; font-weight: 600; letter-spacing: 0.02em;
}
.brand-name { font-family: var(--serif); font-size: 1.28rem; font-weight: 600; }
.nav { margin-left: auto; display: flex; gap: 1.6rem; }
.nav a { color: var(--ink-2); text-decoration: none; font-size: 0.92rem; font-weight: 500; position: relative; padding: 0.2rem 0; }
.nav a::after { content: ""; position: absolute; left: 0; bottom: -2px; width: 0; height: 1.5px; background: var(--clay); transition: width .22s ease; }
.nav a:hover { color: var(--ink); }
.nav a:hover::after { width: 100%; }
.nav-cta { margin-left: 0.4rem; }
/* ---------- Hero ---------- */
.hero { padding: clamp(3rem, 7vw, 6rem) 0 clamp(2.5rem, 5vw, 4rem); }
.hero-grid { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
.hero h1 { font-size: clamp(2.6rem, 6vw, 4.4rem); line-height: 1.02; margin: 0 0 1.1rem; }
.hero h1 em { font-style: italic; color: var(--clay-d); }
.lede { font-size: 1.08rem; color: var(--ink-2); max-width: 44ch; margin: 0 0 1.6rem; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 2rem; }
.hero-stats { display: flex; gap: 2rem; margin: 0; padding-top: 1.4rem; border-top: 1px solid var(--line); }
.hero-stats div { margin: 0; }
.hero-stats dt { font-family: var(--serif); font-size: 1.7rem; color: var(--walnut); }
.hero-stats dd { margin: 0; font-size: 0.8rem; color: var(--muted); letter-spacing: 0.02em; }
.hero-art { position: relative; margin: 0; min-height: 420px; }
.hero-photo { position: absolute; border-radius: var(--r-lg); box-shadow: var(--shadow); }
.hero-photo-a {
inset: 0 24% 22% 0;
background:
linear-gradient(150deg, rgba(44,38,32,0.05), rgba(44,38,32,0.28)),
url("https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?auto=format&fit=crop&w=900&q=70") center/cover;
}
.hero-photo-b {
inset: 40% 0 0 40%;
background:
linear-gradient(150deg, rgba(176,137,104,0.12), rgba(92,68,51,0.35)),
url("https://images.unsplash.com/photo-1493809842364-78817add7ffb?auto=format&fit=crop&w=700&q=70") center/cover;
border: 5px solid var(--paper);
}
.hero-tag {
position: absolute; left: 6%; bottom: 8%; z-index: 3;
background: var(--paper); color: var(--ink); font-size: 0.75rem; font-weight: 600;
padding: 0.4rem 0.8rem; border-radius: 999px; box-shadow: var(--shadow); letter-spacing: 0.02em;
}
/* ---------- Section heads ---------- */
.section-head { max-width: 640px; margin-bottom: 2.4rem; }
.section-head h2 { font-size: clamp(1.9rem, 4vw, 2.9rem); margin: 0 0 0.6rem; line-height: 1.08; }
.section-sub { color: var(--ink-2); margin: 0; font-size: 1.02rem; }
.rooms, .work, .process, .voices, .booking { padding: clamp(3rem, 6vw, 5rem) 0; }
.work { background: var(--paper); border-block: 1px solid var(--line); }
/* ---------- Room explorer ---------- */
.room-tabs { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.6rem; border-bottom: 1px solid var(--line); padding-bottom: 1.2rem; }
.room-tab {
font-family: var(--sans); font-size: 0.92rem; font-weight: 600; color: var(--ink-2);
background: transparent; border: 1px solid var(--line); border-radius: 999px;
padding: 0.5rem 1.15rem; cursor: pointer; transition: all .2s ease;
}
.room-tab:hover { border-color: var(--clay); color: var(--clay-d); }
.room-tab.is-active { background: var(--walnut); border-color: var(--walnut); color: var(--paper); }
.room-tab:focus-visible { outline: 2px solid var(--walnut); outline-offset: 3px; }
.room-panel { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
.room-panel:focus-visible { outline: none; }
.room-visual { position: relative; }
.room-photo {
aspect-ratio: 4 / 3; border-radius: var(--r-lg); box-shadow: var(--shadow);
background-size: cover; background-position: center;
transition: opacity .35s ease, transform .5s ease;
}
.room-chip {
position: absolute; top: 1rem; left: 1rem;
background: var(--paper); color: var(--ink); font-size: 0.75rem; font-weight: 600;
padding: 0.35rem 0.75rem; border-radius: 999px; box-shadow: var(--shadow);
}
.room-detail h3 { font-size: clamp(1.7rem, 3.4vw, 2.3rem); margin: 0 0 0.7rem; }
.room-detail p { color: var(--ink-2); margin: 0 0 1.2rem; max-width: 40ch; }
.room-materials { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0; margin: 0 0 1.4rem; }
.room-materials li {
font-size: 0.78rem; font-weight: 600; color: var(--walnut);
background: rgba(156, 175, 136, 0.18); border: 1px solid rgba(156, 175, 136, 0.4);
padding: 0.3rem 0.7rem; border-radius: var(--r-sm);
}
.fade-swap { opacity: 0; transform: translateY(6px); }
/* ---------- Projects ---------- */
.work-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.4rem; }
.project-card {
background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg);
overflow: hidden; box-shadow: 0 10px 30px -22px rgba(44,38,32,0.5);
transition: transform .25s ease, box-shadow .25s ease; cursor: pointer;
}
.project-card:hover, .project-card:focus-visible {
transform: translateY(-6px); box-shadow: var(--shadow); outline: none;
}
.project-card:focus-visible { outline: 2px solid var(--walnut); outline-offset: 3px; }
.project-photo { aspect-ratio: 16 / 10; background-size: cover; background-position: center; transition: transform .5s ease; }
.project-card:hover .project-photo { transform: scale(1.05); }
.photo-1 { background-image: linear-gradient(120deg, rgba(44,38,32,0.04), rgba(44,38,32,0.18)), url("https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?auto=format&fit=crop&w=800&q=70"); }
.photo-2 { background-image: linear-gradient(120deg, rgba(44,38,32,0.04), rgba(44,38,32,0.18)), url("https://images.unsplash.com/photo-1556911220-bff31c812dba?auto=format&fit=crop&w=800&q=70"); }
.photo-3 { background-image: linear-gradient(120deg, rgba(44,38,32,0.04), rgba(44,38,32,0.18)), url("https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=800&q=70"); }
.photo-4 { background-image: linear-gradient(120deg, rgba(44,38,32,0.04), rgba(44,38,32,0.18)), url("https://images.unsplash.com/photo-1567016432779-094069958ea5?auto=format&fit=crop&w=800&q=70"); }
.project-body { padding: 1.3rem 1.4rem 1.5rem; }
.project-tags { display: flex; gap: 0.5rem; margin-bottom: 0.75rem; }
.tag {
font-size: 0.72rem; font-weight: 600; letter-spacing: 0.02em;
background: var(--walnut); color: var(--paper); padding: 0.25rem 0.65rem; border-radius: 999px;
}
.tag-soft { background: transparent; color: var(--ink-2); border: 1px solid var(--line); }
.project-body h3 { font-size: 1.5rem; margin: 0 0 0.35rem; }
.project-body p { margin: 0; color: var(--ink-2); font-size: 0.94rem; }
/* ---------- Process timeline ---------- */
.timeline { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.4rem; counter-reset: step; }
.timeline li { position: relative; padding-top: 1.4rem; border-top: 2px solid var(--line); }
.timeline li::before { content: ""; position: absolute; top: -6px; left: 0; width: 10px; height: 10px; border-radius: 50%; background: var(--clay); }
.step-no { font-family: var(--serif); font-size: 1.1rem; color: var(--clay-d); font-weight: 600; }
.timeline h3 { font-size: 1.4rem; margin: 0.2rem 0 0.4rem; }
.timeline p { margin: 0; color: var(--ink-2); font-size: 0.92rem; }
/* ---------- Testimonials ---------- */
.voices { background: var(--walnut); color: var(--paper); text-align: center; }
.quote-carousel { position: relative; min-height: 150px; max-width: 720px; margin-inline: auto; }
.quote { position: absolute; inset: 0; margin: 0; opacity: 0; transform: translateY(10px); transition: opacity .5s ease, transform .5s ease; pointer-events: none; }
.quote.is-active { opacity: 1; transform: none; pointer-events: auto; position: relative; }
.quote p { font-family: var(--serif); font-size: clamp(1.5rem, 3.4vw, 2.2rem); line-height: 1.3; margin: 0 0 1.1rem; color: var(--paper); }
.quote footer { color: rgba(251, 249, 245, 0.72); font-size: 0.92rem; }
.quote footer strong { color: var(--sage); font-weight: 600; }
.quote-dots { display: flex; gap: 0.6rem; justify-content: center; margin-top: 1.8rem; }
.dot { width: 10px; height: 10px; border-radius: 50%; border: 0; background: rgba(251,249,245,0.3); cursor: pointer; transition: background .2s ease, transform .2s ease; padding: 0; }
.dot:hover { transform: scale(1.2); }
.dot.is-active { background: var(--sage); }
.dot:focus-visible { outline: 2px solid var(--sage); outline-offset: 3px; }
/* ---------- Booking ---------- */
.booking-grid { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
.booking-copy { position: sticky; top: 92px; }
.estimate {
margin-top: 1.8rem; background: var(--paper); border: 1px solid var(--line);
border-radius: var(--r-lg); padding: 1.3rem 1.5rem; box-shadow: 0 12px 30px -24px rgba(44,38,32,0.5);
}
.estimate-label { margin: 0 0 0.3rem; font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.estimate-value { margin: 0; font-family: var(--serif); font-size: 2.6rem; color: var(--walnut); line-height: 1; transition: color .2s ease; }
.estimate-note { margin: 0.4rem 0 0; color: var(--ink-2); font-size: 0.88rem; }
.booking-form { background: var(--white); border: 1px solid var(--line); border-radius: var(--r-lg); padding: clamp(1.5rem, 3vw, 2.2rem); box-shadow: var(--shadow); }
.field { margin-bottom: 1.1rem; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.field label { display: block; font-size: 0.82rem; font-weight: 600; color: var(--ink); margin-bottom: 0.4rem; }
.field input, .field select, .field textarea {
width: 100%; font-family: var(--sans); font-size: 0.95rem; color: var(--ink);
background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-md);
padding: 0.7rem 0.85rem; transition: border-color .2s ease, box-shadow .2s ease;
}
.field textarea { resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
outline: none; border-color: var(--clay); box-shadow: 0 0 0 3px rgba(176, 137, 104, 0.18);
}
.field.invalid input { border-color: #b3543f; box-shadow: 0 0 0 3px rgba(179, 84, 63, 0.14); }
.err { display: block; min-height: 1rem; margin-top: 0.3rem; font-size: 0.78rem; color: #b3543f; }
.form-fine { text-align: center; color: var(--muted); font-size: 0.8rem; margin: 0.9rem 0 0; }
/* ---------- Footer ---------- */
.site-footer { background: var(--ink); color: var(--paper); padding: 2.8rem 0; }
.footer-inner { display: flex; justify-content: space-between; align-items: center; gap: 1.5rem; flex-wrap: wrap; }
.footer-inner p { margin: 0.6rem 0 0; color: rgba(251,249,245,0.72); font-size: 0.9rem; max-width: 44ch; }
.footer-inner .brand-mark { background: var(--clay); }
.footer-fine { font-size: 0.8rem; color: rgba(251,249,245,0.5) !important; }
/* ---------- Toast ---------- */
.toast {
position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 120%);
background: var(--walnut); color: var(--paper); padding: 0.85rem 1.4rem;
border-radius: 999px; box-shadow: var(--shadow); font-size: 0.92rem; font-weight: 500;
z-index: 90; transition: transform .35s cubic-bezier(.2,.8,.3,1); max-width: 90vw;
}
.toast.show { transform: translate(-50%, 0); }
/* ---------- Reveal on scroll ---------- */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .6s ease, transform .6s ease; }
.reveal.in { opacity: 1; transform: none; }
/* ---------- Responsive ---------- */
@media (max-width: 860px) {
.hero-grid, .room-panel, .booking-grid { grid-template-columns: 1fr; }
.hero-art { min-height: 340px; margin-top: 1rem; }
.work-grid { grid-template-columns: 1fr; }
.timeline { grid-template-columns: repeat(2, 1fr); }
.booking-copy { position: static; }
}
@media (max-width: 520px) {
.nav { display: none; }
.nav-cta { margin-left: auto; }
.hero-stats { gap: 1.2rem; }
.hero-stats dt { font-size: 1.4rem; }
.timeline { grid-template-columns: 1fr; }
.field-row { grid-template-columns: 1fr; }
.footer-inner { flex-direction: column; align-items: flex-start; }
.estimate-value { font-size: 2.2rem; }
.hero-art { min-height: 300px; }
}
@media (prefers-reduced-motion: reduce) {
* { scroll-behavior: auto !important; }
.reveal, .room-photo, .quote, .project-photo, .toast { transition: none !important; }
.reveal { opacity: 1; transform: none; }
}(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");
}, 3200);
}
/* ---------- Room explorer ---------- */
var rooms = {
living: {
title: "The living room",
chip: "Living",
text: "A grounded, sociable space — deep seating around a low travertine table, layered wool underfoot, and warm dimmable light for the long evenings.",
materials: ["Bouclé & wool", "Travertine", "Smoked oak"],
img: "https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?auto=format&fit=crop&w=900&q=70"
},
kitchen: {
title: "The kitchen",
chip: "Kitchen",
text: "The working heart of the home — olive cabinetry, honed stone counters and unlacquered brass that softens beautifully with everyday use.",
materials: ["Olive oak", "Honed marble", "Unlacquered brass"],
img: "https://images.unsplash.com/photo-1556909212-d5b604d0c90d?auto=format&fit=crop&w=900&q=70"
},
bedroom: {
title: "The bedroom",
chip: "Bedroom",
text: "A quiet retreat built around rest — lime-plaster walls, linen bedding and low, warm lighting for slow mornings and softer evenings.",
materials: ["Lime plaster", "Washed linen", "Ash timber"],
img: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=900&q=70"
},
bath: {
title: "The bathroom",
chip: "Bath",
text: "A spa-like calm in the everyday — tadelakt surfaces, aged brass fittings and a deep soaking tub set by a window of soft morning light.",
materials: ["Tadelakt", "Aged brass", "Zellige tile"],
img: "https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&w=900&q=70"
},
studio: {
title: "The home studio",
chip: "Studio",
text: "A focused, flexible room for making and thinking — a generous oak desk, open shelving in warm tones and daylight that lasts into the afternoon.",
materials: ["White oak", "Cork board", "Cotton canvas"],
img: "https://images.unsplash.com/photo-1524758631624-e2822e304c36?auto=format&fit=crop&w=900&q=70"
}
};
var tabs = Array.prototype.slice.call(document.querySelectorAll(".room-tab"));
var roomPhoto = document.getElementById("roomPhoto");
var roomChip = document.getElementById("roomChip");
var roomTitle = document.getElementById("roomTitle");
var roomText = document.getElementById("roomText");
var roomMaterials = document.getElementById("roomMaterials");
var panel = document.getElementById("panel-room");
function setRoom(key) {
var data = rooms[key];
if (!data) return;
// fade out
roomPhoto.classList.add("fade-swap");
setTimeout(function () {
roomPhoto.style.backgroundImage =
"linear-gradient(150deg, rgba(44,38,32,0.04), rgba(44,38,32,0.22)), url('" + data.img + "')";
roomChip.textContent = data.chip;
roomTitle.textContent = data.title;
roomText.textContent = data.text;
roomMaterials.innerHTML = "";
data.materials.forEach(function (m) {
var li = document.createElement("li");
li.textContent = m;
roomMaterials.appendChild(li);
});
roomPhoto.classList.remove("fade-swap");
}, 220);
if (panel) panel.setAttribute("aria-labelledby", "tab-" + key);
}
function activateTab(tab, focus) {
tabs.forEach(function (t) {
var active = t === tab;
t.classList.toggle("is-active", active);
t.setAttribute("aria-selected", active ? "true" : "false");
t.tabIndex = active ? 0 : -1;
});
setRoom(tab.getAttribute("data-room"));
if (focus) tab.focus();
}
tabs.forEach(function (tab, i) {
tab.addEventListener("click", function () { activateTab(tab, false); });
tab.addEventListener("keydown", function (e) {
var idx = null;
if (e.key === "ArrowRight" || e.key === "ArrowDown") idx = (i + 1) % tabs.length;
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") idx = (i - 1 + tabs.length) % tabs.length;
else if (e.key === "Home") idx = 0;
else if (e.key === "End") idx = tabs.length - 1;
if (idx !== null) { e.preventDefault(); activateTab(tabs[idx], true); }
});
});
// preload first room image styling
setRoom("living");
/* ---------- Testimonials carousel ---------- */
var quotes = Array.prototype.slice.call(document.querySelectorAll(".quote"));
var dots = Array.prototype.slice.call(document.querySelectorAll(".dot"));
var qi = 0;
var qTimer;
function showQuote(n) {
qi = (n + quotes.length) % quotes.length;
quotes.forEach(function (q, i) { q.classList.toggle("is-active", i === qi); });
dots.forEach(function (d, i) { d.classList.toggle("is-active", i === qi); });
}
function startQuotes() {
clearInterval(qTimer);
qTimer = setInterval(function () { showQuote(qi + 1); }, 5000);
}
dots.forEach(function (d) {
d.addEventListener("click", function () {
showQuote(parseInt(d.getAttribute("data-i"), 10));
startQuotes();
});
});
if (quotes.length) startQuotes();
/* ---------- Live estimate ---------- */
var sizeSel = document.getElementById("size");
var scopeSel = document.getElementById("scope");
var estValue = document.getElementById("estimateValue");
var estNote = document.getElementById("estimateNote");
var sizeFactor = { small: 0.7, medium: 1, large: 1.55 };
var scopeBase = { room: 3200, multi: 6400, full: 8400 };
var scopeLabel = { room: "Single room", multi: "Multiple rooms", full: "Full-home concept" };
var sizeLabel = { small: "small home", medium: "medium home", large: "large home" };
function fmt(n) { return "$" + Math.round(n / 100) * 100 + "".replace(/\B(?=(\d{3})+(?!\d))/g, ","); }
function money(n) {
n = Math.round(n / 100) * 100;
return "$" + n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function updateEstimate() {
if (!estValue) return;
var scope = scopeSel.value, size = sizeSel.value;
var total = scopeBase[scope] * sizeFactor[size];
estValue.textContent = money(total);
estNote.textContent = scopeLabel[scope] + " · " + sizeLabel[size];
estValue.style.color = "var(--clay-d)";
setTimeout(function () { estValue.style.color = "var(--walnut)"; }, 260);
}
if (sizeSel) sizeSel.addEventListener("change", updateEstimate);
if (scopeSel) scopeSel.addEventListener("change", updateEstimate);
updateEstimate();
/* ---------- Booking form validation ---------- */
var form = document.getElementById("bookForm");
function setError(field, msg) {
var wrap = field.closest(".field");
var err = form.querySelector('.err[data-for="' + field.id + '"]');
if (wrap) wrap.classList.toggle("invalid", !!msg);
if (err) err.textContent = msg || "";
return !msg;
}
if (form) {
var nameF = document.getElementById("name");
var emailF = document.getElementById("email");
[nameF, emailF].forEach(function (f) {
f.addEventListener("input", function () {
if (f.closest(".field").classList.contains("invalid")) validate(f);
});
});
function validate(f) {
if (f === nameF) {
return setError(nameF, nameF.value.trim().length >= 2 ? "" : "Please enter your name.");
}
if (f === emailF) {
var ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailF.value.trim());
return setError(emailF, ok ? "" : "Enter a valid email address.");
}
return true;
}
form.addEventListener("submit", function (e) {
e.preventDefault();
var okName = validate(nameF);
var okEmail = validate(emailF);
if (!okName || !okEmail) {
(okName ? emailF : nameF).focus();
toast("Please check the highlighted fields.");
return;
}
var first = nameF.value.trim().split(" ")[0];
form.reset();
updateEstimate();
toast("Thank you, " + first + " — we’ll be in touch within 2 days.");
});
}
/* ---------- Reveal on scroll ---------- */
var revealEls = Array.prototype.slice.call(document.querySelectorAll(".reveal"));
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) { en.target.classList.add("in"); io.unobserve(en.target); }
});
}, { threshold: 0.14, rootMargin: "0px 0px -40px 0px" });
revealEls.forEach(function (el) { io.observe(el); });
} else {
revealEls.forEach(function (el) { el.classList.add("in"); });
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Marrow & Oak — Residential Interior Design</title>
<meta name="description" content="Marrow & Oak is a residential interior design studio crafting warm, liveable homes room by room." />
<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=Cormorant+Garamond:wght@400;500;600&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Header -->
<header class="site-header" id="top">
<div class="wrap header-inner">
<a class="brand" href="#top" aria-label="Marrow and Oak home">
<span class="brand-mark" aria-hidden="true">M&O</span>
<span class="brand-name">Marrow & Oak</span>
</a>
<nav class="nav" aria-label="Primary">
<a href="#rooms">Rooms</a>
<a href="#work">Projects</a>
<a href="#process">Process</a>
<a href="#voices">Clients</a>
</nav>
<a href="#book" class="btn btn-primary nav-cta">Book a consult</a>
</div>
</header>
<main>
<!-- Hero -->
<section class="hero" aria-labelledby="hero-title">
<div class="wrap hero-grid">
<div class="hero-copy reveal">
<p class="eyebrow">Residential interior design · Est. 2014</p>
<h1 id="hero-title">Homes that feel<br /><em>made for the living.</em></h1>
<p class="lede">We design warm, unhurried interiors — room by room — around how your family actually lives. Natural materials, honest light, and details that age beautifully.</p>
<div class="hero-actions">
<a href="#book" class="btn btn-primary">Book a consultation</a>
<a href="#work" class="btn btn-ghost">See recent homes</a>
</div>
<dl class="hero-stats">
<div><dt>180+</dt><dd>homes furnished</dd></div>
<div><dt>11 yrs</dt><dd>studio practice</dd></div>
<div><dt>4.9★</dt><dd>client rating</dd></div>
</dl>
</div>
<figure class="hero-art reveal" aria-hidden="true">
<div class="hero-photo hero-photo-a"></div>
<div class="hero-photo hero-photo-b"></div>
<span class="hero-tag">Willow House · Reforma</span>
</figure>
</div>
</section>
<!-- Room explorer -->
<section class="rooms" id="rooms" aria-labelledby="rooms-title">
<div class="wrap">
<header class="section-head reveal">
<p class="eyebrow">Room by room</p>
<h2 id="rooms-title">Every space, considered on its own terms</h2>
<p class="section-sub">Select a room to see how we approach light, layout and materials.</p>
</header>
<div class="room-explorer reveal">
<div class="room-tabs" role="tablist" aria-label="Rooms">
<button class="room-tab is-active" role="tab" aria-selected="true" id="tab-living" aria-controls="panel-room" data-room="living">Living</button>
<button class="room-tab" role="tab" aria-selected="false" id="tab-kitchen" aria-controls="panel-room" data-room="kitchen" tabindex="-1">Kitchen</button>
<button class="room-tab" role="tab" aria-selected="false" id="tab-bedroom" aria-controls="panel-room" data-room="bedroom" tabindex="-1">Bedroom</button>
<button class="room-tab" role="tab" aria-selected="false" id="tab-bath" aria-controls="panel-room" data-room="bath" tabindex="-1">Bath</button>
<button class="room-tab" role="tab" aria-selected="false" id="tab-studio" aria-controls="panel-room" data-room="studio" tabindex="-1">Studio</button>
</div>
<div class="room-panel" id="panel-room" role="tabpanel" aria-labelledby="tab-living" tabindex="0">
<div class="room-visual">
<div class="room-photo" id="roomPhoto"></div>
<span class="room-chip" id="roomChip">Living</span>
</div>
<div class="room-detail">
<h3 id="roomTitle">The living room</h3>
<p id="roomText">A grounded, sociable space — deep seating around a low travertine table, layered wool underfoot, and warm dimmable light for the long evenings.</p>
<ul class="room-materials" id="roomMaterials" aria-label="Key materials">
<li>Bouclé & wool</li>
<li>Travertine</li>
<li>Smoked oak</li>
</ul>
<a href="#book" class="link-arrow">Design a room like this <span aria-hidden="true">→</span></a>
</div>
</div>
</div>
</div>
</section>
<!-- Projects -->
<section class="work" id="work" aria-labelledby="work-title">
<div class="wrap">
<header class="section-head reveal">
<p class="eyebrow">Selected homes</p>
<h2 id="work-title">Recent residential projects</h2>
<p class="section-sub">A few of the homes we have shaped this year.</p>
</header>
<div class="work-grid">
<article class="project-card reveal" tabindex="0">
<div class="project-photo photo-1"></div>
<div class="project-body">
<div class="project-tags"><span class="tag">Full home</span><span class="tag tag-soft">140 m²</span></div>
<h3>Willow House</h3>
<p>A young family’s first home reworked into soft, open living with a reading nook by the light.</p>
</div>
</article>
<article class="project-card reveal" tabindex="0">
<div class="project-photo photo-2"></div>
<div class="project-body">
<div class="project-tags"><span class="tag">Kitchen</span><span class="tag tag-soft">Renovation</span></div>
<h3>Fig & Slate</h3>
<p>A galley kitchen opened to the garden, clad in olive cabinetry and unlacquered brass.</p>
</div>
</article>
<article class="project-card reveal" tabindex="0">
<div class="project-photo photo-3"></div>
<div class="project-body">
<div class="project-tags"><span class="tag">Bedroom suite</span><span class="tag tag-soft">2 rooms</span></div>
<h3>The Quiet Wing</h3>
<p>A calm primary suite in plaster and linen, built around morning light and deep rest.</p>
</div>
</article>
<article class="project-card reveal" tabindex="0">
<div class="project-photo photo-4"></div>
<div class="project-body">
<div class="project-tags"><span class="tag">Apartment</span><span class="tag tag-soft">86 m²</span></div>
<h3>Cedar & Co.</h3>
<p>A compact city flat given warmth and storage without losing a square metre of ease.</p>
</div>
</article>
</div>
</div>
</section>
<!-- Process -->
<section class="process" id="process" aria-labelledby="process-title">
<div class="wrap">
<header class="section-head reveal">
<p class="eyebrow">How we work</p>
<h2 id="process-title">A calm, four-step process</h2>
</header>
<ol class="timeline">
<li class="reveal"><span class="step-no">01</span><h3>Consult</h3><p>We visit, listen, and understand how you live before a single sketch.</p></li>
<li class="reveal"><span class="step-no">02</span><h3>Concept</h3><p>Mood, plan and palette — a clear direction you can feel and touch.</p></li>
<li class="reveal"><span class="step-no">03</span><h3>Curate</h3><p>We source furniture, fabric and finishes, then manage every order.</p></li>
<li class="reveal"><span class="step-no">04</span><h3>Complete</h3><p>Install day, styling and the final reveal of a home ready to live in.</p></li>
</ol>
</div>
</section>
<!-- Testimonials -->
<section class="voices" id="voices" aria-labelledby="voices-title">
<div class="wrap">
<h2 id="voices-title" class="visually-hidden">What clients say</h2>
<div class="quote-carousel reveal" aria-live="polite">
<blockquote class="quote is-active">
<p>“They understood our home better than we did. It finally feels like us — warm, uncluttered, ours.”</p>
<footer><strong>Lucía & Marco</strong> · Willow House</footer>
</blockquote>
<blockquote class="quote">
<p>“The kitchen is now the heart of the house. Every material was chosen with such care.”</p>
<footer><strong>Priya Nandakumar</strong> · Fig & Slate</footer>
</blockquote>
<blockquote class="quote">
<p>“Calm, professional, and genuinely kind. The whole process felt unhurried and considered.”</p>
<footer><strong>Tomás Herrera</strong> · The Quiet Wing</footer>
</blockquote>
</div>
<div class="quote-dots" role="tablist" aria-label="Choose testimonial">
<button class="dot is-active" aria-label="Testimonial 1" data-i="0"></button>
<button class="dot" aria-label="Testimonial 2" data-i="1"></button>
<button class="dot" aria-label="Testimonial 3" data-i="2"></button>
</div>
</div>
</section>
<!-- Booking -->
<section class="booking" id="book" aria-labelledby="book-title">
<div class="wrap booking-grid">
<div class="booking-copy reveal">
<p class="eyebrow">Start here</p>
<h2 id="book-title">Book a consultation</h2>
<p class="section-sub">Tell us a little about your home and we will follow up within two working days.</p>
<div class="estimate" aria-live="polite">
<p class="estimate-label">Indicative starting investment</p>
<p class="estimate-value" id="estimateValue">$8,400</p>
<p class="estimate-note" id="estimateNote">Full-home concept · medium home</p>
</div>
</div>
<form class="booking-form reveal" id="bookForm" novalidate>
<div class="field">
<label for="name">Full name</label>
<input id="name" name="name" type="text" autocomplete="name" placeholder="Your name" required />
<span class="err" data-for="name"></span>
</div>
<div class="field">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" placeholder="[email protected]" required />
<span class="err" data-for="email"></span>
</div>
<div class="field-row">
<div class="field">
<label for="size">Home size</label>
<select id="size" name="size">
<option value="small">Small · under 80 m²</option>
<option value="medium" selected>Medium · 80–160 m²</option>
<option value="large">Large · 160 m²+</option>
</select>
</div>
<div class="field">
<label for="scope">Scope</label>
<select id="scope" name="scope">
<option value="room">Single room</option>
<option value="multi">Multiple rooms</option>
<option value="full" selected>Full home</option>
</select>
</div>
</div>
<div class="field">
<label for="message">Anything you would love</label>
<textarea id="message" name="message" rows="3" placeholder="A calmer living room, a kitchen that opens to the garden…"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Request my consult</button>
<p class="form-fine">No obligation · We reply within 2 working days.</p>
</form>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap footer-inner">
<div>
<span class="brand-mark" aria-hidden="true">M&O</span>
<p>Marrow & Oak · Residential interior design studio. Warm homes, made for the living.</p>
</div>
<p class="footer-fine">Fictional studio for demonstration. © 2026 Marrow & Oak.</p>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A calm, editorial landing page for Marrow & Oak, a fictional residential interior design studio. The layout leads with a serif hero over a soft clay gradient, then invites visitors into a room-by-room explorer — tabs for Living, Kitchen, Bedroom, Bath and Studio swap the featured image, caption and material notes with a gentle fade. Below, a grid of recent home projects uses hover-lift cards with tags and a subtle image zoom.
The page is designed to move a homeowner from browsing to booking. A process timeline walks through Consult, Concept, Curate and Complete, while a rotating testimonial keeps social proof in view. A sticky consultation panel carries a small live estimate that reacts to home size and project scope, and the booking form validates inline before firing a toast confirmation.
All interactions are vanilla JavaScript: tabbed room switching with keyboard support, an IntersectionObserver that reveals sections on scroll, an auto-advancing testimonial carousel with manual dots, the reactive estimate calculator, and accessible form validation with a lightweight toast() helper.