---
name: polistack-html-render
description: Render the output of ANY PoliStack tool (bill brief, Senate-race analysis, Super PAC profile, member comparison, donor/lobbying profile, etc.) as a single self-contained HTML file that displays cleanly and immediately in Claude chat or any browser. Generic — does not assume bill-shaped data. Use whenever a user wants a PoliStack tool's response turned into a polished, shareable, visual HTML report.
---

# PoliStack HTML Render Skill

This skill turns the output of any PoliStack tool into **one self-contained HTML file** with the PoliStack visual style — dark premium theme, KPI strips, styled tables, Chart.js plots, timelines, and color-coded callouts. The file renders directly in a Claude artifact and in any browser with **zero local dependencies**.

It is **generic**. It does not assume the data is about a bill. It inspects whatever the tool returned and chooses which visual components to use, while keeping the look and behavior of every component fixed so output stays consistent and on-brand across runs and across different tools.

## Critical principles — read first

1. **Self-contained.** All CSS lives in one inline `<style>` block. All data is inlined into the page. The ONLY external requests permitted are (a) Chart.js from a CDN `<script>`, and (b) member photos from congress.gov. No other external files, fonts, or stylesheets. The file must render correctly when opened on its own.
2. **Render only what the tool provided.** Do NOT invent, pad, or hallucinate sections, numbers, findings, or sources to fill a template. Lighter is better. If the tool returned three things, render those three things well. Comprehension over completeness.
3. **Consistent across runs.** Which sections appear is adaptive, but the styling and behavior of each component are FIXED by this skill. Same input → same rendering. Follow the decision rules and the checklist exactly. Do not improvise new styling each run.
4. **Never show a broken state.** Every chart has a styled-table fallback. Every member photo has an initials-avatar fallback. The file must always look finished.
5. **No internal mechanics in visible copy.** Never expose graph edge names, schema, or internal tier mechanics (e.g. `TRANSFERRED_TO`, `CONTRIBUTED_TO`, `sponsored`, `admin`, "auto-granted"). Refer to data generically as "the PoliStack political knowledge graph." Refer to plans as "free plan / paid plans."

## Output

A single file named `<slug>.html`, where `<slug>` is a kebab-case identifier derived from the analysis subject (e.g. `texas-gop-senate-primary`, `america-pac-profile`, `hr3633-clarity-act`). Produce nothing else — no separate CSS, JS, or data files.

## Document skeleton

Produce a complete HTML document with this structure. Everything inline.

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title><!-- NEUTRAL title: subject + topic. No editorial framing. --></title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
  <style>
    /* === INLINE CSS BLOCK (see "Visual system" below) === */
  </style>
</head>
<body>
  <div class="ps-root">
    <!-- Hero -->
    <!-- KPIGrid (if headline numbers exist) -->
    <main class="ps-main">
      <!-- Executive summary panel + callouts (if a summary exists) -->
      <!-- Substantive panels: tables / charts / timeline as the data warrants -->
      <!-- Sources footer -->
    </main>
  </div>
  <script>
    /* === Chart.js theme defaults + chart initializers (see "Charts" below) === */
  </script>
</body>
</html>
```

Pin Chart.js to the exact version `4.4.1` as shown. Do NOT use `@latest`.

## Visual system — paste this CSS verbatim

Use these exact tokens and base styles. This is what makes every output look like PoliStack. Add component styles below as needed but do not change the token values.

```css
:root{
  --bg:#0b0f1a; --panel:#121826; --panel2:#1a2236; --border:#243049;
  --text:#e6ecf5; --muted:#8a96ad; --accent:#6ee7ff; --accent2:#a78bfa;
  --gop:#d94a4a; --dem:#4a8fd9; --pass:#22c55e; --warn:#f59e0b; --danger:#ef4444;
}
*{box-sizing:border-box;}
body{margin:0;background:var(--bg);color:var(--text);
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  line-height:1.55;-webkit-font-smoothing:antialiased;overflow-x:hidden;}
.ps-root{min-height:100vh;}
.ps-main{max-width:1180px;margin:0 auto;padding:24px 16px 48px;}
a{color:var(--accent);text-decoration:none;}

/* Hero */
.ps-hero{position:relative;overflow:hidden;border-bottom:1px solid var(--border);
  background:linear-gradient(135deg,#131b30 0%,#1a2645 100%);
  padding:64px 16px 32px;}
.ps-hero::after{content:"";position:absolute;top:-40px;right:-40px;width:260px;height:260px;
  pointer-events:none;background:radial-gradient(circle,rgba(110,231,255,0.18),transparent 70%);}
.ps-hero-inner{position:relative;max-width:1180px;margin:0 auto;}
.ps-hero h1{font-size:28px;line-height:1.2;font-weight:800;margin:8px 0 6px;}
.ps-hero .sub{color:var(--muted);font-size:15px;max-width:760px;}
.ps-hero-row{display:flex;gap:16px;align-items:center;flex-wrap:wrap;}

/* Eyebrow / kicker */
.ps-kicker{font-size:12px;text-transform:uppercase;letter-spacing:1px;color:var(--accent);font-weight:600;}

/* Pills + tags */
.ps-pill{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;font-size:12px;
  background:rgba(255,255,255,0.04);border:1px solid var(--border);color:var(--text);}
.ps-pill.pass{color:#d3ffe1;border-color:rgba(34,197,94,0.4);background:rgba(34,197,94,0.10);}
.ps-pill.warn{color:#ffe2bb;border-color:rgba(245,158,11,0.4);background:rgba(245,158,11,0.10);}
.ps-pill.flag{color:#ffd1d1;border-color:rgba(239,68,68,0.35);background:rgba(239,68,68,0.10);}
.ps-tag{display:inline-block;margin-left:8px;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;
  text-transform:uppercase;letter-spacing:0.5px;background:rgba(110,231,255,0.13);color:var(--accent);
  border:1px solid rgba(110,231,255,0.33);vertical-align:middle;}

/* Panels */
.ps-panel{border:1px solid var(--border);background:var(--panel);border-radius:14px;
  padding:24px;margin-bottom:20px;scroll-margin-top:24px;}
.ps-panel h2{font-size:22px;font-weight:700;line-height:1.2;margin:0 0 12px;}
.ps-h3{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:0.8px;
  color:var(--accent);margin:24px 0 12px;}
.ps-panel p{font-size:14px;margin:0 0 12px;}

/* KPI grid */
.ps-kpis{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin:20px 0;}
@media(min-width:640px){.ps-kpis{grid-template-columns:repeat(3,1fr);}}
@media(min-width:980px){.ps-kpis{grid-template-columns:repeat(6,1fr);}}
.ps-kpi{border:1px solid var(--border);background:var(--panel2);border-radius:10px;padding:14px 16px;}
.ps-kpi .label{font-size:10px;text-transform:uppercase;letter-spacing:0.6px;color:var(--muted);font-weight:600;margin-bottom:4px;}
.ps-kpi .value{font-size:20px;font-weight:700;line-height:1.1;}
.ps-kpi .sub{font-size:11px;color:var(--muted);margin-top:4px;}

/* Callouts */
.ps-callout{border-left:4px solid var(--accent);background:rgba(110,231,255,0.06);
  border-radius:8px;padding:14px 16px;margin:12px 0;font-size:14px;}
.ps-callout.warn{border-left-color:var(--warn);background:rgba(245,158,11,0.07);}
.ps-callout.flag{border-left-color:var(--danger);background:rgba(239,68,68,0.07);}
.ps-callout.pass{border-left-color:var(--pass);background:rgba(34,197,94,0.06);}

/* Bullet list with cyan rings */
.ps-ticks{list-style:none;padding:0;margin:0 0 8px;}
.ps-ticks li{position:relative;padding-left:24px;margin-bottom:10px;font-size:14px;}
.ps-ticks li::before{content:"";position:absolute;left:0;top:6px;width:14px;height:14px;border-radius:999px;
  border:2px solid var(--accent);background:rgba(110,231,255,0.15);}

/* Tables */
.ps-table-wrap{overflow-x:auto;margin:12px 0;border:1px solid var(--border);border-radius:10px;}
.ps-table{width:100%;border-collapse:collapse;font-size:13px;min-width:520px;}
.ps-table thead th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:0.6px;
  color:var(--muted);font-weight:600;padding:10px 12px;border-bottom:1px solid var(--border);background:var(--panel2);}
.ps-table tbody td{padding:10px 12px;border-bottom:1px solid var(--border);}
.ps-table tbody tr:nth-child(even){background:rgba(255,255,255,0.015);}
.ps-table tbody tr:last-child td{border-bottom:none;}
.ps-table .num{text-align:right;font-variant-numeric:tabular-nums;}
.ps-r{color:var(--gop);font-weight:600;}
.ps-d{color:var(--dem);font-weight:600;}

/* Timeline */
.ps-timeline{position:relative;padding-left:28px;margin-top:12px;}
.ps-timeline::before{content:"";position:absolute;top:4px;bottom:4px;left:7px;width:1px;background:var(--border);}
.ps-tl-item{position:relative;padding-bottom:24px;}
.ps-tl-item:last-child{padding-bottom:4px;}
.ps-tl-dot{position:absolute;left:-21px;top:5px;width:14px;height:14px;border-radius:999px;background:var(--pass);}
.ps-tl-dot.current{background:var(--warn);box-shadow:0 0 0 4px rgba(245,158,11,0.13);}
.ps-tl-date{font-size:11px;text-transform:uppercase;letter-spacing:0.8px;font-weight:600;color:var(--muted);margin-bottom:2px;}
.ps-tl-date.current{color:var(--warn);}
.ps-tl-title{font-size:15px;font-weight:600;margin-bottom:4px;}
.ps-tl-desc{font-size:13px;color:var(--muted);}

/* Charts */
.ps-chart-card{border:1px solid var(--border);background:var(--panel2);border-radius:10px;padding:16px;margin:12px 0;}
.ps-chart-card .cap{font-size:12px;color:var(--muted);margin-bottom:10px;}
.ps-chart-box{position:relative;height:300px;}

/* Member photo */
.ps-photo{border-radius:999px;object-fit:cover;object-position:top;background:var(--panel);
  border:2px solid var(--border);display:inline-block;}
.ps-avatar{border-radius:999px;display:inline-flex;align-items:center;justify-content:center;font-weight:600;
  background:linear-gradient(135deg,#243049,#1a2236);color:var(--muted);border:2px solid var(--border);}

/* Sources footer */
.ps-sources{margin-top:32px;padding-top:20px;border-top:1px solid var(--border);
  font-size:12.5px;color:var(--muted);line-height:1.6;}
.ps-sources strong{color:var(--text);}

@media(max-width:639px){
  .ps-hero h1{font-size:23px;}
  .ps-panel{padding:18px;}
}
```

## Component HTML patterns — reuse these verbatim

### Hero

```html
<header class="ps-hero">
  <div class="ps-hero-inner">
    <div class="ps-kicker">PoliStack · <!-- module e.g. Bill Intelligence / Money in Politics --></div>
    <div class="ps-hero-row">
      <!-- optional subject photo here (see Member photo) -->
      <div>
        <h1><!-- NEUTRAL title --></h1>
        <p class="sub"><!-- 1–2 sentence factual subtitle from the tool output --></p>
      </div>
    </div>
    <div style="margin-top:14px;display:flex;gap:8px;flex-wrap:wrap;">
      <span class="ps-pill warn"><!-- status --></span>
      <span class="ps-pill"><!-- another fact, e.g. date --></span>
    </div>
  </div>
</header>
```

### KPI grid (only if the tool returned headline numbers)

```html
<div class="ps-kpis">
  <div class="ps-kpi"><div class="label">Label</div><div class="value">$4.4M+</div><div class="sub">context</div></div>
  <!-- 4 or 6 cells total -->
</div>
```

### Panel + section title + tag

```html
<section class="ps-panel" id="section-1">
  <h2>Section title<span class="ps-tag">30-sec read</span></h2>
  <p>Body text from the tool output.</p>
  <h3 class="ps-h3">Sub-section</h3>
  <ul class="ps-ticks"><li>Point one.</li><li>Point two.</li></ul>
</section>
```

### Callout (key findings)

```html
<div class="ps-callout flag"><strong>The signal:</strong> finding stated plainly.</div>
```

Tones: default (cyan), `warn`, `flag`, `pass`. Use sparingly for the 1–3 most important takeaways the tool surfaced.

### Table (rosters, money, comparisons)

```html
<div class="ps-table-wrap">
  <table class="ps-table">
    <thead><tr><th>Member</th><th>Party</th><th class="num">Amount</th></tr></thead>
    <tbody>
      <tr><td>Hill, French</td><td><span class="ps-r">R</span></td><td class="num">$86,000</td></tr>
    </tbody>
  </table>
</div>
```

Use `class="ps-r"` / `class="ps-d"` for party text. Right-align numbers with `class="num"`. The wrapper makes it scroll on mobile.

### Timeline (only if there's a chronology)

```html
<div class="ps-timeline">
  <div class="ps-tl-item">
    <div class="ps-tl-dot"></div>
    <div class="ps-tl-date">May 29, 2025</div>
    <div class="ps-tl-title">Introduced</div>
    <div class="ps-tl-desc">Detail from the tool output.</div>
  </div>
  <div class="ps-tl-item">
    <div class="ps-tl-dot current"></div>
    <div class="ps-tl-date current">May 14, 2026</div>
    <div class="ps-tl-title">Current stage</div>
    <div class="ps-tl-desc">Detail.</div>
  </div>
</div>
```

Past events use the green dot (default), the current/latest stage uses `current` (amber).

### Member photo (with mandatory avatar fallback)

Primary source is congress.gov; bioguide **lowercased**, format `^[a-z]\d{6}$`:

```html
<img class="ps-photo" style="width:72px;height:72px;border-color:var(--accent);box-shadow:0 0 0 3px rgba(110,231,255,0.13);"
  src="https://www.congress.gov/img/member/h001072_200.jpg"
  alt="French Hill" loading="lazy" referrerpolicy="no-referrer"
  onerror="this.outerHTML='&lt;span class=\'ps-avatar\' style=\'width:72px;height:72px;font-size:24px;\'&gt;FH&lt;/span&gt;'">
```

Rules:
- Only the bioguide ID changes between members. Always lowercase it.
- The `onerror` handler replaces the image with an initials avatar so a blocked or missing photo never shows broken. Compute initials from the member's name.
- Use photos only in high-impact spots: the hero subject, and a few pivotal-person cards. **Never** put photos inside data tables.
- If the tool output has no bioguide IDs, skip photos — do not guess IDs.

### Sources footer

```html
<div class="ps-sources">
  <p><strong>Sources:</strong> <!-- list ONLY the sources the tool cited; otherwise: data from the PoliStack political knowledge graph --></p>
  <!-- Include this line whenever the report places money/spend next to a policy or election outcome: -->
  <p style="margin-top:8px;"><strong>On causation:</strong> Engagement and spending shown here are lawful and publicly disclosed. Timing and adjacency are correlation, not evidence of exchange, coordination, or quid pro quo.</p>
  <p style="margin-top:8px;">Generated by <a href="https://polistack.com">PoliStack</a> · <!-- date --></p>
</div>
```

## Charts — Chart.js with mandatory table fallback

Load Chart.js once (in `<head>`, pinned to 4.4.1). Set theme defaults and initialize charts in the body `<script>`. For EVERY chart, also emit the underlying data as a `ps-table` inside a `<noscript>`-style fallback container that is shown if the chart fails to render.

```html
<div class="ps-chart-card">
  <div class="cap">Direct PAC dollars per cosponsor (top 8)</div>
  <div class="ps-chart-box"><canvas id="chart1"></canvas></div>
  <!-- Fallback table, hidden unless the chart can't render -->
  <div id="chart1-fallback" style="display:none;">
    <div class="ps-table-wrap"><table class="ps-table"><thead><tr><th>Member</th><th class="num">Amount</th></tr></thead>
    <tbody><tr><td>Hill</td><td class="num">$86,000</td></tr></tbody></table></div>
  </div>
</div>
```

```html
<script>
  // List EVERY chart id in the report here so each one falls back if Chart.js fails to load.
  var PS_CHART_IDS = ['chart1' /*, 'chart2', 'chart3', ... */];
  function psShowFallback(id){
    var c=document.getElementById(id); if(c) c.closest('.ps-chart-box').style.display='none';
    var f=document.getElementById(id+'-fallback'); if(f) f.style.display='block';
  }
  if (typeof Chart === 'undefined') {
    PS_CHART_IDS.forEach(psShowFallback);
  } else {
    Chart.defaults.color = '#8a96ad';
    Chart.defaults.borderColor = '#243049';
    Chart.defaults.font.family = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
    try {
      new Chart(document.getElementById('chart1'), {
        type: 'bar',
        data: { labels: ['Hill','Huizenga','Emmer'],
          datasets: [{ data: [86000,74500,62000], backgroundColor: '#6ee7ff' }] },
        options: { responsive:true, maintainAspectRatio:false,
          plugins:{ legend:{ display:false } } }
      });
    } catch(e){ psShowFallback('chart1'); }
  }
</script>
```

Chart rules:
- Use charts ONLY where they aid comprehension — distributions, rankings, comparisons of magnitude, money flows, trajectories over time. Use tables for anything fundamentally tabular. Do not chart for decoration.
- Series colors: party colors (`--gop` `#d94a4a` / `--dem` `#4a8fd9`) for partisan data; otherwise `--accent` `#6ee7ff` and `--accent2` `#a78bfa`.
- Every chart MUST have its data also present as a fallback table, and MUST fall back if `Chart` is undefined or construction throws. The data must always be legible without the chart.

## Adaptive section logic — how to decide what to render

Inspect the tool's output and assemble the page from these rules. Render a section only when the data supports it.

| If the tool output contains… | Render |
|---|---|
| Headline numbers / key metrics | **KPIGrid** near the top |
| A short narrative summary | **Hero** subtitle + an **Executive Summary** panel with **Callouts** for the 1–3 standout findings |
| A roster / list of people or entities | **Table** (party-colored if partisan; scrollable) |
| A distribution, ranking, or magnitude comparison | **Chart** (bar/doughnut) + fallback table |
| A dated sequence of events | **Timeline** |
| Money flows / contribution patterns | **Table** + optional top-N bar chart |
| Cited sources | **Sources footer** listing them; if none cited, generic graph attribution |

Recommended order: **Hero → KPIGrid → Executive Summary/Callouts → substantive panels (tables / charts / timeline) → Sources footer.** Omit any section the data doesn't support. Fewer strong sections beat many thin ones — keep it lighter.

## Copy rules

- **Neutral titles only.** Title, `<title>`, and `<h1>` state the subject and topic, not an editorial conclusion. Format like `Subject — Nickname: Topic` (e.g. "H.R. 3633 — CLARITY Act: Digital Asset Market Structure"). Editorial conclusions belong in the body, where the evidence backs them. Avoid titles like "How X Bought Y."
- **Flag unverified claims.** Wrap superlatives ("first/only/never", "all N voted") and suspicious round-number totals in an HTML comment `<!-- TODO: VERIFY: ... -->` so they can be checked before the output is featured publicly. Do not silently trust them.
- **No internal mechanics** in visible copy: no edge names, schema, or tier internals. Use "the PoliStack political knowledge graph" and "free plan / paid plans."
- **Correlation, not causation.** PoliStack's audience includes the lobbyists, firms, and trade associations these reports profile, so every report must read as a neutral intelligence mirror, not an exposé. Present disclosed spend and policy/election outcomes adjacently and let the reader weigh the timing — never assert that money produced an outcome. Avoid loaded verbs (`bought`, `bankrolled`, `engineered`, `quid pro quo`, and "behind" when it ties private money to a result); prefer "connected to," "appears alongside," "with the most at stake in." Whenever a report places money next to an outcome, add a short **"On causation"** line in the sources/methodology footer (see below).

## Worked examples

**Example A — Super PAC profile.** Tool returns: PAC name, total raised/spent, top 8 donors with amounts, spend-by-target breakdown, a few findings. Render: Hero (PAC name + neutral subtitle) → KPIGrid (total raised, total spent, top-donor share, # targets) → Executive Summary panel with 2 Callouts → top-donors **bar chart** (with fallback table) → spend-by-target **table** → Sources footer. No timeline (no chronology), no member photos (no bioguides).

**Example B — Bill brief.** Tool returns: sponsor (with bioguide), status, cosponsor roster (with parties + dates), a House vote, PAC contributions, a legislative timeline, lobbying filers, findings. Render: Hero with sponsor **photo** + status pills → KPIGrid (vote margin, cosponsors, LDA filers, etc.) → Executive Summary + Callouts → **Timeline** → cosponsor **table** (party-colored) → PAC contributions **bar chart** + table → lobbying **table** → Sources footer. Flag the "$4.4M" and "all 21 voted yes" type claims with `TODO: VERIFY`.

**Example C — Member comparison.** Tool returns: two members (with bioguides), metrics side by side, a few narrative points. Render: Hero (neutral "X vs Y" title) → two **photo** cards → comparison **table** or grouped **bar chart** (with fallback) → Callouts for the key differences → Sources footer. No timeline.

## Output checklist — verify before returning the file

- [ ] Single self-contained `.html`; all CSS inline; renders with no local files.
- [ ] Chart.js pinned to `4.4.1` via CDN in `<head>`.
- [ ] Color tokens applied exactly via the `:root` CSS variables.
- [ ] Only sections the tool output supports are rendered — nothing invented or padded.
- [ ] KPIGrid present if headline numbers exist.
- [ ] Tables styled, party-colored where applicable, wrapped for mobile scroll.
- [ ] Every chart uses Chart.js with theme defaults AND has a fallback table that shows if the chart can't render.
- [ ] Member photos use `congress.gov/img/member/<lowercase-bioguide>_200.jpg` with an `onerror` initials-avatar fallback; no photos in tables; no guessed IDs.
- [ ] Timeline with state dots if there's a chronology (current stage = amber).
- [ ] Callouts used for the 1–3 key findings only.
- [ ] Sources footer lists only cited sources, or generic graph attribution.
- [ ] Neutral title; `TODO: VERIFY` comments on superlatives and round numbers.
- [ ] Money→outcome framed as correlation, not causation; loaded verbs avoided; "On causation" footer line present when money sits next to an outcome.
- [ ] No internal mechanics or tier language in visible copy.
- [ ] Responsive at mobile widths (≤640px).

That's the skill. Pass any PoliStack tool's response through it and the result is one polished, self-contained HTML report that renders directly in Claude chat — consistent, on-brand, and legible whether or not charts or photos load.
