Nonprofit — Campaign / Fundraiser
A warm, polished fundraiser landing page for a fictional clean-water charity, featuring an animated goal thermometer with raised-versus-goal totals, donor and days-left counters, and a wells-funded tracker. Visitors pick a perked donation tier or enter a custom one-time or monthly amount, watch a 2x matching note update live, follow a streaming recent-donors feed, read an updates timeline, and share the campaign across networks — all with vanilla JavaScript and trust badges.
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 20px rgba(42, 39, 34, 0.09), 0 2px 6px rgba(42, 39, 34, 0.05);
--sh-lg: 0 18px 50px rgba(42, 39, 34, 0.16);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
color: var(--ink);
background: var(--bg);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3 {
font-family: "Fraunces", Georgia, "Times New Roman", serif;
line-height: 1.18;
color: var(--ink);
margin: 0 0 0.5em;
}
p { margin: 0 0 1em; }
a { color: var(--brand-d); }
.muted { color: var(--muted); }
/* ---------- buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5em;
font: inherit;
font-weight: 600;
border: 1px solid transparent;
border-radius: 999px;
padding: 0.72em 1.4em;
cursor: pointer;
text-decoration: none;
transition: transform 0.12s ease, box-shadow 0.18s ease, background 0.18s ease;
line-height: 1;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn:focus-visible { outline: 3px solid rgba(31, 122, 109, 0.4); outline-offset: 2px; }
.btn-accent {
background: linear-gradient(180deg, var(--accent), var(--accent-d));
color: #fff;
box-shadow: 0 6px 16px rgba(232, 116, 59, 0.32);
}
.btn-accent:hover { box-shadow: 0 10px 24px rgba(232, 116, 59, 0.42); transform: translateY(-1px); }
.btn-ghost {
background: var(--surface);
color: var(--ink);
border-color: var(--line-2);
}
.btn-ghost:hover { border-color: var(--brand); color: var(--brand-d); }
.btn-sm { padding: 0.55em 1.05em; font-size: 0.88rem; }
.btn-block { width: 100%; }
/* ---------- topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 30;
display: flex;
align-items: center;
gap: 1.2rem;
padding: 0.8rem clamp(1rem, 4vw, 2.4rem);
background: rgba(250, 246, 240, 0.86);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.brand {
display: inline-flex;
align-items: center;
gap: 0.55rem;
text-decoration: none;
color: var(--ink);
font-weight: 700;
}
.brand-mark {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 10px;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff;
font-size: 1.1rem;
}
.brand-name { font-family: "Fraunces", serif; font-size: 1.08rem; }
.brand-name em { font-style: normal; color: var(--brand-d); }
.topnav {
display: flex;
gap: 1.3rem;
margin-left: auto;
}
.topnav a {
color: var(--ink-2);
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
}
.topnav a:hover { color: var(--brand-d); }
.topdonate { margin-left: 0.4rem; }
/* ---------- hero ---------- */
.hero {
display: grid;
grid-template-columns: 1.05fr 0.95fr;
gap: clamp(1rem, 3vw, 2.4rem);
align-items: stretch;
max-width: 1180px;
margin: clamp(1.2rem, 4vw, 2.6rem) auto 0;
padding: 0 clamp(1rem, 4vw, 2.4rem);
}
.hero-media {
position: relative;
min-height: 420px;
border-radius: var(--r-lg);
background:
radial-gradient(120% 90% at 20% 15%, rgba(255, 255, 255, 0.35), transparent 55%),
linear-gradient(155deg, #2f9e8e 0%, #1f7a6d 42%, #e8743b 130%);
box-shadow: var(--sh-md);
overflow: hidden;
}
.hero-media::after {
content: "💧";
position: absolute;
right: -14px;
bottom: -18px;
font-size: 9rem;
opacity: 0.18;
filter: blur(0.3px);
}
.photo-tag {
position: absolute;
left: 14px;
bottom: 14px;
background: rgba(42, 39, 34, 0.55);
color: #fff;
font-size: 0.78rem;
padding: 0.4em 0.8em;
border-radius: 999px;
backdrop-filter: blur(4px);
}
.hero-panel {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: clamp(1.3rem, 3vw, 2rem);
box-shadow: var(--sh-md);
display: flex;
flex-direction: column;
}
.badge {
display: inline-flex;
align-items: center;
gap: 0.45em;
align-self: flex-start;
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--brand-d);
background: rgba(31, 122, 109, 0.1);
padding: 0.4em 0.8em;
border-radius: 999px;
margin-bottom: 0.9rem;
}
.badge .dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--ok);
box-shadow: 0 0 0 0 rgba(47, 158, 111, 0.55);
animation: pulse 1.8s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(47, 158, 111, 0.5); }
70% { box-shadow: 0 0 0 7px rgba(47, 158, 111, 0); }
100% { box-shadow: 0 0 0 0 rgba(47, 158, 111, 0); }
}
.hero-panel h1 { font-size: clamp(1.55rem, 3.2vw, 2.15rem); font-weight: 600; }
.lede { color: var(--ink-2); font-size: 1.02rem; }
/* ---------- thermometer ---------- */
.thermo { margin-top: auto; }
.thermo-top {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
}
.raised {
font-family: "Fraunces", serif;
font-size: clamp(1.6rem, 4vw, 2.2rem);
font-weight: 700;
color: var(--brand-d);
display: inline-block;
margin-right: 0.35em;
}
.goal { color: var(--muted); font-size: 0.92rem; }
.pct {
font-weight: 700;
font-size: 1.1rem;
color: var(--accent-d);
}
.thermo-track {
margin: 0.6rem 0 0.9rem;
height: 16px;
border-radius: 999px;
background: rgba(42, 39, 34, 0.08);
overflow: hidden;
box-shadow: inset 0 1px 2px rgba(42, 39, 34, 0.12);
}
.thermo-fill {
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--brand) 0%, #2f9e8e 50%, var(--accent) 120%);
transition: width 0.25s ease;
position: relative;
}
.thermo-fill::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transform: translateX(-100%);
animation: shimmer 2.4s infinite;
}
@keyframes shimmer { 100% { transform: translateX(100%); } }
.thermo-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.6rem;
margin-bottom: 1.1rem;
}
.stat {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 0.65rem 0.5rem;
text-align: center;
}
.stat strong {
display: block;
font-family: "Fraunces", serif;
font-size: 1.3rem;
color: var(--ink);
}
.stat span { font-size: 0.74rem; color: var(--muted); }
.hero-actions { display: flex; gap: 0.7rem; margin-bottom: 1rem; }
.hero-actions .btn { flex: 1; }
.trust {
list-style: none;
margin: 0;
padding: 0.9rem 0 0;
border-top: 1px dashed var(--line-2);
display: flex;
flex-wrap: wrap;
gap: 0.4rem 1rem;
font-size: 0.82rem;
color: var(--ink-2);
}
/* ---------- impact strip ---------- */
.impact-strip {
max-width: 1180px;
margin: clamp(1.4rem, 4vw, 2.4rem) auto;
padding: 0 clamp(1rem, 4vw, 2.4rem);
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
}
.impact {
background: linear-gradient(180deg, var(--surface), #fff);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 1.1rem 1rem;
text-align: center;
box-shadow: var(--sh-sm);
}
.impact strong {
display: block;
font-family: "Fraunces", serif;
font-size: clamp(1.4rem, 3vw, 1.8rem);
color: var(--brand-d);
}
.impact span { font-size: 0.84rem; color: var(--muted); }
/* ---------- layout ---------- */
.layout {
max-width: 1180px;
margin: 0 auto clamp(2rem, 6vw, 4rem);
padding: 0 clamp(1rem, 4vw, 2.4rem);
display: grid;
grid-template-columns: 1fr 340px;
gap: clamp(1.2rem, 3vw, 2rem);
align-items: start;
}
.main-col { display: grid; gap: clamp(1.2rem, 3vw, 1.8rem); }
.side-col { display: grid; gap: clamp(1.2rem, 3vw, 1.8rem); position: sticky; top: 84px; }
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: clamp(1.2rem, 3vw, 1.8rem);
box-shadow: var(--sh-sm);
}
.card h2 {
font-size: clamp(1.25rem, 2.6vw, 1.55rem);
font-weight: 600;
}
/* ---------- story ---------- */
.photo-block { margin: 1.2rem 0; }
.photo {
border-radius: var(--r-md);
min-height: 200px;
box-shadow: var(--sh-sm);
}
.photo.p1 {
background:
radial-gradient(90% 80% at 75% 20%, rgba(255, 255, 255, 0.3), transparent 50%),
linear-gradient(150deg, #d98a2b, #1f7a6d 90%);
}
.photo-block figcaption {
margin-top: 0.5rem;
font-size: 0.82rem;
color: var(--muted);
}
.story blockquote {
margin: 1.2rem 0 0;
padding: 1rem 1.2rem;
border-left: 4px solid var(--accent);
background: rgba(232, 116, 59, 0.06);
border-radius: 0 var(--r-md) var(--r-md) 0;
font-family: "Fraunces", serif;
font-style: italic;
font-size: 1.05rem;
color: var(--ink);
}
.story blockquote cite {
display: block;
margin-top: 0.6rem;
font: 500 0.85rem "Inter", sans-serif;
font-style: normal;
color: var(--ink-2);
}
/* ---------- tiers ---------- */
.tier-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.8rem;
margin: 1rem 0 1.4rem;
}
.tier {
position: relative;
text-align: left;
display: flex;
flex-direction: column;
gap: 0.25rem;
background: var(--bg);
border: 1.5px solid var(--line);
border-radius: var(--r-md);
padding: 1rem;
cursor: pointer;
font: inherit;
color: inherit;
transition: border-color 0.16s, box-shadow 0.16s, transform 0.12s, background 0.16s;
}
.tier:hover { transform: translateY(-2px); box-shadow: var(--sh-md); border-color: var(--line-2); }
.tier:focus-visible { outline: 3px solid rgba(31, 122, 109, 0.4); outline-offset: 2px; }
.tier[aria-checked="true"] {
border-color: var(--brand);
background: rgba(31, 122, 109, 0.06);
box-shadow: 0 0 0 1px var(--brand) inset, var(--sh-md);
}
.tier-amt {
font-family: "Fraunces", serif;
font-size: 1.4rem;
font-weight: 700;
color: var(--brand-d);
}
.tier-name { font-weight: 700; font-size: 0.96rem; }
.tier-perk { font-size: 0.82rem; color: var(--ink-2); line-height: 1.45; }
.tier .ribbon {
position: absolute;
top: -10px;
right: 12px;
background: var(--accent);
color: #fff;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
padding: 0.28em 0.65em;
border-radius: 999px;
box-shadow: var(--sh-sm);
}
/* ---------- give form ---------- */
.give-form { display: grid; gap: 1rem; }
.custom-field span { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.35rem; }
.amount-input {
display: flex;
align-items: center;
background: var(--bg);
border: 1.5px solid var(--line-2);
border-radius: var(--r-md);
padding: 0 0.9rem;
transition: border-color 0.16s, box-shadow 0.16s;
}
.amount-input:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(31, 122, 109, 0.15); }
.amount-input .currency { color: var(--muted); font-weight: 600; }
.amount-input input {
border: none;
background: transparent;
font: 600 1.1rem "Inter", sans-serif;
color: var(--ink);
padding: 0.7rem 0.5rem;
width: 100%;
outline: none;
}
.freq {
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 0.7rem 0.9rem;
display: flex;
gap: 1.4rem;
align-items: center;
}
.freq legend { font-size: 0.85rem; font-weight: 600; padding: 0 0.4rem; }
.freq label { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.92rem; cursor: pointer; }
.freq input { accent-color: var(--brand); }
.give-summary {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(31, 122, 109, 0.07);
border-radius: var(--r-md);
padding: 0.8rem 1rem;
}
.give-summary span { font-size: 0.9rem; color: var(--ink-2); }
.give-summary strong {
font-family: "Fraunces", serif;
font-size: 1.5rem;
color: var(--brand-d);
}
.match-note { margin: -0.3rem 0 0; font-size: 0.86rem; color: var(--warn); }
.match-note strong { color: var(--accent-d); }
.secure { margin: 0; text-align: center; font-size: 0.8rem; color: var(--muted); }
/* ---------- timeline ---------- */
.timeline { list-style: none; margin: 1rem 0 0; padding: 0; }
.timeline li {
position: relative;
display: grid;
grid-template-columns: 64px 1fr;
gap: 0.9rem;
padding-bottom: 1.4rem;
}
.timeline li::before {
content: "";
position: absolute;
left: 71px;
top: 22px;
bottom: -4px;
width: 2px;
background: var(--line-2);
}
.timeline li:last-child { padding-bottom: 0; }
.timeline li:last-child::before { display: none; }
.timeline .when {
font-size: 0.78rem;
font-weight: 700;
color: var(--muted);
text-align: right;
padding-top: 0.15rem;
}
.timeline .t-body { position: relative; padding-left: 1.1rem; }
.timeline .t-body::before {
content: "";
position: absolute;
left: -3px;
top: 5px;
width: 11px;
height: 11px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 3px rgba(232, 116, 59, 0.2);
}
.timeline h3 { font-size: 1.02rem; font-weight: 600; margin-bottom: 0.2rem; }
.timeline p { margin: 0; font-size: 0.92rem; color: var(--ink-2); }
/* ---------- donors ---------- */
.donors-head { display: flex; align-items: center; justify-content: space-between; }
.donors-head h2 { margin: 0; }
.live {
display: inline-flex;
align-items: center;
gap: 0.4em;
font-size: 0.76rem;
font-weight: 600;
color: var(--ok);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.live-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--ok);
animation: pulse 1.8s infinite;
}
.donor-feed { list-style: none; margin: 1rem 0; padding: 0; display: grid; gap: 0.55rem; }
.donor-feed li {
display: flex;
align-items: center;
gap: 0.7rem;
padding: 0.6rem 0.7rem;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: var(--bg);
animation: slidein 0.45s ease;
}
@keyframes slidein {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
.donor-feed .av {
flex: 0 0 auto;
width: 34px; height: 34px;
border-radius: 50%;
display: grid;
place-items: center;
color: #fff;
font-weight: 700;
font-size: 0.82rem;
}
.donor-feed .d-info { line-height: 1.3; min-width: 0; }
.donor-feed .d-name { font-weight: 600; font-size: 0.9rem; }
.donor-feed .d-meta { font-size: 0.76rem; color: var(--muted); }
.donor-feed .d-amt {
margin-left: auto;
font-family: "Fraunces", serif;
font-weight: 700;
color: var(--brand-d);
white-space: nowrap;
}
.donors-foot { margin: 0; font-size: 0.86rem; color: var(--ink-2); }
/* ---------- share ---------- */
.share-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; margin-top: 0.9rem; }
.share-btn {
font: 600 0.88rem "Inter", sans-serif;
border: 1.5px solid var(--line-2);
background: var(--surface);
color: var(--ink);
border-radius: var(--r-md);
padding: 0.7rem;
cursor: pointer;
transition: border-color 0.16s, transform 0.12s, background 0.16s;
}
.share-btn:hover { border-color: var(--brand); background: rgba(31, 122, 109, 0.05); transform: translateY(-1px); }
.share-btn:active { transform: translateY(0); }
.share-btn:focus-visible { outline: 3px solid rgba(31, 122, 109, 0.4); outline-offset: 2px; }
/* ---------- footer ---------- */
.site-foot {
border-top: 1px solid var(--line);
text-align: center;
padding: 2rem 1.5rem;
color: var(--ink-2);
font-size: 0.88rem;
}
.site-foot .fine { color: var(--muted); font-size: 0.8rem; }
/* ---------- toast ---------- */
.toast-wrap {
position: fixed;
bottom: 18px;
left: 50%;
transform: translateX(-50%);
z-index: 80;
display: grid;
gap: 0.5rem;
width: min(92vw, 360px);
}
.toast {
background: var(--ink);
color: #fff;
padding: 0.8rem 1.1rem;
border-radius: var(--r-md);
box-shadow: var(--sh-lg);
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.6rem;
animation: toastin 0.3s ease;
}
.toast.ok { background: var(--brand-d); }
.toast.out { animation: toastout 0.3s ease forwards; }
@keyframes toastin { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toastout { to { opacity: 0; transform: translateY(12px); } }
/* ---------- responsive ---------- */
@media (max-width: 920px) {
.hero { grid-template-columns: 1fr; }
.hero-media { min-height: 240px; order: -1; }
.layout { grid-template-columns: 1fr; }
.side-col { position: static; }
.impact-strip { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.topnav { display: none; }
.tier-grid { grid-template-columns: 1fr; }
.thermo-stats { grid-template-columns: repeat(3, 1fr); gap: 0.4rem; }
.stat strong { font-size: 1.1rem; }
.share-row { grid-template-columns: 1fr; }
.freq { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
.hero-actions { flex-direction: column; }
}
@media (prefers-reduced-motion: reduce) {
*, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}(function () {
"use strict";
/* ---------- campaign state ---------- */
var GOAL = 85000;
var COST_PER_WELL = 7000;
var raised = 61840; // current raised
var donors = 1284; // current donor count
var selectedAmount = 450; // default tier
var frequency = "once";
var fmt = function (n) {
return "$" + Math.round(n).toLocaleString("en-US");
};
/* ---------- toast helper ---------- */
var toastWrap = document.getElementById("toastWrap");
function toast(msg, kind) {
var el = document.createElement("div");
el.className = "toast" + (kind ? " " + kind : "");
el.textContent = msg;
toastWrap.appendChild(el);
setTimeout(function () {
el.classList.add("out");
setTimeout(function () { el.remove(); }, 320);
}, 2600);
}
/* ---------- animated thermometer ---------- */
var thermoFill = document.getElementById("thermoFill");
var thermoBar = document.getElementById("thermoBar");
var raisedAmt = document.getElementById("raisedAmt");
var pctLabel = document.getElementById("pctLabel");
var donorCount = document.getElementById("donorCount");
var wellsCount = document.getElementById("wellsCount");
var donorsFoot = document.getElementById("donorsFoot");
function renderThermo(animate) {
var pct = Math.min(100, (raised / GOAL) * 100);
thermoFill.style.width = pct.toFixed(1) + "%";
pctLabel.textContent = Math.round(pct) + "%";
thermoBar.setAttribute("aria-valuenow", String(Math.round(raised)));
wellsCount.textContent = String(Math.floor(raised / COST_PER_WELL));
if (donorsFoot) {
donorsFoot.innerHTML = "Join <strong>" + donors.toLocaleString("en-US") + "</strong> generous people.";
}
if (!animate) {
raisedAmt.textContent = fmt(raised);
donorCount.textContent = donors.toLocaleString("en-US");
}
}
// count-up animation on first paint
function countUp(el, target, fmtFn, dur) {
var start = performance.now();
function step(now) {
var t = Math.min(1, (now - start) / dur);
var eased = 1 - Math.pow(1 - t, 3);
el.textContent = fmtFn(target * eased);
if (t < 1) requestAnimationFrame(step);
else el.textContent = fmtFn(target);
}
requestAnimationFrame(step);
}
function intro() {
countUp(raisedAmt, raised, fmt, 1100);
countUp(donorCount, donors, function (n) { return Math.round(n).toLocaleString("en-US"); }, 1100);
requestAnimationFrame(function () { renderThermo(true); });
}
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
renderThermo(false);
} else {
setTimeout(intro, 180);
}
/* ---------- tier selection ---------- */
var tierGrid = document.getElementById("tierGrid");
var tiers = Array.prototype.slice.call(tierGrid.querySelectorAll(".tier"));
var customAmt = document.getElementById("customAmt");
var summaryAmt = document.getElementById("summaryAmt");
var matchNote = document.getElementById("matchNote");
var donateBtn = document.getElementById("donateBtn");
function updateSummary() {
summaryAmt.textContent = fmt(selectedAmount);
matchNote.innerHTML = "Matched to <strong>" + fmt(selectedAmount * 2) + "</strong> by the Marigold Trust.";
var label = "Donate " + fmt(selectedAmount);
if (frequency === "monthly") label += "/mo";
donateBtn.textContent = label;
}
function selectTier(btn) {
tiers.forEach(function (t) { t.setAttribute("aria-checked", t === btn ? "true" : "false"); });
selectedAmount = parseInt(btn.getAttribute("data-amount"), 10);
customAmt.value = "";
updateSummary();
}
tiers.forEach(function (btn) {
btn.addEventListener("click", function () { selectTier(btn); });
btn.addEventListener("keydown", function (e) {
var idx = tiers.indexOf(btn);
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
e.preventDefault();
var next = tiers[(idx + 1) % tiers.length];
next.focus(); selectTier(next);
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
e.preventDefault();
var prev = tiers[(idx - 1 + tiers.length) % tiers.length];
prev.focus(); selectTier(prev);
}
});
});
customAmt.addEventListener("input", function () {
var v = parseFloat(customAmt.value);
if (!isNaN(v) && v > 0) {
selectedAmount = v;
tiers.forEach(function (t) { t.setAttribute("aria-checked", "false"); });
updateSummary();
}
});
document.querySelectorAll('input[name="freq"]').forEach(function (r) {
r.addEventListener("change", function () {
if (r.checked) { frequency = r.value; updateSummary(); }
});
});
/* ---------- donate submit ---------- */
document.getElementById("giveForm").addEventListener("submit", function (e) {
e.preventDefault();
if (!selectedAmount || selectedAmount <= 0) {
toast("Please choose or enter a valid amount.");
customAmt.focus();
return;
}
addDonor("You", selectedAmount, true);
raised += selectedAmount;
donors += 1;
renderThermo(false);
var word = frequency === "monthly" ? "monthly gift" : "gift";
toast("Thank you! Your " + word + " of " + fmt(selectedAmount) + " brings Loma Verde closer.", "ok");
});
/* ---------- live donor feed ---------- */
var donorFeed = document.getElementById("donorFeed");
var FIRST = ["Inés", "Marcus", "Priya", "Dao", "Lena", "Tomás", "Aisha", "Noah", "Yuki", "Sofia", "Kwame", "Mei", "Diego", "Hana"];
var LAST = ["C.", "R.", "M.", "Okafor", "Nguyen", "Patel", "Lund", "Ortiz", "Bauer", "Reyes", "Santos", "Adeyemi"];
var WHERE = ["Portland, US", "Lyon, FR", "Mumbai, IN", "Berlin, DE", "Bogotá, CO", "Osaka, JP", "Nairobi, KE", "Toronto, CA", "anonymous donor"];
var COLORS = ["#1f7a6d", "#e8743b", "#2f9e6f", "#d98a2b", "#155e54", "#cc5d28"];
var AMOUNTS = [25, 35, 50, 75, 100, 120, 150, 200, 250, 450, 500];
function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
function avatar(name) {
var s = document.createElement("span");
s.className = "av";
s.style.background = pick(COLORS);
s.textContent = name.charAt(0).toUpperCase();
s.setAttribute("aria-hidden", "true");
return s;
}
function addDonor(name, amount, isYou) {
var li = document.createElement("li");
var av = avatar(name);
var info = document.createElement("div");
info.className = "d-info";
var place = isYou ? "just now · thank you!" : pick(WHERE) + " · " + (Math.floor(Math.random() * 9) + 1) + "m ago";
info.innerHTML = '<div class="d-name">' + name + '</div><div class="d-meta">' + place + "</div>";
var amt = document.createElement("span");
amt.className = "d-amt";
amt.textContent = fmt(amount);
li.appendChild(av);
li.appendChild(info);
li.appendChild(amt);
donorFeed.insertBefore(li, donorFeed.firstChild);
while (donorFeed.children.length > 6) {
donorFeed.removeChild(donorFeed.lastChild);
}
}
// seed initial feed
var seed = [
["Priya Patel", 250], ["Marcus R.", 50], ["anonymous donor", 35], ["Sofia Reyes", 120], ["Noah Lund", 75]
];
seed.forEach(function (d) { addDonor(d[0], d[1], false); });
// simulated live donations
function liveTick() {
var name = pick(FIRST) + " " + pick(LAST);
if (Math.random() < 0.18) name = "anonymous donor";
var amount = pick(AMOUNTS);
addDonor(name, amount, false);
raised = Math.min(GOAL, raised + amount);
donors += 1;
renderThermo(false);
}
var feedTimer = setInterval(liveTick, 6500);
document.addEventListener("visibilitychange", function () {
if (document.hidden) { clearInterval(feedTimer); }
else { feedTimer = setInterval(liveTick, 6500); }
});
/* ---------- sharing ---------- */
var SHARE_URL = "https://brightfutures.example/clean-water-now";
var SHARE_TEXT = "Help build 12 wells for Loma Verde — clean water changes everything.";
function openShare(net) {
var u = encodeURIComponent(SHARE_URL);
var t = encodeURIComponent(SHARE_TEXT);
var map = {
x: "https://twitter.com/intent/tweet?text=" + t + "&url=" + u,
facebook: "https://www.facebook.com/sharer/sharer.php?u=" + u,
whatsapp: "https://wa.me/?text=" + t + "%20" + u
};
if (map[net]) {
window.open(map[net], "_blank", "noopener,width=620,height=520");
toast("Opening share window — thank you for spreading the word!", "ok");
}
}
function copyLink() {
var done = function () { toast("Campaign link copied to clipboard!", "ok"); };
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(SHARE_URL).then(done, function () { toast("Copy failed — link: " + SHARE_URL); });
} else {
var ta = document.createElement("textarea");
ta.value = SHARE_URL;
document.body.appendChild(ta); ta.select();
try { document.execCommand("copy"); done(); } catch (e) { toast("Copy this link: " + SHARE_URL); }
ta.remove();
}
}
document.querySelectorAll(".share-btn").forEach(function (b) {
b.addEventListener("click", function () {
var net = b.getAttribute("data-net");
if (net === "copy") copyLink();
else openShare(net);
});
});
document.getElementById("shareTop").addEventListener("click", function () {
if (navigator.share) {
navigator.share({ title: "Clean Water Now", text: SHARE_TEXT, url: SHARE_URL })
.then(function () { toast("Thanks for sharing!", "ok"); })
.catch(function () {});
} else {
copyLink();
}
});
/* init */
updateSummary();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Clean Water Now — Bright Futures Foundation</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,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>
<header class="topbar">
<a class="brand" href="#" aria-label="Bright Futures Foundation home">
<span class="brand-mark" aria-hidden="true">◍</span>
<span class="brand-name">Bright Futures <em>Foundation</em></span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#story">The Story</a>
<a href="#tiers">Give</a>
<a href="#updates">Updates</a>
</nav>
<a class="btn btn-accent btn-sm topdonate" href="#tiers">Donate</a>
</header>
<main>
<section class="hero" id="top">
<div class="hero-media" role="img" aria-label="Children collecting clean water from a new village well at sunrise">
<span class="photo-tag">📍 Loma Verde village · field photo</span>
</div>
<div class="hero-panel" aria-labelledby="campaign-title">
<span class="badge"><span class="dot" aria-hidden="true"></span> Active campaign</span>
<h1 id="campaign-title">Clean Water Now: 12 Wells for Loma Verde</h1>
<p class="lede">Every dry season, families here walk three hours for water that makes their children sick. Together we can build wells that change that — for good.</p>
<div class="thermo" role="group" aria-label="Fundraising progress">
<div class="thermo-top">
<div>
<strong class="raised" id="raisedAmt">$0</strong>
<span class="goal">raised of <span id="goalAmt">$85,000</span> goal</span>
</div>
<div class="pct" id="pctLabel">0%</div>
</div>
<div class="thermo-track" role="progressbar" aria-valuemin="0" aria-valuemax="85000" aria-valuenow="0" id="thermoBar">
<div class="thermo-fill" id="thermoFill"></div>
</div>
<div class="thermo-stats">
<div class="stat"><strong id="donorCount">0</strong><span>donors</span></div>
<div class="stat"><strong id="daysLeft">19</strong><span>days left</span></div>
<div class="stat"><strong id="wellsCount">0</strong><span>of 12 wells funded</span></div>
</div>
<div class="hero-actions">
<a class="btn btn-accent" href="#tiers">Give now</a>
<button class="btn btn-ghost" id="shareTop" type="button">Share</button>
</div>
<ul class="trust" aria-label="Trust and transparency">
<li>✔ Registered charity #41-2287</li>
<li>✔ Tax-deductible</li>
<li>✔ 89¢ of every $1 to the field</li>
</ul>
</div>
</div>
</section>
<section class="impact-strip" aria-label="Lifetime impact">
<div class="impact"><strong>61,400</strong><span>people reached</span></div>
<div class="impact"><strong>147</strong><span>wells built</span></div>
<div class="impact"><strong>23</strong><span>villages served</span></div>
<div class="impact"><strong>11 yrs</strong><span>on the ground</span></div>
</section>
<div class="layout">
<div class="main-col">
<section class="card story" id="story">
<h2>Why Loma Verde, why now</h2>
<p>Loma Verde sits at the dry edge of the highlands, where the nearest safe water is a half-day round trip. Mothers like <strong>Inés Carral</strong> leave before dawn so their children can drink — time stolen from school, work, and rest.</p>
<p>Our field engineers surveyed twelve sites this spring. Each well serves around <strong>340 people</strong>, runs on a solar pump the community is trained to maintain, and pays for itself in health and hours within a single year. Drill one well and a whole neighborhood stops getting sick from the water they need to live.</p>
<figure class="photo-block">
<div class="photo p1" role="img" aria-label="Field engineer testing water flow at well site two"></div>
<figcaption>Site survey, March 2026 — well #2 cleared for drilling.</figcaption>
</figure>
<blockquote>
“The first well changed our mornings. Now the children walk to school, not to the river.”
<cite>— Inés Carral, Loma Verde community council</cite>
</blockquote>
</section>
<section class="card tiers" id="tiers" aria-labelledby="tiers-h">
<h2 id="tiers-h">Choose your gift</h2>
<p class="muted">Pick a tier or enter your own amount. Every gift is matched 2× this week by the Marigold Trust.</p>
<div class="tier-grid" id="tierGrid" role="radiogroup" aria-label="Donation tiers">
<button class="tier" role="radio" aria-checked="false" data-amount="35">
<span class="tier-amt">$35</span>
<span class="tier-name">Clean Start</span>
<span class="tier-perk">Safe water for one child for a year · thank-you postcard</span>
</button>
<button class="tier" role="radio" aria-checked="false" data-amount="120">
<span class="tier-amt">$120</span>
<span class="tier-name">Family Tap</span>
<span class="tier-perk">Supplies a household · name in the donor wall</span>
</button>
<button class="tier popular" role="radio" aria-checked="true" data-amount="450">
<span class="ribbon">Most loved</span>
<span class="tier-amt">$450</span>
<span class="tier-name">Village Share</span>
<span class="tier-perk">Funds a well section · field-update letters · etched brick</span>
</button>
<button class="tier" role="radio" aria-checked="false" data-amount="7000">
<span class="tier-amt">$7,000</span>
<span class="tier-name">Found a Well</span>
<span class="tier-perk">A full well in your name · plaque · site visit invite</span>
</button>
</div>
<form class="give-form" id="giveForm" novalidate>
<label class="custom-field">
<span>Or enter an amount</span>
<div class="amount-input">
<span class="currency" aria-hidden="true">$</span>
<input type="number" id="customAmt" inputmode="decimal" min="1" step="1" placeholder="250" aria-label="Custom donation amount in dollars" />
</div>
</label>
<fieldset class="freq">
<legend>Frequency</legend>
<label><input type="radio" name="freq" value="once" checked /> One-time</label>
<label><input type="radio" name="freq" value="monthly" /> Monthly</label>
</fieldset>
<div class="give-summary" id="giveSummary" aria-live="polite">
<span>Your gift</span>
<strong id="summaryAmt">$450</strong>
</div>
<p class="match-note" id="matchNote">Matched to <strong>$900</strong> by the Marigold Trust.</p>
<button class="btn btn-accent btn-block" type="submit" id="donateBtn">Donate $450</button>
<p class="secure">🔒 Secure · cancel anytime · 100% refundable for 30 days</p>
</form>
</section>
<section class="card updates" id="updates" aria-labelledby="updates-h">
<h2 id="updates-h">Campaign updates</h2>
<ol class="timeline">
<li>
<span class="when">Jun 14</span>
<div class="t-body">
<h3>Well #3 is flowing 💧</h3>
<p>The community celebrated first water at site three on Saturday. Pump pressure tested strong — that's three wells live and nine to go.</p>
</div>
</li>
<li>
<span class="when">Jun 2</span>
<div class="t-body">
<h3>Marigold Trust doubles every gift</h3>
<p>Our partners committed a 2× match for the next two weeks, putting the final wells within reach faster than planned.</p>
</div>
</li>
<li>
<span class="when">May 18</span>
<div class="t-body">
<h3>Drilling crew on site</h3>
<p>Equipment cleared customs and reached Loma Verde. Local apprentices began the maintenance training program alongside our engineers.</p>
</div>
</li>
</ol>
</section>
</div>
<aside class="side-col" aria-label="Donor activity and sharing">
<section class="card donors">
<div class="donors-head">
<h2>Recent donors</h2>
<span class="live"><span class="live-dot" aria-hidden="true"></span> Live</span>
</div>
<ul class="donor-feed" id="donorFeed" aria-live="polite"></ul>
<p class="donors-foot" id="donorsFoot">Join <strong>0</strong> generous people.</p>
</section>
<section class="card share">
<h2>Spread the word</h2>
<p class="muted">Sharing doubles a campaign's reach. Take ten seconds.</p>
<div class="share-row">
<button class="share-btn" data-net="x" type="button">𝕏 Post</button>
<button class="share-btn" data-net="facebook" type="button">f Share</button>
<button class="share-btn" data-net="whatsapp" type="button">✆ WhatsApp</button>
<button class="share-btn" data-net="copy" type="button">🔗 Copy link</button>
</div>
</section>
</aside>
</div>
</main>
<footer class="site-foot">
<p><strong>Bright Futures Foundation</strong> · Registered charity #41-2287 · Donations are tax-deductible.</p>
<p class="fine">Illustrative demo — fictional organization, not a real charity or donation system.</p>
</footer>
<div class="toast-wrap" id="toastWrap" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Campaign / Fundraiser
A complete fundraiser landing page for the fictional Bright Futures Foundation and its “Clean Water Now: 12 Wells for Loma Verde” campaign. The hero pairs a warm field-photo placeholder with a glass donation panel: an animated goal thermometer counts up to the amount raised, and stat chips track donors, days left, and how many of the twelve wells are funded. Impact numbers and trust badges (registered charity, tax-deductible, 89¢-to-the-field) reinforce transparency.
The giving card presents four donation tiers with perks — keyboard-navigable as a radio group — plus a custom amount field and a one-time / monthly toggle. The summary and donate button update live, and a 2× matching note doubles the displayed gift in real time. Submitting drops your gift into the donor feed, bumps the thermometer, and fires a thank-you toast. Alongside, a simulated live donor feed streams new contributions every few seconds, an updates timeline shares campaign milestones, and the share row posts to X, Facebook, and WhatsApp or copies the link (using the Web Share API where available).
Everything is self-contained vanilla HTML, CSS, and JavaScript: count-up animations, a shimmering
progress fill, accessible buttons and inputs, a toast() helper, and a fully responsive layout that
collapses gracefully down to about 360px.
Illustrative UI only — fictional organization, not a real charity or donation system.