Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,39 @@ Any command that produces a scrollable page supports `--slides` to generate a sl

https://github.com/user-attachments/assets/342d3558-5fcf-4fb2-bc03-f0dd5b9e35dc

## Themes

You can set a preferred color palette so every generated page uses consistent colors. Create a config file in your project:

```bash
# .claude/visual-explainer.local.md
---
theme: dracula
---
```

The agent reads this file and applies the theme's colors instead of picking its own. Fonts, layout, and animations are still chosen freely — only the color palette is fixed.

**Available themes:**

| Theme | Style | Primary mode |
|-------|-------|-------------|
| `dracula` | Purple/pink/cyan on cool dark | Dark |
| `nord` | Arctic frost blues with aurora accents | Dark |
| `one-dark` | Atom's blue/green/purple palette | Dark |
| `catppuccin-mocha` | Soothing pastels on warm dark | Dark |
| `tokyo-night` | Downtown Tokyo blues and purples | Dark |
| `gruvbox-dark` | Retro groove warm oranges and greens | Dark |
| `synthwave-84` | Neon pink/cyan/yellow on deep purple | Dark |
| `solarized-light` | Ethan Schoonover's precise CIELAB palette | Light |
| `github-light` | GitHub Primer clean blues and greens | Light |
| `catppuccin-latte` | Soothing pastels on warm light | Light |
| `gruvbox-light` | Retro groove faded earth tones | Light |

Every theme includes both light and dark mode support via `prefers-color-scheme`. Every generated page includes an interactive theme picker with all 11 themes.

**Adding custom themes:** Create a new file in `themes/<name>.md` following the same structure as existing themes. Define CSS custom properties (using the same variable names) and Mermaid themeVariables.

## How It Works

```
Expand All @@ -106,6 +139,9 @@ plugins/
│ ├── libraries.md (Mermaid, Chart.js, fonts)
│ ├── responsive-nav.md (sticky TOC for multi-section pages)
│ └── slide-patterns.md (slide engine, transitions, presets)
├── themes/ ← 11 pre-built color themes
│ ├── dracula.md, nord.md, one-dark.md, ...
│ └── (add custom themes here)
├── templates/ ← reference templates with different palettes
│ ├── architecture.html
│ ├── mermaid-flowchart.html
Expand All @@ -122,7 +158,6 @@ The skill routes to the right approach automatically: Mermaid for flowcharts and
## Limitations

- Requires a browser to view
- Switching OS theme requires a page refresh for Mermaid SVGs
- Results vary by model capability

## Credits
Expand Down
90 changes: 90 additions & 0 deletions plugins/visual-explainer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,95 @@ For prose accents, see "Prose Page Elements" in `./references/css-patterns.md`.

Vary the choice each time. If the last diagram was dark and technical, make the next one light and editorial. The swap test: if you replaced your styling with a generic dark theme and nobody would notice the difference, you haven't designed anything.

**Interactive theme picker (always include).** Every generated page must include an interactive theme picker — a fixed row of 11 colored circle buttons (one per theme) that swap CSS custom properties on `<html>` and re-render Mermaid diagrams live. Read all 11 theme files from `./themes/` to extract the exact CSS variable values for each. The active theme button gets a white ring (`outline: 2px solid #fff`). Pick one theme as the default on load.

Available themes (read from `./themes/<name>.md`): `dracula`, `nord`, `one-dark`, `catppuccin-mocha`, `tokyo-night`, `gruvbox-dark`, `synthwave-84`, `solarized-light`, `github-light`, `catppuccin-latte`, `gruvbox-light`.

**Implementation pattern:**
```js
const themes = {
'tokyo-night': { '--bg': '#1a1b26', '--surface': '#24283b', /* ... all vars */ },
// ... one entry per theme, values from theme files
};
const mermaidThemeVars = {
'tokyo-night': { background: '#1a1b26', primaryColor: '...', /* ... */ },
// ...
};

function applyTheme(name) {
const vars = themes[name];
Object.entries(vars).forEach(([k, v]) => document.documentElement.style.setProperty(k, v));
document.querySelectorAll('.theme-btn').forEach(b =>
b.style.outline = b.dataset.theme === name ? '2px solid #fff' : 'none');
// Re-render Mermaid diagrams (read from data-source, NOT el.textContent —
// after first render el.textContent is SVG, not the diagram source)
mermaid.initialize({ startOnLoad: false, theme: 'base', themeVariables: mermaidThemeVars[name] });
for (const el of document.querySelectorAll('.mermaid')) {
el.removeAttribute('data-processed');
const { svg } = await mermaid.render(el.id + '-svg', el.dataset.source);
el.innerHTML = svg;
}
}

// Initial render — must save source to data-source BEFORE replacing innerHTML
async function renderMermaid() {
const themeKey = getCurrentTheme();
initMermaid(themeKey);
for (const el of document.querySelectorAll('.mermaid')) {
if (!el.dataset.source) el.dataset.source = el.textContent; // save once
// Mermaid 11 uses htmlLabels (foreignObject) — literal \n in source is NOT
// interpreted as a line break. Convert to <br/> before render.
const src = el.dataset.source.replaceAll('\\n', '<br/>');
const { svg } = await mermaid.render(el.id + '-svg', src);
el.innerHTML = svg;
}
}
```

**Critical:** Always save `el.textContent` to `el.dataset.source` before the first render. After render, `el.textContent` becomes SVG text — if re-render reads that instead of the original source, Mermaid throws "Syntax error in text".

**`\n` in node labels:** Mermaid 11 renders flowchart labels as HTML inside `<foreignObject>`. Literal `\n` (two chars: backslash + n) in the diagram source is NOT converted to a line break — it appears as `\n` in the output. Always preprocess: `src.replaceAll('\\n', '<br/>')` before passing to `mermaid.render()`. Place the picker in a fixed header bar or sticky top strip; keep it out of the document flow so it doesn't break page layout.

**Static config override.** If `.claude/visual-explainer.local.md` has a `theme:` field, use that theme as the default selected theme in the picker (not as a static override — the picker is always present).

**Font pair picker (always include).** Every generated page must also include a font pair switcher — a row of small `Aa` chips (each rendered in its respective display font) that swap `--font-sans` and `--font-mono` CSS variables on `<html>` and re-render Mermaid diagrams live. Place it in the same fixed panel as the theme picker, below the theme dots.

Use `--font-sans` and `--font-mono` CSS variables throughout: `body { font-family: var(--font-sans), system-ui, sans-serif; }` and all monospace elements `font-family: var(--font-mono), monospace`. Pass the current `--font-mono` value into Mermaid's `fontFamily` themeVariable: `getComputedStyle(document.documentElement).getPropertyValue('--font-mono').trim()`.

Default font pairs to offer (load all via a single Google Fonts URL). List the default first in the HTML so it occupies the top-left chip position:
- **Outfit / Cascadia Code** — clean, modern, excellent code readability **(default)**
- **Space Grotesk / Fira Code** — rounded, friendly
- **IBM Plex Sans / IBM Plex Mono** — systematic, professional
- **Fraunces / JetBrains Mono** — editorial serif
- **DM Sans / DM Mono** — minimal, clean
- **Syne / JetBrains Mono** — geometric, technical

Set matching defaults in `:root`: `--font-sans: 'Outfit'; --font-mono: 'Cascadia Code';`

Use Google Fonts only — never system fonts like Consolas (Windows-only). Cascadia Code is the preferred developer mono and is available on Google Fonts.

```js
const fontPairs = {
'cascadia': { sans: "'Outfit'", mono: "'Cascadia Code'" },
'space-grotesk': { sans: "'Space Grotesk'", mono: "'Fira Code'" },
'ibm': { sans: "'IBM Plex Sans'", mono: "'IBM Plex Mono'" },
'fraunces': { sans: "'Fraunces'", mono: "'JetBrains Mono'" },
'dm': { sans: "'DM Sans'", mono: "'DM Mono'" },
'syne': { sans: "'Syne'", mono: "'JetBrains Mono'" },
};

document.querySelectorAll('.font-opt').forEach(opt => {
opt.addEventListener('click', function() {
const pair = fontPairs[this.dataset.font];
document.documentElement.style.setProperty('--font-sans', pair.sans);
document.documentElement.style.setProperty('--font-mono', pair.mono);
document.querySelectorAll('.font-opt').forEach(o => o.classList.remove('active'));
this.classList.add('active');
setTimeout(renderMermaid, 100); // re-render with new fontFamily
});
});
```

### 2. Structure

**Read the reference material** before generating. Don't memorize it — read it each time to absorb the patterns.
Expand Down Expand Up @@ -398,6 +487,7 @@ See `./commands/share.md` for the `/share` command template.
Before delivering, verify:
- **The squint test**: Blur your eyes. Can you still perceive hierarchy? Are sections visually distinct?
- **The swap test**: Would replacing your fonts and colors with a generic dark theme make this indistinguishable from a template? If yes, push the aesthetic further.
- **Theme picker present**: Every page must have the interactive 11-theme picker. Verify buttons render and switching works (CSS vars swap, Mermaid re-renders).
- **Both themes**: Toggle your OS between light and dark mode. Both should look intentional, not broken.
- **Information completeness**: Does the diagram actually convey what the user asked for? Pretty but incomplete is a failure.
- **No overflow**: Resize the browser to different widths. No content should clip or escape its container. Every grid and flex child needs `min-width: 0`. Side-by-side panels need `overflow-wrap: break-word`. Never use `display: flex` on `<li>` for marker characters — it creates anonymous flex items that can't shrink, causing lines with many inline `<code>` badges to overflow. Use absolute positioning for markers instead. See the Overflow Protection section in `./references/css-patterns.md`.
Expand Down
2 changes: 2 additions & 0 deletions plugins/visual-explainer/references/css-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Reusable patterns for layout, connectors, theming, and visual effects in self-co

Always define both light and dark palettes via custom properties. Start with whichever fits the chosen aesthetic, ensure both work.

If a theme is active (configured in `.claude/visual-explainer.local.md`), replace the example palette below with the theme's CSS custom properties. The variable names are identical, so all patterns in this file work unchanged. Read the theme file from `./themes/<theme-name>.md` for the exact values.

```css
:root {
--font-body: 'Outfit', system-ui, sans-serif;
Expand Down
90 changes: 90 additions & 0 deletions plugins/visual-explainer/themes/catppuccin-latte.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Catppuccin Latte

Light-first theme based on [Catppuccin Latte](https://catppuccin.com/palette/). Soothing pastel colors on a warm, light background. The lightest of the four Catppuccin flavors.

## CSS Custom Properties

Primary mode is light. Dark mode via `@media (prefers-color-scheme: dark)`.

```css
:root {
--bg: #eff1f5;
--surface: #ccd0da;
--surface-elevated: #ffffff;
--border: rgba(0, 0, 0, 0.08);
--border-bright: rgba(0, 0, 0, 0.15);
--text: #4c4f69;
--text-dim: #9ca0b0;
--accent: #8839ef;
--accent-dim: rgba(136, 57, 239, 0.10);

--node-a: #1e66f5;
--node-a-dim: rgba(30, 102, 245, 0.10);
--node-b: #40a02b;
--node-b-dim: rgba(64, 160, 43, 0.10);
--node-c: #ea76cb;
--node-c-dim: rgba(234, 118, 203, 0.10);

--green: #40a02b;
--green-dim: rgba(64, 160, 43, 0.10);
--red: #d20f39;
--red-dim: rgba(210, 15, 57, 0.10);
--orange: #fe640b;
--orange-dim: rgba(254, 100, 11, 0.10);
}

@media (prefers-color-scheme: dark) {
:root {
--bg: #1e1e2e;
--surface: #313244;
--surface-elevated: #45475a;
--border: rgba(255, 255, 255, 0.06);
--border-bright: rgba(255, 255, 255, 0.12);
--text: #cdd6f4;
--text-dim: #6c7086;
--accent: #cba6f7;
--accent-dim: rgba(203, 166, 247, 0.12);

--node-a: #89b4fa;
--node-a-dim: rgba(137, 180, 250, 0.12);
--node-b: #a6e3a1;
--node-b-dim: rgba(166, 227, 161, 0.12);
--node-c: #f5c2e7;
--node-c-dim: rgba(245, 194, 231, 0.12);

--green: #a6e3a1;
--green-dim: rgba(166, 227, 161, 0.12);
--red: #f38ba8;
--red-dim: rgba(243, 139, 168, 0.12);
--orange: #fab387;
--orange-dim: rgba(250, 179, 135, 0.12);
}
}
```

## Mermaid themeVariables

Use with `theme: 'base'` in Mermaid configuration.

```json
{
"primaryColor": "#ccd0da",
"primaryTextColor": "#4c4f69",
"primaryBorderColor": "#9ca0b0",
"secondaryColor": "#8839ef",
"secondaryTextColor": "#eff1f5",
"secondaryBorderColor": "#8839ef",
"tertiaryColor": "#eff1f5",
"tertiaryTextColor": "#4c4f69",
"tertiaryBorderColor": "#9ca0b0",
"lineColor": "#9ca0b0",
"textColor": "#4c4f69",
"mainBkg": "#ccd0da",
"nodeBorder": "#9ca0b0",
"clusterBkg": "#eff1f5",
"clusterBorder": "#9ca0b0",
"titleColor": "#4c4f69",
"edgeLabelBackground": "#eff1f5",
"nodeTextColor": "#4c4f69"
}
```
90 changes: 90 additions & 0 deletions plugins/visual-explainer/themes/catppuccin-mocha.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Catppuccin Mocha

Dark-first theme based on [Catppuccin Mocha](https://catppuccin.com/palette/). Soothing pastel colors on a deep, warm dark background. The darkest of the four Catppuccin flavors.

## CSS Custom Properties

Primary mode is dark. Light mode via `@media (prefers-color-scheme: light)`.

```css
:root {
--bg: #1e1e2e;
--surface: #313244;
--surface-elevated: #45475a;
--border: rgba(255, 255, 255, 0.06);
--border-bright: rgba(255, 255, 255, 0.12);
--text: #cdd6f4;
--text-dim: #6c7086;
--accent: #cba6f7;
--accent-dim: rgba(203, 166, 247, 0.12);

--node-a: #89b4fa;
--node-a-dim: rgba(137, 180, 250, 0.12);
--node-b: #a6e3a1;
--node-b-dim: rgba(166, 227, 161, 0.12);
--node-c: #f5c2e7;
--node-c-dim: rgba(245, 194, 231, 0.12);

--green: #a6e3a1;
--green-dim: rgba(166, 227, 161, 0.12);
--red: #f38ba8;
--red-dim: rgba(243, 139, 168, 0.12);
--orange: #fab387;
--orange-dim: rgba(250, 179, 135, 0.12);
}

@media (prefers-color-scheme: light) {
:root {
--bg: #eff1f5;
--surface: #ccd0da;
--surface-elevated: #ffffff;
--border: rgba(0, 0, 0, 0.08);
--border-bright: rgba(0, 0, 0, 0.15);
--text: #4c4f69;
--text-dim: #9ca0b0;
--accent: #8839ef;
--accent-dim: rgba(136, 57, 239, 0.10);

--node-a: #1e66f5;
--node-a-dim: rgba(30, 102, 245, 0.10);
--node-b: #40a02b;
--node-b-dim: rgba(64, 160, 43, 0.10);
--node-c: #ea76cb;
--node-c-dim: rgba(234, 118, 203, 0.10);

--green: #40a02b;
--green-dim: rgba(64, 160, 43, 0.10);
--red: #d20f39;
--red-dim: rgba(210, 15, 57, 0.10);
--orange: #fe640b;
--orange-dim: rgba(254, 100, 11, 0.10);
}
}
```

## Mermaid themeVariables

Use with `theme: 'base'` in Mermaid configuration.

```json
{
"primaryColor": "#313244",
"primaryTextColor": "#cdd6f4",
"primaryBorderColor": "#6c7086",
"secondaryColor": "#cba6f7",
"secondaryTextColor": "#1e1e2e",
"secondaryBorderColor": "#cba6f7",
"tertiaryColor": "#1e1e2e",
"tertiaryTextColor": "#cdd6f4",
"tertiaryBorderColor": "#6c7086",
"lineColor": "#6c7086",
"textColor": "#cdd6f4",
"mainBkg": "#313244",
"nodeBorder": "#6c7086",
"clusterBkg": "#1e1e2e",
"clusterBorder": "#6c7086",
"titleColor": "#cdd6f4",
"edgeLabelBackground": "#1e1e2e",
"nodeTextColor": "#cdd6f4"
}
```
Loading