Nonprofit — Petition / Pledge
A warm, hopeful petition and pledge page for a fictional clean-water charity. It pairs a bold cause statement and impact stats with an animated signature-goal thermometer and progress bar, a validated sign form with optional comment and privacy toggle, a live recent-signatures feed that grows as people sign, and quick share actions. A sticky donate call-to-action and trust badges round out a self-contained, accessible, responsive vanilla build.
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;
--shadow-sm: 0 1px 2px rgba(42, 39, 34, 0.06), 0 2px 8px rgba(42, 39, 34, 0.05);
--shadow-md: 0 6px 22px rgba(42, 39, 34, 0.1), 0 2px 6px rgba(42, 39, 34, 0.06);
--shadow-lg: 0 18px 50px rgba(42, 39, 34, 0.14);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.6;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1,
h2 {
font-family: "Fraunces", Georgia, serif;
font-weight: 600;
line-height: 1.15;
margin: 0;
letter-spacing: -0.01em;
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: 28px 20px 120px;
}
/* ---------- Hero ---------- */
.hero {
display: grid;
grid-template-columns: 1.05fr 1.15fr;
gap: 28px;
align-items: stretch;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--shadow-md);
}
.hero__media {
position: relative;
min-height: 320px;
padding: 18px;
display: flex;
flex-direction: column;
justify-content: space-between;
background:
radial-gradient(120% 90% at 15% 10%, rgba(255, 255, 255, 0.22), transparent 55%),
linear-gradient(150deg, #2f9e6f 0%, var(--brand) 42%, var(--brand-d) 100%);
color: #fff;
}
.hero__media::after {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(70% 60% at 80% 90%, rgba(232, 116, 59, 0.45), transparent 60%),
repeating-linear-gradient(115deg, rgba(255, 255, 255, 0.06) 0 2px, transparent 2px 22px);
pointer-events: none;
}
.hero__chip,
.hero__caption {
position: relative;
z-index: 1;
}
.hero__chip {
align-self: flex-start;
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.34);
padding: 6px 11px;
border-radius: 999px;
backdrop-filter: blur(4px);
}
.hero__caption {
margin: 0;
font-size: 0.92rem;
max-width: 32ch;
text-shadow: 0 1px 10px rgba(0, 0, 0, 0.25);
}
.hero__body {
padding: 30px 30px 30px 6px;
display: flex;
flex-direction: column;
gap: 16px;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
margin: 0;
font-size: 0.78rem;
font-weight: 600;
color: var(--brand-d);
letter-spacing: 0.01em;
}
.eyebrow .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 4px rgba(232, 116, 59, 0.18);
}
.hero__body h1 {
font-size: clamp(1.7rem, 1.1rem + 2.4vw, 2.5rem);
color: var(--ink);
}
.lead {
margin: 0;
color: var(--ink-2);
font-size: 1.02rem;
}
.impact {
display: flex;
gap: 12px;
list-style: none;
margin: 4px 0 0;
padding: 0;
}
.impact li {
flex: 1;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 12px 10px;
text-align: center;
}
.impact strong {
display: block;
font-family: "Fraunces", serif;
font-size: 1.35rem;
color: var(--brand);
font-variant-numeric: tabular-nums;
}
.impact span {
font-size: 0.74rem;
color: var(--muted);
}
.badges {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.badge {
font-size: 0.76rem;
font-weight: 600;
color: var(--brand-d);
background: rgba(31, 122, 109, 0.09);
border: 1px solid rgba(31, 122, 109, 0.2);
padding: 5px 10px;
border-radius: 999px;
}
/* ---------- Grid ---------- */
.grid {
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 22px;
margin-top: 22px;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-sm);
padding: 24px;
}
/* ---------- Goal / thermometer ---------- */
.goal__head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
}
.goal h2 {
font-size: 1.4rem;
}
.goal__sub {
margin: 4px 0 0;
color: var(--ink-2);
font-size: 0.95rem;
}
.goal__sub strong {
color: var(--brand);
font-size: 1.05rem;
font-variant-numeric: tabular-nums;
}
.thermo {
width: 16px;
height: 64px;
border-radius: 999px;
background: rgba(42, 39, 34, 0.08);
position: relative;
overflow: hidden;
flex: none;
}
.thermo__fill {
position: absolute;
inset: auto 0 0 0;
height: 0%;
background: linear-gradient(180deg, var(--accent), var(--accent-d));
transition: height 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
.bar {
margin-top: 14px;
height: 12px;
border-radius: 999px;
background: rgba(42, 39, 34, 0.08);
overflow: hidden;
}
.bar__fill {
display: block;
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--brand), #2f9e6f);
transition: width 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
.goal__remain {
margin: 10px 0 0;
font-size: 0.85rem;
color: var(--muted);
}
/* ---------- Form ---------- */
#signForm {
margin-top: 22px;
display: flex;
flex-direction: column;
gap: 14px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
position: relative;
}
.field label {
font-size: 0.85rem;
font-weight: 600;
color: var(--ink);
}
.opt {
color: var(--muted);
font-weight: 400;
}
input[type="text"],
input[type="email"],
textarea {
font: inherit;
color: var(--ink);
background: var(--bg);
border: 1.5px solid var(--line-2);
border-radius: var(--r-sm);
padding: 11px 13px;
width: 100%;
resize: vertical;
transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
}
input::placeholder,
textarea::placeholder {
color: var(--muted);
}
input:focus,
textarea:focus {
outline: none;
border-color: var(--brand);
background: var(--surface);
box-shadow: 0 0 0 4px rgba(31, 122, 109, 0.14);
}
.field.invalid input {
border-color: var(--danger);
box-shadow: 0 0 0 4px rgba(212, 80, 62, 0.12);
}
.err {
margin: 0;
font-size: 0.78rem;
color: var(--danger);
font-weight: 500;
}
.count {
align-self: flex-end;
font-size: 0.72rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.check {
display: flex;
align-items: center;
gap: 9px;
font-size: 0.85rem;
color: var(--ink-2);
cursor: pointer;
}
.check input {
width: 17px;
height: 17px;
accent-color: var(--brand);
}
.reassure {
margin: 2px 0 0;
font-size: 0.76rem;
color: var(--muted);
text-align: center;
}
/* ---------- Buttons ---------- */
.btn {
font: inherit;
font-weight: 600;
cursor: pointer;
border: 1px solid transparent;
border-radius: var(--r-sm);
padding: 12px 18px;
transition: transform 0.12s, box-shadow 0.18s, background 0.18s, border-color 0.18s;
}
.btn:active {
transform: translateY(1px) scale(0.99);
}
.btn:focus-visible {
outline: 3px solid rgba(31, 122, 109, 0.4);
outline-offset: 2px;
}
.btn--primary {
background: var(--brand);
color: #fff;
box-shadow: 0 6px 16px rgba(31, 122, 109, 0.28);
}
.btn--primary:hover {
background: var(--brand-d);
box-shadow: 0 9px 22px rgba(31, 122, 109, 0.34);
}
.btn--primary.signed {
background: var(--ok);
box-shadow: none;
}
.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);
}
.btn--ghost {
background: var(--surface);
color: var(--brand-d);
border-color: var(--line-2);
padding: 9px 14px;
font-size: 0.85rem;
}
.btn--ghost:hover {
background: rgba(31, 122, 109, 0.07);
border-color: var(--brand);
}
/* ---------- Share ---------- */
.share {
margin-top: 20px;
padding-top: 18px;
border-top: 1px dashed var(--line-2);
}
.share__label {
font-size: 0.8rem;
font-weight: 600;
color: var(--muted);
}
.share__row {
display: flex;
gap: 8px;
margin-top: 10px;
}
.share__row .btn {
flex: 1;
}
/* ---------- Feed ---------- */
.card--feed {
display: flex;
flex-direction: column;
align-self: start;
position: sticky;
top: 18px;
}
.feed__head {
display: flex;
align-items: center;
justify-content: space-between;
}
.feed__head h2 {
font-size: 1.25rem;
}
.live {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.72rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--brand-d);
}
.live__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--ok);
box-shadow: 0 0 0 0 rgba(47, 158, 111, 0.5);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(47, 158, 111, 0.5); }
70% { box-shadow: 0 0 0 8px rgba(47, 158, 111, 0); }
100% { box-shadow: 0 0 0 0 rgba(47, 158, 111, 0); }
}
.feed {
list-style: none;
margin: 16px 0 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.feed li {
display: flex;
gap: 12px;
align-items: flex-start;
padding: 11px 12px;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-md);
}
.feed li.fresh {
animation: slidein 0.5s cubic-bezier(0.22, 1, 0.36, 1);
border-color: rgba(31, 122, 109, 0.35);
background: rgba(31, 122, 109, 0.05);
}
@keyframes slidein {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.avatar {
width: 38px;
height: 38px;
border-radius: 50%;
flex: none;
display: grid;
place-items: center;
color: #fff;
font-weight: 700;
font-size: 0.85rem;
font-family: "Fraunces", serif;
}
.sig__name {
margin: 0;
font-weight: 600;
font-size: 0.92rem;
}
.sig__meta {
margin: 1px 0 0;
font-size: 0.76rem;
color: var(--muted);
}
.sig__comment {
margin: 5px 0 0;
font-size: 0.84rem;
color: var(--ink-2);
font-style: italic;
}
.feed__more {
margin: 14px 0 0;
font-size: 0.82rem;
color: var(--muted);
text-align: center;
}
/* ---------- Donate bar ---------- */
.donate-bar {
position: fixed;
left: 50%;
bottom: 18px;
transform: translateX(-50%);
width: min(700px, calc(100% - 32px));
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 14px 12px 20px;
background: var(--ink);
color: #fff;
border-radius: var(--r-lg);
box-shadow: var(--shadow-lg);
z-index: 20;
}
.donate-bar__text {
display: flex;
flex-direction: column;
line-height: 1.3;
}
.donate-bar__text strong {
font-size: 0.95rem;
}
.donate-bar__text span {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.72);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 90px;
transform: translateX(-50%) translateY(16px);
background: var(--ink);
color: #fff;
padding: 11px 18px;
border-radius: 999px;
font-size: 0.88rem;
font-weight: 500;
box-shadow: var(--shadow-lg);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s, transform 0.3s;
z-index: 40;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ---------- Responsive ---------- */
@media (max-width: 860px) {
.hero {
grid-template-columns: 1fr;
}
.hero__media {
min-height: 200px;
}
.hero__body {
padding: 26px;
}
.grid {
grid-template-columns: 1fr;
}
.card--feed {
position: static;
}
}
@media (max-width: 520px) {
.page {
padding: 18px 14px 130px;
}
.impact {
flex-wrap: wrap;
}
.impact li {
min-width: calc(50% - 6px);
}
.goal__head {
flex-direction: row;
}
.donate-bar {
flex-direction: column;
align-items: stretch;
text-align: center;
padding: 14px;
}
.donate-bar .btn {
width: 100%;
}
.donate-bar__text {
align-items: center;
}
}(function () {
"use strict";
var GOAL = 10000;
var signatures = 7431;
var seedFeed = [
{ name: "Marisol Vega", city: "Rivertown", ago: "2 min ago", comment: "My kids learned to fish on the East Bank." },
{ name: "Dev Okafor", city: "Mill Creek", ago: "6 min ago", comment: "" },
{ name: "Hannah Brooks", city: "Rivertown", ago: "11 min ago", comment: "Clean water is not negotiable." },
{ name: "Theo Lindqvist", city: "Greenfield", ago: "18 min ago", comment: "" },
{ name: "Priya Nair", city: "Rivertown", ago: "24 min ago", comment: "Restore the wetlands before it's too late." }
];
var AVATAR_COLORS = ["#1f7a6d", "#e8743b", "#2f9e6f", "#155e54", "#cc5d28", "#d98a2b"];
var els = {
count: document.getElementById("sigCount"),
remain: document.getElementById("goalRemain"),
barFill: document.getElementById("barFill"),
bar: document.getElementById("bar"),
thermoFill: document.getElementById("thermoFill"),
feed: document.getElementById("feed"),
form: document.getElementById("signForm"),
name: document.getElementById("name"),
email: document.getElementById("email"),
comment: document.getElementById("comment"),
commentCount: document.getElementById("commentCount"),
publicChk: document.getElementById("public"),
signBtn: document.getElementById("signBtn"),
nameErr: document.getElementById("nameErr"),
emailErr: document.getElementById("emailErr"),
toast: document.getElementById("toast"),
donate: document.getElementById("donateBtn")
};
/* ---------- Toast ---------- */
var toastTimer;
function toast(msg) {
els.toast.textContent = msg;
els.toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
els.toast.classList.remove("show");
}, 2600);
}
/* ---------- Helpers ---------- */
function fmt(n) {
return n.toLocaleString("en-US");
}
function initials(name) {
var 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 renderProgress(animate) {
var pct = Math.min(100, (signatures / GOAL) * 100);
if (!animate) {
els.barFill.style.transition = "none";
els.thermoFill.style.transition = "none";
requestAnimationFrame(function () {
els.barFill.style.transition = "";
els.thermoFill.style.transition = "";
});
}
els.barFill.style.width = pct + "%";
els.thermoFill.style.height = pct + "%";
els.count.textContent = fmt(signatures);
els.bar.setAttribute("aria-valuenow", String(signatures));
var left = Math.max(0, GOAL - signatures);
els.remain.textContent =
left > 0
? fmt(left) + " more voices needed to hit our goal."
: "Goal reached — thank you! Stretch goal: 15,000.";
}
function makeRow(sig, fresh) {
var li = document.createElement("li");
if (fresh) li.className = "fresh";
var av = document.createElement("span");
av.className = "avatar";
av.style.background = colorFor(sig.name);
av.textContent = initials(sig.name);
av.setAttribute("aria-hidden", "true");
var body = document.createElement("div");
var nm = document.createElement("p");
nm.className = "sig__name";
nm.textContent = sig.name;
var meta = document.createElement("p");
meta.className = "sig__meta";
meta.textContent = sig.city + " · " + sig.ago;
body.appendChild(nm);
body.appendChild(meta);
if (sig.comment) {
var c = document.createElement("p");
c.className = "sig__comment";
c.textContent = "“" + sig.comment + "”";
body.appendChild(c);
}
li.appendChild(av);
li.appendChild(body);
return li;
}
function renderSeed() {
seedFeed.forEach(function (sig) {
els.feed.appendChild(makeRow(sig, false));
});
}
/* ---------- Count-up impact stats ---------- */
function countUp(el) {
var target = parseInt(el.getAttribute("data-count"), 10);
var start = performance.now();
var dur = 1100;
function step(now) {
var t = Math.min(1, (now - start) / dur);
var eased = 1 - Math.pow(1 - t, 3);
el.textContent = Math.round(target * eased).toLocaleString("en-US");
if (t < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
/* ---------- Validation ---------- */
function setInvalid(fieldEl, errEl, on) {
fieldEl.parentElement.classList.toggle("invalid", on);
errEl.hidden = !on;
}
function validEmail(v) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
}
/* ---------- Sign flow ---------- */
function handleSign(e) {
e.preventDefault();
var nameVal = els.name.value.trim();
var emailVal = els.email.value.trim();
var ok = true;
if (!nameVal) {
setInvalid(els.name, els.nameErr, true);
ok = false;
} else {
setInvalid(els.name, els.nameErr, false);
}
if (!validEmail(emailVal)) {
setInvalid(els.email, els.emailErr, true);
ok = false;
} else {
setInvalid(els.email, els.emailErr, false);
}
if (!ok) {
toast("Please fix the highlighted fields.");
return;
}
signatures += 1;
renderProgress(true);
if (els.publicChk.checked) {
var sig = {
name: nameVal,
city: "Just now",
ago: "moments ago",
comment: els.comment.value.trim()
};
var row = makeRow(sig, true);
els.feed.insertBefore(row, els.feed.firstChild);
setTimeout(function () {
row.classList.remove("fresh");
}, 1600);
}
els.signBtn.textContent = "✓ You signed — thank you!";
els.signBtn.classList.add("signed");
els.signBtn.disabled = true;
toast("Thanks, " + nameVal.split(" ")[0] + "! You're signature #" + fmt(signatures) + ".");
els.form.reset();
els.commentCount.textContent = "0 / 140";
}
/* ---------- Share ---------- */
function share(kind) {
var url = "https://clearwaters.example/petition/rivertown";
var text = "I just signed to protect the Rivertown watershed. Add your name:";
if (kind === "link") {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(
function () { toast("Link copied to clipboard."); },
function () { toast("Copy this: " + url); }
);
} else {
toast("Copy this: " + url);
}
} else if (kind === "x") {
toast("Opening a post… (demo)");
} else if (kind === "mail") {
toast("Opening your email app… (demo)");
}
}
/* ---------- Wire up ---------- */
els.comment.addEventListener("input", function () {
els.commentCount.textContent = els.comment.value.length + " / 140";
});
els.name.addEventListener("input", function () {
if (els.name.value.trim()) setInvalid(els.name, els.nameErr, false);
});
els.email.addEventListener("input", function () {
if (validEmail(els.email.value.trim())) setInvalid(els.email, els.emailErr, false);
});
els.form.addEventListener("submit", handleSign);
document.querySelectorAll("[data-share]").forEach(function (btn) {
btn.addEventListener("click", function () {
share(btn.getAttribute("data-share"));
});
});
els.donate.addEventListener("click", function () {
toast("Redirecting to our secure donation page… (demo)");
});
/* ---------- Boot ---------- */
renderSeed();
renderProgress(false);
requestAnimationFrame(function () {
renderProgress(true);
});
document.querySelectorAll(".impact strong[data-count]").forEach(countUp);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Stand for Clean Rivers — Petition</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>
<main class="page" role="main">
<section class="hero" aria-labelledby="cause-title">
<div class="hero__media" aria-hidden="true">
<span class="hero__chip">Rivertown, fictional campaign</span>
<p class="hero__caption">Volunteers clearing the East Bank after the spring floods.</p>
</div>
<div class="hero__body">
<p class="eyebrow">
<span class="dot" aria-hidden="true"></span>
Clear Waters Coalition · Registered Charity #FIC-204881
</p>
<h1 id="cause-title">Protect the Rivertown watershed for the next generation.</h1>
<p class="lead">
Every spring, untreated runoff pours into the East Bank, choking the wetlands that
18,000 residents rely on for clean drinking water. We're petitioning the county council
to fund permanent filtration and restore 40 acres of riverbank habitat.
</p>
<ul class="impact" role="list">
<li><strong data-count="42">0</strong><span>acres to restore</span></li>
<li><strong data-count="18000">0</strong><span>residents served</span></li>
<li><strong data-count="9">0</strong><span>partner groups</span></li>
</ul>
<div class="badges" role="list" aria-label="Trust badges">
<span class="badge" role="listitem">✓ Tax-deductible</span>
<span class="badge" role="listitem">✓ Verified charity</span>
<span class="badge" role="listitem">✓ 100% to the field</span>
</div>
</div>
</section>
<div class="grid">
<section class="card card--sign" aria-labelledby="sign-title">
<div class="goal">
<div class="goal__head">
<div>
<h2 id="sign-title">Add your name</h2>
<p class="goal__sub"><strong id="sigCount">7,431</strong> of <span id="sigGoal">10,000</span> signatures</p>
</div>
<div class="thermo" aria-hidden="true">
<span class="thermo__fill" id="thermoFill"></span>
</div>
</div>
<div class="bar" role="progressbar" aria-label="Signature progress" aria-valuemin="0" aria-valuemax="10000" aria-valuenow="7431" id="bar">
<span class="bar__fill" id="barFill"></span>
</div>
<p class="goal__remain" id="goalRemain">2,569 more voices needed to hit our goal.</p>
</div>
<form id="signForm" novalidate>
<div class="field">
<label for="name">Full name</label>
<input id="name" name="name" type="text" autocomplete="name" placeholder="Jordan Avery" required />
<p class="err" id="nameErr" role="alert" hidden>Please enter your name.</p>
</div>
<div class="field">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" placeholder="you@example.com" required />
<p class="err" id="emailErr" role="alert" hidden>Please enter a valid email.</p>
</div>
<div class="field">
<label for="comment">Why this matters to you <span class="opt">(optional)</span></label>
<textarea id="comment" name="comment" rows="2" maxlength="140" placeholder="My kids swim in this river every summer."></textarea>
<span class="count" id="commentCount">0 / 140</span>
</div>
<label class="check">
<input type="checkbox" id="public" checked />
<span>Show my name in the public signature feed</span>
</label>
<button type="submit" class="btn btn--primary" id="signBtn">Sign the petition</button>
<p class="reassure">We never sell your data. Unsubscribe anytime.</p>
</form>
<div class="share" aria-label="Share this petition">
<span class="share__label">Spread the word</span>
<div class="share__row">
<button class="btn btn--ghost" data-share="link">Copy link</button>
<button class="btn btn--ghost" data-share="x">Post</button>
<button class="btn btn--ghost" data-share="mail">Email</button>
</div>
</div>
</section>
<section class="card card--feed" aria-labelledby="feed-title">
<div class="feed__head">
<h2 id="feed-title">Recent signatures</h2>
<span class="live" id="liveTag"><span class="live__dot" aria-hidden="true"></span> live</span>
</div>
<ul class="feed" id="feed" role="list"></ul>
<p class="feed__more">…and 7,400+ neighbors who already signed.</p>
</section>
</div>
</main>
<div class="donate-bar" role="region" aria-label="Donate">
<div class="donate-bar__text">
<strong>Fuel the fight downstream.</strong>
<span>A $25 gift filters 5,000 gallons.</span>
</div>
<button class="btn btn--accent" id="donateBtn">Donate now</button>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Petition / Pledge
A campaign-style petition page for the fictional Clear Waters Coalition. The hero leads with the cause — protecting the Rivertown watershed — alongside count-up impact figures (acres to restore, residents served, partner groups) and trust badges signalling a tax-deductible, verified charity. A warm-gradient photo placeholder carries a human caption so the layout reads like a real campaign.
The signature card centres on a goal: a horizontal progress bar and a vertical thermometer animate toward a 10,000-signature target. Signing validates name and email inline, optionally adds a short comment, and respects a privacy toggle for whether the name appears publicly. On submit the counter increments, the progress animates forward, a toast confirms your signature number, and the button locks into a thank-you state.
The right column is a sticky live feed of recent signatures — colour-seeded avatars, city and time metadata, and quoted comments. New signatures slide in at the top with a fresh highlight. Share buttons copy a link or simulate social and email sharing, and a fixed donate bar keeps the primary ask in view. Everything is keyboard-usable, contrast-checked, and collapses cleanly down to ~360px.
Illustrative UI only — fictional organization, not a real charity or donation system.