Construction — Project Portfolio
A rugged construction project gallery with a filterable masonry grid of build cards showing type tag, location and year over gradient placeholders, plus category filters for residential, commercial and renovation and a click-to-open lightbox detail overlay with scope, value and timeline.
MCP
Code
:root {
--hv: #f5c518;
--steel: #2b3440;
--steel-d: #1b222b;
--bg: #eceae6;
--ink: #1a1d22;
--muted: #666c75;
--orange: #e8642a;
--white: #ffffff;
--line: #d4d1cb;
--radius: 4px;
--shadow: 0 10px 28px rgba(27, 34, 43, 0.18);
--font: 'Inter', system-ui, -apple-system, sans-serif;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: var(--font);
color: var(--ink);
background: var(--bg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: 0 20px 64px;
}
/* ---------- Masthead ---------- */
.masthead {
position: relative;
margin: 0 -20px 36px;
padding: 0;
background: var(--steel);
color: var(--white);
overflow: hidden;
}
.hazard {
height: 12px;
background: repeating-linear-gradient(
-45deg,
var(--hv) 0 22px,
var(--steel-d) 22px 44px
);
}
.masthead__inner {
padding: 40px 28px 44px;
}
.kicker {
margin: 0 0 14px;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--hv);
}
.masthead h1 {
margin: 0 0 18px;
font-size: clamp(2.6rem, 9vw, 4.8rem);
font-weight: 900;
line-height: 0.92;
letter-spacing: -0.02em;
text-transform: uppercase;
}
.lede {
margin: 0;
max-width: 46ch;
color: #c7ccd2;
font-size: 1.02rem;
}
/* ---------- Toolbar ---------- */
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 14px;
padding: 0 0 22px;
margin-bottom: 22px;
border-bottom: 3px solid var(--steel);
}
.filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
font-family: inherit;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--steel);
background: var(--white);
border: 2px solid var(--steel);
border-radius: var(--radius);
padding: 9px 16px;
cursor: pointer;
transition: background 0.15s, color 0.15s, transform 0.05s;
}
.chip:hover {
background: #dedbd4;
}
.chip:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.chip:active {
transform: translateY(1px);
}
.chip.is-active {
background: var(--steel);
color: var(--hv);
}
.count {
margin: 0;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--muted);
}
/* ---------- Grid ---------- */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-auto-rows: 10px;
gap: 18px;
}
.card {
display: flex;
flex-direction: column;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
overflow: hidden;
cursor: pointer;
text-align: left;
font-family: inherit;
color: inherit;
padding: 0;
transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow);
border-color: var(--steel);
}
.card:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
}
.card__media {
position: relative;
display: flex;
align-items: flex-end;
padding: 14px;
min-height: 150px;
color: var(--white);
}
.card--tall .card__media {
min-height: 230px;
}
.card__media::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.45) 100%);
}
.card__type {
position: absolute;
top: 12px;
left: 12px;
z-index: 2;
font-size: 0.66rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
background: var(--hv);
color: var(--steel-d);
padding: 4px 9px;
border-radius: 3px;
}
.card__placeholder {
position: relative;
z-index: 2;
font-size: 1.18rem;
font-weight: 800;
letter-spacing: -0.01em;
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.4);
}
.card__body {
padding: 14px 16px 16px;
flex: 1;
}
.card__title {
margin: 0 0 4px;
font-size: 1.05rem;
font-weight: 800;
letter-spacing: -0.01em;
}
.card__meta {
margin: 0;
font-size: 0.84rem;
color: var(--muted);
display: flex;
align-items: center;
gap: 8px;
}
.card__year {
font-weight: 700;
color: var(--steel);
}
.card__dot {
color: var(--line);
}
.empty {
grid-column: 1 / -1;
text-align: center;
padding: 48px 16px;
color: var(--muted);
font-weight: 600;
}
/* ---------- Footer ---------- */
.foot {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 8px;
margin-top: 40px;
padding-top: 18px;
border-top: 1px solid var(--line);
font-size: 0.78rem;
font-weight: 600;
color: var(--muted);
}
/* ---------- Lightbox ---------- */
.lightbox {
position: fixed;
inset: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.lightbox[hidden] {
display: none;
}
.lightbox__backdrop {
position: absolute;
inset: 0;
background: rgba(27, 34, 43, 0.78);
backdrop-filter: blur(2px);
}
.lightbox__panel {
position: relative;
z-index: 1;
width: 100%;
max-width: 560px;
max-height: 90vh;
overflow: auto;
background: var(--white);
border-radius: var(--radius);
box-shadow: var(--shadow);
animation: pop 0.18s ease-out;
}
@keyframes pop {
from {
opacity: 0;
transform: translateY(12px) scale(0.98);
}
to {
opacity: 1;
transform: none;
}
}
.lightbox__close {
position: absolute;
top: 10px;
right: 12px;
z-index: 3;
width: 38px;
height: 38px;
font-size: 1.6rem;
line-height: 1;
color: var(--white);
background: rgba(27, 34, 43, 0.55);
border: none;
border-radius: 50%;
cursor: pointer;
transition: background 0.15s;
}
.lightbox__close:hover {
background: var(--orange);
}
.lightbox__close:focus-visible {
outline: 3px solid var(--hv);
outline-offset: 2px;
}
.lightbox__hero {
position: relative;
display: flex;
align-items: flex-end;
min-height: 200px;
padding: 18px;
color: var(--white);
}
.lightbox__hero::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 40%, rgba(0, 0, 0, 0.5) 100%);
}
.lightbox__type {
position: absolute;
top: 16px;
left: 18px;
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
background: var(--hv);
color: var(--steel-d);
padding: 4px 10px;
border-radius: 3px;
}
.lightbox__label {
position: relative;
z-index: 1;
font-size: 1.4rem;
font-weight: 800;
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.45);
}
.lightbox__body {
padding: 22px 24px 26px;
}
.lightbox__body h2 {
margin: 0 0 4px;
font-size: 1.5rem;
font-weight: 900;
letter-spacing: -0.01em;
text-transform: uppercase;
}
.lightbox__meta {
margin: 0 0 18px;
font-size: 0.88rem;
font-weight: 600;
color: var(--muted);
}
.specs {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
margin: 0 0 18px;
background: var(--line);
border: 1px solid var(--line);
border-radius: var(--radius);
overflow: hidden;
}
.specs div {
background: var(--white);
padding: 12px 14px;
}
.specs dt {
font-size: 0.66rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
margin: 0 0 3px;
}
.specs dd {
margin: 0;
font-size: 0.98rem;
font-weight: 700;
color: var(--steel);
}
.lightbox__desc {
margin: 0;
font-size: 0.95rem;
color: var(--ink);
}
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.page {
padding: 0 14px 48px;
}
.masthead {
margin: 0 -14px 28px;
}
.masthead__inner {
padding: 28px 18px 32px;
}
.toolbar {
align-items: flex-start;
flex-direction: column;
}
.grid {
grid-template-columns: 1fr;
}
.specs {
grid-template-columns: 1fr;
}
}(function () {
'use strict';
var PROJECTS = [
{
id: 'cedar-hollow',
name: 'Cedar Hollow Residence',
type: 'Custom Home',
category: 'residential',
location: 'Bainbridge Island, WA',
year: 2024,
tall: true,
grad: 'linear-gradient(135deg, #3a4a5c, #1b222b)',
scope: 'New build, 4,200 sq ft',
value: '$1.9M',
duration: '14 months',
desc: 'A timber-and-glass waterfront home with exposed Douglas fir beams, radiant-floor heating and a cantilevered deck overlooking Puget Sound.'
},
{
id: 'harbor-point',
name: 'Harbor Point Office Tower',
type: 'Commercial',
category: 'commercial',
location: 'Tacoma, WA',
year: 2023,
tall: false,
grad: 'linear-gradient(135deg, #2b3440, #4a5562)',
scope: 'Ground-up, 7 storeys',
value: '$24.6M',
duration: '26 months',
desc: 'A Class-A office tower with steel-frame construction, a curtain-wall facade and two levels of underground structured parking.'
},
{
id: 'maple-street',
name: 'Maple Street Brownstone',
type: 'Gut Renovation',
category: 'renovation',
location: 'Portland, OR',
year: 2024,
tall: false,
grad: 'linear-gradient(135deg, #7a5c3a, #3d2f1e)',
scope: 'Full interior rebuild',
value: '$640K',
duration: '9 months',
desc: 'A 1912 brownstone taken back to the studs — new electrical, plumbing and structural reinforcement while preserving the original brick and millwork.'
},
{
id: 'ridgeline',
name: 'Ridgeline Townhomes',
type: 'Multi-Family',
category: 'residential',
location: 'Bellevue, WA',
year: 2022,
tall: true,
grad: 'linear-gradient(135deg, #4a5562, #2b3440)',
scope: '12 units, 3 buildings',
value: '$8.3M',
duration: '18 months',
desc: 'A cluster of modern townhomes with rooftop terraces, shared courtyards and pre-wired EV charging in every garage.'
},
{
id: 'foundry-market',
name: 'The Foundry Market Hall',
type: 'Adaptive Reuse',
category: 'commercial',
location: 'Spokane, WA',
year: 2023,
tall: false,
grad: 'linear-gradient(135deg, #e8642a, #8a3414)',
scope: 'Warehouse conversion',
value: '$5.1M',
duration: '15 months',
desc: 'A 1940s iron foundry reborn as a food hall — new mezzanine, kitchen infrastructure for 18 vendors and seismic retrofit of the masonry shell.'
},
{
id: 'lakeside-kitchen',
name: 'Lakeside Kitchen Remodel',
type: 'Renovation',
category: 'renovation',
location: 'Coeur d’Alene, ID',
year: 2025,
tall: false,
grad: 'linear-gradient(135deg, #5c6b5a, #2e3a2c)',
scope: 'Kitchen + great room',
value: '$185K',
duration: '4 months',
desc: 'Wall removal to open a galley kitchen into the great room, with a new structural beam, quartz island and floor-to-ceiling pantry.'
},
{
id: 'summit-clinic',
name: 'Summit Medical Clinic',
type: 'Commercial',
category: 'commercial',
location: 'Eugene, OR',
year: 2024,
tall: true,
grad: 'linear-gradient(135deg, #3a5c5c, #1e3434)',
scope: 'Tenant build-out',
value: '$3.7M',
duration: '11 months',
desc: 'A two-floor outpatient clinic fit-out with lead-lined imaging rooms, medical-gas lines and a glass-walled reception atrium.'
},
{
id: 'orchard-farmhouse',
name: 'Orchard Lane Farmhouse',
type: 'Custom Home',
category: 'residential',
location: 'Hood River, OR',
year: 2025,
tall: false,
grad: 'linear-gradient(135deg, #6b5c3a, #3a3020)',
scope: 'New build, 3,100 sq ft',
value: '$1.3M',
duration: '12 months',
desc: 'A modern farmhouse with board-and-batten siding, a wraparound porch and a detached three-bay workshop on a five-acre orchard.'
}
];
var grid = document.getElementById('grid');
var count = document.getElementById('count');
var chips = Array.prototype.slice.call(document.querySelectorAll('.chip'));
var lightbox = document.getElementById('lightbox');
var lbClose = document.getElementById('lbClose');
var lastFocused = null;
var activeFilter = 'all';
function spanFor(p) {
// approximate masonry row span: 19 base rows, 8 extra for tall cards
return p.tall ? 27 : 19;
}
function render(filter) {
grid.innerHTML = '';
var shown = PROJECTS.filter(function (p) {
return filter === 'all' || p.category === filter;
});
if (!shown.length) {
var empty = document.createElement('p');
empty.className = 'empty';
empty.textContent = 'No projects in this category yet.';
grid.appendChild(empty);
}
shown.forEach(function (p) {
var card = document.createElement('button');
card.type = 'button';
card.className = 'card' + (p.tall ? ' card--tall' : '');
card.style.gridRowEnd = 'span ' + spanFor(p);
card.setAttribute('aria-label', p.name + ', ' + p.type + ', ' + p.location + ', ' + p.year);
card.innerHTML =
'<span class="card__media" style="background:' + p.grad + '">' +
'<span class="card__type">' + p.type + '</span>' +
'<span class="card__placeholder">' + p.name + '</span>' +
'</span>' +
'<span class="card__body">' +
'<span class="card__title" style="display:block">' + p.name + '</span>' +
'<span class="card__meta">' + p.location +
' <span class="card__dot">•</span> ' +
'<span class="card__year">' + p.year + '</span></span>' +
'</span>';
card.addEventListener('click', function () {
openLightbox(p);
});
grid.appendChild(card);
});
var n = shown.length;
count.textContent = n + (n === 1 ? ' project' : ' projects');
}
function setFilter(filter, btn) {
activeFilter = filter;
chips.forEach(function (c) {
var on = c === btn;
c.classList.toggle('is-active', on);
c.setAttribute('aria-pressed', on ? 'true' : 'false');
});
render(filter);
}
chips.forEach(function (c) {
c.addEventListener('click', function () {
setFilter(c.getAttribute('data-filter'), c);
});
});
// ---------- Lightbox ----------
function openLightbox(p) {
lastFocused = document.activeElement;
document.getElementById('lbHero').style.background = p.grad;
document.getElementById('lbType').textContent = p.type;
document.getElementById('lbLabel').textContent = p.name;
document.getElementById('lb-title').textContent = p.name;
document.getElementById('lbMeta').textContent = p.location + ' · Completed ' + p.year;
document.getElementById('lbDesc').textContent = p.desc;
var specs = document.getElementById('lbSpecs');
specs.innerHTML =
'<div><dt>Scope</dt><dd>' + p.scope + '</dd></div>' +
'<div><dt>Contract Value</dt><dd>' + p.value + '</dd></div>' +
'<div><dt>Duration</dt><dd>' + p.duration + '</dd></div>' +
'<div><dt>Category</dt><dd style="text-transform:capitalize">' + p.category + '</dd></div>';
lightbox.hidden = false;
document.body.style.overflow = 'hidden';
lbClose.focus();
}
function closeLightbox() {
lightbox.hidden = true;
document.body.style.overflow = '';
if (lastFocused && typeof lastFocused.focus === 'function') {
lastFocused.focus();
}
}
lightbox.addEventListener('click', function (e) {
if (e.target.hasAttribute('data-close')) {
closeLightbox();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !lightbox.hidden) {
closeLightbox();
}
});
render('all');
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Project Portfolio — Granite Ridge Construction</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>
<div class="page">
<header class="masthead">
<div class="hazard" aria-hidden="true"></div>
<div class="masthead__inner">
<p class="kicker">Granite Ridge Construction Co. · Est. 1998</p>
<h1>Project<br />Portfolio</h1>
<p class="lede">
Forty-plus years of crews on site across the Pacific Northwest — from custom homes to
ground-up commercial builds and full structural renovations.
</p>
</div>
</header>
<main class="content">
<div class="toolbar" role="region" aria-label="Filter projects">
<div class="filters" role="group" aria-label="Project category">
<button class="chip is-active" data-filter="all" aria-pressed="true">All</button>
<button class="chip" data-filter="residential" aria-pressed="false">Residential</button>
<button class="chip" data-filter="commercial" aria-pressed="false">Commercial</button>
<button class="chip" data-filter="renovation" aria-pressed="false">Renovation</button>
</div>
<p class="count" id="count" aria-live="polite">8 projects</p>
</div>
<section class="grid" id="grid" aria-label="Project grid">
<!-- cards injected by script.js -->
</section>
</main>
<footer class="foot">
<span>Granite Ridge Construction Co.</span>
<span>License #WA-GRC-882041 · Bonded & Insured</span>
</footer>
</div>
<!-- Lightbox detail overlay -->
<div class="lightbox" id="lightbox" hidden>
<div class="lightbox__backdrop" data-close></div>
<div class="lightbox__panel" role="dialog" aria-modal="true" aria-labelledby="lb-title">
<button class="lightbox__close" id="lbClose" aria-label="Close project detail" data-close>×</button>
<div class="lightbox__hero" id="lbHero">
<span class="lightbox__type" id="lbType"></span>
<span class="lightbox__label" id="lbLabel"></span>
</div>
<div class="lightbox__body">
<h2 id="lb-title"></h2>
<p class="lightbox__meta" id="lbMeta"></p>
<dl class="specs" id="lbSpecs"></dl>
<p class="lightbox__desc" id="lbDesc"></p>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Project Portfolio
A hard-hat-ready portfolio page for a general contractor. Chunky condensed headings, steel and concrete tones and a stripe of hi-vis yellow set the rugged trades tone. A filter bar lets visitors narrow the grid by Residential, Commercial or Renovation, with a live project count.
Each card uses a gradient placeholder labelled with the build name, a type tag, the location and the completion year. Clicking any card opens a lightbox-style detail overlay showing the full scope, contract value, duration and a short description, dismissible by backdrop click, close button or the Escape key.
All filtering, the live count and the lightbox are handled with plain vanilla JavaScript — no frameworks, no build step. Drop the three snippets into any static page and adjust the data.
Illustrative UI only — not for real construction bidding or contract use.