← back to Patty
app/pulse/page.tsx
115 lines
/**
* /pulse — "Petition Pulse" public visualization of advocacy petitions as
* heartbeat spikes on an ECG line, mirroring Grant's "Deadline River"
* aesthetic in Patty's violet palette.
*
* 2026-05-30 (CNCP _shared item 19): different domain than Grant's federal-
* grants river — Patty visualizes petition signature velocity over the last
* 30 days. Each petition is a pulse spike: x = days-since-creation
* (rightmost = newest), y = signature velocity (signatures/day, taller =
* faster growth), color = category.
*
* No auth required (middleware only matches /api/*). No DB query — the page
* embeds a representative sample so the demo works without backend setup.
* When wired to the real /api/petitions list it can replace the sample with
* live rows server-side; the canvas component is data-shape agnostic.
*/
import PulseCanvas, { type Petition } from './PulseCanvas';
import PulseTable from './PulseTable';
// 10-min ISR matching Grant's river — same fetch-amplification reasoning.
export const revalidate = 600;
// ---------------------------------------------------------------------------
// Sample petitions — representative across the 6 categories so the demo
// looks like a fleet, not a single cause. Numbers (signatures, goal,
// velocity) are illustrative; replace with a live SELECT against
// poppy/patty.petitions when wiring to production data.
// ---------------------------------------------------------------------------
const SAMPLES: Petition[] = [
// Climate
{ id: 'p001', slug: 'fossil-fuel-phaseout', title: 'Federal Fossil Fuel Phaseout by 2035', category: 'climate', signatures: 18432, goal: 25000, velocity: 412, daysSinceStart: 18 },
{ id: 'p002', slug: 'protect-old-growth', title: 'Permanent Protection for Old-Growth Forests', category: 'climate', signatures: 7811, goal: 10000, velocity: 198, daysSinceStart: 21 },
{ id: 'p003', slug: 'methane-emergency', title: 'Declare Methane Leaks a National Emergency', category: 'climate', signatures: 3201, goal: 15000, velocity: 89, daysSinceStart: 11 },
// AI Safety
{ id: 'p010', slug: 'pause-frontier-training', title: 'Mandatory Pause on Frontier Model Training Above 1e26 FLOPs', category: 'ai_safety', signatures: 24108, goal: 30000, velocity: 856, daysSinceStart: 14 },
{ id: 'p011', slug: 'open-eval-mandate', title: 'Open Third-Party Evals for Deployed Frontier Models', category: 'ai_safety', signatures: 9412, goal: 12000, velocity: 312, daysSinceStart: 9 },
{ id: 'p012', slug: 'agi-disclosure', title: 'Public Disclosure Rule for AGI-Class Capabilities', category: 'ai_safety', signatures: 5872, goal: 10000, velocity: 201, daysSinceStart: 24 },
// Governance
{ id: 'p020', slug: 'algorithmic-transparency', title: 'Algorithmic Transparency in Federal Benefits Decisions', category: 'governance', signatures: 4108, goal: 8000, velocity: 121, daysSinceStart: 16 },
{ id: 'p021', slug: 'ranked-choice', title: 'Ranked-Choice Voting for Federal Primaries', category: 'governance', signatures: 11203, goal: 15000, velocity: 287, daysSinceStart: 22 },
{ id: 'p022', slug: 'lobbying-disclosure', title: 'Real-Time Lobbying Expenditure Disclosure', category: 'governance', signatures: 6722, goal: 9000, velocity: 178, daysSinceStart: 27 },
{ id: 'p023', slug: 'ftc-rulemaking', title: 'Public Comment Periods for FTC Rulemaking', category: 'governance', signatures: 2118, goal: 6000, velocity: 63, daysSinceStart: 7 },
// Civil Rights
{ id: 'p030', slug: 'voting-access', title: 'Restore Section 5 Voting Rights Act Preclearance', category: 'civil_rights', signatures: 16204, goal: 20000, velocity: 489, daysSinceStart: 19 },
{ id: 'p031', slug: 'gender-affirming-care', title: 'Federal Protection for Gender-Affirming Care', category: 'civil_rights', signatures: 22041, goal: 25000, velocity: 612, daysSinceStart: 13 },
{ id: 'p032', slug: 'criminal-justice', title: 'End Cash Bail in Federal Pre-Trial Detention', category: 'civil_rights', signatures: 4812, goal: 10000, velocity: 142, daysSinceStart: 20 },
// Economic Justice
{ id: 'p040', slug: 'living-wage', title: '$22 Federal Minimum Wage Indexed to CPI', category: 'economic_justice', signatures: 31204, goal: 40000, velocity: 902, daysSinceStart: 17 },
{ id: 'p041', slug: 'student-debt', title: 'Universal Student Loan Discharge for Under-$75k Households', category: 'economic_justice', signatures: 18722, goal: 25000, velocity: 521, daysSinceStart: 12 },
{ id: 'p042', slug: 'corporate-tax-floor', title: 'Restore Pre-2017 Corporate Tax Floor', category: 'economic_justice', signatures: 5412, goal: 12000, velocity: 167, daysSinceStart: 23 },
{ id: 'p043', slug: 'childcare-subsidy', title: 'Universal Childcare Subsidy for Working Families', category: 'economic_justice', signatures: 8911, goal: 15000, velocity: 234, daysSinceStart: 8 },
// Healthcare
{ id: 'p050', slug: 'medicare-negotiation', title: 'Expand Medicare Drug Price Negotiation to All Drugs', category: 'healthcare', signatures: 14502, goal: 18000, velocity: 401, daysSinceStart: 15 },
{ id: 'p051', slug: 'mental-health-parity', title: 'Enforce Mental Health Parity in Federal Plans', category: 'healthcare', signatures: 6118, goal: 9000, velocity: 189, daysSinceStart: 25 },
{ id: 'p052', slug: 'rural-hospital', title: 'Emergency Rural Hospital Stabilization Fund', category: 'healthcare', signatures: 3922, goal: 8000, velocity: 124, daysSinceStart: 10 },
{ id: 'p053', slug: 'maternal-health', title: 'National Maternal Mortality Review System', category: 'healthcare', signatures: 7411, goal: 10000, velocity: 212, daysSinceStart: 4 },
// A few "older" entries to make the left side feel populated
{ id: 'p060', slug: 'broadband-universal', title: 'Universal Broadband as Public Utility', category: 'governance', signatures: 9821, goal: 12000, velocity: 84, daysSinceStart: 29 },
{ id: 'p061', slug: 'pfas-ban', title: 'Federal Ban on PFAS in Consumer Products', category: 'climate', signatures: 5202, goal: 10000, velocity: 71, daysSinceStart: 28 },
{ id: 'p062', slug: 'data-broker-registry', title: 'National Data Broker Public Registry', category: 'governance', signatures: 3411, goal: 7000, velocity: 52, daysSinceStart: 30 },
];
export default function PulsePage() {
return (
<div style={{
minHeight: '100vh',
background: '#06050a',
color: '#e4d8f1',
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Display", system-ui, sans-serif',
padding: '32px 24px 80px',
}}>
<div style={{ maxWidth: 1500, margin: '0 auto' }}>
<header style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
marginBottom: 8, gap: 24,
}}>
<h1 style={{ margin: 0, fontSize: 22, letterSpacing: '-0.01em', fontWeight: 600 }}>
<span style={{ color: '#a78bfa' }}>●</span> Petition Pulse
<span style={{ fontWeight: 400, color: '#7a6c8a', fontSize: 14, marginLeft: 8 }}>
· live signature velocity across {SAMPLES.length} active petitions
</span>
</h1>
<div style={{ color: '#7a6c8a', fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>
sample · last 30d window
</div>
</header>
<p style={{ color: '#7a6c8a', fontSize: 12, lineHeight: 1.6, maxWidth: 720, marginTop: 0, marginBottom: 24 }}>
Each spike is a petition. X-axis is days since launch (right = newest).
Spike height is current signature velocity (signatures/day). Color is
cause category. Taller, more recent spikes are the petitions worth
riding the momentum on.
</p>
<PulseCanvas petitions={SAMPLES} />
<div style={{ marginTop: 36 }}>
<h2 style={{ fontSize: 13, color: '#a78bfa', textTransform: 'uppercase',
letterSpacing: '0.08em', marginBottom: 12 }}>
Top-velocity petitions
</h2>
<PulseTable petitions={SAMPLES} />
</div>
</div>
</div>
);
}