Nonprofit — Home
A warm, human nonprofit home page for a fictional clean-water charity, built with vanilla HTML, CSS, and JavaScript. It pairs a mission hero and prominent donate CTA with an animated impact stat band, what-we-do cards, a featured campaign progress thermometer, an accessible auto-playing impact-story carousel, a ways-to-help grid, and a newsletter signup. Interactions include count-up counters, a donate quick-amount picker with live impact copy, toasts, and a focus-trapped modal.
MCP
Code
:root {
--brand: #1f7a6d;
--brand-d: #155e54;
--accent: #e8743b;
--accent-d: #cc5d28;
--ink: #2a2722;
--ink-2: #524d44;
--muted: #7a7368;
--bg: #faf6f0;
--surface: #ffffff;
--line: rgba(42, 39, 34, 0.1);
--line-2: rgba(42, 39, 34, 0.18);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--sh-sm: 0 1px 2px rgba(42, 39, 34, 0.06), 0 2px 8px rgba(42, 39, 34, 0.05);
--sh-md: 0 6px 22px rgba(42, 39, 34, 0.1);
--sh-lg: 0 18px 50px rgba(42, 39, 34, 0.16);
--serif: "Fraunces", Georgia, "Times New Roman", serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: var(--sans);
color: var(--ink);
background: var(--bg);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4 { font-family: var(--serif); line-height: 1.12; margin: 0; font-weight: 600; letter-spacing: -0.01em; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
.wrap { width: min(1140px, 92vw); margin-inline: auto; }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
position: absolute; left: -999px; top: 8px; z-index: 100;
background: var(--ink); color: #fff; padding: 8px 14px; border-radius: var(--r-sm);
}
.skip-link:focus { left: 12px; }
:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; border-radius: var(--r-sm); }
.eyebrow {
display: inline-block; font-size: 0.74rem; font-weight: 700; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--brand-d); margin-bottom: 0.6rem;
}
/* ---------- Buttons ---------- */
.btn {
font-family: var(--sans); font-weight: 600; font-size: 0.95rem;
border: 1px solid transparent; border-radius: 999px; padding: 0.7em 1.4em;
cursor: pointer; transition: transform 0.12s ease, box-shadow 0.2s ease, background 0.2s ease;
display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
background: var(--surface); color: var(--ink);
}
.btn:active { transform: translateY(1px); }
.btn-lg { padding: 0.85em 1.7em; font-size: 1.02rem; }
.btn-block { width: 100%; }
.btn-accent { background: var(--accent); color: #fff; box-shadow: 0 6px 16px rgba(232, 116, 59, 0.32); }
.btn-accent:hover { background: var(--accent-d); transform: translateY(-1px); box-shadow: 0 10px 22px rgba(232, 116, 59, 0.38); }
.btn-ghost { background: transparent; border-color: var(--line-2); color: var(--ink); }
.btn-ghost:hover { background: rgba(31, 122, 109, 0.08); border-color: var(--brand); color: var(--brand-d); }
.btn-soft { background: rgba(31, 122, 109, 0.1); color: var(--brand-d); }
.btn-soft:hover { background: rgba(31, 122, 109, 0.18); transform: translateY(-1px); }
/* ---------- Header ---------- */
.site-header {
position: sticky; top: 0; z-index: 40;
background: rgba(250, 246, 240, 0.86); backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.header-inner { display: flex; align-items: center; gap: 1.5rem; padding: 0.8rem 0; }
.brand { display: inline-flex; align-items: center; gap: 0.55rem; font-family: var(--serif); font-weight: 700; font-size: 1.18rem; }
.brand-mark {
width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center;
background: linear-gradient(135deg, var(--brand), var(--brand-d)); color: #fff;
box-shadow: var(--sh-sm);
}
.brand-name em { font-style: normal; color: var(--brand-d); font-weight: 500; }
.main-nav { display: flex; gap: 1.4rem; margin-left: auto; }
.main-nav a { font-weight: 500; color: var(--ink-2); padding: 0.3rem 0; border-bottom: 2px solid transparent; transition: color 0.15s, border-color 0.15s; }
.main-nav a:hover { color: var(--brand-d); border-color: var(--brand); }
.header-donate { padding: 0.55em 1.2em; }
.nav-toggle { display: none; flex-direction: column; gap: 4px; background: none; border: 0; cursor: pointer; padding: 6px; }
.nav-toggle span { width: 22px; height: 2px; background: var(--ink); border-radius: 2px; transition: 0.2s; }
/* ---------- Photo blocks ---------- */
.photo-block {
border-radius: var(--r-md); position: relative; overflow: hidden;
min-height: 220px; background-size: cover;
}
.ph-1 { background: linear-gradient(150deg, #2aa394 0%, #1f7a6d 45%, #155e54 100%); min-height: 360px; }
.ph-2 { background: linear-gradient(150deg, #f0a96a 0%, #e8743b 55%, #cc5d28 100%); min-height: 340px; }
.ph-3 { background: linear-gradient(150deg, #4fbfa8 0%, #1f7a6d 100%); }
.ph-4 { background: linear-gradient(150deg, #f0b27a 0%, #e8743b 100%); }
.ph-5 { background: linear-gradient(150deg, #6fc7b6 0%, #2a8d7e 100%); }
.photo-block::after {
content: ""; position: absolute; inset: 0;
background: radial-gradient(120% 90% at 20% 15%, rgba(255,255,255,0.22), transparent 55%),
radial-gradient(80% 80% at 85% 90%, rgba(0,0,0,0.18), transparent 60%);
}
/* ---------- Hero ---------- */
.hero { padding: 3.2rem 0 0; }
.hero-grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: 3rem; align-items: center; }
.hero-copy h1 { font-size: clamp(2.2rem, 5vw, 3.5rem); margin: 0.2rem 0 0.9rem; }
.hero-copy .lede { color: var(--ink-2); font-size: 1.12rem; max-width: 34ch; }
.hero-actions { display: flex; gap: 0.8rem; margin: 1.6rem 0 1.3rem; flex-wrap: wrap; }
.trust-row { list-style: none; display: flex; flex-wrap: wrap; gap: 0.6rem 1.3rem; padding: 0; margin: 0; color: var(--ink-2); font-size: 0.9rem; font-weight: 500; }
.hero-photo { margin: 0; }
.hero-photo figcaption { margin-top: 0.7rem; font-size: 0.86rem; color: var(--muted); font-style: italic; }
/* ---------- Impact band ---------- */
.impact-band {
margin-top: 3rem; margin-bottom: -2.2rem; position: relative; z-index: 2;
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
box-shadow: var(--sh-md); padding: 1.6rem 1rem;
display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; text-align: center;
}
.stat { padding: 0.4rem 0.6rem; }
.stat + .stat { border-left: 1px solid var(--line); }
.stat-num { display: block; font-family: var(--serif); font-weight: 700; font-size: clamp(1.7rem, 3.4vw, 2.5rem); color: var(--brand-d); }
.stat-label { display: block; font-size: 0.82rem; color: var(--muted); margin-top: 0.2rem; font-weight: 500; }
/* ---------- Sections ---------- */
.section { padding: 5rem 0; }
.section-tint { background: linear-gradient(180deg, #f4ede3, #faf6f0); border-block: 1px solid var(--line); }
.section-head { max-width: 46ch; margin-bottom: 2.4rem; }
.section-head h2 { font-size: clamp(1.8rem, 3.6vw, 2.5rem); margin-bottom: 0.5rem; }
.section-head p { color: var(--ink-2); font-size: 1.05rem; }
/* ---------- What we do ---------- */
.card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.3rem; }
.do-card {
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
padding: 1.7rem; box-shadow: var(--sh-sm); transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s;
}
.do-card:hover { transform: translateY(-4px); box-shadow: var(--sh-md); border-color: var(--line-2); }
.do-icon { font-size: 1.9rem; display: grid; place-items: center; width: 56px; height: 56px; border-radius: 14px; background: rgba(31, 122, 109, 0.1); margin-bottom: 1rem; }
.do-card h3 { font-size: 1.3rem; margin-bottom: 0.4rem; }
.do-card p { color: var(--ink-2); }
.do-stat { display: inline-block; margin-top: 1rem; font-size: 0.84rem; font-weight: 600; color: var(--brand-d); background: rgba(31, 122, 109, 0.08); padding: 0.3em 0.8em; border-radius: 999px; }
/* ---------- Campaign ---------- */
.campaign-grid { display: grid; grid-template-columns: 0.95fr 1.05fr; gap: 3rem; align-items: center; }
.campaign-photo { margin: 0; position: relative; }
.campaign-badge {
position: absolute; top: 1rem; left: 1rem; background: var(--surface); color: var(--accent-d);
font-weight: 700; font-size: 0.78rem; letter-spacing: 0.04em; padding: 0.4em 0.9em;
border-radius: 999px; box-shadow: var(--sh-sm); text-transform: uppercase;
}
.campaign-copy h2 { font-size: clamp(1.8rem, 3.6vw, 2.6rem); margin-bottom: 0.6rem; }
.campaign-copy > p { color: var(--ink-2); margin-bottom: 1.6rem; }
.thermo { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); padding: 1.2rem 1.3rem; box-shadow: var(--sh-sm); }
.thermo-top { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: 0.7rem; flex-wrap: wrap; }
.thermo-top .raised strong { font-family: var(--serif); font-size: 1.5rem; color: var(--brand-d); }
.thermo-top .goal { color: var(--muted); font-size: 0.92rem; }
.thermo-track { height: 14px; background: rgba(42, 39, 34, 0.08); border-radius: 999px; overflow: hidden; }
.thermo-fill { height: 100%; width: 0; border-radius: 999px; background: linear-gradient(90deg, var(--brand), var(--accent)); transition: width 1.4s cubic-bezier(0.22, 1, 0.36, 1); }
.thermo-meta { display: flex; gap: 1.4rem; margin-top: 0.8rem; font-size: 0.88rem; color: var(--ink-2); flex-wrap: wrap; }
.thermo-meta strong { color: var(--ink); }
.campaign-actions { display: flex; gap: 0.8rem; margin-top: 1.4rem; flex-wrap: wrap; }
/* ---------- Carousel ---------- */
.carousel { max-width: 760px; }
.carousel-viewport { overflow: hidden; }
.carousel-track { list-style: none; margin: 0; padding: 0; }
.story {
display: grid; grid-template-columns: 0.9fr 1.1fr; gap: 1.6rem; align-items: center;
background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
padding: 1.6rem; box-shadow: var(--sh-sm); animation: fade 0.5s ease;
}
.story .photo-block { min-height: 200px; }
.story blockquote { margin: 0; }
.story blockquote p { font-family: var(--serif); font-size: 1.25rem; line-height: 1.4; color: var(--ink); margin-bottom: 0.8rem; }
.story blockquote footer { color: var(--muted); font-weight: 600; font-size: 0.9rem; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.carousel-ctl { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-top: 1.3rem; }
.car-btn {
width: 42px; height: 42px; border-radius: 50%; border: 1px solid var(--line-2);
background: var(--surface); color: var(--ink); font-size: 1.4rem; cursor: pointer; line-height: 1;
transition: 0.15s; box-shadow: var(--sh-sm);
}
.car-btn:hover { background: var(--brand); color: #fff; border-color: var(--brand); }
.car-dots { display: flex; gap: 0.5rem; }
.dot { width: 10px; height: 10px; border-radius: 50%; border: 0; background: var(--line-2); cursor: pointer; padding: 0; transition: 0.2s; }
.dot.active { background: var(--accent); transform: scale(1.25); }
/* ---------- Ways to help ---------- */
.help-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.2rem; }
.help-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 1.5rem; box-shadow: var(--sh-sm); display: flex; flex-direction: column; transition: transform 0.18s, box-shadow 0.18s; }
.help-card:hover { transform: translateY(-3px); box-shadow: var(--sh-md); }
.help-card h3 { font-size: 1.2rem; margin-bottom: 0.4rem; }
.help-card p { color: var(--ink-2); font-size: 0.94rem; flex: 1; margin-bottom: 1.1rem; }
/* ---------- Newsletter ---------- */
.newsletter-sec { padding-bottom: 5.5rem; }
.newsletter {
background: linear-gradient(135deg, var(--brand-d), var(--brand)); color: #fff;
border-radius: var(--r-lg); padding: 2.6rem; box-shadow: var(--sh-md);
display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: center;
}
.news-copy h2 { color: #fff; font-size: 1.9rem; margin-bottom: 0.4rem; }
.news-copy p { color: rgba(255, 255, 255, 0.88); }
.news-form { display: flex; gap: 0.6rem; flex-wrap: wrap; position: relative; }
.news-form input {
flex: 1; min-width: 200px; padding: 0.85em 1.1em; border-radius: 999px; border: 1px solid rgba(255,255,255,0.3);
font-family: var(--sans); font-size: 1rem; background: rgba(255, 255, 255, 0.95); color: var(--ink);
}
.news-form input:focus { outline: 3px solid var(--accent); outline-offset: 2px; }
.form-msg { width: 100%; margin: 0; font-size: 0.88rem; min-height: 1.2em; color: #fff; font-weight: 600; }
.form-msg.err { color: #ffd9cf; }
/* ---------- Footer ---------- */
.site-footer { background: #211e19; color: rgba(255, 255, 255, 0.78); padding: 3.4rem 0 1.8rem; }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr; gap: 2.4rem; }
.site-footer .brand { color: #fff; }
.site-footer .brand-name em { color: #7fd6c6; }
.foot-brand p { margin: 1rem 0 1.2rem; max-width: 40ch; font-size: 0.92rem; }
.badges { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.badge { font-size: 0.76rem; font-weight: 600; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.14); padding: 0.4em 0.8em; border-radius: 999px; color: #fff; }
.foot-col h4 { color: #fff; font-size: 0.95rem; margin-bottom: 0.9rem; letter-spacing: 0.02em; }
.foot-col a { display: block; padding: 0.32rem 0; color: rgba(255, 255, 255, 0.72); font-size: 0.92rem; transition: color 0.15s; }
.foot-col a:hover { color: #7fd6c6; }
.foot-bottom { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-top: 2.6rem; padding-top: 1.4rem; border-top: 1px solid rgba(255, 255, 255, 0.12); font-size: 0.84rem; color: rgba(255, 255, 255, 0.55); }
/* ---------- Donate modal ---------- */
.modal { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center; padding: 1.2rem; }
.modal[hidden] { display: none; }
.modal-backdrop { position: absolute; inset: 0; background: rgba(33, 30, 25, 0.55); backdrop-filter: blur(3px); animation: fade 0.25s ease; }
.modal-card {
position: relative; background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--sh-lg);
width: min(440px, 100%); padding: 2rem 1.9rem; animation: pop 0.28s cubic-bezier(0.22, 1, 0.36, 1);
max-height: 92vh; overflow: auto;
}
@keyframes pop { from { opacity: 0; transform: translateY(14px) scale(0.97); } to { opacity: 1; transform: none; } }
.modal-x { position: absolute; top: 0.8rem; right: 0.9rem; width: 34px; height: 34px; border-radius: 50%; border: 0; background: rgba(42,39,34,0.06); font-size: 1.4rem; line-height: 1; cursor: pointer; color: var(--ink-2); }
.modal-x:hover { background: rgba(42,39,34,0.12); }
.modal-card h2 { font-size: 1.6rem; }
.modal-sub { color: var(--ink-2); margin: 0.3rem 0 1.2rem; font-size: 0.95rem; }
.freq-toggle { display: flex; background: rgba(42,39,34,0.06); border-radius: 999px; padding: 4px; margin-bottom: 1.2rem; }
.freq { flex: 1; border: 0; background: transparent; padding: 0.6em; border-radius: 999px; font-family: var(--sans); font-weight: 600; color: var(--ink-2); cursor: pointer; transition: 0.18s; }
.freq.active { background: var(--surface); color: var(--brand-d); box-shadow: var(--sh-sm); }
.amount-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; margin-bottom: 0.7rem; }
.amt-btn { border: 1.5px solid var(--line-2); background: var(--surface); border-radius: var(--r-sm); padding: 0.85em 0.4em; font-family: var(--serif); font-weight: 600; font-size: 1.1rem; cursor: pointer; color: var(--ink); transition: 0.15s; }
.amt-btn:hover { border-color: var(--brand); }
.amt-btn.active { border-color: var(--accent); background: rgba(232, 116, 59, 0.1); color: var(--accent-d); }
.custom-amt { display: flex; align-items: center; gap: 0.5rem; border: 1.5px solid var(--line-2); border-radius: var(--r-sm); padding: 0 0.9rem; margin-bottom: 1rem; }
.custom-amt span { font-weight: 700; color: var(--muted); }
.custom-amt input { flex: 1; border: 0; outline: 0; padding: 0.8em 0; font-family: var(--sans); font-size: 1rem; background: transparent; color: var(--ink); }
.impact-line { background: rgba(31, 122, 109, 0.08); border-radius: var(--r-sm); padding: 0.7em 0.9em; font-size: 0.9rem; color: var(--brand-d); font-weight: 500; margin-bottom: 1.1rem; }
.modal-foot { text-align: center; font-size: 0.8rem; color: var(--muted); margin-top: 0.9rem; }
/* ---------- Toast ---------- */
.toast {
position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%) translateY(20px);
background: var(--ink); color: #fff; padding: 0.85em 1.4em; border-radius: var(--r-md);
box-shadow: var(--sh-lg); font-weight: 500; font-size: 0.94rem; z-index: 90;
opacity: 0; transition: opacity 0.25s, transform 0.25s; max-width: 90vw; text-align: center;
}
.toast[hidden] { display: none; }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* ---------- Responsive ---------- */
@media (max-width: 900px) {
.hero-grid, .campaign-grid, .newsletter { grid-template-columns: 1fr; gap: 2rem; }
.hero-photo { order: -1; }
.card-grid, .help-grid { grid-template-columns: repeat(2, 1fr); }
.footer-grid { grid-template-columns: 1fr 1fr; }
.foot-brand { grid-column: 1 / -1; }
}
@media (max-width: 760px) {
.main-nav, .header-donate { display: none; }
.main-nav.open {
display: flex; flex-direction: column; position: absolute; top: 100%; left: 0; right: 0;
background: var(--surface); padding: 1rem 6vw; gap: 0.4rem; box-shadow: var(--sh-md); border-bottom: 1px solid var(--line);
}
.header-inner { position: relative; }
.nav-toggle { display: flex; margin-left: auto; }
.story { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
.section { padding: 3.4rem 0; }
.impact-band { grid-template-columns: repeat(2, 1fr); gap: 0.6rem 0; padding: 1.2rem 0.6rem; }
.stat:nth-child(3) { border-left: 0; }
.stat:nth-child(3), .stat:nth-child(4) { border-top: 1px solid var(--line); padding-top: 1rem; margin-top: 0.6rem; }
.card-grid, .help-grid { grid-template-columns: 1fr; }
.footer-grid { grid-template-columns: 1fr; }
.amount-grid { grid-template-columns: repeat(3, 1fr); }
.newsletter { padding: 1.8rem; }
.modal-card { padding: 1.6rem 1.3rem; }
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; scroll-behavior: auto; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
clearTimeout(toastTimer);
toastEl.textContent = msg;
toastEl.hidden = false;
// force reflow so the transition runs
void toastEl.offsetWidth;
toastEl.classList.add("show");
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
setTimeout(function () { toastEl.hidden = true; }, 280);
}, 2800);
}
/* ---------- Number formatting ---------- */
function fmtCompact(n) {
if (n >= 1000000) return (n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1) + "M";
if (n >= 1000) return (n / 1000).toFixed(0) + "K";
return String(n);
}
function fmtComma(n) { return n.toLocaleString("en-US"); }
/* ---------- Animated counters ---------- */
function animateCount(el) {
var target = parseInt(el.getAttribute("data-count"), 10) || 0;
var compact = el.getAttribute("data-format") === "compact";
var suffix = el.getAttribute("data-suffix") || "";
var dur = 1600;
var start = performance.now();
function tick(now) {
var p = Math.min((now - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
var val = Math.round(target * eased);
el.textContent = (compact ? fmtCompact(val) : fmtComma(val)) + suffix;
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
var counters = document.querySelectorAll("[data-count]");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { animateCount(e.target); io.unobserve(e.target); }
});
}, { threshold: 0.4 });
counters.forEach(function (c) { io.observe(c); });
} else {
counters.forEach(animateCount);
}
/* ---------- Campaign thermometer ---------- */
var raised = 148200, goal = 240000;
var pct = Math.min(Math.round((raised / goal) * 100), 100);
var thermo = document.getElementById("thermo");
var pctEl = document.getElementById("pct");
function fillThermo() {
if (thermo) thermo.style.width = pct + "%";
if (pctEl) {
var s = performance.now();
(function step(now) {
var p = Math.min((now - s) / 1400, 1);
if (pctEl) pctEl.textContent = Math.round(pct * (1 - Math.pow(1 - p, 3))) + "%";
if (p < 1) requestAnimationFrame(step);
})(performance.now());
}
}
if (thermo && "IntersectionObserver" in window) {
var tio = new IntersectionObserver(function (entries) {
if (entries[0].isIntersecting) { fillThermo(); tio.disconnect(); }
}, { threshold: 0.3 });
tio.observe(thermo);
} else { fillThermo(); }
/* ---------- Mobile nav ---------- */
var toggle = document.querySelector(".nav-toggle");
var nav = document.querySelector(".main-nav");
if (toggle && nav) {
toggle.addEventListener("click", function () {
var open = nav.classList.toggle("open");
toggle.setAttribute("aria-expanded", String(open));
});
nav.addEventListener("click", function (e) {
if (e.target.tagName === "A") { nav.classList.remove("open"); toggle.setAttribute("aria-expanded", "false"); }
});
}
/* ---------- Story carousel ---------- */
var track = document.getElementById("track");
var slides = track ? Array.prototype.slice.call(track.children) : [];
var dotsWrap = document.getElementById("dots");
var idx = 0, autoTimer;
function buildDots() {
if (!dotsWrap) return;
slides.forEach(function (_, i) {
var d = document.createElement("button");
d.className = "dot" + (i === 0 ? " active" : "");
d.setAttribute("role", "tab");
d.setAttribute("aria-label", "Story " + (i + 1));
d.addEventListener("click", function () { go(i); resetAuto(); });
dotsWrap.appendChild(d);
});
}
function go(n) {
idx = (n + slides.length) % slides.length;
slides.forEach(function (s, i) {
s.hidden = i !== idx;
if (i === idx) { s.classList.remove("anim"); void s.offsetWidth; s.style.animation = "none"; s.style.animation = ""; }
});
if (dotsWrap) Array.prototype.forEach.call(dotsWrap.children, function (d, i) { d.classList.toggle("active", i === idx); });
}
function resetAuto() {
clearInterval(autoTimer);
autoTimer = setInterval(function () { go(idx + 1); }, 6000);
}
if (slides.length) {
buildDots();
document.querySelectorAll("[data-car]").forEach(function (b) {
b.addEventListener("click", function () {
go(idx + (b.getAttribute("data-car") === "next" ? 1 : -1));
resetAuto();
});
});
resetAuto();
}
/* ---------- Donate quick picker ---------- */
var modal = document.getElementById("donateModal");
var amountGrid = document.getElementById("amountGrid");
var customAmt = document.getElementById("customAmt");
var impactLine = document.getElementById("impactLine");
var giveBtn = document.getElementById("giveBtn");
var freqBtns = document.querySelectorAll(".freq");
var presets = { once: [25, 50, 100, 250, 500, 1000], monthly: [10, 25, 50, 75, 100, 150] };
var freq = "once";
var amount = 50;
var lastFocus = null;
var impactCopy = [
{ min: 0, txt: "Every dollar funds pipes, pumps, and clean mornings." },
{ min: 25, txt: "$AMT keeps a hand pump maintained for months." },
{ min: 50, txt: "$AMT funds clean water for one family for a year." },
{ min: 100, txt: "$AMT trains a local technician to keep wells flowing." },
{ min: 250, txt: "$AMT helps survey and prep a new well site." },
{ min: 1000, txt: "$AMT funds an entire village hand-pump well." }
];
function impactFor(a) {
var pick = impactCopy[0];
for (var i = 0; i < impactCopy.length; i++) { if (a >= impactCopy[i].min) pick = impactCopy[i]; }
var per = freq === "monthly" ? "/mo" : "";
return pick.txt.replace("$AMT", "$" + fmtComma(a) + per);
}
function renderAmounts() {
if (!amountGrid) return;
amountGrid.innerHTML = "";
presets[freq].forEach(function (v) {
var b = document.createElement("button");
b.className = "amt-btn" + (v === amount ? " active" : "");
b.type = "button";
b.textContent = "$" + v + (freq === "monthly" ? "/mo" : "");
b.addEventListener("click", function () {
amount = v;
if (customAmt) customAmt.value = "";
syncUI();
});
amountGrid.appendChild(b);
});
}
function syncUI() {
if (amountGrid) Array.prototype.forEach.call(amountGrid.children, function (b, i) {
b.classList.toggle("active", presets[freq][i] === amount);
});
if (impactLine) impactLine.textContent = impactFor(amount);
if (giveBtn) giveBtn.textContent = "Give $" + fmtComma(amount) + (freq === "monthly" ? "/month" : "");
}
freqBtns.forEach(function (b) {
b.addEventListener("click", function () {
freq = b.getAttribute("data-freq");
freqBtns.forEach(function (x) { x.classList.remove("active"); x.setAttribute("aria-selected", "false"); });
b.classList.add("active"); b.setAttribute("aria-selected", "true");
if (presets[freq].indexOf(amount) === -1) amount = presets[freq][1];
renderAmounts(); syncUI();
});
});
if (customAmt) {
customAmt.addEventListener("input", function () {
var v = parseInt(customAmt.value, 10);
if (v > 0) { amount = v; syncUI(); }
});
}
if (giveBtn) {
giveBtn.addEventListener("click", function () {
closeModal();
toast("Thank you! Your $" + fmtComma(amount) + (freq === "monthly" ? "/mo " : " ") + "gift is illustrative — no charge made.");
});
}
function openModal() {
if (!modal) return;
lastFocus = document.activeElement;
renderAmounts(); syncUI();
modal.hidden = false;
document.body.style.overflow = "hidden";
var first = modal.querySelector(".freq");
if (first) first.focus();
document.addEventListener("keydown", onKey);
}
function closeModal() {
if (!modal) return;
modal.hidden = true;
document.body.style.overflow = "";
document.removeEventListener("keydown", onKey);
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
function onKey(e) {
if (e.key === "Escape") closeModal();
if (e.key === "Tab") {
var f = modal.querySelectorAll("button, input, [href]");
if (!f.length) return;
var first = f[0], last = f[f.length - 1];
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
}
}
document.querySelectorAll("[data-amount-trigger]").forEach(function (t) {
t.addEventListener("click", function (e) { e.preventDefault(); openModal(); });
});
document.querySelectorAll("[data-close]").forEach(function (c) {
c.addEventListener("click", closeModal);
});
/* ---------- Generic toast triggers + share ---------- */
document.querySelectorAll("[data-toast]").forEach(function (b) {
b.addEventListener("click", function () { toast(b.getAttribute("data-toast")); });
});
document.querySelectorAll("[data-share]").forEach(function (b) {
b.addEventListener("click", function () {
if (navigator.share) {
navigator.share({ title: "Highlands Water Project", text: "Help fund 12 wells before the dry season." }).catch(function () {});
} else {
toast("Campaign link copied — thanks for spreading the word!");
}
});
});
/* ---------- Newsletter ---------- */
var form = document.getElementById("newsForm");
var msg = document.getElementById("newsMsg");
if (form) {
form.addEventListener("submit", function (e) {
e.preventDefault();
var email = form.email.value.trim();
var valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (!valid) {
if (msg) { msg.textContent = "Please enter a valid email address."; msg.classList.add("err"); }
form.email.focus();
return;
}
if (msg) { msg.classList.remove("err"); msg.textContent = "You're in — watch for our next field note."; }
form.reset();
toast("Subscribed! Welcome to Brightwater.");
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Brightwater Collective — Clean water, brighter futures</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=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="wrap header-inner">
<a class="brand" href="#" aria-label="Brightwater Collective home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="22" height="22"><path d="M12 2.5C12 2.5 5 10 5 14.5a7 7 0 0 0 14 0C19 10 12 2.5 12 2.5Z" fill="currentColor"/></svg>
</span>
<span class="brand-name">Brightwater <em>Collective</em></span>
</a>
<nav class="main-nav" aria-label="Primary">
<a href="#what">What we do</a>
<a href="#campaign">Campaigns</a>
<a href="#stories">Stories</a>
<a href="#help">Get involved</a>
</nav>
<button class="btn btn-accent header-donate" data-amount-trigger>Donate</button>
<button class="nav-toggle" aria-label="Toggle menu" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
</header>
<main id="main">
<!-- HERO -->
<section class="hero">
<div class="wrap hero-grid">
<div class="hero-copy">
<span class="eyebrow">Registered charity · Est. 2009</span>
<h1>Clean water for every village we can reach.</h1>
<p class="lede">Brightwater Collective builds wells, trains local technicians, and keeps water flowing long after the trucks leave. Your gift turns into pipes, pumps, and bright mornings.</p>
<div class="hero-actions">
<button class="btn btn-accent btn-lg" data-amount-trigger>Donate now</button>
<a class="btn btn-ghost btn-lg" href="#campaign">See our work</a>
</div>
<ul class="trust-row" aria-label="Trust signals">
<li>✓ Tax-deductible</li>
<li>✓ 87¢ of every $1 to programs</li>
<li>✓ Audited annually</li>
</ul>
</div>
<figure class="hero-photo">
<div class="photo-block ph-1" role="img" aria-label="Children collecting clean water at a new village well"></div>
<figcaption>Amara & her classmates, Lake District — first day of the new well.</figcaption>
</figure>
</div>
<!-- IMPACT STAT BAND -->
<div class="wrap">
<div class="impact-band" aria-label="Lifetime impact">
<div class="stat">
<span class="stat-num" data-count="412">0</span>
<span class="stat-label">Wells built</span>
</div>
<div class="stat">
<span class="stat-num" data-count="1900000" data-format="compact">0</span>
<span class="stat-label">People reached</span>
</div>
<div class="stat">
<span class="stat-num" data-count="38">0</span>
<span class="stat-label">Districts served</span>
</div>
<div class="stat">
<span class="stat-num" data-count="94" data-suffix="%">0</span>
<span class="stat-label">Wells still flowing</span>
</div>
</div>
</div>
</section>
<!-- WHAT WE DO -->
<section class="section" id="what">
<div class="wrap">
<header class="section-head">
<span class="eyebrow">Our approach</span>
<h2>What we do</h2>
<p>Three things, done patiently and well — so the water keeps coming.</p>
</header>
<div class="card-grid">
<article class="do-card">
<span class="do-icon" aria-hidden="true">⛏️</span>
<h3>We build</h3>
<p>Boreholes, hand pumps, and gravity-fed systems engineered for the terrain and the community that will use them.</p>
<span class="do-stat">412 wells · 38 districts</span>
</article>
<article class="do-card">
<span class="do-icon" aria-hidden="true">🧰</span>
<h3>We train</h3>
<p>Local water committees and paid technicians who can fix a pump in an afternoon instead of waiting on a city.</p>
<span class="do-stat">1,240 technicians certified</span>
</article>
<article class="do-card">
<span class="do-icon" aria-hidden="true">📡</span>
<h3>We monitor</h3>
<p>Sensors and field visits track every site, so a broken pump is a phone alert — not a year of empty buckets.</p>
<span class="do-stat">94% uptime, lifetime</span>
</article>
</div>
</div>
</section>
<!-- FEATURED CAMPAIGN -->
<section class="section section-tint" id="campaign">
<div class="wrap campaign-grid">
<figure class="campaign-photo">
<div class="photo-block ph-2" role="img" aria-label="Engineers surveying a dry riverbed in the Highlands region"></div>
<span class="campaign-badge">Active campaign</span>
</figure>
<div class="campaign-copy">
<span class="eyebrow">Highlands Water Project</span>
<h2>Twelve wells before the dry season.</h2>
<p>The Highlands region loses its surface water by July. We have the survey, the crews, and 6 wells funded. Help us reach all twelve before the rivers run dry.</p>
<div class="thermo" role="group" aria-label="Campaign funding progress">
<div class="thermo-top">
<span class="raised"><strong id="raised">$148,200</strong> raised</span>
<span class="goal">of $240,000 goal</span>
</div>
<div class="thermo-track">
<div class="thermo-fill" id="thermo" style="width:0%"></div>
</div>
<div class="thermo-meta">
<span><strong id="pct">0%</strong> funded</span>
<span><strong>1,310</strong> donors</span>
<span><strong>26</strong> days left</span>
</div>
</div>
<div class="campaign-actions">
<button class="btn btn-accent" data-amount-trigger>Fund a well</button>
<button class="btn btn-ghost" data-share>Share this campaign</button>
</div>
</div>
</div>
</section>
<!-- IMPACT STORIES CAROUSEL -->
<section class="section" id="stories">
<div class="wrap">
<header class="section-head">
<span class="eyebrow">From the field</span>
<h2>Impact stories</h2>
<p>The numbers matter. The mornings matter more.</p>
</header>
<div class="carousel" aria-roledescription="carousel" aria-label="Impact stories">
<div class="carousel-viewport">
<ul class="carousel-track" id="track">
<li class="story" aria-roledescription="slide">
<div class="photo-block ph-3" role="img" aria-label="A woman smiling beside a working hand pump"></div>
<blockquote>
<p>"My daughters used to walk three hours for water. Now they walk three minutes — and they're in school."</p>
<footer>— Naïma O., Kembe Village</footer>
</blockquote>
</li>
<li class="story" aria-roledescription="slide" hidden>
<div class="photo-block ph-4" role="img" aria-label="A certified technician repairing a pump"></div>
<blockquote>
<p>"I fixed the pump myself. The whole village trusts me now — and I have steady work close to home."</p>
<footer>— Tomas R., Certified Technician</footer>
</blockquote>
</li>
<li class="story" aria-roledescription="slide" hidden>
<div class="photo-block ph-5" role="img" aria-label="A health worker in a clinic that now has clean water"></div>
<blockquote>
<p>"Waterborne illness in our clinic dropped by half the first year. Clean water is the cheapest medicine we have."</p>
<footer>— Dr. Adaeze N., District Clinic</footer>
</blockquote>
</li>
</ul>
</div>
<div class="carousel-ctl">
<button class="car-btn" data-car="prev" aria-label="Previous story">‹</button>
<div class="car-dots" id="dots" role="tablist" aria-label="Choose story"></div>
<button class="car-btn" data-car="next" aria-label="Next story">›</button>
</div>
</div>
</div>
</section>
<!-- WAYS TO HELP -->
<section class="section section-tint" id="help">
<div class="wrap">
<header class="section-head">
<span class="eyebrow">Get involved</span>
<h2>Ways to help</h2>
</header>
<div class="help-grid">
<article class="help-card">
<h3>Give monthly</h3>
<p>$25/month keeps a hand pump maintained for a full year.</p>
<button class="btn btn-soft" data-amount-trigger>Become a Spring</button>
</article>
<article class="help-card">
<h3>Fundraise</h3>
<p>Birthdays, marathons, bake sales — start a page in two minutes.</p>
<button class="btn btn-soft" data-toast="Fundraiser page created — share it with your circle!">Start a page</button>
</article>
<article class="help-card">
<h3>Corporate match</h3>
<p>Many employers double your gift. We'll handle the paperwork.</p>
<button class="btn btn-soft" data-toast="We'll email your matching-gift kit shortly.">Check my employer</button>
</article>
<article class="help-card">
<h3>Volunteer</h3>
<p>Engineers, translators, storytellers — remote and in-field roles.</p>
<button class="btn btn-soft" data-toast="Thanks! Volunteer roles sent to your inbox.">See open roles</button>
</article>
</div>
</div>
</section>
<!-- NEWSLETTER -->
<section class="section newsletter-sec">
<div class="wrap">
<div class="newsletter">
<div class="news-copy">
<h2>Field notes, four times a year</h2>
<p>Honest updates from the wells you help fund. No spam, unsubscribe anytime.</p>
</div>
<form class="news-form" id="newsForm" novalidate>
<label class="sr-only" for="news-email">Email address</label>
<input id="news-email" type="email" name="email" placeholder="you@example.org" autocomplete="email" required />
<button class="btn btn-accent" type="submit">Subscribe</button>
<p class="form-msg" id="newsMsg" role="status" aria-live="polite"></p>
</form>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap footer-grid">
<div class="foot-brand">
<a class="brand" href="#" aria-label="Brightwater Collective home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20"><path d="M12 2.5C12 2.5 5 10 5 14.5a7 7 0 0 0 14 0C19 10 12 2.5 12 2.5Z" fill="currentColor"/></svg>
</span>
<span class="brand-name">Brightwater <em>Collective</em></span>
</a>
<p>Clean water and the skills to keep it flowing. A fictional 501(c)(3) charity for demo purposes.</p>
<div class="badges" aria-label="Trust badges">
<span class="badge">Registered Charity #84-FICT</span>
<span class="badge">Tax-deductible</span>
<span class="badge">★ 4-star transparency</span>
</div>
</div>
<nav class="foot-col" aria-label="Footer: Organization">
<h4>Organization</h4>
<a href="#">About us</a>
<a href="#">Annual report</a>
<a href="#">Financials</a>
<a href="#">Careers</a>
</nav>
<nav class="foot-col" aria-label="Footer: Support">
<h4>Support</h4>
<a href="#" data-amount-trigger>Donate</a>
<a href="#">Monthly giving</a>
<a href="#">Leave a legacy</a>
<a href="#">Contact</a>
</nav>
</div>
<div class="wrap foot-bottom">
<span>© 2026 Brightwater Collective (fictional).</span>
<span>Privacy · Terms · Donor promise</span>
</div>
</footer>
<!-- DONATE QUICK PICKER -->
<div class="modal" id="donateModal" hidden>
<div class="modal-backdrop" data-close></div>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="donateTitle">
<button class="modal-x" data-close aria-label="Close">×</button>
<h2 id="donateTitle">Make a gift</h2>
<p class="modal-sub">Choose an amount — see the impact instantly.</p>
<div class="freq-toggle" role="tablist" aria-label="Frequency">
<button class="freq active" data-freq="once" role="tab" aria-selected="true">One-time</button>
<button class="freq" data-freq="monthly" role="tab" aria-selected="false">Monthly</button>
</div>
<div class="amount-grid" id="amountGrid"></div>
<label class="custom-amt">
<span>$</span>
<input id="customAmt" type="number" min="1" step="1" inputmode="numeric" placeholder="Other amount" aria-label="Custom amount" />
</label>
<p class="impact-line" id="impactLine">$50 funds clean water for one family for a year.</p>
<button class="btn btn-accent btn-lg btn-block" id="giveBtn">Give $50</button>
<p class="modal-foot">🔒 Secure & encrypted · 100% refundable within 30 days</p>
</div>
</div>
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script src="script.js"></script>
</body>
</html>Home
A complete nonprofit landing page for the fictional Brightwater Collective, a clean-water charity. The hero leads with a mission statement, a high-contrast donate CTA, and trust signals (tax-deductible, program-spend ratio, annual audit). Below it, an impact stat band animates count-up numbers — wells built, people reached, districts served, and uptime — as it scrolls into view. The page continues with a three-part “what we do” card set, a featured Highlands Water Project campaign, an impact-story carousel, a ways-to-help grid, and a newsletter signup, closing on a footer with charity trust badges.
The interactions are all vanilla JavaScript. Counters and the campaign progress thermometer ease into place via IntersectionObserver. The donate button opens a focus-trapped modal with a one-time/monthly toggle, preset and custom amount inputs, and an impact line that updates live (“$50 funds clean water for one family for a year”). The story carousel auto-advances, supports previous/next buttons and clickable dots, and uses the native share sheet when available. A small toast(msg) helper confirms actions, and the newsletter form validates email before showing a friendly status message.
Everything is responsive down to roughly 360px — the nav collapses to a toggle menu, grids reflow to single columns, and the impact band restacks. Styles honor prefers-reduced-motion, and buttons, inputs, and the modal are keyboard-usable with visible focus rings and AA-contrast colors.
Illustrative UI only — fictional organization, not a real charity or donation system.