refactor: remove infographics and transitions skills, add asset-management

Removed skills that don't add value beyond model knowledge:
- infographics: model already produces equivalent output without it
- transitions: patterns are derivable from compose-video constraints

Added:
- asset-management: organize user-uploaded files into assets/ and fonts/
- Typography section in compose-video: min font sizes, font loading
  (Google Fonts + local @font-face), weight pairing, font-display:block
- Assets section in compose-video: project structure with assets/ and
  fonts/ directories, path rules, CORS, "check before creating" rule

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-03-26 17:50:46 +00:00
committed by Vance Ingalls
co-authored by Claude Opus 4.6
parent e2764a2d0c
commit 36a5c23827
6 changed files with 1 additions and 619 deletions
+1 -1
View File
@@ -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": {
-466
View File
@@ -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
<div
class="stat-frame"
style="
position:absolute; inset:0;
display:flex; flex-direction:column;
align-items:flex-start; justify-content:center;
padding: 0 160px;
"
>
<span
class="stat-hero"
id="stat-num"
style="
font-family:'Inter',sans-serif; font-size:200px; font-weight:800;
color:#ffffff; letter-spacing:-0.04em; line-height:1;
"
>0%</span
>
<span
class="stat-label"
style="
font-family:'Inter',sans-serif; font-size:36px; font-weight:500;
color:rgba(255,255,255,0.6); margin-top:16px; letter-spacing:0.02em;
"
>increase in engagement</span
>
</div>
```
### Comparison (Side-by-Side)
Two stats contrasted. Use a vertical divider or spatial separation.
```html
<div style="position:absolute;inset:0;display:flex;">
<!-- Left stat -->
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;">
<span id="stat-before" style="font-size:160px;font-weight:800;color:rgba(255,255,255,0.4);"
>12%</span
>
<span style="font-size:28px;color:rgba(255,255,255,0.4);margin-top:12px;">Before</span>
</div>
<!-- Divider -->
<div style="width:2px;background:rgba(255,255,255,0.1);margin:120px 0;"></div>
<!-- Right stat -->
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;">
<span id="stat-after" style="font-size:160px;font-weight:800;color:#ffffff;">47%</span>
<span style="font-size:28px;color:rgba(255,255,255,0.6);margin-top:12px;">After</span>
</div>
</div>
```
### Bar Chart
Horizontal bars. The bar width IS the data.
```html
<div style="position:absolute;top:50%;left:160px;right:160px;transform:translateY(-50%);">
<div class="bar-row" style="margin-bottom:32px;">
<span style="font-size:20px;color:rgba(255,255,255,0.6);display:block;margin-bottom:8px;"
>Desktop</span
>
<div style="display:flex;align-items:center;gap:16px;">
<div id="bar-1" style="height:48px;width:0%;background:#ffffff;border-radius:4px;"></div>
<span id="bar-1-label" style="font-size:32px;font-weight:700;color:#ffffff;opacity:0;"
>72%</span
>
</div>
</div>
<div class="bar-row">
<span style="font-size:20px;color:rgba(255,255,255,0.6);display:block;margin-bottom:8px;"
>Mobile</span
>
<div style="display:flex;align-items:center;gap:16px;">
<div
id="bar-2"
style="height:48px;width:0%;background:rgba(255,255,255,0.3);border-radius:4px;"
></div>
<span
id="bar-2-label"
style="font-size:32px;font-weight:700;color:rgba(255,255,255,0.6);opacity:0;"
>34%</span
>
</div>
</div>
</div>
```
### Progress / Meter
A single horizontal line that fills. Good for "X out of Y" or percentage completion.
```html
<div style="position:absolute;bottom:200px;left:160px;right:160px;">
<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:16px;">
<span style="font-size:24px;color:rgba(255,255,255,0.5);">Progress</span>
<span id="progress-label" style="font-size:48px;font-weight:800;color:#ffffff;">0%</span>
</div>
<div
style="width:100%;height:8px;background:rgba(255,255,255,0.1);border-radius:4px;overflow:hidden;"
>
<div
id="progress-fill"
style="width:0%;height:100%;background:#ffffff;border-radius:4px;"
></div>
</div>
</div>
```
### Process / Steps
Numbered steps with a connecting line. Show one step at a time or reveal sequentially.
```html
<div style="position:absolute;top:50%;left:160px;right:160px;transform:translateY(-50%);">
<div
class="step"
id="step-1"
style="display:flex;align-items:flex-start;gap:24px;margin-bottom:48px;opacity:0;"
>
<span style="font-size:64px;font-weight:800;color:rgba(255,255,255,0.15);line-height:1;"
>01</span
>
<div>
<span style="font-size:32px;font-weight:700;color:#ffffff;display:block;">Capture</span>
<span style="font-size:20px;color:rgba(255,255,255,0.5);display:block;margin-top:4px;"
>Record your content</span
>
</div>
</div>
<div
class="step"
id="step-2"
style="display:flex;align-items:flex-start;gap:24px;margin-bottom:48px;opacity:0;"
>
<span style="font-size:64px;font-weight:800;color:rgba(255,255,255,0.15);line-height:1;"
>02</span
>
<div>
<span style="font-size:32px;font-weight:700;color:#ffffff;display:block;">Edit</span>
<span style="font-size:20px;color:rgba(255,255,255,0.5);display:block;margin-top:4px;"
>Compose in HyperFrames</span
>
</div>
</div>
<div class="step" id="step-3" style="display:flex;align-items:flex-start;gap:24px;opacity:0;">
<span style="font-size:64px;font-weight:800;color:rgba(255,255,255,0.15);line-height:1;"
>03</span
>
<div>
<span style="font-size:32px;font-weight:700;color:#ffffff;display:block;">Publish</span>
<span style="font-size:20px;color:rgba(255,255,255,0.5);display:block;margin-top:4px;"
>Render and share</span
>
</div>
</div>
</div>
```
## 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
<template id="stat-47-template">
<div data-composition-id="stat-47" data-width="1920" data-height="1080">
<!-- Layout HTML here -->
<style>
[data-composition-id="stat-47"] {
/* scoped styles */
}
</style>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
// Animations here
window.__timelines["stat-47"] = tl;
})();
</script>
</div>
</template>
```
Load in the root composition:
```html
<div
id="el-stat-47"
data-composition-id="stat-47"
data-composition-src="compositions/stat-47.html"
data-start="3"
data-duration="5"
data-track-index="4"
data-width="1920"
data-height="1080"
style="z-index:30;"
></div>
```
## 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 `<style>` block. Don't rely on the parent loading fonts.
- **Dark backgrounds for overlays.** If the infographic overlays video, use a semi-transparent or solid dark background so text is readable.
-23
View File
@@ -1,23 +0,0 @@
# Infographics Skill Evals
Three scenarios testing whether the skill produces well-designed, correctly animated infographic compositions.
## Running
Each eval is a prompt + expected output criteria. Run each prompt in a fresh Claude Code session with the infographics skill installed, then score the output against the criteria.
```bash
# Install skills first
npx tsx packages/cli/src/cli.ts skills --claude
# Run each eval in a fresh session
claude "$(cat skills/infographics/evals/eval-1-single-stat.md)"
claude "$(cat skills/infographics/evals/eval-2-comparison.md)"
claude "$(cat skills/infographics/evals/eval-3-process-steps.md)"
```
## Scoring
Each eval has PASS/FAIL criteria. A criterion passes if the output meets the requirement. Score = passing criteria / total criteria.
**Target:** 80%+ pass rate across all evals.
@@ -1,43 +0,0 @@
Create a HyperFrames composition for a single stat infographic. The narrator says "our conversion rate jumped to forty-seven percent" starting at 2.1 seconds and ending at 3.8 seconds. The stat should appear as an overlay on a dark background at 1920x1080. Save to compositions/stat-conversion.html.
---
## Criteria
### Structure (composition format)
- [ ] File is a valid `<template>` wrapping a `<div>` with `data-composition-id`
- [ ] Has `data-width="1920"` and `data-height="1080"`
- [ ] Includes GSAP script tag (`gsap@3.14.2`)
- [ ] Registers timeline: `window.__timelines["..."] = tl`
- [ ] Timeline created with `{ paused: true }`
- [ ] Script wrapped in IIFE
### Design (typography and layout)
- [ ] Hero stat font size is 120px or larger
- [ ] Label font size is 24-48px
- [ ] No more than 2 colors used (accent + neutral)
- [ ] Left-aligned or centered — not right-aligned
- [ ] Margins are 80px+ from frame edges
- [ ] No gradients, shadows, or glows in styles
- [ ] No border around the stat
- [ ] Uses a professional font (Inter, Montserrat, or similar sans-serif)
### Animation (count-up and choreography)
- [ ] Stat counts up from 0 to 47 (not instant appear)
- [ ] Count-up uses `onUpdate` callback with `Math.round()`
- [ ] Count-up starts at ~2.1s (when narrator says the number)
- [ ] Count-up duration roughly matches narration (2.1s to 3.8s = ~1.7s)
- [ ] Ease is NOT linear (uses power2.out or similar)
- [ ] Label appears AFTER stat lands (not simultaneously)
- [ ] Has an exit animation (opacity fade)
- [ ] Exit is faster than entrance (0.2-0.4s)
### Anti-patterns (should NOT be present)
- [ ] No `Math.random()` or `Date.now()`
- [ ] No decorative animation (no bounce, no spin, no wiggle on the stat)
- [ ] No `display: none` toggling
- [ ] No inline `<style>` without `[data-composition-id]` scoping
@@ -1,42 +0,0 @@
Create a HyperFrames composition showing a before/after comparison infographic. Before: "12% engagement rate". After: "47% engagement rate". The narrator says "we went from twelve percent to forty-seven percent" starting at 5.0s and ending at 7.2s. Landscape 1920x1080, overlay on dark background. Save to compositions/stat-comparison.html.
---
## Criteria
### Structure
- [ ] Valid `<template>` with `data-composition-id`
- [ ] Has `data-width="1920"` and `data-height="1080"`
- [ ] GSAP 3.14.2 included
- [ ] Timeline registered in `window.__timelines`
- [ ] IIFE-wrapped script
### Design (comparison layout)
- [ ] Two distinct stat areas (left/right or before/after labeling)
- [ ] "Before" stat is visually muted (lower opacity, smaller, or dimmer color)
- [ ] "After" stat is visually prominent (full opacity, accent color, or larger)
- [ ] Clear visual hierarchy — the "after" draws the eye first
- [ ] Divider or spatial separation between the two stats
- [ ] No more than 2 colors
- [ ] Margins 80px+ from edges
- [ ] No gradients, shadows, or decorative elements
### Animation (comparison reveal)
- [ ] "Before" appears first (muted entrance)
- [ ] Pause between "before" and "after" reveals (0.3-0.8s gap)
- [ ] "After" enters with more energy than "before" (scale, ease, or speed difference)
- [ ] "After" uses `back.out` or similar overshoot ease (emphasis)
- [ ] "Before" does NOT use overshoot ease (subdued entrance)
- [ ] Timing syncs to narration (~5.0s start)
- [ ] Exit animation present
- [ ] All elements exit together (not reverse choreography)
### Anti-patterns
- [ ] No `Math.random()` or `Date.now()`
- [ ] No simultaneous entrance of both stats (must be staggered)
- [ ] No identical animation for both stats (they should feel different)
- [ ] No `display: none` toggling
@@ -1,44 +0,0 @@
Create a HyperFrames composition showing a 3-step process infographic. Steps: (1) "Record" at 1.0s, (2) "Edit" at 3.5s, (3) "Publish" at 6.0s. Each step has a short description. The narrator introduces each step at the given time. Landscape 1920x1080. Save to compositions/process-steps.html.
---
## Criteria
### Structure
- [ ] Valid `<template>` with `data-composition-id`
- [ ] Has `data-width="1920"` and `data-height="1080"`
- [ ] GSAP 3.14.2 included
- [ ] Timeline registered in `window.__timelines`
- [ ] IIFE-wrapped script
### Design (process layout)
- [ ] Three steps are visually distinct (numbered or labeled)
- [ ] Step numbers use large, muted typography (dim color or low opacity)
- [ ] Step titles are bold and prominent
- [ ] Step descriptions are smaller and muted
- [ ] Consistent vertical spacing between steps
- [ ] Left-aligned layout (not scattered across the frame)
- [ ] Margins 80px+ from edges
- [ ] No icons or clip art — typography only
- [ ] No gradients, shadows, or decorative elements
### Animation (sequential reveal)
- [ ] Steps reveal one at a time, not all at once
- [ ] Step 1 appears at ~1.0s, Step 2 at ~3.5s, Step 3 at ~6.0s
- [ ] Each step fades in with subtle y-offset (upward movement)
- [ ] Active step is visually highlighted (full opacity, accent color on number)
- [ ] Previous steps dim when next step appears (opacity reduction)
- [ ] Previous steps remain visible (dimmed, not hidden)
- [ ] Entrance stagger within each step (number → title → description)
- [ ] Exit animation for the whole group
### Anti-patterns
- [ ] No `Math.random()` or `Date.now()`
- [ ] Steps don't all appear simultaneously
- [ ] No horizontal entrance directions (steps enter vertically, not from sides)
- [ ] No `display: none` toggling
- [ ] No decorative animation on step numbers (no spin, bounce, etc.)