# SkillPatch skill: meme-generation

Generate downloadable, watermark-free memes with a built-in output filter (legibility + funniness heuristics) and a joke-shape rubric for picking the right template. Use this skill when users request memes, want to add humor to content, or need visual aids for social media. Works cross-platform (bundled font, no OS dependency). Supports a curated, API-verified set of meme templates with custom text.

You (the agent) were given this URL and asked to use this skill. This is a **public** skill — no SkillPatch account, API key, or prior setup is required. Two ways to use it:

**1. Use it right now** — the skill's complete file tree (SKILL.md + all reference files) is inlined below; read `SKILL.md` first, then follow it, consulting the other files as it directs.

**2. Install the exact package onto disk** (recommended if you can run a shell — this reproduces the skill byte-for-byte, including any binary assets that can't be inlined):

```bash
mkdir -p .claude/skills/meme-generation
curl -sSL https://skillpatch.dev/install_skill/meme-generation | tar -xz -C .claude/skills/
```

(`.claude/skills/` is Claude Code's convention; use whatever directory your agent loads skills from.)


---

## Skill files (7)

- `meme-generation/SKILL.md`
- `meme-generation/assets/Anton-OFL.txt`
- `meme-generation/assets/Anton-Regular.ttf`
- `meme-generation/complete-markdown-memes-guide.md`
- `meme-generation/examples.md`
- `meme-generation/meme_generator.py`
- `meme-generation/README.md`


### `meme-generation/SKILL.md`

````markdown
---
name: meme-generation
description: Generate downloadable, watermark-free memes with a built-in output filter (legibility + funniness heuristics) and a joke-shape rubric for picking the right template. Use this skill when users request memes, want to add humor to content, or need visual aids for social media. Works cross-platform (bundled font, no OS dependency). Supports a curated, API-verified set of meme templates with custom text.
---

# Meme Generation

This skill renders memes locally as real files — no memegen.link watermark, no dependency on the caller's OS having a particular font installed, and a built-in sanity check that refuses to save a render it knows is broken (see "Change My Mind" in the Output Filter section below for why that check exists).

## Quick Reference

**For comprehensive meme creation guidance:**
- See [Complete Markdown Memes Guide](complete-markdown-memes-guide.md) for **15+ textual meme formats** (greentext, copypasta, ASCII art, chat logs, Reddit AITA, Tumblr chains, wojak dialogues, etc.) AND image meme techniques
- This file focuses on the **memegen.link API** for image meme generation

## Templates Not In memegen's Catalog (e.g. Surprised Pikachu)

memegen.link's catalog doesn't include everything — notably no Pokémon
characters (Surprised Pikachu isn't there), and other licensed-character
templates get pulled over time too. **Do not try to work around this by
bundling a copyrighted character image into this skill's `assets/` folder.**
The bundled font (`assets/Anton-Regular.ttf`) is safe to redistribute because
it's OFL-licensed; a screenshot of a Pokémon anime frame is not — this skill
gets distributed to strangers via SkillPatch, and shipping copyrighted
character art inside that package is a real infringement risk, not a
technicality.

Instead, use `render-image` — the same rendering pipeline (auto-fit caption,
legibility/busyness checks, watermark), pointed at an image the *user*
supplies locally each time, never bundled with the skill:

```bash
python meme_generator.py render-image path/to/your-pikachu.jpg "when the tests pass locally" "and fail in ci"
```

This is the answer whenever someone wants a meme template that isn't in
`TEMPLATES` — check memegen's catalog first (`list-templates` or
`api.memegen.link/templates/`), and if it's genuinely not there, ask the user
for their own local copy of the image rather than sourcing one yourself.

## Overview

The memegen.link API allows you to:
- Generate memes using 100+ popular templates
- Add custom top and bottom text
- Use custom images as backgrounds
- Control dimensions, fonts, and styles
- Create animated memes with GIF/WebP support

## Downloadable, Watermark-Free Memes (preferred)

The public memegen.link API stamps a "Memegen.link" watermark onto any image
where it renders the text itself. The **blank template** (no text) is
watermark-free, so `meme_generator.py render` fetches the blank background
and draws the Impact-style caption locally with Pillow — no memegen
watermark, and you get an actual file on disk instead of a link the user has
to click.

Instead, `render` stamps a small **"skillpatch.dev"** text mark (no logo) into
the bottom-right corner at reduced opacity (55%), so it brands the output
without covering the art or competing with the captions. Pass
`--no-watermark` (or `watermark=False` from Python) to skip it.

## Output Filter (legibility + funniness heuristics)

`render` (and `render_local()` in Python) also runs every meme through two
automatic checks and returns/prints the results:

- **Legibility** — a real score derived from what happened during rendering:
  whether a caption had to shrink all the way to the font-size floor, whether
  it wrapped past 3 lines, and how visually busy (edge-dense) the background
  is directly behind the text. Scored 0–100; flagged issues explain what to
  shorten or reword.
- **Funniness** — an explicitly *heuristic* estimate (code cannot actually
  judge humor): rewards concise captions (2–8 words), low word-overlap
  between top/bottom (juxtaposition), and hyperbole/absurdity words; penalizes
  overlong or repetitive lines. Treat the score and notes as a sanity check,
  not a verdict — read the meme out loud to be sure.

```bash
python meme_generator.py render fine "one more subagent" "context at 4M tokens"
# memes/fine_one_more_subagent_context_at_4m_tokens.png
# legibility: 100/100 (OK)
# funniness (heuristic): 70/100
#   - good contrast between top and bottom captions
```

## Pick the Template by Joke Shape, Not Vibe

A meme reads as "confusing" far more often because the **template shape
doesn't match the joke** than because of anything wrong with the image
itself. `MemeGenerator.TEMPLATES` (in `meme_generator.py`) is a curated,
API-verified list — every entry was checked against the live
`api.memegen.link/templates/` endpoint (several of the old IDs here had
quietly rotted: `changemind`→`cmm`, `distracted`→`db`, `ancient`→`aag`,
`skeptical`→`sk`; `surprised`/`thinking`/`boardroom` don't exist at all
anymore and were dropped).

Each entry carries:
- **`layout`** — `"top_bottom"` (safe for `render_local()`) vs `"multi_slot"`
  (needs 3–4 positioned captions on specific parts of the art — Distracted
  Boyfriend, Galaxy Brain, Epic Handshake — `render_local()` will raise
  rather than silently mis-place text; use `generate(template, captions=[...])`
  instead, one string per slot, and embed the resulting URL).
- **`joke_type`** — the actual shape the caption needs to have:
  - `contrast` (Drake, Awesome Penguin, Interesting Man) — top and bottom
    must be genuinely opposite in substance, not just reworded.
  - `mock_repeat` (Spongebob) — the TOP line alone must already sound
    dubious/hollow; the bottom just echoes it in mocking case. Writing a new
    joke for the bottom, or picking this template for a claim that isn't
    inherently eye-roll-worthy, is what produced the confusing meme earlier
    in this project ("the agent said it fixed the bug" mocked itself with no
    reason to doubt the claim in the first place).
  - `irony` (This is Fine), `reveal` (Disaster Girl, Always Has Been),
    `escalation` (Buzz, Galaxy Brain), `single_line` (Change My Mind), etc.
- **`guidance`** — a one-line rubric to follow when writing the caption for
  that specific template.

`python meme_generator.py list-templates` prints the full table. `render`
prints the matched template's guidance line before generating, and
`_score_funniness` is shape-aware (e.g. it won't penalize `mock_repeat`
templates for the repetition that's supposed to be there).

**Before writing captions, draft 2–3 candidate punchlines and pick the
sharpest — don't one-shot it.** The output filter catches structural
problems (wrapping, low contrast) but has no way to tell if a joke is actually
funny; that judgment call is on whoever's writing the caption. No amount of
heuristic scoring substitutes for this — see the humor theory below and
actually apply it before generating.

```bash
python meme_generator.py render fine "training yolo on a laptop gpu" "this is fine"
# -> memes/fine_training_yolo_on_a_l_this_is_fine.png
```

## Why Things Are Funny (apply this before picking a template)

Code cannot judge humor, but it *can* be told what actually produces it, so
the caption-writing step should reason from these first principles instead of
reaching for whatever template feels most "meme-shaped" (Drake, mostly —
which is exactly why it gets overused: it's the template that requires the
least thought). Four real theories, translated into meme-writing moves:

1. **Benign Violation** (the strongest predictor of "funny" across humor
   research) — a joke lands when something is simultaneously *wrong* (a
   violation: risk, failure, absurdity, taboo) **and** *safe* (benign: no real
   harm, low stakes, recognizable). Too safe = boring observation. Too much
   violation = just uncomfortable. The best AI-agent jokes exploit this
   directly: "the agent deleted prod" is a violation (scary) that's benign
   *because it's a joke, not your actual prod*. Tune the severity of the
   violation to the audience.
2. **Incongruity + Resolution** — humor comes from setting up one expectation
   and resolving it with a logically-connected but unexpected turn. Pure
   randomness isn't funny (no logical connection); pure predictability isn't
   funny (no surprise). This is what `contrast` and `reveal` joke_types are
   built on — the punchline must still *make sense* in hindsight.
3. **Specificity** — concrete, sensory, or numerically specific details beat
   generic ones almost every time. "The agent installs 4 dependencies and
   refactors the entire auth system" is funnier than "the agent does too
   much" because it's a specific, visualizable overreach, not an abstract
   complaint. When drafting, always ask: can I replace a vague noun/verb with
   the exact specific thing that actually happens?
4. **Shared Pain / Recognition** — the fastest route to a laugh from a
   specific audience (here: engineers working with AI agents) is naming a
   frustration they've *personally* lived through, worded precisely enough
   that they think "oh no, that's literally me." Generic AI tropes ("AI is
   taking over") don't have this; "asked it to fix one typo, it rewrote the
   auth system" does.

**Process**: pick the real, specific pain point first (not the template).
Draft 2–3 candidate captions applying different theories above. Judge them
against benign-violation balance and specificity. *Then* pick whichever
`joke_type` in `TEMPLATES` matches the shape of the winning draft — don't
force the draft into whatever template you picked first.

## Learning From Reference Memes

When the user hands you memes they think are funnier than what you've been
producing, **don't copy the template or the topic — reverse-engineer the
specific mechanism that makes them land, then apply that mechanism to a new,
equally specific detail.** Copying the surface (same template, vaguely
similar subject) produces something that *looks* like the reference but isn't
actually funnier, because the thing doing the work was never the template.

Worked example from this project: given a reference meme captioned "We used
to go to a special website, ask strangers for help with programming, and get
humiliated in return" over an old-man-reminiscing image, the temptation is to
copy the "nostalgic old-man" framing. That's not what makes it funny. What
makes it funny is that it's **hyper-specific to one real, shared, slightly
embarrassing developer memory** (Stack Overflow closing your question as a
duplicate instead of answering it) stated as flat, unemotional fact — the
humor is entirely load-bearing on the specificity, not the framing device.

The reusable move: extract the *specific detail* being exploited (not the
topic, not the template), find an equally specific true detail for whatever
you're actually writing about, and pick whichever `joke_type` matches that
detail's shape. That's how "Stack Overflow was just public humiliation" (the
reference's mechanism) became "wait, Stack Overflow was just public
humiliation / always has been" (a `reveal` shape applied to the same detail,
for a different meme) — the template changed, the underlying specificity
technique didn't.

**When given reference memes**: name out loud (in your response, not just
internally) what specific real detail each one is exploiting before drafting
anything. If you can't name a specific detail — only a vague topic or
template — you don't have a joke yet, you have a subject.

## Dark-Humor "AI Jailbreak" Format (default option for AI-related meme requests)

This lands consistently well and should be one of the first angles considered
whenever a meme is about AI/LLMs specifically — not just a fallback, an
actual default to weigh alongside the other joke_types above. The mechanic:

**"Me: [an oddly specific, transparently thin cover story that implies
something dark/illegal without ever stating it]" / [the AI's genuinely
alarmed reaction]**

Why it works, mapped to the theory above:
- **Specificity carries the whole joke.** The cover story must be precise
  enough that the reader decodes the real implication themselves — "how do I
  carry a 67kg dead chicken on a train with zero smell" reads as "how do I
  transport a body" without ever saying so. A vague version ("something
  illegal, wink wink") isn't funny; the exact weight and the specific
  "without the smell" detail is what makes it click.
- **Benign violation at full strength.** The violation (implied dark content)
  is real and it's the whole point — but it stays benign because it's
  obviously fictional, nobody is actually asking this, and the AI's
  reaction confirms it's a joke rather than an instruction being carried out.
- **Recognition** — "for a novel I'm writing," "purely hypothetically,"
  thin-disguise phrasing is something anyone who's used an AI model
  recognizes immediately, which is what makes the setup land before the
  punchline even arrives.

**Template**: use `regret` (Ron Burgundy, "I Immediately Regret This
Decision!") for the AI's reaction — it's a genuine alarmed/scared face, not
an ironic one, which is what this format needs. Per its guidance, don't
write a punchline for the bottom caption — just label who's reacting (e.g.
"Claude reading the question") and let the face carry the emotion. Keep the
top caption to 2 clean lines; if it's wrapping to 3+ or crowding the face,
use `--font-scale` (e.g. `0.5`–`0.6`) rather than shortening the specific
detail that makes the joke work — the specificity is not the part to cut.

**Guardrail**: the dark implication should always stay *implied*, absurd, and
obviously fictional (transporting something, not instructions for anything
actually actionable/harmful) — the format is funny because it's a wink at
AI-safety theater, not because it depicts real violence or gives real
instructions for wrongdoing. If a draft would only be funny by actually
being usable as harmful instructions, it's not this joke anymore.

## Template Variety

Repeating the same template (Drake, mainly) makes memes feel formulaic even
when individual captions are fine. `render_local()` now tracks the last 50
templates used in `memes/.history.json` and:
- `suggest_template_for_context()` filters out templates used in the last 5
  renders when picking from `CONTEXT_MAP`, instead of always returning the
  first (usually Drake) candidate.
- `render` prints a `repeat:` warning if a template shows up 2+ times in your
  last 4 memes, as a nudge to reach for a different `joke_type` — e.g. swap a
  `contrast` joke for a `reveal` (Disaster Girl) or `irony` (This is Fine) one
  covering the same pain point from a different angle.

This is the **default way to produce a meme** for the user. Only fall back to
the raw API URL (below) if Pillow/the Impact font aren't available or the
user explicitly wants a shareable link instead of a file.

The renderer wraps/shrinks text to fit and only positions captions top and
bottom, so it works best with classic two-caption templates (`fine`, `buzz`,
`success`, `mordor`, `yodawg`, etc.). Multi-panel templates like `drake` or
`distracted` still render fine as background art, but their extra caption
slots aren't supported locally — use the API URL for those instead.

## Quick Start

### Basic Meme Structure

**URL Format:**
```
https://api.memegen.link/images/{template}/{top_text}/{bottom_text}.{extension}
```

**Example:**
```
https://api.memegen.link/images/buzz/memes/memes_everywhere.png
```

This generates a Buzz Lightyear meme with "memes" at the top and "memes everywhere" at the bottom.

## Text Formatting

### Spacing
- Use **underscores** (`_`) or **dashes** (`-`) for spaces in text
- Example: `One_Does_Not_Simply` → "One Does Not Simply"

### Special Characters
- Use URL encoding for special characters
- Spaces: `_` or `-`
- Newlines: `~n`
- Question mark: `~q`
- Percent: `~p`
- Slash: `~s`
- Hash/Pound: `~h`
- Quotes: `''` for single, `""` for double

### Single Line Text
For memes with only one line of text, use an underscore for the empty line:
```
https://api.memegen.link/images/yodawg/_/your_text_here.png
```

## Available Templates

**Popular Templates:**
- `buzz` - Buzz Lightyear ("X, X Everywhere")
- `drake` - Drake Hotline Bling (two panels)
- `doge` - Doge (multiple text positions)
- `distracted` - Distracted Boyfriend
- `changemind` - Change My Mind
- `success` - Success Kid
- `skeptical` - Skeptical Third World Kid
- `awesome` - Awesome/Awkward Penguin
- `yodawg` - Yo Dawg
- `ancient` - Ancient Aliens Guy
- `wonka` - Condescending Wonka

**View all templates:**
- API endpoint: `https://api.memegen.link/templates/`
- Interactive docs: `https://api.memegen.link/docs/`

## Advanced Features

### Image Formats

**Supported Extensions:**
- `.png` - Standard format, best quality
- `.jpg` - Smaller file size
- `.webp` - Modern format, good compression
- `.gif` - Animated (if template supports it)

**Example:**
```
https://api.memegen.link/images/buzz/memes/memes_everywhere.webp
```

### Dimensions

**Width & Height:**
```
?width=800
?height=600
?width=800&height=600  (padded to exact dimensions)
```

**Example:**
```
https://api.memegen.link/images/buzz/memes/memes_everywhere.png?width=1200
```

### Layout Options

Control text positioning with the `layout` parameter:
```
?layout=top     # Text at top only
?layout=bottom  # Text at bottom only
?layout=default # Standard top/bottom
```

**Example:**
```
https://api.memegen.link/images/rollsafe/when_you_have/a_good_idea.png?layout=top
```

### Custom Fonts

**Available fonts:**
- View at: `https://api.memegen.link/fonts/`
- Use: `?font=impact` (default)

### Custom Images

Use any image URL as a background:
```
?style=https://example.com/your-image.jpg
```

**Example:**
```
https://api.memegen.link/images/custom/hello/world.png?style=https://i.imgur.com/abc123.jpg
```

## Practical Examples

### Example 1: Drake Meme
```
https://api.memegen.link/images/drake/manual_testing/automated_testing.png
```
Top panel (rejected): "manual testing"
Bottom panel (approved): "automated testing"

### Example 2: Distracted Boyfriend
```
https://api.memegen.link/images/distracted/my_code/new_shiny_framework/current_project.png
```
- Boyfriend: "my code"
- Other girl: "new shiny framework"
- Girlfriend: "current project"

### Example 3: One Does Not Simply
```
https://api.memegen.link/images/mordor/one_does_not_simply/fix_a_bug_without_creating_two_more.png
```

### Example 4: Change My Mind
```
https://api.memegen.link/images/changemind/tabs_are_better_than_spaces.png
```

### Example 5: Success Kid
```
https://api.memegen.link/images/success/all_tests_passing/on_the_first_try.png
```

### Example 6: Custom Dimensions
```
https://api.memegen.link/images/buzz/memes/memes_everywhere.png?width=1200&height=630
```
Perfect for Open Graph social media sharing (1200x630).

## Creating Contextual Memes

### For Code Reviews
```
Template: fry (Futurama Fry - "Not sure if...")
https://api.memegen.link/images/fry/not_sure_if_feature/or_bug.png
```

### For Deployments
```
Template: interesting (The Most Interesting Man)
https://api.memegen.link/images/interesting/i_dont_always_test_my_code/but_when_i_do_i_do_it_in_production.png
```

### For Documentation
```
Template: yodawg
https://api.memegen.link/images/yodawg/yo_dawg_i_heard_you_like_docs/so_i_documented_the_documentation.png
```

### For Performance Issues
```
Template: fine (This is Fine Dog)
https://api.memegen.link/images/fine/memory_usage_at_99~/this_is_fine.png
```

## Best Practices

### 1. Keep Text Concise
- Memes work best with short, punchy text
- Aim for 2-6 words per line
- Long text becomes hard to read

### 2. Choose Appropriate Templates
- Match the template to your message
- Drake =
...<truncated>
````


### `meme-generation/assets/Anton-OFL.txt`

```
Copyright 2020 The Anton Project Authors (https://github.com/googlefonts/AntonFont.git)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

```


### `meme-generation/assets/Anton-Regular.ttf`

```
<binary file omitted>
```


### `meme-generation/complete-markdown-memes-guide.md`

`````markdown
# The Complete Practical Guide to Memes in Markdown Blog Posts

*A comprehensive toolbox for creating textual and image memes in Markdown-based blogs (MkDocs, Jekyll, Hugo, Astro, etc.)*

---

## 📖 Table of Contents

1. [Why Markdown for Memes?](#why-markdown-for-memes)
2. [Core Markdown Techniques](#core-markdown-techniques)
3. [Layout Patterns for Text Memes](#layout-patterns-for-text-memes)
4. [Textual Meme Formats](#textual-meme-formats)
5. [Image Memes via Pure URL](#image-memes-via-pure-url)
6. [Mixing Text + Image Memes](#mixing-text--image-memes)
7. [Advanced Features](#advanced-features)
8. [Production Tips](#production-tips)
9. [Complete Blog Example](#complete-blog-example)

---

## 🎯 Why Markdown for Memes?

Markdown is deceptively powerful for meme creation:

- **Preserves formatting** - Code blocks maintain spacing, ASCII art, and alignment
- **Readable source** - Plain text is portable and version-controllable
- **Native structure** - Blockquotes, emphasis, and spacing create rhythm naturally
- **No uploads needed** - Text memes live in your content, image memes via URL
- **Accessible** - Screen readers handle text better than image-text
- **Fast** - No external dependencies for textual memes

With a few tricks—code fences, blockquotes, emphasis, spacing—you can reproduce nearly any textual meme format inside a blog post.

---

## 🧱 Core Markdown Techniques

Master these building blocks first:

### ✅ Code Fences

Use triple backticks to preserve whitespace, arrows, emoji alignment, ASCII art, and raw text.

````markdown
```text
>be me
>perfect spacing preserved
>mfw markdown just works
```
````

**Use for:** Greentext, ASCII art, chat logs, anything needing exact spacing.

### ✅ Blockquotes

Use `>` for layered dialogue (Tumblr chains, ironic replies, greentext variants).

```markdown
> Person A: this is fine
>> Person B: no it's not
>>> Person C: *concern intensifies*
```

**Use for:** Tumblr chains, Twitter-style replies, nested irony.

### ✅ Hard Line Breaks

Two spaces at end of line or `<br>` for poetic timing.

```markdown
roses are red
markdown is neat
two trailing spaces
make formatting sweet
```

**Use for:** Poetry, dramatic pauses, controlled pacing.

### ✅ Horizontal Rules

Use `---` to reset comedic pacing or transition between bits.

```markdown
this joke is good

---

this joke is completely unrelated
```

**Use for:** Section breaks, punchline separation, tempo control.

### ✅ Emphasis

Use `*italics*` and `**bold**` to simulate tone, stage directions, or emotional beats.

```markdown
**me:** i'm fine
*narrator:* he was not fine
```

**Use for:** Stage directions, emphasis, emotional subtext.

---

## 🎨 Layout Patterns for Text Memes

### ✅ Choose a Meme Container

Each meme should have a clear "block" form for readability:

- **Code block** - Greentext, ASCII art, chat logs
- **Blockquote** - Tumblr chains, Twitter style
- **Heading + narrative** - Reddit AITA/TIFU, fake documentation
- **List** - Corporate satire, fake legal notices
- **Plain paragraph** - Emojicore, slang mutations

### ✅ Aim for Three Layers

Good textual memes read as:

1. **Setup** - Establish context
2. **Pattern / Rhythm** - Build expectation
3. **Punchline or twist** - Subvert or amplify

### ✅ Use Spacing as a Comedic Tool

Memes breathe through white space. Don't be afraid to isolate lines.

```markdown
this is fine

this is also fine

*nothing is fine*
```

---

## 📝 Textual Meme Formats

### 💚 1. Greentext

**Requirements:** Code fence with literal `>` characters

**How to do it:**

````markdown
```text
>be me
>writing markdown memes
>turn blog into 4chan but cleaner
>mfw it actually works
```
````

**Renders as:**

```text
>be me
>writing markdown memes
>turn blog into 4chan but cleaner
>mfw it actually works
```

**Tips:**

- Keep sentence fragments
- Use anticlimax for humor
- Monospace evokes "anon culture" immediately
- Works great for technical narratives

---

### 📦 2. Copypasta (Dramatic Walls of Text)

**Requirements:** Code fence for preservation

**How to do it:**

````markdown
```text
What the fuck did you just fucking say about Markdown, you little bitch?
I'll have you know I graduated top of my class in the Navy Seals of Documentation,
and I've been involved in numerous secret raids on WordPress blogs...
```
````

**Tips:**

- Let it be long - that's the point
- Overblown emotion is essential
- Preserve original formatting for authenticity
- Consider using collapsible sections for very long copypastas

---

### ✍️ 3. Shitpost Poetry & Micro-poems

**Requirements:** Hard line breaks (two spaces or `<br>`)

**How to do it:**

```markdown
roses are red
markdown is sly
press two spaces
to force a new line

violets are blue
syntax highlighting is lit
code fences preserve
every last bit
```

**Tips:**

- Lean into minimalism
- Add `<br>` if a theme strips trailing whitespace
- Break expectations with sudden philosophical turns
- Lowercase adds casual authenticity

---

### 🅰️ 4. ASCII Art & ASCII Storytelling

**Requirements:** Monospaced code fence

**How to do it:**

````markdown
```text
(╯°□°）╯︵ ┻━┻
┬─┬ノ( º _ ºノ)

    ___
   /   \
  | o o |  <- me reading documentation
   \___/
    |||
```
````

**Tips:**

- Use `text` or no language to avoid syntax coloring
- Test in multiple fonts
- Center using HTML `<div align="center">` if needed
- Keep it simple for broad compatibility

---

### 🌀 5. Surreal / Absurdist Text Memes

**Requirements:** Markdown + strategic spacing

**How to do it:**

```markdown
you awaken in the hallway of beans

the moon whispers: *not again*

---

**OPTION 1:** accept the beans
**OPTION 2:** become the beans

you choose Option 3

*there was no Option 3*
```

**Tips:**

- Use italics for dream-logic emphasis
- Insert `---` to create uncanny jumps
- Be playful with lowercase/capitalization
- Treat whitespace as tension

---

### 🗨️ 6. Tumblrisms (Multi-Speaker Chains)

**Requirements:** Nested blockquotes

**How to do it:**

```markdown
> **Person A:** frogs are neat
>> **Person B:** frogs are powerful
>>> **Person C:** I aspire to frog
>>>> **Person D:** this post is making me lose my mind
>>>>> **Person A:** good
```

**Tips:**

- Increase `>` depth to show escalation
- Add italics for emotional chaos
- Bold names for clarity
- Peak at 5 levels deep for readability

---

### 🐦 7. Twitter / X-style Micro-memes

**Requirements:** Blockquote or plain markdown

**How to do it:**

```markdown
> me: i'm productive
> also me: alphabetizes rocks by emotional energy

---

**me at 3am:** what if bread could read

**bread:** *softly* "help"
```

**Tips:**

- Keep it tight (2–4 lines)
- Use italics for "stage directions"
- Short exaggeration = comedy
- Em dash works great: "me: — also me:"

---

### 🧾 8. Reddit AITA / TIFU / Narrative Memes

**Requirements:** Markdown headings + paragraphs

**How to do it:**

```markdown
### TIFU by enabling Markdown features too much

**Context:** I work in documentation.

**What Happened:** Today I formatted a shopping list with H3 headings,
bullet points, and citation links. My family staged an intervention.

**Update:** They've hidden the Markdown guide. I'm formatting this from memory.

**AITA?**
```

**Tips:**

- Use headings for "post titles"
- Sincere tone + mild absurdity = gold
- Include typical Reddit formatting (bold labels)
- The more mundane the subject, the funnier

---

### 🗿 9. Wojak-style Dialogues (Text-Only)

**Requirements:** Bold names + minimal dialogue

**How to do it:**

```markdown
**Doomer:** nothing matters
**Zoomer:** drink water bro
**Wojak:** *internal screaming*
**Chad:** have you tried not caring
**Doomer:** ...wait that's actually helpful
**Chad:** i know
```

**Tips:**

- Keep dialogue short
- Rely on archetypes for instant recognition
- Tone = half-honest, half-ironic
- Works great in code blocks too

---

### 💬 10. Discord / Chat Log Memes

**Requirements:** Code fence with timestamp format

**How to do it:**

````markdown
```text
[12:41] user123: you up?
[12:42] system: error: feelings.exe not found
[12:42] user123: same
[12:43] bot: did you try turning your emotions off and on again?
[12:44] user123: yes
[12:44] system: critical error: emotions.dll missing since 2019
```
````

**Tips:**

- Timestamps add realism
- Use usernames to amplify the joke
- System messages = comedic gold
- Maintain deadpan delivery

---

### 😀 11. Emojicore Memes

**Requirements:** Just good spacing

**How to do it:**

```markdown
🚶💨 leaving my responsibilities

😔👉👈 wondering if coffee counts as a personality

🎯 hitting the target (the target is rock bottom)

✨ manifesting ✨ (chaos)
```

**Tips:**

- Use emojis as syntax, not decoration
- Vertical stacking works great
- Pair emoji with understated text
- Less is more

---

### 📚 12. Fake Wiki / Manual Pages

**Requirements:** Markdown headings + bold labels

**How to do it:**

```markdown
## Bread.exe

**Category:** Deprecated Carbohydrate
**Status:** Unstable
**Introduced:** ~8000 BCE
**Last Update:** Never

### Known Issues

- Becomes stale without manual intervention
- Incompatible with lactose-intolerant systems
- Memory leak when toasted incorrectly

### Workarounds

See: `butter.dll` documentation
```

**Tips:**

- Keep the tone pseudo-academic
- Use definition-style formatting
- Technical jargon for mundane objects = peak comedy
- Cross-reference other fake docs

---

### 🐕 13. Slang-Mutation Text Memes

**Requirements:** Markdown paragraphs with stylized orthography

**How to do it:**

```markdown
how 2 markdown:

1. make text smol
2. add sparklez ✨
3. u done it

**congrations** u did a format

no take backsies
```

**Tips:**

- Lean into childlike spelling
- Use emoji to exaggerate tone
- Mix formal structure (lists) with informal language
- "Congrations" beats "congratulations" every time

---

### 👶 14. ELI5 But Wrong

**Requirements:** Heading + confident incorrect explanation

**How to do it:**

```markdown
### ELI5: Why do volcanoes erupt?

Volcanoes are mountains that get too excited and sneeze the earth.
The rocks fly out because they want to be birds but forgot how.
This is called "geology."

**Follow-up:** Why is lava hot?

Because it's embarrassed about the whole situation.
```

**Tips:**

- Confidence + incorrectness = humor
- Keep explanation "plausible" sounding
- Use actual ELI5 structure for authenticity
- Works great for tech concepts too

---

### 📄 15. Corporate / Legalese Satire

**Requirements:** Lists + bold labels

**How to do it:**

```markdown
## Notice of Emotional Noncompliance

**Issued to:** You
**Date:** Whenever
**Reason:** Your vibe is irregular

### Required Actions

- [ ] Submit Form 42-B "Vibe Correction Protocol" within 5 business days
- [ ] Attend mandatory "Feeling Feelings Appropriately" training
- [ ] Provide written documentation of three (3) genuine smiles

**Failure to comply will result in:**

1. Passive-aggressive Slack messages
2. A pizza party (no pizza will be provided)
3. Being asked to "circle back" indefinitely

---

*This notice has been automatically generated by the Department of Vibes.*
```

**Tips:**

- Official tone for trivial things
- Short, concise bullet points increase contrast
- Use checkboxes for "action items"
- Footer disclaimers are pure gold

---

## 🖼️ Image Memes via Pure URL

Sometimes you want a visual punchline next to your textual meme. With **memegen.link**, you can generate classic image memes just by crafting a URL—no uploads, no editors. Then embed it in your Markdown with standard image syntax.

### 🚀 Quick Start

```markdown
![Drake meme about Markdown](https://api.memegen.link/images/drake/using_word_art/using_markdown.png)
```

That's it—your post renders the Drake format with top "using word art" and bottom "using markdown".

---

### 🔧 URL Anatomy

```
https://api.memegen.link/images/{template}/{top_text}/{bottom_text}.{ext}?{options}
```

**Components:**

- `template`: meme name (e.g., `drake`, `two-buttons`, `distracted-boyfriend`)
- `top_text` / `bottom_text`: your captions
- `ext`: `png` (default), `jpg`, or `webp` (good for lighter pages)
- `options`: query params (e.g., `font`, `width`, `watermark`)

**Example:**

```markdown
![Top/Bottom meme](https://api.memegen.link/images/two-buttons/write_tests/document_everything.png)
```

---

### 📝 Text Encoding Cheatsheet

Memegen uses compact encoding so URLs stay readable:

| Character | Encoding | Example |
|-----------|----------|---------|
| Space | `_` | `hello world` → `hello_world` |
| Hyphen | `--` | `foo-bar` → `foo--bar` |
| Underscore | `__` | `foo_bar` → `foo__bar` |
| Question mark | `~q` | `why?` → `why~q` |
| Percent | `~p` | `50%` → `50~p` |
| Hash | `~h` | `#tag` → `~htag` |
| Slash (literal) | `~s` | `foo/bar` → `foo~sbar` |
| Quotes | `''` | `he said "hi"` → `he_said_''hi''` |

**Line breaks:** Use `%0A` (URL-encoded newline) inside a caption.

**Example:**
```
top_line%0Asecond_line
```

---

### ⚙️ Common Options (Query String)

Append with `?` after the file extension:

```markdown
?font=impact               # Default is already impact; try notosans
?width=600&height=600      # Size control; height optional
?watermark=none            # Remove tiny watermark
?background=URL            # Custom template; URL-encode it
```

**Example:**

```markdown
![Clean meme](https://api.memegen.link/images/drake/write_specs/rely_on_vibes.webp?width=640&watermark=none)
```

---

### 🎭 Popular Templates

Quick reference for common meme formats:

| Template | Use Case |
|----------|----------|
| `drake` | Rejecting one thing, approving another |
| `two-buttons` | Difficult choice between two options |
| `distracted-boyfriend` | Being distracted by something new |
| `gru-plan` | Plan that goes wrong at the end |
| `change-my-mind` | Stating an opinion confidently |
| `mocking-spongebob` | Mocking someone's statement |
| `is-this-a-pigeon` | Misidentifying something obvious |
| `surprised-pikachu` | Shocked by predictable outcome |
| `success-kid` | Celebrating small victories |
| `uno-draw-25` | Refusing to do something even if costly |
| `custom` | Use with `?background=` for any image |

---

### 🎯 Blank Sides & One-Sided Captions

Leave a side blank with `_` (single underscore).

**Top only:**

```markdown
![Top-only](https://api.memegen.link/images/change-my-mind/markdown_is_a_meme_engine/_.png)
```

**Bottom only:**

```markdown
![Bottom-only](https://api.memegen.link/images/success-kid/_/finally_fixed_the_build.png)
```

---

### 📏 Multiline Captions

Use `%0A` for line breaks inside a side:

```markdown
![Multiline](https://api.memegen.link/images/gru-plan/plan_the_feature%0Adeliver_the_feature/forget_the_docs%0Awrite_them_later.png)
```

---

### 🎨 Custom Backgrounds (Brand or Screenshot)

Use the `custom` template + `background` parameter. The background must be publicly reachable.

```markdown
![Custom background meme](https://api.memegen.link/images/custom/top_text/bottom_text.png?background=https%3A%2F%2Fexample.com%2Fimage.png&watermark=none)
```

**Tip:** Pair a screenshot of your app/graph as background to comment on it memetically in the post.

---

### ♿ Accessibility & SEO

**Always provide descriptive alt text:**

```markdown
![Drake rejecting "manual edits", approving "Markdown memegen URLs"](https://api.memegen.link/images/drake/manual_edits/markdown_memegen_urls.png?watermark=none)
```

Screen readers will read your alt text, making the joke accessible to everyone.

---

### 📦 Quick Copy-Paste Recipes

**Drake (top/bottom):**
```markdown
![Drake](https://api.memegen.link/images/drake/write_docs/write_memes.png?watermark=none)
```

**Two Buttons (multiline with %0A):**
```markdown
![Two buttons](https://api.memegen.link/images/two-buttons/fix_the_build%0A_now_/add_more_features%0A_today_.png)
```

**Change My Mind (top only):**
```markdown
![Change my mind](https://api.memegen.link/images/change-my-mind/markdown_is_a_design_tool/_.png)
```

**Surprised Pikachu (bottom only):**
```markdown
![Pikachu](https://api.memegen.link/images/surprised-pikachu/_/forgot_to_escape_slash~sagain.png)
```

**Custom background (brand screenshot):**
```markdown
![Custom](https://api.memegen.link/images/custom/ship_it/friday_release.webp?background=https%3A%2F%2Fyour.cdn%2Fapp_screenshot.png&width=720&watermark=none)
```

---

## 🎭 Mixing Text + Image Memes

Blend text-only formats and image memes to pace a post effectively.

### ✅ Rhythm Pattern

**1. Start with text** to set up context:

````markdown
### The Day I Realized Markdown Is Too Powerful

```text
>be me
>ship blog revamp
>marketing wants "fun"
>accidentally create meme culture
```
````

**2. Follow with a visual** to amplify the punchline:

```markdown
![Two buttons](https://api.memegen.link/images/two-buttons/make_it_fun/make_it_accessible.png)
```

**3. Close with another text format** for resolution:

```markdown
**Status:** fun achieved
**Risk:** puns leaked to production
**Next Steps:** embrace chaos
```

### ✅ Strategic Placement

**Good patterns:**

- Greentext → Image → Corporate satire
- Tumblr chain → ASCII art → Image
- Chat log → Image → Shitpost poetry

**Avoid:**

- 5 images in a row (visual fatigue)
- 3 long copypastas back-to-back (reader exhaustion)
- Image without context (confusing)

### ✅ Complementary Pairs

Match text and image memes thematically:

| Text Format | Image Format | Why It Works |
|-------------|--------------|--------------|
| Greentext | Drake | Both are format-specific classics |
| Corporate satire | Two buttons | Amplifies "decision paralysis" theme |
| Chat log | Surprised Pikachu | Both capture reactions |
| Reddit AITA | Distracted boyfriend | Narrative + visual work together |
| Wojak dialogue | Custom background | Text provides context for visual |

---

## 🔥 Advanced Features

### 🎨 Custom CSS for Greentext (MkDocs / Hugo)

Add custom styling for greentext blocks:

```css
.greentext {
  color: #789922;
  font-family: 'Courier New', monospace;
  background-color: #f0f0f0;
  padding: 1rem;
  border-left: 4px solid #789922;
}
```

Then wrap greentext in HTML:

```html
<div class="greentext">

>be you
>styling markdown like a pro
>mfw it actually looks good

</div>
```

---

### 📢 Admonitions (MkDocs Material, Docusaurus)

Use admonition blocks for "official" memes or mock disclaimers:

```markdown
!!! warning "System Alert"
    Your snacks have been revoked pending performance review.

!!! danger "Critical Error"
    `feelings.exe` has stopped responding.
    Would you like to send an error report? [Yes] [Yes]
```

---

### 🗂️ Collapsible Sections

Great for long copypastas and Tumblr chains:

```markdown
<details>
<summary>Click to expand the legendary copypasta</summary>

What the fuck did you just fucking say about Markdown...
(rest of copypasta)

</details>
```

---

### 🎯 Tabs (Docusaurus, MkDocs)

Show multiple meme variations side-by-side:

```markdown
=== "Greentext"

    ```text
    >be me
    >tabs are cool
    ```

=== "Corporate"

    **Notice:** Tabs have been deemed acceptable for meme deployment.

=== "Image"

    ![Drake](https://api.memegen.link/images/drake/regular_text/tabs.png)
```

---

## 🚀 Production Tips

### ⚡ Performance

**For text memes:**
- Already optimal (it's just text!)
- No external dependencies
- Fast rendering

**For image memes:**
- Prefer `webp` format for 30-50% smaller files
- Set consistent `?width=640` for predictable layout
- Consider lazy loading: `loading="lazy"` on `<img>` tags
- Cache memegen.link URLs (they're stable)

### ♿ Accessibility

**Text memes:**
- Already screen-reader friendly
- Use semantic HTML where appropriate
- Avoid ASCII art for critical information

**Image memes:**
- Always provide descriptive alt text
- Describe the joke, not just "
...<truncated>
`````


### `meme-generation/examples.md`

````markdown
# Meme Generation Examples

This document provides practical examples of using the meme generation skill in various contexts.

## Basic Examples

### Example 1: Simple Meme

**Request:**
"Create a meme about bugs everywhere"

**Response:**
```
Here's a Buzz Lightyear meme about bugs:

![Bugs Everywhere](https://api.memegen.link/images/buzz/bugs/bugs_everywhere.png)
```

### Example 2: Drake Comparison Meme

**Request:**
"Make a Drake meme comparing manual testing vs automated testing"

**Response:**
```
Here's a Drake meme comparing testing approaches:

![Testing Comparison](https://api.memegen.link/images/drake/manual_testing/automated_testing.png)
```

### Example 3: Success Meme

**Request:**
"Generate a success kid meme about all tests passing"

**Response:**
```
Here's a success meme:

![Tests Passing](https://api.memegen.link/images/success/all_tests_passing/on_the_first_try.png)
```

## Programming Examples

### Python Script Example

```python
from meme_generator import MemeGenerator

# Initialize generator
meme = MemeGenerator()

# Generate a basic meme
url = meme.generate(
    template="buzz",
    top_text="features",
    bottom_text="features everywhere"
)
print(f"Meme URL: {url}")

# Generate with custom dimensions for social media
url = meme.generate(
    template="drake",
    top_text="writing tests later",
    bottom_text="writing tests first",
    width=1200,
    height=630
)
print(f"Social media meme: {url}")

# Get markdown for embedding
markdown = meme.get_markdown_image(url, alt_text="TDD Meme")
print(f"Markdown: {markdown}")
```

### CLI Example

```bash
# Generate a basic meme
python meme_generator.py generate buzz "features" "features everywhere"

# Generate with markdown output
python meme_generator.py generate success "deployed" "no errors" --markdown

# Generate with custom dimensions
python meme_generator.py generate drake "old way" "new way" --width 1200 --height 630

# List all available templates
python meme_generator.py list-templates

# Suggest template for context
python meme_generator.py suggest "deployment success"
```

## Context-Specific Examples

### Code Review Context

**Scenario:** Reviewing pull request with many changes

```python
# Not sure if improvements or over-engineering
url = meme.generate("fry", "not sure if improvements", "or over engineering")
```

**Result:**
```
https://api.memegen.link/images/fry/not_sure_if_improvements/or_over_engineering.png
```

### Deployment Context

**Scenario:** Successful production deployment

```python
# Success kid meme
url = meme.generate("success", "deployed to production", "zero downtime")
```

**Result:**
```
https://api.memegen.link/images/success/deployed_to_production/zero_downtime.png
```

### Incident Response Context

**Scenario:** Production fire

```python
# This is fine meme
url = meme.generate("fine", "production is down", "this is fine")
```

**Result:**
```
https://api.memegen.link/images/fine/production_is_down/this_is_fine.png
```

### Documentation Context

**Scenario:** Documentation improvements

```python
# Yo dawg meme
url = meme.generate(
    "yodawg",
    "yo dawg i heard you like docs",
    "so i documented the documentation"
)
```

**Result:**
```
https://api.memegen.link/images/yodawg/yo_dawg_i_heard_you_like_docs/so_i_documented_the_documentation.png
```

## Advanced Examples

### Custom Dimensions for Social Media

**Open Graph (1200x630)**

```python
url = meme.generate(
    "buzz",
    "features",
    "features everywhere",
    width=1200,
    height=630
)
```

### Custom Layout

**Top-only text**

```python
url = meme.generate(
    "rollsafe",
    "cant have bugs in production",
    layout="top"
)
```

### Multiple Format Generation

```python
formats = ["png", "jpg", "webp"]
for fmt in formats:
    url = meme.generate(
        "success",
        "all tests passing",
        "first try",
        extension=fmt
    )
    print(f"{fmt.upper()}: {url}")
```

## Integration Examples

### Slack Bot Integration

```python
def send_deployment_meme(channel: str, status: str):
    """Send a meme to Slack based on deployment status."""
    meme = MemeGenerator()

    if status == "success":
        url = meme.generate("success", "deployed", "no errors")
        message = "Deployment successful!"
    elif status == "failure":
        url = meme.generate("fine", "deployment failed", "this is fine")
        message = "Deployment needs attention"
    else:
        url = meme.generate("fry", "not sure if deployed", "or still deploying")
        message = "Deployment status unclear"

    # Send to Slack (pseudo-code)
    slack_client.chat_postMessage(
        channel=channel,
        text=message,
        attachments=[{"image_url": url}]
    )
```

### GitHub PR Comments

```python
def comment_pr_review(pr_number: int, review_type: str):
    """Add a meme to PR comments based on review."""
    meme = MemeGenerator()

    review_memes = {
        "approved": meme.generate("success", "lgtm", "ship it"),
        "changes_requested": meme.generate("yuno", "y u no", "write tests"),
        "complex": meme.generate("fry", "not sure if feature", "or bug")
    }

    url = review_memes.get(review_type)
    markdown = meme.get_markdown_image(url, alt_text="Code Review Meme")

    # Post comment (pseudo-code)
    github_client.issues.create_comment(
        pr_number,
        f"Code review complete!\n\n{markdown}"
    )
```

### Discord Bot Integration

```python
@bot.command()
async def deploy_meme(ctx, status: str):
    """Generate deployment meme for Discord."""
    meme = MemeGenerator()

    if status == "success":
        url = meme.generate("success", "deployed", "zero downtime")
    elif status == "fail":
        url = meme.generate("fine", "servers on fire", "this is fine")

    await ctx.send(f"Deployment Status: {status}")
    await ctx.send(url)
```

## Batch Generation Examples

### Generate Multiple Memes for a Topic

```python
def generate_testing_memes():
    """Generate a collection of testing-related memes."""
    meme = MemeGenerator()

    memes = [
        meme.generate("drake", "manual testing", "automated testing"),
        meme.generate("success", "all tests passing", "on first try"),
        meme.generate("fry", "not sure if bug", "or feature"),
        meme.generate("interesting", "i dont always test", "but when i do i test in production"),
    ]

    print("Testing Meme Collection:\n")
    for i, url in enumerate(memes, 1):
        print(f"{i}. {url}")

generate_testing_memes()
```

### Generate Memes for Documentation

```python
def generate_docs_memes():
    """Generate documentation-related memes."""
    meme = MemeGenerator()

    topics = [
        ("yodawg", "yo dawg i heard you like docs", "so i documented your docs"),
        ("buzz", "documentation", "documentation everywhere"),
        ("wonka", "oh you write documentation", "tell me more about this fantasy"),
        ("ancient", "documentation", "documentation"),
    ]

    for template, top, bottom in topics:
        url = meme.generate(template, top, bottom)
        markdown = meme.get_markdown_image(url)
        print(markdown)

generate_docs_memes()
```

## Error Handling Examples

### Check Template Validity

```python
def generate_safe_meme(template: str, top: str, bottom: str):
    """Generate meme with error handling."""
    meme = MemeGenerator()

    # Check if template exists
    if template not in meme.TEMPLATES:
        print(f"Warning: '{template}' not in known templates")
        print(f"Falling back to 'buzz'")
        template = "buzz"

    url = meme.generate(template, top, bottom)
    return url
```

### Context-Based Template Selection

```python
def smart_meme_generation(context: str, top: str, bottom: str):
    """Generate meme with automatic template selection."""
    meme = MemeGenerator()

    # Suggest template based on context
    suggested_template = meme.suggest_template_for_context(context)

    print(f"Context: {context}")
    print(f"Suggested template: {suggested_template}")

    url = meme.generate(suggested_template, top, bottom)
    return url

# Examples
url1 = smart_meme_generation("deployment success", "deployed", "no errors")
url2 = smart_meme_generation("debugging nightmare", "bugs", "bugs everywhere")
url3 = smart_meme_generation("code comparison", "old code", "new code")
```

## Tips for Effective Memes

### Good Examples

```python
# ✅ Concise text
meme.generate("buzz", "bugs", "bugs everywhere")

# ✅ Relevant template
meme.generate("drake", "manual work", "automation")

# ✅ Clear message
meme.generate("success", "deployed", "zero errors")
```

### Bad Examples

```python
# ❌ Too much text
meme.generate(
    "buzz",
    "there are way too many bugs in this codebase",
    "seriously there are bugs absolutely everywhere i look"
)

# ❌ Wrong template choice
meme.generate("success", "production is down", "servers crashed")  # Should use 'fine'

# ❌ Unclear message
meme.generate("buzz", "stuff", "things")
```

## Testing Your Memes

```python
def test_meme_generation():
    """Test meme generation with various inputs."""
    meme = MemeGenerator()

    test_cases = [
        ("buzz", "test", "test everywhere", "png"),
        ("drake", "bugs", "features", "jpg"),
        ("success", "deployed", "working", "webp"),
    ]

    print("Testing meme generation:\n")
    for template, top, bottom, ext in test_cases:
        url = meme.generate(template, top, bottom, extension=ext)
        print(f"✓ {template} ({ext}): {url}")

test_meme_generation()
```

## Real-World Workflow Example

```python
def deployment_workflow_with_memes(deployment_result: dict):
    """Complete deployment workflow with contextual memes."""
    meme = MemeGenerator()

    # Pre-deployment
    pre_deploy = meme.generate("buzz", "deployments", "deployments incoming")
    print(f"Pre-deployment: {pre_deploy}")

    # During deployment
    if deployment_result["status"] == "in_progress":
        during = meme.generate("fry", "not sure if deploying", "or already deployed")
        print(f"During: {during}")

    # Post-deployment
    if deployment_result["success"]:
        post = meme.generate(
            "success",
            "deployed to production",
            "zero downtime",
            width=1200,
            height=630
        )
        slack_message = f"Deployment successful! {post}"
    else:
        post = meme.generate("fine", "deployment failed", "this is fine")
        slack_message = f"Deployment needs attention: {post}"

    print(f"Post-deployment: {slack_message}")

# Example usage
deployment_workflow_with_memes({
    "status": "completed",
    "success": True,
    "duration": "5m 32s"
})
```

## Summary

These examples demonstrate:
- Basic meme generation
- Context-aware template selection
- Integration with popular platforms
- Error handling and validation
- Batch generation workflows
- Real-world use cases

For more examples and templates, visit:
- https://api.memegen.link/docs/
- https://api.memegen.link/templates/

````


### `meme-generation/meme_generator.py`

```
#!/usr/bin/env python3
"""Meme Generator Helper.

A Python interface for the memegen.link API to generate memes programmatically.

Usage:
    python meme_generator.py generate buzz "memes" "memes everywhere"
    python meme_generator.py list-templates
    python meme_generator.py suggest "deployment success"

Or import as a module:
    from meme_generator import MemeGenerator
    meme = MemeGenerator()
    url = meme.generate("buzz", "hello", "world")
"""

from __future__ import annotations

import argparse
import io
import json
import textwrap
import urllib.parse
from pathlib import Path

import requests
from PIL import Image, ImageDraw, ImageFilter, ImageFont

ASSETS_DIR = Path(__file__).parent / "assets"

# Bundled Anton (OFL-licensed, redistributable) so caption rendering works
# identically on every OS this skill gets installed on — Windows' Impact.ttf
# is NOT guaranteed to exist on Mac/Linux, which is exactly the kind of thing
# that breaks silently for someone who installs this from a marketplace and
# never files an issue, they just conclude the skill is broken.
_BUNDLED_FONT = ASSETS_DIR / "Anton-Regular.ttf"

# System fallbacks, tried only if the bundled font is somehow missing.
_FONT_FALLBACKS = [
    _BUNDLED_FONT,
    Path(r"C:\Windows\Fonts\impact.ttf"),
    Path("/System/Library/Fonts/Supplemental/Impact.ttf"),
    Path("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"),
    Path("/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf"),
]


def _resolve_font(*preferred: Path) -> Path:
    """Return the first font path that actually exists, checked first-choice-first.

    Raises a clear, actionable error instead of letting Pillow blow up with an
    opaque OSError deep in ImageFont.truetype() if nothing is found.
    """
    for candidate in (*preferred, *_FONT_FALLBACKS):
        if candidate.exists():
            return candidate
    tried = ", ".join(str(c) for c in (*preferred, *_FONT_FALLBACKS))
    msg = f"No usable font found. Tried: {tried}"
    raise FileNotFoundError(msg)


DEFAULT_FONT_PATH = _BUNDLED_FONT

# Localized-max edge-density threshold above which we treat a region as
# already containing baked-in printed text/graphics (e.g. Change My Mind's
# physical sign) that our own caption would collide with. Calibrated against
# the full curated template list: every legitimate top_bottom template caps
# out around 48 on this metric; templates with real pre-existing text in the
# caption zone (like "cmm") measure 62+. See _region_busyness for why a plain
# average over the region misses this (it gets diluted by empty background).
MAX_BUSYNESS = 55

# Words that tend to signal a punchline/exaggeration for the funniness heuristic.
HYPERBOLE_WORDS = {
    "literally", "entire", "always", "never", "immediately", "actually",
    "somehow", "mysteriously", "definitely", "everything", "nothing",
    "everyone", "nobody", "instantly", "secretly", "apparently", "already",
}


class MemeGenerator:
    """Generate memes using the memegen.link API."""

    BASE_URL = "https://api.memegen.link"

    # Curated templates verified against the live memegen.link /templates/ API
    # (several of the old IDs here — changemind, distracted, ancient, skeptical,
    # surprised, thinking, boardroom — no longer exist and 404'd).
    #
    # Each entry documents the JOKE SHAPE, not just the vibe, because picking a
    # template that doesn't match the shape of the joke is what makes a meme
    # confusing even when the image renders fine. `layout` also tells
    # render_local() whether plain top/bottom captions can actually land:
    #   - "top_bottom": the art has real top/bottom caption space; safe to
    #     render locally.
    #   - "multi_slot": the template needs 3+ positioned captions on specific
    #     parts of the image (e.g. one per person). render_local() can't place
    #     those correctly and will refuse — use generate() for a full API URL
    #     with one segment per slot instead.
    TEMPLATES = {
        "buzz": {
            "name": "X, X Everywhere (Buzz Lightyear)",
            "layout": "top_bottom",
            "joke_type": "escalation",
            "guidance": "One strong, absurd statement. Use only top OR bottom text — 'X, X everywhere' doesn't need real two-line contrast.",
        },
        "drake": {
            "name": "Drakeposting",
            "layout": "top_bottom",
            "joke_type": "contrast",
            "guidance": "Top = the rejected/inferior option, bottom = the approved one. Make them GENUINELY opposite in substance, not just reworded.",
        },
        "success": {
            "name": "Success Kid",
            "layout": "top_bottom",
            "joke_type": "statement",
            "guidance": "A clean, specific win. Avoid vague victories — name the exact thing that went right.",
        },
        "fine": {
            "name": "This is Fine",
            "layout": "top_bottom",
            "joke_type": "irony",
            "guidance": "Describe something clearly chaotic/bad as if it's totally normal. The gap between the words and the burning room is the joke.",
        },
        "fry": {
            "name": "Futurama Fry",
            "layout": "top_bottom",
            "joke_type": "uncertainty",
            "guidance": "'Not sure if X / or Y' — two plausible, genuinely ambiguous readings of the same situation.",
        },
        "cmm": {
            "name": "Change My Mind",
            "layout": "boxed_photo",
            "joke_type": "single_line",
            "guidance": (
                "The sign in this photo has 'CHANGE MY MIND' physically printed on it — any generic "
                "top/bottom caption collides with it (measured busyness 62+, hard limit is 55). "
                "render_local() will refuse this template. Use generate() for the real API URL, which "
                "positions text inside the sign correctly server-side (at the cost of the memegen watermark)."
            ),
        },
        "yodawg": {
            "name": "Xzibit Yo Dawg",
            "layout": "top_bottom",
            "joke_type": "recursion",
            "guidance": "'Yo dawg I heard you like X, so I put X in your X' — needs genuine recursive/nested absurdity, not just two related things.",
        },
        "regret": {
            "name": "I Immediately Regret This Decision! (Ron Burgundy)",
            "layout": "top_bottom",
            "joke_type": "alarm",
            "guidance": (
                "Ron Burgundy's face IS a genuine 'oh no, what did I just get into' scared/alarmed "
                "reaction — don't write a punchline for the bottom, just label what's causing the "
                "alarm (e.g. who's reacting, or the realization itself). The face carries the emotion; "
                "top = the thing that triggered it."
            ),
        },
        "interesting": {
            "name": "The Most Interesting Man",
            "layout": "top_bottom",
            "joke_type": "contrast",
            "guidance": "'I don't always X, but when I do, Y' — Y should undercut or subvert X, not just describe it.",
        },
        "mordor": {
            "name": "One Does Not Simply",
            "layout": "top_bottom",
            "joke_type": "statement",
            "guidance": "Frame a mundane task as an epic, nearly impossible quest.",
        },
        "yuno": {
            "name": "Y U NO Guy",
            "layout": "top_bottom",
            "joke_type": "complaint",
            "guidance": "Aggrieved, exasperated demand aimed at someone/something that keeps failing you.",
        },
        "doge": {
            "name": "Doge",
            "layout": "top_bottom",
            "joke_type": "statement",
            "guidance": "Broken, enthusiastic phrasing — 'such X', 'much Y', 'very Z', 'wow'.",
        },
        "wonka": {
            "name": "Condescending Wonka",
            "layout": "top_bottom",
            "joke_type": "sarcasm",
            "guidance": "Fake-polite, dripping condescension aimed at an obvious mistake.",
        },
        "aag": {
            "name": "Ancient Aliens Guy",
            "layout": "top_bottom",
            "joke_type": "conspiracy",
            "guidance": "Absurd overreach concluding '...therefore it was [ridiculous cause]'.",
        },
        "sk": {
            "name": "Skeptical Third World Kid",
            "layout": "top_bottom",
            "joke_type": "skepticism",
            "guidance": "A claim that sounds too convenient, delivered with visible side-eye.",
        },
        "awesome": {
            "name": "Socially Awesome/Awkward Penguin",
            "layout": "top_bottom",
            "joke_type": "contrast",
            "guidance": "Top = the socially smooth version, bottom = the awkward reality — same situation, opposite execution.",
        },
        "rollsafe": {
            "name": "Roll Safe",
            "layout": "top_bottom",
            "joke_type": "fake_wisdom",
            "guidance": "'Can't X if Y' — a piece of 'clever' logic that's actually a terrible workaround.",
        },
        "spongebob": {
            "name": "Mocking Spongebob",
            "layout": "top_bottom",
            "joke_type": "mock_repeat",
            "guidance": (
                "Top = a claim/quote that MUST already sound dubious or hollow on its own "
                "(that's the whole joke). Bottom = the exact same words, just in mOcKiNg CaSe. "
                "Do not invent new content for bottom — if the top line isn't already eye-roll-worthy, "
                "this template is the wrong choice."
            ),
        },
        "astronaut": {
            "name": "Always Has Been",
            "layout": "top_bottom",
            "joke_type": "reveal",
            "guidance": "Top = a naive realization phrased as a question/surprise. Bottom = 'always has been' or an equally deflating gut-punch.",
        },
        "disastergirl": {
            "name": "Disaster Girl",
            "layout": "top_bottom",
            "joke_type": "reveal",
            "guidance": "Top = the innocent setup/task. Bottom = the smirking reveal of the disaster that was actually caused.",
        },
        "db": {
            "name": "Distracted Boyfriend",
            "layout": "multi_slot",
            "joke_type": "temptation",
            "guidance": "3 labels: the boyfriend (you), what he's distracted by, and what he's neglecting. Needs generate() with 3 caption segments, not render_local().",
        },
        "gb": {
            "name": "Galaxy Brain",
            "layout": "multi_slot",
            "joke_type": "escalation",
            "guidance": "4 stacked panels, each needs its own increasingly absurd caption. Needs generate() with 4 segments, not render_local().",
        },
        "handshake": {
            "name": "Epic Handshake",
            "layout": "multi_slot",
            "joke_type": "agreement",
            "guidance": "2 unlikely parties agreeing on one shared thing, 3 labels total. Needs generate() with 3 segments, not render_local().",
        },
    }

    # Context-based template suggestions (top_bottom-compatible only)
    CONTEXT_MAP = {
        "success": ["success", "awesome"],
        "failure": ["fine", "yuno"],
        "comparison": ["drake", "awesome"],
        "uncertainty": ["fry", "sk"],
        "statement": ["buzz", "yodawg", "interesting", "mordor", "cmm"],
        "reaction": ["success", "fine", "astronaut"],
        "humor": ["doge", "wonka", "aag", "rollsafe"],
        "deployment": ["success", "fine", "interesting"],
        "testing": ["success", "fry", "interesting"],
        "debugging": ["fine", "fry", "disastergirl"],
        "documentation": ["yodawg", "buzz", "wonka"],
    }

    def __init__(self) -> None:
        """Initialize the meme generator."""

    def _format_text(self, text: str) -> str:
        """Format text for URL inclusion following memegen rules."""
        replacements = {
            " ": "_",
            "-": "--",
            "_": "__",
            "?": "~q",
            "%": "~p",
            "#": "~h",
            "/": "~s",
            '"': "''",
        }

        escaped = "".join(replacements.get(char, char) for char in text)

        # Percent-encode any remaining reserved characters while preserving
        # memegen's escape sequences and allowed characters.
        return urllib.parse.quote(escaped, safe="-_~")

    def generate(  # noqa: PLR0913
        self,
        template: str,
        top_text: str = "",
        bottom_text: str = "",
        extension: str = "png",
        width: int | None = None,
        height: int | None = None,
        layout: str | None = None,
        style: str | None = None,
        font: str | None = None,
        captions: list[str] | None = None,
    ) -> str:
        """Generate a meme URL.

        Args:
            template: Template name (e.g., 'buzz', 'drake')
            top_text: Text for the top of the meme (ignored if `captions` is given)
            bottom_text: Text for the bottom of the meme (ignored if `captions` is given)
            extension: Image format ('png', 'jpg', 'webp', 'gif')
            width: Optional width in pixels
            height: Optional height in pixels
            layout: Optional layout ('top', 'bottom', 'default')
            style: Optional style or custom background URL
            font: Optional font name
            captions: For multi-slot templates (e.g. Distracted Boyfriend needs 3,
                Galaxy Brain needs 4) — one string per caption slot, in the
                template's slot order. Overrides top_text/bottom_text.

        Returns:
            URL to the generated meme

        """
        if captions is not None:
            segments = [self._format_text(c) if c else "_" for c in captions]
        else:
            segments = [
                self._format_text(top_text) if top_text else "_",
                self._format_text(bottom_text) if bottom_text else "_",
            ]

        # Build base URL
        url = f"{self.BASE_URL}/images/{template}/{'/'.join(segments)}.{extension}"

        # Add query parameters
        params = {}
        if width:
            params["width"] = str(width)
        if height:
            params["height"] = str(height)
        if layout:
            params["layout"] = layout
        if style:
            params["style"] = style
        if font:
            params["font"] = font

        if params:
            query_string = urllib.parse.urlencode(params)
            url = f"{url}?{query_string}"

        return url

    def suggest_template_for_context(self, context: str, avoid_recent: int = 5) -> str:
        """Suggest a template based on context, steering away from recent repeats.

        Drake is the "safe" pick for most comparison/statement contexts, which
        is exactly why it gets overused — this prefers whichever candidate for
        the matched context hasn't shown up in the last `avoid_recent` renders
        (tracked in memes/.history.json), falling back to the first candidate
        only if all of them are recent.

        Args:
            context: Description of the situation (e.g., 'deployment success')
            avoid_recent: How many of the most recent renders to treat as "used up"

        Returns:
            Suggested template name

        """
        context_lower = context.lower()
        recent = set(self._recent_templates(avoid_recent))

        for key, candidates in self.CONTEXT_MAP.items():
            if key in context_lower:
                fresh = [c for c in candidates if c not in recent]
                return fresh[0] if fresh else candidates[0]

        return "buzz"

    def _history_path(self) -> Path:
        return Path("memes") / ".history.json"

    def _recent_templates(self, count: int) -> list[str]:
        """Read the last `count` template IDs used by render_local(), most recent first."""
        history_path = self._history_path()
        if not history_path.exists():
            return []
        try:
            history = json.loads(history_path.read_text())
        except (json.JSONDecodeError, OSError):
            return []
        return list(reversed(history))[:count]

    def _record_template_use(self, template: str) -> None:
        history_path = self._history_path()
        history_path.parent.mkdir(parents=True, exist_ok=True)
        try:
            history = json.loads(history_path.read_text()) if history_path.exists() else []
        except (json.JSONDecodeError, OSError):
            history = []
        history.append(template)
        history_path.write_text(json.dumps(history[-50:]))

    def list_templates(self) -> dict[str, dict]:
        """List all curated templates with their joke shape and guidance.

        Returns:
            Dictionary of template id -> {name, layout, joke_type, guidance}

        """
        return self.TEMPLATES

    def template_guidance(self, template: str) -> dict | None:
        """Look up the joke-shape rubric for a template, if it's in the curated list."""
        return self.TEMPLATES.get(template)

    def get_markdown_image(self, url: str, alt_text: str = "Meme", width: int | None = None) -> str:
        """Generate markdown for embedding the meme image.

        Args:
            url: The meme URL
            alt_text: Alternative text for the image
            width: Optional width specification

        Returns:
            Markdown image syntax

        """
        if width:
            return f'<img src="{url}" alt="{alt_text}" width="{width}"/>'
        return f"![{alt_text}]({url})"

    def render_local(
        self,
        template: str | None = None,
        top_text: str = "",
        bottom_text: str = "",
        output_path: str | Path | None = None,
        font_path: str | Path = DEFAULT_FONT_PATH,
        extension: str = "png",
        watermark: bool = True,
        image_path: str | Path | None = None,
        font_scale: float = 1.0,
    ) -> dict:
        """Render a meme locally as a downloadable file, without the memegen.link watermark.

        The public API only stamps its watermark onto text-rendered images; the
        blank template (no text) is watermark-free. So this fetches the blank
        template and draws the caption ourselves with Pillow, Impact-style.

        Args:
            template: A curated template ID (e.g. 'buzz', 'fine'), fetched from
                memegen.link. Mutually exclusive with `image_path`.
            top_text: Text for the top of the meme
            bottom_text: Text for the bottom of the meme
            output_path: Where to save the file (default: ./memes/{template}_{hash}.png)
            font_path: Path to a TTF font (defaults to the bundled Anton font,
                so this works the same on Windows/Mac/Linux without relying
                on a system font that may not be installed)
            extension: Background image format to fetch ('png', 'jpg', 'webp') — only
                used with `template`, ignored for `image_path`.
            watermark: Whether to stamp the small "skillpatch.dev" corner mark
            image_path: Path to your OWN local image to use as the background
                instead of a curated template — e.g. a personal screenshot, a
                meme character not in memegen's catalog (Surprised Pikachu,
                etc.), or any image you personally have the rights to use.
                This skill does not bundle or redistribute any such image
                itself; you supply it locally each time. Mutually exclusive
                with `template`.
            font_scale: Multiplier on the auto-fit caption size (e.g. 0.7 for
                noticeably smaller text). Useful when the default size covers
                important art, like a reaction template's face.

        Returns:
            Dict with the sav
...<truncated>
```


### `meme-generation/README.md`

````markdown
# Meme Generation Skill

Generate memes locally — no watermark, no dependency on the caller's OS fonts, with built-in sanity checks that refuse to ship a broken render.

## What is this?

This skill renders memes as real downloadable files instead of just handing back a link. It uses the free [memegen.link](https://memegen.link) API purely as an art source (fetching each template's blank background), then draws the caption itself with Pillow — which sidesteps memegen's watermark (only added when *it* renders the text) and gives full control over legibility.

**📚 See also:** [Complete Markdown Memes Guide](complete-markdown-memes-guide.md) for text-only meme formats (greentext, copypasta, ASCII art, chat logs, etc.) that don't need an image at all.

## Quick Start

```bash
python meme_generator.py render fine "one more subagent" "context at 4M tokens"
```

This:
1. Looks up `fine`'s joke shape (`irony`) and prints the caption rubric for it
2. Fetches the blank "This is Fine" art (no watermark, since no text was rendered server-side)
3. Draws both captions locally in the bundled Anton font, auto-sizing/wrapping to fit
4. Runs the output filter (legibility + funniness heuristic) and reports the results
5. Saves to `./memes/fine_one_more_subagent_context_at_4m_tokens.png` and prints the path

Or from Python:

```python
from meme_generator import MemeGenerator

meme = MemeGenerator()
result = meme.render_local("fine", "one more subagent", "context at 4M tokens")
print(result["path"], result["legibility"], result["funniness"])
```

## Why Local Rendering, Not Just a URL

The simple approach — build a memegen.link URL and hand it back — has three problems this skill fixes:

1. **Watermark.** memegen stamps "Memegen.link" onto any image where *it* draws the text. The blank template (no text) doesn't have one, so we draw the text ourselves.
2. **No feedback loop.** A URL either works or 404s; you find out by clicking it. `render_local()` actually inspects what it drew — did the font have to shrink to the size floor, did a caption wrap past 3 lines, is there already dense text/graphics baked into the part of the image it's about to draw on top of (see "Change My Mind" below) — and fails loudly *before* saving instead of silently shipping something unreadable.
3. **Cross-platform fonts.** Impact.ttf ships with Windows but isn't guaranteed anywhere else. This skill bundles [Anton](https://github.com/googlefonts/AntonFont) (OFL-1.1 licensed, `assets/Anton-Regular.ttf`) so rendering looks the same on Windows, macOS, and Linux without depending on what's installed.

## Templates Outside memegen's Catalog

memegen.link doesn't have everything — no Pokémon characters, for instance, so
there's no "Surprised Pikachu" template. This skill deliberately does **not**
work around that by bundling copyrighted character images in `assets/` (the
bundled font is safe to redistribute because it's OFL-licensed; a screenshot
of an anime frame is not, and this skill ships to strangers via SkillPatch).

Instead, use `render-image` to caption any image you personally supply, with
the same auto-fit/legibility/watermark pipeline as `render`:

```bash
python meme_generator.py render-image path/to/your-pikachu.jpg "when the tests pass locally" "and fail in ci"
```

## Output Filter

Every `render_local()` call returns:

- **`legibility`** (0-100 + issues) — derived from real render metrics: font-size floor hit, line-wrap count, and a busyness scan that specifically catches templates with pre-existing text/graphics in the caption zone (a plain average would miss a small dense text block; this uses a localized grid-max instead — see the code comments on `MAX_BUSYNESS`). If a region is too busy, the render is **refused outright**, not just flagged.
- **`funniness`** (0-100 + notes, explicitly heuristic) — proxies for punchier captions: concise phrasing, contrast/juxtaposition between top and bottom (shape-aware — it won't penalize intentional repetition in `mock_repeat` templates like Mocking Spongebob), hyperbole words. This is a sanity check, not a humor judge — no code can actually tell if something is funny.
- **`repeat_warning`** — templates get overused (Drake, mostly) because they require the least thought. `render_local()` tracks the last 50 templates used in `memes/.history.json` and warns if the same one shows up 2+ times in your last 4 renders. `suggest_template_for_context()` actively avoids recently-used candidates.

## Picking the Right Template

`MemeGenerator.TEMPLATES` is a curated, API-verified list (checked live against `api.memegen.link/templates/` — plenty of IDs floating around in older meme-skill boilerplate are dead: `changemind`, `distracted`, `ancient`, `skeptical`, `surprised`, `thinking`, `boardroom`). Each entry has:

- **`layout`** — `top_bottom` (safe for local rendering), `multi_slot` (needs 3-4 positioned captions — Distracted Boyfriend, Galaxy Brain, Epic Handshake; use `generate(captions=[...])` for a full API URL instead), or `boxed_photo` (has its own baked-in text, like Change My Mind's sign — `render_local()` refuses these).
- **`joke_type`** — the actual shape the caption needs (`contrast`, `mock_repeat`, `irony`, `reveal`, `escalation`, `single_line`, `recursion`, etc.)
- **`guidance`** — a one-line rubric for writing a caption that fits that shape.

Run `python meme_generator.py list-templates` to see the full table. Picking a template that doesn't match your joke's shape is the #1 cause of a meme that "doesn't make sense" even when it renders cleanly — see the full rationale in `SKILL.md`'s "Why Things Are Funny" section.

## Requirements

- Python 3.9+
- `pip install pillow requests`
- No API key needed (memegen.link is free/open-source, used only for template art)

## Watermark

Rendered output carries a small `skillpatch.dev` text mark in the bottom-right corner at reduced opacity. Pass `--no-watermark` (CLI) or `watermark=False` (Python) to skip it.

## License

- This skill's code: same license as the repository it's distributed in.
- Bundled font: [Anton](https://github.com/googlefonts/AntonFont) by The Anton Project Authors, [SIL Open Font License 1.1](https://openfontlicense.org/) — free to redistribute.
- Template art: served by [memegen.link](https://github.com/jacebrowning/memegen) (maintained by Jace Browning), free and open-source, no API key required.

````
