← 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>
  );
}