Interior — Project Detail
A warm editorial case-study page for an interior design studio: a full-bleed hero, project brief with scope stats, a filterable image gallery with lightbox, a draggable before/after slider, a materials palette, a pull-quote from the client, and a next-project teaser. Muted natural palette, Cormorant Garamond headings, generous whitespace and thin rules keep the focus on the rooms.
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 50px -28px rgba(44, 38, 32, 0.5);
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, 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-osmoothing: grayscale;
}
img { max-width: 100%; display: block; }
:focus-visible {
outline: 2px solid var(--clay-d);
outline-offset: 3px;
border-radius: var(--r-sm);
}
/* ---------- progress rail ---------- */
.progress {
position: fixed;
top: 0; left: 0; right: 0;
height: 3px;
background: transparent;
z-index: 60;
}
.progress span {
display: block;
height: 100%;
width: 0;
background: linear-gradient(90deg, var(--clay), var(--walnut));
transition: width 0.08s linear;
}
/* ---------- topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 50;
display: flex;
align-items: center;
gap: 20px;
padding: 14px clamp(18px, 5vw, 64px);
background: rgba(251, 249, 245, 0.82);
backdrop-filter: saturate(1.1) blur(10px);
border-bottom: 1px solid var(--line);
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
text-decoration: none;
color: var(--ink);
}
.brand-mark {
display: grid;
place-items: center;
width: 34px; height: 34px;
border: 1px solid var(--walnut);
color: var(--walnut);
border-radius: 50%;
font-family: var(--serif);
font-weight: 600;
font-size: 14px;
letter-spacing: 0.02em;
}
.brand-name {
font-family: var(--serif);
font-size: 1.3rem;
font-weight: 600;
letter-spacing: 0.01em;
}
.topnav {
display: flex;
gap: 26px;
margin-left: 12px;
}
.topnav a {
text-decoration: none;
color: var(--ink-2);
font-size: 0.82rem;
font-weight: 500;
letter-spacing: 0.04em;
text-transform: uppercase;
position: relative;
padding: 4px 0;
transition: color 0.2s;
}
.topnav a::after {
content: "";
position: absolute;
left: 0; bottom: 0;
width: 0; height: 1px;
background: var(--clay-d);
transition: width 0.25s ease;
}
.topnav a:hover { color: var(--ink); }
.topnav a:hover::after { width: 100%; }
.topbar-actions {
margin-left: auto;
display: flex;
gap: 10px;
align-items: center;
}
.btn {
font-family: var(--sans);
font-size: 0.85rem;
font-weight: 600;
border-radius: 999px;
padding: 9px 18px;
cursor: pointer;
border: 1px solid transparent;
transition: transform 0.15s, background 0.2s, color 0.2s, border-color 0.2s;
display: inline-flex;
align-items: center;
gap: 7px;
}
.btn:active { transform: translateY(1px) scale(0.98); }
.btn-ghost {
background: transparent;
border-color: var(--line);
color: var(--ink-2);
}
.btn-ghost:hover { border-color: var(--clay); color: var(--ink); }
.btn-ghost[aria-pressed="true"] {
color: var(--clay-d);
border-color: var(--clay);
background: rgba(176, 137, 104, 0.1);
}
.btn-ghost[aria-pressed="true"] .ico { color: var(--clay-d); }
.btn-solid {
background: var(--walnut);
color: #f4ede4;
}
.btn-solid:hover { background: var(--clay-d); }
.ico { font-size: 1rem; line-height: 1; }
/* ---------- hero ---------- */
.hero {
position: relative;
min-height: 82vh;
display: flex;
align-items: flex-end;
overflow: hidden;
isolation: isolate;
}
.hero-media {
position: absolute;
inset: 0;
background:
linear-gradient(120deg, rgba(92, 68, 51, 0.15), rgba(156, 175, 136, 0.12)),
url("https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?auto=format&fit=crop&w=1600&q=80") center/cover no-repeat,
linear-gradient(160deg, #d9ccbb, #b8a488);
transform: scale(1.06);
animation: heroDrift 18s ease-out forwards;
z-index: -2;
}
@keyframes heroDrift { to { transform: scale(1); } }
.hero-scrim {
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(30, 24, 18, 0.7) 0%, rgba(30, 24, 18, 0.15) 45%, rgba(30, 24, 18, 0.1) 100%);
z-index: -1;
}
.hero-inner {
padding: clamp(28px, 6vw, 72px);
color: #f7f1e8;
max-width: 820px;
}
.eyebrow {
margin: 0 0 14px;
font-size: 0.78rem;
letter-spacing: 0.22em;
text-transform: uppercase;
font-weight: 600;
color: #e7d8c4;
}
.hero h1 {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(2.8rem, 8vw, 5.6rem);
line-height: 0.98;
margin: 0 0 18px;
letter-spacing: 0.005em;
}
.hero-meta {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 0;
padding: 0;
font-size: 0.95rem;
letter-spacing: 0.06em;
color: #ede2d3;
}
.hero-scroll {
position: absolute;
bottom: 22px;
left: 50%;
transform: translateX(-50%);
width: 26px; height: 42px;
border: 1px solid rgba(247, 241, 232, 0.6);
border-radius: 999px;
display: grid;
place-items: start center;
padding-top: 8px;
}
.hero-scroll span {
width: 3px; height: 8px;
border-radius: 2px;
background: #f7f1e8;
animation: scrollDot 1.6s ease-in-out infinite;
}
@keyframes scrollDot {
0%, 100% { opacity: 0.3; transform: translateY(0); }
50% { opacity: 1; transform: translateY(9px); }
}
/* ---------- section shell ---------- */
.section {
padding: clamp(52px, 8vw, 104px) clamp(18px, 6vw, 96px);
max-width: 1220px;
margin: 0 auto;
}
.kicker {
font-size: 0.74rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--clay-d);
font-weight: 600;
margin: 0 0 10px;
}
.section h2 {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(1.9rem, 4vw, 3rem);
line-height: 1.08;
margin: 0;
color: var(--ink);
max-width: 16ch;
}
.section-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
flex-wrap: wrap;
margin-bottom: 34px;
border-bottom: 1px solid var(--line);
padding-bottom: 22px;
}
/* ---------- brief ---------- */
.brief {
display: grid;
grid-template-columns: 1.6fr 1fr;
gap: clamp(30px, 5vw, 72px);
align-items: start;
}
.brief-lead h2 { max-width: 20ch; margin-bottom: 22px; }
.prose {
color: var(--ink-2);
font-size: 1.02rem;
margin: 0 0 18px;
max-width: 56ch;
}
.tags { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 26px; }
.tag {
font-size: 0.78rem;
font-weight: 500;
color: var(--walnut);
border: 1px solid var(--line);
background: var(--paper);
padding: 6px 13px;
border-radius: 999px;
}
.brief-stats {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 26px 26px 22px;
box-shadow: var(--shadow);
}
.brief-stats dl {
margin: 0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 22px 18px;
}
.brief-stats dt {
font-size: 0.72rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 5px;
}
.brief-stats dd {
margin: 0;
font-family: var(--serif);
font-size: 1.5rem;
font-weight: 600;
color: var(--ink);
}
.brief-credit {
margin-top: 26px;
padding-top: 20px;
border-top: 1px solid var(--line);
}
.credit-label {
margin: 0 0 3px;
font-size: 0.72rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.credit-name {
margin: 0;
font-family: var(--serif);
font-size: 1.25rem;
color: var(--ink);
}
/* ---------- gallery ---------- */
.grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 16px;
}
.frame {
position: relative;
border: 0;
padding: 0;
cursor: pointer;
overflow: hidden;
border-radius: var(--r-md);
background: #d9ccbb;
aspect-ratio: 4 / 3;
transition: transform 0.35s cubic-bezier(.2,.7,.2,1), box-shadow 0.35s, opacity 0.3s;
}
.frame.span-2 { grid-column: span 4; aspect-ratio: 16 / 9; }
.frame.span-tall { grid-column: span 2; grid-row: span 2; aspect-ratio: auto; }
.frame > .frame-photo {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
transition: transform 0.6s cubic-bezier(.2,.7,.2,1);
}
.frame::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(30,24,18,0.55), rgba(30,24,18,0) 55%);
opacity: 0.5;
transition: opacity 0.35s;
}
.frame-cap {
position: absolute;
left: 14px; bottom: 12px;
z-index: 2;
color: #f7f1e8;
text-align: left;
transform: translateY(4px);
opacity: 0;
transition: opacity 0.3s, transform 0.3s;
}
.frame-cap .fc-room {
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #ecd9c2;
}
.frame-cap .fc-title {
font-family: var(--serif);
font-size: 1.15rem;
}
.frame:hover, .frame:focus-visible {
transform: translateY(-4px);
box-shadow: var(--shadow);
}
.frame:hover > .frame-photo { transform: scale(1.07); }
.frame:hover::after { opacity: 0.7; }
.frame:hover .frame-cap, .frame:focus-visible .frame-cap {
opacity: 1;
transform: translateY(0);
}
.frame.is-hidden {
display: none;
}
/* ---------- before / after ---------- */
.compare-hint {
margin: 0;
color: var(--muted);
font-size: 0.85rem;
font-style: italic;
}
.ba {
position: relative;
aspect-ratio: 16 / 9;
max-height: 620px;
border-radius: var(--r-lg);
overflow: hidden;
user-select: none;
box-shadow: var(--shadow);
touch-action: pan-y;
}
.ba-img {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
}
.ba-after {
background-image:
linear-gradient(120deg, rgba(156,175,136,0.08), rgba(176,137,104,0.05)),
url("https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?auto=format&fit=crop&w=1600&q=80");
background-color: #cdbfa9;
}
.ba-before {
width: 50%;
background-image:
linear-gradient(180deg, rgba(20,16,12,0.35), rgba(20,16,12,0.5)),
url("https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?auto=format&fit=crop&w=1600&q=80");
background-color: #6b6055;
filter: grayscale(0.35) brightness(0.82);
border-right: 2px solid var(--paper);
}
.ba-tag {
position: absolute;
top: 16px;
z-index: 3;
font-size: 0.72rem;
letter-spacing: 0.14em;
text-transform: uppercase;
font-weight: 600;
padding: 6px 12px;
border-radius: 999px;
backdrop-filter: blur(4px);
}
.ba-tag-before { left: 16px; background: rgba(30,24,18,0.55); color: #f0e6d8; }
.ba-tag-after { right: 16px; background: rgba(251,249,245,0.85); color: var(--walnut); }
.ba-handle {
position: absolute;
top: 0; bottom: 0;
left: 50%;
width: 44px;
transform: translateX(-50%);
z-index: 4;
cursor: ew-resize;
display: grid;
place-items: center;
}
.ba-line {
position: absolute;
top: 0; bottom: 0;
left: 50%;
width: 2px;
transform: translateX(-50%);
background: var(--paper);
}
.ba-grip {
position: relative;
display: grid;
place-items: center;
width: 44px; height: 44px;
border-radius: 50%;
background: var(--paper);
color: var(--walnut);
font-weight: 700;
letter-spacing: -1px;
box-shadow: 0 6px 18px -6px rgba(44,38,32,0.6);
transition: transform 0.15s;
}
.ba-handle:hover .ba-grip { transform: scale(1.08); }
.ba-handle:focus-visible { outline: none; }
.ba-handle:focus-visible .ba-grip {
outline: 2px solid var(--clay-d);
outline-offset: 3px;
}
/* ---------- materials ---------- */
.mat-grid {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
gap: 16px;
}
.mat {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.mat:hover {
transform: translateY(-3px);
box-shadow: var(--shadow);
border-color: rgba(176,137,104,0.4);
}
.mat-swatch {
height: 84px;
position: relative;
}
.mat-swatch::after {
content: "";
position: absolute;
inset: 0;
box-shadow: inset 0 -1px 0 var(--line);
mix-blend-mode: multiply;
opacity: 0.15;
background: repeating-linear-gradient(45deg, #000 0 1px, transparent 1px 6px);
}
.mat-body { padding: 13px 15px 15px; }
.mat-type {
font-size: 0.68rem;
letter-spacing: 0.13em;
text-transform: uppercase;
color: var(--muted);
margin: 0 0 3px;
}
.mat-name {
font-family: var(--serif);
font-size: 1.2rem;
color: var(--ink);
margin: 0 0 3px;
}
.mat-src { font-size: 0.82rem; color: var(--ink-2); margin: 0; }
/* ---------- quote ---------- */
.quote {
text-align: center;
max-width: 900px;
}
.quote figure { margin: 0; }
.quote blockquote {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(1.6rem, 3.6vw, 2.7rem);
line-height: 1.28;
color: var(--walnut);
margin: 0 0 26px;
position: relative;
}
.quote figcaption {
display: flex;
flex-direction: column;
gap: 3px;
}
.q-name { font-weight: 700; color: var(--ink); letter-spacing: 0.02em; }
.q-role {
font-size: 0.85rem;
color: var(--muted);
letter-spacing: 0.06em;
text-transform: uppercase;
}
/* ---------- next project ---------- */
.next {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: stretch;
text-decoration: none;
color: inherit;
max-width: 1220px;
margin: 0 auto clamp(52px, 8vw, 104px);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
background: var(--paper);
transition: box-shadow 0.3s, transform 0.3s;
}
.next:hover { box-shadow: var(--shadow); transform: translateY(-3px); }
.next-media {
min-height: 260px;
background:
linear-gradient(120deg, rgba(92,68,51,0.2), rgba(156,175,136,0.15)),
url("https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1200&q=80") center/cover no-repeat,
linear-gradient(160deg, #cdbfa9, #a89376);
transition: transform 0.5s ease;
}
.next:hover .next-media { transform: scale(1.04); }
.next-body {
padding: clamp(28px, 4vw, 48px);
display: flex;
flex-direction: column;
justify-content: center;
}
.next-body h2 {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(2rem, 4vw, 3rem);
margin: 0 0 8px;
}
.next-meta { color: var(--ink-2); margin: 0 0 22px; letter-spacing: 0.04em; }
.next-cue {
font-weight: 600;
color: var(--clay-d);
letter-spacing: 0.02em;
}
.next-cue span { display: inline-block; transition: transform 0.25s; }
.next:hover .next-cue span { transform: translateX(6px); }
/* ---------- footer ---------- */
.footer {
border-top: 1px solid var(--line);
padding: 32px clamp(18px, 6vw, 96px);
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
font-size: 0.86rem;
color: var(--ink-2);
}
.footer .muted { color: var(--muted); }
/* ---------- lightbox ---------- */
.lightbox {
position: fixed;
inset: 0;
z-index: 80;
background: rgba(24, 19, 14, 0.9);
backdrop-filter: blur(6px);
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 8px;
padding: clamp(16px, 4vw, 48px);
animation: fade 0.2s ease;
}
.lightbox[hidden] { display: none; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.lb-figure {
margin: 0;
max-width: min(1100px, 100%);
justify-self: center;
}
.lb-img {
width: 100%;
aspect-ratio: 3 / 2;
background-size: cover;
background-position: center;
border-radius: var(--r-md);
box-shadow: 0 30px 80px -30px rgba(0,0,0,0.7);
}
.lb-figure figcaption {
color: #ede2d3;
text-align: center;
margin-top: 14px;
font-size: 0.95rem;
}
.lb-figure figcaption b {
font-family: var(--serif);
font-weight: 600;
font-size: 1.15rem;
display: block;
margin-bottom: 2px;
}
.lb-close, .lb-nav {
background: rgba(247,241,232,0.1);
border: 1px solid rgba(247,241,232,0.25);
color: #f7f1e8;
border-radius: 50%;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.2s, transform 0.15s;
}
.lb-close {
position: absolute;
top: 18px; right: 18px;
width: 42px; height: 42px;
font-size: 1rem;
}
.lb-nav { width: 52px; height: 52px; font-size: 1.8rem; line-height: 1; }
.lb-close:hover, .lb-nav:hover { background: rgba(247,241,232,0.22); }
.lb-nav:active { transform: scale(0.92); }
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 24px);
background: var(--walnut);
color: #f4ede4;
padding: 12px 20px;
border-radius: 999px;
font-size: 0.88rem;
font-weight: 500;
box-shadow: var(--shadow);
opacity: 0;
pointer-events: none;
z-index: 90;
transition: opacity 0.3s, transform 0.3s;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- responsive ---------- */
@media (max-width: 880px) {
.topnav { display: none; }
.brief { grid-template-columns: 1fr; }
.grid { grid-template-columns: repeat(2, 1fr); }
.frame.span-2, .frame.span-tall { grid-column: span 2; grid-row: auto; aspect-ratio: 16/9; }
.next { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
body { font-size: 15px; }
.topbar { gap: 12px; padding: 12px 18px; }
.brand-name { display: none; }
.btn .lbl { display: none; }
.btn { padding: 9px 13px; }
.hero { min-height: 74vh; }
.hero-meta { font-size: 0.85rem; gap: 8px; }
.section { padding: 44px 18px; }
.section-head { margin-bottom: 24px; }
.filters { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.grid { grid-template-columns: 1fr; gap: 12px; }
.frame.span-2, .frame.span-tall { grid-column: span 1; }
.brief-stats dl { grid-template-columns: 1fr 1fr; }
.ba { aspect-ratio: 3 / 4; }
.lightbox { grid-template-columns: 1fr; }
.lb-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; }
.lb-prev { left: 12px; }
.lb-next { right: 12px; }
}
/* filter chips (shared with topbar btn family) */
.filters { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
font-family: var(--sans);
font-size: 0.82rem;
font-weight: 500;
color: var(--ink-2);
background: var(--paper);
border: 1px solid var(--line);
border-radius: 999px;
padding: 8px 16px;
cursor: pointer;
transition: color 0.2s, background 0.2s, border-color 0.2s, transform 0.12s;
white-space: nowrap;
}
.chip:hover { border-color: var(--clay); color: var(--ink); }
.chip:active { transform: scale(0.96); }
.chip.is-active {
background: var(--walnut);
border-color: var(--walnut);
color: #f4ede4;
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
html { scroll-behavior: auto; }
}(function () {
"use strict";
/* ---------- data ---------- */
var U = "https://images.unsplash.com/";
var q = "?auto=format&fit=crop&w=1200&q=80";
var photos = [
{ id: 1, room: "living", size: "span-2", title: "The hearth room", cap: "Lime-plastered walls and a reclaimed oak mantel anchor the main living space.", img: U + "photo-1616486338812-3dadae4b4ace" + q },
{ id: 2, room: "kitchen", size: "", title: "Scullery kitchen", cap: "Hand-painted cabinetry in a soft clay tone, topped with honed limestone.", img: U + "photo-1556909212-d5b604d0c90d" + q },
{ id: 3, room: "detail", size: "", title: "Reading nook", cap: "A window seat upholstered in undyed Belgian linen.", img: U + "photo-1567016432779-094069958ea5" + q },
{ id: 4, room: "bedroom", size: "span-tall", title: "Principal bedroom", cap: "North light, layered wools and a low walnut headboard.", img: U + "photo-1616594039964-ae9021a400a0" + q },
{ id: 5, room: "detail", size: "", title: "Shelf styling", cap: "Slipware ceramics and dried grasses against a chalky sage wall.", img: U + "photo-1513694203232-719a280e022f" + q },
{ id: 6, room: "living", size: "", title: "Sitting area", cap: "A pair of vintage rush armchairs beside the garden doors.", img: U + "photo-1493809842364-78817add7ffb" + q },
{ id: 7, room: "kitchen", size: "", title: "Pantry corner", cap: "Open oak shelving keeps everyday tableware within reach.", img: U + "photo-1600585152220-90363fe7e115" + q },
{ id: 8, room: "bedroom", size: "", title: "Guest room", cap: "A restrained palette of oatmeal and warm white.", img: U + "photo-1522708323590-d24dbb6b0267" + q }
];
var materials = [
{ type: "Flooring", name: "Reclaimed oak", src: "Salvaged barn boards, waxed", color: "#a07a52" },
{ type: "Walls", name: "Lime plaster", src: "Bauwerk Colour · Oyster", color: "#e7ddce" },
{ type: "Joinery", name: "Clay cabinetry", src: "Farrow & Ball · Dead Salmon", color: "#b08968" },
{ type: "Stone", name: "Honed limestone", src: "Dijon, brushed finish", color: "#cbbfa6" },
{ type: "Accent", name: "Chalky sage", src: "Little Greene · Aquamarine", color: "#9caf88" },
{ type: "Textiles", name: "Belgian linen", src: "Undyed, stonewashed", color: "#d9cdb8" },
{ type: "Metal", name: "Aged brass", src: "Unlacquered, hand-patinated", color: "#8c6a4f" },
{ type: "Detail", name: "Walnut trim", src: "Solid, oil-finished", color: "#5c4433" }
];
var $ = function (s, c) { return (c || document).querySelector(s); };
var $$ = function (s, c) { return Array.prototype.slice.call((c || document).querySelectorAll(s)); };
/* ---------- toast ---------- */
var toastEl = $("#toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 2400);
}
/* ---------- build gallery ---------- */
var grid = $("#galleryGrid");
photos.forEach(function (p, i) {
var btn = document.createElement("button");
btn.type = "button";
btn.className = "frame" + (p.size ? " " + p.size : "");
btn.setAttribute("data-room", p.room);
btn.setAttribute("data-index", i);
btn.setAttribute("aria-label", "View " + p.title);
var photo = document.createElement("span");
photo.className = "frame-photo";
photo.style.backgroundImage = "url('" + p.img + "')";
var cap = document.createElement("span");
cap.className = "frame-cap";
cap.innerHTML = '<span class="fc-room">' + p.room + '</span><span class="fc-title">' + p.title + "</span>";
btn.appendChild(photo);
btn.appendChild(cap);
btn.addEventListener("click", function () { openLightbox(i); });
grid.appendChild(btn);
});
/* ---------- filters ---------- */
var chips = $$(".chip");
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) { c.classList.remove("is-active"); c.setAttribute("aria-pressed", "false"); });
chip.classList.add("is-active");
chip.setAttribute("aria-pressed", "true");
var f = chip.getAttribute("data-filter");
var shown = 0;
$$(".frame", grid).forEach(function (frame) {
var match = f === "all" || frame.getAttribute("data-room") === f;
frame.classList.toggle("is-hidden", !match);
if (match) shown++;
});
toast(shown + (shown === 1 ? " room" : " rooms") + " shown");
});
});
/* ---------- lightbox ---------- */
var lb = $("#lightbox");
var lbImg = $("#lbImg");
var lbCap = $("#lbCaption");
var lbCurrent = 0;
var lastFocus = null;
function visiblePhotos() {
return $$(".frame", grid).filter(function (f) { return !f.classList.contains("is-hidden"); })
.map(function (f) { return parseInt(f.getAttribute("data-index"), 10); });
}
function renderLb(idx) {
var p = photos[idx];
lbImg.style.backgroundImage = "url('" + p.img + "')";
lbCap.innerHTML = "<b>" + p.title + "</b>" + p.cap;
lbCurrent = idx;
}
function openLightbox(idx) {
lastFocus = document.activeElement;
renderLb(idx);
lb.hidden = false;
document.body.style.overflow = "hidden";
$("#lbClose").focus();
}
function closeLightbox() {
lb.hidden = true;
document.body.style.overflow = "";
if (lastFocus) lastFocus.focus();
}
function step(dir) {
var vis = visiblePhotos();
if (!vis.length) return;
var pos = vis.indexOf(lbCurrent);
if (pos === -1) pos = 0;
pos = (pos + dir + vis.length) % vis.length;
renderLb(vis[pos]);
}
$("#lbClose").addEventListener("click", closeLightbox);
$("#lbPrev").addEventListener("click", function () { step(-1); });
$("#lbNext").addEventListener("click", function () { step(1); });
lb.addEventListener("click", function (e) { if (e.target === lb) closeLightbox(); });
document.addEventListener("keydown", function (e) {
if (lb.hidden) return;
if (e.key === "Escape") closeLightbox();
else if (e.key === "ArrowLeft") step(-1);
else if (e.key === "ArrowRight") step(1);
});
/* ---------- build materials ---------- */
var matGrid = $("#matGrid");
materials.forEach(function (m) {
var li = document.createElement("li");
li.className = "mat";
li.innerHTML =
'<div class="mat-swatch" style="background:' + m.color + '"></div>' +
'<div class="mat-body">' +
'<p class="mat-type">' + m.type + "</p>" +
'<p class="mat-name">' + m.name + "</p>" +
'<p class="mat-src">' + m.src + "</p>" +
"</div>";
li.addEventListener("click", function () { toast(m.name + " — " + m.src); });
matGrid.appendChild(li);
});
/* ---------- before / after slider ---------- */
var ba = $("#ba");
var baBefore = $("#baBefore");
var baHandle = $("#baHandle");
var dragging = false;
function setBA(pct) {
pct = Math.max(0, Math.min(100, pct));
baBefore.style.width = pct + "%";
baHandle.style.left = pct + "%";
baHandle.setAttribute("aria-valuenow", Math.round(pct));
}
function posFromEvent(e) {
var rect = ba.getBoundingClientRect();
var x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
return (x / rect.width) * 100;
}
function startDrag(e) { dragging = true; baHandle.focus(); moveDrag(e); }
function moveDrag(e) {
if (!dragging) return;
if (e.cancelable) e.preventDefault();
setBA(posFromEvent(e));
}
function endDrag() { dragging = false; }
baHandle.addEventListener("mousedown", startDrag);
ba.addEventListener("mousedown", startDrag);
window.addEventListener("mousemove", moveDrag);
window.addEventListener("mouseup", endDrag);
baHandle.addEventListener("touchstart", startDrag, { passive: false });
window.addEventListener("touchmove", moveDrag, { passive: false });
window.addEventListener("touchend", endDrag);
baHandle.addEventListener("keydown", function (e) {
var cur = parseFloat(baHandle.getAttribute("aria-valuenow")) || 50;
if (e.key === "ArrowLeft") { setBA(cur - 4); e.preventDefault(); }
else if (e.key === "ArrowRight") { setBA(cur + 4); e.preventDefault(); }
else if (e.key === "Home") { setBA(0); e.preventDefault(); }
else if (e.key === "End") { setBA(100); e.preventDefault(); }
});
setBA(50);
/* ---------- save + share ---------- */
var saveBtn = $("#saveBtn");
saveBtn.addEventListener("click", function () {
var on = saveBtn.getAttribute("aria-pressed") === "true";
on = !on;
saveBtn.setAttribute("aria-pressed", String(on));
$(".ico", saveBtn).textContent = on ? "♥" : "♡";
$(".lbl", saveBtn).textContent = on ? "Saved" : "Save";
toast(on ? "Project saved to your board" : "Removed from board");
});
$("#shareBtn").addEventListener("click", function () {
if (navigator.share) {
navigator.share({ title: "Willowmere House", text: "A case study by Marlowe & Fern" })
.catch(function () {});
} else {
toast("Link copied to clipboard");
}
});
$("#nextProject").addEventListener("click", function (e) {
e.preventDefault();
toast("Loading Saltmarsh Studio…");
window.scrollTo({ top: 0, behavior: "smooth" });
});
/* ---------- scroll progress + smooth nav ---------- */
var bar = $("#progressBar");
function onScroll() {
var h = document.documentElement;
var max = h.scrollHeight - h.clientHeight;
bar.style.width = (max > 0 ? (h.scrollTop / max) * 100 : 0) + "%";
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Willowmere House — Marlowe & Fern</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=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>
<!-- scroll progress rail -->
<div class="progress" aria-hidden="true"><span id="progressBar"></span></div>
<header class="topbar">
<a class="brand" href="#top">
<span class="brand-mark" aria-hidden="true">M&F</span>
<span class="brand-name">Marlowe & Fern</span>
</a>
<nav class="topnav" aria-label="Section">
<a href="#brief">Brief</a>
<a href="#gallery">Gallery</a>
<a href="#compare">Before / After</a>
<a href="#materials">Materials</a>
</nav>
<div class="topbar-actions">
<button class="btn btn-ghost" id="saveBtn" type="button" aria-pressed="false">
<span class="ico" aria-hidden="true">♡</span><span class="lbl">Save</span>
</button>
<button class="btn btn-solid" id="shareBtn" type="button">Share</button>
</div>
</header>
<main id="top">
<!-- HERO -->
<section class="hero" aria-labelledby="hero-title">
<div class="hero-media" role="img" aria-label="Sunlit living room with linen sofa and oak floors"></div>
<div class="hero-scrim"></div>
<div class="hero-inner">
<p class="eyebrow">Residential · Full Renovation</p>
<h1 id="hero-title">Willowmere House</h1>
<ul class="hero-meta">
<li>Cotswolds, England</li>
<li aria-hidden="true">·</li>
<li>Completed 2026</li>
<li aria-hidden="true">·</li>
<li>214 m²</li>
</ul>
</div>
<a class="hero-scroll" href="#brief" aria-label="Scroll to project brief">
<span></span>
</a>
</section>
<!-- BRIEF -->
<section class="section brief" id="brief" aria-labelledby="brief-title">
<div class="brief-lead">
<p class="kicker">The Brief</p>
<h2 id="brief-title">A weekend farmhouse, softened for slow living.</h2>
<p class="prose">
The clients — a family of four splitting time between London and the country — asked us to
strip back decades of dark panelling and reintroduce daylight without losing the building’s
seventeenth-century bones. We reworked the ground-floor plan around a single, generous
kitchen-and-hearth room, layering lime plaster, reclaimed oak and honest, tactile textiles.
</p>
<p class="prose">
Every surface was chosen to age gracefully: nothing here is meant to look new for long. The
result is a home that feels quietly lived-in on the day the keys were handed back.
</p>
<div class="tags">
<span class="tag">Space planning</span>
<span class="tag">Joinery</span>
<span class="tag">Lighting design</span>
<span class="tag">Styling</span>
</div>
</div>
<aside class="brief-stats" aria-label="Project facts">
<dl>
<div><dt>Scope</dt><dd>Full renovation</dd></div>
<div><dt>Duration</dt><dd>14 months</dd></div>
<div><dt>Rooms</dt><dd>9 spaces</dd></div>
<div><dt>Team</dt><dd>Lead + 3</dd></div>
</dl>
<div class="brief-credit">
<p class="credit-label">Photography</p>
<p class="credit-name">Odette Vann</p>
</div>
</aside>
</section>
<!-- GALLERY -->
<section class="section gallery" id="gallery" aria-labelledby="gallery-title">
<div class="section-head">
<div>
<p class="kicker">The Rooms</p>
<h2 id="gallery-title">A walk through the house</h2>
</div>
<div class="filters" role="group" aria-label="Filter gallery by room">
<button class="chip is-active" data-filter="all" type="button" aria-pressed="true">All</button>
<button class="chip" data-filter="living" type="button" aria-pressed="false">Living</button>
<button class="chip" data-filter="kitchen" type="button" aria-pressed="false">Kitchen</button>
<button class="chip" data-filter="bedroom" type="button" aria-pressed="false">Bedroom</button>
<button class="chip" data-filter="detail" type="button" aria-pressed="false">Details</button>
</div>
</div>
<div class="grid" id="galleryGrid">
<!-- populated by script.js -->
</div>
</section>
<!-- BEFORE / AFTER -->
<section class="section compare" id="compare" aria-labelledby="compare-title">
<div class="section-head">
<div>
<p class="kicker">The Transformation</p>
<h2 id="compare-title">The hearth room, before & after</h2>
</div>
<p class="compare-hint">Drag the handle — or use ← → keys.</p>
</div>
<div class="ba" id="ba">
<div class="ba-img ba-after" role="img" aria-label="Finished hearth room, plastered and furnished"></div>
<div class="ba-img ba-before" id="baBefore" role="img" aria-label="Hearth room before renovation, bare and dark">
<span class="ba-tag ba-tag-before">Before</span>
</div>
<span class="ba-tag ba-tag-after">After</span>
<div class="ba-handle" id="baHandle" role="slider" tabindex="0"
aria-label="Reveal amount" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50">
<span class="ba-line" aria-hidden="true"></span>
<span class="ba-grip" aria-hidden="true">‹ ›</span>
</div>
</div>
</section>
<!-- MATERIALS -->
<section class="section materials" id="materials" aria-labelledby="materials-title">
<div class="section-head">
<div>
<p class="kicker">The Palette</p>
<h2 id="materials-title">Materials & finishes</h2>
</div>
</div>
<ul class="mat-grid" id="matGrid">
<!-- populated by script.js -->
</ul>
</section>
<!-- QUOTE -->
<section class="section quote" aria-label="Client testimonial">
<figure>
<blockquote>
“They listened for months before they moved a single wall. Now the house feels like it
was always meant to be this way — we just stopped fighting it.”
</blockquote>
<figcaption>
<span class="q-name">Helena & Rory Ashcombe</span>
<span class="q-role">Homeowners, Willowmere House</span>
</figcaption>
</figure>
</section>
<!-- NEXT PROJECT -->
<a class="next" href="#top" id="nextProject" aria-label="Next project: Saltmarsh Studio">
<div class="next-media" aria-hidden="true"></div>
<div class="next-body">
<p class="kicker">Next Project</p>
<h2>Saltmarsh Studio</h2>
<p class="next-meta">Coastal retreat · Norfolk · 2025</p>
<span class="next-cue">View project <span aria-hidden="true">→</span></span>
</div>
</a>
</main>
<footer class="footer">
<p>Marlowe & Fern — Interior Architecture & Design</p>
<p class="muted">Fictional studio · demo layout</p>
</footer>
<!-- LIGHTBOX -->
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Photo viewer" hidden>
<button class="lb-close" id="lbClose" type="button" aria-label="Close viewer">✕</button>
<button class="lb-nav lb-prev" id="lbPrev" type="button" aria-label="Previous photo">‹</button>
<figure class="lb-figure">
<div class="lb-img" id="lbImg"></div>
<figcaption id="lbCaption"></figcaption>
</figure>
<button class="lb-nav lb-next" id="lbNext" type="button" aria-label="Next photo">›</button>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A single-project case study for a fictional interior studio, Marlowe & Fern. The page opens on a full-bleed hero with the project title, location and year, then flows into a brief that pairs a scope description with a small grid of project stats. The image gallery filters by room via chip buttons and opens any frame in a keyboard-navigable lightbox.
The centerpiece is a before/after slider you drag (mouse, touch, or arrow keys) to wipe between the empty shell and the finished room. Below it, a materials palette lists the woods, stones and textiles used as swatch chips, and a large pull-quote credits the client. Micro-interactions — hover lifts on gallery frames, a sticky progress rail, toast confirmations on the save and share actions — keep the page feeling alive without pulling attention from the work.
Everything is vanilla: no frameworks, no build step. The lightbox, gallery filter, before/after wipe and toast helper are all hand-rolled and keyboard-accessible, and the layout collapses cleanly to a single column below 520px.