Construction — Project Detail
An in-depth construction project case study page with a photo gallery, key project facts like scope, duration, budget and location, a challenge-and-solution narrative, a materials list, a before and after teaser slider and a sturdy start-your-project call to action.
MCP
Code
:root {
--hv: #f5c518;
--hv-d: #c79a00;
--steel: #2b3440;
--steel-d: #1b222b;
--bg: #eceae6;
--ink: #1a1d22;
--muted: #666c75;
--orange: #e8642a;
--orange-d: #b04518;
--white: #ffffff;
--line: #c9c6bf;
--radius: 4px;
--shadow: 0 6px 22px rgba(27, 34, 43, 0.14);
--font: 'Inter', system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html { scroll-behavior: smooth; }
body {
font-family: var(--font);
background: var(--bg);
color: var(--ink);
line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
.wrap {
width: 100%;
max-width: 1080px;
margin: 0 auto;
padding: 0 20px;
}
h1, h2 {
font-weight: 900;
letter-spacing: -0.02em;
line-height: 1.05;
text-transform: uppercase;
}
a { color: inherit; }
img { max-width: 100%; display: block; }
/* ---------- Placeholder blocks ---------- */
.ph {
background:
repeating-linear-gradient(135deg, rgba(255,255,255,0.04) 0 14px, rgba(0,0,0,0.06) 14px 28px),
linear-gradient(135deg, var(--c1, var(--steel)), var(--c2, var(--steel-d)));
position: relative;
overflow: hidden;
}
.ph__tag {
position: absolute;
left: 14px;
bottom: 14px;
background: rgba(27, 34, 43, 0.78);
color: #fff;
font-size: 0.78rem;
font-weight: 600;
padding: 6px 10px;
border-radius: var(--radius);
letter-spacing: 0.02em;
}
/* ---------- Topbar ---------- */
.topbar {
background: var(--steel-d);
color: #fff;
border-bottom: 3px solid var(--hv);
position: sticky;
top: 0;
z-index: 30;
}
.topbar__in {
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
}
.brand {
font-weight: 900;
font-size: 1.1rem;
letter-spacing: 0.02em;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 9px;
}
.brand__mark {
width: 16px;
height: 16px;
background: var(--hv);
transform: skewX(-12deg);
display: inline-block;
}
.brand__sub { color: var(--hv); }
.topnav {
display: flex;
align-items: center;
gap: 22px;
}
.topnav a {
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
color: #d6d9dd;
}
.topnav a:hover { color: #fff; }
.topnav__cta {
background: var(--hv);
color: var(--steel-d) !important;
padding: 8px 14px;
border-radius: var(--radius);
font-weight: 800 !important;
}
.topnav__cta:hover { background: #ffd633; }
/* ---------- Hazard stripe ---------- */
.hazard {
height: 12px;
background: repeating-linear-gradient(
-45deg, var(--hv) 0 22px, var(--steel-d) 22px 44px
);
}
/* ---------- Hero ---------- */
.hero {
background: var(--steel);
color: #fff;
padding: 38px 0 34px;
}
.kicker {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 14px;
}
.kicker__meta {
color: #aeb4bc;
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.status-badge {
display: inline-block;
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 2px;
}
.status-badge--done {
background: var(--hv);
color: var(--steel-d);
}
.hero__title {
font-size: clamp(2rem, 6vw, 3.4rem);
margin-bottom: 14px;
}
.hero__lede {
max-width: 56ch;
color: #cfd3d8;
font-size: 1.05rem;
}
/* ---------- Gallery ---------- */
.gallery { margin: 28px auto; }
.gallery__main { margin-bottom: 12px; }
.gallery__main .ph {
width: 100%;
aspect-ratio: 16 / 7;
border: 3px solid var(--steel-d);
border-radius: var(--radius);
}
.gallery__thumbs {
list-style: none;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.thumb {
width: 100%;
padding: 0;
border: 3px solid transparent;
border-radius: var(--radius);
cursor: pointer;
background: none;
overflow: hidden;
display: block;
transition: border-color 0.15s, transform 0.15s;
}
.ph--thumb {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
}
.thumb:hover { transform: translateY(-2px); }
.thumb.is-active { border-color: var(--orange); }
.thumb:focus-visible {
outline: 3px solid var(--hv);
outline-offset: 2px;
}
/* ---------- Layout ---------- */
.layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: 32px;
align-items: start;
margin-bottom: 12px;
}
.block { margin-bottom: 30px; }
.block__title {
font-size: 1.4rem;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 3px solid var(--steel-d);
display: inline-block;
}
.block p { color: #34393f; max-width: 64ch; }
.block__hint {
color: var(--muted);
font-size: 0.9rem;
margin-bottom: 14px;
}
.wins {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 14px;
margin-top: 16px;
}
.wins li {
background: var(--white);
border: 2px solid var(--line);
border-left: 5px solid var(--orange);
border-radius: var(--radius);
padding: 10px 14px;
font-size: 0.9rem;
color: var(--muted);
}
.wins strong {
display: block;
font-size: 1.5rem;
font-weight: 900;
color: var(--ink);
line-height: 1;
}
/* Materials */
.materials {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.materials li {
background: var(--steel);
color: #fff;
font-size: 0.86rem;
font-weight: 600;
padding: 7px 13px;
border-radius: var(--radius);
border-bottom: 3px solid var(--steel-d);
}
/* ---------- Before / After ---------- */
.ba {
position: relative;
width: 100%;
aspect-ratio: 16 / 8;
border: 3px solid var(--steel-d);
border-radius: var(--radius);
overflow: hidden;
user-select: none;
touch-action: none;
}
.ba__after, .ba__before {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.ba__before {
width: 50%;
border-right: 3px solid var(--hv);
}
.ba__lbl {
position: absolute;
top: 12px;
font-size: 0.74rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 4px 9px;
border-radius: 2px;
}
.ba__lbl--before { left: 12px; background: var(--hv); color: var(--steel-d); }
.ba__lbl--after { right: 12px; background: rgba(27,34,43,0.8); color: #fff; }
.ba__handle {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 44px;
transform: translateX(-50%);
cursor: ew-resize;
display: flex;
align-items: center;
justify-content: center;
}
.ba__grip {
width: 38px;
height: 38px;
border-radius: 50%;
background: var(--hv);
color: var(--steel-d);
border: 3px solid var(--steel-d);
display: flex;
align-items: center;
justify-content: center;
font-weight: 900;
font-size: 1.05rem;
box-shadow: var(--shadow);
}
.ba__handle:focus-visible { outline: none; }
.ba__handle:focus-visible .ba__grip {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
/* ---------- Facts sidebar ---------- */
.facts {
background: var(--white);
border: 3px solid var(--steel-d);
border-radius: var(--radius);
padding: 20px;
position: sticky;
top: 78px;
box-shadow: var(--shadow);
}
.facts__title {
font-size: 1.15rem;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 2px dashed var(--line);
}
.facts__grid { display: grid; gap: 0; }
.fact {
padding: 12px 0;
border-bottom: 1px solid var(--line);
}
.fact:last-of-type { border-bottom: none; }
.fact dt {
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 3px;
}
.fact dd {
font-size: 1rem;
font-weight: 700;
color: var(--ink);
}
.facts__cta {
display: block;
text-align: center;
margin-top: 18px;
padding: 11px;
background: var(--steel);
color: #fff;
font-weight: 800;
text-decoration: none;
border-radius: var(--radius);
border-bottom: 4px solid var(--orange);
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 0.03em;
transition: background 0.15s;
}
.facts__cta:hover { background: var(--steel-d); }
/* ---------- CTA ---------- */
.cta {
background: var(--steel-d);
color: #fff;
margin-top: 24px;
border-top: 3px solid var(--hv);
}
.cta__in {
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 40px;
align-items: center;
padding: 44px 20px;
}
.cta__title { font-size: 2rem; margin-bottom: 12px; }
.cta__lede { color: #c2c7cd; max-width: 40ch; }
.cta__form { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field span {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #aeb4bc;
}
.field input, .field select {
font-family: inherit;
font-size: 0.95rem;
padding: 11px 12px;
border: 2px solid #3c4756;
background: #222a34;
color: #fff;
border-radius: var(--radius);
}
.field input:focus, .field select:focus {
outline: none;
border-color: var(--hv);
}
.btn {
font-family: inherit;
font-weight: 800;
font-size: 0.95rem;
text-transform: uppercase;
letter-spacing: 0.03em;
padding: 13px 18px;
border: none;
border-radius: var(--radius);
cursor: pointer;
grid-column: 1 / -1;
transition: transform 0.12s, background 0.15s;
}
.btn--hv {
background: var(--hv);
color: var(--steel-d);
border-bottom: 4px solid var(--hv-d);
}
.btn--hv:hover { background: #ffd633; }
.btn--hv:active { transform: translateY(2px); border-bottom-width: 2px; }
.btn:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.cta__note {
grid-column: 1 / -1;
font-size: 0.88rem;
font-weight: 600;
min-height: 1.1em;
}
.cta__note.is-ok { color: var(--hv); }
.cta__note.is-err { color: #ff8a5c; }
/* ---------- Footer ---------- */
.footer {
background: var(--steel);
color: #9aa0a8;
font-size: 0.82rem;
padding: 18px 0;
text-align: center;
border-top: 1px solid #3c4756;
}
/* ---------- Responsive ---------- */
@media (max-width: 880px) {
.layout { grid-template-columns: 1fr; }
.facts { position: static; }
.cta__in { grid-template-columns: 1fr; gap: 24px; }
}
@media (max-width: 520px) {
.topnav { gap: 12px; }
.topnav a:not(.topnav__cta) { display: none; }
.hero { padding: 26px 0; }
.gallery__thumbs { grid-template-columns: repeat(2, 1fr); }
.gallery__main .ph { aspect-ratio: 4 / 3; }
.cta__form { grid-template-columns: 1fr; }
.wins { flex-direction: column; }
.wins li { width: 100%; }
}(function () {
"use strict";
/* ---------- Gallery: thumbnails swap the main image ---------- */
var main = document.getElementById("mainImg");
var thumbs = document.getElementById("thumbs");
if (main && thumbs) {
thumbs.addEventListener("click", function (e) {
var btn = e.target.closest(".thumb");
if (!btn) return;
thumbs.querySelectorAll(".thumb").forEach(function (t) {
t.classList.remove("is-active");
});
btn.classList.add("is-active");
main.style.setProperty("--c1", btn.dataset.c1);
main.style.setProperty("--c2", btn.dataset.c2);
var label = btn.dataset.label || "Project image";
main.dataset.label = label;
var tag = main.querySelector(".ph__tag");
if (tag) tag.textContent = label;
});
}
/* ---------- Before / After wipe slider ---------- */
var ba = document.getElementById("ba");
var before = document.getElementById("baBefore");
var handle = document.getElementById("baHandle");
if (ba && before && handle) {
var dragging = false;
function setPct(pct) {
pct = Math.max(0, Math.min(100, pct));
before.style.width = pct + "%";
handle.style.left = pct + "%";
handle.setAttribute("aria-valuenow", Math.round(pct));
}
function pctFromEvent(clientX) {
var rect = ba.getBoundingClientRect();
return ((clientX - rect.left) / rect.width) * 100;
}
function onMove(clientX) {
if (!dragging) return;
setPct(pctFromEvent(clientX));
}
handle.addEventListener("pointerdown", function (e) {
dragging = true;
handle.setPointerCapture(e.pointerId);
e.preventDefault();
});
handle.addEventListener("pointermove", function (e) {
onMove(e.clientX);
});
handle.addEventListener("pointerup", function () {
dragging = false;
});
// Click anywhere on the strip to jump
ba.addEventListener("pointerdown", function (e) {
if (e.target === handle || handle.contains(e.target)) return;
setPct(pctFromEvent(e.clientX));
});
// Keyboard accessibility
handle.addEventListener("keydown", function (e) {
var cur = parseFloat(handle.getAttribute("aria-valuenow")) || 50;
if (e.key === "ArrowLeft") { setPct(cur - 5); e.preventDefault(); }
else if (e.key === "ArrowRight") { setPct(cur + 5); e.preventDefault(); }
else if (e.key === "Home") { setPct(0); e.preventDefault(); }
else if (e.key === "End") { setPct(100); e.preventDefault(); }
});
setPct(50);
}
/* ---------- CTA form: inline validation + faux submit ---------- */
var form = document.getElementById("ctaForm");
var msg = document.getElementById("formMsg");
if (form && msg) {
form.addEventListener("submit", function (e) {
e.preventDefault();
var name = document.getElementById("name");
var email = document.getElementById("email");
var emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim());
msg.classList.remove("is-ok", "is-err");
if (!name.value.trim()) {
msg.textContent = "Please enter your name.";
msg.classList.add("is-err");
name.focus();
return;
}
if (!emailOk) {
msg.textContent = "Please enter a valid email address.";
msg.classList.add("is-err");
email.focus();
return;
}
var who = name.value.trim().split(" ")[0];
msg.textContent = "Thanks, " + who + " — your estimate request is in. We'll be in touch within 5 business days.";
msg.classList.add("is-ok");
form.reset();
});
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Riverside Mill — Project Detail</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="topbar">
<div class="wrap topbar__in">
<a href="#" class="brand">
<span class="brand__mark" aria-hidden="true"></span>
IRONCLAD <span class="brand__sub">BUILD CO.</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#">Work</a>
<a href="#">Services</a>
<a href="#">About</a>
<a href="#" class="topnav__cta">Get a quote</a>
</nav>
</div>
</header>
<main>
<div class="hazard" role="presentation"></div>
<section class="hero">
<div class="wrap">
<p class="kicker">
<span class="status-badge status-badge--done">Completed</span>
<span class="kicker__meta">Commercial restoration · 2025</span>
</p>
<h1 class="hero__title">Riverside Mill Adaptive Reuse</h1>
<p class="hero__lede">
A derelict 1911 textile mill transformed into 18 loft workspaces and a ground-floor
market hall — keeping the brick shell, replacing everything behind it.
</p>
</div>
</section>
<section class="wrap gallery" aria-label="Project gallery">
<figure class="gallery__main">
<div class="ph" id="mainImg" data-label="Restored facade — south elevation"
style="--c1:#2b3440;--c2:#1b222b">
<span class="ph__tag">Restored facade — south elevation</span>
</div>
</figure>
<ul class="gallery__thumbs" id="thumbs">
<li>
<button class="thumb is-active" type="button"
data-label="Restored facade — south elevation"
data-c1="#2b3440" data-c2="#1b222b" aria-label="View restored facade">
<span class="ph ph--thumb" style="--c1:#2b3440;--c2:#1b222b"></span>
</button>
</li>
<li>
<button class="thumb" type="button"
data-label="Market hall interior"
data-c1="#e8642a" data-c2="#b04518" aria-label="View market hall interior">
<span class="ph ph--thumb" style="--c1:#e8642a;--c2:#b04518"></span>
</button>
</li>
<li>
<button class="thumb" type="button"
data-label="Steel mezzanine framing"
data-c1="#4a5564" data-c2="#2b3440" aria-label="View steel mezzanine framing">
<span class="ph ph--thumb" style="--c1:#4a5564;--c2:#2b3440"></span>
</button>
</li>
<li>
<button class="thumb" type="button"
data-label="Loft workspace 04"
data-c1="#f5c518" data-c2="#c79a00" aria-label="View loft workspace">
<span class="ph ph--thumb" style="--c1:#f5c518;--c2:#c79a00"></span>
</button>
</li>
</ul>
</section>
<div class="wrap layout">
<div class="layout__main">
<section class="block">
<h2 class="block__title">The challenge</h2>
<p>
The mill had stood empty for 22 years. Two-thirds of the timber roof structure had
failed, the river-facing wall was leaning 4° out of plumb, and the entire site
sat inside a designated flood zone. The brief called for keeping the listed brick
shell while inserting a modern, code-compliant building inside it — on a fixed
municipal grant deadline.
</p>
</section>
<section class="block">
<h2 class="block__title">Our solution</h2>
<p>
We stabilised the leaning wall with 26 helical ground anchors, then dropped a fully
independent steel frame inside the shell so the historic masonry carries no new load.
Mass-timber floor plates and a raised market-hall slab lift the occupied levels above
the 100-year flood line. Phased demolition let the market hall open six weeks before
the upper lofts, getting tenant revenue flowing early.
</p>
<ul class="wins">
<li><strong>0</strong> days lost to flooding during the build</li>
<li><strong>91%</strong> of original brick retained</li>
<li><strong>2 wks</strong> ahead of the grant deadline</li>
</ul>
</section>
<section class="block">
<h2 class="block__title">Materials used</h2>
<ul class="materials" aria-label="Materials list">
<li>Reclaimed brick</li>
<li>Hot-rolled steel I-beams</li>
<li>CLT mass-timber panels</li>
<li>Board-formed concrete</li>
<li>Cor-ten weathering steel</li>
<li>Triple-glazed steel windows</li>
<li>Polished concrete floors</li>
<li>Galvanised handrail</li>
</ul>
</section>
<section class="block">
<h2 class="block__title">Before & after</h2>
<p class="block__hint">Drag the handle to wipe between demolition and the finished build.</p>
<div class="ba" id="ba">
<div class="ba__after ph" style="--c1:#2b3440;--c2:#1b222b">
<span class="ba__lbl ba__lbl--after">After</span>
</div>
<div class="ba__before ph" id="baBefore" style="--c1:#5a5046;--c2:#39322c">
<span class="ba__lbl ba__lbl--before">Before</span>
</div>
<div class="ba__handle" id="baHandle" role="slider" tabindex="0"
aria-label="Before and after wipe" aria-valuemin="0" aria-valuemax="100"
aria-valuenow="50">
<span class="ba__grip" aria-hidden="true">↔</span>
</div>
</div>
</section>
</div>
<aside class="layout__side" aria-label="Project facts">
<div class="facts">
<h2 class="facts__title">Project facts</h2>
<dl class="facts__grid">
<div class="fact">
<dt>Scope</dt>
<dd>Adaptive reuse & full fit-out</dd>
</div>
<div class="fact">
<dt>Duration</dt>
<dd>14 months</dd>
</div>
<div class="fact">
<dt>Budget range</dt>
<dd>$6.2M – $6.8M</dd>
</div>
<div class="fact">
<dt>Location</dt>
<dd>Lowell, MA</dd>
</div>
<div class="fact">
<dt>Floor area</dt>
<dd>38,400 sq ft</dd>
</div>
<div class="fact">
<dt>Crew size</dt>
<dd>Up to 42 on site</dd>
</div>
</dl>
<a href="#cta" class="facts__cta">Build something like this →</a>
</div>
</aside>
</div>
<section class="cta" id="cta">
<div class="wrap cta__in">
<div>
<h2 class="cta__title">Start your project</h2>
<p class="cta__lede">
Tell us about your site and timeline. We’ll send a scoped estimate within five
business days — no obligation.
</p>
</div>
<form class="cta__form" id="ctaForm" novalidate>
<label class="field">
<span>Your name</span>
<input type="text" name="name" id="name" autocomplete="name" required />
</label>
<label class="field">
<span>Email</span>
<input type="email" name="email" id="email" autocomplete="email" required />
</label>
<label class="field">
<span>Project type</span>
<select name="type" id="type">
<option>New build</option>
<option>Renovation</option>
<option>Adaptive reuse</option>
<option>Extension</option>
</select>
</label>
<button type="submit" class="btn btn--hv">Request estimate</button>
<p class="cta__note" id="formMsg" role="status" aria-live="polite"></p>
</form>
</div>
</section>
</main>
<footer class="footer">
<div class="wrap">
© 2026 Ironclad Build Co. · Licensed & insured · MA-CSL #104872
</div>
</footer>
<script src="script.js"></script>
</body>
</html>Project Detail
A rugged, trades-grade case-study page for a construction firm. It leads with a hi-vis status banner and a hero gallery whose thumbnails swap the main image, then lays out the hard facts — scope, duration, budget range and location — in a sturdy bordered fact grid.
Below the facts a challenge-and-solution narrative explains the brief and how the crew delivered, followed by a tagged list of materials used. A draggable before/after teaser lets visitors wipe between the demolition and the finished build, and the page closes with a steel-and-hi-vis start-your-project call to action.
Everything is plain HTML, CSS custom properties and vanilla JavaScript — no frameworks, no build step. Swap the placeholder copy and imagery for real project data and drop it straight into a portfolio site.
Illustrative UI only — not for real construction or engineering use.