@opendirectory.dev/skills 0.1.58 → 0.1.59

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendirectory.dev/skills",
3
- "version": "0.1.58",
3
+ "version": "0.1.59",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "bin": {
package/registry.json CHANGED
@@ -134,6 +134,16 @@
134
134
  "version": "1.0.0",
135
135
  "path": "skills/graphic-ebook"
136
136
  },
137
+ {
138
+ "name": "graphic-gif",
139
+ "description": "Creates animated looping GIFs from CSS animations (default) or AI image-to-video.",
140
+ "tags": [
141
+ "AI"
142
+ ],
143
+ "author": "OpenDirectory",
144
+ "version": "1.0.0",
145
+ "path": "skills/graphic-gif"
146
+ },
137
147
  {
138
148
  "name": "graphic-slide-deck",
139
149
  "description": "Generates a professionally designed HTML slide deck from a brief or content.",
@@ -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`
@@ -0,0 +1,313 @@
1
+ ---
2
+ name: graphic-gif
3
+ description: Creates animated looping GIFs from CSS animations (default) or AI image-to-video. 800×800px default, 6 animation types, 4 style presets. Trigger when user says "create an animated gif", "make a looping gif", "animated banner", "CSS animation gif", "social media animation", "make this loop", "animated graphic", or "motion graphic".
4
+ compatibility: [claude-code, gemini-cli, github-copilot]
5
+ author: OpenDirectory
6
+ version: 1.0.0
7
+ ---
8
+
9
+ # graphic-gif
10
+
11
+ Generates an animated looping GIF from CSS animations or an AI image-to-video API. Output: `animation.gif`.
12
+
13
+ Unlike every other `graphic-` skill that outputs a static PNG or PDF, this skill outputs an animated `.gif`. Uses CSS `@keyframes` animations captured frame-by-frame via Playwright and the Web Animations API (Option A, default), or an AI image-to-video pipeline via Kling (Option B).
14
+
15
+ ---
16
+
17
+ ## Critical Rules (read before every generation)
18
+
19
+ 1. **Default is css-animated.** Never use `ai-generated` unless explicitly requested.
20
+ 2. **Canvas is 800×800px square.** All `clamp()` values computed at 800px (1vw = 8px).
21
+ 3. **Single self-contained HTML.** All CSS inline in `<style>`. Font CDN `<link>` only external dependency.
22
+ 4. **Never dump HTML in chat.** Save to file, show summary only.
23
+ 5. **Frame capture uses Web Animations API seeking.** NOT `setTimeout` loops, NOT `animation-delay` tricks.
24
+ 6. **Exact frame count:** `Math.floor(duration_seconds * fps)` frames. The frame at `t=duration_ms` MUST NOT be captured — it duplicates `t=0` and causes a visible stutter at the loop point.
25
+ 7. **No placeholder boxes.** CSS-generated visuals only. No "image goes here" elements.
26
+ 8. **Simpler palettes = smaller files.** Use: `clean-slate`, `terminal`, `electric-burst`, `brutalist`.
27
+ 9. **No animation-delay for stagger.** Bake stagger into `@keyframes` percentages — frame seeking handles timing.
28
+ 10. **Commit to design direction before writing CSS.** Tone, signature element, motion style, unforgettable detail — all decided before first line of code.
29
+
30
+ ---
31
+
32
+ ## Step 1: Intake
33
+
34
+ **Required:** `prompt` (content description AND motion brief)
35
+
36
+ **Optional with defaults:**
37
+
38
+ | Parameter | Default | Options |
39
+ |---|---|---|
40
+ | animation_type | css-animated | css-animated / ai-generated |
41
+ | duration | 3.0 | seconds |
42
+ | fps | 12 | frames per second |
43
+ | loop | true | true / false |
44
+ | style | clean-slate | clean-slate / terminal / electric-burst / brutalist |
45
+ | dimensions | 800x800 | WxH in pixels |
46
+ | optimization | balanced | quality / balanced / filesize |
47
+
48
+ **If prompt is missing or lacks motion description, ask exactly:**
49
+
50
+ > "What should the GIF show? Describe the content AND the motion (e.g., 'Stats count up: 73% of buyers read 3+ pieces of content before purchase. Typewriter effect, one character at a time. Style: terminal. 3 seconds, 12fps.')
51
+ >
52
+ > Key settings (all optional, defaults shown):
53
+ > - animation_type: css-animated (default) or ai-generated
54
+ > - duration: 3.0 seconds
55
+ > - fps: 12
56
+ > - loop: true
57
+ > - style: clean-slate (options: clean-slate / terminal / electric-burst / brutalist)
58
+ > - dimensions: 800x800
59
+ > - optimization: balanced (options: quality / balanced / filesize)"
60
+
61
+ If all required info is present → skip directly to Step 2.
62
+
63
+ ---
64
+
65
+ ## Step 2: Internal Architecture (never shown to user)
66
+
67
+ **For css-animated:**
68
+
69
+ 1. Choose animation type from: `fade-in`, `slide-in`, `typewriter`, `counter`, `pulse`, `loop-scroll`
70
+ 2. Read `references/animation-library.md` — find the chosen type's full HTML/CSS spec
71
+ 3. Read `references/style-presets.md` — load the chosen style's CSS token block
72
+ 4. Calculate frame count: `Math.floor(duration_seconds * fps)` — write this number down
73
+ 5. Commit to design direction:
74
+
75
+ | Decision | Derive from |
76
+ |---|---|
77
+ | Tone | Emotional register for audience (mechanical / warm / electric / professional) |
78
+ | Signature element | ONE visual device used consistently (cursor blink, ghost number, scan-line overlay, accent border) |
79
+ | Motion style | Ease curve philosophy for this type (spring / linear / step / ease-in-out) |
80
+ | Unforgettable detail | The ONE thing a viewer will remember about this GIF |
81
+
82
+ **For ai-generated:**
83
+ 1. Generate base still frame HTML (poster-style layout for the canvas)
84
+ 2. Export as PNG using screenshot
85
+ 3. Call Kling API: `POST https://api.klingai.com/v1/videos/image2video` with `image_url` and `prompt` describing the motion
86
+ 4. Poll for job completion
87
+ 5. Download video → convert to GIF with ffmpeg:
88
+ ```bash
89
+ # Two-pass palette for best color quality
90
+ ffmpeg -i input.mp4 -vf "fps=12,scale=800:800:flags=lanczos,palettegen=stats_mode=diff" palette.png
91
+ ffmpeg -i input.mp4 -i palette.png -vf "fps=12,scale=800:800:flags=lanczos,paletteuse=dither=bayer:bayer_scale=5" output.gif
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Step 3: HTML Generation (css-animated path)
97
+
98
+ Read `references/animation-library.md` and `references/style-presets.md` before generating.
99
+
100
+ **Canvas base — required on every GIF:**
101
+ ```css
102
+ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
103
+
104
+ body {
105
+ width: 800px;
106
+ height: 800px;
107
+ overflow: hidden;
108
+ background: var(--bg);
109
+ font-family: var(--font-body);
110
+ }
111
+
112
+ .canvas {
113
+ width: 800px;
114
+ height: 800px;
115
+ position: relative;
116
+ overflow: hidden;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ }
121
+ ```
122
+
123
+ **Animation rules:**
124
+ - `animation-fill-mode: forwards` (or `both`) on ALL animated elements
125
+ - Timing functions per type:
126
+ - `typewriter` → `steps(N, end)` where N = exact character count
127
+ - `counter` → `linear`
128
+ - `fade-in` → `cubic-bezier(0.22, 1, 0.36, 1)` (ease-out)
129
+ - `slide-in` → `cubic-bezier(0.34, 1.56, 0.64, 1)` (spring overshoot)
130
+ - `pulse` → `ease-in-out` with `animation-iteration-count: infinite`
131
+ - `loop-scroll` → `linear` with `animation-iteration-count: infinite`
132
+ - For one-shot animations (fade-in, slide-in, typewriter, counter): `animation-iteration-count: 1` — looping happens at GIF level
133
+ - **No `animation-delay`** — stagger is baked into `@keyframes` percentages
134
+
135
+ **Typewriter N calculation:**
136
+ Count every character including spaces, punctuation, numbers:
137
+ - "73% of buyers" = 14 characters → `steps(14, end)`
138
+ - "Hello, World!" = 13 characters → `steps(13, end)`
139
+
140
+ **Counter CSS `@property` (required for counter type):**
141
+ ```css
142
+ @property --num {
143
+ syntax: '<integer>';
144
+ inherits: false;
145
+ initial-value: 0;
146
+ }
147
+ .counter {
148
+ animation: countUp var(--duration) linear forwards;
149
+ counter-reset: num var(--num);
150
+ }
151
+ .counter::after { content: counter(num); }
152
+ @keyframes countUp {
153
+ from { --num: 0; }
154
+ to { --num: var(--target); }
155
+ }
156
+ ```
157
+
158
+ **Loop-scroll: content MUST be duplicated:**
159
+ ```html
160
+ <!-- 4 original items + 4 duplicate items -->
161
+ <div class="ticker-track" style="--item-count: 4;">
162
+ [item1][item2][item3][item4][item1][item2][item3][item4]
163
+ </div>
164
+ ```
165
+ `translateX(0 → -50%)` with `linear infinite`.
166
+
167
+ **Design quality rules (from commit in Step 2):**
168
+ - Named signature element MUST be present in CSS/HTML (not just described)
169
+ - Typography: weight contrast minimum 2:1 (e.g., 700 vs 400) between display and supporting text
170
+ - Background: no pure white `#fff` for dark styles — use the preset's exact `--bg` value
171
+ - For terminal style: add scan-line overlay `::after` with `repeating-linear-gradient` at `opacity: 0.03`
172
+ - For brutalist: thick border `4px solid #000` or `4px solid var(--accent)` on key element
173
+ - Unforgettable detail: if it requires an extra element — add it now
174
+
175
+ ---
176
+
177
+ ## Step 4: Self-QA (fix every failure before Step 5)
178
+
179
+ **Canvas:**
180
+ - [ ] `body` and `.canvas` exactly 800×800px (or specified dimensions)
181
+ - [ ] `overflow: hidden` on both `body` and `.canvas`
182
+ - [ ] No elements overflowing the canvas boundary
183
+
184
+ **Animations:**
185
+ - [ ] NO `animation-delay` anywhere — stagger is in `@keyframes` percentages
186
+ - [ ] All animations start at `t=0` (Web Animations API will seek from there)
187
+ - [ ] `animation-fill-mode: forwards` or `both` on all animated elements
188
+ - [ ] One-shot animations: `animation-iteration-count: 1`
189
+ - [ ] Infinite animations (pulse, loop-scroll): `animation-iteration-count: infinite`
190
+
191
+ **Type-specific checks:**
192
+ - [ ] Typewriter: N in `steps(N, end)` = exact character count of text string
193
+ - [ ] Counter: `@property --num` declared with `syntax: '<integer>'` and `initial-value: 0`
194
+ - [ ] Counter: `counter-reset: num var(--num)` and `::after { content: counter(num) }`
195
+ - [ ] Loop-scroll: content duplicated exactly once in HTML
196
+
197
+ **Design:**
198
+ - [ ] No placeholder boxes
199
+ - [ ] Style preset tokens applied from `references/style-presets.md` — no free-floating hex colors
200
+ - [ ] Signature element named in Step 2 is actually present in the HTML/CSS
201
+ - [ ] Unforgettable detail from Step 2 is actually implemented
202
+ - [ ] Font CDN `<link>` present for chosen style's font
203
+
204
+ ---
205
+
206
+ ## Step 5: Export
207
+
208
+ **Determine slug from prompt** (kebab-case, ≤30 chars). Create output directory:
209
+ ```bash
210
+ mkdir -p [slug]
211
+ ```
212
+
213
+ Save HTML:
214
+ ```
215
+ [slug]/animation.html
216
+ ```
217
+
218
+ Open in browser for quick visual check:
219
+ ```bash
220
+ open [slug]/animation.html
221
+ ```
222
+
223
+ Run export script (replace `[skill-root]` with the actual path to this skill):
224
+ ```bash
225
+ bash [skill-root]/scripts/export-gif.sh \
226
+ [slug]/animation.html \
227
+ [slug]/animation.gif \
228
+ --duration [duration] \
229
+ --fps [fps] \
230
+ [--no-loop if loop=false] \
231
+ --optimization [optimization] \
232
+ --width [W] \
233
+ --height [H]
234
+ ```
235
+
236
+ The script:
237
+ 1. Installs `gifenc`, `sharp` (or `jimp`), and `playwright` in a temp directory
238
+ 2. Downloads Chromium if not cached
239
+ 3. Runs `capture-and-encode.mjs` — pauses animations, seeks each frame, screenshots, assembles GIF
240
+ 4. Runs `gifsicle` optimization pass if available
241
+ 5. Reports file size and opens result
242
+
243
+ **If export script not found** at `[skill-root]/scripts/export-gif.sh`, check that the skill was installed with its `scripts/` folder intact.
244
+
245
+ ---
246
+
247
+ ## Step 6: Output Summary
248
+
249
+ Show after successful export:
250
+
251
+ ```
252
+ ## GIF: [1-line description]
253
+ Date: [YYYY-MM-DD] | Style: [style] | Animation: [type] | [duration]s @ [fps]fps
254
+ Dimensions: [WxH] | Frames: [N] | Loop: [true/false]
255
+
256
+ Files
257
+ Source: [slug]/animation.html
258
+ Output: [slug]/animation.gif
259
+ Size: [X] KB
260
+
261
+ Checklist
262
+ - [ ] Preview loops cleanly at start/end point (no stutter)
263
+ - [ ] Text legible at intended display size
264
+ - [ ] File size appropriate: email <500KB / social <3MB
265
+ ```
266
+
267
+ ---
268
+
269
+ ## AI-Generated Path (Option B)
270
+
271
+ Only use when `animation_type: ai-generated` is explicitly specified.
272
+
273
+ **Requirements:**
274
+ - Kling API key in environment: `KLING_API_KEY` (66 free credits/day, no credit card for free tier)
275
+ - `ffmpeg` installed locally for video→GIF conversion
276
+
277
+ **Workflow:**
278
+ 1. Generate a base still frame HTML matching the prompt (poster/graphic style at specified dimensions)
279
+ 2. Export still frame as PNG:
280
+ ```bash
281
+ # Quick screenshot via Playwright
282
+ node -e "
283
+ const { chromium } = require('playwright');
284
+ (async () => {
285
+ const browser = await chromium.launch();
286
+ const page = await browser.newPage({ viewport: { width: W, height: H } });
287
+ await page.goto('file://[slug]/animation.html');
288
+ await page.screenshot({ path: '[slug]/base-frame.png' });
289
+ await browser.close();
290
+ })();
291
+ "
292
+ ```
293
+ 3. Upload to Kling image-to-video endpoint
294
+ 4. Convert result to GIF with ffmpeg two-pass palette
295
+ 5. Apply gifsicle optimization
296
+
297
+ **When Kling is unavailable:** Fall back to css-animated with a note to the user: "AI generation requires a Kling API key (KLING_API_KEY). Falling back to css-animated. Set the key to enable AI generation."
298
+
299
+ ---
300
+
301
+ ## Prompt Tips (show when user asks for guidance)
302
+
303
+ > "Describe motion, not just content. 'Stats count up one by one' beats 'show stats'."
304
+ >
305
+ > "Keep it simple for file size. 1–3 animated elements and a solid background."
306
+ >
307
+ > "Think in loops. The animation should flow invisibly from end back to start."
308
+ >
309
+ > "Specify the animation type explicitly. `typewriter` and `counter` are the most effective for social."
310
+ >
311
+ > ✅ Good: "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."
312
+ >
313
+ > ❌ Bad: "make an animated gif of marketing tips"
@@ -0,0 +1,30 @@
1
+ {
2
+ "skill_name": "graphic-gif",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "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.",
7
+ "expected_output": "Reads terminal preset (JetBrains Mono, #00FF41 accent, #0D1117 bg). Steps(N) set to exact character count of the text string. animation-play-state: paused injected before frame capture. Frame count = floor(3.0 * 12) = 36. Web Animations API seeks to 0ms, 83.3ms, 166.6ms... per frame. gifenc assembles 36 frames at 83ms delay. repeat=0 for infinite loop. gifsicle -O3 --lossy=80 --colors 128 optimization pass. Output: [slug]/animation.gif, ~300-800KB. Summary shown with checklist."
8
+ },
9
+ {
10
+ "id": 2,
11
+ "prompt": "Animated GIF with counter animation. Numbers count from 0 to 500. Show label 'monthly active users'. Style: electric-burst. 2 seconds, 15fps.",
12
+ "expected_output": "@property --num declared with syntax: '<integer>' and initial-value: 0. counter-reset: num var(--num) on counter element. ::after { content: counter(num) }. @keyframes countUp from --num: 0 to --num: 500. Frame count = floor(2.0 * 15) = 30. Seeks 0ms, 66.7ms... up to ~1933ms (NOT 2000ms — excludes endpoint). electric-burst tokens applied (Space Grotesk, #FACC15 accent). Output: animation.gif."
13
+ },
14
+ {
15
+ "id": 3,
16
+ "prompt": "Make a pulsing GIF for a CTA button. Text: 'Book a demo →'. Pulse/breathing effect. Style: clean-slate. 2 seconds, 12fps.",
17
+ "expected_output": "pulse @keyframes with scale(1) at 0%/100% and scale(1.06) at 50%. animation-iteration-count: infinite on .pulse-item. Frame count = floor(2.0 * 12) = 24. Seeks 0ms, 83.3ms... up to ~1916ms (NOT 2000ms). clean-slate tokens (Plus Jakarta Sans, #0F172A accent, white bg). repeat=0 for infinite loop. Output: animation.gif."
18
+ },
19
+ {
20
+ "id": 4,
21
+ "prompt": "Create a loop-scroll ticker GIF. Items: 'Reduce Churn · Increase NRR · Boost LTV · Drive Expansion'. Infinite scroll from right to left. Style: brutalist. 4 seconds, 10fps.",
22
+ "expected_output": "Content duplicated exactly: [4 items] [4 items] = 8 total items in HTML. translateX(0) to translateX(-50%) with linear timing and infinite iteration. Frame count = floor(4.0 * 10) = 40. Seeks 0ms, 100ms, 200ms... up to 3900ms (NOT 4000ms). brutalist tokens (Space Mono, #FF0000 accent, white bg). gifsicle optimization. Output: animation.gif."
23
+ },
24
+ {
25
+ "id": 5,
26
+ "prompt": "make a gif",
27
+ "expected_output": "prompt field is missing motion description. Does NOT read any reference files. Does NOT generate any HTML. Asks exactly: 'What should the GIF show? Describe the content AND the motion (e.g., Stats count up: 73% of buyers...) Key settings (all optional, defaults shown): animation_type, duration, fps, loop, style, dimensions, optimization.' Stops and waits for user response."
28
+ }
29
+ ]
30
+ }