diff --git a/packages/cli/package.json b/packages/cli/package.json index a6205f9432..89884bfb56 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -14,7 +14,7 @@ "build": "bun run build:studio && tsup && bun run build:runtime && bun run build:copy", "build:studio": "cd ../studio && bun run build", "build:runtime": "tsx scripts/build-runtime.ts", - "build:copy": "mkdir -p dist/studio dist/docs dist/templates dist/skills && cp -r ../studio/dist/* dist/studio/ && cp -r src/templates/warm-grain src/templates/play-mode src/templates/swiss-grid src/templates/vignelli dist/templates/ && (cp src/docs/*.md dist/docs/ 2>/dev/null || true) && (cp -r ../../skills/compose-video ../../skills/captions ../../skills/transitions ../../skills/media ../../skills/social-media ../../skills/infographics dist/skills/ 2>/dev/null || true)", + "build:copy": "mkdir -p dist/studio dist/docs dist/templates dist/skills && cp -r ../studio/dist/* dist/studio/ && cp -r src/templates/warm-grain src/templates/play-mode src/templates/swiss-grid src/templates/vignelli dist/templates/ && (cp src/docs/*.md dist/docs/ 2>/dev/null || true) && (cp -r ../../skills/compose-video ../../skills/captions ../../skills/media ../../skills/social-media ../../skills/asset-management dist/skills/ 2>/dev/null || true)", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/skills/infographics/SKILL.md b/skills/infographics/SKILL.md deleted file mode 100644 index 6b651e6a61..0000000000 --- a/skills/infographics/SKILL.md +++ /dev/null @@ -1,466 +0,0 @@ ---- -name: infographics -description: Design and animate professional infographic compositions in HyperFrames. Use when a composition needs stats, comparisons, processes, timelines, or any data visualization with clean design and contextual animation. ---- - -# Infographics - -Design and animate professional infographic compositions with clean typography, intentional layout, and animations that reinforce the data story. - -## When to Use - -- Composition mentions stats, percentages, numbers, or comparisons -- Content describes a process, timeline, hierarchy, or flow -- User asks for "infographic", "data visualization", or "stats overlay" -- The narrative has quantitative claims that benefit from visual reinforcement - -## Design Principles - -### Typography Hierarchy - -Every infographic has exactly three levels: - -1. **Hero number/stat** — the largest element, the thing you see first. 120-280px. Bold weight. -2. **Label/title** — what the number means. 24-48px. Medium weight. Muted color. -3. **Supporting context** — comparison, source, timeframe. 16-24px. Light weight or dimmed. - -```css -.stat-hero { - font-size: 200px; - font-weight: 800; - letter-spacing: -0.04em; - line-height: 1; -} -.stat-label { - font-size: 36px; - font-weight: 500; - opacity: 0.7; - letter-spacing: 0.02em; -} -.stat-context { - font-size: 20px; - font-weight: 400; - opacity: 0.5; -} -``` - -### Layout Rules - -- **One idea per frame.** Don't combine unrelated stats. Each infographic composition shows one concept. -- **Grid-aligned.** Use an invisible grid (margins: 80px, gutters: 40px). Elements snap to grid lines. -- **Generous whitespace.** At least 30% of the frame should be empty. Cramped layouts look amateur. -- **Left-aligned or centered.** Never right-aligned unless the layout demands it (RTL, split-screen right panel). -- **Consistent margins.** 80px from all edges minimum. Content never touches the frame edge. - -### Color - -- **Two colors maximum** per infographic — one for the hero element, one for everything else. -- **Accent color for the stat.** The number/bar/chart gets the accent. Labels and context stay neutral. -- **Dark backgrounds for video overlays.** Use `rgba(0,0,0,0.85)` or solid `#111` — never pure black. -- **No gradients, no shadows, no glows.** Flat, clean, confident. - -### What NOT to Do - -- No clip art, icons-for-decoration, or stock imagery -- No rounded-everything — use sharp corners or very subtle radius (4-8px max) -- No borders around stats — the typography IS the design -- No more than 3 colors in a single frame -- No center-of-screen placement for everything — use intentional asymmetry -- No decorative animation — every movement communicates something - -## Infographic Types - -### Single Stat - -The most common. One number, one label, optional context. - -```html -
- 0% - increase in engagement -
-``` - -### Comparison (Side-by-Side) - -Two stats contrasted. Use a vertical divider or spatial separation. - -```html -
- -
- 12% - Before -
- -
- -
- 47% - After -
-
-``` - -### Bar Chart - -Horizontal bars. The bar width IS the data. - -```html -
-
- Desktop -
-
- 72% -
-
-
- Mobile -
-
- 34% -
-
-
-``` - -### Progress / Meter - -A single horizontal line that fills. Good for "X out of Y" or percentage completion. - -```html -
-
- Progress - 0% -
-
-
-
-
-``` - -### Process / Steps - -Numbered steps with a connecting line. Show one step at a time or reveal sequentially. - -```html -
-
- 01 -
- Capture - Record your content -
-
-
- 02 -
- Edit - Compose in HyperFrames -
-
-
- 03 -
- Publish - Render and share -
-
-
-``` - -## Animation Patterns - -Every animation serves one of three purposes: **reveal**, **emphasize**, or **connect**. If an animation doesn't do one of these, remove it. - -### Count-Up (for numbers) - -The number counts from 0 to its final value. This IS the reveal — don't also fade in. - -```js -// Count from 0 to 47 -const target = { val: 0 }; -tl.to( - target, - { - val: 47, - duration: 1.2, - ease: "power2.out", - onUpdate: () => { - document.getElementById("stat-num").textContent = Math.round(target.val) + "%"; - }, - }, - startTime, -); -``` - -- **Duration:** 0.8-1.5s depending on the magnitude of the number -- **Ease:** `power2.out` — fast start, gentle land. The number "arrives" at its value. -- **Never linear.** Linear count-up feels mechanical. - -### Bar Growth (for bar charts) - -Bars grow from 0% to their target width. Stagger each bar. - -```js -tl.to("#bar-1", { width: "72%", duration: 0.8, ease: "power3.out" }, startTime); -tl.to("#bar-1-label", { opacity: 1, duration: 0.3 }, startTime + 0.6); - -tl.to("#bar-2", { width: "34%", duration: 0.8, ease: "power3.out" }, startTime + 0.2); -tl.to("#bar-2-label", { opacity: 1, duration: 0.3 }, startTime + 0.8); -``` - -- **Stagger:** 0.15-0.25s between bars -- **Label appears** near the end of the bar's growth, not simultaneously -- **Ease:** `power3.out` — bars decelerate as they reach their target - -### Progress Fill - -The fill bar and the label count up in sync. - -```js -const progress = { val: 0 }; -tl.to( - progress, - { - val: 78, - duration: 1.5, - ease: "power2.inOut", - onUpdate: () => { - document.getElementById("progress-fill").style.width = progress.val + "%"; - document.getElementById("progress-label").textContent = Math.round(progress.val) + "%"; - }, - }, - startTime, -); -``` - -### Entrance Choreography - -Elements enter in reading order: top-to-bottom, primary-to-secondary. The hero element enters first, supporting elements follow. - -```js -// 1. Hero stat enters (the thing you see first) -tl.fromTo( - "#stat-num", - { opacity: 0, y: 30 }, - { opacity: 1, y: 0, duration: 0.5, ease: "power3.out" }, - startTime, -); - -// 2. Label follows (explains what the number means) -tl.fromTo( - ".stat-label", - { opacity: 0, y: 20 }, - { opacity: 1, y: 0, duration: 0.4, ease: "power3.out" }, - startTime + 0.15, -); - -// 3. Context last (supporting detail) -tl.fromTo( - ".stat-context", - { opacity: 0 }, - { opacity: 1, duration: 0.3, ease: "power2.out" }, - startTime + 0.3, -); -``` - -- **Stagger:** 0.1-0.2s between hierarchy levels -- **Direction:** `y: 20-30` (subtle upward movement). Never from the sides for stats. -- **Duration:** decreases with hierarchy — hero 0.5s, label 0.4s, context 0.3s -- **Ease:** `power3.out` for primary, `power2.out` for secondary - -### Exit - -Clean, fast, uniform. All elements exit together — don't reverse the entrance choreography. - -```js -tl.to(".stat-frame", { opacity: 0, duration: 0.3, ease: "power2.in" }, exitTime); -``` - -- **Duration:** 0.2-0.4s. Exits are faster than entrances. -- **No y movement on exit.** Just fade. The next frame takes over. - -### Sequential Steps - -Each step enters one at a time, synchronized to narration. - -```js -steps.forEach((step, i) => { - // Step number gets accent color when active - tl.to(step.el, { opacity: 1, duration: 0.4, ease: "power2.out" }, step.startTime); - tl.to(step.numberEl, { color: accentColor, duration: 0.3 }, step.startTime); - - // Previous step dims - if (i > 0) { - tl.to(steps[i - 1].el, { opacity: 0.3, duration: 0.3 }, step.startTime); - tl.to(steps[i - 1].numberEl, { color: dimColor, duration: 0.3 }, step.startTime); - } -}); -``` - -### Comparison Reveal - -Show both sides, but reveal the "after" with emphasis. - -```js -// "Before" appears first, muted -tl.fromTo("#stat-before", { opacity: 0 }, { opacity: 1, duration: 0.4 }, startTime); - -// Pause for the viewer to register "before" -// "After" enters with more energy -tl.fromTo( - "#stat-after", - { opacity: 0, scale: 0.8 }, - { opacity: 1, scale: 1, duration: 0.5, ease: "back.out(1.4)" }, - startTime + 0.6, -); -``` - -## Timing to Narration - -Infographics must sync to the spoken content. The stat appears when the narrator says the number. - -```js -// Transcript: "...engagement increased by forty-seven percent..." -// "forty-seven" starts at 3.2s, "percent" ends at 3.9s -// Start count-up at 3.2s, land at 3.9s -const STAT_START = 3.2; -const STAT_LAND = 3.9; - -tl.to( - target, - { - val: 47, - duration: STAT_LAND - STAT_START, - ease: "power2.out", - onUpdate: () => { - el.textContent = Math.round(target.val) + "%"; - }, - }, - STAT_START, -); -``` - -- **Stat appears** when the narrator starts saying the number -- **Stat lands** (finishes counting) when the narrator finishes saying it -- **Label appears** immediately after the stat lands -- **Exit** when the narrator moves to the next topic (or 1-2s after if there's a pause) - -## Composition Structure - -Each infographic is its own composition file: - -```html - -``` - -Load in the root composition: - -```html -
-``` - -## Constraints - -- **Deterministic.** No `Math.random()`, no `Date.now()`. -- **Register timeline.** `window.__timelines["composition-id"] = tl;` -- **Scoped CSS.** `[data-composition-id="..."]` prefix on all selectors. -- **One infographic per composition.** Don't pack multiple stats into one file. -- **Count-up uses `onUpdate`.** This is the one place callback-driven animation is appropriate. The callback must be deterministic (same input → same display at any given time). -- **Font loading.** Include `@import url(...)` in the composition's `