getsentry/skills包含需要注意的行為
SKILL DETAIL
presentation-creator
getsentry/skills/presentation-creator
Create data-driven presentation slides using React, Vite, and Recharts with Sentry branding. Use when asked to "create a presentation", "build slides", "make a deck", "create a data presentation", "build a Sentry presentation". Scaffolds a complete slide-based app with charts, animations, and single-file HTML output.
安裝量 · 230查看來源
Installation
npx skills add https://github.com/getsentry/skills --skill presentation-creator
技能檔案
SKILL.md
最近同步 · 2026年9月29日
references/chart-patterns.md›
# Recharts Patterns for Sentry Presentations
## Color Usage Rules
**Categorical palette** — for data series, groups, categories where color is just a distinguisher:
```javascript
const CAT = [
'#4e79a7', // steel blue
'#f28e2b', // orange
'#e15759', // coral
'#76b7b2', // teal
'#59a14f', // green
'#edc948', // gold
'#b07aa1', // mauve
'#ff9da7', // pink
'#9c755f', // brown
'#bab0ac', // gray
];
```
**Semantic colors** — ONLY when the color itself carries meaning:
```javascript
const SEM_GREEN = '#2ba185'; // positive / success / good
const SEM_RED = '#f55459'; // negative / failure / bad
const SEM_AMBER = '#d4953a'; // warning / caution
```
**Rule of thumb**: If you could swap two colors without losing information, use `CAT`. If swapping would confuse the meaning (e.g., making "errors" green), use semantic.
## Shared Configuration
### Axis and Grid Defaults
```javascript
const ax = {
axisLine: { stroke: BORDER },
tickLine: false,
tick: { fill: MUTED, fontSize: 11, fontFamily: 'Rubik, system-ui' }
};
const grid = {
strokeDasharray: '3 3',
stroke: '#f0edf3',
vertical: false
};
```
### Tooltip Styling
```javascript
<Tooltip
contentStyle={{
background: '#fff',
border: `1px solid ${BORDER}`,
borderRadius: 6,
fontSize: 12,
fontFamily: 'Rubik, system-ui'
}}
/>
```
## Common Chart Types
### 1. Stacked Area Chart (ComposedChart)
Best for showing volume breakdowns over time.
When the series have inherent good/bad semantics (accepted vs dropped), use semantic colors:
```jsx
<Area type="monotone" dataKey="accepted" stackId="1"
fill={SEM_GREEN} stroke={SEM_GREEN} fillOpacity={0.7} />
<Area type="monotone" dataKey="dropped" stackId="1"
fill={SEM_RED} stroke={SEM_RED} fillOpacity={0.5} />
```
When the series are neutral categories (e.g., different SDK types, regions), use categorical:
```jsx
<Area type="monotone" dataKey="javascript" stackId="1"
fill={CAT[0]} stroke={CAT[0]} fillOpacity={0.6} />
<Area type="monotone" dataKey="python" stackId="1"
fill={CAT[1]} stroke={CAT[1]} fillOpacity={0.6} />
<Area type="monotone" dataKey="ruby" stackId="1"
fill={CAT[2]} stroke={CAT[2]} fillOpacity={0.6} />
```
### 2. Bar Chart (Grouped/Stacked)
For discrete comparisons. Use `CAT` colors unless the bars represent good/bad outcomes.
```jsx
<ResponsiveContainer width="100%" height={280}>
<BarChart data={data}>
<CartesianGrid {...grid} />
<XAxis dataKey="name" {...ax} />
<YAxis {...ax} />
<Bar dataKey="seriesA" fill={CAT[0]} radius={[3, 3, 0, 0]} />
<Bar dataKey="seriesB" fill={CAT[1]} radius={[3, 3, 0, 0]} />
</BarChart>
</ResponsiveContainer>
```
For a single-series bar chart where all bars represent the same metric, use a single `CAT` color uniformly — do NOT alternate colors per bar unless the bars represent distinct categories.
### 3. Line/Area Curve Chart
Best for showing mathematical relationships (rate curves, thresholds).
```jsx
<ResponsiveContainer width="100%" height={300}>
<ComposedChart data={curveData}>
<CartesianGrid {...grid} />
<XAxis dataKey="x" {...ax} label={{ value: 'Incoming (t/s)', ... }} />
<YAxis {...ax} domain={[0, 100]} label={{ value: 'Rate %', ... }} />
<Area type="monotone" dataKey="rate" fill={CAT[0]} fillOpacity={0.15} stroke={CAT[0]} strokeWidth={2} />
</ComposedChart>
</ResponsiveContainer>
```
### 4. Temporal Scenario Chart (stepAfter)
Best for showing discrete rule updates with lag. Use semantic colors when steps represent accept/reject:
```jsx
<Area type="stepAfter" dataKey="accepted" stackId="1"
fill={SEM_GREEN} stroke={SEM_GREEN} fillOpacity={0.6} />
<Area type="stepAfter" dataKey="hardBlocked" stackId="1"
fill={SEM_RED} stroke="none" fillOpacity={0.5} />
```
### 5. Reference Lines and Areas
```jsx
{/* Threshold line — semantic amber for "warning" boundary */}
<ReferenceLine y={300} stroke={SEM_AMBER} strokeDasharray="6 3" />
{/* Shaded zone — semantic green for "safe" region */}
<ReferenceArea x1="03:00" x2="03:10" fill={SEM_GREEN} fillOpacity={0.08}
label={{ value: '~10 min', fill: SEM_GREEN, fontSize: 11 }} />
```
## Data Generation Patterns
### Gaussian Spike
```javascript
function gaussian(x, center, width, height) {
return height * Math.exp(-((x - center) ** 2) / (2 * width ** 2));
}
```
### Sinusoidal Daily Pattern
```javascript
const base = 200 + 50 * Math.sin((i / 144) * Math.PI * 2 - Math.PI / 2);
```
### Exponential Adaptation Lag
```javascript
const lagFactor = Math.min(1, (i - spikeStart) / lagIntervals);
const effectiveRate = prevRate + (targetRate - prevRate) * lagFactor;
```
### useMemo for Data
Always wrap data generation in `useMemo`:
```javascript
const data = useMemo(() => {
return Array.from({ length: 144 }, (_, i) => {
// generate point
return { label, incoming, accepted, sampled };
});
}, []);
```
## Custom Diagram Components
### Zone Diagram
Horizontal bar showing zones. Use semantic colors ONLY when zones carry meaning (e.g., Normal=green, Danger=red). For neutral categories, use `CAT`:
```jsx
function ZoneDiagram({ zones }) {
return (
<div className="zone-diagram">
{zones.map((z, i) => (
<div key={i} style={{ flex: z.flex, background: z.color, padding: '12px 16px', color: '#fff' }}>
<div className="zone-name">{z.name}</div>
<div className="zone-desc">{z.desc}</div>
</div>
))}
</div>
);
}
```
```css
.zone-diagram { display: flex; gap: 2px; border-radius: 8px; overflow: hidden; }
```
### Trace Diagram
Visual span representation for distributed traces. Use `CAT` for different services:
```jsx
function TraceDiagram({ rows }) {
return (
<div className="trace-diagram">
{rows.map((r, i) => (
<div key={i} className="trace-row">
<span className="trace-label">{r.label}</span>
<div className="trace-bar">
{r.spans.map((s, j) => (
<div key={j} style={{
flex: s.w, background: s.bg || CAT[j % CAT.length],
opacity: s.opacity ?? 1,
borderRadius: 3
}} />
))}
</div>
</div>
))}
</div>
);
}
```
### Sparkline (Mini SVG Chart)
```jsx
function Sparkline({ seed = 0, bars = 14, color = CAT[0] }) {
const h = Array.from({ length: bars }, (_, i) =>
20 + ((seed * 17 + i * 31) % 60)
);
return (
<svg width={bars * 5} height={40} style={{ verticalAlign: 'middle' }}>
{h.map((v, i) => (
<rect key={i} x={i * 5} y={40 - v * 0.4} width={3.5}
height={v * 0.4} rx={1} fill={color} opacity={0.7} />
))}
</svg>
);
}
```
## Chart Container Pattern
Always wrap charts in a container div:
```jsx
<div className="chart-wrap d2">
<ResponsiveContainer width="100%" height={320}>
{/* chart */}
</ResponsiveContainer>
<p style={{ fontSize: '0.8rem', color: MUTED, textAlign: 'center', marginTop: 8 }}>
Chart annotation or description
</p>
</div>
```
## Responsive Sizing
- Default chart height: 280-340px
- Side-by-side charts: 260-300px each
- Mini/sparkline charts: 80-120px
- Always use `ResponsiveContainer` with `width="100%"`
- Set explicit `margin` on the chart component for axis label space
references/design-system.md›
# Sentry Presentation Design System
## Color Palette
### Color Philosophy
Use two distinct color sets:
1. **Semantic colors** — Only when the color itself carries meaning (good/bad, success/failure, warning). Do not use these for arbitrary data grouping.
2. **Categorical colors** — For distinguishing data series, groups, or categories where color has no inherent meaning. Inspired by Tableau's palette for maximum distinguishability.
### CSS Variables
```css
:root {
/* ── UI chrome ── */
--dark: #1c1028;
--purple: #6c5fc7;
--purple-light: #b5aade;
--purple-bg: #ede8f5;
--bg: #faf9fb;
--card: #f3f1f5;
--muted: #80708f;
--border: #dbd6e1;
/* ── Semantic (use ONLY when meaning applies) ── */
--semantic-green: #2ba185; /* positive, success, good */
--semantic-red: #f55459; /* negative, failure, bad */
--semantic-amber: #d4953a; /* warning, caution, moderate */
--semantic-green-bg: #e0f5ef;
--semantic-red-bg: #fde8e9;
--semantic-amber-bg: #fdf3e4;
}
```
### JS Color Constants (for Charts.jsx)
```javascript
// ── UI / chrome ──
const DARK = '#1c1028';
const MUTED = '#80708f';
const BORDER = '#dbd6e1';
// ── Categorical palette (Tableau-inspired) ──
// Use for data series, groups, and categories where color is just a label.
const CAT = [
'#4e79a7', // steel blue
'#f28e2b', // orange
'#e15759', // coral
'#76b7b2', // teal
'#59a14f', // green
'#edc948', // gold
'#b07aa1', // mauve
'#ff9da7', // pink
'#9c755f', // brown
'#bab0ac', // gray
];
// ── Semantic (use ONLY when the color conveys meaning) ──
const SEM_GREEN = '#2ba185'; // positive / success / good
const SEM_RED = '#f55459'; // negative / failure / bad
const SEM_AMBER = '#d4953a'; // warning / caution
```
### When to use which
| Scenario | Palette | Example |
|----------|---------|---------|
| Bar chart comparing 7 SKU types | `CAT[0]`–`CAT[6]` | Each SKU gets a distinct hue, none implies "good" or "bad" |
| Stacked area: accepted vs dropped | Semantic | green = accepted (good), red = dropped (bad) |
| Heatmap cells: adopted vs not | Semantic | green dot = adopted, empty = not |
| Before/after comparison | `CAT[0]` / `CAT[1]` | Two neutral colors, neither implies better |
| Priority columns: Protect / Grow / Expand | Semantic | green = protect, amber = grow, red = expand gaps |
| Multiple org lines on same chart | `CAT` | Each org gets next color in sequence |
## Typography
**Font**: Rubik (Google Fonts) with system-ui fallback.
```css
body {
font-family: 'Rubik', system-ui, -apple-system, sans-serif;
color: var(--dark);
background: var(--bg);
line-height: 1.7;
font-size: 0.9rem;
}
```
| Element | Size | Weight | Extra |
|---------|------|--------|-------|
| h1 | 2.5rem | 700 | letter-spacing: -0.03em |
| h2 | 1.55rem | 600 | letter-spacing: -0.02em |
| h3 | 1rem | 600 | — |
| subtitle | 0.95rem | 400 | max-width: 620px, color: var(--muted) |
| body | 0.9rem | 400 | line-height: 1.7 |
## Slide System CSS
```css
.progress {
position: fixed; top: 0; left: 0; height: 3px;
background: var(--purple); transition: width 0.3s; z-index: 10;
}
.slide {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
opacity: 0; pointer-events: none;
transition: opacity 0.45s ease;
}
.slide.active { opacity: 1; pointer-events: auto; }
.slide-content {
width: 100%; max-width: 880px;
padding: 60px 40px 100px;
}
```
## Tags
Used on every slide to label the category (Background, Problem, Proposal, etc.).
```css
.tag {
display: inline-block; font-size: 0.66rem; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.08em;
padding: 4px 10px; border-radius: 4px;
margin-bottom: 8px;
}
.tag-purple { background: var(--purple-bg); color: var(--purple); }
.tag-red { background: var(--semantic-red-bg); color: var(--semantic-red); }
.tag-green { background: var(--semantic-green-bg); color: var(--semantic-green); }
.tag-amber { background: var(--semantic-amber-bg); color: var(--semantic-amber); }
```
## Layout Utilities
```css
.cols { display: flex; gap: 40px; max-width: 1060px; }
.col { flex: 1; }
.cards { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.card {
background: #fff; border: 1px solid var(--border);
border-radius: 8px; padding: 17px;
}
.chart-wrap { max-width: 920px; margin: 0 auto; }
```
## Animations
```css
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.anim h2, .anim .subtitle, .anim .cols,
.anim .cards, .anim .chart-wrap, .anim table,
.anim .zone-diagram, .anim ul {
opacity: 0; animation: fadeUp 0.5s ease both;
}
.anim .d1 { animation-delay: 0.1s; }
.anim .d2 { animation-delay: 0.2s; }
.anim .d3 { animation-delay: 0.3s; }
```
Add the `.anim` class to `.slide-content` only when the slide is active. Use `.d1`, `.d2`, `.d3` on child elements for staggered entrance.
## Persistent Glyph Watermark
Every slide shows a small, low-contrast Sentry glyph in the top-left corner. Use the exact path from `references/sentry-glyph.svg`.
```css
.glyph-watermark {
position: fixed; top: 20px; left: 24px; z-index: 8;
opacity: 0.12; pointer-events: none;
display: flex; align-items: center; gap: 10px;
}
.watermark-title {
font-size: 1.1rem; font-weight: 600;
letter-spacing: -0.01em;
line-height: 1;
}
```
Hidden on the title slide (slide 0), shown on all others:
```jsx
{cur > 0 && (
<div className="glyph-watermark">
<SentryGlyph size={50} />
<span className="watermark-title">Presentation Title</span>
</div>
)}
```
## Navigation
Navigation sits at the bottom of the viewport with **no border, no background separation** — it floats transparently over the slide. Always show the slide number as `{current} / {total}`.
```css
nav {
position: fixed; bottom: 0; left: 0; right: 0;
display: flex; align-items: center; justify-content: center;
gap: 16px; padding: 14px;
z-index: 5;
}
nav button {
background: none; border: none;
cursor: pointer; font-family: inherit;
font-size: 0.8rem; color: var(--muted);
padding: 4px 8px;
}
nav button:hover { color: var(--dark); }
nav button:disabled { opacity: 0.2; cursor: default; }
.slide-number {
font-size: 0.75rem; color: var(--muted);
font-variant-numeric: tabular-nums;
}
```
### Dot Indicators
```css
.dots { display: flex; gap: 6px; }
.dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--border); cursor: pointer;
transition: background 0.2s;
}
.dot.on { background: var(--purple); }
```
### Nav Component
```jsx
function Nav({ cur, total, go, setCur }) {
return (
<nav>
<button onClick={() => go(-1)} disabled={cur === 0}>←</button>
<div className="dots">
{Array.from({ length: total }, (_, i) => (
<div key={i} className={`dot${i === cur ? ' on' : ''}`} onClick={() => setCur(i)} />
))}
</div>
<button onClick={() => go(1)} disabled={cur === total - 1}>→</button>
<span className="slide-number">{cur + 1} / {total}</span>
</nav>
);
}
```
## Icons
Use Material Symbols Outlined for icons:
```html
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200&display=swap" rel="stylesheet" />
```
```jsx
<span className="material-symbols-outlined">chevron_right</span>
```
## Comparison Tables
```css
.compare { width: 100%; border-collapse: collapse; }
.compare th {
text-align: left; font-weight: 600;
padding: 10px 14px; border-bottom: 2px solid var(--border);
}
.compare td { padding: 10px 14px; border-bottom: 1px solid #f0edf3; }
```
## Sentry Logo
**Do NOT hardcode the Sentry logo as inline SVG.** Read the official SVGs from the skill references directory:
- **Full wordmark**: `references/sentry-logo.svg` — the "Sentry" logotype with glyph. Use on title slides.
- **Glyph only**: `references/sentry-glyph.svg` — the standalone glyph mark. Use for compact branding.
Read the SVG file contents and embed them as a React component using `dangerouslySetInnerHTML` or by extracting the `<path>` data:
```jsx
// Read the SVG file, extract the content, and embed it:
function SentryLogo({ width = 120 }) {
// The SVG content should be read from references/sentry-logo.svg
// and embedded directly — never use a hardcoded approximation.
return (
<svg viewBox="0 0 200 44" width={width} fill="none" aria-hidden="true">
{/* paste exact path data from sentry-logo.svg */}
</svg>
);
}
function SentryGlyph({ size = 32 }) {
return (
<svg viewBox="0 0 72 66" width={size} height={size} aria-hidden="true">
{/* paste exact path data from sentry-glyph.svg */}
</svg>
);
}
```
## Wrapup Column Variants
For summary/decision slides with multi-column layouts:
```css
.wrapup-col--muted { border-top: 3px solid #80708f; }
.wrapup-col--muted h3 { color: #3e3450; }
.wrapup-col--muted li::before {
content: 'chevron_right';
font-family: 'Material Symbols Outlined';
color: #80708f;
}
```
references/sentry-glyph.svg›
<svg class="css-lfbo6j e1igk8x04" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 66" width="400" height="367" aria-hidden="true"><path d="M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z" transform="translate(11, 11)" fill="#181225"></path></svg>references/sentry-logo.svg›
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 44" aria-hidden="true" class="css-4zleql e6sdxp70"><path fill="currentColor" d="M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z"></path></svg>SKILL.md›
---
name: presentation-creator
description: Create data-driven presentation slides using React, Vite, and Recharts with Sentry branding. Use when asked to "create a presentation", "build slides", "make a deck", "create a data presentation", "build a Sentry presentation". Scaffolds a complete slide-based app with charts, animations, and single-file HTML output.
---
# Sentry Presentation Builder
Create interactive, data-driven presentation slides using React + Vite + Recharts, styled with the Sentry design system and built as a single distributable HTML file.
## Step 1: Gather Requirements
Ask the user:
1. What is the presentation topic?
2. How many slides (typically 5-8)?
3. What data/charts are needed? (time series, comparisons, diagrams, zone charts)
4. What is the narrative arc? (problem → solution, before → after, technical deep-dive)
### Data Assessment (CRITICAL)
Before designing any slides, assess whether the source content contains **real quantitative data** (numbers, percentages, measurements, time series, costs, metrics). Only create Recharts visualizations for slides where real data exists. Do NOT fabricate, estimate, or invent data to fill charts.
- **Has real data** → use a Recharts chart (bar, area, line, etc.)
- **Has no data** → use text-based layouts: cards, tables, bullet columns, diagrams, or quote blocks. Do NOT create a chart with made-up numbers.
If the source content is purely qualitative (narrative, opinions, strategy, process descriptions), the presentation should use zero charts. Recharts and `Charts.jsx` should only be included in the project if at least one slide has real data to visualize.
## Step 2: Scaffold the Project
Create the project structure:
```
<project-name>/
├── index.html
├── package.json
├── vite.config.js
└── src/
├── main.jsx
├── App.jsx
├── App.css
└── Charts.jsx
```
### index.html
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200&display=swap" rel="stylesheet" />
<title>TITLE</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
```
### package.json
```json
{
"name": "PROJECT_NAME",
"private": true,
"type": "module",
"scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" },
"dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", "recharts": "^2.15.3" },
"devDependencies": { "@vitejs/plugin-react": "^4.3.4", "vite": "^6.0.0", "vite-plugin-singlefile": "^2.3.0" }
}
```
### vite.config.js
```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteSingleFile } from 'vite-plugin-singlefile'
export default defineConfig({ plugins: [react(), viteSingleFile()] })
```
### main.jsx
```jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './App.css'
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
```
## Step 3: Build the Slide System
Read `references/design-system.md` for the complete Sentry color palette, typography, CSS variables, layout utilities, and animation system.
### App.jsx Structure
Define slides as an array of functions returning JSX:
```jsx
const SLIDES = [
() => ( /* Slide 0: Title */ ),
() => ( /* Slide 1: Context */ ),
// ...
];
```
Each slide function returns a `<div className="slide-content">` with:
1. An `<h2>` heading
2. Optional subtitle paragraph
3. Main content (charts, cards, diagrams, tables)
4. Animation classes: `.anim`, `.d1`, `.d2`, `.d3` for staggered fade-in
Do NOT add category tag pills/badges above headings (e.g., "BACKGROUND", "EXPERIMENTS"). They look generic and add no value. Let the heading speak for itself.
### Navigation
Implement keyboard navigation (ArrowRight/Space = next, ArrowLeft = prev) and a bottom nav overlay with prev/next buttons, dot indicators, and slide number. The nav has **no border or background** — it floats transparently. A small low-contrast Sentry glyph watermark sits fixed in the top-left corner of every slide.
```jsx
function App() {
const [cur, setCur] = useState(0);
const go = useCallback((d) => setCur(c => Math.max(0, Math.min(SLIDES.length - 1, c + d))), []);
useEffect(() => {
const h = (e) => {
if (e.target.tagName === 'INPUT') return;
if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); go(1); }
if (e.key === 'ArrowLeft') { e.preventDefault(); go(-1); }
};
window.addEventListener('keydown', h);
return () => window.removeEventListener('keydown', h);
}, [go]);
return (
<>
{cur > 0 && <div className="glyph-watermark"><SentryGlyph size={50} /><span className="watermark-title">TITLE</span></div>}
<div className="progress" style={{ width: `${((cur + 1) / SLIDES.length) * 100}%` }} />
{SLIDES.map((S, i) => (
<div key={i} className={`slide ${i === cur ? 'active' : ''}`}>
<div className={`slide-content${i === cur ? ' anim' : ''}`}>
<S />
</div>
</div>
))}
<Nav cur={cur} total={SLIDES.length} go={go} setCur={setCur} />
</>
);
}
```
## Step 4: Create Charts (Only When Data Exists)
**IMPORTANT: Only create charts for slides backed by real, concrete data from the source content.** If a slide's content is qualitative (strategies, learnings, process descriptions, opinions), use text-based layouts instead (cards, tables, bullet lists, columns). Never invent numbers, fabricate percentages, or generate synthetic data to populate a chart. If you are unsure whether data is real or inferred, do NOT create a chart.
If NO slides require charts, skip this step entirely — do not create `Charts.jsx` or import Recharts.
When real data IS available, read `references/chart-patterns.md` for Recharts component patterns including axis configuration, color constants, chart types, and data generation techniques.
Put all chart components in `Charts.jsx`. Key patterns:
- Use `ResponsiveContainer` with explicit height
- Wrap in `.chart-wrap` div with max-width 920px
- Use `useMemo` for data generation
- **Color rule**: Use the Tableau-inspired categorical palette (`CAT[]`) for distinguishing data series and groups. Only use semantic colors (`SEM_GREEN`, `SEM_RED`, `SEM_AMBER`) when the color itself carries meaning (good/bad, success/failure, warning).
- Common charts: `ComposedChart` with stacked `Area`/`Line`, `BarChart`, custom SVG diagrams
- **Every data point in a chart must come from the source content.** Do not interpolate, extrapolate, or round numbers to make charts look better.
## Step 5: Style with Sentry Design System
Apply the complete CSS from the design system reference. Key elements:
- **Font**: Rubik from Google Fonts
- **Colors**: CSS variables for UI chrome (`--purple`, `--dark`, `--muted`). Semantic CSS variables (`--semantic-green`, `--semantic-red`, `--semantic-amber`) only where color conveys meaning. Categorical palette (`CAT[]`) for all other data visualization.
- **Slides**: Absolute positioned, opacity transitions
- **Animations**: `fadeUp` keyframe with staggered delays
- **Layout**: `.cols` flex rows, `.cards` grid, `.chart-wrap` containers
- **Tags**: `.tag-purple`, `.tag-red`, `.tag-green`, `.tag-amber` for slide labels
- **Logo**: Read the official SVG from `references/sentry-logo.svg` (full wordmark) or `references/sentry-glyph.svg` (glyph only). Do NOT hardcode an approximation — always use the exact SVG paths from these files.
## Step 6: Common Slide Patterns
### Title Slide
Logo (from `references/sentry-logo.svg` or `references/sentry-glyph.svg`) + h1 + subtitle + author/date info.
### Problem/Context Slide
Tag + heading + 2-column card grid with icon headers.
### Data Comparison Slide
Tag + heading + side-by-side charts or before/after comparison table.
### Technical Deep-Dive Slide
Tag + heading + full-width chart + annotation bullets below.
### Summary/Decision Slide
Tag + heading + 3-column layout with category headers and bullet lists.
## Step 7: Iterate and Refine
After initial scaffolding:
1. Run `npm install && npm run dev` to start the dev server
2. Iterate on chart data models and visual design
3. Adjust animations, colors, and layout spacing
4. Build final output: `npm run build` produces a single HTML file in `dist/`
## Output Expectations
A working React + Vite project that:
- Renders as a keyboard-navigable slide deck
- Uses Sentry branding (colors, fonts, icons)
- Contains Recharts visualizations **only for slides with real quantitative data** from the source content — no fabricated data
- Omits `Charts.jsx` and the Recharts dependency entirely if no slides have real data
- Builds to a single distributable HTML file
- Has smooth fade-in animations on slide transitions