@opendirectory.dev/skills 0.1.58 → 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/registry.json +18 -0
- package/skills/graphic-chart/README.md +104 -0
- package/skills/graphic-chart/SKILL.md +318 -0
- package/skills/graphic-chart/evals/evals.json +30 -0
- package/skills/graphic-chart/references/chart-library.md +487 -0
- package/skills/graphic-chart/references/style-presets.md +219 -0
- package/skills/graphic-chart/scripts/export-chart.sh +182 -0
- package/skills/graphic-chart/scripts/screenshot-chart.mjs +143 -0
- package/skills/graphic-gif/README.md +99 -0
- package/skills/graphic-gif/SKILL.md +313 -0
- package/skills/graphic-gif/evals/evals.json +30 -0
- package/skills/graphic-gif/references/animation-library.md +446 -0
- package/skills/graphic-gif/references/style-presets.md +194 -0
- package/skills/graphic-gif/scripts/capture-and-encode.mjs +201 -0
- package/skills/graphic-gif/scripts/export-gif.sh +274 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# export-chart.sh — Screenshot a Chart.js HTML file as a high-quality retina PNG
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# bash scripts/export-chart.sh <path-to-html> [output.png] [--width N] [--height N]
|
|
6
|
+
#
|
|
7
|
+
# Examples:
|
|
8
|
+
# bash scripts/export-chart.sh ./my-chart/chart.html
|
|
9
|
+
# bash scripts/export-chart.sh ./my-chart/chart.html ./chart.png --width 1200 --height 628
|
|
10
|
+
#
|
|
11
|
+
# What this does:
|
|
12
|
+
# 1. Starts a local HTTP server so CDN scripts and fonts load correctly
|
|
13
|
+
# 2. Launches headless Chromium at the specified viewport with deviceScaleFactor: 2
|
|
14
|
+
# 3. Waits for window.__chartReady === true (set by Chart.js animation.onComplete)
|
|
15
|
+
# 4. Takes a PNG screenshot — output is 2× the specified dimensions (retina quality)
|
|
16
|
+
# 5. Cleans up temp files and opens the result
|
|
17
|
+
#
|
|
18
|
+
# Output PNG = 2× viewport size (e.g. 1080×1080 viewport → 2160×2160 PNG)
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
# ─── Colors ────────────────────────────────────────────────
|
|
22
|
+
RED='\033[0;31m'
|
|
23
|
+
GREEN='\033[0;32m'
|
|
24
|
+
CYAN='\033[0;36m'
|
|
25
|
+
YELLOW='\033[1;33m'
|
|
26
|
+
BOLD='\033[1m'
|
|
27
|
+
NC='\033[0m'
|
|
28
|
+
|
|
29
|
+
info() { echo -e "${CYAN}ℹ${NC} $*"; }
|
|
30
|
+
ok() { echo -e "${GREEN}✓${NC} $*"; }
|
|
31
|
+
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
|
32
|
+
err() { echo -e "${RED}✗${NC} $*" >&2; }
|
|
33
|
+
|
|
34
|
+
# ─── Parse flags ──────────────────────────────────────────
|
|
35
|
+
WIDTH=1080
|
|
36
|
+
HEIGHT=1080
|
|
37
|
+
|
|
38
|
+
POSITIONAL=()
|
|
39
|
+
while [[ $# -gt 0 ]]; do
|
|
40
|
+
case $1 in
|
|
41
|
+
--width) WIDTH="$2"; shift 2 ;;
|
|
42
|
+
--height) HEIGHT="$2"; shift 2 ;;
|
|
43
|
+
*) POSITIONAL+=("$1"); shift ;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
set -- "${POSITIONAL[@]}"
|
|
47
|
+
|
|
48
|
+
# ─── Input validation ─────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
if [[ $# -lt 1 ]]; then
|
|
51
|
+
err "Usage: bash scripts/export-chart.sh <path-to-html> [output.png] [--width N] [--height N]"
|
|
52
|
+
err ""
|
|
53
|
+
err "Examples:"
|
|
54
|
+
err " bash scripts/export-chart.sh ./my-chart/chart.html"
|
|
55
|
+
err " bash scripts/export-chart.sh ./my-chart/chart.html ./chart.png --width 1200 --height 628"
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
INPUT_HTML="$1"
|
|
60
|
+
if [[ ! -f "$INPUT_HTML" ]]; then
|
|
61
|
+
err "File not found: $INPUT_HTML"
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
INPUT_HTML=$(cd "$(dirname "$INPUT_HTML")" && pwd)/$(basename "$INPUT_HTML")
|
|
66
|
+
|
|
67
|
+
if [[ $# -ge 2 ]]; then
|
|
68
|
+
OUTPUT_PNG="$2"
|
|
69
|
+
else
|
|
70
|
+
OUTPUT_PNG="$(dirname "$INPUT_HTML")/$(basename "$INPUT_HTML" .html).png"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
OUTPUT_DIR=$(dirname "$OUTPUT_PNG")
|
|
74
|
+
mkdir -p "$OUTPUT_DIR"
|
|
75
|
+
OUTPUT_PNG="$OUTPUT_DIR/$(basename "$OUTPUT_PNG")"
|
|
76
|
+
|
|
77
|
+
echo ""
|
|
78
|
+
echo -e "${BOLD}╔══════════════════════════════════════╗${NC}"
|
|
79
|
+
echo -e "${BOLD}║ Export Chart to PNG ║${NC}"
|
|
80
|
+
echo -e "${BOLD}╚══════════════════════════════════════╝${NC}"
|
|
81
|
+
echo ""
|
|
82
|
+
info "Viewport: ${WIDTH}×${HEIGHT}px → PNG output: $((WIDTH*2))×$((HEIGHT*2))px @2× retina"
|
|
83
|
+
echo ""
|
|
84
|
+
|
|
85
|
+
# ─── Step 1: Check Node.js ────────────────────────────────
|
|
86
|
+
|
|
87
|
+
info "Checking dependencies..."
|
|
88
|
+
|
|
89
|
+
if ! command -v node &>/dev/null; then
|
|
90
|
+
err "Node.js is required but not installed."
|
|
91
|
+
err ""
|
|
92
|
+
err "Install Node.js:"
|
|
93
|
+
err " macOS: brew install node"
|
|
94
|
+
err " or visit https://nodejs.org and download the installer"
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
ok "Node.js found ($(node --version))"
|
|
99
|
+
|
|
100
|
+
# ─── Step 2: Create temp dir + copy screenshot script ─────
|
|
101
|
+
|
|
102
|
+
TEMP_DIR=$(mktemp -d)
|
|
103
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
104
|
+
TEMP_SCRIPT="$TEMP_DIR/screenshot-chart.mjs"
|
|
105
|
+
cp "$SCRIPT_DIR/screenshot-chart.mjs" "$TEMP_SCRIPT"
|
|
106
|
+
|
|
107
|
+
SERVE_DIR=$(dirname "$INPUT_HTML")
|
|
108
|
+
HTML_FILENAME=$(basename "$INPUT_HTML")
|
|
109
|
+
|
|
110
|
+
# ─── Step 3: Install Playwright ───────────────────────────
|
|
111
|
+
|
|
112
|
+
info "Setting up Playwright (headless Chromium)..."
|
|
113
|
+
info "This may take a moment on first run..."
|
|
114
|
+
echo ""
|
|
115
|
+
|
|
116
|
+
cd "$TEMP_DIR"
|
|
117
|
+
|
|
118
|
+
cat > "$TEMP_DIR/package.json" << 'PKG'
|
|
119
|
+
{ "name": "chart-export", "private": true, "type": "module" }
|
|
120
|
+
PKG
|
|
121
|
+
|
|
122
|
+
npm install playwright 2>/dev/null || {
|
|
123
|
+
err "Failed to install Playwright."
|
|
124
|
+
err "Try running: npm install playwright"
|
|
125
|
+
rm -rf "$TEMP_DIR"
|
|
126
|
+
exit 1
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
npx playwright install chromium 2>/dev/null || {
|
|
130
|
+
err "Failed to install Chromium browser for Playwright."
|
|
131
|
+
err "Try running manually: npx playwright install chromium"
|
|
132
|
+
rm -rf "$TEMP_DIR"
|
|
133
|
+
exit 1
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
ok "Playwright ready"
|
|
137
|
+
echo ""
|
|
138
|
+
|
|
139
|
+
# ─── Step 4: Screenshot ───────────────────────────────────
|
|
140
|
+
|
|
141
|
+
info "Launching headless Chromium and capturing chart..."
|
|
142
|
+
echo ""
|
|
143
|
+
|
|
144
|
+
node "$TEMP_SCRIPT" \
|
|
145
|
+
"$SERVE_DIR" \
|
|
146
|
+
"$HTML_FILENAME" \
|
|
147
|
+
"$OUTPUT_PNG" \
|
|
148
|
+
"$WIDTH" \
|
|
149
|
+
"$HEIGHT" || {
|
|
150
|
+
err "Chart screenshot failed."
|
|
151
|
+
err ""
|
|
152
|
+
err "Common causes:"
|
|
153
|
+
err " - window.__chartReady never set (check animation.onComplete in HTML)"
|
|
154
|
+
err " - Chart.js CDN script failed to load (check internet connection)"
|
|
155
|
+
err " - HTML syntax error (open in browser to verify)"
|
|
156
|
+
rm -rf "$TEMP_DIR"
|
|
157
|
+
exit 1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ─── Step 5: Cleanup + success ────────────────────────────
|
|
161
|
+
|
|
162
|
+
rm -rf "$TEMP_DIR"
|
|
163
|
+
|
|
164
|
+
echo ""
|
|
165
|
+
echo -e "${BOLD}════════════════════════════════════════${NC}"
|
|
166
|
+
ok "Chart exported successfully!"
|
|
167
|
+
echo ""
|
|
168
|
+
echo -e " ${BOLD}File:${NC} $OUTPUT_PNG"
|
|
169
|
+
echo ""
|
|
170
|
+
FILE_SIZE=$(du -h "$OUTPUT_PNG" | cut -f1 | xargs)
|
|
171
|
+
echo " Size: $FILE_SIZE"
|
|
172
|
+
echo " Dimensions: $((WIDTH*2))×$((HEIGHT*2))px (@2× retina)"
|
|
173
|
+
echo ""
|
|
174
|
+
echo " Ready for social, decks, reports, or email."
|
|
175
|
+
echo -e "${BOLD}════════════════════════════════════════${NC}"
|
|
176
|
+
echo ""
|
|
177
|
+
|
|
178
|
+
if command -v open &>/dev/null; then
|
|
179
|
+
open "$OUTPUT_PNG"
|
|
180
|
+
elif command -v xdg-open &>/dev/null; then
|
|
181
|
+
xdg-open "$OUTPUT_PNG"
|
|
182
|
+
fi
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// screenshot-chart.mjs — Capture an ECharts v6 HTML file as a retina-quality PNG
|
|
2
|
+
//
|
|
3
|
+
// Args: <serve-dir> <html-filename> <output-png> <width> <height>
|
|
4
|
+
//
|
|
5
|
+
// How it works:
|
|
6
|
+
// 1. Starts a local HTTP server to serve the HTML and allow CDN scripts to load
|
|
7
|
+
// 2. Launches Chromium at the specified viewport with deviceScaleFactor: 2
|
|
8
|
+
// → output PNG is 2× viewport dimensions (retina quality)
|
|
9
|
+
// 3. Waits for window.__chartReady === true
|
|
10
|
+
// → set by chart.on('finished', ...) in the HTML (registered BEFORE setOption)
|
|
11
|
+
// → belt-and-suspenders: also set by chart.on('rendered', ...) + 100ms debounce
|
|
12
|
+
// → ECharts bug #14101/#17500: 'finished' silently skips if registered after setOption
|
|
13
|
+
// 4. Takes the screenshot and saves as PNG
|
|
14
|
+
//
|
|
15
|
+
// CRITICAL: The HTML must register events BEFORE chart.setOption():
|
|
16
|
+
// chart.on('finished', () => { window.__chartReady = true; });
|
|
17
|
+
// chart.on('rendered', () => { clearTimeout(t); t = setTimeout(() => { window.__chartReady = true; }, 100); });
|
|
18
|
+
// chart.setOption(option); // setOption LAST
|
|
19
|
+
|
|
20
|
+
import { chromium } from 'playwright';
|
|
21
|
+
import { createServer } from 'http';
|
|
22
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
23
|
+
import { join, extname } from 'path';
|
|
24
|
+
|
|
25
|
+
const SERVE_DIR = process.argv[2];
|
|
26
|
+
const HTML_FILE = process.argv[3];
|
|
27
|
+
const OUTPUT_PNG = process.argv[4];
|
|
28
|
+
const VP_WIDTH = parseInt(process.argv[5]) || 1080;
|
|
29
|
+
const VP_HEIGHT = parseInt(process.argv[6]) || 1080;
|
|
30
|
+
|
|
31
|
+
// ─── Static file server ───────────────────────────────────
|
|
32
|
+
// Required for CDN scripts and Google Fonts to load via HTTP
|
|
33
|
+
// (file:// protocol blocks cross-origin requests)
|
|
34
|
+
|
|
35
|
+
const MIME_TYPES = {
|
|
36
|
+
'.html': 'text/html',
|
|
37
|
+
'.css': 'text/css',
|
|
38
|
+
'.js': 'application/javascript',
|
|
39
|
+
'.json': 'application/json',
|
|
40
|
+
'.png': 'image/png',
|
|
41
|
+
'.jpg': 'image/jpeg',
|
|
42
|
+
'.svg': 'image/svg+xml',
|
|
43
|
+
'.woff': 'font/woff',
|
|
44
|
+
'.woff2':'font/woff2',
|
|
45
|
+
'.ttf': 'font/ttf',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const server = createServer((req, res) => {
|
|
49
|
+
const decoded = decodeURIComponent(req.url);
|
|
50
|
+
const filePath = join(SERVE_DIR, decoded === '/' ? HTML_FILE : decoded);
|
|
51
|
+
try {
|
|
52
|
+
const content = readFileSync(filePath);
|
|
53
|
+
const ext = extname(filePath).toLowerCase();
|
|
54
|
+
res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' });
|
|
55
|
+
res.end(content);
|
|
56
|
+
} catch {
|
|
57
|
+
res.writeHead(404);
|
|
58
|
+
res.end('Not found');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const port = await new Promise((resolve) => {
|
|
63
|
+
server.listen(0, () => resolve(server.address().port));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log(` Local server on port ${port}`);
|
|
67
|
+
|
|
68
|
+
// ─── Launch browser at 2× deviceScaleFactor ──────────────
|
|
69
|
+
// deviceScaleFactor: 2 → screenshot is 2× VP dimensions (retina)
|
|
70
|
+
// --font-render-hinting=none → sharper text on Linux CI
|
|
71
|
+
|
|
72
|
+
const browser = await chromium.launch({
|
|
73
|
+
args: [
|
|
74
|
+
'--no-sandbox',
|
|
75
|
+
'--disable-dev-shm-usage',
|
|
76
|
+
'--font-render-hinting=none',
|
|
77
|
+
]
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const context = await browser.newContext({
|
|
81
|
+
viewport: { width: VP_WIDTH, height: VP_HEIGHT },
|
|
82
|
+
deviceScaleFactor: 2,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const page = await context.newPage();
|
|
86
|
+
|
|
87
|
+
// Capture browser console errors for diagnostics
|
|
88
|
+
const pageErrors = [];
|
|
89
|
+
page.on('console', msg => {
|
|
90
|
+
if (msg.type() === 'error') pageErrors.push(msg.text());
|
|
91
|
+
});
|
|
92
|
+
page.on('pageerror', err => pageErrors.push(err.message));
|
|
93
|
+
|
|
94
|
+
// ─── Navigate and wait for chart ─────────────────────────
|
|
95
|
+
// Use networkidle to ensure CDN scripts (Chart.js, plugins) finish loading
|
|
96
|
+
// before we check window.__chartReady
|
|
97
|
+
|
|
98
|
+
await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle' });
|
|
99
|
+
|
|
100
|
+
// Wait for fonts (CDN Google Fonts need a moment)
|
|
101
|
+
await page.evaluate(() => document.fonts.ready);
|
|
102
|
+
|
|
103
|
+
// Wait for Chart.js to finish rendering
|
|
104
|
+
// window.__chartReady is set by animation.onComplete in the HTML
|
|
105
|
+
// Timeout: 15s — generous to allow CDN scripts on slow networks
|
|
106
|
+
console.log(' Waiting for chart to render...');
|
|
107
|
+
try {
|
|
108
|
+
await page.waitForFunction(() => window.__chartReady === true, { timeout: 15000 });
|
|
109
|
+
} catch {
|
|
110
|
+
// Provide a helpful error message
|
|
111
|
+
const bodyHTML = await page.evaluate(() => document.body.innerHTML.substring(0, 500));
|
|
112
|
+
console.error(' ERROR: window.__chartReady was never set after 15s.');
|
|
113
|
+
if (pageErrors.length > 0) {
|
|
114
|
+
console.error(' Browser console errors:');
|
|
115
|
+
pageErrors.forEach(e => console.error(' ', e));
|
|
116
|
+
}
|
|
117
|
+
console.error(' Check that your HTML chart config includes:');
|
|
118
|
+
console.error(' animation: { duration: 0, onComplete: () => { window.__chartReady = true; } }');
|
|
119
|
+
console.error(' If using CDN scripts, verify internet access from the headless browser.');
|
|
120
|
+
console.error(' Page body preview:', bodyHTML);
|
|
121
|
+
await browser.close();
|
|
122
|
+
server.close();
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(' Chart rendered — taking screenshot');
|
|
127
|
+
|
|
128
|
+
// ─── Screenshot ───────────────────────────────────────────
|
|
129
|
+
// animations: 'disabled' stops CSS transitions — does NOT affect Chart.js canvas
|
|
130
|
+
// The canvas is already fully drawn at this point (waited for __chartReady)
|
|
131
|
+
|
|
132
|
+
await page.screenshot({
|
|
133
|
+
path: OUTPUT_PNG,
|
|
134
|
+
animations: 'disabled',
|
|
135
|
+
clip: { x: 0, y: 0, width: VP_WIDTH, height: VP_HEIGHT },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await browser.close();
|
|
139
|
+
server.close();
|
|
140
|
+
|
|
141
|
+
const { statSync } = await import('fs');
|
|
142
|
+
const sizeKB = Math.round(statSync(OUTPUT_PNG).size / 1024);
|
|
143
|
+
console.log(` ✓ PNG saved: ${OUTPUT_PNG} (${sizeKB}KB, ${VP_WIDTH * 2}×${VP_HEIGHT * 2}px)`);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# graphic-gif
|
|
2
|
+
|
|
3
|
+
Generate animated looping GIFs from CSS animations. 800×800px default, 6 animation types, 4 style presets.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx "@opendirectory.dev/skills" install graphic-gif --target claude
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Manual Install (2 steps)
|
|
12
|
+
|
|
13
|
+
1. Copy the URL of this skill folder from your browser, paste it at [download-directory.github.io](https://download-directory.github.io/), download the zip.
|
|
14
|
+
2. Open Claude desktop app → sidebar → **Customize** → **Skills** → **+** → **Upload a skill** → drop the extracted folder (the one containing `SKILL.md`).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What it does
|
|
19
|
+
|
|
20
|
+
- Asks for a prompt (content + motion description) and optional settings
|
|
21
|
+
- Generates a self-contained HTML file with CSS `@keyframes` animations
|
|
22
|
+
- Captures frames using Playwright + Web Animations API for frame-accurate seeking
|
|
23
|
+
- Assembles frames into a GIF with [gifenc](https://github.com/mattdesl/gifenc)
|
|
24
|
+
- Optimizes the GIF with gifsicle for 35–50% size reduction
|
|
25
|
+
- Outputs a looping `animation.gif` ready for social, email, or Slack
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
> "Create an animated GIF, css-animated, typewriter effect. Text: '73% of B2B buyers read 3+ pieces of content before contacting sales.' Each character types out one at a time. Style: terminal. 3 seconds, 12fps, loop=true."
|
|
32
|
+
|
|
33
|
+
Output: `animation.gif` — dark background, JetBrains Mono font, green cursor, character-by-character reveal.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Supported Animation Types
|
|
38
|
+
|
|
39
|
+
| Animation | Description | Best Use |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| fade-in | Content fades in from transparent with upward drift | Quotes, announcements, brand messages |
|
|
42
|
+
| slide-in | Elements slide in from edge with spring overshoot | Headlines, stats, before/after |
|
|
43
|
+
| typewriter | Text types out character by character | Insights, hooks, developer content |
|
|
44
|
+
| counter | Numbers count up to a target value | Stats, metrics, growth numbers |
|
|
45
|
+
| pulse | Pulsing / breathing scale or glow effect | CTAs, icons, live indicators |
|
|
46
|
+
| loop-scroll | Infinite scrolling ticker or marquee | Feature lists, social proof, tickers |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Supported Styles
|
|
51
|
+
|
|
52
|
+
| Style | Best for |
|
|
53
|
+
|---|---|
|
|
54
|
+
| clean-slate | Professional B2B, LinkedIn, any audience expecting polish |
|
|
55
|
+
| terminal | Developer audience, typewriter effects, tech metrics |
|
|
56
|
+
| electric-burst | Bold stats, CTAs, high-energy social content |
|
|
57
|
+
| brutalist | Loop-scroll tickers, design-forward brands, raw aesthetic |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Parameters
|
|
62
|
+
|
|
63
|
+
| Parameter | Required | Default | Description |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| prompt | Yes | — | Content description AND motion brief |
|
|
66
|
+
| animation_type | No | css-animated | css-animated / ai-generated |
|
|
67
|
+
| duration | No | 3.0 | Animation duration in seconds |
|
|
68
|
+
| fps | No | 12 | Frames per second (higher = smoother, larger file) |
|
|
69
|
+
| loop | No | true | Whether GIF loops continuously |
|
|
70
|
+
| style | No | clean-slate | Visual style preset |
|
|
71
|
+
| dimensions | No | 800x800 | Output dimensions in pixels |
|
|
72
|
+
| optimization | No | balanced | quality / balanced / filesize |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Output
|
|
77
|
+
|
|
78
|
+
| File | What it is |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `[slug]/animation.html` | Self-contained animated HTML (preview in browser) |
|
|
81
|
+
| `[slug]/animation.gif` | Final looping GIF, ready to use |
|
|
82
|
+
|
|
83
|
+
Typical file size: 150KB–1.5MB depending on animation type, duration, and optimization.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Dependencies
|
|
88
|
+
|
|
89
|
+
**Node.js** — required. Install from [nodejs.org](https://nodejs.org) or `brew install node`.
|
|
90
|
+
|
|
91
|
+
Everything else is bundled inside this skill or installed automatically on first run:
|
|
92
|
+
- `scripts/export-gif.sh` — orchestrator script
|
|
93
|
+
- `scripts/capture-and-encode.mjs` — Playwright frame capture + gifenc assembly
|
|
94
|
+
- `gifenc` + `sharp` (or `jimp`) + `playwright` — auto-installed via npm on first run
|
|
95
|
+
- **gifsicle** — optional but recommended for 35–50% smaller files: `brew install gifsicle`
|
|
96
|
+
|
|
97
|
+
**For AI-generated GIFs (optional):**
|
|
98
|
+
- Kling API key in environment: `KLING_API_KEY`
|
|
99
|
+
- `ffmpeg` for video→GIF conversion: `brew install ffmpeg`
|