{
  "$schema": "https://humangarden.ai/.well-known/skill-spec.schema.json",
  "canonical_url": "https://humangarden.ai/skills/slack-gif-creator/spec.json",
  "slug": "slack-gif-creator",
  "last_tested": "2026-06-05",
  "tested_by": "humangarden",
  "verdict_one_line": "A solid PIL-based GIF toolkit — produced a clean 63 KB looping Slack emoji in 1.6s, validator round-trips, but FPS is silently quantized to GIF-spec centiseconds (15 fps → 16.7) and the 480x480 message variant balloons past 1 MB easily.",
  "fires_when": [
    "user asks for an animated GIF sized for Slack (custom emoji or in-message)",
    "user wants a small looping animation written with PIL primitives (no video-encoder dependency)",
    "user wants reusable easing functions (elastic, bounce, back, cubic) and a frame builder with palette quantization",
    "agent workflows that emit reaction GIFs / status indicators / celebration overlays as part of a larger task"
  ],
  "skip_when": [
    "user wants photorealistic motion, video frames, or anything beyond geometric PIL primitives",
    "user uploads an image and expects automatic style transfer or AI generation — the skill loads images via PIL but provides no model",
    "user needs alpha-channel transparency (GIFs are saved RGB; transparent backgrounds not wired in this toolkit)",
    "user needs SVG / Lottie / APNG / WebM — the only output is .gif",
    "user expects pre-packaged emoji graphics or icon library — the SKILL.md is explicit that none ship"
  ],
  "inputs": [
    {
      "type": "prompt",
      "format": "free text",
      "constraint": "a brief animation idea — the skill expects Claude to translate it into PIL drawing code, not a fill-in-the-blanks form"
    },
    {
      "type": "file",
      "format": "png/jpg",
      "constraint": "optional — user-uploaded image loaded via PIL.Image.open; the skill provides no AI interpretation, the agent decides whether to use it directly or as inspiration"
    }
  ],
  "outputs": [
    {
      "type": "file",
      "format": "gif",
      "quality_note": "128x128 emoji at 12 frames / 15 fps / 48 colors → 63 KB looping (loop=0, infinite); 480x480 message at 30 frames / 20 fps / 96 colors → 1.37 MB (over the comfortable Slack inline threshold)"
    }
  ],
  "installation": {
    "pip": [
      "pillow>=10.0.0",
      "imageio>=2.31.0",
      "imageio-ffmpeg>=0.4.9",
      "numpy>=1.24.0"
    ],
    "npm": [],
    "system": {},
    "notes": "Ships a working requirements.txt (rare among the anthropic skills). Zero system deps; pure-Python via Pillow + imageio. Installed cleanly into a fresh venv in <10s on Python 3.11."
  },
  "artifacts": [
    {
      "kind": "image",
      "file": "https://humangarden.ai/spec-artifacts/slack-gif-creator/celebrating-star.gif",
      "caption": "Emoji-mode output: 128x128, 12 frames, 48 colors, infinite loop. Produced from a 480x480 24-frame render via optimize_for_emoji=True (auto-downsamples + frame-decimates). 62.7 KB on disk, generated in 1.60s. Validator: passes (optimal). Visible animation: elastic pulse + radial sparkle burst + 3-layer glow halo over a warming gradient sky.",
      "role": "output",
      "hero": true
    },
    {
      "kind": "image",
      "file": "https://humangarden.ai/spec-artifacts/slack-gif-creator/celebrating-star-large.gif",
      "caption": "Message-mode output: 480x480, 30 frames, 96 colors, infinite loop. 1366.5 KB — same composition rendered without emoji optimization. The skill itself prints a \"Large file size\" warning at save time, accurately. This is over the comfortable inline-Slack threshold (~1 MB), useful as a reference for why optimize_for_emoji exists.",
      "role": "output",
      "hero": true
    },
    {
      "kind": "image",
      "file": "https://humangarden.ai/spec-artifacts/slack-gif-creator/single-frame.gif",
      "caption": "Edge case: a single-frame \"GIF\" (a vertical red→blue gradient). The skill accepts this without warning and validators.validate_gif() returns passes=True, optimal=True. Technically a static image marketed as an emoji GIF — a downstream pipeline that conflates \"validates\" with \"animates\" will silently ship still images.",
      "role": "reference",
      "hero": false
    },
    {
      "kind": "image",
      "file": "https://humangarden.ai/spec-artifacts/slack-gif-creator/celebrating-star-poster.png",
      "caption": "First-frame PNG poster of the emoji GIF (post-quantization, post-resize). Useful as a static thumbnail; shows the layered glow + rust-orange star outline + inner highlight that the SKILL.md \"make graphics look good\" guidance pushes you toward.",
      "role": "reference",
      "hero": false
    },
    {
      "kind": "text",
      "inline": "Skill structure (what actually ships):\n  SKILL.md            ~255 lines of knowledge + utilities reference\n  requirements.txt    pillow / imageio / imageio-ffmpeg / numpy\n  core/gif_builder.py     270 lines — GIFBuilder, palette quantization, dedup, emoji optimization\n  core/easing.py          235 lines — 14 easing functions + interpolate() + arc motion\n  core/frame_composer.py  175 lines — gradient bg, draw_circle, draw_text, draw_star\n  core/validators.py      135 lines — validate_gif, is_slack_ready\n  LICENSE.txt\n\nOur run (representative prompt → 3 GIF artifacts):\n  celebrating-star.gif       62.7 KB  128x128  12 frames @ 15 fps  (0.8 s, loop=0)\n  celebrating-star-large.gif 1366.5 KB  480x480  30 frames @ 20 fps  (1.5 s, loop=0)\n  single-frame.gif            1.3 KB  128x128   1 frame\n  wall time: 1.60 s emoji + 0.43 s message = ~2 s total\n\nSkill's own validators (validate_gif on each):\n  celebrating-star.gif       passes=True, optimal=True (128x128)\n  celebrating-star-large.gif passes=True (480x480, 1.33 MB — printed \"Large file size\" hint)\n  single-frame.gif           passes=True, optimal=True\n\nEdge case probed:\n  empty builder.save() → raises ValueError(\"No frames to save…\") — correct.\n  single-frame \"GIF\" → accepted silently, validates as Slack-ready emoji.\n",
      "caption": "Structural summary — what the skill ships and what one full run produced.",
      "role": "reference",
      "hero": false
    }
  ],
  "caveats": [
    "FPS is silently quantized to GIF-spec centiseconds. Requesting fps=15 gives a frame duration of 1000/15 ≈ 66.67 ms, which imageio rounds to 60 ms — the resulting GIF plays at 16.7 fps, not 15. The validator reports the real 16.7 fps, but GIFBuilder.save() prints \"@ 15 fps\" in its success message, so the on-disk file and the printed summary disagree. Anything outside {10, 12.5, 16.7, 20, 25, 33.3, 50, 100} fps will drift.",
    "Single-frame \"GIFs\" pass validation. is_slack_ready() returns True for a one-frame file with no warning that there is no animation; downstream pipelines that trust the validator will ship still images marketed as animated GIFs.",
    "optimize_for_emoji=True silently rewrites the builder in-place — it mutates self.width / self.height to 128 and decimates self.frames from N to ~12. Reusing the same builder after save() (or calling save() twice with and without the flag) will produce a different second GIF than expected unless you call builder.clear() and re-add frames.",
    "No alpha channel in output. All frames are forced to RGB at add_frame() time, so RGBA sparkles / transparent backgrounds become opaque against whatever solid color you composited onto. For Slack emoji which Slack itself displays on a light or dark theme, this means you must commit to a background that works on both — the skill does not help with this.",
    "GIFBuilder.save() prints to stdout via bare print() calls (including a Unicode \"✓\" success glyph). Agent harnesses that capture stdout get noisy success messages; piping to a non-UTF8 sink would crash on the checkmark. Easy to wrap, but worth knowing before embedding in a CLI.",
    "No font shipped for draw_text — it falls back to ImageFont.load_default(), which is the tiny built-in PIL bitmap font (~6×11 px). Useful for nothing on a 128×128 emoji. The SKILL.md notes \"If the font should be changed for the emoji, add additional logic here\" — meaning text on emoji is your problem, not the skill's.",
    "480x480 message GIFs balloon past 1 MB at 30 frames / 96 colors (we measured 1.37 MB). The skill itself prints a \"Large file size\" warning but does not auto-degrade — Claude has to choose lower num_colors / fewer frames / smaller dimensions. The optimization advice in the SKILL.md is correct; the defaults are not aggressive enough."
  ],
  "needs_credentials": [],
  "source_repo": "https://github.com/anthropics/skills/tree/main/skills/slack-gif-creator",
  "human_review_url": "https://humangarden.ai/skills/slack-gif-creator/"
}