Nonprofit — Donor Wall
A warm, hopeful donor recognition wall for nonprofits and charities. Supporters are grouped into platinum, gold, and silver giving tiers with colored avatars built from their initials, a tier-colored accent border, and optional gift amounts that can be revealed or kept private. Visitors can filter by tier, search by name, and watch matching supporters highlight on hover. Animated impact counters, trust badges, and a prominent donate call-to-action round out the experience.
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;
--plat: #6c7a89;
--gold: #c79224;
--silver: #9aa3ac;
--shadow-sm: 0 1px 2px rgba(42, 39, 34, 0.06), 0 4px 14px rgba(42, 39, 34, 0.05);
--shadow-md: 0 10px 30px rgba(42, 39, 34, 0.1);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, .brand__name {
font-family: "Fraunces", Georgia, serif;
line-height: 1.15;
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: 26px 22px 60px;
}
/* Masthead */
.masthead {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: 14px; }
.brand__mark {
width: 48px; height: 48px;
display: grid; place-items: center;
border-radius: 14px;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff;
font-family: "Fraunces", serif;
font-weight: 700;
letter-spacing: 0.5px;
box-shadow: var(--shadow-sm);
}
.brand__name { margin: 0; font-size: 1.18rem; font-weight: 700; }
.brand__tag { margin: 0; font-size: 0.82rem; color: var(--muted); }
.trust { display: flex; gap: 8px; flex-wrap: wrap; }
.badge {
font-size: 0.72rem;
font-weight: 600;
color: var(--brand-d);
background: rgba(31, 122, 109, 0.1);
border: 1px solid rgba(31, 122, 109, 0.22);
padding: 5px 10px;
border-radius: 999px;
}
/* Hero */
.hero {
margin-top: 30px;
background:
radial-gradient(120% 140% at 100% 0%, rgba(232, 116, 59, 0.12), transparent 55%),
var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 30px 30px 26px;
box-shadow: var(--shadow-sm);
}
.eyebrow {
margin: 0 0 6px;
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.72rem;
font-weight: 700;
color: var(--accent-d);
}
.hero h1 { margin: 0 0 10px; font-size: clamp(1.9rem, 4.5vw, 2.9rem); font-weight: 600; }
.hero__lede { margin: 0; max-width: 56ch; color: var(--ink-2); }
.impact {
margin-top: 24px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
}
.impact__item {
border: 1px solid var(--line);
background: var(--bg);
border-radius: var(--r-md);
padding: 14px 16px;
}
.impact__num {
display: block;
font-family: "Fraunces", serif;
font-weight: 700;
font-size: 1.6rem;
color: var(--brand-d);
font-variant-numeric: tabular-nums;
}
.impact__label { font-size: 0.78rem; color: var(--muted); }
/* Controls */
.controls {
margin-top: 26px;
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
}
.search {
position: relative;
flex: 1 1 240px;
min-width: 200px;
}
.search__icon {
position: absolute;
left: 14px; top: 50%;
transform: translateY(-50%);
width: 18px; height: 18px;
color: var(--muted);
pointer-events: none;
}
#search {
width: 100%;
padding: 12px 14px 12px 40px;
border: 1px solid var(--line-2);
border-radius: var(--r-md);
background: var(--surface);
font: inherit;
color: var(--ink);
}
#search:focus-visible {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(31, 122, 109, 0.18);
}
.tiers { display: flex; gap: 6px; flex-wrap: wrap; }
.tier-btn {
display: inline-flex;
align-items: center;
gap: 7px;
border: 1px solid var(--line-2);
background: var(--surface);
color: var(--ink-2);
padding: 9px 14px;
border-radius: 999px;
font: inherit;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.1s;
}
.tier-btn:hover { border-color: var(--brand); }
.tier-btn:active { transform: scale(0.97); }
.tier-btn.is-active {
background: var(--brand);
border-color: var(--brand);
color: #fff;
}
.tier-btn:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(31, 122, 109, 0.22);
}
.dot { width: 9px; height: 9px; border-radius: 50%; }
.dot--platinum { background: var(--plat); }
.dot--gold { background: var(--gold); }
.dot--silver { background: var(--silver); }
.amount-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
font-weight: 500;
color: var(--ink-2);
cursor: pointer;
user-select: none;
}
.amount-toggle input { width: 16px; height: 16px; accent-color: var(--brand); cursor: pointer; }
.result-line {
margin: 18px 0 0;
font-size: 0.82rem;
color: var(--muted);
}
/* Wall */
.wall {
margin-top: 14px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 14px;
}
.donor {
display: flex;
align-items: center;
gap: 13px;
padding: 14px;
background: var(--surface);
border: 1px solid var(--line);
border-left: 4px solid var(--tier-color, var(--line-2));
border-radius: var(--r-md);
box-shadow: var(--shadow-sm);
transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
}
.donor:hover,
.donor:focus-within {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
}
.donor.is-dim { opacity: 0.38; }
.donor--platinum { --tier-color: var(--plat); }
.donor--gold { --tier-color: var(--gold); }
.donor--silver { --tier-color: var(--silver); }
.avatar {
width: 46px; height: 46px;
flex: 0 0 auto;
border-radius: 50%;
display: grid;
place-items: center;
color: #fff;
font-weight: 700;
font-size: 0.95rem;
letter-spacing: 0.5px;
}
.donor__body { min-width: 0; flex: 1; }
.donor__name {
margin: 0;
font-weight: 600;
font-size: 0.96rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.donor__meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.76rem;
color: var(--muted);
margin-top: 2px;
}
.tier-chip {
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 700;
font-size: 0.66rem;
padding: 2px 7px;
border-radius: 999px;
color: #fff;
background: var(--tier-color);
}
.donor__amount {
font-weight: 600;
color: var(--brand-d);
font-variant-numeric: tabular-nums;
}
.donor__amount.is-hidden { color: var(--muted); font-weight: 500; }
.empty {
margin: 30px 0;
text-align: center;
color: var(--muted);
font-style: italic;
}
/* Join */
.join {
margin-top: 36px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
flex-wrap: wrap;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff;
border-radius: var(--r-lg);
padding: 26px 28px;
box-shadow: var(--shadow-md);
}
.join h2 { margin: 0 0 4px; font-size: 1.4rem; font-weight: 600; }
.join p { margin: 0; color: rgba(255, 255, 255, 0.86); font-size: 0.9rem; }
.btn {
font: inherit;
font-weight: 700;
border: none;
border-radius: var(--r-md);
cursor: pointer;
padding: 13px 22px;
transition: transform 0.1s, box-shadow 0.16s, background 0.16s;
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.6); }
.btn--donate {
background: var(--accent);
color: #fff;
box-shadow: 0 8px 20px rgba(232, 116, 59, 0.4);
}
.btn--donate:hover { background: var(--accent-d); }
.foot {
margin-top: 30px;
text-align: center;
font-size: 0.78rem;
color: var(--muted);
}
.foot__sep { opacity: 0.5; margin: 0 4px; }
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 18px);
background: var(--ink);
color: #fff;
padding: 12px 20px;
border-radius: var(--r-md);
font-size: 0.88rem;
font-weight: 500;
box-shadow: var(--shadow-md);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 50;
max-width: 90vw;
}
.toast.is-show { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 720px) {
.impact { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
.page { padding: 20px 16px 50px; }
.hero { padding: 22px 18px; }
.controls { gap: 10px; }
.search { flex-basis: 100%; }
.join { padding: 22px 20px; }
.btn--donate { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; }
}(function () {
"use strict";
// ---- Fictional donor data -------------------------------------------------
var DONORS = [
{ name: "Marisol & Aren Vega", tier: "platinum", amount: 50000 },
{ name: "The Linwood Family Trust", tier: "platinum", amount: 42000 },
{ name: "Okonkwo Brothers Co.", tier: "platinum", amount: 38500 },
{ name: "Dr. Priya Raman", tier: "platinum", amount: 30000 },
{ name: "Harbor Light Collective", tier: "platinum", amount: 27500 },
{ name: "Tomás & Wren Castillo", tier: "gold", amount: 18000 },
{ name: "Nadia Fennimore", tier: "gold", amount: 14200 },
{ name: "Greenfield PTA", tier: "gold", amount: 12500 },
{ name: "Samuel Adeyemi", tier: "gold", amount: 11000 },
{ name: "Holloway & Quinn LLP", tier: "gold", amount: 9800 },
{ name: "Ingrid Sørensen", tier: "gold", amount: 8500 },
{ name: "The Marsh Bakery", tier: "gold", amount: 7600 },
{ name: "Devon Park Runners", tier: "gold", amount: 6900 },
{ name: "Aiko Tanaka", tier: "silver", amount: 3800 },
{ name: "Carlos & June Mbeki", tier: "silver", amount: 3200 },
{ name: "The Felder Household", tier: "silver", amount: 2750 },
{ name: "Renata Volkov", tier: "silver", amount: 2400 },
{ name: "Hassan Qureshi", tier: "silver", amount: 1900 },
{ name: "Maple Street Book Club", tier: "silver", amount: 1500 },
{ name: "Olu & Beth Adesanya", tier: "silver", amount: 1200 },
{ name: "Tessa Lindgren", tier: "silver", amount: 950 },
{ name: "The Pham Family", tier: "silver", amount: 700 },
{ name: "Gwen Mortimer", tier: "silver", amount: 500 },
{ name: "Anonymous Friend", tier: "silver", amount: 250 }
];
var AVATAR_COLORS = [
"#1f7a6d", "#cc5d28", "#3d6db0", "#8a5fb0", "#b04f6b",
"#2f9e6f", "#c79224", "#5a7a4a", "#a05c3a", "#4a6b8a"
];
// ---- DOM refs -------------------------------------------------------------
var wall = document.getElementById("wall");
var searchInput = document.getElementById("search");
var tierBtns = Array.prototype.slice.call(document.querySelectorAll(".tier-btn"));
var showAmounts = document.getElementById("show-amounts");
var resultLine = document.getElementById("result-line");
var emptyMsg = document.getElementById("empty");
var donateBtn = document.getElementById("donate-btn");
var toastEl = document.getElementById("toast");
var state = { tier: "all", query: "", amounts: false };
// ---- Helpers --------------------------------------------------------------
function initials(name) {
var clean = name.replace(/\b(The|&|Co\.|LLP|Trust|Family|Household|Collective|Brothers|Club|PTA)\b/gi, " ");
var parts = clean.trim().split(/\s+/).filter(Boolean);
if (!parts.length) parts = name.trim().split(/\s+/);
var a = parts[0] ? parts[0][0] : "";
var b = parts.length > 1 ? parts[parts.length - 1][0] : "";
return (a + b).toUpperCase();
}
function colorFor(name) {
var sum = 0;
for (var i = 0; i < name.length; i++) sum += name.charCodeAt(i);
return AVATAR_COLORS[sum % AVATAR_COLORS.length];
}
function money(n) {
return "$" + n.toLocaleString("en-US");
}
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2600);
}
// ---- Render ---------------------------------------------------------------
function buildCard(d) {
var card = document.createElement("article");
card.className = "donor donor--" + d.tier;
card.tabIndex = 0;
card.dataset.tier = d.tier;
card.dataset.name = d.name.toLowerCase();
var avatar = document.createElement("div");
avatar.className = "avatar";
avatar.style.background = colorFor(d.name);
avatar.setAttribute("aria-hidden", "true");
avatar.textContent = initials(d.name);
var body = document.createElement("div");
body.className = "donor__body";
var nm = document.createElement("p");
nm.className = "donor__name";
nm.textContent = d.name;
var meta = document.createElement("div");
meta.className = "donor__meta";
var chip = document.createElement("span");
chip.className = "tier-chip";
chip.textContent = d.tier;
var amt = document.createElement("span");
amt.className = "donor__amount is-hidden";
amt.dataset.amount = d.amount;
amt.textContent = "Gift recognized";
meta.appendChild(chip);
meta.appendChild(amt);
body.appendChild(nm);
body.appendChild(meta);
card.appendChild(avatar);
card.appendChild(body);
return card;
}
function render() {
var frag = document.createDocumentFragment();
DONORS.forEach(function (d) {
frag.appendChild(buildCard(d));
});
wall.appendChild(frag);
}
// ---- Filtering ------------------------------------------------------------
function applyFilters() {
var q = state.query.trim().toLowerCase();
var cards = wall.querySelectorAll(".donor");
var visible = 0;
cards.forEach(function (card) {
var matchTier = state.tier === "all" || card.dataset.tier === state.tier;
var matchQuery = !q || card.dataset.name.indexOf(q) !== -1;
var show = matchTier && matchQuery;
card.hidden = !show;
if (show) visible++;
});
emptyMsg.hidden = visible !== 0;
var label = state.tier === "all" ? "donors" : state.tier + " donors";
resultLine.textContent = "Showing " + visible + " " + label +
(q ? ' matching "' + state.query.trim() + '"' : "") + ".";
}
function setAmounts(on) {
state.amounts = on;
wall.querySelectorAll(".donor__amount").forEach(function (el) {
if (on) {
el.textContent = money(parseInt(el.dataset.amount, 10));
el.classList.remove("is-hidden");
} else {
el.textContent = "Gift recognized";
el.classList.add("is-hidden");
}
});
}
// ---- Hover highlight (dim others of different tier) ------------------------
wall.addEventListener("mouseover", function (e) {
var card = e.target.closest(".donor");
if (!card || card.hidden) return;
var tier = card.dataset.tier;
wall.querySelectorAll(".donor").forEach(function (c) {
if (!c.hidden) c.classList.toggle("is-dim", c.dataset.tier !== tier);
});
});
wall.addEventListener("mouseleave", function () {
wall.querySelectorAll(".donor").forEach(function (c) {
c.classList.remove("is-dim");
});
});
// ---- Events ---------------------------------------------------------------
tierBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
tierBtns.forEach(function (b) {
b.classList.remove("is-active");
b.setAttribute("aria-selected", "false");
});
btn.classList.add("is-active");
btn.setAttribute("aria-selected", "true");
state.tier = btn.dataset.tier;
applyFilters();
});
});
searchInput.addEventListener("input", function () {
state.query = searchInput.value;
applyFilters();
});
showAmounts.addEventListener("change", function () {
setAmounts(showAmounts.checked);
toast(showAmounts.checked ? "Gift amounts visible" : "Gift amounts hidden");
});
donateBtn.addEventListener("click", function () {
toast("Thank you! Redirecting to our secure donation page…");
});
// ---- Animated impact counters --------------------------------------------
function animateCounters() {
document.querySelectorAll(".impact__num").forEach(function (el) {
var target = parseInt(el.dataset.count, 10);
var dur = 1100;
var start = performance.now();
function step(now) {
var p = Math.min((now - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(target * eased).toLocaleString("en-US");
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
});
}
// ---- Init -----------------------------------------------------------------
render();
applyFilters();
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
document.querySelectorAll(".impact__num").forEach(function (el) {
el.textContent = parseInt(el.dataset.count, 10).toLocaleString("en-US");
});
} else {
animateCounters();
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bright Roots Foundation — Donor Wall</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,[email protected],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>
<div class="page">
<header class="masthead">
<div class="brand">
<span class="brand__mark" aria-hidden="true">BR</span>
<div>
<p class="brand__name">Bright Roots Foundation</p>
<p class="brand__tag">Clean water & schools in rural communities</p>
</div>
</div>
<div class="trust" role="group" aria-label="Trust badges">
<span class="badge">Registered Charity #84-2017</span>
<span class="badge">Tax-deductible</span>
</div>
</header>
<section class="hero" aria-labelledby="wall-title">
<p class="eyebrow">Our Generous Supporters</p>
<h1 id="wall-title">The Donor Wall</h1>
<p class="hero__lede">Every name here helped build a well, fund a classroom, or hand a child their first textbook. Thank you for rooting for brighter futures.</p>
<div class="impact" role="list" aria-label="Impact this year">
<div class="impact__item" role="listitem">
<span class="impact__num" data-count="412800">0</span>
<span class="impact__label">meals served</span>
</div>
<div class="impact__item" role="listitem">
<span class="impact__num" data-count="37">0</span>
<span class="impact__label">wells built</span>
</div>
<div class="impact__item" role="listitem">
<span class="impact__num" data-count="1290">0</span>
<span class="impact__label">students enrolled</span>
</div>
<div class="impact__item" role="listitem">
<span class="impact__num" data-count="208">0</span>
<span class="impact__label">named donors</span>
</div>
</div>
</section>
<section class="controls" aria-label="Filter donors">
<div class="search">
<svg viewBox="0 0 24 24" aria-hidden="true" class="search__icon"><path d="M21 21l-4.3-4.3M11 19a8 8 0 110-16 8 8 0 010 16z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<input id="search" type="search" placeholder="Search donors by name…" aria-label="Search donors by name" autocomplete="off" />
</div>
<div class="tiers" role="tablist" aria-label="Filter by giving tier">
<button class="tier-btn is-active" role="tab" aria-selected="true" data-tier="all">All</button>
<button class="tier-btn" role="tab" aria-selected="false" data-tier="platinum"><span class="dot dot--platinum"></span>Platinum</button>
<button class="tier-btn" role="tab" aria-selected="false" data-tier="gold"><span class="dot dot--gold"></span>Gold</button>
<button class="tier-btn" role="tab" aria-selected="false" data-tier="silver"><span class="dot dot--silver"></span>Silver</button>
</div>
<label class="amount-toggle">
<input type="checkbox" id="show-amounts" />
<span>Show gift amounts</span>
</label>
</section>
<p class="result-line" id="result-line" aria-live="polite"></p>
<main id="wall" class="wall" aria-label="Donor list"></main>
<p class="empty" id="empty" hidden>No donors match your search. Try another name or tier.</p>
<section class="join" aria-labelledby="join-title">
<div class="join__copy">
<h2 id="join-title">Add your name to the wall</h2>
<p>Join 208 supporters changing lives. Gifts of $250+ are recognized here for one year.</p>
</div>
<button class="btn btn--donate" id="donate-btn">Donate & join the wall</button>
</section>
<footer class="foot">
<p>Bright Roots Foundation is a fictional 501(c)(3) created for demonstration. <span class="foot__sep">·</span> 100% of demo gifts fund nothing real.</p>
</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Donor Wall
A donor recognition wall built for nonprofits and charities that want to thank supporters publicly while keeping the experience warm and human. Each donor is rendered as a card with an initials avatar, a tier chip (platinum, gold, or silver), and a tier-colored accent border, so the giving hierarchy reads at a glance without feeling transactional. Gift amounts stay private by default and can be revealed with a single toggle for organizations that prefer full transparency.
The header pairs trust signals — a registered charity number and a tax-deductible badge — with animated impact counters (“412,800 meals served”, “37 wells built”) that count up on load to make the work feel tangible. A prominent donate call-to-action invites visitors to add their own name to the wall.
Interactions are vanilla JavaScript with no dependencies: a tier filter built as an accessible tablist, a live name search, a show-amounts toggle backed by a small toast helper, and a hover highlight that dims donors outside the hovered supporter’s tier so peers stand out together. Cards are keyboard focusable, the layout reflows down to about 360px wide, and counters respect prefers-reduced-motion.
Illustrative UI only — fictional organization, not a real charity or donation system.