Skip to main content

poster

Render a standalone React component (Tailwind + Recharts + lucide-react) to PNG / SVG / PDF / JPG / WebP via the `poster_render` tool. Use when the user asks for a chart, dashboard, report card, social-share image, OG image, year-in-review, editorial data story, magazine layout, or any visual deliverable that should come out as a single image/document file. Not for interactive UIs; posters are compositions.

Source facts

Repository
Michaelliv/pi-poster
Last source activity
April 16, 2026 at 17:26
Detected SKILL.md language
English
Stars
37
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
poster
description
Render a standalone React component (Tailwind + Recharts + lucide-react) to PNG / SVG / PDF / JPG / WebP via the `poster_render` tool. Use when the user asks for a chart, dashboard, report card, social-share image, OG image, year-in-review, editorial data story, magazine layout, or any visual deliverable that should come out as a single image/document file. Not for interactive UIs; posters are compositions.
# poster Render a single-file React component to an image. One tool: **`poster_render`**. Write TSX like a graphic designer composes a poster, not like a web developer building a page. Everything is fixed-width, non-responsive, pixel-precise, and optimized to look **amazing at thumbnail size on a social feed**. ## When to reach for `poster_render` Chart, dashboard snapshot, OG image, social-share card, year-in-review, editorial data story, magazine layout, event poster, calendar, cover image, PDF one-pager, README hero. Anything that's a *composition* meant to be looked at, not interacted with. **Not** for: interactive UIs, forms, multi-page sites, anything needing runtime JS behavior after render. --- ## The contract These aren't style preferences — break them and the tool rejects the render. ### 1. The root element declares the canvas — this is the only source of truth **The tool has no `width` or `height` parameter.** The root <div>'s Tailwind size IS the canvas. The renderer measures it exactly. ```tsx // ✓ width only — height emerges from content (use this 80% of the time) <div className="w-[1600px] p-10 ..."> // ✓ width + height — fixed aspect for magazine covers, story format, etc. <div className="w-[1080px] h-[1350px] p-10 ..."> // ✗ rejected — no root width (canvas defaults, usually wrong) <div className="p-10 ..."> // ✗ rejected — min-h-screen stretches to viewport, not canvas <div className="w-[1600px] min-h-screen ..."> // ✗ rejected — w-full next to w-[Npx] overrides the explicit width <div className="w-[1600px] w-full ..."> // ✗ brittle — aspect without explicit width is indeterminate <div className="aspect-[4/5] p-10 ..."> ``` If you use absolute-positioned decorations (`top: 40%`, gradient blobs), you need a definite parent height — add `h-[Npx]` or `min-h-[Npx]` to the root. ### 2. Font-size floor: 14px Anything ≤12px disappears when the poster is viewed at half scale on a feed. Don't go below `text-sm` (14px) or `text-[14px]`. Recharts axis ticks: `fontSize: 13`. Chart-internal SVG labels: `fontSize: 13+`. ### 3. Use the three bundled fonts No imports needed. They're loaded in the shell: - **`Inter, system-ui`** — default sans - **`'Source Serif 4', serif`** — magazine / editorial / italic "reveal words" - **`'JetBrains Mono', ui-monospace, monospace`** — code, mono metadata Set via inline `style={{ fontFamily: "..." }}` on the root. ### 4. Canvas size by shape | shape | width | height | use | |---|---|---|---| | twitter / landscape | 1600 | auto or 900 | thread images, cover images | | OG image | 1200 | 630 | social previews (1.91:1) | | instagram square | 1200 | 1200 | social square | | story / wrapped | 1080 | 1350 | vertical, mobile-first | | editorial / magazine | 1400 | 1800 | tall data story | | dashboard | 1600 | 1000 | metrics grid | | poster / cover | 1200 | 1600 | print-feel | | weather / hero card | 1400 | 900 | glass-card compositions | Pick a shape, write `w-[Npx]` (add `h-[Npx]` only if fixed-aspect), let auto-fit do the rest. --- ## The layout grammar Every good poster uses these primitives. Mix and match. ### Header row: kicker + title (left) + chip (right) ```tsx <header className="flex items-end justify-between"> <div> <div className="text-[14px] font-bold uppercase tracking-[0.3em] text-white/50"> Pi · Monday, 16 April 2026 </div> <h1 className="mt-2 text-5xl font-black tracking-tight"> Good morning </h1> </div> <div className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[14px] text-white/60"> Live · last 7 days </div> </header> ``` Rule of thumb: **kicker = meta context, title = the answer, right-side chip = status/time**. ### Eyebrow kicker (the "small-caps label") ```tsx <div className="text-[14px] font-bold uppercase tracking-[0.3em] text-white/50"> FIG. 1 — GLOBAL TEMPERATURE ANOMALY </div> ``` - `text-[14px]` to `text-[15px]` - `font-semibold` to `font-bold` - `uppercase` always - `tracking-[0.2em]` to `tracking-[0.5em]` — more tracking = more formal - muted color: `text-white/40` to `text-white/60` on dark, `text-neutral-500` on light ### Italic "reveal word" in a headline The signature move. Break a headline with a gradient-filled Source Serif 4 italic: ```tsx <h1 className="text-7xl font-black tracking-tight leading-[0.9]"> A century and a half of{" "} <em className="italic font-normal" style={{ fontFamily: "'Source Serif 4', serif", background: "linear-gradient(180deg,#fef3c7 0%,#f472b6 55%,#a855f7 100%)", WebkitBackgroundClip: "text", color: "transparent", }} > warming, </em>{" "} charted in one line. </h1> ``` The pastel-to-fuchsia-to-violet gradient is the most reusable "poster-ai look". Other good ones: - `#fef3c7 → #f97316 → #dc2626` (cream → orange → crimson) - `#22d3ee → #8b5cf6` (cyan → violet) - `#fde68a → #f59e0b → #ec4899` (butter → amber → pink) ### Card (the workhorse container) ```tsx <div className="rounded-2xl border border-white/[0.06] bg-white/[0.03] p-5" style={{ boxShadow: "inset 0 1px 0 0 rgba(255,255,255,0.04), 0 20px 40px -24px rgba(0,0,0,0.6)" }} > {children} </div> ``` - `rounded-xl` (12px) to `rounded-3xl` (24px) - subtle border: `border-white/[0.05-0.15]` on dark, `border-neutral-200/80` on light - subtle fill: `bg-white/[0.02-0.08]` on dark, pure `bg-white` on light - inset top highlight + offset shadow for floating feel ### KPI stat ```tsx <div className="rounded-2xl border border-white/[0.06] bg-white/[0.03] p-5"> <div className="flex items-center justify-between"> <div className="flex h-9 w-9 items-center justify-center rounded-xl" style={{ background: "linear-gradient(135deg,#22d3ee,#3b82f6)" }}> <TrendingUpIcon className="h-4 w-4 text-white" /> </div> <span className="text-[14px] font-medium text-emerald-400">+18.4%</span> </div> <div className="mt-4 text-[14px] uppercase tracking-wider text-white/40">ARR</div> <div className="mt-1 text-2xl font-semibold tabular-nums">$14.8M</div> </div> ``` Gradient icon square + label + value + delta chip. Use `tabular-nums` on every number. ### Hero metric (the big single number) ```tsx <div> <div className="text-[14px] font-bold uppercase tracking-[0.3em] text-white/80"> You listened to </div> <div className="mt-2 font-black leading-[0.82] tracking-tighter tabular-nums" style={{ fontSize: 220 }}> 586 </div> <div className="mt-1 text-3xl font-bold">hours of music</div> <div className="mt-1 text-lg text-white/80"> That's longer than 83% of listeners in Portugal. </div> </div> ``` Kicker → ENORMOUS number (180-260px) → unit → context sentence. Set `fontSize` inline when going above `text-9xl`. ### Activity ring (Apple-Watch style) ```tsx const stroke = 18, r = (200 - stroke) / 2, c = 2 * Math.PI * r; const pct = Math.min(value / goal, 1.4); <svg width={200} height={200} className="-rotate-90"> <circle cx={100} cy={100} r={r} stroke={color} strokeOpacity={0.15} strokeWidth={stroke} fill="none" /> <circle cx={100} cy={100} r={r} stroke={color} strokeWidth={stroke} strokeLinecap="round" fill="none" strokeDasharray={c} strokeDashoffset={c * (1 - pct)} style={{ filter: `drop-shadow(0 0 8px ${color}99)` }} /> </svg> ``` ### Contribution heatmap (GitHub-style) ```tsx const levelColor = ["rgba(255,255,255,0.05)", "#0e4429", "#006d32", "#26a641", "#39d353"]; <div className="flex gap-[3px]"> {weeks.map((week, w) => ( <div key={w} className="flex flex-col gap-[3px]"> {week.map((level, d) => ( <div key={d} className="h-[14px] w-[14px] rounded-sm" style={{ background: levelColor[level] }} /> ))} </div> ))} </div> ``` ### Footer ```tsx <footer className="mt-6 flex items-center justify-between border-t border-white/5 pt-4 text-[14px] text-white/30"> <span>poster · generated 2026-04-16</span> <span>nebula.dev/pulse</span> </footer> ``` --- ## Color systems Pick ONE family and stick to it. Mixing accents = muddy. ### Dark theme bases | color | use | |---|---| | `#05050a` | black with fuchsia cast | | `#07060d` / `#0b0a12` | black with violet cast | | `#0a0a0f` | neutral near-black | | `#0d1117` | GitHub black | | `#07080c` | blue-black | ### Light theme bases | color | use | |---|---| | `#fafaf7` | editorial off-white | | `#faf5ed` | warm paper | | `#f5e8de` | magazine cream | | `#fef08a` | brutalist yellow | | `#fef3c7` | pastel cream | ### Accent families (pick one) | family | swatches | vibe | |---|---|---| | **cyan/violet** | `#22d3ee`, `#3b82f6`, `#a78bfa`, `#7c3aed` | tech, analytics, premium | | **amber/rose** | `#fbbf24`, `#f97316`, `#fb7185`, `#e11d48` | warm, retail, alerts | | **emerald** | `#10b981`, `#34d399`, `#39d353` | growth, health, positive | | **fuchsia/violet** | `#ec4899`, `#f472b6`, `#a855f7`, `#6d28d9` | consumer, energy, wrapped | ### Gradient recipes ```ts // Radial "hotspot" — use two at opposite corners for depth "radial-gradient(800px 500px at 90% 0%, rgba(139,92,246,0.18), transparent 60%), #0a0a0f" // Pastel reveal gradient (for gradient text) "linear-gradient(180deg,#fef3c7 0%,#f472b6 55%,#a855f7 100%)" // Tech gradient (for icon squares) "linear-gradient(135deg,#22d3ee,#3b82f6)" // Sunset (for story backgrounds) "radial-gradient(ellipse at top, #7c3aed 0%, #ec4899 40%, #f97316 75%, #fbbf24 100%)" ``` ### Grain overlay (adds poster-print feel) ```tsx <div className="pointer-events-none absolute inset-0 opacity-[0.08] mix-blend-overlay" style={{ backgroundImage: "repeating-radial-gradient(circle at 20% 30%, white 0, white 1px, transparent 1px, transparent 4px)", }} /> ``` --- ## Recharts idioms Posters use Recharts differently than an app does. No tooltips (static render), no legend chrome, minimal axes, gradient fills. ### Always - Wrap in `<ResponsiveContainer width="100%" height="100%">` inside a fixed-height parent
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub