Install
$ agentstack add skill-varnan-tech-opendirectory-graphic-gif ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
graphic-gif
Generates an animated looping GIF from CSS animations or an AI image-to-video API. Output: animation.gif.
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).
Critical Rules (read before every generation)
- Default is css-animated. Never use
ai-generatedunless explicitly requested. - Canvas is 800×800px square. All
clamp()values computed at 800px (1vw = 8px). - Single self-contained HTML. All CSS inline in `
. Font CDN` only external dependency. - Never dump HTML in chat. Save to file, show summary only.
- Frame capture uses Web Animations API seeking. NOT
setTimeoutloops, NOTanimation-delaytricks. - Exact frame count:
Math.floor(duration_seconds * fps)frames. The frame att=duration_msMUST NOT be captured — it duplicatest=0and causes a visible stutter at the loop point. - No placeholder boxes. CSS-generated visuals only. No "image goes here" elements.
- Simpler palettes = smaller files. Use:
clean-slate,terminal,electric-burst,brutalist. - No animation-delay for stagger. Bake stagger into
@keyframespercentages — frame seeking handles timing. - Commit to design direction before writing CSS. Tone, signature element, motion style, unforgettable detail — all decided before first line of code.
Step 1: Intake
Required: prompt (content description AND motion brief)
Optional with defaults:
| Parameter | Default | Options | |---|---|---| | animation_type | css-animated | css-animated / ai-generated | | duration | 3.0 | seconds | | fps | 12 | frames per second | | loop | true | true / false | | style | clean-slate | clean-slate / terminal / electric-burst / brutalist | | dimensions | 800x800 | WxH in pixels | | optimization | balanced | quality / balanced / filesize |
If prompt is missing or lacks motion description, ask exactly:
> "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.') > > Key settings (all optional, defaults shown): > - animation_type: css-animated (default) or ai-generated > - duration: 3.0 seconds > - fps: 12 > - loop: true > - style: clean-slate (options: clean-slate / terminal / electric-burst / brutalist) > - dimensions: 800x800 > - optimization: balanced (options: quality / balanced / filesize)"
If all required info is present → skip directly to Step 2.
Step 2: Internal Architecture (never shown to user)
For css-animated:
- Choose animation type from:
fade-in,slide-in,typewriter,counter,pulse,loop-scroll - Read
references/animation-library.md— find the chosen type's full HTML/CSS spec - Read
references/style-presets.md— load the chosen style's CSS token block - Calculate frame count:
Math.floor(duration_seconds * fps)— write this number down - Commit to design direction:
| Decision | Derive from | |---|---| | Tone | Emotional register for audience (mechanical / warm / electric / professional) | | Signature element | ONE visual device used consistently (cursor blink, ghost number, scan-line overlay, accent border) | | Motion style | Ease curve philosophy for this type (spring / linear / step / ease-in-out) | | Unforgettable detail | The ONE thing a viewer will remember about this GIF |
For ai-generated:
- Generate base still frame HTML (poster-style layout for the canvas)
- Export as PNG using screenshot
- Call Kling API:
POST https://api.klingai.com/v1/videos/image2videowithimage_urlandpromptdescribing the motion - Poll for job completion
- Download video → convert to GIF with ffmpeg:
``bash # Two-pass palette for best color quality ffmpeg -i input.mp4 -vf "fps=12,scale=800:800:flags=lanczos,palettegen=stats_mode=diff" palette.png ffmpeg -i input.mp4 -i palette.png -vf "fps=12,scale=800:800:flags=lanczos,paletteuse=dither=bayer:bayer_scale=5" output.gif ``
Step 3: HTML Generation (css-animated path)
Read references/animation-library.md and references/style-presets.md before generating.
Canvas base — required on every GIF:
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 800px;
height: 800px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
.canvas {
width: 800px;
height: 800px;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
Animation rules:
animation-fill-mode: forwards(orboth) on ALL animated elements- Timing functions per type:
typewriter→steps(N, end)where N = exact character countcounter→linearfade-in→cubic-bezier(0.22, 1, 0.36, 1)(ease-out)slide-in→cubic-bezier(0.34, 1.56, 0.64, 1)(spring overshoot)pulse→ease-in-outwithanimation-iteration-count: infiniteloop-scroll→linearwithanimation-iteration-count: infinite- For one-shot animations (fade-in, slide-in, typewriter, counter):
animation-iteration-count: 1— looping happens at GIF level - No
animation-delay— stagger is baked into@keyframespercentages
Typewriter N calculation: Count every character including spaces, punctuation, numbers:
- "73% of buyers" = 14 characters →
steps(14, end) - "Hello, World!" = 13 characters →
steps(13, end)
Counter CSS @property (required for counter type):
@property --num {
syntax: '';
inherits: false;
initial-value: 0;
}
.counter {
animation: countUp var(--duration) linear forwards;
counter-reset: num var(--num);
}
.counter::after { content: counter(num); }
@keyframes countUp {
from { --num: 0; }
to { --num: var(--target); }
}
Loop-scroll: content MUST be duplicated:
[item1][item2][item3][item4][item1][item2][item3][item4]
translateX(0 → -50%) with linear infinite.
Design quality rules (from commit in Step 2):
- Named signature element MUST be present in CSS/HTML (not just described)
- Typography: weight contrast minimum 2:1 (e.g., 700 vs 400) between display and supporting text
- Background: no pure white
#ffffor dark styles — use the preset's exact--bgvalue - For terminal style: add scan-line overlay
::afterwithrepeating-linear-gradientatopacity: 0.03 - For brutalist: thick border
4px solid #000or4px solid var(--accent)on key element - Unforgettable detail: if it requires an extra element — add it now
Step 4: Self-QA (fix every failure before Step 5)
Canvas:
- [ ]
bodyand.canvasexactly 800×800px (or specified dimensions) - [ ]
overflow: hiddenon bothbodyand.canvas - [ ] No elements overflowing the canvas boundary
Animations:
- [ ] NO
animation-delayanywhere — stagger is in@keyframespercentages - [ ] All animations start at
t=0(Web Animations API will seek from there) - [ ]
animation-fill-mode: forwardsorbothon all animated elements - [ ] One-shot animations:
animation-iteration-count: 1 - [ ] Infinite animations (pulse, loop-scroll):
animation-iteration-count: infinite
Type-specific checks:
- [ ] Typewriter: N in
steps(N, end)= exact character count of text string - [ ] Counter:
@property --numdeclared withsyntax: ''andinitial-value: 0 - [ ] Counter:
counter-reset: num var(--num)and::after { content: counter(num) } - [ ] Loop-scroll: content duplicated exactly once in HTML
Design:
- [ ] No placeholder boxes
- [ ] Style preset tokens applied from
references/style-presets.md— no free-floating hex colors - [ ] Signature element named in Step 2 is actually present in the HTML/CSS
- [ ] Unforgettable detail from Step 2 is actually implemented
- [ ] Font CDN `` present for chosen style's font
Step 5: Export
Determine slug from prompt (kebab-case, ≤30 chars). Create output directory:
mkdir -p [slug]
Save HTML:
[slug]/animation.html
Open in browser for quick visual check:
open [slug]/animation.html
Run export script (replace [skill-root] with the actual path to this skill):
bash [skill-root]/scripts/export-gif.sh \
[slug]/animation.html \
[slug]/animation.gif \
--duration [duration] \
--fps [fps] \
[--no-loop if loop=false] \
--optimization [optimization] \
--width [W] \
--height [H]
The script:
- Installs
gifenc,sharp(orjimp), andplaywrightin a temp directory - Downloads Chromium if not cached
- Runs
capture-and-encode.mjs— pauses animations, seeks each frame, screenshots, assembles GIF - Runs
gifsicleoptimization pass if available - Reports file size and opens result
If export script not found at [skill-root]/scripts/export-gif.sh, check that the skill was installed with its scripts/ folder intact.
Step 6: Output Summary
Show after successful export:
## GIF: [1-line description]
Date: [YYYY-MM-DD] | Style: [style] | Animation: [type] | [duration]s @ [fps]fps
Dimensions: [WxH] | Frames: [N] | Loop: [true/false]
Files
Source: [slug]/animation.html
Output: [slug]/animation.gif
Size: [X] KB
Checklist
- [ ] Preview loops cleanly at start/end point (no stutter)
- [ ] Text legible at intended display size
- [ ] File size appropriate: email {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: W, height: H } });
await page.goto('file://[slug]/animation.html');
await page.screenshot({ path: '[slug]/base-frame.png' });
await browser.close();
})();
"
```
3. Upload to Kling image-to-video endpoint
4. Convert result to GIF with ffmpeg two-pass palette
5. Apply gifsicle optimization
**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."
---
## Prompt Tips (show when user asks for guidance)
> "Describe motion, not just content. 'Stats count up one by one' beats 'show stats'."
>
> "Keep it simple for file size. 1–3 animated elements and a solid background."
>
> "Think in loops. The animation should flow invisibly from end back to start."
>
> "Specify the animation type explicitly. `typewriter` and `counter` are the most effective for social."
>
> ✅ 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."
>
> ❌ Bad: "make an animated gif of marketing tips"
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [Varnan-Tech](https://github.com/Varnan-Tech)
- **Source:** [Varnan-Tech/opendirectory](https://github.com/Varnan-Tech/opendirectory)
- **License:** MIT
- **Homepage:** https://www.opendirectory.dev
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.