Install
$ agentstack add skill-ranacm-marpsmith-theme-generator ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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-slidesskill. - Critiquing colour-blindness / APCA / vision impairment beyond WCAG AA →
deck-a11y-auditskill. - Re-running auto-fit / split passes after theme swap →
marp-slidesskill (theme swap shouldn't trigger them since the layout block is identical).
Workflow
- Collect inputs from the user:
- Required: at least one brand hex (e.g.
#E63946). Accept 1, 2, or 3 brand colors. - Required: a
--slugfor the theme (kebab-case; becomes both the filename and the@themedirective). - 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. Defaultvivid. - 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.
- 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.
- Inspect the contrast report. Every critical pair should pass
OK(≥ 4.5:1, WCAG AA normal text). If anything reportsLARGE-ONLYorFAIL, see "Troubleshooting" below.
- 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.
- Smoke-test by rendering an existing deck. Pick the user's most-recent deck or a canonical one (
lemborexant-drug-interaction.slides-dark.mdor any of the workspace's*.slides-*.mdfiles), copy it to a tmp path, swap thetheme:line in frontmatter to the new slug, and render withmarp:
``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.
- 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— 16pxborder-radiuson.compare-grid > divand.matrix > div. Reads as friendly/modern.uppercase-divider— uppercase + 0.08em letter-spacing + bottom border onsection.section-dividerheadings. 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: noneon 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 h1colour (the "cover title" — typically the most attention-getting accent).section.section-divider :is(h1, h2, h3)colour.section.prose-with-tldr blockquoteborder + bg + text + strong colour.section.big-stat-heroheading +.statcolour.section.pull-quoteglyph + body + h1-inside + attribution colours.section.process-flow ol li::beforebg + fg.section.comparison .compare-grid > div:first-child / :last-childborder + 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.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.