# Theme Generator

> Generate a new Marp slide theme CSS file from 1–3 input brand colors, following the var-based palette contract used by the marp-slides skill. Picks an appropriate color scheme (complementary, split-complementary, triadic, analogous, mono, or scheme-from-inputs), generates every palette slot (bg/fg/heading/em/links/code/tables/cards/compare-A-B/TL;DR/process-flow badge/cover title/section divider/…

- **Type:** Skill
- **Install:** `agentstack add skill-ranacm-marpsmith-theme-generator`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [RanaCM](https://agentstack.voostack.com/s/ranacm)
- **Installs:** 0
- **Category:** [Content & Media](https://agentstack.voostack.com/c/content-and-media)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [RanaCM](https://github.com/RanaCM)
- **Source:** https://github.com/RanaCM/marpsmith/tree/main/skills/theme-generator

## Install

```sh
agentstack add skill-ranacm-marpsmith-theme-generator
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Theme generator for Marp slide decks

Given 1–3 brand colors, produce a complete Marp theme CSS file that drops into `~/.marp/` (or any project's `.marp/`) and conforms to the **var-based palette contract** used by the starter themes in the marp-slides skill. Output is a single self-contained CSS file with palette + appended layouts block.

## Related skills

Part of the **marpsmith** bundle — composes with:
- **marp-slides** — the themes you generate plug into its decks (interchangeable-theme contract).
- **deck-a11y-audit** — audit the generated theme for WCAG contrast before using it.

> Script paths below use `` — the skills directory configured by the active agent harness; the marpsmith skills live there together.

## Scope

This skill **owns**:
- Mapping 1–3 brand hex inputs to every palette slot the marp-slides contract expects.
- Deciding a color scheme (complementary / split-complementary / triadic / analogous / mono / scheme-from-inputs) based on input count + accent-style hint.
- Validating contrast on critical foreground-vs-background pairs (WCAG 2.1 AA), iterating L until pairs pass.
- Suggesting an optional **layout variant** (glow / rounded / uppercase-divider / circle-badges / square-badges) when the palette mood implies one.
- Writing a complete theme CSS file that includes the layouts.css block, ready for `themeSet:` registration.

This skill **defers**:
- Building slide decks → `marp-slides` skill.
- Critiquing colour-blindness / APCA / vision impairment beyond WCAG AA → `deck-a11y-audit` skill.
- Re-running auto-fit / split passes after theme swap → `marp-slides` skill (theme swap shouldn't trigger them since the layout block is identical).

## Workflow

1. **Collect inputs** from the user:
   - **Required:** at least one brand hex (e.g. `#E63946`). Accept 1, 2, or 3 brand colors.
   - **Required:** a `--slug` for the theme (kebab-case; becomes both the filename and the `@theme` directive).
   - **Optional:** `--bg dark|light` — bg-tone hint. If omitted, heuristic uses brand L: very dark ( 0.78 L, low S) → light theme; otherwise dark (default — most brand palettes pop better on dark).
   - **Optional:** `--accent vivid|muted|pastel` — saturation/L tuning. Default `vivid`.
   - **Optional:** `--scheme auto|complementary|split|triadic|analogous|mono` — override the auto-pick.
   - **Optional:** `--variant auto|none|glow|rounded|uppercase-divider|circle-badges|square-badges`.
   - **Optional:** `--out ` — defaults to `/marp-slides/themes/.css`. For a project-local theme, use `/.marp/.css`.

2. **Run the generator script:**
   ```bash
   node /theme-generator/scripts/generate-theme.mjs \
     --brand "#E63946" \
     --slug crimson-night \
     --bg dark \
     --accent vivid \
     --variant glow
   ```
   It writes the CSS file, prints a contrast report to stderr, and exits non-zero only on argument errors.

3. **Inspect the contrast report.** Every critical pair should pass `OK` (≥ 4.5:1, WCAG AA normal text). If anything reports `LARGE-ONLY` or `FAIL`, see "Troubleshooting" below.

4. **Register the theme** in the user's `.marprc.yml`:
   ```yaml
   themeSet:
     - .marp/.css
   ```
   Or, if writing to `/marp-slides/themes/`, the user's existing `themeSet` entry pattern probably already covers it — confirm with the user before adding/removing entries.

5. **Smoke-test by rendering an existing deck.** Pick the user's most-recent deck or a canonical one (`lemborexant-drug-interaction.slides-dark.md` or any of the workspace's `*.slides-*.md` files), copy it to a tmp path, swap the `theme:` line in frontmatter to the new slug, and render with `marp`:
   ```bash
   marp --config-file ./.marprc.yml --html --allow-local-files -o ./.html ./.md
   marp --config-file ./.marprc.yml --images png ./.md
   ```
   Then `Read` slides 1 (title-cover) and a comparison/matrix slide to visually confirm:
   - Heading colour reads against the bg.
   - Compare A / B columns have distinct accents that aren't muddy.
   - TL;DR callouts (if any) have legible text against their own bg.
   - Process-flow badges contrast against the bg.

6. **Report** to the user: scheme used, bg-tone, variant applied, contrast ratios, any iterations needed, and the file path. Mention which existing themes are similar in mood (so the user knows where it fits).

## Decision rubric: which color scheme?

When `--scheme` is `auto` (the default):

| Inputs | Scheme chosen | Rationale |
|---|---|---|
| 3 brand colors | `triadic-from-input` | Honour what the user gave; treat as triadic anchors. |
| 2 brand colors | `dual-input` | Use both inputs; synthesize a 3rd anchor at midpoint + 60° hue shift so it doesn't compete. |
| 1 brand color, no hint | `split` (split-complementary) | Default. Two accents 150°/210° away — high contrast without straight-complement tension. |

You can force a different scheme via `--scheme`:

- **`complementary`** — primary + 180° opposite. Maximum contrast, but two-tone palettes can feel rigid (e.g. brand-only-red themes that want a strict identity).
- **`triadic`** — primary + 120° + 240°. Three roughly-equal anchors. Good when the brand wants playful balance across the spectrum.
- **`analogous`** — primary + ±30°. Harmonious, low-tension. Best for themes that should feel calm/editorial.
- **`mono`** — primary + low-S/high-L tint + low-S/low-L shade. Minimal aesthetic. Best when brand identity is a single recognisable hue and you don't want competing accents.
- **`split`** — primary + 150° + 210°. The default for a reason: gives heading + Compare-A from primary, em/Compare-B from one split anchor, highlight (numbered-list markers, big-stat) from the other. The three roles always have distinct identities without harshness.

The scheme math is deliberately simple HSL plus WCAG-2 contrast iteration. Smoke-render the result before treating it as finished; contrast math catches readability problems, but it does not replace visual judgment.

## Decision rubric: which layout variant?

When `--variant` is `auto`:

| Condition | Variant suggested | Why |
|---|---|---|
| bg = dark + accent = vivid + heading S > 0.75 | `glow` | Neon palettes look great with text-shadow glow on headings + big-stat. |
| accent = pastel | `rounded` | Soft palette + soft corners reinforces the calm aesthetic. |
| accent = muted | `uppercase-divider` | Editorial muted palette pairs with a typographic section-divider treatment. |
| otherwise | `none` | Minimal-signature themes are also valid — `dark.css` itself ships no variant. |

You can also force one explicitly:

- **`glow`** — text-shadow on headings + heavy glow on `.big-stat-hero .stat`. Reads as 80s/synthwave/neon.
- **`rounded`** — 16px `border-radius` on `.compare-grid > div` and `.matrix > div`. Reads as friendly/modern.
- **`uppercase-divider`** — uppercase + 0.08em letter-spacing + bottom border on `section.section-divider` headings. Reads as editorial.
- **`circle-badges`** — `border-radius: 50%` on `.process-flow ol li::before`. Reads as soft/approachable.
- **`square-badges`** — `border-radius: 0; clip-path: none` on the same. Reads as utilitarian/blockish.

Variants must remain single-selector overrides. Do not let a variant change positioning, flex direction, or grid templates — that breaks the layout contract that lets a deck render under any theme.

## Palette slots the generator fills

The contract from `/marp-slides/themes/README.md`. The script generates every slot below; do not hand-edit unless contrast iteration produced a colour you actively dislike.

Root vars on `section`:
- `background-color`, `color`
- `--card-tint` — fill for `.compare-grid > div`, `.matrix > div`
- `--table-row-bg` / `--table-row-alt-bg`
- `--compare-a-accent` / `--compare-a-tint`
- `--compare-b-accent` / `--compare-b-tint`

Type slots: heading, em, strong (= body fg), link, inline code fg/bg, code-block fg/bg.

hljs token slots: comment, keyword, string, number, function, built-in, attr, operator, punctuation.

Tables: th bg, th fg (= heading), td fg (= body), border colour.

Layout overrides:
- `section.title-cover h1` colour (the "cover title" — typically the most attention-getting accent).
- `section.section-divider :is(h1, h2, h3)` colour.
- `section.prose-with-tldr blockquote` border + bg + text + strong colour.
- `section.big-stat-hero` heading + `.stat` colour.
- `section.pull-quote` glyph + body + h1-inside + attribution colours.
- `section.process-flow ol li::before` bg + fg.
- `section.comparison .compare-grid > div:first-child / :last-child` border + border-left.

## Contrast validation

The generator enforces these minimums (WCAG 2.1 AA normal text = 4.5:1):

| Pair | Threshold | Iteration behaviour |
|---|---|---|
| heading vs bg | 4.5:1 | Walk L by 0.025 in the direction that increases contrast (up if bg-L  */
@import 'default';

section { ...palette + var declarations... }
section :is(h1...h6) { color: ... }
section p, ul, ol, li { ... }
section blockquote { ... }
section em, strong, a { ... }
section :not(pre) > code { ...inline code... }
section pre { ...code block... }
section .hljs-* { ...token colours... }
section table { ... }
section hr, dt, dd, header, footer { ... }

/* Layout palette overrides */
section.title-cover h1 { ... }
section.section-divider :is(h1, h2, h3) { ... }
section.prose-with-tldr blockquote { ... }
section.big-stat-hero { ... }
section.pull-quote { ... }
section.process-flow ol li::before { ... }
section.comparison .compare-grid > div:first-child / :last-child { ... }

/* Layout classes — appended from layouts.css (byte-identical to other themes) */
... ~360 lines of layout block ...

/* Optional variant override at the very end */
section :is(h1...h6) { text-shadow: ... }   /* or rounded / uppercase-divider / etc. */
```

## What NOT to do

- **Do not** modify the layout block in the middle of the file. The layout block is byte-identical across all themes — that's what lets one deck render under any theme. If a structural change is genuinely needed, it belongs in `/marp-slides/layouts.css` (and propagates to every theme), not in one theme.
- **Do not** generate themes that override layout structure broadly in their variant block. Variants must target a single layout selector (heading text-shadow, process-flow badge shape, section-divider typography, card border-radius). Anything that rewrites `display`, `flex-direction`, `grid-template-columns`, or positioning belongs in the shared layout block.
- **Do not** add `theme:` at the project's `.marprc.yml` config level — that overrides per-deck frontmatter and breaks theme selection across decks. Only add to `themeSet:`.
- **Do not** invent new palette slots. If a slot doesn't exist in the contract, the layout block won't read it and nothing will render. Extend the contract in `layouts.css` first if you genuinely need a new slot.
- **Do not** skip the smoke render. Contrast math is necessary but not sufficient — the only way to catch a "technically passes WCAG but looks muddy/garish/clashy" theme is to render an actual slide and look at it.
- **Do not** silently change the brand hue. Saturation/L are fair game; hue is identity. If a brand colour fundamentally won't work on the chosen bg-tone (e.g. pure #FFFF00 yellow on light bg), report back and ask whether the user wants to flip bg-tone instead.

## Troubleshooting

**Contrast report shows `FAIL` for heading vs bg.**
The script ran 25 L-iterations and still couldn't reach 4.5:1. Almost always means the brand hue has a saturation ceiling that caps achievable contrast (e.g. pure red on near-black, or pure yellow on white). Options:
1. Flip `--bg` to the opposite tone.
2. Try `--accent muted` — the script lowers saturation, which on yellow/cyan/magenta makes L-iteration more effective.
3. Pick `--scheme mono` — uses a hue-locked palette where the script can tweak only L freely.

**TL;DR text reports low contrast on a light theme.**
TL;DR uses a tinted yellow-family bg. If the highlight colour is already yellow, contrast against a yellow-tinted bg collapses. The script picks a desaturated dark variant of the highlight for the text — if it's still failing, the highlight base is probably yellow-on-light-bg which is structurally hard. Try `--scheme split` to push highlight to a non-yellow anchor.

**Variant `glow` looks blurry on a high-contrast or pastel theme.**
Glow needs a saturated heading colour on a dark bg — `text-shadow: 0 0 8px currentColor` reads as fuzz when the heading is desaturated. Either drop the variant or use `--accent vivid` and `--bg dark`.

**The compare-A border on a light theme is hard to see.**
The script picks compare-A-border at L ≈ 0.85 (light) so it reads as a faint card edge, not a competing accent — this is intentional. If the user wants a bolder card edge, they can hand-edit `--compare-a-tint` to use the accent at higher alpha.

## Quality checklist

Before declaring done:
- [ ] CSS file exists at `--out` path and starts with `/* @theme  */`.
- [ ] Contrast report shows `OK` on every critical pair (heading, body, em, link, highlight, inline-code, TL;DR).
- [ ] At least one smoke render produced — open slides 1 and a comparison slide and read the PNG.
- [ ] Title-cover h1 is the most-saturated/most-attention-getting colour in the palette, not the heading colour.
- [ ] Compare-A and Compare-B accents are visually distinct (not both in the same hue range).
- [ ] No emoji in the CSS file unless the user explicitly asked.
- [ ] Theme registered in the appropriate `.marprc.yml` (project-local or user-global, depending on `--out`).

## Pitfalls

- **Light themes are harder than dark themes.** Yellow, cyan, and magenta all sit at high L by default — pushing them down to meet 4.5:1 against white may shift them visibly toward olive, teal, or burgundy. That's not a bug, that's WCAG. If brand fidelity is more important than accessibility for a particular slot, the user has to make that trade-off explicitly.
- **The script reads `/marp-slides/layouts.css` at run time.** If that file is missing (e.g. the user runs the script in an isolated checkout), the CSS will be palette-only and won't render layout classes correctly. The script warns to stderr and proceeds.
- **HSL is not perceptually uniform.** The iteration loop is mathematically correct for WCAG-2 contrast, but equal HSL lightness does not always look equally bright across hues. Smoke-render the result and adjust by hand if a brand-critical colour looks wrong.
- **Variant + scheme interaction matters.** `glow` on `mono` looks weak (low saturation = weak shadow). `uppercase-divider` on `triadic` competes with already-loud accents. `auto` variant picks try to honour these but the user can always override.
- **Smoke render is the only catch for muddy palettes.** A theme can pass all contrast checks and still look bad if Compare-A and Compare-B are too close in hue. Single-input split-complementary schemes usually work; analogous schemes can read as one-tone.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [RanaCM](https://github.com/RanaCM)
- **Source:** [RanaCM/marpsmith](https://github.com/RanaCM/marpsmith)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-ranacm-marpsmith-theme-generator
- Seller: https://agentstack.voostack.com/s/ranacm
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
