Construction — Build Progress Tracker
A jobsite build-progress dashboard with an overall completion gauge, a six-stage phase stepper from foundation to final, per-phase progress bars with scheduled dates and crews, a dated photo-update feed, and a list of upcoming milestones with live status filtering.
MCP
Code
:root {
--hv: #f5c518;
--steel: #2b3440;
--steel-d: #1b222b;
--bg: #eceae6;
--ink: #1a1d22;
--muted: #666c75;
--orange: #e8642a;
--white: #ffffff;
--line: #d8d6d0;
--radius: 10px;
--shadow: 0 1px 0 rgba(27, 34, 43, 0.04), 0 6px 22px rgba(27, 34, 43, 0.08);
--font: 'Inter', system-ui, -apple-system, sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: var(--font);
background:
repeating-linear-gradient(135deg, rgba(43, 52, 64, 0.025) 0 14px, transparent 14px 28px),
var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
padding: 28px 18px 48px;
}
.tracker {
max-width: 1020px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 20px;
}
/* ---- Project header ---- */
.proj {
background: var(--steel);
color: var(--white);
border-radius: var(--radius);
padding: 26px 28px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
position: relative;
overflow: hidden;
box-shadow: var(--shadow);
}
.proj::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 6px;
background: repeating-linear-gradient(135deg, var(--hv) 0 18px, var(--steel-d) 18px 36px);
}
.proj__tag {
margin: 4px 0 6px;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--hv);
}
.proj__title {
margin: 0;
font-size: clamp(1.5rem, 3.4vw, 2.1rem);
font-weight: 900;
letter-spacing: -0.02em;
line-height: 1.05;
}
.proj__addr {
margin: 8px 0 14px;
color: #b8c0c9;
font-size: 0.92rem;
}
.proj__meta {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 8px 22px;
font-size: 0.86rem;
}
.proj__meta li { color: #e7eaee; font-weight: 600; }
.proj__meta span {
display: block;
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #8a93a0;
}
/* ---- Gauge ---- */
.gauge { position: relative; flex: 0 0 auto; width: 120px; height: 120px; }
.gauge__svg { transform: rotate(-90deg); }
.gauge__track { fill: none; stroke: rgba(255, 255, 255, 0.12); stroke-width: 11; }
.gauge__fill {
fill: none;
stroke: var(--hv);
stroke-width: 11;
stroke-linecap: round;
stroke-dasharray: 326.7;
stroke-dashoffset: 326.7;
transition: stroke-dashoffset 1.2s cubic-bezier(0.22, 1, 0.36, 1);
}
.gauge__label {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--white);
}
.gauge__label strong { font-size: 1.6rem; font-weight: 900; letter-spacing: -0.02em; }
.gauge__label span {
font-size: 0.62rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #9aa3ae;
}
/* ---- Cards ---- */
.card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 22px 24px;
box-shadow: var(--shadow);
}
.card__head { margin-bottom: 18px; }
.card__title {
margin: 0;
font-size: 1.18rem;
font-weight: 800;
letter-spacing: -0.01em;
text-transform: uppercase;
}
.card__sub { margin: 3px 0 0; font-size: 0.84rem; color: var(--muted); }
/* ---- Stepper ---- */
.stepper {
list-style: none;
margin: 0 0 24px;
padding: 0;
display: flex;
gap: 6px;
counter-reset: none;
}
.step {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
position: relative;
text-align: center;
}
.step::after {
content: "";
position: absolute;
top: 15px;
left: 50%;
width: 100%;
height: 3px;
background: var(--line);
z-index: 0;
}
.step:last-child::after { display: none; }
.step.is-done::after { background: var(--steel); }
.step__dot {
position: relative;
z-index: 1;
width: 32px;
height: 32px;
border-radius: 50%;
display: grid;
place-items: center;
background: var(--bg);
border: 2px solid var(--line);
color: var(--muted);
font-weight: 800;
font-size: 0.86rem;
}
.step.is-done .step__dot { background: var(--steel); border-color: var(--steel); color: var(--hv); }
.step.is-active .step__dot {
background: var(--hv);
border-color: var(--steel-d);
color: var(--steel-d);
box-shadow: 0 0 0 4px rgba(245, 197, 24, 0.28);
}
.step__name {
font-size: 0.74rem;
font-weight: 700;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--muted);
}
.step.is-done .step__name,
.step.is-active .step__name { color: var(--ink); }
/* ---- Phases ---- */
.phases { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.phase {
border: 1px solid var(--line);
border-left: 4px solid var(--line);
border-radius: 8px;
padding: 13px 16px;
background: #fafaf8;
}
.phase.is-active {
border-left-color: var(--hv);
background:
repeating-linear-gradient(135deg, rgba(245, 197, 24, 0.07) 0 10px, transparent 10px 20px),
var(--white);
}
.phase__top { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 9px; }
.phase__name { margin: 0; font-size: 0.98rem; font-weight: 800; }
.phase__name small { display: block; font-size: 0.68rem; font-weight: 600; color: var(--muted); text-transform: none; letter-spacing: 0; }
.phase__foot { margin: 8px 0 0; font-size: 0.78rem; color: var(--muted); }
.phase__foot span { font-weight: 800; color: var(--ink); }
.bar { height: 9px; border-radius: 99px; background: var(--line); overflow: hidden; }
.bar__fill {
display: block;
height: 100%;
width: 0;
border-radius: 99px;
background: linear-gradient(90deg, var(--steel), var(--steel-d));
transition: width 1s cubic-bezier(0.22, 1, 0.36, 1);
}
.phase.is-active .bar__fill { background: linear-gradient(90deg, var(--orange), var(--hv)); }
/* ---- Badges ---- */
.badge {
font-size: 0.66rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 4px 9px;
border-radius: 99px;
background: var(--bg);
color: var(--muted);
border: 1px solid var(--line);
white-space: nowrap;
}
.badge--done { background: var(--steel); color: var(--hv); border-color: var(--steel); }
.badge--active { background: var(--orange); color: var(--white); border-color: var(--orange); }
/* ---- Grid: feed + milestones ---- */
.grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 20px; }
/* ---- Photo feed ---- */
.feed { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.post { display: flex; gap: 14px; }
.post__thumb {
flex: 0 0 92px;
height: 70px;
border-radius: 8px;
background: linear-gradient(135deg, var(--c1), var(--c2));
position: relative;
overflow: hidden;
display: grid;
place-items: end start;
}
.post__thumb::before {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(135deg, rgba(245, 197, 24, 0.16) 0 8px, transparent 8px 16px);
opacity: 0.5;
}
.post__thumb::after {
content: attr(data-label);
position: relative;
z-index: 1;
font-size: 0.6rem;
font-weight: 700;
letter-spacing: 0.04em;
color: rgba(255, 255, 255, 0.92);
padding: 5px 7px;
text-transform: uppercase;
}
.post__cap { margin: 0; font-size: 0.88rem; color: var(--ink); }
.post__meta { margin: 5px 0 0; font-size: 0.74rem; color: var(--muted); }
.post__meta strong { color: var(--ink); }
/* ---- Milestones ---- */
.filters { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 16px; }
.chip {
font-family: inherit;
font-size: 0.74rem;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
padding: 6px 13px;
border-radius: 99px;
border: 1.5px solid var(--line);
background: var(--white);
color: var(--muted);
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.08s;
}
.chip:hover { border-color: var(--steel); color: var(--ink); }
.chip:active { transform: translateY(1px); }
.chip.is-on { background: var(--steel); border-color: var(--steel); color: var(--hv); }
.chip:focus-visible { outline: 3px solid rgba(245, 197, 24, 0.6); outline-offset: 2px; }
.ms { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.mile {
display: flex;
align-items: center;
gap: 13px;
padding: 11px 13px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fafaf8;
transition: box-shadow 0.15s, transform 0.12s, border-color 0.15s;
}
.mile:hover { transform: translateX(2px); border-color: var(--steel); box-shadow: var(--shadow); }
.mile__date {
flex: 0 0 auto;
width: 44px;
text-align: center;
background: var(--steel);
color: var(--white);
border-radius: 6px;
padding: 6px 0;
line-height: 1;
}
.mile__date strong { display: block; font-size: 1.1rem; font-weight: 900; }
.mile__date span { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.1em; color: var(--hv); }
.mile__body { flex: 1; min-width: 0; }
.mile__name { margin: 0; font-size: 0.9rem; font-weight: 700; }
.mile__sub { margin: 2px 0 0; font-size: 0.76rem; color: var(--muted); }
.tag {
flex: 0 0 auto;
font-size: 0.62rem;
font-weight: 800;
letter-spacing: 0.05em;
text-transform: uppercase;
padding: 3px 8px;
border-radius: 99px;
}
.tag--inspection { background: rgba(43, 52, 64, 0.1); color: var(--steel); }
.tag--delivery { background: rgba(245, 197, 24, 0.22); color: #8a6d00; }
.tag--pour { background: rgba(232, 100, 42, 0.16); color: var(--orange); }
.ms__empty { margin: 14px 0 0; font-size: 0.86rem; color: var(--muted); text-align: center; }
/* ---- Footer ---- */
.foot { text-align: center; font-size: 0.76rem; color: var(--muted); padding-top: 6px; }
/* ---- Responsive ---- */
@media (max-width: 820px) {
.grid { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
body { padding: 18px 12px 36px; }
.proj { flex-direction: column; align-items: flex-start; padding: 22px 20px; }
.gauge { align-self: center; }
.stepper { overflow-x: auto; gap: 14px; padding-bottom: 6px; }
.step { flex: 0 0 64px; }
.step::after { display: none; }
.phase__top { flex-wrap: wrap; }
.post__thumb { flex-basis: 74px; height: 60px; }
}(function () {
'use strict';
/* ---- Overall completion gauge ----
Computed as the average of the per-phase progress bars so the header
stays in sync with the phase list. */
var CIRC = 2 * Math.PI * 52; // matches r="52" in the SVG
function readPhasePercents() {
var fills = document.querySelectorAll('.phase .bar__fill');
var vals = [];
fills.forEach(function (el) {
var raw = (el.style.getPropertyValue('--pct') || '0').trim();
vals.push(parseFloat(raw) || 0);
});
return vals;
}
function average(arr) {
if (!arr.length) return 0;
var sum = arr.reduce(function (a, b) { return a + b; }, 0);
return Math.round(sum / arr.length);
}
function animateGauge(target) {
var fill = document.getElementById('gaugeFill');
var label = document.getElementById('gaugePct');
if (!fill || !label) return;
fill.style.strokeDasharray = CIRC;
fill.style.strokeDashoffset = CIRC;
// Force reflow so the transition runs from the initial value.
void fill.getBoundingClientRect();
var offset = CIRC * (1 - target / 100);
fill.style.strokeDashoffset = offset;
// Count up the numeric label over the same window.
var start = performance.now();
var dur = 1200;
function tick(now) {
var t = Math.min((now - start) / dur, 1);
var eased = 1 - Math.pow(1 - t, 3);
label.textContent = Math.round(target * eased) + '%';
if (t < 1) requestAnimationFrame(tick);
else label.textContent = target + '%';
}
requestAnimationFrame(tick);
}
/* ---- Animate the per-phase bars in from zero ---- */
function animateBars() {
document.querySelectorAll('.phase .bar__fill').forEach(function (el) {
var pct = (el.style.getPropertyValue('--pct') || '0%').trim();
el.style.width = '0';
void el.getBoundingClientRect();
el.style.width = pct;
});
}
/* ---- Milestone filtering ---- */
function initFilters() {
var chips = document.querySelectorAll('.chip');
var items = Array.prototype.slice.call(document.querySelectorAll('.mile'));
var count = document.getElementById('msCount');
var empty = document.getElementById('msEmpty');
function apply(filter) {
var shown = 0;
items.forEach(function (li) {
var match = filter === 'all' || li.getAttribute('data-type') === filter;
li.style.display = match ? '' : 'none';
if (match) shown++;
});
if (count) count.textContent = shown + (shown === 1 ? ' ahead' : ' ahead');
if (empty) empty.hidden = shown !== 0;
}
chips.forEach(function (chip) {
chip.addEventListener('click', function () {
chips.forEach(function (c) {
c.classList.remove('is-on');
c.removeAttribute('aria-pressed');
});
chip.classList.add('is-on');
chip.setAttribute('aria-pressed', 'true');
apply(chip.getAttribute('data-filter'));
});
});
}
function init() {
var pct = average(readPhasePercents());
// Respect reduced-motion preferences.
var reduce = window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) {
var fill = document.getElementById('gaugeFill');
var label = document.getElementById('gaugePct');
if (fill) {
fill.style.strokeDasharray = CIRC;
fill.style.strokeDashoffset = CIRC * (1 - pct / 100);
}
if (label) label.textContent = pct + '%';
document.querySelectorAll('.phase .bar__fill').forEach(function (el) {
el.style.width = (el.style.getPropertyValue('--pct') || '0%').trim();
});
} else {
// Defer slightly so the entrance reads as motion, not a static render.
requestAnimationFrame(function () {
animateGauge(pct);
animateBars();
});
}
initFilters();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Build Progress Tracker</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>
<main class="tracker" aria-labelledby="proj-title">
<!-- Project header -->
<header class="proj">
<div class="proj__info">
<p class="proj__tag">Project #RV-2418 · Commercial</p>
<h1 id="proj-title" class="proj__title">Riverside Mixed-Use — Block C</h1>
<p class="proj__addr">
<span aria-hidden="true">📍</span> 4820 Wharf Road, Portland, OR 97203
</p>
<ul class="proj__meta">
<li><span>GC</span> Hartwell & Sons</li>
<li><span>PM</span> Dana Okafor</li>
<li><span>Start</span> Mar 3, 2026</li>
<li><span>Target</span> Nov 14, 2026</li>
</ul>
</div>
<div class="gauge" role="img" aria-label="Overall completion 58 percent">
<svg class="gauge__svg" viewBox="0 0 120 120" width="120" height="120">
<circle class="gauge__track" cx="60" cy="60" r="52" />
<circle class="gauge__fill" cx="60" cy="60" r="52" id="gaugeFill" />
</svg>
<div class="gauge__label">
<strong id="gaugePct">0%</strong>
<span>complete</span>
</div>
</div>
</header>
<!-- Phase stepper -->
<section class="card" aria-labelledby="stepper-h">
<div class="card__head">
<h2 id="stepper-h" class="card__title">Build phases</h2>
<p class="card__sub">6 stages · foundation to handover</p>
</div>
<ol class="stepper" id="stepper">
<li class="step is-done">
<span class="step__dot">✓</span>
<span class="step__name">Foundation</span>
</li>
<li class="step is-done">
<span class="step__dot">✓</span>
<span class="step__name">Framing</span>
</li>
<li class="step is-active" aria-current="step">
<span class="step__dot">3</span>
<span class="step__name">MEP</span>
</li>
<li class="step">
<span class="step__dot">4</span>
<span class="step__name">Drywall</span>
</li>
<li class="step">
<span class="step__dot">5</span>
<span class="step__name">Finish</span>
</li>
<li class="step">
<span class="step__dot">6</span>
<span class="step__name">Final</span>
</li>
</ol>
<!-- Per-phase progress -->
<ul class="phases">
<li class="phase">
<div class="phase__top">
<h3 class="phase__name">Foundation</h3>
<span class="badge badge--done">Complete</span>
</div>
<div class="bar"><span class="bar__fill" style="--pct:100%"></span></div>
<p class="phase__foot"><span>100%</span> · Mar 3 – Apr 18 · Granite Earthworks</p>
</li>
<li class="phase">
<div class="phase__top">
<h3 class="phase__name">Framing</h3>
<span class="badge badge--done">Complete</span>
</div>
<div class="bar"><span class="bar__fill" style="--pct:100%"></span></div>
<p class="phase__foot"><span>100%</span> · Apr 21 – Jun 6 · Hartwell Carpentry</p>
</li>
<li class="phase is-active">
<div class="phase__top">
<h3 class="phase__name">MEP <small>Mechanical · Electrical · Plumbing</small></h3>
<span class="badge badge--active">In progress</span>
</div>
<div class="bar"><span class="bar__fill" style="--pct:64%"></span></div>
<p class="phase__foot"><span>64%</span> · Jun 9 – Jul 25 · Coastline Trades</p>
</li>
<li class="phase">
<div class="phase__top">
<h3 class="phase__name">Drywall</h3>
<span class="badge">Scheduled</span>
</div>
<div class="bar"><span class="bar__fill" style="--pct:0%"></span></div>
<p class="phase__foot"><span>0%</span> · Jul 28 – Aug 22 · BoardWorks LLC</p>
</li>
<li class="phase">
<div class="phase__top">
<h3 class="phase__name">Finish</h3>
<span class="badge">Scheduled</span>
</div>
<div class="bar"><span class="bar__fill" style="--pct:0%"></span></div>
<p class="phase__foot"><span>0%</span> · Aug 25 – Oct 17 · Hartwell & Sons</p>
</li>
<li class="phase">
<div class="phase__top">
<h3 class="phase__name">Final</h3>
<span class="badge">Scheduled</span>
</div>
<div class="bar"><span class="bar__fill" style="--pct:0%"></span></div>
<p class="phase__foot"><span>0%</span> · Oct 20 – Nov 14 · Inspection & punchlist</p>
</li>
</ul>
</section>
<div class="grid">
<!-- Photo feed -->
<section class="card" aria-labelledby="feed-h">
<div class="card__head">
<h2 id="feed-h" class="card__title">Site updates</h2>
<p class="card__sub">Latest jobsite captures</p>
</div>
<ul class="feed">
<li class="post">
<div class="post__thumb" data-label="Level 3 rough-in" style="--c1:#2b3440;--c2:#3a4756"></div>
<div class="post__body">
<p class="post__cap">Third-floor electrical rough-in passed walk-through. Panel feeds pulled, ready for inspection.</p>
<p class="post__meta"><strong>Marcus Reyes</strong> · Site Super · Jun 21, 7:42 AM</p>
</div>
</li>
<li class="post">
<div class="post__thumb" data-label="Plumbing risers" style="--c1:#1b222b;--c2:#2b3440"></div>
<div class="post__body">
<p class="post__cap">Plumbing risers set on stack 2. Pressure test scheduled for Monday.</p>
<p class="post__meta"><strong>Priya Nandakumar</strong> · QA Lead · Jun 19, 4:10 PM</p>
</div>
</li>
<li class="post">
<div class="post__thumb" data-label="HVAC ducting" style="--c1:#3a4756;--c2:#2b3440"></div>
<div class="post__body">
<p class="post__cap">Main HVAC trunk hung on floors 1–2. Crew ahead of schedule by two days.</p>
<p class="post__meta"><strong>Coastline Trades</strong> · Sub · Jun 17, 11:25 AM</p>
</div>
</li>
<li class="post">
<div class="post__thumb" data-label="Framing topped out" style="--c1:#2b3440;--c2:#1b222b"></div>
<div class="post__body">
<p class="post__cap">Topping-out complete — last steel beam set on the roof deck. 🎉</p>
<p class="post__meta"><strong>Dana Okafor</strong> · PM · Jun 6, 3:00 PM</p>
</div>
</li>
</ul>
</section>
<!-- Upcoming milestones -->
<section class="card" aria-labelledby="ms-h">
<div class="card__head">
<h2 id="ms-h" class="card__title">Upcoming milestones</h2>
<p class="card__sub" id="msCount">5 ahead</p>
</div>
<div class="filters" role="group" aria-label="Filter milestones by type">
<button class="chip is-on" data-filter="all" type="button">All</button>
<button class="chip" data-filter="inspection" type="button">Inspections</button>
<button class="chip" data-filter="delivery" type="button">Deliveries</button>
<button class="chip" data-filter="pour" type="button">Pours</button>
</div>
<ul class="ms" id="msList">
<li class="mile" data-type="inspection">
<div class="mile__date"><strong>26</strong><span>JUN</span></div>
<div class="mile__body">
<h3 class="mile__name">Electrical rough-in inspection</h3>
<p class="mile__sub">City of Portland · 9:00 AM</p>
</div>
<span class="tag tag--inspection">Inspection</span>
</li>
<li class="mile" data-type="delivery">
<div class="mile__date"><strong>30</strong><span>JUN</span></div>
<div class="mile__body">
<h3 class="mile__name">Drywall & insulation delivery</h3>
<p class="mile__sub">BoardWorks LLC · loading dock B</p>
</div>
<span class="tag tag--delivery">Delivery</span>
</li>
<li class="mile" data-type="pour">
<div class="mile__date"><strong>03</strong><span>JUL</span></div>
<div class="mile__body">
<h3 class="mile__name">Level 4 slab pour</h3>
<p class="mile__sub">Granite Earthworks · 6:00 AM start</p>
</div>
<span class="tag tag--pour">Pour</span>
</li>
<li class="mile" data-type="inspection">
<div class="mile__date"><strong>11</strong><span>JUL</span></div>
<div class="mile__body">
<h3 class="mile__name">Plumbing pressure test</h3>
<p class="mile__sub">QA + city inspector · stacks 1–2</p>
</div>
<span class="tag tag--inspection">Inspection</span>
</li>
<li class="mile" data-type="delivery">
<div class="mile__date"><strong>15</strong><span>JUL</span></div>
<div class="mile__body">
<h3 class="mile__name">Window package delivery</h3>
<p class="mile__sub">Cascade Glazing · staged floor 1</p>
</div>
<span class="tag tag--delivery">Delivery</span>
</li>
</ul>
<p class="ms__empty" id="msEmpty" hidden>No milestones of this type.</p>
</section>
</div>
<footer class="foot">
Build Progress Tracker · synced 2 minutes ago · illustrative demo
</footer>
</main>
<script src="script.js"></script>
</body>
</html>Build Progress Tracker
A rugged, jobsite-ready dashboard for tracking a build from groundbreaking to handover. The header leads with the project name, address and an animated overall completion gauge. Below it a six-stage phase stepper (Foundation, Framing, MEP, Drywall, Finish, Final) shows what’s done, what’s in progress and what’s still ahead, with diagonal hi-vis hazard accents on the active stage.
Each phase carries its own progress bar, scheduled start and finish dates and the assigned crew or sub. A photo-update feed logs dated site captures with author and caption, and an upcoming milestones panel lists the next inspections, deliveries and pours with status chips. Filter chips re-render the milestone list live, and the whole layout collapses to a single column on phones.
Everything is vanilla HTML, CSS and JavaScript — no frameworks, no build step. Drop the three snippets together and the gauge, stepper and filters all work out of the box.
Illustrative UI only — not for real construction project management.